From: downsj Date: Sat, 7 Sep 1996 21:40:23 +0000 (+0000) Subject: Initial import of vim 4.2. X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=358dd6fc396a2e5e85be0789638e306cd82e8587;p=openbsd Initial import of vim 4.2. This is meant to replace nvi in the tree. Vim, in general, works better, provides more features, and does not suffer from the license problems being imposed upon nvi. On the other hand, vim lacks a non-visual ex mode, in addition to open mode. This includes the GUI (X11) code, but doesn't try to compile it. --- diff --git a/usr.bin/vim/Makefile b/usr.bin/vim/Makefile new file mode 100644 index 00000000000..41e237618c5 --- /dev/null +++ b/usr.bin/vim/Makefile @@ -0,0 +1,50 @@ +# $OpenBSD: Makefile,v 1.1.1.1 1996/09/07 21:40:27 downsj Exp $ + +.PATH: ${.CURDIR}/doc + +CFLAGS+=-I${.CURDIR} -I${.OBJDIR} -DHAVE_CONFIG_H + +LDADD+= -ltermlib +DPADD+= ${LIBTERMLIB} +PROG= vim + +SRCS= alloc.c buffer.c charset.c cmdcmds.c cmdline.c csearch.c \ + digraph.c edit.c fileio.c getchar.c help.c linefunc.c main.c mark.c \ + memfile.c memline.c message.c misccmds.c normal.c ops.c option.c \ + pathdef.c quickfix.c regexp.c regsub.c screen.c search.c tables.c \ + tag.c term.c undo.c unix.c version.c window.c + +DOCS= vim_40.txt vim_ami.txt vim_arch.txt vim_diff.txt vim_digr.txt \ + vim_dos.txt vim_gui.txt vim_help.txt vim_idx.txt vim_kcc.txt \ + vim_mac.txt vim_menu.txt vim_mint.txt vim_os2.txt vim_ref.txt \ + vim_rlh.txt vim_tips.txt vim_unix.txt vim_w32.txt vim_win.txt + +# I haven't tried this, but it's a start... +# Use the following to build Motif vim: +#SRCS+= gui.c gui_motif.c gui_x11.c pty_openbsd.c +#LDADD+= -L/some/where/motif/lib -L/some/where/X11/lib -lXm -lXt -lutil +#CFLAGS+= -I/some/where/motif/include -I/some/where/X11/include -DHAVE_X11 -DUSE_GUI_MOTIF +# Use the following to build Athena vim: +#SRCS+= gui.c gui_athena.c gui_x11.c pty_openbsd.c gui_at_sb.c +#LDADD+= -L/some/where/X11/lib -lXaw -lXt -lXmu -lutil +#CFLAGS+= -I/some/where/X11/include -DHAVE_X11 -DUSE_GUI_ATHENA + +CLEANFILES+= mkcmdtab cmdtab.h + +.depend cmdline.o: cmdtab.h + +cmdtab.h: mkcmdtab cmdtab.tab + ${.OBJDIR}/mkcmdtab ${.CURDIR}/cmdtab.tab ${.OBJDIR}/cmdtab.h + +mkcmdtab: mkcmdtab.o + ${CC} mkcmdtab.o -o mkcmdtab + +afterinstall: + install -d -m 555 -o ${BINOWN} -g ${BINGRP} ${DESTDIR}/usr/share/vim + @cd ${.CURDIR}/doc; for i in ${DOCS}; do \ + cmp -s $$i ${DESTDIR}/usr/share/vim/$$i || \ + install -c -m 444 -o ${BINOWN} -g ${BINGRP} $$i \ + ${DESTDIR}/usr/share/vim; done + +.include +.include "../Makefile.inc" diff --git a/usr.bin/vim/alloc.c b/usr.bin/vim/alloc.c new file mode 100644 index 00000000000..8387546dc61 --- /dev/null +++ b/usr.bin/vim/alloc.c @@ -0,0 +1,382 @@ +/* $OpenBSD: alloc.c,v 1.1.1.1 1996/09/07 21:40:23 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * alloc.c + * + * This file contains various routines dealing with allocation and + * deallocation of memory. And some funcions for copying text. + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" + +/* + * Some memory is reserved for error messages and for being able to + * call mf_release_all(), which needs some memory for mf_trans_add(). + */ +#define KEEP_ROOM 8192L + +/* + * Note: if unsinged is 16 bits we can only allocate up to 64K with alloc(). + * Use lalloc for larger blocks. + */ + char_u * +alloc(size) + unsigned size; +{ + return (lalloc((long_u)size, TRUE)); +} + +/* + * alloc() with check for maximum line length + */ + char_u * +alloc_check(size) + unsigned size; +{ +#if !defined(UNIX) && !defined(__EMX__) + if (sizeof(int) == 2 && size > 0x7fff) + { + EMSG("Line is becoming too long"); + return NULL; + } +#endif + return (lalloc((long_u)size, TRUE)); +} + + char_u * +lalloc(size, message) + long_u size; + int message; +{ + register char_u *p; /* pointer to new storage space */ + static int releasing = FALSE; /* don't do mf_release_all() recursive */ + int try_again; + + if (size <= 0) + { + EMSGN("Internal error: lalloc(%ld, )", size); + return NULL; + } +#if defined(MSDOS) && !defined(DJGPP) + if (size >= 0xfff0) /* in MSDOS we can't deal with >64K blocks */ + p = NULL; + else +#endif + + /* + * If out of memory, try to release some memfile blocks. + * If some blocks are released call malloc again. + */ + for (;;) + { + if ((p = (char_u *)malloc(size)) != NULL) + { + if (mch_avail_mem(TRUE) < KEEP_ROOM && !releasing) + { /* System is low... no go! */ + vim_free((char *)p); + p = NULL; + } + } + /* + * Remember that mf_release_all() is being called to avoid an endless loop, + * because mf_release_all() may call alloc() recursively. + */ + if (p != NULL || releasing) + break; + releasing = TRUE; + try_again = mf_release_all(); + releasing = FALSE; + if (!try_again) + break; + } + + /* + * Avoid repeating the error message many times (they take 1 second each). + * Did_outofmem_msg is reset when a character is read. + */ + if (message && p == NULL) + do_outofmem_msg(); + return (p); +} + + void +do_outofmem_msg() +{ + if (!did_outofmem_msg) + { + emsg(e_outofmem); + did_outofmem_msg = TRUE; + } +} + +/* + * copy a string into newly allocated memory + */ + char_u * +strsave(string) + char_u *string; +{ + char_u *p; + + p = alloc((unsigned) (STRLEN(string) + 1)); + if (p != NULL) + STRCPY(p, string); + return p; +} + + char_u * +strnsave(string, len) + char_u *string; + int len; +{ + char_u *p; + + p = alloc((unsigned) (len + 1)); + if (p != NULL) + { + STRNCPY(p, string, len); + p[len] = NUL; + } + return p; +} + +/* + * Same as strsave(), but any characters found in esc_chars are preceded by a + * backslash. + */ + char_u * +strsave_escaped(string, esc_chars) + char_u *string; + char_u *esc_chars; +{ + char_u *p; + char_u *p2; + char_u *escaped_string; + unsigned length; + + /* + * First count the number of backslashes required. + * Then allocate the memory and insert them. + */ + length = 1; /* count the trailing '/' and NUL */ + for (p = string; *p; p++) + { + if (vim_strchr(esc_chars, *p) != NULL) + ++length; /* count a backslash */ + ++length; /* count an ordinary char */ + } + escaped_string = alloc(length); + if (escaped_string != NULL) + { + p2 = escaped_string; + for (p = string; *p; p++) + { + if (vim_strchr(esc_chars, *p) != NULL) + *p2++ = '\\'; + *p2++ = *p; + } + *p2 = NUL; + } + return escaped_string; +} + +/* + * copy a number of spaces + */ + void +copy_spaces(ptr, count) + char_u *ptr; + size_t count; +{ + register size_t i = count; + register char_u *p = ptr; + + while (i--) + *p++ = ' '; +} + +/* + * delete spaces at the end of a string + */ + void +del_trailing_spaces(ptr) + char_u *ptr; +{ + char_u *q; + + q = ptr + STRLEN(ptr); + while (--q > ptr && vim_iswhite(q[0]) && q[-1] != '\\' && + q[-1] != Ctrl('V')) + *q = NUL; +} + +/* + * Isolate one part of a string option where parts are separated with commas. + * The part is copied into buf[maxlen]. + * "*option" is advanced to the next part. + * The length is returned. + */ + int +copy_option_part(option, buf, maxlen, sep_chars) + char_u **option; + char_u *buf; + int maxlen; + char *sep_chars; +{ + int len = 0; + char_u *p = *option; + + /* skip '.' at start of option part, for 'suffixes' */ + if (*p == '.') + buf[len++] = *p++; + while (*p && vim_strchr((char_u *)sep_chars, *p) == NULL) + { + /* + * Skip backslash before a separator character and space. + */ + if (p[0] == '\\' && vim_strchr((char_u *)sep_chars, p[1]) != NULL) + ++p; + if (len < maxlen - 1) + buf[len++] = *p; + ++p; + } + buf[len] = NUL; + + p = skip_to_option_part(p); /* p points to next file name */ + + *option = p; + return len; +} + +/* + * replacement for free() that ignores NULL pointers + */ + void +vim_free(x) + void *x; +{ + if (x != NULL) + free(x); +} + +#ifndef HAVE_MEMSET + void * +vim_memset(ptr, c, size) + void *ptr; + int c; + size_t size; +{ + register char *p = ptr; + + while (size-- > 0) + *p++ = c; + return ptr; +} +#endif + +#ifdef VIM_MEMMOVE +/* + * Version of memmove that handles overlapping source and destination. + * For systems that don't have a function that is guaranteed to do that (SYSV). + */ + void +vim_memmove(dst_arg, src_arg, len) + void *src_arg, *dst_arg; + size_t len; +{ + /* + * A void doesn't have a size, we use char pointers. + */ + register char *dst = dst_arg, *src = src_arg; + + /* overlap, copy backwards */ + if (dst > src && dst < src + len) + { + src +=len; + dst +=len; + while (len-- > 0) + *--dst = *--src; + } + else /* copy forwards */ + while (len-- > 0) + *dst++ = *src++; +} +#endif + +/* + * compare two strings, ignoring case + * return 0 for match, 1 for difference + */ + int +vim_strnicmp(s1, s2, len) + char_u *s1; + char_u *s2; + size_t len; +{ + while (len) + { + if (TO_UPPER(*s1) != TO_UPPER(*s2)) + return 1; /* this character different */ + if (*s1 == NUL) + return 0; /* strings match until NUL */ + ++s1; + ++s2; + --len; + } + return 0; /* strings match */ +} + +/* + * Version of strchr() and strrchr() that handle unsigned char strings + * with characters above 128 correctly. Also it doesn't return a pointer to + * the NUL at the end of the string. + */ + char_u * +vim_strchr(string, n) + char_u *string; + int n; +{ + while (*string) + { + if (*string == n) + return string; + ++string; + } + return NULL; +} + + char_u * +vim_strrchr(string, n) + char_u *string; + int n; +{ + char_u *retval = NULL; + + while (*string) + { + if (*string == n) + retval = string; + ++string; + } + return retval; +} + +/* + * Vim has its own isspace() function, because on some machines isspace() + * can't handle characters above 128. + */ + int +vim_isspace(x) + int x; +{ + return ((x >= 9 && x <= 13) || x == ' '); +} diff --git a/usr.bin/vim/ascii.h b/usr.bin/vim/ascii.h new file mode 100644 index 00000000000..9f53c110f46 --- /dev/null +++ b/usr.bin/vim/ascii.h @@ -0,0 +1,33 @@ +/* $OpenBSD: ascii.h,v 1.1.1.1 1996/09/07 21:40:27 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * Definitions of various common control characters + */ + +#define NUL '\000' +#define TAB '\011' +#define NL '\012' +#define NL_STR (char_u *)"\012" +#define CR '\015' +#define ESC '\033' +#define ESC_STR (char_u *)"\033" +#define DEL 0x7f +#define CSI 0x9b + +#define Ctrl(x) ((x) & 0x1f) +#define Meta(x) ((x) | 0x80) + +/* + * Character that separates dir names in a path. + * For MS-DOS and WIN32 we also use the slash, the backslash causes trouble in + * a command line and the slash works fine. + */ +#define PATHSEP '/' +#define PATHSEPSTR "/" diff --git a/usr.bin/vim/buffer.c b/usr.bin/vim/buffer.c new file mode 100644 index 00000000000..5673ed6a976 --- /dev/null +++ b/usr.bin/vim/buffer.c @@ -0,0 +1,1699 @@ +/* $OpenBSD: buffer.c,v 1.1.1.1 1996/09/07 21:40:27 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * buffer.c: functions for dealing with the buffer structure + */ + +/* + * The buffer list is a double linked list of all buffers. + * Each buffer can be in one of these states: + * never loaded: b_neverloaded == TRUE, only the file name is valid + * not loaded: b_ml.ml_mfp == NULL, no memfile allocated + * hidden: b_nwindows == 0, loaded but not displayed in a window + * normal: loaded and displayed in a window + * + * Instead of storing file names all over the place, each file name is + * stored in the buffer list. It can be referenced by a number. + * + * The current implementation remembers all file names ever used. + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +static void enter_buffer __ARGS((BUF *)); +static void free_buf_options __ARGS((BUF *)); +static char_u *buflist_match __ARGS((regexp *prog, BUF *buf)); +static void buflist_setlnum __ARGS((BUF *, linenr_t)); +static linenr_t buflist_findlnum __ARGS((BUF *)); +static void append_arg_number __ARGS((char_u *, int)); + +/* + * Open current buffer, that is: open the memfile and read the file into memory + * return FAIL for failure, OK otherwise + */ + int +open_buffer() +{ + int retval = OK; + + /* + * The 'readonly' flag is only set when b_neverloaded is being reset. + * When re-entering the same buffer, it should not change, because the + * user may have reset the flag by hand. + */ + if (readonlymode && curbuf->b_filename != NULL && curbuf->b_neverloaded) + curbuf->b_p_ro = TRUE; + + if (ml_open() == FAIL) + { + /* + * There MUST be a memfile, otherwise we can't do anything + * If we can't create one for the current buffer, take another buffer + */ + close_buffer(NULL, curbuf, FALSE, FALSE); + for (curbuf = firstbuf; curbuf != NULL; curbuf = curbuf->b_next) + if (curbuf->b_ml.ml_mfp != NULL) + break; + /* + * if there is no memfile at all, exit + * This is OK, since there are no changes to loose. + */ + if (curbuf == NULL) + { + EMSG("Cannot allocate buffer, exiting..."); + getout(2); + } + EMSG("Cannot allocate buffer, using other one..."); + enter_buffer(curbuf); + return FAIL; + } + if (curbuf->b_filename != NULL) + retval = readfile(curbuf->b_filename, curbuf->b_sfilename, + (linenr_t)0, TRUE, (linenr_t)0, MAXLNUM, FALSE); + else + { + MSG("Empty Buffer"); + msg_col = 0; + msg_didout = FALSE; /* overwrite this message whenever you like */ + } + + /* if first time loading this buffer, init chartab */ + if (curbuf->b_neverloaded) + init_chartab(); + + /* + * Reset the Changed flag first, autocmds may change the buffer. + * Apply the automatic commands, before processing the modelines. + * So the modelines have priority over auto commands. + */ + if (retval != FAIL) + UNCHANGED(curbuf); + +#ifdef AUTOCMD + apply_autocmds(EVENT_BUFENTER, NULL, NULL); +#endif + + if (retval != FAIL) + { + do_modelines(); + curbuf->b_neverloaded = FALSE; + } + + return retval; +} + +/* + * Close the link to a buffer. If "free_buf" is TRUE free the buffer if it + * becomes unreferenced. The caller should get a new buffer very soon! + * if 'del_buf' is TRUE, remove the buffer from the buffer list. + */ + void +close_buffer(win, buf, free_buf, del_buf) + WIN *win; /* if not NULL, set b_last_cursor */ + BUF *buf; + int free_buf; + int del_buf; +{ + if (buf->b_nwindows > 0) + --buf->b_nwindows; + if (buf->b_nwindows == 0 && win != NULL) + set_last_cursor(win); /* may set b_last_cursor */ + if (buf->b_nwindows > 0 || !free_buf) + { + if (buf == curbuf) + u_sync(); /* sync undo before going to another buffer */ + return; + } + + buf_freeall(buf); /* free all things allocated for this buffer */ + /* + * If there is no file name, remove the buffer from the list + */ + if (buf->b_filename == NULL || del_buf) + { + vim_free(buf->b_filename); + vim_free(buf->b_sfilename); + if (buf->b_prev == NULL) + firstbuf = buf->b_next; + else + buf->b_prev->b_next = buf->b_next; + if (buf->b_next == NULL) + lastbuf = buf->b_prev; + else + buf->b_next->b_prev = buf->b_prev; + free_buf_options(buf); + } + else + buf_clear(buf); +} + +/* + * buf_clear() - make buffer empty + */ + void +buf_clear(buf) + BUF *buf; +{ + buf->b_ml.ml_line_count = 1; + buf->b_changed = FALSE; +#ifndef SHORT_FNAME + buf->b_shortname = FALSE; +#endif + buf->b_p_eol = TRUE; + buf->b_ml.ml_mfp = NULL; + buf->b_ml.ml_flags = ML_EMPTY; /* empty buffer */ +} + +/* + * buf_freeall() - free all things allocated for the buffer + */ + void +buf_freeall(buf) + BUF *buf; +{ + u_blockfree(buf); /* free the memory allocated for undo */ + ml_close(buf, TRUE); /* close and delete the memline/memfile */ + buf->b_ml.ml_line_count = 0; /* no lines in buffer */ + u_clearall(buf); /* reset all undo information */ +} + +/* + * do_bufdel() - delete or unload buffer(s) + * + * addr_count == 0: ":bdel" - delete current buffer + * addr_count == 1: ":N bdel" or ":bdel N [N ..] - first delete + * buffer "end_bnr", then any other arguments. + * addr_count == 2: ":N,N bdel" - delete buffers in range + * + * command can be DOBUF_UNLOAD (":bunload") or DOBUF_DEL (":bdel") + * + * Returns error message or NULL + */ + char_u * +do_bufdel(command, arg, addr_count, start_bnr, end_bnr, forceit) + int command; + char_u *arg; /* pointer to extra arguments */ + int addr_count; + int start_bnr; /* first buffer number in a range */ + int end_bnr; /* buffer number or last buffer number in a range */ + int forceit; +{ + int do_current = 0; /* delete current buffer? */ + int deleted = 0; /* number of buffers deleted */ + char_u *errormsg = NULL; /* return value */ + int bnr; /* buffer number */ + char_u *p; + + if (addr_count == 0) + (void)do_buffer(command, DOBUF_CURRENT, FORWARD, 0, forceit); + else + { + if (addr_count == 2) + { + if (*arg) /* both range and argument is not allowed */ + return e_trailing; + bnr = start_bnr; + } + else /* addr_count == 1 */ + bnr = end_bnr; + + for ( ;!got_int; mch_breakcheck()) + { + /* + * delete the current buffer last, otherwise when the + * current buffer is deleted, the next buffer becomes + * the current one and will be loaded, which may then + * also be deleted, etc. + */ + if (bnr == curbuf->b_fnum) + do_current = bnr; + else if (do_buffer(command, DOBUF_FIRST, FORWARD, (int)bnr, + forceit) == OK) + ++deleted; + + /* + * find next buffer number to delete/unload + */ + if (addr_count == 2) + { + if (++bnr > end_bnr) + break; + } + else /* addr_count == 1 */ + { + arg = skipwhite(arg); + if (*arg == NUL) + break; + if (!isdigit(*arg)) + { + p = skiptowhite_esc(arg); + bnr = buflist_findpat(arg, p); + if (bnr < 0) /* failed */ + break; + arg = p; + } + else + bnr = getdigits(&arg); + } + } + if (!got_int && do_current && do_buffer(command, DOBUF_FIRST, + FORWARD, do_current, forceit) == OK) + ++deleted; + + if (deleted == 0) + { + sprintf((char *)IObuff, "No buffers were %s", + command == DOBUF_UNLOAD ? "unloaded" : "deleted"); + errormsg = IObuff; + } + else + smsg((char_u *)"%d buffer%s %s", deleted, + plural((long)deleted), + command == DOBUF_UNLOAD ? "unloaded" : "deleted"); + } + + return errormsg; +} + +/* + * Implementation of the command for the buffer list + * + * action == DOBUF_GOTO go to specified buffer + * action == DOBUF_SPLIT split window and go to specified buffer + * action == DOBUF_UNLOAD unload specified buffer(s) + * action == DOBUF_DEL delete specified buffer(s) + * + * start == DOBUF_CURRENT go to "count" buffer from current buffer + * start == DOBUF_FIRST go to "count" buffer from first buffer + * start == DOBUF_LAST go to "count" buffer from last buffer + * start == DOBUF_MOD go to "count" modified buffer from current buffer + * + * Return FAIL or OK. + */ + int +do_buffer(action, start, dir, count, forceit) + int action; + int start; + int dir; /* FORWARD or BACKWARD */ + int count; /* buffer number or number of buffers */ + int forceit; /* TRUE for :bdelete! */ +{ + BUF *buf; + BUF *delbuf; + int retval; + + switch (start) + { + case DOBUF_FIRST: buf = firstbuf; break; + case DOBUF_LAST: buf = lastbuf; break; + default: buf = curbuf; break; + } + if (start == DOBUF_MOD) /* find next modified buffer */ + { + while (count-- > 0) + { + do + { + buf = buf->b_next; + if (buf == NULL) + buf = firstbuf; + } + while (buf != curbuf && !buf->b_changed); + } + if (!buf->b_changed) + { + EMSG("No modified buffer found"); + return FAIL; + } + } + else if (start == DOBUF_FIRST && count) /* find specified buffer number */ + { + while (buf != NULL && buf->b_fnum != count) + buf = buf->b_next; + } + else + { + while (count-- > 0) + { + if (dir == FORWARD) + { + buf = buf->b_next; + if (buf == NULL) + buf = firstbuf; + } + else + { + buf = buf->b_prev; + if (buf == NULL) + buf = lastbuf; + } + } + } + + if (buf == NULL) /* could not find it */ + { + if (start == DOBUF_FIRST) + { + /* don't warn when deleting */ + if (action != DOBUF_UNLOAD && action != DOBUF_DEL) + EMSGN("Cannot go to buffer %ld", count); + } + else if (dir == FORWARD) + EMSG("Cannot go beyond last buffer"); + else + EMSG("Cannot go before first buffer"); + return FAIL; + } + + /* + * delete buffer buf from memory and/or the list + */ + if (action == DOBUF_UNLOAD || action == DOBUF_DEL) + { + if (!forceit && buf->b_changed) + { + EMSGN("No write since last change for buffer %ld (use ! to override)", + buf->b_fnum); + return FAIL; + } + + /* + * If deleting last buffer, make it empty. + * The last buffer cannot be unloaded. + */ + if (firstbuf->b_next == NULL) + { + if (action == DOBUF_UNLOAD) + { + EMSG("Cannot unload last buffer"); + return FAIL; + } + /* Close any other windows on this buffer */ + close_others(FALSE); + buf = curbuf; + setpcmark(); + retval = do_ecmd(0, NULL, NULL, NULL, FALSE, (linenr_t)1, FALSE); + /* + * The do_ecmd() may create a new buffer, then we have to delete + * the old one. But do_ecmd() may have done that already, check + * if the buffer still exists (it will be the first or second in + * the buffer list). + */ + if (buf != curbuf && (buf == firstbuf || buf == firstbuf->b_next)) + close_buffer(NULL, buf, TRUE, TRUE); + return retval; + } + + /* + * If the deleted buffer is the current one, close the current window + * (unless it's the only window). + */ + while (buf == curbuf && firstwin != lastwin) + close_window(curwin, FALSE); + + /* + * If the buffer to be deleted is not current one, delete it here. + */ + if (buf != curbuf) + { + close_windows(buf); + close_buffer(NULL, buf, TRUE, action == DOBUF_DEL); + return OK; + } + + /* + * Deleting the current buffer: Need to find another buffer to go to. + * There must be another, otherwise it would have been handled above. + */ + if (curbuf->b_next != NULL) + buf = curbuf->b_next; + else + buf = curbuf->b_prev; + } + + /* + * make buf current buffer + */ + setpcmark(); + if (action == DOBUF_SPLIT) /* split window first */ + { + if (win_split(0, FALSE) == FAIL) + return FAIL; + } + curwin->w_alt_fnum = curbuf->b_fnum; /* remember alternate file */ + buflist_altlnum(); /* remember curpos.lnum */ + +#ifdef AUTOCMD + apply_autocmds(EVENT_BUFLEAVE, NULL, NULL); +#endif + delbuf = curbuf; /* close_windows() may change curbuf */ + if (action == DOBUF_UNLOAD || action == DOBUF_DEL) + close_windows(curbuf); + close_buffer(NULL, delbuf, action == DOBUF_UNLOAD || action == DOBUF_DEL, + action == DOBUF_DEL); + enter_buffer(buf); + return OK; +} + +/* + * enter a new current buffer. + * (old curbuf must have been freed already) + */ + static void +enter_buffer(buf) + BUF *buf; +{ + buf_copy_options(curbuf, buf, TRUE); + curwin->w_buffer = buf; + curbuf = buf; + ++curbuf->b_nwindows; + if (curbuf->b_ml.ml_mfp == NULL) /* need to load the file */ + open_buffer(); + else + { + need_fileinfo = TRUE; /* display file info after redraw */ + buf_check_timestamp(curbuf); /* check if file has changed */ +#ifdef AUTOCMD + apply_autocmds(EVENT_BUFENTER, NULL, NULL); +#endif + } + buflist_getlnum(); /* restore curpos.lnum */ + check_arg_idx(); /* check for valid arg_idx */ + maketitle(); + scroll_cursor_halfway(FALSE); /* redisplay at correct position */ + updateScreen(NOT_VALID); +} + +/* + * functions for dealing with the buffer list + */ + +/* + * Add a file name to the buffer list. Return a pointer to the buffer. + * If the same file name already exists return a pointer to that buffer. + * If it does not exist, or if fname == NULL, a new entry is created. + * If use_curbuf is TRUE, may use current buffer. + * This is the ONLY way to create a new buffer. + */ + BUF * +buflist_new(fname, sfname, lnum, use_curbuf) + char_u *fname; + char_u *sfname; + linenr_t lnum; + int use_curbuf; +{ + static int top_file_num = 1; /* highest file number */ + BUF *buf; + + fname_expand(&fname, &sfname); + +/* + * If file name already exists in the list, update the entry + */ + if (fname != NULL && (buf = buflist_findname(fname)) != NULL) + { + if (lnum != 0) + buflist_setlnum(buf, lnum); + /* copy the options now, if 'cpo' doesn't have 's' and not done + * already */ + buf_copy_options(curbuf, buf, FALSE); + return buf; + } + +/* + * If the current buffer has no name and no contents, use the current buffer. + * Otherwise: Need to allocate a new buffer structure. + * + * This is the ONLY place where a new buffer structure is allocated! + */ + if (use_curbuf && curbuf != NULL && curbuf->b_filename == NULL && + curbuf->b_nwindows <= 1 && + (curbuf->b_ml.ml_mfp == NULL || bufempty())) + buf = curbuf; + else + { + buf = (BUF *)alloc((unsigned)sizeof(BUF)); + if (buf == NULL) + return NULL; + (void)vim_memset(buf, 0, sizeof(BUF)); + } + + if (fname != NULL) + { + buf->b_filename = strsave(fname); + buf->b_sfilename = strsave(sfname); + } + if (buf->b_winlnum == NULL) + buf->b_winlnum = (WINLNUM *)alloc((unsigned)sizeof(WINLNUM)); + if ((fname != NULL && (buf->b_filename == NULL || + buf->b_sfilename == NULL)) || buf->b_winlnum == NULL) + { + vim_free(buf->b_filename); + buf->b_filename = NULL; + vim_free(buf->b_sfilename); + buf->b_sfilename = NULL; + if (buf != curbuf) + { + vim_free(buf->b_winlnum); + free_buf_options(buf); + } + return NULL; + } + + if (buf == curbuf) + { + buf_freeall(buf); /* free all things allocated for this buffer */ + buf->b_nwindows = 0; + } + else + { + /* + * Copy the options from the current buffer. + */ + buf_copy_options(curbuf, buf, FALSE); + + /* + * put new buffer at the end of the buffer list + */ + buf->b_next = NULL; + if (firstbuf == NULL) /* buffer list is empty */ + { + buf->b_prev = NULL; + firstbuf = buf; + } + else /* append new buffer at end of list */ + { + lastbuf->b_next = buf; + buf->b_prev = lastbuf; + } + lastbuf = buf; + + buf->b_fnum = top_file_num++; + if (top_file_num < 0) /* wrap around (may cause duplicates) */ + { + EMSG("Warning: List of file names overflow"); + mch_delay(3000L, TRUE); /* make sure it is noticed */ + top_file_num = 1; + } + + buf->b_winlnum->wl_lnum = lnum; + buf->b_winlnum->wl_next = NULL; + buf->b_winlnum->wl_prev = NULL; + buf->b_winlnum->wl_win = curwin; + } + + if (did_cd) + buf->b_xfilename = buf->b_filename; + else + buf->b_xfilename = buf->b_sfilename; + buf->b_u_synced = TRUE; + buf->b_neverloaded = TRUE; + buf_clear(buf); + clrallmarks(buf); /* clear marks */ + fmarks_check_names(buf); /* check file marks for this file */ + + return buf; +} + +/* + * Free the memory for a BUF structure and its options + */ + static void +free_buf_options(buf) + BUF *buf; +{ + free_string_option(buf->b_p_fo); + free_string_option(buf->b_p_isk); + free_string_option(buf->b_p_com); +#ifdef CINDENT + free_string_option(buf->b_p_cink); + free_string_option(buf->b_p_cino); +#endif +#if defined(CINDENT) || defined(SMARTINDENT) + free_string_option(buf->b_p_cinw); +#endif + vim_free(buf); +} + +/* + * get alternate file n + * set linenr to lnum or altlnum if lnum == 0 + * if (options & GETF_SETMARK) call setpcmark() + * if (options & GETF_ALT) we are jumping to an alternate file. + * + * return FAIL for failure, OK for success + */ + int +buflist_getfile(n, lnum, options) + int n; + linenr_t lnum; + int options; +{ + BUF *buf; + + buf = buflist_findnr(n); + if (buf == NULL) + { + if ((options & GETF_ALT) && n == 0) + emsg(e_noalt); + else + EMSGN("buffer %ld not found", n); + return FAIL; + } + + /* if alternate file is the current buffer, nothing to do */ + if (buf == curbuf) + return OK; + + /* altlnum may be changed by getfile(), get it now */ + if (lnum == 0) + lnum = buflist_findlnum(buf); + ++RedrawingDisabled; + if (getfile(buf->b_fnum, NULL, NULL, (options & GETF_SETMARK), lnum) <= 0) + { + --RedrawingDisabled; + return OK; + } + --RedrawingDisabled; + return FAIL; +} + +/* + * go to the last know line number for the current buffer + */ + void +buflist_getlnum() +{ + linenr_t lnum; + + curwin->w_cursor.lnum = 1; + curwin->w_cursor.col = 0; + lnum = buflist_findlnum(curbuf); + if (lnum != 0 && lnum <= curbuf->b_ml.ml_line_count) + curwin->w_cursor.lnum = lnum; +} + +/* + * find file in buffer list by name (it has to be for the current window) + * 'fname' must have a full path. + */ + BUF * +buflist_findname(fname) + char_u *fname; +{ + BUF *buf; + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + if (buf->b_filename != NULL && fnamecmp(fname, buf->b_filename) == 0) + return (buf); + return NULL; +} + +/* + * Find file in buffer list by a regexppattern. + * Return fnum of the found buffer, < 0 for error. + */ + int +buflist_findpat(pattern, pattern_end) + char_u *pattern; + char_u *pattern_end; /* pointer to first char after pattern */ +{ + BUF *buf; + regexp *prog; + int fnum = -1; + char_u *pat; + char_u *match; + int attempt; + char_u *p; + + if (pattern_end == pattern + 1 && (*pattern == '%' || *pattern == '#')) + { + if (*pattern == '%') + fnum = curbuf->b_fnum; + else + fnum = curwin->w_alt_fnum; + } + + /* + * Try four ways of matching: + * attempt == 0: without '^' or '$' (at any position) + * attempt == 1: with '^' at start (only at postion 0) + * attempt == 2: with '$' at end (only match at end) + * attempt == 3: with '^' at start and '$' at end (only full match) + */ + else for (attempt = 0; attempt <= 3; ++attempt) + { + /* may add '^' and '$' */ + pat = file_pat_to_reg_pat(pattern, pattern_end, NULL); + if (pat == NULL) + return -1; + if (attempt < 2) + { + p = pat + STRLEN(pat) - 1; + if (p > pat && *p == '$') /* remove '$' */ + *p = NUL; + } + p = pat; + if (*p == '^' && !(attempt & 1)) /* remove '^' */ + ++p; + prog = vim_regcomp(p); + vim_free(pat); + if (prog == NULL) + return -1; + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + { + match = buflist_match(prog, buf); + if (match != NULL) + { + if (fnum >= 0) /* already found a match */ + { + fnum = -2; + break; + } + fnum = buf->b_fnum; /* remember first match */ + } + } + vim_free(prog); + if (fnum >= 0) /* found one match */ + break; + } + + if (fnum == -2) + EMSG2("More than one match for %s", pattern); + if (fnum < 1) + EMSG2("No matching buffer for %s", pattern); + return fnum; +} + +/* + * Find all buffer names that match. + * For command line expansion of ":buf" and ":sbuf". + * Return OK if matches found, FAIL otherwise. + */ + int +ExpandBufnames(pat, num_file, file, options) + char_u *pat; + int *num_file; + char_u ***file; + int options; +{ + int count = 0; + BUF *buf; + int round; + char_u *p; + int attempt; + regexp *prog; + + *num_file = 0; /* return values in case of FAIL */ + *file = NULL; + + /* + * attempt == 1: try match with '^', match at start + * attempt == 2: try match without '^', match anywhere + */ + for (attempt = 1; attempt <= 2; ++attempt) + { + if (attempt == 2) + { + if (*pat != '^') /* there's no '^', no need to try again */ + break; + ++pat; /* skip the '^' */ + } + prog = vim_regcomp(pat); + if (prog == NULL) + return FAIL; + + /* + * round == 1: Count the matches. + * round == 2: Build the array to keep the matches. + */ + for (round = 1; round <= 2; ++round) + { + count = 0; + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + { + p = buflist_match(prog, buf); + if (p != NULL) + { + if (round == 1) + ++count; + else + { + if (options & WILD_HOME_REPLACE) + p = home_replace_save(buf, p); + else + p = strsave(p); + (*file)[count++] = p; + } + } + } + if (count == 0) /* no match found, break here */ + break; + if (round == 1) + { + *file = (char_u **)alloc((unsigned)(count * sizeof(char_u *))); + if (*file == NULL) + { + vim_free(prog); + return FAIL; + } + } + } + vim_free(prog); + if (count) /* match(es) found, break here */ + break; + } + + *num_file = count; + return (count == 0 ? FAIL : OK); +} + +/* + * Check for a match on the file name for buffer "buf" with regex prog "prog". + */ + static char_u * +buflist_match(prog, buf) + regexp *prog; + BUF *buf; +{ + char_u *match = NULL; + + if (buf->b_sfilename != NULL && + vim_regexec(prog, buf->b_sfilename, TRUE) != 0) + match = buf->b_sfilename; + else if (buf->b_filename != NULL) + { + if (vim_regexec(prog, buf->b_filename, TRUE) != 0) + match = buf->b_filename; + else + { + home_replace(NULL, buf->b_filename, NameBuff, MAXPATHL); + if (vim_regexec(prog, NameBuff, TRUE) != 0) + match = buf->b_filename; + } + } + return match; +} + +/* + * find file in buffer name list by number + */ + BUF * +buflist_findnr(nr) + int nr; +{ + BUF *buf; + + if (nr == 0) + nr = curwin->w_alt_fnum; + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + if (buf->b_fnum == nr) + return (buf); + return NULL; +} + +/* + * get name of file 'n' in the buffer list + */ + char_u * +buflist_nr2name(n, fullname, helptail) + int n; + int fullname; + int helptail; /* for help buffers return tail only */ +{ + BUF *buf; + char_u *fname; + + buf = buflist_findnr(n); + if (buf == NULL) + return NULL; + if (fullname) + fname = buf->b_filename; + else + fname = buf->b_xfilename; + home_replace(helptail ? buf : NULL, fname, NameBuff, MAXPATHL); + return NameBuff; +} + +/* + * set the lnum for the buffer 'buf' and the current window + */ + static void +buflist_setlnum(buf, lnum) + BUF *buf; + linenr_t lnum; +{ + WINLNUM *wlp; + + for (wlp = buf->b_winlnum; wlp != NULL; wlp = wlp->wl_next) + if (wlp->wl_win == curwin) + break; + if (wlp == NULL) /* make new entry */ + { + wlp = (WINLNUM *)alloc((unsigned)sizeof(WINLNUM)); + if (wlp == NULL) + return; + wlp->wl_win = curwin; + } + else /* remove entry from list */ + { + if (wlp->wl_prev) + wlp->wl_prev->wl_next = wlp->wl_next; + else + buf->b_winlnum = wlp->wl_next; + if (wlp->wl_next) + wlp->wl_next->wl_prev = wlp->wl_prev; + } + wlp->wl_lnum = lnum; +/* + * insert entry in front of the list + */ + wlp->wl_next = buf->b_winlnum; + buf->b_winlnum = wlp; + wlp->wl_prev = NULL; + if (wlp->wl_next) + wlp->wl_next->wl_prev = wlp; + + return; +} + +/* + * find the lnum for the buffer 'buf' for the current window + */ + static linenr_t +buflist_findlnum(buf) + BUF *buf; +{ + WINLNUM *wlp; + + for (wlp = buf->b_winlnum; wlp != NULL; wlp = wlp->wl_next) + if (wlp->wl_win == curwin) + break; + + if (wlp == NULL) /* if no lnum for curwin, use the first in the list */ + wlp = buf->b_winlnum; + + if (wlp) + return wlp->wl_lnum; + else + return (linenr_t)1; +} + +/* + * list all know file names (for :files and :buffers command) + */ + void +buflist_list() +{ + BUF *buf; + int len; + + for (buf = firstbuf; buf != NULL && !got_int; buf = buf->b_next) + { + msg_outchar('\n'); + if (buf->b_xfilename == NULL) + STRCPY(NameBuff, "No File"); + else + /* careful: home_replace calls vim_getenv(), which uses IObuff! */ + home_replace(buf, buf->b_xfilename, NameBuff, MAXPATHL); + + sprintf((char *)IObuff, "%3d %c%c%c \"", + buf->b_fnum, + buf == curbuf ? '%' : + (curwin->w_alt_fnum == buf->b_fnum ? '#' : ' '), + buf->b_ml.ml_mfp == NULL ? '-' : + (buf->b_nwindows == 0 ? 'h' : ' '), + buf->b_changed ? '+' : ' '); + + len = STRLEN(IObuff); + STRNCPY(IObuff + len, NameBuff, IOSIZE - 20 - len); + + len = STRLEN(IObuff); + IObuff[len++] = '"'; + /* + * try to put the "line" strings in column 40 + */ + do + { + IObuff[len++] = ' '; + } while (len < 40 && len < IOSIZE - 18); + sprintf((char *)IObuff + len, "line %ld", + buf == curbuf ? curwin->w_cursor.lnum : + (long)buflist_findlnum(buf)); + msg_outtrans(IObuff); + flushbuf(); /* output one line at a time */ + mch_breakcheck(); + } +} + +/* + * get file name and line number for file 'fnum' + * used by DoOneCmd() for translating '%' and '#' + * return FAIL if not found, OK for success + */ + int +buflist_name_nr(fnum, fname, lnum) + int fnum; + char_u **fname; + linenr_t *lnum; +{ + BUF *buf; + + buf = buflist_findnr(fnum); + if (buf == NULL || buf->b_filename == NULL) + return FAIL; + + if (did_cd) + *fname = buf->b_filename; + else + *fname = buf->b_sfilename; + *lnum = buflist_findlnum(buf); + + return OK; +} + +/* + * Set the current file name to 's', short file name to 'ss'. + * The file name with the full path is also remembered, for when :cd is used. + * Returns FAIL for failure (file name already in use by other buffer) + * OK otherwise. + */ + int +setfname(fname, sfname, message) + char_u *fname, *sfname; + int message; +{ + BUF *buf; + + if (fname == NULL || *fname == NUL) + { + vim_free(curbuf->b_filename); + vim_free(curbuf->b_sfilename); + curbuf->b_filename = NULL; + curbuf->b_sfilename = NULL; + } + else + { + fname_expand(&fname, &sfname); +#ifdef USE_FNAME_CASE +# ifdef USE_LONG_FNAME + if (USE_LONG_FNAME) +# endif + fname_case(sfname); /* set correct case for short filename */ +#endif + /* + * if the file name is already used in another buffer: + * - if the buffer is loaded, fail + * - if the buffer is not loaded, delete it from the list + */ + buf = buflist_findname(fname); + if (buf != NULL && buf != curbuf) + { + if (buf->b_ml.ml_mfp != NULL) /* it's loaded, fail */ + { + if (message) + EMSG("Buffer with this name already exists"); + return FAIL; + } + close_buffer(NULL, buf, TRUE, TRUE); /* delete from the list */ + } + fname = strsave(fname); + sfname = strsave(sfname); + if (fname == NULL || sfname == NULL) + { + vim_free(sfname); + vim_free(fname); + return FAIL; + } + vim_free(curbuf->b_filename); + vim_free(curbuf->b_sfilename); + curbuf->b_filename = fname; + curbuf->b_sfilename = sfname; + } + if (did_cd) + curbuf->b_xfilename = curbuf->b_filename; + else + curbuf->b_xfilename = curbuf->b_sfilename; + +#ifndef SHORT_FNAME + curbuf->b_shortname = FALSE; +#endif + /* + * If the file name changed, also change the name of the swapfile + */ + if (curbuf->b_ml.ml_mfp != NULL) + ml_setname(); + + check_arg_idx(); /* check file name for arg list */ + maketitle(); /* set window title */ + status_redraw_all(); /* status lines need to be redrawn */ + fmarks_check_names(curbuf); /* check named file marks */ + ml_timestamp(curbuf); /* reset timestamp */ + return OK; +} + +/* + * set alternate file name for current window + * + * used by dowrite() and do_ecmd() + */ + void +setaltfname(fname, sfname, lnum) + char_u *fname; + char_u *sfname; + linenr_t lnum; +{ + BUF *buf; + + buf = buflist_new(fname, sfname, lnum, FALSE); + if (buf != NULL) + curwin->w_alt_fnum = buf->b_fnum; +} + +/* + * add a file name to the buflist and return its number + * + * used by qf_init(), main() and doarglist() + */ + int +buflist_add(fname) + char_u *fname; +{ + BUF *buf; + + buf = buflist_new(fname, NULL, (linenr_t)0, FALSE); + if (buf != NULL) + return buf->b_fnum; + return 0; +} + +/* + * set alternate lnum for current window + */ + void +buflist_altlnum() +{ + buflist_setlnum(curbuf, curwin->w_cursor.lnum); +} + +/* + * return nonzero if 'fname' is not the same file as current file + * fname must have a full path (expanded by FullName) + */ + int +otherfile(fname) + char_u *fname; +{ /* no name is different */ + if (fname == NULL || *fname == NUL || curbuf->b_filename == NULL) + return TRUE; + return fnamecmp(fname, curbuf->b_filename); +} + + void +fileinfo(fullname, shorthelp, dont_truncate) + int fullname; + int shorthelp; + int dont_truncate; +{ + char_u *name; + int n; + char_u *p; + char_u *buffer; + + buffer = alloc(IOSIZE); + if (buffer == NULL) + return; + + if (fullname > 1) /* 2 CTRL-G: include buffer number */ + { + sprintf((char *)buffer, "buf %d: ", curbuf->b_fnum); + p = buffer + STRLEN(buffer); + } + else + p = buffer; + + *p++ = '"'; + if (curbuf->b_filename == NULL) + STRCPY(p, "No File"); + else + { + if (!fullname && curbuf->b_sfilename != NULL) + name = curbuf->b_sfilename; + else + name = curbuf->b_filename; + home_replace(shorthelp ? curbuf : NULL, name, p, + (int)(IOSIZE - (p - buffer))); + } + + sprintf((char *)buffer + STRLEN(buffer), + "\"%s%s%s%s", + curbuf->b_changed ? (shortmess(SHM_MOD) ? + " [+]" : " [Modified]") : " ", + curbuf->b_notedited ? "[Not edited]" : "", + curbuf->b_p_ro ? (shortmess(SHM_RO) ? "[RO]" : "[readonly]") : "", + (curbuf->b_changed || curbuf->b_notedited || curbuf->b_p_ro) ? + " " : ""); + n = (int)(((long)curwin->w_cursor.lnum * 100L) / + (long)curbuf->b_ml.ml_line_count); + if (curbuf->b_ml.ml_flags & ML_EMPTY) + { + STRCPY(buffer + STRLEN(buffer), no_lines_msg); + } + else if (p_ru) + { + /* Current line and column are already on the screen -- webb */ + sprintf((char *)buffer + STRLEN(buffer), + "%ld line%s --%d%%--", + (long)curbuf->b_ml.ml_line_count, + plural((long)curbuf->b_ml.ml_line_count), + n); + } + else + { + sprintf((char *)buffer + STRLEN(buffer), + "line %ld of %ld --%d%%-- col ", + (long)curwin->w_cursor.lnum, + (long)curbuf->b_ml.ml_line_count, + n); + col_print(buffer + STRLEN(buffer), + (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); + } + + append_arg_number(buffer, !shortmess(SHM_FILE)); + + if (dont_truncate) + msg(buffer); + else + msg_trunc(buffer); + + vim_free(buffer); +} + +/* + * Give some info about the position of the cursor (for "g CTRL-G"). + */ + void +cursor_pos_info() +{ + char_u *p; + char_u buf1[20]; + char_u buf2[20]; + linenr_t lnum; + long char_count = 0; + long char_count_cursor = 0; + int eol_size; + + /* + * Compute the length of the file in characters. + */ + if (curbuf->b_ml.ml_flags & ML_EMPTY) + { + MSG(no_lines_msg); + } + else + { + if (curbuf->b_p_tx) + eol_size = 2; + else + eol_size = 1; + for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) + { + if (lnum == curwin->w_cursor.lnum) + char_count_cursor = char_count + curwin->w_cursor.col + 1; + char_count += STRLEN(ml_get(lnum)) + eol_size; + } + if (!curbuf->b_p_eol && curbuf->b_p_bin) + char_count -= eol_size; + + p = ml_get_curline(); + col_print(buf1, (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); + col_print(buf2, (int)STRLEN(p), linetabsize(p)); + + sprintf((char *)IObuff, "Col %s of %s; Line %ld of %ld; Char %ld of %ld", + (char *)buf1, (char *)buf2, + (long)curwin->w_cursor.lnum, (long)curbuf->b_ml.ml_line_count, + char_count_cursor, char_count); + msg(IObuff); + } +} + + void +col_print(buf, col, vcol) + char_u *buf; + int col; + int vcol; +{ + if (col == vcol) + sprintf((char *)buf, "%d", col); + else + sprintf((char *)buf, "%d-%d", col, vcol); +} + +/* + * put filename in title bar of window and in icon title + */ + +static char_u *lasttitle = NULL; +static char_u *lasticon = NULL; + + void +maketitle() +{ + char_u *t_name; + char_u *i_name; + + if (curbuf->b_filename == NULL) + { + t_name = (char_u *)""; + i_name = (char_u *)"No File"; + } + else + { + home_replace(curbuf, curbuf->b_filename, IObuff, IOSIZE); + append_arg_number(IObuff, FALSE); + t_name = IObuff; + i_name = gettail(curbuf->b_filename); /* use filename only for icon */ + } + + vim_free(lasttitle); + if (p_title && (lasttitle = alloc((unsigned)(strsize(t_name) + 7))) != NULL) + { + STRCPY(lasttitle, "VIM - "); + while (*t_name) + STRCAT(lasttitle, transchar(*t_name++)); + } + else + lasttitle = NULL; + + vim_free(lasticon); + if (p_icon && (lasticon = alloc((unsigned)(strsize(i_name) + 1))) != NULL) + { + *lasticon = NUL; + while (*i_name) + STRCAT(lasticon, transchar(*i_name++)); + } + else + lasticon = NULL; + + resettitle(); +} + +/* + * Append (file 2 of 8) to 'buf'. + */ + static void +append_arg_number(buf, add_file) + char_u *buf; + int add_file; /* Add "file" before the arg number */ +{ + if (arg_count <= 1) /* nothing to do */ + return; + + buf += STRLEN(buf); /* go to the end of the buffer */ + *buf++ = ' '; + *buf++ = '('; + if (add_file) + { + STRCPY(buf, "file "); + buf += 5; + } + sprintf((char *)buf, curwin->w_arg_idx_invalid ? "(%d) of %d)" : + "%d of %d)", curwin->w_arg_idx + 1, arg_count); +} + +/* + * Put current window title back (used after calling a shell) + */ + void +resettitle() +{ + mch_settitle(lasttitle, lasticon); +} + +/* + * If fname is not a full path, make it a full path + */ + char_u * +fix_fname(fname) + char_u *fname; +{ + if (fname != NameBuff) /* if not already expanded */ + { + if (!isFullName(fname)) + { + (void)FullName(fname, NameBuff, MAXPATHL, FALSE); + fname = NameBuff; + } +#ifdef USE_FNAME_CASE + else +# ifdef USE_LONG_FNAME + if (USE_LONG_FNAME) +# endif + { + STRNCPY(NameBuff, fname, MAXPATHL); /* make copy, it may change */ + fname = NameBuff; + fname_case(fname); /* set correct case for filename */ + } +#endif + } + return fname; +} + +/* + * make fname a full file name, set sfname to fname if not NULL + */ + void +fname_expand(fname, sfname) + char_u **fname; + char_u **sfname; +{ + if (*fname == NULL) /* if no file name given, nothing to do */ + return; + if (*sfname == NULL) /* if no short file name given, use fname */ + *sfname = *fname; + *fname = fix_fname(*fname); /* expand to full path */ +} + +/* + * do_arg_all: open up to 'count' windows, one for each argument + */ + void +do_arg_all(count) + int count; +{ + int i; + + if (arg_count <= 1) + { + /* Don't give this obvious error message. We don't want it when the + * ":all" command is in the .vimrc. */ + /* EMSG("Argument list contains less than 2 files"); */ + return; + } + /* + * 1. close all but first window + * 2. make the desired number of windows + * 3. start editing in the windows + */ + setpcmark(); + close_others(FALSE); + curwin->w_arg_idx = 0; + if (count > arg_count || count <= 0) + count = arg_count; + count = make_windows(count); + for (i = 0; i < count; ++i) + { + /* edit file i */ + (void)do_ecmd(0, arg_files[i], NULL, NULL, TRUE, (linenr_t)1, FALSE); + curwin->w_arg_idx = i; + if (i == arg_count - 1) + arg_had_last = TRUE; + if (curwin->w_next == NULL) /* just checking */ + break; + win_enter(curwin->w_next, FALSE); + } + win_enter(firstwin, FALSE); /* back to first window */ +} + +/* + * do_arg_all: open a window for each buffer + * + * 'count' is the maximum number of windows to open. + * when 'all' is TRUE, also load inactive buffers + */ + void +do_buffer_all(count, all) + int count; + int all; +{ + int buf_count; + BUF *buf; + int i; + +/* + * count number of desired windows + */ + buf_count = 0; + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + if (all || buf->b_ml.ml_mfp != NULL) + ++buf_count; + + if (buf_count == 0) /* Cannot happen? */ + { + EMSG("No relevant entries in buffer list"); + return; + } + + /* + * 1. close all but first window + * 2. make the desired number of windows + * 3. stuff commands to fill the windows + */ + close_others(FALSE); + curwin->w_arg_idx = 0; + if (buf_count > count) + buf_count = count; + buf_count = make_windows(buf_count); + buf = firstbuf; + for (i = 0; i < buf_count; ++i) + { + for ( ; buf != NULL; buf = buf->b_next) + if (all || buf->b_ml.ml_mfp != NULL) + break; + if (buf == NULL) /* Cannot happen? */ + break; + if (i != 0) + stuffReadbuff((char_u *)"\n\027\027:"); /* CTRL-W CTRL-W */ + stuffReadbuff((char_u *)":buf "); /* edit Nth buffer */ + stuffnumReadbuff((long)buf->b_fnum); + buf = buf->b_next; + } + stuffReadbuff((char_u *)"\n100\027k"); /* back to first window */ +} + +/* + * do_modelines() - process mode lines for the current file + * + * Returns immediately if the "ml" option isn't set. + */ +static int chk_modeline __ARGS((linenr_t)); + + void +do_modelines() +{ + linenr_t lnum; + int nmlines; + + if (!curbuf->b_p_ml || (nmlines = (int)p_mls) == 0) + return; + + for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count && lnum <= nmlines; + ++lnum) + if (chk_modeline(lnum) == FAIL) + nmlines = 0; + + for (lnum = curbuf->b_ml.ml_line_count; lnum > 0 && lnum > nmlines && + lnum > curbuf->b_ml.ml_line_count - nmlines; --lnum) + if (chk_modeline(lnum) == FAIL) + nmlines = 0; +} + +/* + * chk_modeline() - check a single line for a mode string + * Return FAIL if an error encountered. + */ + static int +chk_modeline(lnum) + linenr_t lnum; +{ + register char_u *s; + register char_u *e; + char_u *linecopy; /* local copy of any modeline found */ + int prev; + int end; + int retval = OK; + char_u *save_sourcing_name; + + prev = -1; + for (s = ml_get(lnum); *s != NUL; ++s) + { + if (prev == -1 || vim_isspace(prev)) + { + if ((prev != -1 && STRNCMP(s, "ex:", (size_t)3) == 0) || + STRNCMP(s, "vi:", (size_t)3) == 0 || + STRNCMP(s, "vim:", (size_t)4) == 0) + break; + } + prev = *s; + } + + if (*s) + { + do /* skip over "ex:", "vi:" or "vim:" */ + ++s; + while (s[-1] != ':'); + + s = linecopy = strsave(s); /* copy the line, it will change */ + if (linecopy == NULL) + return FAIL; + + sourcing_lnum = lnum; /* prepare for emsg() */ + save_sourcing_name = sourcing_name; + sourcing_name = (char_u *)"modelines"; + + end = FALSE; + while (end == FALSE) + { + s = skipwhite(s); + if (*s == NUL) + break; + + /* + * Find end of set command: ':' or end of line. + */ + for (e = s; (*e != ':' || *(e - 1) == '\\') && *e != NUL; ++e) + ; + if (*e == NUL) + end = TRUE; + + /* + * If there is a "set" command, require a terminating ':' and + * ignore the stuff after the ':'. + * "vi:set opt opt opt: foo" -- foo not interpreted + * "vi:opt opt opt: foo" -- foo interpreted + */ + if (STRNCMP(s, "set ", (size_t)4) == 0) + { + if (*e != ':') /* no terminating ':'? */ + break; + end = TRUE; + s += 4; + } + + *e = NUL; /* truncate the set command */ + if (do_set(s) == FAIL) /* stop if error found */ + { + retval = FAIL; + break; + } + s = e + 1; /* advance to next part */ + } + + sourcing_lnum = 0; + sourcing_name = save_sourcing_name; + + vim_free(linecopy); + } + return retval; +} diff --git a/usr.bin/vim/charset.c b/usr.bin/vim/charset.c new file mode 100644 index 00000000000..e25fb2550c9 --- /dev/null +++ b/usr.bin/vim/charset.c @@ -0,0 +1,550 @@ +/* $OpenBSD: charset.c,v 1.1.1.1 1996/09/07 21:40:27 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +/* + * chartab[] is used + * - to quickly recognize ID characters + * - to quickly recognize file name characters + * - to store the size of a character on the screen: Printable is 1 position, + * 2 otherwise + */ +static char_u chartab[256]; +static int chartab_initialized = FALSE; + +#define CHAR_MASK 0x3 /* low two bits for size */ +#define CHAR_ID 0x4 /* third bit set for ID chars */ +#define CHAR_IF 0x8 /* fourth bit set for file name chars */ + +/* + * init_chartab(): Fill chartab[] with flags for ID and file name characters + * and the size of characters on the screen (1 or 2 positions). + * Also fills b_chartab[] with flags for keyword characters for current + * buffer. + * + * Return FAIL if 'iskeyword', 'isident', 'isfname' or 'isprint' option has an + * error, OK otherwise. + */ + int +init_chartab() +{ + int c; + int c2; + char_u *p; + int i; + int tilde; + int do_isalpha; + + /* + * Set the default size for printable characters: + * From to '~' is 1 (printable), others are 2 (not printable). + * This also inits all 'isident' and 'isfname' flags to FALSE. + */ + c = 0; + while (c < ' ') + chartab[c++] = 2; + while (c <= '~') + chartab[c++] = 1; + while (c < 256) + chartab[c++] = 2; + + /* + * Init word char flags all to FALSE + */ + if (curbuf != NULL) + for (c = 0; c < 256; ++c) + curbuf->b_chartab[c] = FALSE; + + /* + * In lisp mode the '-' character is included in keywords. + */ + if (curbuf->b_p_lisp) + curbuf->b_chartab['-'] = TRUE; + + /* Walk through the 'isident', 'iskeyword', 'isfname' and 'isprint' + * options Each option is a list of characters, character numbers or + * ranges, separated by commas, e.g.: "200-210,x,#-178,-" + */ + for (i = 0; i < 4; ++i) + { + if (i == 0) + p = p_isi; /* first round: 'isident' */ + else if (i == 1) + p = p_isp; /* second round: 'isprint' */ + else if (i == 2) + p = p_isf; /* third round: 'isfname' */ + else /* i == 3 */ + p = curbuf->b_p_isk; /* fourth round: 'iskeyword' */ + + while (*p) + { + tilde = FALSE; + do_isalpha = FALSE; + if (*p == '^' && p[1] != NUL) + { + tilde = TRUE; + ++p; + } + if (isdigit(*p)) + c = getdigits(&p); + else + c = *p++; + c2 = -1; + if (*p == '-' && p[1] != NUL) + { + ++p; + if (isdigit(*p)) + c2 = getdigits(&p); + else + c2 = *p++; + } + if (c < 0 || (c2 < c && c2 != -1) || c2 >= 256 || + !(*p == NUL || *p == ',')) + return FAIL; + + if (c2 == -1) /* not a range */ + { + /* + * A single '@' (not "@-@"): + * Decide on letters being ID/printable/keyword chars with + * standard function isalpha(). This takes care of locale. + */ + if (c == '@') + { + do_isalpha = TRUE; + c = 1; + c2 = 255; + } + else + c2 = c; + } + while (c <= c2) + { + if (!do_isalpha || isalpha(c)) + { + if (i == 0) /* (re)set ID flag */ + { + if (tilde) + chartab[c] &= ~CHAR_ID; + else + chartab[c] |= CHAR_ID; + } + else if (i == 1) /* set printable to 1 or 2 */ + { + if (c < ' ' || c > '~') + chartab[c] = (chartab[c] & ~CHAR_MASK) + + (tilde ? 2 : 1); + } + else if (i == 2) /* (re)set fname flag */ + { + if (tilde) + chartab[c] &= ~CHAR_IF; + else + chartab[c] |= CHAR_IF; + } + else /* i == 3 */ /* (re)set keyword flag */ + curbuf->b_chartab[c] = !tilde; + } + ++c; + } + p = skip_to_option_part(p); + } + } + chartab_initialized = TRUE; + return OK; +} + +/* + * Translate any special characters in buf[bufsize]. + * If there is not enough room, not all characters will be translated. + */ + void +trans_characters(buf, bufsize) + char_u *buf; + int bufsize; +{ + int len; /* length of string needing translation */ + int room; /* room in buffer after string */ + char_u *new; /* translated character */ + int new_len; /* length of new[] */ + + len = STRLEN(buf); + room = bufsize - len; + while (*buf) + { + new = transchar(*buf); + new_len = STRLEN(new); + if (new_len > 1) + { + room -= new_len - 1; + if (room <= 0) + return; + vim_memmove(buf + new_len, buf + 1, (size_t)len); + } + vim_memmove(buf, new, (size_t)new_len); + buf += new_len; + --len; + } +} + +/* + * Catch 22: chartab[] can't be initialized before the options are + * initialized, and initializing options may cause transchar() to be called! + * When chartab_initialized == FALSE don't use chartab[]. + */ + char_u * +transchar(c) + int c; +{ + static char_u buf[5]; + int i; + + i = 0; + if (IS_SPECIAL(c)) /* special key code, display as ~@ char */ + { + buf[0] = '~'; + buf[1] = '@'; + i = 2; + c = K_SECOND(c); + } + if ((!chartab_initialized && c >= ' ' && c <= '~') || + (chartab[c] & CHAR_MASK) == 1) /* printable character */ + { + buf[i] = c; + buf[i + 1] = NUL; + } + else + transchar_nonprint(buf + i, c); + return buf; +} + + void +transchar_nonprint(buf, c) + char_u *buf; + int c; +{ + if (c <= 0x7f) /* 0x00 - 0x1f and 0x7f */ + { + if (c == NL) + c = NUL; /* we use newline in place of a NUL */ + buf[0] = '^'; + buf[1] = c ^ 0x40; /* DEL displayed as ^? */ + buf[2] = NUL; + } + else if (c >= ' ' + 0x80 && c <= '~' + 0x80) /* 0xa0 - 0xfe */ + { + buf[0] = '|'; + buf[1] = c - 0x80; + buf[2] = NUL; + } + else /* 0x80 - 0x9f and 0xff */ + { + buf[0] = '~'; + buf[1] = (c - 0x80) ^ 0x40; /* 0xff displayed as ~? */ + buf[2] = NUL; + } +} + +/* + * return the number of characters 'c' will take on the screen + * This is used very often, keep it fast!!! + */ + int +charsize(c) + register int c; +{ + if (IS_SPECIAL(c)) + return (chartab[K_SECOND(c)] & CHAR_MASK) + 2; + return (chartab[c] & CHAR_MASK); +} + +/* + * Return the number of characters string 's' will take on the screen, + * counting TABs as two characters: "^I". + */ + int +strsize(s) + register char_u *s; +{ + register int len = 0; + + while (*s) + len += charsize(*s++); + return len; +} + +/* + * Return the number of characters 'c' will take on the screen, taking + * into account the size of a tab. + * Use a define to make it fast, this is used very often!!! + * Also see getvcol() below. + */ + +#define RET_WIN_BUF_CHARTABSIZE(wp, buf, c, col) \ + if ((c) == TAB && !(wp)->w_p_list) \ + { \ + register int ts; \ + ts = (buf)->b_p_ts; \ + return (int)(ts - (col % ts)); \ + } \ + else \ + return charsize(c); + + int +chartabsize(c, col) + register int c; + colnr_t col; +{ + RET_WIN_BUF_CHARTABSIZE(curwin, curbuf, c, col) +} + + int +win_chartabsize(wp, c, col) + register WIN *wp; + register int c; + colnr_t col; +{ + RET_WIN_BUF_CHARTABSIZE(wp, wp->w_buffer, c, col) +} + +/* + * return the number of characters the string 's' will take on the screen, + * taking into account the size of a tab + */ + int +linetabsize(s) + char_u *s; +{ + colnr_t col = 0; + + while (*s != NUL) + col += lbr_chartabsize(s++, col); + return (int)col; +} + +/* + * return TRUE if 'c' is a normal identifier character + * letters and characters from 'isident' option. + */ + int +isidchar(c) + int c; +{ + return (c < 0x100 && (chartab[c] & CHAR_ID)); +} + +/* + * return TRUE if 'c' is a keyword character: Letters and characters from + * 'iskeyword' option for current buffer. + */ + int +iswordchar(c) + int c; +{ + return (c < 0x100 && curbuf->b_chartab[c]); +} + +/* + * return TRUE if 'c' is a valid file-name character + */ + int +isfilechar(c) + int c; +{ + return (c < 0x100 && (chartab[c] & CHAR_IF)); +} + +/* + * return TRUE if 'c' is a printable character + */ + int +isprintchar(c) + int c; +{ + return (c < 0x100 && (chartab[c] & CHAR_MASK) == 1); +} + +/* + * like chartabsize(), but also check for line breaks on the screen + */ + int +lbr_chartabsize(s, col) + unsigned char *s; + colnr_t col; +{ + if (!curwin->w_p_lbr && *p_sbr == NUL) + RET_WIN_BUF_CHARTABSIZE(curwin, curbuf, *s, col) + + return win_lbr_chartabsize(curwin, s, col, NULL); +} + +/* + * This function is used very often, keep it fast!!!! + * Warning: *head is only set if it's a non-zero value, init to 0 before + * calling. + */ + int +win_lbr_chartabsize(wp, s, col, head) + WIN *wp; + unsigned char *s; + colnr_t col; + int *head; +{ + int c = *s; + int size; + colnr_t col2; + colnr_t colmax; + int added; + int numberextra; + +/* + * No 'linebreak' and 'showbreak': return quickly. + */ + if (!wp->w_p_lbr && *p_sbr == NUL) + RET_WIN_BUF_CHARTABSIZE(wp, wp->w_buffer, c, col) + +/* + * First get normal size, without 'linebreak' + */ + size = win_chartabsize(wp, c, col); +/* + * If 'linebreak' set check at a blank before a non-blank if the line needs a + * break here + */ + if (wp->w_p_lbr && isbreak(c) && !isbreak(s[1]) && + !wp->w_p_list && wp->w_p_wrap) + { + numberextra = curwin->w_p_nu? 8: 0; + /* count all characters from first non-blank after a blank up to next + * non-blank after a blank */ + col2 = col; + colmax = (((col + numberextra) / Columns) + 1) * Columns; + while ((c = *++s) != NUL && (isbreak(c) || + (!isbreak(c) && (col2 == col || !isbreak(s[-1]))))) + { + col2 += win_chartabsize(wp, c, col2); + if (col2 + numberextra >= colmax) /* doesn't fit */ + { + size = Columns - ((col + numberextra) % Columns); + break; + } + } + } + +/* + * May have to add something for 'showbreak' string at start of line + * Set *head to the size of what we add. + */ + added = 0; + if (*p_sbr != NUL && wp->w_p_wrap && col) + { + numberextra = curwin->w_p_nu? 8: 0; + col = (col + numberextra) % Columns; + if (col == 0 || col + size > (colnr_t)Columns) + { + added = STRLEN(p_sbr); + size += added; + if (col != 0) + added = 0; + } + } + if (head != NULL) + *head = added; + return size; +} + +/* + * get virtual column number of pos + * start: on the first position of this character (TAB, ctrl) + * cursor: where the cursor is on this character (first char, except for TAB) + * end: on the last position of this character (TAB, ctrl) + */ + void +getvcol(wp, pos, start, cursor, end) + WIN *wp; + FPOS *pos; + colnr_t *start; + colnr_t *cursor; + colnr_t *end; +{ + int col; + colnr_t vcol; + char_u *ptr; + int incr; + int head; + int ts = wp->w_buffer->b_p_ts; + int c; + + vcol = 0; + ptr = ml_get_buf(wp->w_buffer, pos->lnum, FALSE); + + /* + * This function is used very often, do some speed optimizations. + * When 'list', 'linebreak' and 'showbreak' are not set use a simple loop. + */ + if (!wp->w_p_list && !wp->w_p_lbr && *p_sbr == NUL) + { + head = 0; + for (col = pos->col; ; --col, ++ptr) + { + c = *ptr; + /* make sure we don't go past the end of the line */ + if (c == NUL) + { + incr = 1; /* NUL at end of line only takes one column */ + break; + } + /* A tab gets expanded, depending on the current column */ + if (c == TAB) + incr = ts - (vcol % ts); + else + incr = charsize(c); + + if (col == 0) /* character at pos.col */ + break; + + vcol += incr; + } + } + else + { + for (col = pos->col; ; --col, ++ptr) + { + /* A tab gets expanded, depending on the current column */ + head = 0; + incr = win_lbr_chartabsize(wp, ptr, vcol, &head); + /* make sure we don't go past the end of the line */ + if (*ptr == NUL) + { + incr = 1; /* NUL at end of line only takes one column */ + break; + } + + if (col == 0) /* character at pos.col */ + break; + + vcol += incr; + } + } + if (start != NULL) + *start = vcol + head; + if (end != NULL) + *end = vcol + incr - 1; + if (cursor != NULL) + { + if (*ptr == TAB && (State & NORMAL) && !wp->w_p_list) + *cursor = vcol + incr - 1; /* cursor at end */ + else + *cursor = vcol + head; /* cursor at start */ + } +} diff --git a/usr.bin/vim/cmdcmds.c b/usr.bin/vim/cmdcmds.c new file mode 100644 index 00000000000..7b4f992f317 --- /dev/null +++ b/usr.bin/vim/cmdcmds.c @@ -0,0 +1,1236 @@ +/* $OpenBSD: cmdcmds.c,v 1.1.1.1 1996/09/07 21:40:26 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * cmdcmds.c: functions for command line commands + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +#ifdef USE_TMPNAM +# define mktemp(a) tmpnam(a) +#endif + +extern char *mktemp __ARGS((char *)); + +#ifdef OS2 +static void check_tmpenv __ARGS((void)); +#endif + +#ifdef VIMINFO +static char_u *viminfo_filename __ARGS((char_u *)); +static void do_viminfo __ARGS((FILE *fp_in, FILE *fp_out, int want_info, int want_marks, int force_read)); +static int read_viminfo_up_to_marks __ARGS((char_u *line, FILE *fp, int force)); +#endif /* VIMINFO */ + + void +do_ascii() +{ + int c; + char buf1[20]; + char buf2[20]; + char_u buf3[3]; + + c = gchar_cursor(); + if (c == NL) /* NUL is stored as NL */ + c = NUL; + if (isprintchar(c) && (c < ' ' || c > '~')) + { + transchar_nonprint(buf3, c); + sprintf(buf1, " <%s>", (char *)buf3); + } + else + buf1[0] = NUL; + if (c >= 0x80) + sprintf(buf2, " ", transchar(c & 0x7f)); + else + buf2[0] = NUL; + sprintf((char *)IObuff, "<%s>%s%s %d, Hex %02x, Octal %03o", + transchar(c), buf1, buf2, c, c, c); + msg(IObuff); +} + +/* + * align text: + * type = -1 left aligned + * type = 0 centered + * type = 1 right aligned + */ + void +do_align(start, end, width, type) + linenr_t start; + linenr_t end; + int width; + int type; +{ + FPOS pos; + int len; + int indent = 0; + int new_indent = 0; /* init for GCC */ + char_u *first; + char_u *last; + int save; + +#ifdef RIGHTLEFT + if (curwin->w_p_rl) + type = -type; /* switch left and right aligning */ +#endif + + pos = curwin->w_cursor; + if (type == -1) /* left align: width is used for new indent */ + { + if (width >= 0) + indent = width; + } + else + { + /* + * if 'textwidth' set, use it + * else if 'wrapmargin' set, use it + * if invalid value, use 80 + */ + if (width <= 0) + width = curbuf->b_p_tw; + if (width == 0 && curbuf->b_p_wm > 0) + width = Columns - curbuf->b_p_wm; + if (width <= 0) + width = 80; + } + + if (u_save((linenr_t)(start - 1), (linenr_t)(end + 1)) == FAIL) + return; + for (curwin->w_cursor.lnum = start; + curwin->w_cursor.lnum <= end; ++curwin->w_cursor.lnum) + { + /* find the first non-blank character */ + first = skipwhite(ml_get_curline()); + /* find the character after the last non-blank character */ + for (last = first + STRLEN(first); + last > first && vim_iswhite(last[-1]); --last) + ; + save = *last; + *last = NUL; + len = linetabsize(first); /* get line length */ + *last = save; + if (len == 0) /* skip blank lines */ + continue; + switch (type) + { + case -1: new_indent = indent; /* left align */ + break; + case 0: new_indent = (width - len) / 2; /* center */ + break; + case 1: new_indent = width - len; /* right align */ + break; + } + if (new_indent < 0) + new_indent = 0; + set_indent(new_indent, TRUE); /* set indent */ + } + curwin->w_cursor = pos; + beginline(TRUE); + updateScreen(NOT_VALID); +} + + void +do_retab(start, end, new_ts, force) + linenr_t start; + linenr_t end; + int new_ts; + int force; +{ + linenr_t lnum; + int got_tab = FALSE; + long num_spaces = 0; + long num_tabs; + long len; + long col; + long vcol; + long start_col = 0; /* For start of white-space string */ + long start_vcol = 0; /* For start of white-space string */ + int temp; + long old_len; + char_u *ptr; + char_u *new_line = (char_u *)1; /* init to non-NULL */ + int did_something = FALSE; + int did_undo; /* called u_save for current line */ + + if (new_ts == 0) + new_ts = curbuf->b_p_ts; + for (lnum = start; !got_int && lnum <= end; ++lnum) + { + ptr = ml_get(lnum); + col = 0; + vcol = 0; + did_undo = FALSE; + for (;;) + { + if (vim_iswhite(ptr[col])) + { + if (!got_tab && num_spaces == 0) + { + /* First consecutive white-space */ + start_vcol = vcol; + start_col = col; + } + if (ptr[col] == ' ') + num_spaces++; + else + got_tab = TRUE; + } + else + { + if (got_tab || (force && num_spaces > 1)) + { + /* Retabulate this string of white-space */ + + /* len is virtual length of white string */ + len = num_spaces = vcol - start_vcol; + num_tabs = 0; + if (!curbuf->b_p_et) + { + temp = new_ts - (start_vcol % new_ts); + if (num_spaces >= temp) + { + num_spaces -= temp; + num_tabs++; + } + num_tabs += num_spaces / new_ts; + num_spaces -= (num_spaces / new_ts) * new_ts; + } + if (curbuf->b_p_et || got_tab || + (num_spaces + num_tabs < len)) + { + if (did_undo == FALSE) + { + did_undo = TRUE; + if (u_save((linenr_t)(lnum - 1), + (linenr_t)(lnum + 1)) == FAIL) + { + new_line = NULL; /* flag out-of-memory */ + break; + } + } + + /* len is actual number of white characters used */ + len = num_spaces + num_tabs; + old_len = STRLEN(ptr); + new_line = lalloc(old_len - col + start_col + len + 1, + TRUE); + if (new_line == NULL) + break; + if (start_col > 0) + vim_memmove(new_line, ptr, (size_t)start_col); + vim_memmove(new_line + start_col + len, + ptr + col, (size_t)(old_len - col + 1)); + ptr = new_line + start_col; + for (col = 0; col < len; col++) + ptr[col] = (col < num_tabs) ? '\t' : ' '; + ml_replace(lnum, new_line, FALSE); + did_something = TRUE; + ptr = new_line; + col = start_col + len; + } + } + got_tab = FALSE; + num_spaces = 0; + } + if (ptr[col] == NUL) + break; + vcol += chartabsize(ptr[col++], (colnr_t)vcol); + } + if (new_line == NULL) /* out of memory */ + break; + line_breakcheck(); + } + if (got_int) + emsg(e_interr); + if (did_something) + CHANGED; + curbuf->b_p_ts = new_ts; + coladvance(curwin->w_curswant); +} + +/* + * :move command - move lines line1-line2 to line n + * + * return FAIL for failure, OK otherwise + */ + int +do_move(line1, line2, n) + linenr_t line1; + linenr_t line2; + linenr_t n; +{ + char_u *str; + linenr_t l; + linenr_t extra; /* Num lines added before line1 */ + linenr_t num_lines; /* Num lines moved */ + linenr_t last_line; /* Last line in file after adding new text */ + int has_mark; + + if (n >= line1 && n < line2) + { + EMSG("Move lines into themselves"); + return FAIL; + } + + num_lines = line2 - line1 + 1; + + /* + * First we copy the old text to its new location -- webb + */ + if (u_save(n, n + 1) == FAIL) + return FAIL; + for (extra = 0, l = line1; l <= line2; l++) + { + str = strsave(ml_get(l + extra)); + if (str != NULL) + { + has_mark = ml_has_mark(l + extra); + ml_append(n + l - line1, str, (colnr_t)0, FALSE); + vim_free(str); + if (has_mark) + ml_setmarked(n + l - line1 + 1); + if (n < line1) + extra++; + } + } + + /* + * Now we must be careful adjusting our marks so that we don't overlap our + * mark_adjust() calls. + * + * We adjust the marks within the old text so that they refer to the + * last lines of the file (temporarily), because we know no other marks + * will be set there since these line numbers did not exist until we added + * our new lines. + * + * Then we adjust the marks on lines between the old and new text positions + * (either forwards or backwards). + * + * And Finally we adjust the marks we put at the end of the file back to + * their final destination at the new text position -- webb + */ + last_line = curbuf->b_ml.ml_line_count; + mark_adjust(line1, line2, last_line - line2, 0L); + if (n >= line2) + mark_adjust(line2 + 1, n, -num_lines, 0L); + else + mark_adjust(n + 1, line1 - 1, num_lines, 0L); + mark_adjust(last_line - num_lines + 1, last_line, + -(last_line - n - extra), 0L); + + /* + * Now we delete the original text -- webb + */ + if (u_save(line1 + extra - 1, line2 + extra + 1) == FAIL) + return FAIL; + + for (l = line1; l <= line2; l++) + ml_delete(line1 + extra, TRUE); + + CHANGED; + if (!global_busy && num_lines > p_report) + smsg((char_u *)"%ld line%s moved", num_lines, plural(num_lines)); + return OK; +} + +/* + * :copy command - copy lines line1-line2 to line n + */ + void +do_copy(line1, line2, n) + linenr_t line1; + linenr_t line2; + linenr_t n; +{ + linenr_t lnum; + char_u *p; + + mark_adjust(n + 1, MAXLNUM, line2 - line1 + 1, 0L); + + /* + * there are three situations: + * 1. destination is above line1 + * 2. destination is between line1 and line2 + * 3. destination is below line2 + * + * n = destination (when starting) + * curwin->w_cursor.lnum = destination (while copying) + * line1 = start of source (while copying) + * line2 = end of source (while copying) + */ + if (u_save(n, n + 1) == FAIL) + return; + curwin->w_cursor.lnum = n; + lnum = line2 - line1 + 1; + while (line1 <= line2) + { + /* need to use strsave() because the line will be unlocked + within ml_append */ + p = strsave(ml_get(line1)); + if (p != NULL) + { + ml_append(curwin->w_cursor.lnum, p, (colnr_t)0, FALSE); + vim_free(p); + } + /* situation 2: skip already copied lines */ + if (line1 == n) + line1 = curwin->w_cursor.lnum; + ++line1; + if (curwin->w_cursor.lnum < line1) + ++line1; + if (curwin->w_cursor.lnum < line2) + ++line2; + ++curwin->w_cursor.lnum; + } + CHANGED; + msgmore((long)lnum); +} + +/* + * Handle the ":!cmd" command. Also for ":r !cmd" and ":w !cmd" + * Bangs in the argument are replaced with the previously entered command. + * Remember the argument. + */ + void +do_bang(addr_count, line1, line2, forceit, arg, do_in, do_out) + int addr_count; + linenr_t line1, line2; + int forceit; + char_u *arg; + int do_in, do_out; +{ + static char_u *prevcmd = NULL; /* the previous command */ + char_u *newcmd = NULL; /* the new command */ + int ins_prevcmd; + char_u *t; + char_u *p; + char_u *trailarg; + int len; + int scroll_save = msg_scroll; + + /* + * Disallow shell commands from .exrc and .vimrc in current directory for + * security reasons. + */ + if (secure) + { + secure = 2; + emsg(e_curdir); + return; + } + + if (addr_count == 0) /* :! */ + { + msg_scroll = FALSE; /* don't scroll here */ + autowrite_all(); + msg_scroll = scroll_save; + } + + /* + * Try to find an embedded bang, like in :! ! [args] + * (:!! is indicated by the 'forceit' variable) + */ + ins_prevcmd = forceit; + trailarg = arg; + do + { + len = STRLEN(trailarg) + 1; + if (newcmd != NULL) + len += STRLEN(newcmd); + if (ins_prevcmd) + { + if (prevcmd == NULL) + { + emsg(e_noprev); + vim_free(newcmd); + return; + } + len += STRLEN(prevcmd); + } + if ((t = alloc(len)) == NULL) + { + vim_free(newcmd); + return; + } + *t = NUL; + if (newcmd != NULL) + STRCAT(t, newcmd); + if (ins_prevcmd) + STRCAT(t, prevcmd); + p = t + STRLEN(t); + STRCAT(t, trailarg); + vim_free(newcmd); + newcmd = t; + + /* + * Scan the rest of the argument for '!', which is replaced by the + * previous command. "\!" is replaced by "!" (this is vi compatible). + */ + trailarg = NULL; + while (*p) + { + if (*p == '!') + { + if (p > newcmd && p[-1] == '\\') + vim_memmove(p - 1, p, (size_t)(STRLEN(p) + 1)); + else + { + trailarg = p; + *trailarg++ = NUL; + ins_prevcmd = TRUE; + break; + } + } + ++p; + } + } while (trailarg != NULL); + + vim_free(prevcmd); + prevcmd = newcmd; + + if (bangredo) /* put cmd in redo buffer for ! command */ + { + AppendToRedobuff(prevcmd); + AppendToRedobuff((char_u *)"\n"); + bangredo = FALSE; + } + if (addr_count == 0) /* :! */ + { + /* echo the command */ + msg_start(); + msg_outchar(':'); + msg_outchar('!'); + msg_outtrans(prevcmd); + msg_clr_eos(); + windgoto(msg_row, msg_col); + + do_shell(prevcmd); + } + else /* :range! */ + do_filter(line1, line2, prevcmd, do_in, do_out); +} + +/* + * call a shell to execute a command + */ + void +do_shell(cmd) + char_u *cmd; +{ + BUF *buf; + int save_nwr; + + /* + * Disallow shell commands from .exrc and .vimrc in current directory for + * security reasons. + */ + if (secure) + { + secure = 2; + emsg(e_curdir); + msg_end(); + return; + } + +#ifdef WIN32 + /* + * Check if external commands are allowed now. + */ + if (can_end_termcap_mode(TRUE) == FALSE) + return; +#endif + + /* + * For autocommands we want to get the output on the current screen, to + * avoid having to type return below. + */ + msg_outchar('\r'); /* put cursor at start of line */ +#ifdef AUTOCMD + if (!autocmd_busy) +#endif + stoptermcap(); + msg_outchar('\n'); /* may shift screen one line up */ + + /* warning message before calling the shell */ + if (p_warn +#ifdef AUTOCMD + && !autocmd_busy +#endif + ) + for (buf = firstbuf; buf; buf = buf->b_next) + if (buf->b_changed) + { + MSG_OUTSTR("[No write since last change]\n"); + break; + } + +/* This windgoto is required for when the '\n' resulted in a "delete line 1" + * command to the terminal. */ + + windgoto(msg_row, msg_col); + cursor_on(); + (void)call_shell(cmd, SHELL_COOKED); + need_check_timestamps = TRUE; + +/* + * put the message cursor at the end of the screen, avoids wait_return() to + * overwrite the text that the external command showed + */ + msg_pos((int)Rows - 1, 0); + +#ifdef AUTOCMD + if (!autocmd_busy) +#endif + { + /* + * If K_TI is defined, we assume that we switch screens when + * starttermcap() is called. In that case we really want to wait for + * "hit return to continue". + */ + save_nwr = no_wait_return; + if (*T_TI != NUL) + no_wait_return = FALSE; +#ifdef AMIGA + wait_return(term_console ? -1 : TRUE); /* see below */ +#else + wait_return(TRUE); +#endif + no_wait_return = save_nwr; + starttermcap(); /* start termcap if not done by wait_return() */ + + /* + * In an Amiga window redrawing is caused by asking the window size. + * If we got an interrupt this will not work. The chance that the + * window size is wrong is very small, but we need to redraw the + * screen. Don't do this if ':' hit in wait_return(). THIS IS UGLY + * but it saves an extra redraw. + */ +#ifdef AMIGA + if (skip_redraw) /* ':' hit in wait_return() */ + must_redraw = CLEAR; + else if (term_console) + { + OUTSTR("\033[0 q"); /* get window size */ + if (got_int) + must_redraw = CLEAR; /* if got_int is TRUE, redraw needed */ + else + must_redraw = 0; /* no extra redraw needed */ + } +#endif /* AMIGA */ + } +#ifdef AUTOCMD + else + must_redraw = CLEAR; +#endif +} + +/* + * do_filter: filter lines through a command given by the user + * + * We use temp files and the call_shell() routine here. This would normally + * be done using pipes on a UNIX machine, but this is more portable to + * non-unix machines. The call_shell() routine needs to be able + * to deal with redirection somehow, and should handle things like looking + * at the PATH env. variable, and adding reasonable extensions to the + * command name given by the user. All reasonable versions of call_shell() + * do this. + * We use input redirection if do_in is TRUE. + * We use output redirection if do_out is TRUE. + */ + void +do_filter(line1, line2, buff, do_in, do_out) + linenr_t line1, line2; + char_u *buff; + int do_in, do_out; +{ +#ifdef USE_TMPNAM + char_u itmp[L_tmpnam]; /* use tmpnam() */ + char_u otmp[L_tmpnam]; +#else + char_u itmp[TMPNAMELEN]; + char_u otmp[TMPNAMELEN]; +#endif + linenr_t linecount; + FPOS cursor_save; + + /* + * Disallow shell commands from .exrc and .vimrc in current directory for + * security reasons. + */ + if (secure) + { + secure = 2; + emsg(e_curdir); + return; + } + if (*buff == NUL) /* no filter command */ + return; + +#ifdef WIN32 + /* + * Check if external commands are allowed now. + */ + if (can_end_termcap_mode(TRUE) == FALSE) + return; +#endif + + cursor_save = curwin->w_cursor; + linecount = line2 - line1 + 1; + curwin->w_cursor.lnum = line1; + curwin->w_cursor.col = 0; + + /* + * 1. Form temp file names + * 2. Write the lines to a temp file + * 3. Run the filter command on the temp file + * 4. Read the output of the command into the buffer + * 5. Delete the original lines to be filtered + * 6. Remove the temp files + */ + +#ifndef USE_TMPNAM /* tmpnam() will make its own name */ +# ifdef OS2 + check_tmpenv(); + expand_env(TMPNAME1, itmp, TMPNAMELEN); + expand_env(TMPNAME2, otmp, TMPNAMELEN); +# else + STRCPY(itmp, TMPNAME1); + STRCPY(otmp, TMPNAME2); +# endif +#endif + + if ((do_in && *mktemp((char *)itmp) == NUL) || + (do_out && *mktemp((char *)otmp) == NUL)) + { + emsg(e_notmp); + return; + } + +/* + * The writing and reading of temp files will not be shown. + * Vi also doesn't do this and the messages are not very informative. + */ + ++no_wait_return; /* don't call wait_return() while busy */ + if (do_in && buf_write(curbuf, itmp, NULL, line1, line2, + FALSE, 0, FALSE, TRUE) == FAIL) + { + msg_outchar('\n'); /* keep message from buf_write() */ + --no_wait_return; + (void)emsg2(e_notcreate, itmp); /* will call wait_return */ + goto filterend; + } + if (!do_out) + msg_outchar('\n'); + +#if (defined(UNIX) && !defined(ARCHIE)) || defined(OS2) +/* + * put braces around the command (for concatenated commands) + */ + sprintf((char *)IObuff, "(%s)", (char *)buff); + if (do_in) + { + STRCAT(IObuff, " < "); + STRCAT(IObuff, itmp); + } +#else +/* + * for shells that don't understand braces around commands, at least allow + * the use of commands in a pipe. + */ + STRCPY(IObuff, buff); + if (do_in) + { + char_u *p; + /* + * If there is a pipe, we have to put the '<' in front of it + */ + p = vim_strchr(IObuff, '|'); + if (p) + *p = NUL; + STRCAT(IObuff, " < "); + STRCAT(IObuff, itmp); + p = vim_strchr(buff, '|'); + if (p) + STRCAT(IObuff, p); + } +#endif + if (do_out) + { + char_u *p; + + if ((p = vim_strchr(p_srr, '%')) != NULL && p[1] == 's') + { + p = IObuff + STRLEN(IObuff); + *p++ = ' '; /* not really needed? Not with sh, ksh or bash */ + sprintf((char *)p, (char *)p_srr, (char *)otmp); + } + else + sprintf((char *)IObuff + STRLEN(IObuff), " %s %s", + (char *)p_srr, (char *)otmp); + } + + windgoto((int)Rows - 1, 0); + cursor_on(); + + /* + * When not redirecting the output the command can write anything to the + * screen. If 'shellredir' is equal to ">", screen may be messed up by + * stderr output of external command. Clear the screen later. + * If do_in is FALSE, this could be something like ":r !cat", which may + * also mess up the screen, clear it later. + */ + if (!do_out || STRCMP(p_srr, ">") == 0 || !do_in) + must_redraw = CLEAR; + else + redraw_later(NOT_VALID); + + /* + * When call_shell() fails wait_return() is called to give the user a + * chance to read the error messages. Otherwise errors are ignored, so you + * can see the error messages from the command that appear on stdout; use + * 'u' to fix the text + * Switch to cooked mode when not redirecting stdin, avoids that something + * like ":r !cat" hangs. + */ + if (call_shell(IObuff, SHELL_FILTER | SHELL_COOKED) == FAIL) + { + must_redraw = CLEAR; + wait_return(FALSE); + } + need_check_timestamps = TRUE; + + if (do_out) + { + if (u_save((linenr_t)(line2), (linenr_t)(line2 + 1)) == FAIL) + { + goto error; + } + if (readfile(otmp, NULL, line2, FALSE, (linenr_t)0, MAXLNUM, TRUE) + == FAIL) + { + msg_outchar('\n'); + emsg2(e_notread, otmp); + goto error; + } + + if (do_in) + { + /* put cursor on first filtered line for ":range!cmd" */ + curwin->w_cursor.lnum = line1; + dellines(linecount, TRUE, TRUE); + curbuf->b_op_start.lnum -= linecount; /* adjust '[ */ + curbuf->b_op_end.lnum -= linecount; /* adjust '] */ + } + else + { + /* put cursor on last new line for ":r !cmd" */ + curwin->w_cursor.lnum = curbuf->b_op_end.lnum; + linecount = curbuf->b_op_end.lnum - curbuf->b_op_start.lnum + 1; + } + beginline(TRUE); /* cursor on first non-blank */ + --no_wait_return; + + if (linecount > p_report) + { + if (do_in) + { + sprintf((char *)msg_buf, "%ld lines filtered", (long)linecount); + if (msg(msg_buf) && !msg_scroll) + keep_msg = msg_buf; /* display message after redraw */ + } + else + msgmore((long)linecount); + } + } + else + { +error: + /* put cursor back in same position for ":w !cmd" */ + curwin->w_cursor = cursor_save; + --no_wait_return; + wait_return(FALSE); + } + +filterend: + vim_remove(itmp); + vim_remove(otmp); +} + +#ifdef OS2 +/* + * If $TMP is not defined, construct a sensible default. + * This is required for TMPNAME1 and TMPNAME2 to work. + */ + static void +check_tmpenv() +{ + char_u *envent; + + if (getenv("TMP") == NULL) + { + envent = alloc(8); + if (envent != NULL) + { + strcpy(envent, "TMP=C:/"); + putenv(envent); + } + } +} +#endif /* OS2 */ + +#ifdef VIMINFO + +static int no_viminfo __ARGS((void)); + + static int +no_viminfo() +{ + /* "vim -i NONE" does not read or write a viminfo file */ + return (use_viminfo != NULL && STRCMP(use_viminfo, "NONE") == 0); +} + +/* + * read_viminfo() -- Read the viminfo file. Registers etc. which are already + * set are not over-written unless force is TRUE. -- webb + */ + int +read_viminfo(file, want_info, want_marks, force) + char_u *file; + int want_info; + int want_marks; + int force; +{ + FILE *fp; + + if (no_viminfo()) + return FAIL; + + file = viminfo_filename(file); /* may set to default if NULL */ + if ((fp = fopen((char *)file, READBIN)) == NULL) + return FAIL; + + do_viminfo(fp, NULL, want_info, want_marks, force); + + fclose(fp); + + return OK; +} + +/* + * write_viminfo() -- Write the viminfo file. The old one is read in first so + * that effectively a merge of current info and old info is done. This allows + * multiple vims to run simultaneously, without losing any marks etc. If + * force is TRUE, then the old file is not read in, and only internal info is + * written to the file. -- webb + */ + void +write_viminfo(file, force) + char_u *file; + int force; +{ + FILE *fp_in = NULL; + FILE *fp_out = NULL; +#ifdef USE_TMPNAM + char_u tmpname[L_tmpnam]; /* use tmpnam() */ +#else + char_u tmpname[TMPNAMELEN]; +#endif + + if (no_viminfo()) + return; + +#ifndef USE_TMPNAM /* tmpnam() will make its own name */ +# ifdef OS2 + check_tmpenv(); + expand_env(TMPNAME2, tmpname, TMPNAMELEN); +# else + STRCPY(tmpname, TMPNAME2); +# endif +#endif + + file = viminfo_filename(file); /* may set to default if NULL */ + file = strsave(file); /* make a copy, don't want NameBuff */ + if (file != NULL) + { + fp_in = fopen((char *)file, READBIN); + if (fp_in == NULL) + fp_out = fopen((char *)file, WRITEBIN); + else if (*mktemp((char *)tmpname) != NUL) + fp_out = fopen((char *)tmpname, WRITEBIN); + } + if (file == NULL || fp_out == NULL) + { + EMSG2("Can't write viminfo file %s!", file == NULL ? (char_u *)"" : + fp_in == NULL ? file : tmpname); + if (fp_in != NULL) + fclose(fp_in); + vim_free(file); + return; + } + + do_viminfo(fp_in, fp_out, !force, !force, FALSE); + + fclose(fp_out); /* errors are ignored !? */ + if (fp_in != NULL) + { + fclose(fp_in); + if (vim_rename(tmpname, file) == -1) + vim_remove(tmpname); + } + vim_free(file); +} + + static char_u * +viminfo_filename(file) + char_u *file; +{ + if (file == NULL || *file == NUL) + { + expand_env(use_viminfo == NULL ? (char_u *)VIMINFO_FILE : use_viminfo, + NameBuff, MAXPATHL); + return NameBuff; + } + return file; +} + +/* + * do_viminfo() -- Should only be called from read_viminfo() & write_viminfo(). + */ + static void +do_viminfo(fp_in, fp_out, want_info, want_marks, force_read) + FILE *fp_in; + FILE *fp_out; + int want_info; + int want_marks; + int force_read; +{ + int count = 0; + int eof = FALSE; + char_u *line; + + if ((line = alloc(LSIZE)) == NULL) + return; + + if (fp_in != NULL) + { + if (want_info) + eof = read_viminfo_up_to_marks(line, fp_in, force_read); + else + /* Skip info, find start of marks */ + while (!(eof = vim_fgets(line, LSIZE, fp_in)) && line[0] != '>') + ; + } + if (fp_out != NULL) + { + /* Write the info: */ + fprintf(fp_out, "# This viminfo file was generated by vim\n"); + fprintf(fp_out, "# You may edit it if you're careful!\n\n"); + write_viminfo_search_pattern(fp_out); + write_viminfo_sub_string(fp_out); + write_viminfo_history(fp_out); + write_viminfo_registers(fp_out); + write_viminfo_filemarks(fp_out); + count = write_viminfo_marks(fp_out); + } + if (fp_in != NULL && want_marks) + copy_viminfo_marks(line, fp_in, fp_out, count, eof); + vim_free(line); +} + +/* + * read_viminfo_up_to_marks() -- Only called from do_viminfo(). Reads in the + * first part of the viminfo file which contains everything but the marks that + * are local to a file. Returns TRUE when end-of-file is reached. -- webb + */ + static int +read_viminfo_up_to_marks(line, fp, force) + char_u *line; + FILE *fp; + int force; +{ + int eof; + + prepare_viminfo_history(force ? 9999 : 0); + eof = vim_fgets(line, LSIZE, fp); + while (!eof && line[0] != '>') + { + switch (line[0]) + { + case NUL: + case '\r': + case '\n': + case '#': /* A comment */ + eof = vim_fgets(line, LSIZE, fp); + break; + case '"': + eof = read_viminfo_register(line, fp, force); + break; + case '/': /* Search string */ + case '&': /* Substitute search string */ + case '~': /* Last search string, followed by '/' or '&' */ + eof = read_viminfo_search_pattern(line, fp, force); + break; + case '$': + eof = read_viminfo_sub_string(line, fp, force); + break; + case ':': + case '?': + eof = read_viminfo_history(line, fp); + break; + case '\'': + /* How do we have a file mark when the file is not in the + * buffer list? + */ + eof = read_viminfo_filemark(line, fp, force); + break; +#if 0 + case '+': + /* eg: "+40 /path/dir file", for running vim with no args */ + eof = vim_fgets(line, LSIZE, fp); + break; +#endif + default: + EMSG2("viminfo: Illegal starting char in line %s", line); + eof = vim_fgets(line, LSIZE, fp); + break; + } + } + finish_viminfo_history(); + return eof; +} + +/* + * check string read from viminfo file + * remove '\n' at the end of the line + * - replace CTRL-V CTRL-V with CTRL-V + * - replace CTRL-V 'n' with '\n' + */ + void +viminfo_readstring(p) + char_u *p; +{ + while (*p != NUL && *p != '\n') + { + if (*p == Ctrl('V')) + { + if (p[1] == 'n') + p[0] = '\n'; + vim_memmove(p + 1, p + 2, STRLEN(p)); + } + ++p; + } + *p = NUL; +} + +/* + * write string to viminfo file + * - replace CTRL-V with CTRL-V CTRL-V + * - replace '\n' with CTRL-V 'n' + * - add a '\n' at the end + */ + void +viminfo_writestring(fd, p) + FILE *fd; + char_u *p; +{ + register int c; + + while ((c = *p++) != NUL) + { + if (c == Ctrl('V') || c == '\n') + { + putc(Ctrl('V'), fd); + if (c == '\n') + c = 'n'; + } + putc(c, fd); + } + putc('\n', fd); +} +#endif /* VIMINFO */ + +/* + * Implementation of ":fixdel", also used by get_stty(). + * resulting + * ^? ^H + * not ^? ^? + */ + void +do_fixdel() +{ + char_u *p; + + p = find_termcode((char_u *)"kb"); + add_termcode((char_u *)"kD", p != NULL && *p == 0x7f ? + (char_u *)"\010" : (char_u *)"\177"); +} + + void +print_line(lnum, use_number) + linenr_t lnum; + int use_number; +{ + char_u numbuf[20]; + + msg_outchar('\n'); + if (curwin->w_p_nu || use_number) + { + sprintf((char *)numbuf, "%7ld ", (long)lnum); + set_highlight('n'); /* Highlight line numbers */ + start_highlight(); + msg_outstr(numbuf); + stop_highlight(); + } + msg_prt_line(ml_get(lnum)); +} + +/* + * Implementation of ":file [fname]". + */ + void +do_file(arg, forceit) + char_u *arg; + int forceit; +{ + char_u *fname, *sfname; + BUF *buf; + + if (*arg != NUL) + { + /* + * The name of the current buffer will be changed. + * A new buffer entry needs to be made to hold the old + * file name, which will become the alternate file name. + */ + fname = curbuf->b_filename; + sfname = curbuf->b_sfilename; + curbuf->b_filename = NULL; + curbuf->b_sfilename = NULL; + if (setfname(arg, NULL, TRUE) == FAIL) + { + curbuf->b_filename = fname; + curbuf->b_sfilename = sfname; + return; + } + curbuf->b_notedited = TRUE; + buf = buflist_new(fname, sfname, curwin->w_cursor.lnum, FALSE); + if (buf != NULL) + curwin->w_alt_fnum = buf->b_fnum; + vim_free(fname); + vim_free(sfname); + } + /* print full filename if :cd used */ + fileinfo(did_cd, FALSE, forceit); +} diff --git a/usr.bin/vim/cmdline.c b/usr.bin/vim/cmdline.c new file mode 100644 index 00000000000..5477c4f3a05 --- /dev/null +++ b/usr.bin/vim/cmdline.c @@ -0,0 +1,5383 @@ +/* $OpenBSD: cmdline.c,v 1.1.1.1 1996/09/07 21:40:26 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * cmdline.c: functions for reading in the command line and executing it + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" +#include "cmdtab.h" +#include "ops.h" /* included because we call functions in ops.c */ +#ifdef HAVE_FCNTL_H +# include /* for chdir() */ +#endif + +/* + * variables shared between getcmdline() and redrawcmdline() + */ +static char_u *cmdbuff; /* pointer to command line buffer */ +static int cmdbufflen; /* length of cmdbuff */ +static int cmdlen; /* number of chars on command line */ +static int cmdpos; /* current cursor position */ +static int cmdspos; /* cursor column on screen */ +static int cmdfirstc; /* ':', '/' or '?' */ + +/* + * Typing mode on the command line. Shared by getcmdline() and + * put_on_cmdline(). + */ +static int overstrike = FALSE; /* typing mode */ + +/* + * The next two variables contain the bounds of any range given in a command. + * They are set by do_cmdline(). + */ +static linenr_t line1, line2; + +static int forceit; +static int regname; +static int quitmore = 0; +static int cmd_numfiles = -1; /* number of files found by + filename completion */ +/* + * There are two history tables: + * 0: colon commands + * 1: search commands + */ +static char_u **(history[2]) = {NULL, NULL}; /* history tables */ +static int hisidx[2] = {-1, -1}; /* last entered entry */ +static int hislen = 0; /* actual lengt of history tables */ + +#ifdef RIGHTLEFT +static int cmd_hkmap = 0; /* Hebrew mapping during command line */ +#endif + +static void init_history __ARGS((void)); + +static int is_in_history __ARGS((int, char_u *, int)); +static void putcmdline __ARGS((int)); +static void redrawcmd __ARGS((void)); +static void cursorcmd __ARGS((void)); +static int ccheck_abbr __ARGS((int)); +static char_u *do_one_cmd __ARGS((char_u **, int *, int)); +static int buf_write_all __ARGS((BUF *)); +static int do_write __ARGS((char_u *, int)); +static char_u *getargcmd __ARGS((char_u **)); +static void backslash_halve __ARGS((char_u *p, int expand_wildcards)); +static void do_make __ARGS((char_u *)); +static int do_arglist __ARGS((char_u *)); +static int is_backslash __ARGS((char_u *str)); +static int check_readonly __ARGS((void)); +static int check_changed __ARGS((BUF *, int, int)); +static int check_changed_any __ARGS((void)); +static int check_more __ARGS((int)); +static void vim_strncpy __ARGS((char_u *, char_u *, int)); +static int nextwild __ARGS((int)); +static int showmatches __ARGS((char_u *)); +static linenr_t get_address __ARGS((char_u **)); +static void set_expand_context __ARGS((int, char_u *)); +static char_u *set_one_cmd_context __ARGS((int, char_u *)); +static int ExpandFromContext __ARGS((char_u *, int *, char_u ***, int, int)); +static int ExpandCommands __ARGS((regexp *, int *, char_u ***)); + +/* + * init_history() - initialize the command line history + */ + static void +init_history() +{ + int newlen; /* new length of history table */ + char_u **temp; + register int i; + int j; + int type; + + /* + * If size of history table changed, reallocate it + */ + newlen = (int)p_hi; + if (newlen != hislen) /* history length changed */ + { + for (type = 0; type <= 1; ++type) /* adjust both history tables */ + { + if (newlen) + temp = (char_u **)lalloc((long_u)(newlen * sizeof(char_u *)), + TRUE); + else + temp = NULL; + if (newlen == 0 || temp != NULL) + { + if (hisidx[type] < 0) /* there are no entries yet */ + { + for (i = 0; i < newlen; ++i) + temp[i] = NULL; + } + else if (newlen > hislen) /* array becomes bigger */ + { + for (i = 0; i <= hisidx[type]; ++i) + temp[i] = history[type][i]; + j = i; + for ( ; i <= newlen - (hislen - hisidx[type]); ++i) + temp[i] = NULL; + for ( ; j < hislen; ++i, ++j) + temp[i] = history[type][j]; + } + else /* array becomes smaller or 0 */ + { + j = hisidx[type]; + for (i = newlen - 1; ; --i) + { + if (i >= 0) /* copy newest entries */ + temp[i] = history[type][j]; + else /* remove older entries */ + vim_free(history[type][j]); + if (--j < 0) + j = hislen - 1; + if (j == hisidx[type]) + break; + } + hisidx[type] = newlen - 1; + } + vim_free(history[type]); + history[type] = temp; + } + } + hislen = newlen; + } +} + +/* + * check if command line 'str' is already in history + * 'type' is 0 for ':' commands, '1' for search commands + * if 'move_to_front' is TRUE, matching entry is moved to end of history + */ + static int +is_in_history(type, str, move_to_front) + int type; + char_u *str; + int move_to_front; /* Move the entry to the front if it exists */ +{ + int i; + int last_i = -1; + + if (hisidx[type] < 0) + return FALSE; + i = hisidx[type]; + do + { + if (history[type][i] == NULL) + return FALSE; + if (STRCMP(str, history[type][i]) == 0) + { + if (!move_to_front) + return TRUE; + last_i = i; + break; + } + if (--i < 0) + i = hislen - 1; + } while (i != hisidx[type]); + + if (last_i >= 0) + { + str = history[type][i]; + while (i != hisidx[type]) + { + if (++i >= hislen) + i = 0; + history[type][last_i] = history[type][i]; + last_i = i; + } + history[type][i] = str; + return TRUE; + } + return FALSE; +} + +/* + * Add the given string to the given history. If the string is already in the + * history then it is moved to the front. histype may be 0 for the ':' + * history, or 1 for the '/' history. + */ + void +add_to_history(histype, new_entry) + int histype; + char_u *new_entry; +{ + if (hislen != 0 && !is_in_history(histype, new_entry, TRUE)) + { + if (++hisidx[histype] == hislen) + hisidx[histype] = 0; + vim_free(history[histype][hisidx[histype]]); + history[histype][hisidx[histype]] = strsave(new_entry); + } +} + + +/* + * getcmdline() - accept a command line starting with ':', '/', or '?' + * + * The line is collected in cmdbuff, which is reallocated to fit the command + * line. + * + * Return pointer to allocated string if there is a commandline, NULL + * otherwise. + */ + + char_u * +getcmdline(firstc, count) + int firstc; /* either ':', '/', or '?' */ + long count; /* only used for incremental search */ +{ + register int c; +#ifdef DIGRAPHS + int cc; +#endif + register int i; + int j; + char_u *p; + int hiscnt; /* current history line in use */ + char_u *lookfor = NULL; /* string to match */ + int gotesc = FALSE; /* TRUE when just typed */ + int do_abbr; /* when TRUE check for abbr. */ + int histype; /* history type to be used */ + FPOS old_cursor; + colnr_t old_curswant; + int did_incsearch = FALSE; + int incsearch_postponed = FALSE; + int save_msg_scroll = msg_scroll; + int some_key_typed = FALSE; /* one of the keys was typed */ +#ifdef USE_MOUSE + /* mouse drag and release events are ignored, unless they are + * preceded with a mouse down event */ + int ignore_drag_release = TRUE; +#endif + + overstrike = FALSE; /* always start in insert mode */ + old_cursor = curwin->w_cursor; /* needs to be restored later */ + old_curswant = curwin->w_curswant; +/* + * set some variables for redrawcmd() + */ + cmdfirstc = firstc; + alloc_cmdbuff(0); /* allocate initial cmdbuff */ + if (cmdbuff == NULL) + return NULL; /* out of memory */ + cmdlen = cmdpos = 0; + cmdspos = 1; + State = CMDLINE; +#ifdef USE_MOUSE + setmouse(); +#endif + gotocmdline(TRUE); + msg_outchar(firstc); + /* + * Avoid scrolling when called by a recursive do_cmdline(), e.g. when doing + * ":@0" when register 0 doesn't contain a CR. + */ + msg_scroll = FALSE; + + init_history(); + hiscnt = hislen; /* set hiscnt to impossible history value */ + histype = (firstc == ':' ? 0 : 1); + +#ifdef DIGRAPHS + do_digraph(-1); /* init digraph typahead */ +#endif + + /* collect the command string, handling editing keys */ + for (;;) + { + cursorcmd(); /* set the cursor on the right spot */ + c = vgetc(); + if (KeyTyped) + { + some_key_typed = TRUE; +#ifdef RIGHTLEFT + if (cmd_hkmap) + c = hkmap(c); +#endif + } + if (c == Ctrl('C')) + got_int = FALSE; /* ignore got_int when CTRL-C was typed here */ + + /* free old command line when finished moving around in the + * history list */ + if (lookfor && c != K_S_DOWN && c != K_S_UP && + c != K_DOWN && c != K_UP && + c != K_PAGEDOWN && c != K_PAGEUP && + (cmd_numfiles > 0 || (c != Ctrl('P') && c != Ctrl('N')))) + { + vim_free(lookfor); + lookfor = NULL; + } + + /* + * works like CTRL-P (unless 'wc' is ). + */ + if (c != p_wc && c == K_S_TAB) + c = Ctrl('P'); + + /* free expanded names when finished walking through matches */ + if (cmd_numfiles != -1 && !(c == p_wc && KeyTyped) && c != Ctrl('N') && + c != Ctrl('P') && c != Ctrl('A') && c != Ctrl('L')) + (void)ExpandOne(NULL, NULL, 0, WILD_FREE); + +#ifdef DIGRAPHS + c = do_digraph(c); +#endif + + if (c == '\n' || c == '\r' || (c == ESC && (!KeyTyped || + vim_strchr(p_cpo, CPO_ESC) != NULL))) + { + if (ccheck_abbr(c + ABBR_OFF)) + goto cmdline_changed; + outchar('\r'); /* show that we got the return */ + screen_cur_col = 0; + flushbuf(); + break; + } + + /* hitting twice means: abandon command line */ + /* wildcard expansion is only done when the key is really typed, + * not when it comes from a macro */ + if (c == p_wc && !gotesc && KeyTyped) + { + if (cmd_numfiles > 0) /* typed p_wc twice */ + i = nextwild(WILD_NEXT); + else /* typed p_wc first time */ + i = nextwild(WILD_EXPAND_KEEP); + if (c == ESC) + gotesc = TRUE; + if (i) + goto cmdline_changed; + } + gotesc = FALSE; + + if (c == NUL || c == K_ZERO) /* NUL is stored as NL */ + c = NL; + + do_abbr = TRUE; /* default: check for abbreviation */ + switch (c) + { + case K_BS: + case Ctrl('H'): + case K_DEL: + case Ctrl('W'): + /* + * delete current character is the same as backspace on next + * character, except at end of line + */ + if (c == K_DEL && cmdpos != cmdlen) + ++cmdpos; + if (cmdpos > 0) + { + j = cmdpos; + if (c == Ctrl('W')) + { + while (cmdpos && vim_isspace(cmdbuff[cmdpos - 1])) + --cmdpos; + i = iswordchar(cmdbuff[cmdpos - 1]); + while (cmdpos && !vim_isspace(cmdbuff[cmdpos - 1]) && + iswordchar(cmdbuff[cmdpos - 1]) == i) + --cmdpos; + } + else + --cmdpos; + cmdlen -= j - cmdpos; + i = cmdpos; + while (i < cmdlen) + cmdbuff[i++] = cmdbuff[j++]; + redrawcmd(); + } + else if (cmdlen == 0 && c != Ctrl('W')) + { + vim_free(cmdbuff); /* no commandline to return */ + cmdbuff = NULL; + msg_pos(-1, 0); + msg_outchar(' '); /* delete ':' */ + redraw_cmdline = TRUE; + goto returncmd; /* back to cmd mode */ + } + goto cmdline_changed; + + case K_INS: + overstrike = !overstrike; + /* should change shape of cursor */ + goto cmdline_not_changed; + +/* case '@': only in very old vi */ + case Ctrl('U'): + cmdpos = 0; + cmdlen = 0; + cmdspos = 1; + redrawcmd(); + goto cmdline_changed; + + case ESC: /* get here if p_wc != ESC or when ESC typed twice */ + case Ctrl('C'): + gotesc = TRUE; /* will free cmdbuff after putting it in + history */ + goto returncmd; /* back to cmd mode */ + + case Ctrl('R'): /* insert register */ + putcmdline('"'); + ++no_mapping; + c = vgetc(); + --no_mapping; + if (c != ESC) /* use ESC to cancel inserting register */ + cmdline_paste(c); + redrawcmd(); + goto cmdline_changed; + + case Ctrl('D'): + { + if (showmatches(cmdbuff) == FAIL) + break; /* Use ^D as normal char instead */ + + redrawcmd(); + continue; /* don't do incremental search now */ + } + + case K_RIGHT: + case K_S_RIGHT: + do + { + if (cmdpos >= cmdlen) + break; + cmdspos += charsize(cmdbuff[cmdpos]); + ++cmdpos; + } + while (c == K_S_RIGHT && cmdbuff[cmdpos] != ' '); + goto cmdline_not_changed; + + case K_LEFT: + case K_S_LEFT: + do + { + if (cmdpos <= 0) + break; + --cmdpos; + cmdspos -= charsize(cmdbuff[cmdpos]); + } + while (c == K_S_LEFT && cmdbuff[cmdpos - 1] != ' '); + goto cmdline_not_changed; + +#ifdef USE_MOUSE + case K_MIDDLEDRAG: + case K_MIDDLERELEASE: + case K_IGNORE: + goto cmdline_not_changed; /* Ignore mouse */ + + case K_MIDDLEMOUSE: +# ifdef USE_GUI + /* When GUI is active, also paste when 'mouse' is empty */ + if (!gui.in_use) +# endif + if (!mouse_has(MOUSE_COMMAND)) + goto cmdline_not_changed; /* Ignore mouse */ +# ifdef USE_GUI + if (gui.in_use && yankbuffer == 0) + cmdline_paste('*'); + else +# endif + cmdline_paste(yankbuffer); + redrawcmd(); + goto cmdline_changed; + + case K_LEFTDRAG: + case K_LEFTRELEASE: + case K_RIGHTDRAG: + case K_RIGHTRELEASE: + if (ignore_drag_release) + goto cmdline_not_changed; + /* FALLTHROUGH */ + case K_LEFTMOUSE: + case K_RIGHTMOUSE: + if (c == K_LEFTRELEASE || c == K_RIGHTRELEASE) + ignore_drag_release = TRUE; + else + ignore_drag_release = FALSE; +# ifdef USE_GUI + /* When GUI is active, also move when 'mouse' is empty */ + if (!gui.in_use) +# endif + if (!mouse_has(MOUSE_COMMAND)) + goto cmdline_not_changed; /* Ignore mouse */ + cmdspos = 1; + for (cmdpos = 0; cmdpos < cmdlen; ++cmdpos) + { + i = charsize(cmdbuff[cmdpos]); + if (mouse_row <= cmdline_row + cmdspos / Columns && + mouse_col < cmdspos % Columns + i) + break; + cmdspos += i; + } + goto cmdline_not_changed; +#endif /* USE_MOUSE */ + +#ifdef USE_GUI + case K_SCROLLBAR: + if (!msg_scrolled) + { + gui_do_scroll(); + redrawcmd(); + } + goto cmdline_not_changed; + + case K_HORIZ_SCROLLBAR: + if (!msg_scrolled) + { + gui_do_horiz_scroll(); + redrawcmd(); + } + goto cmdline_not_changed; +#endif + + case Ctrl('B'): /* begin of command line */ + case K_HOME: + cmdpos = 0; + cmdspos = 1; + goto cmdline_not_changed; + + case Ctrl('E'): /* end of command line */ + case K_END: + cmdpos = cmdlen; + cmdbuff[cmdlen] = NUL; + cmdspos = strsize(cmdbuff) + 1; + goto cmdline_not_changed; + + case Ctrl('A'): /* all matches */ + if (!nextwild(WILD_ALL)) + break; + goto cmdline_changed; + + case Ctrl('L'): /* longest common part */ + if (!nextwild(WILD_LONGEST)) + break; + goto cmdline_changed; + + case Ctrl('N'): /* next match */ + case Ctrl('P'): /* previous match */ + if (cmd_numfiles > 0) + { + if (!nextwild((c == Ctrl('P')) ? WILD_PREV : WILD_NEXT)) + break; + goto cmdline_changed; + } + + case K_UP: + case K_DOWN: + case K_S_UP: + case K_S_DOWN: + case K_PAGEUP: + case K_PAGEDOWN: + if (hislen == 0) /* no history */ + goto cmdline_not_changed; + + i = hiscnt; + + /* save current command string so it can be restored later */ + cmdbuff[cmdpos] = NUL; + if (lookfor == NULL && (lookfor = strsave(cmdbuff)) == NULL) + goto cmdline_not_changed; + + j = STRLEN(lookfor); + for (;;) + { + /* one step backwards */ + if (c == K_UP || c == K_S_UP || c == Ctrl('P') || + c == K_PAGEUP) + { + if (hiscnt == hislen) /* first time */ + hiscnt = hisidx[histype]; + else if (hiscnt == 0 && hisidx[histype] != hislen - 1) + hiscnt = hislen - 1; + else if (hiscnt != hisidx[histype] + 1) + --hiscnt; + else /* at top of list */ + { + hiscnt = i; + break; + } + } + else /* one step forwards */ + { + /* on last entry, clear the line */ + if (hiscnt == hisidx[histype]) + { + hiscnt = hislen; + break; + } + /* not on a history line, nothing to do */ + if (hiscnt == hislen) + break; + if (hiscnt == hislen - 1) /* wrap around */ + hiscnt = 0; + else + ++hiscnt; + } + if (hiscnt < 0 || history[histype][hiscnt] == NULL) + { + hiscnt = i; + break; + } + if ((c != K_UP && c != K_DOWN) || hiscnt == i || + STRNCMP(history[histype][hiscnt], + lookfor, (size_t)j) == 0) + break; + } + + if (hiscnt != i) /* jumped to other entry */ + { + vim_free(cmdbuff); + if (hiscnt == hislen) + p = lookfor; /* back to the old one */ + else + p = history[histype][hiscnt]; + + alloc_cmdbuff((int)STRLEN(p)); + if (cmdbuff == NULL) + goto returncmd; + STRCPY(cmdbuff, p); + + cmdpos = cmdlen = STRLEN(cmdbuff); + redrawcmd(); + goto cmdline_changed; + } + beep_flush(); + goto cmdline_not_changed; + + case Ctrl('V'): + case Ctrl('Q'): +#ifdef USE_MOUSE + ignore_drag_release = TRUE; +#endif + putcmdline('^'); + c = get_literal(); /* get next (two) character(s) */ + do_abbr = FALSE; /* don't do abbreviation now */ + break; + +#ifdef DIGRAPHS + case Ctrl('K'): +#ifdef USE_MOUSE + ignore_drag_release = TRUE; +#endif + putcmdline('?'); + ++no_mapping; + ++allow_keys; + c = vgetc(); + --no_mapping; + --allow_keys; + if (c != ESC) /* ESC cancels CTRL-K */ + { + if (IS_SPECIAL(c)) /* insert special key code */ + break; + if (charsize(c) == 1) + putcmdline(c); + ++no_mapping; + ++allow_keys; + cc = vgetc(); + --no_mapping; + --allow_keys; + if (cc != ESC) /* ESC cancels CTRL-K */ + { + c = getdigraph(c, cc, TRUE); + break; + } + } + redrawcmd(); + goto cmdline_not_changed; +#endif /* DIGRAPHS */ + +#ifdef RIGHTLEFT + case Ctrl('_'): /* CTRL-_: switch language mode */ + cmd_hkmap = !cmd_hkmap; + goto cmdline_not_changed; +#endif + + default: + /* + * Normal character with no special meaning. Just set mod_mask + * to 0x0 so that typing Shift-Space in the GUI doesn't enter + * the string . This should only happen after ^V. + */ + if (!IS_SPECIAL(c)) + mod_mask = 0x0; + break; + } + + /* we come here if we have a normal character */ + + if (do_abbr && (IS_SPECIAL(c) || !iswordchar(c)) && ccheck_abbr(c)) + goto cmdline_changed; + + /* + * put the character in the command line + */ + if (IS_SPECIAL(c) || mod_mask != 0x0) + put_on_cmdline(get_special_key_name(c, mod_mask), -1, TRUE); + else + { + IObuff[0] = c; + put_on_cmdline(IObuff, 1, TRUE); + } + goto cmdline_changed; + +/* + * This part implements incremental searches for "/" and "?" + * Jump to cmdline_not_changed when a character has been read but the command + * line did not change. Then we only search and redraw if something changed in + * the past. + * Jump to cmdline_changed when the command line did change. + * (Sorry for the goto's, I know it is ugly). + */ +cmdline_not_changed: + if (!incsearch_postponed) + continue; + +cmdline_changed: + if (p_is && (firstc == '/' || firstc == '?')) + { + /* if there is a character waiting, search and redraw later */ + if (char_avail()) + { + incsearch_postponed = TRUE; + continue; + } + incsearch_postponed = FALSE; + curwin->w_cursor = old_cursor; /* start at old position */ + + /* If there is no command line, don't do anything */ + if (cmdlen == 0) + i = 0; + else + { + cmdbuff[cmdlen] = NUL; + emsg_off = TRUE; /* So it doesn't beep if bad expr */ + i = do_search(firstc, cmdbuff, count, + SEARCH_KEEP + SEARCH_OPT + SEARCH_NOOF); + emsg_off = FALSE; + } + if (i) + { + highlight_match = TRUE; /* highlight position */ + cursupdate(); + } + else + { + highlight_match = FALSE; /* don't highlight */ + /* vim_beep(); */ /* even beeps when invalid expr, e.g. "[" */ + } + updateScreen(NOT_VALID); + redrawcmdline(); + did_incsearch = TRUE; + } + } + +returncmd: + if (did_incsearch) + { + curwin->w_cursor = old_cursor; + curwin->w_curswant = old_curswant; + highlight_match = FALSE; + redraw_later(NOT_VALID); + } + if (cmdbuff != NULL) + { + /* + * Put line in history buffer (":" only when it was typed). + */ + cmdbuff[cmdlen] = NUL; + if (cmdlen != 0 && (some_key_typed || firstc != ':')) + { + add_to_history(histype, cmdbuff); + if (firstc == ':') + { + vim_free(new_last_cmdline); + new_last_cmdline = strsave(cmdbuff); + } + } + + if (gotesc) /* abandon command line */ + { + vim_free(cmdbuff); + cmdbuff = NULL; + MSG(""); + redraw_cmdline = TRUE; + } + } + + /* + * If the screen was shifted up, redraw the whole screen (later). + * If the line is too long, clear it, so ruler and shown command do + * not get printed in the middle of it. + */ + msg_check(); + msg_scroll = save_msg_scroll; + State = NORMAL; +#ifdef USE_MOUSE + setmouse(); +#endif + return cmdbuff; +} + +/* + * Put the given string, of the given length, onto the command line. + * If len is -1, then STRLEN() is used to calculate the length. + * If 'redraw' is TRUE then the new part of the command line, and the remaining + * part will be redrawn, otherwise it will not. If this function is called + * twice in a row, then 'redraw' should be FALSE and redrawcmd() should be + * called afterwards. + */ + int +put_on_cmdline(str, len, redraw) + char_u *str; + int len; + int redraw; +{ + int i; + + if (len < 0) + len = STRLEN(str); + + /* Check if cmdbuff needs to be longer */ + if (cmdlen + len + 1 >= cmdbufflen) + i = realloc_cmdbuff(cmdlen + len); + else + i = OK; + if (i == OK) + { + if (!overstrike) + { + vim_memmove(cmdbuff + cmdpos + len, cmdbuff + cmdpos, + (size_t)(cmdlen - cmdpos)); + cmdlen += len; + } + else if (cmdpos + len > cmdlen) + cmdlen = cmdpos + len; + vim_memmove(cmdbuff + cmdpos, str, (size_t)len); + if (redraw) + msg_outtrans_len(cmdbuff + cmdpos, cmdlen - cmdpos); + cmdpos += len; + while (len--) + cmdspos += charsize(str[len]); + } + if (redraw) + msg_check(); + return i; +} + + void +alloc_cmdbuff(len) + int len; +{ + /* + * give some extra space to avoid having to allocate all the time + */ + if (len < 80) + len = 100; + else + len += 20; + + cmdbuff = alloc(len); /* caller should check for out of memory */ + cmdbufflen = len; +} + +/* + * Re-allocate the command line to length len + something extra. + * return FAIL for failure, OK otherwise + */ + int +realloc_cmdbuff(len) + int len; +{ + char_u *p; + + p = cmdbuff; + alloc_cmdbuff(len); /* will get some more */ + if (cmdbuff == NULL) /* out of memory */ + { + cmdbuff = p; /* keep the old one */ + return FAIL; + } + vim_memmove(cmdbuff, p, (size_t)cmdlen); + vim_free(p); + return OK; +} + +/* + * put a character on the command line. + * Used for CTRL-V and CTRL-K + */ + static void +putcmdline(c) + int c; +{ + char_u buf[1]; + + buf[0] = c; + msg_outtrans_len(buf, 1); + msg_outtrans_len(cmdbuff + cmdpos, cmdlen - cmdpos); + cursorcmd(); +} + +/* + * this fuction is called when the screen size changes and with incremental + * search + */ + void +redrawcmdline() +{ + msg_scrolled = 0; + need_wait_return = FALSE; + compute_cmdrow(); + redrawcmd(); + cursorcmd(); +} + + void +compute_cmdrow() +{ + cmdline_row = lastwin->w_winpos + lastwin->w_height + + lastwin->w_status_height; +} + +/* + * Redraw what is currently on the command line. + */ + static void +redrawcmd() +{ + register int i; + + msg_start(); + msg_outchar(cmdfirstc); + msg_outtrans_len(cmdbuff, cmdlen); + msg_clr_eos(); + + cmdspos = 1; + for (i = 0; i < cmdlen && i < cmdpos; ++i) + cmdspos += charsize(cmdbuff[i]); + /* + * An emsg() before may have set msg_scroll and need_sleep. These are used + * in normal mode, in cmdline mode we can reset them now. + */ + msg_scroll = FALSE; /* next message overwrites cmdline */ +#ifdef SLEEP_IN_EMSG + need_sleep = FALSE; /* don't sleep */ +#endif +} + + static void +cursorcmd() +{ + msg_pos(cmdline_row + (cmdspos / (int)Columns), cmdspos % (int)Columns); + windgoto(msg_row, msg_col); +} + +/* + * Check the word in front of the cursor for an abbreviation. + * Called when the non-id character "c" has been entered. + * When an abbreviation is recognized it is removed from the text with + * backspaces and the replacement string is inserted, followed by "c". + */ + static int +ccheck_abbr(c) + int c; +{ + if (p_paste || no_abbr) /* no abbreviations or in paste mode */ + return FALSE; + + return check_abbr(c, cmdbuff, cmdpos, 0); +} + +/* + * do_cmdline(): execute an Ex command line + * + * 1. If no line given, get one. + * 2. Split up in parts separated with '|'. + * + * This function may be called recursively! + * + * If 'sourcing' is TRUE, the command will be included in the error message. + * If 'repeating' is TRUE, there is no wait_return() and friends. + * + * return FAIL if commandline could not be executed, OK otherwise + */ + int +do_cmdline(cmdline, sourcing, repeating) + char_u *cmdline; + int sourcing; + int repeating; +{ + int cmdlinelen; + char_u *nextcomm; + static int recursive = 0; /* recursive depth */ + int got_cmdline = FALSE; /* TRUE when cmdline was typed */ + int msg_didout_before_start; + +/* + * 1. If no line given: Get a line in cmdbuff. + * If a line is given: Copy it into cmdbuff. + * After this we don't use cmdbuff but cmdline, because of recursiveness + */ + if (cmdline == NULL) + { + if ((cmdline = getcmdline(':', 1L)) == NULL) + { + /* don't call wait_return for aborted command line */ + need_wait_return = FALSE; + return FAIL; + } + got_cmdline = TRUE; + } + else + { + /* Make a copy of the command so we can mess with it. */ + alloc_cmdbuff((int)STRLEN(cmdline)); + if (cmdbuff == NULL) + return FAIL; + STRCPY(cmdbuff, cmdline); + cmdline = cmdbuff; + } + cmdlinelen = cmdbufflen; /* we need to copy it for recursiveness */ + +/* + * All output from the commands is put below each other, without waiting for a + * return. Don't do this when executing commands from a script or when being + * called recursive (e.g. for ":e +command file"). + */ + msg_didout_before_start = msg_didout; + if (!repeating && !recursive) + { + msg_didany = FALSE; /* no output yet */ + msg_start(); + msg_scroll = TRUE; /* put messages below each other */ +#ifdef SLEEP_IN_EMSG + ++dont_sleep; /* don't sleep in emsg() */ +#endif + ++no_wait_return; /* dont wait for return until finished */ + ++RedrawingDisabled; + } + +/* + * 2. Loop for each '|' separated command. + * do_one_cmd will set nextcomm to NULL if there is no trailing '|'. + * cmdline and cmdlinelen may change, e.g. for '%' and '#' expansion. + */ + ++recursive; + for (;;) + { + nextcomm = do_one_cmd(&cmdline, &cmdlinelen, sourcing); + if (nextcomm == NULL) + break; + STRCPY(cmdline, nextcomm); + } + --recursive; + vim_free(cmdline); + +/* + * If there was too much output to fit on the command line, ask the user to + * hit return before redrawing the screen. With the ":global" command we do + * this only once after the command is finished. + */ + if (!repeating && !recursive) + { + --RedrawingDisabled; +#ifdef SLEEP_IN_EMSG + --dont_sleep; +#endif + --no_wait_return; + msg_scroll = FALSE; + if (need_wait_return || (msg_check() && !dont_wait_return)) + { + /* + * The msg_start() above clears msg_didout. The wait_return we do + * here should not overwrite the command that may be shown before + * doing that. + */ + msg_didout = msg_didout_before_start; + wait_return(FALSE); + } + } + +/* + * If the command was typed, remember it for register : + * Do this AFTER executing the command to make :@: work. + */ + if (got_cmdline && new_last_cmdline != NULL) + { + vim_free(last_cmdline); + last_cmdline = new_last_cmdline; + new_last_cmdline = NULL; + } + return OK; +} + +static char *(make_cmd_chars[6]) = +{ " \164\145a", + "\207\171\204\170\060\175\171\174\173\117\032", + " c\157\146\146e\145", + "\200\174\165\161\203\165\060\171\176\203\165\202\204\060\163\177\171\176\060\204\177\060\202\205\176\060\175\161\173\165\032", + " \164o\141\163t", + "\136\137\122\137\124\151\060\165\210\200\165\163\204\203\060\204\170\165\060\143\200\161\176\171\203\170\060\171\176\201\205\171\203\171\204\171\177\176\061\032" +}; + +/* + * Execute one Ex command. + * + * If 'sourcing' is TRUE, the command will be included in the error message. + * + * 2. skip comment lines and leading space + * 3. parse range + * 4. parse command + * 5. parse arguments + * 6. switch on command name + * + * This function may be called recursively! + */ + static char_u * +do_one_cmd(cmdlinep, cmdlinelenp, sourcing) + char_u **cmdlinep; + int *cmdlinelenp; + int sourcing; +{ + char_u *p; + char_u *q; + char_u *s; + char_u *cmd, *arg; + char_u *do_ecmd_cmd = NULL; /* +command for do_ecmd() */ + linenr_t do_ecmd_lnum = 0; /* lnum file for do_ecmd() */ + int i = 0; /* init to shut up gcc */ + int len; + int cmdidx; + long argt; + register linenr_t lnum; + long n = 0; /* init to shut up gcc */ + int addr_count; /* number of address specs */ + FPOS pos; + int append = FALSE; /* write with append */ + int usefilter = FALSE; /* no read/write but filter */ + char_u *nextcomm = NULL; /* no next command yet */ + int amount = 0; /* for ":>"; init for gcc */ + char_u *errormsg = NULL; /* error message */ + WIN *old_curwin = NULL; /* init for GCC */ + + /* when not editing the last file :q has to be typed twice */ + if (quitmore) + --quitmore; + did_emsg = FALSE; /* will be set to TRUE when emsg() used, in which + * case we set nextcomm to NULL to cancel the + * whole command line */ +/* + * 2. skip comment lines and leading space and colons + */ + for (cmd = *cmdlinep; vim_strchr((char_u *)" \t:", *cmd) != NULL; cmd++) + ; + + if (*cmd == '"' || *cmd == NUL) /* ignore comment and empty lines */ + goto doend; + +/* + * 3. parse a range specifier of the form: addr [,addr] [;addr] .. + * + * where 'addr' is: + * + * % (entire file) + * $ [+-NUM] + * 'x [+-NUM] (where x denotes a currently defined mark) + * . [+-NUM] + * [+-NUM].. + * NUM + * + * The cmd pointer is updated to point to the first character following the + * range spec. If an initial address is found, but no second, the upper bound + * is equal to the lower. + */ + + addr_count = 0; + --cmd; + do + { + line1 = line2; + line2 = curwin->w_cursor.lnum; /* default is current line number */ + cmd = skipwhite(cmd + 1); /* skip ',' or ';' and following ' ' */ + lnum = get_address(&cmd); + if (cmd == NULL) /* error detected */ + goto doend; + if (lnum == MAXLNUM) + { + if (*cmd == '%') /* '%' - all lines */ + { + ++cmd; + line1 = 1; + line2 = curbuf->b_ml.ml_line_count; + ++addr_count; + } + else if (*cmd == '*') /* '*' - visual area */ + { + FPOS *fp; + + ++cmd; + fp = getmark('<', FALSE); + if (check_mark(fp) == FAIL) + goto doend; + line1 = fp->lnum; + fp = getmark('>', FALSE); + if (check_mark(fp) == FAIL) + goto doend; + line2 = fp->lnum; + ++addr_count; + } + } + else + line2 = lnum; + addr_count++; + + if (*cmd == ';') + { + if (line2 == 0) + curwin->w_cursor.lnum = 1; + else + curwin->w_cursor.lnum = line2; + } + } while (*cmd == ',' || *cmd == ';'); + + /* One address given: set start and end lines */ + if (addr_count == 1) + { + line1 = line2; + /* ... but only implicit: really no address given */ + if (lnum == MAXLNUM) + addr_count = 0; + } + +/* + * 4. parse command + */ + + /* + * Skip ':' and any white space + */ + cmd = skipwhite(cmd); + if (*cmd == ':') + cmd = skipwhite(cmd + 1); + + /* + * If we got a line, but no command, then go to the line. + * If we find a '|' or '\n' we set nextcomm. + */ + if (*cmd == NUL || *cmd == '"' || + ((*cmd == '|' || *cmd == '\n') && + (nextcomm = cmd + 1) != NULL)) /* just an assignment */ + { + /* + * strange vi behaviour: + * ":3" jumps to line 3 + * ":3|..." prints line 3 + * ":|" prints current line + */ + if (*cmd == '|') + { + cmdidx = CMD_print; + goto cmdswitch; /* UGLY goto */ + } + if (addr_count != 0) + { + if (line2 == 0) + curwin->w_cursor.lnum = 1; + else if (line2 > curbuf->b_ml.ml_line_count) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + else + curwin->w_cursor.lnum = line2; + beginline(MAYBE); + /* This causes problems for ":234", since displaying is disabled + * here */ + /* cursupdate(); */ + } + goto doend; + } + + /* + * Isolate the command and search for it in the command table. + * Exeptions: + * - the 'k' command can directly be followed by any character. + * - the 's' command can be followed directly by 'c', 'g' or 'r' + * but :sre[wind] is another command. + */ + if (*cmd == 'k') + { + cmdidx = CMD_k; + p = cmd + 1; + } + else if (*cmd == 's' && vim_strchr((char_u *)"cgr", cmd[1]) != NULL && + STRNCMP("sre", cmd, (size_t)3) != 0) + { + cmdidx = CMD_substitute; + p = cmd + 1; + } + else + { + p = cmd; + while (isalpha(*p)) + ++p; + /* check for non-alpha command */ + if (p == cmd && vim_strchr((char_u *)"@!=><&~#", *p) != NULL) + ++p; + i = (int)(p - cmd); + + for (cmdidx = 0; cmdidx < CMD_SIZE; ++cmdidx) + if (STRNCMP(cmdnames[cmdidx].cmd_name, (char *)cmd, (size_t)i) == 0) + break; + if (i == 0 || cmdidx == CMD_SIZE) + { + STRCPY(IObuff, "Not an editor command"); + if (!sourcing) + { + STRCAT(IObuff, ": "); + STRNCAT(IObuff, *cmdlinep, 40); + } + errormsg = IObuff; + goto doend; + } + } + + if (*p == '!') /* forced commands */ + { + ++p; + forceit = TRUE; + } + else + forceit = FALSE; + +/* + * 5. parse arguments + */ + argt = cmdnames[cmdidx].cmd_argt; + + if (!(argt & RANGE) && addr_count) /* no range allowed */ + { + errormsg = e_norange; + goto doend; + } + + if (!(argt & BANG) && forceit) /* no allowed */ + { + errormsg = e_nobang; + goto doend; + } + +/* + * If the range is backwards, ask for confirmation and, if given, swap + * line1 & line2 so it's forwards again. + * When global command is busy, don't ask, will fail below. + */ + if (!global_busy && line1 > line2) + { + if (sourcing) + { + errormsg = (char_u *)"Backwards range given"; + goto doend; + } + else if (ask_yesno((char_u *)"Backwards range given, OK to swap", FALSE) != 'y') + goto doend; + lnum = line1; + line1 = line2; + line2 = lnum; + } + /* + * don't complain about the range if it is not used + * (could happen if line_count is accidently set to 0) + */ + if (line1 < 0 || line2 < 0 || line1 > line2 || ((argt & RANGE) && + !(argt & NOTADR) && line2 > curbuf->b_ml.ml_line_count)) + { + errormsg = e_invrange; + goto doend; + } + + if ((argt & NOTADR) && addr_count == 0) /* default is 1, not cursor */ + line2 = 1; + + if (!(argt & ZEROR)) /* zero in range not allowed */ + { + if (line1 == 0) + line1 = 1; + if (line2 == 0) + line2 = 1; + } + + /* + * for the :make command we insert the 'makeprg' option here, + * so things like % get expanded + */ + if (cmdidx == CMD_make) + { + alloc_cmdbuff((int)(STRLEN(p_mp) + STRLEN(p) + 2)); + if (cmdbuff == NULL) /* out of memory */ + goto doend; + /* + * Check for special command characters and echo them. + */ + for (i = 0; i < 6; i += 2) + if (!STRCMP(make_cmd_chars[i], p)) + for (s = (char_u *)(make_cmd_chars[i + 1]); *s; ++s) + msg_outchar(*s - 16); + STRCPY(cmdbuff, p_mp); + STRCAT(cmdbuff, " "); + STRCAT(cmdbuff, p); + /* 'cmd' is not set here, because it is not used at CMD_make */ + vim_free(*cmdlinep); + *cmdlinep = cmdbuff; + *cmdlinelenp = cmdbufflen; + p = cmdbuff; + } + + /* + * Skip to start of argument. + * Don't do this for the ":!" command, because ":!! -l" needs the space. + */ + if (cmdidx == CMD_bang) + arg = p; + else + arg = skipwhite(p); + + if (cmdidx == CMD_write) + { + if (*arg == '>') /* append */ + { + if (*++arg != '>') /* typed wrong */ + { + errormsg = (char_u *)"Use w or w>>"; + goto doend; + } + arg = skipwhite(arg + 1); + append = TRUE; + } + else if (*arg == '!') /* :w !filter */ + { + ++arg; + usefilter = TRUE; + } + } + + if (cmdidx == CMD_read) + { + if (forceit) + { + usefilter = TRUE; /* :r! filter if forceit */ + forceit = FALSE; + } + else if (*arg == '!') /* :r !filter */ + { + ++arg; + usefilter = TRUE; + } + } + + if (cmdidx == CMD_lshift || cmdidx == CMD_rshift) + { + amount = 1; + while (*arg == *cmd) /* count number of '>' or '<' */ + { + ++arg; + ++amount; + } + arg = skipwhite(arg); + } + + /* + * Check for "+command" argument, before checking for next command. + * Don't do this for ":read !cmd" and ":write !cmd". + */ + if ((argt & EDITCMD) && !usefilter) + do_ecmd_cmd = getargcmd(&arg); + + /* + * Check for '|' to separate commands and '"' to start comments. + * Don't do this for ":read !cmd" and ":write !cmd". + */ + if ((argt & TRLBAR) && !usefilter) + { + for (p = arg; *p; ++p) + { + if (*p == Ctrl('V')) + { + if (argt & (USECTRLV | XFILE)) + ++p; /* skip CTRL-V and next char */ + else + STRCPY(p, p + 1); /* remove CTRL-V and skip next char */ + if (*p == NUL) /* stop at NUL after CTRL-V */ + break; + } + else if ((*p == '"' && !(argt & NOTRLCOM)) || + *p == '|' || *p == '\n') + { + /* + * We remove the '\' before the '|', unless USECTRLV is used + * AND 'b' is present in 'cpoptions'. + */ + if ((vim_strchr(p_cpo, CPO_BAR) == NULL || + !(argt & USECTRLV)) && *(p - 1) == '\\') + { + STRCPY(p - 1, p); /* remove the backslash */ + --p; + } + else + { + if (*p == '|' || *p == '\n') + nextcomm = p + 1; + *p = NUL; + break; + } + } + } + if (!(argt & NOTRLCOM)) /* remove trailing spaces */ + del_trailing_spaces(arg); + } + + /* + * Check for to end a shell command. + * Also do this for ":read !cmd" and ":write !cmd". + */ + else if (cmdidx == CMD_bang || usefilter) + { + for (p = arg; *p; ++p) + { + if (*p == '\\' && p[1]) + ++p; + else if (*p == '\n') + { + nextcomm = p + 1; + *p = NUL; + break; + } + } + } + + if ((argt & DFLALL) && addr_count == 0) + { + line1 = 1; + line2 = curbuf->b_ml.ml_line_count; + } + + regname = 0; + /* accept numbered register only when no count allowed (:put) */ + if ((argt & REGSTR) && *arg != NUL && is_yank_buffer(*arg, FALSE) && + !((argt & COUNT) && isdigit(*arg))) + { + regname = *arg; + arg = skipwhite(arg + 1); + } + + if ((argt & COUNT) && isdigit(*arg)) + { + n = getdigits(&arg); + arg = skipwhite(arg); + if (n <= 0) + { + errormsg = e_zerocount; + goto doend; + } + if (argt & NOTADR) /* e.g. :buffer 2, :sleep 3 */ + { + line2 = n; + if (addr_count == 0) + addr_count = 1; + } + else + { + line1 = line2; + line2 += n - 1; + ++addr_count; + /* + * Be vi compatible: no error message for out of range. + */ + if (line2 > curbuf->b_ml.ml_line_count) + line2 = curbuf->b_ml.ml_line_count; + } + } + /* no arguments allowed */ + if (!(argt & EXTRA) && *arg != NUL && + vim_strchr((char_u *)"|\"", *arg) == NULL) + { + errormsg = e_trailing; + goto doend; + } + + if ((argt & NEEDARG) && *arg == NUL) + { + errormsg = e_argreq; + goto doend; + } + + /* + * change '%' to curbuf->b_filename + * '#' to curwin->w_altfile + * '' to word under the cursor + * '' to WORD under the cursor + * '' to path name under the cursor + * '' to file name for autocommand + */ + if (argt & XFILE) + { + char_u *buf = NULL; + int expand_wildcards; /* need to expand wildcards */ + int spec_idx; + static char *(spec_str[]) = + { + "%", +#define SPEC_PERC 0 + "#", +#define SPEC_HASH 1 + "", /* cursor word */ +#define SPEC_CWORD 2 + "", /* cursor WORD */ +#define SPEC_CCWORD 3 + "", /* cursor path name */ +#define SPEC_CFILE 4 + "" /* autocommand file name */ +#define SPEC_AFILE 5 + }; +#define SPEC_COUNT 6 + + /* + * Decide to expand wildcards *before* replacing '%', '#', etc. If + * the file name contains a wildcard it should not cause expanding. + * (it will be expanded anyway if there is a wildcard before replacing). + */ + expand_wildcards = mch_has_wildcard(arg); + for (p = arg; *p; ++p) + { + /* + * Check if there is something to do. + */ + for (spec_idx = 0; spec_idx < SPEC_COUNT; ++spec_idx) + { + n = strlen(spec_str[spec_idx]); + if (STRNCMP(p, spec_str[spec_idx], n) == 0) + break; + } + if (spec_idx == SPEC_COUNT) /* no match */ + continue; + + /* + * Skip when preceded with a backslash "\%" and "\#". + * Note: In "\\%" the % is also not recognized! + */ + if (*(p - 1) == '\\') + { + --p; + STRCPY(p, p + 1); /* remove escaped char */ + continue; + } + + /* + * word or WORD under cursor + */ + if (spec_idx == SPEC_CWORD || spec_idx == SPEC_CCWORD) + { + len = find_ident_under_cursor(&q, spec_idx == SPEC_CWORD ? + (FIND_IDENT|FIND_STRING) : FIND_STRING); + if (len == 0) + goto doend; + } + + /* + * '#': Alternate file name + * '%': Current file name + * File name under the cursor + * File name for autocommand + * and following modifiers + */ + else + { + switch (spec_idx) + { + case SPEC_PERC: /* '%': current file */ + if (curbuf->b_filename == NULL) + { + errormsg = (char_u *)"No file name to substitute for '%'"; + goto doend; + } + q = curbuf->b_xfilename; + break; + + case SPEC_HASH: /* '#' or "#99": alternate file */ + q = p + 1; + i = (int)getdigits(&q); + n = q - p; /* length of what we expand */ + + if (buflist_name_nr(i, &q, &do_ecmd_lnum) == + FAIL) + { + errormsg = (char_u *)"no alternate filename to substitute for '#'"; + goto doend; + } + break; + + case SPEC_CFILE: /* file name under cursor */ + q = file_name_at_cursor(FNAME_MESS|FNAME_HYP); + if (q == NULL) + goto doend; + buf = q; + break; + + case SPEC_AFILE: /* file name for autocommand */ + q = autocmd_fname; + if (q == NULL) + { + errormsg = (char_u *)"no autocommand filename to substitute for \"\""; + goto doend; + } + break; + } + + len = STRLEN(q); /* length of new string */ + if (p[n] == '<') /* remove the file name extension */ + { + ++n; + if ((s = vim_strrchr(q, '.')) != NULL && s >= gettail(q)) + len = s - q; + } + else + { + char_u *tail; + + /* ":p" - full path/filename */ + if (p[n] == ':' && p[n + 1] == 'p') + { + n += 2; + s = FullName_save(q); + vim_free(buf); /* free any allocated file name */ + if (s == NULL) + goto doend; + q = s; + len = STRLEN(q); + buf = q; + } + + tail = gettail(q); + + /* ":h" - head, remove "/filename" */ + /* ":h" can be repeated */ + while (p[n] == ':' && p[n + 1] == 'h') + { + n += 2; + while (tail > q && ispathsep(tail[-1])) + --tail; + len = tail - q; + while (tail > q && !ispathsep(tail[-1])) + --tail; + } + + /* ":t" - tail, just the basename */ + if (p[n] == ':' && p[n + 1] == 't') + { + n += 2; + len -= tail - q; + q = tail; + } + + /* ":e" - extension */ + /* ":e" can be repeated */ + /* ":r" - root, without extension */ + /* ":r" can be repeated */ + while (p[n] == ':' && + (p[n + 1] == 'e' || p[n + 1] == 'r')) + { + /* find a '.' in the tail: + * - for second :e: before the current fname + * - otherwise: The last '.' + */ + if (p[n + 1] == 'e' && q > tail) + s = q - 2; + else + s = q + len - 1; + for ( ; s > tail; --s) + if (s[0] == '.') + break; + if (p[n + 1] == 'e') /* :e */ + { + if (s > tail) + { + len += q - (s + 1); + q = s + 1; + } + else if (q <= tail) + len = 0; + } + else /* :r */ + { + if (s > tail) /* remove one extension */ + len = s - q; + } + n += 2; + } + } + + /* TODO - ":s/pat/foo/" - substitute */ + /* if (p[n] == ':' && p[n + 1] == 's') */ + } + + /* + * The new command line is build in cmdbuff[]. + * First allocate it. + */ + i = STRLEN(*cmdlinep) + len + 3; + if (nextcomm) + i += STRLEN(nextcomm); /* add space for next command */ + alloc_cmdbuff(i); + if (cmdbuff == NULL) /* out of memory! */ + goto doend; + + i = p - *cmdlinep; /* length of part before c */ + vim_memmove(cmdbuff, *cmdlinep, (size_t)i); + vim_memmove(cmdbuff + i, q, (size_t)len); /* append the string */ + i += len; /* remember the end of the string */ + STRCPY(cmdbuff + i, p + n); /* append what is after '#' or '%' */ + p = cmdbuff + i - 1; /* remember where to continue */ + vim_free(buf); /* free any allocated string */ + + if (nextcomm) /* append next command */ + { + i = STRLEN(cmdbuff) + 1; + STRCPY(cmdbuff + i, nextcomm); + nextcomm = cmdbuff + i; + } + cmd = cmdbuff + (cmd - *cmdlinep); + arg = cmdbuff + (arg - *cmdlinep); + vim_free(*cmdlinep); + *cmdlinep = cmdbuff; + *cmdlinelenp = cmdbufflen; + } + + /* + * One file argument: expand wildcards. + * Don't do this with ":r !command" or ":w !command". + */ + if ((argt & NOSPC) && !usefilter) + { +#if defined(UNIX) + /* + * Only for Unix we check for more than one file name. + * For other systems spaces are considered to be part + * of the file name. + * Only check here if there is no wildcard, otherwise ExpandOne + * will check for errors. This allows ":e `ls ve*.c`" on Unix. + */ + if (!expand_wildcards) + for (p = arg; *p; ++p) + { + /* skip escaped characters */ + if (p[1] && (*p == '\\' || *p == Ctrl('V'))) + ++p; + else if (vim_iswhite(*p)) + { + errormsg = (char_u *)"Only one file name allowed"; + goto doend; + } + } +#endif + /* + * halve the number of backslashes (this is vi compatible) + */ + backslash_halve(arg, expand_wildcards); + + if (expand_wildcards) + { + if ((p = ExpandOne(arg, NULL, WILD_LIST_NOTFOUND, + WILD_EXPAND_FREE)) == NULL) + goto doend; + n = arg - *cmdlinep; + i = STRLEN(p) + n; + if (nextcomm) + i += STRLEN(nextcomm); + alloc_cmdbuff(i); + if (cmdbuff != NULL) + { + STRNCPY(cmdbuff, *cmdlinep, n); + STRCPY(cmdbuff + n, p); + if (nextcomm) /* append next command */ + { + i = STRLEN(cmdbuff) + 1; + STRCPY(cmdbuff + i, nextcomm); + nextcomm = cmdbuff + i; + } + cmd = cmdbuff + (cmd - *cmdlinep); + arg = cmdbuff + n; + vim_free(*cmdlinep); + *cmdlinep = cmdbuff; + *cmdlinelenp = cmdbufflen; + } + vim_free(p); + } + } + } + + /* + * Accept buffer name. Cannot be used at the same time with a buffer + * number. + */ + if ((argt & BUFNAME) && *arg && addr_count == 0) + { + /* + * :bdelete and :bunload take several arguments, separated by spaces: + * find next space (skipping over escaped characters). + * The others take one argument: ignore trailing spaces. + */ + if (cmdidx == CMD_bdelete || cmdidx == CMD_bunload) + p = skiptowhite_esc(arg); + else + { + p = arg + STRLEN(arg); + while (p > arg && vim_iswhite(p[-1])) + --p; + } + line2 = buflist_findpat(arg, p); + if (line2 < 0) /* failed */ + goto doend; + addr_count = 1; + arg = skipwhite(p); + } + +/* + * 6. switch on command name + * arg points to the argument of the command + * nextcomm points to the next command (if any) + * cmd points to the name of the command (except for :make) + * cmdidx is the index for the command + * forceit is TRUE if ! present + * addr_count is the number of addresses given + * line1 is the first line number + * line2 is the second line number or count + * do_ecmd_cmd is +command argument to be used in edited file + * do_ecmd_lnum is the line number in edited file + * append is TRUE with ":w >>file" command + * usefilter is TRUE with ":w !command" and ":r!command" + * amount is number of '>' or '<' for shift command + */ +cmdswitch: + switch (cmdidx) + { + /* + * quit current window, quit Vim if closed the last window + */ + case CMD_quit: + /* if more files or windows we won't exit */ + if (check_more(FALSE) == OK && only_one_window()) + exiting = TRUE; + if (check_changed(curbuf, FALSE, FALSE) || + check_more(TRUE) == FAIL || + (only_one_window() && check_changed_any())) + { + exiting = FALSE; + settmode(1); + break; + } + if (only_one_window()) /* quit last window */ + getout(0); + close_window(curwin, TRUE); /* may free buffer */ + break; + + /* + * try to quit all windows + */ + case CMD_qall: + exiting = TRUE; + if (!check_changed_any()) + getout(0); + exiting = FALSE; + settmode(1); + break; + + /* + * close current window, unless it is the last one + */ + case CMD_close: + close_window(curwin, FALSE); /* don't free buffer */ + break; + + /* + * close all but current window, unless it is the last one + */ + case CMD_only: + close_others(TRUE); + break; + + case CMD_stop: + case CMD_suspend: +#ifdef WIN32 + /* + * Check if external commands are allowed now. + */ + if (can_end_termcap_mode(TRUE) == FALSE) + break; +#endif + if (!forceit) + autowrite_all(); + windgoto((int)Rows - 1, 0); + outchar('\n'); + flushbuf(); + stoptermcap(); + mch_restore_title(3); /* restore window titles */ + mch_suspend(); /* call machine specific function */ + maketitle(); + starttermcap(); + scroll_start(); /* scroll screen before redrawing */ + must_redraw = CLEAR; + set_winsize(0, 0, FALSE); /* May have resized window -- webb */ + break; + + case CMD_exit: + case CMD_xit: + case CMD_wq: + /* if more files or windows we won't exit */ + if (check_more(FALSE) == OK && only_one_window()) + exiting = TRUE; + if (((cmdidx == CMD_wq || curbuf->b_changed) && + do_write(arg, FALSE) == FAIL) || + check_more(TRUE) == FAIL || + (only_one_window() && check_changed_any())) + { + exiting = FALSE; + settmode(1); + break; + } + if (only_one_window()) /* quit last window, exit Vim */ + getout(0); + close_window(curwin, TRUE); /* quit current window, may free buffer */ + break; + + case CMD_xall: /* write all changed files and exit */ + case CMD_wqall: /* write all changed files and quit */ + exiting = TRUE; + /* FALLTHROUGH */ + + case CMD_wall: /* write all changed files */ + { + BUF *buf; + int error = 0; + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + { + if (buf->b_changed) + { + if (buf->b_filename == NULL) + { + emsg(e_noname); + ++error; + } + else if (!forceit && buf->b_p_ro) + { + EMSG2("\"%s\" is readonly, use ! to write anyway", buf->b_xfilename); + ++error; + } + else if (buf_write_all(buf) == FAIL) + ++error; + } + } + if (exiting) + { + if (!error) + getout(0); /* exit Vim */ + exiting = FALSE; + settmode(1); + } + } + break; + + case CMD_preserve: /* put everything in .swp file */ + ml_preserve(curbuf, TRUE); + break; + + case CMD_recover: /* recover file */ + recoverymode = TRUE; + if (!check_changed(curbuf, FALSE, TRUE) && + (*arg == NUL || setfname(arg, NULL, TRUE) == OK)) + ml_recover(); + recoverymode = FALSE; + break; + + case CMD_args: + /* + * ":args file": handle like :next + */ + if (*arg != NUL && *arg != '|' && *arg != '\n') + goto do_next; + + if (arg_count == 0) /* no file name list */ + { + if (check_fname() == OK) /* check for no file name */ + smsg((char_u *)"[%s]", curbuf->b_filename); + break; + } + /* + * Overwrite the command, in most cases there is no scrolling + * required and no wait_return(). + */ + gotocmdline(TRUE); + for (i = 0; i < arg_count; ++i) + { + if (i == curwin->w_arg_idx) + msg_outchar('['); + msg_outtrans(arg_files[i]); + if (i == curwin->w_arg_idx) + msg_outchar(']'); + msg_outchar(' '); + } + break; + + case CMD_wnext: + case CMD_wNext: + case CMD_wprevious: + if (cmd[1] == 'n') + i = curwin->w_arg_idx + (int)line2; + else + i = curwin->w_arg_idx - (int)line2; + line1 = 1; + line2 = curbuf->b_ml.ml_line_count; + if (do_write(arg, FALSE) == FAIL) + break; + goto donextfile; + + case CMD_next: + case CMD_snext: +do_next: + /* + * check for changed buffer now, if this fails the + * argument list is not redefined. + */ + if (!(p_hid || cmdidx == CMD_snext) && + check_changed(curbuf, TRUE, FALSE)) + break; + + if (*arg != NUL) /* redefine file list */ + { + if (do_arglist(arg) == FAIL) + break; + i = 0; + } + else + i = curwin->w_arg_idx + (int)line2; + +donextfile: if (i < 0 || i >= arg_count) + { + if (arg_count <= 1) + EMSG("There is only one file to edit"); + else if (i < 0) + EMSG("Cannot go before first file"); + else + EMSG("Cannot go beyond last file"); + break; + } + setpcmark(); + if (*cmd == 's') /* split window first */ + { + if (win_split(0, FALSE) == FAIL) + break; + } + else + { + register int other; + + /* + * if 'hidden' set, only check for changed file when + * re-editing the same buffer + */ + other = TRUE; + if (p_hid) + other = otherfile(fix_fname(arg_files[i])); + if ((!p_hid || !other) && + check_changed(curbuf, TRUE, !other)) + break; + } + curwin->w_arg_idx = i; + if (i == arg_count - 1) + arg_had_last = TRUE; + (void)do_ecmd(0, arg_files[curwin->w_arg_idx], + NULL, do_ecmd_cmd, p_hid, do_ecmd_lnum, FALSE); + break; + + case CMD_previous: + case CMD_sprevious: + case CMD_Next: + case CMD_sNext: + i = curwin->w_arg_idx - (int)line2; + goto donextfile; + + case CMD_rewind: + case CMD_srewind: + i = 0; + goto donextfile; + + case CMD_last: + case CMD_slast: + i = arg_count - 1; + goto donextfile; + + case CMD_argument: + case CMD_sargument: + if (addr_count) + i = line2 - 1; + else + i = curwin->w_arg_idx; + goto donextfile; + + case CMD_all: + case CMD_sall: + if (addr_count == 0) + line2 = 9999; + do_arg_all((int)line2); /* open a window for each argument */ + break; + + case CMD_buffer: /* :[N]buffer [N] to buffer N */ + case CMD_sbuffer: /* :[N]sbuffer [N] to buffer N */ + if (*arg) + { + errormsg = e_trailing; + break; + } + if (addr_count == 0) /* default is current buffer */ + (void)do_buffer(*cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO, + DOBUF_CURRENT, FORWARD, 0, 0); + else + (void)do_buffer(*cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO, + DOBUF_FIRST, FORWARD, (int)line2, 0); + break; + + case CMD_bmodified: /* :[N]bmod [N] to next modified buffer */ + case CMD_sbmodified: /* :[N]sbmod [N] to next modified buffer */ + (void)do_buffer(*cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO, + DOBUF_MOD, FORWARD, (int)line2, 0); + break; + + case CMD_bnext: /* :[N]bnext [N] to next buffer */ + case CMD_sbnext: /* :[N]sbnext [N] to next buffer */ + (void)do_buffer(*cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO, + DOBUF_CURRENT, FORWARD, (int)line2, 0); + break; + + case CMD_bNext: /* :[N]bNext [N] to previous buffer */ + case CMD_bprevious: /* :[N]bprevious [N] to previous buffer */ + case CMD_sbNext: /* :[N]sbNext [N] to previous buffer */ + case CMD_sbprevious: /* :[N]sbprevious [N] to previous buffer */ + (void)do_buffer(*cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO, + DOBUF_CURRENT, BACKWARD, (int)line2, 0); + break; + + case CMD_brewind: /* :brewind to first buffer */ + case CMD_sbrewind: /* :sbrewind to first buffer */ + (void)do_buffer(*cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO, + DOBUF_FIRST, FORWARD, 0, 0); + break; + + case CMD_blast: /* :blast to last buffer */ + case CMD_sblast: /* :sblast to last buffer */ + (void)do_buffer(*cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO, + DOBUF_LAST, FORWARD, 0, 0); + break; + + case CMD_bunload: /* :[N]bunload[!] [N] [bufname] unload buffer */ + case CMD_bdelete: /* :[N]bdelete[!] [N] [bufname] delete buffer */ + errormsg = do_bufdel( + cmdidx == CMD_bdelete ? DOBUF_DEL : DOBUF_UNLOAD, + arg, addr_count, (int)line1, (int)line2, forceit); + break; + + case CMD_unhide: + case CMD_sunhide: /* open a window for loaded buffers */ + if (addr_count == 0) + line2 = 9999; + (void)do_buffer_all((int)line2, FALSE); + break; + + case CMD_ball: + case CMD_sball: /* open a window for every buffer */ + if (addr_count == 0) + line2 = 9999; + (void)do_buffer_all((int)line2, TRUE); + break; + + case CMD_buffers: + case CMD_files: + case CMD_ls: + buflist_list(); + break; + + case CMD_write: + if (usefilter) /* input lines to shell command */ + do_bang(1, line1, line2, FALSE, arg, TRUE, FALSE); + else + (void)do_write(arg, append); + break; + + /* + * set screen mode + * if no argument given, just get the screen size and redraw + */ + case CMD_mode: + if (*arg == NUL || mch_screenmode(arg) != FAIL) + set_winsize(0, 0, FALSE); + break; + + /* + * set, increment or decrement current window height + */ + case CMD_resize: + n = atol((char *)arg); + if (*arg == '-' || *arg == '+') + win_setheight(curwin->w_height + (int)n); + else + { + if (n == 0) /* default is very high */ + n = 9999; + win_setheight((int)n); + } + break; + + /* + * :sview [+command] file split window with new file, ro + * :split [[+command] file] split window with current or new file + * :new [[+command] file] split window with no or new file + */ + case CMD_sview: + case CMD_split: + case CMD_new: + old_curwin = curwin; + if (win_split(addr_count ? (int)line2 : 0, FALSE) == FAIL) + break; + /*FALLTHROUGH*/ + + case CMD_edit: + case CMD_ex: + case CMD_visual: + case CMD_view: + if ((cmdidx == CMD_new) && *arg == NUL) + { + setpcmark(); + (void)do_ecmd(0, NULL, NULL, do_ecmd_cmd, TRUE, + (linenr_t)1, FALSE); + } + else if (cmdidx != CMD_split || *arg != NUL) + { + n = readonlymode; + if (cmdidx == CMD_view || cmdidx == CMD_sview) + readonlymode = TRUE; + setpcmark(); + (void)do_ecmd(0, arg, NULL, do_ecmd_cmd, p_hid, + do_ecmd_lnum, FALSE); + readonlymode = n; + } + else + updateScreen(NOT_VALID); + /* if ":split file" worked, set alternate filename in + * old window to new file */ + if ((cmdidx == CMD_new || cmdidx == CMD_split) && + *arg != NUL && curwin != old_curwin && + old_curwin->w_buffer != curbuf) + old_curwin->w_alt_fnum = curbuf->b_fnum; + break; + +#ifdef USE_GUI + /* + * Change from the terminal version to the GUI version. File names may + * be given to redefine the args list -- webb + */ + case CMD_gvim: + case CMD_gui: + if (arg[0] == '-' && arg[1] == 'f' && + (arg[2] == NUL || vim_iswhite(arg[2]))) + { + gui.dofork = FALSE; + arg = skipwhite(arg + 2); + } + else + gui.dofork = TRUE; + if (!gui.in_use) + gui_start(); + if (*arg != NUL && *arg != '|' && *arg != '\n') + goto do_next; + break; +#endif + + case CMD_file: + do_file(arg, forceit); + break; + + case CMD_swapname: + if (curbuf->b_ml.ml_mfp == NULL || + (p = curbuf->b_ml.ml_mfp->mf_fname) == NULL) + MSG("No swap file"); + else + msg(p); + break; + + case CMD_mfstat: /* print memfile statistics, for debugging */ + mf_statistics(); + break; + + case CMD_read: + if (usefilter) /* :r!cmd */ + { + do_bang(1, line1, line2, FALSE, arg, FALSE, TRUE); + break; + } + if (u_save(line2, (linenr_t)(line2 + 1)) == FAIL) + break; + if (*arg == NUL) + { + if (check_fname() == FAIL) /* check for no file name */ + break; + i = readfile(curbuf->b_filename, curbuf->b_sfilename, + line2, FALSE, (linenr_t)0, MAXLNUM, FALSE); + } + else + { + i = readfile(arg, NULL, + line2, FALSE, (linenr_t)0, MAXLNUM, FALSE); + } + if (i == FAIL) + { + emsg2(e_notopen, arg); + break; + } + + updateScreen(NOT_VALID); + break; + + case CMD_cd: + case CMD_chdir: +#ifdef UNIX + /* + * for UNIX ":cd" means: go to home directory + */ + if (*arg == NUL) /* use NameBuff for home directory name */ + { + expand_env((char_u *)"$HOME", NameBuff, MAXPATHL); + arg = NameBuff; + } +#endif + if (*arg != NUL) + { + if (!did_cd) + { + BUF *buf; + + /* use full path from now on for names of files + * being edited and swap files */ + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + { + buf->b_xfilename = buf->b_filename; + mf_fullname(buf->b_ml.ml_mfp); + } + status_redraw_all(); + } + did_cd = TRUE; + if (vim_chdir((char *)arg)) + emsg(e_failed); + break; + } + /*FALLTHROUGH*/ + + case CMD_pwd: + if (mch_dirname(NameBuff, MAXPATHL) == OK) + msg(NameBuff); + else + emsg(e_unknown); + break; + + case CMD_equal: + smsg((char_u *)"line %ld", (long)line2); + break; + + case CMD_list: + i = curwin->w_p_list; + curwin->w_p_list = 1; + case CMD_number: /* :nu */ + case CMD_pound: /* :# */ + case CMD_print: /* :p */ + for ( ;!got_int; mch_breakcheck()) + { + print_line(line1, + (cmdidx == CMD_number || cmdidx == CMD_pound)); + if (++line1 > line2) + break; + flushbuf(); /* show one line at a time */ + } + setpcmark(); + curwin->w_cursor.lnum = line2; /* put cursor at last line */ + + if (cmdidx == CMD_list) + curwin->w_p_list = i; + + break; + + case CMD_shell: + do_shell(NULL); + break; + + case CMD_sleep: + n = curwin->w_winpos + curwin->w_row - msg_scrolled; + if (n >= 0) + { + windgoto((int)n, curwin->w_col); + flushbuf(); + } + mch_delay(line2 * 1000L, TRUE); + break; + + case CMD_stag: + postponed_split = TRUE; + /*FALLTHROUGH*/ + case CMD_tag: + do_tag(arg, 0, addr_count ? (int)line2 : 1); + break; + + case CMD_pop: + do_tag((char_u *)"", 1, addr_count ? (int)line2 : 1); + break; + + case CMD_tags: + do_tags(); + break; + + case CMD_marks: + do_marks(arg); + break; + + case CMD_jumps: + do_jumps(); + break; + + case CMD_ascii: + do_ascii(); + break; + + case CMD_checkpath: + find_pattern_in_path(NULL, 0, FALSE, FALSE, CHECK_PATH, 1L, + forceit ? ACTION_SHOW_ALL : ACTION_SHOW, + (linenr_t)1, (linenr_t)MAXLNUM); + break; + + case CMD_digraphs: +#ifdef DIGRAPHS + if (*arg) + putdigraph(arg); + else + listdigraphs(); +#else + EMSG("No digraphs in this version"); +#endif /* DIGRAPHS */ + break; + + case CMD_set: + (void)do_set(arg); + break; + + case CMD_fixdel: + do_fixdel(); + break; + +#ifdef AUTOCMD + case CMD_autocmd: + /* + * Disallow auto commands from .exrc and .vimrc in current + * directory for security reasons. + */ + if (secure) + { + secure = 2; + errormsg = e_curdir; + } + else + do_autocmd(arg, forceit); /* handle the auto commands */ + break; + + case CMD_doautocmd: + do_doautocmd(arg); /* apply the automatic commands */ + do_modelines(); + break; +#endif + + case CMD_abbreviate: + case CMD_cabbrev: + case CMD_iabbrev: + case CMD_cnoreabbrev: + case CMD_inoreabbrev: + case CMD_noreabbrev: + case CMD_unabbreviate: + case CMD_cunabbrev: + case CMD_iunabbrev: + i = ABBREV; + goto doabbr; /* almost the same as mapping */ + + case CMD_nmap: + case CMD_vmap: + case CMD_cmap: + case CMD_imap: + case CMD_map: + case CMD_nnoremap: + case CMD_vnoremap: + case CMD_cnoremap: + case CMD_inoremap: + case CMD_noremap: + /* + * If we are sourcing .exrc or .vimrc in current directory we + * print the mappings for security reasons. + */ + if (secure) + { + secure = 2; + msg_outtrans(cmd); + msg_outchar('\n'); + } + case CMD_nunmap: + case CMD_vunmap: + case CMD_cunmap: + case CMD_iunmap: + case CMD_unmap: + i = 0; +doabbr: + if (*cmd == 'c') /* cmap, cunmap, cnoremap, etc. */ + { + i += CMDLINE; + ++cmd; + } + else if (*cmd == 'i') /* imap, iunmap, inoremap, etc. */ + { + i += INSERT; + ++cmd; + } + /* nmap, nunmap, nnoremap */ + else if (*cmd == 'n' && *(cmd + 1) != 'o') + { + i += NORMAL; + ++cmd; + } + else if (*cmd == 'v') /* vmap, vunmap, vnoremap */ + { + i += VISUAL; + ++cmd; + } + else if (forceit || i) /* map!, unmap!, noremap!, abbrev */ + i += INSERT + CMDLINE; + else /* map, unmap, noremap */ + i += NORMAL + VISUAL; + switch (do_map((*cmd == 'n') ? 2 : (*cmd == 'u'), arg, i)) + { + case 1: emsg(e_invarg); + break; + case 2: emsg(e_nomap); + break; + case 3: emsg(e_ambmap); + break; + } + break; + + case CMD_mapclear: + case CMD_imapclear: + case CMD_nmapclear: + case CMD_vmapclear: + case CMD_cmapclear: + map_clear(*cmd, forceit, FALSE); + break; + + case CMD_abclear: + case CMD_iabclear: + case CMD_cabclear: + map_clear(*cmd, FALSE, TRUE); + break; + +#ifdef USE_GUI + case CMD_menu: case CMD_noremenu: case CMD_unmenu: + case CMD_nmenu: case CMD_nnoremenu: case CMD_nunmenu: + case CMD_vmenu: case CMD_vnoremenu: case CMD_vunmenu: + case CMD_imenu: case CMD_inoremenu: case CMD_iunmenu: + case CMD_cmenu: case CMD_cnoremenu: case CMD_cunmenu: + gui_do_menu(cmd, arg, forceit); + break; +#endif /* USE_GUI */ + + case CMD_display: + case CMD_registers: + do_dis(arg); /* display buffer contents */ + break; + + case CMD_help: + do_help(arg); + break; + + case CMD_version: + do_version(arg); + break; + + case CMD_winsize: /* obsolete command */ + line1 = getdigits(&arg); + arg = skipwhite(arg); + line2 = getdigits(&arg); + set_winsize((int)line1, (int)line2, TRUE); + break; + + case CMD_delete: + case CMD_yank: + case CMD_rshift: + case CMD_lshift: + yankbuffer = regname; + curbuf->b_op_start.lnum = line1; + curbuf->b_op_end.lnum = line2; + op_line_count = line2 - line1 + 1; + op_motion_type = MLINE; + if (cmdidx != CMD_yank) /* set cursor position for undo */ + { + setpcmark(); + curwin->w_cursor.lnum = line1; + beginline(MAYBE); + } + switch (cmdidx) + { + case CMD_delete: + do_delete(); + break; + case CMD_yank: + (void)do_yank(FALSE, TRUE); + break; +#ifdef RIGHTLEFT + case CMD_rshift: + do_shift(curwin->w_p_rl ? LSHIFT : RSHIFT, FALSE, amount); + break; + case CMD_lshift: + do_shift(curwin->w_p_rl ? RSHIFT : LSHIFT, FALSE, amount); + break; +#else + case CMD_rshift: + do_shift(RSHIFT, FALSE, amount); + break; + case CMD_lshift: + do_shift(LSHIFT, FALSE, amount); + break; +#endif + } + break; + + case CMD_put: + yankbuffer = regname; + curwin->w_cursor.lnum = line2; + do_put(forceit ? BACKWARD : FORWARD, -1L, FALSE); + break; + + case CMD_t: + case CMD_copy: + case CMD_move: + n = get_address(&arg); + if (arg == NULL) /* error detected */ + { + nextcomm = NULL; + break; + } + /* + * move or copy lines from 'line1'-'line2' to below line 'n' + */ + if (n == MAXLNUM || n < 0 || n > curbuf->b_ml.ml_line_count) + { + emsg(e_invaddr); + break; + } + + if (cmdidx == CMD_move) + { + if (do_move(line1, line2, n) == FAIL) + break; + } + else + do_copy(line1, line2, n); + u_clearline(); + beginline(MAYBE); + updateScreen(NOT_VALID); + break; + + case CMD_and: /* :& */ + case CMD_tilde: /* :~ */ + case CMD_substitute: /* :s */ + do_sub(line1, line2, arg, &nextcomm, + cmdidx == CMD_substitute ? 0 : + cmdidx == CMD_and ? 1 : 2); + break; + + case CMD_join: + curwin->w_cursor.lnum = line1; + if (line1 == line2) + { + if (addr_count >= 2) /* :2,2join does nothing */ + break; + if (line2 == curbuf->b_ml.ml_line_count) + { + beep_flush(); + break; + } + ++line2; + } + do_do_join(line2 - line1 + 1, !forceit, FALSE); + beginline(TRUE); + break; + + case CMD_global: + if (forceit) + *cmd = 'v'; + case CMD_vglobal: + do_glob(*cmd, line1, line2, arg); + break; + + case CMD_at: /* :[addr]@r */ + curwin->w_cursor.lnum = line2; + /* put the register in mapbuf */ + if (do_execbuf(*arg, TRUE, + vim_strchr(p_cpo, CPO_EXECBUF) != NULL) == FAIL) + beep_flush(); + else + /* execute from the mapbuf */ + while (vpeekc() == ':') + { + (void)vgetc(); + (void)do_cmdline((char_u *)NULL, TRUE, TRUE); + } + break; + + case CMD_bang: + do_bang(addr_count, line1, line2, forceit, arg, TRUE, TRUE); + break; + + case CMD_undo: + u_undo(1); + break; + + case CMD_redo: + u_redo(1); + break; + + case CMD_source: + if (forceit) /* :so! read vi commands */ + (void)openscript(arg); + /* :so read ex commands */ + else if (do_source(arg, FALSE) == FAIL) + emsg2(e_notopen, arg); + break; + +#ifdef VIMINFO + case CMD_rviminfo: + p = p_viminfo; + if (*p_viminfo == NUL) + p_viminfo = (char_u *)"'100"; + if (read_viminfo(arg, TRUE, TRUE, forceit) == FAIL) + EMSG("Cannot open viminfo file for reading"); + p_viminfo = p; + break; + + case CMD_wviminfo: + p = p_viminfo; + if (*p_viminfo == NUL) + p_viminfo = (char_u *)"'100"; + write_viminfo(arg, forceit); + p_viminfo = p; + break; +#endif /* VIMINFO */ + + case CMD_mkvimrc: + if (*arg == NUL) + arg = (char_u *)VIMRC_FILE; + /*FALLTHROUGH*/ + + case CMD_mkexrc: + { + FILE *fd; + + if (*arg == NUL) + arg = (char_u *)EXRC_FILE; +#ifdef UNIX + /* with Unix it is possible to open a directory */ + if (mch_isdir(arg)) + { + EMSG2("\"%s\" is a directory", arg); + break; + } +#endif + if (!forceit && vim_fexists(arg)) + { + EMSG2("\"%s\" exists (use ! to override)", arg); + break; + } + + if ((fd = fopen((char *)arg, WRITEBIN)) == NULL) + { + EMSG2("Cannot open \"%s\" for writing", arg); + break; + } + + /* Write the version command for :mkvimrc */ + if (cmdidx == CMD_mkvimrc) + { +#ifdef USE_CRNL + fprintf(fd, "version 4.0\r\n"); +#else + fprintf(fd, "version 4.0\n"); +#endif + } + + if (makemap(fd) == FAIL || makeset(fd) == FAIL || + fclose(fd)) + emsg(e_write); + break; + } + + case CMD_cc: + qf_jump(0, addr_count ? (int)line2 : 0); + break; + + case CMD_cfile: + if (*arg != NUL) + { + /* + * Great trick: Insert 'ef=' before arg. + * Always ok, because "cf " must be there. + */ + arg -= 3; + arg[0] = 'e'; + arg[1] = 'f'; + arg[2] = '='; + (void)do_set(arg); + } + if (qf_init() == OK) + qf_jump(0, 0); /* display first error */ + break; + + case CMD_clist: + qf_list(forceit); + break; + + case CMD_cnext: + qf_jump(FORWARD, addr_count ? (int)line2 : 1); + break; + + case CMD_cNext: + case CMD_cprevious: + qf_jump(BACKWARD, addr_count ? (int)line2 : 1); + break; + + case CMD_cquit: + getout(1); /* this does not always work. why? */ + + case CMD_mark: + case CMD_k: + pos = curwin->w_cursor; /* save curwin->w_cursor */ + curwin->w_cursor.lnum = line2; + beginline(MAYBE); + (void)setmark(*arg); /* set mark */ + curwin->w_cursor = pos; /* restore curwin->w_cursor */ + break; + + case CMD_center: + case CMD_right: + case CMD_left: + do_align(line1, line2, atoi((char *)arg), + cmdidx == CMD_center ? 0 : cmdidx == CMD_right ? 1 : -1); + break; + + case CMD_retab: + n = getdigits(&arg); + do_retab(line1, line2, (int)n, forceit); + u_clearline(); + updateScreen(NOT_VALID); + break; + + case CMD_make: + do_make(arg); + break; + + /* + * :normal[!] {commands} - execute normal mode commands + * Mostly used for ":autocmd". + */ + case CMD_normal: + /* + * Stuff the argument into the typeahead buffer. + * Execute normal() until there is no more typeahead than + * there was before this command. + */ + len = typelen; + ins_typebuf(arg, forceit ? -1 : 0, 0, TRUE); + while ((!stuff_empty() || + (!typebuf_typed() && typelen > len)) && !got_int) + { + adjust_cursor(); /* put cursor on an existing line */ + cursupdate(); /* update cursor position */ + normal(); /* get and execute a normal mode command */ + } + break; + + case CMD_isearch: + case CMD_dsearch: + i = ACTION_SHOW; + goto find_pat; + + case CMD_ilist: + case CMD_dlist: + i = ACTION_SHOW_ALL; + goto find_pat; + + case CMD_ijump: + case CMD_djump: + i = ACTION_GOTO; + goto find_pat; + + case CMD_isplit: + case CMD_dsplit: + i = ACTION_SPLIT; +find_pat: + { + int whole = TRUE; + + n = 1; + if (isdigit(*arg)) /* get count */ + { + n = getdigits(&arg); + arg = skipwhite(arg); + } + if (*arg == '/') /* Match regexp, not just whole words */ + { + whole = FALSE; + ++arg; + for (p = arg; *p && *p != '/'; p++) + if (*p == '\\' && p[1] != NUL) + p++; + if (*p) + { + *p++ = NUL; + p = skipwhite(p); + + /* Check for trailing illegal characters */ + if (*p && vim_strchr((char_u *)"|\"\n", *p) == NULL) + errormsg = e_trailing; + else + nextcomm = p; + } + } + find_pattern_in_path(arg, (int)STRLEN(arg), whole, !forceit, + *cmd == 'd' ? FIND_DEFINE : FIND_ANY, + n, i, line1, line2); + } + break; + + default: + /* Normal illegal commands have already been handled */ + errormsg = (char_u *)"Sorry, this command is not implemented"; + } + + +doend: + if (errormsg != NULL) + { + emsg(errormsg); + if (sourcing) + { + MSG_OUTSTR(": "); + msg_outtrans(*cmdlinep); + } + } + if (did_emsg) + nextcomm = NULL; /* cancel nextcomm at an error */ + forceit = FALSE; /* reset now so it can be used in getfile() */ + if (nextcomm && *nextcomm == NUL) /* not really a next command */ + nextcomm = NULL; + return nextcomm; +} + +/* + * If 'autowrite' option set, try to write the file. + * + * return FAIL for failure, OK otherwise + */ + int +autowrite(buf) + BUF *buf; +{ + if (!p_aw || (!forceit && buf->b_p_ro) || buf->b_filename == NULL) + return FAIL; + return buf_write_all(buf); +} + +/* + * flush all buffers, except the ones that are readonly + */ + void +autowrite_all() +{ + BUF *buf; + + if (!p_aw) + return; + for (buf = firstbuf; buf; buf = buf->b_next) + if (buf->b_changed && !buf->b_p_ro) + (void)buf_write_all(buf); +} + +/* + * flush the contents of a buffer, unless it has no file name + * + * return FAIL for failure, OK otherwise + */ + static int +buf_write_all(buf) + BUF *buf; +{ + return (buf_write(buf, buf->b_filename, buf->b_sfilename, + (linenr_t)1, buf->b_ml.ml_line_count, 0, 0, TRUE, FALSE)); +} + +/* + * write current buffer to file 'fname' + * if 'append' is TRUE, append to the file + * + * if *fname == NUL write to current file + * if b_notedited is TRUE, check for overwriting current file + * + * return FAIL for failure, OK otherwise + */ + static int +do_write(fname, append) + char_u *fname; + int append; +{ + int other; + char_u *sfname = NULL; /* init to shut up gcc */ + + if (*fname == NUL) + other = FALSE; + else + { + sfname = fname; + fname = fix_fname(fname); + other = otherfile(fname); + } + + /* + * if we have a new file name put it in the list of alternate file names + */ + if (other) + setaltfname(fname, sfname, (linenr_t)1); + + /* + * writing to the current file is not allowed in readonly mode + * and need a file name + */ + if (!other && (check_readonly() || check_fname() == FAIL)) + return FAIL; + + if (!other) + { + fname = curbuf->b_filename; + sfname = curbuf->b_sfilename; + /* + * Not writing the whole file is only allowed with '!'. + */ + if ((line1 != 1 || line2 != curbuf->b_ml.ml_line_count) && + !forceit && !append && !p_wa) + { + EMSG("Use ! to write partial buffer"); + return FAIL; + } + } + + /* + * write to other file or b_notedited set or not writing the whole file: + * overwriting only allowed with '!' + */ + if ((other || curbuf->b_notedited) && !forceit && + !append && !p_wa && vim_fexists(fname)) + { /* don't overwrite existing file */ +#ifdef UNIX + /* with UNIX it is possible to open a directory */ + if (mch_isdir(fname)) + EMSG2("\"%s\" is a directory", fname); + else +#endif + emsg(e_exists); + return FAIL; + } + return (buf_write(curbuf, fname, sfname, line1, line2, + append, forceit, TRUE, FALSE)); +} + +/* + * start editing a new file + * + * fnum: file number; if zero use fname/sfname + * fname: the file name + * - full path if sfname used, + * - any file name if sfname is NULL + * - empty string to re-edit with the same file name (but may be + * in a different directory) + * - NULL to start an empty buffer + * sfname: the short file name (or NULL) + * command: the command to be executed after loading the file + * hide: if TRUE don't free the current buffer + * newlnum: put cursor on this line number (if possible) + * set_help: set b_help flag of (new) buffer before opening file + * + * return FAIL for failure, OK otherwise + */ + int +do_ecmd(fnum, fname, sfname, command, hide, newlnum, set_help) + int fnum; + char_u *fname; + char_u *sfname; + char_u *command; + int hide; + linenr_t newlnum; + int set_help; +{ + int other_file; /* TRUE if editing another file */ + int oldbuf = FALSE; /* TRUE if using existing buffer */ + BUF *buf; + + if (fnum != 0) + { + if (fnum == curbuf->b_fnum) /* file is already being edited */ + return OK; /* nothing to do */ + other_file = TRUE; + } + else + { + /* if no short name given, use fname for short name */ + if (sfname == NULL) + sfname = fname; +#ifdef USE_FNAME_CASE +# ifdef USE_LONG_FNAME + if (USE_LONG_FNAME) +# endif + fname_case(sfname); /* set correct case for short filename */ +#endif + + if (fname == NULL) + other_file = TRUE; + /* there is no file name */ + else if (*fname == NUL && curbuf->b_filename == NULL) + other_file = FALSE; + else + { + if (*fname == NUL) /* re-edit with same file name */ + { + fname = curbuf->b_filename; + sfname = curbuf->b_sfilename; + } + fname = fix_fname(fname); /* may expand to full path name */ + other_file = otherfile(fname); + } + } +/* + * if the file was changed we may not be allowed to abandon it + * - if we are going to re-edit the same file + * - or if we are the only window on this file and if hide is FALSE + */ + if ((!other_file || (curbuf->b_nwindows == 1 && !hide)) && + check_changed(curbuf, FALSE, !other_file)) + { + if (fnum == 0 && other_file && fname != NULL) + setaltfname(fname, sfname, (linenr_t)1); + return FAIL; + } + +/* + * End Visual mode before switching to another buffer, so the text can be + * copied into the GUI selection buffer. + */ + if (VIsual_active) + end_visual_mode(); + +/* + * If we are starting to edit another file, open a (new) buffer. + * Otherwise we re-use the current buffer. + */ + if (other_file) + { + curwin->w_alt_fnum = curbuf->b_fnum; + buflist_altlnum(); + + if (fnum) + buf = buflist_findnr(fnum); + else + buf = buflist_new(fname, sfname, 1L, TRUE); + if (buf == NULL) + return FAIL; + if (buf->b_ml.ml_mfp == NULL) /* no memfile yet */ + { + oldbuf = FALSE; + buf->b_nwindows = 1; + } + else /* existing memfile */ + { + oldbuf = TRUE; + ++buf->b_nwindows; + buf_check_timestamp(buf); + } + + /* + * make the (new) buffer the one used by the current window + * if the old buffer becomes unused, free it if hide is FALSE + * If the current buffer was empty and has no file name, curbuf + * is returned by buflist_new(). + */ + if (buf != curbuf) + { +#ifdef AUTOCMD + apply_autocmds(EVENT_BUFLEAVE, NULL, NULL); +#endif +#ifdef VIMINFO + curbuf->b_last_cursor = curwin->w_cursor; +#endif + buf_copy_options(curbuf, buf, TRUE); + close_buffer(curwin, curbuf, !hide, FALSE); + curwin->w_buffer = buf; + curbuf = buf; + } + + curwin->w_pcmark.lnum = 1; + curwin->w_pcmark.col = 0; + } + else if (check_fname() == FAIL) + return FAIL; + +/* + * If we get here we are sure to start editing + */ + /* don't redraw until the cursor is in the right line */ + ++RedrawingDisabled; + if (set_help) + curbuf->b_help = TRUE; + +/* + * other_file oldbuf + * FALSE FALSE re-edit same file, buffer is re-used + * FALSE TRUE not posible + * TRUE FALSE start editing new file, new buffer + * TRUE TRUE start editing in existing buffer (nothing to do) + */ + if (!other_file) /* re-use the buffer */ + { + if (newlnum == 0) + newlnum = curwin->w_cursor.lnum; + buf_freeall(curbuf); /* free all things for buffer */ + buf_clear(curbuf); + curbuf->b_op_start.lnum = 0; /* clear '[ and '] marks */ + curbuf->b_op_end.lnum = 0; + } + + /* + * Check if we are editing the w_arg_idx file in the argument list. + */ + check_arg_idx(); + + if (!oldbuf) /* need to read the file */ + (void)open_buffer(); +#ifdef AUTOCMD + else + apply_autocmds(EVENT_BUFENTER, NULL, NULL); +#endif + win_init(curwin); + maketitle(); + + if (command == NULL) + { + if (newlnum) + { + curwin->w_cursor.lnum = newlnum; + check_cursor(); + beginline(MAYBE); + } + else + beginline(TRUE); + } + + /* + * Did not read the file, need to show some info about the file. + * Do this after setting the cursor. + */ + if (oldbuf) + fileinfo(did_cd, TRUE, FALSE); + + if (command != NULL) + do_cmdline(command, TRUE, FALSE); + --RedrawingDisabled; + if (!skip_redraw) + updateScreen(CURSUPD); /* redraw now */ + + if (p_im) + need_start_insertmode = TRUE; + return OK; +} + +/* + * get + command from ex argument + */ + static char_u * +getargcmd(argp) + char_u **argp; +{ + char_u *arg = *argp; + char_u *command = NULL; + + if (*arg == '+') /* +[command] */ + { + ++arg; + if (vim_isspace(*arg)) + command = (char_u *)"$"; + else + { + /* + * should check for "\ " (but vi has a bug that prevents it to work) + */ + command = arg; + arg = skiptowhite(command); + if (*arg) + *arg++ = NUL; /* terminate command with NUL */ + } + + arg = skipwhite(arg); /* skip over spaces */ + *argp = arg; + } + return command; +} + +/* + * Halve the number of backslashes in a file name argument. + * For MS-DOS we only do this if the character after the backslash + * is not a normal file character. + * For Unix, when wildcards are going to be expanded, don't remove + * backslashes before special characters. + */ + static void +backslash_halve(p, expand_wildcards) + char_u *p; + int expand_wildcards; /* going to expand wildcards later */ +{ + for ( ; *p; ++p) + if (is_backslash(p) +#if defined(MSDOS) || defined(WIN32) + && p[1] != '*' && p[1] != '?' +#endif +#if defined(UNIX) || defined(OS2) + && !(expand_wildcards && + vim_strchr((char_u *)" *?[{`$\\", p[1])) +#endif + ) + STRCPY(p, p + 1); +} + + static void +do_make(arg) + char_u *arg; +{ + if (*p_ef == NUL) + { + EMSG("errorfile option not set"); + return; + } + + autowrite_all(); + vim_remove(p_ef); + + sprintf((char *)IObuff, "%s %s %s", arg, p_sp, p_ef); + MSG_OUTSTR(":!"); + msg_outtrans(IObuff); /* show what we are doing */ + do_shell(IObuff); + +#ifdef AMIGA + flushbuf(); + /* read window status report and redraw before message */ + (void)char_avail(); +#endif + + if (qf_init() == OK) + qf_jump(0, 0); /* display first error */ + + vim_remove(p_ef); +} + +/* + * Redefine the argument list to 'str'. + * + * Return FAIL for failure, OK otherwise. + */ + static int +do_arglist(str) + char_u *str; +{ + int new_count = 0; + char_u **new_files = NULL; + int exp_count; + char_u **exp_files; + char_u **t; + char_u *p; + int inquote; + int i; + + while (*str) + { + /* + * create a new entry in new_files[] + */ + t = (char_u **)lalloc((long_u)(sizeof(char_u *) * (new_count + 1)), TRUE); + if (t != NULL) + for (i = new_count; --i >= 0; ) + t[i] = new_files[i]; + vim_free(new_files); + if (t == NULL) + return FAIL; + new_files = t; + new_files[new_count++] = str; + + /* + * isolate one argument, taking quotes + */ + inquote = FALSE; + for (p = str; *str; ++str) + { + /* + * for MSDOS et.al. a backslash is part of a file name. + * Only skip ", space and tab. + */ + if (is_backslash(str)) + *p++ = *++str; + else + { + if (!inquote && vim_isspace(*str)) + break; + if (*str == '"') + inquote ^= TRUE; + else + *p++ = *str; + } + } + str = skipwhite(str); + *p = NUL; + } + + i = ExpandWildCards(new_count, new_files, &exp_count, + &exp_files, FALSE, TRUE); + vim_free(new_files); + if (i == FAIL) + return FAIL; + if (exp_count == 0) + { + emsg(e_nomatch); + return FAIL; + } + if (arg_exp) /* arg_files[] has been allocated, free it */ + FreeWild(arg_count, arg_files); + else + arg_exp = TRUE; + arg_files = exp_files; + arg_count = exp_count; + arg_had_last = FALSE; + + /* + * put all file names in the buffer list + */ + for (i = 0; i < arg_count; ++i) + (void)buflist_add(arg_files[i]); + + return OK; +} + +/* + * Return TRUE if "str" starts with a backslash that should be removed. + * For MS-DOS, WIN32 and OS/2 this is only done when the character after the + * backslash is not a normal file name character. + */ + static int +is_backslash(str) + char_u *str; +{ +#ifdef BACKSLASH_IN_FILENAME + return (str[0] == '\\' && str[1] != NUL && + !(isfilechar(str[1]) && str[1] != '\\')); +#else + return (str[0] == '\\' && str[1] != NUL); +#endif +} + +/* + * Check if we are editing the w_arg_idx file in the argument list. + */ + void +check_arg_idx() +{ + int t; + + if (arg_count > 1 && (curbuf->b_filename == NULL || + curwin->w_arg_idx >= arg_count || + (t = fullpathcmp(arg_files[curwin->w_arg_idx], + curbuf->b_filename)) == FPC_DIFF || t == FPC_DIFFX)) + curwin->w_arg_idx_invalid = TRUE; + else + curwin->w_arg_idx_invalid = FALSE; +} + + void +gotocmdline(clr) + int clr; +{ + msg_start(); + if (clr) /* clear the bottom line(s) */ + msg_clr_eos(); /* will reset clear_cmdline */ + windgoto(cmdline_row, 0); +} + + static int +check_readonly() +{ + if (!forceit && curbuf->b_p_ro) + { + emsg(e_readonly); + return TRUE; + } + return FALSE; +} + +/* + * return TRUE if buffer was changed and cannot be abandoned. + */ + static int +check_changed(buf, checkaw, mult_win) + BUF *buf; + int checkaw; /* do autowrite if buffer was changed */ + int mult_win; /* check also when several windows for the buffer */ +{ + if ( !forceit && + buf->b_changed && (mult_win || buf->b_nwindows <= 1) && + (!checkaw || autowrite(buf) == FAIL)) + { + emsg(e_nowrtmsg); + return TRUE; + } + return FALSE; +} + +/* + * return TRUE if any buffer was changed and cannot be abandoned. + * That changed buffer becomes the current buffer. + */ + static int +check_changed_any() +{ + BUF *buf; + int save; + + if (!forceit) + { + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + { + if (buf->b_changed) + { + /* There must be a wait_return for this message, do_buffer + * will cause a redraw */ + exiting = FALSE; + if (EMSG2("No write since last change for buffer \"%s\"", + buf->b_xfilename == NULL ? (char_u *)"No File" : + buf->b_xfilename)) + { + save = no_wait_return; + no_wait_return = FALSE; + wait_return(FALSE); + no_wait_return = save; + } + (void)do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, + buf->b_fnum, 0); + return TRUE; + } + } + } + return FALSE; +} + +/* + * return FAIL if there is no filename, OK if there is one + * give error message for FAIL + */ + int +check_fname() +{ + if (curbuf->b_filename == NULL) + { + emsg(e_noname); + return FAIL; + } + return OK; +} + +/* + * - if there are more files to edit + * - and this is the last window + * - and forceit not used + * - and not repeated twice on a row + * return FAIL and give error message if 'message' TRUE + * return OK otherwise + */ + static int +check_more(message) + int message; /* when FALSE check only, no messages */ +{ + if (!forceit && only_one_window() && arg_count > 1 && !arg_had_last && + quitmore == 0) + { + if (message) + { + EMSGN("%ld more files to edit", arg_count - curwin->w_arg_idx - 1); + quitmore = 2; /* next try to quit is allowed */ + } + return FAIL; + } + return OK; +} + +/* + * try to abandon current file and edit a new or existing file + * 'fnum' is the number of the file, if zero use fname/sfname + * + * return 1 for "normal" error, 2 for "not written" error, 0 for success + * -1 for succesfully opening another file + * 'lnum' is the line number for the cursor in the new file (if non-zero). + */ + int +getfile(fnum, fname, sfname, setpm, lnum) + int fnum; + char_u *fname; + char_u *sfname; + int setpm; + linenr_t lnum; +{ + int other; + + if (fnum == 0) + { + fname_expand(&fname, &sfname); /* make fname full path, set sfname */ + other = otherfile(fname); + } + else + other = (fnum != curbuf->b_fnum); + + if (other) + ++no_wait_return; /* don't wait for autowrite message */ + if (other && !forceit && curbuf->b_nwindows == 1 && + !p_hid && curbuf->b_changed && autowrite(curbuf) == FAIL) + { + if (other) + --no_wait_return; + emsg(e_nowrtmsg); + return 2; /* file has been changed */ + } + if (other) + --no_wait_return; + if (setpm) + setpcmark(); + if (!other) + { + if (lnum != 0) + curwin->w_cursor.lnum = lnum; + check_cursor(); + beginline(MAYBE); + + return 0; /* it's in the same file */ + } + if (do_ecmd(fnum, fname, sfname, NULL, p_hid, lnum, FALSE) == OK) + return -1; /* opened another file */ + return 1; /* error encountered */ +} + +/* + * vim_strncpy() + * + * This is here because strncpy() does not guarantee successful results when + * the to and from strings overlap. It is only currently called from nextwild() + * which copies part of the command line to another part of the command line. + * This produced garbage when expanding files etc in the middle of the command + * line (on my terminal, anyway) -- webb. + */ + static void +vim_strncpy(to, from, len) + char_u *to; + char_u *from; + int len; +{ + int i; + + if (to <= from) + { + while (len-- && *from) + *to++ = *from++; + if (len >= 0) + *to = *from; /* Copy NUL */ + } + else + { + for (i = 0; i < len; i++) + { + to++; + if (*from++ == NUL) + { + i++; + break; + } + } + for (; i > 0; i--) + *--to = *--from; + } +} + +/* + * Return FALSE if this is not an appropriate context in which to do + * completion of anything, & TRUE if it is (even if there are no matches). + * For the caller, this means that the character is just passed through like a + * normal character (instead of being expanded). This allows :s/^I^D etc. + */ + static int +nextwild(type) + int type; +{ + int i; + char_u *p1; + char_u *p2; + int oldlen; + int difflen; + int v; + + if (cmd_numfiles == -1) + set_expand_context(cmdfirstc, cmdbuff); + if (expand_context == EXPAND_UNSUCCESSFUL) + { + beep_flush(); + return OK; /* Something illegal on command line */ + } + if (expand_context == EXPAND_NOTHING) + { + /* Caller can use the character as a normal char instead */ + return FAIL; + } + expand_interactively = TRUE; + + MSG_OUTSTR("..."); /* show that we are busy */ + flushbuf(); + + i = expand_pattern - cmdbuff; + oldlen = cmdpos - i; + + if (type == WILD_NEXT || type == WILD_PREV) + { + /* + * Get next/previous match for a previous expanded pattern. + */ + p2 = ExpandOne(NULL, NULL, 0, type); + } + else + { + /* + * Translate string into pattern and expand it. + */ + if ((p1 = addstar(&cmdbuff[i], oldlen)) == NULL) + p2 = NULL; + else + { + p2 = ExpandOne(p1, strnsave(&cmdbuff[i], oldlen), + WILD_HOME_REPLACE, type); + vim_free(p1); + } + } + + if (p2 != NULL) + { + if (cmdlen + (difflen = STRLEN(p2) - oldlen) > cmdbufflen - 4) + v = realloc_cmdbuff(cmdlen + difflen); + else + v = OK; + if (v == OK) + { + vim_strncpy(&cmdbuff[cmdpos + difflen], &cmdbuff[cmdpos], + cmdlen - cmdpos); + STRNCPY(&cmdbuff[i], p2, STRLEN(p2)); + cmdlen += difflen; + cmdpos += difflen; + } + vim_free(p2); + } + + redrawcmd(); + if (cmd_numfiles <= 0 && p2 == NULL) + beep_flush(); + else if (cmd_numfiles == 1) + (void)ExpandOne(NULL, NULL, 0, WILD_FREE); /* free expanded pattern */ + + expand_interactively = FALSE; /* reset for next call */ + return OK; +} + +#define MAXSUFLEN 30 /* maximum length of a file suffix */ + +/* + * Do wildcard expansion on the string 'str'. + * Return a pointer to alloced memory containing the new string. + * Return NULL for failure. + * + * mode = WILD_FREE: just free previously expanded matches + * mode = WILD_EXPAND_FREE: normal expansion, do not keep matches + * mode = WILD_EXPAND_KEEP: normal expansion, keep matches + * mode = WILD_NEXT: use next match in multiple match, wrap to first + * mode = WILD_PREV: use previous match in multiple match, wrap to first + * mode = WILD_ALL: return all matches concatenated + * mode = WILD_LONGEST: return longest matched part + * + * options = WILD_LIST_NOTFOUND: list entries without a match + * options = WILD_HOME_REPLACE: do home_replace() for buffer names + */ + char_u * +ExpandOne(str, orig, options, mode) + char_u *str; + char_u *orig; /* original string which is expanded */ + int options; + int mode; +{ + char_u *ss = NULL; + static char_u **cmd_files = NULL; /* list of input files */ + static int findex; + static char_u *orig_save = NULL; /* kept value of orig */ + int i, found = 0; + int multmatch = FALSE; + long_u len; + char_u *setsuf; + int fnamelen, setsuflen; + char_u suf_buf[MAXSUFLEN]; + char_u *p; + +/* + * first handle the case of using an old match + */ + if (mode == WILD_NEXT || mode == WILD_PREV) + { + if (cmd_numfiles > 0) + { + if (mode == WILD_PREV) + { + if (findex == -1) + findex = cmd_numfiles; + --findex; + } + else /* mode == WILD_NEXT */ + ++findex; + + /* + * When wrapping around, return the original string, set findex to + * -1. + */ + if (findex < 0) + { + if (orig_save == NULL) + findex = cmd_numfiles - 1; + else + findex = -1; + } + if (findex >= cmd_numfiles) + { + if (orig_save == NULL) + findex = 0; + else + findex = -1; + } + if (findex == -1) + return strsave(orig_save); + return strsave(cmd_files[findex]); + } + else + return NULL; + } + +/* free old names */ + if (cmd_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) + { + FreeWild(cmd_numfiles, cmd_files); + cmd_numfiles = -1; + vim_free(orig_save); + orig_save = NULL; + } + findex = 0; + + if (mode == WILD_FREE) /* only release file name */ + return NULL; + + if (cmd_numfiles == -1) + { + vim_free(orig_save); + orig_save = orig; + if (ExpandFromContext(str, &cmd_numfiles, &cmd_files, FALSE, + options) == FAIL) + /* error: do nothing */; + else if (cmd_numfiles == 0) + { + if (!expand_interactively) + emsg(e_nomatch); + } + else + { + /* + * If the pattern starts with a '~', replace the home diretory + * with '~' again. + */ + if (*str == '~' && (options & WILD_HOME_REPLACE)) + { + for (i = 0; i < cmd_numfiles; ++i) + { + p = home_replace_save(NULL, cmd_files[i]); + if (p != NULL) + { + vim_free(cmd_files[i]); + cmd_files[i] = p; + } + } + } + + /* + * Insert backslashes into a file name before a space, \, %, # and + * wildmatch characters, except '~'. + */ + if (expand_interactively && + (expand_context == EXPAND_FILES || + expand_context == EXPAND_BUFFERS || + expand_context == EXPAND_DIRECTORIES)) + { + for (i = 0; i < cmd_numfiles; ++i) + { + p = strsave_escaped(cmd_files[i], +#ifdef BACKSLASH_IN_FILENAME + (char_u *)" *?[{`$%#"); +#else + (char_u *)" *?[{`$\\%#"); +#endif + if (p != NULL) + { + vim_free(cmd_files[i]); + cmd_files[i] = p; + } + } + } + + if (mode != WILD_ALL && mode != WILD_LONGEST) + { + if (cmd_numfiles > 1) /* more than one match; check suffix */ + { + found = -2; + for (i = 0; i < cmd_numfiles; ++i) + { + fnamelen = STRLEN(cmd_files[i]); + setsuflen = 0; + for (setsuf = p_su; *setsuf; ) + { + setsuflen = copy_option_part(&setsuf, suf_buf, + MAXSUFLEN, ".,"); + if (fnamelen >= setsuflen && STRNCMP(suf_buf, + cmd_files[i] + fnamelen - setsuflen, + (size_t)setsuflen) == 0) + break; + setsuflen = 0; + } + if (setsuflen) /* suffix matched: ignore file */ + continue; + if (found >= 0) + { + multmatch = TRUE; + break; + } + found = i; + } + } + if (multmatch || found < 0) + { + /* Can we ever get here unless it's while expanding + * interactively? If not, we can get rid of this all + * together. Don't really want to wait for this message + * (and possibly have to hit return to continue!). + */ + if (!expand_interactively) + emsg(e_toomany); + else + beep_flush(); + found = 0; /* return first one */ + multmatch = TRUE; /* for found < 0 */ + } + if (found >= 0 && !(multmatch && mode == WILD_EXPAND_FREE)) + ss = strsave(cmd_files[found]); + } + } + } + + /* Find longest common part */ + if (mode == WILD_LONGEST && cmd_numfiles > 0) + { + for (len = 0; cmd_files[0][len]; ++len) + { + for (i = 0; i < cmd_numfiles; ++i) + { +#ifdef CASE_INSENSITIVE_FILENAME + if ((expand_context == EXPAND_DIRECTORIES || + expand_context == EXPAND_FILES || + expand_context == EXPAND_BUFFERS) && + toupper(cmd_files[i][len]) != toupper(cmd_files[0][len])) + break; + else +#endif + if (cmd_files[i][len] != cmd_files[0][len]) + break; + } + if (i < cmd_numfiles) + { + vim_beep(); + break; + } + } + ss = alloc((unsigned)len + 1); + if (ss) + { + STRNCPY(ss, cmd_files[0], len); + ss[len] = NUL; + } + findex = -1; /* next p_wc gets first one */ + } + + /* Concatenate all matching names */ + if (mode == WILD_ALL && cmd_numfiles > 0) + { + len = 0; + for (i = 0; i < cmd_numfiles; ++i) + len += STRLEN(cmd_files[i]) + 1; + ss = lalloc(len, TRUE); + if (ss) + { + *ss = NUL; + for (i = 0; i < cmd_numfiles; ++i) + { + STRCAT(ss, cmd_files[i]); + if (i != cmd_numfiles - 1) + STRCAT(ss, " "); + } + } + } + + if (mode == WILD_EXPAND_FREE || mode == WILD_ALL) + { + FreeWild(cmd_numfiles, cmd_files); + cmd_numfiles = -1; + } + return ss; +} + +/* + * show all matches for completion on the command line + */ + static int +showmatches(buff) + char_u *buff; +{ + char_u *file_str; + int num_files; + char_u **files_found; + int i, j, k; + int maxlen; + int lines; + int columns; + char_u *p; + int lastlen; + + set_expand_context(cmdfirstc, cmdbuff); + if (expand_context == EXPAND_UNSUCCESSFUL) + { + beep_flush(); + return OK; /* Something illegal on command line */ + } + if (expand_context == EXPAND_NOTHING) + { + /* Caller can use the character as a normal char instead */ + return FAIL; + } + expand_interactively = TRUE; + + /* add star to file name, or convert to regexp if not expanding files! */ + file_str = addstar(expand_pattern, (int)(buff + cmdpos - expand_pattern)); + if (file_str == NULL) + { + expand_interactively = FALSE; + return OK; + } + + msg_didany = FALSE; /* lines_left will be set */ + msg_start(); /* prepare for paging */ + msg_outchar('\n'); + flushbuf(); + cmdline_row = msg_row; + msg_didany = FALSE; /* lines_left will be set again */ + msg_start(); /* prepare for paging */ + + /* find all files that match the description */ + if (ExpandFromContext(file_str, &num_files, &files_found, FALSE, 0) == FAIL) + { + num_files = 0; + files_found = (char_u **)""; + } + + /* find the length of the longest file name */ + maxlen = 0; + for (i = 0; i < num_files; ++i) + { + if (expand_context == EXPAND_FILES || expand_context == EXPAND_BUFFERS) + { + home_replace(NULL, files_found[i], NameBuff, MAXPATHL); + j = strsize(NameBuff); + } + else + j = strsize(files_found[i]); + if (j > maxlen) + maxlen = j; + } + + /* compute the number of columns and lines for the listing */ + maxlen += 2; /* two spaces between file names */ + columns = ((int)Columns + 2) / maxlen; + if (columns < 1) + columns = 1; + lines = (num_files + columns - 1) / columns; + + (void)set_highlight('d'); /* find out highlight mode for directories */ + + /* list the files line by line */ + for (i = 0; i < lines; ++i) + { + lastlen = 999; + for (k = i; k < num_files; k += lines) + { + for (j = maxlen - lastlen; --j >= 0; ) + msg_outchar(' '); + if (expand_context == EXPAND_FILES || + expand_context == EXPAND_BUFFERS) + { + /* highlight directories */ + j = (mch_isdir(files_found[k])); + home_replace(NULL, files_found[k], NameBuff, MAXPATHL); + p = NameBuff; + } + else + { + j = FALSE; + p = files_found[k]; + } + if (j) + start_highlight(); + lastlen = msg_outtrans(p); + if (j) + stop_highlight(); + } + msg_outchar('\n'); + flushbuf(); /* show one line at a time */ + if (got_int) + { + got_int = FALSE; + break; + } + } + vim_free(file_str); + FreeWild(num_files, files_found); + +/* + * we redraw the command below the lines that we have just listed + * This is a bit tricky, but it saves a lot of screen updating. + */ + cmdline_row = msg_row; /* will put it back later */ + + expand_interactively = FALSE; + return OK; +} + +/* + * Prepare a string for expansion. + * When expanding file names: The string will be used with ExpandWildCards(). + * Copy the file name into allocated memory and add a '*' at the end. + * When expanding other names: The string will be used with regcomp(). Copy + * the name into allocated memory and add ".*" at the end. + */ + char_u * +addstar(fname, len) + char_u *fname; + int len; +{ + char_u *retval; + int i, j; + int new_len; + char_u *tail; + + if (expand_interactively && expand_context != EXPAND_FILES && + expand_context != EXPAND_DIRECTORIES) + { + /* + * Matching will be done internally (on something other than files). + * So we convert the file-matching-type wildcards into our kind for + * use with vim_regcomp(). First work out how long it will be: + */ + + /* for help tags the translation is done in find_help_tags() */ + if (expand_context == EXPAND_HELP) + retval = strnsave(fname, len); + else + { + new_len = len + 2; /* +2 for '^' at start, NUL at end */ + for (i = 0; i < len; i++) + { + if (fname[i] == '*' || fname[i] == '~') + new_len++; /* '*' needs to be replaced by ".*" + '~' needs to be replaced by "\~" */ + + /* Buffer names are like file names. "." should be literal */ + if (expand_context == EXPAND_BUFFERS && fname[i] == '.') + new_len++; /* "." becomes "\." */ + } + retval = alloc(new_len); + if (retval != NULL) + { + retval[0] = '^'; + j = 1; + for (i = 0; i < len; i++, j++) + { + if (fname[i] == '\\' && ++i == len) /* skip backslash */ + break; + + switch (fname[i]) + { + case '*': retval[j++] = '.'; + break; + case '~': retval[j++] = '\\'; + break; + case '?': retval[j] = '.'; + continue; + case '.': if (expand_context == EXPAND_BUFFERS) + retval[j++] = '\\'; + break; + } + retval[j] = fname[i]; + } + retval[j] = NUL; + } + } + } + else + { + retval = alloc(len + 4); + if (retval != NULL) + { + STRNCPY(retval, fname, len); + retval[len] = NUL; + backslash_halve(retval, TRUE); /* remove some backslashes */ + len = STRLEN(retval); + + /* + * Don't add a star to ~, ~user, $var or `cmd`. + * ~ would be at the start of the tail. + * $ could be anywhere in the tail. + * ` could be anywhere in the file name. + */ + tail = gettail(retval); + if (*tail != '~' && vim_strchr(tail, '$') == NULL + && vim_strchr(retval, '`') == NULL) + { +#ifdef MSDOS + /* + * if there is no dot in the file name, add "*.*" instead of + * "*". + */ + for (i = len - 1; i >= 0; --i) + if (vim_strchr((char_u *)".\\/:", retval[i]) != NULL) + break; + if (i < 0 || retval[i] != '.') + { + retval[len++] = '*'; + retval[len++] = '.'; + } +#endif + retval[len++] = '*'; + } + retval[len] = NUL; + } + } + return retval; +} + +/* + * do_source: read the file "fname" and execute its lines as EX commands + * + * This function may be called recursively! + * + * return FAIL if file could not be opened, OK otherwise + */ + int +do_source(fname, check_other) + register char_u *fname; + int check_other; /* check for .vimrc and _vimrc */ +{ + register FILE *fp; + register int len; +#ifdef USE_CRNL + int has_cr; + int textmode = -1; /* -1 = unknown, 0 = NL, 1 = CR-NL */ + int error = FALSE; +#endif + /* use NameBuff for expanded name */ + expand_env(fname, NameBuff, MAXPATHL); + fp = fopen((char *)NameBuff, READBIN); + if (fp == NULL && check_other) + { + /* + * Try again, replacing file name ".vimrc" by "_vimrc" or vice versa + * (if applicable) + */ + len = STRLEN(NameBuff); + if (((len > 6 && ispathsep(NameBuff[len - 7])) || len == 6) && + (NameBuff[len - 6] == '.' || NameBuff[len - 6] == '_') && + (STRCMP(&NameBuff[len - 5], "vimrc") == 0)) + { + if (NameBuff[len - 6] == '_') + NameBuff[len - 6] = '.'; + else + NameBuff[len - 6] = '_'; + fp = fopen((char *)NameBuff, READBIN); + } + } + + if (fp == NULL) + return FAIL; + +#ifdef USE_CRNL + /* no automatic textmode: Set default to CR-NL */ + if (!p_ta) + textmode = 1; +#endif + sourcing_name = fname; + sourcing_lnum = 1; +#ifdef SLEEP_IN_EMSG + ++dont_sleep; /* don't call sleep() in emsg() */ +#endif + len = 0; + while (fgets((char *)IObuff + len, IOSIZE - len, fp) != NULL && !got_int) + { + len = STRLEN(IObuff) - 1; + if (len >= 0 && IObuff[len] == '\n') /* remove trailing newline */ + { +#ifdef USE_CRNL + has_cr = (len > 0 && IObuff[len - 1] == '\r'); + if (textmode == -1) + { + if (has_cr) + textmode = 1; + else + textmode = 0; + } + + if (textmode) + { + if (has_cr) /* remove trailing CR-LF */ + --len; + else /* lines like ":map xx yy^M" will have failed */ + { + if (!error) + EMSG("Warning: Wrong line separator, ^M may be missing"); + error = TRUE; + textmode = 0; + } + } +#endif + /* escaped newline, read more */ + if (len > 0 && len < IOSIZE && IObuff[len - 1] == Ctrl('V')) + { + IObuff[len - 1] = '\n'; /* remove CTRL-V */ + ++sourcing_lnum; + continue; + } + IObuff[len] = NUL; + } + /* check for ^C here, so recursive :so will be broken */ + mch_breakcheck(); + do_cmdline(IObuff, TRUE, TRUE); + len = 0; + ++sourcing_lnum; + } + fclose(fp); + if (got_int) + emsg(e_interr); +#ifdef SLEEP_IN_EMSG + --dont_sleep; +#endif + sourcing_name = NULL; + sourcing_lnum = 0; + return OK; +} + +/* + * get a single EX address + * + * Set ptr to the next character after the part that was interpreted. + * Set ptr to NULL when an error is encountered. + */ + static linenr_t +get_address(ptr) + char_u **ptr; +{ + linenr_t cursor_lnum = curwin->w_cursor.lnum; + int c; + int i; + long n; + char_u *cmd; + FPOS pos; + FPOS *fp; + linenr_t lnum; + + cmd = skipwhite(*ptr); + lnum = MAXLNUM; + do + { + switch (*cmd) + { + case '.': /* '.' - Cursor position */ + ++cmd; + lnum = cursor_lnum; + break; + + case '$': /* '$' - last line */ + ++cmd; + lnum = curbuf->b_ml.ml_line_count; + break; + + case '\'': /* ''' - mark */ + if (*++cmd == NUL || (check_mark( + fp = getmark(*cmd++, FALSE)) == FAIL)) + goto error; + lnum = fp->lnum; + break; + + case '/': + case '?': /* '/' or '?' - search */ + c = *cmd++; + pos = curwin->w_cursor; /* save curwin->w_cursor */ + if (c == '/') /* forward search, start on next line */ + { + ++curwin->w_cursor.lnum; + curwin->w_cursor.col = 0; + } + else /* backward search, start on prev line */ + { + --curwin->w_cursor.lnum; + curwin->w_cursor.col = MAXCOL; + } + searchcmdlen = 0; + if (!do_search(c, cmd, 1L, + SEARCH_HIS + SEARCH_MSG + SEARCH_START)) + { + cmd = NULL; + curwin->w_cursor = pos; + goto error; + } + lnum = curwin->w_cursor.lnum; + curwin->w_cursor = pos; + /* adjust command string pointer */ + cmd += searchcmdlen; + break; + + case '\\': /* "\?", "\/" or "\&", repeat search */ + ++cmd; + if (*cmd == '&') + i = RE_SUBST; + else if (*cmd == '?' || *cmd == '/') + i = RE_SEARCH; + else + { + emsg(e_backslash); + cmd = NULL; + goto error; + } + + /* forward search, start on next line */ + if (*cmd != '?') + { + pos.lnum = curwin->w_cursor.lnum + 1; + pos.col = 0; + } + /* backward search, start on prev line */ + else + { + pos.lnum = curwin->w_cursor.lnum - 1; + pos.col = MAXCOL; + } + if (searchit(&pos, *cmd == '?' ? BACKWARD : FORWARD, + (char_u *)"", 1L, + SEARCH_MSG + SEARCH_START, i) == OK) + lnum = pos.lnum; + else + { + cmd = NULL; + goto error; + } + ++cmd; + break; + + default: + if (isdigit(*cmd)) /* absolute line number */ + lnum = getdigits(&cmd); + } + + for (;;) + { + cmd = skipwhite(cmd); + if (*cmd != '-' && *cmd != '+' && !isdigit(*cmd)) + break; + + if (lnum == MAXLNUM) + lnum = cursor_lnum; /* "+1" is same as ".+1" */ + if (isdigit(*cmd)) + i = '+'; /* "number" is same as "+number" */ + else + i = *cmd++; + if (!isdigit(*cmd)) /* '+' is '+1', but '+0' is not '+1' */ + n = 1; + else + n = getdigits(&cmd); + if (i == '-') + lnum -= n; + else + lnum += n; + } + cursor_lnum = lnum; + } while (*cmd == '/' || *cmd == '?'); + +error: + *ptr = cmd; + return lnum; +} + + +/* + * Must parse the command line so far to work out what context we are in. + * Completion can then be done based on that context. + * This routine sets two global variables: + * char_u *expand_pattern The start of the pattern to be expanded within + * the command line (ends at the cursor). + * int expand_context The type of thing to expand. Will be one of: + * + * EXPAND_UNSUCCESSFUL Used sometimes when there is something illegal on + * the command line, like an unknown command. Caller + * should beep. + * EXPAND_NOTHING Unrecognised context for completion, use char like + * a normal char, rather than for completion. eg + * :s/^I/ + * EXPAND_COMMANDS Cursor is still touching the command, so complete + * it. + * EXPAND_BUFFERS Complete file names for :buf and :sbuf commands. + * EXPAND_FILES After command with XFILE set, or after setting + * with P_EXPAND set. eg :e ^I, :w>>^I + * EXPAND_DIRECTORIES In some cases this is used instead of the latter + * when we know only directories are of interest. eg + * :set dir=^I + * EXPAND_SETTINGS Complete variable names. eg :set d^I + * EXPAND_BOOL_SETTINGS Complete boolean variables only, eg :set no^I + * EXPAND_TAGS Complete tags from the files in p_tags. eg :ta a^I + * EXPAND_HELP Complete tags from the file 'helpfile'/vim_tags + * EXPAND_EVENTS Complete event names + * + * -- webb. + */ + static void +set_expand_context(firstc, buff) + int firstc; /* either ':', '/', or '?' */ + char_u *buff; /* buffer for command string */ +{ + char_u *nextcomm; + char_u old_char; + + old_char = cmdbuff[cmdpos]; + cmdbuff[cmdpos] = NUL; + nextcomm = buff; + while (nextcomm != NULL) + nextcomm = set_one_cmd_context(firstc, nextcomm); + cmdbuff[cmdpos] = old_char; +} + +/* + * This is all pretty much copied from do_one_cmd(), with all the extra stuff + * we don't need/want deleted. Maybe this could be done better if we didn't + * repeat all this stuff. The only problem is that they may not stay perfectly + * compatible with each other, but then the command line syntax probably won't + * change that much -- webb. + */ + static char_u * +set_one_cmd_context(firstc, buff) + int firstc; /* either ':', '/', or '?' */ + char_u *buff; /* buffer for command string */ +{ + char_u *p; + char_u *cmd, *arg; + int i; + int cmdidx; + long argt; + char_u delim; + int forced = FALSE; + int usefilter = FALSE; /* filter instead of file name */ + + expand_pattern = buff; + if (firstc != ':') + { + expand_context = EXPAND_NOTHING; + return NULL; + } + expand_context = EXPAND_COMMANDS; /* Default until we get past command */ + +/* + * 2. skip comment lines and leading space, colons or bars + */ + for (cmd = buff; vim_strchr((char_u *)" \t:|", *cmd) != NULL; cmd++) + ; + expand_pattern = cmd; + + if (*cmd == NUL) + return NULL; + if (*cmd == '"') /* ignore comment lines */ + { + expand_context = EXPAND_NOTHING; + return NULL; + } + +/* + * 3. parse a range specifier of the form: addr [,addr] [;addr] .. + */ + /* + * Backslashed delimiters after / or ? will be skipped, and commands will + * not be expanded between /'s and ?'s or after "'". -- webb + */ + while (*cmd != NUL && (vim_isspace(*cmd) || isdigit(*cmd) || + vim_strchr((char_u *)".$%'/?-+,;", *cmd) != NULL)) + { + if (*cmd == '\'') + { + if (*++cmd == NUL) + expand_context = EXPAND_NOTHING; + } + else if (*cmd == '/' || *cmd == '?') + { + delim = *cmd++; + while (*cmd != NUL && *cmd != delim) + if (*cmd++ == '\\' && *cmd != NUL) + ++cmd; + if (*cmd == NUL) + expand_context = EXPAND_NOTHING; + } + if (*cmd != NUL) + ++cmd; + } + +/* + * 4. parse command + */ + + cmd = skipwhite(cmd); + expand_pattern = cmd; + if (*cmd == NUL) + return NULL; + if (*cmd == '"') + { + expand_context = EXPAND_NOTHING; + return NULL; + } + + if (*cmd == '|' || *cmd == '\n') + return cmd + 1; /* There's another command */ + + /* + * Isolate the command and search for it in the command table. + * Exeptions: + * - the 'k' command can directly be followed by any character. + * - the 's' command can be followed directly by 'c', 'g' or 'r' + */ + if (*cmd == 'k') + { + cmdidx = CMD_k; + p = cmd + 1; + } + else + { + p = cmd; + while (isalpha(*p) || *p == '*') /* Allow * wild card */ + ++p; + /* check for non-alpha command */ + if (p == cmd && vim_strchr((char_u *)"@!=><&~#", *p) != NULL) + ++p; + i = (int)(p - cmd); + + if (i == 0) + { + expand_context = EXPAND_UNSUCCESSFUL; + return NULL; + } + for (cmdidx = 0; cmdidx < CMD_SIZE; ++cmdidx) + if (STRNCMP(cmdnames[cmdidx].cmd_name, cmd, (size_t)i) == 0) + break; + } + + /* + * If the cursor is touching the command, and it ends in an alphabetic + * character, complete the command name. + */ + if (p == cmdbuff + cmdpos && isalpha(p[-1])) + return NULL; + + if (cmdidx == CMD_SIZE) + { + if (*cmd == 's' && vim_strchr((char_u *)"cgr", cmd[1]) != NULL) + { + cmdidx = CMD_substitute; + p = cmd + 1; + } + else + { + /* Not still touching the command and it was an illegal command */ + expand_context = EXPAND_UNSUCCESSFUL; + return NULL; + } + } + + expand_context = EXPAND_NOTHING; /* Default now that we're past command */ + + if (*p == '!') /* forced commands */ + { + forced = TRUE; + ++p; + } + +/* + * 5. parse arguments + */ + argt = cmdnames[cmdidx].cmd_argt; + + arg = skipwhite(p); + + if (cmdidx == CMD_write) + { + if (*arg == '>') /* append */ + { + if (*++arg == '>') /* It should be */ + ++arg; + arg = skipwhite(arg); + } + else if (*arg == '!') /* :w !filter */ + { + ++arg; + usefilter = TRUE; + } + } + + if (cmdidx == CMD_read) + { + usefilter = forced; /* :r! filter if forced */ + if (*arg == '!') /* :r !filter */ + { + ++arg; + usefilter = TRUE; + } + } + + if (cmdidx == CMD_lshift || cmdidx == CMD_rshift) + { + while (*arg == *cmd) /* allow any number of '>' or '<' */ + ++arg; + arg = skipwhite(arg); + } + + /* Does command allow "+command"? */ + if ((argt & EDITCMD) && !usefilter && *arg == '+') + { + /* Check if we're in the +command */ + p = arg + 1; + arg = skiptowhite(arg); + + /* Still touching the command after '+'? */ + if (arg >= cmdbuff + cmdpos) + return p; + + /* Skip space after +command to get to the real argument */ + arg = skipwhite(arg); + } + + /* + * Check for '|' to separate commands and '"' to start comments. + * Don't do this for ":read !cmd" and ":write !cmd". + */ + if ((argt & TRLBAR) && !usefilter) + { + p = arg; + while (*p) + { + if (*p == Ctrl('V')) + { + if (p[1] != NUL) + ++p; + } + else if ((*p == '"' && !(argt & NOTRLCOM)) || *p == '|' || *p == '\n') + { + if (*(p - 1) != '\\') + { + if (*p == '|' || *p == '\n') + return p + 1; + return NULL; /* It's a comment */ + } + } + ++p; + } + } + + /* no arguments allowed */ + if (!(argt & EXTRA) && *arg != NUL && + vim_strchr((char_u *)"|\"", *arg) == NULL) + return NULL; + + /* Find start of last argument (argument just before cursor): */ + p = cmdbuff + cmdpos; + while (p != arg && *p != ' ' && *p != TAB) + p--; + if (*p == ' ' || *p == TAB) + p++; + expand_pattern = p; + + if (argt & XFILE) + { + int in_quote = FALSE; + char_u *bow = NULL; /* Beginning of word */ + + /* + * Allow spaces within back-quotes to count as part of the argument + * being expanded. + */ + expand_pattern = skipwhite(arg); + for (p = expand_pattern; *p; ++p) + { + if (*p == '\\' && p[1]) + ++p; +#ifdef SPACE_IN_FILENAME + else if (vim_iswhite(*p) && (!(argt & NOSPC) || usefilter)) +#else + else if (vim_iswhite(*p)) +#endif + { + p = skipwhite(p); + if (in_quote) + bow = p; + else + expand_pattern = p; + --p; + } + else if (*p == '`') + { + if (!in_quote) + { + expand_pattern = p; + bow = p + 1; + } + in_quote = !in_quote; + } + } + + /* + * If we are still inside the quotes, and we passed a space, just + * expand from there. + */ + if (bow != NULL && in_quote) + expand_pattern = bow; + expand_context = EXPAND_FILES; + } + +/* + * 6. switch on command name + */ + switch (cmdidx) + { + case CMD_cd: + case CMD_chdir: + expand_context = EXPAND_DIRECTORIES; + break; + case CMD_global: + case CMD_vglobal: + delim = *arg; /* get the delimiter */ + if (delim) + ++arg; /* skip delimiter if there is one */ + + while (arg[0] != NUL && arg[0] != delim) + { + if (arg[0] == '\\' && arg[1] != NUL) + ++arg; + ++arg; + } + if (arg[0] != NUL) + return arg + 1; + break; + case CMD_and: + case CMD_substitute: + delim = *arg; + if (delim) + ++arg; + for (i = 0; i < 2; i++) + { + while (arg[0] != NUL && arg[0] != delim) + { + if (arg[0] == '\\' && arg[1] != NUL) + ++arg; + ++arg; + } + if (arg[0] != NUL) /* skip delimiter */ + ++arg; + } + while (arg[0] && vim_strchr((char_u *)"|\"#", arg[0]) == NULL) + ++arg; + if (arg[0] != NUL) + return arg; + break; + case CMD_isearch: + case CMD_dsearch: + case CMD_ilist: + case CMD_dlist: + case CMD_ijump: + case CMD_djump: + case CMD_isplit: + case CMD_dsplit: + arg = skipwhite(skipdigits(arg)); /* skip count */ + if (*arg == '/') /* Match regexp, not just whole words */ + { + for (++arg; *arg && *arg != '/'; arg++) + if (*arg == '\\' && arg[1] != NUL) + arg++; + if (*arg) + { + arg = skipwhite(arg + 1); + + /* Check for trailing illegal characters */ + if (*arg && vim_strchr((char_u *)"|\"\n", *arg) == NULL) + expand_context = EXPAND_NOTHING; + else + return arg; + } + } + break; +#ifdef AUTOCMD + case CMD_autocmd: + return set_context_in_autocmd(arg, FALSE); + + case CMD_doautocmd: + return set_context_in_autocmd(arg, TRUE); +#endif + case CMD_set: + set_context_in_set_cmd(arg); + break; + case CMD_stag: + case CMD_tag: + expand_context = EXPAND_TAGS; + expand_pattern = arg; + break; + case CMD_help: + expand_context = EXPAND_HELP; + expand_pattern = arg; + break; + case CMD_bdelete: + case CMD_bunload: + while ((expand_pattern = vim_strchr(arg, ' ')) != NULL) + arg = expand_pattern + 1; + case CMD_buffer: + case CMD_sbuffer: + expand_context = EXPAND_BUFFERS; + expand_pattern = arg; + break; +#ifdef USE_GUI + case CMD_menu: case CMD_noremenu: case CMD_unmenu: + case CMD_nmenu: case CMD_nnoremenu: case CMD_nunmenu: + case CMD_vmenu: case CMD_vnoremenu: case CMD_vunmenu: + case CMD_imenu: case CMD_inoremenu: case CMD_iunmenu: + case CMD_cmenu: case CMD_cnoremenu: case CMD_cunmenu: + return gui_set_context_in_menu_cmd(cmd, arg, forced); + break; +#endif + default: + break; + } + return NULL; +} + +/* + * Do the expansion based on the global variables expand_context and + * expand_pattern -- webb. + */ + static int +ExpandFromContext(pat, num_file, file, files_only, options) + char_u *pat; + int *num_file; + char_u ***file; + int files_only; + int options; +{ + regexp *prog; + int ret; + int i; + int count; + + if (!expand_interactively || expand_context == EXPAND_FILES) + return ExpandWildCards(1, &pat, num_file, file, files_only, + (options & WILD_LIST_NOTFOUND)); + else if (expand_context == EXPAND_DIRECTORIES) + { + if (ExpandWildCards(1, &pat, num_file, file, files_only, + (options & WILD_LIST_NOTFOUND)) == FAIL) + return FAIL; + count = 0; + for (i = 0; i < *num_file; i++) + if (mch_isdir((*file)[i])) + (*file)[count++] = (*file)[i]; + else + vim_free((*file)[i]); + if (count == 0) + { + vim_free(*file); + *file = (char_u **)""; + *num_file = -1; + return FAIL; + } + *num_file = count; + return OK; + } + *file = (char_u **)""; + *num_file = 0; + if (expand_context == EXPAND_OLD_SETTING) + return ExpandOldSetting(num_file, file); + + if (expand_context == EXPAND_HELP) + return find_help_tags(pat, num_file, file); + + set_reg_ic(pat); /* set reg_ic according to p_ic, p_scs and pat */ +#ifdef AUTOCMD + if (expand_context == EXPAND_EVENTS) + reg_ic = TRUE; /* always ignore case for events */ +#endif + reg_magic = p_magic; + + if (expand_context == EXPAND_BUFFERS) + return ExpandBufnames(pat, num_file, file, options); + + prog = vim_regcomp(pat); + if (prog == NULL) + return FAIL; + + if (expand_context == EXPAND_COMMANDS) + ret = ExpandCommands(prog, num_file, file); + else if (expand_context == EXPAND_SETTINGS || + expand_context == EXPAND_BOOL_SETTINGS) + ret = ExpandSettings(prog, num_file, file); + else if (expand_context == EXPAND_TAGS) + ret = find_tags(NULL, prog, num_file, file, FALSE); +#ifdef AUTOCMD + else if (expand_context == EXPAND_EVENTS) + ret = ExpandEvents(prog, num_file, file); +#endif +#ifdef USE_GUI + else if (expand_context == EXPAND_MENUS) + ret = gui_ExpandMenuNames(prog, num_file, file); +#endif + else + ret = FAIL; + + vim_free(prog); + return ret; +} + + static int +ExpandCommands(prog, num_file, file) + regexp *prog; + int *num_file; + char_u ***file; +{ + int cmdidx; + int count; + int round; + + /* + * round == 1: Count the matches. + * round == 2: Save the matches into the array. + */ + for (round = 1; round <= 2; ++round) + { + count = 0; + for (cmdidx = 0; cmdidx < CMD_SIZE; cmdidx++) + if (vim_regexec(prog, cmdnames[cmdidx].cmd_name, TRUE)) + { + if (round == 1) + count++; + else + (*file)[count++] = strsave(cmdnames[cmdidx].cmd_name); + } + if (round == 1) + { + *num_file = count; + if (count == 0 || (*file = (char_u **) + alloc((unsigned)(count * sizeof(char_u *)))) == NULL) + return FAIL; + } + } + return OK; +} + +#ifdef VIMINFO +static char_u **viminfo_history[2] = {NULL, NULL}; +static int viminfo_hisidx[2] = {0, 0}; +static int viminfo_hislen = 0; +static int viminfo_add_at_front = FALSE; + + void +prepare_viminfo_history(len) + int len; +{ + int i; + int num; + int type; + + init_history(); + viminfo_add_at_front = (len != 0); + if (len > hislen) + len = hislen; + + for (type = 0; type <= 1; ++type) + { + /* If there are more spaces available than we request, then fill them + * up */ + for (i = 0, num = 0; i < hislen; i++) + if (history[type][i] == NULL) + num++; + if (num > len) + len = num; + viminfo_hisidx[type] = 0; + if (len <= 0) + viminfo_history[type] = NULL; + else + viminfo_history[type] = (char_u **)lalloc(len * sizeof(char_u *), + FALSE); + } + viminfo_hislen = len; + if (viminfo_history[0] == NULL || viminfo_history[1] == NULL) + viminfo_hislen = 0; +} + + int +read_viminfo_history(line, fp) + char_u *line; + FILE *fp; +{ + int type; + + type = (line[0] == ':' ? 0 : 1); + if (viminfo_hisidx[type] != viminfo_hislen) + { + viminfo_readstring(line); + if (!is_in_history(type, line + 1, viminfo_add_at_front)) + viminfo_history[type][viminfo_hisidx[type]++] = strsave(line + 1); + } + return vim_fgets(line, LSIZE, fp); +} + + void +finish_viminfo_history() +{ + int idx; + int i; + int type; + + for (type = 0; type <= 1; ++type) + { + if (history[type] == NULL) + return; + idx = hisidx[type] + viminfo_hisidx[type]; + if (idx >= hislen) + idx -= hislen; + if (viminfo_add_at_front) + hisidx[type] = idx; + else + { + if (hisidx[type] == -1) + hisidx[type] = hislen - 1; + do + { + if (history[type][idx] != NULL) + break; + if (++idx == hislen) + idx = 0; + } while (idx != hisidx[type]); + if (idx != hisidx[type] && --idx < 0) + idx = hislen - 1; + } + for (i = 0; i < viminfo_hisidx[type]; i++) + { + history[type][idx] = viminfo_history[type][i]; + if (--idx < 0) + idx = hislen - 1; + } + vim_free(viminfo_history[type]); + viminfo_history[type] = NULL; + } +} + + void +write_viminfo_history(fp) + FILE *fp; +{ + int i; + int type; + int num_saved; + + init_history(); + if (hislen == 0) + return; + for (type = 0; type <= 1; ++type) + { + num_saved = get_viminfo_parameter(type == 0 ? ':' : '/'); + if (num_saved == 0) + continue; + if (num_saved < 0) /* Use default */ + num_saved = hislen; + fprintf(fp, "\n# %s History (newest to oldest):\n", + type == 0 ? "Command Line" : "Search String"); + if (num_saved > hislen) + num_saved = hislen; + i = hisidx[type]; + if (i >= 0) + while (num_saved--) + { + if (history[type][i] != NULL) + { + putc(type == 0 ? ':' : '?', fp); + viminfo_writestring(fp, history[type][i]); + } + if (--i < 0) + i = hislen - 1; + } + } +} +#endif /* VIMINFO */ diff --git a/usr.bin/vim/cmdtab.tab b/usr.bin/vim/cmdtab.tab new file mode 100644 index 00000000000..922fc7419d2 --- /dev/null +++ b/usr.bin/vim/cmdtab.tab @@ -0,0 +1,253 @@ +/* $OpenBSD: cmdtab.tab,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* vi:ts=4:sw=4 + * + * VIM - Vi IMproved + * + * Code Contributions By: Bram Moolenaar mool@plex.nl + * Tim Thompson twitch!tjt + * Tony Andrews onecom!wldrdg!tony + * G. R. (Fred) Walter watmath!watcgl!grwalter + */ + +/* + * |This file is read by mkcmdtab to produce cmdtab.h. + * + * The bars are used to recognize file positions. Do not insert/delete them.| + */ + +#define RANGE 0x01 /* allow a linespecs */ +#define BANG 0x02 /* allow a ! after the command name */ +#define EXTRA 0x04 /* allow extra args after command name */ +#define XFILE 0x08 /* expand wildcards in extra part */ +#define NOSPC 0x10 /* no spaces allowed in the extra part */ +#define DFLALL 0x20 /* default file range is 1,$ */ +#define NODFL 0x40 /* do not default to the current file name */ +#define NEEDARG 0x80 /* argument required */ +#define TRLBAR 0x100 /* check for trailing vertical bar */ +#define REGSTR 0x200 /* allow "x for register designation */ +#define COUNT 0x400 /* allow count in argument, after command */ +#define NOTRLCOM 0x800 /* no trailing comment allowed */ +#define ZEROR 0x1000 /* zero line number allowed */ +#define USECTRLV 0x2000 /* do not remove CTRL-V from argument */ +#define NOTADR 0x4000 /* number before command is not an address */ +#define EDITCMD 0x8000 /* has "+command" argument */ +#define BUFNAME 0x10000 /* accepts buffer name */ +#define FILES (XFILE + EXTRA) /* multiple extra files allowed */ +#define WORD1 (EXTRA + NOSPC) /* one extra word allowed */ +#define FILE1 (FILES + NOSPC) /* 1 file allowed, defaults to current file */ +#define NAMEDF (FILE1 + NODFL) /* 1 file allowed, defaults to "" */ +#define NAMEDFS (FILES + NODFL) /* multiple files allowed, default is "" */ + +/* + * This array maps ex command names to command codes. The order in which + * command names are listed below is significant -- ambiguous abbreviations + * are always resolved to be the first possible match (e.g. "r" is taken + * to mean "read", not "rewind", because "read" comes before "rewind"). + * Not supported commands are included to avoid ambiguities. + */ +static struct +{ + char_u *cmd_name; /* name of the command */ + long_u cmd_argt; /* command line arguments permitted/needed/used */ +} cmdnames[] = +{ +| {(char_u *)"append", BANG+RANGE+TRLBAR}, /* not supported */ + {(char_u *)"all", RANGE+NOTADR+COUNT+TRLBAR}, + {(char_u *)"abbreviate", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"abclear", TRLBAR}, + {(char_u *)"args", BANG+NAMEDFS+EDITCMD+TRLBAR}, + {(char_u *)"argument", BANG+RANGE+NOTADR+COUNT+EXTRA+EDITCMD+TRLBAR}, + {(char_u *)"ascii", TRLBAR}, + {(char_u *)"autocmd", BANG+EXTRA+NOTRLCOM+USECTRLV}, + {(char_u *)"buffer", RANGE+NOTADR+BUFNAME+COUNT+EXTRA+TRLBAR}, + {(char_u *)"ball", RANGE+NOTADR+COUNT+TRLBAR}, + {(char_u *)"buffers", TRLBAR}, + {(char_u *)"bdelete", BANG+RANGE+BUFNAME+NOTADR+COUNT+EXTRA+TRLBAR}, + {(char_u *)"bunload", BANG+RANGE+BUFNAME+NOTADR+COUNT+EXTRA+TRLBAR}, + {(char_u *)"bmodified", RANGE+NOTADR+COUNT+TRLBAR}, + {(char_u *)"bnext", RANGE+NOTADR+COUNT+TRLBAR}, + {(char_u *)"bNext", RANGE+NOTADR+COUNT+TRLBAR}, + {(char_u *)"bprevious", RANGE+NOTADR+COUNT+TRLBAR}, + {(char_u *)"brewind", RANGE+TRLBAR}, + {(char_u *)"blast", RANGE+TRLBAR}, + {(char_u *)"change", BANG+RANGE+COUNT+TRLBAR}, /* not supported */ + {(char_u *)"cabbrev", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"cabclear", TRLBAR}, + {(char_u *)"cc", RANGE+NOTADR+COUNT+TRLBAR+BANG}, + {(char_u *)"cd", NAMEDF+TRLBAR}, + {(char_u *)"center", TRLBAR+RANGE+EXTRA}, + {(char_u *)"cfile", TRLBAR+FILE1+BANG}, + {(char_u *)"chdir", NAMEDF+TRLBAR}, + {(char_u *)"checkpath", TRLBAR+BANG}, + {(char_u *)"clist", TRLBAR+BANG}, + {(char_u *)"close", BANG+TRLBAR}, + {(char_u *)"cmap", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"cmapclear", TRLBAR}, + {(char_u *)"cmenu", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"cnext", RANGE+NOTADR+COUNT+TRLBAR+BANG}, + {(char_u *)"cNext", RANGE+NOTADR+COUNT+TRLBAR+BANG}, + {(char_u *)"cnoremap", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"cnoremenu", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"cnoreabbrev", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"copy", RANGE+EXTRA+TRLBAR}, + {(char_u *)"cprevious", RANGE+NOTADR+COUNT+TRLBAR+BANG}, + {(char_u *)"cquit", TRLBAR+BANG}, + {(char_u *)"cunmap", EXTRA+TRLBAR+USECTRLV}, + {(char_u *)"cunmenu", EXTRA+TRLBAR+USECTRLV}, + {(char_u *)"cunabbrev", EXTRA+TRLBAR+USECTRLV}, + {(char_u *)"delete", RANGE+REGSTR+COUNT+TRLBAR}, + {(char_u *)"display", EXTRA+NOTRLCOM+TRLBAR}, + {(char_u *)"digraphs", EXTRA+TRLBAR}, + {(char_u *)"djump", BANG+RANGE+DFLALL+EXTRA}, + {(char_u *)"dlist", BANG+RANGE+DFLALL+EXTRA}, + {(char_u *)"doautocmd", EXTRA+TRLBAR}, + {(char_u *)"dsearch", BANG+RANGE+DFLALL+EXTRA}, + {(char_u *)"dsplit", BANG+RANGE+DFLALL+EXTRA+TRLBAR}, + {(char_u *)"edit", BANG+FILE1+EDITCMD+TRLBAR}, + {(char_u *)"ex", BANG+FILE1+EDITCMD+TRLBAR}, + {(char_u *)"exit", RANGE+BANG+FILE1+DFLALL+TRLBAR}, + {(char_u *)"file", BANG+FILE1+TRLBAR}, + {(char_u *)"files", TRLBAR}, + {(char_u *)"fixdel", TRLBAR}, + {(char_u *)"global", RANGE+BANG+EXTRA+DFLALL}, + {(char_u *)"gui", BANG+NAMEDFS+EDITCMD+TRLBAR}, + {(char_u *)"gvim", BANG+NAMEDFS+EDITCMD+TRLBAR}, + {(char_u *)"help", EXTRA+NOTRLCOM}, + {(char_u *)"insert", BANG+RANGE+TRLBAR}, /* not supported */ + {(char_u *)"iabbrev", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"iabclear", TRLBAR}, + {(char_u *)"ijump", BANG+RANGE+DFLALL+EXTRA}, + {(char_u *)"ilist", BANG+RANGE+DFLALL+EXTRA}, + {(char_u *)"imap", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"imapclear", TRLBAR}, + {(char_u *)"imenu", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"inoremap", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"inoremenu", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"inoreabbrev", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"isearch", BANG+RANGE+DFLALL+EXTRA}, + {(char_u *)"isplit", BANG+RANGE+DFLALL+EXTRA+TRLBAR}, + {(char_u *)"iunmap", EXTRA+TRLBAR+USECTRLV}, + {(char_u *)"iunmenu", EXTRA+TRLBAR+USECTRLV}, + {(char_u *)"iunabbrev", EXTRA+TRLBAR+USECTRLV}, + {(char_u *)"join", BANG+RANGE+COUNT+TRLBAR}, + {(char_u *)"jumps", TRLBAR}, + {(char_u *)"k", RANGE+WORD1+TRLBAR}, + {(char_u *)"list", RANGE+COUNT+TRLBAR}, + {(char_u *)"last", EXTRA+BANG+EDITCMD+TRLBAR}, + {(char_u *)"left", TRLBAR+RANGE+EXTRA}, + {(char_u *)"ls", TRLBAR}, + {(char_u *)"move", RANGE+EXTRA+TRLBAR}, + {(char_u *)"mark", RANGE+WORD1+TRLBAR}, + {(char_u *)"marks", EXTRA+TRLBAR}, + {(char_u *)"map", BANG+EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"mapclear", BANG+TRLBAR}, + {(char_u *)"make", NEEDARG+EXTRA+TRLBAR+XFILE}, + {(char_u *)"menu", BANG+EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"mkexrc", BANG+FILE1+TRLBAR}, + {(char_u *)"mkvimrc", BANG+FILE1+TRLBAR}, + {(char_u *)"mfstat", TRLBAR}, /* for debugging */ + {(char_u *)"mode", WORD1+TRLBAR}, + {(char_u *)"next", RANGE+NOTADR+BANG+NAMEDFS+EDITCMD+TRLBAR}, + {(char_u *)"new", BANG+FILE1+RANGE+NOTADR+EDITCMD+TRLBAR}, + {(char_u *)"nmap", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"nmapclear", TRLBAR}, + {(char_u *)"nmenu", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"nnoremap", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"nnoremenu", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"number", RANGE+COUNT+TRLBAR}, + {(char_u *)"#", RANGE+COUNT+TRLBAR}, + {(char_u *)"noremap", BANG+EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"noremenu", BANG+EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"noreabbrev", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"normal", BANG+EXTRA+NEEDARG+NOTRLCOM+USECTRLV}, + {(char_u *)"nunmap", EXTRA+TRLBAR+USECTRLV}, + {(char_u *)"nunmenu", EXTRA+TRLBAR+USECTRLV}, + {(char_u *)"Next", EXTRA+RANGE+NOTADR+COUNT+BANG+EDITCMD+TRLBAR}, + {(char_u *)"open", TRLBAR}, /* not supported */ + {(char_u *)"only", BANG+TRLBAR}, + {(char_u *)"print", RANGE+COUNT+TRLBAR}, + {(char_u *)"pop", RANGE+NOTADR+COUNT+TRLBAR+ZEROR}, + {(char_u *)"put", RANGE+BANG+REGSTR+TRLBAR}, + {(char_u *)"preserve", TRLBAR}, + {(char_u *)"previous", EXTRA+RANGE+NOTADR+COUNT+BANG+EDITCMD+TRLBAR}, + {(char_u *)"pwd", TRLBAR}, + {(char_u *)"quit", BANG+TRLBAR}, + {(char_u *)"qall", BANG+TRLBAR}, + {(char_u *)"read", BANG+RANGE+NAMEDF+TRLBAR+ZEROR}, + {(char_u *)"rewind", EXTRA+BANG+EDITCMD+TRLBAR}, + {(char_u *)"recover", BANG+FILE1+TRLBAR}, + {(char_u *)"redo", TRLBAR}, + {(char_u *)"registers", EXTRA+NOTRLCOM+TRLBAR}, + {(char_u *)"resize", TRLBAR+WORD1}, + {(char_u *)"retab", TRLBAR+RANGE+DFLALL+BANG+WORD1}, + {(char_u *)"right", TRLBAR+RANGE+EXTRA}, + {(char_u *)"rviminfo", BANG+FILE1+TRLBAR}, /* only when VIMINFO defined */ + {(char_u *)"substitute", RANGE+EXTRA}, + {(char_u *)"sargument", BANG+RANGE+NOTADR+COUNT+EXTRA+EDITCMD+TRLBAR}, + {(char_u *)"sall", RANGE+NOTADR+COUNT+TRLBAR}, + {(char_u *)"sbuffer", RANGE+NOTADR+BUFNAME+COUNT+EXTRA+TRLBAR}, + {(char_u *)"sball", RANGE+NOTADR+COUNT+TRLBAR}, + {(char_u *)"sbmodified", RANGE+NOTADR+COUNT+TRLBAR}, + {(char_u *)"sbnext", RANGE+NOTADR+COUNT+TRLBAR}, + {(char_u *)"sbNext", RANGE+NOTADR+COUNT+TRLBAR}, + {(char_u *)"sbprevious", RANGE+NOTADR+COUNT+TRLBAR}, + {(char_u *)"sbrewind", TRLBAR}, + {(char_u *)"sblast", TRLBAR}, + {(char_u *)"suspend", TRLBAR+BANG}, + {(char_u *)"set", EXTRA+TRLBAR}, + {(char_u *)"shell", TRLBAR}, + {(char_u *)"sleep", RANGE+COUNT+NOTADR+TRLBAR}, + {(char_u *)"source", BANG+NAMEDF+NEEDARG+TRLBAR}, + {(char_u *)"split", BANG+FILE1+RANGE+NOTADR+EDITCMD+TRLBAR}, + {(char_u *)"snext", RANGE+NOTADR+BANG+NAMEDFS+EDITCMD+TRLBAR}, + {(char_u *)"sNext", EXTRA+RANGE+NOTADR+COUNT+BANG+EDITCMD+TRLBAR}, + {(char_u *)"sprevious", EXTRA+RANGE+NOTADR+COUNT+BANG+EDITCMD+TRLBAR}, + {(char_u *)"srewind", EXTRA+BANG+EDITCMD+TRLBAR}, + {(char_u *)"slast", EXTRA+BANG+EDITCMD+TRLBAR}, + {(char_u *)"stop", TRLBAR+BANG}, + {(char_u *)"stag", RANGE+NOTADR+BANG+WORD1+TRLBAR+ZEROR}, + {(char_u *)"sunhide", RANGE+NOTADR+COUNT+TRLBAR}, + {(char_u *)"sview", NEEDARG+RANGE+BANG+FILE1+EDITCMD+TRLBAR}, + {(char_u *)"swapname", TRLBAR}, + {(char_u *)"t", RANGE+EXTRA+TRLBAR}, + {(char_u *)"tag", RANGE+NOTADR+BANG+WORD1+TRLBAR+ZEROR}, + {(char_u *)"tags", TRLBAR}, + {(char_u *)"undo", TRLBAR}, + {(char_u *)"unabbreviate", EXTRA+TRLBAR+USECTRLV}, + {(char_u *)"unhide", RANGE+NOTADR+COUNT+TRLBAR}, + {(char_u *)"unmap", BANG+EXTRA+TRLBAR+USECTRLV}, + {(char_u *)"unmenu", BANG+EXTRA+TRLBAR+USECTRLV}, + {(char_u *)"vglobal", RANGE+EXTRA+DFLALL}, + {(char_u *)"version", EXTRA+TRLBAR}, + {(char_u *)"visual", RANGE+BANG+FILE1+EDITCMD+TRLBAR}, + {(char_u *)"view", RANGE+BANG+FILE1+EDITCMD+TRLBAR}, + {(char_u *)"vmap", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"vmapclear", TRLBAR}, + {(char_u *)"vmenu", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"vnoremap", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"vnoremenu", EXTRA+TRLBAR+NOTRLCOM+USECTRLV}, + {(char_u *)"vunmap", EXTRA+TRLBAR+USECTRLV}, + {(char_u *)"vunmenu", EXTRA+TRLBAR+USECTRLV}, + {(char_u *)"write", RANGE+BANG+FILE1+DFLALL+TRLBAR}, + {(char_u *)"wnext", RANGE+NOTADR+BANG+FILE1+TRLBAR}, + {(char_u *)"wNext", RANGE+NOTADR+BANG+FILE1+TRLBAR}, + {(char_u *)"wprevious", RANGE+NOTADR+BANG+FILE1+TRLBAR}, + {(char_u *)"winsize", EXTRA+NEEDARG+TRLBAR}, + {(char_u *)"wq", RANGE+BANG+FILE1+DFLALL+TRLBAR}, + {(char_u *)"wall", BANG+TRLBAR}, + {(char_u *)"wqall", BANG+FILE1+DFLALL+TRLBAR}, + {(char_u *)"wviminfo", BANG+FILE1+TRLBAR}, /* only when VIMINFO defined */ + {(char_u *)"xit", RANGE+BANG+FILE1+DFLALL+TRLBAR}, + {(char_u *)"xall", BANG+TRLBAR}, + {(char_u *)"yank", RANGE+REGSTR+COUNT+TRLBAR}, + {(char_u *)"z", RANGE+COUNT+TRLBAR}, /* not supported */ + {(char_u *)"@", RANGE+EXTRA+TRLBAR}, + {(char_u *)"!", RANGE+BANG+NAMEDFS}, + {(char_u *)"<", RANGE+COUNT+TRLBAR}, + {(char_u *)">", RANGE+COUNT+TRLBAR}, + {(char_u *)"=", RANGE+TRLBAR}, + {(char_u *)"&", RANGE+EXTRA}, + {(char_u *)"~", RANGE+EXTRA} +| +}; +| diff --git a/usr.bin/vim/config.h b/usr.bin/vim/config.h new file mode 100644 index 00000000000..edfd092f8fc --- /dev/null +++ b/usr.bin/vim/config.h @@ -0,0 +1,244 @@ +/* $OpenBSD: config.h,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* config.h. Generated automatically by configure. */ +/* config.h.in. Generated automatically from configure.in by autoheader. */ + +/* Define unless no X support found */ +/* #undef HAVE_X11 */ + +/* Define when curses library found */ +/* #undef HAVE_LIBCURSES */ + +/* Define when termcap library found */ +/* #undef HAVE_LIBTERMCAP */ + +/* Define when termlib library found */ +#define HAVE_LIBTERMLIB 1 + +/* Define when ncurses library found */ +/* #undef HAVE_LIBNCURSES */ + +/* Define when terminfo support found */ +#define TERMINFO 1 + +/* Define when termcap.h contains ospeed */ +/* #undef HAVE_OSPEED */ + +/* Define when ospeed can be extern */ +#define OSPEED_EXTERN 1 + +/* Define when termcap.h contains UP, BC and PC */ +/* #undef HAVE_UP_BC_PC */ + +/* Define when UP, BC and PC can be extern */ +#define UP_BC_PC_EXTERN 1 + +/* Define when termcap.h defines outfuntype */ +/* #undef HAVE_OUTFUNTYPE */ + +/* Define when __DATE__ " " __TIME__ can be used */ +#define HAVE_DATE_TIME 1 + +#define UNIX 1 /* define always by current configure script */ +/* #undef SVR4 */ /* an elf-based system is SVR4. What is linux? */ + +/* Defined to the size of an int */ +#define SIZEOF_INT 4 + +/* + * If we cannot trust one of the following from the libraries, we use our + * own safe but probably slower vim_memmove(). + */ +/* #undef USEBCOPY */ +#define USEMEMMOVE 1 +/* #undef USEMEMCPY */ + +/* Define to empty if the keyword does not work. */ +/* #undef const */ + +/* Define to `int' if doesn't define. */ +/* #undef mode_t */ + +/* Define to `long' if doesn't define. */ +/* #undef off_t */ + +/* Define to `long' if doesn't define. */ +/* #undef pid_t */ + +/* Define to `unsigned' if doesn't define. */ +/* #undef size_t */ + +/* Define to `int' if doesn't define. */ +/* #undef uid_t */ + +/* Define to `int' if doesn't define. */ +/* #undef gid_t */ + +/* Define if you can safely include both and . */ +#define TIME_WITH_SYS_TIME 1 + +/* Define if you can safely include both and . */ +#define SYS_SELECT_WITH_SYS_TIME 1 + +/* Define as the return type of signal handlers (int or void). */ +#define RETSIGTYPE void + +/* Define as the command at the end of signal handlers ("" or "return 0;"). */ +#define SIGRETURN return + +/* Define if touuper/tolower only work on lower/upercase characters */ +/* #undef BROKEN_TOUPPER */ + +/* Define if tgetstr() has a second argument that is (char *) */ +/* #undef TGETSTR_CHAR_P */ + +/* Define if you have the sigset() function. */ +/* #undef HAVE_SIGSET */ + +/* Define if the getcwd() function should not be used. */ +/* #undef BAD_GETCWD */ + +/* Define if you have the getcwd() function. */ +#define HAVE_GETCWD 1 + +/* Define if you have the getwd() function. */ +#define HAVE_GETWD 1 + +/* Define if you have the select() function. */ +#define HAVE_SELECT 1 + +/* Define if you have the strcspn() function. */ +#define HAVE_STRCSPN 1 + +/* Define if you have the strtol() function. */ +#define HAVE_STRTOL 1 + +/* Define if you have the killpg() function. */ +#define HAVE_KILLPG 1 + +/* Define if you have the tgetent() function. */ +#define HAVE_TGETENT 1 + +/* Define if you have the memset() function. */ +#define HAVE_MEMSET 1 + +/* Define if you have the strerror() function. */ +#define HAVE_STRERROR 1 + +/* Define if you have the fchown() function. */ +#define HAVE_FCHOWN 1 + +/* Define if you have the rename() function. */ +#define HAVE_RENAME 1 + +/* Define if you have the fsync() function. */ +#define HAVE_FSYNC 1 + +/* Define if you have the fchdir() function. */ +#define HAVE_FCHDIR 1 + +/* Define if you have the setenv() function. */ +#define HAVE_SETENV 1 + +/* Define if you have the putenv() function. */ +#define HAVE_PUTENV 1 + +/* Define if you have the gettimeofday() function. */ +#define HAVE_GETTIMEOFDAY 1 + +/* Define if you have the getpwuid() function. */ +#define HAVE_GETPWUID 1 + +/* Define if you have the getpwnam() function. */ +#define HAVE_GETPWNAM 1 + +/* Define if you have the qsort() function. */ +#define HAVE_QSORT 1 + +/* Define if you have the header file. */ +#define HAVE_DIRENT_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_NDIR_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_DIR_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_NDIR_H */ + +/* Define if you have that is POSIX.1 compatible. */ +#define HAVE_SYS_WAIT_H 1 + +/* Define if you have a that is not POSIX.1 compatible. */ +/* #undef HAVE_UNION_WAIT */ + +/* This is currently unused in vim: */ +/* Define if you have the ANSI C header files. */ +/* #undef STDC_HEADERS */ + +/* instead, we check a few STDC things ourselves */ +#define HAVE_STDLIB_H 1 +#define HAVE_STRING_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_SELECT_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_UTSNAME_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_TERMCAP_H */ + +/* Define if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define if you have the header file. */ +#define HAVE_SGTTY_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_TERMIO_H */ + +/* Define if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_STROPTS_H */ + +/* Define if you have the header file. */ +#define HAVE_ERRNO_H 1 + +/* Define if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_SYSTEMINFO_H */ + +/* Define if you have the header file. */ +#define HAVE_LOCALE_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_STREAM_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_PTEM_H */ + +/* Define if you have the header file. */ +#define HAVE_TERMIOS_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_LIBC_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_STATFS_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_POLL_H */ + +/* Define if you have the header file. */ +#define HAVE_PWD_H 1 diff --git a/usr.bin/vim/csearch.c b/usr.bin/vim/csearch.c new file mode 100644 index 00000000000..0e399b07608 --- /dev/null +++ b/usr.bin/vim/csearch.c @@ -0,0 +1,690 @@ +/* $OpenBSD: csearch.c,v 1.1.1.1 1996/09/07 21:40:26 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * + * csearch.c: do_sub() and do_glob() for :s, :g and :v + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +/* we use modified Henry Spencer's regular expression routines */ +#include "regexp.h" + +static int do_sub_msg __ARGS((void)); + +#ifdef VIMINFO + static char_u *old_sub = NULL; +#endif /* VIMINFO */ + +/* do_sub(lp, up, cmd) + * + * Perform a substitution from line 'lp' to line 'up' using the + * command pointed to by 'cmd' which should be of the form: + * + * /pattern/substitution/gc + * + * The trailing 'g' is optional and, if present, indicates that multiple + * substitutions should be performed on each line, if applicable. + * The trailing 'c' is optional and, if present, indicates that a confirmation + * will be asked for each replacement. + * The usual escapes are supported as described in the regexp docs. + * + * use_old == 0 for :substitute + * use_old == 1 for :& + * use_old == 2 for :~ + */ + +static long sub_nsubs; /* total number of substitutions */ +static linenr_t sub_nlines; /* total number of lines changed */ + + void +do_sub(lp, up, cmd, nextcommand, use_old) + linenr_t lp; + linenr_t up; + char_u *cmd; + char_u **nextcommand; + int use_old; +{ + linenr_t lnum; + long i; + char_u *ptr; + char_u *old_line; + regexp *prog; + static int do_all = FALSE; /* do multiple substitutions per line */ + static int do_ask = FALSE; /* ask for confirmation */ + int do_print = FALSE; /* print last line with subst. */ + char_u *pat, *sub; +#ifndef VIMINFO /* otherwise it is global */ + static char_u *old_sub = NULL; +#endif + int delimiter; + int sublen; + int got_quit = FALSE; + int got_match = FALSE; + int temp; + int which_pat; + + if (!global_busy) + { + sub_nsubs = 0; + sub_nlines = 0; + } + + if (use_old == 2) + which_pat = RE_LAST; /* use last used regexp */ + else + which_pat = RE_SUBST; /* use last substitute regexp */ + + /* new pattern and substitution */ + if (use_old == 0 && *cmd != NUL && + vim_strchr((char_u *)"0123456789gcr|\"", *cmd) == NULL) + { + /* don't accept alphanumeric for separator */ + if (isalpha(*cmd) || isdigit(*cmd)) + { + EMSG("Regular expressions can't be delimited by letters or digits"); + return; + } + /* + * undocumented vi feature: + * "\/sub/" and "\?sub?" use last used search pattern (almost like + * //sub/r). "\&sub&" use last substitute pattern (like //sub/). + */ + if (*cmd == '\\') + { + ++cmd; + if (vim_strchr((char_u *)"/?&", *cmd) == NULL) + { + emsg(e_backslash); + return; + } + if (*cmd != '&') + which_pat = RE_SEARCH; /* use last '/' pattern */ + pat = (char_u *)""; /* empty search pattern */ + delimiter = *cmd++; /* remember delimiter character */ + } + else /* find the end of the regexp */ + { + which_pat = RE_LAST; /* use last used regexp */ + delimiter = *cmd++; /* remember delimiter character */ + pat = cmd; /* remember start of search pat */ + cmd = skip_regexp(cmd, delimiter); + if (cmd[0] == delimiter) /* end delimiter found */ + *cmd++ = NUL; /* replace it with a NUL */ + } + + /* + * Small incompatibility: vi sees '\n' as end of the command, but in + * Vim we want to use '\n' to find/substitute a NUL. + */ + sub = cmd; /* remember the start of the substitution */ + + while (cmd[0]) + { + if (cmd[0] == delimiter) /* end delimiter found */ + { + *cmd++ = NUL; /* replace it with a NUL */ + break; + } + if (cmd[0] == '\\' && cmd[1] != 0) /* skip escaped characters */ + ++cmd; + ++cmd; + } + + vim_free(old_sub); + old_sub = strsave(sub); + } + else /* use previous pattern and substitution */ + { + if (old_sub == NULL) /* there is no previous command */ + { + emsg(e_nopresub); + return; + } + pat = NULL; /* myregcomp() will use previous pattern */ + sub = old_sub; + } + + /* + * find trailing options + */ + if (!p_ed) + { + if (p_gd) /* default is global on */ + do_all = TRUE; + else + do_all = FALSE; + do_ask = FALSE; + } + while (*cmd) + { + /* + * Note that 'g' and 'c' are always inverted, also when p_ed is off + * 'r' is never inverted. + */ + if (*cmd == 'g') + do_all = !do_all; + else if (*cmd == 'c') + do_ask = !do_ask; + else if (*cmd == 'r') /* use last used regexp */ + which_pat = RE_LAST; + else if (*cmd == 'p') + do_print = TRUE; + else + break; + ++cmd; + } + + /* + * check for a trailing count + */ + cmd = skipwhite(cmd); + if (isdigit(*cmd)) + { + i = getdigits(&cmd); + if (i <= 0) + { + emsg(e_zerocount); + return; + } + lp = up; + up += i - 1; + } + + /* + * check for trailing '|', '"' or '\n' + */ + cmd = skipwhite(cmd); + if (*cmd) + { + if (vim_strchr((char_u *)"|\"\n", *cmd) == NULL) + { + emsg(e_trailing); + return; + } + else + *nextcommand = cmd + 1; + } + + if ((prog = myregcomp(pat, RE_SUBST, which_pat, SEARCH_HIS)) == NULL) + { + emsg(e_invcmd); + return; + } + + /* + * ~ in the substitute pattern is replaced with the old pattern. + * We do it here once to avoid it to be replaced over and over again. + */ + sub = regtilde(sub, (int)p_magic); + + old_line = NULL; + for (lnum = lp; lnum <= up && !(got_int || got_quit); ++lnum) + { + ptr = ml_get(lnum); + if (vim_regexec(prog, ptr, TRUE)) /* a match on this line */ + { + char_u *new_end, *new_start = NULL; + char_u *old_match, *old_copy; + char_u *prev_old_match = NULL; + char_u *p1; + int did_sub = FALSE; + int match, lastone; + unsigned len, needed_len; + unsigned new_start_len = 0; + + /* make a copy of the line, so it won't be taken away when updating + the screen */ + if ((old_line = strsave(ptr)) == NULL) + continue; + vim_regexec(prog, old_line, TRUE); /* match again on this line to + * update the pointers. TODO: + * remove extra vim_regexec() */ + if (!got_match) + { + setpcmark(); + got_match = TRUE; + } + + old_copy = old_match = old_line; + for (;;) /* loop until nothing more to replace */ + { + /* + * Save the position of the last change for the final cursor + * position (just like the real vi). + */ + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = (int)(prog->startp[0] - old_line); + + /* + * Match empty string does not count, except for first match. + * This reproduces the strange vi behaviour. + * This also catches endless loops. + */ + if (old_match == prev_old_match && old_match == prog->endp[0]) + { + ++old_match; + goto skip; + } + old_match = prog->endp[0]; + prev_old_match = old_match; + + while (do_ask) /* loop until 'y', 'n', 'q', CTRL-E or CTRL-Y typed */ + { + temp = RedrawingDisabled; + RedrawingDisabled = FALSE; + comp_Botline(curwin); + search_match_len = prog->endp[0] - prog->startp[0]; + /* invert the matched string + * remove the inversion afterwards */ + if (search_match_len == 0) + search_match_len = 1; /* show something! */ + highlight_match = TRUE; + updateScreen(CURSUPD); + highlight_match = FALSE; + redraw_later(NOT_VALID); + /* same highlighting as for wait_return */ + (void)set_highlight('r'); + msg_highlight = TRUE; + smsg((char_u *)"replace with %s (y/n/a/q/^E/^Y)?", sub); + showruler(TRUE); + RedrawingDisabled = temp; + + ++no_mapping; /* don't map this key */ + i = vgetc(); + --no_mapping; + + /* clear the question */ + msg_didout = FALSE; /* don't scroll up */ + msg_col = 0; + gotocmdline(TRUE); + if (i == 'q' || i == ESC || i == Ctrl('C')) + { + got_quit = TRUE; + break; + } + else if (i == 'n') + goto skip; + else if (i == 'y') + break; + else if (i == 'a') + { + do_ask = FALSE; + break; + } + else if (i == Ctrl('E')) + scrollup_clamp(); + else if (i == Ctrl('Y')) + scrolldown_clamp(); + } + if (got_quit) + break; + + /* get length of substitution part */ + sublen = vim_regsub(prog, sub, old_line, FALSE, (int)p_magic); + if (new_start == NULL) + { + /* + * Get some space for a temporary buffer to do the + * substitution into (and some extra space to avoid + * too many calls to alloc()/free()). + */ + new_start_len = STRLEN(old_copy) + sublen + 25; + if ((new_start = alloc_check(new_start_len)) == NULL) + goto outofmem; + *new_start = NUL; + new_end = new_start; + } + else + { + /* + * Extend the temporary buffer to do the substitution into. + * Avoid an alloc()/free(), it takes a lot of time. + */ + len = STRLEN(new_start); + needed_len = len + STRLEN(old_copy) + sublen + 1; + if (needed_len > new_start_len) + { + needed_len += 20; /* get some extra */ + if ((p1 = alloc_check(needed_len)) == NULL) + goto outofmem; + STRCPY(p1, new_start); + vim_free(new_start); + new_start = p1; + new_start_len = needed_len; + } + new_end = new_start + len; + } + + /* + * copy the text up to the part that matched + */ + i = prog->startp[0] - old_copy; + vim_memmove(new_end, old_copy, (size_t)i); + new_end += i; + + vim_regsub(prog, sub, new_end, TRUE, (int)p_magic); + sub_nsubs++; + did_sub = TRUE; + + /* + * Now the trick is to replace CTRL-Ms with a real line break. + * This would make it impossible to insert CTRL-Ms in the text. + * That is the way vi works. In Vim the line break can be + * avoided by preceding the CTRL-M with a CTRL-V. Now you can't + * precede a line break with a CTRL-V, big deal. + */ + while ((p1 = vim_strchr(new_end, CR)) != NULL) + { + if (p1 == new_end || p1[-1] != Ctrl('V')) + { + if (u_inssub(lnum) == OK) /* prepare for undo */ + { + *p1 = NUL; /* truncate up to the CR */ + mark_adjust(lnum, MAXLNUM, 1L, 0L); + ml_append(lnum - 1, new_start, + (colnr_t)(p1 - new_start + 1), FALSE); + ++lnum; + ++up; /* number of lines increases */ + STRCPY(new_start, p1 + 1); /* copy the rest */ + new_end = new_start; + } + } + else /* remove CTRL-V */ + { + STRCPY(p1 - 1, p1); + new_end = p1; + } + } + + old_copy = prog->endp[0]; /* remember next character to be copied */ + /* + * continue searching after the match + * prevent endless loop with patterns that match empty strings, + * e.g. :s/$/pat/g or :s/[a-z]* /(&)/g + */ +skip: + match = -1; + lastone = (*old_match == NUL || got_int || got_quit || !do_all); + if (lastone || do_ask || + (match = vim_regexec(prog, old_match, (int)FALSE)) == 0) + { + if (new_start) + { + /* + * Copy the rest of the line, that didn't match. + * Old_match has to be adjusted, we use the end of the + * line as reference, because the substitute may have + * changed the number of characters. + */ + STRCAT(new_start, old_copy); + i = old_line + STRLEN(old_line) - old_match; + if (u_savesub(lnum) == OK) + ml_replace(lnum, new_start, TRUE); + + vim_free(old_line); /* free the temp buffer */ + old_line = new_start; + new_start = NULL; + old_match = old_line + STRLEN(old_line) - i; + if (old_match < old_line) /* safety check */ + { + EMSG("do_sub internal error: old_match < old_line"); + old_match = old_line; + } + old_copy = old_line; + } + if (match == -1 && !lastone) + match = vim_regexec(prog, old_match, (int)FALSE); + if (match <= 0) /* quit loop if there is no more match */ + break; + } + line_breakcheck(); + + } + if (did_sub) + ++sub_nlines; + vim_free(old_line); /* free the copy of the original line */ + old_line = NULL; + } + line_breakcheck(); + } + +outofmem: + vim_free(old_line); /* may have to free an allocated copy of the line */ + if (sub_nsubs) + { + CHANGED; + if (!global_busy) + { + updateScreen(CURSUPD); /* need this to update LineSizes */ + beginline(TRUE); + if (!do_sub_msg() && do_ask) + MSG(""); + } + if (do_print) + print_line(curwin->w_cursor.lnum, FALSE); + } + else if (!global_busy) + { + if (got_int) /* interrupted */ + emsg(e_interr); + else if (got_match) /* did find something but nothing substituted */ + MSG(""); + else /* nothing found */ + emsg(e_nomatch); + } + + vim_free(prog); +} + +/* + * Give message for number of substitutions. + * Can also be used after a ":global" command. + * Return TRUE if a message was given. + */ + static int +do_sub_msg() +{ + if (sub_nsubs > p_report) + { + sprintf((char *)msg_buf, "%s%ld substitution%s on %ld line%s", + got_int ? "(Interrupted) " : "", + sub_nsubs, plural(sub_nsubs), + (long)sub_nlines, plural((long)sub_nlines)); + if (msg(msg_buf)) + keep_msg = msg_buf; + return TRUE; + } + if (got_int) + { + emsg(e_interr); + return TRUE; + } + return FALSE; +} + +/* + * do_glob(cmd) + * + * Execute a global command of the form: + * + * g/pattern/X : execute X on all lines where pattern matches + * v/pattern/X : execute X on all lines where pattern does not match + * + * where 'X' is an EX command + * + * The command character (as well as the trailing slash) is optional, and + * is assumed to be 'p' if missing. + * + * This is implemented in two passes: first we scan the file for the pattern and + * set a mark for each line that (not) matches. secondly we execute the command + * for each line that has a mark. This is required because after deleting + * lines we do not know where to search for the next match. + */ + + void +do_glob(type, lp, up, cmd) + int type; + linenr_t lp, up; + char_u *cmd; +{ + linenr_t lnum; /* line number according to old situation */ + linenr_t old_lcount; /* curbuf->b_ml.ml_line_count before the command */ + int ndone; + + char_u delim; /* delimiter, normally '/' */ + char_u *pat; + regexp *prog; + int match; + int which_pat; + + if (global_busy) + { + EMSG("Cannot do :global recursive"); /* will increment global_busy */ + return; + } + + which_pat = RE_LAST; /* default: use last used regexp */ + sub_nsubs = 0; + sub_nlines = 0; + + /* + * undocumented vi feature: + * "\/" and "\?": use previous search pattern. + * "\&": use previous substitute pattern. + */ + if (*cmd == '\\') + { + ++cmd; + if (vim_strchr((char_u *)"/?&", *cmd) == NULL) + { + emsg(e_backslash); + return; + } + if (*cmd == '&') + which_pat = RE_SUBST; /* use previous substitute pattern */ + else + which_pat = RE_SEARCH; /* use previous search pattern */ + ++cmd; + pat = (char_u *)""; + } + else if (*cmd == NUL) + { + EMSG("Regular expression missing from global"); + return; + } + else + { + delim = *cmd; /* get the delimiter */ + if (delim) + ++cmd; /* skip delimiter if there is one */ + pat = cmd; /* remember start of pattern */ + cmd = skip_regexp(cmd, delim); + if (cmd[0] == delim) /* end delimiter found */ + *cmd++ = NUL; /* replace it with a NUL */ + } + + if ((prog = myregcomp(pat, RE_BOTH, which_pat, SEARCH_HIS)) == NULL) + { + emsg(e_invcmd); + return; + } + +/* + * pass 1: set marks for each (not) matching line + */ + ndone = 0; + for (lnum = lp; lnum <= up && !got_int; ++lnum) + { + /* a match on this line? */ + match = vim_regexec(prog, ml_get(lnum), (int)TRUE); + if ((type == 'g' && match) || (type == 'v' && !match)) + { + ml_setmarked(lnum); + ndone++; + } + line_breakcheck(); + } + +/* + * pass 2: execute the command for each line that has been marked + */ + if (got_int) + MSG("Interrupted"); + else if (ndone == 0) + msg(e_nomatch); + else + { + /* + * Set current position only once for a global command. + * If global_busy is set, setpcmark() will not do anything. + * If there is an error, global_busy will be incremented. + */ + setpcmark(); + + global_busy = 1; + old_lcount = curbuf->b_ml.ml_line_count; + while (!got_int && (lnum = ml_firstmarked()) != 0 && global_busy == 1) + { + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + if (*cmd == NUL || *cmd == '\n') + do_cmdline((char_u *)"p", FALSE, TRUE); + else + do_cmdline(cmd, FALSE, TRUE); + mch_breakcheck(); + } + + global_busy = 0; + + must_redraw = CLEAR; + cursupdate(); + + /* If subsitutes done, report number of substitues, otherwise report + * number of extra or deleted lines. */ + if (!do_sub_msg()) + msgmore(curbuf->b_ml.ml_line_count - old_lcount); + } + + ml_clearmarked(); /* clear rest of the marks */ + vim_free(prog); +} + +#ifdef VIMINFO + int +read_viminfo_sub_string(line, fp, force) + char_u *line; + FILE *fp; + int force; +{ + if (old_sub != NULL && force) + vim_free(old_sub); + if (force || old_sub == NULL) + { + viminfo_readstring(line); + old_sub = strsave(line + 1); + } + return vim_fgets(line, LSIZE, fp); +} + + void +write_viminfo_sub_string(fp) + FILE *fp; +{ + if (get_viminfo_parameter('/') != 0 && old_sub != NULL) + { + fprintf(fp, "\n# Last Substitute String:\n$"); + viminfo_writestring(fp, old_sub); + } +} +#endif /* VIMINFO */ diff --git a/usr.bin/vim/digraph.c b/usr.bin/vim/digraph.c new file mode 100644 index 00000000000..be759d9b91e --- /dev/null +++ b/usr.bin/vim/digraph.c @@ -0,0 +1,552 @@ +/* $OpenBSD: digraph.c,v 1.1.1.1 1996/09/07 21:40:26 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * digraph.c: code for digraphs + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +#ifdef DIGRAPHS + +static int getexactdigraph __ARGS((int, int, int)); +static void printdigraph __ARGS((char_u *)); + +static char_u (*digraphnew)[3]; /* pointer to added digraphs */ +static int digraphcount = 0; /* number of added digraphs */ + +#if defined(MSDOS) || defined(WIN32) || defined(OS2) +char_u digraphdefault[][3] = /* standard MSDOS digraphs */ + {{'C', ',', 128}, /* ~@ (SAS C can't handle the real char) */ + {'u', '"', 129}, /*  */ + {'e', '\'', 130}, /* ‚ */ + {'a', '^', 131}, /* ƒ */ + {'a', '"', 132}, /* „ */ + {'a', '`', 133}, /* … */ + {'a', '@', 134}, /* † */ + {'c', ',', 135}, /* ~G (SAS C can't handle the real char) */ + {'e', '^', 136}, /* ~H (SAS C can't handle the real char) */ + {'e', '"', 137}, /* ‰ */ + {'e', '`', 138}, /* Š */ + {'i', '"', 139}, /* ‹ */ + {'i', '^', 140}, /* Œ */ + {'i', '`', 141}, /*  */ + {'A', '"', 142}, /* Ž */ + {'A', '@', 143}, /*  */ + {'E', '\'', 144}, /*  */ + {'a', 'e', 145}, /* ‘ */ + {'A', 'E', 146}, /* ’ */ + {'o', '^', 147}, /* “ */ + {'o', '"', 148}, /* ” */ + {'o', '`', 149}, /* • */ + {'u', '^', 150}, /* – */ + {'u', '`', 151}, /* — */ + {'y', '"', 152}, /* ˜ */ + {'O', '"', 153}, /* ™ */ + {'U', '"', 154}, /* š */ + {'c', '|', 155}, /* › */ + {'$', '$', 156}, /* œ */ + {'Y', '-', 157}, /* ~] (SAS C can't handle the real char) */ + {'P', 't', 158}, /* ž */ + {'f', 'f', 159}, /* Ÿ */ + {'a', '\'', 160}, /*   */ + {'i', '\'', 161}, /* ¡ */ + {'o', '\'', 162}, /* ¢ */ + {'u', '\'', 163}, /* xx (SAS C can't handle the real char) */ + {'n', '~', 164}, /* ¤ */ + {'N', '~', 165}, /* ¥ */ + {'a', 'a', 166}, /* ¦ */ + {'o', 'o', 167}, /* § */ + {'~', '?', 168}, /* ¨ */ + {'-', 'a', 169}, /* © */ + {'a', '-', 170}, /* ª */ + {'1', '2', 171}, /* « */ + {'1', '4', 172}, /* ¬ */ + {'~', '!', 173}, /* ­ */ + {'<', '<', 174}, /* ® */ + {'>', '>', 175}, /* ¯ */ + + {'s', 's', 225}, /* á */ + {'j', 'u', 230}, /* æ */ + {'o', '/', 237}, /* í */ + {'+', '-', 241}, /* ñ */ + {'>', '=', 242}, /* ò */ + {'<', '=', 243}, /* ó */ + {':', '-', 246}, /* ö */ + {'~', '~', 247}, /* ÷ */ + {'~', 'o', 248}, /* ø */ + {'2', '2', 253}, /* ý */ + {NUL, NUL, NUL} + }; + +#else /* !MSDOS && !WIN32 */ +# ifdef MINT +char_u digraphdefault[][3] = /* standard ATARI digraphs */ + {{'C', ',', 128}, /* ~@ */ + {'u', '"', 129}, /*  */ + {'e', '\'', 130}, /* ‚ */ + {'a', '^', 131}, /* ƒ */ + {'a', '"', 132}, /* „ */ + {'a', '`', 133}, /* … */ + {'a', '@', 134}, /* † */ + {'c', ',', 135}, /* ~G */ + {'e', '^', 136}, /* ~H */ + {'e', '"', 137}, /* ‰ */ + {'e', '`', 138}, /* Š */ + {'i', '"', 139}, /* ‹ */ + {'i', '^', 140}, /* Œ */ + {'i', '`', 141}, /*  */ + {'A', '"', 142}, /* Ž */ + {'A', '@', 143}, /*  */ + {'E', '\'', 144}, /*  */ + {'a', 'e', 145}, /* ‘ */ + {'A', 'E', 146}, /* ’ */ + {'o', '^', 147}, /* “ */ + {'o', '"', 148}, /* ” */ + {'o', '`', 149}, /* • */ + {'u', '^', 150}, /* – */ + {'u', '`', 151}, /* — */ + {'y', '"', 152}, /* ˜ */ + {'O', '"', 153}, /* ™ */ + {'U', '"', 154}, /* š */ + {'c', '|', 155}, /* › */ + {'$', '$', 156}, /* œ */ + {'Y', '-', 157}, /* ~] */ + {'s', 's', 158}, /* ž */ + {'f', 'f', 159}, /* Ÿ */ + {'a', '\'', 160}, /*   */ + {'i', '\'', 161}, /* ¡ */ + {'o', '\'', 162}, /* ¢ */ + {'u', '\'', 163}, /* £ */ + {'n', '~', 164}, /* ¤ */ + {'N', '~', 165}, /* ¥ */ + {'a', 'a', 166}, /* ¦ */ + {'o', 'o', 167}, /* § */ + {'~', '?', 168}, /* ¨ */ + {'-', 'a', 169}, /* © */ + {'a', '-', 170}, /* ª */ + {'1', '2', 171}, /* « */ + {'1', '4', 172}, /* ¬ */ + {'~', '!', 173}, /* ­ */ + {'<', '<', 174}, /* ® */ + {'>', '>', 175}, /* ¯ */ + {'j', 'u', 230}, /* æ */ + {'o', '/', 237}, /* í */ + {'+', '-', 241}, /* ñ */ + {'>', '=', 242}, /* ò */ + {'<', '=', 243}, /* ó */ + {':', '-', 246}, /* ö */ + {'~', '~', 247}, /* ÷ */ + {'~', 'o', 248}, /* ø */ + {'2', '2', 253}, /* ý */ + {NUL, NUL, NUL} + }; + +# else /* !MINT */ +# ifdef _INCLUDE_HPUX_SOURCE + +char_u digraphdefault[][3] = /* default HPUX digraphs */ + {{'A', '`', 161}, /* ¡ */ + {'A', '^', 162}, /* ¢ */ + {'E', '`', 163}, /* £ */ + {'E', '^', 164}, /* ¤ */ + {'E', '"', 165}, /* ¥ */ + {'I', '^', 166}, /* ¦ */ + {'I', '"', 167}, /* § */ + {'\'', '\'', 168}, /* ¨ */ + {'`', '`', 169}, /* © */ + {'^', '^', 170}, /* ª */ + {'"', '"', 171}, /* « */ + {'~', '~', 172}, /* ¬ */ + {'U', '`', 173}, /* ­ */ + {'U', '^', 174}, /* ® */ + {'L', '=', 175}, /* ¯ */ + {'~', '_', 176}, /* ° */ + {'Y', '\'', 177}, /* ± */ + {'y', '\'', 178}, /* ² */ + {'~', 'o', 179}, /* ³ */ + {'C', ',', 180}, /* ´ */ + {'c', ',', 181}, /* µ */ + {'N', '~', 182}, /* ¶ */ + {'n', '~', 183}, /* · */ + {'~', '!', 184}, /* ¸ */ + {'~', '?', 185}, /* ¹ */ + {'o', 'x', 186}, /* º */ + {'L', '-', 187}, /* » */ + {'Y', '=', 188}, /* ¼ */ + {'p', 'p', 189}, /* ½ */ + {'f', 'l', 190}, /* ¾ */ + {'c', '|', 191}, /* ¿ */ + {'a', '^', 192}, /* À */ + {'e', '^', 193}, /* Á */ + {'o', '^', 194}, /*  */ + {'u', '^', 195}, /* à */ + {'a', '\'', 196}, /* Ä */ + {'e', '\'', 197}, /* Å */ + {'o', '\'', 198}, /* Æ */ + {'u', '\'', 199}, /* Ç */ + {'a', '`', 200}, /* È */ + {'e', '`', 201}, /* É */ + {'o', '`', 202}, /* Ê */ + {'u', '`', 203}, /* Ë */ + {'a', '"', 204}, /* Ì */ + {'e', '"', 205}, /* Í */ + {'o', '"', 206}, /* Î */ + {'u', '"', 207}, /* Ï */ + {'A', 'o', 208}, /* Ð */ + {'i', '^', 209}, /* Ñ */ + {'O', '/', 210}, /* Ò */ + {'A', 'E', 211}, /* Ó */ + {'a', 'o', 212}, /* Ô */ + {'i', '\'', 213}, /* Õ */ + {'o', '/', 214}, /* Ö */ + {'a', 'e', 215}, /* × */ + {'A', '"', 216}, /* Ø */ + {'i', '`', 217}, /* Ù */ + {'O', '"', 218}, /* Ú */ + {'U', '"', 219}, /* Û */ + {'E', '\'', 220}, /* Ü */ + {'i', '"', 221}, /* Ý */ + {'s', 's', 222}, /* Þ */ + {'O', '^', 223}, /* ß */ + {'A', '\'', 224}, /* à */ + {'A', '~', 225}, /* á */ + {'a', '~', 226}, /* â */ + {'D', '-', 227}, /* ã */ + {'d', '-', 228}, /* ä */ + {'I', '\'', 229}, /* å */ + {'I', '`', 230}, /* æ */ + {'O', '\'', 231}, /* ç */ + {'O', '`', 232}, /* è */ + {'O', '~', 233}, /* é */ + {'o', '~', 234}, /* ê */ + {'S', '~', 235}, /* ë */ + {'s', '~', 236}, /* ì */ + {'U', '\'', 237}, /* í */ + {'Y', '"', 238}, /* î */ + {'y', '"', 239}, /* ï */ + {'p', '-', 240}, /* ð */ + {'p', '~', 241}, /* ñ */ + {'~', '.', 242}, /* ò */ + {'j', 'u', 243}, /* ó */ + {'P', 'p', 244}, /* ô */ + {'3', '4', 245}, /* õ */ + {'-', '-', 246}, /* ö */ + {'1', '4', 247}, /* ÷ */ + {'1', '2', 248}, /* ø */ + {'a', '_', 249}, /* ù */ + {'o', '_', 250}, /* ú */ + {'<', '<', 251}, /* û */ + {'x', 'x', 252}, /* ü */ + {'>', '>', 253}, /* ý */ + {'+', '-', 254}, /* þ */ + {'n', 'u', 255}, /* (char excluded, is EOF on some systems */ + {NUL, NUL, NUL} + }; + +# else /* _INCLUDE_HPUX_SOURCE */ + +char_u digraphdefault[][3] = /* standard ISO digraphs */ + {{'~', '!', 161}, /* ¡ */ + {'c', '|', 162}, /* ¢ */ + {'$', '$', 163}, /* £ */ + {'o', 'x', 164}, /* ¤ */ + {'Y', '-', 165}, /* ¥ */ + {'|', '|', 166}, /* ¦ */ + {'p', 'a', 167}, /* § */ + {'"', '"', 168}, /* ¨ */ + {'c', 'O', 169}, /* © */ + {'a', '-', 170}, /* ª */ + {'<', '<', 171}, /* « */ + {'-', ',', 172}, /* ¬ */ + {'-', '-', 173}, /* ­ */ + {'r', 'O', 174}, /* ® */ + {'-', '=', 175}, /* ¯ */ + {'~', 'o', 176}, /* ° */ + {'+', '-', 177}, /* ± */ + {'2', '2', 178}, /* ² */ + {'3', '3', 179}, /* ³ */ + {'\'', '\'', 180}, /* ´ */ + {'j', 'u', 181}, /* µ */ + {'p', 'p', 182}, /* ¶ */ + {'~', '.', 183}, /* · */ + {',', ',', 184}, /* ¸ */ + {'1', '1', 185}, /* ¹ */ + {'o', '-', 186}, /* º */ + {'>', '>', 187}, /* » */ + {'1', '4', 188}, /* ¼ */ + {'1', '2', 189}, /* ½ */ + {'3', '4', 190}, /* ¾ */ + {'~', '?', 191}, /* ¿ */ + {'A', '`', 192}, /* À */ + {'A', '\'', 193}, /* Á */ + {'A', '^', 194}, /*  */ + {'A', '~', 195}, /* à */ + {'A', '"', 196}, /* Ä */ + {'A', '@', 197}, /* Å */ + {'A', 'E', 198}, /* Æ */ + {'C', ',', 199}, /* Ç */ + {'E', '`', 200}, /* È */ + {'E', '\'', 201}, /* É */ + {'E', '^', 202}, /* Ê */ + {'E', '"', 203}, /* Ë */ + {'I', '`', 204}, /* Ì */ + {'I', '\'', 205}, /* Í */ + {'I', '^', 206}, /* Î */ + {'I', '"', 207}, /* Ï */ + {'D', '-', 208}, /* Ð */ + {'N', '~', 209}, /* Ñ */ + {'O', '`', 210}, /* Ò */ + {'O', '\'', 211}, /* Ó */ + {'O', '^', 212}, /* Ô */ + {'O', '~', 213}, /* Õ */ + {'O', '"', 214}, /* Ö */ + {'/', '\\', 215}, /* × */ + {'O', '/', 216}, /* Ø */ + {'U', '`', 217}, /* Ù */ + {'U', '\'', 218}, /* Ú */ + {'U', '^', 219}, /* Û */ + {'U', '"', 220}, /* Ü */ + {'Y', '\'', 221}, /* Ý */ + {'I', 'p', 222}, /* Þ */ + {'s', 's', 223}, /* ß */ + {'a', '`', 224}, /* à */ + {'a', '\'', 225}, /* á */ + {'a', '^', 226}, /* â */ + {'a', '~', 227}, /* ã */ + {'a', '"', 228}, /* ä */ + {'a', '@', 229}, /* å */ + {'a', 'e', 230}, /* æ */ + {'c', ',', 231}, /* ç */ + {'e', '`', 232}, /* è */ + {'e', '\'', 233}, /* é */ + {'e', '^', 234}, /* ê */ + {'e', '"', 235}, /* ë */ + {'i', '`', 236}, /* ì */ + {'i', '\'', 237}, /* í */ + {'i', '^', 238}, /* î */ + {'i', '"', 239}, /* ï */ + {'d', '-', 240}, /* ð */ + {'n', '~', 241}, /* ñ */ + {'o', '`', 242}, /* ò */ + {'o', '\'', 243}, /* ó */ + {'o', '^', 244}, /* ô */ + {'o', '~', 245}, /* õ */ + {'o', '"', 246}, /* ö */ + {':', '-', 247}, /* ÷ */ + {'o', '/', 248}, /* ø */ + {'u', '`', 249}, /* ù */ + {'u', '\'', 250}, /* ú */ + {'u', '^', 251}, /* û */ + {'u', '"', 252}, /* ü */ + {'y', '\'', 253}, /* ý */ + {'i', 'p', 254}, /* þ */ + {'y', '"', 255}, /* (char excluded, is EOF on some systems */ + {NUL, NUL, NUL} + }; + +# endif /* _INCLUDE_HPUX_SOURCE */ +# endif /* !MINT */ +#endif /* !MSDOS && !WIN32 */ + +/* + * handle digraphs after typing a character + */ + int +do_digraph(c) + int c; +{ + static int backspaced; /* character before K_BS */ + static int lastchar; /* last typed character */ + + if (c == -1) /* init values */ + { + backspaced = -1; + } + else if (p_dg) + { + if (backspaced >= 0) + c = getdigraph(backspaced, c, FALSE); + backspaced = -1; + if ((c == K_BS || c == Ctrl('H')) && lastchar >= 0) + backspaced = lastchar; + } + lastchar = c; + return c; +} + +/* + * lookup the pair char1, char2 in the digraph tables + * if no match, return char2 + */ + static int +getexactdigraph(char1, char2, meta) + int char1; + int char2; + int meta; +{ + int i; + int retval; + + if (IS_SPECIAL(char1) || IS_SPECIAL(char2)) + return char2; + retval = 0; + for (i = 0; ; ++i) /* search added digraphs first */ + { + if (i == digraphcount) /* end of added table, search defaults */ + { + for (i = 0; digraphdefault[i][0] != 0; ++i) + if (digraphdefault[i][0] == char1 && digraphdefault[i][1] == char2) + { + retval = digraphdefault[i][2]; + break; + } + break; + } + if (digraphnew[i][0] == char1 && digraphnew[i][1] == char2) + { + retval = digraphnew[i][2]; + break; + } + } + + if (retval == 0) /* digraph deleted or not found */ + { + if (char1 == ' ' && meta) /* --> meta-char */ + return (char2 | 0x80); + return char2; + } + return retval; +} + +/* + * Get digraph. + * Allow for both char1-char2 and char2-char1 + */ + int +getdigraph(char1, char2, meta) + int char1; + int char2; + int meta; +{ + int retval; + + if (((retval = getexactdigraph(char1, char2, meta)) == char2) && + (char1 != char2) && + ((retval = getexactdigraph(char2, char1, meta)) == char1)) + return char2; + return retval; +} + +/* + * put the digraphs in the argument string in the digraph table + * format: {c1}{c2} char {c1}{c2} char ... + */ + void +putdigraph(str) + char_u *str; +{ + int char1, char2, n; + char_u (*newtab)[3]; + int i; + + while (*str) + { + str = skipwhite(str); + if ((char1 = *str++) == 0 || (char2 = *str++) == 0) + return; + if (char1 == ESC || char2 == ESC) + { + EMSG("Escape not allowed in digraph"); + return; + } + str = skipwhite(str); + if (!isdigit(*str)) + { + emsg(e_number); + return; + } + n = getdigits(&str); + if (digraphnew) /* search the table for existing entry */ + { + for (i = 0; i < digraphcount; ++i) + if (digraphnew[i][0] == char1 && digraphnew[i][1] == char2) + { + digraphnew[i][2] = n; + break; + } + if (i < digraphcount) + continue; + } + newtab = (char_u (*)[3])alloc(digraphcount * 3 + 3); + if (newtab) + { + vim_memmove((char *)newtab, (char *)digraphnew, + (size_t)(digraphcount * 3)); + vim_free(digraphnew); + digraphnew = newtab; + digraphnew[digraphcount][0] = char1; + digraphnew[digraphcount][1] = char2; + digraphnew[digraphcount][2] = n; + ++digraphcount; + } + } +} + + void +listdigraphs() +{ + int i; + + msg_outchar('\n'); + printdigraph(NULL); + for (i = 0; digraphdefault[i][0] && !got_int; ++i) + { + if (getexactdigraph(digraphdefault[i][0], digraphdefault[i][1], + FALSE) == digraphdefault[i][2]) + printdigraph(digraphdefault[i]); + mch_breakcheck(); + } + for (i = 0; i < digraphcount && !got_int; ++i) + { + printdigraph(digraphnew[i]); + mch_breakcheck(); + } + must_redraw = CLEAR; /* clear screen, because some digraphs may be wrong, + * in which case we messed up NextScreen */ +} + + static void +printdigraph(p) + char_u *p; +{ + char_u buf[9]; + static int len; + + if (p == NULL) + len = 0; + else if (p[2] != 0) + { + if (len > Columns - 11) + { + msg_outchar('\n'); + len = 0; + } + if (len) + MSG_OUTSTR(" "); + sprintf((char *)buf, "%c%c %c %3d", p[0], p[1], p[2], p[2]); + msg_outstr(buf); + len += 11; + } +} + +#endif /* DIGRAPHS */ diff --git a/usr.bin/vim/doc/Makefile b/usr.bin/vim/doc/Makefile new file mode 100644 index 00000000000..1d3cf5f9040 --- /dev/null +++ b/usr.bin/vim/doc/Makefile @@ -0,0 +1,18 @@ +# $OpenBSD: Makefile,v 1.1.1.1 1996/09/07 21:40:32 downsj Exp $ + +DOCS = vim_ami.txt vim_arch.txt vim_diff.txt vim_digr.txt vim_dos.txt \ + vim_help.txt vim_idx.txt vim_mac.txt vim_w32.txt vim_ref.txt \ + vim_tips.txt vim_unix.txt vim_win.txt vim_gui.txt vim_40.txt \ + vim_kcc.txt vim_rlh.txt vim_menu.txt vim_os2.txt vim_mint.txt + +all: vim_tags + +vim_tags: doctags $(DOCS) + ./doctags $(DOCS) | sort >vim_tags + uniq -d -2 vim_tags + +doctags: doctags.c + cc doctags.c -o doctags + +clean: + -rm doctags diff --git a/usr.bin/vim/doc/doctags.c b/usr.bin/vim/doc/doctags.c new file mode 100644 index 00000000000..5197252c303 --- /dev/null +++ b/usr.bin/vim/doc/doctags.c @@ -0,0 +1,67 @@ +/* $OpenBSD: doctags.c,v 1.1.1.1 1996/09/07 21:40:30 downsj Exp $ */ +/* vim:set ts=4 sw=4: + * this program makes a tags file for vim_ref.txt + * + * Usage: doctags vim_ref.txt vim_win.txt ... >tags + * + * A tag in this context is an identifier between stars, e.g. *c_files* + */ + +#include +#include +#include +#include + +#define LINELEN 200 + + int +main(argc, argv) + int argc; + char **argv; +{ + char line[LINELEN]; + char *p1, *p2; + char *p; + FILE *fd; + + if (argc <= 1) + { + fprintf(stderr, "Usage: doctags docfile ... >tags\n"); + exit(1); + } + printf("vim_tags\tvim_tags\t1\n"); + while (--argc > 0) + { + ++argv; + fd = fopen(argv[0], "r"); + if (fd == NULL) + { + fprintf(stderr, "Unable to open %s for reading\n", argv[0]); + continue; + } + while (fgets(line, LINELEN, fd) != NULL) + { + p1 = strchr(line, '*'); /* find first '*' */ + while (p1 != NULL) + { + p2 = strchr(p1 + 1, '*'); /* find second '*' */ + if (p2 != NULL) + { + for (p = p1 + 1; p < p2; ++p) + if (*p == ' ' || *p == '\t' || *p == '|') + break; + if (p == p2) /* if it is all valid + characters */ + { + *p2 = '\0'; + printf("%s\t%s\t/\\*%s\\*\n", p1 + 1, argv[0], p1 + 1); + p2 = strchr(p2 + 1, '*'); + } + } + p1 = p2; + } + } + fclose(fd); + } + return 0; +} diff --git a/usr.bin/vim/doc/vim.1 b/usr.bin/vim/doc/vim.1 new file mode 100644 index 00000000000..a4c366e0f8a --- /dev/null +++ b/usr.bin/vim/doc/vim.1 @@ -0,0 +1,304 @@ +.\" $OpenBSD: vim.1,v 1.1.1.1 1996/09/07 21:40:30 downsj Exp $ +.TH VIM 1 "1996 May 27" +.SH NAME +vim \- Vi IMproved, a programmers text editor +.SH SYNOPSIS +.B vim +[options] [file ..] +.br +.B vim +[options] \-t tag +.br +.B vim +[options] \-e [errorfile] +.SH DESCRIPTION +.B Vim +is a text editor that is upwards compatible to vi. +It can be used to edit any ASCII text. +It is especially useful for editing +programs. +.PP +There are a lot of enhancements above vi: multi level undo, +multi windows and buffers, command line +editing, filename completion, on-line help, visual selection, etc.. +Read vim_diff.txt for a summary of the differences between +.B Vim +and vi. +.PP +While running +.B Vim +a lot of help can be obtained from the on-line help system. +See ON-LINE HELP below. +.PP +Most often +.B Vim +is started to edit a single file with the command +.PP + vim file +.PP +More generally +.B Vim +is started with: +.PP + vim [options] [filelist] +.PP +If the filelist is missing, the editor will start with an empty buffer. +Otherwise exactly one out of the following three may be used to choose one or +more files to be edited. +.TP 12 +file .. +A list of file names. +The first one will be the current file and read into the buffer. +The cursor will be positioned on the first line of the buffer. +You can get to the other files with the ":next" command. +To edit a file that starts with a dash, precede the filelist with "--". +.TP +-t {tag} +The file to edit and the initial cursor position depends on a "tag", a sort +of goto label. +{tag} is looked up in the tags file, the associated file becomes the current +file and the associated command is executed. +Mostly this is used for C programs. +{tag} then should be a function name. +The effect is that the file containing that function becomes the current file +and the cursor is positioned on the start of the function. +See ":help tag_commands". +.TP +-e [errorfile] +Start in quickFix mode. +The file [errorfile] is read and the first error is displayed. +If [errorfile] is omitted the file name is obtained from the 'errorfile' +option (defaults to "AztecC.Err" for the Amiga, "errors.vim" on other +systems). +Further errors can be jumped to with the ":cn" command. +See ":help quickfix". +.SH OPTIONS +The options, if present, must precede the filelist. +The options may be given in any order. +Options can mostly be combined after a single dash. +.TP 12 ++[num] +For the first file the cursor will be positioned on line "num". +If "num" is missing, the cursor will be positioned on the last line. +.TP ++/{pat} +For the first file the cursor will be positioned on the +first occurrence of {pat}. +See ":help search_pattern" for the available search patterns. +.TP ++{command} +.TP +-c {command} +{command} will be executed after the +first file has been read. +{command} is interpreted as an Ex command. +If the {command} contains spaces it must be enclosed in double quotes (this +depends on the shell that is used). +Example: Vim "+set si" main.c +.br +Note: You can use only one "+" or "-c" argument. +.TP +-b +Binary mode. +A few options will be set that makes it possible to edit a binary or +executable file. +.TP +-d {device} +Open {device} for use as a terminal. +Only on the Amiga. +Example: +"\-d con:20/30/600/150". +.TP +-i {viminfo} +When using the viminfo file is enabled, this option sets the file name to use, +instead of the default "~/.viminfo". +This can also be used to skip the use of the .viminfo file, by giving the name +"NONE". +.TP +-g +If +.B Vim +has been compiled with GUI support, this option turns the GUI on. +If no GUI support was compiled in, an error message is given and +.B Vim +aborts. +.TP +-H +If +.B Vim +has been compiled with RIGHTLEFT support for editing right-to-left +oriented files and Hebrew keyboard mapping, this option starts +.B Vim +in Hebrew mode, i.e. hkmap and rightleft are set. +Otherwise an error message is given and +.B Vim +aborts. +.TP +-n +No swap file will be used. +Recovery after a crash will be impossible. +Handy if you want to edit a file on a very slow medium (e.g. floppy). +Can also be done with ":set uc=0". +Can be undone with ":set uc=200". +.TP +-o[N] +Open N windows. +When N is omitted, open one window for each file. +.TP +-r +List swap files, with information about using them for recovery. +.TP +-r {file} +Recovery mode. +The swap file is used to recover a crashed editing session. +The swap file is a file with the same file name as the text file with ".swp" +appended. +See ":help recovery". +.TP +-s {scriptin} +The script file {scriptin} is read. +The characters in the file are interpreted as if you had typed them. +The same can be done with the command ":source! {scriptin}". +If the end of the file is reached before the editor exits, further characters +are read from the keyboard. +.TP +-T {terminal} +Tells +.B Vim +the name of the terminal you are using. +Should be a terminal known +to +.B Vim +(builtin) or defined in the termcap or terminfo file. +.TP +-u {vimrc} +Use the commands in the file "vimrc" for initializations. +All the other initializations are skipped. +Use this to edit a special kind of files. +It can also be used to skip all initializations by giving the name "NONE". +See ":help initialization" within vim for more details. +.TP +-v +View mode. +The 'readonly' option will be set. +You can still edit the buffer, but will be prevented from accidently +overwriting a file. +If you do want to overwrite a file, add an exclamation mark to the Ex command, +as in ":w!". +The -v option also implies the -n option (see below). +The 'readonly' option can be reset with ":set noro". +See ":help 'readonly'". +.TP +-w {scriptout} +All the characters that you type are recorded in the file +{scriptout}, until you exit +.B Vim. +This is useful if you want to create a script file to be used with "vim -s" or +":source!". +If the {scriptout} file exists, characters are appended. +.TP +-W {scriptout} +Like -w, but an existing file is overwritten. +.TP +-x +(Amiga only) +.B Vim +is not restarted to open a new window. +This option should be used when +.B Vim +is executed by a program that will wait for the edit +session to finish (e.g. mail). +The ":sh" and ":!" commands will not work. +.TP +-- +Denotes the end of the options, arguments after this will be handled as a file +name. +This can be used to edit a file name that starts with a '-'. +.SH ON-LINE HELP +Type ":help" in +.B Vim +to get started. +Type ":help subject" to get help on a specific subject. +For example: ":help ZZ" to get help for the "ZZ" command. +Use and CTRL-D to complete subjects (":help cmdline_completion"). +Tags are present to jump from one place to another (sort of hypertext links, +see ":help"). +All files mentioned below can be viewed in this way, for example +":help vim_ref.txt". +.SH FILES +Vim documentation (in /usr/share/vim). +.TP 15 +vim_ref.txt +A complete reference of +.B Vim +(long). +.TP +vim_help.txt +File used by the on-line help (short), contains links to all other files. +.TP +vim_win.txt +Explanation of the multi windows and buffers commands and options. +.TP +vim_idx.txt +Overview of all command characters (useful when adding new mappings). +.TP +vim_tips.txt +Some useful tips about using +.B Vim +for specific file types. +.TP +vim_digr.txt +Overview of the available digraphs. +.TP +vim_kcc.txt +About copying +.B Vim +and Uganda. +.TP +vim_diff.txt +Overview of the differences between +.B Vim +and vi. +.TP +vim_.txt +Machine specific comments. + can be ami (Amiga), arch (Archimedes), unix, w32 (Windows 95/NT), +dos (MS-DOS), mint (Atari MiNT) and mac (Macintosh). +.TP +vim_gui.txt +Specific comments about the GUI version. +.TP +vim_rlh.txt +Information about using +.B Vim +for editing right-to-left oriented files. +.TP +/usr/local/lib/vim/vimrc +System wide +.B Vim +initializations +.TP +/usr/local/lib/vim/gvimrc +System wide gvim initializations +.PP +For recent info read the VIM home page: +.br + +.SH AUTHOR +Most of +.B Vim +was made by Bram Moolenaar. +.br +.B Vim +is based on Stevie, worked on by: Tim Thompson, +Tony Andrews and G.R. (Fred) Walter +.SH BUGS +Probably. +See the "todo" file that comes with the distribution. +.PP +Note that a number of things that may be regarded as bugs by some, are in fact +caused by a too-faithful reproduction of vi's behaviour. +Other people may think other things are bugs "because vi does it differently". +Those people should take a closer look at the vim_diff.txt file (or type :help +vim_diff.txt when in Vim). +Also have a look at the 'compatible' option. diff --git a/usr.bin/vim/doc/vim_40.txt b/usr.bin/vim/doc/vim_40.txt new file mode 100644 index 00000000000..720430400f1 --- /dev/null +++ b/usr.bin/vim/doc/vim_40.txt @@ -0,0 +1,2341 @@ +*vim_40.txt* For Vim version 4.2. Last modification: 1996 June 16 + + +Welcome to Vim Version 4.0! + +This document lists the differences between Vim 3.0 and Vim 4.0. +See |vim_diff.txt| for a short overview. +Although 4.0 is mentioned here, this is also for version 4.1, 4.2, etc.. + +CONTENTS: + +VERSION WARNING MESSAGE |version_warning| + +INCOMPATIBLE CHANGES |Incompatible_changes| + 'backup' option default changed |backup_changed| + Extension for backup file changed |backup_extension| + Structure of swap file changed |swapfile_changed| + "-w scriptout" argument changed |scriptout_changed| + Backspace and Delete keys |backspace_delete| + Escape for | changed |escape_bar| + Key codes changed |key_codes_changed| + Terminal options changed |termcap_changed| + 'errorformat' option changed |errorformat_changed| + 'graphic' option gone |graphic_option_gone| + 'yankendofline' option gone |ye_option_gone| + 'icon' and 'title' default value changed |icon_changed| + 'highlight' option changed |highlight_changed| + 'tildeop' and 'weirdinvert' short names changed |short_name_changed| + Use of "v", "V" and "CTRL-V" in Visual mode |use_visual_cmds| + CTRL-B in Insert mode removed |toggle_revins| + +NEW AND IMPROVED FEATURES |new_features| + New on-line help system |new_help| + Tag support improved |new_tags| + Command-line editing improvements |new_commandline| + Improved indenting for C programs |new_cindent| + Searching for words in include files |new_include| + Word completion in Insert mode |new_complete| + Automatic commands |new_autocmd| + Options |new_options| + Support for editing one-line paragraphs |new_para| + Usage of key names |new_keys| + Viminfo |new_viminfo| + Compilation improvements |compilation| + Improved (error) messages |new_msg| + Swap file |new_swapfile| + Mouse support |new_mouse| + Graphical User Interface (GUI) |new_gui| + Support for Windows NT and Windows 95 |new_win32| + Support for OS/2 |new_os2| + Support for MiNT |new_mint| + Miscellaneous |new_misc| + +VI COMPATIBILITY IMPROVEMENTS |vi_compat| + +BUG FIXES |bug_fixes| + + +VERSION WARNING MESSAGE *version* +======================= *version_warning* + +If you got the message + + No ":version 4.0" command found in any .vimrc + +when you started Vim, you should add this line to your vimrc: + + version 4.0 |:version| + +But read the information below first! + + +INCOMPATIBLE CHANGES *Incompatible_changes* +==================== + +This section is important for everybody upgrading from 3.0 to 4.0. Read it +carefully to avoid unexpected problems. + + +'backup' option default changed *backup_changed* +------------------------------- + +The default value for 'backup' used to be on. This resulted in a backup file +being made when the original file was overwritten. + +Now the default for 'backup' is off. As soon as the writing of the file has +succesfully finished, the backup file is deleted. If you want to keep the +backup file, set 'backup' on in your vimrc. The reason for this change is +that many people complained that leaving a backup file behind is not +Vi-compatible. |'backup'| + + +Extension for backup file changed *backup_extension* +--------------------------------- + +The extension for the backup file used to be ".bak". Since other programs +also use this extension and some users make copies with this extension, it was +changed to the less obvious "~". Another advantage is that this takes less +space, which is useful when working on a system with short file names. For +example, on MS-DOS the backup files for "longfile.c" and "longfile.h" would +both become "longfile.bak"; now they will be "longfile.c~" and "longfile.h~". + +If you prefer to use ".bak", you can set the 'backupext' option: + :set bex=.bak |'backupext'| + + +Structure of swap file changed *swapfile_changed* +------------------------------ + +The contents of the swap file were extended with several parameters. Vim +stores the user name and other information about the edited file to make +recovery more easy and to be able to know where the swap file comes from. The +first part of the swap file can now be understood on a machine with a +different byte order or sizeof(int). When you try to recover a file on such a +machine, you will get an error message that this is not possible. + +Because of this change, swap files cannot be exchanged between 3.0 and 4.0. +If you have a swap file from a crashed session with 3.0, use Vim 3.0 to +recover the file---don't use 4.0. |swap_file| + + +"-w scriptout" argument changed *scriptout_changed* +------------------------------- + +"vim -w scriptout" used to append to the scriptout file. Since this was +illogical, it now creates a new file. An existing file is not overwritten +(to avoid destroying an existing file for those who rely on the appending). + |-w| + + +Backspace and Delete keys *backspace_delete* +------------------------- + +In 3.0 both the delete key and the backspace key worked as a backspace in +insert mode; they deleted the character to the left of the cursor. In 4.0 the +delete key has a new function: it deletes the character under the cursor, just +like it does on the command line. If the cursor is after the end of the line +and 'bs' is set, two lines are joined. || |i_| + +In 3.0 the backspace key was always defined as CTRL-H and delete as CTRL-?. +In 4.0 the code for the backspace and delete key is obtained from termcap or +termlib, and adjusted for the "stty erase" value on Unix. This helps people +who define the erase character according to the keyboard they are working on. + || |i_| + +If you prefer backspace and delete in Insert mode to have the old behaviour, +put this line in your vimrc: + + inoremap ^? ^H + +And you may also want to add these, to fix the values for and : + + set t_kb=^H + set t_kD=^? + +(Enter ^H with CTRL-V CTRL-H and ^? with CTRL-V CTRL-? or .) + +If the value for t_kb is correct, but the t_kD value is not, use the ":fixdel" +command. It will set t_kD according to the value of t_kb. This is useful if +you are using several different terminals. |:fixdel| + +When ^H is not recognized as or , it is used like a backspace. + + +Escape for | changed *escape_bar* +-------------------- + +When the 'b' flag is present in 'cpoptions', the backslash cannot be used to +escape '|' in mapping and abbreviate commands, only CTRL-V can. This is +Vi-compatible. If you work in Vi-compatible mode and had used "\|" to include +a bar in a mapping, this needs to be replaced by "^V|". See |:bar|. + + +Key codes changed *key_codes_changed* +----------------- + +The internal representation of key codes has changed dramatically. In 3.0 a +one-byte code was used to represent a key. This caused problems with +different characters sets that also used these codes. In 4.0 a three-byte +code is used that cannot be confused with a character. |key_notation| + +If you have used the single-byte key codes in your vimrc for mappings, you +will have to replace them with the 4.0 codes. Instead of using the three-byte +code directly, you should use the symbolic representation for this in <>. See +the table below. The table also lists the old name, as it was used in the 3.0 +documentation. + +The key names in <> can be used in mappings directly. This makes it possible +to copy/paste examples or type them literally. The <> notation has been +introduced for this |<>|. The 'B' and '<' flags must not be present in +'cpoptions' to enable this to work |'cpoptions'|. + +old name new name old code old MS-DOS code + hex dec hex dec + + + + + + + + + + + + + + + 0x80 128 0xb0 176 + 0x81 129 0xb1 177 + 0x82 130 0xb2 178 + 0x83 131 0xb3 179 + 0x84 132 0xb4 180 + 0x85 133 0xb5 181 + 0x86 134 0xb6 182 + 0x87 135 0xb7 183 + + 0x88 136 0xb8 184 + 0x89 137 0xb9 185 + 0x8a 138 0xba 186 + 0x8b 139 0xbb 187 + 0x8c 140 0xbc 188 + 0x8d 141 0xbd 189 + 0x8e 142 0xbe 190 + 0x8f 143 0xbf 191 + 0x90 144 0xc0 192 + 0x91 145 0xc1 193 + + 0x92 146 0xc2 194 + 0x93 147 0xc3 195 + 0x94 148 0xc4 196 + 0x95 149 0xc5 197 + 0x96 150 0xc6 198 + 0x97 151 0xc7 199 + 0x98 152 0xc8 200 + 0x99 153 0xc9 201 + 0x9a 154 0xca 202 + 0x9b 155 0xcb 203 + + 0x9c 156 0xcc 204 + 0x9d 157 0xcd 205 + + (not used) 0x9e 158 0xce 206 + (not used) 0x9f 159 0xcf 207 + + +Terminal options changed *termcap_changed* +------------------------ + +The names of the terminal options have been changed to match the termcap names +of these options. All terminal options now have the name t_xx, where xx is +the termcap name. Normally these options are not used, unless you have a +termcap entry that is wrong or incomplete, or you have set the highlight +options to a different value. |terminal_options| + +Note that for some keys there is no termcap name. Use the <> type of name +instead, which is a good idea anyway. + +Note that "t_ti" has become "t_mr" (invert/reverse output) and "t_ts" has +become "t_ti" (init terminal mode). Be careful when you use "t_ti"! + +old name new name meaning +t_cdl t_DL delete number of lines *t_cdl* +t_ci t_vi cursor invisible *t_ci* +t_cil t_AL insert number of lines *t_cil* +t_cm t_cm move cursor +t_cri t_RI cursor number of chars right *t_cri* +t_cv t_ve cursor visible *t_cv* +t_cvv t_vs cursor very visible *t_cvv* +t_dl t_dl delete line +t_cs t_cs scroll region +t_ed t_cl clear display *t_ed* +t_el t_ce clear line *t_el* +t_il t_al insert line *t_il* + t_da display may be retained above the screen + t_db display may be retained below the screen +t_ke t_ke put terminal out of keypad transmit mode +t_ks t_ks put terminal in keypad transmit mode +t_ms t_ms save to move cursor in highlight mode +t_se t_se normal mode (undo t_so) +t_so t_so shift out (standout) mode +t_ti t_mr reverse highlight +t_tb t_md bold mode *t_tb* +t_tp t_me highlight end *t_tp* +t_sr t_sr scroll reverse +t_te t_te out of termcap mode +t_ts t_ti into termcap mode *t_ts* +t_vb t_vb visual bell +t_csc t_CS cursor is relative to scroll region *t_csc* + +t_ku t_ku arrow up +t_kd t_kd arrow down +t_kr t_kr arrow right +t_kl t_kl arrow left +t_sku shifted arrow up *t_sku* +t_skd shifted arrow down *t_skd* +t_skr t_%i shifted arrow right *t_skr* +t_skl t_#4 shifted arrow left *t_skl* +t_f1 t_k1 function key 1 *t_f1* +t_f2 t_k2 function key 2 *t_f2* +t_f3 t_k3 function key 3 *t_f3* +t_f4 t_k4 function key 4 *t_f4* +t_f5 t_k5 function key 5 *t_f5* +t_f6 t_k6 function key 6 *t_f6* +t_f7 t_k7 function key 7 *t_f7* +t_f8 t_k8 function key 8 *t_f8* +t_f9 t_k9 function key 9 *t_f9* +t_f10 t_k; function key 10 *t_f10* +t_sf1 shifted function key 1 *t_sf1* +t_sf2 shifted function key 2 *t_sf2* +t_sf3 shifted function key 3 *t_sf3* +t_sf4 shifted function key 4 *t_sf4* +t_sf5 shifted function key 5 *t_sf5* +t_sf6 shifted function key 6 *t_sf6* +t_sf7 shifted function key 7 *t_sf7* +t_sf8 shifted function key 8 *t_sf8* +t_sf9 shifted function key 9 *t_sf9* +t_sf10 shifted function key 10 *t_sf10* +t_help t_%1 help key *t_help* +t_undo t_&8 undo key *t_undo* + + +'errorformat' option changed *errorformat_changed* +---------------------------- + +'errorformat' can now contain several formats, separated by commas. The first +format that matches is used. The default values have been adjusted to catch +the most common formats. |errorformat| + +If you have a format that contains a comma, it needs to be preceded with a +backslash. Type two backslashes, because the ":set" command will eat one. + + +'graphic' option gone *graphic_option_gone* +--------------------- + +The 'graphic' option was used to make the characters between <~> and 0xa0 +display directly on the screen. Now the 'isprint' option takes care of this +with many more possibilities. The default setting is the same; you only need +to look into this if you previously set the 'graphic' option in your vimrc. + |'isprint'| + + +'yankendofline' option gone *ye_option_gone* +--------------------------- + +The 'yankendofline' option has been removed. Instead you can just use + :map Y y$ + + +'icon' and 'title' default value changed *icon_changed* +---------------------------------------- + +The 'title' option is now only set by default if the original title can be +restored. Avoids 'Thanks for flying Vim" titles. If you want them anyway, +put ":set title" in your vimrc. |'title'| + +The default for 'icon' now depends on the possibility of restoring the +original value, just like 'title'. If you don't like your icon titles to be +changed, add this line to your vimrc: |'icon'| + :set noicon + + +'highlight' option changed *highlight_changed* +-------------------------- + +The 'i' flag now means italic highlighting, instead of invert. The 'r' flag +is used for reverse highlighting, which is what 'i' used to be. Normally you +won't see the difference, because italic mode is not supported on most +terminals and reverse mode is used as a fallback. |'highlight'| + +When an occasion is not present in 'highlight', use the mode from the default +value for 'highlight', instead of reverse mode. + + +'tildeop' and 'weirdinvert' short names changed *short_name_changed* +----------------------------------------------- + +Renamed 'to' (abbreviation for 'tildeop') to 'top'. |'tildeop'| +Renamed 'wi' (abbreviation for 'weirdinvert') to 'wiv'. |'weirdinvert'| + +This was done because Vi uses 'wi' as the short name for 'window' and 'to' as +the short name for 'timeout'. This means that if you try setting these +options, you won't get an error message, but the effect will be different. + + +Use of "v", "V" and "CTRL-V" in Visual mode *use_visual_cmds* +------------------------------------------- + +In Visual mode, "v", "V", and "CTRL-V" used to end Visual mode. Now this +happens only if the Visual mode was in the corresponding type. Otherwise the +type of Visual mode is changed. Now only ESC can be used in all circumstances +to end Visual mode without doing anything. |v_V| + + +CTRL-B in Insert mode removed *toggle_revins* +----------------------------- + +CTRL-B in Insert mode used to toggle the 'revins' option. If you don't know +this and accidentally hit CTRL-B, it is very difficult to find out how to undo +it. Since hardly anybody uses this feature, it is disabled by default. If +you want to use it, define RIGHTLEFT in feature.h before compiling. |'revins'| + + +NEW AND IMPROVED FEATURES *new_features* +========================= + +New on-line help system *new_help* +----------------------- + +Help is displayed in a window. The usual commands can be used to move around, +search for a string, etc. |online_help| + +All the documentation is in the help system: index, reference, etc. + +Tags can be used to jump around, just like hypertext links. + +The ":help" command accepts an argument that can be the name of a command. A +help window is opened and the cursor is positioned at the help for that +command. The argument is looked up in the help tags file, using a fuzzy match +algorithm. The best match is used. |:help|. + +Added 'helpheight' option: Minimal height of a new help window. + +Made the default help key for all systems. + +Display "[help]" in the status line of a help window. + +Help files are always started in readonly mode. + + +Tag support improved *new_tags* +-------------------- + +Added support for static tags, "file:tag ...". See |static_tag|. + +Use the tag with best match: First tag in current file, then global tag in +other file, finally static tag in other file. Match with same case always +goes before match with case ignored. + +A few attempts are made to find a tag. See |tag_priority|. + +Added ":stag", same as ":tag", but also split window. See |:stag|. + +When deleting/inserting lines, the marks in the tag stack are adjusted to +reflect this change. + +Command-line completion for tags is improved. + +Tags are allowed to start with a number. + +For file names that start with "./" in the 'tags' option, the '.' is replaced +with the path of the current file. This makes it possible to use a tags file +in the same directory as the file being edited. + +When comparing the current file name with the file name for the tag, consider +the path too, otherwise tag in wrong directory could be used. + +Added support for Emacs style tags file (if "EMACS_TAGS" has been defined at +compile time). Now Vim is one of the few editors that supports both tag file +formats! + +When executing the search command to find the pattern from a tags file, the +first try is with matching case; if this fails, try again ignoring case. + +The 't' flag in 'cpoptions' can be set to remember the search pattern used for +tag search. A following "n" will use it. This is Vi-compatible, but it is +rarely useful. + +Added CTRL-] command to Visual mode: :ta to highlighted text (only when no +more than one line is highlighted). Same for "K": Use keyword program for +highlighted text. |v_CTRL-]| + + +Command-line editing improvements *new_commandline* +--------------------------------- + +There is no longer one command-line history. The search strings are now in a +separate list, where they can be accessed only when entering a search string. + |cmdline_history| + +The search string for a tag search is always put in the search history; this +does not depend on the 't' flag in 'cpoptions'. + +Only command lines and search strings where at least one character was really +typed by the user are put into the history. + +For command-line history, exchanged meaning of and keys, +and keys. That is more like other uses of command-line history +(shell) and some keyboards don't have shifted cursor and page keys, which +makes it impossible for this feature to be used. |c_| + +Put all search strings, including those from "*" and "#" commands, in the +search history. + +Added completion of old settings. For example: ":set hf=". Makes it +possible to change an option without completely retyping it. Backslashes are +inserted where necessary. + +Fixed command-line completion when entering the range of lines. + +When completing file names, insert a backslash before , '\', '#' and +'%' to avoid their special meaning. + +When a command-line is already in the history, the old entry is removed. When +searching through the command history, the entry from which it was started is +remembered. + +Added paging for showing matches on the command line. This can also be +interrupted. + +Added CTRL-R command on command-line: Insert contents of register. +Output '"' after CTRL-R command, to indicate that a register name has to be +entered. |c_CTRL-R| + + +Improved indenting for C programs *new_cindent* +--------------------------------- + +The 'cindent' option and friends have been added. It automates the indenting +for C programs. It is not 100% correct in all cases, but good enough for +normal editing. |'cindent'| + +The "=" operator can be used to indent some lines of text with the internal +indenting algorithm; it is used when 'equalprg' is empty, which is the default +now (most implementations of the "indent" program can't work as a filter +anyway). |=| + +Added automatic formatting of comments and the 'comments' option. + |format_comments| + +The 'formatoptions' option can be used to change when formatting is done and +to select the formatting method. + +The 'smartindent' option is still there; it's useful for non-C code and C code +that doesn't conform to what is supported by 'cindent'. + +Improvements for 'smartindent': |'smartindent'| +- When entering '{', indent is not rounded to 'shiftwidth'. +- When entering '}' and the matching '{' is preceded with a '(', set indent to + the line containing the matching ')' +- Ignore lines starting with '#'. +- When typing '{' after "O", delete one indent, unless indent of previous line + is less or equal. +- Don't add an indent when inserting a NL before a '{'. +- Do smart indenting after "cc" or "S". + +Fixed bug where } would only be smart-indented to line up with the line +containing the { when there was some spacing before the }. + +The 'cinwords' option can be set to keywords that start an extra indent for +'smartindent' and 'cindent' mode. This used to be a table in the C code; now +it is configurable. The defaults are the same, and it should make no +difference unless you change the 'cinw' option. |'cinwords'| + + +Searching for words in include files *new_include* +------------------------------------ + +Commands have been added that not only search in the current file, but also in +included files. The 'include' option can be set to a pattern that includes a +file (the default is for C programs). |include_search| + +The search can be done for identifiers with "[i" or defines with "[d". Use +":checkpath" to check if included files can be found. |:checkpath| + +A jump to the first found match can be done with "[ CTRL-I" or "[ CTRL-D". +This is very useful to jump to the definition of a variable. |[_CTRL-I| + +Added ":isearch", ":ilist", ":ijump", ":isplit", ":dsearch, etc. Allows for +an identifier to be found that is not in the text yet. |:isearch| + + +Word completion in Insert mode *new_complete* +------------------------------ + +In Insert mode the word before the cursor can be completed with CTRL-N and +CTRL-P. There used to be mappings for this, but the internal implementation +has better support for errors, ignores duplicates, etc. |compl_current| + +CTRL-X mode has been added. It is a sub-mode in Insert mode, where commands +can be used to scroll the text up or down, |i_CTRL-X_CTRL-E|, and completion +can be done is several ways. |ins_completion| + +Completion can be done from a dictionary, defined with 'dictionary'. This is +very useful when typing long words. |i_CTRL-X_CTRL-K| + +In C programs completion can be done from included files. This is very useful +when typing long function or structure names, e.g., for X windows. + |i_CTRL-X_CTRL-I| + +There is also completion for whole lines |i_CTRL-X_CTRL-L|, tags +|i_CTRL-X_CTRL-]|, file names |i_CTRL-X_CTRL-F|, and defines |i_CTRL-X_CTRL-D|. + +Added 'infercase' option. When doing keyword completion in Insert mode, and +'ignorecase' is also on, the case of the match is adjusted. |ins_completion| + + +Automatic commands *new_autocmd* +------------------ + +On certain events a set of commands can be executed, depending on the file +name. Supported events are starting to edit a file, moving to another buffer, +moving to another window, etc. This can be used to set 'cindent' on for +C-code files only, set 'dictionary' depending on the type of file, set +mappings for certain file types, edit compressed files, etc. |autocommand|. + +Added ":normal[!] {command}", execute Normal mode command from cmdline. +Useful for autocommands. Use with care! |:normal| + + +Text objects *new_textobj* +------------ + +After an operator and in Visual mode, text object commands can be used to +select a word, line, or paragraph. |object_select|. +a word object +A WORD object +s sentence object +p paragraph object +S object between '(' and ')' +P object between '{' and '}' + +A few examples: +"da" delete the word under cursor +"vPPP" select blocks +"yP" yank the current block +">P" increase the indent for the current block + +The space after or before the object is included. + + +Options *new_options* +------- + +Allow white space between option name and following character. ":set ai ?" +works now. + +Added '&' after option: reset to default value. Added '!' after boolean +option: invert value. |set_option| + +For setting numeric options, hex and octal can be used. + +When setting an option that is a list of flags, give an error messages for +illegal characters. Also for 'highlight' option. + +Environment variables in options are now replaced at any position, not just at +the start. |:set_env| + +For string options, a Tab ends the string; this is Vi-compatible. When +setting a hidden string option, skip backslashed blanks. When expanding the +value of a string option, insert a backslash before a Tab. Updated makeset() +and putescstr() for putting a backslash before the Tab. |:set| + +Names in 'tags', 'path', 'dictionary', and 'suffixes' options can also be +separated with commas. Commas in environment variables and file names are no +longer allowed. A comma can be included in these options by preceding it with +a backslash. Spaces after a comma are ignored. |'tags'| + +Added the 'shortmess' option. It is a list of flags that can be used to +shorten the messages that are given when reading or writing a file, avoiding +the "Hit return to continue" prompt. +When 'terse' is set, 's' flag is added to 'shortmess'. When 'terse' is reset, +'s' flag is removed from 'shortmess'. |'shortmess'| + +Added 'scrolloff' option: Make that many lines of context visible around the +cursor. If "so=999", the cursor line is always in the middle of the window. + |'scrolloff'| + +Added 'ttybuiltin' option: try builtin termcaps before external ones; default: +on. When a terminal is known both in the builtin termcap and in the external +one, use entries from both. Which entry is used first depends on +'ttybuiltin'. |'ttybuiltin'| + +Added 'ttimeoutlen' option: Like 'timeoutlen' but for key codes only. When +set to negative number (which is the default), 'timeoutlen' is used. + |'ttimeoutlen'| + +Implemented incremental search; use 'incsearch' option to switch it on. + |'incsearch'| + +Added options for adjusting character set and characters in identifiers: +'isident' - Characters in identifiers |'isident'| +'isprint' - Characters that can be displayed directly |'isprint'| +'iskeyword' - Characters that are in (key)words |'iskeyword'| +'isfname' - Characters that can be in a file name |'isfname'| + +Increased default for 'undolevels' for Unix and Win32 from 100 to 1000. + |'undolevels'| + +Changed 'directory' and 'backupdir' into a list of paths. '.' is the same +directory as the file. 'backupdir' now also works for non-Unix systems. No +need for weird ">" character to avoid current dir. If ">" has been given +anyway, it is automatically removed (for backwards compatibility with 3.0). +Made default for 'directory' ".,~/tmp,/tmp". |'directory'| +Changed default for 'backupdir'. Used to be ".", now it's ".,~" for Unix; +likewise for other systems. Helps when current directory is not writable (but +the file is) and 'writebackup' is set. |'backupdir'| + + +Changed default for 'keywordprg' to "man" (except on MS-DOS or Win32); much +more useful for most of us. When 'keywordprg' is empty, ":help" is used, get +help for word under the cursor. |'keywordprg'| + +Added the 'startofline' option. When off, a lot of commands that move the +cursor up or down don't put the cursor on the first non-blank on the line. +Example: This makes CTRL-F followed by CTRL-B keep the cursor in the same +column. |'startofline'| + +Added options 'flash' and 'novice' for Vi compatibility; they are not used. + |'flash'| |'novice'| + +Accept special key sequence, e.g., , when setting 'wildchar'. +Give error message when setting 'wildchar' option to non-number. + |'wildchar'| + +Added types to 'highlight' option: |'highlight'| + m for -- more -- message + t for titles + n for the line number that is shown with the ":number" command. + 8 for meta keys + w for "search hit TOP .." messages + M for mode message (e.g., "-- INSERT --"). + +When filtering, use the 'shellredir' option to redirect the output. When +possible, 'shellredir' is initialized to include stderr. |'shellredir'| + +Moved initialization of 'shellpipe' and 'shellredir' to after other +initializations, so that they work correctly when 'shell' option is set. + |'shellpipe'| + +Added '2' to 'formatoptions': 'Q' will keep the indent of the second line of a +paragraph. |'formatoptions'| + +Added 'maxmapdepth' option: maximum recursiveness of mapping. + |'maxmapdepth'| + +Added 'smartcase' option: Don't ignore case if a typed search pattern contains +uppercase characters. |'smartcase'| + +Added 'langmap' option, for those who put their keyboard in language mode, +e.g., Greek. Only if HAVE_LANGMAP defined at compile time. |'langmap'| + +Changed default for 'errorfile' option from "errors" to "errors.vim", to +reduce the risk of accidently overwriting an existing file. |'errorfile'| + +Changed 'whichwrap' into a string option. When setting it to a number, it is +automatically converted, for backwards compatibility with 3.0. |'whichwrap'| + +Expand environment options for 'term'; allows ":set term=$TERM". |'term'| + +Replace 'nobuf' by 'writedelay' ('wd'), time to delay writes by. Makes it +possible to reduce the writing speed to almost nothing. |'writedelay'| + +Added 's' and 'S' flags to 'cpoptions', influence the behaviour of options for +a buffer: Copy when created, copy when first entered, or copy every time. +Compatible with version 3.0 by default, compatible with Vi when 'S' included. + |'cpoptions'| + +When 'bin' option is set, save the values of the options that are set to 0. +Restore the values when 'bin is reset. |'bin'| + +Added 'ttyscroll' option: Maximum number of screen lines to do scroll with. +When trying to scroll more lines, redraw the window. |'ttyscroll'| + +Added 'modified' option. It's now possible to force a file to be modifed or +not modified. |'modified'| + + +Support for editing one-line paragraphs *new_para* +--------------------------------------- + +Sometimes it is useful to keep a paragraph as a single long line. A few +facilities have been added to make it easier to edit this text. + +When the 'linebreak' option is set, long lines are broken at a convenient +character. This can be defined with the 'breakat' option. By default, this +is " ^I!@*-+_;:,./?". |'linebreak'| + +The 'showbreak' option can be set to the string to display on continuation +lines. This makes it easy to see which lines have been broken. + |'showbreak'| + +The commands "gk", "gj", "g0", "g^" and "g$" have been added: Move a screen +line. They differ from "k", "j", etc. when lines wrap. |gk| |g0| + + +Usage of key names *new_keys* +------------------ + +Special keys now all have a name like , , , etc. This name is +used for mappings, in listings, and many other things. It can be disabled by +adding the '<' flag in 'cpoptions'. |key_notation| + +For keys that don't have a <> name, but do have a termcap name, the +form can be used. The "xx" entry from the termcap is used. |terminal_options| + +Show meta keys in mappings as M-key. Use highlight option '8' for higlighting +the meta keys. |meta| + +Added a few special keys. + and work like CTRL-B and CTRL-F. || || + goes to column one, goes to end of line, in Insert and Normal +mode. || || + in normal mode starts Insert mode; in insert/replace mode, toggles +between insert and replace mode; in command-line editing, toggles between +insert and overstrike. || |i_| |c_| + +It is now possible to use both and (they used to be the same +key). |function-key| + + +Viminfo *new_viminfo* +------- + +A lot of the history that is kept in Vim (command-line history, marks, +registers, etc) can be stored in a viminfo file. It is read on startup, +restoring your last environment. |viminfo_file|. + + +Compilation improvements *compilation* +------------------------ + +The Unix version has a completely different Makefile. Autoconf is used to +adjust to different Unix flavors. Prototypes for functions are obtained from +include files, missing prototypes are used from osdef.h. It should be much +more straightforward to compile Vim on a Unix system. |vim_unix.txt| + +The INSTALL file now contains instructions for compiling. + +In 3.0 you needed an Amiga C compiler to generate prototypes. Now it can be +done on Unix too, by using cproto. + +A lot of warning messages for GCC have been removed. + +A makefile target, 'make shadow', helps for building Vim for multiple machines +in the same source tree. It is a poor man's VPATH---but much more portable +and flexible than any trick that gmake could perform: It creates a +subdirectory called shadow containing all the required symbolic links. For +example, try 'make shadow; mv shadow sun4-gcc; cd sun4-gcc; ./configure' + + +Improved (error) messages *new_msg* +------------------------- + +Give error messages that includes the command line for ":" commands that are +not typed by the user. Changed "Invalid command" message into ": Not +an editor command". + +When an error message is given while sourcing, starting up, executing +autocommands, or modelines, the source of the error and line number are +mentioned. + +Don't sleep after giving an error message. Error messages now never overwrite +other messages and each other. + +Distinguish between an unimplemented Ex command and an unrecognized command +in the error message. + +When jumping to file under cursor, give proper error message instead of beep. + |gf| + +Changed the displaying of messages for many commands. Makes ":1p|2p" work. +Avoids having to type some extra returns. + +Added error message for using '!' in the command-line where it is not allowed. + +Added wait_return() when filtering finds an error while executing the shell +command. Makes it possible to read the error message. + +When printing errors for ":set", use transchar() to avoid problems with +non-printable characters. |:set| + +Fixed having to type RETURN twice when starting to edit another file and the +message is too long. + +When search pattern is not found or contains an error, print the pattern. + |search_pattern| + +Improved error message for unrecognized command in vimrc. Now the whole line +is displayed. |vimrc| + +When error detected while writing a file, give error message instead of normal +message. + +Improved error messages for some options: Show character that caused the +error. + +Completely changed 'showcommand'. Now also shows parts of mappings, CTRL-V, +etc. |'showcmd'| + +Changed the messages given when 'showmode' is set. Show VISUAL when Visual +mode is active. Changed "-- INSERT COMMAND --" to "-- (insert) --". +Add "(paste)" in Insert mode when paste option is set. + +Add [RO] to status line for read-only files. + +Improved error messages for using '%' and '#' on command-line. + +An error in a regular expression would get two error messages. The last one, +"invalid search string" is now omitted. Don't get the "hit RETURN to +continue" message. + + +Swap file *new_swapfile* +--------- + +The "swap file exists" message used to be given when the name of an existing +swap file matches the name of a new swap file. Now this only happens when the +name of the original file is also the same. This is implemented by storing +the name of the file in the swapfile and always writing the first block of the +swap file to disk. Helpful when putting all swap files in one directory. +Unix: Give swapfile same protection as original file: makes it possible for +others to check the file name in the swap file. +Unix: Store the inode of the original file in the swap file. Use it when +checking if the swap file is for a certain file. Helps when editing the same +file with a different path name (over a network). |swap_file| + +Give more information about the swap file with the "swap file exists" message +and when recovering. + +Added 'swapsync' option: When empty swap files are not synced (for busy Unix +systems). When set to "sync", sync() is used; when set to "fsync", fsync() is +used. Makes it possible to fix complaints about pauses. The default is +"fsync". |'swapsync'| + +Reduce the calls to fsync() or sync. Don't call it when syncing block 0 or +when the file has not been changed. Don't sync block 0 for a help file. + +When file system is full, the error message for writing to the swap file was +repeated over and over, making it difficult to continue editing. Now it is +only given once for every key hit. + +Included catching of deadly signals for Unix and Win32. Swap files for +unmodified buffers are deleted, other swap files are preserved before exiting. +After catching some deadly signals, produce a core dump. + +Added ":recover [file]" command, for recover after getting the "swap file +exists" message. |:recover| + +Improved recovery when there are multiple swap files. You get a choice which +one to use (the swap file from the active editing session is ignored). +"Vim -r" gives information about date and contents of the swap files. Allow +":recover" without a file name, to recover from ".swp". |-r| + +Use home_replace() for the file names displayed during recovery. + +When editing in readonly mode, don't set p_uc to zero and do use a swap file. +Fixes problem of not being able to edit large files with "Vim -v". 'uc' is +set to 10000 to reduce the number of writes to the swapfile. + |'updatecount'| + +When looking for swap files, also consider the shortname version. Finds +swapfiles on MS-DOS (FAT) file systems. |swap_file| + +When encountering a serious error while doing "vim -r file" (like swap file +not found or empty swap file), quit Vim. |-r| + +MS-DOS and Win32: Always use full path name for swap file; otherwise it can't +be deleted after using ":!cd dir". |swap_file| + + +Mouse support *new_mouse* +------------- + +The mouse is supported in an xterm and for MS-DOS and Win32. This can be +controlled with the 'mouse' option. |mouse_using| |'mouse'| + +The mouse can be used for: +- positioning the cursor in the text || +- positioning the cursor in the command-line |c_| +- selecting the Visual area || +- inserting (previously) selected text at the cursor positon || +- moving a status line |drag_status_line| +- selecting the active window || +- jumping to a tag in the text || |g| +- popping a position from the tag stack || |g| +- confirming the "Hit return to continue" message |'mouse'|. +- etc. + +By default, the mouse support is off for Unix, allowing the normal copy/paste +with the mouse in an xterm. Switch it on with ":set mouse=a". For MS-DOS, +Win32, and GUI, the mouse is on by default. + +When quickly repeating a mouse click, this is recognized as a double, triple +or quadruple click. The 'mousetime' option sets the maximum time between two +clicks for GUI, MS-DOS, Win32, and xterm. |'mousetime'| + + +Graphical User Interface (GUI) *new_gui* +------------------------------ + +Included support for GUI: menus, mouse, scrollbars, etc. Currently only with +Motif and Athena interface. You need at least Motif version 1.2 and/or X11R5. +Motif 2.0 and X11R6 are OK. Motif 1.1 and X11R4 don't work properly (but you +might make it work with a bit of work) |gui|. + +Added options for GUI: +'guioptions' (list of flags that set the GUI behaviour) |'guioptions'| +'guifont' (list of fonts to be used) |'guifont'| +'guipty' (whether to use pipes or pty for external commands) |'guipty'| + +Added text register '*' (for text selected in GUI with mouse). + |registers| + + +Support for Windows NT and Windows 95 *new_win32* +------------------------------------- + +There is now a new version for NT that can also be used for Windows 95. +It supports long file names, uses all available memory, and many more +enhancements. |vim_w32.txt| + +There is also an MS-DOS version that has been compiled with DJGPP. It uses +all available memory. It supports long file names when available. + |vim_dos.txt| + + +Support for OS/2 *new_os2* +---------------- + +There is now a version of Vim for OS/2. It was compiled with EMX, which made +this port quite easy. It mostly uses the Unix files and settings. + |vim_os2.txt| + + +Support for MiNT *new_mint* +---------------- + +There is now a version of Vim for MiNT. It was compiled with gcc under +a Unix-like environment. + |vim_mint.txt| + + +Miscellaneous new features *new_misc* +-------------------------- + +When using CTRL-A and CTRL-X, leading zeros are preserved for octal and +hexadecimal numbers. |CTRL-A| |CTRL-X| + +"%" now works to match comments of the form "/* Comment /* */". |%| + +Added "gd", Go Declaration: search for identifier under cursor from the start +of the current function. "gD" searches from start of the file. Included +files are not used. |gd| |gD| + +Added commands ":ascii" and "ga", show value of character under the cursor in +several ways. |ga| |:ascii| + +Added "gg" command to goto line 1. |gg| + +Added "gI" command: Start insert in column 1. |gI| + +Added "g~", "gu" and "gU" operators, change case of selected text. + |g~| |gu| |gU| + +Added "ge" and "gE", go back to end of word or WORD. |ge| |gE| + +Added "g CTRL-G": Display info about the position of the cursor. |g_CTRL-G| +Added virtual column number to CTRL-G. |CTRL-G| +When using count > 1 with CTRL-G, show buffer number. + +Added "gv" command: reselect last Visual area. In Visual mode, exchange the +current and the previous Visual area. |gv| + +Implemented "zh" and "zl": scroll screen left/right when 'wrap' is off. +Implemented "zs" and "ze": scroll screen with cursor at start or end of screen +when 'wrap' is off. |zh| |zl| |zs| |ze| + +Added "g*" and "g#" commands: like "*" and "#" but without using "\<" and "\>" +for matching whole words. |gstar| |g#| + +Put character attributes in a separate byte in NextScreen; makes updating of +highlighted parts more reliable. + +Added column number to ":marks" command. |:marks| +Improved error messages for marks that are unknown, not set, or invalid. +Added ''' mark to ":marks". + +Added argument to ":display" and ":marks", show info for the argument. + |:marks| |:display| + +Added the ":retab" command. Can be used to change the size of a , +replace spaces with a , or a with spaces. |:retab| + +If VIMINIT is set but is empty, ignore it. |VIMINIT| + +Added ":ls", synonym for ":files". |:ls| + +Included setting of window size for iris_ansi window. +Included better checks for minimum window size and give error message when it +is too small. +When resizing the window and 'equalalways' is set, make all windows of equal +height (all lines were taken/given from/to the last window). + +When using the 'c' flag for the ":substitute" command, accept CTRL-E and +CTRL-Y to scroll the window up/down. +For ":s///c", the complete match is highlighted (like with incsearch). +When doing ":s/$/asdf/c", highlight one character beyond the end of the line +to show where the substitute will take place. +Added 'a' reply to substitute with confirmation: like 'y' for all remaining +replacements. |:s_c| + +For ":s", don't accept digit as separator; ":s8foo8bar8" doesn't work. + |:s| + +Changed "--more--" prompt to be more informative when a wrong key is typed. +When 'q' is typed at "--more--" message, don't display an extra line, don't +wait for return to be hit. +Added 'd' to --more-- responses: down half a page. +Added ':' to --more-- responses: start editing a command-line. |'more'| + +Put the cursor after the end of the line in Insert mode when using "CTRL-O $", +"CTRL-O 80|", and when putting text after the line. |i_CTRL-O| + +When splitting a window, the new window inherits the alternate file name. +With ":split [file]" and ":new [file]", the alternate file name in the current +window is set to [file]. |:split| + +Added CTRL-W CTRL-^ command: split and edit alternate file. |CTRL-W_CTRL-^| + +Made it possible to put options for Vim after a single "-", e.g., "vim -gf". +Allow "--" to make arguments after it be interpreted as file names only. +Give error message when repeating "-s" or "-w" option. + +Implemented "Vim -r" to list any swap files that can be found. In version 3.0 +this command used to crash Vim. |-r|. + +Removed reverse replace mode (can we call this a new feature?). It is too +complicated to do right and nobody will probably use it anyway. Reverse +insert is still possible. |'revins'| + +Added "gq" as an alias to "Q". The "Q" command will be changed in a following +version of Vim, to make it Vi compatible (start Ex mode). |gq| + +The "Q" operator no longer affects empty lines. You can now format a lot of +paragraphs without losing the separating blank lines. Use "Qp..." to format a +few consecutive paragraphs. |Q| + +Formatting with "Q" fixes up the indent of the first line (replace with +minimal number of tabs/spaces). |Q| + +After "Q", put cursor at first non-blank of the last formatted line. This +makes it easier to repeat the formatting. +Made "Q}" put the cursor on the line after the paragraph (where the "}" +command would have taken the cursor). Makes it possible to use "." to format +the next paragraph. |Q| + +When formatting with "Q" while 'tw' and 'wm' are both zero, use a textwidth of +79, or the screen width minus one if that is smaller. Joining all the lines +doesn't make sense for the "Q" command. |Q| + +Added CTRL-W CTRL-T (go to top window) and CTRL-W CTRL-B (go to bottom +window). |CTRL-W_CTRL-T|. + +When in Insert mode and wrapping from column one to the last character, don't +stick at the end but in the column with a following "k" or "j" command. + +Added "[P" and "]P" as synonyms for "[p". These commands are now redoable. +Fixed cursor positioning and character-wise text. |[p| + +Improved the handling of empty lines for "[p" and the like. |[p| + +Improved ":center", ":right", and ":left"; blank lines are no longer affected, +tabs are taken into account, trailing blanks are ignored. |formatting| + +Made '.' in 'path' be replaced with directory of current file. Makes "gf" +work on "#include "vim.h"" when editing "src/normal.c". Empty part in 'path' +stands for current directory. |gf| |'path'| + +Added '-' register for deletes of less than one line. |registers|. + +When :bdel and :bunload are used to remove buffers that have active windows, +those windows are closed instead of giving an error message. Don't give an +error message when some, but not all, of the buffers do not exist. +Added buffer name argument to ":bunload" and ":bdelete". Completion also +works, and '%' and '#' can be used. |:bdelete| |:bunload| + +When file name changed, also change name of swap file. This makes it easier +to find the swap file for recovery. |swap_file| + +The Amiga trick for recognizing an MS-DOS-compatible filesystem is now also +done for UNIX. Fixes problems with wrong swap file and backup file name on +an MS-DOS partition for FreeBSD and Linux. |auto_shortname| + +When writing the file fails and there is a backup file, try to put the backup +in place of the new file. Avoids losing the original file when trying to +write again and overwriting the backup file. |write_fail| + +If 'backup' is off and 'writebackup' is on, don't delete an existing backup +file; instead use another file name. |backup_table| + +When going to another window in Visual mode: if it is the same buffer, +update Visual area; if jumping to another buffer, reset Visual mode. + |Visual_mode| + +When an empty buffer is edited and written out, that file is also empty. +Added "No lines in buffer" message when last line in buffer is deleted. + +Added checks for creating too long lines on non-UNIX systems. |limits| + +When last file in argument list has been accessed, quit without asking. +This works more intuitively when using ":prev". |arglist_quit| + +Set alternate file name when using buffer commands like ":bmod", ":buf", +":bdel", etc. |:buffer| + +Changes to quickfix: ":cl" lists only recognized errors, use ":cl!" to list +all. ":cn" and ":cp" don't go to an unrecognized error but give an error +message when at the first/last error. When a column number is not found, put +the cursor at the first non-blank. When deciding to redisplay the message or +not, take tabs into account. When ":cp" or ":cn" fail for some reason, don't +change the error index. Changed the format of the ":cl" listing. Don't show +the column number if it is zero. Take care of tabs when displaying an error +message. Error number or count can also be before ":cc", ":cp", and ":cn". + |quickfix| + +When asked a yes/no question, ESC is the same as 'n'. |:s_c| + +Removed a few large arrays from the stack; MS-DOS was running out of stack +space. + +Added ":view file" and ":sview file": start editing a file with 'readonly' +set. |:view| |:sview| + +Removed restriction on command-line length. + +When adding a jump to the jumplist, remove older jumps to the same line. + |jumplist| + +Added separate mapping for normal mode and Visual mode. New commands ":nmap", +":nnoremap", ":nunmap", ":vmap", ":vunmap", and ":vnoremap". |:nmap| + +When 'visualbell' is set and 't_vb' is empty, don't beep or flash or anything +(used to display some ^G in the command-line which was deleted before you +could see it). |t_vb| |'visualbell'| + +Added "-u vimrc" Vim argument: Read initializations only from specified vimrc +file and skip the other initializations. Use "-u NONE" to skip. |-u| + +Added "-i viminfo" Vim argument: Set name for viminfo file. Use "-i NONE" +to skip reading viminfo. |-i| + +For commands that get a file name out of the text (e.g., "gf", "[f"), ignore +the part of a hypertext link that gives the file type and machine name. + |gf| + +When 'columns' or 'lines' is changed, try to set the window size. + +Termcap stuff is not used when not in full-screen mode. + +Process modelines after recovering a file. When checking modelines runs into +an error, don't check other lines. |modeline| + +When 'wrap' option is off, make sure the whole character under the cursor is +on the screen (for TAB and non-printable characters). |'wrap'| + +Added '\r', '\n', '\b', '\e' and '\t' to regular expressions. +Added "\i" to regular expressions, match identifier character. "\I" does the +same but without digits (for start of identifier). Also added "\k" and "\K" +for keywords, "\f" and "\F" for file name, "\p" and "\P" for printable +characters. |search_pattern| + +The trick in GetChars() in unix.c to keep on reading characters until none are +available is not done if Read() returns more than one character. Should speed +up getting characters from the keyboard on many systems. + +Added implementation of 'lisp' option. It is not 100% the same as Vi. +Added "-l" Vim argument: Switch lisp mode on. |-l| + +MS-DOS: Only beep once in ten times between key hits. Needed because +beeps take a lot of time to wait for. + +Made argument to GetChars a long; makes it possible to wait for more than +32 seconds on 16-bit machines (for the very patient). |'timeoutlen'| + +Made "#" and "*" find the text under the cursor. |star| |#| + +Unix: Expand path names starting with "~" to full path names. Makes "gf" work +on "~user/path". |gf| + +Fixed behaviour of "x" and "X", when 'whichwrap' is set, to include 'l' and +'h' for wrapping. Now "x" on an empty line deletes that line and "X" in +column 0 joins the line with the previous line. |x| |X| |'whichwrap'| + +When doing ":make" and 'autowrite' is set, write all buffers, not just current +one. Just like ":!cmd". |:make| + +":make" now shows the command that is executed by the shell, including +'shellpipe' and 'errorfile'. Helps to understand how 'shellpipe' is used. + |:make| + +Allow for digraphs to be entered in reverse: Char1-char2 and char2-char1 both +work. |digraphs| + +When 'splitbelow' is not set, the space from closing a window goes to the +window below it instead of above it. Makes the sequence split-window, +close-window not change the window layout. |'splitbelow'| + +Added '< and '> mark. '< is lowest position of Visual area and '> highest +position. Use ":'<,'>" when ":" is used in Visual mode. Makes it possible to +use the command-line history. |'<| + +Implemented ":*", which is short for ":'<,'>", Visual area. |:star| + +Stop Visual mode when starting to edit another file (e.g., with "'A"). + +When doing ":xit" or "ZZ" in a window for a buffer that has changed, write the +buffer even if there are other windows on this buffer. |:xit| + +Give a warning message when 'patchmode' and 'backupext' are equal. + +When input/output is not from/to a terminal, just give a warning message, +don't exit. + +In Insert mode, CTRL-K ESC does not exit Insert mode. The two characters +typed after CTRL-K are not mapped. +Typing a special key after CTRL-K inserts the <> name. CTRL-V inserts the +terminal key code (except in the GUI). |i_CTRL-K| |i_CTRL-V| + +Improved screen output. The cursor positioning code is avoided whenever +possible. When outputting a char on the next line, column zero, use CR-LF to +go there instead of cursor positioning, which uses fewer characters. +Added using scroll regions when inserting/deleting screen lines. Works faster +for terminals that don't have erase/delete line functions but do have +scrolling regions (vt100). |t_cs| + +No longer do home_replace() when it results in just "~"; it was confusing that +'backupext' and 'backupdir' are both shown as "~" by default. + +Unix: When making a backup file, set the group the same as the group of the +original file. If this fails, set the protection bits for the group to be +the same as for others. |'backup'| + +Fixed: When 'textauto' is set, only the first line was used to decide to set +or reset 'textmode'. Would cause problems for, say, a file with mappings. +Now when editing a file where the first line ends in ^M, but a later one +doesn't, the file is reloaded in notextmode. |'textauto'| + +When sourcing a file on MS-DOS, Win32, or OS/2 and 'textauto' is set, try to +recognize non-textmode files by the first line. |:source_crnl| + +For ":" commands that have one file name argument: Only Unix sees the space as +a file name separator. For Amiga, MS-DOS, et al., a space is considered to be +part of the file name (except a trailing space). For Unix, don't give an +error message for an argument with spaces when a wildcard is used; give the +error when expanding the wildcard results in more than one file. This allows +the use of ":e `ls ve*.c`" on Unix. + +When replacing the name of the home directory with "~", try both $HOME and the +"real" home directory: `cd $HOME; pwd`. Fixes problems when home dir is +mounted or contains links. Fixed home_replace replacing "/home/pieter/file" +with "~er/file" when home is "/home/piet", Command-line completion on file +names and buffer names use "~/" for home directory when approriate. + +When completing with CTRL-L and there are multiple matches, beep! Ignore case +when comparing file names for MS-DOS and Win32. For Amiga, don't ignore case +for non-file names. |c_CTRL-L| + +Added ":registers" as a synonym for ":display". |:registers| + +Added check for "locale.h"; renamed USE_LOCALE to HAVE_LOCALE_H. + +Recognize a terminal name as xterm when it starts with "xterm" and iris-ansi +when it starts with iris-ansi. This will also catch "xterms". Also: ignore +case, catch "Xterm". This is used when deciding to use the window title. +Unix: If terminal is xterm, hpterm, dtterm, sun-cmd, screen, or iris-ansi, set +'ttyfast' option. +Added builtin termcap entry for iris-ansi. The entry in the termcap is often +wrong. |'term'| + +Included SAVE_XTERM_SCREEN in feature.h, to include the t_ti and t_te entries +for the builtin xterm. They enable saving the xterm contents when starting +Vim and restoring it on exit. |xterm-screens| + +Special trick to make copy/paste of wrapped lines work with xterms: If the +first column is to be written, write the last char of the preceding line +twice. This will work with all terminal types (regardless of the xn,am +settings). (Juergen Weigert) But only when 'ttyfast' is set. + +When 'title' and/or 'icon' is reset in vimrc, don't even check if they can be +restored. This reduces startup time when using Vim in an xterm on a remote +machine. |'title'| + +When coming back from a CTRL-Z, before setting title, store the current title +again for restoring later, as it might have changed. |'title'| + +Expanding shell variables works now. Also made ":e $VAR^I", ":e this$VAR^I", +and ":e `echo hi`^I" expand correctly by not adding a "*" in these cases. + +Command-line expansion: Command after '+' will be expanded; e.g., in +":e +s^D". Where file names may be expanded, support for back-quotes has been +added. |+cmd| + +Set '[ and '] marks to start and end of undone/redone lines. +Set '[ and '] to start/end of inserted text after inserting text. |'[| + +Marks in the jumplist are not deleted when deleting lines; avoids "mark not +set" error messages when using CTRL-O command. If there are two marks for the +same line, the oldest one is removed. |CTRL-O| + +Added setting of previous context mark when changing files (with ":e file" or +":n" or ":buf n"). Now you can go back with CTRL-O. |CTRL-O| + +Added list of options that are enabled/disabled at compile time to ":version" +command (+GUI -digraphs -eindent, etc.). For Unix, add a line to the +":version" command to show how it was compiled. |:version| + +Made CTRL-N and CTRL-P for command-line completion line cyclic. |c_CTRL-N| + +"" in the command line where a file name is expected is expanded to +the current word under the cursor. "" is expanded to the WORD under +the cursor, "" to the file name under the cursor. "" is +expanded to the file name for the current autocommand. |:| + +Added character 163 (pound sign on English keyboards) as an alternative for +the '#' command. |#| + +Added ":cNext" as a nickname for ":cprevious". |:cNext| + +Made ":bnext" and ":bNext" wrap around the end of the buffer list. |:bnext| + +When a regexp contains a '[' without a matching ']', assume the '[' is a +normal character. This makes ":help [" work. |search_pattern| + +When "[{" or "]}" is used with a count > 1, and not enough levels are found, +just use the last match found. Makes "99[{" go to the start of a function. + |[{| + +Added commands to search for #if/#endif and start/end of comment: "[#", "]#", +"[*", "[/", "]*" and "]/". |[#| |[*| |[/" + +Added break checking to do_do_join(); makes it possible to use CTRL-C when +joining lots of lines. |J| + +Made it possible to include ":sall" in vimrc. main() won't open the buffer +and won't split the windows when this already has been done by a command in +the vimrc. Added count to ":sall", ":sunhide" and ":sball": maximum number +of windows. |:sall| + +Added ":abclear" and ":mapclear": remove all abbreviations/mappings. + |:mapclear| |:abclear| + +MS-DOS: For Visual mode, always invert the character under the cursor, also +when cursor cannot be switched off (it's mostly not a block cursor). + |t_vi| + +Made ":g/#/d" a lot faster by not calling cursupdate() for each deleted line. + |:global| + +When opening windows for all buffers or arguments, make one window with at +least 'winheight' or 5 lines. Avoids ending up with all one line windows. + |:all| |:ball| + +MS-DOS and Win32: Always use a backslash in file names. Using a slash caused +trouble with some external commands. Using a backslash before a normal +filename character is now allowed for most commands. + +Added check for last modification time of original file before overwriting it. +When it has changed since reading or writing it, ask the user if he wants to +overwrite it or not. Also check when suspending, calling a shell (command), +and un-hiding a buffer, and give a warning message if it has changed. + |timestamp| + +Added filename argument for ":buffer" and ":sbuffer". Allow an incomplete +specification, jump to the buffer where the name matches, unless there are +several matches. |:buffer| + +When there is an argument list and we are not really editing the 3rd file in +it, display "((3) of 5)" instead of "(3 of 5)". + +Added termcap options for "da" and "db". On terminals that have "da", scroll +reverse moves lines from above down onto the screen; on terminals that have +"db", deleting a line moves lines from below onto the screen. These lines +need to be cleared. |t_da| |t_db| + +Added termcap options for "cd", clear until end of display. Works faster than +repeating "ce", clear to end of line. |t_cd| + +When changing the terminal name, restore the title and other things before +clearing the terminal codes, and set them back after setting the new codes. + +Don't clear the screen if stderr is redirected for filter command. Don't give +message for writing or reading the temporary file for a filter command (unless +there is an error). |:!| + +When reading a file, also set the no-end-of-line when not in binary mode. +When 'binary' is set later and the file is written, the end-of-line is not +written. Helps when having an autocommand to gunzip "*.gz" files. + |'binary'| |'eol'| + +Unix: Allow reading from fifos (named pipes) and sockets. Give a message when +doing it. + +Added modifiers for '%' and '#' on the command-line: ":p" for path, ":e" for +extension, etc. |::p| + +When replacing '%', '#', etc. on the command-line, do wildcard expansion if +there were any wildcards before replacing. Makes ":so `macros_file %` work. +But for ":e %" no expansion is done, in case the current file name contains a +wildcard. |:_%| + +MS-DOS and Win32: When $VIM is not defined, use $HOME. |'helpfile'| + +When replacing home directory with "~", and it's not followed by anything, +make it "~/". Looks more like a path name and can't be confused with "~" for +'backupext'. |home_replace| + +Added 'aleph', 'hkmap', and 'rightleft' options. Can be used to edit text +that is written from right to left (e.g., Hebrew). Improvement above reverse +insert. Only when RIGHTLEFT is defined at compile time. + |vim_rlh.txt| + +Added extra flag 'm' to 'cpoptions'. If included, typing does not interrupt a +'showmatch' delay, like Vi. By default it is not included; when a character +is typed, the showmatch is aborted. |'showmatch'| + +Line number of ruler is zero when buffer is empty. Makes it possible to see +the difference between an empty buffer and a buffer with a single line in it. +Column number of ruler is zero when line is empty. Makes is possible to see +the difference between an empty line and a line with a single space in it. + |'ruler'| + +When trying to quit Vim while there is a modified, hidden buffer, make that +buffer the current buffer. Helps the user to decide to use "q!" or ":wq". + |hidden_quit| + +Added Shift-Tab for command line completion: Works like CTRL-P. It works for +Amiga, MS-DOS, and Win32. |c_| + +When trying to open a ".vimrc" file and it fails, try opening "_vimrc". Also +the other way around. This helps for OS/2, Win32, and combined Unix/DOS +machines. + +When doing ":wn" or 'autowrite' is on, there are two file messages, but it's +not clear which is for writing. Added "written" to message for writing. Also +helps to avoid confusion when appending to a file. Can be made shorter with +'w' and 'W' flags in 'shortmess'. + + +VI COMPATIBILITY IMPROVEMENTS *vi_compat* +============================= + +Added "\?", "\/", and "\&" to Ex address parsing; use previous search or +substitute pattern. |:range| + +Set alternate file name for ":file fname" command. |:file_f| + +When using "D" in an empty line, give beep. |D| + +Accept CTRL-Q in Insert mode and command-line mode like CTRL-V. + |i_CTRL-Q| |c_CTRL-Q| + +Added "-R" Vim argument: readonly mode. |-R| + +Added "-L" Vim argument, same as "-r", do recovery. |-L| + +Don't set 'readonly' for each file in readonlymode. |'readonly'| + +"Vim +/pat file" now finds pat in line 1. |-+/| + +Quit readonly mode when the 'readonly' option is reset in any buffer. + |'readonly'| + +Made ":|" print current line (used to do nothing). |:bar| + +Fixed: ":@r" only executed the first line as an Ex command, following lines in +normal mode. +For ":@r", when register "r" is not linewise, add a return anyway (only when +'cpoptions contains 'e'). |:@| + +Implemented "-w{number}" Vim argument. It is ignored (Vi sets the 'window' +option to {number}). |-w_nr| + +When using ":wq" after the message "No lines in buffer", an empty file is +created (used to be a file with a single newline). + +Allow writing a readonly buffer with ":wq file". |:wq| + +Allow range for ":xit", ":exit", and ":wq". |:xit| |:wq| + +Added ":open" command (not supported), to make ":o" not be recognized as +":only". |:open| + +Also set previous context mark when moving within the line (but not when not +moving the cursor at all). Makes the mapping "map 0 my^V|mzl$`z`y``" work. + |''| + +Added 'cpoptions': flags that influence the Vi-compatible behaviour. +This includes a flag that switches on the display of a dollar sign for a +change within one line, without updating screen but putting a '$' a the end of +the changed text. |'cpoptions'| + +Only when 'f' flag is present in 'cpoptions' is the file name for a ":read" +command used to set the name of the current buffer, if it didn't have a +name yet. Likewise for the 'F' flag and ":write". + +In replace mode, NL does not replace a character but is inserted. +Fixed 'r'. It didn't delete a character. Now also made replace with a +newline and a count Vi-compatible: only one newline is inserted. + +Fixed moving marks with the ":move" command. |:move| + +Allow ';' after search command, e.g., "/pat/;?foo". |//;| + +Made work like "1|" instead of "0": makes the cursor stick in column 1 +when moving up/down. || + +"O" in an empty buffer now inserts a new line as it should. +":0r file" in an empty file appends an empty line after the file, just like +Vi. |:read| +"dd" deletes a single, empty line in the buffer, making the buffer empty. + +In an Ex address, the '+' character is not required before a number, ".2d" is +the same as ".+2d", "1copy 2 3 4" is the same as "1copy 2+3+4". + |:range| + +An ESC in normal mode does not flush the map buffer, only beeps. Makes +":map g axx^[^[ayy^[" work. + +After undo, put cursor on first non-white instead of in column 0. +Put cursor on first non-white after a few Ex commands and after "2>>". + |u| |>>| + +The commands ":print", ":number", and ":list" did not leave the cursor on the +last line. + +Fixed "vim -c '/return' -t main" not working. Now the tag is jumped to before +executing the "-c" command. |-c| |-t| + +In a search pattern, '*' is not magic when used as the first character. + |search_pattern| + +Made searching a bit more Vi-compatible. Fixed search for "/.*p" advancing +only one character at a time instead of jumping to after the 'p'. +"abababababab" only gets three matches for "/abab", instead of five. +When 'c' is not present in 'cpoptions', the Vim version 3.0 way is used. + |'cpo'| + +When writing part of the buffer to the current file, '!' is required. Could +accidentally destroy the file when giving a line range or when Visual was +active while doing ":w". +Error message when trying "1,10w"; now it is "Use ! to write partial buffer". +It is also given when the current file does not exist yet. |:w!| + +"r" now sets the last inserted text. |r| + +Allow "map a ab", head recursive mapping (just like Vi). |recursive_mapping| + +Remove trailing spaces for Ex commands that accept a "+command" argument, when +it is not followed by another command; e.g., when using ":e file ". |:+cmd| + +Fixed CTRL-V's not being removed from argument to ":map". |:map| + +Made "|" exclusive. |bar| + +Doing "%" on an empty line now beeps. |%| + +Changed "No lines in buffer" from error message into normal message. This is +not Vi-compatible (let's call it a negative compatibility improvement), but it +works more like one would expect. + +Fixed removing not enough backslashes and too many CTRL-V's from filenames. + |:filename| + +When command line causes an error, don't execute the next command after '|'. + |:bar| + +When starting to edit a new file, put cursor on first non-blank of line, +instead of column 1. + +Changed CTRL-D and CTRL-U to scroll a certain amount of screen lines instead +of physical lines (makes a difference with wrapping lines). +Changed the cursor positioning for CTRL-U and CTRL-D when lines wrap. The +cursor is now kept a fixed number of FILE lines from the start of the window, +instead of SCREEN lines. This should make CTRL-U followed by CTRL-D make the +cursor return to the same line in most cases (but not always). +Made CTRL-U on first line and CTRL-D on last line in buffer produce a beep +and not do anything. This is Vi-compatible. |CTRL-D| |CTRL-U| + +Added support for abbreviations that end in a non-keyword character. + |abbreviations| + +When 'formatoptions' option is set to "vt", formatting is done Vi-compatibly. + |fo_table| + +When COMPATIBLE is defined when compiling, 'modeline' is off by default. + +Made the use of modelines a bit more Vi-compatible: There must be white space +before "ex:". When using "vi:set " there must be a terminating ':'. + |modeline| + +Fixed Vi incompatibility: "ou" didn't put the cursor back where it was +before. "Ou" is still incompatible, this is considered a bug in Vi, +because it puts the cursor on another line. |u| + +Made "zz" at end of file also put cursor in middle of window, making some "~" +lines visible. |zz| + +Fixed files being written when 'autowrite' is on and a filter command is used. + |'autowrite'| + +Screen updating for "J" improved a bit. Was clearing the cursor line instead +of the next line. Looks smoother now. + +For ":sleep" command: place the displayed cursor at actual cursor position. + +Removed restriction on number of screen columns (MAX_COLUMNS). It was not +used for anything. + +When using a substitute command in a ":global" command, summarise the number +of substitutions once, instead of giving a message for each line. + +Fixed: The ":global" command could set a mark in the jumplist for every +matching line. Now only set the previous context mark once. Also makes +":g/pat/s//foo/" run quite a bit faster. + +":global" didn't stop at an error message, e.g., from ":g/pat/s//asd/p". Now +it stops at the first error message. + +When the 'x' flag is present in 'cpoptions', typing on the command-line +executes it, instead of abandoning it. + +Added 'p' flag to ":substitute", print last line with substitution. + +Fixed: ":0;/that/" should not set previous context mark. + +Fixed: ":r file" should put cursor on first non-blank. + +Adjusted default for 'history' when COMPATIBLE defined. Adjusted setting of +'iskeyword' when 'compatible' is set or COMPATIBLE defined. + +Fixed: ":set paste all" should also recognize "all". + +Fixed: Vi allows for a ':' between the range and an Ex command. + +Fixed Vi incompatibility: A shell command ":!ls" can be followed by a newline +and another Ex command; e.g., ":!ls^@p" (Where ^@ is entered as . +Don't do this when there is a backslash before the newline. + +Filter command didn't put cursor on first non-blank after filtering. + +Don't skip spaces when using ":!! -x". + +"r!ls" didn't set the previous "!" command. Now ":r!!" also works. +Fixed: Only one '!' argument to a ":!" command was expanded. Now this is Vi +compatible. The '!' can be escaped with a backslash. + +Added '!' flag to 'cpoptions'. When present, a shell command ":!cmd" sets the +function to use for redoing a filter command with ".". + +Fixed: "dfYfX.;" did put cursor on "Y" instead of "X". Don't set last +searched character when redo-ing a command. + +Fixed: "/pat" ":s//foo" should use "pat" for search pattern. It's hard to +find out how Vi works... + +Added 'tag' as a short name for the 'tags' option (Vi compatible). +Added 'ed' abbreviation for 'edcompatible' option, 'scr' for 'scroll', 'tty' +for 'ttytype', 'wi' for 'window', 'to' for 'timeout' (Vi has them too). + +Added: 'tagstack' ('tgst') option. It's hidden (Vi: enable tag stack). +Fixed: 'ttytype' option works like an alias for 'term'. + + +BUG FIXES *bug_fixes* +========= + +Changed method to save characters for BS in replace mode. Now works correctly +also when 'et' set and entering a TAB, replacing with CR several times, with +keyword completion and when deleting a NL where spaces have been deleted. + +Fixed an occasional core dump when using ^P or ^N in Insert mode under certain +conditions. + +Fixed ":s/\(.*\)/\1/"; was replacing any with line break. + +Fixed line being printed when there is a '|' after a ":s" command, e.g., +":%s/a/b/g|%s/b/c/g" printed the last line where an 'a' is replaced by a 'b'. + +Don't map the key for the y/n question for the ":s///c" command. + +Fixed bug where inserting a new-line before a line starting with 'if', etc. +would cause a smart-indent because of that 'if'. + +Doing CTRL-@ when there was no inserted text yet didn't quit Insert mode. + +Fixed bug where nowrap was set and doing 'j' or 'k' caused a sideways scroll +with the cursor still in the middle of the screen. Happened when moving from +a line with few tabs to a line with many tabs. + +When CTRL-T fails (e.g., when buffer was changed), don't change position +in tag stack. + +If file system full and write to swap file failed, was getting error message +for lnum > line_count (with ":preserve"). + +Fixed cursor not visible when doing CTRL-Z for some versions of Unix (White). + +Fixed problem with "CTRL-O ." in Insert mode when repeated command also +involves Insert mode. + +Fixed "line count wrong" error with undo that deletes the first line. + +After undo, "''" puts the cursor back to where it was before the undo. + +Inserting a tab with 'et' set did not work correctly when there was a real tab +in front of it (Brown). + +Fixed core dump when using CTRL-W ] twice (tag stack was invalid). + +Fixed overwriting "at top of tag stack" error message. Only show file message +for a tag when it is in another file. + +Added '~' to the special characters for tag search patterns. A tag with a '~' +in the search command is now correctly executed. + +Fixed '^' recognized as start of line in "/[ ^I]^". + +'wrapmargin' is restored to its previous value when 'paste' is reset. +When 'paste' is set, behave as if 'formatoptions' is empty. + +Fixed '^' appearing in first window when CTRL-V entered in second window on +same buffer. + +Fixed using count to reselect Visual area when area was one line. + +Fixed setting curswant properly after Visual selection. + +Fixed problem that column number was ridiculous when using "V" with ":". + +After truncating an autoindent, leave curswant after the indent. + +Fixed ":n #"; put the cursor on the right line like ":e #". + +Recompute column for shown command when rearranging windows. + +Check screen size after Vim has been suspended. + +When creating a new buffer, set 'readonly' to false by default. Fixes getting +an empty readonly buffer after ":new" in a readonly buffer. + +Fixed outputting meta characters (esp. meta-space) when switching highlighting +on/off. + +Added file-number argument to getfile() and do_ecmd() to be able to edit a +specific buffer. Fixes problem with CTRL-^ to buffer without a file name. + +With ":file name" command, update status lines for new file name. + +When recovering, "Original file may have been changed" message was +overwritten. Also changed it into an error message and don't wait for return +after "using swap file" message. + +Fixed core dump when using commands like ":swap" in vimrc file. + +Fixed "more" for :global command. + +Don't accept ":g", global command without any argument. + +Fixed bug: When setting 'history' to 0 (happens when setting 'compatible') +there could be a crash or a hang (only when 'viminfo' didn't include lines in +history). + +After using ":set invlist", cursor would not stick to the column. + +Fixed problem of trailing 'q' with executing recorded buffer when 'sc' set. +Remove the character(s) that stopped recording from the register (normally +'q', but can be more when using a mapping for 'q'). + +MS-DOS: Replace call to delay() by loop around biostime(). Fixes problem with +mouse crawling under Windows 95. + +Fixed CTRL-Z not working for Apollos. + +Fixed cursor not ending on right character when doing CTRL-T or CTRL-D in the +indent in Insert mode. + +When 'columns' was set to zero, core dump or hang might happen. + +Fixed message from ":cn" being deleted by screen redraw. + +Fixed window resizing causing trouble while waiting for "-- more --" message. + +Don't wait for a return after abandoning the command-line. +Fixed extra wait_return() after error message on starting up. + +Fixed matching braces inside quotes for "%". + +Fixed "illegal line number" error when doing ":e!" after adding some lines at +the end of the file. + +Fixed ":map #1 :help" not working. + +Fixed abbreviations not working properly. + +Fixed calling free() in buf_write() when smallbuf[] is used: could cause big +problems when almost out of memory. + +Removed double redraw when returning from ":stop" command. + +After recovering there was one extra empty line at the end. + +Fixed core dump when reporting number of buffers deleted. +Fixed core dump when deleting the current buffer. + +Fixed cursor in wrong position after ":set invwrap" and cursor was below a +long line. + +Fixed ":split file" causing a "hit return" message. + +Fixed core dump when screen is made bigger while in --more-- mode. + +Fixed "@:" executing last Ex command without prepending ":". + +Made Visual reselect work after "J" command. + +Fixed problem that 'showcmd' would cause mappings to happen in the wrong mode. +e.g., ":map g 1G^M:sleep 3^M:g" would show ":1G". + +Fixed problem when mapping ends in a character that start a key code; would +wait for other characters for each character. + +The number of lines reported when starting to edit a file was one too much. + +Fixed problem that in some situations the screen would be scrolled up at every +message and when typing on the command-line. + +Removed the screenclear() calls for MS-DOS in fileio.c; some messages got lost +by it. Just use CTRL-L to redraw the screen in the rare case that you get the +"insert disk b: in drive a:" message. + +When doing ":/pat/p" on the last line without 'wrapscan' set, used to start in +line 1 anyway. Now an error message is given and the command aborted. +Don't display "search hit bot" message twice for ":/pat/p" and overwrite it +with any error message. + +When an error was detected in an Ex address (e.g., pattern not found), the +command was executed anyway; now it is aborted. + +Fixed problem that after using Visual block mode, any yank, delete, and tilde +commands would still use the Visual coordinates. Added "block_mode" variable. + +Fixed not always redrawing correctly with ":unhide" command. + +For Unix: Give error message when more than one file name given to Ex commands +that accept only one. + +Fixed: When "+command" argument given to Ex command, wildcards in file name +after it were not correctly expanded. + +Changed most calls to outstr() into msg_outstr(); makes screen output correct +for some cases; e.g., when using "!!cp file", the message for reading the +temporary file was messed up. + +When out of memory because undo is impossible, and 'y' is typed in answer to +the 'continue anyway' question, don't flush buffers (makes Maze macros work on +MS-DOS). + +Sometimes mappings would not be recognized, because the wrong flags in +noremapstr[] were checked. + +Fixed problem with handling concatenated commands after expanding wildcards, +e.g., with ":e bu*c|ls". + +Fixed problem with argument like '#' to a command like ":wnext" causing the +command not to do the right thing, depending on the file name. + +Display file names with msg_outtrans(); control characters will be shown. +Also use transchar() for setting the window title and icon. + +Fixed not resetting the yankbuffer when operator fails or is cancelled with +ESC (e.g., using "p" after ""ad" would still use register 'a'. + +When "[[" or "]]" is used without an operator, put cursor on begin of line. + +Adjust Insstart when using CTRL-T and CTRL-D in Insert mode; would not be able +to backspace over some characters. + +":unhide" could redisplay some buffers at the wrong position; added call +to cursupdate() to enter_buffer(). + +Fixed freeing memory twice when opening of memfile fails. + +After "z.", screen was displayed one line down compared to other positioning. + +Fixed "%<" and "#<" in the command line not working. + +Fixed two bugs in "2cc". The auto-indent was taken from the last line instead +of the first. When the cursor was on the last character in the line, it would +be positioned on the first char of the next line. + +Fixed bug with doing "cc" on last few lines of the file; would delete wrong +line. + +Fixed problem with formatting when char in column to break is a CTRL-M. +Replaced isspace() by iswhite(). + +Call starttermcap() in wait_return() only when redraw is done. Moved calling +starttermcap() to avoid switching screens before calling wait_return(). +Fixed calling wait_return() on the wrong screen if T_KS switches between two +screens. Would hide the result of commands like ":!ls". + +Fixed screen not being updated when a command typed in response to +wait_return(). Don't update screen when '/' or '?' typed, just like with ':'. +Fixed extra call to wait_return() when hitting "/". + +When splitting window, don't copy scroll option from other window; avoids +"invalid scroll size" error message. + +Adjusting marks for undo/redo was one line off. + +Fixed extra space after cursor in Insert mode when 'wrap' is off and the +cursor is at the end of the line. + +Fixed bug: In some cases with multiple windows, the 'tabstop' and 'list' +option of the wrong window would be used. + +Fixed problem with char_avail() causing real_State to have the wrong value, +causing u_sync() to be called while in Insert mode, causing undo not to +function correctly. + +Fixed horizontal scrolling not working properly when 'number' option set. + +Fixed bug with automatic formatting: wouldn't wrap when there was only a +single non-blank in the first column. + +Fixed problem of displaying the inverted area for Visual block mode. Happens +when moving up-down stays in the same column in the line but the screen column +changes (e.g., when moving over a TAB). + +Delete temp files from filter command when writing to the temp file fails. + +When not being able to read the input in unix.c, could get an unexpected exit. +Added retry for 100 times and flush files before exit. + +Fixed redisplaying problem: When ":" hit in wait_return(), a window would be +redrawn if it has the current buffer but is not the current window. This +would result in the output of the ":" command being overwritten. + +Don't give error messages for setting terminal options in vimrc for "vim -r". + +When doing "2CTRL-^" if current buffer is 2, don't do anything (used to move +the cursor to another line). + +Fixed horizontal scrolling problem with ":set nowrap ss=1 nu". + +Fixed undo not working when using CTRL-V in Insert mode and automatic wrapping +occurs. + +When setting terminal to raw mode, always do it. Always set terminal to raw +mode after executing an external program. Fixes problem when external program +unexpectedly switches terminal to cooked mode. When terminal is in not-raw +mode, don't set it again, it causes problems. + +":[range]yank" moved the cursor; now it leaves it where it was. + +When undoing ":[range]d", ":[range]>", or ":[range]<", put cursor on first +undone line. + +Fixed abbreviations behaving tail-recursively. Fixed abbreviations being +applied while inserting a register in Insert mode with CTRL-R. + +Fixed "-- INSERT --" message being deleted when entering CTRL-R ESC in Insert +mode. + +Fixed bug when 'showmode' on: When using CTRL-E in Visual mode the message +was deleted one out of two times. + +Corrected use of CTRL-@ (NUL) in cmdline editing: Inserted a NL, now K_ZERO. + +Fixed messages for CTRL-O in Insert mode; error messages for "CTRL-O :" were +overwritten. + +Fixed wait-for-return when executing a filter command when 'autowrite' is set. +Don't redisplay the filter command, as it will be overwritten anyway. + +When there is no file name and executing a filter command, don't use the file +name of the temporary file for the current file. + +Fixed ":!!" not working. + +Discovered that some versions of strchr() and strrchr() can't handle +characters above 127. Made a Vim version of these functions. Fixes problems +with special keys and mouse with DJGPP. + +For DJGPP use _dos_commit() instead of dup()/close(). + +Removed the define VIM_ISSPACE, added vim_isspace(), which is always used. +It's not worth risking trying the compiler's isspace(). + +Removed NO_FREE_NULL defined, always use vim_free() instead of free(). There's +no need to take a risk by assuming that free() can handle NULL pointers. + +Fixed not accepting '!' with ":file". Fixed 'shortmess' set to 1 if setting +file name would fail. + +Fixed: When Visual block mode started on an empty line, the block was always at +least two characters wide. + +Fixed: Doing "*" on a special character (e.g., CTRL-K) would not work. + +Renamed variable names that are keywords for some compilers: "new", "old", +"delete", and "class". + +Fixed a few screen redisplay bugs: When opening a new line with "o" on the +last-but-one line in the last-but-one window, the next window would be +scrolled down one line. When appending a character to the last line on the +screen, it would not get updated until after the second character typed. When +deleting the last line in a window, the windows below it (or the status line) +would get scrolled up. + +Fixed bug: Screen was scrolled two lines instead of one when cursor was moved +to one line above the top line (with 'so' = 0 and 'sj' = 0). + +Fixed: ":mkexrc" didn't handle special keys in mappings correctly. +Add "version 4.0" to the start of the file generated with ":mkvimrc". +Add a backslash before a '"' for ":mkexrc" and ":mkvimrc". +Don't write 'endofline', 'scroll', and 'ttyfast' with ":mkvimrc" or ":mkexrc". +Fixed: ":mkvimrc" must put a backslash before a backslash for options values. +A mapping that contains a didn't work properly, now the "" is used +instead of a CTRL-V before the end of the line. An option that contains a + needs a backslash. + +Fixed other windows on the current buffer not being updated if undo resulted +in an unmodified buffer. + +Fixed: When inserting chars in the last line on the screen, making it too long +to fit on the screen, it made the line disappear for a moment. + +Fixed display not being redrawn at cursor position with ":buf 1". + +Fixed putting a blank instead of a reversed blank between the file name and +the ruler in the status line when the file name was truncated. + +Fixed: When doing "vlllu" with 'showcmd', the "-- VISUAL --" message would not +be deleted. + +Give error message if lalloc() is called with a size <= 0. + +If a mapping contains a CTRL-V in "from" or "to" part, don't interpret the +following bytes as terminal codes. Makes mapping ":map q :s/hi ^V^V^H" work. + +Unix: Fixed problem where an invalid setting for WINDOWID would cause Vim to +exit immediately. + +Unix: Re-open stdout and stderr when executing a shell for command-line +completion. Makes it work on Alphas and with bash. + +Fixed problem with scrolling for hpterm (scroll reverse doesn't work as +expected). + +Fixed bug: ":d 2" on last line in buffer gave "ml_get: invalid lnum" errors +and undo didn't work correctly. + +Unix: When termio.h and termios.h are both present, use termios.h. + +Message "[no write since last change]" for shell command was not put in column +1. + +Fixed bug: "Hit return to ..." message was overwriting the output of a shell +command when 'cmdheight' was bigger than 2. + +Unix: Use utsname() if it is present for mch_get_host_name(). + +Fixed "?pat?e", "/pat/s-2", and "?pat?e+2" getting stuck on a match. + +Only for Unix is the short file name used for reading/writing the file when +":cd" has not been used. On MS-DOS and Win32, a "cd" in a sub-shell also +changes the current directory for Vim, which causes the file to be written +in the wrong directory. + +Fixed not removing the highlighted area when a buffer has several windows with +"v...y". + +Made it possible to set number options to a negative number (for 'undolevels'). + +Fixed hang in Insert mode for using cursor keys that start with ESC with a +slow terminal, caused by using read with zero length. + +Fixed a CTRL-W in Insert mode on the last line of the window causing a +scroll-up. + +MS-DOS: Fixed a bug in msdos.c that wrote over the end of allocated memory +when expanding file names; could cause all kinds of problems. + +Amiga: Sort each wildcard component separately, not all together. + +Fixed serious performance problem: When abandoning a file, the freed memfile +blocks were not counted, causing all blocks to be flushed to disk after +abandoning more than 2Mb worth of files. + +Fixed one-second delay between "Unknown options" and ": opt" for autocommand +errors. + +After doing ":set term=name", output t_me and redraw the screen; makes any new +color settings work (just hitting CTRL-L doesn't do this). + +When 'updatecount' was set to zero, every character typed would cause a +sync(), instead of not syncing at all. + +Fixed display not updating correctly when joining lines, e.g., "v11jJ". +With ":join", leave cursor on first non-blank in the line. + +Use #define SIZEOF_INT instead of sizeof(int) in fileio.c; avoids warning +"statement not reached" for some compilers. + +Fixed obscure hang in mch_inchar() in unix.c. + +Added a set of digraphs for HPUX. + +When quickfix does not find a complete match, reset the line number to 0; +avoid a jump to a line that could have been any number in the output of the +'makeprg'. + +Fixed operator with "}", when end is on last line in the file and it's empty. +The last line was included instead of excluded. + +Fixed bug: When ":g/pat/d" resulted in an empty file, undo only put one line +back! Could happen with many other operations that result in an empty file. + +Changed exit status when shell cannot be executed from 127 to 122, some shells +(bash) are already using 127. Makes error message "cannot execute shell" +disappear when in fact the command could not be executed, e.g., for ":!burp". + +When there is no file name and executing a filter command, don't use the file +name of the temporary file for the current file. + +Fixed "w" on last char in the file (not being an empty line) not beeping. + +Fixed cursor not being positioned correctly, when resizing the window in +Normal mode and the cursor is on a TAB. + +Give beep when trying to use "I" after an operator, without first moving the +cursor to the start of the line. + +When listing options, check length of number options too, 'maxmapdepth' set to +9999999 would show 999999. Also account for replacing the home directory when +checking the length of expandable string options. + +'showmatch': Only show match when visible, not when 'wrap' off and match is +left or right of the screen. + +Fixed: Message "freeing N lines" would cause a hit-return message when an +(error) message has been given before it. + +Fixed bug: "(" would get stuck at a line ending in ".}". "]" after line end +was not recognized. + +Fixed reported problem with MS-DOS and Amiga when using more than 32000 lines; +replaced int with long in memline.c. + +Fixed problem when using a negative count to a quickfix command, e.g., +":cn -1". + +Give error message when extra argument given for ":cnext", ":cprevious", or +":cc". + +"@%" and "@." used register 0. Now "@%" gives an error message and "@." +correctly executes the previously inserted text. + +Fixed: "|j" moved cursor to last char in the line. + +Fixed bug for MS-DOS: Expanding "~3" would make Vim hang. + +When 'insertmode' set, don't give the warning "changing a readonly file" when +entering insert mode, but only when actually changing something. + +Made deleting with "D" more efficient. Was deleting one character at a time, +which is very slow for long lines. + +When using the "-oN" command line option, make windows equal height after +loading the buffers. "vim -o5 *.h" looked weird. + +When there is no file name, ":rew", ":next", and others gave misleading error +message. + +Fixed: When deleting text, start pos in col 0 and end pos in col 0, with last +character included, the yank became linewise. This should not be done when +the movement is inclusive. + +Changed: Only give "output not to a terminal" message when going to do +something full screen. Don't ask to hit return for GUI help when output +doesn't come from or go to a terminal (for "vim -h | more"). + +Fixed: CTRL-Z/fg made windows equal height; should only be done when the +window height has changed. + +Give error message for ":set tw=0,wrap"! + +When checking for an existing file, don't use fopen(fname, "r"), but stat(). +It's a bit faster and more reliable (e.g., when the file exists but it does not +have read permission). + +When reading a script file, undo undoes everything that the script did (after +starting to edit the current file). + +Command line completion: just after non-alphabetic commands now expands +like there was a space after the command (e.g., :!). + +Unix: Don't accept expanded file names that don't exist. Helps when using +/bin/sh (would expand "foo" to "foo\*"). + +Fixed: ":recover" didn't work correctly when cursor was not on line 1. +Also: Don't clear the current buffer when ":recover" runs into an error that +prevents recovering (e.g., when .swp file is empty). + +Unix: When executing a filter command, switch terminal to cooked mode. This +makes it possible that the filter can be killed with CTRL-C. You might lose +typeahead on some systems. + +When using CTRL-G in visual mode, show the file message until a key is hit. +Would be overwritten with the mode message immediately. + +Fixed: ":unabbreviate" was before ":undo", should be the other way around, +because ":u" and ":un" mean ":undo". + +Fixed: When using CTRL-L on the command line and there are several matches, +beep_flush() would be called, causing any mapped characters to be flushed. +This breaks the mapping ":cmap ^X ^L^D", that simulates tcsh's autolist +completion. + +Fixed: Could enter numbers larger than 255 with CTRL-V{digits}. Entering +CTRL-V256 would cause a crash a little later. + +When writing a file with ":w!", always reset the 'readonly' option, not only +for Unix and not only when the file is not writable. Changed error message +when trying to write a buffer with 'readonly' set to "'readonly' option is set +(use ! to override)". + +Fixed: "gf" did an autowrite, even though no valid file name was found. + +Fixed: ":so! $VAR/file" didn't work, although ":so $VAR/file" works. + +Fixed: Putting non-alpha garbage after an option name in a ":set" command +wasn't recognized; e.g., with ":set ts-8". + +Fixed: Would use NULL pointer in out-of-memory situtations in mf_alloc_bhdr(). + + + vim:ts=8:sw=8:js:tw=78: diff --git a/usr.bin/vim/doc/vim_ami.txt b/usr.bin/vim/doc/vim_ami.txt new file mode 100644 index 00000000000..6366564c2a5 --- /dev/null +++ b/usr.bin/vim/doc/vim_ami.txt @@ -0,0 +1,68 @@ +*vim_ami.txt* For Vim version 4.2. Last modification: 1996 Apr 19 + +This file contains the particularities for the Amiga version of Vim. + +Installation on the Amiga: +- Assign "vim:" to the directory where the vim_help.txt file is (for the help + command). +- With DOS 1.3 or earlier: Put "arp.library" in "libs:". Make sure that + newcli and run are in "c:" (for executing external commands). +- Put a shell that accepts a command with "-c" (e.g. "Csh" from Fish disk + 624) in "c:" or in any other directory that is in your search path (for + executing external commands). + +If you have sufficient memory you can avoid startup delays by making Vim and +csh resident with the command "rez csh vim". You will have to put +"rezlib.library" in your "libs:" directory. Under 2.0 you will need rez +version 0.5. + +If you do not use digraphs, you can save some memory by recompiling without +the DIGRAPHS option. If you want to use Vim with other terminals you can +recompile with the TERMCAP option. Vim compiles with Manx 5.x and SAS 6.x. +See the makefiles and feature.h. + +If you want to use different colors set the termcap codes: + t_mr (for inverted text) + t_md (for bold text) + t_me (for normal text after t_mr and t_md) + t_so (for standout mode) + t_se (for normal text after t_so) + t_us (for underlined text) + t_ue (for normal text after t_us) + t_ZH (for italic text) + t_ZR (for normal text after t_ZH) + +Standard ANSI escape sequences are used. The codes are: +30 grey char 40 grey cell >0 grey background 0 all attributes off +31 black char 41 black cell >1 black background 1 boldface +32 white char 42 white cell >2 white background 2 faint +33 blue char 43 blue cell >3 blue background 3 italic +34 grey char 44 grey cell >4 grey background 4 underscore +35 black char 45 black cell >5 black background 7 reverse video +36 white char 46 white cell >6 white background 8 invisible +37 blue char 47 blue cell >7 blue background + +The codes with '>' must be the last. The cell and background color should be +the same. The codes can be combined by separating them with a semicolon. For +example to get white text on a blue background: + :set t_me=^V[0;32;43;>3m + :set t_se=^V[0;32;43;>3m + :set t_ue=^V[0;32;43;>3m + :set t_ZR=^V[0;32;43;>3m + :set t_md=^V[1;32;43;>3m + :set t_mr=^V[7;32;43;>3m + :set t_so=^V[0;31;43;>3m + :set t_us=^V[4;32;43;>3m + :set t_ZH=^V[3;32;43;>3m + +When using multiple commands with a filter command, e.g. + :r! echo this; echo that +Only the output of the last command is used. To fix this you have to group the +commands. This depends on the shell you use (that is why it is not done +automatically in Vim). Examples: + :r! (echo this; echo that) + :r! {echo this; echo that} + +Commands that accept a single file name allow for embedded spaces in the file +name. However, when using commands that accept several file names, embedded +spaces need to be escaped with a backslash. diff --git a/usr.bin/vim/doc/vim_arch.txt b/usr.bin/vim/doc/vim_arch.txt new file mode 100644 index 00000000000..b5d40396146 --- /dev/null +++ b/usr.bin/vim/doc/vim_arch.txt @@ -0,0 +1,41 @@ +*vim_arch.txt* For Vim version 4.2. Last modification: 1996 Mar 6 + +This file contains the particularities for the Archimedes version of Vim. + +============================================================================ +The Archimedes version has not been tested. It probably doesn't work at the +moment, because only patches for Vim version 2.0 were available. +============================================================================ + +vim_hlp.txt needs to be saved as Lib:vimhlp (you should set Lib$Path to be a +path to the directory you want to save this. Your system vimrc file should +be named vimrc in this directory too.) + +Another path to set is Tmp$Path This should be the directory you want vim to +use when it does pipes. + +UnixLib's translations means that local .vimrc files should really be named +/vimrc. Of course vim does these translations, so vim .vimrc will create the +file /vimrc. + +You need a termcap file - I haven't built this in. To install the termcap +file, you need to assign a variable, UnixLib$/etc to your "/etc" directory +and copy the file "arctermcap" as "/etc/termcap" You also need your TERM +variable to be set to acorn0 before you use vim. Note also that I've set the +"cl" command, clear screen to clear to green screen. You can change this if +you want. + +Errorfile handling may not work properly. This is not vim's fault, but +UnixLib's system() and exec() handling. It may be OK for Acorn's cc, but gcc +uses exec() and loses redirections, so gcc -c fred.c 2> errorfile doesn't +generate a valid errorfile. Problem is that this is a biggy within UnixLib +to fix, I think. + +When you call a shell up with :sh and ^Z, you'll get a GOS shell by default. +This means that running another application will exit GOS and throw you back +into vim. I'm working on a bash-like shell for the Arc which will get round +this. + +If I've missed anything, please drop me a line and I'll try to fix it. + +Alun Jones, auj@aber.ac.uk diff --git a/usr.bin/vim/doc/vim_diff.txt b/usr.bin/vim/doc/vim_diff.txt new file mode 100644 index 00000000000..0b3ac232ed3 --- /dev/null +++ b/usr.bin/vim/doc/vim_diff.txt @@ -0,0 +1,722 @@ +*vim_diff.txt* For Vim version 4.2. Last modification: 1996 June 16 + +This is a summary of the differences between VIM and vi. It is not complete. +see also |vim_ref.txt|, look for comments in {}, like "{not in Vi}". + +Vim is mostly POSIX 1003.2-1 compliant. The commands known to be missing +are "Q", ":append", ":change", ":insert", ":open" and ":z". There are +probably a lot of small differences. + + +THE MOST INTERESTING ADDITIONS +============================== + +Vi compatibility. |'compatible'| + Although Vim is 99% Vi compatible, some things in vi can be + considered to be a bug, or at least need improvement. Therefore Vim + behaves a little bit different here and there by default. When the + 'compatible' option is set, all options are given a vi-compatible + value, Vim will behave like the "real" vi as much as possible. The + 'cpoptions' option can be used to set vi compatibility on/off for a + few specific items. |'cpoptions'| + +Support for different systems. + Vim can be used on: + - All Unix systems (it works on all systems it was tested on). + - Amiga (500, 1000, 1200, 2000, 3000, 4000, ...). + - MS-DOS in real-mode (no additional drivers required). + - In protected mode on Windows 3.1 and MS-DOS (DPMI driver required). + - Windows 95 and Windows NT, with support for long file names. + - OS/2 (needs emx.dll) + - Atari MiNT + - Macintosh (although there is no binary available, needs some work) + +Multi level undo. |undo| + 'u' goes backward in time, 'ctrl-R' goes forward again. Set option + 'undolevels' to the number of changes to be remembered (default 1000). + Set 'undolevels' to 0 for a vi-compatible one level undo. Set it to + -1 for no undo at all. + + When all changes in a buffer have been undone, the buffer is not + considered changed anymore. You can exit it with :q, without . + +Multiple windows and buffers. |vim_win.txt| + Vim can split the screen into several windows, each editing a + different buffer or the same buffer at a different location. Buffers + can still be loaded (and changed) but not displayed in a window. This + is called a hidden buffer. Many commands and options have been added + for this facility. + +Repeat a series of commands. |q| + 'q' starts recording typed characters into named register + (append to the register if register name is upper case). A subsequent + 'q' stops recording. The register can then be executed with the + '@' command. This is very useful to repeat a complex action. + +Flexible insert mode. |ins_special_special| + The arrow keys can be used in insert mode to move around in the file. + This breaks the insert in two parts as far as undo and redo is + concerned. + + CTRL-O can be used to execute a single command-mode command. This is + almost the same as hitting , typing the command and hitting 'a'. + +Visual mode. |Visual_mode| + Visual can be used to first highlight a piece of text and then give a + command to do something with it. This is an (easy to use) alternative + to first giving the operator and then moving to the end of the text + to be operated upon. 'v' and 'V' are used to start Visual mode. 'v' + works on characters and 'V' on lines. Move the cursor to extend the + Visual part. It is shown highlighted on the screen. By typing 'o' + the other end of the Visual text can be moved. The Visual text can + be affected by an operator: + d delete + c change + y yank + > or < insert or delete indent + ! filter through external program + = filter through indent + : start ":" command for the Visual lines. + Q format text to 'textwidth' columns (obsolete) + gq format text to 'textwidth' columns + J join lines + ~ swap case + u make lowercase + U make uppercase + +Block operators. |visual_block| + With Visual a rectangular block of text can be selected. Start Visual + with CTRL-V. The block can be deleted ('d'), yanked ('y') or its case + can be changed ('~', 'u' and 'U'). A deleted or yanked block can be + put into the text with the 'p' and 'P' commands. + +Online help system. |:help| + Help is displayed in a window. The usual commands can be used to + move around, search for a string, etc. Tags can be used to jump + around in the help files, just like hypertext links. The ":help" + command takes an argument to quickly jump to the info on a subject. + is the quick access to the help system. The name of the help + index file can be set with the 'helpfile' option. + +Command-line editing and history. |cmdline_editing| + You can insert or delete at any place in the command-line using the + cursor keys. The right/left cursor keys can be used to move + forward/backward one character. The shifted right/left cursor keys + can be used to move forward/backward one word. CTRL-B/CTRL-E can be + used to go to the begin/end of the command-line. + + |cmdline_history| + The command-lines are remembered. The up/down cursor keys can be used + to recall previous command-lines. The 'history' option can be set to + the number of lines that will be remembered. There is a separate + history for commands and for search patterns. + +Command-line completion. |cmdline_completion| + While entering a command-line (on the bottom line of the screen) + can be typed to complete + what example + - command :e + - tag :ta scr + - option :set sc + - option value :set hf= + - filename :e ve + - etc. + + If there are multiple matches, CTRL-N (next) and CTRL-P (previous) + will walk through the matches. works like CTRL-N, but wraps + around to the first match. + + The 'wildchar' option can be set to the character for command-line + completion, is the default. CTRL-D can be typed after an + (incomplete) wildcard; all matches will be listed. CTRL-A will insert + all matches. CTRL-L will insert the longest common part of the + matches. + +Horizontal scrolling. |'wrap'| + If the 'wrap' option is off, long lines will not wrap and only part + of them will be shown. When the cursor is moved to a part that is not + shown, the screen will scroll horizontally. The minimum number of + columns to scroll can be set with the 'sidescroll' option. The "zh" + and "zl" commands can be used to scroll sideways. + +Text formatting. |formatting| + The 'textwidth' option can be used to automatically limit the line + length. This supplements the 'wrapmargin' option of Vi, which was not + very useful. The "gq" operator can be used to format a piece of text + (for example, "gqp" formats the current paragraph). Commands for text + alignment: ":center", ":left" and ":right". + +Edit-compile-edit speedup. |quickfix| + The ":make" command can be used to run the compilation and jump to + the first error. Alternatively Vim can be started with the "-e" + option from the compiler. A file with compiler error messages is + interpreted. Vim starts editing at the first error. + + Each line in the error file is scanned for the name of a file, line + number and error message. The 'errorformat' option can be set to a + list of scanf-like strings to handle output from many compilers. + + The ":cn" command can be used to jump to the next error. + ":cl" lists all the error messages. Other commands are available + (almost the same as with Manx's Z editor on the Amiga). + The 'errorfile' option has the name of the file with error messages. + The 'makeprg' option contains the name of the program to be executed + with the ":make" command. + The 'shellpipe' option contains the string to be used to put the + output of the compiler into the errorfile. + +Improved indenting for C programs |'cindent'| + When the 'cindent' option is on the indent of each line is + automatically adjusted. C syntax is mostly recognized. The indent + for various styles can be set with 'cinoptions'. The keys to trigger + indenting can be set with 'cinkeys'. + + Comments can be automatically formatted. The 'comment' option can be + set to the characters that start and end a comment. This works best + for C code, but also works for e-mail (">" at start of the line) and + other types of text. The "=" operator can be used to re-indent + lines. + +Searching for words in include files |include_search| + The "[i" command can be used to search for a match of the word under + the cursor in the current and included files. The 'include' option + can be set the a pattern that describes a command to include a file + (the default is for C programs). + The "[I" command lists all matches, the "[ CTRL-I" command jumps to + a match. + The "[d", "[D" and "[ CTRL-D" commands do the same, but only for + lines where the pattern given with the 'define' option matches. + +Word completion in Insert mode |ins_completion| + In insert mode "CTRL-N" and "CTRL-P" can be used to find match with + the word before the cursor. This is useful to complete long words. + "CTRL-X CTRL-I" can be used to complete the word before the cursor + from the current and included files. + "CTRL-X CTRL-K" can be used to complete the word before the cursor + from a dictionary file, given with the 'dictionary' option + +Automatic commands |autocommand| + Commands can be automatically executed when reading a file, writing a + file, jumping to another buffer, etc., depending on the file name. + This is useful to set options and mappings for C programs, + documentation, plain text, e-mail, etc. This also makes it possible + to edit compressed files. + +Viminfo |viminfo_file| + The command-line history, marks and registers can be stored in a file + that is read on startup. This can be used to repeat a search command + or command-line command after exiting and restarting Vim. It is also + possible to jump right back to where the last edit stopped with "'0". + The 'viminfo' option can be set to select which items to store in the + .viminfo file. This is off by default. + +Mouse support |mouse_using| + The mouse is supported in the GUI version, in an xterm for Unix, for + MS-DOS, and Win32. It can be used to position the cursor, select the + visual area, paste a register, etc. + +Graphical User Interface (GUI) |gui| + Included support for GUI: menu's, mouse, scrollbars, etc. You can + define your own menus. Better support for CTRL/SHIFT/ALT keys in + combination with special keys and mouse. Currently only with Motif + and Athena interface. + + +IMPROVEMENTS FROM VERSION 3.0 TO VERSION 4.0 +============================================ + +Besides the big changes mentioned here there are an awful lot of small +improvements. For a more complete list see |new_features|. + +New on-line help system |new_help| + Help is now displayed in a window. + Tags can be used to jump around in all the documentation (like + hypertext links). + +Command-line editing improved |new_commandline| + Separate history for search commands. + Improved command-line completion. + +Improved indenting for C programs |new_cindent| + Added 'cindent' option for configurable C indent mode. + The "=" operator can be used to indent lines. + Formatting of comments (also for other languages and e-mail). + +Searching for words in include files |new_include| + Added commands to search for a word in the current file and in + included files. + List the first match, list all matches or jump to a match. + +Word completion in Insert mode |new_complete| + Added commands to complete the word before the cursor in Insert mode. + Matches can be found in the current file, included files and/or a + dictionary. + +Automatic commands |new_autocmd| + Execute commands for a specific file type. + Can be used to set options to a different value for C code, plain + text e-mail, etc. + +Text objects |new_textobj| + After an operator and in visual mode text object commands can be used + to quickly select a word, line or paragraph. + +Options |new_options| + Added '&' after an option: reset to default value. + Added '!' after a boolean option: invert value. + A whole bunch of new options. + Command-line completion can be used to get the old value. + +Support for editing one-line paragraphs |new_para| + When a line is longer than the window width, it can be broken at a + space or other convenient position. + Commands have been added to move the cursor a screen line up/down. + +Usage of key names |new_keys| + Special keys now all have a name like , , etc. + This name is used for mappings, in listings, and many other things. + Added a number of new special keys. + +Viminfo |new_viminfo| + The command-line history, marks and registers can be stored in a file + that is read on startup. + +Compilation improvements |compilation| + Autoconf is used to adjust to different Unix flavors, editing the + Makefile is rarely required. + +Tag support improved |new_tags| + Static tags and Emacs tag files are now supported. + Tag completion works better. + +Improved (error) messages |new_msg| + A lot of error messages include the reason for the error. + The text shown when 'showcmd' is set includes partially typed + mappings. + +Swap file |new_swapfile| + Included checking of the actual file name in a swap file, avoids + giving bogus "swap file exists" messages. + Give more information about the swap file with the "swap file exists" + message and when recovering. + Included catching of deadly signals for Unix. Swap files for + unmodified buffers are deleted, other swap files are preserved before + exiting. + Improved recovery when there are multiple swap files. + +Mouse support |new_mouse| + The mouse is supported in the GUI version, in an xterm for Unix, for + MS-DOS, and Win32. It can be used to position the cursor, select the + visual area, paste a register, etc. + +Graphical User Interface (GUI) |new_gui| + Included support for GUI: menu's, mouse, scrollbars, etc. Currently + only with Motif and Athena interface. + +Support for Windows 95/NT |new_win32| + There is now a version of Vim for Windows NT and Windows 95. It + supports long file names, uses all available memory and many more + enhancements. + There is also a protected mode version, compiled with DJGPP, that + runs under Windows 3.1 and MS-DOS. It supports long file names where + available. + +Miscellaneous new features |new_misc| + Implemented incremental search. + A whole bunch of commands that start with "g": Goto declaration, show + ascii value of character under the cursor, go back to end of word, + reselect last Visual area, etc.. + Added the ":retab" command. Can be used to change the size of a + , replace spaces with a or a with spaces. + Implemented "Vim -r" to list any swap files that can be found. + The "Q" operator no longer affects empty lines. + Added '-' register for deletes of less than one line, see |registers|. + Quickfix: Support for several error formats at the same time. + Added separate mapping for normal mode and visual mode. + Allow for digraphs to be entered in reverse. + Added commands to search for #if/#endif and start/end of comment. + Added ":abclear" and ":mapclear": remove all abbreviations/mappings. + Added check for last modification time of original file before + overwriting it. + +vi compatibility improvements |vi_compat| + A few more things have been made vi-compatible. + The 'cpoptions' option has a few flags to switch specific + vi-compatibility items on/off. + + +OLDER VIM FEATURES +================== + +These features were already present in Vim version 3.0. + + +Vim arguments: + +When Vim is started with "-v" (View) then readonly mode is used (includes +"-n"). + +When Vim is started with "-b" (Binary) then some options are set to be able +to edit binary or executable files. + +When Vim is started with "-s scriptfile", the characters read from +"scriptfile" are treated as if you typed them. If end of file is reached +before the editor exits, further characters are read from the console. + +The "-w" option can be used to record all typed characters in a script file. +This file can then be used to redo the editing, possibly on another file or +after changing some commands in the script file. + +The "-n" option disables the writing of a ".swp" file (see below). + +The "-c command" option does the same as the "+command" option. + +The "-T terminal" option sets the terminal type. + +The "-e" option starts Vim in quickfix mode. + +The "-o" option opens a window for each argument. "-o4" opens four windows. + + +In command mode: + +Missing command: "Q" (go to Ex mode) (but see |pseudo-Q|). +Missing Ex commands: append, change, insert, open and z. + +The command characters are shown in the last line of the screen. They are +removed when the command is finished. If you do not want this (on a slow +terminal) reset the 'showcmd' option. + +If the 'ruler' option is set, the current cursor position is shown in the +last line of the screen. + +'u' and CTRL-R accept a count for the number of undos/redos. + +'U' still works after having moved off the last changed line and after 'u'. + +Nulls in the file are replaced with internally. This allows editing of +binary files (more or less). + +Characters with the 8th bit set are displayed. The characters between '~' and +0xa0 are displayed as "~?", "~@", "~A", etc., unless the "graphic' option is +set. + +'=' is an operator to filter lines through an external command (vi: lisp +stuff). The name of the command can be set with the 'equalprg' option. The +default is "indent". + +'][' goes to the next ending of a C function ('}' in column 1). +'[]' goes to the previous ending of a C function ('}' in column 1). + +']f', '[f' and 'gf' start editing the file whose name is under the cursor. +CTRL-W f splits the window and starts editing the file whose name is under +the cursor. + +'*' searches forward for the identifier under the cursor, '#' backward. +'K' runs the program defined by the "keywordprg" option, with the identifier +under the cursor as argument. + +'%' can be preceded with a count. The cursor jumps to the line that +percentage down in the file. The normal '%' function to jump to the matching +brace skips braces inside quotes. + +With the CTRL-] command, the cursor may be in the middle of the identifier. + +The used tags are remembered. Commands that can be used with the tag stack +are CTRL-T, ':pop' and ':tag'. ':tags' lists the tag stack. + +The 'tags' option can be set to a list of tag file names. Thus multiple +tag files can be used. For file names that start with '.', the '.' is +replaced with the path of the current file. This makes it possible to use a +tags file in the same directory as the file being edited. + +Previously used file names are remembered in the alternate file name list. +CTRL-^ accepts a count, which is an index in this list. + +Search patterns have more features. The character is seen as part of the +search pattern and the substitute string of ":s". Vi sees it as the end of +the command. + +Searches can put the cursor on the end of a match and may include a character +offset. + +Count added to '~', ':next', ':Next', 'n' and 'N'. + +The command ":next!" with 'autowrite' set does not write the file. In vi the +file was written, but this is considered to be a bug, because one does not +expect it and the file is not written with ':rewind!'. + +"5r" replaces five characters with five line breaks. Vi replaces five +characters with a single line break. + +In Vi when entering a in replace mode deletes a character only when 'ai' +is set (but does not show it until you hit ). Vim always deletes a +character (and shows it immediately). + +Added :wnext command. Same as ":write" followed by ":next". + +The ":w!" command always writes, also when the file is write protected. + +If option "tildeop" has been set, '~' is an operator (must be followed by a +movement command). + +With the 'J' (join) command you can reset the 'joinspaces' (js) option to +have only one space after a period (Vi inserts two spaces). + +'cw' can be used to change white space formed by several characters (Vi is +confusing: 'cw' only changes one space, while 'dw' deletes all white space). + +'o' and 'O' accept a count for repeating the insert (Vi clears a part of +display). + +':dis' command shows the contents of the yank registers. + +Previously used file names are remembered in the alternate file name list. +":files" command shows the list of alternate filenames. +'#' is replaced with the th alternate filename in the list. +"#<" is replaced with the current filename without extension. + +Flags after command not supported (no plans to include it). + +On non-UNIX systems ":cd" command shows current directory instead of going to +the home directory. ":pwd" prints the current directory on all systems. + +':source!' command reads Vi commands from a file. + +':mkexrc' command writes current modified options and mappings to a ".exrc" +file. ':mkvimrc' writes to a ".vimrc" file. + +No check for "tail recursion" with mappings. This allows things like +":map! foo ^]foo". + +When a mapping starts with number, vi loses the count typed before it (e.g. +when using the mapping ":map g 4G" the command "7g" goes to line 4). This is +considered a vi bug. Vim concatenates the counts (in the example it becomes +"74G"), as most people would expect. + +The :put! command inserts the contents of a register above the current line. + +The 'p' and 'P' commands of vi cannot be repeated with '.' when the putted +text is less than a line. In Vim they can always be repeated. + +The named register '.' can be used with commands p, P and :put. The contents +of the register is the last inserted text. + +":noremap" command can be used to enter a mapping that will not be remapped. +This is useful to exchange the meaning of two keys. ":cmap", ":cunmap" and +":cnoremap" can be used for mapping in command-line editing only. ":imap", +":iunmap" and ":inoremap" can be used for mapping in insert mode only. +Similar commands exist for abbreviations: ":noreabbrev", ":iabbrev" +":cabbrev", ":iunabbrev", ":cunabbrev", ":inoreabbrev", ":cnoreabbrev". + +In vi the command ":map foo bar" would remove a previous mapping +":map bug foo". This is considered a bug, so it is not included in Vim. +":unmap! foo" does remove ":map! bug foo", because unmapping would be very +difficult otherwise (this is vi compatible). + +":@r" command executes register r (is in some versions of vi). + +The ':' register contains the last command-line. +The '%' register contians the current file name. + +CTRL-O/CTRL-I can be used to jump to older/newer positions. These are the +same positions as used with the '' command, but may be in another file. The +':jumps' command lists the older positions. + +If the 'shiftround' option is set, an indent is rounded to a multiple of +'shiftwidth' with '>' and '<' commands. + +The 'scrolljump' option can be set to the minimum number of lines to scroll +when the cursor gets off the screen. Use this when scrolling is slow. + +The 'scrolloff' option can be set to the minimum number of lines to keep +above and below the cursor. This gives some context to where you are +editing. When set to a large number the cursor line is always in the middle +of the window. + +Uppercase marks can be used to jump between files. The ':marks' command lists +all currently set marks. The commands "']" and "`]" jump to the end of the +previous operator or end of the text inserted with the put command. "'[" and +"`[" do jump to the start. + +The 'shelltype' option can be set to reflect the type of shell used. + +The 'highlight' option can be set for the higlight mode to be used for +several commands. + +The CTRL-A (add) and CTRL-X (subtract) commands are new. The count to the +command (default 1) is added to/subtracted from the number at or after the +cursor. That number may be decimal, octal (starts with a '0') or hexadecimal +(starts with '0x'). Very useful in macros. + +With the :set command the prefix "inv" can be used to invert toggle options. + +In both Vi and Vim you can create a line break with the ":substitute" command +by using a CTRL-M. For Vi this means you cannot insert a real CTRL-M in the +text. With Vim you can put a real CTRL-M in the text by preceding it with a +CTRL-V. + + +In insert mode: + +If the 'revins' option is set, insert happens backwards. This is for typing +Hebrew. When inserting normal characters the cursor will not be shifted and +the text moves rightwards. Backspace, CTRL-W and CTRL-U will also work in +the opposite direction. CTRL-B toggles the 'revins' option. In replace mode +'revins' has no effect. + +The backspace key can be used just like CTRL-D to remove auto-indents. + +You can backspace, ctrl-U and CTRL-W over line breaks if the 'backspace' (bs) +option is set to non-zero. You can backspace over the start of insert if the +'backspace' option is set to 2. + +When the 'paste' option is set, a few option are reset and mapping in insert +mode and abbreviation are disabled. This allows for pasting text in windowing +systems without unexpected results. When the 'paste' option is reset, the old +option values are restored. + +CTRL-T/CTRL-D always insert/delete an indent in the current line, no matter +what column the cursor is in. + +CTRL-@ (insert previously inserted text) works always (Vi: only when typed as +first character). + +CTRL-A works like CTRL-@ but does not leave insert mode. + +CTRL-R <0-9a-z> can be used to insert the contents of a register. + +When the 'smartindent' (si) option is set, C programs will be better +auto-indented. + +CTRL-Y and CTRL-E can be used to copy a character from above/below the +current cursor position. + +After CTRL-V you can enter a three digit decimal number. This byte value is +inserted in the text as a single character. Useful for international +characters that are not on your keyboard. + +When the 'expandtab' (et) option is set, a is expanded to the +appropriate number of spaces. + +The window always reflects the contents of the buffer (Vi does not do this +when changing text and in some other cases). + +If Vim is compiled with DIGRAPHS defined, digraphs are supported. A set of +normal Amiga digraphs is included. They are shown with the :digraph" command. +More can be added with ":digraph {char1}{char2} {number}". A digraph is +entered with "CTRL-K {char1} {char2}" or "{char1} BS {char2}" (only when +'digraph' option is set). + +When repeating an insert, e.g. "10atest " vi would only handle wrapmargin +for the first insert. Vim does it for all. + +A count to the 'i' or 'a' command is used for all the text. Vi uses the count +only for one line. "3iabcdef" would insert "abcabcaccdef" in Vi +but "abcdefabcdefabcdef" in Vim. + + +In Command-line mode: + + terminates the command-line without executing it. In vi the command +line would be executed, which is not what most people expect (hitting +should always get you back to command mode). To avoid problems with some +obscure macros, an in a macro will execute the command. If you want a +typed to execute the command like vi does you can fix this with + ":cmap ^V ^V" + +general: + +Missing options: autoprint (ap), beautify (bf), hardtabs (ht), lisp, mesg, +open, optimize (op), prompt, redraw, slowopen (slow), window, w300, w1200 and +w9600. These options can be set but are otherwise ignored. + +When the 'compatible' option is set, all options are set for maximum +vi-compatibility + +The 'ttimeout' option is like 'timeout', but only works for cursor and +function keys, not for ordinary mapped characters. The 'timoutlen' option +gives the number of milliseconds that is waited for. If the 'esckeys' option +is not set, cursor and function keys that start with are not recognized +in insert mode. + +There is an option for each terminal string. Can be used when termcap is not +supported or to change individual strings. + +When the 'textmode' option is set (default for MS-DOS, OS/2, and Win32) + is used as line separator. When reset (default for Unix and Amiga) + is used. When the 'textauto' option is set, Vim tries to detect the +type of line separator used by reading up to the first . The 'textmode' +option is set accordingly. + +On systems that have no job control (older Unix systems and non-Unix systems) +the CTRL-Z, ":stop" or ":suspend" command starts a new shell. + +If Vim is started on the Amiga without an interactive window for output, a +window is opened (and :sh still works). You can give a device to use for +editing with the '-d' argument, e.g. "-d con:20/20/600/150". + +On startup the VIMINIT or EXINIT environment variables, the file s:.vimrc or +s:.exrc and .vimrc or .exrc are read for initialization commands. When +reading .vimrc and .exrc some commands are not allowed because of security +reasons (shell commands and writing to a file, :map commands are echoed). +This can be overrided with the 'secure' option. + +Line length can be at least up to the maximum value of an int (for the Amiga +32767 characters, for most 32-bit systems much larger). Editing such lines +is not always possible. File length up to 2147483646 lines. If a line is +larger than the screen, the last line is filled with <@>s and only the part +of the line before that is shown (unless 'wrap' option is reset). + +The 'columns' option is used to set or get the width of the display. + +The name of the current file name is shown in the title bar of the window. + +Wildcards in file names are expanded. + +Option settings are read from the first and last few lines of the file. +Option 'modelines' determines how many lines are tried (default is 5). Note +that this is different from the Vi versions that can execute any Ex command +in a modeline (a major security problem). + +If the 'insertmode' option is set (e.g. in .exrc), Vim starts in insert mode. + +All text is kept in memory. Available memory limits the file size (and other +things such as undo). This may be a problem with MS-DOS, is hardly a problem +on the Amiga and almost never with Unix and Win32. + +If the 'backup' or 'writebackup' option is set: Before a file is overwritten, +a backup file (.bak) is made. If the "backup" option is set it is left +behind. + +If the 'binary' option is set and the file does not have an end-of-line for +the last line, the end-of-line is not appended when writing. + +Vim creates a file ending in ".swp" to store parts of the file that have been +changed or that do not fit in memory. This file can be used to recover from +an aborted editing session with "vim -r file". Using the swap file can be +switched off by setting the 'updatecount' option to 0 or starting Vim with +the "-n" option. Use the 'directory' option for placing the .swp file +somewhere else. + +The 'shortname' (sn) option, when set, tells Vim that ".bak" and ".swp" +filenames are to be MS-DOS-like: 8 characters plus 3 for extention. This +should be used on messydos or crossdos filesystems on the Amiga. If this +option is off, Vim tries to guess if MS-DOS filename restrictions are +effective. + +Error messages are shown at least one second (Vi overwrites error messages). + +If Vim asks to "Hit RETURN to continue", you can hit any key. Characters +other than , and are interpreted as the (start of) a +command. (Vi only accepts a command starting with ':'). + +The contents of the numbered and unnamed registers is remembered when +changing files. + +The "No lines in buffer" message is a normal message instead of an error +message, since that may cause a mapping to be aborted. + +The AUX: device of the Amiga is supported. + + vim:tw=77:js: diff --git a/usr.bin/vim/doc/vim_digr.txt b/usr.bin/vim/doc/vim_digr.txt new file mode 100644 index 00000000000..5635e49dbe2 --- /dev/null +++ b/usr.bin/vim/doc/vim_digr.txt @@ -0,0 +1,75 @@ +*vim_digr.txt* For Vim version 4.2. Last modification: 1996 June 8 + +These are the default digraph characters for Vim. + +Besides the digraphs listed below, meta characters can be entered with CTRL-K + {char}. Only cannot be used for {char}. Use CTRL-V 155 to +enter meta-ESC (CSI). + +The first two characters in each column are the characters you have to type to +enter the digraph. + +In the middle of each column is the resulting character. This may be mangled +if you look at this file on something other than the system that they were +meant for or if you print it. + +The decimal number is the ASCII code for the character. + + *digraph_table* +Default digraphs on non-MS-DOS, non-Win32 machines: +~! ¡ 161 c| ¢ 162 $$ £ 163 ox ¤ 164 Y- ¥ 165 || ¦ 166 pa § 167 +"" ¨ 168 cO © 169 a- ª 170 << « 171 -, ¬ 172 -- ­ 173 rO ® 174 +-= ¯ 175 ~o ° 176 +- ± 177 22 ² 178 33 ³ 179 '' ´ 180 ju µ 181 +pp ¶ 182 ~. · 183 ,, ¸ 184 11 ¹ 185 o- º 186 >> » 187 14 ¼ 188 +12 ½ 189 34 ¾ 190 ~? ¿ 191 A` À 192 A' Á 193 A^  194 A~ à 195 +A" Ä 196 A@ Å 197 AE Æ 198 C, Ç 199 E` È 200 E' É 201 E^ Ê 202 +E" Ë 203 I` Ì 204 I' Í 205 I^ Î 206 I" Ï 207 D- Ð 208 N~ Ñ 209 +O` Ò 210 O' Ó 211 O^ Ô 212 O~ Õ 213 O" Ö 214 /\ × 215 O/ Ø 216 +U` Ù 217 U' Ú 218 U^ Û 219 U" Ü 220 Y' Ý 221 Ip Þ 222 ss ß 223 +a` à 224 a' á 225 a^ â 226 a~ ã 227 a" ä 228 a@ å 229 ae æ 230 +c, ç 231 e` è 232 e' é 233 e^ ê 234 e" ë 235 i` ì 236 i' í 237 +i^ î 238 i" ï 239 d- ð 240 n~ ñ 241 o` ò 242 o' ó 243 o^ ô 244 +o~ õ 245 o" ö 246 :- ÷ 247 o/ ø 248 u` ù 249 u' ú 250 u^ û 251 +u" ü 252 y' ý 253 ip þ 254 y" ÿ 255 + + +Default digraphs for some HPUX machines: +A` ¡ 161 A^ ¢ 162 E` £ 163 E^ ¤ 164 E" ¥ 165 I^ ¦ 166 I" § 167 +'' ¨ 168 `` © 169 ^^ ª 170 "" « 171 ~~ ¬ 172 U` ­ 173 U^ ® 174 +L= ¯ 175 ~_ ° 176 Y' ± 177 y' ² 178 ~o ³ 179 C, ´ 180 c, µ 181 +N~ ¶ 182 n~ · 183 ~! ¸ 184 ~? ¹ 185 ox º 186 L- » 187 Y= ¼ 188 +pp ½ 189 fl ¾ 190 c| ¿ 191 a^ À 192 e^ Á 193 o^  194 u^ à 195 +a' Ä 196 e' Å 197 o' Æ 198 u' Ç 199 a` È 200 e` É 201 o` Ê 202 +u` Ë 203 a" Ì 204 e" Í 205 o" Î 206 u" Ï 207 Ao Ð 208 i^ Ñ 209 +O/ Ò 210 AE Ó 211 ao Ô 212 i' Õ 213 o/ Ö 214 ae × 215 A" Ø 216 +i` Ù 217 O" Ú 218 U" Û 219 E' Ü 220 i" Ý 221 ss Þ 222 O^ ß 223 +A' à 224 A~ á 225 a~ â 226 D- ã 227 d- ä 228 I' å 229 I` æ 230 +O' ç 231 O` è 232 O~ é 233 o~ ê 234 S~ ë 235 s~ ì 236 U' í 237 +Y" î 238 y" ï 239 p- ð 240 p~ ñ 241 ~. ò 242 ju ó 243 Pp ô 244 +34 õ 245 -- ö 246 14 ÷ 247 12 ø 248 a_ ù 249 o_ ú 250 << û 251 +xx ü 252 >> ý 253 +- þ 254 nu ÿ 255 + + +Default digraphs on MS-DOS and Win32: +C, € 128 u"  129 e' ‚ 130 a^ ƒ 131 a" „ 132 a` … 133 a@ † 134 +c, ‡ 135 e^ ˆ 136 e" ‰ 137 e` Š 138 i" ‹ 139 i^ Œ 140 i`  141 +A" Ž 142 A@  143 E'  144 ae ‘ 145 AE ’ 146 o^ “ 147 o" ” 148 +o` • 149 u^ – 150 u` — 151 y" ˜ 152 O" ™ 153 U" š 154 c| › 155 +$$ œ 156 Y-  157 Pt ž 158 ff Ÿ 159 a'   160 i' ¡ 161 o' ¢ 162 +u' £ 163 n~ ¤ 164 N~ ¥ 165 aa ¦ 166 oo § 167 ~? ¨ 168 -a © 169 +a- ª 170 12 « 171 14 ¬ 172 ~! ­ 173 << ® 174 >> ¯ 175 ss á 225 +ju æ 230 o/ í 237 +- ñ 241 >= ò 242 <= ó 243 :- ö 246 ~~ ÷ 247 +~o ø 248 22 ý 253 + + +Default digraphs for MiNT: +C, € 128 u"  129 e' ‚ 130 a^ ƒ 131 a" „ 132 a` … 133 a@ † 134 +c, ‡ 135 e^ ˆ 136 e" ‰ 137 e` Š 138 i" ‹ 139 i^ Œ 140 i`  141 +A" Ž 142 A@  143 E'  144 ae ‘ 145 AE ’ 146 o^ “ 147 o" ” 148 +o` • 149 u^ – 150 u` — 151 y" ˜ 152 O" ™ 153 U" š 154 c| › 155 +$$ œ 156 Y-  157 ss ž 158 ff Ÿ 159 a'   160 i' ¡ 161 o' ¢ 162 +u' £ 163 n~ ¤ 164 N~ ¥ 165 aa ¦ 166 oo § 167 ~? ¨ 168 -a © 169 +a- ª 170 12 « 171 14 ¬ 172 ~! ­ 173 << ® 174 >> ¯ 175 ju æ 230 +o/ í 237 +- ñ 241 >= ò 242 <= ó 243 :- ö 246 ~~ ÷ 247 ~o ø 248 +22 ý 253 + vim:ts=8:sw=8:js:tw=78: diff --git a/usr.bin/vim/doc/vim_dos.txt b/usr.bin/vim/doc/vim_dos.txt new file mode 100644 index 00000000000..6e1ba5b8bcd --- /dev/null +++ b/usr.bin/vim/doc/vim_dos.txt @@ -0,0 +1,240 @@ +*vim_dos.txt* For Vim version 4.2. Last modification: 1996 June 13 + +This file contains the particularities for the MS-DOS version of Vim. + +There are Three versions of Vim that can be used with MS-DOS machines: + +16-bit version Can be used on any MS-DOS system, only uses up to + 640 Kbyte of memory. Also runs on OS/2, Windows 95, and NT. +32-bit version Requires 386 processor and a DPMI driver, uses all + available memory. Supports long file names where available. +Win32 version Requires Windows 95 or Windows NT, uses all available + memory, supports long file names, etc. See |vim_w32.txt| + +It is recommended to use the 32-bit or Win32 version. Although the 16-bit +version is able to edit very big files, it quickly runs out of memory when +making big changes. Disabling undo helps: ":set ul=-1". The screen updating +of the 16-bit version is the fastest of the three on DOS or Windows 95; on +Windows NT, the Win32 version is just as fast. + +For the 32-bit version, you may need a DPMI driver when running in MS-DOS. If +you are running Windows or installed a clever memory manager, it will probably +run already. If you get the message "No DPMI", you need to install a DPMI +driver. Such a driver is included with the executable. The latest version of +"CWSDPMI.ZIP" can be obtained from: "ftp.neosoft.com:pub/users/s/sandmann". + +Known problems: +- When using smartdrive (MS-DOS 6.x) with write-behind caching, it is possible + that Vim will try to create a swap file on a read-only file system (e.g. + write protected floppy). You will then be given the message + "A serious disk error has occurred .., Retry (r)? " There is nothing + you can do but unprotect the floppy or switch off the computer. Even + CTRL-ALT-DEL will not get you out of this. This is really a problem of + smartdrive, not Vim. Smartdrive works fine otherwise. If this bothers you, + don't use the write-behind caching. +- The 16 bits MS-DOS version can only have about 10 files open (in a window or + hidden) at one time. With more files you will get error messages when + trying to read or write a file, and for filter commands. +- The 32 bits MS-DOS version runs out of file descriptors when using a command + like ":r!ls"; Vim crashes after about five to twelve tries. + +Using backslashes in file names can be a problem. Vi halves the number of +backslashes for some commands. Vim is a bit more tolerant and backslashes +are not removed from a file name, so ":e c:\foo\bar" works as expected. But +when a backslash is used before a special character (space, comma, backslash, +etc.), it is removed. Use slashes to avoid problems: ":e c:/foo/bar" works +fine. Vim will replace the slashes with backslashes internally, to avoid +problems with some MS-DOS programs. + +The default output method for the screen is to use bios calls. This will work +right away on most systems. You do not need ansi.sys. You can use ":mode" to +set the current screen mode. See vim_ref.txt, section 20.3 |:mode|. + +You can set the color used in five modes with nine termcap options. Which of +the five modes is used for which action depends on the 'highlight' ('hl') +option. See vim_ref.txt |'highlight'|. + + ":set t_mr=^V^[\|xxm" start of invert mode + ":set t_md=^V^[\|xxm" start of bold mode + ":set t_me=^V^[\|xxm" back to normal text + + ":set t_so=^V^[\|xxm" start of standout mode + ":set t_se=^V^[\|xxm" back to normal text + + ":set t_us=^V^[\|xxm" start of underline mode + ":set t_ue=^V^[\|xxm" back to normal text + + ":set t_ZH=^V^[\|xxm" start of italics mode + ":set t_ZR=^V^[\|xxm" back to normal text + +^V is CTRL-V +^[ is +xx must be replaced with a decimal code: The foreground color number and + background color number added together: + +COLOR FOREGROUND BACKGROUND +black 0 0 +blue 1 16 +green 2 32 +cyan 3 48 +red 4 64 +magenta 5 80 +brown 6 96 +lighgray 7 112 +darkgray 8 +lightblue 9 +lightgreen 10 +lighcyan 11 +lightred 12 +lighmagenta 13 +yellow 14 +white 15 +blink 128 + +When you use 0, the color is reset to the one used when you started Vim. This +is the default for t_me. The default for t_mr is black on grey, 0 + 112 = 112. +The default for t_md is white on cyan, 15 + 48 = 63. The default for t_so is +white on blue, 15 + 16 = 31. These colors were chosen, because they also look +good when using an inverted display. But you can change them as you like. + +The termcap codes that are translated into bios calls are: + t_cl |J clear screen + t_ce |K clear to end of line + t_al |L insert line + t_dl |M delete line + t_cm |{row};{col}H position cursor + t_cs |{row};{row}r set scrolling region + t_.. |attrm set character attribute + +If you set these with the ":set" command, don't forget to put a backslash +before the '|', otherwise it will be recognized as the end of the command. + +If you want to use another output method (e.g., when using a terminal on a COM +port), set the terminal name to "pcansi". You can change the termcap options +when needed (see chapter 20 of vim_ref.txt |terminal_options|). Note that the +normal IBM ansi.sys does not support all the codes of the builtin pcansi +terminal. If you use ansi.sys, you will need to delete the termcap entries +t_al and t_dl with + ":set t_al= t_dl=". +Otherwise, the screen will not be updated correctly. It is better to use +nansi.sys, nnansi.sys, or the like instead of ansi.sys. + +If you want to use Vim on a terminal connected to a COM: port, reset the +'bioskey' option. Otherwise the commands will be read from the PC keyboard. +CTRL-C and CTRL-P may not work correctly with 'bioskey' reset. + +If the "tx" (textmode) option is set (which is the default), Vim will accept +a single or a pair for end-of-line. When writing a file, Vim +will use . Thus, if you edit a file and write it, is replaced +with . If the "tx" option is not set, the single will be used +for end-of-line. A will be shown as ^M. You can use Vim to replace + with by reading in any mode and writing in text mode (":se +tx"). You can use Vim to replace with by reading in text mode +and writing in non-text mode (":se notx"). 'textmode' is set automatically +when 'textauto' is on (which is the default), so you don't really have to +worry about what you are doing. + |'textmode'| |'textauto'| + +If you want to edit a script file or a binary file, you should reset the +'textmode' and 'textauto' options before loading the file. Script files and +binary files may contain single characters which would be replaced with +. You can reset 'textmode' and 'textauto' automatically by starting +Vim with the "-b" (binary) option. + +You should set the environment variable "VIM" to the directory where the Vim +documentation files are. If "VIM" is used but not defined, "HOME" is tried +too. + +If the HOME environment variable is not set, the value "C:/" is used as a +default. + +The default help filename is "$VIM\vim_help.txt". If the environment variable +$VIM is not defined or the file is not found, the DOS search path is used to +search for the file "vim_help.txt". If you do not want to put "vim_help.txt" +in your search path, use the command ":set helpfile=pathname" to tell Vim +where the help file is. |'helpfile'| + +Vim will look for initializations in eight places. The first that is found +is used and the others are ignored. The order is + - The environment variable VIMINIT + - The file "$VIM/_vimrc" + - The file "$HOME/_vimrc" + - The file "$VIM/.vimrc" + - The file "$HOME/.vimrc" + - The environment variable EXINIT + - The file "$VIM/_exrc" + - The file "$HOME/_exrc" + +The ":cd" command recognizes the drive specifier and changes the current +drive. Use ":cd c:" to make drive C the active drive. Use ":cd d:\dos" to go +to the directory "dos" in the root of drive D. |:cd| + +Use CTRL-break instead of CTRL-C to interrupt searches. The CTRL-C is not +detected until a key is read. + +Use CTRL-arrow-left and CTRL-arrow-right instead of SHIFT-arrow-left and +SHIFT-arrow-right. The arrow-up and arrow-down cannot be used with SHIFT or +CTRL. + +Temporary files (for filtering) are put in the current directory. + +The default for the sh (shell) option is "command". If COMSPEC is defined it +is used instead. External commands are started with "command /c +". Typing CTRL-Z starts a new command shell. Return to Vim with +"exit". + +MS-DOS allows for only one filename extension. Therefore, when appending an +extension, the '.' in the original filename is replaced with a '_', the name +is truncated to 8 characters, and the new extension (e.g., ".swp") is +appended. Two examples: "test.c" becomes "test_c.bak", "thisisat.est" +becomes "thisisat.bak". To reduce these problems, the default for +'backupext' is "~" instead of ".bak". The backup file for "thisisat.est" +then becomes "thisisat.es~". The 'shortname' option is not available, +because it would always be set. + +If the 32-bit DOS version is run on Windows 95 or Windows NT, it can use long +filenames, as can the Win32 version. + +The MS-DOS binary was compiled with Borland-C++ version 4.0, using +makefile.bcc. Other compilers should also work. Use makefile.dos for +Turbo-C 2.0. Use makefile.bcc for other Borland compilers, also Turbo-C++ +3.0 (with small changes). If you get all kinds of strange error messages +when compiling, try adding characters at the end of each line. This can +be done with the addcr program: "make addcr". This will compile addcr.c to +addcr.exe and then execute the addcr.bat file. Sometimes this fails. In +that case, execute the addcr.bat file from the DOS prompt. + + +The "spawno" library by Ralf Brown was used in order to free memory when Vim +starts a shell or other external command. Only about 200 bytes are taken from +conventional memory. When recompiling get the spawno library from Simtel, +directory "msdos/c". It is called something like "spwno413.zip". Or remove +the library from the makefile. + +A swap file is used to store most of the text. You should be able to edit +very large files. However, memory is used for undo and other things. If you +delete a lot of text, you can still run out of memory in the 16-bit version. + +In the 16-bit version the line length is limited to about 32000 characters. +When reading a file the lines are automatically split. But editing a line +in such a way that it becomes too long may give unexpected results. + +If Vim gives an "Out of memory" warning, you should stop editing. The result +of further editing actions is unpredictable. Setting 'undolevels' to 0 saves +some memory. Running the maze macros on a big maze is guaranteed to run out +of memory, because each change is remembered for undo. In this case set +'undolevels' to a negative number. This will switch off undo completely. + +In the 32-bit version, extended memory is used to avoid these problems. + +About using Vim to edit a symbolically linked file on a Unix NFS file server: +When writing the file, Vim does not "write through" the symlink. Instead, it +deletes the symbolic link and creates a new file in its place. + On Unix, Vim is prepared for links (symbolic or hard). A backup copy of +the original file is made and then the original file is overwritten. This +assures that all properties of the file remain the same. On non-Unix systems, +the original file is renamed and a new file is written. Only the protection +bits are set like the original file. However, this doesn't work properly when +working on an NFS-mounted file system where links and other things exist. The +only way to fix this in the current version is not making a backup file, by +":set nobackup nowritebackup" |'writebackup'| diff --git a/usr.bin/vim/doc/vim_gui.txt b/usr.bin/vim/doc/vim_gui.txt new file mode 100644 index 00000000000..755240937a7 --- /dev/null +++ b/usr.bin/vim/doc/vim_gui.txt @@ -0,0 +1,509 @@ +*vim_gui.txt* For Vim version 4.2. Last modification: 1996 June 11 + + + Vim's Graphical User Interface + + + 1. Introduction *gui_intro* *gui* *gvim* + +1.1 How to Start the GUI Version of Vim *gui_start* *GUI* + +First you must make sure you actually compile Vim with the GUI code +included. You can check this with the ":version" command, it should include +"+GUI_Motif" or "+GUI_Athena". Then you may run the GUI version of Vim in +either of these ways: + gvim [options] [files...] + vim -g [options] [files...] + +So if you call the executable "gvim", or make "gvim" a link to the +executable, then the GUI version will automatically be used. You may also +start up the GUI from within the terminal version by using one of these +commands: + :gui [+cmd] [-f] [files...] *:gu* *:gui* + :gvim [+cmd] [-f] [files...] *:gv* *:gvim* + + *gui_fork* +When the GUI is started, it does a fork() and exits the current process. +When gvim was started from a shell this makes the shell accept further +commands. If you don't want this (e.g. when using gvim for a mail program +that waits for gvim to exit), start gvim with "gvim -f", "vim -gf" or use +":gui -f". Don't use "vim -fg", because "-fg" specifies the foreground +color. If you want the GUI to run in the foreground always, include the 'f' +flag in 'guioptions'. |-f|. + +When the GUI starts up, the file ~/.gvimrc is sourced if it exists, and then +the file ./.gvimrc. This file may contain commands to set up your own +customised menus (see |:menu|) and initialise other things that you may want +to set up differently from the terminal version. + +There are a number of options which only have meaning in the GUI version of +Vim. These are 'guifont', 'guipty' and 'guioptions'. They are documented +in |vim_ref.txt| with all the other options. + + +1.2 GUI Resources *gui_resources* + +If using the Motif or Athena version of the GUI (which are the only versions +available at the moment), a number of X resources are available. You should +use Vim's class "Vim" when setting these. They are as follows: + + Resource name Meaning + + background Color of background. + foreground Color of normal text. + boldColor Color of bold text. + italicColor Color of italic text. + underlineColor Color of underlined text. + cursorColor Color of the cursor. + font Name of font used for normal text. + boldFont Name of font used for bold text. + italicFont Name of font used for italic text. + boldItalicFont Name of font used for bold, italic text. + geometry Initial geometry to use for gvim's window (default + is same size as terminal that started it). + scrollbarWidth Thickness of scrollbars. + menuHeight Height of the menu bar. + borderWidth Thickness of border around text area. + reverseVideo Boolean: should reverse video be used? + menuBackground Color of menu backgrounds. + menuForeground Color of menu foregrounds. + scrollBackground Color of trough portion of scrollbars. + scrollForeground Color of slider and arrow portions of scrollbars. + +A special font for italic, bold, and italic-bold text will only be used if +the user has specified one via a resource. No attempt is made to guess what +fonts should be used for these based on the normal text font at the moment. + +Note: All fonts must be of the same size!!! If you don't do this, text will +disappear. Vim does not check the font sizes. + +If any of these things are also set with Vim commands, eg with +":set guifont=Screen15", then this will override the X resources (currently +'guifont' is the only option that is supported). + +Here is an example of what you might put in your ~/.Xdefaults file: + +Vim*useSchemes: all +Vim*sgiMode: true +Vim*useEnhancedFSB: true +Vim.foreground: Black +Vim.background: Wheat +Vim.boldColor: Blue +Vim.italColor: Cyan +Vim.underlineColor: Brown +Vim.cursorColor: DarkGreen +Vim*menuBar*font: 7x13 + +The first three of these are standard resources on Silicon Graphics machines +which make Motif applications look even better, highly recommended! + +Don't use "Vim*geometry" in the defaults. This will break the menus. Use +"Vim.geometry" instead. + +The resources can also be set with arguments to vim: + + argument meaning *-gui* + + -display Run vim on *-display* + -iconic Start vim iconified *-iconic* + -background Use for the background *-background* + -bg idem *-bg* + -foreground Use for normal text *-foreground* + -fg idem *-fg* + -bold Use for bold text *-bold* + -italic Use for italic text *-italic* + -underline Use for underlined text *-underline* + -ul idem *-ul* + -cursor Use for cursor *-cursor* + -font Use for normal text *-font* + -fn idem *-fn* + -boldfont Use for bold text *-boldfont* + -italicfont Use for italic text *-italicfont* + -geometry Use for initial geometry *-geometry* + -geom idem *-geom* + -borderwidth Use a border width of *-borderwidth* + -bw idem *-bw* + *-scrollbarwidth* + -scrollbarwidth Use a scrollbar width of + -sw idem *-sw* + -menuheight Use a menu bar height of *-menuheight* + -mh idem *-mh* + -reverse Use reverse video *-reverse* + -rv idem *-rv* + +reverse Don't use reverse video *-+reverse* + +rv idem *-+rv* + -xrm Set the specified resource *-xrm* + + + 2. Scrollbars *gui_scrollbars* + +There are vertical scrollbars and a horizontal scrollbars. You may +configure which ones appear with the 'guioptions' option. + +The interface looks like this (with ":set guioptions=mlrb"): + + +------------------------------+ + | File Edit Help | <- Menu bar (m) + +-+--------------------------+-+ + |^| |^| + |#| Text area. |#| + | | | | + |v|__________________________|v| + Normal status line -> |-+ File.c 5,2 +-| + between Vim windows |^|""""""""""""""""""""""""""|^| + | | | | + | | Another file buffer. | | + | | | | + |#| |#| + Left scrollbar (l) -> |#| |#| <- Right + |#| |#| scrollbar (r) + | | | | + |v| |v| + +-+--------------------------+-+ + | |< #### >| | <- Bottom + +-+--------------------------+-+ scrollbar (b) + +Any of the scrollbar or menu components may be turned off by not putting the +appropriate letter in the 'guioptions' string. The bottom scrollbar is +only useful when 'nowrap' is set. + + +2.1 Vertical Scrollbars *gui_vert_scroll* + +Each Vim window has a scrollbar next to it which may be scrolled up and down +to move through the text in that buffer. The size of the scrollbar-thumb +indicates the fraction of the buffer which can be seen in the window. +When the scrollbar is dragged all the way down, the last line of the file +will appear in the top of the window. + + +2.2 Horizontal Scrollbars *gui_horiz_scroll* + +The horizontal scrollbar (at the bottom of the Vim GUI) may be used to +scroll text sideways when the 'wrap' option is turned off. The +scrollbar-thumb size is such that the text of the current cursor line may be +scrolled as far as possible left and right. + + + 3. Mouse Control *gui_mouse* + +The mouse only works if the appropriate flag in the 'mouse' option is set. +When the GUI is switched on the 'mouse' option is set to "a", enabling +it for all modes except for the "hit return to continue" message. This can +be changed from the "gvimrc" file. + + +3.1 Moving Cursor with Mouse *gui_mouse_move* + +Click the left mouse button where you want the cursor to go, and it does! +This works in Normal mode (if 'mouse' contains 'n' or 'a'), Visual mode (if +'mouse' contains 'v' or 'a') and Insert mode (if 'mouse' contains 'i' or +'a'), if you click the mouse somewhere in a text buffer. You may use this +with an operator such as 'd' to delete text from the current cursor position +to the position you point to with the mouse. That is, you hit 'd' and then +click the mouse somewhere. If you are on the ':' line (or '/' or '?'), then +clicking the left or right mouse button will position the cursor on the ':' +line (if 'mouse' contains 'c' or 'a'). In any situation the middle mouse +button may be clicked to paste the current selection. + + +3.2 Visual Selection with Mouse *gui_mouse_select* + +The right mouse button may be clicked somewhere to extend the visual +selection to the position pointed to with the mouse. If already in Visual +mode then the closest end will be extended, otherwise Visual mode is started +and extends from the old cursor position to the new one. + +Double clicking may be done to make the selection word-wise, triple clicking +makes it line-wise, and quadruple clicking makes it rectangular block-wise. + +Visual selection, however it is invoked, makes Vim the owner of the +windowing system's primary selection, so that the highlighted text may be +pasted into other windows. Similarly, selected text from other windows may +be pasted into Vim in Normal mode, Insert mode, or on the ':' line by +clicking the middle mouse button. + + +3.3 Other Text Selection with Mouse *gui_mouse_xterm_like* + +When in Command-line mode, at the hit-return prompt or whenever the current +mode is not in the 'mouse' option, a different kind of selection is used. +It is more like what happens in an xterm. Let's call this xterm-like +selection. Any text in the Vim window can be selected. Select the text by +pressing the left mouse button at the start, drag to the end and release. +Right mouse button extends the selection. Middle mouse button pasts the +text. + + +3.4 Using Mouse on Status Lines *gui_mouse_status* + +Clicking the left or right mouse button on the status line below a Vim +window makes that window the current window. The mouse may then be dragged +up and down to move the status line, thus resizing the windows above and +below it. + + +3.5 Various Mouse Clicks *gui_mouse_various* + + Search forward for the word under the mouse click. + Search backward for the word under the mouse click. + Jump to the tag name under the mouse click. + Jump back to position before the previous tag jump + (same as "CTRL-T") + + +3.6 Mouse Mappings *gui_mouse_mapping* + +The mouse events, complete with modifiers, may be mapped. Eg: + :map + :map + :map + :map <2-S-LeftMouse> <2-RightMouse> + :map <2-S-LeftDrag> <2-RightDrag> + :map <2-S-LeftRelease> <2-RightRelease> + :map <3-S-LeftMouse> <3-RightMouse> + :map <3-S-LeftDrag> <3-RightDrag> + :map <3-S-LeftRelease> <3-RightRelease> + :map <4-S-LeftMouse> <4-RightMouse> + :map <4-S-LeftDrag> <4-RightDrag> + :map <4-S-LeftRelease> <4-RightRelease> +These mappings make selection work the way it probably should in a Motif +application, with shift-left mouse allowing for extending the visual area +rather than the right mouse button. + +Mouse mapping with modifiers does not work for xterm-like selection. + + + 4. Making GUI Selections *gui_selections* + +You may make selections with the mouse (see |gui_mouse_select|), or by using +Vim's visual mode (see |v|). If 'a' is present in 'guioptions', then +whenever visual mode is invoked, or when the cursor moves while in visual +mode, Vim becomes the owner of the windowing system's primary selection. +There is a special register for storing this selection, it is the <"*> +register. Nothing is put in here unless the information about what text is +selected is about to change (eg with a left mouse click somewhere), or when +another application wants to paste the selected text. Then the text is put +in the <"*> register. Similarly, when we want to paste a selection from +another application, eg by clicking the middle mouse button, the selection +is put in the <"*> register first, and then 'put' like any other register. + +Note that when pasting text from one Vim into another separate Vim, the type +of selection (character, line, or block) will also be copied. + + + 5. Menus *gui_menus* + +5.1 Using Menus *gui_using_menus* + +The default menus are quite simple at the moment. Power Vim users won't use +them much. But the power is in adding your own menus and menu items. They +are most useful for things that you can't remember what the key sequence +was. + +Eventually I would like to expand the set of default menus to use pop up and +other windows for confirmation, file selection etc. Maybe have a dialogue +box for browsing and choosing buffers, etc. + +Motif supports Tear-off menus. These are sort of sticky menus or pop-up +menus that are present all the time. If the resizing does not work +correctly, this may be caused by using something like "Vim*geometry" in de +defaults. Use "Vim.geometry" instead. + +To help you set up your own menus, you can start off with the default set. +See |vim_menu.txt| for a set of menu commands that recreates the default +menus. You can insert this in your .gvimrc file and change it to your +liking. + +5.2 Creating New Menus *gui_creating_menus* + + *:me* *:menu* *:noremenu* + *:nmenu* *:nnoremenu* + *:vmenu* *:vnoremenu* + *:imenu* *:inoremenu* + *:cmenu* *:cnoremenu* +To create a new menu item, use the ":menu" commands. They are exactly like +the ":map" set of commands but the first argument is a menu item name, given +as a path of menus and submenus with a '.' between them. eg: + :menu File.Save :w^M + :inoremenu File.Save ^O:w^M + :menu Edit.Big\ Changes.Delete\ All\ Spaces :%s/[ ^I]//g^M + +This last one will create a new item in the menu bar called "Edit", holding +the mouse button down on this will pop up a menu containing the item +"Big Changes", which is a sub-menu containing the item "Delete All Spaces", +which when selected, performs the operation. + +Note that the '<' and 'k' flags in 'cpoptions' also apply here (when +included they make the <> form and raw key codes not being recognized). + + +5.3 Showing What Menus Are Mapped To *gui_showing_menus* + +To see what an existing menu is mapped to, use just one argument after the +menu commands (just like you would with the ":map" commands). If the menu +specified is a submenu, then all menus under that hierarchy will be shown. +If no argument is given after :menu at all, then ALL menu items are shown +for the appropriate mode (eg, Command-line mode for :cmenu). + +Note that hitting while entering a menu name after a menu command may +be used to complete the name of the menu item. + + +5.4 Deleting Menus *gui_delete_menus* + + *:unmenu* *:nunmenu* *:vunmenu* *:iunmenu* *:cunmenu* + *:unme* *:nunme* *:vunme* *:iunme* *:cunme* +To delete a menu item or a whole submenu, use the unmenu commands, which are +analogous to the unmap commands. Eg: + :unmenu! Edit.Paste + +This will remove the Paste item from the Edit menu for Insert and +Command-line modes. + +Note that hitting while entering a menu name after an umenu command +may be used to complete the name of the menu item for the appropriate mode. + +To remove all menus use: + :unmenu * " remove all menus in Normal and visual mode + :unmenu! * " remove all menus in Insert and Command-line mode + + +5.5 Examples for Menus + +Here is an example on how to add menu items with menu's! You can add a menu +item for the keyword under the cursor. The register "z" is used. + +:nmenu Words.Add\ Var wb"zye:menu! Words.z z +:nmenu Words.Remove\ Var wb"zye:unmenu! Words.z +:vmenu Words.Add\ Var "zy:menu! Words.z z +:vmenu Words.Remove\ Var "zy:unmenu! Words.z +:imenu Words.Add\ Var wb"zye:menu! Words.z za +:imenu Words.Remove\ Var wb"zye:unmenu! Words.za + +(the rhs is in <> notation, you can copy/paste this text to try out the +mappings, or put these lines in your gvimrc; "" is CTRL-R, "" is +the key. |<>|) + + + 6. Extras *gui_extras* + +This section describes other features which are related to the GUI. + + - With the GUI, there is no wait for one second after hitting escape, + because the key codes don't start with . + + - Typing ^V followed by a special key in the GUI will insert "", + since the internal string used is meaningless. + Modifiers may also be held down to get "". + + - In the GUI, the modifiers SHIFT, CTRL, and ALT (or META) may be used + within mappings of special keys and mouse events. eg: + :map + + - In the GUI, several normal keys may have modifiers in mappings etc, + these are , , , , . + + + 7. Shell Commands *gui_pty* + +WARNING: Executing an external command from the GUI will not always work. +"normal" commands like "ls", "grep" and "make" work fine. Commands that +require an intelligent terminal like "less" and "ispell" won't work. Some +may even hang and need to be killed from another terminal. So be careful! + +There are two ways to do the I/O with a shell command: Pipes and a +pseudo-tty. The default is to use pipes. This should work on any Unix +system. The disadvantage is that some shell commands will notice that a +pipe is being used and behave differently. The ":sh" command won't show a +prompt, although it will sort of work. ":!ls" will list the files in one +column. + +Alternatively a pseudo-tty can be used. Unfortunately, the implementation of +this is different on every Unix system. And many systems require root +permission. Still this might work better than pipes on some systems. To +see if this works better for you set the 'guipty' option on. Be prepared to +"kill" the started command or Vim, commands like ":r !cat" may hang! + + + 8. Compiling *gui_compiling* + +You need at least Motif version 1.2 and/or X11R5. Motif 2.0 and X11R6 are +OK. Motif 1.1 and X11R4 don't work properly (but you might make it work +with a bit of work). + +By default the Makefile tries to compile Vim with the GUI. When the Motif +files cannot be found the Athena files will be searched for. When both of +them cannot be found, the GUI will be disabled. When you have the Motif or +Athena files in a directory where configure doesn't look, edit the Makefile +to enter the names of the directories. Search for "GUI_INC_LOC" for an +example to set the Motif directories, "CONF_OPT_X" for Athena. + +Don't try to mix files from different Motif, Athena and X11 versions. This +will cause problems. For example, using header files for X11R5 with a +library for X11R6 probably doesn't work (although the linking won't give an +error message, Vim will crash later). + +The Athena version uses the Xaw widget set by default. If you have the 3D +version, you might want to link with Xaw3d instead. This will make the +menus look a bit better. Edit the Makefile and look for "XAW_LIB". The +scrollbars will remain the same, because Vim has its own, which are already +3D (in fact, they look more like Motif). + + + 9. To Do List *gui_todo* + + - Flashes really badly in certain cases when running remotely from a + Sun. + + - When taking the geometry of the window where gvim was started, check + that the GUI window will fit on the screen. + + - Window should be redrawn when resizing at the hit-return prompt. + + - Use different cursor and mouse shapes/colours for different modes. + + - Scrollbars with Motif 1.1 are ordered upside down! Do we care? + + - Motif steals from us, to pop up menus with the keyboard. How do + we get it back if we want it? + + - Paste in Insert mode should not do autowrap etc. + + - Option/resource to use pointer focus from the mouse to select the + current Vim window. + + - Add a new command :highlight for specifying how various things should + be highlighted, allowing colours to be given. Currently it's all hard + coded, the text colour, bg colour etc. + + - We need a nice little picture to go on the icon :-) But how do we do + that? + + - ":menu" should take count for where the menu is inserted (for first + menu item that doesn't exist yet) + + - Would be nice if menu items can be specified for several modes at + once. + + - Make better default menus! + + - Add "Buffers" menu, list of (hidden) buffers. + + - Add menu item to "Keep Insert mode". More generally: Include a nice + way to change options. + + - When using a pseudo-tty Vim should behave like some terminal. Terminal + codes to/from shell should be translated. + + - Make it easy to specify a shorcut (key) for a menu item. + + - :mkgvimrc command, that includes menus. + + - Would it be useful to be able to quit the GUI and go back to the + terminal where it was started from? + + - Finish off the todo list! + + + vim:tw=76:ts=8:sw=4 diff --git a/usr.bin/vim/doc/vim_help.txt b/usr.bin/vim/doc/vim_help.txt new file mode 100644 index 00000000000..e6183370efa --- /dev/null +++ b/usr.bin/vim/doc/vim_help.txt @@ -0,0 +1,1182 @@ +*vim_help.txt* For Vim version 4.2. Last modification: 1996 June 16 + + VIM help file + +VIM stands for Vi IMproved. Most of VIM was made by Bram Moolenaar. + k + Move around: Use the cursor keys, or "h" to go left, h l + "j" to go down, "k" to go up, "l" to go right. j +Close this window: Use ":q". +Jump to a subject: Position the cursor on the tag between |bars| and hit + CTRL-]. Use CTRL-T to go back. + With the mouse: ":set mouse=a" to enable the mouse (in xterm). + Hit "g" and click left mouse button on tag between |bars|. + Hit "g" and click right mouse button to go back. +Getting help on a + specific subject: It is possible to go directly to whatever you want help + on, by giving an argument to the ":help" command, |:help|. + It is possible to further specify the context: + what prepend example + Normal mode commands (nothing) :help x + Visual mode commands v_ :help v_u + Insert mode commands i_ :help i_ + command-line commands : :help :quit + command-line editing c_ :help c_ + Vim command options - :help -r + options ' :help 'textwidth' + + tag subject tag subject *index* + +|X_lr| motion: Left-right |X_re| Repeating commands +|X_ud| motion: Up-down |X_km| Key mapping +|X_tm| motion: Text object |X_ab| Abbreviations +|X_pa| motion: Pattern searches |X_op| Options +|X_ma| motion: Marks |X_ur| Undo/Redo commands +|X_vm| motion: Various |X_et| External commands +|X_ta| motion: Using tags |X_qf| Quickfix commands +|X_sc| Scrolling |X_vc| Various commands +|X_in| insert: Inserting text |X_ce| Ex: Command-line editing +|X_ai| insert: Keys |X_ra| Ex: Ranges +|X_ss| insert: Special keys |X_ex| Ex: Special characters +|X_di| insert: Digraphs |X_ed| Editing a file +|X_si| insert: Special inserts |X_fl| Using the argument list +|X_de| change: Deleting text |X_wq| Writing and quitting +|X_cm| change: Copying and moving |X_st| Starting VIM +|X_ch| change: Changing text |X_ac| Automatic commands +|X_co| change: Complex |X_wi| Multi-window functions +|X_vi| Visual mode |X_bu| Buffer list functions +|X_to| Text objects + +|howto| how to do common things +|copying| About copying Vim and Uganda +|credits| People who contributed +|www| Vim on the World Wide Web +|bugs| Where to send bug reports + +list of documentation files: remarks about specific systems: + +|vim_help.txt| quick reference (this file) |vim_ami.txt| Amiga +|vim_idx.txt| alphabetical index |vim_arch.txt| Archimedes +|vim_ref.txt| reference manual |vim_dos.txt| MS-DOS +|vim_win.txt| reference for window commands |vim_mac.txt| Macintosh +|vim_diff.txt| differences between Vim and Vi |vim_mint.txt| MiNT +|vim_digr.txt| list of available digraphs |vim_os2.txt| OS/2 +|vim_tips.txt| tips on using Vim |vim_unix.txt| Unix +|vim_gui.txt| about the Graphical User Interface |vim_w32.txt| Win-NT/95 +|vim_rlh.txt| about right-to-left editing +|vim_40.txt| about this version +|vim_tags| all the tags you can jump to (index of tags) + +------------------------------------------------------------------------------ +How to ... *howdoi* *how_do_i* *howto* *how_to* + +exit? I'm trapped, help me! |:quit| +initialize Vim |initialization| +suspend Vim |suspend| +recover after a crash |crash_recovery| +keep a backup of my file |backup| + +edit files |edit_files| +insert text |inserting| +delete text |deleting| +change text |changing| +edit binary files |edit_binary| +copy and move text |copy_move| +repeat commands |repeating| +undo and redo |undo_redo| +format text |formatting| +format comments |format_comments| +indent C programs |C_indenting| +automatically set indent |'autoindent'| + +move around |cursor_motions| +word motions |word_motions| +left-right motions |left_right_motions| +up-down motions |up_down_motions| +text-object motions |object_motions| +various motions |various_motions| +text-object selection |object_select| +move freely beyond beginning/end of line |'whichwrap'| +specify pattern for searches |pattern_searches| +do tags and special searches |tags_and_searches| +search in include'd files used to find + variables, functions, or macros |include_search| +look up manual for the keyword under cursor |K| + +scroll |scrolling| +scroll horizontally/sideways |'sidescroll'| +set scroll boundary |'scrolloff'| + +change modes |mode_switching| +use visual mode |Visual_mode| +start Vim in Insert mode |'insertmode'| + +map keys |key_mapping| +create abbreviations |abbreviations| + +expand a tab to spaces in Insert mode |ins_expandtab| +insert contents of a register in Insert mode |i_CTRL-R| +complete words in Insert mode |ins_completion| +break a line before it gets too long |ins_textwidth| + +do command-line editing |cmdline_editing| +do command-line completion |cmdline_completion| +increase the height of command-line |'cmdheight'| +specify command-line ranges |cmdline_ranges| +specify commands to be executed automatically + before/after reading/writing + entering/leaving a buffer/window |autocommand| + +write automatically |'autowrite'| +speedup edit-compile-edit cycle or compile + and fix errors within Vim |quickfix| + +set options |options| +set options automatically |auto_setting| +save settings |save_settings| +comment my exrc/vimrc/gvimrc files |:quote| +change the default help height |'helpheight'| +set various highlighting modes |'highlight'| +set the window title |'title'| +set window icon title |'icon'| +avoid seeing the change messages on every line |'report'| +avoid "Hit RETURN ..." messages |'shortmess'| + +use mouse with Vim |mouse_using| +manage multiple windows and buffers |vim_win.txt| +use the gui |vim_gui.txt| +catch 40 (or more!) winks, or take a catnap! |:sleep| +do dishes using Vim You can't! (yet) + +------------------------------------------------------------------------------ +N is used to indicate an optional count that can be given before the command. +------------------------------------------------------------------------------ +*X_lr* Left-right motions + +|h| N h left (also: CTRL-H, , or key) +|l| N l right (also: or key) +|0| 0 to first character in the line (also: key) +|^| ^ to first non-blank character in the line +|$| N $ to the last character in the line (N-1 lines lower) + (also: key) +|g0| N g0 to first character in screen line (differs from "0" + when lines wrap) +|g^| N g^ to first non-blank character in screen line (differs + from "^" when lines wrap) +|g$| N g$ to last character in screen line (differs from "$" + when lines wrap) +|bar| N | to column N (default: 1) +|f| N f to the Nth occurrence of to the right +|F| N F to the Nth occurrence of to the left +|t| N t till before the Nth occurrence of to the right +|T| N T till before the Nth occurrence of to the left +|;| N ; repeat the last "f", "F", "t", or "T" N times +|,| N , repeat the last "f", "F", "t", or "T" N times in + opposite direction +------------------------------------------------------------------------------ +*X_ud* Up-down motions + +|k| N k up N lines (also: CTRL-P and ) +|j| N j down N lines (also: CTRL-J, CTRL-N, , and ) +|-| N - up N lines, on the first non-blank character +|+| N + down N lines, on the first non-blank character (also: + CTRL-M and ) +|_| N _ down N-1 lines, on the first non-blank character +|G| N G goto line N (default: last line), on the first + non-blank character +|gg| N gg goto line N (default: first line), on the first + non-blank character +|N%| N % goto line N percentage down in the file. N must be + given, otherwise it is the |%| command. +|gk| N gk up N screen lines (differs from "k" when line wraps) +|gj| N gj down N screen lines (differs from "j" when line wraps) +------------------------------------------------------------------------------ +*X_tm* Text object motions + +|w| N w N words forward +|W| N W N blank-separated WORDS forward +|e| N e forward to the end of the Nth word +|E| N E forward to the end of the Nth blank-separated WORD +|b| N b N words backward +|B| N B N blank-separated WORDS backward +|ge| N ge backward to the end of the Nth word +|gE| N gE backward to the end of the Nth blank-separated WORD + +|)| N ) N sentences forward +|(| N ( N sentences backward +|}| N } N paragraphs forward +|{| N { N paragraphs backward +|]]| N ]] N sections forward, at start of section +|[[| N [[ N sections backward, at start of section +|][| N ][ N sections forward, at end of section +|[]| N [] N sections backward, at end of section +|[(| N [( N times back to unclosed '(' +|[(| N [{ N times back to unclosed '{' +|])| N ]) N times forward to unclosed ')' +|])| N ]} N times forward to unclosed '}' +|[#| N [# N times back to unclosed "#if" or "#else" +|]#| N ]# N times forward to unclosed "#else" or "#endif" +|[star| N [* N times back to start of comment "/*" +|]star| N ]* N times forward to end of comment "*/" +------------------------------------------------------------------------------ +*X_pa* Pattern searches + +|/| N /{pattern}[/[offset]] + search forward for the Nth occurrence of {pattern} +|?| N ?{pattern}[?[offset]] + search backward for the Nth occurrence of {pattern} +|/| N / repeat last search, in the forward direction +|?| N ? repeat last search, in the backward direction +|n| N n repeat last search +|N| N N repeat last search, in opposite direction +|star| N * search forward for the identifier under the cursor +|#| N # search backward for the identifier under the cursor +|gstar| N g* like "*", but also find partial matches +|g#| N g# like "#", but also find partial matches +|gd| gd goto local declaration of identifier under the cursor +|gD| gD goto global declaration of identifier under the cursor + +|search_pattern| Special characters in search patterns + + meaning magic nomagic + matches any single character . \. + matches start of line ^ ^ + matches end of line $ $ + matches start of word \< \< + matches end of word \> \> + matches a single char from the range [a-z] \[a-z] + matches a single char not in the range [^a-z] \[^a-z] + matches an identifier char \i \i + idem but excluding digits \I \I + matches a keyword character \k \k + idem but excluding digits \K \K + matches a filename character \f \f + idem but excluding digits \F \F + matches a printable character \p \p + idem but excluding digits \P \P + + matches \e \e + matches \t \t + matches \r \r + matches \b \b + +matches 0 or more of the preceding atom * \* +matches 1 or more of the preceding atom \+ \+ + matches 0 or 1 of the preceding atom \= \= + separates two branches \| \| + group a pattern into an atom \(\) \(\) + +|search_offset| Offsets allowed after search command + + [num] [num] lines downwards, in column 1 + +[num] [num] lines downwards, in column 1 + -[num] [num] lines upwards, in column 1 + e[+num] [num] characters to the right of the end of the match + e[-num] [num] characters to the left of the end of the match + s[+num] [num] characters to the right of the start of the match + s[-num] [num] characters to the left of the start of the match + b[+num] [num] characters to the right of the start (begin) of the match + b[-num] [num] characters to the left of the start (begin) of the match + ;{search command} execute {search command} next +------------------------------------------------------------------------------ +*X_ma* Marks and motions + +|m| m mark current position with mark +|`a| ` go to mark within current file +|`A| ` go to mark in any file +|`0| `<0-9> go to the position where Vim was last exited +|``| `` go to the position before the last jump +|`"| `" go to the position when last editing this file +|`[| `[ go to the start of the previously operated or put text +|`]| `] go to the end of the previously operated or put text +|`<| `< go to the start of the (previous) Visual area +|`>| `> go to the end of the (previous) Visual area +|'| '> + same as `, but on the first non-blank in the line +|:marks| :marks print the active marks +|CTRL-O| N CTRL-O go to Nth older position in jump list +|CTRL-I| N CTRL-I go to Nth newer position in jump list +|:ju| :ju[mps] print the jump list +------------------------------------------------------------------------------ +*X_vm* Various motions + +|%| % find the next brace, bracket, comment, or "#if"/ + "#else"/"#endif" in this line and go to its match +|H| N H go to the Nth line in the window, on the first + non-blank +|M| M go to the middle line in the window, on the first + non-blank +|L| N L go to the Nth line from the bottom, on the first + non-blank +------------------------------------------------------------------------------ +*X_ta* Using tags + +|:ta| :ta[g][!] {tag} Jump to tag {tag} +|:ta| :[count]ta[g][!] Jump to [count]'th newer tag in tag list +|CTRL-]| CTRL-] Jump to the tag under cursor, unless changes + have been made +|CTRL-T| N CTRL-T Jump back from Nth older tag in tag list +|:po| :[count]po[p][!] Jump back from [count]'th older tag in tag list +|:tags| :tags Print tag list +------------------------------------------------------------------------------ +*X_sc* Scrolling + +|CTRL-E| N CTRL-E window N lines downwards (default: 1) +|CTRL-D| N CTRL-D window N lines Downwards (default: 1/2 window) +|CTRL-F| N CTRL-F window N pages Forwards (downwards) +|CTRL-Y| N CTRL-Y window N lines upwards (default: 1) +|CTRL-U| N CTRL-U window N lines Upwards (default: 1/2 window) +|CTRL-B| N CTRL-B window N pages Backwards (upwards) +|z| z or zt redraw, current line at top of window +|z.| z. or zz redraw, current line at center of window +|z-| z- or zb redraw, current line at bottom of window + +|zh| N zh scroll screen N characters to the right +|zl| N zl scroll screen N characters to the left +------------------------------------------------------------------------------ +*X_in* Inserting text + +|a| N a append text after the cursor (N times) +|A| N A append text at the end of the line (N times) +|i| N i insert text before the cursor (N times) (also: ) +|I| N I insert text before the first non-blank in the line (N times) +|gI| N gI insert text in column 1 (N times) +|o| N o open a new line below the current line, append text (N times) +|O| N O open a new line above the current line, append text (N times) +------------------------------------------------------------------------------ +*X_ai* Keys in Insert mode + + char action in Insert mode +|i_| end Insert mode, back to Normal mode +|i_CTRL-C| CTRL-C like , but do not do an abbreviation +|i_CTRL-A| CTRL-A insert previously inserted text +|i_CTRL-@| CTRL-@ insert previously inserted text and stop + insert +|i_CTRL-R| CTRL-R <0-9a-z%:.-"> insert contents of register <0-9a-z%:.-"> +|i_| or or CTRL-M or CTRL-J + begin new line +|i_CTRL-E| CTRL-E insert the character from below the cursor +|i_CTRL-Y| CTRL-Y insert the character from above the cursor +|i_CTRL-V| CTRL-V .. insert character literally, or enter decimal + byte value +|i_CTRL-N| CTRL-N insert next match of identifier before the + cursor +|i_CTRL-P| CTRL-P insert previous match of identifier before + the cursor +|i_CTRL-X| CTRL-X ... complete the word before the cursor in + various ways +|i_| or CTRL-H delete the character before the cursor +|i_| delete the character under the cursor +|i_CTRL-W| CTRL-W delete word before the cursor +|i_CTRL-U| CTRL-U delete all entered characters in the current + line +|i_CTRL-T| CTRL-T insert one shiftwidth of indent in front of + the current line +|i_CTRL-D| CTRL-D delete one shiftwidth of indent in front of + the current line +|i_0_CTRL-D| 0 CTRL-D delete all indent in the current line +|i_^_CTRL-D| ^ CTRL-D delete all indent in the current line, + restore indent in next line +|i_CTRL-K| CTRL-K {char1} {char2} + enter digraph (See |X_di|) +|i_digraph| {char1} {char2} + enter digraph if 'digraph' option set +|i_CTRL-B| CTRL-B toggle 'revins' (reverse insert) option +------------------------------------------------------------------------------ +*X_ss* Special keys in Insert mode + +|i_| cursor keys move cursor left/right/up/down +|i_| shift-left/right one word left/right +|i_| shift-up/down one screenful backward/forward +|i_CTRL-O| CTRL-O {command} execute {command} +|i_| cursor after last character in the line +|i_| cursor to first character in the line +------------------------------------------------------------------------------ +*X_di* Digraphs + +|:dig| :dig[raphs] show current list of digraphs +|:dig| :dig[raphs] {char1}{char2} {number} ... + add digraph(s) to the list +------------------------------------------------------------------------------ +*X_si* Special inserts + +|:r| :r [file] insert the contents of [file] below the cursor +|:r!| :r! {command} insert the standard output of {command} below the + cursor +------------------------------------------------------------------------------ +*X_de* Deleting text + +|x| N x delete N characters under and after the cursor +|| N delete N characters under and after the cursor +|X| N X delete N characters before the cursor +|d| N d{motion} delete the text that is moved over with {motion} +|v_d| {visual}d delete the highlighted text +|dd| N dd delete N lines +|D| N D delete to end-of-line (and N-1 more lines) +|J| N J join N-1 lines (delete newlines) +|v_J| {visual}J join the highlighted lines +|:d| :[range]d [x] delete [range] lines [into register x] +------------------------------------------------------------------------------ +*X_cm* Copying and moving text + +|quote| " use register for the next delete, yank, or put +|:reg| :reg show the contents of all registers +|:reg| :reg {arg} show the contents of registers mentioned in {arg} +|y| N y{motion} yank the text moved over with {motion} into a register +|v_y| {visual}y yank the highlighted text into a register +|yy| N yy yank N lines into a register +|Y| N Y yank N lines into a register +|p| N p put a register after the cursor position (N times) +|P| N P put a register before the cursor position (N times) +|]p| N ]p like p, but adjust indent to current line +|[p| N [p like P, but adjust indent to current line +------------------------------------------------------------------------------ +*X_ch* Changing text + +|R| N R enter Replace mode (repeat the entered text N times) +|c| N c{motion} change the text that is moved over with {motion} +|v_c| {visual}c change the highlighted text +|cc| N cc change N lines +|S| N S change N lines +|C| N C change to end-of-line (and N-1 more lines) +|s| N s change N characters +|r| N r replace N characters with + +|~| N ~ switch case for N characters and advance cursor +|v_~| {visual}~ switch case for highlighted text +|v_u| {visual}u make highlighted text lowercase +|v_U| {visual}U make highlighted text uppercase +|g~| g~{motion} switch case for the text that is moved over with + {motion} +|gu| gu{motion} make the text that is moved over with {motion} + lowercase +|gU| gU{motion} make the text that is moved over with {motion} + uppercase + +|CTRL-A| N CTRL-A add N to the number at or after the cursor +|CTRL-X| N CTRL-X subtract N from the number at or after the cursor + +|<| N <{motion} move the lines that are moved over with {motion} one + shiftwidth left +|<<| N << move N lines one shiftwidth left +|>| N >{motion} move the lines that are moved over with {motion} one + shiftwidth right +|>>| N >> move N lines one shiftwidth right +|gq| N gq{motion} format the lines that are moved over with {motion} to + 'textwidth' length +|:ce| :[range]ce[nter] [width] + center the lines in [range] +|:le| :[range]le[ft] [indent] + left-align the lines in [range] [with indent] +|:ri| :[range]ri[ght] [width] + right-align the lines in [range] +------------------------------------------------------------------------------ +*X_co* Complex changes + +|!| N !{motion}{command} + filter the lines that are moved over through {command} +|!!| N !!{command} + filter N lines through {command} +|v_!| {visual}!{command} + filter the highlighted lines through {command} +|:range!| :[range]! {command} + filter [range] lines through {command} +|=| N ={motion} + filter the lines that are moved over through "indent" +|==| N == filter N lines through "indent" +|v_=| {visual}= + filter the highlighted lines through "indent" +|:s| :[range]s[ubstitute]/{pattern}/{string}/[g][c] + substitute {pattern} by {string} in [range] lines; + with [g], replace all occurrences of {pattern}; + with [c], confirm each replacement +|:s| :[range]s[ubstitute] [g][c] + repeat previous ":s" with new range and options +|&| & Repeat previous ":s" on current line without options +|:ret| :[range]ret[ab][!] [tabstop] + set 'tabstop' to new value and adjust white space + accordingly +------------------------------------------------------------------------------ +*X_vi* Visual mode + +|v| v start highlighting characters } move cursor and use +|V| V start highlighting linewise } operator to affect +|CTRL-V| CTRL-V start highlighting blockwise } highlighted text +|v_o| o exchange cursor position with start of highlighting +|gv| gv start highlighting on previous visual area +|v_v| v highlight characters or stop highlighting +|v_V| V highlight linewise or stop highlighting +|v_CTRL-V| CTRL-V highlight blockwise or stop highlighting +------------------------------------------------------------------------------ +*X_to* Text objects (only in Visual mode or after an operator) + +|v_a| N a Select current word +|v_A| N A Select current WORD +|v_s| N s Select current sentence +|v_p| N p Select current paragraph +|v_S| N S Select current block (from "[(" to "])") +|v_P| N P Select current block (from "[{" to "]}") +------------------------------------------------------------------------------ +*X_re* Repeating commands + +|.| N . repeat last change (with count replaced with N) +|q| q record typed characters into register +|q| q record typed characters, appended to register +|q| q stop recording +|@| N @ execute the contents of register (N times) +|@@| N @@ repeat previous @ (N times) +|:@| :@ execute the contents of register as an Ex + command +|:@@| :@@ repeat previous :@ +|:g| :[range]g[lobal]/{pattern}/[cmd] + Execute Ex command [cmd] (default: ":p") on the lines + within [range] where {pattern} matches. +|:g| :[range]g[lobal]!/{pattern}/[cmd] + Execute Ex command [cmd] (default: ":p") on the lines + within [range] where {pattern} does NOT match. +|:so| :so[urce] {file} + Read Ex commands from {file}. +|:so| :so[urce]! {file} + Read Vim commands from {file}. +|:sl| :sl[eep] [N] + don't do anything for N seconds +|gs| N gs Goto Sleep for N seconds +------------------------------------------------------------------------------ +*X_km* Key mapping + +|:map| :ma[p] {lhs} {rhs} Map {lhs} to {rhs} in Normal and Visual + mode. +|:map!| :ma[p]! {lhs} {rhs} Map {lhs} to {rhs} in Insert and Command-line + mode. +|:noremap| :no[remap][!] {lhs} {rhs} + Same as ":map", no remapping for this {rhs} +|:unmap| :unm[ap] {lhs} Remove the mapping of {lhs} for Normal and + Visual mode. +|:unmap!| :unm[ap]! {lhs} Remove the mapping of {lhs} for Insert and + Command-line mode. +|:map_l| :ma[p] [lhs] List mappings (starting with [lhs]) for + Normal and Visual mode. +|:map_l! :ma[p]! [lhs] List mappings (starting with [lhs]) for + Insert and Command-line mode. +|:cmap| :cmap/:cunmap/:cnoremap + like ":map!"/":unmap!"/":noremap!" but for + Command-line mode only +|:imap| :imap/:iunmap/:inoremap + like ":map!"/":unmap!"/":noremap!" but for + Insert mode only +|:nmap| :nmap/:nunmap/:nnoremap + like ":map"/":unmap"/":noremap" but for + Normal mode only +|:vmap| :vmap/:vunmap/:vnoremap + like ":map"/":unmap"/":noremap" but for + Visual mode only +|:mkexrc| :mk[exrc][!] [file] write current mappings, abbreviations, and + settings to [file] (default: ".exrc"; + use ! to overwrite) +|:mkvimrc| :mkv[imrc][!] [file] + same as ":mkexrc", but with default ".vimrc" +|:mapc| :mapc[lear] remove mappings for Normal and Visual mode +|:mapc| :mapc[lear]! remove mappings for Insert and Cmdline mode +|:imapc| :imapc[lear] remove mappings for Insert mode +|:vmapc| :vmapc[lear] remove mappings for Visual mode +|:nmapc| :nmapc[lear] remove mappings for Normal mode +|:cmapc| :cmapc[lear] remove mappings for Cmdline mode +------------------------------------------------------------------------------ +*X_ab* Abbreviations + +|:abbreviate| :ab[breviate] {lhs} {rhs} add abbreviation for {lhs} to {rhs} +|:abbreviate| :ab[breviate] {lhs} show abbr's that start with {lhs} +|:abbreviate| :ab[breviate] show all abbreviations +|:unabbreviate| :una[bbreviate] {lhs} remove abbreviation for {lhs} +|:noreabbrev| :norea[bbrev] [lhs] [rhs] like ":ab", but don't remap [rhs] +|:iabbrev| :iab/:iunab/:inoreab like ":ab", but only for Insert mode +|:cabbrev| :cab/:cunab/:cnoreab like ":ab", but only for + Command-line mode +|:abclear| :abc[lear] remove all abbreviations +|:cabclear| :cabc[lear] remove all abbr's for Cmdline mode +|:iabclear| :iabc[lear] remove all abbr's for Insert mode +------------------------------------------------------------------------------ +*X_op* Options + +|:set| :se[t] Show all modified options. +|:set| :se[t] all Show all options. +|:set| :se[t] {option} Set toggle option on, show string or number + option. +|:set| :se[t] no{option} Set toggle option off. +|:set| :se[t] inv{option} invert toggle option. +|:set| :se[t] {option}={value} Set string or number option to {value}. +|:set| :se[t] {option}? Show value of {option}. +|:set| :se[t] {option}& Reset {option} to its default value. + +|:fix| :fix[del] Set value of 't_kD' according to value of + 't_kb'. + +Short explanation of each option: *option_list* +|'aleph'| |'al'| ASCII code of the letter Aleph (RIGHTLEFT) +|'autoindent'| |'ai'| take indent for new line from previous line +|'autowrite'| |'aw'| automatically write file if changed +|'backspace'| |'bs'| how backspace works at start of line +|'backup'| |'bk'| keep backup file after overwriting a file +|'backupdir'| |'bdir'| list of directories for the backup file +|'backupext'| |'bex'| extension used for the backup file +|'binary'| |'bin'| edit binary file mode +|'bioskey'| |'biosk'| MS-DOS: use bios calls for input characters +|'breakat'| |'brk'| characters that may cause a line break +|'cindent'| |'cin'| do C program indenting +|'cinkeys'| |'cink'| keys that trigger indent when 'cindent' is set +|'cinoptions'| |'cino'| how to do indenting when 'cindent' is set +|'cinwords'| |'cinw'| words where 'si' and 'cin' add an indent +|'cmdheight'| |'ch'| number of lines to use for the command-line +|'columns'| |'co'| number of columns in the display +|'comments'| |'com'| patterns that can start a comment line +|'compatible'| |'cp'| behave Vi-compatibly as much as possible +|'cpoptions'| |'cpo'| flags for Vi-compatible behaviour +|'define'| |'def'| pattern to be used to find a macro definition +|'dictionary'| |'dict'| list of filenames used for keyword completion +|'digraph'| |'dg'| enable the entering of digraphs in Insert mode +|'directory'| |'dir'| list of directory names for the swapfile +|'edcompatible'| |'ed'| toggle flags of ":substitute" command +|'endofline'| |'eol'| write end-of-line for last line in file +|'equalalways'| |'ea'| windows are automatically made the same size +|'equalprg'| |'ep'| external program to use for "=" command +|'errorbells'| |'eb'| ring the bell for error messages +|'errorfile'| |'ef'| name of the error file for the QuickFix mode +|'errorformat'| |'efm'| description of the lines in the error file +|'esckeys'| |'ek'| recognize function keys in Insert mode +|'expandtab'| |'et'| use spaces when is inserted +|'exrc'| read .vimrc and .exrc in the current directory +|'formatoptions'| |'fo'| how automatic formatting is to be done +|'formatprg'| |'fp'| name of external program used with "gq" command +|'gdefault'| |'gd'| the ":substitute" flag 'g' is default on +|'guifont'| |'gfn'| GUI: Name(s) of font(s) to be used +|'guioptions'| |'go'| GUI: Which components and options are used +|'guipty'| GUI: try to use a pseudo-tty for ":!" commands +|'helpfile'| |'hf'| name of this help file +|'helpheight'| |'hh'| minimum height of a new help window +|'hidden'| |'hid'| don't unload buffer when it is abandoned +|'highlight'| |'hl'| sets highlighting mode for various occasions +|'history'| |'hi'| number of command-lines that are remembered +|'hkmap'| |'hk'| Hebrew keyboard mapping (RIGHTLEFT) +|'icon'| set icon of the window to the name of the file +|'ignorecase'| |'ic'| ignore case in search patterns +|'include'| |'inc'| pattern to be used to find an include file +|'incsearch'| |'is'| highlight match while typing search pattern +|'infercase'| |'inf'| adjust case of match for keyword completion +|'insertmode'| |'im'| start the edit of a file in Insert mode +|'isfname'| |'isf'| characters included in filenames and pathnames +|'isident'| |'isi'| characters included in identifiers +|'isprint'| |'isp'| printable characters +|'iskeyword'| |'isk'| characters included in keywords +|'joinspaces'| |'js'| two spaces after a period with a join command +|'keywordprg'| |'kp'| program to use for the "K" command +|'langmap'| |'lmap'| alphabetic characters for other language mode +|'laststatus'| |'ls'| tells when last window has status lines +|'linebreak'| |'lbr'| wrap long lines at a blank +|'lines'| number of lines in the display +|'lisp'| automatic indenting for Lisp +|'list'| show and end-of-line +|'magic'| changes special characters in search patterns +|'makeprg'| |'mp'| program to use for the ":make" command +|'maxmapdepth'| |'mmd'| maximum recursive depth for mapping +|'maxmem'| |'mm'| maximum memory (in Kbyte) used for one buffer +|'maxmemtot'| |'mmt'| maximum memory (in Kbyte) used for all buffers +|'modeline'| |'ml'| recognize modelines at start or end of file +|'modelines'| |'mls'| number of lines checked for modelines +|'modified'| |'mod'| buffer has been modified +|'more'| pause listings when the whole screen is filled +|'mouse'| enable the use of mouse clicks +|'mousetime'| |'mouset'| max time between mouse double-click +|'number'| |'nu'| print the line number in front of each line +|'paragraphs'| |'para'| nroff macros that separate paragraphs +|'paste'| allow pasting text +|'patchmode'| |'pm'| keep the oldest version of a file +|'path'| |'pa'| list of directories searched with "gf" et.al. +|'readonly'| |'ro'| disallow writing the buffer +|'remap'| allow mappings to work recursively +|'report'| threshold for reporting nr. of lines changed +|'restorescreen'| |'rs'| Win32: restore screen when exiting +|'revins'| |'ri'| inserting characters will work backwards +|'rightleft'| |'rl'| window is right-to-left oriented (RIGHTLEFT) +|'ruler'| |'ru'| show cursor line and column in the status line +|'scroll'| |'scr'| lines to scroll with CTRL-U and CTRL-D +|'scrolljump'| |'sj'| minimum number of lines to scroll +|'scrolloff'| |'so'| minimum nr. of lines above and below cursor +|'sections'| |'sect'| nroff macros that separate sections +|'secure'| secure mode for reading .vimrc in current dir +|'shell'| |'sh'| name of shell to use for external commands +|'shellpipe'| |'sp'| string to put output of ":make" in error file +|'shellredir'| |'srr'| string to put output of filter in a temp file +|'shelltype'| |'st'| Amiga: influences how to use a shell +|'shiftround'| |'sr'| round indent to multiple of shiftwidth +|'shiftwidth'| |'sw'| number of spaces to use for (auto)indent step +|'shortmess'| |'shm'| list of flags, reduce length of messages +|'shortname'| |'sn'| non-MS-DOS: File names assumed to be 8.3 chars +|'showbreak'| |'sbr'| string to use at the start of wrapped lines +|'showcmd'| |'sc'| show (partial) command in status line +|'showmatch'| |'sm'| briefly jump to matching bracket if insert one +|'showmode'| |'smd'| message on status line to show current mode +|'sidescroll'| |'ss'| minimum number of columns to scroll horizontal +|'smartcase'| |'scs'| no ignore case when pattern has uppercase +|'smartindent'| |'si'| smart autoindenting for C programs +|'smarttab'| |'sta'| use 'shiftwidth' when inserting +|'splitbelow'| |'sb'| new window from split is below the current one +|'startofline'| |'sol'| commands move cursor to first blank in line +|'suffixes'| |'su'| suffixes that are ignored with multiple match +|'swapsync'| |'sws'| how to sync swapfile +|'tabstop'| |'ts'| number of spaces that in file uses +|'taglength'| |'tl'| number of significant characters for a tag +|'tagrelative'| |'tr'| filenames in tag file are relative +|'tags'| |'tag'| list of filenames used by the tag command +|'term'| name of the terminal +|'terse'| shorten some messages +|'textauto'| |'ta'| set 'textmode' automatically when reading file +|'textmode'| |'tx'| lines are separated by +|'textwidth'| |'tw'| maximum width of text that is being inserted +|'tildeop'| |'top'| tilde command "~" behaves like an operator +|'timeout'| |'to'| time out on mappings and key codes +|'ttimeout'| time out on mappings +|'timeoutlen'| |'tm'| time out time in milliseconds +|'ttimeoutlen'| |'ttm'| time out time for key codes in milliseconds +|'title'| set title of window to the name of the file +|'ttybuiltin'| |'tbi'| use built-in termcap before external termcap +|'ttyfast'| |'tf'| indicates a fast terminal connection +|'ttyscroll'| |'tsl'| maximum number of lines for a scroll +|'ttytype'| |'tty'| alias for 'term' +|'undolevels'| |'ul'| maximum number of changes that can be undone +|'updatecount'| |'uc'| after this many characters flush swapfile +|'updatetime'| |'ut'| after this many milliseconds flush swapfile +|'viminfo'| |'vi'| use .viminfo file upon startup and exiting +|'visualbell'| |'vb'| use visual bell instead of beeping +|'warn'| warn for shell command when buffer was changed +|'weirdinvert'| |'wi'| for terminals that have weird inversion method +|'whichwrap'| |'ww'| allow specified keys to cross line boundaries +|'wildchar'| |'wc'| command-line character for wildcard expansion +|'winheight'| |'wh'| minimum number of lines for the current window +|'wrap'| long lines wrap and continue on the next line +|'wrapmargin'| |'wm'| chars from the right where wrapping starts +|'wrapscan'| |'ws'| searches wrap around the end of the file +|'writeany'| |'wa'| write to file with no need for "!" override +|'writebackup'| |'wb'| make a backup before overwriting a file +|'writedelay'| |'wd'| delay this many msec for each char (for debug) +------------------------------------------------------------------------------ +*X_ur* Undo/Redo commands + +|u| N u undo last N changes +|CTRL-R| N CTRL-R redo last N undone changes +|U| U restore last changed line +------------------------------------------------------------------------------ +*X_et* External commands + +|:shell| :sh[ell] start a shell +|:!| :!{command} execute {command} with a shell +|K| K lookup keyword under the cursor with + 'keywordprg' program (default: "man") +------------------------------------------------------------------------------ +*X_qf* Quickfix commands + +|:cc| :cc [nr] display error [nr] (default is the same again) +|:cnext| :cn display the next error +|:cprevious| :cp display the previous error +|:clist| :cl list all errors +|:cfile| :cf read errors from the file 'errorfile' +|:cquit| :cq quit without writing and return error code (to + the compiler) +|:make| :make [args] start make, read errors, and jump to first + error +------------------------------------------------------------------------------ +*X_vc* Various commands + +|CTRL-L| CTRL-L Clear and redraw the screen. +|CTRL-G| CTRL-G show current file name (with path) and cursor + position +|ga| ga show ascii value of character under cursor in + decimal, hex, and octal +|g_CTRL-G| g CTRL-G show cursor column, line, and character + position +|CTRL-C| CTRL-C during searches: interrupt the search +|CTRL-BREAK| CTRL-BREAK MS-DOS: during searches: interrupt the search +|| while entering a count: delete last character +|:version| :ve[rsion] show exact version number of this Vim +|:mode| :mode N MS-DOS: set screen mode to N (number, C80, + C4350, etc.) +|:normal| :norm[al][!] {commands} + Execute Normal mode commands. +------------------------------------------------------------------------------ +*X_ce* Command-line editing + +|c_| abandon command-line (if 'wildchar' is + , type it twice) + +|c_CTRL-V| CTRL-V {char} insert {char} literally +|c_CTRL-V| CTRL-V {number} enter decimal value of character (up to + three digits) +|c_CTRL-K| CTRL-K {char1} {char2} + enter digraph (See |X_di|) +|c_CTRL-R| CTRL-R <0-9a-z"%:-> + insert contents of register <0-9a-z"%:-> + +|c_| / cursor left/right +|c_| / cursor one word left/right +|c_CTRL-B| CTRL-B/CTRL-E cursor to beginning/end of command-line + +|c_| delete the character in front of the cursor +|c_| delete the character under the cursor +|c_CTRL-W| CTRL-W delete the word in front of the cursor +|c_CTRL-U| CTRL-U remove all characters + +|c_| / recall older/newer command-line that starts + with current command +|c_| / recall older/newer command-line from history + + Context-sensitive completion on the command-line: + +|c_wildchar| 'wildchar' (default: ) + do completion on the pattern in front of the + cursor. If there are multiple matches, + beep and show the first one; further + 'wildchar' will show the next ones. +|c_CTRL-D| CTRL-D list all names that match the pattern in + front of the cursor +|c_CTRL-A| CTRL-A insert all names that match pattern in front + of cursor +|c_CTRL-L| CTRL-L insert longest common part of names that + match pattern +|c_CTRL-N| CTRL-N after 'wildchar' with multiple matches: go + to next match +|c_CTRL-P| CTRL-P after 'wildchar' with multiple matches: go + to previous match +------------------------------------------------------------------------------ +*X_ra* Ex ranges + +|:range| , separates two line numbers +|:range| ; idem, set cursor to the first line number + before interpreting the second one + +|:range| {number} an absolute line number +|:range| . the current line +|:range| $ the last line in the file +|:range| % equal to 1,$ (the entire file) +|:range| * equal to '<,'> (visual area) +|:range| 't position of mark t +|:range| /{pattern} the next line where {pattern} matches +|:range| ?{pattern} the previous line where {pattern} matches + +|:range| +[num] add [num] to the preceding line number + (default: 1) +|:range| -[num] subtract [num] from the preceding line + number (default: 1) +------------------------------------------------------------------------------ +*X_ex* Special Ex characters + +|:bar| | separates two commands (not for ":global" and ":!") +|:quote| " begins comment + +|:_%| % current filename (only where filename is expected) +|:_#| #[number] alternate filename [number] (only where filename is + expected) + Note: The next four are typed literally; these are not special keys! +|:| word under the cursor (only where filename is + expected) +|:| WORD under the cursor (only where filename is + expected) (see |WORD|) +|:| file name under the cursor (only where filename is + expected) +|:| file name for autocommand (only where filename is + expected) + + After "%", "#", "", or "" + |::p| :p full path + |::h| :h head + |::t| :t tail + |::r| :r root + |::e| :e extension +------------------------------------------------------------------------------ +*X_ed* Editing a file + +|:edit| :e[dit] Edit the current file, unless changes have + been made. +|:edit!| :e[dit]! Edit the current file always. Discard any + changes. +|:edit_f| :e[dit] {file} Edit {file}, unless changes have been made. +|:edit!_f| :e[dit]! {file} Edit {file} always. Discard any changes. +|CTRL-^| N CTRL-^ Edit alternate file N (equivalent to ":e #N"). +|gf| gf or ]f Edit the file whose name is under the cursor +|:pwd| :pwd Print the current directory name. +|:cd| :cd [path] Change the current directory to [path]. +|:file| :f[ile] Print the current filename and the cursor + position. +|:file| :f[ile] {name} Set the current filename to {name}. +|:files| :files Show alternate filenames. +------------------------------------------------------------------------------ +*X_fl* Using the argument list |argument_list| + +|:args| :ar[gs] Print the argument list, with the current file + in "[]". +|:all| :all or :sall Open a window for every file in the arg list. +|:wn| :wn[ext][!] Write file and edit next file. +|:wn| :wn[ext][!] {file} Write to {file} and edit next file, unless + {file} exists. With !, overwrite existing + file. +|:wN| :wN[ext][!] [file] Write file and edit previous file. + + in current window in new window +|:argument| :argu[ment] N :sar[gument] N Edit file N +|:next| :n[ext] :sn[ext] Edit next file +|:next_f| :n[ext] {arglist} :sn[ext] {arglist} define new arg list + and edit first file +|:Next| :N[ext] :sN[ext] Edit previous file +|:rewind| :rew[ind][!] :srew[ind] Edit first file +|:last| :last :slast Edit last file +------------------------------------------------------------------------------ +*X_wq* Writing and quitting + +|:w| :[range]w[rite][!] Write to the current file. +|:w_f| :[range]w[rite] {file} Write to {file}, unless it already + exists. +|:w_f| :[range]w[rite]! {file} Write to {file}. Overwrite an existing + file. +|:w_a| :[range]w[rite][!] >> Append to the current file. +|:w_a| :[range]w[rite][!] >> {file} Append to {file}. +|:w_c| :[range]w[rite] !{cmd} Execute {cmd} with [range] lines as + standard input. +|:wall| :wall[!] write all changed buffers + +|:q| :q[uit] Quit current buffer, unless changes have been + made. Exit Vim when there are no other + non-help buffers +|:q| :q[uit]! Quit current buffer always, discard any + changes. Exit Vim when there are no other + non-help buffers +|:qa| :qall Exit Vim, unless changes have been made. +|:qa| :qall! Exit Vim always, discard any changes. +|:cq| :cq Quit without writing and return error code. + +|:wq| :wq[!] Write the current file and exit. +|:wq| :wq[!] {file} Write to {file} and exit. +|:xit| :x[it][!] [file] Like ":wq" but write only when changes have + been made +|ZZ| ZZ Same as ":x". +|:xall| :xall[!] or :wqall[!] + Write all changed buffers and exit + +|:stop| :st[op][!] Suspend VIM or start new shell. If 'aw' option + is set and [!] not given write the buffer. +|CTRL-Z| CTRL-Z Same as ":stop!" +------------------------------------------------------------------------------ +*X_st* Starting VIM + +|-vim| vim [options] start editing with an empty buffer +|-file| vim [options] {file ..} start editing one or more files +|-tag| vim [options] -t {tag} edit the file associated with {tag} +|-qf| vim [options] -e [fname] start editing in QuickFix mode, + display the first error + + Vim arguments: + +|-gui| -g start GUI (also allows other options) + +|-+| +[num] put the cursor at line [num] (default: last line) +|-+c| +{command} execute {command} after loading the file +|-+/| +/{pat} {file ..} put the cursor at the first occurrence of {pat} +|-v| -v read-only mode (View), implies -n +|-R| -R read-only mode, same as -v +|-b| -b binary mode +|-l| -l lisp mode +|-H| -H Hebrew mode ('hkmap' and 'rightleft' are set) +|-r| -r give list of swap files +|-r| -r {file ..} recover aborted edit session +|-n| -n do not create swapfile +|-o| -o [N] open N windows (default: one for each file) +|-x| -x Amiga: do not restart VIM to open a window (for + e.g., mail) +|-s| -s {scriptin} first read commands from the file {scriptin} +|-w| -w {scriptout} write typed chars to file {scriptout} (append) +|-W| -W {scriptout} write typed chars to file {scriptout} (overwrite) +|-T| -T {terminal} set terminal name +|-d| -d {device} Amiga: open {device} to be used as a console +|-u| -u {vimrc} read inits from {vimrc} instead of other inits +|-i| -i {viminfo} read info from {viminfo} instead of other files +|--| -- end of options, other arguments are file names +------------------------------------------------------------------------------ +*X_ac* Automatic Commands + +|viminfo_file| Read registers, marks, history at startup, save when exiting. + +|:rviminfo| :rv[iminfo] [file] Read info from viminfo file [file] +|:rviminfo| :rv[iminfo]! [file] idem, overwrite exisiting info +|:wviminfo| :wv[iminfo] [file] Add info to viminfo file [file] +|:wviminfo| :wv[iminfo]! [file] Write info to viminfo file [file] + +|modeline| Automatic option setting when editing a file + +|modeline| vim:{set-arg}: .. In the first and last lines of the + file (see 'ml' option), {set-arg} is + given as an argument to ":set" + +|autocommand| Automatic execution of commands on certain events. + +|:autocmd| :au List all autocommands +|:autocmd| :au {event} List all autocommands for {event} +|:autocmd| :au {event} {pat} List all autocommands for {event} with + {pat} +|:autocmd| :au {event} {pat} {cmd} Enter new autocommands for {event} + with {pat} +|:autocmd| :au! Remove all autocommands +|:autocmd| :au! {event} Remove all autocommands for {event} +|:autocmd| :au! * {pat} Remove all autocommands for {pat} +|:autocmd| :au! {event} {pat} Remove all autocommands for {event} + with {pat} +|:autocmd| :au! {event} {pat} {cmd} Remove all autocommands for {event} + with {pat} and enter new one +------------------------------------------------------------------------------ +*X_wi* Multi-window functions + +|CTRL-W_s| CTRL-W s or :split Split window into two parts +|:split_f| :split {file} Split window and edit {file} in one of + them +|CTRL-W_]| CTRL-W ] Split window and jump to tag under + cursor +|CTRL-W_f| CTRL-W f Split window and edit file name under + the cursor +|CTRL-W_CTRL-^| CTRL-W CTRL-^ Split window and edit alternate file +|CTRL-W_n| CTRL-W n or :new Create new empty window +|CTRL-W_q| CTRL-W q or :q[uit] Quit editing and close window +|CTRL-W_c| CTRL-W c or :cl[ose] Make buffer hidden and close window +|CTRL-W_o| CTRL-W o or :on[ly] Make current window only one on the + screen + +|CTRL-W_j| CTRL-W j Move cursor to window below +|CTRL-W_k| CTRL-W k Move cursor to window above +|CTRL-W_CTRL-W| CTRL-W CTRL-W Move cursor to window below (wrap) +|CTRL-W_W| CTRL-W W Move cursor to window above (wrap) +|CTRL-W_t| CTRL-W t Move cursor to top window +|CTRL-W_b| CTRL-W b Move cursor to bottom window +|CTRL-W_p| CTRL-W p Move cursor to previous active window + +|CTRL-W_r| CTRL-W r Rotate windows downwards +|CTRL-W_R| CTRL-W R Rotate windows upwards +|CTRL-W_x| CTRL-W x Exchange current window with next one + +|CTRL-W_=| CTRL-W = Make all windows equal height +|CTRL-W_-| CTRL-W - Decrease current window height +|CTRL-W_+| CTRL-W + Increase current window height +|CTRL-W__| CTRL-W _ Set current window height (default: + very high) +------------------------------------------------------------------------------ +*X_bu* Buffer list functions + +|:buffers| :buffers or :files list all known buffer and file names + +|:ball| :ball or :sball edit all args/buffers +|:unhide| :unhide or :sunhide edit all loaded buffers + +|:bunload| :bunload[!] [N] unload buffer [N] from memory +|:bdelete| :bdelete[!] [N] unload buffer [N] and delete it from + the buffer list + + in current window in new window +|:buffer| :[N]buffer [N] :[N]sbuffer [N] to arg/buf N +|:bnext| :[N]bnext [N] :[N]sbnext [N] to Nth next arg/buf +|:bNext| :[N]bNext [N] :[N]sbNext [N] to Nth previous arg/buf +|:bprevious| :[N]bprevious [N] :[N]sbprevious [N] to Nth previous arg/buf +|:brewind| :brewind :sbrewind to first arg/buf +|:blast| :blast :sblast to last arg/buf +|:bmodified| :[N]bmod [N] :[N]sbmod [N] to Nth modified buf +------------------------------------------------------------------------------ +*bars* Bars example + +Now that you've jumped here with CTRL-], g, or , you +can use CTRL-T, g, or to go back to where you were. +------------------------------------------------------------------------------ + +For the most recent information about Vim: *www* *faq* *FAQ* + VIM home page: + VIM FAQ: + +Bug reports: *bugs* + Bram Moolenaar +Please be brief; all the time that is spend on answering mail is subtracted +from the time that is spent on improving Vim! Always give a reproducible +example and try to find out which settings or other things influence the +appearance of the bug. Try different machines, if possible. Send me patches +if you can! In case of doubt, include the output of these commands: + :version + :!uname -a " Unix only + :map + :map! + :au + :set all + :set termcap + +Usenet News group where Vim is discussed: *news* *usenet* + comp.editors + + *vimdev* *mail_list* +There are three mailing lists for Vim: + For discussions about using existing versions + of Vim: Useful mappings, questions, answers, + where to get a specific version, etc. + For discussions about changing Vim: New + features, porting, etc. + Announcements about new versions of Vim; also + beta-test versions and ports to different + systems. + +NOTE: You can only send messages to these lists if you have subscribed! Also: +you need to send the messages from the same location as the one you subscribed +from (yes, Majordomo is not flexible). + +If you want to join, send a message to + +and put "info" in the body. Then Majordomo will give you a little help. + +An archive is kept at: + + + + +*credits* *author* + +Most of Vim was written by Bram Moolenaar . + +Parts of the documentation come from several Vi manuals, written by: + W.N. Joy + Alan P.W. Hewett + Mark Horton + +The Vim editor is based on Stevie and includes (ideas from) other software, +worked on by the people mentioned here. Other people helped by giving me +suggestions and discussing what is good and bad in Vim. + + Tony Andrews Stevie + Gert van Antwerpen changes for DJGPP on MS-DOS + Berkeley DB(3) ideas for swapfile + Keith Bostic nvi + Ralf Brown SPAWNO library for MS-DOS + Robert Colon many useful remarks + Kayhan Demirel sent me news in Uganda + Chris & John Downey xvi (ideas for multi-windows version) + Eric Fischer Mac port, 'cindent', and other improvements + Bill Foster Athena GUI port + Loic Grenie xvim (ideas for multi windows version) + Steve Kirkendall Elvis + Sergey Laskavy Vim's help from Moscow + Avner Lottem Edit in right-to-left windows (RIGHTLEFT) + George V. Reilly Win32 port + Stephen Riehm bug collector + Olaf Seibert DICE version and regexp improvements + Peter da Silva termlib + Paul Slootman OS/2 port + Henry Spencer regular expressions + Tim Thompson Stevie + G. R. (Fred) Walter Stevie + Robert Webb Command-line completion, GUI version, and lots + of patches + Juergen Weigert Lattice version, AUX improvements, UNIX and + MS-DOS ports, autoconf + +I wish to thank all the people that sent me bug reports and suggestions. The +list is too long to mention them all here. Vim would not be the same without +the ideas from all these people: they keep Vim alive! + + vim:ts=8:sw=8:js:tw=78:fo=tcq2:isk=!-~,^*,^\|,^\": diff --git a/usr.bin/vim/doc/vim_idx.txt b/usr.bin/vim/doc/vim_idx.txt new file mode 100644 index 00000000000..7b15ce40236 --- /dev/null +++ b/usr.bin/vim/doc/vim_idx.txt @@ -0,0 +1,949 @@ +*vim_idx.txt* For Vim version 4.2. Last modification: 1996 June 16 + +This file contains a list of all commands for each mode, with a tag and a +short description. The list is sorted on ASCII value. + +When looking for certain functionality use the search command. For example, +to look for deleting something, use: "/delete". + + Sections: +|insert_index| 1. Insert mode +|normal_index| 2. Normal mode +|objects| 2.1. text objects +|CTRL-W| 2.2. window commands +|[| 2.3. Square bracket commands +|g| 2.4. Commands starting with 'g' +|visual_index| 3. Visual mode +|ex_edit_index| 4. Command-line editing +|ex_cmd_index| 5. EX commands + +For an overview of options see vim_help.txt |option_list|. +For a complete description of each option see vim_ref.txt |options|. + + +1. Insert mode *insert_index* +============== + +tag char action +----------------------------------------------------------------------- +|i_CTRL-@| CTRL-@ insert previously inserted text and stop + insert +|i_CTRL-A| CTRL-A insert previously inserted text +|i_CTRL-B| CTRL-B Only if compiled with TOGGLE_REVINS (which is + not the default): toggle 'revins' (backwards + insert) option. +|i_CTRL-C| CTRL-C quit insert mode, without checking for + abbreviation. +|i_CTRL-D| CTRL-D delete one shiftwidth of indent in the current + line +|i_CTRL-E| CTRL-E insert the character which is below the cursor + CTRL-F not used + CTRL-G not used +|i_| delete character before the cursor +|i_digraph| {char1}{char2} + enter digraph (only when 'digraph' option set) +|i_CTRL-H| CTRL-H same as +|i_| insert a character +|i_CTRL-I| CTRL-I same as +|i_| same as +|i_CTRL-J| CTRL-J same as +|i_CTRL-K| CTRL-K {char1} {char2} + enter digraph + CTRL-L not used +|i_| begin new line +|i_CTRL-M| CTRL-M same as +|i_CTRL-N| CTRL-N find next match for keyword in front of the + cursor +|i_CTRL-O| CTRL-O execute a single command and return to insert + mode +|i_CTRL-P| CTRL-P find previous match for keyword in front of + the cursor +|i_CTRL-Q| CTRL-Q same as CTRL-V (used for terminal control flow) +|i_CTRL-R| CTRL-R <0-9a-z"%:> + insert contents of register <0-9a-z"%:> + CTRL-S (used for terminal control flow) +|i_CTRL-T| CTRL-T insert one shiftwidth of indent in current + line +|i_CTRL-U| CTRL-U delete all entered characters in the current + line +|i_CTRL-V| CTRL-V {char} insert next non-digit literally +|i_CTRL-V_digit| CTRL-V {number} insert three digit decimal number as a single + byte. +|i_CTRL-W| CTRL-W delete word before the cursor +|i_CTRL-X| CTRL-X {mode} enter CTRL-X sub mode, see below +|i_CTRL-Y| CTRL-Y insert the character which is above the cursor + CTRL-Z not used +|i_| end insert mode +|i_CTRL-[| CTRL-[ same as + CTRL-\ not used + CTRL-] not used + CTRL-^ not used +|i_CTRL-_| CTRL-_ change languate (RIGHTLEFT) + + to '~' not used, except '0' and '^' followed by + CTRL-D + +|i_0_CTRL-D| 0 CTRL-D delete all indent in the current line +|i_^_CTRL-D| ^ CTRL-D delete all indent in the current line, restore + it in the next line + +|i_| delete character under the cursor + + Meta characters (0x80 to 0xff, 128 to 255) + not used + +|i_| cursor one line down +|i_| cursor past end of line +|i_| cursor past end of file +|i_| same as +|i_| stop insert mode and display help window +|i_| cursor to start of line +|i_| cursor to start of file +|i_| toggle Insert/Replace mode +|i_| cursor one character left +|i_| cursor at mouse click +|i_| one screenfull forward +|i_| one screenfull backward +|i_| cursor one character right +|i_| same as +|i_| cursor one word left +|i_| cursor one word right +|i_| same as +|i_| cursor one line up + +commands in CTRL-X submode + +|i_CTRL-X_CTRL-D| CTRL-X CTRL-D complete defined identifiers +|i_CTRL-X_CTRL-E| CTRL-X CTRL-E scroll up +|i_CTRL-X_CTRL-F| CTRL-X CTRL-F complete file names +|i_CTRL-X_CTRL-I| CTRL-X CTRL-I complete identifiers +|i_CTRL-X_CTRL-K| CTRL-X CTRL-K complete identifers from dictionary +|i_CTRL-X_CTRL-L| CTRL-X CTRL-L complete whole lines +|i_CTRL-X_CTRL-Y| CTRL-X CTRL-Y scroll down +|i_CTRL-X_CTRL-]| CTRL-X CTRL-] complete tags + + +2. Normal mode *normal_index* +============== + +CHAR any non-blank chararacter +WORD any sequences of non-blank characters +N a number entered before the command +{motion} a cursor movement command +Nmove the text that is moved over with a {motion} +SECTION a section that possibly starts with '}' instead of '{' + +note: 1 = cursor movement command; 2 = can be undone/redone + +tag char note action in Normal mode +------------------------------------------------------------------------------ + CTRL-@ not used +|CTRL-A| CTRL-A 2 add N to number at/after cursor +|CTRL-B| CTRL-B 1 scroll N screens Backwards +|CTRL-C| CTRL-C interrupt current (search) command +|CTRL-D| CTRL-D scroll Down N lines (default: half a screen) +|CTRL-E| CTRL-E scroll N lines upwards (N lines Extra) +|CTRL-F| CTRL-F 1 scroll N screens Forward +|CTRL-G| CTRL-G display current file name and position +|| 1 same as "h" +|CTRL-H| CTRL-H 1 same as "h" +|| 1 go to N newer entry in jump list +|CTRL-I| CTRL-I 1 same as +|| 1 same as "j" +|CTRL-J| CTRL-J 1 same as "j" + CTRL-K not used +|CTRL-L| CTRL-L redraw screen +|| 1 cursor to the first CHAR N lines lower +|CTRL-M| CTRL-M> 1 same as +|CTRL-N| CTRL-N 1 same as "j" +|CTRL-O| CTRL-O 1 go to N older entry in jump list +|CTRL-P| CTRL-P 1 cursor N lines upward + CTRL-Q (used for terminal control flow) +|CTRL-R| CTRL-R 2 redo changes which were undone with 'u' + CTRL-S (used for terminal control flow) +|CTRL-T| CTRL-T jump to N older Tag in tag list +|CTRL-U| CTRL-U scroll N lines Upwards (default: half a + screen) +|CTRL-V| CTRL-V start blockwise Visual +|CTRL-W| CTRL-W {char} window commands, see |CTRL-W| +|CTRL-X| CTRL-X 2 subtract N from number at/after cursor +|CTRL-Y| CTRL-Y scroll N lines downwards +|CTRL-Z| CTRL-Z suspend program (or start new shell) + CTRL-[ not used + CTRL-\ not used +|CTRL-]| CTRL-] :ta to ident under cursor +|CTRL-^| CTRL-^ edit Nth alternate file (equivalent to + ":e #N") + CTRL-_ not used + +|| 1 same as "l" +|!| !{motion}{filter} + 2 filter Nmove text through the {filter} + command +|!!| !!{filter} 2 filter N lines through the {filter} command +|quote| " use buffer for next + delete, yank or put (upper case to append) + (<.%:> only work with put) +|#| # 1 search backward for the Nth occurrence of + the ident under the cursor +|$| $ 1 cursor to the end of Nth next line +|%| % 1 find the next (curly/square) bracket on + this line and go to its match, or go to + matching comment bracket, or go to matching + preprocessor directive. +|N%| {count}% 1 go to N percentage in the file +|&| & 2 repeat last :s +|'| ' 1 cursor to the first CHAR on the line with + mark +|''| '' 1 cursor to the first CHAR of the line where + the cursor was before the latest jump. +|'<| '< 1 cursor to the first CHAR of the line where + highlighted area starts/started +|'>| '> 1 cursor to the first CHAR of the line where + highlighted area ends/ended +|'[| '[ 1 cursor to the first CHAR on the line of the + start of last operated text or start of + putted text +|']| '] 1 cursor to the first CHAR on the line of the + end of last operated text or end of putted + text +|(| ( 1 cursor N sentences backward +|)| ) 1 cursor N sentences forward +|star| * 1 search forward for the Nth occurrence of + the ident under the cursor +|+| + 1 cursor to the first CHAR N lines lower +|,| , 1 repeat latest f, t, F or T in opposite + direction N times +|-| - 1 cursor to the first CHAR N lines higher +|.| . 2 repeat last change with count replaced with + N +|/| /{pattern} 1 search forward for the Nth occurrence of + {pattern} +|/| / 1 search forward for {pattern} of last search +|count| 0 1 cursor to the first char of the line +|count| 1 prepend to command to give a count +|count| 2 " +|count| 3 " +|count| 4 " +|count| 5 " +|count| 6 " +|count| 7 " +|count| 8 " +|count| 9 " +|:| : start entering an Ex command +|N:| {count}: start entering an Ex command with range + from current line to N lines down +|;| ; 1 repeat latest f, t, F or T N times +|<| <{motion} 2 shift Nmove lines one 'shiftwidth' + leftwards +|<<| << 2 shift N lines one 'shiftwidth' leftwards +|=| ={motion} 2 filter Nmove lines through "indent" +|==| == 2 filter N lines through "indent" +|>| >{motion} 2 shift Nmove lines one 'shiftwidth' + rightwards +|>>| >> 2 shift N lines one 'shiftwidth' rightwards +|?| ? 1 search backward for the Nth previous + occurrence of +|?| ? 1 search backward for {pattern} of last search +|@| @ 2 execute the contents of named buffer + N times +|@:| @: repeat the previous ":" command N times +|@@| @@ 2 repeat the previous @ N times +|A| A 2 append text at the end of the line N times +|B| B 1 cursor N WORDS backward +|C| ["x]C 2 change from the cursor position to the end + of the line, and N-1 more lines [into + buffer x]; synonym for "c$" +|D| ["x]D 2 delete the characters under the cursor + until the end of the line and N-1 more + lines [into buffer x]; synonym for "d$" +|E| E 1 cursor forward to the end of WORD N +|F| F{char} 1 cursor to the Nth occurrence of {char} to + the left +|G| G 1 cursor to line N, default last line +|H| H 1 cursor to line N from top of screen +|I| I 2 insert text before the first CHAR on the + line N times +|J| J 2 Join N lines; default is 2 +|K| K lookup Keyword under the cursor with + 'keywordprg' +|L| L 1 cursor to line N from bottom of screen +|M| M 1 cursor to middle line of screen +|N| N 1 repeat the latest '/' or '?' N times in + opposite direction +|O| O 2 begin a new line above the cursor and + insert text, repeat N times +|P| ["x]P 2 put the text [from buffer x] before the + cursor N times +|Q| Q{motion} 2 format Nmove lines (obsolete) +|R| R 2 enter replace mode: overtype existing + characters, repeat the entered text N-1 + times +|S| ["x]S 2 delete N lines [into buffer x] and start + insert; synonym for "^cc" or "0cc", + depending on autoindent +|T| T{char} 1 cursor till after Nth occurrence of {char} + to the left +|U| U 2 undo all latest changes on one line +|V| V start linewise Visual mode +|W| W 1 cursor N WORDS forward +|X| ["x]X 2 delete N characters before the cursor [into + buffer x] +|Y| ["x]Y yank N lines [into buffer x]; synonym for + "yy" +|ZZ| ZZ store current file if modified, and exit +|[| [{char} square bracket command (see below) + \ not used +|]| ]{char} square bracket command (see below) +|^| ^ 1 cursor to the first CHAR of the line +|_| _ 1 cursor to the first CHAR N - 1 lines lower +|`| ` 1 cursor to the mark +|`<| `< 1 cursor to the start of the highlighted area +|`>| `> 1 cursor to the end of the highlighted area +|`[| `[ 1 cursor to the start of last operated text + or start of putted text +|`]| `] 1 cursor to the end of last operated text or + end of putted text +|``| `` 1 cursor to the position before latest jump +|a| a 2 append text after the cursor N times +|b| b 1 cursor N words backward +|c| ["x]c{motion} 2 delete Nmove text [into buffer x] and start + insert +|cc| ["x]cc 2 delete N lines [into buffer x] and start + insert +|d| ["x]d{motion} 2 delete Nmove text [into buffer x] +|dd| ["x]dd 2 delete N lines [into buffer x] +|e| e 1 cursor forward to the end of word N +|f| f{char} 1 cursor to Nth occurrence of {char} to the + right +|g| g{char} extended commands, see below +|h| h 1 cursor N chars to the left +|i| i 2 insert text before the cursor N times +|j| j 1 cursor N lines downward +|k| k 1 cursor N lines upward +|l| l 1 cursor N chars to the right +|m| m set mark at cursor position +|n| n 1 repeat the latest '/' or '?' N times +|o| o 2 begin a new line below the cursor and + insert text, repeat N times +|p| ["x]p 2 put the text [from register x] after the + cursor N times +|q| q<0-9a-zA-Z"> record typed characters into named register + <0-9a-zA-Z"> (upper case to append) +|q| q (while recording) stops recording +|r| r{char} 2 replace N chars with {char} +|s| ["x]s 2 (substitute) delete N characters [into + buffer x] and start insert +|t| t{char} 1 cursor till before Nth occurrence of {char} + to the right +|u| u 2 undo changes +|v| v start characterwise Visual mode +|w| w 1 cursor N words forward +|x| ["x]x 2 delete N characters under and after the + cursor [into buffer x] +|y| ["x]y{motion} yank Nmove text [into buffer x] +|yy| ["x]yy yank N lines [into buffer x] +|z| z redraw, cursor line to top of window, + cursor on first non-blank +|zN| z{height} redraw, make window {height} lines high +|z.| z. redraw, cursor line to center of window, + cursor on first non-blank +|z-| z- redraw, cursor line at bottom of window, + cursor on first non-blank +|zb| zb redraw, cursor line at bottom of window +|ze| ze when 'wrap' off scroll horizontally to + position the cursor at the end (right side) + of the screen +|zh| zh when 'wrap' off scroll screen N characters + to the right +|zl| zl when 'wrap' off scroll screen N characters + to the left +|zs| zs when 'wrap' off scroll horizontally to + position the cursor at the start (left + side) of the screen +|zt| zt redraw, cursor line at top of window +|zz| zz redraw, cursor line at center of window +|z| z same as "zh" +|z| z same as "zl" +|{| { 1 cursor N paragraphs backward +|bar| | 1 cursor to column N +|}| } 1 cursor N paragraphs forward +|~| ~ 2 'tildeop' off: switch case of N characters + under cursor and move the cursor N + characters to the right +|~| ~{motion} 'tildeop' on: switch case of Nmove text +|| ":ta" to the keyword at the mouse click +|| same as "CTRL-T" +|| ["x] 2 same as "x" +|N| {count} remove the last digit from {count} +|| 1 same as "j" +|| 1 same as "$" +|| 1 same as "G" +|| same as +|| open a help window +|| 1 same as "0" +|| 1 same as "gg" +|| 2 same as "i" +|| 1 same as "h" +|| 1 move cursor to the mouse click position +|| 2 same as "P" at the mouse click position +|| same as CTRL-F +|| same as CTRL-B +|| 1 same as "l" +|| start Visual mode, move cursor to the mouse + click position +|| 1 same as CTRL-F +|| 1 same as "b" +|| same as "*" at the mouse click position +|| 1 same as "w" +|| same as "#" at the mouse click position +|| 1 same as CTRL-B +|| 2 same as "u" +|| 1 same as "k" + + +2.1 text objects *objects* + +These can be used after an operator or in Visual mode to select an object +|v_a| a a word (with white space) +|v_A| A a WORD (with white space) +|v_p| p a paragraph +|v_s| s a sentence +|v_P| P a block from "[{" to "]}" +|v_S| S a block from "[(" and "])" + + +2.2 window commands *CTRL-W* + +tag command action in Normal mode +------------------------------------------------------------------------------ +|CTRL-W_CTRL-B| CTRL-W CTRL-B same as "CTRL-W b" +|CTRL-W_CTRL-C| CTRL-W CTRL-C same as "CTRL-W c" +|CTRL-W_CTRL-D| CTRL-W CTRL-D same as "CTRL-W d" +|CTRL-W_CTRL-F| CTRL-W CTRL-F same as "CTRL-W f" +|CTRL-W_CTRL-I| CTRL-W CTRL-I same as "CTRL-W i" +|CTRL-W_CTRL-J| CTRL-W CTRL-J same as "CTRL-W j" +|CTRL-W_CTRL-K| CTRL-W CTRL-K same as "CTRL-W k" +|CTRL-W_CTRL-N| CTRL-W CTRL-N same as "CTRL-W n" +|CTRL-W_CTRL-O| CTRL-W CTRL-O same as "CTRL-W o" +|CTRL-W_CTRL-P| CTRL-W CTRL-P same as "CTRL-W p" +|CTRL-W_CTRL-Q| CTRL-W CTRL-Q same as "CTRL-W q" +|CTRL-W_CTRL-R| CTRL-W CTRL-R same as "CTRL-W r" +|CTRL-W_CTRL-S| CTRL-W CTRL-S same as "CTRL-W s" +|CTRL-W_CTRL-T| CTRL-W CTRL-T same as "CTRL-W t" +|CTRL-W_CTRL-W| CTRL-W CTRL-W same as "CTRL-W w" +|CTRL-W_CTRL-X| CTRL-W CTRL-X same as "CTRL-W x" +|CTRL-W_CTRL-]| CTRL-W CTRL-] same as "CTRL-W ]" +|CTRL-W_CTRL-^| CTRL-W CTRL-^ same as "CTRL-W ^" +|CTRL-W_CTRL-_| CTRL-W CTRL-_ same as "CTRL-W _" +|CTRL-W_+| CTRL-W + increase current window height N lines +|CTRL-W_-| CTRL-W - decrease current window height N lines +|CTRL-W_=| CTRL-W = make all windows the same height +|CTRL-W_R| CTRL-W R rotate windows upwards N times +|CTRL-W_S| CTRL-W S same as "CTRL-W s" +|CTRL-W_W| CTRL-W W go to N previous window (wrap around) +|CTRL-W_]| CTRL-W ] split window and jump to tag under cursor +|CTRL-W_^| CTRL-W ^ split current window and edit alternate + file N +|CTRL-W__| CTRL-W _ set current window height to N (default: + very high) +|CTRL-W_b| CTRL-W b go to bottom window +|CTRL-W_c| CTRL-W c close current window (like ":close") +|CTRL-W_d| CTRL-W d split window and jump to definition under + the cursor +|CTRL-W_f| CTRL-W f split window and edit filename under the + cursor +|CTRL-W_i| CTRL-W i split window and jump to declaration of + identifier under the cursor +|CTRL-W_j| CTRL-W j go to N next window (stop at last window) +|CTRL-W_k| CTRL-W k go to N previous window (stop at first + window) +|CTRL-W_n| CTRL-W n open new window, N lines high +|CTRL-W_o| CTRL-W o close all but current window (like ":only") +|CTRL-W_p| CTRL-W p go to previous (last accessed) window +|CTRL-W_q| CTRL-W q quit current window (like ":quit") +|CTRL-W_r| CTRL-W r rotate windows downwards N times +|CTRL-W_s| CTRL-W s split current window in two parts, new + window N lines high +|CTRL-W_t| CTRL-W t go to top window +|CTRL-W_w| CTRL-W w go to N next window (wrap around) +|CTRL-W_x| CTRL-W x exchange current window with window N + (default: next window) +|CTRL-W_| CTRL-W same as "CTRL-W j" +|CTRL-W_| CTRL-W same as "CTRL-W k" + + +2.3 Square bracket commands *[* *]* + +tag char note action in Normal mode +------------------------------------------------------------------------------ +|[_CTRL-D| [_CTRL-D jump to first #define found in current and + included files matching the word under the + cursor, start searching at beginning of + current file +|[_CTRL-I| [_CTRL-I jump to first line in current and included + files that contains the word under the + cursor, start searching at beginning of + current file +|[#| [# 1 cursor to N previous unmatched #if, #else + or #ifdef +|[(| [( 1 cursor N times back to unmatched '(' +|[star| [* 1 same as "[/" +|[/| [/ 1 cursor to N previous start of a C comment +|[D| [D list all defines found in current and + included files matching the word under the + cursor, start searching at beginning of + current file +|[I| [I list all lines found in current and + included files that contain the word under + the cursor, start searching at beginning of + current file +|[P| [P 2 same as "[p" +|[[| [[ 1 cursor N sections backward +|[]| [] 1 cursor N SECTIONS backward +|[d| [d show first #define found in current and + included files matching the word under the + cursor, start searching at beginning of + current file +|[f| [f same as "gf" +|[i| [i show first line found in current and + included files that contains the word under + the cursor, start searching at beginning of + current file +|[p| [p 2 like "P", but adjust indent to current line +|[{| [{ 1 cursor N times back to unmatched '{' +|[ [ 2 same as "[p" + +|]_CTRL-D| ]_CTRL-D jump to first #define found in current and + included files matching the word under the + cursor, start searching at cursor position +|]_CTRL-I| ]_CTRL-I jump to first line in current and included + files that contains the word under the + cursor, start searching at cursor position +|]#| ]# 1 cursor to N next unmatched #endif or #else +|])| ]) 1 cursor N times forward to unmatched ')' +|]star| ]* 1 same as "]/" +|]/| ]/ 1 cursor to N next end of a C comment +|]D| ]D list all #defines found in current and + included files matching the word under the + cursor, start searching at cursor position +|]I| ]I list all lines found in current and + included files that contain the word under + the cursor, start searching at cursor + position +|]P| ]P 2 same as "[p" +|][| ][ 1 cursor N SECTIONS forward +|]]| ]] 1 cursor N sections forward +|]d| ]d show first #define found in current and + included files matching the word under the + cursor, start searching at cursor position +|]f| ]f same as "gf" +|]i| ]i show first line found in current and + included files that contains the word under + the cursor, start searching at cursor + position +|]p| ]p 2 like "p", but adjust indent to current line +|]}| ]} 1 cursor N times forward to unmatched '}' +|] ] 2 same as "]p" + + +2.4 Commands starting with 'g' *g* + +tag char note action in Normal mode +------------------------------------------------------------------------------ +|g#| g# 1 like "#", but without using "\<" and "\>" +|g$| g$ 1 when 'wrap' off go to rightmost character of + the current line that is on the screen; + when 'wrap' on go to the rightmost character + of the current screen line +|gstar| g* 1 like "*", but without using "\<" and "\>" +|g0| g0 1 when 'wrap' off go to leftmost character of + the current line that is on the screen; + when 'wrap' on go to the leftmost character + of the current screen line +|gD| gD 1 goto definiton of word under the cursor in + current file +|gE| gE 1 go backwards to the end of the previous + WORD +|gI| gI 2 like "I", but always start in column 1 +|gU| gU{motion} 2 make Nmove text uppercase +|g^| g^ 1 when 'wrap' off go to leftmost non-white + character of the current line that is on + the screen; when 'wrap' on go to the + leftmost non-white character of the current + screen line +|g_CTRL-G| g_CTRL-G show information about current cursor + position +|ga| ga print ascii value of character under the + cursor +|gd| gd 1 goto definiton of word under the cursor in + current function +|ge| ge 1 go backwards to the end of the previous + word +|gf| gf start editing the file whose name is under + the cursor +|gg| gg 1 cursor to line N, default first line +|gj| gj 1 like "j", but when 'wrap' on go N screen + lines down +|gk| gk 1 like "k", but when 'wrap' on go N screen + lines up +|gq| gq{motion} 2 format Nmove text (same as "Q") +|gs| gs goto sleep for N seconds (default 1) +|gu| gu{motion} 2 make Nmove text lowercase +|gv| gv reselect the previous Visual area +|g~| g~{motion} 2 swap case for Nmove text +|g| g 1 same as "gj" +|g| g 1 same as "g$" +|g| g 1 same as "g0" +|g| g same as +|g| g same as +|g| g 1 same as "gk" + + +3. Visual mode *visual_index* +============== + +Most commands in Visual mode are the same as in Normal mode. The ones listed +here are those that are different. + +tag command note action in Visual mode +------------------------------------------------------------------------------ +|v_CTRL-V| CTRL-V make Visual mode blockwise or stop Visual + mode +|v_CTRL-]| CTRL-] jump to highlighted tag +|v_!| !{filter} 2 filter the highlighted lines through the + external command {filter} +|v_:| : start a command-line with the highlighted + lines as a range +|v_<| < 2 shift the highlighted lines one + 'shiftwidth' left +|v_=| = 2 filter the highlighted lines through the + external program given with the 'equalprg' + option +|v_>| > 2 shift the highlighted lines one + 'shiftwidth' right +|v_A| A extend the highlighted area with a WORD +|v_C| C 2 delete the highlighted lines and start + insert +|v_D| D 2 delete the highlighted lines +|v_J| J 2 join the highlighted lines +|v_K| K run 'keywordprg' on the highlighted text +|v_P| P extend the highlighted text with a {} block +|v_Q| Q 2 format the highlighted lines (obsolete) +|v_R| R 2 delete the highlighted lines and start + insert +|v_S| S extend the highlighted text with a () block +|v_U| U 2 make highlighted text uppercase +|v_V| V make Visual mode linewise or stop Visual + mode +|v_X| X 2 delete the highlighted lines +|v_Y| Y yank the highlighted lines +|v_a| a extend the highlighted area with a word +|v_c| c 2 delete the highlighted text and start insert +|v_d| d 2 delete the highlighted text +|v_gq| gq 2 format the highlighted lines +|v_gv| gv exchange current and previous highlighted + text +|v_o| o move cursor to other end of hightlighted + text +|v_p| p extend the highlighted area with a paragraph +|v_r| r 2 delete the highlighted text and start insert +|v_s| s extend the highlighted area with a sentence +|v_u| u 2 make highlighted text lowercase +|v_v| v make Visual mode characterwise or stop + Visual mode +|v_x| x 2 delete the highlighted text +|v_y| y yank the highlighted text +|v_~| ~ 2 swap case for the highlighted text + + +4. Command-line editing *ex_edit_index* +======================= + +Get to the command-line with the ':', '!', '/' or '?' commands. +Normal characters are inserted at the current cursor position. +"Completion" below refers to context-sensitive completion. It will complete +filenames, tags, commands etc. as appropriate. + + CTRL-@ not used +|c_CTRL-A| CTRL-A do completion on the pattern in front of the + cursor and insert all matches +|c_CTRL-B| CTRL-B cursor to begin of command-line +|c_CTRL-C| CTRL-C same as +|c_CTRL-D| CTRL-D list completions that match the pattern in + front of the cursor +|c_CTRL-E| CTRL-E cursor to end of command-line + CTRL-F not used + CTRL-G not used +|c_| delete the character in front of the cursor +|c_digraph| {char1} {char2} + enter digraph when 'digraph' is on +|c_CTRL-H| CTRL-H same as +|c_| if 'wildchar' is : Do completion on + the pattern in front of the cursor +|c_| same as CTRL-P +|c_wildchar| 'wildchar' Do completion on the pattern in front of the + cursor (default: ) +|c_CTRL-I| CTRL-I same as +|c_| same as +|c_CTRL-J| CTRL-J same as +|c_CTRL-K| CTRL-K {char1} {char2} + enter digraph +|c_CTRL-L| CTRL-L do completion on the pattern in front of the + cursor and insert the longest common part +|c_| execute entered command +|c_| CTRL-M same as +|c_CTRL-N| CTRL-N after using 'wildchar' with multiple matches: + go to next match, otherwise: same as + CTRL-O not used +|c_CTRL-P| CTRL-P after using 'wildchar' with multiple matches: + go to previous match, otherwise: same as +|c_CTRL-Q| CTRL-Q same as CTRL-V (used for terminal control flow) +|c_CTRL-R| CTRL-R <0-9a-z"%:> + insert contents of register <0-9a-z"%:> + CTRL-S (used for terminal control flow) + CTRL-T not used +|c_CTRL-U| CTRL-U remove all characters +|c_CTRL-V| CTRL-V insert next non-digit literally, insert three + digit decimal number as a single byte. +|c_CTRL-W| CTRL-W delete the word in front of the cursor + CTRL-X not used + CTRL-Y not used + CTRL-Z not used +|c_| abandon command-line without executing it +|c_| CTRL-[ same as + CTRL-\ not used + CTRL-] not used + CTRL-^ not used +|c_CTRL-_| CTRL-_ change languate (RIGHTLEFT) +|c_| delete the character under the cursor +|c_| recall next command-line from history that + matches pattern in front of the cursor +|c_| cursor to end of command-line +|c_| cursor to start of command-line +|c_| toggle insert/overstrike mode +|c_| cursor left +|c_| cursor at mouse click +|c_| same as +|c_| same as +|c_| cursor right +|c_| recall next command-line from history +|c_| cursor one word left +|c_| cursor one word right +|c_| recall previous command-line from history +|c_| recall previous command-line from history that + matches pattern in front of the cursor + + +5. EX commands *ex_cmd_index* +============== + +This is a brief listing of all the ":" commands, without mentioning any +arguments. + +|:!| :! filter lines or execute an external command +|:!!| :!! repeat last ":!" command +|:#| :# same as ":number" +|:&| :& repeat last ":substitute" +|:<| :< shift lines one 'shiftwidth' left +|:=| := print the cursor line number +|:>| :> shift lines one 'shiftwidth' right +|:@| :@ execute contents of a register +|:@@| :@@ repeat the previous ":@" +|:Next| :N[ext] go to previous file in the argument list +|:append| :a[ppend] append text (not implemented) +|:abbreviate| :ab[breviate] enter abbreviation +|:abclear| :abc[lear] remove all abbreviations +|:all| :al[l] open a window for each file in the argument + list +|:args| :ar[gs] print the argument list +|:argument| :argu[ment] go to specific file in the argument list +|:ascii| :a[scii] print ascii value of character under the + cursor +|:autocmd| :au[tocmd] enter or show autocommands +|:buffer| :b[uffer] go to specific buffer in the buffer list +|:bNext| :bN[ext] go to next buffer in the buffer list +|:ball| :ba[ll] open a window for each file in the buffer list +|:bdelete| :bd[elete] delete specific files from the buffer list +|:blast| :bl[ast] go to last file in the buffer list +|:bmodified| :bm[odified] go to next file in the buffer list that has + been modified +|:bnext| :bn[ext] go to next file in the buffer list +|:bprevious| :bp[revious] go to previous file in the buffer list +|:brewind| :br[ewind] go to last file in the buffer list +|:buffers| :buffers list all files in the buffer list +|:bunload| :bun[load] unload a specific buffer +|:change| :c[hange] replace a line (not implemented) +|:cNext| :cN[ext] go to previous error +|:cabbrev| :ca[bbrev] like ":abbreviate" but for command-line mode +|:cabclear| :cabc[lear] clear all abbreviations for command-line mode +|:cc| :cc go to specific error +|:cd| :cd change directory +|:center| :ce[nter] format lines at the center +|:cfile| :cf[ile] read the file with error messages +|:chdir| :chd[ir] change directory +|:checkpath| :che[ckpath] list included files +|:clist| :cl[ist] list all errors +|:close| :clo[se] close current window +|:cmap| :cm[ap] like ":map" but for command-line mode +|:cmapclear| :cmapc[lear] clear all mappings for command-line mode +|:cnext| :cn[ext] go to next error +|:cnoremap| :cno[remap] like ":noremap" but for command-line mode +|:cnoreabbrev| :cnorea[bbrev] like ":noreabbrev" but for command-line mode +|:copy| :co[py] copy lines +|:cprevious| :cp[revious] go to previous error +|:cquit| :cq[uit] quit Vim with an error code +|:cunmap| :cu[nmap] like ":unmap" but for command-line mode +|:cunabbrev| :cuna[bbrev] like ":unabbrev" but for command-line mode +|:delete| :d[elete] delete lines +|:display| :di[splay] display registers +|:digraphs| :dig[raphs] show or enter digraphs +|:djump| :dj[ump] jump to #define +|:dlist| :dl[ist] list #defines +|:doautocmd| :do[autocmd] apply autocommands +|:dsearch| :ds[earch] list one #define +|:dsplit| :dsp[lit] split window and jump to #define +|:edit| :e[dit] edit a file +|:ex| :ex same as ":edit" +|:exit| :exi[t] same as ":xit" +|:file| :f[ile] show or set the current file name +|:files| :files list all files in the buffer list +|:fixdel| :fix[del] set key code of +|:global| :g[lobal] execute commands for matching lines +|:gui| :gu[i] start the GUI +|:gvim| :gv[im] start the GUI +|:help| :h[elp] open a help window +|:insert| :i[nsert] insert text (not implemented) +|:iabbrev| :ia[bbrev] like ":abbrev" but for Insert mode +|:iabclear| :iabc[lear] like ":abclear" but for Insert mode +|:ijump| :ij[ump] jump to definition of identifier +|:ilist| :il[ist] list lines where identifier matches +|:imap| :im[ap] like ":map" but for Insert mode +|:imapclear| :imapc[lear] like ":mapclear" but for Insert mode +|:inoremap| :ino[remap] like ":noremap" but for Insert mode +|:inoreabbrev| :inorea[bbrev] like ":noreabbrev" but for Insert mode +|:isearch| :is[earch] list one line where identifier matches +|:isplit| :isp[lit] split window and jump to definition of + identifier +|:iunmap| :iu[nmap] like ":unmap" but for Insert mode +|:iunabbrev| :iuna[bbrev] like ":unabbrev" but for Insert mode +|:join| :j[oin] join lines +|:jumps| :ju[mps] print the jump list +|:k| :k set a mark +|:list| :l[ist] print lines +|:last| :la[st] go to the last file in the argument list +|:left| :le[ft] left align lines +|:ls| :ls list all buffers +|:move| :m[ove] move lines +|:mark| :ma[rk] set a mark +|:make| :mak[e] execute external command 'makeprg' and parse + error messages +|:map| :map show or enter a mapping +|:mapclear| :mapc[lear] clear all mappings for Normal and Visual mode +|:marks| :marks list all marks +|:menu| :me[nu] enter a new menu item +|:mkexrc| :mk[exrc] write current mappings and settings to a file +|:mkvimrc| :mkv[imrc] write current mappings and settings to a file +|:mode| :mod[e] show or change the screen mode +|:next| :n[ext] go to next file in the argument list +|:new| :new create a new empty window +|:nmap| :nm[ap] like ":map" but for Normal mode +|:nmapclear| :nmapc[lear] clear all mappings for Normal mode +|:nnoremap| :nn[oremap] like ":noremap" but for Normal mode +|:noremap| :no[remap] enter a mapping that will not be remapped +|:noreabbrev| :norea[bbrev] enter an abbreviation that will not be + remapped +|:normal| :norm[al] execute Normal mode commands +|:number| :nu[mber] print lines with line number +|:nunmap| :nun[map] like ":unmap" but for Normal mode +|:open| :o[pen] start open mode (not implemented) +|:only| :on[ly] close all windows except current one +|:print| :p[rint] print lines +|:pop| :po[p] jump to older entry in tag stack +|:preserve| :pre[serve] write all text to swap file +|:previous| :prev[ious] go to previous file in argument list +|:put| :pu[t] insert contents of register in the text +|:pwd| :pw[d] print current directory +|:quit| :q[uit] quit current window or Vim +|:qall| :qa[ll] quit Vim +|:read| :r[ead] read file into the text +|:recover| :rec[over] recover a file from a swap file +|:redo| :red[o] redo one undone change +|:registers| :reg[isters] display the contents of registers +|:resize| :res[ize] change current window height +|:retab| :ret[ab] change tab size +|:rewind| :rew[ind] go to the first file in the argument list +|:right| :ri[ght] right align text +|:rviminfo| :rv[iminfo] read from viminfo file +|:substitute| :s[ubstitute] find and replace text +|:sNext| :sN[ext] split window and go to previous file in + argument list +|:sargument| :sa[rgument] split window and go to specific file in + argument list +|:sall| :sal[l] open a window for each file in argument list +|:sbuffer| :sb[uffer] split window and go to specific file in the + buffer list +|:sbNext| :sbN[ext] split window and go to previous file in the + buffer list +|:sball| :sba[ll] open a window for each file in the buffer list +|:sblast| :sbl[ast] split window and go to last file in buffer + list +|:sbmodified| :sbm[odified] split window and go to modified file in the + buffer list +|:sbnext| :sbn[ext] split window and go to next file in the buffer + list +|:sbprevious| :sbp[revious] split window and go to previous file in the + buffer list +|:sbrewind| :sbr[ewind] split window and go to first file in the + buffer list +|:set| :se[t] show or set options +|:shell| :sh[ell] escape to a shell +|:sleep| :sl[eep] do nothing for a few seconds +|:slast| :sla[st] split window and go to last file in the + argument list +|:snext| :sn[ext] split window and go to next file in the + argument list +|:source| :so[urce] read Vim or Ex commands from a file +|:split| :sp[lit] split current window +|:sprevious| :spr[evious] split window and go to previous file in the + argument list +|:srewind| :sr[ewind] split window and go to first file in the + argument list +|:stop| :st[op] suspend the editor or escape to a shell +|:stag| :sta[g] split window and jump to a tag +|:sunhide| :sun[hide] same as ":unhide" +|:suspend| :sus[pend] same as ":stop" +|:sview| :sv[iew] split window and edit file read-only +|:swapname| :sw[apname] show the name of the current swap file +|:t| :t same as ":copy" +|:tag| :ta[g] jump to tag +|:tags| :tags show the contents of the tag stack +|:undo| :u[ndo] undo last change(s) +|:unabbreviate| :una[bbreviate] remove abbreviation +|:unhide| :unh[ide] open a window for each loaded file in the + buffer list +|:unmap| :unm[ap] remove mapping +|:version| :ve[rsion] print version number and other info +|:vglobal| :v[global] execute commands for not matching lines +|:visual| :vi[sual] same as ":edit" +|:view| :vie[w] edit a file read-only +|:vmap| :vm[ap] like ":map" but for Visual mode +|:vmapclear| :vmapc[lear] remove all mappings for Visual mode +|:vnoremap| :vn[oremap] like ":noremap" but for Visual mode +|:vunmap| :vu[nmap] like ":unmap" but for Visual mode +|:write| :w[rite] write to a file +|:wNext| :wN[ext] write to a file and go to previous file in + argument list +|:wall| :wa[ll] write all (changed) buffers +|:wnext| :wn[ext] write to a file and go to next file in + argument list +|:wprevious| :wp[revious] write to a file and go to previous file in + argument list +|:wq| :wq write to a file and quit window or Vim +|:wqall| :wqa[ll] write all changed buffers and quit Vim +|:wviminfo| :wv[iminfo] write to viminfo file +|:xit| :x[it] write if buffer changed and quit window or Vim +|:xall| :xa[ll] same as ":wqall" +|:yank| :y[ank] yank lines into a register +|:z| :z print some lines (not implemented) +|:~| :~ repeat last ":substitute" diff --git a/usr.bin/vim/doc/vim_kcc.txt b/usr.bin/vim/doc/vim_kcc.txt new file mode 100644 index 00000000000..57884877629 --- /dev/null +++ b/usr.bin/vim/doc/vim_kcc.txt @@ -0,0 +1,124 @@ +*vim_kcc.txt* *uganda* *copying* + +Vim is Charityware. There are no restrictions on using or copying Vim, but I +encourage you to make a donation to charity, see below. If you include Vim on +a CD-ROM you should send me a copy. + +If you are happy with Vim, please express that by reading the rest of this +file. + + +Summer 1993 I spent my holidays in Uganda, working for Kibaale Children's +Centre (KCC) with a Dutch team. I was very impressed with what I experienced +there. Of course I knew Africa from books and television, but actually being +there and talking and working with the people is very different. January 1994 +I spent another month there, which made me realize that I could be of help to +these people. Summer 1994 to summer 1995 I spent a whole year at the centre, +working as a volunteer. I have helped to expand the centre and worked in the +area of water and sanitation. + +KCC is located in Kibaale, a small town in the south of Uganda, near Tanzania. +The area is known as Rakai District. The population is mostly farmers. +Although people are poor, there is enough food. But this district is +suffering from AIDS more than any other part of the world. Some say that it +started there. Estimations are that 10 to 30% of the Ugandans are infected +with HIV. Because parents die, there are many orphans. In this district +about 60.000 children have lost one or both parents, out of a population of +350.000. And this is still continuing. + +The children need a lot of help. The Kibaale Children Centre is working hard +to provide the needy with food, medical care and education. Food and medical +care to keep them healthy now, and education so that they can take care of +themselves in the future. KCC works on a Christian base, but help is given to +children of any religion. + +The key to solving the problems in this area is education. This has been +neglected in the past years with president Idi Amin and the following civil +wars. Now that the government is stable again the children and parents have +to learn how to take care of themselves and how to avoid infections. There is +also help for people who are ill and hungry, but the primary goal is to +prevent people from getting ill and to learn how to grow healthy food. + +Most of the orphans are living in an extended family. An uncle or older +sister is taking care of them. Because these families are big and the income +(if any) is low, a child is lucky if it gets healthy food. Clothes, medical +care and schooling is beyond its reach. To help these needy children a +sponsorship program was put into place. A child can be financially adopted. +For a few dollars a month KCC sees to it that the child gets indispensable +items, is healthy, goes to school and KCC takes care of anything else that +needs to be done for the child and the family that supports him. + +Besides helping the child directly, the environment where the child grows up +needs to be improved. KCC helps schools to improve their teaching methods. +There is a demonstration school at the centre and teacher trainings are given. +Health workers are being trained, hygiene education is carried out and +households are stimulated to build a proper latrine. I helped setting up a +production site for cement slabs. These are used to built a good latrine. +They are sold at a very low price. + +A few times a week a health team goes out into small villages to do +immunization, treatment and education. KCC is the only organization in the +area that can provide the transport for this. Patients would otherwise have +to travel hours by bicycle, if they have one. There is no ambulance in the +area and they don't have money to hire a taxi (if there is one). For most +people KCC is the only help they can get. + +Now that I'm back in Holland, I would like to continue supporting KCC. To do +this I'm raising funds and organizing the sponsorship program. Please +consider one of these options: + +1. Sponsor a child: $15 a month. (Holland: fl 25) +2. Sponsor a child and the improvement of its environment: $25 a month + (Holland: fl 40) +3. Sponsor the health team: Any amount a month or per quarter +4. A one-time donation + +Compared with other organizations that do child sponsorship the amounts are +very low. This is because the money goes directly to the centre. Less than +5% is used for administration. This is possible because this is a small +organization that works with volunteers. If you would like to sponsor a +child, you should have the intention to do this for at least a year. + +How do you know that the money will be spent right? First of all you have my +personal guarantee as the author of Vim. Further the centre is co-sponsored +and inspected by World Vision, Save the Children Fund and International Child +Care Fund. I have worked with the centre as a volunteer from September 1994 +to August 1995. I trust the people that are working there. + +If you have any further questions, send me e-mail: mool@oce.nl. + +The director of the centre is: + Sekaran Vellasamy + p.o. box 1658 + Masaka, Uganda, East Africa + +Sending money: + +USA and Canada: Contact ICCF in Vancouver. You can send them a one time + donation directly. Ask for information about sponsorship. + International Child Care Fund + 3456 Fraser street + Vancouver, B.C. V5V 4C4 + Canada + Fax: 604-8761448 + +Holland: Use one of my accounts: + Rabobank Venlo, nr. 3765.05.117 + Postbank, nr. 1644503 + +Europe: To avoid banking costs the best thing is to send me a + Eurocheque, written out to "Bram Moolenaar" in Dutch Guilders + (fl). But any other method should work. Ask for information + about sponsorship. + stichting ICCF Holland + Bram Moolenaar + Clematisstraat 30 + 5925 BE Venlo + The Netherlands. + +Others: Transfer the money to one of my accounts if possible. + Otherwise, send a cheque in US dollars to the above address. + Minimal amount: $70 (my bank does not accept smaller amounts + for foreign cheques) + +Bram Moolenaar diff --git a/usr.bin/vim/doc/vim_mac.txt b/usr.bin/vim/doc/vim_mac.txt new file mode 100644 index 00000000000..8f92e8d9e85 --- /dev/null +++ b/usr.bin/vim/doc/vim_mac.txt @@ -0,0 +1,14 @@ +*vim_mac.txt* For Vim version 3.0. Last modification: 1996 May 22 + +There was a Mac port for version 3.0 of Vim. Unfortunately 4.0 has not been +ported yet. I hope it will be available at a later time. This file will be +included then. Here are the first few lines from the old file: + +VIM Release Notes +Initial Macintosh release, VIM version 3.0 +19 October 1994 + +Eric Fischer +enf1@midway.uchicago.edu, eric@jcp.uchicago.edu, etaoin@uchicago.edu +5759 N. Guilford Ave +Indianapolis IN 46220 USA diff --git a/usr.bin/vim/doc/vim_menu.txt b/usr.bin/vim/doc/vim_menu.txt new file mode 100644 index 00000000000..24801b33942 --- /dev/null +++ b/usr.bin/vim/doc/vim_menu.txt @@ -0,0 +1,69 @@ +" *vim_menu.txt* +" These menu commands should recreate the default Vim menus. +" You can use this as a start for your own set of menus. +" The colons at the start of each line are just to indicate these are colon +" commands, they could be omitted. +" If the and string appear literally in the output of ":menu", you +" need to remove the '<' flag from 'cpoptions' |'cpoptions'| + +" First remove any menus that are currently present +:unmenu * +:unmenu! * + +" Help menu +" Note that the help commands use to leave Insert/Visual/Command-line +" mode +:nmenu Help.Overview\ \ :help +:vmenu Help.Overview\ \ :help +:menu! Help.Overview\ \ :help + +:nmenu Help.How\ to\.\.\. :help how_to +:vmenu Help.How\ to\.\.\. :help how_to +:menu! Help.How\ to\.\.\. :help how_to + +:nmenu Help.GUI :help gui +:vmenu Help.GUI :help gui +:menu! Help.GUI :help gui + +:nmenu Help.Version :version +:vmenu Help.Version :version +:menu! Help.Version :version + +:nmenu Help.Credits :help credits +:vmenu Help.Credits :help credits +:menu! Help.Credits :help credits + +:nmenu Help.Copying :help uganda +:vmenu Help.Copying :help uganda +:menu! Help.Copying :help uganda + +" File menu +:nmenu File.Save\ \ \ \ \ \ \ :w :w +:imenu File.Save\ \ \ \ \ \ \ :w :w + +:nmenu File.Close\ \ \ \ \ \ :q :q +:vmenu File.Close\ \ \ \ \ \ :q :q +:menu! File.Close\ \ \ \ \ \ :q :q + +:nmenu File.Quit\ \ \ \ \ \ \ :qa :qa +:vmenu File.Quit\ \ \ \ \ \ \ :qa :qa +:menu! File.Quit\ \ \ \ \ \ \ :qa :qa + +:nmenu File.Save-Quit\ \ :wqa :wqa +:vmenu File.Save-Quit\ \ :wqa :wqa +:menu! File.Save-Quit\ \ :wqa :wqa + +" Edit menu +:nmenu Edit.Undo u +:nmenu Edit.Redo + +:vmenu Edit.Cut x +:vmenu Edit.Copy y + +:nmenu Edit.Put\ Before [p +:imenu Edit.Put\ Before [p +:nmenu Edit.Put\ After ]p +:imenu Edit.Put\ After ]p + +:nmenu Edit.Paste i* +:menu! Edit.Paste * diff --git a/usr.bin/vim/doc/vim_mint.txt b/usr.bin/vim/doc/vim_mint.txt new file mode 100644 index 00000000000..6ae58bb2be0 --- /dev/null +++ b/usr.bin/vim/doc/vim_mint.txt @@ -0,0 +1,32 @@ +*vim_mint.txt* For Vim version 4.2. Last modification 1996 June 11 + +This file contains the particularities for the Atari MiNT version of Vim. + +For compiling Vim on the Atari running MiNT see "INSTALL" and "Makefile" +in the src directory. + +Vim for MiNT behaves almost exactly like the Unix version. +The Unix behavior described in the documentation also refers to the +MiNT version of Vim unless explicitly stated otherwise. + +For wildcard expansion of <~> (home directory) you need a shell that +expands the tilde. The vanilla Bourne shell doesn't recognize it. +With csh and ksh it should work OK. + +The MiNT version of vim needs the termcap file /etc/termcap with the +terminal capabilities of your terminal. Builtin termcaps are +supported for the vt52 terminal. Termcap entries for the TOSWIN window +manager and the virtual console terminals have been appended to the +termcap file that comes with the Vim disrribution. + +If you should encounter problems with swapped and keys, see +|:fixdel|. + +Because terminal updating under MiNT is often slow (e.g. serial line +terminal), the 'showcommand' and 'ruler' options are default off. +If you have a fast terminal, try setting them on. You might +also want to set 'ttyfast'. + +Send bug reports to + + Jens M. Felderhoff, e-mail: jmf@infko.uni-koblenz.de diff --git a/usr.bin/vim/doc/vim_os2.txt b/usr.bin/vim/doc/vim_os2.txt new file mode 100644 index 00000000000..988a785a598 --- /dev/null +++ b/usr.bin/vim/doc/vim_os2.txt @@ -0,0 +1,96 @@ +*vim_os2.txt* For Vim version 4.2. Last modification: 1996 June 13 + +This file contains the particularities for the OS/2 version of Vim. + + +Note: This OS/2 port works well for me and a couple of other OS/2 users; + however, since I haven't had much feedback, that either means no + (OS/2-specific) bugs exist (besides the one mentioned below), or no one + has yet created a situation in which any bugs are apparent. Report any + problems or other comments to paul@wau.mis.ah.nl (email valid up to at + least September 1996, after that try paul@wurtel.hobby.nl, + paul@murphy.nl, or paulS@toecompst.nl). Textmode/notextmode, binary + mode, and FAT handling all seem to work well, which would seem to be the + most likely places for trouble. + + A known problem is that files opened by Vim are inherited by other + programs that are started via a shell escape from within Vim. + This specifically means that Vim won't be able to remove the swap + file(s) associated with buffers open at the time the other program was + started, until the other program is stopped. At that time, the swap file + may be removed, but if Vim could not do that the first time, it won't be + removed at all. You'll get warnings that some other Vim session may be + editing the file when you start Vim up again on that file. This can be + reproduced with ":!start epm". Now quit Vim, and start Vim again with + the file that was in the buffer at the time epm was started. I'm + working on this! + + +Prerequisites: + +- To run Vim, you need the emx runtime environment (at least rev. 0.9b). This + is generally available as (ask Archie about it): + + emxrt.zip emx runtime package + + I've included a copy of emx.dll, which should be copied to one of the + directories listed in your LIBPATH. Emx is GPL'ed, but the emx.dll library + is not (read COPYING.EMX to find out what that means to you). + + This emx.dll is from the emxfix04.zip package, which unfortunately has a bug + in select(). Versions of Vim before 3.27 will appear to hang when starting + (actually, while processing vimrc). Hit return a couple of times until Vim + starts working if this happens. Next, get an up to date version of Vim! + +The VIM environment variable is used to find the location of the help files +and the system .vimrc. Place an entry such as this in CONFIG.SYS: + +SET VIM=c:/local/lib/vim + +and copy all the vim_*.txt files and .vimrc to that directory. Vim will then +be able to find them. + +Note: .vimrc may also be called _vimrc to accommodate those who have chosen to +install OS/2 on a FAT file system. Vim first tries to find .vimrc and if that +fails, looks for _vimrc in the same place. + +If you're using network drives with OS/2, then you can install Vim on a +network drive (including .vimrc; this is then called the "system" vimrc file), +and then use a personal copy of .vimrc (the "user" vimrc file). This should be +located in a directory indicated by the HOME environment variable. + +This HOME environment variable is also used when using ~ in file names, so +":e ~/textfile" will edit the file "textfile" in the directory referred to by +HOME. Additionally you can use other environment variables in file names, as +as ":n $SRC/*.c". + +If the HOME environment variable is not set, the value "C:/" is used as a +default. + +Using slashes ('/') and backslashes ('\') can be a bit of a problem (see +vim_dos.txt for more explanation), but in almost all cases Vim does "The Right +Thing". Vim itself uses backslashes in file names, but will happily accept +forward slashes if they are entered (in fact, often that works better!). + +Use "os2ansi" as the TERM environment variable (or don't set it at all, as the +default is the correct value). You can set term to os2ansi in the .vimrc, in +case you need TERM to be a different value for other applications. The +problem is that OS/2 ANSI emulation is quite limited (it doesn't have insert / +delete line, for example). + +If you want to use a different value for TERM (because of other programs, for +example), make sure that the termcap entry for that TERM value has the +appropriate key mappings. The termcap.dat distributed with emx does not always +have them. Here are some suitable values to add to the termcap entry of your +choice; these allow the cursor keys and the named function keys (such as +pagedown) to work. + + :ku=\316H:kd=\316P:kl=\316K:kr=\316M:%i=\316t:#4=\316s:\ + :kD=\316S:kI=\316R:kN=\316Q:kP=\316I:kh=\316G:@7=\316O:\ + :k1=\316;:k2=\316<:k3=\316=:k4=\316>:k5=\316?:k6=\316@:\ + :k7=\316A:k8=\316B:k9=\316C:k;=\316D: + + +Paul Slootman + +vim:ts=8:tw=78: diff --git a/usr.bin/vim/doc/vim_ref.txt b/usr.bin/vim/doc/vim_ref.txt new file mode 100644 index 00000000000..e9358ebe02d --- /dev/null +++ b/usr.bin/vim/doc/vim_ref.txt @@ -0,0 +1,9855 @@ +*vim_ref.txt* For Vim version 4.2. Last modification: 1996 June 17 + + + VIM REFERENCE MANUAL + + By Bram Moolenaar + + + +There is a contents listing at the end of this document. + +This manual mostly assumes that there is only one window. The commands and +options for multiple windows and buffers are explained in |vim_win.txt|. + +This manual describes running Vim from a normal terminal or with a terminal +emulator in a window. See |vim_gui.txt| for the GUI version. + +You can use tags to jump to the explanation of a subject. Position the cursor +on an item name between bars (e.g., |intro|) or an option name in single +quotes (e.g., 'textwidth' ) and hit CTRL-]. This mostly also works on a +command in double quotes (e.g., ":buf"). Three special characters in the +names of the tags are not allowed. They have been replaced with letters: +"bar" for '|', "star" for '*', and "quote" for '"'. + + tag starts with example + Normal and Visual mode nothing |x| + Visual mode "v_" |v_u| + Insert mode "i_" |i_| + Command line commands ":" |:quit| + Command line editing "c_" |c_| + Vim command options "-" |-r| + Vim options "'" |'shell'| + + +1. Introduction *intro* +=============== + +Vim stands for Vi IMproved. It used to be Vi IMitation, but there are so many +improvements that a name change was appropriate. Vim is a text editor which +includes almost all the commands from the Unix program "Vi" and a lot of new +ones. It is very useful for editing programs and other 8-bit ASCII text. All +commands are given with the keyboard. This has the advantage that you can +keep your fingers on the keyboard and your eyes on the screen. For those who +want it, there is mouse support and a GUI version with scrollbars and menus +(see |vim_gui.txt|). + +Throughout this manual the differences between Vi and Vim are mentioned in +curly braces. See |vim_diff.txt| for a summary of the differences. + +This manual refers to Vim on various machines. There may be small differences +between different computers and terminals. Besides the remarks given in this +document, there is a separate document for each supported system: + system see + Amiga |vim_ami.txt| + Archimedes |vim_arch.txt| + Atari MiNT |vim_mint.txt| + Macintosh |vim_mac.txt| + MS-DOS |vim_dos.txt| + OS/2 |vim_os2.txt| + Unix |vim_unix.txt| + Win32: Windows NT / Windows 95 |vim_w32.txt| + +This manual is a reference for all the Vim commands and options. This is not +an introduction to the use of Vim. There are many books on vi that contain a +section for beginners. + +A summary of this manual can be found in the file "vim_help.txt", +|vim_help.txt|. It can be accessed from within Vim with the or +key and with the command ":help", |:help|. The 'helpfile' option can be set +to the name of the help file, so you can put it in any place you like. + + +2. Notation *notation* +=========== + +[] Characters in square brackets are optional. + + *count* +[count] An optional number that may precede the command to multiply + or iterate the command. If no number is given, a count of one + is used, unless otherwise noted. Note that in this manual the + [count] is not mentioned in the description of the command, + but only in the explanation. This was done to make the + commands easier to lookup. If the "sc" option is on + (|'showcmd'|), the (partially) entered count is shown at the + bottom of the window. You can use to erase the last + digit (|N|). + +["x] An optional register designation where text can be stored. + See |registers|. The x is a single character between 'a' and + 'z' or 'A' and 'Z' or '"', and in some cases (with the put + command) between '0' and '9', '%', ':', or '.'. The uppercase + and lowercase letters designate the same register, but the + lowercase letter is used to overwrite the previous register + contents, while the uppercase letter is used to append to the + previous register contents. Without the ""x" or with """", + the stored text is put into the unnamed register. + +{} Curly braces denote parts of the command which must appear, + but which can take a number of different values. The + differences between Vim and Vi are also given in curly braces + (this will be clear from the context). + + *{motion}* +{motion} A command that moves the cursor. See the list in chapter 6, + |cursor_motions|. This is used after an operator command + |operator| to move over the text that is to be operated upon. + If the motion includes a count and the operator also had a + count, the two counts are multiplied. For example: "2d3w" + deletes six words. The motion can also be a mouse click. The + mouse is currently only supported for MS-DOS, Win32 and xterm + under Unix. + + *{Visual}* +{Visual} A piece of text that is started with the "v", "V", or CTRL-V + command and ended by the cursor position. This is used + before an operator command |operator| to highlight the text + that is to be operated upon. See the chapter on Visual mode + |Visual_mode|. + + A special character from the table below or a single ASCII + character. + +'character' A single ASCII character. + + A single character from the range to . For + example: is a lowercase letter. Multiple ranges may be + concatenated. For example, is any alphanumeric + character. + +CTRL-{char} {char} typed as a control character; that is, typing {char} + while holding the CTRL key down. The case of {char} does not + matter; thus CTRL-A and CTRL-a are equivalent. + +'option' An option, or parameter, that can be set to a value, is + enclosed in single quotes. See chapter 19, |options|. + +"command" In examples, the commands you can type are enclosed in double + quotes. + + *key_notation* +notation meaning equivalent decimal value(s) +----------------------------------------------------------------------- + zero CTRL-@ 0 (stored as 10) + backspace CTRL-H 8 *backspace* + tab CTRL-I 9 *tab* + *linefeed* + linefeed CTRL-J 10 (used for ) + *carriage return* + carriage return CTRL-M 13 + escape CTRL-[ 27 *escape* + space 32 *space* + delete 127 + + cursor-up *cursor-up* *cursor_up* + cursor-down *cursor-down* *cursor_down* + cursor-left *cursor-left* *cursor_left* + cursor-right *cursor-right* *cursor_right* + shift-cursor-up + shift-cursor-down + shift-cursor-left + shift-cursor-right + - function keys 1 to 12 *function_key* *function-key* + - shift-function keys 1 to 12 + help key + undo key + insert key + home *home* + end *end* + page-up *page_up* *page-up* + page-down *page_down* *page-down* + shift-key *shift* + control-key *control* *ctrl* + alt-key or meta-key *meta* *alt* + key with "xx" entry in termcap +----------------------------------------------------------------------- + +Note: The shifted cursor keys, the help key, and the undo key are only +available on a few terminals. On the Amiga, shifted function key 10 produces +a code (CSI) that is also used by key sequences. It will be recognized only +after typing another key. + +Note: There are two codes for the delete key. 127 is the ASCII value for the +delete key, which is always recognized. Some delete keys send another value, +in which case this value is obtained from the termcap entry "kD". Both values +have the same effect. Also see |:fixdel|. + + *<>* +Some of the examples are given in the <> notation. The rules are: + 1. Any printable characters are typed directly, except backslash and '<' + 2. A backslash is represented with "\\", double backslash. + 3. A real '<' is represented with "\<". + 4. "" means the special key typed. This is the notation explained in + the table above. A few examples: + Escape key + CTRL-G + cursor up key + Control- left mouse click + Shifted function key 11 + Meta- a ('a' with bit 8 set) + Meta- A ('A' with bit 8 set) + "kd" termcap entry (cursor down key) + +If you want to use this notation in Vim, you have to remove the 'B' flag from +'cpoptions' and make sure the '<' flag is excluded (it already is by default). + :set cpo=ceFs +For mapping, abbreviation and menu commands you can then copy-paste the +examples and use them directly. Or type them literally, including the '<' and +'>' characters. This does NOT work for other commands, like ":set" and +":autocmd"! + + +3. Starting Vim *starting* +=============== + +3.1 Vim arguments *vim_arguments* + +Most often, Vim is started to edit a single file with the command + + vim file *-vim* + +More generally, Vim is started with: + + vim [options] [arglist] + +If the arglist is missing, the editor will start with an empty buffer. +Otherwise exactly one out of the following three may be used to choose one +or more files to be edited. + + *-file* *--* +file .. A list of file names. The first one will be the current file + and read into the buffer. The cursor will be positioned on + the first line of the buffer. To avoid a file name starting + with a '-' being interpreted as an option, precede the arglist + with "--", e.g.: + Vim -- -filename + + *-t* *-tag* +-t {tag} A tag. "tag" is looked up in the tags file, the associated + file becomes the current file, and the associated command is + executed. Mostly this is used for C programs. In that case, + "tag" should be a function name. The effect is that the file + containing that function becomes the current file and the + cursor is positioned on the start of the function (see the + section on tags, |tags|). + + *-e* *-qf* +-e [errorfile] QuickFix mode. The file with the name [errorfile] is read + and the first error is displayed. If [errorfile] is not + given, the 'errorfile' option is used for the file name + (default "AztecC.Err" for the Amiga, "errors.vim" for other + systems). See section 5.5: "using the QuickFix mode", + |quickfix|. {not in Vi} + + *startup-options* +The options, if present, must precede the arglist. The options may be given +in any order. Single-letter options can be combined after one dash. + + *-+* ++[num] The cursor will be positioned on line "num" for the first + file being edited. If "num" is missing, the cursor will be + positioned on the last line. + + *-+/* ++/{pat} The cursor will be positioned on the first line containing + "pat" in the first file being edited (see the section + "pattern searches" for the available search patterns, + |search_pattern|). + ++{command} *-+c* *-c* +-c {command} "command" will be executed after the first file has been + read (and after autocommands and modelines for that file have + been processed). "command" is interpreted as an Ex command. + If the "command" contains spaces, it must be enclosed in + double quotes (this depends on the shell that is used). + Example: vim "+set si" main.c + + Note: You can use only one "+" or "-c" argument in a Vim + command. + + *-r* +-r Recovery mode. Without a file name argument, a list of + existing swap files is given. With a file name, a swap file + is read to recover a crashed editing session. See the + chapter "Recovery after a crash", |crash_recovery|. + + *-L* +-L Same as -r. {only in some versions of Vi: "List recoverable + edit sessions"} + + *-v* +-v View mode. The 'readonly' option will be set for all the + files being edited. You can still edit the buffer, but will + be prevented from accidentally overwriting a file. If you + forgot that you are in View mode and did make some changes, + you can overwrite a file by adding an exclamation mark to + the Ex command, as in ":w!". The 'readonly' option can be + reset with ":set noro" (see the options chapter, |options|). + Subsequent edits will not be done in readonly mode. Calling + the executable "view" has the same effect as the -v option. + If your system does not support links and you do not want to + have the executable twice, you could make an alias: "alias + view vim -v". The 'updatecount' option will be set to 10000, + meaning that the swap file will not be updated automatically + very often. {Vi: "ex -v" means to start ex in vi mode. + "vi -v" does nothing} + + *-R* +-R Readonly mode. Same as -v. + + *-b* +-b Binary mode. The 'textauto', 'textmode', and 'expandtab' + options will be reset. The 'textwidth' option is set to 0. + 'modeline' is reset. The 'binary' option is set. This is + done after reading the vimrc/exrc files but before reading + any file in the arglist. See also 5.6: + "Editing binary files", |edit_binary|. {not in Vi} + + *-l* +-l Lisp mode. Sets the 'lisp' and 'showmatch' options on. + + *-H* +-H Hebrew mode. Sets the 'hkmap' and 'rightleft' options on. + (Only when compiled with LEFTRIGHT defined, otherwise Vim + gives an error message and exits). {not in Vi} + + *-n* +-n No swap file will be used. Recovery after a crash will be + impossible. Handy if you want to view or edit a file on a + very slow medium (e.g., a floppy). Can also be done with + ":set updatecount=0". You can switch it on again by setting + the 'updatecount' option to some value, e.g., ":set uc=100". + {not in Vi} + + *-o* +-o[N] Open N windows. If [N] is not given, one window is opened + for every file given as argument. If there is not enough + room, only the first few files get a window. If there are + more windows than arguments, the last few windows will be + editing an empty file. {not in Vi} + + *-T* +-T {terminal} Set the terminal type to "terminal". This influences the + codes that Vim will send to your terminal. This is normally + not needed, because Vim will be able to find out what type + of terminal you are using (See chapter 20, |terminal_info|). + {not in Vi} + + *-d* +-d {device} Amiga only: The "device" is opened to be used for editing. + Normally you would use this to set the window position and + size: "-d con:x/y/width/height", e.g., + "-d con:30/10/600/150". But you can also use it to start + editing on another device, e.g., AUX:. {not in Vi} + + *-x* +-x Amiga only: Do not restart Vim to open a new window. This + option should be used when Vim is started by a program that + will wait for the edit session to finish (e.g., mail or + readnews). See section 3.3, |amiga_window|. {not in Vi} + + *-f* +-f GUI only: Do not disconnect from the program that started Vim. + 'f' stands for "foreground". If omitted, the GUI forks a new + process and exits the current one. "-f" should be used when + gvim is started by a program that will wait for the edit + session to finish (e.g., mail or readnews). If you want gvim + never to fork, include 'f' in 'guioptions'. Careful: You can + use "-gf" to start the GUI in the foreground, but "-fg" is + used to specify the foreground color. {not in Vi} |gui_fork| + + *-u* +-u {vimrc} The file "vimrc" is read for initializations. Other + initializations are skipped; see |initialization|. This can + be used to start Vim in a special mode, with special + mappings and settings. A shell alias can be used to make + this easy to use. For example: + "alias vimc vim -u ~/.c_vimrc !*". + Also consider using autocommands; see |autocommand|. + When {vimrc} is equal to "NONE" (all uppercase), all + initializations from files and environment variables are + skipped. {not in Vi} + + *-i* +-i {viminfo} The file "viminfo" is used instead of the default viminfo + file. If the name "NONE" is used (all uppercase), no viminfo + file is read or written, even if 'viminfo' is set or when + ":rv" or ":wv" are used. See also |viminfo_file|. {not in Vi} + + *-s* +-s {scriptin} The script file "scriptin" is read. The characters in the + file are interpreted as if you had typed them. The same can + be done with the command ":source! {scriptin}". If the end + of the file is reached before the editor exits, further + characters are read from the keyboard. See also the section + "complex repeats", |complex_repeat|. {not in Vi} + + *-w* +-w {scriptout} All the characters that you type are recorded in the file + "scriptout", until you exit Vim. This is useful if you want + to create a script file to be used with "vim -s" or + ":source!". When the "scriptout" file already exists, new + characters are appended. See also the section "complex + repeats", |complex_repeat|. {not in Vi} + + *-W* +-W {scriptout} Like -w, but do not append, overwrite an existing file. {not + in Vi} + + *-w_nr* +-w{number} Does nothing. This was included for Vi-compatibility. In Vi + it sets the 'window' option, which is not implemented in Vim. + +Example for using a script file to change a name in several files: + Create a file "subs.vi" containing substitute commands and a :wq + command: + + :%s/Jones/Smith/g + :%s/Allen/Peter/g + :wq + + Execute Vim on all files you want to change: + + foreach i ( *.let ) vim -s subs.vi $i + +If the executable is called "view", Vim will start in Readonly mode. This is +useful if you can make a hard or symbolic link from "view" to "vim". +Starting in Readonly mode can also be done with "vim -v". + + +3.2 Workbench (Amiga only) *workbench* + +Vim can be started from the workbench by clicking on its icon twice. It will +then start with an empty buffer. + +Vim can be started to edit one or more files by using a "Project" icon. The +"Default Tool" of the icon must be the full pathname of the Vim executable. +The name of the ".info" file must be the same as the name of the text file. +By clicking on this icon twice, Vim will be started with the filename as +current filename, which will be read into the buffer (if it exists). You can +edit multiple files by pressing the shift key while clicking on icons, and +clicking twice on the last one. The "Default Tool" for all these icons must +be the same. + +It is not possible to give arguments to Vim, other than filenames, from the +workbench. + + +3.3 Vim window (Amiga only) *amiga_window* + +Vim will run in the CLI window where it was started. If Vim was started with +the "run" or "runback" command, or if Vim was started from the workbench, it +will open a window of its own. + +Technical detail: + To open the new window a little trick is used. As soon as Vim + recognizes that it does not run in a normal CLI window, it will + create a script file in "t:". This script file contains the same + command as the one Vim was started with, and an "endcli" command. This + script file is then executed with a "newcli" command (the "c:run" and + "c:newcli" commands are required for this to work). The script file + will hang around until reboot, or until you delete it. This method + is required to get the ":sh" and ":!" commands to work correctly. + But when Vim was started with the -e option (Quickfix mode) or with + the -x option, this method is not used. The reason for this is that + when a compiler starts Vim with the -e option it will wait for a + return code. With the script trick, the compiler cannot get the + return code. The -x option can be used when Vim is started by a mail + program which also waits for the edit session to finish. As a + consequence, the ":sh" and ":!" commands are not available when the + -e or -x option is used. + +Vim will automatically recognize the window size and react to window +resizing. Under Amiga DOS 1.3, it is advised to use the fastfonts program, +"FF", to speed up display redrawing. + + +3.4 Initialization *initialization* *startup* + +This section is about the non-GUI version of Vim. See |gui_fork| for +additional initialization when starting the GUI. + +At startup, Vim checks environment variables and files and sets values +accordingly. Vim proceeds in this order: + +1. Setting the 'shell' option *SHELL* *COMSPEC* + The environment variable SHELL, if it exists, is used to set the + 'shell' option. On MS-DOS and Win32, the COMPSPEC variable is used + if SHELL is not set. + +2. Setting the 'term' option *TERM* + The environment variable TERM, if it exists, is used to set the 'term' + option. + +3. Reading Ex commands from environment variables and/or files + An environment variable is read as one Ex command line, where multiple + commands must be separated with '|' or "". + *vimrc* *exrc* + A file that contains initialization commands is called a "vimrc" file. + Each line in a vimrc file is executed as an Ex command line. It is + sometimes also referred to as "exrc" file. They are the same type of + file, but "exrc" is what Vi always used, "vimrc" is a Vim specific + name. + + If Vim was started with "-u filename", the file "filename" is used. + All following initializations until 4. are skipped. + "vim -u NONE" can be used to skip these initializations. |-u| + + a. For Unix the system vimrc file is read for initializations. The path + of this file is shown with the ":version" command. + + *VIMINIT* *.vimrc* *_vimrc* *EXINIT* *.exrc* *_exrc* + b. Four places are searched for initializations. The first that exists + is used, the others are ignored. + - The environment variable VIMINIT + - The user vimrc file: + "~/.vimrc" (for Unix and OS/2) + "s:.vimrc" (for Amiga) + "$VIM\_vimrc" (for MS-DOS and Win32) + Note: For Unix, OS/2 and Amiga, when ".vimrc" does not exist, + "_vimrc" is also tried, in case an MS-DOS compatible file + system is used. For MS-DOS and Win32 ".vimrc" is checked + after "_vimrc", in case long file names are used. + If $VIM is not set, $HOME is used. + - The environment variable EXINIT + - The user exrc file: + "~/.exrc" (for Unix and OS/2) + "s:.exrc" (for Amiga) + "$VIM\_exrc" (for MS-DOS and Win32). + + c. If the 'exrc' option is on (which is not the default), the current + directory is searched for three files. The first that exists is used, + the others are ignored. + - The file ".vimrc" (for Unix, Amiga and OS/2) + "_vimrc" (for MS-DOS and Win32) + - The file "_vimrc" (for Unix, Amiga and OS/2) + ".vimrc" (for MS-DOS and Win32) + - The file ".exrc" (for Unix, Amiga and OS/2) + "_exrc" (for MS-DOS and Win32) + +4. Setting 'shellpipe' and 'shellredir' + The 'shellpipe' and 'shellredir' options are set according to the + value of the 'shell' option, unless they have been set before. + This means that Vim will figure out the values of 'shellpipe' and + 'shellredir' for you, unless you have set them yourself. + +5. Read the viminfo file + If the 'viminfo' option is not empty, the viminfo file is read. The + default is empty, so 'viminfo' must have been set by one of the + previous initializations. See |viminfo_file|. + +Some hints on using initializations: + +Standard setup: +Create a vimrc file to set the default settings and mappings for all your edit +sessions. Put it in a place so that it will be found by 3b: + ~/.vimrc (Unix and OS/2) + s:.vimrc (Amiga) + $VIM\_vimrc (MS-DOS and Win32) + +Local setup: +Put all commands that you need for editing a specific directory only into a +vimrc file and place it in that directory under the name ".vimrc" ("_vimrc" +for MS-DOS and Win32). NOTE: To make Vim look for these special files you +have to turn on the option 'exrc'. See |trojan_horse| too. + +System setup: +This only applies if you are managing a Unix system with several users and +want to set the defaults for all users. Create a vimrc file with commands +for default settings and mappings and put it in the place that is given with +the ":version" command. + +Saving the current state of Vim to a file: +Whenever you have changed values of options or when you have created a +mapping, then you may want to save them in a vimrc file for later use. See +|save_settings| about saving the current state of settings to a file. + +Avoiding setup problems for Vi users: +Vi uses the variable EXINIT and the file "~/.exrc". So if you do not want to +interfere with Vi, then use the variable VIMINIT and the file "vimrc" instead. + +Amiga environment variables: +On the Amiga, two types of environment variables exist. The ones set with the +DOS 1.3 (or later) setenv command are recognized. See the AmigaDos 1.3 +manual. The environment variables set with the old Manx Set command (before +version 5.0) are not recognized. + +MS-DOS line separators: +On MS-DOS-like systems (MS-DOS itself, Win32, and OS/2), Vim assumes that all +the vimrc files have pairs as line separators. This will give +problems if you have a file with only s and have a line like +":map xx yy^M". The trailing ^M will be ignored. + +Avoiding trojan horses: *trojan_horse* +While reading the "vimrc" or the "exrc" file in the current directory, some +commands can be disabled for security reasons by setting the 'secure' option. +Otherwise it would be possible that you accidentally use a vimrc file that +somebody else created and contains nasty commands. The disabled commands are +the ones that start a shell, the ones that write to a file, and ":autocmd". +The ":map" commands are echoed, so you can see which keys are being mapped. + If you want Vim to execute all commands in a local vimrc file, you +can reset the 'secure' option in the EXINIT or VIMINIT environment variable or +in the global "exrc" or "vimrc" file. This is not possible in "vimrc" or +"exrc" in the current directory, for obvious reasons. + On Unix systems, this only happens if you are not the owner of the +vimrc file. Warning: If you unpack an archive that contains a vimrc or exrc +file, it will be owned by you. You won't have the security protection. Check +the vimrc file before you start Vim in that directory, or reset the 'exrc' +option. Some Unix systems allow a user to do "chown" on a file. This makes +it possible for another user to create a nasty vimrc and make you the owner. +Be careful! + + *slow_start* +If Vim takes a long time to start up, there may be a few causes: +- If the Unix version was compiled with the GUI and/or X11 (check the output + of ":version" for "+GUI" and "+X11"), it may need to load shared libraries + and connect to the X11 server. Try compiling a version with GUI and X11 + disabled. This also should make the executable smaller. +- If you have "viminfo" enabled, the loading of the viminfo file may take a + while. You can find out if this is the problem by disabling viminfo for a + moment (use the Vim argument "-i NONE", |-i|). Try reducing the number of + lines stored in a register with ":set viminfo='20\"50". + |viminfo_file|. + + +3.5 Suspending *suspend* + + *CTRL-Z* *v_CTRL-Z* +CTRL-Z On Unix systems: Suspend Vim. On other systems: + start a new shell (like ":sh"). Same as ":stop". + Works in Normal and in Visual mode. In Insert and + Command-line mode, the CTRL-Z is inserted as a normal + character. + + +:sus[pend][!] or *:sus* *:suspend* *:st* *:stop* +:st[op][!] Suspend the editor. If the '!' is not given, the + buffer was changed, 'autowrite' is set, and a filename + is known, the buffer will be written. + +On many Unix systems, it is possible to suspend Vim with CTRL-Z. This is only +possible in Normal and Visual mode (see next chapter, |vim_modes|). Vim will +continue if you make it the foreground job again. On other systems, CTRL-Z +will start a new shell. This is the same as the ":sh" command. Vim will +continue if you exit from the shell. + + +3.6 The viminfo file *viminfo_file* + +The viminfo file is used to store: +- The command line history. +- The search string history. +- Contents of registers. +- Marks for several files. +- File marks, pointing to locations in files. +- Last search/substitute pattern (for 'n' and '&'). + +The viminfo file is only supported when Vim has been compiled with VIMINFO +defined. If the output of ":version" contains "+viminfo" then it was; if it +contains "-viminfo" then it wasn't. By default, VIMINFO is defined. + +When Vim is started and the 'viminfo' option is non-empty, the contents of +the viminfo file are read and the info can be used in the appropriate places. +The marks are not read in at startup (but file marks are). See +|initialization| for how to set the 'viminfo' option upon startup. + +When Vim is exited and 'viminfo' is non-empty, the info is stored in the +viminfo file (it's actually merged with the existing one, if one exists). The +'viminfo' option is a string containing information about what info should be +stored, and contains limits on how much should be stored (see 'viminfo'). + +Marks are stored for each file separately. When a file is read and 'viminfo' +is non-empty, the marks for that file are read from the viminfo file. NOTE: +The marks are only written when exiting Vim, which is fine because marks are +remembered for all the files you have opened in the current editing session, +unless ":bdel" is used. If you want to save the marks for a file that you are +about to abandon with ":bdel", use ":wv". The '[' and ']' marks are not +stored, but the '"' mark is. The '"' mark is very useful for jumping to the +cursor position when the file was last exited. No marks are saved for files +that start with any string given with the "r" flag in 'viminfo'. This can be +used to avoid saving marks for files on removable media (for MS-DOS you would +use "ra:,rb:", for Amiga "rdf0:,rdf1:,rdf2:"). + + *viminfo_file_marks* +Uppercase marks ('A to 'Z) are stored when writing the viminfo file. The +numbered marks ('0 to '9) are a bit special. When the viminfo file is written +(when exiting or with the ":wviminfo" command), '0 is set to the current cursor +position and file. The old '0 is moved to '1, '1 to '2, etc. This +resembles what happens with the "1 to "9 delete registers. If the current +cursor position is already present in '0 to '9, it is moved to '0, to avoid +having the same position twice. The result is that with "'0", you can jump +back to the file and line where you exited Vim. + +The default name of the viminfo file is "$HOME/.viminfo" for Unix, +"s:.viminfo" for Amiga, "$VIM\viminfo" for MS-DOS and Win32. The "-i" Vim +argument can be used to set another file name, |-i|. For the commands below, +another file name can be given, overriding the default and the name given with +"-i". When the file name given with the "-i" Vim argument is "NONE" +(all uppercase), no viminfo file is ever read or written. + +Two commands can be used to read and write the viminfo file manually. This +can be used to exchange registers between two running Vim programs: First +type ":wv" in one and then ":rv" in the other. Note that if the register +already contained something, then ":rv!" would be required. Also note +however that this means everything will be overwritten with information from +the first Vim, including the command line history, etc. + +The viminfo file itself can be edited by hand too, although we suggest you +start with an existing one to get the format right. It is reasonably +self-explanatory once you're in there. This can be useful in order to +create a second file, say "~/.my_viminfo" which could contain certain +settings that you always want when you first start Vim. For example, you +can preload registers with particular data, or put certain commands in the +command line history. A line in your .vimrc file like + rviminfo! ~/.my_viminfo +can be used to load this information. You could even have different viminfos +for different types of files (e.g., C code) and load them based on the file +name, using the ":autocmd" command (see |:autocmd|). + + *:rv* *:rviminfo* +:rv[iminfo][!] [file] Read from viminfo file [file] (default: see above). + If [!] is given, then any information that is + already set (registers, marks, etc.) will be + overwritten. {not in Vi} + + *:wv* *:wviminfo* +:wv[iminfo][!] [file] Write to viminfo file [file] (default: see above). + The information in the file is first read in to make + a merge between old and new info. When [!] is used, + the old information is not read first, only the + internal info is written. If 'viminfo' is empty, marks + for up to 100 files will be written. {not in Vi} + + +4. Modes *vim_modes* +======== + +4.1 Introduction + +Vim has four BASIC modes: + +Normal mode In Normal mode you can enter all the editor + commands. If you start the editor you are in this + mode (unless you have set the 'insertmode' option, + see below). This is also known as command mode. + +Visual mode This is like Normal mode, but the movement commands + extend a highlighted area. When a non-movement + command is used, it is executed for the highlighted + area. See |Visual_mode|. + +Insert mode In Insert mode the text you type is inserted into the + buffer. If the 'showmode' option is on (which is + default), the string "-- INSERT --" is shown at the + bottom of the window. |mode_ins_repl| + +Command-line mode In Command-line mode you can enter one line of text + at the bottom of the window. This is for the Ex + commands, ":", the pattern search commands, "?" and + "/", and the filter command, "!". |mode_cmdline| + +There are two ADDITIONAL modes: + +Replace mode Replace mode is a special case of Insert mode. You + can do the same things as in Insert mode, but for + each character you enter, one character of the existing + text is deleted. If the 'showmode' option is on, + (which is the default), the string "-- REPLACE --" is + shown at the bottom of the window. |replace_mode| + +Insert command mode Entered when CTRL-O given in Insert mode. This is + like Normal mode, but after executing one command Vim + returns to Insert mode. The string "-- (insert) --" + is shown at the bottom of the window. + +4.2 Switching from mode to mode *mode_switching* + +If for any reason you do not know which mode you are in, you can always get +back to Normal mode by typing twice. You will know you are back in +Normal mode when you see the screen flash or hear the bell after you type +. + +- go from Normal mode to Visual mode by giving one of the commands "vV^V" +- go from Normal mode to Insert mode by giving one of the commands + "iIaAoOcCsS". +- go from Normal mode to Replace mode with the "R" command (not the "r" + command!). +- go from Normal mode to Command-line mode with the one of the commands + ":/?!". + + *i_esc* +- go from Insert or Replace mode to Normal mode with (twice in some + rare cases). +- go from Visual mode to Normal mode by giving a non-movement command, which + causes the command to be executed, or by hitting or 'v', which does + nothing. +- go from Command-line mode to Normal mode by: + - hitting or , which causes the entered command to be executed + - deleting the complete line (e.g., with CTRL-U) and giving a final + - hitting CTRL-C or , which quits the command line without executing + the command. + In the last case may be the character defined with the 'wildchar' + option, in which case it will start command line completion. You can ignore + that and type again. {Vi: when hitting the command line is + executed. This is unexpected for most people; therefore it was changed in + Vim. But when the is part of a mapping, the command line is executed. + If you want the Vi behaviour also when typing , use ":cmap ^V + ^V^M"} + +- go from Insert mode to Replace mode by hitting . +- go from Replace mode to Insert mode by hitting . +- go from Visual mode to Command-line mode by hitting ':'. The line numbers + of the highlighted area will be inserted in the command line. + +If the 'insertmode' option is on, editing a file will start in Insert mode. + + +4.3 Insert and Replace mode *mode_ins_repl* + +If you are working in a special language mode when inserting text, see the +'langmap' option, |'langmap'|, on how to avoid switching this mode on and off +all the time. + +4.3.1 special keys *ins_special_keys* + +In Insert and Replace mode, the following characters have a special meaning; +other characters are inserted directly. To insert one of these special +characters into the buffer, precede it with CTRL-V. To insert a +character use "CTRL-V CTRL-@" or "CTRL-V 000". On some systems, you have to +use "CTRL-V 003" to insert a CTRL-C. + +char action +----------------------------------------------------------------------- + *i_CTRL-[* *i_* + or CTRL-[ End insert or Replace mode, go back to Normal mode. Finish + abbreviation. + *i_CTRL-C* +CTRL-C Quit insert mode, go back to Normal mode. Do not check for + abbreviations. + + *i_CTRL-@* +CTRL-@ Insert previously inserted text and stop insert. {Vi: only + when typed as first char, only up to 128 chars} + *i_CTRL-A* +CTRL-A Insert previously inserted text. {not in Vi} + + *i_CTRL-H* *i_* + or CTRL-H Delete the character before the cursor (see below). + See |:fixdel| if your does not do what you want. + {Vi: does not delete autoindents} + *i_* + Delete the character under the cursor. If the cursor is at + the end of the line, and the 'backspace' option is non-zero, + delete the newline; the next line is appended after the + current one. See |:fixdel| if your key does not do what + you want. {not in Vi} + *i_CTRL-W* +CTRL-W Delete the word before the cursor (see below). See the + section "word motions", |word_motions|, for the definition of + a word. + *i_CTRL-U* +CTRL-U Delete all entered characters in the current line (see + below). + + *i_CTRL-I* *i_* + or CTRL-I Insert a tab. If the 'expandtab' option is on, the + equivalent number of spaces is inserted (use CTRL-V to + avoid the expansion). See also the 'smarttab' option and + section 4.3.4, |ins_expandtab|. + *i_CTRL-J* *i_* + or CTRL-J Begin new line. + *i_CTRL-M* *i_* + or CTRL-M Begin new line. + *i_CTRL-K* +CTRL-K {char1} {char2} + Enter digraph (see 4.7, |digraphs|). When {char1} is a special + key, the code for that key is inserted. Neither char is + considered for mapping. {not in Vi} + +CTRL-N Find next keyword (see 4.3.6, |i_CTRL-N|). {not in Vi} +CTRL-P Find previous keyword (see 4.3.6, |i_CTRL-P|). {not in Vi} + +CTRL-R <0-9a-z"%:.-> *i_CTRL-R* + Insert the contents of register. Between typing CTRL-R and + the second character '"' will be displayed to indicate that + you are expected to enter the name of a register. + The text is inserted as if you typed it, but mappings and + abbreviations are not used. If you have options like + 'textwidth', 'formatoptions', or 'autoindent' set, this will + influence what will be inserted. This is different from what + happens with the "p" command and pasting with the mouse. + Special registers: + '"' the unnamed register, containing the text of + the last delete or yank + '%' the current file name + ':' the last command line + '.' the last inserted text + '-' the last small (less than a line) delete + See |registers| about registers. {not in Vi} + + *i_CTRL-T* +CTRL-T Insert one shiftwidth of indent at the start of the current + line. The indent is always rounded to a 'shiftwidth' (this is + vi compatible). {Vi: only when in indent} + *i_CTRL-D* +CTRL-D Delete one shiftwidth of indent at the start of the current + line. The indent is always rounded to a 'shiftwidth' (this is + vi compatible). {Vi: CTRL-D works only when used after + autoindent} + *i_0_CTRL-D* +0 CTRL-D Delete all indent in the current line. {Vi: CTRL-D works + only when used after autoindent} + *i_^_CTRL-D* +^ CTRL-D Delete all indent in the current line. The indent is + restored in the next line. This is useful when inserting a + label. {Vi: CTRL-D works only when used after autoindent} + + *i_CTRL-V* +CTRL-V Insert next non-digit literally. For special keys, the + terminal code is inserted. Up to three digits form the + decimal value of a single byte (see below |i_CTRL-V_digit|. + The non-digit and the three digits are not considered for + mapping. {Vi: no decimal byte entry} + + *i_CTRL-Q* +CTRL-Q Same as CTRL-V. + +CTRL-X Enter CTRL-X mode. This is a sub-mode where commands can + be given to complete words or scroll the window. See below, + |i_CTRL-X|, and in 4.3.6, |ins_completion|. {not in Vi} + + *i_CTRL-E* +CTRL-E Insert the character which is below the cursor. {not in Vi} + *i_CTRL-Y* +CTRL-Y Insert the character which is above the cursor. {not in Vi} + + *i_CTRL-B* +CTRL-B Toggle the 'revins' option (B for Backwards). Only if + compiled with RIGHTLEFT (which is not the default). See + |ins_reverse|. {not in Vi} + *i_CTRL-_* +CTRL-_ This key is only available if Vim was compiled with RIGHTLEFT. + Its purpose is to switch between languages while in insert + mode, as follows: + - When in a rightleft window, revins and nohkmap are toggled, + since English will likely be inserted in this case. + - When in a norightleft window, revins and hkmap are toggled, + since Hebrew will likely be inserted in this case. + + CTRL-_ moves the cursor to the end of the typed text, unlike + CTRL-B which leaves the cursor in the same place. + + Please refer to |vim_rlh.txt| for more information about + right-to-left mode. {not in Vi} + + *i_* + Toggle between insert and replace mode. {not in Vi} +----------------------------------------------------------------------- + +The effect of the , CTRL-W, and CTRL-U depend on the 'backspace' option +(unless 'revins' is set): + +backspace action + option + 0 delete stops in column 1 and start position of insert + 1 delete stops at start position of insert + 2 delete always; CTRL-W and CTRL-U stop once at start position of + insert + +If the 'backspace' option is non-zero and the cursor is in column 1 when one +of the three keys is used, the current line is joined with the previous +line. This effectively deletes the newline in front of the cursor. {Vi: does +not cross lines, does not delete past start position of insert} + + *i_CTRL-V_digit* +With CTRL-V followed by one, two, or three digits, you can enter the decimal +value of any byte, except 10. Normally CTRL-V is followed by three digits. +The formed byte is inserted as soon as you type the third digit. If you type +only one or two digits and then a non-digit, the decimal value of those one +or two digits form the byte. After that the non-digit is dealt with in the +normal way. If you enter a value of 10, it will end up in the file as a 0. +The 10 is a , which is used internally to represent the character. +When writing the buffer to a file, the character is translated into +. The character is written at the end of each line. Thus if you +want to insert a character in a file you will have to make a line +break. The maximum value that can be entered is 255. + + *i_CTRL-X* *insert_expand* +CTRL-X enters a sub-mode where several commands can be used. Most of these +commands do keyword completion; see 4.3.6, |ins_completion|. These are only +available when Vim was compiled with INSERT_EXPAND defined. If ":version" +shows "+insert_expand" then it was; if it shows "-insert_expand" then these +commands are not available. Two commands can be used to scroll the window up +or down, without exiting insert mode: + + *i_CTRL-X_CTRL-E* +CTRL-X CTRL-E scroll window one line up. + + *i_CTRL-X_CTRL-Y* +CTRL-X CTRL-Y scroll window one line down. + +After CTRL-X is pressed, each CTRL-E (CTRL-Y) scrolls the window up (down) by +one line unless that would cause the cursor to move from its current position +in the file. As soon as another key is pressed, CTRL-X mode is exited and +that key is interpreted as in Insert mode. + + +4.3.2 special special keys *ins_special_special* + +The following keys are special. They stop the current insert, do something, +and then restart insertion. This means you can do something without getting +out of Insert mode. This is very handy if you prefer to use the Insert mode +all the time, just like editors that don't have a separate Normal mode. You +may also want to set the 'backspace' option to 2 and set the 'insertmode' +option. You can use CTRL-O if you want to map a function key to a command. + +The changes (inserted or deleted characters) before and after these keys can +be undone separately. Only the last change can be redone and always behaves +like an "i" command. + +char action +----------------------------------------------------------------------- + cursor one line up *i_* + cursor one line down *i_* + cursor one character left *i_* + cursor one character right *i_* + cursor one word back (like "b" command) *i_* + cursor one word forward (like "w" command) *i_* + cursor to first char in the line *i_* + cursor to after last char in the line *i_* + cursor to first char in the file *i_* + cursor to after last char in the file *i_* + cursor to position of mouse click *i_* + move window one page up *i_* + move window one page up *i_* + move window one page down *i_* + move window one page down *i_* +CTRL-O execute one command and return to Insert mode*i_CTRL-O* +----------------------------------------------------------------------- + +The CTRL-O command sometimes has one side effect: If the cursor was beyond the +end of the line, it will be put on the last character in the line. +The shifted cursor keys are not available on all terminals. + +When the 'whichwrap' option is set appropriately, the and +keys on the first/last character in the line make the cursor wrap to the +previous/next line. + + +4.3.3 'textwidth' and 'wrapmargin' options *ins_textwidth* + +The 'textwidth' option can be used to automatically break a line before it +gets too long. Set the 'textwidth' option to the desired maximum line +length. If you then type more characters (not spaces or tabs), the +last word will be put on a new line (unless it is the only word on the +line). If you set 'textwidth' to 0, this feature is disabled. + +The 'wrapmargin' option does almost the same. The difference is that +'textwidth' has a fixed width while 'wrapmargin' depends on the width of the +screen. When using 'wrapmargin' this is equal to using 'textwidth' with a +value equal to (columns - 'wrapmargin'), where columns is the width of the +screen. + +When 'textwidth' and 'wrapmargin' are both set, 'textwidth' is used. + +The line is only broken automatically when using insert mode, or when +appending to a line. When in replace mode and the line length is not +changed, the line will not be broken. + +Long lines are broken if you enter a non-white character after the margin. +The situations where a line will be broken can be restricted by adding +characters to the 'formatoptions' option: +"l" Only break a line if it was not longer than 'textwidth' when the insert + started. +"v" Only break at a white character that has been entered during the + current insert command. This is mostly Vi-compatible. +"lv" Only break if the line was not longer than 'textwidth' when the insert + started and only at a white character that has been entered during the + current insert command. Only differs from "l" when entering non-white + characters while crossing the 'textwidth' boundary. + +If you want to format a block of text, you can use the "gq" operator. Type +"gq" and a movement command to move the cursor to the end of the block. In +many cases, the command "gq}" will do what you want (format until the end of +paragraph). Alternatively, you can use "gqp", which will format the whole +paragraph, no matter where the cursor currently is. Or you can use Visual +mode: hit "v", move to the end of the block, and hit "gq". See also |gq|. + + +4.3.4 'expandtab' and 'smarttab' options *ins_expandtab* + +If the 'expandtab' option is on, spaces will be used to fill the amount of +whitespace of the tab. If you want to enter a real , type CTRL-V first. +The 'expandtab' option is off by default. Note that in Replace mode, a single +character is replaced with several spaces. The result of this is that the +number of characters in the line increases. Backspacing will delete one +space at a time. The original character will be put back for only one space +that you backspace over (the last one). {Vi does not have the 'expandtab' +option} + + *ins_smarttab* +When the 'smarttab' option is on, a inserts 'shiftwidth' positions at +the beginning of a line and 'tabstop' positions in other places. This means +that often spaces instead of a character are inserted. When 'smarttab +is off, a always inserts 'tabstop' positions, and 'shiftwidth' is only +used for ">>" and the like. {not in Vi} + + +4.3.5 Replace mode *replace_mode* + +In Replace mode, one character in the line is deleted for every character you +type. If there is no character to delete (at the end of the line), the +typed character is appended (as in Insert mode). Thus the number of +characters in a line stays the same until you get to the end of the line. +If a is typed, a line break is inserted and no character is deleted. + +Be careful with characters. If you type a normal printing character in +its place, the number of characters is still the same, but the number of +columns will become smaller. + +If you delete characters in Replace mode (with , CTRL-W, or CTRL-U), what +happens is that you delete the changes. The characters that were replaced +are restored. If you had typed past the existing text, the characters you +added are deleted. This is effectively a character-at-a-time undo. + +If the 'expandtab' option is on, a will replace one character with +several spaces. The result of this is that the number of characters in the +line increases. Backspacing will delete one space at a time. The original +character will be put back for only one space that you backspace over (the +last one). {Vi does not have the 'expandtab' option} + + +4.3.6 Insert mode completion *ins_completion* + +In Insert and Replace modes, there are several commands to complete part of a +keyword or line that has been typed. This is useful if you are using +complicated keywords (e.g., function names with capitals and underscores). +Completion can be done for: + +1. Whole lines |i_CTRL-X_CTRL-L| +2. keywords in the current file |i_CTRL-N| +3. keywords in 'dictionary' |i_CTRL-X_CTRL-K| +4. keywords in the current and included files |i_CTRL-X_CTRL-I| +5. tags |i_CTRL-X_CTRL-]| +6. file names |i_CTRL-X_CTRL-F| +7. definitions or macros |i_CTRL-X_CTRL-D| + +All these (except 2) are done in CTRL-X mode. This is a sub-mode of Insert +and Replace modes. You enter CTRL-X mode by typing CTRL-X and one of the +CTRL-X commands. You exit CTRL-X mode by typing a key that is not a valid +CTRL-X mode command. Valid keys are the CTRL-X command itself, CTRL-N (next), +and CTRL-P (previous). + +Also see the 'infercase' option if you want to adjust the case of the match. + +Note: The keys that are valid in CTRL-X mode are not mapped. This allows for +":map ^F ^X^F" to work (where ^F is CTRL-F and ^X is CTRL-X). The key that +ends CTRL-X mode (any key that is not a valid CTRL-X mode command) is mapped. + +The following mappings are suggested to make typing the completion commands +a bit easier (although they will hide other commands): + :inoremap ^] ^X^] + :inoremap ^F ^X^F + :inoremap ^D ^X^D + :inoremap ^L ^X^L + + +Completing whole lines *compl_whole_line* + + *i_CTRL-X_CTRL-L* +CTRL-X CTRL-L Search backwards for a line that starts with the + same characters as in the current line before the + cursor. Indent is ignored. The found line is + inserted in front of the cursor. + CTRL-L or + CTRL-P Search backwards for next matching line. This line + replaces the previous matching line. + + CTRL-N Search forward for next matching line. This line + replaces the previous matching line. + +Completing keywords in current file *compl_current* + + *i_CTRL-P* + *i_CTRL-N* +The keys CTRL-N and CTRL-P can be used to complete the keyword that is in +front of the cursor. This is useful if you are writing a program that has +complicated variable names, and you want to copy a name from the text before +of after the cursor. + +If there is a keyword in front of the cursor (a name made out of alphabetic +characters and characters in 'iskeyword'), it is used as the search pattern, +with "\<" prepended (meaning: start of a word). Otherwise "\<\k\k" is used +as search pattern (start of any keyword of at least two characters). + +With CTRL-N (next), the search goes forward; with CTRL-P (previous), the +search goes backward. The first time the search starts where the cursor is. +Subsequently, the search starts at the last found position. If you type any +other character than CTRL-P or CTRL-N, the current text is accepted and the +search pattern is forgotten. + +If the search found a match, it is inserted at the cursor position. Any +previous match is replaced. If no match was found, Vim will beep. + +In Replace mode, the number of characters that are replaced depends on the +length of the matched string. This works like typing the characters of the +matched string in Replace mode. + +If there is not a valid keyword character before the cursor, any keyword of +at least two characters is matched. + e.g., to get: + printf("(%g, %g, %g)", vector[0], vector[1], vector[2]); + just type: + printf("(%g, %g, %g)", vector[0], ^P[1], ^P[2]); + +Multiple repeats of the same completion are skipped; thus a different match +will be inserted at each CTRL-N and CTRL-P (unless there is only one +matching keyword). + +If there is only one completion found, then a second CTRL-P or CTRL-N will +give the message 'No other matches'. + +If the only match in the file is an exact match, where no extra characters +would be typed, then the message 'Exact match only' is given (this is also +useful for checking that you typed the word correctly). + +The mode (-- INSERT --) is shown, unless there is another more important +message (e.g., "Pattern not found"). This other message will stay until +another key is hit, and then the mode is shown again. + +Single character matches are never included, as they usually just get in +the way of what you were really after. + e.g., to get: + printf("name = %s\n", name); + just type: + printf("name = %s\n", n^P); + or even: + printf("name = %s\n", ^P); +The 'n' in '\n' is skipped. + + +Completing keywords in 'dictionary' *compl_dictionary* + + *i_CTRL-X_CTRL-K* +CTRL-X CTRL-K Search the files given with the 'dictionary' option + for words that start with the keyword in front of the + cursor. This is like CTRL-N, but only the dictionary + files are searched, not the current file. The found + keyword is inserted in front of the cursor. This + could potentially be pretty slow, since all matches + are found before the first match is used. By default, + the 'dictionary' option is empty. + + CTRL-K or + CTRL-N Search forward for next matching keyword. This + keyword replaces the previous matching keyword. + + CTRL-P Search backwards for next matching keyword. This + keyword replaces the previous matching keyword. + + +Completing keywords in the current and included files *compl_keyword* + +The 'include' option is used to specify a line that contains an include file +name. The 'path' option is used to search for include files. + + *i_CTRL-X_CTRL-I* +CTRL-X CTRL-I Search for the first keyword in the current and + included files that starts with the same characters + as those before the cursor. The matched keyword is + inserted in front of the cursor. + + CTRL-N Search forwards for next matching keyword. This + keyword replaces the previous matching keyword. + Note: CTRL-I is the same as , which is likely to + be typed after a succesful completion, therefore + CTRL-I is not used for searching for the next match. + + CTRL-P Search backward for previous matching keyword. This + keyword replaces the previous matching keyword. + + +Completing tags *compl_tag* + *i_CTRL-X_CTRL-]* +CTRL-X CTRL-] Search for the first tag that starts with the same + characters as before the cursor. The matching tag is + inserted in front of the cursor. Alphabetic + characters and characters in 'iskeyword' are used + to decide which characters are included in the tag + name (same as for a keyword). + CTRL-] or + CTRL-N Search forwards for next matching tag. This tag + replaces the previous matching tag. + + CTRL-P Search backward for previous matching tag. This tag + replaces the previous matching tag. + + +Completing file names *compl_filename* + *i_CTRL-X_CTRL-F* +CTRL-X CTRL-F Search for the first file name that starts with the + same characters as before the cursor. The matching + file name is inserted in front of the cursor. + Alphabetic characters and characters in 'isfname' + are used to decide which characters are included in + the file name. Note: the 'path' option is not used + here (yet). + CTRL-F or + CTRL-N Search forwards for next matching file name. This + file name replaces the previous matching file name. + + CTRL-P Search backward for previous matching file name. + This file name replaces the previous matching file + name. + + +Completing definitions or macros *compl_define* + +The 'define' option is used to specify a line that contains a definition. +The 'include' option is used to specify a line that contains an include file +name. The 'path' option is used to search for include files. + + *i_CTRL-X_CTRL-D* +CTRL-X CTRL-D Search in the current and included files for the + first definition (or macro) name that starts with + the same characters as before the cursor. The found + definition name is inserted in front of the cursor. + CTRL-D or + CTRL-N Search forwards for next matching macro name. This + macro name replaces the previous matching macro + name. + + CTRL-P Search backward for previous matching macro name. + This macro name replaces the previous matching macro + name. + + +4.4 Command-line mode *mode_cmdline* *:* + +Command-line mode is used to enter Ex commands (":"), search patterns +("/" and "?"), and filter commands ("!"). + + +4.4.1 Command line editing *cmdline_editing* + +Normally characters are inserted in front of the cursor position. You can +move around in the command line with the left and right cursor keys. With the + key, you can toggle between inserting and overstriking characters. +{Vi: can only alter the last character in the line} + +Note that if your keyboard does not have working cursor keys or any of the +other special keys, you can use ":cnoremap" to define another key for them. +For example, to define tcsh style editing keys: *tcsh-style* + :cnoremap ^A + :cnoremap ^F + :cnoremap ^B + :cnoremap ^[b + :cnoremap ^[f +(All ^x characters entered with CTRL-V CTRL-x, typed literally). + + *cmdline_history* +The command lines that you enter are remembered in a history table. You can +recall them with the up and down cursor keys. There are actually two history +tables: one for ':' commands, one for search strings. These are completely +separate. The search strings history can be accessed only when entering a +search string, the ':' history only when entering a command line for the ":" +command. Use the 'history' option to set the number of lines that are +remembered (default: 20). Note that when you enter a command line that is +excactly the same as an older one, the old one is removed (to avoid repeated +commands moving older commands out of the history). Only commands that are +typed are remembered. Ones that come from mappings are not put in the history +(detail: the decision is made from the last key that was typed for the line, +normally ). All searches are put in the search history, including the ones +that come from commands like "*" and "#". {Vi: no history} + +There is an automatic completion of names on the command line; see 4.4.2, +|cmdline_completion|. + + *c_CTRL-V* +CTRL-V Insert next non-digit literally. Up to three digits form the + decimal value of a single byte. The non-digit and the three + digits are not considered for mapping. This works the same + way as in Insert mode (see above, |i_CTRL-V|). + *c_CTRL-Q* +CTRL-Q Same as CTRL-V. + + *c_* + cursor left + *c_* + cursor right + *c_* + cursor one word left + *c_* + cursor one word right +CTRL-B or *c_CTRL-B* *c_* + cursor to beginning of command line +CTRL-E or *c_CTRL-E* *c_* + cursor to end of command line + + *c_* + cursor to position of mouse click. + +CTRL-H *c_* *c_CTRL-H* + delete the character in front of the cursor (see |:fixdel| if + your key does not do what you want). + *c_* + delete the character under the cursor (at end of line: + character before the cursor) (see |:fixdel| if your + key does not do what you want). + *c_CTRL-W* +CTRL-W delete the word before the cursor + *c_CTRL-U* +CTRL-U remove all characters + + Note: if the command line becomes empty with one of the + delete commands, Command-line mode is quit. + *c_* + Toggle between insert and overstrike. {not in Vi} + +{char1} {char2} or *c_digraph* +CTRL-K {char1} {char2} *c_CTRL-K* + enter digraph (see 4.7, |digraphs|). When {char1} is a special + key, the code for that key is inserted. {not in Vi} + +CTRL-R <0-9a-z"%:-> *c_CTRL-R* + Insert the contents of a numbered or named register. Between + typing CTRL-R and the second character '"' will be displayed + to indicate that you are expected to enter the name of a + register. The text is inserted as if you typed it, but + mappings and abbreviations are not used. Special registers: + '"' the unnamed register, containing the text of + the last delete or yank + '%' the current file name + ':' the last command line + '-' the last small (less than a line) delete + Note: The '.' register (last inserted text) is not available + here. See |registers| about registers. {not in Vi} + +CTRL-J *c_CTRL-J* *c_* *c_* + or start entered command + *c_* + When typed and 'x' not present in 'cpoptions', quit + Command-line mode without executing. In macros or when 'x' + present in 'cpoptions', start entered command. + *c_CTRL-C* +CTRL-C quit command line without executing + + *c_* + recall older command line from history, whose beginning + matches the current command line (see below). + *c_* + recall more recent command line from history, whose beginning + matches the current command line (see below). + + *c_* *c_* + or + recall older command line from history + *c_* *c_* + or + recall more recent command line from history + +CTRL-D command line completion (see 4.4.2, |cmdline_completion|) +'wildchar' option + command line completion (see 4.4.2, |cmdline_completion|) +CTRL-N command line completion (see 4.4.2, |cmdline_completion|) +CTRL-P command line completion (see 4.4.2, |cmdline_completion|) +CTRL-A command line completion (see 4.4.2, |cmdline_completion|) +CTRL-L command line completion (see 4.4.2, |cmdline_completion|) + + *c_CTRL-_* +CTRL-_ switch between Hebrew and English keyboard mode, which is + private to the command line and not related to hkmap. + This is useful when Hebrew text entry is required in the + command line, searches, abbreviations, etc. Applies only if + Vim is compiled with RIGHTLEFT. See |vim_rlh.txt|. + +The and keys take the current command line as a search string. +The beginning of the next/previous command lines are compared with this +string. The first line that matches is the new command line. When typing +these two keys repeatedly, the same string is used again. For example, this +can be used to find the previous substitute command: Type ":s" and then . +The same could be done by typing a number of times until the desired +command line is shown. (Note: the shifted arrow keys do not work on all +terminals) + + +4.4.2 Command line completion *cmdline_completion* + +When editing the command line, a few commands can be used to complete the +word before the cursor. This is available for: + +- Command names: at the start of the command line. Works always. +- tags: only after the ":tag" command. +- file names: only after a command that accepts a file name or a setting for + an option that can be set to a file name. This is called file name + completion. +- options: only after the ":set" command. + +These are the commands that can be used: + + *c_CTRL-D* +CTRL-D List names that match the pattern in front of the cursor. + When showing file names, directories are highlighted (see + 'highlight' option) + *c_CTRL-I* *c_wildchar* *c_* +'wildchar' option + A match is done on the pattern in front of the cursor. The + match (if there are several, the first match) is inserted + in place of the pattern. (Note: does not work inside a + macro, because or are mostly used as 'wildchar', + and these have a special meaning in some macros.) When typed + again and there were multiple matches, the next + match is inserted. After the last match, the first is used + again (wrap around). + *c_CTRL-N* +CTRL-N After using 'wildchar' which got multiple matches, go to next + match. Otherwise recall more recent command line from history. + *c_CTRL-P* *c_* +CTRL-P After using 'wildchar' which got multiple matches, go to + previous match. Otherwise recall older command line from + history. only works with the GUI, on the Amiga and + with MS-DOS. + *c_CTRL-A* +CTRL-A All names that match the pattern in front of the cursor are + inserted. + *c_CTRL-L* +CTRL-L A match is done on the pattern in front of the cursor. If + there is one match, it is inserted in place of the pattern. + If there are multiple matches the longest common part is + inserted in place of the pattern. + +The 'wildchar' option defaults to (CTRL-E when compiled with +COMPATIBLE; in a previous version was used). In the pattern standard +wildcards '*' and '?' are accepted. '*' matches any string, '?' matches +exactly one character. + +If you like tcsh's autolist completion, you can use this mapping: + :cnoremap X +(Where X is the command key to use, is CTRL-L and is CTRL-L) +This will find the longest match and then list all matching files. + + *suffixes* +For filename completion you can use the 'suffixes' option to set a priority +between files with almost the same name. If there are multiple matches, +those files with an extension that is in the 'suffixes' option are ignored. +The default is ".bak,~,.o,.h,.info,.swp", which means that files ending in +".bak", "~", ".o", ".h", ".info" and ".swp" are sometimes ignored. It is +impossible to ignore suffixes with two dots. Examples: + +pattern: files: match: +test* test.c test.h test.o test.c +test* test.h test.o test.h and test.o +test* test.i test.h test.c test.i and test.c + +If there is more than one matching file (after ignoring the ones matching +the 'suffixes' option) the first file name is inserted. You can see that +there is only one match when you type 'wildchar' twice and the completed +match stays the same. You can get to the other matches by entering +'wildchar', CTRL-N or CTRL-P. All files are included, also the ones with +extensions matching the 'suffixes' option. + +The old value of an option can be obtained by hitting 'wildchar' just after +the '='. For example, typing 'wildchar' after ":set dir=" will insert the +current value of 'dir'. This overrules filename completion for the options +that take a file name. + +If you would like using for CTRL-P in an xterm, put this command in +your .cshrc: + xmodmap -e "keysym Tab = Tab Find" +And this in your .vimrc: + cmap [1~ ( is CTRL-P) + + +4.4.3 Ex command lines *cmdline_lines* + +The Ex commands have a few specialties: + + *:quote* +'"' at the start of a line causes the whole line to be ignored. '"' +after a command causes the rest of the line to be ignored. This can be used +to add comments. Example: + :set ai "set 'autoindent' option +It is not possible to add a comment to a shell command ":!cmd" or to the +":map" command and friends, because they see the '"' as part of their +argument. + + *:bar* +'|' can be used to separate commands, so you can give multiple commands in one +line. The commands ":global", "vglobal", ":!", ":r !", ":w !", ":help" and +":autocmd" see the '|' as their argument, and can therefore not be followed by +another command. If you want '|' to be included in one of the other commands, +precede it with '\'. Note that this is confusing (inherited from Vi). With +":g" the '|' is included in the command, with ":s" it is not. There is one +execption: When the 'b' flag is present in 'cpoptions', with the ":map" and +":abbr" commands and friends CTRL-V needs to be used instead of '\'. See also +|map_bar|. +Examples: + :!ls | wc view the output of two commands + :r !ls | wc insert the same output in the text + :%g/foo/p|> moves all matching lines one shiftwidth + :%s/foo/bar/|> moves one line one shiftwidth + :map q 10^V| map "q" to "10|" + :map q 10\| map \ l map "q" to "10\" and map "\" to "l" + (when 'b' is present in 'cpoptions') + +You can also use to separate commands in the same way as with '|'. To +insert a use CTRL-V CTRL-J. "^@" will be shown. Using '|' is the +preferred method. But for external commands a must be used, because a +'|' is included in the external command. To avoid the special meaning of +it must be preceded with a backslash. Example: + :r !date-join +This reads the current date into the file and joins it with the previous line. + +Because of vi compatibility the following strange commands are supported: + :| print current line (like ":p") + :3| print line 3 (like ":3p") + :3 goto line 3 + +A colon is allowed between the range and the command name. It is ignored +(this is vi compatible). For example ":1,$:s/pat/string". + +When the character '%' or '#' is used where a filename is expected, they are +expanded to the current and alternate filename (see the chapter "editing +files" |:_%| |:_#|). + +Embedded spaces in filenames are allowed on the Amiga if one filename is +expected as argument. Trailing spaces will be ignored, unless escaped with a +backslash or CTRL-V. Note that the ":next" command uses spaces to separate +file names. Escape the spaces to include them in a file name. Example: + :next foo\ bar goes\ to school\ +starts editing the three files "foo bar", "goes to" and "school ". + +When you want to use the special characters '"' or '|' in a command, or want +to use '%' or '#' in a filename, precede them with a backslash. The backslash +is not required in a range and in the ":substitute" command. + + *:_!* +The '!' (bang) character after an Ex command makes the command behave in a +different way. The '!' should be placed immediately after the command, without +any blanks in between. If you insert blanks the '!' will be seen as an +argument for the command, which has a different meaning. For example: + :w! name write the current buffer to file "name", overwriting + any existing file + :w !name send the current buffer as standard input to command + "name" + + +4.4.4 Ex command line ranges *cmdline_ranges* + +Some Ex commands accept a line range in front of them. This is noted as +[range]. It consists of one or more line specifiers, separated with ',' or +';'. When separated with ';' the cursor position will be set to that line +before interpreting the next line specifier. The default line specifier for +most commands is the cursor position, but the commands ":write" and +":global" have the whole file (1,$) as default. If more line specifiers are +given than required for the command, the first one(s) will be ignored. + +Line numbers may be specified with: *:range* + {number} an absolute line number + . the current line *:.* + $ the last line in the file *:$* + % equal to 1,$ (the entire file) *:%* + * equal to '<,'> (the Visual area) *:star* + 't position of mark t (lower case) *:'* + /{pattern}[/] the next line where {pattern} matches *:/* + ?{pattern}[?] the previous line where {pattern} matches *:?* + \/ the next line where the previously used search + pattern matches + \? the previous line where the previously used search + pattern matches + \& the next line where the previously used substitute + pattern matches + +Each may be followed (several times) by '+' or '-' and an optional number. +This number is added or subtracted from the preceding line number. If the +number is omitted, 1 is used. + +The "/" and "?" may be preceded with another address. The search starts from +there. The "/" and "?" after {pattern} are required to separate the pattern +from anything that follows. + +The {number} must be between 0 and the number of lines in the file. A 0 is +interpreted as a 1, except with the commands tag, pop and read. + +Examples: + .+3 three lines below the cursor + /that/+1 the line below the next line containing "that" + .,$ from current line until end of file + 0;/that the first line containing "that" + +Some commands allow for a count after the command. This count is used as the +number of lines to be used, starting with the line given in the last line +specifier (the default is the cursor line). The commands that accept a count +are the ones that use a range but do not have a file name argument (because +a file name can also be a number). + +Examples: + :s/x/X/g 5 substitute 'x' by 'X' in the current line and four + following lines + :23d 4 delete lines 23, 24, 25 and 26 + +A range should have the lower line number first. If this is not the case, Vim +will ask you if it should swap the line numbers. This is not done within the +global command ":g". + + *N:* +When giving a count before entering ":", this is translated into: + :.,.+(count - 1) +In words: The 'count' lines at and after the cursor. Example: To delete +three lines: + 3:d is translated into: .,.+2d + + *v_:* +{Visual}: Starts a command line with the Visual selected lines as a + range. The code ":'<,'>" is used for this range, which makes + it possible to select a similar line from the command line + history for repeating a command on different Visually selected + lines. + + +4.4.5 Ex special characters *cmdline_special* + +In Ex commands, at places where a file name can be used, the following +characters have a special meaning. To avoid the special meaning of '%' and +'#' insert a backslash before it. + % is replaced with the current filename *:_%* + # is replaced with the alternate filename *:_#* + #n (where n is a number) is replaced with the filename of + buffer n. "#0" is the same as "#" + +Note: the next four are typed literally, these are not special keys! + *:* *:* *:* *:* + is replaced with the word under the cursor + is replaced with the WORD under the cursor (see |WORD|) + is replaced with the path name under the cursor + when executing autocommands, is replaced with the file name + for a file read or write + + *:_%:* *::p* *::h* *::t* *::r* *::e* +After "%", "#", "#n", "" or "" modifiers can be given (in this +order): + :p Make file name a full path. Must be the first modifier. + :h Head of the file name (the last component and any + separators removed). Cannot be used with :e, :r or :t. + Can be repeated to remove several components at the end. + When there is no head the result is empty. + :t Tail of the file name (last component of the name). Must + precede any :r or :e. + :r Root of the file name (the last extension removed). When + there is only an extension (file name that starts with + '.', e.g., ".vimrc"), it is not removed. Can be repeated to + remove several extensions (last one first). + :e Extension of the file name. Only makes sense when used + alone. When there is no extension the result is empty. + When there is only an extension (file name that starts with + '.'), the result is empty. Can be repeated to include more + extensions. If there are not enough extensions (but at + least one) as much as possible are included. + +Examples, when the file name is "src/version.c": + :p /home/mool/vim/src/version.c + :h src + :p:h /home/mool/vim/src + :p:h:h /home/mool/vim + :t version.c + :p:t version.c + :r src/version + :p:r /home/mool/vim/src/version + :t:r version + :e c + +Examples, when the file name is "src/version.c.gz": + :p /home/mool/vim/src/version.c.gz + :e gz + :e:e c.gz + :e:e:e c.gz + :e:e:r c + :r src/version.c + :r:e c + :r:r src/version + :r:r:r src/version + + *:_%<* +If a "<" is appended to "%", "#", "#n" or "CTRL-V p" the extension of the file +name is removed (everything after and including the last '.' in the file +name). This is included for backwards compatibility with version 3.0, the +":r" form is preferred. Examples: + + % current file name + %< current file name without extension + # alternate file name for current window + #< idem, without extension + #31 alternate file number 31 + #31< idem, without extension + word under the cursor + WORD under the cursor (see |WORD|) + path name under the cursor + < idem, without extension + +Note: Where a file name is expected wildcards expansion is done. On Unix the +shell is used for this. Backticks also work, like in ":n `echo *.c`". +But expansion is only done if there are any wildcards before expanding the +'%', '#', etc.. This avoids expanding wildcards inside a file name. +Examples: (alternate filename is "?readme?") + command expands to + :e # :e ?readme? + :e `ls #` :e {files matching "?readme?"} + :e #.* :e {files matching "?readme?.*"} + + +4.5 The window contents *window_contents* + +In command and Insert/Replace mode the screen window will show the current +contents of the buffer: What You See Is What You Get. There are two +exceptions: +- When the 'cpoptions' option contains '$', and the change is within one line, + the text is not directly deleted, but a '$' is put at the last deleted + character. +- When inserting text in one window, other windows on the same text are not + updated until the insert is finished. +{Vi: The screen is not always updated on slow terminals} + +Lines longer than the window width will wrap, unless the 'wrap' option is off +(see below). The 'linebreak' option can be set to wrap at a blank character. + +The bottom lines in the window may start with one of these two characters: + +'@' The next line is too long to fit in the window. +'~' Below the last line in the buffer. + +If the bottom line is completely filled with '@', the line that is at the +top of the window is too long to fit in the window. If the cursor is on this +line you can't see what you are doing, because this part of the line is not +shown. However, the part of the line before the '@'s can be edited normally. +{Vi: gives an "internal error" on lines that do not fit in the window} + +The 'showbreak' option contains the string to put in front of wrapped lines. + + *wrap_off* +If the 'wrap' option is off, long lines will not wrap. Only the part that +fits on the screen is shown. If the cursor is moved to a part of the line +that is not shown, the screen is scrolled horizontally. The advantage of +this method is that columns are shown as they are and lines that cannot fit +on the screen can be edited. The disadvantage is that you cannot see all the +characters of a line at once. The 'sidescroll' option can be set to the +minimal number of columns to scroll. {Vi: has no 'wrap' option} + +All normal ASCII characters are displayed directly on the screen. The +is replaced with the number of spaces that it represents. Other non-printing +characters are replaced with "^{char}", where {char} is the non-printing +character with 64 added. Thus character 7 (bell) will be shown as "^G". +Characters between 127 and 160 are replaced with "~{char}", where {char} is +the character with 64 subtracted. These characters occupy more than one +position on the screen. The cursor can only be positioned on the first one. + +If you set the 'number' option, all lines will be preceded with their +number. Tip: If you don't like wrapping lines to mix with the line numbers, +set the 'showbreak' option to eight spaces: + ":set showbreak=\ \ \ \ \ \ \ \ " + +If you set the 'list' option, characters will not be shown as several +spaces, but as "^I". A '$' will be placed at the end of the line, so you can +find trailing blanks. + +In Command-line mode only the command line itself is shown correctly. The +display of the buffer contents is updated as soon as you go back to Command +mode. + +Some commands hand over the window to external commands (e.g., ":shell" and +"="). After these commands are finished the window may be clobbered with +output from the external command, so it needs to be redrawn. This is also +the case if something is displayed on the status line that is longer than +the width of the window. If you are expected to have a look at the screen +before it is redrawn, you get this message: + + Press RETURN or enter command to continue + +After you type a key the screen will be redrawn and Vim continues. If you +type , or nothing else happens. If you type any other key, it +will be interpreted as (the start of) a new command. {Vi: only ":" commands +are interpreted} + +The last line of the window is used for status and other messages. The +status messages will only be used if an option is on: + +status message option default Unix default +current mode 'showmode' on on +command characters 'showcmd' on off +cursor position 'ruler' off off + +The current mode is "-- INSERT --" or "-- REPLACE --", see |'showmode'|. The +command characters are those that you typed but were not used yet. {Vi: does +not show the characters you typed or the cursor position} + +If you have a slow terminal you can switch off the status messages to speed +up editing: + :set nosc noru nosm + +If there is an error, an error message will be shown for at least one second +(in reverse video). {Vi: error messages may be overwritten with other +messages before you have a chance to read them} + +Some commands show how many lines were affected. Above which threshold this +happens can be controlled with the 'report' option (default 2). + +On the Amiga Vim will run in a CLI window. The name Vim and the full name of +the current filename will be shown in the title bar. When the window is +resized, Vim will automatically redraw the window. You may make the window as +small as you like, but if it gets too small not a single line will fit in it. +Make it at least 40 characters wide to be able to read most messages on the +last line. + +On most Unix systems window resize works ok. {Vi: not ok} + + +4.6 Abbreviations *abbreviations* + +Abbreviations are used in insert mode, Replace mode and Command-line mode. +If you enter a word that is an abbreviation, it is replaced with the word it +stands for. This can be used to save typing for often used long words. + +There are three types of abbreviations: + +full-id The "full-id" type consists entirely of keyword characters (letters + and characters from 'iskeyword' option). This is the most common + abbreviation. + + Examples: "foo", "g3", "-1" + +end-id The "end-id" type ends in a keyword character, but all the other + characters are not keyword characters. + + Examples: "#i", "..f", "$/7" + +non-id The "non-id" type ends in a non-keyword character, the other + characters may be of any type, excluding space and Tab. {this type + is not supported by Vi} + + Examples: "def#", "4/7$" + +Examples of strings that cannot be abbreviations: "a.b", "#def", "a b", "_$r" + +An abbreviation is only recognized when you type a non-keyword character. +This can also be the that ends insert mode or the that ends a +command. The characters before the cursor must match the abbreviation. Each +type has an additional rule: + +full-id In front of the match is a non-keyword character, or this is where + the line or insertion starts. Exception: When the abbreviation is + only one character, it is not recognized if there is a non-keyword + character in front of it, other than a space or a . + +end-id In front of the match is a keyword character, or a space or a , + or this is where the line or insertion starts. + +non-id In front of the match is a space, or the start of the line or + the insertion. + +Examples: ( is where you type a non-keyword character) + ":ab foo four old otters" (Note that spaces in the are allowed + and included in the replacement string.) + " foo" is expanded to " four old otters" + " foobar" is not expanded + "barfoo" is not expanded + + ":ab #i #include" + "#i" is expanded to "#include" + ">#i" is not expanded + + ":ab ;; " + "test;;" is not expanded + "test ;;" is expanded to "test " + +To avoid the abbreviation in insert mode: Type part of the abbreviation, exit +insert mode with , re-enter insert mode with "a" and type the rest. Or +type CTRL-V before the character after the abbreviation. +To avoid the abbreviation in Command-line mode: Type CTRL-V twice somewhere in +the abbreviation to avoid it to be replaced. A CTRL-V in front of a normal +character is mostly ignored otherwise. + +There are no default abbreviations. + +Abbreviations are never recursive. You can use ":ab f f-o-o" without any +problem. But abbreviations can be mapped. {some versions of Vi support +recursive abbreviations, for no apparent reason} + +Abbreviations are disabled if the 'paste' option is on. + + *:ab* *:abbreviate* +:ab[breviate] list all abbreviations. The character in the first + column indicates the mode where the abbreviation is + used: 'i' for insert mode, 'c' for Command-line + mode, '!' for both. + +:ab[breviate] list the abbreviations that start with + +:ab[breviate] + add abbreviation for to . If already + existed it is replaced with the new . may + contain spaces. + + *:una* *:unabbreviate* +:una[bbreviate] remove abbreviation for from the list + + *:norea* *:noreabbrev* +:norea[bbrev] [lhs] [rhs] + same as ":ab", but no remapping for this {not + in Vi} + + *:ca* *:cabbrev* +:ca[bbrev] [lhs] [rhs] same as ":ab", but for Command-line mode only. {not + in Vi} + + *:cuna* *:cunabbrev* +:cuna[bbrev] same as ":una", but for Command-line mode only. {not + in Vi} + + *:cnorea* *:cnoreabbrev* +:cnorea[bbrev] [lhs] [rhs] + same as ":ab", but for Command-line mode only and no + remapping for this {not in Vi} + + *:ia* *:iabbrev* +:ia[bbrev] [lhs] [rhs] same as ":ab", but for Insert mode only. {not in Vi} + + *:iuna* *:iunabbrev* +:iuna[bbrev] same as ":una", but for insert mode only. {not in + Vi} + + *:inorea* *:inoreabbrev* +:inorea[bbrev] [lhs] [rhs] + same as ":ab", but for Insert mode only and no + remapping for this {not in Vi} + + *:abc* *:abclear* +:abc[lear] Remove all abbreviations. {not in Vi} + + *:iabc* *:iabclear* +:iabc[lear] Remove all abbreviations for Insert mode. {not in Vi} + + *:cabc* *:cabclear* +:cabc[lear] Remove all abbreviations for Command-line mode. {not + in Vi} + + + +4.7 Digraphs *digraphs* + + *:dig* *:digraphs* +:dig[raphs] show currently defined digraphs. {not in Vi} + +:dig[raphs] {char1}{char2} {number} ... + Add digraph {char1}{char2} to the list. {number} is + the decimal representation of the character. + +Digraphs are used to enter characters that normally cannot be entered by +an ordinary keyboard. These are mostly accented characters which have the +eighth bit set. The digraphs are easier to remember than the decimal number +that can be entered with CTRL-V (see above). + +Vim must have been compiled with DIGRAPHS defined. If this wasn't done, the +":digraph" command will display an error message. You can also check this +with the ":version" command. If it shows "+digraphs" then it's included, +"-digraphs" means it's not included. + +There are two methods to enter digraphs: *i_digraph* + CTRL-K {char1} {char2} or + {char1} {char2} +The first is always available. The second only when the 'digraph' option is +set. + +If a digraph with {char1}{char2} does not exist, a digraph {char2}{char1} is +searched for. This will help when you don't remember which character comes +first. + +Note that when you enter CTRL-K {char1}, where {char1} is a special key, the +code for that special key is entered. This is not a digraph. + +Once you have entered the digraph the character is treated like a normal +character, taking up only one character in the file and on the screen. +Example: + '|' '|' will enter the double '|' character (166) + 'a' '^' will enter an 'a' with a hat (226) + CTRL-K '-' '-' will enter a minus sign (173) + +The default digraphs are listed in the file "vim_digr.txt" |digraph_table|. +There are two sets: One that is used for MS-DOS and one for the international +standard character set that is mostly used on Unix systems and the Amiga. +With the wrong character set they will look illogical. + +For CTRL-K there is one general digraph: CTRL-K {char} will enter +{char} with the highest bit set. This can be used to enter meta-characters. + +The character cannot be part of a digraph. When hitting the +entering of the digraph is aborted and Insert mode or command-line mode is +ended, just like hitting an . + +If you accidently typed an 'a' that should be an 'e', you will type 'a' +'e'. But that is a digraph, so you will not get what you want. To correct +this, you will have to type e again. To avoid this don't set the +'digraph' option and use CTRL-K to enter digraphs. + +You may have problems using Vim with characters which have an ascii value > +128. For example: You insert ue (u-umlaut) and the editor echoes \334 in +Insert mode. After leaving the Insert mode everything is fine. Also fmt +removes all characters with ascii > 128 from the text being formated. +On some Unix systems this means you have to define the environment-variable +LC_CTYPE. If you are using csh then put in your .cshrc following line: + setenv LC_CTYPE=iso_8859_1 + + +4.8 Using the mouse *mouse_using* + +This section is about using the mouse on a terminal or a terminal window. How +to use the mouse in a GUI window is explained in |gui_mouse|. Don't forget to +do ":set mouse=a", otherwise Vim won't recognize the mouse in all modes (See +'mouse'). + +Currently the mouse is supported for Unix in an xterm window and for MS-DOS. +Mouse clicks can be used to position the cursor, select the Visual area and +paste. There are no menus, use the GUI version for that. + +The characters in the 'mouse' option tell in which situations the mouse will +be used by Vim: + n Normal mode + v Visual mode + i Insert mode + c Command-line mode + h all previous modes when in a help file + a all previous modes + r for "Hit return ..." question + +The default for 'mouse' is empty, the mouse is not used. Normally you would do + :set mouse=a +to start using the mouse (this is equivalent to setting 'mouse' to "nvich"). +If you only want to use the mouse in a few modes or also want to use it for +the two questions you will have to concatenate the letters for those modes. +For example: + :set mouse=nv +Will make the mouse work in Normal mode and Visual mode. + :set mouse=h +Will make the mouse work in help files only (so you can use "g" to +jump to tags). + +In an xterm, with the currently active mode included in the 'mouse' option, +normal mouse clicks are used by Vim, mouse clicks with the shift or ctrl key +pressed go the the xterm. With the currently active mode not included in +'mouse' all mouse clicks go to the xterm. + +Here is how you copy and paste a piece of text: + +Copy/paste with the mouse and Visual mode ('mouse' option must be set, see +above): +1. Press left mouse button on first letter of text, move mouse pointer to last + letter of the text and release the button. This will start Visual mode and + highlight the selected area. +2. Press "y" to yank the Visual text in the unnamed register. +3. Click the left mouse button at the insert position. +4. Click the middle mouse button. + +Shortcut: If the insert position is on the screen at the same time as the +Visual text, you can do 2, 3 and 4 all in one: Click the middle mouse button +at the insert position. + + *xterm_copy_paste* +Copy/paste in xterm with (current mode NOT included in 'mouse'): +1. Press left mouse button on first letter of text, move mouse pointer to last + letter of the text and release the button. +2. Use normal Vim commands to put the cursor at the insert position. +3. Press "a" to start Insert mode. +4. Click the middle mouse button. +5. Press ESC to end Insert mode. +(The same can be done with anything in 'mouse' if you keep the shift key +pressed while using the mouse) + +Note: if you loose the 8th bit when pasting (special characters are translated +into other characters), you may have to do "stty cs8 -istrip" in your shell +before starting Vim. + +Thus in an xterm the shift and ctrl keys cannot be used with the mouse. To +make it possible to do the mouse commands that require the ctrl modifier, the +"g" key can be typed before using the mouse: + "g" is " (jump to tag under mouse click) + "g" is " ("CTRL-T") + +A short overview of what the mouse buttons do: + +Normal Mode: +event position Visual change action + cursor window + yes end yes + yes end yes "CTRL-]" (2) + yes no change yes "*" (2) ** + yes start or extend (1) no ** + yes start or extend (1) no + yes if not active no put + yes if active no yank and put + yes start or extend yes + yes no change yes "#" (2) ** + no no change no "CTRL-T" + yes extend no ** + yes extend no ** + +Insert or Replace Mode: +event position Visual change action + cursor window + yes (cannot be active) yes + yes (cannot be active) yes "CTRL-O^]" (2) + yes (cannot be active) yes "CTRL-O*" (2) + yes start or extend (1) no like CTRL-O (1) + yes start or extend (1) no like CTRL-O (1) + no (cannot be active) no put register + yes start or extend yes like CTRL-O + yes (cannot be active) yes "CTRL-O#" (2) + no (cannot be active) no "CTRL-O CTRL-T" + +(1) only if mouse pointer moved since press +(2) only if click is in same buffer + +Clicking the left mouse button causes the cursor to be positioned. If the +click is in another window that window is made the active window. When +editing the command line the cursor can only be positioned on the +command line. When in Insert mode Vim remains in Insert mode. If 'scrolloff' +is set, and the cursor is positioned within 'scrolloff' lines from the window +border, the text is scrolled. + +A Visual area can be selected by pressing the left mouse button on the first +character, moving the mouse to the last character, then releasing the mouse +button. You will not always see the Visual selection until you release the +button, only in some versions (GUI, MS-DOS, WIN32) will the dragging be shown +immediately. Note that you can make the text scroll by moving the mouse at +least one character in the first/last line in the window when 'scrolloff' is +non-zero. + +In Normal and Visual mode clicking the right mouse button causes the Visual +area to be extended. When clicking in a window which is editing another +buffer, the Visual mode is stopped. + +Double, triple and quadruple clicks are supported when the GUI is active, +for MS-DOS and Win32, and for an xterm (if the gettimeofday() function is +available). Double clicking may be done to make the selection word-wise, +triple clicking makes it line-wise, and quadruple clicking makes it +rectangular block-wise. For MS-DOS and xterm the time for double clicking can +be set with the 'mousetime' option. For the other systems this time is +defined outside of Vim. + +In Insert mode, when a Visual area is selected, Vim goes into Normal mode +temporarily. When Visual mode ends, it returns to Insert mode. This is like +using CTRL-O in Insert mode. + + *drag_status_line* +When working with several windows, the size of the windows can be changed by +dragging the status line with the mouse. Point the mouse at a status line, +press the left button, move the mouse to the new position of the status line, +release the button. Just clicking the mouse in a status line makes that window +the current window, without moving the cursor. If by selecting a window it +will change position or size, the dragging of the status line will look +confusing, but it will work (just try it). + +Mouse clicks can be mapped. The codes for mouse clicks are: + code mouse button normal action + left pressed set cursor position + left moved while pressed extend Visual area + left released set Visual area end + middle pressed paste text at cursor position + middle moved while pressed - + middle released - + right pressed extend Visual area + right moved while pressed extend Visual area + right released set Visual area end + +Examples: + :noremap +Paste at the position of the middle mouse button click (otherwise the paste +would be done at the cursor position). + + :noremap y +Immediately yank the Visually highlighted text. + +Note the use of ":noremap" instead of "map" to avoid a recursive mapping. + + *mouse_swap_buttons* +To swap the meaning of the left and right mouse buttons: + :noremap + :noremap + :noremap + :noremap + :noremap + :noremap + :noremap g + :noremap g + :noremap! + :noremap! + :noremap! + :noremap! + :noremap! + :noremap! + + +4.9 Online help *online_help* + + *help* ** *:h* *:help* ** *i_* *i_* + or +:h[elp] Split the window and display the help file in + read-only mode. If there is a help window open + already, use that one. {not in Vi} + +:h[elp] {subject} Like ":help", additionally jump to the tag + {subject}. {subject} can be a regular expression. + :help z. jump to help for any "z" command + :help z\. jump to the help for "z." + If there is no full match for the pattern, or there + are several matches, the "best" match will be used. + A match is considered to be better when: + - if no match with same case is found, a match with + ignoring case will be used + - the match is after a non-alphanumereic character + - it is at near the beginning of the tag + - more alphanumeric characters match + - the length of the matched is smaller + Note that the longer the {subject} you give, the less + matches will be found. You can get an idea how this + all works by using commandline completion (type CTRL-D + after ":help subject". {not in Vi} + +The help file name can be set with the 'helpfile' option. The initial height +of the help window can be set with the 'helpheight' option (default 20). +Jump to specific subjects by using tags. This can be done in two ways: +- Use the "CTRL-]" command while standing on the name of a command or option. + This only works when the tag is a keyword. "" and + "g" work just like "CTRL-]". +- use the ":ta {subject}" command. This works with all characters. + +Use "CTRL-T" to jump back. +Use ":q" to close the help window. + + *help_xterm_window* +If you want to have the help in another xterm window, you could use this +command: + :!xterm -e vim +help & + + *doc_files* +All the help files must be in the same directory. The files are: + vim_help.txt overview and quick reference |vim_help.txt| + vim_idx.txt alphabetical index of all commands |vim_idx.txt| + vim_ref.txt reference manual (this file) |vim_ref.txt| + vim_win.txt reference manual for windows commands |vim_win.txt| + vim_diff.txt main differences between Vim and Vi |vim_diff.txt| + vim_digr.txt list of available digraphs |vim_digr.txt| + vim_tips.txt tips on using Vim |vim_tips.txt| + vim_gui.txt about the Graphical User Interface |vim_gui.txt| + vim_40.txt about version 4.0 |vim_40.txt| + vim_rlh.txt about the 'rightleft' option |vim_rlh.txt| + + vim_unix.txt Unix specific remarks |vim_unix.txt| + vim_ami.txt Amiga specific remarks |vim_ami.txt| + vim_dos.txt MS-DOS specific remarks |vim_dos.txt| + vim_w32.txt Windows-NT/95 specific remarks |vim_w32.txt| + vim_os2.txt OS/2 specific remarks |vim_os2.txt| + vim_arch.txt Archimedes specific remarks |vim_arch.txt| + vim_mac.txt Macintosh specific remarks |vim_mac.txt| + vim_mint.txt Atari MiNT specific remarks |vim_mint.txt| + + vim_tags tags file for documentation + + +5. Editing files *edit_files* +================ + +5.1 Introduction *edit_intro* + +Editing a file with Vim means: + +1. reading the file into the internal buffer +2. changing the buffer with editor commands +3. writing the buffer into a file + +As long as you don't write the buffer, the original file remains unchanged. +If you start editing a file (read a file into the buffer), the file name is +remembered as the "current filename". + +If there already was a current filename, then that one becomes the alternate +file name. All filenames are remembered in the file list. When you enter a +filename, for editing (e.g., with ":e filename") or writing (e.g., with (:w +filename"), the filename is added to the list. You can use this list to +remember which files you edited and to quickly switch from one file to +another with the CTRL-^ command (e.g., to copy text). First type the number +of the file and then hit CTRL-^. {Vi: only one alternate filename} + +CTRL-G or *CTRL-G* *:f* *:file* +:f[ile] Prints the current filename (as typed), the + cursor position (unless the 'ruler' option is set), + and the file status (readonly, modified). See the + 'shortmess' option about how tho make this message + shorter. {Vi does not include column number} + +{count}CTRL-G Like CTRL-G, but prints the current filename with full + path. If the count is higher than 1 the current + buffer number is also given. {not in Vi} + + *g_CTRL-G* +g CTRL-G Prints the current position of the cursor in three + ways: Column, Line and Character. If there are + characters in the line that take more than one + position on the screen ( or special character), + both the "real" column and the screen column are + shown, separated with a dash. See also 'ruler' + option. {not in Vi} + + *:file_f* +:f[ile] {name} Sets the current filename to {name}. + +:buffers +:files +:ls List all the currently known file names. See + 'vim_win.txt' |:files| |:buffers| |:ls|. {not in + Vi} + +Vim will remember the full path name of a file name that you enter. In most +cases when the file name is displayed only the name you typed is shown, but +the full path name is being used if you used the ":cd" command |:cd|. + + *home_replace* +If the environment variable 'HOME' is set, and the file name starts with that +string, it is often displayed with HOME replaced with "~". This was done to +keep file names short. When reading or writing files the full name is still +used, the "~" is only used when displaying file names. When replacing the +file name would result in just "~", "~/" is used instead (to avoid confusion +with 'backupext' set to "~"). + +When writing the buffer, the default is to use the current filename. Thus +when you give the "ZZ" or ":wq" command, the original file will be +overwritten. If you do not want this, the buffer can be written into another +file by giving a filename argument to the ":write" command. For example: + + vim testfile + [change the buffer with editor commands] + :w newfile + :q + +This will create a file "newfile", that is a modified copy of "testfile". +The file "testfile" will remain unchanged. Anyway, if the 'backup' option is +set, Vim renames or copies the original file before it will be overwritten. +You can use this file if you discover that you need the original file. See +also the 'patchmode' option. The name of the backup file is normally the same +as the original file with 'backupext' appended. The default "~" is a bit +strange to avoid accidently overwriting existing files. If you prefer ".bak" +change the 'backupext' option. Extra dots are replaced with '_' on MS-DOS +machines, when Vim has detected that an MS-DOS-like filesystem is being used +(e.g., messydos or crossdos) or when the 'shortname' option is on. The +backup file can be placed in another directory by setting 'backupdir'. + + *auto_shortname* +Technical: On the Amiga you can use 30 characters for a file name. But on an + MS-DOS-compatible filesystem only 8 plus 3 characters are + available. Vim tries to detect the type of filesystem when it is + creating the .swp file. If an MS-DOS-like filesystem is suspected, + a flag is set that has the same effect as setting the 'shortname' + option. This flag will be reset as soon as you start editing a + new file. The flag will be used when making the filename for the + ".swp" and ".~" files for the current file. But when you are + editing a file in a normal filesystem and write to an MS-DOS-like + filesystem the flag will not have been set. In that case the + creation of the ".~" file may fail and you will get an error + message. Use the 'shortname' option in this case. + +When you started editing without giving a file name, "No File" is displayed in +messages. If the ":write" command is used with a file name argument, the file +name for the current file is set to that file name. This only happens when +the 'F' flag is included in 'cpoptions' (by default it is included). This is +useful when entering text in an empty buffer and then writing it to a file. +If 'cpoptions' contains the 'f' flag (by default it is NOT included) the file +name is set for the ":read file" command. This is useful when starting Vim +without an argument and then doing ":read file" to start editing a file. +Because the file name was set without really starting to edit that file, you +are protected from overwriting that file. This is done by setting the +"notedited" flag. You can see if this flag is set with the CTRL-G or ":file" +command. It will include "[Not edited]" when the "notedited" flag is set. +When writing the buffer to the current file name (with ":w!"), the "notedited" +flag is reset. + +Vim remembers whether you have changed the buffer. You are protected from +losing the changes you made. If you try to quit without writing, or want to +start editing another file, this will be refused. In order to overrule this +protection add a '!' to the command. The changes will then be lost. For +example: ":q" will not work if the buffer was changed, but ":q!" will. To see +whether the buffer was changed use the "CTRL-G" command. The message includes +the string "[Modified]" if the buffer has been changed. + + +5.2 Editing a file *edit_a_file* + + *:e* *:edit* +:e[dit] [+cmd] Edit the current file, unless changes have been made. + + *:edit!* +:e[dit]! [+cmd] Edit the current file always. Discard any changes to + the buffer. + + *:edit_f* +:e[dit] [+cmd] {file} Edit {file}, unless changes have been made. + + *:edit!_f* +:e[dit]! [+cmd] {file} Edit {file} always. Discard any changes to the + buffer. + +:e[dit] #[count] Edit the [count]th alternate filename (as shown by + :files). This command does the same as [count] CTRL-^. + + *:ex* +:ex [+cmd] [file] Same as :edit. {Vi: go from visual to Ex mode} + + *:vi* *:visual* +:vi[sual] [+cmd] [file] Same as :edit. {Vi: go from Ex to visual mode} + + *:vie* *:view* +:vie[w] [+cmd] file Same as :edit, but set 'readonly' option for this + buffer. {not in Vi} + + *CTRL-^* +[count]CTRL-^ Edit [count]th alternate file (equivalent to ":e + #[count]"). Without count this gets you to the + previously edited file. This is a quick way to + toggle between two (or more) files. If the + 'autowrite' option is on and the buffer was + changed, write it. + +]f *]f* +[f *[f* *gf* +gf Edit the file whose name is under or after the + cursor. Mnemonic: "goto file". Uses the 'isfname' + option to find out which characters are supposed to be + in a file name. Uses the 'path' variable as a list of + directory names to look for the file. Also looks for + the file relative to the current file. This command + fails if the current file cannot be abandoned. If the + name is a hypertext link, that looks like + "type://machine/path", only "/path" is used. For Unix + the '~' character is expanded, like in "~user/file". + {not in Vi} + + *:cd* +:cd On non-Unix systems: Print the current directory + name. On Unix systems: Change the current directory + to the home directory. + +:cd {path} Change the current directory to {path}. Does not + change the meaning of an already entered file name, + because its full path name is remembered. On MS-DOS + this also changes the active drive. + + *:chd* *:chdir* +:chd[ir] [path] Same as :cd. + + *:pw* *:pwd* +:pw[d] Print the current directory name. {Vi: no pwd} + +These commands are used to start editing a single file. This means that the +file is read into the buffer and the current filename is set. You may use the +":cd" command to get to another directory, so you will not have to type that +directory name in front of the filenames. One warning: After using ":cd" the +full path name will be used for reading and writing files. On some networked +file systems this may cause problems. The result of using the full path name +is that the file names currently in use will remain referring to the same +file. Example: If you have a file a:test and a directory a:vim the commands +":e test" ":cd vim" ":w" will overwrite the file a:test and not write +a:vim/test. But if you do ":w test" the file a:vim/test will be written, +because you gave a new file name and did not refer to a file name before the +":cd". + + *:filename* +Note for systems other than Unix and MS-DOS: When using a command that +accepts a single file name (like ":edit file") spaces in the file name are +allowed, but trailing spaces are ignored. This is useful on systems that +allow file names with embedded spaces (like the Amiga). Example: The command +":e Long File Name " will edit the file "Long File Name". When using a +command that accepts more than one file name (like ":next file1 file2") +embedded spaces must be escaped with a backslash. + +On Unix you can also use backticks in the file name, for example: + :e `find . -name ver\\*.c -print` +The backslashes before the star are required to prevent "ver*.c" to be +expanded by the shell before executing the find program. + +You can use the ":e!" command if you messed up the buffer and want to start +all over again. The ":e" command is only useful if you have changed the +current filename. + +Note that ":e file" will fail if there are changes in the current buffer, +also when the 'autowrite' option is on. This is illogical, because with +other commands (e.g., ":next") the current buffer would be written and +abandoned, but this behaviour is compatible with Vi. If you encounter this +problem, you can use CTRL-^ to jump to the file, because the alternate file +name is set to the argument of the ":e" command. + + *:+cmd* +The [+cmd] can be used to position the cursor in the newly opened file: + + Start at the last line. + +{num} Start at line {num}. + +/{pat} Start at first line containing {pat}. {pat} must not + contain any spaces. + +{command} Execute {command} after opening the new file. + {command} is an Ex command. It must not contain + spaces. + + *textmode_io* +When reading a file when the 'textmode' option is off (default for +non-MS-DOS) the character is interpreted as end-of-line. If 'textmode' +is on (default for MS-DOS), is also interpreted as end-of-line. +Also see |textmode_read|. + +When writing a file when the 'textmode' option is off a character is +used to separate lines. When the 'textmode' option is on is used. +Also see |textmode_write|. + +You can read a file with 'textmode' set and write it with 'textmode' reset. +This will replace all pairs by . If you read a file with +'textmode' reset and write with 'textmode' set, all characters will be +replaced with . + +If you start editing a new file and the 'textauto' option is on (which is the +default), Vim will try to detect whether the lines in the file are separated +by a single (as used on Unix and Amiga) or by a pair (MS-DOS). +Only when ALL lines end in the 'textmode' option is set, otherwise it +is reset. If the 'textmode' option is set on non-MS-DOS systems the message +"[textmode]" is shown to remind you that something unusual is happening. On +MS-DOS systems you get the message "[notextmode]" if the 'textmode' option is +not set. + +If the 'textauto' option is off and 'textmode' is on, but while reading a file +some lines did not end in , "[CR missing]" will be included in the +file message. + +Before editing binary, executable or Vim script files you should set the +'textmode' and 'textauto' options off. With 'textmode' on you risk that +single characters are unexpectedly replaced with . A simple way +to do this is by starting Vim with the "-b" option. + + +5.3 The argument list *argument_list* + +If you give more than one filename when starting Vim, this list is +remembered as the argument list. Do not confuse this with the file list, +which you can see with the ":files" command |:files|. The argument list was +already present in Vi, the file list is new in Vim. The file names in the +argument list will also be present in the file list (unless they were +deleted with ":bdel"). + +You can use the argument list with the following commands: + + *:ar* *:args* +:ar[gs] Print the argument list, with the current file in + square brackets. + + *:argu* *:argument* +:[count]argu[ment] [count] [+cmd] + Edit file [count] in the argument list, unless + changes have been made and the 'autowrite' option is + off. {Vi: no such command} + +:[count]argu[ment]! [count] [+cmd] + Edit file [count] in the argument list, discard any + changes to the current buffer. {Vi: no such command} + + *:n* *:next* +:[count]n[ext] [+cmd] Edit [count] next file, unless changes have been + made and the 'autowrite' option is off {Vi: no + count}. + +:[count]n[ext]! [+cmd] Edit [count] next file, discard any changes to the + buffer {Vi: no count}. + +:ar[gs] [+cmd] {arglist} +:n[ext] [+cmd] {arglist} + Define {arglist} as the new argument list and edit + the first one, unless changes have been made and the + 'autowrite' option is off. + +:ar[gs]! [+cmd] {arglist} +:n[ext]! [+cmd] {arglist} *:next_f* + Define {arglist} as the new argument list and edit + the first one. Discard any changes to the buffer. + +:[count]N[ext] [count] [+cmd] *:Next* *:N* + Edit [count] previous file in argument list, unless + changes have been made and the 'autowrite' option is + off {Vi: no count}. + +:[count]N[ext]! [count] [+cmd] + Edit [count] previous file in argument list. Discard + any changes to the buffer {Vi: no count}. + +:[count]prev[ious] [count] [+cmd] *:prev* *:previous* + Same as :Next {Vi: only in some versions} + + *:rew* *:rewind* +:rew[ind] [+cmd] Start editing the first file in the argument list, + unless changes have been made and the 'autowrite' + option is off. + +:rew[ind]! [+cmd] Start editing the first file in the argument list. + Discard any changes to the buffer. + + *:la* *:last* +:la[st] [+cmd] Start editing the last file in the argument list, + unless changes have been made and the 'autowrite' + option is off. {not in Vi} + +:la[st]! [+cmd] Start editing the last file in the argument list. + Discard any changes to the buffer. {not in Vi} + + *:wn* *:wnext* +:[count]wn[ext] [+cmd] Write current file and start editing the [count] + next file. {not in Vi} + +:[count]wn[ext] [+cmd] {file} + Write current file to {file} and start editing the + [count] next file, unless {file} already exists and + the 'writeany' option is off. {not in Vi} + +:[count]wn[ext]! [+cmd] {file} + Write current file to {file} and start editing the + [count] next file. {not in Vi} + +:[count]wN[ext][!] [+cmd] [file] *:wN* *:wNext* +:[count]wp[revous][!] [+cmd] [file] *:wp* *:wprevious* + Same as :wnext, but go to previous file instead of + next. {not in Vi} + +The [count] in the commands above defaults to one. For some commands it is +possible to use two counts. The last one (rightmost one) is used. + +For [+cmd] see 5.2 |edit_a_file|. + +The wildcards in the argument list are expanded and the filenames are sorted. +Thus you can use the command "vim *.c" to edit all the C files. From within +Vim the command ":n *.c" does the same. On Unix you can also use backticks, +for example: + :n `find . -name \\*.c -print` +The backslashes before the star are required to prevent "*.c" to be expanded +by the shell before executing the find program. + + *arglist_quit* +You are protected from leaving Vim if you have not been editing the last file +in the argument list. This prevents you from forgetting that you were editing +one out of several files. To exit anyway try to exit twice. If there are +changes in the current buffer this will fail. You can exit anyway, and save +any changes, with the ":wq!" command. To lose any changes use the ":q!" +command. + +When there is an argument list you can see which file you are editing in the +title of the window (if there is one and 'title' is on) and with the file +message you get with the "CTRL-G" command. You will see something like + (file 4 of 11) +If 'shortmess' contains 'f' it will be + (4 of 11) +If you are not really editing the file at the current position in the argument +list it will be + (file (4) of 11) +This means that you are position 4 in the argument list, but not editing the +fourth file in the argument list. This happens when you do ":e file". + + +5.4 Writing and quitting *write_quit* *save_file* + + *:w* *:write* +:[range]w[rite][!] Write the specified lines to the current file. + + *:w_f* *:write_f* +:[range]w[rite] {file} Write the specified lines to {file}, unless it + already exists and the 'writeany' option is off. + + *:w!* +:[range]w[rite]! {file} Write the specified lines to {file}. Overwrite an + existing file. + + *:w_a* *:write_a* +:[range]w[rite][!] >> Append the specified lines to the current file. + +:[range]w[rite][!] >> {file} + Append the specified lines to {file}. '!' forces the + write even if file does not exist. + + *:w_c* *:write_c* +:[range]w[rite] !{cmd} Execute {cmd} with [range] lines as standard input + (note the space in front of the '!'). {cmd} is + executed like with ":!{cmd}", any '!' is replaced with + the previous command |:!|. + +The default [range] for the ":w" command is the whole buffer (1,$). +If a file name is give with ":w" it becomes the alternate file. This can be +used when the write fails and you want to try again later with ":w #". + + + *:q* *:quit* +:q[uit] Quit, unless changes have been made or not editing + the last file in the argument list. + +:q[uit]! Quit always, without writing. + +:cq Quit always, without writing, and return an error + code. Used for Manx's QuickFix mode (see 5.5 + |quickfix|). + + *:wq* +:wq Write the current file and exit (unless editing the + last file in the argument list or the file is + read-only). + +:wq! Write the current file and exit. + +:wq {file} Write to {file}. Exit if not editing the last + file in the argument list. + +:wq! {file} Write to {file} and exit. + +:[range]wq[!] [file] Same as above, but only write the lines in [range]. + + *:x* *:xit* +:[range]x[it][!] [file] + Like ":wq", but write only when changes have been + made. + + *:exi* *:exit* +:[range]exi[t][!] [file] + Same as :xit. + + *ZZ* +ZZ Write current file, if modified, and exit (same as + ":x"). (Note: If there are several windows for the + current file, the file is written if it was modified + and the window is closed). + + *timestamp* +Vim remembers the timestamp of the file when you start editing it. When you +write a file the timestamp is checked. If the file has been changed since you +started editing it, Vim will ask you if you really want to overwrite the file: + + WARNING: The file has been changed since reading it!!! + Do you really want to write to it (y/n)? + +If you hit 'y' Vim will continue writing the file. If you hit 'n' the write is +aborted. If you used ":wq" or "ZZ" Vim will not exit, you will get another +chance to write the file. +The message would normally mean that somebody has written to the file after +the edit session started. This could be another person, in which case you +probably want to check if your changes to the file and the changes from the +other person should be merged. Write the file under another name and check for +differences (the "diff" program can be used for this). +It is also possible that you modified the file yourself, from another edit +session or with another command (e.g., a filter command). Then you will know +which version of the file you want to keep. + + *backup* +If you write to an existing file (but do not append) while the 'backup', +'writebackup' or 'patchmode' option is on, a backup of the original file is +made. On Unix systems the file is copied, on other systems the file is +renamed. After the file has been successfully written and when the +'writebackup' option is on and the 'backup' option is off, the backup file is +deleted. When the 'patchmode' option is on the backup file may be renamed. + + *backup_table* +'backup' 'writebackup' action + off off no backup made + off on backup current file, deleted afterwards (default) + on off delete old backup, backup current file + on on delete old backup, backup current file + +When the 'backup' option is on, an old backup file (with the same name as the +new backup file) will be deleted. If 'backup' is not set, but 'writebackup' is +set, an existing backup file will not be deleted. The backup file that is made +while the file is being written will have a different name. + +The directories given with the 'backupdir' option is used to put the backup +file in. (default: same directory as the written file). + +On Unix systems: +When you write to an existing file, that file is truncated and then filled +with the new text. This means that protection bits, owner and symbolic links +are unmodified. The backup file however, is a new file, owned by the user who +edited the file. The group of the backup is set to the group of the original +file. If this fails, the protection bits for the group are made the same as +for others. + +If the creation of a backup file fails, the write is not done. If you want +to write anyway add a '!' to the command. + + *write_fail* +If the writing of the new file fails, you have to be careful not to lose +your changes AND the original file. If there is no backup file and writing +the new file failed, you have already lost the original file! DON'T EXIT VIM +UNTIL YOU WRITE OUT THE FILE! If a backup was made, it is put back in place +of the original file (if possible). If you exit Vim, and lose the changes +you made, the original file will mostly still be there. If putting back the +original file fails, there will be an error message telling you that you +lost the original file. + + *textmode_write* +If the 'textmode' option is on is used for end-of-line. This is +default for MS-DOS, Win32 and OS/2. On other systems the message "[textmode]" +is shown to remind you that an usual end-of-line marker was used. If the +'textmode' is not set NL is used for end-of-line. On MS-DOS, Win32 and OS/2 +the message "[notextmode]" is shown. See also |textmode_io| and the +'textmode' and 'textauto' options. + + +5.5 Using the QuickFix mode *quickfix* + +Vim has a special mode to speedup the edit-compile-edit cycle. This is +inspired by the quickfix option of the Manx's Aztec C compiler on the Amiga. +The idea is to save the error messages from the compiler in a file and use +Vim to jump to the errors one by one. You can then examine each problem and +fix it, without having to remember all the error messages. + +If you are using Manx's Aztec C compiler on the Amiga you should do the +following: +- Set the CCEDIT environment variable with the command + mset "CCEDIT=vim -e" +- Compile with the -qf option. If the compiler finds any errors, Vim is + started and the cursor is positioned on the first error. The error message + will be displayed on the last line. You can go to other errors with the + commands mentioned below. You can fix the errors and write the file(s). +- If you exit Vim normally the compiler will re-compile the same file. If you + exit with the :cq command, the compiler will terminate. Do this if you + cannot fix the error, or if another file needs to be compiled first. + +If you are using another compiler you should save the error messages in a +file and start Vim with "vim -e filename". An easy way to do this is with +the ":make" command (see below). The 'errorformat' option should be set to +match the error messages from your compiler (see below). + +The following commands can be used if you are in QuickFix mode: + + *:cc* +:cc [nr] Display error [nr]. If [nr] is omitted, the same + error is displayed again. {not in Vi} + + *:cn* *:cnext* +:[count]cn[ext] Display the [count] next error in the list that + includes a file name. If there are no file names at + all, go the the [count] next error. {not in Vi} + +:[count]cN[ext] *:cp* *:cprevious* *:cN* *:cNext* +:[count]cp[revious] Display the [count] previous error in the list that + includes a file name. If there are no file names at + all, go the the [count] previous error. {not in Vi} + + *:cq* *:cquit* +:cq[uit] Quit Vim with an error code, so that the compiler + will not compile the same file again. {not in Vi} + + *:cf* *:cfile* +:cf[ile] [errorfile] Read the error file and jump to the first error. + This is done automatically when Vim is started with + the -e option. You can use this command when you + keep Vim running while compiling. If you give the + name of the errorfile, the 'errorfile' option will + be set to [errorfile] {not in Vi} + + *:cl* *:clist* +:cl[ist] List all errors that inlcude a file name. {not in Vi} + +:cl[ist]! List all errors. {not in Vi} + + *:mak* *:make* +:mak[e] [arguments] 1. If the 'autowrite' option is on, write any changed + buffers + 2. Any existing 'errorfile' is deleted. + 3. The program given with the 'makeprg' option is + started (default "make") with the optional + [arguments] and the output is saved in + 'errorfile' (for Unix it is also echoed on the + screen). + 4. The 'errorfile' is then read and the first error + is jumped to. + 5. The 'errorfile' is deleted. + {not in Vi} + +The name of the file can be set with the 'errorfile' option. The default is +"AztecC.Err" for the Amiga and "errors.vim" for other systems. The format of +the file from the Aztec compiler is: + + filename>linenumber:columnnumber:errortype:errornumber:errormessage + + filename name of the file in which the error was detected + linenumber line number where the error was detected + columnnumber column number where the error was detected + errortype type of the error, normally a single 'E' or 'W' + errornumber number of the error (for lookup in the manual) + errormessage description of the error + + *errorformat* +Another compiler is likely to use a different format. You should set the +'errorformat' option to a scanf-like string that describes the format. +First, you need to know how scanf works. Look in the documentation of your +C compiler. Vim will understand eight conversion characters. Others are +invalid. + %f file name (finds a string) + %l line number (finds a number) + %c column number (finds a number) + %t error type (finds a single character) + %n error number (finds a number) + %m error message (finds a string) + %* any scanf non-assignable conversion + %% the single '%' character + +Examples: +%f>%l:%c:%t:%n:%m" for the AztecC.Err file +%f:%l:\ %t%*[^0123456789]%n:\ %m for Manx/Aztec C error messages + (scanf() doesn't understand [0-9]) +%f\ %l\ %t%*[^0-9]%n:\ %m for SAS C +\"%f\"\\,%*[^0-9]%l:\ %m for generic C compilers +%f:%l:\ %m for GCC +%f(%l)\ :\ %*[^:]:\ %m old SCO C compiler (pre-OS5) +%f(%l)\ :\ %t%*[^0-9]%n:\ %m idem, with error type and number +%f:%l:\ %m,In\ file\ included\ from\ %f:%l:,\^I\^Ifrom\ %f:%l%m + for GCC, with some extras + +Note the backslash in front of a space and double quote. It is required for +the :set command. There are two backslashes in front of a comma, one for the +:set command and one to avoid recognizing the comma as a separator of error +formats. + +The "%f" and "%m" conversions have to detect the end of the string. They +should be followed by a character that cannot be in the string. Everything +up to that character is included in the string. Be careful: "%f%l" will +include everything up to the first '%' in the file name. If the "%f" or "%m" +is at the end, everything up to the end of the line is included. + +To be able to detect output from several compilers, several format patterns +may be put in 'errorformat', separated by commas (note: blanks after the comma +are ignored). The first pattern that has a complete match is used. If no +match is found, matching parts from the last one will be used, although the +file name is removed and the error message is set to the whole message. If +there is a pattern that may match output from several compilers (but not in a +right way), put it after one that is more restrictive. To include a comma in +a pattern precede it with a backslash (you have to type two in a set command). +To include a backslash itself give two backslashes (you have to type four in a +set command). + +If a line is detected that does not completely match the 'errorformat', the +whole line is put in the error message and the entry is marked "not valid" +These lines are skipped with the ":cn" and ":cp" commands (unless there is +no valid line at all). You can use ":cl!" to display all the error messages. + +If the error format does not contain a file name Vim cannot switch to the +correct file. You will have to do this by hand. + +If you have a compiler that produces error messages that do not fit in the +format string, you could write a program that translates the error messages +into this format. You can use this program with the ":make" command by +changing the 'makeprg' option. For example: + ":set mp=make\ \\\|&\ error_filter". +The backslashes before the pipe character are required to avoid it to be +recognized as a command separator. The backslash before each space is +required for the set command. + + *:make_makeprg* +The ":make" command executes the command given with the 'makeprg' option. +This is done by passing the command to the shell given with the 'shell' +option. This works almost like typing + + ":!{makeprg} [arguments] {shellpipe} {errorfile}". + +{makeprg} is the string given with the 'makeprg' option. Any command can be +used, not just "make". Characters '%' and '#' are expanded as usual on a +command line. You can use "#<" to insert the current filename without +extension, for example ":set makeprg=make\ #<.o". + +[arguments] is anything that is typed after ":make". +{shellpipe} is the 'shellpipe' option. +{errorfile} is the 'errorfile' option. + +The 'shellpipe' option defaults to ">" for the Amiga, MS-DOS and Win32. This +means that the output of the compiler is saved in a file and not shown on the +screen directly. For Unix "| tee" is used. The compiler output is shown on +the screen and saved in a file the same time. Depending on the shell used +"|& tee" or "2>&1| tee" is the default, so stderr output will be included. + +There are some restrictions to the Quickfix mode on the Amiga. The +compiler only writes the first 25 errors to the errorfile (Manx's +documentation does not say how to get more). If you want to find the others, +you will have to fix a few errors and exit the editor. After recompiling, +up to 25 remaining errors will be found. + +On the Amiga, if Vim was started from the compiler, the :sh and :! commands +will not work, because Vim is then running in the same process as the +compiler and these two commands may guru the machine then. + +If you insert or delete lines, mostly the correct error location is still +found because hidden marks are used (Manx's Z editor does not do this). +Sometimes, when the mark has been deleted for some reason, the message "line +changed" is shown to warn you that the error location may not be correct. If +you quit Vim and start again the marks are lost and the error locations may +not be correct anymore. + + +5.6 Editing binary files *edit_binary* + +Although Vim was made to edit text files, it is possible to edit binary +files. The "-b" Vim argument (b for binary) sets some options for +editing binary files ('binary' on, 'textwidth' to 0, 'textmode' and +'textauto' off, 'modeline' off, 'expandtab' off). Setting the 'binary' +option has the same effect. Don't forget to do this before reading the file. + +There are a few things to remember when editing binary files: +- When editing executable files the number of characters must not change. + Use only the "R" or "r" command to change text. Do not delete characters + with "x" or by backspacing. +- Set the 'textwidth' option to 0. Otherwise lines will unexpectedly be + split in two. +- When there are not many end-of-line characters, the lines will become very + long. If you want to edit a line that does not fit on the screen reset the + 'wrap' option. Horizontal scrolling is used then. If a line becomes too + long (more than about 32767 characters on the Amiga, much more on 32-bit + systems, see |limits|) you cannot edit that line. The line will be split + when reading the file. It is also possible that you get an "out of memory" + error when reading the file. +- Make sure the 'textmode' and 'textauto' options are off before loading the + file. In 'textmode' both and are considered to end a line + and when the file is written the will be replaced with . The + 'modelines' option should also be off, because there may be a string like + ":vi:" in the file that would give unpredictable results. +- characters are shown on the screen as ^@. You can enter them with + "CTRL-V CTRL-@" or "CTRL-V 000" {Vi cannot handle characters in the + file} +- To insert a character in the file split up a line. When writing the + buffer to a file a will be written for the end of line. +- Vim normally appends an end-of-line character at the end of the file if + there is none. Setting the 'binary' option prevents this. If you want to + add the final end-of-line, set the 'endofline' option. You can also read the + value of this option to see if there was an end-of-line character for the + last line (you cannot see this in the text). + + +5.7 Automatic commands *autocommand* + +You can specify commands to be executed automatically for when reading or +writing a file, when entering or leaving a buffer or window, and when exiting +Vim. For example, 'cindent' can be set for files matching *.c, and unset +otherwise. Autocommands can be used to edit compressed files. These commands +are normally put in your .vimrc or .exrc file. {All this is not in Vi} + +WARNING: Using autocommands is very powerful, but may lead to unexpected side +effects. Be careful not to destroy your text. +- It's a good idea to first do some testing on a copy of a file first. For + example: If you use autocommands to decompress a file when starting to edit + it, make sure that the autocommands for compressing when writing work + correctly. +- Be prepared for an error halfway through (e.g., disk full). Vim will mostly + be able to undo the changes to the buffer, but you may have to clean up the + changes to other files by hand (e.g., compress a file that has been + decompressed). +- If the BufRead* events allow you to edit a compressed file, the FileRead* + events should do the same (to be able to do recovery in some rare cases). + It's a good idea to use the same autocommands for the File* and Buf* events + when possible. + +The autocommand feature is only included if Vim has been compiled with AUTOCMD +defined. If the output of ":version" contains "+autocmd" it is included (this +is the default), if it contains "-autocmd" then the autocommand feature +doesn't work. + +Note: This command cannot be followed by another command, since any '|' is +considered part of the command. + + *:au* *:autocmd* +:au[tocmd] {event} {pat} {cmd} + Add {cmd} to the list of commands that will be + automatically executed on {event} for a file matching + {pat}. It is not added if it is already there (as may + happen when .vimrc is sourced again). The order of + entering {cmd} and {pat} is important. + +:au[tocmd] {event} {pat} + Show the auto-commands associated with {event} and + {pat}. + +:au[tocmd] * {pat} Show the auto-commands associated with pat} for all + events. + +:au[tocmd] {event} Show all auto-commands for {event}. + +:au[tocmd] Show all auto-commands. + +:au[tocmd]! {event} {pat} {cmd} + Remove all auto-commands associated with {event} and + {pat}, and add the command {cmd}. + +:au[tocmd]! {event} {pat} + Remove all auto-commands associated with {event} and + {pat}. + +:au[tocmd]! * {pat} Remove all auto-commands associated with {pat} for all + events. + +:au[tocmd]! {event} Remove ALL auto-commands for {event}. + +:au[tocmd]! Remove ALL auto-commands. + + *:do* *:doautocmd* +:do[autocmd] {event} [fname] + Apply the autocommands matching [fname] (default: + current file name) for {event} to the current buffer. + This can be used when the current file name does not + match the right pattern, after changing settings, or + to execute autocommands for a certain event. + +These events are recognized. Case is ignored, for example "BUFread" and +"bufread" can be used instead of "BufRead". + + *BufNewFile* +BufNewFile When starting to edit a file that doesn't + exist. Can be used to read in a skeleton + file. + *BufReadPre* +BufReadPre When starting to edit a new buffer, before + reading the file into the buffer. Not used + when starting to edit a new file. + *BufRead* *BufReadPost* +BufRead or BufReadPost When starting to edit a new buffer, after + reading the file into the buffer, before + executing the modelines. This does NOT work + for ":r file". Not used when starting to edit + a new file. + *FileReadPre* +FileReadPre Before reading a file with a ":read" command. + *FileReadPost* +FileReadPost After reading a file with a ":read" command. + Note that the '[ and '] marks are set to the + first and last line of the read, this can be + used to operate on the just read lines. + *FilterReadPre* +FilterReadPre Before reading a file from a filter command. + The file name of the current buffer is used to + match with the pattern, not the name of the + temporary file that is the output of the + filter command. + *FilterReadPost* +FilterReadPost After reading a file from a filter command. + Like FilterReadPre, the file name of the + current buffer is used. + *BufWrite* *BufWritePre* +BufWrite or BufWritePre Before writing the whole buffer to a file. + *BufWritePost* +BufWritePost After writing the whole buffer to a file + (should undo the commands for BufWritePre). + *FileWritePre* +FileWritePre Before writing to a file, when not writing the + whole buffer. + *FileWritePost* +FileWritePost After writing to a file, when not writing the + whole buffer. + *FileAppendPre* +FileAppendPre Before appending to a file. + *FileAppendPost* +FileAppendPost After appending to a file. + *FilterWritePre* +FilterWritePre Before writing a file for a filter command. + The file name of the current buffer is used to + match with the pattern, not the name of the + temporary file that is the input for the + filter command. + *FilterWritePost* +FilterWritePost After writing a file for a filter command. + Like FilterWritePre, the file name of the + current buffer is used. + *BufEnter* +BufEnter After entering a buffer. Useful for setting + options for a file type. Also executed when + starting to edit a buffer, after the + BufReadPost autocommands. + *BufLeave* +BufLeave Before leaving to another buffer. Also when + leaving or closing the current window and the + new current window is not for the same buffer. + *WinEnter* +WinEnter After entering another window. Not done for + the first window, when Vim is just started. + Useful for setting the window height. + If the window is for another buffer, the + BufEnter autocommands are executed after the + WinEnter autocommands. + *WinLeave* +WinLeave Before leaving to another window. If the + window to be entered is for a different + buffer, the BufLeave autocommands are executed + before the WinLeave autocommands. + *VimLeave* +VimLeave Before exiting Vim, just before writing the + .viminfo file. There is no VimEnter event, + because you can use the .vimrc for that. + +For READING FILES there are three possible pairs of events, only one pair is +used at a time: +BufNewFile starting to edit a non-existant file +BufReadPre BufReadPost starting to edit an existing file +FilterReadPre FilterReadPost read the temp file with filter output +FileReadPre FileReadPost any other file read + +Before the *ReadPre event the '[ mark is set to the line just above where the +new lines will be inserted. +Before the *ReadPost event the '[ mark is set to the first line that was just +read, the '] mark to the last line. +Careful: '[ and '] will change when using commands that change the buffer. +"" can be used for the file name that is being read, in commands where +a file name is expected (where you can also use "%" for the current file +name) |:|. + +Examples for reading compressed files: + :autocmd! BufReadPre,FileReadPre *.gz set bin + :autocmd BufReadPost,FileReadPost *.gz '[,']!gunzip + :autocmd BufReadPost,FileReadPost *.gz set nobin + +NOTE: When using the examples given, any existing autocommands for the same +event/pattern combination will be removed, because of the '!'. + +For WRITING FILES there are four possible pairs of events, only one pair is +used at a time: +BufWritePre BufWritePost writing the whole buffer +FilterWritePre FilterWritePost writing to the temp file with filter input +FileAppendPre FileAppendPost appending to a file +FileWritePre FileWritePost any other file write + +Note that the *WritePost commands should undo any changes to the buffer that +were caused by the *WritePre commands, otherwise writing the file will have +the side effect of changing the buffer. + +Before the *WritePre event the '[ mark is set to the first line that will be +written, the '] mark to the last line. +Careful: '[ and '] will change when using commands that change the buffer. +"" can be used for the file name that is being written, in commands +where a file name is expected (where you can also use "%" for the current file +name) |:|. + +Examples for writing compressed files: + :autocmd! BufWritePost,FileWritePost *.gz !mv :r + :autocmd BufWritePost,FileWritePost *.gz !gzip :r + + :autocmd! FileAppendPre *.gz !gunzip + :autocmd FileAppendPre *.gz !mv :r + :autocmd! FileAppendPost *.gz !mv :r + :autocmd FileAppendPost *.gz !gzip :r + +(":r" is the file name without the extension, see |:_%:|) + +The commands executed for the BufNewFile, BufRead/BufReadPost, BufWritePost, +FileAppendPost and VimLeave events do not set or reset the changed flag of the +buffer. When you decompress the buffer with the BufReadPost autocommands, you +can still exit with ":q". When you use ":undo" in BufWritePost to undo the +changes made by BufWritePre commands, you can still do ":q" (this also makes +"ZZ" work). + +To execute Normal mode commands from an autocommand, use the ":normal" +command. Use with care! If the Normal mode command is not finished, the user +needs to type characters (e.g., after ":normal m" you need to type a mark +name). + +If you want the buffer not to be modified after changing it, reset the +'modified' option. This makes it possible to exit the buffer with ":q" +instead of ":q!". + +Autocommands do not nest. If you use ":r" or ":w" in an autocommand, the +BufRead and BufWrite autocommands are not executed for those commands. It's +also not possible to use the ":au" command in an autocommand (that could be a +self-modifying command!). + +There is currently no way to disable the autocommands. If you want to write a +file without executing the autocommands for that type of file, write it under +another name and rename it with a shell command. + +Note: When doing a ":read file" command and the last line in the file does not +have an end-of-line character, this is remembered. When executing the +FileReadPost autocommands and the same line is written again as the last line +in a file, no end-of-line character is written if 'binary' is set. This makes +a filter command on the just read lines write the same file as was read. + +Multiple patterns may be given separated by commas. Here are some examples: + + :autocmd BufRead * set tw=79 nocin ic infercase fo=2croq + :autocmd BufRead .letter set tw=72 fo=2tcrq + :autocmd BufEnter .letter set dict=/usr/lib/dict/words + :autocmd BufLeave .letter set dict= + :autocmd BufRead,BufNewFile *.c,*.h set tw=0 cin noic + :autocmd BufEnter *.c,*.h abbr FOR for(i = 0; i < 3; i++)^M{^M}^[O + :autocmd BufLeave *.c,*.h unabbr FOR + +For makefiles (makefile, Makefile, imakefile, makefile.unix, etc.): + + :autocmd BufEnter ?akefile* set include=^s\=include + :autocmd BufLeave ?akefile* set include& + +To always start editing C files at the first function: + + :autocmd BufRead *.c,*.h 1;/^{ + +Without the "1;" above, the search would start from wherever the file was +entered, rather than from the start of the file. + +To read a skeleton file for new C files: + + :autocmd BufNewFile *.c 0r ~/.skeleton.c + :autocmd BufNewFile *.h 0r ~/.skeleton.h + +To insert the current date and time in a *.html file when writing it: + +:autocmd BufWritePre,FileWritePre *.html ks|1,20g/Last modification: /normal f:lD:read !date^MkJ's + +(to insert the ^M type CTRL-V CTRL-M) +You need to have a line "Last modification: " in the first 20 lines +of the file for this to work. The (and anything in the same line +after it) will be replaced with the current date and time. Explanation: + ks mark current position with mark 's' + 1,20g/pattern/ find lines that contain the pattern + normal f: find the ':' + lD delete the old date and time + !date^M read the current date and time into the next line + kJ Join the date and time with the previous line + 's return the cursor to the old position + +When entering :autocmd on the command line, completion of events and command +names may be done (with , CTRL-D, etc.) where appropriate. + +All matching auto-commands will be executed in the order that they were +specified. It is recommended that your first auto-command be used for all +files by using "*" as the file pattern. This means that you can define +defaults you like here for any settings, and if there is another matching +auto-command it will override these. But if there is no other matching +auto-command, then at least your default settings are recovered (if entering +this file from another for which auto-commands did match). Note that "*" will +also match files starting with ".", unlike Unix shells. + +Normally the file pattern is tested for a match against just the tail part of +the file name (without its leading directory path), but if a path separator +character (eg '/' on Unix) appears in the pattern, then it will be tested +against the full file name. For example: + + :autocmd BufRead */vim/src/* set wrap + + +6. Cursor motions *cursor_motions* +================= + +These commands move the cursor position. If the new position is off of the +screen, the screen is scrolled to show the cursor (see also 'scrolljump' and +'scrolloff' options). + + *operator* +The motion commands can be used after an operator command, to have the command +operate on the text that was moved over. That is the text between the cursor +position before and after the motion. Operators are generally used to delete +or change text. The following operators are available: + |c| c change + |d| d delete + |y| y yank into register (does not change the text) + |~| ~ swap case (only if 'tildeop' is set) + |g~| g~ swap case + |gu| gu make lower case + |gU| gU make upper case + |!| ! filter through an external program + |=| = filter through 'equalprg' or C-indenting if empty + |Q| Q text formatting (obsolete) + |gq| gq text formatting + |>| > shift right + |<| < shift left +If the motion includes a count and the operator also had a count before it, +the two counts are multiplied. For example: "2d3w" deletes six words. + The operator either affects whole lines, or the characters between the +start and end position. Generally, motions that move between lines affect +lines (are linewise), and motions that move within a line affect characters. +However, there are some exceptions. + A character motion is either inclusive or exclusive. When inclusive, +the start and end position of the motion are included in the operation. +When exclusive, the last character towards the end of the buffer is not +included. Linewise motions always include the start and end position. + Which motions are linewise, inclusive or exclusive is mentioned +below. There are however, two general exceptions: +1. If the motion is exclusive and the end of the motion is in column 1, the + end of the motion is moved to the end of the previous line and the motion + becomes inclusive. Example: "}" ends at the first line after a paragraph, + but "V}" will not include that line. +2. If the motion is exclusive, the end of the motion is in column 1 and the + start of the motion was at or before the first non-blank in the line, the + motion becomes linewise. Example: If a paragraph begins with some blanks + and you do "d}" while standing on the first non-blank, all the lines of + the paragraph are deleted, including the blanks. If you do a put now, the + deleted lines will be inserted below the cursor position. + +Instead of first giving the operator and then a motion you can use Visual +mode: mark the start of the text with "v", move the cursor to the end of the +text that is to be affected and then hit the operator. The text between the +start and the cursor position is highlighted, so you can see what text will +be operated upon. This allows much more freedom, but requires more key +strokes and has limited redo functionality. See the chapter on Visual mode +|Visual_mode|. + +If you want to know where you are in the file use the "CTRL-G" command +|CTRL-G| or the "g CTRL-G command |g_CTRL-G|. If you set the 'ruler' option, +the cursor position is continuously shown in the status line (which slows down +Vim a little). + +NOTE: Experienced users prefer the hjkl keys because they are always right +under their fingers. Beginners often prefer the arrow keys, because they +do not know what the hjkl keys do. The mnemonic value of hjkl is clear from +looking at the keyboard. Think of j as an arrow pointing downwards. + + +6.1 Left-right motions *left_right_motions* + +h or *h* + or ** +CTRL-H or *CTRL-H* ** + [count] characters to the left (exclusive). + Note: If you prefer to delete a character, use + the mapping: + :map CTRL-V X + (to enter "CTRL-V" type the CTRL-V key, followed + by the key) + See |:fixdel| if the key does not do what you + want. + +l or *l* + or ** ** + [count] characters to the right (exclusive). + + *0* +0 To the first character of the line (exclusive). When + moving up or down, stay in same screen column (if + possible). + + ** + To the first character of the line (exclusive). When + moving up or down, stay in same text column (if + possible). Works like "1|", which differs from "0" + when the line starts with a . {not in Vi} + + *^* +^ To the first non-blank character of the line + (exclusive). + + *$* ** +$ or To the end of line and [count - 1] lines downward + (inclusive). + + *g0* *g* +g0 or g When lines wrap ('wrap on): To the first character of + the screen line (exclusive). Differs from "0" when a + line is wider than the screen. + When lines don't wrap ('wrap' off): To the leftmost + character of the current line that is on the screen. + Differs from "0" when the first character of the line + is not on the screen. {not in Vi} + + *g^* +g^ When lines wrap ('wrap' on): To the first non-blank + character of the screen line (exclusive). Differs + from "^" when a line is wider than the screen. + When lines don't wrap ('wrap' off): To the leftmost + non-blank character of the current line that is on the + screen. Differs from "^" when the first non-blank + character of the line is not on the screen. {not in + Vi} + + *g$* *g* +g$ or g When lines wrap ('wrap' on): To the last character of + the screen line and [count - 1] screen lines downward + (inclusive). Differs from "$" when a line is wider + than the screen. + When lines don't wrap ('wrap' off): To the righmost + character of the current line that is visible on the + screen. Differs from "$" when the last character of + the line is not on the screen or when a count is used. + {not in Vi} + + *bar* +| To screen column [count] in the current line + (exclusive). + + *f* +f{char} To [count]'th occurrence of {char} to the right. The + cursor is placed on {char} (inclusive). + + *F* +F{char} To the [count]'th occurrence of {char} to the left. + The cursor is placed on {char} (inclusive). + + *t* +t{char} Till before [count]'th occurrence of {char} to the + right. The cursor is placed on the character left of + {char} (inclusive). + + *T* +T{char} Till after [count]'th occurrence of {char} to the + left. The cursor is placed on the character right of + {char} (inclusive). + + *;* +; Repeat latest f, t, F or T [count] times. + + *,* +, Repeat latest f, t, F or T in opposite direction + [count] times. + +These commands move the cursor to the specified column in the current line. +They stop at the first column and at the end of the line, except "$", which +may move to one of the next lines. See 'whichwrap' option to make some of the +commands move accross line boundaries. + + +6.2 Up-down motions *up_down_motions* + +k or *k* + or ** *CTRL-P* +CTRL-P [count] lines upward (linewise). + +j or *j* + or ** +CTRL-J or *CTRL-J* + or ** *CTRL-N* +CTRL-N [count] lines downward (linewise). + +gk or *gk* *g* +g [count] display lines upward (exclusive). Differs + from 'k' when lines wrap. {not in Vi} + +gj or *gj* *g* +g [count] display lines downward (exclusive). Differs + from 'j' when lines wrap. {not in Vi} + + *-* +- [count] lines upward, on the first non-blank + character (linewise). + ++ or *+* +CTRL-M or *CTRL-M* ** + [count] lines downward, on the first non-blank + character (linewise). + + *_* +_ [count] - 1 lines downward, on the first non-blank + character (linewise). + + or *G* ** +G Goto line [count], default last line, on the first + non-blank character (linewise). If 'startofline' not + set, keep the same column. + + or *gg* ** +gg Goto line [count], default first line, on the first + non-blank character (linewise). If 'startofline' not + set, keep the same column. + +:[range] Set the cursor on the (last) specified line number + (cannot be used with an operator). + + *N%* +{count}% Go to {count} percentage in the file, on the first + non-blank in the line (linewise). To compute the new + line number this formula is used: {count} * + number-of-lines / 100. See also 'startofline' + option. {not in Vi} + +These commands move to the specified line. They stop when reaching the first +or the last line. The first two commands put the cursor in the same column +(if possible) as it was after the last command that changed the column, +except after the "$" command, then the cursor will be put on the last +character of the line. + + +6.3 Word motions *word_motions* + + or ** *w* +w [count] words forward (exclusive). + + *W* +W [count] WORDS forward (exclusive). + + *e* +e Forward to the end of word [count] (inclusive). + + *E* +E Forward to the end of WORD [count] (inclusive). + + or ** *b* +b [count] words backward (exclusive). + + *B* +B [count] WORDS backward (exclusive). + + *ge* +ge Backward to the end of word [count] (inclusive). + + *gE* +gE Backward to the end of WORD [count] (inclusive). + + *word* *WORD* +These commands move over words or WORDS. A word consists of a sequence of +letters, digits and underscores, or a sequence of other non-blank +characters, separated with white space (spaces, tabs, end of line). A WORD +consists of a sequence of non-blank characters, separated with white space. +An empty line is also considered to be a word and a WORD. + +Special case: "cw" and "cW" are treated like "ce" and "cE" if the cursor is +on a non-blank. This is because "cw" is interpreted as change-word, and a +word does not include the following white space. {Vi: "cw" when on a blank +followed by other blanks changes only the first blank; this is probably a +bug, because "dw" deletes all the blanks} + +Another special case: When using the "w" motion in combination with an +operator and the last word moved over is at the end of a line, the end of +that word becomes the end of the operated text, not the first word in the +next line. + +The original Vi implementation of "e" is buggy. For example, the "e" command +will stop on the first character of a line if the previous line was empty. +But when you use "2e" this does not happen. In Vim "ee" and "2e" are the +same, which is more logical. However, this causes a small incompatibility +between Vi and Vim. + + +6.4 Text object motions *object_motions* + + *(* +( [count] sentences backward (exclusive). + + *)* +) [count] sentences forward (exclusive). + + *{* +{ [count] paragraphs backward (exclusive). + + *}* +} [count] paragraphs forward (exclusive). + + *]]* +]] [count] sections forward or to the next '{' in the + first column. When used after an operator, then the + '}' in the first column. (linewise). + + *][* +][ [count] sections forward or to the next '}' in the + first column (linewise). + + *[[* +[[ [count] sections backward or to the previous '{' in + the first column (linewise). + + *[]* +[] [count] sections backward or to the previous '}' in + the first column (linewise). + +These commands move over three kinds of text objects. + + *sentence* +A sentence is defined as ending at a '.', '!' or '?' followed by either the +end of a line, or by a space. {Vi: two spaces} Any number of closing ')', +']', '"' and ''' characters my appear after the '.', '!' or '?' before the +spaces or end of line. A paragraph and section boundary is also a sentence +boundary. + + *paragraph* +A paragraph begins after each empty line, and also at each of a set of +paragraph macros, specified by the pairs of characters in the 'paragraphs' +option. The default is "IPLPPPQPP LIpplpipbp", which corresponds to the +macros ".IP", ".LP", etc. (These are nroff macros, so the dot must be in the +first column). A section boundary is also a paragraph boundary. Note that +this does not include a '{' or '}' in the first column. + + *section* +A section begins after a form-feed in the first column and at each of a set +of section macros, specified by the pairs of characters in the 'sections' +option. The default is "SHNHH HUnhsh". + +The "]" and "[" commands stop at the '{' or <}" in the first column. This is +useful to find the start or end of a function in a C program. Note that the +first character of the command determines the search direction and the +second character the type of brace found. + + +6.5 Text object selection *object_select* + + *v_a* +a select [count] words (see |word|). {not in Vi} + + *v_A* +A select [count] WORDS (see |WORD|). {not in Vi} + + *v_s* +s select [count] sentences (see |sentence|). {not in Vi} + + *v_p* +p select [count] paragraphs (see |paragraph|). {not in + Vi} + + *v_S* +S select [count] blocks, from "[count] [(" to the + matching ')' (see |[(|). {not in Vi} + + *v_P* +P select [count] blocks, from "[count] [{" to the + matching '}' (see |[{|). {not in Vi} + +These object selection commands can only be used in Visual mode and after an +operator. + +When used after an operator: +For non-block objects: + The operator applies to the object and the white space after the + object. If there is no white space after the object or when the + cursor was in the white space before the object, the white space + before the object is included. +For a block object: + The operator applies to the block excluding the surrounding braces. + If the cursor was on one of the braces (or on the indent before '{' or + '}) they are included. + +When used in Visual mode: +When start and end of the Visual area are the same (just after typing "v"): + One object is selected, the same as for using an operator. +When start and end of the Visual area are not the same: + For non-block objects the area is extended by one object or the white + space up to the next object. The direction in which this happens + depends on which side of the Visual area the cursor is. For the block + objects the block is extended one level outwards. + +For illustration, here is a list of delete commands, grouped from small to big +objects. Note that for a single character and a whole line the existing vi +movement commands are used. + "dl" delete character (alias: "x") |dl| + "da" delete word *da* + "dA" delete WORD (see |WORD|) *dA* + "dd" delete line |dd| + "ds" delete sentence *ds* + "dS" delete '(' ')' block *dS* + "dp" delete paragraph *dp* + "dP" delete '{' '}' block *dP* + +Note the difference between using a movement command and an object. The +movement command operates from here (cursor position) to where the movement +takes us. When using an object the whole object is operated upon, no matter +where on the object the cursor is. For example, compare "dw" and "da": "dw" +deletes from the cursor position to the start of the next word, "da" deletes +the word under the cursor and the space after or before it. + + +6.6 Pattern searches *pattern_searches* + + */* +/{pattern}[/] Search forward for the [count]'th occurrence of + {pattern} (exclusive). + +/{pattern}/{offset} Search forward for the [count]'th occurrence of + {pattern} and go {offset} lines up or down (see + below). (linewise). + + */* +/ Search forward for the [count]'th latest used + pattern with latest used {offset}. + +//{offset} Search forward for the [count]'th latest used + pattern with new {offset}. If {offset} is empty no + offset is used. + + *?* +?{pattern}[?] Search backward for the [count]'th previous + occurrence of {pattern} (exclusive). + +?{pattern}?{offset} Search backward for the [count]'th previous + occurrence of {pattern} and go {offset} lines up or + down (see below) (linewise). + + *?* +? Search backward for the [count]'th latest used + pattern with latest used {offset}. + +??{offset} Search backward for the [count]'th latest used + pattern with new {offset}. If {offset} is empty no + offset is used. + + *n* +n Repeat the latest "/" or "?" [count] times. {Vi: no + count} + + *N* +N Repeat the latest "/" or "?" [count] times in + opposite direction. {Vi: no count} + + *star* +* Search forward for the [count]'th occurrence of the + keyword after or under the cursor (exclusive). Only + whole keywords are search for, like with the command + "/\". If there is no keyword after or + under the cursor, any non-blank word is used to + search for. {not in Vi} + + *#* +# Same as "*", but search backward. The English pound + sign (character 163) also works. {not in Vi} + + *gstar* +g* Like "*", but don't put "\<" and "\>" around the word. + This makes the search also find matches that are not a + whole word. {not in Vi} + + *g#* +g# Like "#", but don't put "\<" and "\>" around the word. + This makes the search also find matches that are not a + whole word. {not in Vi} + + HINT: If you want to search for something else than a + whole word, you could use these mappings, to search + for Visually highlighted text: + :vmap / y/" + :vmap ? y?" + (<> notation, see |<>|). + + *gd* +gd Goto local Declaration. When the cursor is on a local + variable, this command will jump to its declaration. + First a search is made for the end of the previous + function, just like "[]". If it is not found the + search stops in line 1. From this position a search is + made for the keyword under the cursor, like with "*", + but lines that look like a comment are ignored (see + 'comments' option). Note that this is not guaranteed + to work, Vim does not really check the syntax, it only + searches for a match with the keyword. If included + files also need to be searched use the commands listed + in |include_search|. {not in Vi} + + *gD* +gD Goto global Declaration. When the cursor is on a + global variable that is defined in the file, this + command will jump to its declaration. This works just + like "gd", except that the search for the keyword + always starts in line 1. {not in Vi} + + *CTRL-C* +CTRL-C Interrupt current (search) command. + +While typing the search pattern the current match will be shown if the +'incsearch' option is on. + +These commands search for the specified pattern. With "/" and "?" an +additional offset may be given. There are two types of offsets: line offsets +and character offsets. {the character offsets are not in Vi} + + *search_offset* +The offset gives the cursor position relative to the found match: + [num] [num] lines downwards, in column 1 + +[num] [num] lines downwards, in column 1 + -[num] [num] lines upwards, in column 1 + e[+num] [num] characters to the right of the end of the match + e[-num] [num] characters to the left of the end of the match + s[+num] [num] characters to the right of the start of the match + s[-num] [num] characters to the left of the start of the match + b[+num] [num] characters to the right of the start (begin) of the match + b[-num] [num] characters to the left of the start (begin) of the match + +If a '-' or '+' is given but [num] is omitted, a count of one will be used. +When including an offset with 'e', the search becomes inclusive (the +character the cursor lands on is included in operations). + +Examples: + +pattern cursor position +/test/+1 one line below "test", in column 1 +/test/e on the last t of "test" +/test/s+2 on the 's' of "test" +/test/b-3 three characters before "test" + +If one of these commands is used after an operator, the characters between +the cursor position before and after the search is affected. However, if a +line offset is given, the whole lines between the two cursor positions are +affected. + + *//;* +A very special offset is ';' followed by another search command. For example: + + /test 1/;/test + /test.*/+1;?ing? + +The first one first finds the next occurence of "test 1", and then the first +occurence of "test" after that. + +This is like executing two search commands after each other, except that: +- It can be used as a single motion command after an operator. +- The direction for a following "n" or "N" command comes from the first + search command. +- When an error occurs the cursor is not moved at all. + +The last used and are remembered. They can be used to +repeat the search, possibly in another direction or with another count. Note +that two patterns are remembered: one for 'normal' search commands and one +for the substitute command ":s". Each time an empty is given, the +previously used is used. + +In Vi the ":tag" command sets the last search pattern when the tag is searched +for. In Vim this is not done, the previous search pattern is still remembered, +unless the 't' flag is present in 'cpoptions'. The search pattern is always +put in the search history. + +If the 'wrapscan' option is on (which is the default), searches wrap around +the end of the buffer. If 'wrapscan' is not set, the backward search stops +at the beginning and the forward search stops at the end of the buffer. If +'wrapscan' is set and the pattern was not found the error message "pattern +not found" is given, and the cursor will not be moved. If 'wrapscan' is not +set the message becomes "search hit BOTTOM without match" when searching +forward, or "search hit TOP without match" when searching backward. If +wrapscan is set and the search wraps around the end of the file the message +"search hit TOP, continuing at BOTTOM" or "search hit BOTTOM, continuing at +TOP" is given when searching backwards or forwards respectively. This can be +switched off by setting the 's' flag in the 'shortmess' option. The highlight +method 'w' is used for this message (default: standout). + +The "*" and "#" commands search for the keyword currently under the cursor. +If there is no keyword under the cursor, the first one to the right is used. +This keyword may only contain letters and characters in 'iskeyword'. Note +that if you type with ten fingers, the characters are easy to remember: the +"#" is under your left hand middle finger (search to the left and up) and +the "*" is under your right hand middle finger (search to the right and +down). If there is no keyword under or after the cursor, a search is done +for any word under or after the cursor. Blanks (s and/or s) are +then recognized as delimiters for the word. + + +The definition of a pattern: *search_pattern* + +Patterns may contain special characters, depending on the setting of the +'magic' option. + +1. A pattern is one or more branches, separated by "\|". It matches anything + that matches one of the branches. Example: "foo\|bar" matches "foo" and + "bar". + +2. A branch is one or more pieces, concatenated. It matches a match for the + first, followed by a match for the second, etc. Example: "foo[0-9]bar", + first match "foo", then a digit and then "bar". + +3. A piece is an atom, possibly followed by: + 'magic' 'nomagic' + option option + * \* matches 0 or more of the preceding atom + \+ \+ matches 1 or more of the preceding atom {not + in Vi} + \= \= matches 0 or 1 of the preceding atom {not in + Vi} + Examples: + .* .\* matches anything, also empty string + ^.\+$ ^.\+$ matches any non-empty line + foo\= foo\= matches "fo" and "foo" + + +4. An atom can be: + - One of these five: + magic nomagic + ^ ^ at beginning of pattern, matches start of + line + $ $ at end of pattern or in front of "\|", + matches end of line + . \. matches any single character + \< \< matches the beginning of a word + \> \> matches the end of a word + \i \i matches any identifier character (see + 'isident' option) {not in Vi} + \I \I like "\i", but excluding digits {not in Vi} + \k \k matches any keyword character (see + 'iskeyword' option) {not in Vi} + \K \K like "\k", but excluding digits {not in Vi} + \f \f matches any file name character (see + 'isfname' option) {not in Vi} + \F \F like "\f", but excluding digits {not in Vi} + \p \p matches any printable character (see + 'isprint' option) {not in Vi} + \P \P like "\p", but excluding digits {not in Vi} + \e \e + \t \t + \r \r + \b \b + ~ \~ matches the last given substitute pattern + \(\) \(\) A pattern enclosed by escaped parentheses + (e.g., "\(^a\)") matches that pattern + x x A single character, with no special meaning, + matches itself + \x \x A backslash followed by a single character, + with no special meaning, matches the single + character + [] \[] A range. This is a sequence of characters + enclosed in "[]" or "\[]". It matches any + single character from the sequence. If the + sequence begins with "^", it matches any + single character NOT in the sequence. If two + characters in the sequence are separated by + '-', this is shorthand for the full list of + ASCII characters between them. E.g., "[0-9]" + matches any decimal digit. To include a + literal "]" in the sequence, make it the + first character (following a possible "^"). + E.g., "[]xyz]" or "[^]xyz]". To include a + literal '-', make it the first or last + character. + +If the 'ignorecase' option is on, the case of letters is ignored. + +It is impossible to have a pattern that contains a line break. + +Examples: +^beep( Probably the start of the C function "beep". + +[a-zA-Z]$ Any alphabetic character at the end of a line. + +\<\I\i or +\(^\|[^a-zA-Z0-9_]\)[a-zA-Z_]\+[a-zA-Z0-9_]* + A C identifier (will stop in front of it). + +\(\.$\|\. \) A period followed by end-of-line or a space. + Note that "\(\. \|\.$\)" does not do the same, + because '$' is not end-of-line in front of '\)'. + This was done to remain Vi-compatible. + +[.!?][])"']*\($\|[ ]\) A search pattern that finds the end of a sentence, + with almost the same definition as the ")" command. + +Technical detail: + characters in the file are stored as in memory. In the display +they are shown as "^@". The translation is done when reading and writing +files. To match a with a search pattern you can just enter CTRL-@ or +"CTRL-V 000". This is probably just what you expect. Internally the +character is replaced with a in the search pattern. What is unusual is +that typing CTRL-V CTRL-J also inserts a , thus also searches for a + in the file. {Vi cannot handle characters in the file at all} + + +6.7 Various motions *various_motions* + + *m* +m Set mark at cursor position (does not move + the cursor, this is not a motion command). + + *:ma* *:mark* +:[range]ma[rk] Set mark at last line number in [range], + column 0. Default is cursor line. + + *:k* +:[range]k Same as :mark, but the space before the mark name can + be omitted. + + *'* *'a* +' To the first non-blank character on the line with + mark (linewise). + *'A* *'0* +' To the first non-blank character on the line with + mark in the correct file (linewise when in + same file, not a motion command when in other file). + {not in Vi} + + *`* *`a* +` To the mark (exclusive). + *`A* *`0* +` To the mark in the correct file (exclusive + when in same file, not a motion command when in + other file). {not in Vi} + + *:marks* +:marks List all the current marks (not a motion command). + {not in Vi} + +:marks {arg} List the marks that are mentioned in {arg} (not a + motion command). For example: + :marks aB + to list marks 'a' and 'B'. {not in Vi} + +A mark is not visible in any way. It is just a position in the file that is +remembered. Do not confuse marks with named registers, they are totally +unrelated. + +'a - 'z lowercase marks, valid within one file +'A - 'Z uppercase marks, also called file marks, valid between files +'0 - '9 numbered marks, set from .viminfo file + +Lowercase marks 'a to 'z are remembered as long as the file remains in the +buffer list. If you remove the file from the buffer list, change a character +in a line or delete a line that contains a mark, that mark is erased. +Lowercase marks can be used in combination with operators. For example: "d't" +deletes the lines from the cursor position to mark 't'. Hint: Use mark 't' for +Top, 'b' for Bottom, etc.. Lowercase marks are restored when using undo and +redo. + +Uppercase marks 'A to 'Z include the file name. {Vi: no uppercase marks} You +can use them to jump from file to file. You can only use an uppercase mark +with an operator if the mark is in the current file. The line number of the +mark remains correct, even if you insert/delete lines or edit another file for +a moment. When the 'viminfo' option is not empty, uppercase marks are kept in +the .viminfo file. See |viminfo_file_marks|. + +Numbered marks '0 to '9 are quite different. They can not be set directly. +They are only present when using a viminfo file |viminfo_file|. Basically '0 +is the location of the cursor when you last exited Vim, '1 the last but one +time, etc. See |viminfo_file_marks|. + + *'[* +'[ To the first non-blank character on the first line + of the previously operated, inserted or putted text. + {not in Vi} + + *`[* +`[ To the first character of the previously operated, + inserted or putted text. {not in Vi} + + *']* +'] To the first non-blank character on the last line of + the previously operated, inserted or putted text. + {not in Vi} + + *`]* +`] To the last character of the previously operated, + inserted or putted text. {not in Vi} + +After executing an operator the Cursor is put at the beginning of the text +that was operated upon. After a put command ("p" or "P") the cursor is +sometimes placed at the first inserted line and sometimes on the last inserted +character. The four commands above put the cursor at either end. Example: +After yanking 10 lines you want to go to the last one of them: "10Y']". After +inserting several lines with the "p" command you want to jump to the lowest +inserted line: "p']". This also works for text that has been inserted. + +Note: After deleting text, the start and end positions are the same, except +when using blockwise Visual mode. These commands do not work when no +operator or put command has been used yet in the current file. + + *'<* +'< To the first non-blank character on the first line + of the last selected Visual area. {not in Vi}. + + *`<* +`< To the first character of the last selected Visual + area. {not in Vi}. + + *'>* +'> To the first non-blank character on the last line + of the last selected Visual area. {not in Vi}. + + *`>* +`> To the last character of the last selected Visual + area. {not in Vi}. + + *''* +'' To the first non-blank character of the line where + the cursor was before the latest jump (linewise). + + *``* +`` To the position before latest jump (exclusive). + + *'"* +'" To the first non-blank character of the line where + the cursor was the last time the current buffer was + exited (linewise). {not in Vi}. + + *`"* +`" To the cursor position when last exiting the current + buffer (exclusive). {not in Vi}. + +A "jump" is one of the following commands: "'", "`", "G", "/", "?", "n", +"N", "%", "(", ")", "[[", "]]", "{", "}", ":s", ":tag", "L", "M", "H" and +the commands that start editing a new file. If you make the cursor "jump" +with one of these commands, the position of the cursor before the jump is +remembered. You can return to that position with the "''" and "``" command, +unless the line containing that position was changed or deleted. + + *CTRL-O* +CTRL-O Go to [count] Older cursor position in jump list + (not a motion command). {not in Vi} + + or *CTRL-I* ** +CTRL-I Go to [count] newer cursor position in jump list + (not a motion command). {not in Vi} + + *:ju* *:jumps* +:ju[mps] Print the jump list (not a motion command). {not in + Vi} + + *jumplist* +Jumps are remembered in a jump list. With the CTRL-O and CTRL-I command you +can go to cursor positions before older jumps, and back again. Thus you can +move up and down the list. + +For example, after three jump commands you have this jump list: + + jump line file + 1 1 -current- + 2 70 -current- + 3 1154 -current- +> + +You are currently in line 1167. If you then use the CTRL-O command, the +cursor is put in line 1154. This results in: + + jump line file + 1 1 -current- + 2 70 -current- +> 3 1154 -current- + 4 1167 -current- + +The pointer will be set at the last used jump position. The next CTRL-O +command will use the entry above it, the next CTRL-I command will use the +entry below it. If the pointer is below the last entry, this indicates that +you did not use a CTRL-I or CTRL-O before. In this case the CTRL-O command +will cause the cursor position to be added to the jump list, so you can get +back to the position before the CTRL-O. In this case this is line 1167. + +With more CTRL-O commands you will go to lines 70 and 1. If you use CTRL-I +you can go back to 1154 and 1167 again. + +If you use a jump command, the current line number is inserted at the end of +the jump list. If the same line was already in the jump list, it is removed. +The result is that when repeating CTRL-O you will get back to old positions +only once. + +After the CTRL-O command that got you into line 1154 you could give another +jump command (e.g., "G"). The jump list would then become: + + jump line file + 1 1 -current- + 2 70 -current- + 3 1167 -current- + 4 1154 -current- +> + +The line numbers will be adjusted for deleted and inserted lines. This fails +if you stop editing a file without writing, like with ":n!". + + *%* +% Find the next item in this line after or under the + cursor and jump to its match (inclusive). Items can + be: + ([{}]) parenthesis or (curly/square) brackets + /* */ start or end of C-style comment + #if, #ifdef, #else, #elif, #endif + C preprocessor conditionals + Parens and braces preceded with a backslash are + ignored. When the '%' character is not present in + 'cpoptions', parens and braces inside quotes are + ignored, unless the number of parens/braces in a line + is uneven and this line and the previous one does not + end in a backslash. No count is allowed ({count}% + jumps to a line {count} percentage down the file). + Using '%' on #if/#else/#endif makes the movement + linewise. + + *[(* +[( go to [count] previous unmatched '('. {not in Vi} + + *[{* +[{ go to [count] previous unmatched '{'. {not in Vi} + + *])* +]) go to [count] next unmatched ')'. {not in Vi} + + *]}* +]} go to [count] next unmatched '}'. {not in Vi} + +The above four commands can be used to go to the start or end of the current +code block. It is like doing "%" on the '(', ')', '{' or '}' at the other +end of the code block, but you can do this from anywhere in the code block. +Very useful for C programs. Example: When standing on "case x:", "[{" will +bring you back to the switch statement. + + *[#* +[# go to [count] previous unmatched "#if" or "#else". + {not in Vi} + + *]#* +]# go to [count] next unmatched "#else" or "#endif". {not + in Vi} + +These two commands work in C programs that contain #if/#else/#endif +constructs. It brings you to the start or end of the #if/#else/#endif where +the current line is included. You can then use "%" to go to the matching line. + + *[star* *[/* +[* or [/ go to [count] previous start of a C comment "/*". {not + in Vi} + + *]star* *]/* +]* or ]/ go to [count] next end of a C comment "*/". {not + in Vi} + + + *H* +H To line [count] from top (Home) of screen (default: + first line on the screen) on the first non-blank + character (linewise). See also 'startofline' option. + Cursor is adjusted for 'scrolloff' option. + + *M* +M To Middle line of screen, on the first non-blank + character (linewise). See also 'startofline' option. + + *L* +L To line [count] from bottom of screen (default: Last + line on the screen) on the first non-blank character + (linewise). See also 'startofline' option. + Cursor is adjusted for 'scrolloff' option. + + Moves to the position on the screen where the mouse + click is (inclusive). See also ||. If the + position is in a status line, that window is made the + active window and the cursor is not moved. {not in Vi} + + +7. Scrolling *scrolling* +============ + +Move edit window (the part of the buffer that you see) downwards (this means +that more lines downwards in the text buffer are seen): + + *CTRL-E* +CTRL-E Scroll window [count] lines downwards in the buffer. + Mnemonic: Extra lines. + + *CTRL-D* +CTRL-D Scroll window Downwards in the buffer. The number of + lines comes from the 'scroll' option (default: half a + screen). If [count] given, first set 'scroll' option + to [count]. The cursor is moved the same number of + lines down in the file (if possible; when lines wrap + and when hitting the end of the file there may be a + difference). When the cursor is on the last line of + the buffer nothing happens and a beep is produced. + See also 'startofline' option. + {difference from vi: Vim scrolls 'scroll' screen + lines, instead of file lines; makes a difference when + lines wrap} + + or ** + or ** *CTRL-F* +CTRL-F Scroll window [count] pages Forwards (downwards) in + the buffer. See also 'startofline' option. + +Move edit window (the part of the buffer that you see) upwards (this means +that more lines upwards in the text buffer are seen): + + *CTRL-Y* +CTRL-Y Scroll window [count] lines upwards in the buffer. + + *CTRL-U* +CTRL-U Scroll window Upwards in the buffer. The number of + lines comes from the 'scroll' option (default: half a + screen). If [count] given, first set the 'scroll' + option to [count]. The cursor is moved the same + number of lines up in the file (if possible; when + lines wrap and when hitting the end of the file there + may be a difference). When the cursor is on the first + line of the buffer nothing happens and a beep is + produced. See also 'startofline' option. + {difference from vi: Vim scrolls 'scroll' screen + lines, instead of file lines; makes a difference when + lines wrap} + + or ** + or ** *CTRL-B* +CTRL-B Scroll window [count] pages Backwards (upwards) in the + buffer. See also 'startofline' option. + +Window repositioning: + + *z* *z* +z Redraw, line [count] at top of window (default + cursor line). Put cursor at first non-blank in the + line. + + *zt* +zt Like "z", but leave the cursor in the same + column. {not in Vi} + + *zN* +z{height} Redraw, make window {height} lines tall. This is + useful to make the number of lines small when screen + updating is very slow. Cannot make the height more + than the physical screen height. + + *z.* +z. Redraw, line [count] at center of window (default + cursor line). Put cursor at first non-blank in the + line. + + *zz* +zz Like "z.", but leave the cursor in the same column. + Careful: If caps-lock is on, this commands becomes + "ZZ": write buffer and exit! {not in Vi} + + *z-* +z- Redraw, line [count] at bottom of window (default + cursor line). Put cursor at first non-blank in the + line. + + *zb* +zb Like "z-", but leave the cursor in the same column. + {not in Vi} + +These commands move the contents of the window. If the cursor position is +moved off of the window, the cursor is moved onto the window (with +'scrolloff' screen lines around it). A page is the number of lines in the +window minus two. The mnemonics for these commands may be a bit confusing. +Remember that the commands refer to moving the window upwards or downwards +in the buffer. When the window moves upwards in the buffer, the text in the +window moves downwards on your screen. + +z or *zl* *z* +zl Scroll the screen [count] characters to the left. + This only works when 'wrap' is off. {not in Vi} + +z or *zh* *z* +zh Scroll the screen [count] characters to the right. + This only works when 'wrap' is off. {not in Vi} + +For these two commands the cursor follows the screen. If the character that +the cursor is on is moved off the screen, the cursor is moved to the closest +character that is on the screen. The value of 'sidescroll' is not used. + + *zs* +zs Scroll the screen horizontally to position the cursor + at the start (left side) of the screen. This only + works when 'wrap' is off. {not in Vi} + + *ze* +ze Scroll the screen horizontally to position the cursor + at the end (right side) of the screen. This only + works when 'wrap' is off. {not in Vi} + +For these two commands the cursor is not moved in the text, only the text +scrolls on the screen. + + +8. Tags and special searches *tags_and_searches* +============================ + +8.1 Tags *tag_commands* + + *:ta* *:tag* +:ta[g][!] {ident} Jump to the definition of {ident}, using the + information in the tags file. Put {ident} in the tag + stack. See below for [!]. + +g *g* + ** *CTRL-]* +CTRL-] ":ta" to the keyword under or after cursor. Put the + keyword in the tag stack. {Vi: identifier after the + cursor} + + *v_CTRL-]* +{Visual}CTRL-] ":ta" to the text that is highlighted. {not in Vi} + +g *g* + ** *CTRL-T* +CTRL-T Jump to [count] older entry in the tag stack + (default 1). {not in Vi} + + *:po* *:pop* +:[count]po[p][!] Jump to [count] older entry in tag stack (default 1). + See below for [!]. {not in Vi} + +:[count]ta[g][!] Jump to [count] newer entry in tag stack (default 1). + See below for [!]. {not in Vi} + + *:tags* +:tags Show the contents of the tag stack. The active + entry is marked with a '>'. {not in Vi} + +A tag is an identifier that appears in the "tags" file. It is a sort of label +that can be jumped to. For example: In C programs each function name can be +used as a tag. + +With the ":tag" command the cursor will be positioned on the tag. With the +CTRL-] command, the keyword on which the cursor is standing is used as the +tag. If the cursor is not on a keyword, the first keyword to the right of the +cursor is used. + + *tag_priority* +When there are multiple matches for a tag, this priority is used: +1. The first matching static tag with a full matching tag for the current + file. +2. The first matching global tag with a full matching tag. +3. The first matching static tag with a full matching tag for another file. +4. The first matching static tag with an ignore-case matching tag for the + current file. +5. The first matching global tag with an ignore-case matching tag. +6. The first matching static tag with an ignore-case matching tag for another + file. + + *static_tag* +A static tag is a tag that is defined for a specific file. In a C program this +could be a static function. + +In Vi jumping to a tag sets the current search pattern. This means that +the "n" command after jumping to a tag does not search for the same pattern +that it did before jumping to the tag. Vim does not do this as we consider it +to be a bug. You can still find the tag search pattern in the search history. +If you really want the old Vi behaviour, set the 't' flag in 'cpoptions'. + +If the tag is in the current file this will always work. Otherwise the +performed actions depend on whether the current file was changed, whether a ! +is added to the command and on the 'autowrite' option: + + tag in file autowrite +current file changed ! option action +----------------------------------------------------------------------------- + yes x x x goto tag + no no x x read other file, goto tag + no yes yes x abandon current file, read other file, goto + tag + no yes no on write current file, read other file, goto + tag + no yes no off fail +----------------------------------------------------------------------------- + +- If the tag is in the current file, the command will always work. +- If the tag is in another file and the current file was not changed, the + other file will be made the current file and read into the buffer. +- If the tag is in another file, the current file was changed and a ! is + added to the command, the changes to the current file are lost, the other + file will be made the current file and read into the buffer. +- If the tag is in another file, the current file was changed and the + 'autowrite' option is on, the current file will be written, the other + file will be made the current file and read into the buffer. +- If the tag is in another file, the current file was changed and the + 'autowrite' option is off, the command will fail. If you want to save + the changes, use the ":w" command and then use ":tag" without an argument. + This works because the tag is put on the stack anyway. If you want to lose + the changes you can use the ":tag!" command. + +The ":tag" command works very well for C programs. If you see a call to a +function and wonder what that function does, position the cursor inside of +the function name and hit CTRL-]. This will bring you to the function +definition. An easy way back is with the CTRL-T command. Also read about the +tag stack below. + +A tags file can be created with the external command 'ctags'. It will +contain a tag for each function. Some versions of 'ctags' will also make a +tag for each "#defined" macro. + +The lines in the tags file should have this format: + + {tag}{separator}{filename}{separator}{command} + +{tag} the identifier +{separator} one or more or space characters +{filename} the file that contains the definition of {tag} +{command} the Ex command that positions the cursor on the tag. + +The identifier normally is the name of a function, but it can be any +identifier. There is one special form for local (static) functions: +{filename}:{identifier}. Some ctags programs make use of this to separate +local (static) functions from global functions. +(Detail: Vim compares only the last part of the filename and ignores any +path before it). + + *tag_search* +The command can be any Ex command, but normally it is a search command like + "/^main(argc, argv)" +If it is a search command, and the search fails, another try is done ignoring +case. If that fails too, a search is done for: + "^main[ \t]*(" +(the tag with '^' prepended and "[ \t]*(" appended). When using function +names, this will find the function name when it is in column 0. This will +help when the arguments to the function have changed since the tags file was +made. If this search also fails another search is done with: + "^[#a-zA-Z_].*main[ \t]*(" +This means: A line starting with '#' or an identifier and containing the tag +followed by white space and a '('. This will find macro names and function +names with a type prepended. {the extra searches are not in Vi}. + +In Vi the ":tag" command sets the last search pattern when the tag is searched +for. In Vim this is not done, the previous search pattern is still remembered, +unless the 't' flag is present in 'cpoptions'. The search pattern is always +put in the search history. + + *emacs_tags* +Emacs style tag files are supported if Vim was compiled with EMACS_TAGS +defined. Check the output of ":version", if it contains "+emacs_tags" then it +was. Sorry, there is no explanation about Emacs tag files here, it is only +supported for backwards compatibility :-). + + *tags_option* +The 'tags' option is a list of file names. Each of these files is searched +for the tag. This can be used to use a different tags file than the default +file "tags". It can also be used to access a common tags file. + +The next file in the list is not used when: +- A matching static tag for the current buffer has been found. +- A matching global tag has been found. +This also depends on the 'ignorecase' option. If it is off, and the tags file +only has a match without matching case, the next tags file is searched for a +match with matching case. If no tag with matching case is found, the first +match without matching case is used. If 'ignorecase' is on, and a matching +global tag with or without matching case is found, this one is used, no +further tags files are searched. + +When a tag file name starts with "./", the '.' is replaced with the path of +the current file. This makes it possible to use a tags file in the directory +where the current file is (no matter what the current directory is). The idea +of using "./" is that you can define which tag file is searched first: In the +current directory ("tags,./tags") or in the directory of the current file +("./tags,tags"). + +For example: + + :set tags=./tags,tags,/home/user/commontags + +In this example the tag will first be searched for in the file "tags" in the +directory where the current file is. Next the "tags" file in the current +directory. If it is not found there, then the file "/home/user/commontags" +will be searched for the tag. + +Instead of the comma a space may be used. Then a backslash is required for +the space to be included in the string option: + + :set tags=tags\ /home/user/commontags + +To include a space in a file name use three backslashes. To include a comma in +a file name use two backslashes. For example, use: + + :set tags=tag\\\ file,/home/user/common\\,tags + +for the files "tag file" and "/home/user/common,tags". The 'tags' option will +have the value "tag\ file,/home/user/common\,tags". + +If the 'tagrelative' option is on (which is the default) and using a tag file +in another directory, file names in that tag file are relative to the +directory where the tag file is. + + + *tagstack* +The tags that you use are remembered in the tag stack. You can print this +stack with the ":tags" command. The result looks like this: + + # TO tag FROM line in file + 1 main 1 harddisk2:text/vim/test +> 2 FuncA 58 -current- + 3 FuncC 357 harddisk2:text/vim/src/amiga.c + +This list shows the tags that you jumped to and the cursor position before that +jump. The older tags are at the top, the newer at the bottom. + +The '>' points to the active entry. This is the tag that will be used by the +next ":tag" command. The CTRL-T and ":pop" command will use the position +above the active entry. + +The line number and file name are remembered to be able to get back to where +you were before the tag command. The line number will be correct, also when +deleting/inserting lines, unless this was done by another program (e.g. +another instance of Vim). + +You can jump to previously used tags with several commands. Some examples: + + ":pop" or CTRL-T to position before previous tag + {count}CTRL-T to position before {count} older tag + ":tag" to newer tag + ":0tag" to last used tag + +The most obvious way to use this is while browsing through the call graph of +a program. Consider the following call graph: + + main ---> FuncA ---> FuncC + ---> FuncB + +(Explanation: main calls FuncA and FuncB; FuncA calls FuncC). +You can get from main to FuncA by using CTRL-] on the call to FuncA. Then +you can CTRL-] to get to FuncC. If you now want to go back to main you can +use CTRL-T twice. Then you can CTRL-] to FuncB. + +If you issue a ":ta {ident}" or CTRL-] command, this tag is inserted at the +current position in the stack. If the stack was full (it can hold up to 20 +entries), the oldest entry is deleted and the older entries shift one +position up (their index number is decremented by one). If the last used +entry was not at the bottom, the entries below the last used one are +deleted. This means that an old branch in the call graph is lost. After the +commands explained above the tag stack will look like this: + + # TO tag FROM line in file + 1 main 1 harddisk2:text/vim/test + 2 FuncB 59 harddisk2:text/vim/src/main.c +> + + +8.2 Include file searches *include_search* + +These commands look for a string in the current file and in all encountered +included files (recursively). This can be used to find the definition of a +variable, function or macro. If you only want to search in the current +buffer, use the commands listed at |pattern_searches|. + +When a line is encountered that includes another file, that file is searched +before continuing in the current buffer. Files included by included files are +also searched. When an include file could not be found it is silently +ignored. Use the ":checkpath" command to discover which files could not be +found, possibly your 'path' option is not set up correctly. Note: the +included file is searched, not a buffer that may be editing that file. Only +for the current file the lines in the buffer are used. + +The string can be any keyword or a defined macro. For the keyword any match +will be found. For defined macros only lines that match with the 'define' +option will be found. The default is "\^#[ \t]*define", which is for C +programs. Also, when a match is found for a defined macro, the displaying of +lines continues with the next line when a line ends in a backslash. + +The commands that start with "[" start searching from the start of the current +file. The commands that start with "]" start at the current cursor position. + +The 'include' option is used to define a line that includes another file. The +default is "\^#[ \t]*include", which is for C programs. Note: Vim does not +recognize C syntax, if the 'include' option matches a line inside +"#ifdef/#endif" or inside a comment, it is searched anyway. The 'isfname' +option is used to recognize the file name that comes after the matched +pattern. + +The 'path' option is used to find the directory for the include files that +do not have an absolute path. + +The 'comments' option is used for the commands that display a single line or +jump to a line. It defines patterns that may start a comment. Those lines +are ignored for the search, unless [!] is used. One exception: When the line +matches the pattern "^# *define" it is not considered to be a comment. + + *[i* +[i Display the first line that contains the keyword + under the cursor. The search starts at the beginning + of the file. Lines that look like a comment are + ignored (see 'comments' option). If a count is given, + the count'th matching line is displayed. {not in Vi} + + *]i* +]i like "[i", but start at the current cursor position. + {not in Vi} + + *:is* *:isearch* +:[range]is[earch][!] [count] [/]pattern[/] + Like "[i" and "]i", but search in [range] lines + (default: whole file). Without [!] lines that are + recognized as comments are skipped. Without [/] only + whole words are matched, using the pattern + "\". {not in Vi} + + *[I* +[I Display all lines that contain the keyword under the + cursor. File names and line numbers are displayed + for the found lines. The search starts at the + beginning of the file. {not in Vi} + + *]I* +]I like "[I", but start at the current cursor position. + {not in Vi} + + *:il* *:ilist* +:[range]il[ist][!] [/]pattern[/] + Like "[I" and "]I", but search in [range] lines + (default: whole file). Without [!] lines that are + recognized as comments are skipped. Without [/] only + whole words are matched, using the pattern + "\". {not in Vi} + + *[_CTRL-I* +[ CTRL-I Jump to the first line that contains the keyword + under the cursor. The search starts at the beginning + of the file. Lines that look like a comment are + ignored (see 'comments' option). If a count is given, + the count'th matching line is jumped to. {not in Vi} + + *]_CTRL-I* +] CTRL-I like "[ CTRL-I", but start at the current cursor + position. {not in Vi} + + *:ij* *:ijump* +:[range]ij[ump][!] [count] [/]pattern[/] + Like "[ CTRL-I" and "] CTRL-I", but search in + [range] lines (default: whole file). Without [!] + lines that are recognized as comments are skipped. + Without [/] only whole words are matched, using the + pattern "\". {not in Vi} + +CTRL-W CTRL-I *CTRL-W_CTRL-I* *CTRL-W_i* +CTRL-W i Open a new window, with the cursor on the first line + that contains the keyword under the cursor. The + search starts at the beginning of the file. Lines + that look like a comment line are ignored (see + 'comments' option). If a count is given, the count'th + matching line is jumped to. {not in Vi} + + *:isp* *:isplit* +:[range]isp[lit][!] [count] [/]pattern[/] + Like "CTRL-W i" and "CTRL-W i", but search in + [range] lines (default: whole file). Without [!] + lines that are recognized as comments are skipped. + Without [/] only whole words are matched, using the + pattern "\". {not in Vi} + + *[d* +[d Display the first macro definition that contains the + macro under the cursor. The search starts from the + beginning of the file. If a count is given, the + count'th matching line is displayed. {not in Vi} + + *]d* +]d like "[d", but start at the current cursor position. + {not in Vi} + + *:ds* *:dsearch* +:[range]ds[earch][!] [count] [/]pattern[/] + Like "[d" and "]d", but search in [range] lines + (default: whole file). Without [!] lines that are + recognized as comments are skipped. Without [/] only + whole words are matched, using the pattern + "\". {not in Vi} + + *[D* +[D Display all macro definitions that contain the macro + under the cursor. File names and line numbers are + displayed for the found lines. The search starts + from the beginning of the file. {not in Vi} + + *]D* +]D like "[D", but start at the current cursor position. + {not in Vi} + + *:dl* *:dlist* +:[range]dl[ist][!] [/]pattern[/] + Like "[D" and "]D", but search in [range] lines + (default: whole file). Without [!] lines that are + recognized as comments are skipped. Without [/] only + whole words are matched, using the pattern + "\". {not in Vi} + + *[_CTRL-D* +[ CTRL-D Jump to the first macro definition that contains the + keyword under the cursor. The search starts from + the beginning of the file. If a count is given, the + count'th matching line is jumped to. {not in Vi} + + *]_CTRL-D* +] CTRL-D like "[ CTRL-D", but start at the current cursor + position. {not in Vi} + + *:dj* *:djump* +:[range]dj[ump][!] [count] [/]pattern[/] + Like "[ CTRL-D" and "] CTRL-D", but search in + [range] lines (default: whole file). Without [!] + lines that are recognized as comments are skipped. + Without [/] only whole words are matched, using the + pattern "\". {not in Vi} + +CTRL-W CTRL-D *CTRL-W_CTRL-D* *CTRL-W_d* +CTRL-W d Open a new window, with the cursor on the first + macro definition line that contains the keyword + under the cursor. The search starts from the + beginning of the file. If a count is given, the + count'th matching line is jumped to. {not in Vi} + + *:dsp* *:dsplit* +:[range]dsp[lit][!] [count] [/]pattern[/] + Like "CTRL-W d", but search in [range] lines + (default: whole file). Without [!] lines that are + recognized as comments are skipped. Without [/] only + whole words are matched, using the pattern + "\". {not in Vi} + + *:che* *:checkpath* +:che[ckpath] List all the included files that could not be found. + {not in Vi} + +:che[ckpath]! List all the included files. {not in Vi} + + +9. Inserting text *inserting* +================= + +The following commands can be used to insert new text into the buffer. They +can all be undone. The non-Ex commands can be repeated with the "." command. + + *a* +a Append text after the cursor [count] times. + + *A* +A Append text at the end of the line [count] times. + + or *i* *insert* ** +i Insert text before the cursor [count] times. + + *I* +I Insert text before the first CHAR on the line + [count] times. + + *gI* +gI Insert text in column 1 [count] times. {not in Vi} + + *o* +o Begin a new line below the cursor and insert text, + repeat [count] times. {Vi: blank [count] screen + lines} + + *O* +O Begin a new line above the cursor and insert text, + repeat [count] times. {Vi: blank [count] screen + lines} + +These commands are used to start inserting text. They can be undone and +repeated. You can end Insert mode with . See the section "Insert and +Replace mode" |mode_ins_repl| for the other special characters in Insert +mode. The effect of [count] takes place after Insert mode is exited. + +When 'autoindent' is on, the indent for a new line is obtained from the +previous line. When 'smartindent' or 'cindent' is on, the indent for a line +is automatically adjusted for C programs. + +'textwidth' can be set to the maximum width for a line. When a line becomes +too long when appending characters a line break is automatically inserted. + + *:r* *:read* +:r[ead] [name] Insert the file [name] (default: current file) below + the cursor. + +:{range}r[ead] [name] Insert the file [name] (default: current file) below + the specified line. + + *:r!* *:read!* +:r[ead] !{cmd} Execute {cmd} and insert its standard output below + the cursor. A temporary file is used to store the + output of the command which is then read into the + buffer. 'shellredir' is used to save the output of + the command, which can be set to include stderr or + not. {cmd} is executed like with ":!{cmd}", any '!' + is replaced with the previous command |:!|. + +These commands insert the contents of a file, or the output of a command, +into the buffer. They can be undone. They cannot be repeated with the "." +command. They work on a line basis, insertion starts below the line in which +the cursor is, or below the specified line. To insert text above the first +line use the command ":0r {name}". + + *textmode_read* +The character is recognized as end-of-line marker. If the 'textmode' +option is on, a in front of an is ignored and a CTRL-Z at the end +of the file is ignored. The 'textmode' option is default on for MS-DOS, Win32 +and OS/2. + +If the 'textauto' option is on Vim tries to recognize the type of end-of-line +marker (see |textmode_io|). However, the 'textmode' option will not be +changed. Only while reading the file the text mode is used or not. + +On non-MS-DOS, Win32, and OS/2 systems the message "[textmode]" is shown if a +file is read in text mode, to remind you that something unusual is done. On +MS-DOS, Win32, and OS/2 the message "[notextmode]" is shown if a file is read +without text mode. + +An example on how to use ":r !": + :r !uuencode binfile binfile +This command reads "binfile", uuencodes it and reads it into the current +buffer. Useful when you are editing e-mail and want to include a binary +file. + + +10. Deleting text *deleting* +================= + +["x] or ** *x* *dl* +["x]x Delete [count] characters under and after the cursor + [into register x] (not linewise). Does the same as + "dl". See |:fixdel| if the key does not do what + you want. Also see |'whichwrap'|. { not in Vi} + + *X* *dh* +["x]X Delete [count] characters before the cursor [into + register x] (not linewise). Does the same as "dh". + Also see |'whichwrap'|. + + *d* +["x]d{motion} Delete text that is moved over [into register x]. + See below for exception. + + *dd* +["x]dd Delete [count] lines [into register x] (linewise). + + *D* +["x]D Delete the characters under the cursor until the end + of the line and [count]-1 more lines [into register + x]; synonym for d$ (not linewise). + +{Visual}["x]x or *v_x* *v_d* +{Visual}["x]d Delete the highlighted text [into register x] (see + the chapter on Visual mode |Visual_mode|). {not in + Vi} + +{Visual}["x]X or *v_X* *v_D* +{Visual}["x]D Delete the highlighted lines [into register x] (see + the chapter on Visual mode |Visual_mode|). {not in + Vi} + + *:d* *:delete* +:[range]d[elete] [x] Delete [range] lines (default: current line) [into + register x]. + +:[range]d[elete] [x] {count} + Delete {count} lines, starting with [range] + (default: current line |cmdline_ranges|) [into + register x]. + +These commands delete text. They can be repeated with the "." command +(except ":d") and undone. Use Visual mode to delete blocks of text. See +|registers| for an explanation of registers. + +An exception for the d{motion} command: If the motion is not linewise, the +start and end of the motion are not in the same line and before the start +and after the end are only blanks, the delete becomes linewise. This means +that the blank line that would remain is also deleted. + + + *J* +J Join [count] lines, with a minimum of two lines. + + *v_J* +{Visual}J Join the highlighted lines, with a minimum of two + lines. {not in Vi} + + *:j* *:join* +:[range]j[oin][!] Join [range] lines. Same as "J", except when [!] is + given, then no spaces will be inserted or deleted. + When [range] is given and the start and end of the + range are equal, nothing happens. Default is to join + two lines. + +:[range]j[oin][!] {count} + Join {count} lines, starting with [range] (default: + current line |cmdline_ranges|). Same as "J", except + when [!] is given, then no spaces will be inserted + or deleted. + +These commands delete the newline between lines. This has the effect of +joining them into one line. They can be repeated (except ":j") and undone. + +One space is inserted in place of the , unless the line ended with a +space, or the next line started with a ')'. If the next line has +leading white space it is deleted first. If the 'joinspaces' option is on, +two spaces are inserted after a period. + + +11. Changing text *changing* +================= + +The following commands can be used to change text, that is delete some text +and insert something else, with one command. They can all be undone. The +non-Ex commands can be repeated with the "." command. + + +11.1 Delete and insert *delete_insert* + + *R* +R Enter Replace mode: Each character you type replaces + an existing character, starting with the character + under the cursor. Repeat the entered text [count]-1 + times. + + *c* +["x]c{motion} Delete {motion} text [into register x] and start + insert. + + *cc* +["x]cc Delete [count] lines [into register x] and start + insert (linewise). If 'autoindent' is on, preserve + the indent of the first line. + + *C* +["x]C Delete from the cursor position to the end of the + line and [count]-1 more lines [into register x], and + start insert. Synonym for c$ (not linewise). + + *s* +["x]s Delete [count] characters [into register x] and start + insert (s stands for Substitute). Synonym for "cl" + (not linewise). + + *S* +["x]S Delete [count] lines [into register x] and start + insert. Synonym for "cc" (not linewise). + +{Visual}["x]c or *v_c* *v_r* +{Visual}["x]r Delete the highlighted text [into register x] and + start insert (see the chapter on Visual mode + |Visual_mode|). {not in Vi} + +{Visual}["x]C or *v_C* *v_R* +{Visual}["x]R Delete the highlighted lines [into register x] and + start insert (see the chapter on Visual mode + |Visual_mode|). {not in Vi} + +Notes: +- You can end Insert and Replace mode with . +- See the section "Insert and Replace mode" |mode_ins_repl| for the other + special characters in these modes. +- The effect of [count] takes place after Insert or Replace mode is exited. +- When the 'cpoptions' option contains '$', and the change is within one line, + the text is not directly deleted, but a '$' is put at the last deleted + character. + +See |registers| for an explanation of registers. + +Replace mode is just like Insert mode, except that for every character you +enter, one character is deleted. If the end of a line is reached, further +characters are appended (just like Insert mode). In Replace mode the +backspace key restores the original text (if there was any) (see section +"Insert and Replace mode" |mode_ins_repl|). + + *cw* *cW* +Special case: "cw" and "cW" are treated like "ce" and "cE" if the cursor is +on a non-blank. This is because "cw" is interpreted as change-word, and a +word does not include the following white space. {Vi: "cw" when on a blank +followed by other blanks changes only the first blank; this is probably a +bug, because "dw" deletes all the blanks} + + +11.2 Simple changes *simple_change* + + *r* +r{char} Replace the character under the cursor with {char}. If + {char} is a or the character will be + replaced with a line break. Replacing with a real + can be done by using CTRL-V . CTRL-V will + replace with a . {Vi: CTRL-V still replaces + with a line break, cannot replace something with a + } + If a [count] is given that many characters will be + replaced with [count] {char}s. When {char} is a + or only one is inserted. "5r" replaces five + characters with a single line break; + When replacing with a or autoindenting is + done. This works just like deleting the characters + that are replaced and then doing "i". + + *~* +~ 'notildeop' option: switch case of the character + under the cursor and move the cursor to the right. + If a [count] is given do that many characters {Vi: + no count} + +~{motion} 'tildeop' option: switch case of {motion} text. {Vi: + tilde cannot be used as an operator} + + *g~* +g~{motion} switch case of {motion} text. {Not in Vi} + + *v_~* +{Visual}~ switch case of highlighted text (see the chapter on + Visual mode |Visual_mode|). {not in Vi} + + *v_U* +{Visual}U Make highlighted text uppercase (see the chapter on + Visual mode |Visual_mode|). {not in Vi} + + *gU* +gU{motion} Make {motion} text uppercase. {not in Vi} + + *v_u* +{Visual}u Make highlighted text lowercase (see the chapter on + Visual mode |Visual_mode|). {not in Vi} + + *gu* +gu{motion} Make {motion} text lowercase. {not in Vi} + + *CTRL-A* +CTRL-A Add [count] to the number at or after the cursor. + {not in Vi} + + *CTRL-X* +CTRL-X Subtract [count] from the number at or after the + cursor. {not in Vi} + +The CTRL-A and CTRL-X commands work for (signed) decimal numbers and +unsigned octal and hexadecimal numbers. Numbers starting with '0x' or '0X' +are assumed to be hexadecimal. To decide whether the hexadecimal number +should be printed uppercase or not, the case of the rightmost letter in the +number is considered. If there is no letter in the current number, the +previously detected case is used. Numbers starting with a '0' are considered +to be octal. Other numbers are decimal and may be preceded with a minus +sign. If the cursor is on a number, that one will be used. Otherwise the +number right of the cursor will be used. + +For octal and hexadecimal numbers with leading zeros, the number of +characters in the number remains equal (when possible). When doing CTRL-A on +"0077" it becomes "0100", CTRL-X on "0x0100" becomes "0x00ff". Note that +when there are no leading zeros this does not work, so CTRL-X on "0x100" +results in "0xff". Note that decimal numbers with leading zeros are +impossible, because they are recognized as octal numbers. + +The CTRL-A command is very useful in a macro. Example: How to make a +numbered list. + +1. Create the first entry. The entry should start with a number. +2. qa - start recording into buffer 'a' +3. Y - yank the entry +4. p - put a copy of the entry below the first one +5. CTRL-A - increment the number +6. q - stop recording +7. @a - repeat the yank, put and increment times + + *<* +<{motion} Shift the {motion} lines one 'shiftwidth' leftwards. + + *<<* +<< Shift [count] lines one 'shiftwidth' leftwards. + + *v_<* +{Visual}[count]< Shift the highlighted lines [count] 'shiftwidth' + leftwards (see the chapter on Visual mode + |Visual_mode|). {not in Vi} + + *>* +>{motion} Shift {motion} lines one 'shiftwidth' rightwards. + + *>>* +>> Shift [count] lines one 'shiftwidth' rightwards. + + *v_>* +{Visual}[count]> Shift the highlighted lines [count] 'shiftwidth' + rightwards (see the chapter on Visual mode + |Visual_mode|). {not in Vi} + + *:<* +:[range]< Shift [range] lines one 'shiftwidth' left. Repeat '<' + for shifting multiple 'shiftwidth's. + +:[range]< {count} Shift {count} lines one 'shiftwidth' left, starting + with [range] (default current line |cmdline_ranges|). + Repeat '<' for shifting multiple 'shiftwidth's. + +:[range]le[ft] [indent] left align lines in [range]. Sets the indent in the + lines to [indent] (default 0). {not in Vi} + + *:>* +:[range]> Shift {count} [range] lines one 'shiftwidth' right. + Repeat '>' for shifting multiple 'shiftwidth's. + +:[range]> {count} Shift {count} lines one 'shiftwidth' right, starting + with [range] (default current line |cmdline_ranges|). + Repeat '>' for shifting multiple 'shiftwidth's. + +The ">" and "<" commands are handy for changing the indent within programs. +The size of the white space which is inserted or deleted can be set with the +'shiftwidth' option. Normally the 'shiftwidth' option is 8, but you can set it +to, say, 3 to make smaller indents. The shift leftwards stops when there is no +indent. The shift right does not do anything with empty lines. + +If the 'shiftround' option is on, the indent is rounded to a multiple of +'shiftwidth'. + +If the 'smartindent' option is on, or 'cindent' is on and 'cinkeys' contains +'#', lines starting with '#' will not be shifted right (they are supposed to +be C preprocessor lines that must stay in column 1). + +When the 'expandtab' option is off (this is the default) s are used as +much as possible to make the indent. You can use ">><<" to replace an indent +made out of spaces with the same indent made out of s (and a few +spaces if necessary). If the 'expandtab' option is on, only spaces are +used. Then you can use ">><<" to replace s in the indent by spaces (or +use ":retab!"). + +To move a line several 'shiftwidth's use the Visual mode or the ":" +commands. For example: + Vjj4> move three lines 4 indents to the right + :<<< move current line 3 indents to the left + :>> 5 move 5 lines 2 indents to the right + :5>> move line 5 2 indents to the right + + +11.3 Complex changes *complex_change* + + *!* +!{motion}{filter} Filter {motion} text through the external program + {filter}. + + *!!* +!!{filter} Filter [count] lines through the external program + {filter}. + + *v_!* +{Visual}!{filter} Filter the highlighted lines through the external + program {filter} (see the chapter on Visual mode + |Visual_mode|). {not in Vi} + +:{range}![!]{filter} [!][arg] *:range!* + Filter {range} lines through the external program + {filter}. The optional bangs are replaced with the + latest given command. The optional [arg] is + appended. The output of the filter command is + temporaryly saved in a file and then read into the + buffer. The 'shellredir' option is used to write the + output of the filter in the temporary file. + + *=* +={motion} Filter {motion} lines through the external program + given with the 'equalprg' option. When the 'equalprg' + option is empty (this is the default), use the + internal formatting function to set the indent of each + line |C_indenting|. + + *==* +== Filter [count] lines through the external program + given with the 'equalprg' option. When the 'equalprg' + option is empty (this is the default), use the + internal formatting function |C_indenting|. + + *v_=* +{Visual}= Filter the highlighted lines through the external + program given with the 'equalprg' option. When the + 'equalprg' option is empty (this is the default), + use the internal formatting function |C_indenting|. + (see the chapter on Visual mode |Visual_mode|). {not + in Vi} + +A filter is a program that accepts text at standard input, changes it in some +way, and sends it to standard output. The commands above can be used to send +some text through a filter. An example of a filter is "sort", which sorts +lines alphabetically. The "indent" program is used to pretty indent C programs +(you need a version of indent that works like a filter, not all versions do +that). The shell, given with the 'shell' option, is used to execute the +command (See also the 'shelltype' option). The filter commands can be redone +with ".". There cannot be a comment (with '"') after the ":!" command. + + + *:s* *:substitute* +:[range]s[ubstitute]/{pattern}/{string}/[g][c][r][p] [count] + For each line in [range] replace {pattern} with + {string}. See below for the flags. + +:[range]s[ubstitute] [g][c][r] [count] +:[range]&[g][c][r] [count] *:&* + Repeat last :substitute with same search pattern and + substitute string. The flags may be different (see + below). + +:[range]~[g][c][r] [count] *:~* + Repeat last substitute with same substitute string + but with last used search pattern. This is like + "&r". See explanation for [r] below. + + *&* +& Synonym for ":s//~/" (repeat last substitute). + +The arguments that can be given to the substitute commands: +[g] All occurrences in the line are replaced. Otherwise only the first + occurrence in the line is replaced. If the 'edcompatible' option is + on this flag is remembered and toggled each time it is used. It is + reset when a new search pattern is given. If the 'gdefault' option + is on, this flag is default on, give the [g] to switch it off. +[c] Each substitute has to be confirmed. The cursor is positioned on the + matching string. You can type: *:s_c* + 'y' to substitute this match + 'n' to skip this match + to skip this match + 'a' to substitute this and all remaining matches {not in Vi} + 'q' to quit substituting {not in Vi} + CTRL-E to scroll the screen up {not in Vi} + CTRL-Y to scroll the screen down {not in Vi}. + If the 'edcompatible' option is on the [c] flag is remembered and + toggled each time it is used. It is reset when a new search pattern + is given. +[r] When the search pattern is empty use the previously used search + pattern instead of the search pattern from the last substitute or + ":global". If the last command that did a search was a substitute or + ":global" there is no effect. If the last command was a search + command, like "/", the pattern from that command is used. +[p] Print the line containing the last substitute. +[count] That many lines are are searched, starting with the last line number + in [range] (default current line |cmdline_ranges|). + +If the {pattern} for the substitute command is empty, the pattern from the +last substitute or ":global" command is used. With the [r] flag the pattern +from the last substitute, ":global" or search command is used. + +For compatibility with Vi these two execptions are allowed: +"\/{string}/" and "\?{string}?" do the same as "//{string}/r". +"\&{string}&" does the same as "//{string}/". + +Instead of the '/' which surrounds the pattern and replacement string, you +can use any other character, but not an alphanumeric character, '"' or '|' +or '#'. This is useful if you want to include a '/' in the search pattern or +replacement string. Example: ":s+/+//+" + +For the definition of a pattern see 6.6, "Pattern searches" |search_pattern|. + +Some characters in {string} have a special meaning: + +magic nomagic action + & \& replaced with the whole matched pattern + \& & replaced with & + \0 replaced with the whole matched pattern + \1 replaced with the matched pattern in the first pair of () + \2 replaced with the matched pattern in the second pair of () + .. .. + \9 replaced with the matched pattern in the ninth pair of () + ~ \~ replaced with the {string} of the previous substitute + \~ ~ replaced with ~ + \u next character made uppercase + \U following characters made uppercase + \l next character made lowercase + \L following characters made lowercase + \e end of /u, /U, /l and /L + \E end of /u, /U, /l and /L + split line in two at this point + \r idem + \n + \b + \t + CTRL-V insert a carriage-return (CTRL-M) + +Examples: +:s/a\|b/xxx\0xxx/g modifies "a b" in "xxxaxxx xxxbxxx" +:s/\([abc]\)\([efg]\)/\2\1/g modifies "af fa bg" in "fa fa gb" +:s/abcde/abc^Mde/ modifies "abcde" in "abc", "de" (two lines) +:s/$/^V^M/ modifies "abcde" in "abcde^M" + +Note: To insert a ^M you have to type CTRL-V . To insert a ^V you have +to type CTRL-V CTRL-V. So to insert the ^V^M in the last example you have to +type CTRL-V CTRL-V CTRL-V . + +Because CTRL-V inserts a , it is impossible to insert a CTRL-V just +in front of a line break. You will have to split it up in two parts: + :s/foo/^Vxxxx/ + :s/xxxx/^M/ + +When using parentheses in combination with '|', like in \([ab]\)\|\([cd]\), +either the first or second pattern in parentheses did not match, so either +\1 or \2 is empty. Example: +:s/\([ab]\)\|\([cd]\)/\1x/g modifies "a b c d" in "ax bx x x" + + *:ret* *:retab* +:[range]ret[ab][!] [new_tabstop] + All sequences of white-space containing a tab are + replaced with new strings of white-space using the new + tabstop value given. If no new tabstop size is + given, the current value of 'tabstop' is used. With + !, strings of normal spaces will also be replace with + tabs where appropriate. With 'expandtab' on, all + tabs will be replaced with the appropriate number of + spaces. This command sets 'tabstop' to the new + value given, and if performed on the whole file, + which is default, should not make any visible + change. Careful: In a C program a inside a + string will also be affected. Use "\t" to avoid this + (that's a good habit anyway). {not in Vi} + + +11.4 Formatting text *formatting* + +:[range]ce[nter] [width] *:ce* *:center* + Center lines in [range] between [width] columns + (default 'textwidth' or 80 when 'textwidth' is 0). + {not in Vi} + +:[range]ri[ght] [width] *:ri* *:right* + right align lines in [range] at [width] columns + (default 'textwidth' or 80 when 'textwidth' is 0). + {not in Vi} + + *:le* *:left* +:[range]le[ft] [indent] left align lines in [range]. Sets the indent in the + lines to [indent] (default 0). {not in Vi} + +gq{motion} *Q* *gq* +Q{motion} Format the lines that were moved over. The length of + each line will be restricted to the width given with + the 'textwidth' option. See below. If the + 'textwidth' option is 0, the width of the screen is + used (with a maximum of 79). {not in Vi} + NOTE: The "Q" command is used in Vi to go to Ex mode. + In a future version of Vim this will be made + compatible. Use "gq" for formatting now, to avoid + problems when upgrading to a newer version of Vim. + +{Visual}gq *v_Q* *v_gq* +{Visual}Q Format the highlighted text. (see the chapter on + Visual mode |Visual_mode|). {not in Vi} + +Example: To format the current paragraph use "gqp". + +After the "gq" command the cursor is left in the line where the motion command +would take the cursor. This allows for the formatting to be repeated with +".". This works fine with "gqj" (format current and next line) and "gq}" +(format until end of paragraph). Note: When 'formatprg' is set, the cursor is +left on the first formatted line (like when using a filter command). + +If the 'autoindent' option is on, the indent of the first line is used for +the following lines. + +Empty lines are left unchanged (but lines with spaces or tabs are!). + +The 'formatprg' option can be set to the name of an external program, which +will be used instead of the internal function. The 'textwidth' and other +options will not be used then. + + *format_comments* +Comments can be formatted in a special way. A comment is recognized by a +specific string at the start of the line (ignoring white space). Three types +of comments can be used: + +- Repeating the comment string at the start of each line. An example is the + type of comment used in shell scripts, starting with "#". +- Only the first line has the comment string, following lines don't. An + example is this list with dashes. +- Three-piece comments, that have a start string, an end string and optional + lines in between. The strings for the start, middle and end are different. + An example is the C-style comment: + /* + * this is a C comment + */ + +The 'comments' option can be set to a comma separated list of parts. Each +part defines a type of comment that is recognized. A part consists of: + {flags}:{string} + +{string} is the literal text that must appear. + +{flags}: + n Nested comment. Nesting with mixed parts is allowed. If 'comments' + is "n:),n:>" a line starting with "> ) >" is accepted as comment. + + b Blank (, or end-of-line) required after {string}. + + f First line has comment only, will not be repeated on next line, but + indent is kept (for bullet-lists). + s Start of three-piece comment + m Middle of a three-piece comment + e End of a three-piece comment + + l Left adjust middle with start or end (default). Only recognized when + used together with 's' or 'e'. + r Right adjust middle with start or end. Only recognized when used + together with 's' or 'e'. + +When neither 'f', 's', 'm' or 'e' is given, a repeated comment string is +assumed. It is possible to have an empty flags field. + +Blank space in the text before and after the {string} is also included. The +{string} in the 'comments' option should not include leading or trailing +blanks (although it is allowed, in which case they are required). + +When one comment leader is part of another, put it after that one. For +example, to include both "-" and "->", use + :set comments=f:->,f:- + +A three-piece comment must always be given as start-middle-end, with no other +parts in between. An example of a three-piece comment is "sr:/*,mb:*,el:*/" for +C-comments. To avoid recognizing "*ptr" the 'b' flag is included for the +middle. For three-piece comments the text after the start and middle is +checked for the appearance of the end. If it is, the comment will not +continue below. The middle part must be present, because otherwise Vim can't +recognize the middle lines. + +Examples: +"b:*" Includes lines starting with "*", but not if the "*" is followed by a + non-blank. This avoids a pointer dereference like "*str" to be + recognized as a comment. +"n:>" Includes a line starting with ">", ">>", ">>>", etc. +"fb:-" Format a list that starts with "- ". + +By default, "b:#" is included. This means that a line that starts with +"#include" is not recognized as a comment line. But a line that starts with +"# define" is recognized. In C code this is good, because somewhere after this +a "#endif" is needed. + + *fo_table* +The 'formatoptions' option can be set to influence the way how comments are +formatted. It is a string option, that may contain any of these letters. The +default is "tcq". Commas can be added for readability. + +letter meaning when present in 'formatoptions' + +t Do text autowrapping using textwidth +c Do comment autowrapping using textwidth, inserting the current + comment leader automatically. +r Automatically insert the current comment leader after hitting + in insert mode. +o Automatically insert the current comment leader after hitting 'o' or + 'O' in Normal mode. +q Allow formatting of comments with "gq" (or "Q", which is obsolete). + Note that blank lines, or lines containing only the comment leader + will be left untouched. A new paragraph starts after such a line, or + when the comment leader changes. +2 When formatting text the indent of the second line of a paragraph is + used for the rest of the paragraph. This allows for paragraphs with + a different indent for the first line. +v Vi-compatible auto wrapping in insert mode: Only break a line at a + blank that has been entered during the current insert command. (Note: + this is not 100% Vi compatible, Vi has some "unexpected features" or + bugs in this area. It uses the screen column instead of the line + column) +b Like 'v', but only auto wrap if a blank has been entered at or before + the wrap margin. If the line was longer than 'textwidth' when the + insert started, or no blank was entered in the current insert before + reaching 'textwidth', there is no auto wrapping. +l Long lines are not broken in insert mode: When a line was longer than + 'textwidth' when the insert command started it is not automatically + formatted. + +With 't' and 'c' you can decide when auto-wrapping is done: +value action +"" no automatic formatting, "gq" can be used for manual formatting +"t" automatic formatting of text, not for comments +"c" automatic formatting for comments, not for text (good for C code) +"tc" automatic formatting for text and comments + +Note that when 'textwidth' is 0, no formatting is done anyway (but the comment +leader is inserted). +Note that when 'paste' is on, no formatting is done at all. +Note that 'textwidth' can be non-zero even though auto-wrapping never occurs. +This is good because it can be used for formatting only in this case (with +"gq"). + +If "/*", "*" and/or "*/" are in the 'comments' variable, then Vim has some +built in stuff to treat these types of comments a bit more cleverly. +Opening a new line before or after "/*" or "*/" (with 'r' or 'o' present in +'formatoptions') gives the correct start of the line automatically. The same +happens with formatting and auto-wrapping. Opening a line after a line +starting with "/*" or "*" and containing "*/", will cause no comment leader to +be inserted, and the indent of the new line is taken from the line containing +the start of the comment. +E.g.: + /* + * Your typical comment. + */ + The indent on this line is the same as the start of the above + comment. + +All this should be really cool, especially in conjunction with the new +:autocmd command to prepare different settings for different types of file. + +Some examples: + for C code: fo="croq" (only format comments) + for Mail/news: fo="tcrq" (format all, don't start comment with "o" + command) + + +11.5 Indenting C programs *C_indenting* + +C programs can be automatically indented. Only the indent is set, no other +formatting is done. To format comments see |format_comments|. + +There are in fact three methods that can be used. +'autoindent' Just takes the indent from the previous line. +'smartindent' Is like 'autoindent' but also recognizes some C syntax to + increase/reduce the indent where appropriate. +'cindent' Works more clever than the other two and can be configured to + different indenting styles. +The rest of this section is about the 'cindent' option. + +Note that the indenting done with 'cindent' does not work for 100%. Vim is +not a C compiler, not all syntax is recognized. + +Four options are used for C program indenting: +'cindent' When on automatic C program indenting is enabled. +'cinkeys' Keys that trigger reindenting in insert mode. +'cinoptions' For setting your preferred indent style. +'cinwords' Defines keywords that start an extra indent in the next line. + +If 'lisp' is not on and 'equalprg' is empty, the "=" operator indents using +this algorithm rather than calling an external program. + +See |autocommand| for how to automatically set the 'cindent' option for C code +files and reset it for others. + + *'cinkeys'* *'cink'* +The 'cinkeys' option can be set to a string that says when to do indenting. +The default is "0{,0},:,0#,!^F,o,O,e". This means that indenting is done +when: + "0{" typing '{' as the first character in a line + "0}" typing '}' as the first character in a line + ":" typing ':' anywhere + "0#" typing '#' as the first character in a line + "!^F" typing CTRL-F, which is not inserted + "o" typing a anywhere and for the "o" command (not in insert + mode!) + "O" for the "O" command (not in insert mode!) + "e" typing the second 'e' for an "else" at the start of a line + +Characters that can be prepended: +'!' When the key is preceded with a '!' the key will not be inserted but + will just cause the current line to be reindented. This allows you to + set a command key for reindenting the current line. By default CTRL-F + is used for this. Careful with CTRL-I, you might think that it is a + nice command for Indenting, but it is the same as a . +'*' When the key is preceded with a '*' the reindenting will be done + before inserting the key. If you use "*" this means that the + current line will be reindented, before opening a new line. + +When the key is not preceded with a '!' or '*' the reindenting will be done +after inserting the key. So ';' will set the indentation of the line +including the ';'. + +'0' When a zero is used before the key (but after '!' or '*') it will only + trigger reindenting if the key is the first character typed in the + line. + +Special key names: +<> Angle brackets mean spelled-out names of keys. For example: "", + "". +'^' Letters preceded by a caret (^) are control characters. For example: + "^F" is CTRL-F. +'o' Means to reindent a line for the "o" command and whenever a new + line is opened below the current one. This includes hitting + in insert mode. +'O' Means to reindent a line for the "O" command. +'e' Means to reindent a line that starts with "else" when an 'e' is + inserted. + +If you really want to reindent when you type 'o', 'O', 'e', '0', '<', '>', '*' +or '!', use "", "", "", "<0>", "<<>", "<>>", "<*>" or "", +respectively, for those keys. + +For an emacs-style indent mode, where lines aren't indented every time you +press Return but only if you press Tab, I suggest: + :set cinkeys=0{,0},:,0#,!,!^F + +Note: When the indent of the current line was changed manually, cindenting +won't be done for any key. This is to avoid re-indenting after you changed +the indent by typing , or in the indent, or used CTRL-T or +CTRL-D. + +How the indenting is done can be set with 'cinoptions'. In the list below, +"N" represents a number of your choice. It can be negative. When there is an +'s' after the number, it is multiplied with 'shiftwidth'. "1s" is +'shiftwidth', "2s" is two times 'shiftwidth', etc. A decimal point is allowed +too: "-0.5s" is minus half a 'shiftwidth'. The examples given below assume a +'shiftwidth' of 4. + + >N Amount added for "normal" indent. Used after a line that should + increase the indent (lines starting with "if", an opening brace, + etc.). (default 'shiftwidth'). + + cino= cino=>2 cino=>2s + if (cond) if (cond) if (cond) + { { { + foo; foo; foo; + } } } + + eN Add N to the prevailing indent inside a set of braces if the + opening brace at the End of the line (more precise: is not the + first character in a line). This is useful if you want a + different indent when the '{' is at the start of the line from + when '{' is at the end of the line. (default 0). + + cino= cino=e2 cino=e-2 + if (cond) { if (cond) { if (cond) { + foo; foo; foo; + } } } + else else else + { { { + bar; bar; bar; + } } } + + nN Add N to the prevailing indent for a statement after an "if", + "while", etc., if it is Not inside a set of braces. This is + useful if you want a different indent when there is no '{' + before the statement from when there is a '{' before it. + (default 0). + + cino= cino=n2 cino=n-2 + if (cond) if (cond) if (cond) + foo; foo; foo; + else else else + { { { + bar; bar; bar; + } } } + + fN The First opening brace of a function or other block is placed + in column N. Only for an opening brace that is not inside other + braces and is at the start of the line. What comes after the + brace is put relative to this brace. (default 0). + + cino= cino=f.5s cino=f1s + func() func() func() + { { { + int foo; int foo; int foo; + + {N Opening braces are placed N characters from the prevailing + indent. Only for opening braces that are inside + other braces. (default 0). + + cino= cino={.5s cino={1s + if (cond) if (cond) if (cond) + { { { + foo; foo; foo; + + }N Closing braces are placed N characters from the matching opening + brace. (default 0). + + cino= cino={2,}-0.5s cino=}2 + if (cond) if (cond) if (cond) + { { { + foo; foo; foo; + } } } + + ^N Add N to the prevailing indent inside a set of braces if the + opening brace is in column 0. This is used to have a different + indent for whole of a function (some may like to set it to a + negative number). (default 0). + + cino= cino=^-2 cino=^-s + func() func() func() + { { { + if (cond) if (cond) if (cond) + { { { + a = b; a = b; a = b; + } } } + } } } + + :N Case labels are placed N characters from the indent of the + switch(). (default 'shiftwidth'). + + cino= cino=:0 + switch (x) switch(x) + { { + case 1: case 1: + a = b; a = b; + default: default: + } } + + =N Statements after a case label are placed N characters from the + indent of the label. (default 'shiftwidth'). + + cino= cino==10 + case 11: case 11: a = a + 1; + a = a + 1; b = b + 1; + + pN Parameter declarations for K&R-style function declarations will + be indented N characters from the margin. (default + 'shiftwidth'). + + cino= cino=p0 cino=p2s + func(a, b) func(a, b) func(a, b) + int a; int a; int a; + char b; char b; char b; + + tN Type for a function declaration will be indented N characters + from the margin. (default 'shiftwidth'). + + cino= cino=t0 cino=t7 + int int int + func() func() func() + + +N Continuation lines (lines that spill onto two) are indented N + additional characters. (default 'shiftwidth'). + + cino= cino=+10 + a = b + 9 * a = b + 9 * + c; c; + + cN Comment lines after the comment opener, when there is no other + text to line up with, are indented N characters from the comment + opener. (default 3). See also |format_comments|. + + cino= cino=c5 + /* /* + text. text. + */ */ + + (N When in unclosed parentheses, indent N characters from the line + with the unclosed parentheses. When N is 0 or the line starts + with '(' line up with the unclosed parentheses. (default + 'shiftwidth' * 2). + + cino= cino=(0 + if (c1 && (c2 || if (c1 && (c2 || + c3)) c3)) + foo; + if (c1 && if (c1 && + (c2 || c3) (c2 || c3) + } } + + )N Unclosed parentheses will be searched for at most N lines away. + This is just to limit the time needed to search for parentheses. + (default 20 lines). + + *N Unclosed comments will be searched for at most N lines away. + This is just to limit the time needed to search for the start of + a comment. (default 30 lines). + +The defaults, spelled out in full, would be + cinoptions=>s,e0,n0,f0,{0,}0,^0,:s,=s,ps,ts,+s,(2s,)20,*30 + +Lines are put in column 1 if: +- It starts with '#' (preprocessor directives), if 'cinkeys' contains '#'. +- It starts with a label (a keyword followed by ':', other than "case" and + "default"). +- Any combination of indentations causes the line to have less than 0 + indentation. + + +12. Copying and moving text *copy_move* +=========================== + + *quote* +" Use register for next delete, yank + or put (use uppercase character to append with + delete and yank) (<.%:> only work with put). + + *:reg* *:registers* +:reg[isters] Display the contents of all numbered and named + registers. {not in Vi} + +:reg[isters] {arg} Display the contents of the numbered and named + registers that are mentioned in {arg}. For example: + :dis 1a + to display registers '1' and 'a'. Spaces are allowed + in {arg}. {not in Vi} + + *:di* *:display* +:di[splay] [arg] Same as :registers. {not in Vi} + + *y* +["x]y{motion} Yank {motion} text [into register x]. + + *yy* +["x]yy Yank [count] lines [into register x] (linewise). + + *Y* +["x]Y yank [count] lines [into register x] (synonym for + yy, linewise). If you like "Y" to work from the + cursor to the end of line (which is more logical, + but not Vi-compatible) use ":map Y y$". + + *v_y* +{Visual}["x]y Yank the highlighed text [into register x] (see the + chapter on Visual mode |Visual_mode|). {not in Vi} + + *v_Y* +{Visual}["x]Y Yank the highlighted lines [into register x] (see the + chapter on Visual mode |Visual_mode|). {not in Vi} + + *:y* *:yank* +:[range]y[ank] [x] Yank [range] lines [into register x]. + +:[range]y[ank] [x] {count} + Yank {count} lines, starting with last line number + in [range] (default: current line |cmdline_ranges|), + [into register x]. + + *p* +["x]p Put the text [from register x] after the cursor + [count] times. {Vi: no count} + +["x]P or *P* ** +["x] Put the text [from register x] before the cursor + [count] times. Using the mouse only works when + 'mouse' contains 'n' or 'a'. {Vi: no count} + + *:pu* *:put* +:[line]pu[t] [x] Put the text [from register x] after [line] (default + current line). + +:[line]pu[t]! [x] Put the text [from register x] before [line] (default + current line). + +["x]]p or *]p* *]* +["x]] Like "p", but adjust the indent to the current line. + Using the mouse only works when 'mouse' contains 'n' + or 'a'. {not in Vi} + +["x][P or *[P* +["x]]P or *]P* +["x][p or *[p* *[* +["x][ Like "P", but adjust the indent to the current line. + Using the mouse only works when 'mouse' contains 'n' + or 'a'. {not in Vi} + +These commands can be used to copy text from one place to another. This is +done by first getting the text into a register with a yank, delete or change +command. The register can then be inserted with a put command. All registers +are kept when changing files. Thus you can also use this to move text from +one file to another (the CTRL-^ command is a quick way to toggle between two +files). + +The put commands can be repeated with "." (except for :put) and undone. If +the command that was used to get the text into the register was linewise, the +text will be inserted below ("p") or above ("P") the line where the cursor +is. Otherwise the text will be inserted after ("p") or before ("P") the +cursor. With the ":put" command the text will always be inserted in the next +line. You can exchange two characters with the command sequence "xp". You +can exchange two lines with the command sequence "ddp". You can exchange +two words with the command sequence "deep" (start with the cursor in the +blank space before the first word). The "']" or "`]" command can be used +after the put command to move the cursor to the end of the inserted text, +"'[" or "`[" to move the cursor to the start. + +If the command that was used to get the text into the register used +blockwise Visual mode, the block of text will be inserted before ("P") or +after ("p") the cursor column, in the current and next lines. Vim will make +the whole block of text start in the same column. Thus the inserted text +looks the same as when it was yanked or deleted. Some characters may +be replaced with spaces to make this happen. However, if the width of the +block is not a multiple of a width and the text after the inserted +block contains s, that text may be misaligned. + +There are five types of registers: *registers* +- The unnamed register "" *quote_quote* *quotequote* +- 10 numbered registers "0 to "9 *quote_number* *quote0* +- The small delete register "- *quote_-* *quote-* +- 26 named registers "a to "z or "A to "Z *quote_alpha* *quotea* +- three read-only registers ":, ". and "% + + The unnamed register is the register where all text deleted with +the "d", "c", "s", "x" commands or copied with the yank "y" command is +placed, regardless of whether or not a specific register was used (e.g. +"xdd). The contents of this register are used by any put command (p or P) +which does not specify a register. Additionally it can be accessed by the +name '"'. This means you have to type two double quotes. {Vi: register +contents lost when changing files, no '"'} + The numbered registers are filled with yank and delete commands. +Numbered register 0 is filled with the last yank command, unless another +register was specified with ["x]. Numbered register 1 is filled with the text +that was deleted by each delete or change command, unless another register was +specified or the text is less than one line (text deleted with "x" or "dw" +will be put in the small delete register). The contents of register 1 are put +in 2, 2 in 3, and so forth. The contents of register 9 are lost. {Vi: +numbered register contents are lost when changing files; register 0 does not +exist} + The small delete register is filled with delete commands that delete +less than one line, except when a register was specified with ["x]. + The named registers are only filled when you say so. They are named +'a' to 'z' normally. If you use an uppercase letter, the same registers as +with the lower case letter is used, but the text is appended to the previous +register contents. With a lower case letter the previous contents are lost. + The read-only registers are '%', ':' and '.'. They can only be used +with the commands "p", "P", ":put" and with CTRL-R. + *quote_.* *quote.* + ". Contains the last inserted text (the same as what is inserted + with the insert mode commands CTRL-A and CTRL-@). Note: this + doesn't work with CTRL-R on the command line. It works a bit + differently, like inserting the text instead of putting it + ('textwidth' and other options affect what is inserted). + *quote_%* *quote%* + "% Contains the name of the current file. + *quote_:* *quote:* + ": Contains the last command line. It can be used with "@:", + this repeats the last command line. + +If you use a put command without specifying a register, the register that +was last written to is used (this is also the contents of the unnamed +register). If you are confused, use the ":dis" command to find out what will +be put (all named and numbered registers are displayed; the unnamed register +is labelled '"'). + +The next three commands always work on whole lines. + +:[range]co[py] {address} *:co* *:copy* + Copy the lines given by [range] to below the line + given by {address}. + + *:t* +:t Synonym for copy. + +:[range]m[ove] {address} *:m* *:move* + Move the lines given by [range] to below the line + given by {address}. + + +13. Visual mode *Visual_mode* +=============== + +Visual mode is a flexible and easy way to select a piece of text for an +operator. It is the only way to select a block of text. {Vi has no Visual +mode, the name "visual" is used for Normal mode, to distinguish it from Ex +mode} + + *v* +v start Visual mode per character. {not in Vi} + + *V* +V start Visual mode linewise. {not in Vi} + + *CTRL-V* +CTRL-V start Visual mode blockwise. {not in Vi} + + *v_o* +o go to Other end of highlighted text: The current + cursor position becomes the start of the highlighted + text and the cursor is moved to the Other end of the + highlighted text. {not in Vi} + + *gv* *v_gv* +gv Start Visual mode with the same area as the previous + area and the same mode. In Visual mode the current and + the previous Visual area are exchanged. {not in Vi} + + ** + Set the current cursor position. If Visual mode is + active it is stopped. Only when 'mouse' option is + contains 'n' or 'a'. If the position is within 'so' + lines from the last line on the screen the text is + scrolled up. If the position is within 'so' lines from + the first line on the screen the text is scrolled + down. {not in Vi} + + ** + Start Visual mode if it is not active. The text from + the cursor position to the position of the click is + highlighted. If Visual mode was already active move + the start or end of the highlighted text, which ever + is closest, to the position of the click. Only when + 'mouse' option contains 'n' or 'a'. {not in Vi} + + ** + This works like a , if it is not a + the same position as . In an xterm you + won't see the selected area until the button is + released. Only when 'mouse' option contains 'n' or + 'a'. {not in Vi} + + +To apply an operator on a piece of text: + 1. mark the start of the text with "v", "V" or CTRL-V + The character under the cursor will be used as the start. + 2. move to the end of the text + The text from the start of the Visual mode up to and + including the character under the cursor is highlighted. + 3. hit an operator + The highlighted characters will be operated upon. + +The 'highlight' option can be used to set the display mode to use for +highlighting in Visual mode. + +The highlighted text includes the character under the cursor. On terminals +where it is possible to make the cursor invisible the cursor position is +also highlighted. On terminals where this is not possible the cursor is +displayed normally. If your cursor cannot be made invisible and you want Vim +to highlight the character under the cursor anyway, you could set the 't_cv' +and 't_ci' options to something harmless, for example: + :set t_cv=^[^[ t_ci=^[^[ + +With "v" the text before the start position and after the end position will +not be highlighted. However, All uppercase and non-alpha operators, except +"~", will work on whole lines anyway. See the list of operators below. + + *visual_block* +With CTRL-V (blockwise Visual mode) the highlighted text will be a rectangle +between start position and the cursor. However, some operators work on whole +lines anyway (see the list below). The change and substitute operators will +delete the highlighted text and then start insertion at the top left +position. + +When the "$" command is used with blockwise Visual mode, the right end of the +highlighted text will be determined by the longest highlighted line. This +stops when a motion command is used that does not move straight up or down. + +If you use , click the left mouse button or use any command that +does a jump to another buffer while in Visual mode, the highlighting stops +and no text is affected. Also when you hit "v" in characterwise Visual mode, +"CTRL-V" in blockwise Visual mode or "V" in linewise Visual mode. If you hit +CTRL-Z the highlighting stops and the editor is suspended or a new shell is +started |CTRL-Z|. + + new mode after typing: *v_v* *v_CTRL-V* *v_V* +old mode "v" "CTRL-V" "V" + +Normal Visual blockwise Visual linewise Visual +Visual Normal blockwise Visual linewise Visual +blockwise Visual Visual Normal linewise Visual +linewise Visual Visual blockwise Visual Normal + +For moving the end of the block many commands can be used, but you cannot +use Ex commands, commands that make changes or abandon the file. Commands +(starting with) ".pPiIaAO&", CTRL-^, "ZZ", CTRL-], CTRL-T, CTRL-R, CTRL-I +and CTRL-O cause a beep and Visual mode continues. + +If Visual mode is not active and the "v", "V" or CTRL-V is preceded with a +count, the size of the previously highlighted area is used for a start. You +can then move the end of the highlighted area and give an operator. The type +of the old area is used (character, line or blockwise). +- Linewise Visual mode: The number of lines is multiplied with the count. +- Blockwise Visual mode: The number of lines and columns is multiplied with + the count. +- Normal Visual mode within one line: The number of characters is multiplied + with the count. +- Normal Visual mode with several lines: The number of lines is multiplied + with the count, in the last line the same number of characters is used as + in the last line in the previously highlighted area. +The start of the text is the Cursor position. If the "$" command was used as +one of the last commands to extend the highlighted text, the area will be +extended to the rightmost column of the longest line. + +If you want to highlight exactly the same area as the last time, you can use +"gv" |gv| |v_gv|. + +The operators that can be used are: + ~ switch case |v_tilde| + d delete |v_d| + c change |v_c| + y yank |v_y| + > shift right (1)(*) |v_>| + < shift left (1)(*) |v_<| + ! filter through external command (1) |v_!| + = filter through 'equalprg' option command (1) |v_=| + Q format lines to 'textwidth' length (1)(obsolete)|v_Q| + gq format lines to 'textwidth' length (1) |v_gq| + +The objects that can be used are: + a word |v_a| + A WORD (see |WORD|) |v_A| + s sentence |v_s| + p paragraph |v_p| + P block |v_P| + +Additionally the following commands can be used: + : start ex command for highlighted lines (1) |v_:| + r change |v_r| + C change (2) |v_C| + R change (2) |v_R| + x delete |v_x| + D delete (2) |v_D| + X delete (2) |v_X| + Y yank (2) |v_Y| + J join (1) |v_J| + U make uppercase |v_U| + u make lowercase |v_u| + ^] find tag |v_CTRL-]| + +(1): always whole lines, see |:visual_example| +(2): whole lines when not using CTRL-V +(*): in a future a blockwise shift will move the block only, not whole + lines. + +Note that the ":vmap" command can be used to specifically map keys in Visual +mode. + +If you want to give a register name using the """ command, do this just before +typing the operator character: "v{move around}"xd". + +If you want to give a count to the command, do this just before typing the +operator character: "v{move around}3>" (move lines 3 indents to the right). + + *repeat_Visual* +When repeating a Visual mode operator, the operator will be applied to the +same amount of text as the last time: +- Linewise Visual mode: The same number of lines. +- Blockwise Visual mode: The same number of lines and columns. +- Normal Visual mode within one line: The same number of characters. +- Normal Visual mode with several lines: The same number of lines, in the + last line the same number of characters as in the last line the last time. +The start of the text is the Cursor position. If the "$" command was used as +one of the last commands to extend the highlighted text, the repeating will +be applied up to the rightmost column of the longest line. + + *:visual_example* +Currently the ":" command works on whole lines only. When you select part of +a line, doing something like ":!date" will replace the whole line. If you +want only part of the line to be replaced you will have to make a mapping for +it. In a future release ":" may work on partial lines. + +Here is an example, to replace the selected text with the output of "date": + +:vmap _a a`\!!datekJJ + +(In the <> notation |<>|, when typing it you should type it literally; you +need to remove the 'B' and '<' flags from 'cpoptions') + +What this does is: + stop Visual mode +a break the line after the Visual area +`\< jump to the start of the Visual area +i break the line before the Visual area +!!date filter the Visual text through date +kJJ Join the lines again + + +14. Various commands *various* +==================== + + *CTRL-L* +CTRL-L Clear and redraw the screen. + + *N* + When entering a number: Remove the last digit. + Note: if you like to use for this, add this + mapping to your .vimrc: + :map CTRL-V CTRL-V + See |:fixdel| if your key does not do what you + want. + +:as[cii] or *ga* *:as* *:ascii* +ga Print the ascii value of the character under the + cursor in decimal, hexadecimal and octal. For + example, when the cursor is on a 'R': + 82, Hex 52, Octal 122 + When the character is a non-standard ASCII character, + but printable according to the 'isprint' option, the + non-printable version is also given. When the + character is larger than 127, the form is also + printed. For example: + <~A> 129, Hex 81, Octal 201 +

<|~> 254, Hex fe, Octal 376 + (where

is a special character) + The character in a file is stored internally as + , but it will be shown as: + <^@> 0, Hex 00, Octal 000 + Mnemonic: Get Ascii value. {not in Vi} + + *:p* *:print* +:[range]p[rint] Print [range] lines (default current line). + +:[range]p[rint] {count} + Print {count} lines, starting with [range] (default + current line |cmdline_ranges|). + + *:l* *:list* +:[range]l[ist] [count] + Same as :print, but display unprintable characters + with '^'. + + *:nu* *:number* +:[range]nu[mber] [count] + Same as :print, but precede each line with its line + number. (See also 'highlight' option). + + *:#* +:[range]# [count] synonym for :number. + + *:=* +:= Print the cursor line number. + +:norm[al][!] {commands} *:norm* *:normal* + Execute Normal mode commands {commands}. This makes + it possible to execute normal mode commands typed on + the command line. {commands} is executed like it is + typed. For undo all commands are undone together. If + the [!] is given, mappings will not be used. If + {commands} does not finish a command, more characters + need to be typed. Mostly useful for autocommands. + This command cannot be followed by another command, + since any '|' is considered part of the command. + {not in vi} + + *:sh* *:shell* +:sh[ell] Escape to a shell (name from 'shell' option). + + *:!cmd* *:!* +:!{cmd} Execute {cmd} with the shell. See also the 'shell' + and 'shelltype' option. Any '!' in {cmd} is replaced + with the previous external command (see also + 'cpoptions'). But not when there is a backslash + before the '!', then that backslash is removed. + Example: ":!ls" followed by ":!echo ! \! \\!" + executes "echo ls ! \!". Also see |shell_window|. + + *:!!* +:!! Repeat last ":!{cmd}". + + *:ve* *:version* +:ve[rsion] Print the version number of the editor. If the + compiler used understands "__DATE__" the compilation + date is mentioned. Otherwise a fixed release-date is + shown. The following lines contain information about + which options were defined when Vim was compiled. + +:ve[rsion] {nr} Set the version number to {nr}. Used in .vimrc files. + When omitted Vim will give a warning message. If {nr} + is higher than the current Vim version this will + result in an error message. {not in Vi} + + *K* +K Run a program to lookup the keyword under the + cursor. The name of the program is given with the + 'keywordprg' (kp) option (default is "man"). The + keyword is formed of letters, numbers and the + characters in 'iskeyword'. The keyword under or + right of the cursor is used. The same can be done + with the command + ":!{program} {keyword}". + There is an example of a program to use in the tools + directory of Vim. It is called 'ref' and does a + simple spelling check. + If 'keywordprg' is empty, the ":help" command is used. + {not in Vi} + + *v_K* +{Visual}K Like "K", but use the visually highlighted text for + the keyword. Only works when the highlighted text is + not more than one line. {not in Vi} + +[N]gs *gs* *:sl* *:sleep* +:[N]sl[eep] [N] Do nothing for [N] seconds. Can be interrupted with + CTRL-C (CTRL-break on MS-DOS). "gs" stands for "goto + sleep". While sleeping the cursor is positioned in + the text (if visible). {not in Vi} + + +15. Repeating commands *repeating* +====================== + +15.1 Single repeats *single_repeat* + + *.* +. Repeat last change with count replaced with [count]. + +Simple changes can be repeated with the "." command. Without a count, the +count of the last change is used. If you enter a count, it will replace the +last one. If the last change included a specification of a numbered register, +the register number will be incremented. See the section on undo and redo for +an example how to use this |undo_redo|. Note that when repeating a command +that used a Visual selection, the same SIZE of area is used, see +|repeat_Visual|. + + *@:* +@: Repeat last command line [count] times. + + +15.2 Multiple repeats *multi_repeat* + + *:g* *:global* +:[range]g[lobal]/{pattern}/[cmd] + Execute the Ex command [cmd] (default ":p") on the + lines within [range] where {pattern} matches. + +:[range]g[lobal]!/{pattern}/[cmd] + Execute the Ex command [cmd] (default ":p") on the + lines within [range] where {pattern} does NOT match. + + *:v* *:vglobal* +:[range]v[global]/{pattern}/[cmd] + Same as :g!. + +The global commands work by first scanning through the [range] lines and +marking each line where a match occurs. In a second scan the [cmd] is +executed for each marked line with its line number prepended. If a line is +changed or deleted its mark disappears. The default for [range] is the whole +buffer (1,$). Use "CTRL-C" to interrupt the command. If an error message is +given for a line the global command aborts. + +To repeat a non-Ex command, you will have to put the command in a file and +use "source!". For example: + :g/pat/so! scriptfile +Make sure that the scriptfile ends with a whole command, otherwise Vim will +wait for you to type the rest of the command for each match. The screen will +not have been updated, so you don't know what you are doing. + +The undo/redo command will undo/redo the whole global command at once. +The previous context mark will only be set once (with "''" you go back to +where the cursor was before the global command). + +The global command sets both the last used search pattern and the last used +substitute pattern (this is vi compatible). This makes it easy to globally +replace a string: + :g/pat/s//PAT/g +This replaces all occurences of "pat" with "PAT". The same can be done with: + :%s/pat/PAT/g +Which is two characters shorter! + + +15.3 Complex repeats *complex_repeat* + + *q* +q<0-9a-zA-Z"> Record typed characters into register <0-9a-zA-Z"> + (uppercase to append). The 'q' command is disabled + while executing a register. {Vi: no recording} + +q Stops recording. (Implementation note: The 'q' that + stops recording is not stored in the register, unless + it was the result of a mapping) {Vi: no recording} + + *@* +@<0-9a-z".> Execute the contents of register <0-9a-z".> [count] + times. Note that register '%' (name of the current + file) cannot be used. See also |@:|. {Vi: only named + registers} + + *@@* +@@ Repeat the previous @<0-9a-z":> [count] times. + + *:@* +:[addr]@<0-9a-z"> Execute the contents of register <0-9a-z"> as an Ex + command. First set cursor at line [addr] (default is + current line). When the last line in the register does + not have a it will be added automatically when + the 'e' flag is present in 'cpoptions'. {Vi: only in + some versions} Future: Will execute the register for + each line in the address range. + +:[addr]@: Repeat last command line [count] times. First set + cursor at line [addr] (default is current line). + {Vi: only in some versions} + + *:@@* +:[addr]@@ Repeat the previous :@<0-9a-z">. First set cursor at + line [addr] (default is current line). {Vi: only in + some versions} + + *:so* *:source* +:so[urce] {file} Read Ex commands from {file}. + +:so[urce]! {file} Read Vim commands from {file}. {not in Vi} + +All commands and command sequences can be repeated by putting them in a named +register and then executing it. There are two ways to get the commands in the +register: +- Use the record command "q". You type the commands once, and while they are + being executed they are stored in a register. Easy, because you can see + what you are doing. If you make a mistake, 'put' the register into the + file, edit the command sequence, and then delete it into the register + again. You can continue recording by appending to the register (use an + uppercase letter). +- Delete or yank the command sequence into the register. + +Often used command sequences can be put under a function key with the ':map' +command. + +An alternative is to put the commands in a file, and execute them with the +':source!' command. Useful for long command sequences. Can be combined with +the ':map' command to put complicated commands under a function key. + +The ':source' command reads Ex commands from a file line by line. You will +have to type any needed keyboard input. The ':source!' command reads from a +script file character by character, interpreting each character as if you +typed it. + +Example: When you give the ":!ls" command you are asked to "hit return to +continue". If you ':source' a file with the line "!ls" in it, you will have +to type the return yourself. But if you ':source!' a file with the line +":!ls" in it, the next characters from that file are read until a is +found. You will not have to type yourself, unless ":!ls" was the last +line in the file. + +It is possible to put ':source[!]' commands in the script file, so you can +make a top-down hierarchy of script files. The ':source' command can be +nested as deep as the number of files that can be opened at one time (about +15). The ':source!' command can be nested up to 15 levels deep. + +In script files terminal-dependent key codes are represented by +terminal-independent two character codes. This means that they can be used +in the same way on different kinds of terminals. The first character of a +key code is 0x80 or 128, shown on the screen as "~@". The second one can be +found in the list |key_notation|. Any of these codes can also be entered +with CTRL-V followed by the three digit decimal code. This does NOT work for +the termcap codes, these can only be used in mappings. + + *:source_crnl* +MS-DOS, Win32 and OS/2: Files that are read with ":source" normally have +- line separators. These always work. If you are using a file with + line separators (for example, a file made on Unix), this will be +recognized if you have 'textauto' on and the first line does not end in a +. This fails if the first line has something like ":map :help^M", +where "^M" is a . If the first line ends in a , but following ones +don't, you will get an error message, because the from the first lines +will be lost. + + +16. Undo and redo *undo_redo* +================= + + or *undo* ** *u* +u Undo [count] changes. {Vi: only one level} + + *:u* *:undo* +:u[ndo] Undo one change. {Vi: only one level} + + *CTRL-R* +CTRL-R Redo [count] changes which were undone. {Vi: redraw + screen} + + *:red* *:redo* +:red[o] Redo one change which was undone. {Vi: no redo} + + *U* +U Undo all latest changes on one line. {Vi: while not + moved off of it} + +The last changes are remembered. You can go back in time with the "u" +command. You can then go forward again with the 'CTRL-R' command. If you +make a new change after the "u" command, the 'CTRL-R' will not be possible +anymore. The number of changes that are remembered is set with the +'undolevels' option. If it is zero, the old fashioned Vi undo is present: +one level of undo and undo undoes itself. If it is negative no undo is +possible. Use this if you are running out of memory. + +The "U" command is treated by undo/redo just like any other command. Thus a +"u" command undos a "U" command and a 'CTRL-R' command redoes it again. When +mixing "U", "u" and 'CTRL-R' you will notice that the "U" command will +restore the situation of a line to before the previous "U" command. This may +be confusing. Try it out to get used to it. + +When all changes have been undone the buffer is not considered to be changed. +Vim can then be exit with ":q" instead of ":q!". {this is not in Vi} + +The numbered registers can also be used for undoing deletes. Each time you +delete text, it is put into register "1. The contents of register "1 are +shifted to "2, etc. The contents of register "9 are lost. You can now get +back the most recent deleted text with the put command: '"1P'. (also, if the +deleted text was the result of the last delete or copy operation, 'P' or 'p' +also works as this puts the contents of the unnamed register). You can get +back the text of three deletes ago with '"3P'. + +If you want to get back more than one part of deleted text, you can use a +special feature of the repeat command ".". It will increase the number of the +register used. So if you first do ""1P", the following "." will result in a +'"2P'. Repeating this will result in all numbered registers being inserted. + +Example: If you deleted text with 'dd....' it can be restored with + '"1P....'. + +If you don't know in which register the deleted text is, you can use the +:display command. An alternative is to try the first register with '"1P', and +if it is not what you want do 'u.'. This will remove the contents of the +first put, and repeat the put command for the second register. Repeat the +'u.' until you got what you want. + + +17. Key mapping *key_mapping* +=============== + +There are commands to enter new mappings, remove mappings and list mappings: + +:map {lhs} {rhs} *:map* +:nm[ap] {lhs} {rhs} *:nm* *:nmap* +:vm[ap] {lhs} {rhs} *:vm* *:vmap* +:map! {lhs} {rhs} *:map!* +:im[ap] {lhs} {rhs} *:im* *:imap* +:cm[ap] {lhs} {rhs} *:cm* *:cmap* + Map the key sequence {lhs} to {rhs} for the modes + where the map command applies. + + +:no[remap] {lhs} {rhs} *:no* *:noremap* +:nn[oremap] {lhs} {rhs} *:nn* *:nnoremap* +:vn[oremap] {lhs} {rhs} *:vn* *:vnoremap* +:no[remap]! {lhs} {rhs} *:no!* *:noremap!* +:ino[remap] {lhs} {rhs} *:ino* *:inoremap* +:cno[remap] {lhs} {rhs} *:cno* *:cnoremap* + Map the key sequence {lhs} to {rhs} for the modes + where the map command applies. Disallow mapping of + {rhs}. {not in Vi} + + +:unm[ap] {lhs} *:unm* *:unmap* +:nun[map] {lhs} *:nun* *:nunmap* +:vu[nmap] {lhs} *:vu* *:vunmap* +:unm[ap]! {lhs} *:unm!* *:unmap!* +:iu[nmap] {lhs} *:iu* *:iunmap* +:cu[nmap] {lhs} *:cu* *:cunmap* + Remove the mapping of {lhs} for the modes where the + map command applies. + +:mapc[lear] *:mapc* *:mapclear* +:nmapc[lear] *:nmapc* *:nmapclear* +:vmapc[lear] *:vmapc* *:vmapclear* +:mapc[lear]! *:mapc!* *:mapclear!* +:imapc[lear] *:imapc* *:imapclear* +:cmapc[lear] *:cmapc* *:cmapclear* + Remove all mappings for the modes where the map + command applies. {not in Vi} + +:map +:nm[ap] +:vm[ap] +:map! +:im[ap] +:cm[ap] + List all key mappings for the modes where the map + command applies. + +:map {lhs} *:map_l* +:nm[ap] {lhs} *:nmap_l* +:vm[ap] {lhs} *:vmap_l* +:map! {lhs} *:map_l!* +:im[ap] {lhs} *:imap_l* +:cm[ap] {lhs} *:cmap_l* + List the key mappings for the key sequences starting + with {lhs} in the modes where the map command applies. + {not in Vi} + +These commands are used to map a key or key sequence to a string of +characters. You can use this to put command sequences under function keys, +translate one key into another, etc. See the "Options" chapter below for how +to save and restore the current mapping |options|. + +There are four sets of mappings +- For Insert mode. These are also used in Replace mode. +- For Command-line mode: When entering a ":" or "/" command. +- For Normal mode: When typing commands. +- For Visual mode: When typing commands while the Visual area is highlighted. + +Overview of which map command works in which mode: + + commands: modes: + Normal Visual Insert Command-line +:map :noremap :unmap :mapclear X X . . +:nmap :nnoremap :nunmap :nmapclear X . . . +:vmap :vnoremap :vunmap :vmapclear . X . . +:map! :noremap! :unmap! :mapclear! . . X X +:imap :inoremap :iunmap :imapclear . . X . +:cmap :cnoremap :cunmap :cmapclear . . . X + +The original Vi did not have separate mappings for Normal/Visual mode and +Insert/Command-line mode. Therefore the ":map" and ":map!" commands enter +and display mappings for both. In Vim you can use the ":nmap", "vmap", +":cmap" and ":imap" commands to enter mappings for each mode separately. +When listing mappings the character in column 1 is + + char mode + Normal and Visual + n Normal + v Visual + ! Insert and Command-line + i Insert + c Command-line + +Note: When using mappings for Visual mode, you can use the '<' mark, which +is the start of the last selected Visual area |'<|. + +Everything from the first non-blank after {lhs} up to the end of the line +(or '|') is considered to be part of {rhs}. This allows the {rhs} to end +with a space. + + *map_backslash* +Note that only CTRL-V is mentioned here as a special character for mappings +and abbreviations. When 'cpoptions' does not contain 'B', a backslash can +also be used like CTRL-V. The <> notation can be fully used then |<>|. + + *map_space_in_lhs* +To include a space in {lhs} precede it with a CTRL-V (type two CTRL-Vs for +each space). + *map_space_in_rhs* +If you want a {rhs} that starts with a space, precede {rhs} with a single +CTRL-V (you have to type CTRL-V two times). + *map_empty_rhs* +You can create an empty {rhs} by typing nothing after a single CTRL-V (you +have to type CTRL-V two times). + + *map_bar* +It is not possible to put a comment after this command, because the '"' +character is considered to be part of the {rhs}. To put a '|' in {rhs} +escape it with a backslash or a CTRL-V (to get one CTRL-V you have to type +it twice). When 'b' is present in 'cpoptions', only CTRL-V can be used, "\|" +will be recognized as a mapping ending in a '\' and then another command. This +is vi compatible, but unlogical. Summary: + :map _l :!ls \| more^M works in Vim when 'b' is not in 'cpo' + :map _l :!ls ^V| more^M works always, in Vim and vi + +To avoid mapping of the characters you type in insert or Command-line mode, +type a CTRL-V first. The mapping in Insert mode is disabled if the 'paste' +option is on. + +Note that when an error is enountered (that causes an error message) the rest +of the mapping is not executed. This is vi-compatible. + +Note that the second character (argument) of the commands @zZtTfF[]rm'`"v +and CTRL-X is not mapped. This was done to be able to use all the named +registers and marks, even when the command with the same name has been +mapped. + +Some examples (given as you type them; e.g., the "^V" is CTRL-V which you +type, but will not show up on the screen): + + :map g /foo^V^Mcwbar^V^[ (replace next "foo" with "bar") + :map! qq quadrillion questions + +Vim will compare what you type with the start of a mapped sequence. If there +is an incomplete match, it will get more characters until there either is a +complete match or until there is no match at all. Example: If you map! "qq", +the first 'q' will not appear on the screen until you type another +character. This is because Vim cannot know if the next character will be a +'q' or not. If the 'timeout' option is on (which is the default) Vim will +only wait for one second (or as long as specified with the 'timeoutlen' +option). After that it assumes that the 'q' is to be interpreted as such. If +type slowly, or your system is slow, reset the 'timeout' option. Then you +might want to set the 'ttimeout' option. See the "Options" chapter |options|. + + *recursive_mapping* +If you include the {lhs} in the {rhs} you have a recursive mapping. When +{lhs} is typed, it will be replaced with {rhs}. When the {lhs} which is +included in {rhs} is encountered it will be replaced with {rhs}, and so on. +This makes it possible to repeat a command an infinite number of times. The +only problem is that the only way to stop this is by causing an error. The +macros to solve a maze uses this, look there for an example. There is one +exception: If the {rhs} starts with {lhs}, that part is not mapped again. +For example: + :map a ab +will execute the "a" command and insert a 'b' in the text. The 'a' in the +{rhs} will not be mapped again. + +If you want to exchange the meaning of two keys you should use the :noremap +command. For example: + :noremap k j + :noremap j k +This will exchange the cursor up and down commands. + +With the normal :map command, when the 'remap' option is on, mapping takes +place until the text is found not to be a part of a {lhs}. For example, if +you use: + :map x y + :map y x +Vim will replace x with y, and then y with x, etc. When this has happened +'maxmapdepth' times (default 1000), Vim will give the error message +"recursive mapping". + +See the file "index" for keys that are not used and thus can be mapped +without losing any builtin function. I suggest you use function keys, +and meta-keys. If you are prepared to lose a command that you hardly ever use +you can make mappings that start with '_' or '-'. You can also use +":help ^D" to find out if a key is used for some command. ( is the +specific key you want to find out about, ^D is CTRL-D). + +If you include an undo command inside a mapped sequence, this will bring the +text back in the state before executing the macro. This is compatible with +the original Vi, as long as there is only one undo command in the mapped +sequence (having two undo commands in a mapped sequence did not make sense +in the original Vi, you would get back the text before the first undo). + +There are three ways to map a special key: +1. The Vi-compatible method: Map the key code. Often this is a sequence that + starts with . To enter a mapping like this you type ":map " and then + you have to type CTRL-V before hitting the function key. Note that when + the key code for the key is in the termcap (the t_ options), it will + automatically be translated into the internal code and become the second + way of mapping (unless the 'k' flag is included in 'cpoptions'). +2. The second method is to use the internal code for the function key. To + enter such a mapping type CTRL-K and then hit the function key, or use + the form "#1", "#2", .. "#9", "#0", "", "", "", etc. + (see table of keys |key_notation|, all keys from can be used). The + first ten function keys can be defined in two ways: Just the number, like + "#2", and with "", like "". Both stand for function key 2. "#0" + refers to function key 10, defined with option 't_f10', which may be + function key zero on some keyboards. The <> form cannot be used when + 'cpoptions' includes the '<' flag. +3. Use the termcap entry, with the form , where "xx" is the name of the + termcap entry. Any string entry can be used. For example: + :map G + Maps function key 13 to "G". This does not work if 'cpoptions' includes + the '<' flag. + +The advantage of the second and third method is that the mapping will work on +different terminals without modification (the function key will be +translated into the same internal code or the actual key code, no matter what +terminal you are using. The termcap must be correct for this to work, and you +must use the same mappings). + +DETAIL: Vim first checks if a sequence from the keyboard is mapped. If it +isn't the terminal key codes are tried (see section 20.2 +|terminal_options|). If a terminal code is found it is replaced with the +internal code. Then the check for a mapping is done again (so you can map an +internal code to something else). What is written into the script file +depends on what is recognized. If the terminal key code was recognized as a +mapping the key code itself is written to the script file. If it was +recognized as a terminal code the internal code is written to the script +file. + + +18. Recovery after a crash *crash_recovery* +========================== + +You have spent several hours typing in that text that has to be finished +next morning, and then disaster strikes: Your computer crashes. + + DON'T PANIC! + +You can recover most of your changes from the files that Vim uses to store +the contents of the file. Mostly you can recover your work with one command: + vim -r filename + +18.1 The swap file *swap_file* + +Vim stores the things you changed in a swap file. Using the original file +you started from plus the swap file you can mostly recover your work. + +You can see the name of the current swap file being used with the command: + + :sw[apname] *:sw* *:swapname* + +The name of the swap file is normally the same as the file you are editing, +with the extension ".swp". On MS-DOS and Win32 machines and when the +'shortname' option is on, any '.' in the original file name is replaced with +'_'. If this file already exists (e.g., when you are recovering from a crash) +a warning is given and another extension is used, ".swo", ".swn", etc. An +existing file will never be overwritten. The swap file is deleted as soon as +Vim stops editing the file. + +Technical: The replacement of '.' with '_' is done to avoid problems with + MS-DOS compatible filesystems (e.g., crossdos, multidos). If Vim + is able to detect that the file is on an MS-DOS-like filesystem, a + flag is set that has the same effect as the 'shortname' option. + This flag is reset when you start editing another file. + + If the ".swp" filename already exists, the last character is + decremented until there is no file with that name or ".swa" is + reached. In the last case, no swap file is created. + +By setting the 'directory' option you can place the swap file in another place +than where the edited file is. +Advantages: +- You will not pollute the directories with ".swp" files. +- When the 'directory' is on another partition, reduce the risk of damaging + the file system where the file is (in a crash). +Disadvantages: +- You can get name collisions from files with the same name but in different + directories (although Vim tries to avoid that by comparing the path name). + This will result in bogus ATTENTION warning messages. +- When you use your home directory, and somebody else tries to edit the same + file, he will not see your swap file and will not get the ATTENTION waring + message. +On the Amiga you can also use a recoverable ram disk, but there is no 100% +guarantee that this works. Putting swap files in a normal ram disk (like RAM: +on the Amiga) or in a place that is cleared when rebooting (like /tmp on Unix) +makes no sense, you will lose the swap file in a crash. + +If you want to put swap files in a fixed place, put a command resembling the +following ones in your .vimrc: + :set dir=dh2:tmp (for Amiga) + :set dir=~/tmp (for Unix) + :set dir=c:\\tmp (for MS-DOS and Win32) +This is also very handy when editing files on floppy. Of course you will have +to create that "tmp" directory for this to work! + +When starting to edit a file, Vim checks if a swap file already exists for +that file. If there is one, you will get a message indicating that something +is wrong: + + ATTENTION + Found a swap file by the name "../doc/vim_ref.txt.swp" + dated: Thu May 16 11:46:31 1996 + file name: ~mool/vim/vim/doc/vim_ref.txt + host name: Kibaale + user name: mool + process ID: 211 (still running) + While opening file "../doc/vim_ref.txt" + dated: Wed May 15 21:38:40 1996 + +You are to take one of two actions: + +1. Quit editing this file, because another edit session is active on this + file. Continuing to edit will result in two versions of the same file. + The one that is written last will overwrite the other one, resulting in + loss of changes. The text "(still running)" indicates that the process + editing this file runs on the same computer (Unix only). When working over + a network you will not see this message, because the process will be + running on another computer. +2. Recover a previously crashed edit session. See below |recovery|. + +Vim cannot always detect that a swap file already exists for a file. This is +the case when the other edit session puts the swap files in another +directory or when the path name for the file is different when editing it on +different machines. + +The swap file is updated after typing 200 characters or when you have not +typed anything for four seconds. This only happens if the buffer was +changed, not when you only moved around. The reason why it is not kept up to +date all the time is that this would slow down normal work too much. You can +change the 200 character count with the 'updatecount' option. You can set +the time with the 'updatetime' option. The time is given in milliseconds. +After writing to the swap file Vim syncs the file to disk. This takes some +time, especially on busy Unix systems. If you don't want this you can set the +'swapsync' option to an empty string. The risk of loosing work becomes bigger +though. On some non-Unix systems (MS-DOS, Amiga) the swap file won't be +written at all. + +If the writing to the swap file is not wanted, it can be switched off by +setting the 'updatecount' option to 0. The same is done when starting Vim +with the "-n" option. Writing can be switched back on by setting the +'updatecount' option to non-zero. Swap files will be created for all buffers +when doing this. But when setting 'updatecount' to zero, the existing swap +files will not be removed, it will only affect files that will be opened +after this. + +If you want to make sure that your changes are in the swap file use this +command: + + *:pre* *:preserve* +:pre[serve] Write all text for all buffers into swap file. The + original file is no longer needed for recovery. {Vi: + emergency exit} + +A Vim swap file can be recognized by the first six characters: "b0VIM ". +After that comes the version number, e.g., "3.0". + + +18.2 Recovery *recovery* + +In most cases recovery is quite easy: Start Vim on the same file you were +editing when the crash happened, with the "-r" option added. Vim will read +the ".swp" file and may read bits and pieces of the original file. + +Example: vim -r vim_ref.txt + +If you were editing without a file name, give an empty string as argument: + vim -r "" + +If there are several swap files that look they may be the one you want to +use, a list is given of these swap files and you are requested to enter the +number of the one you want to use. In case you don't know which one to use, +just try them one by one and check the resulting files if they are what you +expected. + +If you know which swap file needs to be used, you can recover by giving the +swap file name. Vim will then find out the name of the original file from +the swap file. + +Example: Vim -r vim_ref.txt.swo + +This is also handy when the swap file is in another directory than expected. +If this still does not work, see what file names Vim reports and rename the +files accordingly. Check the 'directory' option to see where Vim may have +put the swap file. + +Another way to do recovery is to start Vim and use the ":recover" command. +This is easy when you start Vim to edit a file and you get the "ATTENTION: +Found a swap file ..." message. In this case the single command ":recover" +will do the work. You can also give the name of the file or the swap file to +the recover command: + *:rec* *:recover* +:rec[over] [file] Try to recover [file] from the swap file. If [file] + is not given use the file name for the current + buffer. The current contents of the buffer are lost. + This command fails if the buffer was modified. + +:rec[over]! [file] Like ":recover", but any changes in the current + buffer are lost. + +Vim has some intelligence about what to do if the swap file is corrupt in +some way. If Vim has doubt about what it found, it will give an error +message and insert lines with "???" in the text. If you see an error message +while recovering, search in the file for "???" to see what is wrong. You may +want to cut and paste to get the text you need. + +Be sure that the recovery was successful before overwriting the original +file or deleting the swap file. It is good practice to write the recovered +file elsewhere and run 'diff' to find out if the changes you want are in the +recovered file. + +Once you are sure the recovery is ok delete the swap file. Otherwise, you +will continue to get warning messages that the ".swp" file already exists. + +{Vi: recovers in another way and sends mail if there is something to recover} + + +19. Options *options* +=========== + +Vi has a number of internal variables and switches which can be set to +achieve special effects. These options come in three forms: + toggle can only be on or off *toggle* + number has a numeric value + string has a string value + +19.1 Setting options *set_option* + + *:se* *:set* +:se[t] Show all options that differ from their default value. + +:se[t] all Show all but terminal options. + +:se[t] termcap Show all terminal options. + +:se[t] {option}? Show value of {option}. + +:se[t] {option} Toggle option: set, switch it on. + Number option: show value. + String option: show value. + +:se[t] no{option} Toggle option: Reset, switch it off. + +:se[t] {option}! or +:se[t] inv{option} Toggle option: Invert value. {not in Vi} + +:se[t] {option}& Reset option to its default value. {not in Vi} + +:se[t] {option}={value} or +:se[t] {option}:{value} + Set string or number option to {value}. For numeric + options the value can be given in decimal, hex + (preceded with 0x) or octal (preceded with '0') + (hex and octal are only available for machines which + have the strtol() function). The old value can be + inserted by typing (or whatever the value of + 'wildchar' is). See 4.4.2 |cmdline_completion|. + See |option_backslash| for using backslashes in + {value}. + + *:fix* *:fixdel* +:fix[del] Set the value of 't_kD': + 't_kb' is 't_kD' becomes + CTRL-? CTRL-H + not CTRL-? CTRL-? + + (CTRL-? is 0177 octal, 0x7f hex) {not in Vi} + + If your delete key terminal code is wrong, but the + code for backspace is allright, you can put this in + your .vimrc: + :fixdel + This works no matter what the actual code for + backspace is. + + If the backspace key terminal code is + wrong you can use this: + :set t_kb=^V + :fixdel + Where "^V" is CTRL-V and "" is the backspace + key. This will only work for terminals with the same + code for the backspace key, you cannot use this in + your .vimrc unless the code for backspace is the same + on all your systems. + + If your key sends a strange key sequence (not + CTRL-? or CTRL-H) you cannot use ":fixdel". Then use: + :set t_kD=^V + Where "^V" is CTRL-V and "" is the delete key. + This will only work on systems with the same terminal + codes for delete. + + Note about Linux: By default the backspace key + produces CTRL-?, which is wrong. You can fix it by + putting this line in your rc.local: + echo "keycode 14 = BackSpace" | loadkeys + +The {option} arguments to ":set" may be repeated. For example: + ":set ai nosi sw=3 ts=3". +If you make an error in one of the arguments an error message will be given +and the text up to the next space will be skipped. Thus following arguments +will be processed. + +For {option} the form "t_xx" may be used to set a termcap option. This will +override the value from the termcap. You can then use it in a mapping. If +the "xx" part contains special characters, use the form: + :set =^[Ot + +The listing from ":set" looks different from Vi. Long string options are put +at the end of the list. The number of options is quite large. The output of +"set all" probably does not fit on the screen, causing Vim to give the +"--more--" message. See the 'more' option. + + *:set_env* +Environment variables in most string options will be expanded. If the +environment variable exists the '$' and the following environment variable +name is replaced with its value. If it does not exist the '$' and the name +are not modified. Any non-id character (not a letter, digit or '_') may +follow the environment variable name. That character and what follows is +appended to the value of the environment variable. Examples: + :set term=$TERM.new + :set path=/usr/$INCLUDE,$HOME/include,. + +Using "~" is like using "$HOME", but it is only recognized at the start of an +option and after a space or comma. + + *option_backslash* +To include white space in a string option value it has to be preceded with a +backslash. To include a backslash you have to use two. Effectively this +means that the number of backslashes in an option value is halved (rounded +down). +A few examples: + :set tags=tags\ /usr/tags results in "tags /usr/tags" + :set tags=tags\\,file results in "tags\,file" + :set tags=tags\\\ file results in "tags\ file" + +For MS-DOS and WIN32 backslashes in file names are mostly not removed. More +precise: For options that expect a file name (those where environment +variables are expanded) a backslash before a normal file name character is not +removed. But a backslash before a special character (space, backslash, comma, +etc.) is used like explained above. + + +19.2 Automatically setting options *auto_setting* + +Besides changing options with the ":set" command, there are three alternatives +to set options automatically for one or more files: + +1. When starting Vim initializations are read from various places. See + |initialization|. Most of them are performed for all editing sessions, + and some of them depend on the directory where Vim is started. +2. If you start editing a new file, the automatic commands are executed. + This can be used to set options for files matching a particular pattern and + many other things. See the section "Automatic commands" |autocommand|. +3. If you start editing a new file, and the 'modeline' option is on, a + number of lines at the beginning and end of the file are checked for + modelines. This is explained here. + + *modeline* +There are two forms of modelines. The first form: + [text]{white}{vi:|vim:|ex:}[white]{options} + +[text] any text or empty +{white} at least one white space ( or ) +{vi:|vim:|ex:} the string "vi:", "vim:" or "ex:" +[white] optional white space +{options} a list of option settings, separated with white space or ':', + where each part between ':' is the argument for a ":set" + command + +Example: + " vi:noai:sw=3 ts=6" + +The second form (this is compatible with some versions of Vi): + + [text]{white}{vi:|vim:|ex:}[white]set {options}:[text] + +[text] any text or empty +{white} at least one white space ( or ) +{vi:|vim:|ex:} the string "vi:", "vim:" or "ex:" +[white] optional white space +set the string "set " (note the space) +{options} a list of options, separated with white space, which is the + argument for a ":set" command +: a colon +[text] any text or empty + +Example: + "/* vim: set ai tw=75: */" + +The white space before {vi:|vim:|ex:} is required. This minimizes the chance +that a normal word like "lex:" is caught. There is one exception: "vi:" and +"vim:" can also be at the start of the line (for compatibility with version +3.0). Using "ex:" at the start of the line will be ignored (this could be +short for "example:"). + +The number of lines that are checked can be set with the 'modelines' option. +If 'modeline' is off or 'modelines' is 0 no lines are checked. + +Note that for the first form all of the rest of the line is used, thus a line +like: + "/* vi:ts=4: */" +will give an error message for the trailing "*/". This line is OK: + "/* vi:set ts=4: */" + +If an error is detected the rest of the line is skipped. + +If you want to include a ':' in a set command precede it with a '\'. No other +commands than "set" are supported, for security reasons (somebody might create +a trojan horse text file with modelines). + + +19.3 Saving settings *save_settings* + + *:mk* *:mkexrc* +:mk[exrc] [file] Write current key mappings and changed options to + [file] (default ".exrc" in the current directory), + unless it already exists. {not in Vi} + +:mk[exrc]! [file] Always write current key mappings and changed + options to [file] (default ".exrc" in the current + directory). {not in Vi} + + *:mkv* *:mkvimrc* +:mkv[imrc][!] [file] Like as :mkexrc, but default is ".vimrc" in the + current directory. The ":version" command is also + written to the file. {not in Vi} + +These commands will write ":map" and ":set" commands to a file, in such a way +that when these commands are executed, the current key mappings and options +will be set to the same values. The options 'columns', 'endofline', 'lines', +'modified', 'scroll', 'term' and 'ttyfast' are not included, because these are +terminal or file dependent. Note that the options 'binary', 'textmode', +'paste' and 'readonly' are included, this might not always be what you want. + +A common method is to use a default ".vimrc" file, make some modifications +with ":map" and ":set" commands and write the modified file. First read the +default ".vimrc" in with a command like ":source ~piet/.vimrc.Cprogs", change +the settings and then save them in the current directory with ":mkvimrc!". If +you want to make this file your default .vimrc, move it to your home directory +(on Unix), s: (Amiga) or $VIM directory (MS-DOS). You could also use +autocommands |autocommand| and/or modelines |modeline|. + + +19.4 Options summary *option_summary* + +In the list below all the options are mentioned with their full name and some +with an abbreviation between parens. Both forms may be used. In this +document when an option that can be toggled is "set" that means that ":set +option" is entered. When an option is "reset", ":set nooption" is used. + +Most options are the same in all windows and buffers. There are a few that +are specific to how the text is presented in a window. These can be set to a +different value in each window. For example the 'list' option can be set in +one window and reset in another for the same text, giving both types of view +at the same time. There are a few options that are specific to a certain +file. These can have a different value for each file or buffer. For example +the 'textwith' option can be 78 for a normal text file and 0 for a C +program. + + global one option for all buffers and windows + local to window each window has its own copy of this option + local to buffer each buffer has its own copy of this option + +When creating a new window the option values from the currently active window +are used as a default value for the window-specific options. For the +buffer-specific options this depends on the 's' and 'S' flags in the +'cpoptions' option. If 's' in included (which is the default) the values for +buffer options are copied from the currently active buffer when a buffer is +first entered. If 'S' is present the options are copied each time the buffer +is entered, this is almost like having global options. If 's' and 'S' are not +present, the options are copied from the currently active buffer when the +buffer is created. + +A jump table for the options with a short description can be found at |X_op|. + + *'aleph'* *'al'* +aleph (al) number (default 128 for MS-DOS, 224 otherwise) + global + {not in Vi} + {This option applies only if Vim was compiled with + RIGHTLEFT defined} + The ASCII code for the first letter of the Hebrew alphabet. The + routine that maps the keyboard in Hebrew mode, both in Insert mode + (when hkmap is set) and on the command line (when hitting CTRL-_) + outputs the Hebrew characters in the range [aleph..aleph+26]. + aleph=128 applies to PC code, and aleph=224 applies to ISO 8859-8. + See |vim_rlh.txt|. + + *'autoindent'* *'ai'* *'noautoindent'* *'noai'* +autoindent (ai) toggle (default off) + local to buffer + Copy indent from current line when starting a new line (typing + in Insert mode or when using the "o" or "O" command). If you do not + type anything on the new line except and then type or + , the indent is deleted again. When autoindent is on, + formatting (with the "gq" command or when you reach 'textwidth' in + Insert mode) uses the indentation of the first line. When + 'smartindent' or 'cindent' is on the indent is changed in specific + cases. The 'autoindent' option is reset when the 'paste' option is + set. {small difference from Vi: After the indent is deleted when + typing or , the cursor position when moving up or down is + after the deleted indent; Vi puts the cursor somewhere in the deleted + indent}. + + *'autowrite'* *'aw'* *'noautowrite'* *'noaw'* +autowrite (aw) toggle (default off) + global + Write the contents of the file, if it has been modified, on each + :next, :rewind, :previous, :stop, :suspend, :tag, :!, :make, CTRL-] + and CTRL-^ command; and when a CTRL-O, CTRL-I, ', or ` + command takes one to another file. + + *'backspace'* *'bs'* +backspace (bs) number (default 0) + global + {not in Vi} + Influences the working of , , CTRL-W and CTRL-U in Insert + mode. If set to 0 Vi compatible backspacing is used. When 1 allow + backspacing over newlines. When larger than 1 allow backspacing over + the start of insert. In the last case CTRL-W and CTRL-U stop once at + the start of insert. See |:fixdel| if your or key does not + do what you want. + + *'backup'* *'bk'* *'nobackup'* *'nobk'* +backup (bk) toggle (default off) + global + {not in Vi} + Make a backup before overwriting a file. Leave it around after the + file has been successfully written. If you do not want to keep the + backup file, but you do want a backup while the file is being + written, reset this option and set the 'writebackup' option (this is + the default). If you do not want a backup file at all reset both + options (use this if your file system is almost full). See the table + in section 5.4 for more explanations |backup_table|. + + *'backupdir'* *'bdir'* +backupdir (bdir) string (default for Amiga: ".,t:", + for MS-DOS and Win32: ".,c:/tmp,c:/temp" + for Unix: ".,~/tmp,~/") + global + {not in Vi} + List of directories for the backup file, separated with commas. + - The backup file will be created in the first directory in the list + where this is possible. + - Empty means that no backup file will be created ('patchmode' is + impossible!). + - A directory '.' means to put the backup file in the same directory + as the edited file. Characters after the "." are ignored, "./temp" + is handled in the same way as ".". + - Spaces after the comma are ignored, other spaces are considered part + of the directory name. To have a space at the start of a directory + name, precede it with a backslash. + - To include a comma in a directory name precede it with a backslash. + - A directory name may end in an '/'. + - Environment variables are expanded |:set_env|. + - Careful with '\' characters, type one before a space, type two to + get one in the option (see |option_backslash|), for example: + :set bdir=c:\\tmp,\ dir\\,with\\,commas,\\\ dir\ with\ spaces + - For backwards compatibility with Vim version 3.0 a '>' at the start + of the option is removed. + See also 'backup' and 'writebackup' options. + + *'backupext'* *'bex'* +backupext (bex) string (default "~") + global + {not in Vi} + String which is appended to a file name to make the name of the + backup file. The default is quite unusual, because this avoids + accidently overwriting existing files with a backup file. You might + prefer using ".bak", but make sure that you don't have files with + ".bak" that you want to keep. + + *'binary'* *'bin'* *'nobinary'* *'nobin'* +binary (bin) toggle (default off) + local to buffer + {not in Vi} + This option should be set before editing a binary file. You can also + use the "-b" Vim argument. When this option is switched on a few + options will be changed (also when it already was on): + 'textwidth' will be set to 0 + 'wrapmargin' will be set to 0 + 'modeline' will be off + 'textmode' will be off + 'textauto' will be off + 'expandtab' will be off + NOTE: When you start editing a(nother) file while the 'bin' option is + on, settings from the modelines or autocommands may change the + settings again (e.g., 'textwidth'), causing trouble when editing. You + might want to set 'bin' again when the file has been loaded. + The previous values of these options are remembered and restored when + 'bin' is switched fron on to off. Each buffer has its own set of + saved option values, except for 'textauto', which is global. + When writing a file the end-of-line for the last line is only written + if there was one in the original file (normally Vim appends an + end-of-line to the last line if there is none; this would make the + file longer). See the 'endofline' option. + + *'bioskey'* *'biosk'* *'nobioskey'* *'nobiosk'* +bioskey (biosk) toggle (default on) + global + {not in Vi} {Only for MS-DOS} + When on the bios is called to obtain a keyboard character. This works + better to detect CTRL-C, but only works for the console. When using a + terminal over a serial port reset this option. + + *'breakat'* *'brk'* +breakat (brk) string (default " ^I!@*-+_;:,./?") + global + {not in Vi} + This option lets you choose which characters might cause a line + break if 'linebreak' is on. + + *'cindent'* *'cin'* *'nocindent'* *'nocin'* +cindent (cin) toggle (default off) + local to buffer + {not in Vi} + {Only present when compiled with CINDENT enabled, + ":version" says "+cindent" instead of "-cindent"} + Enables automatic C program indenting See 'cinkeys' to set the keys + that trigger reindenting in insert mode and 'cinoptions' to set your + preferred indent style. If 'lisp' is not on and 'equalprg' is empty, + the "=" operator indents using this algorithm rather than calling an + external program. See |C_indenting|. This option is switched off + when 'paste' is set. When you don't like the way 'cindent' works, try + the 'smartindent' option. + +cinkeys (cink) string (default "0{,0},:,0#,!^F,o,O,e") + local to buffer + {not in Vi} + {Only present when compiled with CINDENT enabled} + A list of keys that, when typed in insert mode, cause reindenting of + the current line. Only happens if 'cindent' is on. See + |C_indenting|. + + *'cinoptions'* *'cino'* +cinoptions (cino) string (default "") + local to buffer + {not in Vi} + {Only present when compiled with CINDENT enabled} + The 'cinoptions' affect the way 'cindent' reindents lines in a C + program. See |C_indenting|. + + *'cinwords'* *'cinw'* +cinwords (cinw) string (default "if,else,while,do,for,switch") + local to buffer + {not in Vi} + {Only present when compiled with CINDENT or + SMARTINDENT enabled} + These keywords start an extra indent in the next line when + 'smartindent' or 'cindent' is set. For 'cindent' this is only done at + an appropriate place (inside {}). + + *'cmdheight'* *'ch'* +cmdheight (ch) number (default 1) + global + {not in Vi} + Number of lines to use for the command line. If you are annoyed by + "Hit return ..." caused by long messages, set this option to a larger + value. + + *'columns'* *'co'* +columns (co) number (default 80 or terminal width) + global + {not in Vi} + Number of columns of the screen. Normally this is set by the terminal + initialization and does not have to be set by hand. + + *'comments'* *'com'* +comments (com) string (default + "sr:/*,mb:*,el:*/,://,b:#,:%,:XCOMM,n:>,fb:-") + local to buffer + {not in Vi} + A comma separated list of patterns that can start a comment line. See + |format_comments|. See |option_backslash| about using backslashes to + insert a space. + + *'compatible'* *'cp'* *'nocompatible'* *'nocp'* +compatible (cp) toggle (default off, on when compiled with COMPATIBLE + defined, ":version" shows "+compatible") + global + {not in Vi} + At the moment this option is set, several other options will be set + or reset to make Vim Vi-compatible. Switching this option off has no + effect. See also 'cpoptions'. + + option new value effect + + backspace 0 normal backspace + backup off no backup file + cindent off no C code indentation + cpoptions "bcefFkmorsStx$!%" vi-compatible flags + digraph off no digraphs + esckeys off no -keys in Insert mode + expandtab off tabs not expanded to spaces + formatoptions "vt" Vi compatible formatting + gdefault off no default 'g' flag for ":s" + history 0 no commandline history + insertmode off do not start in Insert mode + iskeyword "@,48-57,_" keywords contain alphanumeric + characters and '_' + joinspaces on insert 2 spaces after period + modeline off no modelines + more off no pauses in listings + revins off no reverse insert + ruler off no ruler + scrolljump 1 no jump scroll + scrolloff 0 no scroll offset + shiftround off indent not rounded to shiftwidth + showcmd off command characters not shown + showmode off current mode not shown + smartcase off no automatic ignore case switch + smartindent off no smart indentation + smarttab off no smart tab size + startofline on goto startofline with some commands + textauto off no automatic textmode detection + textwidth 0 no automatic line wrap + tildeop off tilde is not an operator + ttimeout off no terminal timeout + undolevels 0 no multilevel undo + updatecount 0 no swap file + whichwrap "" left-right movements don't wrap + wildchar CTRL-E only when the current value is + use CTRL-E for cmdline completion + writebackup off no backup file written + + *'cpoptions'* *'cpo'* +cpoptions (cpo) string (default "BceFs", unless compiled with + COMPATIBLE defined, then all flags are + included) + global + {not in Vi} + Sequence of characters. When a character is present this indicates + vi-compatible behaviour. This is used for things where not being + vi-compatible is mostly or sometimes preferred. 'cpoptions' stands + for 'compatibleoptions'. Commas can be added for readability. + + contains behaviour + b "\|" in a ":map" command is recognized as the end of + the map command. The '\' is included in the mapping, + the text after the '|' is interpreted as the next + command. Use a CTRL-V instead of a backslash to + include the '|' in the mapping. Applies to all + mapping, abbreviation, menu and autocmd commands. + See also |map_bar|. + B A backslash has no special meaning in mappings, + abbreviations and the "to" part of the menu commands. + Remove this flag to be able to use a backslash like a + CTRL-V. This flag must be removed to be able to fully + use the <> notation. For example, the command + ":map X \" results in X being mapped to: + 'B' included: "\^[" (^[ is a real ) + 'B' excluded: "" (5 characters) + ('<' excluded in both cases) + < Disable the recognition of special key codes in <> + form in mappings, abbreviations, and the "to" part of + menu commands. For example, the command + ":map X " results in X being mapped to: + '<' included: "" (5 characters) + '<' excluded: "^I" (^I is a real ) + Also see the 'k' flag below. + c Searching continues at the end of any match at the + cursor position. When not present searching continues + one character from the cursor position. With 'c' + "abababababab" only gets three matches when repeating + "/abab", without 'c' there are five matches. + e When executing a register with ":@r", always add a + to the last line, also when the register is not + linewise. If this flag is not present, the register + is not linewise and the last line does not end in a + , then the last line is put on the command line + and can be edited before hitting . + f When included, a ":read" command with a file name + argument will set the file name for the current buffer, + if the current buffer doesn't have a file name yet. + F When included, a ":write" command with a file name + argument will set the file name for the current buffer, + if the current buffer doesn't have a file name yet. + k Disable the recognition of raw key codes in + mappings, abbreviations, and the "to" part of menu + commands. For example, if sends ^[OA (where ^[ + is ), the command ":map X ^[OA" results in X + being mapped to: + 'k' included: "^[OA" (3 characters) + 'k' excluded: "" (one key code) + Also see the '<' flag above. + m When included, a showmatch will always wait half a + second. When not included, a showmatch will wait half + a second or until a character is typed. |'showmatch'| + o Line offset to search command is not remembered for + next search. + r Redo ("." command) uses "/" to repeat a search + command, instead of the actually used search string. + s Set buffer options when entering the buffer for the + first time. This is like it is in Vim version 3.0. + And it is the default. If not present the options are + set when the buffer is created. + S Set buffer options always when entering a buffer + (except 'readonly' and 'textmode'). This is the + (most) vi compatible setting. + The options are set to the values in the current + buffer. When you change an option and go to another + buffer, the value is copied. Effectively makes the + buffer options global to all buffers. + + 's' 'S' copy buffer options + no no when buffer created + yes no when buffer first entered (default) + no yes each time when buffer entered (vi comp.) + yes yes when buffer created or first entered + + t Search pattern for the tag command is remembered for + "n" command. Otherwise Vim only puts the pattern in + the history for search pattern, but doesn't change the + last used search pattern. + x on the command line executes the command line. + The default in Vim is to abandon the command line, + because normally aborts a command. |c_| + $ When making a change to one line, don't redisplay the + line, but put a '$' at the end of the changed text. + The changed text will be overwritten when you type the + new text. The line is redisplayed if you type any + command that moves the cursor from the insertion + point. + ! When redoing a filter command, use the last used + external command, whatever it was. Otherwise the last + used -filter- command is used. + % Vi-compatible matching is done for the "%" command. + Parens inside single and double quotes are also + counted, causing a string that contains a paren to + disturb the matching. For example, in a line like + "if (strcmp("foo(", s))" the first paren does not + match the last one. When this flag is not included, + parens inside single and double quotes are treated + specially. When matching a paren outside of quotes, + everyting inside quotes is ignored. When matching a + paren inside quotes, it will find the matching one (if + there is one). This works very well for C programs. + + *'define'* *'def'* +define (def) string (default "^#[ \t]*define") + global + {not in Vi} + Pattern to be used to find a macro definition. It is a search + pattern, just like for the "/" command. The default value is for C + programs. This option is used for the commands like "[i" and "[d" + |include_search|. The 'isident' option is used to recognize the + identifier name after the match. See |option_backslash| about + inserting backslashes to include a space or backslash. + + *'dictionary'* *'dict'* +dictionary (dict) string (default "") + global + {not in Vi} + List of file names, separated by commas, that are used to lookup words + for keyword completion commands |i_CTRL-X_CTRL-K|. Each file should + contain a list of words, one word per line. To include a comma in a + file name precede it with a backslash. Spaces after a comma are + ignored, otherwise spaces are included in the file name. See + |option_backslash| about using backslashes. + + *'digraph'* *'dg'* *'nodigraph'* *'nodg'* +digraph (dg) toggle (default off) + global + {not in Vi} + {Only applies when compiled with DIGRAPHS defined, + check with ":version"} + Enable the entering of digraphs in Insert mode with {char1} + {char2}. See |digraphs|. + + *'directory'* *'dir'* +directory (dir) string (default for Amiga: ".,t:", + for MS-DOS and Win32: ".,c:\tmp,c:\temp" + for Unix: ".,~/tmp,/tmp") + global + List of directory names for the swap file, separated with commas. + - The swap file will be created in the first directory where this is + possible. + - Empty means that no swap file will be used (recovery is + impossible!). + - A directory '.' means to put the swap file in the same directory as + the edited file. Everything after the "." is ignored, "./temp" is + handled in the same way as ".". + - Spaces after the comma are ignored, other spaces are considered part + of the directory name. To have a space at the start of a directory + name, precede it with a backslash. + - To include a comma in a directory name precede it with a backslash. + - A directory name may end in an ':' or '/'. + - Environment variables are expanded |:set_env|. + - Careful with '\' characters, type one before a space, type two to + get one in the option (see |option_backslash|), for example: + :set dir=c:\\tmp,\ dir\\,with\\,commas,\\\ dir\ with\ spaces + - For backwards compatibility with Vim version 3.0 a '>' at the start + of the option is removed. + Using "." first in the list is recommended. This means that editing + the same file twice will result in a warning. Using "/tmp" on Unix is + discouraged, when the system crashes you lose the swap file. That is + why a "tmp" directory in your home directory is used first. + {Vi: directory to put temp file in, defaults to "/tmp"} + + *'ed'* *'edcompatible'* *'noed'* *'noedcompatible'* +edcompatible (ed) toggle (default off) + global + Makes the 'g' and 'c' flags of the ":substitute" command to be + toggled each time the flag is given. See 11.3 |complex_change|. See + also 'gdefault' option. + + *'endofline'* *'eol'* *'noendofline'* *'noeol'* +endofline (eol) toggle (default on) + local to buffer + {not in Vi} + When writing a file and this option is off and the 'binary' option + is on, no end of line (newline) character will be written for the + last line in the file. This option is automatically set when + starting to edit a new file, unless the file does not have an end of + line (newline) for the last line in the file, in which case it is + reset. Normally you don't have to set or reset this option. When + 'binary' is off the value is not used when writing the file. When + 'binary' is on it is used to remember the presence of a newline for + the last line in the file, so that when you write the file the + situation from the original file can be kept. But you can change it + when you want to. + + *'equalalways'* *'ea'* *'noequalalways'* *'noea'* +equalalways (ea) toggle (default on) + global + {not in Vi} + When on all the windows are automatically made the same size after + splitting or closing a window. When off, splitting a window will + reduce the size of the current window and leave the other windows the + same. When closing a window the extra lines are given the the window + above it. + + *'equalprg'* *'ep'* +equalprg (ep) string (default "") + global + {not in Vi} + External program to use for "=" command. When this option is empty + the internal formatting functions are used ('lisp' or 'cindent'). + Environment variables are expanded |:set_env|. See |option_backslash| + about including spaces and backslashes. + + *'errorbells'* *'eb'* *'noerrorbells'* *'noeb'* +errorbells (eb) toggle (default off) + global + Ring the bell (beep or screen flash) for error messages. This only + makes a difference for error messages, the bell will be used always + for a lot of errors without a message (e.g., hitting in Normal + mode). See 'visualbell' on how to make the bell behave like a beep, + screen flash or do nothing. + + *'errorfile'* *'ef'* +errorfile (ef) string (default "AztecC.Err" or "errors.vim") + global + {not in Vi} + Name of the error file for the QuickFix mode (see 5.5 + |:make_makeprg|). Environment variables are expanded |:set_env|. See + |option_backslash| about including spaces and backslashes. + + *'errorformat'* *'efm'* +errorformat (efm) string (default is very long) + global + {not in Vi} + Scanf-like description of the format for the lines in the error file + (see 5.5 |errorformat|). + + *'esckeys'* *'ek'* *'noesckeys'* *'noek'* +esckeys (ek) toggle (default on, off when compiled with COMPATIBLE + defined) + global + {not in Vi} + Function keys that start with an are recognized in Insert + mode. When this option is off, the cursor and function keys cannot be + used in Insert mode if they start with an . The advantage of + this is that the single is recognized immediately, instead of + after one second. Instead of resetting this option, you might want to + try changing the values for 'timeoutlen' and 'ttimeoutlen'. + + *'expandtab'* *'et'* *'noexpandtab'* *'noet'* +expandtab (et) toggle (default off) + local to buffer + {not in Vi} + In Insert mode: Use the appropriate number of spaces to insert a + . Spaces are used in indents with the '>' and '<' commands and + when 'autoindent' is on. To insert a real tab when 'expandtab' is + on, use CTRL-V. See also ":retab" command in 11.3 |:retab| + and section 4.3.4 |ins_expandtab|. + + *'exrc'* *'noexrc'* +exrc toggle (default off) + global + {not in Vi} + Enables the reading of .vimrc and .exrc in the current directory. If + you switch this option on you should also consider setting the + 'secure' option (see 3.4 |initialization|). Using a local .exrc or + .vimrc is a potential security leak, use with care! + + *'formatoptions'* *'fo'* +formatoptions (fo) string (default "tcq", "vt" when compiled with + COMPATIBLE defined) + local to buffer + {not in Vi} + This is a sequence of letters which describes how automatic + formatting is to be done. See |fo_table|. When the 'paste' option is + on, no formatting is done (like 'formatoptions' is empty). Commas can + be inserted for readability. + + *'formatprg'* *'fp'* +formatprg (fp) string (default "") + global + {not in Vi} + The name of an external program that will be used to format the lines + selected with the "gq" command. The program must take the input on + stdin and produce the output on stdout. The Unix program 'fmt' is + such a program. If this option is an empty string, the internal + format function will be used |C_indenting|. Environment variables are + expanded |:set_env|. See |option_backslash| about including spaces + and backslashes. + + *'gdefault'* *'gd'* *'nogdefault'* *'nogd'* +gdefault (gd) toggle (default off) + global + {not in Vi} + When on, the ":substitute" flag 'g' is default on. This means that + all matches in a line are substituted instead of one. When a 'g' flag + is given to a ":substitute" command, this will toggle the substitution + of all or one match. See 11.3 |complex_change|. + + command 'gdefault' on 'gdefault' off + :s/// subst. all subst. one + :s///g subst. one subst. all + :s///gg subst. all subst. one + + *'guifont'* *'gfn'* +guifont (gfn) string (default "") + global + {not in Vi} + {Only available when compiled with GUI enabled} + This is a list of fonts which should be tried when starting the GUI + version of vim. The fonts are separated with commas. Spaces after a + comma are ignored. To include a comma in a font name precede it with + a backslash. Setting an option requires an extra backslash before a + space and a backslash. See also |option_backslash|. For example: + :set guifont=Screen15,\ 7x13,font\\,with\\,commas + will make vim try to use the font "Screen15" first, and if it fails + it will try to use "7x13" and then "font,with,commas" instead. If + none of the fonts can be loaded, vim will try using other resource + settings (for X, it will use the Vim.font resource), and finally it + will try some builtin default which should always be there ("7x13" in + the case of X). The font names given should be "normal" fonts. Vim + will try to find the related bold and italic fonts. + + *'guioptions'* *'go'* +guioptions (go) string (default "agmr") + global + {not in Vi} + {Only available when compiled with GUI enabled} + This option only has an effect in the GUI version of vim. It is a + sequence of letters which describes what components and options of the + GUI should be used. Valid letters are as follows: + + 'a' Autoselect: If present, then whenever VISUAL mode is started, + or the Visual area extended, vim tries to become the owner of + the windowing system's global selection. This means that the + Visually highlighted text is available for pasting into other + applications as well as into vim itself. When the Visual mode + ends, possibly due to an operation on the text, or when an + application wants to paste the selection, the highlighted text + is automatically yanked into the <"*> GUI selection register. + Thus the selection is still available for pasting into other + applications after the VISUAL mode has ended. + If not present, then vim won't become the owner of the + windowing system's global selection unless explicitly told to + by selecting "Cut" or "Copy" etc from the "Edit" menu (which + have not been implemented yet!). + + 'f' Foreground: Don't use fork() to detatch the GUI from the shell + where it was started. Use this for programs that wait for the + editor to finish (e.g., an e-mail program). Altenatively you + can use "gvim -f" or ":gui -f" to start the GUI in the + foreground. |gui_fork| + + 'm' Menu bar is present when 'm' is included. + 'g' Grey menu items: Make menu items that are not active grey. If + 'g' is not included inactive menu items are not shown at all. + + 'r' Right-hand scrollbar is present when 'r' is included. + 'l' Left-hand scrollbar is present when 'l' is included. + 'b' Bottom (horizontal) scrollbar is present when 'b' is included. + + And yes, you may even have scrollbars on the left AND the right if + you really want to :-). See |gui_scrollbars| for more information. + + *'guipty'* *'noguipty'* +guipty toggle (default off) + global + {not in Vi} + {Only available when compiled with GUI enabled} + Only in the GUI: If on, an attempt is made to open a pseudo-tty for + I/O to/from shell commands. See |gui_pty|. + + *'helpfile'* *'hf'* +helpfile (hf) string (default (Amiga) "vim:vim_help.txt" + (MS-DOS, Win32, OS/2) "$VIM/vim_help.txt" + (Unix) "/usr/local/lib/vim/vim_help.txt") + global + {not in Vi} + Name of the help file. All help files should be placed together in + one directory. Environment variables are expanded |:set_env|. For + example: "$VIM/doc/vim_help.txt". If $VIM is not set, $HOME is also + tried. For Unix the default is adjusted at compile time to where the + help files are being installed. See |option_backslash| about + including spaces and backslashes. + + *'helpheight'* *'hh'* +helpheight (hh) number (default 20) + global + {not in Vi} + Minimal initial height of the help window when it is opened with the + ":help" command. The initial height of the help window is half of the + current window, or (when the 'ea' option is on) the same as other + windows. When the height is less than 'helpheight', the height is + set to 'helpheight'. Set to zero to disable. + + *'hidden'* *'hid'* *'nohidden'* *'nohid'* +hidden (hid) toggle (default off) + global + {not in Vi} + When off the current buffer is unloaded when it is abandoned. When + on the current buffer becomes hidden when starting to edit another + buffer. If the current buffer is also displayed in another window it + does not become hidden. The commands that move through the buffer + list make the current buffer hidden although the 'hidden' option is + off. See also |vim_win.txt|. + + *'highlight'* *'hl'* +highlight (hl) string (default "8b,db,es,hs,mb,Mn,nu,rs,sr,tb,vr,ws") + global + {not in Vi} + This option can be used to set highlighting mode for various + occasions. It is a comma separated list of character pairs. The + first character in a pair gives the occasion, the second the mode to + use for that occasion. The occasions are: + 8 Meta & special keys listed with ":map" + d directories in CTRL-D listing + e error messages + h help file headers + m "--More--" message + M Mode (e.g., "-- INSERT --") + n line number for ":number" and ":#" commands + r return to continue message and yes/no questions + s status lines + t Titles for output from ":set all", ":autocmd" etc. + v Visual mode + w warning messages + The display modes are: + r reverse (termcap entry "mr" and "me") + i italic (termcap entry "ZH" and "ZR") + b bold (termcap entry "md" and "me") + s standout (termcap entry "so" and "se") + u underline (termcap entry "us" and "ue") + n no highlighting + The default is used for occasions that are not included. + + *'history'* *'hi'* +history (hi) number (default 20) + global + {not in Vi} + A history of ":" commands, and a history of previous search patterns + are remembered. This option decides how many entries may be stored in + each of these histories (see |cmdline_editing|). + + *'hkmap'* *'hk'* *'nohkmap'* *'nohk'* +hkmap (kh) toggle (default off) + global + {not in Vi} + {Only available if Vim was compiled with RIGHTLEFT + defined} + When on, the keyboard is mapped for the Hebrew character set. + Normally you would use CTRL-_ in insert mode to toggle this option. + See |vim_rlh.txt|. + + *'icon'* *'noicon'* +icon toggle (default off, on when title can be restored) + global + {not in Vi} + When on the icon of the window will be set to the name of the file + currently being edited. Only the last part of the name is used. Only + works if the terminal supports setting window icons (currently only + Unix xterm and iris-ansi). When Vim was compiled with HAVE_X11 + defined, the original icon will be restored if possible |X11|. + + *'ignorecase'* *'ic'* *'noignorecase'* *'noic'* +ignorecase (ic) toggle (default off) + global + Ignore case in search patterns. Also used when searching in the tags + file. + + *'include'* *'inc'* +include (inc) string (default "^#[ \t]*include") + global + {not in Vi} + Pattern to be used to find an include command. It is a search + pattern, just like for the "/" command (See 6.6, "Pattern + searches" |search_pattern|). The default value is for C programs. + This option is used for the commands "[i", "]I", "[d", etc.. The + 'isfname' option is used to recognize the file name that comes after + the matched pattern. See |option_backslash| about including spaces + and backslashes. + + *'incsearch'* *'is'* *'noincsearch'* *'nois'* +incsearch (is) toggle (default off) + global + {not in Vi} + While typing a search pattern, show immediately where the so far + typed pattern matches. The matched string is highlighted. If the + pattern is invalid or not found, nothing is shown. The screen will + be updated often, this is only useful on fast terminals. + + *'infercase'* *'inf'* *'noinfercase'* *'noinf'* +infercase (inf) toggle (default off) + local to buffer + {not in Vi} + When doing keyword completion in insert mode |ins_completion|, and + 'ignorecase' is also on, the case of the match is adjusted. If the + typed text contains a lowercase letter where the match has an upper + case letter, the completed part is made lower case. If the typed text + has no lower case letters and the match has a lower case letter where + the typed text has an upper case letter, and there is a letter before + it, the completed part is made uppercase. + + *'insertmode'* *'im'* *'noinsertmode'* *'noim'* +insertmode (im) toggle (default off) + global + {not in Vi} + Start the edit of a file in Insert mode. Useful if you want to use + Vim like a modeless editor (use the cursor keys to move around, use + CTRL-O for other commands |i_CTRL-O|). + + *'isfname'* *'isf'* +isfname (isf) string (default for MS-DOS, Win32 and OS/2: + "@,48-57,/,.,-,_,+,,,$,:,\" + for AMIGA: "@,48-57,/,.,-,_,+,,,$,:" + otherwise: "@,48-57,/,.,-,_,+,,,$,:,~") + global + {not in Vi} + The characters given by this option are included in file names and + path names. File names are used for commands like "gf", "[i" and in + the tags file. Besides the characters in this option characters that + are defined by the C function isalpha() are also always included + (this depends on the character set and "locale"). + + The format of this option is a list of parts, separated with commas. + Each part can be a single character number or a range. A range is two + character numbers with '-' in between. A character number can be a + decimal number between 0 and 255 or the ASCII character itself (does + not work for digits). Example: + "_,-,128-140,#-43" (include '_' and '-' and the range + 128 to 140 and '#' to 43) + If a part starts with '^', the following character number or range + will be excluded from the option. The option is interpreted from left + to right. Put the excluded character after the range where it is + included. To include '^' itself use it as the last character of the + option or the end of a range. Example: + "^a-z,#,^" (exclude 'a' to 'z', include '#' and '^') + If the character is '@', all characters where isalpha() returns TRUE + are included. Normally these are the characters a to z and A to Z, + plus accented characters. To include '@' itself use "@-@". Examples: + "@,^a-z" All alphabetic characters, excluding lower + case letters. + "a-z,A-Z,@-@" All letters plus the '@' character. + A comma can be included by using it where a character number is + expected. Example: + "48-57,,,_" Digits, command and underscore. + A comma can be excluded by prepending a '^'. Example: + " -~,^,,9" All characters from space to '~', excluding + comma, plus . + See |option_backslash| about including spaces and backslashes. + + *'isident'* *'isi'* +isident (isi) string (default for MS-DOS, Win32 and OS/2: + "@,48-57,_,128-167,224-235" + otherwise: "@,48-57,_,192-255") + global + {not in Vi} + The characters given by this option are included in identifiers. + Identifiers are used in recognizing environment variables and after + a match of the 'define' option. See 'isfname' for a description of + the format of this option. + + *'iskeyword'* *'isk'* +iskeyword (isk) string (default for MS-DOS and Win32: + "@,48-57,_,128-167,224-235" + otherwise: "@,48-57,_,192-255" + but when compiled with COMPATIBLE defined: + "@,48-57,_") + local to buffer + {not in Vi} + Keywords are used in searching and recognizing with many commands: + "*", "[i", etc. See 'isfname' for a description of the format of this + option. For C programs you could use "a-z,A-Z,48-57,_,.,-,>". For a + help file it is set to all non-blank printable characters except '*', + '"' and '|'. When the 'lisp' option is on the '-' character is always + included. + + *'isprint'* *'isp'* +isprint (isp) string (default for MS-DOS and Win32: "@,~-255" + otherwise: "@,161-255") + global + {not in Vi} + The characters given by this option are displayed directly on the + screen. The characters from space (ascii 32) to '~' (ascii 126) are + always displayed directly, even when they are not included in + 'isprint' or excluded. See 'isfname' for a description of the format + of this option. Non-printable characters are displayed with two + characters: + 0 - 31 "^@" - "^_" + 32 - 126 always single characters + 127 "^?" + 128 - 159 "~@" - "~_" + 160 - 254 "| " - "|~" + 255 "~?" + + *'joinspaces'* *'js'* *'nojoinspaces'* *'nojs'* +joinspaces (js) toggle (default on) + global + {not in Vi} + Insert two spaces after a period with a join command. + + *'keywordprg'* *'kp'* +keywordprg (kp) string (default "man") + global + {not in Vi} + Program to use for the "K" command. Environment variables are + expanded |:set_env|. When empty ":help" is used. See + |option_backslash| about including spaces and backslashes. + + *'langmap'* *'lmap'* +langmap (lmap) string (default "") + global + {not in Vi} + {Only included when Vim was compiled with HAVE_LANGMAP + defined (Check ":version" for "+langmap"). + This option allows support for keyboards that have a mode for a + special language. The idea is that when you are typing text in Insert + mode your keyboard is switched in the special language mode, you get + different key codes for the special characters. When in command mode + the 'langmap' option takes care of translating these special + characters to the original meaning of the key. This means you don't + have to change the keyboard mode to be able to execute normal mode + commands. + + Example (for greek): *greek* + :set langmap=ÁA,ÂB,ØC,ÄD,ÅE,ÖF,ÃG,ÇH,ÉI,ÎJ,ÊK,ËL,ÌM,ÍN,ÏO,ÐP,QQ,ÑR,ÓS,ÔT,ÈU,ÙV,WW,×X,ÕY,ÆZ,áa,âb,øc,äd,åe,öf,ãg,çh,éi,îj,êk,ël,ìm,ín,ïo,ðp,qq,ñr,ós,ôt,èu,ùv,òw,÷x,õy,æz + Example (exchanges meaning of z and y for commands): + :set langmap=zy,yz,ZY,YZ + + The 'langmap' option is a list of parts, separated with commas. Each + part can be in one of two forms: + 1. A list of pairs. Each pair is a "from" character immediately + followed by the "to" character. Examples: "aA", "aAbBcC". + 2. A list of "from" characters, a semi-colon and a list of "to" + characters. Example: "abc;ABC" + Example: "aA,fgh;FGH,cCdDeE" + Special characters need to be preceded with a backslash. These are + ";", ',' and backslash itself. + + This will allow you to activate vim actions without having to switch + back and forth between the languages. Your language characters will + be understood as normal vim English characters (according to the + langmap mappings) in the following cases: + o Normal/Visual mode (commands, buffer/register names, user mappings) + o Insert/Replace Mode: Register names after CTRL-R + o Insert/Replace Mode: Mappings + Characters entered in Command-line mode will NOT be affected by + this option. Note that this option can be changed at any time + allowing to switch between mappings for different languages/encodings. + Use a mapping to avoid having to type it each time! + + *'laststatus'* *'ls'* +laststatus (ls) number (default 1) + global + {not in Vi} + The value of this option influences when the last window will have a + status line: + 0: never + 1: only if there are at least two windows + 2: always + The screen looks nicer with a status line if you have several + windows, but it takes another screen line. + + *'linebreak'* *'lbr'* *'nolinebreak'* *'nolbr'* +linebreak (lbr) toggle (default off) + local to window + {not in Vi} + If on Vim will wrap long lines at a character in 'breakat' rather + than at the last character that fits on the screen. Unlike + 'wrapmargin' and 'textwidth', this does not insert newline characters + in the file, it only affects the way the file is displayed, not its + contents. The value of 'showbreak' is used to put in front of wrapped + lines. This option is not used when the 'wrap' option is off. Note + that characters after a line break are mostly not displayed + correctly. + + *'lines'* +lines number (default 24 or terminal height) + global + Number of lines in the display. Normally you don't need to set this. + That is done automatically by the terminal initialization code. When + you do set this, and Vim is unable to change the physical number of + lines on the display, redisplaying may be wrong. + + *'lisp'* *'nolisp'* +lisp toggle (default off) + local to buffer + {Only included when compiled with LISPINDENT enabled, + ":version" says "+lispindent" instead of + "-lispindent"} + Lisp mode: When a return is typed in insert mode set the indent for + the next line to Lisp standards (well, sort of). Also happens with + "cc" or "S". 'autoindent' must also be on for this to work. The '-' + character is included in keyword characters. Redefines the "=" + operator to use this same indentation algorithm rather than calling an + external program if 'equalprg' is empty. This option is reset when + 'paste' is set. {Vi: Does it a little bit differently} + + *'list'* *'nolist'* +list toggle (default off) + local to window + List mode: Show tabs as CTRL-I, show end of line with $. Useful to + see the difference between tabs and spaces and for trailing blanks. + Note that this will also affect formatting (set with 'textwidth' or + 'wrapmargin'). + + *'magic'* *'nomagic'* +magic toggle (default on) + global + Changes the special characters that can be used in search patterns. + See section "Pattern searches" |search_pattern|. + + *'makeprg'* *'mp'* +makeprg (mp) string (default "make") + global + {not in Vi} + Program to use for the ":make" command. See |:make_makeprg|. This + option may contain '%' and '#' characters, which are expanded like + when used in a command line. Environment variables are expanded + |:set_env|. See |option_backslash| about including spaces and + backslashes. + + *'maxmapdepth'* *'mmd'* +maxmapdepth (mmd) number (default 1000) + global + {not in Vi} + Maximum number of times a mapping is done without resulting in a + character to be used. This normally catches endless mappings, like + ":map x y" with ":map y x". It still does not catch ":map g wg", + because the 'w' is used before the next mapping is done. See also + |key_mapping|. + + *'maxmem'* *'mm'* +maxmem (mm) number (default 512) + global + {not in Vi} + Maximum amount of memory (in Kbyte) to use for one buffer. When this + limit is reached allocating extra memory for a buffer will cause + other memory to be freed. See also 'maxmemtot'. + + *'maxmemtot'* *'mmt'* +maxmemtot (mmt) number (default 2048, or half the amount of memory + available) + global + {not in Vi} + Maximum amount of memory (in Kbyte) to use for all buffers together. + See also 'maxmem'. + + *'modeline'* *'ml'* *'nomodeline'* *'noml'* +modeline (ml) toggle (default on, off when compiled with COMPATIBLE + defined) + local to buffer + *'modelines'* *'mls'* +modelines (mls) number (default 5) + global + {not in Vi} + If 'modeline' is on 'modelines' gives the number of lines that is + checked for set commands. If 'modeline' is off or 'modelines' is zero + no lines are checked. See 19.1 |modeline|. 'modeline' is reset when + 'compatible' is set. + + *'modified'* *'mod'* *'nomodified'* *'nomod'* +modified (mod) toggle (default off) + local to buffer + {not in Vi} + When on the buffer is considered to be modified. This option is set + by every command that makes a change to the buffer. Only the undo + command may reset it, when all changes have been undone. + + *'more'* *'nomore'* +more toggle (default on, off when compiled with COMPATIBLE + defined) + global + {not in Vi} + Listings pause when the whole screen is filled. Type: + or for one more line. + for the next page. + 'd' for down half a page. + 'q' or CTRL-C to stop the listing. + ':' to stop the listing and enter a command line. + Any other key causes the meaning of the keys to be displayed. + When this option is off there are no pauses, the listing continues + until finished. When 'compatible' is set this option is reset. + Note: The key typed at the "more" prompt is directly obtained from the + terminal, it is not mapped and typeahead is ignored. + + *'mouse'* +mouse string (default "", "a" for MS-DOS and Win32) + global + {not in Vi} + Enable the use of the mouse. Only works for certain terminals + (MS-DOS, Win32 and xterm). The mouse can be enabled for different + modes: + n Normal mode + v Visual mode + i Insert mode + c Command-line mode + h all previous modes when editing a help file + a all previous modes + r for "Hit return ..." question + Normally you would enable the mouse in all four modes with: + :set mouse=a + Note: Normal copy/paste in an xterm can still be used by pressing the + shift key when the mouse is being used by Vim. See |mouse_using|. + + *'mousetime'* *'mouset'* +mousetime (mouset) number (default 500) + global + {not in Vi} + Only for GUI, MS-DOS, Win32 and Unix with xterm. Defines the maximum + time in msec between two mouse clicks for the second click to be + recognized as a multi click. + + *'number'* *'nu'* *'nonumber'* *'nonu'* +number (nu) toggle (default off) + local to window + Print the line number in front of each line. Tip: If you don't like + wrapping lines to mix with the line numbers, set the 'showbreak' + option to eight spaces: + :set showbreak=\ \ \ \ \ \ \ \ + + *'paragraphs'* *'para'* +paragraphs (para) string (default "IPLPPPQPP LIpplpipbp") + global + Specifies the nroff macros that separate paragraphs. These are pairs + of two letters (see section 6.4 |object_motions|). + + *'paste'* *'nopaste'* +paste toggle (default off) + global + {not in Vi} + Put Vim in Paste mode. This is useful if you want to cut or copy + some text from one window and paste it in Vim. This will avoid + unexpected effects. When the 'paste' option is switched on (also when + it was already on): + - mapping in Insert mode is disabled + - abbreviations are disabled + - 'textwidth' is set to 0 + - 'autoindent' is reset + - 'smartindent' is reset + - 'cindent' is reset + - 'lisp' is reset + - 'revins' is reset + - 'ruler' is reset + - 'showmatch' is reset + - 'formatoptions' is used like it is empty + NOTE: When you start editing another file while the 'paste' option is + on, settings from the modelines or autocommands may change the + settings again, causing trouble when pasting text. You might want to + set the 'paste' option again. + When the 'paste' option is reset the mentioned options are restored to + the value before the moment 'paste' was switched from off to on. + Resetting 'paste' before ever setting it does not have any effect. If + you use this often, you could map a function key to the command ":set + invpaste^V^M". + + *'patchmode'* *'pm'* +patchmode (pm) string (default "") + global + {not in Vi} + When non-empty the oldest version of a file is kept. This can be used + to keep the original version of a file if you are changing files in a + source distribution. Only the first time that a file is edited a copy + of the original file will be kept. The name of the copy is the name + of the original file with the string in the 'patchmode' option + appended. This option should start with a dot. Use a string like + ".org". 'backupdir' must not be empty for this to work (Detail: The + backup file is renamed to the patchmode file after the new file has + been succesfully written, that's why it must be possible to write a + backup file). If there was no file to be backed up, an empty file is + created. + + *'path'* *'pa'* +path (pa) string (default on Unix: ".,/usr/include,," + on OS/2: ".,/emx/include,," + other systems: ".,,") + global + {not in Vi} + This is a list of directories which will be searched when using the + gf, [f, ]f, ^Wf and other commands, provided that the file being + searched for has a relative path (not starting with '/'). The + directories in the 'path' option may be relative or absolute. + - Use commas to separate directory names: + :set path=.,/usr/local/include,/usr/include + - Spaces can also be used to separate directory names (for backwards + compatibility with version 3.0). To have a space in a directory + name, precede it with an extra backslash: + :set path=.,/dir/with\\ space + - To include a comma in a directory name precede it with an extra + backslash: + :set path=.,/dir/with\\,comma + - To search relative to the directory where the current file is use + :set path=. + - To search in the current directory use an empty string between two + commas: + :set path=,, + - A directory name may end in a ':' or '/'. + - Environment variables are expanded |:set_env|. + - Careful with '\' characters, type two to get one in the option: + :set path=.,c:\\include + Or just use '/' instead: + :set path=.,c:/include + Don't forget "." or files won't even be found in the same directory as + the file! + + *'readonly'* *'ro'* *'noreadonly'* *'noro'* +readonly (ro) toggle (default off) + local to buffer + {not in Vi} + If on, writes fail unless you use a '!'. Protects you from + accidentally overwriting a file. Default on when Vim is started + in view mode ("vim -v") or when the executable is called "view". + {not in Vi:} When using the ":view" command the 'readonly' option is + set for the newly edited buffer. When using ":w!" the 'readonly' + option is reset for the current buffer. + + *'remap'* *'noremap'* +remap toggle (default on) + global + Allows for mappings to work recursively. If you do not want this for + a single entry, use the :noremap[!] command. + + *'report'* +report number (default 2) + global + Threshold for reporting number of lines changed. When the number of + changed lines is more than 'report' a message will be given for most + ":" commands. For the ":substitute" command the number of + substitutions is used instead of the number of lines. + + *'restorescreen'* *'rs'* *'norestorescreen'* *'nors'* +restorescreen (rs) toggle (default on) + global + {not in Vi} {Windows 95/NT version only} + When set, the screen contents is restored when exiting Vim. This also + happens when executing external commands. + + *'revins'* *'ri'* *'norevins'* *'nori'* +revins (ri) toggle (default off) + global + {not in Vi} + {only when compiled with RIGHTLEFT defined} + Inserting characters in Insert mode will work backwards. See "typing + backwards" |ins_reverse|. This option can be toggled with the CTRL-B + command in Insert mode. This option is reset when 'compatible' or + 'paste' is set. + + *'rightleft'* *'rl'* *'norightleft'* *'norl'* +rightleft (rl) toggle (default off) + local to window + {not in Vi} + {only when compiled with RIGHTLEFT defined} + When on, display orientation becomes right-to-left, i.e., character + that are stored in the file apear from the right to the left. Using + this option, it is possible to edit files for languages that are + written from the right to the left such as Hebrew and Arabic. This + option is per window, so it is possible to edit mixed files + simultaneously, or to view the same file in both ways (this is + sometimes usefull when editing Hebrew TeX--XeT files). See + |vim_rlh.txt|. + + *'ruler'* *'ru'* *'noruler'* *'noru'* +ruler (ru) toggle (default off) + global + {not in Vi} + Show the line and column number of the cursor position in the status + line, separated by a comma. If there are characters in the line that + take two positions on the screen, both the "real" column and the + screen column are shown, separated with a dash. For an empty line + "0-1" is shown. For an empty buffer the line number will also be + zero: "0,0-1". This option is reset when the 'paste' option is set. + If you don't want to see the ruler all the time but want to know where + you are, use "g CTRL-G" |g_CTRL-G|. + + *'scroll'* *'scr'* +scroll (scr) number (default 'lines' / 2) + local to window + Number of lines to scroll with CTRL-U and CTRL-D commands. Will be + set to half the number of lines in the window when the window size + changes. If you give a count to the CTRL-U or CTRL-D command it will + be used as the new value for 'scroll'. Reset to 'lines' / 2 with + ":set scroll=0". {difference from vi: 'scroll' gives the number of + screen lines instead of file lines, makes a difference when lines + wrap} + + *'scrolljump'* *'sj'* +scrolljump (sj) number (default 1) + global + {not in Vi} + Minimal number of lines to scroll when the cursor gets off the + screen (e.g., with "j"). Not used for scroll commands (e.g., CTRL-E, + CTRL-D). Useful if your terminal scrolls very slowly. + + *'scrolloff'* *'so'* +scrolloff (so) number (default 0) + global + {not in Vi} + Minimal number of screen lines to keep above and below the cursor. + This will make some context visible around where you are working. If + you set it to a very large value (999) the cursor line will always be + in the middle of the window (except at the start or end of the file or + when long lines wrap). + + *'sections'* *'sect'* +sections (sect) string (default "SHNHH HUnhsh") + global + Specifies the nroff macros that separate sections. These are pairs of + two letters (See section 6.4 |object_motions|). + + *'secure'* *'nosecure'* +secure toggle (default off) + global + {not in Vi} + When on, ":autocmd", shell and write commands are not allowed in + ".vimrc" and ".exrc" in the current directory and map commands are + displayed. Switch it off only if you know that you will not run into + problems, or when the 'exrc' option is off. On Unix this option is + only used if the ".vimrc" or ".exrc" is not owned by you. This can be + dangerous if the systems allows users to do a "chown". You better set + 'secure' at the end of your ~/.vimrc then. + + *'shell'* *'sh'* +shell (sh) string (default $SHELL or "sh", + MS-DOS and Win32: "command", + OS/2: "cmd") + global + Name of the shell to use for ! and :! commands. When changing the + value also check the 'shelltype', 'shellpipe' and 'shellredir' + options. It is allowed to give an argument to the command, e.g. + "csh -f". See |option_backslash| about including spaces and + backslashes. Environment variables are expanded |:set_env|. + + *'shellpipe'* *'sp'* +shellpipe (sp) string (default ">", "| tee", "|& tee" or "2>&1| tee") + global + {not in Vi} + String to be used to put the output of the ":make" command in the + error file. See also |:make_makeprg|. See |option_backslash| about + including spaces and backslashes. + For the Amiga and MS-DOS the default is ">". The output is directly + saved in a file and not echoed to the screen. + For Unix the default it "| tee". The stdout of the compiler is saved + in a file and echoed to the screen. If the 'shell' option is "csh" or + "tcsh" after initializations, the default becomes "|& tee". If the + 'shell' option is "sh", "ksh", "zsh" or "bash" the default becomes + "2>&1| tee". This means that stderr is also included. + The initialization of this option is done after reading the ".vimrc" + and the other initializations, so that when the 'shell' option is set + there, the 'shellpipe' option changes automatically, unless it was + explicitly set before. + In the future pipes may be used for filtering and this option will + become obsolete (at least for Unix). + + *'shellredir'* *'srr'* +shellredir (srr) string (default ">", ">&" or ">%s 2>&1") + global + {not in Vi} + String to be used to put the output of a filter in a temporary file. + See also |:!|. See |option_backslash| about including spaces and + backslashes. + The name of the temporary file can be represented by "%s" if necessary + (the file name is appended automatically if no %s appears in the value + of this option). + The default is ">". For Unix, if the 'shell' option is "csh", "tcsh" + or "zsh" during initializations, the default becomes ">&". If the + 'shell' option is "sh", "ksh" or "bash" the default becomes + ">%s 2>&1". This means that stderr is also included. + The initialization of this option is done after reading the ".vimrc" + and the other initializations, so that when the 'shell' option is set + there, the 'shellredir' option changes automatically unless it was + explicitly set before. + In the future pipes may be used for filtering and this option will + become obsolete (at least for Unix). + + *'shelltype'* *'st'* +shelltype (st) number (default 0) + global + {not in Vi} + On the Amiga this option influences the way how the commands work + which use a shell. + 0 and 1: always use the shell + 2 and 3: use the shell only to filter lines + 4 and 5: use shell only for ':sh' command + When not using the shell, the command is executed directly. + + 0 and 2: use 'shell -c cmd' to start external commands + 1 and 3: use 'shell cmd' to start external commands + + *'shiftround'* *'sr'* *'noshiftround'* *'nosr'* +shiftround (sr) toggle (default off) + global + {not in Vi} + Round indent to multiple of 'shiftwidth'. Applies to > and < + commands. CTRL-T and CTRL-D in Insert mode always round the indent to + a multiple of 'shiftwidth' (this is vi compatible). + + *'shiftwidth'* *'sw'* +shiftwidth (sw) number (default 8) + local to buffer + Number of spaces to use for each step of (auto)indent. + + *'shortmess'* *'shm'* +shortmess (shm) string (default "") + global + {not in Vi} + This option helps to avoid all the "Hit return" messages caused by + file messages, for example with CTRL-G, and to avoid some other + messages. It is a list of flags: + flag meaning when present + l use "999L, 888C" instead of "999 lines, 888 characters" + m use "[+]" instead of "[Modified]" + r use "[RO]" instead of "[readonly]" + x use "[tx]" instead of "[textmode]" and "[notx]" instead of + "[notextmode]" + f use "(3 of 5)" instead of "(file 3 of 5)" + i use "[noeol]" instead of "[Incomplete last line]" + n use "[New]" instead of "[New File]" + w use "[w]" instead of "written" for file write message. + a all of the above abbreviations + + o overwrite message for writing a file with subsequent message + for reading a file (useful for ":wn" or when 'autowrite' on) + W don't give "written" or "[w]" when writing a file + s don't give "search hit BOTTOM, continuing at TOP" or "search + hit TOP, continuing at BOTTOM" messages + t trunctate file message at the start if it is too long to fit + on the command line, "<" will appear in the left most column. + This gives you the opportunity to avoid that a change between buffers + requires you to hit return, but still gives as useful a message as + possible for the space available. To get the whole message that you + would have got with 'shm' empty, use ":file!" + Useful values: + shm= -- No abbreviation of message. + shm=a -- Abbreviation, but no loss of information. + shm=at -- Abbreviation, and truncate message when necessary. + + *'shortname'* *'sn'* *'noshortname'* *'nosn'* +shortname (sn) toggle (default off) + local to buffer + {not in Vi} + Filenames can be 8 characters plus one extension of 3 characters. + Multiple dots in file names are not allowed. When this option is on, + dots in filenames are replaced with underscores when adding an + extension (".~" or ".swp"). This option is not available for + MS-DOS and Win32, because then it would always be on. This option is + useful when editing files on an MS-DOS compatible filesystem, e.g., + messydos or crossdos. + + *'showbreak'* *'sbr'* +showbreak (sbr) string (default "") + global + {not in Vi} + String to put at the start of lines that have been wrapped. Useful + values are "> " or "+++ ". Only printable characters are allowed, + excluding and comma (in a future version the comma might be used + to separate the part that is shown at the end and at the start of a + line). + + *'showcmd'* *'sc'* *'noshowcmd'* *'nosc'* +showcmd (sc) toggle (default on, off for Unix) + global + {not in Vi} + Show (partial) command in status line. Set this option off if your + terminal is slow. + + *'showmatch'* *'sm'* *'noshowmatch'* *'nosm'* +showmatch (sm) toggle (default off) + global + When a bracket is inserted, briefly jump to the matching one. The + jump is only done if the match can be seen on the screen. + A Beep is given if there is no match (no matter if the match can be + seen or not). This option is reset when the 'paste' option is set. + When the 'm' flag is not included in 'cpoptions', typing a character + will immediately move the cursor back to where it belongs. + + *'showmode'* *'smd'* *'noshowmode'* *'nosmd'* +showmode (smd) toggle (default on, off when compiled with COMPATIBLE + defined) + global + If in Insert, Replace or Visual mode put a message on the last line. + Use the 'M' flag in 'highlight' to set the type of highlighting for + this message. + + *'sidescroll'* *'ss'* +sidescroll (ss) number (default 0) + global + {not in Vi} + The minimal number of columns to scroll horizontally. Used only when + the 'wrap' option is off and the cursor is moved off of the screen. + When it is zero the cursor will be put in the middle of the screen. + When using a slow terminal set it to a large number or 0. When using + a fast terminal use a small number or 1. Not used for "zh" and "zl" + commands. + + *'smartcase'* *'scs'* *'nosmartcase'* *'noscs'* +smartcase (scs) toggle (default off) + global + {not in Vi} + Override the 'ignorecase' option if the search pattern contains upper + case characters. Only used when the search pattern is typed and + 'ignorecase' option is on. Used for the commands "/", "?", "n", "N", + ":g" and ":s". Not used for "*", "#", "gd", tag search, etc.. + + *'smartindent'* *'si'* *'nosmartindent'* *'nosi'* +smartindent (si) toggle (default off) + local to buffer + {not in Vi} + {Only included when compiled with SMARTINDENT defined, + check with ":version"} + Do smart autoindenting when starting a new line. Works best for C + programs, but can also be used for other languages. 'cindent' does + something like this, works better in most cases, but is more strict, + see |C_indenting|. When 'cindent' is on setting 'si' has no effect. + Normally 'autoindent' should also be on when using 'smartindent'. + An indent is automatically inserted: + - After a line ending in '{'. + - After a line starting with a keyword from 'cinwords'. + - Before a line starting with '}' (only with the "O" command). + When typing '}' as the first character in a new line, that line is + given the same indent as the matching '{'. + When typing '#' as the first character in a new line, the indent for + that line is removed, the '#' is put in the first column. The indent + is restored for the next line. If you don't want this, use this + mapping: ":inoremap # X^H#", where ^H is entered with CTRL-V CTRL-H. + When using the ">>" command, lines starting with '#' are not shifted + right. + 'smartindent' is reset when the 'paste' option is set. + + *'smarttab'* *'sta'* *'nosmarttab'* *'nosta'* +smarttab (sta) toggle (default off) + global + {not in Vi} + When on, a in front of a line inserts 'shiftwidth' positions, + 'tabstop' in other places. When off a always inserts 'tabstop' + positions, 'shiftwidth' is only used for ">>" and the like. See also + section 4.3.4 |ins_expandtab|. + + *'splitbelow'* *'sb'* *'nosplitbelow'* *'nosb'* +splitbelow (sb) toggle (default off) + global + {not in Vi} + When on, spliting a window will put the new window below the current + one. + + *'startofline'* *'sol'* *'nostartofline'* *'nosol'* +startofline (sol) toggle (default on) + global + {not in Vi} + When on the commands listed below move the cursor to the first + blank of the line. When off the cursor is kept in the same column + (if possible). This applies to the commands: CTRL-D, CTRL-U, CTRL-B, + CTRL-F, "G", "H", "M", "L", , and to the commands "d", "<<" and ">>" + with a linewise operator and with "%" with a count. This option is + set when the 'compatible' option is set. + + *'suffixes'* *'su'* +suffixes (su) string (default ".bak,~,.o,.h,.info,.swp") + global + {not in Vi} + Files with these suffixes are ignored when multiple files match a + wildcard. See |suffixes|. Commas can be used to separate the + suffixes. Spaces after the comma are ignored. A dot is also seen as + the start of a suffix. To include a dot or comma in a suffix, precede + it with a backslash (see |option_backslash| about including spaces and + backslashes). + + *'swapsync'* *'sws'* +swapsync (sws) string (default "fsync") + global + {not in Vi} + When this option is not empty a swap file is synced to disk after + writing to it. This takes some time, especially on busy unix systems. + When this option is empty parts of the swap file may be in memory and + not written to disk. When the system crashes you may lose more work. + On Unix the system does a sync now and then without Vim asking for it, + so the disadvantage of setting this option off is small. On some + systems the swap file will not be written at all. For a unix system + setting it to "sync" will use the sync() call instead of the default + fsync(), which may work better on some systems. + + *'tabstop'* *'ts'* +tabstop (ts) number (default 8) + local to buffer + Number of spaces that a in the file counts for. (See also + ":retab" command in 11.3 |:retab|). + + *'taglength'* *'tl'* +taglength (tl) number (default 0) + global + If non-zero, tags are significant up to this number of characters. + + *'tagrelative'* *'tr'* *'notagrelative'* *'notr'* +tagrelative (tr) toggle (default on, off when compiled with COMPATIBLE + defined) + global + {not in Vi} + If on and using a tag file in another directory, file names in that + tag file are relative to the directory where the tag file is. When + the 'compatible' option is set, this option is reset. + + *'tags'* *'tag'* +tags (tag) string (default "./tags,tags", when compiled with + Emacs tags enabled: "./tags,./TAGS,tags,TAGS") + global + Filenames for the tag command, separated by spaces or commas. To + include a space or comma in a filename, precede it with a backslash + (see |option_backslash| about including spaces and backslashes). + When a file name starts with "./", the '.' is replaced with the path + of the current file. Environment variables are expanded |:set_env|. + Also see |tags_option|. + When Vim was compiled with EMACS_TAGS defined Emacs-style tag files + are also supported. They are automatically recognized. The default + value becomes "./tags,./TAGS,tags,TAGS". If ":version" shows + "+emacs_tags" then the Emacs tags support is included. {Vi: default + is "tags /usr/lib/tags"} + + *'term'* +term string (default is $TERM, if that fails: + on Unix: "ansi" + on Amiga: "amiga" + on MS-DOS: "pcterm" + on OS/2: "os2ansi" + on Win 32: "win32") + global + Name of the terminal. Used for choosing the terminal control + characters. Environment variables are expanded |:set_env|. + For example: + :set term=$TERM + See |termcap|. + + *'terse'* *'noterse'* +terse toggle (default off) + global + {not in Vi} + When set: Add 's' flag to 'shortmess' option (this makes the message + for a search that hits the start or end of the file not being + displayed). When reset: Remove 's' flag from 'shortmess' option. {Vi + shortens a lot of messages} + + *'textauto'* *'ta'* *'notextauto'* *'nota'* +textauto (ta) toggle (default on, off when compiled with COMPATIBLE + defined) + global + {not in Vi} + When starting to edit a file a check is done for the line separator. + If all lines end in 'textmode' is set, otherwise 'textmode' + is reset. When reading a file, the same is done, but this happens + like 'textmode' has been set appropriately for that file only, the + option is not changed. See |textmode_io|. + + *'textmode'* *'tx'* *'notextmode'* *'notx'* +textmode (tx) toggle (MS-DOS, Win32 and OS/2: default on, others: + default off) + local to buffer + {not in Vi} + When off, separates lines. When on, separates lines + and CTRL-Z at end of file is ignored. Only used when reading and + writing files. Set automatically when starting to edit a file and + 'textauto' is on. See |textmode_io|. + + *'textwidth'* *'tw'* +textwidth (tw) number (default 0) + local to buffer + {not in Vi} + Maximum width of text that is being inserted. A longer line will be + broken after white space to get this width. A zero value disables + this. 'textwidth' is set to 0 when the 'paste' option is set. When + 'textwidth' is zero, 'wrapmargin' may be used. See also + 'formatoptions' and |ins_textwidth|. + + *'tildeop'* *'top'* *'notildeop'* *'notop'* +tildeop (top) toggle (default off) + global + {not in Vi} + When on: The tilde command "~" behaves like an operator. + + *'timeout'* *'to'* *'notimeout'* *'noto'* +timeout (to) toggle (default on) + global + *'ttimeout'* *'nottimeout'* +ttimeout toggle (default off) + global + {not in Vi} + These two options together determine the behaviour when part of a + mapped key sequence or keyboard code has been received: + + timeout ttimeout action + off off no time out + on on or off time out on :mappings and key codes + off on time out on key codes + + If there is no time out, Vim will wait until either the complete + mapping or key sequence has been received, or it is clear that there + is no mapping or key sequence for the received characters. For + example: if you have mapped "vl" and Vim has received 'v', the next + character is needed to see if the 'v' is followed by an 'l'. With a + time out Vim will wait for about 1 second for the next character to + arrive. After that the already received characters are interpreted + as single characters. The waiting time can be changed with the + 'timeoutlen' option. + On slow terminals or very busy systems time out may cause + malfunctioning cursor keys. If both options are off, Vim waits + forever after an entered if there are key codes that start + with . You will have to type twice. If you do not have + problems with key codes, but would like to have :mapped key + sequences not time out in 1 second, set the ttimeout option and + reset the timeout option. + + *'timeoutlen'* *'tm'* +timeoutlen (tm) number (default 1000) + global + {not in all versions of Vi} + *'ttimeoutlen'* *'ttm'* +ttimeoutlen (ttm) number (default -1) + global + {not in Vi} + The time in milliseconds that is waited for a key code or mapped key + sequence to complete. Normally only 'timeoutlen' is used and + 'ttimeoutline' is -1. When a different timeout value for key codes is + desired set 'ttimeoutlen' to a non-negative number. + + ttimeoutlen mapping delay key code delay + < 0 'timeoutlen' 'timeoutlen' + >= 0 'timeoutlen' 'ttimeoutlen' + + The timeout only happens when the 'timeout' and 'ttimeout' options + tell so. A useful setting would be + :set timeout timeoutlen=3000 ttimeloutlen=100 + (time out on mapping after three seconds, time out on key codes after + a tenth of a second). + + *'title'* *'notitle'* +title toggle (default off, on when title can be restored) + global + {not in Vi} + When on the title of the window will be set to "VIM - filename", + where filename is the name of the file currently being edited. Only + works if the terminal supports setting window titles (currently Amiga + console, Unix xterm and iris-ansi). + *X11* + When Vim was compiled with HAVE_X11 defined, the original title will + be restored if possible. The output of ":version" will include "+X11" + when HAVE_X11 was defined, otherwise it will be "-X11". This also + works for the icon name |'icon'|. + If the title cannot be restored, it is set to "Thanks for flying Vim". + You will have to restore the title outside of Vim then. When using an + xterm from a remote machine you can use this command: + rsh machine_name xterm -display $DISPLAY & + then the WINDOWID environment variable should be inherited and the + title of the window should change back to what it should be after + exiting Vim (rather than using the "Thanks..." message). + + *'ttybuiltin'* *'tbi'* *'nottybuiltin'* *'notbi'* +ttybuiltin (tbi) toggle (default on) + global + {not in Vi} + When on, the builtin termcaps are searched before the external ones. + When off the builtin termcaps are searched after the external ones. + When this option is changed, you should set the 'term' option next for + the change to take effect, for example: + :set notbi term=$TERM + See also |termcap|. + + *'ttyfast'* *'tf'* *'nottyfast'* *'notf'* +ttyfast (tf) toggle (default off, on when 'term' is xterm, hpterm, + sun-cmd, scren, dtterm or iris-ansi) + global + {not in Vi} + Indicates a fast terminal connection. More characters will be sent to + the screen for redrawing, instead of using insert/delete line + commands. Improves smoothness of redrawing when there are multiple + windows and the terminal does not support a scrolling region. + Also enables the extra writing of characters at the end of each screen + line for lines that wrap. This helps when using copy/paste with the + mouse in an xterm and other terminals. + + *'ttyscroll'* *'tsl'* +ttyscroll (tsl) number (default 999) + global + Maximum number of lines to scroll the screen. If there are more lines + to scroll the window is redrawn. For terminals where scrolling is + very slow and redrawing is not slow this can be set to a small number, + e.g., 3, to speed up displaying. + + *'ttytype'* *'tty'* +ttytype (tty) string (default from $TERM) + global + Alias for 'term', see above. + + *'undolevels'* *'ul'* +undolevels (ul) number (default 100, 1000 for Unix and OS/2, 0 when + compiled with COMPATIBLE defined) + global + {not in Vi} + Maximum number of changes that can be undone. Set to 0 for Vi + compatibility: one level of undo and 'u' undoes itself. Set to a + negative number for no undo at all (saves memory). + + *'updatecount'* *'uc'* +updatecount (uc) number (default 200, 0 when compiled with COMPATIBLE + defined) + global + {not in Vi} + After typing this many characters the swap file will be written to + disk. When zero no swap file will be created at all (see chapter on + recovery |crash_recovery|). 'updatecount' is set to zero by starting + Vim with the "-n" option, see |starting|. When editing in readonly + mode this option will be initialized to 10000. Also see |'swapsync'|. + + *'updatetime'* *'ut'* +updatetime (ut) number (default 4000) + global + {not in Vi} + If this many milliseconds nothing is typed the swap file will be + written to disk (see chapter on recovery |crash_recovery|). + + *'viminfo'* *'vi'* +viminfo (vi) string (default "") + global + {not in Vi} + {only included when Vim was compiled with VIMINFO + defined, use ":version" to check this} + When non-empty, the viminfo file is read upon startup and written + when exiting Vim (see |viminfo_file|). The string should be a comma + separated list of parameters, each consisting of a single character + identifying the particular parameter, followed by a number or string + which specifies the value of that parameter. If a particular + character is left out, then the default value is used for that + parameter. The following is a list characters and their meanings. + char value + ' Maximum number of previously edited files for which + the marks are remembered. This parameter must always + be included when 'viminfo' is non-empty. + f Whether file marks need to be stored. If zero, file + marks ('0 to '9, 'A to 'Z) are not stored. When not + present or when non-zero, they are all stored. '0 is + used for the current cursor position (when exiting or + when doing ":wviminfo"). + r Removable media. The argument is a string (up to the + next ','). This parameter can be given several times. + Each specifies the start of a path for which no marks + will be stored. This is to avoid removable media. + For MS-DOS you could use "ra:,rb:", for Amiga + "rdf0:,rdf1:,rdf2:". Case is ignored. Maximum length + is 50 characters. + " Maximum number of lines saved for each register. If + zero then registers are not saved. If no number given + all lines are saved. Dont forget to put a backslash + before the ", otherwise it will be recognized as the + start of a comment! + : Maximum number of items in the command line history to + be saved. Default: value of 'history'. + / Maximum number of items in the search pattern history + to be saved. If non-zero, then the previous search + and substitute patterns are also saved. Default: + value of 'history'. + Example: + :set viminfo='50,\"1000,:0 + means that marks will be remembered for the last 50 files you edited, + contents of registers (up to 1000 lines each) will be remembered, + command line history will not be saved, and since '/' is not + specified, the default will be used, that is, save all of the search + history, and also the previous search and substitute patterns. + + *'visualbell'* *'vb'* *'novisualbell'* *'novb'* *beep* +visualbell (vb) toggle (default off) + global + {not in Vi} + Use visual bell instead of beeping. The terminal code to display the + visual bell is given with 't_vb'. When no beep of flash is wanted, + Does not work on the Amiga, you always get a screen flash. use ":set + vb t_vb=". Also see 'errorbells'. + + *'warn'* *'nowarn'* +warn toggle (default on) + global + Give a warning message when a shell command is used while the buffer + has been changed. + + *'weirdinvert'* *'wiv'* *'noweirdinvert'* *'nowiv'* +weirdinvert (wiv) toggle (default off) + global + {not in Vi} + Set this option for terminals that have a weird inversion method. + Makes the start/end invert code outputted before every character. + Slows down terminal I/O a lot, but it makes Visual mode work. + + *'whichwrap'* *'ww'* +whichwrap (ww) string (default "b,s", "" when compiled with + COMPATIBLE defined) + global + {not in Vi} + Allow specified keys that move the cursor left/right to wrap to the + previous/next line when the cursor is on the first/last character in + the line. Concatenate characters to allow this for these keys: + char key mode + b Normal and Visual + s Normal and Visual + h "h" Normal and Visual + l "l" Normal and Visual + < Normal and Visual + > Normal and Visual + [ Insert and Replace + ] Insert and Replace + For example: + ":set ww=<,>,[,]" + allows wrap only when cursor keys are used. + When the movement keys are used in combination with a delete or change + operator, the newline also counts for a character. This makes "3h" + different from "3dh" when the cursor crosses the end of a line. This + is also true for "x" and "X", because they do the same as "dl" and + "dh". If you use this, you may also want to use the mapping + ":map X" to make backspace delete the character in front of the + cursor. When 'compatible' is set, 'whichwrap' is set to "". + + *'wildchar'* *'wc'* +wildchar (wc) number (default , CTRL-E when compiled with + COMPATIBLE defined) + global + {not in Vi} + Character you have to type to start wildcard expansion in the + command line. CTRL-E is used when the 'compatible' option is set. + The character is not recognized when used inside a macro. Although + 'wc' is a number option, you can set it to a special key: ":set + wc=". + + *'winheight'* *'wh'* +winheight (wh) number (default 0) + global + {not in Vi} + Minimal number of lines for the current window. If the current + window is smaller, its size is increased, at the cost of the height + of other windows. Set it to 999 to make the current window always + fill the screen. Set it to a small number for normal editing. The + height is not adjusted after one of the commands to change the + height of the current window. + + *'wrap'* *'nowrap'* +wrap toggle (default on) + local to window + {not in Vi} + When on, lines longer than the width of the window will wrap and + displaying continues on the next line. When off lines will not wrap + and only part of long lines will be displayed. When the cursor is + moved to a part that is not shown, the screen will scroll horizontally + (also see 'sidescroll' option and |wrap_off|). If you want to break + long lines, see 'textwidth'. + + *'wrapmargin'* *'wm'* +wrapmargin (wm) number (default 0) + local to buffer + Number of characters from the right window border where wrapping + starts. When typing text beyond this limit, a newline will be + inserted and inserting continues on the next line. When 'textwidth' + is non-zero, this option is not used. See also 'formatoptions' and + |ins_textwidth|. {Vi: works differently and less useful} + + *'wrapscan'* *'ws'* *'nowrapscan'* *'nows'* +wrapscan (ws) toggle (default on) + global + Searches wrap around the end of the file. + + *'writeany'* *'wa'* *'nowriteany'* *'nowa'* +writeany (wa) toggle (default off) + global + Allows writing to any file with no need for "!" override. + + *'writebackup'* *'wb'* *'nowritebackup'* *'nowb'* +writebackup (wb) toggle (default on, off when compiled with WRITEBACKUP + not defined or COMPATIBLE defined) + global + {not in Vi} + Make a backup before overwriting a file. The backup is removed after + the file was successfully written, unless the 'backup' option is + also on. Reset this option if your file system is almost full. See + the table in section 5.4 for another explanation |backup_table|. + + *'writedelay'* *'wd'* +writedelay (wd) number (default 0) + global + {not in Vi} + The number of microseconds to wait for each character sent to the + screen. When non-zero, characters are sent to the terminal one by + one. For MS-DOS pcterm this does not work. For debugging purposes. + + +20. Terminal information *terminal_info* +======================== + +Vim uses information about the terminal you are using to fill the screen and +recognize what keys you hit. If this information is not correct the screen +may be messed up or keys may not be recognized. The actions which have to be +performed on the screen are accomplished by outputting a string of +characters. Special keys produce a string of characters. These strings are +stored in the terminal options, see section 20.2 |terminal_options|. + + +20.1 Startup *startup_terminal* + +When Vim is started a default terminal type is assumed. For the Amiga this is +a standard CLI window, for MS-DOS the pc terminal, for Unix an ansi terminal. +A few other terminal types are always available, see below |builtin_terms|. + +You can give the terminal name with the '-T' Vim argument. If it is not given +Vim will try to get the name from the TERM environment variable. + + *termcap* *terminfo* +On Unix the terminfo database or termcap file is used. This is referred to as +"termcap" in all the documentation. At compile time, when running configure, +the choice whether to use terminfo or termcap is done automatically. When +running Vim the output of ":version" will show "+terminfo" if terminfo is +used. If terminfo is not used "-terminfo" is shown. + +On non-Unix systems a termcap is only available if Vim was compiled with +TERMCAP defined. + + *builtin_terms* +Which builtin terminals are available depends on a few defines in feature.h, +which needs to be set at compile time: + define output of ":version" terminals builtin +NO_BUILTIN_TCAPS -builtin_terms none +SOME_BUILTIN_TCAPS +builtin_terms most common ones (default) +ALL_BUILTIN_TCAPS ++builtin_terms all available + +You can see a list of available builtin terminals with ":set term=xxx". + +If the termcap code is included Vim will try to get the strings for the +terminal you are using from the termcap file and the builtin termcaps. Both +are always used, if an entry for the terminal you are using is present. Which +one is used first depends on the 'ttybuiltin' option: + +'ttybuiltin' on 1: builtin termcap 2: external termcap +'ttybuiltin' off 1: external termcap 2: builtin termcap + +If an option is missing in one of them, it will be obtained from the other +one. If an option is present in both, the one first encountered is used. + +Which external termcap file is used varies from system to system and may +depend on the environment variables "TERMCAP" and "TERMPATH". See "man +tgetent". + +For normal editing the terminal will be put into "raw" mode. The strings +defined with 't_ti' and 't_ks' will be sent to the terminal. Normally this +puts the terminal in a state where the termcap codes are valid and activates +the cursor and function keys. When Vim exits the terminal will be put back +into the mode it was before Vim started. The strings defined with 't_te' and +'t_ke' will be sent to the terminal. On the Amiga with commands that execute +an external command (e.g., "!!") the terminal will be put into Normal mode for +a moment. This means that you can stop the output to the screen by hitting a +printing key. Output resumes when you hit . + +Some termcap entries are wrong in the sense that after sending 't_ks' the +cursor keys send codes different from the codes defined in the termcap. To +avoid this you can set 't_ks' (and 't_ke') to empty strings. This must be +done during initialization (see 3.4 |initialization|), otherwise its too late. + +Some termcap entries assume that the highest bit is always reset. For +example: The cursor-up entry for the amiga could be ":ku=\EA:". But the Amiga +really sends "\233A". This works fine if the highest bit is reset, e.g., when +using an Amiga over a serial line. If the cursor keys don't work, try the +entry ":ku=\233A:". + +Some termcap entries have the entry ":ku=\E[A:". But the Amiga really sends +"\233A". On output "\E[" and "\233" are often equivalent, on input they +aren't. You will have to change the termcap entry, or change the key code with +the :set command to fix this. + +Many cursor key codes start with an . Vim must find out if this a single +hit of the key or the start of a cursor key sequence. It waits for a +next character to arrive. If it does not arrive within one second a single + is assumed. On very slow systems this may fail, causing cursor keys not +to work sometimes. If you discover this problem reset the 'timeout' option. +Vim will wait for the next character to arrive after an . If you want to +enter a single you must type it twice. Resetting the 'esckeys' option +avoids this problems in Insert mode, but you lose the possibility to use +cursor and function keys in Insert mode. + +On the Amiga the recognition of window resizing is activated only when the +terminal name is "amiga" or "builtin_amiga". + +Some terminals have confusing codes for the cursor keys. The televideo 925 is +such a terminal. It sends a CTRL-H for cursor-left. This would make it +impossible to distinguish a backspace and cursor-left. To avoid this problem +CTRL-H is never recognized as cursor-left. + + *vt100_cursor_keys* *xterm_cursor_keys* +Other terminals (e.g., vt100 and xterm) have cursor keys that send OA, +OB, etc. Unfortunately these are valid commands in insert mode: Stop +insert, Open a new line above the new one, start inserting 'A', 'B', etc. +Instead of performing these commands Vim will recognize this key sequence as a +cursor key movement. To avoid this you could use these settings: + :set notimeout " don't timeout on mappings + :set ttimeout " do timeout on terminal key codes + :set timeoutlen=100 " timemout in 100 msec +This requires the keys to be hit withing 100msec. When you type you normally +are not that fast. The cursor key codes arrive within 100 msec, so they are +still recognized. + +The default termcap entry for xterm on sun and other platforms does not +contain the entry for scroll regions. Add ":cs=\E[%i%d;%dr:" to the xterm +entry in /etc/termcap and everything should work. + + +20.2 Terminal options *terminal_options* + +The terminal options can be set just like normal options. But they are not +shown with the ":set all" command. Instead use ":set termcap". + +It is always possible to change individual strings by setting the +appropriate option. For example: + + :set t_ce=^V^[[K (CTRL-V, , [, K) + +{Vi: no terminal options. You have to exit Vi, edit the termcap entry and +try again} + +The options are listed below. The associated termcap code is always equal to +the last two characters of the option name. Two termcap codes are required: +Cursor positioning and clear screen. + +OUTPUT CODES + option meaning + + t_AL add number of blank lines *t_AL* *'t_AL'* + t_al add new blank line *t_al* *'t_al'* + t_cd clear to end of screen *t_cd* *'t_cd'* + t_ce clear to end of line *t_ce* *'t_ce'* + t_cl clear screen (required!) *t_cl* *'t_cl'* + t_cm cursor motion (required!) *t_cm* *'t_cm'* + t_CS if non-empty, cursor relative to scroll region *t_CS* *'t_CS'* + t_cs define scrolling region *t_cs* *'t_cs'* + t_da if non-empty, lines from above scroll down *t_da* *'t_da'* + t_db if non-empty, lines from below scroll up *t_db* *'t_db'* + t_DL delete number of lines *t_DL* *'t_DL'* + t_dl delete line *t_dl* *'t_dl'* + t_ke out of "keypad transmit" mode *t_ke* *'t_ke'* + t_ks put terminal in "keypad transmit" mode *t_ks* *'t_ks'* + t_md bold mode *t_md* *'t_md'* + t_me Normal mode (undoes t_mr and t_md) *t_me* *'t_me'* + t_mr reverse (invert) mode *t_mr* *'t_mr'* + *t_ms* *'t_ms'* + t_ms if non-empty, cursor can be moved in standout/inverse mode + t_RI cursor number of chars right *t_RI* *'t_RI'* + t_se standout end *t_se* *'t_se'* + t_so standout mode *t_so* *'t_so'* + t_sr scroll reverse (backward) *t_sr* *'t_sr'* + t_te out of "termcap" mode *t_te* *'t_te'* + t_ti put terminal in "termcap" mode *t_ti* *'t_ti'* + t_ue underline end *t_ue* *'t_ue'* + t_us underline mode *t_us* *'t_us'* + t_vb visual bell *t_vb* *'t_vb'* + t_ve cursor visible *t_ve* *'t_ve'* + t_vi cursor invisible *t_vi* *'t_vi'* + t_vs cursor very visible *t_vs* *'t_vs'* + t_ZH italics mode *t_ZH* *'t_ZH'* + t_ZR italics end *t_ZR* *'t_ZR'* + +KEY CODES +Note: Use the <> form if possible + + option name meaning + + t_ku arrow up *t_ku* *'t_ku'* + t_kd arrow down *t_kd* *'t_kd'* + t_kr arrow right *t_kr* *'t_kr'* + t_kl arrow left *t_kl* *'t_kl'* + shift arrow up + shift arrow down + t_%i shift arrow right *t_%i* *'t_%i'* + t_#4 shift arrow left *t_#4* *'t_#4'* + t_k1 function key 1 *t_k1* *'t_k1'* + t_k2 function key 2 *t_k2* *'t_k2'* + t_k3 function key 3 *t_k3* *'t_k3'* + t_k4 function key 4 *t_k4* *'t_k4'* + t_k5 function key 5 *t_k5* *'t_k5'* + t_k6 function key 6 *t_k6* *'t_k6'* + t_k7 function key 7 *t_k7* *'t_k7'* + t_k8 function key 8 *t_k8* *'t_k8'* + t_k9 function key 9 *t_k9* *'t_k9'* + t_k; function key 10 *t_k;* *'t_k;'* + t_F1 function key 11 *t_F1* *'t_F1'* + t_F2 function key 12 *t_F2* *'t_F2'* + shifted function key 2 + shifted function key 3 + shifted function key 4 + shifted function key 5 + shifted function key 6 + shifted function key 7 + shifted function key 8 + shifted function key 9 + shifted function key 10 + shifted function key 11 + shifted function key 12 + t_%1 help key *t_%1* *'t_%1'* + t_&8 undo key *t_&8* *'t_&8'* + t_kI insert key *t_kI* *'t_kI'* + t_kD delete key *t_kD* *'t_kD'* + t_kb backspace key *t_kb* *'t_kb'* + t_kh home key *t_kh* *'t_kh'* + t_@7 end key *t_@7* *'t_@7'* + t_kP page-up key *t_kP* *'t_kP'* + t_kN page-down key *t_kN* *'t_kN'* + +Note about t_so and t_mr: When the termcap entry "so" is not present the +entry for "mr" is used. And vice versa. The same is done for "se" and "me". +If your terminal supports both inversion and standout mode, you can see two +different modes. If you terminal supports only one of the modes, both will +look the same. + +If inversion or other highlighting does not work correctly, try setting the +'weirdinvert' option. This makes the start-highlight or end-highlight termcap +code to be outputted before every character. This slows down terminal I/O a +lot, but it makes inversion work on some terminals. + +Some termcaps do not include an entry for 'cs' (scroll region), although the +terminal does support it. For example: xterm on a sun. You can use the +builtin_xterm or define t_cs yourself. For example: + + :set t_cs=^V^[[%i%d;%dr + +Where ^V is CTRL-V and ^[ is . + +Unfortunately it is not possible to deduct from the termcap how cursor +positioning should be done when using a scrolling region: Relative to the +beginning of the screen or relative to the beginning of the scrolling region. +Most terminals use the first method. A known exception is the MS-DOS console +(pcterm). The 't_CS' option should be set to any string when cursor +positioning is relative to the start of the scrolling region. It should be +set to an empty string otherwise. It is default "yes" when 'term' is +"pcterm". + +Note for xterm users: The shifted cursor keys normally don't work. You can + make them work with the xmodmap command and some mappings in Vim. + + Give these commands in the xterm: + xmodmap -e "keysym Up = Up F13" + xmodmap -e "keysym Down = Down F16" + xmodmap -e "keysym Left = Left F18" + xmodmap -e "keysym Right = Right F19" + + And use these mappings in Vim: + :map + :map! + :map + :map! + :map + :map! + :map + :map! + +Instead of, say, you can use any other command that you want to use the +shift-cursor-up key for. (Note: To help people that have a Sun keyboard with +left side keys F14 is not used because it is confused with the undo key; F15 +is not used, because it does a window-to-front; F17 is not used, because it +closes the window. On other systems you can probably use them) + + +20.3 Window size *window_size* + +[This is about the size of the whole window Vim is using, not a window that is +created with the :split command] + +If you are running Vim on an Amiga and the terminal name is "amiga" or +"builtin_amiga", the amiga-specific window resizing will be enabled. On Unix +systems three methods are tried to get the window size: + +- an ioctl call (TIOCGSIZE or TIOCGWINSZ, depends on your system) +- the environment variables "LINES" and "COLUMNS" +- from the termcap entries "li" and "co" + +If everything fails a default size of 24 lines and 80 columns is assumed. If +a window-resize signal is received the size will be set again. If the window +size is wrong you can use the 'lines' and 'columns' options to set the +correct values. + +One command can be used to set the screen size: + + *:mod* *:mode* +:mod[e] [mode] + +Without argument this only detects the screen size. With MS-DOS it is +possible to switch screen mode. [mode] can be one of these values: + "bw40" 40 columns black&white + "c40" 40 columns color + "bw80" 80 columns black&white + "c80" 80 columns color (most people use this) + "mono" 80 columns monochrome + "c4350" 43 or 50 lines EGA/VGA mode + number mode number to use, depends on your video card + + +20.4 Slow and fast terminals *slow_fast_terminal* + *slow_terminal* + +If you have a fast terminal you may like to set the 'ruler' option. The +cursor position is shown in the status line. If you are using horizontal +scrolling ('wrap' option off) consider setting 'sidescroll' to a small +number. + +If you have a slow terminal you may want to reset the 'showcmd' option. +The command characters will not be shown in the status line. If the terminal +scrolls very slowly, set the 'scrolljump' to 5 or so. If the cursor is moved +off the screen (e.g., with "j") Vim will scroll 5 lines at a time. Another +possibility is to reduce the number of lines that Vim uses with the command +"z". + +If the characters from the terminal are arriving with more than 1 second +between them you might want to set the 'timeout' and/or 'ttimeout' option. +See the "Options" chapter |options|. + +If your terminal does not support a scrolling region, but it does support +insert/delete line commands, scrolling with multiple windows may make the +lines jump up and down. If you don't want this set the 'ttyfast' option. +This will redraw the window instead of scroll it. + +If your terminal scrolls very slowly, but redrawing is not slow, set the +'ttyscroll' option to a small number, e.g., 3. This will make Vim redraw the +screen instead of scrolling, when there are more than 3 lines to be scrolled. + +If you are using Vim over a slow serial line, you might want to try running +Vim inside the "screen" program. Screen will optimize the terminal I/O quite +a bit. + +If you are testing termcap options, but you cannot see what is happening, +you might want to set the 'writedelay' option. When non-zero, one character +is sent to the terminal at a time (does not work for MS-DOS). This makes the +screen updating a lot slower, making it possible to see what is happening. + + *hpterm* +When you are using an hpterm you probably run into a few problems. The best +thing to do is to use an xterm instead. If you want to use an hpterm for some +reason, try (re)setting some options: + :set t_sr= + :set t_al= + :set t_dl= + :set ttyfast redraw screen instead of scrolling + + :set weirdinvert makes highlighting work better, but + slows down screen updating a lot + + +21. Differences from Vi and Ex *vi_differences* +============================== + +Throughout this document differences between Vim and Vi/Ex are given in +curly braces. This chapter only lists what has not been mentioned in +previous chapters. Also see |vim_diff.txt| for an overview. + + +21.1 Missing commands *missing_commands* + +A large number of the "Ex" commands (the commands that start with a colon) +are included. However, there is no Ex mode. + +These commands are in Vi, but not in Vim. + +Q {Vi: go to Ex mode} See |pseudo-Q|. + +:a[ppend] {Vi: append text} *:a* *:append* +:c[hange] {Vi: replace lines} *:c* *:change* +:i[nsert] {Vi: insert text} *:i* *:insert* +:o[pen] {Vi: start editing in open mode}*:o* *:open* +:z {Vi: print some lines} *:z* + + +21.2 Missing options *missing_options* + +These options are in the Unix Vi, but not in Vim. If you try to set one of +them you won't get an error message, but the value is not used and cannot be +printed. + +autoprint (ap) toggle (default on) *'autoprint'* *'ap'* +beautify (bf) toggle (default off) *'beautify'* *'bf'* +flash (fl) toggle (default ??) *'flash'* *'fl'* +graphic (gr) toggle (default off) *'graphic'* *'gr'* +hardtabs (ht) number (default 8) *'hardtabs'* *'ht'* + number of spaces that a moves on the display +mesg toggle (default on) *'mesg'* +novice toggle (default ??) *'novice'* +open toggle (default on) *'open'* +optimize (op) toggle (default off) *'optimize'* *'op'* +prompt toggle (default on) *'prompt'* +redraw toggle (default off) *'redraw'* +slowopen (slow) toggle (default off) *'slowopen'* *'slow'* +sourceany toggle (default off) *'sourceany'* +tagstack (tgst) toggle (default on) *'tagstack'* *'tgst'* + enables the tagstack and ":pop". +window (wi) number (default 23) *'window'* *'wi'* +w300 number (default 23) *'w300'* +w1200 number (default 23) *'w1200'* +w9600 number (default 23) *'w9600'* + + +21.3 Limits *limits* + +Vim has only a few limits for the files that can be edited {Vi: can not handle + characters and characters above 128, has limited line length, many other +limits}. + +Maximum line length On machines with 16-bit ints (Amiga and MS-DOS real + mode): 32767, otherwise 2147483647 characters. + Longer lines are split. +Maximum number of lines 2147483647 lines. +Maximum file size Only limited by available disk space for the swap + file. +Length of a file name Unix and Win32: 1024 characters, otherwise 128 + characters. +Maximum display width Unix and Win32: 1024 characters, otherwise 255 + characters + +Information for undo and registers are kept in memory, thus when making (big) +changes the amount of (virtual) memory available limits the number of undo +levels and the text that can be kept in registers. Other things are also kept +in memory: Command line history, error messages for Quickfix mode, etc. + + +CONTENTS *reference_contents* *ref* *reference* + +[Note: The commands for multiple windows and buffers are explained in +a different file, see |vim_win.txt|] + + 1. Introduction |intro| + 2. Notation |notation| + 3. Starting Vim |starting| + 3.1 Vim arguments |vim_arguments| + 3.2 Workbench (Amiga only) |workbench| + 3.3 Vim window (Amiga only) |amiga_window| + 3.4 Initialization |initialization| + 3.5 Suspending |suspend| + 3.6 The viminfo file |viminfo_file| + 4. Modes |vim_modes| + 4.1 Introduction + 4.2 Switching from mode to mode |mode_switching| + 4.3 Insert and Replace mode |mode_ins_repl| + 4.3.1 special keys |ins_special_keys| + 4.3.2 special special keys |ins_special_special| + 4.3.3 'textwidth' option |ins_textwidth| + 4.3.4 'expandtab' option |ins_expandtab| + 4.3.5 Replace mode |replace_mode| + 4.3.6 Insert mode completion |ins_completion| + 4.4 Command-line mode |mode_cmdline| + 4.4.1 Command line editing |cmdline_editing| + 4.4.2 Command line completion |cmdline_completion| + 4.4.3 Ex command lines |cmdline_lines| + 4.4.4 Ex command line ranges |cmdline_ranges| + 4.4.5 Ex special characters |cmdline_special| + 4.5 The window contents |window_contents| + 4.6 Abbreviations |abbreviations| + 4.7 Digraphs |digraphs| + 4.8 Using the mouse |mouse_using| + 4.9 Online help |online_help| + 5. Editing files |edit_files| + 5.1 Introduction |edit_intro| + 5.2 Editing a file |edit_a_file| + 5.3 The argument list |argument_list| + 5.4 Writing and quitting |write_quit| + 5.5 Using the QuickFix mode |quickfix| + 5.6 Editing binary files |edit_binary| + 5.7 Automatic commands |autocommand| + 6. Cursor motions |cursor_motions| + 6.1 Left-right motions |left_right_motions| + 6.2 Up-down motions |up_down_motions| + 6.3 Word motions |word_motions| + 6.4 Text object motions |object_motions| + 6.5 Text object selection |object_select| + 6.6 Pattern searches |pattern_searches| + 6.7 Various motions |various_motions| + 7. Scrolling |scrolling| + 8. Tags and special searches |tags_and_searches| + 8.1 Tags |tag_commands| + 8.2 Identifier searches |include_search| + 9. Inserting text |inserting| +10. Deleting text |deleting| +11. Changing text |changing| + 11.1 Delete and insert |delete_insert| + 11.2 Simple changes |simple_change| + 11.3 Complex changes |complex_change| + 11.4 Formatting text |formatting| + 11.5 Formatting C programs |C_indenting| +12. Copying and moving text |copy_move| +13. Visual mode |Visual_mode| +14. Various commands |various| +15. Repeating commands |repeating| + 15.1 Single repeats |single_repeat| + 15.2 Multiple repeats |multi_repeat| + 15.3 Complex repeats |complex_repeat| +16. Undo and redo |undo_redo| +17. Key mapping |key_mapping| +18. Recovery after a crash |crash_recovery| + 18.1 The swap file |swap_file| + 18.2 Recovery |recovery| +19. Options |options| + 19.1 Setting options |set_option| + 19.2 Automatically setting options |auto_setting| + 19.3 Saving settings |save_settings| + 19.4 Options summary |option_summary| +20. Terminal information |terminal_info| + 20.1 Startup |startup| + 20.2 Terminal options |terminal_options| + 20.3 Window size |window_size| + 20.4 Slow and fast terminals |slow_fast_terminal| +21. Differences from Vi and Ex |vi_differences| + 21.1 Missing commands |missing_commands| + 21.2 Missing options |missing_options| + 21.3 Limits |limits| + + vim:tw=78:ts=8:sw=8: diff --git a/usr.bin/vim/doc/vim_rlh.txt b/usr.bin/vim/doc/vim_rlh.txt new file mode 100644 index 00000000000..fd667d043aa --- /dev/null +++ b/usr.bin/vim/doc/vim_rlh.txt @@ -0,0 +1,173 @@ +*vim_rlh.txt* For Vim version 4.2. Last modification: 1996 June 11 + +Right to Left and Hebrew Mapping for Vim *hebrew* +======================================== + +These functions have been made by Avner Lottem +E-mail: lottem@tx.technion.ac.il +Phone: +972-4-8307322 + + +Introduction +------------ +In order to use right-to-left and Hebrew mapping support, it is necessary to +compile Vim with RIGHTLEFT. In right-to-left oriented files the characters +appear on the screen from right to left. This kind of file is most useful +when writing Hebrew documents using TeX--XeT, troffh, composing faxes or +writing Hebrew memos. + +Logical order files, where direction is encoded for every character (or group +of characters) are not supported as this kind of support is out of the scope +of a simple addition to an existing editor. Also, no Hebrew commands, prompts +and help files were added, the standard Vi interface was maintained. The +intension here was to incorporate Hebrew support to an existing modern and +live editor, hoping that the Hebrew support will continue to live in +subsequent versions. Many other Hebrew supported packages were designed for a +particular version of the original (English) software and when it continued +developing, the Hebrew version stayed behind. Therefore this particular +support to Vim tries to be as simple (and short) as possible, so that it could +be incorporated into the official source. + + +Highlights +---------- +o Editing left-to-right files as in the original Vim, no change. + +o Viewing and editing files in right-to-left windows. File orientation + is per window, so it is possible to view the same file in right-to-left + and left-to-right modes, simultaneously. + (This is sometimes useful when editing documents with TeX--XeT.) + +o Compatibility to the original Vim. Almost all features work in + right-to-left mode (see Bugs below). + +o Changing keyboard mapping and reverse insert modes using a single + command. + +o Backing from reverse insert mode to the correct place in the file + (if possible). + +o No special terminal with right-to-left capabilities is required. The + right-to-left changes are completely hardware independent. Only + Hebrew font is necessary. + + The MIT X distribution includes at least two fonts: heb6x13 and heb8x13. + Some fonts are on sunsite.unc.edu: /pub/Linux/X11/fonts/hebxfonts-0.2.tgz. + More pointers are in the Hebrew HOWTO on sunsite: /pub/Linux/docs/HOWTO. + To create a Hebrew font for a DOS box under MS-Windows, refer to a hack on + tochnapc2.technion.ac.il: /pub/lottem/doswin-0.1.tgz. + +o ALL enhancements depend on a compilation variable, RIGHTLEFT, + so it is possible to compile Vim without this support. + +o It should be quite easy to adjust this support to handle other right-to + left languages, such as Arabic, by simply changing the keyboard mapping + according to the character encoding. + + +Change details +-------------- ++ Variables: + + rightleft (rl) sets window orientation to right-to-left. + + hkmap (hk) sets keyboard mapping to Hebrew, in insert/replace modes. + + aleph (al), numeric, holds the decimal code of Aleph, for keyboard + mapping. + ++ Encoding: + + Under Unix, ISO 8859-8 encoding (Hebrew letters codes: 224-250). + + Under MS DOS, PC encoding (Hebrew letters codes: 128-154). + These are defaults, that can be overided using the aleph variable. + ++ Vim arguments: + + 'vim -H file' starts editing a Hebrew file, i.e. rightleft and hkmap + are set. + ++ Keyboard: + + CTRL-_ in insert/replace modes toggles revins and hkmap as follows: + + When in rightleft window, revins+nohkmap are toggled, since English + will likely be inserted in this case. + + When in norightleft window, revins+hkmap are toggled, since Hebrew + will likely be inserted in this case. + + CTRL-_ moves the cursor to the end of the typed text, unlike CTRL-B + that leaves the cursor in the same place. + + + CTRL-_ in command mode only toggles keyboard mapping (see Bugs below). + This setting is independent of hkmap variable, which only applies to + insert/replace mode. + + Note: On some keyboards, CTRL-_ is mapped to CTRL-?. + + + Keyboard mapping while hkmap is set: + + q w e r t y u i o p The characters in the mapping shown + / ' ÷ ø à è å ï í ô here are for ISO 8859-8. + + a s d f g h j k l ; ' + ù ã â ë ò é ç ì ê ó , + + z x c v b n m , . / + æ ñ á ä ð î ö ú õ . + + +Bugs +---- +o Does not handle CTRL-A and CTRL-X commands (add and subtract) + correctly when in rightleft window. + +o Does not support reverse insert and rightleft modes on the command-line. + However, functionality of the editor is not reduced, because it is + possible to enter mappings, abbreviations and searches with Hebrew text, + typed from the left to the right on the command-line. + +o Somewhat slower in right-to-left mode, because right-to-left motion is + emulated inside Vim, not by the controlling terminal. + +o Does not support 7 bit terminals. Only a terminal with complete + Hebrew English font (without need to send escape sequences to switch + between Hebrew and English) is supported. + +o When the Athena GUI is used, the bottom scrollbar works in the wrong + direction. This is difficult to fix. + +o When both 'rightleft' and 'revins' are on: 'textwidth' does not work. + Lines do not wrap at all; you just get a single, long line. + + +Typing backwards *ins_reverse* + +[for all this RIGHTLEFT must have been defined in feature.h at compile time] + +If the 'revins' (reverse insert) option is set, inserting happens backwards. +This can be used to type Hebrew. When inserting characters the cursor is not +moved and the text moves rightwards. A deletes the character under the +cursor. CTRL-W and CTRL-U also work in the opposite direction. , CTRL-W +and CTRL-U do not stop at the start of insert or end of line, no matter how +the 'backspace' option is set. + +In Insert mode the 'revins' option can be toggled with CTRL-B. + +There is no reverse replace mode (yet). + +If the 'showmode' option is set, "-- REVERSE INSERT --" will be shown in the +status line when reverse Insert mode is active. + +Reverse Insert mode can be also entered via CTRL-_, which has some extra +functionality: First, keyboard mapping is changed according to the window +orientation -- if in a left-to-right window, revins is used to enter Hebrew +text, so the keyboard changes to Hebrew (hkmap is set); if in a right-to-left +window, revins is used to enter English text, so the keyboard changes to +English (hkmap is reset). Second, when exiting revins via CTRL-_, the cursor +moves to the end of the typed text (if possible). + + +Pasting when in a rightleft window +---------------------------------- + +When cutting text with the mouse and pasting it in a rightleft window +the text will be reversed, because the characters come from the cut buffer +from the left to the right, while inserted in the file from the right to +the left. In order to avoid it, toggle revins (by typing CTRL-? or CTRL-_) +before pasting. diff --git a/usr.bin/vim/doc/vim_tags b/usr.bin/vim/doc/vim_tags new file mode 100644 index 00000000000..acd78b65659 --- /dev/null +++ b/usr.bin/vim/doc/vim_tags @@ -0,0 +1,1932 @@ +! vim_ref.txt /\*!\* +!! vim_ref.txt /\*!!\* +# vim_ref.txt /\*#\* +$ vim_ref.txt /\*$\* +% vim_ref.txt /\*%\* +& vim_ref.txt /\*&\* +' vim_ref.txt /\*'\* +'" vim_ref.txt /\*'"\* +'' vim_ref.txt /\*''\* +'0 vim_ref.txt /\*'0\* +'< vim_ref.txt /\*'<\* +'> vim_ref.txt /\*'>\* +'A vim_ref.txt /\*'A\* +'[ vim_ref.txt /\*'[\* +'] vim_ref.txt /\*']\* +'a vim_ref.txt /\*'a\* +'ai' vim_ref.txt /\*'ai'\* +'al' vim_ref.txt /\*'al'\* +'aleph' vim_ref.txt /\*'aleph'\* +'ap' vim_ref.txt /\*'ap'\* +'autoindent' vim_ref.txt /\*'autoindent'\* +'autoprint' vim_ref.txt /\*'autoprint'\* +'autowrite' vim_ref.txt /\*'autowrite'\* +'aw' vim_ref.txt /\*'aw'\* +'backspace' vim_ref.txt /\*'backspace'\* +'backup' vim_ref.txt /\*'backup'\* +'backupdir' vim_ref.txt /\*'backupdir'\* +'backupext' vim_ref.txt /\*'backupext'\* +'bdir' vim_ref.txt /\*'bdir'\* +'beautify' vim_ref.txt /\*'beautify'\* +'bex' vim_ref.txt /\*'bex'\* +'bf' vim_ref.txt /\*'bf'\* +'bin' vim_ref.txt /\*'bin'\* +'binary' vim_ref.txt /\*'binary'\* +'biosk' vim_ref.txt /\*'biosk'\* +'bioskey' vim_ref.txt /\*'bioskey'\* +'bk' vim_ref.txt /\*'bk'\* +'breakat' vim_ref.txt /\*'breakat'\* +'brk' vim_ref.txt /\*'brk'\* +'bs' vim_ref.txt /\*'bs'\* +'ch' vim_ref.txt /\*'ch'\* +'cin' vim_ref.txt /\*'cin'\* +'cindent' vim_ref.txt /\*'cindent'\* +'cink' vim_ref.txt /\*'cink'\* +'cinkeys' vim_ref.txt /\*'cinkeys'\* +'cino' vim_ref.txt /\*'cino'\* +'cinoptions' vim_ref.txt /\*'cinoptions'\* +'cinw' vim_ref.txt /\*'cinw'\* +'cinwords' vim_ref.txt /\*'cinwords'\* +'cmdheight' vim_ref.txt /\*'cmdheight'\* +'co' vim_ref.txt /\*'co'\* +'columns' vim_ref.txt /\*'columns'\* +'com' vim_ref.txt /\*'com'\* +'comments' vim_ref.txt /\*'comments'\* +'compatible' vim_ref.txt /\*'compatible'\* +'cp' vim_ref.txt /\*'cp'\* +'cpo' vim_ref.txt /\*'cpo'\* +'cpoptions' vim_ref.txt /\*'cpoptions'\* +'def' vim_ref.txt /\*'def'\* +'define' vim_ref.txt /\*'define'\* +'dg' vim_ref.txt /\*'dg'\* +'dict' vim_ref.txt /\*'dict'\* +'dictionary' vim_ref.txt /\*'dictionary'\* +'digraph' vim_ref.txt /\*'digraph'\* +'dir' vim_ref.txt /\*'dir'\* +'directory' vim_ref.txt /\*'directory'\* +'ea' vim_ref.txt /\*'ea'\* +'eb' vim_ref.txt /\*'eb'\* +'ed' vim_ref.txt /\*'ed'\* +'edcompatible' vim_ref.txt /\*'edcompatible'\* +'ef' vim_ref.txt /\*'ef'\* +'efm' vim_ref.txt /\*'efm'\* +'ek' vim_ref.txt /\*'ek'\* +'endofline' vim_ref.txt /\*'endofline'\* +'eol' vim_ref.txt /\*'eol'\* +'ep' vim_ref.txt /\*'ep'\* +'equalalways' vim_ref.txt /\*'equalalways'\* +'equalprg' vim_ref.txt /\*'equalprg'\* +'errorbells' vim_ref.txt /\*'errorbells'\* +'errorfile' vim_ref.txt /\*'errorfile'\* +'errorformat' vim_ref.txt /\*'errorformat'\* +'esckeys' vim_ref.txt /\*'esckeys'\* +'et' vim_ref.txt /\*'et'\* +'expandtab' vim_ref.txt /\*'expandtab'\* +'exrc' vim_ref.txt /\*'exrc'\* +'fl' vim_ref.txt /\*'fl'\* +'flash' vim_ref.txt /\*'flash'\* +'fo' vim_ref.txt /\*'fo'\* +'formatoptions' vim_ref.txt /\*'formatoptions'\* +'formatprg' vim_ref.txt /\*'formatprg'\* +'fp' vim_ref.txt /\*'fp'\* +'gd' vim_ref.txt /\*'gd'\* +'gdefault' vim_ref.txt /\*'gdefault'\* +'gfn' vim_ref.txt /\*'gfn'\* +'go' vim_ref.txt /\*'go'\* +'gr' vim_ref.txt /\*'gr'\* +'graphic' vim_ref.txt /\*'graphic'\* +'guifont' vim_ref.txt /\*'guifont'\* +'guioptions' vim_ref.txt /\*'guioptions'\* +'guipty' vim_ref.txt /\*'guipty'\* +'hardtabs' vim_ref.txt /\*'hardtabs'\* +'helpfile' vim_ref.txt /\*'helpfile'\* +'helpheight' vim_ref.txt /\*'helpheight'\* +'hf' vim_ref.txt /\*'hf'\* +'hh' vim_ref.txt /\*'hh'\* +'hi' vim_ref.txt /\*'hi'\* +'hid' vim_ref.txt /\*'hid'\* +'hidden' vim_ref.txt /\*'hidden'\* +'highlight' vim_ref.txt /\*'highlight'\* +'history' vim_ref.txt /\*'history'\* +'hk' vim_ref.txt /\*'hk'\* +'hkmap' vim_ref.txt /\*'hkmap'\* +'hl' vim_ref.txt /\*'hl'\* +'ht' vim_ref.txt /\*'ht'\* +'ic' vim_ref.txt /\*'ic'\* +'icon' vim_ref.txt /\*'icon'\* +'ignorecase' vim_ref.txt /\*'ignorecase'\* +'im' vim_ref.txt /\*'im'\* +'inc' vim_ref.txt /\*'inc'\* +'include' vim_ref.txt /\*'include'\* +'incsearch' vim_ref.txt /\*'incsearch'\* +'inf' vim_ref.txt /\*'inf'\* +'infercase' vim_ref.txt /\*'infercase'\* +'insertmode' vim_ref.txt /\*'insertmode'\* +'is' vim_ref.txt /\*'is'\* +'isf' vim_ref.txt /\*'isf'\* +'isfname' vim_ref.txt /\*'isfname'\* +'isi' vim_ref.txt /\*'isi'\* +'isident' vim_ref.txt /\*'isident'\* +'isk' vim_ref.txt /\*'isk'\* +'iskeyword' vim_ref.txt /\*'iskeyword'\* +'isp' vim_ref.txt /\*'isp'\* +'isprint' vim_ref.txt /\*'isprint'\* +'joinspaces' vim_ref.txt /\*'joinspaces'\* +'js' vim_ref.txt /\*'js'\* +'keywordprg' vim_ref.txt /\*'keywordprg'\* +'kp' vim_ref.txt /\*'kp'\* +'langmap' vim_ref.txt /\*'langmap'\* +'laststatus' vim_ref.txt /\*'laststatus'\* +'lbr' vim_ref.txt /\*'lbr'\* +'linebreak' vim_ref.txt /\*'linebreak'\* +'lines' vim_ref.txt /\*'lines'\* +'lisp' vim_ref.txt /\*'lisp'\* +'list' vim_ref.txt /\*'list'\* +'lmap' vim_ref.txt /\*'lmap'\* +'ls' vim_ref.txt /\*'ls'\* +'magic' vim_ref.txt /\*'magic'\* +'makeprg' vim_ref.txt /\*'makeprg'\* +'maxmapdepth' vim_ref.txt /\*'maxmapdepth'\* +'maxmem' vim_ref.txt /\*'maxmem'\* +'maxmemtot' vim_ref.txt /\*'maxmemtot'\* +'mesg' vim_ref.txt /\*'mesg'\* +'ml' vim_ref.txt /\*'ml'\* +'mls' vim_ref.txt /\*'mls'\* +'mm' vim_ref.txt /\*'mm'\* +'mmd' vim_ref.txt /\*'mmd'\* +'mmt' vim_ref.txt /\*'mmt'\* +'mod' vim_ref.txt /\*'mod'\* +'modeline' vim_ref.txt /\*'modeline'\* +'modelines' vim_ref.txt /\*'modelines'\* +'modified' vim_ref.txt /\*'modified'\* +'more' vim_ref.txt /\*'more'\* +'mouse' vim_ref.txt /\*'mouse'\* +'mouset' vim_ref.txt /\*'mouset'\* +'mousetime' vim_ref.txt /\*'mousetime'\* +'mp' vim_ref.txt /\*'mp'\* +'noai' vim_ref.txt /\*'noai'\* +'noautoindent' vim_ref.txt /\*'noautoindent'\* +'noautowrite' vim_ref.txt /\*'noautowrite'\* +'noaw' vim_ref.txt /\*'noaw'\* +'nobackup' vim_ref.txt /\*'nobackup'\* +'nobin' vim_ref.txt /\*'nobin'\* +'nobinary' vim_ref.txt /\*'nobinary'\* +'nobiosk' vim_ref.txt /\*'nobiosk'\* +'nobioskey' vim_ref.txt /\*'nobioskey'\* +'nobk' vim_ref.txt /\*'nobk'\* +'nocin' vim_ref.txt /\*'nocin'\* +'nocindent' vim_ref.txt /\*'nocindent'\* +'nocompatible' vim_ref.txt /\*'nocompatible'\* +'nocp' vim_ref.txt /\*'nocp'\* +'nodg' vim_ref.txt /\*'nodg'\* +'nodigraph' vim_ref.txt /\*'nodigraph'\* +'noea' vim_ref.txt /\*'noea'\* +'noeb' vim_ref.txt /\*'noeb'\* +'noed' vim_ref.txt /\*'noed'\* +'noedcompatible' vim_ref.txt /\*'noedcompatible'\* +'noek' vim_ref.txt /\*'noek'\* +'noendofline' vim_ref.txt /\*'noendofline'\* +'noeol' vim_ref.txt /\*'noeol'\* +'noequalalways' vim_ref.txt /\*'noequalalways'\* +'noerrorbells' vim_ref.txt /\*'noerrorbells'\* +'noesckeys' vim_ref.txt /\*'noesckeys'\* +'noet' vim_ref.txt /\*'noet'\* +'noexpandtab' vim_ref.txt /\*'noexpandtab'\* +'noexrc' vim_ref.txt /\*'noexrc'\* +'nogd' vim_ref.txt /\*'nogd'\* +'nogdefault' vim_ref.txt /\*'nogdefault'\* +'noguipty' vim_ref.txt /\*'noguipty'\* +'nohid' vim_ref.txt /\*'nohid'\* +'nohidden' vim_ref.txt /\*'nohidden'\* +'nohk' vim_ref.txt /\*'nohk'\* +'nohkmap' vim_ref.txt /\*'nohkmap'\* +'noic' vim_ref.txt /\*'noic'\* +'noicon' vim_ref.txt /\*'noicon'\* +'noignorecase' vim_ref.txt /\*'noignorecase'\* +'noim' vim_ref.txt /\*'noim'\* +'noincsearch' vim_ref.txt /\*'noincsearch'\* +'noinf' vim_ref.txt /\*'noinf'\* +'noinfercase' vim_ref.txt /\*'noinfercase'\* +'noinsertmode' vim_ref.txt /\*'noinsertmode'\* +'nois' vim_ref.txt /\*'nois'\* +'nojoinspaces' vim_ref.txt /\*'nojoinspaces'\* +'nojs' vim_ref.txt /\*'nojs'\* +'nolbr' vim_ref.txt /\*'nolbr'\* +'nolinebreak' vim_ref.txt /\*'nolinebreak'\* +'nolisp' vim_ref.txt /\*'nolisp'\* +'nolist' vim_ref.txt /\*'nolist'\* +'nomagic' vim_ref.txt /\*'nomagic'\* +'noml' vim_ref.txt /\*'noml'\* +'nomod' vim_ref.txt /\*'nomod'\* +'nomodeline' vim_ref.txt /\*'nomodeline'\* +'nomodified' vim_ref.txt /\*'nomodified'\* +'nomore' vim_ref.txt /\*'nomore'\* +'nonu' vim_ref.txt /\*'nonu'\* +'nonumber' vim_ref.txt /\*'nonumber'\* +'nopaste' vim_ref.txt /\*'nopaste'\* +'noreadonly' vim_ref.txt /\*'noreadonly'\* +'noremap' vim_ref.txt /\*'noremap'\* +'norestorescreen' vim_ref.txt /\*'norestorescreen'\* +'norevins' vim_ref.txt /\*'norevins'\* +'nori' vim_ref.txt /\*'nori'\* +'norightleft' vim_ref.txt /\*'norightleft'\* +'norl' vim_ref.txt /\*'norl'\* +'noro' vim_ref.txt /\*'noro'\* +'nors' vim_ref.txt /\*'nors'\* +'noru' vim_ref.txt /\*'noru'\* +'noruler' vim_ref.txt /\*'noruler'\* +'nosb' vim_ref.txt /\*'nosb'\* +'nosc' vim_ref.txt /\*'nosc'\* +'noscs' vim_ref.txt /\*'noscs'\* +'nosecure' vim_ref.txt /\*'nosecure'\* +'noshiftround' vim_ref.txt /\*'noshiftround'\* +'noshortname' vim_ref.txt /\*'noshortname'\* +'noshowcmd' vim_ref.txt /\*'noshowcmd'\* +'noshowmatch' vim_ref.txt /\*'noshowmatch'\* +'noshowmode' vim_ref.txt /\*'noshowmode'\* +'nosi' vim_ref.txt /\*'nosi'\* +'nosm' vim_ref.txt /\*'nosm'\* +'nosmartcase' vim_ref.txt /\*'nosmartcase'\* +'nosmartindent' vim_ref.txt /\*'nosmartindent'\* +'nosmarttab' vim_ref.txt /\*'nosmarttab'\* +'nosmd' vim_ref.txt /\*'nosmd'\* +'nosn' vim_ref.txt /\*'nosn'\* +'nosol' vim_ref.txt /\*'nosol'\* +'nosplitbelow' vim_ref.txt /\*'nosplitbelow'\* +'nosr' vim_ref.txt /\*'nosr'\* +'nosta' vim_ref.txt /\*'nosta'\* +'nostartofline' vim_ref.txt /\*'nostartofline'\* +'nota' vim_ref.txt /\*'nota'\* +'notagrelative' vim_ref.txt /\*'notagrelative'\* +'notbi' vim_ref.txt /\*'notbi'\* +'noterse' vim_ref.txt /\*'noterse'\* +'notextauto' vim_ref.txt /\*'notextauto'\* +'notextmode' vim_ref.txt /\*'notextmode'\* +'notf' vim_ref.txt /\*'notf'\* +'notildeop' vim_ref.txt /\*'notildeop'\* +'notimeout' vim_ref.txt /\*'notimeout'\* +'notitle' vim_ref.txt /\*'notitle'\* +'noto' vim_ref.txt /\*'noto'\* +'notop' vim_ref.txt /\*'notop'\* +'notr' vim_ref.txt /\*'notr'\* +'nottimeout' vim_ref.txt /\*'nottimeout'\* +'nottybuiltin' vim_ref.txt /\*'nottybuiltin'\* +'nottyfast' vim_ref.txt /\*'nottyfast'\* +'notx' vim_ref.txt /\*'notx'\* +'novb' vim_ref.txt /\*'novb'\* +'novice' vim_ref.txt /\*'novice'\* +'novisualbell' vim_ref.txt /\*'novisualbell'\* +'nowa' vim_ref.txt /\*'nowa'\* +'nowarn' vim_ref.txt /\*'nowarn'\* +'nowb' vim_ref.txt /\*'nowb'\* +'noweirdinvert' vim_ref.txt /\*'noweirdinvert'\* +'nowiv' vim_ref.txt /\*'nowiv'\* +'nowrap' vim_ref.txt /\*'nowrap'\* +'nowrapscan' vim_ref.txt /\*'nowrapscan'\* +'nowriteany' vim_ref.txt /\*'nowriteany'\* +'nowritebackup' vim_ref.txt /\*'nowritebackup'\* +'nows' vim_ref.txt /\*'nows'\* +'nu' vim_ref.txt /\*'nu'\* +'number' vim_ref.txt /\*'number'\* +'op' vim_ref.txt /\*'op'\* +'open' vim_ref.txt /\*'open'\* +'optimize' vim_ref.txt /\*'optimize'\* +'pa' vim_ref.txt /\*'pa'\* +'para' vim_ref.txt /\*'para'\* +'paragraphs' vim_ref.txt /\*'paragraphs'\* +'paste' vim_ref.txt /\*'paste'\* +'patchmode' vim_ref.txt /\*'patchmode'\* +'path' vim_ref.txt /\*'path'\* +'pm' vim_ref.txt /\*'pm'\* +'prompt' vim_ref.txt /\*'prompt'\* +'readonly' vim_ref.txt /\*'readonly'\* +'redraw' vim_ref.txt /\*'redraw'\* +'remap' vim_ref.txt /\*'remap'\* +'report' vim_ref.txt /\*'report'\* +'restorescreen' vim_ref.txt /\*'restorescreen'\* +'revins' vim_ref.txt /\*'revins'\* +'ri' vim_ref.txt /\*'ri'\* +'rightleft' vim_ref.txt /\*'rightleft'\* +'rl' vim_ref.txt /\*'rl'\* +'ro' vim_ref.txt /\*'ro'\* +'rs' vim_ref.txt /\*'rs'\* +'ru' vim_ref.txt /\*'ru'\* +'ruler' vim_ref.txt /\*'ruler'\* +'sb' vim_ref.txt /\*'sb'\* +'sbr' vim_ref.txt /\*'sbr'\* +'sc' vim_ref.txt /\*'sc'\* +'scr' vim_ref.txt /\*'scr'\* +'scroll' vim_ref.txt /\*'scroll'\* +'scrolljump' vim_ref.txt /\*'scrolljump'\* +'scrolloff' vim_ref.txt /\*'scrolloff'\* +'scs' vim_ref.txt /\*'scs'\* +'sect' vim_ref.txt /\*'sect'\* +'sections' vim_ref.txt /\*'sections'\* +'secure' vim_ref.txt /\*'secure'\* +'sh' vim_ref.txt /\*'sh'\* +'shell' vim_ref.txt /\*'shell'\* +'shellpipe' vim_ref.txt /\*'shellpipe'\* +'shellredir' vim_ref.txt /\*'shellredir'\* +'shelltype' vim_ref.txt /\*'shelltype'\* +'shiftround' vim_ref.txt /\*'shiftround'\* +'shiftwidth' vim_ref.txt /\*'shiftwidth'\* +'shm' vim_ref.txt /\*'shm'\* +'shortmess' vim_ref.txt /\*'shortmess'\* +'shortname' vim_ref.txt /\*'shortname'\* +'showbreak' vim_ref.txt /\*'showbreak'\* +'showcmd' vim_ref.txt /\*'showcmd'\* +'showmatch' vim_ref.txt /\*'showmatch'\* +'showmode' vim_ref.txt /\*'showmode'\* +'si' vim_ref.txt /\*'si'\* +'sidescroll' vim_ref.txt /\*'sidescroll'\* +'sj' vim_ref.txt /\*'sj'\* +'slow' vim_ref.txt /\*'slow'\* +'slowopen' vim_ref.txt /\*'slowopen'\* +'sm' vim_ref.txt /\*'sm'\* +'smartcase' vim_ref.txt /\*'smartcase'\* +'smartindent' vim_ref.txt /\*'smartindent'\* +'smarttab' vim_ref.txt /\*'smarttab'\* +'smd' vim_ref.txt /\*'smd'\* +'sn' vim_ref.txt /\*'sn'\* +'so' vim_ref.txt /\*'so'\* +'sol' vim_ref.txt /\*'sol'\* +'sourceany' vim_ref.txt /\*'sourceany'\* +'sp' vim_ref.txt /\*'sp'\* +'splitbelow' vim_ref.txt /\*'splitbelow'\* +'sr' vim_ref.txt /\*'sr'\* +'srr' vim_ref.txt /\*'srr'\* +'ss' vim_ref.txt /\*'ss'\* +'st' vim_ref.txt /\*'st'\* +'sta' vim_ref.txt /\*'sta'\* +'startofline' vim_ref.txt /\*'startofline'\* +'su' vim_ref.txt /\*'su'\* +'suffixes' vim_ref.txt /\*'suffixes'\* +'sw' vim_ref.txt /\*'sw'\* +'swapsync' vim_ref.txt /\*'swapsync'\* +'sws' vim_ref.txt /\*'sws'\* +'t_#4' vim_ref.txt /\*'t_#4'\* +'t_%1' vim_ref.txt /\*'t_%1'\* +'t_%i' vim_ref.txt /\*'t_%i'\* +'t_&8' vim_ref.txt /\*'t_&8'\* +'t_@7' vim_ref.txt /\*'t_@7'\* +'t_AL' vim_ref.txt /\*'t_AL'\* +'t_CS' vim_ref.txt /\*'t_CS'\* +'t_DL' vim_ref.txt /\*'t_DL'\* +'t_F1' vim_ref.txt /\*'t_F1'\* +'t_F2' vim_ref.txt /\*'t_F2'\* +'t_RI' vim_ref.txt /\*'t_RI'\* +'t_ZH' vim_ref.txt /\*'t_ZH'\* +'t_ZR' vim_ref.txt /\*'t_ZR'\* +'t_al' vim_ref.txt /\*'t_al'\* +'t_cd' vim_ref.txt /\*'t_cd'\* +'t_ce' vim_ref.txt /\*'t_ce'\* +'t_cl' vim_ref.txt /\*'t_cl'\* +'t_cm' vim_ref.txt /\*'t_cm'\* +'t_cs' vim_ref.txt /\*'t_cs'\* +'t_da' vim_ref.txt /\*'t_da'\* +'t_db' vim_ref.txt /\*'t_db'\* +'t_dl' vim_ref.txt /\*'t_dl'\* +'t_k1' vim_ref.txt /\*'t_k1'\* +'t_k2' vim_ref.txt /\*'t_k2'\* +'t_k3' vim_ref.txt /\*'t_k3'\* +'t_k4' vim_ref.txt /\*'t_k4'\* +'t_k5' vim_ref.txt /\*'t_k5'\* +'t_k6' vim_ref.txt /\*'t_k6'\* +'t_k7' vim_ref.txt /\*'t_k7'\* +'t_k8' vim_ref.txt /\*'t_k8'\* +'t_k9' vim_ref.txt /\*'t_k9'\* +'t_k;' vim_ref.txt /\*'t_k;'\* +'t_kD' vim_ref.txt /\*'t_kD'\* +'t_kI' vim_ref.txt /\*'t_kI'\* +'t_kN' vim_ref.txt /\*'t_kN'\* +'t_kP' vim_ref.txt /\*'t_kP'\* +'t_kb' vim_ref.txt /\*'t_kb'\* +'t_kd' vim_ref.txt /\*'t_kd'\* +'t_ke' vim_ref.txt /\*'t_ke'\* +'t_kh' vim_ref.txt /\*'t_kh'\* +'t_kl' vim_ref.txt /\*'t_kl'\* +'t_kr' vim_ref.txt /\*'t_kr'\* +'t_ks' vim_ref.txt /\*'t_ks'\* +'t_ku' vim_ref.txt /\*'t_ku'\* +'t_md' vim_ref.txt /\*'t_md'\* +'t_me' vim_ref.txt /\*'t_me'\* +'t_mr' vim_ref.txt /\*'t_mr'\* +'t_ms' vim_ref.txt /\*'t_ms'\* +'t_se' vim_ref.txt /\*'t_se'\* +'t_so' vim_ref.txt /\*'t_so'\* +'t_sr' vim_ref.txt /\*'t_sr'\* +'t_te' vim_ref.txt /\*'t_te'\* +'t_ti' vim_ref.txt /\*'t_ti'\* +'t_ue' vim_ref.txt /\*'t_ue'\* +'t_us' vim_ref.txt /\*'t_us'\* +'t_vb' vim_ref.txt /\*'t_vb'\* +'t_ve' vim_ref.txt /\*'t_ve'\* +'t_vi' vim_ref.txt /\*'t_vi'\* +'t_vs' vim_ref.txt /\*'t_vs'\* +'ta' vim_ref.txt /\*'ta'\* +'tabstop' vim_ref.txt /\*'tabstop'\* +'tag' vim_ref.txt /\*'tag'\* +'taglength' vim_ref.txt /\*'taglength'\* +'tagrelative' vim_ref.txt /\*'tagrelative'\* +'tags' vim_ref.txt /\*'tags'\* +'tagstack' vim_ref.txt /\*'tagstack'\* +'tbi' vim_ref.txt /\*'tbi'\* +'term' vim_ref.txt /\*'term'\* +'terse' vim_ref.txt /\*'terse'\* +'textauto' vim_ref.txt /\*'textauto'\* +'textmode' vim_ref.txt /\*'textmode'\* +'textwidth' vim_ref.txt /\*'textwidth'\* +'tf' vim_ref.txt /\*'tf'\* +'tgst' vim_ref.txt /\*'tgst'\* +'tildeop' vim_ref.txt /\*'tildeop'\* +'timeout' vim_ref.txt /\*'timeout'\* +'timeoutlen' vim_ref.txt /\*'timeoutlen'\* +'title' vim_ref.txt /\*'title'\* +'tl' vim_ref.txt /\*'tl'\* +'tm' vim_ref.txt /\*'tm'\* +'to' vim_ref.txt /\*'to'\* +'top' vim_ref.txt /\*'top'\* +'tr' vim_ref.txt /\*'tr'\* +'ts' vim_ref.txt /\*'ts'\* +'tsl' vim_ref.txt /\*'tsl'\* +'ttimeout' vim_ref.txt /\*'ttimeout'\* +'ttimeoutlen' vim_ref.txt /\*'ttimeoutlen'\* +'ttm' vim_ref.txt /\*'ttm'\* +'tty' vim_ref.txt /\*'tty'\* +'ttybuiltin' vim_ref.txt /\*'ttybuiltin'\* +'ttyfast' vim_ref.txt /\*'ttyfast'\* +'ttyscroll' vim_ref.txt /\*'ttyscroll'\* +'ttytype' vim_ref.txt /\*'ttytype'\* +'tw' vim_ref.txt /\*'tw'\* +'tx' vim_ref.txt /\*'tx'\* +'uc' vim_ref.txt /\*'uc'\* +'ul' vim_ref.txt /\*'ul'\* +'undolevels' vim_ref.txt /\*'undolevels'\* +'updatecount' vim_ref.txt /\*'updatecount'\* +'updatetime' vim_ref.txt /\*'updatetime'\* +'ut' vim_ref.txt /\*'ut'\* +'vb' vim_ref.txt /\*'vb'\* +'vi' vim_ref.txt /\*'vi'\* +'viminfo' vim_ref.txt /\*'viminfo'\* +'visualbell' vim_ref.txt /\*'visualbell'\* +'w1200' vim_ref.txt /\*'w1200'\* +'w300' vim_ref.txt /\*'w300'\* +'w9600' vim_ref.txt /\*'w9600'\* +'wa' vim_ref.txt /\*'wa'\* +'warn' vim_ref.txt /\*'warn'\* +'wb' vim_ref.txt /\*'wb'\* +'wc' vim_ref.txt /\*'wc'\* +'wd' vim_ref.txt /\*'wd'\* +'weirdinvert' vim_ref.txt /\*'weirdinvert'\* +'wh' vim_ref.txt /\*'wh'\* +'whichwrap' vim_ref.txt /\*'whichwrap'\* +'wi' vim_ref.txt /\*'wi'\* +'wildchar' vim_ref.txt /\*'wildchar'\* +'window' vim_ref.txt /\*'window'\* +'winheight' vim_ref.txt /\*'winheight'\* +'wiv' vim_ref.txt /\*'wiv'\* +'wm' vim_ref.txt /\*'wm'\* +'wrap' vim_ref.txt /\*'wrap'\* +'wrapmargin' vim_ref.txt /\*'wrapmargin'\* +'wrapscan' vim_ref.txt /\*'wrapscan'\* +'writeany' vim_ref.txt /\*'writeany'\* +'writebackup' vim_ref.txt /\*'writebackup'\* +'writedelay' vim_ref.txt /\*'writedelay'\* +'ws' vim_ref.txt /\*'ws'\* +'ww' vim_ref.txt /\*'ww'\* +( vim_ref.txt /\*(\* +) vim_ref.txt /\*)\* ++ vim_ref.txt /\*+\* +, vim_ref.txt /\*,\* +,mb: vim_ref.txt /\*,mb:\* +,mb: vim_ref.txt /\*,mb:\* +- vim_ref.txt /\*-\* +-+ vim_ref.txt /\*-+\* +-+/ vim_ref.txt /\*-+/\* +-+c vim_ref.txt /\*-+c\* +-+reverse vim_gui.txt /\*-+reverse\* +-+rv vim_gui.txt /\*-+rv\* +-- vim_ref.txt /\*--\* +-H vim_ref.txt /\*-H\* +-L vim_ref.txt /\*-L\* +-R vim_ref.txt /\*-R\* +-T vim_ref.txt /\*-T\* +-W vim_ref.txt /\*-W\* +-b vim_ref.txt /\*-b\* +-background vim_gui.txt /\*-background\* +-bg vim_gui.txt /\*-bg\* +-bold vim_gui.txt /\*-bold\* +-boldfont vim_gui.txt /\*-boldfont\* +-borderwidth vim_gui.txt /\*-borderwidth\* +-bw vim_gui.txt /\*-bw\* +-c vim_ref.txt /\*-c\* +-cursor vim_gui.txt /\*-cursor\* +-d vim_ref.txt /\*-d\* +-display vim_gui.txt /\*-display\* +-e vim_ref.txt /\*-e\* +-f vim_ref.txt /\*-f\* +-fg vim_gui.txt /\*-fg\* +-file vim_ref.txt /\*-file\* +-fn vim_gui.txt /\*-fn\* +-font vim_gui.txt /\*-font\* +-foreground vim_gui.txt /\*-foreground\* +-geom vim_gui.txt /\*-geom\* +-geometry vim_gui.txt /\*-geometry\* +-gui vim_gui.txt /\*-gui\* +-i vim_ref.txt /\*-i\* +-iconic vim_gui.txt /\*-iconic\* +-italic vim_gui.txt /\*-italic\* +-italicfont vim_gui.txt /\*-italicfont\* +-l vim_ref.txt /\*-l\* +-menuheight vim_gui.txt /\*-menuheight\* +-mh vim_gui.txt /\*-mh\* +-n vim_ref.txt /\*-n\* +-o vim_ref.txt /\*-o\* +-qf vim_ref.txt /\*-qf\* +-r vim_ref.txt /\*-r\* +-reverse vim_gui.txt /\*-reverse\* +-rv vim_gui.txt /\*-rv\* +-s vim_ref.txt /\*-s\* +-scrollbarwidth vim_gui.txt /\*-scrollbarwidth\* +-sw vim_gui.txt /\*-sw\* +-t vim_ref.txt /\*-t\* +-tag vim_ref.txt /\*-tag\* +-u vim_ref.txt /\*-u\* +-ul vim_gui.txt /\*-ul\* +-underline vim_gui.txt /\*-underline\* +-v vim_ref.txt /\*-v\* +-vim vim_ref.txt /\*-vim\* +-w vim_ref.txt /\*-w\* +-w_nr vim_ref.txt /\*-w_nr\* +-x vim_ref.txt /\*-x\* +-xrm vim_gui.txt /\*-xrm\* +. vim_ref.txt /\*.\* +.c, vim_ref.txt /\*.c,\* +.c, vim_ref.txt /\*.c,\* +.c, vim_ref.txt /\*.c,\* +.c, vim_ref.txt /\*.c,\* +.exrc vim_ref.txt /\*.exrc\* +.vimrc vim_ref.txt /\*.vimrc\* +/ vim_ref.txt /\*/\* +//; vim_ref.txt /\*//;\* +/ vim_ref.txt /\*/\* +/vim/src/ vim_ref.txt /\*/vim/src/\* +0 vim_ref.txt /\*0\* +: vim_ref.txt /\*:\* +:! vim_ref.txt /\*:!\* +:!! vim_ref.txt /\*:!!\* +:!cmd vim_ref.txt /\*:!cmd\* +:# vim_ref.txt /\*:#\* +:$ vim_ref.txt /\*:$\* +:% vim_ref.txt /\*:%\* +:& vim_ref.txt /\*:&\* +:' vim_ref.txt /\*:'\* +:+cmd vim_ref.txt /\*:+cmd\* +:. vim_ref.txt /\*:.\* +:/ vim_ref.txt /\*:/\* +::e vim_ref.txt /\*::e\* +::h vim_ref.txt /\*::h\* +::p vim_ref.txt /\*::p\* +::r vim_ref.txt /\*::r\* +::t vim_ref.txt /\*::t\* +:< vim_ref.txt /\*:<\* +: vim_ref.txt /\*:\* +: vim_ref.txt /\*:\* +: vim_ref.txt /\*:\* +: vim_ref.txt /\*:\* +:= vim_ref.txt /\*:=\* +:> vim_ref.txt /\*:>\* +:? vim_ref.txt /\*:?\* +:@ vim_ref.txt /\*:@\* +:@@ vim_ref.txt /\*:@@\* +:N vim_ref.txt /\*:N\* +:Next vim_ref.txt /\*:Next\* +:_! vim_ref.txt /\*:_!\* +:_# vim_ref.txt /\*:_#\* +:_% vim_ref.txt /\*:_%\* +:_%: vim_ref.txt /\*:_%:\* +:_%< vim_ref.txt /\*:_%<\* +:a vim_ref.txt /\*:a\* +:ab vim_ref.txt /\*:ab\* +:abbreviate vim_ref.txt /\*:abbreviate\* +:abc vim_ref.txt /\*:abc\* +:abclear vim_ref.txt /\*:abclear\* +:al vim_win.txt /\*:al\* +:all vim_win.txt /\*:all\* +:append vim_ref.txt /\*:append\* +:ar vim_ref.txt /\*:ar\* +:args vim_ref.txt /\*:args\* +:argu vim_ref.txt /\*:argu\* +:argument vim_ref.txt /\*:argument\* +:as vim_ref.txt /\*:as\* +:ascii vim_ref.txt /\*:ascii\* +:au vim_ref.txt /\*:au\* +:autocmd vim_ref.txt /\*:autocmd\* +:b vim_win.txt /\*:b\* +:bN vim_win.txt /\*:bN\* +:bNext vim_win.txt /\*:bNext\* +:ba vim_win.txt /\*:ba\* +:ball vim_win.txt /\*:ball\* +:bar vim_ref.txt /\*:bar\* +:bd vim_win.txt /\*:bd\* +:bdelete vim_win.txt /\*:bdelete\* +:bl vim_win.txt /\*:bl\* +:blast vim_win.txt /\*:blast\* +:bm vim_win.txt /\*:bm\* +:bmodified vim_win.txt /\*:bmodified\* +:bn vim_win.txt /\*:bn\* +:bnext vim_win.txt /\*:bnext\* +:bp vim_win.txt /\*:bp\* +:bprevious vim_win.txt /\*:bprevious\* +:br vim_win.txt /\*:br\* +:brewind vim_win.txt /\*:brewind\* +:buffer vim_win.txt /\*:buffer\* +:buffers vim_win.txt /\*:buffers\* +:bun vim_win.txt /\*:bun\* +:bunload vim_win.txt /\*:bunload\* +:c vim_ref.txt /\*:c\* +:cN vim_ref.txt /\*:cN\* +:cNext vim_ref.txt /\*:cNext\* +:ca vim_ref.txt /\*:ca\* +:cabbrev vim_ref.txt /\*:cabbrev\* +:cabc vim_ref.txt /\*:cabc\* +:cabclear vim_ref.txt /\*:cabclear\* +:cc vim_ref.txt /\*:cc\* +:cd vim_ref.txt /\*:cd\* +:ce vim_ref.txt /\*:ce\* +:center vim_ref.txt /\*:center\* +:cf vim_ref.txt /\*:cf\* +:cfile vim_ref.txt /\*:cfile\* +:change vim_ref.txt /\*:change\* +:chd vim_ref.txt /\*:chd\* +:chdir vim_ref.txt /\*:chdir\* +:che vim_ref.txt /\*:che\* +:checkpath vim_ref.txt /\*:checkpath\* +:cl vim_ref.txt /\*:cl\* +:clist vim_ref.txt /\*:clist\* +:clo vim_win.txt /\*:clo\* +:close vim_win.txt /\*:close\* +:cm vim_ref.txt /\*:cm\* +:cmap vim_ref.txt /\*:cmap\* +:cmap_l vim_ref.txt /\*:cmap_l\* +:cmapc vim_ref.txt /\*:cmapc\* +:cmapclear vim_ref.txt /\*:cmapclear\* +:cmenu vim_gui.txt /\*:cmenu\* +:cn vim_ref.txt /\*:cn\* +:cnext vim_ref.txt /\*:cnext\* +:cno vim_ref.txt /\*:cno\* +:cnorea vim_ref.txt /\*:cnorea\* +:cnoreabbrev vim_ref.txt /\*:cnoreabbrev\* +:cnoremap vim_ref.txt /\*:cnoremap\* +:cnoremenu vim_gui.txt /\*:cnoremenu\* +:co vim_ref.txt /\*:co\* +:copy vim_ref.txt /\*:copy\* +:cp vim_ref.txt /\*:cp\* +:cprevious vim_ref.txt /\*:cprevious\* +:cq vim_ref.txt /\*:cq\* +:cquit vim_ref.txt /\*:cquit\* +:cu vim_ref.txt /\*:cu\* +:cuna vim_ref.txt /\*:cuna\* +:cunabbrev vim_ref.txt /\*:cunabbrev\* +:cunmap vim_ref.txt /\*:cunmap\* +:cunme vim_gui.txt /\*:cunme\* +:cunmenu vim_gui.txt /\*:cunmenu\* +:d vim_ref.txt /\*:d\* +:delete vim_ref.txt /\*:delete\* +:di vim_ref.txt /\*:di\* +:dig vim_ref.txt /\*:dig\* +:digraphs vim_ref.txt /\*:digraphs\* +:display vim_ref.txt /\*:display\* +:dj vim_ref.txt /\*:dj\* +:djump vim_ref.txt /\*:djump\* +:dl vim_ref.txt /\*:dl\* +:dlist vim_ref.txt /\*:dlist\* +:do vim_ref.txt /\*:do\* +:doautocmd vim_ref.txt /\*:doautocmd\* +:ds vim_ref.txt /\*:ds\* +:dsearch vim_ref.txt /\*:dsearch\* +:dsp vim_ref.txt /\*:dsp\* +:dsplit vim_ref.txt /\*:dsplit\* +:e vim_ref.txt /\*:e\* +:edit vim_ref.txt /\*:edit\* +:edit! vim_ref.txt /\*:edit!\* +:edit!_f vim_ref.txt /\*:edit!_f\* +:edit_f vim_ref.txt /\*:edit_f\* +:ex vim_ref.txt /\*:ex\* +:exi vim_ref.txt /\*:exi\* +:exit vim_ref.txt /\*:exit\* +:f vim_ref.txt /\*:f\* +:file vim_ref.txt /\*:file\* +:file_f vim_ref.txt /\*:file_f\* +:filename vim_ref.txt /\*:filename\* +:files vim_win.txt /\*:files\* +:fix vim_ref.txt /\*:fix\* +:fixdel vim_ref.txt /\*:fixdel\* +:g vim_ref.txt /\*:g\* +:global vim_ref.txt /\*:global\* +:gu vim_gui.txt /\*:gu\* +:gui vim_gui.txt /\*:gui\* +:gv vim_gui.txt /\*:gv\* +:gvim vim_gui.txt /\*:gvim\* +:h vim_ref.txt /\*:h\* +:help vim_ref.txt /\*:help\* +:i vim_ref.txt /\*:i\* +:ia vim_ref.txt /\*:ia\* +:iabbrev vim_ref.txt /\*:iabbrev\* +:iabc vim_ref.txt /\*:iabc\* +:iabclear vim_ref.txt /\*:iabclear\* +:ij vim_ref.txt /\*:ij\* +:ijump vim_ref.txt /\*:ijump\* +:il vim_ref.txt /\*:il\* +:ilist vim_ref.txt /\*:ilist\* +:im vim_ref.txt /\*:im\* +:imap vim_ref.txt /\*:imap\* +:imap_l vim_ref.txt /\*:imap_l\* +:imapc vim_ref.txt /\*:imapc\* +:imapclear vim_ref.txt /\*:imapclear\* +:imenu vim_gui.txt /\*:imenu\* +:ino vim_ref.txt /\*:ino\* +:inorea vim_ref.txt /\*:inorea\* +:inoreabbrev vim_ref.txt /\*:inoreabbrev\* +:inoremap vim_ref.txt /\*:inoremap\* +:inoremenu vim_gui.txt /\*:inoremenu\* +:insert vim_ref.txt /\*:insert\* +:is vim_ref.txt /\*:is\* +:isearch vim_ref.txt /\*:isearch\* +:isp vim_ref.txt /\*:isp\* +:isplit vim_ref.txt /\*:isplit\* +:iu vim_ref.txt /\*:iu\* +:iuna vim_ref.txt /\*:iuna\* +:iunabbrev vim_ref.txt /\*:iunabbrev\* +:iunmap vim_ref.txt /\*:iunmap\* +:iunme vim_gui.txt /\*:iunme\* +:iunmenu vim_gui.txt /\*:iunmenu\* +:j vim_ref.txt /\*:j\* +:join vim_ref.txt /\*:join\* +:ju vim_ref.txt /\*:ju\* +:jumps vim_ref.txt /\*:jumps\* +:k vim_ref.txt /\*:k\* +:l vim_ref.txt /\*:l\* +:la vim_ref.txt /\*:la\* +:last vim_ref.txt /\*:last\* +:le vim_ref.txt /\*:le\* +:left vim_ref.txt /\*:left\* +:list vim_ref.txt /\*:list\* +:ls vim_win.txt /\*:ls\* +:m vim_ref.txt /\*:m\* +:ma vim_ref.txt /\*:ma\* +:mak vim_ref.txt /\*:mak\* +:make vim_ref.txt /\*:make\* +:make_makeprg vim_ref.txt /\*:make_makeprg\* +:map vim_ref.txt /\*:map\* +:map! vim_ref.txt /\*:map!\* +:map_l vim_ref.txt /\*:map_l\* +:map_l! vim_ref.txt /\*:map_l!\* +:mapc vim_ref.txt /\*:mapc\* +:mapc! vim_ref.txt /\*:mapc!\* +:mapclear vim_ref.txt /\*:mapclear\* +:mapclear! vim_ref.txt /\*:mapclear!\* +:mark vim_ref.txt /\*:mark\* +:marks vim_ref.txt /\*:marks\* +:me vim_gui.txt /\*:me\* +:menu vim_gui.txt /\*:menu\* +:mk vim_ref.txt /\*:mk\* +:mkexrc vim_ref.txt /\*:mkexrc\* +:mkv vim_ref.txt /\*:mkv\* +:mkvimrc vim_ref.txt /\*:mkvimrc\* +:mod vim_ref.txt /\*:mod\* +:mode vim_ref.txt /\*:mode\* +:move vim_ref.txt /\*:move\* +:n vim_ref.txt /\*:n\* +:new vim_win.txt /\*:new\* +:next vim_ref.txt /\*:next\* +:next_f vim_ref.txt /\*:next_f\* +:nm vim_ref.txt /\*:nm\* +:nmap vim_ref.txt /\*:nmap\* +:nmap_l vim_ref.txt /\*:nmap_l\* +:nmapc vim_ref.txt /\*:nmapc\* +:nmapclear vim_ref.txt /\*:nmapclear\* +:nmenu vim_gui.txt /\*:nmenu\* +:nn vim_ref.txt /\*:nn\* +:nnoremap vim_ref.txt /\*:nnoremap\* +:nnoremenu vim_gui.txt /\*:nnoremenu\* +:no vim_ref.txt /\*:no\* +:no! vim_ref.txt /\*:no!\* +:norea vim_ref.txt /\*:norea\* +:noreabbrev vim_ref.txt /\*:noreabbrev\* +:noremap vim_ref.txt /\*:noremap\* +:noremap! vim_ref.txt /\*:noremap!\* +:noremenu vim_gui.txt /\*:noremenu\* +:norm vim_ref.txt /\*:norm\* +:normal vim_ref.txt /\*:normal\* +:nu vim_ref.txt /\*:nu\* +:number vim_ref.txt /\*:number\* +:nun vim_ref.txt /\*:nun\* +:nunmap vim_ref.txt /\*:nunmap\* +:nunme vim_gui.txt /\*:nunme\* +:nunmenu vim_gui.txt /\*:nunmenu\* +:o vim_ref.txt /\*:o\* +:on vim_win.txt /\*:on\* +:only vim_win.txt /\*:only\* +:open vim_ref.txt /\*:open\* +:p vim_ref.txt /\*:p\* +:po vim_ref.txt /\*:po\* +:pop vim_ref.txt /\*:pop\* +:pre vim_ref.txt /\*:pre\* +:preserve vim_ref.txt /\*:preserve\* +:prev vim_ref.txt /\*:prev\* +:previous vim_ref.txt /\*:previous\* +:print vim_ref.txt /\*:print\* +:pu vim_ref.txt /\*:pu\* +:put vim_ref.txt /\*:put\* +:pw vim_ref.txt /\*:pw\* +:pwd vim_ref.txt /\*:pwd\* +:q vim_ref.txt /\*:q\* +:qa vim_win.txt /\*:qa\* +:qall vim_win.txt /\*:qall\* +:quit vim_ref.txt /\*:quit\* +:quote vim_ref.txt /\*:quote\* +:r vim_ref.txt /\*:r\* +:r! vim_ref.txt /\*:r!\* +:range vim_ref.txt /\*:range\* +:range! vim_ref.txt /\*:range!\* +:read vim_ref.txt /\*:read\* +:read! vim_ref.txt /\*:read!\* +:rec vim_ref.txt /\*:rec\* +:recover vim_ref.txt /\*:recover\* +:red vim_ref.txt /\*:red\* +:redo vim_ref.txt /\*:redo\* +:reg vim_ref.txt /\*:reg\* +:registers vim_ref.txt /\*:registers\* +:res vim_win.txt /\*:res\* +:resize vim_win.txt /\*:resize\* +:ret vim_ref.txt /\*:ret\* +:retab vim_ref.txt /\*:retab\* +:rew vim_ref.txt /\*:rew\* +:rewind vim_ref.txt /\*:rewind\* +:ri vim_ref.txt /\*:ri\* +:right vim_ref.txt /\*:right\* +:rv vim_ref.txt /\*:rv\* +:rviminfo vim_ref.txt /\*:rviminfo\* +:s vim_ref.txt /\*:s\* +:sN vim_win.txt /\*:sN\* +:sNext vim_win.txt /\*:sNext\* +:s_c vim_ref.txt /\*:s_c\* +:sa vim_win.txt /\*:sa\* +:sal vim_win.txt /\*:sal\* +:sall vim_win.txt /\*:sall\* +:sargument vim_win.txt /\*:sargument\* +:sb vim_win.txt /\*:sb\* +:sbN vim_win.txt /\*:sbN\* +:sbNext vim_win.txt /\*:sbNext\* +:sba vim_win.txt /\*:sba\* +:sball vim_win.txt /\*:sball\* +:sbl vim_win.txt /\*:sbl\* +:sblast vim_win.txt /\*:sblast\* +:sbm vim_win.txt /\*:sbm\* +:sbmodified vim_win.txt /\*:sbmodified\* +:sbn vim_win.txt /\*:sbn\* +:sbnext vim_win.txt /\*:sbnext\* +:sbp vim_win.txt /\*:sbp\* +:sbprevious vim_win.txt /\*:sbprevious\* +:sbr vim_win.txt /\*:sbr\* +:sbrewind vim_win.txt /\*:sbrewind\* +:sbuffer vim_win.txt /\*:sbuffer\* +:se vim_ref.txt /\*:se\* +:set vim_ref.txt /\*:set\* +:set_env vim_ref.txt /\*:set_env\* +:sh vim_ref.txt /\*:sh\* +:shell vim_ref.txt /\*:shell\* +:sl vim_ref.txt /\*:sl\* +:sla vim_win.txt /\*:sla\* +:slast vim_win.txt /\*:slast\* +:sleep vim_ref.txt /\*:sleep\* +:sn vim_win.txt /\*:sn\* +:snext vim_win.txt /\*:snext\* +:so vim_ref.txt /\*:so\* +:source vim_ref.txt /\*:source\* +:source_crnl vim_ref.txt /\*:source_crnl\* +:sp vim_win.txt /\*:sp\* +:split vim_win.txt /\*:split\* +:split_f vim_win.txt /\*:split_f\* +:spr vim_win.txt /\*:spr\* +:sprevious vim_win.txt /\*:sprevious\* +:sr vim_win.txt /\*:sr\* +:srewind vim_win.txt /\*:srewind\* +:st vim_ref.txt /\*:st\* +:sta vim_win.txt /\*:sta\* +:stag vim_win.txt /\*:stag\* +:star vim_ref.txt /\*:star\* +:stop vim_ref.txt /\*:stop\* +:substitute vim_ref.txt /\*:substitute\* +:sun vim_win.txt /\*:sun\* +:sunhide vim_win.txt /\*:sunhide\* +:sus vim_ref.txt /\*:sus\* +:suspend vim_ref.txt /\*:suspend\* +:sv vim_win.txt /\*:sv\* +:sview vim_win.txt /\*:sview\* +:sw vim_ref.txt /\*:sw\* +:swapname vim_ref.txt /\*:swapname\* +:t vim_ref.txt /\*:t\* +:ta vim_ref.txt /\*:ta\* +:tag vim_ref.txt /\*:tag\* +:tags vim_ref.txt /\*:tags\* +:u vim_ref.txt /\*:u\* +:una vim_ref.txt /\*:una\* +:unabbreviate vim_ref.txt /\*:unabbreviate\* +:undo vim_ref.txt /\*:undo\* +:unh vim_win.txt /\*:unh\* +:unhide vim_win.txt /\*:unhide\* +:unm vim_ref.txt /\*:unm\* +:unm! vim_ref.txt /\*:unm!\* +:unmap vim_ref.txt /\*:unmap\* +:unmap! vim_ref.txt /\*:unmap!\* +:unme vim_gui.txt /\*:unme\* +:unmenu vim_gui.txt /\*:unmenu\* +:v vim_ref.txt /\*:v\* +:ve vim_ref.txt /\*:ve\* +:version vim_ref.txt /\*:version\* +:vglobal vim_ref.txt /\*:vglobal\* +:vi vim_ref.txt /\*:vi\* +:vie vim_ref.txt /\*:vie\* +:view vim_ref.txt /\*:view\* +:visual vim_ref.txt /\*:visual\* +:visual_example vim_ref.txt /\*:visual_example\* +:vm vim_ref.txt /\*:vm\* +:vmap vim_ref.txt /\*:vmap\* +:vmap_l vim_ref.txt /\*:vmap_l\* +:vmapc vim_ref.txt /\*:vmapc\* +:vmapclear vim_ref.txt /\*:vmapclear\* +:vmenu vim_gui.txt /\*:vmenu\* +:vn vim_ref.txt /\*:vn\* +:vnoremap vim_ref.txt /\*:vnoremap\* +:vnoremenu vim_gui.txt /\*:vnoremenu\* +:vu vim_ref.txt /\*:vu\* +:vunmap vim_ref.txt /\*:vunmap\* +:vunme vim_gui.txt /\*:vunme\* +:vunmenu vim_gui.txt /\*:vunmenu\* +:w vim_ref.txt /\*:w\* +:w! vim_ref.txt /\*:w!\* +:wN vim_ref.txt /\*:wN\* +:wNext vim_ref.txt /\*:wNext\* +:w_a vim_ref.txt /\*:w_a\* +:w_c vim_ref.txt /\*:w_c\* +:w_f vim_ref.txt /\*:w_f\* +:wa vim_win.txt /\*:wa\* +:wall vim_win.txt /\*:wall\* +:wn vim_ref.txt /\*:wn\* +:wnext vim_ref.txt /\*:wnext\* +:wp vim_ref.txt /\*:wp\* +:wprevious vim_ref.txt /\*:wprevious\* +:wq vim_ref.txt /\*:wq\* +:wqa vim_win.txt /\*:wqa\* +:wqall vim_win.txt /\*:wqall\* +:write vim_ref.txt /\*:write\* +:write_a vim_ref.txt /\*:write_a\* +:write_c vim_ref.txt /\*:write_c\* +:write_f vim_ref.txt /\*:write_f\* +:wv vim_ref.txt /\*:wv\* +:wviminfo vim_ref.txt /\*:wviminfo\* +:x vim_ref.txt /\*:x\* +:xa vim_win.txt /\*:xa\* +:xall vim_win.txt /\*:xall\* +:xit vim_ref.txt /\*:xit\* +:y vim_ref.txt /\*:y\* +:yank vim_ref.txt /\*:yank\* +:z vim_ref.txt /\*:z\* +:~ vim_ref.txt /\*:~\* +; vim_ref.txt /\*;\* +< vim_ref.txt /\*<\* +<< vim_ref.txt /\*<<\* +<> vim_ref.txt /\*<>\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* + vim_ref.txt /\*\* += vim_ref.txt /\*=\* +== vim_ref.txt /\*==\* +> vim_ref.txt /\*>\* +>> vim_ref.txt /\*>>\* +? vim_ref.txt /\*?\* +? vim_ref.txt /\*?\* +@ vim_ref.txt /\*@\* +@: vim_ref.txt /\*@:\* +@@ vim_ref.txt /\*@@\* +A vim_ref.txt /\*A\* +B vim_ref.txt /\*B\* +BufEnter vim_ref.txt /\*BufEnter\* +BufLeave vim_ref.txt /\*BufLeave\* +BufNewFile vim_ref.txt /\*BufNewFile\* +BufRead vim_ref.txt /\*BufRead\* +BufReadPost vim_ref.txt /\*BufReadPost\* +BufReadPre vim_ref.txt /\*BufReadPre\* +BufWrite vim_ref.txt /\*BufWrite\* +BufWritePost vim_ref.txt /\*BufWritePost\* +BufWritePre vim_ref.txt /\*BufWritePre\* +C vim_ref.txt /\*C\* +C-editing vim_tips.txt /\*C-editing\* +COMSPEC vim_ref.txt /\*COMSPEC\* +CTRL-A vim_ref.txt /\*CTRL-A\* +CTRL-B vim_ref.txt /\*CTRL-B\* +CTRL-C vim_ref.txt /\*CTRL-C\* +CTRL-D vim_ref.txt /\*CTRL-D\* +CTRL-E vim_ref.txt /\*CTRL-E\* +CTRL-F vim_ref.txt /\*CTRL-F\* +CTRL-G vim_ref.txt /\*CTRL-G\* +CTRL-H vim_ref.txt /\*CTRL-H\* +CTRL-I vim_ref.txt /\*CTRL-I\* +CTRL-J vim_ref.txt /\*CTRL-J\* +CTRL-L vim_ref.txt /\*CTRL-L\* +CTRL-M vim_ref.txt /\*CTRL-M\* +CTRL-N vim_ref.txt /\*CTRL-N\* +CTRL-O vim_ref.txt /\*CTRL-O\* +CTRL-P vim_ref.txt /\*CTRL-P\* +CTRL-R vim_ref.txt /\*CTRL-R\* +CTRL-T vim_ref.txt /\*CTRL-T\* +CTRL-U vim_ref.txt /\*CTRL-U\* +CTRL-V vim_ref.txt /\*CTRL-V\* +CTRL-W vim_idx.txt /\*CTRL-W\* +CTRL-W_+ vim_win.txt /\*CTRL-W_+\* +CTRL-W_- vim_win.txt /\*CTRL-W_-\* +CTRL-W_ vim_win.txt /\*CTRL-W_\* +CTRL-W_ vim_win.txt /\*CTRL-W_\* +CTRL-W_= vim_win.txt /\*CTRL-W_=\* +CTRL-W_CTRL-B vim_win.txt /\*CTRL-W_CTRL-B\* +CTRL-W_CTRL-C vim_win.txt /\*CTRL-W_CTRL-C\* +CTRL-W_CTRL-D vim_ref.txt /\*CTRL-W_CTRL-D\* +CTRL-W_CTRL-F vim_win.txt /\*CTRL-W_CTRL-F\* +CTRL-W_CTRL-I vim_ref.txt /\*CTRL-W_CTRL-I\* +CTRL-W_CTRL-J vim_win.txt /\*CTRL-W_CTRL-J\* +CTRL-W_CTRL-K vim_win.txt /\*CTRL-W_CTRL-K\* +CTRL-W_CTRL-N vim_win.txt /\*CTRL-W_CTRL-N\* +CTRL-W_CTRL-O vim_win.txt /\*CTRL-W_CTRL-O\* +CTRL-W_CTRL-P vim_win.txt /\*CTRL-W_CTRL-P\* +CTRL-W_CTRL-Q vim_win.txt /\*CTRL-W_CTRL-Q\* +CTRL-W_CTRL-R vim_win.txt /\*CTRL-W_CTRL-R\* +CTRL-W_CTRL-S vim_win.txt /\*CTRL-W_CTRL-S\* +CTRL-W_CTRL-T vim_win.txt /\*CTRL-W_CTRL-T\* +CTRL-W_CTRL-W vim_win.txt /\*CTRL-W_CTRL-W\* +CTRL-W_CTRL-X vim_win.txt /\*CTRL-W_CTRL-X\* +CTRL-W_CTRL-] vim_win.txt /\*CTRL-W_CTRL-]\* +CTRL-W_CTRL-^ vim_win.txt /\*CTRL-W_CTRL-^\* +CTRL-W_CTRL-_ vim_win.txt /\*CTRL-W_CTRL-_\* +CTRL-W_R vim_win.txt /\*CTRL-W_R\* +CTRL-W_S vim_win.txt /\*CTRL-W_S\* +CTRL-W_W vim_win.txt /\*CTRL-W_W\* +CTRL-W_] vim_win.txt /\*CTRL-W_]\* +CTRL-W_^ vim_win.txt /\*CTRL-W_^\* +CTRL-W__ vim_win.txt /\*CTRL-W__\* +CTRL-W_b vim_win.txt /\*CTRL-W_b\* +CTRL-W_c vim_win.txt /\*CTRL-W_c\* +CTRL-W_d vim_ref.txt /\*CTRL-W_d\* +CTRL-W_f vim_win.txt /\*CTRL-W_f\* +CTRL-W_i vim_ref.txt /\*CTRL-W_i\* +CTRL-W_j vim_win.txt /\*CTRL-W_j\* +CTRL-W_k vim_win.txt /\*CTRL-W_k\* +CTRL-W_n vim_win.txt /\*CTRL-W_n\* +CTRL-W_o vim_win.txt /\*CTRL-W_o\* +CTRL-W_p vim_win.txt /\*CTRL-W_p\* +CTRL-W_q vim_win.txt /\*CTRL-W_q\* +CTRL-W_r vim_win.txt /\*CTRL-W_r\* +CTRL-W_s vim_win.txt /\*CTRL-W_s\* +CTRL-W_t vim_win.txt /\*CTRL-W_t\* +CTRL-W_w vim_win.txt /\*CTRL-W_w\* +CTRL-W_x vim_win.txt /\*CTRL-W_x\* +CTRL-X vim_ref.txt /\*CTRL-X\* +CTRL-Y vim_ref.txt /\*CTRL-Y\* +CTRL-Z vim_ref.txt /\*CTRL-Z\* +CTRL-] vim_ref.txt /\*CTRL-]\* +CTRL-^ vim_ref.txt /\*CTRL-^\* +C_indenting vim_ref.txt /\*C_indenting\* +D vim_ref.txt /\*D\* +E vim_ref.txt /\*E\* +EXINIT vim_ref.txt /\*EXINIT\* +F vim_ref.txt /\*F\* +FAQ vim_help.txt /\*FAQ\* +FileAppendPost vim_ref.txt /\*FileAppendPost\* +FileAppendPre vim_ref.txt /\*FileAppendPre\* +FileReadPost vim_ref.txt /\*FileReadPost\* +FileReadPre vim_ref.txt /\*FileReadPre\* +FileWritePost vim_ref.txt /\*FileWritePost\* +FileWritePre vim_ref.txt /\*FileWritePre\* +FilterReadPost vim_ref.txt /\*FilterReadPost\* +FilterReadPre vim_ref.txt /\*FilterReadPre\* +FilterWritePost vim_ref.txt /\*FilterWritePost\* +FilterWritePre vim_ref.txt /\*FilterWritePre\* +G vim_ref.txt /\*G\* +GUI vim_gui.txt /\*GUI\* +H vim_ref.txt /\*H\* +I vim_ref.txt /\*I\* +Incompatible_changes vim_40.txt /\*Incompatible_changes\* +J vim_ref.txt /\*J\* +K vim_ref.txt /\*K\* +L vim_ref.txt /\*L\* +M vim_ref.txt /\*M\* +N vim_ref.txt /\*N\* +N% vim_ref.txt /\*N%\* +N: vim_ref.txt /\*N:\* +N vim_ref.txt /\*N\* +O vim_ref.txt /\*O\* +P vim_ref.txt /\*P\* +Q vim_ref.txt /\*Q\* +R vim_ref.txt /\*R\* +S vim_ref.txt /\*S\* +SHELL vim_ref.txt /\*SHELL\* +T vim_ref.txt /\*T\* +TERM vim_ref.txt /\*TERM\* +U vim_ref.txt /\*U\* +V vim_ref.txt /\*V\* +VIMINIT vim_ref.txt /\*VIMINIT\* +VimLeave vim_ref.txt /\*VimLeave\* +Visual_mode vim_ref.txt /\*Visual_mode\* +W vim_ref.txt /\*W\* +WORD vim_ref.txt /\*WORD\* +WinEnter vim_ref.txt /\*WinEnter\* +WinLeave vim_ref.txt /\*WinLeave\* +X vim_ref.txt /\*X\* +X11 vim_ref.txt /\*X11\* +X_ab vim_help.txt /\*X_ab\* +X_ac vim_help.txt /\*X_ac\* +X_ai vim_help.txt /\*X_ai\* +X_bu vim_help.txt /\*X_bu\* +X_ce vim_help.txt /\*X_ce\* +X_ch vim_help.txt /\*X_ch\* +X_cm vim_help.txt /\*X_cm\* +X_co vim_help.txt /\*X_co\* +X_de vim_help.txt /\*X_de\* +X_di vim_help.txt /\*X_di\* +X_ed vim_help.txt /\*X_ed\* +X_et vim_help.txt /\*X_et\* +X_ex vim_help.txt /\*X_ex\* +X_fl vim_help.txt /\*X_fl\* +X_in vim_help.txt /\*X_in\* +X_km vim_help.txt /\*X_km\* +X_lr vim_help.txt /\*X_lr\* +X_ma vim_help.txt /\*X_ma\* +X_op vim_help.txt /\*X_op\* +X_pa vim_help.txt /\*X_pa\* +X_qf vim_help.txt /\*X_qf\* +X_ra vim_help.txt /\*X_ra\* +X_re vim_help.txt /\*X_re\* +X_sc vim_help.txt /\*X_sc\* +X_si vim_help.txt /\*X_si\* +X_ss vim_help.txt /\*X_ss\* +X_st vim_help.txt /\*X_st\* +X_ta vim_help.txt /\*X_ta\* +X_tm vim_help.txt /\*X_tm\* +X_to vim_help.txt /\*X_to\* +X_ud vim_help.txt /\*X_ud\* +X_ur vim_help.txt /\*X_ur\* +X_vc vim_help.txt /\*X_vc\* +X_vi vim_help.txt /\*X_vi\* +X_vm vim_help.txt /\*X_vm\* +X_wi vim_help.txt /\*X_wi\* +X_wq vim_help.txt /\*X_wq\* +Y vim_ref.txt /\*Y\* +ZZ vim_ref.txt /\*ZZ\* +[ vim_idx.txt /\*[\* +[# vim_ref.txt /\*[#\* +[( vim_ref.txt /\*[(\* +[/ vim_ref.txt /\*[/\* +[ vim_ref.txt /\*[\* +[D vim_ref.txt /\*[D\* +[I vim_ref.txt /\*[I\* +[P vim_ref.txt /\*[P\* +[[ vim_ref.txt /\*[[\* +[] vim_ref.txt /\*[]\* +[_CTRL-D vim_ref.txt /\*[_CTRL-D\* +[_CTRL-I vim_ref.txt /\*[_CTRL-I\* +[d vim_ref.txt /\*[d\* +[f vim_ref.txt /\*[f\* +[i vim_ref.txt /\*[i\* +[p vim_ref.txt /\*[p\* +[star vim_ref.txt /\*[star\* +[{ vim_ref.txt /\*[{\* +] vim_idx.txt /\*]\* +]# vim_ref.txt /\*]#\* +]) vim_ref.txt /\*])\* +]/ vim_ref.txt /\*]/\* +] vim_ref.txt /\*]\* +]D vim_ref.txt /\*]D\* +]I vim_ref.txt /\*]I\* +]P vim_ref.txt /\*]P\* +][ vim_ref.txt /\*][\* +]] vim_ref.txt /\*]]\* +]_CTRL-D vim_ref.txt /\*]_CTRL-D\* +]_CTRL-I vim_ref.txt /\*]_CTRL-I\* +]d vim_ref.txt /\*]d\* +]f vim_ref.txt /\*]f\* +]i vim_ref.txt /\*]i\* +]p vim_ref.txt /\*]p\* +]star vim_ref.txt /\*]star\* +]} vim_ref.txt /\*]}\* +^ vim_ref.txt /\*^\* +_ vim_ref.txt /\*_\* +_exrc vim_ref.txt /\*_exrc\* +_vimrc vim_ref.txt /\*_vimrc\* +` vim_ref.txt /\*`\* +`" vim_ref.txt /\*`"\* +`0 vim_ref.txt /\*`0\* +`< vim_ref.txt /\*`<\* +`> vim_ref.txt /\*`>\* +`A vim_ref.txt /\*`A\* +`[ vim_ref.txt /\*`[\* +`] vim_ref.txt /\*`]\* +`` vim_ref.txt /\*``\* +`a vim_ref.txt /\*`a\* +a vim_ref.txt /\*a\* +abbreviations vim_ref.txt /\*abbreviations\* +active_buffer vim_win.txt /\*active_buffer\* +alt vim_ref.txt /\*alt\* +amiga_window vim_ref.txt /\*amiga_window\* +and vim_tips.txt /\*and\* +arglist_quit vim_ref.txt /\*arglist_quit\* +argument_list vim_ref.txt /\*argument_list\* +author vim_help.txt /\*author\* +auto_setting vim_ref.txt /\*auto_setting\* +auto_shortname vim_ref.txt /\*auto_shortname\* +autocommand vim_ref.txt /\*autocommand\* +b vim_ref.txt /\*b\* +backspace vim_ref.txt /\*backspace\* +backspace_delete vim_40.txt /\*backspace_delete\* +backup vim_ref.txt /\*backup\* +backup_changed vim_40.txt /\*backup_changed\* +backup_extension vim_40.txt /\*backup_extension\* +backup_table vim_ref.txt /\*backup_table\* +bar vim_ref.txt /\*bar\* +bars vim_help.txt /\*bars\* +beep vim_ref.txt /\*beep\* +bug_fixes vim_40.txt /\*bug_fixes\* +bugs vim_help.txt /\*bugs\* +builtin_terms vim_ref.txt /\*builtin_terms\* +c vim_ref.txt /\*c\* +cW vim_ref.txt /\*cW\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_ vim_ref.txt /\*c_\* +c_CTRL-A vim_ref.txt /\*c_CTRL-A\* +c_CTRL-B vim_ref.txt /\*c_CTRL-B\* +c_CTRL-C vim_ref.txt /\*c_CTRL-C\* +c_CTRL-D vim_ref.txt /\*c_CTRL-D\* +c_CTRL-E vim_ref.txt /\*c_CTRL-E\* +c_CTRL-H vim_ref.txt /\*c_CTRL-H\* +c_CTRL-I vim_ref.txt /\*c_CTRL-I\* +c_CTRL-J vim_ref.txt /\*c_CTRL-J\* +c_CTRL-K vim_ref.txt /\*c_CTRL-K\* +c_CTRL-L vim_ref.txt /\*c_CTRL-L\* +c_CTRL-N vim_ref.txt /\*c_CTRL-N\* +c_CTRL-P vim_ref.txt /\*c_CTRL-P\* +c_CTRL-Q vim_ref.txt /\*c_CTRL-Q\* +c_CTRL-R vim_ref.txt /\*c_CTRL-R\* +c_CTRL-U vim_ref.txt /\*c_CTRL-U\* +c_CTRL-V vim_ref.txt /\*c_CTRL-V\* +c_CTRL-W vim_ref.txt /\*c_CTRL-W\* +c_CTRL-_ vim_ref.txt /\*c_CTRL-_\* +c_digraph vim_ref.txt /\*c_digraph\* +c_wildchar vim_ref.txt /\*c_wildchar\* +cc vim_ref.txt /\*cc\* +changing vim_ref.txt /\*changing\* +cmdline_completion vim_ref.txt /\*cmdline_completion\* +cmdline_editing vim_ref.txt /\*cmdline_editing\* +cmdline_history vim_ref.txt /\*cmdline_history\* +cmdline_lines vim_ref.txt /\*cmdline_lines\* +cmdline_ranges vim_ref.txt /\*cmdline_ranges\* +cmdline_special vim_ref.txt /\*cmdline_special\* +compilation vim_40.txt /\*compilation\* +compl_current vim_ref.txt /\*compl_current\* +compl_define vim_ref.txt /\*compl_define\* +compl_dictionary vim_ref.txt /\*compl_dictionary\* +compl_filename vim_ref.txt /\*compl_filename\* +compl_keyword vim_ref.txt /\*compl_keyword\* +compl_tag vim_ref.txt /\*compl_tag\* +compl_whole_line vim_ref.txt /\*compl_whole_line\* +complex_change vim_ref.txt /\*complex_change\* +complex_repeat vim_ref.txt /\*complex_repeat\* +control vim_ref.txt /\*control\* +copy_move vim_ref.txt /\*copy_move\* +copying vim_kcc.txt /\*copying\* +count vim_ref.txt /\*count\* +crash_recovery vim_ref.txt /\*crash_recovery\* +credits vim_help.txt /\*credits\* +ctrl vim_ref.txt /\*ctrl\* +cursor-down vim_ref.txt /\*cursor-down\* +cursor-left vim_ref.txt /\*cursor-left\* +cursor-right vim_ref.txt /\*cursor-right\* +cursor-up vim_ref.txt /\*cursor-up\* +cursor_down vim_ref.txt /\*cursor_down\* +cursor_left vim_ref.txt /\*cursor_left\* +cursor_motions vim_ref.txt /\*cursor_motions\* +cursor_right vim_ref.txt /\*cursor_right\* +cursor_up vim_ref.txt /\*cursor_up\* +cw vim_ref.txt /\*cw\* +d vim_ref.txt /\*d\* +dA vim_ref.txt /\*dA\* +dP vim_ref.txt /\*dP\* +dS vim_ref.txt /\*dS\* +da vim_ref.txt /\*da\* +dd vim_ref.txt /\*dd\* +delete_insert vim_ref.txt /\*delete_insert\* +deleting vim_ref.txt /\*deleting\* +dh vim_ref.txt /\*dh\* +digraph_table vim_digr.txt /\*digraph_table\* +digraphs vim_ref.txt /\*digraphs\* +dl vim_ref.txt /\*dl\* +doc_files vim_ref.txt /\*doc_files\* +dp vim_ref.txt /\*dp\* +drag_status_line vim_ref.txt /\*drag_status_line\* +ds vim_ref.txt /\*ds\* +e vim_ref.txt /\*e\* +edit-no-break vim_tips.txt /\*edit-no-break\* +edit_a_file vim_ref.txt /\*edit_a_file\* +edit_binary vim_ref.txt /\*edit_binary\* +edit_files vim_ref.txt /\*edit_files\* +edit_intro vim_ref.txt /\*edit_intro\* +emacs_keys vim_tips.txt /\*emacs_keys\* +emacs_tags vim_ref.txt /\*emacs_tags\* +end vim_ref.txt /\*end\* +errorformat vim_ref.txt /\*errorformat\* +errorformat_changed vim_40.txt /\*errorformat_changed\* +escape vim_ref.txt /\*escape\* +escape_bar vim_40.txt /\*escape_bar\* +ex_cmd_index vim_idx.txt /\*ex_cmd_index\* +ex_edit_index vim_idx.txt /\*ex_edit_index\* +exrc vim_ref.txt /\*exrc\* +f vim_ref.txt /\*f\* +faq vim_help.txt /\*faq\* +fo_table vim_ref.txt /\*fo_table\* +fork vim_unix.txt /\*fork\* +format_bullet_list vim_tips.txt /\*format_bullet_list\* +format_comments vim_ref.txt /\*format_comments\* +formatting vim_ref.txt /\*formatting\* +function-key vim_ref.txt /\*function-key\* +function_key vim_ref.txt /\*function_key\* +g vim_idx.txt /\*g\* +g# vim_ref.txt /\*g#\* +g$ vim_ref.txt /\*g$\* +g0 vim_ref.txt /\*g0\* +g vim_ref.txt /\*g\* +g vim_ref.txt /\*g\* +g vim_ref.txt /\*g\* +g vim_ref.txt /\*g\* +g vim_ref.txt /\*g\* +g vim_ref.txt /\*g\* +gD vim_ref.txt /\*gD\* +gE vim_ref.txt /\*gE\* +gI vim_ref.txt /\*gI\* +gU vim_ref.txt /\*gU\* +g^ vim_ref.txt /\*g^\* +g_CTRL-G vim_ref.txt /\*g_CTRL-G\* +ga vim_ref.txt /\*ga\* +gd vim_ref.txt /\*gd\* +ge vim_ref.txt /\*ge\* +gf vim_ref.txt /\*gf\* +gg vim_ref.txt /\*gg\* +gj vim_ref.txt /\*gj\* +gk vim_ref.txt /\*gk\* +gq vim_ref.txt /\*gq\* +graphic_option_gone vim_40.txt /\*graphic_option_gone\* +greek vim_ref.txt /\*greek\* +gs vim_ref.txt /\*gs\* +gstar vim_ref.txt /\*gstar\* +gu vim_ref.txt /\*gu\* +gui vim_gui.txt /\*gui\* +gui_compiling vim_gui.txt /\*gui_compiling\* +gui_creating_menus vim_gui.txt /\*gui_creating_menus\* +gui_delete_menus vim_gui.txt /\*gui_delete_menus\* +gui_extras vim_gui.txt /\*gui_extras\* +gui_fork vim_gui.txt /\*gui_fork\* +gui_horiz_scroll vim_gui.txt /\*gui_horiz_scroll\* +gui_intro vim_gui.txt /\*gui_intro\* +gui_menus vim_gui.txt /\*gui_menus\* +gui_mouse vim_gui.txt /\*gui_mouse\* +gui_mouse_mapping vim_gui.txt /\*gui_mouse_mapping\* +gui_mouse_move vim_gui.txt /\*gui_mouse_move\* +gui_mouse_select vim_gui.txt /\*gui_mouse_select\* +gui_mouse_status vim_gui.txt /\*gui_mouse_status\* +gui_mouse_various vim_gui.txt /\*gui_mouse_various\* +gui_mouse_xterm_like vim_gui.txt /\*gui_mouse_xterm_like\* +gui_pty vim_gui.txt /\*gui_pty\* +gui_resources vim_gui.txt /\*gui_resources\* +gui_scrollbars vim_gui.txt /\*gui_scrollbars\* +gui_selections vim_gui.txt /\*gui_selections\* +gui_showing_menus vim_gui.txt /\*gui_showing_menus\* +gui_start vim_gui.txt /\*gui_start\* +gui_todo vim_gui.txt /\*gui_todo\* +gui_using_menus vim_gui.txt /\*gui_using_menus\* +gui_vert_scroll vim_gui.txt /\*gui_vert_scroll\* +gv vim_ref.txt /\*gv\* +gvim vim_gui.txt /\*gvim\* +gzip-helpfile vim_tips.txt /\*gzip-helpfile\* +g~ vim_ref.txt /\*g~\* +h vim_ref.txt /\*h\* +hebrew vim_rlh.txt /\*hebrew\* +help vim_ref.txt /\*help\* +help_xterm_window vim_ref.txt /\*help_xterm_window\* +hidden_buffer vim_win.txt /\*hidden_buffer\* +hidden_quit vim_win.txt /\*hidden_quit\* +highlight_changed vim_40.txt /\*highlight_changed\* +home vim_ref.txt /\*home\* +home_replace vim_ref.txt /\*home_replace\* +how_do_i vim_help.txt /\*how_do_i\* +how_to vim_help.txt /\*how_to\* +howdoi vim_help.txt /\*howdoi\* +howto vim_help.txt /\*howto\* +hpterm vim_ref.txt /\*hpterm\* +html-editing vim_tips.txt /\*html-editing\* +i vim_ref.txt /\*i\* +i_0_CTRL-D vim_ref.txt /\*i_0_CTRL-D\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_ vim_ref.txt /\*i_\* +i_CTRL-@ vim_ref.txt /\*i_CTRL-@\* +i_CTRL-A vim_ref.txt /\*i_CTRL-A\* +i_CTRL-B vim_ref.txt /\*i_CTRL-B\* +i_CTRL-C vim_ref.txt /\*i_CTRL-C\* +i_CTRL-D vim_ref.txt /\*i_CTRL-D\* +i_CTRL-E vim_ref.txt /\*i_CTRL-E\* +i_CTRL-H vim_ref.txt /\*i_CTRL-H\* +i_CTRL-I vim_ref.txt /\*i_CTRL-I\* +i_CTRL-J vim_ref.txt /\*i_CTRL-J\* +i_CTRL-K vim_ref.txt /\*i_CTRL-K\* +i_CTRL-M vim_ref.txt /\*i_CTRL-M\* +i_CTRL-N vim_ref.txt /\*i_CTRL-N\* +i_CTRL-O vim_ref.txt /\*i_CTRL-O\* +i_CTRL-P vim_ref.txt /\*i_CTRL-P\* +i_CTRL-Q vim_ref.txt /\*i_CTRL-Q\* +i_CTRL-R vim_ref.txt /\*i_CTRL-R\* +i_CTRL-T vim_ref.txt /\*i_CTRL-T\* +i_CTRL-U vim_ref.txt /\*i_CTRL-U\* +i_CTRL-V vim_ref.txt /\*i_CTRL-V\* +i_CTRL-V_digit vim_ref.txt /\*i_CTRL-V_digit\* +i_CTRL-W vim_ref.txt /\*i_CTRL-W\* +i_CTRL-X vim_ref.txt /\*i_CTRL-X\* +i_CTRL-X_CTRL-D vim_ref.txt /\*i_CTRL-X_CTRL-D\* +i_CTRL-X_CTRL-E vim_ref.txt /\*i_CTRL-X_CTRL-E\* +i_CTRL-X_CTRL-F vim_ref.txt /\*i_CTRL-X_CTRL-F\* +i_CTRL-X_CTRL-I vim_ref.txt /\*i_CTRL-X_CTRL-I\* +i_CTRL-X_CTRL-K vim_ref.txt /\*i_CTRL-X_CTRL-K\* +i_CTRL-X_CTRL-L vim_ref.txt /\*i_CTRL-X_CTRL-L\* +i_CTRL-X_CTRL-Y vim_ref.txt /\*i_CTRL-X_CTRL-Y\* +i_CTRL-X_CTRL-] vim_ref.txt /\*i_CTRL-X_CTRL-]\* +i_CTRL-Y vim_ref.txt /\*i_CTRL-Y\* +i_CTRL-[ vim_ref.txt /\*i_CTRL-[\* +i_CTRL-_ vim_ref.txt /\*i_CTRL-_\* +i_^_CTRL-D vim_ref.txt /\*i_^_CTRL-D\* +i_digraph vim_ref.txt /\*i_digraph\* +i_esc vim_ref.txt /\*i_esc\* +icon_changed vim_40.txt /\*icon_changed\* +inactive_buffer vim_win.txt /\*inactive_buffer\* +include_search vim_ref.txt /\*include_search\* +index vim_help.txt /\*index\* +initialization vim_ref.txt /\*initialization\* +ins_completion vim_ref.txt /\*ins_completion\* +ins_expandtab vim_ref.txt /\*ins_expandtab\* +ins_reverse vim_rlh.txt /\*ins_reverse\* +ins_smarttab vim_ref.txt /\*ins_smarttab\* +ins_special_keys vim_ref.txt /\*ins_special_keys\* +ins_special_special vim_ref.txt /\*ins_special_special\* +ins_textwidth vim_ref.txt /\*ins_textwidth\* +insert vim_ref.txt /\*insert\* +insert_expand vim_ref.txt /\*insert_expand\* +insert_index vim_idx.txt /\*insert_index\* +inserting vim_ref.txt /\*inserting\* +intro vim_ref.txt /\*intro\* +j vim_ref.txt /\*j\* +jumplist vim_ref.txt /\*jumplist\* +k vim_ref.txt /\*k\* +key_codes_changed vim_40.txt /\*key_codes_changed\* +key_mapping vim_ref.txt /\*key_mapping\* +key_notation vim_ref.txt /\*key_notation\* +l vim_ref.txt /\*l\* +left_right_motions vim_ref.txt /\*left_right_motions\* +limits vim_ref.txt /\*limits\* +linefeed vim_ref.txt /\*linefeed\* +m vim_ref.txt /\*m\* +mail_list vim_help.txt /\*mail_list\* +map_backslash vim_ref.txt /\*map_backslash\* +map_bar vim_ref.txt /\*map_bar\* +map_empty_rhs vim_ref.txt /\*map_empty_rhs\* +map_space_in_lhs vim_ref.txt /\*map_space_in_lhs\* +map_space_in_rhs vim_ref.txt /\*map_space_in_rhs\* +menuBar vim_gui.txt /\*menuBar\* +meta vim_ref.txt /\*meta\* +missing_commands vim_ref.txt /\*missing_commands\* +missing_options vim_ref.txt /\*missing_options\* +mode_cmdline vim_ref.txt /\*mode_cmdline\* +mode_ins_repl vim_ref.txt /\*mode_ins_repl\* +mode_switching vim_ref.txt /\*mode_switching\* +modeline vim_ref.txt /\*modeline\* +mouse_swap_buttons vim_ref.txt /\*mouse_swap_buttons\* +mouse_using vim_ref.txt /\*mouse_using\* +multi_repeat vim_ref.txt /\*multi_repeat\* +n vim_ref.txt /\*n\* +new_autocmd vim_40.txt /\*new_autocmd\* +new_cindent vim_40.txt /\*new_cindent\* +new_commandline vim_40.txt /\*new_commandline\* +new_complete vim_40.txt /\*new_complete\* +new_features vim_40.txt /\*new_features\* +new_gui vim_40.txt /\*new_gui\* +new_help vim_40.txt /\*new_help\* +new_include vim_40.txt /\*new_include\* +new_keys vim_40.txt /\*new_keys\* +new_mint vim_40.txt /\*new_mint\* +new_misc vim_40.txt /\*new_misc\* +new_mouse vim_40.txt /\*new_mouse\* +new_msg vim_40.txt /\*new_msg\* +new_options vim_40.txt /\*new_options\* +new_os2 vim_40.txt /\*new_os2\* +new_para vim_40.txt /\*new_para\* +new_swapfile vim_40.txt /\*new_swapfile\* +new_tags vim_40.txt /\*new_tags\* +new_textobj vim_40.txt /\*new_textobj\* +new_viminfo vim_40.txt /\*new_viminfo\* +new_win32 vim_40.txt /\*new_win32\* +news vim_help.txt /\*news\* +normal_index vim_idx.txt /\*normal_index\* +notation vim_ref.txt /\*notation\* +o vim_ref.txt /\*o\* +object_motions vim_ref.txt /\*object_motions\* +object_select vim_ref.txt /\*object_select\* +objects vim_idx.txt /\*objects\* +online_help vim_ref.txt /\*online_help\* +operator vim_ref.txt /\*operator\* +option_backslash vim_ref.txt /\*option_backslash\* +option_list vim_help.txt /\*option_list\* +option_summary vim_ref.txt /\*option_summary\* +options vim_ref.txt /\*options\* +p vim_ref.txt /\*p\* +page-down vim_ref.txt /\*page-down\* +page-up vim_ref.txt /\*page-up\* +page_down vim_ref.txt /\*page_down\* +page_up vim_ref.txt /\*page_up\* +paragraph vim_ref.txt /\*paragraph\* +pattern_searches vim_ref.txt /\*pattern_searches\* +pseudo-Q vim_tips.txt /\*pseudo-Q\* +q vim_ref.txt /\*q\* +quickfix vim_ref.txt /\*quickfix\* +quote vim_ref.txt /\*quote\* +quote% vim_ref.txt /\*quote%\* +quote- vim_ref.txt /\*quote-\* +quote. vim_ref.txt /\*quote.\* +quote0 vim_ref.txt /\*quote0\* +quote: vim_ref.txt /\*quote:\* +quote_% vim_ref.txt /\*quote_%\* +quote_- vim_ref.txt /\*quote_-\* +quote_. vim_ref.txt /\*quote_.\* +quote_: vim_ref.txt /\*quote_:\* +quote_alpha vim_ref.txt /\*quote_alpha\* +quote_number vim_ref.txt /\*quote_number\* +quote_quote vim_ref.txt /\*quote_quote\* +quotea vim_ref.txt /\*quotea\* +quotequote vim_ref.txt /\*quotequote\* +r vim_ref.txt /\*r\* +recovery vim_ref.txt /\*recovery\* +recursive_mapping vim_ref.txt /\*recursive_mapping\* +ref vim_ref.txt /\*ref\* +reference vim_ref.txt /\*reference\* +reference_contents vim_ref.txt /\*reference_contents\* +registers vim_ref.txt /\*registers\* +rename-files vim_tips.txt /\*rename-files\* +repeat_Visual vim_ref.txt /\*repeat_Visual\* +repeating vim_ref.txt /\*repeating\* +replace_mode vim_ref.txt /\*replace_mode\* +s vim_ref.txt /\*s\* +save_file vim_ref.txt /\*save_file\* +save_settings vim_ref.txt /\*save_settings\* +scriptout_changed vim_40.txt /\*scriptout_changed\* +scroll-insert vim_tips.txt /\*scroll-insert\* +scroll-smooth vim_tips.txt /\*scroll-smooth\* +scrolling vim_ref.txt /\*scrolling\* +search_offset vim_ref.txt /\*search_offset\* +search_pattern vim_ref.txt /\*search_pattern\* +section vim_ref.txt /\*section\* +sentence vim_ref.txt /\*sentence\* +set_option vim_ref.txt /\*set_option\* +shell_window vim_tips.txt /\*shell_window\* +shift vim_ref.txt /\*shift\* +short_name_changed vim_40.txt /\*short_name_changed\* +simple_change vim_ref.txt /\*simple_change\* +single_repeat vim_ref.txt /\*single_repeat\* +slow_fast_terminal vim_ref.txt /\*slow_fast_terminal\* +slow_start vim_ref.txt /\*slow_start\* +slow_terminal vim_ref.txt /\*slow_terminal\* +space vim_ref.txt /\*space\* +speed-up vim_tips.txt /\*speed-up\* +spoon vim_unix.txt /\*spoon\* +star vim_ref.txt /\*star\* +starting vim_ref.txt /\*starting\* +startup vim_ref.txt /\*startup\* +startup-options vim_ref.txt /\*startup-options\* +startup_terminal vim_ref.txt /\*startup_terminal\* +static_tag vim_ref.txt /\*static_tag\* +status_line vim_win.txt /\*status_line\* +suffixes vim_ref.txt /\*suffixes\* +suspend vim_ref.txt /\*suspend\* +swap_file vim_ref.txt /\*swap_file\* +swapfile_changed vim_40.txt /\*swapfile_changed\* +t vim_ref.txt /\*t\* +t_#4 vim_ref.txt /\*t_#4\* +t_%1 vim_ref.txt /\*t_%1\* +t_%i vim_ref.txt /\*t_%i\* +t_&8 vim_ref.txt /\*t_&8\* +t_@7 vim_ref.txt /\*t_@7\* +t_AL vim_ref.txt /\*t_AL\* +t_CS vim_ref.txt /\*t_CS\* +t_DL vim_ref.txt /\*t_DL\* +t_F1 vim_ref.txt /\*t_F1\* +t_F2 vim_ref.txt /\*t_F2\* +t_RI vim_ref.txt /\*t_RI\* +t_ZH vim_ref.txt /\*t_ZH\* +t_ZR vim_ref.txt /\*t_ZR\* +t_al vim_ref.txt /\*t_al\* +t_cd vim_ref.txt /\*t_cd\* +t_cdl vim_40.txt /\*t_cdl\* +t_ce vim_ref.txt /\*t_ce\* +t_ci vim_40.txt /\*t_ci\* +t_cil vim_40.txt /\*t_cil\* +t_cl vim_ref.txt /\*t_cl\* +t_cm vim_ref.txt /\*t_cm\* +t_cri vim_40.txt /\*t_cri\* +t_cs vim_ref.txt /\*t_cs\* +t_csc vim_40.txt /\*t_csc\* +t_cv vim_40.txt /\*t_cv\* +t_cvv vim_40.txt /\*t_cvv\* +t_da vim_ref.txt /\*t_da\* +t_db vim_ref.txt /\*t_db\* +t_dl vim_ref.txt /\*t_dl\* +t_ed vim_40.txt /\*t_ed\* +t_el vim_40.txt /\*t_el\* +t_f1 vim_40.txt /\*t_f1\* +t_f10 vim_40.txt /\*t_f10\* +t_f2 vim_40.txt /\*t_f2\* +t_f3 vim_40.txt /\*t_f3\* +t_f4 vim_40.txt /\*t_f4\* +t_f5 vim_40.txt /\*t_f5\* +t_f6 vim_40.txt /\*t_f6\* +t_f7 vim_40.txt /\*t_f7\* +t_f8 vim_40.txt /\*t_f8\* +t_f9 vim_40.txt /\*t_f9\* +t_help vim_40.txt /\*t_help\* +t_il vim_40.txt /\*t_il\* +t_k1 vim_ref.txt /\*t_k1\* +t_k2 vim_ref.txt /\*t_k2\* +t_k3 vim_ref.txt /\*t_k3\* +t_k4 vim_ref.txt /\*t_k4\* +t_k5 vim_ref.txt /\*t_k5\* +t_k6 vim_ref.txt /\*t_k6\* +t_k7 vim_ref.txt /\*t_k7\* +t_k8 vim_ref.txt /\*t_k8\* +t_k9 vim_ref.txt /\*t_k9\* +t_k; vim_ref.txt /\*t_k;\* +t_kD vim_ref.txt /\*t_kD\* +t_kI vim_ref.txt /\*t_kI\* +t_kN vim_ref.txt /\*t_kN\* +t_kP vim_ref.txt /\*t_kP\* +t_kb vim_ref.txt /\*t_kb\* +t_kd vim_ref.txt /\*t_kd\* +t_ke vim_ref.txt /\*t_ke\* +t_kh vim_ref.txt /\*t_kh\* +t_kl vim_ref.txt /\*t_kl\* +t_kr vim_ref.txt /\*t_kr\* +t_ks vim_ref.txt /\*t_ks\* +t_ku vim_ref.txt /\*t_ku\* +t_md vim_ref.txt /\*t_md\* +t_me vim_ref.txt /\*t_me\* +t_mr vim_ref.txt /\*t_mr\* +t_ms vim_ref.txt /\*t_ms\* +t_se vim_ref.txt /\*t_se\* +t_sf1 vim_40.txt /\*t_sf1\* +t_sf10 vim_40.txt /\*t_sf10\* +t_sf2 vim_40.txt /\*t_sf2\* +t_sf3 vim_40.txt /\*t_sf3\* +t_sf4 vim_40.txt /\*t_sf4\* +t_sf5 vim_40.txt /\*t_sf5\* +t_sf6 vim_40.txt /\*t_sf6\* +t_sf7 vim_40.txt /\*t_sf7\* +t_sf8 vim_40.txt /\*t_sf8\* +t_sf9 vim_40.txt /\*t_sf9\* +t_skd vim_40.txt /\*t_skd\* +t_skl vim_40.txt /\*t_skl\* +t_skr vim_40.txt /\*t_skr\* +t_sku vim_40.txt /\*t_sku\* +t_so vim_ref.txt /\*t_so\* +t_sr vim_ref.txt /\*t_sr\* +t_tb vim_40.txt /\*t_tb\* +t_te vim_ref.txt /\*t_te\* +t_ti vim_ref.txt /\*t_ti\* +t_tp vim_40.txt /\*t_tp\* +t_ts vim_40.txt /\*t_ts\* +t_ue vim_ref.txt /\*t_ue\* +t_undo vim_40.txt /\*t_undo\* +t_us vim_ref.txt /\*t_us\* +t_vb vim_ref.txt /\*t_vb\* +t_ve vim_ref.txt /\*t_ve\* +t_vi vim_ref.txt /\*t_vi\* +t_vs vim_ref.txt /\*t_vs\* +tab vim_ref.txt /\*tab\* +tag_commands vim_ref.txt /\*tag_commands\* +tag_priority vim_ref.txt /\*tag_priority\* +tag_search vim_ref.txt /\*tag_search\* +tags_and_searches vim_ref.txt /\*tags_and_searches\* +tags_option vim_ref.txt /\*tags_option\* +tagstack vim_ref.txt /\*tagstack\* +tcsh-style vim_ref.txt /\*tcsh-style\* +termcap vim_ref.txt /\*termcap\* +termcap_changed vim_40.txt /\*termcap_changed\* +terminal_info vim_ref.txt /\*terminal_info\* +terminal_options vim_ref.txt /\*terminal_options\* +terminfo vim_ref.txt /\*terminfo\* +textmode_io vim_ref.txt /\*textmode_io\* +textmode_read vim_ref.txt /\*textmode_read\* +textmode_write vim_ref.txt /\*textmode_write\* +timestamp vim_ref.txt /\*timestamp\* +toggle vim_ref.txt /\*toggle\* +toggle_revins vim_40.txt /\*toggle_revins\* +trojan_horse vim_ref.txt /\*trojan_horse\* +type-mistakes vim_tips.txt /\*type-mistakes\* +u vim_ref.txt /\*u\* +uganda vim_kcc.txt /\*uganda\* +undo vim_ref.txt /\*undo\* +undo_redo vim_ref.txt /\*undo_redo\* +up_down_motions vim_ref.txt /\*up_down_motions\* +use_visual_cmds vim_40.txt /\*use_visual_cmds\* +useful-mappings vim_tips.txt /\*useful-mappings\* +usenet vim_help.txt /\*usenet\* +v vim_ref.txt /\*v\* +v_! vim_ref.txt /\*v_!\* +v_: vim_ref.txt /\*v_:\* +v_< vim_ref.txt /\*v_<\* +v_= vim_ref.txt /\*v_=\* +v_> vim_ref.txt /\*v_>\* +v_A vim_ref.txt /\*v_A\* +v_C vim_ref.txt /\*v_C\* +v_CTRL-V vim_ref.txt /\*v_CTRL-V\* +v_CTRL-Z vim_ref.txt /\*v_CTRL-Z\* +v_CTRL-] vim_ref.txt /\*v_CTRL-]\* +v_D vim_ref.txt /\*v_D\* +v_J vim_ref.txt /\*v_J\* +v_K vim_ref.txt /\*v_K\* +v_P vim_ref.txt /\*v_P\* +v_Q vim_ref.txt /\*v_Q\* +v_R vim_ref.txt /\*v_R\* +v_S vim_ref.txt /\*v_S\* +v_U vim_ref.txt /\*v_U\* +v_V vim_ref.txt /\*v_V\* +v_X vim_ref.txt /\*v_X\* +v_Y vim_ref.txt /\*v_Y\* +v_a vim_ref.txt /\*v_a\* +v_c vim_ref.txt /\*v_c\* +v_d vim_ref.txt /\*v_d\* +v_gq vim_ref.txt /\*v_gq\* +v_gv vim_ref.txt /\*v_gv\* +v_o vim_ref.txt /\*v_o\* +v_p vim_ref.txt /\*v_p\* +v_r vim_ref.txt /\*v_r\* +v_s vim_ref.txt /\*v_s\* +v_u vim_ref.txt /\*v_u\* +v_v vim_ref.txt /\*v_v\* +v_x vim_ref.txt /\*v_x\* +v_y vim_ref.txt /\*v_y\* +v_~ vim_ref.txt /\*v_~\* +various vim_ref.txt /\*various\* +various_motions vim_ref.txt /\*various_motions\* +version vim_40.txt /\*version\* +version_warning vim_40.txt /\*version_warning\* +vi_compat vim_40.txt /\*vi_compat\* +vi_differences vim_ref.txt /\*vi_differences\* +vim_40.txt vim_40.txt /\*vim_40.txt\* +vim_ami.txt vim_ami.txt /\*vim_ami.txt\* +vim_arch.txt vim_arch.txt /\*vim_arch.txt\* +vim_arguments vim_ref.txt /\*vim_arguments\* +vim_diff.txt vim_diff.txt /\*vim_diff.txt\* +vim_digr.txt vim_digr.txt /\*vim_digr.txt\* +vim_dos.txt vim_dos.txt /\*vim_dos.txt\* +vim_gui.txt vim_gui.txt /\*vim_gui.txt\* +vim_help.txt vim_help.txt /\*vim_help.txt\* +vim_idx.txt vim_idx.txt /\*vim_idx.txt\* +vim_kcc.txt vim_kcc.txt /\*vim_kcc.txt\* +vim_mac.txt vim_mac.txt /\*vim_mac.txt\* +vim_menu.txt vim_menu.txt /\*vim_menu.txt\* +vim_mint.txt vim_mint.txt /\*vim_mint.txt\* +vim_modes vim_ref.txt /\*vim_modes\* +vim_os2.txt vim_os2.txt /\*vim_os2.txt\* +vim_ref.txt vim_ref.txt /\*vim_ref.txt\* +vim_rlh.txt vim_rlh.txt /\*vim_rlh.txt\* +vim_tags vim_tags 1 +vim_tips.txt vim_tips.txt /\*vim_tips.txt\* +vim_unix.txt vim_unix.txt /\*vim_unix.txt\* +vim_w32.txt vim_w32.txt /\*vim_w32.txt\* +vim_win.txt vim_win.txt /\*vim_win.txt\* +vimdev vim_help.txt /\*vimdev\* +viminfo_file vim_ref.txt /\*viminfo_file\* +viminfo_file_marks vim_ref.txt /\*viminfo_file_marks\* +vimrc vim_ref.txt /\*vimrc\* +visual_block vim_ref.txt /\*visual_block\* +visual_index vim_idx.txt /\*visual_index\* +vt100_cursor_keys vim_ref.txt /\*vt100_cursor_keys\* +w vim_ref.txt /\*w\* +window_contents vim_ref.txt /\*window_contents\* +window_size vim_ref.txt /\*window_size\* +word vim_ref.txt /\*word\* +word_motions vim_ref.txt /\*word_motions\* +workbench vim_ref.txt /\*workbench\* +wrap_off vim_ref.txt /\*wrap_off\* +write_fail vim_ref.txt /\*write_fail\* +write_quit vim_ref.txt /\*write_quit\* +www vim_help.txt /\*www\* +x vim_ref.txt /\*x\* +xterm-screens vim_tips.txt /\*xterm-screens\* +xterm_copy_paste vim_ref.txt /\*xterm_copy_paste\* +xterm_cursor_keys vim_ref.txt /\*xterm_cursor_keys\* +y vim_ref.txt /\*y\* +ye_option_gone vim_40.txt /\*ye_option_gone\* +yy vim_ref.txt /\*yy\* +z vim_ref.txt /\*z\* +z- vim_ref.txt /\*z-\* +z. vim_ref.txt /\*z.\* +z vim_ref.txt /\*z\* +z vim_ref.txt /\*z\* +z vim_ref.txt /\*z\* +zN vim_ref.txt /\*zN\* +zb vim_ref.txt /\*zb\* +ze vim_ref.txt /\*ze\* +zh vim_ref.txt /\*zh\* +zl vim_ref.txt /\*zl\* +zs vim_ref.txt /\*zs\* +zt vim_ref.txt /\*zt\* +zz vim_ref.txt /\*zz\* +{ vim_ref.txt /\*{\* +{Visual} vim_ref.txt /\*{Visual}\* +{motion} vim_ref.txt /\*{motion}\* +} vim_ref.txt /\*}\* +~ vim_ref.txt /\*~\* diff --git a/usr.bin/vim/doc/vim_tips.txt b/usr.bin/vim/doc/vim_tips.txt new file mode 100644 index 00000000000..28ac34e8610 --- /dev/null +++ b/usr.bin/vim/doc/vim_tips.txt @@ -0,0 +1,307 @@ +*vim_tips.txt* For Vim version 4.2. Last modification: 1996 June 16 + +Contents: + +Editing C programs |C-editing| +Editing local HTML files (WWW) |html-editing| +Editing paragraphs without a line break |edit-no-break| +Switching screens in an xterm |xterm-screens| +Scrolling in Insert mode |scroll-insert| +Smooth scrolling |scroll-smooth| +Correcting common typing mistakes |type-mistakes| +Renaming files |rename-files| +Speeding up external commands |speed-up| +Useful mappings |useful-mappings| +Compressing the help files |gzip-helpfile| +Executing shell commands in a window |shell_window| +Pseudo-Ex mode |pseudo-Q| + + +Editing C programs *C-editing* +================== + +There are quite a few features in Vim to help you edit C program files. Here +is an overview with tags to jump to: + +|C_indenting| Automatically set the indent of line while typing + text. +|=| Re-indent a few lines. +|format_comments| Format comments. + +|:checkpath| Show all recursively included files. +|[i| Search for identifier under cursor in current and + included files. +|[_CTRL-I| Jump to match for "[i" +|[I| List all lines in current and included files where + identifier under the cursor matches. +|[d| Search for define under cursor in current and included + files. + +|CTRL-]| Jump to tag under cursor. +|CTRL-T| Jump back from tag. + +|gd| Go to Declaration of local variable under cursor. +|gD| Go to Declaration of global variable under cursor. + +|gf| Go to file name under the cursor. + +|%| Go to matching (), {}, [], /* */, #if, #else, #endif. +|[/| Go to previous start of comment. +|]/| Go to next end of comment. +|[#| Go back to unclosed #if, #ifdef, or #else. +|]#| Go forward to unclosed #else or #endif. +|[(| Go back to unclosed '(' +|])| Go forward to unclosed ')' +|[{| Go back to unclosed '{' +|]}| Go forward to unclosed '}' + +|v_S| Select current block from "[(" to "])" +|v_P| Select current block from "[{" to "]}" + + +Editing local HTML files (WWW) *html-editing* +============================== + +Vim has some features which can help simplify the creation and maintenance of +HTML files, provided that the files you are editing are available on the local +file system. The "]f", "gf" and "CTRL-W f" commands can be used to jump to +the file whose name appears under the cursor, thus not only checking that the +link is valid (at least the file name part of the URL) but also providing a +quick and easy way to edit many related HTML pages at once. |gf| +A set of macros to help with editing html can be found on the Vim pages. |www| + + +Editing paragraphs without a line break *edit-no-break* +======================================= + +If you are typing text in Vim that will later go to a word processor, it is +useful to keep a paragraph as a single line. To make this more easy: +- set 'wrap' on, to make lines wrap |'wrap'| +- set 'linebreak' on, to make wrapping happen at a blank |'linebreak'| +- use "gk" and "gj" to move one screen line up or down |gj| + + +Switching screens in an xterm *xterm-screens* +============================= + +(From comp.editors, by Juergen Weigert, in reply to a question) + +>> Another question is that after exiting vim, the screen is left as it +>> was, i.e. the contents of the file I was viewing (editting) was left on +>> the screen. The output from my previous like "ls" were lost, +>> ie. no longer in the scrolling buffer. I know that there is a way to +>> restore the screen after exiting vim or other vi like editors, +>> I just don't know how. Helps are appreciated. Thanks. + +>I imagine someone else can answer this. I assume though that vim and vi do +>the same thing as each other for a given xterm setup. + +They not necessarily do the same thing, as this may be a termcap vs. +terminfo problem. You should be aware that there are two databases for +describing attributes of a particular type of terminal: termcap and +terminfo. This can cause differences when the entries differ *and* when of +the programs in question one uses terminfo and the other uses termcap. + +In your particular problem, you are looking for the control sequences +^[[?47h and ^[[?47l. These switch between xterms alternate and main screen +buffer. As a quick workaround a command sequence like + echo -n "^[[?47h"; vim ... ; echo -n "^[[?47l" +may do what you want. (My notation ^[ means the ESC character, further down +you'll see that the databases use \E instead). + +On startup, vim echoes the value of the termcap variable ti (terminfo: +smcup) to the terminal. When exiting, it echoes te (terminfo: rmcup). Thus +these two variables are the correct place where the above mentioned control +sequences should go. + +Compare your xterm termcap entry (found in /etc/termcap) with your xterm +terminfo entry (retrieved with /usr/5bin/infocmp -C xterm). Both should +contain entries similar to: + :te=\E[2J\E[?47l\E8:ti=\E7\E[?47h: + +PS: If you find any difference, someone (your sysadmin?) should better check + the complete termcap and terminfo database for consistency. + +NOTE: If you recompile Vim with SAVE_XTERM_SCREEN defined in feature.h, the +builtin xterm will include the mentioned "te" and "ti" entries. + + +Scrolling in Insert mode *scroll-insert* +======================== + +If you are in insert mode and you want to see something that is just off the +screen, you can use CTRL-X CTRL-E and CTRL-X CTRL-Y to scroll the screen. + |i_CTRL-X_CTRL-E| + +To make this easier, you could use these mappings: + :inoremap ^E ^X^E + :inoremap ^Y ^X^Y +(Type CTRL-V CTRL-E to enter ^E, CTRL-V CTRL-X to enter ^X, etc.) + +Also consider setting 'scrolloff' to a larger value, so that you can always see +some context around the cursor. If 'scrolloff' is bigger than half the window +height, the cursor will always be in the middle and the text is scrolled when +the cursor is moved up/down. + + +Smooth scrolling *scroll-smooth* +================ + +If you like the scrolling to go a bit smoother, you can use these mappings: + :map ^U ^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y + :map ^D ^E^E^E^E^E^E^E^E^E^E^E^E^E^E^E^E + +(Type CTRL-V CTRL-U to enter ^U, CTRL-V CTRL-Y to enter ^Y, etc.) + + +Correcting common typing mistakes *type-mistakes* +================================= + +When there are a few words that you keep on typing in the wrong way, make +abbreviations that correct them. For example: + :ab teh the + :ab fro for + + +Renaming files *rename-files* +============== + +Say I have a directory with the following files in them (directory picked at +random :-): + +addcr.c +alloc.c +amiga.c +... + +and I want to rename *.c *.bla. I'd do it like this: + +$ vim +:r! ls *.c +:%s/\(.*\).c/mv & \1.bla +:w !sh +:q! + + +Speeding up external commands *speed-up* +============================= + +In some situations, execution of an external command can be very slow. This +can also slow down wildcard expansion on Unix. Here are a few suggestions to +increase the speed. + +If your .cshrc (or other file, depending on the shell used) is very long, you +should separate it into a section for interactive use and a section for +non-interactive use (often called secondary shells). When you execute a +command from Vim like ":!ls", you do not need the interactive things (for +example, setting the prompt). Put the stuff that is not needed after these +lines: + + if ($?prompt == 0) then + exit 0 + endif + +Another way is to include the "-f" flag in the 'shell' option, e.g.: + + :set shell=csh\ -f + +(the backslash is needed to include the space in the option). +This will make csh completely skip the use of the .cshrc file. This may cause +some things to stop working though. + + +Useful mappings *useful-mappings* +=============== + +Here are a few mappings that some people like to use. + + :map ' ` +Make the single quote work like a backtick. Puts the cursor on the column of +a mark, instead of going to the first non-blank character in the line. + + *emacs_keys* +For Emacs-style editing on the command-line: +:cnoremap ^A start of line +:cnoremap ^B back one character +:cnoremap ^D delete character under cursor +:cnoremap ^E end of line +:cnoremap ^F forward one character +:cnoremap ^N recall newer command-line +:cnoremap ^P recall previous (older) command-line +:cnoremap ^B back one word +:cnoremap ^F forward one word + + *format_bullet_list* +This mapping will format any bullet list. It requires that there is an empty +line above and below each list entry: + + :map _f :set ai{O}{)^Wi gq}{dd5lDJ + +(<> notation |<>|. Note that ^W is "^" "W", not CTRL-W. You can copy/paste +this into Vim if '<' is not included in 'cpoptions') + +You also need to set 'textwidth' to a non-zero value, e.g., + set tw=70 + +A mapping that does about the same, but takes the indent for the list from the +first line (Note: this mapping is a single long line): + + :map _f :set ai}{a WWmmkD`mikkddpJgq}'mJOj + + +Compressing the help files *gzip-helpfile* +========================== + +For those of you who are really short on disk space, you can compress the help +files and still be able to view them with Vim. This makes accessing the help +files a bit slower. + +(1) Compress all the help files: "gzip doc/*.txt". + +(2) Edit "doc/vim_tags" and do ":%s=\\.txt/=.txt.gz/=" + (<> notation |<>|. You have to type a single backslash for \\ and + a real Tab for ) + +(3) Add these lines to your vimrc: + set helpfile=/vim_help.txt.gz + autocmd BufReadPre *.txt.gz set bin + autocmd BufReadPost *.txt.gz '[,']!gunzip + autocmd BufReadPost *.txt.gz set nobin + set cmdheight=2 + +Where is the directory where the help files are. If you have +already included autocommands to edit ".gz" files, you should omit the three +"autocmd" lines. Setting the command height to two is to avoid having to hit +return; it is not mandatory. + + +Executing shell commands in a window *shell_window* +==================================== + +There have been questions for the possibility to execute a shell in a window +inside Vim. The answer: you can't! Including this would add a lot of code to +Vim, which is a good reason not to do this. After all, Vim is an editor, it +is not supposed to do non-editing tasks. However, to get something like this, +you might try splitting your terminal screen or display window with the +"splitvt" program. You can probably find it on some ftp server. The person +that knows more about this is Sam Lantinga (slouken@cs.ucdavis.edu). + + +Pseudo-Ex mode *pseudo-Q* +============== + +If you really miss the "Q" command from Vi a lot, you might want to try this +mapping. It is far from a real Ex mode, but it looks like it. The "Q" +command can still be used for formatting if you use Visual mode. +You need to type "visual" to get out of Ex mode. Unfortunately, any +error message does that too. + +nnoremap Q :cnoremap :: +cabbrev visual cunmap + +(<> notation |<>|. You should enter this literally.) + +In a following version of Vim the "Q" command will be made Vi compatible. Use +"gq" for formatting text. + + vim:ts=8:sw=8:js:tw=78: diff --git a/usr.bin/vim/doc/vim_unix.txt b/usr.bin/vim/doc/vim_unix.txt new file mode 100644 index 00000000000..48631b983b7 --- /dev/null +++ b/usr.bin/vim/doc/vim_unix.txt @@ -0,0 +1,44 @@ +*vim_unix.txt* For Vim version 4.2. Last modification: 1996 Apr 23 + +This file contains the particularities for the Unix version of Vim. + +For compiling Vim on Unix see "INSTALL" and "Makefile" in the src directory. + +The default help filename is "/usr/local/lib/vim/vim_help.txt" +The files "$HOME/.vimrc" and "$HOME/.exrc" are used instead of "s:.vimrc" and +"s:.exrc". Additionally "/usr/local/etc/vimrc" is used first. +If "/usr/local/share" exists it is used instead of "/usr/local/lib" + +Temporary files (for filtering) are put in "/tmp". + +With wildcard expansion you can use <~> (home directory) and <$> +(environment variable). + + *fork* *spoon* +For executing external commands fork()/exec() is used when possible, otherwise +system() is used, which is a bit slower. The output of ":version" includes +"+fork" when fork()/exec() is used. This can be changed at compile time by +defining USE_SYSTEM in feature.h. (For forking of the GUI version see +|gui_fork|). + +Because terminal updating under Unix is often slow (e.g. serial line +terminal, shell window in suntools), the 'showcommand' and 'ruler' options +are default off. If you have a fast terminal, try setting them on. You might +also want to set 'ttyfast'. + +When using Vim in an xterm the mouse clicks can be used by Vim by setting +'mouse' to "a". If you then still want the xterm copy/paste with the mouse, +press the shift key when using the mouse. See |mouse_using|. + +To use colors in Vim you can use the following example (if your terminal +supports colors!): + :set t_me=^[[0;1;36m " normal mode (undoes t_mr and t_md) + :set t_mr=^[[0;1;33;44m " reverse (invert) mode + :set t_md=^[[1;33;41m " bold mode + :set t_se=^[[1;36;40m " standout end + :set t_so=^[[1;32;45m " standout mode + :set t_ue=^[[0;1;36m " underline end + :set t_us=^[[1;32m " underline mode start + +The file "tools/Vim132" is a shell script that can be used to put Vim in 132 +column mode on a vt100 and lookalikes. diff --git a/usr.bin/vim/doc/vim_w32.txt b/usr.bin/vim/doc/vim_w32.txt new file mode 100644 index 00000000000..b6cd5e2f1eb --- /dev/null +++ b/usr.bin/vim/doc/vim_w32.txt @@ -0,0 +1,280 @@ +*vim_w32.txt* For Vim version 4.2. Last modification: 1996 June 13 + +This file documents the idiosyncrasies of the Win32 version of Vim. + +The Win32 version was written by George V. Reilly . +The original Windows NT port was done by Roger Knobbe . + +The Win32 version of Vim works on both Windows NT and Windows 95. It is a +console-mode application (i.e., it runs in a command prompt window or a "DOS +box"). It is not a full-fledged Windows GUI application, although it may +become one some day. It will not run in the Win32s subsystem in Windows +3.1; use the 32-bit DOS version of Vim instead. See |vim_dos.txt|. + +Vim for Win32 compiles with the Microsoft Visual C++ 2.0 compiler and later, +and with the Borland C++ 4.5 32-bit compiler and later. It compiles on +Windows 95 and all four NT platforms: i386, Alpha, MIPS, and PowerPC. The +NT/i386 and the Windows 95 binaries are identical. Use Makefile.w32 to +compile with Visual C++ and Makefile.b32 to compile with Borland C++. + +You can set the colour used in five modes with nine termcap options. Which of +the five modes is used for which action depends on the |'highlight'| option. + + ":set t_mr=^V^[\|xxm" start of invert mode + ":set t_md=^V^[\|xxm" start of bold mode + ":set t_me=^V^[\|xxm" back to normal text + + ":set t_so=^V^[\|xxm" start of standout mode + ":set t_se=^V^[\|xxm" back to normal text + + ":set t_us=^V^[\|xxm" start of underline mode + ":set t_ue=^V^[\|xxm" back to normal text + + ":set t_ZH=^V^[\|xxm" start of italics mode + ":set t_ZR=^V^[\|xxm" back to normal text + +^V is CTRL-V +^[ is ESC +xx must be replaced by a decimal code, which is the foreground colour number + and background colour number added together: + +COLOUR FOREGROUND BACKGROUND + black 0 0 + blue 1 16 + green 2 32 + cyan 3 48 + red 4 64 + magenta 5 80 + brown 6 96 + lightgray 7 112 + darkgray 8 128 + lightblue 9 144 + lightgreen 10 160 + lighcyan 11 176 + lightred 12 192 + lightmagenta 13 208 + yellow 14 224 + white 15 240 + +When you use 0, the colour is reset to the one used when you started Vim +(usually 7, lightgray on black, but you can override this in NT. If you have +overridden the default colours in a command prompt, you may need to adjust +some of the highlight colours in your vimrc---see below). + +The defaults for the various highlight modes are: + t_mr 112 reverse mode: black text on lightgray + t_md 63 bold mode: white text on cyan + t_me 0 normal mode (revert to default) + + t_so 31 standout mode: white text on blue + t_se 0 standout mode end (revert to default) + + t_czh 225 italic mode: blue text on yellow + t_czr 0 italic mode end (revert to default) + + t_us 67 underline mode: cyan text on red + t_ue 0 underline mode end (revert to default) + +These colours were chosen because they also look good when using an inverted +display, but you can change them to your liking. + +Example: +:set t_mr=^V^[\|97m " start of invert mode: blue on brown +:set t_md=^V^[\|67m " start of bold mode: cyan on red +:set t_me=^V^[\|112m " back to normal mode: black on light gray + +:set t_so=^V^[\|37m " start of standout mode: magenta on green +:set t_se=^V^[\|112m " back to normal mode: black on light gray + +If the "tx" (textmode) option is set (which is the default), Vim will accept +a single or a pair for end-of-line. When writing a file, Vim +will use . Thus, if you edit a file and write it, is replaced +with . If the "tx" option is not set, a single will be used +for end-of-line. A will be shown as ^M. You can use Vim to replace + with by reading in any mode and writing in text mode (":se +tx"). You can use Vim to replace with by reading in text mode +and writing in non-text mode (":se notx"). 'textmode' is set automatically +when 'textauto' is on (which is the default), so you don't really have to +worry about what you are doing. |'textmode'| |'textauto'| + +When 'restorescreen' is set (which is the default), Vim will restore the +original contents of the console when exiting or when executing external +commands. If you don't want this, use ":set nors". |'restorescreen'| + +Using backslashes in file names can be a problem. Vi halves the number of +backslashes for some commands. Vim is a bit more tolerant and backslashes +are not removed from a file name, so ":e c:\foo\bar" works as expected. But +when a backslash is used before a special character (space, comma, backslash, +etc.), it is removed. Use slashes to avoid problems: ":e c:/foo/bar" works +fine. Vim will replace the slashes with backslashes internally, to avoid +problems with some MS-DOS programs and Win32 programs. + +If you want to edit a script file or a binary file, you should reset the +'textmode' and 'textauto' options before loading the file. Script files and +binary files may contain single characters which would be replaced with +. You can reset 'textmode' and 'textauto' automatically by starting +Vim with the "-b" (binary) option. + +You should set the environment variable "VIM" to the directory where the Vim +documentation files are. If "VIM" is used but not defined, "HOME" is tried +too. + +If the HOME environment variable is not set, the value "C:/" is used as a +default. + +The default help filename is "$VIM\vim_help.txt". If the environment variable +$VIM is not defined or the file is not found, the Win32 search path is used to +search for the file "vim_help.txt". If you do not want to put "vim_help.txt" +in your search path, use the command ":set helpfile=pathname" to tell Vim +where the help file is. |'helpfile'| + +Vim will look for initializations in eight places. The first that is found +is used and the others are ignored. The order is: + - The environment variable VIMINIT + - The file "$VIM/_vimrc" + - The file "$HOME/_vimrc" + - The file "$VIM/.vimrc" + - The file "$HOME/.vimrc" + - The environment variable EXINIT + - The file "$VIM/_exrc" + - The file "$HOME/_exrc" + +The only kind of terminal type that the Win32 version of Vim understands is +"win32", which is built-in. If you set the TERM environment variable to +anything else, you will probably get very strange behaviour from Vim. This is +most likely to happen when running a non-standard shell such as Cygnus's +GNU-Win32 bash (http://www.cygnus.com/misc/gnu-win32) or the one in the MKS +toolkit. Use "unset TERM" before starting Vim! + +The ":cd" command recognizes the drive specifier and changes the current +drive. Use ":cd c:" to make drive C the active drive. Use ":cd d:\foo" to go +to the directory "foo" in the root of drive D. UNC names are also recognized; +e.g., ":cd \\server\share\dir". |:cd| + +Use CTRL-BREAK instead of CTRL-C to interrupt searches. The CTRL-C is not +detected until a key is read. + +Temporary files (for filtering) are put in the current directory. + +The default for the 'sh' ('shell') option is "command.com" on Windows 95 and +"cmd.exe" on Windows NT. If SHELL is defined, it is used instead, and if +SHELL is not defined but COMSPEC is, COMPSPEC is used. External commands are +started with "command /c ". Typing CTRL-Z starts a new +command subshell. Return to Vim with "exit". |'shell'| |CTRL-Z| + +The Win32 binary was compiled with Visual C++ version 4.0, using Makefile.w32. +Other compilers should also work. If you get all kinds of strange error +messages when compiling (you shouldn't with the Microsoft or Borland 32-bit +compilers), try adding characters at the end of each line. This can be +done with the addcr program: "nmake -f makefile.w32 addcr". This will compile +addcr.c to addcr.exe and then execute the addcr.bat file. Sometimes this +fails. In that case, execute the addcr.bat file from the DOS prompt. + +The Win32 version of Vim supports using the mouse. If you have a two-button +mouse, the middle button can be emulated by pressing both left and right +buttons simultaneously. |mouse_using| + +Q. Why does the Win32 version of Vim update the screen so slowly on Windows 95? +A. The support for Win32 console mode applications is very buggy in Win95. + For some unknown reason, the screen updates very slowly when Vim is run at + one of the standard resolutions (80x25, 80x43, or 80x50) and the 16-bit DOS + version updates the screen much more quickly than the Win32 version. + However, if the screen is set to some other resolution, such as by ":set + columns=100" or ":set lines=40", screen updating becomes about as fast as + it is with the 16-bit version. + + WARNING: Changing 'columns' may make Windows 95 crash while updating the + window (complaints --> Microsoft). Since this mostly works, this has not + been disabled, but be careful with changing 'columns'. + + Changing the screen resolution makes updates faster, but it brings problems + with it of its own. External commands (e.g., ":!dir") can cause Vim to + freeze when the screen is set to a non-standard resolution, particularly + when 'columns' is not equal to 80. It is not possible for Vim to reliably + set the screen resolution back to the value it had upon startup before + running external commands, so if you change the number of 'lines' or + 'columns', be very, very careful. In fact, Vim will not allow you to + execute external commands when 'columns' is not equal to 80, because it is + so likely to freeze up afterwards. + + None of the above applies on Windows NT. Screen updates are fast, no + matter how many 'lines' or 'columns' the window is set to, and external + commands will not cause Vim to freeze. + +Q. So if the Win32 version updates the screen so slowly on Windows 95 and the + 16-bit DOS version updates the screen quickly, why would I want to run the + Win32 version? +A. Firstly, the Win32 version isn't that slow, especially when the screen is + set to some non-standard number of 'lines' or 'columns'. Secondly, the + 16-bit DOS version has some severe limitations: it can't edit big files and + it doesn't know about long filenames. The Win32 version doesn't have these + limitations and it's faster overall (the same is true for the 32-bit DJGPP + DOS version of Vim). The Win32 version is smarter about handling the + screen, the mouse, and the keyboard than the DJGPP version is. + +Q. And what about the 16-bit DOS version versus the Win32 version on NT? +A. There are no good reasons to run the 16-bit DOS version on NT. The Win32 + version updates the screen just as fast as the 16-bit version does when + running on NT. All of the above disadvantages apply. Finally, DOS + applications can take a long time to start up and will run more slowly. On + non-Intel NT platforms, the DOS version is almost unusably slow, because it + runs on top of an 80x86 emulator. + +Q. Why can't I paste into Vim when running Windows 95? +A. In the properties dialog box for the MS-DOS window, go to "MS-DOS + Prompt/Misc/Fast pasting" and make sure that it is NOT checked. You should + also do ":set paste" in Vim to avoid unexpected effects. |'paste'| + +Q. How do I type dead keys on Windows 95? + (A dead key is an accent key, such as acute, grave, or umlaut, that doesn't + produce a character by itself, but when followed by another key, produces + an accented character, such as a-acute, e-grave, u-umlaut, n-tilde, and so + on. Very useful for most European languages. English-language keyboard + layouts don't use dead keys, as far as we know.) +A. You don't. The console mode input routines simply do not work correctly in + Windows 95, and I have not been able to work around them. In the words + of a senior developer at Microsoft: + Win95 console support has always been and will always be flaky. + + The flakiness is unavoidable because we are stuck between the world of + MS-DOS keyboard TSRs like KEYB (which wants to cook the data; + important for international) and the world of Win32. + + So keys that don't "exist" in MS-DOS land (like dead keys) have a + very tenuous existence in Win32 console land. Keys that act + differently between MS-DOS land and Win32 console land (like + capslock) will act flaky. + + Don't even _mention_ the problems with multiple language keyboard + layouts... + + You may be able to fashion some sort of workaround with the digraphs + mechanism. |digraphs| + + Alternatively, you can try one of the DOS versions of Vim where dead keys + reportedly do work. + +Q. How do I type dead keys on Windows NT? +A. Dead keys work on NT. Just type them as you would in any other application. + +Q. When will a real GUI version of Vim (gvim) for Win32 with scrollbars, + menus, pasting from the clipboard, and so on become available? +A. A few months after Vim 4.0 is released. Apart from the features you + might expect in gvim (see |vim_gui.txt|), it is expected that a real GUI + version will also be able to handle dead keys correctly and that the + problems with external commands will be a thing of the past. + +Q. I'm using Vim to edit a symbolically linked file on a Unix NFS file server. + When I write the file, Vim does not "write through" the symlink. Instead, + it deletes the symbolic link and creates a new file in its place. Why? +A. On Unix, Vim is prepared for links (symbolic or hard). A backup copy of + the original file is made and then the original file is overwritten. This + assures that all properties of the file remain the same. On non-Unix + systems, the original file is renamed and a new file is written. Only the + protection bits are set like the original file. However, this doesn't work + properly when working on an NFS-mounted file system where links and other + things exist. The only way to fix this in the current version is not + making a backup file, by ":set nobackup nowritebackup" |'writebackup'| + + + vim:ts=8:sw=8:js:tw=78:fo=tcq2: diff --git a/usr.bin/vim/doc/vim_win.txt b/usr.bin/vim/doc/vim_win.txt new file mode 100644 index 00000000000..1a3b5d7a388 --- /dev/null +++ b/usr.bin/vim/doc/vim_win.txt @@ -0,0 +1,561 @@ +*vim_win.txt* For Vim version 4.2. Last modification: 1996 June 16 + +Editing with multiple windows and buffers. + +The commands which have been added to use multiple windows and buffers are +explained here. Additionally, there are explanations for commands that work +differently when used in combination with more than one window. + + +A window is a viewport onto a buffer. You can use multiple windows on one +buffer, or several windows on different buffers. + +A buffer is a file loaded into memory for editing. The original file remains +unchanged until you write the buffer to the file. + +A buffer can be in one of three states: + + *active_buffer* +active: The buffer is displayed in a window. If there is a file for this + buffer, it has been read into the buffer. The buffer may have been + modified. + *hidden_buffer* +hidden: The buffer is not displayed. If there is a file for this buffer, it + has been read into the buffer. The buffer may have been modified. + *inactive_buffer* +inactive: The buffer is not displayed and does not contain anything. Options + for the buffer are remembered if the file was once loaded. + +In a table: + +state displayed loaded ":buffers" + in window shows +active yes yes ' ' +hidden no yes 'h' +inactive no no '-' + + +Starting Vim +------------ + +By default, Vim starts with one window, just like Vi. + +The "-o" argument to Vim can be used to open a window for each file in the +argument list: "Vim -o file1 file2 file3" will open three windows. + +"-oN", where N is a decimal number, opens N windows. If there are more +filenames than windows, only N windows are opened and some files do not get a +window. If there are more windows than filenames, the last few windows will +be editing empty buffers. + +If there are many filenames, the windows will become very small. You might +want to set the 'winheight' option to create a workable situation. + + *status_line* +A status line will be used to separate windows. The 'laststatus' option tells +when the last window also has a status line: + 'laststatus' = 0 never a status line + 'laststatus' = 1 status line if there is more than one window + 'laststatus' = 2 always a status line +Normally, inversion is used to display the status line. This can be changed +with the 's' character in the 'highlight' option. For example, "sb" sets it to +bold characters. If no highlighting is used for the status line ("sn"), the +'=' character is used. If the mouse is supported and enabled with the 'mouse' +option, a status line can be dragged to resize windows. + +Note: If you expect your status line to be in reverse video and it isn't, +check if the 'highlight' option contains "si". In version 3.0, this meant to +invert the status line. Now it should be "sr", reverse the status line, as +"si" now stands for italic! If italic is not available on your terminal, the +status line is inverted anyway; you will only see this problem on terminals +that have termcap codes for italics. + + +Opening a new window +-------------------- + +CTRL-W s *CTRL-W_s* +CTRL-W S *CTRL-W_S* +CTRL-W CTRL-S *CTRL-W_CTRL-S* +:[N]sp[lit] *:sp* *:split* + Split current window in two. The result is two viewports on + the same file. Make new window N high (default is to use half + the height of the current window). Reduces the current window + height to create room (and others, if the 'equalalways' option + is set). (Note: CTRL-S does not work on all terminals). + +CTRL-W n *CTRL-W_n* +CTRL-W CTRL_N *CTRL-W_CTRL-N* +:[N]new *:new* + Create a new window and start editing an empty file in it. + Make new window N high (default is to use half the existing + height). Reduces the current window height to create room (and + others, if the 'equalalways' option is set). + +:[N]new [+command] {file} +:[N]split [+command] {file} *:split_f* + Create a new window and start editing file {file} in it. If + [+command] is given, execute the command when the file has + been loaded. Make new window N high (default is to use half + the existing height). Reduces the current window height to + create room (and others, if the 'equalalways' option is set). + +:[N]sv[iew] [+command] {file} *:sv* *:sview* + Same as ":split", but set 'readonly' option for this buffer. + +CTRL-W CTRL-^ *CTRL-W_CTRL-^* *CTRL-W_^* +CTRL-W ^ Does ":split #", split window in two and edit alternate file. + When a count is given, it becomes ":split #N", split window + and edit buffer N. + +Closing a window +---------------- + +CTRL-W q *CTRL-W_q* +CTRL-W CTRL-Q *CTRL-W_CTRL-Q* +:quit Quit current window, unless the buffer was changed and there + are no other windows for this buffer. When quitting the last + window (not counting help windows), exit Vim. (Note: CTRL-Q + does not work on all terminals) + +:quit! Quit current window. If this was the last window for a buffer, + any changes to that buffer are lost. When quitting the last + window (not counting help windows), exit Vim. + +CTRL-W CTRL-C *CTRL-W_CTRL-C* +CTRL-W c *CTRL-W_c* *:clo* *:close* +:clo[se] Quit current window, unless it is the last window on the + screen. The buffer becomes hidden (unless there is another + window editing it). (Note: CTRL-W CTRL-C does not work). + +CTRL-W o *CTRL-W_o* +CTRL-W CTRL-O *CTRL-W_CTRL-O* *:on* *:only* +:on[ly] Make the current window the only one on the screen. All other + windows are closed. All buffers in the other windows become + hidden. + + +Moving the cursor to other windows +---------------------------------- + +CTRL-W *CTRL-W_* +CTRL-W CTRL-J *CTRL-W_CTRL-J* *CTRL-W_j* +CTRL-W j move cursor to Nth window below current one. + +CTRL-W *CTRL-W_* +CTRL-W CTRL-K *CTRL-W_CTRL-K* *CTRL-W_k* +CTRL-W k move cursor to Nth window above current one. + +CTRL-W w *CTRL-W_w* *CTRL-W_CTRL-W* +CTRL-W CTRL-W Without count: move cursor to window below current one. If + there is no window below, go to top window. + With count: go to Nth window. + + *CTRL-W_W* +CTRL-W W Without count: move cursor to window above current one. If + there is no window above, go to bottom window. + With count: go to Nth window. + +CTRL-W t *CTRL-W_t* *CTRL-W_CTRL-T* +CTRL-W CTRL-T move cursor to top window. + +CTRL-W b *CTRL-W_b* *CTRL-W_CTRL-B* +CTRL-W CTRL-B move cursor to bottom window. + +CTRL-W p *CTRL-W_p* *CTRL-W_CTRL-P* +CTRL-W CTRL-P go to previous (last accessed) window. + +If Visual mode is active and the new window is not for the same buffer, the +Visual mode is ended. + + +Moving windows around +--------------------- + +CTRL-W r *CTRL-W_r* *CTRL-W_CTRL-R* +CTRL-W CTRL-R Rotate windows downwards. The first window becomes the second + one, the second one becomes the third one, etc. The last + window becomes the first window. The cursor remains in the + same window. + + *CTRL-W_R* +CTRL-W R Rotate windows upwards. The second window becomes the first + one, the third one becomes the second one, etc. The first + window becomes the last window. The cursor remains in the + same window. + +CTRL-W x *CTRL-W_x* *CTRL-W_CTRL-X* +CTRL-W CTRL-X Without count: Exchange current window with next one. If there + is no next window, exchange with previous window. + With count: Exchange current window with Nth window (first + window is 1). The cursor is put in the other window. + + +Window resizing +--------------- + + *CTRL-W_=* +CTRL-W = make all windows (almost) equally high. + +:res[ize] -N *:res* *:resize* *CTRL-W_-* +CTRL-W - decrease current window height by N + +:resize +N *CTRL-W_+* +CTRL-W + increase current window height by N + +:resize [N] +CTRL-W CTRL-_ *CTRL-W_CTRL-_* *CTRL-W__* +CTRL-W _ set current window height to N (default: highest possible) + +z set current window height to nr + +You can also resize the window by dragging a status line up or down with the +mouse. This only works if the version of Vim that is being used supports the +mouse and the 'mouse' option has been set to enable it. + +The option 'winheight' ('wh') is used to set the minimal window height of the +current window. This option is used each time another window becomes the +current window. If the option is '0', it is disabled. Set 'winheight' to a +very large value, e.g., '9999', to make the current window always fill all +available space. Set it to a reasonable value, e.g., '10', to make editing in +the current window comfortable. + +When the option 'equalalways' ('ea') is set, all the windows are automatically +made the same size after splitting or closing a window. If you don't set this +option, splitting a window will reduce the size of the current window and +leave the other windows the same. When closing a window, the extra lines are +given to the window above it. + +The option 'commandheight' ('ch') is used to set the height of the command +line. If you are annoyed by the "Hit RETURN to continue" questions for long +messages, set this option to 2 or 3. + +If there is only one window, resizing that window will also change the command +line height. If there are several windows, resizing the current window will +also change the height of the window below it (and sometimes the window above +it). + + +Exiting Vim with multiple windows or buffers +-------------------------------------------- + + *:qa* *:qall* +:qa[ll] Exit Vim, unless there are some buffers which have been + changed. (Use ":bmod" to go to the next modified buffer). + +:qall! Exit Vim. Any changes to buffers are lost. + +:wqa[ll] *:wqa* *:wqall* *:xa* *:xall* +:xa[ll] Write all changed buffers and exit Vim. If there are buffers + without a file name, which are readonly or which cannot be + written for another reason, Vim is not quit. + +:wqall! +:xall! Write all changed buffers, even the ones that are readonly, + and exit Vim. If there are buffers without a file name or + which cannot be written for another reason, Vim is not quit. + + +Writing with multiple buffers +----------------------------- + + *:wa* *:wall* +:wa[ll] Write all changed buffers. Buffers without a file name or + which are readonly are not written. + +:wa[ll]! Write all changed buffers, even the ones that are readonly. + Buffers without a file name are not written. + + +Overview of argument and buffer list commands +--------------------------------------------- + + args list buffer list meaning +1. :[N]argument [N] 11. :[N]buffer [N] to arg/buf N +2. :[N]next [file ..] 12. :[N]bnext [N] to Nth next arg/buf +3. :[N]Next [N] 13. :[N]bNext [N] to Nth previous arg/buf +4. :[N]previous [N] 14. :[N]bprevious [N] to Nth previous arg/buf +5. :rewind 15. :brewind to first arg/buf +6. :last 16. :blast to last arg/buf +7. :all 17. :ball edit all args/buffers + 18. :unhide edit all loaded buffers + 19. :[N]bmod [N] to Nth modified buf + + split & args list split & buffer list meaning +21. :[N]sargument [N] 31. :[N]sbuffer [N] split + to arg/buf N +22. :[N]snext [file ..] 32. :[N]sbnext [N] split + to Nth next arg/buf +23. :[N]sNext [N] 33. :[N]sbNext [N] split + to Nth previous arg/buf +24. :[N]sprevious [N] 34. :[N]sbprevious [N] split + to Nth previous arg/buf +25. :srewind 35. :sbrewind split + to first arg/buf +26. :slast 36. :sblast split + to last arg/buf +27. :sall 37: :sball edit all args/buffers + 38. :sunhide edit all loaded buffers + 39. :[N]sbmod [N] split + to Nth modified buf + +40. :args list of arguments +41. :buffers list of buffers + +The meaning of [N] depends on the command: + [N] is number of buffers to go forward/backward on ?2, ?3, and ?4 + [N] is an argument number, defaulting to current argument, for 1 and 21 + [N] is a buffer number, defaulting to current buffer, for 11 and 31 + [N] is a count for 19 and 39 + +Note: ":next" is an exception, because it must accept a list of file names +for compatibility with Vi. + + +The argument list and multiple windows +-------------------------------------- + +The current position in the argument list can be different for each window. +Remember that when doing ":e file", the position in the argument list stays +the same, but you are not editing the file at that position. To indicate +this, the file message (and the title, if you have one) shows +"(file (N) of M)", where "(N)" is the current position in the file list, and +"M" the number of files in the file list. + +All the entries in the argument list are added to the buffer list. Thus, you +can also get to them with the buffer list commands, like ":bnext". + +:[N]al[l] [N] *:al* *:all* *:sal* *:sall* +:[N]sal[l] [N] Rearrange the screen to open one window for each argument. + All other windows are closed (buffers become hidden). When a + count is given, this is the maximum number of windows to open. + +:[N]sa[rgument][!] [N] *:sa* *:sargument* + Short for ":split | argument [N]": split window and go to Nth + argument. But when there is no such argument, the window is + not split. + +:[N]sn[ext][!] [file ..] *:sn* *:snext* + Short for ":split | [N]next": split window and go to Nth next + argument. But when there is no next file, the window is not + split. + +:[N]spr[evious][!] [N] *:spr* *:sprevious* +:[N]sN[ext][!] [N] *:sN* *:sNext* + Short for ":split | [N]Next": split window and go to Nth + previous argument. But when there is no previous file, the + window is not split. + + *:sr* *:srewind* +:sr[ewind][!] Short for ":split | rewind": split window and go to first + argument. But when there is no argument list, the window is + not split. + + *:sla* *:slast* +:sla[st][!] Short for ":split | last": split window and go to last + argument. But when there is no argument list, the window is + not split. + + +Tag or file name under the cursor +--------------------------------- + + *:sta* *:stag* +:sta[g][!] [tagname] + Does ":tag[!] [tagname]" and splits the window for the found + tag. See also |:tag|. + +CTRL-W ] *CTRL-W_]* *CTRL-W_CTRL-]* +CTRL-W CTRL-] split current window in two. Use identifier under cursor as a + tag and jump to it in the new upper window. Make new window N + high. + +CTRL-W f *CTRL-W_f* *CTRL-W_CTRL-F* +CTRL-W CTRL-F split current window in two. Edit file name under cursor. + Like ":split ]f", but window isn't split if the file does not + exist. Uses the 'path' variable as a list of directory names + where to look for the file. Also the path for current file is + used to search for the file name. If the name is a hypertext + link that looks like "type://machine/path", only "/path" is + used. + + +Using hidden buffers +-------------------- + +A hidden buffer is not displayed in a window, but is still loaded into memory. +This makes it possible to jump from file to file, without the need to read or +write the file every time and having to keep the file in a window. + +If the option 'hidden' ('hid') is set, abandoned buffers are kept for all +commands that start editing another file: ":edit", ":next", ":tag", etc. The +commands that move through the buffer list make the current buffer hidden +although the 'hidden' option is not set (see below). + +You can make a hidden buffer not hidden by starting to edit it with any +command. Or by deleting it with the ":bdelete" command. + + *hidden_quit* +When you try to quit Vim while there is a hidden, modified buffer, you will +get an error message and Vim will make that buffer the current buffer. You +can then decide to write this buffer (":wq") or quit without writing (":q!"). +Be careful: there may be more hidden, modified buffers! + +:files *:files* +:buffers *:buffers* *:ls* +:ls Show all buffers. Example: + + 1 #h "/test/text" line 1 + 2 - "asdf" line 0 + 3 % + "version.c" line 1 + + Each buffer has a unique number. That number will not change, + so you can always go to a specific buffer with ":buffer N" or + "N CTRL-^", where N is the buffer number. + + '-' indicates a buffer that is not loaded. 'h' indicates a + hidden buffer: It is loaded, but currently not displayed in a + window. '%' indicates the buffer in the current window. '#' + indicates the alternate buffer for ":e #" or CTRL-^. '+' + indicates a modified buffer. + +:[N]bd[elete] *:bd* *:bdelete* +:bd[elete] [N] + Unload buffer [N] (default: current buffer) and delete it from + the buffer list. If the buffer was changed, this fails. The + file remains unaffected. Any windows for this buffer are + closed. If buffer [N] is the current buffer, the next buffer + (displayed in a window) becomes the current buffer. + +:[N]bdelete! +:bdelete! [N] + Unload buffer [N] (default: current buffer) and delete it from + the buffer list. If the buffer was changed the changes are + lost. The file remains unaffected. Any windows for this + buffer are closed. If buffer [N] is the current buffer, the + next buffer (displayed in a window) becomes the current + buffer. + +:bdelete[!] {bufname} + Like ":bdelete[!] [N]", but buffer given by name. Note that a + buffer whose name is a number cannot be referenced by that + name; use the buffer number instead. Insert a backslash + before a space in a buffer name. + +:N,Mbdelete[!] do ":bdelete[!]" for all buffers in the range N to M + (inclusive). + +:bdelete[!] N1 N2 ... + do ":bdelete[!]" for buffer N1, N2, etc. The arguments can be + buffer numbers or buffer names (but not buffer names that are + a number). Insert a backslash before a space in a buffer + name. + +:[N]bun[load] *:bun* *:bunload* +:bun[load] [N] + Unload buffer [N] (default: current buffer). The memory + allocated for this buffer will be freed. The buffer remains + in the buffer list. If the buffer was changed, this fails. + Any windows for this buffer are closed. If buffer [N] is the + current buffer, the next buffer (displayed in a window) + becomes the current buffer. + +:[N]bunload! +:bunload! [N] + Unload buffer [N] (default: current buffer). The memory + allocated for this buffer will be freed. The buffer remains + in the buffer list. If the buffer was changed, the changes + are lost. Any windows for this buffer are closed. If buffer + [N] is the current buffer, the next buffer (displayed in a + window) becomes the current buffer. + +:bunload[!] {bufname} + Like ":bunload[!] [N]", but buffer given by name. Note that a + buffer whose name is a number cannot be referenced by that + name; use the buffer number instead. Insert a backslash + before a space in a buffer name. + +:N,Mbunload[!] do ":bunload[!]" for all buffers in the range N to M + (inclusive). + +:bunload[!] N1 N2 ... + do ":bunload[!]" for buffer N1, N2, etc. The arguments can be + buffer numbers or buffer names (but not buffer names that are + a number). Insert a backslash before a space in a buffer + name. + +:[N]b[uffer] [N] *:b* *:buffer* + Edit buffer [N] from the buffer list. If [N] is not given, + the current buffer remains being edited. + +:[N]b[uffer] {filename} + Edit buffer for {filename} from the buffer list. + +:[N]sb[uffer] [N] *:sb* *:sbuffer* + Split window and edit buffer [N] from the buffer list. If [N] + is not given, the current buffer is edited. + +:[N]sb[uffer] {filename} + Split window and edit buffer for {filename} from the buffer + list. + + *:bn* *:bnext* +:[N]bn[ext] [N] + Go to [N]th next buffer in buffer list. [N] defaults to one. + Wraps around the end of the buffer list. + + *:sbn* *:sbnext* +:[N]sbn[ext] [N] + Split window and go to [N]th next buffer in buffer list. + Wraps around the end of the buffer list. + +:[N]bN[ext] [N] *:bN* *:bNext* *:bp* *:bprevious* +:[N]bp[revious] [N] + Go to [N]th previous buffer in buffer list. [N] defaults to + one. Wraps around the start of the buffer list. + +:[N]sbN[ext] [N] *:sbN* *:sbNext* *:sbp* *:sbprevious* +:[N]sbp[revious] [N] + Split window and go to [N]th previous buffer in buffer list. + Wraps around the start of the buffer list. + + *:br* *:brewind* +:br[ewind] Go to first buffer in buffer list. + + *:sbr* *:sbrewind* +:sbr[ewind] Split window and go to first buffer in buffer list + + *:bl* *:blast* +:bl[ast] Go to last buffer in buffer list. + + *:sbl* *:sblast* +:sbl[ast] Split window and go to last buffer in buffer list. + +:[N]bm[odified] [N] *:bm* *:bmodified* + Go to [N]th next modified buffer in buffer list. + +:[N]sbm[odified] [N] *:sbm* *:sbmodified* + Split window and go to [N]th next modified buffer in buffer + list. + +:[N]unh[ide] [N] *:unh* *:unhide* *:sun* *:sunhide* +:[N]sun[hide] [N] + Rearrange the screen to open one window for each loaded buffer + in the buffer list. When a count is given, this is the + maximum number of windows to open. + +:[N]ba[ll] [N] *:ba* *:ball* *:sba* *:sball* +:[N]sba[ll] [N] Rearrange the screen to open one window for each buffer in + the buffer list. When a count is given, this is the maximum + number of windows to open. + + +Memory usage limits +------------------- + +The option 'maxmem' ('mm') is used to set the maximum memory used for one +buffer (in kilobytes). 'maxmemtot' is used to set the maximum memory used for +all buffers (in kilobytes). The defaults depend on the system used. For the +Amiga and MS-DOS, 'maxmemtot' is set depending on the amount of memory +available. If you don't like Vim to swap to a file, set 'maxmem' and +'maxmemtot' to a very large value. The swapfile will then only be used for +recovery. If you don't want a swapfile at all, set 'updatecount' to 0, or +use the "-n" argument when starting Vim. Note that the 'maxmem' option is +only used when a buffer is created. Changing this option does not affect +buffers that have already been loaded. Thus you can set it to different +values for different files. 'maxmemtot' works always. + + vim:ts=8:sw=8:js:tw=78:fo=tcq2:isk=!-~,^*,^\|,^\": diff --git a/usr.bin/vim/edit.c b/usr.bin/vim/edit.c new file mode 100644 index 00000000000..48c98cc1405 --- /dev/null +++ b/usr.bin/vim/edit.c @@ -0,0 +1,3579 @@ +/* $OpenBSD: edit.c,v 1.1.1.1 1996/09/07 21:40:26 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * edit.c: functions for insert mode + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" +#include "ops.h" /* for op_type */ + +#ifdef INSERT_EXPAND +/* + * definitions used for CTRL-X submode + */ +#define CTRL_X_WANT_IDENT 0x100 + +#define CTRL_X_NOT_DEFINED_YET (1) +#define CTRL_X_SCROLL (2) +#define CTRL_X_WHOLE_LINE (3) +#define CTRL_X_FILES (4) +#define CTRL_X_TAGS (5 + CTRL_X_WANT_IDENT) +#define CTRL_X_PATH_PATTERNS (6 + CTRL_X_WANT_IDENT) +#define CTRL_X_PATH_DEFINES (7 + CTRL_X_WANT_IDENT) +#define CTRL_X_FINISHED (8) +#define CTRL_X_DICTIONARY (9 + CTRL_X_WANT_IDENT) + +struct Completion +{ + char_u *str; + char_u *fname; + int original; + struct Completion *next; + struct Completion *prev; +}; + +struct Completion *first_match = NULL; +struct Completion *curr_match = NULL; + +static int add_completion __ARGS((char_u *str, int len, char_u *, int dir)); +static int make_cyclic __ARGS((void)); +static void complete_dictionaries __ARGS((char_u *, int)); +static void free_completions __ARGS((void)); +static int count_completions __ARGS((void)); +#endif /* INSERT_EXPAND */ + +#define BACKSPACE_CHAR 1 +#define BACKSPACE_WORD 2 +#define BACKSPACE_WORD_NOT_SPACE 3 +#define BACKSPACE_LINE 4 + +static void change_indent __ARGS((int type, int amount, int round)); +static void insert_special __ARGS((int, int)); +static void start_arrow __ARGS((FPOS *end_insert_pos)); +static void stop_arrow __ARGS((void)); +static void stop_insert __ARGS((FPOS *end_insert_pos)); +static int echeck_abbr __ARGS((int)); + +static FPOS Insstart; /* This is where the latest insert/append + * mode started. */ +static colnr_t Insstart_textlen; /* length of line when insert started */ +static colnr_t Insstart_blank_vcol; /* vcol for first inserted blank */ + +static char_u *last_insert = NULL; + /* the text of the previous insert */ +static int last_insert_skip; + /* number of chars in front of previous insert */ +static int new_insert_skip; + /* number of chars in front of the current insert */ +#ifdef INSERT_EXPAND +static char_u *original_text = NULL; + /* Original text typed before completion */ +#endif + +#ifdef CINDENT +static int can_cindent; /* may do cindenting on this line */ +#endif + +/* + * edit() returns TRUE if it returns because of a CTRL-O command + */ + int +edit(initstr, startln, count) + int initstr; + int startln; /* if set, insert at start of line */ + long count; +{ + int c; + int cc; + char_u *ptr; + linenr_t lnum; + int temp = 0; + int mode; + int lastc = 0; + colnr_t mincol; + static linenr_t o_lnum = 0; + static int o_eol = FALSE; + int need_redraw = FALSE; + int i; + int did_backspace = TRUE; /* previous char was backspace */ +#ifdef RIGHTLEFT + int revins; /* reverse insert mode */ + int revinschars = 0; /* how much to skip after edit */ + int revinslegal = 0; /* was the last char 'legal'? */ + int revinsscol = -1; /* start column of revins session */ +#endif +#ifdef INSERT_EXPAND + FPOS first_match_pos; + FPOS last_match_pos; + FPOS *complete_pos; + char_u *complete_pat = NULL; + char_u *tmp_ptr; + char_u *mesg = NULL; /* Message about completion */ + char_u *quick_m; /* Message without sleep */ + int started_completion = FALSE; + colnr_t complete_col = 0; /* init for gcc */ + int complete_direction; + int done_dir = 0; /* Found all matches in this + * direction */ + int num_matches; + char_u **matches; + regexp *prog; + int save_sm = -1; /* init for gcc */ + int save_p_scs; +#endif +#ifdef CINDENT + int line_is_white = FALSE; /* line is empty before insert */ +#endif + FPOS tpos; + + /* sleep before redrawing, needed for "CTRL-O :" that results in an + * error message */ + if (msg_scroll || emsg_on_display) + { + mch_delay(1000L, TRUE); + msg_scroll = FALSE; + emsg_on_display = FALSE; + } +#ifdef SLEEP_IN_EMSG + if (need_sleep) + { + mch_delay(1000L, TRUE); + need_sleep = FALSE; + } +#endif + +#ifdef USE_MOUSE + /* + * When doing a paste with the middle mouse button, Insstart is set to + * where the paste started. + */ + if (where_paste_started.lnum != 0) + Insstart = where_paste_started; + else +#endif + { + Insstart = curwin->w_cursor; + if (startln) + Insstart.col = 0; + } + Insstart_textlen = linetabsize(ml_get_curline()); + Insstart_blank_vcol = MAXCOL; + + if (initstr != NUL && !restart_edit) + { + ResetRedobuff(); + AppendNumberToRedobuff(count); + AppendCharToRedobuff(initstr); + if (initstr == 'g') /* "gI" command */ + AppendCharToRedobuff('I'); + } + + if (initstr == 'R') + State = REPLACE; + else + State = INSERT; + +#ifdef USE_MOUSE + setmouse(); +#endif + clear_showcmd(); +#ifdef RIGHTLEFT + revins = (State == INSERT && p_ri); /* there is no reverse replace mode */ + if (revins) + undisplay_dollar(); +#endif + + /* + * When CTRL-O . is used to repeat an insert, we get here with + * restart_edit non-zero, but something in the stuff buffer + */ + if (restart_edit && stuff_empty()) + { +#ifdef USE_MOUSE + /* + * After a paste we consider text typed to be part of the insert for + * the pasted text. You can backspace over the paste text too. + */ + if (where_paste_started.lnum) + arrow_used = FALSE; + else +#endif + arrow_used = TRUE; + restart_edit = 0; + /* + * If the cursor was after the end-of-line before the CTRL-O + * and it is now at the end-of-line, put it after the end-of-line + * (this is not correct in very rare cases). + * Also do this if curswant is greater than the current virtual column. + * Eg after "^O$" or "^O80|". + */ + if (((o_eol && curwin->w_cursor.lnum == o_lnum) || + curwin->w_curswant > curwin->w_virtcol) && + *(ptr = ml_get_curline() + curwin->w_cursor.col) + != NUL && + *(ptr + 1) == NUL) + ++curwin->w_cursor.col; + } + else + { + arrow_used = FALSE; + o_eol = FALSE; + } +#ifdef USE_MOUSE + where_paste_started.lnum = 0; +#endif +#ifdef CINDENT + can_cindent = TRUE; +#endif + + if (p_smd) + showmode(); + + if (!p_im) + change_warning(); /* give a warning if readonly */ + +#ifdef DIGRAPHS + do_digraph(-1); /* clear digraphs */ +#endif + +/* + * Get the current length of the redo buffer, those characters have to be + * skipped if we want to get to the inserted characters. + */ + ptr = get_inserted(); + new_insert_skip = STRLEN(ptr); + vim_free(ptr); + + old_indent = 0; + + for (;;) + { +#ifdef RIGHTLEFT + if (!revinslegal) + revinsscol = -1; /* reset on illegal motions */ + else + revinslegal = 0; +#endif + if (arrow_used) /* don't repeat insert when arrow key used */ + count = 0; + + /* set curwin->w_curswant for next K_DOWN or K_UP */ + if (!arrow_used) + curwin->w_set_curswant = TRUE; + + /* Figure out where the cursor is based on curwin->w_cursor. */ + mincol = curwin->w_col; + i = curwin->w_row; + cursupdate(); + + /* + * When emsg() was called msg_scroll will have been set. + */ + msg_scroll = FALSE; + + /* + * If we inserted a character at the last position of the last line in + * the window, scroll the window one line up. This avoids an extra + * redraw. + * This is detected when the cursor column is smaller after inserting + * something. + */ + if (curwin->w_p_wrap && !did_backspace && + (int)curwin->w_col < (int)mincol - curbuf->b_p_ts && + i == curwin->w_winpos + curwin->w_height - 1 && + curwin->w_cursor.lnum != curwin->w_topline) + { + ++curwin->w_topline; + updateScreen(VALID_TO_CURSCHAR); + cursupdate(); + need_redraw = FALSE; + } + did_backspace = FALSE; + + /* + * redraw is postponed until after cursupdate() to make 'dollar' + * option work correctly. + */ + if (need_redraw) + { + updateline(); + need_redraw = FALSE; + } + + showruler(0); + setcursor(); + emsg_on_display = FALSE; /* may remove error message now */ + + c = vgetc(); + if (c == Ctrl('C')) + got_int = FALSE; + +#ifdef RIGHTLEFT + if (p_hkmap && KeyTyped) + c = hkmap(c); /* Hebrew mode mapping */ +#endif + +#ifdef INSERT_EXPAND + if (ctrl_x_mode == CTRL_X_NOT_DEFINED_YET) + { + /* We have just entered ctrl-x mode and aren't quite sure which + * ctrl-x mode it will be yet. Now we decide -- webb + */ + switch (c) + { + case Ctrl('E'): + case Ctrl('Y'): + ctrl_x_mode = CTRL_X_SCROLL; + if (State == INSERT) + edit_submode = (char_u *)" (insert) Scroll (^E/^Y)"; + else + edit_submode = (char_u *)" (replace) Scroll (^E/^Y)"; + break; + case Ctrl('L'): + ctrl_x_mode = CTRL_X_WHOLE_LINE; + edit_submode = (char_u *)" Whole line completion (^L/^N/^P)"; + break; + case Ctrl('F'): + ctrl_x_mode = CTRL_X_FILES; + edit_submode = (char_u *)" File name completion (^F/^N/^P)"; + break; + case Ctrl('K'): + ctrl_x_mode = CTRL_X_DICTIONARY; + edit_submode = (char_u *)" Dictionary completion (^K/^N/^P)"; + break; + case Ctrl(']'): + ctrl_x_mode = CTRL_X_TAGS; + edit_submode = (char_u *)" Tag completion (^]/^N/^P)"; + break; + case Ctrl('I'): + ctrl_x_mode = CTRL_X_PATH_PATTERNS; + edit_submode = (char_u *)" Path pattern completion (^N/^P)"; + break; + case Ctrl('D'): + ctrl_x_mode = CTRL_X_PATH_DEFINES; + edit_submode = (char_u *)" Definition completion (^D/^N/^P)"; + break; + default: + ctrl_x_mode = 0; + break; + } + showmode(); + } + else if (ctrl_x_mode) + { + /* We we're already in ctrl-x mode, do we stay in it? */ + if (!is_ctrl_x_key(c)) + { + if (ctrl_x_mode == CTRL_X_SCROLL) + ctrl_x_mode = 0; + else + ctrl_x_mode = CTRL_X_FINISHED; + edit_submode = NULL; + } + showmode(); + } + if (started_completion || ctrl_x_mode == CTRL_X_FINISHED) + { + /* Show error message from attempted keyword completion (probably + * 'Pattern not found') until another key is hit, then go back to + * showing what mode we are in. + */ + showmode(); + if ((ctrl_x_mode == 0 && c != Ctrl('N') && c != Ctrl('P')) || + ctrl_x_mode == CTRL_X_FINISHED) + { + /* Get here when we have finished typing a sequence of ^N and + * ^P or other completion characters in CTRL-X mode. Free up + * memory that was used, and make sure we can redo the insert + * -- webb. + */ + if (curr_match != NULL) + { + /* + * If any of the original typed text has been changed, + * eg when ignorecase is set, we must add back-spaces to + * the redo buffer. We add as few as necessary to delete + * just the part of the original text that has changed + * -- webb + */ + ptr = curr_match->str; + tmp_ptr = original_text; + while (*tmp_ptr && *tmp_ptr == *ptr) + { + ++tmp_ptr; + ++ptr; + } + for (temp = 0; tmp_ptr[temp]; ++temp) + AppendCharToRedobuff(K_BS); + if (*ptr) + AppendToRedobuff(ptr); + } + /* Break line if it's too long */ + lnum = curwin->w_cursor.lnum; + insertchar(NUL, FALSE, -1); + if (lnum != curwin->w_cursor.lnum) + updateScreen(CURSUPD); + else + need_redraw = TRUE; + + vim_free(complete_pat); + complete_pat = NULL; + vim_free(original_text); + original_text = NULL; + free_completions(); + started_completion = FALSE; + ctrl_x_mode = 0; + p_sm = save_sm; + if (edit_submode != NULL) + { + edit_submode = NULL; + showmode(); + } + } + } +#endif /* INSERT_EXPAND */ + + if (c != Ctrl('D')) /* remember to detect ^^D and 0^D */ + lastc = c; + +#ifdef DIGRAPHS + c = do_digraph(c); +#endif /* DIGRAPHS */ + + if (c == Ctrl('V') || c == Ctrl('Q')) + { + if (NextScreen != NULL) + screen_outchar('^', curwin->w_winpos + curwin->w_row, +#ifdef RIGHTLEFT + curwin->w_p_rl ? (int)Columns - 1 - curwin->w_col : +#endif + curwin->w_col); + AppendToRedobuff((char_u *)"\026"); /* CTRL-V */ + cursupdate(); + + if (!add_to_showcmd(c, FALSE)) + setcursor(); + + c = get_literal(); + clear_showcmd(); + insert_special(c, TRUE); + need_redraw = TRUE; +#ifdef RIGHTLEFT + revinschars++; + revinslegal++; +#endif + continue; + } + +#ifdef CINDENT + if (curbuf->b_p_cin +# ifdef INSERT_EXPAND + && !ctrl_x_mode +# endif + ) + { + line_is_white = inindent(0); + + /* + * A key name preceded by a bang means that this + * key wasn't destined to be inserted. Skip ahead + * to the re-indenting if we find one. + */ + if (in_cinkeys(c, '!', line_is_white)) + goto force_cindent; + + /* + * A key name preceded by a star means that indenting + * has to be done before inserting the key. + */ + if (can_cindent && in_cinkeys(c, '*', line_is_white)) + { + stop_arrow(); + + /* re-indent the current line */ + fixthisline(get_c_indent); + + /* draw the changes on the screen later */ + need_redraw = TRUE; + } + } +#endif /* CINDENT */ + +#ifdef RIGHTLEFT + if (curwin->w_p_rl) + switch (c) + { + case K_LEFT: c = K_RIGHT; break; + case K_S_LEFT: c = K_S_RIGHT; break; + case K_RIGHT: c = K_LEFT; break; + case K_S_RIGHT: c = K_S_LEFT; break; + } +#endif + + switch (c) /* handle character in insert mode */ + { + case K_INS: /* toggle insert/replace mode */ + if (State == REPLACE) + State = INSERT; + else + State = REPLACE; + AppendCharToRedobuff(K_INS); + showmode(); + break; + +#ifdef INSERT_EXPAND + case Ctrl('X'): /* Enter ctrl-x mode */ + /* We're not sure which ctrl-x mode it will be yet */ + ctrl_x_mode = CTRL_X_NOT_DEFINED_YET; + MSG("^X mode (^E/^Y/^L/^]/^F/^I/^K/^D)"); + break; +#endif /* INSERT_EXPAND */ + + case Ctrl('O'): /* execute one command */ + if (echeck_abbr(Ctrl('O') + ABBR_OFF)) + break; + count = 0; + if (State == INSERT) + restart_edit = 'I'; + else + restart_edit = 'R'; + o_lnum = curwin->w_cursor.lnum; + o_eol = (gchar_cursor() == NUL); + goto doESCkey; + + /* Hitting the help key in insert mode is like */ + case K_HELP: + case K_F1: + stuffcharReadbuff(K_HELP); + /*FALLTHROUGH*/ + + case ESC: /* an escape ends input mode */ + if (echeck_abbr(ESC + ABBR_OFF)) + break; + /*FALLTHROUGH*/ + + case Ctrl('C'): +doESCkey: + temp = curwin->w_cursor.col; + if (!arrow_used) + { + AppendToRedobuff(ESC_STR); + + if (--count > 0) /* repeat what was typed */ + { + (void)start_redo_ins(); + continue; + } + stop_insert(&curwin->w_cursor); + if (dollar_vcol) + { + dollar_vcol = 0; + /* may have to redraw status line if this was the + * first change, show "[+]" */ + if (curwin->w_redr_status == TRUE) + must_redraw = NOT_VALID; + else + need_redraw = TRUE; + } + } + if (need_redraw) + updateline(); + + /* When an autoindent was removed, curswant stays after the + * indent */ + if (!restart_edit && (colnr_t)temp == curwin->w_cursor.col) + curwin->w_set_curswant = TRUE; + + /* + * The cursor should end up on the last inserted character. + */ + if (curwin->w_cursor.col != 0 && + (!restart_edit || gchar_cursor() == NUL) +#ifdef RIGHTLEFT + && !revins +#endif + ) + --curwin->w_cursor.col; + if (State == REPLACE) + replace_flush(); /* free replace stack */ + State = NORMAL; +#ifdef USE_MOUSE + setmouse(); +#endif + /* inchar() may have deleted the "INSERT" message */ + /* for CTRL-O we display -- INSERT COMMAND -- */ + if (Recording || restart_edit) + showmode(); + else if (p_smd) + MSG(""); + old_indent = 0; + return (c == Ctrl('O')); + + /* + * Insert the previously inserted text. + * For ^@ the trailing ESC will end the insert, unless there + * is an error. + */ + case K_ZERO: + case NUL: + case Ctrl('A'): + if (stuff_inserted(NUL, 1L, (c == Ctrl('A'))) == FAIL && + c != Ctrl('A')) + goto doESCkey; /* quit insert mode */ + break; + + /* + * insert the contents of a register + */ + case Ctrl('R'): + if (NextScreen != NULL) + screen_outchar('"', curwin->w_winpos + curwin->w_row, +#ifdef RIGHTLEFT + curwin->w_p_rl ? (int)Columns - 1 - curwin->w_col : +#endif + curwin->w_col); + if (!add_to_showcmd(c, FALSE)) + setcursor(); + /* don't map the register name. This also prevents the + * mode message to be deleted when ESC is hit */ + ++no_mapping; +#ifdef HAVE_LANGMAP + cc = vgetc(); + LANGMAP_ADJUST(cc, TRUE); + if (insertbuf(cc) == FAIL) +#else + if (insertbuf(vgetc()) == FAIL) +#endif + { + beep_flush(); + need_redraw = TRUE; /* remove the '"' */ + } + --no_mapping; + clear_showcmd(); + break; + +#ifdef RIGHTLEFT + case Ctrl('B'): /* toggle reverse insert mode */ + p_ri = !p_ri; + revins = (State == INSERT && p_ri); + if (revins) + undisplay_dollar(); + showmode(); + break; + + case Ctrl('_'): /* toggle language: khmap and revins */ + /* Move to end of reverse inserted text */ + if (revins && revinschars && revinsscol >= 0) + while (gchar_cursor() != NUL && revinschars--) + ++curwin->w_cursor.col; + p_ri = !p_ri; + revins = (State == INSERT && p_ri); + if (revins) + { + revinsscol = curwin->w_cursor.col; + revinslegal++; + revinschars = 0; + undisplay_dollar(); + } + else + revinsscol = -1; + p_hkmap = curwin->w_p_rl ^ p_ri; /* be consistent! */ + showmode(); + break; +#endif + + /* + * If the cursor is on an indent, ^T/^D insert/delete one + * shiftwidth. Otherwise ^T/^D behave like a "<<" or ">>". + * Always round the indent to 'shiftwith', this is compatible + * with vi. But vi only supports ^T and ^D after an + * autoindent, we support it everywhere. + */ + case Ctrl('D'): /* make indent one shiftwidth smaller */ +#ifdef INSERT_EXPAND + if (ctrl_x_mode == CTRL_X_PATH_DEFINES) + goto docomplete; +#endif /* INSERT_EXPAND */ + /* FALLTHROUGH */ + case Ctrl('T'): /* make indent one shiftwidth greater */ + stop_arrow(); + AppendCharToRedobuff(c); + + /* + * 0^D and ^^D: remove all indent. + */ + if ((lastc == '0' || lastc == '^') && curwin->w_cursor.col) + { + --curwin->w_cursor.col; + (void)delchar(FALSE); /* delete the '^' or '0' */ + if (lastc == '^') + old_indent = get_indent(); /* remember curr. indent */ + change_indent(INDENT_SET, 0, TRUE); + } + else + change_indent(c == Ctrl('D') ? INDENT_DEC : INDENT_INC, + 0, TRUE); + + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; +#ifdef CINDENT + can_cindent = FALSE; /* no cindenting after ^D or ^T */ +#endif + goto redraw; + + case K_DEL: + stop_arrow(); + if (gchar_cursor() == NUL) /* delete newline */ + { + temp = curwin->w_cursor.col; + if (!p_bs || /* only if 'bs' set */ + u_save((linenr_t)(curwin->w_cursor.lnum - 1), + (linenr_t)(curwin->w_cursor.lnum + 2)) == FAIL || + do_join(FALSE, TRUE) == FAIL) + beep_flush(); + else + curwin->w_cursor.col = temp; + } + else if (delchar(FALSE) == FAIL)/* delete char under cursor */ + beep_flush(); + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + AppendCharToRedobuff(c); + goto redraw; + + case K_BS: + case Ctrl('H'): + mode = BACKSPACE_CHAR; +dodel: + /* can't delete anything in an empty file */ + /* can't backup past first character in buffer */ + /* can't backup past starting point unless 'backspace' > 1 */ + /* can backup to a previous line if 'backspace' == 0 */ + if (bufempty() || ( +#ifdef RIGHTLEFT + !revins && +#endif + ((curwin->w_cursor.lnum == 1 && + curwin->w_cursor.col <= 0) || + (p_bs < 2 && (arrow_used || + (curwin->w_cursor.lnum == Insstart.lnum && + curwin->w_cursor.col <= Insstart.col) || + (curwin->w_cursor.col <= 0 && p_bs == 0)))))) + { + beep_flush(); + goto redraw; + } + + stop_arrow(); +#ifdef CINDENT + if (inindent(0)) + can_cindent = FALSE; +#endif +#ifdef RIGHTLEFT + if (revins) /* put cursor after last inserted char */ + inc_cursor(); +#endif + if (curwin->w_cursor.col <= 0) /* delete newline! */ + { + lnum = Insstart.lnum; + if (curwin->w_cursor.lnum == Insstart.lnum +#ifdef RIGHTLEFT + || revins +#endif + ) + { + if (u_save((linenr_t)(curwin->w_cursor.lnum - 2), + (linenr_t)(curwin->w_cursor.lnum + 1)) == FAIL) + goto redraw; + --Insstart.lnum; + Insstart.col = 0; + } + /* + * In replace mode: + * cc < 0: NL was inserted, delete it + * cc >= 0: NL was replaced, put original characters back + */ + cc = -1; + if (State == REPLACE) + cc = replace_pop(); + /* in replace mode, in the line we started replacing, we + only move the cursor */ + if (State != REPLACE || curwin->w_cursor.lnum > lnum) + { + temp = gchar_cursor(); /* remember current char */ + --curwin->w_cursor.lnum; + (void)do_join(FALSE, curs_rows() == OK); + if (temp == NUL && gchar_cursor() != NUL) + ++curwin->w_cursor.col; + /* + * in REPLACE mode we have to put back the text that + * was replace by the NL. On the replace stack is + * first a NUL-terminated sequence of characters that + * were deleted and then the character that NL + * replaced. + */ + if (State == REPLACE) + { + /* + * Do the next ins_char() in NORMAL state, to + * prevent ins_char() from replacing characters and + * avoiding showmatch(). + */ + State = NORMAL; + /* + * restore blanks deleted after cursor + */ + while (cc > 0) + { + temp = curwin->w_cursor.col; + ins_char(cc); + curwin->w_cursor.col = temp; + cc = replace_pop(); + } + cc = replace_pop(); + if (cc > 0) + { + ins_char(cc); + dec_cursor(); + } + State = REPLACE; + } + } + else + dec_cursor(); + did_ai = FALSE; + } + else + { +#ifdef RIGHTLEFT + if (revins) /* put cursor on last inserted char */ + dec_cursor(); +#endif + mincol = 0; + /* keep indent */ + if (mode == BACKSPACE_LINE && curbuf->b_p_ai +#ifdef RIGHTLEFT + && !revins +#endif + ) + { + temp = curwin->w_cursor.col; + beginline(TRUE); + if (curwin->w_cursor.col < (colnr_t)temp) + mincol = curwin->w_cursor.col; + curwin->w_cursor.col = temp; + } + + /* delete upto starting point, start of line or previous + * word */ + do + { +#ifdef RIGHTLEFT + if (!revins) /* put cursor on char to be deleted */ +#endif + dec_cursor(); + + /* start of word? */ + if (mode == BACKSPACE_WORD && + !vim_isspace(gchar_cursor())) + { + mode = BACKSPACE_WORD_NOT_SPACE; + temp = iswordchar(gchar_cursor()); + } + /* end of word? */ + else if (mode == BACKSPACE_WORD_NOT_SPACE && + (vim_isspace(cc = gchar_cursor()) || + iswordchar(cc) != temp)) + { +#ifdef RIGHTLEFT + if (!revins) +#endif + inc_cursor(); +#ifdef RIGHTLEFT + else if (State == REPLACE) + dec_cursor(); +#endif + break; + } + if (State == REPLACE) + { + /* + * cc < 0: replace stack empty, just move cursor + * cc == 0: character was inserted, delete it + * cc > 0: character was replace, put original back + */ + cc = replace_pop(); + if (cc > 0) + pchar_cursor(cc); + else if (cc == 0) + (void)delchar(FALSE); + } + else /* State != REPLACE */ + { + (void)delchar(FALSE); +#ifdef RIGHTLEFT + if (revinschars) + { + revinschars--; + revinslegal++; + } + if (revins && gchar_cursor() == NUL) + break; +#endif + } + /* Just a single backspace?: */ + if (mode == BACKSPACE_CHAR) + break; + } while ( +#ifdef RIGHTLEFT + revins || +#endif + (curwin->w_cursor.col > mincol && + (curwin->w_cursor.lnum != Insstart.lnum || + curwin->w_cursor.col != Insstart.col))); + did_backspace = TRUE; + } + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + if (curwin->w_cursor.col <= 1) + did_ai = FALSE; + /* + * It's a little strange to put backspaces into the redo + * buffer, but it makes auto-indent a lot easier to deal + * with. + */ + AppendCharToRedobuff(c); +redraw: + need_redraw = TRUE; + break; + + case Ctrl('W'): /* delete word before cursor */ + mode = BACKSPACE_WORD; + goto dodel; + + case Ctrl('U'): /* delete inserted text in current line */ + mode = BACKSPACE_LINE; + goto dodel; + +#ifdef USE_MOUSE + case K_LEFTMOUSE: + case K_LEFTDRAG: + case K_LEFTRELEASE: + case K_MIDDLEMOUSE: + case K_MIDDLEDRAG: + case K_MIDDLERELEASE: + case K_RIGHTMOUSE: + case K_RIGHTDRAG: + case K_RIGHTRELEASE: +#ifdef USE_GUI + /* When GUI is active, also move/paste when 'mouse' is empty */ + if (!gui.in_use) +#endif + if (!mouse_has(MOUSE_INSERT)) + break; + + undisplay_dollar(); + tpos = curwin->w_cursor; + if (do_mouse(c, BACKWARD, 1L, FALSE)) + { + start_arrow(&tpos); +# ifdef CINDENT + can_cindent = TRUE; +# endif + } + + break; + + case K_IGNORE: + break; +#endif + +#ifdef USE_GUI + case K_SCROLLBAR: + undisplay_dollar(); + tpos = curwin->w_cursor; + if (gui_do_scroll()) + { + start_arrow(&tpos); +# ifdef CINDENT + can_cindent = TRUE; +# endif + } + break; + + case K_HORIZ_SCROLLBAR: + undisplay_dollar(); + tpos = curwin->w_cursor; + if (gui_do_horiz_scroll()) + { + start_arrow(&tpos); +#ifdef CINDENT + can_cindent = TRUE; +#endif + } + break; +#endif + + case K_LEFT: + undisplay_dollar(); + tpos = curwin->w_cursor; + if (oneleft() == OK) + { + start_arrow(&tpos); +#ifdef RIGHTLEFT + /* If exit reversed string, position is fixed */ + if (revinsscol != -1 && + (int)curwin->w_cursor.col >= revinsscol) + revinslegal++; + revinschars++; +#endif + } + + /* + * if 'whichwrap' set for cursor in insert mode may go to + * previous line + */ + else if (vim_strchr(p_ww, '[') != NULL && + curwin->w_cursor.lnum > 1) + { + start_arrow(&tpos); + --(curwin->w_cursor.lnum); + coladvance(MAXCOL); + curwin->w_set_curswant = TRUE; /* so we stay at the end */ + } + else + beep_flush(); + break; + + case K_HOME: + undisplay_dollar(); + tpos = curwin->w_cursor; + if ((mod_mask & MOD_MASK_CTRL)) + curwin->w_cursor.lnum = 1; + curwin->w_cursor.col = 0; + curwin->w_curswant = 0; + start_arrow(&tpos); + break; + + case K_END: + undisplay_dollar(); + tpos = curwin->w_cursor; + if ((mod_mask & MOD_MASK_CTRL)) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + coladvance(MAXCOL); + curwin->w_curswant = MAXCOL; + start_arrow(&tpos); + break; + + case K_S_LEFT: + undisplay_dollar(); + if (curwin->w_cursor.lnum > 1 || curwin->w_cursor.col > 0) + { + start_arrow(&curwin->w_cursor); + (void)bck_word(1L, 0, FALSE); + } + else + beep_flush(); + break; + + case K_RIGHT: + undisplay_dollar(); + if (gchar_cursor() != NUL) + { + start_arrow(&curwin->w_cursor); + curwin->w_set_curswant = TRUE; + ++curwin->w_cursor.col; +#ifdef RIGHTLEFT + revinslegal++; + if (revinschars) + revinschars--; +#endif + } + /* if 'whichwrap' set for cursor in insert mode may go + * to next line */ + else if (vim_strchr(p_ww, ']') != NULL && + curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) + { + start_arrow(&curwin->w_cursor); + curwin->w_set_curswant = TRUE; + ++curwin->w_cursor.lnum; + curwin->w_cursor.col = 0; + } + else + beep_flush(); + break; + + case K_S_RIGHT: + undisplay_dollar(); + if (curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count || + gchar_cursor() != NUL) + { + start_arrow(&curwin->w_cursor); + (void)fwd_word(1L, 0, 0); + } + else + beep_flush(); + break; + + case K_UP: + undisplay_dollar(); + tpos = curwin->w_cursor; + if (cursor_up(1L) == OK) + { + start_arrow(&tpos); +#ifdef CINDENT + can_cindent = TRUE; +#endif + } + else + beep_flush(); + break; + + case K_S_UP: + case K_PAGEUP: + undisplay_dollar(); + tpos = curwin->w_cursor; + if (onepage(BACKWARD, 1L) == OK) + { + start_arrow(&tpos); +#ifdef CINDENT + can_cindent = TRUE; +#endif + } + else + beep_flush(); + break; + + case K_DOWN: + undisplay_dollar(); + tpos = curwin->w_cursor; + if (cursor_down(1L) == OK) + { + start_arrow(&tpos); +#ifdef CINDENT + can_cindent = TRUE; +#endif + } + else + beep_flush(); + break; + + case K_S_DOWN: + case K_PAGEDOWN: + undisplay_dollar(); + tpos = curwin->w_cursor; + if (onepage(FORWARD, 1L) == OK) + { + start_arrow(&tpos); +#ifdef CINDENT + can_cindent = TRUE; +#endif + } + else + beep_flush(); + break; + + case TAB: /* TAB or Complete patterns along path */ +#ifdef INSERT_EXPAND + if (ctrl_x_mode == CTRL_X_PATH_PATTERNS) + goto docomplete; +#endif /* INSERT_EXPAND */ + + if (Insstart_blank_vcol == MAXCOL && + curwin->w_cursor.lnum == Insstart.lnum) + Insstart_blank_vcol = curwin->w_virtcol; + if (echeck_abbr(TAB + ABBR_OFF)) + break; + i = inindent(0); +#ifdef CINDENT + if (i) + can_cindent = FALSE; +#endif + if (!curbuf->b_p_et && !(p_sta && i)) + goto normalchar; + + stop_arrow(); + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + AppendToRedobuff((char_u *)"\t"); + + if (p_sta && i) /* insert tab in indent */ + { + change_indent(INDENT_INC, 0, p_sr); + goto redraw; + } + + /* + * p_et is set: expand a tab into spaces + */ + temp = (int)curbuf->b_p_ts; + temp -= curwin->w_virtcol % temp; + + /* + * insert the first space with ins_char(); it will delete one + * char in replace mode. Insert the rest with ins_str(); it + * will not delete any chars + */ + ins_char(' '); + while (--temp) + { + ins_str((char_u *)" "); + if (State == REPLACE) /* no char replaced */ + replace_push(NUL); + } + goto redraw; + + case CR: + case NL: + if (echeck_abbr(c + ABBR_OFF)) + break; + stop_arrow(); + if (State == REPLACE) + replace_push(NUL); +#ifdef RIGHTLEFT + /* NL in reverse insert will allways start in the end of + * current line. */ + if (revins) + while (gchar_cursor() != NUL) + ++curwin->w_cursor.col; +#endif + + AppendToRedobuff(NL_STR); + if (has_format_option(FO_RET_COMS)) + fo_do_comments = TRUE; + i = Opencmd(FORWARD, TRUE, FALSE); + fo_do_comments = FALSE; +#ifdef CINDENT + can_cindent = TRUE; +#endif + if (!i) + goto doESCkey; /* out of memory */ + break; + +#ifdef DIGRAPHS + case Ctrl('K'): +#ifdef INSERT_EXPAND + if (ctrl_x_mode == CTRL_X_DICTIONARY) + goto docomplete; +#endif + if (NextScreen != NULL) + screen_outchar('?', curwin->w_winpos + curwin->w_row, +#ifdef RIGHTLEFT + curwin->w_p_rl ? (int)Columns - 1 - curwin->w_col : +#endif + curwin->w_col); + if (!add_to_showcmd(c, FALSE)) + setcursor(); + /* don't map the digraph chars. This also prevents the + * mode message to be deleted when ESC is hit */ + ++no_mapping; + ++allow_keys; + c = vgetc(); + --no_mapping; + --allow_keys; + if (IS_SPECIAL(c)) /* special key */ + { + clear_showcmd(); + insert_special(c, TRUE); + need_redraw = TRUE; + break; + } + if (c != ESC) + { + if (charsize(c) == 1 && NextScreen != NULL) + screen_outchar(c, curwin->w_winpos + curwin->w_row, +#ifdef RIGHTLEFT + curwin->w_p_rl ? (int)Columns - 1 - curwin->w_col : +#endif + curwin->w_col); + if (!add_to_showcmd(c, FALSE)) + setcursor(); + ++no_mapping; + ++allow_keys; + cc = vgetc(); + --no_mapping; + --allow_keys; + if (cc != ESC) + { + AppendToRedobuff((char_u *)"\026"); /* CTRL-V */ + c = getdigraph(c, cc, TRUE); + clear_showcmd(); + goto normalchar; + } + } + clear_showcmd(); + need_redraw = TRUE; + break; +#else /* DIGRAPHS */ +# ifdef INSERT_EXPAND + case Ctrl('K'): + if (ctrl_x_mode != CTRL_X_DICTIONARY) + goto normalchar; + goto docomplete; +# endif /* INSERT_EXPAND */ +#endif /* DIGRAPHS */ + +#ifdef INSERT_EXPAND + case Ctrl(']'): /* Tag name completion after ^X */ + if (ctrl_x_mode != CTRL_X_TAGS) + goto normalchar; + goto docomplete; + + case Ctrl('F'): /* File name completion after ^X */ + if (ctrl_x_mode != CTRL_X_FILES) + goto normalchar; + goto docomplete; + + case Ctrl('L'): /* Whole line completion after ^X */ + if (ctrl_x_mode != CTRL_X_WHOLE_LINE) + goto normalchar; + /* FALLTHROUGH */ + + case Ctrl('P'): /* Do previous pattern completion */ + case Ctrl('N'): /* Do next pattern completion */ +docomplete: + if (c == Ctrl('P') || c == Ctrl('L')) + complete_direction = BACKWARD; + else + complete_direction = FORWARD; + quick_m = mesg = NULL; /* No message by default */ + if (!started_completion) + { + /* First time we hit ^N or ^P (in a row, I mean) */ + + /* Turn off 'sm' so we don't show matches with ^X^L */ + save_sm = p_sm; + p_sm = FALSE; + + if (ctrl_x_mode == 0) + { + edit_submode = (char_u *)" Keyword completion (^P/^N)"; + showmode(); + } + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + stop_arrow(); + done_dir = 0; + first_match_pos = curwin->w_cursor; + ptr = tmp_ptr = ml_get(first_match_pos.lnum); + complete_col = first_match_pos.col; + temp = (int)complete_col - 1; + + /* Work out completion pattern and original text -- webb */ + if (ctrl_x_mode == 0 || (ctrl_x_mode & CTRL_X_WANT_IDENT)) + { + if (temp < 0 || !iswordchar(ptr[temp])) + { + /* Match any word of at least two chars */ + complete_pat = strsave((char_u *)"\\<\\k\\k"); + if (complete_pat == NULL) + break; + tmp_ptr += complete_col; + temp = 0; + } + else + { + while (temp >= 0 && iswordchar(ptr[temp])) + temp--; + tmp_ptr += ++temp; + if ((temp = (int)complete_col - temp) == 1) + { + /* Only match word with at least two + * chars -- webb + */ + sprintf((char *)IObuff, "\\<%c\\k", *tmp_ptr); + complete_pat = strsave(IObuff); + if (complete_pat == NULL) + break; + } + else + { + complete_pat = alloc(temp + 3); + if (complete_pat == NULL) + break; + sprintf((char *)complete_pat, "\\<%.*s", temp, + tmp_ptr); + } + } + } + else if (ctrl_x_mode == CTRL_X_WHOLE_LINE) + { + tmp_ptr = skipwhite(ptr); + temp = (int)complete_col - (tmp_ptr - ptr); + complete_pat = strnsave(tmp_ptr, temp); + if (complete_pat == NULL) + break; + } + else if (ctrl_x_mode == CTRL_X_FILES) + { + while (temp >= 0 && isfilechar(ptr[temp])) + temp--; + tmp_ptr += ++temp; + temp = (int)complete_col - temp; + complete_pat = addstar(tmp_ptr, temp); + if (complete_pat == NULL) + break; + } + original_text = strnsave(tmp_ptr, temp); + if (original_text == NULL) + { + vim_free(complete_pat); + complete_pat = NULL; + break; + } + + complete_col = tmp_ptr - ptr; + first_match_pos.col -= temp; + + /* So that ^N can match word immediately after cursor */ + if (ctrl_x_mode == 0) + dec(&first_match_pos); + + last_match_pos = first_match_pos; + + /* Get list of all completions now, if appropriate */ + if (ctrl_x_mode == CTRL_X_PATH_PATTERNS || + ctrl_x_mode == CTRL_X_PATH_DEFINES) + { + started_completion = TRUE; + find_pattern_in_path(complete_pat, + (int)STRLEN(complete_pat), FALSE, FALSE, + (ctrl_x_mode == CTRL_X_PATH_DEFINES) ? FIND_DEFINE + : FIND_ANY, 1L, ACTION_EXPAND, + (linenr_t)1, (linenr_t)MAXLNUM); + + if (make_cyclic() > 1) + { + sprintf((char *)IObuff, "There are %d matches", + count_completions()); + mesg = IObuff; + } + } + else if (ctrl_x_mode == CTRL_X_DICTIONARY) + { + started_completion = TRUE; + if (*p_dict == NUL) + mesg = (char_u *)"'dictionary' option is empty"; + else + { + complete_dictionaries(complete_pat, + complete_direction); + if (make_cyclic() > 1) + { + sprintf((char *)IObuff, + "There are %d matching words", + count_completions()); + mesg = IObuff; + } + } + } + else if (ctrl_x_mode == CTRL_X_TAGS) + { + started_completion = TRUE; + /* set reg_ic according to p_ic, p_scs and pat */ + set_reg_ic(complete_pat); + prog = vim_regcomp(complete_pat); + if (prog != NULL && + find_tags(NULL, prog, &num_matches, &matches, FALSE) + == OK && num_matches > 0) + { + for (i = 0; i < num_matches; i++) + if (add_completion(matches[i], -1, NULL, + FORWARD) == RET_ERROR) + break; + FreeWild(num_matches, matches); + vim_free(prog); + if (make_cyclic() > 1) + { + sprintf((char *)IObuff, + "There are %d matching tags", + count_completions()); + mesg = IObuff; + } + } + else + { + vim_free(prog); + vim_free(complete_pat); + complete_pat = NULL; + } + } + else if (ctrl_x_mode == CTRL_X_FILES) + { + started_completion = TRUE; + expand_interactively = TRUE; + if (ExpandWildCards(1, &complete_pat, &num_matches, + &matches, FALSE, FALSE) == OK) + { + for (i = 0; i < num_matches; i++) + if (add_completion(matches[i], -1, NULL, + FORWARD) == RET_ERROR) + break; + FreeWild(num_matches, matches); + if (make_cyclic() > 1) + { + sprintf((char *)IObuff, + "There are %d matching file names", + count_completions()); + mesg = IObuff; + } + } + else + { + vim_free(complete_pat); + complete_pat = NULL; + } + expand_interactively = FALSE; + } + } + /* + * In insert mode: Delete the typed part. + * In replace mode: Put the old characters back, if any. + */ + while (curwin->w_cursor.col > complete_col) + { + curwin->w_cursor.col--; + if (State == REPLACE) + { + if ((cc = replace_pop()) > 0) + pchar(curwin->w_cursor, cc); + } + else + delchar(FALSE); + } + complete_pos = NULL; + if (started_completion && curr_match == NULL && + (p_ws || done_dir == BOTH_DIRECTIONS)) + quick_m = e_patnotf; + else if (curr_match != NULL && complete_direction == FORWARD && + curr_match->next != NULL) + curr_match = curr_match->next; + else if (curr_match != NULL && complete_direction == BACKWARD && + curr_match->prev != NULL) + curr_match = curr_match->prev; + else + { + complete_pos = (complete_direction == FORWARD) ? + &last_match_pos : &first_match_pos; + /* + * If 'infercase' is set, don't use 'smartcase' here + */ + save_p_scs = p_scs; + if (curbuf->b_p_inf) + p_scs = FALSE; + for (;;) + { + if (ctrl_x_mode == CTRL_X_WHOLE_LINE) + temp = search_for_exact_line(complete_pos, + complete_direction, complete_pat); + else + temp = searchit(complete_pos, complete_direction, + complete_pat, 1L, + SEARCH_KEEP + SEARCH_NFMSG, RE_LAST); + if (temp == FAIL) + { + if (!p_ws && done_dir != -complete_direction) + { + /* + * With nowrapscan, we haven't finished + * looking in the other direction yet -- webb + */ + temp = OK; + done_dir = complete_direction; + } + else if (!p_ws) + done_dir = BOTH_DIRECTIONS; + break; + } + if (!started_completion) + { + started_completion = TRUE; + first_match_pos = *complete_pos; + last_match_pos = *complete_pos; + } + else if (first_match_pos.lnum == last_match_pos.lnum && + first_match_pos.col == last_match_pos.col) + { + /* We have found all the matches in this file */ + temp = FAIL; + break; + } + ptr = ml_get_pos(complete_pos); + if (ctrl_x_mode == CTRL_X_WHOLE_LINE) + temp = STRLEN(ptr); + else + { + tmp_ptr = ptr; + temp = 0; + while (*tmp_ptr != NUL && iswordchar(*tmp_ptr++)) + temp++; + } + if (add_completion_and_infercase(ptr, temp, NULL, + complete_direction) != FAIL) + { + temp = OK; + break; + } + } + p_scs = save_p_scs; + } + if (complete_pos != NULL && temp == FAIL) + { + int tot; + + tot = count_completions(); /* Total num matches */ + if (curr_match != NULL) + (void)make_cyclic(); + if (tot > 1) + { + sprintf((char *)IObuff, + "All %d matches have now been found", tot); + mesg = IObuff; + } + else if (tot == 0) + quick_m = e_patnotf; + } + + /* eat the ESC to avoid leaving insert mode */ + if (got_int) + { + (void)vgetc(); + got_int = FALSE; + } + + /* + * When using match from another file, show the file name. + */ + if (curr_match != NULL) + ptr = curr_match->str; + else /* back to what has been typed */ + ptr = original_text; + + if (curr_match == NULL || curr_match->original) + { + edit_submode_extra = (char_u *)"Back at original"; + edit_submode_highl = TRUE; + } + else if (first_match != NULL && first_match->next != NULL && + (first_match->next == first_match || + first_match->next->original)) + { + edit_submode_extra = (char_u *)"(the only match)"; + edit_submode_highl = FALSE; + } + + /* + * Use ins_char() to insert the text, it is a bit slower than + * ins_str(), but it takes care of replace mode. + */ + if (ptr != NULL) + while (*ptr) + ins_char(*ptr++); + + started_completion = TRUE; + need_redraw = TRUE; + (void)set_highlight('r'); + msg_highlight = TRUE; + if (mesg != NULL) + { + msg(mesg); + mch_delay(1000L, FALSE); + } + else if (quick_m != NULL) + msg(quick_m); + else if (edit_submode_extra != NULL) + showmode(); + edit_submode_extra = NULL; + msg_highlight = FALSE; + + /* + * If there is a file name for the match, overwrite any + * previous message, it's more interesting to know where the + * match comes from, except when using the dictionary. + * Truncate the file name to avoid a wait for return. + */ + if (curr_match != NULL && curr_match->fname != NULL && + (ctrl_x_mode != CTRL_X_DICTIONARY || + (mesg == NULL && quick_m == NULL))) + { + STRCPY(IObuff, "match in file "); + i = (strsize(curr_match->fname) + 16) - sc_col; + if (i <= 0) + i = 0; + else + STRCAT(IObuff, "<"); + STRCAT(IObuff, curr_match->fname + i); + msg(IObuff); + } + break; +#endif /* INSERT_EXPAND */ + + case Ctrl('Y'): /* copy from previous line */ +#ifdef INSERT_EXPAND + if (ctrl_x_mode == CTRL_X_SCROLL) + { + scrolldown_clamp(); + updateScreen(VALID); + break; + } +#endif /* INSERT_EXPAND */ + lnum = curwin->w_cursor.lnum - 1; + goto copychar; + + case Ctrl('E'): /* copy from next line */ +#ifdef INSERT_EXPAND + if (ctrl_x_mode == CTRL_X_SCROLL) + { + scrollup_clamp(); + updateScreen(VALID); + break; + } +#endif /* INSERT_EXPAND */ + lnum = curwin->w_cursor.lnum + 1; +copychar: + if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) + { + beep_flush(); + break; + } + + /* try to advance to the cursor column */ + temp = 0; + ptr = ml_get(lnum); + while ((colnr_t)temp < curwin->w_virtcol && *ptr) + temp += lbr_chartabsize(ptr++, (colnr_t)temp); + + if ((colnr_t)temp > curwin->w_virtcol) + --ptr; + if ((c = *ptr) == NUL) + { + beep_flush(); + break; + } + + /*FALLTHROUGH*/ + default: +normalchar: + /* + * do some very smart indenting when entering '{' or '}' + */ + if (((did_si || can_si_back) && c == '{') || + (can_si && c == '}')) + { + FPOS *pos, old_pos; + + /* for '}' set indent equal to indent of line + * containing matching '{' + */ + if (c == '}' && (pos = findmatch('{')) != NULL) + { + old_pos = curwin->w_cursor; + /* + * If the matching '{' has a ')' immediately before it + * (ignoring white-space), then line up with the start + * of the line containing the matching '(' if there is + * one. This handles the case where an + * "if (..\n..) {" statement continues over multiple + * lines -- webb + */ + ptr = ml_get(pos->lnum); + i = pos->col; + if (i > 0) /* skip blanks before '{' */ + while (--i > 0 && vim_iswhite(ptr[i])) + ; + curwin->w_cursor.lnum = pos->lnum; + curwin->w_cursor.col = i; + if (ptr[i] == ')' && (pos = findmatch('(')) != NULL) + curwin->w_cursor = *pos; + i = get_indent(); + curwin->w_cursor = old_pos; + set_indent(i, TRUE); + } + else if (curwin->w_cursor.col > 0) + { + /* + * when inserting '{' after "O" reduce indent, but not + * more than indent of previous line + */ + temp = TRUE; + if (c == '{' && can_si_back && + curwin->w_cursor.lnum > 1) + { + old_pos = curwin->w_cursor; + i = get_indent(); + while (curwin->w_cursor.lnum > 1) + { + ptr = skipwhite( + ml_get(--(curwin->w_cursor.lnum))); + /* ignore empty lines and lines starting with + * '#'. + */ + if (*ptr != '#' && *ptr != NUL) + break; + } + if (get_indent() >= i) + temp = FALSE; + curwin->w_cursor = old_pos; + } + if (temp) + shift_line(TRUE, FALSE, 1); + } + } + /* set indent of '#' always to 0 */ + if (curwin->w_cursor.col > 0 && can_si && c == '#') + { + /* remember current indent for next line */ + old_indent = get_indent(); + set_indent(0, TRUE); + } + + if (c == ' ') + { +#ifdef CINDENT + if (inindent(0)) + can_cindent = FALSE; +#endif + if (Insstart_blank_vcol == MAXCOL && + curwin->w_cursor.lnum == Insstart.lnum) + Insstart_blank_vcol = curwin->w_virtcol; + } + + if (iswordchar(c) || !echeck_abbr(c)) + { + insert_special(c, FALSE); + need_redraw = TRUE; +#ifdef RIGHTLEFT + revinslegal++; + revinschars++; +#endif + } + break; + } /* end of switch (c) */ + +#ifdef CINDENT + if (curbuf->b_p_cin && can_cindent +# ifdef INSERT_EXPAND + && !ctrl_x_mode +# endif + ) + { +force_cindent: + /* + * Indent now if a key was typed that is in 'cinkeys'. + */ + if (in_cinkeys(c, ' ', line_is_white)) + { + stop_arrow(); + + /* re-indent the current line */ + fixthisline(get_c_indent); + + /* draw the changes on the screen later */ + need_redraw = TRUE; + } + } +#endif /* CINDENT */ + + } /* for (;;) */ +} + +/* + * Insert an indent (for or CTRL-T) or delete an indent (for CTRL-D). + * Keep the cursor on the same character. + * type == INDENT_INC increase indent (for CTRL-T or ) + * type == INDENT_DEC decrease indent (for CTRL-D) + * type == INDENT_SET set indent to "amount" + * if round is TRUE, round the indent to 'shiftwidth' (only with _INC and _Dec). + */ + static void +change_indent(type, amount, round) + int type; + int amount; + int round; +{ + int vcol; + int last_vcol; + int insstart_less; /* reduction for Insstart.col */ + int new_cursor_col; + int i; + char_u *ptr; + int save_p_list; + + /* for the following tricks we don't want list mode */ + save_p_list = curwin->w_p_list; + if (save_p_list) + { + curwin->w_p_list = FALSE; + curs_columns(FALSE); /* recompute w_virtcol */ + } + vcol = curwin->w_virtcol; + + /* determine offset from first non-blank */ + new_cursor_col = curwin->w_cursor.col; + beginline(TRUE); + new_cursor_col -= curwin->w_cursor.col; + + insstart_less = curwin->w_cursor.col; + + /* + * If the cursor is in the indent, compute how many screen columns the + * cursor is to the left of the first non-blank. + */ + if (new_cursor_col < 0) + vcol = get_indent() - vcol; + + /* + * Set the new indent. The cursor will be put on the first non-blank. + */ + if (type == INDENT_SET) + set_indent(amount, TRUE); + else + shift_line(type == INDENT_DEC, round, 1); + insstart_less -= curwin->w_cursor.col; + + /* + * Try to put cursor on same character. + * If the cursor is at or after the first non-blank in the line, + * compute the cursor column relative to the column of the first + * non-blank character. + * If we are not in insert mode, leave the cursor on the first non-blank. + * If the cursor is before the first non-blank, position it relative + * to the first non-blank, counted in screen columns. + */ + if (new_cursor_col >= 0) + new_cursor_col += curwin->w_cursor.col; + else if (!(State & INSERT)) + new_cursor_col = curwin->w_cursor.col; + else + { + /* + * Compute the screen column where the cursor should be. + */ + vcol = get_indent() - vcol; + curwin->w_virtcol = (vcol < 0) ? 0 : vcol; + + /* + * Advance the cursor until we reach the right screen column. + */ + vcol = last_vcol = 0; + new_cursor_col = -1; + ptr = ml_get_curline(); + while (vcol <= (int)curwin->w_virtcol) + { + last_vcol = vcol; + ++new_cursor_col; + vcol += lbr_chartabsize(ptr + new_cursor_col, (colnr_t)vcol); + } + vcol = last_vcol; + + /* + * May need to insert spaces to be able to position the cursor on + * the right screen column. + */ + if (vcol != (int)curwin->w_virtcol) + { + curwin->w_cursor.col = new_cursor_col; + i = (int)curwin->w_virtcol - vcol; + ptr = alloc(i + 1); + if (ptr != NULL) + { + new_cursor_col += i; + ptr[i] = NUL; + while (--i >= 0) + ptr[i] = ' '; + ins_str(ptr); + vim_free(ptr); + } + } + + /* + * When changing the indent while the cursor is in it, reset + * Insstart_col to 0. + */ + insstart_less = Insstart.col; + } + + curwin->w_p_list = save_p_list; + + if (new_cursor_col <= 0) + curwin->w_cursor.col = 0; + else + curwin->w_cursor.col = new_cursor_col; + curwin->w_set_curswant = TRUE; + + /* + * May have to adjust the start of the insert. + */ + if ((State & INSERT) && curwin->w_cursor.lnum == Insstart.lnum && + Insstart.col != 0) + { + if ((int)Insstart.col <= insstart_less) + Insstart.col = 0; + else + Insstart.col -= insstart_less; + } +} + +#ifdef INSERT_EXPAND +/* + * Is the character 'c' a valid key to keep us in the current ctrl-x mode? + * -- webb + */ + int +is_ctrl_x_key(c) + int c; +{ + switch (ctrl_x_mode) + { + case 0: /* Not in any ctrl-x mode */ + break; + case CTRL_X_NOT_DEFINED_YET: + if (c == Ctrl('X') || c == Ctrl('Y') || c == Ctrl('E') || + c == Ctrl('L') || c == Ctrl('F') || c == Ctrl(']') || + c == Ctrl('I') || c == Ctrl('D') || c == Ctrl('P') || + c == Ctrl('N')) + return TRUE; + break; + case CTRL_X_SCROLL: + if (c == Ctrl('Y') || c == Ctrl('E')) + return TRUE; + break; + case CTRL_X_WHOLE_LINE: + if (c == Ctrl('L') || c == Ctrl('P') || c == Ctrl('N')) + return TRUE; + break; + case CTRL_X_FILES: + if (c == Ctrl('F') || c == Ctrl('P') || c == Ctrl('N')) + return TRUE; + break; + case CTRL_X_DICTIONARY: + if (c == Ctrl('K') || c == Ctrl('P') || c == Ctrl('N')) + return TRUE; + break; + case CTRL_X_TAGS: + if (c == Ctrl(']') || c == Ctrl('P') || c == Ctrl('N')) + return TRUE; + break; + case CTRL_X_PATH_PATTERNS: + if (c == Ctrl('P') || c == Ctrl('N')) + return TRUE; + break; + case CTRL_X_PATH_DEFINES: + if (c == Ctrl('D') || c == Ctrl('P') || c == Ctrl('N')) + return TRUE; + break; + default: + emsg(e_internal); + break; + } + return FALSE; +} + +/* + * This is like add_completion(), but if ic and inf are set, then the + * case of the originally typed text is used, and the case of the completed + * text is infered, ie this tries to work out what case you probably wanted + * the rest of the word to be in -- webb + */ + int +add_completion_and_infercase(str, len, fname, dir) + char_u *str; + int len; + char_u *fname; + int dir; +{ + int has_lower = FALSE; + int was_letter = FALSE; + int orig_len; + int idx; + + if (p_ic && curbuf->b_p_inf && len < IOSIZE) + { + /* Infer case of completed part -- webb */ + orig_len = STRLEN(original_text); + + /* Use IObuff, str would change text in buffer! */ + STRNCPY(IObuff, str, len); + IObuff[len] = NUL; + + /* Rule 1: Were any chars converted to lower? */ + for (idx = 0; idx < orig_len; ++idx) + { + if (islower(original_text[idx])) + { + has_lower = TRUE; + if (isupper(IObuff[idx])) + { + /* Rule 1 is satisfied */ + for (idx = orig_len; idx < len; ++idx) + IObuff[idx] = TO_LOWER(IObuff[idx]); + break; + } + } + } + + /* + * Rule 2: No lower case, 2nd consecutive letter converted to + * upper case. + */ + if (!has_lower) + { + for (idx = 0; idx < orig_len; ++idx) + { + if (was_letter && isupper(original_text[idx]) && + islower(IObuff[idx])) + { + /* Rule 2 is satisfied */ + for (idx = orig_len; idx < len; ++idx) + IObuff[idx] = TO_UPPER(IObuff[idx]); + break; + } + was_letter = isalpha(original_text[idx]); + } + } + + /* Copy the original case of the part we typed */ + STRNCPY(IObuff, original_text, orig_len); + + return add_completion(IObuff, len, fname, dir); + } + return add_completion(str, len, fname, dir); +} + +/* + * Add a match to the list of matches. + * If the given string is already in the list of completions, then return + * FAIL, otherwise add it to the list and return OK. If there is an error, + * maybe because alloc returns NULL, then RET_ERROR is returned -- webb. + */ + static int +add_completion(str, len, fname, dir) + char_u *str; + int len; + char_u *fname; + int dir; +{ + struct Completion *match; + + mch_breakcheck(); + if (got_int) + return RET_ERROR; + if (len < 0) + len = STRLEN(str); + + /* + * If the same match is already present, don't add it. + */ + if (first_match != NULL) + { + match = first_match; + do + { + if (STRNCMP(match->str, str, (size_t)len) == 0 && + match->str[len] == NUL) + return FAIL; + match = match->next; + } while (match != NULL && match != first_match); + } + + /* + * Allocate a new match structure. + * Copy the values to the new match structure. + */ + match = (struct Completion *)alloc((unsigned)sizeof(struct Completion)); + if (match == NULL) + return RET_ERROR; + if ((match->str = strnsave(str, len)) == NULL) + { + vim_free(match); + return RET_ERROR; + } + if (fname != NULL) + match->fname = strsave(fname); /* ignore errors */ + else + match->fname = NULL; + match->original = FALSE; + + /* + * Link the new match structure in the list of matches. + */ + if (first_match == NULL) + { + first_match = curr_match = match; + curr_match->next = curr_match->prev = NULL; + } + else + { + if (dir == FORWARD) + { + match->next = NULL; + match->prev = curr_match; + curr_match->next = match; + curr_match = match; + } + else /* BACKWARD */ + { + match->prev = NULL; + match->next = curr_match; + curr_match->prev = match; + first_match = curr_match = match; + } + } + + return OK; +} + +/* + * Make the completion list cyclic. Add the original text at the end. + * Return the number of matches (excluding the original). + */ + static int +make_cyclic() +{ + struct Completion *match, *orig; + int count = 0; + + if (first_match != NULL) + { + /* + * Find the end of the list. + */ + match = first_match; + count = 1; + while (match->next != NULL) + { + match = match->next; + ++count; + } + + if (original_text != NULL) + { + /* + * Allocate a new structure for the original text. + * Copy the original text to the new structure. + * Link it in the list at the end. + */ + orig = (struct Completion *)alloc((unsigned)sizeof( + struct Completion)); + if (orig != NULL) + { + if ((orig->str = strsave(original_text)) == NULL) + vim_free(orig); + else + { + orig->fname = NULL; + orig->original = TRUE; + orig->prev = match; + match->next = orig; + match = orig; + curr_match = orig; + } + } + } + match->next = first_match; + first_match->prev = match; + } + return count; +} + +/* + * Add any identifiers that match the given pattern to the list of + * completions. + */ + static void +complete_dictionaries(pat, dir) + char_u *pat; + int dir; +{ + struct Completion *save_curr_match = curr_match; + char_u *dict = p_dict; + char_u *ptr; + char_u *buf; + char_u *fname; + int at_start; + FILE *fp; + struct regexp *prog = NULL; + + if ((buf = alloc(LSIZE)) == NULL) + return; + if (curr_match != NULL) + { + while (curr_match->next != NULL) + curr_match = curr_match->next; + } + if (*dict != NUL) + { + (void)set_highlight('r'); + msg_highlight = TRUE; + MSG("Please wait, searching dictionaries"); + set_reg_ic(pat); /* set reg_ic according to p_ic, p_scs and pat */ + reg_magic = p_magic; + prog = vim_regcomp(pat); + } + while (*dict != NUL && prog != NULL && !got_int) + { + /* copy one dictionary file name into buf */ + (void)copy_option_part(&dict, buf, LSIZE, ","); + + fp = fopen((char *)buf, "r"); /* open dictionary file */ + + if (fp != NULL) + { + fname = strsave(buf); /* keep name of file */ + /* + * Read dictionary file line by line. + * Check each line for a match. + */ + while (!got_int && !vim_fgets(buf, LSIZE, fp)) + { + ptr = buf; + at_start = TRUE; + while (vim_regexec(prog, ptr, at_start)) + { + at_start = FALSE; + ptr = prog->startp[0]; + while (iswordchar(*ptr)) + ++ptr; + if (add_completion_and_infercase(prog->startp[0], + (int)(ptr - prog->startp[0]), fname, FORWARD) + == RET_ERROR) + break; + } + line_breakcheck(); + } + fclose(fp); + vim_free(fname); + } + } + vim_free(prog); + if (save_curr_match != NULL) + curr_match = save_curr_match; + else if (dir == BACKWARD) + curr_match = first_match; + vim_free(buf); +} + +/* + * Free the list of completions + */ + static void +free_completions() +{ + struct Completion *match; + + if (first_match == NULL) + return; + curr_match = first_match; + do + { + match = curr_match; + curr_match = curr_match->next; + vim_free(match->str); + vim_free(match->fname); + vim_free(match); + } while (curr_match != NULL && curr_match != first_match); + first_match = curr_match = NULL; +} + +/* + * Return the number of items in the Completion list + */ + static int +count_completions() +{ + struct Completion *match; + int num = 0; + + if (first_match == NULL) + return 0; + match = first_match; + do + { + if (!match->original) /* original string doesn't count */ + num++; + match = match->next; + } while (match != NULL && match != first_match); + return num; +} +#endif /* INSERT_EXPAND */ + +/* + * Next character is interpreted literally. + * A one, two or three digit decimal number is interpreted as its byte value. + * If one or two digits are entered, the next character is given to vungetc(). + */ + int +get_literal() +{ + int cc; + int nc; + int i; + + if (got_int) + return Ctrl('C'); + +#ifdef USE_GUI + /* + * In GUI there is no point inserting the internal code for a special key. + * It is more useful to insert the string "" instead. This would + * probably be useful in a text window too, but it would not be + * vi-compatible (maybe there should be an option for it?) -- webb + */ + if (gui.in_use) + ++allow_keys; +#endif + ++no_mapping; /* don't map the next key hits */ + cc = 0; + for (i = 0; i < 3; ++i) + { + do + nc = vgetc(); + while (nc == K_IGNORE || nc == K_SCROLLBAR || nc == K_HORIZ_SCROLLBAR); + if (!(State & CMDLINE)) + add_to_showcmd(nc, FALSE); + if (IS_SPECIAL(nc) || !isdigit(nc)) + break; + cc = cc * 10 + nc - '0'; + if (cc > 255) + cc = 255; /* limit range to 0-255 */ + nc = 0; + } + if (i == 0) /* no number entered */ + { + if (nc == K_ZERO) /* NUL is stored as NL */ + { + cc = '\n'; + nc = 0; + } + else + { + cc = nc; + nc = 0; + } + } + + if (cc == 0) /* NUL is stored as NL */ + cc = '\n'; + + --no_mapping; +#ifdef USE_GUI + if (gui.in_use) + --allow_keys; +#endif + if (nc) + vungetc(nc); + got_int = FALSE; /* CTRL-C typed after CTRL-V is not an interrupt */ + return cc; +} + +/* + * Insert character, taking care of special keys and mod_mask + */ + static void +insert_special(c, allow_modmask) + int c; + int allow_modmask; +{ + char_u *p; + int len; + + /* + * Special function key, translate into "". Up to the last '>' is + * inserted with ins_str(), so as not to replace characters in replace + * mode. + * Only use mod_mask for special keys, to avoid things like , + * unless 'allow_modmask' is TRUE. + */ + if (IS_SPECIAL(c) || (mod_mask && allow_modmask)) + { + p = get_special_key_name(c, mod_mask); + len = STRLEN(p); + c = p[len - 1]; + if (len > 2) + { + p[len - 1] = NUL; + ins_str(p); + AppendToRedobuff(p); + } + } + insertchar(c, FALSE, -1); +} + +/* + * Special characters in this context are those that need processing other + * than the simple insertion that can be performed here. This includes ESC + * which terminates the insert, and CR/NL which need special processing to + * open up a new line. This routine tries to optimize insertions performed by + * the "redo", "undo" or "put" commands, so it needs to know when it should + * stop and defer processing to the "normal" mechanism. + */ +#define ISSPECIAL(c) ((c) < ' ' || (c) >= DEL) + + void +insertchar(c, force_formatting, second_indent) + unsigned c; + int force_formatting; /* format line regardless of p_fo */ + int second_indent; /* indent for second line if >= 0 */ +{ + int haveto_redraw = FALSE; + int textwidth; + colnr_t leader_len; + int first_line = TRUE; + int fo_ins_blank; + int save_char = NUL; + + stop_arrow(); + + /* + * find out textwidth to be used: + * if 'textwidth' option is set, use it + * else if 'wrapmargin' option is set, use Columns - 'wrapmargin' + * if invalid value, use 0. + * Set default to window width (maximum 79) for "Q" command. + */ + textwidth = curbuf->b_p_tw; + if (textwidth == 0 && curbuf->b_p_wm) + textwidth = Columns - curbuf->b_p_wm; + if (textwidth < 0) + textwidth = 0; + if (force_formatting && textwidth == 0) + { + textwidth = Columns - 1; + if (textwidth > 79) + textwidth = 79; + } + + fo_ins_blank = has_format_option(FO_INS_BLANK); + + /* + * Try to break the line in two or more pieces when: + * - Always do this if we have been called to do formatting only. + * - Otherwise: + * - Don't do this if inserting a blank + * - Don't do this if an existing character is being replaced. + * - Do this if the cursor is not on the line where insert started + * or - 'formatoptions' doesn't have 'l' or the line was not too long + * before the insert. + * - 'formatoptions' doesn't have 'b' or a blank was inserted at or + * before 'textwidth' + */ + if (force_formatting || (!vim_iswhite(c) && + !(State == REPLACE && *ml_get_cursor() != NUL) && + (curwin->w_cursor.lnum != Insstart.lnum || + ((!has_format_option(FO_INS_LONG) || + Insstart_textlen <= (colnr_t)textwidth) && + (!fo_ins_blank || Insstart_blank_vcol <= (colnr_t)textwidth))))) + { + /* + * When 'ai' is off we don't want a space under the cursor to be + * deleted. Replace it with an 'x' temporarily. + */ + if (!curbuf->b_p_ai && vim_iswhite(gchar_cursor())) + { + save_char = gchar_cursor(); + pchar_cursor('x'); + } + while (textwidth && curwin->w_virtcol >= (colnr_t)textwidth) + { + int startcol; /* Cursor column at entry */ + int wantcol; /* column at textwidth border */ + int foundcol; /* column for start of spaces */ + int end_foundcol = 0; /* column for start of word */ + colnr_t len; + + if (!force_formatting && has_format_option(FO_WRAP_COMS)) + fo_do_comments = TRUE; + + /* Don't break until after the comment leader */ + leader_len = get_leader_len(ml_get_curline(), NULL); + if (!force_formatting && leader_len == 0 && + !has_format_option(FO_WRAP)) + + { + textwidth = 0; + break; + } + if ((startcol = curwin->w_cursor.col) == 0) + break; + /* find column of textwidth border */ + coladvance((colnr_t)textwidth); + wantcol = curwin->w_cursor.col; + + curwin->w_cursor.col = startcol - 1; + foundcol = 0; + /* + * Find position to break at. + * Stop at start of line. + * Stop at first entered white when 'formatoptions' has 'v' + */ + while (curwin->w_cursor.col > 0 && + ((!fo_ins_blank && !has_format_option(FO_INS_VI)) || + curwin->w_cursor.lnum != Insstart.lnum || + curwin->w_cursor.col >= Insstart.col)) + { + if (vim_iswhite(gchar_cursor())) + { + /* remember position of blank just before text */ + end_foundcol = curwin->w_cursor.col; + while (curwin->w_cursor.col > 0 && + vim_iswhite(gchar_cursor())) + --curwin->w_cursor.col; + if (curwin->w_cursor.col == 0 && + vim_iswhite(gchar_cursor())) + break; /* only spaces in front of text */ + /* Don't break until after the comment leader */ + if (curwin->w_cursor.col < leader_len) + break; + foundcol = curwin->w_cursor.col + 1; + if (curwin->w_cursor.col < (colnr_t)wantcol) + break; + } + --curwin->w_cursor.col; + } + + if (foundcol == 0) /* no spaces, cannot break line */ + { + curwin->w_cursor.col = startcol; + break; + } + + /* + * offset between cursor position and line break is used by + * replace stack functions + */ + replace_offset = startcol - end_foundcol - 1; + + /* + * adjust startcol for spaces that will be deleted and + * characters that will remain on top line + */ + curwin->w_cursor.col = foundcol; + while (vim_iswhite(gchar_cursor())) + { + ++curwin->w_cursor.col; + --startcol; + } + startcol -= foundcol; + if (startcol < 0) + startcol = 0; + + /* put cursor after pos. to break line */ + curwin->w_cursor.col = foundcol; + + Opencmd(FORWARD, FALSE, TRUE); + + replace_offset = 0; + if (second_indent >= 0 && first_line) + set_indent(second_indent, TRUE); + first_line = FALSE; + + /* + * check if cursor is not past the NUL off the line, cindent may + * have added or removed indent. + */ + curwin->w_cursor.col += startcol; + len = STRLEN(ml_get_curline()); + if (curwin->w_cursor.col > len) + curwin->w_cursor.col = len; + + curs_columns(FALSE); /* update curwin->w_virtcol */ + haveto_redraw = TRUE; +#ifdef CINDENT + can_cindent = TRUE; +#endif + } + + if (save_char) /* put back space after cursor */ + pchar_cursor(save_char); + + if (c == NUL) /* formatting only */ + return; + fo_do_comments = FALSE; + if (haveto_redraw) + { + /* + * If the cursor ended up just below the screen we scroll up here + * to avoid a redraw of the whole screen in the most common cases. + */ + if (curwin->w_cursor.lnum == curwin->w_botline && + !curwin->w_empty_rows) + win_del_lines(curwin, 0, 1, TRUE, TRUE); + updateScreen(CURSUPD); + } + } + if (c == NUL) /* only formatting was wanted */ + return; + + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + + /* + * If there's any pending input, grab up to INPUT_BUFLEN at once. + * This speeds up normal text input considerably. + */ +#define INPUT_BUFLEN 100 + if (!ISSPECIAL(c) && vpeekc() != NUL && State != REPLACE +#ifdef RIGHTLEFT + && !p_ri +#endif + ) + { + char_u p[INPUT_BUFLEN + 1]; + int i; + + p[0] = c; + i = 1; + while ((c = vpeekc()) != NUL && !ISSPECIAL(c) && i < INPUT_BUFLEN && + (textwidth == 0 || + (curwin->w_virtcol += charsize(p[i - 1])) < (colnr_t)textwidth) && + !(!no_abbr && !iswordchar(c) && iswordchar(p[i - 1]))) + { +#ifdef RIGHTLEFT + c = vgetc(); + if (p_hkmap && KeyTyped) + c = hkmap(c); /* Hebrew mode mapping */ + p[i++] = c; +#else + p[i++] = vgetc(); +#endif + } + +#ifdef DIGRAPHS + do_digraph(-1); /* clear digraphs */ + do_digraph(p[i-1]); /* may be the start of a digraph */ +#endif + p[i] = '\0'; + ins_str(p); + AppendToRedobuff(p); + } + else + { + ins_char(c); + AppendCharToRedobuff(c); + } +} + +/* + * start_arrow() is called when an arrow key is used in insert mode. + * It resembles hitting the key. + */ + static void +start_arrow(end_insert_pos) + FPOS *end_insert_pos; +{ + if (!arrow_used) /* something has been inserted */ + { + AppendToRedobuff(ESC_STR); + arrow_used = TRUE; /* this means we stopped the current insert */ + stop_insert(end_insert_pos); + } +} + +/* + * stop_arrow() is called before a change is made in insert mode. + * If an arrow key has been used, start a new insertion. + */ + static void +stop_arrow() +{ + if (arrow_used) + { + (void)u_save_cursor(); /* errors are ignored! */ + Insstart = curwin->w_cursor; /* new insertion starts here */ + Insstart_textlen = linetabsize(ml_get_curline()); + ResetRedobuff(); + AppendToRedobuff((char_u *)"1i"); /* pretend we start an insertion */ + arrow_used = FALSE; + } +} + +/* + * do a few things to stop inserting + */ + static void +stop_insert(end_insert_pos) + FPOS *end_insert_pos; /* where insert ended */ +{ + stop_redo_ins(); + + /* + * save the inserted text for later redo with ^@ + */ + vim_free(last_insert); + last_insert = get_inserted(); + last_insert_skip = new_insert_skip; + + /* + * If we just did an auto-indent, remove the white space from the end of + * the line, and put the cursor back. + */ + if (did_ai && !arrow_used) + { + if (gchar_cursor() == NUL && curwin->w_cursor.col > 0) + --curwin->w_cursor.col; + while (vim_iswhite(gchar_cursor())) + delchar(TRUE); + if (gchar_cursor() != NUL) + ++curwin->w_cursor.col; /* put cursor back on the NUL */ + if (curwin->w_p_list) /* the deletion is only seen in list + * mode */ + updateline(); + } + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + + /* set '[ and '] to the inserted text */ + curbuf->b_op_start = Insstart; + curbuf->b_op_end = *end_insert_pos; +} + +/* + * Set the last inserted text to a single character. + * Used for the replace command. + */ + void +set_last_insert(c) + int c; +{ + vim_free(last_insert); + last_insert = alloc(4); + if (last_insert != NULL) + { + last_insert[0] = Ctrl('V'); + last_insert[1] = c; + last_insert[2] = ESC; + last_insert[3] = NUL; + /* Use the CTRL-V only when not entering a digit */ + last_insert_skip = isdigit(c) ? 1 : 0; + } +} + +/* + * move cursor to start of line + * if flag == TRUE move to first non-white + * if flag == MAYBE then move to first non-white if startofline is set, + * otherwise don't move at all. + */ + void +beginline(flag) + int flag; +{ + if (flag == MAYBE && !p_sol) + coladvance(curwin->w_curswant); + else + { + curwin->w_cursor.col = 0; + if (flag) + { + register char_u *ptr; + + for (ptr = ml_get_curline(); vim_iswhite(*ptr); ++ptr) + ++curwin->w_cursor.col; + } + curwin->w_set_curswant = TRUE; + } +} + +/* + * oneright oneleft cursor_down cursor_up + * + * Move one char {right,left,down,up}. + * Return OK when sucessful, FAIL when we hit a line of file boundary. + */ + + int +oneright() +{ + char_u *ptr; + + ptr = ml_get_cursor(); + if (*ptr++ == NUL || *ptr == NUL) + return FAIL; + curwin->w_set_curswant = TRUE; + ++curwin->w_cursor.col; + return OK; +} + + int +oneleft() +{ + if (curwin->w_cursor.col == 0) + return FAIL; + curwin->w_set_curswant = TRUE; + --curwin->w_cursor.col; + return OK; +} + + int +cursor_up(n) + long n; +{ + if (n != 0 && curwin->w_cursor.lnum == 1) + return FAIL; + if (n >= curwin->w_cursor.lnum) + curwin->w_cursor.lnum = 1; + else + curwin->w_cursor.lnum -= n; + + /* try to advance to the column we want to be at */ + coladvance(curwin->w_curswant); + + if (op_type == NOP) + cursupdate(); /* make sure curwin->w_topline is valid */ + + return OK; +} + + int +cursor_down(n) + long n; +{ + if (n != 0 && curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) + return FAIL; + curwin->w_cursor.lnum += n; + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + + /* try to advance to the column we want to be at */ + coladvance(curwin->w_curswant); + + if (op_type == NOP) + cursupdate(); /* make sure curwin->w_topline is valid */ + + return OK; +} + +/* + * screengo() -- + * + * move 'dist' lines in direction 'dir', counting lines by *screen* + * lines rather than lines in the file + * 'dist' must be positive. + * + * return OK if able to move cursor, FAIL otherwise. + */ + + int +screengo(dir, dist) + int dir; + long dist; +{ + int linelen = linetabsize(ml_get_curline()); + int retval = OK; + int atend = FALSE; + int n; + + op_motion_type = MCHAR; + op_inclusive = FALSE; + + /* + * Instead of sticking at the last character of the line in the file we + * try to stick in the last column of the screen + */ + if (curwin->w_curswant == MAXCOL) + { + atend = TRUE; + curwin->w_curswant = ((curwin->w_virtcol + + (curwin->w_p_nu ? 8 : 0)) / Columns + 1) * Columns - 1; + if (curwin->w_p_nu && curwin->w_curswant > 8) + curwin->w_curswant -= 8; + } + else + while (curwin->w_curswant >= (colnr_t)(linelen + Columns)) + curwin->w_curswant -= Columns; + + while (dist--) + { + if (dir == BACKWARD) + { + /* move back within line */ + if ((long)curwin->w_curswant >= Columns) + curwin->w_curswant -= Columns; + else /* to previous line */ + { + if (curwin->w_cursor.lnum == 1) + { + retval = FAIL; + break; + } + --curwin->w_cursor.lnum; + linelen = linetabsize(ml_get_curline()); + n = ((linelen + (curwin->w_p_nu ? 8 : 0) - 1) / Columns) + * Columns; + if (curwin->w_p_nu && + (long)curwin->w_curswant >= Columns - 8 && n) + n -= Columns; + curwin->w_curswant += n; + } + } + else /* dir == FORWARD */ + { + n = ((linelen + (curwin->w_p_nu ? 8 : 0) - 1) / Columns) * Columns; + if (curwin->w_p_nu && n > 8) + n -= 8; + /* move forward within line */ + if (curwin->w_curswant < (colnr_t)n) + curwin->w_curswant += Columns; + else /* to next line */ + { + if (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) + { + retval = FAIL; + break; + } + curwin->w_cursor.lnum++; + linelen = linetabsize(ml_get_curline()); + curwin->w_curswant %= Columns; + } + } + } + coladvance(curwin->w_curswant); + if (atend) + curwin->w_curswant = MAXCOL; /* stick in the last column */ + if (op_type == NOP) + cursupdate(); + return retval; +} + +/* + * move screen 'count' pages up or down and update screen + * + * return FAIL for failure, OK otherwise + */ + int +onepage(dir, count) + int dir; + long count; +{ + linenr_t lp; + long n; + int off; + + if (curbuf->b_ml.ml_line_count == 1) /* nothing to do */ + return FAIL; + for ( ; count > 0; --count) + { + if (dir == FORWARD ? (curwin->w_topline >= + curbuf->b_ml.ml_line_count - 1) : (curwin->w_topline == 1)) + { + beep_flush(); + return FAIL; + } + if (dir == FORWARD) + { + /* at end of file */ + if (curwin->w_botline > curbuf->b_ml.ml_line_count) + curwin->w_topline = curbuf->b_ml.ml_line_count; + /* next line is big */ + /* or just three lines on screen */ + else + { + if (plines(curwin->w_botline) >= curwin->w_height - 2 || + curwin->w_botline - curwin->w_topline <= 3) + off = 0; + else + off = 2; + curwin->w_topline = curwin->w_botline - off; + curwin->w_cursor.lnum = curwin->w_topline; + } + comp_Botline(curwin); + } + else /* dir == BACKWARDS */ + { + lp = curwin->w_topline; + /* + * If the first two lines on the screen are not too big, we keep + * them on the screen. + */ + if ((n = plines(lp)) > curwin->w_height / 2) + --lp; + else if (lp < curbuf->b_ml.ml_line_count && + n + plines(lp + 1) < curwin->w_height / 2) + ++lp; + curwin->w_cursor.lnum = lp; + n = 0; + while (n <= curwin->w_height && lp >= 1) + { + n += plines(lp); + --lp; + } + if (n <= curwin->w_height) /* at begin of file */ + { + curwin->w_topline = 1; + comp_Botline(curwin); + } + else if (lp >= curwin->w_topline - 2) /* very long lines */ + { + --curwin->w_topline; + comp_Botline(curwin); + curwin->w_cursor.lnum = curwin->w_botline - 1; + } + else + { + curwin->w_topline = lp + 2; + comp_Botline(curwin); + } + } + } + cursor_correct(); + beginline(MAYBE); + updateScreen(VALID); + return OK; +} + +/* #define KEEP_SCREEN_LINE */ + + void +halfpage(flag, Prenum) + int flag; + linenr_t Prenum; +{ + long scrolled = 0; + int i; + int n; + + if (Prenum) + curwin->w_p_scroll = (Prenum > curwin->w_height) ? + curwin->w_height : Prenum; + n = (curwin->w_p_scroll <= curwin->w_height) ? + curwin->w_p_scroll : curwin->w_height; + + if (flag) /* scroll down */ + { + while (n > 0 && curwin->w_botline <= curbuf->b_ml.ml_line_count) + { + i = plines(curwin->w_topline); + n -= i; + if (n < 0 && scrolled) + break; + scrolled += i; + ++curwin->w_topline; + comp_Botline(curwin); /* compute curwin->w_botline */ +#ifndef KEEP_SCREEN_LINE + if (curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) + ++curwin->w_cursor.lnum; +#endif + } +#ifndef KEEP_SCREEN_LINE + /* + * When hit bottom of the file: move cursor down. + */ + if (n > 0) + { + curwin->w_cursor.lnum += n; + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + } +#else + /* try to put the cursor in the same screen line */ + while ((curwin->w_cursor.lnum < curwin->w_topline || scrolled > 0) + && curwin->w_cursor.lnum < curwin->w_botline - 1) + { + scrolled -= plines(curwin->w_cursor.lnum); + if (scrolled < 0 && curwin->w_cursor.lnum >= curwin->w_topline) + break; + ++curwin->w_cursor.lnum; + } +#endif + } + else /* scroll up */ + { + while (n > 0 && curwin->w_topline > 1) + { + i = plines(curwin->w_topline - 1); + n -= i; + if (n < 0 && scrolled) + break; + scrolled += i; + --curwin->w_topline; +#ifndef KEEP_SCREEN_LINE + if (curwin->w_cursor.lnum > 1) + --curwin->w_cursor.lnum; +#endif + } + comp_Botline(curwin); /* compute curwin->w_botline */ +#ifndef KEEP_SCREEN_LINE + /* + * When hit top of the file: move cursor up. + */ + if (n > 0) + { + if (curwin->w_cursor.lnum > (linenr_t)n) + curwin->w_cursor.lnum -= n; + else + curwin->w_cursor.lnum = 1; + } +#else + /* try to put the cursor in the same screen line */ + scrolled += n; /* move cursor when topline is 1 */ + while (curwin->w_cursor.lnum > curwin->w_topline && + (scrolled > 0 || curwin->w_cursor.lnum >= curwin->w_botline)) + { + scrolled -= plines(curwin->w_cursor.lnum - 1); + if (scrolled < 0 && curwin->w_cursor.lnum < curwin->w_botline) + break; + --curwin->w_cursor.lnum; + } +#endif + } + cursor_correct(); + beginline(MAYBE); + updateScreen(VALID); +} + +/* + * Stuff the last inserted text in the read buffer. + * Last_insert actually is a copy of the redo buffer, so we + * first have to remove the command. + */ + int +stuff_inserted(c, count, no_esc) + int c; + long count; + int no_esc; +{ + char_u *esc_ptr = NULL; + char_u *ptr; + + ptr = get_last_insert(); + if (ptr == NULL) + { + EMSG(e_noinstext); + return FAIL; + } + + if (c) + stuffcharReadbuff(c); + if (no_esc && (esc_ptr = (char_u *)vim_strrchr(ptr, 27)) != NULL) + *esc_ptr = NUL; /* remove the ESC */ + + do + stuffReadbuff(ptr); + while (--count > 0); + + if (esc_ptr != NULL) + *esc_ptr = 27; /* put the ESC back */ + + return OK; +} + + char_u * +get_last_insert() +{ + if (last_insert == NULL) + return NULL; + return last_insert + last_insert_skip; +} + +/* + * Check the word in front of the cursor for an abbreviation. + * Called when the non-id character "c" has been entered. + * When an abbreviation is recognized it is removed from the text and + * the replacement string is inserted in typebuf[], followed by "c". + */ + static int +echeck_abbr(c) + int c; +{ + if (p_paste || no_abbr) /* no abbreviations or in paste mode */ + return FALSE; + + return check_abbr(c, ml_get_curline(), curwin->w_cursor.col, + curwin->w_cursor.lnum == Insstart.lnum ? Insstart.col : 0); +} + +/* + * replace-stack functions + * + * When replacing characters the replaced character is remembered + * for each new character. This is used to re-insert the old text + * when backspacing. + * + * replace_offset is normally 0, in which case replace_push will add a new + * character at the end of the stack. If replace_offset is not 0, that many + * characters will be left on the stack above the newly inserted character. + */ + +char_u *replace_stack = NULL; +long replace_stack_nr = 0; /* next entry in replace stack */ +long replace_stack_len = 0; /* max. number of entries */ + + void +replace_push(c) + int c; /* character that is replaced (NUL is none) */ +{ + char_u *p; + + if (replace_stack_nr < replace_offset) /* nothing to do */ + return; + if (replace_stack_len <= replace_stack_nr) + { + replace_stack_len += 50; + p = lalloc(sizeof(char_u) * replace_stack_len, TRUE); + if (p == NULL) /* out of memory */ + { + replace_stack_len -= 50; + return; + } + if (replace_stack != NULL) + { + vim_memmove(p, replace_stack, + (size_t)(replace_stack_nr * sizeof(char_u))); + vim_free(replace_stack); + } + replace_stack = p; + } + p = replace_stack + replace_stack_nr - replace_offset; + if (replace_offset) + vim_memmove(p + 1, p, (size_t)(replace_offset * sizeof(char_u))); + *p = c; + ++replace_stack_nr; +} + +/* + * pop one item from the replace stack + * return -1 if stack empty + * return 0 if no character was replaced + * return replaced character otherwise + */ + int +replace_pop() +{ + if (replace_stack_nr == 0) + return -1; + return (int)replace_stack[--replace_stack_nr]; +} + +/* + * make the replace stack empty + * (called when exiting replace mode) + */ + void +replace_flush() +{ + vim_free(replace_stack); + replace_stack = NULL; + replace_stack_len = 0; + replace_stack_nr = 0; +} + +#if defined(LISPINDENT) || defined(CINDENT) +/* + * Re-indent the current line, based on the current contents of it and the + * surrounding lines. Fixing the cursor position seems really easy -- I'm very + * confused what all the part that handles Control-T is doing that I'm not. + * "get_the_indent" should be get_c_indent or get_lisp_indent. + */ + + void +fixthisline(get_the_indent) + int (*get_the_indent) __ARGS((void)); +{ + change_indent(INDENT_SET, get_the_indent(), FALSE); + if (linewhite(curwin->w_cursor.lnum)) + did_ai = TRUE; /* delete the indent if the line stays empty */ +} +#endif /* defined(LISPINDENT) || defined(CINDENT) */ + +#ifdef CINDENT +/* + * return TRUE if 'cinkeys' contains the key "keytyped", + * when == '*': Only if key is preceded with '*' (indent before insert) + * when == '!': Only if key is prededed with '!' (don't insert) + * when == ' ': Only if key is not preceded with '*'(indent afterwards) + * + * If line_is_empty is TRUE accept keys with '0' before them. + */ + int +in_cinkeys(keytyped, when, line_is_empty) + int keytyped; + int when; + int line_is_empty; +{ + char_u *look; + int try_match; + char_u *p; + + for (look = curbuf->b_p_cink; *look; ) + { + /* + * Find out if we want to try a match with this key, depending on + * 'when' and a '*' or '!' before the key. + */ + switch (when) + { + case '*': try_match = (*look == '*'); break; + case '!': try_match = (*look == '!'); break; + default: try_match = (*look != '*'); break; + } + if (*look == '*' || *look == '!') + ++look; + + /* + * If there is a '0', only accept a match if the line is empty. + */ + if (*look == '0') + { + if (!line_is_empty) + try_match = FALSE; + ++look; + } + + /* + * does it look like a control character? + */ + if (*look == '^' && look[1] >= '@' && look[1] <= '_') + { + if (try_match && keytyped == Ctrl(look[1])) + return TRUE; + look += 2; + } + /* + * 'o' means "o" command, open forward. + * 'O' means "O" command, open backward. + */ + else if (*look == 'o') + { + if (try_match && keytyped == KEY_OPEN_FORW) + return TRUE; + ++look; + } + else if (*look == 'O') + { + if (try_match && keytyped == KEY_OPEN_BACK) + return TRUE; + ++look; + } + + /* + * 'e' means to check for "else" at start of line and just before the + * cursor. + */ + else if (*look == 'e') + { + if (try_match && keytyped == 'e' && curwin->w_cursor.col >= 4) + { + p = ml_get_curline(); + if (skipwhite(p) == p + curwin->w_cursor.col - 4 && + STRNCMP(p + curwin->w_cursor.col - 4, "else", 4) == 0) + return TRUE; + } + ++look; + } + + /* + * ':' only causes an indent if it is at the end of a label or case + * statement. + */ + else if (*look == ':') + { + if (try_match && keytyped == ':') + { + p = ml_get_curline(); + if (iscase(p) || islabel(30)) + return TRUE; + } + ++look; + } + + + /* + * Is it a key in <>, maybe? + */ + else if (*look == '<') + { + if (try_match) + { + /* + * make up some named keys , , , <0>, <>>, <<>, <*> + * and so that people can re-indent on o, O, e, 0, <, >, * + * and ! keys if they really really want to. + */ + if (vim_strchr((char_u *)"<>!*oOe0", look[1]) != NULL && + keytyped == look[1]) + return TRUE; + + if (keytyped == get_special_key_code(look + 1)) + return TRUE; + } + while (*look && *look != '>') + look++; + while (*look == '>') + look++; + } + + /* + * ok, it's a boring generic character. + */ + else + { + if (try_match && *look == keytyped) + return TRUE; + ++look; + } + + /* + * Skip over ", ". + */ + look = skip_to_option_part(look); + } + return FALSE; +} +#endif /* CINDENT */ + +#if defined(RIGHTLEFT) || defined(PROTO) +/* + * Map Hebrew keyboard when in hkmap mode. + */ + int +hkmap(c) + int c; +{ + switch(c) + { + case '`': return ';'; + case '/': return '.'; + case '\'': return ','; + case 'q': return '/'; + case 'w': return '\''; + + /* Hebrew letters - set offset from 'a' */ + case ',': c = '{'; break; + case '.': c = 'v'; break; + case ';': c = 't'; break; + default: { + static char str[] = "zqbcxlsjphmkwonu ydafe rig"; + + if (c < 'a' || c > 'z') + return c; + c = str[c - 'a']; + break; + } + } + + return c - 'a' + p_aleph; +} +#endif diff --git a/usr.bin/vim/feature.h b/usr.bin/vim/feature.h new file mode 100644 index 00000000000..c526eb33912 --- /dev/null +++ b/usr.bin/vim/feature.h @@ -0,0 +1,217 @@ +/* $OpenBSD: feature.h,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* vi:set ts=8 sw=8: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ +/* + * feature.h: Defines for optional code and preferences + * + * Edit this file to include/exclude parts of Vim, before compiling. + * The only other file that may be edited is Makefile, it contains machine + * specific options. + * + * When you want to include a define, change the "#if 0" into "#if 1". + */ + +/* + * Optional code: + * ============== + */ + +/* + * DIGRAPHS When defined: Include digraph support. + * In insert mode and on the command line you will be + * able to use digraphs. The CTRL-K command will work. + */ +#define DIGRAPHS + +/* + * HAVE_LANGMAP When defined: Include support for 'langmap' option. + * Only useful when you put your keyboard in a special + * language mode, e.g. for typing greek. + */ +#undef HAVE_LANGMAP + +/* + * INSERT_EXPAND When defined: Support for CTRL-N/CTRL-P/CTRL-X in + * insert mode. Takes about 4Kbyte of code. + */ +#define INSERT_EXPAND + +/* + * RIGHTLEFT When defined: Right-to-left typing and Hebrew support + * Takes some code. + */ +#define RIGHTLEFT + +/* + * EMACS_TAGS When defined: Include support for emacs style + * TAGS file. Takes some code. + */ +#define EMACS_TAGS + +/* + * AUTOCMD When defined: Include support for ":autocmd" + */ +#define AUTOCMD + +/* + * VIMINFO When defined: Include support for reading/writing + * the viminfo file. Takes about 8Kbyte of code. + */ +#define VIMINFO + +/* + * Choose one out of the following four: + * + * NO_BUILTIN_TCAPS When defined: Do not include any builtin termcap + * entries (used only with HAVE_TGETENT defined). + * + * (nothing) Machine specific termcap entries will be included. + * + * SOME_BUILTIN_TCAPS When defined: Include most useful builtin termcap + * entries (used only with NO_BUILTIN_TCAPS not defined). + * This is the default. + * + * ALL_BUILTIN_TCAPS When defined: Include all builtin termcap entries + * (used only with NO_BUILTIN_TCAPS not defined). + */ +#define NO_BUILTIN_TCAPS + +#ifndef NO_BUILTIN_TCAPS +# if 0 +# define ALL_BUILTIN_TCAPS +# else +# if 1 +# define SOME_BUILTIN_TCAPS /* default */ +# endif +# endif +#endif + +/* + * LISPINDENT When defined: Include lisp indenting (From Eric + * Fischer). Doesn't completely work like vi (yet). + * CINDENT When defined: Include C code indenting (From Eric + * Fischer). + * SMARTINDENT When defined: Do smart C code indenting when the 'si' + * option is set. It's not as good as CINDENT, only + * included to keep the old code. + * + * These two need to be defined when making prototypes. + */ +#define LISPINDENT + +#define CINDENT + +#define SMARTINDENT + +/* + * Preferences: + * ============ + */ + +/* + * COMPATIBLE When defined: Start in vi-compatible mode. + * Sets all option defaults to their vi-compatible value. + */ +#undef COMPATIBLE + +/* + * WRITEBACKUP When defined: 'writebackup' is default on: Use + * a backup file while overwriting a file. + */ +#undef WRITEBACKUP + +/* + * SAVE_XTERM_SCREEN When defined: The t_ti and t_te entries for the + * builtin xterm will be set to save the screen when + * starting Vim and restoring it when exiting. + */ +#define SAVE_XTERM_SCREEN + +/* + * DEBUG When defined: Output a lot of debugging garbage. + */ +#undef DEBUG + +/* + * VIMRC_FILE Name of the .vimrc file in current dir. + */ +#define VIMRC_FILE ".vimrc" + +/* + * EXRC_FILE Name of the .exrc file in current dir. + */ +#define EXRC_FILE ".exrc" + +/* + * GVIMRC_FILE Name of the .gvimrc file in current dir. + */ +#define GVIMRC_FILE ".gvimrc" + +/* + * USR_VIMRC_FILE Name of the user .vimrc file. + */ +#define USR_VIMRC_FILE "$HOME/.vimrc" + +/* + * USR_EXRC_FILE Name of the user .exrc file. + */ +#define USR_EXRC_FILE "$HOME/.exrc" + +/* + * USR_GVIMRC_FILE Name of the user .gvimrc file. + */ +#define USR_GVIMRC_FILE "$HOME/.gvimrc" + +/* + * SYS_VIMRC_FILE Name of the system-wide .vimrc file. + */ +#define SYS_VIMRC_FILE "/etc/vimrc" + +/* + * SYS_COMPATRC_FILE Name of the system-wide .vimrc file for compat mode. + */ +#define SYS_COMPATRC_FILE "/etc/virc" + +/* + * SYS_GVIMRC_FILE Name of the system-wide .gvimrc file. + */ +#define SYS_GVIMRC_FILE "/etc/gvimrc" + +/* + * VIM_HLP Name of the help file. + */ +#define VIM_HLP "/usr/share/vim/vim_help.txt" + + +/* + * Machine dependent: + * ================== + */ + +/* + * USE_SYSTEM Unix only. When defined: Use system() instead of + * fork/exec for starting a shell. + */ +#undef USE_SYSTEM + +/* + * WANT_X11 Unix only. When defined: Include code for xterm title + * saving. Only works if HAVE_X11 is also defined. + */ +#undef WANT_X11 + +/* + * WANT_GUI Would be nice, but that doesn't work. To compile Vim + * with the GUI (gvim) you have to edit Makefile. + */ + +/* + * NO_ARP Amiga only. When defined: Do not use arp.library, DOS + * 2.0 required. + */ +#undef NO_ARP diff --git a/usr.bin/vim/fileio.c b/usr.bin/vim/fileio.c new file mode 100644 index 00000000000..1fb6f12a0d6 --- /dev/null +++ b/usr.bin/vim/fileio.c @@ -0,0 +1,1991 @@ +/* $OpenBSD: fileio.c,v 1.1.1.1 1996/09/07 21:40:26 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * fileio.c: read from and write to a file + */ + +#if defined MSDOS || defined WIN32 +# include /* for lseek(), must be before vim.h */ +#endif + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" +#ifdef HAVE_FCNTL_H +# include +#endif + +#ifdef LATTICE +# include /* for Lock() and UnLock() */ +#endif + +#define BUFSIZE 8192 /* size of normal write buffer */ +#define SBUFSIZE 256 /* size of emergency write buffer */ + +#ifdef VIMINFO +static void check_marks_read __ARGS((void)); +#endif +static void msg_add_fname __ARGS((BUF *, char_u *)); +static int msg_add_textmode __ARGS((int)); +static void msg_add_lines __ARGS((int, long, long)); +static int write_buf __ARGS((int, char_u *, int)); + +static linenr_t write_no_eol_lnum = 0; /* non-zero lnum when last line of + next binary write should not have + an eol */ + + void +filemess(buf, name, s) + BUF *buf; + char_u *name; + char_u *s; +{ + msg_add_fname(buf, name); /* put file name in IObuff with quotes */ + STRCAT(IObuff, s); + /* + * For the first message may have to start a new line. + * For further ones overwrite the previous one, reset msg_scroll before + * calling filemess(). + */ + msg_start(); + msg_outtrans(IObuff); + stop_highlight(); + msg_clr_eos(); + flushbuf(); +} + +/* + * Read lines from file 'fname' into the buffer after line 'from'. + * + * 1. We allocate blocks with lalloc, as big as possible. + * 2. Each block is filled with characters from the file with a single read(). + * 3. The lines are inserted in the buffer with ml_append(). + * + * (caller must check that fname != NULL) + * + * lines_to_skip is the number of lines that must be skipped + * lines_to_read is the number of lines that are appended + * When not recovering lines_to_skip is 0 and lines_to_read MAXLNUM. + * + * return FAIL for failure, OK otherwise + */ + int +readfile(fname, sfname, from, newfile, lines_to_skip, lines_to_read, filtering) + char_u *fname; + char_u *sfname; + linenr_t from; + int newfile; + linenr_t lines_to_skip; + linenr_t lines_to_read; + int filtering; +{ + int fd; + register char_u c; + register linenr_t lnum = from; + register char_u *ptr = NULL; /* pointer into read buffer */ + register char_u *buffer = NULL; /* read buffer */ + char_u *new_buffer = NULL; /* init to shut up gcc */ + char_u *line_start = NULL; /* init to shut up gcc */ + colnr_t len; + register long size; + register char_u *p; + long filesize = 0; + int split = 0; /* number of split lines */ +#define UNKNOWN 0x0fffffff /* file size is unknown */ + linenr_t linecnt = curbuf->b_ml.ml_line_count; + int error = FALSE; /* errors encountered */ + int tx_error = FALSE; /* textmode, but no CR */ + long linerest = 0; /* remaining characters in line */ + int firstpart = TRUE; /* reading first part */ +#ifdef UNIX + int perm; +#endif + int textmode; /* accept CR-LF linebreak */ + struct stat st; + int file_readonly; + linenr_t skip_count = lines_to_skip; + linenr_t read_count = lines_to_read; + int msg_save = msg_scroll; + linenr_t read_no_eol_lnum = 0; /* non-zero lnum when last + line of last read was + missing the eol */ + + + /* + * If there is no file name yet, use the one for the read file. + * b_notedited is set to reflect this. + * Don't do this for a read from a filter. + * Only do this when 'cpoptions' contains the 'f' flag. + */ + if (curbuf->b_filename == NULL && !filtering && + vim_strchr(p_cpo, CPO_FNAMER) != NULL) + { + if (setfname(fname, sfname, FALSE) == OK) + curbuf->b_notedited = TRUE; + } + + if (shortmess(SHM_OVER) || curbuf->b_help) + msg_scroll = FALSE; /* overwrite previous file message */ + else + msg_scroll = TRUE; /* don't overwrite previous file message */ + if (sfname == NULL) + sfname = fname; + /* + * For Unix: Use the short filename whenever possible. + * Avoids problems with networks and when directory names are changed. + * Don't do this for MS-DOS, a "cd" in a sub-shell may have moved us to + * another directory, which we don't detect. + */ +#if defined(UNIX) || defined(__EMX__) + if (!did_cd) + fname = sfname; +#endif + +#ifdef UNIX + /* + * On Unix it is possible to read a directory, so we have to + * check for it before the open(). + */ + perm = getperm(fname); +# ifdef _POSIX_SOURCE + if (perm >= 0 && !S_ISREG(perm) /* not a regular file ... */ +# ifdef S_ISFIFO + && !S_ISFIFO(perm) /* ... or fifo or socket */ +# endif + ) +# else + if (perm >= 0 && (perm & S_IFMT) != S_IFREG /* not a regular file ... */ +# ifdef S_IFIFO + && (perm & S_IFMT) != S_IFIFO /* ... or fifo ... */ +# endif +# ifdef S_IFSOCK + && (perm & S_IFMT) != S_IFSOCK /* ... or socket */ +# endif + ) +# endif + { +# ifdef _POSIX_SOURCE + if (S_ISDIR(perm)) +# else + if ((perm & S_IFMT) == S_IFDIR) +# endif + filemess(curbuf, fname, (char_u *)"is a directory"); + else + filemess(curbuf, fname, (char_u *)"is not a file"); + msg_scroll = msg_save; + return FAIL; + } +#endif + + /* + * When opening a new file we take the readonly flag from the file. + * Default is r/w, can be set to r/o below. + * Don't reset it when in readonly mode + */ + if (newfile && !readonlymode) /* default: set file not readonly */ + curbuf->b_p_ro = FALSE; + + if (newfile) + { + if (stat((char *)fname, &st) >= 0) /* remember time of file */ + { + curbuf->b_mtime = st.st_mtime; + curbuf->b_mtime_read = st.st_mtime; +#ifdef UNIX + /* + * Set the protection bits of the swap file equal to the original + * file. This makes it possible for others to read the name of the + * original file from the swapfile. + */ + if (curbuf->b_ml.ml_mfp->mf_fname != NULL) + (void)setperm(curbuf->b_ml.ml_mfp->mf_fname, + (st.st_mode & 0777) | 0600); +#endif + } + else + { + curbuf->b_mtime = 0; + curbuf->b_mtime_read = 0; + } + } + +/* + * for UNIX: check readonly with perm and access() + * for MSDOS and Amiga: check readonly by trying to open the file for writing + */ + file_readonly = FALSE; +#if defined(UNIX) || defined(DJGPP) || defined(__EMX__) + if ( +# ifdef UNIX + !(perm & 0222) || +# endif + access((char *)fname, W_OK)) + file_readonly = TRUE; + fd = open((char *)fname, O_RDONLY | O_EXTRA); +#else + if (!newfile || readonlymode || (fd = + open((char *)fname, O_RDWR | O_EXTRA)) < 0) + { + file_readonly = TRUE; + fd = open((char *)fname, O_RDONLY | O_EXTRA); /* try to open ro */ + } +#endif + + if (fd < 0) /* cannot open at all */ + { +#ifndef UNIX + int isdir_f; +#endif + msg_scroll = msg_save; +#ifndef UNIX + /* + * On MSDOS and Amiga we can't open a directory, check here. + */ + isdir_f = (mch_isdir(fname)); + /* replace with short name now, for the messages */ + if (!did_cd) + fname = sfname; + if (isdir_f) + filemess(curbuf, fname, (char_u *)"is a directory"); + else +#endif + if (newfile) + { +#ifdef UNIX + if (perm < 0) +#endif + { + filemess(curbuf, fname, (char_u *)"[New File]"); +#ifdef AUTOCMD + apply_autocmds(EVENT_BUFNEWFILE, fname, fname); +#endif + return OK; /* a new file is not an error */ + } +#ifdef UNIX + else + filemess(curbuf, fname, (char_u *)"[Permission Denied]"); +#endif + } + + return FAIL; + } + + /* + * Only set the 'ro' flag for readonly files the first time they are + * loaded. + * Help files always get readonly mode + */ + if ((newfile && file_readonly) || curbuf->b_help) + curbuf->b_p_ro = TRUE; + + if (newfile) + curbuf->b_p_eol = TRUE; + +#ifndef UNIX + /* replace with short name now, for the messages */ + if (!did_cd) + fname = sfname; +#endif + ++no_wait_return; /* don't wait for return yet */ + + /* + * Set '[ mark to the line above where the lines go (line 1 if zero). + */ + curbuf->b_op_start.lnum = ((from == 0) ? 1 : from); + curbuf->b_op_start.col = 0; + +#ifdef AUTOCMD + { + int m = msg_scroll; + int n = msg_scrolled; + + /* + * The output from the autocommands should not overwrite anything and + * should not be overwritten: Set msg_scroll, restore its value if no + * output was done. + */ + msg_scroll = TRUE; + if (filtering) + apply_autocmds(EVENT_FILTERREADPRE, NULL, fname); + else if (newfile) + apply_autocmds(EVENT_BUFREADPRE, NULL, fname); + else + apply_autocmds(EVENT_FILEREADPRE, fname, fname); + if (msg_scrolled == n) + msg_scroll = m; + } +#endif + + if (!recoverymode && !filtering) + filemess(curbuf, fname, (char_u *)""); /* show that we are busy */ + + msg_scroll = FALSE; /* overwrite the file message */ + + /* + * Set textmode now, before the "retry" caused by 'textauto' and after the + * autocommands, that may reset it. + */ + textmode = curbuf->b_p_tx; + +retry: + while (!error && !got_int) + { + /* + * We allocate as much space for the file as we can get, plus + * space for the old line plus room for one terminating NUL. + * The amount is limited by the fact that read() only can read + * upto max_unsigned characters (and other things). + */ +#if SIZEOF_INT <= 2 + if (linerest >= 0x7ff0) + { + ++split; + *ptr = NL; /* split line by inserting a NL */ + size = 1; + } + else +#endif + { +#if SIZEOF_INT > 2 + size = 0x10000L; /* use buffer >= 64K */ +#else + size = 0x7ff0L - linerest; /* limit buffer to 32K */ +#endif + + for ( ; size >= 10; size >>= 1) + { + if ((new_buffer = lalloc((long_u)(size + linerest + 1), FALSE)) != NULL) + break; + } + if (new_buffer == NULL) + { + do_outofmem_msg(); + error = TRUE; + break; + } + if (linerest) /* copy characters from the previous buffer */ + vim_memmove(new_buffer, ptr - linerest, (size_t)linerest); + vim_free(buffer); + buffer = new_buffer; + ptr = buffer + linerest; + line_start = buffer; + + if ((size = read(fd, (char *)ptr, (size_t)size)) <= 0) + { + if (size < 0) /* read error */ + error = TRUE; + break; + } + filesize += size; /* count the number of characters */ + + /* + * when reading the first part of a file: guess EOL type + */ + if (firstpart && p_ta) + { + for (p = ptr; p < ptr + size; ++p) + if (*p == NL) + { + if (p > ptr && p[-1] == CR) /* found CR-NL */ + textmode = TRUE; + else /* found a single NL */ + textmode = FALSE; + /* if editing a new file: may set p_tx */ + if (newfile) + curbuf->b_p_tx = textmode; + break; + } + } + } + + firstpart = FALSE; + + /* + * This loop is executed once for every character read. + * Keep it fast! + */ + --ptr; + while (++ptr, --size >= 0) + { + if ((c = *ptr) != NUL && c != NL) /* catch most common case */ + continue; + if (c == NUL) + *ptr = NL; /* NULs are replaced by newlines! */ + else + { + if (skip_count == 0) + { + *ptr = NUL; /* end of line */ + len = ptr - line_start + 1; + if (textmode) + { + if (ptr[-1] == CR) /* remove CR */ + { + ptr[-1] = NUL; + --len; + } + /* + * Reading in textmode, but no CR-LF found! + * When 'textauto' set, delete all the lines read so + * far and start all over again. + * Otherwise give an error message later. + */ + else if (!tx_error) + { + if (p_ta && lseek(fd, 0L, SEEK_SET) == 0) + { + while (lnum > from) + ml_delete(lnum--, FALSE); + textmode = FALSE; + if (newfile) + curbuf->b_p_tx = FALSE; + linerest = 0; + filesize = 0; + skip_count = lines_to_skip; + read_count = lines_to_read; + goto retry; + } + else + tx_error = TRUE; + } + } + if (ml_append(lnum, line_start, len, newfile) == FAIL) + { + error = TRUE; + break; + } + ++lnum; + if (--read_count == 0) + { + error = TRUE; /* break loop */ + line_start = ptr; /* nothing left to write */ + break; + } + } + else + --skip_count; + line_start = ptr + 1; + } + } + linerest = ptr - line_start; + mch_breakcheck(); + } + + /* not an error, max. number of lines reached */ + if (error && read_count == 0) + error = FALSE; + + /* + * If we get EOF in the middle of a line, note the fact and + * complete the line ourselves. + * In textmode ignore a trailing CTRL-Z, unless 'binary' set. + */ + if (!error && !got_int && linerest != 0 && + !(!curbuf->b_p_bin && textmode && + *line_start == Ctrl('Z') && ptr == line_start + 1)) + { + if (newfile) /* remember for when writing */ + curbuf->b_p_eol = FALSE; + *ptr = NUL; + if (ml_append(lnum, line_start, + (colnr_t)(ptr - line_start + 1), newfile) == FAIL) + error = TRUE; + else + read_no_eol_lnum = ++lnum; + } + if (lnum != from && !newfile) /* added at least one line */ + CHANGED; + + close(fd); /* errors are ignored */ + vim_free(buffer); + + --no_wait_return; /* may wait for return now */ + + /* in recovery mode everything but autocommands are skipped */ + if (!recoverymode) + { + + /* need to delete the last line, which comes from the empty buffer */ + if (newfile && !(curbuf->b_ml.ml_flags & ML_EMPTY)) + { + ml_delete(curbuf->b_ml.ml_line_count, FALSE); + --linecnt; + } + linecnt = curbuf->b_ml.ml_line_count - linecnt; + if (filesize == 0) + linecnt = 0; + if (!newfile) + mark_adjust(from + 1, MAXLNUM, (long)linecnt, 0L); + + if (got_int) + { + filemess(curbuf, fname, e_interr); + msg_scroll = msg_save; +#ifdef VIMINFO + check_marks_read(); +#endif /* VIMINFO */ + return OK; /* an interrupt isn't really an error */ + } + + if (!filtering) + { + msg_add_fname(curbuf, fname); /* fname in IObuff with quotes */ + c = FALSE; + +#ifdef UNIX +# ifdef S_ISFIFO + if (S_ISFIFO(perm)) /* fifo or socket */ + { + STRCAT(IObuff, "[fifo/socket]"); + c = TRUE; + } +# else +# ifdef S_IFIFO + if ((perm & S_IFMT) == S_IFIFO) /* fifo */ + { + STRCAT(IObuff, "[fifo]"); + c = TRUE; + } +# endif +# ifdef S_IFSOCK + if ((perm & S_IFMT) == S_IFSOCK) /* or socket */ + { + STRCAT(IObuff, "[socket]"); + c = TRUE; + } +# endif +# endif +#endif + if (curbuf->b_p_ro) + { + STRCAT(IObuff, shortmess(SHM_RO) ? "[RO]" : "[readonly]"); + c = TRUE; + } + if (read_no_eol_lnum) + { + STRCAT(IObuff, shortmess(SHM_LAST) ? "[noeol]" : + "[Incomplete last line]"); + c = TRUE; + } + if (tx_error) + { + STRCAT(IObuff, "[CR missing]"); + c = TRUE; + } + if (split) + { + STRCAT(IObuff, "[long lines split]"); + c = TRUE; + } + if (error) + { + STRCAT(IObuff, "[READ ERRORS]"); + c = TRUE; + } + if (msg_add_textmode(textmode)) + c = TRUE; + msg_add_lines(c, (long)linecnt, filesize); + msg_trunc(IObuff); + } + + if (error && newfile) /* with errors we should not write the file */ + curbuf->b_p_ro = TRUE; + + u_clearline(); /* cannot use "U" command after adding lines */ + + if (from < curbuf->b_ml.ml_line_count) + { + curwin->w_cursor.lnum = from + 1; /* cursor at first new line */ + beginline(TRUE); /* on first non-blank */ + } + + /* + * Set '[ and '] marks to the newly read lines. + */ + curbuf->b_op_start.lnum = from + 1; + curbuf->b_op_start.col = 0; + curbuf->b_op_end.lnum = from + linecnt; + curbuf->b_op_end.col = 0; + } + msg_scroll = msg_save; + +#ifdef AUTOCMD + { + int m = msg_scroll; + int n = msg_scrolled; + + /* + * Trick: We remember if the last line of the read didn't have + * an eol for when writing it again. This is required for + * ":autocmd FileReadPost *.gz set bin|%!gunzip" to work. + */ + write_no_eol_lnum = read_no_eol_lnum; + + /* + * The output from the autocommands should not overwrite anything and + * should not be overwritten: Set msg_scroll, restore its value if no + * output was done. + */ + msg_scroll = TRUE; + if (filtering) + apply_autocmds(EVENT_FILTERREADPOST, NULL, fname); + else if (newfile) + apply_autocmds(EVENT_BUFREADPOST, NULL, fname); + else + apply_autocmds(EVENT_FILEREADPOST, fname, fname); + if (msg_scrolled == n) + msg_scroll = m; + + write_no_eol_lnum = 0; + } +#endif + +#ifdef VIMINFO + check_marks_read(); +#endif /* VIMINFO */ + + if (recoverymode && error) + return FAIL; + return OK; +} + +#ifdef VIMINFO + static void +check_marks_read() +{ + if (!curbuf->b_marks_read && get_viminfo_parameter('\'') > 0) + { + read_viminfo(NULL, FALSE, TRUE, FALSE); + curbuf->b_marks_read = TRUE; + } +} +#endif /* VIMINFO */ + +/* + * writeit - write to file 'fname' lines 'start' through 'end' + * + * We do our own buffering here because fwrite() is so slow. + * + * If forceit is true, we don't care for errors when attempting backups (jw). + * In case of an error everything possible is done to restore the original file. + * But when forceit is TRUE, we risk loosing it. + * When reset_changed is TRUE and start == 1 and end == + * curbuf->b_ml.ml_line_count, reset curbuf->b_changed. + * + * This function must NOT use NameBuff (because it's called by autowrite()). + * + * return FAIL for failure, OK otherwise + */ + int +buf_write(buf, fname, sfname, start, end, append, forceit, + reset_changed, filtering) + BUF *buf; + char_u *fname; + char_u *sfname; + linenr_t start, end; + int append; + int forceit; + int reset_changed; + int filtering; +{ + int fd; + char_u *backup = NULL; + char_u *ffname; +#ifdef AUTOCMD + BUF *save_buf; +#endif + register char_u *s; + register char_u *ptr; + register char_u c; + register int len; + register linenr_t lnum; + long nchars; + char_u *errmsg = NULL; + char_u *buffer; + char_u smallbuf[SBUFSIZE]; + char_u *backup_ext; + int bufsize; + long perm = -1; /* file permissions */ + int retval = OK; + int newfile = FALSE; /* TRUE if file doesn't exist yet */ + int msg_save = msg_scroll; + int overwriting; /* TRUE if writing over original */ +#if defined(UNIX) || defined(__EMX__XX) /*XXX fix me sometime? */ + struct stat st_old; + int made_writable = FALSE; /* 'w' bit has been set */ +#endif +#ifdef AMIGA + BPTR flock; +#endif + /* writing everything */ + int whole = (start == 1 && end == buf->b_ml.ml_line_count); +#ifdef AUTOCMD + linenr_t old_line_count = buf->b_ml.ml_line_count; +#endif + + if (fname == NULL || *fname == NUL) /* safety check */ + return FAIL; + + /* + * If there is no file name yet, use the one for the written file. + * b_notedited is set to reflect this (in case the write fails). + * Don't do this when the write is for a filter command. + * Only do this when 'cpoptions' contains the 'f' flag. + */ + if (reset_changed && whole && buf == curbuf && + curbuf->b_filename == NULL && !filtering && + vim_strchr(p_cpo, CPO_FNAMEW) != NULL) + { + if (setfname(fname, sfname, FALSE) == OK) + curbuf->b_notedited = TRUE; + } + + if (sfname == NULL) + sfname = fname; + /* + * For Unix: Use the short filename whenever possible. + * Avoids problems with networks and when directory names are changed. + * Don't do this for MS-DOS, a "cd" in a sub-shell may have moved us to + * another directory, which we don't detect + */ + ffname = fname; /* remember full fname */ +#ifdef UNIX + if (!did_cd) + fname = sfname; +#endif + + /* make sure we have a valid backup extension to use */ + if (*p_bex == NUL) + backup_ext = (char_u *)".bak"; + else + backup_ext = p_bex; + + if (buf->b_filename != NULL && fnamecmp(ffname, buf->b_filename) == 0) + overwriting = TRUE; + else + overwriting = FALSE; + + /* + * Disallow writing from .exrc and .vimrc in current directory for + * security reasons. + */ + if (secure) + { + secure = 2; + emsg(e_curdir); + return FAIL; + } + + if (exiting) + settmode(0); /* when exiting allow typahead now */ + + ++no_wait_return; /* don't wait for return yet */ + + /* + * Set '[ and '] marks to the lines to be written. + */ + buf->b_op_start.lnum = start; + buf->b_op_start.col = 0; + buf->b_op_end.lnum = end; + buf->b_op_end.col = 0; + +#ifdef AUTOCMD + /* + * Apply PRE aucocommands. + * Careful: The autocommands may call buf_write() recursively! + */ + save_buf = curbuf; + curbuf = buf; + curwin->w_buffer = buf; + if (append) + apply_autocmds(EVENT_FILEAPPENDPRE, fname, fname); + else if (filtering) + apply_autocmds(EVENT_FILTERWRITEPRE, NULL, fname); + else if (reset_changed && whole) + apply_autocmds(EVENT_BUFWRITEPRE, fname, fname); + else + apply_autocmds(EVENT_FILEWRITEPRE, fname, fname); + curbuf = save_buf; + curwin->w_buffer = save_buf; + + /* + * The autocommands may have changed the number of lines in the file. + * When writing the whole file, adjust the end. + * When writing part of the file, assume that the autocommands only + * changed the number of lines that are to be written (tricky!). + */ + if (buf->b_ml.ml_line_count != old_line_count) + { + if (whole) /* writing all */ + end = buf->b_ml.ml_line_count; + else if (buf->b_ml.ml_line_count > old_line_count) /* more lines */ + end += buf->b_ml.ml_line_count - old_line_count; + else /* less lines */ + { + end -= old_line_count - buf->b_ml.ml_line_count; + if (end < start) + { + --no_wait_return; + EMSG("Autocommand changed number of lines in unexpected way"); + return FAIL; + } + } + } +#endif + + if (shortmess(SHM_OVER)) + msg_scroll = FALSE; /* overwrite previous file message */ + else + msg_scroll = TRUE; /* don't overwrite previous file message */ + if (!filtering) + filemess(buf, +#ifndef UNIX + did_cd ? fname : sfname, +#else + fname, +#endif + (char_u *)""); /* show that we are busy */ + msg_scroll = FALSE; /* always overwrite the file message now */ + + buffer = alloc(BUFSIZE); + if (buffer == NULL) /* can't allocate big buffer, use small + * one (to be able to write when out of + * memory) */ + { + buffer = smallbuf; + bufsize = SBUFSIZE; + } + else + bufsize = BUFSIZE; + +#if defined(UNIX) && !defined(ARCHIE) + /* get information about original file (if there is one) */ + st_old.st_dev = st_old.st_ino = 0; + if (stat((char *)fname, &st_old)) + newfile = TRUE; + else + { +#ifdef _POSIX_SOURCE + if (!S_ISREG(st_old.st_mode)) /* not a file */ +#else + if ((st_old.st_mode & S_IFMT) != S_IFREG) /* not a file */ +#endif + { +#ifdef _POSIX_SOURCE + if (S_ISDIR(st_old.st_mode)) +#else + if ((st_old.st_mode & S_IFMT) == S_IFDIR) +#endif + errmsg = (char_u *)"is a directory"; + else + errmsg = (char_u *)"is not a file"; + goto fail; + } + if (buf->b_mtime_read != 0 && + buf->b_mtime_read != st_old.st_mtime && overwriting) + { + msg_scroll = TRUE; /* don't overwrite messages here */ + (void)set_highlight('e'); /* set highlight for error messages */ + msg_highlight = TRUE; + /* don't use emsg() here, don't want to flush the buffers */ + MSG("WARNING: The file has been changed since reading it!!!"); + if (ask_yesno((char_u *)"Do you really want to write to it", + TRUE) == 'n') + { + retval = FAIL; + goto fail; + } + msg_scroll = FALSE; /* always overwrite the file message now */ + } + perm = st_old.st_mode; + } +/* + * If we are not appending, the file exists, and the 'writebackup', 'backup' + * or 'patchmode' option is set, try to make a backup copy of the file. + */ + if (!append && perm >= 0 && (p_wb || p_bk || *p_pm != NUL) && + (fd = open((char *)fname, O_RDONLY | O_EXTRA)) >= 0) + { + int bfd, buflen; + char_u copybuf[BUFSIZE + 1], *wp; + int some_error = FALSE; + struct stat st_new; + char_u *dirp; +#ifndef SHORT_FNAME + int did_set_shortname; +#endif + + /* + * Try to make the backup in each directory in the 'bdir' option. + * + * Unix semantics has it, that we may have a writable file, + * that cannot be recreated with a simple open(..., O_CREAT, ) e.g: + * - the directory is not writable, + * - the file may be a symbolic link, + * - the file may belong to another user/group, etc. + * + * For these reasons, the existing writable file must be truncated + * and reused. Creation of a backup COPY will be attempted. + */ + dirp = p_bdir; + while (*dirp) + { + st_new.st_dev = st_new.st_ino = 0; + st_new.st_gid = 0; + + /* + * Isolate one directory name. + */ + len = copy_option_part(&dirp, copybuf, BUFSIZE, ","); + + if (*copybuf == '.') /* use same dir as file */ + STRCPY(copybuf, fname); + else /* use dir from 'bdir' option */ + { + if (!ispathsep(copybuf[len - 1])) + copybuf[len++] = PATHSEP; + STRCPY(copybuf + len, gettail(fname)); + } + +#ifndef SHORT_FNAME + did_set_shortname = FALSE; +#endif + + /* + * May try twice if 'shortname' not set. + */ + for (;;) + { + /* + * Make backup file name. + */ + backup = buf_modname(buf, copybuf, backup_ext); + if (backup == NULL) + { + some_error = TRUE; /* out of memory */ + goto nobackup; + } + + /* + * Check if backup file already exists. + */ + if (!stat((char *)backup, &st_new)) + { + /* + * Check if backup file is same as original file. + * May happen when modname gave the same file back. + * E.g. silly link, or filename-length reached. + * If we don't check here, we either ruin the file when + * copying or erase it after writing. jw. + */ + if (st_new.st_dev == st_old.st_dev && + st_new.st_ino == st_old.st_ino) + { + vim_free(backup); + backup = NULL; /* there is no backup file to delete */ +#ifndef SHORT_FNAME + /* + * may try again with 'shortname' set + */ + if (!(buf->b_shortname || buf->b_p_sn)) + { + buf->b_shortname = TRUE; + did_set_shortname = TRUE; + continue; + } + /* setting shortname didn't help */ + if (did_set_shortname) + buf->b_shortname = FALSE; +#endif + break; + } + + /* + * If we are not going to keep the backup file, don't + * delete an existing one, try to use another name. + * Change one character, just before the extension. + */ + if (!p_bk) + { + wp = backup + STRLEN(backup) - 1 - STRLEN(backup_ext); + if (wp < backup) /* empty file name ??? */ + wp = backup; + *wp = 'z'; + while (*wp > 'a' && !stat((char *)backup, &st_new)) + --*wp; + /* They all exist??? Must be something wrong. */ + if (*wp == 'a') + { + vim_free(backup); + backup = NULL; + } + } + } + break; + } + + /* + * Try to create the backup file + */ + if (backup != NULL) + { + /* remove old backup, if present */ + vim_remove(backup); + bfd = open((char *)backup, O_WRONLY | O_CREAT | O_EXTRA, 0666); + if (bfd < 0) + { + vim_free(backup); + backup = NULL; + } + else + { + /* set file protection same as original file, but strip + * s-bit */ + (void)setperm(backup, perm & 0777); + + /* + * Try to set the group of the backup same as the original + * file. If this fails, set the protection bits for the + * group same as the protection bits for others. + */ + if (st_new.st_gid != st_old.st_gid && +#ifdef HAVE_FCHOWN /* sequent-ptx lacks fchown() */ + fchown(bfd, -1, st_old.st_gid) != 0) +#else + chown(backup, -1, st_old.st_gid) != 0) +#endif + setperm(backup, (perm & 0707) | ((perm & 07) << 3)); + + /* copy the file. */ + while ((buflen = read(fd, (char *)copybuf, BUFSIZE)) > 0) + { + if (write_buf(bfd, copybuf, buflen) == FAIL) + { + errmsg = (char_u *)"Can't write to backup file (use ! to override)"; + break; + } + } + if (close(bfd) < 0 && errmsg == NULL) + errmsg = (char_u *)"Close error for backup file (use ! to override)"; + if (buflen < 0) + errmsg = (char_u *)"Can't read file for backup (use ! to override)"; + break; + } + } + } +nobackup: + close(fd); /* ignore errors for closing read file */ + + if (backup == NULL && errmsg == NULL) + errmsg = (char_u *)"Cannot create backup file (use ! to override)"; + /* ignore errors when forceit is TRUE */ + if ((some_error || errmsg) && !forceit) + { + retval = FAIL; + goto fail; + } + errmsg = NULL; + } + /* When using ":w!" and the file was read-only: make it writable */ + if (forceit && (st_old.st_uid == getuid()) && perm >= 0 && !(perm & 0200)) + { + perm |= 0200; + (void)setperm(fname, perm); + made_writable = TRUE; + } + +#else /* end of UNIX, start of the rest */ + +/* + * If we are not appending, the file exists, and the 'writebackup' or + * 'backup' option is set, make a backup. + * Do not make any backup, if "writebackup" and "backup" are + * both switched off. This helps when editing large files on + * almost-full disks. (jw) + */ + perm = getperm(fname); + if (perm < 0) + newfile = TRUE; + else if (mch_isdir(fname)) + { + errmsg = (char_u *)"is a directory"; + goto fail; + } + if (!append && perm >= 0 && (p_wb || p_bk || *p_pm != NUL)) + { + char_u *dirp; + char_u *p; + + /* + * Form the backup file name - change path/fo.o.h to path/fo.o.h.bak + * Try all directories in 'backupdir', first one that works is used. + */ + dirp = p_bdir; + while (*dirp) + { + /* + * Isolate one directory name. + */ + len = copy_option_part(&dirp, IObuff, IOSIZE, ","); + +#ifdef VMS + if (!memcmp(IObuff, "sys$disk:", 9)) +#else + if (*IObuff == '.') /* use same dir as file */ +#endif + backup = buf_modname(buf, fname, backup_ext); + else /* use dir from 'bdir' option */ + { + if (!ispathsep(IObuff[len - 1])) + IObuff[len++] = PATHSEP; + STRCPY(IObuff + len, gettail(fname)); + backup = buf_modname(buf, IObuff, backup_ext); + } + if (backup != NULL) + { + /* + * If we are not going to keep the backup file, don't + * delete an existing one, try to use another name. + * Change one character, just before the extension. + */ + if (!p_bk && getperm(backup) >= 0) + { + p = backup + STRLEN(backup) - 1 - STRLEN(backup_ext); + if (p < backup) /* empty file name ??? */ + p = backup; + *p = 'z'; + while (*p > 'a' && getperm(backup) >= 0) + --*p; + /* They all exist??? Must be something wrong! */ + if (*p == 'a') + { + vim_free(backup); + backup = NULL; + } + } + } + if (backup != NULL) + { + + /* + * Delete any existing backup and move the current version to + * the backup. For safety, we don't remove the backup until + * the write has finished successfully. And if the 'backup' + * option is set, leave it around. + */ +#ifdef AMIGA + /* + * With MSDOS-compatible filesystems (crossdos, messydos) it is + * possible that the name of the backup file is the same as the + * original file. To avoid the chance of accidently deleting the + * original file (horror!) we lock it during the remove. + * This should not happen with ":w", because startscript() + * should detect this problem and set buf->b_shortname, + * causing modname to return a correct ".bak" filename. This + * problem does exist with ":w filename", but then the + * original file will be somewhere else so the backup isn't + * really important. If autoscripting is off the rename may + * fail. + */ + flock = Lock((UBYTE *)fname, (long)ACCESS_READ); +#endif + vim_remove(backup); +#ifdef AMIGA + if (flock) + UnLock(flock); +#endif + /* + * If the renaming of the original file to the backup file + * works, quit here. + */ + if (vim_rename(fname, backup) == 0) + break; + + vim_free(backup); /* don't do the rename below */ + backup = NULL; + } + } + if (backup == NULL && !forceit) + { + errmsg = (char_u *)"Can't make backup file (use ! to override)"; + goto fail; + } + } +#endif /* UNIX */ + + /* When using ":w!" and writing to the current file, readonly makes no + * sense, reset it */ + if (forceit && overwriting) + buf->b_p_ro = FALSE; + + /* + * If the original file is being overwritten, there is a small chance that + * we crash in the middle of writing. Therefore the file is preserved now. + * This makes all block numbers positive so that recovery does not need + * the original file. + * Don't do this if there is a backup file and we are exiting. + */ + if (reset_changed && !newfile && !otherfile(ffname) && + !(exiting && backup != NULL)) + ml_preserve(buf, FALSE); + + /* + * We may try to open the file twice: If we can't write to the + * file and forceit is TRUE we delete the existing file and try to create + * a new one. If this still fails we may have lost the original file! + * (this may happen when the user reached his quotum for number of files). + * Appending will fail if the file does not exist and forceit is FALSE. + */ + while ((fd = open((char *)fname, O_WRONLY | O_EXTRA | (append ? + (forceit ? (O_APPEND | O_CREAT) : O_APPEND) : + (O_CREAT | O_TRUNC)), 0666)) < 0) + { + /* + * A forced write will try to create a new file if the old one is + * still readonly. This may also happen when the directory is + * read-only. In that case the vim_remove() will fail. + */ + if (!errmsg) + { + errmsg = (char_u *)"Can't open file for writing"; + if (forceit) + { +#ifdef UNIX + /* we write to the file, thus it should be marked + writable after all */ + perm |= 0200; + made_writable = TRUE; + if (st_old.st_uid != getuid() || st_old.st_gid != getgid()) + perm &= 0777; +#endif /* UNIX */ + if (!append) /* don't remove when appending */ + vim_remove(fname); + continue; + } + } +/* + * If we failed to open the file, we don't need a backup. Throw it away. + * If we moved or removed the original file try to put the backup in its place. + */ + if (backup != NULL) + { +#ifdef UNIX + struct stat st; + + /* + * There is a small chance that we removed the original, try + * to move the copy in its place. + * This may not work if the vim_rename() fails. + * In that case we leave the copy around. + */ + /* file does not exist */ + if (stat((char *)fname, &st) < 0) + /* put the copy in its place */ + vim_rename(backup, fname); + /* original file does exist */ + if (stat((char *)fname, &st) >= 0) + vim_remove(backup); /* throw away the copy */ +#else + /* try to put the original file back */ + vim_rename(backup, fname); +#endif + } + goto fail; + } + errmsg = NULL; + + if (end > buf->b_ml.ml_line_count) + end = buf->b_ml.ml_line_count; + len = 0; + s = buffer; + nchars = 0; + if (buf->b_ml.ml_flags & ML_EMPTY) + start = end + 1; + for (lnum = start; lnum <= end; ++lnum) + { + /* + * The next while loop is done once for each character written. + * Keep it fast! + */ + ptr = ml_get_buf(buf, lnum, FALSE) - 1; + while ((c = *++ptr) != NUL) + { + if (c == NL) + *s = NUL; /* replace newlines with NULs */ + else + *s = c; + ++s; + if (++len != bufsize) + continue; + if (write_buf(fd, buffer, bufsize) == FAIL) + { + end = 0; /* write error: break loop */ + break; + } + nchars += bufsize; + s = buffer; + len = 0; + } + /* write failed or last line has no EOL: stop here */ + if (end == 0 || (lnum == end && buf->b_p_bin && + (lnum == write_no_eol_lnum || + (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol)))) + break; + if (buf->b_p_tx) /* write CR-NL */ + { + *s = CR; + ++s; + if (++len == bufsize) + { + if (write_buf(fd, buffer, bufsize) == FAIL) + { + end = 0; /* write error: break loop */ + break; + } + nchars += bufsize; + s = buffer; + len = 0; + } + } + *s = NL; + ++s; + if (++len == bufsize && end) + { + if (write_buf(fd, buffer, bufsize) == FAIL) + { + end = 0; /* write error: break loop */ + break; + } + nchars += bufsize; + s = buffer; + len = 0; + } + } + if (len && end) + { + if (write_buf(fd, buffer, len) == FAIL) + end = 0; /* write error */ + nchars += len; + } + + if (close(fd) != 0) + { + errmsg = (char_u *)"Close failed"; + goto fail; + } +#ifdef UNIX + if (made_writable) + perm &= ~0200; /* reset 'w' bit for security reasons */ +#endif + if (perm >= 0) + (void)setperm(fname, perm); /* set permissions of new file same as old file */ + + if (end == 0) + { + errmsg = (char_u *)"write error (file system full?)"; + /* + * If we have a backup file, try to put it in place of the new file, + * because it is probably corrupt. This avoids loosing the original + * file when trying to make a backup when writing the file a second + * time. + * For unix this means copying the backup over the new file. + * For others this means renaming the backup file. + * If this is OK, don't give the extra warning message. + */ + if (backup != NULL) + { +#ifdef UNIX + char_u copybuf[BUFSIZE + 1]; + int bfd, buflen; + + if ((bfd = open((char *)backup, O_RDONLY | O_EXTRA)) >= 0) + { + if ((fd = open((char *)fname, + O_WRONLY | O_CREAT | O_TRUNC | O_EXTRA, 0666)) >= 0) + { + /* copy the file. */ + while ((buflen = read(bfd, (char *)copybuf, BUFSIZE)) > 0) + if (write_buf(fd, copybuf, buflen) == FAIL) + break; + if (close(fd) >= 0 && buflen == 0) /* success */ + end = 1; + } + close(bfd); /* ignore errors for closing read file */ + } +#else + if (vim_rename(backup, fname) == 0) + end = 1; +#endif + } + goto fail; + } + + lnum -= start; /* compute number of written lines */ + --no_wait_return; /* may wait for return now */ + +#ifndef UNIX + /* use shortname now, for the messages */ + if (!did_cd) + fname = sfname; +#endif + if (!filtering) + { + msg_add_fname(buf, fname); /* put fname in IObuff with quotes */ + c = FALSE; + if (newfile) + { + STRCAT(IObuff, shortmess(SHM_NEW) ? "[New]" : "[New File]"); + c = TRUE; + } + if (msg_add_textmode(buf->b_p_tx)) /* may add [textmode] */ + c = TRUE; + msg_add_lines(c, (long)lnum, nchars); /* add line/char count */ + if (!shortmess(SHM_WRITE)) + STRCAT(IObuff, shortmess(SHM_WRI) ? " [w]" : " written"); + + msg_trunc(IObuff); + } + + if (reset_changed && whole) /* when written everything */ + { + UNCHANGED(buf); + u_unchanged(buf); + } + + /* + * If written to the current file, update the timestamp of the swap file + * and reset the 'notedited' flag. Also sets buf->b_mtime. + */ + if (!exiting && overwriting) + { + ml_timestamp(buf); + buf->b_notedited = FALSE; + } + + /* + * If we kept a backup until now, and we are in patch mode, then we make + * the backup file our 'original' file. + */ + if (*p_pm) + { + char *org = (char *)buf_modname(buf, fname, p_pm); + + if (backup != NULL) + { + struct stat st; + + /* + * If the original file does not exist yet + * the current backup file becomes the original file + */ + if (org == NULL) + EMSG("patchmode: can't save original file"); + else if (stat(org, &st) < 0) + { + vim_rename(backup, (char_u *)org); + vim_free(backup); /* don't delete the file */ + backup = NULL; + } + } + /* + * If there is no backup file, remember that a (new) file was + * created. + */ + else + { + int empty_fd; + + if (org == NULL || (empty_fd = + open(org, O_CREAT | O_EXTRA, 0666)) < 0) + EMSG("patchmode: can't touch empty original file"); + else + close(empty_fd); + } + if (org != NULL) + { + setperm((char_u *)org, getperm(fname) & 0777); + vim_free(org); + } + } + + /* + * Remove the backup unless 'backup' option is set + */ + if (!p_bk && backup != NULL && vim_remove(backup) != 0) + EMSG("Can't delete backup file"); + + goto nofail; + +fail: + --no_wait_return; /* may wait for return now */ +nofail: + + vim_free(backup); + if (buffer != smallbuf) + vim_free(buffer); + + if (errmsg != NULL) + { + /* can't use emsg() here, do something alike */ + if (p_eb) + beep_flush(); /* also includes flush_buffers() */ + else + flush_buffers(FALSE); /* flush internal buffers */ + (void)set_highlight('e'); /* set highlight mode for error messages */ + start_highlight(); + filemess(buf, +#ifndef UNIX + did_cd ? fname : sfname, +#else + fname, +#endif + errmsg); + retval = FAIL; + if (end == 0) + { + MSG_OUTSTR("\nWARNING: Original file may be lost or damaged\n"); + MSG_OUTSTR("don't quit the editor until the file is sucessfully written!"); + } + } + msg_scroll = msg_save; + +#ifdef AUTOCMD + /* + * Apply POST aucocommands. + * Careful: The autocommands may call buf_write() recursively! + */ + save_buf = curbuf; + curbuf = buf; + curwin->w_buffer = buf; + if (append) + apply_autocmds(EVENT_FILEAPPENDPOST, fname, fname); + else if (filtering) + apply_autocmds(EVENT_FILTERWRITEPOST, NULL, fname); + else if (reset_changed && whole) + apply_autocmds(EVENT_BUFWRITEPOST, fname, fname); + else + apply_autocmds(EVENT_FILEWRITEPOST, fname, fname); + curbuf = save_buf; + curwin->w_buffer = save_buf; +#endif + + return retval; +} + +/* + * Put file name into IObuff with quotes. + */ + static void +msg_add_fname(buf, fname) + BUF *buf; + char_u *fname; +{ + /* careful: home_replace calls vim_getenv(), which also uses IObuff! */ + home_replace(buf, fname, IObuff + 1, IOSIZE - 1); + IObuff[0] = '"'; + STRCAT(IObuff, "\" "); +} + +/* + * Append message for text mode to IObuff. + * Return TRUE if something appended. + */ + static int +msg_add_textmode(textmode) + int textmode; +{ +#ifdef USE_CRNL + if (!textmode) + { + STRCAT(IObuff, shortmess(SHM_TEXT) ? "[notx]" : "[notextmode]"); + return TRUE; + } +#else + if (textmode) + { + STRCAT(IObuff, shortmess(SHM_TEXT) ? "[tx]" : "[textmode]"); + return TRUE; + } +#endif + return FALSE; +} + +/* + * Append line and character count to IObuff. + */ + static void +msg_add_lines(insert_space, lnum, nchars) + int insert_space; + long lnum; + long nchars; +{ + char_u *p; + + p = IObuff + STRLEN(IObuff); + + if (insert_space) + *p++ = ' '; + if (shortmess(SHM_LINES)) + sprintf((char *)p, "%ldL, %ldC", lnum, nchars); + else + sprintf((char *)p, "%ld line%s, %ld character%s", + lnum, plural(lnum), + nchars, plural(nchars)); +} + +/* + * write_buf: call write() to write a buffer + * + * return FAIL for failure, OK otherwise + */ + static int +write_buf(fd, buf, len) + int fd; + char_u *buf; + int len; +{ + int wlen; + + while (len) + { + wlen = write(fd, (char *)buf, (size_t)len); + if (wlen <= 0) /* error! */ + return FAIL; + len -= wlen; + buf += wlen; + } + return OK; +} + +/* + * add extention to filename - change path/fo.o.h to path/fo.o.h.ext or + * fo_o_h.ext for MSDOS or when shortname option set. + * + * Assumed that fname is a valid name found in the filesystem we assure that + * the return value is a different name and ends in 'ext'. + * "ext" MUST be at most 4 characters long if it starts with a dot, 3 + * characters otherwise. + * Space for the returned name is allocated, must be freed later. + */ + + char_u * +modname(fname, ext) + char_u *fname, *ext; +{ + return buf_modname(curbuf, fname, ext); +} + + char_u * +buf_modname(buf, fname, ext) + BUF *buf; + char_u *fname, *ext; +{ + char_u *retval; + register char_u *s; + register char_u *e; + register char_u *ptr; + register int fnamelen, extlen; + + extlen = STRLEN(ext); + + /* + * if there is no filename we must get the name of the current directory + * (we need the full path in case :cd is used) + */ + if (fname == NULL || *fname == NUL) + { + retval = alloc((unsigned)(MAXPATHL + extlen + 3)); + if (retval == NULL) + return NULL; + if (mch_dirname(retval, MAXPATHL) == FAIL || + (fnamelen = STRLEN(retval)) == 0) + { + vim_free(retval); + return NULL; + } + if (!ispathsep(retval[fnamelen - 1])) + { + retval[fnamelen++] = PATHSEP; + retval[fnamelen] = NUL; + } + } + else + { + fnamelen = STRLEN(fname); + retval = alloc((unsigned)(fnamelen + extlen + 2)); + if (retval == NULL) + return NULL; + STRCPY(retval, fname); + } + + /* + * search backwards until we hit a '/', '\' or ':' replacing all '.' + * by '_' for MSDOS or when shortname option set and ext starts with a dot. + * Then truncate what is after the '/', '\' or ':' to 8 characters for + * MSDOS and 26 characters for AMIGA, a lot more for UNIX. + */ + for (ptr = retval + fnamelen; ptr >= retval; ptr--) + { + if (*ext == '.' +#ifdef USE_LONG_FNAME + && (!USE_LONG_FNAME || buf->b_p_sn || buf->b_shortname) +#else +# ifndef SHORT_FNAME + && (buf->b_p_sn || buf->b_shortname) +# endif +#endif + ) + if (*ptr == '.') /* replace '.' by '_' */ + *ptr = '_'; + if (ispathsep(*ptr)) + break; + } + ptr++; + + /* the filename has at most BASENAMELEN characters. */ +#ifndef SHORT_FNAME + if (STRLEN(ptr) > (unsigned)BASENAMELEN) + ptr[BASENAMELEN] = '\0'; +#endif + + s = ptr + STRLEN(ptr); + + /* + * For 8.3 filenames we may have to reduce the length. + */ +#ifdef USE_LONG_FNAME + if (!USE_LONG_FNAME || buf->b_p_sn || buf->b_shortname) +#else +# ifndef SHORT_FNAME + if (buf->b_p_sn || buf->b_shortname) +# endif +#endif + { + /* + * If there is no file name, and the extension starts with '.', put a + * '_' before the dot, because just ".ext" is invalid. + */ + if (fname == NULL || *fname == NUL) + { + if (*ext == '.') + *s++ = '_'; + } + /* + * If the extension starts with '.', truncate the base name at 8 + * characters + */ + else if (*ext == '.') + { + if (s - ptr > (size_t)8) + { + s = ptr + 8; + *s = '\0'; + } + } + /* + * If the extension doesn't start with '.', and the file name + * doesn't have an extension yet, append a '.' + */ + else if ((e = vim_strchr(ptr, '.')) == NULL) + *s++ = '.'; + /* + * If If the extension doesn't start with '.', and there already is an + * extension, it may need to be tructated + */ + else if ((int)STRLEN(e) + extlen > 4) + s = e + 4 - extlen; + } +#ifdef OS2 + /* + * If there is no file name, and the extension starts with '.', put a + * '_' before the dot, because just ".ext" may be invalid if it's on a + * FAT partition, and on HPFS it doesn't matter. + */ + else if ((fname == NULL || *fname == NUL) && *ext == '.') + *s++ = '_'; +#endif + + /* + * Append the extention. + * ext can start with '.' and cannot exceed 3 more characters. + */ + STRCPY(s, ext); + + /* + * Check that, after appending the extension, the file name is really + * different. + */ + if (fname != NULL && STRCMP(fname, retval) == 0) + { + /* we search for a character that can be replaced by '_' */ + while (--s >= ptr) + { + if (*s != '_') + { + *s = '_'; + break; + } + } + if (s < ptr) /* fname was "________." how tricky! */ + *ptr = 'v'; + } + return retval; +} + +/* vim_fgets(); + * + * Like fgets(), but if the file line is too long, it is truncated and the + * rest of the line is thrown away. Returns TRUE for end-of-file. + * Note: do not pass IObuff as the buffer since this is used to read and + * discard the extra part of any long lines. + */ + int +vim_fgets(buf, size, fp) + char_u *buf; + int size; + FILE *fp; +{ + char *eof; + + buf[size - 2] = NUL; + eof = fgets((char *)buf, size, fp); + if (buf[size - 2] != NUL && buf[size - 2] != '\n') + { + buf[size - 1] = NUL; /* Truncate the line */ + + /* Now throw away the rest of the line: */ + do + { + IObuff[IOSIZE - 2] = NUL; + fgets((char *)IObuff, IOSIZE, fp); + } while (IObuff[IOSIZE - 2] != NUL && IObuff[IOSIZE - 2] != '\n'); + } + return (eof == NULL); +} + +/* + * rename() only works if both files are on the same file system, this + * function will (attempts to?) copy the file across if rename fails -- webb + * Return -1 for failure, 0 for success. + */ + int +vim_rename(from, to) + char_u *from; + char_u *to; +{ + int fd_in; + int fd_out; + int n; + char *errmsg = NULL; + + /* + * First delete the "to" file, this is required on some systems to make + * the rename() work, on other systems it makes sure that we don't have + * two files when the rename() fails. + */ + vim_remove(to); + + /* + * First try a normal rename, return if it works. + */ + if (rename((char *)from, (char *)to) == 0) + return 0; + + /* + * Rename() failed, try copying the file. + */ + fd_in = open((char *)from, O_RDONLY | O_EXTRA); + if (fd_in == -1) + return -1; + fd_out = open((char *)to, O_CREAT | O_TRUNC | O_WRONLY | O_EXTRA, 0666); + if (fd_out == -1) + { + close(fd_in); + return -1; + } + while ((n = read(fd_in, (char *)IObuff, (size_t)IOSIZE)) > 0) + if (write(fd_out, (char *)IObuff, (size_t)n) != n) + { + errmsg = "writing to"; + break; + } + close(fd_in); + if (close(fd_out) < 0) + errmsg = "closing"; + if (n < 0) + { + errmsg = "reading"; + to = from; + } + if (errmsg != NULL) + { + sprintf((char *)IObuff, "Error %s '%s'", errmsg, to); + emsg(IObuff); + return -1; + } + vim_remove(from); + return 0; +} + +/* + * Check if any not hidden buffer has been changed. + * Postpone the check if there are characters in the stuff buffer, a global + * command is being executed, a mapping is being executed or an autocommand is + * busy. + */ + void +check_timestamps() +{ + BUF *buf; + + if (!stuff_empty() || global_busy || !typebuf_typed() +#ifdef AUTOCMD + || autocmd_busy +#endif + ) + need_check_timestamps = TRUE; /* check later */ + else + { + ++no_wait_return; + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + buf_check_timestamp(buf); + --no_wait_return; + need_check_timestamps = FALSE; + } +} + +/* + * Check if buffer "buf" has been changed. + */ + void +buf_check_timestamp(buf) + BUF *buf; +{ + struct stat st; + char_u *path; + + if ( buf->b_filename != NULL && + buf->b_ml.ml_mfp != NULL && + !buf->b_notedited && + buf->b_mtime != 0 && + stat((char *)buf->b_filename, &st) >= 0 && + buf->b_mtime != st.st_mtime) + { + path = home_replace_save(buf, buf->b_xfilename); + if (path != NULL) + { + EMSG2("Warning: File \"%s\" has changed since editing started", + path); + buf->b_mtime = st.st_mtime; + vim_free(path); + } + } +} diff --git a/usr.bin/vim/getchar.c b/usr.bin/vim/getchar.c new file mode 100644 index 00000000000..15f2c1c3dc7 --- /dev/null +++ b/usr.bin/vim/getchar.c @@ -0,0 +1,2216 @@ +/* $OpenBSD: getchar.c,v 1.1.1.1 1996/09/07 21:40:26 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * 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 "option.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 old_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() + * + * typebuf[] contains all characters that are not consumed yet. + * typebuf[typeoff] is the first valid character in typebuf[]. + * typebuf[typeoff + typelen - 1] is the last valid char. + * typebuf[typeoff + typelen] must be NUL. + * The part in front may contain the result of mappings, abbreviations and + * @a commands. The length of this part is typemaplen. + * After it are characters that come from the terminal. + * no_abbr_cnt is the number of characters in typebuf that should not be + * considered for abbreviations. + * Some parts of typebuf may not be mapped. These parts are remembered in + * noremapbuf, which is the same length as typebuf and contains TRUE for the + * characters that are not to be remapped. noremapbuf[typeoff] is the first + * valid flag. + * (typebuf has been put in globals.h, because check_termcode() needs it). + */ +static char_u *noremapbuf = NULL; /* flags for typeahead characters */ +#define TYPELEN_INIT (3 * (MAXMAPLEN + 3)) +static char_u typebuf_init[TYPELEN_INIT]; /* initial typebuf */ +static char_u noremapbuf_init[TYPELEN_INIT]; /* initial noremapbuf */ + +static int typemaplen = 0; /* nr of mapped characters in typebuf */ +static int no_abbr_cnt = 0; /* nr of chars without abbrev. in typebuf */ +static int last_recorded_len = 0; /* number of last recorded chars */ + +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, int)); +static void copy_redo __ARGS((int)); +static void init_typebuf __ARGS((void)); +static void gotchars __ARGS((char_u *, int)); +static int vgetorpeek __ARGS((int)); +static int inchar __ARGS((char_u *, int, long)); +static void map_free __ARGS((struct mapblock *)); +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; + vim_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; + char_u *p2; + char_u *str; + 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) + { + p2 = p; + for (bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next) + for (str = bp->b_str; *str; ) + *p2++ = *str++; + *p2 = NUL; + } + 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; + size_t len; + + p = get_bufcont(&recordbuff, TRUE); + free_buff(&recordbuff); + /* + * Remove the characters that were added the last time, these must be the + * (possibly mapped) characters that stopped recording. + */ + len = STRLEN(p); + if ((int)len >= last_recorded_len) + p[len - last_recorded_len] = NUL; + 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 >= (int)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[4]; + + /* + * translate special key code into three byte sequence + */ + if (IS_SPECIAL(c) || c == K_SPECIAL || c == NUL) + { + temp[0] = K_SPECIAL; + temp[1] = K_SECOND(c); + temp[2] = K_THIRD(c); + temp[3] = NUL; + } + else + { + 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; + vim_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; +{ + init_typebuf(); + + 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(typebuf, MAXMAPLEN, 10L)) + ; + typeoff = MAXMAPLEN; + typelen = 0; + } + else /* remove mapped characters only */ + { + typeoff += typemaplen; + typelen -= typemaplen; + } + typemaplen = 0; + no_abbr_cnt = 0; +} + +/* + * The previous contents of the redo buffer is kept in old_redobuffer. + * This is used for the CTRL-O <.> command in insert mode. + */ + void +ResetRedobuff() +{ + if (!block_redo) + { + free_buff(&old_redobuff); + old_redobuff = redobuff; + redobuff.bh_first.b_next = NULL; + } +} + + 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 + * if old is TRUE, use old_redobuff instead of redobuff + */ + static int +read_redo(init, old_redo) + int init; + int old_redo; +{ + static struct bufblock *bp; + static char_u *p; + int c; + + if (init) + { + if (old_redo) + bp = old_redobuff.bh_first.b_next; + else + bp = redobuff.bh_first.b_next; + if (bp == NULL) + return FAIL; + p = bp->b_str; + return OK; + } + if ((c = *p) != NUL) + { + if (c == K_SPECIAL) + { + c = TO_SPECIAL(p[1], p[2]); + p += 2; + } + 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) + * if old_redo is TRUE, use old_redobuff instead of redobuff + */ + static void +copy_redo(old_redo) + int old_redo; +{ + register int c; + + while ((c = read_redo(FALSE, old_redo)) != NUL) + stuffcharReadbuff(c); +} + +/* + * Stuff the redo buffer into the stuffbuff. + * Insert the redo count into the command. + * If 'old_redo' is TRUE, the last but one command is repeated + * instead of the last command (inserting text). This is used for + * CTRL-O <.> in insert mode + * + * return FAIL for failure, OK otherwise + */ + int +start_redo(count, old_redo) + long count; + int old_redo; +{ + register int c; + + if (read_redo(TRUE, old_redo) == FAIL) /* init the pointers; return if nothing to redo */ + return FAIL; + + c = read_redo(FALSE, old_redo); + +/* copy the buffer name, if present */ + if (c == '"') + { + add_buff(&stuffbuff, (char_u *)"\""); + c = read_redo(FALSE, old_redo); + +/* if a numbered buffer is used, increment the number */ + if (c >= '1' && c < '9') + ++c; + add_char_buff(&stuffbuff, c); + c = read_redo(FALSE, old_redo); + } + + if (c == 'v') /* redo Visual */ + { + VIsual = curwin->w_cursor; + VIsual_active = TRUE; + redo_VIsual_busy = TRUE; + c = read_redo(FALSE, old_redo); + } + +/* try to enter the count (in place of a previous count) */ + if (count) + { + while (isdigit(c)) /* skip "old" count */ + c = read_redo(FALSE, old_redo); + add_num_buff(&stuffbuff, count); + } + +/* copy from the redo buffer into the stuff buffer */ + add_char_buff(&stuffbuff, c); + copy_redo(old_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, FALSE) == FAIL) + return FAIL; + start_stuff(); + +/* skip the count and the command character */ + while ((c = read_redo(FALSE, FALSE)) != NUL) + { + c = TO_UPPER(c); + if (vim_strchr((char_u *)"AIRO", c) != NULL) + { + if (c == 'O') + stuffReadbuff(NL_STR); + break; + } + } + +/* copy the typed text from the redo buffer into the stuff buffer */ + copy_redo(FALSE); + block_redo = TRUE; + return OK; +} + + void +set_redo_ins() +{ + block_redo = TRUE; +} + + void +stop_redo_ins() +{ + block_redo = FALSE; +} + +/* + * Initialize typebuf to point to typebuf_init. + * Alloc() cannot be used here: In out-of-memory situations it would + * be impossible to type anything. + */ + static void +init_typebuf() +{ + if (typebuf == NULL) + { + typebuf = typebuf_init; + noremapbuf = noremapbuf_init; + typebuflen = TYPELEN_INIT; + typelen = 0; + typeoff = 0; + } +} + +/* + * insert a string in position 'offset' in the typeahead buffer (for "@r" + * and ":normal" command, vgetorpeek() and check_termcode()) + * + * If noremap is 0, new string can be mapped again. + * If noremap is -1, new string cannot be mapped again. + * If noremap is >0, that many characters of the new string cannot be mapped. + * + * If nottyped is TRUE, the string does not return KeyTyped (don't use when + * offset is non-zero!). + * + * return FAIL for failure, OK otherwise + */ + int +ins_typebuf(str, noremap, offset, nottyped) + char_u *str; + int noremap; + int offset; + int nottyped; +{ + register char_u *s1, *s2; + register int newlen; + register int addlen; + register int i; + register int newoff; + + init_typebuf(); + + addlen = STRLEN(str); + /* + * Easy case: there is room in front of typebuf[typeoff] + */ + if (offset == 0 && addlen <= typeoff) + { + typeoff -= addlen; + vim_memmove(typebuf + typeoff, str, (size_t)addlen); + } + /* + * Need to allocate new buffer. + * In typebuf there must always be room for MAXMAPLEN + 4 characters. + * We add some extra room to avoid having to allocate too often. + */ + else + { + newoff = MAXMAPLEN + 4; + newlen = typelen + addlen + newoff + 2 * (MAXMAPLEN + 4); + if (newlen < 0) /* string is getting too long */ + { + emsg(e_toocompl); /* also calls flush_buffers */ + setcursor(); + return FAIL; + } + s1 = alloc(newlen); + if (s1 == NULL) /* out of memory */ + return FAIL; + s2 = alloc(newlen); + if (s2 == NULL) /* out of memory */ + { + vim_free(s1); + return FAIL; + } + typebuflen = newlen; + + /* copy the old chars, before the insertion point */ + vim_memmove(s1 + newoff, typebuf + typeoff, (size_t)offset); + /* copy the new chars */ + vim_memmove(s1 + newoff + offset, str, (size_t)addlen); + /* copy the old chars, after the insertion point, including + * the NUL at the end */ + vim_memmove(s1 + newoff + offset + addlen, typebuf + typeoff + offset, + (size_t)(typelen - offset + 1)); + if (typebuf != typebuf_init) + vim_free(typebuf); + typebuf = s1; + + vim_memmove(s2 + newoff, noremapbuf + typeoff, (size_t)offset); + vim_memmove(s2 + newoff + offset + addlen, + noremapbuf + typeoff + offset, (size_t)(typelen - offset)); + if (noremapbuf != noremapbuf_init) + vim_free(noremapbuf); + noremapbuf = s2; + + typeoff = newoff; + } + typelen += addlen; + + /* + * Adjust noremapbuf[] for the new characters: + * If noremap < 0: all the new characters are flagged not remappable + * If noremap == 0: all the new characters are flagged mappable + * If noremap > 0: 'noremap' characters are flagged not remappable, the + * rest mappable + */ + if (noremap < 0) /* length not specified */ + noremap = addlen; + for (i = 0; i < addlen; ++i) + noremapbuf[typeoff + i + offset] = (noremap-- > 0); + + /* this is only correct for offset == 0! */ + if (nottyped) /* the inserted string is not typed */ + typemaplen += addlen; + if (no_abbr_cnt && offset == 0) /* and not used for abbreviations */ + no_abbr_cnt += addlen; + + return OK; +} + +/* + * Return TRUE if there are no characters in the typeahead buffer that have + * not been typed (result from a mapping or come from ":normal"). + */ + int +typebuf_typed() +{ + return typemaplen == 0; +} + +/* + * remove "len" characters from typebuf[typeoff + offset] + */ + void +del_typebuf(len, offset) + int len; + int offset; +{ + int i; + + typelen -= len; + /* + * Easy case: Just increase typeoff. + */ + if (offset == 0 && typebuflen - (typeoff + len) >= MAXMAPLEN + 3) + typeoff += len; + /* + * Have to move the characters in typebuf[] and noremapbuf[] + */ + else + { + i = typeoff + offset; + /* + * Leave some extra room at the end to avoid reallocation. + */ + if (typeoff > MAXMAPLEN) + { + vim_memmove(typebuf + MAXMAPLEN, typebuf + typeoff, (size_t)offset); + vim_memmove(noremapbuf + MAXMAPLEN, noremapbuf + typeoff, + (size_t)offset); + typeoff = MAXMAPLEN; + } + /* adjust typebuf (include the NUL at the end) */ + vim_memmove(typebuf + typeoff + offset, typebuf + i + len, + (size_t)(typelen - offset + 1)); + /* adjust noremapbuf[] */ + vim_memmove(noremapbuf + typeoff + offset, noremapbuf + i + len, + (size_t)(typelen - offset)); + } + + if (typemaplen > offset) /* adjust typemaplen */ + { + if (typemaplen < offset + len) + typemaplen = offset; + else + typemaplen -= len; + } + if (no_abbr_cnt > offset) /* adjust no_abbr_cnt */ + { + if (no_abbr_cnt < offset + len) + no_abbr_cnt = offset; + else + no_abbr_cnt -= len; + } +} + +/* + * 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; +{ + int c; + char_u buf[2]; + + /* remember how many chars were last recorded */ + if (Recording) + last_recorded_len += len; + + buf[1] = NUL; + while (len--) + { + c = *s++; + updatescript(c); + + if (Recording) + { + buf[0] = c; + add_buff(&recordbuff, buf); + } + } + + /* + * Do not sync in insert mode, unless cursor key has been used. + * Also don't sync while reading a script file. + */ + if ((!(State & (INSERT + CMDLINE)) || arrow_used) && + scriptin[curscript] == NULL) + 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; + /* use NameBuff for expanded name */ + expand_env(name, NameBuff, MAXPATHL); + if ((scriptin[curscript] = fopen((char *)NameBuff, 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' and 'updatecount' is non-zero. + */ + void +updatescript(c) + int c; +{ + static int count = 0; + + if (c && scriptout) + putc(c, scriptout); + if (c == 0 || (p_uc > 0 && ++count >= p_uc)) + { + ml_sync_all(c == 0, TRUE); + count = 0; + } +} + +#define K_NEEDMORET -1 /* keylen value for incomplete key-code */ +#define M_NEEDMORET -2 /* keylen value for incomplete mapping */ + +static int old_char = -1; /* ungotten character */ + + int +vgetc() +{ + int c, c2; + + mod_mask = 0x0; + last_recorded_len = 0; + for (;;) /* this is done twice if there are modifiers */ + { + if (mod_mask) /* no mapping after modifier has been read */ + { + ++no_mapping; + ++allow_keys; + } + c = vgetorpeek(TRUE); + if (mod_mask) + { + --no_mapping; + --allow_keys; + } + + /* Get two extra bytes for special keys */ + if (c == K_SPECIAL) + { + ++no_mapping; + c2 = vgetorpeek(TRUE); /* no mapping for these chars */ + c = vgetorpeek(TRUE); + --no_mapping; + if (c2 == KS_MODIFIER) + { + mod_mask = c; + continue; + } + c = TO_SPECIAL(c2, c); + } +#ifdef MSDOS + /* + * If K_NUL was typed, it is replaced by K_NUL, 3 in mch_inchar(). + * Delete the 3 here. + */ + else if (c == K_NUL && vpeekc() == 3) + (void)vgetorpeek(TRUE); +#endif + + return check_shifted_spec_key(c); + } +} + + int +vpeekc() +{ + return (vgetorpeek(FALSE)); +} + +/* + * Call vpeekc() without causing anything to be mapped. + * Return TRUE if a character is available, FALSE otherwise. + */ + int +char_avail() +{ + int retval; + + ++no_mapping; + retval = vgetorpeek(FALSE); + --no_mapping; + return (retval != NUL); +} + + void +vungetc(c) /* unget one character (can only be done once!) */ + int c; +{ + old_char = c; +} + +/* + * get a character: 1. from a previously ungotten character + * 2. from the stuffbuffer + * 3. from the typeahead buffer + * 4. from the user + * + * if "advance" is TRUE (vgetc()): + * really get the character. + * KeyTyped is set to TRUE in the case the user typed the key. + * KeyStuffed is TRUE if the character comes from the stuff buffer. + * if "advance" is FALSE (vpeekc()): + * just look whether there is a character available. + */ + + static int +vgetorpeek(advance) + int advance; +{ + register int c, c1; + int keylen = 0; /* init for gcc */ +#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 */ + int local_State; + register int mlen; + int max_mlen; + int i; +#ifdef USE_GUI + int idx; +#endif + + /* + * VISUAL state is never set, it is used only here, therefore a check is + * made if NORMAL state is actually VISUAL state. + */ + local_State = State; + if ((State & NORMAL) && VIsual_active) + local_State = VISUAL; + +/* + * get a character: 1. from a previously ungotten character + */ + if (old_char >= 0) + { + c = old_char; + if (advance) + old_char = -1; + return c; + } + + if (advance) + KeyStuffed = FALSE; + + init_typebuf(); + start_stuff(); + if (advance && typemaplen == 0) + Exec_reg = FALSE; + do + { +/* + * get a character: 2. from the stuffbuffer + */ + c = read_stuff(advance); + if (c != NUL && !got_int) + { + if (advance) + { + KeyTyped = FALSE; + KeyStuffed = TRUE; + } + if (no_abbr_cnt == 0) + no_abbr_cnt = 1; /* no abbreviations now */ + } + 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 (;;) + { + mch_breakcheck(); /* check for CTRL-C */ + if (got_int) + { + c = inchar(typebuf, MAXMAPLEN, 0L); /* 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 (typelen > 0) /* check for a mappable key sequence */ + { + /* + * walk through the maplist until we find an + * entry that matches. + * + * Don't look for mappings if: + * - timed out + * - no_mapping set: mapping disabled (e.g. for CTRL-V) + * - typebuf[typeoff] should not be remapped + * - in insert or cmdline mode and 'paste' option set + * - waiting for "hit return to continue" and CR or SPACE + * typed + * - waiting for a char with --more-- + * - in Ctrl-X mode, and we get a valid char for that mode + */ + mp = NULL; + max_mlen = 0; + if (!timedout && no_mapping == 0 && (typemaplen == 0 || + (p_remap && noremapbuf[typeoff] == FALSE)) + && !(p_paste && (State & (INSERT + CMDLINE))) + && !(State == HITRETURN && (typebuf[typeoff] == CR + || typebuf[typeoff] == ' ')) + && State != ASKMORE +#ifdef INSERT_EXPAND + && !(ctrl_x_mode && is_ctrl_x_key(typebuf[typeoff])) +#endif + ) + { + c1 = typebuf[typeoff]; +#ifdef HAVE_LANGMAP + LANGMAP_ADJUST(c1, TRUE); +#endif + for (mp = maplist.m_next; mp; mp = mp->m_next) + { + /* + * Only consider an entry if + * - the first character matches and + * - it is not an abbreviation and + * - it is for the current state + */ + if ( mp->m_keys[0] == c1 && + !(mp->m_mode & ABBREV) && + (mp->m_mode & local_State)) + { + int n; +#ifdef HAVE_LANGMAP + int c2; +#endif + + /* find the match length of this mapping */ + for (mlen = 1; mlen < typelen; ++mlen) + { +#ifdef HAVE_LANGMAP + c2 = typebuf[typeoff + mlen]; + LANGMAP_ADJUST(c2, TRUE); + if (mp->m_keys[mlen] != c2) +#else + if (mp->m_keys[mlen] != + typebuf[typeoff + mlen]) +#endif + break; + } + + /* if one of the typed keys cannot be + * remapped, skip the entry */ + for (n = 0; n < mlen; ++n) + if (noremapbuf[typeoff + n] == TRUE) + break; + if (n != mlen) + continue; + + /* (partly) match found */ + keylen = mp->m_keylen; + if (mlen == (keylen > typelen ? + typelen : keylen)) + { + /* partial match, need more chars */ + if (keylen > typelen) + keylen = M_NEEDMORET; + break; + } + /* no match, may have to check for + * termcode at next character */ + if (max_mlen < mlen) + max_mlen = mlen; + } + } + } + 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 ((no_mapping == 0 || allow_keys != 0) && + (typemaplen == 0 || + (p_remap && noremapbuf[typeoff] == FALSE)) && + !timedout) + keylen = check_termcode(max_mlen + 1); + else + keylen = 0; + if (keylen == 0) /* no matching terminal code */ + { +#ifdef AMIGA /* check for window bounds report */ + if (typemaplen == 0 && + (typebuf[typeoff] & 0xff) == CSI) + { + for (s = typebuf + typeoff + 1; + s < typebuf + typeoff + typelen && + (isdigit(*s) || *s == ';' || *s == ' '); + ++s) + ; + if (*s == 'r' || *s == '|') /* found one */ + { + del_typebuf((int)(s + 1 - + (typebuf + typeoff)), 0); + /* get size and redraw screen */ + set_winsize(0, 0, FALSE); + continue; + } + if (*s == NUL) /* need more characters */ + keylen = K_NEEDMORET; + } + if (keylen >= 0) +#endif + { +/* + * get a character: 3. from the typeahead buffer + */ + c = typebuf[typeoff] & 255; + if (advance) /* remove chars from typebuf */ + { + if (typemaplen) + KeyTyped = FALSE; + else + { + KeyTyped = TRUE; + /* write char to script file(s) */ + gotchars(typebuf + typeoff, 1); + } + del_typebuf(1, 0); + } + break; /* got character, break for loop */ + } + } + if (keylen > 0) /* full matching terminal code */ + { +#ifdef USE_GUI + if (typebuf[typeoff] == K_SPECIAL && + typebuf[typeoff + 1] == KS_MENU) + { + /* + * Using a menu causes a break in undo! + */ + u_sync(); + del_typebuf(3, 0); + idx = gui_get_menu_index(current_menu, + local_State); + if (idx != MENU_INDEX_INVALID) + { + ins_typebuf(current_menu->strings[idx], + current_menu->noremap[idx] ? -1 : 0, + 0, TRUE); + } + } +#endif /* USE_GUI */ + continue; /* try mapping again */ + } + + /* partial match: get some more characters */ + keylen = K_NEEDMORET; + } + /* complete match */ + if (keylen >= 0 && keylen <= typelen) + { + /* write chars to script file(s) */ + if (keylen > typemaplen) + gotchars(typebuf + typeoff + typemaplen, + keylen - typemaplen); + + del_typebuf(keylen, 0); /* 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 >= p_mmd) + { + EMSG("recursive mapping"); + if (State == CMDLINE) + redrawcmdline(); + else + setcursor(); + flush_buffers(FALSE); + mapdepth = 0; /* for next one */ + c = -1; + break; + } + /* + * Insert the 'to' part in the typebuf. + * If 'from' field is the same as the start of the + * 'to' field, don't remap this part. + * If m_noremap is set, don't remap the whole 'to' + * part. + */ + if (ins_typebuf(mp->m_str, mp->m_noremap ? -1 : + STRNCMP(mp->m_str, mp->m_keys, + (size_t)keylen) ? 0 : keylen, + 0, TRUE) == FAIL) + { + 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 && typelen == 1 && typebuf[typeoff] == ESC && + !no_mapping && typemaplen == 0 && (State & INSERT) && + (p_timeout || (keylen == K_NEEDMORET && p_ttimeout)) && + (c = inchar(typebuf + typeoff + typelen, 3, 0L)) == 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) + { + /* + * We are expecting to truncate the trailing + * white-space, so find the last non-white + * character -- webb + */ + colnr_t col, vcol; + char_u *ptr; + + col = vcol = curwin->w_col = 0; + ptr = ml_get_curline(); + while (col < curwin->w_cursor.col) + { + if (!vim_iswhite(ptr[col])) + curwin->w_col = vcol; + vcol += lbr_chartabsize(ptr + col, + (colnr_t)vcol); + ++col; + } + if (curwin->w_p_nu) + curwin->w_col += 8; + } + else + --curwin->w_col; + } + else if (curwin->w_p_wrap && curwin->w_row) + { + --curwin->w_row; + curwin->w_col = Columns - 1; + } + } + setcursor(); + flushbuf(); + } + typelen += c; + /* buffer full, don't map */ + if (typelen >= typemaplen + MAXMAPLEN) + { + timedout = TRUE; + continue; + } +/* + * get a character: 4. from the user + */ + /* + * If we have a partial match (and are going to wait for more + * input from the user), show the partially matched characters + * to the user with showcmd -- webb. + */ + i = 0; + if (typelen > 0 && (State & (NORMAL | INSERT)) && advance) + { + push_showcmd(); + while (i < typelen) + (void)add_to_showcmd(typebuf[typeoff + i++], TRUE); + } + + c = inchar(typebuf + typeoff + typelen, + typemaplen + MAXMAPLEN - typelen, + !advance + ? 0 + : ((typelen == 0 || !(p_timeout || (p_ttimeout && + keylen == K_NEEDMORET))) + ? -1L + : ((keylen == K_NEEDMORET && p_ttm >= 0) + ? p_ttm + : p_tm))); + + if (i) + pop_showcmd(); + + if (c <= NUL) /* no character available */ + { + if (!advance) + break; + if (typelen) /* timed out */ + { + timedout = TRUE; + continue; + } + } + else + { /* allow mapping for just typed characters */ + while (typebuf[typeoff + typelen] != NUL) + noremapbuf[typeoff + typelen++] = FALSE; + } + } /* 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 to exit insert mode, 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 && !no_mapping) + delmode(); + else if (c != ESC && mode_deleted) + showmode(); + } + + return c; +} + +/* + * inchar() - get one character from + * 1. a scriptfile + * 2. the keyboard + * + * As much characters as we can get (upto 'maxlen') are put in buf and + * NUL terminated (buffer length must be 'maxlen' + 1). + * Minimum for 'maxlen' is 3!!!! + * + * If we got an interrupt all input is read until none is available. + * + * If wait_time == 0 there is no waiting for the char. + * If wait_time == n we wait for n msec for a character to arrive. + * If wait_time == -1 we wait forever for a character to arrive. + * + * Return the number of obtained characters. + */ + + static int +inchar(buf, maxlen, wait_time) + char_u *buf; + int maxlen; + long wait_time; /* milli seconds */ +{ + int len = 0; /* init for GCC */ + int retesc = FALSE; /* return ESC with gotint */ + register int c; + register int i; + + if (wait_time == -1L || wait_time > 100L) /* flush output before waiting */ + { + cursor_on(); + flushbuf(); + } + /* + * Don't reset these when at the hit-return prompt, otherwise a endless + * recursive loop may result (write error in swapfile, hit-return, timeout + * on char wait, flush swapfile, write error....). + */ + if (State != HITRETURN) + { + did_outofmem_msg = FALSE; /* display out of memory message (again) */ + did_swapwrite_msg = FALSE; /* display swap file write error again */ + } + undo_off = FALSE; /* restart undo now */ + +/* + * first try script file + * If interrupted: Stop reading script files. + */ + c = -1; + while (scriptin[curscript] != NULL && c < 0) + { + if (got_int || (c = getc(scriptin[curscript])) < 0) /* reached EOF */ + { + /* when reading script file is interrupted, return an ESC to + get back to normal mode */ + if (got_int) + retesc = TRUE; + fclose(scriptin[curscript]); + scriptin[curscript] = NULL; + if (curscript > 0) + --curscript; + } + else + { + buf[0] = c; + len = 1; + } + } + + if (c < 0) /* did not get a character from script */ + { + /* + * If we got an interrupt, skip all previously typed characters and + * return TRUE if quit reading script file. + */ + if (got_int) /* skip typed characters */ + { + while (mch_inchar(buf, maxlen, 0L)) + ; + return retesc; + } + /* fill up to a third of the buffer, because each character may be + * tripled below */ + len = mch_inchar(buf, maxlen / 3, wait_time); + } + + /* + * Two characters are special: NUL and K_SPECIAL. + * Replace NUL by K_SPECIAL KS_ZERO K_FILLER + * Replace K_SPECIAL by K_SPECIAL KS_SPECIAL K_FILLER + * Don't replace K_SPECIAL when reading a script file. + */ + for (i = len; --i >= 0; ++buf) + { + if (buf[0] == NUL || (buf[0] == K_SPECIAL && c < 0)) + { + vim_memmove(buf + 3, buf + 1, (size_t)i); + buf[2] = K_THIRD(buf[0]); + buf[1] = K_SECOND(buf[0]); + buf[0] = K_SPECIAL; + buf += 2; + len += 2; + } + } + *buf = NUL; /* add trailing NUL */ + return len; +} + +/* + * 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. Note: keys cannot be a read-only string, + * it will be modified. + * + * for :map mode is NORMAL + VISUAL + * for :map! mode is INSERT + CMDLINE + * for :cmap mode is CMDLINE + * for :imap mode is INSERT + * for :nmap mode is NORMAL + * for :vmap mode is VISUAL + * 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 +do_map(maptype, keys, mode) + int maptype; + char_u *keys; + int mode; +{ + struct mapblock *mp, *mprev; + char_u *arg; + char_u *p; + int n; + int len = 0; /* init for GCC */ + char_u *newstr; + int hasarg; + int haskey; + int did_it = FALSE; + int abbrev = 0; + int round; + char_u *keys_buf = NULL; + char_u *arg_buf = NULL; + int retval = 0; + int do_backslash; + + if (mode & ABBREV) /* not a mapping but an abbreviation */ + { + abbrev = ABBREV; + mode &= ~ABBREV; + } +/* + * find end of keys and skip CTRL-Vs (and backslashes) in it + * Accept backslash like CTRL-V when 'cpoptions' does not contain 'B'. + * with :unmap white space is included in the keys, no argument possible + */ + p = keys; + do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL); + while (*p && (maptype == 1 || !vim_iswhite(*p))) + { + if ((p[0] == Ctrl('V') || (do_backslash && p[0] == '\\')) && + p[1] != NUL) + ++p; /* skip CTRL-V or backslash */ + ++p; + } + if (*p != NUL) + *p++ = NUL; + p = skipwhite(p); + arg = p; + hasarg = (*arg != NUL); + haskey = (*keys != NUL); + + /* check for :unmap without argument */ + if (maptype == 1 && !haskey) + { + retval = 1; + goto theend; + } + + /* + * If mapping has been given as ^V say, then replace the term codes + * with the appropriate two bytes. If it is a shifted special key, unshift + * it too, giving another two bytes. + * replace_termcodes() may move the result to allocated memory, which + * needs to be freed later (*keys_buf and *arg_buf). + * replace_termcodes() also removes CTRL-Vs and sometimes backslashes. + */ + if (haskey) + keys = replace_termcodes(keys, &keys_buf, TRUE); + if (hasarg) + arg = replace_termcodes(arg, &arg_buf, FALSE); + +/* + * check arguments and translate function keys + */ + if (haskey) + { + len = STRLEN(keys); + if (len > MAXMAPLEN) /* maximum length of MAXMAPLEN chars */ + { + retval = 1; + goto theend; + } + + if (abbrev) + { + /* + * If an abbreviation ends in a keyword character, the + * rest must be all keyword-char or all non-keyword-char. + * Otherwise we won't be able to find the start of it in a + * vi-compatible way. + * An abbrevation cannot contain white space. + */ + if (iswordchar(keys[len - 1])) /* ends in keyword char */ + for (n = 0; n < len - 2; ++n) + if (iswordchar(keys[n]) != iswordchar(keys[len - 2])) + { + retval = 1; + goto theend; + } + for (n = 0; n < len; ++n) + if (vim_iswhite(keys[n])) + { + retval = 1; + goto theend; + } + } + } + + 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; + retval = 3; + goto theend; + } + 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) + { + retval = 4; /* no mem */ + goto theend; + } + vim_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 */ + { + map_free(mprev); + mp = mprev; /* continue with next entry */ + } + } + } + } + } + + if (maptype == 1) /* delete entry */ + { + if (!did_it) + retval = 2; /* no match */ + goto theend; + } + + if (!haskey || !hasarg) /* print entries */ + { + if (!did_it) + { + if (abbrev) + MSG("No abbreviation found"); + else + MSG("No mapping found"); + } + goto theend; /* listing finished */ + } + + if (did_it) /* have added the new entry already */ + goto theend; +/* + * 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) + { + retval = 4; /* no mem */ + goto theend; + } + mp->m_keys = strsave(keys); + mp->m_str = strsave(arg); + if (mp->m_keys == NULL || mp->m_str == NULL) + { + vim_free(mp->m_keys); + vim_free(mp->m_str); + vim_free(mp); + retval = 4; /* no mem */ + goto theend; + } + 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; + +theend: + vim_free(keys_buf); + vim_free(arg_buf); + return retval; +} + +/* + * Delete one entry from the maplist. + * The argument is a pointer to the PREVIOUS entry! + */ + static void +map_free(mprev) + struct mapblock *mprev; +{ + struct mapblock *mp; + + mp = mprev->m_next; + vim_free(mp->m_keys); + vim_free(mp->m_str); + mprev->m_next = mp->m_next; + vim_free(mp); +} + +/* + * Clear all mappings or abbreviations. + * 'abbr' should be FALSE for mappings, TRUE for abbreviations. + */ + void +map_clear(modec, force, abbr) + int modec; + int force; + int abbr; +{ + struct mapblock *mp; + int mode; + + if (force) /* :mapclear! */ + mode = INSERT + CMDLINE; + else if (modec == 'i') + mode = INSERT; + else if (modec == 'n') + mode = NORMAL; + else if (modec == 'c') + mode = CMDLINE; + else if (modec == 'v') + mode = VISUAL; + else + mode = VISUAL + NORMAL; + + for (mp = &maplist; mp->m_next != NULL; ) + { + if (abbr != !(mp->m_next->m_mode & ABBREV) && mp->m_next->m_mode & mode) + { + mp->m_next->m_mode &= ~mode; + if ((mp->m_next->m_mode & ~ABBREV) == 0) /* entry can be deleted */ + { + map_free(mp); + continue; + } + } + mp = mp->m_next; + } +} + + static void +showmap(mp) + struct mapblock *mp; +{ + int len; + + if (msg_didout) + msg_outchar('\n'); + if ((mp->m_mode & (INSERT + CMDLINE)) == INSERT + CMDLINE) + MSG_OUTSTR("! "); + else if (mp->m_mode & INSERT) + MSG_OUTSTR("i "); + else if (mp->m_mode & CMDLINE) + MSG_OUTSTR("c "); + else if (!(mp->m_mode & VISUAL)) + MSG_OUTSTR("n "); + else if (!(mp->m_mode & NORMAL)) + MSG_OUTSTR("v "); + else + MSG_OUTSTR(" "); + /* Get length of what we write */ + len = msg_outtrans_special(mp->m_keys, TRUE); + do + { + msg_outchar(' '); /* padd with blanks */ + ++len; + } while (len < 12); + if (mp->m_noremap) + msg_outchar('*'); + else + msg_outchar(' '); + /* Use FALSE below if we only want things like to show up as such on + * the rhs, and not M-x etc, TRUE gets both -- webb + */ + msg_outtrans_special(mp->m_str, TRUE); + 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". + * + * Vim addition: Allow for abbreviations that end in a non-keyword character. + * Then there must be white space before the abbr. + * + * 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[4]; + struct mapblock *mp; + int is_id = TRUE; + int vim_abbr; + + if (no_abbr_cnt) /* abbrev. are not recursive */ + return FALSE; + + /* + * Check for word before the cursor: If it ends in a keyword char all + * chars before it must be al keyword chars or non-keyword chars, but not + * white space. If it ends in a non-keyword char we accept any characters + * before it except white space. + */ + if (col == 0) /* cannot be an abbr. */ + return FALSE; + + if (!iswordchar(ptr[col - 1])) + vim_abbr = TRUE; /* Vim added abbr. */ + else + { + vim_abbr = FALSE; /* vi compatible abbr. */ + if (col > 1) + is_id = iswordchar(ptr[col - 2]); + } + for (len = col - 1; len > 0 && !vim_isspace(ptr[len - 1]) && + (vim_abbr || is_id == iswordchar(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: + * Insert the rest of the abbreviation in typebuf[]. + * This goes from end to start. + * + * Characters 0x000 - 0x100: normal chars, may need CTRL-V, + * except K_SPECIAL: Becomes K_SPECIAL KS_SPECIAL K_FILLER + * Characters where IS_SPECIAL() == TRUE: key codes, need + * K_SPECIAL. Other characters (with ABBR_OFF): don't use CTRL-V. + */ + j = 0; + /* special key code, split up */ + if (IS_SPECIAL(c) || c == K_SPECIAL) + { + tb[j++] = K_SPECIAL; + tb[j++] = K_SECOND(c); + c = K_THIRD(c); + } + else if (c < 0x100 && (c < ' ' || c > '~')) + tb[j++] = Ctrl('V'); /* special char needs CTRL-V */ + tb[j++] = c; + tb[j] = NUL; + /* insert the last typed char */ + (void)ins_typebuf(tb, TRUE, 0, TRUE); + /* insert the to string */ + (void)ins_typebuf(mp->m_str, mp->m_noremap, 0, TRUE); + /* no abbrev. for these chars */ + no_abbr_cnt += STRLEN(mp->m_str) + j + 1; + + tb[0] = Ctrl('H'); + tb[1] = NUL; + while (len--) /* delete the from string */ + (void)ins_typebuf(tb, TRUE, 0, TRUE); + 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 + VISUAL: + break; + case NORMAL: + c1 = 'n'; + break; + case VISUAL: + c1 = 'v'; + 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 USE_CRNL + 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 */ +{ + int c; + int modifiers; + + for ( ; *str; ++str) + { + c = *str; + /* + * Special key codes have to be translated to be able to make sense + * when they are read back. + */ + if (c == K_SPECIAL && !set) + { + modifiers = 0x0; + if (str[1] == KS_MODIFIER) + { + modifiers = str[2]; + str += 3; + c = *str; + } + if (c == K_SPECIAL) + { + c = TO_SPECIAL(str[1], str[2]); + str += 2; + } + if (IS_SPECIAL(c) || modifiers) /* special key */ + { + fprintf(fd, (char *)get_special_key_name(c, modifiers)); + continue; + } + } + /* + * A '\n' in a map command should be written as . + * A '\n' in a set command should be written as \^V^J. + */ + if (c == NL) + { + if (set) + fprintf(fd, "\\\026\n"); + else + fprintf(fd, ""); + continue; + } + /* + * some characters have to be escaped with CTRL-V to + * prevent them from misinterpreted in DoOneCmd(). + * A space, Tab and '"' has to be escaped with a backslash to + * prevent it to be misinterpreted in do_set(). + */ + if (set && (vim_iswhite(c) || c == '"' || c == '\\')) + { + if (putc('\\', fd) < 0) + return FAIL; + } + else if (c < ' ' || c > '~' || c == '|') + { + if (putc(Ctrl('V'), fd) < 0) + return FAIL; + } + if (putc(c, fd) < 0) + return FAIL; + } + return OK; +} + +/* + * Check all mappings for the presence of special key codes. + * Used after ":set term=xxx". + */ + void +check_map_keycodes() +{ + struct mapblock *mp; + char_u *p; + int i; + char_u buf[3]; + char_u *save_name; + + save_name = sourcing_name; + sourcing_name = (char_u *)"mappings";/* don't give error messages */ + for (mp = maplist.m_next; mp != NULL; mp = mp->m_next) + { + for (i = 0; i <= 1; ++i) /* do this twice */ + { + if (i == 0) + p = mp->m_keys; /* once for the "from" part */ + else + p = mp->m_str; /* and once for the "to" part */ + while (*p) + { + if (*p == K_SPECIAL) + { + ++p; + if (*p < 128) /* only for "normal" termcap entries */ + { + buf[0] = p[0]; + buf[1] = p[1]; + buf[2] = NUL; + (void)add_termcap_entry(buf, FALSE); + } + ++p; + } + ++p; + } + } + } + sourcing_name = save_name; +} diff --git a/usr.bin/vim/globals.h b/usr.bin/vim/globals.h new file mode 100644 index 00000000000..9076e14bfbe --- /dev/null +++ b/usr.bin/vim/globals.h @@ -0,0 +1,446 @@ +/* $OpenBSD: globals.h,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * definition of global variables + * + * EXTERN is only defined in main.c (and in option.h) + */ + +#ifndef EXTERN +# define EXTERN extern +# define INIT(x) +#else +# ifndef INIT +# define INIT(x) x +# endif +#endif + +/* + * Number of Rows and Columns in the screen. + * Must be long to be able to use them as options in option.c. + */ +EXTERN long Rows; /* number of rows in the screen */ +EXTERN long Columns; /* number of columns in the screen */ + +/* + * The characters that are currently on the screen are kept in NextScreen. + * It is a single block of characters, twice the size of the screen. + * First come the characters for one line, then the attributes for that line. + * + * "LinePointers[n]" points into NextScreen, at the start of line 'n'. + * "LinePointers[n] + Columns" points to the attibutes of line 'n'. + */ +EXTERN char_u *NextScreen INIT(= NULL); +EXTERN char_u **LinePointers INIT(= NULL); + +EXTERN int screen_Rows INIT(= 0); /* actual size of NextScreen */ +EXTERN int screen_Columns INIT(= 0); /* actual size of NextScreen */ + +/* + * Positioning the cursor is reduced by remembering the last position. + * Mostly used by screen_char(). + */ +EXTERN int screen_cur_row, screen_cur_col; /* last known cursor position */ + +/* + * When vgetc() is called, it sets mod_mask to the set of modifiers that are + * held down based on the KSMOD_* symbols that are read first. + */ +EXTERN int mod_mask INIT(= 0x0); /* current key modifiers */ + +/* + * Cmdline_row is the row where the command line starts, just below the + * last window. + * When the cmdline gets longer than the available space the screen gets + * scrolled up. After a CTRL-D (show matches), after hitting ':' after + * "hit return", and for the :global command, the command line is + * temporarily moved. The old position is restored with the next call to + * updateScreen(). + */ +EXTERN int cmdline_row; + +EXTERN int redraw_cmdline INIT(= FALSE); /* cmdline must be redrawn */ +EXTERN int clear_cmdline INIT(= FALSE); /* cmdline must be cleared */ +EXTERN int modified INIT(= FALSE); /* buffer was modified since + last redraw */ +EXTERN int screen_cleared INIT(= FALSE); /* screen has been cleared */ + +/* + * When '$' is included in 'cpoptions' option set: + * When a change command is given that deletes only part of a line, a dollar + * is put at the end of the changed text. dollar_vcol is set to the virtual + * column of this '$'. + */ +EXTERN colnr_t dollar_vcol INIT(= 0); + +/* + * used for completion on the command line + */ +EXTERN int expand_context INIT(= CONTEXT_UNKNOWN); +EXTERN char_u *expand_pattern INIT(= NULL); +EXTERN int expand_interactively INIT(= FALSE); + +/* + * Functions for putting characters in the command line, + * while keeping NextScreen updated. + */ +EXTERN int msg_col; +EXTERN int msg_row; +EXTERN int msg_scrolled; + +EXTERN char_u *keep_msg INIT(= NULL); /* msg to be shown after redraw */ +EXTERN int keep_msg_highlight INIT(= 0);/* highlight for keep_msg */ +#ifdef SLEEP_IN_EMSG +EXTERN int need_sleep INIT(= FALSE); /* call sleep() before redraw */ +#endif +EXTERN int need_fileinfo INIT(= FALSE);/* do fileinfo() after redraw */ +EXTERN int msg_scroll INIT(= FALSE); /* msg_start() will scroll */ +EXTERN int msg_didout INIT(= FALSE); /* msg_outstr() was used in line */ +EXTERN int msg_didany INIT(= FALSE); /* msg_outstr() was used at all */ +EXTERN int emsg_off INIT(= FALSE); /* don't display errors for now */ +EXTERN int did_emsg; /* set by emsg() for DoOneCmd() */ +EXTERN int emsg_on_display INIT(= FALSE); /* there is an error message */ +EXTERN char_u *sourcing_name INIT( = NULL);/* name of error message source */ +EXTERN linenr_t sourcing_lnum INIT(= 0); /* line number of the source file */ + +EXTERN int msg_highlight INIT(= FALSE);/* message should be highlighted */ +EXTERN char_u *highlight INIT(= NULL); /* string for start of highlighting */ +EXTERN char_u *unhighlight INIT(= NULL); /* string for end of highlighting */ +EXTERN int scroll_region INIT(= FALSE);/* terminal supports scroll region */ +EXTERN int highlight_match INIT(= FALSE); /* show search match pos */ +EXTERN int search_match_len; /* length of matched string */ +EXTERN int no_smartcase INIT(= FALSE); /* don't use 'smartcase' once */ +EXTERN int need_check_timestamps INIT(= FALSE); /* got STOP signal */ + +#ifdef AUTOCMD +EXTERN int autocmd_busy INIT(= FALSE); /* Is apply_autocmds() busy? */ +#endif + +#ifdef USE_MOUSE +/* + * Mouse coordinates, set by check_termcode() + */ +EXTERN int mouse_row; +EXTERN int mouse_col; +EXTERN int mouse_past_bottom INIT(= FALSE);/* mouse below last line */ +EXTERN int mouse_past_eol INIT(= FALSE); /* mouse right of line */ +#endif + +#ifdef USE_GUI +/* + * Menu item just selected, set by check_termcode() + */ +EXTERN GuiMenu *current_menu; + +/* + * Scrollbar moved and new value, set by check_termcode() + */ +EXTERN int current_scrollbar; +EXTERN long_u scrollbar_value; +#endif + +/* + * All windows are linked in a list. firstwin points to the first entry, lastwin + * to the last entry (can be the same as firstwin) and curwin to the currently + * active window. + */ +EXTERN WIN *firstwin; /* first window */ +EXTERN WIN *lastwin; /* last window */ +EXTERN WIN *curwin; /* currently active window */ + +/* + * All buffers are linked in a list. 'firstbuf' points to the first entry, + * 'lastbuf' to the last entry and 'curbuf' to the currently active buffer. + */ +EXTERN BUF *firstbuf INIT(= NULL); /* first buffer */ +EXTERN BUF *lastbuf INIT(= NULL); /* last buffer */ +EXTERN BUF *curbuf INIT(= NULL); /* currently active buffer */ + +/* + * list of files being edited (argument list) + */ +EXTERN char_u **arg_files; /* list of files */ +EXTERN int arg_count; /* number of files */ +EXTERN int arg_exp; /* when TRUE arg_files must be freed */ +EXTERN int arg_had_last INIT(= FALSE); /* accessed last file in arglist */ + +EXTERN int ru_col; /* column for ruler */ +EXTERN int sc_col; /* column for shown command */ + +/* + * When starting or exiting some things are done differently (e.g. screen + * updating). + */ +EXTERN int starting INIT(= TRUE); + /* set to FALSE when starting up finished */ +EXTERN int exiting INIT(= FALSE); + /* set to TRUE when abandoning Vim */ +EXTERN int full_screen INIT(= TRUE); + /* set to FALSE when not doing full-screen + * output and only writing some messages */ + +EXTERN int secure INIT(= FALSE); + /* set to TRUE when only "safe" commands are + * allowed, e.g. when sourcing .exrc or .vimrc + * in current directory */ + +EXTERN int found_version INIT(= 0); + /* version nr found after :version command */ + +EXTERN FPOS VIsual; /* start position of Visual */ +EXTERN FPOS VIsual_save; /* copy of VIsual before 'v' command */ +EXTERN int VIsual_active INIT(= FALSE); + /* wheter Visual mode is active */ +EXTERN FPOS VIsual_end; /* end position of Visual; set when + VIsual_active becomes FALSE */ + +EXTERN int VIsual_mode INIT(= 'v'); + /* type of Visual mode */ +EXTERN int VIsual_mode_save; + /* copy of VIsual_mode before 'v' command */ +EXTERN int redo_VIsual_busy INIT(= FALSE); + /* TRUE when redo-ing a visual */ + +#ifdef USE_MOUSE +/* + * When pasting text with the middle mouse button in visual mode with + * restart_edit set, remember where it started so we can set Insstart. + */ +EXTERN FPOS where_paste_started; +#endif + +/* + * This flag is used to make auto-indent work right on lines where only a + * or is typed. It is set when an auto-indent is done, and + * reset when any other editting is done on the line. If an or + * is received, and did_ai is TRUE, the line is truncated. + */ +EXTERN int did_ai INIT(= FALSE); + +/* + * This flag is set when a smart indent has been performed. When the next typed + * character is a '{' the inserted tab will be deleted again. + */ +EXTERN int did_si INIT(= FALSE); + +/* + * This flag is set after an auto indent. If the next typed character is a '}' + * one indent will be removed. + */ +EXTERN int can_si INIT(= FALSE); + +/* + * This flag is set after an "O" command. If the next typed character is a '{' + * one indent will be removed. + */ +EXTERN int can_si_back INIT(= FALSE); + +EXTERN int old_indent INIT(= 0); /* for ^^D command in insert mode */ + +EXTERN int State INIT(= NORMAL); /* This is the current state of the + * command interpreter. */ +EXTERN int no_mapping INIT(= FALSE); /* currently no mapping allowed */ +EXTERN int allow_keys INIT(= FALSE); /* allow key codes when no_mapping + * is set */ + +EXTERN int restart_edit INIT(= 0); /* call edit when next command finished + */ +EXTERN int arrow_used; /* Normally FALSE, set to TRUE after + * hitting cursor key in insert mode. + * Used by vgetorpeek() to decide when + * to call u_sync() */ +#ifdef INSERT_EXPAND +EXTERN char_u *edit_submode INIT(= NULL); /* msg for CTRL-X submode */ +EXTERN char_u *edit_submode_extra INIT(= NULL);/* extra info for msg */ +EXTERN int edit_submode_highl; /* extra info highlighted */ +EXTERN int ctrl_x_mode INIT(= 0); /* Which Ctrl-X mode are we in? */ +#endif + +EXTERN int Recording INIT(= FALSE);/* TRUE when recording into a register + */ +EXTERN int Exec_reg INIT(= FALSE); /* TRUE when executing a register */ + +EXTERN int did_cd INIT(= FALSE); /* TRUE when :cd dir used */ +EXTERN int no_abbr INIT(= TRUE); /* TRUE when no abbreviations loaded */ +EXTERN int fo_do_comments INIT(= FALSE); + /* TRUE when comments are to be + * formatted */ +#if defined MSDOS || defined WIN32 +EXTERN int beep_count INIT(= 0); /* nr of beeps since last char typed */ +#endif + +EXTERN char_u *IObuff; /* sprintf's are done in this buffer */ +EXTERN char_u *NameBuff; /* file names are expanded in this + * buffer */ +EXTERN char_u msg_buf[MSG_BUF_LEN]; /* small buffer for messages */ + +EXTERN int RedrawingDisabled INIT(= FALSE); + /* Set to TRUE if doing :g */ + +EXTERN int readonlymode INIT(= FALSE); /* Set to TRUE for "view" */ +EXTERN int recoverymode INIT(= FALSE); /* Set to TRUE for "-r" option */ + +EXTERN char_u *typebuf INIT(= NULL); /* buffer for typed characters */ +EXTERN int typebuflen; /* size of typebuf */ +EXTERN int typeoff; /* current position in typebuf */ +EXTERN int typelen; /* number of valid chars in typebuf */ +EXTERN int KeyTyped; /* TRUE if user typed current char */ +EXTERN int KeyStuffed; /* TRUE if current char from stuffbuf */ + +EXTERN int must_redraw INIT(= 0); /* type of redraw necessary */ +EXTERN int skip_redraw INIT(= FALSE); /* skip redraw once */ +EXTERN int do_redraw INIT(= FALSE); /* extra redraw once */ + +EXTERN char_u *use_viminfo INIT(= NULL); /* name of viminfo file to use */ + +#define NSCRIPT 15 +EXTERN FILE *scriptin[NSCRIPT]; /* streams to read script from */ +EXTERN int curscript INIT(= 0); /* index in scriptin[] */ +EXTERN FILE *scriptout INIT(= NULL); /* stream to write script to */ + +EXTERN int got_int INIT(= FALSE); /* set to TRUE when interrupt + signal occurred */ +EXTERN int term_console INIT(= FALSE); /* set to TRUE when consule used */ +EXTERN int termcap_active INIT(= FALSE); /* set by starttermcap() */ +EXTERN int bangredo INIT(= FALSE); /* set to TRUE whith ! command */ +EXTERN int searchcmdlen; /* length of previous search cmd */ +EXTERN int reg_ic INIT(= 0); /* p_ic passed to vim_regexec() */ +EXTERN int reg_magic; /* p_magic passed to ergexec() */ + +EXTERN int did_outofmem_msg INIT(= FALSE); + /* set after out of memory msg */ +EXTERN int did_swapwrite_msg INIT(= FALSE); + /* set after swap write error msg */ +EXTERN int undo_off INIT(= FALSE); /* undo switched off for now */ +EXTERN int global_busy INIT(= 0); /* set when :global is executing */ +#ifdef SLEEP_IN_EMSG +EXTERN int dont_sleep INIT(= FALSE); /* set when sleep() in emsg() not + wanted */ +#endif +EXTERN int need_start_insertmode INIT(= FALSE); + /* start insert mode soon */ +EXTERN int rc_did_emsg INIT(= FALSE); /* vim_regcomp() called emsg() */ +EXTERN int no_wait_return INIT(= 0); /* don't wait for return now */ +EXTERN int need_wait_return INIT(= 0); /* need to wait for return later */ +EXTERN int dont_wait_return INIT(= 0); /* no need to wait for return */ +EXTERN int quit_more INIT(= FALSE); /* 'q' hit at "--more--" msg */ +EXTERN char_u *last_cmdline INIT(= NULL); /* last command line (for ":) */ +EXTERN char_u *new_last_cmdline INIT(= NULL); /* new value for last_cmdline */ +EXTERN char_u *autocmd_fname INIT(= NULL); /* fname for "^Vf" on cmdline */ + +EXTERN int postponed_split INIT(= FALSE); /* for CTRL-W CTRL-] command */ +EXTERN int replace_offset INIT(= 0); /* offset for replace_push() */ + +EXTERN char_u *escape_chars INIT(= (char_u *)" \t\\\"|"); + /* need backslash in cmd line */ + +EXTERN char_u *help_save_isk INIT(= NULL);/* 'isk' saved by do_help() */ +EXTERN long help_save_ts INIT(= 0); /* 'ts' saved by do_help() */ +EXTERN int keep_help_flag INIT(= FALSE); /* doing :ta from help file */ + +/* + * When a string option is NULL (which only happens in out-of-memory + * situations), it is set to empty_option, to avoid having to check for NULL + * everywhere. + */ +EXTERN char_u *empty_option INIT(= (char_u *)""); + +#ifdef DEBUG +EXTERN FILE *debugfp INIT(=NULL); +#endif + +#ifdef HAVE_LANGMAP +EXTERN char_u langmap_mapchar[256]; /* mapping for language keys */ +#endif + +EXTERN char breakat_flags[256]; /* which characters are in 'breakat' */ + +extern char *Version; /* this is in version.c */ +extern char *longVersion; /* this is in version.c */ + +/* + * Some file names for Unix are stored in pathdef.c, to make their value + * depend on the Makefile. + */ +#if defined(HAVE_CONFIG_H) || defined(OS2) +extern char_u *sys_vimrc_fname; /* this is in pathdef.c */ +extern char_u *sys_compatrc_fname; /* this is in pathdef.c */ +extern char_u *sys_gvimrc_fname; /* this is in pathdef.c */ +extern char_u *help_fname; /* this is in pathdef.c */ +extern char_u *all_cflags; /* this is in pathdef.c */ +#endif + +EXTERN char_u no_lines_msg[] INIT(="--No lines in buffer--"); + +/* + * The error messages that can be shared are included here. + * Excluded are very specific errors and debugging messages. + */ +EXTERN char_u e_abbr[] INIT(="No such abbreviation"); +EXTERN char_u e_abort[] INIT(="Command aborted"); +EXTERN char_u e_ambmap[] INIT(="Ambiguous mapping"); +EXTERN char_u e_argreq[] INIT(="Argument required"); +EXTERN char_u e_backslash[] INIT(="\\ should be followed by /, ? or &"); +EXTERN char_u e_curdir[] INIT(="Command not allowed from from .exrc/.vimrc in current dir"); +EXTERN char_u e_errorf[] INIT(="No errorfile name"); +EXTERN char_u e_exists[] INIT(="File exists (use ! to override)"); +EXTERN char_u e_failed[] INIT(="Command failed"); +EXTERN char_u e_internal[] INIT(="Internal error"); +EXTERN char_u e_interr[] INIT(="Interrupted"); +EXTERN char_u e_invaddr[] INIT(="Invalid address"); +EXTERN char_u e_invarg[] INIT(="Invalid argument"); +EXTERN char_u e_invrange[] INIT(="Invalid range"); +EXTERN char_u e_invcmd[] INIT(="Invalid command"); +EXTERN char_u e_markinval[] INIT(="Mark has invalid line number"); +EXTERN char_u e_marknotset[] INIT(="Mark not set"); +EXTERN char_u e_nesting[] INIT(="Scripts nested too deep"); +EXTERN char_u e_noalt[] INIT(="No alternate file"); +EXTERN char_u e_nobang[] INIT(="No ! allowed"); +EXTERN char_u e_nogvim[] INIT(="GUI cannot be used: Not enabled at compile time\n"); +EXTERN char_u e_nohebrew[] INIT(="Hebrew cannot be used: Not enabled at compile time\n"); +EXTERN char_u e_noinstext[] INIT(="No inserted text yet"); +EXTERN char_u e_nolastcmd[] INIT(="No previous command line"); +EXTERN char_u e_nomap[] INIT(="No such mapping"); +EXTERN char_u e_nomatch[] INIT(="No match"); +EXTERN char_u e_nomore[] INIT(="No more files to edit"); +EXTERN char_u e_noname[] INIT(="No file name"); +EXTERN char_u e_nopresub[] INIT(="No previous substitute regular expression"); +EXTERN char_u e_noprev[] INIT(="No previous command"); +EXTERN char_u e_noprevre[] INIT(="No previous regular expression"); +EXTERN char_u e_norange[] INIT(="No range allowed"); +EXTERN char_u e_noroom[] INIT(="Not enough room"); +EXTERN char_u e_notcreate[] INIT(="Can't create file %s"); +EXTERN char_u e_notmp[] INIT(="Can't get temp file name"); +EXTERN char_u e_notopen[] INIT(="Can't open file %s"); +EXTERN char_u e_notread[] INIT(="Can't read file %s"); +EXTERN char_u e_nowrtmsg[] INIT(="No write since last change (use ! to override)"); +EXTERN char_u e_null[] INIT(="Null argument"); +EXTERN char_u e_number[] INIT(="Number expected"); +EXTERN char_u e_openerrf[] INIT(="Can't open errorfile %s"); +EXTERN char_u e_outofmem[] INIT(="Out of memory!"); +EXTERN char_u e_patnotf[] INIT(="Pattern not found"); +EXTERN char_u e_positive[] INIT(="Argument must be positive"); +EXTERN char_u e_quickfix[] INIT(="No Errors"); +EXTERN char_u e_re_damg[] INIT(="Damaged match string"); +EXTERN char_u e_re_corr[] INIT(="Corrupted regexp program"); +EXTERN char_u e_readonly[] INIT(="'readonly' option is set (use ! to override)"); +EXTERN char_u e_readerrf[] INIT(="Error while reading errorfile"); +EXTERN char_u e_scroll[] INIT(="Invalid scroll size"); +EXTERN char_u e_toocompl[] INIT(="Command too complex"); +EXTERN char_u e_toombra[] INIT(="Too many ("); +EXTERN char_u e_toomket[] INIT(="Too many )"); +EXTERN char_u e_toomsbra[] INIT(="Too many ["); +EXTERN char_u e_toolong[] INIT(="Command too long"); +EXTERN char_u e_toomany[] INIT(="Too many file names"); +EXTERN char_u e_trailing[] INIT(="Trailing characters"); +EXTERN char_u e_umark[] INIT(="Unknown mark"); +EXTERN char_u e_unknown[] INIT(="Unknown"); +EXTERN char_u e_write[] INIT(="Error while writing"); +EXTERN char_u e_zerocount[] INIT(="Zero count"); diff --git a/usr.bin/vim/gui.c b/usr.bin/vim/gui.c new file mode 100644 index 00000000000..9c84ea9beca --- /dev/null +++ b/usr.bin/vim/gui.c @@ -0,0 +1,2024 @@ +/* $OpenBSD: gui.c,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * GUI/Motif support by Robert Webb + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +/* Structure containing all the GUI information */ +Gui gui; + +/* Set to TRUE after adding/removing menus to ensure they are updated */ +int force_menu_update = FALSE; + + +static void gui_check_screen __ARGS((void)); +static void gui_outstr __ARGS((char_u *, int)); +static void gui_update_selection __ARGS((void)); +static int gui_get_menu_cmd_modes __ARGS((char_u *, int, int *, int *)); +static int gui_add_menu_path __ARGS((char_u *, int, void (*)(), char_u *, int)); +static int gui_remove_menu __ARGS((GuiMenu **, char_u *, int)); +static void gui_free_menu __ARGS((GuiMenu *)); +static void gui_free_menu_string __ARGS((GuiMenu *, int)); +static int gui_show_menus __ARGS((char_u *, int)); +static void gui_show_menus_recursive __ARGS((GuiMenu *, int, int)); +static char_u *gui_menu_name_skip __ARGS((char_u *name)); +static void gui_create_initial_menus __ARGS((GuiMenu *, GuiMenu *)); +static void gui_update_scrollbars __ARGS((void)); +static void gui_update_horiz_scrollbar __ARGS((void)); + +/* + * The Athena scrollbars can move the thumb to after the end of the scrollbar, + * this makes the thumb indicate the part of the text that is shown. Motif + * can't do this. + */ +#ifdef USE_GUI_ATHENA +# define SCROLL_PAST_END +#endif + +/* + * gui_start -- Called when user wants to start the GUI. + */ + void +gui_start() +{ + char_u *old_term; + + old_term = strsave(term_strings[KS_NAME]); + mch_setmouse(FALSE); /* first switch mouse off */ + + /* set_termname() will call gui_init() to start the GUI */ + termcapinit((char_u *)"builtin_gui"); + + if (!gui.in_use) /* failed to start GUI */ + termcapinit(old_term); + + vim_free(old_term); + + /* + * Quit the current process and continue in the child. + * Makes "gvim file" disconnect from the shell it was started in. + * Don't do this when Vim was started with "-f" or the 'f' flag is present + * in 'guioptions'. + */ + if (gui.in_use && gui.dofork && + vim_strchr(p_guioptions, GO_FORG) == NULL && fork() > 0) + exit(0); +} + +/* + * Call this when vim starts up, whether or not the GUI is started + */ + void +gui_prepare(argc, argv) + int *argc; + char **argv; +{ + /* Menu items may be added before the GUI is started, so set this now */ + gui.root_menu = NULL; + gui.in_use = FALSE; /* No GUI yet (maybe later) */ + gui.starting = FALSE; /* No GUI yet (maybe later) */ + gui.dofork = TRUE; /* default is to use fork() */ + gui_mch_prepare(argc, argv); +} + +static struct default_menu +{ + char *name; /* name of menu item */ + int mode; /* mode where menu is valid */ + char *command; /* resulting command */ +} default_menus[] = +{ + /* Help menu. Some reason Motif is happier if this is added first. */ + {"Help.Overview ", MENU_NORMAL_MODE, ":help\r"}, + {"Help.Overview ", MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE, + "\033:help\r"}, + {"Help.How to\\.\\.\\.",MENU_NORMAL_MODE, ":help how_to\r"}, + {"Help.How to\\.\\.\\.",MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE, + "\033:help how_to\r"}, + {"Help.GUI", MENU_NORMAL_MODE, ":help gui\r"}, + {"Help.GUI", MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE, + "\033:help gui\r"}, + {"Help.Version", MENU_NORMAL_MODE, ":version\r"}, + {"Help.Version", MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE, + "\033:version\r"}, + {"Help.Credits", MENU_NORMAL_MODE, ":help credits\r"}, + {"Help.Credits", MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE, + "\033:help credits\r"}, + {"Help.Copying", MENU_NORMAL_MODE, ":help uganda\r"}, + {"Help.Copying", MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE, + "\033:help uganda\r"}, + + /* File menu */ + {"File.Save :w", MENU_NORMAL_MODE, ":w\r"}, + {"File.Save :w", MENU_INSERT_MODE, "\017:w\r"}, + + {"File.Close :q", MENU_NORMAL_MODE, ":q\r"}, + {"File.Close :q", MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE, + "\033:q\r"}, + + {"File.Quit :qa", MENU_NORMAL_MODE, ":qa\r"}, + {"File.Quit :qa", MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE, + "\033:qa\r"}, + + {"File.Save-Quit :wqa",MENU_NORMAL_MODE, ":wqa\r"}, + {"File.Save-Quit :wqa",MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE, + "\033:wqa\r"}, + + /* Edit menu */ + {"Edit.Undo", MENU_NORMAL_MODE, "u"}, + {"Edit.Redo", MENU_NORMAL_MODE, "\022"}, + + {"Edit.Cut", MENU_VISUAL_MODE, "x"}, + {"Edit.Copy", MENU_VISUAL_MODE, "y"}, + {"Edit.Put Before", MENU_NORMAL_MODE, "[p"}, + {"Edit.Put Before", MENU_INSERT_MODE, "\017[p"}, + {"Edit.Put After", MENU_NORMAL_MODE, "]p"}, + {"Edit.Put After", MENU_INSERT_MODE, "\017]p"}, + {"Edit.Paste", MENU_NORMAL_MODE, "i\022*\033"}, /* CTRL-R * */ + {"Edit.Paste", MENU_INSERT_MODE|MENU_CMDLINE_MODE, + "\022*"}, /* CTRL-R * */ + {NULL, 0, NULL} +}; + +/* + * This is the call which starts the GUI. + */ + void +gui_init() +{ + char_u *env_str; + int i; + + gui.dying = FALSE; + gui.in_focus = FALSE; + gui.dragged_sb = SB_NONE; + gui.dragged_wp = NULL; + gui.col = gui.num_cols = 0; + gui.row = gui.num_rows = 0; + + /* Initialise gui.cursor_row: */ + INVALIDATE_CURSOR(); + gui.scroll_region_top = 0; + gui.scroll_region_bot = Rows - 1; + gui.highlight_mask = HL_NORMAL; + gui.char_width = 0; + gui.char_height = 0; + gui.char_ascent = 0; + gui.border_width = 0; + + gui.selection.owned = FALSE; + gui.selection.start.lnum = 0; + gui.selection.start.col = 0; + gui.selection.end.lnum = 0; + gui.selection.end.col = 0; + gui.selection.state = SELECT_CLEARED; + + gui.root_menu = NULL; + gui.menu_is_active = TRUE; /* default: include menu */ + + gui.scrollbar_width = SB_DEFAULT_WIDTH; + gui.menu_height = MENU_DEFAULT_HEIGHT; + for (i = 0; i < 3; i++) + gui.new_sb[i] = FALSE; + + gui.prev_wrap = -1; + + for (i = 0; default_menus[i].name != NULL; ++i) + gui_add_menu_path((char_u *)default_menus[i].name, + default_menus[i].mode, gui_menu_cb, + (char_u *)default_menus[i].command, TRUE); + + /* + * Switch on the mouse by default. + * This can be changed in the .gvimrc. + */ + set_string_option((char_u *)"mouse", -1, (char_u *)"a", TRUE); + + /* + * Get system wide defaults for gvim (Unix only) + */ +#ifdef HAVE_CONFIG_H + do_source(sys_gvimrc_fname, FALSE); +#endif + + /* + * Try to read GUI initialization commands from the following places: + * - environment variable GVIMINIT + * - the user gvimrc file (~/.gvimrc for Unix) + * The first that exists is used, the rest is ignored. + */ + if ((env_str = vim_getenv((char_u *)"GVIMINIT")) != NULL && *env_str != NUL) + { + sourcing_name = (char_u *)"GVIMINIT"; + do_cmdline(env_str, TRUE, TRUE); + sourcing_name = NULL; + } + else + do_source((char_u *)USR_GVIMRC_FILE, FALSE); + + /* + * Read initialization commands from ".gvimrc" in current directory. This + * is only done if the 'exrc' option is set. Because of security reasons + * we disallow shell and write commands now, except for unix if the file is + * owned by the user or 'secure' option has been reset in environment of + * global ".gvimrc". Only do this if GVIMRC_FILE is not the same as + * USR_GVIMRC_FILE or sys_gvimrc_fname. + */ + if (p_exrc) + { +#ifdef UNIX + { + struct stat s; + + /* if ".gvimrc" file is not owned by user, set 'secure' mode */ + if (stat(GVIMRC_FILE, &s) || s.st_uid != getuid()) + secure = p_secure; + } +#else + secure = p_secure; +#endif + + i = FAIL; + if (fullpathcmp((char_u *)USR_GVIMRC_FILE, + (char_u *)GVIMRC_FILE) != FPC_SAME +#ifdef HAVE_CONFIG_H + && fullpathcmp(sys_gvimrc_fname, + (char_u *)GVIMRC_FILE) != FPC_SAME +#endif + ) + i = do_source((char_u *)GVIMRC_FILE, FALSE); + } + + /* + * Actually start the GUI itself. + */ + gui.in_use = TRUE; /* Must be set after menus have been set up */ + if (gui_mch_init() == FAIL) + { + gui.in_use = FALSE; + return; + } + + maketitle(); + + gui_create_initial_menus(gui.root_menu, NULL); +} + + void +gui_exit() +{ + gui.in_use = FALSE; + gui_mch_exit(); +} + +/* + * Set the font. Uses the 'font' option. The first font name that works is + * used. If none is found, use the default font. + */ + int +gui_init_font() +{ +#define FONTLEN 100 + char_u *font_list; + char_u font_name[FONTLEN]; + + if (!gui.in_use) + return FAIL; + + for (font_list = p_guifont; *font_list != NUL; ) + { + /* Isolate one font name */ + (void)copy_option_part(&font_list, font_name, FONTLEN, ","); + if (gui_mch_init_font(font_name) == OK) + return OK; + } + + /* + * Couldn't load any font in 'font', tell gui_mch_init_font() to try and + * find a font we can load. + */ + return gui_mch_init_font(NULL); +} + + void +gui_set_cursor(row, col) + int row; + int col; +{ + gui.row = row; + gui.col = col; +} + +/* + * gui_check_screen - check if the cursor is on the screen. + */ + static void +gui_check_screen() +{ + if (gui.row >= Rows) + gui.row = Rows - 1; + if (gui.col >= Columns) + gui.col = Columns - 1; + if (gui.cursor_row >= Rows || gui.cursor_col >= Columns) + INVALIDATE_CURSOR(); +} + + void +gui_update_cursor() +{ + gui_check_screen(); + if (gui.row != gui.cursor_row || gui.col != gui.cursor_col) + { + gui_undraw_cursor(); + gui.cursor_row = gui.row; + gui.cursor_col = gui.col; + gui_mch_draw_cursor(); + } +} + +/* + * Should be called after the GUI window has been resized. Its arguments are + * the new width and height of the window in pixels. + */ + void +gui_resize_window(pixel_width, pixel_height) + int pixel_width; + int pixel_height; +{ + gui.num_cols = (pixel_width - 2 * gui.border_offset) / gui.char_width; + gui.num_rows = (pixel_height - 2 * gui.border_offset) / gui.char_height; + + gui_reset_scroll_region(); + /* + * At the "more" prompt there is no redraw, put the cursor at the last + * line here (why does it have to be one row too low???). + */ + if (State == ASKMORE) + gui.row = gui.num_rows; + + if (gui.num_rows != screen_Rows || gui.num_cols != screen_Columns) + set_winsize(0, 0, FALSE); + gui_update_cursor(); +} + +/* + * Make scroll region cover whole screen. + */ + void +gui_reset_scroll_region() +{ + gui.scroll_region_top = 0; + gui.scroll_region_bot = gui.num_rows - 1; +} + + void +gui_start_highlight(mask) + long_u mask; +{ + gui.highlight_mask |= mask; +} + + void +gui_stop_highlight(mask) + long_u mask; +{ + gui.highlight_mask &= ~mask; +} + + void +gui_write(s, len) + char_u *s; + int len; +{ + char_u *p; + int arg1 = 0, arg2 = 0; + +/* #define DEBUG_GUI_WRITE */ +#ifdef DEBUG_GUI_WRITE + { + int i; + char_u *str; + + printf("gui_write(%d):\n ", len); + for (i = 0; i < len; i++) + if (s[i] == ESC) + { + if (i != 0) + printf("\n "); + printf(""); + } + else + { + str = transchar(s[i]); + if (str[0] && str[1]) + printf("<%s>", (char *)str); + else + printf("%s", (char *)str); + } + printf("\n"); + } +#endif + while (len) + { + if (s[0] == '\n') + { + len--; + s++; + gui.col = 0; + if (gui.row < gui.scroll_region_bot) + gui.row++; + else + gui_mch_delete_lines(gui.scroll_region_top, 1); + } + else if (s[0] == '\r') + { + len--; + s++; + gui.col = 0; + } + else if (s[0] == Ctrl('G')) /* Beep */ + { + gui_mch_beep(); + len--; + s++; + } + else if (s[0] == ESC && s[1] == '|') + { + p = s + 2; + if (isdigit(*p)) + { + arg1 = getdigits(&p); + if (p > s + len) + break; + if (*p == ';') + { + ++p; + arg2 = getdigits(&p); + if (p > s + len) + break; + } + } + switch (*p) + { + case 'C': /* Clear screen */ + gui_mch_clear_block(0, 0, Rows - 1, Columns - 1); + break; + case 'M': /* Move cursor */ + gui_set_cursor(arg1, arg2); + break; + case 'R': /* Set scroll region */ + if (arg1 < arg2) + { + gui.scroll_region_top = arg1; + gui.scroll_region_bot = arg2; + } + else + { + gui.scroll_region_top = arg2; + gui.scroll_region_bot = arg1; + } + break; + case 'd': /* Delete line */ + gui_mch_delete_lines(gui.row, 1); + break; + case 'D': /* Delete lines */ + gui_mch_delete_lines(gui.row, arg1); + break; + case 'i': /* Insert line */ + gui_mch_insert_lines(gui.row, 1); + break; + case 'I': /* Insert lines */ + gui_mch_insert_lines(gui.row, arg1); + break; + case '$': /* Clear to end-of-line */ + gui_mch_clear_block(gui.row, gui.col, gui.row, Columns - 1); + break; + case 'h': /* Turn on highlighting */ + gui_start_highlight(arg1); + break; + case 'H': /* Turn off highlighting */ + gui_stop_highlight(arg1); + break; + case 'f': /* flash the window (visual bell) */ + gui_mch_flash(); + break; + default: + p = s + 1; /* Skip the ESC */ + break; + } + len -= ++p - s; + s = p; + } + else if (s[0] < 0x20) /* Ctrl character, shouldn't happen */ + { + /* + * For some reason vim sends me a ^M after hitting return on the + * ':' line. Make sure we ignore this here. + */ + len--; /* Skip this char */ + s++; + } + else + { + p = s; + while (len && *p >= 0x20) + { + len--; + p++; + } + gui_outstr(s, p - s); + s = p; + } + } + gui_update_cursor(); + gui_update_scrollbars(); + gui_update_horiz_scrollbar(); + + /* + * We need to make sure this is cleared since Athena doesn't tell us when + * he is done dragging. + */ + gui.dragged_sb = SB_NONE; + + if (vim_strchr(p_guioptions, GO_ASEL) != NULL) + gui_update_selection(); + gui_mch_flush(); /* In case vim decides to take a nap */ +} + + static void +gui_outstr(s, len) + char_u *s; + int len; +{ + int this_len; + + if (len == 0) + return; + + if (len < 0) + len = STRLEN(s); + + while (gui.col + len > Columns) + { + this_len = Columns - gui.col; + gui_mch_outstr_nowrap(s, this_len, TRUE, FALSE); + s += this_len; + len -= this_len; + } + gui_mch_outstr_nowrap(s, len, TRUE, FALSE); +} + +/* + * Un-draw the cursor. Actually this just redraws the character at the given + * position. + */ + void +gui_undraw_cursor() +{ + if (IS_CURSOR_VALID()) + gui_redraw_block(gui.cursor_row, gui.cursor_col, gui.cursor_row, + gui.cursor_col); +} + + void +gui_redraw(x, y, w, h) + int x; + int y; + int w; + int h; +{ + int row1, col1, row2, col2; + + row1 = Y_2_ROW(y); + col1 = X_2_COL(x); + row2 = Y_2_ROW(y + h - 1); + col2 = X_2_COL(x + w - 1); + + gui_redraw_block(row1, col1, row2, col2); + + /* We may need to redraw the cursor */ + gui_update_cursor(); +} + + void +gui_redraw_block(row1, col1, row2, col2) + int row1; + int col1; + int row2; + int col2; +{ + int old_row, old_col; + long_u old_hl_mask; + char_u *screenp, *attrp, first_attr; + int idx, len; + + /* Don't try to draw outside the window! */ + /* Check everything, strange values may be caused by big border width */ + col1 = check_col(col1); + col2 = check_col(col2); + row1 = check_row(row1); + row2 = check_row(row2); + + /* Don't try to update when NextScreen is not valid */ + if (!screen_cleared || NextScreen == NULL) + return; + + /* Remember where our cursor was */ + old_row = gui.row; + old_col = gui.col; + old_hl_mask = gui.highlight_mask; + + for (gui.row = row1; gui.row <= row2; gui.row++) + { + gui.col = col1; + screenp = LinePointers[gui.row] + gui.col; + attrp = screenp + Columns; + len = col2 - col1 + 1; + while (len > 0) + { + switch (attrp[0]) + { + case CHAR_INVERT: + gui.highlight_mask = HL_INVERSE; + break; + case CHAR_UNDERL: + gui.highlight_mask = HL_UNDERLINE; + break; + case CHAR_BOLD: + gui.highlight_mask = HL_BOLD; + break; + case CHAR_STDOUT: + gui.highlight_mask = HL_STANDOUT; + break; + case CHAR_ITALIC: + gui.highlight_mask = HL_ITAL; + break; + case CHAR_NORMAL: + default: + gui.highlight_mask = HL_NORMAL; + break; + } + first_attr = attrp[0]; + for (idx = 0; len > 0 && attrp[idx] == first_attr; idx++) + --len; + gui_mch_outstr_nowrap(screenp, idx, FALSE, FALSE); + screenp += idx; + attrp += idx; + } + } + + /* Put the cursor back where it was */ + gui.row = old_row; + gui.col = old_col; + gui.highlight_mask = old_hl_mask; +} + +/* + * Check bounds for column number + */ + int +check_col(col) + int col; +{ + if (col < 0) + return 0; + if (col >= (int)Columns) + return (int)Columns - 1; + return col; +} + +/* + * Check bounds for row number + */ + int +check_row(row) + int row; +{ + if (row < 0) + return 0; + if (row >= (int)Rows) + return (int)Rows - 1; + return row; +} + +/* + * Generic mouse support function. Add a mouse event to the input buffer with + * the given properties. + * button --- may be any of MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT, + * MOUSE_DRAG, or MOUSE_RELEASE. + * x, y --- Coordinates of mouse in pixels. + * repeated_click --- TRUE if this click comes only a short time after a + * previous click. + * modifiers --- Bit field which may be any of the following modifiers + * or'ed together: MOUSE_SHIFT | MOUSE_CTRL | MOUSE_ALT. + * This function will ignore drag events where the mouse has not moved to a new + * character. + */ + void +gui_send_mouse_event(button, x, y, repeated_click, modifiers) + int button; + int x; + int y; + int repeated_click; + int_u modifiers; +{ + static int prev_row = 0, prev_col = 0; + static int prev_button = -1; + static linenr_t prev_topline = 0; + static int num_clicks = 1; + char_u string[6]; + int row, col; + + row = Y_2_ROW(y); + col = X_2_COL(x); + + /* + * If we are dragging and the mouse hasn't moved far enough to be on a + * different character, then don't send an event to vim. + */ + if (button == MOUSE_DRAG && row == prev_row && col == prev_col) + return; + + /* + * If topline has changed (window scrolled) since the last click, reset + * repeated_click, because we don't want starting Visual mode when + * clicking on a different character in the text. + */ + if (curwin->w_topline != prev_topline) + repeated_click = FALSE; + + string[0] = CSI; /* this sequence is recognized by check_termcode() */ + string[1] = KS_MOUSE; + string[2] = K_FILLER; + if (button != MOUSE_DRAG && button != MOUSE_RELEASE) + { + if (repeated_click) + { + /* + * Handle multiple clicks. They only count if the mouse is still + * pointing at the same character. + */ + if (button != prev_button || row != prev_row || col != prev_col) + num_clicks = 1; + else if (++num_clicks > 4) + num_clicks = 1; + } + else + num_clicks = 1; + prev_button = button; + prev_topline = curwin->w_topline; + + string[3] = (char_u)(button | 0x20); + SET_NUM_MOUSE_CLICKS(string[3], num_clicks); + } + else + string[3] = (char_u)button; + + string[3] |= modifiers; + string[4] = (char_u)(col + ' ' + 1); + string[5] = (char_u)(row + ' ' + 1); + add_to_input_buf(string, 6); + + prev_row = row; + prev_col = col; +} + +/* + * Selection stuff, for cutting and pasting text to other windows. + */ + +/* + * Check whether the VIsual area has changed, and if so try to become the owner + * of the selection, and free any old converted selection we may still have + * lying around. If the VIsual mode has ended, make a copy of what was + * selected so we can still give it to others. Will probably have to make sure + * this is called whenever VIsual mode is ended. + */ + static void +gui_update_selection() +{ + /* If visual mode is only due to a redo command ("."), then ignore it */ + if (redo_VIsual_busy) + return; + if (!VIsual_active) + { + gui_mch_clear_selection(); + gui.selection.start = gui.selection.end = VIsual; + } + else if (lt(VIsual, curwin->w_cursor)) + { + if (!equal(gui.selection.start, VIsual) || + !equal(gui.selection.end, curwin->w_cursor)) + { + gui_mch_clear_selection(); + gui.selection.start = VIsual; + gui.selection.end = curwin->w_cursor; + gui_free_selection(); + gui_own_selection(); + } + } + else + { + if (!equal(gui.selection.start, curwin->w_cursor) || + !equal(gui.selection.end, VIsual)) + { + gui_mch_clear_selection(); + gui.selection.start = curwin->w_cursor; + gui.selection.end = VIsual; + gui_free_selection(); + gui_own_selection(); + } + } +} + + void +gui_own_selection() +{ + /* + * Also want to check somehow that we are reading from the keyboard rather + * than a mapping etc. + */ + if (!gui.selection.owned && gui_mch_own_selection()) + { + gui_free_selection(); + gui.selection.owned = TRUE; + } +} + + void +gui_lose_selection() +{ + gui_free_selection(); + gui.selection.owned = FALSE; + gui_mch_lose_selection(); +} + + void +gui_copy_selection() +{ + if (VIsual_active) + { + if (vim_strchr(p_guioptions, GO_ASEL) == NULL) + gui_update_selection(); + gui_own_selection(); + if (gui.selection.owned) + gui_get_selection(); + } +} + + void +gui_auto_select() +{ + if (vim_strchr(p_guioptions, GO_ASEL) != NULL) + gui_copy_selection(); +} + +/* + * Menu stuff. + */ + + void +gui_menu_cb(menu) + GuiMenu *menu; +{ + char_u bytes[3 + sizeof(long_u)]; + + bytes[0] = CSI; + bytes[1] = KS_MENU; + bytes[2] = K_FILLER; + add_long_to_buf((long_u)menu, bytes + 3); + add_to_input_buf(bytes, 3 + sizeof(long_u)); +} + +/* + * Return the index into the menu->strings or menu->noremap arrays for the + * current state. Returns MENU_INDEX_INVALID if there is no mapping for the + * given menu in the current mode. + */ + int +gui_get_menu_index(menu, state) + GuiMenu *menu; + int state; +{ + int idx; + + if (VIsual_active) + idx = MENU_INDEX_VISUAL; + else if ((state & NORMAL)) + idx = MENU_INDEX_NORMAL; + else if ((state & INSERT)) + idx = MENU_INDEX_INSERT; + else if ((state & CMDLINE)) + idx = MENU_INDEX_CMDLINE; + else + idx = MENU_INDEX_INVALID; + + if (idx != MENU_INDEX_INVALID && menu->strings[idx] == NULL) + idx = MENU_INDEX_INVALID; + return idx; +} + +/* + * Return the modes specified by the given menu command (eg :menu! returns + * MENU_CMDLINE_MODE | MENU_INSERT_MODE). If noremap is not NULL, then the + * flag it points to is set according to whether the command is a "nore" + * command. If unmenu is not NULL, then the flag it points to is set + * according to whether the command is an "unmenu" command. + */ + static int +gui_get_menu_cmd_modes(cmd, force, noremap, unmenu) + char_u *cmd; + int force; /* Was there a "!" after the command? */ + int *noremap; + int *unmenu; +{ + int modes = 0x0; + + if (*cmd == 'n' && cmd[1] != 'o') /* nmenu, nnoremenu */ + { + modes |= MENU_NORMAL_MODE; + cmd++; + } + else if (*cmd == 'v') /* vmenu, vnoremenu */ + { + modes |= MENU_VISUAL_MODE; + cmd++; + } + else if (*cmd == 'i') /* imenu, inoremenu */ + { + modes |= MENU_INSERT_MODE; + cmd++; + } + else if (*cmd == 'c') /* cmenu, cnoremenu */ + { + modes |= MENU_CMDLINE_MODE; + cmd++; + } + else if (force) /* menu!, noremenu! */ + modes |= MENU_INSERT_MODE | MENU_CMDLINE_MODE; + else /* menu, noremenu */ + modes |= MENU_NORMAL_MODE | MENU_VISUAL_MODE; + + if (noremap != NULL) + *noremap = (*cmd == 'n'); + if (unmenu != NULL) + *unmenu = (*cmd == 'u'); + return modes; +} + +/* + * Do the :menu commands. + */ + void +gui_do_menu(cmd, arg, force) + char_u *cmd; + char_u *arg; + int force; +{ + char_u *menu_path; + int modes; + char_u *map_to; + int noremap; + int unmenu; + char_u *map_buf; + + modes = gui_get_menu_cmd_modes(cmd, force, &noremap, &unmenu); + menu_path = arg; + if (*menu_path == NUL) + { + gui_show_menus(menu_path, modes); + return; + } + while (*arg && !vim_iswhite(*arg)) + { + if ((*arg == '\\' || *arg == Ctrl('V')) && arg[1] != NUL) + arg++; + arg++; + } + if (*arg != NUL) + *arg++ = NUL; + arg = skipwhite(arg); + map_to = arg; + if (*map_to == NUL && !unmenu) + { + gui_show_menus(menu_path, modes); + return; + } + else if (*map_to != NUL && unmenu) + { + EMSG("Trailing characters"); + return; + } + if (unmenu) + { + if (STRCMP(menu_path, "*") == 0) /* meaning: remove all menus */ + menu_path = (char_u *)""; + gui_remove_menu(&gui.root_menu, menu_path, modes); + } + else + { + /* Replace special key codes */ + map_to = replace_termcodes(map_to, &map_buf, FALSE); + gui_add_menu_path(menu_path, modes, gui_menu_cb, map_to, noremap); + vim_free(map_buf); + } +} + +/* + * Add the menu with the given name to the menu hierarchy + */ + static int +gui_add_menu_path(path_name, modes, call_back, call_data, noremap) + char_u *path_name; + int modes; + void (*call_back)(); + char_u *call_data; + int noremap; +{ + GuiMenu **menup; + GuiMenu *menu = NULL; + GuiMenu *parent; + char_u *p; + char_u *name; + int i; + + /* Make a copy so we can stuff around with it, since it could be const */ + path_name = strsave(path_name); + if (path_name == NULL) + return FAIL; + menup = &gui.root_menu; + parent = NULL; + name = path_name; + while (*name) + { + /* Get name of this element in the menu hierarchy */ + p = gui_menu_name_skip(name); + + /* See if it's already there */ + menu = *menup; + while (menu != NULL) + { + if (STRCMP(name, menu->name) == 0) + { + if (*p == NUL && menu->children != NULL) + { + EMSG("Menu path must not lead to a sub-menu"); + vim_free(path_name); + return FAIL; + } + else if (*p != NUL && menu->children == NULL) + { + EMSG("Part of menu-item path is not sub-menu"); + vim_free(path_name); + return FAIL; + } + break; + } + menup = &menu->next; + menu = menu->next; + } + if (menu == NULL) + { + if (*p == NUL && parent == NULL) + { + EMSG("Must not add menu items directly to menu bar"); + vim_free(path_name); + return FAIL; + } + + /* Not already there, so lets add it */ + menu = (GuiMenu *)alloc(sizeof(GuiMenu)); + if (menu == NULL) + { + vim_free(path_name); + return FAIL; + } + menu->modes = modes; + menu->name = strsave(name); + menu->cb = NULL; + for (i = 0; i < 4; i++) + { + menu->strings[i] = NULL; + menu->noremap[i] = FALSE; + } + menu->children = NULL; + menu->next = NULL; + if (gui.in_use) /* Otherwise it will be added when GUI starts */ + { + if (*p == NUL) + { + /* Real menu item, not sub-menu */ + gui_mch_add_menu_item(menu, parent); + + /* Want to update menus now even if mode not changed */ + force_menu_update = TRUE; + } + else + { + /* Sub-menu (not at end of path yet) */ + gui_mch_add_menu(menu, parent); + } + } + *menup = menu; + } + else + { + /* + * If this menu option was previously only available in other + * modes, then make sure it's available for this one now + */ + menu->modes |= modes; + } + + menup = &menu->children; + parent = menu; + name = p; + } + vim_free(path_name); + + if (menu != NULL) + { + menu->cb = call_back; + p = (call_data == NULL) ? NULL : strsave(call_data); + + /* May match more than one of these */ + if (modes & MENU_NORMAL_MODE) + { + gui_free_menu_string(menu, MENU_INDEX_NORMAL); + menu->strings[MENU_INDEX_NORMAL] = p; + menu->noremap[MENU_INDEX_NORMAL] = noremap; + } + if (modes & MENU_VISUAL_MODE) + { + gui_free_menu_string(menu, MENU_INDEX_VISUAL); + menu->strings[MENU_INDEX_VISUAL] = p; + menu->noremap[MENU_INDEX_VISUAL] = noremap; + } + if (modes & MENU_INSERT_MODE) + { + gui_free_menu_string(menu, MENU_INDEX_INSERT); + menu->strings[MENU_INDEX_INSERT] = p; + menu->noremap[MENU_INDEX_INSERT] = noremap; + } + if (modes & MENU_CMDLINE_MODE) + { + gui_free_menu_string(menu, MENU_INDEX_CMDLINE); + menu->strings[MENU_INDEX_CMDLINE] = p; + menu->noremap[MENU_INDEX_CMDLINE] = noremap; + } + } + return OK; +} + +/* + * Remove the (sub)menu with the given name from the menu hierarchy + * Called recursively. + */ + static int +gui_remove_menu(menup, name, modes) + GuiMenu **menup; + char_u *name; + int modes; +{ + GuiMenu *menu; + GuiMenu *child; + char_u *p; + + if (*menup == NULL) + return OK; /* Got to bottom of hierarchy */ + + /* Get name of this element in the menu hierarchy */ + p = gui_menu_name_skip(name); + + /* Find the menu */ + menu = *menup; + while (menu != NULL) + { + if (*name == NUL || STRCMP(name, menu->name) == 0) + { + if (*p != NUL && menu->children == NULL) + { + EMSG("Part of menu-item path is not sub-menu"); + return FAIL; + } + if ((menu->modes & modes) != 0x0) + { + if (gui_remove_menu(&menu->children, p, modes) == FAIL) + return FAIL; + } + else if (*name != NUL) + { + EMSG("Menu only exists in another mode"); + return FAIL; + } + + /* + * When name is empty, we are removing all menu items for the given + * modes, so keep looping, otherwise we are just removing the named + * menu item (which has been found) so break here. + */ + if (*name != NUL) + break; + + /* Remove the menu item for the given mode[s] */ + menu->modes &= ~modes; + + if (menu->modes == 0x0) + { + /* The menu item is no longer valid in ANY mode, so delete it */ + *menup = menu->next; + gui_free_menu(menu); + } + else + menup = &menu->next; + } + else + menup = &menu->next; + menu = *menup; + } + if (*name != NUL) + { + if (menu == NULL) + { + EMSG("No menu of that name"); + return FAIL; + } + + /* Recalculate modes for menu based on the new updated children */ + menu->modes = 0x0; + for (child = menu->children; child != NULL; child = child->next) + menu->modes |= child->modes; + if (menu->modes == 0x0) + { + /* The menu item is no longer valid in ANY mode, so delete it */ + *menup = menu->next; + gui_free_menu(menu); + } + } + + return OK; +} + +/* + * Free the given menu structure + */ + static void +gui_free_menu(menu) + GuiMenu *menu; +{ + int i; + + gui_mch_destroy_menu(menu); /* Free machine specific menu structures */ + vim_free(menu->name); + for (i = 0; i < 4; i++) + gui_free_menu_string(menu, i); + vim_free(menu); + + /* Want to update menus now even if mode not changed */ + force_menu_update = TRUE; +} + +/* + * Free the menu->string with the given index. + */ + static void +gui_free_menu_string(menu, idx) + GuiMenu *menu; + int idx; +{ + int count = 0; + int i; + + for (i = 0; i < 4; i++) + if (menu->strings[i] == menu->strings[idx]) + count++; + if (count == 1) + vim_free(menu->strings[idx]); + menu->strings[idx] = NULL; +} + +/* + * Show the mapping associated with a menu item or hierarchy in a sub-menu. + */ + static int +gui_show_menus(path_name, modes) + char_u *path_name; + int modes; +{ + char_u *p; + char_u *name; + GuiMenu *menu; + GuiMenu *parent = NULL; + + menu = gui.root_menu; + name = path_name = strsave(path_name); + if (path_name == NULL) + return FAIL; + + /* First, find the (sub)menu with the given name */ + while (*name) + { + p = gui_menu_name_skip(name); + while (menu != NULL) + { + if (STRCMP(name, menu->name) == 0) + { + /* Found menu */ + if (*p != NUL && menu->children == NULL) + { + EMSG("Part of menu-item path is not sub-menu"); + vim_free(path_name); + return FAIL; + } + else if ((menu->modes & modes) == 0x0) + { + EMSG("Menu only exists in another mode"); + vim_free(path_name); + return FAIL; + } + break; + } + menu = menu->next; + } + if (menu == NULL) + { + EMSG("No menu of that name"); + vim_free(path_name); + return FAIL; + } + name = p; + parent = menu; + menu = menu->children; + } + + /* Now we have found the matching menu, and we list the mappings */ + set_highlight('t'); /* Highlight title */ + start_highlight(); + MSG_OUTSTR("\n--- Menus ---"); + stop_highlight(); + + gui_show_menus_recursive(parent, modes, 0); + return OK; +} + +/* + * Recursively show the mappings associated with the menus under the given one + */ + static void +gui_show_menus_recursive(menu, modes, depth) + GuiMenu *menu; + int modes; + int depth; +{ + int i; + int bit; + + if (menu != NULL && (menu->modes & modes) == 0x0) + return; + + if (menu != NULL) + { + msg_outchar('\n'); + if (got_int) /* "q" hit for "--more--" */ + return; + for (i = 0; i < depth; i++) + MSG_OUTSTR(" "); + set_highlight('d'); /* Same as for directories!? */ + start_highlight(); + msg_outstr(menu->name); + stop_highlight(); + } + + if (menu != NULL && menu->children == NULL) + { + for (bit = 0; bit < 4; bit++) + if ((menu->modes & modes & (1 << bit)) != 0) + { + msg_outchar('\n'); + if (got_int) /* "q" hit for "--more--" */ + return; + for (i = 0; i < depth + 2; i++) + MSG_OUTSTR(" "); + msg_outchar("nvic"[bit]); + if (menu->noremap[bit]) + msg_outchar('*'); + else + msg_outchar(' '); + MSG_OUTSTR(" "); + msg_outtrans_special(menu->strings[bit], TRUE); + } + } + else + { + if (menu == NULL) + { + menu = gui.root_menu; + depth--; + } + else + menu = menu->children; + for (; menu != NULL; menu = menu->next) + gui_show_menus_recursive(menu, modes, depth + 1); + } +} + +/* + * Used when expanding menu names. + */ +static GuiMenu *expand_menu = NULL; +static int expand_modes = 0x0; + +/* + * Work out what to complete when doing command line completion of menu names. + */ + char_u * +gui_set_context_in_menu_cmd(cmd, arg, force) + char_u *cmd; + char_u *arg; + int force; +{ + char_u *after_dot; + char_u *p; + char_u *path_name = NULL; + char_u *name; + int unmenu; + GuiMenu *menu; + + expand_context = EXPAND_UNSUCCESSFUL; + + after_dot = arg; + for (p = arg; *p && !vim_iswhite(*p); ++p) + { + if ((*p == '\\' || *p == Ctrl('V')) && p[1] != NUL) + p++; + else if (*p == '.') + after_dot = p + 1; + } + if (*p == NUL) /* Complete the menu name */ + { + /* + * With :unmenu, you only want to match menus for the appropriate mode. + * With :menu though you might want to add a menu with the same name as + * one in another mode, so match menus fom other modes too. + */ + expand_modes = gui_get_menu_cmd_modes(cmd, force, NULL, &unmenu); + if (!unmenu) + expand_modes = MENU_ALL_MODES; + + menu = gui.root_menu; + if (after_dot != arg) + { + path_name = alloc(after_dot - arg); + if (path_name == NULL) + return NULL; + STRNCPY(path_name, arg, after_dot - arg - 1); + path_name[after_dot - arg - 1] = NUL; + } + name = path_name; + while (name != NULL && *name) + { + p = gui_menu_name_skip(name); + while (menu != NULL) + { + if (STRCMP(name, menu->name) == 0) + { + /* Found menu */ + if ((*p != NUL && menu->children == NULL) + || ((menu->modes & expand_modes) == 0x0)) + { + /* + * Menu path continues, but we have reached a leaf. + * Or menu exists only in another mode. + */ + vim_free(path_name); + return NULL; + } + break; + } + menu = menu->next; + } + if (menu == NULL) + { + /* No menu found with the name we were looking for */ + vim_free(path_name); + return NULL; + } + name = p; + menu = menu->children; + } + + expand_context = EXPAND_MENUS; + expand_pattern = after_dot; + expand_menu = menu; + } + else /* We're in the mapping part */ + expand_context = EXPAND_NOTHING; + return NULL; +} + +/* + * Expand the menu names. + */ + int +gui_ExpandMenuNames(prog, num_file, file) + regexp *prog; + int *num_file; + char_u ***file; +{ + GuiMenu *menu; + int round; + int count; + + /* + * round == 1: Count the matches. + * round == 2: Save the matches into the array. + */ + for (round = 1; round <= 2; ++round) + { + count = 0; + for (menu = expand_menu; menu != NULL; menu = menu->next) + if ((menu->modes & expand_modes) != 0x0 + && vim_regexec(prog, menu->name, TRUE)) + { + if (round == 1) + count++; + else + (*file)[count++] = strsave_escaped(menu->name, + (char_u *)" \t\\."); + } + if (round == 1) + { + *num_file = count; + if (count == 0 || (*file = (char_u **) + alloc((unsigned)(count * sizeof(char_u *)))) == NULL) + return FAIL; + } + } + return OK; +} + +/* + * Skip over this element of the menu path and return the start of the next + * element. Any \ and ^Vs are removed from the current element. + */ + static char_u * +gui_menu_name_skip(name) + char_u *name; +{ + char_u *p; + + for (p = name; *p && *p != '.'; p++) + if (*p == '\\' || *p == Ctrl('V')) + { + STRCPY(p, p + 1); + if (*p == NUL) + break; + } + if (*p) + *p++ = NUL; + return p; +} + +/* + * After we have started the GUI, then we can create any menus that have been + * defined. This is done once here. gui_add_menu_path() may have already been + * called to define these menus, and may be called again. This function calls + * itself recursively. Should be called at the top level with: + * gui_create_initial_menus(gui.root_menu, NULL); + */ + static void +gui_create_initial_menus(menu, parent) + GuiMenu *menu; + GuiMenu *parent; +{ + while (menu) + { + if (menu->children != NULL) + { + gui_mch_add_menu(menu, parent); + gui_create_initial_menus(menu->children, menu); + } + else + gui_mch_add_menu_item(menu, parent); + menu = menu->next; + } +} + + +/* + * Set which components are present. + * If "oldval" is not NULL, "oldval" is the previous value, the new * value is + * in p_guioptions. + */ + void +gui_init_which_components(oldval) + char_u *oldval; +{ + char_u *p; + int i; + int grey_old, grey_new; + char_u *temp; + + if (oldval != NULL) + { + /* + * Check if the menu's go from grey to non-grey or vise versa. + */ + grey_old = (vim_strchr(oldval, GO_GREY) != NULL); + grey_new = (vim_strchr(p_guioptions, GO_GREY) != NULL); + if (grey_old != grey_new) + { + temp = p_guioptions; + p_guioptions = oldval; + gui_x11_update_menus(MENU_ALL_MODES); + p_guioptions = temp; + } + } + + gui.menu_is_active = FALSE; + for (i = 0; i < 3; i++) + gui.which_scrollbars[i] = FALSE; + for (p = p_guioptions; *p; p++) + switch (*p) + { + case GO_LEFT: + gui.which_scrollbars[SB_LEFT] = TRUE; + break; + case GO_RIGHT: + gui.which_scrollbars[SB_RIGHT] = TRUE; + break; + case GO_BOT: + gui.which_scrollbars[SB_BOTTOM] = TRUE; + break; + case GO_MENUS: + gui.menu_is_active = TRUE; + break; + case GO_GREY: + /* make menu's have grey items, ignored here */ + break; + default: + /* Should give error message for internal error */ + break; + } + if (gui.in_use) + gui_mch_create_which_components(); +} + + +/* + * Vertical scrollbar stuff: + */ + + static void +gui_update_scrollbars() +{ + WIN *wp; + int worst_update = SB_UPDATE_NOTHING; + int val, size, max; + int which_sb; + int cmdline_height; + + /* + * Don't want to update a scrollbar while we're dragging it. But if we + * have both a left and right scrollbar, and we drag one of them, we still + * need to update the other one. + */ + if ((gui.dragged_sb == SB_LEFT || gui.dragged_sb == SB_RIGHT) && + (!gui.which_scrollbars[SB_LEFT] || !gui.which_scrollbars[SB_RIGHT])) + return; + + if (gui.dragged_sb == SB_LEFT || gui.dragged_sb == SB_RIGHT) + { + /* + * If we have two scrollbars and one of them is being dragged, just + * copy the scrollbar position from the dragged one to the other one. + */ + which_sb = SB_LEFT + SB_RIGHT - gui.dragged_sb; + if (gui.dragged_wp != NULL) + gui.dragged_wp->w_scrollbar.update[which_sb] = SB_UPDATE_VALUE; + else + gui.cmdline_sb.update[which_sb] = SB_UPDATE_VALUE; + + gui_mch_update_scrollbars(SB_UPDATE_VALUE, which_sb); + return; + } + + /* Return straight away if there is neither a left nor right scrollbar */ + if (!gui.which_scrollbars[SB_LEFT] && !gui.which_scrollbars[SB_RIGHT]) + return; + + cmdline_height = Rows; + for (wp = firstwin; wp; wp = wp->w_next) + { + cmdline_height -= wp->w_height + wp->w_status_height; + if (wp->w_buffer == NULL) /* just in case */ + continue; +#ifdef SCROLL_PAST_END + max = wp->w_buffer->b_ml.ml_line_count; +#else + max = wp->w_buffer->b_ml.ml_line_count + wp->w_height - 1; +#endif + if (max < 1) /* empty buffer */ + max = 1; + val = wp->w_topline; + size = wp->w_height; +#ifdef SCROLL_PAST_END + if (val > max) /* just in case */ + val = max; +#else + if (size > max) /* just in case */ + size = max; + if (val > max - size + 1) + { + val = max - size + 1; + if (val < 1) /* minimal value is 1 */ + val = 1; + } +#endif + if (size < 1 || wp->w_botline - 1 > max) + { + /* + * This can happen during changing files. Just don't update the + * scrollbar for now. + */ + } + else if (wp->w_scrollbar.height == 0) + { + /* Must be a new window */ + wp->w_scrollbar.update[SB_LEFT] = SB_UPDATE_CREATE; + wp->w_scrollbar.update[SB_RIGHT] = SB_UPDATE_CREATE; + wp->w_scrollbar.value = val; + wp->w_scrollbar.size = size; + wp->w_scrollbar.max = max; + wp->w_scrollbar.top = wp->w_winpos; + wp->w_scrollbar.height = wp->w_height; + wp->w_scrollbar.status_height = wp->w_status_height; + gui.num_scrollbars++; + worst_update = SB_UPDATE_CREATE; + } + else if (wp->w_scrollbar.top != wp->w_winpos + || wp->w_scrollbar.height != wp->w_height + || wp->w_scrollbar.status_height != wp->w_status_height) + { + /* Height of scrollbar has changed */ + wp->w_scrollbar.update[SB_LEFT] = SB_UPDATE_HEIGHT; + wp->w_scrollbar.update[SB_RIGHT] = SB_UPDATE_HEIGHT; + wp->w_scrollbar.value = val; + wp->w_scrollbar.size = size; + wp->w_scrollbar.max = max; + wp->w_scrollbar.top = wp->w_winpos; + wp->w_scrollbar.height = wp->w_height; + wp->w_scrollbar.status_height = wp->w_status_height; + if (worst_update < SB_UPDATE_HEIGHT) + worst_update = SB_UPDATE_HEIGHT; + } + else if (wp->w_scrollbar.value != val + || wp->w_scrollbar.size != size + || wp->w_scrollbar.max != max) + { + /* Thumb of scrollbar has moved */ + wp->w_scrollbar.update[SB_LEFT] = SB_UPDATE_VALUE; + wp->w_scrollbar.update[SB_RIGHT] = SB_UPDATE_VALUE; + wp->w_scrollbar.value = val; + wp->w_scrollbar.size = size; + wp->w_scrollbar.max = max; + if (worst_update < SB_UPDATE_VALUE) + worst_update = SB_UPDATE_VALUE; + } + + /* + * We may have just created the left scrollbar say, when we already had + * the right one. + */ + if (gui.new_sb[SB_LEFT]) + wp->w_scrollbar.update[SB_LEFT] = SB_UPDATE_CREATE; + if (gui.new_sb[SB_RIGHT]) + wp->w_scrollbar.update[SB_RIGHT] = SB_UPDATE_CREATE; + } + if (cmdline_height < 1) + cmdline_height = 1; /* Shouldn't happen, but just in case */ + + /* Check the command line scrollbar */ + if (gui.cmdline_sb.height != cmdline_height + || gui.cmdline_sb.status_height != lastwin->w_status_height) + { + /* Height of scrollbar has changed */ + gui.cmdline_sb.update[SB_LEFT] = SB_UPDATE_HEIGHT; + gui.cmdline_sb.update[SB_RIGHT] = SB_UPDATE_HEIGHT; + gui.cmdline_sb.value = 0; + gui.cmdline_sb.size = 1; /* No thumb */ + gui.cmdline_sb.max = 0; + gui.cmdline_sb.top = Rows - cmdline_height; + gui.cmdline_sb.height = cmdline_height; + gui.cmdline_sb.status_height = lastwin->w_status_height; + if (worst_update < SB_UPDATE_HEIGHT) + worst_update = SB_UPDATE_HEIGHT; + } + + /* + * If we have just created the left or right scrollbar-box, then we need to + * update the height of the command line scrollbar (it will already be + * created). + */ + if (gui.new_sb[SB_LEFT]) + { + gui.cmdline_sb.update[SB_LEFT] = SB_UPDATE_HEIGHT; + worst_update = SB_UPDATE_CREATE; + gui.new_sb[SB_LEFT] = FALSE; + } + if (gui.new_sb[SB_RIGHT]) + { + gui.cmdline_sb.update[SB_RIGHT] = SB_UPDATE_HEIGHT; + worst_update = SB_UPDATE_CREATE; + gui.new_sb[SB_RIGHT] = FALSE; + } + + if (worst_update != SB_UPDATE_NOTHING) + { + if (gui.which_scrollbars[SB_LEFT] && gui.dragged_sb != SB_LEFT) + gui_mch_update_scrollbars(worst_update, SB_LEFT); + if (gui.which_scrollbars[SB_RIGHT] && gui.dragged_sb != SB_RIGHT) + gui_mch_update_scrollbars(worst_update, SB_RIGHT); + } +} + +/* + * Scroll a window according to the values set in the globals current_scrollbar + * and scrollbar_value. Return TRUE if the cursor in the current window moved + * or FALSE otherwise. + */ + int +gui_do_scroll() +{ + WIN *wp, *old_wp; + int i; + FPOS old_cursor; + + for (wp = firstwin, i = 0; i < current_scrollbar; i++) + { + if (wp == NULL) + break; + wp = wp->w_next; + } + if (wp != NULL) + { + old_cursor = curwin->w_cursor; + old_wp = curwin; + curwin = wp; + curbuf = wp->w_buffer; + i = (long)scrollbar_value - (long)wp->w_topline; + if (i < 0) + scrolldown(-i); + else if (i > 0) + scrollup(i); + if (p_so) + cursor_correct(); + coladvance(curwin->w_curswant); + + curwin = old_wp; + curbuf = old_wp->w_buffer; + + if (wp == curwin) + cursupdate(); /* fix window for 'so' */ + wp->w_redr_type = VALID; + updateWindow(wp); /* update window, status line, and cmdline */ + + return !equal(curwin->w_cursor, old_cursor); + } + else + { + /* Command-line scrollbar, unimplemented */ + return FALSE; + } +} + + +/* + * Horizontal scrollbar stuff: + */ + + static void +gui_update_horiz_scrollbar() +{ + int value, size, max; + + if (!gui.which_scrollbars[SB_BOTTOM]) + return; + + if (gui.dragged_sb == SB_BOTTOM) + return; + + if (curwin->w_p_wrap && gui.prev_wrap) + return; + + /* + * It is possible for the cursor to be invalid if we're in the middle of + * something (like changing files). If so, don't do anything for now. + */ + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + return; + + if (curwin->w_p_wrap) + { + value = 0; + size = Columns; +#ifdef SCROLL_PAST_END + max = 0; +#else + max = Columns - 1; +#endif + } + else + { + value = curwin->w_leftcol; + size = Columns; +#ifdef SCROLL_PAST_END + max = gui_get_max_horiz_scroll(); +#else + max = gui_get_max_horiz_scroll() + Columns - 1; +#endif + } + gui_mch_update_horiz_scrollbar(value, size, max + 1); + gui.prev_wrap = curwin->w_p_wrap; +} + +/* + * Determine the maximum value for scrolling right. + */ + int +gui_get_max_horiz_scroll() +{ + int max = 0; + char_u *p; + + p = ml_get_curline(); + if (p[0] != NUL) + while (p[1] != NUL) /* Don't count last character */ + max += chartabsize(*p++, (colnr_t)max); + return max; +} + +/* + * Do a horizontal scroll. Return TRUE if the cursor moved, or FALSE otherwise + */ + int +gui_do_horiz_scroll() +{ + char_u *p; + int i; + int vcol; + int ret_val = FALSE; + + /* no wrapping, no scrolling */ + if (curwin->w_p_wrap) + return FALSE; + + curwin->w_leftcol = scrollbar_value; + + i = 0; + vcol = 0; + p = ml_get_curline(); + while (p[i] && i < curwin->w_cursor.col && vcol < curwin->w_leftcol) + vcol += chartabsize(p[i++], (colnr_t)vcol); + if (vcol < curwin->w_leftcol) + { + /* + * Cursor is on a character that is at least partly off the left hand + * side of the screen. + */ + while (p[i] && vcol < curwin->w_leftcol) + vcol += chartabsize(p[i++], (colnr_t)vcol); + curwin->w_cursor.col = i; + curwin->w_set_curswant = TRUE; + ret_val = TRUE; + } + + while (p[i] && i <= curwin->w_cursor.col + && vcol <= curwin->w_leftcol + Columns) + vcol += chartabsize(p[i++], (colnr_t)vcol); + if (vcol > curwin->w_leftcol + Columns) + { + /* + * Cursor is on a character that is at least partly off the right hand + * side of the screen. + */ + if (i < 2) + i = 0; + else + i -= 2; + curwin->w_cursor.col = i; + curwin->w_set_curswant = TRUE; + ret_val = TRUE; + } + updateScreen(NOT_VALID); + return ret_val; +} diff --git a/usr.bin/vim/gui.h b/usr.bin/vim/gui.h new file mode 100644 index 00000000000..5b16cd37e3c --- /dev/null +++ b/usr.bin/vim/gui.h @@ -0,0 +1,233 @@ +/* $OpenBSD: gui.h,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * Motif support by Robert Webb + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* For debugging */ +/* #define D(x) printf x; */ +#define D(x) + +#ifdef USE_GUI_MOTIF +# define USE_GUI_X11 +# include +#endif + +/* + * No, Athena doesn't work. Probably never will, there is probably a public + * domain X widget set that is more like Motif which should be used instead. + */ +#ifdef USE_GUI_ATHENA +# define USE_GUI_X11 +# include +# include +#endif + +/* + * These macros convert between character row/column and pixel coordinates. + * TEXT_X - Convert character column into X pixel coord for drawing strings. + * TEXT_Y - Convert character row into Y pixel coord for drawing strings. + * FILL_X - Convert character column into X pixel coord for filling the area + * under the character. + * FILL_Y - Convert character row into Y pixel coord for filling the area + * under the character. + * X_2_COL - Convert X pixel coord into character column. + * Y_2_ROW - Convert Y pixel coord into character row. + */ +#define TEXT_X(col) ((col) * gui.char_width + gui.border_offset) +#define TEXT_Y(row) ((row) * gui.char_height + gui.char_ascent \ + + gui.border_offset) +#define FILL_X(col) ((col) * gui.char_width + gui.border_offset) +#define FILL_Y(row) ((row) * gui.char_height + gui.border_offset) +#define X_2_COL(x) (((x) - gui.border_offset) / gui.char_width) +#define Y_2_ROW(y) (((y) - gui.border_offset) / gui.char_height) + +/* Menu modes */ +#define MENU_NORMAL_MODE 0x01 +#define MENU_VISUAL_MODE 0x02 +#define MENU_INSERT_MODE 0x04 +#define MENU_CMDLINE_MODE 0x08 +#define MENU_ALL_MODES 0x0f + +/* Indices into GuiMenu->strings[] and GuiMenu->noremap[] for each mode */ +#define MENU_INDEX_INVALID -1 +#define MENU_INDEX_NORMAL 0 +#define MENU_INDEX_VISUAL 1 +#define MENU_INDEX_INSERT 2 +#define MENU_INDEX_CMDLINE 3 + +/* Update types for scrollbars, getting more severe on the way down */ +#define SB_UPDATE_NOTHING 0 +#define SB_UPDATE_VALUE 1 +#define SB_UPDATE_HEIGHT 2 +#define SB_UPDATE_CREATE 3 + +/* Indices for arrays of scrollbars */ +#define SB_NONE -1 +#define SB_LEFT 0 +#define SB_RIGHT 1 +#define SB_BOTTOM 2 + +/* Default size of scrollbar */ +#define SB_DEFAULT_WIDTH 20 + +/* Default height of the menu bar */ +#define MENU_DEFAULT_HEIGHT 32 + +/* Highlighting attribute bits. */ +#define HL_NORMAL 0x00 +#define HL_INVERSE 0x01 +#define HL_BOLD 0x02 +#define HL_ITAL 0x04 +#define HL_UNDERLINE 0x08 +#define HL_STANDOUT 0x10 +#define HL_SELECTED 0x20 +#define HL_ALL 0x3f + +#ifdef USE_GUI_X11 + +/* Selection states for X11 selection. */ +#define SELECT_CLEARED 0 +#define SELECT_IN_PROGRESS 1 +#define SELECT_DONE 2 + +#define SELECT_MODE_CHAR 0 +#define SELECT_MODE_WORD 1 +#define SELECT_MODE_LINE 2 + +#endif + +/* + * When we know the cursor is no longer being displayed (eg it has been written + * over). + */ +#define INVALIDATE_CURSOR() (gui.cursor_row = -1) + +/* #define INVALIDATE_CURSOR() do{printf("Invalidate cursor %d\n", __LINE__); gui.cursor_row = -1;}while(0) */ + +/* + * For checking whether cursor needs redrawing, or whether it doesn't need + * undrawing. + */ +#define IS_CURSOR_VALID() (gui.cursor_row >= 0) + +typedef struct GuiSelection +{ + int owned; /* Flag: do we own the selection? */ + FPOS start; /* Start of selected area */ + FPOS end; /* End of selected area */ +#ifdef USE_GUI_X11 + Atom atom; /* Vim's own special selection format */ + short_u origin_row; + short_u origin_start_col; + short_u origin_end_col; + short_u word_start_col; + short_u word_end_col; + FPOS prev; /* Previous position */ + short_u state; /* Current selection state */ + short_u mode; /* Select by char, word, or line. */ +#endif +} GuiSelection; + +typedef struct GuiMenu +{ + int modes; /* Which modes is this menu visible for? */ + char_u *name; /* Name shown in menu */ + void (*cb)(); /* Call-back routine */ + char_u *strings[4]; /* Mapped string for each mode */ + int noremap[4]; /* A noremap flag for each mode */ + struct GuiMenu *children; /* Children of sub-menu */ + struct GuiMenu *next; /* Next item in menu */ +#ifdef USE_GUI_X11 + Widget id; /* Manage this to enable item */ + Widget submenu_id; /* If this is submenu, add children here */ +#endif +} GuiMenu; + +typedef struct GuiScrollbar +{ + int update[2]; /* What kind of update is required? */ + /* (For left & right scrollbars) */ + int value; /* Represents top line number visible */ + int size; /* Size of scrollbar thumb */ + int max; /* Number of lines in buffer */ + int top; /* Top of scroll bar (chars from row 0) */ + int height; /* Height of scroll bar (num rows) */ + int status_height; /* Height of status line */ +#ifdef USE_GUI_X11 + Widget id[2]; /* Id of real scroll bar (left & right) */ +#endif +} GuiScrollbar; + +typedef struct Gui +{ + int in_focus; /* Vim has input focus */ + int in_use; /* Is the GUI being used? */ + int starting; /* GUI will start in a little while */ + int dying; /* Is vim dying? Then output to terminal */ + int dofork; /* Use fork() when GUI is starting */ + int dragged_sb; /* Which scrollbar being dragged, if any? */ + struct window *dragged_wp; /* Which WIN's sb being dragged, if any? */ + int col; /* Current cursor column in GUI display */ + int row; /* Current cursor row in GUI display */ + int cursor_col; /* Physical cursor column in GUI display */ + int cursor_row; /* Physical cursor row in GUI display */ + int num_cols; /* Number of columns */ + int num_rows; /* Number of rows */ + int scroll_region_top; /* Top (first) line of scroll region */ + int scroll_region_bot; /* Bottom (last) line of scroll region */ + long_u highlight_mask; /* Highlight attribute mask */ + GuiSelection selection; /* Info about selected text */ + GuiMenu *root_menu; /* Root of menu hierarchy */ + int num_scrollbars; /* Number of scrollbars (= #windows + 1) */ + int scrollbar_width; /* Width of scrollbars */ + int menu_height; /* Height of the menu bar */ + int menu_is_active; /* TRUE if menu is present */ + GuiScrollbar cmdline_sb; /* Scroll bar for command line */ + /* (Other scrollbars in 'struct window') */ + int which_scrollbars[3];/* Which scrollbar boxes are active? */ + int new_sb[3]; /* Which scrollbar boxes are new? */ + int prev_wrap; /* For updating the horizontal scrollbar */ + int char_width; /* Width of char in pixels */ + int char_height; /* Height of char in pixels */ + int char_ascent; /* Ascent of char in pixels */ + int border_width; /* Width of our border around text area */ + int border_offset; /* Total pixel offset for all borders */ +#ifdef USE_GUI_X11 + Display *dpy; /* X display */ + Window wid; /* Window id of text area */ + int visibility; /* Is window partially/fully obscured? */ + GC text_gc; + GC back_gc; + GC invert_gc; + XFontStruct *norm_font; + XFontStruct *bold_font; + XFontStruct *ital_font; + XFontStruct *boldital_font; + Pixel back_pixel; /* Pixel value of background */ + Pixel norm_pixel; /* Pixel value of normal text */ + Pixel bold_pixel; /* Pixel value of bold text */ + Pixel ital_pixel; /* Pixel value of ital text */ + Pixel underline_pixel; /* Pixel value of underlined text */ + Pixel cursor_pixel; /* Pixel value of cursor */ + Pixel menu_fg_pixel; /* Pixel value of menu foregound */ + Pixel menu_bg_pixel; /* Pixel value of menu backgound */ + Pixel scroll_fg_pixel; /* Pixel value of scrollbar foreground */ + Pixel scroll_bg_pixel; /* Pixel value of scrollbar background */ + + /* X Resources */ + char_u *dflt_font; /* Resource font, used if 'font' not set */ + char_u *dflt_bold_fn; /* Resource bold font */ + char_u *dflt_ital_fn; /* Resource italic font */ + char_u *dflt_boldital_fn; /* Resource bold-italic font */ + char_u *geom; /* Geometry, eg "80x24" */ + Bool rev_video; /* Use reverse video? */ +#endif +} Gui; + +extern Gui gui; /* this is in gui.c */ +extern int force_menu_update; /* this is in gui.c */ diff --git a/usr.bin/vim/gui_at_sb.c b/usr.bin/vim/gui_at_sb.c new file mode 100644 index 00000000000..27f57263d5e --- /dev/null +++ b/usr.bin/vim/gui_at_sb.c @@ -0,0 +1,1160 @@ +/* $OpenBSD: gui_at_sb.c,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* vi:set ts=4 sw=4: */ +/* MODIFIED ATHENA SCROLLBAR (USING ARROWHEADS AT ENDS OF TRAVEL) */ +/* Modifications Copyright 1992 by Mitch Trachtenberg */ +/* Rights, permissions, and disclaimer of warranty are as in the */ +/* DEC and MIT notice below. */ +/* $XConsortium: Scrollbar.c,v 1.72 94/04/17 20:12:40 kaleb Exp $ */ + +/* + * Modified for Vim by Bill Foster and Bram Moolenaar + */ + +/*********************************************************** + +Copyright (c) 1987, 1988, 1994 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + + +Copyright 1987, 1988 by Digital Equipment Corporation, Maynard, Massachusetts. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Digital not be +used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +******************************************************************/ + +/* ScrollBar.c */ +/* created by weissman, Mon Jul 7 13:20:03 1986 */ +/* converted by swick, Thu Aug 27 1987 */ + +#include +#include + +#include +#include "vim.h" +#include "gui_at_sb.h" + +#include + +/* Private definitions. */ + +static char defaultTranslations[] = + ": NotifyScroll()\n\ + : MoveThumb() NotifyThumb() \n\ + : NotifyScroll()\n\ + : HandleThumb() \n\ + : HandleThumb() \n\ + : MoveThumb() NotifyThumb() \n\ + : EndScroll()"; + +static float floatZero = 0.0; + +#define Offset(field) XtOffsetOf(ScrollbarRec, field) + +static XtResource resources[] = +{ +/* {XtNscrollCursor, XtCCursor, XtRCursor, sizeof(Cursor), + Offset(scrollbar.cursor), XtRString, "crosshair"},*/ + {XtNlength, XtCLength, XtRDimension, sizeof(Dimension), + Offset(scrollbar.length), XtRImmediate, (XtPointer) 1}, + {XtNthickness, XtCThickness, XtRDimension, sizeof(Dimension), + Offset(scrollbar.thickness), XtRImmediate, (XtPointer) 14}, + {XtNorientation, XtCOrientation, XtROrientation, sizeof(XtOrientation), + Offset(scrollbar.orientation), XtRImmediate, (XtPointer) XtorientVertical}, + {XtNscrollProc, XtCCallback, XtRCallback, sizeof(XtPointer), + Offset(scrollbar.scrollProc), XtRCallback, NULL}, + {XtNthumbProc, XtCCallback, XtRCallback, sizeof(XtPointer), + Offset(scrollbar.thumbProc), XtRCallback, NULL}, + {XtNjumpProc, XtCCallback, XtRCallback, sizeof(XtPointer), + Offset(scrollbar.jumpProc), XtRCallback, NULL}, + {XtNthumb, XtCThumb, XtRBitmap, sizeof(Pixmap), + Offset(scrollbar.thumb), XtRImmediate, (XtPointer) XtUnspecifiedPixmap}, + {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel), + Offset(scrollbar.foreground), XtRString, XtDefaultForeground}, + {XtNshown, XtCShown, XtRFloat, sizeof(float), + Offset(scrollbar.shown), XtRFloat, (XtPointer)&floatZero}, + {XtNtopOfThumb, XtCTopOfThumb, XtRFloat, sizeof(float), + Offset(scrollbar.top), XtRFloat, (XtPointer)&floatZero}, + {XtNmaxOfThumb, XtCMaxOfThumb, XtRFloat, sizeof(float), + Offset(scrollbar.max), XtRFloat, (XtPointer)&floatZero}, + {XtNminimumThumb, XtCMinimumThumb, XtRDimension, sizeof(Dimension), + Offset(scrollbar.min_thumb), XtRImmediate, (XtPointer) 7}, + {XtNshadowWidth, XtCShadowWidth, XtRDimension, sizeof(Dimension), + Offset(scrollbar.shadow_width), XtRImmediate, (XtPointer) 1}, + {XtNtopShadowPixel, XtCTopShadowPixel, XtRPixel, sizeof(Pixel), + Offset(scrollbar.top_shadow_pixel), XtRString, XtDefaultBackground}, + {XtNbottomShadowPixel, XtCBottomShadowPixel, XtRPixel, sizeof(Pixel), + Offset(scrollbar.bot_shadow_pixel), XtRString, XtDefaultForeground} +}; +#undef Offset + +static void ClassInitialize __ARGS((void)); +static void Initialize __ARGS((Widget, Widget, ArgList, Cardinal *)); +static void Destroy __ARGS((Widget)); +static void Realize __ARGS((Widget, Mask *, XSetWindowAttributes *)); +static void Resize __ARGS((Widget)); +static void Redisplay __ARGS((Widget, XEvent *, Region)); +static Boolean SetValues __ARGS((Widget, Widget, Widget, ArgList, Cardinal *)); + +static void HandleThumb __ARGS((Widget, XEvent *, String *, Cardinal *)); +static void MoveThumb __ARGS((Widget, XEvent *, String *, Cardinal *)); +static void NotifyThumb __ARGS((Widget, XEvent *, String *, Cardinal *)); +static void NotifyScroll __ARGS((Widget, XEvent *, String *, Cardinal *)); +static void EndScroll __ARGS((Widget, XEvent *, String *, Cardinal *)); +static void _Xaw3dDrawShadows __ARGS((Widget, XEvent *, Region, Boolean)); +static void AllocTopShadowGC __ARGS((Widget)); +static void AllocBotShadowGC __ARGS((Widget)); + +static XtActionsRec actions[] = +{ + {"HandleThumb", HandleThumb}, + {"MoveThumb", MoveThumb}, + {"NotifyThumb", NotifyThumb}, + {"NotifyScroll", NotifyScroll}, + {"EndScroll", EndScroll} +}; + + +ScrollbarClassRec vim_scrollbarClassRec = +{ + { /* core fields */ + /* superclass */ (WidgetClass) &simpleClassRec, + /* class_name */ "Scrollbar", + /* size */ sizeof(ScrollbarRec), + /* class_initialize */ ClassInitialize, + /* class_part_init */ NULL, + /* class_inited */ FALSE, + /* initialize */ Initialize, + /* initialize_hook */ NULL, + /* realize */ Realize, + /* actions */ actions, + /* num_actions */ XtNumber(actions), + /* resources */ resources, + /* num_resources */ XtNumber(resources), + /* xrm_class */ NULLQUARK, + /* compress_motion */ TRUE, + /* compress_exposure*/ TRUE, + /* compress_enterleave*/ TRUE, + /* visible_interest */ FALSE, + /* destroy */ Destroy, + /* resize */ Resize, + /* expose */ Redisplay, + /* set_values */ SetValues, + /* set_values_hook */ NULL, + /* set_values_almost */ XtInheritSetValuesAlmost, + /* get_values_hook */ NULL, + /* accept_focus */ NULL, + /* version */ XtVersion, + /* callback_private */ NULL, + /* tm_table */ defaultTranslations, + /* query_geometry */ XtInheritQueryGeometry, + /* display_accelerator*/ XtInheritDisplayAccelerator, + /* extension */ NULL + }, + { /* simple fields */ + /* change_sensitive */ XtInheritChangeSensitive + }, + { /* scrollbar fields */ + /* ignore */ 0 + } + +}; + +WidgetClass vim_scrollbarWidgetClass = (WidgetClass)&vim_scrollbarClassRec; + +#define NoButton -1 +#define PICKLENGTH(widget, x, y) \ + ((widget->scrollbar.orientation == XtorientHorizontal) ? (x) : (y)) +#define MIN(x,y) ((x) < (y) ? (x) : (y)) +#define MAX(x,y) ((x) > (y) ? (x) : (y)) + +#define LINE_DELAY 300 +#define PAGE_DELAY 300 +#define LINE_REPEAT 50 +#define PAGE_REPEAT 250 + +static void ClassInitialize() +{ + XawInitializeWidgetSet(); + XtAddConverter( XtRString, XtROrientation, XmuCvtStringToOrientation, + (XtConvertArgList)NULL, (Cardinal)0 ); +} + +#define MARGIN(sbw) (sbw)->scrollbar.thickness + (sbw)->scrollbar.shadow_width + +static void FillArea(sbw, top, bottom, fill, draw_shadow) + ScrollbarWidget sbw; + Position top, bottom; + int fill; + int draw_shadow; +{ + int tlen = bottom - top; /* length of thumb in pixels */ + int sw, margin, floor; + int lx, ly, lw, lh; + + if (bottom <= 0 || bottom <= top) + return; + if ((sw = sbw->scrollbar.shadow_width) < 0) + sw = 0; + margin = MARGIN (sbw); + floor = sbw->scrollbar.length - margin + 2; + + if (sbw->scrollbar.orientation == XtorientHorizontal) + { + lx = ((top < margin) ? margin : top); + ly = sw; + lw = (((top + tlen) > floor) ? floor - top : tlen); + lh = sbw->core.height - 2 * sw; + } + else + { + lx = sw; + ly = ((top < margin) ? margin : top); + lw = sbw->core.width - 2 * sw; + lh = (((top + tlen) > floor) ? floor - top : tlen); + } + if (lh <= 0 || lw <= 0) + return; + + if (draw_shadow) + { + if (!sbw->scrollbar.orientation == XtorientHorizontal) + { + /* Top border */ + XDrawLine (XtDisplay ((Widget) sbw), XtWindow ((Widget) sbw), + sbw->scrollbar.top_shadow_GC, + lx, ly, lx + lw - 1, ly); + + /* Bottom border */ + XDrawLine (XtDisplay ((Widget) sbw), XtWindow ((Widget) sbw), + sbw->scrollbar.bot_shadow_GC, + lx, ly + lh - 1, lx + lw - 1, ly + lh - 1); + } + else + { + /* Left border */ + XDrawLine (XtDisplay ((Widget) sbw), XtWindow ((Widget) sbw), + sbw->scrollbar.top_shadow_GC, + lx, ly, lx, ly + lh - 1); + + /* Right border */ + XDrawLine (XtDisplay ((Widget) sbw), XtWindow ((Widget) sbw), + sbw->scrollbar.bot_shadow_GC, + lx + lw - 1, ly, lx + lw - 1, ly + lh - 1); + } + return; + } + + if (fill) + { + XFillRectangle(XtDisplay((Widget) sbw), XtWindow((Widget) sbw), + sbw->scrollbar.gc, + lx, ly, (unsigned int) lw, (unsigned int) lh); + + if (!sbw->scrollbar.orientation == XtorientHorizontal) + { + /* Left border */ + XDrawLine (XtDisplay ((Widget) sbw), XtWindow ((Widget) sbw), + sbw->scrollbar.top_shadow_GC, + lx, ly, lx, ly + lh - 1); + + /* Right border */ + XDrawLine (XtDisplay ((Widget) sbw), XtWindow ((Widget) sbw), + sbw->scrollbar.bot_shadow_GC, + lx + lw - 1, ly, lx + lw - 1, ly + lh - 1); + } + else + { + /* Top border */ + XDrawLine (XtDisplay ((Widget) sbw), XtWindow ((Widget) sbw), + sbw->scrollbar.top_shadow_GC, + lx, ly, lx + lw - 1, ly); + + /* Bottom border */ + XDrawLine (XtDisplay ((Widget) sbw), XtWindow ((Widget) sbw), + sbw->scrollbar.bot_shadow_GC, + lx, ly + lh - 1, lx + lw - 1, ly + lh - 1); + } + } + else + { + XClearArea (XtDisplay((Widget) sbw), XtWindow((Widget) sbw), + lx, ly, (unsigned int) lw, (unsigned int) lh, + FALSE); + } +} + +/* Paint the thumb in the area specified by sbw->top and + sbw->shown. The old area is erased. The painting and + erasing is done cleverly so that no flickering will occur. + */ + +static void PaintThumb(sbw) + ScrollbarWidget sbw; +{ + Position oldtop, oldbot, newtop, newbot; + Dimension margin, tzl; + + margin = MARGIN (sbw); + tzl = sbw->scrollbar.length - 2 * margin; + newtop = margin + (int)(tzl * sbw->scrollbar.top); + newbot = newtop + (int)(tzl * sbw->scrollbar.shown) + 1; + if (newbot < newtop + (int)sbw->scrollbar.min_thumb) + newbot = newtop + sbw->scrollbar.min_thumb; + + oldtop = sbw->scrollbar.topLoc; + oldbot = oldtop + sbw->scrollbar.shownLength; + sbw->scrollbar.topLoc = newtop; + sbw->scrollbar.shownLength = newbot - newtop; + if (XtIsRealized ((Widget) sbw)) + { + if (newtop < oldtop) + FillArea(sbw, newtop, MIN(newbot, oldtop+1),1,0); + if (newtop > oldtop) + FillArea(sbw, oldtop, MIN(newtop, oldbot ),0,0); + if (newbot < oldbot) + FillArea(sbw, MAX(newbot, oldtop), oldbot, 0,0); + if (newbot > oldbot) + FillArea(sbw, MAX(newtop, oldbot-1), newbot, 1,0); + + /* Only draw the missing shadows */ + FillArea(sbw, newtop, newbot, 0, 1); + } +} + +static void PaintArrows(sbw) + ScrollbarWidget sbw; +{ + XPoint point[6]; + Dimension thickness = sbw->scrollbar.thickness - 1; + Dimension size; + Dimension off; + + if (XtIsRealized((Widget) sbw)) + { + if (thickness * 2 > sbw->scrollbar.length) + { + size = sbw->scrollbar.length / 2; + off = (thickness - size) / 2; + } + else + { + size = thickness; + off = 0; + } + point[0].x = off + sbw->scrollbar.shadow_width; + point[0].y = size; + point[1].x = thickness - off - sbw->scrollbar.shadow_width; + point[1].y = size; + point[2].x = thickness / 2; + point[2].y = sbw->scrollbar.shadow_width; + + point[3].x = off + sbw->scrollbar.shadow_width; + point[3].y = sbw->scrollbar.length - size; + point[4].x = thickness - off - sbw->scrollbar.shadow_width; + point[4].y = sbw->scrollbar.length - size; + point[5].x = thickness / 2; + point[5].y = sbw->scrollbar.length - sbw->scrollbar.shadow_width - 1; + + /* horizontal arrows require that x and y coordinates be swapped */ + if (sbw->scrollbar.orientation == XtorientHorizontal) + { + int n; + int swap; + for (n = 0; n < 6; n++) + { + swap = point[n].x; + point[n].x = point[n].y; + point[n].y = swap; + } + } + /* draw the up/left arrow */ + XFillPolygon (XtDisplay ((Widget) sbw), XtWindow ((Widget) sbw), + sbw->scrollbar.gc, + point, 3, + Convex, CoordModeOrigin); + XDrawLines (XtDisplay ((Widget) sbw), XtWindow ((Widget) sbw), + sbw->scrollbar.bot_shadow_GC, + point, 3, + CoordModeOrigin); + XDrawLine (XtDisplay ((Widget) sbw), XtWindow ((Widget) sbw), + sbw->scrollbar.top_shadow_GC, + point[0].x, point[0].y, + point[2].x, point[2].y); + /* draw the down/right arrow */ + XFillPolygon (XtDisplay ((Widget) sbw), XtWindow ((Widget) sbw), + sbw->scrollbar.gc, + point+3, 3, + Convex, CoordModeOrigin); + XDrawLine (XtDisplay ((Widget) sbw), XtWindow ((Widget) sbw), + sbw->scrollbar.top_shadow_GC, + point[3].x, point[3].y, + point[4].x, point[4].y); + XDrawLine (XtDisplay ((Widget) sbw), XtWindow ((Widget) sbw), + sbw->scrollbar.top_shadow_GC, + point[3].x, point[3].y, + point[5].x, point[5].y); + XDrawLine (XtDisplay ((Widget) sbw), XtWindow ((Widget) sbw), + sbw->scrollbar.bot_shadow_GC, + point[4].x, point[4].y, + point[5].x, point[5].y); + } +} + +/* Function Name: Destroy + * Description: Called as the scrollbar is going away... + * Arguments: w - the scrollbar. + * Returns: nonw + */ +static void Destroy(w) + Widget w; +{ + ScrollbarWidget sbw = (ScrollbarWidget) w; + if (sbw->scrollbar.timer_id != (XtIntervalId) 0) + XtRemoveTimeOut (sbw->scrollbar.timer_id); + XtReleaseGC(w, sbw->scrollbar.gc); + XtReleaseGC(w, sbw->scrollbar.top_shadow_GC); + XtReleaseGC(w, sbw->scrollbar.bot_shadow_GC); +} + +/* Function Name: CreateGC + * Description: Creates the GC. + * Arguments: w - the scrollbar widget. + * Returns: none. + */ + +static void CreateGC (w) + Widget w; +{ + ScrollbarWidget sbw = (ScrollbarWidget) w; + XGCValues gcValues; + XtGCMask mask; + unsigned int depth = 1; + + if (sbw->scrollbar.thumb == XtUnspecifiedPixmap) + { + sbw->scrollbar.thumb = XmuCreateStippledPixmap (XtScreen(w), + (Pixel) 1, (Pixel) 0, depth); + } + else if (sbw->scrollbar.thumb != None) + { + Window root; + int x, y; + unsigned int width, height, bw; + + if (XGetGeometry (XtDisplay(w), sbw->scrollbar.thumb, &root, &x, &y, + &width, &height, &bw, &depth) == 0) + { + XtAppError (XtWidgetToApplicationContext (w), + "Scrollbar Widget: Could not get geometry of thumb pixmap."); + } + } + + gcValues.foreground = sbw->scrollbar.foreground; + gcValues.background = sbw->core.background_pixel; + mask = GCForeground | GCBackground; + + if (sbw->scrollbar.thumb != None) + { + gcValues.fill_style = FillSolid; + mask |= GCFillStyle; +#if 0 + if (depth == 1) + { + gcValues.fill_style = FillOpaqueStippled; + gcValues.stipple = sbw->scrollbar.thumb; + mask |= GCFillStyle | GCStipple; + } + else + { + gcValues.fill_style = FillTiled; + gcValues.tile = sbw->scrollbar.thumb; + mask |= GCFillStyle | GCTile; + } +#endif + } + /* the creation should be non-caching, because */ + /* we now set and clear clip masks on the gc returned */ + sbw->scrollbar.gc = XtGetGC (w, mask, &gcValues); +} + +static void SetDimensions(sbw) + ScrollbarWidget sbw; +{ + if (sbw->scrollbar.orientation == XtorientVertical) + { + sbw->scrollbar.length = sbw->core.height; + sbw->scrollbar.thickness = sbw->core.width; + } + else + { + sbw->scrollbar.length = sbw->core.width; + sbw->scrollbar.thickness = sbw->core.height; + } +} + +/* ARGSUSED */ +static void Initialize(request, new, args, num_args) + Widget request; /* what the client asked for */ + Widget new; /* what we're going to give him */ + ArgList args; + Cardinal *num_args; +{ + ScrollbarWidget sbw = (ScrollbarWidget) new; + + CreateGC(new); + AllocTopShadowGC(new); + AllocBotShadowGC(new); + + if (sbw->core.width == 0) + sbw->core.width = (sbw->scrollbar.orientation == XtorientVertical) + ? sbw->scrollbar.thickness : sbw->scrollbar.length; + + if (sbw->core.height == 0) + sbw->core.height = (sbw->scrollbar.orientation == XtorientHorizontal) + ? sbw->scrollbar.thickness : sbw->scrollbar.length; + + SetDimensions(sbw); + sbw->scrollbar.scroll_mode = SMODE_NONE; + sbw->scrollbar.timer_id = (XtIntervalId)0; + sbw->scrollbar.topLoc = 0; + sbw->scrollbar.shownLength = sbw->scrollbar.min_thumb; +} + +static void Realize(w, valueMask, attributes) + Widget w; + Mask *valueMask; + XSetWindowAttributes *attributes; +{ +#if 0 + ScrollbarWidget sbw = (ScrollbarWidget) w; + + if (sbw->simple.cursor_name == NULL) + XtVaSetValues(w, XtNcursorName, "crosshair", NULL); + + /* dont set the cursor of the window to anything */ + *valueMask &= ~CWCursor; +#endif + + /* + * The Simple widget actually stuffs the value in the valuemask. + */ + (*vim_scrollbarWidgetClass->core_class.superclass->core_class.realize) + (w, valueMask, attributes); +} + +/* ARGSUSED */ +static Boolean SetValues(current, request, desired, args, num_args) + Widget current, /* what I am */ + request, /* what he wants me to be */ + desired; /* what I will become */ + ArgList args; + Cardinal *num_args; +{ + ScrollbarWidget sbw = (ScrollbarWidget) current; + ScrollbarWidget dsbw = (ScrollbarWidget) desired; + Boolean redraw = FALSE; + +/* + * If these values are outside the acceptable range ignore them... + */ + + if (dsbw->scrollbar.top < 0.0 || dsbw->scrollbar.top > 1.0) + dsbw->scrollbar.top = sbw->scrollbar.top; + + if (dsbw->scrollbar.shown < 0.0 || dsbw->scrollbar.shown > 1.0) + dsbw->scrollbar.shown = sbw->scrollbar.shown; + +/* + * Change colors and stuff... + */ + if (XtIsRealized(desired)) + { + if (sbw->scrollbar.foreground != dsbw->scrollbar.foreground || + sbw->core.background_pixel != dsbw->core.background_pixel || + sbw->scrollbar.thumb != dsbw->scrollbar.thumb) + { + XtReleaseGC(desired, sbw->scrollbar.gc); + CreateGC (desired); + redraw = TRUE; + } + if (sbw->scrollbar.top != dsbw->scrollbar.top || + sbw->scrollbar.shown != dsbw->scrollbar.shown) + redraw = TRUE; + } + return redraw; +} + +static void Resize (w) + Widget w; +{ + /* ForgetGravity has taken care of background, but thumb may + * have to move as a result of the new size. */ + SetDimensions ((ScrollbarWidget) w); + Redisplay (w, (XEvent*) NULL, (Region)NULL); +} + + +/* ARGSUSED */ +static void Redisplay(w, event, region) + Widget w; + XEvent *event; + Region region; +{ + ScrollbarWidget sbw = (ScrollbarWidget) w; + int x, y; + unsigned int width, height; + + _Xaw3dDrawShadows(w, event, region, FALSE); + + if (sbw->scrollbar.orientation == XtorientHorizontal) + { + x = sbw->scrollbar.topLoc; + y = 1; + width = sbw->scrollbar.shownLength; + height = sbw->core.height - 2; + } + else + { + x = 1; + y = sbw->scrollbar.topLoc; + width = sbw->core.width - 2; + height = sbw->scrollbar.shownLength; + } + if (region == NULL || + XRectInRegion (region, x, y, width, height) != RectangleOut) + { + /* Forces entire thumb to be painted. */ + sbw->scrollbar.topLoc = -(sbw->scrollbar.length + 1); + PaintThumb (sbw); + } + /* we'd like to be region aware here!!!! */ + PaintArrows(sbw); +} + + +static Boolean CompareEvents (oldEvent, newEvent) + XEvent *oldEvent, *newEvent; +{ +#define Check(field) if (newEvent->field != oldEvent->field) return False; + + Check(xany.display); + Check(xany.type); + Check(xany.window); + + switch (newEvent->type) + { + case MotionNotify: + Check(xmotion.state); + break; + case ButtonPress: + case ButtonRelease: + Check(xbutton.state); + Check(xbutton.button); + break; + case KeyPress: + case KeyRelease: + Check(xkey.state); + Check(xkey.keycode); + break; + case EnterNotify: + case LeaveNotify: + Check(xcrossing.mode); + Check(xcrossing.detail); + Check(xcrossing.state); + break; + } +#undef Check + + return True; +} + +struct EventData +{ + XEvent *oldEvent; + int count; +}; + +static Bool PeekNotifyEvent (dpy, event, args) + Display *dpy; + XEvent *event; + char *args; +{ + struct EventData *eventData = (struct EventData*)args; + + return ((++eventData->count == QLength(dpy)) /* since PeekIf blocks */ + || CompareEvents(event, eventData->oldEvent)); +} + + +static Boolean LookAhead (w, event) + Widget w; + XEvent *event; +{ + XEvent newEvent; + struct EventData eventData; + + if (QLength (XtDisplay (w)) == 0) + return False; + + eventData.count = 0; + eventData.oldEvent = event; + + XPeekIfEvent (XtDisplay (w), &newEvent, PeekNotifyEvent, (char*)&eventData); + + return CompareEvents (event, &newEvent); +} + + +static void ExtractPosition(event, x, y, state) + XEvent *event; + Position *x, *y; /* RETURN */ + unsigned int *state; /* RETURN */ +{ + switch( event->type ) + { + case MotionNotify: + *x = event->xmotion.x; + *y = event->xmotion.y; + if (state != NULL) + *state = event->xmotion.state; + break; + case ButtonPress: + case ButtonRelease: + *x = event->xbutton.x; + *y = event->xbutton.y; + if (state != NULL) + *state = event->xbutton.state; + break; + case KeyPress: + case KeyRelease: + *x = event->xkey.x; + *y = event->xkey.y; + if (state != NULL) + *state = event->xkey.state; + break; + case EnterNotify: + case LeaveNotify: + *x = event->xcrossing.x; + *y = event->xcrossing.y; + if (state != NULL) + *state = event->xcrossing.state; + break; + default: + *x = 0; *y = 0; + if (state != NULL) + *state = 0; + } +} + +/* ARGSUSED */ +static void HandleThumb(w, event, params, num_params) + Widget w; + XEvent *event; + String *params; /* unused */ + Cardinal *num_params; /* unused */ +{ + Position x, y, loc; + ScrollbarWidget sbw = (ScrollbarWidget) w; + + ExtractPosition(event, &x, &y, NULL); + loc = PICKLENGTH(sbw, x, y); + /* if the motion event puts the pointer in thumb, call Move and Notify */ + /* also call Move and Notify if we're already in continuous scroll mode */ + if (sbw->scrollbar.scroll_mode == SMODE_CONT || + (loc >= sbw->scrollbar.topLoc && + loc <= sbw->scrollbar.topLoc + sbw->scrollbar.shownLength)) + { + XtCallActionProc(w, "MoveThumb", event, params, *num_params); + XtCallActionProc(w, "NotifyThumb", event, params, *num_params); + } +} + +static void RepeatNotify(client_data, idp) + XtPointer client_data; + XtIntervalId *idp; +{ + ScrollbarWidget sbw = (ScrollbarWidget) client_data; + int call_data; + char mode = sbw->scrollbar.scroll_mode; + unsigned long rep; + + if (mode == SMODE_NONE || mode == SMODE_CONT) + { + sbw->scrollbar.timer_id = (XtIntervalId)0; + return; + } + + if (mode == SMODE_LINE_DOWN || mode == SMODE_LINE_UP) + { + call_data = ONE_LINE_DATA; + rep = LINE_REPEAT; + } + else + { + call_data = ONE_PAGE_DATA; + rep = PAGE_REPEAT; + } + + if (mode == SMODE_PAGE_UP || mode == SMODE_LINE_UP) + call_data = -call_data; + + XtCallCallbacks((Widget)sbw, XtNscrollProc, (XtPointer)call_data); + + sbw->scrollbar.timer_id = + XtAppAddTimeOut(XtWidgetToApplicationContext((Widget)sbw), + rep, + RepeatNotify, + client_data); +} + +/* + * Same as above, but for floating numbers. + */ +static float FloatInRange(num, small, big) + float num, small, big; +{ + return (num < small) ? small : ((num > big) ? big : num); +} + +/* ARGSUSED */ +static void NotifyScroll(w, event, params, num_params) + Widget w; + XEvent *event; + String *params; + Cardinal *num_params; +{ + ScrollbarWidget sbw = (ScrollbarWidget) w; + Position x, y, loc; + Dimension arrow_size; + unsigned long delay = 0; + int call_data = 0; + unsigned int state; + + if (sbw->scrollbar.scroll_mode == SMODE_CONT) /* if scroll continuous */ + return; + + if (LookAhead (w, event)) + return; + + ExtractPosition(event, &x, &y, &state); + loc = PICKLENGTH(sbw, x, y); + + if (sbw->scrollbar.thickness * 2 > sbw->scrollbar.length) + arrow_size = sbw->scrollbar.length / 2; + else + arrow_size = sbw->scrollbar.thickness; + + /* + * handle CTRL modifier + */ + if (state & ControlMask) + { + if (loc > sbw->scrollbar.topLoc + (Position)sbw->scrollbar.shownLength) + call_data = END_PAGE_DATA; + else + call_data = -END_PAGE_DATA; + sbw->scrollbar.scroll_mode = SMODE_NONE; + } + /* + * handle first arrow zone + */ + else if (loc < (Position)arrow_size) + { + call_data = -ONE_LINE_DATA; + sbw->scrollbar.scroll_mode = SMODE_LINE_UP; + delay = LINE_DELAY; + } + + /* + * handle last arrow zone + */ + else if (loc > (Position)(sbw->scrollbar.length - arrow_size)) + { + call_data = ONE_LINE_DATA; + sbw->scrollbar.scroll_mode = SMODE_LINE_DOWN; + delay = LINE_DELAY; + } + + /* + * handle zone "above" the thumb + */ + else if (loc < sbw->scrollbar.topLoc) + { + call_data = -ONE_PAGE_DATA; + sbw->scrollbar.scroll_mode = SMODE_PAGE_UP; + delay = PAGE_DELAY; + } + + /* + * handle zone "below" the thumb + */ + else if (loc > sbw->scrollbar.topLoc + (Position)sbw->scrollbar.shownLength) + { + call_data = ONE_PAGE_DATA; + sbw->scrollbar.scroll_mode = SMODE_PAGE_DOWN; + delay = PAGE_DELAY; + } + + if (call_data) + XtCallCallbacks(w, XtNscrollProc, (XtPointer)call_data); + + /* establish autoscroll */ + if (delay) + sbw->scrollbar.timer_id = + XtAppAddTimeOut(XtWidgetToApplicationContext(w), + delay, RepeatNotify, (XtPointer)w); +} + +/* ARGSUSED */ +static void EndScroll(w, event, params, num_params) + Widget w; + XEvent *event; /* unused */ + String *params; /* unused */ + Cardinal *num_params; /* unused */ +{ + ScrollbarWidget sbw = (ScrollbarWidget) w; + + sbw->scrollbar.scroll_mode = SMODE_NONE; + /* no need to remove any autoscroll timeout; it will no-op */ + /* because the scroll_mode is SMODE_NONE */ + /* but be sure to remove timeout in destroy proc */ +} + +static float FractionLoc(sbw, x, y) + ScrollbarWidget sbw; + int x, y; +{ + int margin; + float height, width; + + margin = MARGIN(sbw); + x -= margin; + y -= margin; + height = sbw->core.height - 2 * margin; + width = sbw->core.width - 2 * margin; + return PICKLENGTH(sbw, x / width, y / height); +} + + +static void MoveThumb(w, event, params, num_params) + Widget w; + XEvent *event; + String *params; /* unused */ + Cardinal *num_params; /* unused */ +{ + ScrollbarWidget sbw = (ScrollbarWidget)w; + Position x, y; + float top; + char old_mode = sbw->scrollbar.scroll_mode; + + sbw->scrollbar.scroll_mode = SMODE_CONT; /* indicate continuous scroll */ + + if (LookAhead(w, event)) + return; + + if (!event->xmotion.same_screen) + return; + + ExtractPosition(event, &x, &y, NULL); + + top = FractionLoc(sbw, x, y); + + if (old_mode != SMODE_CONT) /* start dragging: set offset */ + if (event->xbutton.button == Button2) + sbw->scrollbar.scroll_off = sbw->scrollbar.shown / 2.; + else + sbw->scrollbar.scroll_off = top - sbw->scrollbar.top; + + top -= sbw->scrollbar.scroll_off; + top = FloatInRange(top, 0.0, sbw->scrollbar.max); + + sbw->scrollbar.top = top; + PaintThumb(sbw); + XFlush(XtDisplay(w)); /* re-draw it before Notifying */ +} + + +/* ARGSUSED */ +static void NotifyThumb(w, event, params, num_params) + Widget w; + XEvent *event; + String *params; /* unused */ + Cardinal *num_params; /* unused */ +{ + ScrollbarWidget sbw = (ScrollbarWidget)w; + + if (LookAhead(w, event)) + return; + + /* thumbProc is not pretty, but is necessary for backwards + compatibility on those architectures for which it work{s,ed}; + the intent is to pass a (truncated) float by value. */ + XtCallCallbacks(w, XtNthumbProc, *(XtPointer*)&sbw->scrollbar.top); + XtCallCallbacks(w, XtNjumpProc, (XtPointer)&sbw->scrollbar.top); +} + +/* ARGSUSED */ +static void +AllocTopShadowGC (w) + Widget w; +{ + ScrollbarWidget sbw = (ScrollbarWidget) w; + XtGCMask valuemask; + XGCValues myXGCV; + + valuemask = GCForeground; + myXGCV.foreground = sbw->scrollbar.top_shadow_pixel; + sbw->scrollbar.top_shadow_GC = XtGetGC(w, valuemask, &myXGCV); +} + +/* ARGSUSED */ +static void +AllocBotShadowGC (w) + Widget w; +{ + ScrollbarWidget sbw = (ScrollbarWidget) w; + XtGCMask valuemask; + XGCValues myXGCV; + + valuemask = GCForeground; + myXGCV.foreground = sbw->scrollbar.bot_shadow_pixel; + sbw->scrollbar.bot_shadow_GC = XtGetGC(w, valuemask, &myXGCV); +} + +/* ARGSUSED */ +static void +_Xaw3dDrawShadows(gw, event, region, out) + Widget gw; + XEvent *event; + Region region; + Boolean out; +{ + XPoint pt[6]; + ScrollbarWidget sbw = (ScrollbarWidget) gw; + Dimension s = sbw->scrollbar.shadow_width; + /* + * draw the shadows using the core part width and height, + * and the scrollbar part shadow_width. + * + * no point to do anything if the shadow_width is 0 or the + * widget has not been realized. + */ + if ((s > 0) && XtIsRealized (gw)) + { + Dimension h = sbw->core.height; + Dimension w = sbw->core.width; + Dimension wms = w - s; + Dimension hms = h - s; + Display *dpy = XtDisplay (gw); + Window win = XtWindow (gw); + GC top, bot; + + if (out) + { + top = sbw->scrollbar.top_shadow_GC; + bot = sbw->scrollbar.bot_shadow_GC; + } + else + { + top = sbw->scrollbar.bot_shadow_GC; + bot = sbw->scrollbar.top_shadow_GC; + } + + /* top-left shadow */ + if ((region == NULL) || + (XRectInRegion (region, 0, 0, w, s) != RectangleOut) || + (XRectInRegion (region, 0, 0, s, h) != RectangleOut)) + { + pt[0].x = 0; pt[0].y = h; + pt[1].x = pt[1].y = 0; + pt[2].x = w; pt[2].y = 0; + pt[3].x = wms; pt[3].y = s; + pt[4].x = pt[4].y = s; + pt[5].x = s; pt[5].y = hms; + XFillPolygon (dpy, win, top, pt, 6,Complex,CoordModeOrigin); + } + + /* bottom-right shadow */ + if ((region == NULL) || + (XRectInRegion (region, 0, hms, w, s) != RectangleOut) || + (XRectInRegion (region, wms, 0, s, h) != RectangleOut)) + { + pt[0].x = 0; pt[0].y = h; + pt[1].x = w; pt[1].y = h; + pt[2].x = w; pt[2].y = 0; + pt[3].x = wms; pt[3].y = s; + pt[4].x = wms; pt[4].y = hms; + pt[5].x = s; pt[5].y = hms; + XFillPolygon (dpy, win, bot, pt,6, Complex,CoordModeOrigin); + } + } +} + +/************************************************************ + * + * Public routines. + * + ************************************************************/ + +/* Set the scroll bar to the given location. */ + +void vim_XawScrollbarSetThumb (w, top, shown, max) + Widget w; +#if NeedWidePrototypes + double top, shown, max; +#else + float top, shown, max; +#endif +{ + ScrollbarWidget sbw = (ScrollbarWidget) w; + + if (sbw->scrollbar.scroll_mode == SMODE_CONT) /* if still thumbing */ + return; + + sbw->scrollbar.max = (max > 1.0) ? 1.0 : + (max >= 0.0) ? max : sbw->scrollbar.max; + + sbw->scrollbar.top = (top > sbw->scrollbar.max) ? sbw->scrollbar.max : + (top >= 0.0) ? top : sbw->scrollbar.top; + + sbw->scrollbar.shown = (shown > 1.0) ? 1.0 : + (shown >= 0.0) ? shown : sbw->scrollbar.shown; + + PaintThumb (sbw); +} diff --git a/usr.bin/vim/gui_at_sb.h b/usr.bin/vim/gui_at_sb.h new file mode 100644 index 00000000000..c3d601f5e09 --- /dev/null +++ b/usr.bin/vim/gui_at_sb.h @@ -0,0 +1,163 @@ +/* $OpenBSD: gui_at_sb.h,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* vi:set ts=8 sw=4: */ +/* MODIFIED ATHENA SCROLLBAR (USING ARROWHEADS AT ENDS OF TRAVEL) */ +/* Modifications Copyright 1992 by Mitch Trachtenberg */ +/* Rights, permissions, and disclaimer of warranty are as in the */ +/* DEC and MIT notice below. See usage warning in .c file. */ +/* + * $XConsortium: ScrollbarP.h,v 1.3 94/04/17 20:12:42 jim Exp $ + */ + + +/*********************************************************** + +Copyright (c) 1987, 1988 X Consortium + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of the X Consortium shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the X Consortium. + + +Copyright 1987, 1988 by Digital Equipment Corporation, Maynard, Massachusetts. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Digital not be +used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +******************************************************************/ + +#ifndef _Scrollbar_h +#define _Scrollbar_h + +/**************************************************************** + * + * Scrollbar Widget + * + ****************************************************************/ + +#include +#include +#include + +/* + * Most things we need are in StringDefs.h + */ +#define XtCMinimumThumb "MinimumThumb" +#define XtCShown "Shown" +#define XtCTopOfThumb "TopOfThumb" +#define XtCMaxOfThumb "MaxOfThumb" +#define XtCShadowWidth "ShadowWidth" +#define XtCTopShadowPixel "TopShadowPixel" +#define XtCBottomShadowPixel "BottomShadowPixel" + +#define XtNminimumThumb "minimumThumb" +#define XtNtopOfThumb "topOfThumb" +#define XtNmaxOfThumb "maxOfThumb" +#define XtNshadowWidth "shadowWidth" +#define XtNtopShadowPixel "topShadowPixel" +#define XtNbottomShadowPixel "bottomShadowPixel" + +typedef struct _ScrollbarRec *ScrollbarWidget; +typedef struct _ScrollbarClassRec *ScrollbarWidgetClass; + +extern WidgetClass vim_scrollbarWidgetClass; + +#if NeedWidePrototypes +extern void vim_XawScrollbarSetThumb __ARGS((Widget, double, double, double)); +#else +extern void vim_XawScrollbarSetThumb __ARGS((Widget, float, float, float)); +#endif + +typedef struct +{ + /* public */ + Pixel foreground; /* thumb foreground color */ + XtOrientation orientation; /* horizontal or vertical */ + XtCallbackList scrollProc; /* proportional scroll */ + XtCallbackList thumbProc; /* jump (to position) scroll */ + XtCallbackList jumpProc; /* same as thumbProc but pass data by ref */ + Pixmap thumb; /* thumb color */ + float top; /* What percent is above the win's top */ + float shown; /* What percent is shown in the win */ + float max; /* Maximum value for top */ + Dimension length; /* either height or width */ + Dimension thickness; /* either width or height */ + Dimension min_thumb; /* minium size for the thumb. */ + + /* private */ + XtIntervalId timer_id; /* autorepeat timer; remove on destruction */ + char scroll_mode; /* see below */ + float scroll_off; /* offset from event to top of thumb */ + GC gc; /* a (shared) gc */ + Position topLoc; /* Pixel that corresponds to top */ + Dimension shownLength; /* Num pixels corresponding to shown */ + + /* From 3d widget */ + Dimension shadow_width; + Pixel top_shadow_pixel; + Pixel bot_shadow_pixel; + int top_shadow_contrast; + int bot_shadow_contrast; + GC top_shadow_GC; + GC bot_shadow_GC; +} ScrollbarPart; + +#define SMODE_NONE 0 +#define SMODE_CONT 1 +#define SMODE_PAGE_UP 2 +#define SMODE_PAGE_DOWN 3 +#define SMODE_LINE_UP 4 +#define SMODE_LINE_DOWN 5 + +#define ONE_LINE_DATA 1 +#define ONE_PAGE_DATA 10 +#define END_PAGE_DATA 9999 + +typedef struct _ScrollbarRec { + CorePart core; + SimplePart simple; + ScrollbarPart scrollbar; +} ScrollbarRec; + +typedef struct {int empty;} ScrollbarClassPart; + +typedef struct _ScrollbarClassRec { + CoreClassPart core_class; + SimpleClassPart simple_class; + ScrollbarClassPart scrollbar_class; +} ScrollbarClassRec; + +extern ScrollbarClassRec vim_scrollbarClassRec; + +#endif /* _Scrollbar_h */ diff --git a/usr.bin/vim/gui_athena.c b/usr.bin/vim/gui_athena.c new file mode 100644 index 00000000000..281816514f0 --- /dev/null +++ b/usr.bin/vim/gui_athena.c @@ -0,0 +1,1034 @@ +/* $OpenBSD: gui_athena.c,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * GUI/Motif support by Robert Webb + * Athena port by Bill Foster + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" +#include "ops.h" +#include "gui_at_sb.h" + +#define puller_width 19 +#define puller_height 19 + +static char puller_bits[] = +{ + 0x00,0x00,0xf8,0x00,0x00,0xf8,0xf8,0x7f,0xf8,0x04,0x80,0xf8,0x04,0x80,0xf9, + 0x84,0x81,0xf9,0x84,0x83,0xf9,0x84,0x87,0xf9,0x84,0x8f,0xf9,0x84,0x8f,0xf9, + 0x84,0x87,0xf9,0x84,0x83,0xf9,0x84,0x81,0xf9,0x04,0x80,0xf9,0x04,0x80,0xf9, + 0xf8,0xff,0xf9,0xf0,0x7f,0xf8,0x00,0x00,0xf8,0x00,0x00,0xf8 +}; + +extern Widget vimShell; + +static Widget vimPanes; +static Widget vimForm = (Widget)NULL; +static Widget textArea; +static Widget scrollbarBox[3]; /* Left, right & bottom scrollbar boxes */ +static Widget bottomScrollbar; /* Bottom scrollbar */ +static Widget leftBottomScrollFiller; /* Left filler for bottom scrollbar */ +static Widget rightBottomScrollFiller; /* Right filler for bottom scrollbar */ +static Widget leftScrollbarFiller; /* Filler for left scrollbar */ +static Widget rightScrollbarFiller; /* Filler for right scrollbar */ +static Widget menuBar; + +static void gui_athena_scroll_cb_jump __ARGS((Widget, XtPointer, XtPointer)); +static void gui_athena_scroll_cb_scroll __ARGS((Widget, XtPointer, XtPointer)); +static void gui_athena_reorder_menus __ARGS((void)); +static void gui_athena_pullright_action __ARGS((Widget, XEvent *, String *, + Cardinal *)); + +static XtActionsRec pullAction = { "menu-pullright", + (XtActionProc)gui_athena_pullright_action }; +static XtTranslations parentTrans, menuTrans; +static Pixmap pullerBitmap; + +/* + * Scrollbar callback (XtNjumpProc) for when the scrollbar is dragged with the + * left or middle mouse button. + */ + static void +gui_athena_scroll_cb_jump(w, client_data, call_data) + Widget w; + XtPointer client_data, call_data; +{ + char_u bytes[4 + sizeof(long_u)]; + WIN *wp; + GuiScrollbar *sb; + int sb_num; + int i; + int byte_count; + long_u value; + + gui.dragged_sb = SB_NONE; + for (i = 0; i <= SB_BOTTOM; i++) + if (XtParent(w) == scrollbarBox[i]) + { + gui.dragged_sb = i; + break; + } + + switch (gui.dragged_sb) + { + case SB_LEFT: + case SB_RIGHT: + gui.dragged_wp = (WIN *)client_data; + sb_num = 0; + wp = firstwin; + for ( ; wp != gui.dragged_wp && wp != NULL; wp = wp->w_next) + sb_num++; + + if (gui.dragged_wp == NULL) + return; + + sb = &wp->w_scrollbar; + + value = *((float *)call_data) * (float)sb->max + 0.5; + ++value; /* range is 1 to line_count */ + sb->value = value; + + bytes[0] = CSI; + bytes[1] = KS_SCROLLBAR; + bytes[2] = K_FILLER; + bytes[3] = (char_u)sb_num; + byte_count = 4; + break; + + case SB_BOTTOM: + /* why not use sb->max? */ + value = *((float *)call_data) * + (float)(gui_get_max_horiz_scroll()) + 0.5; + bytes[0] = CSI; + bytes[1] = KS_HORIZ_SCROLLBAR; + bytes[2] = K_FILLER; + byte_count = 3; + break; + + case SB_NONE: + default: + return; + } + + add_long_to_buf(value, bytes + byte_count); + add_to_input_buf(bytes, byte_count + sizeof(long_u)); +} + +/* + * Scrollbar callback (XtNscrollProc) for paging up or down with the left or + * right mouse buttons. + */ + static void +gui_athena_scroll_cb_scroll(w, client_data, call_data) + Widget w; + XtPointer client_data, call_data; +{ + char_u bytes[4 + sizeof(long_u)]; + WIN *wp; + GuiScrollbar *sb; + int sb_num; + int i; + int byte_count; + long value; + int data = (int)call_data; + + for (i = 0; i <= SB_BOTTOM; i++) + if (XtParent(w) == scrollbarBox[i]) + { + gui.dragged_sb = i; + break; + } + + switch (gui.dragged_sb) + { + case SB_LEFT: + case SB_RIGHT: + gui.dragged_wp = (WIN *)client_data; + sb_num = 0; + wp = firstwin; + for ( ; wp != gui.dragged_wp && wp != NULL; wp = wp->w_next) + sb_num++; + + if (gui.dragged_wp == NULL) + return; + + sb = &wp->w_scrollbar; + + if (sb->size > 5) + i = sb->size - 2; /* use two lines of context */ + else + i = sb->size; + switch (data) + { + case ONE_LINE_DATA: data = 1; break; + case -ONE_LINE_DATA: data = -1; break; + case ONE_PAGE_DATA: data = i; break; + case -ONE_PAGE_DATA: data = -i; break; + case END_PAGE_DATA: data = sb->max; break; + case -END_PAGE_DATA: data = -sb->max; break; + default: data = 0; break; + } + value = sb->value + data; + if (value > sb->max) + value = sb->max; + else if (value < 1) /* range is 1 to line_count */ + value = 1; + + bytes[0] = CSI; + bytes[1] = KS_SCROLLBAR; + bytes[2] = K_FILLER; + bytes[3] = (char_u)sb_num; + byte_count = 4; + break; + + case SB_BOTTOM: + if (data < -1) + data = -(Columns - 5); + else if (data > 1) + data = (Columns - 5); + value = curwin->w_leftcol + data; + if (value < 0) /* range is 0 to max_col */ + value = 0; + else + { + int max; + /* why not use sb->max here? */ + max = gui_get_max_horiz_scroll(); + if (value >= max) + value = max; + } + + bytes[0] = CSI; + bytes[1] = KS_HORIZ_SCROLLBAR; + bytes[2] = K_FILLER; + byte_count = 3; + break; + + case SB_NONE: + default: + return; + } + + /* + * This type of scrolling doesn't move the thumb automatically so we need + * make sure the scrollbar still gets updated. + */ + gui.dragged_sb = SB_NONE; + + add_long_to_buf((long_u)value, bytes + byte_count); + add_to_input_buf(bytes, byte_count + sizeof(long_u)); +} + +/* + * Create all the Athena widgets necessary. + */ + void +gui_mch_create_widgets() +{ + Dimension base_width, base_height; + + /* + * We don't have any borders handled internally by the textArea to worry + * about so only skip over the configured border width. + */ + gui.border_offset = gui.border_width; + + base_width = 2 * gui.border_offset; + base_height = 2 * gui.border_offset; + + XtInitializeWidgetClass(panedWidgetClass); + XtInitializeWidgetClass(simpleMenuWidgetClass); + XtInitializeWidgetClass(vim_scrollbarWidgetClass); + XtInitializeWidgetClass(labelWidgetClass); + + /* Panes for menu bar, middle stuff, and bottom scrollbar box */ + vimPanes = XtVaCreateManagedWidget("vimPanes", + panedWidgetClass, vimShell, + XtNorientation, XtorientVertical, + NULL); + + /* The top menu bar */ + menuBar = XtVaCreateManagedWidget("menuBar", + boxWidgetClass, vimPanes, + XtNmin, gui.menu_height, + XtNborderWidth, 1, + XtNallowResize, True, + XtNresizeToPreferred, True, + XtNskipAdjust, True, + XtNshowGrip, False, + XtNforeground, gui.menu_fg_pixel, + XtNbackground, gui.menu_bg_pixel, + XtNborderColor, gui.menu_fg_pixel, + NULL); + + /* + * Panes for the middle stuff (left scrollbar box, text area, and right + * scrollbar box. + */ + vimForm = XtVaCreateManagedWidget("vimForm", + panedWidgetClass, vimPanes, + XtNallowResize, True, + XtNorientation, XtorientHorizontal, + XtNborderWidth, 0, + XtNdefaultDistance, 0, + XtNshowGrip, False, + NULL); + + /* Panes for the left window scrollbars. */ + scrollbarBox[SB_LEFT] = XtVaCreateWidget("scrollBarBox", + panedWidgetClass, vimForm, + XtNpreferredPaneSize, gui.scrollbar_width, + XtNallowResize, True, + XtNskipAdjust, True, + XtNborderWidth, 1, + XtNshowGrip, False, + XtNforeground, gui.scroll_fg_pixel, + XtNbackground, gui.scroll_fg_pixel, + XtNborderColor, gui.scroll_fg_pixel, + NULL); + + /* The text area. */ + textArea = XtVaCreateManagedWidget("textArea", + coreWidgetClass, vimForm, + XtNallowResize, True, + XtNshowGrip, False, + XtNbackground, gui.back_pixel, + XtNborderWidth, 0, + XtNheight, Rows * gui.char_height + base_height, + XtNwidth, Columns * gui.char_width + base_width, + NULL); + + /* Panes for the right window scrollbars. */ + scrollbarBox[SB_RIGHT] = XtVaCreateWidget("scrollBarBox", + panedWidgetClass, vimForm, + XtNpreferredPaneSize, gui.scrollbar_width, + XtNallowResize, True, + XtNskipAdjust, True, + XtNborderWidth, 1, + XtNresizeToPreferred, True, + XtNshowGrip, False, + XtNforeground, gui.scroll_fg_pixel, + XtNbackground, gui.scroll_fg_pixel, + XtNborderColor, gui.scroll_fg_pixel, + NULL); + + /* Panes for the bottom scrollbar and fillers on each side. */ + scrollbarBox[SB_BOTTOM] = XtVaCreateWidget("scrollBarBox", + panedWidgetClass, vimPanes, + XtNpreferredPaneSize, gui.scrollbar_width, + XtNallowResize, True, + XtNskipAdjust, True, + XtNborderWidth, 1, + XtNresizeToPreferred, True, + XtNshowGrip, False, + XtNforeground, gui.scroll_fg_pixel, + XtNbackground, gui.scroll_fg_pixel, + XtNborderColor, gui.scroll_fg_pixel, + XtNorientation, XtorientHorizontal, + NULL); + + /* A filler for the gap on the left side of the bottom scrollbar. */ + leftBottomScrollFiller = XtVaCreateManagedWidget("", + labelWidgetClass, scrollbarBox[SB_BOTTOM], + XtNshowGrip, False, + XtNresize, False, + XtNborderWidth, 4, + XtNmin, gui.scrollbar_width + 1, + XtNmax, gui.scrollbar_width + 1, + XtNforeground, gui.scroll_fg_pixel, + XtNbackground, gui.scroll_fg_pixel, + XtNborderColor, gui.scroll_fg_pixel, + NULL); + + /* The bottom scrollbar. */ + bottomScrollbar = XtVaCreateManagedWidget("bottomScrollBar", + vim_scrollbarWidgetClass, scrollbarBox[SB_BOTTOM], + XtNresizeToPreferred, True, + XtNallowResize, True, + XtNskipAdjust, True, + XtNshowGrip, False, + XtNorientation, XtorientHorizontal, + XtNforeground, gui.scroll_fg_pixel, + XtNbackground, gui.scroll_bg_pixel, + NULL); + + XtAddCallback(bottomScrollbar, XtNjumpProc, + gui_athena_scroll_cb_jump, (XtPointer)NULL); + XtAddCallback(bottomScrollbar, XtNscrollProc, + gui_athena_scroll_cb_scroll, (XtPointer)NULL); + + vim_XawScrollbarSetThumb(bottomScrollbar, 0., 1., 0.); + + /* A filler for the gap on the right side of the bottom scrollbar. */ + rightBottomScrollFiller = XtVaCreateManagedWidget("", + labelWidgetClass, scrollbarBox[SB_BOTTOM], + XtNshowGrip, False, + XtNresize, False, + XtNborderWidth, 4, + XtNmin, gui.scrollbar_width + 1, + XtNmax, gui.scrollbar_width + 1, + XtNforeground, gui.scroll_fg_pixel, + XtNbackground, gui.scroll_fg_pixel, + NULL); + + /* A filler for the gap on the bottom of the left scrollbar. */ + leftScrollbarFiller = XtVaCreateManagedWidget("", + labelWidgetClass, scrollbarBox[SB_LEFT], + XtNshowGrip, False, + XtNresize, False, + XtNborderWidth, 4, + XtNmin, gui.scrollbar_width + 1, + XtNmax, gui.scrollbar_width + 1, + XtNforeground, gui.scroll_fg_pixel, + XtNbackground, gui.scroll_fg_pixel, + NULL); + + /* A filler for the gap on the bottom of the right scrollbar. */ + rightScrollbarFiller = XtVaCreateManagedWidget("", + labelWidgetClass, scrollbarBox[SB_RIGHT], + XtNshowGrip, False, + XtNresize, False, + XtNborderWidth, 4, + XtNmin, gui.scrollbar_width + 1, + XtNmax, gui.scrollbar_width + 1, + XtNforeground, gui.scroll_fg_pixel, + XtNbackground, gui.scroll_fg_pixel, + NULL); + + gui.num_scrollbars = 0; + + /* + * Text area callbacks + */ + XtAddEventHandler(textArea, VisibilityChangeMask, FALSE, + gui_x11_visibility_cb, (XtPointer)0); + + XtAddEventHandler(textArea, ExposureMask, FALSE, gui_x11_expose_cb, + (XtPointer)0); + + XtAddEventHandler(textArea, StructureNotifyMask, FALSE, + gui_x11_resize_window_cb, (XtPointer)0); + + XtAddEventHandler(vimShell, FocusChangeMask, FALSE, gui_x11_focus_change_cb, + (XtPointer)0); + + XtAddEventHandler(vimPanes, KeyPressMask, FALSE, gui_x11_key_hit_cb, + (XtPointer)0); + + XtAddEventHandler(textArea, ButtonPressMask | ButtonReleaseMask | + ButtonMotionMask, FALSE, gui_x11_mouse_cb, (XtPointer)0); + + parentTrans = XtParseTranslationTable(": highlight() menu-pullright()"); + menuTrans = XtParseTranslationTable(": unhighlight() MenuPopdown()\n: notify() unhighlight() MenuPopdown()\n: highlight()"); + + XtAppAddActions(XtWidgetToApplicationContext(vimForm), &pullAction, 1); + + pullerBitmap = XCreateBitmapFromData(gui.dpy, DefaultRootWindow(gui.dpy), + (char *)puller_bits, puller_width, puller_height); +} + + int +gui_mch_get_winsize() +{ + Dimension base_width, base_height; + Dimension total_width, total_height; + Dimension left_width = 0, right_width = 0; + Dimension bottom_height = 0, menu_height = 0; + + base_height = 2 * gui.border_offset; + base_width = 2 * gui.border_offset; + + if (gui.which_scrollbars[SB_LEFT]) + XtVaGetValues(scrollbarBox[SB_LEFT], XtNwidth, &left_width, NULL); + + if (gui.which_scrollbars[SB_RIGHT]) + XtVaGetValues(scrollbarBox[SB_RIGHT], XtNwidth, &right_width, NULL); + + if (gui.which_scrollbars[SB_BOTTOM]) + XtVaGetValues(scrollbarBox[SB_BOTTOM], XtNheight, &bottom_height, NULL); + + if (XtIsManaged(menuBar)) + XtVaGetValues(menuBar, XtNheight, &menu_height, NULL); + + base_width += left_width + right_width; + base_height += menu_height + bottom_height; + + XtVaGetValues(vimShell, + XtNheight, &total_height, + XtNwidth, &total_width, + NULL); + + gui.num_rows = (total_height - base_height) / gui.char_height; + gui.num_cols = (total_width - base_width) / gui.char_width; + + Rows = gui.num_rows; + Columns = gui.num_cols; + gui_reset_scroll_region(); + + return OK; +} + + void +gui_mch_set_winsize() +{ + Dimension left_width = 0, right_width = 0; + Dimension bottom_height = 0, menu_height = 0; + Dimension base_width, base_height; + + base_width = 2 * gui.border_offset; + base_height = 2 * gui.border_offset; + + if (gui.which_scrollbars[SB_LEFT]) + XtVaGetValues(scrollbarBox[SB_LEFT], XtNwidth, &left_width, NULL); + + if (gui.which_scrollbars[SB_RIGHT]) + XtVaGetValues(scrollbarBox[SB_RIGHT], XtNwidth, &right_width, NULL); + + if (gui.which_scrollbars[SB_BOTTOM]) + XtVaGetValues(scrollbarBox[SB_BOTTOM], XtNheight, &bottom_height, NULL); + + if (XtIsManaged(menuBar)) + XtVaGetValues(menuBar, XtNheight, &menu_height, NULL); + + base_width += left_width + right_width; + base_height += menu_height + bottom_height; + + XtVaSetValues(vimShell, + XtNwidthInc, gui.char_width, + XtNheightInc, gui.char_height, + XtNbaseWidth, base_width, + XtNbaseHeight, base_height, + XtNminWidth, base_width + MIN_COLUMNS * gui.char_width, + XtNminHeight, base_height + MIN_ROWS * gui.char_height, + XtNwidth, base_width + Columns * gui.char_width, + XtNheight, base_height + Rows * gui.char_height, + NULL); +} + +/* + * Menu stuff. + */ + + void +gui_mch_add_menu(menu, parent) + GuiMenu *menu; + GuiMenu *parent; +{ + char_u *pullright_name; + + if (parent == NULL) + { + menu->id = XtVaCreateManagedWidget(menu->name, + menuButtonWidgetClass, menuBar, + XtNmenuName, menu->name, + XtNforeground, gui.menu_fg_pixel, + XtNbackground, gui.menu_bg_pixel, + NULL); + + menu->submenu_id = XtVaCreatePopupShell(menu->name, + simpleMenuWidgetClass, menu->id, + XtNforeground, gui.menu_fg_pixel, + XtNbackground, gui.menu_bg_pixel, + NULL); + + gui_athena_reorder_menus(); + } + else + { + menu->id = XtVaCreateManagedWidget(menu->name, + smeBSBObjectClass, parent->submenu_id, + XtNforeground, gui.menu_fg_pixel, + XtNbackground, gui.menu_bg_pixel, + XtNrightMargin, puller_width, + XtNrightBitmap, pullerBitmap, + + NULL); + XtAddCallback(menu->id, XtNcallback, gui_x11_menu_cb, + (XtPointer)menu); + + pullright_name = strnsave(menu->name, strlen(menu->name) + + strlen("-pullright")); + strcat(pullright_name, "-pullright"); + menu->submenu_id = XtVaCreatePopupShell(pullright_name, + simpleMenuWidgetClass, parent->submenu_id, + XtNforeground, gui.menu_fg_pixel, + XtNbackground, gui.menu_bg_pixel, + XtNtranslations, menuTrans, + NULL); + vim_free(pullright_name); + + XtOverrideTranslations(parent->submenu_id, parentTrans); + } +} + + void +gui_mch_add_menu_item(menu, parent) + GuiMenu *menu; + GuiMenu *parent; +{ + menu->submenu_id = (Widget)0; + menu->id = XtVaCreateManagedWidget(menu->name, + smeBSBObjectClass, parent->submenu_id, + XtNforeground, gui.menu_fg_pixel, + XtNbackground, gui.menu_bg_pixel, + NULL); + XtAddCallback(menu->id, XtNcallback, gui_x11_menu_cb, + (XtPointer)menu); +} + +/* + * Destroy the machine specific menu widget. + */ + void +gui_mch_destroy_menu(menu) + GuiMenu *menu; +{ + if (menu->id != (Widget)NULL) + { + /* + * This is a hack for the Athena simpleMenuWidget to keep it from + * getting a BadValue error when it's last child is destroyed. We + * check to see if this is the last child and if so, go ahead and + * delete the parent ahead of time. The parent will delete it's + * children like all good widgets do. + */ + if (XtParent(menu->id) != menuBar) + { + int num_children; + + XtVaGetValues(XtParent(menu->id), + XtNnumChildren, &num_children, NULL); + if (num_children <= 1) + XtDestroyWidget(XtParent(menu->id)); + else + XtDestroyWidget(menu->id); + } + else + XtDestroyWidget(menu->id); + menu->id = (Widget)NULL; + } +} + +/* + * Reorder the menus so "Help" is the rightmost item on the menu. + */ + static void +gui_athena_reorder_menus() +{ + Widget *children; + Widget help_widget = (Widget)NULL; + int num_children; + int i; + + XtVaGetValues(menuBar, + XtNchildren, &children, + XtNnumChildren, &num_children, + NULL); + + XtUnmanageChildren(children, num_children); + + for (i = 0; i < num_children - 1; i++) + if (help_widget == (Widget)NULL) + { + if (strcmp((char *)XtName(children[i]), "Help") == 0) + { + help_widget = children[i]; + children[i] = children[i + 1]; + } + } + else + children[i] = children[i + 1]; + + if (help_widget != (Widget)NULL) + children[num_children - 1] = help_widget; + + XtManageChildren(children, num_children); +} + + +/* + * Scrollbar stuff: + */ + + void +gui_mch_create_which_components() +{ + static int prev_which_scrollbars[3] = {-1, -1, -1}; + static int prev_menu_is_active = -1; + + int i; + WIN *wp; + + /* + * When removing the left/right scrollbar and creating the right/left + * scrollbar, we have to force a redraw (the size of the text area doesn't + * change). + */ + if (prev_which_scrollbars[SB_LEFT] != gui.which_scrollbars[SB_LEFT] && + prev_which_scrollbars[SB_RIGHT] != gui.which_scrollbars[SB_RIGHT]) + must_redraw = CLEAR; + + gui_x11_use_resize_callback(textArea, FALSE); + + for (i = 0; i < 3; i++) + { + if (gui.which_scrollbars[i] != prev_which_scrollbars[i]) + { + if (gui.which_scrollbars[i]) + { + switch (i) + { + /* When adding the left one, we need to reorder them all */ + case SB_LEFT: + XtUnmanageChild(textArea); + if (gui.which_scrollbars[SB_RIGHT]) + XtUnmanageChild(scrollbarBox[SB_RIGHT]); + XtManageChild(scrollbarBox[SB_LEFT]); + XtManageChild(textArea); + if (gui.which_scrollbars[SB_RIGHT]) + XtManageChild(scrollbarBox[SB_RIGHT]); + + /* + * When adding at the left and we have a bottom + * scrollbar, we need to reorder these too. + */ + if (gui.which_scrollbars[SB_BOTTOM]) + { + XtUnmanageChild(bottomScrollbar); + if (gui.which_scrollbars[SB_RIGHT]) + XtUnmanageChild(rightBottomScrollFiller); + + XtManageChild(leftBottomScrollFiller); + XtManageChild(bottomScrollbar); + if (gui.which_scrollbars[SB_RIGHT]) + XtManageChild(rightBottomScrollFiller); + } + break; + + case SB_RIGHT: + XtManageChild(rightBottomScrollFiller); + XtManageChild(scrollbarBox[i]); + break; + + case SB_BOTTOM: + /* Unmanage the bottom scrollbar and fillers */ + XtUnmanageChild(leftBottomScrollFiller); + XtUnmanageChild(bottomScrollbar); + XtUnmanageChild(rightBottomScrollFiller); + + /* + * Now manage the bottom scrollbar and fillers that + * are supposed to be there. + */ + if (gui.which_scrollbars[SB_LEFT]) + XtManageChild(leftBottomScrollFiller); + XtManageChild(bottomScrollbar); + if (gui.which_scrollbars[SB_RIGHT]) + XtManageChild(rightBottomScrollFiller); + + XtManageChild(scrollbarBox[i]); + break; + } + } + else + { + switch (i) + { + case SB_LEFT: + XtUnmanageChild(leftBottomScrollFiller); + break; + + case SB_RIGHT: + XtUnmanageChild(rightBottomScrollFiller); + break; + } + XtUnmanageChild(scrollbarBox[i]); + } + } + if (gui.which_scrollbars[i] != prev_which_scrollbars[i]) + { + if (i == SB_LEFT || i == SB_RIGHT) + { + if (gui.which_scrollbars[i]) + { + /* Scrollbar box has just appeared */ + gui.new_sb[i] = TRUE; + } + else if (prev_which_scrollbars[i] == TRUE) + { + /* Scrollbar box has just been deleted */ + for (wp = firstwin; wp != NULL; wp = wp->w_next) + XtDestroyWidget(wp->w_scrollbar.id[i]); + } + } + } + prev_which_scrollbars[i] = gui.which_scrollbars[i]; + } + + if (gui.menu_is_active != prev_menu_is_active) + { + if (gui.menu_is_active) + { + XtUnmanageChild(menuBar); + XtUnmanageChild(vimForm); + if (gui.which_scrollbars[SB_BOTTOM]) + XtUnmanageChild(scrollbarBox[SB_BOTTOM]); + + XtManageChild(menuBar); + XtManageChild(vimForm); + if (gui.which_scrollbars[SB_BOTTOM]) + XtManageChild(scrollbarBox[SB_BOTTOM]); + } + else + XtUnmanageChild(menuBar); + prev_menu_is_active = gui.menu_is_active; + } + + gui_x11_use_resize_callback(textArea, TRUE); + if (vimForm != (Widget)NULL && XtIsRealized(vimForm)) + gui_mch_set_winsize(); +} + + +/* + * Vertical scrollbar stuff: + */ + void +gui_mch_update_scrollbars(worst_update, which_sb) + int worst_update; + int which_sb; /* SB_LEFT or SB_RIGHT */ +{ + WIN *wp; + GuiScrollbar *sb; + int idx; + Dimension h; /* Height of scrollbar (in pixels) */ + Dimension y; /* Coord of top of scrollbar (in pixels) */ + int tmp; + float val = 0., size = 0.; + + if (worst_update >= SB_UPDATE_HEIGHT) + { + XawPanedSetRefigureMode(scrollbarBox[which_sb], False); + gui_x11_use_resize_callback(textArea, FALSE); + } + + /* + * This has to get cleared manually since Athena doesn't tell us when the + * draggin' stops. + */ + gui.dragged_sb = SB_NONE; + + for (wp = firstwin, idx = 0; wp; wp = wp->w_next, idx++) + { + sb = &wp->w_scrollbar; + if (sb->update[which_sb] >= SB_UPDATE_VALUE) + { + val = (float)(sb->value - 1) / (float)sb->max; + size = (float)sb->size / (float)sb->max; + } + if (sb->update[which_sb] == SB_UPDATE_CREATE) + { + sb->id[which_sb] = XtVaCreateManagedWidget("scrollBar", + vim_scrollbarWidgetClass, scrollbarBox[which_sb], + XtNborderWidth, 1, + XtNdefaultDistance, 0, + XtNallowResize, True, + XtNpreferredPaneSize, True, + XtNresizeToPreferred, True, + XtNskipAdjust, True, + XtNorientation, XtorientVertical, + XtNshowGrip, False, + XtNforeground, gui.scroll_fg_pixel, + XtNbackground, gui.scroll_bg_pixel, + NULL); + XtAddCallback(sb->id[which_sb], XtNjumpProc, + gui_athena_scroll_cb_jump, (XtPointer)wp); + XtAddCallback(sb->id[which_sb], XtNscrollProc, + gui_athena_scroll_cb_scroll, (XtPointer)wp); + } + if (sb->update[which_sb] >= SB_UPDATE_HEIGHT) + { + h = sb->height * gui.char_height + + sb->status_height * gui.char_height / 2; + y = wp->w_winpos * gui.char_height + gui.border_offset; + + if (wp == firstwin) + { + /* Height of top scrollbar includes width of top border */ + h += gui.border_offset; + } + else + { + /* + * Height of other scrollbars includes half of status bar above + */ + tmp = wp->w_prev->w_status_height * (gui.char_height + 1) / 2; + h += tmp; + y -= tmp; + } + + XtVaSetValues(sb->id[which_sb], + XtNheight, h, + XtNmin, h, + XtNmax, h, + XtNy, y, + NULL); + vim_XawScrollbarSetThumb(sb->id[which_sb], val, size, 1.0); + } + else if (sb->update[which_sb] == SB_UPDATE_VALUE) + { + vim_XawScrollbarSetThumb(sb->id[which_sb], val, size, 1.0); + } + sb->update[which_sb] = SB_UPDATE_NOTHING; + } + + if (worst_update >= SB_UPDATE_HEIGHT) + { + if (worst_update >= SB_UPDATE_CREATE) + gui_mch_reorder_scrollbars(which_sb); + XawPanedSetRefigureMode(scrollbarBox[which_sb], True); + gui_x11_use_resize_callback(textArea, TRUE); + } +} + + void +gui_mch_reorder_scrollbars(which_sb) + int which_sb; +{ + WIN *wp; + + if (which_sb == SB_LEFT) + XtUnmanageChild(leftScrollbarFiller); + else + XtUnmanageChild(rightScrollbarFiller); + + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp->w_scrollbar.id[which_sb] != NULL) + XtUnmanageChild(wp->w_scrollbar.id[which_sb]); + + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp->w_scrollbar.id[which_sb] != NULL) + XtManageChild(wp->w_scrollbar.id[which_sb]); + + if (which_sb == SB_LEFT) + XtManageChild(leftScrollbarFiller); + else + XtManageChild(rightScrollbarFiller); + +} + + void +gui_mch_destroy_scrollbar(wp) + WIN *wp; +{ + if (gui.which_scrollbars[SB_LEFT]) + XtDestroyWidget(wp->w_scrollbar.id[SB_LEFT]); + if (gui.which_scrollbars[SB_RIGHT]) + XtDestroyWidget(wp->w_scrollbar.id[SB_RIGHT]); + gui.num_scrollbars--; +} + + +/* + * Horizontal scrollbar stuff: + */ + void +gui_mch_update_horiz_scrollbar(value, size, max) + int value; + int size; + int max; +{ + static int prev_value = -1, prev_size = -1, prev_max = -1; + float val, shown, maxval; + + if (value == prev_value && size == prev_size && max == prev_max) + return; + + prev_value = value; + prev_size = size; + prev_max = max; + + if (max == 1) /* maximum is one more than maximal value */ + { + val = 0.0; + shown = 1.0; + maxval = 0.0; + } + else + { + val = (float)value / (float)max; + shown = (float)size / (float)max; + maxval = 1.0; + } + vim_XawScrollbarSetThumb(bottomScrollbar, val, shown, maxval); +} + + Window +gui_mch_get_wid() +{ + return( XtWindow(textArea) ); +} + + static void +gui_athena_pullright_action(w, event, args, nargs) + Widget w; + XEvent *event; + String *args; + Cardinal *nargs; +{ + Widget menuw; + Dimension width, height; + char_u *pullright_name; + Widget popup; + + if (event->type != MotionNotify) + return; + + /* Get the active entry for the current menu */ + if ((menuw = XawSimpleMenuGetActiveEntry(w)) == (Widget)NULL) + return; + + XtVaGetValues(w, + XtNwidth, &width, + XtNheight, &height, + NULL); + + if (event->xmotion.x >= width || event->xmotion.y >= height) + return; + + /* We do the pull-off when the pointer is in the rightmost 1/4th */ + if (event->xmotion.x < (width * 3) / 4) + return; + + pullright_name = strnsave(XtName(menuw), strlen(XtName(menuw)) + + strlen("-pullright")); + strcat(pullright_name, "-pullright"); + popup = XtNameToWidget(w, pullright_name); + vim_free(pullright_name); + + if (popup == (Widget)NULL) + return; + + XtVaSetValues(popup, + XtNx, event->xmotion.x_root, + XtNy, event->xmotion.y_root - 7, + NULL); + + XtPopup(popup, XtGrabExclusive); +} diff --git a/usr.bin/vim/gui_motif.c b/usr.bin/vim/gui_motif.c new file mode 100644 index 00000000000..b50e1c34839 --- /dev/null +++ b/usr.bin/vim/gui_motif.c @@ -0,0 +1,794 @@ +/* $OpenBSD: gui_motif.c,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * GUI/Motif support by Robert Webb + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#if (XmVersion >= 1002) +# include +#endif + +#include +#include +#include + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" +#include "ops.h" + +extern Widget vimShell; + +static Widget vimForm; +static Widget textArea; +static Widget scrollbarBox[3]; /* Left, right & bottom scrollbar boxes */ +static Widget menuBar; + +/* + * Call-back routines. + */ + + static void +scroll_cb(w, client_data, call_data) + Widget w; + XtPointer client_data, call_data; +{ + char_u bytes[4 + sizeof(long_u)]; + WIN *wp; + GuiScrollbar *sb; + int sb_num; + + if (((XmScrollBarCallbackStruct *)call_data)->reason == XmCR_DRAG) + gui.dragged_sb = (XtParent(w) == scrollbarBox[SB_LEFT]) ? SB_LEFT + : SB_RIGHT; + else + gui.dragged_sb = SB_NONE; + gui.dragged_wp = (WIN *)client_data; + sb_num = 0; + for (wp = firstwin; wp != gui.dragged_wp && wp != NULL; wp = wp->w_next) + sb_num++; + + bytes[0] = CSI; + bytes[1] = KS_SCROLLBAR; + bytes[2] = K_FILLER; + bytes[3] = (char_u)sb_num; + if (gui.dragged_wp == NULL) + sb = &gui.cmdline_sb; + else + sb = &wp->w_scrollbar; + sb->value = ((XmScrollBarCallbackStruct *)call_data)->value; + add_long_to_buf((long_u)sb->value, bytes + 4); + add_to_input_buf(bytes, 4 + sizeof(long_u)); +} + + static void +horiz_scroll_cb(w, client_data, call_data) + Widget w; + XtPointer client_data, call_data; +{ + char_u bytes[3 + sizeof(long_u)]; + + if (((XmScrollBarCallbackStruct *)call_data)->reason == XmCR_DRAG) + gui.dragged_sb = SB_BOTTOM; + else + gui.dragged_sb = SB_NONE; + + bytes[0] = CSI; + bytes[1] = KS_HORIZ_SCROLLBAR; + bytes[2] = K_FILLER; + add_long_to_buf((long_u)((XmScrollBarCallbackStruct *)call_data)->value, + bytes + 3); + add_to_input_buf(bytes, 3 + sizeof(long_u)); +} + +/* + * End of call-back routines + */ + +/* + * Create all the motif widgets necessary. + */ + void +gui_mch_create_widgets() +{ + int i; + Dimension n; + + /* + * Start out by adding the configured border width into the border offset + */ + gui.border_offset = gui.border_width; + + /* + * Install the tearOffModel resource converter. + */ +#if (XmVersion >= 1002) + XmRepTypeInstallTearOffModelConverter(); +#endif + + XtInitializeWidgetClass(xmFormWidgetClass); + XtInitializeWidgetClass(xmRowColumnWidgetClass); + XtInitializeWidgetClass(xmRowColumnWidgetClass); + XtInitializeWidgetClass(xmPrimitiveWidgetClass); + + vimForm = XtVaCreateManagedWidget("vimForm", + xmFormWidgetClass, vimShell, + XmNresizePolicy, XmRESIZE_GROW, + XmNforeground, gui.menu_fg_pixel, + XmNbackground, gui.menu_bg_pixel, + NULL); + + menuBar = XtVaCreateManagedWidget("menuBar", + xmRowColumnWidgetClass, vimForm, + XmNresizeHeight, False, +#if (XmVersion >= 1002) + XmNtearOffModel, XmTEAR_OFF_ENABLED, +#endif + XmNtopAttachment, XmATTACH_FORM, + XmNleftAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNrowColumnType, XmMENU_BAR, + XmNheight, gui.menu_height, + XmNforeground, gui.menu_fg_pixel, + XmNbackground, gui.menu_bg_pixel, + NULL); + + scrollbarBox[SB_LEFT] = XtVaCreateWidget("leftScrollBarBox", + xmRowColumnWidgetClass, vimForm, + XmNresizeWidth, False, + XmNtopAttachment, XmATTACH_WIDGET, + XmNtopWidget, menuBar, + XmNbottomAttachment, XmATTACH_FORM, + XmNleftAttachment, XmATTACH_FORM, + XmNmarginWidth, 0, + XmNmarginHeight, 0, + XmNspacing, 0, + XmNwidth, gui.scrollbar_width, + XmNforeground, gui.scroll_fg_pixel, + XmNbackground, gui.scroll_fg_pixel, + NULL); + + scrollbarBox[SB_RIGHT] = XtVaCreateWidget("rightScrollBarBox", + xmRowColumnWidgetClass, vimForm, + XmNresizeWidth, False, + XmNtopAttachment, XmATTACH_WIDGET, + XmNtopWidget, menuBar, + XmNbottomAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNmarginWidth, 0, + XmNmarginHeight, 0, + XmNspacing, 0, + XmNwidth, gui.scrollbar_width, + XmNforeground, gui.scroll_fg_pixel, + XmNbackground, gui.scroll_fg_pixel, + NULL); + + scrollbarBox[SB_BOTTOM] = XtVaCreateWidget("bottomScrollBarBox", + xmScrollBarWidgetClass, vimForm, + XmNorientation, XmHORIZONTAL, + XmNminimum, 0, + XmNvalue, 0, + XmNsliderSize, Columns, + XmNmaximum, Columns, /* Motif want one more than actual max */ + XmNresizeHeight, False, + XmNbottomAttachment, XmATTACH_FORM, + XmNheight, gui.scrollbar_width, + XmNshadowThickness, 1, + XmNbackground, gui.scroll_fg_pixel, + XmNtroughColor, gui.scroll_bg_pixel, + NULL); + XtAddCallback(scrollbarBox[SB_BOTTOM], XmNvalueChangedCallback, + horiz_scroll_cb, (XtPointer)NULL); + XtAddCallback(scrollbarBox[SB_BOTTOM], XmNdragCallback, + horiz_scroll_cb, (XtPointer)NULL); + + textArea = XtVaCreateManagedWidget("textArea", + xmPrimitiveWidgetClass, vimForm, + XmNtopAttachment, XmATTACH_WIDGET, + XmNtopWidget, menuBar, + XmNbackground, gui.back_pixel, + + /* These take some control away from the user, but avoids making them + * add resources to get a decent looking setup. */ + XmNborderWidth, 0, + XmNhighlightThickness, 0, + XmNshadowThickness, 0, + NULL); + + /* + * If there are highlight or shadow borders, add their widths to our + * border offset so we don't draw over them. + */ + XtVaGetValues(textArea, XmNhighlightThickness, &n, NULL); + gui.border_offset += n; + XtVaGetValues(textArea, XmNshadowThickness, &n, NULL); + gui.border_offset += n; + + /* Create the command line scroll bars */ + for (i = 0; i < 2; i++) + { + gui.cmdline_sb.id[i] = XtVaCreateManagedWidget("cmdlineScrollBar", + xmScrollBarWidgetClass, scrollbarBox[i], + XmNshadowThickness, 1, + XmNshowArrows, False, + XmNbackground, gui.scroll_fg_pixel, + XmNtroughColor, gui.scroll_fg_pixel, + NULL); + XtAddCallback(gui.cmdline_sb.id[i], XmNvalueChangedCallback, + scroll_cb, (XtPointer)NULL); + XtAddCallback(gui.cmdline_sb.id[i], XmNdragCallback, + scroll_cb, (XtPointer)NULL); + } + gui.num_scrollbars = 1; + + /* + * Text area callbacks + */ + XtAddEventHandler(textArea, VisibilityChangeMask, FALSE, + gui_x11_visibility_cb, (XtPointer)0); + + XtAddEventHandler(textArea, ExposureMask, FALSE, gui_x11_expose_cb, + (XtPointer)0); + + XtAddEventHandler(textArea, StructureNotifyMask, FALSE, + gui_x11_resize_window_cb, (XtPointer)0); + + XtAddEventHandler(textArea, FocusChangeMask, FALSE, gui_x11_focus_change_cb, + (XtPointer)0); + + XtAddEventHandler(textArea, KeyPressMask, FALSE, gui_x11_key_hit_cb, + (XtPointer)0); + + XtAddEventHandler(textArea, ButtonPressMask | ButtonReleaseMask | + ButtonMotionMask, FALSE, gui_x11_mouse_cb, (XtPointer)0); +} + + int +gui_mch_get_winsize() +{ + Dimension n; + Dimension base_width = 0, base_height = 0; + + if (gui.which_scrollbars[SB_LEFT]) + { + XtVaGetValues(scrollbarBox[SB_LEFT], XmNwidth, &n, NULL); + base_width += n; + } + if (gui.which_scrollbars[SB_RIGHT]) + { + XtVaGetValues(scrollbarBox[SB_RIGHT], XmNwidth, &n, NULL); + base_width += n; + } + if (gui.which_scrollbars[SB_BOTTOM]) + { + XtVaGetValues(scrollbarBox[SB_BOTTOM], XmNheight, &n, NULL); + base_height += n; + } + + base_height += 2 * gui.border_offset; + base_width += 2 * gui.border_offset; + + if (gui.menu_is_active) + { + XtVaGetValues(menuBar, XmNheight, &n, NULL); + base_height += n; + } + + XtVaGetValues(vimShell, XmNheight, &n, NULL); + gui.num_rows = (int)(n - base_height) / (int)gui.char_height; + + XtVaGetValues(vimShell, XmNwidth, &n, NULL); + gui.num_cols = (int)(n - base_width) / (int)gui.char_width; + + Rows = gui.num_rows; + Columns = gui.num_cols; + gui_reset_scroll_region(); + + return OK; +} + + void +gui_mch_set_winsize() +{ + Dimension left_width, right_width, bottom_height, menu_height; + Dimension base_width = 0, base_height = 0; + + base_width += 2 * gui.border_offset; + base_height += 2 * gui.border_offset; + + if (gui.which_scrollbars[SB_LEFT]) + { + XtVaGetValues(scrollbarBox[SB_LEFT], XmNwidth, &left_width, NULL); + base_width += left_width; + } + if (gui.which_scrollbars[SB_RIGHT]) + { + XtVaGetValues(scrollbarBox[SB_RIGHT], XmNwidth, &right_width, NULL); + base_width += right_width; + } + if (gui.which_scrollbars[SB_BOTTOM]) + { + XtVaGetValues(scrollbarBox[SB_BOTTOM], XmNheight, &bottom_height, NULL); + base_height += bottom_height; + } + if (gui.menu_is_active) + { + XtVaGetValues(menuBar, XmNheight, &menu_height, NULL); + base_height += menu_height; + } + + XtVaSetValues(vimShell, +#ifdef XmNbaseWidth + XmNbaseWidth, base_width, + XmNbaseHeight, base_height, +#endif + XmNwidthInc, gui.char_width, + XmNheightInc, gui.char_height, + XmNminWidth, base_width + MIN_COLUMNS * gui.char_width, + XmNminHeight, base_height + MIN_ROWS * gui.char_height, + XmNwidth, base_width + Columns * gui.char_width, + XmNheight, base_height + Rows * gui.char_height, + NULL); +} + +/* + * Menu stuff. + */ + + void +gui_mch_add_menu(menu, parent) + GuiMenu *menu; + GuiMenu *parent; +{ +#if (XmVersion >= 1002) + Widget widget; + XmString label = XmStringCreate((char *)menu->name, + XmFONTLIST_DEFAULT_TAG); +#else + XmString label = XmStringCreate((char *)menu->name, + XmSTRING_DEFAULT_CHARSET); +#endif + Widget shell; + + menu->id = XtVaCreateWidget("subMenu", + xmCascadeButtonWidgetClass, + (parent == NULL) ? menuBar : parent->submenu_id, + XmNlabelString, label, + XmNforeground, gui.menu_fg_pixel, + XmNbackground, gui.menu_bg_pixel, + NULL); + /* XtFree((char *)label); makes Lesstif crash */ + + /* if 'guic' contains 'g', make menu's contain grey items */ + if (vim_strchr(p_guioptions, GO_GREY) != NULL) + XtManageChild(menu->id); + + shell = XtVaCreateWidget("subMenuShell", + xmMenuShellWidgetClass, menu->id, + XmNwidth, 1, + XmNheight, 1, + XmNforeground, gui.menu_fg_pixel, + XmNbackground, gui.menu_bg_pixel, + NULL); + menu->submenu_id = XtVaCreateWidget("rowColumnMenu", + xmRowColumnWidgetClass, shell, + XmNrowColumnType, XmMENU_PULLDOWN, +#if (XmVersion >= 1002) + XmNtearOffModel, XmTEAR_OFF_ENABLED, +#endif + NULL); + +#if (XmVersion >= 1002) + /* Set the colors for the tear off widget */ + if ((widget = XmGetTearOffControl(menu->submenu_id)) != (Widget)NULL) + XtVaSetValues(widget, + XmNforeground, gui.menu_fg_pixel, + XmNbackground, gui.menu_bg_pixel, + NULL); +#endif + + XtVaSetValues(menu->id, + XmNsubMenuId, menu->submenu_id, + NULL); + + /* + * The "Help" menu is a special case, and should be placed at the far right + * hand side of the menu-bar. + */ + if (parent == NULL && STRCMP((char *)menu->name, "Help") == 0) + XtVaSetValues(menuBar, + XmNmenuHelpWidget, menu->id, + NULL); + + if (parent == NULL) + XtVaSetValues(XtParent(menu->id), + XmNforeground, gui.menu_fg_pixel, + XmNbackground, gui.menu_bg_pixel, + NULL); +} + + void +gui_mch_add_menu_item(menu, parent) + GuiMenu *menu; + GuiMenu *parent; +{ +#if (XmVersion >= 1002) + XmString label = XmStringCreate((char *)menu->name, + XmFONTLIST_DEFAULT_TAG); +#else + XmString label = XmStringCreate((char *)menu->name, + XmSTRING_DEFAULT_CHARSET); +#endif + + menu->submenu_id = (Widget)0; + menu->id = XtVaCreateWidget("subMenu", + xmPushButtonWidgetClass, parent->submenu_id, + XmNlabelString, label, + XmNforeground, gui.menu_fg_pixel, + XmNbackground, gui.menu_bg_pixel, + NULL); + /* XtFree((char *)label); makes Lesstif crash */ + + if (vim_strchr(p_guioptions, GO_GREY) != NULL) + XtManageChild(menu->id); + + XtAddCallback(menu->id, XmNactivateCallback, gui_x11_menu_cb, + (XtPointer)menu); +} + +/* + * Destroy the machine specific menu widget. + */ + void +gui_mch_destroy_menu(menu) + GuiMenu *menu; +{ + if (menu->id != (Widget)0) + XtDestroyWidget(menu->id); + if (menu->submenu_id != (Widget)0) + XtDestroyWidget(menu->submenu_id); +} + +/* + * Scrollbar stuff: + */ + + void +gui_mch_create_which_components() +{ + static int prev_which_scrollbars[3] = {FALSE, FALSE, FALSE}; + + int i; + char *attach = NULL, *widget = NULL; /* NOT char_u */ + WIN *wp; + + gui_x11_use_resize_callback(textArea, FALSE); + + for (i = 0; i < 3; i++) + { + switch (i) + { + case SB_LEFT: attach = XmNleftAttachment; + widget = XmNleftWidget; break; + case SB_RIGHT: attach = XmNrightAttachment; + widget = XmNrightWidget; break; + case SB_BOTTOM: attach = XmNbottomAttachment; + widget = XmNbottomWidget; break; + } + if (gui.which_scrollbars[i]) + { + XtManageChild(scrollbarBox[i]); + XtVaSetValues(textArea, + attach, XmATTACH_WIDGET, + widget, scrollbarBox[i], + NULL); + if (i != SB_BOTTOM && gui.which_scrollbars[SB_BOTTOM]) + XtVaSetValues(scrollbarBox[SB_BOTTOM], + attach, XmATTACH_WIDGET, + widget, scrollbarBox[i], + NULL); + } + else + { + XtUnmanageChild(scrollbarBox[i]); + XtVaSetValues(textArea, + attach, XmATTACH_FORM, + NULL); + if (i != SB_BOTTOM && gui.which_scrollbars[SB_BOTTOM]) + XtVaSetValues(scrollbarBox[SB_BOTTOM], + attach, XmATTACH_FORM, + NULL); + } + if (gui.which_scrollbars[i] != prev_which_scrollbars[i]) + { + if (i == SB_LEFT || i == SB_RIGHT) + { + if (gui.which_scrollbars[i]) + { + /* Scrollbar box has just appeared */ + gui.new_sb[i] = TRUE; + } + else + { + /* Scrollbar box has just been deleted */ + for (wp = firstwin; wp != NULL; wp = wp->w_next) + XtDestroyWidget(wp->w_scrollbar.id[i]); + } + } + prev_which_scrollbars[i] = gui.which_scrollbars[i]; + } + } + if (gui.menu_is_active) + { + XtManageChild(menuBar); + XtVaSetValues(textArea, XmNtopAttachment, XmATTACH_WIDGET, + XmNtopWidget, menuBar, NULL); + if (gui.which_scrollbars[SB_LEFT]) + { + XtVaSetValues(scrollbarBox[SB_LEFT], + XmNtopAttachment, XmATTACH_WIDGET, + XmNtopWidget, menuBar, + NULL); + } + if (gui.which_scrollbars[SB_RIGHT]) + { + XtVaSetValues(scrollbarBox[SB_RIGHT], + XmNtopAttachment, XmATTACH_WIDGET, + XmNtopWidget, menuBar, + NULL); + } + } + else + { + XtUnmanageChild(menuBar); + XtVaSetValues(textArea, XmNtopAttachment, XmATTACH_FORM, NULL); + if (gui.which_scrollbars[SB_LEFT]) + { + XtVaSetValues(scrollbarBox[SB_LEFT], + XmNtopAttachment, XmATTACH_FORM, NULL); + } + if (gui.which_scrollbars[SB_RIGHT]) + { + XtVaSetValues(scrollbarBox[SB_RIGHT], + XmNtopAttachment, XmATTACH_FORM, NULL); + } + } + gui_x11_use_resize_callback(textArea, TRUE); + if (vimForm != (Widget)NULL) + gui_mch_set_winsize(); +} + + +/* + * Vertical scrollbar stuff: + */ + + void +gui_mch_update_scrollbars(worst_update, which_sb) + int worst_update; + int which_sb; /* SB_LEFT or SB_RIGHT */ +{ + WIN *wp; + GuiScrollbar *sb; + int idx; + Dimension h; /* Height of scrollbar (in pixels) */ + Dimension y; /* Coord of top of scrollbar (in pixels) */ + int tmp; + int val = 0, size = 0, max = 0; + + if (worst_update >= SB_UPDATE_HEIGHT) + { + gui_x11_use_resize_callback(textArea, FALSE); + XtUnmanageChild(scrollbarBox[which_sb]); + } + + for (wp = firstwin, idx = 0; wp; wp = wp->w_next, idx++) + { + sb = &wp->w_scrollbar; + if (sb->update[which_sb] >= SB_UPDATE_VALUE) + { + val = sb->value; + size = sb->size; + max = sb->max + 1; /* Motif has max one past the end */ + } + if (sb->update[which_sb] == SB_UPDATE_CREATE) + { + sb->id[which_sb] = XtVaCreateManagedWidget("scrollBar", + xmScrollBarWidgetClass, scrollbarBox[which_sb], + XmNshadowThickness, 1, +#if (XmVersion >= 1002) /* What do we do otherwise? */ + XmNpositionIndex, idx, +#endif + XmNminimum, 1, + XmNmaximum, max, + XmNbackground, gui.scroll_fg_pixel, + XmNtroughColor, gui.scroll_bg_pixel, + NULL); + XtAddCallback(sb->id[which_sb], XmNvalueChangedCallback, + scroll_cb, (XtPointer)wp); + XtAddCallback(sb->id[which_sb], XmNdragCallback, + scroll_cb, (XtPointer)wp); + } + if (sb->update[which_sb] >= SB_UPDATE_HEIGHT) + { + h = sb->height * gui.char_height + + sb->status_height * gui.char_height / 2; + y = wp->w_winpos * gui.char_height; + + if (wp == firstwin) + { + /* Height of top scrollbar includes width of top border */ + h += gui.border_offset; + } + else + { + /* + * Height of other scrollbars includes half of status bar above + */ + tmp = wp->w_prev->w_status_height * (gui.char_height + 1) / 2; + h += tmp; + y += gui.border_offset - tmp; + } + + XtVaSetValues(sb->id[which_sb], + XmNvalue, val, + XmNsliderSize, size, + XmNpageIncrement, (size > 2 ? size - 2 : 1), + XmNmaximum, max, + XmNheight, h, + XmNy, y, + NULL); + } + else if (sb->update[which_sb] == SB_UPDATE_VALUE) + { + XtVaSetValues(sb->id[which_sb], + XmNvalue, val, + XmNsliderSize, size, + XmNpageIncrement, (size > 2 ? size - 2 : 1), + XmNmaximum, max, + NULL); + } + sb->update[which_sb] = SB_UPDATE_NOTHING; + } + + /* Command line scrollbar */ + sb = &gui.cmdline_sb; + max = sb->max + 1; /* Motif has max one past the end */ + if (sb->update[which_sb] == SB_UPDATE_HEIGHT) + { + h = lastwin->w_status_height * (gui.char_height + 1) / 2; + y = (Rows - sb->height) * gui.char_height - h; + h += sb->height * gui.char_height; + + /* Height of cmdline scrollbar includes width of bottom border */ + h += gui.border_offset; + + XtVaSetValues(sb->id[which_sb], + XmNvalue, sb->value, + XmNsliderSize, sb->size, + XmNmaximum, max, + XmNheight, h, + XmNy, y, + NULL); + } + else if (sb->update[which_sb] == SB_UPDATE_VALUE) + { + XtVaSetValues(sb->id[which_sb], + XmNvalue, sb->value, + XmNsliderSize, sb->size, + XmNmaximum, max, + NULL); + } + sb->update[which_sb] = SB_UPDATE_NOTHING; + + if (worst_update >= SB_UPDATE_HEIGHT) + { + if (worst_update >= SB_UPDATE_CREATE) + gui_mch_reorder_scrollbars(which_sb); + XtManageChild(scrollbarBox[which_sb]); + gui_x11_use_resize_callback(textArea, TRUE); + } +} + + void +gui_mch_reorder_scrollbars(which_sb) + int which_sb; +{ + Widget *children; + int num_children; + Widget tmp; + WIN *wp, *wp2; + int i, j; + + XtVaGetValues(scrollbarBox[which_sb], + XmNchildren, &children, + XmNnumChildren, &num_children, + NULL); + + /* Should be in same order as in the window list */ + wp = firstwin; + for (i = 0; i < num_children; i++, wp = wp->w_next) + { + if (wp == NULL) + break; /* Shouldn't happen */ + if (wp->w_scrollbar.id[which_sb] != children[i]) + { + /* It's in the wrong place, find what should go here */ + wp2 = wp->w_next; + for (j = i + 1; j < num_children; j++, wp2 = wp2->w_next) + { + if (wp2 == NULL) + break; /* Shouldn't happen */ + if (wp->w_scrollbar.id[which_sb] == children[j]) + break; /* Found it */ + } + if (j >= num_children || wp2 == NULL) + break; /* Shouldn't happen */ + tmp = children[i]; + children[i] = children[j]; + children[j] = tmp; + } + } + + XtVaSetValues(scrollbarBox[which_sb], + XmNchildren, children, + NULL); +} + + void +gui_mch_destroy_scrollbar(wp) + WIN *wp; +{ + if (gui.which_scrollbars[SB_LEFT]) + XtDestroyWidget(wp->w_scrollbar.id[SB_LEFT]); + if (gui.which_scrollbars[SB_RIGHT]) + XtDestroyWidget(wp->w_scrollbar.id[SB_RIGHT]); + gui.num_scrollbars--; +} + + +/* + * Horizontal scrollbar stuff: + */ + + void +gui_mch_update_horiz_scrollbar(value, size, max) + int value; + int size; + int max; +{ + static int prev_value = -1, prev_size = -1, prev_max = -1; + + if (value == prev_value && size == prev_size && max == prev_max) + return; + + prev_value = value; + prev_size = size; + prev_max = max; + + XtVaSetValues(scrollbarBox[SB_BOTTOM], + XmNvalue, value, + XmNsliderSize, size, + XmNpageIncrement, (size > 2 ? size - 2 : 1), + XmNmaximum, max, + NULL); +} + + Window +gui_mch_get_wid() +{ + return( XtWindow(textArea) ); +} diff --git a/usr.bin/vim/gui_x11.c b/usr.bin/vim/gui_x11.c new file mode 100644 index 00000000000..0664fc2e69f --- /dev/null +++ b/usr.bin/vim/gui_x11.c @@ -0,0 +1,2379 @@ +/* $OpenBSD: gui_x11.c,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * GUI/Motif support by Robert Webb + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +#include +#include +#include +#include +#include + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" +#include "ops.h" + +#define VIM_NAME "vim" +#define VIM_CLASS "Vim" + +/* Default resource values */ +#define DFLT_FONT "7x13" +#define DFLT_MENU_BG_COLOR "gray77" +#define DFLT_MENU_FG_COLOR "black" +#define DFLT_SCROLL_BG_COLOR "gray60" +#define DFLT_SCROLL_FG_COLOR "gray77" + +Widget vimShell = (Widget)NULL; + +static XtAppContext app_context; +static Atom Atom_WM_DELETE_WINDOW; + +static Pixel gui_x11_get_color __ARGS((char_u *)); +static void gui_x11_set_color __ARGS((GC, Pixel)); +static void gui_x11_set_font __ARGS((GC, Font)); +static void gui_x11_check_copy_area __ARGS((void)); +static void gui_x11_update_menus_recurse __ARGS((GuiMenu *, int)); + +static void gui_x11_request_selection_cb __ARGS((Widget, XtPointer, + Atom *, Atom *, XtPointer, + long_u *, int *)); + +static Boolean gui_x11_convert_selection_cb __ARGS((Widget, Atom *, Atom *, + Atom *, XtPointer *, + long_u *, int *)); + +static void gui_x11_lose_ownership_cb __ARGS((Widget, Atom *)); + +static void gui_x11_wm_protocol_handler __ARGS((Widget, XtPointer, + XEvent *, Boolean *)); + +static void gui_x11_invert_area __ARGS((int, int, int, int)); +static void gui_x11_yank_selection __ARGS((int, int, int, int)); +static void gui_x11_get_word_boundaries __ARGS((GuiSelection *, int, int)); +static int gui_x11_get_line_end __ARGS((int)); +static void gui_x11_update_selection __ARGS((GuiSelection *, int, int, int, + int)); + +#define char_class(c) (c <= ' ' ? ' ' : iswordchar(c)) + +#define invert_rectangle(r, c, nr, nc) \ + XFillRectangle(gui.dpy, gui.wid, gui.invert_gc, \ + FILL_X(c), FILL_Y(r), (nc) * gui.char_width, \ + (nr) * gui.char_height) + + +static struct +{ + KeySym key_sym; + char_u vim_code0; + char_u vim_code1; +} special_keys[] = +{ + {XK_Up, 'k', 'u'}, + {XK_Down, 'k', 'd'}, + {XK_Left, 'k', 'l'}, + {XK_Right, 'k', 'r'}, + + {XK_F1, 'k', '1'}, + {XK_F2, 'k', '2'}, + {XK_F3, 'k', '3'}, + {XK_F4, 'k', '4'}, + {XK_F5, 'k', '5'}, + {XK_F6, 'k', '6'}, + {XK_F7, 'k', '7'}, + {XK_F8, 'k', '8'}, + {XK_F9, 'k', '9'}, + {XK_F10, 'k', ';'}, + + {XK_F11, 'F', '1'}, + {XK_F12, 'F', '2'}, + {XK_F13, 'F', '3'}, + {XK_F14, 'F', '4'}, + {XK_F15, 'F', '5'}, + {XK_F16, 'F', '6'}, + {XK_F17, 'F', '7'}, + {XK_F18, 'F', '8'}, + {XK_F19, 'F', '9'}, + {XK_F20, 'F', 'A'}, + + {XK_F21, 'F', 'B'}, + {XK_F22, 'F', 'C'}, + {XK_F23, 'F', 'D'}, + {XK_F24, 'F', 'E'}, + {XK_F25, 'F', 'F'}, + {XK_F26, 'F', 'G'}, + {XK_F27, 'F', 'H'}, + {XK_F28, 'F', 'I'}, + {XK_F29, 'F', 'J'}, + {XK_F30, 'F', 'K'}, + + {XK_F31, 'F', 'L'}, + {XK_F32, 'F', 'M'}, + {XK_F33, 'F', 'N'}, + {XK_F34, 'F', 'O'}, + {XK_F35, 'F', 'P'}, /* keysymdef.h defines up to F35 */ + + {XK_Help, '%', '1'}, + {XK_Undo, '&', '8'}, + {XK_BackSpace, 'k', 'b'}, + {XK_Insert, 'k', 'I'}, + {XK_Delete, 'k', 'D'}, + {XK_Home, 'k', 'h'}, + {XK_End, '@', '7'}, + {XK_Prior, 'k', 'P'}, + {XK_Next, 'k', 'N'}, + {XK_Print, '%', '9'}, + + /* Keypad keys: */ +#ifdef XK_KP_Left + {XK_KP_Left, 'k', 'l'}, + {XK_KP_Right, 'k', 'r'}, + {XK_KP_Up, 'k', 'u'}, + {XK_KP_Down, 'k', 'd'}, + {XK_KP_Insert, 'k', 'I'}, + {XK_KP_Delete, 'k', 'D'}, + {XK_KP_Home, 'k', 'h'}, + {XK_KP_End, '@', '7'}, + {XK_KP_Prior, 'k', 'P'}, + {XK_KP_Next, 'k', 'N'}, +#endif + + /* End of list marker: */ + {(KeySym)0, 0, 0} +}; + +#define XtNboldColor "boldColor" +#define XtCBoldColor "BoldColor" +#define XtNitalicColor "italicColor" +#define XtCItalicColor "ItalicColor" +#define XtNunderlineColor "underlineColor" +#define XtCUnderlineColor "UnderlineColor" +#define XtNcursorColor "cursorColor" +#define XtCCursorColor "CursorColor" +#define XtNboldFont "boldFont" +#define XtCBoldFont "BoldFont" +#define XtNitalicFont "italicFont" +#define XtCItalicFont "ItalicFont" +#define XtNboldItalicFont "boldItalicFont" +#define XtCBoldItalicFont "BoldItalicFont" +#define XtNscrollbarWidth "scrollbarWidth" +#define XtCScrollbarWidth "ScrollbarWidth" +#define XtNmenuHeight "menuHeight" +#define XtCMenuHeight "MenuHeight" + +/* Resources for setting the foreground and background colors of menus */ +#define XtNmenuBackground "menuBackground" +#define XtCMenuBackground "MenuBackground" +#define XtNmenuForeground "menuForeground" +#define XtCMenuForeground "MenuForeground" + +/* Resources for setting the foreground and background colors of scrollbars */ +#define XtNscrollBackground "scrollBackground" +#define XtCScrollBackground "ScrollBackground" +#define XtNscrollForeground "scrollForeground" +#define XtCScrollForeground "ScrollForeground" + +/* + * X Resources: + */ +static XtResource vim_resources[] = +{ + { + XtNforeground, + XtCForeground, + XtRPixel, + sizeof(Pixel), + XtOffsetOf(Gui, norm_pixel), + XtRString, + XtDefaultForeground + }, + { + XtNbackground, + XtCBackground, + XtRPixel, + sizeof(Pixel), + XtOffsetOf(Gui, back_pixel), + XtRString, + XtDefaultBackground + }, + { + XtNboldColor, + XtCBoldColor, + XtRPixel, + sizeof(Pixel), + XtOffsetOf(Gui, bold_pixel), + XtRString, + XtDefaultForeground + }, + { + XtNitalicColor, + XtCItalicColor, + XtRPixel, + sizeof(Pixel), + XtOffsetOf(Gui, ital_pixel), + XtRString, + XtDefaultForeground + }, + { + XtNunderlineColor, + XtCUnderlineColor, + XtRPixel, + sizeof(Pixel), + XtOffsetOf(Gui, underline_pixel), + XtRString, + XtDefaultForeground + }, + { + XtNcursorColor, + XtCCursorColor, + XtRPixel, + sizeof(Pixel), + XtOffsetOf(Gui, cursor_pixel), + XtRString, + XtDefaultForeground + }, + { + XtNfont, + XtCFont, + XtRString, + sizeof(String *), + XtOffsetOf(Gui, dflt_font), + XtRImmediate, + XtDefaultFont + }, + { + XtNboldFont, + XtCBoldFont, + XtRString, + sizeof(String *), + XtOffsetOf(Gui, dflt_bold_fn), + XtRImmediate, + "" + }, + { + XtNitalicFont, + XtCItalicFont, + XtRString, + sizeof(String *), + XtOffsetOf(Gui, dflt_ital_fn), + XtRImmediate, + "" + }, + { + XtNboldItalicFont, + XtCBoldItalicFont, + XtRString, + sizeof(String *), + XtOffsetOf(Gui, dflt_boldital_fn), + XtRImmediate, + "" + }, + { + XtNgeometry, + XtCGeometry, + XtRString, + sizeof(String *), + XtOffsetOf(Gui, geom), + XtRImmediate, + "" + }, + { + XtNreverseVideo, + XtCReverseVideo, + XtRBool, + sizeof(Bool), + XtOffsetOf(Gui, rev_video), + XtRImmediate, + (XtPointer) False + }, + { + XtNborderWidth, + XtCBorderWidth, + XtRInt, + sizeof(int), + XtOffsetOf(Gui, border_width), + XtRImmediate, + (XtPointer) 2 + }, + { + XtNscrollbarWidth, + XtCScrollbarWidth, + XtRInt, + sizeof(int), + XtOffsetOf(Gui, scrollbar_width), + XtRImmediate, + (XtPointer) SB_DEFAULT_WIDTH + }, + { + XtNmenuHeight, + XtCMenuHeight, + XtRInt, + sizeof(int), + XtOffsetOf(Gui, menu_height), + XtRImmediate, + (XtPointer) MENU_DEFAULT_HEIGHT + }, + { + XtNmenuForeground, + XtCMenuForeground, + XtRPixel, + sizeof(Pixel), + XtOffsetOf(Gui, menu_fg_pixel), + XtRString, + DFLT_MENU_FG_COLOR + }, + { + XtNmenuBackground, + XtCMenuBackground, + XtRPixel, + sizeof(Pixel), + XtOffsetOf(Gui, menu_bg_pixel), + XtRString, + DFLT_MENU_BG_COLOR + }, + { + XtNscrollForeground, + XtCScrollForeground, + XtRPixel, + sizeof(Pixel), + XtOffsetOf(Gui, scroll_fg_pixel), + XtRString, + DFLT_SCROLL_FG_COLOR + }, + { + XtNscrollBackground, + XtCScrollBackground, + XtRPixel, + sizeof(Pixel), + XtOffsetOf(Gui, scroll_bg_pixel), + XtRString, + DFLT_SCROLL_BG_COLOR + }, +}; + +/* + * This table holds all the X GUI command line options allowed. This includes + * the standard ones so that we can skip them when vim is started without the + * GUI (but the GUI might start up later). + * When changing this, also update doc/vim_gui.txt and the usage message!!! + */ +static XrmOptionDescRec cmdline_options[] = +{ + /* We handle these options ourselves */ + {"-bg", ".background", XrmoptionSepArg, NULL}, + {"-background", ".background", XrmoptionSepArg, NULL}, + {"-fg", ".foreground", XrmoptionSepArg, NULL}, + {"-foreground", ".foreground", XrmoptionSepArg, NULL}, + {"-bold", ".boldColor", XrmoptionSepArg, NULL}, + {"-italic", ".italicColor", XrmoptionSepArg, NULL}, + {"-ul", ".underlineColor", XrmoptionSepArg, NULL}, + {"-underline", ".underlineColor", XrmoptionSepArg, NULL}, + {"-cursor", ".cursorColor", XrmoptionSepArg, NULL}, + {"-fn", ".font", XrmoptionSepArg, NULL}, + {"-font", ".font", XrmoptionSepArg, NULL}, + {"-boldfont", ".boldFont", XrmoptionSepArg, NULL}, + {"-italicfont", ".italicFont", XrmoptionSepArg, NULL}, + {"-geom", ".geometry", XrmoptionSepArg, NULL}, + {"-geometry", ".geometry", XrmoptionSepArg, NULL}, + {"-reverse", "*reverseVideo", XrmoptionNoArg, "True"}, + {"-rv", "*reverseVideo", XrmoptionNoArg, "True"}, + {"+reverse", "*reverseVideo", XrmoptionNoArg, "False"}, + {"+rv", "*reverseVideo", XrmoptionNoArg, "False"}, + {"-display", ".display", XrmoptionSepArg, NULL}, + {"-iconic", "*iconic", XrmoptionNoArg, "True"}, + {"-name", ".name", XrmoptionSepArg, NULL}, + {"-bw", ".borderWidth", XrmoptionSepArg, NULL}, + {"-borderwidth", ".borderWidth", XrmoptionSepArg, NULL}, + {"-sw", ".scrollbarWidth", XrmoptionSepArg, NULL}, + {"-scrollbarwidth", ".scrollbarWidth", XrmoptionSepArg, NULL}, + {"-mh", ".menuHeight", XrmoptionSepArg, NULL}, + {"-menuheight", ".menuHeight", XrmoptionSepArg, NULL}, + {"-xrm", NULL, XrmoptionResArg, NULL} +}; + +static int gui_argc = 0; +static char **gui_argv = NULL; + +/* + * Call-back routines. + */ + + void +gui_x11_timer_cb(timed_out, interval_id) + XtPointer timed_out; + XtIntervalId *interval_id; +{ + *((int *)timed_out) = TRUE; +} + + void +gui_x11_visibility_cb(w, dud, event, bool) + Widget w; + XtPointer dud; + XEvent *event; + Boolean *bool; +{ + if (event->type != VisibilityNotify) + return; + + gui.visibility = event->xvisibility.state; + + /* + * When we do an XCopyArea(), and the window is partially obscured, we want + * to receive an event to tell us whether it worked or not. + */ + XSetGraphicsExposures(gui.dpy, gui.text_gc, + gui.visibility != VisibilityUnobscured); +} + + void +gui_x11_expose_cb(w, dud, event, bool) + Widget w; + XtPointer dud; + XEvent *event; + Boolean *bool; +{ + XExposeEvent *gevent; + + if (event->type != Expose) + return; + + gevent = (XExposeEvent *)event; + gui_redraw(gevent->x, gevent->y, gevent->width, gevent->height); + + /* Clear the border areas if needed */ + if (gevent->x < FILL_X(0)) + XClearArea(gui.dpy, gui.wid, 0, 0, FILL_X(0), 0, False); + if (gevent->y < FILL_Y(0)) + XClearArea(gui.dpy, gui.wid, 0, 0, 0, FILL_Y(0), False); + if (gevent->x > FILL_X(Columns)) + XClearArea(gui.dpy, gui.wid, FILL_X(Columns), 0, 0, 0, False); + if (gevent->y > FILL_Y(Rows)) + XClearArea(gui.dpy, gui.wid, 0, FILL_Y(Rows), 0, 0, False); + + if (gui.selection.state != SELECT_CLEARED) + gui_x11_redraw_selection(gevent->x, gevent->y, gevent->width, + gevent->height); +} + + void +gui_x11_resize_window_cb(w, dud, event, bool) + Widget w; + XtPointer dud; + XEvent *event; + Boolean *bool; +{ + if (event->type != ConfigureNotify) + return; + + gui_resize_window(event->xconfigure.width, event->xconfigure.height); + + /* Make sure the border strips on the right and bottom get cleared. */ + XClearArea(gui.dpy, gui.wid, FILL_X(Columns), 0, 0, 0, False); + XClearArea(gui.dpy, gui.wid, 0, FILL_Y(Rows), 0, 0, False); +} + + void +gui_x11_focus_change_cb(w, data, event, bool) + Widget w; + XtPointer data; + XEvent *event; + Boolean *bool; +{ + if (event->type == FocusIn) + gui.in_focus = TRUE; + else + gui.in_focus = FALSE; + if (gui.row == gui.cursor_row && gui.col == gui.cursor_col) + INVALIDATE_CURSOR(); + gui_update_cursor(); +} + + void +gui_x11_key_hit_cb(w, dud, event, bool) + Widget w; + XtPointer dud; + XEvent *event; + Boolean *bool; +{ + XKeyPressedEvent *ev_press; + char_u string[3], string2[3]; + KeySym key_sym; + int num, i; + + ev_press = (XKeyPressedEvent *)event; + + num = XLookupString(ev_press, (char *)string, sizeof(string), + &key_sym, NULL); + + if (key_sym == XK_space) + string[0] = ' '; /* Otherwise Ctrl-Space doesn't work */ + + /* Check for Alt/Meta key (Mod1Mask) */ + if (num == 1 && (ev_press->state & Mod1Mask)) + { + /* + * Before we set the 8th bit, check to make sure the user doesn't + * already have a mapping defined for this sequence. We determine this + * by checking to see if the input would be the same without the + * Alt/Meta key. + */ + ev_press->state &= ~Mod1Mask; + if (XLookupString(ev_press, (char *)string2, sizeof(string2), + &key_sym, NULL) == 1 && string[0] == string2[0]) + string[0] |= 0x80; + ev_press->state |= Mod1Mask; + } + +#if 0 + if (num == 1 && string[0] == CSI) /* this doesn't work yet */ + { + string[1] = CSI; + string[2] = CSI; + num = 3; + } +#endif + + /* Check for special keys, making sure BS and DEL are recognised. */ + if (num == 0 || key_sym == XK_BackSpace || key_sym == XK_Delete) + { + for (i = 0; special_keys[i].key_sym != (KeySym)0; i++) + { + if (special_keys[i].key_sym == key_sym) + { + string[0] = CSI; + string[1] = special_keys[i].vim_code0; + string[2] = special_keys[i].vim_code1; + num = 3; + } + } + } + + /* Unrecognised key */ + if (num == 0) + return; + + /* Special keys (and a few others) may have modifiers */ + if (num == 3 || key_sym == XK_space || key_sym == XK_Tab + || key_sym == XK_Return || key_sym == XK_Linefeed + || key_sym == XK_Escape) + { + string2[0] = CSI; + string2[1] = KS_MODIFIER; + string2[2] = 0; + if (ev_press->state & ShiftMask) + string2[2] |= MOD_MASK_SHIFT; + if (ev_press->state & ControlMask) + string2[2] |= MOD_MASK_CTRL; + if (ev_press->state & Mod1Mask) + string2[2] |= MOD_MASK_ALT; + if (string2[2] != 0) + add_to_input_buf(string2, 3); + } + if (num == 1 && string[0] == Ctrl('C')) + { + trash_input_buf(); + got_int = TRUE; + } + add_to_input_buf(string, num); +} + + void +gui_x11_mouse_cb(w, dud, event, bool) + Widget w; + XtPointer dud; + XEvent *event; + Boolean *bool; +{ + static XtIntervalId timer = (XtIntervalId)0; + static int timed_out = TRUE; + + int button; + int repeated_click = FALSE; + int x, y; + int_u x_modifiers; + int_u vim_modifiers; + int checkfor; + + if (event->type == MotionNotify) + { + x = event->xmotion.x; + y = event->xmotion.y; + button = MOUSE_DRAG; + x_modifiers = event->xmotion.state; + } + else + { + x = event->xbutton.x; + y = event->xbutton.y; + if (event->type == ButtonPress) + { + /* Handle multiple clicks */ + if (!timed_out) + { + XtRemoveTimeOut(timer); + repeated_click = TRUE; + } + timed_out = FALSE; + timer = XtAppAddTimeOut(app_context, (long_u)p_mouset, + gui_x11_timer_cb, &timed_out); + switch (event->xbutton.button) + { + case Button1: button = MOUSE_LEFT; break; + case Button2: button = MOUSE_MIDDLE; break; + case Button3: button = MOUSE_RIGHT; break; + default: + return; /* Unknown button */ + } + } + else if (event->type == ButtonRelease) + button = MOUSE_RELEASE; + else + return; /* Unknown mouse event type */ + + x_modifiers = event->xbutton.state; + } + + vim_modifiers = 0x0; + if (x_modifiers & ShiftMask) + vim_modifiers |= MOUSE_SHIFT; + if (x_modifiers & ControlMask) + vim_modifiers |= MOUSE_CTRL; + if (x_modifiers & Mod1Mask) /* Alt or Meta key */ + vim_modifiers |= MOUSE_ALT; + + /* If an x11 selection is in progress, finish it */ + if (gui.selection.state == SELECT_IN_PROGRESS) + { + gui_x11_process_selection(button, x, y, repeated_click, vim_modifiers); + return; + } + + /* Determine which mouse settings to look for based on the current mode */ + switch (State) + { + case NORMAL_BUSY: + case NORMAL: checkfor = MOUSE_NORMAL; break; + case VISUAL: checkfor = MOUSE_VISUAL; break; + case REPLACE: + case INSERT: checkfor = MOUSE_INSERT; break; + case HITRETURN: checkfor = MOUSE_RETURN; break; + + /* + * On the command line, use the X11 selection on all lines but the + * command line. + */ + case CMDLINE: + if (Y_2_ROW(y) < cmdline_row) + checkfor = ' '; + else + checkfor = MOUSE_COMMAND; + break; + + default: + checkfor = ' '; + break; + }; + /* + * Allow selection of text in the command line in "normal" modes. + */ + if ((State == NORMAL || State == NORMAL_BUSY || + State == INSERT || State == REPLACE) && + Y_2_ROW(y) >= gui.num_rows - p_ch) + checkfor = ' '; + + /* + * If the mouse settings say to not use the mouse, use the X11 selection. + * But if Visual is active, assume that only the Visual area will be + * selected. + */ + if (!mouse_has(checkfor) && !VIsual_active) + { + /* If the selection is done, allow the right button to extend it */ + if (gui.selection.state == SELECT_DONE && button == MOUSE_RIGHT) + { + gui_x11_process_selection(button, x, y, repeated_click, + vim_modifiers); + return; + } + + /* Allow the left button to start the selection */ + else if (button == MOUSE_LEFT) + { + gui_x11_start_selection(button, x, y, repeated_click, + vim_modifiers); + return; + } + } + + if (gui.selection.state != SELECT_CLEARED) + gui_mch_clear_selection(); + gui_send_mouse_event(button, x, y, repeated_click, vim_modifiers); +} + +/* + * End of call-back routines + */ + +/* + * Parse the GUI related command-line arguments. Any arguments used are + * deleted from argv, and *argc is decremented accordingly. This is called + * when vim is started, whether or not the GUI has been started. + */ + void +gui_mch_prepare(argc, argv) + int *argc; + char **argv; +{ + int arg; + int i; + + /* + * Move all the entries in argv which are relevant to X into gui_argv. + */ + gui_argc = 0; + gui_argv = (char **)lalloc(*argc * sizeof(char *), FALSE); + if (gui_argv == NULL) + return; + gui_argv[gui_argc++] = argv[0]; + arg = 1; + while (arg < *argc) + { + /* Look for argv[arg] in cmdline_options[] table */ + for (i = 0; i < XtNumber(cmdline_options); i++) + if (strcmp(argv[arg], cmdline_options[i].option) == 0) + break; + + if (i < XtNumber(cmdline_options)) + { + /* Found match in table, so move it into gui_argv */ + gui_argv[gui_argc++] = argv[arg]; + if (--*argc > arg) + { + vim_memmove(&argv[arg], &argv[arg + 1], (*argc - arg) + * sizeof(char *)); + if (cmdline_options[i].argKind != XrmoptionNoArg) + { + /* Move the options argument as well */ + gui_argv[gui_argc++] = argv[arg]; + if (--*argc > arg) + vim_memmove(&argv[arg], &argv[arg + 1], (*argc - arg) + * sizeof(char *)); + } + } + } + else + arg++; + } +} + +/* + * Initialise the X GUI. Create all the windows, set up all the call-backs + * etc. + */ + int +gui_mch_init() +{ + Widget AppShell; + long_u gc_mask; + XGCValues gc_vals; + Pixel tmp_pixel; + int x, y, mask; + unsigned w, h; + + XtToolkitInitialize(); + app_context = XtCreateApplicationContext(); + gui.dpy = XtOpenDisplay(app_context, 0, VIM_NAME, VIM_CLASS, + cmdline_options, XtNumber(cmdline_options), +#ifndef XtSpecificationRelease + (Cardinal*)&gui_argc, gui_argv); +#else +#if XtSpecificationRelease == 4 + (Cardinal*)&gui_argc, gui_argv); +#else + &gui_argc, gui_argv); +#endif +#endif + + vim_free(gui_argv); + + if (gui.dpy == NULL) + { + x = full_screen; + full_screen = FALSE; /* use fprintf() */ + EMSG("cannot open display"); + full_screen = x; + return FAIL; + } + + /* Uncomment this to enable synchronous mode for debugging */ + /* XSynchronize(gui.dpy, True); */ + + /* + * So converters work. + */ + XtInitializeWidgetClass(applicationShellWidgetClass); + XtInitializeWidgetClass(topLevelShellWidgetClass); + + /* + * The applicationShell is created as an unrealized + * parent for multiple topLevelShells. The topLevelShells + * are created as popup children of the applicationShell. + * This is a recommendation of Paul Asente & Ralph Swick in + * _X_Window_System_Toolkit_ p. 677. + */ + AppShell = XtVaAppCreateShell(VIM_NAME, VIM_CLASS, + applicationShellWidgetClass, gui.dpy, + NULL); + + /* + * Get the application resources + */ + XtVaGetApplicationResources(AppShell, &gui, + vim_resources, XtNumber(vim_resources), NULL); + + /* + * Check validity of resources + */ + if (gui.border_width < 0) + gui.border_width = 0; + + /* For reverse video, swap foreground and background colours */ + if (gui.rev_video) + { + tmp_pixel = gui.norm_pixel; + gui.norm_pixel = gui.back_pixel; + gui.back_pixel = tmp_pixel; + } + + /* Create shell widget to put vim in */ + vimShell = XtVaCreatePopupShell("VIM", + topLevelShellWidgetClass, AppShell, + XtNborderWidth, 0, + NULL); + + /* + * Check that none of the colours are the same as the background color + */ + if (gui.norm_pixel == gui.back_pixel) + { + gui.norm_pixel = gui_x11_get_color((char_u *)"White"); + if (gui.norm_pixel == gui.back_pixel) + gui.norm_pixel = gui_x11_get_color((char_u *)"Black"); + } + if (gui.bold_pixel == gui.back_pixel) + gui.bold_pixel = gui.norm_pixel; + if (gui.ital_pixel == gui.back_pixel) + gui.ital_pixel = gui.norm_pixel; + if (gui.underline_pixel == gui.back_pixel) + gui.underline_pixel = gui.norm_pixel; + if (gui.cursor_pixel == gui.back_pixel) + gui.cursor_pixel = gui.norm_pixel; + + /* + * Set up the GCs. The font attributes will be set in gui_init_font(). + */ + + gc_mask = GCForeground | GCBackground; + gc_vals.foreground = gui.norm_pixel; + gc_vals.background = gui.back_pixel; + gui.text_gc = XCreateGC(gui.dpy, DefaultRootWindow(gui.dpy), gc_mask, + &gc_vals); + + gc_vals.foreground = gui.back_pixel; + gc_vals.background = gui.norm_pixel; + gui.back_gc = XCreateGC(gui.dpy, DefaultRootWindow(gui.dpy), gc_mask, + &gc_vals); + + gc_mask |= GCFunction; + gc_vals.foreground = gui.norm_pixel ^ gui.back_pixel; + gc_vals.background = gui.norm_pixel ^ gui.back_pixel; + gc_vals.function = GXxor; + gui.invert_gc = XCreateGC(gui.dpy, DefaultRootWindow(gui.dpy), gc_mask, + &gc_vals); + + gui.visibility = VisibilityUnobscured; + gui.selection.atom = XInternAtom(gui.dpy, "VIM_SELECTION", False); + + /* + * Set up the fonts. This call will also set the window to the correct + * size for the used font. + */ + gui.norm_font = NULL; + gui.bold_font = NULL; + gui.ital_font = NULL; + gui.boldital_font = NULL; + if (gui_init_font() == FAIL) + return FAIL; + + /* Now adapt the supplied(?) geometry-settings */ + /* Added by Kjetil Jacobsen */ + if (gui.geom != NULL && *gui.geom != NUL) + { + mask = XParseGeometry((char *)gui.geom, &x, &y, &w, &h); + if (mask & WidthValue) + Columns = w; + if (mask & HeightValue) + Rows = h; + /* + * Set the (x,y) position of the main window only if specified in the + * users geometry, so we get good defaults when they don't. This needs + * to be done before the shell is popped up. + */ + if (mask & (XValue|YValue)) + XtVaSetValues(vimShell, XtNgeometry, gui.geom, NULL); + } + gui.num_cols = Columns; + gui.num_rows = Rows; + gui_reset_scroll_region(); + + gui_mch_create_widgets(); + + /* Configure the desired scrollbars */ + gui_init_which_components(NULL); + + /* Actually open the window */ + XtPopup(vimShell, XtGrabNone); + + gui_mch_set_winsize(); + + gui.wid = gui_mch_get_wid(); + + /* Add a callback for the Close item on the window managers menu */ + Atom_WM_DELETE_WINDOW = XInternAtom(gui.dpy, "WM_DELETE_WINDOW", False); + XSetWMProtocols(gui.dpy, XtWindow(vimShell), &Atom_WM_DELETE_WINDOW, 1); + XtAddEventHandler(vimShell, NoEventMask, True, gui_x11_wm_protocol_handler, + NULL); + +#ifdef ENABLE_EDITRES + /* + * Enable editres protocol (see editres(1)) + * Usually will need to add -lXmu to the linker line as well. + */ + { + extern void _XEditResCheckMessages(); + XtAddEventHandler(vimShell, 0, True, _XEditResCheckMessages, + (XtPointer)NULL); + } +#endif + + return OK; +} + + void +gui_mch_exit() +{ + XtCloseDisplay(gui.dpy); +} + +/* + * While unmanaging and re-managing scrollbars etc, we don't want the resize + * callback to be called, so this function is used to disable this callback, + * and then re-enable it afterwards. XSync() makes sure we don't register the + * callback before the server has finished processing any resize events we have + * created, otherwise we can get in a loop where the window flashes between two + * sizes, since resize_window_cb() calls set_winsize(). + */ + void +gui_x11_use_resize_callback(widget, enabled) + Widget widget; + int enabled; +{ + if (enabled) + { + XSync(gui.dpy, False); + XtAddEventHandler(widget, StructureNotifyMask, FALSE, + gui_x11_resize_window_cb, (XtPointer)0); + XtAddEventHandler(widget, ExposureMask, FALSE, gui_x11_expose_cb, + (XtPointer)0); + } + else + { + XtRemoveEventHandler(widget, StructureNotifyMask, FALSE, + gui_x11_resize_window_cb, (XtPointer)0); + XtRemoveEventHandler(widget, ExposureMask, FALSE, gui_x11_expose_cb, + (XtPointer)0); + XSync(gui.dpy, False); + } +} + +/* + * Initialise vim to use the font with the given name. Return FAIL if the font + * could not be loaded, OK otherwise. + */ + int +gui_mch_init_font(font_name) + char_u *font_name; +{ + XFontStruct *font = NULL; + + if (font_name == NULL) + { + /* + * If none of the fonts in 'font' could be loaded, try the one set in + * the X resource, and finally just try using DFLT_FONT, which will + * hopefully always be there. + */ + font = XLoadQueryFont(gui.dpy, (char *)gui.dflt_font); + if (font == NULL) + font_name = (char_u *)DFLT_FONT; + font_name = (char_u *)DFLT_FONT; + } + if (font == NULL) + font = XLoadQueryFont(gui.dpy, (char *)font_name); + if (font == NULL) + return FAIL; + if (font->max_bounds.width != font->min_bounds.width) + { + sprintf((char *)IObuff, "Font \"%s\" is not fixed-width", + (char *)font_name); + emsg(IObuff); + XFreeFont(gui.dpy, font); + return FAIL; + } + if (gui.norm_font != NULL) + XFreeFont(gui.dpy, gui.norm_font); + gui.norm_font = font; + gui.char_width = font->max_bounds.width; + gui.char_height = font->ascent + font->descent; + gui.char_ascent = font->ascent; + +#ifdef DEBUG + printf("Font Information for '%s':\n", font_name); + printf(" w = %d, h = %d, ascent = %d, descent = %d\n", gui.char_width, + gui.char_height, gui.char_ascent, font->descent); + printf(" max ascent = %d, max descent = %d, max h = %d\n", + font->max_bounds.ascent, font->max_bounds.descent, + font->max_bounds.ascent + font->max_bounds.descent); + printf(" min lbearing = %d, min rbearing = %d\n", + font->min_bounds.lbearing, font->min_bounds.rbearing); + printf(" max lbearing = %d, max rbearing = %d\n", + font->max_bounds.lbearing, font->max_bounds.rbearing); + printf(" leftink = %d, rightink = %d\n", + (font->min_bounds.lbearing < 0), + (font->max_bounds.rbearing > gui.char_width)); + printf("\n"); +#endif + + /* + * Try to load other fonts for bold, italic, and bold-italic. + * We should also try to work out what font to use for these when they are + * not specified by X resources, but we don't yet. + */ + if (gui.dflt_bold_fn != NULL && *gui.dflt_bold_fn != NUL) + gui.bold_font = XLoadQueryFont(gui.dpy, (char *)gui.dflt_bold_fn); + if (gui.dflt_ital_fn != NULL && *gui.dflt_ital_fn != NUL) + gui.ital_font = XLoadQueryFont(gui.dpy, (char *)gui.dflt_ital_fn); + if (gui.dflt_boldital_fn != NULL && *gui.dflt_boldital_fn != NUL) + gui.boldital_font = XLoadQueryFont(gui.dpy, (char *)gui.dflt_boldital_fn); + + /* Must set the appropriate fonts for all the GCs */ + gui_x11_set_font(gui.text_gc, gui.norm_font->fid); + gui_x11_set_font(gui.back_gc, gui.norm_font->fid); + + /* + * Set the window sizes if the window is already visible. + */ + if (vimShell != (Widget)NULL && XtIsRealized(vimShell)) + { + WIN *wp; + + gui_mch_set_winsize(); + + /* Force the scrollbar heights to get updated when a font is changed */ + if (gui.which_scrollbars[SB_LEFT]) + { + for (wp = firstwin; wp; wp = wp->w_next) + wp->w_scrollbar.update[SB_LEFT] = SB_UPDATE_HEIGHT; + gui_mch_update_scrollbars(SB_UPDATE_HEIGHT, SB_LEFT); + } + if (gui.which_scrollbars[SB_RIGHT]) + { + for (wp = firstwin; wp; wp = wp->w_next) + wp->w_scrollbar.update[SB_RIGHT] = SB_UPDATE_HEIGHT; + gui_mch_update_scrollbars(SB_UPDATE_HEIGHT, SB_RIGHT); + } + } + + return OK; +} + +/* + * Return OK if the key with the termcap name "name" is supported. + */ + int +gui_mch_haskey(name) + char_u *name; +{ + int i; + + for (i = 0; special_keys[i].key_sym != (KeySym)0; i++) + if (name[0] == special_keys[i].vim_code0 && + name[1] == special_keys[i].vim_code1) + return OK; + return FAIL; +} + +/* + * Return the text window-id and display. Only required for X-based GUI's + */ + int +gui_get_x11_windis(win, dis) + Window *win; + Display **dis; +{ + *win = XtWindow(vimShell); + *dis = gui.dpy; + return OK; +} + + void +gui_mch_beep() +{ + XBell(gui.dpy, 0); +} + + void +gui_mch_flash() +{ + /* Do a visual beep by reversing the foreground and background colors */ + XFillRectangle(gui.dpy, gui.wid, gui.invert_gc, 0, 0, + FILL_X(Columns) + gui.border_offset, + FILL_Y(Rows) + gui.border_offset); + XSync(gui.dpy, False); + mch_delay(20L, TRUE); /* wait 1/50 of a second */ + XFillRectangle(gui.dpy, gui.wid, gui.invert_gc, 0, 0, + FILL_X(Columns) + gui.border_offset, + FILL_Y(Rows) + gui.border_offset); +} + +/* + * Iconify the GUI window. + */ + void +gui_mch_iconify() +{ + XIconifyWindow(gui.dpy, XtWindow(vimShell), DefaultScreen(gui.dpy)); +} + +/* + * Return the Pixel value (color) for the given color name. This routine was + * pretty much taken from example code in the Silicon Graphics OSF/Motif + * Programmer's Guide. + */ + static Pixel +gui_x11_get_color(name) + char_u *name; +{ + XrmValue from, to; + + from.size = STRLEN(name) + 1; + if (from.size < sizeof(String)) + from.size = sizeof(String); + from.addr = (char *)name; + to.addr = NULL; + XtConvert(vimShell, XtRString, &from, XtRPixel, &to); + if (to.addr != NULL) + return (Pixel) *((Pixel *)to.addr); + else + return (Pixel)NULL; +} + +/* + * Set the current text font (gc should be either gui.text_gc or gui.back_gc) + */ + static void +gui_x11_set_font(gc, fid) + GC gc; + Font fid; +{ + static Font prev_text_fid = (Font) -1; + static Font prev_back_fid = (Font) -1; + + if (gc == gui.text_gc && fid != prev_text_fid) + { + XSetFont(gui.dpy, gc, fid); + prev_text_fid = fid; + } + else if (gc == gui.back_gc && fid != prev_back_fid) + { + XSetFont(gui.dpy, gc, fid); + prev_back_fid = fid; + } +} + +/* + * Set the current text color (gc should be either gui.text_gc or gui.back_gc) + */ + static void +gui_x11_set_color(gc, pixel) + GC gc; + Pixel pixel; +{ + static Pixel prev_text_pixel = (Pixel) -1; + static Pixel prev_back_pixel = (Pixel) -1; + + if (gc == gui.text_gc && pixel != prev_text_pixel) + { + XSetForeground(gui.dpy, gc, pixel); + prev_text_pixel = pixel; + } + else if (gc == gui.back_gc && pixel != prev_back_pixel) + { + XSetBackground(gui.dpy, gc, pixel); + prev_back_pixel = pixel; + } +} + +/* + * Draw the cursor. + */ + void +gui_mch_draw_cursor() +{ + /* Only write to the screen after LinePointers[] has been initialized */ + if (screen_cleared && NextScreen != NULL) + { + /* Clear the selection if we are about to write over it */ + if (gui.selection.state == SELECT_DONE + && gui.row >= gui.selection.start.lnum + && gui.row <= gui.selection.end.lnum) + gui_mch_clear_selection(); + + if (gui.row < screen_Rows && gui.col < screen_Columns) + gui_mch_outstr_nowrap(LinePointers[gui.row] + gui.col, + 1, FALSE, TRUE); + if (!gui.in_focus) + { + gui_x11_set_color(gui.text_gc, gui.cursor_pixel); + XDrawRectangle(gui.dpy, gui.wid, gui.text_gc, FILL_X(gui.col), + FILL_Y(gui.row), gui.char_width - 1, gui.char_height - 1); + } + } +} + +/* + * Catch up with any queued X events. This may put keyboard input into the + * input buffer, call resize call-backs, trigger timers etc. If there is + * nothing in the X event queue (& no timers pending), then we return + * immediately. + */ + void +gui_mch_update() +{ + while(XtAppPending(app_context) && !is_input_buf_full()) + XtAppProcessEvent(app_context, XtIMAll); +} + +/* + * The main GUI input routine. Waits for a character from the keyboard. + * wtime == -1 Wait forever. + * wtime == 0 Don't wait. + * wtime > 0 Wait wtime milliseconds for a character. + * Returns OK if a character was found to be available within the given time, + * or FAIL otherwise. + */ + int +gui_mch_wait_for_chars(wtime) + int wtime; +{ + XtIntervalId timer = (XtIntervalId)0; + XtIntervalId updatescript_timer = (XtIntervalId)0; + /* + * Make these static, in case gui_x11_timer_cb is called after leaving + * this function (otherwise a random value on the stack may be changed). + */ + static int timed_out; + static int do_updatescript; + + timed_out = FALSE; + do_updatescript = FALSE; + + /* + * If we're going to wait a bit, update the menus for the current State. + */ + if (wtime != 0) + gui_x11_update_menus(0); + gui_mch_update(); + if (!is_input_buf_empty()) /* Got char, return immediately */ + return OK; + else if (wtime == 0) /* Don't wait for char */ + return FAIL; + else if (wtime > 0) + { + timer = XtAppAddTimeOut(app_context, (long_u)wtime, gui_x11_timer_cb, + &timed_out); + } + else + { + /* We are waiting for ever, so update swap files after p_ut msec's */ + updatescript_timer = XtAppAddTimeOut(app_context, (long_u)p_ut, + gui_x11_timer_cb, &do_updatescript); + } + + while (!timed_out) + { + /* + * Don't use gui_mch_update() because then we will spin-lock until a + * char arrives, instead we use XtAppProcessEvent() to hang until an + * event arrives. No need to check for input_buf_full because we are + * returning as soon as it contains a single char. Note that + * XtAppNextEvent() may not be used because it will not return after a + * timer event has arrived -- webb + */ + XtAppProcessEvent(app_context, XtIMAll); + + /* + * If no characters arrive within 'updatetime' milli-seconds, flush all + * the swap files to disk. + */ + if (do_updatescript) + { + updatescript(0); + do_updatescript = FALSE; + updatescript_timer = (XtIntervalId)0; + } + if (!is_input_buf_empty()) + { + if (timer != (XtIntervalId)0 && !timed_out) + XtRemoveTimeOut(timer); + if (updatescript_timer != (XtIntervalId)0 && !do_updatescript) + XtRemoveTimeOut(updatescript_timer); + return OK; + } + } + return FAIL; +} + +/* + * Output routines. + */ + +/* Flush any output to the screen */ + void +gui_mch_flush() +{ + XFlush(gui.dpy); +} + +/* + * Clear a rectangular region of the screen from text pos (row1, col1) to + * (row2, col2) inclusive. + */ + void +gui_mch_clear_block(row1, col1, row2, col2) + int row1; + int col1; + int row2; + int col2; +{ + /* Clear the selection if we are about to write over it */ + if (gui.selection.state == SELECT_DONE + && row2 >= gui.selection.start.lnum + && row1 <= gui.selection.end.lnum) + gui_mch_clear_selection(); + + XFillRectangle(gui.dpy, gui.wid, gui.back_gc, FILL_X(col1), + FILL_Y(row1), (col2 - col1 + 1) * gui.char_width, + (row2 - row1 + 1) * gui.char_height); + + /* Invalidate cursor if it was in this block */ + if (gui.cursor_row >= row1 && gui.cursor_row <= row2 + && gui.cursor_col >= col1 && gui.cursor_col <= col2) + INVALIDATE_CURSOR(); +} + +/* + * Output the given string at the current cursor position. If the string is + * too long to fit on the line, then it is truncated. wrap_cursor may be + * TRUE if the cursor position should be wrapped when the end of the line is + * reached, however the string will still be truncated and not continue on the + * next line. is_cursor should only be TRUE when this function is being called + * to actually draw the cursor. + */ + void +gui_mch_outstr_nowrap(s, len, wrap_cursor, is_cursor) + char_u *s; + int len; + int wrap_cursor; + int is_cursor; +{ + GC text_gc; + long_u highlight_mask; + + if (len == 0) + return; + + if (len < 0) + len = STRLEN(s); + + if (is_cursor && gui.in_focus) + highlight_mask = gui.highlight_mask | HL_INVERSE; + else + highlight_mask = gui.highlight_mask; + + if (highlight_mask & (HL_INVERSE | HL_STANDOUT)) + text_gc = gui.back_gc; + else + text_gc = gui.text_gc; + + /* Set the font */ + if ((highlight_mask & (HL_BOLD | HL_STANDOUT)) && gui.bold_font != NULL) + if ((highlight_mask & HL_ITAL) && gui.boldital_font != NULL) + gui_x11_set_font(text_gc, gui.boldital_font->fid); + else + gui_x11_set_font(text_gc, gui.bold_font->fid); + else if ((highlight_mask & HL_ITAL) && gui.ital_font != NULL) + gui_x11_set_font(text_gc, gui.ital_font->fid); + else + gui_x11_set_font(text_gc, gui.norm_font->fid); + + /* Set the color */ + if (is_cursor && gui.in_focus) + gui_x11_set_color(text_gc, gui.cursor_pixel); + else if (highlight_mask & (HL_BOLD | HL_STANDOUT)) + gui_x11_set_color(text_gc, gui.bold_pixel); + else if (highlight_mask & HL_ITAL) + gui_x11_set_color(text_gc, gui.ital_pixel); + else if (highlight_mask & HL_UNDERLINE) + gui_x11_set_color(text_gc, gui.underline_pixel); + else + gui_x11_set_color(text_gc, gui.norm_pixel); + + /* Clear the selection if we are about to write over it */ + if (gui.selection.state == SELECT_DONE + && gui.row >= gui.selection.start.lnum + && gui.row <= gui.selection.end.lnum) + gui_mch_clear_selection(); + + /* Draw the text */ + XDrawImageString(gui.dpy, gui.wid, text_gc, + TEXT_X(gui.col), TEXT_Y(gui.row), (char *)s, len); + + /* No bold font, so fake it */ + if ((highlight_mask & (HL_BOLD | HL_STANDOUT)) && gui.bold_font == NULL) + XDrawString(gui.dpy, gui.wid, text_gc, + TEXT_X(gui.col) + 1, TEXT_Y(gui.row), (char *)s, len); + + /* Underline the text */ + if ((highlight_mask & HL_UNDERLINE) + || ((highlight_mask & HL_ITAL) && gui.ital_font == NULL)) + XDrawLine(gui.dpy, gui.wid, text_gc, FILL_X(gui.col), + FILL_Y(gui.row + 1) - 1, FILL_X(gui.col + len) - 1, + FILL_Y(gui.row + 1) - 1); + + if (!is_cursor) + { + /* Invalidate the old physical cursor position if we wrote over it */ + if (gui.cursor_row == gui.row && gui.cursor_col >= gui.col + && gui.cursor_col < gui.col + len) + INVALIDATE_CURSOR(); + + /* Update the cursor position */ + gui.col += len; + if (wrap_cursor && gui.col >= Columns) + { + gui.col = 0; + gui.row++; + } + } +} + +/* + * Delete the given number of lines from the given row, scrolling up any + * text further down within the scroll region. + */ + void +gui_mch_delete_lines(row, num_lines) + int row; + int num_lines; +{ + if (gui.visibility == VisibilityFullyObscured) + return; /* Can't see the window */ + + if (num_lines <= 0) + return; + + if (row + num_lines > gui.scroll_region_bot) + { + /* Scrolled out of region, just blank the lines out */ + gui_mch_clear_block(row, 0, gui.scroll_region_bot, Columns - 1); + } + else + { + XCopyArea(gui.dpy, gui.wid, gui.wid, gui.text_gc, + FILL_X(0), FILL_Y(row + num_lines), + gui.char_width * Columns, + gui.char_height * (gui.scroll_region_bot - row - num_lines + 1), + FILL_X(0), FILL_Y(row)); + + /* Update gui.cursor_row if the cursor scrolled or copied over */ + if (gui.cursor_row >= row) + { + if (gui.cursor_row < row + num_lines) + INVALIDATE_CURSOR(); + else if (gui.cursor_row <= gui.scroll_region_bot) + gui.cursor_row -= num_lines; + } + + gui_mch_clear_block(gui.scroll_region_bot - num_lines + 1, 0, + gui.scroll_region_bot, Columns - 1); + gui_x11_check_copy_area(); + } +} + +/* + * Insert the given number of lines before the given row, scrolling down any + * following text within the scroll region. + */ + void +gui_mch_insert_lines(row, num_lines) + int row; + int num_lines; +{ + if (gui.visibility == VisibilityFullyObscured) + return; /* Can't see the window */ + + if (num_lines <= 0) + return; + + if (row + num_lines > gui.scroll_region_bot) + { + /* Scrolled out of region, just blank the lines out */ + gui_mch_clear_block(row, 0, gui.scroll_region_bot, Columns - 1); + } + else + { + XCopyArea(gui.dpy, gui.wid, gui.wid, gui.text_gc, + FILL_X(0), FILL_Y(row), + gui.char_width * Columns, + gui.char_height * (gui.scroll_region_bot - row - num_lines + 1), + FILL_X(0), FILL_Y(row + num_lines)); + + /* Update gui.cursor_row if the cursor scrolled or copied over */ + if (gui.cursor_row >= gui.row) + { + if (gui.cursor_row <= gui.scroll_region_bot - num_lines) + gui.cursor_row += num_lines; + else if (gui.cursor_row <= gui.scroll_region_bot) + INVALIDATE_CURSOR(); + } + + gui_mch_clear_block(row, 0, row + num_lines - 1, Columns - 1); + gui_x11_check_copy_area(); + } +} + +/* + * Scroll the text between gui.scroll_region_top & gui.scroll_region_bot by the + * number of lines given. Positive scrolls down (text goes up) and negative + * scrolls up (text goes down). + */ + static void +gui_x11_check_copy_area() +{ + XEvent event; + XGraphicsExposeEvent *gevent; + + if (gui.visibility != VisibilityPartiallyObscured) + return; + + XFlush(gui.dpy); + + /* Wait to check whether the scroll worked or not */ + for (;;) + { + if (XCheckTypedEvent(gui.dpy, NoExpose, &event)) + return; /* The scroll worked. */ + + if (XCheckTypedEvent(gui.dpy, GraphicsExpose, &event)) + { + gevent = (XGraphicsExposeEvent *)&event; + gui_redraw(gevent->x, gevent->y, gevent->width, gevent->height); + if (gevent->count == 0) + return; /* This was the last expose event */ + } + XSync(gui.dpy, False); + } +} + +/* + * X Selection stuff, for cutting and pasting text to other windows. + */ + + static void +gui_x11_request_selection_cb(w, success, selection, type, value, length, format) + Widget w; + XtPointer success; + Atom *selection; + Atom *type; + XtPointer value; + long_u *length; + int *format; +{ + int motion_type; + long_u len; + char_u *p; + + if (value == NULL || *length == 0) + { + gui_free_selection(); /* ??? */ + *(int *)success = FALSE; + return; + } + motion_type = MCHAR; + p = (char_u *)value; + len = *length; + if (*type == gui.selection.atom) + { + motion_type = *p++; + len--; + } + gui_yank_selection(motion_type, p, len); + + XtFree((char *)value); + *(int *)success = TRUE; +} + + void +gui_request_selection() +{ + XEvent event; + Atom type = gui.selection.atom; + int success; + int i; + + for (i = 0; i < 2; i++) + { + XtGetSelectionValue(vimShell, XA_PRIMARY, type, + gui_x11_request_selection_cb, (XtPointer)&success, CurrentTime); + + /* Do we need this?: */ + XFlush(gui.dpy); + + /* + * Wait for result of selection request, otherwise if we type more + * characters, then they will appear before the one that requested the + * paste! Don't worry, we will catch up with any other events later. + */ + for (;;) + { + if (XCheckTypedEvent(gui.dpy, SelectionNotify, &event)) + break; + + /* Do we need this?: */ + XSync(gui.dpy, False); + } + XtDispatchEvent(&event); + + if (success) + return; + type = XA_STRING; + } +} + + static Boolean +gui_x11_convert_selection_cb(w, selection, target, type, value, length, format) + Widget w; + Atom *selection; + Atom *target; + Atom *type; + XtPointer *value; + long_u *length; + int *format; +{ + char_u *string; + char_u *result; + int motion_type; + + if (!gui.selection.owned) + return False; /* Shouldn't ever happen */ + + if (*target == gui.selection.atom) + { + gui_get_selection(); + motion_type = gui_convert_selection(&string, length); + if (motion_type < 0) + return False; + + (*length)++; + *value = XtMalloc(*length); + result = (char_u *)*value; + if (result == NULL) + return False; + result[0] = motion_type; + vim_memmove(result + 1, string, (size_t)(*length - 1)); + *type = *target; + *format = 8; /* 8 bits per char */ + return True; + } + else if (*target == XA_STRING) + { + gui_get_selection(); + motion_type = gui_convert_selection(&string, length); + if (motion_type < 0) + return False; + + *value = XtMalloc(*length); + result = (char_u *)*value; + if (result == NULL) + return False; + vim_memmove(result, string, (size_t)(*length)); + *type = *target; + *format = 8; /* 8 bits per char */ + return True; + } + else + return False; +} + + static void +gui_x11_lose_ownership_cb(w, selection) + Widget w; + Atom *selection; +{ + gui_lose_selection(); +} + + void +gui_mch_lose_selection() +{ + XtDisownSelection(vimShell, XA_PRIMARY, CurrentTime); + gui_mch_clear_selection(); +} + + int +gui_mch_own_selection() +{ + if (XtOwnSelection(vimShell, XA_PRIMARY, CurrentTime, + gui_x11_convert_selection_cb, gui_x11_lose_ownership_cb, + NULL) == False) + return FAIL; + + return OK; +} + +/* + * Menu stuff. + */ + + void +gui_x11_menu_cb(w, client_data, call_data) + Widget w; + XtPointer client_data, call_data; +{ + gui_menu_cb((GuiMenu *)client_data); +} + +/* + * Used recursively by gui_x11_update_menus (see below) + */ + static void +gui_x11_update_menus_recurse(menu, mode) + GuiMenu *menu; + int mode; +{ + while (menu) + { + if (menu->modes & mode) + { + if (vim_strchr(p_guioptions, GO_GREY) != NULL) + XtSetSensitive(menu->id, True); + else + XtManageChild(menu->id); + gui_x11_update_menus_recurse(menu->children, mode); + } + else + { + if (vim_strchr(p_guioptions, GO_GREY) != NULL) + XtSetSensitive(menu->id, False); + else + XtUnmanageChild(menu->id); + } + menu = menu->next; + } +} + +/* + * Make sure only the valid menu items appear for this mode. If + * force_menu_update is not TRUE, then we only do this if the mode has changed + * since last time. If "modes" is not 0, then we use these modes instead. + */ + void +gui_x11_update_menus(modes) + int modes; +{ + static int prev_mode = -1; + + int mode = 0; + + if (modes != 0x0) + mode = modes; + else if (VIsual_active) + mode = MENU_VISUAL_MODE; + else if (State & NORMAL) + mode = MENU_NORMAL_MODE; + else if (State & INSERT) + mode = MENU_INSERT_MODE; + else if (State & CMDLINE) + mode = MENU_CMDLINE_MODE; + + if (force_menu_update || mode != prev_mode) + { + gui_x11_update_menus_recurse(gui.root_menu, mode); + prev_mode = mode; + force_menu_update = FALSE; + } +} + +/* + * Function called when window closed. Preserve files and exit. + * Should put up a requester! + */ +/*ARGSUSED*/ + static void +gui_x11_wm_protocol_handler(w, client_data, event, bool) + Widget w; + XtPointer client_data; + XEvent *event; + Boolean *bool; +{ + /* + * On some HPUX system with Motif 1.2 this function is somehow called when + * starting up. This if () avoids an unexpected exit. + */ + if (event->type != ClientMessage || + ((XClientMessageEvent *)event)->data.l[0] != Atom_WM_DELETE_WINDOW) + return; + + STRCPY(IObuff, "Vim: Window closed\n"); + preserve_exit(); /* preserve files and exit */ +} + +/* + * Compare two screen positions ala strcmp() + */ + static int +gui_x11_compare_pos(row1, col1, row2, col2) + short_u row1; + short_u col1; + short_u row2; + short_u col2; +{ + if (row1 > row2) return( 1); + if (row1 < row2) return(-1); + if (col1 > col2) return( 1); + if (col1 < col2) return(-1); + return( 0); +} + +/* + * Start out the selection + */ + void +gui_x11_start_selection(button, x, y, repeated_click, modifiers) + int button; + int x; + int y; + int repeated_click; + int_u modifiers; +{ + GuiSelection *gs = &gui.selection; + + if (gs->state == SELECT_DONE) + gui_mch_clear_selection(); + + gs->start.lnum = Y_2_ROW(y); + gs->start.col = X_2_COL(x); + gs->end = gs->start; + gs->origin_row = gs->start.lnum; + gs->state = SELECT_IN_PROGRESS; + + if (repeated_click) + { + if (++(gs->mode) > SELECT_MODE_LINE) + gs->mode = SELECT_MODE_CHAR; + } + else + gs->mode = SELECT_MODE_CHAR; + + /* clear the cursor until the selection is made */ + gui_undraw_cursor(); + + switch (gs->mode) + { + case SELECT_MODE_CHAR: + gs->origin_start_col = gs->start.col; + gs->word_end_col = gui_x11_get_line_end(gs->start.lnum); + break; + + case SELECT_MODE_WORD: + gui_x11_get_word_boundaries(gs, gs->start.lnum, gs->start.col); + gs->origin_start_col = gs->word_start_col; + gs->origin_end_col = gs->word_end_col; + + gui_x11_invert_area(gs->start.lnum, gs->word_start_col, + gs->end.lnum, gs->word_end_col); + gs->start.col = gs->word_start_col; + gs->end.col = gs->word_end_col; + break; + + case SELECT_MODE_LINE: + gui_x11_invert_area(gs->start.lnum, 0, gs->start.lnum, + gui.num_cols); + gs->start.col = 0; + gs->end.col = gui.num_cols; + break; + } + + gs->prev = gs->start; + +#ifdef DEBUG_SELECTION + printf("Selection started at (%u,%u)\n", gs->start.lnum, gs->start.col); +#endif +} + +/* + * Continue processing the selection + */ + void +gui_x11_process_selection(button, x, y, repeated_click, modifiers) + int button; + int x; + int y; + int repeated_click; + int_u modifiers; +{ + GuiSelection *gs = &gui.selection; + int row, col; + int diff; + + if (button == MOUSE_RELEASE) + { + /* Check to make sure we have something selected */ + if (gs->start.lnum == gs->end.lnum && gs->start.col == gs->end.col) + { + gui_update_cursor(); + gs->state = SELECT_CLEARED; + return; + } + +#ifdef DEBUG_SELECTION + printf("Selection ended: (%u,%u) to (%u,%u)\n", gs->start.lnum, + gs->start.col, gs->end.lnum, gs->end.col); +#endif + gui_free_selection(); + gui_own_selection(); + gui_x11_yank_selection(gs->start.lnum, gs->start.col, gs->end.lnum, + gs->end.col); + gui_update_cursor(); + + gs->state = SELECT_DONE; + return; + } + + row = Y_2_ROW(y); + col = X_2_COL(x); + + row = check_row(row); + col = check_col(col); + + if (col == gs->prev.col && row == gs->prev.lnum) + return; + + /* + * When extending the selection with the right mouse button, swap the + * start and end if the position is before half the selection + */ + if (gs->state == SELECT_DONE && button == MOUSE_RIGHT) + { + /* + * If the click is before the start, or the click is inside the + * selection and the start is the closest side, set the origin to the + * end of the selection. + */ + if (gui_x11_compare_pos(row, col, gs->start.lnum, gs->start.col) < 0 || + (gui_x11_compare_pos(row, col, gs->end.lnum, gs->end.col) < 0 && + (((gs->start.lnum == gs->end.lnum && + gs->end.col - col > col - gs->start.col)) || + ((diff = (gs->end.lnum - row) - (row - gs->start.lnum)) > 0 || + (diff == 0 && col < (gs->start.col + gs->end.col) / 2))))) + { + gs->origin_row = gs->end.lnum; + gs->origin_start_col = gs->end.col - 1; + gs->origin_end_col = gs->end.col; + } + else + { + gs->origin_row = gs->start.lnum; + gs->origin_start_col = gs->start.col; + gs->origin_end_col = gs->start.col; + } + if (gs->mode == SELECT_MODE_WORD) + { + gui_x11_get_word_boundaries(gs, gs->origin_row, + gs->origin_start_col); + gs->origin_start_col = gs->word_start_col; + gs->origin_end_col = gs->word_end_col; + } + } + + /* set state, for when using the right mouse button */ + gs->state = SELECT_IN_PROGRESS; + +#ifdef DEBUG_SELECTION + printf("Selection extending to (%d,%d)\n", row, col); +#endif + + switch (gs->mode) + { + case SELECT_MODE_CHAR: + /* If we're on a different line, find where the line ends */ + if (row != gs->prev.lnum) + gs->word_end_col = gui_x11_get_line_end(row); + + /* See if we are before or after the origin of the selection */ + if (gui_x11_compare_pos(row, col, gs->origin_row, + gs->origin_start_col) >= 0) + { + if (col >= (int)gs->word_end_col) + gui_x11_update_selection(gs, gs->origin_row, + gs->origin_start_col, row, gui.num_cols); + else + gui_x11_update_selection(gs, gs->origin_row, + gs->origin_start_col, row, col + 1); + } + else + { + if (col >= (int)gs->word_end_col) + gui_x11_update_selection(gs, row, gs->word_end_col, + gs->origin_row, gs->origin_start_col + 1); + else + gui_x11_update_selection(gs, row, col, gs->origin_row, + gs->origin_start_col + 1); + } + break; + + case SELECT_MODE_WORD: + /* If we are still within the same word, do nothing */ + if (row == gs->prev.lnum && col >= (int)gs->word_start_col + && col < (int)gs->word_end_col) + return; + + /* Get new word boundaries */ + gui_x11_get_word_boundaries(gs, row, col); + + /* Handle being after the origin point of selection */ + if (gui_x11_compare_pos(row, col, gs->origin_row, + gs->origin_start_col) >= 0) + gui_x11_update_selection(gs, gs->origin_row, + gs->origin_start_col, row, gs->word_end_col); + else + gui_x11_update_selection(gs, row, gs->word_start_col, + gs->origin_row, gs->origin_end_col); + break; + + case SELECT_MODE_LINE: + if (row == gs->prev.lnum) + return; + + if (gui_x11_compare_pos(row, col, gs->origin_row, + gs->origin_start_col) >= 0) + gui_x11_update_selection(gs, gs->origin_row, 0, row, + gui.num_cols); + else + gui_x11_update_selection(gs, row, 0, gs->origin_row, + gui.num_cols); + break; + } + + gs->prev.lnum = row; + gs->prev.col = col; + +#ifdef DEBUG_SELECTION + printf("Selection is: (%u,%u) to (%u,%u)\n", gs->start.lnum, + gs->start.col, gs->end.lnum, gs->end.col); +#endif +} + +/* + * Called after an Expose event to redraw the selection + */ + void +gui_x11_redraw_selection(x, y, w, h) + int x; + int y; + int w; + int h; +{ + GuiSelection *gs = &gui.selection; + int row1, col1, row2, col2; + int row; + int start; + int end; + + if (gs->state == SELECT_CLEARED) + return; + + row1 = Y_2_ROW(y); + col1 = X_2_COL(x); + row2 = Y_2_ROW(y + h - 1); + col2 = X_2_COL(x + w - 1); + + /* Limit the rows that need to be re-drawn */ + if (gs->start.lnum > row1) + row1 = gs->start.lnum; + if (gs->end.lnum < row2) + row2 = gs->end.lnum; + + /* Look at each row that might need to be re-drawn */ + for (row = row1; row <= row2; row++) + { + /* For the first selection row, use the starting selection column */ + if (row == gs->start.lnum) + start = gs->start.col; + else + start = 0; + + /* For the last selection row, use the ending selection column */ + if (row == gs->end.lnum) + end = gs->end.col; + else + end = gui.num_cols; + + if (col1 > start) + start = col1; + + if (col2 < end) + end = col2 + 1; + + if (end > start) + invert_rectangle(row, start, 1, end - start); + } +} + +/* + * Called from outside to clear selected region from the display + */ + void +gui_mch_clear_selection() +{ + GuiSelection *gs = &gui.selection; + + if (gs->state == SELECT_CLEARED) + return; + + gui_x11_invert_area(gs->start.lnum, gs->start.col, gs->end.lnum, + gs->end.col); + gs->state = SELECT_CLEARED; +} + +/* + * Invert a region of the display between a starting and ending row and column + */ + static void +gui_x11_invert_area(row1, col1, row2, col2) + int row1; + int col1; + int row2; + int col2; +{ + /* Swap the from and to positions so the from is always before */ + if (gui_x11_compare_pos(row1, col1, row2, col2) > 0) + { + int tmp_row, tmp_col; + tmp_row = row1; + tmp_col = col1; + row1 = row2; + col1 = col2; + row2 = tmp_row; + col2 = tmp_col; + } + + /* If all on the same line, do it the easy way */ + if (row1 == row2) + { + invert_rectangle(row1, col1, 1, col2 - col1); + return; + } + + /* Handle a piece of the first line */ + if (col1 > 0) + { + invert_rectangle(row1, col1, 1, gui.num_cols - col1); + row1++; + } + + /* Handle a piece of the last line */ + if (col2 < gui.num_cols - 1) + { + invert_rectangle(row2, 0, 1, col2); + row2--; + } + + /* Handle the rectangle thats left */ + if (row2 >= row1) + invert_rectangle(row1, 0, row2 - row1 + 1, gui.num_cols); +} + +/* + * Yank the currently selected area into the special selection buffer so it + * will be available for pasting. + */ + static void +gui_x11_yank_selection(row1, col1, row2, col2) + int row1; + int col1; + int row2; + int col2; +{ + char_u *buffer; + char_u *bufp; + int row; + int start_col; + int end_col; + int line_end_col; + int add_newline_flag = FALSE; + + /* Create a temporary buffer for storing the text */ + buffer = lalloc((row2 - row1 + 1) * gui.num_cols + 1, TRUE); + if (buffer == NULL) + return; /* Should there be an error message here? */ + + /* Process each row in the selection */ + for (bufp = buffer, row = row1; row <= row2; row++) + { + if (row == row1) + start_col = col1; + else + start_col = 0; + + if (row == row2) + end_col = col2; + else + end_col = gui.num_cols; + + line_end_col = gui_x11_get_line_end(row); + + /* See if we need to nuke some trailing whitespace */ + if (end_col >= gui.num_cols && (row < row2 || end_col > line_end_col)) + { + /* Get rid of trailing whitespace */ + end_col = line_end_col; + if (end_col < start_col) + end_col = start_col; + + /* If the last line extended to the end, add an extra newline */ + if (row == row2) + add_newline_flag = TRUE; + } + + /* If after the first row, we need to always add a newline */ + if (row > row1) + *bufp++ = NL; + + if (gui.row < screen_Rows && end_col <= screen_Columns) + { + STRNCPY(bufp, &LinePointers[row][start_col], end_col - start_col); + bufp += end_col - start_col; + } + } + + /* Add a newline at the end if the selection ended there */ + if (add_newline_flag) + *bufp++ = NL; + + gui_yank_selection(MCHAR, buffer, bufp - buffer); + vim_free(buffer); +} + +/* + * Find the starting and ending positions of the word at the given row and + * column. + */ + static void +gui_x11_get_word_boundaries(gs, row, col) + GuiSelection *gs; + int row; + int col; +{ + char start_class; + int temp_col; + + if (row >= screen_Rows || col >= screen_Columns) + return; + + start_class = char_class(LinePointers[row][col]); + + temp_col = col; + for ( ; temp_col > 0; temp_col--) + if (char_class(LinePointers[row][temp_col - 1]) != start_class) + break; + + gs->word_start_col = temp_col; + + temp_col = col; + for ( ; temp_col < screen_Columns; temp_col++) + if (char_class(LinePointers[row][temp_col]) != start_class) + break; + gs->word_end_col = temp_col; + +#ifdef DEBUG_SELECTION + printf("Current word: col %u to %u\n", gs->word_start_col, + gs->word_end_col); +#endif +} + +/* + * Find the column position for the last non-whitespace character on the given + * line. + */ + static int +gui_x11_get_line_end(row) + int row; +{ + int i; + + if (row >= screen_Rows) + return 0; + for (i = screen_Columns; i > 0; i--) + if (LinePointers[row][i - 1] != ' ') + break; + return i; +} + +/* + * Update the currently selected region by adding and/or subtracting from the + * beginning or end and inverting the changed area(s). + */ + static void +gui_x11_update_selection(gs, row1, col1, row2, col2) + GuiSelection *gs; + int row1; + int col1; + int row2; + int col2; +{ + /* See if we changed at the beginning of the selection */ + if (row1 != gs->start.lnum || col1 != gs->start.col) + { + gui_x11_invert_area(row1, col1, gs->start.lnum, gs->start.col); + gs->start.lnum = row1; + gs->start.col = col1; + } + + /* See if we changed at the end of the selection */ + if (row2 != gs->end.lnum || col2 != gs->end.col) + { + gui_x11_invert_area(row2, col2, gs->end.lnum, gs->end.col); + gs->end.lnum = row2; + gs->end.col = col2; + } +} diff --git a/usr.bin/vim/help.c b/usr.bin/vim/help.c new file mode 100644 index 00000000000..b4c988e895c --- /dev/null +++ b/usr.bin/vim/help.c @@ -0,0 +1,302 @@ +/* $OpenBSD: help.c,v 1.1.1.1 1996/09/07 21:40:26 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * help.c: open a read-only window on the vim_help.txt file + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + + void +do_help(arg) + char_u *arg; +{ + char_u *fnamep; + FILE *helpfd; /* file descriptor of help file */ + int n; + WIN *wp; + int num_matches; + char_u **matches; + int need_free = FALSE; + + /* + * If an argument is given, check if there is a match for it. + */ + if (*arg != NUL) + { + n = find_help_tags(arg, &num_matches, &matches); + if (num_matches == 0 || n == FAIL) + { + EMSG2("Sorry, no help for %s", arg); + return; + } + + /* The first file is the best match */ + arg = strsave(matches[0]); + need_free = TRUE; + FreeWild(num_matches, matches); + } + + /* + * If there is already a help window open, use that one. + */ + if (!curwin->w_buffer->b_help) + { + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp->w_buffer != NULL && wp->w_buffer->b_help) + break; + if (wp != NULL && wp->w_buffer->b_nwindows > 0) + win_enter(wp, TRUE); + else + { + /* + * There is no help buffer yet. + * Try to open the file specified by the "helpfile" option. + */ + fnamep = p_hf; + if ((helpfd = fopen((char *)p_hf, READBIN)) == NULL) + { +#if defined(MSDOS) + /* + * for MSDOS: try the DOS search path + */ + fnamep = searchpath("vim_help.txt"); + if (fnamep == NULL || + (helpfd = fopen((char *)fnamep, READBIN)) == NULL) + { + smsg((char_u *)"Sorry, help file \"%s\" and \"vim_help.txt\" not found", p_hf); + goto erret; + } +#else + smsg((char_u *)"Sorry, help file \"%s\" not found", p_hf); + goto erret; +#endif + } + fclose(helpfd); + + if (win_split(0, FALSE) == FAIL) + goto erret; + + if (curwin->w_height < p_hh) + win_setheight((int)p_hh); + +#ifdef RIGHTLEFT + curwin->w_p_rl = 0; /* help window is left-to-right */ +#endif + curwin->w_p_nu = 0; /* no line numbers */ + + /* + * open help file (do_ecmd() will set b_help flag, readfile() will + * set b_p_ro flag) + */ + (void)do_ecmd(0, fnamep, NULL, NULL, TRUE, (linenr_t)0, TRUE); + + /* save the values of the options we change */ + vim_free(help_save_isk); + help_save_isk = strsave(curbuf->b_p_isk); + help_save_ts = curbuf->b_p_ts; + + /* accept all chars for keywords, except ' ', '*', '"', '|' */ + set_string_option((char_u *)"isk", -1, + (char_u *)"!-~,^*,^|,^\"", TRUE); + curbuf->b_p_ts = 8; + check_buf_options(curbuf); + (void)init_chartab(); /* needed because 'isk' changed */ + } + } + + restart_edit = 0; /* don't want insert mode in help file */ + + stuffReadbuff((char_u *)":ta "); + if (arg != NULL && *arg != NUL) + stuffReadbuff(arg); + else + stuffReadbuff((char_u *)"vim_help.txt"); /* go to the index */ + stuffcharReadbuff('\n'); + +erret: + if (need_free) + vim_free(arg); +} + +/* + * Return a heuristic indicating how well the given string matches. The + * smaller the number, the better the match. This is the order of priorities, + * from best match to worst match: + * - Match with least alpha-numeric characters is better. + * - Match with least total characters is better. + * - Match towards the start is better. + * Assumption is made that the matched_string passed has already been found to + * match some string for which help is requested. webb. + */ + int +help_heuristic(matched_string, offset) + char_u *matched_string; + int offset; /* offset for match */ +{ + int num_letters; + char_u *p; + + num_letters = 0; + for (p = matched_string; *p; p++) + if (isalnum(*p)) + num_letters++; + + /* + * Multiply the number of letters by 100 to give it a much bigger + * weighting than the number of characters. + * If the match starts in the middle of a word, add 10000 to put it + * somewhere in the last half. + * If the match is more than 2 chars from the start, multiply by 200 to + * put it after matches at the start. + */ + if (isalnum(matched_string[offset]) && offset > 0 && + isalnum(matched_string[offset - 1])) + offset += 10000; + else if (offset > 2) + offset *= 200; + return (int)(100 * num_letters + STRLEN(matched_string) + offset); +} + +static int help_compare __ARGS((const void *s1, const void *s2)); + +/* + * Compare functions for qsort() below, that checks the help heuristics number + * that has been put after the tagname by find_tags(). + */ + static int +help_compare(s1, s2) + const void *s1; + const void *s2; +{ + char *p1; + char *p2; + + p1 = *(char **)s1 + strlen(*(char **)s1) + 1; + p2 = *(char **)s2 + strlen(*(char **)s2) + 1; + return strcmp(p1, p2); +} + +/* + * Find all help tags matching "arg", sort them and return in matches[], with + * the number of matches in num_matches. + * We try first with case, and then ignoring case. Then we try to choose the + * "best" match from the ones found. + */ + int +find_help_tags(arg, num_matches, matches) + char_u *arg; + int *num_matches; + char_u ***matches; +{ + char_u *s, *d; + regexp *prog; + int attempt; + int retval = FAIL; + + reg_magic = p_magic; + d = IObuff; /* assume IObuff is long enough! */ + + /* + * Replace "|" with "bar", """ with "quote" and "*" with "star" to + * match the name of the tags for these commands. + * Replace "*" with ".*" and "?" with "." to match command line + * completion. + * Insert a backslash before '~', '$' and '.' to avoid their + * special meaning. + * Replace "^x" by "CTRL-X". Don't do this for "^_" to make + * ":help i_^_CTRL-D" work. + * If tag starts with ', toss everything after a second '. Fixes + * CTRL-] on 'option'. (would include the trailing '.'). + */ + if (STRCMP(arg, "*") == 0 || STRCMP(arg, "[*") == 0 || + STRCMP(arg, "]*") == 0) + { + if (*arg != '*') + *d++ = *arg; + STRCPY(d, "star"); + d += 4; + } + else + { + for (s = arg; *s; ++s) + { + if (d - IObuff > IOSIZE - 10) /* getting too long!? */ + break; + switch (*s) + { + case '|': STRCPY(d, "bar"); + d += 3; + continue; + case '\"': STRCPY(d, "quote"); + d += 5; + continue; + case '*': *d++ = '.'; + break; + /* "?", ":?" and "?" are real tags */ + case '?': if (arg[1] == NUL || + STRCMP(arg, ":?") == 0 || + STRCMP(arg, "?") == 0) + break; + *d++ = '.'; + continue; + case '$': + case '.': + case '~': *d++ = '\\'; + break; + } + if (*s < ' ' || (*s == '^' && s[1] && s[1] != '_')) /* ^x */ + { + STRCPY(d, "CTRL-"); + d += 5; + if (*s < ' ') + { + *d++ = *s + '@'; + continue; + } + ++s; + } + else if (*s == '^') /* "^" or "CTRL-^" or "^_" */ + *d++ = '\\'; + *d++ = *s; + if (*s == '\'' && s > arg && *arg == '\'') + break; + } + } + *d = NUL; + + reg_ic = FALSE; + prog = vim_regcomp(IObuff); + if (prog == NULL) + return FAIL; + + /* First try to match with case, then without */ + for (attempt = 0; attempt < 2; ++attempt, reg_ic = TRUE) + { + *matches = (char_u **)""; + *num_matches = 0; + retval = find_tags(NULL, prog, num_matches, matches, TRUE); + if (retval == FAIL || *num_matches) + break; + } + vim_free(prog); + +#ifdef HAVE_QSORT + /* + * Sort the matches found on the heuristic number that is after the + * tag name. If there is no qsort, the output will be messy! + */ + qsort((void *)*matches, (size_t)*num_matches, + sizeof(char_u *), help_compare); +#endif + return OK; +} diff --git a/usr.bin/vim/keymap.h b/usr.bin/vim/keymap.h new file mode 100644 index 00000000000..e971ec24a3d --- /dev/null +++ b/usr.bin/vim/keymap.h @@ -0,0 +1,321 @@ +/* $OpenBSD: keymap.h,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +#define K_CCIRCM 0x1e /* control circumflex */ + +/* + * For MSDOS some keys produce codes larger than 0xff. They are split into two + * chars, the first one is K_NUL (same value used in term.h). + */ +#define K_NUL (0xce) /* for MSDOS: special key follows */ + +/* + * Keycode definitions for special keys. + * + * Any special key code sequences are replaced by these codes. + */ + +/* + * K_SPECIAL is the first byte of a special key code and is always followed by + * two bytes. + * The second byte can have any value. ASCII is used for normal termcap + * entries, 0x80 and higher for special keys, see below. + * The third byte is guaranteed to be between 0x02 and 0x7f. + */ + +#define K_SPECIAL (0x80) + +/* + * characters 0x0000 - 0x00ff are "normal" + * characters 0x0100 - 0x01ff are used for abbreviations + * characters 0x0200 - 0xffff are special key codes + */ +#define IS_SPECIAL(c) ((c) >= 0x200) +#define IS_ABBR(c) ((c) >= 0x100 && (c) < 0x200) +#define ABBR_OFF 0x100 + +/* + * NUL cannot be in the input string, therefore it is replaced by + * K_SPECIAL KS_ZERO K_FILLER + */ +#define KS_ZERO 255 + +/* + * K_SPECIAL cannot be in the input string, therefore it is replaced by + * K_SPECIAL KS_SPECIAL K_FILLER + */ +#define KS_SPECIAL 254 + +/* + * KS_EXTRA is used for keys that have no termcap name + * K_SPECIAL KS_EXTRA KE_xxx + */ +#define KS_EXTRA 253 + +/* + * KS_MODIFIER is used when a modifier is given for a (special) key + * K_SPECIAL KS_MODIFIER bitmask + */ +#define KS_MODIFIER 252 + +/* + * These are used for the GUI + * K_SPECIAL KS_xxx K_FILLER + */ +#define KS_MOUSE 251 +#define KS_MENU 250 +#define KS_SCROLLBAR 249 +#define KS_HORIZ_SCROLLBAR 248 + +/* + * Filler used after KS_SPECIAL and others + */ +#define K_FILLER ('X') + +/* + * translation of three byte code "K_SPECIAL a b" into int "K_xxx" and back + */ +#define TERMCAP2KEY(a, b) ((a) + ((b) << 8)) +#define KEY2TERMCAP0(x) ((x) & 0xff) +#define KEY2TERMCAP1(x) (((x) >> 8) & 0xff) + +/* + * get second or third byte when translating special key code into three bytes + */ +#define K_SECOND(c) ((c) == K_SPECIAL ? KS_SPECIAL : (c) == NUL ? KS_ZERO : KEY2TERMCAP0(c)) + +#define K_THIRD(c) (((c) == K_SPECIAL || (c) == NUL) ? K_FILLER : KEY2TERMCAP1(c)) + +/* + * get single int code from second byte after K_SPECIAL + */ +#define TO_SPECIAL(a, b) ((a) == KS_SPECIAL ? K_SPECIAL : (a) == KS_ZERO ? K_ZERO : TERMCAP2KEY(a, b)) + +/* + * Codes for keys that do not have a termcap name. + * + * K_SPECIAL KS_EXTRA KE_xxx + */ +#define KE_NAME 3 /* name of this terminal entry */ + +#define KE_S_UP 4 +#define KE_S_DOWN 5 + +#define KE_S_F1 6 /* shifted function keys */ +#define KE_S_F2 7 +#define KE_S_F3 8 +#define KE_S_F4 9 +#define KE_S_F5 10 +#define KE_S_F6 11 +#define KE_S_F7 12 +#define KE_S_F8 13 +#define KE_S_F9 14 +#define KE_S_F10 15 + +#define KE_S_F11 16 +#define KE_S_F12 17 +#define KE_S_F13 18 +#define KE_S_F14 19 +#define KE_S_F15 20 +#define KE_S_F16 21 +#define KE_S_F17 22 +#define KE_S_F18 23 +#define KE_S_F19 24 +#define KE_S_F20 25 + +#define KE_S_F21 26 +#define KE_S_F22 27 +#define KE_S_F23 28 +#define KE_S_F24 29 +#define KE_S_F25 30 +#define KE_S_F26 31 +#define KE_S_F27 32 +#define KE_S_F28 33 +#define KE_S_F29 34 +#define KE_S_F30 35 + +#define KE_S_F31 36 +#define KE_S_F32 37 +#define KE_S_F33 38 +#define KE_S_F34 39 +#define KE_S_F35 40 + +#define KE_MOUSE 41 /* mouse event start */ + + /* + * Symbols for pseudo keys which are translated from the real key symbols + * above. + */ +#define KE_LEFTMOUSE 42 /* Left mouse button click */ +#define KE_LEFTDRAG 43 /* Drag with left mouse button down */ +#define KE_LEFTRELEASE 44 /* Left mouse button release */ +#define KE_MIDDLEMOUSE 45 /* Middle mouse button click */ +#define KE_MIDDLEDRAG 46 /* Drag with middle mouse button down */ +#define KE_MIDDLERELEASE 47 /* Middle mouse button release */ +#define KE_RIGHTMOUSE 48 /* Right mouse button click */ +#define KE_RIGHTDRAG 49 /* Drag with right mouse button down */ +#define KE_RIGHTRELEASE 50 /* Right mouse button release */ + +#define KE_IGNORE 51 /* Ignored mouse drag/release */ + +#define KE_TAB 52 /* unshifted TAB key */ +#define KE_S_TAB 53 /* shifted TAB key */ + +/* + * the three byte codes are replaced with the following int when using vgetc() + */ +#define K_ZERO TERMCAP2KEY(KS_ZERO, K_FILLER) + +#define K_UP TERMCAP2KEY('k', 'u') +#define K_DOWN TERMCAP2KEY('k', 'd') +#define K_LEFT TERMCAP2KEY('k', 'l') +#define K_RIGHT TERMCAP2KEY('k', 'r') +#define K_S_UP TERMCAP2KEY(KS_EXTRA, KE_S_UP) +#define K_S_DOWN TERMCAP2KEY(KS_EXTRA, KE_S_DOWN) +#define K_S_LEFT TERMCAP2KEY('#', '4') +#define K_S_RIGHT TERMCAP2KEY('%', 'i') +#define K_TAB TERMCAP2KEY(KS_EXTRA, KE_TAB) +#define K_S_TAB TERMCAP2KEY(KS_EXTRA, KE_S_TAB) + +#define K_F1 TERMCAP2KEY('k', '1') /* function keys */ +#define K_F2 TERMCAP2KEY('k', '2') +#define K_F3 TERMCAP2KEY('k', '3') +#define K_F4 TERMCAP2KEY('k', '4') +#define K_F5 TERMCAP2KEY('k', '5') +#define K_F6 TERMCAP2KEY('k', '6') +#define K_F7 TERMCAP2KEY('k', '7') +#define K_F8 TERMCAP2KEY('k', '8') +#define K_F9 TERMCAP2KEY('k', '9') +#define K_F10 TERMCAP2KEY('k', ';') + +#define K_F11 TERMCAP2KEY('F', '1') +#define K_F12 TERMCAP2KEY('F', '2') +#define K_F13 TERMCAP2KEY('F', '3') +#define K_F14 TERMCAP2KEY('F', '4') +#define K_F15 TERMCAP2KEY('F', '5') +#define K_F16 TERMCAP2KEY('F', '6') +#define K_F17 TERMCAP2KEY('F', '7') +#define K_F18 TERMCAP2KEY('F', '8') +#define K_F19 TERMCAP2KEY('F', '9') +#define K_F20 TERMCAP2KEY('F', 'A') + +#define K_F21 TERMCAP2KEY('F', 'B') +#define K_F22 TERMCAP2KEY('F', 'C') +#define K_F23 TERMCAP2KEY('F', 'D') +#define K_F24 TERMCAP2KEY('F', 'E') +#define K_F25 TERMCAP2KEY('F', 'F') +#define K_F26 TERMCAP2KEY('F', 'G') +#define K_F27 TERMCAP2KEY('F', 'H') +#define K_F28 TERMCAP2KEY('F', 'I') +#define K_F29 TERMCAP2KEY('F', 'J') +#define K_F30 TERMCAP2KEY('F', 'K') + +#define K_F31 TERMCAP2KEY('F', 'L') +#define K_F32 TERMCAP2KEY('F', 'M') +#define K_F33 TERMCAP2KEY('F', 'N') +#define K_F34 TERMCAP2KEY('F', 'O') +#define K_F35 TERMCAP2KEY('F', 'P') + +#define K_S_F1 TERMCAP2KEY(KS_EXTRA, KE_S_F1) /* shifted func. keys */ +#define K_S_F2 TERMCAP2KEY(KS_EXTRA, KE_S_F2) +#define K_S_F3 TERMCAP2KEY(KS_EXTRA, KE_S_F3) +#define K_S_F4 TERMCAP2KEY(KS_EXTRA, KE_S_F4) +#define K_S_F5 TERMCAP2KEY(KS_EXTRA, KE_S_F5) +#define K_S_F6 TERMCAP2KEY(KS_EXTRA, KE_S_F6) +#define K_S_F7 TERMCAP2KEY(KS_EXTRA, KE_S_F7) +#define K_S_F8 TERMCAP2KEY(KS_EXTRA, KE_S_F8) +#define K_S_F9 TERMCAP2KEY(KS_EXTRA, KE_S_F9) +#define K_S_F10 TERMCAP2KEY(KS_EXTRA, KE_S_F10) + +#define K_S_F11 TERMCAP2KEY(KS_EXTRA, KE_S_F11) +#define K_S_F12 TERMCAP2KEY(KS_EXTRA, KE_S_F12) +#define K_S_F13 TERMCAP2KEY(KS_EXTRA, KE_S_F13) +#define K_S_F14 TERMCAP2KEY(KS_EXTRA, KE_S_F14) +#define K_S_F15 TERMCAP2KEY(KS_EXTRA, KE_S_F15) +#define K_S_F16 TERMCAP2KEY(KS_EXTRA, KE_S_F16) +#define K_S_F17 TERMCAP2KEY(KS_EXTRA, KE_S_F17) +#define K_S_F18 TERMCAP2KEY(KS_EXTRA, KE_S_F18) +#define K_S_F19 TERMCAP2KEY(KS_EXTRA, KE_S_F19) +#define K_S_F20 TERMCAP2KEY(KS_EXTRA, KE_S_F20) + +#define K_S_F21 TERMCAP2KEY(KS_EXTRA, KE_S_F21) +#define K_S_F22 TERMCAP2KEY(KS_EXTRA, KE_S_F22) +#define K_S_F23 TERMCAP2KEY(KS_EXTRA, KE_S_F23) +#define K_S_F24 TERMCAP2KEY(KS_EXTRA, KE_S_F24) +#define K_S_F25 TERMCAP2KEY(KS_EXTRA, KE_S_F25) +#define K_S_F26 TERMCAP2KEY(KS_EXTRA, KE_S_F26) +#define K_S_F27 TERMCAP2KEY(KS_EXTRA, KE_S_F27) +#define K_S_F28 TERMCAP2KEY(KS_EXTRA, KE_S_F28) +#define K_S_F29 TERMCAP2KEY(KS_EXTRA, KE_S_F29) +#define K_S_F30 TERMCAP2KEY(KS_EXTRA, KE_S_F30) + +#define K_S_F31 TERMCAP2KEY(KS_EXTRA, KE_S_F31) +#define K_S_F32 TERMCAP2KEY(KS_EXTRA, KE_S_F32) +#define K_S_F33 TERMCAP2KEY(KS_EXTRA, KE_S_F33) +#define K_S_F34 TERMCAP2KEY(KS_EXTRA, KE_S_F34) +#define K_S_F35 TERMCAP2KEY(KS_EXTRA, KE_S_F35) + +#define K_HELP TERMCAP2KEY('%', '1') +#define K_UNDO TERMCAP2KEY('&', '8') + +#define K_BS TERMCAP2KEY('k', 'b') + +#define K_INS TERMCAP2KEY('k', 'I') +#define K_DEL TERMCAP2KEY('k', 'D') +#define K_HOME TERMCAP2KEY('k', 'h') +#define K_END TERMCAP2KEY('@', '7') +#define K_PAGEUP TERMCAP2KEY('k', 'P') +#define K_PAGEDOWN TERMCAP2KEY('k', 'N') + +#define K_MOUSE TERMCAP2KEY(KS_MOUSE, K_FILLER) +#define K_MENU TERMCAP2KEY(KS_MENU, K_FILLER) +#define K_SCROLLBAR TERMCAP2KEY(KS_SCROLLBAR, K_FILLER) +#define K_HORIZ_SCROLLBAR TERMCAP2KEY(KS_HORIZ_SCROLLBAR, K_FILLER) + +/* + * Symbols for pseudo keys which are translated from the real key symbols + * above. + */ +#define K_LEFTMOUSE TERMCAP2KEY(KS_EXTRA, KE_LEFTMOUSE) +#define K_LEFTDRAG TERMCAP2KEY(KS_EXTRA, KE_LEFTDRAG) +#define K_LEFTRELEASE TERMCAP2KEY(KS_EXTRA, KE_LEFTRELEASE) +#define K_MIDDLEMOUSE TERMCAP2KEY(KS_EXTRA, KE_MIDDLEMOUSE) +#define K_MIDDLEDRAG TERMCAP2KEY(KS_EXTRA, KE_MIDDLEDRAG) +#define K_MIDDLERELEASE TERMCAP2KEY(KS_EXTRA, KE_MIDDLERELEASE) +#define K_RIGHTMOUSE TERMCAP2KEY(KS_EXTRA, KE_RIGHTMOUSE) +#define K_RIGHTDRAG TERMCAP2KEY(KS_EXTRA, KE_RIGHTDRAG) +#define K_RIGHTRELEASE TERMCAP2KEY(KS_EXTRA, KE_RIGHTRELEASE) + +#define K_IGNORE TERMCAP2KEY(KS_EXTRA, KE_IGNORE) + +/* Bits for modifier mask */ +#define MOD_MASK_SHIFT 0x02 +#define MOD_MASK_CTRL 0x04 +#define MOD_MASK_ALT 0x08 +#define MOD_MASK_2CLICK 0x10 +#define MOD_MASK_3CLICK 0x20 +#define MOD_MASK_4CLICK 0x40 + +#define MOD_MASK_MULTI_CLICK (MOD_MASK_2CLICK|MOD_MASK_3CLICK|MOD_MASK_4CLICK) + +/* + * The length of the longest special key name, including modifiers. + * Current longest is (length includes '<' and '>'). + */ +#define MAX_KEY_NAME_LEN 23 + +/* Maximum length of a special key event as tokens. This includes modifiers. + * The longest event is something like which would be the + * following string of tokens: + * + * bitmask . + * + * This is a total of 6 tokens, and is currently the longest one possible. + */ +#define MAX_KEY_CODE_LEN 6 diff --git a/usr.bin/vim/linefunc.c b/usr.bin/vim/linefunc.c new file mode 100644 index 00000000000..716db3f5705 --- /dev/null +++ b/usr.bin/vim/linefunc.c @@ -0,0 +1,170 @@ +/* $OpenBSD: linefunc.c,v 1.1.1.1 1996/09/07 21:40:26 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * linefunc.c: some functions to move to the next/previous line and + * to the next/previous character + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" + +/* + * coladvance(col) + * + * Try to advance the Cursor to the specified column. + * + * return OK if desired column is reached, FAIL if not + */ + + int +coladvance(wcol) + colnr_t wcol; +{ + int idx; + register char_u *ptr; + register colnr_t col; + + ptr = ml_get_curline(); + + /* try to advance to the specified column */ + idx = -1; + col = 0; + while (col <= wcol && *ptr) + { + ++idx; + /* Count a tab for what it's worth (if list mode not on) */ + col += lbr_chartabsize(ptr, col); + ++ptr; + } + /* + * in insert mode it is allowed to be one char beyond the end of the line + */ + if ((State & INSERT) && col <= wcol) + ++idx; + if (idx < 0) + curwin->w_cursor.col = 0; + else + curwin->w_cursor.col = idx; + if (col <= wcol) + return FAIL; /* Couldn't reach column */ + else + return OK; /* Reached column */ +} + +/* + * inc(p) + * + * Increment the line pointer 'p' crossing line boundaries as necessary. + * Return 1 when crossing a line, -1 when at end of file, 0 otherwise. + */ + int +inc_cursor() +{ + return inc(&curwin->w_cursor); +} + + int +inc(lp) + register FPOS *lp; +{ + register char_u *p = ml_get_pos(lp); + + if (*p != NUL) /* still within line, move to next char (may be NUL) */ + { + lp->col++; + return ((p[1] != NUL) ? 0 : 1); + } + if (lp->lnum != curbuf->b_ml.ml_line_count) /* there is a next line */ + { + lp->col = 0; + lp->lnum++; + return 1; + } + return -1; +} + +/* + * incl(lp): same as inc(), but skip the NUL at the end of non-empty lines + */ + int +incl(lp) + register FPOS *lp; +{ + register int r; + + if ((r = inc(lp)) == 1 && lp->col) + r = inc(lp); + return r; +} + +/* + * dec(p) + * + * Decrement the line pointer 'p' crossing line boundaries as necessary. + * Return 1 when crossing a line, -1 when at start of file, 0 otherwise. + */ + int +dec_cursor() +{ + return dec(&curwin->w_cursor); +} + + int +dec(lp) + register FPOS *lp; +{ + if (lp->col > 0) + { /* still within line */ + lp->col--; + return 0; + } + if (lp->lnum > 1) + { /* there is a prior line */ + lp->lnum--; + lp->col = STRLEN(ml_get(lp->lnum)); + return 1; + } + return -1; /* at start of file */ +} + +/* + * decl(lp): same as dec(), but skip the NUL at the end of non-empty lines + */ + int +decl(lp) + register FPOS *lp; +{ + register int r; + + if ((r = dec(lp)) == 1 && lp->col) + r = dec(lp); + return r; +} + +/* + * make sure curwin->w_cursor in on a valid character + */ + void +adjust_cursor() +{ + colnr_t len; + + if (curwin->w_cursor.lnum == 0) + curwin->w_cursor.lnum = 1; + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + + len = STRLEN(ml_get_curline()); + if (len == 0) + curwin->w_cursor.col = 0; + else if (curwin->w_cursor.col >= len) + curwin->w_cursor.col = len - 1; +} diff --git a/usr.bin/vim/macros.h b/usr.bin/vim/macros.h new file mode 100644 index 00000000000..7e6e599d273 --- /dev/null +++ b/usr.bin/vim/macros.h @@ -0,0 +1,69 @@ +/* $OpenBSD: macros.h,v 1.1.1.1 1996/09/07 21:40:27 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * macros.h: macro definitions for often used code + */ + +/* + * pchar(lp, c) - put character 'c' at position 'lp' + */ +#define pchar(lp, c) (*(ml_get_buf(curbuf, (lp).lnum, TRUE) + (lp).col) = (c)) + +/* + * Position comparisons + */ +#define lt(a, b) (((a).lnum != (b).lnum) \ + ? ((a).lnum < (b).lnum) : ((a).col < (b).col)) + +#define ltoreq(a, b) (((a).lnum != (b).lnum) \ + ? ((a).lnum < (b).lnum) : ((a).col <= (b).col)) + +#define equal(a, b) (((a).lnum == (b).lnum) && ((a).col == (b).col)) + +/* + * lineempty() - return TRUE if the line is empty + */ +#define lineempty(p) (*ml_get(p) == NUL) + +/* + * bufempty() - return TRUE if the current buffer is empty + */ +#define bufempty() (curbuf->b_ml.ml_line_count == 1 && *ml_get((linenr_t)1) == NUL) + +/* + * On some systems toupper()/tolower() only work on lower/uppercase characters + */ +#ifdef BROKEN_TOUPPER +# define TO_UPPER(c) (islower(c) ? toupper(c) : (c)) +# define TO_LOWER(c) (isupper(c) ? tolower(c) : (c)) +#else +# define TO_UPPER toupper +# define TO_LOWER tolower +#endif + +#ifdef HAVE_LANGMAP +/* + * Adjust chars in a language according to 'langmap' option. + * NOTE that there is NO overhead if 'langmap' is not set; but even + * when set we only have to do 2 ifs and an array lookup. + * Don't apply 'langmap' if the character comes from the Stuff buffer. + * The do-while is just to ignore a ';' after the macro. + */ +# define LANGMAP_ADJUST(c, condition) do { \ + if (*p_langmap && (condition) && !KeyStuffed && (c) < 256) \ + c = langmap_mapchar[c]; \ + } while (0) +#endif + +/* + * isbreak() is used very often if 'linebreak' is set, use a macro to make + * it work fast. + */ +#define isbreak(c) (breakat_flags[(char_u)(c)]) diff --git a/usr.bin/vim/main.c b/usr.bin/vim/main.c new file mode 100644 index 00000000000..879da9558e5 --- /dev/null +++ b/usr.bin/vim/main.c @@ -0,0 +1,1081 @@ +/* $OpenBSD: main.c,v 1.1.1.1 1996/09/07 21:40:26 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +#define EXTERN +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +#ifdef SPAWNO +# include /* special MSDOS swapping library */ +#endif + +static void usage __PARMS((int, char_u *)); +static int stdout_notty = FALSE; /* is stdout not a terminal? */ + +/* + * Types of usage message required. These must match the array of error + * messages in usage(). + */ +#define USAGE_UNKNOWN_OPTION 0 +#define USAGE_TOO_MANY_ARGS 1 +#define USAGE_ARG_MISSING 2 +#define USAGE_GARBAGE 3 + + static void +usage(n, str) + int n; + char_u *str; +{ + register int i; + static char_u *(use[]) = {(char_u *)"[file ..]", + (char_u *)"-t tag", + (char_u *)"-e [errorfile]"}; + static char_u *(errors[]) = {(char_u *)"Unknown option", + (char_u *)"Too many arguments", + (char_u *)"Argument missing after", + (char_u *)"Garbage after option", + }; + +#if defined(UNIX) || defined(__EMX__) + reset_signals(); /* kill us with CTRL-C here, if you like */ +#endif + + fprintf(stderr, longVersion); + fprintf(stderr, "\n"); + fprintf(stderr, (char *)errors[n]); + if (str != NULL) + fprintf(stderr, ": \"%s\"", str); + fprintf(stderr, "\nusage:"); + for (i = 0; ; ++i) + { + fprintf(stderr, " vim [options] "); + fprintf(stderr, (char *)use[i]); + if (i == (sizeof(use) / sizeof(char_u *)) - 1) + break; + fprintf(stderr, "\n or:"); + } + + fprintf(stderr, "\n\nOptions:\n"); +#ifdef USE_GUI + fprintf(stderr, " -g\t\t\tRun using GUI\n"); + fprintf(stderr, " -f\t\t\tForeground: Don't fork when starting GUI\n"); +#endif + fprintf(stderr, " -R or -v\t\tReadonly mode (view mode)\n"); + fprintf(stderr, " -b\t\t\tBinary mode\n"); + fprintf(stderr, " -l\t\t\tLisp mode\n"); + fprintf(stderr, " -n\t\t\tNo swap file, use memory only\n"); + fprintf(stderr, " -r\t\t\tList swap files\n"); + fprintf(stderr, " -r (with file name)\tRecover crashed session\n"); + fprintf(stderr, " -L\t\t\tSame as -r\n"); +#ifdef AMIGA + fprintf(stderr, " -x\t\t\tDon't use newcli to open window\n"); + fprintf(stderr, " -d \t\tUse for I/O\n"); +#endif +#ifdef RIGHTLEFT + fprintf(stderr, " -H\t\t\tstart in Hebrew mode\n"); +#endif + fprintf(stderr, " -T \tSet terminal type to \n"); + fprintf(stderr, " -o[N]\t\tOpen N windows (default: one for each file)\n"); + fprintf(stderr, " +\t\t\tStart at end of file\n"); + fprintf(stderr, " +\t\tStart at line \n"); + fprintf(stderr, " -c \t\tExecute first\n"); + fprintf(stderr, " -s \tRead commands from script file \n"); + fprintf(stderr, " -w \tAppend commands to script file \n"); + fprintf(stderr, " -W \tWrite commands to script file \n"); + fprintf(stderr, " -u \t\tUse instead of any .vimrc\n"); + fprintf(stderr, " -i \t\tUse instead of .viminfo\n"); + fprintf(stderr, " --\t\t\tEnd of options\n"); + +#ifdef USE_GUI_X11 +# ifdef USE_GUI_MOTIF + fprintf(stderr, "\nOptions recognised by gvim (Motif version):\n"); +# else +# ifdef USE_GUI_ATHENA + fprintf(stderr, "\nOptions recognised by gvim (Athena version):\n"); +# endif /* USE_GUI_ATHENA */ +# endif /* USE_GUI_MOTIF */ + fprintf(stderr, " -display \tRun vim on \n"); + fprintf(stderr, " -iconic\t\tStart vim iconified\n"); +# if 0 + fprintf(stderr, " -name \t\tUse resource as if vim was \n"); + fprintf(stderr, "\t\t\t (Unimplemented)\n"); +# endif + fprintf(stderr, " -background \tUse for the background (also: -bg)\n"); + fprintf(stderr, " -foreground \tUse for normal text (also: -fg)\n"); + fprintf(stderr, " -bold \tUse for bold text\n"); + fprintf(stderr, " -italic \tUse for italic text\n"); + fprintf(stderr, " -underline \tUse for underlined text (also: -ul)\n"); + fprintf(stderr, " -cursor \tUse for cursor\n"); + fprintf(stderr, " -font \t\tUse for normal text (also: -fn)\n"); + fprintf(stderr, " -boldfont \tUse for bold text\n"); + fprintf(stderr, " -italicfont \tUse for italic text\n"); + fprintf(stderr, " -geometry \tUse for initial geometry (also: -geom)\n"); + fprintf(stderr, " -borderwidth \tUse a border width of (also: -bw)\n"); + fprintf(stderr, " -scrollbarwidth \tUse a scrollbar width of (also: -sw)\n"); + fprintf(stderr, " -menuheight \tUse a menu bar height of (also: -mh)\n"); + fprintf(stderr, " -reverse\t\tUse reverse video (also: -rv)\n"); + fprintf(stderr, " +reverse\t\tDon't use reverse video (also: +rv)\n"); + fprintf(stderr, " -xrm \tSet the specified resource\n"); +#endif /* USE_GUI_X11 */ + + mch_windexit(1); +} + +#ifdef HAVE_LOCALE_H +# include +#endif + + void +main(argc, argv) + int argc; + char **argv; +{ + char_u *initstr; /* init string from the environment */ + char_u *term = NULL; /* specified terminal name */ + char_u *fname = NULL; /* file name from command line */ + char_u *command = NULL; /* command from + or -c option */ + char_u *tagname = NULL; /* tag from -t option */ + char_u *use_vimrc = NULL; /* vimrc from -u option */ + int c; + int doqf = 0; + int i; + int bin_mode = FALSE; /* -b option used */ + int vi_mode = FALSE; /* run as vi */ + int window_count = 1; /* number of windows to use */ + int arg_idx = 0; /* index for arg_files[] */ + int check_version = FALSE; /* check .vimrc version number */ + int argv_idx; /* index in argv[n][] */ + +#if defined(MSDOS) || defined(WIN32) || defined(OS2) + static struct initmap + { + char_u *arg; + int mode; + } initmappings[] = + { + /* normal and visual mode */ +#ifdef MSDOS + {(char_u *)"\316w H", NORMAL+VISUAL}, /* CTRL-HOME is 'H' */ + {(char_u *)"\316u L", NORMAL+VISUAL}, /* CTRL-END is 'L' */ + {(char_u *)"\316\204 1G", NORMAL+VISUAL}, /* CTRL-PageUp is '1G' */ + {(char_u *)"\316v G", NORMAL+VISUAL}, /* CTRL-PageDown is 'G' */ +#else /* WIN32 */ + /* Use the Windows (CUA) keybindings */ + {(char_u *)"\316w 1G", NORMAL+VISUAL}, /* CTRL-HOME is '1G' */ + {(char_u *)"\316u G$", NORMAL+VISUAL}, /* CTRL-END is 'G$' */ + {(char_u *)"\316\204 H", NORMAL+VISUAL}, /* CTRL-PageUp is 'H' */ + {(char_u *)"\316v L$", NORMAL+VISUAL}, /* CTRL-PageDown is 'L$' */ + {(char_u *)"\316s B", NORMAL+VISUAL}, /* CTRL-Left is 'B' */ + {(char_u *)"\316t W", NORMAL+VISUAL}, /* CTRL-Right is 'W' */ +#endif /* WIN32 */ + + /* insert mode */ +#ifdef MSDOS + {(char_u *)"\316w \017H", INSERT}, /* CTRL-HOME is '^OH' */ + {(char_u *)"\316u \017L", INSERT}, /* CTRL-END is '^OL' */ + {(char_u *)"\316\204 \017\061G", INSERT}, /* CTRL-PageUp is '^O1G' */ + {(char_u *)"\316v \017G", INSERT}, /* CTRL-PageDown is '^OG' */ +#else /* WIN32 */ + /* Use the Windows (CUA) keybindings */ + {(char_u *)"\316w \017\061G", INSERT}, /* CTRL-HOME is '^O1G' */ + {(char_u *)"\316u \017G\017$", INSERT}, /* CTRL-END is '^OG^O$' */ + {(char_u *)"\316\204 \017H",INSERT}, /* CTRL-PageUp is '^OH'*/ + {(char_u *)"\316v \017L\017$", INSERT}, /* CTRL-PageDown ='^OL^O$'*/ + {(char_u *)"\316s \017B", INSERT}, /* CTRL-Left is '^OB' */ + {(char_u *)"\316t \017W", INSERT}, /* CTRL-Right is '^OW' */ +#endif /* WIN32 */ + }; +#endif + +#ifdef __EMX__ + _wildcard(&argc, &argv); +#endif + +#ifdef HAVE_LOCALE_H + setlocale(LC_ALL, ""); /* for ctype() and the like */ +#endif + +#ifdef USE_GUI + gui_prepare(&argc, argv); /* Prepare for possibly starting GUI sometime */ +#endif + +/* + * Check if we have an interactive window. + * On the Amiga: If there is no window, we open one with a newcli command + * (needed for :! to * work). mch_check_win() will also handle the -d argument. + */ + stdout_notty = (mch_check_win(argc, argv) == FAIL); + +/* + * allocate the first window and buffer. Can't do anything without it + */ + if ((curwin = win_alloc(NULL)) == NULL || + (curbuf = buflist_new(NULL, NULL, 1L, FALSE)) == NULL) + mch_windexit(0); + curwin->w_buffer = curbuf; + screen_start(); /* don't know where cursor is yet */ + +/* + * Allocate space for the generic buffers (needed for set_init_1()). + */ + if ((IObuff = alloc(IOSIZE)) == NULL || + (NameBuff = alloc(MAXPATHL)) == NULL) + mch_windexit(0); + +/* + * Set the default values for the options. + * First find out the home directory, needed to expand "~" in options. + */ + init_homedir(); /* find real value of $HOME */ + set_init_1(); + +/* + * If the executable is called "view" we start in readonly mode. + */ + if (STRCMP(gettail((char_u *)argv[0]), (char_u *)"view") == 0) + { + readonlymode = TRUE; + curbuf->b_p_ro = TRUE; + if (p_uc) /* if we are doing any updating.. */ + p_uc = 10000; /* ..don't update very often */ + } + +/* + * If the executable is called "gvim" we run the GUI version. + */ + if (STRCMP(gettail((char_u *)argv[0]), (char_u *)"gvim") == 0) + { +#ifdef USE_GUI + gui.starting = TRUE; +#else + fprintf(stderr, (char *)e_nogvim); + mch_windexit(2); +#endif + } + +/* + * If the executable is called "vi" we switch to compat mode. + */ + if (STRCMP(gettail((char_u *)argv[0]), (char_u *)"vi") == 0) + { + vi_mode = TRUE; + } + + ++argv; + /* + * Process the command line arguments + * '+{command}' execute command + * '-b' binary + * '-c {command}' execute command + * '-d {device}' device (for Amiga) + * '-f' Don't fork when starging GUI. (if USE_GUI defined) + * '-g' Run with GUI. (if USE_GUI defined) + * '-H' Start in right-left mode + * '-i viminfo' use instead of p_viminfo + * '-n' no .vim file + * '-o[N]' open N windows (default: number of files) + * '-r' recovery mode + * '-L' recovery mode + * '-s scriptin' read from script file + * '-T terminal' terminal name + * '-u vimrc' read initializations from a file + * '-v' view or Readonly mode + * '-R' view or Readonly mode + * '-w scriptout' write to script file (append) + * '-W scriptout' write to script file (overwrite) + * '-x' open window directly, not with newcli + */ + argv_idx = 1; /* active option letter is argv[0][argv_idx] */ + + while (argc > 1 && ((c = argv[0][0]) == '+' || (c == '-' && + vim_strchr((char_u *)"bcdfgHilLnorRsTuvwWx", + c = argv[0][argv_idx]) != NULL))) + { + ++argv_idx; /* advance to next option letter by default */ + switch (c) + { + case '+': /* + or +{number} or +/{pat} or +{command} */ + argv_idx = -1; /* skip to next argument */ + if (argv[0][1] == NUL) + command = (char_u *)"$"; + else + command = (char_u *)&(argv[0][1]); + break; + + case 'b': + bin_mode = TRUE; /* postpone to after reading .exrc files */ + break; + +#ifdef USE_GUI + case 'f': + gui.dofork = FALSE; /* don't fork() when starting GUI */ + break; +#endif + + case 'g': +#ifdef USE_GUI + gui.starting = TRUE; /* start GUI a bit later */ +#else + fprintf(stderr, (char *)e_nogvim); + mch_windexit(2); +#endif + break; + + case 'H': /* start in Hebrew mode: rl + hkmap set */ +#ifdef RIGHTLEFT + curwin->w_p_rl = p_hkmap = TRUE; +#else + fprintf(stderr, (char *)e_nohebrew); + mch_windexit(2); +#endif + break; + + case 'l': /* -l: lisp mode, 'lisp' and 'showmatch' on */ + curbuf->b_p_lisp = TRUE; + p_sm = TRUE; + break; + + case 'n': + p_uc = 0; + break; + + case 'o': + window_count = 0; /* default: open window for each file */ + if (isdigit(argv[0][argv_idx])) + { + window_count = atoi(&(argv[0][argv_idx])); + while (isdigit(argv[0][argv_idx])) + ++argv_idx; + } + break; + + case 'r': + case 'L': + recoverymode = 1; + break; + + case 'v': + case 'R': + readonlymode = TRUE; + curbuf->b_p_ro = TRUE; + if (p_uc) /* if we are doing any updating.. */ + p_uc = 10000; /* ..don't update very often */ + break; + + case 'x': + break; /* This is ignored as it is handled in mch_check_win() */ + + + case 'w': + if (isdigit(argv[0][argv_idx])) /* -w{number}; set window height */ + { + argv_idx = -1; + break; /* not implemented, ignored */ + } + /* FALLTHROUGH */ + + default: /* options with argument */ + /* + * Check there's no garbage immediately after the option letter. + */ + if (argv[0][argv_idx] != NUL) + usage(USAGE_GARBAGE, (char_u *)argv[0]); + + --argc; + if (argc < 2) + usage(USAGE_ARG_MISSING, (char_u *)argv[0]); + ++argv; + argv_idx = -1; + + switch (c) + { + case 'c': /* -c {command} */ + command = (char_u *)argv[0]; + break; + + /* case 'd': This is ignored as it is handled in mch_check_win() */ + + case 'i': /* -i {viminfo} */ + use_viminfo = (char_u *)argv[0]; + break; + + case 's': /* -s {scriptin} */ + if (scriptin[0] != NULL) + { + fprintf(stderr, + "Attempt to open script file again: \"%s %s\"\n", + argv[-1], argv[0]); + mch_windexit(2); + } + if ((scriptin[0] = fopen(argv[0], READBIN)) == NULL) + { + fprintf(stderr, "Cannot open \"%s\" for reading\n", argv[0]); + mch_windexit(2); + } + break; + +/* + * The -T term option is always available and when HAVE_TERMLIB is supported + * it overrides the environment variable TERM. + */ + case 'T': /* -T {terminal} */ + term = (char_u *)argv[0]; + break; + + case 'u': /* -u {vimrc} */ + use_vimrc = (char_u *)argv[0]; + break; + + case 'w': /* -w {scriptout} (append) */ + case 'W': /* -W {scriptout} (overwrite) */ + if (scriptout != NULL) + { + fprintf(stderr, + "Attempt to open script file again: \"%s %s\"\n", + argv[-1], argv[0]); + mch_windexit(2); + } + if ((scriptout = fopen(argv[0], + c == 'w' ? APPENDBIN : WRITEBIN)) == NULL) + { + fprintf(stderr, "cannot open \"%s\" for output\n", argv[0]); + mch_windexit(2); + } + break; + } + } + /* + * If there are no more letters after the current "-", go to next + * argument. argv_idx is set to -1 when the current argument is to be + * skipped. + */ + if (argv_idx <= 0 || argv[0][argv_idx] == NUL) + { + --argc; + ++argv; + argv_idx = 1; + } + } + + /* note that we may use mch_windexit() before mch_windinit()! */ + mch_windinit(); /* inits Rows and Columns */ +/* + * Set the default values for the options that use Rows and Columns. + */ + set_init_2(); + + firstwin->w_height = Rows - 1; + cmdline_row = Rows - 1; + + /* + * Process the other command line arguments. + * -e[errorfile] quickfix mode + * -t[tagname] jump to tag + * [--] [file ..] file names + */ + if (argc > 1) + { + if (argv[0][0] == '-' && (argv[0][1] != '-' || argv[0][2] != NUL)) + { + switch (argv[0][1]) + { + case 'e': /* -e QuickFix mode */ + switch (argc) + { + case 2: + if (argv[0][2]) /* -eerrorfile */ + p_ef = (char_u *)argv[0] + 2; + break; /* -e */ + + case 3: /* -e errorfile */ + if (argv[0][2] != NUL) + usage(USAGE_GARBAGE, (char_u *)argv[0]); + ++argv; + p_ef = (char_u *)argv[0]; + break; + + default: /* argc > 3: too many arguments */ + usage(USAGE_TOO_MANY_ARGS, NULL); + } + doqf = 1; + break; + + case 't': /* -t tag or -ttag */ + switch (argc) + { + case 2: + if (argv[0][2]) /* -ttag */ + { + tagname = (char_u *)argv[0] + 2; + break; + } + usage(USAGE_ARG_MISSING, (char_u *)argv[0]); + break; + + case 3: /* -t tag */ + if (argv[0][2] != NUL) /* also -ttag?! */ + usage(USAGE_GARBAGE, (char_u *)argv[0]); + ++argv; + tagname = (char_u *)argv[0]; + break; + + default: /* argc > 3: too many arguments */ + usage(USAGE_TOO_MANY_ARGS, NULL); + } + break; + + default: + usage(USAGE_UNKNOWN_OPTION, (char_u *)argv[0]); + } + } + else /* must be a file name */ + { + /* + * Skip a single "--" argument, used in front of a file name that + * starts with '-'. + */ + if (argc > 2 && STRCMP(argv[0], "--") == 0) + { + ++argv; + --argc; + } + +#if (!defined(UNIX) && !defined(__EMX__)) || defined(ARCHIE) + if (ExpandWildCards(argc - 1, (char_u **)argv, &arg_count, + &arg_files, TRUE, TRUE) == OK && arg_count != 0) + { + fname = arg_files[0]; + arg_exp = TRUE; + } +#else + arg_files = (char_u **)argv; + arg_count = argc - 1; + fname = (char_u *)argv[0]; +#endif + if (arg_count > 1) + { + printf("%d files to edit\n", arg_count); + screen_start(); /* don't know where cursor is now */ + } + } + } + + RedrawingDisabled = TRUE; + + /* + * When listing swap file names, don't do cursor positioning et. al. + */ + if (recoverymode && fname == NULL) + full_screen = FALSE; + +#ifdef USE_GUI + /* + * We don't want to open the GUI window until after we've read .vimrc, + * otherwise we don't know what font we will use, and hence we don't know + * what size the window should be. So if there are errors in the .vimrc + * file, they will have to go to the terminal -- webb + */ + if (gui.starting) + full_screen = FALSE; +#endif + + /* + * Now print a warning if stdout is not a terminal. + */ + if (full_screen && (stdout_notty || mch_check_input() == FAIL)) + { + if (stdout_notty) + fprintf(stderr, "Vim: Warning: Output is not to a terminal\n"); + if (mch_check_input() == FAIL) + fprintf(stderr, "Vim: Warning: Input is not from a terminal\n"); + mch_delay(2000L, TRUE); + screen_start(); /* don't know where cursor is now */ + } + + curbuf->b_nwindows = 1; /* there is one window */ + win_init(curwin); /* init current window */ + init_yank(); /* init yank buffers */ + if (full_screen) + termcapinit(term); /* set terminal name and get terminal + capabilities */ + screenclear(); /* clear screen (just inits screen structures, + because starting is TRUE) */ + + if (full_screen) + msg_start(); /* in case a mapping or error message is printed */ + msg_scroll = TRUE; + no_wait_return = TRUE; + +#if defined(MSDOS) || defined(WIN32) || defined(OS2) +/* + * Default mapping for some often used keys. + * Need to put string in allocated memory, because do_map() will modify it. + */ + for (i = 0; i < sizeof(initmappings) / sizeof(struct initmap); ++i) + { + initstr = strsave(initmappings[i].arg); + if (initstr != NULL) + { + do_map(0, initstr, initmappings[i].mode); + vim_free(initstr); + } + } +#endif + +/* + * If -u option give, use only the initializations from that file and nothing + * else. + */ + if (use_vimrc != NULL) + { + if (STRCMP(use_vimrc, "NONE") != 0) + { + if (do_source(use_vimrc, FALSE) == OK) + check_version = TRUE; + else + EMSG2("Cannot read from \"%s\"", use_vimrc); + } + } + else + { + + /* + * get system wide defaults (for unix) + */ +#if defined(HAVE_CONFIG_H) || defined(OS2) + if (do_source(((vi_mode == TRUE) ? sys_compatrc_fname + : sys_vimrc_fname), TRUE) == OK) + check_version = TRUE; +#endif + + /* + * Try to read initialization commands from the following places: + * - environment variable VIMINIT + * - user vimrc file (s:.vimrc for Amiga, ~/.vimrc for Unix) + * - environment variable EXINIT + * - user exrc file (s:.exrc for Amiga, ~/.exrc for Unix) + * The first that exists is used, the rest is ignored. + */ + if ((initstr = vim_getenv((char_u *)"VIMINIT")) != NULL && + *initstr != NUL) + { + sourcing_name = (char_u *)"VIMINIT"; + do_cmdline(initstr, TRUE, TRUE); + sourcing_name = NULL; + } + else if (do_source((char_u *)USR_VIMRC_FILE, TRUE) == FAIL) + { + if ((initstr = vim_getenv((char_u *)"EXINIT")) != NULL) + { + sourcing_name = (char_u *)"EXINIT"; + do_cmdline(initstr, TRUE, TRUE); + sourcing_name = NULL; + } + else + (void)do_source((char_u *)USR_EXRC_FILE, FALSE); + } + else + check_version = TRUE; + + /* + * Read initialization commands from ".vimrc" or ".exrc" in current + * directory. This is only done if the 'exrc' option is set. + * Because of security reasons we disallow shell and write commands now, + * except for unix if the file is owned by the user or 'secure' option has + * been reset in environmet of global ".exrc" or ".vimrc". + * Only do this if VIMRC_FILE is not the same as USR_VIMRC_FILE or + * sys_vimrc_fname. + */ + if (p_exrc) + { +#ifdef UNIX + { + struct stat s; + + /* if ".vimrc" file is not owned by user, set 'secure' mode */ + if (stat(VIMRC_FILE, &s) || s.st_uid != getuid()) + secure = p_secure; + } +#else + secure = p_secure; +#endif + + i = FAIL; + if (fullpathcmp((char_u *)USR_VIMRC_FILE, + (char_u *)VIMRC_FILE) != FPC_SAME +#if defined(HAVE_CONFIG_H) || defined(OS2) + && fullpathcmp(((vi_mode == TRUE) ? sys_compatrc_fname : sys_vimrc_fname), + (char_u *)VIMRC_FILE) != FPC_SAME +#endif + ) + i = do_source((char_u *)VIMRC_FILE, TRUE); +#ifdef UNIX + if (i == FAIL) + { + struct stat s; + + /* if ".exrc" is not owned by user set 'secure' mode */ + if (stat(EXRC_FILE, &s) || s.st_uid != getuid()) + secure = p_secure; + else + secure = 0; + } + else + check_version = TRUE; +#endif + if (i == FAIL && fullpathcmp((char_u *)USR_EXRC_FILE, + (char_u *)EXRC_FILE) != FPC_SAME) + (void)do_source((char_u *)EXRC_FILE, FALSE); + } + } + + /* + * Recovery mode without a file name: List swap files. + * This uses the 'dir' option, therefore it must be after the + * initializations. + */ + if (recoverymode && fname == NULL) + { + recover_names(NULL, TRUE, 0); + mch_windexit(0); + } + + /* + * Set a few option defaults after reading .vimrc files: + * 'title' and 'icon', Unix: 'shellpipe' and 'shellredir'. + */ + set_init_3(); + +#ifdef USE_GUI + if (gui.starting) + { + gui_start(); + gui.starting = FALSE; + full_screen = TRUE; + } +#endif + + /* + * If we read a .vimrc but it does not contain a "version 4.0" command, + * give the user a pointer to the help for the new version. + */ + if (check_version && found_version == 0) + { + MSG("This is Vim version 4.0."); + MSG("No \":version 4.0\" command found in any .vimrc."); + MSG("Use \":help version\" for info about this new version."); + } + +#ifdef VIMINFO +/* + * Read in registers, history etc, but not marks, from the viminfo file + */ + if (*p_viminfo != NUL) + read_viminfo(NULL, TRUE, FALSE, FALSE); +#endif /* VIMINFO */ + +#ifdef SPAWNO /* special MSDOS swapping library */ + init_SPAWNO("", SWAP_ANY); +#endif + + if (bin_mode) /* -b option used */ + { + set_options_bin(curbuf->b_p_bin, 1); + curbuf->b_p_bin = 1; /* binary file I/O */ + } + + /* Don't set the file name if there was a command in .vimrc that already + * loaded the file */ + if (curbuf->b_filename == NULL) + { + (void)setfname(fname, NULL, TRUE); /* includes maketitle() */ + ++arg_idx; /* used first argument name */ + } + + if (window_count == 0) + window_count = arg_count; + if (window_count > 1) + { + /* Don't change the windows if there was a command in .vimrc that + * already split some windows */ + if (firstwin->w_next == NULL) + window_count = make_windows(window_count); + else + window_count = win_count(); + } + else + window_count = 1; + +/* + * "-e errorfile": Load the error file now. + * If the error file can't be read, exit before doing anything else. + */ + if (doqf && qf_init() == FAIL) /* if reading error file fails: exit */ + mch_windexit(3); + +/* + * Start putting things on the screen. + * Scroll screen down before drawing over it + * Clear screen now, so file message will not be cleared. + */ + starting = FALSE; + no_wait_return = FALSE; + msg_scroll = FALSE; +#ifdef USE_GUI + /* + * This seems to be required to make callbacks to be called now, instead + * of after things have been put on the screen, which then may be deleted + * when getting a resize callback. + */ + if (gui.in_use) + gui_mch_wait_for_chars(50); +#endif + +/* + * When done something that is not allowed or error message call wait_return. + * This must be done before starttermcap(), because it may switch to another + * screen. It must be done after settmode(1), because we want to react on a + * single key stroke. + * Call settmode and starttermcap here, so the T_KS and T_TI may be defined + * by termcapinit and redifined in .exrc. + */ + settmode(1); + if (secure == 2 || need_wait_return || msg_didany) + wait_return(TRUE); + + starttermcap(); /* start termcap if not done by wait_return() */ +#ifdef USE_MOUSE + setmouse(); /* may start using the mouse */ +#endif + if (scroll_region) + scroll_region_reset(); /* In case Rows changed */ + + secure = 0; + + scroll_start(); + screenclear(); /* clear screen */ + + no_wait_return = TRUE; + + if (recoverymode) /* do recover */ + { + msg_scroll = TRUE; /* scroll message up */ + ml_recover(); + msg_scroll = FALSE; + if (curbuf->b_ml.ml_mfp == NULL) /* failed */ + getout(1); +#ifdef AUTOCMD + apply_autocmds(EVENT_BUFENTER, NULL, NULL); +#endif + do_modelines(); /* do modelines */ + } + /* Only read the file if there is none for the current buffer, a command + * in the .vimrc might have loaded a file */ + else if (curbuf->b_ml.ml_mfp == NULL) + (void)open_buffer(); /* create memfile and read file */ + + setpcmark(); + + /* + * When started with "-e errorfile" jump to first error now. + */ + if (doqf) + qf_jump(0, 0); + + /* + * If opened more than one window, start editing files in the other windows. + * Make_windows() has already opened the windows. + */ + for (i = 1; i < window_count; ++i) + { + if (curwin->w_next == NULL) /* just checking */ + break; + win_enter(curwin->w_next, FALSE); + + /* Only open the file if there is no file in this window yet (that can + * happen when .vimrc contains ":sall") */ + if (curbuf == firstwin->w_buffer || curbuf->b_filename == NULL) + { + curwin->w_arg_idx = arg_idx; + /* edit file from arg list, if there is one */ + (void)do_ecmd(0, arg_idx < arg_count ? arg_files[arg_idx] : NULL, + NULL, NULL, TRUE, (linenr_t)1, FALSE); + if (arg_idx == arg_count - 1) + arg_had_last = TRUE; + ++arg_idx; + } + mch_breakcheck(); + if (got_int) + { + (void)vgetc(); /* only break the file loading, not the rest */ + break; + } + } + win_enter(firstwin, FALSE); /* back to first window */ + if (window_count > 1) + win_equal(curwin, FALSE); /* adjust heights */ + + /* + * If there are more file names in the argument list than windows, + * put the rest of the names in the buffer list. + */ + while (arg_idx < arg_count) + (void)buflist_add(arg_files[arg_idx++]); + + /* + * Need to jump to the tag before executing the '-c command'. + * Makes "vim -c '/return' -t main" work. + */ + if (tagname) + { + STRCPY(IObuff, "ta "); + STRCAT(IObuff, tagname); + do_cmdline(IObuff, TRUE, TRUE); + } + + if (command) + { + /* + * We start commands on line 0, make "vim +/pat file" match a + * pattern on line 1. + */ + curwin->w_cursor.lnum = 0; + sourcing_name = (char_u *)"command line"; + do_cmdline(command, TRUE, TRUE); + sourcing_name = NULL; + } + + RedrawingDisabled = FALSE; + redraw_later(NOT_VALID); + no_wait_return = FALSE; + + /* start in insert mode */ + if (p_im) + need_start_insertmode = TRUE; + +/* + * main command loop + */ + for (;;) + { + if (stuff_empty()) + { + if (need_check_timestamps) + check_timestamps(); + if (need_wait_return) /* if wait_return still needed ... */ + wait_return(FALSE); /* ... call it now */ + if (need_start_insertmode) + { + need_start_insertmode = FALSE; + stuffReadbuff((char_u *)"i"); /* start insert mode next */ + /* skip the fileinfo message now, because it would be shown + * after insert mode finishes! */ + need_fileinfo = FALSE; + } + } + dont_wait_return = FALSE; + if (got_int) + { + (void)vgetc(); /* flush all buffers */ + got_int = FALSE; + } + adjust_cursor(); /* put cursor on an existing line */ + msg_scroll = FALSE; + quit_more = FALSE; + keep_help_flag = FALSE; + /* + * If skip redraw is set (for ":" in wait_return()), don't redraw now. + * If there is nothing in the stuff_buffer or do_redraw is TRUE, + * update cursor and redraw. + */ + if (skip_redraw) + skip_redraw = FALSE; + else if (do_redraw || stuff_empty()) + { + cursupdate(); /* Figure out where the cursor is based + on curwin->w_cursor. */ +#ifdef SLEEP_IN_EMSG + if (need_sleep) /* sleep before redrawing */ + { + mch_delay(1000L, TRUE); + need_sleep = FALSE; + } +#endif + if (VIsual_active) + update_curbuf(INVERTED);/* update inverted part */ + if (must_redraw) + updateScreen(must_redraw); + else if (redraw_cmdline) + showmode(); + if (keep_msg != NULL) + { + if (keep_msg_highlight) + { + (void)set_highlight(keep_msg_highlight); + msg_highlight = TRUE; + } + msg(keep_msg); /* display message after redraw */ + } + if (need_fileinfo) /* used after jumping to a tag */ + { + fileinfo(did_cd, TRUE, FALSE); + need_fileinfo = FALSE; + } + + emsg_on_display = FALSE; /* can delete error message now */ + msg_didany = FALSE; /* reset lines_left in msg_start() */ + do_redraw = FALSE; + showruler(FALSE); + + setcursor(); + cursor_on(); + } + + /* + * get and execute a normal mode command + */ + normal(); + } + /*NOTREACHED*/ +} + + void +getout(r) + int r; +{ + exiting = TRUE; + + /* Position the cursor on the last screen line, below all the text */ +#ifdef USE_GUI + if (!gui.in_use) +#endif + windgoto((int)Rows - 1, 0); + +#ifdef AUTOCMD + apply_autocmds(EVENT_VIMLEAVE, NULL, NULL); + + /* Position the cursor again, the autocommands may have moved it */ +# ifdef USE_GUI + if (!gui.in_use) +# endif + windgoto((int)Rows - 1, 0); +#endif + +#ifdef VIMINFO + /* Write out the registers, history, marks etc, to the viminfo file */ + if (*p_viminfo != NUL) + write_viminfo(NULL, FALSE); +#endif /* VIMINFO */ + + outchar('\r'); + outchar('\n'); + mch_windexit(r); +} diff --git a/usr.bin/vim/mark.c b/usr.bin/vim/mark.c new file mode 100644 index 00000000000..5d64ad3f40e --- /dev/null +++ b/usr.bin/vim/mark.c @@ -0,0 +1,948 @@ +/* $OpenBSD: mark.c,v 1.1.1.1 1996/09/07 21:40:26 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * mark.c: functions for setting marks and jumping to them + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +/* + * This file contains routines to maintain and manipulate marks. + */ + +/* + * If a named file mark's lnum is non-zero, it is valid. + * If a named file mark's fnum is non-zero, it is for an existing buffer, + * otherwise it is from .viminfo and namedfm_names[n] is the file name. + * There are marks 'A - 'Z (set by user) and '0 to '9 (set when writing + * viminfo). + */ +#define EXTRA_MARKS 10 /* marks 0-9 */ +static struct filemark namedfm[NMARKS + EXTRA_MARKS]; /* marks with file nr */ +static char_u *namedfm_names[NMARKS + EXTRA_MARKS]; /* name for namedfm[] */ + +static void show_one_mark __ARGS((int, char_u *, FPOS *, char_u *)); +static void cleanup_jumplist __ARGS((void)); +#ifdef VIMINFO +static int removable __ARGS((char_u *name)); +#endif + +/* + * setmark(c) - set named mark 'c' at current cursor position + * + * Returns OK on success, FAIL if no room for mark or bad name given. + */ + int +setmark(c) + int c; +{ + int i; + + if (c > 'z') /* some islower() and isupper() cannot handle + characters above 127 */ + return FAIL; + if (islower(c)) + { + i = c - 'a'; + curbuf->b_namedm[i] = curwin->w_cursor; + return OK; + } + if (isupper(c)) + { + i = c - 'A'; + namedfm[i].mark = curwin->w_cursor; + namedfm[i].fnum = curbuf->b_fnum; + return OK; + } + return FAIL; +} + +/* + * setpcmark() - set the previous context mark to the current position + * and add it to the jump list + */ + void +setpcmark() +{ + int i; +#ifdef ROTATE + struct filemark tempmark; +#endif + + /* for :global the mark is set only once */ + if (global_busy) + return; + + curwin->w_prev_pcmark = curwin->w_pcmark; + curwin->w_pcmark = curwin->w_cursor; + +#ifdef ROTATE + /* + * If last used entry is not at the top, put it at the top by rotating + * the stack until it is (the newer entries will be at the bottom). + * Keep one entry (the last used one) at the top. + */ + if (curwin->w_jumplistidx < curwin->w_jumplistlen) + ++curwin->w_jumplistidx; + while (curwin->w_jumplistidx < curwin->w_jumplistlen) + { + tempmark = curwin->w_jumplist[curwin->w_jumplistlen - 1]; + for (i = curwin->w_jumplistlen - 1; i > 0; --i) + curwin->w_jumplist[i] = curwin->w_jumplist[i - 1]; + curwin->w_jumplist[0] = tempmark; + ++curwin->w_jumplistidx; + } +#endif + + /* If jumplist is full: remove oldest entry */ + if (++curwin->w_jumplistlen > JUMPLISTSIZE) + { + curwin->w_jumplistlen = JUMPLISTSIZE; + for (i = 1; i < JUMPLISTSIZE; ++i) + curwin->w_jumplist[i - 1] = curwin->w_jumplist[i]; + } + curwin->w_jumplistidx = curwin->w_jumplistlen - 1; + +#ifdef ARCHIE + /* Workaround for a bug in gcc 2.4.5 R2 on the Archimedes + * Should be fixed in 2.5.x. + */ + curwin->w_jumplist[curwin->w_jumplistidx].mark.ptr = curwin->w_pcmark.ptr; + curwin->w_jumplist[curwin->w_jumplistidx].mark.col = curwin->w_pcmark.col; +#else + curwin->w_jumplist[curwin->w_jumplistidx].mark = curwin->w_pcmark; +#endif + curwin->w_jumplist[curwin->w_jumplistidx].fnum = curbuf->b_fnum; + ++curwin->w_jumplistidx; + + /* remove any duplicates, from the new entry or from previous deletes */ + cleanup_jumplist(); +} + +/* + * checkpcmark() - To change context, call setpcmark(), then move the current + * position to where ever, then call checkpcmark(). This + * ensures that the previous context will only be changed if + * the cursor moved to a different line. -- webb. + * If pcmark was deleted (with "dG") the previous mark is + * restored. + */ + void +checkpcmark() +{ + if (curwin->w_prev_pcmark.lnum != 0 && + (equal(curwin->w_pcmark, curwin->w_cursor) || + curwin->w_pcmark.lnum == 0)) + { + curwin->w_pcmark = curwin->w_prev_pcmark; + curwin->w_prev_pcmark.lnum = 0; /* Show it has been checked */ + } +} + +/* + * move "count" positions in the jump list (count may be negative) + */ + FPOS * +movemark(count) + int count; +{ + FPOS *pos; + + cleanup_jumplist(); + + if (curwin->w_jumplistlen == 0) /* nothing to jump to */ + return (FPOS *)NULL; + + if (curwin->w_jumplistidx + count < 0 || + curwin->w_jumplistidx + count >= curwin->w_jumplistlen) + return (FPOS *)NULL; + + /* + * if first CTRL-O or CTRL-I command after a jump, add cursor position to + * list. Careful: If there are duplicates (CTRL-O immidiately after + * starting Vim on a file), another entry may have been removed. + */ + if (curwin->w_jumplistidx == curwin->w_jumplistlen) + { + setpcmark(); + --curwin->w_jumplistidx; /* skip the new entry */ + if (curwin->w_jumplistidx + count < 0) + return (FPOS *)NULL; + } + + curwin->w_jumplistidx += count; + /* jump to other file */ + if (curwin->w_jumplist[curwin->w_jumplistidx].fnum != curbuf->b_fnum) + { + if (buflist_getfile(curwin->w_jumplist[curwin->w_jumplistidx].fnum, + curwin->w_jumplist[curwin->w_jumplistidx].mark.lnum, + 0) == FAIL) + return (FPOS *)NULL; + curwin->w_cursor.col = + curwin->w_jumplist[curwin->w_jumplistidx].mark.col; + pos = (FPOS *)-1; + } + else + pos = &(curwin->w_jumplist[curwin->w_jumplistidx].mark); + return pos; +} + +/* + * getmark(c) - find mark for char 'c' + * + * Return pointer to FPOS if found (caller needs to check lnum!) + * NULL if there is no mark called 'c'. + * -1 if mark is in other file (only if changefile is TRUE) + */ + FPOS * +getmark(c, changefile) + int c; + int changefile; +{ + FPOS *posp; + FPOS *startp, *endp; + static FPOS pos_copy; + int len; + char_u *p; + + posp = NULL; + if (c > '~') /* check for islower()/isupper() */ + ; + else if (c == '\'' || c == '`') /* previous context mark */ + { + pos_copy = curwin->w_pcmark; /* need to make a copy because */ + posp = &pos_copy; /* w_pcmark may be changed soon */ + } + else if (c == '"') /* to pos when leaving buffer */ + posp = &(curbuf->b_last_cursor); + else if (c == '[') /* to start of previous operator */ + posp = &(curbuf->b_op_start); + else if (c == ']') /* to end of previous operator */ + posp = &(curbuf->b_op_end); + else if (c == '<' || c == '>') /* start/end of visual area */ + { + if (VIsual_active) + startp = &VIsual_save; + else + startp = &VIsual; + endp = &VIsual_end; + if ((c == '<') == lt(*startp, *endp)) + posp = startp; + else + posp = endp; + } + else if (islower(c)) /* normal named mark */ + posp = &(curbuf->b_namedm[c - 'a']); + else if (isupper(c) || isdigit(c)) /* named file mark */ + { + if (isdigit(c)) + c = c - '0' + NMARKS; + else + c -= 'A'; + posp = &(namedfm[c].mark); + + if (namedfm[c].fnum == 0 && namedfm_names[c] != NULL) + { + /* + * First expand "~/" in the file name to the home directory. + * Try to find the shortname by comparing the fullname with the + * current directory. + */ + expand_env(namedfm_names[c], NameBuff, MAXPATHL); + mch_dirname(IObuff, IOSIZE); + len = STRLEN(IObuff); + if (fnamencmp(IObuff, NameBuff, len) == 0) + { + p = NameBuff + len; + if (ispathsep(*p)) + ++p; + } + else + p = NULL; + /* buflist_new will call fmarks_check_names() */ + (void)buflist_new(NameBuff, p, (linenr_t)1, FALSE); + } + + if (namedfm[c].fnum != curbuf->b_fnum) /* mark is in other file */ + { + if (namedfm[c].mark.lnum != 0 && changefile && namedfm[c].fnum) + { + if (buflist_getfile(namedfm[c].fnum, + namedfm[c].mark.lnum, GETF_SETMARK) == OK) + { + curwin->w_cursor.col = namedfm[c].mark.col; + return (FPOS *)-1; + } + } + posp = &pos_copy; /* mark exists, but is not valid in + current buffer */ + pos_copy.lnum = 0; + } + } + return posp; +} + +/* + * Check all file marks for a name that matches the file name in buf. + * May replace the name with an fnum. + */ + void +fmarks_check_names(buf) + BUF *buf; +{ + char_u *name; + int i; + + if (buf->b_filename == NULL) + return; + + name = home_replace_save(buf, buf->b_filename); + if (name == NULL) + return; + + for (i = 0; i < NMARKS + EXTRA_MARKS; ++i) + { + if (namedfm[i].fnum == 0 && namedfm_names[i] != NULL && + fnamecmp(name, namedfm_names[i]) == 0) + { + namedfm[i].fnum = buf->b_fnum; + vim_free(namedfm_names[i]); + namedfm_names[i] = NULL; + } + } + vim_free(name); +} + +/* + * Check a if a position from a mark is valid. + * Give and error message and return FAIL if not. + */ + int +check_mark(pos) + FPOS *pos; +{ + if (pos == NULL) + { + emsg(e_umark); + return FAIL; + } + if (pos->lnum == 0) + { + emsg(e_marknotset); + return FAIL; + } + if (pos->lnum > curbuf->b_ml.ml_line_count) + { + emsg(e_markinval); + return FAIL; + } + return OK; +} + +/* + * clrallmarks() - clear all marks in the buffer 'buf' + * + * Used mainly when trashing the entire buffer during ":e" type commands + */ + void +clrallmarks(buf) + BUF *buf; +{ + static int i = -1; + + if (i == -1) /* first call ever: initialize */ + for (i = 0; i < NMARKS + 1; i++) + { + namedfm[i].mark.lnum = 0; + namedfm_names[i] = NULL; + } + + for (i = 0; i < NMARKS; i++) + buf->b_namedm[i].lnum = 0; + buf->b_op_start.lnum = 0; /* start/end op mark cleared */ + buf->b_op_end.lnum = 0; +} + +/* + * get name of file from a filemark + * Careful: buflist_nr2name returns NameBuff. + */ + char_u * +fm_getname(fmark) + struct filemark *fmark; +{ + if (fmark->fnum != curbuf->b_fnum) /* not current file */ + return buflist_nr2name(fmark->fnum, FALSE, TRUE); + return (char_u *)"-current-"; +} + +/* + * print the marks + */ + void +do_marks(arg) + char_u *arg; +{ + int i; + char_u *name; + + if (arg != NULL && *arg == NUL) + arg = NULL; + + show_one_mark('\'', arg, &curwin->w_pcmark, NULL); + for (i = 0; i < NMARKS; ++i) + show_one_mark(i + 'a', arg, &curbuf->b_namedm[i], NULL); + for (i = 0; i < NMARKS + EXTRA_MARKS; ++i) + { + name = namedfm[i].fnum == 0 ? namedfm_names[i] : + fm_getname(&namedfm[i]); + if (name != NULL) + show_one_mark(i >= NMARKS ? i - NMARKS + '0' : i + 'A', + arg, &namedfm[i].mark, name); + } + show_one_mark('"', arg, &curbuf->b_last_cursor, NULL); + show_one_mark('[', arg, &curbuf->b_op_start, NULL); + show_one_mark(']', arg, &curbuf->b_op_end, NULL); + show_one_mark('<', arg, &VIsual, NULL); + show_one_mark('>', arg, &VIsual_end, NULL); + show_one_mark(-1, arg, NULL, NULL); +} + + static void +show_one_mark(c, arg, p, name) + int c; + char_u *arg; + FPOS *p; + char_u *name; +{ + static int did_title = FALSE; + + if (c == -1) /* finish up */ + { + if (did_title) + did_title = FALSE; + else + { + if (arg == NULL) + MSG("No marks set"); + else + EMSG2("No marks matching \"%s\"", arg); + } + } + /* don't output anything if 'q' typed at --more-- prompt */ + else if (!got_int && (arg == NULL || vim_strchr(arg, c) != NULL) && + p->lnum != 0) + { + if (!did_title) + { + set_highlight('t'); /* Highlight title */ + start_highlight(); + MSG_OUTSTR("\nmark line col file"); + stop_highlight(); + did_title = TRUE; + } + msg_outchar('\n'); + if (!got_int) + { + sprintf((char *)IObuff, " %c %5ld %3d ", c, p->lnum, p->col); + if (name != NULL) + STRCAT(IObuff, name); + msg_outtrans(IObuff); + } + flushbuf(); /* show one line at a time */ + } +} + +/* + * print the jumplist + */ + void +do_jumps() +{ + int i; + char_u *name; + + cleanup_jumplist(); + set_highlight('t'); /* Highlight title */ + start_highlight(); + MSG_OUTSTR("\n jump line file"); + stop_highlight(); + for (i = 0; i < curwin->w_jumplistlen; ++i) + { + if (curwin->w_jumplist[i].mark.lnum != 0) + { + name = fm_getname(&curwin->w_jumplist[i]); + if (name == NULL) /* file name not available */ + continue; + + msg_outchar('\n'); + sprintf((char *)IObuff, "%c %2d %5ld %s", + i == curwin->w_jumplistidx ? '>' : ' ', + i + 1, + curwin->w_jumplist[i].mark.lnum, + name); + msg_outtrans(IObuff); + } + flushbuf(); + } + if (curwin->w_jumplistidx == curwin->w_jumplistlen) + MSG_OUTSTR("\n>"); +} + +/* + * adjust marks between line1 and line2 (inclusive) to move 'amount' lines + * If 'amount' is MAXLNUM the mark is made invalid. + * If 'amount_after' is non-zero adjust marks after line 2. + */ + +#define one_adjust(add) \ + { \ + lp = add; \ + if (*lp >= line1 && *lp <= line2) \ + { \ + if (amount == MAXLNUM) \ + *lp = 0; \ + else \ + *lp += amount; \ + } \ + else if (amount_after && *lp > line2) \ + *lp += amount_after; \ + } + +/* don't delete the line, just put at first deleted line */ +#define one_adjust_nodel(add) \ + { \ + lp = add; \ + if (*lp >= line1 && *lp <= line2) \ + { \ + if (amount == MAXLNUM) \ + *lp = line1; \ + else \ + *lp += amount; \ + } \ + else if (amount_after && *lp > line2) \ + *lp += amount_after; \ + } + + void +mark_adjust(line1, line2, amount, amount_after) + linenr_t line1; + linenr_t line2; + long amount; + long amount_after; +{ + int i; + int fnum = curbuf->b_fnum; + linenr_t *lp; + WIN *win; + + if (line2 < line1 && amount_after == 0L) /* nothing to do */ + return; + +/* named marks, lower case and upper case */ + for (i = 0; i < NMARKS; i++) + { + one_adjust(&(curbuf->b_namedm[i].lnum)); + if (namedfm[i].fnum == fnum) + one_adjust(&(namedfm[i].mark.lnum)); + } + for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++) + { + if (namedfm[i].fnum == fnum) + one_adjust(&(namedfm[i].mark.lnum)); + } + +/* previous context mark */ + one_adjust(&(curwin->w_pcmark.lnum)); + +/* previous pcmark */ + one_adjust(&(curwin->w_prev_pcmark.lnum)); + +/* Visual area */ + one_adjust_nodel(&(VIsual.lnum)); + one_adjust_nodel(&(VIsual_end.lnum)); + +/* marks in the tag stack */ + for (i = 0; i < curwin->w_tagstacklen; i++) + if (curwin->w_tagstack[i].fmark.fnum == fnum) + one_adjust_nodel(&(curwin->w_tagstack[i].fmark.mark.lnum)); + +/* quickfix marks */ + qf_mark_adjust(line1, line2, amount, amount_after); + +/* jumplist marks */ + for (win = firstwin; win != NULL; win = win->w_next) + { + /* + * When deleting lines, this may create duplicate marks in the + * jumplist. They will be removed later. + */ + for (i = 0; i < win->w_jumplistlen; ++i) + if (win->w_jumplist[i].fnum == fnum) + one_adjust_nodel(&(win->w_jumplist[i].mark.lnum)); + /* + * also adjust the line at the top of the window and the cursor + * position for windows with the same buffer. + */ + if (win != curwin && win->w_buffer == curbuf) + { + if (win->w_topline >= line1 && win->w_topline <= line2) + { + if (amount == MAXLNUM) /* topline is deleted */ + { + if (line1 <= 1) + win->w_topline = 1; + else + win->w_topline = line1 - 1; + } + else /* keep topline on the same line */ + win->w_topline += amount; + } + else if (amount_after && win->w_topline > line2) + win->w_topline += amount_after; + if (win->w_cursor.lnum >= line1 && win->w_cursor.lnum <= line2) + { + if (amount == MAXLNUM) /* line with cursor is deleted */ + { + if (line1 <= 1) + win->w_cursor.lnum = 1; + else + win->w_cursor.lnum = line1 - 1; + win->w_cursor.col = 0; + } + else /* keep cursor on the same line */ + win->w_cursor.lnum += amount; + } + else if (amount_after && win->w_cursor.lnum > line2) + win->w_cursor.lnum += amount_after; + } + } +} + +/* + * When deleting lines, this may create duplicate marks in the + * jumplist. They will be removed here for the current window. + */ + static void +cleanup_jumplist() +{ + int i; + int from, to; + + to = 0; + for (from = 0; from < curwin->w_jumplistlen; ++from) + { + if (curwin->w_jumplistidx == from) + curwin->w_jumplistidx = to; + for (i = from + 1; i < curwin->w_jumplistlen; ++i) + if (curwin->w_jumplist[i].fnum == curwin->w_jumplist[from].fnum && + curwin->w_jumplist[i].mark.lnum == + curwin->w_jumplist[from].mark.lnum) + break; + if (i >= curwin->w_jumplistlen) /* no duplicate */ + curwin->w_jumplist[to++] = curwin->w_jumplist[from]; + } + if (curwin->w_jumplistidx == curwin->w_jumplistlen) + curwin->w_jumplistidx = to; + curwin->w_jumplistlen = to; +} + + void +set_last_cursor(win) + WIN *win; +{ + win->w_buffer->b_last_cursor = win->w_cursor; +} + +#ifdef VIMINFO + int +read_viminfo_filemark(line, fp, force) + char_u *line; + FILE *fp; + int force; +{ + int idx; + char_u *str; + + /* We only get here (hopefully) if line[0] == '\'' */ + str = line + 1; + if (*str > 127 || (!isdigit(*str) && !isupper(*str))) + EMSG2("viminfo: Illegal file mark name in line %s", line); + else + { + if (isdigit(*str)) + idx = *str - '0' + NMARKS; + else + idx = *str - 'A'; + if (namedfm[idx].mark.lnum == 0 || force) + { + str = skipwhite(str + 1); + namedfm[idx].mark.lnum = getdigits(&str); + str = skipwhite(str); + namedfm[idx].mark.col = getdigits(&str); + str = skipwhite(str); + viminfo_readstring(line); + namedfm_names[idx] = strsave(str); + } + } + return vim_fgets(line, LSIZE, fp); +} + + void +write_viminfo_filemarks(fp) + FILE *fp; +{ + int i; + char_u *name; + + if (get_viminfo_parameter('\'') == 0) + return; + + fprintf(fp, "\n# File marks:\n"); + + /* + * Find a mark that is the same file and position as the cursor. + * That one, or else the last one is deleted. + * Move '0 to '1, '1 to '2, etc. until the matching one or '9 + * Set '0 mark to current cursor position. + */ + if (curbuf->b_filename != NULL && !removable(curbuf->b_filename)) + { + name = buflist_nr2name(curbuf->b_fnum, TRUE, FALSE); + for (i = NMARKS; i < NMARKS + EXTRA_MARKS - 1; ++i) + if (namedfm[i].mark.lnum == curwin->w_cursor.lnum && + (namedfm_names[i] == NULL ? + namedfm[i].fnum == curbuf->b_fnum : + STRCMP(name, namedfm_names[i]) == 0)) + break; + + vim_free(namedfm_names[i]); + for ( ; i > NMARKS; --i) + { + namedfm[i] = namedfm[i - 1]; + namedfm_names[i] = namedfm_names[i - 1]; + } + namedfm[NMARKS].mark = curwin->w_cursor; + namedfm[NMARKS].fnum = curbuf->b_fnum; + namedfm_names[NMARKS] = NULL; + } + + for (i = 0; i < NMARKS + EXTRA_MARKS; i++) + { + if (namedfm[i].mark.lnum == 0) /* not set */ + continue; + + if (namedfm[i].fnum) /* there is a buffer */ + name = buflist_nr2name(namedfm[i].fnum, TRUE, FALSE); + else + name = namedfm_names[i]; /* use name from .viminfo */ + if (name == NULL) + continue; + + fprintf(fp, "'%c %ld %ld %s\n", + i < NMARKS ? i + 'A' : i - NMARKS + '0', + (long)namedfm[i].mark.lnum, + (long)namedfm[i].mark.col, + name); + } +} + +/* + * Return TRUE if "name" is on removable media (depending on 'viminfo'). + */ + static int +removable(name) + char_u *name; +{ + char_u *p; + char_u part[51]; + int retval = FALSE; + + name = home_replace_save(NULL, name); + if (name != NULL) + { + for (p = p_viminfo; *p; ) + { + copy_option_part(&p, part, 51, ", "); + if (part[0] == 'r' && vim_strnicmp(part + 1, name, + (size_t)STRLEN(part + 1)) == 0) + { + retval = TRUE; + break; + } + } + vim_free(name); + } + return retval; +} + +/* + * Write all the named marks for all buffers. + * Return the number of buffers for which marks have been written. + */ + int +write_viminfo_marks(fp_out) + FILE *fp_out; +{ + int count; + BUF *buf; + WIN *win; + int is_mark_set; + int i; + + /* + * Set b_last_cursor for the all buffers that have a window. + */ + for (win = firstwin; win != NULL; win = win->w_next) + set_last_cursor(win); + + fprintf(fp_out, "\n# History of marks within files (newest to oldest):\n"); + count = 0; + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + { + /* + * Only write something if buffer has been loaded and at least one + * mark is set. + */ + if (buf->b_marks_read) + { + if (buf->b_last_cursor.lnum != 0) + is_mark_set = TRUE; + else + { + is_mark_set = FALSE; + for (i = 0; i < NMARKS; i++) + if (buf->b_namedm[i].lnum != 0) + { + is_mark_set = TRUE; + break; + } + } + if (is_mark_set && buf->b_filename != NULL && + buf->b_filename[0] != NUL && !removable(buf->b_filename)) + { + home_replace(NULL, buf->b_filename, IObuff, IOSIZE); + fprintf(fp_out, "\n> %s\n", (char *)IObuff); + if (buf->b_last_cursor.lnum != 0) + fprintf(fp_out, "\t\"\t%ld\t%d\n", + buf->b_last_cursor.lnum, buf->b_last_cursor.col); + for (i = 0; i < NMARKS; i++) + if (buf->b_namedm[i].lnum != 0) + fprintf(fp_out, "\t%c\t%ld\t%d\n", 'a' + i, + buf->b_namedm[i].lnum, buf->b_namedm[i].col); + count++; + } + } + } + + return count; +} + +/* + * Handle marks in the viminfo file: + * fp_out == NULL read marks for current buffer only + * fp_out != NULL copy marks for buffers not in buffer list + */ + void +copy_viminfo_marks(line, fp_in, fp_out, count, eof) + char_u *line; + FILE *fp_in; + FILE *fp_out; + int count; + int eof; +{ + BUF *buf; + int num_marked_files; + char_u save_char; + int load_marks; + int copy_marks_out; + char_u *str; + int i; + char_u *p; + + num_marked_files = get_viminfo_parameter('\''); + while (!eof && (count < num_marked_files || fp_out == NULL)) + { + if (line[0] != '>') + { + if (line[0] != '\n' && line[0] != '\r' && line[0] != '#') + EMSG2("viminfo: Illegal starting char in line %s", line); + eof = vim_fgets(line, LSIZE, fp_in); + continue; /* Skip this dud line */ + } + + /* + * Find filename, set str to start. + * Ignore leading and trailing white space. + */ + str = skipwhite(line + 1); + p = str + STRLEN(str); + while (p != str && (*p == NUL || vim_isspace(*p))) + p--; + if (*p) + p++; + save_char = *p; + *p = NUL; + + /* + * If fp_out == NULL, load marks for current buffer. + * If fp_out != NULL, copy marks for buffers not in buflist. + */ + load_marks = copy_marks_out = FALSE; + if (fp_out == NULL) + { + if (curbuf->b_filename != NULL && + fullpathcmp(str, curbuf->b_filename) == FPC_SAME) + load_marks = TRUE; + } + else /* fp_out != NULL */ + { + /* This is slow if there are many buffers!! */ + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + if (buf->b_filename != NULL && + fullpathcmp(str, buf->b_filename) == FPC_SAME) + break; + + /* + * copy marks if the buffer has not been loaded + */ + if (buf == NULL || !buf->b_marks_read) + { + copy_marks_out = TRUE; + *p = save_char; + fputs("\n", fp_out); + fputs((char *)line, fp_out); + count++; + } + } + while (!(eof = vim_fgets(line, LSIZE, fp_in)) && line[0] == TAB) + { + if (load_marks) + { + if (line[1] == '"') + sscanf((char *)line + 2, "%ld %d", + &curbuf->b_last_cursor.lnum, + &curbuf->b_last_cursor.col); + else if ((i = line[1] - 'a') >= 0 && i < NMARKS) + sscanf((char *)line + 2, "%ld %d", + &curbuf->b_namedm[i].lnum, + &curbuf->b_namedm[i].col); + } + else if (copy_marks_out) + fputs((char *)line, fp_out); + } + if (load_marks) + return; + } +} +#endif /* VIMINFO */ diff --git a/usr.bin/vim/memfile.c b/usr.bin/vim/memfile.c new file mode 100644 index 00000000000..223d9ce2f05 --- /dev/null +++ b/usr.bin/vim/memfile.c @@ -0,0 +1,1183 @@ +/* $OpenBSD: memfile.c,v 1.1.1.1 1996/09/07 21:40:26 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* for debugging */ +#define CHECK(c, s) if (c) printf(s) + +/* + * memfile.c: Contains the functions for handling blocks of memory which can + * be stored in a file. This is the implementation of a sort of virtual memory. + * + * A memfile consists of a sequence of blocks. The blocks numbered from 0 + * upwards have been assigned a place in the actual file. The block number + * is equal to the page number in the file. The + * blocks with negative numbers are currently in memory only. They can be + * assigned a place in the file when too much memory is being used. At that + * moment they get a new, positive, number. A list is used for translation of + * negative to positive numbers. + * + * The size of a block is a multiple of a page size, normally the page size of + * the device the file is on. Most blocks are 1 page long. A Block of multiple + * pages is used for a line that does not fit in a single page. + * + * Each block can be in memory and/or in a file. The block stays in memory + * as long as it is locked. If it is no longer locked it can be swapped out to + * the file. It is only written to the file if it has been changed. + * + * Under normal operation the file is created when opening the memory file and + * deleted when closing the memory file. Only with recovery an existing memory + * file is opened. + */ + +#if defined MSDOS || defined WIN32 +# include /* for lseek(), must be before vim.h */ +#endif + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" +#ifdef HAVE_FCNTL_H +# include +#endif + +/* + * Some systems have the page size in statfs, some in stat + */ +#ifdef HAVE_SYS_STATFS_H +# include +# define STATFS statfs +# define F_BSIZE f_bsize +# ifdef MINT +# define fstatfs(fd, buf, len, nul) fstat((fd), (buf)) +# endif +#else +# define STATFS stat +# define F_BSIZE st_blksize +# define fstatfs(fd, buf, len, nul) fstat((fd), (buf)) +#endif + +/* + * for Amiga Dos 2.0x we use Flush + */ +#ifdef AMIGA +# ifndef NO_ARP +extern int dos2; /* this is in amiga.c */ +# endif +# ifdef SASC +# include +# include /* for chkufb() */ +# endif +#endif + +#define MEMFILE_PAGE_SIZE 4096 /* default page size */ + +static long total_mem_used = 0; /* total memory used for memfiles */ + +static void mf_ins_hash __ARGS((MEMFILE *, BHDR *)); +static void mf_rem_hash __ARGS((MEMFILE *, BHDR *)); +static BHDR *mf_find_hash __ARGS((MEMFILE *, blocknr_t)); +static void mf_ins_used __ARGS((MEMFILE *, BHDR *)); +static void mf_rem_used __ARGS((MEMFILE *, BHDR *)); +static BHDR *mf_release __ARGS((MEMFILE *, int)); +static BHDR *mf_alloc_bhdr __ARGS((MEMFILE *, int)); +static void mf_free_bhdr __ARGS((BHDR *)); +static void mf_ins_free __ARGS((MEMFILE *, BHDR *)); +static BHDR *mf_rem_free __ARGS((MEMFILE *)); +static int mf_read __ARGS((MEMFILE *, BHDR *)); +static int mf_write __ARGS((MEMFILE *, BHDR *)); +static int mf_trans_add __ARGS((MEMFILE *, BHDR *)); +static void mf_do_open __ARGS((MEMFILE *, char_u *, int)); + +/* + * The functions for using a memfile: + * + * mf_open() open a new or existing memfile + * mf_open_file() open a swap file for an existing memfile + * mf_close() close (and delete) a memfile + * mf_new() create a new block in a memfile and lock it + * mf_get() get an existing block and lock it + * mf_put() unlock a block, may be marked for writing + * mf_free() remove a block + * mf_sync() sync changed parts of memfile to disk + * mf_release_all() release as much memory as possible + * mf_trans_del() may translate negative to positive block number + * mf_fullname() make file name full path (use before first :cd) + */ + +/* + * mf_open: open an existing or new memory block file + * + * fname: name of file to use (NULL means no file at all) + * Note: fname must have been allocated, it is not copied! + * If opening the file fails, fname is NOT freed. + * trunc_file: if TRUE: file should be truncated when opening + * + * If fname != NULL and file cannot be opened, fail. + * + * return value: identifier for this memory block file. + */ + MEMFILE * +mf_open(fname, trunc_file) + char_u *fname; + int trunc_file; +{ + MEMFILE *mfp; + int i; + long size; +#ifdef UNIX + struct STATFS stf; +#endif + + if ((mfp = (MEMFILE *)alloc((unsigned)sizeof(MEMFILE))) == NULL) + return NULL; + + if (fname == NULL) /* no file for this memfile, use memory only */ + { + mfp->mf_fname = NULL; + mfp->mf_xfname = NULL; + mfp->mf_fd = -1; + } + else + { + mf_do_open(mfp, fname, trunc_file); /* try to open the file */ + + /* if the file cannot be opened, return here */ + if (mfp->mf_fd < 0) + { + vim_free(mfp); + return NULL; + } + } + + mfp->mf_free_first = NULL; /* free list is empty */ + mfp->mf_used_first = NULL; /* used list is empty */ + mfp->mf_used_last = NULL; + mfp->mf_dirty = FALSE; + mfp->mf_used_count = 0; + for (i = 0; i < MEMHASHSIZE; ++i) + { + mfp->mf_hash[i] = NULL; /* hash lists are empty */ + mfp->mf_trans[i] = NULL; /* trans lists are empty */ + } + mfp->mf_page_size = MEMFILE_PAGE_SIZE; + +#ifdef UNIX + /* + * Try to set the page size equal to the block size of the device. + * Speeds up I/O a lot. + * NOTE: minimal block size depends on size of block 0 data! It's not done + * with a sizeof(), because block 0 is defined in memline.c (Sorry). + * The maximal block size is arbitrary. + */ + if (mfp->mf_fd >= 0 && + fstatfs(mfp->mf_fd, &stf, sizeof(struct statfs), 0) == 0 && + stf.F_BSIZE >= 1048 && stf.F_BSIZE <= 50000) + mfp->mf_page_size = stf.F_BSIZE; +#endif + + if (mfp->mf_fd < 0 || trunc_file || + (size = lseek(mfp->mf_fd, 0L, SEEK_END)) <= 0) + mfp->mf_blocknr_max = 0; /* no file or empty file */ + else + mfp->mf_blocknr_max = size / mfp->mf_page_size; + mfp->mf_blocknr_min = -1; + mfp->mf_neg_count = 0; + mfp->mf_infile_count = mfp->mf_blocknr_max; + if (mfp->mf_fd < 0) + mfp->mf_used_count_max = 0; /* no limit */ + else + mfp->mf_used_count_max = p_mm * 1024 / mfp->mf_page_size; + + return mfp; +} + +/* + * mf_open_file: open a file for an existing memfile. Used when updatecount + * set from 0 to some value. + * + * fname: name of file to use (NULL means no file at all) + * Note: fname must have been allocated, it is not copied! + * If opening the file fails, fname is NOT freed. + * + * return value: FAIL if file could not be opened, OK otherwise + */ + int +mf_open_file(mfp, fname) + MEMFILE *mfp; + char_u *fname; +{ + mf_do_open(mfp, fname, TRUE); /* try to open the file */ + + if (mfp->mf_fd < 0) + return FAIL; + + mfp->mf_dirty = TRUE; + return OK; +} + +/* + * close a memory file and delete the associated file if 'del_file' is TRUE + */ + void +mf_close(mfp, del_file) + MEMFILE *mfp; + int del_file; +{ + BHDR *hp, *nextp; + NR_TRANS *tp, *tpnext; + int i; + + if (mfp == NULL) /* safety check */ + return; + if (mfp->mf_fd >= 0) + { + if (close(mfp->mf_fd) < 0) + EMSG("Close error on swap file"); + } + if (del_file && mfp->mf_fname != NULL) + vim_remove(mfp->mf_fname); + /* free entries in used list */ + for (hp = mfp->mf_used_first; hp != NULL; hp = nextp) + { + total_mem_used -= hp->bh_page_count * mfp->mf_page_size; + nextp = hp->bh_next; + mf_free_bhdr(hp); + } + while (mfp->mf_free_first != NULL) /* free entries in free list */ + vim_free(mf_rem_free(mfp)); + for (i = 0; i < MEMHASHSIZE; ++i) /* free entries in trans lists */ + for (tp = mfp->mf_trans[i]; tp != NULL; tp = tpnext) + { + tpnext = tp->nt_next; + vim_free(tp); + } + vim_free(mfp->mf_fname); + vim_free(mfp->mf_xfname); + vim_free(mfp); +} + +/* + * get a new block + * + * negative: TRUE if negative block number desired (data block) + */ + BHDR * +mf_new(mfp, negative, page_count) + MEMFILE *mfp; + int negative; + int page_count; +{ + BHDR *hp; /* new BHDR */ + BHDR *freep; /* first block in free list */ + char_u *p; + + /* + * If we reached the maximum size for the used memory blocks, release one + * If a BHDR is returned, use it and adjust the page_count if necessary. + */ + hp = mf_release(mfp, page_count); + +/* + * Decide on the number to use: + * If there is a free block, use its number. + * Otherwise use mf_block_min for a negative number, mf_block_max for + * a positive number. + */ + freep = mfp->mf_free_first; + if (!negative && freep != NULL && freep->bh_page_count >= page_count) + { + /* + * If the block in the free list has more pages, take only the number + * of pages needed and allocate a new BHDR with data + * + * If the number of pages matches and mf_release did not return a BHDR, + * use the BHDR from the free list and allocate the data + * + * If the number of pages matches and mf_release returned a BHDR, + * just use the number and free the BHDR from the free list + */ + if (freep->bh_page_count > page_count) + { + if (hp == NULL && (hp = mf_alloc_bhdr(mfp, page_count)) == NULL) + return NULL; + hp->bh_bnum = freep->bh_bnum; + freep->bh_bnum += page_count; + freep->bh_page_count -= page_count; + } + else if (hp == NULL) /* need to allocate memory for this block */ + { + if ((p = (char_u *)alloc(mfp->mf_page_size * page_count)) == NULL) + return NULL; + hp = mf_rem_free(mfp); + hp->bh_data = p; + } + else /* use the number, remove entry from free list */ + { + freep = mf_rem_free(mfp); + hp->bh_bnum = freep->bh_bnum; + vim_free(freep); + } + } + else /* get a new number */ + { + if (hp == NULL && (hp = mf_alloc_bhdr(mfp, page_count)) == NULL) + return NULL; + if (negative) + { + hp->bh_bnum = mfp->mf_blocknr_min--; + mfp->mf_neg_count++; + } + else + { + hp->bh_bnum = mfp->mf_blocknr_max; + mfp->mf_blocknr_max += page_count; + } + } + hp->bh_flags = BH_LOCKED | BH_DIRTY; /* new block is always dirty */ + mfp->mf_dirty = TRUE; + hp->bh_page_count = page_count; + mf_ins_used(mfp, hp); + mf_ins_hash(mfp, hp); + + return hp; +} + +/* + * get existing block 'nr' with 'page_count' pages + * + * Note: The caller should first check a negative nr with mf_trans_del() + */ + BHDR * +mf_get(mfp, nr, page_count) + MEMFILE *mfp; + blocknr_t nr; + int page_count; +{ + BHDR *hp; + /* doesn't exist */ + if (nr >= mfp->mf_blocknr_max || nr <= mfp->mf_blocknr_min) + return NULL; + + /* + * see if it is in the cache + */ + hp = mf_find_hash(mfp, nr); + if (hp == NULL) /* not in the hash list */ + { + if (nr < 0 || nr >= mfp->mf_infile_count) /* can't be in the file */ + return NULL; + + /* could check here if the block is in the free list */ + + /* + * Check if we need to flush an existing block. + * If so, use that block. + * If not, allocate a new block. + */ + hp = mf_release(mfp, page_count); + if (hp == NULL && (hp = mf_alloc_bhdr(mfp, page_count)) == NULL) + return NULL; + + hp->bh_bnum = nr; + hp->bh_flags = 0; + hp->bh_page_count = page_count; + if (mf_read(mfp, hp) == FAIL) /* cannot read the block! */ + { + mf_free_bhdr(hp); + return NULL; + } + } + else + { + mf_rem_used(mfp, hp); /* remove from list, insert in front below */ + mf_rem_hash(mfp, hp); + } + + hp->bh_flags |= BH_LOCKED; + mf_ins_used(mfp, hp); /* put in front of used list */ + mf_ins_hash(mfp, hp); /* put in front of hash list */ + + return hp; +} + +/* + * release the block *hp + * + * dirty: Block must be written to file later + * infile: Block should be in file (needed for recovery) + * + * no return value, function cannot fail + */ + void +mf_put(mfp, hp, dirty, infile) + MEMFILE *mfp; + BHDR *hp; + int dirty; + int infile; +{ + int flags; + + flags = hp->bh_flags; + CHECK((flags & BH_LOCKED) == 0, "block was not locked"); + flags &= ~BH_LOCKED; + if (dirty) + { + flags |= BH_DIRTY; + mfp->mf_dirty = TRUE; + } + hp->bh_flags = flags; + if (infile) + mf_trans_add(mfp, hp); /* may translate negative in positive nr */ +} + +/* + * block *hp is no longer in used, may put it in the free list of memfile *mfp + */ + void +mf_free(mfp, hp) + MEMFILE *mfp; + BHDR *hp; +{ + vim_free(hp->bh_data); /* free the memory */ + mf_rem_hash(mfp, hp); /* get *hp out of the hash list */ + mf_rem_used(mfp, hp); /* get *hp out of the used list */ + if (hp->bh_bnum < 0) + { + vim_free(hp); /* don't want negative numbers in free list */ + mfp->mf_neg_count--; + } + else + mf_ins_free(mfp, hp); /* put *hp in the free list */ +} + +/* + * sync the memory file *mfp to disk + * if 'all' is FALSE blocks with negative numbers are not synced, even when + * they are dirty! + * if 'check_char' is TRUE, stop syncing when a character becomes available, + * but sync at least one block. + * if 'do_fsync' is TRUE make sure buffers are flushed to disk, so they will + * survive a system crash. + * + * Return FAIL for failure, OK otherwise + */ + int +mf_sync(mfp, all, check_char, do_fsync) + MEMFILE *mfp; + int all; + int check_char; + int do_fsync; +{ + int status; + BHDR *hp; +#ifdef SYNC_DUP_CLOSE + int fd; +#endif + + if (mfp->mf_fd < 0) /* there is no file, nothing to do */ + { + mfp->mf_dirty = FALSE; + return FAIL; + } + + /* + * sync from last to first (may reduce the probability of an inconsistent + * file) If a write fails, it is very likely caused by a full filesystem. + * Then we only try to write blocks within the existing file. If that also + * fails then we give up. + */ + status = OK; + for (hp = mfp->mf_used_last; hp != NULL; hp = hp->bh_prev) + if ((all || hp->bh_bnum >= 0) && (hp->bh_flags & BH_DIRTY) && + (status == OK || (hp->bh_bnum >= 0 && + hp->bh_bnum < mfp->mf_infile_count))) + { + if (mf_write(mfp, hp) == FAIL) + { + if (status == FAIL) /* double error: quit syncing */ + break; + status = FAIL; + } + if (check_char && mch_char_avail()) /* char available now */ + break; + } + + /* + * If the whole list is flushed, the memfile is not dirty anymore. + * In case of an error this flag is also set, to avoid trying all the time. + */ + if (hp == NULL || status == FAIL) + mfp->mf_dirty = FALSE; + + if (do_fsync && *p_sws != NUL) + { +#if defined(UNIX) +# ifdef HAVE_FSYNC + /* + * most Unixes have the very useful fsync() function, just what we need. + * However, with OS/2 and EMX it is also available, but there are + * reports of bad problems with it (a bug in HPFS.IFS). + * So we disable use of it here in case someone tries to be smart + * and changes conf_os2.h... (even though there is no __EMX__ test + * in the #if, as __EMX__ does not have sync(); we hope for a timely + * sync from the system itself). + */ +# if defined(__EMX__) + error "Dont use fsync with EMX! Read emxdoc.doc or emxfix01.doc for info." +# endif + if (STRCMP(p_sws, "fsync") == 0) + { + if (fsync(mfp->mf_fd)) + status = FAIL; + } + else +# endif + sync(); +#endif +#ifdef DJGPP + if (_dos_commit(mfp->mf_fd)) + status = FAIL; +#else +# ifdef SYNC_DUP_CLOSE + /* + * MSdos is a bit more work: Duplicate the file handle and close it. + * This should flush the file to disk. + */ + if ((fd = dup(mfp->mf_fd)) >= 0) + close(fd); +# endif +#endif +#ifdef AMIGA + /* + * Flush() only exists for AmigaDos 2.0. + * For 1.3 it should be done with close() + open(), but then the risk + * is that the open() may fail and lose the file.... + */ +# ifndef NO_ARP + if (dos2) +# endif +# ifdef SASC + { + struct UFB *fp = chkufb(mfp->mf_fd); + + if (fp != NULL) + Flush(fp->ufbfh); + } +# else +# ifdef _DCC + { + BPTR fh = (BPTR)fdtofh(mfp->mf_fd); + + if (fh != 0) + Flush(fh); + } +# else /* assume Manx */ + Flush(_devtab[mfp->mf_fd].fd); +# endif +# endif +#endif /* AMIGA */ + } + + return status; +} + +/* + * insert block *hp in front of hashlist of memfile *mfp + */ + static void +mf_ins_hash(mfp, hp) + MEMFILE *mfp; + BHDR *hp; +{ + BHDR *hhp; + int hash; + + hash = MEMHASH(hp->bh_bnum); + hhp = mfp->mf_hash[hash]; + hp->bh_hash_next = hhp; + hp->bh_hash_prev = NULL; + if (hhp != NULL) + hhp->bh_hash_prev = hp; + mfp->mf_hash[hash] = hp; +} + +/* + * remove block *hp from hashlist of memfile list *mfp + */ + static void +mf_rem_hash(mfp, hp) + MEMFILE *mfp; + BHDR *hp; +{ + if (hp->bh_hash_prev == NULL) + mfp->mf_hash[MEMHASH(hp->bh_bnum)] = hp->bh_hash_next; + else + hp->bh_hash_prev->bh_hash_next = hp->bh_hash_next; + + if (hp->bh_hash_next) + hp->bh_hash_next->bh_hash_prev = hp->bh_hash_prev; +} + +/* + * look in hash lists of memfile *mfp for block header with number 'nr' + */ + static BHDR * +mf_find_hash(mfp, nr) + MEMFILE *mfp; + blocknr_t nr; +{ + BHDR *hp; + + for (hp = mfp->mf_hash[MEMHASH(nr)]; hp != NULL; hp = hp->bh_hash_next) + if (hp->bh_bnum == nr) + break; + return hp; +} + +/* + * insert block *hp in front of used list of memfile *mfp + */ + static void +mf_ins_used(mfp, hp) + MEMFILE *mfp; + BHDR *hp; +{ + hp->bh_next = mfp->mf_used_first; + mfp->mf_used_first = hp; + hp->bh_prev = NULL; + if (hp->bh_next == NULL) /* list was empty, adjust last pointer */ + mfp->mf_used_last = hp; + else + hp->bh_next->bh_prev = hp; + mfp->mf_used_count += hp->bh_page_count; + total_mem_used += hp->bh_page_count * mfp->mf_page_size; +} + +/* + * remove block *hp from used list of memfile *mfp + */ + static void +mf_rem_used(mfp, hp) + MEMFILE *mfp; + BHDR *hp; +{ + if (hp->bh_next == NULL) /* last block in used list */ + mfp->mf_used_last = hp->bh_prev; + else + hp->bh_next->bh_prev = hp->bh_prev; + if (hp->bh_prev == NULL) /* first block in used list */ + mfp->mf_used_first = hp->bh_next; + else + hp->bh_prev->bh_next = hp->bh_next; + mfp->mf_used_count -= hp->bh_page_count; + total_mem_used -= hp->bh_page_count * mfp->mf_page_size; +} + +/* + * Release the least recently used block from the used list if the number + * of used memory blocks gets to big. + * + * Return the block header to the caller, including the memory block, so + * it can be re-used. Make sure the page_count is right. + */ + static BHDR * +mf_release(mfp, page_count) + MEMFILE *mfp; + int page_count; +{ + BHDR *hp; + + /* + * don't release a block if + * there is no file for this memfile + * or + * there is no limit to the number of blocks for this memfile or + * the maximum is not reached yet + * and + * total memory used is not up to 'maxmemtot' + */ + if (mfp->mf_fd < 0 || ((mfp->mf_used_count < mfp->mf_used_count_max || + mfp->mf_used_count_max == 0) && + (total_mem_used >> 10) < p_mmt)) + return NULL; + + for (hp = mfp->mf_used_last; hp != NULL; hp = hp->bh_prev) + if (!(hp->bh_flags & BH_LOCKED)) + break; + if (hp == NULL) /* not a single one that can be released */ + return NULL; + + /* + * If the block is dirty, write it. + * If the write fails we don't free it. + */ + if ((hp->bh_flags & BH_DIRTY) && mf_write(mfp, hp) == FAIL) + return NULL; + + mf_rem_used(mfp, hp); + mf_rem_hash(mfp, hp); + +/* + * If a BHDR is returned, make sure that the page_count of bh_data is right + */ + if (hp->bh_page_count != page_count) + { + vim_free(hp->bh_data); + if ((hp->bh_data = alloc(mfp->mf_page_size * page_count)) == NULL) + { + vim_free(hp); + return NULL; + } + hp->bh_page_count = page_count; + } + return hp; +} + +/* + * release as many blocks as possible + * Used in case of out of memory + * + * return TRUE if any memory was released + */ + int +mf_release_all() +{ + BUF *buf; + MEMFILE *mfp; + BHDR *hp; + int retval = FALSE; + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + { + mfp = buf->b_ml.ml_mfp; + if (mfp != NULL && mfp->mf_fd >= 0) /* only if there is a memfile with a file */ + for (hp = mfp->mf_used_last; hp != NULL; ) + { + if (!(hp->bh_flags & BH_LOCKED) && + (!(hp->bh_flags & BH_DIRTY) || mf_write(mfp, hp) != FAIL)) + { + mf_rem_used(mfp, hp); + mf_rem_hash(mfp, hp); + mf_free_bhdr(hp); + hp = mfp->mf_used_last; /* re-start, list was changed */ + retval = TRUE; + } + else + hp = hp->bh_prev; + } + } + return retval; +} + +/* + * Allocate a block header and a block of memory for it + */ + static BHDR * +mf_alloc_bhdr(mfp, page_count) + MEMFILE *mfp; + int page_count; +{ + BHDR *hp; + + if ((hp = (BHDR *)alloc((unsigned)sizeof(BHDR))) != NULL) + { + if ((hp->bh_data = (char_u *)alloc(mfp->mf_page_size * page_count)) + == NULL) + { + vim_free(hp); /* not enough memory */ + return NULL; + } + hp->bh_page_count = page_count; + } + return hp; +} + +/* + * Free a block header and the block of memory for it + */ + static void +mf_free_bhdr(hp) + BHDR *hp; +{ + vim_free(hp->bh_data); + vim_free(hp); +} + +/* + * insert entry *hp in the free list + */ + static void +mf_ins_free(mfp, hp) + MEMFILE *mfp; + BHDR *hp; +{ + hp->bh_next = mfp->mf_free_first; + mfp->mf_free_first = hp; +} + +/* + * remove the first entry from the free list and return a pointer to it + * Note: caller must check that mfp->mf_free_first is not NULL! + */ + static BHDR * +mf_rem_free(mfp) + MEMFILE *mfp; +{ + BHDR *hp; + + hp = mfp->mf_free_first; + mfp->mf_free_first = hp->bh_next; + return hp; +} + +/* + * read a block from disk + * + * Return FAIL for failure, OK otherwise + */ + static int +mf_read(mfp, hp) + MEMFILE *mfp; + BHDR *hp; +{ + long_u offset; + unsigned page_size; + unsigned size; + + if (mfp->mf_fd < 0) /* there is no file, can't read */ + return FAIL; + + page_size = mfp->mf_page_size; + offset = page_size * hp->bh_bnum; + size = page_size * hp->bh_page_count; + if ((long_u)lseek(mfp->mf_fd, offset, SEEK_SET) != offset) + { + EMSG("Seek error in swap file read"); + return FAIL; + } + if ((unsigned)read(mfp->mf_fd, (char *)hp->bh_data, (size_t)size) != size) + { + EMSG("Read error in swap file"); + return FAIL; + } + return OK; +} + +/* + * write a block to disk + * + * Return FAIL for failure, OK otherwise + */ + static int +mf_write(mfp, hp) + MEMFILE *mfp; + BHDR *hp; +{ + long_u offset; /* offset in the file */ + blocknr_t nr; /* block nr which is being written */ + BHDR *hp2; + unsigned page_size; /* number of bytes in a page */ + unsigned page_count; /* number of pages written */ + unsigned size; /* number of bytes written */ + + if (mfp->mf_fd < 0) /* there is no file, can't write */ + return FAIL; + + if (hp->bh_bnum < 0) /* must assign file block number */ + if (mf_trans_add(mfp, hp) == FAIL) + return FAIL; + + page_size = mfp->mf_page_size; + + /* + * We don't want gaps in the file. Write the blocks in front of *hp + * to extend the file. + * If block 'mf_infile_count' is not in the hash list, it has been + * freed. Fill the space in the file with data from the current block. + */ + for (;;) + { + nr = hp->bh_bnum; + if (nr > mfp->mf_infile_count) /* beyond end of file */ + { + nr = mfp->mf_infile_count; + hp2 = mf_find_hash(mfp, nr); /* NULL catched below */ + } + else + hp2 = hp; + + offset = page_size * nr; + if ((long_u)lseek(mfp->mf_fd, offset, SEEK_SET) != offset) + { + EMSG("Seek error in swap file write"); + return FAIL; + } + if (hp2 == NULL) /* freed block, fill with dummy data */ + page_count = 1; + else + page_count = hp2->bh_page_count; + size = page_size * page_count; + if ((unsigned)write(mfp->mf_fd, + (char *)(hp2 == NULL ? hp : hp2)->bh_data, (size_t)size) != size) + { + /* + * Avoid repeating the error message, this mostly happens when the + * disk is full. We give the message again only after a succesful + * write or when hitting a key. We keep on trying, in case some + * space becomes available. + */ + if (!did_swapwrite_msg) + EMSG("Write error in swap file"); + did_swapwrite_msg = TRUE; + return FAIL; + } + did_swapwrite_msg = FALSE; + if (hp2 != NULL) /* written a non-dummy block */ + hp2->bh_flags &= ~BH_DIRTY; + /* appended to the file */ + if (nr + (blocknr_t)page_count > mfp->mf_infile_count) + mfp->mf_infile_count = nr + page_count; + if (nr == hp->bh_bnum) /* written the desired block */ + break; + } + return OK; +} + +/* + * Make block number for *hp positive and add it to the translation list + * + * Return FAIL for failure, OK otherwise + */ + static int +mf_trans_add(mfp, hp) + MEMFILE *mfp; + BHDR *hp; +{ + BHDR *freep; + blocknr_t new_bnum; + int hash; + NR_TRANS *np; + int page_count; + + if (hp->bh_bnum >= 0) /* it's already positive */ + return OK; + + if ((np = (NR_TRANS *)alloc((unsigned)sizeof(NR_TRANS))) == NULL) + return FAIL; + +/* + * get a new number for the block. + * If the first item in the free list has sufficient pages, use its number + * Otherwise use mf_blocknr_max. + */ + freep = mfp->mf_free_first; + page_count = hp->bh_page_count; + if (freep != NULL && freep->bh_page_count >= page_count) + { + new_bnum = freep->bh_bnum; + /* + * If the page count of the free block was larger, recude it. + * If the page count matches, remove the block from the free list + */ + if (freep->bh_page_count > page_count) + { + freep->bh_bnum += page_count; + freep->bh_page_count -= page_count; + } + else + { + freep = mf_rem_free(mfp); + vim_free(freep); + } + } + else + { + new_bnum = mfp->mf_blocknr_max; + mfp->mf_blocknr_max += page_count; + } + + np->nt_old_bnum = hp->bh_bnum; /* adjust number */ + np->nt_new_bnum = new_bnum; + + mf_rem_hash(mfp, hp); /* remove from old hash list */ + hp->bh_bnum = new_bnum; + mf_ins_hash(mfp, hp); /* insert in new hash list */ + + hash = MEMHASH(np->nt_old_bnum); /* insert in trans list */ + np->nt_next = mfp->mf_trans[hash]; + mfp->mf_trans[hash] = np; + if (np->nt_next != NULL) + np->nt_next->nt_prev = np; + np->nt_prev = NULL; + + return OK; +} + +/* + * Lookup a tranlation from the trans lists and delete the entry + * + * Return the positive new number when found, the old number when not found + */ + blocknr_t +mf_trans_del(mfp, old_nr) + MEMFILE *mfp; + blocknr_t old_nr; +{ + int hash; + NR_TRANS *np; + blocknr_t new_bnum; + + hash = MEMHASH(old_nr); + for (np = mfp->mf_trans[hash]; np != NULL; np = np->nt_next) + if (np->nt_old_bnum == old_nr) + break; + if (np == NULL) /* not found */ + return old_nr; + + mfp->mf_neg_count--; + new_bnum = np->nt_new_bnum; + if (np->nt_prev != NULL) /* remove entry from the trans list */ + np->nt_prev->nt_next = np->nt_next; + else + mfp->mf_trans[hash] = np->nt_next; + if (np->nt_next != NULL) + np->nt_next->nt_prev = np->nt_prev; + vim_free(np); + + return new_bnum; +} + +/* + * Set mfp->mf_xfname according to mfp->mf_fname and some other things. + * Don't get the full path name if did_cd is TRUE, then fname should + * already be a full path name. + */ + void +mf_set_xfname(mfp) + MEMFILE *mfp; +{ + mfp->mf_xfname = NULL; + if (!did_cd) + mfp->mf_xfname = FullName_save(mfp->mf_fname); +} + +/* + * Make the name of the file used for the memfile a full path. + * Used before doing a :cd + */ + void +mf_fullname(mfp) + MEMFILE *mfp; +{ + if (mfp != NULL && mfp->mf_fname != NULL && mfp->mf_xfname != NULL) + { + vim_free(mfp->mf_fname); + mfp->mf_fname = mfp->mf_xfname; + mfp->mf_xfname = NULL; + } +} + +/* + * return TRUE if there are any translations pending for 'mfp' + */ + int +mf_need_trans(mfp) + MEMFILE *mfp; +{ + return (mfp->mf_fname != NULL && mfp->mf_neg_count > 0); +} + +#if 1 /* included for beta release, TODO: remove later */ +/* + * print statistics for a memfile (for debugging) + */ + void +mf_statistics() +{ + MEMFILE *mfp; + BHDR *hp; + int used = 0; + int locked = 0; + int dirty = 0; + int nfree = 0; + int negative = 0; + + mfp = curbuf->b_ml.ml_mfp; + if (mfp == NULL) + MSG("No memfile"); + else + { + for (hp = mfp->mf_used_first; hp != NULL; hp = hp->bh_next) + { + ++used; + if (hp->bh_flags & BH_LOCKED) + ++locked; + if (hp->bh_flags & BH_DIRTY) + ++dirty; + if (hp->bh_bnum < 0) + ++negative; + } + for (hp = mfp->mf_free_first; hp != NULL; hp = hp->bh_next) + ++nfree; + sprintf((char *)IObuff, "%d used (%d locked, %d dirty, %d (%d) negative), %d free", + used, locked, dirty, negative, (int)mfp->mf_neg_count, nfree); + msg(IObuff); + sprintf((char *)IObuff, "Total mem used is %ld bytes", total_mem_used); + msg(IObuff); + } +} +#endif + +/* + * open a swap file for a memfile + */ + static void +mf_do_open(mfp, fname, trunc_file) + MEMFILE *mfp; + char_u *fname; + int trunc_file; +{ + mfp->mf_fname = fname; + /* + * Get the full path name before the open, because this is + * not possible after the open on the Amiga. + * fname cannot be NameBuff, because it must have been allocated. + */ + mf_set_xfname(mfp); + + /* + * try to open the file + */ + mfp->mf_fd = open((char *)fname, + (trunc_file ? (O_CREAT | O_RDWR | O_TRUNC) : (O_RDONLY)) | O_EXTRA + +#ifdef AMIGA /* Amiga has no mode argument */ + ); +#endif +#ifdef UNIX /* open in rw------- mode */ + , (mode_t)0600); +#endif +#if defined(MSDOS) || defined(WIN32) || defined(__EMX__) + , S_IREAD | S_IWRITE); /* open read/write */ +#endif +#ifdef VMS /* open in rw------- mode */ + , 0600); +#endif + + /* + * If the file cannot be opened, use memory only + */ + if (mfp->mf_fd < 0) + { + vim_free(mfp->mf_xfname); + mfp->mf_fname = NULL; + mfp->mf_xfname = NULL; + } +} diff --git a/usr.bin/vim/memline.c b/usr.bin/vim/memline.c new file mode 100644 index 00000000000..ef110ed2edf --- /dev/null +++ b/usr.bin/vim/memline.c @@ -0,0 +1,3383 @@ +/* $OpenBSD: memline.c,v 1.1.1.1 1996/09/07 21:40:25 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* for debugging */ +/* #define CHECK(c, s) if (c) EMSG(s) */ +#define CHECK(c, s) + +/* + * memline.c: Contains the functions for appending, deleting and changing the + * text lines. The memfile functions are used to store the information in blocks + * of memory, backed up by a file. The structure of the information is a tree. + * The root of the tree is a pointer block. The leaves of the tree are data + * blocks. In between may be several layers of pointer blocks, forming branches. + * + * Three types of blocks are used: + * - Block nr 0 contains information for recovery + * - Pointer blocks contain list of pointers to other blocks. + * - Data blocks contain the actual text. + * + * Block nr 0 contains the block0 structure (see below). + * + * Block nr 1 is the first pointer block. It is the root of the tree. + * Other pointer blocks are branches. + * + * If a line is too big to fit in a single page, the block + * containing that line is made big enough to hold the line. It may span + * several pages. Otherwise all blocks are one page. + * + * A data block that was filled when starting to edit a file and was not + * changed since then, can have a negative block number. This means that it + * has not yet been assigned a place in the file. When recovering, the lines + * in this data block can be read from the original file. When the block is + * changed (lines appended/deleted/changed) or when it is flushed it gets + * a positive number. Use mf_trans_del() to get the new number, before + * calling mf_get(). + */ + +/* + * The following functions are available to work with a memline: + * + * ml_open() open a new memline for a buffer + * ml_setname() change the file name for a memline + * ml_close() close the memline for a buffer + * ml_close_all() close all memlines + * ml_recover() read a memline for recovery + * ml_sync_all() flush changed blocks to file for all files + * ml_preserve() flush everything into the file for one file + * ml_get() get a pointer to a line + * ml_get_pos() get a pointer to a position in a line + * ml_get_cursor() get a pointer to the char under the cursor + * ml_get_buf() get a pointer to a line in a specific buffer + * ml_line_alloced() return TRUE if line was allocated + * ml_append() append a new line + * ml_replace() replace a line + * ml_delete() delete a line + * ml_setmarked() set mark for a line (for :global command) + * ml_firstmarked() get first line with a mark (for :global command) + * ml_clearmarked() clear all line marks (for :global command) + */ + +#if defined MSDOS || defined WIN32 +# include +#endif + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" +#ifdef HAVE_FCNTL_H +# include +#endif +#ifndef UNIX /* it's in unix.h for Unix */ +# include +#endif + +#ifdef SASC +# include /* for Open() and Close() */ +#endif + +typedef struct block0 ZERO_BL; /* contents of the first block */ +typedef struct pointer_block PTR_BL; /* contents of a pointer block */ +typedef struct data_block DATA_BL; /* contents of a data block */ +typedef struct pointer_entry PTR_EN; /* block/line-count pair */ + +#define DATA_ID (('d' << 8) + 'a') /* data block id */ +#define PTR_ID (('p' << 8) + 't') /* pointer block id */ +#define BLOCK0_ID0 'b' /* block 0 id 0 */ +#define BLOCK0_ID1 '0' /* block 0 id 1 */ + +/* + * pointer to a block, used in a pointer block + */ +struct pointer_entry +{ + blocknr_t pe_bnum; /* block number */ + linenr_t pe_line_count; /* number of lines in this branch */ + linenr_t pe_old_lnum; /* lnum for this block (for recovery) */ + int pe_page_count; /* number of pages in block pe_bnum */ +}; + +/* + * A pointer block contains a list of branches in the tree. + */ +struct pointer_block +{ + short_u pb_id; /* ID for pointer block: PTR_ID */ + short_u pb_count; /* number of pointer in this block */ + short_u pb_count_max; /* maximum value for pb_count */ + PTR_EN pb_pointer[1]; /* list of pointers to blocks (actually longer) + * followed by empty space until end of page */ +}; + +/* + * A data block is a leaf in the tree. + * + * The text of the lines is at the end of the block. The text of the first line + * in the block is put at the end, the text of the second line in front of it, + * etc. Thus the order of the lines is the opposite of the line number. + */ +struct data_block +{ + short_u db_id; /* ID for data block: DATA_ID */ + unsigned db_free; /* free space available */ + unsigned db_txt_start; /* byte where text starts */ + unsigned db_txt_end; /* byte just after data block */ + linenr_t db_line_count; /* number of lines in this block */ + unsigned db_index[1]; /* index for start of line (actually bigger) + * followed by empty space upto db_txt_start + * followed by the text in the lines until + * end of page */ +}; + +/* + * The low bits of db_index hold the actual index. The topmost bit is + * used for the global command to be able to mark a line. + * This method is not clean, but otherwise there would be at least one extra + * byte used for each line. + * The mark has to be in this place to keep it with the correct line when other + * lines are inserted or deleted. + */ +#define DB_MARKED ((unsigned)1 << ((sizeof(unsigned) * 8) - 1)) +#define DB_INDEX_MASK (~DB_MARKED) + +#define INDEX_SIZE (sizeof(unsigned)) /* size of one db_index entry */ +#define HEADER_SIZE (sizeof(DATA_BL) - INDEX_SIZE) /* size of data block header */ + +#define B0_FNAME_SIZE 900 +#define B0_UNAME_SIZE 40 +#define B0_HNAME_SIZE 40 +/* + * Restrict the numbers to 32 bits, otherwise most compilers will complain. + * This won't detect a 64 bit machine that only swaps a byte in the top 32 + * bits, but that is crazy anyway. + */ +#define B0_MAGIC_LONG 0x30313233 +#define B0_MAGIC_INT 0x20212223 +#define B0_MAGIC_SHORT 0x10111213 +#define B0_MAGIC_CHAR 0x55 + +/* + * Block zero holds all info about the swap file. + * + * NOTE: DEFINITION OF BLOCK 0 SHOULD NOT CHANGE, it makes all existing swap + * files unusable! + * + * If size of block0 changes anyway, adjust minimal block size + * in mf_open()!! + * + * This block is built up of single bytes, to make it portable accros + * different machines. b0_magic_* is used to check the byte order and size of + * variables, because the rest of the swap is not portable. + */ +struct block0 +{ + char_u b0_id[2]; /* id for block 0: BLOCK0_ID0 and BLOCK0_ID1 */ + char_u b0_version[10]; /* Vim version string */ + char_u b0_page_size[4];/* number of bytes per page */ + char_u b0_mtime[4]; /* last modification time of file */ + char_u b0_ino[4]; /* inode of b0_fname */ + char_u b0_pid[4]; /* process id of creator (or 0) */ + char_u b0_uname[B0_UNAME_SIZE]; /* name of user (uid if no name) */ + char_u b0_hname[B0_HNAME_SIZE]; /* host name (if it has a name) */ + char_u b0_fname[B0_FNAME_SIZE]; /* name of file being edited */ + long b0_magic_long; /* check for byte order of long */ + int b0_magic_int; /* check for byte order of int */ + short b0_magic_short; /* check for byte order of short */ + char_u b0_magic_char; /* check for last char */ +}; + +#define STACK_INCR 5 /* nr of entries added to ml_stack at a time */ + +/* + * The line number where the first mark may be is remembered. + * If it is 0 there are no marks at all. + * (always used for the current buffer only, no buffer change possible while + * executing a global command). + */ +static linenr_t lowest_marked = 0; + +/* + * arguments for ml_find_line() + */ +#define ML_DELETE 0x11 /* delete line */ +#define ML_INSERT 0x12 /* insert line */ +#define ML_FIND 0x13 /* just find the line */ +#define ML_FLUSH 0x02 /* flush locked block */ +#define ML_SIMPLE(x) (x & 0x10) /* DEL, INS or FIND */ + +static void ml_open_file __ARGS((BUF *)); +static void set_b0_fname __ARGS((ZERO_BL *, BUF *buf)); +static void swapfile_info __ARGS((char_u *)); +static int get_names __ARGS((char_u **, char_u *)); +static int ml_append_int __ARGS((BUF *, linenr_t, char_u *, colnr_t, int)); +static int ml_delete_int __ARGS((BUF *, linenr_t, int)); +static char_u *findswapname __ARGS((BUF *, char_u **, char_u *)); +static void ml_flush_line __ARGS((BUF *)); +static BHDR *ml_new_data __ARGS((MEMFILE *, int, int)); +static BHDR *ml_new_ptr __ARGS((MEMFILE *)); +static BHDR *ml_find_line __ARGS((BUF *, linenr_t, int)); +static int ml_add_stack __ARGS((BUF *)); +static char_u *makeswapname __ARGS((BUF *, char_u *)); +static void ml_lineadd __ARGS((BUF *, int)); +static int b0_magic_wrong __ARGS((ZERO_BL *)); +#ifdef CHECK_INODE +static int fnamecmp_ino __ARGS((char_u *, char_u *, long)); +#endif +static void long_to_char __ARGS((long, char_u *)); +static long char_to_long __ARGS((char_u *)); + +/* + * open a new memline for 'curbuf' + * + * return FAIL for failure, OK otherwise + */ + int +ml_open() +{ + MEMFILE *mfp; + char_u *fname; + BHDR *hp = NULL; + ZERO_BL *b0p; + PTR_BL *pp; + DATA_BL *dp; + char_u *dirp; + +/* + * init fields in memline struct + */ + curbuf->b_ml.ml_stack_size = 0; /* no stack yet */ + curbuf->b_ml.ml_stack = NULL; /* no stack yet */ + curbuf->b_ml.ml_stack_top = 0; /* nothing in the stack */ + curbuf->b_ml.ml_locked = NULL; /* no cached block */ + curbuf->b_ml.ml_line_lnum = 0; /* no cached line */ + +/* + * Make file name for swap file. + * If we are unable to find a file name, mf_fname will be NULL + * and the memfile will be in memory only (no recovery possible). + * When 'updatecount' is 0 and for help files on Unix there is never a swap + * file. + */ +#if defined(UNIX) || defined(__EMX__) +/* why only on Unix an exception for help files??? */ + if (p_uc == 0 || curbuf->b_help) +#else + if (p_uc == 0) +#endif + dirp = (char_u *)""; + else + dirp = p_dir; + +/* + * Open the memfile. + * + * If no swap file is wanted *dirp will be NUL, try with fname == NULL. + * Otherwise try all directories in 'directory' option. If that fails try + * with fname == NULL. + */ + for (;;) + { + if (*dirp == NUL) + fname = NULL; + else + { + fname = findswapname(curbuf, &dirp, NULL); + if (fname == NULL) + continue; + } + mfp = mf_open(fname, TRUE); + /* stop when memfile opened or no other fname to try */ + if (mfp != NULL || (fname == NULL && *dirp == NUL)) + break; + vim_free(fname); + } + + if (mfp == NULL) + goto error; + curbuf->b_ml.ml_mfp = mfp; + curbuf->b_ml.ml_flags = ML_EMPTY; + curbuf->b_ml.ml_line_count = 1; + + if (!(p_uc == 0 || curbuf->b_help) && mfp->mf_fname == NULL) + { + need_wait_return = TRUE; /* call wait_return later */ + ++no_wait_return; + (void)EMSG("Unable to open swap file, recovery impossible"); + --no_wait_return; + } +#if defined(MSDOS) || defined(WIN32) + /* + * set full pathname for swap file now, because a ":!cd dir" may + * change directory without us knowing it. + */ + mf_fullname(mfp); +#endif + +/* + * fill block0 struct and write page 0 + */ + if ((hp = mf_new(mfp, FALSE, 1)) == NULL) + goto error; + if (hp->bh_bnum != 0) + { + EMSG("didn't get block nr 0?"); + goto error; + } + b0p = (ZERO_BL *)(hp->bh_data); + + (void)vim_memset(b0p, 0, sizeof(ZERO_BL)); /* init all to zero */ + + b0p->b0_id[0] = BLOCK0_ID0; + b0p->b0_id[1] = BLOCK0_ID1; + b0p->b0_magic_long = (long)B0_MAGIC_LONG; + b0p->b0_magic_int = (int)B0_MAGIC_INT; + b0p->b0_magic_short = (short)B0_MAGIC_SHORT; + b0p->b0_magic_char = B0_MAGIC_CHAR; + + STRNCPY(b0p->b0_version, Version, 10); + set_b0_fname(b0p, curbuf); + long_to_char((long)mfp->mf_page_size, b0p->b0_page_size); + (void)mch_get_user_name(b0p->b0_uname, B0_UNAME_SIZE); + b0p->b0_uname[B0_UNAME_SIZE - 1] = NUL; + mch_get_host_name(b0p->b0_hname, B0_HNAME_SIZE); + b0p->b0_hname[B0_HNAME_SIZE - 1] = NUL; + long_to_char(mch_get_pid(), b0p->b0_pid); + + /* + * Always sync block number 0 to disk, so we can check the file name in + * the swap file in findswapname(). Don't do this for help files though. + */ + mf_put(mfp, hp, TRUE, FALSE); + if (!curbuf->b_help) + mf_sync(mfp, FALSE, FALSE, FALSE); + +/* + * fill in root pointer block and write page 1 + */ + if ((hp = ml_new_ptr(mfp)) == NULL) + goto error; + if (hp->bh_bnum != 1) + { + EMSG("didn't get block nr 1?"); + goto error; + } + pp = (PTR_BL *)(hp->bh_data); + pp->pb_count = 1; + pp->pb_pointer[0].pe_bnum = 2; + pp->pb_pointer[0].pe_page_count = 1; + pp->pb_pointer[0].pe_old_lnum = 1; + pp->pb_pointer[0].pe_line_count = 1; /* line count after insertion */ + mf_put(mfp, hp, TRUE, FALSE); + +/* + * allocate first data block and create an empty line 1. + */ + if ((hp = ml_new_data(mfp, FALSE, 1)) == NULL) + goto error; + if (hp->bh_bnum != 2) + { + EMSG("didn't get block nr 2?"); + goto error; + } + + dp = (DATA_BL *)(hp->bh_data); + dp->db_index[0] = --dp->db_txt_start; /* at end of block */ + dp->db_free -= 1 + INDEX_SIZE; + dp->db_line_count = 1; + *((char_u *)dp + dp->db_txt_start) = NUL; /* emtpy line */ + + return OK; + +error: + if (mfp != NULL) + { + if (hp) + mf_put(mfp, hp, FALSE, FALSE); + mf_close(mfp, TRUE); /* will also free(mfp->mf_fname) */ + } + else + vim_free(fname); + curbuf->b_ml.ml_mfp = NULL; + return FAIL; +} + +/* + * ml_setname() is called when the file name has been changed. + * It may rename the swap file. + */ + void +ml_setname() +{ + int success = FALSE; + MEMFILE *mfp; + char_u *fname; + char_u *dirp; + +/* + * When 'updatecount' is 0 there is never a swap file. + * For help files we will make a swap file now. + */ + if (p_uc == 0) + return; + + mfp = curbuf->b_ml.ml_mfp; + if (mfp->mf_fd < 0) /* there is no swap file yet */ + { + ml_open_file(curbuf); /* create a swap file */ + return; + } + +/* + * Try all directories in the 'directory' option. + */ + dirp = p_dir; + for (;;) + { + if (*dirp == NUL) /* tried all directories, fail */ + break; + fname = findswapname(curbuf, &dirp, mfp->mf_fname); + if (fname == NULL) /* no file name found for this dir */ + continue; + +#if defined(MSDOS) || defined(WIN32) + /* + * Set full pathname for swap file now, because a ":!cd dir" may + * change directory without us knowing it. + */ + if (!did_cd) + { + char_u *p; + + p = FullName_save(fname); + vim_free(fname); + fname = p; + if (fname == NULL) + continue; + } +#endif + /* if the file name is the same we don't have to do anything */ + if (fnamecmp(fname, mfp->mf_fname) == 0) + { + vim_free(fname); + success = TRUE; + break; + } + /* need to close the swap file before renaming */ + if (mfp->mf_fd >= 0) + { + close(mfp->mf_fd); + mfp->mf_fd = -1; + } + + /* try to rename the swap file */ + if (vim_rename(mfp->mf_fname, fname) == 0) + { + success = TRUE; + vim_free(mfp->mf_fname); + mfp->mf_fname = fname; + vim_free(mfp->mf_xfname); + mf_set_xfname(mfp); + break; + } + vim_free(fname); /* this fname didn't work, try another */ + } + + if (mfp->mf_fd == -1) /* need to (re)open the swap file */ + { + mfp->mf_fd = open((char *)mfp->mf_fname, O_RDWR | O_EXTRA); + if (mfp->mf_fd < 0) + { + /* could not (re)open the swap file, what can we do???? */ + EMSG("Oops, lost the swap file!!!"); + return; + } + } + if (!success) + EMSG("Could not rename swap file"); +} + +/* + * Open a file for the memfile for all buffers. + * Used when 'updatecount' changes from zero to non-zero. + */ + void +ml_open_files() +{ + BUF *buf; + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + ml_open_file(buf); +} + +/* + * Open a swap file for an existing memfile, if there is non swap file yet. + */ + static void +ml_open_file(buf) + BUF *buf; +{ + MEMFILE *mfp; + char_u *fname; + char_u *dirp; + + mfp = buf->b_ml.ml_mfp; + if (mfp == NULL || mfp->mf_fd >= 0) /* nothing to do */ + return; + + dirp = p_dir; +/* + * Try all directories in 'directory' option. + */ + for (;;) + { + if (*dirp == NUL) + break; + fname = findswapname(buf, &dirp, NULL); + if (fname == NULL) + continue; + if (mf_open_file(mfp, fname) == OK) + break; + vim_free(fname); + } + + if (mfp->mf_fname == NULL) /* Failed! */ + { + need_wait_return = TRUE; /* call wait_return later */ + ++no_wait_return; + (void)EMSG2("Unable to open swap file for \"%s\", recovery impossible", + buf->b_xfilename == NULL ? (char_u *)"No File" + : buf->b_xfilename); + --no_wait_return; + } +#if defined(MSDOS) || defined(WIN32) + /* + * set full pathname for swap file now, because a ":!cd dir" may + * change directory without us knowing it. + */ + else + mf_fullname(mfp); +#endif +} + +/* + * Close memline for buffer 'buf'. + * If 'del_file' is TRUE, delete the swap file + */ + void +ml_close(buf, del_file) + BUF *buf; + int del_file; +{ + if (buf->b_ml.ml_mfp == NULL) /* not open */ + return; + mf_close(buf->b_ml.ml_mfp, del_file); /* close the .swp file */ + if (buf->b_ml.ml_line_lnum != 0 && (buf->b_ml.ml_flags & ML_LINE_DIRTY)) + vim_free(buf->b_ml.ml_line_ptr); + vim_free(buf->b_ml.ml_stack); + buf->b_ml.ml_mfp = NULL; +} + +/* + * Close all existing memlines and memfiles. + * Used when exiting. + * When 'del_file' is TRUE, delete the memfiles. + */ + void +ml_close_all(del_file) + int del_file; +{ + BUF *buf; + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + ml_close(buf, del_file); +} + +/* + * Close all memfiles for not modified buffers. + * Only use just before exiting! + */ + void +ml_close_notmod() +{ + BUF *buf; + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + if (!buf->b_changed) + ml_close(buf, TRUE); /* close all not-modified buffers */ +} + +/* + * Update the timestamp in the .swp file. + * Used when the file has been written. + */ + void +ml_timestamp(buf) + BUF *buf; +{ + MEMFILE *mfp; + BHDR *hp; + ZERO_BL *b0p; + + mfp = buf->b_ml.ml_mfp; + + if (mfp == NULL || (hp = mf_get(mfp, (blocknr_t)0, 1)) == NULL) + return; + b0p = (ZERO_BL *)(hp->bh_data); + if (b0p->b0_id[0] != BLOCK0_ID0 || b0p->b0_id[1] != BLOCK0_ID1) + EMSG("ml_timestamp: Didn't get block 0??"); + else + set_b0_fname(b0p, buf); + mf_put(mfp, hp, TRUE, FALSE); +} + +/* + * Write file name and timestamp into block 0 of a swap file. + * Also set buf->b_mtime. + * Don't use NameBuff[]!!! + */ + static void +set_b0_fname(b0p, buf) + ZERO_BL *b0p; + BUF *buf; +{ + struct stat st; + size_t flen, ulen; + char_u uname[B0_UNAME_SIZE]; + + if (buf->b_filename == NULL) + b0p->b0_fname[0] = NUL; + else + { + /* + * For a file under the home directory of the current user, we try to + * replace the home directory path with "~user". This helps when + * editing the same file on different machines over a network. + * First replace home dir path with "~/" with home_replace(). + * Then insert the user name to get "~user/". + */ + home_replace(NULL, buf->b_filename, b0p->b0_fname, B0_FNAME_SIZE); + if (b0p->b0_fname[0] == '~') + { + flen = STRLEN(b0p->b0_fname); + /* If there is no user name or it is too long, don't use "~/" */ + if (mch_get_user_name(uname, B0_UNAME_SIZE) == FAIL || + (ulen = STRLEN(uname)) + flen > B0_FNAME_SIZE - 1) + STRNCPY(b0p->b0_fname, buf->b_filename, B0_FNAME_SIZE); + else + { + vim_memmove(b0p->b0_fname + ulen + 1, b0p->b0_fname + 1, flen); + vim_memmove(b0p->b0_fname + 1, uname, ulen); + } + } + if (stat((char *)buf->b_filename, &st) >= 0) + { + long_to_char((long)st.st_mtime, b0p->b0_mtime); +#ifdef CHECK_INODE + long_to_char((long)st.st_ino, b0p->b0_ino); +#endif + buf->b_mtime = st.st_mtime; + buf->b_mtime_read = st.st_mtime; + } + else + { + long_to_char(0L, b0p->b0_mtime); +#ifdef CHECK_INODE + long_to_char(0L, b0p->b0_ino); +#endif + buf->b_mtime = 0; + buf->b_mtime_read = 0; + } + } +} + +/* + * try to recover curbuf from the .swp file + */ + void +ml_recover() +{ + BUF *buf = NULL; + MEMFILE *mfp = NULL; + char_u *fname; + BHDR *hp = NULL; + ZERO_BL *b0p; + PTR_BL *pp; + DATA_BL *dp; + IPTR *ip; + blocknr_t bnum; + int page_count; + struct stat org_stat, swp_stat; + int len; + int directly; + linenr_t lnum; + char_u *p; + int i; + long error; + int cannot_open; + linenr_t line_count; + int has_error; + int idx; + int top; + int txt_start; + long size; + int called_from_main; + int serious_error = TRUE; + long mtime; + + called_from_main = (curbuf->b_ml.ml_mfp == NULL); +/* + * If the file name ends in ".sw?" we use it directly. + * Otherwise a search is done to find the swap file(s). + */ + fname = curbuf->b_xfilename; + if (fname == NULL) /* When there is no file name */ + fname = (char_u *)""; + len = STRLEN(fname); + if (len >= 4 && vim_strnicmp(fname + len - 4, + (char_u *)".sw", (size_t)3) == 0) + { + directly = TRUE; + fname = strsave(fname); /* make a copy for mf_open */ + } + else + { + directly = FALSE; + + /* count the number of matching swap files */ + len = recover_names(&fname, FALSE, 0); + if (len == 0) /* no swap files found */ + { + EMSG2("No swap file found for %s", fname); + fname = NULL; + goto theend; + } + if (len == 1) /* one swap file found, use it */ + i = 1; + else /* several swap files found, choose */ + { + /* list the names of the swap files */ + (void)recover_names(&fname, TRUE, 0); + msg_outchar('\n'); + MSG_OUTSTR("Enter number of swap file to use (0 to quit): "); + i = get_number(); + if (i < 1 || i > len) + { + fname = NULL; + goto theend; + } + } + /* get the swap file name that will be used */ + (void)recover_names(&fname, FALSE, i); + } + if (fname == NULL) + goto theend; /* out of memory */ + + /* When called from main() still need to initialize storage structure */ + if (called_from_main && ml_open() == FAIL) + getout(1); + +/* + * allocate a buffer structure (only the memline in it is really used) + */ + buf = (BUF *)alloc((unsigned)sizeof(BUF)); + if (buf == NULL) + goto theend; + +/* + * init fields in memline struct + */ + buf->b_ml.ml_stack_size = 0; /* no stack yet */ + buf->b_ml.ml_stack = NULL; /* no stack yet */ + buf->b_ml.ml_stack_top = 0; /* nothing in the stack */ + buf->b_ml.ml_line_lnum = 0; /* no cached line */ + buf->b_ml.ml_locked = NULL; /* no locked block */ + buf->b_ml.ml_flags = 0; + +/* + * open the memfile from the old swap file + */ + mfp = mf_open(fname, FALSE); + if (mfp == NULL || mfp->mf_fd < 0) + { + EMSG2("Cannot open %s", fname); + goto theend; + } + buf->b_ml.ml_mfp = mfp; +#if defined(MSDOS) || defined(WIN32) + /* + * set full pathname for swap file now, because a ":!cd dir" may + * change directory without us knowing it. + * Careful: May free fname! + */ + mf_fullname(mfp); +#endif + +/* + * try to read block 0 + */ + if ((hp = mf_get(mfp, (blocknr_t)0, 1)) == NULL) + { + msg_start(); + MSG_OUTSTR("Unable to read block 0 from "); + msg_outtrans(mfp->mf_fname); + MSG_OUTSTR("\nMaybe no changes were made or Vim did not update the swap file"); + msg_end(); + goto theend; + } + b0p = (ZERO_BL *)(hp->bh_data); + if (b0p->b0_id[0] != BLOCK0_ID0 || b0p->b0_id[1] != BLOCK0_ID1) + { + EMSG2("%s is not a swap file", mfp->mf_fname); + goto theend; + } + if (b0_magic_wrong(b0p)) + { + msg_start(); + MSG_OUTSTR("The file "); + msg_outtrans(mfp->mf_fname); +#if defined(MSDOS) || defined(WIN32) + if (STRNCMP(b0p->b0_hname, "PC ", 3) == 0) + MSG_OUTSTR(" cannot be used with this version of Vim.\n"); + else +#endif + MSG_OUTSTR(" cannot be used on this computer.\n"); + MSG_OUTSTR("The file was created on "); + /* avoid going past the end of currupted hostname */ + b0p->b0_fname[0] = NUL; + MSG_OUTSTR(b0p->b0_hname); + MSG_OUTSTR(",\nor the file has been damaged."); + msg_end(); + goto theend; + } + /* + * If we guessed the wrong page size, we have to recalculate the + * highest block number in the file. + */ + if (mfp->mf_page_size != (unsigned)char_to_long(b0p->b0_page_size)) + { + mfp->mf_page_size = (unsigned)char_to_long(b0p->b0_page_size); + if ((size = lseek(mfp->mf_fd, 0L, SEEK_END)) <= 0) + mfp->mf_blocknr_max = 0; /* no file or empty file */ + else + mfp->mf_blocknr_max = size / mfp->mf_page_size; + mfp->mf_infile_count = mfp->mf_blocknr_max; + } + +/* + * If .swp file name given directly, use name from swap file for buffer. + */ + if (directly) + { + expand_env(b0p->b0_fname, NameBuff, MAXPATHL); + if (setfname(NameBuff, NULL, TRUE) == FAIL) + goto theend; + } + + home_replace(NULL, mfp->mf_fname, NameBuff, MAXPATHL); + smsg((char_u *)"Using swap file \"%s\"", NameBuff); + + if (curbuf->b_filename == NULL) + STRCPY(NameBuff, "No File"); + else + home_replace(NULL, curbuf->b_filename, NameBuff, MAXPATHL); + smsg((char_u *)"Original file \"%s\"", NameBuff); + msg_outchar((char_u)'\n'); + +/* + * check date of swap file and original file + */ + mtime = char_to_long(b0p->b0_mtime); + if (curbuf->b_filename != NULL && + stat((char *)curbuf->b_filename, &org_stat) != -1 && + ((stat((char *)mfp->mf_fname, &swp_stat) != -1 && + org_stat.st_mtime > swp_stat.st_mtime) || + org_stat.st_mtime != mtime)) + { + EMSG("Warning: Original file may have been changed"); + } + flushbuf(); + mf_put(mfp, hp, FALSE, FALSE); /* release block 0 */ + hp = NULL; + + /* + * Now that we are sure that the file is going to be recovered, clear the + * contents of the current buffer. + */ + while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) + ml_delete((linenr_t)1, FALSE); + + bnum = 1; /* start with block 1 */ + page_count = 1; /* which is 1 page */ + lnum = 0; /* append after line 0 in curbuf */ + line_count = 0; + idx = 0; /* start with first index in block 1 */ + error = 0; + buf->b_ml.ml_stack_top = 0; + buf->b_ml.ml_stack = NULL; + buf->b_ml.ml_stack_size = 0; /* no stack yet */ + + if (curbuf->b_filename == NULL) + cannot_open = TRUE; + else + cannot_open = FALSE; + + serious_error = FALSE; + for ( ; !got_int; line_breakcheck()) + { + if (hp != NULL) + mf_put(mfp, hp, FALSE, FALSE); /* release previous block */ + + /* + * get block + */ + if ((hp = mf_get(mfp, (blocknr_t)bnum, page_count)) == NULL) + { + if (bnum == 1) + { + EMSG2("Unable to read block 1 from %s", mfp->mf_fname); + goto theend; + } + ++error; + ml_append(lnum++, (char_u *)"???MANY LINES MISSING", (colnr_t)0, TRUE); + } + else /* there is a block */ + { + pp = (PTR_BL *)(hp->bh_data); + if (pp->pb_id == PTR_ID) /* it is a pointer block */ + { + /* check line count when using pointer block first time */ + if (idx == 0 && line_count != 0) + { + for (i = 0; i < (int)pp->pb_count; ++i) + line_count -= pp->pb_pointer[i].pe_line_count; + if (line_count != 0) + { + ++error; + ml_append(lnum++, (char_u *)"???LINE COUNT WRONG", (colnr_t)0, TRUE); + } + } + + if (pp->pb_count == 0) + { + ml_append(lnum++, (char_u *)"???EMPTY BLOCK", (colnr_t)0, TRUE); + ++error; + } + else if (idx < (int)pp->pb_count) /* go a block deeper */ + { + if (pp->pb_pointer[idx].pe_bnum < 0) + { + /* + * Data block with negative block number. + * Try to read lines from the original file. + * This is slow, but it works. + */ + if (!cannot_open) + { + line_count = pp->pb_pointer[idx].pe_line_count; + if (readfile(curbuf->b_filename, NULL, lnum, FALSE, + pp->pb_pointer[idx].pe_old_lnum - 1, + line_count, FALSE) == FAIL) + cannot_open = TRUE; + else + lnum += line_count; + } + if (cannot_open) + { + ++error; + ml_append(lnum++, (char_u *)"???LINES MISSING", (colnr_t)0, TRUE); + } + ++idx; /* get same block again for next index */ + continue; + } + + /* + * going one block deeper in the tree + */ + if ((top = ml_add_stack(buf)) < 0) /* new entry in stack */ + { + ++error; + break; /* out of memory */ + } + ip = &(buf->b_ml.ml_stack[top]); + ip->ip_bnum = bnum; + ip->ip_index = idx; + + bnum = pp->pb_pointer[idx].pe_bnum; + line_count = pp->pb_pointer[idx].pe_line_count; + page_count = pp->pb_pointer[idx].pe_page_count; + continue; + } + } + else /* not a pointer block */ + { + dp = (DATA_BL *)(hp->bh_data); + if (dp->db_id != DATA_ID) /* block id wrong */ + { + if (bnum == 1) + { + EMSG2("Block 1 ID wrong (%s not a .swp file?)", + mfp->mf_fname); + goto theend; + } + ++error; + ml_append(lnum++, (char_u *)"???BLOCK MISSING", (colnr_t)0, TRUE); + } + else + { + /* + * it is a data block + * Append all the lines in this block + */ + has_error = FALSE; + /* + * check length of block + * if wrong, use length in pointer block + */ + if (page_count * mfp->mf_page_size != dp->db_txt_end) + { + ml_append(lnum++, (char_u *)"??? from here until ???END lines may be messed up", (colnr_t)0, TRUE); + ++error; + has_error = TRUE; + dp->db_txt_end = page_count * mfp->mf_page_size; + } + + /* make sure there is a NUL at the end of the block */ + *((char_u *)dp + dp->db_txt_end - 1) = NUL; + + /* + * check number of lines in block + * if wrong, use count in data block + */ + if (line_count != dp->db_line_count) + { + ml_append(lnum++, (char_u *)"??? from here until ???END lines may have been inserted/deleted", (colnr_t)0, TRUE); + ++error; + has_error = TRUE; + } + + for (i = 0; i < dp->db_line_count; ++i) + { + txt_start = (dp->db_index[i] & DB_INDEX_MASK); + if (txt_start <= HEADER_SIZE || txt_start >= (int)dp->db_txt_end) + { + p = (char_u *)"???"; + ++error; + } + else + p = (char_u *)dp + txt_start; + ml_append(lnum++, p, (colnr_t)0, TRUE); + } + if (has_error) + ml_append(lnum++, (char_u *)"???END", (colnr_t)0, TRUE); + } + } + } + + if (buf->b_ml.ml_stack_top == 0) /* finished */ + break; + + /* + * go one block up in the tree + */ + ip = &(buf->b_ml.ml_stack[--(buf->b_ml.ml_stack_top)]); + bnum = ip->ip_bnum; + idx = ip->ip_index + 1; /* go to next index */ + page_count = 1; + } + + /* + * The dummy line from the empty buffer will now be after the last line in + * the buffer. Delete it. + */ + ml_delete(curbuf->b_ml.ml_line_count, FALSE); + + recoverymode = FALSE; + if (got_int) + EMSG("Recovery Interrupted"); + else if (error) + EMSG("Errors detected while recovering; look for lines starting with ???"); + else + { + MSG("Recovery completed. You should check if everything is OK."); + MSG_OUTSTR("\n(You might want to write out this file under another name\n"); + MSG_OUTSTR("and run diff with the original file to check for changes)\n"); + MSG_OUTSTR("Delete the .swp file afterwards.\n\n"); + cmdline_row = msg_row; + } + +theend: + if (mfp != NULL) + { + if (hp != NULL) + mf_put(mfp, hp, FALSE, FALSE); + mf_close(mfp, FALSE); /* will also vim_free(mfp->mf_fname) */ + } + else + vim_free(fname); + vim_free(buf); + if (serious_error && called_from_main) + ml_close(curbuf, TRUE); + return; +} + +/* + * Find the names of swap files in current directory and the directory given + * with the 'directory' option. + * + * Used to: + * - list the swap files for "vim -r" + * - count the number of swap files when recovering + * - list the swap files when recovering + * - find the name of the n'th swap file when recovering + */ + int +recover_names(fname, list, nr) + char_u **fname; /* base for swap file name */ + int list; /* when TRUE, list the swap file names */ + int nr; /* when non-zero, return nr'th swap file name */ +{ + int num_names; + char_u *(names[6]); + char_u *tail; + char_u *p; + int num_files; + int file_count = 0; + char_u **files; + int i; + char_u *dirp; + char_u *dir_name; + + if (list) + { + /* use msg() to start the scrolling properly */ + msg((char_u *)"Swap files found:"); + msg_outchar('\n'); + } + expand_interactively = TRUE; + /* + * Do the loop for every directory in 'directory'. + */ + dirp = p_dir; + while (*dirp) + { + /* find character after directory name */ + dir_name = dirp; + while (*dirp && *dirp != ',') + ++dirp; + dir_name = strnsave(dir_name, (int)(dirp - dir_name)); + if (dir_name == NULL) /* out of memory */ + break; + if (*dir_name == '.') /* check current dir */ + { + if (fname == NULL || *fname == NULL) + { + names[0] = strsave((char_u *)"*.sw?"); +#ifdef UNIX + /* for Unix names starting with a dot are special */ + names[1] = strsave((char_u *)".*.sw?"); + names[2] = strsave((char_u *)".sw?"); + num_names = 3; +#else + num_names = 1; +#endif + } + else + num_names = get_names(names, *fname); + } + else /* check directory dir_name */ + { + if (fname == NULL || *fname == NULL) + { + names[0] = concat_fnames(dir_name, (char_u *)"*.sw?", TRUE); +#ifdef UNIX + /* for Unix names starting with a dot are special */ + names[1] = concat_fnames(dir_name, (char_u *)".*.sw?", TRUE); + names[2] = concat_fnames(dir_name, (char_u *)".sw?", TRUE); + num_names = 3; +#else + num_names = 1; +#endif + } + else + { + tail = gettail(*fname); + tail = concat_fnames(dir_name, tail, TRUE); + if (tail == NULL) + num_names = 0; + else + { + num_names = get_names(names, tail); + vim_free(tail); + } + } + } + + /* check for out-of-memory */ + for (i = 0; i < num_names; ++i) + { + if (names[i] == NULL) + { + for (i = 0; i < num_names; ++i) + vim_free(names[i]); + num_names = 0; + } + } + if (num_names == 0) + num_files = 0; + else if (ExpandWildCards(num_names, names, + &num_files, &files, TRUE, FALSE) == FAIL) + { + MSG_OUTSTR(files); /* print error message */ + num_files = 0; + } + /* + * remove swapfile name of the current buffer, it must be ignored + */ + if (curbuf->b_ml.ml_mfp != NULL && + (p = curbuf->b_ml.ml_mfp->mf_fname) != NULL) + { + for (i = 0; i < num_files; ++i) + if (fullpathcmp(p, files[i]) == FPC_SAME) + { + vim_free(files[i]); + --num_files; + for ( ; i < num_files; ++i) + files[i] = files[i + 1]; + break; + } + } + if (nr) + { + file_count += num_files; + if (nr <= file_count) + { + *fname = strsave(files[nr - 1 + num_files - file_count]); + dirp = (char_u *)""; /* stop searching */ + } + } + else if (list) + { + if (*dir_name == '.') + { + if (fname == NULL || *fname == NULL) + MSG_OUTSTR(" In current directory:\n"); + else + MSG_OUTSTR(" Using specified name:\n"); + } + else + { + MSG_OUTSTR(" In directory "); + msg_home_replace(dir_name); + MSG_OUTSTR(":\n"); + } + + if (num_files) + { + for (i = 0; i < num_files; ++i) + { + /* print the swap file name */ + msg_outnum((long)++file_count); + MSG_OUTSTR(". "); + msg_outstr(gettail(files[i])); + msg_outchar('\n'); + swapfile_info(files[i]); + } + } + else + MSG_OUTSTR(" -- none --\n"); + flushbuf(); + } + else + file_count += num_files; + + for (i = 0; i < num_names; ++i) + vim_free(names[i]); + FreeWild(num_files, files); + + /* advance dirp to next directory name */ + vim_free(dir_name); + if (*dirp == ',') + ++dirp; + dirp = skipwhite(dirp); + } + expand_interactively = FALSE; + return file_count; +} + +/* + * Give information about an existing swap file + */ + static void +swapfile_info(fname) + char_u *fname; +{ + struct stat st; + int fd; + struct block0 b0; + time_t x; + + /* print the swap file date */ + if (stat((char *)fname, &st) != -1) + { + MSG_OUTSTR(" dated: "); + x = st.st_mtime; /* Manx C can't do &st.st_mtime */ + MSG_OUTSTR(ctime(&x)); + } + + /* + * print the original file name + */ + fd = open((char *)fname, O_RDONLY | O_EXTRA); + if (fd >= 0) + { + if (read(fd, (char *)&b0, sizeof(b0)) == sizeof(b0)) + { + if (b0.b0_id[0] != BLOCK0_ID0 || + b0.b0_id[1] != BLOCK0_ID1) + MSG_OUTSTR(" [is not a swap file]"); + else + { + MSG_OUTSTR(" file name: "); + msg_outstr(b0.b0_fname); + + if (*(b0.b0_hname) != NUL) + { + MSG_OUTSTR("\n host name: "); + msg_outstr(b0.b0_hname); + } + + if (*(b0.b0_uname) != NUL) + { + MSG_OUTSTR("\n user name: "); + msg_outstr(b0.b0_uname); + } + + if (char_to_long(b0.b0_pid) != 0L) + { + MSG_OUTSTR("\n process ID: "); + msg_outnum(char_to_long(b0.b0_pid)); +#if defined(UNIX) || defined(__EMX__) + /* EMX kill() not working correctly, it seems */ + if (kill(char_to_long(b0.b0_pid), 0) == 0) + MSG_OUTSTR(" (still running)"); +#endif + } + + if (b0_magic_wrong(&b0)) + { +#if defined(MSDOS) || defined(WIN32) + if (STRNCMP(b0.b0_hname, "PC ", 3) == 0) + MSG_OUTSTR("\n [not usable with this version of Vim]"); + else +#endif + MSG_OUTSTR("\n [not usable on this computer]"); + } + + } + } + else + MSG_OUTSTR(" [cannot be read]"); + close(fd); + } + else + MSG_OUTSTR(" [cannot be opened]"); + msg_outchar('\n'); +} + + static int +get_names(names, fname) + char_u **names; + char_u *fname; +{ + int num_names; + +#ifdef SHORT_FNAME + /* + * (MS-DOS) always short names + */ + names[0] = modname(fname, (char_u *)".sw?"); + num_names = 1; +#else /* !SHORT_FNAME */ +# ifdef WIN32 + /* + * (WIN32) never short names + */ + num_names = 1; + names[0] = concat_fnames(fname, (char_u *)".sw?", FALSE); +# else /* !WIN32 */ + /* + * (Not MS-DOS or WIN32) maybe short name, maybe not: try both. + * Only use the short name if it is different. + */ + + int i; + + names[0] = concat_fnames(fname, (char_u *)".sw?", FALSE); + i = curbuf->b_shortname; + curbuf->b_shortname = TRUE; + names[1] = modname(fname, (char_u *)".sw?"); + curbuf->b_shortname = i; + if (STRCMP(names[0], names[1]) == 0) + { + vim_free(names[1]); + num_names = 1; + } + else + num_names = 2; +# endif /* !WIN32 */ +#endif /* !SHORT_FNAME */ + return num_names; +} + +/* + * sync all memlines + * + * If 'check_file' is TRUE, check if original file exists and was not changed. + * If 'check_char' is TRUE, stop syncing when character becomes available, but + * always sync at least one block. + */ + void +ml_sync_all(check_file, check_char) + int check_file; + int check_char; +{ + BUF *buf; + struct stat st; + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + { + if (buf->b_ml.ml_mfp == NULL || buf->b_ml.ml_mfp->mf_fname == NULL) + continue; /* no file */ + + ml_flush_line(buf); /* flush buffered line */ + /* flush locked block */ + (void)ml_find_line(buf, (linenr_t)0, ML_FLUSH); + if (buf->b_changed && check_file && mf_need_trans(buf->b_ml.ml_mfp) && + buf->b_filename != NULL) + { + /* + * if original file does not exist anymore or has been changed + * call ml_preserve to get rid of all negative numbered blocks + */ + if (stat((char *)buf->b_filename, &st) == -1 || + st.st_mtime != buf->b_mtime_read) + { + ml_preserve(buf, FALSE); + need_check_timestamps = TRUE; /* give message later */ + } + } + if (buf->b_ml.ml_mfp->mf_dirty) + { + mf_sync(buf->b_ml.ml_mfp, FALSE, check_char, buf->b_changed); + if (check_char && mch_char_avail()) /* character available now */ + break; + } + } +} + +/* + * sync one buffer, including negative blocks + * + * after this all the blocks are in the swap file + * + * Used for the :preserve command and when the original file has been + * changed or deleted. + * + * when message is TRUE the success of preserving is reported + */ + void +ml_preserve(buf, message) + BUF *buf; + int message; +{ + BHDR *hp; + linenr_t lnum; + MEMFILE *mfp = buf->b_ml.ml_mfp; + int status; + + if (mfp == NULL || mfp->mf_fname == NULL) + { + if (message) + EMSG("Cannot preserve, there is no swap file"); + return; + } + + ml_flush_line(buf); /* flush buffered line */ + (void)ml_find_line(buf, (linenr_t)0, ML_FLUSH); /* flush locked block */ + status = mf_sync(mfp, TRUE, FALSE, TRUE); + + /* stack is invalid after mf_sync(.., TRUE, ..) */ + buf->b_ml.ml_stack_top = 0; + + /* + * Some of the data blocks may have been changed from negative to + * positive block number. In that case the pointer blocks need to be + * updated. + * + * We don't know in which pointer block the references are, so we visit + * all data blocks until there are no more translations to be done (or + * we hit the end of the file, which can only happen in case a write fails, + * e.g. when file system if full). + * ml_find_line() does the work by translating the negative block numbers + * when getting the first line of each data block. + */ + if (mf_need_trans(mfp)) + { + lnum = 1; + while (mf_need_trans(mfp) && lnum <= buf->b_ml.ml_line_count) + { + hp = ml_find_line(buf, lnum, ML_FIND); + if (hp == NULL) + { + status = FAIL; + goto theend; + } + CHECK(buf->b_ml.ml_locked_low != lnum, "low != lnum"); + lnum = buf->b_ml.ml_locked_high + 1; + } + (void)ml_find_line(buf, (linenr_t)0, ML_FLUSH); /* flush locked block */ + if (mf_sync(mfp, TRUE, FALSE, TRUE) == FAIL) /* sync the updated pointer blocks */ + status = FAIL; + buf->b_ml.ml_stack_top = 0; /* stack is invalid now */ + } +theend: + if (message) + { + if (status == OK) + MSG("File preserved"); + else + EMSG("Preserve failed"); + } +} + +/* + * get a pointer to a (read-only copy of a) line + * + * On failure an error message is given and IObuff is returned (to avoid + * having to check for error everywhere). + */ + char_u * +ml_get(lnum) + linenr_t lnum; +{ + return ml_get_buf(curbuf, lnum, FALSE); +} + +/* + * ml_get_pos: get pointer to position 'pos' + */ + char_u * +ml_get_pos(pos) + FPOS *pos; +{ + return (ml_get_buf(curbuf, pos->lnum, FALSE) + pos->col); +} + +/* + * ml_get_pos: get pointer to cursor line. + */ + char_u * +ml_get_curline() +{ + return ml_get_buf(curbuf, curwin->w_cursor.lnum, FALSE); +} + +/* + * ml_get_pos: get pointer to cursor position + */ + char_u * +ml_get_cursor() +{ + return (ml_get_buf(curbuf, curwin->w_cursor.lnum, FALSE) + + curwin->w_cursor.col); +} + +/* + * get a pointer to a line in a specific buffer + * + * will_change: if TRUE mark the buffer dirty (chars in the line will be + * changed) + */ + char_u * +ml_get_buf(buf, lnum, will_change) + BUF *buf; + linenr_t lnum; + int will_change; /* line will be changed */ +{ + BHDR *hp; + DATA_BL *dp; + char_u *ptr; + + if (lnum > buf->b_ml.ml_line_count) /* invalid line number */ + { + EMSGN("ml_get: invalid lnum: %ld", lnum); +errorret: + STRCPY(IObuff, "???"); + return IObuff; + } + if (lnum <= 0) /* pretend line 0 is line 1 */ + lnum = 1; + + if (buf->b_ml.ml_mfp == NULL) /* there are no lines */ + return (char_u *)""; + +/* + * See if it is the same line as requested last time. + * Otherwise may need to flush last used line. + */ + if (buf->b_ml.ml_line_lnum != lnum) + { + ml_flush_line(buf); + + /* + * find the data block containing the line + * This also fills the stack with the blocks from the root to the data block + * This also releases any locked block. + */ + if ((hp = ml_find_line(buf, lnum, ML_FIND)) == NULL) + { + EMSGN("ml_get: cannot find line %ld", lnum); + goto errorret; + } + + dp = (DATA_BL *)(hp->bh_data); + + ptr = (char_u *)dp + ((dp->db_index[lnum - buf->b_ml.ml_locked_low]) & DB_INDEX_MASK); + buf->b_ml.ml_line_ptr = ptr; + buf->b_ml.ml_line_lnum = lnum; + buf->b_ml.ml_flags &= ~ML_LINE_DIRTY; + } + if (will_change) + buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS); + + return buf->b_ml.ml_line_ptr; +} + +/* + * Check if a line that was just obtained by a call to ml_get + * is in allocated memory. + */ + int +ml_line_alloced() +{ + return (curbuf->b_ml.ml_flags & ML_LINE_DIRTY); +} + +/* + * append a line after lnum (may be 0 to insert a line in front of the file) + * + * newfile: TRUE when starting to edit a new file, meaning that pe_old_lnum + * will be set for recovery + * + * return FAIL for failure, OK otherwise + */ + int +ml_append(lnum, line, len, newfile) + linenr_t lnum; /* append after this line (can be 0) */ + char_u *line; /* text of the new line */ + colnr_t len; /* length of new line, including NUL, or 0 */ + int newfile; /* flag, see above */ +{ + if (curbuf->b_ml.ml_line_lnum != 0) + ml_flush_line(curbuf); + return ml_append_int(curbuf, lnum, line, len, newfile); +} + + static int +ml_append_int(buf, lnum, line, len, newfile) + BUF *buf; + linenr_t lnum; /* append after this line (can be 0) */ + char_u *line; /* text of the new line */ + colnr_t len; /* length of line, including NUL, or 0 */ + int newfile; /* flag, see above */ +{ + int i; + int line_count; /* number of indexes in current block */ + int offset; + int from, to; + int space_needed; /* space needed for new line */ + int page_size; + int page_count; + int db_idx; /* index for lnum in data block */ + BHDR *hp; + MEMFILE *mfp; + DATA_BL *dp; + PTR_BL *pp; + IPTR *ip; + + /* lnum out of range */ + if (lnum > buf->b_ml.ml_line_count || buf->b_ml.ml_mfp == NULL) + return FAIL; + + if (lowest_marked && lowest_marked > lnum) + lowest_marked = lnum + 1; + + if (len == 0) + len = STRLEN(line) + 1; /* space needed for the text */ + space_needed = len + INDEX_SIZE; /* space needed for text + index */ + + mfp = buf->b_ml.ml_mfp; + page_size = mfp->mf_page_size; + +/* + * find the data block containing the previous line + * This also fills the stack with the blocks from the root to the data block + * This also releases any locked block. + */ + if ((hp = ml_find_line(buf, lnum == 0 ? (linenr_t)1 : lnum, + ML_INSERT)) == NULL) + return FAIL; + + buf->b_ml.ml_flags &= ~ML_EMPTY; + + if (lnum == 0) /* got line one instead, correct db_idx */ + db_idx = -1; /* careful, it is negative! */ + else + db_idx = lnum - buf->b_ml.ml_locked_low; + /* get line count before the insertion */ + line_count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low; + + dp = (DATA_BL *)(hp->bh_data); + +/* + * If + * - there is not enough room in the current block + * - appending to the last line in the block + * - not appending to the last line in the file + * insert in front of the next block. + */ + if ((int)dp->db_free < space_needed && db_idx == line_count - 1 && + lnum < buf->b_ml.ml_line_count) + { + /* + * Now that the line is not going to be inserted in the block that we + * expected, the line count has to be adjusted in the pointer blocks + * by using ml_locked_lineadd. + */ + --(buf->b_ml.ml_locked_lineadd); + --(buf->b_ml.ml_locked_high); + if ((hp = ml_find_line(buf, lnum + 1, ML_INSERT)) == NULL) + return FAIL; + + db_idx = -1; /* careful, it is negative! */ + /* get line count before the insertion */ + line_count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low; + CHECK(buf->b_ml.ml_locked_low != lnum + 1, "locked_low != lnum + 1"); + + dp = (DATA_BL *)(hp->bh_data); + } + + ++buf->b_ml.ml_line_count; + + if ((int)dp->db_free >= space_needed) /* enough room in data block */ + { +/* + * Insert new line in existing data block, or in data block allocated above. + */ + dp->db_txt_start -= len; + dp->db_free -= space_needed; + ++(dp->db_line_count); + + /* + * move the text of the lines that follow to the front + * adjust the indexes of the lines that follow + */ + if (line_count > db_idx + 1) /* if there are following lines */ + { + /* + * Offset is the start of the previous line. + * This will become the character just after the new line. + */ + if (db_idx < 0) + offset = dp->db_txt_end; + else + offset = ((dp->db_index[db_idx]) & DB_INDEX_MASK); + vim_memmove((char *)dp + dp->db_txt_start, + (char *)dp + dp->db_txt_start + len, + (size_t)(offset - (dp->db_txt_start + len))); + for (i = line_count - 1; i > db_idx; --i) + dp->db_index[i + 1] = dp->db_index[i] - len; + dp->db_index[db_idx + 1] = offset - len; + } + else /* add line at the end */ + dp->db_index[db_idx + 1] = dp->db_txt_start; + + /* + * copy the text into the block + */ + vim_memmove((char *)dp + dp->db_index[db_idx + 1], line, (size_t)len); + + /* + * Mark the block dirty. + */ + buf->b_ml.ml_flags |= ML_LOCKED_DIRTY; + if (!newfile) + buf->b_ml.ml_flags |= ML_LOCKED_POS; + } + else /* not enough space in data block */ + { +/* + * If there is not enough room we have to create a new data block and copy some + * lines into it. + * Then we have to insert an entry in the pointer block. + * If this pointer block also is full, we go up another block, and so on, up + * to the root if necessary. + * The line counts in the pointer blocks have already been adjusted by + * ml_find_line(). + */ + long line_count_left, line_count_right; + int page_count_left, page_count_right; + BHDR *hp_left; + BHDR *hp_right; + BHDR *hp_new; + int lines_moved; + int data_moved = 0; /* init to shut up gcc */ + int total_moved = 0; /* init to shut up gcc */ + DATA_BL *dp_right, *dp_left; + int stack_idx; + int in_left; + int lineadd; + blocknr_t bnum_left, bnum_right; + linenr_t lnum_left, lnum_right; + int pb_idx; + PTR_BL *pp_new; + + /* + * We are going to allocate a new data block. Depending on the + * situation it will be put to the left or right of the existing + * block. If possible we put the new line in the left block and move + * the lines after it to the right block. Otherwise the new line is + * also put in the right block. This method is more efficient when + * inserting a lot of lines at one place. + */ + if (db_idx < 0) /* left block is new, right block is existing */ + { + lines_moved = 0; + in_left = TRUE; + /* space_needed does not change */ + } + else /* left block is existing, right block is new */ + { + lines_moved = line_count - db_idx - 1; + if (lines_moved == 0) + in_left = FALSE; /* put new line in right block */ + /* space_needed does not change */ + else + { + data_moved = ((dp->db_index[db_idx]) & DB_INDEX_MASK) - + dp->db_txt_start; + total_moved = data_moved + lines_moved * INDEX_SIZE; + if ((int)dp->db_free + total_moved >= space_needed) + { + in_left = TRUE; /* put new line in left block */ + space_needed = total_moved; + } + else + { + in_left = FALSE; /* put new line in right block */ + space_needed += total_moved; + } + } + } + + page_count = ((space_needed + HEADER_SIZE) + page_size - 1) / page_size; + if ((hp_new = ml_new_data(mfp, newfile, page_count)) == NULL) + { + /* correct line counts in pointer blocks */ + --(buf->b_ml.ml_locked_lineadd); + --(buf->b_ml.ml_locked_high); + return FAIL; + } + if (db_idx < 0) /* left block is new */ + { + hp_left = hp_new; + hp_right = hp; + line_count_left = 0; + line_count_right = line_count; + } + else /* right block is new */ + { + hp_left = hp; + hp_right = hp_new; + line_count_left = line_count; + line_count_right = 0; + } + dp_right = (DATA_BL *)(hp_right->bh_data); + dp_left = (DATA_BL *)(hp_left->bh_data); + bnum_left = hp_left->bh_bnum; + bnum_right = hp_right->bh_bnum; + page_count_left = hp_left->bh_page_count; + page_count_right = hp_right->bh_page_count; + + /* + * May move the new line into the right/new block. + */ + if (!in_left) + { + dp_right->db_txt_start -= len; + dp_right->db_free -= len + INDEX_SIZE; + dp_right->db_index[0] = dp_right->db_txt_start; + vim_memmove((char *)dp_right + dp_right->db_txt_start, + line, (size_t)len); + ++line_count_right; + } + /* + * may move lines from the left/old block to the right/new one. + */ + if (lines_moved) + { + /* + */ + dp_right->db_txt_start -= data_moved; + dp_right->db_free -= total_moved; + vim_memmove((char *)dp_right + dp_right->db_txt_start, + (char *)dp_left + dp_left->db_txt_start, + (size_t)data_moved); + offset = dp_right->db_txt_start - dp_left->db_txt_start; + dp_left->db_txt_start += data_moved; + dp_left->db_free += total_moved; + + /* + * update indexes in the new block + */ + for (to = line_count_right, from = db_idx + 1; + from < line_count_left; ++from, ++to) + dp_right->db_index[to] = dp->db_index[from] + offset; + line_count_right += lines_moved; + line_count_left -= lines_moved; + } + + /* + * May move the new line into the left (old or new) block. + */ + if (in_left) + { + dp_left->db_txt_start -= len; + dp_left->db_free -= len + INDEX_SIZE; + dp_left->db_index[line_count_left] = dp_left->db_txt_start; + vim_memmove((char *)dp_left + dp_left->db_txt_start, + line, (size_t)len); + ++line_count_left; + } + + if (db_idx < 0) /* left block is new */ + { + lnum_left = lnum + 1; + lnum_right = 0; + } + else /* right block is new */ + { + lnum_left = 0; + if (in_left) + lnum_right = lnum + 2; + else + lnum_right = lnum + 1; + } + dp_left->db_line_count = line_count_left; + dp_right->db_line_count = line_count_right; + + /* + * release the two data blocks + * The new one (hp_new) already has a correct blocknumber. + * The old one (hp, in ml_locked) gets a positive blocknumber if + * we changed it and we are not editing a new file. + */ + if (lines_moved || in_left) + buf->b_ml.ml_flags |= ML_LOCKED_DIRTY; + if (!newfile && db_idx >= 0 && in_left) + buf->b_ml.ml_flags |= ML_LOCKED_POS; + mf_put(mfp, hp_new, TRUE, FALSE); + + /* + * flush the old data block + * set ml_locked_lineadd to 0, because the updating of the + * pointer blocks is done below + */ + lineadd = buf->b_ml.ml_locked_lineadd; + buf->b_ml.ml_locked_lineadd = 0; + ml_find_line(buf, (linenr_t)0, ML_FLUSH); /* flush data block */ + + /* + * update pointer blocks for the new data block + */ + for (stack_idx = buf->b_ml.ml_stack_top - 1; stack_idx >= 0; --stack_idx) + { + ip = &(buf->b_ml.ml_stack[stack_idx]); + pb_idx = ip->ip_index; + if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) + return FAIL; + pp = (PTR_BL *)(hp->bh_data); /* must be pointer block */ + if (pp->pb_id != PTR_ID) + { + EMSG("pointer block id wrong 3"); + mf_put(mfp, hp, FALSE, FALSE); + return FAIL; + } + /* + * TODO: If the pointer block is full and we are adding at the end + * try to insert in front of the next block + */ + if (pp->pb_count < pp->pb_count_max) /* block not full, add one entry */ + { + if (pb_idx + 1 < (int)pp->pb_count) + vim_memmove(&pp->pb_pointer[pb_idx + 2], + &pp->pb_pointer[pb_idx + 1], + (size_t)(pp->pb_count - pb_idx - 1) * sizeof(PTR_EN)); + ++pp->pb_count; + pp->pb_pointer[pb_idx].pe_line_count = line_count_left; + pp->pb_pointer[pb_idx].pe_bnum = bnum_left; + pp->pb_pointer[pb_idx].pe_page_count = page_count_left; + pp->pb_pointer[pb_idx + 1].pe_line_count = line_count_right; + pp->pb_pointer[pb_idx + 1].pe_bnum = bnum_right; + pp->pb_pointer[pb_idx + 1].pe_page_count = page_count_right; + + if (lnum_left != 0) + pp->pb_pointer[pb_idx].pe_old_lnum = lnum_left; + if (lnum_right != 0) + pp->pb_pointer[pb_idx + 1].pe_old_lnum = lnum_right; + + mf_put(mfp, hp, TRUE, FALSE); + buf->b_ml.ml_stack_top = stack_idx + 1; /* truncate stack */ + + if (lineadd) + { + --(buf->b_ml.ml_stack_top); + /* fix line count for rest of blocks in the stack */ + ml_lineadd(buf, lineadd); + /* fix stack itself */ + buf->b_ml.ml_stack[buf->b_ml.ml_stack_top].ip_high += + lineadd; + ++(buf->b_ml.ml_stack_top); + } + + return OK; + } + else /* pointer block full */ + { + /* + * split the pointer block + * allocate a new pointer block + * move some of the pointer into the new block + * prepare for updating the parent block + */ + for (;;) /* do this twice when splitting block 1 */ + { + hp_new = ml_new_ptr(mfp); + if (hp_new == NULL) /* TODO: try to fix tree */ + return FAIL; + pp_new = (PTR_BL *)(hp_new->bh_data); + + if (hp->bh_bnum != 1) + break; + + /* + * if block 1 becomes full the tree is given an extra level + * The pointers from block 1 are moved into the new block. + * block 1 is updated to point to the new block + * then continue to split the new block + */ + vim_memmove(pp_new, pp, (size_t)page_size); + pp->pb_count = 1; + pp->pb_pointer[0].pe_bnum = hp_new->bh_bnum; + pp->pb_pointer[0].pe_line_count = buf->b_ml.ml_line_count; + pp->pb_pointer[0].pe_old_lnum = 1; + pp->pb_pointer[0].pe_page_count = 1; + mf_put(mfp, hp, TRUE, FALSE); /* release block 1 */ + hp = hp_new; /* new block is to be split */ + pp = pp_new; + CHECK(stack_idx != 0, "stack_idx should be 0"); + ip->ip_index = 0; + ++stack_idx; /* do block 1 again later */ + } + /* + * move the pointers after the current one to the new block + * If there are none, the new entry will be in the new block. + */ + total_moved = pp->pb_count - pb_idx - 1; + if (total_moved) + { + vim_memmove(&pp_new->pb_pointer[0], + &pp->pb_pointer[pb_idx + 1], + (size_t)(total_moved) * sizeof(PTR_EN)); + pp_new->pb_count = total_moved; + pp->pb_count -= total_moved - 1; + pp->pb_pointer[pb_idx + 1].pe_bnum = bnum_right; + pp->pb_pointer[pb_idx + 1].pe_line_count = line_count_right; + pp->pb_pointer[pb_idx + 1].pe_page_count = page_count_right; + if (lnum_right) + pp->pb_pointer[pb_idx + 1].pe_old_lnum = lnum_right; + } + else + { + pp_new->pb_count = 1; + pp_new->pb_pointer[0].pe_bnum = bnum_right; + pp_new->pb_pointer[0].pe_line_count = line_count_right; + pp_new->pb_pointer[0].pe_page_count = page_count_right; + pp_new->pb_pointer[0].pe_old_lnum = lnum_right; + } + pp->pb_pointer[pb_idx].pe_bnum = bnum_left; + pp->pb_pointer[pb_idx].pe_line_count = line_count_left; + pp->pb_pointer[pb_idx].pe_page_count = page_count_left; + if (lnum_left) + pp->pb_pointer[pb_idx].pe_old_lnum = lnum_left; + lnum_left = 0; + lnum_right = 0; + + /* + * recompute line counts + */ + line_count_right = 0; + for (i = 0; i < (int)pp_new->pb_count; ++i) + line_count_right += pp_new->pb_pointer[i].pe_line_count; + line_count_left = 0; + for (i = 0; i < (int)pp->pb_count; ++i) + line_count_left += pp->pb_pointer[i].pe_line_count; + + bnum_left = hp->bh_bnum; + bnum_right = hp_new->bh_bnum; + page_count_left = 1; + page_count_right = 1; + mf_put(mfp, hp, TRUE, FALSE); + mf_put(mfp, hp_new, TRUE, FALSE); + } + } + EMSG("Updated too many blocks?"); + buf->b_ml.ml_stack_top = 0; /* invalidate stack */ + } + return OK; +} + +/* + * replace line lnum, with buffering, in current buffer + * + * If copy is TRUE, make a copy of the line, otherwise the line has been + * copied to allocated memory already. + * + * return FAIL for failure, OK otherwise + */ + int +ml_replace(lnum, line, copy) + linenr_t lnum; + char_u *line; + int copy; +{ + if (line == NULL) /* just checking... */ + return FAIL; + + if (curbuf->b_ml.ml_line_lnum != lnum) /* other line buffered */ + ml_flush_line(curbuf); /* flush it */ + else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) /* same line allocated */ + vim_free(curbuf->b_ml.ml_line_ptr); /* free it */ + if (copy && (line = strsave(line)) == NULL) /* allocate memory */ + return FAIL; + curbuf->b_ml.ml_line_ptr = line; + curbuf->b_ml.ml_line_lnum = lnum; + curbuf->b_ml.ml_flags = (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY; + + return OK; +} + +/* + * delete line 'lnum' + * + * return FAIL for failure, OK otherwise + */ + int +ml_delete(lnum, message) + linenr_t lnum; + int message; +{ + ml_flush_line(curbuf); + return ml_delete_int(curbuf, lnum, message); +} + + static int +ml_delete_int(buf, lnum, message) + BUF *buf; + linenr_t lnum; + int message; +{ + BHDR *hp; + MEMFILE *mfp; + DATA_BL *dp; + PTR_BL *pp; + IPTR *ip; + int count; /* number of entries in block */ + int idx; + int stack_idx; + int text_start; + int line_start; + int line_size; + int i; + + if (lnum < 1 || lnum > buf->b_ml.ml_line_count) + return FAIL; + + if (lowest_marked && lowest_marked > lnum) + lowest_marked--; + +/* + * If the file becomes empty the last line is replaced by an empty line. + */ + if (buf->b_ml.ml_line_count == 1) /* file becomes empty */ + { + if (message) + keep_msg = no_lines_msg; + i = ml_replace((linenr_t)1, (char_u *)"", TRUE); + buf->b_ml.ml_flags |= ML_EMPTY; + return i; + } + +/* + * find the data block containing the line + * This also fills the stack with the blocks from the root to the data block + * This also releases any locked block. + */ + mfp = buf->b_ml.ml_mfp; + if (mfp == NULL) + return FAIL; + + if ((hp = ml_find_line(buf, lnum, ML_DELETE)) == NULL) + return FAIL; + + dp = (DATA_BL *)(hp->bh_data); + /* compute line count before the delete */ + count = (long)(buf->b_ml.ml_locked_high) - (long)(buf->b_ml.ml_locked_low) + 2; + idx = lnum - buf->b_ml.ml_locked_low; + + --buf->b_ml.ml_line_count; + +/* + * special case: If there is only one line in the data block it becomes empty. + * Then we have to remove the entry, pointing to this data block, from the + * pointer block. If this pointer block also becomes empty, we go up another + * block, and so on, up to the root if necessary. + * The line counts in the pointer blocks have already been adjusted by + * ml_find_line(). + */ + if (count == 1) + { + mf_free(mfp, hp); /* free the data block */ + buf->b_ml.ml_locked = NULL; + + for (stack_idx = buf->b_ml.ml_stack_top - 1; stack_idx >= 0; --stack_idx) + { + buf->b_ml.ml_stack_top = 0; /* stack is invalid when failing */ + ip = &(buf->b_ml.ml_stack[stack_idx]); + idx = ip->ip_index; + if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) + return FAIL; + pp = (PTR_BL *)(hp->bh_data); /* must be pointer block */ + if (pp->pb_id != PTR_ID) + { + EMSG("pointer block id wrong 4"); + mf_put(mfp, hp, FALSE, FALSE); + return FAIL; + } + count = --(pp->pb_count); + if (count == 0) /* the pointer block becomes empty! */ + mf_free(mfp, hp); + else + { + if (count != idx) /* move entries after the deleted one */ + vim_memmove(&pp->pb_pointer[idx], &pp->pb_pointer[idx + 1], + (size_t)(count - idx) * sizeof(PTR_EN)); + mf_put(mfp, hp, TRUE, FALSE); + + buf->b_ml.ml_stack_top = stack_idx; /* truncate stack */ + /* fix line count for rest of blocks in the stack */ + if (buf->b_ml.ml_locked_lineadd) + { + ml_lineadd(buf, buf->b_ml.ml_locked_lineadd); + buf->b_ml.ml_stack[buf->b_ml.ml_stack_top].ip_high += + buf->b_ml.ml_locked_lineadd; + } + ++(buf->b_ml.ml_stack_top); + + return OK; + } + } + CHECK(1, "deleted block 1?"); + + return OK; + } + + /* + * delete the text by moving the next lines forwards + */ + text_start = dp->db_txt_start; + line_start = ((dp->db_index[idx]) & DB_INDEX_MASK); + if (idx == 0) /* first line in block, text at the end */ + line_size = dp->db_txt_end - line_start; + else + line_size = ((dp->db_index[idx - 1]) & DB_INDEX_MASK) - line_start; + vim_memmove((char *)dp + text_start + line_size, (char *)dp + text_start, + (size_t)(line_start - text_start)); + + /* + * delete the index by moving the next indexes backwards + * Adjust the indexes for the text movement. + */ + for (i = idx; i < count - 1; ++i) + dp->db_index[i] = dp->db_index[i + 1] + line_size; + + dp->db_free += line_size + INDEX_SIZE; + dp->db_txt_start += line_size; + --(dp->db_line_count); + + /* + * mark the block dirty and make sure it is in the file (for recovery) + */ + buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS); + + return OK; +} + +/* + * set the B_MARKED flag for line 'lnum' + */ + void +ml_setmarked(lnum) + linenr_t lnum; +{ + BHDR *hp; + DATA_BL *dp; + /* invalid line number */ + if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count || + curbuf->b_ml.ml_mfp == NULL) + return; /* give error message? */ + + if (lowest_marked == 0 || lowest_marked > lnum) + lowest_marked = lnum; + + /* + * find the data block containing the line + * This also fills the stack with the blocks from the root to the data block + * This also releases any locked block. + */ + if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) + return; /* give error message? */ + + dp = (DATA_BL *)(hp->bh_data); + dp->db_index[lnum - curbuf->b_ml.ml_locked_low] |= DB_MARKED; + curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY; +} + +/* + * find the first line with its B_MARKED flag set + */ + linenr_t +ml_firstmarked() +{ + BHDR *hp; + DATA_BL *dp; + linenr_t lnum; + int i; + + if (curbuf->b_ml.ml_mfp == NULL) + return (linenr_t) 0; + + /* + * The search starts with lowest_marked line. This is the last line where + * a mark was found, adjusted by inserting/deleting lines. + */ + for (lnum = lowest_marked; lnum <= curbuf->b_ml.ml_line_count; ) + { + /* + * Find the data block containing the line. + * This also fills the stack with the blocks from the root to the data + * block This also releases any locked block. + */ + if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) + return (linenr_t)0; /* give error message? */ + + dp = (DATA_BL *)(hp->bh_data); + + for (i = lnum - curbuf->b_ml.ml_locked_low; + lnum <= curbuf->b_ml.ml_locked_high; ++i, ++lnum) + if ((dp->db_index[i]) & DB_MARKED) + { + (dp->db_index[i]) &= DB_INDEX_MASK; + curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY; + lowest_marked = lnum + 1; + return lnum; + } + } + + return (linenr_t) 0; +} + +/* + * return TRUE if line 'lnum' has a mark + */ + int +ml_has_mark(lnum) + linenr_t lnum; +{ + BHDR *hp; + DATA_BL *dp; + + if (curbuf->b_ml.ml_mfp == NULL || + (hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) + return FALSE; + + dp = (DATA_BL *)(hp->bh_data); + return (int)((dp->db_index[lnum - curbuf->b_ml.ml_locked_low]) & DB_MARKED); +} + +/* + * clear all DB_MARKED flags + */ + void +ml_clearmarked() +{ + BHDR *hp; + DATA_BL *dp; + linenr_t lnum; + int i; + + if (curbuf->b_ml.ml_mfp == NULL) /* nothing to do */ + return; + + /* + * The search starts with line lowest_marked. + */ + for (lnum = lowest_marked; lnum <= curbuf->b_ml.ml_line_count; ) + { + /* + * Find the data block containing the line. + * This also fills the stack with the blocks from the root to the data block + * This also releases any locked block. + */ + if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) + return; /* give error message? */ + + dp = (DATA_BL *)(hp->bh_data); + + for (i = lnum - curbuf->b_ml.ml_locked_low; + lnum <= curbuf->b_ml.ml_locked_high; ++i, ++lnum) + if ((dp->db_index[i]) & DB_MARKED) + { + (dp->db_index[i]) &= DB_INDEX_MASK; + curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY; + } + } + + lowest_marked = 0; + return; +} + +/* + * flush ml_line if necessary + */ + static void +ml_flush_line(buf) + BUF *buf; +{ + BHDR *hp; + DATA_BL *dp; + linenr_t lnum; + char_u *new_line; + char_u *old_line; + colnr_t new_len; + int old_len; + int extra; + int idx; + int start; + int count; + int i; + + if (buf->b_ml.ml_line_lnum == 0 || + buf->b_ml.ml_mfp == NULL) /* nothing to do */ + return; + + if (buf->b_ml.ml_flags & ML_LINE_DIRTY) + { + lnum = buf->b_ml.ml_line_lnum; + new_line = buf->b_ml.ml_line_ptr; + + hp = ml_find_line(buf, lnum, ML_FIND); + if (hp == NULL) + EMSGN("Cannot find line %ld", lnum); + else + { + dp = (DATA_BL *)(hp->bh_data); + idx = lnum - buf->b_ml.ml_locked_low; + start = ((dp->db_index[idx]) & DB_INDEX_MASK); + old_line = (char_u *)dp + start; + if (idx == 0) /* line is last in block */ + old_len = dp->db_txt_end - start; + else /* text of previous line follows */ + old_len = (dp->db_index[idx - 1] & DB_INDEX_MASK) - start; + new_len = STRLEN(new_line) + 1; + extra = new_len - old_len; /* negative if lines gets smaller */ + + /* + * if new line fits in data block, replace directly + */ + if ((int)dp->db_free >= extra) + { + /* if the length changes and there are following lines */ + count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 1; + if (extra != 0 && idx < count - 1) + { + /* move text of following lines */ + vim_memmove((char *)dp + dp->db_txt_start - extra, + (char *)dp + dp->db_txt_start, + (size_t)(start - dp->db_txt_start)); + + /* adjust pointers of this and following lines */ + for (i = idx + 1; i < count; ++i) + dp->db_index[i] -= extra; + } + dp->db_index[idx] -= extra; + + /* adjust free space */ + dp->db_free -= extra; + dp->db_txt_start -= extra; + + /* copy new line into the data block */ + vim_memmove(old_line - extra, new_line, (size_t)new_len); + buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS); + } + else + { + /* + * Cannot do it in one data block: delete and append. + */ + /* How about handling errors??? */ + (void)ml_delete_int(buf, lnum, FALSE); + (void)ml_append_int(buf, lnum - 1, new_line, new_len, FALSE); + } + } + vim_free(new_line); + } + + buf->b_ml.ml_line_lnum = 0; +} + +/* + * create a new, empty, data block + */ + static BHDR * +ml_new_data(mfp, negative, page_count) + MEMFILE *mfp; + int negative; + int page_count; +{ + BHDR *hp; + DATA_BL *dp; + + if ((hp = mf_new(mfp, negative, page_count)) == NULL) + return NULL; + + dp = (DATA_BL *)(hp->bh_data); + dp->db_id = DATA_ID; + dp->db_txt_start = dp->db_txt_end = page_count * mfp->mf_page_size; + dp->db_free = dp->db_txt_start - HEADER_SIZE; + dp->db_line_count = 0; + + return hp; +} + +/* + * create a new, empty, pointer block + */ + static BHDR * +ml_new_ptr(mfp) + MEMFILE *mfp; +{ + BHDR *hp; + PTR_BL *pp; + + if ((hp = mf_new(mfp, FALSE, 1)) == NULL) + return NULL; + + pp = (PTR_BL *)(hp->bh_data); + pp->pb_id = PTR_ID; + pp->pb_count = 0; + pp->pb_count_max = (mfp->mf_page_size - sizeof(PTR_BL)) / sizeof(PTR_EN) + 1; + + return hp; +} + +/* + * lookup line 'lnum' in a memline + * + * action: if ML_DELETE or ML_INSERT the line count is updated while searching + * if ML_FLUSH only flush a locked block + * if ML_FIND just find the line + * + * If the block was found it is locked and put in ml_locked. + * The stack is updated to lead to the locked block. The ip_high field in + * the stack is updated to reflect the last line in the block AFTER the + * insert or delete, also if the pointer block has not been updated yet. But + * if if ml_locked != NULL ml_locked_lineadd must be added to ip_high. + * + * return: NULL for failure, pointer to block header otherwise + */ + static BHDR * +ml_find_line(buf, lnum, action) + BUF *buf; + linenr_t lnum; + int action; +{ + DATA_BL *dp; + PTR_BL *pp; + IPTR *ip; + BHDR *hp; + MEMFILE *mfp; + linenr_t t; + blocknr_t bnum, bnum2; + int dirty; + linenr_t low, high; + int top; + int page_count; + int idx; + + mfp = buf->b_ml.ml_mfp; + + /* + * If there is a locked block check if the wanted line is in it. + * If not, flush and release the locked block. + * Don't do this for ML_INSERT_SAME, because the stack need to be updated. + * Don't do this for ML_FLUSH, because we want to flush the locked block. + */ + if (buf->b_ml.ml_locked) + { + if (ML_SIMPLE(action) && buf->b_ml.ml_locked_low <= lnum && + buf->b_ml.ml_locked_high >= lnum) + { + /* remember to update pointer blocks and stack later */ + if (action == ML_INSERT) + { + ++(buf->b_ml.ml_locked_lineadd); + ++(buf->b_ml.ml_locked_high); + } + else if (action == ML_DELETE) + { + --(buf->b_ml.ml_locked_lineadd); + --(buf->b_ml.ml_locked_high); + } + return (buf->b_ml.ml_locked); + } + + mf_put(mfp, buf->b_ml.ml_locked, buf->b_ml.ml_flags & ML_LOCKED_DIRTY, + buf->b_ml.ml_flags & ML_LOCKED_POS); + buf->b_ml.ml_locked = NULL; + + /* + * if lines have been added or deleted in the locked block, need to + * update the line count in pointer blocks + */ + if (buf->b_ml.ml_locked_lineadd) + ml_lineadd(buf, buf->b_ml.ml_locked_lineadd); + } + + if (action == ML_FLUSH) /* nothing else to do */ + return NULL; + + bnum = 1; /* start at the root of the tree */ + page_count = 1; + low = 1; + high = buf->b_ml.ml_line_count; + + if (action == ML_FIND) /* first try stack entries */ + { + for (top = buf->b_ml.ml_stack_top - 1; top >= 0; --top) + { + ip = &(buf->b_ml.ml_stack[top]); + if (ip->ip_low <= lnum && ip->ip_high >= lnum) + { + bnum = ip->ip_bnum; + low = ip->ip_low; + high = ip->ip_high; + buf->b_ml.ml_stack_top = top; /* truncate stack at prev entry */ + break; + } + } + if (top < 0) + buf->b_ml.ml_stack_top = 0; /* not found, start at the root */ + } + else /* ML_DELETE or ML_INSERT */ + buf->b_ml.ml_stack_top = 0; /* start at the root */ + +/* + * search downwards in the tree until a data block is found + */ + for (;;) + { + if ((hp = mf_get(mfp, bnum, page_count)) == NULL) + goto error_noblock; + + /* + * update high for insert/delete + */ + if (action == ML_INSERT) + ++high; + else if (action == ML_DELETE) + --high; + + dp = (DATA_BL *)(hp->bh_data); + if (dp->db_id == DATA_ID) /* data block */ + { + buf->b_ml.ml_locked = hp; + buf->b_ml.ml_locked_low = low; + buf->b_ml.ml_locked_high = high; + buf->b_ml.ml_locked_lineadd = 0; + buf->b_ml.ml_flags &= ~(ML_LOCKED_DIRTY | ML_LOCKED_POS); + return hp; + } + + pp = (PTR_BL *)(dp); /* must be pointer block */ + if (pp->pb_id != PTR_ID) + { + EMSG("pointer block id wrong"); + goto error_block; + } + + if ((top = ml_add_stack(buf)) < 0) /* add new entry to stack */ + goto error_block; + ip = &(buf->b_ml.ml_stack[top]); + ip->ip_bnum = bnum; + ip->ip_low = low; + ip->ip_high = high; + ip->ip_index = -1; /* index not known yet */ + + dirty = FALSE; + for (idx = 0; idx < (int)pp->pb_count; ++idx) + { + t = pp->pb_pointer[idx].pe_line_count; + CHECK(t == 0, "pe_line_count is zero"); + if ((low += t) > lnum) + { + ip->ip_index = idx; + bnum = pp->pb_pointer[idx].pe_bnum; + page_count = pp->pb_pointer[idx].pe_page_count; + high = low - 1; + low -= t; + + /* + * a negative block number may have been changed + */ + if (bnum < 0) + { + bnum2 = mf_trans_del(mfp, bnum); + if (bnum != bnum2) + { + bnum = bnum2; + pp->pb_pointer[idx].pe_bnum = bnum; + dirty = TRUE; + } + } + + break; + } + } + if (idx >= (int)pp->pb_count) /* past the end: something wrong! */ + { + if (lnum > buf->b_ml.ml_line_count) + EMSGN("line number out of range: %ld past the end", + lnum - buf->b_ml.ml_line_count); + + else + EMSGN("line count wrong in block %ld", bnum); + goto error_block; + } + if (action == ML_DELETE) + { + pp->pb_pointer[idx].pe_line_count--; + dirty = TRUE; + } + else if (action == ML_INSERT) + { + pp->pb_pointer[idx].pe_line_count++; + dirty = TRUE; + } + mf_put(mfp, hp, dirty, FALSE); + } + +error_block: + mf_put(mfp, hp, FALSE, FALSE); +error_noblock: +/* + * If action is ML_DELETE or ML_INSERT we have to correct the tree for + * the incremented/decremented line counts, because there won't be a line + * inserted/deleted after all. + */ + if (action == ML_DELETE) + ml_lineadd(buf, 1); + else if (action == ML_INSERT) + ml_lineadd(buf, -1); + buf->b_ml.ml_stack_top = 0; + return NULL; +} + +/* + * add an entry to the info pointer stack + * + * return -1 for failure, number of the new entry otherwise + */ + static int +ml_add_stack(buf) + BUF *buf; +{ + int top; + IPTR *newstack; + + top = buf->b_ml.ml_stack_top; + + /* may have to increase the stack size */ + if (top == buf->b_ml.ml_stack_size) + { + CHECK(top > 0, "Stack size increases"); /* more than 5 levels??? */ + + newstack = (IPTR *)alloc((unsigned)sizeof(IPTR) * + (buf->b_ml.ml_stack_size + STACK_INCR)); + if (newstack == NULL) + return -1; + vim_memmove(newstack, buf->b_ml.ml_stack, (size_t)top * sizeof(IPTR)); + vim_free(buf->b_ml.ml_stack); + buf->b_ml.ml_stack = newstack; + buf->b_ml.ml_stack_size += STACK_INCR; + } + + buf->b_ml.ml_stack_top++; + return top; +} + +/* + * Update the pointer blocks on the stack for inserted/deleted lines. + * The stack itself is also updated. + * + * When a insert/delete line action fails, the line is not inserted/deleted, + * but the pointer blocks have already been updated. That is fixed here by + * walking through the stack. + * + * Count is the number of lines added, negative if lines have been deleted. + */ + static void +ml_lineadd(buf, count) + BUF *buf; + int count; +{ + int idx; + IPTR *ip; + PTR_BL *pp; + MEMFILE *mfp = buf->b_ml.ml_mfp; + BHDR *hp; + + for (idx = buf->b_ml.ml_stack_top - 1; idx >= 0; --idx) + { + ip = &(buf->b_ml.ml_stack[idx]); + if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) + break; + pp = (PTR_BL *)(hp->bh_data); /* must be pointer block */ + if (pp->pb_id != PTR_ID) + { + mf_put(mfp, hp, FALSE, FALSE); + EMSG("pointer block id wrong 2"); + break; + } + pp->pb_pointer[ip->ip_index].pe_line_count += count; + ip->ip_high += count; + mf_put(mfp, hp, TRUE, FALSE); + } +} + +/* + * make swap file name out of the filename and a directory name + */ + static char_u * +makeswapname(buf, dir_name) + BUF *buf; + char_u *dir_name; +{ + char_u *r, *s, *fname; + +#ifdef VMS + r = modname(buf->b_xfilename, (char_u *)"_swp"); +#else + r = modname(buf->b_xfilename, (char_u *)".swp"); +#endif + /* + * do not use dir_name + * - if dir_name starts with '.' (use current directory) + * - if out of memory + */ + if (*dir_name == '.' || r == NULL) + return r; + + fname = gettail(r); + s = concat_fnames(dir_name, fname, TRUE); + vim_free(r); + return s; +} + +/* + * Find out what name to use for the swap file for buffer 'buf'. + * + * Several names are tried to find one that does not exist + * + * Note: if MAXNAMLEN is not correct, you will get error messages for + * not being able to open the swapfile + */ + static char_u * +findswapname(buf, dirp, old_fname) + BUF *buf; + char_u **dirp; /* pointer to list of directories */ + char_u *old_fname; /* don't give warning for this filename */ +{ + char_u *fname; + int n; + time_t x; + char_u *dir_name; + +#ifdef AMIGA + BPTR fh; +#endif + +#ifndef SHORT_FNAME + int r; + FILE *dummyfd = NULL; + +/* + * If we start editing a new file, e.g. "test.doc", which resides on an MSDOS + * compatible filesystem, it is possible that the file "test.doc.swp" which we + * create will be exactly the same file. To avoid this problem we temporarily + * create "test.doc". + */ + if (!(buf->b_p_sn || buf->b_shortname) && buf->b_xfilename && + getperm(buf->b_xfilename) < 0) + dummyfd = fopen((char *)buf->b_xfilename, "w"); +#endif + +/* + * Isolate a directory name from *dirp and put it in dir_name. + * First allocate some memore to put the directory name in. + */ + dir_name = alloc((unsigned)STRLEN(*dirp) + 1); + if (dir_name != NULL) + (void)copy_option_part(dirp, dir_name, 31000, ","); + +/* + * we try different names until we find one that does not exist yet + */ + if (dir_name == NULL) /* out of memory */ + fname = NULL; + else + fname = makeswapname(buf, dir_name); + + for (;;) + { + if (fname == NULL) /* must be out of memory */ + break; + if ((n = STRLEN(fname)) == 0) /* safety check */ + { + vim_free(fname); + fname = NULL; + break; + } +#if (defined(UNIX) || defined(OS2)) && !defined(ARCHIE) && !defined(SHORT_FNAME) +/* + * Some systems have a MS-DOS compatible filesystem that use 8.3 character + * file names. If this is the first try and the swap file name does not fit in + * 8.3, detect if this is the case, set shortname and try again. + */ + if (fname[n - 1] == 'p' && !(buf->b_p_sn || buf->b_shortname)) + { + char_u *tail; + char_u *fname2; + struct stat s1, s2; + int f1, f2; + int created1 = FALSE, created2 = FALSE; + int same = FALSE; + + /* + * Check if swapfilename does not fit in 8.3: + * It either contains two dots or it is longer than 8 chars. + */ + tail = gettail(buf->b_xfilename); + if (vim_strchr(tail, '.') != NULL || STRLEN(tail) > (size_t)8) + { + fname2 = alloc(n + 1); + if (fname2 != NULL) + { + STRCPY(fname2, fname); + if (vim_strchr(tail, '.') != NULL) + fname2[n - 1] = 'x'; /* change ".swp" to ".swx" */ + else + fname2[n - 5] += 1; /* change "x.swp" to "y.swp" */ + /* + * may need to create the files to be able to use stat() + */ + f1 = open((char *)fname, O_RDONLY | O_EXTRA); + if (f1 < 0) + { + f1 = open((char *)fname, O_RDWR|O_CREAT|O_EXCL|O_EXTRA +#ifdef AMIGA /* Amiga has no mode argument */ + ); +#endif +#ifdef UNIX /* open in rw------- mode */ + , (mode_t)0600); +#endif +#if defined(MSDOS) || defined(WIN32) || defined(OS2) /* open read/write */ + , S_IREAD | S_IWRITE); +#endif +#if defined(OS2) + if (f1 < 0 && errno == ENOENT) + same = TRUE; +#endif + created1 = TRUE; + } + if (f1 >= 0) + { + f2 = open((char *)fname2, O_RDONLY | O_EXTRA); + if (f2 < 0) + { + f2 = open((char *)fname2, + O_RDWR|O_CREAT|O_EXCL|O_EXTRA +#ifdef AMIGA /* Amiga has no mode argument */ + ); +#endif +#ifdef UNIX /* open in rw------- mode */ + , (mode_t)0600); +#endif +#if defined(MSDOS) || defined(WIN32) || defined(OS2) /* open read/write */ + , S_IREAD | S_IWRITE); +#endif + created2 = TRUE; + } + if (f2 >= 0) + { + /* + * Both files exist now. If stat() returns the + * same device and inode they are the same file. + */ + if (fstat(f1, &s1) != -1 && + fstat(f2, &s2) != -1 && + s1.st_dev == s2.st_dev && + s1.st_ino == s2.st_ino) + same = TRUE; + close(f2); + if (created2) + vim_remove(fname2); + } + close(f1); + if (created1) + vim_remove(fname); + } + vim_free(fname2); + if (same) + { + buf->b_shortname = TRUE; + vim_free(fname); + fname = makeswapname(buf, dir_name); + continue; /* try again with b_shortname set */ + } + } + } + } +#endif + /* + * check if the swapfile already exists + */ + if (getperm(fname) < 0) /* it does not exist */ + { +#ifdef AMIGA + fh = Open((UBYTE *)fname, (long)MODE_NEWFILE); + /* + * on the Amiga getperm() will return -1 when the file exists but + * is being used by another program. This happens if you edit + * a file twice. + */ + if (fh != (BPTR)NULL) /* can open file, OK */ + { + Close(fh); + break; + } + if (IoErr() != ERROR_OBJECT_IN_USE && + IoErr() != ERROR_OBJECT_EXISTS) +#endif + break; + } + /* + * A file name equal to old_fname is OK to use. + */ + if (old_fname != NULL && fnamecmp(fname, old_fname) == 0) + break; + + /* + * get here when file already exists + */ + if (fname[n - 1] == 'p') /* first try */ + { +#ifndef SHORT_FNAME + /* + * on MS-DOS compatible filesystems (e.g. messydos) file.doc.swp + * and file.doc are the same file. To guess if this problem is + * present try if file.doc.swx exists. If it does, we set + * buf->b_shortname and try file_doc.swp (dots replaced by + * underscores for this file), and try again. If it doesn't we + * assume that "file.doc.swp" already exists. + */ + if (!(buf->b_p_sn || buf->b_shortname)) /* not tried yet */ + { + fname[n - 1] = 'x'; + r = getperm(fname); /* try "file.swx" */ + fname[n - 1] = 'p'; + if (r >= 0) /* "file.swx" seems to exist */ + { + buf->b_shortname = TRUE; + vim_free(fname); + fname = makeswapname(buf, dir_name); + continue; /* try again with '.' replaced by '_' */ + } + } +#endif + /* + * If we get here the ".swp" file really exists. + * Give an error message, unless recovering, no file name, we are + * viewing a help file or when the path of the file is different + * (happens when all .swp files are in one directory). + */ + if (!recoverymode && buf->b_xfilename != NULL && !buf->b_help) + { + int fd; + struct block0 b0; + int differ = FALSE; + + /* + * Try to read block 0 from the swap file to get the original + * file name (and inode number). + */ + fd = open((char *)fname, O_RDONLY | O_EXTRA); + if (fd >= 0) + { + if (read(fd, (char *)&b0, sizeof(b0)) == sizeof(b0)) + { + /* + * The name in the swap file may be "~user/path/file". + * Expand it first. + */ + expand_env(b0.b0_fname, NameBuff, MAXPATHL); +#ifdef CHECK_INODE + if (fnamecmp_ino(buf->b_filename, NameBuff, + char_to_long(b0.b0_ino))) + differ = TRUE; +#else + if (fnamecmp(NameBuff, buf->b_filename) != 0) + differ = TRUE; +#endif + } + close(fd); + } + if (differ == FALSE) + { + struct stat st; + + ++no_wait_return; +#ifdef SLEEP_IN_EMSG + ++dont_sleep; +#endif + (void)EMSG("ATTENTION"); +#ifdef SLEEP_IN_EMSG + --dont_sleep; +#endif + MSG_OUTSTR("\nFound a swap file by the name \""); + msg_home_replace(fname); + MSG_OUTSTR("\"\n"); + swapfile_info(fname); + MSG_OUTSTR("While opening file \""); + msg_outtrans(buf->b_xfilename); + MSG_OUTSTR("\"\n"); + if (stat((char *)buf->b_xfilename, &st) != -1) + { + MSG_OUTSTR(" dated: "); + x = st.st_mtime; /* Manx C can't do &st.st_mtime */ + MSG_OUTSTR(ctime(&x)); + } + MSG_OUTSTR("\n(1) Another program may be editing the same file.\n"); + MSG_OUTSTR(" If this is the case, quit this edit session to avoid having\n"); + MSG_OUTSTR(" two different instances of the same file when making changes.\n"); + MSG_OUTSTR("\n(2) An edit session for this file crashed.\n"); + MSG_OUTSTR(" If this is the case, use \":recover\" or \"vim -r "); + msg_outtrans(buf->b_xfilename); + MSG_OUTSTR("\"\n to recover the changes (see \":help recovery)\".\n"); + MSG_OUTSTR(" If you did this already, delete the swap file \""); + msg_outtrans(fname); + MSG_OUTSTR("\"\n to avoid this message.\n\n"); + cmdline_row = msg_row; + --no_wait_return; + need_wait_return = TRUE; /* call wait_return later */ + } + } + } + + if (fname[n - 1] == 'a') /* tried enough names, give up */ + { + vim_free(fname); + fname = NULL; + break; + } + --fname[n - 1]; /* change last char of the name */ + } + + vim_free(dir_name); +#ifndef SHORT_FNAME + if (dummyfd) /* file has been created temporarily */ + { + fclose(dummyfd); + vim_remove(buf->b_xfilename); + } +#endif + return fname; +} + + static int +b0_magic_wrong(b0p) + ZERO_BL *b0p; +{ + return (b0p->b0_magic_long != (long)B0_MAGIC_LONG || + b0p->b0_magic_int != (int)B0_MAGIC_INT || + b0p->b0_magic_short != (short)B0_MAGIC_SHORT || + b0p->b0_magic_char != B0_MAGIC_CHAR); +} + +#ifdef CHECK_INODE +/* + * Compare current file name with file name from swap file. + * Try to use inode numbers when possible. + * Return non-zero when files are different. + * + * When comparing file names a few things have to be taken into consideration: + * - When working over a network the full path of a file depends on the host. + * We check the inode number if possible. It is not 100% reliable though, + * because the device number cannot be used over a network. + * - When a file does not exist yet (editing a new file) there is no inode + * number. + * - The file name in a swap file may not be valid on the current host. The + * "~user" form is used whenever possible to avoid this. + * + * This is getting complicated, let's make a table: + * + * ino_c ino_s fname_c fname_s differ = + * + * both files exist -> compare inode numbers: + * != 0 != 0 X X ino_c != ino_s + * + * inode number(s) unknown, file names available -> compare file names + * == 0 X OK OK fname_c != fname_s + * X == 0 OK OK fname_c != fname_s + * + * current file doesn't exist, file for swap file exist, file name(s) not + * available -> probably different + * == 0 != 0 FAIL X TRUE + * == 0 != 0 X FAIL TRUE + * + * current file exists, inode for swap unknown, file name(s) not + * available -> probably different + * != 0 == 0 FAIL X TRUE + * != 0 == 0 X FAIL TRUE + * + * current file doesn't exist, inode for swap unknown, one file name not + * available -> probably different + * == 0 == 0 FAIL OK TRUE + * == 0 == 0 OK FAIL TRUE + * + * current file doesn't exist, inode for swap unknown, both file names not + * available -> probably same file + * == 0 == 0 FAIL FAIL FALSE + */ + + static int +fnamecmp_ino(fname_c, fname_s, ino_block0) + char_u *fname_c; /* current file name */ + char_u *fname_s; /* file name from swap file */ + long ino_block0; +{ + struct stat st; + long ino_c = 0; /* ino of current file */ + long ino_s; /* ino of file from swap file */ + char_u buf_c[MAXPATHL]; /* full path of fname_c */ + char_u buf_s[MAXPATHL]; /* full path of fname_s */ + int retval_c; /* flag: buf_c valid */ + int retval_s; /* flag: buf_s valid */ + + + if (stat((char *)fname_c, &st) == 0) + ino_c = st.st_ino; + + /* + * First we try to get the inode from the file name, because the inode in + * the swap file may be outdated. If that fails (e.g. this path is not + * valid on this machine), use the inode from block 0. + */ + if (stat((char *)fname_s, &st) == 0) + ino_s = st.st_ino; + else + ino_s = ino_block0; + + if (ino_c && ino_s) + return (ino_c != ino_s); + + /* + * One of the inode numbers is unknown, try a forced FullName() and + * compare the file names. + */ + retval_c = FullName(fname_c, buf_c, MAXPATHL, TRUE); + retval_s = FullName(fname_s, buf_s, MAXPATHL, TRUE); + if (retval_c == OK && retval_s == OK) + return (STRCMP(buf_c, buf_s) != 0); + + /* + * Can't compare inodes or filenames, guess that the files are different, + * unless both appear not to exist at all. + */ + if (ino_s == 0 && ino_c == 0 && retval_c == FAIL && retval_s == FAIL) + return FALSE; + return TRUE; +} +#endif /* CHECK_INODE */ + +/* + * Move a long integer into a four byte character array. + * Used for machine independency in block zero. + */ + static void +long_to_char(n, s) + long n; + char_u *s; +{ + s[0] = (n & 0xff); + n >>= 8; + s[1] = (n & 0xff); + n >>= 8; + s[2] = (n & 0xff); + n >>= 8; + s[3] = (n & 0xff); +} + + static long +char_to_long(s) + char_u *s; +{ + long retval; + + retval = s[3]; + retval <<= 8; + retval += s[2]; + retval <<= 8; + retval += s[1]; + retval <<= 8; + retval += s[0]; + + return retval; +} diff --git a/usr.bin/vim/message.c b/usr.bin/vim/message.c new file mode 100644 index 00000000000..5815dae88dd --- /dev/null +++ b/usr.bin/vim/message.c @@ -0,0 +1,832 @@ +/* $OpenBSD: message.c,v 1.1.1.1 1996/09/07 21:40:25 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * message.c: functions for displaying messages on the command line + */ + +#include "vim.h" +#include "globals.h" +#define MESSAGE /* don't include prototype for smsg() */ +#include "proto.h" +#include "option.h" + +static void msg_screen_outchar __ARGS((int c)); +static int msg_check_screen __ARGS((void)); + +static int lines_left = -1; /* lines left for listing */ + +/* + * msg(s) - displays the string 's' on the status line + * When terminal not initialized (yet) fprintf(stderr,..) is used. + * return TRUE if wait_return not called + */ + int +msg(s) + char_u *s; +{ + msg_start(); + if (msg_highlight) + start_highlight(); + msg_outtrans(s); + if (msg_highlight) + { + stop_highlight(); + msg_highlight = FALSE; /* clear for next call */ + } + msg_clr_eos(); + return msg_end(); +} + +/* + * automatic prototype generation does not understand this function + */ +#ifndef PROTO +int smsg __ARGS((char_u *, long, long, long, + long, long, long, long, long, long, long)); + +/* VARARGS */ + int +smsg(s, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) + char_u *s; + long a1, a2, a3, a4, a5, a6, a7, a8, a9, a10; +{ + sprintf((char *)IObuff, (char *)s, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); + return msg(IObuff); +} +#endif + +/* + * emsg() - display an error message + * + * Rings the bell, if appropriate, and calls message() to do the real work + * When terminal not initialized (yet) fprintf(stderr,..) is used. + * + * return TRUE if wait_return not called + */ + int +emsg(s) + char_u *s; +{ + char_u *Buf; +#ifdef SLEEP_IN_EMSG + int retval; +#endif + static int last_lnum = 0; + static char_u *last_sourcing_name = NULL; + + if (emsg_off) /* no error messages at the moment */ + return TRUE; + + if (global_busy) /* break :global command */ + ++global_busy; + + if (p_eb) + beep_flush(); /* also includes flush_buffers() */ + else + flush_buffers(FALSE); /* flush internal buffers */ + did_emsg = TRUE; /* flag for DoOneCmd() */ + ++msg_scroll; /* don't overwrite a previous message */ + (void)set_highlight('e'); /* set highlight mode for error messages */ + msg_highlight = TRUE; + if (msg_scrolled) + need_wait_return = TRUE; /* needed in case emsg() is called after + * wait_return has reset need_wait_return + * and a redraw is expected because + * msg_scrolled is non-zero */ + +/* + * First output name and line number of source of error message + */ + if (sourcing_name != NULL && + (sourcing_name != last_sourcing_name || sourcing_lnum != last_lnum) + && (Buf = alloc(MAXPATHL + 30)) != NULL) + { + ++no_wait_return; + if (sourcing_name != last_sourcing_name) + { + sprintf((char *)Buf, "Error detected while processing %s:", + sourcing_name); + msg(Buf); + msg_highlight = TRUE; + } + /* lnum is 0 when executing a command from the command line + * argument, we don't want a line number then */ + if (sourcing_lnum != 0) + { + (void)set_highlight('n'); /* highlight mode for line numbers */ + sprintf((char *)Buf, "line %4ld:", sourcing_lnum); + msg(Buf); + (void)set_highlight('e'); /* highlight mode for error messages */ + msg_highlight = TRUE; + } + --no_wait_return; + last_lnum = sourcing_lnum; /* only once for each line */ + vim_free(Buf); + } + last_sourcing_name = sourcing_name; /* do this also when it is NULL */ + +#ifdef SLEEP_IN_EMSG +/* + * Msg returns TRUE if wait_return() was not called. + * In that case may call sleep() to give the user a chance to read the message. + * Don't call sleep() if dont_sleep is set. + */ + retval = msg(s); + if (retval) + { + if (dont_sleep || need_wait_return) + need_sleep = TRUE; /* sleep before removing the message */ + else + mch_delay(1000L, TRUE); /* give user chance to read message */ + } + /* --msg_scroll; don't overwrite this message */ + return retval; +#else + emsg_on_display = TRUE; /* remember there is an error message */ + return msg(s); +#endif +} + + int +emsg2(s, a1) + char_u *s, *a1; +{ + /* Check for NULL strings (just in case) */ + if (a1 == NULL) + a1 = (char_u *)"[NULL]"; + /* Check for very long strings (can happen with ":help ^A") */ + if (STRLEN(s) + STRLEN(a1) >= IOSIZE) + a1 = (char_u *)"[string too long]"; + sprintf((char *)IObuff, (char *)s, (char *)a1); + return emsg(IObuff); +} + + int +emsgn(s, n) + char_u *s; + long n; +{ + sprintf((char *)IObuff, (char *)s, n); + return emsg(IObuff); +} + +/* + * Like msg(), but truncate to a single line if p_shm contains 't'. + * Careful: The string may be changed! + */ + int +msg_trunc(s) + char_u *s; +{ + int n; + + if (shortmess(SHM_TRUNC) && (n = (int)STRLEN(s) - + (int)(Rows - cmdline_row - 1) * Columns - sc_col + 1) > 0) + { + s[n] = '<'; + return msg(s + n); + } + else + return msg(s); +} + +/* + * wait for the user to hit a key (normally a return) + * if 'redraw' is TRUE, clear and redraw the screen + * if 'redraw' is FALSE, just redraw the screen + * if 'redraw' is -1, don't redraw at all + */ + void +wait_return(redraw) + int redraw; +{ + int c; + int oldState; + int tmpState; + + if (redraw == TRUE) + must_redraw = CLEAR; + +/* + * With the global command (and some others) we only need one return at the + * end. Adjust cmdline_row to avoid the next message overwriting the last one. + */ + if (no_wait_return) + { + need_wait_return = TRUE; + cmdline_row = msg_row; + return; + } + oldState = State; + if (quit_more) + { + c = CR; /* just pretend CR was hit */ + quit_more = FALSE; + got_int = FALSE; + } + else + { + State = HITRETURN; +#ifdef USE_MOUSE + setmouse(); +#endif + if (msg_didout) /* start on a new line */ + msg_outchar('\n'); + if (got_int) + MSG_OUTSTR("Interrupt: "); + + (void)set_highlight('r'); + start_highlight(); +#ifdef ORG_HITRETURN + MSG_OUTSTR("Press RETURN to continue"); + stop_highlight(); + do { + c = vgetc(); + } while (vim_strchr((char_u *)"\r\n: ", c) == NULL); + if (c == ':') /* this can vi too (but not always!) */ + stuffcharReadbuff(c); +#else + MSG_OUTSTR("Press RETURN or enter command to continue"); + stop_highlight(); + do + { + c = vgetc(); + got_int = FALSE; + } while (c == Ctrl('C') +#ifdef USE_GUI + || c == K_SCROLLBAR || c == K_HORIZ_SCROLLBAR +#endif +#ifdef USE_MOUSE + || c == K_LEFTDRAG || c == K_LEFTRELEASE + || c == K_MIDDLEDRAG || c == K_MIDDLERELEASE + || c == K_RIGHTDRAG || c == K_RIGHTRELEASE + || c == K_IGNORE || + (!mouse_has(MOUSE_RETURN) && + (c == K_LEFTMOUSE || + c == K_MIDDLEMOUSE || + c == K_RIGHTMOUSE)) +#endif + ); + mch_breakcheck(); +#ifdef USE_MOUSE + /* + * Avoid that the mouse-up event causes visual mode to start. + */ + if (c == K_LEFTMOUSE || c == K_MIDDLEMOUSE || c == K_RIGHTMOUSE) + jump_to_mouse(MOUSE_SETPOS); + else +#endif + if (vim_strchr((char_u *)"\r\n ", c) == NULL) + { + stuffcharReadbuff(c); + do_redraw = TRUE; /* need a redraw even though there is + something in the stuff buffer */ + } +#endif + } + + /* + * If the user hits ':', '?' or '/' we get a command line from the next + * line. + */ + if (c == ':' || c == '?' || c == '/') + { + cmdline_row = msg_row; + skip_redraw = TRUE; /* skip redraw once */ + do_redraw = FALSE; + } + +/* + * If the window size changed set_winsize() will redraw the screen. + * Otherwise the screen is only redrawn if 'redraw' is set and no ':' typed. + */ + tmpState = State; + State = oldState; /* restore State before set_winsize */ +#ifdef USE_MOUSE + setmouse(); +#endif + msg_check(); + + need_wait_return = FALSE; + emsg_on_display = FALSE; /* can delete error message now */ +#ifdef SLEEP_IN_EMSG + need_sleep = FALSE; /* no need to call sleep() anymore */ +#endif + msg_didany = FALSE; /* reset lines_left at next msg_start() */ + lines_left = -1; + if (keep_msg != NULL && linetabsize(keep_msg) >= + (Rows - cmdline_row - 1) * Columns + sc_col) + keep_msg = NULL; /* don't redisplay message, it's too long */ + + if (tmpState == SETWSIZE) /* got resize event while in vgetc() */ + { + starttermcap(); /* start termcap before redrawing */ + set_winsize(0, 0, FALSE); + } + else if (!skip_redraw && (redraw == TRUE || (msg_scrolled && redraw != -1))) + { + starttermcap(); /* start termcap before redrawing */ + updateScreen(VALID); + } + + dont_wait_return = TRUE; /* don't wait again in main() */ +} + +/* + * Prepare for outputting characters in the command line. + */ + void +msg_start() +{ + keep_msg = NULL; /* don't display old message now */ + keep_msg_highlight = 0; + if (!msg_scroll && full_screen) /* overwrite last message */ + msg_pos(cmdline_row, 0); + else if (msg_didout) /* start message on next line */ + { + msg_outchar('\n'); + cmdline_row = msg_row; + } + if (!msg_didany) + lines_left = cmdline_row; + msg_didout = FALSE; /* no output on current line yet */ + cursor_off(); +} + +/* + * Move message position. This should always be used after moving the cursor. + * Use negative value if row or col does not have to be changed. + */ + void +msg_pos(row, col) + int row, col; +{ + if (row >= 0) + msg_row = row; + if (col >= 0) + msg_col = col; +} + + void +msg_outchar(c) + int c; +{ + char_u buf[4]; + + if (IS_SPECIAL(c)) + { + buf[0] = K_SPECIAL; + buf[1] = K_SECOND(c); + buf[2] = K_THIRD(c); + buf[3] = NUL; + } + else + { + buf[0] = c; + buf[1] = NUL; + } + msg_outstr(buf); +} + + void +msg_outnum(n) + long n; +{ + char_u buf[20]; + + sprintf((char *)buf, "%ld", n); + msg_outstr(buf); +} + + void +msg_home_replace(fname) + char_u *fname; +{ + char_u *name; + + name = home_replace_save(NULL, fname); + if (name != NULL) + msg_outtrans(name); + vim_free(name); +} + +/* + * output 'len' characters in 'str' (including NULs) with translation + * if 'len' is -1, output upto a NUL character + * return the number of characters it takes on the screen + */ + int +msg_outtrans(str) + register char_u *str; +{ + return msg_outtrans_len(str, (int)STRLEN(str)); +} + + int +msg_outtrans_len(str, len) + register char_u *str; + register int len; +{ + int retval = 0; + + while (--len >= 0) + { + msg_outstr(transchar(*str)); + retval += charsize(*str); + ++str; + } + return retval; +} + +/* + * Output the string 'str' upto a NUL character. + * Return the number of characters it takes on the screen. + * + * If K_SPECIAL is encountered, then it is taken in conjunction with the + * following character and shown as , etc. In addition, if 'all' + * is TRUE, then any other character which has its 8th bit set is shown as + * , where x is the equivalent character without its 8th bit set. If a + * character is displayed in one of these special ways, is also highlighted + * (its highlight name is '8' in the p_hl variable). + * This function is used to show mappings, where we want to see how to type + * the character/string -- webb + */ + int +msg_outtrans_special(str, all) + register char_u *str; + register int all; /* etc as well as etc */ +{ + int retval = 0; + char_u *string; + int c; + int modifiers; + + set_highlight('8'); + for (; *str; ++str) + { + c = *str; + if (c == K_SPECIAL && str[1] != NUL && str[2] != NUL) + { + modifiers = 0x0; + if (str[1] == KS_MODIFIER) + { + modifiers = str[2]; + str += 3; + c = *str; + } + if (c == K_SPECIAL) + { + c = TO_SPECIAL(str[1], str[2]); + str += 2; + if (c == K_ZERO) /* display as ^@ */ + c = NUL; + } + if (IS_SPECIAL(c) || modifiers) /* special key */ + { + string = get_special_key_name(c, modifiers); + start_highlight(); + msg_outstr(string); + retval += STRLEN(string); + stop_highlight(); + flushbuf(); /* Otherwise gets overwritten by spaces */ + continue; + } + } + if ((c & 0x80) && all) + { + start_highlight(); + MSG_OUTSTR(""); + stop_highlight(); + } + else + { + msg_outstr(transchar(c)); + retval += charsize(c); + } + } + return retval; +} + +/* + * print line for :p command + */ + void +msg_prt_line(s) + char_u *s; +{ + register int si = 0; + register int c; + register int col = 0; + + int n_extra = 0; + int n_spaces = 0; + char_u *p = NULL; /* init to make SASC shut up */ + int n; + + for (;;) + { + if (n_extra) + { + --n_extra; + c = *p++; + } + else if (n_spaces) + { + --n_spaces; + c = ' '; + } + else + { + c = s[si++]; + if (c == TAB && !curwin->w_p_list) + { + /* tab amount depends on current column */ + n_spaces = curbuf->b_p_ts - col % curbuf->b_p_ts - 1; + c = ' '; + } + else if (c == NUL && curwin->w_p_list) + { + p = (char_u *)""; + n_extra = 1; + c = '$'; + } + else if (c != NUL && (n = charsize(c)) > 1) + { + n_extra = n - 1; + p = transchar(c); + c = *p++; + } + } + + if (c == NUL) + break; + + msg_outchar(c); + col++; + } +} + +/* + * output a string to the screen at position msg_row, msg_col + * Update msg_row and msg_col for the next message. + */ + void +msg_outstr(s) + char_u *s; +{ + int oldState; + char_u buf[20]; + + /* + * If there is no valid screen, use fprintf so we can see error messages. + * If termcap is not active, we may be writing in an alternate console + * window, cursor positioning may not work correctly (window size may be + * different, e.g. for WIN32 console). + */ + if (!msg_check_screen() +#ifdef WIN32 + || !termcap_active +#endif + ) + { +#ifdef WIN32 + mch_settmode(0); /* cook so that \r and \n are handled correctly */ +#endif + fprintf(stderr, (char *)s); + msg_didout = TRUE; /* assume that line is not empty */ +#ifdef WIN32 + mch_settmode(1); +#endif + return; + } + + msg_didany = TRUE; /* remember that something was outputted */ + while (*s) + { + /* + * The screen is scrolled up when: + * - When outputting a newline in the last row + * - when outputting a character in the last column of the last row + * (some terminals scroll automatically, some don't. To avoid + * problems we scroll ourselves) + */ + if (msg_row >= Rows - 1 && (*s == '\n' || msg_col >= Columns - 1 || + (*s == TAB && msg_col >= ((Columns - 1) & ~7)))) + { + screen_del_lines(0, 0, 1, (int)Rows, TRUE); /* always works */ + msg_row = Rows - 2; + if (msg_col >= Columns) /* can happen after screen resize */ + msg_col = Columns - 1; + ++msg_scrolled; + need_wait_return = TRUE; /* may need wait_return in main() */ + if (cmdline_row > 0) + --cmdline_row; + /* + * if screen is completely filled wait for a character + */ + if (p_more && --lines_left == 0 && State != HITRETURN) + { + oldState = State; + State = ASKMORE; +#ifdef USE_MOUSE + setmouse(); +#endif + msg_moremsg(FALSE); + for (;;) + { + /* + * Get a typed character directly from the user. + * Don't use vgetc(), it syncs undo and eats mapped + * characters. Disadvantage: Special keys and mouse + * cannot be used here, typeahead is ignored. + */ + flushbuf(); + (void)mch_inchar(buf, 20, -1L); + switch (buf[0]) + { + case CR: /* one extra line */ + case NL: + lines_left = 1; + break; + case ':': /* start new command line */ + stuffcharReadbuff(':'); + cmdline_row = Rows - 1; /* put ':' on this line */ + skip_redraw = TRUE; /* skip redraw once */ + dont_wait_return = TRUE; /* don't wait in main() */ + /*FALLTHROUGH*/ + case 'q': /* quit */ + case Ctrl('C'): + got_int = TRUE; + quit_more = TRUE; + break; + case 'd': /* Down half a page */ + lines_left = Rows / 2; + break; + case ' ': /* one extra page */ + lines_left = Rows - 1; + break; + default: /* no valid response */ + msg_moremsg(TRUE); + continue; + } + break; + } + /* clear the --more-- message */ + screen_fill((int)Rows - 1, (int)Rows, + 0, (int)Columns, ' ', ' '); + State = oldState; +#ifdef USE_MOUSE + setmouse(); +#endif + if (quit_more) + { + msg_row = Rows - 1; + msg_col = 0; + return; /* the string is not displayed! */ + } + } + } + if (*s == '\n') /* go to next line */ + { + msg_didout = FALSE; /* remember that line is empty */ + msg_col = 0; + if (++msg_row >= Rows) /* safety check */ + msg_row = Rows - 1; + } + else if (*s == '\r') /* go to column 0 */ + { + msg_col = 0; + } + else if (*s == '\b') /* go to previous char */ + { + if (msg_col) + --msg_col; + } + else if (*s == TAB) /* translate into spaces */ + { + do + msg_screen_outchar(' '); + while (msg_col & 7); + } + else + msg_screen_outchar(*s); + ++s; + } +} + + static void +msg_screen_outchar(c) + int c; +{ + msg_didout = TRUE; /* remember that line is not empty */ + screen_outchar(c, msg_row, msg_col); + if (++msg_col >= Columns) + { + msg_col = 0; + ++msg_row; + } +} + + void +msg_moremsg(full) + int full; +{ + /* + * Need to restore old highlighting when we've finished with it + * because the output that's paging may be relying on it not + * changing -- webb + */ + remember_highlight(); + set_highlight('m'); + start_highlight(); + screen_msg((char_u *)"-- More --", (int)Rows - 1, 0); + if (full) + screen_msg((char_u *)" (RET: line, SPACE: page, d: half page, q: quit)", + (int)Rows - 1, 10); + stop_highlight(); + recover_old_highlight(); +} + +/* + * msg_check_screen - check if the screen is initialized. + * Also check msg_row and msg_col, if they are too big it may cause a crash. + */ + static int +msg_check_screen() +{ + if (!full_screen || !screen_valid(FALSE)) + return FALSE; + + if (msg_row >= Rows) + msg_row = Rows - 1; + if (msg_col >= Columns) + msg_col = Columns - 1; + return TRUE; +} + +/* + * clear from current message position to end of screen + * Note: msg_col is not updated, so we remember the end of the message + * for msg_check(). + */ + void +msg_clr_eos() +{ + if (!msg_check_screen() +#ifdef WIN32 + || !termcap_active +#endif + ) + return; + screen_fill(msg_row, msg_row + 1, msg_col, (int)Columns, ' ', ' '); + screen_fill(msg_row + 1, (int)Rows, 0, (int)Columns, ' ', ' '); +} + +/* + * end putting a message on the screen + * call wait_return if the message does not fit in the available space + * return TRUE if wait_return not called. + */ + int +msg_end() +{ + /* + * if the string is larger than the window, + * or the ruler option is set and we run into it, + * we have to redraw the window. + * Do not do this if we are abandoning the file or editing the command line. + */ + if (!exiting && msg_check() && State != CMDLINE) + { + wait_return(FALSE); + return FALSE; + } + flushbuf(); + return TRUE; +} + +/* + * If the written message has caused the screen to scroll up, or if we + * run into the shown command or ruler, we have to redraw the window later. + */ + int +msg_check() +{ + if (msg_scrolled || (msg_row == Rows - 1 && msg_col >= sc_col)) + { + redraw_later(NOT_VALID); + redraw_cmdline = TRUE; + return TRUE; + } + return FALSE; +} diff --git a/usr.bin/vim/misccmds.c b/usr.bin/vim/misccmds.c new file mode 100644 index 00000000000..32dfc86c106 --- /dev/null +++ b/usr.bin/vim/misccmds.c @@ -0,0 +1,3906 @@ +/* $OpenBSD: misccmds.c,v 1.1.1.1 1996/09/07 21:40:25 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * misccmds.c: functions that didn't seem to fit elsewhere + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" +#ifdef HAVE_FCNTL_H +# include /* for chdir() */ +#endif + +static int get_indent_str __ARGS((char_u *ptr)); +static void check_status __ARGS((BUF *)); + +/* + * count the size of the indent in the current line + */ + int +get_indent() +{ + return get_indent_str(ml_get_curline()); +} + +/* + * count the size of the indent in line "lnum" + */ + int +get_indent_lnum(lnum) + linenr_t lnum; +{ + return get_indent_str(ml_get(lnum)); +} + +/* + * count the size of the indent in line "ptr" + */ + static int +get_indent_str(ptr) + register char_u *ptr; +{ + register int count = 0; + + for ( ; *ptr; ++ptr) + { + if (*ptr == TAB) /* count a tab for what it is worth */ + count += (int)curbuf->b_p_ts - (count % (int)curbuf->b_p_ts); + else if (*ptr == ' ') + ++count; /* count a space for one */ + else + break; + } + return (count); +} + +/* + * set the indent of the current line + * leaves the cursor on the first non-blank in the line + */ + void +set_indent(size, del_first) + register int size; + int del_first; +{ + int oldstate = State; + register int c; + + State = INSERT; /* don't want REPLACE for State */ + curwin->w_cursor.col = 0; + if (del_first) /* delete old indent */ + { + /* vim_iswhite() is a define! */ + while ((c = gchar_cursor()), vim_iswhite(c)) + (void)delchar(FALSE); + } + if (!curbuf->b_p_et) /* if 'expandtab' is set, don't use TABs */ + while (size >= (int)curbuf->b_p_ts) + { + ins_char(TAB); + size -= (int)curbuf->b_p_ts; + } + while (size) + { + ins_char(' '); + --size; + } + State = oldstate; +} + +#if defined(CINDENT) || defined(SMARTINDENT) + +static int is_cinword __ARGS((char_u *line)); + +/* + * Return TRUE if the string "line" starts with a word from 'cinwords'. + */ + static int +is_cinword(line) + char_u *line; +{ + char_u *cinw; + char_u *cinw_buf; + int cinw_len; + int retval = FALSE; + int len; + + cinw_len = STRLEN(curbuf->b_p_cinw) + 1; + cinw_buf = alloc((unsigned)cinw_len); + if (cinw_buf != NULL) + { + line = skipwhite(line); + for (cinw = curbuf->b_p_cinw; *cinw; ) + { + len = copy_option_part(&cinw, cinw_buf, cinw_len, ","); + if (STRNCMP(line, cinw_buf, len) == 0 && + (!iswordchar(line[len]) || !iswordchar(line[len - 1]))) + { + retval = TRUE; + break; + } + } + vim_free(cinw_buf); + } + return retval; +} +#endif + +/* + * Opencmd + * + * Add a new line below or above the current line. + * Caller must take care of undo. + * + * Return TRUE for success, FALSE for failure + */ + + int +Opencmd(dir, redraw, del_spaces) + int dir; /* FORWARD or BACKWARD */ + int redraw; /* redraw afterwards */ + int del_spaces; /* delete spaces after cursor */ +{ + char_u *saved_line; /* copy of the original line */ + char_u *p_extra = NULL; /* what goes to next line */ + int extra_len = 0; /* length of p_extra string */ + FPOS old_cursor; /* old cursor position */ + int newcol = 0; /* new cursor column */ + int newindent = 0; /* auto-indent of the new line */ + int n; + int trunc_line = FALSE; /* truncate current line afterwards */ + int retval = FALSE; /* return value, default is FAIL */ + int lead_len; /* length of comment leader */ + char_u *lead_flags; /* position in 'comments' for comment leader */ + char_u *leader = NULL; /* copy of comment leader */ + char_u *allocated = NULL; /* allocated memory */ + char_u *p; + int saved_char = NUL; /* init for GCC */ + FPOS *pos; + int old_plines = 0; /* init for GCC */ + int new_plines = 0; /* init for GCC */ +#ifdef SMARTINDENT + int no_si = FALSE; /* reset did_si afterwards */ + int first_char = NUL; /* init for GCC */ +#endif + + /* + * make a copy of the current line so we can mess with it + */ + saved_line = strsave(ml_get_curline()); + if (saved_line == NULL) /* out of memory! */ + return FALSE; + + if (State == INSERT || State == REPLACE) + { + p_extra = saved_line + curwin->w_cursor.col; +#ifdef SMARTINDENT + if (curbuf->b_p_si) /* need first char after new line break */ + { + p = skipwhite(p_extra); + first_char = *p; + } +#endif + extra_len = STRLEN(p_extra); + saved_char = *p_extra; + *p_extra = NUL; + } + + u_clearline(); /* cannot do "U" command when adding lines */ +#ifdef SMARTINDENT + did_si = FALSE; +#endif + + /* + * If 'autoindent' and/or 'smartindent' is set, try to figure out what + * indent to use for the new line. + */ + if (curbuf->b_p_ai +#ifdef SMARTINDENT + || curbuf->b_p_si +#endif + ) + { + /* + * count white space on current line + */ + newindent = get_indent(); + if (newindent == 0) + newindent = old_indent; /* for ^^D command in insert mode */ + old_indent = 0; + + /* + * If we just did an auto-indent, then we didn't type anything on + * the prior line, and it should be truncated. + */ + if (dir == FORWARD && did_ai) + trunc_line = TRUE; + +#ifdef SMARTINDENT + /* + * Do smart indenting. + * In insert/replace mode (only when dir == FORWARD) + * we may move some text to the next line. If it starts with '{' + * don't add an indent. Fixes inserting a NL before '{' in line + * "if (condition) {" + */ + else if (curbuf->b_p_si && *saved_line != NUL && + (p_extra == NULL || first_char != '{')) + { + char_u *ptr; + char_u last_char; + + old_cursor = curwin->w_cursor; + ptr = saved_line; + lead_len = get_leader_len(ptr, NULL); + if (dir == FORWARD) + { + /* + * Skip preprocessor directives, unless they are + * recognised as comments. + */ + if (lead_len == 0 && ptr[0] == '#') + { + while (ptr[0] == '#' && curwin->w_cursor.lnum > 1) + ptr = ml_get(--curwin->w_cursor.lnum); + newindent = get_indent(); + } + lead_len = get_leader_len(ptr, NULL); + if (lead_len > 0) + { + /* + * This case gets the following right: + * \* + * * A comment (read "\" as "/"). + * *\ + * #define IN_THE_WAY + * This should line up here; + */ + p = skipwhite(ptr); + if (p[0] == '/' && p[1] == '*') + p++; + if (p[0] == '*') + { + for (p++; *p; p++) + { + if (p[0] == '/' && p[-1] == '*') + { + /* + * End of C comment, indent should line up + * with the line containing the start of + * the comment + */ + curwin->w_cursor.col = p - ptr; + if ((pos = findmatch(NUL)) != NULL) + { + curwin->w_cursor.lnum = pos->lnum; + newindent = get_indent(); + } + } + } + } + } + else /* Not a comment line */ + { + /* Find last non-blank in line */ + p = ptr + STRLEN(ptr) - 1; + while (p > ptr && vim_iswhite(*p)) + --p; + last_char = *p; + + /* + * find the character just before the '{' or ';' + */ + if (last_char == '{' || last_char == ';') + { + if (p > ptr) + --p; + while (p > ptr && vim_iswhite(*p)) + --p; + } + /* + * Try to catch lines that are split over multiple + * lines. eg: + * if (condition && + * condition) { + * Should line up here! + * } + */ + if (*p == ')') + { + curwin->w_cursor.col = p - ptr; + if ((pos = findmatch('(')) != NULL) + { + curwin->w_cursor.lnum = pos->lnum; + newindent = get_indent(); + ptr = ml_get_curline(); + } + } + /* + * If last character is '{' do indent, without + * checking for "if" and the like. + */ + if (last_char == '{') + { + did_si = TRUE; /* do indent */ + no_si = TRUE; /* don't delete it when '{' typed */ + } + /* + * Look for "if" and the like, use 'cinwords'. + * Don't do this if the previous line ended in ';' or + * '}'. + */ + else if (last_char != ';' && last_char != '}' && + is_cinword(ptr)) + did_si = TRUE; + } + } + else /* dir == BACKWARD */ + { + /* + * Skip preprocessor directives, unless they are + * recognised as comments. + */ + if (lead_len == 0 && ptr[0] == '#') + { + int was_backslashed = FALSE; + + while ((ptr[0] == '#' || was_backslashed) && + curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) + { + if (*ptr && ptr[STRLEN(ptr) - 1] == '\\') + was_backslashed = TRUE; + else + was_backslashed = FALSE; + ptr = ml_get(++curwin->w_cursor.lnum); + } + if (was_backslashed) + newindent = 0; /* Got to end of file */ + else + newindent = get_indent(); + } + p = skipwhite(ptr); + if (*p == '}') /* if line starts with '}': do indent */ + did_si = TRUE; + else /* can delete indent when '{' typed */ + can_si_back = TRUE; + } + curwin->w_cursor = old_cursor; + } + if (curbuf->b_p_si) + can_si = TRUE; +#endif /* SMARTINDENT */ + + did_ai = TRUE; + } + + /* + * Find out if the current line starts with a comment leader. + * This may then be inserted in front of the new line. + */ + lead_len = get_leader_len(saved_line, &lead_flags); + if (lead_len > 0) + { + char_u *lead_repl = NULL; /* replaces comment leader */ + int lead_repl_len = 0; /* length of *lead_repl */ + char_u lead_middle[COM_MAX_LEN]; /* middle-comment string */ + char_u lead_end[COM_MAX_LEN]; /* end-comment string */ + char_u *comment_end = NULL; /* where lead_end has been found */ + int extra_space = FALSE; /* append extra space */ + int current_flag; + + /* + * If the comment leader has the start, middle or end flag, it may not + * be used or may be replaced with the middle leader. + */ + for (p = lead_flags; *p && *p != ':'; ++p) + { + if (*p == COM_START || *p == COM_MIDDLE) + { + current_flag = *p; + if (*p == COM_START) + { + /* + * Doing "O" on a start of comment does not insert leader. + */ + if (dir == BACKWARD) + { + lead_len = 0; + break; + } + + /* find start of middle part */ + (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ","); + } + + /* + * Isolate the strings of the middle and end leader. + */ + while (*p && p[-1] != ':') /* find end of middle flags */ + ++p; + (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ","); + while (*p && p[-1] != ':') /* find end of end flags */ + ++p; + (void)copy_option_part(&p, lead_end, COM_MAX_LEN, ","); + + /* + * If the end of the comment is in the same line, don't use + * the comment leader. + */ + if (dir == FORWARD) + { + n = STRLEN(lead_end); + for (p = saved_line + lead_len; *p; ++p) + if (STRNCMP(p, lead_end, n) == 0) + { + comment_end = p; + lead_len = 0; + break; + } + } + + /* + * Doing "o" on a start of comment inserts the middle leader. + */ + if (lead_len) + { + if (current_flag == COM_START) + { + lead_repl = lead_middle; + lead_repl_len = STRLEN(lead_middle); + } + + /* + * If we have hit RETURN immediately after the start + * comment leader, then put a space after the middle + * comment leader on the next line. + */ + if (!vim_iswhite(saved_line[lead_len - 1]) && + ((p_extra != NULL && + (int)curwin->w_cursor.col == lead_len) || + (p_extra == NULL && saved_line[lead_len] == NUL))) + extra_space = TRUE; + } + break; + } + if (*p == COM_END) + { + /* + * Doing "o" on the end of a comment does not insert leader. + * Remember where the end is, might want to use it to find the + * start (for C-comments). + */ + if (dir == FORWARD) + { + comment_end = skipwhite(saved_line); + lead_len = 0; + break; + } + + /* + * Doing "O" on the end of a comment inserts the middle leader. + * Find the string for the middle leader, searching backwards. + */ + while (p > curbuf->b_p_com && *p != ',') + --p; + for (lead_repl = p; lead_repl > curbuf->b_p_com && + lead_repl[-1] != ':'; --lead_repl) + ; + lead_repl_len = p - lead_repl; + break; + } + if (*p == COM_FIRST) + { + /* + * Comment leader for first line only: Don't repeat leader + * when using "O", blank out leader when using "o". + */ + if (dir == BACKWARD) + lead_len = 0; + else + { + lead_repl = (char_u *)""; + lead_repl_len = 0; + } + break; + } + } + if (lead_len) + { + /* allocate buffer (may concatenate p_exta later) */ + leader = alloc(lead_len + lead_repl_len + extra_space + + extra_len + 1); + allocated = leader; /* remember to free it later */ + + if (leader == NULL) + lead_len = 0; + else + { + STRNCPY(leader, saved_line, lead_len); + leader[lead_len] = NUL; + + /* + * Replace leader with lead_repl, right or left adjusted + */ + if (lead_repl != NULL) + { + for (p = lead_flags; *p && *p != ':'; ++p) + if (*p == COM_RIGHT || *p == COM_LEFT) + break; + if (*p == COM_RIGHT) /* right adjusted leader */ + { + /* find last non-white in the leader to line up with */ + for (p = leader + lead_len - 1; p > leader && + vim_iswhite(*p); --p) + ; + + ++p; + if (p < leader + lead_repl_len) + p = leader; + else + p -= lead_repl_len; + vim_memmove(p, lead_repl, (size_t)lead_repl_len); + if (p + lead_repl_len > leader + lead_len) + p[lead_repl_len] = NUL; + + /* blank-out any other chars from the old leader. */ + while (--p >= leader) + if (!vim_iswhite(*p)) + *p = ' '; + } + else /* left adjusted leader */ + { + p = skipwhite(leader); + vim_memmove(p, lead_repl, (size_t)lead_repl_len); + + /* blank-out any other chars from the old leader. */ + for (p += lead_repl_len; p < leader + lead_len; ++p) + if (!vim_iswhite(*p)) + *p = ' '; + *p = NUL; + } + + /* Recompute the indent, it may have changed. */ + if (curbuf->b_p_ai +#ifdef SMARTINDENT + || curbuf->b_p_si +#endif + ) + newindent = get_indent_str(leader); + } + + lead_len = STRLEN(leader); + if (extra_space) + { + leader[lead_len++] = ' '; + leader[lead_len] = NUL; + } + + newcol = lead_len; + + /* + * if a new indent will be set below, remove the indent that + * is in the comment leader + */ + if (newindent +#ifdef SMARTINDENT + || did_si +#endif + ) + { + while (lead_len && vim_iswhite(*leader)) + { + --lead_len; + --newcol; + ++leader; + } + } + + } +#ifdef SMARTINDENT + did_si = can_si = FALSE; +#endif + } + else if (comment_end != NULL) + { + /* + * We have finished a comment, so we don't use the leader. + * If this was a C-comment and 'ai' or 'si' is set do a normal + * indent to align with the line containing the start of the + * comment. + */ + if (comment_end[0] == '*' && comment_end[1] == '/' && + (curbuf->b_p_ai +#ifdef SMARTINDENT + || curbuf->b_p_si +#endif + )) + { + old_cursor = curwin->w_cursor; + curwin->w_cursor.col = comment_end - saved_line; + if ((pos = findmatch(NUL)) != NULL) + { + curwin->w_cursor.lnum = pos->lnum; + newindent = get_indent(); + } + curwin->w_cursor = old_cursor; + } + } + } + + /* (State == INSERT || State == REPLACE), only when dir == FORWARD */ + if (p_extra != NULL) + { + *p_extra = saved_char; /* restore char that NUL replaced */ + + /* + * When 'ai' set or "del_spaces" TRUE, skip to the first non-blank. + * + * When in REPLACE mode, put the deleted blanks on the replace + * stack, followed by a NUL, so they can be put back when + * a BS is entered. + */ + if (State == REPLACE) + replace_push(NUL); /* end of extra blanks */ + if (curbuf->b_p_ai || del_spaces) + { + while (*p_extra == ' ' || *p_extra == '\t') + { + if (State == REPLACE) + replace_push(*p_extra); + ++p_extra; + } + } + if (*p_extra != NUL) + did_ai = FALSE; /* append some text, don't trucate now */ + } + + if (p_extra == NULL) + p_extra = (char_u *)""; /* append empty line */ + + /* concatenate leader and p_extra, if there is a leader */ + if (lead_len) + { + STRCAT(leader, p_extra); + p_extra = leader; + } + + old_cursor = curwin->w_cursor; + if (dir == BACKWARD) + --curwin->w_cursor.lnum; + if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_t)0, FALSE) == FAIL) + goto theend; + mark_adjust(curwin->w_cursor.lnum + 1, MAXLNUM, 1L, 0L); + if (newindent +#ifdef SMARTINDENT + || did_si +#endif + ) + { + ++curwin->w_cursor.lnum; +#ifdef SMARTINDENT + if (did_si) + { + if (p_sr) + newindent -= newindent % (int)curbuf->b_p_sw; + newindent += (int)curbuf->b_p_sw; + } +#endif + set_indent(newindent, FALSE); + /* + * In REPLACE mode the new indent must be put on + * the replace stack for when it is deleted with BS + */ + if (State == REPLACE) + for (n = 0; n < (int)curwin->w_cursor.col; ++n) + replace_push(NUL); + newcol += curwin->w_cursor.col; +#ifdef SMARTINDENT + if (no_si) + did_si = FALSE; +#endif + } + /* + * In REPLACE mode the extra leader must be put on the replace stack for + * when it is deleted with BS. + */ + if (State == REPLACE) + while (lead_len-- > 0) + replace_push(NUL); + + curwin->w_cursor = old_cursor; + + if (dir == FORWARD) + { + if (redraw) /* want to know the old number of screen lines */ + { + old_plines = plines(curwin->w_cursor.lnum); + new_plines = old_plines; + } + if (trunc_line || State == INSERT || State == REPLACE) + { + if (trunc_line) + { + /* find start of trailing white space */ + for (n = STRLEN(saved_line); n > 0 && + vim_iswhite(saved_line[n - 1]); --n) + ; + saved_line[n] = NUL; + } + else /* truncate current line at cursor */ + *(saved_line + curwin->w_cursor.col) = NUL; + ml_replace(curwin->w_cursor.lnum, saved_line, FALSE); + saved_line = NULL; + new_plines = plines(curwin->w_cursor.lnum); + } + + /* + * Get the cursor to the start of the line, so that 'curwin->w_row' + * gets set to the right physical line number for the stuff that + * follows... + */ + curwin->w_cursor.col = 0; + + if (redraw) + { + /* + * Call cursupdate() to compute w_row. + * But we don't want it to update the srceen. + */ + ++RedrawingDisabled; + cursupdate(); + --RedrawingDisabled; + + /* + * If we're doing an open on the last logical line, then go ahead + * and scroll the screen up. Otherwise, just insert a blank line + * at the right place if the number of screen lines changed. + * We use calls to plines() in case the cursor is resting on a + * long line, we want to know the row below the line. + */ + n = curwin->w_row + new_plines; + if (n == curwin->w_winpos + curwin->w_height) + scrollup(1L); + else + win_ins_lines(curwin, n, + plines(curwin->w_cursor.lnum + 1) + new_plines - old_plines, + TRUE, TRUE); + } + + /* + * Put the cursor on the new line. Careful: the cursupdate() and + * scrollup() above may have moved w_cursor, we must use old_cursor. + */ + curwin->w_cursor.lnum = old_cursor.lnum + 1; + } + else if (redraw) /* insert physical line above current line */ + win_ins_lines(curwin, curwin->w_row, 1, TRUE, TRUE); + + curwin->w_cursor.col = newcol; + +#ifdef LISPINDENT + /* + * May do lisp indenting. + */ + if (leader == NULL && curbuf->b_p_lisp && curbuf->b_p_ai) + fixthisline(get_lisp_indent); +#endif +#ifdef CINDENT + /* + * May do indenting after opening a new line. + */ + if (leader == NULL && curbuf->b_p_cin && + in_cinkeys(dir == FORWARD ? KEY_OPEN_FORW : + KEY_OPEN_BACK, ' ', linewhite(curwin->w_cursor.lnum))) + fixthisline(get_c_indent); +#endif + + if (redraw) + { + updateScreen(VALID_TO_CURSCHAR); + cursupdate(); /* update curwin->w_row */ + } + CHANGED; + + retval = TRUE; /* success! */ +theend: + vim_free(saved_line); + vim_free(allocated); + return retval; +} + +/* + * get_leader_len() returns the length of the prefix of the given string + * which introduces a comment. If this string is not a comment then 0 is + * returned. + * When "flags" is non-zero, it is set to point to the flags of the recognized + * comment leader. + */ + int +get_leader_len(line, flags) + char_u *line; + char_u **flags; +{ + int i, j; + int got_com = FALSE; + int found_one; + char_u part_buf[COM_MAX_LEN]; /* buffer for one option part */ + char_u *string; /* pointer to comment string */ + char_u *list; + + if (!fo_do_comments) /* don't format comments at all */ + return 0; + + i = 0; + while (vim_iswhite(line[i])) /* leading white space is ignored */ + ++i; + + /* + * Repeat to match several nested comment strings. + */ + while (line[i]) + { + /* + * scan through the 'comments' option for a match + */ + found_one = FALSE; + for (list = curbuf->b_p_com; *list; ) + { + /* + * Get one option part into part_buf[]. Advance list to next one. + * put string at start of string. + */ + if (!got_com && flags != NULL) /* remember where flags started */ + *flags = list; + (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ","); + string = vim_strchr(part_buf, ':'); + if (string == NULL) /* missing ':', ignore this part */ + continue; + *string++ = NUL; /* isolate flags from string */ + + /* + * When already found a nested comment, only accept further + * nested comments. + */ + if (got_com && vim_strchr(part_buf, COM_NEST) == NULL) + continue; + + /* + * Line contents and string must match. + */ + for (j = 0; string[j] != NUL && string[j] == line[i + j]; ++j) + ; + if (string[j] != NUL) + continue; + + /* + * When 'b' flag used, there must be white space or an + * end-of-line after the string in the line. + */ + if (vim_strchr(part_buf, COM_BLANK) != NULL && + !vim_iswhite(line[i + j]) && line[i + j] != NUL) + continue; + + /* + * We have found a match, stop searching. + */ + i += j; + got_com = TRUE; + found_one = TRUE; + break; + } + + /* + * No match found, stop scanning. + */ + if (!found_one) + break; + + /* + * Include any trailing white space. + */ + while (vim_iswhite(line[i])) + ++i; + + /* + * If this comment doesn't nest, stop here. + */ + if (vim_strchr(part_buf, COM_NEST) == NULL) + break; + } + return (got_com ? i : 0); +} + +/* + * plines(p) - return the number of physical screen lines taken by line 'p' + */ + int +plines(p) + linenr_t p; +{ + return plines_win(curwin, p); +} + + int +plines_win(wp, p) + WIN *wp; + linenr_t p; +{ + register long col; + register char_u *s; + register int lines; + + if (!wp->w_p_wrap) + return 1; + + s = ml_get_buf(wp->w_buffer, p, FALSE); + if (*s == NUL) /* empty line */ + return 1; + + col = linetabsize(s); + + /* + * If list mode is on, then the '$' at the end of the line takes up one + * extra column. + */ + if (wp->w_p_list) + col += 1; + + /* + * If 'number' mode is on, add another 8. + */ + if (wp->w_p_nu) + col += 8; + + lines = (col + (Columns - 1)) / Columns; + if (lines <= wp->w_height) + return lines; + return (int)(wp->w_height); /* maximum length */ +} + +/* + * Count the physical lines (rows) for the lines "first" to "last" inclusive. + */ + int +plines_m(first, last) + linenr_t first, last; +{ + return plines_m_win(curwin, first, last); +} + + int +plines_m_win(wp, first, last) + WIN *wp; + linenr_t first, last; +{ + int count = 0; + + while (first <= last) + count += plines_win(wp, first++); + return (count); +} + +/* + * Insert or replace a single character at the cursor position. + * When in REPLACE mode, replace any existing character. + */ + void +ins_char(c) + int c; +{ + register char_u *p; + char_u *newp; + char_u *oldp; + int oldlen; + int extra; + colnr_t col = curwin->w_cursor.col; + linenr_t lnum = curwin->w_cursor.lnum; + + oldp = ml_get(lnum); + oldlen = STRLEN(oldp) + 1; + + if (State != REPLACE || *(oldp + col) == NUL) + extra = 1; + else + extra = 0; + + /* + * a character has to be put on the replace stack if there is a + * character that is replaced, so it can be put back when BS is used. + * Otherwise a 0 is put on the stack, indicating that a new character + * was inserted, which can be deleted when BS is used. + */ + if (State == REPLACE) + replace_push(!extra ? *(oldp + col) : 0); + newp = alloc_check((unsigned)(oldlen + extra)); + if (newp == NULL) + return; + vim_memmove(newp, oldp, (size_t)col); + p = newp + col; + vim_memmove(p + extra, oldp + col, (size_t)(oldlen - col)); + *p = c; + ml_replace(lnum, newp, FALSE); + + /* + * If we're in insert or replace mode and 'showmatch' is set, then check for + * right parens and braces. If there isn't a match, then beep. If there + * is a match AND it's on the screen, then flash to it briefly. If it + * isn't on the screen, don't do anything. + */ +#ifdef RIGHTLEFT + if (p_sm && (State & INSERT) && + ((!curwin->w_p_rl && (c == ')' || c == '}' || c == ']')) || + (curwin->w_p_rl && (c == '(' || c == '{' || c == '[')))) +#else + if (p_sm && (State & INSERT) && (c == ')' || c == '}' || c == ']')) +#endif + showmatch(); + +#ifdef RIGHTLEFT + if (!p_ri || State == REPLACE) /* normal insert: cursor right */ +#endif + ++curwin->w_cursor.col; + CHANGED; +} + +/* + * Insert a string at the cursor position. + * Note: Nothing special for replace mode. + */ + void +ins_str(s) + char_u *s; +{ + register char_u *oldp, *newp; + register int newlen = STRLEN(s); + int oldlen; + colnr_t col = curwin->w_cursor.col; + linenr_t lnum = curwin->w_cursor.lnum; + + oldp = ml_get(lnum); + oldlen = STRLEN(oldp); + + newp = alloc_check((unsigned)(oldlen + newlen + 1)); + if (newp == NULL) + return; + vim_memmove(newp, oldp, (size_t)col); + vim_memmove(newp + col, s, (size_t)newlen); + vim_memmove(newp + col + newlen, oldp + col, (size_t)(oldlen - col + 1)); + ml_replace(lnum, newp, FALSE); + curwin->w_cursor.col += newlen; + CHANGED; +} + +/* + * delete one character under the cursor + * + * return FAIL for failure, OK otherwise + */ + int +delchar(fixpos) + int fixpos; /* if TRUE fix the cursor position when done */ +{ + char_u *oldp, *newp; + colnr_t oldlen; + linenr_t lnum = curwin->w_cursor.lnum; + colnr_t col = curwin->w_cursor.col; + int was_alloced; + + oldp = ml_get(lnum); + oldlen = STRLEN(oldp); + + if (col >= oldlen) /* can't do anything (happens with replace mode) */ + return FAIL; + +/* + * If the old line has been allocated the deletion can be done in the + * existing line. Otherwise a new line has to be allocated + */ + was_alloced = ml_line_alloced(); /* check if oldp was allocated */ + if (was_alloced) + newp = oldp; /* use same allocated memory */ + else + { + newp = alloc((unsigned)oldlen); /* need to allocated a new line */ + if (newp == NULL) + return FAIL; + vim_memmove(newp, oldp, (size_t)col); + } + vim_memmove(newp + col, oldp + col + 1, (size_t)(oldlen - col)); + if (!was_alloced) + ml_replace(lnum, newp, FALSE); + + /* + * If we just took off the last character of a non-blank line, we don't + * want to end up positioned at the NUL. + */ + if (fixpos && curwin->w_cursor.col > 0 && col == oldlen - 1) + --curwin->w_cursor.col; + + CHANGED; + return OK; +} + +/* + * Delete from cursor to end of line. + * + * return FAIL for failure, OK otherwise + */ + int +truncate_line(fixpos) + int fixpos; /* if TRUE fix the cursor position when done */ +{ + char_u *newp; + linenr_t lnum = curwin->w_cursor.lnum; + colnr_t col = curwin->w_cursor.col; + + if (col == 0) + newp = strsave((char_u *)""); + else + newp = strnsave(ml_get(lnum), col); + + if (newp == NULL) + return FAIL; + + ml_replace(lnum, newp, FALSE); + + /* + * If "fixpos" is TRUE we don't want to end up positioned at the NUL. + */ + if (fixpos && curwin->w_cursor.col > 0) + --curwin->w_cursor.col; + + CHANGED; + return OK; +} + + void +dellines(nlines, dowindow, undo) + long nlines; /* number of lines to delete */ + int dowindow; /* if true, update the window */ + int undo; /* if true, prepare for undo */ +{ + int num_plines = 0; + + if (nlines <= 0) + return; + /* + * There's no point in keeping the window updated if redrawing is disabled + * or we're deleting more than a window's worth of lines. + */ + if (RedrawingDisabled) + dowindow = FALSE; + else if (nlines > (curwin->w_height - curwin->w_row) && dowindow) + { + dowindow = FALSE; + /* flaky way to clear rest of window */ + win_del_lines(curwin, curwin->w_row, curwin->w_height, TRUE, TRUE); + } + /* save the deleted lines for undo */ + if (undo && u_savedel(curwin->w_cursor.lnum, nlines) == FAIL) + return; + + /* adjust marks for deleted lines and lines that follow */ + mark_adjust(curwin->w_cursor.lnum, curwin->w_cursor.lnum + nlines - 1, + MAXLNUM, -nlines); + + while (nlines-- > 0) + { + if (curbuf->b_ml.ml_flags & ML_EMPTY) /* nothing to delete */ + break; + + /* + * Set up to delete the correct number of physical lines on the + * window + */ + if (dowindow) + num_plines += plines(curwin->w_cursor.lnum); + + ml_delete(curwin->w_cursor.lnum, TRUE); + + CHANGED; + + /* If we delete the last line in the file, stop */ + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + { + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + break; + } + } + curwin->w_cursor.col = 0; + /* + * Delete the correct number of physical lines on the window + */ + if (dowindow && num_plines > 0) + win_del_lines(curwin, curwin->w_row, num_plines, TRUE, TRUE); +} + + int +gchar(pos) + FPOS *pos; +{ + return (int)(*(ml_get_pos(pos))); +} + + int +gchar_cursor() +{ + return (int)(*(ml_get_cursor())); +} + +/* + * Write a character at the current cursor position. + * It is directly written into the block. + */ + void +pchar_cursor(c) + int c; +{ + *(ml_get_buf(curbuf, curwin->w_cursor.lnum, TRUE) + + curwin->w_cursor.col) = c; +} + +/* + * Put *pos at end of current buffer + */ + void +goto_endofbuf(pos) + FPOS *pos; +{ + char_u *p; + + pos->lnum = curbuf->b_ml.ml_line_count; + pos->col = 0; + p = ml_get(pos->lnum); + while (*p++) + ++pos->col; +} + +/* + * When extra == 0: Return TRUE if the cursor is before or on the first + * non-blank in the line. + * When extra == 1: Return TRUE if the cursor is before the first non-blank in + * the line. + */ + int +inindent(extra) + int extra; +{ + register char_u *ptr; + register colnr_t col; + + for (col = 0, ptr = ml_get_curline(); vim_iswhite(*ptr); ++col) + ++ptr; + if (col >= curwin->w_cursor.col + extra) + return TRUE; + else + return FALSE; +} + +/* + * skipwhite: skip over ' ' and '\t'. + */ + char_u * +skipwhite(p) + register char_u *p; +{ + while (vim_iswhite(*p)) /* skip to next non-white */ + ++p; + return p; +} + +/* + * skipdigits: skip over digits; + */ + char_u * +skipdigits(p) + register char_u *p; +{ + while (isdigit(*p)) /* skip to next non-digit */ + ++p; + return p; +} + +/* + * skiptowhite: skip over text until ' ' or '\t' or NUL. + */ + char_u * +skiptowhite(p) + register char_u *p; +{ + while (*p != ' ' && *p != '\t' && *p != NUL) + ++p; + return p; +} + +/* + * skiptowhite_esc: Like skiptowhite(), but also skip escaped chars + */ + char_u * +skiptowhite_esc(p) + register char_u *p; +{ + while (*p != ' ' && *p != '\t' && *p != NUL) + { + if ((*p == '\\' || *p == Ctrl('V')) && *(p + 1) != NUL) + ++p; + ++p; + } + return p; +} + +/* + * getdigits: get a number from a string and skip over it + * + * note: you must give a pointer to a char_u pointer! + */ + + long +getdigits(pp) + char_u **pp; +{ + register char_u *p; + long retval; + + p = *pp; + retval = atol((char *)p); + p = skipdigits(p); /* skip to next non-digit */ + *pp = p; + return retval; +} + +/* + * Skip to next part of an option argument: Skip space and comma. + */ + char_u * +skip_to_option_part(p) + char_u *p; +{ + if (*p == ',') + ++p; + while (*p == ' ') + ++p; + return p; +} + + char * +plural(n) + long n; +{ + static char buf[2] = "s"; + + if (n == 1) + return &(buf[1]); + return &(buf[0]); +} + +/* + * set_Changed is called when something in the current buffer is changed + */ + void +set_Changed() +{ + if (!curbuf->b_changed) + { + change_warning(); + curbuf->b_changed = TRUE; + check_status(curbuf); + } + modified = TRUE; /* used for redrawing */ +} + +/* + * unset_Changed is called when the changed flag must be reset for buffer 'buf' + */ + void +unset_Changed(buf) + BUF *buf; +{ + if (buf->b_changed) + { + buf->b_changed = 0; + check_status(buf); + } +} + +/* + * check_status: called when the status bars for the buffer 'buf' + * need to be updated + */ + static void +check_status(buf) + BUF *buf; +{ + WIN *wp; + int i; + + i = 0; + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp->w_buffer == buf && wp->w_status_height) + { + wp->w_redr_status = TRUE; + ++i; + } + if (i) + redraw_later(NOT_VALID); +} + +/* + * If the file is readonly, give a warning message with the first change. + * Don't do this for autocommands. + * Don't use emsg(), because it flushes the macro buffer. + * If we have undone all changes b_changed will be FALSE, but b_did_warn + * will be TRUE. + */ + void +change_warning() +{ + if (curbuf->b_did_warn == FALSE && curbuf->b_changed == 0 && +#ifdef AUTOCMD + !autocmd_busy && +#endif + curbuf->b_p_ro) + { + curbuf->b_did_warn = TRUE; + MSG("Warning: Changing a readonly file"); + mch_delay(1000L, TRUE); /* give him some time to think about it */ + } +} + +/* + * Ask for a reply from the user, a 'y' or a 'n'. + * No other characters are accepted, the message is repeated until a valid + * reply is entered or CTRL-C is hit. + * If direct is TRUE, don't use vgetc but mch_inchar, don't get characters from + * any buffers but directly from the user. + * + * return the 'y' or 'n' + */ + int +ask_yesno(str, direct) + char_u *str; + int direct; +{ + int r = ' '; + char_u buf[20]; + int len = 0; + int idx = 0; + + if (exiting) /* put terminal in raw mode for this question */ + settmode(1); + while (r != 'y' && r != 'n') + { + (void)set_highlight('r'); /* same highlighting as for wait_return */ + msg_highlight = TRUE; + smsg((char_u *)"%s (y/n)?", str); + if (direct) + { + flushbuf(); + if (idx >= len) + { + len = mch_inchar(buf, 20, -1L); + idx = 0; + } + r = buf[idx++]; + } + else + r = vgetc(); + if (r == Ctrl('C') || r == ESC) + r = 'n'; + msg_outchar(r); /* show what you typed */ + flushbuf(); + } + return r; +} + +/* + * get a number from the user + */ + int +get_number() +{ + int n = 0; + int c; + + for (;;) + { + windgoto(msg_row, msg_col); + c = vgetc(); + if (isdigit(c)) + { + n = n * 10 + c - '0'; + msg_outchar(c); + } + else if (c == K_DEL || c == K_BS || c == Ctrl('H')) + { + n /= 10; + MSG_OUTSTR("\b \b"); + } + else if (c == CR || c == NL || c == Ctrl('C')) + break; + } + return n; +} + + void +msgmore(n) + long n; +{ + long pn; + + if (global_busy || /* no messages now, wait until global is finished */ + keep_msg) /* there is a message already, skip this one */ + return; + + if (n > 0) + pn = n; + else + pn = -n; + + if (pn > p_report) + { + sprintf((char *)msg_buf, "%ld %s line%s %s", + pn, n > 0 ? "more" : "fewer", plural(pn), + got_int ? "(Interrupted)" : ""); + if (msg(msg_buf)) + keep_msg = msg_buf; + } +} + +/* + * flush map and typeahead buffers and give a warning for an error + */ + void +beep_flush() +{ + flush_buffers(FALSE); + vim_beep(); +} + +/* + * give a warning for an error + */ + void +vim_beep() +{ + if (p_vb) + { +#ifdef DJGPP + ScreenVisualBell(); +#else + outstr(T_VB); +#endif + } + else + { +#if defined MSDOS || defined WIN32 /* ? gvr */ + /* + * The number of beeps outputted is reduced to avoid having to wait + * for all the beeps to finish. This is only a problem on systems + * where the beeps don't overlap. + */ + if (beep_count == 0 || beep_count == 10) + { + outchar('\007'); + beep_count = 1; + } + else + ++beep_count; +#else + outchar('\007'); +#endif + } +} + +/* + * To get the "real" home directory: + * - get value of $HOME + * For Unix: + * - go to that directory + * - do mch_dirname() to get the real name of that directory. + * This also works with mounts and links. + * Don't do this for MS-DOS, it will change the "current dir" for a drive. + */ +static char_u *homedir = NULL; + + void +init_homedir() +{ + char_u *var; + + var = vim_getenv((char_u *)"HOME"); +#if defined(OS2) || defined(MSDOS) || defined(WIN32) + /* + * Default home dir is C:/ + * Best assumption we can make in such a situation. + */ + if (var == NULL) + var = "C:/"; +#endif + if (var != NULL) + { +#ifdef UNIX + if (mch_dirname(NameBuff, MAXPATHL) == OK) + { + if (!vim_chdir((char *)var) && mch_dirname(IObuff, IOSIZE) == OK) + var = IObuff; + vim_chdir((char *)NameBuff); + } +#endif + homedir = strsave(var); + } +} + +/* + * Expand environment variable with path name. + * For Unix and OS/2 "~/" is also expanded, like $HOME. + * If anything fails no expansion is done and dst equals src. + * Note that IObuff must NOT be used as either src or dst! This is because + * vim_getenv() may use IObuff to do its expansion. + */ + void +expand_env(src, dst, dstlen) + char_u *src; /* input string e.g. "$HOME/vim.hlp" */ + char_u *dst; /* where to put the result */ + int dstlen; /* maximum length of the result */ +{ + char_u *tail; + int c; + char_u *var; + int copy_char; +#if defined(UNIX) || defined(OS2) + int mustfree; + int at_start = TRUE; +#endif + + src = skipwhite(src); + --dstlen; /* leave one char space for "\," */ + while (*src && dstlen > 0) + { + copy_char = TRUE; + if (*src == '$' +#if defined(UNIX) || defined(OS2) + || (*src == '~' && at_start) +#endif + ) + { +#if defined(UNIX) || defined(OS2) + mustfree = FALSE; + + /* + * The variable name is copied into dst temporarily, because it may + * be a string in read-only memory and a NUL needs to be inserted. + */ + if (*src == '$') /* environment var */ + { +#endif + tail = src + 1; + var = dst; + c = dstlen - 1; + while (c-- > 0 && *tail && isidchar(*tail)) +#ifdef OS2 + { /* env vars only in uppercase */ + *var++ = toupper(*tail); /* toupper() may be a macro! */ + tail++; + } +#else + *var++ = *tail++; +#endif + *var = NUL; +#if defined(OS2) || defined(MSDOS) || defined(WIN32) + /* use "C:/" when $HOME is not set */ + if (STRCMP(dst, "HOME") == 0) + var = homedir; + else +#endif + var = vim_getenv(dst); +#if defined(UNIX) || defined(OS2) + } + /* home directory */ + else if (src[1] == NUL || + vim_strchr((char_u *)"/ ,\t\n", src[1]) != NULL) + { + var = homedir; + tail = src + 1; + } + else /* user directory */ +# ifdef OS2 + { + /* cannot expand user's home directory, so don't try */ + var = NULL; + tail = ""; /* shut gcc up about "may be used uninitialized" */ + } +# else + { + /* + * Copy ~user to dst[], so we can put a NUL after it. + */ + tail = src; + var = dst; + c = dstlen - 1; + while (c-- > 0 && *tail && + isfilechar(*tail) && !ispathsep(*tail)) + *var++ = *tail++; + *var = NUL; + + /* + * If the system supports getpwnam(), use it. + * Otherwise, or if getpwnam() fails, the shell is used to + * expand ~user. This is slower and may fail if the shell + * does not support ~user (old versions of /bin/sh). + */ +# if defined(HAVE_GETPWNAM) && defined(HAVE_PWD_H) + { + struct passwd *pw; + + pw = getpwnam((char *)dst + 1); + if (pw != NULL) + var = (char_u *)pw->pw_dir; + else + var = NULL; + } + if (var == NULL) +# endif + { + var = ExpandOne(dst, NULL, 0, WILD_EXPAND_FREE); + mustfree = TRUE; + } + } +# endif /* OS2 */ +#endif /* UNIX || OS2 */ + if (var != NULL && *var != NUL && + (STRLEN(var) + STRLEN(tail) + 1 < (unsigned)dstlen)) + { + STRCPY(dst, var); + dstlen -= STRLEN(var); + dst += STRLEN(var); + /* if var[] ends in a path separator and tail[] starts + * with it, skip a character */ + if (*var && ispathsep(*(dst-1)) && ispathsep(*tail)) + ++tail; + src = tail; + copy_char = FALSE; + } +#if defined(UNIX) || defined(OS2) + if (mustfree) + vim_free(var); +#endif + } + + if (copy_char) /* copy at least one char */ + { +#if defined(UNIX) || defined(OS2) + /* + * Recogize the start of a new name, for '~'. + */ + at_start = FALSE; +#endif + if (src[0] == '\\') + { + *dst++ = *src++; + --dstlen; + } +#if defined(UNIX) || defined(OS2) + else if (src[0] == ' ' || src[0] == ',') + at_start = TRUE; +#endif + *dst++ = *src++; + --dstlen; + } + } + *dst = NUL; +} + +/* + * Replace home directory by "~/" in each space or comma separated filename in + * 'src'. If anything fails (except when out of space) dst equals src. + */ + void +home_replace(buf, src, dst, dstlen) + BUF *buf; /* when not NULL, check for help files */ + char_u *src; /* input file name */ + char_u *dst; /* where to put the result */ + int dstlen; /* maximum length of the result */ +{ + size_t dirlen = 0, envlen = 0; + size_t len; + char_u *homedir_env; + char_u *p; + + if (src == NULL) + { + *dst = NUL; + return; + } + + /* + * If the file is a help file, remove the path completely. + */ + if (buf != NULL && buf->b_help) + { + STRCPY(dst, gettail(src)); + return; + } + + /* + * We check both the value of the $HOME environment variable and the + * "real" home directory. + */ + if (homedir != NULL) + dirlen = STRLEN(homedir); + homedir_env = vim_getenv((char_u *)"HOME"); + if (homedir_env != NULL) + envlen = STRLEN(homedir_env); + + src = skipwhite(src); + while (*src && dstlen > 0) + { + /* + * Here we are at the beginning of a filename. + * First, check to see if the beginning of the filename matches + * $HOME or the "real" home directory. Check that there is a '/' + * after the match (so that if e.g. the file is "/home/pieter/bla", + * and the home directory is "/home/piet", the file does not end up + * as "~er/bla" (which would seem to indicate the file "bla" in user + * er's home directory)). + */ + p = homedir; + len = dirlen; + for (;;) + { + if (len && fnamencmp(src, p, len) == 0 && (ispathsep(src[len]) || + src[len] == ',' || src[len] == ' ' || src[len] == NUL)) + { + src += len; + if (--dstlen > 0) + *dst++ = '~'; + /* + * If it's just the home directory, make it "~/". + */ + if (!ispathsep(src[0]) && --dstlen > 0) + *dst++ = '/'; + } + if (p == homedir_env) + break; + p = homedir_env; + len = envlen; + } + + /* skip to separator: space or comma */ + while (*src && *src != ',' && *src != ' ' && --dstlen > 0) + *dst++ = *src++; + /* skip separator */ + while ((*src == ' ' || *src == ',') && --dstlen > 0) + *dst++ = *src++; + } + /* if (dstlen == 0) out of space, what to do??? */ + + *dst = NUL; +} + +/* + * Like home_replace, store the replaced string in allocated memory. + * When something fails, NULL is returned. + */ + char_u * +home_replace_save(buf, src) + BUF *buf; /* when not NULL, check for help files */ + char_u *src; /* input file name */ +{ + char_u *dst; + unsigned len; + + if (src == NULL) /* just in case */ + len = 3; + else + len = STRLEN(src) + 3; /* extra space for "~/" and trailing NUL */ + dst = alloc(len); + if (dst != NULL) + home_replace(buf, src, dst, len); + return dst; +} + +/* + * Compare two file names and return: + * FPC_SAME if they both exist and are the same file. + * FPC_DIFF if they both exist and are different files. + * FPC_NOTX if they both don't exist. + * FPC_DIFFX if one of them doesn't exist. + * For the first name environment variables are expanded + */ + int +fullpathcmp(s1, s2) + char_u *s1, *s2; +{ +#ifdef UNIX + char_u buf1[MAXPATHL]; + struct stat st1, st2; + int r1, r2; + + expand_env(s1, buf1, MAXPATHL); + r1 = stat((char *)buf1, &st1); + r2 = stat((char *)s2, &st2); + if (r1 != 0 && r2 != 0) + return FPC_NOTX; + if (r1 != 0 || r2 != 0) + return FPC_DIFFX; + if (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) + return FPC_SAME; + return FPC_DIFF; +#else + char_u *buf1 = NULL; + char_u *buf2 = NULL; + int retval = FPC_DIFF; + int r1, r2; + + if ((buf1 = alloc(MAXPATHL)) != NULL && (buf2 = alloc(MAXPATHL)) != NULL) + { + expand_env(s1, buf2, MAXPATHL); + /* + * If FullName() failed, the file probably doesn't exist. + */ + r1 = FullName(buf2, buf1, MAXPATHL, FALSE); + r2 = FullName(s2, buf2, MAXPATHL, FALSE); + if (r1 != OK && r2 != OK) + retval = FPC_NOTX; + else if (r1 != OK || r2 != OK) + retval = FPC_DIFFX; + else if (fnamecmp(buf1, buf2)) + retval = FPC_DIFF; + else + retval = FPC_SAME; + } + vim_free(buf1); + vim_free(buf2); + return retval; +#endif +} + +/* + * get the tail of a path: the file name. + */ + char_u * +gettail(fname) + char_u *fname; +{ + register char_u *p1, *p2; + + if (fname == NULL) + return (char_u *)""; + for (p1 = p2 = fname; *p2; ++p2) /* find last part of path */ + { + if (ispathsep(*p2)) + p1 = p2 + 1; + } + return p1; +} + +/* + * return TRUE if 'c' is a path separator. + */ + int +ispathsep(c) + int c; +{ +#ifdef UNIX + return (c == PATHSEP); /* UNIX has ':' inside file names */ +#else +# ifdef BACKSLASH_IN_FILENAME + return (c == ':' || c == PATHSEP || c == '\\'); +# else + return (c == ':' || c == PATHSEP); +# endif +#endif +} + +/* + * Concatenate filenames fname1 and fname2 into allocated memory. + * Only add a '/' when 'sep' is TRUE and it is neccesary. + */ + char_u * +concat_fnames(fname1, fname2, sep) + char_u *fname1; + char_u *fname2; + int sep; +{ + char_u *dest; + + dest = alloc((unsigned)(STRLEN(fname1) + STRLEN(fname2) + 2)); + if (dest != NULL) + { + STRCPY(dest, fname1); + if (sep && *dest && !ispathsep(*(dest + STRLEN(dest) - 1))) + STRCAT(dest, PATHSEPSTR); + STRCAT(dest, fname2); + } + return dest; +} + +/* + * FullName_save - Make an allocated copy of a full file name. + * Returns NULL when failed. + */ + char_u * +FullName_save(fname) + char_u *fname; +{ + char_u *buf; + char_u *new_fname = NULL; + + buf = alloc((unsigned)MAXPATHL); + if (buf != NULL) + { + if (FullName(fname, buf, MAXPATHL, FALSE) != FAIL) + new_fname = strsave(buf); + vim_free(buf); + } + return new_fname; +} + +#ifdef CINDENT + +/* + * Functions for C-indenting. + * Most of this originally comes from Eric Fischer. + */ +/* + * Below "XXX" means that this function may unlock the current line. + */ + +static int isdefault __ARGS((char_u *)); +static char_u *after_label __ARGS((char_u *l)); +static int get_indent_nolabel __ARGS((linenr_t lnum)); +static int skip_label __ARGS((linenr_t, char_u **pp, int ind_maxcomment)); +static int ispreproc __ARGS((char_u *)); +static int iscomment __ARGS((char_u *)); +static int commentorempty __ARGS((char_u *)); +static int isterminated __ARGS((char_u *)); +static int isfuncdecl __ARGS((char_u *)); +static char_u *skip_string __ARGS((char_u *p)); +static int isif __ARGS((char_u *)); +static int iselse __ARGS((char_u *)); +static int isdo __ARGS((char_u *)); +static int iswhileofdo __ARGS((char_u *, linenr_t, int)); +static FPOS *find_start_comment __ARGS((int ind_maxcomment)); +static FPOS *find_start_brace __ARGS((int)); +static FPOS *find_match_paren __ARGS((int, int)); +static int find_last_paren __ARGS((char_u *l)); +static int find_match __ARGS((int lookfor, linenr_t ourscope, + int ind_maxparen, int ind_maxcomment)); + +/* + * Recognize a label: "label:". + * Note: curwin->w_cursor must be where we are looking for the label. + */ + int +islabel(ind_maxcomment) /* XXX */ + int ind_maxcomment; +{ + char_u *s; + + s = skipwhite(ml_get_curline()); + + /* + * Exclude "default" from labels, since it should be indented + * like a switch label. + */ + + if (isdefault(s)) + return FALSE; + + if (!isidchar(*s)) /* need at least one ID character */ + return FALSE; + + while (isidchar(*s)) + s++; + + s = skipwhite(s); + + /* "::" is not a label, it's C++ */ + if (*s == ':' && s[1] != ':') + { + /* + * Only accept a label if the previous line is terminated or is a case + * label. + */ + FPOS cursor_save; + FPOS *trypos; + char_u *line; + + cursor_save = curwin->w_cursor; + while (curwin->w_cursor.lnum > 1) + { + --curwin->w_cursor.lnum; + + /* + * If we're in a comment now, skip to the start of the comment. + */ + curwin->w_cursor.col = 0; + if ((trypos = find_start_comment(ind_maxcomment)) != NULL) /* XXX */ + curwin->w_cursor = *trypos; + + line = ml_get_curline(); + if (ispreproc(line)) /* ignore #defines, #if, etc. */ + continue; + if (commentorempty(line)) + continue; + + curwin->w_cursor = cursor_save; + if (isterminated(line) || iscase(line)) + return TRUE; + return FALSE; + } + curwin->w_cursor = cursor_save; + return TRUE; /* label at start of file??? */ + } + return FALSE; +} + +/* + * Recognize a switch label: "case .*:" or "default:". + */ + int +iscase(s) + char_u *s; +{ + s = skipwhite(s); + if (STRNCMP(s, "case", 4) == 0 && !isidchar(s[4])) + { + for (s += 4; *s; ++s) + if (*s == ':') + { + if (s[1] == ':') /* skip over "::" for C++ */ + ++s; + else + return TRUE; + } + return FALSE; + } + + if (isdefault(s)) + return TRUE; + return FALSE; +} + +/* + * Recognize a "default" switch label. + */ + static int +isdefault(s) + char_u *s; +{ + return (STRNCMP(s, "default", 7) == 0 && + *(s = skipwhite(s + 7)) == ':' && + s[1] != ':'); +} + +/* + * Return a pointer to the first non-empty non-comment character after a ':'. + * Return NULL if not found. + * case 234: a = b; + * ^ + */ + static char_u * +after_label(l) + char_u *l; +{ + for ( ; *l; ++l) + if (*l == ':') + { + if (l[1] == ':') /* skip over "::" for C++ */ + ++l; + else + break; + } + if (*l == NUL) + return NULL; + l = skipwhite(l + 1); + if (commentorempty(l)) + return NULL; + return l; +} + +/* + * Get indent of line "lnum", skipping a label. + * Return 0 if there is nothing after the label. + */ + static int +get_indent_nolabel(lnum) /* XXX */ + linenr_t lnum; +{ + char_u *l; + FPOS fp; + colnr_t col; + char_u *p; + + l = ml_get(lnum); + p = after_label(l); + if (p == NULL) + return 0; + + fp.col = p - l; + fp.lnum = lnum; + getvcol(curwin, &fp, &col, NULL, NULL); + return (int)col; +} + +/* + * Find indent for line "lnum", ignoring any case or jump label. + * Also return a pointer to the text (after the label). + * label: if (asdf && asdfasdf) + * ^ + */ + static int +skip_label(lnum, pp, ind_maxcomment) + linenr_t lnum; + char_u **pp; + int ind_maxcomment; +{ + char_u *l; + int amount; + FPOS cursor_save; + + cursor_save = curwin->w_cursor; + curwin->w_cursor.lnum = lnum; + l = ml_get_curline(); + if (iscase(l) || islabel(ind_maxcomment)) /* XXX */ + { + amount = get_indent_nolabel(lnum); + l = after_label(ml_get_curline()); + if (l == NULL) /* just in case */ + l = ml_get_curline(); + } + else + { + amount = get_indent(); + l = ml_get_curline(); + } + *pp = l; + + curwin->w_cursor = cursor_save; + return amount; +} + +/* + * Recognize a preprocessor statement: Any line that starts with '#'. + */ + static int +ispreproc(s) + char_u *s; +{ + s = skipwhite(s); + if (*s == '#') + return TRUE; + return 0; +} + +/* + * Recognize the start of a C or C++ comment. + */ + static int +iscomment(p) + char_u *p; +{ + return (p[0] == '/' && (p[1] == '*' || p[1] == '/')); +} + +/* + * Recognize an empty or comment line. + */ + static int +commentorempty(s) + char_u *s; +{ + s = skipwhite(s); + if (*s == NUL || iscomment(s)) + return TRUE; + return FALSE; +} + +/* + * Recognize a line that starts with '{' or '}', or ends with ';' or '}'. + * Also consider a line terminated if it ends in ','. This is not 100% + * correct, but this mostly means we are in initializations and then it's OK. + */ + static int +isterminated(s) + char_u *s; +{ + s = skipwhite(s); + + if (*s == '{' || *s == '}') + return TRUE; + + while (*s) + { + if (iscomment(s)) /* at start of comment ignore rest of line */ + return FALSE; + s = skip_string(s); + if ((*s == ';' || *s == '{' || *s == ',') && commentorempty(s + 1)) + return TRUE; + s++; + } + return FALSE; +} + +/* + * Recognize the basic picture of a function declaration -- it needs to + * have an open paren somewhere and a close paren at the end of the line and + * no semicolons anywhere. + */ + static int +isfuncdecl(s) + char_u *s; +{ + while (*s && *s != '(' && *s != ';') + if (iscomment(s++)) + return FALSE; /* comment before () ??? */ + if (*s != '(') + return FALSE; /* ';' before any () or no '(' */ + + while (*s && *s != ';') + { + if (*s == ')' && commentorempty(s + 1)) + return TRUE; + if (iscomment(s++)) + return FALSE; /* comment between ( and ) ??? */ + } + return FALSE; +} + +/* + * Skip over a "string" and a 'c' character. + */ + static char_u * +skip_string(p) + char_u *p; +{ + int i; + + /* + * We loop, because strings may be concatenated: "date""time". + */ + for ( ; ; ++p) + { + if (p[0] == '\'') /* 'c' or '\n' or '\000' */ + { + if (!p[1]) /* ' at end of line */ + break; + i = 2; + if (p[1] == '\\') /* '\n' or '\000' */ + { + ++i; + while (isdigit(p[i - 1])) /* '\000' */ + ++i; + } + if (p[i] == '\'') /* check for trailing ' */ + { + p += i; + continue; + } + } + else if (p[0] == '"') /* start of string */ + { + for (++p; p[0]; ++p) + { + if (p[0] == '\\' && p[1]) + ++p; + else if (p[0] == '"') /* end of string */ + break; + } + continue; + } + break; /* no string found */ + } + if (!*p) + --p; /* backup from NUL */ + return p; +} + + static int +isif(p) + char_u *p; +{ + return (STRNCMP(p, "if", 2) == 0 && !isidchar(p[2])); +} + + static int +iselse(p) + char_u *p; +{ + return (STRNCMP(p, "else", 4) == 0 && !isidchar(p[4])); +} + + static int +isdo(p) + char_u *p; +{ + return (STRNCMP(p, "do", 2) == 0 && !isidchar(p[2])); +} + +/* + * Check if this is a "while" that should have a matching "do". + * We only accept a "while (condition) ;", with only white space between the + * ')' and ';'. The condition may be spread over several lines. + */ + static int +iswhileofdo(p, lnum, ind_maxparen) /* XXX */ + char_u *p; + linenr_t lnum; + int ind_maxparen; +{ + FPOS cursor_save; + FPOS *trypos; + int retval = FALSE; + + p = skipwhite(p); + if (STRNCMP(p, "while", 5) == 0 && !isidchar(p[5])) + { + cursor_save = curwin->w_cursor; + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + if ((trypos = findmatchlimit(0, 0, ind_maxparen)) != NULL) + { + p = ml_get_pos(trypos) + 1; + p = skipwhite(p); + if (*p == ';') + retval = TRUE; + } + curwin->w_cursor = cursor_save; + } + return retval; +} + +/* + * Find the start of a comment, not knowing if we are in a comment right now. + * Search starts at w_cursor.lnum and goes backwards. + */ + static FPOS * +find_start_comment(ind_maxcomment) /* XXX */ + int ind_maxcomment; +{ + FPOS *pos; + char_u *line; + char_u *p; + + if ((pos = findmatchlimit('*', FM_BACKWARD, ind_maxcomment)) == NULL) + return NULL; + + /* + * Check if the comment start we found is inside a string. + */ + line = ml_get(pos->lnum); + for (p = line; *p && (unsigned)(p - line) < pos->col; ++p) + p = skip_string(p); + if ((unsigned)(p - line) > pos->col) + return NULL; + return pos; +} + +/* + * Find the '{' at the start of the block we are in. + * Return NULL of no match found. + * Ignore a '{' that is in a comment, makes indenting the next three lines + * work. */ +/* foo() */ +/* { */ +/* } */ + + static FPOS * +find_start_brace(ind_maxcomment) /* XXX */ + int ind_maxcomment; +{ + FPOS cursor_save; + FPOS *trypos; + FPOS *pos; + static FPOS pos_copy; + + cursor_save = curwin->w_cursor; + while ((trypos = findmatchlimit('{', FM_BLOCKSTOP, 0)) != NULL) + { + pos_copy = *trypos; /* copy FPOS, next findmatch will change it */ + trypos = &pos_copy; + curwin->w_cursor = *trypos; + pos = NULL; + if (!iscomment(skipwhite(ml_get(trypos->lnum))) && + (pos = find_start_comment(ind_maxcomment)) == NULL) /* XXX */ + break; + if (pos != NULL) + curwin->w_cursor.lnum = pos->lnum; + } + curwin->w_cursor = cursor_save; + return trypos; +} + +/* + * Find the matching '(', failing if it is in a comment. + * Return NULL of no match found. + */ + static FPOS * +find_match_paren(ind_maxparen, ind_maxcomment) /* XXX */ + int ind_maxparen; + int ind_maxcomment; +{ + FPOS cursor_save; + FPOS *trypos; + static FPOS pos_copy; + + cursor_save = curwin->w_cursor; + if ((trypos = findmatchlimit('(', 0, ind_maxparen)) != NULL) + { + if (iscomment(skipwhite(ml_get(trypos->lnum)))) + trypos = NULL; + else + { + pos_copy = *trypos; /* copy trypos, findmatch will change it */ + trypos = &pos_copy; + curwin->w_cursor = *trypos; + if (find_start_comment(ind_maxcomment) != NULL) /* XXX */ + trypos = NULL; + } + } + curwin->w_cursor = cursor_save; + return trypos; +} + +/* + * Set w_cursor.col to the column number of the last ')' in line "l". + */ + static int +find_last_paren(l) + char_u *l; +{ + int i; + int retval = FALSE; + + curwin->w_cursor.col = 0; /* default is start of line */ + + for (i = 0; l[i]; i++) + { + i = skip_string(l + i) - l; /* ignore parens in quotes */ + if (l[i] == ')') + { + curwin->w_cursor.col = i; + retval = TRUE; + } + } + return retval; +} + + int +get_c_indent() +{ + /* + * spaces from a block's opening brace the prevailing indent for that + * block should be + */ + int ind_level = curbuf->b_p_sw; + + /* + * spaces from the edge of the line an open brace that's at the end of a + * line is imagined to be. + */ + int ind_open_imag = 0; + + /* + * spaces from the prevailing indent for a line that is not precededof by + * an opening brace. + */ + int ind_no_brace = 0; + + /* + * column where the first { of a function should be located + */ + int ind_first_open = 0; + + /* + * spaces from the prevailing indent a leftmost open brace should be + * located + */ + int ind_open_extra = 0; + + /* + * spaces from the matching open brace (real location for one at the left + * edge; imaginary location from one that ends a line) the matching close + * brace should be located + */ + int ind_close_extra = 0; + + /* + * spaces from the edge of the line an open brace sitting in the leftmost + * column is imagined to be + */ + int ind_open_left_imag = 0; + + /* + * spaces from the switch() indent a "case xx" label should be located + */ + int ind_case = curbuf->b_p_sw; + + /* + * spaces from the "case xx:" code after a switch() should be located + */ + int ind_case_code = curbuf->b_p_sw; + + /* + * amount K&R-style parameters should be indented + */ + int ind_param = curbuf->b_p_sw; + + /* + * amount a function type spec should be indented + */ + int ind_func_type = curbuf->b_p_sw; + + /* + * additional spaces beyond the prevailing indent a continuation line + * should be located + */ + int ind_continuation = curbuf->b_p_sw; + + /* + * spaces from the indent of the line with an unclosed parentheses + */ + int ind_unclosed = curbuf->b_p_sw * 2; + + /* + * spaces from the comment opener when there is nothing after it. + */ + int ind_in_comment = 3; + + /* + * max lines to search for an open paren + */ + int ind_maxparen = 20; + + /* + * max lines to search for an open comment + */ + int ind_maxcomment = 30; + + FPOS cur_curpos; + int amount; + int scope_amount; + int cur_amount; + colnr_t col; + char_u *theline; + char_u *linecopy; + FPOS *trypos; + FPOS our_paren_pos; + char_u *start; + int start_brace; +#define BRACE_IN_COL0 1 /* '{' is in comumn 0 */ +#define BRACE_AT_START 2 /* '{' is at start of line */ +#define BRACE_AT_END 3 /* '{' is at end of line */ + linenr_t ourscope; + char_u *l; + char_u *look; + int lookfor; +#define LOOKFOR_IF 1 +#define LOOKFOR_DO 2 +#define LOOKFOR_CASE 3 +#define LOOKFOR_ANY 4 +#define LOOKFOR_TERM 5 +#define LOOKFOR_UNTERM 6 + int whilelevel; + linenr_t lnum; + char_u *options; + int fraction = 0; /* init for GCC */ + int divider; + int n; + + for (options = curbuf->b_p_cino; *options; ) + { + l = options++; + if (*options == '-') + ++options; + n = getdigits(&options); + divider = 0; + if (*options == '.') /* ".5s" means a fraction */ + { + fraction = atol((char *)++options); + while (isdigit(*options)) + { + ++options; + if (divider) + divider *= 10; + else + divider = 10; + } + } + if (*options == 's') /* "2s" means two times 'shiftwidth' */ + { + if (n == 0 && fraction == 0) + n = curbuf->b_p_sw; /* just "s" is one 'shiftwidth' */ + else + { + n *= curbuf->b_p_sw; + if (divider) + n += (curbuf->b_p_sw * fraction + divider / 2) / divider; + } + ++options; + } + if (l[1] == '-') + n = -n; + switch (*l) + { + case '>': ind_level = n; break; + case 'e': ind_open_imag = n; break; + case 'n': ind_no_brace = n; break; + case 'f': ind_first_open = n; break; + case '{': ind_open_extra = n; break; + case '}': ind_close_extra = n; break; + case '^': ind_open_left_imag = n; break; + case ':': ind_case = n; break; + case '=': ind_case_code = n; break; + case 'p': ind_param = n; break; + case 't': ind_func_type = n; break; + case 'c': ind_in_comment = n; break; + case '+': ind_continuation = n; break; + case '(': ind_unclosed = n; break; + case ')': ind_maxparen = n; break; + case '*': ind_maxcomment = n; break; + } + } + + /* remember where the cursor was when we started */ + + cur_curpos = curwin->w_cursor; + + /* get the current contents of the line. + * This is required, because onle the most recent line obtained with + * ml_get is valid! */ + + linecopy = strsave(ml_get(cur_curpos.lnum)); + if (linecopy == NULL) + return 0; + + /* + * In insert mode and the cursor is on a ')' trunctate the line at the + * cursor position. We don't want to line up with the matching '(' when + * inserting new stuff. + */ + if ((State & INSERT) && linecopy[curwin->w_cursor.col] == ')') + linecopy[curwin->w_cursor.col] = NUL; + + theline = skipwhite(linecopy); + + /* move the cursor to the start of the line */ + + curwin->w_cursor.col = 0; + + /* + * #defines and so on always go at the left when included in 'cinkeys'. + */ + if (*theline == '#' && (*linecopy == '#' || in_cinkeys('#', ' ', TRUE))) + { + amount = 0; + } + + /* + * Is it a non-case label? Then that goes at the left margin too. + */ + else if (islabel(ind_maxcomment)) /* XXX */ + { + amount = 0; + } + + /* + * If we're inside a comment and not looking at the start of the + * comment... + */ + else if (!iscomment(theline) && + (trypos = find_start_comment(ind_maxcomment)) != NULL) /* XXX */ + { + + /* find how indented the line beginning the comment is */ + getvcol(curwin, trypos, &col, NULL, NULL); + amount = col; + + /* if our line starts with an asterisk, line up with the + * asterisk in the comment opener; otherwise, line up + * with the first character of the comment text. + */ + if (theline[0] == '*') + { + amount += 1; + } + else + { + /* + * If we are more than one line away from the comment opener, take + * the indent of the previous non-empty line. + * If we are just below the comment opener and there are any + * white characters after it line up with the text after it. + * up with them; otherwise, just use a single space. + */ + amount = -1; + for (lnum = cur_curpos.lnum - 1; lnum > trypos->lnum; --lnum) + { + if (linewhite(lnum)) /* skip blank lines */ + continue; + amount = get_indent_lnum(lnum); /* XXX */ + break; + } + if (amount == -1) /* use the comment opener */ + { + start = ml_get(trypos->lnum); + look = start + trypos->col + 2; /* skip / and * */ + if (*look) /* if something after it */ + trypos->col = skipwhite(look) - start; + getvcol(curwin, trypos, &col, NULL, NULL); + amount = col; + if (!*look) + amount += ind_in_comment; + } + } + } + + /* + * Are we inside parentheses? + */ /* XXX */ + else if ((trypos = find_match_paren(ind_maxparen, ind_maxcomment)) != NULL) + { + /* + * If the matching paren is more than one line away, use the indent of + * a previous non-empty line that matches the same paren. + */ + amount = -1; + our_paren_pos = *trypos; + if (theline[0] != ')') + { + for (lnum = cur_curpos.lnum - 1; lnum > our_paren_pos.lnum; --lnum) + { + l = skipwhite(ml_get(lnum)); + if (commentorempty(l)) /* skip comment lines */ + continue; + if (ispreproc(l)) /* ignore #defines, #if, etc. */ + continue; + curwin->w_cursor.lnum = lnum; + /* XXX */ + if ((trypos = find_match_paren(ind_maxparen, + ind_maxcomment)) != NULL && + trypos->lnum == our_paren_pos.lnum && + trypos->col == our_paren_pos.col) + { + amount = get_indent_lnum(lnum); /* XXX */ + break; + } + } + } + + /* + * Line up with line where the matching paren is. + * If the line starts with a '(' or the indent for unclosed + * parentheses is zero, line up with the unclosed parentheses. + */ + if (amount == -1) + { + amount = skip_label(our_paren_pos.lnum, &look, ind_maxcomment); + if (theline[0] == ')' || ind_unclosed == 0 || + *skipwhite(look) == '(') + { + + /* + * If we're looking at a close paren, line up right there; + * otherwise, line up with the next non-white character. + */ + if (theline[0] != ')') + { + col = our_paren_pos.col + 1; + look = ml_get(our_paren_pos.lnum); + while (vim_iswhite(look[col])) + col++; + if (look[col] != NUL) /* In case of trailing space */ + our_paren_pos.col = col; + else + our_paren_pos.col++; + } + + /* + * Find how indented the paren is, or the character after it if + * we did the above "if". + */ + getvcol(curwin, &our_paren_pos, &col, NULL, NULL); + amount = col; + } + else + amount += ind_unclosed; + } + } + + /* + * Are we at least inside braces, then? + */ + else if ((trypos = find_start_brace(ind_maxcomment)) != NULL) /* XXX */ + { + ourscope = trypos->lnum; + start = ml_get(ourscope); + + /* + * Now figure out how indented the line is in general. + * If the brace was at the start of the line, we use that; + * otherwise, check out the indentation of the line as + * a whole and then add the "imaginary indent" to that. + */ + look = skipwhite(start); + if (*look == '{') + { + getvcol(curwin, trypos, &col, NULL, NULL); + amount = col; + if (*start == '{') + start_brace = BRACE_IN_COL0; + else + start_brace = BRACE_AT_START; + } + else + { + /* + * that opening brace might have been on a continuation + * line. if so, find the start of the line. + */ + curwin->w_cursor.lnum = ourscope; + + /* + * position the cursor over the rightmost paren, so that + * matching it will take us back to the start of the line. + */ + lnum = ourscope; + if (find_last_paren(start) && + (trypos = find_match_paren(ind_maxparen, + ind_maxcomment)) != NULL) + lnum = trypos->lnum; + + /* + * It could have been something like + * case 1: if (asdf && + * ldfd) { + * } + */ + amount = skip_label(lnum, &l, ind_maxcomment); + + start_brace = BRACE_AT_END; + } + + /* + * if we're looking at a closing brace, that's where + * we want to be. otherwise, add the amount of room + * that an indent is supposed to be. + */ + if (theline[0] == '}') + { + /* + * they may want closing braces to line up with something + * other than the open brace. indulge them, if so. + */ + amount += ind_close_extra; + } + else + { + /* + * If we're looking at an "else", try to find an "if" + * to match it with. + * If we're looking at a "while", try to find a "do" + * to match it with. + */ + lookfor = 0; + if (iselse(theline)) + lookfor = LOOKFOR_IF; + else if (iswhileofdo(theline, cur_curpos.lnum, ind_maxparen)) + /* XXX */ + lookfor = LOOKFOR_DO; + if (lookfor) + { + curwin->w_cursor.lnum = cur_curpos.lnum; + if (find_match(lookfor, ourscope, ind_maxparen, + ind_maxcomment) == OK) + { + amount = get_indent(); /* XXX */ + goto theend; + } + } + + /* + * We get here if we are not on an "while-of-do" or "else" (or + * failed to find a matching "if"). + * Search backwards for something to line up with. + * First set amount for when we don't find anything. + */ + + /* + * if the '{' is _really_ at the left margin, use the imaginary + * location of a left-margin brace. Otherwise, correct the + * location for ind_open_extra. + */ + + if (start_brace == BRACE_IN_COL0) /* '{' is in column 0 */ + { + amount = ind_open_left_imag; + } + else + { + if (start_brace == BRACE_AT_END) /* '{' is at end of line */ + amount += ind_open_imag; + else + { + amount -= ind_open_extra; + if (amount < 0) + amount = 0; + } + } + + if (iscase(theline)) /* it's a switch() label */ + { + lookfor = LOOKFOR_CASE; /* find a previous switch() label */ + amount += ind_case; + } + else + { + lookfor = LOOKFOR_ANY; + amount += ind_level; /* ind_level from start of block */ + } + scope_amount = amount; + whilelevel = 0; + + /* + * Search backwards. If we find something we recognize, line up + * with that. + * + * if we're looking at an open brace, indent + * the usual amount relative to the conditional + * that opens the block. + */ + curwin->w_cursor = cur_curpos; + for (;;) + { + curwin->w_cursor.lnum--; + curwin->w_cursor.col = 0; + + /* + * If we went all the way back to the start of our scope, line + * up with it. + */ + if (curwin->w_cursor.lnum <= ourscope) + { + if (lookfor == LOOKFOR_UNTERM) + amount += ind_continuation; + else if (lookfor != LOOKFOR_TERM) + amount = scope_amount; + break; + } + + /* + * If we're in a comment now, skip to the start of the comment. + */ /* XXX */ + if ((trypos = find_start_comment(ind_maxcomment)) != NULL) + { + curwin->w_cursor.lnum = trypos->lnum + 1; + continue; + } + + l = ml_get_curline(); + + /* + * If this is a switch() label, may line up relative to that. + */ + if (iscase(l)) + { + /* + * case xx: + * c = 99 + <- this indent plus continuation + *-> here; + */ + if (lookfor == LOOKFOR_UNTERM) + { + amount += ind_continuation; + break; + } + + /* + * case xx: <- line up with this case + * x = 333; + * case yy: + */ + if (lookfor == LOOKFOR_CASE) + { + /* + * Check that this case label is not for another + * switch() + */ /* XXX */ + if ((trypos = find_start_brace(ind_maxcomment)) == + NULL || trypos->lnum == ourscope) + { + amount = get_indent(); /* XXX */ + break; + } + continue; + } + + n = get_indent_nolabel(curwin->w_cursor.lnum); /* XXX */ + + /* + * case xx: if (cond) <- line up with this if + * y = y + 1; + * -> s = 99; + * + * case xx: + * if (cond) <- line up with this line + * y = y + 1; + * -> s = 99; + */ + if (lookfor == LOOKFOR_TERM) + { + if (n) + amount = n; + break; + } + + /* + * case xx: x = x + 1; <- line up with this x + * -> y = y + 1; + * + * case xx: if (cond) <- line up with this if + * -> y = y + 1; + */ + if (n) + { + amount = n; + l = after_label(ml_get_curline()); + if (l != NULL && is_cinword(l)) + amount += ind_level + ind_no_brace; + break; + } + + /* + * Try to get the indent of a statement before the + * switch label. If nothing is found, line up relative + * to the switch label. + * break; <- may line up with this line + * case xx: + * -> y = 1; + */ + scope_amount = get_indent() + ind_case_code; /* XXX */ + lookfor = LOOKFOR_ANY; + continue; + } + + /* + * Looking for a switch() label, ignore other lines. + */ + if (lookfor == LOOKFOR_CASE) + continue; + + /* + * Ignore jump labels with nothing after them. + */ + if (islabel(ind_maxcomment)) + { + l = after_label(ml_get_curline()); + if (l == NULL || commentorempty(l)) + continue; + } + + /* + * Ignore #defines, #if, etc. + * Ignore comment and empty lines. + * (need to get the line again, islabel() may have unlocked it) + */ + l = ml_get_curline(); + if (ispreproc(l) || commentorempty(l)) + continue; + + /* + * What happens next depends on the line being terminated. + */ + if (!isterminated(l)) + { + /* + * if we're in the middle of a paren thing, + * go back to the line that starts it so + * we can get the right prevailing indent + * if ( foo && + * bar ) + */ + /* + * position the cursor over the rightmost paren, so that + * matching it will take us back to the start of the line. + */ + (void)find_last_paren(l); + if ((trypos = find_match_paren(ind_maxparen, + ind_maxcomment)) != NULL) + { + /* + * Check if we are on a case label now. This is + * handled above. + * case xx: if ( asdf && + * asdf) + */ + curwin->w_cursor.lnum = trypos->lnum; + l = ml_get_curline(); + if (iscase(l)) + { + ++curwin->w_cursor.lnum; + continue; + } + } + + /* + * Get indent and pointer to text for current line, + * ignoring any jump label. XXX + */ + cur_amount = skip_label(curwin->w_cursor.lnum, + &l, ind_maxcomment); + + /* + * If this is just above the line we are indenting, and it + * starts with a '{', line it up with this line. + * while (not) + * -> { + * } + */ + if (lookfor != LOOKFOR_TERM && theline[0] == '{') + { + amount = cur_amount + ind_open_extra; + break; + } + + /* + * Check if we are after an "if", "while", etc. + */ + if (is_cinword(l)) + { + /* + * Found an unterminated line after an if (), line up + * with the last one. + * if (cond) + * 100 + + * -> here; + */ + if (lookfor == LOOKFOR_UNTERM) + { + amount += ind_continuation; + break; + } + + /* + * If this is just above the line we are indenting, we + * are finished. + * while (not) + * -> here; + * Otherwise this indent can be used when the line + * before this is terminated. + * yyy; + * if (stat) + * while (not) + * xxx; + * -> here; + */ + amount = cur_amount; + if (lookfor != LOOKFOR_TERM) + { + amount += ind_level + ind_no_brace; + break; + } + + /* + * Special trick: when expecting the while () after a + * do, line up with the while() + * do + * x = 1; + * -> here + */ + l = skipwhite(ml_get_curline()); + if (isdo(l)) + { + if (whilelevel == 0) + break; + --whilelevel; + } + + /* + * When searching for a terminated line, don't use the + * one between the "if" and the "else". + */ + if (iselse(l)) + { + if (find_match(LOOKFOR_IF, ourscope, + ind_maxparen, ind_maxcomment) == FAIL) + break; + } + } + + /* + * If we're below an unterminated line that is not an + * "if" or something, we may line up with this line or + * add someting for a continuation line, depending on + * the line before this one. + */ + else + { + /* + * Found two unterminated lines on a row, line up with + * the last one. + * c = 99 + + * 100 + + * -> here; + */ + if (lookfor == LOOKFOR_UNTERM) + break; + + /* + * Found first unterminated line on a row, may line up + * with this line, remember its indent + * 100 + + * -> here; + */ + amount = cur_amount; + if (lookfor != LOOKFOR_TERM) + lookfor = LOOKFOR_UNTERM; + } + } + + /* + * Check if we are after a while (cond); + * If so: Ignore the matching "do". + */ + /* XXX */ + else if (iswhileofdo(l, curwin->w_cursor.lnum, ind_maxparen)) + { + /* + * Found an unterminated line after a while ();, line up + * with the last one. + * while (cond); + * 100 + <- line up with this one + * -> here; + */ + if (lookfor == LOOKFOR_UNTERM) + { + amount += ind_continuation; + break; + } + + if (whilelevel == 0) + { + lookfor = LOOKFOR_TERM; + amount = get_indent(); /* XXX */ + if (theline[0] == '{') + amount += ind_open_extra; + } + ++whilelevel; + } + + /* + * We are after a "normal" statement. + * If we had another statement we can stop now and use the + * indent of that other statement. + * Otherwise the indent of the current statement may be used, + * search backwards for the next "normal" statement. + */ + else + { + /* + * Found a terminated line above an unterminated line. Add + * the amount for a continuation line. + * x = 1; + * y = foo + + * -> here; + */ + if (lookfor == LOOKFOR_UNTERM) + { + amount += ind_continuation; + break; + } + + /* + * Found a terminated line above a terminated line or "if" + * etc. line. Use the amount of the line below us. + * x = 1; x = 1; + * if (asdf) y = 2; + * while (asdf) ->here; + * here; + * ->foo; + */ + if (lookfor == LOOKFOR_TERM) + { + if (whilelevel == 0) + break; + } + + /* + * First line above the one we're indenting is terminated. + * To know what needs to be done look further backward for + * a terminated line. + */ + else + { + /* + * position the cursor over the rightmost paren, so + * that matching it will take us back to the start of + * the line. Helps for: + * func(asdr, + * asdfasdf); + * here; + */ + l = ml_get_curline(); + if (find_last_paren(l) && + (trypos = find_match_paren(ind_maxparen, + ind_maxcomment)) != NULL) + { + /* + * Check if we are on a case label now. This is + * handled above. + * case xx: if ( asdf && + * asdf) + */ + curwin->w_cursor.lnum = trypos->lnum; + l = ml_get_curline(); + if (iscase(l)) + { + ++curwin->w_cursor.lnum; + continue; + } + } + + /* + * Get indent and pointer to text for current line, + * ignoring any jump label. + */ + amount = skip_label(curwin->w_cursor.lnum, + &l, ind_maxcomment); + + if (theline[0] == '{') + amount += ind_open_extra; + lookfor = LOOKFOR_TERM; + + /* + * If we're at the end of a block, skip to the start of + * that block. + */ + if (*skipwhite(l) == '}' && + (trypos = find_start_brace(ind_maxcomment)) + != NULL) /* XXX */ + curwin->w_cursor.lnum = trypos->lnum; + } + } + } + } + } + + /* + * ok -- we're not inside any sort of structure at all! + * + * this means we're at the top level, and everything should + * basically just match where the previous line is, except + * for the lines immediately following a function declaration, + * which are K&R-style parameters and need to be indented. + */ + else + { + /* + * if our line starts with an open brace, forget about any + * prevailing indent and make sure it looks like the start + * of a function + */ + + if (theline[0] == '{') + { + amount = ind_first_open; + } + + /* + * If the NEXT line is a function declaration, the current + * line needs to be indented as a function type spec. + * Don't do this if the current line looks like a comment. + */ + else if (cur_curpos.lnum < curbuf->b_ml.ml_line_count && + !commentorempty(theline) && + isfuncdecl(ml_get(cur_curpos.lnum + 1))) + { + amount = ind_func_type; + } + else + { + amount = 0; + curwin->w_cursor = cur_curpos; + + /* search backwards until we find something we recognize */ + + while (curwin->w_cursor.lnum > 1) + { + curwin->w_cursor.lnum--; + curwin->w_cursor.col = 0; + + l = ml_get_curline(); + + /* + * If we're in a comment now, skip to the start of the comment. + */ /* XXX */ + if ((trypos = find_start_comment(ind_maxcomment)) != NULL) + { + curwin->w_cursor.lnum = trypos->lnum + 1; + continue; + } + + /* + * If the line looks like a function declaration, and we're + * not in a comment, put it the left margin. + */ + if (isfuncdecl(theline)) + break; + + /* + * Skip preprocessor directives and blank lines. + */ + if (ispreproc(l)) + continue; + + if (commentorempty(l)) + continue; + + /* + * If the PREVIOUS line is a function declaration, the current + * line (and the ones that follow) needs to be indented as + * parameters. + */ + if (isfuncdecl(l)) + { + amount = ind_param; + break; + } + + /* + * Doesn't look like anything interesting -- so just + * use the indent of this line. + * + * Position the cursor over the rightmost paren, so that + * matching it will take us back to the start of the line. + */ + find_last_paren(l); + + if ((trypos = find_match_paren(ind_maxparen, + ind_maxcomment)) != NULL) + curwin->w_cursor.lnum = trypos->lnum; + amount = get_indent(); /* XXX */ + break; + } + } + } + +theend: + /* put the cursor back where it belongs */ + curwin->w_cursor = cur_curpos; + + vim_free(linecopy); + + if (amount < 0) + return 0; + return amount; +} + + static int +find_match(lookfor, ourscope, ind_maxparen, ind_maxcomment) + int lookfor; + linenr_t ourscope; + int ind_maxparen; + int ind_maxcomment; +{ + char_u *look; + FPOS *theirscope; + char_u *mightbeif; + int elselevel; + int whilelevel; + + if (lookfor == LOOKFOR_IF) + { + elselevel = 1; + whilelevel = 0; + } + else + { + elselevel = 0; + whilelevel = 1; + } + + curwin->w_cursor.col = 0; + + while (curwin->w_cursor.lnum > ourscope + 1) + { + curwin->w_cursor.lnum--; + curwin->w_cursor.col = 0; + + look = skipwhite(ml_get_curline()); + if (iselse(look) || isif(look) || isdo(look) || + iswhileofdo(look, curwin->w_cursor.lnum, ind_maxparen)) /* XXX */ + { + /* + * if we've gone outside the braces entirely, + * we must be out of scope... + */ + theirscope = find_start_brace(ind_maxcomment); /* XXX */ + if (theirscope == NULL) + break; + + /* + * and if the brace enclosing this is further + * back than the one enclosing the else, we're + * out of luck too. + */ + if (theirscope->lnum < ourscope) + break; + + /* + * and if they're enclosed in a *deeper* brace, + * then we can ignore it because it's in a + * different scope... + */ + if (theirscope->lnum > ourscope) + continue; + + /* + * if it was an "else" (that's not an "else if") + * then we need to go back to another if, so + * increment elselevel + */ + look = skipwhite(ml_get_curline()); + if (iselse(look)) + { + mightbeif = skipwhite(look + 4); + if (!isif(mightbeif)) + ++elselevel; + continue; + } + + /* + * if it was a "while" then we need to go back to + * another "do", so increment whilelevel. + */ + if (iswhileofdo(look, curwin->w_cursor.lnum, ind_maxparen))/* XXX */ + { + ++whilelevel; + continue; + } + + /* If it's an "if" decrement elselevel */ + look = skipwhite(ml_get_curline()); + if (isif(look)) + { + elselevel--; + /* + * When looking for an "if" ignore "while"s that + * get in the way. + */ + if (elselevel == 0 && lookfor == LOOKFOR_IF) + whilelevel = 0; + } + + /* If it's a "do" decrement whilelevel */ + if (isdo(look)) + whilelevel--; + + /* + * if we've used up all the elses, then + * this must be the if that we want! + * match the indent level of that if. + */ + if (elselevel <= 0 && whilelevel <= 0) + { + return OK; + } + } + } + return FAIL; +} + +#endif /* CINDENT */ + +#ifdef LISPINDENT + int +get_lisp_indent() +{ + FPOS *pos, realpos; + long amount = 0; + char_u *that; + colnr_t col; + colnr_t maybe; + colnr_t firsttry; + + + realpos = curwin->w_cursor; + curwin->w_cursor.col = 0; + + if ((pos = findmatch('(')) != NULL) + { + curwin->w_cursor.lnum = pos->lnum; + curwin->w_cursor.col = pos->col; + col = pos->col; + + that = ml_get_curline(); + maybe = get_indent(); /* XXX */ + + if (maybe == 0) + amount = 2; + else + { + while (*that && col) + { + amount += lbr_chartabsize(that, (colnr_t)amount); + col--; + that++; + } + + that++; + amount++; + firsttry = amount; + + /* + * Go to the start of the second word. + * If there is no second word, go back to firsttry. + * Also stop at a '('. + */ + + while (vim_iswhite(*that)) + { + amount += lbr_chartabsize(that, (colnr_t)amount); + that++; + } + while (*that && !vim_iswhite(*that) && *that != '(') + { + amount += lbr_chartabsize(that, (colnr_t)amount); + that++; + } + while (vim_iswhite(*that)) + { + amount += lbr_chartabsize(that, (colnr_t)amount); + that++; + } + if (! *that) + amount = firsttry; + } + } + else /* no matching '(' found, use indent of previous non-empty line */ + { + while (curwin->w_cursor.lnum > 1) + { + --curwin->w_cursor.lnum; + if (!linewhite(curwin->w_cursor.lnum)) + break; + } + amount = get_indent(); /* XXX */ + } + + curwin->w_cursor = realpos; + + if (amount < 0) + amount = 0; + return (int)amount; +} +#endif /* LISPINDENT */ + +#if defined(UNIX) || defined(WIN32) || defined(__EMX__) +/* + * Preserve files and exit. + * When called IObuff must contain a message. + */ + void +preserve_exit() +{ + BUF *buf; + +#ifdef USE_GUI + if (gui.in_use) + { + gui.dying = TRUE; + trash_output_buf(); /* trash any pending output */ + } + else +#endif + { + windgoto((int)Rows - 1, 0); + + /* + * Switch terminal mode back now, so these messages end up on the + * "normal" screen (if there are two screens). + */ + settmode(0); +#ifdef WIN32 + if (can_end_termcap_mode(FALSE) == TRUE) +#endif + stoptermcap(); + flushbuf(); + } + + outstr(IObuff); + screen_start(); /* don't know where cursor is now */ + flushbuf(); + + ml_close_notmod(); /* close all not-modified buffers */ + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + { + if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) + { + OUTSTR("Vim: preserving files...\n"); + screen_start(); /* don't know where cursor is now */ + flushbuf(); + ml_sync_all(FALSE, FALSE); /* preserve all swap files */ + break; + } + } + + ml_close_all(FALSE); /* close all memfiles, without deleting */ + + OUTSTR("Vim: Finished.\n"); + + getout(1); +} +#endif /* defined(UNIX) || defined(WIN32) || defined(__EMX__) */ + +/* + * return TRUE if "fname" exists. + */ + int +vim_fexists(fname) + char_u *fname; +{ + struct stat st; + + if (stat((char *)fname, &st)) + return FALSE; + return TRUE; +} + +/* + * Check for CTRL-C pressed, but only once in a while. + * Should be used instead of mch_breakcheck() for functions that check for + * each line in the file. Calling mch_breakcheck() each time takes too much + * time, because it can be a system call. + */ + +#ifndef BREAKCHECK_SKIP +# define BREAKCHECK_SKIP 32 +#endif + + void +line_breakcheck() +{ + static int count = 0; + + if (++count == BREAKCHECK_SKIP) + { + count = 0; + mch_breakcheck(); + } +} + +/* + * Free the list of files returned by ExpandWildCards() or other expansion + * functions. + */ + void +FreeWild(num, file) + int num; + char_u **file; +{ + if (file == NULL || num == 0) + return; +#if defined(__EMX__) && defined(__ALWAYS_HAS_TRAILING_NULL_POINTER) /* XXX */ + /* + * Is this still OK for when other functions thatn ExpandWildCards() have + * been used??? + */ + _fnexplodefree((char **)file); +#else + while (num--) + vim_free(file[num]); + vim_free(file); +#endif +} + diff --git a/usr.bin/vim/mkcmdtab.c b/usr.bin/vim/mkcmdtab.c new file mode 100644 index 00000000000..23310edf064 --- /dev/null +++ b/usr.bin/vim/mkcmdtab.c @@ -0,0 +1,114 @@ +/* $OpenBSD: mkcmdtab.c,v 1.1.1.1 1996/09/07 21:40:24 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * mkcmdtab.c: separate program that reads cmdtab.tab and produces cmdtab.h + * + * call with: mkcmdtab cmdtab.tab cmdtab.h + */ + +#include "vim.h" + +#if defined(UTS4) + int +#else + void +#endif +main(argc, argv) + int argc; + char **argv; +{ + register int c; + char buffer[100]; + int count; + int i; + FILE *ifp, *ofp; + + if (argc != 3) + { + fprintf(stderr, "Usage: mkcmdtab cmdtab.tab cmdtab.h\n"); + exit(10); + } + ifp = fopen(argv[1], "r"); + if (ifp == NULL) + { + perror(argv[1]); + exit(10); + } + ofp = fopen(argv[2], "w"); + if (ofp == NULL) + { + perror(argv[2]); + exit(10); + } + + while ((c = getc(ifp)) != '|' && c != EOF) + putc(c, ofp); + fprintf(ofp, "THIS FILE IS AUTOMATICALLY PRODUCED - DO NOT EDIT"); + while ((c = getc(ifp)) != '|' && c != EOF) + ; + while ((c = getc(ifp)) != '|' && c != EOF) + putc(c, ofp); + + count = 0; + while ((c = getc(ifp)) != '|' && c != EOF) + { + putc(c, ofp); + while ((c = getc(ifp)) != '"' && c != EOF) + putc(c, ofp); + putc(c, ofp); + + i = 0; + while ((c = getc(ifp)) != '"' && c != EOF) + { + putc(c, ofp); + buffer[i++] = c; + } + putc(c, ofp); + buffer[i] = 0; + + while ((c = getc(ifp)) != '\n' && c != EOF) + putc(c, ofp); + putc(c, ofp); + + switch (buffer[0]) + { + case '@': strcpy(buffer, "at"); + break; + case '!': strcpy(buffer, "bang"); + break; + case '<': strcpy(buffer, "lshift"); + break; + case '>': strcpy(buffer, "rshift"); + break; + case '=': strcpy(buffer, "equal"); + break; + case '&': strcpy(buffer, "and"); + break; + case '~': strcpy(buffer, "tilde"); + break; + case '#': strcpy(buffer, "pound"); + break; + } + + fprintf(ofp, "#define CMD_%s %d\n", buffer, count++); + } + + fprintf(ofp, "#define CMD_SIZE %d\n", count); + + while ((c = getc(ifp)) != '|' && c != EOF) + putc(c, ofp); + + if (c != '|') + { + fprintf(stderr, "not enough |'s\n"); + exit(1); + } + exit(0); +} diff --git a/usr.bin/vim/normal.c b/usr.bin/vim/normal.c new file mode 100644 index 00000000000..e7968e0f94c --- /dev/null +++ b/usr.bin/vim/normal.c @@ -0,0 +1,3460 @@ +/* $OpenBSD: normal.c,v 1.1.1.1 1996/09/07 21:40:25 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * Contains the main routine for processing characters in command mode. + * Communicates closely with the code in ops.c to handle the operators. + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +#undef EXTERN +#undef INIT +#define EXTERN +#define INIT(x) x +#include "ops.h" + +/* + * Generally speaking, every command in normal() should either clear any + * pending operator (with clearop()), or set the motion type variable. + */ + +/* + * If a count is given before the operator, it is saved in opnum. + */ +static linenr_t opnum = 0; +static linenr_t Prenum; /* The (optional) number before a command. */ +static int prechar = NUL; /* prepended command char */ +/* + * The visual area is remembered for reselection. + */ +static int resel_VIsual_mode = NUL; /* 'v', 'V', or Ctrl-V */ +static linenr_t resel_VIsual_line_count; /* number of lines */ +static colnr_t resel_VIsual_col; /* number of cols or end column */ + +#ifdef USE_MOUSE +static void find_start_of_word __ARGS((FPOS *)); +static void find_end_of_word __ARGS((FPOS *)); +static int get_mouse_class __ARGS((int)); +#endif +static void prep_redo __ARGS((long, int, int, int, int)); +static int checkclearop __ARGS((void)); +static int checkclearopq __ARGS((void)); +static void clearop __ARGS((void)); +static void clearopbeep __ARGS((void)); +static void del_from_showcmd __ARGS((int)); +static void do_gd __ARGS((int nchar)); + +/* + * normal + * + * Execute a command in normal mode. + * + * This is basically a big switch with the cases arranged in rough categories + * in the following order: + * + * 0. Macros (q, @) + * 1. Screen positioning commands (^U, ^D, ^F, ^B, ^E, ^Y, z) + * 2. Control commands (:, , ^L, ^G, ^^, ZZ, *, ^], ^T) + * 3. Cursor motions (G, H, M, L, l, K_RIGHT, , h, K_LEFT, ^H, k, K_UP, + * ^P, +, CR, LF, j, K_DOWN, ^N, _, |, B, b, W, w, E, e, $, ^, 0) + * 4. Searches (?, /, n, N, T, t, F, f, ,, ;, ], [, %, (, ), {, }) + * 5. Edits (., u, K_UNDO, ^R, U, r, J, p, P, ^A, ^S) + * 6. Inserts (A, a, I, i, o, O, R) + * 7. Operators (~, d, c, y, >, <, !, =, Q) + * 8. Abbreviations (x, X, D, C, s, S, Y, &) + * 9. Marks (m, ', `, ^O, ^I) + * 10. Buffer setting (") + * 11. Visual (v, V, ^V) + * 12. Suspend (^Z) + * 13. Window commands (^W) + * 14. extended commands (starting with 'g') + * 15. mouse click + * 16. scrollbar movement + * 17. The end (ESC) + */ + + void +normal() +{ + register int c; + long n = 0; /* init for GCC */ + int flag = FALSE; + int flag2 = FALSE; + int type = 0; /* type of operation */ + int dir = FORWARD; /* search direction */ + int nchar = NUL; /* next command char */ + int finish_op; + linenr_t Prenum1; + char_u *searchbuff = NULL; /* buffer for search string */ + FPOS *pos = NULL; /* init for gcc */ + char_u *ptr = NULL; + int command_busy = FALSE; + int ctrl_w = FALSE; /* got CTRL-W command */ + int old_col = 0; + int dont_adjust_op_end = FALSE; + + Prenum = 0; + /* + * If there is an operator pending, then the command we take this time + * will terminate it. Finish_op tells us to finish the operation before + * returning this time (unless the operation was cancelled). + */ + finish_op = (op_type != NOP); + + if (!finish_op && !yankbuffer) + opnum = 0; + + State = NORMAL_BUSY; + c = vgetc(); +#ifdef HAVE_LANGMAP + LANGMAP_ADJUST(c, TRUE); +#endif + if (c == NUL) + c = K_ZERO; + (void)add_to_showcmd(c, FALSE); + +getcount: + /* Pick up any leading digits and compute 'Prenum' */ + while ((c >= '1' && c <= '9') || (Prenum != 0 && (c == K_DEL || c == '0'))) + { + if (c == K_DEL) + { + Prenum /= 10; + del_from_showcmd(4); /* delete the digit and ~@% */ + } + else + Prenum = Prenum * 10 + (c - '0'); + if (Prenum < 0) /* got too large! */ + Prenum = 999999999; + c = vgetc(); +#ifdef HAVE_LANGMAP + LANGMAP_ADJUST(c, TRUE); +#endif + (void)add_to_showcmd(c, FALSE); + } + +/* + * If we got CTRL-W there may be a/another count + */ + if (c == Ctrl('W') && !ctrl_w && op_type == NOP) + { + ctrl_w = TRUE; + opnum = Prenum; /* remember first count */ + Prenum = 0; + ++no_mapping; + ++allow_keys; /* no mapping for nchar, but keys */ + c = vgetc(); /* get next character */ +#ifdef HAVE_LANGMAP + LANGMAP_ADJUST(c, TRUE); +#endif + --no_mapping; + --allow_keys; + (void)add_to_showcmd(c, FALSE); + goto getcount; /* jump back */ + } + + /* + * If we're in the middle of an operator (including after entering a yank + * buffer with ") AND we had a count before the + * operator, then that count overrides the current value of Prenum. What + * this means effectively, is that commands like "3dw" get turned into + * "d3w" which makes things fall into place pretty neatly. + * If you give a count before AND after the operator, they are multiplied. + */ + if (opnum != 0) + { + if (Prenum) + Prenum *= opnum; + else + Prenum = opnum; + opnum = 0; + } + + Prenum1 = (Prenum == 0 ? 1 : Prenum); /* Prenum often defaults to 1 */ + + /* + * Get an additional character if we need one. + * For CTRL-W we already got it when looking for a count. + */ + if (ctrl_w) + { + nchar = c; + c = Ctrl('W'); + } + else if ((op_type == NOP && vim_strchr((char_u *)"@zm\"", c) != NULL) || + (op_type == NOP && !VIsual_active && + vim_strchr((char_u *)"rZ", c) != NULL) || + vim_strchr((char_u *)"tTfF[]g'`", c) != NULL || + (c == 'q' && !Recording && !Exec_reg)) + { + ++no_mapping; + ++allow_keys; /* no mapping for nchar, but allow key codes */ + nchar = vgetc(); +#ifdef HAVE_LANGMAP + /* adjust chars > 127: tTfFr should leave lang of nchar unchanged! */ + LANGMAP_ADJUST(nchar, vim_strchr((char_u *)"tTfFr", c) == NULL); +#endif +#ifdef RIGHTLEFT + if (p_hkmap && strchr("tTfFr", c) && KeyTyped) /* Hebrew mapped char */ + nchar = hkmap(nchar); +#endif + --no_mapping; + --allow_keys; + (void)add_to_showcmd(nchar, FALSE); + } + if (p_sc) + flushbuf(); /* flush the showcmd characters onto the + * screen so we can see them while the command + * is being executed + */ + + State = NORMAL; + if (nchar == ESC) + { + clearop(); + goto normal_end; + } + msg_didout = FALSE; /* don't scroll screen up for normal command */ + msg_col = 0; + +#ifdef RIGHTLEFT + if (curwin->w_p_rl && KeyTyped) /* invert horizontal operations */ + switch (c) + { + case 'l': c = 'h'; break; + case K_RIGHT: c = K_LEFT; break; + case 'h': c = 'l'; break; + case K_LEFT: c = K_RIGHT; break; + case '>': c = '<'; break; + case '<': c = '>'; break; + } +#endif + switch (c) + { + +/* + * 0: Macros + */ + case 'q': /* (stop) recording into a named register */ + if (checkclearop()) + break; + /* command is ignored while executing a register */ + if (!Exec_reg && do_record(nchar) == FAIL) + clearopbeep(); + break; + + case '@': /* execute a named buffer */ + if (checkclearop()) + break; + while (Prenum1--) + { + if (do_execbuf(nchar, FALSE, FALSE) == FAIL) + { + clearopbeep(); + break; + } + } + break; + +/* + * 1: Screen positioning commands + */ + case Ctrl('D'): + flag = TRUE; + + case Ctrl('U'): + if ((c == Ctrl('U') && curwin->w_cursor.lnum == 1) || + (c == Ctrl('D') && curwin->w_cursor.lnum == + curbuf->b_ml.ml_line_count)) + clearopbeep(); + else + { + if (checkclearop()) + break; + halfpage(flag, Prenum); + } + break; + + case Ctrl('B'): + case K_S_UP: + case K_PAGEUP: + dir = BACKWARD; + + case Ctrl('F'): + case K_S_DOWN: + case K_PAGEDOWN: + if (checkclearop()) + break; + (void)onepage(dir, Prenum1); + break; + + case Ctrl('E'): + if (checkclearop()) + break; + scrollup(Prenum1); + if (p_so) + cursor_correct(); + /* We may have moved to another line -- webb */ + coladvance(curwin->w_curswant); + cursupdate(); + updateScreen(VALID); + break; + + case Ctrl('Y'): + if (checkclearop()) + break; + scrolldown(Prenum1); + if (p_so) + cursor_correct(); + /* We may have moved to another line -- webb */ + coladvance(curwin->w_curswant); + updateScreen(VALID); + break; + + case 'z': + if (checkclearop()) + break; + if (nchar < 0x100 && isdigit(nchar)) + { + Prenum = nchar - '0'; + for (;;) + { + ++no_mapping; + ++allow_keys; /* no mapping for nchar, but allow key codes */ + nchar = vgetc(); +#ifdef HAVE_LANGMAP + LANGMAP_ADJUST(c, TRUE); +#endif + --no_mapping; + --allow_keys; + (void)add_to_showcmd(nchar, FALSE); + if (c == K_DEL) + Prenum /= 10; + else if (nchar < 0x100 && isdigit(nchar)) + Prenum = Prenum * 10 + (nchar - '0'); + else if (nchar == CR) + { + win_setheight((int)Prenum); + break; + } + else if (nchar == 'l' || nchar == 'h' || + nchar == K_LEFT || nchar == K_RIGHT) + { + Prenum1 = Prenum ? Prenum : 1; + goto dozet; + } + else + { + clearopbeep(); + break; + } + } + op_type = NOP; + break; + } +dozet: + /* + * If line number given, set cursor, except for "zh", "zl", "ze" and + * "zs" + */ + if (vim_strchr((char_u *)"hles", nchar) == NULL && + nchar != K_LEFT && nchar != K_RIGHT && + Prenum && Prenum != curwin->w_cursor.lnum) + { + setpcmark(); + if (Prenum > curbuf->b_ml.ml_line_count) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + else + curwin->w_cursor.lnum = Prenum; + } + switch (nchar) + { + case NL: /* put curwin->w_cursor at top of screen */ + case CR: + beginline(TRUE); + /* FALLTHROUGH */ + case 't': + scroll_cursor_top(0, TRUE); + break; + + case '.': /* put curwin->w_cursor in middle of screen */ + beginline(TRUE); + /* FALLTHROUGH */ + case 'z': + scroll_cursor_halfway(TRUE); + break; + + case '-': /* put curwin->w_cursor at bottom of screen */ + beginline(TRUE); + /* FALLTHROUGH */ + case 'b': + scroll_cursor_bot(0, TRUE); + break; + + /* "zh" - scroll screen to the right */ + case 'h': + case K_LEFT: + if (!curwin->w_p_wrap) + { + colnr_t s, e; + + if ((colnr_t)Prenum1 > curwin->w_leftcol) + curwin->w_leftcol = 0; + else + curwin->w_leftcol -= (colnr_t)Prenum1; + n = curwin->w_leftcol + Columns - + (curwin->w_p_nu ? 8 : 0) - 1; + if (curwin->w_virtcol > (colnr_t)n) + coladvance((colnr_t)n); + + getvcol(curwin, &curwin->w_cursor, &s, NULL, &e); + if (e > (colnr_t)n) + coladvance(s - 1); + redraw_later(NOT_VALID); + } + break; + + /* "zl" - scroll screen to the left */ + case 'l': + case K_RIGHT: + if (!curwin->w_p_wrap) + { + colnr_t s, e; + + /* scroll the window left */ + curwin->w_leftcol += (colnr_t)Prenum1; + + /* If the cursor has moved off the screen, put it at the + * first char on the screen */ + if (curwin->w_leftcol > curwin->w_virtcol) + (void)coladvance(curwin->w_leftcol); + + /* If the start of the character under the cursor is not + * on the screen, advance the cursor one more char. If + * this fails (last char of the line) adjust the + * scrolling. */ + getvcol(curwin, &curwin->w_cursor, &s, NULL, &e); + if (s < curwin->w_leftcol) + if (coladvance(e + 1) == FAIL) + curwin->w_leftcol = s; + + redraw_later(NOT_VALID); + } + break; + + /* "zs" - scroll screen, cursor at the start */ + case 's': + if (!curwin->w_p_wrap) + { + colnr_t s; + + getvcol(curwin, &curwin->w_cursor, &s, NULL, NULL); + curwin->w_leftcol = s; + redraw_later(NOT_VALID); + } + break; + + /* "ze" - scroll screen, cursor at the end */ + case 'e': + if (!curwin->w_p_wrap) + { + colnr_t e; + + getvcol(curwin, &curwin->w_cursor, NULL, NULL, &e); + if ((long)e < Columns) + curwin->w_leftcol = 0; + else + curwin->w_leftcol = e - Columns + 1; + redraw_later(NOT_VALID); + } + break; + + case Ctrl('S'): /* ignore CTRL-S and CTRL-Q to avoid problems */ + case Ctrl('Q'): /* with terminals that use xon/xoff */ + break; + + default: + clearopbeep(); + } + updateScreen(VALID); + break; + +/* + * 2: Control commands + */ + case ':': + if (VIsual_active) + goto dooperator; + if (checkclearop()) + break; + /* + * translate "count:" into ":.,.+(count - 1)" + */ + if (Prenum) + { + stuffReadbuff((char_u *)"."); + if (Prenum > 1) + { + stuffReadbuff((char_u *)",.+"); + stuffnumReadbuff((long)Prenum - 1L); + } + } + do_cmdline(NULL, FALSE, FALSE); + break; + + case K_HELP: + case K_F1: + if (checkclearopq()) + break; + do_help((char_u *)""); + break; + + case Ctrl('L'): + if (checkclearop()) + break; + updateScreen(CLEAR); + break; + + case Ctrl('G'): + if (checkclearop()) + break; + /* print full name if count given or :cd used */ + fileinfo(did_cd | (int)Prenum, FALSE, FALSE); + + /* + * In Visual mode and "^O^G" in Insert mode, the message will be + * overwritten by the mode message. Wait a bit, until a key is hit. + */ + if ((VIsual_active || (restart_edit && p_smd)) && KeyTyped) + { + setcursor(); + flushbuf(); + mch_delay(10000L, FALSE); + } + break; + + case K_CCIRCM: /* CTRL-^, short for ":e #" */ + if (checkclearopq()) + break; + (void)buflist_getfile((int)Prenum, (linenr_t)0, GETF_SETMARK|GETF_ALT); + break; + + case 'Z': /* write, if changed, and exit */ + if (checkclearopq()) + break; + if (nchar != 'Z') + { + clearopbeep(); + break; + } + stuffReadbuff((char_u *)":x\n"); + break; + + case Ctrl(']'): /* :ta to current identifier */ + case 'K': /* run program for current identifier */ + if (VIsual_active) /* :ta to visual highlighted text */ + { + if (VIsual.lnum != curwin->w_cursor.lnum) + { + clearopbeep(); + break; + } + if (lt(curwin->w_cursor, VIsual)) + { + ptr = ml_get_pos(&curwin->w_cursor); + n = VIsual.col - curwin->w_cursor.col + 1; + } + else + { + ptr = ml_get_pos(&VIsual); + n = curwin->w_cursor.col - VIsual.col + 1; + } + end_visual_mode(); + ++RedrawingDisabled; + update_curbuf(NOT_VALID); /* update the inversion later */ + --RedrawingDisabled; + } + if (checkclearopq()) + break; + /*FALLTHROUGH*/ + + case 163: /* the pound sign, '#' for English keyboards */ + if (c == 163) + c = '#'; + /*FALLTHROUGH*/ + + case '*': /* / to current identifier or string */ + case '#': /* ? to current identifier or string */ +search_word: + if (c == 'g') + type = nchar; /* "g*" or "g#" */ + else + type = c; + if (ptr == NULL && (n = find_ident_under_cursor(&ptr, (type == '*' || + type == '#') ? FIND_IDENT|FIND_STRING : FIND_IDENT)) == 0) + { + clearop(); + break; + } + + if (Prenum) + stuffnumReadbuff(Prenum); + switch (type) + { + case '*': + stuffReadbuff((char_u *)"/"); + /* FALLTHROUGH */ + + case '#': + if (type == '#') + stuffReadbuff((char_u *)"?"); + + /* + * put cursor at start of word, makes search skip the word + * under the cursor + */ + curwin->w_cursor.col = ptr - ml_get_curline(); + + if (c != 'g' && iswordchar(*ptr)) + stuffReadbuff((char_u *)"\\<"); + no_smartcase = TRUE; /* don't use 'smartcase' now */ + break; + + case 'K': + if (*p_kp == NUL) + stuffReadbuff((char_u *)":he "); + else + { + stuffReadbuff((char_u *)":! "); + stuffReadbuff(p_kp); + stuffReadbuff((char_u *)" "); + } + break; + default: + if (curbuf->b_help) + stuffReadbuff((char_u *)":he "); + else + stuffReadbuff((char_u *)":ta "); + } + + /* + * Now grab the chars in the identifier + */ + while (n--) + { + /* put a backslash before \ and some others */ + if (*ptr == '\\' || (!(type == '*' || type == '#') && + vim_strchr(escape_chars, *ptr) != NULL)) + stuffcharReadbuff('\\'); + /* don't interpret the characters as edit commands */ + if (*ptr < ' ' || *ptr > '~') + stuffcharReadbuff(Ctrl('V')); + stuffcharReadbuff(*ptr++); + } + + if (c != 'g' && (type == '*' || type == '#') && iswordchar(ptr[-1])) + stuffReadbuff((char_u *)"\\>"); + stuffReadbuff((char_u *)"\n"); + break; + + case Ctrl('T'): /* backwards in tag stack */ + if (checkclearopq()) + break; + do_tag((char_u *)"", 2, (int)Prenum1); + break; + +/* + * Cursor motions + */ + case 'G': +goto_line: + op_motion_type = MLINE; + setpcmark(); + if (Prenum == 0 || Prenum > curbuf->b_ml.ml_line_count) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + else + curwin->w_cursor.lnum = Prenum; + beginline(MAYBE); + break; + + case 'H': + case 'M': + if (c == 'M') + { + int used = 0; + + for (n = 0; curwin->w_topline + n < curbuf->b_ml.ml_line_count; ++n) + if ((used += plines(curwin->w_topline + n)) >= + (curwin->w_height - curwin->w_empty_rows + 1) / 2) + break; + if (n && used > curwin->w_height) + --n; + } + else + n = Prenum; + op_motion_type = MLINE; + setpcmark(); + curwin->w_cursor.lnum = curwin->w_topline + n; + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + cursor_correct(); /* correct for 'so' */ + beginline(MAYBE); + break; + + case 'L': + op_motion_type = MLINE; + setpcmark(); + curwin->w_cursor.lnum = curwin->w_botline - 1; + if (Prenum >= curwin->w_cursor.lnum) + curwin->w_cursor.lnum = 1; + else + curwin->w_cursor.lnum -= Prenum; + cursor_correct(); /* correct for 'so' */ + beginline(MAYBE); + break; + + case 'l': + case K_RIGHT: + case ' ': + op_motion_type = MCHAR; + op_inclusive = FALSE; + n = Prenum1; + while (n--) + { + if (oneright() == FAIL) + { + /* space wraps to next line if 'whichwrap' bit 1 set */ + /* 'l' wraps to next line if 'whichwrap' bit 2 set */ + /* CURS_RIGHT wraps to next line if 'whichwrap' bit 3 set */ + if (((c == ' ' && vim_strchr(p_ww, 's') != NULL) || + (c == 'l' && vim_strchr(p_ww, 'l') != NULL) || + (c == K_RIGHT && vim_strchr(p_ww, '>') != NULL)) && + curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) + { + /* When deleting we also count the NL as a character. + * Set op_inclusive when last char in the line is + * included, move to next line after that */ + if ((op_type == DELETE || op_type == CHANGE) && + !op_inclusive && !lineempty(curwin->w_cursor.lnum)) + op_inclusive = TRUE; + else + { + ++curwin->w_cursor.lnum; + curwin->w_cursor.col = 0; + curwin->w_set_curswant = TRUE; + op_inclusive = FALSE; + } + continue; + } + if (op_type == NOP) + beep_flush(); + else + { + if (lineempty(curwin->w_cursor.lnum)) + clearopbeep(); + else + { + op_inclusive = TRUE; + if (n) + beep_flush(); + } + } + break; + } + } + break; + + case 'h': + case K_LEFT: + case K_BS: + case Ctrl('H'): + op_motion_type = MCHAR; + op_inclusive = FALSE; + n = Prenum1; + while (n--) + { + if (oneleft() == FAIL) + { + /* backspace and del wrap to previous line if 'whichwrap' + * bit 0 set. + * 'h' wraps to previous line if 'whichwrap' bit 2 set. + * CURS_LEFT wraps to previous line if 'whichwrap' bit 3 + * set. */ + if ( (((c == K_BS || c == Ctrl('H')) + && vim_strchr(p_ww, 'b') != NULL) || + (c == 'h' && vim_strchr(p_ww, 'h') != NULL) || + (c == K_LEFT && vim_strchr(p_ww, '<') != NULL)) && + curwin->w_cursor.lnum > 1) + { + --(curwin->w_cursor.lnum); + coladvance(MAXCOL); + curwin->w_set_curswant = TRUE; + + /* When the NL before the first char has to be deleted we + * put the cursor on the NUL after the previous line. + * This is a very special case, be careful! + * don't adjust op_end now, otherwise it won't work */ + if ((op_type == DELETE || op_type == CHANGE) && + !lineempty(curwin->w_cursor.lnum)) + { + ++curwin->w_cursor.col; + dont_adjust_op_end = TRUE; + } + continue; + } + else if (op_type != DELETE && op_type != CHANGE) + beep_flush(); + else if (Prenum1 == 1) + clearopbeep(); + break; + } + } + break; + + case '-': + flag = TRUE; + /* FALLTHROUGH */ + + case 'k': + case K_UP: + case Ctrl('P'): +normal_k: + op_motion_type = MLINE; + if (cursor_up(Prenum1) == FAIL) + clearopbeep(); + else if (flag) + beginline(TRUE); + break; + + case '+': + case CR: + flag = TRUE; + /* FALLTHROUGH */ + + case 'j': + case K_DOWN: + case Ctrl('N'): + case NL: +normal_j: + op_motion_type = MLINE; + if (cursor_down(Prenum1) == FAIL) + clearopbeep(); + else if (flag) + beginline(TRUE); + break; + + /* + * This is a strange motion command that helps make operators more + * logical. It is actually implemented, but not documented in the + * real 'vi'. This motion command actually refers to "the current + * line". Commands like "dd" and "yy" are really an alternate form of + * "d_" and "y_". It does accept a count, so "d3_" works to delete 3 + * lines. + */ + case '_': +lineop: + old_col = curwin->w_curswant; + op_motion_type = MLINE; + if (cursor_down((long)(Prenum1 - 1)) == FAIL) + clearopbeep(); + if (op_type == DELETE || op_type == LSHIFT || op_type == RSHIFT) + beginline(MAYBE); + else if (op_type != YANK) /* 'Y' does not move cursor */ + beginline(TRUE); + break; + + case K_HOME: + if ((mod_mask & MOD_MASK_CTRL)) + goto goto_line_one; + Prenum = 1; + /* FALLTHROUGH */ + + case '|': + op_motion_type = MCHAR; + op_inclusive = FALSE; + beginline(FALSE); + if (Prenum > 0) + { + coladvance((colnr_t)(Prenum - 1)); + curwin->w_curswant = (colnr_t)(Prenum - 1); + } + else + curwin->w_curswant = 0; + /* keep curswant at the column where we wanted to go, not where + we ended; differs is line is too short */ + curwin->w_set_curswant = FALSE; + break; + + /* + * Word Motions + */ + + case 'B': + type = 1; + /* FALLTHROUGH */ + + case 'b': + case K_S_LEFT: + op_motion_type = MCHAR; + op_inclusive = FALSE; + curwin->w_set_curswant = TRUE; + if (bck_word(Prenum1, type, FALSE) == FAIL) + clearopbeep(); + break; + + case 'E': + type = 1; + /* FALLTHROUGH */ + + case 'e': + op_inclusive = TRUE; + goto dowrdcmd; + + case 'W': + type = 1; + /* FALLTHROUGH */ + + case 'w': + case K_S_RIGHT: + op_inclusive = FALSE; + flag = TRUE; + /* + * This is a little strange. To match what the real vi does, we + * effectively map 'cw' to 'ce', and 'cW' to 'cE', provided that we + * are not on a space or a TAB. This seems impolite at first, but it's + * really more what we mean when we say 'cw'. + * Another strangeness: When standing on the end of a word "ce" will + * change until the end of the next wordt, but "cw" will change only + * one character! This is done by setting type to 2. + */ + if (op_type == CHANGE && (n = gchar_cursor()) != ' ' && n != TAB && + n != NUL) + { + op_inclusive = TRUE; + flag = FALSE; + flag2 = TRUE; + } + +dowrdcmd: + op_motion_type = MCHAR; + curwin->w_set_curswant = TRUE; + if (flag) + n = fwd_word(Prenum1, type, op_type != NOP); + else + n = end_word(Prenum1, type, flag2, FALSE); + if (n == FAIL) + clearopbeep(); + break; + + case K_END: + if ((mod_mask & MOD_MASK_CTRL)) + goto goto_line; + /* FALLTHROUGH */ + + case '$': + op_motion_type = MCHAR; + op_inclusive = TRUE; + curwin->w_curswant = MAXCOL; /* so we stay at the end */ + if (cursor_down((long)(Prenum1 - 1)) == FAIL) + { + clearopbeep(); + break; + } + break; + + case '^': + flag = TRUE; + /* FALLTHROUGH */ + + case '0': + op_motion_type = MCHAR; + op_inclusive = FALSE; + beginline(flag); + break; + +/* + * 4: Searches + */ + case '?': + case '/': + if ((searchbuff = getcmdline(c, Prenum1)) == NULL) + { + clearop(); + break; + } + op_motion_type = MCHAR; + op_inclusive = FALSE; + curwin->w_set_curswant = TRUE; + + n = do_search(c, searchbuff, Prenum1, + SEARCH_MARK | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG); + if (n == 0) + clearop(); + else if (n == 2) + op_motion_type = MLINE; + break; + + case 'N': + flag = SEARCH_REV; + + case 'n': + op_motion_type = MCHAR; + op_inclusive = FALSE; + curwin->w_set_curswant = TRUE; + if (!do_search(0, NULL, Prenum1, + SEARCH_MARK | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG | flag)) + clearop(); + break; + + /* + * Character searches + */ + case 'T': + dir = BACKWARD; + /* FALLTHROUGH */ + + case 't': + type = 1; + goto docsearch; + + case 'F': + dir = BACKWARD; + /* FALLTHROUGH */ + + case 'f': +docsearch: + op_motion_type = MCHAR; + if (dir == BACKWARD) + op_inclusive = FALSE; + else + op_inclusive = TRUE; + curwin->w_set_curswant = TRUE; + if (nchar >= 0x100 || !searchc(nchar, dir, type, Prenum1)) + clearopbeep(); + break; + + case ',': + flag = 1; + /* FALLTHROUGH */ + + case ';': + dir = flag; + goto docsearch; /* nchar == NUL, thus repeat previous search */ + + /* + * section or C function searches + */ + case '[': + dir = BACKWARD; + /* FALLTHROUGH */ + + case ']': + op_motion_type = MCHAR; + op_inclusive = FALSE; + + /* + * "[f" or "]f" : Edit file under the cursor (same as "gf") + */ + if (nchar == 'f') + goto gotofile; + + /* + * Find the occurence(s) of the identifier or define under cursor + * in current and included files or jump to the first occurence. + * + * search list jump + * fwd bwd fwd bwd fwd bwd + * identifier "]i" "[i" "]I" "[I" "]^I" "[^I" + * define "]d" "[d" "]D" "[D" "]^D" "[^D" + */ + if (nchar == 'i' || nchar == 'I' || nchar == Ctrl('I') || + nchar == 'd' || nchar == 'D' || nchar == Ctrl('D')) + { + int len; + + if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) + { + clearop(); + break; + } + find_pattern_in_path(ptr, len, TRUE, + Prenum == 0 ? !isupper(nchar) : FALSE, + ((nchar & 0xf) == ('d' & 0xf)) ? FIND_DEFINE : FIND_ANY, + Prenum1, + isupper(nchar) ? ACTION_SHOW_ALL : + islower(nchar) ? ACTION_SHOW : ACTION_GOTO, + c == ']' ? curwin->w_cursor.lnum : (linenr_t)1, + (linenr_t)MAXLNUM); + curwin->w_set_curswant = TRUE; + break; + } + + /* + * "[{", "[(", "]}" or "])": go to Nth unclosed '{', '(', '}' or ')' + * "[#", "]#": go to start/end of Nth innermost #if..#endif construct. + * "[/", "[*", "]/", "]*": go to Nth comment start/end. + */ + if ((c == '[' && vim_strchr((char_u *)"{(*/#", nchar) != NULL) || + (c == ']' && vim_strchr((char_u *)"})*/#", nchar) != NULL)) + { + FPOS old_pos; + FPOS new_pos; + + if (nchar == '*') + nchar = '/'; + old_pos = curwin->w_cursor; + new_pos.lnum = 0; + while (Prenum1--) + { + if ((pos = findmatchlimit(nchar, + (c == '[') ? FM_BACKWARD : FM_FORWARD, 0)) == NULL) + { + if (new_pos.lnum == 0) /* nothing found */ + clearopbeep(); + else + pos = &new_pos; /* use last one found */ + break; + } + curwin->w_cursor = *pos; + new_pos= *pos; + } + curwin->w_cursor = old_pos; + if (pos != NULL) + { + setpcmark(); + curwin->w_cursor = *pos; + curwin->w_set_curswant = TRUE; + } + break; + } + + /* + * "[[", "[]", "]]" and "][": move to start or end of function + */ + if (nchar == '[' || nchar == ']') + { + if (nchar == c) /* "]]" or "[[" */ + flag = '{'; + else + flag = '}'; /* "][" or "[]" */ + + curwin->w_set_curswant = TRUE; + /* + * Imitate strange vi behaviour: When using "]]" with an operator + * we also stop at '}'. + */ + if (!findpar(dir, Prenum1, flag, + (op_type != NOP && dir == FORWARD && flag == '{'))) + clearopbeep(); + else if (op_type == NOP) + beginline(TRUE); + break; + } + + /* + * "[p", "[P", "]P" and "]p": put with indent adjustment + */ + if (nchar == 'p' || nchar == 'P') + { + if (checkclearopq()) + break; + prep_redo(Prenum, NUL, c, nchar, NUL); + do_put((c == ']' && nchar == 'p') ? FORWARD : BACKWARD, + Prenum1, TRUE); + break; + } + +#ifdef USE_MOUSE + /* + * [ or ] followed by a middle mouse click: put selected text with + * indent adjustment. Any other button just does as usual. + */ + if (nchar >= K_LEFTMOUSE && nchar <= K_RIGHTRELEASE) + { + (void)do_mouse(nchar, (c == ']') ? FORWARD : BACKWARD, + Prenum1, TRUE); + break; + } +#endif /* USE_MOUSE */ + + /* + * end of '[' and ']': not a valid nchar + */ + clearopbeep(); + break; + + case '%': + op_inclusive = TRUE; + if (Prenum) /* {cnt}% : goto {cnt} percentage in file */ + { + if (Prenum > 100) + clearopbeep(); + else + { + op_motion_type = MLINE; + setpcmark(); + /* round up, so CTRL-G will give same value */ + curwin->w_cursor.lnum = (curbuf->b_ml.ml_line_count * + Prenum + 99) / 100; + beginline(MAYBE); + } + } + else /* % : go to matching paren */ + { + op_motion_type = MCHAR; + if ((pos = findmatch(NUL)) == NULL) + clearopbeep(); + else + { + setpcmark(); + curwin->w_cursor = *pos; + curwin->w_set_curswant = TRUE; + } + } + break; + + case '(': + dir = BACKWARD; + /* FALLTHROUGH */ + + case ')': + op_motion_type = MCHAR; + if (c == ')') + op_inclusive = FALSE; + else + op_inclusive = TRUE; + curwin->w_set_curswant = TRUE; + + if (findsent(dir, Prenum1) == FAIL) + clearopbeep(); + break; + + case '{': + dir = BACKWARD; + /* FALLTHROUGH */ + + case '}': + op_motion_type = MCHAR; + op_inclusive = FALSE; + curwin->w_set_curswant = TRUE; + if (!findpar(dir, Prenum1, NUL, FALSE)) + clearopbeep(); + break; + +/* + * 5: Edits + */ + case '.': /* redo command */ + if (checkclearopq()) + break; + /* + * if restart_edit is TRUE, the last but one command is repeated + * instead of the last command (inserting text). This is used for + * CTRL-O <.> in insert mode + */ + if (start_redo(Prenum, restart_edit && !arrow_used) == FAIL) + clearopbeep(); + break; + + case 'u': /* undo */ + if (VIsual_active || op_type == vim_strchr(opchars, 'u') - opchars + 1) + goto dooperator; + case K_UNDO: + if (checkclearopq()) + break; + u_undo((int)Prenum1); + curwin->w_set_curswant = TRUE; + break; + + case Ctrl('R'): /* undo undo */ + if (checkclearopq()) + break; + u_redo((int)Prenum1); + curwin->w_set_curswant = TRUE; + break; + + case 'U': /* Undo line */ + if (VIsual_active || op_type == vim_strchr(opchars, 'U') - opchars + 1) + goto dooperator; + if (checkclearopq()) + break; + u_undoline(); + curwin->w_set_curswant = TRUE; + break; + + case 'r': + if (VIsual_active) + { + c = 'c'; + goto dooperator; + } + if (checkclearop()) + break; + ptr = ml_get_cursor(); + /* special key or not enough characters to replace */ + if (nchar >= 0x100 || STRLEN(ptr) < (unsigned)Prenum1) + { + clearopbeep(); + break; + } + /* + * Replacing with a TAB is done by edit(), because it is complicated + * when 'expandtab' is set. + * Other characters are done below to avoid problems with things like + * CTRL-V 048 (for edit() this would be R CTRL-V 0 ESC). + */ + if (nchar == '\t' && curbuf->b_p_et) + { + prep_redo(Prenum1, NUL, 'r', '\t', NUL); + stuffnumReadbuff(Prenum1); + stuffcharReadbuff('R'); + stuffcharReadbuff('\t'); + stuffcharReadbuff(ESC); + break; + } + + if (nchar == Ctrl('V')) /* get another character */ + { + c = Ctrl('V'); + nchar = get_literal(); + } + else + c = NUL; + prep_redo(Prenum1, NUL, 'r', c, nchar); + if (u_save_cursor() == FAIL) /* save line for undo */ + break; + /* + * Replace characters by a newline. + * Strange vi behaviour: Only one newline is inserted. + * Delete the characters here. + * Insert the newline with an insert command, takes care of + * autoindent. + */ + if (c != Ctrl('V') && (nchar == '\r' || nchar == '\n')) + { + while (Prenum1--) /* delete the characters */ + delchar(FALSE); + /* replacing the last character of a line is different */ + if (curwin->w_cursor.col > 0 && gchar_cursor() == NUL) + { + --curwin->w_cursor.col; + stuffcharReadbuff('a'); + } + else + stuffcharReadbuff('i'); + stuffcharReadbuff('\r'); + stuffcharReadbuff(ESC); + } + else + { + while (Prenum1--) /* replace the characters */ + { + /* + * Replace a 'normal' character. + * Get ptr again, because u_save and/or showmatch() will have + * released the line. At the same time we let know that the + * line will be changed. + */ + ptr = ml_get_buf(curbuf, curwin->w_cursor.lnum, TRUE); + ptr[curwin->w_cursor.col] = nchar; + if (p_sm && (nchar == ')' || nchar == '}' || nchar == ']')) + showmatch(); + ++curwin->w_cursor.col; + } + --curwin->w_cursor.col; /* cursor on the last replaced char */ + } + curwin->w_set_curswant = TRUE; + CHANGED; + updateline(); + set_last_insert(nchar); + break; + + case 'J': + if (VIsual_active) /* join the visual lines */ + goto dooperator; + if (checkclearop()) + break; + if (Prenum <= 1) + Prenum = 2; /* default for join is two lines! */ + if (curwin->w_cursor.lnum + Prenum - 1 > curbuf->b_ml.ml_line_count) + { + clearopbeep(); /* beyond last line */ + break; + } + + prep_redo(Prenum, NUL, 'J', NUL, NUL); + do_do_join(Prenum, TRUE, TRUE); + break; + + case 'P': + dir = BACKWARD; + /* FALLTHROUGH */ + + case 'p': + /* + * 'P' after an operator or with Visual: Set current block. + * 'p' after an operator or with Visual: Set current paragraph. + */ + if (op_type != NOP || VIsual_active) + { + if (c == 'P') + { + if (current_block('{', Prenum1) == FAIL) + clearopbeep(); + } + else + { + if (current_par(c, Prenum1) == FAIL) + clearopbeep(); + } + curwin->w_set_curswant = TRUE; + } + else + { + prep_redo(Prenum, NUL, c, NUL, NUL); + do_put(dir, Prenum1, FALSE); + } + break; + + case Ctrl('A'): /* add to number */ + case Ctrl('X'): /* subtract from number */ + if (checkclearopq()) + break; + if (do_addsub((int)c, Prenum1) == OK) + prep_redo(Prenum1, NUL, c, NUL, NUL); + break; + +/* + * 6: Inserts + */ + case 'A': + type = 1; + /* FALLTHROUGH */ + + case 'a': + if (op_type != NOP || VIsual_active) + { + if (current_word(Prenum1, type) == FAIL) + clearopbeep(); + curwin->w_set_curswant = TRUE; + } + else + { + if (c == 'A') + { + curwin->w_set_curswant = TRUE; + while (oneright() == OK) + ; + } + + /* Works just like an 'i'nsert on the next character. */ + if (u_save_cursor() == OK) + { + if (!lineempty(curwin->w_cursor.lnum)) + inc_cursor(); + command_busy = edit(c, FALSE, Prenum1); + } + } + break; + + case 'I': + if (checkclearopq()) + break; + beginline(TRUE); + /* FALLTHROUGH */ + + case 'i': + case K_INS: +insert_command: + if (checkclearopq()) + break; + if (u_save_cursor() == OK) + command_busy = edit(c, FALSE, Prenum1); + break; + + case 'o': + if (VIsual_active) /* switch start and end of visual */ + { + Prenum = VIsual.lnum; + VIsual.lnum = curwin->w_cursor.lnum; + curwin->w_cursor.lnum = Prenum; + n = VIsual.col; + VIsual.col = curwin->w_cursor.col; + curwin->w_cursor.col = (int)n; + curwin->w_set_curswant = TRUE; + break; + } + if (checkclearop()) + break; + if (has_format_option(FO_OPEN_COMS)) + fo_do_comments = TRUE; + if (u_save(curwin->w_cursor.lnum, + (linenr_t)(curwin->w_cursor.lnum + 1)) == OK && + Opencmd(FORWARD, TRUE, FALSE)) + command_busy = edit('o', TRUE, Prenum1); + fo_do_comments = FALSE; + break; + + case 'O': + if (checkclearopq()) + break; + if (has_format_option(FO_OPEN_COMS)) + fo_do_comments = TRUE; + if (u_save((linenr_t)(curwin->w_cursor.lnum - 1), + curwin->w_cursor.lnum) == OK && Opencmd(BACKWARD, TRUE, FALSE)) + command_busy = edit('O', TRUE, Prenum1); + fo_do_comments = FALSE; + break; + + case 'R': + if (VIsual_active) + { + c = 'c'; + VIsual_mode = 'V'; + goto dooperator; + } + if (checkclearopq()) + break; + if (u_save_cursor() == OK) + command_busy = edit('R', FALSE, Prenum1); + break; + +/* + * 7: Operators + */ + case '~': /* swap case */ + /* + * if tilde is not an operator and Visual is off: swap case + * of a single character + */ + if (!p_to && !VIsual_active && + op_type != vim_strchr(opchars, '~') - opchars + 1) + { + if (checkclearopq()) + break; + if (lineempty(curwin->w_cursor.lnum)) + { + clearopbeep(); + break; + } + prep_redo(Prenum, NUL, '~', NUL, NUL); + + if (u_save_cursor() == FAIL) + break; + + for (; Prenum1 > 0; --Prenum1) + { + if (gchar_cursor() == NUL) + break; + swapchar(&curwin->w_cursor); + inc_cursor(); + } + + curwin->w_set_curswant = TRUE; + CHANGED; + updateline(); + break; + } + /*FALLTHROUGH*/ + + case 'd': + case 'c': + case 'y': + case '>': + case '<': + case '!': + case '=': + case 'Q': /* should start Ex mode */ +dooperator: + n = vim_strchr(opchars, c) - opchars + 1; + if (n == op_type) /* double operator works on lines */ + goto lineop; + if (checkclearop()) + break; + if (Prenum != 0) + opnum = Prenum; + curbuf->b_op_start = curwin->w_cursor; + op_type = (int)n; + break; + +/* + * 8: Abbreviations + */ + + /* when Visual the next commands are operators */ + case K_DEL: + c = 'x'; /* DEL key behaves like 'x' */ + case 'S': + case 'Y': + case 'D': + case 'C': + case 'x': + case 'X': + case 's': + /* + * 's' or 'S' with an operator: Operate on sentence or section. + */ + if (op_type != NOP || VIsual_active) + { + if (c == 's') /* sentence */ + { + if (current_sent(Prenum1) == FAIL) + clearopbeep(); + curwin->w_set_curswant = TRUE; + break; + } + if (c == 'S') /* block with () */ + { + if (current_block('(', Prenum1) == FAIL) + clearopbeep(); + curwin->w_set_curswant = TRUE; + break; + } + } + if (VIsual_active) + { + static char_u trans[] = "YyDdCcxdXd"; + + /* uppercase means linewise */ + if (isupper(c) && VIsual_mode != Ctrl('V')) + VIsual_mode = 'V'; + c = *(vim_strchr(trans, c) + 1); + goto dooperator; + } + + case '&': + if (checkclearopq()) + break; + if (Prenum) + stuffnumReadbuff(Prenum); + + { + static char_u *(ar[8]) = {(char_u *)"dl", (char_u *)"dh", + (char_u *)"d$", (char_u *)"c$", + (char_u *)"cl", (char_u *)"cc", + (char_u *)"yy", (char_u *)":s\r"}; + static char_u *str = (char_u *)"xXDCsSY&"; + + stuffReadbuff(ar[(int)(vim_strchr(str, c) - str)]); + } + break; + +/* + * 9: Marks + */ + + case 'm': + if (checkclearop()) + break; + if (setmark(nchar) == FAIL) + clearopbeep(); + break; + + case '\'': + flag = TRUE; + /* FALLTHROUGH */ + + case '`': + pos = getmark(nchar, (op_type == NOP)); + if (pos == (FPOS *)-1) /* jumped to other file */ + { + if (flag) + beginline(TRUE); + break; + } + +cursormark: + if (check_mark(pos) == FAIL) + clearop(); + else + { + if (c == '\'' || c == '`') + setpcmark(); + curwin->w_cursor = *pos; + if (flag) + beginline(TRUE); + } + op_motion_type = flag ? MLINE : MCHAR; + op_inclusive = FALSE; /* ignored if not MCHAR */ + curwin->w_set_curswant = TRUE; + break; + + case Ctrl('O'): /* goto older pcmark */ + Prenum1 = -Prenum1; + /* FALLTHROUGH */ + + case Ctrl('I'): /* goto newer pcmark */ + if (checkclearopq()) + break; + pos = movemark((int)Prenum1); + if (pos == (FPOS *)-1) /* jump to other file */ + { + curwin->w_set_curswant = TRUE; + break; + } + if (pos != NULL) /* can jump */ + goto cursormark; + clearopbeep(); + break; + +/* + * 10. Buffer setting + */ + case '"': + if (checkclearop()) + break; + if (nchar != NUL && is_yank_buffer(nchar, FALSE)) + { + yankbuffer = nchar; + opnum = Prenum; /* remember count before '"' */ + } + else + clearopbeep(); + break; + +/* + * 11. Visual + */ + case 'v': + case 'V': + case Ctrl('V'): + if (checkclearop()) + break; + + /* change Visual mode */ + if (VIsual_active) + { + if (VIsual_mode == c) /* stop visual mode */ + { + end_visual_mode(); + } + else /* toggle char/block mode */ + { /* or char/line mode */ + VIsual_mode = c; + showmode(); + } + update_curbuf(NOT_VALID); /* update the inversion */ + } + /* start Visual mode */ + else + { + VIsual_save = VIsual; /* keep for "gv" */ + VIsual_mode_save = VIsual_mode; + start_visual_highlight(); + if (Prenum) /* use previously selected part */ + { + if (resel_VIsual_mode == NUL) /* there is none */ + { + beep_flush(); + break; + } + VIsual = curwin->w_cursor; + VIsual_active = TRUE; +#ifdef USE_MOUSE + setmouse(); +#endif + if (p_smd) + redraw_cmdline = TRUE; /* show visual mode later */ + /* + * For V and ^V, we multiply the number of lines even if there + * was only one -- webb + */ + if (resel_VIsual_mode != 'v' || resel_VIsual_line_count > 1) + { + curwin->w_cursor.lnum += resel_VIsual_line_count * Prenum - 1; + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + } + VIsual_mode = resel_VIsual_mode; + if (VIsual_mode == 'v') + { + if (resel_VIsual_line_count <= 1) + curwin->w_cursor.col += resel_VIsual_col * Prenum - 1; + else + curwin->w_cursor.col = resel_VIsual_col; + } + if (resel_VIsual_col == MAXCOL) + { + curwin->w_curswant = MAXCOL; + coladvance(MAXCOL); + } + else if (VIsual_mode == Ctrl('V')) + { + curwin->w_curswant = curwin->w_virtcol + + resel_VIsual_col * Prenum - 1; + coladvance((colnr_t)curwin->w_curswant); + } + else + curwin->w_set_curswant = TRUE; + curs_columns(TRUE); /* recompute w_virtcol */ + update_curbuf(NOT_VALID); /* show the inversion */ + } + else + { + VIsual = curwin->w_cursor; + VIsual_mode = c; + VIsual_active = TRUE; +#ifdef USE_MOUSE + setmouse(); +#endif + if (p_smd) + redraw_cmdline = TRUE; /* show visual mode later */ + updateline(); /* start the inversion */ + } + } + break; + +/* + * 12. Suspend + */ + + case Ctrl('Z'): + clearop(); + if (VIsual_active) + end_visual_mode(); /* stop Visual */ + stuffReadbuff((char_u *)":st\r"); /* with autowrite */ + break; + +/* + * 13. Window commands + */ + + case Ctrl('W'): + if (checkclearop()) + break; + do_window(nchar, Prenum); /* everything is in window.c */ + break; + +/* + * 14. extended commands (starting with 'g') + */ + case 'g': + switch (nchar) + { + /* + * "gv": reselect the previous visual area + */ + case 'v': + if (checkclearop()) + break; + if (VIsual_active) + pos = &VIsual_save; + else + pos = &VIsual; + if (pos->lnum == 0 || pos->lnum > curbuf->b_ml.ml_line_count || + VIsual_end.lnum == 0) + beep_flush(); + else + { + FPOS tt; + int t; + + /* exchange previous and current visual area */ + if (VIsual_active) + { + tt = VIsual; + VIsual = VIsual_save; + VIsual_save = tt; + t = VIsual_mode; + VIsual_mode = VIsual_mode_save; + VIsual_mode_save = t; + tt = curwin->w_cursor; + } + curwin->w_cursor = VIsual_end; + if (VIsual_active) + VIsual_end = tt; + check_cursor(); + VIsual_active = TRUE; +#ifdef USE_MOUSE + setmouse(); +#endif + update_curbuf(NOT_VALID); + showmode(); + } + break; + + /* + * "gj" and "gk" two new funny movement keys -- up and down + * movement based on *screen* line rather than *file* line. + */ + case 'j': + case K_DOWN: + if (!curwin->w_p_wrap) + goto normal_j; + if (screengo(FORWARD, Prenum1) == FAIL) + clearopbeep(); + break; + + case 'k': + case K_UP: + if (!curwin->w_p_wrap) + goto normal_k; + if (screengo(BACKWARD, Prenum1) == FAIL) + clearopbeep(); + break; + + /* + * "g0", "g^" and "g$": Like "0", "^" and "$" but for screen lines. + */ + case '^': + flag = TRUE; + /* FALLTHROUGH */ + + case '0': + case K_HOME: + op_motion_type = MCHAR; + op_inclusive = FALSE; + if (curwin->w_p_wrap) + { + n = ((curwin->w_virtcol + (curwin->w_p_nu ? 8 : 0)) / + Columns) * Columns; + if (curwin->w_p_nu && n > 8) + n -= 8; + } + else + n = curwin->w_leftcol; + coladvance((colnr_t)n); + if (flag) + while (vim_iswhite(gchar_cursor()) && oneright() == OK) + ; + curwin->w_set_curswant = TRUE; + break; + + case '$': + case K_END: + op_motion_type = MCHAR; + op_inclusive = TRUE; + if (curwin->w_p_wrap) + { + curwin->w_curswant = MAXCOL; /* so we stay at the end */ + if (Prenum1 == 1) + { + n = ((curwin->w_virtcol + (curwin->w_p_nu ? 8 : 0)) / + Columns + 1) * Columns - 1; + if (curwin->w_p_nu && n > 8) + n -= 8; + coladvance((colnr_t)n); + } + else if (screengo(FORWARD, Prenum1 - 1) == FAIL) + clearopbeep(); + } + else + { + n = curwin->w_leftcol + Columns - 1; + if (curwin->w_p_nu) + n -= 8; + coladvance((colnr_t)n); + curwin->w_set_curswant = TRUE; + } + break; + + /* + * "g*" and "g#", like "*" and "#" but without using "\<" and "\>" + */ + case '*': + case '#': + goto search_word; + + /* + * ge and gE: go back to end of word + */ + case 'e': + case 'E': + op_motion_type = MCHAR; + curwin->w_set_curswant = TRUE; + op_inclusive = TRUE; + if (bckend_word(Prenum1, nchar == 'E', FALSE) == FAIL) + clearopbeep(); + break; + + /* + * g CTRL-G: display info about cursor position + */ + case Ctrl('G'): + cursor_pos_info(); + break; + + /* + * "gI": Start insert in column 1. + */ + case 'I': + beginline(FALSE); + goto insert_command; + + /* + * "gf": goto file, edit file under cursor + * "]f" and "[f": can also be used. + */ + case 'f': +gotofile: + ptr = file_name_at_cursor(FNAME_MESS|FNAME_HYP|FNAME_EXP); + if (ptr != NULL) + { + /* do autowrite if necessary */ + if (curbuf->b_changed && curbuf->b_nwindows <= 1 && !p_hid) + autowrite(curbuf); + setpcmark(); + (void)do_ecmd(0, ptr, NULL, NULL, p_hid, (linenr_t)0, + FALSE); + vim_free(ptr); + } + else + clearop(); + break; + + /* + * "gs": Goto sleep, but keep on checking for CTRL-C + */ + case 's': + while (Prenum1-- && !got_int) + { + mch_delay(1000L, TRUE); + mch_breakcheck(); + } + break; + + /* + * "ga": Display the ascii value of the character under the + * cursor. It is displayed in decimal, hex, and octal. -- webb + */ + case 'a': + do_ascii(); + break; + + /* + * "gg": Goto the first line in file. With a count it goes to + * that line number like for G. -- webb + */ + case 'g': +goto_line_one: + if (Prenum == 0) + Prenum = 1; + goto goto_line; + + /* + * Operater to format text: + * gq same as 'Q' operator. + * Operators to change the case of text: + * g~ Toggle the case of the text. + * gu Change text to lower case. + * gU Change text to upper case. + * --webb + */ + case 'q': + case '~': + case 'u': + case 'U': + prechar = c; + c = nchar; + goto dooperator; + + /* + * "gd": Find first occurence of pattern under the cursor in the + * current function + * "gD": idem, but in the current file. + */ + case 'd': + case 'D': + do_gd(nchar); + break; + +#ifdef USE_MOUSE + /* + * g<*Mouse> : + */ + case K_MIDDLEMOUSE: + case K_MIDDLEDRAG: + case K_MIDDLERELEASE: + case K_LEFTMOUSE: + case K_LEFTDRAG: + case K_LEFTRELEASE: + case K_RIGHTMOUSE: + case K_RIGHTDRAG: + case K_RIGHTRELEASE: + mod_mask = MOD_MASK_CTRL; + (void)do_mouse(nchar, BACKWARD, Prenum1, FALSE); + break; + + case K_IGNORE: + break; +#endif + + default: + clearopbeep(); + break; + } + break; + +/* + * 15. mouse click + */ +#ifdef USE_MOUSE + case K_MIDDLEMOUSE: + case K_MIDDLEDRAG: + case K_MIDDLERELEASE: + case K_LEFTMOUSE: + case K_LEFTDRAG: + case K_LEFTRELEASE: + case K_RIGHTMOUSE: + case K_RIGHTDRAG: + case K_RIGHTRELEASE: + (void)do_mouse(c, BACKWARD, Prenum1, FALSE); + break; + + case K_IGNORE: + break; +#endif + +#ifdef USE_GUI +/* + * 16. scrollbar movement + */ + case K_SCROLLBAR: + if (op_type != NOP) + clearopbeep(); + + /* Even if an operator was pending, we still want to scroll */ + gui_do_scroll(); + break; + + case K_HORIZ_SCROLLBAR: + if (op_type != NOP) + clearopbeep(); + + /* Even if an operator was pending, we still want to scroll */ + gui_do_horiz_scroll(); + break; +#endif + +/* + * 17. The end + */ + case ESC: + /* Don't drop through and beep if we are canceling a command: */ + if (!VIsual_active && (op_type != NOP || + opnum || Prenum || yankbuffer)) + { + clearop(); /* don't beep */ + break; + } + if (VIsual_active) + { + end_visual_mode(); /* stop Visual */ + update_curbuf(NOT_VALID); + clearop(); /* don't beep */ + break; + } + /* ESC in normal mode: beep, but don't flush buffers */ + clearop(); + vim_beep(); + break; + + default: /* not a known command */ + clearopbeep(); + break; + + } /* end of switch on command character */ + +/* + * if we didn't start or finish an operator, reset yankbuffer, unless we + * need it later. + */ + if (!finish_op && !op_type && vim_strchr((char_u *)"\"DCYSsXx.", c) == NULL) + yankbuffer = 0; + +/* + * If an operation is pending, handle it... + */ + do_pending_operator(c, nchar, finish_op, searchbuff, + &command_busy, old_col, FALSE, dont_adjust_op_end); + +normal_end: + if (op_type == NOP && yankbuffer == 0) + clear_showcmd(); + + if (restart_edit && op_type == NOP && !VIsual_active + && !command_busy && stuff_empty() && yankbuffer == 0) + (void)edit(restart_edit, FALSE, 1L); + + checkpcmark(); /* check if we moved since setting pcmark */ + vim_free(searchbuff); + +/* + * Update the other windows for the current buffer if modified has been set in + * set_Changed() (This should be done more efficiently) + */ + if (modified) + { + WIN *wp; + + for (wp = firstwin; wp; wp = wp->w_next) + if (wp != curwin && wp->w_buffer == curbuf) + { + cursor_off(); + wp->w_redr_type = NOT_VALID; + /* + * don't do the actual redraw if wait_return() has just been + * called and the user typed a ":" + */ + if (!skip_redraw) + win_update(wp); + } + modified = FALSE; + } +} + +/* + * Handle an operator after visual mode or when the movement is finished + */ + void +do_pending_operator(c, nchar, finish_op, searchbuff, command_busy, + old_col, gui_yank, dont_adjust_op_end) + register int c; + int nchar; + int finish_op; + char_u *searchbuff; + int *command_busy; + int old_col; + int gui_yank; /* yanking visual area for GUI */ + int dont_adjust_op_end; +{ + /* The visual area is remembered for redo */ + static int redo_VIsual_mode = NUL; /* 'v', 'V', or Ctrl-V */ + static linenr_t redo_VIsual_line_count; /* number of lines */ + static colnr_t redo_VIsual_col; /* number of cols or end column */ + static long redo_VIsual_Prenum; /* Prenum for operator */ + + linenr_t Prenum1 = 1L; + FPOS old_cursor; + int VIsual_was_active = VIsual_active; + int redraw; + +#ifdef USE_GUI + /* + * Yank the visual area into the GUI selection register before we operate + * on it and lose it forever. This could call do_pending_operator() + * recursively, but that's OK because gui_yank will be TRUE for the + * nested call. Note also that we call gui_copy_selection() and not + * gui_auto_select(). This is because even when 'autoselect' is not set, + * if we operate on the text, eg by deleting it, then this is considered to + * be an explicit request for it to be put in the global cut buffer, so we + * always want to do it here. -- webb + */ + if (gui.in_use && op_type != NOP && !gui_yank && VIsual_active + && !redo_VIsual_busy) + gui_copy_selection(); +#endif + old_cursor = curwin->w_cursor; + + /* + * If an operation is pending, handle it... + */ + if ((VIsual_active || finish_op) && op_type != NOP) + { + op_is_VIsual = VIsual_active; + if (op_type != YANK && !VIsual_active) /* can't redo yank */ + { + prep_redo(Prenum, prechar, opchars[op_type - 1], c, nchar); + if (c == '/' || c == '?') /* was a search */ + { + /* + * If 'cpoptions' does not contain 'r', insert the search + * pattern to really repeat the same command. + */ + if (vim_strchr(p_cpo, CPO_REDO) == NULL) + AppendToRedobuff(searchbuff); + AppendToRedobuff(NL_STR); + } + } + + if (redo_VIsual_busy) + { + curbuf->b_op_start = curwin->w_cursor; + curwin->w_cursor.lnum += redo_VIsual_line_count - 1; + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + VIsual_mode = redo_VIsual_mode; + if (VIsual_mode == 'v') + { + if (redo_VIsual_line_count <= 1) + curwin->w_cursor.col += redo_VIsual_col - 1; + else + curwin->w_cursor.col = redo_VIsual_col; + } + if (redo_VIsual_col == MAXCOL) + { + curwin->w_curswant = MAXCOL; + coladvance(MAXCOL); + } + Prenum = redo_VIsual_Prenum; + } + else if (VIsual_active) + { + curbuf->b_op_start = VIsual; + VIsual_end = curwin->w_cursor; + if (VIsual_mode == 'V') + curbuf->b_op_start.col = 0; + } + + if (lt(curbuf->b_op_start, curwin->w_cursor)) + { + curbuf->b_op_end = curwin->w_cursor; + curwin->w_cursor = curbuf->b_op_start; + } + else + { + curbuf->b_op_end = curbuf->b_op_start; + curbuf->b_op_start = curwin->w_cursor; + } + op_line_count = curbuf->b_op_end.lnum - curbuf->b_op_start.lnum + 1; + + if (VIsual_active || redo_VIsual_busy) + { + if (VIsual_mode == Ctrl('V')) /* block mode */ + { + colnr_t start, end; + + op_block_mode = TRUE; + getvcol(curwin, &(curbuf->b_op_start), + &op_start_vcol, NULL, &op_end_vcol); + if (!redo_VIsual_busy) + { + getvcol(curwin, &(curbuf->b_op_end), &start, NULL, &end); + if (start < op_start_vcol) + op_start_vcol = start; + if (end > op_end_vcol) + op_end_vcol = end; + } + + /* if '$' was used, get op_end_vcol from longest line */ + if (curwin->w_curswant == MAXCOL) + { + curwin->w_cursor.col = MAXCOL; + op_end_vcol = 0; + for (curwin->w_cursor.lnum = curbuf->b_op_start.lnum; + curwin->w_cursor.lnum <= curbuf->b_op_end.lnum; + ++curwin->w_cursor.lnum) + { + getvcol(curwin, &curwin->w_cursor, NULL, NULL, &end); + if (end > op_end_vcol) + op_end_vcol = end; + } + curwin->w_cursor = curbuf->b_op_start; + } + else if (redo_VIsual_busy) + op_end_vcol = op_start_vcol + redo_VIsual_col - 1; + coladvance(op_start_vcol); + } + + if (!redo_VIsual_busy) + { + /* + * Prepare to reselect and redo Visual: this is based on the + * size of the Visual text + */ + resel_VIsual_mode = VIsual_mode; + if (curwin->w_curswant == MAXCOL) + resel_VIsual_col = MAXCOL; + else if (VIsual_mode == Ctrl('V')) + resel_VIsual_col = op_end_vcol - op_start_vcol + 1; + else if (op_line_count > 1) + resel_VIsual_col = curbuf->b_op_end.col; + else + resel_VIsual_col = curbuf->b_op_end.col - + curbuf->b_op_start.col + 1; + resel_VIsual_line_count = op_line_count; + } + /* can't redo yank and : */ + if (op_type != YANK && op_type != COLON) + { + prep_redo(0L, NUL, 'v', prechar, opchars[op_type - 1]); + redo_VIsual_mode = resel_VIsual_mode; + redo_VIsual_col = resel_VIsual_col; + redo_VIsual_line_count = resel_VIsual_line_count; + redo_VIsual_Prenum = Prenum; + } + + /* + * Mincl defaults to TRUE. + * If op_end is on a NUL (empty line) op_inclusive becomes FALSE + * This makes "d}P" and "v}dP" work the same. + */ + op_inclusive = TRUE; + if (VIsual_mode == 'V') + op_motion_type = MLINE; + else + { + op_motion_type = MCHAR; + if (*ml_get_pos(&(curbuf->b_op_end)) == NUL) + op_inclusive = FALSE; + } + + redo_VIsual_busy = FALSE; + /* + * Switch Visual off now, so screen updating does + * not show inverted text when the screen is redrawn. + * With YANK and sometimes with COLON and FILTER there is no screen + * redraw, so it is done here to remove the inverted part. + */ + if (!gui_yank) + { + VIsual_active = FALSE; +#ifdef USE_MOUSE + setmouse(); +#endif + if (p_smd) + clear_cmdline = TRUE; /* unshow visual mode later */ + if (op_type == YANK || op_type == COLON || op_type == FILTER) + update_curbuf(NOT_VALID); + } + + /* set Prenum1 for LSHIFT and RSHIFT, e.g. "V3j2>" */ + if (Prenum == 0) + Prenum1 = 1L; + else + Prenum1 = Prenum; + } + + curwin->w_set_curswant = TRUE; + + /* op_empty is set when start and end are the same */ + op_empty = (op_motion_type == MCHAR && !op_inclusive && + equal(curbuf->b_op_start, curbuf->b_op_end)); + + /* + * If the end of an operator is in column one while op_motion_type is + * MCHAR and op_inclusive is FALSE, we put op_end after the last character + * in the previous line. If op_start is on or before the first non-blank + * in the line, the operator becomes linewise (strange, but that's the way + * vi does it). + */ + if (op_motion_type == MCHAR && op_inclusive == FALSE && + !dont_adjust_op_end && curbuf->b_op_end.col == 0 && + op_line_count > 1) + { + op_end_adjusted = TRUE; /* remember that we did this */ + --op_line_count; + --curbuf->b_op_end.lnum; + if (inindent(0)) + op_motion_type = MLINE; + else + { + curbuf->b_op_end.col = STRLEN(ml_get(curbuf->b_op_end.lnum)); + if (curbuf->b_op_end.col) + { + --curbuf->b_op_end.col; + op_inclusive = TRUE; + } + } + } + else + op_end_adjusted = FALSE; + switch (op_type) + { + case LSHIFT: + case RSHIFT: + do_shift(op_type, TRUE, (int)Prenum1); + break; + + case JOIN: + if (op_line_count < 2) + op_line_count = 2; + if (curwin->w_cursor.lnum + op_line_count - 1 > + curbuf->b_ml.ml_line_count) + beep_flush(); + else + { + /* + * If the cursor position has been changed, recompute the + * current cursor position in the window. If it's not visible, + * don't keep the window updated when joining the lines. + */ + if (old_cursor.lnum != curwin->w_cursor.lnum || + old_cursor.col != curwin->w_cursor.col) + redraw = (curs_rows() == OK); + else + redraw = TRUE; + do_do_join(op_line_count, TRUE, redraw); + } + break; + + case DELETE: + if (!op_empty) + do_delete(); + break; + + case YANK: + if (!op_empty) + (void)do_yank(FALSE, !gui_yank); + break; + + case CHANGE: + *command_busy = do_change(); /* will set op_type to NOP */ + break; + + case FILTER: + if (vim_strchr(p_cpo, CPO_FILTER) != NULL) + AppendToRedobuff((char_u *)"!\r"); /* use any last used !cmd */ + else + bangredo = TRUE; /* do_bang() will put cmd in redo buffer */ + + case INDENT: + case COLON: + +#if defined(LISPINDENT) || defined(CINDENT) + /* + * If 'equalprg' is empty, do the indenting internally. + */ + if (op_type == INDENT && *p_ep == NUL) + { +# ifdef LISPINDENT + if (curbuf->b_p_lisp) + { + do_reindent(get_lisp_indent); + break; + } +# endif +# ifdef CINDENT + do_reindent(get_c_indent); + break; +# endif + } +#endif /* defined(LISPINDENT) || defined(CINDENT) */ + +dofilter: + if (VIsual_was_active) + sprintf((char *)IObuff, ":'<,'>"); + else + sprintf((char *)IObuff, ":%ld,%ld", + (long)curbuf->b_op_start.lnum, + (long)curbuf->b_op_end.lnum); + stuffReadbuff(IObuff); + if (op_type != COLON) + stuffReadbuff((char_u *)"!"); + if (op_type == INDENT) + { +#ifndef CINDENT + if (*p_ep == NUL) + stuffReadbuff((char_u *)"indent"); + else +#endif + stuffReadbuff(p_ep); + stuffReadbuff((char_u *)"\n"); + } + else if (op_type == FORMAT || op_type == GFORMAT) + { + if (*p_fp == NUL) + stuffReadbuff((char_u *)"fmt"); + else + stuffReadbuff(p_fp); + stuffReadbuff((char_u *)"\n"); + } + /* do_cmdline() does the rest */ + break; + + case TILDE: + case UPPER: + case LOWER: + if (!op_empty) + do_tilde(); + break; + + case FORMAT: + case GFORMAT: + if (*p_fp != NUL) + goto dofilter; /* use external command */ + do_format(); /* use internal function */ + break; + + default: + clearopbeep(); + } + prechar = NUL; + if (!gui_yank) + { + /* + * if 'sol' not set, go back to old column for some commands + */ + if (!p_sol && op_motion_type == MLINE && (op_type == LSHIFT || + op_type == RSHIFT || op_type == DELETE)) + coladvance(curwin->w_curswant = old_col); + op_type = NOP; + } + else + curwin->w_cursor = old_cursor; + op_block_mode = FALSE; + yankbuffer = 0; + } +} + +#ifdef USE_MOUSE +/* + * Do the appropriate action for the current mouse click in the current mode. + * + * Normal Mode: + * event modi- position visual change action + * fier cursor window + * left press - yes end yes + * left press C yes end yes "^]" (2) + * left press S yes end yes "*" (2) + * left drag - yes start if moved no + * left relse - yes start if moved no + * middle press - yes if not active no put register + * middle press - yes if active no yank and put + * right press - yes start or extend yes + * right press S yes no change yes "#" (2) + * right drag - yes extend no + * right relse - yes extend no + * + * Insert or Replace Mode: + * event modi- position visual change action + * fier cursor window + * left press - yes (cannot be active) yes + * left press C yes (cannot be active) yes "CTRL-O^]" (2) + * left press S yes (cannot be active) yes "CTRL-O*" (2) + * left drag - yes start or extend (1) no CTRL-O (1) + * left relse - yes start or extend (1) no CTRL-O (1) + * middle press - no (cannot be active) no put register + * right press - yes start or extend yes CTRL-O + * right press S yes (cannot be active) yes "CTRL-O#" (2) + * + * (1) only if mouse pointer moved since press + * (2) only if click is in same buffer + * + * Return TRUE if start_arrow() should be called for edit mode. + */ + int +do_mouse(c, dir, count, fix_indent) + int c; /* K_LEFTMOUSE, etc */ + int dir; /* Direction to 'put' if necessary */ + long count; + int fix_indent; /* Do we fix indent for 'put' if necessary? */ +{ + static int ignore_drag_release = FALSE; + static FPOS orig_cursor; + static int do_always = FALSE; /* ignore 'mouse' setting next time */ + static int got_click = FALSE; /* got a click some time back */ + + int which_button; /* MOUSE_LEFT, _MIDDLE or _RIGHT */ + int is_click; /* If FALSE it's a drag or release event */ + int is_drag; /* If TRUE it's a drag event */ + int jump_flags = 0; /* flags for jump_to_mouse() */ + FPOS start_visual; + FPOS end_visual; + BUF *save_buffer; + int diff; + int moved; /* Has cursor moved? */ + int c1, c2; + int VIsual_was_active = VIsual_active; + + /* + * When GUI is active, always recognize mouse events, otherwise: + * - Ignore mouse event in normal mode if 'mouse' doesn't include 'n'. + * - Ignore mouse event in visual mode if 'mouse' doesn't include 'v'. + * - For command line and insert mode 'mouse' is checked before calling + * do_mouse(). + */ + if (do_always) + do_always = FALSE; + else +#ifdef USE_GUI + if (!gui.in_use) +#endif + { + if (VIsual_active) + { + if (!mouse_has(MOUSE_VISUAL)) + return FALSE; + } + else if (State == NORMAL && !mouse_has(MOUSE_NORMAL)) + return FALSE; + } + + which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag); + + /* + * Ignore drag and release events if we didn't get a click. + */ + if (is_click) + got_click = TRUE; + else + { + if (!got_click) /* didn't get click, ignore */ + return FALSE; + if (!is_drag) /* release, reset got_click */ + got_click = FALSE; + } + + /* + * ALT is currently ignored + */ + if ((mod_mask & MOD_MASK_ALT)) + return FALSE; + + /* + * CTRL right mouse button does CTRL-T + */ + if (is_click && (mod_mask & MOD_MASK_CTRL) && which_button == MOUSE_RIGHT) + { + if (State & INSERT) + stuffcharReadbuff(Ctrl('O')); + stuffcharReadbuff(Ctrl('T')); + got_click = FALSE; /* ignore drag&release now */ + return FALSE; + } + + /* + * CTRL only works with left mouse button + */ + if ((mod_mask & MOD_MASK_CTRL) && which_button != MOUSE_LEFT) + return FALSE; + + /* + * When a modifier is down, ignore drag and release events, as well as + * multiple clicks and the middle mouse button. + */ + if ((mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT)) && + (!is_click || (mod_mask & MOD_MASK_MULTI_CLICK) || + which_button == MOUSE_MIDDLE)) + return FALSE; + + /* + * If the button press was used as the movement command for an operator + * (eg "d"), or it is the middle button that is held down, ignore + * drag/release events. + */ + if (!is_click && (ignore_drag_release || which_button == MOUSE_MIDDLE)) + return FALSE; + + /* + * Middle mouse button does a 'put' of the selected text + */ + if (which_button == MOUSE_MIDDLE) + { + if (State == NORMAL) + { + /* + * If an operator was pending, we don't know what the user wanted + * to do. Go back to normal mode: Clear the operator and beep(). + */ + if (op_type != NOP) + { + clearopbeep(); + return FALSE; + } + + /* + * If visual was active, yank the highlighted text and put it + * before the mouse pointer position. + */ + if (VIsual_active) + { + stuffcharReadbuff('y'); + stuffcharReadbuff(K_MIDDLEMOUSE); + do_always = TRUE; /* ignore 'mouse' setting next time */ + return FALSE; + } + /* + * The rest is below jump_to_mouse() + */ + } + + /* + * Middle click in insert mode doesn't move the mouse, just insert the + * contents of a register. '.' register is special, can't insert that + * with do_put(). + */ + else if (State & INSERT) + { + if (yankbuffer == '.') + insertbuf(yankbuffer); + else + { +#ifdef USE_GUI + if (gui.in_use && yankbuffer == 0) + yankbuffer = '*'; +#endif + do_put(BACKWARD, 1L, fix_indent); + + /* Put cursor after the end of the just pasted text. */ + curwin->w_cursor = curbuf->b_op_end; + if (gchar_cursor() != NUL) + ++curwin->w_cursor.col; + + /* Repeat it with CTRL-R x, not exactly the same, but mostly + * works fine. */ + AppendCharToRedobuff(Ctrl('R')); + if (yankbuffer == 0) + AppendCharToRedobuff('"'); + else + AppendCharToRedobuff(yankbuffer); + } + return FALSE; + } + else + return FALSE; + } + + if (!is_click) + jump_flags |= MOUSE_FOCUS; + + start_visual.lnum = 0; + + if ((State & (NORMAL | INSERT)) && + !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) + { + if (which_button == MOUSE_LEFT) + { + if (is_click) + { + if (VIsual_active) + { + end_visual_mode(); + update_curbuf(NOT_VALID); + } + } + else + jump_flags |= MOUSE_MAY_VIS; + } + else if (which_button == MOUSE_RIGHT) + { + if (is_click && VIsual_active) + { + /* + * Remember the start and end of visual before moving the + * cursor. + */ + if (lt(curwin->w_cursor, VIsual)) + { + start_visual = curwin->w_cursor; + end_visual = VIsual; + } + else + { + start_visual = VIsual; + end_visual = curwin->w_cursor; + } + } + jump_flags |= MOUSE_MAY_VIS; + } + } + + if (!is_drag) + { + /* + * If an operator is pending, ignore all drags and releases until the + * next mouse click. + */ + ignore_drag_release = (op_type != NOP); + } + + /* + * Jump! + */ + if (!is_click) + jump_flags |= MOUSE_DID_MOVE; + save_buffer = curbuf; + moved = (jump_to_mouse(jump_flags) & CURSOR_MOVED); + + /* When jumping to another buffer, stop visual mode */ + if (curbuf != save_buffer && VIsual_active) + { + end_visual_mode(); + update_curbuf(NOT_VALID); /* delete the inversion */ + } + else if (start_visual.lnum) /* right click in visual mode */ + { + /* + * If the click is before the start of visual, change the start. If + * the click is after the end of visual, change the end. If the click + * is inside the visual, change the closest side. + */ + if (lt(curwin->w_cursor, start_visual)) + VIsual = end_visual; + else if (lt(end_visual, curwin->w_cursor)) + VIsual = start_visual; + else + { + /* In the same line, compare column number */ + if (end_visual.lnum == start_visual.lnum) + { + if (curwin->w_cursor.col - start_visual.col > + end_visual.col - curwin->w_cursor.col) + VIsual = start_visual; + else + VIsual = end_visual; + } + + /* In different lines, compare line number */ + else + { + diff = (curwin->w_cursor.lnum - start_visual.lnum) - + (end_visual.lnum - curwin->w_cursor.lnum); + + if (diff > 0) /* closest to end */ + VIsual = start_visual; + else if (diff < 0) /* closest to start */ + VIsual = end_visual; + else /* in the middle line */ + { + if (curwin->w_cursor.col < + (start_visual.col + end_visual.col) / 2) + VIsual = end_visual; + else + VIsual = start_visual; + } + } + } + } + /* + * If Visual mode started in insert mode, execute "CTRL-O" + */ + else if ((State & INSERT) && VIsual_active) + stuffcharReadbuff(Ctrl('O')); + /* + * If cursor has moved, need to update Cline_row + */ + else if (moved) + cursupdate(); + + /* + * Middle mouse click: Put text before cursor. + */ + if (which_button == MOUSE_MIDDLE) + { +#ifdef USE_GUI + if (gui.in_use && yankbuffer == 0) + yankbuffer = '*'; +#endif + if (yank_buffer_mline()) + { + if (mouse_past_bottom) + dir = FORWARD; + } + else if (mouse_past_eol) + dir = FORWARD; + + if (fix_indent) + { + c1 = (dir == BACKWARD) ? '[' : ']'; + c2 = 'p'; + } + else + { + c1 = (dir == FORWARD) ? 'p' : 'P'; + c2 = NUL; + } + prep_redo(Prenum, NUL, c1, c2, NUL); + /* + * Remember where the paste started, so in edit() Insstart can be set + * to this position + */ + if (restart_edit) + where_paste_started = curwin->w_cursor; + do_put(dir, count, fix_indent); + + /* Put cursor at the end of the just pasted text. */ + curwin->w_cursor = curbuf->b_op_end; + if (restart_edit && gchar_cursor() != NUL) + ++curwin->w_cursor.col; /* put cursor after the text */ + } + + /* + * Ctrl-Mouse click jumps to the tag under the mouse pointer + */ + else if ((mod_mask & MOD_MASK_CTRL)) + { + if (State & INSERT) + stuffcharReadbuff(Ctrl('O')); + stuffcharReadbuff(Ctrl(']')); + ignore_drag_release = TRUE; /* ignore drag and release now */ + } + + /* + * Shift-Mouse click searches for the next occurrence of the word under + * the mouse pointer + */ + else if ((mod_mask & MOD_MASK_SHIFT)) + { + if (State & INSERT) + stuffcharReadbuff(Ctrl('O')); + if (which_button == MOUSE_LEFT) + stuffcharReadbuff('*'); + else /* MOUSE_RIGHT */ + stuffcharReadbuff('#'); + } + + /* Handle double clicks */ + else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (NORMAL | INSERT))) + { + if (is_click || !VIsual_active) + { + if (VIsual_active) + orig_cursor = VIsual; + else + { + start_visual_highlight(); + VIsual = curwin->w_cursor; + orig_cursor = VIsual; + VIsual_active = TRUE; +#ifdef USE_MOUSE + setmouse(); +#endif + if (p_smd) + redraw_cmdline = TRUE; /* show visual mode later */ + } + if (mod_mask & MOD_MASK_2CLICK) + VIsual_mode = 'v'; + else if (mod_mask & MOD_MASK_3CLICK) + VIsual_mode = 'V'; + else if (mod_mask & MOD_MASK_4CLICK) + VIsual_mode = Ctrl('V'); + } + if (mod_mask & MOD_MASK_2CLICK) + { + if (lt(curwin->w_cursor, orig_cursor)) + { + find_start_of_word(&curwin->w_cursor); + find_end_of_word(&VIsual); + } + else + { + find_start_of_word(&VIsual); + find_end_of_word(&curwin->w_cursor); + } + curwin->w_set_curswant = TRUE; + } + if (is_click) + { + curs_columns(TRUE); /* recompute w_virtcol */ + update_curbuf(NOT_VALID); /* update the inversion */ + } + } + else if (VIsual_active && VIsual_was_active != VIsual_active) + VIsual_mode = 'v'; + + return moved; +} + + static void +find_start_of_word(pos) + FPOS *pos; +{ + char_u *ptr; + int cclass; + + ptr = ml_get(pos->lnum); + cclass = get_mouse_class(ptr[pos->col]); + + /* Can't test pos->col >= 0 because pos->col is unsigned */ + while (pos->col > 0 && get_mouse_class(ptr[pos->col]) == cclass) + pos->col--; + if (pos->col != 0 || get_mouse_class(ptr[0]) != cclass) + pos->col++; +} + + static void +find_end_of_word(pos) + FPOS *pos; +{ + char_u *ptr; + int cclass; + + ptr = ml_get(pos->lnum); + cclass = get_mouse_class(ptr[pos->col]); + while (ptr[pos->col] && get_mouse_class(ptr[pos->col]) == cclass) + pos->col++; + pos->col--; +} + + static int +get_mouse_class(c) + int c; +{ + if (c == ' ' || c == '\t') + return ' '; + + if (isidchar(c)) + return 'a'; + + /* + * There are a few special cases where we want certain combinations of + * characters to be considered as a single word. These are things like + * "->", "/ *", "*=", "+=", "&=", "<=", ">=", "!=" etc. Otherwise, each + * character is in it's own class. + */ + if (c != NUL && vim_strchr((char_u *)"-+*/%<>&|^!=", c) != NULL) + return '='; + return c; +} +#endif /* USE_MOUSE */ + +/* + * start highlighting for visual mode + */ + void +start_visual_highlight() +{ + static int didwarn = FALSE; /* warned for broken inversion */ + + if (!didwarn && set_highlight('v') == FAIL)/* cannot highlight */ + { + EMSG("Warning: terminal cannot highlight"); + didwarn = TRUE; + } +} + +/* + * End visual mode. If we are using the GUI, and autoselect is set, then + * remember what was selected in case we need to paste it somewhere while we + * still own the selection. This function should ALWAYS be called to end + * visual mode. + */ + void +end_visual_mode() +{ +#ifdef USE_GUI + if (gui.in_use) + gui_auto_select(); +#endif + VIsual_active = FALSE; +#ifdef USE_MOUSE + setmouse(); +#endif + VIsual_end = curwin->w_cursor; /* remember for '> mark */ + if (p_smd) + clear_cmdline = TRUE; /* unshow visual mode later */ +} + +/* + * Find the identifier under or to the right of the cursor. If none is + * found and find_type has FIND_STRING, then find any non-white string. The + * length of the string is returned, or zero if no string is found. If a + * string is found, a pointer to the string is put in *string, but note that + * the caller must use the length returned as this string may not be NUL + * terminated. + */ + int +find_ident_under_cursor(string, find_type) + char_u **string; + int find_type; +{ + char_u *ptr; + int col = 0; /* init to shut up GCC */ + int i; + + /* + * if i == 0: try to find an identifier + * if i == 1: try to find any string + */ + ptr = ml_get_curline(); + for (i = (find_type & FIND_IDENT) ? 0 : 1; i < 2; ++i) + { + /* + * skip to start of identifier/string + */ + col = curwin->w_cursor.col; + while (ptr[col] != NUL && + (i == 0 ? !iswordchar(ptr[col]) : vim_iswhite(ptr[col]))) + ++col; + + /* + * Back up to start of identifier/string. This doesn't match the + * real vi but I like it a little better and it shouldn't bother + * anyone. + * When FIND_IDENT isn't defined, we backup until a blank. + */ + while (col > 0 && (i == 0 ? iswordchar(ptr[col - 1]) : + (!vim_iswhite(ptr[col - 1]) && + (!(find_type & FIND_IDENT) || !iswordchar(ptr[col - 1]))))) + --col; + + /* + * if we don't want just any old string, or we've found an identifier, + * stop searching. + */ + if (!(find_type & FIND_STRING) || iswordchar(ptr[col])) + break; + } + /* + * didn't find an identifier or string + */ + if (ptr[col] == NUL || (!iswordchar(ptr[col]) && i == 0)) + { + if (find_type & FIND_STRING) + EMSG("No string under cursor"); + else + EMSG("No identifier under cursor"); + return 0; + } + ptr += col; + *string = ptr; + col = 0; + while (i == 0 ? iswordchar(*ptr) : (*ptr != NUL && !vim_iswhite(*ptr))) + { + ++ptr; + ++col; + } + return col; +} + + static void +prep_redo(num, pre_char, cmd, c, nchar) + long num; + int pre_char; + int cmd; + int c; + int nchar; +{ + ResetRedobuff(); + if (yankbuffer != 0) /* yank from specified buffer */ + { + AppendCharToRedobuff('\"'); + AppendCharToRedobuff(yankbuffer); + } + if (num) + AppendNumberToRedobuff(num); + if (pre_char != NUL) + AppendCharToRedobuff(pre_char); + AppendCharToRedobuff(cmd); + if (c != NUL) + AppendCharToRedobuff(c); + if (nchar != NUL) + AppendCharToRedobuff(nchar); +} + +/* + * check for operator active and clear it + * + * return TRUE if operator was active + */ + static int +checkclearop() +{ + if (op_type == NOP) + return (FALSE); + clearopbeep(); + return (TRUE); +} + +/* + * check for operator or Visual active and clear it + * + * return TRUE if operator was active + */ + static int +checkclearopq() +{ + if (op_type == NOP && !VIsual_active) + return (FALSE); + clearopbeep(); + return (TRUE); +} + + static void +clearop() +{ + op_type = NOP; + yankbuffer = 0; + prechar = NUL; +} + + static void +clearopbeep() +{ + clearop(); + beep_flush(); +} + +/* + * Routines for displaying a partly typed command + */ + +static char_u showcmd_buf[SHOWCMD_COLS + 1]; +static char_u old_showcmd_buf[SHOWCMD_COLS + 1]; /* For push_showcmd() */ +static int is_showcmd_clear = TRUE; + +static void display_showcmd __ARGS((void)); + + void +clear_showcmd() +{ + if (!p_sc) + return; + + showcmd_buf[0] = NUL; + + /* + * Don't actually display something if there is nothing to clear. + */ + if (is_showcmd_clear) + return; + + display_showcmd(); +} + +/* + * Add 'c' to string of shown command chars. + * Return TRUE if setcursor() has been called. + */ + int +add_to_showcmd(c, display_always) + int c; + int display_always; +{ + char_u *p; + int old_len; + int extra_len; + int overflow; + + if (!p_sc) + return FALSE; + + p = transchar(c); + old_len = STRLEN(showcmd_buf); + extra_len = STRLEN(p); + overflow = old_len + extra_len - SHOWCMD_COLS; + if (overflow > 0) + STRCPY(showcmd_buf, showcmd_buf + overflow); + STRCAT(showcmd_buf, p); + + if (!display_always && char_avail()) + return FALSE; + + display_showcmd(); + + return TRUE; +} + +/* + * Delete 'len' characters from the end of the shown command. + */ + static void +del_from_showcmd(len) + int len; +{ + int old_len; + + if (!p_sc) + return; + + old_len = STRLEN(showcmd_buf); + if (len > old_len) + len = old_len; + showcmd_buf[old_len - len] = NUL; + + if (!char_avail()) + display_showcmd(); +} + + void +push_showcmd() +{ + if (p_sc) + STRCPY(old_showcmd_buf, showcmd_buf); +} + + void +pop_showcmd() +{ + if (!p_sc) + return; + + STRCPY(showcmd_buf, old_showcmd_buf); + + display_showcmd(); +} + + static void +display_showcmd() +{ + int len; + + cursor_off(); + + len = STRLEN(showcmd_buf); + if (len == 0) + is_showcmd_clear = TRUE; + else + { + screen_msg(showcmd_buf, (int)Rows - 1, sc_col); + is_showcmd_clear = FALSE; + } + + /* + * clear the rest of an old message by outputing up to SHOWCMD_COLS spaces + */ + screen_msg((char_u *)" " + len, (int)Rows - 1, sc_col + len); + + setcursor(); /* put cursor back where it belongs */ +} + +/* + * Implementation of "gd" and "gD" command. + */ + static void +do_gd(nchar) + int nchar; +{ + int len; + char_u *pat; + FPOS old_pos; + int t; + int save_p_ws; + int save_p_scs; + char_u *ptr; + + if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0 || + (pat = alloc(len + 5)) == NULL) + { + clearopbeep(); + return; + } + sprintf((char *)pat, iswordchar(*ptr) ? "\\<%.*s\\>" : + "%.*s", len, ptr); + old_pos = curwin->w_cursor; + save_p_ws = p_ws; + save_p_scs = p_scs; + p_ws = FALSE; /* don't wrap around end of file now */ + p_scs = FALSE; /* don't switch ignorecase off now */ + fo_do_comments = TRUE; + + /* + * Search back for the end of the previous function. + * If this fails, and with "gD", go to line 1. + * Search forward for the identifier, ignore comment lines. + */ + if (nchar == 'D' || !findpar(BACKWARD, 1L, '}', FALSE)) + { + setpcmark(); /* Set in findpar() otherwise */ + curwin->w_cursor.lnum = 1; + } + + while ((t = searchit(&curwin->w_cursor, FORWARD, pat, 1L, 0, RE_LAST)) + == OK && + get_leader_len(ml_get_curline(), NULL) && + old_pos.lnum > curwin->w_cursor.lnum) + ++curwin->w_cursor.lnum; + if (t == FAIL || old_pos.lnum <= curwin->w_cursor.lnum) + { + clearopbeep(); + curwin->w_cursor = old_pos; + } + else + curwin->w_set_curswant = TRUE; + + vim_free(pat); + p_ws = save_p_ws; + p_scs = save_p_scs; + fo_do_comments = FALSE; +} diff --git a/usr.bin/vim/ops.c b/usr.bin/vim/ops.c new file mode 100644 index 00000000000..68a992611e7 --- /dev/null +++ b/usr.bin/vim/ops.c @@ -0,0 +1,2673 @@ +/* $OpenBSD: ops.c,v 1.1.1.1 1996/09/07 21:40:25 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * ops.c: implementation of various operators: do_shift, do_delete, do_tilde, + * do_change, do_yank, do_put, do_join + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" +#include "ops.h" + +/* + * Number of registers. + * 0 = unnamed register, for normal yanks and puts + * 1..9 = number registers, for deletes + * 10..35 = named registers + * 36 = delete register (-) + * 37 = GUI selection register (*). Only if USE_GUI defined + */ +#ifdef USE_GUI +# define NUM_REGISTERS 38 +#else +# define NUM_REGISTERS 37 +#endif + +/* + * Symbolic names for some registers. + */ +#define DELETION_REGISTER 36 +#ifdef USE_GUI +# define GUI_SELECTION_REGISTER 37 +#endif + +/* + * Each yank buffer is an array of pointers to lines. + */ +static struct yankbuf +{ + char_u **y_array; /* pointer to array of line pointers */ + linenr_t y_size; /* number of lines in y_array */ + char_u y_type; /* MLINE, MCHAR or MBLOCK */ +} y_buf[NUM_REGISTERS]; + +static struct yankbuf *y_current; /* ptr to current yank buffer */ +static int yankappend; /* TRUE when appending */ +static struct yankbuf *y_previous = NULL; /* ptr to last written yank buffr */ + +/* + * structure used by block_prep, do_delete and do_yank for blockwise operators + */ +struct block_def +{ + int startspaces; + int endspaces; + int textlen; + char_u *textstart; + colnr_t textcol; +}; + +static void get_yank_buffer __ARGS((int)); +static int stuff_yank __ARGS((int, char_u *)); +static void free_yank __ARGS((long)); +static void free_yank_all __ARGS((void)); +static void block_prep __ARGS((struct block_def *, linenr_t, int)); +static int same_leader __ARGS((int, char_u *, int, char_u *)); +static int fmt_end_block __ARGS((linenr_t, int *, char_u **)); + +/* + * do_shift - handle a shift operation + */ + void +do_shift(op, curs_top, amount) + int op; + int curs_top; + int amount; +{ + register long i; + int first_char; + + if (u_save((linenr_t)(curwin->w_cursor.lnum - 1), + (linenr_t)(curwin->w_cursor.lnum + op_line_count)) == FAIL) + return; + for (i = op_line_count; --i >= 0; ) + { + first_char = *ml_get_curline(); + if (first_char == NUL) /* empty line */ + curwin->w_cursor.col = 0; + /* + * Don't move the line right if it starts with # and p_si is set. + */ + else +#if defined(SMARTINDENT) || defined(CINDENT) + if (first_char != '#' || ( +# ifdef SMARTINDENT + !curbuf->b_p_si +# endif +# if defined(SMARTINDENT) && defined(CINDENT) + && +# endif +# ifdef CINDENT + (!curbuf->b_p_cin || !in_cinkeys('#', ' ', TRUE)) +# endif + )) +#endif + { + /* if (op_block_mode) + shift the block, not the whole line + else */ + shift_line(op == LSHIFT, p_sr, amount); + } + ++curwin->w_cursor.lnum; + } + + if (curs_top) /* put cursor on first line, for ">>" */ + { + curwin->w_cursor.lnum -= op_line_count; + beginline(MAYBE); /* shift_line() may have changed cursor.col */ + } + else + --curwin->w_cursor.lnum; /* put cursor on last line, for ":>" */ + updateScreen(CURSUPD); + + if (op_line_count > p_report) + smsg((char_u *)"%ld line%s %ced %d time%s", op_line_count, + plural(op_line_count), (op == RSHIFT) ? '>' : '<', + amount, plural((long)amount)); +} + +/* + * shift the current line one shiftwidth left (if left != 0) or right + * leaves cursor on first blank in the line + */ + void +shift_line(left, round, amount) + int left; + int round; + int amount; +{ + register int count; + register int i, j; + int p_sw = (int)curbuf->b_p_sw; + + count = get_indent(); /* get current indent */ + + if (round) /* round off indent */ + { + i = count / p_sw; /* number of p_sw rounded down */ + j = count % p_sw; /* extra spaces */ + if (j && left) /* first remove extra spaces */ + --amount; + if (left) + { + i -= amount; + if (i < 0) + i = 0; + } + else + i += amount; + count = i * p_sw; + } + else /* original vi indent */ + { + if (left) + { + count -= p_sw * amount; + if (count < 0) + count = 0; + } + else + count += p_sw * amount; + } + set_indent(count, TRUE); /* set new indent */ +} + +#if defined(LISPINDENT) || defined(CINDENT) +/* + * do_reindent - handle reindenting a block of lines for C or lisp. + * + * mechanism copied from do_shift, above + */ + void +do_reindent(how) + int (*how) __ARGS((void)); +{ + register long i; + char_u *l; + int count; + + if (u_save((linenr_t)(curwin->w_cursor.lnum - 1), + (linenr_t)(curwin->w_cursor.lnum + op_line_count)) == FAIL) + return; + + for (i = op_line_count; --i >= 0 && !got_int; ) + { + /* it's a slow thing to do, so give feedback so there's no worry that + * the computer's just hung. */ + + if ((i % 50 == 0 || i == op_line_count - 1) && op_line_count > p_report) + smsg((char_u *)"%ld line%s to indent... ", i, plural(i)); + + /* + * Be vi-compatible: For lisp indenting the first line is not + * indented, unless there is only one line. + */ +#ifdef LISPINDENT + if (i != op_line_count - 1 || op_line_count == 1 || + how != get_lisp_indent) +#endif + { + l = skipwhite(ml_get_curline()); + if (*l == NUL) /* empty or blank line */ + count = 0; + else + count = how(); /* get the indent for this line */ + + set_indent(count, TRUE); + } + ++curwin->w_cursor.lnum; + } + + /* put cursor on first non-blank of indented line */ + curwin->w_cursor.lnum -= op_line_count; + beginline(MAYBE); + + updateScreen(CURSUPD); + + if (op_line_count > p_report) + { + i = op_line_count - (i + 1); + smsg((char_u *)"%ld line%s indented ", i, plural(i)); + } +} +#endif /* defined(LISPINDENT) || defined(CINDENT) */ + +/* + * check if character is name of yank buffer + * Note: There is no check for 0 (default register), caller should do this + */ + int +is_yank_buffer(c, writing) + int c; + int writing; /* if TRUE check for writable buffers */ +{ + if (c > '~') + return FALSE; + if (isalnum(c) || (!writing && vim_strchr((char_u *)".%:", c) != NULL) || + c == '"' || c == '-' +#ifdef USE_GUI + || (gui.in_use && c == '*') +#endif + ) + return TRUE; + return FALSE; +} + +/* + * Set y_current and yankappend, according to the value of yankbuffer. + * + * If yankbuffer is 0 and writing, use buffer 0 + * If yankbuffer is 0 and reading, use previous buffer + */ + static void +get_yank_buffer(writing) + int writing; +{ + register int i; + + yankappend = FALSE; + if (((yankbuffer == 0 && !writing) || yankbuffer == '"') && + y_previous != NULL) + { + y_current = y_previous; + return; + } + i = yankbuffer; + if (isdigit(i)) + i -= '0'; + else if (islower(i)) + i -= 'a' - 10; + else if (isupper(i)) + { + i -= 'A' - 10; + yankappend = TRUE; + } + else if (yankbuffer == '-') + i = DELETION_REGISTER; +#ifdef USE_GUI + else if (gui.in_use && yankbuffer == '*') + i = GUI_SELECTION_REGISTER; +#endif + else /* not 0-9, a-z, A-Z or '-': use buffer 0 */ + i = 0; + y_current = &(y_buf[i]); + if (writing) /* remember the buffer we write into for do_put() */ + y_previous = y_current; +} + +/* + * return TRUE if the current yank buffer has type MLINE + */ + int +yank_buffer_mline() +{ + if (yankbuffer != 0 && !is_yank_buffer(yankbuffer, FALSE)) + return FALSE; + get_yank_buffer(FALSE); + return (y_current->y_type == MLINE); +} + +/* + * start or stop recording into a yank buffer + * + * return FAIL for failure, OK otherwise + */ + int +do_record(c) + int c; +{ + char_u *p; + static int bufname; + int retval; + + if (Recording == FALSE) /* start recording */ + { + /* registers 0-9, a-z and " are allowed */ + if (c > '~' || (!isalnum(c) && c != '"')) + retval = FAIL; + else + { + Recording = TRUE; + showmode(); + bufname = c; + retval = OK; + } + } + else /* stop recording */ + { + Recording = FALSE; + MSG(""); + p = get_recorded(); + if (p == NULL) + retval = FAIL; + else + retval = (stuff_yank(bufname, p)); + } + return retval; +} + +/* + * stuff string 'p' into yank buffer 'bufname' (append if uppercase) + * 'p' is assumed to be alloced. + * + * return FAIL for failure, OK otherwise + */ + static int +stuff_yank(bufname, p) + int bufname; + char_u *p; +{ + char_u *lp; + char_u **pp; + + yankbuffer = bufname; + /* check for read-only buffer */ + if (yankbuffer != 0 && !is_yank_buffer(yankbuffer, TRUE)) + return FAIL; + get_yank_buffer(TRUE); + if (yankappend && y_current->y_array != NULL) + { + pp = &(y_current->y_array[y_current->y_size - 1]); + lp = lalloc((long_u)(STRLEN(*pp) + STRLEN(p) + 1), TRUE); + if (lp == NULL) + { + vim_free(p); + return FAIL; + } + STRCPY(lp, *pp); + STRCAT(lp, p); + vim_free(p); + vim_free(*pp); + *pp = lp; + } + else + { + free_yank_all(); + if ((y_current->y_array = + (char_u **)alloc((unsigned)sizeof(char_u *))) == NULL) + { + vim_free(p); + return FAIL; + } + y_current->y_array[0] = p; + y_current->y_size = 1; + y_current->y_type = MCHAR; /* used to be MLINE, why? */ + } + return OK; +} + +/* + * execute a yank buffer (register): copy it into the stuff buffer + * + * return FAIL for failure, OK otherwise + */ + int +do_execbuf(c, colon, addcr) + int c; + int colon; /* insert ':' before each line */ + int addcr; /* always add '\n' to end of line */ +{ + static int lastc = NUL; + long i; + char_u *p; + int truncated; + int retval; + + + if (c == '@') /* repeat previous one */ + c = lastc; + if (c == '%' || !is_yank_buffer(c, FALSE)) /* check for valid buffer */ + return FAIL; + lastc = c; + + if (c == ':') /* use last command line */ + { + if (last_cmdline == NULL) + { + EMSG(e_nolastcmd); + return FAIL; + } + vim_free(new_last_cmdline); /* don't keep the cmdline containing @: */ + new_last_cmdline = NULL; + if (ins_typebuf((char_u *)"\n", FALSE, 0, TRUE) == FAIL) + return FAIL; + if (ins_typebuf(last_cmdline, FALSE, 0, TRUE) == FAIL) + return FAIL; + if (ins_typebuf((char_u *)":", FALSE, 0, TRUE) == FAIL) + return FAIL; + } + else if (c == '.') /* use last inserted text */ + { + p = get_last_insert(); + if (p == NULL) + { + EMSG(e_noinstext); + return FAIL; + } + i = STRLEN(p); + if (i > 0 && p[i - 1] == ESC) /* remove trailing ESC */ + { + p[i - 1] = NUL; + truncated = TRUE; + } + else + truncated = FALSE; + retval = ins_typebuf(p, FALSE, 0, TRUE); + if (truncated) + p[i - 1] = ESC; + return retval; + } + else + { + yankbuffer = c; + get_yank_buffer(FALSE); + if (y_current->y_array == NULL) + return FAIL; + + /* + * Insert lines into typeahead buffer, from last one to first one. + */ + for (i = y_current->y_size; --i >= 0; ) + { + /* insert newline between lines and after last line if type is MLINE */ + if (y_current->y_type == MLINE || i < y_current->y_size - 1 + || addcr) + { + if (ins_typebuf((char_u *)"\n", FALSE, 0, TRUE) == FAIL) + return FAIL; + } + if (ins_typebuf(y_current->y_array[i], FALSE, 0, TRUE) == FAIL) + return FAIL; + if (colon && ins_typebuf((char_u *)":", FALSE, 0, TRUE) == FAIL) + return FAIL; + } + Exec_reg = TRUE; /* disable the 'q' command */ + } + return OK; +} + +/* + * Insert a yank buffer: copy it into the Read buffer. + * Used by CTRL-R command and middle mouse button in insert mode. + * + * return FAIL for failure, OK otherwise + */ + int +insertbuf(c) + int c; +{ + long i; + int retval = OK; + + /* + * It is possible to get into an endless loop by having CTRL-R a in + * register a and then, in insert mode, doing CTRL-R a. + * If you hit CTRL-C, the loop will be broken here. + */ + mch_breakcheck(); + if (got_int) + return FAIL; + + /* check for valid buffer */ + if (c != NUL && !is_yank_buffer(c, FALSE)) + return FAIL; + +#ifdef USE_GUI + if (c == '*') + gui_get_selection(); /* may fill * register */ +#endif + + if (c == '.') /* insert last inserted text */ + retval = stuff_inserted(NUL, 1L, TRUE); + else if (c == '%') /* insert file name */ + { + if (check_fname() == FAIL) + return FAIL; + stuffReadbuff(curbuf->b_xfilename); + } + else if (c == ':') /* insert last command line */ + { + if (last_cmdline == NULL) + { + EMSG(e_nolastcmd); + return FAIL; + } + stuffReadbuff(last_cmdline); + } + else /* name or number register */ + { + yankbuffer = c; + get_yank_buffer(FALSE); + if (y_current->y_array == NULL) + retval = FAIL; + else + { + + for (i = 0; i < y_current->y_size; ++i) + { + stuffReadbuff(y_current->y_array[i]); + /* insert newline between lines and after last line if type is + * MLINE */ + if (y_current->y_type == MLINE || i < y_current->y_size - 1) + stuffReadbuff((char_u *)"\n"); + } + } + } + + return retval; +} + +/* + * paste a yank buffer into the command line. + * used by CTRL-R command in command-line mode + * insertbuf() can't be used here, because special characters from the + * register contents will be interpreted as commands. + * + * return FAIL for failure, OK otherwise + */ + int +cmdline_paste(c) + int c; +{ + long i; + + if (!is_yank_buffer(c, FALSE)) /* check for valid buffer */ + return FAIL; + +#ifdef USE_GUI + if (c == '*') + gui_get_selection(); +#endif + + if (c == '.') /* insert last inserted text */ + return FAIL; /* Unimplemented */ + + if (c == '%') /* insert file name */ + { + if (check_fname() == FAIL) + return FAIL; + return put_on_cmdline(curbuf->b_xfilename, -1, TRUE); + } + + if (c == ':') /* insert last command line */ + { + if (last_cmdline == NULL) + return FAIL; + return put_on_cmdline(last_cmdline, -1, TRUE); + } + + yankbuffer = c; + get_yank_buffer(FALSE); + if (y_current->y_array == NULL) + return FAIL; + + for (i = 0; i < y_current->y_size; ++i) + { + put_on_cmdline(y_current->y_array[i], -1, FALSE); + + /* insert ^M between lines and after last line if type is MLINE */ + if (y_current->y_type == MLINE || i < y_current->y_size - 1) + put_on_cmdline((char_u *)"\r", 1, FALSE); + } + return OK; +} + +/* + * do_delete - handle a delete operation + */ + void +do_delete() +{ + register int n; + linenr_t lnum; + char_u *ptr; + char_u *newp, *oldp; + linenr_t old_lcount = curbuf->b_ml.ml_line_count; + int did_yank = FALSE; + struct block_def bd; + + if (curbuf->b_ml.ml_flags & ML_EMPTY) /* nothing to do */ + return; + +/* + * Imitate the strange Vi behaviour: If the delete spans more than one line + * and op_motion_type == MCHAR and the result is a blank line, make the delete + * linewise. Don't do this for the change command. + */ + if (op_motion_type == MCHAR && op_line_count > 1 && op_type == DELETE) + { + ptr = ml_get(curbuf->b_op_end.lnum) + curbuf->b_op_end.col + + op_inclusive; + ptr = skipwhite(ptr); + if (*ptr == NUL && inindent(0)) + op_motion_type = MLINE; + } + +/* + * Check for trying to delete (e.g. "D") in an empty line. + * Note: For change command it is ok. + */ + if (op_motion_type == MCHAR && op_line_count == 1 && + op_type == DELETE && *ml_get(curbuf->b_op_start.lnum) == NUL) + { + beep_flush(); + return; + } + +/* + * Do a yank of whatever we're about to delete. + * If a yank buffer was specified, put the deleted text into that buffer + */ + if (yankbuffer != 0) + { + /* check for read-only buffer */ + if (!is_yank_buffer(yankbuffer, TRUE)) + { + beep_flush(); + return; + } + get_yank_buffer(TRUE); /* yank into specified buffer */ + if (do_yank(TRUE, FALSE) == OK) /* yank without message */ + did_yank = TRUE; + } + +/* + * Put deleted text into register 1 and shift number buffers if + * the delete contains a line break, or when a yankbuffer has been specified! + */ + if (yankbuffer != 0 || op_motion_type == MLINE || op_line_count > 1) + { + y_current = &y_buf[9]; + free_yank_all(); /* free buffer nine */ + for (n = 9; n > 1; --n) + y_buf[n] = y_buf[n - 1]; + y_previous = y_current = &y_buf[1]; + y_buf[1].y_array = NULL; /* set buffer one to empty */ + yankbuffer = 0; + } + else if (yankbuffer == 0) /* yank into unnamed buffer */ + { + yankbuffer = '-'; /* use special delete buffer */ + get_yank_buffer(TRUE); + yankbuffer = 0; + } + + if (yankbuffer == 0 && do_yank(TRUE, FALSE) == OK) + did_yank = TRUE; + +/* + * If there's too much stuff to fit in the yank buffer, then get a + * confirmation before doing the delete. This is crude, but simple. And it + * avoids doing a delete of something we can't put back if we want. + */ + if (!did_yank) + { + if (ask_yesno((char_u *)"cannot yank; delete anyway", TRUE) != 'y') + { + emsg(e_abort); + return; + } + } + +/* + * block mode delete + */ + if (op_block_mode) + { + if (u_save((linenr_t)(curbuf->b_op_start.lnum - 1), + (linenr_t)(curbuf->b_op_end.lnum + 1)) == FAIL) + return; + + for (lnum = curwin->w_cursor.lnum; + curwin->w_cursor.lnum <= curbuf->b_op_end.lnum; + ++curwin->w_cursor.lnum) + { + block_prep(&bd, curwin->w_cursor.lnum, TRUE); + if (bd.textlen == 0) /* nothing to delete */ + continue; + + /* + * If we delete a TAB, it may be replaced by several characters. + * Thus the number of characters may increase! + */ + n = bd.textlen - bd.startspaces - bd.endspaces; /* number of chars deleted */ + oldp = ml_get_curline(); + newp = alloc_check((unsigned)STRLEN(oldp) + 1 - n); + if (newp == NULL) + continue; + /* copy up to deleted part */ + vim_memmove(newp, oldp, (size_t)bd.textcol); + /* insert spaces */ + copy_spaces(newp + bd.textcol, (size_t)(bd.startspaces + bd.endspaces)); + /* copy the part after the deleted part */ + oldp += bd.textcol + bd.textlen; + vim_memmove(newp + bd.textcol + bd.startspaces + bd.endspaces, + oldp, STRLEN(oldp) + 1); + /* replace the line */ + ml_replace(curwin->w_cursor.lnum, newp, FALSE); + } + curwin->w_cursor.lnum = lnum; + CHANGED; + updateScreen(VALID_TO_CURSCHAR); + op_line_count = 0; /* no lines deleted */ + } + else if (op_motion_type == MLINE) + { + if (op_type == CHANGE) + { + /* Delete the lines except the first one. + * Temporarily move the cursor to the next line. + * Save the current line number, if the last line is deleted + * it may be changed. + */ + if (op_line_count > 1) + { + lnum = curwin->w_cursor.lnum; + ++curwin->w_cursor.lnum; + dellines((long)(op_line_count - 1), TRUE, TRUE); + curwin->w_cursor.lnum = lnum; + } + if (u_save_cursor() == FAIL) + return; + if (curbuf->b_p_ai) /* don't delete indent */ + { + beginline(TRUE); /* put cursor on first non-white */ + did_ai = TRUE; /* delete the indent when ESC hit */ + } + truncate_line(FALSE); + if (curwin->w_cursor.col > 0) + --curwin->w_cursor.col; /* put cursor on last char in line */ + } + else + { + dellines(op_line_count, TRUE, TRUE); + } + u_clearline(); /* "U" command should not be possible after "dd" */ + beginline(TRUE); + } + else if (op_line_count == 1) /* delete characters within one line */ + { + if (u_save_cursor() == FAIL) + return; + /* if 'cpoptions' contains '$', display '$' at end of change */ + if (vim_strchr(p_cpo, CPO_DOLLAR) != NULL && op_type == CHANGE && + curbuf->b_op_end.lnum == curwin->w_cursor.lnum && !op_is_VIsual) + display_dollar(curbuf->b_op_end.col - !op_inclusive); + n = curbuf->b_op_end.col - curbuf->b_op_start.col + 1 - !op_inclusive; + while (n-- > 0) + if (delchar(TRUE) == FAIL) + break; + } + else /* delete characters between lines */ + { + if (u_save_cursor() == FAIL) /* save first line for undo */ + return; + truncate_line(TRUE); /* delete from cursor to end of line */ + + curbuf->b_op_start = curwin->w_cursor; /* remember curwin->w_cursor */ + ++curwin->w_cursor.lnum; + /* includes save for undo */ + dellines((long)(op_line_count - 2), TRUE, TRUE); + + if (u_save_cursor() == FAIL) /* save last line for undo */ + return; + n = curbuf->b_op_end.col - !op_inclusive; + curwin->w_cursor.col = 0; + while (n-- >= 0) /* delete from start of line until op_end */ + if (delchar(TRUE) == FAIL) + break; + curwin->w_cursor = curbuf->b_op_start; /* restore curwin->w_cursor */ + (void)do_join(FALSE, curs_rows() == OK); + } + + if ((op_motion_type == MCHAR && op_line_count == 1) || op_type == CHANGE) + { + if (dollar_vcol) + must_redraw = 0; /* don't want a redraw now */ + cursupdate(); + if (!dollar_vcol) + updateline(); + } + else if (!global_busy) /* no need to update screen for :global */ + updateScreen(CURSUPD); + + msgmore(curbuf->b_ml.ml_line_count - old_lcount); + + /* correct op_end for deleted text (for "']" command) */ + if (op_block_mode) + curbuf->b_op_end.col = curbuf->b_op_start.col; + else + curbuf->b_op_end = curbuf->b_op_start; +} + +/* + * do_tilde - handle the (non-standard vi) tilde operator + */ + void +do_tilde() +{ + FPOS pos; + struct block_def bd; + + if (u_save((linenr_t)(curbuf->b_op_start.lnum - 1), + (linenr_t)(curbuf->b_op_end.lnum + 1)) == FAIL) + return; + + pos = curbuf->b_op_start; + if (op_block_mode) /* Visual block mode */ + { + for (; pos.lnum <= curbuf->b_op_end.lnum; ++pos.lnum) + { + block_prep(&bd, pos.lnum, FALSE); + pos.col = bd.textcol; + while (--bd.textlen >= 0) + { + swapchar(&pos); + if (inc(&pos) == -1) /* at end of file */ + break; + } + } + } + else /* not block mode */ + { + if (op_motion_type == MLINE) + { + pos.col = 0; + curbuf->b_op_end.col = STRLEN(ml_get(curbuf->b_op_end.lnum)); + if (curbuf->b_op_end.col) + --curbuf->b_op_end.col; + } + else if (!op_inclusive) + dec(&(curbuf->b_op_end)); + + while (ltoreq(pos, curbuf->b_op_end)) + { + swapchar(&pos); + if (inc(&pos) == -1) /* at end of file */ + break; + } + } + + if (op_motion_type == MCHAR && op_line_count == 1 && !op_block_mode) + { + cursupdate(); + updateline(); + } + else + updateScreen(CURSUPD); + + if (op_line_count > p_report) + smsg((char_u *)"%ld line%s ~ed", + op_line_count, plural(op_line_count)); +} + +/* + * If op_type == UPPER: make uppercase, + * if op_type == LOWER: make lowercase, + * else swap case of character at 'pos' + */ + void +swapchar(pos) + FPOS *pos; +{ + int c; + + c = gchar(pos); + if (islower(c) && op_type != LOWER) + { + pchar(*pos, toupper(c)); + CHANGED; + } + else if (isupper(c) && op_type != UPPER) + { + pchar(*pos, tolower(c)); + CHANGED; + } +} + +/* + * do_change - handle a change operation + * + * return TRUE if edit() returns because of a CTRL-O command + */ + int +do_change() +{ + register colnr_t l; + + l = curbuf->b_op_start.col; + if (op_motion_type == MLINE) + { + l = 0; + can_si = TRUE; /* It's like opening a new line, do si */ + } + + if (!op_empty) + do_delete(); /* delete the text and take care of undo */ + + if ((l > curwin->w_cursor.col) && !lineempty(curwin->w_cursor.lnum)) + inc_cursor(); + +#ifdef LISPINDENT + if (op_motion_type == MLINE) + { + if (curbuf->b_p_lisp && curbuf->b_p_ai) + fixthisline(get_lisp_indent); +# ifdef CINDENT + else if (curbuf->b_p_cin) + fixthisline(get_c_indent); +# endif + } +#endif + + op_type = NOP; /* don't want op_type == CHANGED in Insert mode */ + return edit(NUL, FALSE, (linenr_t)1); +} + +/* + * set all the yank buffers to empty (called from main()) + */ + void +init_yank() +{ + register int i; + + for (i = 0; i < NUM_REGISTERS; ++i) + y_buf[i].y_array = NULL; +} + +/* + * Free "n" lines from the current yank buffer. + * Called for normal freeing and in case of error. + */ + static void +free_yank(n) + long n; +{ + if (y_current->y_array != NULL) + { + register long i; + + for (i = n; --i >= 0; ) + { + if ((i & 1023) == 1023) /* this may take a while */ + { + /* + * This message should never cause a hit-return message. + * Overwrite this message with any next message. + */ + ++no_wait_return; + smsg((char_u *)"freeing %ld lines", i + 1); + --no_wait_return; + msg_didout = FALSE; + msg_col = 0; + } + vim_free(y_current->y_array[i]); + } + vim_free(y_current->y_array); + y_current->y_array = NULL; + if (n >= 1000) + MSG(""); + } +} + + static void +free_yank_all() +{ + free_yank(y_current->y_size); +} + +/* + * Yank the text between curwin->w_cursor and startpos into a yank buffer. + * If we are to append ("uppercase), we first yank into a new yank buffer and + * then concatenate the old and the new one (so we keep the old one in case + * of out-of-memory). + * + * return FAIL for failure, OK otherwise + */ + int +do_yank(deleting, mess) + int deleting; + int mess; +{ + long i; /* index in y_array[] */ + struct yankbuf *curr; /* copy of y_current */ + struct yankbuf newbuf; /* new yank buffer when appending */ + char_u **new_ptr; + register linenr_t lnum; /* current line number */ + long j; + int yanktype = op_motion_type; + long yanklines = op_line_count; + linenr_t yankendlnum = curbuf->b_op_end.lnum; + + char_u *pnew; + struct block_def bd; + + /* check for read-only buffer */ + if (yankbuffer != 0 && !is_yank_buffer(yankbuffer, TRUE)) + { + beep_flush(); + return FAIL; + } + if (!deleting) /* do_delete() already set y_current */ + get_yank_buffer(TRUE); + + curr = y_current; + /* append to existing contents */ + if (yankappend && y_current->y_array != NULL) + y_current = &newbuf; + else + free_yank_all(); /* free previously yanked lines */ + +/* + * If the cursor was in column 1 before and after the movement, and the + * operator is not inclusive, the yank is always linewise. + */ + if (op_motion_type == MCHAR && curbuf->b_op_start.col == 0 && + !op_inclusive && curbuf->b_op_end.col == 0 && yanklines > 1) + { + yanktype = MLINE; + --yankendlnum; + --yanklines; + } + + y_current->y_size = yanklines; + y_current->y_type = yanktype; /* set the yank buffer type */ + y_current->y_array = (char_u **)lalloc((long_u)(sizeof(char_u *) * + yanklines), TRUE); + + if (y_current->y_array == NULL) + { + y_current = curr; + return FAIL; + } + + i = 0; + lnum = curbuf->b_op_start.lnum; + +/* + * Visual block mode + */ + if (op_block_mode) + { + y_current->y_type = MBLOCK; /* set the yank buffer type */ + for ( ; lnum <= yankendlnum; ++lnum) + { + block_prep(&bd, lnum, FALSE); + + if ((pnew = alloc(bd.startspaces + bd.endspaces + + bd.textlen + 1)) == NULL) + goto fail; + y_current->y_array[i++] = pnew; + + copy_spaces(pnew, (size_t)bd.startspaces); + pnew += bd.startspaces; + + vim_memmove(pnew, bd.textstart, (size_t)bd.textlen); + pnew += bd.textlen; + + copy_spaces(pnew, (size_t)bd.endspaces); + pnew += bd.endspaces; + + *pnew = NUL; + } + } + else + { +/* + * there are three parts for non-block mode: + * 1. if yanktype != MLINE yank last part of the top line + * 2. yank the lines between op_start and op_end, inclusive when + * yanktype == MLINE + * 3. if yanktype != MLINE yank first part of the bot line + */ + if (yanktype != MLINE) + { + if (yanklines == 1) /* op_start and op_end on same line */ + { + j = curbuf->b_op_end.col - curbuf->b_op_start.col + + 1 - !op_inclusive; + if ((y_current->y_array[0] = strnsave(ml_get(lnum) + + curbuf->b_op_start.col, (int)j)) == NULL) + { +fail: + free_yank(i); /* free the allocated lines */ + y_current = curr; + return FAIL; + } + goto success; + } + if ((y_current->y_array[0] = strsave(ml_get(lnum++) + + curbuf->b_op_start.col)) == NULL) + goto fail; + ++i; + } + + while (yanktype == MLINE ? (lnum <= yankendlnum) : (lnum < yankendlnum)) + { + if ((y_current->y_array[i] = strsave(ml_get(lnum++))) == NULL) + goto fail; + ++i; + } + if (yanktype != MLINE) + { + if ((y_current->y_array[i] = strnsave(ml_get(yankendlnum), + curbuf->b_op_end.col + 1 - !op_inclusive)) == NULL) + goto fail; + } + } + +success: + if (curr != y_current) /* append the new block to the old block */ + { + new_ptr = (char_u **)lalloc((long_u)(sizeof(char_u *) * + (curr->y_size + y_current->y_size)), TRUE); + if (new_ptr == NULL) + goto fail; + for (j = 0; j < curr->y_size; ++j) + new_ptr[j] = curr->y_array[j]; + vim_free(curr->y_array); + curr->y_array = new_ptr; + + if (yanktype == MLINE) /* MLINE overrides MCHAR and MBLOCK */ + curr->y_type = MLINE; + + /* concatenate the last line of the old block with the first line of + * the new block */ + if (curr->y_type == MCHAR) + { + pnew = lalloc((long_u)(STRLEN(curr->y_array[curr->y_size - 1]) + + STRLEN(y_current->y_array[0]) + 1), TRUE); + if (pnew == NULL) + { + i = y_current->y_size - 1; + goto fail; + } + STRCPY(pnew, curr->y_array[--j]); + STRCAT(pnew, y_current->y_array[0]); + vim_free(curr->y_array[j]); + vim_free(y_current->y_array[0]); + curr->y_array[j++] = pnew; + i = 1; + } + else + i = 0; + while (i < y_current->y_size) + curr->y_array[j++] = y_current->y_array[i++]; + curr->y_size = j; + vim_free(y_current->y_array); + y_current = curr; + } + if (mess) /* Display message about yank? */ + { + if (yanktype == MCHAR && !op_block_mode) + --yanklines; + if (yanklines > p_report) + { + cursupdate(); /* redisplay now, so message is not deleted */ + smsg((char_u *)"%ld line%s yanked", yanklines, plural(yanklines)); + } + } + + return OK; +} + +/* + * put contents of register into the text + * For ":put" command count == -1. + */ + void +do_put(dir, count, fix_indent) + int dir; /* BACKWARD for 'P', FORWARD for 'p' */ + long count; + int fix_indent; /* make indent look nice */ +{ + char_u *ptr; + char_u *newp, *oldp; + int yanklen; + int oldlen; + int totlen = 0; /* init for gcc */ + linenr_t lnum; + colnr_t col; + long i; /* index in y_array[] */ + int y_type; + long y_size; + char_u **y_array; + long nr_lines = 0; + colnr_t vcol; + int delcount; + int incr = 0; + long j; + FPOS new_cursor; + int indent; + int orig_indent = 0; /* init for gcc */ + int indent_diff = 0; /* init for gcc */ + int first_indent = TRUE; + FPOS old_pos; + struct block_def bd; + char_u *insert_string = NULL; + +#ifdef USE_GUI + if (yankbuffer == '*') + gui_get_selection(); +#endif + + if (fix_indent) + orig_indent = get_indent(); + + curbuf->b_op_start = curwin->w_cursor; /* default for "'[" command */ + if (dir == FORWARD) + curbuf->b_op_start.col++; + curbuf->b_op_end = curwin->w_cursor; /* default for "']" command */ + + /* + * Using inserted text works differently, because the buffer includes + * special characters (newlines, etc.). + */ + if (yankbuffer == '.') + { + (void)stuff_inserted((dir == FORWARD ? (count == -1 ? 'o' : 'a') : + (count == -1 ? 'O' : 'i')), count, FALSE); + return; + } + + /* + * For '%' (file name) and ':' (last command line) we have to create a + * fake yank buffer. + */ + if (yankbuffer == '%') /* use file name */ + { + if (check_fname() == FAIL) + return; + insert_string = curbuf->b_xfilename; + } + else if (yankbuffer == ':') /* use last command line */ + { + if (last_cmdline == NULL) + { + EMSG(e_nolastcmd); + return; + } + insert_string = last_cmdline; + } + + if (insert_string != NULL) + { + y_type = MCHAR; /* use fake one-line yank buffer */ + y_size = 1; + y_array = &insert_string; + } + else + { + get_yank_buffer(FALSE); + + y_type = y_current->y_type; + y_size = y_current->y_size; + y_array = y_current->y_array; + } + + if (count == -1) /* :put command */ + { + y_type = MLINE; + count = 1; + } + + if (y_size == 0 || y_array == NULL) + { + EMSG2("Nothing in register %s", transchar(yankbuffer)); + return; + } + + if (y_type == MBLOCK) + { + lnum = curwin->w_cursor.lnum + y_size + 1; + if (lnum > curbuf->b_ml.ml_line_count) + lnum = curbuf->b_ml.ml_line_count + 1; + if (u_save(curwin->w_cursor.lnum - 1, lnum) == FAIL) + return; + } + else if (u_save_cursor() == FAIL) + return; + + yanklen = STRLEN(y_array[0]); + CHANGED; + + lnum = curwin->w_cursor.lnum; + col = curwin->w_cursor.col; + +/* + * block mode + */ + if (y_type == MBLOCK) + { + if (dir == FORWARD && gchar_cursor() != NUL) + { + getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col); + ++col; + ++curwin->w_cursor.col; + } + else + getvcol(curwin, &curwin->w_cursor, &col, NULL, NULL); + for (i = 0; i < y_size; ++i) + { + bd.startspaces = 0; + bd.endspaces = 0; + bd.textcol = 0; + vcol = 0; + delcount = 0; + + /* add a new line */ + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + { + ml_append(curbuf->b_ml.ml_line_count, (char_u *)"", + (colnr_t)1, FALSE); + ++nr_lines; + } + oldp = ml_get_curline(); + oldlen = STRLEN(oldp); + for (ptr = oldp; vcol < col && *ptr; ++ptr) + { + /* Count a tab for what it's worth (if list mode not on) */ + incr = lbr_chartabsize(ptr, (colnr_t)vcol); + vcol += incr; + ++bd.textcol; + } + if (vcol < col) /* line too short, padd with spaces */ + { + bd.startspaces = col - vcol; + } + else if (vcol > col) + { + bd.endspaces = vcol - col; + bd.startspaces = incr - bd.endspaces; + --bd.textcol; + delcount = 1; + } + yanklen = STRLEN(y_array[i]); + totlen = count * yanklen + bd.startspaces + bd.endspaces; + newp = alloc_check((unsigned)totlen + oldlen + 1); + if (newp == NULL) + break; + /* copy part up to cursor to new line */ + ptr = newp; + vim_memmove(ptr, oldp, (size_t)bd.textcol); + ptr += bd.textcol; + /* may insert some spaces before the new text */ + copy_spaces(ptr, (size_t)bd.startspaces); + ptr += bd.startspaces; + /* insert the new text */ + for (j = 0; j < count; ++j) + { + vim_memmove(ptr, y_array[i], (size_t)yanklen); + ptr += yanklen; + } + /* may insert some spaces after the new text */ + copy_spaces(ptr, (size_t)bd.endspaces); + ptr += bd.endspaces; + /* move the text after the cursor to the end of the line. */ + vim_memmove(ptr, oldp + bd.textcol + delcount, + (size_t)(oldlen - bd.textcol - delcount + 1)); + ml_replace(curwin->w_cursor.lnum, newp, FALSE); + + ++curwin->w_cursor.lnum; + if (i == 0) + curwin->w_cursor.col += bd.startspaces; + } + /* for "']" command */ + curbuf->b_op_end.lnum = curwin->w_cursor.lnum - 1; + curbuf->b_op_end.col = bd.textcol + totlen - 1; + curwin->w_cursor.lnum = lnum; + cursupdate(); + updateScreen(VALID_TO_CURSCHAR); + } + else /* not block mode */ + { + if (y_type == MCHAR) + { + /* if type is MCHAR, FORWARD is the same as BACKWARD on the next char */ + if (dir == FORWARD && gchar_cursor() != NUL) + { + ++col; + if (yanklen) + { + ++curwin->w_cursor.col; + ++curbuf->b_op_end.col; + } + } + new_cursor = curwin->w_cursor; + } + else if (dir == BACKWARD) + /* if type is MLINE, BACKWARD is the same as FORWARD on the previous line */ + --lnum; + +/* + * simple case: insert into current line + */ + if (y_type == MCHAR && y_size == 1) + { + totlen = count * yanklen; + if (totlen) + { + oldp = ml_get(lnum); + newp = alloc_check((unsigned)(STRLEN(oldp) + totlen + 1)); + if (newp == NULL) + return; /* alloc() will give error message */ + vim_memmove(newp, oldp, (size_t)col); + ptr = newp + col; + for (i = 0; i < count; ++i) + { + vim_memmove(ptr, y_array[0], (size_t)yanklen); + ptr += yanklen; + } + vim_memmove(ptr, oldp + col, STRLEN(oldp + col) + 1); + ml_replace(lnum, newp, FALSE); + /* put cursor on last putted char */ + curwin->w_cursor.col += (colnr_t)(totlen - 1); + } + curbuf->b_op_end = curwin->w_cursor; + updateline(); + } + else + { + while (--count >= 0) + { + i = 0; + if (y_type == MCHAR) + { + /* + * Split the current line in two at the insert position. + * First insert y_array[size - 1] in front of second line. + * Then append y_array[0] to first line. + */ + ptr = ml_get(lnum) + col; + totlen = STRLEN(y_array[y_size - 1]); + newp = alloc_check((unsigned)(STRLEN(ptr) + totlen + 1)); + if (newp == NULL) + goto error; + STRCPY(newp, y_array[y_size - 1]); + STRCAT(newp, ptr); + /* insert second line */ + ml_append(lnum, newp, (colnr_t)0, FALSE); + vim_free(newp); + + oldp = ml_get(lnum); + newp = alloc_check((unsigned)(col + yanklen + 1)); + if (newp == NULL) + goto error; + /* copy first part of line */ + vim_memmove(newp, oldp, (size_t)col); + /* append to first line */ + vim_memmove(newp + col, y_array[0], (size_t)(yanklen + 1)); + ml_replace(lnum, newp, FALSE); + + curwin->w_cursor.lnum = lnum; + i = 1; + } + + while (i < y_size) + { + if ((y_type != MCHAR || i < y_size - 1) && + ml_append(lnum, y_array[i], (colnr_t)0, FALSE) == FAIL) + goto error; + lnum++; + i++; + if (fix_indent) + { + old_pos = curwin->w_cursor; + curwin->w_cursor.lnum = lnum; + ptr = ml_get(lnum); +#if defined(SMARTINDENT) || defined(CINDENT) + if (*ptr == '#' +# ifdef SMARTINDENT + && curbuf->b_p_si +# endif +# ifdef CINDENT + && curbuf->b_p_cin && in_cinkeys('#', ' ', TRUE) +# endif + ) + + indent = 0; /* Leave # lines at start */ + else +#endif + if (*ptr == NUL) + indent = 0; /* Ignore empty lines */ + else if (first_indent) + { + indent_diff = orig_indent - get_indent(); + indent = orig_indent; + first_indent = FALSE; + } + else if ((indent = get_indent() + indent_diff) < 0) + indent = 0; + set_indent(indent, TRUE); + curwin->w_cursor = old_pos; + } + ++nr_lines; + } + } + + /* put '] at last inserted character */ + curbuf->b_op_end.lnum = lnum; + col = STRLEN(y_array[y_size - 1]); + if (col > 1) + curbuf->b_op_end.col = col - 1; + else + curbuf->b_op_end.col = 0; + + if (y_type == MLINE) + { + curwin->w_cursor.col = 0; + if (dir == FORWARD) + { + updateScreen(NOT_VALID); /* recomp. curwin->w_botline */ + ++curwin->w_cursor.lnum; + } + /* put cursor on first non-blank in last inserted line */ + beginline(TRUE); + } + else /* put cursor on first inserted character */ + { + curwin->w_cursor = new_cursor; + } + +error: + if (y_type == MLINE) /* for '[ */ + { + curbuf->b_op_start.col = 0; + if (dir == FORWARD) + curbuf->b_op_start.lnum++; + } + mark_adjust(curbuf->b_op_start.lnum + (y_type == MCHAR), + MAXLNUM, nr_lines, 0L); + updateScreen(CURSUPD); + } + } + + msgmore(nr_lines); + curwin->w_set_curswant = TRUE; +} + +/* Return the character name of the register with the given number */ + int +get_register_name(num) + int num; +{ + if (num == -1) + return '"'; + else if (num < 10) + return num + '0'; + else if (num == DELETION_REGISTER) + return '-'; +#ifdef USE_GUI + else if (num == GUI_SELECTION_REGISTER) + return '*'; +#endif + else + return num + 'a' - 10; +} + +/* + * display the contents of the yank buffers + */ + void +do_dis(arg) + char_u *arg; +{ + register int i, n; + register long j; + register char_u *p; + register struct yankbuf *yb; + char_u name; + + if (arg != NULL && *arg == NUL) + arg = NULL; + + set_highlight('t'); /* Highlight title */ + start_highlight(); + MSG_OUTSTR("\n--- Registers ---"); + stop_highlight(); + for (i = -1; i < NUM_REGISTERS; ++i) + { + if (i == -1) + { + if (y_previous != NULL) + yb = y_previous; + else + yb = &(y_buf[0]); + } + else + yb = &(y_buf[i]); + name = get_register_name(i); + if (yb->y_array != NULL && (arg == NULL || + vim_strchr(arg, name) != NULL)) + { + msg_outchar('\n'); + msg_outchar('"'); + msg_outchar(name); + MSG_OUTSTR(" "); + + n = (int)Columns - 6; + for (j = 0; j < yb->y_size && n > 1; ++j) + { + if (j) + { + MSG_OUTSTR("^J"); + n -= 2; + } + for (p = yb->y_array[j]; *p && (n -= charsize(*p)) >= 0; ++p) + msg_outtrans_len(p, 1); + } + flushbuf(); /* show one line at a time */ + } + } + + /* + * display last inserted text + */ + if ((p = get_last_insert()) != NULL && + (arg == NULL || vim_strchr(arg, '.') != NULL)) + { + MSG_OUTSTR("\n\". "); + dis_msg(p, TRUE); + } + + /* + * display last command line + */ + if (last_cmdline != NULL && (arg == NULL || vim_strchr(arg, ':') != NULL)) + { + MSG_OUTSTR("\n\": "); + dis_msg(last_cmdline, FALSE); + } + + /* + * display current file name + */ + if (curbuf->b_xfilename != NULL && + (arg == NULL || vim_strchr(arg, '%') != NULL)) + { + MSG_OUTSTR("\n\"% "); + dis_msg(curbuf->b_xfilename, FALSE); + } +} + +/* + * display a string for do_dis() + * truncate at end of screen line + */ + void +dis_msg(p, skip_esc) + char_u *p; + int skip_esc; /* if TRUE, ignore trailing ESC */ +{ + int n; + + n = (int)Columns - 6; + while (*p && !(*p == ESC && skip_esc && *(p + 1) == NUL) && + (n -= charsize(*p)) >= 0) + msg_outtrans_len(p++, 1); +} + +/* + * join 'count' lines (minimal 2), including u_save() + */ + void +do_do_join(count, insert_space, redraw) + long count; + int insert_space; + int redraw; /* can redraw, curwin->w_col valid */ +{ + if (u_save((linenr_t)(curwin->w_cursor.lnum - 1), + (linenr_t)(curwin->w_cursor.lnum + count)) == FAIL) + return; + + if (count > 10) + redraw = FALSE; /* don't redraw each small change */ + while (--count > 0) + { + line_breakcheck(); + if (got_int || do_join(insert_space, redraw) == FAIL) + { + beep_flush(); + break; + } + } + if (redraw) + redraw_later(VALID_TO_CURSCHAR); + else + redraw_later(NOT_VALID); + + /* + * Need to update the screen if the line where the cursor is became too + * long to fit on the screen. + */ + cursupdate(); +} + +/* + * Join two lines at the cursor position. + * + * return FAIL for failure, OK ohterwise + */ + int +do_join(insert_space, redraw) + int insert_space; + int redraw; /* should only be TRUE when curwin->w_row valid */ +{ + char_u *curr; + char_u *next; + char_u *newp; + int endcurr1, endcurr2; + int currsize; /* size of the current line */ + int nextsize; /* size of the next line */ + int spaces; /* number of spaces to insert */ + int rows_to_del = 0;/* number of rows on screen to delete */ + linenr_t t; + + if (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) + return FAIL; /* can't join on last line */ + + if (redraw) + rows_to_del = plines_m(curwin->w_cursor.lnum, + curwin->w_cursor.lnum + 1); + + curr = ml_get_curline(); + currsize = STRLEN(curr); + endcurr1 = endcurr2 = NUL; + if (currsize > 0) + { + endcurr1 = *(curr + currsize - 1); + if (currsize > 1) + endcurr2 = *(curr + currsize - 2); + } + + next = ml_get((linenr_t)(curwin->w_cursor.lnum + 1)); + spaces = 0; + if (insert_space) + { + next = skipwhite(next); + spaces = 1; + if (*next == ')' || currsize == 0) + spaces = 0; + else + { + if (endcurr1 == ' ' || endcurr1 == TAB) + { + spaces = 0; + if (currsize > 1) + endcurr1 = endcurr2; + } + if (p_js && vim_strchr((char_u *)".!?", endcurr1) != NULL) + spaces = 2; + } + } + nextsize = STRLEN(next); + + newp = alloc_check((unsigned)(currsize + nextsize + spaces + 1)); + if (newp == NULL) + return FAIL; + + /* + * Insert the next line first, because we already have that pointer. + * Curr has to be obtained again, because getting next will have + * invalidated it. + */ + vim_memmove(newp + currsize + spaces, next, (size_t)(nextsize + 1)); + + curr = ml_get_curline(); + vim_memmove(newp, curr, (size_t)currsize); + + copy_spaces(newp + currsize, (size_t)spaces); + + ml_replace(curwin->w_cursor.lnum, newp, FALSE); + + /* + * Delete the following line. To do this we move the cursor there + * briefly, and then move it back. After dellines() the cursor may + * have moved up (last line deleted), so the current lnum is kept in t. + */ + t = curwin->w_cursor.lnum; + ++curwin->w_cursor.lnum; + dellines(1L, FALSE, FALSE); + curwin->w_cursor.lnum = t; + + /* + * the number of rows on the screen is reduced by the difference + * in number of rows of the two old lines and the one new line + */ + if (redraw) + { + rows_to_del -= plines(curwin->w_cursor.lnum); + if (rows_to_del > 0) + win_del_lines(curwin, curwin->w_cline_row + curwin->w_cline_height, + rows_to_del, TRUE, TRUE); + } + + /* + * go to first character of the joined line + */ + if (currsize == 0) + curwin->w_cursor.col = 0; + else + { + curwin->w_cursor.col = currsize - 1; + (void)oneright(); + } + CHANGED; + + return OK; +} + +/* + * Return TRUE if the two comment leaders given are the same. The cursor is + * in the first line. White-space is ignored. Note that the whole of + * 'leader1' must match 'leader2_len' characters from 'leader2' -- webb + */ + static int +same_leader(leader1_len, leader1_flags, leader2_len, leader2_flags) + int leader1_len; + char_u *leader1_flags; + int leader2_len; + char_u *leader2_flags; +{ + int idx1 = 0, idx2 = 0; + char_u *p; + char_u *line1; + char_u *line2; + + if (leader1_len == 0) + return (leader2_len == 0); + + /* + * If first leader has 'f' flag, the lines can be joined only if the + * second line does not have a leader. + * If first leader has 'e' flag, the lines can never be joined. + * If fist leader has 's' flag, the lines can only be joined if there is + * some text after it and the second line has the 'm' flag. + */ + if (leader1_flags != NULL) + { + for (p = leader1_flags; *p && *p != ':'; ++p) + { + if (*p == COM_FIRST) + return (leader2_len == 0); + if (*p == COM_END) + return FALSE; + if (*p == COM_START) + { + if (*(ml_get_curline() + leader1_len) == NUL) + return FALSE; + if (leader2_flags == NULL || leader2_len == 0) + return FALSE; + for (p = leader2_flags; *p && *p != ':'; ++p) + if (*p == COM_MIDDLE) + return TRUE; + return FALSE; + } + } + } + + /* + * Get current line and next line, compare the leaders. + * The first line has to be saved, only one line can be locked at a time. + */ + line1 = strsave(ml_get_curline()); + if (line1 != NULL) + { + for (idx1 = 0; vim_iswhite(line1[idx1]); ++idx1) + ; + line2 = ml_get(curwin->w_cursor.lnum + 1); + for (idx2 = 0; idx2 < leader2_len; ++idx2) + { + if (!vim_iswhite(line2[idx2])) + { + if (line1[idx1++] != line2[idx2]) + break; + } + else + while (vim_iswhite(line1[idx1])) + ++idx1; + } + vim_free(line1); + } + return (idx2 == leader2_len && idx1 == leader1_len); +} + +/* + * implementation of the format operator 'Q' + */ + void +do_format() +{ + long old_line_count = curbuf->b_ml.ml_line_count; + int prev_is_blank = FALSE; + int is_end_block = TRUE; + int next_is_end_block; + int leader_len = 0; /* init for gcc */ + int next_leader_len; + char_u *leader_flags = NULL; + char_u *next_leader_flags; + int advance = TRUE; + int second_indent = -1; + int do_second_indent; + int first_par_line = TRUE; + + if (u_save((linenr_t)(curwin->w_cursor.lnum - 1), + (linenr_t)(curwin->w_cursor.lnum + op_line_count)) == FAIL) + return; + + /* check for 'q' and '2' in 'formatoptions' */ + fo_do_comments = has_format_option(FO_Q_COMS); + do_second_indent = has_format_option(FO_Q_SECOND); + + /* + * get info about the previous and current line. + */ + if (curwin->w_cursor.lnum > 1) + is_end_block = fmt_end_block(curwin->w_cursor.lnum - 1, + &next_leader_len, &next_leader_flags); + next_is_end_block = fmt_end_block(curwin->w_cursor.lnum, + &next_leader_len, &next_leader_flags); + + curwin->w_cursor.lnum--; + while (--op_line_count >= 0) + { + /* + * Advance to next block. + */ + if (advance) + { + curwin->w_cursor.lnum++; + prev_is_blank = is_end_block; + is_end_block = next_is_end_block; + leader_len = next_leader_len; + leader_flags = next_leader_flags; + } + + /* + * The last line to be formatted. + */ + if (op_line_count == 0) + { + next_is_end_block = TRUE; + next_leader_len = 0; + next_leader_flags = NULL; + } + else + next_is_end_block = fmt_end_block(curwin->w_cursor.lnum + 1, + &next_leader_len, &next_leader_flags); + advance = TRUE; + + /* + * For the first line of a paragraph, check indent of second line. + * Don't do this for comments and empty lines. + */ + if (first_par_line && do_second_indent && + prev_is_blank && !is_end_block && + curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count && + leader_len == 0 && next_leader_len == 0 && + !lineempty(curwin->w_cursor.lnum + 1)) + second_indent = get_indent_lnum(curwin->w_cursor.lnum + 1); + + /* + * Skip end-of-block (blank) lines + */ + if (is_end_block) + { + } + /* + * If we have got to the end of a paragraph, format it. + */ + else if (next_is_end_block || !same_leader(leader_len, leader_flags, + next_leader_len, next_leader_flags)) + { + /* replace indent in first line with minimal number of tabs and + * spaces, according to current options */ + set_indent(get_indent(), TRUE); + + /* put cursor on last non-space */ + coladvance(MAXCOL); + while (curwin->w_cursor.col && vim_isspace(gchar_cursor())) + dec_cursor(); + curs_columns(FALSE); /* update curwin->w_virtcol */ + + /* do the formatting */ + State = INSERT; /* for Opencmd() */ + insertchar(NUL, TRUE, second_indent); + State = NORMAL; + first_par_line = TRUE; + second_indent = -1; + } + else + { + /* + * Still in same paragraph, so join the lines together. + * But first delete the comment leader from the second line. + */ + advance = FALSE; + curwin->w_cursor.lnum++; + curwin->w_cursor.col = 0; + while (next_leader_len--) + delchar(FALSE); + curwin->w_cursor.lnum--; + if (do_join(TRUE, FALSE) == FAIL) + { + beep_flush(); + break; + } + first_par_line = FALSE; + } + } + fo_do_comments = FALSE; + /* + * Leave the cursor at the first non-blank of the last formatted line. + * If the cursor was move one line back (e.g. with "Q}") go to the next + * line, so "." will do the next lines. + */ + if (op_end_adjusted && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) + ++curwin->w_cursor.lnum; + beginline(TRUE); + updateScreen(NOT_VALID); + msgmore(curbuf->b_ml.ml_line_count - old_line_count); +} + +/* + * Blank lines, and lines containing only the comment leader, are left + * untouched by the formatting. The function returns TRUE in this + * case. It also returns TRUE when a line starts with the end of a comment + * ('e' in comment flags), so that this line is skipped, and not joined to the + * previous line. A new paragraph starts after a blank line, or when the + * comment leader changes -- webb. + */ + static int +fmt_end_block(lnum, leader_len, leader_flags) + linenr_t lnum; + int *leader_len; + char_u **leader_flags; +{ + char_u *flags = NULL; /* init for GCC */ + char_u *ptr; + + ptr = ml_get(lnum); + *leader_len = get_leader_len(ptr, leader_flags); + + if (*leader_len > 0) + { + /* + * Search for 'e' flag in comment leader flags. + */ + flags = *leader_flags; + while (*flags && *flags != ':' && *flags != COM_END) + ++flags; + } + + return (ptr[*leader_len] == NUL || + (*leader_len > 0 && *flags == COM_END) || + startPS(lnum, NUL, FALSE)); +} + +/* + * prepare a few things for block mode yank/delete/tilde + * + * for delete: + * - textlen includes the first/last char to be (partly) deleted + * - start/endspaces is the number of columns that are taken by the + * first/last deleted char minus the number of columns that have to be deleted. + * for yank and tilde: + * - textlen includes the first/last char to be wholly yanked + * - start/endspaces is the number of columns of the first/last yanked char + * that are to be yanked. + */ + static void +block_prep(bd, lnum, is_del) + struct block_def *bd; + linenr_t lnum; + int is_del; +{ + colnr_t vcol; + int incr = 0; + char_u *pend; + char_u *pstart; + + bd->startspaces = 0; + bd->endspaces = 0; + bd->textlen = 0; + bd->textcol = 0; + vcol = 0; + pstart = ml_get(lnum); + while (vcol < op_start_vcol && *pstart) + { + /* Count a tab for what it's worth (if list mode not on) */ + incr = lbr_chartabsize(pstart, (colnr_t)vcol); + vcol += incr; + ++pstart; + ++bd->textcol; + } + if (vcol < op_start_vcol) /* line too short */ + { + if (!is_del) + bd->endspaces = op_end_vcol - op_start_vcol + 1; + } + else /* vcol >= op_start_vcol */ + { + bd->startspaces = vcol - op_start_vcol; + if (is_del && vcol > op_start_vcol) + bd->startspaces = incr - bd->startspaces; + pend = pstart; + if (vcol > op_end_vcol) /* it's all in one character */ + { + bd->startspaces = op_end_vcol - op_start_vcol + 1; + if (is_del) + bd->startspaces = incr - bd->startspaces; + } + else + { + while (vcol <= op_end_vcol && *pend) + { + /* Count a tab for what it's worth (if list mode not on) */ + incr = lbr_chartabsize(pend, (colnr_t)vcol); + vcol += incr; + ++pend; + } + if (vcol < op_end_vcol && !is_del) /* line too short */ + { + bd->endspaces = op_end_vcol - vcol; + } + else if (vcol > op_end_vcol) + { + bd->endspaces = vcol - op_end_vcol - 1; + if (!is_del && pend != pstart && bd->endspaces) + --pend; + } + } + if (is_del && bd->startspaces) + { + --pstart; + --bd->textcol; + } + bd->textlen = (int)(pend - pstart); + } + bd->textstart = pstart; +} + +#define NUMBUFLEN 30 + +/* + * add or subtract 'Prenum1' from a number in a line + * 'command' is CTRL-A for add, CTRL-X for subtract + * + * return FAIL for failure, OK otherwise + */ + int +do_addsub(command, Prenum1) + int command; + linenr_t Prenum1; +{ + register int col; + char_u buf[NUMBUFLEN]; + int hex; /* 'X': hexadecimal; '0': octal */ + static int hexupper = FALSE; /* 0xABC */ + long n; + char_u *ptr; + int i; + int c; + int zeros = 0; /* number of leading zeros */ + int digits = 0; /* number of digits in the number */ + + ptr = ml_get_curline(); + col = curwin->w_cursor.col; + + /* first check if we are on a hexadecimal number */ + while (col > 0 && isxdigit(ptr[col])) + --col; + if (col > 0 && (ptr[col] == 'X' || ptr[col] == 'x') && + ptr[col - 1] == '0' && isxdigit(ptr[col + 1])) + --col; /* found hexadecimal number */ + else + { + /* first search forward and then backward for start of number */ + col = curwin->w_cursor.col; + + while (ptr[col] != NUL && !isdigit(ptr[col])) + ++col; + + while (col > 0 && isdigit(ptr[col - 1])) + --col; + } + + if (isdigit(ptr[col]) && u_save_cursor() == OK) + { + ptr = ml_get_curline(); /* get it again, because + u_save may have changed it */ + curwin->w_set_curswant = TRUE; + + hex = 0; /* default is decimal */ + if (ptr[col] == '0') /* could be hex or octal */ + { + hex = TO_UPPER(ptr[col + 1]); /* assume hexadecimal */ + if (hex != 'X' || !isxdigit(ptr[col + 2])) + { + if (isdigit(hex)) + hex = '0'; /* octal */ + else + hex = 0; /* 0 by itself is decimal */ + } + } + + if (!hex && col > 0 && ptr[col - 1] == '-') + --col; + + ptr += col; + /* + * we copy the number into a buffer because some versions of sscanf + * cannot handle characters with the upper bit set, making some special + * characters handled like digits. + */ + for (i = 0; *ptr && !(*ptr & 0x80) && i < NUMBUFLEN - 1; ++i) + buf[i] = *ptr++; + buf[i] = NUL; + + if (hex == '0') + sscanf((char *)buf, "%lo", &n); + else if (hex) + sscanf((char *)buf + 2, "%lx", &n); /* "%X" doesn't work! */ + else + n = atol((char *)buf); + + if (command == Ctrl('A')) + n += Prenum1; + else + n -= Prenum1; + + if (hex == 'X') /* skip the '0x' */ + col += 2; + else if (hex == '0') + col++; /* skip the '0' */ + curwin->w_cursor.col = col; + + c = gchar_cursor(); + do /* delete the old number */ + { + if (digits == 0 && c == '0') + ++zeros; /* count the number of leading zeros */ + else + ++digits; /* count the number of digits */ + if (isalpha(c)) + { + if (isupper(c)) + hexupper = TRUE; + else + hexupper = FALSE; + } + (void)delchar(FALSE); + c = gchar_cursor(); + } + while (hex ? (hex == '0' ? c >= '0' && c <= '7' : + isxdigit(c)) : isdigit(c)); + + if (hex == 0) + sprintf((char *)buf, "%ld", n); + else + { + if (hex == '0') + sprintf((char *)buf, "%lo", n); + else if (hex && hexupper) + sprintf((char *)buf, "%lX", n); + else if (hex) + sprintf((char *)buf, "%lx", n); + /* adjust number of zeros to the new number of digits, so the + * total length of the number remains the same */ + if (zeros) + { + zeros += digits - STRLEN(buf); + if (zeros > 0) + { + vim_memmove(buf + zeros, buf, STRLEN(buf) + 1); + for (col = 0; zeros > 0; --zeros) + buf[col++] = '0'; + } + } + } + ins_str(buf); /* insert the new number */ + --curwin->w_cursor.col; + updateline(); + return OK; + } + else + { + beep_flush(); + return FAIL; + } +} + +#ifdef VIMINFO + int +read_viminfo_register(line, fp, force) + char_u *line; + FILE *fp; + int force; +{ + int eof; + int do_it = TRUE; + int size; + int limit; + int i; + int set_prev = FALSE; + char_u *str; + char_u **array = NULL; + + /* We only get here (hopefully) if line[0] == '"' */ + str = line + 1; + if (*str == '"') + { + set_prev = TRUE; + str++; + } + if (!isalnum(*str) && *str != '-') + { + EMSG2("viminfo: Illegal register name in line %s", line); + do_it = FALSE; + } + yankbuffer = *str++; + get_yank_buffer(FALSE); + yankbuffer = 0; + if (!force && y_current->y_array != NULL) + do_it = FALSE; + size = 0; + limit = 100; /* Optimized for registers containing <= 100 lines */ + if (do_it) + { + if (set_prev) + y_previous = y_current; + vim_free(y_current->y_array); + array = y_current->y_array = + (char_u **)alloc((unsigned)(limit * sizeof(char_u *))); + str = skipwhite(str); + if (STRNCMP(str, "CHAR", 4) == 0) + y_current->y_type = MCHAR; + else if (STRNCMP(str, "BLOCK", 5) == 0) + y_current->y_type = MBLOCK; + else + y_current->y_type = MLINE; + } + while (!(eof = vim_fgets(line, LSIZE, fp)) && line[0] == TAB) + { + if (do_it) + { + if (size >= limit) + { + y_current->y_array = (char_u **) + alloc((unsigned)(limit * 2 * sizeof(char_u *))); + for (i = 0; i < limit; i++) + y_current->y_array[i] = array[i]; + vim_free(array); + limit *= 2; + array = y_current->y_array; + } + viminfo_readstring(line); + str = strsave(line + 1); + if (str != NULL) + array[size++] = str; + else + do_it = FALSE; + } + } + if (do_it) + { + if (size == 0) + { + vim_free(array); + y_current->y_array = NULL; + } + else if (size < limit) + { + y_current->y_array = + (char_u **)alloc((unsigned)(size * sizeof(char_u *))); + for (i = 0; i < size; i++) + y_current->y_array[i] = array[i]; + vim_free(array); + } + y_current->y_size = size; + } + return eof; +} + + void +write_viminfo_registers(fp) + FILE *fp; +{ + int i, j; + char_u *type; + char_u c; + int num_lines; + int max_num_lines; + + fprintf(fp, "\n# Registers:\n"); + + max_num_lines = get_viminfo_parameter('"'); + if (max_num_lines == 0) + return; + for (i = 0; i < NUM_REGISTERS; i++) + { + if (y_buf[i].y_array == NULL) + continue; +#ifdef USE_GUI + /* Skip '*' register, we don't want it back next time */ + if (i == GUI_SELECTION_REGISTER) + continue; +#endif + switch (y_buf[i].y_type) + { + case MLINE: + type = (char_u *)"LINE"; + break; + case MCHAR: + type = (char_u *)"CHAR"; + break; + case MBLOCK: + type = (char_u *)"BLOCK"; + break; + default: + sprintf((char *)IObuff, "Unknown register type %d", + y_buf[i].y_type); + emsg(IObuff); + type = (char_u *)"LINE"; + break; + } + if (y_previous == &y_buf[i]) + fprintf(fp, "\""); + if (i == DELETION_REGISTER) + c = '-'; + else if (i < 10) + c = '0' + i; + else + c = 'a' + i - 10; + fprintf(fp, "\"%c\t%s\n", c, type); + num_lines = y_buf[i].y_size; + + /* If max_num_lines < 0, then we save ALL the lines in the register */ + if (max_num_lines > 0 && num_lines > max_num_lines) + num_lines = max_num_lines; + for (j = 0; j < num_lines; j++) + { + putc('\t', fp); + viminfo_writestring(fp, y_buf[i].y_array[j]); + } + } +} +#endif /* VIMINFO */ + +#if defined(USE_GUI) || defined(PROTO) +/* + * Text selection stuff that uses the GUI selection register '*'. When using a + * GUI this may be text from another window, otherwise it is the last text we + * had highlighted with VIsual mode. With mouse support, clicking the middle + * button performs the paste, otherwise you will need to do <"*p>. + */ + + void +gui_free_selection() +{ + struct yankbuf *y_ptr = y_current; + + y_current = &y_buf[GUI_SELECTION_REGISTER]; /* '*' register */ + free_yank_all(); + y_current->y_size = 0; + y_current = y_ptr; +} + +/* + * Get the selected text and put it in the gui text register '*'. + */ + void +gui_get_selection() +{ + struct yankbuf *old_y_previous, *old_y_current; + char_u old_yankbuffer; + FPOS old_cursor, old_visual; + int old_op_type; + + if (gui.selection.owned) + { + if (y_buf[GUI_SELECTION_REGISTER].y_array != NULL) + return; + + /* Get the text between gui.selection.start & gui.selection.end */ + old_y_previous = y_previous; + old_y_current = y_current; + old_yankbuffer = yankbuffer; + old_cursor = curwin->w_cursor; + old_visual = VIsual; + old_op_type = op_type; + yankbuffer = '*'; + op_type = YANK; + do_pending_operator('y', NUL, FALSE, NULL, NULL, 0, TRUE, TRUE); + y_previous = old_y_previous; + y_current = old_y_current; + yankbuffer = old_yankbuffer; + curwin->w_cursor = old_cursor; + VIsual = old_visual; + op_type = old_op_type; + } + else + { + gui_free_selection(); + + /* Try to get selected text from another window */ + gui_request_selection(); + } +} + +/* Convert from the GUI selection string into the '*' register */ + void +gui_yank_selection(type, str, len) + int type; + char_u *str; + long_u len; +{ + struct yankbuf *y_ptr = &y_buf[GUI_SELECTION_REGISTER]; /* '*' register */ + int lnum; + int start; + int i; + + gui_free_selection(); + + /* Count the number of lines within the string */ + y_ptr->y_size = 1; + for (i = 0; i < len; i++) + if (str[i] == '\n') + y_ptr->y_size++; + + if (type != MCHAR && i > 0 && str[i - 1] == '\n') + y_ptr->y_size--; + + y_ptr->y_array = (char_u **)lalloc(y_ptr->y_size * sizeof(char_u *), TRUE); + if (y_ptr->y_array == NULL) + return; + y_ptr->y_type = type; + lnum = 0; + start = 0; + for (i = 0; i < len; i++) + { + if (str[i] == NUL) + str[i] = '\n'; + else if (str[i] == '\n') + { + str[i] = NUL; + if (type == MCHAR || i != len - 1) + { + if ((y_ptr->y_array[lnum] = strsave(str + start)) == NULL) + { + y_ptr->y_size = lnum; + return; + } + lnum++; + start = i + 1; + } + } + } + if ((y_ptr->y_array[lnum] = alloc(i - start + 1)) == NULL) + return; + if (i - start > 0) + STRNCPY(y_ptr->y_array[lnum], str + start, i - start); + y_ptr->y_array[lnum][i - start] = NUL; + y_ptr->y_size = lnum + 1; +} + +/* + * Convert the '*' register into a GUI selection string returned in *str with + * length *len. + */ + int +gui_convert_selection(str, len) + char_u **str; + long_u *len; +{ + struct yankbuf *y_ptr = &y_buf[GUI_SELECTION_REGISTER]; /* '*' register */ + char_u *p; + int lnum; + int i, j; + + *str = NULL; + *len = 0; + if (y_ptr->y_array == NULL) + return -1; + + for (i = 0; i < y_ptr->y_size; i++) + *len += STRLEN(y_ptr->y_array[i]) + 1; + + /* + * Don't want newline character at end of last line if we're in MCHAR mode. + */ + if (y_ptr->y_type == MCHAR && *len > 1) + (*len)--; + + p = *str = lalloc(*len, TRUE); + if (p == NULL) + return -1; + lnum = 0; + for (i = 0, j = 0; i < *len; i++, j++) + { + if (y_ptr->y_array[lnum][j] == '\n') + p[i] = NUL; + else if (y_ptr->y_array[lnum][j] == NUL) + { + p[i] = '\n'; + lnum++; + j = -1; + } + else + p[i] = y_ptr->y_array[lnum][j]; + } + return y_ptr->y_type; +} +#endif /* USE_GUI || PROTO */ diff --git a/usr.bin/vim/ops.h b/usr.bin/vim/ops.h new file mode 100644 index 00000000000..af04066978e --- /dev/null +++ b/usr.bin/vim/ops.h @@ -0,0 +1,70 @@ +/* $OpenBSD: ops.h,v 1.1.1.1 1996/09/07 21:40:27 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * ops.h: Things mostly shared between normal.c, cmdline.c and ops.c + */ + +/* + * Operators + */ +#define NOP 0 /* no pending operation */ +#define DELETE 1 +#define YANK 2 +#define CHANGE 3 +#define LSHIFT 4 /* left shift */ +#define RSHIFT 5 /* right shift */ +#define FILTER 6 +#define TILDE 7 /* switch case */ +#define INDENT 8 +#define FORMAT 9 +#define COLON 10 +#define UPPER 11 /* make upper case */ +#define LOWER 12 /* make lower case */ +#define JOIN 13 /* only for visual mode */ +#define GFORMAT 14 /* "gq" */ + +/* + * operator characters; the order must correspond to the defines above! + */ +EXTERN char_u *opchars INIT(= (char_u *)"dyc<>!~=Q:UuJq"); + +/* + * When a cursor motion command is made, it is marked as being a character or + * line oriented motion. Then, if an operator is in effect, the operation + * becomes character or line oriented accordingly. + * + * Character motions are marked as being inclusive or not. Most char. motions + * are inclusive, but some (e.g. 'w') are not. + * + * Generally speaking, every command in normal() should either clear any pending + * operator (with CLEAROP), or set the motion type variable. + */ + +/* + * Motion types + */ +#define MCHAR 0 +#define MLINE 1 +#define MBLOCK 2 + +EXTERN int op_type INIT(= NOP); /* current pending operator type */ +EXTERN int op_motion_type; /* type of the current cursor motion */ +EXTERN int op_inclusive; /* TRUE if char motion is inclusive */ +EXTERN int op_block_mode INIT(= FALSE); + /* current operator is Visual block mode */ +EXTERN colnr_t op_start_vcol; /* start col for block mode operator */ +EXTERN colnr_t op_end_vcol; /* end col for block mode operator */ +EXTERN int op_end_adjusted; /* backuped op_end one char */ +EXTERN long op_line_count; /* number of lines from op_start to + op_end (inclusive) */ +EXTERN int op_empty; /* op_start and op_end the same */ +EXTERN int op_is_VIsual; /* opeartor on visual area */ + +EXTERN int yankbuffer INIT(= 0); /* current yank buffer */ diff --git a/usr.bin/vim/option.c b/usr.bin/vim/option.c new file mode 100644 index 00000000000..7fa7f665fa3 --- /dev/null +++ b/usr.bin/vim/option.c @@ -0,0 +1,4165 @@ +/* $OpenBSD: option.c,v 1.1.1.1 1996/09/07 21:40:25 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * Code to handle user-settable options. This is all pretty much table- + * driven. To add a new option, put it in the options array, and add a + * variable for it in option.h. If it's a numeric option, add any necessary + * bounds checks to do_set(). + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +struct option +{ + char *fullname; /* full option name */ + char *shortname; /* permissible abbreviation */ + short flags; /* see below */ + char_u *var; /* pointer to variable */ + char_u *def_val; /* default value for variable (can be the same + as the actual value) */ +}; + +/* + * Flags + * + * Note: P_EXPAND and P_IND can never be used at the same time. + * Note: P_IND cannot be used for a terminal option. + */ +#define P_BOOL 0x01 /* the option is boolean */ +#define P_NUM 0x02 /* the option is numeric */ +#define P_STRING 0x04 /* the option is a string */ +#define P_ALLOCED 0x08 /* the string option is in allocated memory, + must use vim_free() when assigning new + value. Not set if default is the same. */ +#define P_EXPAND 0x10 /* environment expansion */ +#define P_IND 0x20 /* indirect, is in curwin or curbuf */ +#define P_NODEFAULT 0x40 /* has no default value */ +#define P_DEF_ALLOCED 0x80 /* default value is in allocated memory, must + use vim_free() when assigning new value */ +#define P_WAS_SET 0x100 /* option has been set/reset */ +#define P_NO_MKRC 0x200 /* don't include in :mkvimrc output */ + +/* + * The options that are in curwin or curbuf have P_IND set and a var field + * that contains one of the values below. + */ +#define PV_LIST 1 +#define PV_NU 2 +#define PV_SCROLL 3 +#define PV_WRAP 4 +#define PV_LBR 5 + +#define PV_AI 6 +#define PV_BIN 7 +#define PV_CIN 8 +#define PV_CINK 9 +#define PV_CINO 10 +#define PV_CINW 11 +#define PV_COM 12 +#define PV_EOL 13 +#define PV_ET 14 +#define PV_FO 15 +#define PV_LISP 16 +#define PV_ML 17 +#define PV_MOD 18 +#define PV_RO 20 +#define PV_SI 21 +#define PV_SN 22 +#define PV_SW 23 +#define PV_TS 24 +#define PV_TW 25 +#define PV_TX 26 +#define PV_WM 27 +#define PV_ISK 28 +#define PV_INF 29 +#define PV_RL 30 + +/* + * The option structure is initialized here. + * The order of the options should be alphabetic for ":set all". + * The options with a NULL variable are 'hidden': a set command for + * them is ignored and they are not printed. + */ +static struct option options[] = +{ +#ifdef RIGHTLEFT + {"aleph", "al", P_NUM, (char_u *)&p_aleph, +# if defined(MSDOS) || defined(WIN32) || defined(OS2) + (char_u *)128L}, +# else + (char_u *)224L}, +# endif +#endif + {"autoindent", "ai", P_BOOL|P_IND, (char_u *)PV_AI, + (char_u *)FALSE}, + {"autoprint", "ap", P_BOOL, (char_u *)NULL, + (char_u *)FALSE}, + {"autowrite", "aw", P_BOOL, (char_u *)&p_aw, + (char_u *)FALSE}, + {"backspace", "bs", P_NUM, (char_u *)&p_bs, + (char_u *)0L}, + {"backup", "bk", P_BOOL, (char_u *)&p_bk, + (char_u *)FALSE}, + {"backupdir", "bdir", P_STRING|P_EXPAND, + (char_u *)&p_bdir, + (char_u *)DEF_BDIR}, + {"backupext", "bex", P_STRING, (char_u *)&p_bex, +#ifdef VMS + (char_u *)"_"}, +#else + (char_u *)"~"}, +#endif + {"beautify", "bf", P_BOOL, (char_u *)NULL, + (char_u *)FALSE}, + {"binary", "bin", P_BOOL|P_IND, (char_u *)PV_BIN, + (char_u *)FALSE}, + {"bioskey", "biosk",P_BOOL, +#ifdef MSDOS + (char_u *)&p_biosk, +#else + (char_u *)NULL, +#endif + (char_u *)TRUE}, + {"breakat", "brk", P_STRING, (char_u *)&p_breakat, + (char_u *)" \t!@*-+_;:,./?"}, +#ifdef CINDENT + {"cindent", "cin", P_BOOL|P_IND, (char_u *)PV_CIN, + (char_u *)FALSE}, + {"cinkeys", "cink", P_STRING|P_IND|P_ALLOCED, (char_u *)PV_CINK, + (char_u *)"0{,0},:,0#,!^F,o,O,e"}, + {"cinoptions", "cino", P_STRING|P_IND|P_ALLOCED, (char_u *)PV_CINO, + (char_u *)""}, +#endif /* CINDENT */ +#if defined(SMARTINDENT) || defined(CINDENT) + {"cinwords", "cinw", P_STRING|P_IND|P_ALLOCED, (char_u *)PV_CINW, + (char_u *)"if,else,while,do,for,switch"}, +#endif + {"cmdheight", "ch", P_NUM, (char_u *)&p_ch, + (char_u *)1L}, + {"columns", "co", P_NUM|P_NODEFAULT|P_NO_MKRC, (char_u *)&Columns, + (char_u *)80L}, + {"comments", "com", P_STRING|P_IND|P_ALLOCED, (char_u *)PV_COM, + (char_u *)"sr:/*,mb:*,el:*/,://,b:#,:%,:XCOMM,n:>,fb:-"}, + {"compatible", "cp", P_BOOL, (char_u *)&p_cp, + (char_u *)FALSE}, + {"cpoptions", "cpo", P_STRING, (char_u *)&p_cpo, +#ifdef COMPATIBLE + (char_u *)CPO_ALL}, +#else + (char_u *)CPO_DEFAULT}, +#endif + {"define", "def", P_STRING, (char_u *)&p_def, + (char_u *)"^#[ \\t]*define"}, + {"dictionary", "dict", P_STRING|P_EXPAND, (char_u *)&p_dict, + (char_u *)""}, + {"digraph", "dg", P_BOOL, +#ifdef DIGRAPHS + (char_u *)&p_dg, +#else + (char_u *)NULL, +#endif /* DIGRAPHS */ + (char_u *)FALSE}, + {"directory", "dir", P_STRING|P_EXPAND, (char_u *)&p_dir, + (char_u *)DEF_DIR}, + {"edcompatible","ed", P_BOOL, (char_u *)&p_ed, + (char_u *)FALSE}, + {"endofline", "eol", P_BOOL|P_IND|P_NO_MKRC, (char_u *)PV_EOL, + (char_u *)FALSE}, + {"equalalways", "ea", P_BOOL, (char_u *)&p_ea, + (char_u *)TRUE}, + {"equalprg", "ep", P_STRING|P_EXPAND, (char_u *)&p_ep, + (char_u *)""}, + {"errorbells", "eb", P_BOOL, (char_u *)&p_eb, + (char_u *)FALSE}, + {"errorfile", "ef", P_STRING|P_EXPAND, (char_u *)&p_ef, +#ifdef AMIGA + (char_u *)"AztecC.Err"}, +#else + (char_u *)"errors.vim"}, +#endif + {"errorformat", "efm", P_STRING, (char_u *)&p_efm, +#ifdef AMIGA + /* don't use [^0-9] here, Manx C can't handle it */ + (char_u *)"%f>%l:%c:%t:%n:%m,%f:%l: %t%*[^0123456789]%n: %m,%f %l %t%*[^0123456789]%n: %m,%*[^\"]\"%f\"%*[^0123456789]%l: %m,%f:%l:%m"}, +#else +# if defined MSDOS || defined WIN32 + (char_u *)"%*[^\"]\"%f\"%*[^0-9]%l: %m,%f(%l) : %m,%*[^ ] %f %l: %m,%f:%l:%m"}, +# elif defined(__EMX__) /* put most common here (i.e. gcc format) at front */ + (char_u *)"%f:%l:%m,%*[^\"]\"%f\"%*[^0-9]%l: %m,\"%f\"%*[^0-9]%l: %m"}, +# else + (char_u *)"%*[^\"]\"%f\"%*[^0-9]%l: %m,\"%f\"%*[^0-9]%l: %m,%f:%l:%m"}, +# endif +#endif + {"esckeys", "ek", P_BOOL, (char_u *)&p_ek, +#ifdef COMPATIBLE + (char_u *)FALSE}, +#else + (char_u *)TRUE}, +#endif + {"expandtab", "et", P_BOOL|P_IND, (char_u *)PV_ET, + (char_u *)FALSE}, + {"exrc", NULL, P_BOOL, (char_u *)&p_exrc, + (char_u *)FALSE}, + {"flash", "fl", P_BOOL, (char_u *)NULL, + (char_u *)FALSE}, + {"formatoptions","fo", P_STRING|P_IND|P_ALLOCED, (char_u *)PV_FO, +#ifdef COMPATIBLE + (char_u *)FO_DFLT_VI}, +#else + (char_u *)FO_DFLT}, +#endif + {"formatprg", "fp", P_STRING|P_EXPAND, (char_u *)&p_fp, + (char_u *)""}, + {"gdefault", "gd", P_BOOL, (char_u *)&p_gd, + (char_u *)FALSE}, + {"graphic", "gr", P_BOOL, (char_u *)NULL, + (char_u *)FALSE}, + {"guifont", "gfn", P_STRING, +#ifdef USE_GUI + (char_u *)&p_guifont, + (char_u *)""}, +#else + (char_u *)NULL, + (char_u *)NULL}, +#endif + {"guioptions", "go", P_STRING, +#ifdef USE_GUI + (char_u *)&p_guioptions, +# ifdef UNIX + (char_u *)"aAgmr"}, +# else + (char_u *)"Agmr"}, +# endif +#else + (char_u *)NULL, + (char_u *)NULL}, +#endif +#if defined(USE_GUI) + {"guipty", NULL, P_BOOL, (char_u *)&p_guipty, + (char_u *)FALSE}, +#endif + {"hardtabs", "ht", P_NUM, (char_u *)NULL, + (char_u *)0L}, + {"helpfile", "hf", P_STRING|P_EXPAND, (char_u *)&p_hf, + (char_u *)""}, + {"helpheight", "hh", P_NUM, (char_u *)&p_hh, + (char_u *)20L}, + {"hidden", "hid", P_BOOL, (char_u *)&p_hid, + (char_u *)FALSE}, + {"highlight", "hl", P_STRING, (char_u *)&p_hl, + (char_u *)"8b,db,es,hs,mb,Mn,nu,rs,sr,tb,vr,ws"}, + {"history", "hi", P_NUM, (char_u *)&p_hi, +#ifdef COMPATIBLE + (char_u *)0L}, +#else + (char_u *)20L}, +#endif +#ifdef RIGHTLEFT + {"hkmap", "hk", P_BOOL, (char_u *)&p_hkmap, + (char_u *)FALSE}, +#endif + {"icon", NULL, P_BOOL, (char_u *)&p_icon, + (char_u *)FALSE}, + {"ignorecase", "ic", P_BOOL, (char_u *)&p_ic, + (char_u *)FALSE}, + {"include", "inc", P_STRING, (char_u *)&p_inc, + (char_u *)"^#[ \\t]*include"}, + {"incsearch", "is", P_BOOL, (char_u *)&p_is, + (char_u *)FALSE}, + {"infercase", "inf", P_BOOL|P_IND, (char_u *)PV_INF, + (char_u *)FALSE}, + {"insertmode", "im", P_BOOL, (char_u *)&p_im, + (char_u *)FALSE}, + {"isfname", "isf", P_STRING, (char_u *)&p_isf, +#ifdef BACKSLASH_IN_FILENAME + (char_u *)"@,48-57,/,.,-,_,+,,,$,:,\\"}, +#else +# ifdef AMIGA + (char_u *)"@,48-57,/,.,-,_,+,,,$,:"}, +# else /* UNIX */ + (char_u *)"@,48-57,/,.,-,_,+,,,$,:,~"}, +# endif +#endif + {"isident", "isi", P_STRING, (char_u *)&p_isi, +#if defined(MSDOS) || defined(WIN32) || defined(OS2) + (char_u *)"@,48-57,_,128-167,224-235"}, +#else + (char_u *)"@,48-57,_,192-255"}, +#endif + {"iskeyword", "isk", P_STRING|P_IND|P_ALLOCED, (char_u *)PV_ISK, +#ifdef COMPATIBLE + (char_u *)"@,48-57,_"}, +#else +# if defined MSDOS || defined WIN32 + (char_u *)"@,48-57,_,128-167,224-235"}, +# else + (char_u *)"@,48-57,_,192-255"}, +# endif +#endif + {"isprint", "isp", P_STRING, (char_u *)&p_isp, +#if defined MSDOS || defined WIN32 + (char_u *)"@,~-255"}, +#else + (char_u *)"@,161-255"}, +#endif + {"joinspaces", "js", P_BOOL, (char_u *)&p_js, + (char_u *)TRUE}, + {"keywordprg", "kp", P_STRING|P_EXPAND, (char_u *)&p_kp, +#if defined(MSDOS) || defined(WIN32) + (char_u *)""}, +#else + (char_u *)"man"}, +#endif + {"langmap", "lmap", P_STRING, +#ifdef HAVE_LANGMAP + (char_u *)&p_langmap, + (char_u *)""}, +#else + (char_u *)NULL, + (char_u *)NULL}, +#endif + {"laststatus", "ls", P_NUM, (char_u *)&p_ls, + (char_u *)1L}, + {"linebreak", "lbr", P_BOOL|P_IND, (char_u *)PV_LBR, + (char_u *)FALSE}, + {"lines", NULL, P_NUM|P_NODEFAULT|P_NO_MKRC, (char_u *)&Rows, +#if defined MSDOS || defined WIN32 + (char_u *)25L}, +#else + (char_u *)24L}, +#endif + {"lisp", NULL, P_BOOL|P_IND, (char_u *)PV_LISP, + (char_u *)FALSE}, + {"list", NULL, P_BOOL|P_IND, (char_u *)PV_LIST, + (char_u *)FALSE}, + {"magic", NULL, P_BOOL, (char_u *)&p_magic, + (char_u *)TRUE}, + {"makeprg", "mp", P_STRING|P_EXPAND, (char_u *)&p_mp, + (char_u *)"make"}, + {"maxmapdepth", "mmd", P_NUM, (char_u *)&p_mmd, + (char_u *)1000L}, + {"maxmem", "mm", P_NUM, (char_u *)&p_mm, + (char_u *)MAXMEM}, + {"maxmemtot", "mmt", P_NUM, (char_u *)&p_mmt, + (char_u *)MAXMEMTOT}, + {"mesg", NULL, P_BOOL, (char_u *)NULL, + (char_u *)FALSE}, + {"modeline", "ml", P_BOOL|P_IND, (char_u *)PV_ML, +#ifdef COMPATIBLE + (char_u *)FALSE}, +#else + (char_u *)TRUE}, +#endif + {"modelines", "mls", P_NUM, (char_u *)&p_mls, + (char_u *)5L}, + {"modified", "mod", P_BOOL|P_IND|P_NO_MKRC, (char_u *)PV_MOD, + (char_u *)FALSE}, + {"more", NULL, P_BOOL, (char_u *)&p_more, +#ifdef COMPATIBLE + (char_u *)FALSE}, +#else + (char_u *)TRUE}, +#endif + {"mouse", NULL, P_STRING, (char_u *)&p_mouse, +#if defined(MSDOS) || defined(WIN32) + (char_u *)"a"}, +#else + (char_u *)""}, +#endif + {"mousetime", "mouset", P_NUM, (char_u *)&p_mouset, + (char_u *)500L}, + {"novice", NULL, P_BOOL, (char_u *)NULL, + (char_u *)FALSE}, + {"number", "nu", P_BOOL|P_IND, (char_u *)PV_NU, + (char_u *)FALSE}, + {"open", NULL, P_BOOL, (char_u *)NULL, + (char_u *)FALSE}, + {"optimize", "opt", P_BOOL, (char_u *)NULL, + (char_u *)FALSE}, + {"paragraphs", "para", P_STRING, (char_u *)&p_para, + (char_u *)"IPLPPPQPP LIpplpipbp"}, + {"paste", NULL, P_BOOL, (char_u *)&p_paste, + (char_u *)FALSE}, + {"patchmode", "pm", P_STRING, (char_u *)&p_pm, + (char_u *)""}, + {"path", "pa", P_STRING|P_EXPAND, (char_u *)&p_path, +#if defined AMIGA || defined MSDOS || defined WIN32 + (char_u *)".,,"}, +#elif defined(__EMX__) + (char_u *)".,/emx/include,,"}, +#else + (char_u *)".,/usr/include,,"}, +#endif + {"prompt", NULL, P_BOOL, (char_u *)NULL, + (char_u *)FALSE}, + {"readonly", "ro", P_BOOL|P_IND, (char_u *)PV_RO, + (char_u *)FALSE}, + {"redraw", NULL, P_BOOL, (char_u *)NULL, + (char_u *)FALSE}, + {"remap", NULL, P_BOOL, (char_u *)&p_remap, + (char_u *)TRUE}, + {"report", NULL, P_NUM, (char_u *)&p_report, + (char_u *)2L}, +#ifdef WIN32 + {"restorescreen", "rs", P_BOOL, (char_u *)&p_rs, + (char_u *)TRUE}, +#endif +#ifdef RIGHTLEFT + {"revins", "ri", P_BOOL, (char_u *)&p_ri, + (char_u *)FALSE}, + {"rightleft", "rl", P_BOOL|P_IND, (char_u *)PV_RL, + (char_u *)FALSE}, +#endif + {"ruler", "ru", P_BOOL, (char_u *)&p_ru, + (char_u *)FALSE}, + {"scroll", "scr", P_NUM|P_IND|P_NO_MKRC, (char_u *)PV_SCROLL, + (char_u *)12L}, + {"scrolljump", "sj", P_NUM, (char_u *)&p_sj, + (char_u *)1L}, + {"scrolloff", "so", P_NUM, (char_u *)&p_so, + (char_u *)0L}, + {"sections", "sect", P_STRING, (char_u *)&p_sections, + (char_u *)"SHNHH HUnhsh"}, + {"secure", NULL, P_BOOL, (char_u *)&p_secure, + (char_u *)FALSE}, + {"shell", "sh", P_STRING|P_EXPAND, (char_u *)&p_sh, +#if defined(MSDOS) + (char_u *)"command"}, +#elif defined(WIN32) + (char_u *)""}, /* set in set_init_1() */ +#elif defined(__EMX__) + (char_u *)"cmd.exe"}, +#elif defined(ARCHIE) + (char_u *)"gos"}, +#else + (char_u *)"sh"}, +#endif + {"shellpipe", "sp", P_STRING, (char_u *)&p_sp, +#if defined(UNIX) || defined(OS2) +# ifdef ARCHIE + (char_u *)"2>"}, +# else + (char_u *)"| tee"}, +# endif +#else + (char_u *)">"}, +#endif + {"shellredir", "srr", P_STRING, (char_u *)&p_srr, + (char_u *)">"}, + {"shelltype", "st", P_NUM, (char_u *)&p_st, + (char_u *)0L}, + {"shiftround", "sr", P_BOOL, (char_u *)&p_sr, + (char_u *)FALSE}, + {"shiftwidth", "sw", P_NUM|P_IND, (char_u *)PV_SW, + (char_u *)8L}, + {"shortmess", "shm", P_STRING, (char_u *)&p_shm, + (char_u *)""}, + {"shortname", "sn", P_BOOL|P_IND, +#ifdef SHORT_FNAME + (char_u *)NULL, +#else + (char_u *)PV_SN, +#endif + (char_u *)FALSE}, + {"showbreak", "sbr", P_STRING, (char_u *)&p_sbr, + (char_u *)""}, + {"showcmd", "sc", P_BOOL, (char_u *)&p_sc, +#if defined(COMPATIBLE) || defined(UNIX) + (char_u *)FALSE}, +#else + (char_u *)TRUE}, +#endif + {"showmatch", "sm", P_BOOL, (char_u *)&p_sm, + (char_u *)FALSE}, + {"showmode", "smd", P_BOOL, (char_u *)&p_smd, +#if defined(COMPATIBLE) + (char_u *)FALSE}, +#else + (char_u *)TRUE}, +#endif + {"sidescroll", "ss", P_NUM, (char_u *)&p_ss, + (char_u *)0L}, + {"slowopen", "slow", P_BOOL, (char_u *)NULL, + (char_u *)FALSE}, + {"smartcase", "scs", P_BOOL, (char_u *)&p_scs, + (char_u *)FALSE}, +#ifdef SMARTINDENT + {"smartindent", "si", P_BOOL|P_IND, (char_u *)PV_SI, + (char_u *)FALSE}, +#endif + {"smarttab", "sta", P_BOOL, (char_u *)&p_sta, + (char_u *)FALSE}, + {"sourceany", NULL, P_BOOL, (char_u *)NULL, + (char_u *)FALSE}, + {"splitbelow", "sb", P_BOOL, (char_u *)&p_sb, + (char_u *)FALSE}, + {"startofline", "sol", P_BOOL, (char_u *)&p_sol, + (char_u *)TRUE}, + {"suffixes", "su", P_STRING, (char_u *)&p_su, + (char_u *)".bak,~,.o,.h,.info,.swp"}, + {"swapsync", "sws", P_STRING, (char_u *)&p_sws, + (char_u *)"fsync"}, + {"tabstop", "ts", P_NUM|P_IND, (char_u *)PV_TS, + (char_u *)8L}, + {"taglength", "tl", P_NUM, (char_u *)&p_tl, + (char_u *)0L}, + {"tagrelative", "tr", P_BOOL, (char_u *)&p_tr, +#if defined(COMPATIBLE) + (char_u *)FALSE}, +#else + (char_u *)TRUE}, +#endif + {"tags", "tag", P_STRING|P_EXPAND, (char_u *)&p_tags, +#ifdef EMACS_TAGS + (char_u *)"./tags,./TAGS,tags,TAGS"}, +#else + (char_u *)"./tags,tags"}, +#endif + {"tagstack", "tgst", P_BOOL, (char_u *)NULL, + (char_u *)FALSE}, + {"term", NULL, P_STRING|P_EXPAND|P_NODEFAULT|P_NO_MKRC, + (char_u *)&term_strings[KS_NAME], + (char_u *)""}, + {"terse", NULL, P_BOOL, (char_u *)&p_terse, + (char_u *)FALSE}, + {"textauto", "ta", P_BOOL, (char_u *)&p_ta, +#if defined(COMPATIBLE) + (char_u *)FALSE}, +#else + (char_u *)TRUE}, +#endif + {"textmode", "tx", P_BOOL|P_IND, (char_u *)PV_TX, +#ifdef USE_CRNL + (char_u *)TRUE}, +#else + (char_u *)FALSE}, +#endif + {"textwidth", "tw", P_NUM|P_IND, (char_u *)PV_TW, + (char_u *)0L}, + {"tildeop", "top", P_BOOL, (char_u *)&p_to, + (char_u *)FALSE}, + {"timeout", "to", P_BOOL, (char_u *)&p_timeout, + (char_u *)TRUE}, + {"timeoutlen", "tm", P_NUM, (char_u *)&p_tm, + (char_u *)1000L}, + {"title", NULL, P_BOOL, (char_u *)&p_title, + (char_u *)FALSE}, + {"ttimeout", NULL, P_BOOL, (char_u *)&p_ttimeout, + (char_u *)FALSE}, + {"ttimeoutlen", "ttm", P_NUM, (char_u *)&p_ttm, + (char_u *)-1L}, + {"ttybuiltin", "tbi", P_BOOL, (char_u *)&p_tbi, + (char_u *)TRUE}, + {"ttyfast", "tf", P_BOOL|P_NO_MKRC, (char_u *)&p_tf, + (char_u *)FALSE}, + {"ttyscroll", "tsl", P_NUM, (char_u *)&p_ttyscroll, + (char_u *)999L}, + {"ttytype", "tty", P_STRING|P_EXPAND|P_NODEFAULT|P_NO_MKRC, + (char_u *)&term_strings[KS_NAME], + (char_u *)""}, + {"undolevels", "ul", P_NUM, (char_u *)&p_ul, +#ifdef COMPATIBLE + (char_u *)0L}, +#else +# if defined(UNIX) || defined(WIN32) || defined(OS2) + (char_u *)1000L}, +# else + (char_u *)100L}, +# endif +#endif + {"updatecount", "uc", P_NUM, (char_u *)&p_uc, +#ifdef COMPATIBLE + (char_u *)0L}, +#else + (char_u *)200L}, +#endif + {"updatetime", "ut", P_NUM, (char_u *)&p_ut, + (char_u *)4000L}, + {"viminfo", "vi", P_STRING, +#ifdef VIMINFO + (char_u *)&p_viminfo, +#else + (char_u *)NULL, +#endif /* VIMINFO */ + (char_u *)""}, + {"visualbell", "vb", P_BOOL, (char_u *)&p_vb, + (char_u *)FALSE}, + {"w300", NULL, P_NUM, (char_u *)NULL, + (char_u *)0L}, + {"w1200", NULL, P_NUM, (char_u *)NULL, + (char_u *)0L}, + {"w9600", NULL, P_NUM, (char_u *)NULL, + (char_u *)0L}, + {"warn", NULL, P_BOOL, (char_u *)&p_warn, + (char_u *)TRUE}, + {"weirdinvert", "wiv", P_BOOL, (char_u *)&p_wiv, + (char_u *)FALSE}, + {"whichwrap", "ww", P_STRING, (char_u *)&p_ww, +#ifdef COMPATIBLE + (char_u *)""}, +#else + (char_u *)"b,s"}, +#endif + {"wildchar", "wc", P_NUM, (char_u *)&p_wc, +#ifdef COMPATIBLE + (char_u *)(long)Ctrl('E')}, +#else + (char_u *)(long)TAB}, +#endif + {"window", "wi", P_NUM, (char_u *)NULL, + (char_u *)0L}, + {"winheight", "wh", P_NUM, (char_u *)&p_wh, + (char_u *)0L}, + {"wrap", NULL, P_BOOL|P_IND, (char_u *)PV_WRAP, + (char_u *)TRUE}, + {"wrapmargin", "wm", P_NUM|P_IND, (char_u *)PV_WM, + (char_u *)0L}, + {"wrapscan", "ws", P_BOOL, (char_u *)&p_ws, + (char_u *)TRUE}, + {"writeany", "wa", P_BOOL, (char_u *)&p_wa, + (char_u *)FALSE}, + {"writebackup", "wb", P_BOOL, (char_u *)&p_wb, +#if defined(COMPATIBLE) && !defined(WRITEBACKUP) + (char_u *)FALSE}, +#else + (char_u *)TRUE}, +#endif + {"writedelay", "wd", P_NUM, (char_u *)&p_wd, + (char_u *)0L}, + +/* terminal output codes */ + {"t_AL", NULL, P_STRING, (char_u *)&term_strings[KS_CAL], + (char_u *)""}, + {"t_al", NULL, P_STRING, (char_u *)&term_strings[KS_AL], + (char_u *)""}, + {"t_cd", NULL, P_STRING, (char_u *)&term_strings[KS_CD], + (char_u *)""}, + {"t_ce", NULL, P_STRING, (char_u *)&term_strings[KS_CE], + (char_u *)""}, + {"t_cl", NULL, P_STRING, (char_u *)&term_strings[KS_CL], + (char_u *)""}, + {"t_cm", NULL, P_STRING, (char_u *)&term_strings[KS_CM], + (char_u *)""}, + {"t_CS", NULL, P_STRING, (char_u *)&term_strings[KS_CSC], + (char_u *)""}, + {"t_cs", NULL, P_STRING, (char_u *)&term_strings[KS_CS], + (char_u *)""}, + {"t_da", NULL, P_STRING, (char_u *)&term_strings[KS_DA], + (char_u *)""}, + {"t_db", NULL, P_STRING, (char_u *)&term_strings[KS_DB], + (char_u *)""}, + {"t_DL", NULL, P_STRING, (char_u *)&term_strings[KS_CDL], + (char_u *)""}, + {"t_dl", NULL, P_STRING, (char_u *)&term_strings[KS_DL], + (char_u *)""}, + {"t_ke", NULL, P_STRING, (char_u *)&term_strings[KS_KE], + (char_u *)""}, + {"t_ks", NULL, P_STRING, (char_u *)&term_strings[KS_KS], + (char_u *)""}, + {"t_md", NULL, P_STRING, (char_u *)&term_strings[KS_MD], + (char_u *)""}, + {"t_me", NULL, P_STRING, (char_u *)&term_strings[KS_ME], + (char_u *)""}, + {"t_mr", NULL, P_STRING, (char_u *)&term_strings[KS_MR], + (char_u *)""}, + {"t_ms", NULL, P_STRING, (char_u *)&term_strings[KS_MS], + (char_u *)""}, + {"t_RI", NULL, P_STRING, (char_u *)&term_strings[KS_CRI], + (char_u *)""}, + {"t_se", NULL, P_STRING, (char_u *)&term_strings[KS_SE], + (char_u *)""}, + {"t_so", NULL, P_STRING, (char_u *)&term_strings[KS_SO], + (char_u *)""}, + {"t_sr", NULL, P_STRING, (char_u *)&term_strings[KS_SR], + (char_u *)""}, + {"t_te", NULL, P_STRING, (char_u *)&term_strings[KS_TE], + (char_u *)""}, + {"t_ti", NULL, P_STRING, (char_u *)&term_strings[KS_TI], + (char_u *)""}, + {"t_ue", NULL, P_STRING, (char_u *)&term_strings[KS_UE], + (char_u *)""}, + {"t_us", NULL, P_STRING, (char_u *)&term_strings[KS_US], + (char_u *)""}, + {"t_vb", NULL, P_STRING, (char_u *)&term_strings[KS_VB], + (char_u *)""}, + {"t_ve", NULL, P_STRING, (char_u *)&term_strings[KS_VE], + (char_u *)""}, + {"t_vi", NULL, P_STRING, (char_u *)&term_strings[KS_VI], + (char_u *)""}, + {"t_vs", NULL, P_STRING, (char_u *)&term_strings[KS_VS], + (char_u *)""}, + {"t_ZH", NULL, P_STRING, (char_u *)&term_strings[KS_CZH], + (char_u *)""}, + {"t_ZR", NULL, P_STRING, (char_u *)&term_strings[KS_CZR], + (char_u *)""}, + +/* terminal key codes are not here */ + + {NULL, NULL, 0, NULL, NULL} /* end marker */ +}; + +#define PARAM_COUNT (sizeof(options) / sizeof(struct option)) + +#ifdef AUTOCMD +/* + * structures for automatic commands + */ + +typedef struct AutoCmd +{ + char_u *cmd; /* The command to be executed */ + struct AutoCmd *next; /* Next AutoCmd in list */ +} AutoCmd; + +typedef struct AutoPat +{ + char_u *pat; /* pattern as typed */ + char_u *reg_pat; /* pattern converted to regexp */ + int allow_directories; /* Pattern may match whole path */ + AutoCmd *cmds; /* list of commands to do */ + struct AutoPat *next; /* next AutoPat in AutoPat list */ +} AutoPat; + +static struct event_name +{ + char *name; /* event name */ + int event; /* event number */ +} event_names[] = +{ + {"BufEnter", EVENT_BUFENTER}, + {"BufLeave", EVENT_BUFLEAVE}, + {"BufNewFile", EVENT_BUFNEWFILE}, + {"BufReadPost", EVENT_BUFREADPOST}, + {"BufReadPre", EVENT_BUFREADPRE}, + {"BufRead", EVENT_BUFREADPOST}, + {"BufWritePost", EVENT_BUFWRITEPOST}, + {"BufWritePre", EVENT_BUFWRITEPRE}, + {"BufWrite", EVENT_BUFWRITEPRE}, + {"FileAppendPost", EVENT_FILEAPPENDPOST}, + {"FileAppendPre", EVENT_FILEAPPENDPRE}, + {"FileReadPost", EVENT_FILEREADPOST}, + {"FileReadPre", EVENT_FILEREADPRE}, + {"FileWritePost", EVENT_FILEWRITEPOST}, + {"FileWritePre", EVENT_FILEWRITEPRE}, + {"FilterReadPost", EVENT_FILTERREADPOST}, + {"FilterReadPre", EVENT_FILTERREADPRE}, + {"FilterWritePost", EVENT_FILTERWRITEPOST}, + {"FilterWritePre", EVENT_FILTERWRITEPRE}, + {"VimLeave", EVENT_VIMLEAVE}, + {"WinEnter", EVENT_WINENTER}, + {"WinLeave", EVENT_WINLEAVE}, + {NULL, 0} +}; + +static AutoPat *first_autopat[NUM_EVENTS] = +{ + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL +}; +#endif + +static void set_option_default __ARGS((int, int)); +static void illegal_char __ARGS((char_u *, int)); +static char_u *option_expand __ARGS((int)); +static int findoption __ARGS((char_u *)); +static int find_key_option __ARGS((char_u *)); +static void showoptions __ARGS((int)); +static int option_changed __ARGS((struct option *)); +static void showoneopt __ARGS((struct option *)); +static int istermoption __ARGS((struct option *)); +static char_u *get_varp __ARGS((struct option *)); +static void option_value2string __ARGS((struct option *)); +#ifdef HAVE_LANGMAP +static void langmap_init __ARGS((void)); +static void langmap_set __ARGS((void)); +#endif +static void paste_option_changed __ARGS((void)); +static void p_compatible_set __ARGS((void)); +static void fill_breakat_flags __ARGS((void)); + +/* + * Initialize the options, first part. + * + * Called only once from main(), just after creating the first buffer. + */ + void +set_init_1() +{ + char_u *p; + int opt_idx; + long n; + +#ifdef HAVE_LANGMAP + langmap_init(); +#endif + +/* + * Find default value for 'shell' option. + */ + if ((p = vim_getenv((char_u *)"SHELL")) != NULL +#if defined(MSDOS) || defined(WIN32) || defined(OS2) +# ifdef __EMX__ + || (p = vim_getenv((char_u *)"EMXSHELL")) != NULL +# endif + || (p = vim_getenv((char_u *)"COMSPEC")) != NULL +# ifdef WIN32 + || (p = default_shell()) != NULL +# endif +#endif + ) + { + p = strsave(p); + if (p != NULL) /* we don't want a NULL */ + { + opt_idx = findoption((char_u *)"sh"); + options[opt_idx].def_val = p; + options[opt_idx].flags |= P_DEF_ALLOCED; + } + } + +/* + * Set default for 'helpfile' option. This cannot be done at compile time, + * because for Unix it is an external variable. + */ + opt_idx = findoption((char_u *)"hf"); +#if defined(HAVE_CONFIG_H) || defined(OS2) + options[opt_idx].def_val = help_fname; +#else + options[opt_idx].def_val = (char_u *)VIM_HLP; +#endif + +/* + * 'maxmemtot' and 'maxmem' may have to be adjusted for available memory + */ + opt_idx = findoption((char_u *)"maxmemtot"); + if (options[opt_idx].def_val == (char_u *)0L) + { + n = (mch_avail_mem(FALSE) >> 11); + options[opt_idx].def_val = (char_u *)n; + opt_idx = findoption((char_u *)"maxmem"); + if ((long)options[opt_idx].def_val > n || + (long)options[opt_idx].def_val == 0L) + options[opt_idx].def_val = (char_u *)n; + } + +/* + * set all the options (except the terminal options) to their default value + */ + for (opt_idx = 0; !istermoption(&options[opt_idx]); opt_idx++) + if (!(options[opt_idx].flags & P_NODEFAULT)) + set_option_default(opt_idx, FALSE); + + curbuf->b_p_initialized = TRUE; + check_buf_options(curbuf); + check_options(); + + /* + * initialize the table for 'iskeyword' et.al. + * Must be before option_expand(), because that one needs isidchar() + */ + init_chartab(); + + /* + * initialize the table for 'breakat'. + */ + fill_breakat_flags(); + + /* + * Expand environment variables and things like "~" for the defaults. + * If option_expand() returns non-NULL the variable is expanded. This can + * only happen for non-indirect options. + * Also set the default to the expanded value, so ":set" does not list + * them. Don't set the P_ALLOCED flag, because we don't want to free the + * default. + */ + for (opt_idx = 0; !istermoption(&options[opt_idx]); opt_idx++) + { + p = option_expand(opt_idx); + if (p != NULL) + { + *(char_u **)options[opt_idx].var = p; + options[opt_idx].def_val = p; + options[opt_idx].flags |= P_DEF_ALLOCED; + } + } +} + +/* + * Set an option to its default value. + */ + static void +set_option_default(opt_idx, dofree) + int opt_idx; + int dofree; /* TRUE when old value may be freed */ +{ + char_u *varp; /* pointer to variable for current option */ + + varp = get_varp(&(options[opt_idx])); + if (varp != NULL) /* nothing to do for hidden option */ + { + if (options[opt_idx].flags & P_STRING) + { + /* indirect options are always in allocated memory */ + if (options[opt_idx].flags & P_IND) + set_string_option(NULL, opt_idx, + options[opt_idx].def_val, dofree); + else + { + if (dofree && (options[opt_idx].flags & P_ALLOCED)) + free_string_option(*(char_u **)(varp)); + *(char_u **)varp = options[opt_idx].def_val; + options[opt_idx].flags &= ~P_ALLOCED; + } + } + else if (options[opt_idx].flags & P_NUM) + *(long *)varp = (long)options[opt_idx].def_val; + else /* P_BOOL */ + /* the cast to long is required for Manx C */ + *(int *)varp = (int)(long)options[opt_idx].def_val; + } +} + +/* + * Initialize the options, part two: After getting Rows and Columns + */ + void +set_init_2() +{ +/* + * 'scroll' defaults to half the window height. Note that this default is + * wrong when the window height changes. + */ + options[findoption((char_u *)"scroll")].def_val = (char_u *)(Rows >> 1); + + comp_col(); +} + +/* + * Initialize the options, part three: After reading the .vimrc + */ + void +set_init_3() +{ + int idx1; + +#if defined(UNIX) || defined(OS2) +/* + * Set 'shellpipe' and 'shellredir', depending on the 'shell' option. + * This is done after other initializations, where 'shell' might have been + * set, but only if they have not been set before. + */ + char_u *p; + int idx2; + int do_sp; + int do_srr; + + idx1 = findoption((char_u *)"sp"); + idx2 = findoption((char_u *)"srr"); + do_sp = !(options[idx1].flags & P_WAS_SET); + do_srr = !(options[idx2].flags & P_WAS_SET); + + /* + * Default for p_sp is "| tee", for p_srr is ">". + * For known shells it is changed here to include stderr. + */ + p = gettail(p_sh); + if ( fnamecmp(p, "csh") == 0 || + fnamecmp(p, "tcsh") == 0 +# ifdef OS2 /* also check with .exe extension */ + || fnamecmp(p, "csh.exe") == 0 + || fnamecmp(p, "tcsh.exe") == 0 +# endif + ) + { + if (do_sp) + { + p_sp = (char_u *)"|& tee"; + options[idx1].def_val = p_sp; + } + if (do_srr) + { + p_srr = (char_u *)">&"; + options[idx2].def_val = p_srr; + } + } + else +# ifndef OS2 /* Always use bourne shell style redirection if we reach this */ + if ( STRCMP(p, "sh") == 0 || + STRCMP(p, "ksh") == 0 || + STRCMP(p, "zsh") == 0 || + STRCMP(p, "bash") == 0) +# endif + { + if (do_sp) + { + p_sp = (char_u *)"2>&1| tee"; + options[idx1].def_val = p_sp; + } + if (do_srr) + { + p_srr = (char_u *)">%s 2>&1"; + options[idx2].def_val = p_srr; + } + } +#endif + +/* + * 'title' and 'icon' only default to true if they have not been set or reset + * in .vimrc and we can read the old value. + * When 'title' and 'icon' have been reset in .vimrc, we won't even check if + * they can be reset. this reduces startup time when using X on a remote + * machine. + */ + idx1 = findoption((char_u *)"title"); + if (!(options[idx1].flags & P_WAS_SET) && mch_can_restore_title()) + { + options[idx1].def_val = (char_u *)TRUE; + p_title = TRUE; + } + idx1 = findoption((char_u *)"icon"); + if (!(options[idx1].flags & P_WAS_SET) && mch_can_restore_icon()) + { + options[idx1].def_val = (char_u *)TRUE; + p_icon = TRUE; + } +} + +/* + * Parse 'arg' for option settings. + * + * 'arg' may be IObuff, but only when no errors can be present and option + * does not need to be expanded with option_expand(). + * + * return FAIL if errors are detected, OK otherwise + */ + int +do_set(arg) + char_u *arg; /* option string (may be written to!) */ +{ + register int opt_idx; + char_u *errmsg; + char_u errbuf[80]; + char_u *startarg; + int prefix; /* 1: nothing, 0: "no", 2: "inv" in front of name */ + int nextchar; /* next non-white char after option name */ + int afterchar; /* character just after option name */ + int len; + int i; + int key; + int flags; /* flags for current option */ + char_u *varp = NULL; /* pointer to variable for current option */ + char_u *oldval; /* previous value if *varp */ + int errcnt = 0; /* number of errornous entries */ + long oldRows = Rows; /* remember old Rows */ + long oldColumns = Columns; /* remember old Columns */ + int oldbin; /* remember old bin option */ + long oldch = p_ch; /* remember old command line height */ + int oldea = p_ea; /* remember old 'equalalways' */ + long olduc = p_uc; /* remember old 'updatecount' */ + int did_show = FALSE; /* already showed one value */ + WIN *wp; + + if (*arg == NUL) + { + showoptions(0); + return OK; + } + + while (*arg) /* loop to process all options */ + { + errmsg = NULL; + startarg = arg; /* remember for error message */ + + if (STRNCMP(arg, "all", (size_t)3) == 0) + { + showoptions(1); + arg += 3; + } + else if (STRNCMP(arg, "termcap", (size_t)7) == 0) + { + showoptions(2); + show_termcodes(); + arg += 7; + } + else + { + prefix = 1; + if (STRNCMP(arg, "no", (size_t)2) == 0) + { + prefix = 0; + arg += 2; + } + else if (STRNCMP(arg, "inv", (size_t)3) == 0) + { + prefix = 2; + arg += 3; + } + /* find end of name */ + if (*arg == '<') + { + opt_idx = -1; + /* check for ;> */ + if (arg[1] == 't' && arg[2] == '_' && arg[3] && arg[4]) + len = 5; + else + { + len = 1; + while (arg[len] != NUL && arg[len] != '>') + ++len; + } + if (arg[len] != '>') + { + errmsg = e_invarg; + goto skip; + } + nextchar = arg[len]; + arg[len] = NUL; /* put NUL after name */ + if (arg[1] == 't' && arg[2] == '_') /* could be term code */ + opt_idx = findoption(arg + 1); + key = 0; + if (opt_idx == -1) + key = find_key_option(arg + 1); + arg[len++] = nextchar; /* restore nextchar */ + nextchar = arg[len]; + } + else + { + len = 0; + /* + * The two characters after "t_" may not be alphanumeric. + */ + if (arg[0] == 't' && arg[1] == '_' && arg[2] && arg[3]) + { + len = 4; + } + else + { + while (isalnum(arg[len]) || arg[len] == '_') + ++len; + } + nextchar = arg[len]; + arg[len] = NUL; /* put NUL after name */ + opt_idx = findoption(arg); + key = 0; + if (opt_idx == -1) + key = find_key_option(arg); + arg[len] = nextchar; /* restore nextchar */ + } + + if (opt_idx == -1 && key == 0) /* found a mismatch: skip */ + { + errmsg = (char_u *)"Unknown option"; + goto skip; + } + + if (opt_idx >= 0) + { + if (options[opt_idx].var == NULL) /* hidden option: skip */ + goto skip; + + flags = options[opt_idx].flags; + varp = get_varp(&(options[opt_idx])); + } + else + flags = P_STRING; + + /* remember character after option name */ + afterchar = nextchar; + + /* skip white space, allow ":set ai ?" */ + while (vim_iswhite(nextchar)) + nextchar = arg[++len]; + + if (vim_strchr((char_u *)"?=:!&", nextchar) != NULL) + { + arg += len; + len = 0; + } + + /* + * allow '=' and ':' as MSDOS command.com allows only one + * '=' character per "set" command line. grrr. (jw) + */ + if (nextchar == '?' || (prefix == 1 && vim_strchr((char_u *)"=:&", + nextchar) == NULL && !(flags & P_BOOL))) + { /* print value */ + if (did_show) + msg_outchar('\n'); /* cursor below last one */ + else + { + gotocmdline(TRUE); /* cursor at status line */ + did_show = TRUE; /* remember that we did a line */ + } + if (opt_idx >= 0) + showoneopt(&options[opt_idx]); + else + { + char_u name[2]; + char_u *p; + + name[0] = KEY2TERMCAP0(key); + name[1] = KEY2TERMCAP1(key); + p = find_termcode(name); + if (p == NULL) + { + errmsg = (char_u *)"Unknown option"; + goto skip; + } + else + (void)show_one_termcode(name, p, TRUE); + } + if (nextchar != '?' && nextchar != NUL && + !vim_iswhite(afterchar)) + errmsg = e_trailing; + } + else + { + if (flags & P_BOOL) /* boolean */ + { + if (nextchar == '=' || nextchar == ':') + { + errmsg = e_invarg; + goto skip; + } + + /* + * in secure mode, setting of the secure option is not + * allowed + */ + if (secure && (int *)varp == &p_secure) + { + errmsg = (char_u *)"not allowed here"; + goto skip; + } + + oldbin = curbuf->b_p_bin; /* remember old bin option */ + + /* + * ":set opt!" or ":set invopt": invert + * ":set opt&": reset to default value + * ":set opt" or ":set noopt": set or reset + */ + if (prefix == 2 || nextchar == '!') + *(int *)(varp) ^= 1; + else if (nextchar == '&') + /* the cast to long is required for Manx C */ + *(int *)(varp) = (int)(long)options[opt_idx].def_val; + else + *(int *)(varp) = prefix; + + /* handle the setting of the compatible option */ + if ((int *)varp == &p_cp && p_cp) + { + p_compatible_set(); + } + /* when 'readonly' is reset, also reset readonlymode */ + else if ((int *)varp == &curbuf->b_p_ro && !curbuf->b_p_ro) + readonlymode = FALSE; + + /* when 'bin' is set also set some other options */ + else if ((int *)varp == &curbuf->b_p_bin) + { + set_options_bin(oldbin, curbuf->b_p_bin); + } + /* when 'terse' is set change 'shortmess' */ + else if ((int *)varp == &p_terse) + { + char_u *p; + + p = vim_strchr(p_shm, SHM_SEARCH); + + /* insert 's' in p_shm */ + if (p_terse && p == NULL) + { + STRCPY(IObuff, p_shm); + STRCAT(IObuff, "s"); + set_string_option((char_u *)"shm", -1, + IObuff, TRUE); + } + /* remove 's' from p_shm */ + else if (!p_terse && p != NULL) + vim_memmove(p, p + 1, STRLEN(p)); + } + /* when 'paste' is set or reset also change other options */ + else if ((int *)varp == &p_paste) + { + paste_option_changed(); + } + /* + * When 'lisp' option changes include/exclude '-' in + * keyword characters. + */ + else if (varp == (char_u *)&(curbuf->b_p_lisp)) + init_chartab(); /* ignore errors */ + + else if (!starting && ((int *)varp == &p_title || + (int *)varp == &p_icon)) + { + /* + * When setting 'title' or 'icon' on, call maketitle() + * to create and display it. + * When resetting 'title' or 'icon', call maketitle() + * to clear it and call mch_restore_title() to get the + * old value back. + */ + maketitle(); + if (!*(int *)varp) + mch_restore_title((int *)varp == &p_title ? 1 : 2); + } + } + else /* numeric or string */ + { + if (vim_strchr((char_u *)"=:&", nextchar) == NULL || + prefix != 1) + { + errmsg = e_invarg; + goto skip; + } + if (flags & P_NUM) /* numeric */ + { + /* + * Different ways to set a number option: + * & set to default value + * accept special key codes for 'wildchar' + * c accept any non-digit for 'wildchar' + * 0-9 set number + * other error + */ + arg += len + 1; + if (nextchar == '&') + *(long *)(varp) = (long)options[opt_idx].def_val; + else if ((long *)varp == &p_wc && + (*arg == '<' || *arg == '^' || + ((!arg[1] || vim_iswhite(arg[1])) && + !isdigit(*arg)))) + { + if (*arg == '<') + { + i = get_special_key_code(arg + 1); + if (i == 0) + i = find_key_option(arg + 1); + } + else if (*arg == '^') + i = arg[1] ^ 0x40; + else + i = *arg; + if (i == 0) + { + errmsg = e_invarg; + goto skip; + } + p_wc = i; + } + /* allow negative numbers (for 'undolevels') */ + else if (*arg == '-' || isdigit(*arg)) + { + i = 0; + if (*arg == '-') + i = 1; +#ifdef HAVE_STRTOL + *(long *)(varp) = strtol((char *)arg, NULL, 0); + if (arg[i] == '0' && TO_UPPER(arg[i + 1]) == 'X') + i += 2; +#else + *(long *)(varp) = atol((char *)arg); +#endif + while (isdigit(arg[i])) + ++i; + if (arg[i] != NUL && !vim_iswhite(arg[i])) + { + errmsg = e_invarg; + goto skip; + } + } + else + { + errmsg = (char_u *)"Number required after ="; + goto skip; + } + + /* + * Number options that need some action when changed + */ + if ((long *)varp == &p_wh || (long *)varp == &p_hh) + { + if (p_wh < 0) + { + errmsg = e_positive; + p_wh = 0; + } + if (p_hh < 0) + { + errmsg = e_positive; + p_hh = 0; + } + /* Change window height NOW */ + if (p_wh && lastwin != firstwin) + { + win_equal(curwin, FALSE); + must_redraw = CLEAR; + } + } + /* (re)set last window status line */ + if ((long *)varp == &p_ls) + last_status(); + } + else if (opt_idx >= 0) /* string */ + { + char_u *save_arg = NULL; + char_u *s, *p; + int new_value_alloced; /* new string option + was allocated */ + + /* The old value is kept until we are sure that the new + * value is valid. set_option_default() is therefore + * called with FALSE + */ + oldval = *(char_u **)(varp); + if (nextchar == '&') /* set to default val */ + { + set_option_default(opt_idx, FALSE); + new_value_alloced = + (options[opt_idx].flags & P_ALLOCED); + } + else + { + arg += len + 1; /* jump to after the '=' or ':' */ + + /* + * Convert 'whichwrap' number to string, for + * backwards compatibility with Vim 3.0. + * Misuse errbuf[] for the resulting string. + */ + if (varp == (char_u *)&p_ww && isdigit(*arg)) + { + *errbuf = NUL; + i = getdigits(&arg); + if (i & 1) + STRCAT(errbuf, "b,"); + if (i & 2) + STRCAT(errbuf, "s,"); + if (i & 4) + STRCAT(errbuf, "h,l,"); + if (i & 8) + STRCAT(errbuf, "<,>,"); + if (i & 16) + STRCAT(errbuf, "[,],"); + if (*errbuf != NUL) /* remove trailing , */ + errbuf[STRLEN(errbuf) - 1] = NUL; + save_arg = arg; + arg = errbuf; + } + /* + * Remove '>' before 'dir' and 'bdir', for + * backwards compatibility with version 3.0 + */ + else if (*arg == '>' && (varp == (char_u *)&p_dir || + varp == (char_u *)&p_bdir)) + { + ++arg; + } + + /* + * Copy the new string into allocated memory. + * Can't use set_string_option(), because we need + * to remove the backslashes. + */ + /* get a bit too much */ + s = alloc((unsigned)(STRLEN(arg) + 1)); + if (s == NULL) /* out of memory, don't change */ + break; + *(char_u **)(varp) = s; + + /* + * Copy the string, skip over escaped chars. + * For MS-DOS and WIN32 backslashes before normal + * file name characters are not removed. + */ + while (*arg && !vim_iswhite(*arg)) + { + if (*arg == '\\' && arg[1] != NUL +#ifdef BACKSLASH_IN_FILENAME + && !((flags & P_EXPAND) + && isfilechar(arg[1]) + && arg[1] != '\\') +#endif + ) + ++arg; + *s++ = *arg++; + } + *s = NUL; + if (save_arg != NULL) /* number for 'whichwrap' */ + arg = save_arg; + new_value_alloced = TRUE; + } + + /* expand environment variables and ~ */ + s = option_expand(opt_idx); + if (s != NULL) + { + if (new_value_alloced) + vim_free(*(char_u **)(varp)); + *(char_u **)(varp) = s; + new_value_alloced = TRUE; + } + + /* + * options that need some action + * to perform when changed (jw) + */ + if (varp == (char_u *)&term_strings[KS_NAME]) + { + if (term_strings[KS_NAME][0] == NUL) + errmsg = (char_u *)"Cannot set 'term' to empty string"; +#ifdef USE_GUI + if (gui.in_use) + errmsg = (char_u *)"Cannot change term in GUI"; +#endif + else if (set_termname(term_strings[KS_NAME]) == + FAIL) + errmsg = (char_u *)"Not found in termcap"; + else + { + /* Screen colors may have changed. */ + outstr(T_ME); + updateScreen(CLEAR); + } + } + + else if ((varp == (char_u *)&p_bex || + varp == (char_u *)&p_pm)) + { + if (STRCMP(*p_bex == '.' ? p_bex + 1 : p_bex, + *p_pm == '.' ? p_pm + 1 : p_pm) == 0) + errmsg = (char_u *)"'backupext' and 'patchmode' are equal"; + } + /* + * 'isident', 'iskeyword', 'isprint or 'isfname' + * option: refill chartab[] + * If the new option is invalid, use old value. + * 'lisp' option: refill chartab[] for '-' char + */ + else if (varp == (char_u *)&p_isi || + varp == (char_u *)&(curbuf->b_p_isk) || + varp == (char_u *)&p_isp || + varp == (char_u *)&p_isf) + { + if (init_chartab() == FAIL) + errmsg = e_invarg; /* error in value */ + } + else if (varp == (char_u *)&p_hl) + { + /* Check 'highlight' */ + for (s = p_hl; *s; ) + { + if (vim_strchr((char_u *)"8dehmMnrstvw", + (i = s[0])) == NULL || + vim_strchr((char_u *)"bsnuir", + (i = s[1])) == NULL || + ((i = s[2]) != NUL && i != ',')) + { + illegal_char(errbuf, i); + errmsg = errbuf; + break; + } + if (s[2] == NUL) + break; + s = skipwhite(s + 3); + } + } + else if (varp == (char_u *)&(curbuf->b_p_com)) + { + for (s = curbuf->b_p_com; *s; ) + { + while (*s && *s != ':') + { + if (vim_strchr((char_u *)COM_ALL, *s) == NULL) + { + errmsg = (char_u *)"Illegal flag"; + break; + } + ++s; + } + if (*s++ == NUL) + errmsg = (char_u *)"Missing colon"; + else if (*s == ',') + errmsg = (char_u *)"Zero length string"; + if (errmsg != NULL) + break; + while (*s && *s != ',') + { + if (*s == '\\' && s[1] != NUL) + ++s; + ++s; + } + s = skip_to_option_part(s); + } + } +#ifdef VIMINFO + else if (varp == (char_u *)&(p_viminfo)) + { + for (s = p_viminfo; *s;) + { + /* Check it's a valid character */ + if (vim_strchr((char_u *)"\"'fr:/", *s) == NULL) + { + illegal_char(errbuf, *s); + errmsg = errbuf; + break; + } + if (*s == 'r') + { + while (*++s && *s != ',') + ; + } + else + { + while (isdigit(*++s)) + ; + + /* Must be a number after the character */ + if (!isdigit(*(s - 1))) + { + sprintf((char *)errbuf, + "Missing number after <%s>", + transchar(*(s - 1))); + errmsg = errbuf; + break; + } + } + s = skip_to_option_part(s); + } + if (*p_viminfo && errmsg == NULL + && get_viminfo_parameter('\'') < 0) + errmsg = (char_u *)"Must specify a ' value"; + } +#endif /* VIMINFO */ + else if (istermoption(&options[opt_idx]) && full_screen) + { + ttest(FALSE); + if (varp == (char_u *)&term_strings[KS_ME]) + { + outstr(T_ME); + updateScreen(CLEAR); + } + } + else if (varp == (char_u *)&p_sbr) + { + for (s = p_sbr; *s; ++s) + if (charsize(*s) != 1) + errmsg = (char_u *)"contains unprintable character"; + } +#ifdef USE_GUI + else if (varp == (char_u *)&p_guifont) + { + gui_init_font(); + } +#endif /* USE_GUI */ +#ifdef HAVE_LANGMAP + else if (varp == (char_u *)&p_langmap) + langmap_set(); +#endif + else if (varp == (char_u *)&p_breakat) + fill_breakat_flags(); + else + { + /* + * Check options that are a list of flags. + */ + p = NULL; + if (varp == (char_u *)&p_ww) + p = (char_u *)WW_ALL; + if (varp == (char_u *)&p_shm) + p = (char_u *)SHM_ALL; + else if (varp == (char_u *)&(p_cpo)) + p = (char_u *)CPO_ALL; + else if (varp == (char_u *)&(curbuf->b_p_fo)) + p = (char_u *)FO_ALL; + else if (varp == (char_u *)&p_mouse) + { +#ifdef USE_MOUSE + p = (char_u *)MOUSE_ALL; +#else + if (*p_mouse != NUL) + errmsg = (char_u *)"No mouse support"; +#endif + } +#ifdef USE_GUI + else if (varp == (char_u *)&p_guioptions) + p = (char_u *)GO_ALL; +#endif /* USE_GUI */ + if (p != NULL) + { + for (s = *(char_u **)(varp); *s; ++s) + if (vim_strchr(p, *s) == NULL) + { + illegal_char(errbuf, *s); + errmsg = errbuf; + break; + } + } + } + if (errmsg != NULL) /* error detected */ + { + if (new_value_alloced) + vim_free(*(char_u **)(varp)); + *(char_u **)(varp) = oldval; + (void)init_chartab(); /* back to the old value */ + goto skip; + } + +#ifdef USE_GUI + if (varp == (char_u *)&p_guioptions) + gui_init_which_components(oldval); +#endif /* USE_GUI */ + + /* + * Free string options that are in allocated memory. + */ + if (flags & P_ALLOCED) + free_string_option(oldval); + if (new_value_alloced) + options[opt_idx].flags |= P_ALLOCED; + } + else /* key code option */ + { + char_u name[2]; + char_u *p; + + name[0] = KEY2TERMCAP0(key); + name[1] = KEY2TERMCAP1(key); + if (nextchar == '&') + { + if (add_termcap_entry(name, TRUE) == FAIL) + errmsg = (char_u *)"Not found in termcap"; + } + else + { + arg += len + 1; /* jump to after the '=' or ':' */ + for(p = arg; *p && !vim_iswhite(*p); ++p) + { + if (*p == '\\' && *(p + 1)) + ++p; + } + nextchar = *p; + *p = NUL; + add_termcode(name, arg); + *p = nextchar; + } + if (full_screen) + ttest(FALSE); + } + } + if (opt_idx >= 0) + options[opt_idx].flags |= P_WAS_SET; + } + +skip: + /* + * Check the bounds for numeric options here + */ + if (Rows < min_rows() && full_screen) + { + sprintf((char *)errbuf, "Need at least %d lines", min_rows()); + errmsg = errbuf; + Rows = min_rows(); + } + if (Columns < MIN_COLUMNS && full_screen) + { + sprintf((char *)errbuf, "Need at least %d columns", + MIN_COLUMNS); + errmsg = errbuf; + Columns = MIN_COLUMNS; + } + /* + * If the screenheight has been changed, assume it is the physical + * screenheight. + */ + if ((oldRows != Rows || oldColumns != Columns) && full_screen) + { + mch_set_winsize(); /* try to change the window size */ + check_winsize(); /* in case 'columns' changed */ +#ifdef MSDOS + set_window(); /* active window may have changed */ +#endif + } + + if (curbuf->b_p_ts <= 0) + { + errmsg = e_positive; + curbuf->b_p_ts = 8; + } + if (curbuf->b_p_tw < 0) + { + errmsg = e_positive; + curbuf->b_p_tw = 0; + } + if (p_tm < 0) + { + errmsg = e_positive; + p_tm = 0; + } + if ((curwin->w_p_scroll <= 0 || + curwin->w_p_scroll > curwin->w_height) && full_screen) + { + if (curwin->w_p_scroll != 0) + errmsg = e_scroll; + win_comp_scroll(curwin); + } + if (p_report < 0) + { + errmsg = e_positive; + p_report = 1; + } + if ((p_sj < 0 || p_sj >= Rows) && full_screen) + { + if (Rows != oldRows) /* Rows changed, just adjust p_sj */ + p_sj = Rows / 2; + else + { + errmsg = e_scroll; + p_sj = 1; + } + } + if (p_so < 0 && full_screen) + { + errmsg = e_scroll; + p_so = 0; + } + if (p_uc < 0) + { + errmsg = e_positive; + p_uc = 100; + } + if (p_ch < 1) + { + errmsg = e_positive; + p_ch = 1; + } + if (p_ut < 0) + { + errmsg = e_positive; + p_ut = 2000; + } + if (p_ss < 0) + { + errmsg = e_positive; + p_ss = 0; + } + + /* + * Advance to next argument. + * - skip until a blank found, taking care of backslashes + * - skip blanks + */ + while (*arg != NUL && !vim_iswhite(*arg)) + if (*arg++ == '\\' && *arg != NUL) + ++arg; + } + arg = skipwhite(arg); + + if (errmsg) + { + ++no_wait_return; /* wait_return done below */ +#ifdef SLEEP_IN_EMSG + ++dont_sleep; /* don't wait in emsg() */ +#endif + emsg(errmsg); /* show error highlighted */ +#ifdef SLEEP_IN_EMSG + --dont_sleep; +#endif + MSG_OUTSTR(": "); + /* show argument normal */ + while (startarg < arg) + msg_outstr(transchar(*startarg++)); + msg_end(); /* check for scrolling */ + --no_wait_return; + + ++errcnt; /* count number of errors */ + did_show = TRUE; /* error message counts as show */ + if (sourcing_name != NULL) + break; + } + } + + /* + * when 'updatecount' changes from zero to non-zero, open swap files + */ + if (p_uc && !olduc) + ml_open_files(); + + if (p_ch != oldch) /* p_ch changed value */ + command_height(); +#ifdef USE_MOUSE + if (*p_mouse == NUL) + mch_setmouse(FALSE); /* switch mouse off */ + else + setmouse(); /* in case 'mouse' changed */ +#endif + comp_col(); /* in case 'ruler' or 'showcmd' changed */ + curwin->w_set_curswant = TRUE; /* in case 'list' changed */ + + /* + * Update the screen in case we changed something like "tabstop" or + * "lines" or "list" that will change its appearance. + * Also update the cursor position, in case 'wrap' is changed. + */ + for (wp = firstwin; wp; wp = wp->w_next) + wp->w_redr_status = TRUE; /* mark all status lines dirty */ + if (p_ea && !oldea) + win_equal(curwin, FALSE); + updateScreen(CURSUPD); + return (errcnt == 0 ? OK : FAIL); +} + + static void +illegal_char(errbuf, c) + char_u *errbuf; + int c; +{ + sprintf((char *)errbuf, "Illegal character <%s>", (char *)transchar(c)); +} + +/* + * set_options_bin - called when 'bin' changes value. + */ + void +set_options_bin(oldval, newval) + int oldval; + int newval; +{ + /* + * The option values that are changed when 'bin' changes are + * copied when 'bin is set and restored when 'bin' is reset. + */ + if (newval) + { + if (!oldval) /* switched on */ + { + curbuf->b_p_tw_nobin = curbuf->b_p_tw; + curbuf->b_p_wm_nobin = curbuf->b_p_wm; + curbuf->b_p_tx_nobin = curbuf->b_p_tx; + curbuf->b_p_ta_nobin = p_ta; + curbuf->b_p_ml_nobin = curbuf->b_p_ml; + curbuf->b_p_et_nobin = curbuf->b_p_et; + } + + curbuf->b_p_tw = 0; /* no automatic line wrap */ + curbuf->b_p_wm = 0; /* no automatic line wrap */ + curbuf->b_p_tx = 0; /* no text mode */ + p_ta = 0; /* no text auto */ + curbuf->b_p_ml = 0; /* no modelines */ + curbuf->b_p_et = 0; /* no expandtab */ + } + else if (oldval) /* switched off */ + { + curbuf->b_p_tw = curbuf->b_p_tw_nobin; + curbuf->b_p_wm = curbuf->b_p_wm_nobin; + curbuf->b_p_tx = curbuf->b_p_tx_nobin; + p_ta = curbuf->b_p_ta_nobin; + curbuf->b_p_ml = curbuf->b_p_ml_nobin; + curbuf->b_p_et = curbuf->b_p_et_nobin; + } +} + +#ifdef VIMINFO +/* + * Find the parameter represented by the given character (eg ', :, ", or /), + * and return its associated value in the 'viminfo' string. If the parameter + * is not specified in the string, return -1. + */ + int +get_viminfo_parameter(type) + int type; +{ + char_u *p; + + p = vim_strchr(p_viminfo, type); + if (p != NULL && isdigit(*++p)) + return (int)atol((char *)p); + return -1; +} +#endif + +/* + * Expand environment variables for some string options. + * These string options cannot be indirect! + * Return pointer to allocated memory, or NULL when not expanded. + */ + static char_u * +option_expand(opt_idx) + int opt_idx; +{ + char_u *p; + + /* if option doesn't need expansion or is hidden: nothing to do */ + if (!(options[opt_idx].flags & P_EXPAND) || options[opt_idx].var == NULL) + return NULL; + + p = *(char_u **)(options[opt_idx].var); + + /* + * Expanding this with NameBuff, expand_env() must not be passed IObuff. + */ + expand_env(p, NameBuff, MAXPATHL); + if (STRCMP(NameBuff, p) == 0) /* they are the same */ + return NULL; + + return strsave(NameBuff); +} + +/* + * Check for string options that are NULL (normally only termcap options). + */ + void +check_options() +{ + int opt_idx; + char_u **p; + + for (opt_idx = 0; options[opt_idx].fullname != NULL; opt_idx++) + if ((options[opt_idx].flags & P_STRING) && options[opt_idx].var != NULL) + { + p = (char_u **)get_varp(&(options[opt_idx])); + if (*p == NULL) + *p = empty_option; + } +} + +/* + * Check string options in a buffer for NULL value. + */ + void +check_buf_options(buf) + BUF *buf; +{ + if (buf->b_p_fo == NULL) + buf->b_p_fo = empty_option; + if (buf->b_p_isk == NULL) + buf->b_p_isk = empty_option; + if (buf->b_p_com == NULL) + buf->b_p_com = empty_option; +#ifdef CINDENT + if (buf->b_p_cink == NULL) + buf->b_p_cink = empty_option; + if (buf->b_p_cino == NULL) + buf->b_p_cino = empty_option; +#endif +#if defined(SMARTINDENT) || defined(CINDENT) + if (buf->b_p_cinw == NULL) + buf->b_p_cinw = empty_option; +#endif +} + +/* + * Free the string allocated for an option. + * Checks for the string being empty_option. This may happen if we're out of + * memory, strsave() returned NULL, which was replaced by empty_option by + * check_options(). + * Does NOT check for P_ALLOCED flag! + */ + void +free_string_option(p) + char_u *p; +{ + if (p != empty_option) + vim_free(p); +} + +/* + * Set a string option to a new value. + * The string is copied into allocated memory. + * If 'dofree' is set, the old value may be freed. + * if (opt_idx == -1) name is used, otherwise opt_idx is used. + */ + void +set_string_option(name, opt_idx, val, dofree) + char_u *name; + int opt_idx; + char_u *val; + int dofree; +{ + char_u *s; + char_u **varp; + + if (opt_idx == -1) /* use name */ + { + opt_idx = findoption(name); + if (opt_idx == -1) /* not found (should not happen) */ + return; + } + + if (options[opt_idx].var == NULL) /* don't set hidden option */ + return; + + s = strsave(val); + if (s != NULL) + { + varp = (char_u **)get_varp(&(options[opt_idx])); + if (dofree && (options[opt_idx].flags & P_ALLOCED)) + free_string_option(*varp); + *varp = s; + /* if 'term' option set for the first time: set default value */ + if (varp == &(term_strings[KS_NAME]) && + *(options[opt_idx].def_val) == NUL) + { + options[opt_idx].def_val = s; + options[opt_idx].flags |= P_DEF_ALLOCED; + } + else + options[opt_idx].flags |= P_ALLOCED; + } +} + +/* + * find index for option 'arg' + * return -1 if not found + */ + static int +findoption(arg) + char_u *arg; +{ + int opt_idx; + char *s; + + for (opt_idx = 0; (s = options[opt_idx].fullname) != NULL; opt_idx++) + { + if (STRCMP(arg, s) == 0) /* match full name */ + break; + } + if (s == NULL) + { + for (opt_idx = 0; options[opt_idx].fullname != NULL; opt_idx++) + { + s = options[opt_idx].shortname; + if (s != NULL && STRCMP(arg, s) == 0) /* match short name */ + break; + s = NULL; + } + } + if (s == NULL) + opt_idx = -1; + return opt_idx; +} + + char_u * +get_highlight_default() +{ + int i; + + i = findoption((char_u *)"hl"); + if (i >= 0) + return options[i].def_val; + return (char_u *)NULL; +} + + static int +find_key_option(arg) + char_u *arg; +{ + int key; + int c; + + /* don't use get_special_key_code() for t_xx, we don't want it to call + * add_termcap_entry() */ + if (arg[0] == 't' && arg[1] == '_' && arg[2] && arg[3]) + key = TERMCAP2KEY(arg[2], arg[3]); + + /* is a special case, because TAB isn't a special key */ + else if (vim_strnicmp(arg, (char_u *)"S-Tab", (size_t)5) == 0) + key = K_S_TAB; + else + { + /* Currently only the shift modifier is recognized */ + mod_mask = 0; + if (TO_LOWER(arg[0]) == 's' && arg[1] == '-') + { + mod_mask = MOD_MASK_SHIFT; + arg += 2; + } + c = get_special_key_code(arg); + key = check_shifted_spec_key(c); + if (mod_mask && c == key) /* key can't be shifted */ + key = 0; + } + return key; +} + +/* + * if 'all' == 0: show changed options + * if 'all' == 1: show all normal options + * if 'all' == 2: show all terminal options + */ + static void +showoptions(all) + int all; +{ + struct option *p; + int col; + int isterm; + char_u *varp; + struct option **items; + int item_count; + int run; + int row, rows; + int cols; + int i; + int len; + +#define INC 20 +#define GAP 3 + + items = (struct option **)alloc((unsigned)(sizeof(struct option *) * + PARAM_COUNT)); + if (items == NULL) + return; + + set_highlight('t'); /* Highlight title */ + start_highlight(); + if (all == 2) + MSG_OUTSTR("\n--- Terminal codes ---"); + else + MSG_OUTSTR("\n--- Options ---"); + stop_highlight(); + + /* + * do the loop two times: + * 1. display the short items + * 2. display the long items (only strings and numbers) + */ + for (run = 1; run <= 2 && !got_int; ++run) + { + /* + * collect the items in items[] + */ + item_count = 0; + for (p = &options[0]; p->fullname != NULL; p++) + { + isterm = istermoption(p); + varp = get_varp(p); + if (varp != NULL && ( + (all == 2 && isterm) || + (all == 1 && !isterm) || + (all == 0 && option_changed(p)))) + { + if (p->flags & P_BOOL) + len = 1; /* a toggle option fits always */ + else + { + option_value2string(p); + len = STRLEN(p->fullname) + strsize(NameBuff) + 1; + } + if ((len <= INC - GAP && run == 1) || + (len > INC - GAP && run == 2)) + items[item_count++] = p; + } + } + + /* + * display the items + */ + if (run == 1) + { + cols = (Columns + GAP - 3) / INC; + if (cols == 0) + cols = 1; + rows = (item_count + cols - 1) / cols; + } + else /* run == 2 */ + rows = item_count; + for (row = 0; row < rows && !got_int; ++row) + { + msg_outchar('\n'); /* go to next line */ + if (got_int) /* 'q' typed in more */ + break; + col = 0; + for (i = row; i < item_count; i += rows) + { + msg_pos(-1, col); /* make columns */ + showoneopt(items[i]); + col += INC; + } + flushbuf(); + mch_breakcheck(); + } + } + vim_free(items); +} + +/* + * Return TRUE if option is different from the default value + */ + static int +option_changed(p) + struct option *p; +{ + char_u *varp; + + varp = get_varp(p); + if (varp == NULL) + return FALSE; /* hidden option is never changed */ + + if (p->flags & P_NUM) + return (*(long *)varp != (long)p->def_val); + if (p->flags & P_BOOL) + /* the cast to long is required for Manx C */ + return (*(int *)varp != (int)(long)p->def_val); + /* P_STRING */ + return STRCMP(*(char_u **)varp, p->def_val); +} + +/* + * showoneopt: show the value of one option + * must not be called with a hidden option! + */ + static void +showoneopt(p) + struct option *p; +{ + char_u *varp; + + varp = get_varp(p); + + if ((p->flags & P_BOOL) && !*(int *)varp) + MSG_OUTSTR("no"); + else + MSG_OUTSTR(" "); + MSG_OUTSTR(p->fullname); + if (!(p->flags & P_BOOL)) + { + msg_outchar('='); + option_value2string(p); /* put string of option value in NameBuff */ + msg_outtrans(NameBuff); + } +} + +/* + * Write modified options as set command to a file. + * Return FAIL on error, OK otherwise. + */ + int +makeset(fd) + FILE *fd; +{ + struct option *p; + char_u *s; + int e; + char_u *varp; + + /* + * The options that don't have a default (terminal name, columns, lines) + * are never written. Terminal options are also not written. + */ + for (p = &options[0]; !istermoption(p); p++) + if (!(p->flags & P_NO_MKRC) && !istermoption(p) && + (option_changed(p))) + { + varp = get_varp(p); + if (p->flags & P_BOOL) + fprintf(fd, "set %s%s", *(int *)(varp) ? "" : "no", + p->fullname); + else if (p->flags & P_NUM) + fprintf(fd, "set %s=%ld", p->fullname, *(long *)(varp)); + else /* P_STRING */ + { + fprintf(fd, "set %s=", p->fullname); + s = *(char_u **)(varp); + /* some characters have to be escaped with CTRL-V or + * backslash */ + if (s != NULL && putescstr(fd, s, TRUE) == FAIL) + return FAIL; + } +#ifdef USE_CRNL + putc('\r', fd); +#endif + /* + * Only check error for this putc, should catch at least + * the "disk full" situation. + */ + e = putc('\n', fd); + if (e < 0) + return FAIL; + } + return OK; +} + +/* + * Clear all the terminal options. + * If the option has been allocated, free the memory. + * Terminal options are never hidden or indirect. + */ + void +clear_termoptions() +{ + struct option *p; + + /* + * Reset a few things before clearing the old options. This may cause + * outputting a few things that the terminal doesn't understand, but the + * screen will be cleared later, so this is OK. + */ +#ifdef USE_MOUSE + mch_setmouse(FALSE); /* switch mouse off */ +#endif + mch_restore_title(3); /* restore window titles */ +#ifdef WIN32 + /* + * Check if this is allowed now. + */ + if (can_end_termcap_mode(FALSE) == TRUE) +#endif + stoptermcap(); /* stop termcap mode */ + + for (p = &options[0]; p->fullname != NULL; p++) + if (istermoption(p)) + { + if (p->flags & P_ALLOCED) + free_string_option(*(char_u **)(p->var)); + if (p->flags & P_DEF_ALLOCED) + free_string_option(p->def_val); + *(char_u **)(p->var) = empty_option; + p->def_val = empty_option; + p->flags &= ~(P_ALLOCED|P_DEF_ALLOCED); + } + clear_termcodes(); +} + +/* + * Set the terminal option defaults to the current value. + * Used after setting the terminal name. + */ + void +set_term_defaults() +{ + struct option *p; + + for (p = &options[0]; p->fullname != NULL; p++) + if (istermoption(p) && p->def_val != *(char_u **)(p->var)) + { + if (p->flags & P_DEF_ALLOCED) + { + free_string_option(p->def_val); + p->flags &= ~P_DEF_ALLOCED; + } + p->def_val = *(char_u **)(p->var); + if (p->flags & P_ALLOCED) + { + p->flags |= P_DEF_ALLOCED; + p->flags &= ~P_ALLOCED; /* don't free the value now */ + } + } +} + +/* + * return TRUE if 'p' starts with 't_' + */ + static int +istermoption(p) + struct option *p; +{ + return (p->fullname[0] == 't' && p->fullname[1] == '_'); +} + +/* + * Compute columns for ruler and shown command. 'sc_col' is also used to + * decide what the maximum length of a message on the status line can be. + * If there is a status line for the last window, 'sc_col' is independent + * of 'ru_col'. + */ + +#define COL_RULER 17 /* columns needed by ruler */ + + void +comp_col() +{ + int last_has_status = (p_ls == 2 || (p_ls == 1 && firstwin != lastwin)); + + sc_col = 0; + ru_col = 0; + if (p_ru) + { + ru_col = COL_RULER + 1; + /* no last status line, adjust sc_col */ + if (!last_has_status) + sc_col = ru_col; + } + if (p_sc) + { + sc_col += SHOWCMD_COLS; + if (!p_ru || last_has_status) /* no need for separating space */ + ++sc_col; + } + sc_col = Columns - sc_col; + ru_col = Columns - ru_col; + if (sc_col <= 0) /* screen too narrow, will become a mess */ + sc_col = 1; + if (ru_col <= 0) + ru_col = 1; +} + + static char_u * +get_varp(p) + struct option *p; +{ + if (!(p->flags & P_IND) || p->var == NULL) + return p->var; + + switch ((long)(p->var)) + { + case PV_LIST: return (char_u *)&(curwin->w_p_list); + case PV_NU: return (char_u *)&(curwin->w_p_nu); +#ifdef RIGHTLEFT + case PV_RL: return (char_u *)&(curwin->w_p_rl); +#endif + case PV_SCROLL: return (char_u *)&(curwin->w_p_scroll); + case PV_WRAP: return (char_u *)&(curwin->w_p_wrap); + case PV_LBR: return (char_u *)&(curwin->w_p_lbr); + + case PV_AI: return (char_u *)&(curbuf->b_p_ai); + case PV_BIN: return (char_u *)&(curbuf->b_p_bin); +#ifdef CINDENT + case PV_CIN: return (char_u *)&(curbuf->b_p_cin); + case PV_CINK: return (char_u *)&(curbuf->b_p_cink); + case PV_CINO: return (char_u *)&(curbuf->b_p_cino); +#endif +#if defined(SMARTINDENT) || defined(CINDENT) + case PV_CINW: return (char_u *)&(curbuf->b_p_cinw); +#endif + case PV_COM: return (char_u *)&(curbuf->b_p_com); + case PV_EOL: return (char_u *)&(curbuf->b_p_eol); + case PV_ET: return (char_u *)&(curbuf->b_p_et); + case PV_FO: return (char_u *)&(curbuf->b_p_fo); + case PV_INF: return (char_u *)&(curbuf->b_p_inf); + case PV_ISK: return (char_u *)&(curbuf->b_p_isk); + case PV_LISP: return (char_u *)&(curbuf->b_p_lisp); + case PV_ML: return (char_u *)&(curbuf->b_p_ml); + case PV_MOD: return (char_u *)&(curbuf->b_changed); + case PV_RO: return (char_u *)&(curbuf->b_p_ro); +#ifdef SMARTINDENT + case PV_SI: return (char_u *)&(curbuf->b_p_si); +#endif +#ifndef SHORT_FNAME + case PV_SN: return (char_u *)&(curbuf->b_p_sn); +#endif + case PV_SW: return (char_u *)&(curbuf->b_p_sw); + case PV_TS: return (char_u *)&(curbuf->b_p_ts); + case PV_TW: return (char_u *)&(curbuf->b_p_tw); + case PV_TX: return (char_u *)&(curbuf->b_p_tx); + case PV_WM: return (char_u *)&(curbuf->b_p_wm); + default: EMSG("get_varp ERROR"); + } + /* always return a valid pointer to avoid a crash! */ + return (char_u *)&(curbuf->b_p_wm); +} + +/* + * Copy options from one window to another. + * Used when creating a new window. + * The 'scroll' option is not copied, because it depends on the window height. + */ + void +win_copy_options(wp_from, wp_to) + WIN *wp_from; + WIN *wp_to; +{ + wp_to->w_p_list = wp_from->w_p_list; + wp_to->w_p_nu = wp_from->w_p_nu; +#ifdef RIGHTLEFT + wp_to->w_p_rl = wp_from->w_p_rl; +#endif + wp_to->w_p_wrap = wp_from->w_p_wrap; + wp_to->w_p_lbr = wp_from->w_p_lbr; +} + +/* + * Copy options from one buffer to another. + * Used when creating a new buffer and when entering a buffer. + * Only do this once for a new buffer, otherwise allocated memory for the + * string option will be lost. + * When "entering" is TRUE we will enter the bp_to buffer. + */ + void +buf_copy_options(bp_from, bp_to, entering) + BUF *bp_from; + BUF *bp_to; + int entering; +{ + /* + * Don't copy if one of the pointers is NULL or they are the same. + */ + if (bp_from == NULL || bp_to == NULL || bp_from == bp_to) + return; + + /* + * Always copy when entering and 'cpo' contains 'S'. + * Don't copy when already initialized. + * Don't copy when 'cpo' contains 's' and not entering. + */ + if ((vim_strchr(p_cpo, CPO_BUFOPTGLOB) == NULL || !entering) && + (bp_to->b_p_initialized || + (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL))) + { + check_buf_options(bp_to); /* make sure we don't have NULLs */ + return; + } + + /* + * If already initialized, need to free the allocated strings. + * Copy 'readonly' and 'textmode' only when not initialized. + */ + if (bp_to->b_p_initialized) + { + free_string_option(bp_to->b_p_fo); + free_string_option(bp_to->b_p_isk); + free_string_option(bp_to->b_p_com); +#ifdef CINDENT + free_string_option(bp_to->b_p_cink); + free_string_option(bp_to->b_p_cino); +#endif +#if defined(CINDENT) || defined(SMARTINDENT) + free_string_option(bp_to->b_p_cinw); +#endif + } + else + { + bp_to->b_p_ro = FALSE; /* don't copy readonly */ + bp_to->b_p_tx = bp_from->b_p_tx; + bp_to->b_p_tx_nobin = bp_from->b_p_tx_nobin; + } + + bp_to->b_p_ai = bp_from->b_p_ai; + bp_to->b_p_ai_save = bp_from->b_p_ai_save; + bp_to->b_p_sw = bp_from->b_p_sw; + bp_to->b_p_tw = bp_from->b_p_tw; + bp_to->b_p_tw_save = bp_from->b_p_tw_save; + bp_to->b_p_tw_nobin = bp_from->b_p_tw_nobin; + bp_to->b_p_wm = bp_from->b_p_wm; + bp_to->b_p_wm_save = bp_from->b_p_wm_save; + bp_to->b_p_wm_nobin = bp_from->b_p_wm_nobin; + bp_to->b_p_bin = bp_from->b_p_bin; + bp_to->b_p_et = bp_from->b_p_et; + bp_to->b_p_et_nobin = bp_from->b_p_et_nobin; + bp_to->b_p_ml = bp_from->b_p_ml; + bp_to->b_p_ml_nobin = bp_from->b_p_ml_nobin; + bp_to->b_p_inf = bp_from->b_p_inf; +#ifndef SHORT_FNAME + bp_to->b_p_sn = bp_from->b_p_sn; +#endif + bp_to->b_p_com = strsave(bp_from->b_p_com); + bp_to->b_p_fo = strsave(bp_from->b_p_fo); +#ifdef SMARTINDENT + bp_to->b_p_si = bp_from->b_p_si; + bp_to->b_p_si_save = bp_from->b_p_si_save; +#endif +#ifdef CINDENT + bp_to->b_p_cin = bp_from->b_p_cin; + bp_to->b_p_cin_save = bp_from->b_p_cin_save; + bp_to->b_p_cink = strsave(bp_from->b_p_cink); + bp_to->b_p_cino = strsave(bp_from->b_p_cino); +#endif +#if defined(SMARTINDENT) || defined(CINDENT) + bp_to->b_p_cinw = strsave(bp_from->b_p_cinw); +#endif +#ifdef LISPINDENT + bp_to->b_p_lisp = bp_from->b_p_lisp; + bp_to->b_p_lisp_save = bp_from->b_p_lisp_save; +#endif + bp_to->b_p_ta_nobin = bp_from->b_p_ta_nobin; + + /* + * Don't copy the options set by do_help(), use the saved values + */ + if (!keep_help_flag && bp_from->b_help && help_save_isk != NULL) + { + bp_to->b_p_isk = strsave(help_save_isk); + if (bp_to->b_p_isk != NULL) + init_chartab(); + bp_to->b_p_ts = help_save_ts; + bp_to->b_help = FALSE; + } + else + { + bp_to->b_p_isk = strsave(bp_from->b_p_isk); + vim_memmove(bp_to->b_chartab, bp_from->b_chartab, (size_t)256); + bp_to->b_p_ts = bp_from->b_p_ts; + bp_to->b_help = bp_from->b_help; + } + check_buf_options(bp_to); + + /* + * Set the flag that indicates that the options have been ininitialized. + * Avoids loosing allocated memory. + */ + bp_to->b_p_initialized = TRUE; +} + +static int expand_option_idx = -1; +static char_u expand_option_name[5] = {'t', '_', NUL, NUL, NUL}; + + void +set_context_in_set_cmd(arg) + char_u *arg; +{ + int nextchar; + int flags = 0; /* init for GCC */ + int opt_idx = 0; /* init for GCC */ + char_u *p; + char_u *after_blank = NULL; + int is_term_option = FALSE; + int key; + + expand_context = EXPAND_SETTINGS; + if (*arg == NUL) + { + expand_pattern = arg; + return; + } + p = arg + STRLEN(arg) - 1; + if (*p == ' ' && *(p - 1) != '\\') + { + expand_pattern = p + 1; + return; + } + while (p != arg && (*p != ' ' || *(p - 1) == '\\')) + { + /* remember possible start of file name to expand */ + if ((*p == ' ' || (*p == ',' && *(p - 1) != '\\')) && + after_blank == NULL) + after_blank = p + 1; + p--; + } + if (p != arg) + p++; + if (STRNCMP(p, "no", (size_t) 2) == 0) + { + expand_context = EXPAND_BOOL_SETTINGS; + p += 2; + } + if (STRNCMP(p, "inv", (size_t) 3) == 0) + { + expand_context = EXPAND_BOOL_SETTINGS; + p += 3; + } + expand_pattern = arg = p; + if (*arg == '<') + { + while (*p != '>') + if (*p++ == NUL) /* expand terminal option name */ + return; + key = get_special_key_code(arg + 1); + if (key == 0) /* unknown name */ + { + expand_context = EXPAND_NOTHING; + return; + } + nextchar = *++p; + is_term_option = TRUE; + expand_option_name[2] = KEY2TERMCAP0(key); + expand_option_name[3] = KEY2TERMCAP1(key); + } + else + { + if (p[0] == 't' && p[1] == '_') + { + p += 2; + if (*p != NUL) + ++p; + if (*p == NUL) + return; /* expand option name */ + nextchar = *++p; + is_term_option = TRUE; + expand_option_name[2] = p[-2]; + expand_option_name[3] = p[-1]; + } + else + { + while (isalnum(*p) || *p == '_' || *p == '*') /* Allow * wildcard */ + p++; + if (*p == NUL) + return; + nextchar = *p; + *p = NUL; + opt_idx = findoption(arg); + *p = nextchar; + if (opt_idx == -1 || options[opt_idx].var == NULL) + { + expand_context = EXPAND_NOTHING; + return; + } + flags = options[opt_idx].flags; + if (flags & P_BOOL) + { + expand_context = EXPAND_NOTHING; + return; + } + } + } + if ((nextchar != '=' && nextchar != ':') + || expand_context == EXPAND_BOOL_SETTINGS) + { + expand_context = EXPAND_UNSUCCESSFUL; + return; + } + if (expand_context != EXPAND_BOOL_SETTINGS && p[1] == NUL) + { + expand_context = EXPAND_OLD_SETTING; + if (is_term_option) + expand_option_idx = -1; + else + expand_option_idx = opt_idx; + expand_pattern = p + 1; + return; + } + expand_context = EXPAND_NOTHING; + if (is_term_option || (flags & P_NUM)) + return; + if (after_blank != NULL) + expand_pattern = after_blank; + else + expand_pattern = p + 1; + if (flags & P_EXPAND) + { + p = options[opt_idx].var; + if (p == (char_u *)&p_bdir || p == (char_u *)&p_dir || + p == (char_u *)&p_path) + expand_context = EXPAND_DIRECTORIES; + else + expand_context = EXPAND_FILES; + } + return; +} + + int +ExpandSettings(prog, num_file, file) + regexp *prog; + int *num_file; + char_u ***file; +{ + int num_normal = 0; /* Number of matching non-term-code settings */ + int num_term = 0; /* Number of matching terminal code settings */ + int opt_idx; + int match; + int count = 0; + char_u *str; + int loop; + int is_term_opt; + char_u name_buf[MAX_KEY_NAME_LEN]; + + /* do this loop twice: + * loop == 0: count the number of matching options + * loop == 1: copy the matching options into allocated memory + */ + for (loop = 0; loop <= 1; ++loop) + { + if (expand_context != EXPAND_BOOL_SETTINGS) + { + if (vim_regexec(prog, (char_u *)"all", TRUE)) + { + if (loop == 0) + num_normal++; + else + (*file)[count++] = strsave((char_u *)"all"); + } + if (vim_regexec(prog, (char_u *)"termcap", TRUE)) + { + if (loop == 0) + num_normal++; + else + (*file)[count++] = strsave((char_u *)"termcap"); + } + } + for (opt_idx = 0; (str = (char_u *)options[opt_idx].fullname) != NULL; + opt_idx++) + { + if (options[opt_idx].var == NULL) + continue; + if (expand_context == EXPAND_BOOL_SETTINGS + && !(options[opt_idx].flags & P_BOOL)) + continue; + is_term_opt = istermoption(&options[opt_idx]); + if (is_term_opt && num_normal > 0) + continue; + match = FALSE; + if (vim_regexec(prog, str, TRUE) || + (options[opt_idx].shortname != NULL && + vim_regexec(prog, + (char_u *)options[opt_idx].shortname, TRUE))) + match = TRUE; + else if (is_term_opt) + { + name_buf[0] = '<'; + name_buf[1] = 't'; + name_buf[2] = '_'; + name_buf[3] = str[2]; + name_buf[4] = str[3]; + name_buf[5] = '>'; + name_buf[6] = NUL; + if (vim_regexec(prog, name_buf, TRUE)) + { + match = TRUE; + str = name_buf; + } + } + if (match) + { + if (loop == 0) + { + if (is_term_opt) + num_term++; + else + num_normal++; + } + else + (*file)[count++] = strsave(str); + } + } + /* + * Check terminal key codes, these are not in the option table + */ + if (expand_context != EXPAND_BOOL_SETTINGS && num_normal == 0) + { + for (opt_idx = 0; (str = get_termcode(opt_idx)) != NULL; opt_idx++) + { + if (!isprint(str[0]) || !isprint(str[1])) + continue; + + name_buf[0] = 't'; + name_buf[1] = '_'; + name_buf[2] = str[0]; + name_buf[3] = str[1]; + name_buf[4] = NUL; + + match = FALSE; + if (vim_regexec(prog, name_buf, TRUE)) + match = TRUE; + else + { + name_buf[0] = '<'; + name_buf[1] = 't'; + name_buf[2] = '_'; + name_buf[3] = str[0]; + name_buf[4] = str[1]; + name_buf[5] = '>'; + name_buf[6] = NUL; + + if (vim_regexec(prog, name_buf, TRUE)) + match = TRUE; + } + if (match) + { + if (loop == 0) + num_term++; + else + (*file)[count++] = strsave(name_buf); + } + } + /* + * Check special key names. + */ + for (opt_idx = 0; (str = get_key_name(opt_idx)) != NULL; opt_idx++) + { + name_buf[0] = '<'; + STRCPY(name_buf + 1, str); + STRCAT(name_buf, ">"); + + reg_ic = TRUE; /* ignore case here */ + if (vim_regexec(prog, name_buf, TRUE)) + { + if (loop == 0) + num_term++; + else + (*file)[count++] = strsave(name_buf); + } + } + } + if (loop == 0) + { + if (num_normal > 0) + *num_file = num_normal; + else if (num_term > 0) + *num_file = num_term; + else + return OK; + *file = (char_u **) alloc((unsigned)(*num_file * sizeof(char_u *))); + if (*file == NULL) + { + *file = (char_u **)""; + return FAIL; + } + } + } + return OK; +} + + int +ExpandOldSetting(num_file, file) + int *num_file; + char_u ***file; +{ + char_u *var = NULL; /* init for GCC */ + char_u *buf; + + *num_file = 0; + *file = (char_u **)alloc((unsigned)sizeof(char_u *)); + if (*file == NULL) + return FAIL; + + /* + * For a terminal key code epand_option_idx is < 0. + */ + if (expand_option_idx < 0) + { + var = find_termcode(expand_option_name + 2); + if (var == NULL) + expand_option_idx = findoption(expand_option_name); + } + + if (expand_option_idx >= 0) + { + /* put string of option value in NameBuff */ + option_value2string(&options[expand_option_idx]); + var = NameBuff; + } + else if (var == NULL) + var = (char_u *)""; + + /* A backslash is required before some characters */ + buf = strsave_escaped(var, escape_chars); + + if (buf == NULL) + { + vim_free(*file); + *file = NULL; + return FAIL; + } + + *file[0] = buf; + *num_file = 1; + return OK; +} + +/* + * Get the value for the numeric or string option *op in a nice format into + * NameBuff[]. Must not be called with a hidden option! + */ + static void +option_value2string(op) + struct option *op; +{ + char_u *varp; + + varp = get_varp(op); + if (op->flags & P_NUM) + { + if ((long *)varp == &p_wc) + { + if (IS_SPECIAL(p_wc) || find_special_key_in_table((int)p_wc) >= 0) + STRCPY(NameBuff, get_special_key_name((int)p_wc, 0)); + else + STRCPY(NameBuff, transchar((int)p_wc)); + } + else + sprintf((char *)NameBuff, "%ld", *(long *)varp); + } + else /* P_STRING */ + { + varp = *(char_u **)(varp); + if (varp == NULL) /* just in case */ + NameBuff[0] = NUL; + else if (op->flags & P_EXPAND) + home_replace(NULL, varp, NameBuff, MAXPATHL); + else + STRNCPY(NameBuff, varp, MAXPATHL); + } +} + +/* + * Convert the given pattern "pat" which has shell style wildcards in it, into + * a regular expression, and return the result. If there is a directory path + * separator to be matched, then TRUE is put in allow_directories, otherwise + * FALSE is put there -- webb. + */ + char_u * +file_pat_to_reg_pat(pat, pat_end, allow_directories) + char_u *pat; + char_u *pat_end; /* first char after pattern */ + int *allow_directories; /* Result passed back out in here */ +{ + int size; + char_u *endp; + char_u *reg_pat; + char_u *p; + int i; + int nested = 0; + int add_dollar = TRUE; + + if (allow_directories != NULL) + *allow_directories = FALSE; + + size = 2; /* '^' at start, '$' at end */ + for (p = pat; p < pat_end; p++) + { + switch (*p) + { + case '*': + case '.': + case ',': + case '{': + case '}': + case '~': +#ifdef BACKSLASH_IN_FILENAME + case '\\': +#endif + size += 2; + break; + default: + size++; + break; + } + } + reg_pat = alloc(size + 1); + if (reg_pat == NULL) + return NULL; + i = 0; + if (pat[0] == '*') + while (pat[0] == '*' && pat < pat_end - 1) + pat++; + else + reg_pat[i++] = '^'; + endp = pat_end - 1; + if (*endp == '*') + { + while (endp - pat > 0 && *endp == '*') + endp--; + add_dollar = FALSE; + } + for (p = pat; *p && nested >= 0 && p <= endp; p++) + { + switch (*p) + { + case '*': + reg_pat[i++] = '.'; + reg_pat[i++] = '*'; + break; + case '.': + case '~': + reg_pat[i++] = '\\'; + reg_pat[i++] = *p; + break; + case '?': + reg_pat[i++] = '.'; + break; + case '\\': + if (p[1] == NUL) + break; +#ifdef BACKSLASH_IN_FILENAME + /* translate "\x" to "\\x", "\*" to "\\.*", and "\?" to "\\." */ + if (isfilechar(p[1]) || p[1] == '*' || p[1] == '?') + { + reg_pat[i++] = '\\'; + reg_pat[i++] = '\\'; + if (allow_directories != NULL) + *allow_directories = TRUE; + break; + } + ++p; +#else + if (*++p == '?') + reg_pat[i++] = '?'; + else +#endif + if (*p == ',') + reg_pat[i++] = ','; + else + { + if (allow_directories != NULL && ispathsep(*p)) + *allow_directories = TRUE; + reg_pat[i++] = '\\'; + reg_pat[i++] = *p; + } + break; + case '{': + reg_pat[i++] = '\\'; + reg_pat[i++] = '('; + nested++; + break; + case '}': + reg_pat[i++] = '\\'; + reg_pat[i++] = ')'; + --nested; + break; + case ',': + if (nested) + { + reg_pat[i++] = '\\'; + reg_pat[i++] = '|'; + } + else + reg_pat[i++] = ','; + break; + default: + if (allow_directories != NULL && ispathsep(*p)) + *allow_directories = TRUE; + reg_pat[i++] = *p; + break; + } + } + if (add_dollar) + reg_pat[i++] = '$'; + reg_pat[i] = NUL; + if (nested != 0) + { + if (nested < 0) + EMSG("Missing {."); + else + EMSG("Missing }."); + vim_free(reg_pat); + reg_pat = NULL; + } + return reg_pat; +} + +#ifdef AUTOCMD +/* + * functions for automatic commands + */ + +static void show_autocmd __ARGS((AutoPat *ap, int event)); +static void del_autocmd __ARGS((AutoPat *ap)); +static void del_autocmd_cmds __ARGS((AutoPat *ap)); +static int event_name2nr __ARGS((char_u *start, char_u **end)); +static char *event_nr2name __ARGS((int event)); +static char_u *find_end_event __ARGS((char_u *arg)); +static int do_autocmd_event __ARGS((int event, char_u *pat, + char_u *cmd, int force)); + + static void +show_autocmd(ap, event) + AutoPat *ap; + int event; +{ + AutoCmd *ac; + + if (got_int) /* "q" hit for "--more--" */ + return; + msg_outchar('\n'); + if (got_int) /* "q" hit for "--more--" */ + return; + msg_outchar('\n'); + if (got_int) /* "q" hit for "--more--" */ + return; + MSG_OUTSTR(event_nr2name(event)); + MSG_OUTSTR(" "); + msg_outstr(ap->pat); + for (ac = ap->cmds; ac != NULL; ac = ac->next) + { + MSG_OUTSTR("\n "); + if (got_int) /* hit "q" at "--more--" prompt */ + return; + msg_outtrans(ac->cmd); + } +} + +/* + * Delete an autocommand pattern. + */ + static void +del_autocmd(ap) + AutoPat *ap; +{ + vim_free(ap->pat); + vim_free(ap->reg_pat); + del_autocmd_cmds(ap); + vim_free(ap); +} + +/* + * Delete the commands from a pattern. + */ + static void +del_autocmd_cmds(ap) + AutoPat *ap; +{ + AutoCmd *ac; + + while (ap->cmds != NULL) + { + ac = ap->cmds; + ap->cmds = ac->next; + vim_free(ac->cmd); + vim_free(ac); + } +} + +/* + * Return the event number for event name "start". + * Return -1 if the event name was not found. + * Return a pointer to the next event name in "end". + */ + static int +event_name2nr(start, end) + char_u *start; + char_u **end; +{ + char_u *p; + int i; + int len; + + /* the event name ends with end of line, a blank or a comma */ + for (p = start; *p && !vim_iswhite(*p) && *p != ','; ++p) + ; + for (i = 0; event_names[i].name != NULL; ++i) + { + len = strlen(event_names[i].name); + if (len == p - start && + vim_strnicmp((char_u *)event_names[i].name, (char_u *)start, (size_t)len) == 0) + break; + } + if (*p == ',') + ++p; + *end = p; + if (event_names[i].name == NULL) + return -1; + return event_names[i].event; +} + +/* + * Return the name for event "event". + */ + static char * +event_nr2name(event) + int event; +{ + int i; + + for (i = 0; event_names[i].name != NULL; ++i) + if (event_names[i].event == event) + return event_names[i].name; + return "Unknown"; +} + +/* + * Scan over the events. "*" stands for all events. + */ + static char_u * +find_end_event(arg) + char_u *arg; +{ + char_u *pat; + char_u *p; + + if (*arg == '*') + { + if (arg[1] && !vim_iswhite(arg[1])) + { + EMSG2("Illegal character after *: %s", arg); + return NULL; + } + pat = arg + 1; + } + else + { + for (pat = arg; *pat && !vim_iswhite(*pat); pat = p) + { + if (event_name2nr(pat, &p) < 0) + { + EMSG2("No such event: %s", pat); + return NULL; + } + } + } + return pat; +} + +/* + * do_autocmd() -- implements the :autocmd command. Can be used in the + * following ways: + * + * :autocmd Add to the list of commands that + * will be automatically executed for + * when editing a file matching . + * :autocmd Show the auto-commands associated with + * and . + * :autocmd Show the auto-commands associated with + * . + * :autocmd Show all auto-commands. + * :autocmd! Remove all auto-commands associated with + * and , and add the command + * . + * :autocmd! Remove all auto-commands associated with + * and . + * :autocmd! Remove all auto-commands associated with + * . + * :autocmd! Remove ALL auto-commands. + * + * Multiple events and patterns may be given separated by commas. Here are + * some examples: + * :autocmd bufread,bufenter *.c,*.h set tw=0 smartindent noic + * :autocmd bufleave * set tw=79 nosmartindent ic infercase + * + * :autocmd * *.c show all autocommands for *.c files. + */ + void +do_autocmd(arg, force) + char_u *arg; + int force; +{ + char_u *pat; + char_u *cmd; + int event; + + /* + * Don't change autocommands while executing one. + */ + if (autocmd_busy) + return; + + /* + * Scan over the events. + * If we find an illegal name, return here, don't do anything. + */ + pat = find_end_event(arg); + if (pat == NULL) + return; + + /* + * Scan over the pattern. Put a NUL at the end. + */ + pat = skipwhite(pat); + cmd = pat; + while (*cmd && (!vim_iswhite(*cmd) || cmd[-1] == '\\')) + cmd++; + if (*cmd) + *cmd++ = NUL; + + /* + * Find the start of the commands. + */ + cmd = skipwhite(cmd); + + /* + * Print header when showing autocommands. + */ + if (!force && *cmd == NUL) + { + set_highlight('t'); /* Highlight title */ + start_highlight(); + MSG_OUTSTR("\n--- Auto-Commands ---"); + stop_highlight(); + } + + /* + * Loop over the events. + */ + if (*arg == '*' || *arg == NUL) + { + for (event = 0; event < NUM_EVENTS; ++event) + if (do_autocmd_event(event, pat, cmd, force) == FAIL) + break; + } + else + { + while (*arg && !vim_iswhite(*arg)) + if (do_autocmd_event(event_name2nr(arg, &arg), pat, + cmd, force) == FAIL) + break; + } +} + +/* + * do_autocmd() for one event. + * If *pat == NUL do for all patterns. + * If *cmd == NUL show entries. + * If force == TRUE delete entries. + */ + static int +do_autocmd_event(event, pat, cmd, force) + int event; + char_u *pat; + char_u *cmd; + int force; +{ + AutoPat *ap; + AutoPat *ap2; + AutoPat **final_ap; + AutoCmd *ac; + AutoCmd **final_ac; + int nested; + char_u *endpat; + int len; + + /* + * Show or delete all patterns for an event. + */ + if (*pat == NUL) + { + for (ap = first_autopat[event]; ap != NULL; ap = ap2) + { + ap2 = ap->next; + if (force) + del_autocmd(ap); + else + show_autocmd(ap, event); + } + if (force) + first_autopat[event] = NULL; + } + + /* + * Loop through all the specified patterns. + */ + for ( ; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat)) + { + /* + * Find end of the pattern. + * Watch out for a comma in braces, like "*.\{obj,o\}". + */ + nested = 0; + for (endpat = pat; + *endpat && (*endpat != ',' || nested || endpat[-1] == '\\'); + ++endpat) + { + if (*endpat == '{') + nested++; + else if (*endpat == '}') + nested--; + } + if (pat == endpat) /* ignore single comma */ + continue; + + /* + * Find entry with same pattern. + */ + final_ap = &first_autopat[event]; + for (ap = first_autopat[event]; ap != NULL; ap = *final_ap) + { + len = STRLEN(ap->pat); + if (len == endpat - pat && STRNCMP(pat, ap->pat, len) == 0) + break; + final_ap = &ap->next; + } + + /* + * Add a new pattern. + * Show and delete are ignored if pattern is not found. + */ + if (ap == NULL) + { + if (*cmd == NUL) + continue; + + /* Add the autocmd at the end of the list */ + ap = (AutoPat *)alloc((unsigned)sizeof(AutoPat)); + if (ap == NULL) + return FAIL; + ap->pat = strnsave(pat, (int)(endpat - pat)); + if (ap->pat == NULL) + { + vim_free(ap); + return FAIL; + } + ap->reg_pat = file_pat_to_reg_pat(pat, endpat, + &ap->allow_directories); + if (ap->reg_pat == NULL) + { + vim_free(ap->pat); + vim_free(ap); + return FAIL; + } + ap->cmds = NULL; + *final_ap = ap; + ap->next = NULL; + } + + /* + * Remove existing autocommands. + * If not adding any new autocmd's for this pattern, delete the + * pattern from the autopat list + */ + else if (force) + { + del_autocmd_cmds(ap); + if (*cmd == NUL) + { + if (ap == first_autopat[event]) + first_autopat[event] = ap->next; + else + { + for (ap2 = first_autopat[event]; + ap2->next != ap; + ap2 = ap2->next) + ; + ap2->next = ap->next; + } + del_autocmd(ap); + } + } + + /* + * Show autocmd's for this autopat + */ + if (*cmd == NUL && !force) + { + show_autocmd(ap, event); + } + + /* + * Add the autocmd at the end if it's not already there. + */ + else if (*cmd != NUL) + { + final_ac = &(ap->cmds); + for (ac = ap->cmds; + ac != NULL && STRCMP(cmd, ac->cmd) != 0; + ac = ac->next) + final_ac = &ac->next; + if (ac == NULL) + { + ac = (AutoCmd *)alloc((unsigned)sizeof(AutoCmd)); + if (ac == NULL) + return FAIL; + ac->cmd = strsave(cmd); + if (ac->cmd == NULL) + { + vim_free(ac); + return FAIL; + } + ac->next = NULL; + *final_ac = ac; + } + } + } + return OK; +} + +/* + * Implementation of ":doautocmd event [fname]". + */ + void +do_doautocmd(arg) + char_u *arg; +{ + char_u *fname; + int nothing_done = TRUE; + + if (*arg == '*') + { + EMSG("Can't execute autocommands for ALL events"); + return; + } + + /* + * Scan over the events. + * If we find an illegal name, return here, don't do anything. + */ + fname = find_end_event(arg); + if (fname == NULL) + return; + + fname = skipwhite(fname); + + /* + * Loop over the events. + */ + while (*arg && !vim_iswhite(*arg)) + if (apply_autocmds(event_name2nr(arg, &arg), fname, NULL)) + nothing_done = FALSE; + + if (nothing_done) + MSG("No matching autocommands"); +} + +/* + * Execute autocommands for "event" and file name "fname". + * Return TRUE if some commands were executed. + */ + int +apply_autocmds(event, fname, fname_io) + int event; + char_u *fname; /* NULL or empty means use actual file name */ + char_u *fname_io; /* fname to use for "^Vf" on cmdline */ +{ + struct regexp *prog; + char_u *tail; + AutoPat *ap; + AutoCmd *ac; + int temp; + int save_changed = curbuf->b_changed; + char_u *save_name; + char_u *full_fname = NULL; + int retval = FALSE; + + if (autocmd_busy) /* no nesting allowed */ + return retval; + + /* Don't redraw while doing auto commands. */ + temp = RedrawingDisabled; + RedrawingDisabled = TRUE; + save_name = sourcing_name; /* may be called from .vimrc */ + autocmd_fname = fname_io; + + /* + * While applying autocmds, we don't want to allow the commands + * :doautocmd or :autocmd. + */ + autocmd_busy = TRUE; + + /* + * When the file name is NULL or empty, use the file name of the current + * buffer. Always use the full path of the file name to match with, in + * case "allow_directories" is set. + */ + if (fname == NULL || *fname == NUL) + { + fname = curbuf->b_filename; + if (fname == NULL) + fname = (char_u *)""; + } + else + { + full_fname = FullName_save(fname); + fname = full_fname; + } + + tail = gettail(fname); + + for (ap = first_autopat[event]; ap != NULL; ap = ap->next) + { +#ifdef CASE_INSENSITIVE_FILENAME + reg_ic = TRUE; /* Always ignore case */ +#else + reg_ic = FALSE; /* Don't ever ignore case */ +#endif + reg_magic = TRUE; /* Always use magic */ + prog = vim_regcomp(ap->reg_pat); + + if (prog != NULL && + ((ap->allow_directories && vim_regexec(prog, fname, TRUE)) || + (!ap->allow_directories && vim_regexec(prog, tail, TRUE)))) + { + sprintf((char *)IObuff, "%s Auto commands for \"%s\"", + event_nr2name(event), (char *)ap->pat); + sourcing_name = strsave(IObuff); + for (ac = ap->cmds; ac != NULL; ac = ac->next) + { + do_cmdline(ac->cmd, TRUE, TRUE); + retval = TRUE; + } + vim_free(sourcing_name); + } + vim_free(prog); + } + RedrawingDisabled = temp; + autocmd_busy = FALSE; + sourcing_name = save_name; + autocmd_fname = NULL; + vim_free(full_fname); + + /* Some events don't set or reset the Changed flag */ + if (event == EVENT_BUFREADPOST || event == EVENT_BUFWRITEPOST || + event == EVENT_FILEAPPENDPOST || event == EVENT_VIMLEAVE) + curbuf->b_changed = save_changed; + + return retval; +} + + char_u * +set_context_in_autocmd(arg, doautocmd) + char_u *arg; + int doautocmd; /* TRUE for :doautocmd, FALSE for :autocmd */ +{ + char_u *p; + + /* skip over event name */ + for (p = arg; *p && !vim_iswhite(*p); ++p) + if (*p == ',') + arg = p + 1; + if (*p == NUL) + { + expand_context = EXPAND_EVENTS; /* expand event name */ + expand_pattern = arg; + return NULL; + } + + /* skip over pattern */ + arg = skipwhite(p); + while (*arg && (!vim_iswhite(*arg) || arg[-1] == '\\')) + arg++; + if (*arg) + return arg; /* expand (next) command */ + + if (doautocmd) + expand_context = EXPAND_FILES; /* expand file names */ + else + expand_context = EXPAND_NOTHING; /* pattern is not expanded */ + return NULL; +} + + int +ExpandEvents(prog, num_file, file) + regexp *prog; + int *num_file; + char_u ***file; +{ + int i; + int count; + int round; + + /* + * round == 1: Count the matches. + * round == 2: Save the matches into the array. + */ + for (round = 1; round <= 2; ++round) + { + count = 0; + for (i = 0; event_names[i].name != NULL; i++) + if (vim_regexec(prog, (char_u *)event_names[i].name, TRUE)) + { + if (round == 1) + count++; + else + (*file)[count++] = strsave((char_u *)event_names[i].name); + } + if (round == 1) + { + *num_file = count; + if (count == 0 || (*file = (char_u **) + alloc((unsigned)(count * sizeof(char_u *)))) == NULL) + return FAIL; + } + } + return OK; +} + +#endif /* AUTOCMD */ + +#ifdef HAVE_LANGMAP +/* + * Any character has an equivalent character. This is used for keyboards that + * have a special language mode that sends characters above 128 (although + * other characters can be translated too). + */ + +/* + * char_u langmap_mapchar[256]; + * Normally maps each of the 128 upper chars to an <128 ascii char; used to + * "translate" native lang chars in normal mode or some cases of + * insert mode without having to tediously switch lang mode back&forth. + */ + + static void +langmap_init() +{ + int i; + + for (i = 0; i < 256; i++) /* we init with a-one-to one map */ + langmap_mapchar[i] = i; +} + +/* + * Called when langmap option is set; the language map can be + * changed at any time! + */ + static void +langmap_set() +{ + char_u *p; + char_u *p2; + int from, to; + + langmap_init(); /* back to one-to-one map first */ + + for (p = p_langmap; p[0]; ) + { + for (p2 = p; p2[0] && p2[0] != ',' && p2[0] != ';'; ++p2) + if (p2[0] == '\\' && p2[1]) + ++p2; + if (p2[0] == ';') + ++p2; /* abcd;ABCD form, p2 points to A */ + else + p2 = NULL; /* aAbBcCdD form, p2 is NULL */ + while (p[0]) + { + if (p[0] == '\\' && p[1]) + ++p; + from = p[0]; + if (p2 == NULL) + { + if (p[1] == '\\') + ++p; + to = p[1]; + } + else + { + if (p2[0] == '\\') + ++p2; + to = p2[0]; + } + if (to == NUL) + { + EMSG2("'langmap': Matching character missing for %s", + transchar(from)); + return; + } + langmap_mapchar[from] = to; + + /* Advance to next pair */ + if (p2 == NULL) + { + p += 2; + if (p[0] == ',') + { + ++p; + break; + } + } + else + { + ++p; + ++p2; + if (*p == ';') + { + p = p2; + if (p[0]) + { + if (p[0] != ',') + { + EMSG2("'langmap': Extra characters after semicolon: %s", p); + return; + } + ++p; + } + break; + } + } + } + } +} +#endif + +/* + * Return TRUE if format option 'x' is in effect. + * Take care of no formatting when 'paste' is set. + */ + int +has_format_option(x) + int x; +{ + if (p_paste) + return FALSE; + return (vim_strchr(curbuf->b_p_fo, x) != NULL); +} + +/* + * Return TRUE if "x" is present in 'shortmess' option, or + * 'shortmess' contains 'a' and "x" is present in SHM_A. + */ + int +shortmess(x) + int x; +{ + return (vim_strchr(p_shm, x) != NULL || (vim_strchr(p_shm, 'a') != NULL && + vim_strchr((char_u *)SHM_A, x) != NULL)); +} + +/* + * set_paste_option() - Called after p_paste was set or reset. + */ + static void +paste_option_changed() +{ + static int old_p_paste = FALSE; + static int save_sm = 0; + static int save_ru = 0; +#ifdef RIGHTLEFT + static int save_ri = 0; + static int save_hkmap = 0; +#endif + BUF *buf; + + if (p_paste) + { + /* + * Paste switched from off to on. + * Save the current values, so they can be restored later. + */ + if (!old_p_paste) + { + /* save options for each buffer */ + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + { + buf->b_p_tw_save = buf->b_p_tw; + buf->b_p_wm_save = buf->b_p_wm; + buf->b_p_ai_save = buf->b_p_ai; +#ifdef SMARTINDENT + buf->b_p_si_save = buf->b_p_si; +#endif +#ifdef CINDENT + buf->b_p_cin_save = buf->b_p_cin; +#endif +#ifdef LISPINDENT + buf->b_p_lisp_save = buf->b_p_lisp; +#endif + } + + /* save global options */ + save_sm = p_sm; + save_ru = p_ru; +#ifdef RIGHTLEFT + save_ri = p_ri; + save_hkmap = p_hkmap; +#endif + } + + /* + * Always set the option values, also when 'paste' is set when it is + * already on. + */ + /* set options for each buffer */ + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + { + buf->b_p_tw = 0; /* textwidth is 0 */ + buf->b_p_wm = 0; /* wrapmargin is 0 */ + buf->b_p_ai = 0; /* no auto-indent */ +#ifdef SMARTINDENT + buf->b_p_si = 0; /* no smart-indent */ +#endif +#ifdef CINDENT + buf->b_p_cin = 0; /* no c indenting */ +#endif +#ifdef LISPINDENT + buf->b_p_lisp = 0; /* no lisp indenting */ +#endif + } + + /* set global options */ + p_sm = 0; /* no showmatch */ + p_ru = 0; /* no ruler */ +#ifdef RIGHTLEFT + p_ri = 0; /* no reverse insert */ + p_hkmap = 0; /* no Hebrew keyboard */ +#endif + } + + /* + * Paste switched from on to off: Restore saved values. + */ + else if (old_p_paste) + { + /* restore options for each buffer */ + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + { + buf->b_p_tw = buf->b_p_tw_save; + buf->b_p_wm = buf->b_p_wm_save; + buf->b_p_ai = buf->b_p_ai_save; +#ifdef SMARTINDENT + buf->b_p_si = buf->b_p_si_save; +#endif +#ifdef CINDENT + buf->b_p_cin = buf->b_p_cin_save; +#endif +#ifdef LISPINDENT + buf->b_p_lisp = buf->b_p_lisp_save; +#endif + } + + /* restore global options */ + p_sm = save_sm; + p_ru = save_ru; +#ifdef RIGHTLEFT + p_ri = save_ri; + p_hkmap = save_hkmap; +#endif + } + + old_p_paste = p_paste; +} + +/* + * p_compatible_set() - Called when p_cp has been set. + */ + static void +p_compatible_set() +{ + p_bs = 0; /* normal backspace */ + /* backspace and space do not wrap */ + set_string_option((char_u *)"ww", -1, (char_u *)"", TRUE); + p_bk = 0; /* no backup file */ + /* Use textwidth for formatting, don't format comments */ + set_string_option((char_u *)"fo", -1, (char_u *)FO_DFLT_VI, TRUE); + /* all compatible flags on */ + set_string_option((char_u *)"cpo", -1, (char_u *)CPO_ALL, TRUE); + set_string_option((char_u *)"isk", -1, (char_u *)"@,48-57,_", TRUE); + /* no short messages */ + set_string_option((char_u *)"shm", -1, (char_u *)"", TRUE); +#ifdef DIGRAPHS + p_dg = 0; /* no digraphs */ +#endif /* DIGRAPHS */ + p_ek = 0; /* no ESC keys in insert mode */ + curbuf->b_p_et = 0; /* no expansion of tabs */ + p_gd = 0; /* /g is not default for :s */ + p_hi = 0; /* no history */ + p_scs = 0; /* no ignore case switch */ + p_im = 0; /* do not start in insert mode */ + p_js = 1; /* insert 2 spaces after period */ + curbuf->b_p_ml = 0; /* no modelines */ + p_more = 0; /* no -- more -- for listings */ + p_ru = 0; /* no ruler */ +#ifdef RIGHTLEFT + p_ri = 0; /* no reverse insert */ + p_hkmap = 0; /* no Hebrew keyboard mapping */ +#endif + p_sj = 1; /* no scrolljump */ + p_so = 0; /* no scrolloff */ + p_sr = 0; /* do not round indent to shiftwidth */ + p_sc = 0; /* no showcommand */ + p_smd = 0; /* no showmode */ +#ifdef SMARTINDENT + curbuf->b_p_si = 0; /* no smartindent */ +#endif +#ifdef CINDENT + curbuf->b_p_cin = 0; /* no C indenting */ +#endif + p_sta = 0; /* no smarttab */ + p_sol = TRUE; /* Move cursor to start-of-line */ + p_ta = 0; /* no automatic textmode detection */ + curbuf->b_p_tw = 0; /* no automatic line wrap */ + p_to = 0; /* no tilde operator */ + p_ttimeout = 0; /* no terminal timeout */ + p_tr = 0; /* tag file names not relative */ + p_ul = 0; /* no multilevel undo */ + p_uc = 0; /* no autoscript file */ + p_wb = 0; /* no backup file */ + if (p_wc == TAB) + p_wc = Ctrl('E'); /* normal use for TAB */ + init_chartab(); /* make b_p_isk take effect */ +} + +/* + * fill_breakat_flags() -- called when 'breakat' changes value. + */ + static void +fill_breakat_flags() +{ + char_u *c; + int i; + + for (i = 0; i < 256; i++) + breakat_flags[i] = FALSE; + + if (p_breakat != NULL) + for (c = p_breakat; *c; c++) + breakat_flags[*c] = TRUE; +} diff --git a/usr.bin/vim/option.h b/usr.bin/vim/option.h new file mode 100644 index 00000000000..42d649cdc01 --- /dev/null +++ b/usr.bin/vim/option.h @@ -0,0 +1,247 @@ +/* $OpenBSD: option.h,v 1.1.1.1 1996/09/07 21:40:27 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * option.h: definition of global variables for settable options + * + * EXTERN is only defined in main.c (and vim.h) + */ + +#ifndef EXTERN +# define EXTERN extern +# define INIT(x) +#else +# ifndef INIT +# define INIT(x) x +# endif +#endif + +/* Formatting options for the p_fo option: */ +#define FO_WRAP 't' +#define FO_WRAP_COMS 'c' +#define FO_RET_COMS 'r' +#define FO_OPEN_COMS 'o' +#define FO_Q_COMS 'q' +#define FO_Q_SECOND '2' +#define FO_INS_VI 'v' +#define FO_INS_LONG 'l' +#define FO_INS_BLANK 'b' + +#define FO_DFLT_VI "vt" +#define FO_DFLT "tcq" +#define FO_ALL "tcroq2vlb," /* for do_set() */ + +/* characters for the p_cpo option: */ +#define CPO_BAR 'b' /* "\|" ends a mapping */ +#define CPO_BSLASH 'B' /* backslash in mapping is not special */ +#define CPO_SEARCH 'c' +#define CPO_EXECBUF 'e' +#define CPO_FNAMER 'f' /* set file name for ":r file" */ +#define CPO_FNAMEW 'F' /* set file name for ":w file" */ +#define CPO_KEYCODE 'k' /* don't recognize raw key code in mappings */ +#define CPO_SHOWMATCH 'm' +#define CPO_LINEOFF 'o' +#define CPO_REDO 'r' +#define CPO_BUFOPT 's' +#define CPO_BUFOPTGLOB 'S' +#define CPO_TAGPAT 't' +#define CPO_ESC 'x' +#define CPO_DOLLAR '$' +#define CPO_FILTER '!' +#define CPO_MATCH '%' +#define CPO_SPECI '<' /* don't recognize <> in mappings */ +#define CPO_DEFAULT "BceFs" +#define CPO_ALL "bBcefFkmorsStx$!%<" + +/* characters for p_ww option: */ +#define WW_ALL "bshl<>[]," + +/* characters for p_mouse option: */ +#define MOUSE_NORMAL 'n' /* use mouse in normal mode */ +#define MOUSE_VISUAL 'v' /* use mouse in visual mode */ +#define MOUSE_INSERT 'i' /* use mouse in insert mode */ +#define MOUSE_COMMAND 'c' /* use mouse in command line mode */ +#define MOUSE_HELP 'h' /* use mouse in help buffers */ +#define MOUSE_RETURN 'r' /* use mouse for hit-return message */ +#define MOUSE_A "nvich" /* used for 'a' flag */ +#define MOUSE_ALL "anvicrh" /* all possible characters */ + +/* characters for p_shm option: */ +#define SHM_RO 'r' /* readonly */ +#define SHM_MOD 'm' /* modified */ +#define SHM_FILE 'f' /* (file 1 of 2) */ +#define SHM_LAST 'i' /* last line incomplete */ +#define SHM_TEXT 'x' /* tx instead of textmode */ +#define SHM_LINES 'l' /* "L" instead of "lines" */ +#define SHM_NEW 'n' /* "[New]" instead of "[New file]" */ +#define SHM_WRI 'w' /* "[w]" instead of "written" */ +#define SHM_A "rmfixlnw" /* represented by 'a' flag */ +#define SHM_WRITE 'W' /* don't use "written" at all */ +#define SHM_TRUNC 't' /* trunctate message */ +#define SHM_OVER 'o' /* overwrite file messages */ +#define SHM_SEARCH 's' /* no search hit bottom messages */ +#define SHM_ALL "rmfixlnwaWtos" /* all possible flags for 'shm' */ + +/* characters for p_guioptions: */ +#define GO_ASEL 'a' /* GUI: autoselect */ +#define GO_BOT 'b' /* GUI: use bottom scrollbar */ +#define GO_FORG 'f' /* GUI: start GUI in foreground */ +#define GO_GREY 'g' /* GUI: use grey menu items */ +#define GO_LEFT 'l' /* GUI: use left scrollbar */ +#define GO_MENUS 'm' /* GUI: use menu bar */ +#define GO_RIGHT 'r' /* GUI: use right scrollbar */ +#define GO_ALL "abfglmr" /* all possible flags for 'go' */ + +/* flags for 'comments' option */ +#define COM_NEST 'n' /* comments strings nest */ +#define COM_BLANK 'b' /* needs blank after string */ +#define COM_START 's' /* start of comment */ +#define COM_MIDDLE 'm' /* middle of comment */ +#define COM_END 'e' /* end of comment */ +#define COM_FIRST 'f' /* first line comment only */ +#define COM_LEFT 'l' /* left adjusted */ +#define COM_RIGHT 'r' /* right adjusted */ +#define COM_ALL "nbsmeflr" /* all flags for 'comments' option */ +#define COM_MAX_LEN 50 /* maximum lenght of a part */ + +/* + * The following are actual variabables for the options + */ + +#ifdef RIGHTLEFT +EXTERN int p_aleph; /* Hebrew 'Aleph' encoding */ +#endif +EXTERN int p_aw; /* auto-write */ +EXTERN long p_bs; /* backspace over newlines in insert mode */ +EXTERN int p_bk; /* make backups when writing out files */ +EXTERN char_u *p_bdir; /* list of directory names for backup files */ +EXTERN char_u *p_bex; /* extension for backup file */ +#ifdef MSDOS +EXTERN int p_biosk; /* Use bioskey() instead of kbhit() */ +#endif +EXTERN char_u *p_breakat; /* characters that can cause a line break */ +EXTERN long p_ch; /* command line height */ +EXTERN int p_cp; /* vi-compatible */ +EXTERN char_u *p_cpo; /* vi-compatible option flags */ +EXTERN char_u *p_def; /* Pattern for recognising definitions */ +EXTERN char_u *p_dict; /* Dictionaries for ^P/^N */ +#ifdef DIGRAPHS +EXTERN int p_dg; /* enable digraphs */ +#endif /* DIGRAPHS */ +EXTERN char_u *p_dir; /* list of directories for swap file */ +EXTERN int p_ed; /* :s is ed compatible */ +EXTERN int p_ea; /* make windows equal height */ +EXTERN char_u *p_ep; /* program name for '=' command */ +EXTERN int p_eb; /* ring bell for errors */ +EXTERN char_u *p_ef; /* name of errorfile */ +EXTERN char_u *p_efm; /* error format */ +EXTERN int p_ek; /* function keys with ESC in insert mode */ +EXTERN int p_exrc; /* read .exrc in current dir */ +EXTERN char_u *p_fp; /* name of format program */ +EXTERN int p_gd; /* /g is default for :s */ +#ifdef USE_GUI +EXTERN char_u *p_guifont; /* GUI font list */ +EXTERN char_u *p_guioptions; /* Which GUI components? */ +EXTERN int p_guipty; /* use pseudo pty for external commands */ +#endif +EXTERN char_u *p_hf; /* name of help file */ +EXTERN long p_hh; /* help window height */ +EXTERN int p_hid; /* buffers can be hidden */ +EXTERN char_u *p_hl; /* which highlight mode to use */ +EXTERN long p_hi; /* command line history size */ +#ifdef RIGHTLEFT +EXTERN int p_hkmap; /* Hebrew keyboard map */ +#endif +EXTERN int p_icon; /* put file name in icon if possible */ +EXTERN int p_ic; /* ignore case in searches */ +EXTERN int p_is; /* incremental search */ +EXTERN int p_im; /* start editing in input mode */ +EXTERN char_u *p_inc; /* Pattern for including other files */ +EXTERN char_u *p_isf; /* characters in a file name */ +EXTERN char_u *p_isi; /* characters in an identifier */ +EXTERN char_u *p_isp; /* characters that are printable */ +EXTERN int p_js; /* use two spaces after '.' with Join */ +EXTERN char_u *p_kp; /* keyword program */ +#ifdef HAVE_LANGMAP +EXTERN char_u *p_langmap; /* mapping for some language */ +#endif +EXTERN long p_ls; /* last window has status line */ +EXTERN int p_magic; /* use some characters for reg exp */ +EXTERN char_u *p_mp; /* program for :make command */ +EXTERN long p_mmd; /* maximal map depth */ +EXTERN long p_mm; /* maximal amount of memory for buffer */ +EXTERN long p_mmt; /* maximal amount of memory for Vim */ +EXTERN long p_mls; /* number of mode lines */ +EXTERN char_u *p_mouse; /* enable mouse clicks (for xterm) */ +EXTERN long p_mouset; /* mouse double click time */ +EXTERN int p_more; /* wait when screen full when listing */ +EXTERN char_u *p_para; /* paragraphs */ +EXTERN int p_paste; /* paste mode */ +EXTERN char_u *p_pm; /* patchmode file suffix */ +EXTERN char_u *p_path; /* path for "]f" and "^Wf" */ +EXTERN int p_remap; /* remap */ +EXTERN long p_report; /* minimum number of lines for report */ +#ifdef WIN32 +EXTERN int p_rs; /* restore startup screen upon exit */ +#endif +#ifdef RIGHTLEFT +EXTERN int p_ri; /* reverse direction of insert */ +#endif +EXTERN int p_ru; /* show column/line number */ +EXTERN long p_sj; /* scroll jump size */ +EXTERN long p_so; /* scroll offset */ +EXTERN char_u *p_sections; /* sections */ +EXTERN int p_secure; /* do .exrc and .vimrc in secure mode */ +EXTERN char_u *p_sh; /* name of shell to use */ +EXTERN char_u *p_sp; /* string for output of make */ +EXTERN char_u *p_srr; /* string for output of filter */ +EXTERN long p_st; /* type of shell */ +EXTERN int p_sr; /* shift round off (for < and >) */ +EXTERN char_u *p_shm; /* When to use short message */ +EXTERN char_u *p_sbr; /* string for break of line */ +EXTERN int p_sc; /* show command in status line */ +EXTERN int p_sm; /* showmatch */ +EXTERN int p_smd; /* show mode */ +EXTERN long p_ss; /* sideways scrolling offset */ +EXTERN int p_scs; /* 'smartcase' */ +EXTERN int p_sta; /* smart-tab for expand-tab */ +EXTERN int p_sb; /* split window backwards */ +EXTERN int p_sol; /* Move cursor to start-of-line? */ +EXTERN char_u *p_su; /* suffixes for wildcard expansion */ +EXTERN char_u *p_sws; /* swap file syncing */ +EXTERN long p_tl; /* used tag length */ +EXTERN int p_tr; /* tag file name is relative */ +EXTERN char_u *p_tags; /* tags search path */ +EXTERN int p_terse; /* terse messages */ +EXTERN int p_ta; /* auto textmode detection */ +EXTERN int p_to; /* tilde is an operator */ +EXTERN int p_timeout; /* mappings entered within one second */ +EXTERN long p_tm; /* timeoutlen (msec) */ +EXTERN int p_title; /* set window title if possible */ +EXTERN int p_ttimeout; /* key codes entered within one second */ +EXTERN long p_ttm; /* key code timeoutlen (msec) */ +EXTERN int p_tbi; /* 'ttybuiltin' use builtin termcap first */ +EXTERN int p_tf; /* terminal fast I/O */ +EXTERN long p_ttyscroll; /* maximum number of screen lines for a scroll */ +EXTERN long p_ul; /* number of Undo Levels */ +EXTERN long p_uc; /* update count for swap file */ +EXTERN long p_ut; /* update time for swap file */ +#ifdef VIMINFO +EXTERN char_u *p_viminfo; /* Parameters for using ~/.viminfo file */ +#endif /* VIMINFO */ +EXTERN int p_vb; /* visual bell only (no beep) */ +EXTERN int p_warn; /* warn for changes at shell command */ +EXTERN int p_wiv; /* inversion of text is weird */ +EXTERN char_u *p_ww; /* which keys wrap to next/prev line */ +EXTERN long p_wc; /* character for wildcard exapansion */ +EXTERN long p_wh; /* desired window height */ +EXTERN int p_ws; /* wrap scan */ +EXTERN int p_wa; /* write any */ +EXTERN int p_wb; /* write backup files */ +EXTERN long p_wd; /* write delay for screen output (for testing) */ diff --git a/usr.bin/vim/osdef.h b/usr.bin/vim/osdef.h new file mode 100644 index 00000000000..7f6230136b7 --- /dev/null +++ b/usr.bin/vim/osdef.h @@ -0,0 +1,82 @@ +/* $OpenBSD: osdef.h,v 1.1.1.1 1996/09/07 21:40:27 downsj Exp $ */ +/* +* osdef.h is automagically created from osdef?.h.in by osdef.sh -- DO NOT EDIT +*/ +/* autoconf cannot fiddle out declarations. Use our homebrewn tools. (jw) */ +/* + * Declarations that may cause conflicts belong here so that osdef.sh + * can clean out the forest. Everything else belongs in unix.h + * + * How this works: + * - This file contains all unix prototypes that Vim might need. + * - The shell script osdef.sh is executed at compile time to remove all the + * prototypes that are in an include file. This results in osdef.h. + * - osdef.h is included in vim.h. + * + * sed cannot always handle so many commands, this is file 1 of 2 + */ + +#ifndef ferror /* let me say it again: "macros should never have prototypes" */ +#endif +#if defined(sun) || defined(_SEQUENT_) +/* used inside of stdio macros getc(), puts(), putchar()... */ +extern int _flsbuf __ARGS((int, FILE *)); +extern int _filbuf __ARGS((FILE *)); +#endif + +#if !defined(HAVE_SELECT) +struct pollfd; /* for poll __ARGS */ +extern int poll __ARGS((struct pollfd *, long, int)); +#endif + +#ifdef HAVE_MEMSET +#endif +#ifdef HAVE_STRCSPN +#endif +#ifdef USEBCOPY +#else +# ifdef USEMEMCPY +# else +# ifdef USEMEMMOVE +# endif +# endif +#endif +/* used inside of FDZERO macro: */ +#ifdef HAVE_STRTOL +#endif + +#ifndef USE_SYSTEM +#endif + + +#ifdef HAVE_SIGSET +extern RETSIGTYPE (*sigset __ARGS((int, RETSIGTYPE (*func) SIGPROTOARG))) __PARMS(SIGPROTOARG); +#endif + + +#if defined(HAVE_GETCWD) && !defined(sun) +#else +#endif + + + + +extern int tgetent __ARGS((char *, char *)); +extern int tgetnum __ARGS((char *)); +extern int tgetflag __ARGS((char *)); +extern char *tgoto __ARGS((char *, int, int)); +extern int tputs __ARGS((char *, int, int (*)(int))); + +#ifdef HAVE_TERMIOS_H +#endif + +#ifdef HAVE_SYS_STATFS_H +struct statfs; /* for fstatfs __ARGS */ +extern int fstatfs __ARGS((int, struct statfs *, int, int)); +#endif + +#ifdef HAVE_GETTIMEOFDAY +#endif + +#ifdef HAVE_GETPWNAM +#endif diff --git a/usr.bin/vim/pathdef.c b/usr.bin/vim/pathdef.c new file mode 100644 index 00000000000..2689a89c5f4 --- /dev/null +++ b/usr.bin/vim/pathdef.c @@ -0,0 +1,7 @@ +/* $OpenBSD: pathdef.c,v 1.1.1.1 1996/09/07 21:40:25 downsj Exp $ */ +#include "vim.h" +char_u *sys_compatrc_fname = (char_u *)SYS_COMPATRC_FILE; +char_u *sys_vimrc_fname = (char_u *)SYS_VIMRC_FILE; +char_u *sys_gvimrc_fname = (char_u *)SYS_GVIMRC_FILE; +char_u *help_fname = (char_u *)VIM_HLP; +char_u *all_cflags = (char_u *)""; diff --git a/usr.bin/vim/proto.h b/usr.bin/vim/proto.h new file mode 100644 index 00000000000..707a8ebba1f --- /dev/null +++ b/usr.bin/vim/proto.h @@ -0,0 +1,105 @@ +/* $OpenBSD: proto.h,v 1.1.1.1 1996/09/07 21:40:27 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * proto.h: include the (automatically generated) function prototypes + * + * the proto/xxx.pro files are automatically generated when using Manx/Aztec C. + * For other compilers you will have to edit them. + */ + +#include "regexp.h" /* for struct regexp */ + +/* + * don't include these while generating prototypes, prevents problems when + * files are missing + */ +#ifndef PROTO + +/* + * Machine-dependent routines. + */ +# ifdef AMIGA +# include "proto/amiga.pro" +# endif +# if defined(UNIX) || defined(__EMX__) +# include "proto/unix.pro" +# ifndef HAVE_RENAME + int rename __PARMS((const char *, const char *)); +# endif +# endif +# ifdef MSDOS +# include "proto/msdos.pro" +# endif +# ifdef WIN32 +# include "proto/win32.pro" +# endif +# ifdef VMS +# include "proto/vms.pro" +# endif + +# include "proto/alloc.pro" +# include "proto/buffer.pro" +# include "proto/charset.pro" +# include "proto/cmdcmds.pro" +# include "proto/cmdline.pro" +# include "proto/csearch.pro" +# include "proto/digraph.pro" +# include "proto/edit.pro" +# include "proto/fileio.pro" +# include "proto/getchar.pro" +# include "proto/help.pro" +# include "proto/linefunc.pro" +# include "proto/main.pro" +# include "proto/mark.pro" +# ifndef MESSAGE +void smsg __PARMS((char_u *, ...)); /* cannot be produced automatically */ +# endif +# include "proto/memfile.pro" +# include "proto/memline.pro" +# include "proto/message.pro" +# include "proto/misccmds.pro" +# include "proto/normal.pro" +# include "proto/ops.pro" +# include "proto/option.pro" +# include "proto/quickfix.pro" +# include "proto/regexp.pro" +# include "proto/regsub.pro" +# include "proto/screen.pro" +# include "proto/search.pro" +# include "proto/tables.pro" +# include "proto/tag.pro" +# include "proto/term.pro" +# if defined(HAVE_TGETENT) && (defined(AMIGA) || defined(VMS)) +# include "proto/termlib.pro" +# endif +# include "proto/undo.pro" +# include "proto/version.pro" +# include "proto/window.pro" + +# ifdef USE_GUI +# include "proto/gui.pro" +# ifdef USE_GUI_MOTIF +# include "proto/gui_motif.pro" +# endif +# ifdef USE_GUI_ATHENA +# include "proto/gui_athena.pro" +# endif +# if defined(USE_GUI_MOTIF) || defined(USE_GUI_ATHENA) +# include "proto/gui_x11.pro" +# endif +# if !defined(HAVE_SETENV) && !defined(HAVE_PUTENV) +extern int putenv __ARGS((char *string)); /* from pty.c */ +# endif +# endif /* USE_GUI */ +# if defined(USE_GUI) +extern int OpenPTY __ARGS((char **ttyn)); /* from pty.c */ +# endif + +#endif /* PROTO */ diff --git a/usr.bin/vim/proto/alloc.pro b/usr.bin/vim/proto/alloc.pro new file mode 100644 index 00000000000..2faf5f75f2a --- /dev/null +++ b/usr.bin/vim/proto/alloc.pro @@ -0,0 +1,17 @@ +/* $OpenBSD: alloc.pro,v 1.1.1.1 1996/09/07 21:40:30 downsj Exp $ */ +/* alloc.c */ +char_u *alloc __PARMS((unsigned size)); +char_u *alloc_check __PARMS((unsigned size)); +char_u *lalloc __PARMS((long_u size, int message)); +void do_outofmem_msg __PARMS((void)); +char_u *strsave __PARMS((char_u *string)); +char_u *strnsave __PARMS((char_u *string, int len)); +char_u *strsave_escaped __PARMS((char_u *string, char_u *esc_chars)); +void copy_spaces __PARMS((char_u *ptr, size_t count)); +void del_trailing_spaces __PARMS((char_u *ptr)); +int copy_option_part __PARMS((char_u **option, char_u *buf, int maxlen, char *sep_chars)); +void vim_free __PARMS((void *x)); +int vim_strnicmp __PARMS((char_u *s1, char_u *s2, size_t len)); +char_u *vim_strchr __PARMS((char_u *string, int n)); +char_u *vim_strrchr __PARMS((char_u *string, int n)); +int vim_isspace __PARMS((int x)); diff --git a/usr.bin/vim/proto/amiga.pro b/usr.bin/vim/proto/amiga.pro new file mode 100644 index 00000000000..21391d7f718 --- /dev/null +++ b/usr.bin/vim/proto/amiga.pro @@ -0,0 +1,38 @@ +/* $OpenBSD: amiga.pro,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* amiga.c */ +void win_resize_on __PARMS((void)); +void win_resize_off __PARMS((void)); +void mch_write __PARMS((char_u *p, int len)); +int mch_inchar __PARMS((char_u *buf, int maxlen, long time)); +int mch_char_avail __PARMS((void)); +long mch_avail_mem __PARMS((int special)); +void mch_delay __PARMS((long msec, int ignoreinput)); +void mch_suspend __PARMS((void)); +void mch_windinit __PARMS((void)); +int mch_check_win __PARMS((int argc, char **argv)); +int mch_check_input __PARMS((void)); +void fname_case __PARMS((char_u *name)); +void mch_settitle __PARMS((char_u *title, char_u *icon)); +void mch_restore_title __PARMS((int which)); +int mch_can_restore_title __PARMS((void)); +int mch_can_restore_icon __PARMS((void)); +int mch_get_user_name __PARMS((char_u *s, int len)); +void mch_get_host_name __PARMS((char_u *s, int len)); +long mch_get_pid __PARMS((void)); +int mch_dirname __PARMS((char_u *buf, int len)); +int FullName __PARMS((char_u *fname, char_u *buf, int len, int force)); +int isFullName __PARMS((char_u *fname)); +long getperm __PARMS((char_u *name)); +int setperm __PARMS((char_u *name, long perm)); +int mch_isdir __PARMS((char_u *name)); +void mch_windexit __PARMS((int r)); +void mch_settmode __PARMS((int raw)); +int mch_screenmode __PARMS((char_u *arg)); +int mch_get_winsize __PARMS((void)); +void mch_set_winsize __PARMS((void)); +int call_shell __PARMS((char_u *cmd, int options)); +void mch_breakcheck __PARMS((void)); +long Chk_Abort __PARMS((void)); +int ExpandWildCards __PARMS((int num_pat, char_u **pat, int *num_file, char_u ***file, int files_only, int list_notfound)); +int mch_has_wildcard __PARMS((char_u *p)); +char_u *vim_getenv __PARMS((char_u *var)); diff --git a/usr.bin/vim/proto/buffer.pro b/usr.bin/vim/proto/buffer.pro new file mode 100644 index 00000000000..cd2d09b75a7 --- /dev/null +++ b/usr.bin/vim/proto/buffer.pro @@ -0,0 +1,33 @@ +/* $OpenBSD: buffer.pro,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* buffer.c */ +int open_buffer __PARMS((void)); +void close_buffer __PARMS((WIN *win, BUF *buf, int free_buf, int del_buf)); +void buf_clear __PARMS((BUF *buf)); +void buf_freeall __PARMS((BUF *buf)); +char_u *do_bufdel __PARMS((int command, char_u *arg, int addr_count, int start_bnr, int end_bnr, int forceit)); +int do_buffer __PARMS((int action, int start, int dir, int count, int forceit)); +BUF *buflist_new __PARMS((char_u *fname, char_u *sfname, linenr_t lnum, int use_curbuf)); +int buflist_getfile __PARMS((int n, linenr_t lnum, int options)); +void buflist_getlnum __PARMS((void)); +BUF *buflist_findname __PARMS((char_u *fname)); +int buflist_findpat __PARMS((char_u *pattern, char_u *pattern_end)); +int ExpandBufnames __PARMS((char_u *pat, int *num_file, char_u ***file, int options)); +BUF *buflist_findnr __PARMS((int nr)); +char_u *buflist_nr2name __PARMS((int n, int fullname, int helptail)); +void buflist_list __PARMS((void)); +int buflist_name_nr __PARMS((int fnum, char_u **fname, linenr_t *lnum)); +int setfname __PARMS((char_u *fname, char_u *sfname, int message)); +void setaltfname __PARMS((char_u *fname, char_u *sfname, linenr_t lnum)); +int buflist_add __PARMS((char_u *fname)); +void buflist_altlnum __PARMS((void)); +int otherfile __PARMS((char_u *fname)); +void fileinfo __PARMS((int fullname, int shorthelp, int dont_truncate)); +void cursor_pos_info __PARMS((void)); +void col_print __PARMS((char_u *buf, int col, int vcol)); +void maketitle __PARMS((void)); +void resettitle __PARMS((void)); +char_u *fix_fname __PARMS((char_u *fname)); +void fname_expand __PARMS((char_u **fname, char_u **sfname)); +void do_arg_all __PARMS((int count)); +void do_buffer_all __PARMS((int count, int all)); +void do_modelines __PARMS((void)); diff --git a/usr.bin/vim/proto/charset.pro b/usr.bin/vim/proto/charset.pro new file mode 100644 index 00000000000..5cb2d2ce492 --- /dev/null +++ b/usr.bin/vim/proto/charset.pro @@ -0,0 +1,18 @@ +/* $OpenBSD: charset.pro,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* charset.c */ +int init_chartab __PARMS((void)); +void trans_characters __PARMS((char_u *buf, int bufsize)); +char_u *transchar __PARMS((int c)); +void transchar_nonprint __PARMS((char_u *buf, int c)); +int charsize __PARMS((register int c)); +int strsize __PARMS((register char_u *s)); +int chartabsize __PARMS((register int c, colnr_t col)); +int win_chartabsize __PARMS((register WIN *wp, register int c, colnr_t col)); +int linetabsize __PARMS((char_u *s)); +int isidchar __PARMS((int c)); +int iswordchar __PARMS((int c)); +int isfilechar __PARMS((int c)); +int isprintchar __PARMS((int c)); +int lbr_chartabsize __PARMS((unsigned char *s, colnr_t col)); +int win_lbr_chartabsize __PARMS((WIN *wp, unsigned char *s, colnr_t col, int *head)); +void getvcol __PARMS((WIN *wp, FPOS *pos, colnr_t *start, colnr_t *cursor, colnr_t *end)); diff --git a/usr.bin/vim/proto/cmdcmds.pro b/usr.bin/vim/proto/cmdcmds.pro new file mode 100644 index 00000000000..91dab6bbfc2 --- /dev/null +++ b/usr.bin/vim/proto/cmdcmds.pro @@ -0,0 +1,17 @@ +/* $OpenBSD: cmdcmds.pro,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* cmdcmds.c */ +void do_ascii __PARMS((void)); +void do_align __PARMS((linenr_t start, linenr_t end, int width, int type)); +void do_retab __PARMS((linenr_t start, linenr_t end, int new_ts, int force)); +int do_move __PARMS((linenr_t line1, linenr_t line2, linenr_t n)); +void do_copy __PARMS((linenr_t line1, linenr_t line2, linenr_t n)); +void do_bang __PARMS((int addr_count, linenr_t line1, linenr_t line2, int forceit, char_u *arg, int do_in, int do_out)); +void do_shell __PARMS((char_u *cmd)); +void do_filter __PARMS((linenr_t line1, linenr_t line2, char_u *buff, int do_in, int do_out)); +int read_viminfo __PARMS((char_u *file, int want_info, int want_marks, int force)); +void write_viminfo __PARMS((char_u *file, int force)); +void viminfo_readstring __PARMS((char_u *p)); +void viminfo_writestring __PARMS((FILE *fd, char_u *p)); +void do_fixdel __PARMS((void)); +void print_line __PARMS((linenr_t lnum, int use_number)); +void do_file __PARMS((char_u *arg, int forceit)); diff --git a/usr.bin/vim/proto/cmdline.pro b/usr.bin/vim/proto/cmdline.pro new file mode 100644 index 00000000000..00980c8bb76 --- /dev/null +++ b/usr.bin/vim/proto/cmdline.pro @@ -0,0 +1,24 @@ +/* $OpenBSD: cmdline.pro,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* cmdline.c */ +void add_to_history __PARMS((int histype, char_u *new_entry)); +char_u *getcmdline __PARMS((int firstc, long count)); +int put_on_cmdline __PARMS((char_u *str, int len, int redraw)); +void alloc_cmdbuff __PARMS((int len)); +int realloc_cmdbuff __PARMS((int len)); +void redrawcmdline __PARMS((void)); +void compute_cmdrow __PARMS((void)); +int do_cmdline __PARMS((char_u *cmdline, int sourcing, int repeating)); +int autowrite __PARMS((BUF *buf)); +void autowrite_all __PARMS((void)); +int do_ecmd __PARMS((int fnum, char_u *fname, char_u *sfname, char_u *command, int hide, linenr_t newlnum, int set_help)); +void check_arg_idx __PARMS((void)); +void gotocmdline __PARMS((int clr)); +int check_fname __PARMS((void)); +int getfile __PARMS((int fnum, char_u *fname, char_u *sfname, int setpm, linenr_t lnum)); +char_u *ExpandOne __PARMS((char_u *str, char_u *orig, int options, int mode)); +char_u *addstar __PARMS((char_u *fname, int len)); +int do_source __PARMS((register char_u *fname, int check_other)); +void prepare_viminfo_history __PARMS((int len)); +int read_viminfo_history __PARMS((char_u *line, FILE *fp)); +void finish_viminfo_history __PARMS((void)); +void write_viminfo_history __PARMS((FILE *fp)); diff --git a/usr.bin/vim/proto/csearch.pro b/usr.bin/vim/proto/csearch.pro new file mode 100644 index 00000000000..591310b66da --- /dev/null +++ b/usr.bin/vim/proto/csearch.pro @@ -0,0 +1,6 @@ +/* $OpenBSD: csearch.pro,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* csearch.c */ +void do_sub __PARMS((linenr_t lp, linenr_t up, char_u *cmd, char_u **nextcommand, int use_old)); +void do_glob __PARMS((int type, linenr_t lp, linenr_t up, char_u *cmd)); +int read_viminfo_sub_string __PARMS((char_u *line, FILE *fp, int force)); +void write_viminfo_sub_string __PARMS((FILE *fp)); diff --git a/usr.bin/vim/proto/digraph.pro b/usr.bin/vim/proto/digraph.pro new file mode 100644 index 00000000000..bcb4ec2c44c --- /dev/null +++ b/usr.bin/vim/proto/digraph.pro @@ -0,0 +1,6 @@ +/* $OpenBSD: digraph.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* digraph.c */ +int do_digraph __PARMS((int c)); +int getdigraph __PARMS((int char1, int char2, int meta)); +void putdigraph __PARMS((char_u *str)); +void listdigraphs __PARMS((void)); diff --git a/usr.bin/vim/proto/edit.pro b/usr.bin/vim/proto/edit.pro new file mode 100644 index 00000000000..87da424ce99 --- /dev/null +++ b/usr.bin/vim/proto/edit.pro @@ -0,0 +1,24 @@ +/* $OpenBSD: edit.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* edit.c */ +int edit __PARMS((int initstr, int startln, long count)); +int is_ctrl_x_key __PARMS((int c)); +int add_completion_and_infercase __PARMS((char_u *str, int len, char_u *fname, int dir)); +int get_literal __PARMS((void)); +void insertchar __PARMS((unsigned c, int force_formatting, int second_indent)); +void set_last_insert __PARMS((int c)); +void beginline __PARMS((int flag)); +int oneright __PARMS((void)); +int oneleft __PARMS((void)); +int cursor_up __PARMS((long n)); +int cursor_down __PARMS((long n)); +int screengo __PARMS((int dir, long dist)); +int onepage __PARMS((int dir, long count)); +void halfpage __PARMS((int flag, linenr_t Prenum)); +int stuff_inserted __PARMS((int c, long count, int no_esc)); +char_u *get_last_insert __PARMS((void)); +void replace_push __PARMS((int c)); +int replace_pop __PARMS((void)); +void replace_flush __PARMS((void)); +void fixthisline __PARMS((int (*get_the_indent)(void))); +int in_cinkeys __PARMS((int keytyped, int when, int line_is_empty)); +int hkmap __PARMS((int c)); diff --git a/usr.bin/vim/proto/fileio.pro b/usr.bin/vim/proto/fileio.pro new file mode 100644 index 00000000000..97451d31001 --- /dev/null +++ b/usr.bin/vim/proto/fileio.pro @@ -0,0 +1,11 @@ +/* $OpenBSD: fileio.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* fileio.c */ +void filemess __PARMS((BUF *buf, char_u *name, char_u *s)); +int readfile __PARMS((char_u *fname, char_u *sfname, linenr_t from, int newfile, linenr_t lines_to_skip, linenr_t lines_to_read, int filtering)); +int buf_write __PARMS((BUF *buf, char_u *fname, char_u *sfname, linenr_t start, linenr_t end, int append, int forceit, int reset_changed, int filtering)); +char_u *modname __PARMS((char_u *fname, char_u *ext)); +char_u *buf_modname __PARMS((BUF *buf, char_u *fname, char_u *ext)); +int vim_fgets __PARMS((char_u *buf, int size, FILE *fp)); +int vim_rename __PARMS((char_u *from, char_u *to)); +void check_timestamps __PARMS((void)); +void buf_check_timestamp __PARMS((BUF *buf)); diff --git a/usr.bin/vim/proto/getchar.pro b/usr.bin/vim/proto/getchar.pro new file mode 100644 index 00000000000..1fe1ecb7c46 --- /dev/null +++ b/usr.bin/vim/proto/getchar.pro @@ -0,0 +1,32 @@ +/* $OpenBSD: getchar.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* getchar.c */ +char_u *get_recorded __PARMS((void)); +char_u *get_inserted __PARMS((void)); +int stuff_empty __PARMS((void)); +void flush_buffers __PARMS((int typeahead)); +void ResetRedobuff __PARMS((void)); +void AppendToRedobuff __PARMS((char_u *s)); +void AppendCharToRedobuff __PARMS((int c)); +void AppendNumberToRedobuff __PARMS((long n)); +void stuffReadbuff __PARMS((char_u *s)); +void stuffcharReadbuff __PARMS((int c)); +void stuffnumReadbuff __PARMS((long n)); +int start_redo __PARMS((long count, int old_redo)); +int start_redo_ins __PARMS((void)); +void set_redo_ins __PARMS((void)); +void stop_redo_ins __PARMS((void)); +int ins_typebuf __PARMS((char_u *str, int noremap, int offset, int nottyped)); +int typebuf_typed __PARMS((void)); +void del_typebuf __PARMS((int len, int offset)); +int openscript __PARMS((char_u *name)); +void updatescript __PARMS((int c)); +int vgetc __PARMS((void)); +int vpeekc __PARMS((void)); +int char_avail __PARMS((void)); +void vungetc __PARMS((int c)); +int do_map __PARMS((int maptype, char_u *keys, int mode)); +void map_clear __PARMS((int modec, int force, int abbr)); +int check_abbr __PARMS((int c, char_u *ptr, int col, int mincol)); +int makemap __PARMS((FILE *fd)); +int putescstr __PARMS((FILE *fd, char_u *str, int set)); +void check_map_keycodes __PARMS((void)); diff --git a/usr.bin/vim/proto/gui.pro b/usr.bin/vim/proto/gui.pro new file mode 100644 index 00000000000..8e8fc369298 --- /dev/null +++ b/usr.bin/vim/proto/gui.pro @@ -0,0 +1,33 @@ +/* $OpenBSD: gui.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* gui.c */ +void gui_start __PARMS((void)); +void gui_prepare __PARMS((int *argc, char **argv)); +void gui_init __PARMS((void)); +void gui_exit __PARMS((void)); +int gui_init_font __PARMS((void)); +void gui_set_cursor __PARMS((int row, int col)); +void gui_update_cursor __PARMS((void)); +void gui_resize_window __PARMS((int pixel_width, int pixel_height)); +void gui_reset_scroll_region __PARMS((void)); +void gui_start_highlight __PARMS((long_u mask)); +void gui_stop_highlight __PARMS((long_u mask)); +void gui_write __PARMS((char_u *s, int len)); +void gui_undraw_cursor __PARMS((void)); +void gui_redraw __PARMS((int x, int y, int w, int h)); +void gui_redraw_block __PARMS((int row1, int col1, int row2, int col2)); +int check_col __PARMS((int col)); +int check_row __PARMS((int row)); +void gui_send_mouse_event __PARMS((int button, int x, int y, int repeated_click, int_u modifiers)); +void gui_own_selection __PARMS((void)); +void gui_lose_selection __PARMS((void)); +void gui_copy_selection __PARMS((void)); +void gui_auto_select __PARMS((void)); +void gui_menu_cb __PARMS((GuiMenu *menu)); +int gui_get_menu_index __PARMS((GuiMenu *menu, int state)); +void gui_do_menu __PARMS((char_u *cmd, char_u *arg, int force)); +char_u *gui_set_context_in_menu_cmd __PARMS((char_u *cmd, char_u *arg, int force)); +int gui_ExpandMenuNames __PARMS((regexp *prog, int *num_file, char_u ***file)); +void gui_init_which_components __PARMS((char_u *oldval)); +int gui_do_scroll __PARMS((void)); +int gui_get_max_horiz_scroll __PARMS((void)); +int gui_do_horiz_scroll __PARMS((void)); diff --git a/usr.bin/vim/proto/gui_athena.pro b/usr.bin/vim/proto/gui_athena.pro new file mode 100644 index 00000000000..c145d60a499 --- /dev/null +++ b/usr.bin/vim/proto/gui_athena.pro @@ -0,0 +1,14 @@ +/* $OpenBSD: gui_athena.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* gui_athena.c */ +void gui_mch_create_widgets __PARMS((void)); +int gui_mch_get_winsize __PARMS((void)); +void gui_mch_set_winsize __PARMS((void)); +void gui_mch_add_menu __PARMS((GuiMenu *menu, GuiMenu *parent)); +void gui_mch_add_menu_item __PARMS((GuiMenu *menu, GuiMenu *parent)); +void gui_mch_destroy_menu __PARMS((GuiMenu *menu)); +void gui_mch_create_which_components __PARMS((void)); +void gui_mch_update_scrollbars __PARMS((int worst_update, int which_sb)); +void gui_mch_reorder_scrollbars __PARMS((int which_sb)); +void gui_mch_destroy_scrollbar __PARMS((WIN *wp)); +void gui_mch_update_horiz_scrollbar __PARMS((int value, int size, int max)); +Window gui_mch_get_wid __PARMS((void)); diff --git a/usr.bin/vim/proto/gui_motif.pro b/usr.bin/vim/proto/gui_motif.pro new file mode 100644 index 00000000000..dae62d8af1e --- /dev/null +++ b/usr.bin/vim/proto/gui_motif.pro @@ -0,0 +1,14 @@ +/* $OpenBSD: gui_motif.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* gui_motif.c */ +void gui_mch_create_widgets __PARMS((void)); +int gui_mch_get_winsize __PARMS((void)); +void gui_mch_set_winsize __PARMS((void)); +void gui_mch_add_menu __PARMS((GuiMenu *menu, GuiMenu *parent)); +void gui_mch_add_menu_item __PARMS((GuiMenu *menu, GuiMenu *parent)); +void gui_mch_destroy_menu __PARMS((GuiMenu *menu)); +void gui_mch_create_which_components __PARMS((void)); +void gui_mch_update_scrollbars __PARMS((int worst_update, int which_sb)); +void gui_mch_reorder_scrollbars __PARMS((int which_sb)); +void gui_mch_destroy_scrollbar __PARMS((WIN *wp)); +void gui_mch_update_horiz_scrollbar __PARMS((int value, int size, int max)); +Window gui_mch_get_wid __PARMS((void)); diff --git a/usr.bin/vim/proto/gui_x11.pro b/usr.bin/vim/proto/gui_x11.pro new file mode 100644 index 00000000000..17fa91e6aac --- /dev/null +++ b/usr.bin/vim/proto/gui_x11.pro @@ -0,0 +1,36 @@ +/* $OpenBSD: gui_x11.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* gui_x11.c */ +void gui_x11_timer_cb __PARMS((XtPointer timed_out, XtIntervalId *interval_id)); +void gui_x11_visibility_cb __PARMS((Widget w, XtPointer dud, XEvent *event, Boolean *bool)); +void gui_x11_expose_cb __PARMS((Widget w, XtPointer dud, XEvent *event, Boolean *bool)); +void gui_x11_resize_window_cb __PARMS((Widget w, XtPointer dud, XEvent *event, Boolean *bool)); +void gui_x11_focus_change_cb __PARMS((Widget w, XtPointer data, XEvent *event, Boolean *bool)); +void gui_x11_key_hit_cb __PARMS((Widget w, XtPointer dud, XEvent *event, Boolean *bool)); +void gui_x11_mouse_cb __PARMS((Widget w, XtPointer dud, XEvent *event, Boolean *bool)); +void gui_mch_prepare __PARMS((int *argc, char **argv)); +int gui_mch_init __PARMS((void)); +void gui_mch_exit __PARMS((void)); +void gui_x11_use_resize_callback __PARMS((Widget widget, int enabled)); +int gui_mch_init_font __PARMS((char_u *font_name)); +int gui_mch_haskey __PARMS((char_u *name)); +int gui_get_x11_windis __PARMS((Window *win, Display **dis)); +void gui_mch_beep __PARMS((void)); +void gui_mch_flash __PARMS((void)); +void gui_mch_iconify __PARMS((void)); +void gui_mch_draw_cursor __PARMS((void)); +void gui_mch_update __PARMS((void)); +int gui_mch_wait_for_chars __PARMS((int wtime)); +void gui_mch_flush __PARMS((void)); +void gui_mch_clear_block __PARMS((int row1, int col1, int row2, int col2)); +void gui_mch_outstr_nowrap __PARMS((char_u *s, int len, int wrap_cursor, int is_cursor)); +void gui_mch_delete_lines __PARMS((int row, int num_lines)); +void gui_mch_insert_lines __PARMS((int row, int num_lines)); +void gui_request_selection __PARMS((void)); +void gui_mch_lose_selection __PARMS((void)); +int gui_mch_own_selection __PARMS((void)); +void gui_x11_menu_cb __PARMS((Widget w, XtPointer client_data, XtPointer call_data)); +void gui_x11_update_menus __PARMS((int modes)); +void gui_x11_start_selection __PARMS((int button, int x, int y, int repeated_click, int_u modifiers)); +void gui_x11_process_selection __PARMS((int button, int x, int y, int repeated_click, int_u modifiers)); +void gui_x11_redraw_selection __PARMS((int x, int y, int w, int h)); +void gui_mch_clear_selection __PARMS((void)); diff --git a/usr.bin/vim/proto/help.pro b/usr.bin/vim/proto/help.pro new file mode 100644 index 00000000000..d22705c72e0 --- /dev/null +++ b/usr.bin/vim/proto/help.pro @@ -0,0 +1,5 @@ +/* $OpenBSD: help.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* help.c */ +void do_help __PARMS((char_u *arg)); +int help_heuristic __PARMS((char_u *matched_string, int offset)); +int find_help_tags __PARMS((char_u *arg, int *num_matches, char_u ***matches)); diff --git a/usr.bin/vim/proto/linefunc.pro b/usr.bin/vim/proto/linefunc.pro new file mode 100644 index 00000000000..5c4bf15010d --- /dev/null +++ b/usr.bin/vim/proto/linefunc.pro @@ -0,0 +1,10 @@ +/* $OpenBSD: linefunc.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* linefunc.c */ +int coladvance __PARMS((colnr_t wcol)); +int inc_cursor __PARMS((void)); +int inc __PARMS((register FPOS *lp)); +int incl __PARMS((register FPOS *lp)); +int dec_cursor __PARMS((void)); +int dec __PARMS((register FPOS *lp)); +int decl __PARMS((register FPOS *lp)); +void adjust_cursor __PARMS((void)); diff --git a/usr.bin/vim/proto/main.pro b/usr.bin/vim/proto/main.pro new file mode 100644 index 00000000000..be5de966e01 --- /dev/null +++ b/usr.bin/vim/proto/main.pro @@ -0,0 +1,4 @@ +/* $OpenBSD: main.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* main.c */ +void main __PARMS((int argc, char **argv)); +void getout __PARMS((int r)); diff --git a/usr.bin/vim/proto/mark.pro b/usr.bin/vim/proto/mark.pro new file mode 100644 index 00000000000..2e13b798db8 --- /dev/null +++ b/usr.bin/vim/proto/mark.pro @@ -0,0 +1,19 @@ +/* $OpenBSD: mark.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* mark.c */ +int setmark __PARMS((int c)); +void setpcmark __PARMS((void)); +void checkpcmark __PARMS((void)); +FPOS *movemark __PARMS((int count)); +FPOS *getmark __PARMS((int c, int changefile)); +void fmarks_check_names __PARMS((BUF *buf)); +int check_mark __PARMS((FPOS *pos)); +void clrallmarks __PARMS((BUF *buf)); +char_u *fm_getname __PARMS((struct filemark *fmark)); +void do_marks __PARMS((char_u *arg)); +void do_jumps __PARMS((void)); +void mark_adjust __PARMS((linenr_t line1, linenr_t line2, long amount, long amount_after)); +void set_last_cursor __PARMS((WIN *win)); +int read_viminfo_filemark __PARMS((char_u *line, FILE *fp, int force)); +void write_viminfo_filemarks __PARMS((FILE *fp)); +int write_viminfo_marks __PARMS((FILE *fp_out)); +void copy_viminfo_marks __PARMS((char_u *line, FILE *fp_in, FILE *fp_out, int count, int eof)); diff --git a/usr.bin/vim/proto/memfile.pro b/usr.bin/vim/proto/memfile.pro new file mode 100644 index 00000000000..024f782e98b --- /dev/null +++ b/usr.bin/vim/proto/memfile.pro @@ -0,0 +1,16 @@ +/* $OpenBSD: memfile.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* memfile.c */ +MEMFILE *mf_open __PARMS((char_u *fname, int trunc_file)); +int mf_open_file __PARMS((MEMFILE *mfp, char_u *fname)); +void mf_close __PARMS((MEMFILE *mfp, int del_file)); +BHDR *mf_new __PARMS((MEMFILE *mfp, int negative, int page_count)); +BHDR *mf_get __PARMS((MEMFILE *mfp, blocknr_t nr, int page_count)); +void mf_put __PARMS((MEMFILE *mfp, BHDR *hp, int dirty, int infile)); +void mf_free __PARMS((MEMFILE *mfp, BHDR *hp)); +int mf_sync __PARMS((MEMFILE *mfp, int all, int check_char, int do_fsync)); +int mf_release_all __PARMS((void)); +blocknr_t mf_trans_del __PARMS((MEMFILE *mfp, blocknr_t old_nr)); +void mf_set_xfname __PARMS((MEMFILE *mfp)); +void mf_fullname __PARMS((MEMFILE *mfp)); +int mf_need_trans __PARMS((MEMFILE *mfp)); +void mf_statistics __PARMS((void)); diff --git a/usr.bin/vim/proto/memline.pro b/usr.bin/vim/proto/memline.pro new file mode 100644 index 00000000000..8b92905bf96 --- /dev/null +++ b/usr.bin/vim/proto/memline.pro @@ -0,0 +1,26 @@ +/* $OpenBSD: memline.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* memline.c */ +int ml_open __PARMS((void)); +void ml_setname __PARMS((void)); +void ml_open_files __PARMS((void)); +void ml_close __PARMS((BUF *buf, int del_file)); +void ml_close_all __PARMS((int del_file)); +void ml_close_notmod __PARMS((void)); +void ml_timestamp __PARMS((BUF *buf)); +void ml_recover __PARMS((void)); +int recover_names __PARMS((char_u **fname, int list, int nr)); +void ml_sync_all __PARMS((int check_file, int check_char)); +void ml_preserve __PARMS((BUF *buf, int message)); +char_u *ml_get __PARMS((linenr_t lnum)); +char_u *ml_get_pos __PARMS((FPOS *pos)); +char_u *ml_get_curline __PARMS((void)); +char_u *ml_get_cursor __PARMS((void)); +char_u *ml_get_buf __PARMS((BUF *buf, linenr_t lnum, int will_change)); +int ml_line_alloced __PARMS((void)); +int ml_append __PARMS((linenr_t lnum, char_u *line, colnr_t len, int newfile)); +int ml_replace __PARMS((linenr_t lnum, char_u *line, int copy)); +int ml_delete __PARMS((linenr_t lnum, int message)); +void ml_setmarked __PARMS((linenr_t lnum)); +linenr_t ml_firstmarked __PARMS((void)); +int ml_has_mark __PARMS((linenr_t lnum)); +void ml_clearmarked __PARMS((void)); diff --git a/usr.bin/vim/proto/message.pro b/usr.bin/vim/proto/message.pro new file mode 100644 index 00000000000..42f533f2534 --- /dev/null +++ b/usr.bin/vim/proto/message.pro @@ -0,0 +1,22 @@ +/* $OpenBSD: message.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* message.c */ +int msg __PARMS((char_u *s)); +int emsg __PARMS((char_u *s)); +int emsg2 __PARMS((char_u *s, char_u *a1)); +int emsgn __PARMS((char_u *s, long n)); +int msg_trunc __PARMS((char_u *s)); +void wait_return __PARMS((int redraw)); +void msg_start __PARMS((void)); +void msg_pos __PARMS((int row, int col)); +void msg_outchar __PARMS((int c)); +void msg_outnum __PARMS((long n)); +void msg_home_replace __PARMS((char_u *fname)); +int msg_outtrans __PARMS((register char_u *str)); +int msg_outtrans_len __PARMS((register char_u *str, register int len)); +int msg_outtrans_special __PARMS((register char_u *str, register int all)); +void msg_prt_line __PARMS((char_u *s)); +void msg_outstr __PARMS((char_u *s)); +void msg_moremsg __PARMS((int full)); +void msg_clr_eos __PARMS((void)); +int msg_end __PARMS((void)); +int msg_check __PARMS((void)); diff --git a/usr.bin/vim/proto/misccmds.pro b/usr.bin/vim/proto/misccmds.pro new file mode 100644 index 00000000000..dd98071b4b5 --- /dev/null +++ b/usr.bin/vim/proto/misccmds.pro @@ -0,0 +1,53 @@ +/* $OpenBSD: misccmds.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* misccmds.c */ +int get_indent __PARMS((void)); +int get_indent_lnum __PARMS((linenr_t lnum)); +void set_indent __PARMS((register int size, int del_first)); +int Opencmd __PARMS((int dir, int redraw, int del_spaces)); +int get_leader_len __PARMS((char_u *line, char_u **flags)); +int plines __PARMS((linenr_t p)); +int plines_win __PARMS((WIN *wp, linenr_t p)); +int plines_m __PARMS((linenr_t first, linenr_t last)); +int plines_m_win __PARMS((WIN *wp, linenr_t first, linenr_t last)); +void ins_char __PARMS((int c)); +void ins_str __PARMS((char_u *s)); +int delchar __PARMS((int fixpos)); +int truncate_line __PARMS((int fixpos)); +void dellines __PARMS((long nlines, int dowindow, int undo)); +int gchar __PARMS((FPOS *pos)); +int gchar_cursor __PARMS((void)); +void pchar_cursor __PARMS((int c)); +void goto_endofbuf __PARMS((FPOS *pos)); +int inindent __PARMS((int extra)); +char_u *skipwhite __PARMS((register char_u *p)); +char_u *skipdigits __PARMS((register char_u *p)); +char_u *skiptowhite __PARMS((register char_u *p)); +char_u *skiptowhite_esc __PARMS((register char_u *p)); +long getdigits __PARMS((char_u **pp)); +char_u *skip_to_option_part __PARMS((char_u *p)); +char *plural __PARMS((long n)); +void set_Changed __PARMS((void)); +void unset_Changed __PARMS((BUF *buf)); +void change_warning __PARMS((void)); +int ask_yesno __PARMS((char_u *str, int direct)); +int get_number __PARMS((void)); +void msgmore __PARMS((long n)); +void beep_flush __PARMS((void)); +void vim_beep __PARMS((void)); +void init_homedir __PARMS((void)); +void expand_env __PARMS((char_u *src, char_u *dst, int dstlen)); +void home_replace __PARMS((BUF *buf, char_u *src, char_u *dst, int dstlen)); +char_u *home_replace_save __PARMS((BUF *buf, char_u *src)); +int fullpathcmp __PARMS((char_u *s1, char_u *s2)); +char_u *gettail __PARMS((char_u *fname)); +int ispathsep __PARMS((int c)); +char_u *concat_fnames __PARMS((char_u *fname1, char_u *fname2, int sep)); +char_u *FullName_save __PARMS((char_u *fname)); +int islabel __PARMS((int ind_maxcomment)); +int iscase __PARMS((char_u *s)); +int get_c_indent __PARMS((void)); +int get_lisp_indent __PARMS((void)); +void preserve_exit __PARMS((void)); +int vim_fexists __PARMS((char_u *fname)); +void line_breakcheck __PARMS((void)); +void FreeWild __PARMS((int num, char_u **file)); diff --git a/usr.bin/vim/proto/msdos.pro b/usr.bin/vim/proto/msdos.pro new file mode 100644 index 00000000000..38a9f7262e6 --- /dev/null +++ b/usr.bin/vim/proto/msdos.pro @@ -0,0 +1,40 @@ +/* $OpenBSD: msdos.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* msdos.c */ +long mch_avail_mem __PARMS((int special)); +void mch_delay __PARMS((long msec, int ignoreinput)); +int vim_remove __PARMS((char_u *name)); +void mch_write __PARMS((char_u *s, int len)); +int mch_inchar __PARMS((char_u *buf, int maxlen, long time)); +int mch_char_avail __PARMS((void)); +void mch_suspend __PARMS((void)); +void mch_windinit __PARMS((void)); +int mch_check_win __PARMS((int argc, char **argv)); +int mch_check_input __PARMS((void)); +void mch_settitle __PARMS((char_u *title, char_u *icon)); +void mch_restore_title __PARMS((int which)); +int mch_can_restore_title __PARMS((void)); +int mch_can_restore_icon __PARMS((void)); +int mch_get_user_name __PARMS((char_u *s, int len)); +void mch_get_host_name __PARMS((char_u *s, int len)); +long mch_get_pid __PARMS((void)); +int mch_dirname __PARMS((char_u *buf, int len)); +int FullName __PARMS((char_u *fname, char_u *buf, int len, int force)); +int isFullName __PARMS((char_u *fname)); +long getperm __PARMS((char_u *name)); +int setperm __PARMS((char_u *name, long perm)); +int mch_isdir __PARMS((char_u *name)); +void mch_windexit __PARMS((int r)); +void interrupt catch_cbrk __PARMS((void)); +void interrupt catch_cint __PARMS((unsigned bp, unsigned di, unsigned si, unsigned ds, unsigned es, unsigned dx, unsigned cx, unsigned bx, unsigned ax)); +void mch_settmode __PARMS((int raw)); +void mch_setmouse __PARMS((int on)); +int mch_screenmode __PARMS((char_u *arg)); +int mch_get_winsize __PARMS((void)); +void set_window __PARMS((void)); +void mch_set_winsize __PARMS((void)); +int call_shell __PARMS((char_u *cmd, int options)); +void mch_breakcheck __PARMS((void)); +int mch_has_wildcard __PARMS((char_u *s)); +int ExpandWildCards __PARMS((int num_pat, char_u **pat, int *num_file, char_u ***file, int files_only, int list_notfound)); +int vim_chdir __PARMS((char *path)); +char_u *vim_getenv __PARMS((char_u *var)); diff --git a/usr.bin/vim/proto/normal.pro b/usr.bin/vim/proto/normal.pro new file mode 100644 index 00000000000..6e45d0f169b --- /dev/null +++ b/usr.bin/vim/proto/normal.pro @@ -0,0 +1,12 @@ +/* $OpenBSD: normal.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* normal.c */ +void normal __PARMS((void)); +void do_pending_operator __PARMS((register int c, int nchar, int finish_op, char_u *searchbuff, int *command_busy, int old_col, int gui_yank, int dont_adjust_op_end)); +int do_mouse __PARMS((int c, int dir, long count, int fix_indent)); +void start_visual_highlight __PARMS((void)); +void end_visual_mode __PARMS((void)); +int find_ident_under_cursor __PARMS((char_u **string, int find_type)); +void clear_showcmd __PARMS((void)); +int add_to_showcmd __PARMS((int c, int display_always)); +void push_showcmd __PARMS((void)); +void pop_showcmd __PARMS((void)); diff --git a/usr.bin/vim/proto/ops.pro b/usr.bin/vim/proto/ops.pro new file mode 100644 index 00000000000..c836f2e8d8f --- /dev/null +++ b/usr.bin/vim/proto/ops.pro @@ -0,0 +1,31 @@ +/* $OpenBSD: ops.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* ops.c */ +void do_shift __PARMS((int op, int curs_top, int amount)); +void shift_line __PARMS((int left, int round, int amount)); +void do_reindent __PARMS((int (*how)(void))); +int is_yank_buffer __PARMS((int c, int writing)); +int yank_buffer_mline __PARMS((void)); +int do_record __PARMS((int c)); +int do_execbuf __PARMS((int c, int colon, int addcr)); +int insertbuf __PARMS((int c)); +int cmdline_paste __PARMS((int c)); +void do_delete __PARMS((void)); +void do_tilde __PARMS((void)); +void swapchar __PARMS((FPOS *pos)); +int do_change __PARMS((void)); +void init_yank __PARMS((void)); +int do_yank __PARMS((int deleting, int mess)); +void do_put __PARMS((int dir, long count, int fix_indent)); +int get_register_name __PARMS((int num)); +void do_dis __PARMS((char_u *arg)); +void dis_msg __PARMS((char_u *p, int skip_esc)); +void do_do_join __PARMS((long count, int insert_space, int redraw)); +int do_join __PARMS((int insert_space, int redraw)); +void do_format __PARMS((void)); +int do_addsub __PARMS((int command, linenr_t Prenum1)); +int read_viminfo_register __PARMS((char_u *line, FILE *fp, int force)); +void write_viminfo_registers __PARMS((FILE *fp)); +void gui_free_selection __PARMS((void)); +void gui_get_selection __PARMS((void)); +void gui_yank_selection __PARMS((int type, char_u *str, long_u len)); +int gui_convert_selection __PARMS((char_u **str, long_u *len)); diff --git a/usr.bin/vim/proto/option.pro b/usr.bin/vim/proto/option.pro new file mode 100644 index 00000000000..d7972ef06f8 --- /dev/null +++ b/usr.bin/vim/proto/option.pro @@ -0,0 +1,30 @@ +/* $OpenBSD: option.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* option.c */ +void set_init_1 __PARMS((void)); +void set_init_2 __PARMS((void)); +void set_init_3 __PARMS((void)); +int do_set __PARMS((char_u *arg)); +void set_options_bin __PARMS((int oldval, int newval)); +int get_viminfo_parameter __PARMS((int type)); +void check_options __PARMS((void)); +void check_buf_options __PARMS((BUF *buf)); +void free_string_option __PARMS((char_u *p)); +void set_string_option __PARMS((char_u *name, int opt_idx, char_u *val, int dofree)); +char_u *get_highlight_default __PARMS((void)); +int makeset __PARMS((FILE *fd)); +void clear_termoptions __PARMS((void)); +void set_term_defaults __PARMS((void)); +void comp_col __PARMS((void)); +void win_copy_options __PARMS((WIN *wp_from, WIN *wp_to)); +void buf_copy_options __PARMS((BUF *bp_from, BUF *bp_to, int entering)); +void set_context_in_set_cmd __PARMS((char_u *arg)); +int ExpandSettings __PARMS((regexp *prog, int *num_file, char_u ***file)); +int ExpandOldSetting __PARMS((int *num_file, char_u ***file)); +char_u *file_pat_to_reg_pat __PARMS((char_u *pat, char_u *pat_end, int *allow_directories)); +void do_autocmd __PARMS((char_u *arg, int force)); +void do_doautocmd __PARMS((char_u *arg)); +int apply_autocmds __PARMS((int event, char_u *fname, char_u *fname_io)); +char_u *set_context_in_autocmd __PARMS((char_u *arg, int doautocmd)); +int ExpandEvents __PARMS((regexp *prog, int *num_file, char_u ***file)); +int has_format_option __PARMS((int x)); +int shortmess __PARMS((int x)); diff --git a/usr.bin/vim/proto/quickfix.pro b/usr.bin/vim/proto/quickfix.pro new file mode 100644 index 00000000000..cd3eee7e821 --- /dev/null +++ b/usr.bin/vim/proto/quickfix.pro @@ -0,0 +1,6 @@ +/* $OpenBSD: quickfix.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* quickfix.c */ +int qf_init __PARMS((void)); +void qf_jump __PARMS((int dir, int errornr)); +void qf_list __PARMS((int all)); +void qf_mark_adjust __PARMS((linenr_t line1, linenr_t line2, long amount, long amount_after)); diff --git a/usr.bin/vim/proto/regexp.pro b/usr.bin/vim/proto/regexp.pro new file mode 100644 index 00000000000..c1fadd44035 --- /dev/null +++ b/usr.bin/vim/proto/regexp.pro @@ -0,0 +1,6 @@ +/* $OpenBSD: regexp.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* regexp.c */ +char_u *skip_regexp __PARMS((char_u *p, int dirc)); +regexp *vim_regcomp __PARMS((char_u *exp)); +int vim_regexec __PARMS((register regexp *prog, register char_u *string, int at_bol)); +int cstrncmp __PARMS((char_u *s1, char_u *s2, int n)); diff --git a/usr.bin/vim/proto/regsub.pro b/usr.bin/vim/proto/regsub.pro new file mode 100644 index 00000000000..0868b69409f --- /dev/null +++ b/usr.bin/vim/proto/regsub.pro @@ -0,0 +1,4 @@ +/* $OpenBSD: regsub.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* regsub.c */ +char_u *regtilde __PARMS((char_u *source, int magic)); +int vim_regsub __PARMS((regexp *prog, char_u *source, char_u *dest, int copy, int magic)); diff --git a/usr.bin/vim/proto/screen.pro b/usr.bin/vim/proto/screen.pro new file mode 100644 index 00000000000..03b64c1e66a --- /dev/null +++ b/usr.bin/vim/proto/screen.pro @@ -0,0 +1,47 @@ +/* $OpenBSD: screen.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* screen.c */ +void updateline __PARMS((void)); +void update_curbuf __PARMS((int type)); +void updateScreen __PARMS((int type)); +void updateWindow __PARMS((WIN *wp)); +void win_update __PARMS((WIN *wp)); +void status_redraw_all __PARMS((void)); +void win_redr_status __PARMS((WIN *wp)); +void display_dollar __PARMS((colnr_t col)); +void undisplay_dollar __PARMS((void)); +void screen_outchar __PARMS((int c, int row, int col)); +void screen_msg __PARMS((char_u *text, int row, int col)); +void screen_start __PARMS((void)); +int set_highlight __PARMS((int context)); +void start_highlight __PARMS((void)); +void stop_highlight __PARMS((void)); +void remember_highlight __PARMS((void)); +void recover_old_highlight __PARMS((void)); +void screen_fill __PARMS((int start_row, int end_row, int start_col, int end_col, int c1, int c2)); +void comp_Botline_all __PARMS((void)); +void comp_Botline __PARMS((WIN *wp)); +void screenalloc __PARMS((int clear)); +void screenclear __PARMS((void)); +void check_cursor __PARMS((void)); +void cursupdate __PARMS((void)); +void scroll_cursor_top __PARMS((int min_scroll, int always)); +void scroll_cursor_bot __PARMS((int min_scroll, int set_topline)); +void scroll_cursor_halfway __PARMS((int atend)); +void cursor_correct __PARMS((void)); +int curs_rows __PARMS((void)); +void curs_columns __PARMS((int scroll)); +void scrolldown __PARMS((long line_count)); +void scrollup __PARMS((long line_count)); +void scrolldown_clamp __PARMS((void)); +void scrollup_clamp __PARMS((void)); +int win_ins_lines __PARMS((WIN *wp, int row, int line_count, int invalid, int mayclear)); +int win_del_lines __PARMS((WIN *wp, int row, int line_count, int invalid, int mayclear)); +void win_rest_invalid __PARMS((WIN *wp)); +int screen_del_lines __PARMS((int off, int row, int line_count, int end, int force)); +void showmode __PARMS((void)); +void delmode __PARMS((void)); +void showruler __PARMS((int always)); +void win_redr_ruler __PARMS((WIN *wp, int always)); +int screen_valid __PARMS((int clear)); +int jump_to_mouse __PARMS((int flags)); +void redraw_later __PARMS((int type)); diff --git a/usr.bin/vim/proto/search.pro b/usr.bin/vim/proto/search.pro new file mode 100644 index 00000000000..0c3a7bfecc6 --- /dev/null +++ b/usr.bin/vim/proto/search.pro @@ -0,0 +1,26 @@ +/* $OpenBSD: search.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* search.c */ +regexp *myregcomp __PARMS((char_u *pat, int sub_cmd, int which_pat, int options)); +void set_reg_ic __PARMS((char_u *pat)); +int searchit __PARMS((FPOS *pos, int dir, char_u *str, long count, int options, int which_pat)); +int do_search __PARMS((int dirc, char_u *str, long count, int options)); +int search_for_exact_line __PARMS((FPOS *pos, int dir, char_u *pat)); +int searchc __PARMS((int c, register int dir, int type, long count)); +FPOS *findmatch __PARMS((int initc)); +FPOS *findmatchlimit __PARMS((int initc, int flags, int maxtravel)); +void showmatch __PARMS((void)); +int findsent __PARMS((int dir, long count)); +int findpar __PARMS((register int dir, long count, int what, int both)); +int startPS __PARMS((linenr_t lnum, int para, int both)); +int fwd_word __PARMS((long count, int type, int eol)); +int bck_word __PARMS((long count, int type, int stop)); +int end_word __PARMS((long count, int type, int stop, int empty)); +int bckend_word __PARMS((long count, int type, int eol)); +int current_word __PARMS((long count, int type)); +int current_sent __PARMS((long count)); +int current_block __PARMS((int what, long count)); +int current_par __PARMS((int type, long count)); +int linewhite __PARMS((linenr_t lnum)); +void find_pattern_in_path __PARMS((char_u *ptr, int len, int whole, int skip_comments, int type, long count, int action, linenr_t start_lnum, linenr_t end_lnum)); +int read_viminfo_search_pattern __PARMS((char_u *line, FILE *fp, int force)); +void write_viminfo_search_pattern __PARMS((FILE *fp)); diff --git a/usr.bin/vim/proto/tables.pro b/usr.bin/vim/proto/tables.pro new file mode 100644 index 00000000000..ddf82949386 --- /dev/null +++ b/usr.bin/vim/proto/tables.pro @@ -0,0 +1,11 @@ +/* $OpenBSD: tables.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* tables.c */ +int name_to_mod_mask __PARMS((int c)); +int check_shifted_spec_key __PARMS((int c)); +int unshift_special_key __PARMS((char_u *p)); +char_u *get_special_key_name __PARMS((int c, int modifiers)); +int find_special_key_in_table __PARMS((int c)); +int get_special_key_code __PARMS((char_u *name)); +char_u *get_key_name __PARMS((int i)); +int get_mouse_button __PARMS((int code, int *is_click, int *is_drag)); +int get_pseudo_mouse_code __PARMS((int button, int is_click, int is_drag)); diff --git a/usr.bin/vim/proto/tag.pro b/usr.bin/vim/proto/tag.pro new file mode 100644 index 00000000000..aec136450c9 --- /dev/null +++ b/usr.bin/vim/proto/tag.pro @@ -0,0 +1,5 @@ +/* $OpenBSD: tag.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* tag.c */ +void do_tag __PARMS((char_u *tag, int type, int count)); +void do_tags __PARMS((void)); +int find_tags __PARMS((char_u *tag, regexp *prog, int *num_file, char_u ***file, int help_only)); diff --git a/usr.bin/vim/proto/term.pro b/usr.bin/vim/proto/term.pro new file mode 100644 index 00000000000..d6217e3d5a7 --- /dev/null +++ b/usr.bin/vim/proto/term.pro @@ -0,0 +1,38 @@ +/* $OpenBSD: term.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* term.c */ +int set_termname __PARMS((char_u *term)); +void getlinecol __PARMS((void)); +int add_termcap_entry __PARMS((char_u *name, int force)); +void termcapinit __PARMS((char_u *term)); +void flushbuf __PARMS((void)); +void trash_output_buf __PARMS((void)); +void outchar __PARMS((unsigned c)); +void outstrn __PARMS((char_u *s)); +void outstr __PARMS((register char_u *s)); +void windgoto __PARMS((int row, int col)); +void setcursor __PARMS((void)); +void ttest __PARMS((int pairs)); +void add_long_to_buf __PARMS((long_u val, char_u *dst)); +int get_long_from_buf __PARMS((char_u *buf, long_u *val)); +void outnum __PARMS((register long n)); +void check_winsize __PARMS((void)); +void set_winsize __PARMS((int width, int height, int mustset)); +void settmode __PARMS((int raw)); +void starttermcap __PARMS((void)); +void stoptermcap __PARMS((void)); +void setmouse __PARMS((void)); +int mouse_has __PARMS((int c)); +void scroll_start __PARMS((void)); +void cursor_on __PARMS((void)); +void cursor_off __PARMS((void)); +void scroll_region_set __PARMS((WIN *wp, int off)); +void scroll_region_reset __PARMS((void)); +void clear_termcodes __PARMS((void)); +void add_termcode __PARMS((char_u *name, char_u *string)); +char_u *find_termcode __PARMS((char_u *name)); +char_u *get_termcode __PARMS((int i)); +void del_termcode __PARMS((char_u *name)); +int check_termcode __PARMS((int max_offset)); +char_u *replace_termcodes __PARMS((char_u *from, char_u **bufp, int from_part)); +void show_termcodes __PARMS((void)); +int show_one_termcode __PARMS((char_u *name, char_u *code, int printit)); diff --git a/usr.bin/vim/proto/termlib.pro b/usr.bin/vim/proto/termlib.pro new file mode 100644 index 00000000000..c589c93c821 --- /dev/null +++ b/usr.bin/vim/proto/termlib.pro @@ -0,0 +1,8 @@ +/* $OpenBSD: termlib.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* termlib.c */ +int tgetent __PARMS((char *tbuf, char *term)); +int tgetflag __PARMS((char *id)); +int tgetnum __PARMS((char *id)); +char *tgetstr __PARMS((char *id, char **buf)); +char *tgoto __PARMS((char *cm, int col, int line)); +int tputs __PARMS((char *cp, int affcnt, void (*outc)(unsigned int))); diff --git a/usr.bin/vim/proto/undo.pro b/usr.bin/vim/proto/undo.pro new file mode 100644 index 00000000000..387f5731c7e --- /dev/null +++ b/usr.bin/vim/proto/undo.pro @@ -0,0 +1,16 @@ +/* $OpenBSD: undo.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* undo.c */ +int u_save_cursor __PARMS((void)); +int u_save __PARMS((linenr_t top, linenr_t bot)); +int u_savesub __PARMS((linenr_t lnum)); +int u_inssub __PARMS((linenr_t lnum)); +int u_savedel __PARMS((linenr_t lnum, long nlines)); +void u_undo __PARMS((int count)); +void u_redo __PARMS((int count)); +void u_sync __PARMS((void)); +void u_unchanged __PARMS((BUF *buf)); +void u_clearall __PARMS((BUF *buf)); +void u_saveline __PARMS((linenr_t lnum)); +void u_clearline __PARMS((void)); +void u_undoline __PARMS((void)); +void u_blockfree __PARMS((BUF *buf)); diff --git a/usr.bin/vim/proto/unix.pro b/usr.bin/vim/proto/unix.pro new file mode 100644 index 00000000000..cd3560d9198 --- /dev/null +++ b/usr.bin/vim/proto/unix.pro @@ -0,0 +1,44 @@ +/* $OpenBSD: unix.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* unix.c */ +void mch_write __PARMS((char_u *s, int len)); +int mch_inchar __PARMS((char_u *buf, int maxlen, long wtime)); +int mch_char_avail __PARMS((void)); +long mch_avail_mem __PARMS((int special)); +void mch_delay __PARMS((long msec, int ignoreinput)); +void mch_resize __PARMS((void)); +void mch_suspend __PARMS((void)); +void mch_windinit __PARMS((void)); +void reset_signals __PARMS((void)); +int mch_check_win __PARMS((int argc, char **argv)); +int mch_check_input __PARMS((void)); +int mch_can_restore_title __PARMS((void)); +int mch_can_restore_icon __PARMS((void)); +void mch_settitle __PARMS((char_u *title, char_u *icon)); +int is_xterm __PARMS((char_u *name)); +int is_iris_ansi __PARMS((char_u *name)); +int is_fastterm __PARMS((char_u *name)); +void mch_restore_title __PARMS((int which)); +int mch_get_user_name __PARMS((char_u *s, int len)); +void mch_get_host_name __PARMS((char_u *s, int len)); +long mch_get_pid __PARMS((void)); +int mch_dirname __PARMS((char_u *buf, int len)); +int FullName __PARMS((char_u *fname, char_u *buf, int len, int force)); +int isFullName __PARMS((char_u *fname)); +long getperm __PARMS((char_u *name)); +int setperm __PARMS((char_u *name, int perm)); +int mch_isdir __PARMS((char_u *name)); +void mch_windexit __PARMS((int r)); +void mch_settmode __PARMS((int raw)); +void get_stty __PARMS((void)); +void mch_setmouse __PARMS((int on)); +int mch_screenmode __PARMS((char_u *arg)); +int mch_get_winsize __PARMS((void)); +void mch_set_winsize __PARMS((void)); +int call_shell __PARMS((char_u *cmd, int options)); +int is_input_buf_full __PARMS((void)); +int is_input_buf_empty __PARMS((void)); +void add_to_input_buf __PARMS((char_u *s, int len)); +void trash_input_buf __PARMS((void)); +void mch_breakcheck __PARMS((void)); +int ExpandWildCards __PARMS((int num_pat, char_u **pat, int *num_file, char_u ***file, int files_only, int list_notfound)); +int mch_has_wildcard __PARMS((char_u *p)); diff --git a/usr.bin/vim/proto/version.pro b/usr.bin/vim/proto/version.pro new file mode 100644 index 00000000000..84957111d11 --- /dev/null +++ b/usr.bin/vim/proto/version.pro @@ -0,0 +1,3 @@ +/* $OpenBSD: version.pro,v 1.1.1.1 1996/09/07 21:40:29 downsj Exp $ */ +/* version.c */ +void do_version __PARMS((char_u *arg)); diff --git a/usr.bin/vim/proto/win32.pro b/usr.bin/vim/proto/win32.pro new file mode 100644 index 00000000000..a54eaaea505 --- /dev/null +++ b/usr.bin/vim/proto/win32.pro @@ -0,0 +1,42 @@ +/* $OpenBSD: win32.pro,v 1.1.1.1 1996/09/07 21:40:30 downsj Exp $ */ +/* win32.c */ +void mch_setmouse __PARMS((int on)); +int mch_inchar __PARMS((char_u *buf, int maxlen, long time)); +void mch_windinit __PARMS((void)); +void mch_windexit __PARMS((int r)); +int mch_check_win __PARMS((int argc, char **argv)); +int mch_check_input __PARMS((void)); +void fname_case __PARMS((char_u *name)); +void mch_settitle __PARMS((char_u *title, char_u *icon)); +void mch_restore_title __PARMS((int which)); +int mch_can_restore_title __PARMS((void)); +int mch_can_restore_icon __PARMS((void)); +int mch_get_user_name __PARMS((char_u *s, int len)); +void mch_get_host_name __PARMS((char_u *s, int len)); +long mch_get_pid __PARMS((void)); +int mch_dirname __PARMS((char_u *buf, int len)); +int FullName __PARMS((char_u *fname, char_u *buf, int len, int force)); +int isFullName __PARMS((char_u *fname)); +long getperm __PARMS((char_u *name)); +int setperm __PARMS((char_u *name, long perm)); +int mch_isdir __PARMS((char_u *name)); +void mch_settmode __PARMS((int raw)); +int mch_get_winsize __PARMS((void)); +void mch_set_winsize __PARMS((void)); +void mch_suspend __PARMS((void)); +int call_shell __PARMS((char_u *cmd, int options)); +int mch_has_wildcard __PARMS((char_u *s)); +int ExpandWildCards __PARMS((int num_pat, char_u **pat, int *num_file, char_u ***file, int files_only, int list_notfound)); +int vim_chdir __PARMS((char *path)); +int can_end_termcap_mode __PARMS((int give_msg)); +void mch_write __PARMS((char_u *s, int len)); +void mch_delay __PARMS((long msec, int ignoreinput)); +int vim_remove __PARMS((char_u *name)); +void mch_breakcheck __PARMS((void)); +long mch_avail_mem __PARMS((int special)); +int mch_char_avail __PARMS((void)); +int mch_screenmode __PARMS((char_u *arg)); +int win95rename __PARMS((const char *pszOldFile, const char *pszNewFile)); +char_u *vim_getenv __PARMS((char_u *var)); +char *default_shell __PARMS((void)); +void DumpPutS __PARMS((const char *psz)); diff --git a/usr.bin/vim/proto/window.pro b/usr.bin/vim/proto/window.pro new file mode 100644 index 00000000000..7207eff2f94 --- /dev/null +++ b/usr.bin/vim/proto/window.pro @@ -0,0 +1,26 @@ +/* $OpenBSD: window.pro,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* window.c */ +void do_window __PARMS((int nchar, long Prenum)); +int win_split __PARMS((int new_height, int redraw)); +int win_count __PARMS((void)); +int make_windows __PARMS((int count)); +void win_equal __PARMS((WIN *next_curwin, int redraw)); +void close_windows __PARMS((BUF *buf)); +void close_window __PARMS((WIN *win, int free_buf)); +void close_others __PARMS((int message)); +void win_init __PARMS((WIN *wp)); +void win_enter __PARMS((WIN *wp, int undo_sync)); +WIN *win_alloc __PARMS((WIN *after)); +void win_free __PARMS((WIN *wp)); +int win_alloc_lsize __PARMS((WIN *wp)); +void win_free_lsize __PARMS((WIN *wp)); +void screen_new_rows __PARMS((void)); +void win_setheight __PARMS((int height)); +void win_drag_status_line __PARMS((int offset)); +void win_comp_scroll __PARMS((WIN *wp)); +void command_height __PARMS((void)); +void last_status __PARMS((void)); +char_u *file_name_at_cursor __PARMS((int options)); +char_u *get_file_name_in_path __PARMS((char_u *ptr, int col, int options)); +int min_rows __PARMS((void)); +int only_one_window __PARMS((void)); diff --git a/usr.bin/vim/pty_openbsd.c b/usr.bin/vim/pty_openbsd.c new file mode 100644 index 00000000000..5138ecd529f --- /dev/null +++ b/usr.bin/vim/pty_openbsd.c @@ -0,0 +1,23 @@ +/* $OpenBSD: pty_openbsd.c,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ + +/* + * A quick, OpenBSD specific pty.c replacement. It's not even entirely + * correct; but it's certainly not GPL'd. + */ + +#include +#include + +int OpenPTY(name) + char **name; +{ + static char ttyname[64]; + int mfd, sfd, error; + + error = openpty(&mfd, &sfd, ttyname, NULL, NULL); + if (error < 0) + return (-1); + + *name = ttyname; + return (mfd); +} diff --git a/usr.bin/vim/quickfix.c b/usr.bin/vim/quickfix.c new file mode 100644 index 00000000000..306a499aa64 --- /dev/null +++ b/usr.bin/vim/quickfix.c @@ -0,0 +1,636 @@ +/* $OpenBSD: quickfix.c,v 1.1.1.1 1996/09/07 21:40:25 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * quickfix.c: functions for quickfix mode, using a file with error messages + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +static void qf_free __ARGS((void)); +static char_u *qf_types __ARGS((int, int)); + +/* + * for each error the next struct is allocated and linked in a list + */ +struct qf_line +{ + struct qf_line *qf_next; /* pointer to next error in the list */ + struct qf_line *qf_prev; /* pointer to previous error in the list */ + linenr_t qf_lnum; /* line number where the error occurred */ + int qf_fnum; /* file number for the line */ + int qf_col; /* column where the error occurred */ + int qf_nr; /* error number */ + char_u *qf_text; /* description of the error */ + char_u qf_cleared;/* set to TRUE if line has been deleted */ + char_u qf_type; /* type of the error (mostly 'E') */ + char_u qf_valid; /* valid error message detected */ +}; + +static struct qf_line *qf_start; /* pointer to the first error */ +static struct qf_line *qf_ptr; /* pointer to the current error */ + +static int qf_count = 0; /* number of errors (0 means no error list) */ +static int qf_index; /* current index in the error list */ +static int qf_nonevalid; /* set to TRUE if not a single valid entry found */ + +#define MAX_ADDR 7 /* maximum number of % recognized, also adjust + sscanf() below */ + +/* + * Structure used to hold the info of one part of 'errorformat' + */ +struct eformat +{ + char_u *fmtstr; /* pre-formatted part of 'errorformat' */ +#ifdef UTS2 + char_u *(adr[MAX_ADDR]); /* addresses used */ +#else + void *(adr[MAX_ADDR]); +#endif + int adr_cnt; /* number of addresses used */ + struct eformat *next; /* pointer to next (NULL if last) */ +}; + +/* + * Read the errorfile into memory, line by line, building the error list. + * Return FAIL for error, OK for success. + */ + int +qf_init() +{ + char_u *namebuf; + char_u *errmsg; + int col; + int type; + int valid; + long lnum; + int enr; + FILE *fd; + struct qf_line *qfp = NULL; + struct qf_line *qfprev = NULL; /* init to make SASC shut up */ + char_u *efmp; + struct eformat *fmt_first = NULL; + struct eformat *fmt_last = NULL; + struct eformat *fmt_ptr; + char_u *efm; + int maxlen; + int len; + int i, j; + int retval = FAIL; + + if (*p_ef == NUL) + { + emsg(e_errorf); + return FAIL; + } + + namebuf = alloc(CMDBUFFSIZE + 1); + errmsg = alloc(CMDBUFFSIZE + 1); + if (namebuf == NULL || errmsg == NULL) + goto qf_init_end; + + if ((fd = fopen((char *)p_ef, "r")) == NULL) + { + emsg2(e_openerrf, p_ef); + goto qf_init_end; + } + qf_free(); + qf_index = 0; + +/* + * Each part of the format string is copied and modified from p_efm to fmtstr. + * Only a few % characters are allowed. + */ + efm = p_efm; + while (efm[0]) + { + /* + * Allocate a new eformat structure and put it at the end of the list + */ + fmt_ptr = (struct eformat *)alloc((unsigned)sizeof(struct eformat)); + if (fmt_ptr == NULL) + goto error2; + if (fmt_first == NULL) /* first one */ + fmt_first = fmt_ptr; + else + fmt_last->next = fmt_ptr; + fmt_last = fmt_ptr; + fmt_ptr->next = NULL; + fmt_ptr->adr_cnt = 0; + + /* + * Isolate one part in the 'errorformat' option + */ + for (len = 0; efm[len] != NUL && efm[len] != ','; ++len) + if (efm[len] == '\\' && efm[len + 1] != NUL) + ++len; + + /* + * Get some space to modify the format string into. + * Must be able to do the largest expansion (x3) MAX_ADDR times. + */ + maxlen = len + MAX_ADDR * 3 + 4; + if ((fmt_ptr->fmtstr = alloc(maxlen)) == NULL) + goto error2; + + for (i = 0; i < MAX_ADDR; ++i) + fmt_ptr->adr[i] = NULL; + + for (efmp = efm, i = 0; efmp < efm + len; ++efmp, ++i) + { + if (efmp[0] != '%') /* copy normal character */ + { + if (efmp[0] == '\\' && efmp + 1 < efm + len) + ++efmp; + fmt_ptr->fmtstr[i] = efmp[0]; + } + else + { + fmt_ptr->fmtstr[i++] = '%'; + switch (efmp[1]) + { + case 'f': /* filename */ + fmt_ptr->adr[fmt_ptr->adr_cnt++] = namebuf; + /* FALLTHROUGH */ + + case 'm': /* message */ + if (efmp[1] == 'm') + fmt_ptr->adr[fmt_ptr->adr_cnt++] = errmsg; + fmt_ptr->fmtstr[i++] = '['; + fmt_ptr->fmtstr[i++] = '^'; +#ifdef __EMX__ + /* don't allow spaces in filename. This fixes + * the broken sscanf() where an empty message + * is accepted as a valid conversion. + */ + if (efmp[1] == 'f') + fmt_ptr->fmtstr[i++] = ' '; +#endif + if (efmp[2] == '\\') /* could be "%m\," */ + j = 3; + else + j = 2; + if (efmp + j < efm + len) + fmt_ptr->fmtstr[i++] = efmp[j]; + else + { + /* + * The %f or %m is the last one in the format, + * stop at the CR of NL at the end of the line. + */ +#ifdef USE_CRNL + fmt_ptr->fmtstr[i++] = '\r'; +#endif + fmt_ptr->fmtstr[i++] = '\n'; + } + fmt_ptr->fmtstr[i] = ']'; + break; + case 'c': /* column */ + fmt_ptr->adr[fmt_ptr->adr_cnt++] = &col; + fmt_ptr->fmtstr[i] = 'd'; + break; + case 'l': /* line */ + fmt_ptr->adr[fmt_ptr->adr_cnt++] = &lnum; + fmt_ptr->fmtstr[i++] = 'l'; + fmt_ptr->fmtstr[i] = 'd'; + break; + case 'n': /* error number */ + fmt_ptr->adr[fmt_ptr->adr_cnt++] = &enr; + fmt_ptr->fmtstr[i] = 'd'; + break; + case 't': /* error type */ + fmt_ptr->adr[fmt_ptr->adr_cnt++] = &type; + fmt_ptr->fmtstr[i] = 'c'; + break; + case '%': /* %% */ + case '*': /* %*: no assignment */ + fmt_ptr->fmtstr[i] = efmp[1]; + break; + default: + EMSG("invalid % in format string"); + goto error2; + } + if (fmt_ptr->adr_cnt == MAX_ADDR) + { + EMSG("too many % in format string"); + goto error2; + } + ++efmp; + } + if (i >= maxlen - 6) + { + EMSG("invalid format string"); + goto error2; + } + } + fmt_ptr->fmtstr[i] = NUL; + + /* + * Advance to next part + */ + efm = skip_to_option_part(efm + len); /* skip comma and spaces */ + } + if (fmt_first == NULL) /* nothing found */ + { + EMSG("'errorformat' contains no pattern"); + goto error2; + } + + /* + * Read the lines in the error file one by one. + * Try to recognize one of the error formats in each line. + */ + while (fgets((char *)IObuff, CMDBUFFSIZE, fd) != NULL && !got_int) + { + if ((qfp = (struct qf_line *)alloc((unsigned)sizeof(struct qf_line))) + == NULL) + goto error2; + + IObuff[CMDBUFFSIZE] = NUL; /* for very long lines */ + + /* + * Try to match each part of 'errorformat' until we find a complete + * match or none matches. + */ + valid = TRUE; + for (fmt_ptr = fmt_first; fmt_ptr != NULL; fmt_ptr = fmt_ptr->next) + { + namebuf[0] = NUL; + errmsg[0] = NUL; + lnum = 0; + col = 0; + enr = -1; + type = 0; + + /* + * If first char of the format and message don't match, there is + * no need to try sscanf() on it... Somehow I believe there are + * very slow implementations of sscanf(). + * -- Paul Slootman + */ + if (fmt_ptr->fmtstr[0] != '%' && fmt_ptr->fmtstr[0] != IObuff[0]) + continue; + + if (sscanf((char *)IObuff, (char *)fmt_ptr->fmtstr, + fmt_ptr->adr[0], fmt_ptr->adr[1], fmt_ptr->adr[2], + fmt_ptr->adr[3], fmt_ptr->adr[4], fmt_ptr->adr[5], + fmt_ptr->adr[6]) == fmt_ptr->adr_cnt) + break; + } + if (fmt_ptr == NULL) + { + namebuf[0] = NUL; /* no match found, remove file name */ + lnum = 0; /* don't jump to this line */ + valid = FALSE; + STRCPY(errmsg, IObuff); /* copy whole line to error message */ + if ((efmp = vim_strrchr(errmsg, '\n')) != NULL) + *efmp = NUL; +#ifdef USE_CRNL + if ((efmp = vim_strrchr(errmsg, '\r')) != NULL) + *efmp = NUL; +#endif + } + + if (namebuf[0] == NUL) /* no file name */ + qfp->qf_fnum = 0; + else + qfp->qf_fnum = buflist_add(namebuf); + if ((qfp->qf_text = strsave(errmsg)) == NULL) + goto error1; + qfp->qf_lnum = lnum; + qfp->qf_col = col; + qfp->qf_nr = enr; + qfp->qf_type = type; + qfp->qf_valid = valid; + + if (qf_count == 0) /* first element in the list */ + { + qf_start = qfp; + qfp->qf_prev = qfp; /* first element points to itself */ + } + else + { + qfp->qf_prev = qfprev; + qfprev->qf_next = qfp; + } + qfp->qf_next = qfp; /* last element points to itself */ + qfp->qf_cleared = FALSE; + qfprev = qfp; + ++qf_count; + if (qf_index == 0 && qfp->qf_valid) /* first valid entry */ + { + qf_index = qf_count; + qf_ptr = qfp; + } + line_breakcheck(); + } + if (!ferror(fd)) + { + if (qf_index == 0) /* no valid entry found */ + { + qf_ptr = qf_start; + qf_index = 1; + qf_nonevalid = TRUE; + } + else + qf_nonevalid = FALSE; + retval = OK; + goto qf_init_ok; + } + emsg(e_readerrf); +error1: + vim_free(qfp); +error2: + qf_free(); +qf_init_ok: + fclose(fd); + for (fmt_ptr = fmt_first; fmt_ptr != NULL; fmt_ptr = fmt_first) + { + fmt_first = fmt_ptr->next; + vim_free(fmt_ptr->fmtstr); + vim_free(fmt_ptr); + } +qf_init_end: + vim_free(namebuf); + vim_free(errmsg); + return retval; +} + +/* + * jump to a quickfix line + * if dir == FORWARD go "errornr" valid entries forward + * if dir == BACKWARD go "errornr" valid entries backward + * else if "errornr" is zero, redisplay the same line + * else go to entry "errornr" + */ + void +qf_jump(dir, errornr) + int dir; + int errornr; +{ + struct qf_line *old_qf_ptr; + int old_qf_index; + static char_u *e_no_more_errors = (char_u *)"No more errors"; + char_u *err = e_no_more_errors; + linenr_t i; + + if (qf_count == 0) + { + emsg(e_quickfix); + return; + } + + old_qf_ptr = qf_ptr; + old_qf_index = qf_index; + if (dir == FORWARD) /* next valid entry */ + { + while (errornr--) + { + old_qf_ptr = qf_ptr; + old_qf_index = qf_index; + do + { + if (qf_index == qf_count || qf_ptr->qf_next == NULL) + { + qf_ptr = old_qf_ptr; + qf_index = old_qf_index; + if (err != NULL) + { + emsg(err); + return; + } + errornr = 0; + break; + } + ++qf_index; + qf_ptr = qf_ptr->qf_next; + } while (!qf_nonevalid && !qf_ptr->qf_valid); + err = NULL; + } + } + else if (dir == BACKWARD) /* previous valid entry */ + { + while (errornr--) + { + old_qf_ptr = qf_ptr; + old_qf_index = qf_index; + do + { + if (qf_index == 1 || qf_ptr->qf_prev == NULL) + { + qf_ptr = old_qf_ptr; + qf_index = old_qf_index; + if (err != NULL) + { + emsg(err); + return; + } + errornr = 0; + break; + } + --qf_index; + qf_ptr = qf_ptr->qf_prev; + } while (!qf_nonevalid && !qf_ptr->qf_valid); + err = NULL; + } + } + else if (errornr != 0) /* go to specified number */ + { + while (errornr < qf_index && qf_index > 1 && qf_ptr->qf_prev != NULL) + { + --qf_index; + qf_ptr = qf_ptr->qf_prev; + } + while (errornr > qf_index && qf_index < qf_count && qf_ptr->qf_next != NULL) + { + ++qf_index; + qf_ptr = qf_ptr->qf_next; + } + } + + /* + * If there is a file name, + * read the wanted file if needed, and check autowrite etc. + */ + if (qf_ptr->qf_fnum == 0 || buflist_getfile(qf_ptr->qf_fnum, + (linenr_t)1, GETF_SETMARK) == OK) + { + /* + * Go to line with error, unless qf_lnum is 0. + */ + i = qf_ptr->qf_lnum; + if (i > 0) + { + if (i > curbuf->b_ml.ml_line_count) + i = curbuf->b_ml.ml_line_count; + curwin->w_cursor.lnum = i; + } + if (qf_ptr->qf_col > 0) + { + curwin->w_cursor.col = qf_ptr->qf_col - 1; + adjust_cursor(); + } + else + beginline(TRUE); + cursupdate(); + smsg((char_u *)"(%d of %d)%s%s: %s", qf_index, qf_count, + qf_ptr->qf_cleared ? (char_u *)" (line deleted)" : (char_u *)"", + qf_types(qf_ptr->qf_type, qf_ptr->qf_nr), qf_ptr->qf_text); + /* + * if the message is short, redisplay after redrawing the screen + */ + if (linetabsize(IObuff) < ((int)p_ch - 1) * Columns + sc_col) + keep_msg = IObuff; + } + else if (qf_ptr->qf_fnum != 0) + { + /* + * Couldn't open file, so put index back where it was. This could + * happen if the file was readonly and we changed something - webb + */ + qf_ptr = old_qf_ptr; + qf_index = old_qf_index; + } +} + +/* + * list all errors + */ + void +qf_list(all) + int all; /* If not :cl!, only show recognised errors */ +{ + BUF *buf; + char_u *fname; + struct qf_line *qfp; + int i; + + if (qf_count == 0) + { + emsg(e_quickfix); + return; + } + + if (qf_nonevalid) + all = TRUE; + qfp = qf_start; + set_highlight('d'); /* Same as for directories */ + for (i = 1; !got_int && i <= qf_count; ++i) + { + if (qfp->qf_valid || all) + { + msg_outchar('\n'); + start_highlight(); + fname = NULL; + if (qfp->qf_fnum != 0 && + (buf = buflist_findnr(qfp->qf_fnum)) != NULL) + fname = buf->b_xfilename; + if (fname == NULL) + sprintf((char *)IObuff, "%2d", i); + else + sprintf((char *)IObuff, "%2d %s", i, fname); + msg_outtrans(IObuff); + stop_highlight(); + if (qfp->qf_lnum == 0) + IObuff[0] = NUL; + else if (qfp->qf_col == 0) + sprintf((char *)IObuff, ":%ld", qfp->qf_lnum); + else + sprintf((char *)IObuff, ":%ld, col %d", + qfp->qf_lnum, qfp->qf_col); + sprintf((char *)IObuff + STRLEN(IObuff), "%s: ", + qf_types(qfp->qf_type, qfp->qf_nr)); + msg_outstr(IObuff); + msg_prt_line(qfp->qf_text); + flushbuf(); /* show one line at a time */ + } + qfp = qfp->qf_next; + mch_breakcheck(); + } +} + +/* + * free the error list + */ + static void +qf_free() +{ + struct qf_line *qfp; + + while (qf_count) + { + qfp = qf_start->qf_next; + vim_free(qf_start->qf_text); + vim_free(qf_start); + qf_start = qfp; + --qf_count; + } +} + +/* + * qf_mark_adjust: adjust marks + */ + void +qf_mark_adjust(line1, line2, amount, amount_after) + linenr_t line1; + linenr_t line2; + long amount; + long amount_after; +{ + register int i; + struct qf_line *qfp; + + if (qf_count) + for (i = 0, qfp = qf_start; i < qf_count; ++i, qfp = qfp->qf_next) + if (qfp->qf_fnum == curbuf->b_fnum) + { + if (qfp->qf_lnum >= line1 && qfp->qf_lnum <= line2) + { + if (amount == MAXLNUM) + qfp->qf_cleared = TRUE; + else + qfp->qf_lnum += amount; + } + if (amount_after && qfp->qf_lnum > line2) + qfp->qf_lnum += amount_after; + } +} + +/* + * Make a nice message out of the error character and the error number: + * char number message + * e or E 0 " error" + * w or W 0 " warning" + * other 0 "" + * w or W n " warning n" + * other n " error n" + */ + static char_u * +qf_types(c, nr) + int c, nr; +{ + static char_u buf[20]; + char_u *p1; + + p1 = (char_u *)" error"; + if (c == 'W' || c == 'w') + p1 = (char_u *)" warning"; + else if (nr <= 0 && c != 'E' && c != 'e') + p1 = (char_u *)""; + + if (nr <= 0) + return p1; + + sprintf((char *)buf, "%s %3d", p1, nr); + return buf; +} diff --git a/usr.bin/vim/regexp.c b/usr.bin/vim/regexp.c new file mode 100644 index 00000000000..409719ffcba --- /dev/null +++ b/usr.bin/vim/regexp.c @@ -0,0 +1,1895 @@ +/* $OpenBSD: regexp.c,v 1.1.1.1 1996/09/07 21:40:25 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE + * + * This is NOT the original regular expression code as written by + * Henry Spencer. This code has been modified specifically for use + * with the VIM editor, and should not be used apart from compiling + * VIM. If you want a good regular expression library, get the + * original code. The copyright notice that follows is from the + * original. + * + * NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE + * + * + * vim_regcomp and vim_regexec -- vim_regsub and regerror are elsewhere + * + * Copyright (c) 1986 by University of Toronto. + * Written by Henry Spencer. Not derived from licensed software. + * + * Permission is granted to anyone to use this software for any + * purpose on any computer system, and to redistribute it freely, + * subject to the following restrictions: + * + * 1. The author is not responsible for the consequences of use of + * this software, no matter how awful, even if they arise + * from defects in it. + * + * 2. The origin of this software must not be misrepresented, either + * by explicit claim or by omission. + * + * 3. Altered versions must be plainly marked as such, and must not + * be misrepresented as being the original software. + * + * Beware that some of this code is subtly aware of the way operator + * precedence is structured in regular expressions. Serious changes in + * regular-expression syntax might require a total rethink. + * + * $Log: regexp.c,v $ + * Revision 1.1.1.1 1996/09/07 21:40:25 downsj + * Initial import of vim 4.2. + * + * This is meant to replace nvi in the tree. Vim, in general, works better, + * provides more features, and does not suffer from the license problems + * being imposed upon nvi. + * + * On the other hand, vim lacks a non-visual ex mode, in addition to open mode. + * + * This includes the GUI (X11) code, but doesn't try to compile it. + * + * Revision 1.2 88/04/28 08:09:45 tony + * First modification of the regexp library. Added an external variable + * 'reg_ic' which can be set to indicate that case should be ignored. + * Added a new parameter to vim_regexec() to indicate that the given string + * comes from the beginning of a line and is thus eligible to match + * 'beginning-of-line'. + * + * Revisions by Olaf 'Rhialto' Seibert, rhialto@mbfys.kun.nl: + * Changes for vi: (the semantics of several things were rather different) + * - Added lexical analyzer, because in vi magicness of characters + * is rather difficult, and may change over time. + * - Added support for \< \> \1-\9 and ~ + * - Left some magic stuff in, but only backslashed: \| \+ + * - * and \+ still work after \) even though they shouldn't. + */ +#include "vim.h" +#include "globals.h" +#include "proto.h" +#undef DEBUG + +#include +#include "regexp.h" +#include "option.h" + +/* + * The "internal use only" fields in regexp.h are present to pass info from + * compile to execute that permits the execute phase to run lots faster on + * simple cases. They are: + * + * regstart char that must begin a match; '\0' if none obvious + * reganch is the match anchored (at beginning-of-line only)? + * regmust string (pointer into program) that match must include, or NULL + * regmlen length of regmust string + * + * Regstart and reganch permit very fast decisions on suitable starting points + * for a match, cutting down the work a lot. Regmust permits fast rejection + * of lines that cannot possibly match. The regmust tests are costly enough + * that vim_regcomp() supplies a regmust only if the r.e. contains something + * potentially expensive (at present, the only such thing detected is * or + + * at the start of the r.e., which can involve a lot of backup). Regmlen is + * supplied because the test in vim_regexec() needs it and vim_regcomp() is + * computing it anyway. + */ + +/* + * Structure for regexp "program". This is essentially a linear encoding + * of a nondeterministic finite-state machine (aka syntax charts or + * "railroad normal form" in parsing technology). Each node is an opcode + * plus a "next" pointer, possibly plus an operand. "Next" pointers of + * all nodes except BRANCH implement concatenation; a "next" pointer with + * a BRANCH on both ends of it is connecting two alternatives. (Here we + * have one of the subtle syntax dependencies: an individual BRANCH (as + * opposed to a collection of them) is never concatenated with anything + * because of operator precedence.) The operand of some types of node is + * a literal string; for others, it is a node leading into a sub-FSM. In + * particular, the operand of a BRANCH node is the first node of the branch. + * (NB this is *not* a tree structure: the tail of the branch connects + * to the thing following the set of BRANCHes.) The opcodes are: + */ + +/* definition number opnd? meaning */ +#define END 0 /* no End of program. */ +#define BOL 1 /* no Match "" at beginning of line. */ +#define EOL 2 /* no Match "" at end of line. */ +#define ANY 3 /* no Match any one character. */ +#define ANYOF 4 /* str Match any character in this string. */ +#define ANYBUT 5 /* str Match any character not in this + * string. */ +#define BRANCH 6 /* node Match this alternative, or the + * next... */ +#define BACK 7 /* no Match "", "next" ptr points backward. */ +#define EXACTLY 8 /* str Match this string. */ +#define NOTHING 9 /* no Match empty string. */ +#define STAR 10 /* node Match this (simple) thing 0 or more + * times. */ +#define PLUS 11 /* node Match this (simple) thing 1 or more + * times. */ +#define BOW 12 /* no Match "" after [^a-zA-Z0-9_] */ +#define EOW 13 /* no Match "" at [^a-zA-Z0-9_] */ +#define IDENT 14 /* no Match identifier char */ +#define WORD 15 /* no Match keyword char */ +#define FNAME 16 /* no Match file name char */ +#define PRINT 17 /* no Match printable char */ +#define SIDENT 18 /* no Match identifier char but no digit */ +#define SWORD 19 /* no Match word char but no digit */ +#define SFNAME 20 /* no Match file name char but no digit */ +#define SPRINT 21 /* no Match printable char but no digit */ +#define MOPEN 30 /* no Mark this point in input as start of + * #n. */ + /* MOPEN+1 is number 1, etc. */ +#define MCLOSE 40 /* no Analogous to MOPEN. */ +#define BACKREF 50 /* node Match same string again \1-\9 */ + +#define Magic(x) ((x)|('\\'<<8)) + +/* + * Opcode notes: + * + * BRANCH The set of branches constituting a single choice are hooked + * together with their "next" pointers, since precedence prevents + * anything being concatenated to any individual branch. The + * "next" pointer of the last BRANCH in a choice points to the + * thing following the whole choice. This is also where the + * final "next" pointer of each individual branch points; each + * branch starts with the operand node of a BRANCH node. + * + * BACK Normal "next" pointers all implicitly point forward; BACK + * exists to make loop structures possible. + * + * STAR,PLUS '=', and complex '*' and '+', are implemented as circular + * BRANCH structures using BACK. Simple cases (one character + * per match) are implemented with STAR and PLUS for speed + * and to minimize recursive plunges. + * Note: We would like to use "\?" instead of "\=", but a "\?" + * can be part of a pattern to escape the special meaning of '?' + * at the end of the pattern in "?pattern?e". + * + * MOPEN,MCLOSE ...are numbered at compile time. + */ + +/* + * A node is one char of opcode followed by two chars of "next" pointer. + * "Next" pointers are stored as two 8-bit pieces, high order first. The + * value is a positive offset from the opcode of the node containing it. + * An operand, if any, simply follows the node. (Note that much of the + * code generation knows about this implicit relationship.) + * + * Using two bytes for the "next" pointer is vast overkill for most things, + * but allows patterns to get big without disasters. + */ +#define OP(p) (*(p)) +#define NEXT(p) (((*((p)+1)&0377)<<8) + (*((p)+2)&0377)) +#define OPERAND(p) ((p) + 3) + +/* + * Utility definitions. + */ +#ifndef CHARBITS +#define UCHARAT(p) ((int)*(unsigned char *)(p)) +#else +#define UCHARAT(p) ((int)*(p)&CHARBITS) +#endif + +#define EMSG_RETURN(m) { emsg(m); rc_did_emsg = TRUE; return NULL; } + +static int ismult __ARGS((int)); +static char_u *cstrchr __ARGS((char_u *, int)); + + static int +ismult(c) + int c; +{ + return (c == Magic('*') || c == Magic('+') || c == Magic('=')); +} + +/* + * Flags to be passed up and down. + */ +#define HASWIDTH 01 /* Known never to match null string. */ +#define SIMPLE 02 /* Simple enough to be STAR/PLUS operand. */ +#define SPSTART 04 /* Starts with * or +. */ +#define WORST 0 /* Worst case. */ + +/* + * The following allows empty REs, meaning "the same as the previous RE". + * per the ed(1) manual. + */ +/* #define EMPTY_RE */ /* this is done outside of regexp */ +#ifdef EMPTY_RE +char_u *reg_prev_re; +#endif + +#define TILDE +#ifdef TILDE +char_u *reg_prev_sub; +#endif + +/* + * Global work variables for vim_regcomp(). + */ + +static char_u *regparse; /* Input-scan pointer. */ +static int regnpar; /* () count. */ +static char_u regdummy; +static char_u *regcode; /* Code-emit pointer; ®dummy = don't. */ +static long regsize; /* Code size. */ +static char_u **regendp; /* Ditto for endp. */ + +/* + * META contains all characters that may be magic, except '^' and '$'. + * This depends on the configuration options TILDE, BACKREF. + * (could be done simpler for compilers that know string concatenation) + */ + +#ifdef TILDE +# ifdef BACKREF + static char_u META[] = ".[()|=+*<>iIkKfFpP~123456789"; +# else + static char_u META[] = ".[()|=+*<>iIkKfFpP~"; +# endif +#else +# ifdef BACKREF + static char_u META[] = ".[()|=+*<>iIkKfFpP123456789"; +# else + static char_u META[] = ".[()|=+*<>iIkKfFpP"; +# endif +#endif + +/* + * REGEXP_ABBR contains all characters which act as abbreviations after '\'. + * These are: + * \r - New line (CR). + * \t - Tab (TAB). + * \e - Escape (ESC). + * \b - Backspace (Ctrl('H')). + */ +static char_u REGEXP_ABBR[] = "rteb"; + +/* + * Forward declarations for vim_regcomp()'s friends. + */ +static void initchr __ARGS((char_u *)); +static int getchr __ARGS((void)); +static int peekchr __ARGS((void)); +#define PeekChr() curchr /* shortcut only when last action was peekchr() */ +static int curchr; +static void skipchr __ARGS((void)); +static void ungetchr __ARGS((void)); +static char_u *reg __ARGS((int, int *)); +static char_u *regbranch __ARGS((int *)); +static char_u *regpiece __ARGS((int *)); +static char_u *regatom __ARGS((int *)); +static char_u *regnode __ARGS((int)); +static char_u *regnext __ARGS((char_u *)); +static void regc __ARGS((int)); +static void unregc __ARGS((void)); +static void reginsert __ARGS((int, char_u *)); +static void regtail __ARGS((char_u *, char_u *)); +static void regoptail __ARGS((char_u *, char_u *)); + +#ifndef HAVE_STRCSPN +static size_t strcspn __ARGS((const char_u *, const char_u *)); +#endif + +/* + * skip past regular expression + * stop at end of 'p' of where 'dirc' is found ('/', '?', etc) + * take care of characters with a backslash in front of it + * skip strings inside [ and ]. + */ + char_u * +skip_regexp(p, dirc) + char_u *p; + int dirc; +{ + int in_range = FALSE; + + for (; p[0] != NUL; ++p) + { + if (p[0] == dirc && !in_range) /* found end of regexp */ + break; + if ((p[0] == '[' && p_magic) || (p[0] == '\\' && p[1] == '[' && !p_magic)) + { + in_range = TRUE; + if (p[0] == '\\') + ++p; + /* "[^]" and "[]" are not the end of a range */ + if (p[1] == '^') + ++p; + if (p[1] == ']') + ++p; + } + else if (p[0] == '\\' && p[1] != NUL) + ++p; /* skip next character */ + else if (p[0] == ']') + in_range = FALSE; + } + return p; +} + +/* + - vim_regcomp - compile a regular expression into internal code + * + * We can't allocate space until we know how big the compiled form will be, + * but we can't compile it (and thus know how big it is) until we've got a + * place to put the code. So we cheat: we compile it twice, once with code + * generation turned off and size counting turned on, and once "for real". + * This also means that we don't allocate space until we are sure that the + * thing really will compile successfully, and we never have to move the + * code and thus invalidate pointers into it. (Note that it has to be in + * one piece because vim_free() must be able to free it all.) + * + * Beware that the optimization-preparation code in here knows about some + * of the structure of the compiled regexp. + */ + regexp * +vim_regcomp(exp) + char_u *exp; +{ + register regexp *r; + register char_u *scan; + register char_u *longest; + register int len; + int flags; +/* extern char *malloc();*/ + + if (exp == NULL) + EMSG_RETURN(e_null); + +#ifdef EMPTY_RE /* this is done outside of regexp */ + if (*exp == '\0') + { + if (reg_prev_re) + exp = reg_prev_re; + else + EMSG_RETURN(e_noprevre); + } +#endif + + /* First pass: determine size, legality. */ + initchr((char_u *)exp); + regnpar = 1; + regsize = 0L; + regcode = ®dummy; + regendp = NULL; + regc(MAGIC); + if (reg(0, &flags) == NULL) + return NULL; + + /* Small enough for pointer-storage convention? */ + if (regsize >= 32767L) /* Probably could be 65535L. */ + EMSG_RETURN(e_toolong); + + /* Allocate space. */ +/* r = (regexp *) malloc((unsigned) (sizeof(regexp) + regsize));*/ + r = (regexp *) alloc((unsigned) (sizeof(regexp) + regsize)); + if (r == NULL) + return NULL; + +#ifdef EMPTY_RE /* this is done outside of regexp */ + if (exp != reg_prev_re) { + vim_free(reg_prev_re); + if (reg_prev_re = alloc(STRLEN(exp) + 1)) + STRCPY(reg_prev_re, exp); + } +#endif + + /* Second pass: emit code. */ + initchr((char_u *)exp); + regnpar = 1; + regcode = r->program; + regendp = r->endp; + regc(MAGIC); + if (reg(0, &flags) == NULL) { + vim_free(r); + return NULL; + } + + /* Dig out information for optimizations. */ + r->regstart = '\0'; /* Worst-case defaults. */ + r->reganch = 0; + r->regmust = NULL; + r->regmlen = 0; + scan = r->program + 1; /* First BRANCH. */ + if (OP(regnext(scan)) == END) { /* Only one top-level choice. */ + scan = OPERAND(scan); + + /* Starting-point info. */ + if (OP(scan) == EXACTLY) + r->regstart = *OPERAND(scan); + else if (OP(scan) == BOL) + r->reganch++; + + /* + * If there's something expensive in the r.e., find the longest + * literal string that must appear and make it the regmust. Resolve + * ties in favor of later strings, since the regstart check works + * with the beginning of the r.e. and avoiding duplication + * strengthens checking. Not a strong reason, but sufficient in the + * absence of others. + */ + /* + * When the r.e. starts with BOW, it is faster to look for a regmust + * first. Used a lot for "#" and "*" commands. (Added by mool). + */ + if (flags & SPSTART || OP(scan) == BOW) { + longest = NULL; + len = 0; + for (; scan != NULL; scan = regnext(scan)) + if (OP(scan) == EXACTLY && STRLEN(OPERAND(scan)) >= (size_t)len) { + longest = OPERAND(scan); + len = STRLEN(OPERAND(scan)); + } + r->regmust = longest; + r->regmlen = len; + } + } +#ifdef DEBUG + regdump(r); +#endif + return r; +} + +/* + - reg - regular expression, i.e. main body or parenthesized thing + * + * Caller must absorb opening parenthesis. + * + * Combining parenthesis handling with the base level of regular expression + * is a trifle forced, but the need to tie the tails of the branches to what + * follows makes it hard to avoid. + */ + static char_u * +reg(paren, flagp) + int paren; /* Parenthesized? */ + int *flagp; +{ + register char_u *ret; + register char_u *br; + register char_u *ender; + register int parno = 0; + int flags; + + *flagp = HASWIDTH; /* Tentatively. */ + + /* Make an MOPEN node, if parenthesized. */ + if (paren) { + if (regnpar >= NSUBEXP) + EMSG_RETURN(e_toombra); + parno = regnpar; + regnpar++; + ret = regnode(MOPEN + parno); + if (regendp) + regendp[parno] = NULL; /* haven't seen the close paren yet */ + } else + ret = NULL; + + /* Pick up the branches, linking them together. */ + br = regbranch(&flags); + if (br == NULL) + return NULL; + if (ret != NULL) + regtail(ret, br); /* MOPEN -> first. */ + else + ret = br; + if (!(flags & HASWIDTH)) + *flagp &= ~HASWIDTH; + *flagp |= flags & SPSTART; + while (peekchr() == Magic('|')) { + skipchr(); + br = regbranch(&flags); + if (br == NULL) + return NULL; + regtail(ret, br); /* BRANCH -> BRANCH. */ + if (!(flags & HASWIDTH)) + *flagp &= ~HASWIDTH; + *flagp |= flags & SPSTART; + } + + /* Make a closing node, and hook it on the end. */ + ender = regnode((paren) ? MCLOSE + parno : END); + regtail(ret, ender); + + /* Hook the tails of the branches to the closing node. */ + for (br = ret; br != NULL; br = regnext(br)) + regoptail(br, ender); + + /* Check for proper termination. */ + if (paren && getchr() != Magic(')')) + EMSG_RETURN(e_toombra) + else if (!paren && peekchr() != '\0') + { + if (PeekChr() == Magic(')')) + EMSG_RETURN(e_toomket) + else + EMSG_RETURN(e_trailing) /* "Can't happen". */ + /* NOTREACHED */ + } + /* + * Here we set the flag allowing back references to this set of + * parentheses. + */ + if (paren && regendp) + regendp[parno] = ender; /* have seen the close paren */ + return ret; +} + +/* + - regbranch - one alternative of an | operator + * + * Implements the concatenation operator. + */ + static char_u * +regbranch(flagp) + int *flagp; +{ + register char_u *ret; + register char_u *chain; + register char_u *latest; + int flags; + + *flagp = WORST; /* Tentatively. */ + + ret = regnode(BRANCH); + chain = NULL; + while (peekchr() != '\0' && PeekChr() != Magic('|') && PeekChr() != Magic(')')) { + latest = regpiece(&flags); + if (latest == NULL) + return NULL; + *flagp |= flags & HASWIDTH; + if (chain == NULL) /* First piece. */ + *flagp |= flags & SPSTART; + else + regtail(chain, latest); + chain = latest; + } + if (chain == NULL) /* Loop ran zero times. */ + (void) regnode(NOTHING); + + return ret; +} + +/* + - regpiece - something followed by possible [*+=] + * + * Note that the branching code sequences used for = and the general cases + * of * and + are somewhat optimized: they use the same NOTHING node as + * both the endmarker for their branch list and the body of the last branch. + * It might seem that this node could be dispensed with entirely, but the + * endmarker role is not redundant. + */ +static char_u * +regpiece(flagp) + int *flagp; +{ + register char_u *ret; + register int op; + register char_u *next; + int flags; + + ret = regatom(&flags); + if (ret == NULL) + return NULL; + + op = peekchr(); + if (!ismult(op)) { + *flagp = flags; + return ret; + } + if (!(flags & HASWIDTH) && op != Magic('=')) + EMSG_RETURN((char_u *)"*+ operand could be empty"); + *flagp = (op != Magic('+')) ? (WORST | SPSTART) : (WORST | HASWIDTH); + + if (op == Magic('*') && (flags & SIMPLE)) + reginsert(STAR, ret); + else if (op == Magic('*')) { + /* Emit x* as (x&|), where & means "self". */ + reginsert(BRANCH, ret); /* Either x */ + regoptail(ret, regnode(BACK)); /* and loop */ + regoptail(ret, ret); /* back */ + regtail(ret, regnode(BRANCH)); /* or */ + regtail(ret, regnode(NOTHING)); /* null. */ + } else if (op == Magic('+') && (flags & SIMPLE)) + reginsert(PLUS, ret); + else if (op == Magic('+')) { + /* Emit x+ as x(&|), where & means "self". */ + next = regnode(BRANCH); /* Either */ + regtail(ret, next); + regtail(regnode(BACK), ret); /* loop back */ + regtail(next, regnode(BRANCH)); /* or */ + regtail(ret, regnode(NOTHING)); /* null. */ + } else if (op == Magic('=')) { + /* Emit x= as (x|) */ + reginsert(BRANCH, ret); /* Either x */ + regtail(ret, regnode(BRANCH)); /* or */ + next = regnode(NOTHING);/* null. */ + regtail(ret, next); + regoptail(ret, next); + } + skipchr(); + if (ismult(peekchr())) + EMSG_RETURN((char_u *)"Nested *=+"); + + return ret; +} + +/* + - regatom - the lowest level + * + * Optimization: gobbles an entire sequence of ordinary characters so that + * it can turn them into a single node, which is smaller to store and + * faster to run. + */ +static char_u * +regatom(flagp) + int *flagp; +{ + register char_u *ret; + int flags; + + *flagp = WORST; /* Tentatively. */ + + switch (getchr()) { + case Magic('^'): + ret = regnode(BOL); + break; + case Magic('$'): + ret = regnode(EOL); + break; + case Magic('<'): + ret = regnode(BOW); + break; + case Magic('>'): + ret = regnode(EOW); + break; + case Magic('.'): + ret = regnode(ANY); + *flagp |= HASWIDTH | SIMPLE; + break; + case Magic('i'): + ret = regnode(IDENT); + *flagp |= HASWIDTH | SIMPLE; + break; + case Magic('k'): + ret = regnode(WORD); + *flagp |= HASWIDTH | SIMPLE; + break; + case Magic('I'): + ret = regnode(SIDENT); + *flagp |= HASWIDTH | SIMPLE; + break; + case Magic('K'): + ret = regnode(SWORD); + *flagp |= HASWIDTH | SIMPLE; + break; + case Magic('f'): + ret = regnode(FNAME); + *flagp |= HASWIDTH | SIMPLE; + break; + case Magic('F'): + ret = regnode(SFNAME); + *flagp |= HASWIDTH | SIMPLE; + break; + case Magic('p'): + ret = regnode(PRINT); + *flagp |= HASWIDTH | SIMPLE; + break; + case Magic('P'): + ret = regnode(SPRINT); + *flagp |= HASWIDTH | SIMPLE; + break; + case Magic('('): + ret = reg(1, &flags); + if (ret == NULL) + return NULL; + *flagp |= flags & (HASWIDTH | SPSTART); + break; + case '\0': + case Magic('|'): + case Magic(')'): + EMSG_RETURN(e_internal) /* Supposed to be caught earlier. */ + /* NOTREACHED */ + case Magic('='): + EMSG_RETURN((char_u *)"\\= follows nothing") + /* NOTREACHED */ + case Magic('+'): + EMSG_RETURN((char_u *)"\\+ follows nothing") + /* NOTREACHED */ + case Magic('*'): + if (reg_magic) + EMSG_RETURN((char_u *)"* follows nothing") + else + EMSG_RETURN((char_u *)"\\* follows nothing") + /* break; Not Reached */ +#ifdef TILDE + case Magic('~'): /* previous substitute pattern */ + if (reg_prev_sub) { + register char_u *p; + + ret = regnode(EXACTLY); + p = reg_prev_sub; + while (*p) { + regc(*p++); + } + regc('\0'); + if (p - reg_prev_sub) { + *flagp |= HASWIDTH; + if ((p - reg_prev_sub) == 1) + *flagp |= SIMPLE; + } + } else + EMSG_RETURN(e_nopresub); + break; +#endif +#ifdef BACKREF + case Magic('1'): + case Magic('2'): + case Magic('3'): + case Magic('4'): + case Magic('5'): + case Magic('6'): + case Magic('7'): + case Magic('8'): + case Magic('9'): { + int refnum; + + ungetchr(); + refnum = getchr() - Magic('0'); + /* + * Check if the back reference is legal. We use the parentheses + * pointers to mark encountered close parentheses, but this + * is only available in the second pass. Checking opens is + * always possible. + * Should also check that we don't refer to something that + * is repeated (+*=): what instance of the repetition should + * we match? TODO. + */ + if (refnum < regnpar && + (regendp == NULL || regendp[refnum] != NULL)) + ret = regnode(BACKREF + refnum); + else + EMSG_RETURN((char_u *)"Illegal back reference"); + } + break; +#endif + case Magic('['): + { + char_u *p; + + /* + * If there is no matching ']', we assume the '[' is a normal + * character. This makes ":help [" work. + */ + p = regparse; + if (*p == '^') /* Complement of range. */ + ++p; + if (*p == ']' || *p == '-') + ++p; + while (*p != '\0' && *p != ']') + { + if (*p == '-') + { + ++p; + if (*p != ']' && *p != '\0') + ++p; + } + else if (*p == '\\' && p[1] != '\0') + p += 2; + else + ++p; + } + if (*p == ']') /* there is a matching ']' */ + { + /* + * In a character class, different parsing rules apply. + * Not even \ is special anymore, nothing is. + */ + if (*regparse == '^') { /* Complement of range. */ + ret = regnode(ANYBUT); + regparse++; + } else + ret = regnode(ANYOF); + if (*regparse == ']' || *regparse == '-') + regc(*regparse++); + while (*regparse != '\0' && *regparse != ']') { + if (*regparse == '-') { + regparse++; + if (*regparse == ']' || *regparse == '\0') + regc('-'); + else { + register int cclass; + register int cclassend; + + cclass = UCHARAT(regparse - 2) + 1; + cclassend = UCHARAT(regparse); + if (cclass > cclassend + 1) + EMSG_RETURN(e_invrange); + for (; cclass <= cclassend; cclass++) + regc(cclass); + regparse++; + } + } else if (*regparse == '\\' && regparse[1]) { + regparse++; + regc(*regparse++); + } else + regc(*regparse++); + } + regc('\0'); + if (*regparse != ']') + EMSG_RETURN(e_toomsbra); + skipchr(); /* let's be friends with the lexer again */ + *flagp |= HASWIDTH | SIMPLE; + break; + } + } + /* FALLTHROUGH */ + + default: + { + register int len; + int chr; + + ungetchr(); + len = 0; + ret = regnode(EXACTLY); + /* + * Always take at least one character, for '[' without matching + * ']'. + */ + while ((chr = peekchr()) != '\0' && (chr < Magic(0) || len == 0)) + { + regc(chr); + skipchr(); + len++; + } +#ifdef DEBUG + if (len == 0) + EMSG_RETURN((char_u *)"Unexpected magic character; check META."); +#endif + /* + * If there is a following *, \+ or \= we need the character + * in front of it as a single character operand + */ + if (len > 1 && ismult(chr)) + { + unregc(); /* Back off of *+= operand */ + ungetchr(); /* and put it back for next time */ + --len; + } + regc('\0'); + *flagp |= HASWIDTH; + if (len == 1) + *flagp |= SIMPLE; + } + break; + } + + return ret; +} + +/* + - regnode - emit a node + */ +static char_u * /* Location. */ +regnode(op) + int op; +{ + register char_u *ret; + register char_u *ptr; + + ret = regcode; + if (ret == ®dummy) { + regsize += 3; + return ret; + } + ptr = ret; + *ptr++ = op; + *ptr++ = '\0'; /* Null "next" pointer. */ + *ptr++ = '\0'; + regcode = ptr; + + return ret; +} + +/* + - regc - emit (if appropriate) a byte of code + */ +static void +regc(b) + int b; +{ + if (regcode != ®dummy) + *regcode++ = b; + else + regsize++; +} + +/* + - unregc - take back (if appropriate) a byte of code + */ +static void +unregc() +{ + if (regcode != ®dummy) + regcode--; + else + regsize--; +} + +/* + - reginsert - insert an operator in front of already-emitted operand + * + * Means relocating the operand. + */ +static void +reginsert(op, opnd) + int op; + char_u *opnd; +{ + register char_u *src; + register char_u *dst; + register char_u *place; + + if (regcode == ®dummy) { + regsize += 3; + return; + } + src = regcode; + regcode += 3; + dst = regcode; + while (src > opnd) + *--dst = *--src; + + place = opnd; /* Op node, where operand used to be. */ + *place++ = op; + *place++ = '\0'; + *place = '\0'; +} + +/* + - regtail - set the next-pointer at the end of a node chain + */ +static void +regtail(p, val) + char_u *p; + char_u *val; +{ + register char_u *scan; + register char_u *temp; + register int offset; + + if (p == ®dummy) + return; + + /* Find last node. */ + scan = p; + for (;;) { + temp = regnext(scan); + if (temp == NULL) + break; + scan = temp; + } + + if (OP(scan) == BACK) + offset = (int)(scan - val); + else + offset = (int)(val - scan); + *(scan + 1) = (char_u) ((offset >> 8) & 0377); + *(scan + 2) = (char_u) (offset & 0377); +} + +/* + - regoptail - regtail on operand of first argument; nop if operandless + */ +static void +regoptail(p, val) + char_u *p; + char_u *val; +{ + /* "Operandless" and "op != BRANCH" are synonymous in practice. */ + if (p == NULL || p == ®dummy || OP(p) != BRANCH) + return; + regtail(OPERAND(p), val); +} + +/* + - getchr() - get the next character from the pattern. We know about + * magic and such, so therefore we need a lexical analyzer. + */ + +/* static int curchr; */ +static int prevchr; +static int nextchr; /* used for ungetchr() */ +/* + * Note: prevchr is sometimes -1 when we are not at the start, + * eg in /[ ^I]^ the pattern was never found even if it existed, because ^ was + * taken to be magic -- webb + */ +static int at_start; /* True when we are on the first character */ + +static void +initchr(str) +char_u *str; +{ + regparse = str; + curchr = prevchr = nextchr = -1; + at_start = TRUE; +} + +static int +peekchr() +{ + if (curchr < 0) { + switch (curchr = regparse[0]) { + case '.': + /* case '+':*/ + /* case '=':*/ + case '[': + case '~': + if (reg_magic) + curchr = Magic(curchr); + break; + case '*': + /* * is not magic as the very first character, eg "?*ptr" */ + if (reg_magic && !at_start) + curchr = Magic('*'); + break; + case '^': + /* ^ is only magic as the very first character */ + if (at_start) + curchr = Magic('^'); + break; + case '$': + /* $ is only magic as the very last character and in front of '\|' */ + if (regparse[1] == NUL || (regparse[1] == '\\' && regparse[2] == '|')) + curchr = Magic('$'); + break; + case '\\': + regparse++; + if (regparse[0] == NUL) + curchr = '\\'; /* trailing '\' */ + else if (vim_strchr(META, regparse[0])) + { + /* + * META contains everything that may be magic sometimes, except + * ^ and $ ("\^" and "\$" are never magic). + * We now fetch the next character and toggle its magicness. + * Therefore, \ is so meta-magic that it is not in META. + */ + curchr = -1; + at_start = FALSE; /* We still want to be able to say "/\*ptr" */ + peekchr(); + curchr ^= Magic(0); + } + else if (vim_strchr(REGEXP_ABBR, regparse[0])) + { + /* + * Handle abbreviations, like "\t" for TAB -- webb + */ + switch (regparse[0]) + { + case 'r': curchr = CR; break; + case 't': curchr = TAB; break; + case 'e': curchr = ESC; break; + case 'b': curchr = Ctrl('H'); break; + } + } + else + { + /* + * Next character can never be (made) magic? + * Then backslashing it won't do anything. + */ + curchr = regparse[0]; + } + break; + } + } + + return curchr; +} + +static void +skipchr() +{ + regparse++; + at_start = FALSE; + prevchr = curchr; + curchr = nextchr; /* use previously unget char, or -1 */ + nextchr = -1; +} + +static int +getchr() +{ + int chr; + + chr = peekchr(); + skipchr(); + + return chr; +} + +/* + * put character back. Works only once! + */ +static void +ungetchr() +{ + nextchr = curchr; + curchr = prevchr; + /* + * Backup regparse as well; not because we will use what it points at, + * but because skipchr() will bump it again. + */ + regparse--; +} + +/* + * vim_regexec and friends + */ + +/* + * Global work variables for vim_regexec(). + */ +static char_u *reginput; /* String-input pointer. */ +static char_u *regbol; /* Beginning of input, for ^ check. */ +static char_u **regstartp; /* Pointer to startp array. */ +/* static char_u **regendp; */ /* Ditto for endp. */ + +/* + * Forwards. + */ +static int regtry __ARGS((regexp *, char_u *)); +static int regmatch __ARGS((char_u *)); +static int regrepeat __ARGS((char_u *)); + +#ifdef DEBUG +int regnarrate = 1; +void regdump __ARGS((regexp *)); +static char_u *regprop __ARGS((char_u *)); +#endif + +/* + * vim_regexec - match a regexp against a string + * Return non-zero if there is a match. + */ +int +vim_regexec(prog, string, at_bol) + register regexp *prog; + register char_u *string; + int at_bol; +{ + register char_u *s; + + /* Be paranoid... */ + if (prog == NULL || string == NULL) { + emsg(e_null); + rc_did_emsg = TRUE; + return 0; + } + /* Check validity of program. */ + if (UCHARAT(prog->program) != MAGIC) { + emsg(e_re_corr); + rc_did_emsg = TRUE; + return 0; + } + /* If there is a "must appear" string, look for it. */ + if (prog->regmust != NULL) { + s = string; + while ((s = cstrchr(s, prog->regmust[0])) != NULL) { + if (cstrncmp(s, prog->regmust, prog->regmlen) == 0) + break; /* Found it. */ + s++; + } + if (s == NULL) /* Not present. */ + return 0; + } + /* Mark beginning of line for ^ . */ + if (at_bol) + regbol = string; /* is possible to match bol */ + else + regbol = NULL; /* we aren't there, so don't match it */ + + /* Simplest case: anchored match need be tried only once. */ + if (prog->reganch) + return regtry(prog, string); + + /* Messy cases: unanchored match. */ + s = string; + if (prog->regstart != '\0') + /* We know what char it must start with. */ + while ((s = cstrchr(s, prog->regstart)) != NULL) { + if (regtry(prog, s)) + return 1; + s++; + } + else + /* We don't -- general case. */ + do { + if (regtry(prog, s)) + return 1; + } while (*s++ != '\0'); + + /* Failure. */ + return 0; +} + +/* + - regtry - try match at specific point + */ +static int /* 0 failure, 1 success */ +regtry(prog, string) + regexp *prog; + char_u *string; +{ + register int i; + register char_u **sp; + register char_u **ep; + + reginput = string; + regstartp = prog->startp; + regendp = prog->endp; + + sp = prog->startp; + ep = prog->endp; + for (i = NSUBEXP; i > 0; i--) { + *sp++ = NULL; + *ep++ = NULL; + } + if (regmatch(prog->program + 1)) { + prog->startp[0] = string; + prog->endp[0] = reginput; + return 1; + } else + return 0; +} + +/* + - regmatch - main matching routine + * + * Conceptually the strategy is simple: check to see whether the current + * node matches, call self recursively to see whether the rest matches, + * and then act accordingly. In practice we make some effort to avoid + * recursion, in particular by going through "ordinary" nodes (that don't + * need to know whether the rest of the match failed) by a loop instead of + * by recursion. + */ +static int /* 0 failure, 1 success */ +regmatch(prog) + char_u *prog; +{ + register char_u *scan; /* Current node. */ + char_u *next; /* Next node. */ + + scan = prog; +#ifdef DEBUG + if (scan != NULL && regnarrate) + fprintf(stderr, "%s(\n", regprop(scan)); +#endif + while (scan != NULL) { +#ifdef DEBUG + if (regnarrate) { + fprintf(stderr, "%s...\n", regprop(scan)); + } +#endif + next = regnext(scan); + switch (OP(scan)) { + case BOL: + if (reginput != regbol) + return 0; + break; + case EOL: + if (*reginput != '\0') + return 0; + break; + case BOW: /* \; reginput points after d */ + if (reginput == regbol || !iswordchar(reginput[-1])) + return 0; + if (reginput[0] && iswordchar(reginput[0])) + return 0; + break; + case ANY: + if (*reginput == '\0') + return 0; + reginput++; + break; + case IDENT: + if (!isidchar(*reginput)) + return 0; + reginput++; + break; + case WORD: + if (!iswordchar(*reginput)) + return 0; + reginput++; + break; + case FNAME: + if (!isfilechar(*reginput)) + return 0; + reginput++; + break; + case PRINT: + if (charsize(*reginput) != 1) + return 0; + reginput++; + break; + case SIDENT: + if (isdigit(*reginput) || !isidchar(*reginput)) + return 0; + reginput++; + break; + case SWORD: + if (isdigit(*reginput) || !iswordchar(*reginput)) + return 0; + reginput++; + break; + case SFNAME: + if (isdigit(*reginput) || !isfilechar(*reginput)) + return 0; + reginput++; + break; + case SPRINT: + if (isdigit(*reginput) || charsize(*reginput) != 1) + return 0; + reginput++; + break; + case EXACTLY:{ + register int len; + register char_u *opnd; + + opnd = OPERAND(scan); + /* Inline the first character, for speed. */ + if (*opnd != *reginput && (!reg_ic || TO_UPPER(*opnd) != TO_UPPER(*reginput))) + return 0; + len = STRLEN(opnd); + if (len > 1 && cstrncmp(opnd, reginput, len) != 0) + return 0; + reginput += len; + } + break; + case ANYOF: + if (*reginput == '\0' || cstrchr(OPERAND(scan), *reginput) == NULL) + return 0; + reginput++; + break; + case ANYBUT: + if (*reginput == '\0' || cstrchr(OPERAND(scan), *reginput) != NULL) + return 0; + reginput++; + break; + case NOTHING: + break; + case BACK: + break; + case MOPEN + 1: + case MOPEN + 2: + case MOPEN + 3: + case MOPEN + 4: + case MOPEN + 5: + case MOPEN + 6: + case MOPEN + 7: + case MOPEN + 8: + case MOPEN + 9:{ + register int no; + register char_u *save; + + no = OP(scan) - MOPEN; + save = regstartp[no]; + regstartp[no] = reginput; /* Tentatively */ +#ifdef DEBUG + printf("MOPEN %d pre @'%s' ('%s' )'%s'\n", + no, save, + regstartp[no] ? regstartp[no] : "NULL", + regendp[no] ? regendp[no] : "NULL"); +#endif + + if (regmatch(next)) { +#ifdef DEBUG + printf("MOPEN %d post @'%s' ('%s' )'%s'\n", + no, save, + regstartp[no] ? regstartp[no] : "NULL", + regendp[no] ? regendp[no] : "NULL"); +#endif + return 1; + } + regstartp[no] = save; /* We were wrong... */ + return 0; + } + /* break; Not Reached */ + case MCLOSE + 1: + case MCLOSE + 2: + case MCLOSE + 3: + case MCLOSE + 4: + case MCLOSE + 5: + case MCLOSE + 6: + case MCLOSE + 7: + case MCLOSE + 8: + case MCLOSE + 9:{ + register int no; + register char_u *save; + + no = OP(scan) - MCLOSE; + save = regendp[no]; + regendp[no] = reginput; /* Tentatively */ +#ifdef DEBUG + printf("MCLOSE %d pre @'%s' ('%s' )'%s'\n", + no, save, + regstartp[no] ? regstartp[no] : "NULL", + regendp[no] ? regendp[no] : "NULL"); +#endif + + if (regmatch(next)) { +#ifdef DEBUG + printf("MCLOSE %d post @'%s' ('%s' )'%s'\n", + no, save, + regstartp[no] ? regstartp[no] : "NULL", + regendp[no] ? regendp[no] : "NULL"); +#endif + return 1; + } + regendp[no] = save; /* We were wrong... */ + return 0; + } + /* break; Not Reached */ +#ifdef BACKREF + case BACKREF + 1: + case BACKREF + 2: + case BACKREF + 3: + case BACKREF + 4: + case BACKREF + 5: + case BACKREF + 6: + case BACKREF + 7: + case BACKREF + 8: + case BACKREF + 9:{ + register int no; + int len; + + no = OP(scan) - BACKREF; + if (regendp[no] != NULL) { + len = (int)(regendp[no] - regstartp[no]); + if (cstrncmp(regstartp[no], reginput, len) != 0) + return 0; + reginput += len; + } else { + /*emsg("backref to 0-repeat");*/ + /*return 0;*/ + } + } + break; +#endif + case BRANCH:{ + register char_u *save; + + if (OP(next) != BRANCH) /* No choice. */ + next = OPERAND(scan); /* Avoid recursion. */ + else { + do { + save = reginput; + if (regmatch(OPERAND(scan))) + return 1; + reginput = save; + scan = regnext(scan); + } while (scan != NULL && OP(scan) == BRANCH); + return 0; + /* NOTREACHED */ + } + } + break; + case STAR: + case PLUS:{ + register int nextch; + register int no; + register char_u *save; + register int min; + + /* + * Lookahead to avoid useless match attempts when we know + * what character comes next. + */ + nextch = '\0'; + if (OP(next) == EXACTLY) + { + nextch = *OPERAND(next); + if (reg_ic) + nextch = TO_UPPER(nextch); + } + min = (OP(scan) == STAR) ? 0 : 1; + save = reginput; + no = regrepeat(OPERAND(scan)); + while (no >= min) + { + /* If it could work, try it. */ + if (nextch == '\0' || (*reginput == nextch || + (reg_ic && TO_UPPER(*reginput) == nextch))) + if (regmatch(next)) + return 1; + /* Couldn't or didn't -- back up. */ + no--; + reginput = save + no; + } + return 0; + } + /* break; Not Reached */ + case END: + return 1; /* Success! */ + /* break; Not Reached */ + default: + emsg(e_re_corr); + return 0; + /* break; Not Reached */ + } + + scan = next; + } + + /* + * We get here only if there's trouble -- normally "case END" is the + * terminating point. + */ + emsg(e_re_corr); + return 0; +} + +/* + - regrepeat - repeatedly match something simple, report how many + */ +static int +regrepeat(p) + char_u *p; +{ + register int count = 0; + register char_u *scan; + register char_u *opnd; + + scan = reginput; + opnd = OPERAND(p); + switch (OP(p)) { + case ANY: + count = STRLEN(scan); + scan += count; + break; + case IDENT: + for (count = 0; isidchar(*scan); ++count) + ++scan; + break; + case WORD: + for (count = 0; iswordchar(*scan); ++count) + ++scan; + break; + case FNAME: + for (count = 0; isfilechar(*scan); ++count) + ++scan; + break; + case PRINT: + for (count = 0; charsize(*scan) == 1; ++count) + ++scan; + break; + case SIDENT: + for (count = 0; !isdigit(*scan) && isidchar(*scan); ++count) + ++scan; + break; + case SWORD: + for (count = 0; !isdigit(*scan) && iswordchar(*scan); ++count) + ++scan; + break; + case SFNAME: + for (count = 0; !isdigit(*scan) && isfilechar(*scan); ++count) + ++scan; + break; + case SPRINT: + for (count = 0; !isdigit(*scan) && charsize(*scan) == 1; ++count) + ++scan; + break; + case EXACTLY: + while (*opnd == *scan || (reg_ic && TO_UPPER(*opnd) == TO_UPPER(*scan))) + { + count++; + scan++; + } + break; + case ANYOF: + while (*scan != '\0' && cstrchr(opnd, *scan) != NULL) + { + count++; + scan++; + } + break; + case ANYBUT: + while (*scan != '\0' && cstrchr(opnd, *scan) == NULL) { + count++; + scan++; + } + break; + default: /* Oh dear. Called inappropriately. */ + emsg(e_re_corr); + count = 0; /* Best compromise. */ + break; + } + reginput = scan; + + return count; +} + +/* + - regnext - dig the "next" pointer out of a node + */ +static char_u * +regnext(p) + register char_u *p; +{ + register int offset; + + if (p == ®dummy) + return NULL; + + offset = NEXT(p); + if (offset == 0) + return NULL; + + if (OP(p) == BACK) + return p - offset; + else + return p + offset; +} + +#ifdef DEBUG + +/* + - regdump - dump a regexp onto stdout in vaguely comprehensible form + */ +void +regdump(r) + regexp *r; +{ + register char_u *s; + register int op = EXACTLY; /* Arbitrary non-END op. */ + register char_u *next; + + + s = r->program + 1; + while (op != END) { /* While that wasn't END last time... */ + op = OP(s); + printf("%2d%s", s - r->program, regprop(s)); /* Where, what. */ + next = regnext(s); + if (next == NULL) /* Next ptr. */ + printf("(0)"); + else + printf("(%d)", (s - r->program) + (next - s)); + s += 3; + if (op == ANYOF || op == ANYBUT || op == EXACTLY) { + /* Literal string, where present. */ + while (*s != '\0') { + putchar(*s); + s++; + } + s++; + } + putchar('\n'); + } + + /* Header fields of interest. */ + if (r->regstart != '\0') + printf("start `%c' ", r->regstart); + if (r->reganch) + printf("anchored "); + if (r->regmust != NULL) + printf("must have \"%s\"", r->regmust); + printf("\n"); +} + +/* + - regprop - printable representation of opcode + */ +static char_u * +regprop(op) + char_u *op; +{ + register char_u *p; + static char_u buf[50]; + + (void) strcpy(buf, ":"); + + switch (OP(op)) { + case BOL: + p = "BOL"; + break; + case EOL: + p = "EOL"; + break; + case ANY: + p = "ANY"; + break; + case IDENT: + p = "IDENT"; + break; + case WORD: + p = "WORD"; + break; + case FNAME: + p = "FNAME"; + break; + case PRINT: + p = "PRINT"; + break; + case SIDENT: + p = "SIDENT"; + break; + case SWORD: + p = "SWORD"; + break; + case SFNAME: + p = "SFNAME"; + break; + case SPRINT: + p = "SPRINT"; + break; + case ANYOF: + p = "ANYOF"; + break; + case ANYBUT: + p = "ANYBUT"; + break; + case BRANCH: + p = "BRANCH"; + break; + case EXACTLY: + p = "EXACTLY"; + break; + case NOTHING: + p = "NOTHING"; + break; + case BACK: + p = "BACK"; + break; + case END: + p = "END"; + break; + case MOPEN + 1: + case MOPEN + 2: + case MOPEN + 3: + case MOPEN + 4: + case MOPEN + 5: + case MOPEN + 6: + case MOPEN + 7: + case MOPEN + 8: + case MOPEN + 9: + sprintf(buf + STRLEN(buf), "MOPEN%d", OP(op) - MOPEN); + p = NULL; + break; + case MCLOSE + 1: + case MCLOSE + 2: + case MCLOSE + 3: + case MCLOSE + 4: + case MCLOSE + 5: + case MCLOSE + 6: + case MCLOSE + 7: + case MCLOSE + 8: + case MCLOSE + 9: + sprintf(buf + STRLEN(buf), "MCLOSE%d", OP(op) - MCLOSE); + p = NULL; + break; + case BACKREF + 1: + case BACKREF + 2: + case BACKREF + 3: + case BACKREF + 4: + case BACKREF + 5: + case BACKREF + 6: + case BACKREF + 7: + case BACKREF + 8: + case BACKREF + 9: + sprintf(buf + STRLEN(buf), "BACKREF%d", OP(op) - BACKREF); + p = NULL; + break; + case STAR: + p = "STAR"; + break; + case PLUS: + p = "PLUS"; + break; + default: + sprintf(buf + STRLEN(buf), "corrupt %d", OP(op)); + p = NULL; + break; + } + if (p != NULL) + (void) strcat(buf, p); + return buf; +} +#endif + +/* + * The following is provided for those people who do not have strcspn() in + * their C libraries. They should get off their butts and do something + * about it; at least one public-domain implementation of those (highly + * useful) string routines has been published on Usenet. + */ +#ifndef HAVE_STRCSPN +/* + * strcspn - find length of initial segment of s1 consisting entirely + * of characters not from s2 + */ + + static size_t +strcspn(s1, s2) + const char_u *s1; + const char_u *s2; +{ + register char_u *scan1; + register char_u *scan2; + register int count; + + count = 0; + for (scan1 = s1; *scan1 != '\0'; scan1++) { + for (scan2 = s2; *scan2 != '\0';) /* ++ moved down. */ + if (*scan1 == *scan2++) + return count; + count++; + } + return count; +} +#endif + +/* + * Compare two strings, ignore case if reg_ic set. + * Return 0 if strings match, non-zero otherwise. + */ + int +cstrncmp(s1, s2, n) + char_u *s1, *s2; + int n; +{ + if (!reg_ic) + return STRNCMP(s1, s2, (size_t)n); + + return vim_strnicmp(s1, s2, (size_t)n); +} + +/* + * cstrchr: This function is used a lot for simple searches, keep it fast! + */ + static char_u * +cstrchr(s, c) + char_u *s; + register int c; +{ + register char_u *p; + + if (!reg_ic) + return vim_strchr(s, c); + + c = TO_UPPER(c); + + for (p = s; *p; p++) + { + if (TO_UPPER(*p) == c) + return p; + } + return NULL; +} diff --git a/usr.bin/vim/regexp.h b/usr.bin/vim/regexp.h new file mode 100644 index 00000000000..156630ce78c --- /dev/null +++ b/usr.bin/vim/regexp.h @@ -0,0 +1,41 @@ +/* $OpenBSD: regexp.h,v 1.1.1.1 1996/09/07 21:40:27 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE + * + * This is NOT the original regular expression code as written by + * Henry Spencer. This code has been modified specifically for use + * with the VIM editor, and should not be used apart from compiling + * VIM. If you want a good regular expression library, get the + * original code. + * + * NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE + * + * Definitions etc. for regexp(3) routines. + * + * Caveat: this is V8 regexp(3) [actually, a reimplementation thereof], + * not the System V one. + */ + +#ifndef _REGEXP_H +#define _REGEXP_H + +#define NSUBEXP 10 +typedef struct regexp +{ + char_u *startp[NSUBEXP]; + char_u *endp[NSUBEXP]; + char_u regstart; /* Internal use only. */ + char_u reganch; /* Internal use only. */ + char_u *regmust; /* Internal use only. */ + int regmlen; /* Internal use only. */ + char_u program[1]; /* Unwarranted chumminess with compiler. */ +} regexp; + +/* + * The first byte of the regexp internal "program" is actually this magic + * number; the start node begins in the second byte. + */ + +#define MAGIC 0234 + +#endif /* _REGEXP_H */ diff --git a/usr.bin/vim/regsub.c b/usr.bin/vim/regsub.c new file mode 100644 index 00000000000..6ed82170f18 --- /dev/null +++ b/usr.bin/vim/regsub.c @@ -0,0 +1,354 @@ +/* $OpenBSD: regsub.c,v 1.1.1.1 1996/09/07 21:40:25 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE + * + * This is NOT the original regular expression code as written by + * Henry Spencer. This code has been modified specifically for use + * with the VIM editor, and should not be used apart from compiling + * VIM. If you want a good regular expression library, get the + * original code. The copyright notice that follows is from the + * original. + * + * NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE + * + * vim_regsub + * + * Copyright (c) 1986 by University of Toronto. + * Written by Henry Spencer. Not derived from licensed software. + * + * Permission is granted to anyone to use this software for any + * purpose on any computer system, and to redistribute it freely, + * subject to the following restrictions: + * + * 1. The author is not responsible for the consequences of use of + * this software, no matter how awful, even if they arise + * from defects in it. + * + * 2. The origin of this software must not be misrepresented, either + * by explicit claim or by omission. + * + * 3. Altered versions must be plainly marked as such, and must not + * be misrepresented as being the original software. + * + * $Log: regsub.c,v $ + * Revision 1.1.1.1 1996/09/07 21:40:25 downsj + * Initial import of vim 4.2. + * + * This is meant to replace nvi in the tree. Vim, in general, works better, + * provides more features, and does not suffer from the license problems + * being imposed upon nvi. + * + * On the other hand, vim lacks a non-visual ex mode, in addition to open mode. + * + * This includes the GUI (X11) code, but doesn't try to compile it. + * + * Revision 1.2 88/04/28 08:11:25 tony + * First modification of the regexp library. Added an external variable + * 'reg_ic' which can be set to indicate that case should be ignored. + * Added a new parameter to vim_regexec() to indicate that the given string + * comes from the beginning of a line and is thus eligible to match + * 'beginning-of-line'. + * + * Revisions by Olaf 'Rhialto' Seibert, rhialto@mbfys.kun.nl: + * Changes for vi: (the semantics of several things were rather different) + * - Added lexical analyzer, because in vi magicness of characters + * is rather difficult, and may change over time. + * - Added support for \< \> \1-\9 and ~ + * - Left some magic stuff in, but only backslashed: \| \+ + * - * and \+ still work after \) even though they shouldn't. + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" + +#ifndef __ARGS +# define __ARGS(a) a +#endif + +#include +#include "regexp.h" + +#ifdef LATTICE +# include /* for size_t */ +#endif + +#ifndef CHARBITS +#define UCHARAT(p) ((int)*(char_u *)(p)) +#else +#define UCHARAT(p) ((int)*(p)&CHARBITS) +#endif + +extern char_u *reg_prev_sub; + + /* This stuff below really confuses cc on an SGI -- webb */ +#ifdef __sgi +# undef __ARGS +# define __ARGS(x) () +#endif + + /* + * We should define ftpr as a pointer to a function returning a pointer to + * a function returning a pointer to a function ... + * This is impossible, so we declare a pointer to a function returning a + * pointer to a function returning void. This should work for all compilers. + */ +typedef void (*(*fptr) __ARGS((char_u *, int)))(); + +static fptr do_upper __ARGS((char_u *, int)); +static fptr do_Upper __ARGS((char_u *, int)); +static fptr do_lower __ARGS((char_u *, int)); +static fptr do_Lower __ARGS((char_u *, int)); + + static fptr +do_upper(d, c) + char_u *d; + int c; +{ + *d = TO_UPPER(c); + + return (fptr)NULL; +} + + static fptr +do_Upper(d, c) + char_u *d; + int c; +{ + *d = TO_UPPER(c); + + return (fptr)do_Upper; +} + + static fptr +do_lower(d, c) + char_u *d; + int c; +{ + *d = TO_LOWER(c); + + return (fptr)NULL; +} + + static fptr +do_Lower(d, c) + char_u *d; + int c; +{ + *d = TO_LOWER(c); + + return (fptr)do_Lower; +} + +/* + * regtilde: replace tildes in the pattern by the old pattern + * + * Short explanation of the tilde: it stands for the previous replacement + * pattern. If that previous pattern also contains a ~ we should go back + * a step further... but we insert the previous pattern into the current one + * and remember that. + * This still does not handle the case where "magic" changes. TODO? + * + * New solution: The tilde's are parsed once before the first call to + * vim_regsub(). In the old solution (tilde handled in regsub()) is was + * possible to get an endless loop. + */ + char_u * +regtilde(source, magic) + char_u *source; + int magic; +{ + char_u *newsub = NULL; + char_u *tmpsub; + char_u *p; + int len; + int prevlen; + + for (p = source; *p; ++p) + { + if ((*p == '~' && magic) || (*p == '\\' && *(p + 1) == '~' && !magic)) + { + if (reg_prev_sub) + { + /* length = len(current) - 1 + len(previous) + 1 */ + prevlen = STRLEN(reg_prev_sub); + tmpsub = alloc((unsigned)(STRLEN(source) + prevlen)); + if (tmpsub) + { + /* copy prefix */ + len = (int)(p - source); /* not including ~ */ + STRNCPY(tmpsub, source, len); + /* interpretate tilde */ + STRCPY(tmpsub + len, reg_prev_sub); + /* copy postfix */ + if (!magic) + ++p; /* back off \ */ + STRCAT(tmpsub + len, p + 1); + + vim_free(newsub); + newsub = tmpsub; + p = newsub + len + prevlen; + } + } + else if (magic) + STRCPY(p, p + 1); /* remove '~' */ + else + STRCPY(p, p + 2); /* remove '\~' */ + } + else if (*p == '\\' && p[1]) /* skip escaped characters */ + ++p; + } + + vim_free(reg_prev_sub); + if (newsub) + { + source = newsub; + reg_prev_sub = newsub; + } + else + reg_prev_sub = strsave(source); + return source; +} + +/* + - vim_regsub - perform substitutions after a regexp match + * + * If copy is TRUE really copy into dest, otherwise dest is not written to. + * + * Returns the size of the replacement, including terminating \0. + */ + int +vim_regsub(prog, source, dest, copy, magic) + regexp *prog; + char_u *source; + char_u *dest; + int copy; + int magic; +{ + register char_u *src; + register char_u *dst; + register char_u *s; + register int c; + register int no; + fptr func = (fptr)NULL; + + if (prog == NULL || source == NULL || dest == NULL) + { + emsg(e_null); + return 0; + } + if (UCHARAT(prog->program) != MAGIC) + { + emsg(e_re_corr); + return 0; + } + src = source; + dst = dest; + + while ((c = *src++) != '\0') + { + no = -1; + if (c == '&' && magic) + no = 0; + else if (c == '\\' && *src != NUL) + { + if (*src == '&' && !magic) + { + ++src; + no = 0; + } + else if ('0' <= *src && *src <= '9') + { + no = *src++ - '0'; + } + else if (vim_strchr((char_u *)"uUlLeE", *src)) + { + switch (*src++) + { + case 'u': func = (fptr)do_upper; + continue; + case 'U': func = (fptr)do_Upper; + continue; + case 'l': func = (fptr)do_lower; + continue; + case 'L': func = (fptr)do_Lower; + continue; + case 'e': + case 'E': func = (fptr)NULL; + continue; + } + } + } + if (no < 0) /* Ordinary character. */ + { + if (c == '\\' && *src != NUL) + { + /* Check for abbreviations -- webb */ + switch (*src) + { + case 'r': c = CR; break; + case 'n': c = NL; break; + case 't': c = TAB; break; + /* Oh no! \e already has meaning in subst pat :-( */ + /* case 'e': c = ESC; break; */ + case 'b': c = Ctrl('H'); break; + default: + /* Normal character, not abbreviation */ + c = *src; + break; + } + src++; + } + if (copy) + { + if (func == (fptr)NULL) /* just copy */ + *dst = c; + else /* change case */ + func = (fptr)(func(dst, c)); + /* Turbo C complains without the typecast */ + } + dst++; + } + else if (prog->startp[no] != NULL && prog->endp[no] != NULL) + { + for (s = prog->startp[no]; s < prog->endp[no]; ++s) + { + if (copy && *s == '\0') /* we hit NUL. */ + { + emsg(e_re_damg); + goto exit; + } + /* + * Insert a CTRL-V in front of a CR, otherwise + * it will be replaced by a line break. + */ + if (*s == CR) + { + if (copy) + { + dst[0] = Ctrl('V'); + dst[1] = CR; + } + dst += 2; + } + else + { + if (copy) + { + if (func == (fptr)NULL) /* just copy */ + *dst = *s; + else /* change case */ + func = (fptr)(func(dst, *s)); + /* Turbo C complains without the typecast */ + } + ++dst; + } + } + } + } + if (copy) + *dst = '\0'; + +exit: + return (int)((dst - dest) + 1); +} diff --git a/usr.bin/vim/screen.c b/usr.bin/vim/screen.c new file mode 100644 index 00000000000..0c67873b55d --- /dev/null +++ b/usr.bin/vim/screen.c @@ -0,0 +1,3363 @@ +/* $OpenBSD: screen.c,v 1.1.1.1 1996/09/07 21:40:24 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * screen.c: code for displaying on the screen + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" +#include "ops.h" /* For op_inclusive */ + +char *tgoto __PARMS((char *cm, int col, int line)); + +static int canopt; /* TRUE when cursor goto can be optimized */ +static int attributes = 0; /* current attributes for screen character*/ +static int highlight_attr = 0; /* attributes when highlighting on */ +#ifdef RIGHTLEFT +static int rightleft = 0; /* set to 1 for right to left in screen_fill */ +#endif + +static int win_line __ARGS((WIN *, linenr_t, int, int)); +static void comp_Botline_sub __ARGS((WIN *wp, linenr_t lnum, int done)); +static void screen_char __ARGS((char_u *, int, int)); +static void screenclear2 __ARGS((void)); +static void lineclear __ARGS((char_u *p)); +static int screen_ins_lines __ARGS((int, int, int, int)); + +/* + * updateline() - like updateScreen() but only for cursor line + * + * Check if the size of the cursor line has changed. If it did change, lines + * below the cursor will move up or down and we need to call the routine + * updateScreen() to examine the entire screen. + */ + void +updateline() +{ + int row; + int n; + + if (!screen_valid(TRUE)) + return; + + if (must_redraw) /* must redraw whole screen */ + { + updateScreen(must_redraw); + return; + } + + if (RedrawingDisabled) + { + must_redraw = NOT_VALID; /* remember to update later */ + return; + } + + cursor_off(); + + (void)set_highlight('v'); + row = win_line(curwin, curwin->w_cursor.lnum, + curwin->w_cline_row, curwin->w_height); + + if (row == curwin->w_height + 1) /* line too long for window */ + { + /* window needs to be scrolled up to show the cursor line */ + if (curwin->w_topline < curwin->w_cursor.lnum) + ++curwin->w_topline; + updateScreen(VALID_TO_CURSCHAR); + cursupdate(); + } + else if (!dollar_vcol) + { + n = row - curwin->w_cline_row; + if (n != curwin->w_cline_height) /* line changed size */ + { + if (n < curwin->w_cline_height) /* got smaller: delete lines */ + win_del_lines(curwin, row, + curwin->w_cline_height - n, FALSE, TRUE); + else /* got bigger: insert lines */ + win_ins_lines(curwin, + curwin->w_cline_row + curwin->w_cline_height, + n - curwin->w_cline_height, FALSE, TRUE); + updateScreen(VALID_TO_CURSCHAR); + } + else if (clear_cmdline || redraw_cmdline) + showmode(); /* clear cmdline, show mode and ruler */ + } +} + +/* + * update all windows that are editing the current buffer + */ + void +update_curbuf(type) + int type; +{ + WIN *wp; + + for (wp = firstwin; wp; wp = wp->w_next) + if (wp->w_buffer == curbuf && wp->w_redr_type < type) + wp->w_redr_type = type; + updateScreen(type); +} + +/* + * updateScreen() + * + * Based on the current value of curwin->w_topline, transfer a screenfull + * of stuff from Filemem to NextScreen, and update curwin->w_botline. + */ + + void +updateScreen(type) + int type; +{ + WIN *wp; + + if (!screen_valid(TRUE)) + return; + + dollar_vcol = 0; + + if (must_redraw) + { + if (type < must_redraw) /* use maximal type */ + type = must_redraw; + must_redraw = 0; + } + + if (type == CURSUPD) /* update cursor and then redraw NOT_VALID */ + { + curwin->w_lsize_valid = 0; + cursupdate(); /* will call updateScreen() */ + return; + } + if (curwin->w_lsize_valid == 0 && type < NOT_VALID) + type = NOT_VALID; + + if (RedrawingDisabled) + { + must_redraw = type; /* remember type for next time */ + curwin->w_redr_type = type; + curwin->w_lsize_valid = 0; /* don't use w_lsize[] now */ + return; + } + + /* + * if the screen was scrolled up when displaying a message, scroll it down + */ + if (msg_scrolled) + { + clear_cmdline = TRUE; + if (msg_scrolled > Rows - 5) /* clearing is faster */ + type = CLEAR; + else if (type != CLEAR) + { + if (screen_ins_lines(0, 0, msg_scrolled, (int)Rows) == FAIL) + type = CLEAR; + win_rest_invalid(firstwin); /* should do only first/last few */ + } + msg_scrolled = 0; + need_wait_return = FALSE; + } + + /* + * reset cmdline_row now (may have been changed temporarily) + */ + compute_cmdrow(); + + if (type == CLEAR) /* first clear screen */ + { + screenclear(); /* will reset clear_cmdline */ + type = NOT_VALID; + } + + if (clear_cmdline) /* first clear cmdline */ + { + if (emsg_on_display) + { + mch_delay(1000L, TRUE); + emsg_on_display = FALSE; + } + msg_row = cmdline_row; + msg_col = 0; + msg_clr_eos(); /* will reset clear_cmdline */ + } + +/* return if there is nothing to do */ + if (!((type == VALID && curwin->w_topline == curwin->w_lsize_lnum[0]) || + (type == INVERTED && + curwin->w_old_cursor_lnum == curwin->w_cursor.lnum && + curwin->w_old_cursor_vcol == curwin->w_virtcol && + curwin->w_old_curswant == curwin->w_curswant))) + { + /* + * go from top to bottom through the windows, redrawing the ones that + * need it + */ + curwin->w_redr_type = type; + cursor_off(); + for (wp = firstwin; wp; wp = wp->w_next) + { + if (wp->w_redr_type) + win_update(wp); + if (wp->w_redr_status) + win_redr_status(wp); + } + } + if (redraw_cmdline) + showmode(); +} + +#ifdef USE_GUI +/* + * Update a single window, its status line and maybe the command line msg. + * Used for the GUI scrollbar. + */ + void +updateWindow(wp) + WIN *wp; +{ + win_update(wp); + if (wp->w_redr_status) + win_redr_status(wp); + if (redraw_cmdline) + showmode(); +} +#endif + +/* + * update a single window + * + * This may cause the windows below it also to be redrawn + */ + void +win_update(wp) + WIN *wp; +{ + int type = wp->w_redr_type; + register int row; + register int endrow; + linenr_t lnum; + linenr_t lastline = 0; /* only valid if endrow != Rows -1 */ + int done; /* if TRUE, we hit the end of the file */ + int didline; /* if TRUE, we finished the last line */ + int srow = 0; /* starting row of the current line */ + int idx; + int i; + long j; + + if (type == NOT_VALID) + { + wp->w_redr_status = TRUE; + wp->w_lsize_valid = 0; + } + + idx = 0; + row = 0; + lnum = wp->w_topline; + + /* The number of rows shown is w_height. */ + /* The default last row is the status/command line. */ + endrow = wp->w_height; + + if (type == VALID || type == VALID_TO_CURSCHAR) + { + /* + * We handle two special cases: + * 1: we are off the top of the screen by a few lines: scroll down + * 2: wp->w_topline is below wp->w_lsize_lnum[0]: may scroll up + */ + if (wp->w_topline < wp->w_lsize_lnum[0]) /* may scroll down */ + { + j = wp->w_lsize_lnum[0] - wp->w_topline; + if (j < wp->w_height - 2) /* not too far off */ + { + lastline = wp->w_lsize_lnum[0] - 1; + i = plines_m_win(wp, wp->w_topline, lastline); + if (i < wp->w_height - 2) /* less than a screen off */ + { + /* + * Try to insert the correct number of lines. + * If not the last window, delete the lines at the bottom. + * win_ins_lines may fail. + */ + if (win_ins_lines(wp, 0, i, FALSE, wp == firstwin) == OK && + wp->w_lsize_valid) + { + endrow = i; + + if ((wp->w_lsize_valid += j) > wp->w_height) + wp->w_lsize_valid = wp->w_height; + for (idx = wp->w_lsize_valid; idx - j >= 0; idx--) + { + wp->w_lsize_lnum[idx] = wp->w_lsize_lnum[idx - j]; + wp->w_lsize[idx] = wp->w_lsize[idx - j]; + } + idx = 0; + } + } + else if (lastwin == firstwin) + screenclear(); /* far off: clearing the screen is faster */ + } + else if (lastwin == firstwin) + screenclear(); /* far off: clearing the screen is faster */ + } + else /* may scroll up */ + { + j = -1; + /* try to find wp->w_topline in wp->w_lsize_lnum[] */ + for (i = 0; i < wp->w_lsize_valid; i++) + { + if (wp->w_lsize_lnum[i] == wp->w_topline) + { + j = i; + break; + } + row += wp->w_lsize[i]; + } + if (j == -1) /* wp->w_topline is not in wp->w_lsize_lnum */ + { + row = 0; + if (lastwin == firstwin) + screenclear(); /* far off: clearing the screen is faster */ + } + else + { + /* + * Try to delete the correct number of lines. + * wp->w_topline is at wp->w_lsize_lnum[i]. + */ + if ((row == 0 || win_del_lines(wp, 0, row, + FALSE, wp == firstwin) == OK) && wp->w_lsize_valid) + { + srow = row; + row = 0; + for (;;) + { + if (type == VALID_TO_CURSCHAR && + lnum == wp->w_cursor.lnum) + break; + if (row + srow + (int)wp->w_lsize[j] >= wp->w_height) + break; + wp->w_lsize[idx] = wp->w_lsize[j]; + wp->w_lsize_lnum[idx] = lnum++; + + row += wp->w_lsize[idx++]; + if ((int)++j >= wp->w_lsize_valid) + break; + } + wp->w_lsize_valid = idx; + } + else + row = 0; /* update all lines */ + } + } + if (endrow == wp->w_height && idx == 0) /* no scrolling */ + wp->w_lsize_valid = 0; + } + + done = didline = FALSE; + + if (VIsual_active) /* check if we are updating the inverted part */ + { + linenr_t from, to; + + /* find the line numbers that need to be updated */ + if (curwin->w_cursor.lnum < wp->w_old_cursor_lnum) + { + from = curwin->w_cursor.lnum; + to = wp->w_old_cursor_lnum; + } + else + { + from = wp->w_old_cursor_lnum; + to = curwin->w_cursor.lnum; + } + /* if VIsual changed, update the maximal area */ + if (VIsual.lnum != wp->w_old_visual_lnum) + { + if (wp->w_old_visual_lnum < from) + from = wp->w_old_visual_lnum; + if (wp->w_old_visual_lnum > to) + to = wp->w_old_visual_lnum; + if (VIsual.lnum < from) + from = VIsual.lnum; + if (VIsual.lnum > to) + to = VIsual.lnum; + } + /* if in block mode and changed column or wp->w_curswant: update all + * lines */ + if (VIsual_mode == Ctrl('V') && + (curwin->w_virtcol != wp->w_old_cursor_vcol || + wp->w_curswant != wp->w_old_curswant)) + { + if (from > VIsual.lnum) + from = VIsual.lnum; + if (to < VIsual.lnum) + to = VIsual.lnum; + } + + if (from < wp->w_topline) + from = wp->w_topline; + if (from >= wp->w_botline) + from = wp->w_botline - 1; + if (to >= wp->w_botline) + to = wp->w_botline - 1; + + /* find the minimal part to be updated */ + if (type == INVERTED) + { + while (lnum < from) /* find start */ + { + row += wp->w_lsize[idx++]; + ++lnum; + } + srow = row; + for (j = idx; j < wp->w_lsize_valid; ++j) /* find end */ + { + if (wp->w_lsize_lnum[j] == to + 1) + { + endrow = srow; + break; + } + srow += wp->w_lsize[j]; + } + } + + /* if we update the lines between from and to set old_cursor */ + if (type == INVERTED || (lnum <= from && + (endrow == wp->w_height || lastline >= to))) + { + wp->w_old_cursor_lnum = curwin->w_cursor.lnum; + wp->w_old_cursor_vcol = curwin->w_virtcol; + wp->w_old_visual_lnum = VIsual.lnum; + wp->w_old_curswant = wp->w_curswant; + } + } + else + { + wp->w_old_cursor_lnum = 0; + wp->w_old_visual_lnum = 0; + } + + (void)set_highlight('v'); + + /* + * Update the screen rows from "row" to "endrow". + * Start at line "lnum" which is at wp->w_lsize_lnum[idx]. + */ + for (;;) + { + if (lnum > wp->w_buffer->b_ml.ml_line_count) + { + done = TRUE; /* hit the end of the file */ + break; + } + srow = row; + row = win_line(wp, lnum, srow, endrow); + if (row > endrow) /* past end of screen */ + { /* we may need the size of that */ + wp->w_lsize[idx] = plines_win(wp, lnum); + wp->w_lsize_lnum[idx++] = lnum; /* too long line later on */ + break; + } + + wp->w_lsize[idx] = row - srow; + wp->w_lsize_lnum[idx++] = lnum; + if (++lnum > wp->w_buffer->b_ml.ml_line_count) + { + done = TRUE; + break; + } + + if (row == endrow) + { + didline = TRUE; + break; + } + } + if (idx > wp->w_lsize_valid) + wp->w_lsize_valid = idx; + + /* Do we have to do off the top of the screen processing ? */ + if (endrow != wp->w_height) + { + row = 0; + for (idx = 0; idx < wp->w_lsize_valid && row < wp->w_height; idx++) + row += wp->w_lsize[idx]; + + if (row < wp->w_height) + { + done = TRUE; + } + else if (row > wp->w_height) /* Need to blank out the last line */ + { + lnum = wp->w_lsize_lnum[idx - 1]; + srow = row - wp->w_lsize[idx - 1]; + didline = FALSE; + } + else + { + lnum = wp->w_lsize_lnum[idx - 1] + 1; + didline = TRUE; + } + } + + wp->w_empty_rows = 0; + /* + * If we didn't hit the end of the file, and we didn't finish the last + * line we were working on, then the line didn't fit. + */ + if (!done && !didline) + { + if (lnum == wp->w_topline) + { + /* + * Single line that does not fit! + * Fill last line with '@' characters. + */ + screen_fill(wp->w_winpos + wp->w_height - 1, + wp->w_winpos + wp->w_height, 0, (int)Columns, '@', '@'); + wp->w_botline = lnum + 1; + } + else + { + /* + * Clear the rest of the screen and mark the unused lines. + */ +#ifdef RIGHTLEFT + if (wp->w_p_rl) + rightleft = 1; +#endif + screen_fill(wp->w_winpos + srow, + wp->w_winpos + wp->w_height, 0, (int)Columns, '@', ' '); +#ifdef RIGHTLEFT + rightleft = 0; +#endif + wp->w_botline = lnum; + wp->w_empty_rows = wp->w_height - srow; + } + } + else + { + /* make sure the rest of the screen is blank */ + /* put '~'s on rows that aren't part of the file. */ +#ifdef RIGHTLEFT + if (wp->w_p_rl) + rightleft = 1; +#endif + screen_fill(wp->w_winpos + row, + wp->w_winpos + wp->w_height, 0, (int)Columns, '~', ' '); +#ifdef RIGHTLEFT + rightleft = 0; +#endif + wp->w_empty_rows = wp->w_height - row; + + if (done) /* we hit the end of the file */ + wp->w_botline = wp->w_buffer->b_ml.ml_line_count + 1; + else + wp->w_botline = lnum; + } + + wp->w_redr_type = 0; +} + +/* + * mark all status lines for redraw; used after first :cd + */ + void +status_redraw_all() +{ + WIN *wp; + + for (wp = firstwin; wp; wp = wp->w_next) + wp->w_redr_status = TRUE; + updateScreen(NOT_VALID); +} + +/* + * Redraw the status line of window wp. + * + * If inversion is possible we use it. Else '=' characters are used. + */ + void +win_redr_status(wp) + WIN *wp; +{ + int row; + char_u *p; + int len; + int fillchar; + + if (wp->w_status_height) /* if there is a status line */ + { + if (set_highlight('s') == OK) /* can highlight */ + { + fillchar = ' '; + start_highlight(); + } + else /* can't highlight, use '=' */ + fillchar = '='; + + p = wp->w_buffer->b_xfilename; + if (p == NULL) + STRCPY(NameBuff, "[No File]"); + else + { + home_replace(wp->w_buffer, p, NameBuff, MAXPATHL); + trans_characters(NameBuff, MAXPATHL); + } + p = NameBuff; + len = STRLEN(p); + + if (wp->w_buffer->b_help || wp->w_buffer->b_changed || + wp->w_buffer->b_p_ro) + *(p + len++) = ' '; + if (wp->w_buffer->b_help) + { + STRCPY(p + len, "[help]"); + len += 6; + } + if (wp->w_buffer->b_changed) + { + STRCPY(p + len, "[+]"); + len += 3; + } + if (wp->w_buffer->b_p_ro) + { + STRCPY(p + len, "[RO]"); + len += 4; + } + + if (len > ru_col - 1) + { + p += len - (ru_col - 1); + *p = '<'; + len = ru_col - 1; + } + + row = wp->w_winpos + wp->w_height; + screen_msg(p, row, 0); + screen_fill(row, row + 1, len, ru_col, fillchar, fillchar); + + stop_highlight(); + win_redr_ruler(wp, TRUE); + } + else /* no status line, can only be last window */ + redraw_cmdline = TRUE; + wp->w_redr_status = FALSE; +} + +/* + * display line "lnum" of window 'wp' on the screen + * Start at row "startrow", stop when "endrow" is reached. + * Return the number of last row the line occupies. + */ + + static int +win_line(wp, lnum, startrow, endrow) + WIN *wp; + linenr_t lnum; + int startrow; + int endrow; +{ + char_u *screenp; + int c; + int col; /* visual column on screen */ + long vcol; /* visual column for tabs */ + int row; /* row in the window, excl w_winpos */ + int screen_row; /* row on the screen, incl w_winpos */ + char_u *ptr; + char_u extra[16]; /* "%ld" must fit in here */ + char_u *p_extra; + char_u *showbreak = NULL; + int n_extra; + int n_spaces = 0; + + int fromcol, tocol; /* start/end of inverting */ + int noinvcur = FALSE; /* don't invert the cursor */ + FPOS *top, *bot; + + if (startrow > endrow) /* past the end already! */ + return startrow; + + row = startrow; + screen_row = row + wp->w_winpos; + col = 0; + vcol = 0; + fromcol = -10; + tocol = MAXCOL; + canopt = TRUE; + + /* + * handle visual active in this window + */ + if (VIsual_active && wp->w_buffer == curwin->w_buffer) + { + /* Visual is after curwin->w_cursor */ + if (ltoreq(curwin->w_cursor, VIsual)) + { + top = &curwin->w_cursor; + bot = &VIsual; + } + else /* Visual is before curwin->w_cursor */ + { + top = &VIsual; + bot = &curwin->w_cursor; + } + if (VIsual_mode == Ctrl('V')) /* block mode */ + { + if (lnum >= top->lnum && lnum <= bot->lnum) + { + colnr_t from, to; + + getvcol(wp, top, (colnr_t *)&fromcol, NULL, (colnr_t *)&tocol); + getvcol(wp, bot, &from, NULL, &to); + if ((int)from < fromcol) + fromcol = from; + if ((int)to > tocol) + tocol = to; + ++tocol; + + if (wp->w_curswant == MAXCOL) + tocol = MAXCOL; + } + } + else /* non-block mode */ + { + if (lnum > top->lnum && lnum <= bot->lnum) + fromcol = 0; + else if (lnum == top->lnum) + getvcol(wp, top, (colnr_t *)&fromcol, NULL, NULL); + if (lnum == bot->lnum) + { + getvcol(wp, bot, NULL, NULL, (colnr_t *)&tocol); + ++tocol; + } + + if (VIsual_mode == 'V') /* linewise */ + { + if (fromcol > 0) + fromcol = 0; + tocol = MAXCOL; + } + } + /* if the cursor can't be switched off, don't invert the + * character where the cursor is */ +#ifndef MSDOS + if (!highlight_match && *T_VI == NUL && + lnum == curwin->w_cursor.lnum && wp == curwin) + noinvcur = TRUE; +#endif + + if (tocol <= (int)wp->w_leftcol) /* inverting is left of screen */ + fromcol = 0; + /* start of invert is left of screen */ + else if (fromcol >= 0 && fromcol < (int)wp->w_leftcol) + fromcol = wp->w_leftcol; + + /* if inverting in this line, can't optimize cursor positioning */ + if (fromcol >= 0) + canopt = FALSE; + } + /* + * handle incremental search position highlighting + */ + else if (highlight_match && wp == curwin && search_match_len) + { + if (lnum == curwin->w_cursor.lnum) + { + getvcol(curwin, &(curwin->w_cursor), + (colnr_t *)&fromcol, NULL, NULL); + curwin->w_cursor.col += search_match_len; + getvcol(curwin, &(curwin->w_cursor), + (colnr_t *)&tocol, NULL, NULL); + curwin->w_cursor.col -= search_match_len; + canopt = FALSE; + if (fromcol == tocol) /* do at least one character */ + tocol = fromcol + 1; /* happens when past end of line */ + } + } + + ptr = ml_get_buf(wp->w_buffer, lnum, FALSE); + if (!wp->w_p_wrap) /* advance to first character to be displayed */ + { + while ((colnr_t)vcol < wp->w_leftcol && *ptr) + vcol += win_chartabsize(wp, *ptr++, (colnr_t)vcol); + if ((colnr_t)vcol > wp->w_leftcol) + { + n_spaces = vcol - wp->w_leftcol; /* begin with some spaces */ + vcol = wp->w_leftcol; + } + } + screenp = LinePointers[screen_row]; +#ifdef RIGHTLEFT + if (wp->w_p_rl) + { + col = Columns - 1; /* col follows screenp here */ + screenp += Columns - 1; + } +#endif + if (wp->w_p_nu) + { +#ifdef RIGHTLEFT + if (wp->w_p_rl) /* reverse line numbers */ + { + char_u *c1, *c2, t; + + sprintf((char *)extra, " %-7ld", (long)lnum); + for (c1 = extra, c2 = extra + STRLEN(extra) - 1; c1 < c2; + c1++, c2--) + { + t = *c1; + *c1 = *c2; + *c2 = t; + } + } + else +#endif + sprintf((char *)extra, "%7ld ", (long)lnum); + p_extra = extra; + n_extra = 8; + vcol -= 8; /* so vcol is 0 when line number has been printed */ + } + else + { + p_extra = NULL; + n_extra = 0; + } + for (;;) + { + if (!canopt) /* Visual or match highlighting in this line */ + { + if (((vcol == fromcol && !(noinvcur && + (colnr_t)vcol == wp->w_virtcol)) || + (noinvcur && (colnr_t)vcol == wp->w_virtcol + 1 && + vcol >= fromcol)) && vcol < tocol) + start_highlight(); /* start highlighting */ + else if (attributes && (vcol == tocol || + (noinvcur && (colnr_t)vcol == wp->w_virtcol))) + stop_highlight(); /* stop highlighting */ + } + + /* Get the next character to put on the screen. */ + + /* + * if 'showbreak' is set it contains the characters to put at the + * start of each broken line + */ + if ( +#ifdef RIGHTLEFT + (wp->w_p_rl ? col == -1 : col == Columns) +#else + col == Columns +#endif + && (*ptr != NUL || (wp->w_p_list && n_extra == 0) || + (n_extra && *p_extra) || n_spaces) && + vcol != 0 && STRLEN(p_sbr) != 0) + showbreak = p_sbr; + if (showbreak != NULL) + { + c = *showbreak++; + if (*showbreak == NUL) + showbreak = NULL; + } + /* + * The 'extra' array contains the extra stuff that is inserted to + * represent special characters (non-printable stuff). + */ + else if (n_extra) + { + c = *p_extra++; + n_extra--; + } + else if (n_spaces) + { + c = ' '; + n_spaces--; + } + else + { + c = *ptr++; + /* + * Found last space before word: check for line break + */ + if (wp->w_p_lbr && isbreak(c) && !isbreak(*ptr) && !wp->w_p_list) + { + n_spaces = win_lbr_chartabsize(wp, ptr - 1, + (colnr_t)vcol, NULL) - 1; + if (vim_iswhite(c)) + c = ' '; + } + else if (!isprintchar(c)) + { + /* + * when getting a character from the file, we may have to turn + * it into something else on the way to putting it into + * 'NextScreen'. + */ + if (c == TAB && !wp->w_p_list) + { + /* tab amount depends on current column */ + n_spaces = (int)wp->w_buffer->b_p_ts - + vcol % (int)wp->w_buffer->b_p_ts - 1; + c = ' '; + } + else if (c == NUL && wp->w_p_list) + { + p_extra = (char_u *)""; + n_extra = 1; + c = '$'; + --ptr; /* put it back at the NUL */ + } + else if (c != NUL) + { + p_extra = transchar(c); + n_extra = charsize(c) - 1; + c = *p_extra++; + } + } + } + + if (c == NUL) + { + if (attributes) + { + /* invert at least one char, used for Visual and empty line or + * highlight match at end of line. If it's beyond the last + * char on the screen, just overwrite that one (tricky!) */ + if (vcol == fromcol) + { +#ifdef RIGHTLEFT + if (wp->w_p_rl) + { + if (col < 0) + { + ++screenp; + ++col; + } + } + else +#endif + { + if (col >= Columns) + { + --screenp; + --col; + } + } + if (*screenp != ' ' || *(screenp + Columns) != attributes) + { + *screenp = ' '; + *(screenp + Columns) = attributes; + screen_char(screenp, screen_row, col); + } +#ifdef RIGHTLEFT + if (wp->w_p_rl) + { + --screenp; + --col; + } + else +#endif + { + ++screenp; + ++col; + } + } + stop_highlight(); + } + /* + * blank out the rest of this row, if necessary + */ +#ifdef RIGHTLEFT + if (wp->w_p_rl) + { + while (col >= 0 && *screenp == ' ' && + *(screenp + Columns) == 0) + { + --screenp; + --col; + } + if (col >= 0) + screen_fill(screen_row, screen_row + 1, + 0, col + 1, ' ', ' '); + } + else +#endif + { + while (col < Columns && *screenp == ' ' && + *(screenp + Columns) == 0) + { + ++screenp; + ++col; + } + if (col < Columns) + screen_fill(screen_row, screen_row + 1, + col, (int)Columns, ' ', ' '); + } + row++; + break; + } + if ( +#ifdef RIGHTLEFT + wp->w_p_rl ? (col < 0) : +#endif + (col >= Columns) + ) + { + col = 0; + ++row; + ++screen_row; + if (!wp->w_p_wrap) + break; + if (row == endrow) /* line got too long for screen */ + { + ++row; + break; + } + screenp = LinePointers[screen_row]; +#ifdef RIGHTLEFT + if (wp->w_p_rl) + { + col = Columns - 1; /* col is not used if breaking! */ + screenp += Columns - 1; + } +#endif + } + + /* + * Store the character in NextScreen. + */ + if (*screenp != c || *(screenp + Columns) != attributes) + { + /* + * Special trick to make copy/paste of wrapped lines work with + * xterm/screen: + * If the first column is to be written, write the preceding + * char twice. This will work with all terminal types + * (regardless of the xn,am settings). + * Only do this on a fast tty. + */ + if (p_tf && row > startrow && col == 0 && + LinePointers[screen_row - 1][Columns - 1 + Columns] == + attributes) + { + if (screen_cur_row != screen_row - 1 || + screen_cur_col != Columns) + screen_char(LinePointers[screen_row - 1] + Columns - 1, + screen_row - 1, (int)(Columns - 1)); + screen_char(LinePointers[screen_row - 1] + Columns - 1, + screen_row - 1, (int)Columns); + screen_start(); + } + + *screenp = c; + *(screenp + Columns) = attributes; + screen_char(screenp, screen_row, col); + } +#ifdef RIGHTLEFT + if (wp->w_p_rl) + { + --screenp; + --col; + } + else +#endif + { + ++screenp; + ++col; + } + ++vcol; + /* stop before '$' of change command */ + if (wp == curwin && dollar_vcol && vcol >= (long)wp->w_virtcol) + break; + } + + stop_highlight(); + return (row); +} + +/* + * Called when p_dollar is set: display a '$' at the end of the changed text + * Only works when cursor is in the line that changes. + */ + void +display_dollar(col) + colnr_t col; +{ + colnr_t save_col; + + if (RedrawingDisabled) + return; + + cursor_off(); + save_col = curwin->w_cursor.col; + curwin->w_cursor.col = col; + curs_columns(FALSE); + if (!curwin->w_p_wrap) + curwin->w_col -= curwin->w_leftcol; + if (curwin->w_col < Columns) + { + screen_msg((char_u *)"$", curwin->w_winpos + curwin->w_row, +#ifdef RIGHTLEFT + curwin->w_p_rl ? (int)Columns - 1 - curwin->w_col : +#endif + curwin->w_col); + dollar_vcol = curwin->w_virtcol; + } + curwin->w_cursor.col = save_col; +} + +/* + * Call this function before moving the cursor from the normal insert position + * in insert mode. + */ + void +undisplay_dollar() +{ + if (dollar_vcol) + { + dollar_vcol = 0; + updateline(); + } +} + +/* + * output a single character directly to the screen + * update NextScreen + */ + void +screen_outchar(c, row, col) + int c; + int row, col; +{ + char_u buf[2]; + + buf[0] = c; + buf[1] = NUL; + screen_msg(buf, row, col); +} + +/* + * put string '*text' on the screen at position 'row' and 'col' + * update NextScreen + * Note: only outputs within one row, message is truncated at screen boundary! + * Note: if NextScreen, row and/or col is invalid, nothing is done. + */ + void +screen_msg(text, row, col) + char_u *text; + int row; + int col; +{ + char_u *screenp; + + if (NextScreen != NULL && row < Rows) /* safety check */ + { + screenp = LinePointers[row] + col; + while (*text && col < Columns) + { + if (*screenp != *text || *(screenp + Columns) != attributes) + { + *screenp = *text; + *(screenp + Columns) = attributes; + screen_char(screenp, row, col); + } + ++screenp; + ++col; + ++text; + } + } +} + +/* + * Reset cursor position. Use whenever cursor was moved because of outputting + * something directly to the screen (shell commands) or a terminal control + * code. + */ + void +screen_start() +{ + screen_cur_row = screen_cur_col = 9999; +} + +/* + * set_highlight - set highlight depending on 'highlight' option and context. + * + * return FAIL if highlighting is not possible, OK otherwise + */ + int +set_highlight(context) + int context; +{ + int i; + int mode; + char_u *p; + + /* + * Try to find the mode in the 'highlight' option. + * If not found, try the default for the 'highlight' option. + * If still not found, use 'r' (should not happen). + */ + mode = 'r'; + for (i = 0; i < 2; ++i) + { + if (i) + p = get_highlight_default(); + else + p = p_hl; + if (p == NULL) + continue; + + while (*p) + { + if (*p == context) /* found what we are looking for */ + break; + while (*p && *p != ',') /* skip to comma */ + ++p; + p = skip_to_option_part(p); /* skip comma and spaces */ + } + if (p[0] && p[1]) + { + mode = p[1]; + break; + } + } + + switch (mode) + { + case 'b': highlight = T_MD; /* bold */ + unhighlight = T_ME; + highlight_attr = CHAR_BOLD; + break; + case 's': highlight = T_SO; /* standout */ + unhighlight = T_SE; + highlight_attr = CHAR_STDOUT; + break; + case 'n': highlight = NULL; /* no highlighting */ + unhighlight = NULL; + highlight_attr = 0; + break; + case 'u': highlight = T_US; /* underline */ + unhighlight = T_UE; + highlight_attr = CHAR_UNDERL; + break; + case 'i': highlight = T_CZH; /* italic */ + unhighlight = T_CZR; + highlight_attr = CHAR_ITALIC; + break; + default: highlight = T_MR; /* reverse (invert) */ + unhighlight = T_ME; + highlight_attr = CHAR_INVERT; + break; + } + if (highlight == NULL || *highlight == NUL || + unhighlight == NULL || *unhighlight == NUL) + { + highlight = NULL; + return FAIL; + } + return OK; +} + + void +start_highlight() +{ + if (full_screen && +#ifdef WIN32 + termcap_active && +#endif + highlight != NULL) + { + outstr(highlight); + attributes = highlight_attr; + } +} + + void +stop_highlight() +{ + if (attributes) + { + outstr(unhighlight); + attributes = 0; + } +} + +/* + * variables used for one level depth of highlighting + * Used for "-- More --" message. + */ + +static char_u *old_highlight = NULL; +static char_u *old_unhighlight = NULL; +static int old_highlight_attr = 0; + + void +remember_highlight() +{ + old_highlight = highlight; + old_unhighlight = unhighlight; + old_highlight_attr = highlight_attr; +} + + void +recover_old_highlight() +{ + highlight = old_highlight; + unhighlight = old_unhighlight; + highlight_attr = old_highlight_attr; +} + +/* + * put character '*p' on the screen at position 'row' and 'col' + */ + static void +screen_char(p, row, col) + char_u *p; + int row; + int col; +{ + int c; + int noinvcurs; + + /* + * Outputting the last character on the screen may scrollup the screen. + * Don't to it! + */ + if (col == Columns - 1 && row == Rows - 1) + return; + if (screen_cur_col != col || screen_cur_row != row) + { + /* check if no cursor movement is allowed in standout mode */ + if (attributes && !p_wiv && *T_MS == NUL) + noinvcurs = 7; + else + noinvcurs = 0; + + /* + * If we're on the same row (which happens a lot!), try to + * avoid a windgoto(). + * If we are only a few characters off, output the + * characters. That is faster than cursor positioning. + * This can't be used when switching between inverting and not + * inverting. + */ + if (screen_cur_row == row && screen_cur_col < col) + { + register int i; + + i = col - screen_cur_col; + if (i <= 4 + noinvcurs) + { + /* stop at the first character that has different attributes + * from the ones that are active */ + while (i && *(p - i + Columns) == attributes) + { + c = *(p - i--); + outchar(c); + } + } + if (i) + { + if (noinvcurs) + stop_highlight(); + + if (*T_CRI != NUL) /* use tgoto interface! jw */ + OUTSTR(tgoto((char *)T_CRI, 0, i)); + else + windgoto(row, col); + + if (noinvcurs) + start_highlight(); + } + } + /* + * If the cursor is at the line above where we want to be, use CR LF, + * this is quicker than windgoto(). + * Don't do this if the cursor went beyond the last column, the cursor + * position is unknown then (some terminals wrap, some don't ) + */ + else if (screen_cur_row + 1 == row && col == 0 && + screen_cur_col < Columns) + { + if (noinvcurs) + stop_highlight(); + outchar('\n'); + if (noinvcurs) + start_highlight(); + } + else + { + if (noinvcurs) + stop_highlight(); + windgoto(row, col); + if (noinvcurs) + start_highlight(); + } + screen_cur_row = row; + screen_cur_col = col; + } + + /* + * For weird invert mechanism: output (un)highlight before every char + * Lots of extra output, but works. + */ + if (p_wiv) + { + if (attributes) + outstr(highlight); + else if (full_screen) + outstr(unhighlight); + } + outchar(*p); + screen_cur_col++; +} + +/* + * Fill the screen from 'start_row' to 'end_row', from 'start_col' to 'end_col' + * with character 'c1' in first column followed by 'c2' in the other columns. + */ + void +screen_fill(start_row, end_row, start_col, end_col, c1, c2) + int start_row, end_row; + int start_col, end_col; + int c1, c2; +{ + int row; + int col; + char_u *screenp; + char_u *attrp; + int did_delete; + int c; + + if (end_row > Rows) /* safety check */ + end_row = Rows; + if (end_col > Columns) /* safety check */ + end_col = Columns; + if (NextScreen == NULL || + start_row >= end_row || start_col >= end_col) /* nothing to do */ + return; + + for (row = start_row; row < end_row; ++row) + { + /* try to use delete-line termcap code */ + did_delete = FALSE; + if (attributes == 0 && c2 == ' ' && end_col == Columns && *T_CE != NUL +#ifdef RIGHTLEFT + && !rightleft +#endif + ) + { + /* + * check if we really need to clear something + */ + col = start_col; + screenp = LinePointers[row] + start_col; + if (c1 != ' ') /* don't clear first char */ + { + ++col; + ++screenp; + } + + /* skip blanks (used often, keep it fast!) */ + attrp = screenp + Columns; + while (col < end_col && *screenp == ' ' && *attrp == 0) + { + ++col; + ++screenp; + ++attrp; + } + if (col < end_col) /* something to be cleared */ + { + windgoto(row, col); /* clear rest of this screen line */ + outstr(T_CE); + screen_start(); /* don't know where cursor is now */ + col = end_col - col; + while (col--) /* clear chars in NextScreen */ + { + *attrp++ = 0; + *screenp++ = ' '; + } + } + did_delete = TRUE; /* the chars are cleared now */ + } + + screenp = LinePointers[row] + +#ifdef RIGHTLEFT + (rightleft ? (int)Columns - 1 - start_col : start_col); +#else + start_col; +#endif + c = c1; + for (col = start_col; col < end_col; ++col) + { + if (*screenp != c || *(screenp + Columns) != attributes) + { + *screenp = c; + *(screenp + Columns) = attributes; + if (!did_delete || c != ' ') + screen_char(screenp, row, +#ifdef RIGHTLEFT + rightleft ? Columns - 1 - col : +#endif + col); + } +#ifdef RIGHTLEFT + if (rightleft) + --screenp; + else +#endif + ++screenp; + if (col == start_col) + { + if (did_delete) + break; + c = c2; + } + } + if (row == Rows - 1) /* overwritten the command line */ + { + redraw_cmdline = TRUE; + if (c1 == ' ' && c2 == ' ') + clear_cmdline = FALSE; /* command line has been cleared */ + } + } +} + +/* + * recompute all w_botline's. Called after Rows changed. + */ + void +comp_Botline_all() +{ + WIN *wp; + + for (wp = firstwin; wp; wp = wp->w_next) + comp_Botline(wp); +} + +/* + * compute wp->w_botline. Can be called after wp->w_topline changed. + */ + void +comp_Botline(wp) + WIN *wp; +{ + comp_Botline_sub(wp, wp->w_topline, 0); +} + +/* + * Compute wp->w_botline, may have a start at the cursor position. + * Code shared between comp_Botline() and cursupdate(). + */ + static void +comp_Botline_sub(wp, lnum, done) + WIN *wp; + linenr_t lnum; + int done; +{ + int n; + + for ( ; lnum <= wp->w_buffer->b_ml.ml_line_count; ++lnum) + { + n = plines_win(wp, lnum); + if (done + n > wp->w_height) + break; + done += n; + } + + /* wp->w_botline is the line that is just below the window */ + wp->w_botline = lnum; + + /* Also set wp->w_empty_rows, otherwise scroll_cursor_bot() won't work */ + if (done == 0) + wp->w_empty_rows = 0; /* single line that doesn't fit */ + else + wp->w_empty_rows = wp->w_height - done; +} + + void +screenalloc(clear) + int clear; +{ + register int new_row, old_row; + WIN *wp; + int outofmem = FALSE; + int len; + char_u *new_NextScreen; + char_u **new_LinePointers; + + /* + * Allocation of the screen buffers is done only when the size changes + * and when Rows and Columns have been set and we are doing full screen + * stuff. + */ + if ((NextScreen != NULL && Rows == screen_Rows && Columns == screen_Columns) + || Rows == 0 || Columns == 0 || !full_screen) + return; + + comp_col(); /* recompute columns for shown command and ruler */ + + /* + * We're changing the size of the screen. + * - Allocate new arrays for NextScreen. + * - Move lines from the old arrays into the new arrays, clear extra + * lines (unless the screen is going to be cleared). + * - Free the old arrays. + */ + for (wp = firstwin; wp; wp = wp->w_next) + win_free_lsize(wp); + + new_NextScreen = (char_u *)malloc((size_t) (Rows * Columns * 2)); + new_LinePointers = (char_u **)malloc(sizeof(char_u *) * Rows); + + for (wp = firstwin; wp; wp = wp->w_next) + { + if (win_alloc_lsize(wp) == FAIL) + { + outofmem = TRUE; + break; + } + } + + if (new_NextScreen == NULL || new_LinePointers == NULL || outofmem) + { + do_outofmem_msg(); + vim_free(new_NextScreen); + new_NextScreen = NULL; + } + else + { + for (new_row = 0; new_row < Rows; ++new_row) + { + new_LinePointers[new_row] = new_NextScreen + new_row * Columns * 2; + + /* + * If the screen is not going to be cleared, copy as much as + * possible from the old screen to the new one and clear the rest + * (used when resizing the window at the "--more--" prompt or when + * executing an external command, for the GUI). + */ + if (!clear) + { + lineclear(new_LinePointers[new_row]); + old_row = new_row + (screen_Rows - Rows); + if (old_row >= 0) + { + if (screen_Columns < Columns) + len = screen_Columns; + else + len = Columns; + vim_memmove(new_LinePointers[new_row], + LinePointers[old_row], (size_t)len); + vim_memmove(new_LinePointers[new_row] + Columns, + LinePointers[old_row] + screen_Columns, (size_t)len); + } + } + } + } + + vim_free(NextScreen); + vim_free(LinePointers); + NextScreen = new_NextScreen; + LinePointers = new_LinePointers; + + must_redraw = CLEAR; /* need to clear the screen later */ + if (clear) + screenclear2(); + +#ifdef USE_GUI + else if (gui.in_use && NextScreen != NULL && Rows != screen_Rows) + { + gui_redraw_block(0, 0, Rows - 1, Columns - 1); + /* + * Adjust the position of the cursor, for when executing an external + * command. + */ + if (msg_row >= Rows) /* Rows got smaller */ + msg_row = Rows - 1; /* put cursor at last row */ + else if (Rows > screen_Rows) /* Rows got bigger */ + msg_row += Rows - screen_Rows; /* put cursor in same place */ + if (msg_col >= Columns) /* Columns got smaller */ + msg_col = Columns - 1; /* put cursor at last column */ + } +#endif + + screen_Rows = Rows; + screen_Columns = Columns; +} + + void +screenclear() +{ + if (emsg_on_display) + { + mch_delay(1000L, TRUE); + emsg_on_display = FALSE; + } + screenalloc(FALSE); /* allocate screen buffers if size changed */ + screenclear2(); /* clear the screen */ +} + + static void +screenclear2() +{ + int i; + + if (starting || NextScreen == NULL) + return; + + outstr(T_CL); /* clear the display */ + + /* blank out NextScreen */ + for (i = 0; i < Rows; ++i) + lineclear(LinePointers[i]); + + screen_cleared = TRUE; /* can use contents of NextScreen now */ + + win_rest_invalid(firstwin); + clear_cmdline = FALSE; + redraw_cmdline = TRUE; + if (must_redraw == CLEAR) /* no need to clear again */ + must_redraw = NOT_VALID; + compute_cmdrow(); + msg_pos((int)Rows - 1, 0); /* put cursor on last line for messages */ + screen_start(); /* don't know where cursor is now */ + msg_scrolled = 0; /* can't scroll back */ + msg_didany = FALSE; + msg_didout = FALSE; +} + +/* + * Clear one line in NextScreen. + */ + static void +lineclear(p) + char_u *p; +{ + (void)vim_memset(p, ' ', (size_t)Columns); + (void)vim_memset(p + Columns, 0, (size_t)Columns); +} + +/* + * check cursor for a valid lnum + */ + void +check_cursor() +{ + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + if (curwin->w_cursor.lnum <= 0) + curwin->w_cursor.lnum = 1; +} + + void +cursupdate() +{ + linenr_t lnum; + long line_count; + int sline; + int done; + int temp; + + if (!screen_valid(TRUE)) + return; + + /* + * Make sure the cursor is on a valid line number + */ + check_cursor(); + + /* + * If the buffer is empty, always set topline to 1. + */ + if (bufempty()) /* special case - file is empty */ + { + curwin->w_topline = 1; + curwin->w_cursor.lnum = 1; + curwin->w_cursor.col = 0; + curwin->w_lsize[0] = 0; + if (curwin->w_lsize_valid == 0) /* don't know about screen contents */ + updateScreen(NOT_VALID); + curwin->w_lsize_valid = 1; + } + + /* + * If the cursor is above the top of the window, scroll the window to put + * it at the top of the window. + * If we weren't very close to begin with, we scroll to put the cursor in + * the middle of the window. + */ + else if (curwin->w_cursor.lnum < curwin->w_topline + p_so && + curwin->w_topline > 1) + { + temp = curwin->w_height / 2 - 1; + if (temp < 2) + temp = 2; + /* not very close, put cursor halfway screen */ + if (curwin->w_topline + p_so - curwin->w_cursor.lnum >= temp) + scroll_cursor_halfway(FALSE); + else + scroll_cursor_top((int)p_sj, FALSE); + updateScreen(VALID); + } + + /* + * If the cursor is below the bottom of the window, scroll the window + * to put the cursor on the window. If the cursor is less than a + * windowheight down compute the number of lines at the top which have + * the same or more rows than the rows of the lines below the bottom. + * Note: After curwin->w_botline was computed lines may have been + * added or deleted, it may be greater than ml_line_count. + */ + else if ((long)curwin->w_cursor.lnum >= (long)curwin->w_botline - p_so && + curwin->w_botline <= curbuf->b_ml.ml_line_count) + { + line_count = curwin->w_cursor.lnum - curwin->w_botline + 1 + p_so; + if (line_count <= curwin->w_height + 1) + scroll_cursor_bot((int)p_sj, FALSE); + else + scroll_cursor_halfway(FALSE); + updateScreen(VALID); + } + + /* + * If the window contents is unknown, need to update the screen. + */ + else if (curwin->w_lsize_valid == 0) + updateScreen(NOT_VALID); + + /* + * Figure out the row number of the cursor line. + * This is used often, keep it fast! + */ + curwin->w_row = sline = 0; + /* curwin->w_lsize[] invalid */ + if (RedrawingDisabled || curwin->w_lsize_valid == 0) + { + done = 0; + for (lnum = curwin->w_topline; lnum != curwin->w_cursor.lnum; ++lnum) + done += plines(lnum); + curwin->w_row = done; + + /* + * Also need to compute w_botline and w_empty_rows, because + * updateScreen() will not have done that. + */ + comp_Botline_sub(curwin, lnum, done); + } + else + { + for (done = curwin->w_cursor.lnum - curwin->w_topline; done > 0; --done) + curwin->w_row += curwin->w_lsize[sline++]; + } + + curwin->w_cline_row = curwin->w_row; + curwin->w_col = curwin->w_virtcol = 0; + if (!RedrawingDisabled && sline > curwin->w_lsize_valid) + /* Should only happen with a line that is too */ + /* long to fit on the last screen line. */ + curwin->w_cline_height = 0; + else + { + /* curwin->w_lsize[] invalid */ + if (RedrawingDisabled || curwin->w_lsize_valid == 0) + curwin->w_cline_height = plines(curwin->w_cursor.lnum); + else + curwin->w_cline_height = curwin->w_lsize[sline]; + /* compute curwin->w_virtcol and curwin->w_col */ + curs_columns(!RedrawingDisabled); + if (must_redraw) + updateScreen(must_redraw); + } + + if (curwin->w_set_curswant) + { + curwin->w_curswant = curwin->w_virtcol; + curwin->w_set_curswant = FALSE; + } +} + +/* + * Recompute topline to put the cursor at the top of the window. + * Scroll at least "min_scroll" lines. + * If "always" is TRUE, always set topline (for "zt"). + */ + void +scroll_cursor_top(min_scroll, always) + int min_scroll; + int always; +{ + int scrolled = 0; + int extra = 0; + int used; + int i; + int sline; /* screen line for cursor */ + + /* + * Decrease topline until: + * - it has become 1 + * - (part of) the cursor line is moved off the screen or + * - moved at least 'scrolljump' lines and + * - at least 'scrolloff' lines above and below the cursor + */ + used = plines(curwin->w_cursor.lnum); + for (sline = 1; sline < curwin->w_cursor.lnum; ++sline) + { + i = plines(curwin->w_cursor.lnum - sline); + used += i; + extra += i; + if (extra <= p_so && + curwin->w_cursor.lnum + sline < curbuf->b_ml.ml_line_count) + used += plines(curwin->w_cursor.lnum + sline); + if (used > curwin->w_height) + break; + if (curwin->w_cursor.lnum - sline < curwin->w_topline) + scrolled += i; + + /* + * If scrolling is needed, scroll at least 'sj' lines. + */ + if ((curwin->w_cursor.lnum - (sline - 1) >= curwin->w_topline || + scrolled >= min_scroll) && extra > p_so) + break; + } + + /* + * If we don't have enough space, put cursor in the middle. + * This makes sure we get the same position when using "k" and "j" + * in a small window. + */ + if (used > curwin->w_height) + scroll_cursor_halfway(FALSE); + else + { + /* + * If "always" is FALSE, only adjust topline to a lower value, higher + * value may happen with wrapping lines + */ + if (curwin->w_cursor.lnum - (sline - 1) < curwin->w_topline || always) + curwin->w_topline = curwin->w_cursor.lnum - (sline - 1); + if (curwin->w_topline > curwin->w_cursor.lnum) + curwin->w_topline = curwin->w_cursor.lnum; + } +} + +/* + * Recompute topline to put the cursor at the bottom of the window. + * Scroll at least "min_scroll" lines. + * If "set_topline" is TRUE, set topline and botline first (for "zb"). + * This is messy stuff!!! + */ + void +scroll_cursor_bot(min_scroll, set_topline) + int min_scroll; + int set_topline; +{ + int used; + int scrolled = 0; + int extra = 0; + int sline; /* screen line for cursor from bottom */ + int i; + linenr_t lnum; + linenr_t line_count; + linenr_t old_topline = curwin->w_topline; + linenr_t old_botline = curwin->w_botline; + int old_empty_rows = curwin->w_empty_rows; + linenr_t cln; /* Cursor Line Number */ + + cln = curwin->w_cursor.lnum; + if (set_topline) + { + used = 0; + curwin->w_botline = cln + 1; + for (curwin->w_topline = curwin->w_botline; + curwin->w_topline != 1; + --curwin->w_topline) + { + i = plines(curwin->w_topline - 1); + if (used + i > curwin->w_height) + break; + used += i; + } + curwin->w_empty_rows = curwin->w_height - used; + } + + used = plines(cln); + if (cln >= curwin->w_botline) + { + scrolled = used; + if (cln == curwin->w_botline) + scrolled -= curwin->w_empty_rows; + } + + /* + * Stop counting lines to scroll when + * - hitting start of the file + * - scrolled nothing or at least 'sj' lines + * - at least 'so' lines below the cursor + * - lines between botline and cursor have been counted + */ + for (sline = 1; sline < cln; ++sline) + { + if ((((scrolled <= 0 || scrolled >= min_scroll) && extra >= p_so) || + cln + sline > curbuf->b_ml.ml_line_count) && + cln - sline < curwin->w_botline) + break; + i = plines(cln - sline); + used += i; + if (used > curwin->w_height) + break; + if (cln - sline >= curwin->w_botline) + { + scrolled += i; + if (cln - sline == curwin->w_botline) + scrolled -= curwin->w_empty_rows; + } + if (cln + sline <= curbuf->b_ml.ml_line_count) + { + i = plines(cln + sline); + used += i; + if (used > curwin->w_height) + break; + if (extra < p_so || scrolled < min_scroll) + { + extra += i; + if (cln + sline >= curwin->w_botline) + { + scrolled += i; + if (cln + sline == curwin->w_botline) + scrolled -= curwin->w_empty_rows; + } + } + } + } + /* curwin->w_empty_rows is larger, no need to scroll */ + if (scrolled <= 0) + line_count = 0; + /* more than a screenfull, don't scroll but redraw */ + else if (used > curwin->w_height) + line_count = used; + /* scroll minimal number of lines */ + else + { + for (i = 0, lnum = curwin->w_topline; + i < scrolled && lnum < curwin->w_botline; ++lnum) + i += plines(lnum); + if (i >= scrolled) /* it's possible to scroll */ + line_count = lnum - curwin->w_topline; + else /* below curwin->w_botline, don't scroll */ + line_count = 9999; + } + + /* + * Scroll up if the cursor is off the bottom of the screen a bit. + * Otherwise put it at 1/2 of the screen. + */ + if (line_count >= curwin->w_height && line_count > min_scroll) + scroll_cursor_halfway(FALSE); + else + scrollup(line_count); + + /* + * If topline didn't change we need to restore w_botline and w_empty_rows + * (we changed them). + * If topline did change, updateScreen() will set botline. + */ + if (curwin->w_topline == old_topline && set_topline) + { + curwin->w_botline = old_botline; + curwin->w_empty_rows = old_empty_rows; + } +} + +/* + * Recompute topline to put the cursor halfway the window + * If "atend" is TRUE, also put it halfway at the end of the file. + */ + void +scroll_cursor_halfway(atend) + int atend; +{ + int above = 0; + linenr_t topline; + int below = 0; + linenr_t botline; + int used; + int i; + linenr_t cln; /* Cursor Line Number */ + + topline = botline = cln = curwin->w_cursor.lnum; + used = plines(cln); + while (topline > 1) + { + if (below <= above) /* add a line below the cursor */ + { + if (botline + 1 <= curbuf->b_ml.ml_line_count) + { + i = plines(botline + 1); + used += i; + if (used > curwin->w_height) + break; + below += i; + ++botline; + } + else + { + ++below; /* count a "~" line */ + if (atend) + ++used; + } + } + + if (below > above) /* add a line above the cursor */ + { + i = plines(topline - 1); + used += i; + if (used > curwin->w_height) + break; + above += i; + --topline; + } + } + curwin->w_topline = topline; +} + +/* + * Correct the cursor position so that it is in a part of the screen at least + * 'so' lines from the top and bottom, if possible. + * If not possible, put it at the same position as scroll_cursor_halfway(). + * When called topline and botline must be valid! + */ + void +cursor_correct() +{ + int above = 0; /* screen lines above topline */ + linenr_t topline; + int below = 0; /* screen lines below botline */ + linenr_t botline; + int above_wanted, below_wanted; + linenr_t cln; /* Cursor Line Number */ + int max_off; + + /* + * How many lines we would like to have above/below the cursor depends on + * whether the first/last line of the file is on screen. + */ + above_wanted = p_so; + below_wanted = p_so; + if (curwin->w_topline == 1) + { + above_wanted = 0; + max_off = curwin->w_height / 2; + if (below_wanted > max_off) + below_wanted = max_off; + } + if (curwin->w_botline == curbuf->b_ml.ml_line_count + 1) + { + below_wanted = 0; + max_off = (curwin->w_height - 1) / 2; + if (above_wanted > max_off) + above_wanted = max_off; + } + + /* + * If there are sufficient file-lines above and below the cursor, we can + * return now. + */ + cln = curwin->w_cursor.lnum; + if (cln >= curwin->w_topline + above_wanted && + cln < curwin->w_botline - below_wanted) + return; + + /* + * Narrow down the area where the cursor can be put by taking lines from + * the top and the bottom until: + * - the desired context lines are found + * - the lines from the top is past the lines from the bottom + */ + topline = curwin->w_topline; + botline = curwin->w_botline - 1; + while ((above < above_wanted || below < below_wanted) && topline < botline) + { + if (below < below_wanted && (below <= above || above >= above_wanted)) + { + below += plines(botline); + --botline; + } + if (above < above_wanted && (above < below || below >= below_wanted)) + { + above += plines(topline); + ++topline; + } + } + if (topline == botline || botline == 0) + curwin->w_cursor.lnum = topline; + else if (topline > botline) + curwin->w_cursor.lnum = botline; + else + { + if (cln < topline && curwin->w_topline > 1) + curwin->w_cursor.lnum = topline; + if (cln > botline && curwin->w_botline <= curbuf->b_ml.ml_line_count) + curwin->w_cursor.lnum = botline; + } +} + +/* + * Compute curwin->w_row. + * Can be called when topline and botline have not been updated. + * return OK when cursor is in the window, FAIL when it isn't. + */ + int +curs_rows() +{ + linenr_t lnum; + int i; + + if (curwin->w_cursor.lnum < curwin->w_topline || + curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count || + curwin->w_cursor.lnum >= curwin->w_botline) + return FAIL; + + curwin->w_row = i = 0; + for (lnum = curwin->w_topline; lnum != curwin->w_cursor.lnum; ++lnum) + if (RedrawingDisabled) /* curwin->w_lsize[] invalid */ + curwin->w_row += plines(lnum); + else + curwin->w_row += curwin->w_lsize[i++]; + return OK; +} + +/* + * compute curwin->w_col and curwin->w_virtcol + */ + void +curs_columns(scroll) + int scroll; /* when TRUE, may scroll horizontally */ +{ + int diff; + int extra; + int new_leftcol; + colnr_t startcol; + colnr_t endcol; + + getvcol(curwin, &curwin->w_cursor, + &startcol, &(curwin->w_virtcol), &endcol); + + /* remove '$' from change command when cursor moves onto it */ + if (startcol > dollar_vcol) + dollar_vcol = 0; + + curwin->w_col = curwin->w_virtcol; + if (curwin->w_p_nu) + { + curwin->w_col += 8; + endcol += 8; + } + + curwin->w_row = curwin->w_cline_row; + if (curwin->w_p_wrap) /* long line wrapping, adjust curwin->w_row */ + { + while (curwin->w_col >= Columns) + { + curwin->w_col -= Columns; + curwin->w_row++; + } + } + else if (scroll) /* no line wrapping, compute curwin->w_leftcol if + * scrolling is on. If scrolling is off, + * curwin->w_leftcol is assumed to be 0 */ + { + /* If Cursor is left of the screen, scroll rightwards */ + /* If Cursor is right of the screen, scroll leftwards */ + if ((extra = (int)startcol - (int)curwin->w_leftcol) < 0 || + (extra = (int)endcol - (int)(curwin->w_leftcol + Columns) + 1) > 0) + { + if (extra < 0) + diff = -extra; + else + diff = extra; + + /* far off, put cursor in middle of window */ + if (p_ss == 0 || diff >= Columns / 2) + new_leftcol = curwin->w_col - Columns / 2; + else + { + if (diff < p_ss) + diff = p_ss; + if (extra < 0) + new_leftcol = curwin->w_leftcol - diff; + else + new_leftcol = curwin->w_leftcol + diff; + } + if (new_leftcol < 0) + curwin->w_leftcol = 0; + else + curwin->w_leftcol = new_leftcol; + /* screen has to be redrawn with new curwin->w_leftcol */ + redraw_later(NOT_VALID); + } + curwin->w_col -= curwin->w_leftcol; + } + /* Cursor past end of screen */ + /* happens with line that does not fit on screen */ + if (curwin->w_row > curwin->w_height - 1) + curwin->w_row = curwin->w_height - 1; +} + + void +scrolldown(line_count) + long line_count; +{ + register long done = 0; /* total # of physical lines done */ + + /* Scroll up 'line_count' lines. */ + while (line_count--) + { + if (curwin->w_topline == 1) + break; + done += plines(--curwin->w_topline); + } + /* + * Compute the row number of the last row of the cursor line + * and move it onto the screen. + */ + curwin->w_row += done; + if (curwin->w_p_wrap) + curwin->w_row += plines(curwin->w_cursor.lnum) - + 1 - curwin->w_virtcol / Columns; + while (curwin->w_row >= curwin->w_height && curwin->w_cursor.lnum > 1) + curwin->w_row -= plines(curwin->w_cursor.lnum--); + comp_Botline(curwin); +} + + void +scrollup(line_count) + long line_count; +{ + curwin->w_topline += line_count; + if (curwin->w_topline > curbuf->b_ml.ml_line_count) + curwin->w_topline = curbuf->b_ml.ml_line_count; + if (curwin->w_cursor.lnum < curwin->w_topline) + curwin->w_cursor.lnum = curwin->w_topline; + comp_Botline(curwin); +} + +/* + * Scroll the screen one line down, but don't do it if it would move the + * cursor off the screen. + */ + void +scrolldown_clamp() +{ + int end_row; + + if (curwin->w_topline == 1) + return; + + /* + * Compute the row number of the last row of the cursor line + * and make sure it doesn't go off the screen. Make sure the cursor + * doesn't go past 'scrolloff' lines from the screen end. + */ + end_row = curwin->w_row + plines(curwin->w_topline - 1); + if (curwin->w_p_wrap) + end_row += plines(curwin->w_cursor.lnum) - 1 - + curwin->w_virtcol / Columns; + if (end_row < curwin->w_height - p_so) + --curwin->w_topline; +} + +/* + * Scroll the screen one line up, but don't do it if it would move the cursor + * off the screen. + */ + void +scrollup_clamp() +{ + int start_row; + + if (curwin->w_topline == curbuf->b_ml.ml_line_count) + return; + + /* + * Compute the row number of the first row of the cursor line + * and make sure it doesn't go off the screen. Make sure the cursor + * doesn't go before 'scrolloff' lines from the screen start. + */ + start_row = curwin->w_row - plines(curwin->w_topline); + if (curwin->w_p_wrap) + start_row -= curwin->w_virtcol / Columns; + if (start_row >= p_so) + ++curwin->w_topline; +} + +/* + * insert 'line_count' lines at 'row' in window 'wp' + * if 'invalid' is TRUE the wp->w_lsize_lnum[] is invalidated. + * if 'mayclear' is TRUE the screen will be cleared if it is faster than + * scrolling. + * Returns FAIL if the lines are not inserted, OK for success. + */ + int +win_ins_lines(wp, row, line_count, invalid, mayclear) + WIN *wp; + int row; + int line_count; + int invalid; + int mayclear; +{ + int did_delete; + int nextrow; + int lastrow; + int retval; + + if (invalid) + wp->w_lsize_valid = 0; + + if (RedrawingDisabled || line_count <= 0 || wp->w_height < 5) + return FAIL; + + if (line_count > wp->w_height - row) + line_count = wp->w_height - row; + + if (mayclear && Rows - line_count < 5) /* only a few lines left: redraw is faster */ + { + screenclear(); /* will set wp->w_lsize_valid to 0 */ + return FAIL; + } + + /* + * Delete all remaining lines + */ + if (row + line_count >= wp->w_height) + { + screen_fill(wp->w_winpos + row, wp->w_winpos + wp->w_height, + 0, (int)Columns, ' ', ' '); + return OK; + } + + /* + * when scrolling, the message on the command line should be cleared, + * otherwise it will stay there forever. + */ + clear_cmdline = TRUE; + + /* + * if the terminal can set a scroll region, use that + */ + if (scroll_region) + { + scroll_region_set(wp, row); + retval = screen_ins_lines(wp->w_winpos + row, 0, line_count, + wp->w_height - row); + scroll_region_reset(); + return retval; + } + + if (wp->w_next && p_tf) /* don't delete/insert on fast terminal */ + return FAIL; + + /* + * If there is a next window or a status line, we first try to delete the + * lines at the bottom to avoid messing what is after the window. + * If this fails and there are following windows, don't do anything to avoid + * messing up those windows, better just redraw. + */ + did_delete = FALSE; + if (wp->w_next || wp->w_status_height) + { + if (screen_del_lines(0, wp->w_winpos + wp->w_height - line_count, + line_count, (int)Rows, FALSE) == OK) + did_delete = TRUE; + else if (wp->w_next) + return FAIL; + } + /* + * if no lines deleted, blank the lines that will end up below the window + */ + if (!did_delete) + { + wp->w_redr_status = TRUE; + redraw_cmdline = TRUE; + nextrow = wp->w_winpos + wp->w_height + wp->w_status_height; + lastrow = nextrow + line_count; + if (lastrow > Rows) + lastrow = Rows; + screen_fill(nextrow - line_count, lastrow - line_count, + 0, (int)Columns, ' ', ' '); + } + + if (screen_ins_lines(0, wp->w_winpos + row, line_count, (int)Rows) == FAIL) + { + /* deletion will have messed up other windows */ + if (did_delete) + { + wp->w_redr_status = TRUE; + win_rest_invalid(wp->w_next); + } + return FAIL; + } + + return OK; +} + +/* + * delete 'line_count' lines at 'row' in window 'wp' + * If 'invalid' is TRUE curwin->w_lsize_lnum[] is invalidated. + * If 'mayclear' is TRUE the screen will be cleared if it is faster than + * scrolling + * Return OK for success, FAIL if the lines are not deleted. + */ + int +win_del_lines(wp, row, line_count, invalid, mayclear) + WIN *wp; + int row; + int line_count; + int invalid; + int mayclear; +{ + int retval; + + if (invalid) + wp->w_lsize_valid = 0; + + if (RedrawingDisabled || line_count <= 0) + return FAIL; + + if (line_count > wp->w_height - row) + line_count = wp->w_height - row; + + /* only a few lines left: redraw is faster */ + if (mayclear && Rows - line_count < 5) + { + screenclear(); /* will set wp->w_lsize_valid to 0 */ + return FAIL; + } + + /* + * Delete all remaining lines + */ + if (row + line_count >= wp->w_height) + { + screen_fill(wp->w_winpos + row, wp->w_winpos + wp->w_height, + 0, (int)Columns, ' ', ' '); + return OK; + } + + /* + * when scrolling, the message on the command line should be cleared, + * otherwise it will stay there forever. + */ + clear_cmdline = TRUE; + + /* + * if the terminal can set a scroll region, use that + */ + if (scroll_region) + { + scroll_region_set(wp, row); + retval = screen_del_lines(wp->w_winpos + row, 0, line_count, + wp->w_height - row, FALSE); + scroll_region_reset(); + return retval; + } + + if (wp->w_next && p_tf) /* don't delete/insert on fast terminal */ + return FAIL; + + if (screen_del_lines(0, wp->w_winpos + row, line_count, + (int)Rows, FALSE) == FAIL) + return FAIL; + + /* + * If there are windows or status lines below, try to put them at the + * correct place. If we can't do that, they have to be redrawn. + */ + if (wp->w_next || wp->w_status_height || cmdline_row < Rows - 1) + { + if (screen_ins_lines(0, wp->w_winpos + wp->w_height - line_count, + line_count, (int)Rows) == FAIL) + { + wp->w_redr_status = TRUE; + win_rest_invalid(wp->w_next); + } + } + /* + * If this is the last window and there is no status line, redraw the + * command line later. + */ + else + redraw_cmdline = TRUE; + return OK; +} + +/* + * window 'wp' and everything after it is messed up, mark it for redraw + */ + void +win_rest_invalid(wp) + WIN *wp; +{ + while (wp) + { + wp->w_lsize_valid = 0; + wp->w_redr_type = NOT_VALID; + wp->w_redr_status = TRUE; + wp = wp->w_next; + } + redraw_cmdline = TRUE; +} + +/* + * The rest of the routines in this file perform screen manipulations. The + * given operation is performed physically on the screen. The corresponding + * change is also made to the internal screen image. In this way, the editor + * anticipates the effect of editing changes on the appearance of the screen. + * That way, when we call screenupdate a complete redraw isn't usually + * necessary. Another advantage is that we can keep adding code to anticipate + * screen changes, and in the meantime, everything still works. + */ + +/* + * types for inserting or deleting lines + */ +#define USE_T_CAL 1 +#define USE_T_CDL 2 +#define USE_T_AL 3 +#define USE_T_CE 4 +#define USE_T_DL 5 +#define USE_T_SR 6 +#define USE_NL 7 +#define USE_T_CD 8 + +/* + * insert lines on the screen and update NextScreen + * 'end' is the line after the scrolled part. Normally it is Rows. + * When scrolling region used 'off' is the offset from the top for the region. + * 'row' and 'end' are relative to the start of the region. + * + * return FAIL for failure, OK for success. + */ + static int +screen_ins_lines(off, row, line_count, end) + int off; + int row; + int line_count; + int end; +{ + int i; + int j; + char_u *temp; + int cursor_row; + int type; + int result_empty; + + /* + * FAIL if + * - there is no valid screen + * - the screen has to be redrawn completely + * - the line count is less than one + * - the line count is more than 'ttyscroll' + */ + if (!screen_valid(TRUE) || line_count <= 0 || line_count > p_ttyscroll) + return FAIL; + + /* + * There are seven ways to insert lines: + * 1. Use T_CD (clear to end of display) if it exists and the result of + * the insert is just empty lines + * 2. Use T_CAL (insert multiple lines) if it exists and T_AL is not + * present or line_count > 1. It looks better if we do all the inserts + * at once. + * 3. Use T_CDL (delete multiple lines) if it exists and the result of the + * insert is just empty lines and T_CE is not present or line_count > + * 1. + * 4. Use T_AL (insert line) if it exists. + * 5. Use T_CE (erase line) if it exists and the result of the insert is + * just empty lines. + * 6. Use T_DL (delete line) if it exists and the result of the insert is + * just empty lines. + * 7. Use T_SR (scroll reverse) if it exists and inserting at row 0 and + * the 'da' flag is not set or we have clear line capability. + * + * Careful: In a hpterm scroll reverse doesn't work as expected, it moves + * the scrollbar for the window. It does have insert line, use that if it + * exists. + */ + result_empty = (row + line_count >= end); + if (*T_CD != NUL && result_empty) + type = USE_T_CD; + else if (*T_CAL != NUL && (line_count > 1 || *T_AL == NUL)) + type = USE_T_CAL; + else if (*T_CDL != NUL && result_empty && (line_count > 1 || *T_CE == NUL)) + type = USE_T_CDL; + else if (*T_AL != NUL) + type = USE_T_AL; + else if (*T_CE != NUL && result_empty) + type = USE_T_CE; + else if (*T_DL != NUL && result_empty) + type = USE_T_DL; + else if (*T_SR != NUL && row == 0 && (*T_DA == NUL || *T_CE)) + type = USE_T_SR; + else + return FAIL; + + /* + * For clearing the lines screen_del_lines is used. This will also take + * care of t_db if necessary. + */ + if (type == USE_T_CD || type == USE_T_CDL || + type == USE_T_CE || type == USE_T_DL) + return screen_del_lines(off, row, line_count, end, FALSE); + + /* + * If text is retained below the screen, first clear or delete as many + * lines at the bottom of the window as are about to be inserted so that + * the deleted lines won't later surface during a screen_del_lines. + */ + if (*T_DB) + screen_del_lines(off, end - line_count, line_count, end, FALSE); + + if (*T_CSC != NUL) /* cursor relative to region */ + cursor_row = row; + else + cursor_row = row + off; + + /* + * Shift LinePointers line_count down to reflect the inserted lines. + * Clear the inserted lines in NextScreen. + */ + row += off; + end += off; + for (i = 0; i < line_count; ++i) + { + j = end - 1 - i; + temp = LinePointers[j]; + while ((j -= line_count) >= row) + LinePointers[j + line_count] = LinePointers[j]; + LinePointers[j + line_count] = temp; + lineclear(temp); + } + + windgoto(cursor_row, 0); + if (type == USE_T_CAL) + { + OUTSTR(tgoto((char *)T_CAL, 0, line_count)); + screen_start(); /* don't know where cursor is now */ + } + else + { + for (i = 0; i < line_count; i++) + { + if (type == USE_T_AL) + { + if (i && cursor_row != 0) + windgoto(cursor_row, 0); + outstr(T_AL); + } + else /* type == USE_T_SR */ + outstr(T_SR); + screen_start(); /* don't know where cursor is now */ + } + } + + /* + * With scroll-reverse and 'da' flag set we need to clear the lines that + * have been scrolled down into the region. + */ + if (type == USE_T_SR && *T_DA) + { + for (i = 0; i < line_count; ++i) + { + windgoto(off + i, 0); + outstr(T_CE); + screen_start(); /* don't know where cursor is now */ + } + } + + return OK; +} + +/* + * delete lines on the screen and update NextScreen + * 'end' is the line after the scrolled part. Normally it is Rows. + * When scrolling region used 'off' is the offset from the top for the region. + * 'row' and 'end' are relative to the start of the region. + * + * Return OK for success, FAIL if the lines are not deleted. + */ + int +screen_del_lines(off, row, line_count, end, force) + int off; + int row; + int line_count; + int end; + int force; /* even when line_count > p_ttyscroll */ +{ + int j; + int i; + char_u *temp; + int cursor_row; + int cursor_end; + int result_empty; /* result is empty until end of region */ + int can_delete; /* deleting line codes can be used */ + int type; + + /* + * FAIL if + * - there is no valid screen + * - the screen has to be redrawn completely + * - the line count is less than one + * - the line count is more than 'ttyscroll' + */ + if (!screen_valid(TRUE) || line_count <= 0 || + (!force && line_count > p_ttyscroll)) + return FAIL; + + /* + * Check if the rest of the current region will become empty. + */ + result_empty = row + line_count >= end; + + /* + * We can delete lines only when 'db' flag not set or when 'ce' option + * available. + */ + can_delete = (*T_DB == NUL || *T_CE); + + /* + * There are four ways to delete lines: + * 1. Use T_CD if it exists and the result is empty. + * 2. Use newlines if row == 0 and count == 1 or T_CDL does not exist. + * 3. Use T_CDL (delete multiple lines) if it exists and line_count > 1 or + * none of the other ways work. + * 4. Use T_CE (erase line) if the result is empty. + * 5. Use T_DL (delete line) if it exists. + */ + if (*T_CD != NUL && result_empty) + type = USE_T_CD; + else if (row == 0 && (line_count == 1 || *T_CDL == NUL)) + type = USE_NL; + else if (*T_CDL != NUL && line_count > 1 && can_delete) + type = USE_T_CDL; + else if (*T_CE != NUL && result_empty) + type = USE_T_CE; + else if (*T_DL != NUL && can_delete) + type = USE_T_DL; + else if (*T_CDL != NUL && can_delete) + type = USE_T_CDL; + else + return FAIL; + + if (*T_CSC != NUL) /* cursor relative to region */ + { + cursor_row = row; + cursor_end = end; + } + else + { + cursor_row = row + off; + cursor_end = end + off; + } + + /* + * Now shift LinePointers line_count up to reflect the deleted lines. + * Clear the inserted lines in NextScreen. + */ + row += off; + end += off; + for (i = 0; i < line_count; ++i) + { + j = row + i; + temp = LinePointers[j]; + while ((j += line_count) <= end - 1) + LinePointers[j - line_count] = LinePointers[j]; + LinePointers[j - line_count] = temp; + lineclear(temp); + } + + /* delete the lines */ + if (type == USE_T_CD) + { + windgoto(cursor_row, 0); + outstr(T_CD); + screen_start(); /* don't know where cursor is now */ + } + else if (type == USE_T_CDL) + { + windgoto(cursor_row, 0); + OUTSTR(tgoto((char *)T_CDL, 0, line_count)); + screen_start(); /* don't know where cursor is now */ + } + /* + * Deleting lines at top of the screen or scroll region: Just scroll + * the whole screen (scroll region) up by outputting newlines on the + * last line. + */ + else if (type == USE_NL) + { + windgoto(cursor_end - 1, 0); + for (i = line_count; --i >= 0; ) + outchar('\n'); /* cursor will remain on same line */ + } + else + { + for (i = line_count; --i >= 0; ) + { + if (type == USE_T_DL) + { + windgoto(cursor_row, 0); + outstr(T_DL); /* delete a line */ + } + else /* type == USE_T_CE */ + { + windgoto(cursor_row + i, 0); + outstr(T_CE); /* erase a line */ + } + screen_start(); /* don't know where cursor is now */ + } + } + + /* + * If the 'db' flag is set, we need to clear the lines that have been + * scrolled up at the bottom of the region. + */ + if (*T_DB && (type == USE_T_DL || type == USE_T_CDL)) + { + for (i = line_count; i > 0; --i) + { + windgoto(cursor_end - i, 0); + outstr(T_CE); /* erase a line */ + screen_start(); /* don't know where cursor is now */ + } + } + return OK; +} + +/* + * show the current mode and ruler + * + * If clear_cmdline is TRUE, clear the rest of the cmdline. + * If clear_cmdline is FALSE there may be a message there that needs to be + * cleared only if a mode is shown. + */ + void +showmode() +{ + int need_clear = FALSE; + int do_mode = (p_smd && + ((State & INSERT) || restart_edit || VIsual_active)); + + if (do_mode || Recording) + { + if (emsg_on_display) + { + mch_delay(1000L, TRUE); + emsg_on_display = FALSE; + } + msg_didout = FALSE; /* never scroll up */ + msg_col = 0; + gotocmdline(FALSE); + set_highlight('M'); /* Highlight mode */ + if (do_mode) + { + start_highlight(); + MSG_OUTSTR("--"); +#ifdef INSERT_EXPAND + if (edit_submode != NULL) /* CTRL-X in Insert mode */ + { + msg_outstr(edit_submode); + if (edit_submode_extra != NULL) + { + msg_outchar(' '); /* add a space in between */ + if (edit_submode_highl) + { + stop_highlight(); + set_highlight('r'); /* Highlight mode */ + start_highlight(); + } + msg_outstr(edit_submode_extra); + if (edit_submode_highl) + { + stop_highlight(); + set_highlight('M'); /* Highlight mode */ + start_highlight(); + } + } + } + else +#endif + { + if (State == INSERT) + { +#ifdef RIGHTLEFT + if (p_ri) + MSG_OUTSTR(" REVERSE"); +#endif + MSG_OUTSTR(" INSERT"); + } + else if (State == REPLACE) + MSG_OUTSTR(" REPLACE"); + else if (restart_edit == 'I') + MSG_OUTSTR(" (insert)"); + else if (restart_edit == 'R') + MSG_OUTSTR(" (replace)"); +#ifdef RIGHTLEFT + if (p_hkmap) + MSG_OUTSTR(" Hebrew"); +#endif + if ((State & INSERT) && p_paste) + MSG_OUTSTR(" (paste)"); + if (VIsual_active) + { + MSG_OUTSTR(" VISUAL"); + if (VIsual_mode == Ctrl('V')) + MSG_OUTSTR(" BLOCK"); + else if (VIsual_mode == 'V') + MSG_OUTSTR(" LINE"); + } + } + MSG_OUTSTR(" --"); + need_clear = TRUE; + } + if (Recording) + { + if (!need_clear) + start_highlight(); + MSG_OUTSTR("recording"); + need_clear = TRUE; + } + if (need_clear) + stop_highlight(); + if (need_clear || clear_cmdline) + msg_clr_eos(); + msg_didout = FALSE; /* overwrite this message */ + msg_col = 0; + } + else if (clear_cmdline) /* just clear anything */ + { + msg_row = cmdline_row; + msg_col = 0; + msg_clr_eos(); /* will reset clear_cmdline */ + } + win_redr_ruler(lastwin, TRUE); + redraw_cmdline = FALSE; +} + +/* + * delete mode message + */ + void +delmode() +{ + if (Recording) + MSG("recording"); + else + MSG(""); +} + +/* + * if ruler option is set: show current cursor position + * if always is FALSE, only print if position has changed + */ + void +showruler(always) + int always; +{ + win_redr_ruler(curwin, always); +} + + void +win_redr_ruler(wp, always) + WIN *wp; + int always; +{ + static linenr_t old_lnum = 0; + static colnr_t old_col = 0; + char_u buffer[30]; + int row; + int fillchar; + + if (p_ru && (redraw_cmdline || always || + wp->w_cursor.lnum != old_lnum || wp->w_virtcol != old_col)) + { + cursor_off(); + if (wp->w_status_height) + { + row = wp->w_winpos + wp->w_height; + if (set_highlight('s') == OK) /* can use highlighting */ + { + fillchar = ' '; + start_highlight(); + } + else + fillchar = '='; + } + else + { + row = Rows - 1; + fillchar = ' '; + } + /* + * Some sprintfs return the length, some return a pointer. + * To avoid portability problems we use strlen() here. + */ + + sprintf((char *)buffer, "%ld,", + (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? + 0L : + (long)(wp->w_cursor.lnum)); + /* + * Check if cursor.lnum is valid, since win_redr_ruler() may be called + * after deleting lines, before cursor.lnum is corrected. + */ + if (wp->w_cursor.lnum <= wp->w_buffer->b_ml.ml_line_count) + col_print(buffer + STRLEN(buffer), + !(State & INSERT) && + *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, FALSE) == NUL ? + 0 : + (int)wp->w_cursor.col + 1, + (int)wp->w_virtcol + 1); + + screen_msg(buffer, row, ru_col); + screen_fill(row, row + 1, ru_col + (int)STRLEN(buffer), + (int)Columns, fillchar, fillchar); + old_lnum = wp->w_cursor.lnum; + old_col = wp->w_virtcol; + stop_highlight(); + } +} + +/* + * screen_valid - allocate screen buffers if size changed + * If "clear" is TRUE: clear screen if it has been resized. + * Returns TRUE if there is a valid screen to write to. + * Returns FALSE when starting up and screen not initialized yet. + */ + int +screen_valid(clear) + int clear; +{ + screenalloc(clear); /* allocate screen buffers if size changed */ + return (NextScreen != NULL); +} + +#ifdef USE_MOUSE +/* + * Move the cursor to the specified row and column on the screen. + * Change current window if neccesary. Returns an integer with the + * CURSOR_MOVED bit set if the cursor has moved or unset otherwise. + * + * If flags has MOUSE_FOCUS, then the current window will not be changed, and + * if the mouse is outside the window then the text will scroll, or if the + * mouse was previously on a status line, then the status line may be dragged. + * + * If flags has MOUSE_MAY_VIS, then VIsual mode will be started before the + * cursor is moved unless the cursor was on a status line. Ignoring the + * CURSOR_MOVED bit, this function returns one of IN_UNKNOWN, IN_BUFFER, or + * IN_STATUS_LINE depending on where the cursor was clicked. + * + * If flags has MOUSE_DID_MOVE, nothing is done if the mouse didn't move since + * the last call. + * + * If flags has MOUSE_SETPOS, nothing is done, only the current position is + * remembered. + */ + int +jump_to_mouse(flags) + int flags; +{ + static int on_status_line = 0; /* #lines below bottom of window */ + static int prev_row = -1; + static int prev_col = -1; + + WIN *wp, *old_curwin; + FPOS old_cursor; + int count; + int first; + int row = mouse_row; + int col = mouse_col; + + mouse_past_bottom = FALSE; + mouse_past_eol = FALSE; + + if ((flags & MOUSE_DID_MOVE) && prev_row == mouse_row && + prev_col == mouse_col) + return IN_BUFFER; /* mouse pointer didn't move */ + + prev_row = mouse_row; + prev_col = mouse_col; + + if ((flags & MOUSE_SETPOS)) + return IN_BUFFER; /* mouse pointer didn't move */ + + old_curwin = curwin; + old_cursor = curwin->w_cursor; + + if (!(flags & MOUSE_FOCUS)) + { + if (row < 0 || col < 0) /* check if it makes sense */ + return IN_UNKNOWN; + + /* find the window where the row is in */ + for (wp = firstwin; wp->w_next; wp = wp->w_next) + if (row < wp->w_next->w_winpos) + break; + /* + * winpos and height may change in win_enter()! + */ + row -= wp->w_winpos; + if (row >= wp->w_height) /* In (or below) status line */ + on_status_line = row - wp->w_height + 1; + else + on_status_line = 0; + win_enter(wp, TRUE); + if (on_status_line) /* In (or below) status line */ + { + /* Don't use start_arrow() if we're in the same window */ + if (curwin == old_curwin) + return IN_STATUS_LINE; + else + return IN_STATUS_LINE | CURSOR_MOVED; + } + + curwin->w_cursor.lnum = curwin->w_topline; + } + else if (on_status_line) + { + /* Drag the status line */ + count = row - curwin->w_winpos - curwin->w_height + 1 - on_status_line; + win_drag_status_line(count); + return IN_STATUS_LINE; /* Cursor didn't move */ + } + else /* keep_window_focus must be TRUE */ + { + row -= curwin->w_winpos; + + /* + * When clicking beyond the end of the window, scroll the screen. + * Scroll by however many rows outside the window we are. + */ + if (row < 0) + { + count = 0; + for (first = TRUE; curwin->w_topline > 1; --curwin->w_topline) + { + count += plines(curwin->w_topline - 1); + if (!first && count > -row) + break; + first = FALSE; + } + redraw_later(VALID); + row = 0; + } + else if (row >= curwin->w_height) + { + count = 0; + for (first = TRUE; curwin->w_topline < curbuf->b_ml.ml_line_count; + ++curwin->w_topline) + { + count += plines(curwin->w_topline); + if (!first && count > row - curwin->w_height + 1) + break; + first = FALSE; + } + redraw_later(VALID); + row = curwin->w_height - 1; + } + curwin->w_cursor.lnum = curwin->w_topline; + } + +#ifdef RIGHTLEFT + if (curwin->w_p_rl) + col = Columns - 1 - col; +#endif + + if (curwin->w_p_nu) /* skip number in front of the line */ + if ((col -= 8) < 0) + col = 0; + + if (curwin->w_p_wrap) /* lines wrap */ + { + while (row) + { + count = plines(curwin->w_cursor.lnum); + if (count > row) + { + col += row * Columns; + break; + } + if (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) + { + mouse_past_bottom = TRUE; + break; + } + row -= count; + ++curwin->w_cursor.lnum; + } + } + else /* lines don't wrap */ + { + curwin->w_cursor.lnum += row; + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + { + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + mouse_past_bottom = TRUE; + } + col += curwin->w_leftcol; + } + curwin->w_curswant = col; + curwin->w_set_curswant = FALSE; /* May still have been TRUE */ + if (coladvance(col) == FAIL) + { + /* Mouse click beyond end of line */ + op_inclusive = TRUE; + mouse_past_eol = TRUE; + } + else + op_inclusive = FALSE; + + if ((flags & MOUSE_MAY_VIS) && !VIsual_active) + { + start_visual_highlight(); + VIsual = old_cursor; + VIsual_active = TRUE; +#ifdef USE_MOUSE + setmouse(); +#endif + if (p_smd) + redraw_cmdline = TRUE; /* show visual mode later */ + } + + if (curwin == old_curwin && curwin->w_cursor.lnum == old_cursor.lnum && + curwin->w_cursor.col == old_cursor.col) + return IN_BUFFER; /* Cursor has not moved */ + return IN_BUFFER | CURSOR_MOVED; /* Cursor has moved */ +} +#endif /* USE_MOUSE */ + +/* + * Redraw the screen later, with UpdateScreen(type). + * Set must_redraw only of not already set to a higher value. + * e.g. if must_redraw is CLEAR, type == NOT_VALID will do nothing. + */ + void +redraw_later(type) + int type; +{ + if (must_redraw < type) + must_redraw = type; +} diff --git a/usr.bin/vim/search.c b/usr.bin/vim/search.c new file mode 100644 index 00000000000..749fdc8f49d --- /dev/null +++ b/usr.bin/vim/search.c @@ -0,0 +1,3138 @@ +/* $OpenBSD: search.c,v 1.1.1.1 1996/09/07 21:40:24 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ +/* + * search.c: code for normal mode searching commands + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" +#include "ops.h" /* for op_inclusive */ + +/* modified Henry Spencer's regular expression routines */ +#include "regexp.h" + +static int inmacro __ARGS((char_u *, char_u *)); +static int cls __ARGS((void)); +static int skip_chars __ARGS((int, int)); +static void back_in_line __ARGS((void)); +static void find_first_blank __ARGS((FPOS *)); +static void show_pat_in_path __ARGS((char_u *, int, + int, int, FILE *, linenr_t *, long)); +static void give_warning __ARGS((char_u *)); + +static char_u *top_bot_msg = (char_u *)"search hit TOP, continuing at BOTTOM"; +static char_u *bot_top_msg = (char_u *)"search hit BOTTOM, continuing at TOP"; + +/* + * This file contains various searching-related routines. These fall into + * three groups: + * 1. string searches (for /, ?, n, and N) + * 2. character searches within a single line (for f, F, t, T, etc) + * 3. "other" kinds of searches like the '%' command, and 'word' searches. + */ + +/* + * String searches + * + * The string search functions are divided into two levels: + * lowest: searchit(); called by do_search() and edit(). + * Highest: do_search(); changes curwin->w_cursor, called by normal(). + * + * The last search pattern is remembered for repeating the same search. + * This pattern is shared between the :g, :s, ? and / commands. + * This is in myregcomp(). + * + * The actual string matching is done using a heavily modified version of + * Henry Spencer's regular expression library. + */ + +/* + * Two search patterns are remembered: One for the :substitute command and + * one for other searches. last_pattern points to the one that was + * used the last time. + */ +static char_u *search_pattern = NULL; +static int search_magic = TRUE; +static int search_no_scs = FALSE; +static char_u *subst_pattern = NULL; +static int subst_magic = TRUE; +static int subst_no_scs = FALSE; +static char_u *last_pattern = NULL; +static int last_magic = TRUE; +static int last_no_scs = FALSE; +static char_u *mr_pattern = NULL; /* pattern used by myregcomp() */ + +static int want_start; /* looking for start of line? */ + +/* + * Type used by find_pattern_in_path() to remember which included files have + * been searched already. + */ +typedef struct SearchedFile +{ + FILE *fp; /* File pointer */ + char_u *name; /* Full name of file */ + linenr_t lnum; /* Line we were up to in file */ + int matched; /* Found a match in this file */ +} SearchedFile; + +/* + * translate search pattern for vim_regcomp() + * + * sub_cmd == RE_SEARCH: save pat in search_pattern (normal search command) + * sub_cmd == RE_SUBST: save pat in subst_pattern (:substitute command) + * sub_cmd == RE_BOTH: save pat in both patterns (:global command) + * which_pat == RE_SEARCH: use previous search pattern if "pat" is NULL + * which_pat == RE_SUBST: use previous sustitute pattern if "pat" is NULL + * which_pat == RE_LAST: use last used pattern if "pat" is NULL + * options & SEARCH_HIS: put search string in history + * options & SEARCH_KEEP: keep previous search pattern + * + */ + regexp * +myregcomp(pat, sub_cmd, which_pat, options) + char_u *pat; + int sub_cmd; + int which_pat; + int options; +{ + rc_did_emsg = FALSE; + reg_magic = p_magic; + if (pat == NULL || *pat == NUL) /* use previous search pattern */ + { + if (which_pat == RE_SEARCH) + { + if (search_pattern == NULL) + { + emsg(e_noprevre); + rc_did_emsg = TRUE; + return (regexp *) NULL; + } + pat = search_pattern; + reg_magic = search_magic; + no_smartcase = search_no_scs; + } + else if (which_pat == RE_SUBST) + { + if (subst_pattern == NULL) + { + emsg(e_nopresub); + rc_did_emsg = TRUE; + return (regexp *) NULL; + } + pat = subst_pattern; + reg_magic = subst_magic; + no_smartcase = subst_no_scs; + } + else /* which_pat == RE_LAST */ + { + if (last_pattern == NULL) + { + emsg(e_noprevre); + rc_did_emsg = TRUE; + return (regexp *) NULL; + } + pat = last_pattern; + reg_magic = last_magic; + no_smartcase = last_no_scs; + } + } + else if (options & SEARCH_HIS) + add_to_history(1, pat); /* put new pattern in history */ + + mr_pattern = pat; + + /* + * save the currently used pattern in the appropriate place, + * unless the pattern should not be remembered + */ + if (!(options & SEARCH_KEEP)) + { + /* + * search or global command + */ + if (sub_cmd == RE_SEARCH || sub_cmd == RE_BOTH) + { + if (search_pattern != pat) + { + vim_free(search_pattern); + search_pattern = strsave(pat); + last_pattern = search_pattern; + search_magic = reg_magic; + last_magic = reg_magic; /* Magic sticks with the r.e. */ + search_no_scs = no_smartcase; + last_no_scs = no_smartcase; + } + } + /* + * substitute or global command + */ + if (sub_cmd == RE_SUBST || sub_cmd == RE_BOTH) + { + if (subst_pattern != pat) + { + vim_free(subst_pattern); + subst_pattern = strsave(pat); + last_pattern = subst_pattern; + subst_magic = reg_magic; + last_magic = reg_magic; /* Magic sticks with the r.e. */ + subst_no_scs = no_smartcase; + last_no_scs = no_smartcase; + } + } + } + + want_start = (*pat == '^'); /* looking for start of line? */ + set_reg_ic(pat); /* tell the vim_regexec routine how to search */ + return vim_regcomp(pat); +} + +/* + * Set reg_ic according to p_ic, p_scs and the search pattern. + */ + void +set_reg_ic(pat) + char_u *pat; +{ + char_u *p; + + reg_ic = p_ic; + if (!no_smartcase && p_scs +#ifdef INSERT_EXPAND + && !(ctrl_x_mode && curbuf->b_p_inf) +#endif + ) + { + /* don't ignore case if pattern has uppercase */ + for (p = pat; *p; ) + if (isupper(*p++)) + reg_ic = FALSE; + } + no_smartcase = FALSE; +} + +/* + * lowest level search function. + * Search for 'count'th occurrence of 'str' in direction 'dir'. + * Start at position 'pos' and return the found position in 'pos'. + * + * if (options & SEARCH_MSG) == 0 don't give any messages + * if (options & SEARCH_MSG) == SEARCH_NFMSG don't give 'notfound' messages + * if (options & SEARCH_MSG) == SEARCH_MSG give all messages + * if (options & SEARCH_HIS) put search pattern in history + * if (options & SEARCH_END) return position at end of match + * if (options & SEARCH_START) accept match at pos itself + * if (options & SEARCH_KEEP) keep previous search pattern + * + * Return OK for success, FAIL for failure. + */ + int +searchit(pos, dir, str, count, options, which_pat) + FPOS *pos; + int dir; + char_u *str; + long count; + int options; + int which_pat; +{ + int found; + linenr_t lnum; /* no init to shut up Apollo cc */ + regexp *prog; + char_u *ptr; + register char_u *match = NULL, *matchend = NULL; /* init for GCC */ + int loop; + FPOS start_pos; + int at_first_line; + int extra_col; + int match_ok; + char_u *p; + + if ((prog = myregcomp(str, RE_SEARCH, which_pat, + (options & (SEARCH_HIS + SEARCH_KEEP)))) == NULL) + { + if ((options & SEARCH_MSG) && !rc_did_emsg) + emsg2((char_u *)"Invalid search string: %s", mr_pattern); + return FAIL; + } + + if (options & SEARCH_START) + extra_col = 0; + else + extra_col = 1; + + +/* + * find the string + */ + do /* loop for count */ + { + start_pos = *pos; /* remember start pos for detecting no match */ + found = 0; /* default: not found */ + + /* + * Start searching in current line, unless searching backwards and + * we're in column 0 or searching forward and we're past the end of + * the line + */ + if (dir == BACKWARD && start_pos.col == 0) + { + lnum = pos->lnum - 1; + at_first_line = FALSE; + } + else + { + lnum = pos->lnum; + at_first_line = TRUE; + } + + for (loop = 0; loop <= 1; ++loop) /* loop twice if 'wrapscan' set */ + { + for ( ; lnum > 0 && lnum <= curbuf->b_ml.ml_line_count; + lnum += dir, at_first_line = FALSE) + { + ptr = ml_get(lnum); + /* forward search, first line */ + if (dir == FORWARD && at_first_line && start_pos.col > 0 && + want_start) + continue; /* match not possible */ + + /* + * Look for a match somewhere in the line. + */ + if (vim_regexec(prog, ptr, TRUE)) + { + match = prog->startp[0]; + matchend = prog->endp[0]; + + /* + * Forward search in the first line: match should be after + * the start position. If not, continue at the end of the + * match (this is vi compatible). + */ + if (dir == FORWARD && at_first_line) + { + match_ok = TRUE; + /* + * When *match == NUL the cursor will be put one back + * afterwards, compare with that position, otherwise + * "/$" will get stuck on end of line. Same for + * matchend. + */ + while ((options & SEARCH_END) ? + ((int)(matchend - ptr) - 1 - (int)(*matchend == NUL) < + (int)start_pos.col + extra_col) : + ((int)(match - ptr) - (int)(*match == NUL) < + (int)start_pos.col + extra_col)) + { + /* + * If vi-compatible searching, continue at the end + * of the match, otherwise continue one position + * forward. + */ + if (vim_strchr(p_cpo, CPO_SEARCH) != NULL) + { + p = matchend; + if (match == p && *p != NUL) + ++p; + } + else + { + p = match; + if (*p != NUL) + ++p; + } + if (*p != NUL && vim_regexec(prog, p, FALSE)) + { + match = prog->startp[0]; + matchend = prog->endp[0]; + } + else + { + match_ok = FALSE; + break; + } + } + if (!match_ok) + continue; + } + if (dir == BACKWARD && !want_start) + { + /* + * Now, if there are multiple matches on this line, + * we have to get the last one. Or the last one before + * the cursor, if we're on that line. + * When putting the new cursor at the end, compare + * relative to the end of the match. + */ + match_ok = FALSE; + for (;;) + { + if (!at_first_line || ((options & SEARCH_END) ? + ((prog->endp[0] - ptr) - 1 + extra_col + <= (int)start_pos.col) : + ((prog->startp[0] - ptr) + extra_col + <= (int)start_pos.col))) + { + match_ok = TRUE; + match = prog->startp[0]; + matchend = prog->endp[0]; + } + else + break; + /* + * If vi-compatible searching, continue at the end + * of the match, otherwise continue one position + * forward. + */ + if (vim_strchr(p_cpo, CPO_SEARCH) != NULL) + { + p = matchend; + if (p == match && *p != NUL) + ++p; + } + else + { + p = match; + if (*p != NUL) + ++p; + } + if (*p == NUL || !vim_regexec(prog, p, (int)FALSE)) + break; + } + + /* + * If there is only a match after the cursor, skip + * this match. + */ + if (!match_ok) + continue; + } + + pos->lnum = lnum; + if (options & SEARCH_END && !(options & SEARCH_NOOF)) + pos->col = (int) (matchend - ptr - 1); + else + pos->col = (int) (match - ptr); + found = 1; + break; + } + line_breakcheck(); /* stop if ctrl-C typed */ + if (got_int) + break; + + if (loop && lnum == start_pos.lnum) + break; /* if second loop, stop where started */ + } + at_first_line = FALSE; + + /* + * stop the search if wrapscan isn't set, after an interrupt and + * after a match + */ + if (!p_ws || got_int || found) + break; + + /* + * If 'wrapscan' is set we continue at the other end of the file. + * If 'shortmess' does not contain 's', we give a message. + * This message is also remembered in keep_msg for when the screen + * is redrawn. The keep_msg is cleared whenever another message is + * written. + */ + if (dir == BACKWARD) /* start second loop at the other end */ + { + lnum = curbuf->b_ml.ml_line_count; + if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG)) + give_warning(top_bot_msg); + } + else + { + lnum = 1; + if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG)) + give_warning(bot_top_msg); + } + } + if (got_int) + break; + } + while (--count > 0 && found); /* stop after count matches or no match */ + + vim_free(prog); + + if (!found) /* did not find it */ + { + if (got_int) + emsg(e_interr); + else if ((options & SEARCH_MSG) == SEARCH_MSG) + { + if (p_ws) + EMSG2("Pattern not found: %s", mr_pattern); + else if (lnum == 0) + EMSG2("search hit TOP without match for: %s", mr_pattern); + else + EMSG2("search hit BOTTOM without match for: %s", mr_pattern); + } + return FAIL; + } + search_match_len = matchend - match; + + return OK; +} + +/* + * Highest level string search function. + * Search for the 'count'th occurence of string 'str' in direction 'dirc' + * If 'dirc' is 0: use previous dir. + * If 'str' is NULL or empty : use previous string. + * If 'options & SEARCH_REV' : go in reverse of previous dir. + * If 'options & SEARCH_ECHO': echo the search command and handle options + * If 'options & SEARCH_MSG' : may give error message + * If 'options & SEARCH_OPT' : interpret optional flags + * If 'options & SEARCH_HIS' : put search pattern in history + * If 'options & SEARCH_NOOF': don't add offset to position + * If 'options & SEARCH_MARK': set previous context mark + * If 'options & SEARCH_KEEP': keep previous search pattern + * + * Careful: If lastoffline == TRUE and lastoff == 0 this makes the + * movement linewise without moving the match position. + * + * return 0 for failure, 1 for found, 2 for found and line offset added + */ + int +do_search(dirc, str, count, options) + int dirc; + char_u *str; + long count; + int options; +{ + FPOS pos; /* position of the last match */ + char_u *searchstr; + static int lastsdir = '/'; /* previous search direction */ + static int lastoffline;/* previous/current search has line offset */ + static int lastend; /* previous/current search set cursor at end */ + static long lastoff; /* previous/current line or char offset */ + int old_lastsdir; + int old_lastoffline; + int old_lastend; + long old_lastoff; + int retval; /* Return value */ + register char_u *p; + register long c; + char_u *dircp; + int i = 0; /* init for GCC */ + + /* + * A line offset is not remembered, this is vi compatible. + */ + if (lastoffline && vim_strchr(p_cpo, CPO_LINEOFF) != NULL) + { + lastoffline = FALSE; + lastoff = 0; + } + + /* + * Save the values for when (options & SEARCH_KEEP) is used. + * (there is no "if ()" around this because gcc wants them initialized) + */ + old_lastsdir = lastsdir; + old_lastoffline = lastoffline; + old_lastend = lastend; + old_lastoff = lastoff; + + pos = curwin->w_cursor; /* start searching at the cursor position */ + + /* + * Find out the direction of the search. + */ + if (dirc == 0) + dirc = lastsdir; + else + lastsdir = dirc; + if (options & SEARCH_REV) + { +#ifdef WIN32 + /* There is a bug in the Visual C++ 2.2 compiler which means that + * dirc always ends up being '/' */ + dirc = (dirc == '/') ? '?' : '/'; +#else + if (dirc == '/') + dirc = '?'; + else + dirc = '/'; +#endif + } + + /* + * Repeat the search when pattern followed by ';', e.g. "/foo/;?bar". + */ + for (;;) + { + searchstr = str; + dircp = NULL; + /* use previous pattern */ + if (str == NULL || *str == NUL || *str == dirc) + { + if (search_pattern == NULL) /* no previous pattern */ + { + emsg(e_noprevre); + retval = 0; + goto end_do_search; + } + searchstr = (char_u *)""; /* make myregcomp() use search_pattern */ + } + + if (str != NULL && *str != NUL) /* look for (new) offset */ + { + /* + * Find end of regular expression. + * If there is a matching '/' or '?', toss it. + */ + p = skip_regexp(str, dirc); + if (*p == dirc) + { + dircp = p; /* remember where we put the NUL */ + *p++ = NUL; + } + lastoffline = FALSE; + lastend = FALSE; + lastoff = 0; + /* + * Check for a line offset or a character offset. + * For get_address (echo off) we don't check for a character + * offset, because it is meaningless and the 's' could be a + * substitute command. + */ + if (*p == '+' || *p == '-' || isdigit(*p)) + lastoffline = TRUE; + else if ((options & SEARCH_OPT) && + (*p == 'e' || *p == 's' || *p == 'b')) + { + if (*p == 'e') /* end */ + lastend = SEARCH_END; + ++p; + } + if (isdigit(*p) || *p == '+' || *p == '-') /* got an offset */ + { + if (isdigit(*p) || isdigit(*(p + 1))) + lastoff = atol((char *)p); /* 'nr' or '+nr' or '-nr' */ + else if (*p == '-') /* single '-' */ + lastoff = -1; + else /* single '+' */ + lastoff = 1; + ++p; + while (isdigit(*p)) /* skip number */ + ++p; + } + searchcmdlen = p - str; /* compute length of search command + for get_address() */ + str = p; /* put str after search command */ + } + + if (options & SEARCH_ECHO) + { + msg_start(); + msg_outchar(dirc); + msg_outtrans(*searchstr == NUL ? search_pattern : searchstr); + if (lastoffline || lastend || lastoff) + { + msg_outchar(dirc); + if (lastend) + msg_outchar('e'); + else if (!lastoffline) + msg_outchar('s'); + if (lastoff < 0) + { + msg_outchar('-'); + msg_outnum((long)-lastoff); + } + else if (lastoff > 0 || lastoffline) + { + msg_outchar('+'); + msg_outnum((long)lastoff); + } + } + msg_clr_eos(); + (void)msg_check(); + + gotocmdline(FALSE); + flushbuf(); + } + + /* + * If there is a character offset, subtract it from the current + * position, so we don't get stuck at "?pat?e+2" or "/pat/s-2". + * This is not done for a line offset, because then we would not be vi + * compatible. + */ + if (!lastoffline && lastoff) + { + if (lastoff > 0) + { + c = lastoff; + while (c--) + if ((i = dec(&pos)) != 0) + break; + if (i == -1) /* at start of buffer */ + goto_endofbuf(&pos); + } + else + { + c = -lastoff; + while (c--) + if ((i = inc(&pos)) != 0) + break; + if (i == -1) /* at end of buffer */ + { + pos.lnum = 1; + pos.col = 0; + } + } + } + + c = searchit(&pos, dirc == '/' ? FORWARD : BACKWARD, searchstr, count, + lastend + (options & + (SEARCH_KEEP + SEARCH_HIS + SEARCH_MSG + + ((str != NULL && *str == ';') ? 0 : SEARCH_NOOF))), + 2); + if (dircp != NULL) + *dircp = dirc; /* put second '/' or '?' back for normal() */ + if (c == FAIL) + { + retval = 0; + goto end_do_search; + } + if (lastend) + op_inclusive = TRUE; /* 'e' includes last character */ + + retval = 1; /* pattern found */ + + /* + * Add character and/or line offset + */ + if (!(options & SEARCH_NOOF) || *str == ';') + { + if (lastoffline) /* Add the offset to the line number. */ + { + c = pos.lnum + lastoff; + if (c < 1) + pos.lnum = 1; + else if (c > curbuf->b_ml.ml_line_count) + pos.lnum = curbuf->b_ml.ml_line_count; + else + pos.lnum = c; + pos.col = 0; + + retval = 2; /* pattern found, line offset added */ + } + else + { + if (lastoff > 0) /* to the right, check for end of line */ + { + p = ml_get_pos(&pos) + 1; + c = lastoff; + while (c-- && *p++ != NUL) + ++pos.col; + } + else /* to the left, check for start of line */ + { + if ((c = pos.col + lastoff) < 0) + c = 0; + pos.col = c; + } + } + } + + /* + * The search command can be followed by a ';' to do another search. + * For example: "/pat/;/foo/+3;?bar" + * This is like doing another search command, except: + * - The remembered direction '/' or '?' is from the first search. + * - When an error happens the cursor isn't moved at all. + * Don't do this when called by get_address() (it handles ';' itself). + */ + if (!(options & SEARCH_OPT) || str == NULL || *str != ';') + break; + + dirc = *++str; + if (dirc != '?' && dirc != '/') + { + retval = 0; + EMSG("Expected '?' or '/' after ';'"); + goto end_do_search; + } + ++str; + } + + if (options & SEARCH_MARK) + setpcmark(); + curwin->w_cursor = pos; + curwin->w_set_curswant = TRUE; + +end_do_search: + if (options & SEARCH_KEEP) + { + lastsdir = old_lastsdir; + lastoffline = old_lastoffline; + lastend = old_lastend; + lastoff = old_lastoff; + } + return retval; +} + +/* + * search_for_exact_line(pos, dir, pat) + * + * Search for a line starting with the given pattern (ignoring leading + * white-space), starting from pos and going in direction dir. pos will + * contain the position of the match found. Blank lines will never match. + * Return OK for success, or FAIL if no line found. + */ + int +search_for_exact_line(pos, dir, pat) + FPOS *pos; + int dir; + char_u *pat; +{ + linenr_t start = 0; + char_u *ptr; + char_u *p; + + if (curbuf->b_ml.ml_line_count == 0) + return FAIL; + for (;;) + { + pos->lnum += dir; + if (pos->lnum < 1) + { + if (p_ws) + { + pos->lnum = curbuf->b_ml.ml_line_count; + if (!shortmess(SHM_SEARCH)) + give_warning(top_bot_msg); + } + else + { + pos->lnum = 1; + break; + } + } + else if (pos->lnum > curbuf->b_ml.ml_line_count) + { + if (p_ws) + { + pos->lnum = 1; + if (!shortmess(SHM_SEARCH)) + give_warning(bot_top_msg); + } + else + { + pos->lnum = 1; + break; + } + } + if (pos->lnum == start) + break; + if (start == 0) + start = pos->lnum; + ptr = ml_get(pos->lnum); + p = skipwhite(ptr); + pos->col = p - ptr; + if (*p != NUL && STRNCMP(p, pat, STRLEN(pat)) == 0) + return OK; + else if (*p != NUL && p_ic) + { + ptr = pat; + while (*p && TO_LOWER(*p) == TO_LOWER(*ptr)) + { + ++p; + ++ptr; + } + if (*ptr == NUL) + return OK; + } + } + return FAIL; +} + +/* + * Character Searches + */ + +/* + * searchc(c, dir, type, count) + * + * Search for character 'c', in direction 'dir'. If 'type' is 0, move to the + * position of the character, otherwise move to just before the char. + * Repeat this 'count' times. + */ + int +searchc(c, dir, type, count) + int c; + register int dir; + int type; + long count; +{ + static int lastc = NUL; /* last character searched for */ + static int lastcdir; /* last direction of character search */ + static int lastctype; /* last type of search ("find" or "to") */ + register int col; + char_u *p; + int len; + + if (c != NUL) /* normal search: remember args for repeat */ + { + if (!KeyStuffed) /* don't remember when redoing */ + { + lastc = c; + lastcdir = dir; + lastctype = type; + } + } + else /* repeat previous search */ + { + if (lastc == NUL) + return FALSE; + if (dir) /* repeat in opposite direction */ + dir = -lastcdir; + else + dir = lastcdir; + type = lastctype; + c = lastc; + } + + p = ml_get_curline(); + col = curwin->w_cursor.col; + len = STRLEN(p); + + while (count--) + { + for (;;) + { + if ((col += dir) < 0 || col >= len) + return FALSE; + if (p[col] == c) + break; + } + } + if (type) + col -= dir; + curwin->w_cursor.col = col; + return TRUE; +} + +/* + * "Other" Searches + */ + +/* + * findmatch - find the matching paren or brace + * + * Improvement over vi: Braces inside quotes are ignored. + */ + FPOS * +findmatch(initc) + int initc; +{ + return findmatchlimit(initc, 0, 0); +} + +/* + * findmatchlimit -- find the matching paren or brace, if it exists within + * maxtravel lines of here. A maxtravel of 0 means search until you fall off + * the edge of the file. + * + * flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#') + * FM_FORWARD search forwards (when initc is '/', '*' or '#') + * FM_BLOCKSTOP stop at start/end of block ({ or } in column 0) + * FM_SKIPCOMM skip comments (not implemented yet!) + */ + + FPOS * +findmatchlimit(initc, flags, maxtravel) + int initc; + int flags; + int maxtravel; +{ + static FPOS pos; /* current search position */ + int findc; /* matching brace */ + int c; + int count = 0; /* cumulative number of braces */ + int idx = 0; /* init for gcc */ + static char_u table[6] = {'(', ')', '[', ']', '{', '}'}; +#ifdef RIGHTLEFT + static char_u table_rl[6] = {')', '(', ']', '[', '}', '{'}; +#endif + int inquote = FALSE; /* TRUE when inside quotes */ + register char_u *linep; /* pointer to current line */ + char_u *ptr; + int do_quotes; /* check for quotes in current line */ + int at_start; /* do_quotes value at start position */ + int hash_dir = 0; /* Direction searched for # things */ + int comment_dir = 0; /* Direction searched for comments */ + FPOS match_pos; /* Where last slash-star was found */ + int start_in_quotes; /* start position is in quotes */ + int traveled = 0; /* how far we've searched so far */ + int ignore_cend = FALSE; /* ignore comment end */ + int cpo_match; /* vi compatible matching */ + int dir; /* Direction to search */ + + pos = curwin->w_cursor; + linep = ml_get(pos.lnum); + + cpo_match = (vim_strchr(p_cpo, CPO_MATCH) != NULL); + + /* Direction to search when initc is '/', '*' or '#' */ + if (flags & FM_BACKWARD) + dir = BACKWARD; + else if (flags & FM_FORWARD) + dir = FORWARD; + else + dir = 0; + + /* + * if initc given, look in the table for the matching character + * '/' and '*' are special cases: look for start or end of comment. + * When '/' is used, we ignore running backwards into an star-slash, for + * "[*" command, we just want to find any comment. + */ + if (initc == '/' || initc == '*') + { + comment_dir = dir; + if (initc == '/') + ignore_cend = TRUE; + idx = (dir == FORWARD) ? 0 : 1; + initc = NUL; + } + else if (initc != '#' && initc != NUL) + { + for (idx = 0; idx < 6; ++idx) +#ifdef RIGHTLEFT + if ((curwin->w_p_rl ? table_rl : table)[idx] == initc) + { + initc = (curwin->w_p_rl ? table_rl : table)[idx = idx ^ 1]; + +#else + if (table[idx] == initc) + { + initc = table[idx = idx ^ 1]; +#endif + break; + } + if (idx == 6) /* invalid initc! */ + return NULL; + } + /* + * Either initc is '#', or no initc was given and we need to look under the + * cursor. + */ + else + { + if (initc == '#') + { + hash_dir = dir; + } + else + { + /* + * initc was not given, must look for something to match under + * or near the cursor. + */ + if (linep[0] == '#' && pos.col == 0) + { + /* If it's not #if, #else etc, we should look for a brace + * instead */ + for (c = 1; vim_iswhite(linep[c]); c++) + ; + if (STRNCMP(linep + c, "if", (size_t)2) == 0 || + STRNCMP(linep + c, "endif", (size_t)5) == 0 || + STRNCMP(linep + c, "el", (size_t)2) == 0) + hash_dir = 1; + } + + /* + * Are we on a comment? + */ + else if (linep[pos.col] == '/') + { + if (linep[pos.col + 1] == '*') + { + comment_dir = 1; + idx = 0; + pos.col++; + } + else if (pos.col > 0 && linep[pos.col - 1] == '*') + { + comment_dir = -1; + idx = 1; + pos.col--; + } + } + else if (linep[pos.col] == '*') + { + if (linep[pos.col + 1] == '/') + { + comment_dir = -1; + idx = 1; + } + else if (pos.col > 0 && linep[pos.col - 1] == '/') + { + comment_dir = 1; + idx = 0; + } + } + + /* + * If we are not on a comment or the # at the start of a line, then + * look for brace anywhere on this line after the cursor. + */ + if (!hash_dir && !comment_dir) + { + /* + * find the brace under or after the cursor + */ + linep = ml_get(pos.lnum); + idx = 6; /* error if this line is empty */ + for (;;) + { + initc = linep[pos.col]; + if (initc == NUL) + break; + + for (idx = 0; idx < 6; ++idx) +#ifdef RIGHTLEFT + if ((curwin->w_p_rl ? table_rl : table)[idx] == initc) +#else + if (table[idx] == initc) +#endif + break; + if (idx != 6) + break; + ++pos.col; + } + if (idx == 6) + { + if (linep[0] == '#') + hash_dir = 1; + else + return NULL; + } + } + } + if (hash_dir) + { + /* + * Look for matching #if, #else, #elif, or #endif + */ + op_motion_type = MLINE; /* Linewise for this case only */ + if (initc != '#') + { + ptr = skipwhite(linep + 1); + if (STRNCMP(ptr, "if", (size_t)2) == 0 || + STRNCMP(ptr, "el", (size_t)2) == 0) + hash_dir = 1; + else if (STRNCMP(ptr, "endif", (size_t)5) == 0) + hash_dir = -1; + else + return NULL; + } + pos.col = 0; + while (!got_int) + { + if (hash_dir > 0) + { + if (pos.lnum == curbuf->b_ml.ml_line_count) + break; + } + else if (pos.lnum == 1) + break; + pos.lnum += hash_dir; + linep = ml_get(pos.lnum); + line_breakcheck(); + if (linep[0] != '#') + continue; + ptr = linep + 1; + while (*ptr == ' ' || *ptr == TAB) + ptr++; + if (hash_dir > 0) + { + if (STRNCMP(ptr, "if", (size_t)2) == 0) + count++; + else if (STRNCMP(ptr, "el", (size_t)2) == 0) + { + if (count == 0) + return &pos; + } + else if (STRNCMP(ptr, "endif", (size_t)5) == 0) + { + if (count == 0) + return &pos; + count--; + } + } + else + { + if (STRNCMP(ptr, "if", (size_t)2) == 0) + { + if (count == 0) + return &pos; + count--; + } + else if (initc == '#' && STRNCMP(ptr, "el", (size_t)2) == 0) + { + if (count == 0) + return &pos; + } + else if (STRNCMP(ptr, "endif", (size_t)5) == 0) + count++; + } + } + return NULL; + } + } + +#ifdef RIGHTLEFT + findc = (curwin->w_p_rl ? table_rl : table)[idx ^ 1]; +#else + findc = table[idx ^ 1]; /* get matching brace */ +#endif + idx &= 1; + + do_quotes = -1; + start_in_quotes = MAYBE; + while (!got_int) + { + /* + * Go to the next position, forward or backward. We could use + * inc() and dec() here, but that is much slower + */ + if (idx) /* backward search */ + { + if (pos.col == 0) /* at start of line, go to prev. one */ + { + if (pos.lnum == 1) /* start of file */ + break; + --pos.lnum; + + if (maxtravel && traveled++ > maxtravel) + break; + + linep = ml_get(pos.lnum); + pos.col = STRLEN(linep); /* put pos.col on trailing NUL */ + do_quotes = -1; + line_breakcheck(); + } + else + --pos.col; + } + else /* forward search */ + { + if (linep[pos.col] == NUL) /* at end of line, go to next one */ + { + if (pos.lnum == curbuf->b_ml.ml_line_count) /* end of file */ + break; + ++pos.lnum; + + if (maxtravel && traveled++ > maxtravel) + break; + + linep = ml_get(pos.lnum); + pos.col = 0; + do_quotes = -1; + line_breakcheck(); + } + else + ++pos.col; + } + + /* + * If FM_BLOCKSTOP given, stop at a '{' or '}' in column 0. + */ + if (pos.col == 0 && (flags & FM_BLOCKSTOP) && + (linep[0] == '{' || linep[0] == '}')) + { + if (linep[0] == findc && count == 0) /* match! */ + return &pos; + break; /* out of scope */ + } + + if (comment_dir) + { + /* Note: comments do not nest, and we ignore quotes in them */ + if (comment_dir == 1) + { + if (linep[pos.col] == '*' && linep[pos.col + 1] == '/') + { + pos.col++; + return &pos; + } + } + else /* Searching backwards */ + { + /* + * A comment may contain slash-star, it may also start or end + * with slash-star-slash. I'm not using real examples though + * because "gcc -Wall" would complain -- webb + */ + if (pos.col == 0) + continue; + else if (linep[pos.col - 1] == '/' && linep[pos.col] == '*') + { + count++; + match_pos = pos; + match_pos.col--; + } + else if (linep[pos.col - 1] == '*' && linep[pos.col] == '/') + { + if (count > 0) + pos = match_pos; + else if (pos.col > 1 && linep[pos.col - 2] == '/') + pos.col -= 2; + else if (ignore_cend) + continue; + else + return NULL; + return &pos; + } + } + continue; + } + + /* + * If smart matching ('cpoptions' does not contain '%'), braces inside + * of quotes are ignored, but only if there is an even number of + * quotes in the line. + */ + if (cpo_match) + do_quotes = 0; + else if (do_quotes == -1) + { + /* + * count the number of quotes in the line, skipping \" and '"' + */ + at_start = do_quotes; + for (ptr = linep; *ptr; ++ptr) + { + if (ptr == linep + curwin->w_cursor.col) + at_start = (do_quotes & 1); + if (*ptr == '"' && (ptr == linep || ptr[-1] != '\\') && + (ptr == linep || ptr[-1] != '\'' || ptr[1] != '\'')) + ++do_quotes; + } + do_quotes &= 1; /* result is 1 with even number of quotes */ + + /* + * If we find an uneven count, check current line and previous + * one for a '\' at the end. + */ + if (!do_quotes) + { + inquote = FALSE; + if (ptr[-1] == '\\') + { + do_quotes = 1; + if (start_in_quotes == MAYBE) + { + inquote = !at_start; + if (inquote) + start_in_quotes = TRUE; + } + else if (idx) /* backward search */ + inquote = TRUE; + } + if (pos.lnum > 1) + { + ptr = ml_get(pos.lnum - 1); + if (*ptr && *(ptr + STRLEN(ptr) - 1) == '\\') + { + do_quotes = 1; + if (start_in_quotes == MAYBE) + { + inquote = at_start; + if (inquote) + start_in_quotes = TRUE; + } + else if (!idx) /* forward search */ + inquote = TRUE; + } + } + } + } + if (start_in_quotes == MAYBE) + start_in_quotes = FALSE; + + /* + * If 'smartmatch' is set: + * Things inside quotes are ignored by setting 'inquote'. If we + * find a quote without a preceding '\' invert 'inquote'. At the + * end of a line not ending in '\' we reset 'inquote'. + * + * In lines with an uneven number of quotes (without preceding '\') + * we do not know which part to ignore. Therefore we only set + * inquote if the number of quotes in a line is even, unless this + * line or the previous one ends in a '\'. Complicated, isn't it? + */ + switch (c = linep[pos.col]) + { + case NUL: + /* at end of line without trailing backslash, reset inquote */ + if (pos.col == 0 || linep[pos.col - 1] != '\\') + { + inquote = FALSE; + start_in_quotes = FALSE; + } + break; + + case '"': + /* a quote that is preceded with a backslash is ignored */ + if (do_quotes && (pos.col == 0 || linep[pos.col - 1] != '\\')) + { + inquote = !inquote; + start_in_quotes = FALSE; + } + break; + + /* + * If smart matching ('cpoptions' does not contain '%'): + * Skip things in single quotes: 'x' or '\x'. Be careful for single + * single quotes, eg jon's. Things like '\233' or '\x3f' are not + * skipped, there is never a brace in them. + */ + case '\'': + if (vim_strchr(p_cpo, CPO_MATCH) == NULL) + { + if (idx) /* backward search */ + { + if (pos.col > 1) + { + if (linep[pos.col - 2] == '\'') + pos.col -= 2; + else if (linep[pos.col - 2] == '\\' && + pos.col > 2 && linep[pos.col - 3] == '\'') + pos.col -= 3; + } + } + else if (linep[pos.col + 1]) /* forward search */ + { + if (linep[pos.col + 1] == '\\' && + linep[pos.col + 2] && linep[pos.col + 3] == '\'') + pos.col += 3; + else if (linep[pos.col + 2] == '\'') + pos.col += 2; + } + } + break; + + default: + /* Check for match outside of quotes, and inside of + * quotes when the start is also inside of quotes */ + if (!inquote || start_in_quotes == TRUE) + { + if (c == initc) + count++; + else if (c == findc) + { + if (count == 0) + return &pos; + count--; + } + } + } + } + + if (comment_dir == -1 && count > 0) + { + pos = match_pos; + return &pos; + } + return (FPOS *) NULL; /* never found it */ +} + +/* + * Move cursor briefly to character matching the one under the cursor. + * Show the match only if it is visible on the screen. + */ + void +showmatch() +{ + FPOS *lpos, csave; + colnr_t vcol; + + if ((lpos = findmatch(NUL)) == NULL) /* no match, so beep */ + beep_flush(); + else if (lpos->lnum >= curwin->w_topline) + { + if (!curwin->w_p_wrap) + getvcol(curwin, lpos, NULL, &vcol, NULL); + if (curwin->w_p_wrap || (vcol >= curwin->w_leftcol && + vcol < curwin->w_leftcol + Columns)) + { + updateScreen(VALID_TO_CURSCHAR); /* show the new char first */ + csave = curwin->w_cursor; + curwin->w_cursor = *lpos; /* move to matching char */ + cursupdate(); + showruler(0); + setcursor(); + cursor_on(); /* make sure that the cursor is shown */ + flushbuf(); + + /* + * brief pause, unless 'm' is present in 'cpo' and a character is + * available. + */ + if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) + mch_delay(500L, TRUE); + else if (!char_avail()) + mch_delay(500L, FALSE); + curwin->w_cursor = csave; /* restore cursor position */ + cursupdate(); + } + } +} + +/* + * findsent(dir, count) - Find the start of the next sentence in direction + * 'dir' Sentences are supposed to end in ".", "!" or "?" followed by white + * space or a line break. Also stop at an empty line. + * Return OK if the next sentence was found. + */ + int +findsent(dir, count) + int dir; + long count; +{ + FPOS pos, tpos; + register int c; + int (*func) __PARMS((FPOS *)); + int startlnum; + int noskip = FALSE; /* do not skip blanks */ + + pos = curwin->w_cursor; + if (dir == FORWARD) + func = incl; + else + func = decl; + + while (count--) + { + /* + * if on an empty line, skip upto a non-empty line + */ + if (gchar(&pos) == NUL) + { + do + if ((*func)(&pos) == -1) + break; + while (gchar(&pos) == NUL); + if (dir == FORWARD) + goto found; + } + /* + * if on the start of a paragraph or a section and searching forward, + * go to the next line + */ + else if (dir == FORWARD && pos.col == 0 && + startPS(pos.lnum, NUL, FALSE)) + { + if (pos.lnum == curbuf->b_ml.ml_line_count) + return FAIL; + ++pos.lnum; + goto found; + } + else if (dir == BACKWARD) + decl(&pos); + + /* go back to the previous non-blank char */ + while ((c = gchar(&pos)) == ' ' || c == '\t' || + (dir == BACKWARD && vim_strchr((char_u *)".!?)]\"'", c) != NULL)) + { + if (decl(&pos) == -1) + break; + /* when going forward: Stop in front of empty line */ + if (lineempty(pos.lnum) && dir == FORWARD) + { + incl(&pos); + goto found; + } + } + + /* remember the line where the search started */ + startlnum = pos.lnum; + + for (;;) /* find end of sentence */ + { + c = gchar(&pos); + if (c == NUL || (pos.col == 0 && startPS(pos.lnum, NUL, FALSE))) + { + if (dir == BACKWARD && pos.lnum != startlnum) + ++pos.lnum; + break; + } + if (c == '.' || c == '!' || c == '?') + { + tpos = pos; + do + if ((c = inc(&tpos)) == -1) + break; + while (vim_strchr((char_u *)")]\"'", c = gchar(&tpos)) != NULL); + if (c == -1 || c == ' ' || c == '\t' || c == NUL) + { + pos = tpos; + if (gchar(&pos) == NUL) /* skip NUL at EOL */ + inc(&pos); + break; + } + } + if ((*func)(&pos) == -1) + { + if (count) + return FAIL; + noskip = TRUE; + break; + } + } +found: + /* skip white space */ + while (!noskip && ((c = gchar(&pos)) == ' ' || c == '\t')) + if (incl(&pos) == -1) + break; + } + + setpcmark(); + curwin->w_cursor = pos; + return OK; +} + +/* + * findpar(dir, count, what) - Find the next paragraph in direction 'dir' + * Paragraphs are currently supposed to be separated by empty lines. + * Return TRUE if the next paragraph was found. + * If 'what' is '{' or '}' we go to the next section. + * If 'both' is TRUE also stop at '}'. + */ + int +findpar(dir, count, what, both) + register int dir; + long count; + int what; + int both; +{ + register linenr_t curr; + int did_skip; /* TRUE after separating lines have + been skipped */ + int first; /* TRUE on first line */ + + curr = curwin->w_cursor.lnum; + + while (count--) + { + did_skip = FALSE; + for (first = TRUE; ; first = FALSE) + { + if (*ml_get(curr) != NUL) + did_skip = TRUE; + + if (!first && did_skip && startPS(curr, what, both)) + break; + + if ((curr += dir) < 1 || curr > curbuf->b_ml.ml_line_count) + { + if (count) + return FALSE; + curr -= dir; + break; + } + } + } + setpcmark(); + if (both && *ml_get(curr) == '}') /* include line with '}' */ + ++curr; + curwin->w_cursor.lnum = curr; + if (curr == curbuf->b_ml.ml_line_count) + { + if ((curwin->w_cursor.col = STRLEN(ml_get(curr))) != 0) + { + --curwin->w_cursor.col; + op_inclusive = TRUE; + } + } + else + curwin->w_cursor.col = 0; + return TRUE; +} + +/* + * check if the string 's' is a nroff macro that is in option 'opt' + */ + static int +inmacro(opt, s) + char_u *opt; + register char_u *s; +{ + register char_u *macro; + + for (macro = opt; macro[0]; ++macro) + { + if (macro[0] == s[0] && (((s[1] == NUL || s[1] == ' ') && + (macro[1] == NUL || macro[1] == ' ')) || macro[1] == s[1])) + break; + ++macro; + if (macro[0] == NUL) + break; + } + return (macro[0] != NUL); +} + +/* + * startPS: return TRUE if line 'lnum' is the start of a section or paragraph. + * If 'para' is '{' or '}' only check for sections. + * If 'both' is TRUE also stop at '}' + */ + int +startPS(lnum, para, both) + linenr_t lnum; + int para; + int both; +{ + register char_u *s; + + s = ml_get(lnum); + if (*s == para || *s == '\f' || (both && *s == '}')) + return TRUE; + if (*s == '.' && (inmacro(p_sections, s + 1) || + (!para && inmacro(p_para, s + 1)))) + return TRUE; + return FALSE; +} + +/* + * The following routines do the word searches performed by the 'w', 'W', + * 'b', 'B', 'e', and 'E' commands. + */ + +/* + * To perform these searches, characters are placed into one of three + * classes, and transitions between classes determine word boundaries. + * + * The classes are: + * + * 0 - white space + * 1 - keyword charactes (letters, digits and underscore) + * 2 - everything else + */ + +static int stype; /* type of the word motion being performed */ + +/* + * cls() - returns the class of character at curwin->w_cursor + * + * The 'type' of the current search modifies the classes of characters if a + * 'W', 'B', or 'E' motion is being done. In this case, chars. from class 2 + * are reported as class 1 since only white space boundaries are of interest. + */ + static int +cls() +{ + register int c; + + c = gchar_cursor(); + if (c == ' ' || c == '\t' || c == NUL) + return 0; + + if (iswordchar(c)) + return 1; + + /* + * If stype is non-zero, report these as class 1. + */ + return (stype == 0) ? 2 : 1; +} + + +/* + * fwd_word(count, type, eol) - move forward one word + * + * Returns FAIL if the cursor was already at the end of the file. + * If eol is TRUE, last word stops at end of line (for operators). + */ + int +fwd_word(count, type, eol) + long count; + int type; + int eol; +{ + int sclass; /* starting class */ + int i; + int last_line; + + stype = type; + while (--count >= 0) + { + sclass = cls(); + + /* + * We always move at least one character, unless on the last character + * in the buffer. + */ + last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count); + i = inc_cursor(); + if (i == -1 || (i == 1 && last_line)) + /* started at last char in file */ + return FAIL; + if (i == 1 && eol && count == 0) /* started at last char in line */ + return OK; + + /* + * Go one char past end of current word (if any) + */ + if (sclass != 0) + while (cls() == sclass) + { + i = inc_cursor(); + if (i == -1 || (i == 1 && eol && count == 0)) + return OK; + } + + /* + * go to next non-white + */ + while (cls() == 0) + { + /* + * We'll stop if we land on a blank line + */ + if (curwin->w_cursor.col == 0 && *ml_get_curline() == NUL) + break; + + i = inc_cursor(); + if (i == -1 || (i == 1 && eol && count == 0)) + return OK; + } + } + return OK; +} + +/* + * bck_word() - move backward 'count' words + * + * If stop is TRUE and we are already on the start of a word, move one less. + * + * Returns FAIL if top of the file was reached. + */ + int +bck_word(count, type, stop) + long count; + int type; + int stop; +{ + int sclass; /* starting class */ + + stype = type; + while (--count >= 0) + { + sclass = cls(); + if (dec_cursor() == -1) /* started at start of file */ + return FAIL; + + if (!stop || sclass == cls() || sclass == 0) + { + /* + * Skip white space before the word. + * Stop on an empty line. + */ + while (cls() == 0) + { + if (curwin->w_cursor.col == 0 && + lineempty(curwin->w_cursor.lnum)) + goto finished; + + if (dec_cursor() == -1) /* hit start of file, stop here */ + return OK; + } + + /* + * Move backward to start of this word. + */ + if (skip_chars(cls(), BACKWARD)) + return OK; + } + + inc_cursor(); /* overshot - forward one */ +finished: + stop = FALSE; + } + return OK; +} + +/* + * end_word() - move to the end of the word + * + * There is an apparent bug in the 'e' motion of the real vi. At least on the + * System V Release 3 version for the 80386. Unlike 'b' and 'w', the 'e' + * motion crosses blank lines. When the real vi crosses a blank line in an + * 'e' motion, the cursor is placed on the FIRST character of the next + * non-blank line. The 'E' command, however, works correctly. Since this + * appears to be a bug, I have not duplicated it here. + * + * Returns FAIL if end of the file was reached. + * + * If stop is TRUE and we are already on the end of a word, move one less. + * If empty is TRUE stop on an empty line. + */ + int +end_word(count, type, stop, empty) + long count; + int type; + int stop; + int empty; +{ + int sclass; /* starting class */ + + stype = type; + while (--count >= 0) + { + sclass = cls(); + if (inc_cursor() == -1) + return FAIL; + + /* + * If we're in the middle of a word, we just have to move to the end + * of it. + */ + if (cls() == sclass && sclass != 0) + { + /* + * Move forward to end of the current word + */ + if (skip_chars(sclass, FORWARD)) + return FAIL; + } + else if (!stop || sclass == 0) + { + /* + * We were at the end of a word. Go to the end of the next word. + * First skip white space, if 'empty' is TRUE, stop at empty line. + */ + while (cls() == 0) + { + if (empty && curwin->w_cursor.col == 0 && + lineempty(curwin->w_cursor.lnum)) + goto finished; + if (inc_cursor() == -1) /* hit end of file, stop here */ + return FAIL; + } + + /* + * Move forward to the end of this word. + */ + if (skip_chars(cls(), FORWARD)) + return FAIL; + } + dec_cursor(); /* overshot - one char backward */ +finished: + stop = FALSE; /* we move only one word less */ + } + return OK; +} + +/* + * bckend_word(count, type) - move back to the end of the word + * + * If 'eol' is TRUE, stop at end of line. + * + * Returns FAIL if start of the file was reached. + */ + int +bckend_word(count, type, eol) + long count; + int type; + int eol; +{ + int sclass; /* starting class */ + int i; + + stype = type; + while (--count >= 0) + { + sclass = cls(); + if ((i = dec_cursor()) == -1) + return FAIL; + if (eol && i == 1) + return OK; + + /* + * Move backward to before the start of this word. + */ + if (sclass != 0) + { + while (cls() == sclass) + if ((i = dec_cursor()) == -1 || (eol && i == 1)) + return OK; + } + + /* + * Move backward to end of the previous word + */ + while (cls() == 0) + { + if (curwin->w_cursor.col == 0 && lineempty(curwin->w_cursor.lnum)) + break; + if ((i = dec_cursor()) == -1 || (eol && i == 1)) + return OK; + } + } + return OK; +} + + static int +skip_chars(cclass, dir) + int cclass; + int dir; +{ + while (cls() == cclass) + if ((dir == FORWARD ? inc_cursor() : dec_cursor()) == -1) + return TRUE; + return FALSE; +} + +/* + * Go back to the start of the word or the start of white space + */ + static void +back_in_line() +{ + int sclass; /* starting class */ + + sclass = cls(); + for (;;) + { + if (curwin->w_cursor.col == 0) /* stop at start of line */ + break; + --curwin->w_cursor.col; + if (cls() != sclass) /* stop at start of word */ + { + ++curwin->w_cursor.col; + break; + } + } +} + +/* + * Find word under cursor, cursor at end + */ + int +current_word(count, type) + long count; + int type; +{ + FPOS start; + FPOS pos; + int inclusive = TRUE; + + stype = type; + + /* + * When visual area is bigger than one character: Extend it. + */ + if (VIsual_active && !equal(curwin->w_cursor, VIsual)) + { + if (lt(curwin->w_cursor, VIsual)) + { + if (decl(&curwin->w_cursor) == -1) + return FAIL; + if (cls() == 0) + { + if (bckend_word(count, type, TRUE) == FAIL) + return FAIL; + (void)incl(&curwin->w_cursor); + } + else + { + if (bck_word(count, type, TRUE) == FAIL) + return FAIL; + } + } + else + { + if (incl(&curwin->w_cursor) == -1) + return FAIL; + if (cls() == 0) + { + if (fwd_word(count, type, TRUE) == FAIL) + return FAIL; + (void)oneleft(); + } + else + { + if (end_word(count, type, TRUE, TRUE) == FAIL) + return FAIL; + } + } + return OK; + } + + /* + * Go to start of current word or white space. + */ + back_in_line(); + start = curwin->w_cursor; + + /* + * If the start is on white space, find end of word. + * Otherwise find start of next word. + */ + if (cls() == 0) + { + if (end_word(count, type, TRUE, TRUE) == FAIL) + return FAIL; + } + else + { + if (fwd_word(count, type, TRUE) == FAIL) + return FAIL; + /* + * If end is just past a new-line, we don't want to include the first + * character on the line + */ + if (oneleft() == FAIL) /* put cursor on last char of area */ + inclusive = FALSE; + else + { + pos = curwin->w_cursor; /* save cursor position */ + /* + * If we don't include white space at the end, move the start to + * include some white space there. This makes "d." work better on + * the last word in a sentence. Don't delete white space at start + * of line (indent). + */ + if (cls() != 0) + { + curwin->w_cursor = start; + if (oneleft() == OK) + { + back_in_line(); + if (cls() == 0 && curwin->w_cursor.col > 0) + start = curwin->w_cursor; + } + } + curwin->w_cursor = pos; /* put cursor back at end */ + } + } + if (VIsual_active) + { + /* should do something when inclusive == FALSE ! */ + VIsual = start; + VIsual_mode = 'v'; + update_curbuf(NOT_VALID); /* update the inversion */ + } + else + { + curbuf->b_op_start = start; + op_motion_type = MCHAR; + op_inclusive = inclusive; + } + return OK; +} + +/* + * Find sentence under the cursor, cursor at end. + */ + int +current_sent(count) + long count; +{ + FPOS start; + FPOS pos; + int start_blank; + int c; + + pos = curwin->w_cursor; + start = pos; + + /* + * When visual area is bigger than one character: Extend it. + */ + if (VIsual_active && !equal(curwin->w_cursor, VIsual)) + { + if (lt(pos, VIsual)) + { + /* + * Do a "sentence backward" on the next character. + * If we end up on the same position, we were already at the start + * of a sentence + */ + if (incl(&curwin->w_cursor) == -1) + return FAIL; + findsent(BACKWARD, 1L); + start = curwin->w_cursor; + if (count > 1) + findsent(BACKWARD, count - 1); + /* + * When at start of sentence: Include blanks in front of sentence. + * Use current_word() to cross line boundaries. + * If we don't end up on a blank or on an empty line, go back to + * the start of the previous sentence. + */ + if (equal(pos, start)) + { + current_word(1L, 0); + c = gchar_cursor(); + if (c != NUL && !vim_iswhite(c)) + findsent(BACKWARD, 1L); + } + + } + else + { + /* + * When one char before start of sentence: Don't include blanks in + * front of next sentence. + * Else go count sentences forward. + */ + findsent(FORWARD, 1L); + incl(&pos); + if (equal(pos, curwin->w_cursor)) + { + findsent(FORWARD, count); + find_first_blank(&curwin->w_cursor); + } + else if (count > 1) + findsent(FORWARD, count - 1); + decl(&curwin->w_cursor); + } + return OK; + } + + /* + * Find start of next sentence. + */ + findsent(FORWARD, 1L); + + /* + * If cursor started on blank, check if it is just before the start of the + * next sentence. + */ + while (vim_iswhite(gchar(&pos))) + incl(&pos); + if (equal(pos, curwin->w_cursor)) + { + start_blank = TRUE; + /* + * go back to first blank + */ + while (decl(&start) != -1) + { + if (!vim_iswhite(gchar(&start))) + { + incl(&start); + break; + } + } + } + else + { + start_blank = FALSE; + findsent(BACKWARD, 1L); + start = curwin->w_cursor; + } + findsent(FORWARD, count); + + /* + * If the blank in front of the sentence is included, exclude the blanks + * at the end of the sentence, go back to the first blank. + */ + if (start_blank) + find_first_blank(&curwin->w_cursor); + else + { + /* + * If there are no trailing blanks, try to include leading blanks + */ + pos = curwin->w_cursor; + decl(&pos); + if (!vim_iswhite(gchar(&pos))) + find_first_blank(&start); + } + + if (VIsual_active) + { + VIsual = start; + VIsual_mode = 'v'; + decl(&curwin->w_cursor); /* don't include the cursor char */ + update_curbuf(NOT_VALID); /* update the inversion */ + } + else + { + curbuf->b_op_start = start; + op_motion_type = MCHAR; + op_inclusive = FALSE; + } + return OK; +} + + int +current_block(what, count) + int what; /* '(' or '{' */ + long count; +{ + FPOS old_pos; + FPOS *pos = NULL; + FPOS start_pos; + FPOS *end_pos; + FPOS old_start, old_end; + int inclusive = FALSE; + int other; + + old_pos = curwin->w_cursor; + if (what == '{') + other = '}'; + else + other = ')'; + + old_end = curwin->w_cursor; /* remember where we started */ + old_start = old_end; + + /* + * If we start on '(', '{', ')' or '}', use the whole block inclusive. + */ + if (!VIsual_active || (VIsual.lnum == curwin->w_cursor.lnum && + VIsual.col == curwin->w_cursor.col)) + { + if (what == '{') /* ignore indent */ + while (inindent(1)) + if (inc_cursor() != 0) + break; + if (gchar_cursor() == what) /* cursor on '(' or '{' */ + { + ++curwin->w_cursor.col; + inclusive = TRUE; + } + else if (gchar_cursor() == other) /* cursor on ')' or '}' */ + inclusive = TRUE; + } + else if (lt(VIsual, curwin->w_cursor)) + { + old_start = VIsual; + curwin->w_cursor = VIsual; /* cursor at low end of Visual */ + } + else + old_end = VIsual; + + /* + * Search backwards for unclosed '(' or '{'. + * put this position in start_pos. + */ + while (count--) + { + if ((pos = findmatch(what)) == NULL) + break; + curwin->w_cursor = *pos; + start_pos = *pos; /* the findmatch for end_pos will overwrite *pos */ + } + + /* + * Search for matching ')' or '}'. + * Put this position in curwin->w_cursor. + */ + if (pos == NULL || (end_pos = findmatch(other)) == NULL) + { + curwin->w_cursor = old_pos; + return FAIL; + } + curwin->w_cursor = *end_pos; + + /* + * Try to exclude the '(', '{', ')' and '}'. + * If the ending '}' is only preceded by indent, skip that indent. + * But only if the resulting area is not smaller than what we started with. + */ + if (!inclusive) + { + incl(&start_pos); + old_pos = curwin->w_cursor; + decl(&curwin->w_cursor); + if (what == '{') + while (inindent(0)) + if (decl(&curwin->w_cursor) != 0) + break; + if (!lt(start_pos, old_start) && !lt(old_end, curwin->w_cursor)) + { + decl(&start_pos); + curwin->w_cursor = old_pos; + } + } + + if (VIsual_active) + { + VIsual = start_pos; + VIsual_mode = 'v'; + update_curbuf(NOT_VALID); /* update the inversion */ + showmode(); + } + else + { + curbuf->b_op_start = start_pos; + op_motion_type = MCHAR; + op_inclusive = TRUE; + } + + return OK; +} + + int +current_par(type, count) + int type; /* 'p' for paragraph, 'S' for section */ + long count; +{ + linenr_t start; + linenr_t end; + int white_in_front; + int dir; + int start_is_white; + int retval = OK; + + if (type == 'S') /* not implemented yet */ + return FAIL; + + start = curwin->w_cursor.lnum; + + /* + * When visual area is more than one line: extend it. + */ + if (VIsual_active && start != VIsual.lnum) + { + if (start < VIsual.lnum) + dir = BACKWARD; + else + dir = FORWARD; + while (count--) + { + if (start == (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count)) + retval = FAIL; + + start_is_white = -1; + for (;;) + { + if (start == (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count)) + break; + if (start_is_white >= 0 && + (start_is_white != linewhite(start + dir) || + startPS(start + (dir > 0 ? 1 : 0), 0, 0))) + break; + start += dir; + if (start_is_white < 0) + start_is_white = linewhite(start); + } + } + curwin->w_cursor.lnum = start; + curwin->w_cursor.col = 0; + return retval; + } + + /* + * First move back to the start of the paragraph or white lines + */ + white_in_front = linewhite(start); + while (start > 1) + { + if (white_in_front) /* stop at first white line */ + { + if (!linewhite(start - 1)) + break; + } + else /* stop at first non-white line of start of paragraph */ + { + if (linewhite(start - 1) || startPS(start, 0, 0)) + break; + } + --start; + } + + /* + * Move past the end of the white lines. + */ + end = start; + while (linewhite(end) && end < curbuf->b_ml.ml_line_count) + ++end; + + --end; + while (count--) + { + if (end == curbuf->b_ml.ml_line_count) + return FAIL; + + ++end; + /* + * skip to end of paragraph + */ + while (end < curbuf->b_ml.ml_line_count && + !linewhite(end + 1) && !startPS(end + 1, 0, 0)) + ++end; + + if (count == 0 && white_in_front) + break; + + /* + * skip to end of white lines after paragraph + */ + while (end < curbuf->b_ml.ml_line_count && linewhite(end + 1)) + ++end; + } + + /* + * If there are no empty lines at the end, try to find some empty lines at + * the start (unless that has been done already). + */ + if (!white_in_front && !linewhite(end)) + while (start > 1 && linewhite(start - 1)) + --start; + + if (VIsual_active) + { + VIsual.lnum = start; + VIsual_mode = 'V'; + update_curbuf(NOT_VALID); /* update the inversion */ + showmode(); + } + else + { + curbuf->b_op_start.lnum = start; + op_motion_type = MLINE; + } + curwin->w_cursor.lnum = end; + curwin->w_cursor.col = 0; + + return OK; +} + +/* + * linewhite -- return TRUE if line 'lnum' is empty or has white chars only. + */ + int +linewhite(lnum) + linenr_t lnum; +{ + char_u *p; + + p = skipwhite(ml_get(lnum)); + return (*p == NUL); +} + + static void +find_first_blank(posp) + FPOS *posp; +{ + while (decl(posp) != -1) + { + if (!vim_iswhite(gchar(posp))) + { + incl(posp); + break; + } + } +} + + void +find_pattern_in_path(ptr, len, whole, skip_comments, + type, count, action, start_lnum, end_lnum) + char_u *ptr; /* pointer to search pattern */ + int len; /* length of search pattern */ + int whole; /* match whole words only */ + int skip_comments; /* don't match inside comments */ + int type; /* Type of search; are we looking for a type? a + macro? */ + long count; + int action; /* What to do when we find it */ + linenr_t start_lnum; /* first line to start searching */ + linenr_t end_lnum; /* last line for searching */ +{ + SearchedFile *files; /* Stack of included files */ + SearchedFile *bigger; /* When we need more space */ + int max_path_depth = 50; + long match_count = 1; + + char_u *pat; + char_u *new_fname; + char_u *curr_fname = curbuf->b_xfilename; + char_u *prev_fname = NULL; + linenr_t lnum; + int depth; + int depth_displayed; /* For type==CHECK_PATH */ + int old_files; + int already_searched; + char_u *file_line; + char_u *line; + char_u *p; + char_u *p2 = NUL; /* Init for gcc */ + char_u save_char = NUL; + int define_matched; + struct regexp *prog = NULL; + struct regexp *include_prog = NULL; + struct regexp *define_prog = NULL; + int matched = FALSE; + int did_show = FALSE; + int found = FALSE; + int i; + + file_line = alloc(LSIZE); + if (file_line == NULL) + return; + + reg_magic = p_magic; + if (type != CHECK_PATH) + { + pat = alloc(len + 5); + if (pat == NULL) + goto fpip_end; + sprintf((char *)pat, whole ? "\\<%.*s\\>" : "%.*s", len, ptr); + set_reg_ic(pat); /* set reg_ic according to p_ic, p_scs and pat */ + prog = vim_regcomp(pat); + vim_free(pat); + if (prog == NULL) + goto fpip_end; + } + reg_ic = FALSE; /* don't ignore case in include and define patterns */ + if (*p_inc != NUL) + { + include_prog = vim_regcomp(p_inc); + if (include_prog == NULL) + goto fpip_end; + } + if (type == FIND_DEFINE && *p_def != NUL) + { + define_prog = vim_regcomp(p_def); + if (define_prog == NULL) + goto fpip_end; + } + files = (SearchedFile *)lalloc(max_path_depth * sizeof(SearchedFile), TRUE); + if (files == NULL) + goto fpip_end; + for (i = 0; i < max_path_depth; i++) + { + files[i].fp = NULL; + files[i].name = NULL; + files[i].lnum = 0; + files[i].matched = FALSE; + } + old_files = max_path_depth; + depth = depth_displayed = -1; + + lnum = start_lnum; + if (end_lnum > curbuf->b_ml.ml_line_count) + end_lnum = curbuf->b_ml.ml_line_count; + if (lnum > end_lnum) /* do at least one line */ + lnum = end_lnum; + line = ml_get(lnum); + + for (;;) + { + if (include_prog != NULL && vim_regexec(include_prog, line, TRUE)) + { + new_fname = get_file_name_in_path(include_prog->endp[0] + 1, + 0, FNAME_EXP); + already_searched = FALSE; + if (new_fname != NULL) + { + /* Check whether we have already searched in this file */ + for (i = 0;; i++) + { + if (i == depth + 1) + i = old_files; + if (i == max_path_depth) + break; + if (STRCMP(new_fname, files[i].name) == 0) + { + if (type != CHECK_PATH && + action == ACTION_SHOW_ALL && files[i].matched) + { + msg_outchar('\n'); /* cursor below last one */ + if (!got_int) /* don't display if 'q' + typed at "--more--" + mesage */ + { + set_highlight('d'); /* Same as for dirs */ + start_highlight(); + msg_home_replace(new_fname); + stop_highlight(); + MSG_OUTSTR(" (includes previously listed match)"); + prev_fname = NULL; + } + } + vim_free(new_fname); + new_fname = NULL; + already_searched = TRUE; + break; + } + } + } + + if (type == CHECK_PATH && (action == ACTION_SHOW_ALL || + (new_fname == NULL && !already_searched))) + { + if (did_show) + msg_outchar('\n'); /* cursor below last one */ + else + { + gotocmdline(TRUE); /* cursor at status line */ + set_highlight('t'); /* Highlight title */ + start_highlight(); + MSG_OUTSTR("--- Included files "); + if (action != ACTION_SHOW_ALL) + MSG_OUTSTR("not found "); + MSG_OUTSTR("in path ---\n"); + stop_highlight(); + } + did_show = TRUE; + while (depth_displayed < depth && !got_int) + { + ++depth_displayed; + for (i = 0; i < depth_displayed; i++) + MSG_OUTSTR(" "); + msg_home_replace(files[depth_displayed].name); + MSG_OUTSTR(" -->\n"); + } + if (!got_int) /* don't display if 'q' typed + for "--more--" message */ + { + for (i = 0; i <= depth_displayed; i++) + MSG_OUTSTR(" "); + set_highlight('d'); /* Same as for directories */ + start_highlight(); + /* + * Isolate the file name. + * Include the surrounding "" or <> if present. + */ + for (p = include_prog->endp[0] + 1; !isfilechar(*p); p++) + ; + for (i = 0; isfilechar(p[i]); i++) + ; + if (p[-1] == '"' || p[-1] == '<') + { + --p; + ++i; + } + if (p[i] == '"' || p[i] == '>') + ++i; + save_char = p[i]; + p[i] = NUL; + msg_outstr(p); + p[i] = save_char; + stop_highlight(); + if (new_fname == NULL && action == ACTION_SHOW_ALL) + { + if (already_searched) + MSG_OUTSTR(" (Already listed)"); + else + MSG_OUTSTR(" NOT FOUND"); + } + } + flushbuf(); /* output each line directly */ + } + + if (new_fname != NULL) + { + /* Push the new file onto the file stack */ + if (depth + 1 == old_files) + { + bigger = (SearchedFile *)lalloc(max_path_depth * 2 + * sizeof(SearchedFile), TRUE); + if (bigger != NULL) + { + for (i = 0; i <= depth; i++) + bigger[i] = files[i]; + for (i = depth + 1; i < old_files + max_path_depth; i++) + { + bigger[i].fp = NULL; + bigger[i].name = NULL; + bigger[i].lnum = 0; + bigger[i].matched = FALSE; + } + for (i = old_files; i < max_path_depth; i++) + bigger[i + max_path_depth] = files[i]; + old_files += max_path_depth; + max_path_depth *= 2; + vim_free(files); + files = bigger; + } + } + if ((files[depth + 1].fp = fopen((char *)new_fname, "r")) + == NULL) + vim_free(new_fname); + else + { + if (++depth == old_files) + { + /* + * lalloc() for 'bigger' must have failed above. We + * will forget one of our already visited files now. + */ + vim_free(files[old_files].name); + ++old_files; + } + files[depth].name = curr_fname = new_fname; + files[depth].lnum = 0; + files[depth].matched = FALSE; + } + } + } + else + { + /* + * Check if the line is a define (type == FIND_DEFINE) + */ + p = line; + define_matched = FALSE; + if (define_prog != NULL && vim_regexec(define_prog, line, TRUE)) + { + /* + * Pattern must be first identifier after 'define', so skip + * to that position before checking for match of pattern. Also + * don't let it match beyond the end of this identifier. + */ + p = define_prog->endp[0] + 1; + while (*p && !isidchar(*p)) + p++; + p2 = p; + while (*p2 && isidchar(*p2)) + p2++; + save_char = *p2; + *p2 = NUL; + define_matched = TRUE; + } + + /* + * Look for a match. Don't do this if we are looking for a + * define and this line didn't match define_prog above. + */ + if ((define_prog == NULL || define_matched) && + prog != NULL && vim_regexec(prog, p, p == line)) + { + matched = TRUE; + /* + * Check if the line is not a comment line (unless we are + * looking for a define). A line starting with "# define" is + * not considered to be a comment line. + */ + if (!define_matched && skip_comments) + { + fo_do_comments = TRUE; + if ((*line != '#' || + STRNCMP(skipwhite(line + 1), "define", 6) != 0) && + get_leader_len(line, NULL)) + matched = FALSE; + + /* + * Also check for a "/ *" or "/ /" before the match. + * Skips lines like "int idx; / * normal index * /" when + * looking for "normal". + */ + else + for (p = line; *p && p < prog->startp[0]; ++p) + if (p[0] == '/' && (p[1] == '*' || p[1] == '/')) + { + matched = FALSE; + break; + } + fo_do_comments = FALSE; + } + } + if (define_matched) + *p2 = save_char; + } + if (matched) + { +#ifdef INSERT_EXPAND + if (action == ACTION_EXPAND) + { + if (depth == -1 && lnum == curwin->w_cursor.lnum) + break; + found = TRUE; + p = prog->startp[0]; + while (iswordchar(*p)) + ++p; + if (add_completion_and_infercase(prog->startp[0], + (int)(p - prog->startp[0]), + curr_fname == curbuf->b_xfilename ? NULL : curr_fname, + FORWARD) == RET_ERROR) + break; + } + else +#endif + if (action == ACTION_SHOW_ALL) + { + found = TRUE; + if (!did_show) + gotocmdline(TRUE); /* cursor at status line */ + if (curr_fname != prev_fname) + { + if (did_show) + msg_outchar('\n'); /* cursor below last one */ + if (!got_int) /* don't display if 'q' typed + at "--more--" mesage */ + { + set_highlight('d'); /* Same as for directories */ + start_highlight(); + msg_home_replace(curr_fname); + stop_highlight(); + } + prev_fname = curr_fname; + } + did_show = TRUE; + if (!got_int) + show_pat_in_path(line, type, TRUE, action, + (depth == -1) ? NULL : files[depth].fp, + (depth == -1) ? &lnum : &files[depth].lnum, + match_count++); + + /* Set matched flag for this file and all the ones that + * include it */ + for (i = 0; i <= depth; ++i) + files[i].matched = TRUE; + } + else if (--count <= 0) + { + found = TRUE; + if (depth == -1 && lnum == curwin->w_cursor.lnum) + EMSG("Match is on current line"); + else if (action == ACTION_SHOW) + { + show_pat_in_path(line, type, did_show, action, + (depth == -1) ? NULL : files[depth].fp, + (depth == -1) ? &lnum : &files[depth].lnum, 1L); + did_show = TRUE; + } + else + { + if (action == ACTION_SPLIT) + { + if (win_split(0, FALSE) == FAIL) + break; + } + if (depth == -1) + { + setpcmark(); + curwin->w_cursor.lnum = lnum; + } + else + if (getfile(0, files[depth].name, NULL, TRUE, + files[depth].lnum) > 0) + break; /* failed to jump to file */ + } + if (action != ACTION_SHOW) + { + curwin->w_cursor.col = prog->startp[0] - line; + curwin->w_set_curswant = TRUE; + } + break; + } + matched = FALSE; + } + line_breakcheck(); + if (got_int) + break; + while (depth >= 0) + { + if (!vim_fgets(line = file_line, LSIZE, files[depth].fp)) + { + ++files[depth].lnum; + break; + } + fclose(files[depth].fp); + --old_files; + files[old_files].name = files[depth].name; + files[old_files].matched = files[depth].matched; + --depth; + curr_fname = (depth == -1) ? curbuf->b_xfilename + : files[depth].name; + if (depth < depth_displayed) + depth_displayed = depth; + } + if (depth < 0) + { + if (++lnum > end_lnum) + break; + line = ml_get(lnum); + } + } + for (i = 0; i <= depth; i++) + { + fclose(files[i].fp); + vim_free(files[i].name); + } + for (i = old_files; i < max_path_depth; i++) + vim_free(files[i].name); + vim_free(files); + + if (type == CHECK_PATH) + { + if (!did_show) + { + if (action != ACTION_SHOW_ALL) + MSG("All included files were found"); + else + MSG("No included files"); + } + } + else if (!found +#ifdef INSERT_EXPAND + && action != ACTION_EXPAND +#endif + ) + { + if (got_int) + emsg(e_interr); + else if (type == FIND_DEFINE) + EMSG("Couldn't find definition"); + else + EMSG("Couldn't find pattern"); + } + if (action == ACTION_SHOW || action == ACTION_SHOW_ALL) + msg_end(); + +fpip_end: + vim_free(file_line); + vim_free(prog); + vim_free(include_prog); + vim_free(define_prog); +} + + static void +show_pat_in_path(line, type, did_show, action, fp, lnum, count) + char_u *line; + int type; + int did_show; + int action; + FILE *fp; + linenr_t *lnum; + long count; +{ + char_u *p; + + if (did_show) + msg_outchar('\n'); /* cursor below last one */ + else + gotocmdline(TRUE); /* cursor at status line */ + if (got_int) /* 'q' typed at "--more--" message */ + return; + for (;;) + { + p = line + STRLEN(line) - 1; + if (fp != NULL) + { + /* We used fgets(), so get rid of newline at end */ + if (p >= line && *p == '\n') + --p; + if (p >= line && *p == '\r') + --p; + *(p + 1) = NUL; + } + if (action == ACTION_SHOW_ALL) + { + sprintf((char *)IObuff, "%3ld: ", count); /* show match nr */ + msg_outstr(IObuff); + set_highlight('n'); /* Highlight line numbers */ + start_highlight(); + sprintf((char *)IObuff, "%4ld", *lnum); /* show line nr */ + msg_outstr(IObuff); + stop_highlight(); + MSG_OUTSTR(" "); + } + msg_prt_line(line); + flushbuf(); /* show one line at a time */ + + /* Definition continues until line that doesn't end with '\' */ + if (got_int || type != FIND_DEFINE || p < line || *p != '\\') + break; + + if (fp != NULL) + { + if (vim_fgets(line, LSIZE, fp)) /* end of file */ + break; + ++*lnum; + } + else + { + if (++*lnum > curbuf->b_ml.ml_line_count) + break; + line = ml_get(*lnum); + } + msg_outchar('\n'); + } +} + +#ifdef VIMINFO + int +read_viminfo_search_pattern(line, fp, force) + char_u *line; + FILE *fp; + int force; +{ + char_u *lp; + char_u **pattern; + + lp = line; + if (lp[0] == '~') + lp++; + if (lp[0] == '/') + pattern = &search_pattern; + else + pattern = &subst_pattern; + if (*pattern != NULL && force) + vim_free(*pattern); + if (force || *pattern == NULL) + { + viminfo_readstring(lp); + *pattern = strsave(lp + 1); + if (line[0] == '~') + last_pattern = *pattern; + } + return vim_fgets(line, LSIZE, fp); +} + + void +write_viminfo_search_pattern(fp) + FILE *fp; +{ + if (get_viminfo_parameter('/') != 0) + { + if (search_pattern != NULL) + { + fprintf(fp, "\n# Last Search Pattern:\n"); + fprintf(fp, "%s/", (last_pattern == search_pattern) ? "~" : ""); + viminfo_writestring(fp, search_pattern); + } + if (subst_pattern != NULL) + { + fprintf(fp, "\n# Last Substitute Search Pattern:\n"); + fprintf(fp, "%s&", (last_pattern == subst_pattern) ? "~" : ""); + viminfo_writestring(fp, subst_pattern); + } + } +} +#endif /* VIMINFO */ + +/* + * Give a warning message. + * Use 'w' highlighting and may repeat the message after redrawing + */ + static void +give_warning(message) + char_u *message; +{ + (void)set_highlight('w'); + msg_highlight = TRUE; + if (msg(message) && !msg_scroll) + { + keep_msg = message; + keep_msg_highlight = 'w'; + } + msg_didout = FALSE; /* overwrite this message */ + msg_col = 0; +} diff --git a/usr.bin/vim/structs.h b/usr.bin/vim/structs.h new file mode 100644 index 00000000000..cb7f747c450 --- /dev/null +++ b/usr.bin/vim/structs.h @@ -0,0 +1,551 @@ +/* $OpenBSD: structs.h,v 1.1.1.1 1996/09/07 21:40:27 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * This file contains various definitions of structures that are used by Vim + */ + +/* + * file position + */ + +typedef struct fpos FPOS; +/* + * there is something wrong in the SAS compiler that makes typedefs not + * valid in include files + */ +#ifdef SASC +typedef long linenr_t; +typedef unsigned colnr_t; +typedef unsigned short short_u; +#endif + +struct fpos +{ + linenr_t lnum; /* line number */ + colnr_t col; /* column number */ +}; + +/* + * Sorry to put this here, but gui.h needs the FPOS type, and WIN needs gui.h + * for GuiScrollbar. There is probably somewhere better it could go -- webb + */ +#ifdef USE_GUI +# include "gui.h" +#endif + +/* + * marks: positions in a file + * (a normal mark is a lnum/col pair, the same as a file position) + */ + +#define NMARKS 26 /* max. # of named marks */ +#define JUMPLISTSIZE 30 /* max. # of marks in jump list */ +#define TAGSTACKSIZE 20 /* max. # of tags in tag stack */ + +struct filemark +{ + FPOS mark; /* cursor position */ + int fnum; /* file number */ +}; + +/* + * the taggy struct is used to store the information about a :tag command: + * the tag name and the cursor position BEFORE the :tag command + */ +struct taggy +{ + char_u *tagname; /* tag name */ + struct filemark fmark; /* cursor position */ +}; + +/* + * line number list + */ + +/* + * Each window can have a different line number associated with a buffer. + * The window-pointer/line-number pairs are kept in the line number list. + * The list of line numbers is kept in most-recently-used order. + */ + +typedef struct window WIN; +typedef struct winlnum WINLNUM; + +struct winlnum +{ + WINLNUM *wl_next; /* next entry or NULL for last entry */ + WINLNUM *wl_prev; /* previous entry or NULL for first entry */ + WIN *wl_win; /* pointer to window that did set wl_lnum */ + linenr_t wl_lnum; /* last cursor line in the file */ +}; + +/* + * stuctures used for undo + */ + +struct u_entry +{ + struct u_entry *ue_next; /* pointer to next entry in list */ + linenr_t ue_top; /* number of line above undo block */ + linenr_t ue_bot; /* number of line below undo block */ + linenr_t ue_lcount; /* linecount when u_save called */ + char_u **ue_array; /* array of lines in undo block */ + long ue_size; /* number of lines in ue_array */ +}; + +struct u_header +{ + struct u_header *uh_next; /* pointer to next header in list */ + struct u_header *uh_prev; /* pointer to previous header in list */ + struct u_entry *uh_entry; /* pointer to first entry */ + FPOS uh_cursor; /* cursor position before saving */ + int uh_flags; /* see below */ + FPOS uh_namedm[NMARKS]; /* marks before undo/after redo */ +}; + +/* values for uh_flags */ +#define UH_CHANGED 0x01 /* b_changed flag before undo/after redo */ +#define UH_EMPTYBUF 0x02 /* buffer was empty */ + +/* + * stuctures used in undo.c + */ +#if defined(UNIX) || defined(WIN32) || defined(__EMX__) +# define ALIGN_LONG /* longword alignment and use filler byte */ +# define ALIGN_SIZE (sizeof(long)) +#else +# define ALIGN_SIZE (sizeof(short)) +#endif + +#define ALIGN_MASK (ALIGN_SIZE - 1) + +typedef struct m_info info_t; + +/* + * stucture used to link chunks in one of the free chunk lists. + */ +struct m_info +{ +#ifdef ALIGN_LONG + long_u m_size; /* size of the chunk (including m_info) */ +#else + short_u m_size; /* size of the chunk (including m_info) */ +#endif + info_t *m_next; /* pointer to next free chunk in the list */ +}; + +/* + * structure used to link blocks in the list of allocated blocks. + */ +struct m_block +{ + struct m_block *mb_next; /* pointer to next allocated block */ + info_t mb_info; /* head of free chuck list for this block */ +}; + +/* + * things used in memfile.c + */ + +typedef struct block_hdr BHDR; +typedef struct memfile MEMFILE; +typedef long blocknr_t; + +/* + * for each (previously) used block in the memfile there is one block header. + * + * The block may be linked in the used list OR in the free list. + * The used blocks are also kept in hash lists. + * + * The used list is a doubly linked list, most recently used block first. + * The blocks in the used list have a block of memory allocated. + * mf_used_count is the number of pages in the used list. + * The hash lists are used to quickly find a block in the used list. + * The free list is a single linked list, not sorted. + * The blocks in the free list have no block of memory allocated and + * the contents of the block in the file (if any) is irrelevant. + */ + +struct block_hdr +{ + BHDR *bh_next; /* next block_hdr in free or used list */ + BHDR *bh_prev; /* previous block_hdr in used list */ + BHDR *bh_hash_next; /* next block_hdr in hash list */ + BHDR *bh_hash_prev; /* previous block_hdr in hash list */ + blocknr_t bh_bnum; /* block number */ + char_u *bh_data; /* pointer to memory (for used block) */ + int bh_page_count; /* number of pages in this block */ + +#define BH_DIRTY 1 +#define BH_LOCKED 2 + char bh_flags; /* BH_DIRTY or BH_LOCKED */ +}; + +/* + * when a block with a negative number is flushed to the file, it gets + * a positive number. Because the reference to the block is still the negative + * number, we remember the translation to the new positive number in the + * double linked trans lists. The structure is the same as the hash lists. + */ +typedef struct nr_trans NR_TRANS; + +struct nr_trans +{ + NR_TRANS *nt_next; /* next nr_trans in hash list */ + NR_TRANS *nt_prev; /* previous nr_trans in hash list */ + blocknr_t nt_old_bnum; /* old, negative, number */ + blocknr_t nt_new_bnum; /* new, positive, number */ +}; + +/* + * Simplistic hashing scheme to quickly locate the blocks in the used list. + * 64 blocks are found directly (64 * 4K = 256K, most files are smaller). + */ +#define MEMHASHSIZE 64 +#define MEMHASH(nr) ((nr) & (MEMHASHSIZE - 1)) + +struct memfile +{ + char_u *mf_fname; /* name of the file */ + char_u *mf_xfname; /* idem, full path */ + int mf_fd; /* file descriptor */ + BHDR *mf_free_first; /* first block_hdr in free list */ + BHDR *mf_used_first; /* mru block_hdr in used list */ + BHDR *mf_used_last; /* lru block_hdr in used list */ + unsigned mf_used_count; /* number of pages in used list */ + unsigned mf_used_count_max; /* maximum number of pages in memory */ + BHDR *mf_hash[MEMHASHSIZE]; /* array of hash lists */ + NR_TRANS *mf_trans[MEMHASHSIZE]; /* array of trans lists */ + blocknr_t mf_blocknr_max; /* highest positive block number + 1*/ + blocknr_t mf_blocknr_min; /* lowest negative block number - 1 */ + blocknr_t mf_neg_count; /* number of negative blocks numbers */ + blocknr_t mf_infile_count; /* number of pages in the file */ + unsigned mf_page_size; /* number of bytes in a page */ + int mf_dirty; /* Set to TRUE if there are dirty blocks */ +}; + +/* + * things used in memline.c + */ +typedef struct info_pointer IPTR; /* block/index pair */ + +/* + * When searching for a specific line, we remember what blocks in the tree + * are the branches leading to that block. This is stored in ml_stack. + * Each entry is a pointer to info in a block (may be data block or pointer block) + */ +struct info_pointer +{ + blocknr_t ip_bnum; /* block number */ + linenr_t ip_low; /* lowest lnum in this block */ + linenr_t ip_high; /* highest lnum in this block */ + int ip_index; /* index for block with current lnum */ +}; + +typedef struct memline MEMLINE; + +/* + * the memline structure holds all the information about a memline + */ +struct memline +{ + linenr_t ml_line_count; /* number of lines in the buffer */ + + MEMFILE *ml_mfp; /* pointer to associated memfile */ + +#define ML_EMPTY 1 /* empty buffer */ +#define ML_LINE_DIRTY 2 /* cached line was changed and allocated */ +#define ML_LOCKED_DIRTY 4 /* ml_locked was changed */ +#define ML_LOCKED_POS 8 /* ml_locked needs positive block number */ + int ml_flags; + + IPTR *ml_stack; /* stack of pointer blocks (array of IPTRs) */ + int ml_stack_top; /* current top if ml_stack */ + int ml_stack_size; /* total number of entries in ml_stack */ + + linenr_t ml_line_lnum; /* line number of cached line, 0 if not valid */ + char_u *ml_line_ptr; /* pointer to cached line */ + + BHDR *ml_locked; /* block used by last ml_get */ + linenr_t ml_locked_low; /* first line in ml_locked */ + linenr_t ml_locked_high; /* last line in ml_locked */ + int ml_locked_lineadd; /* number of lines inserted in ml_locked */ +}; + +/* + * buffer: structure that holds information about one file + * + * Several windows can share a single Buffer + * A buffer is unallocated if there is no memfile for it. + * A buffer is new if the associated file has never been loaded yet. + */ + +typedef struct buffer BUF; + +struct buffer +{ + MEMLINE b_ml; /* associated memline (also contains + * line count) */ + + BUF *b_next; /* links in list of buffers */ + BUF *b_prev; + + int b_changed; /* 'modified': Set to TRUE if + * something in the file has + * been changed and not written out. + */ + + int b_notedited; /* Set to TRUE when file name is + * changed after starting to edit, + * reset when file is written out. */ + + int b_nwindows; /* nr of windows open on this buffer */ + + int b_neverloaded; /* file has never been loaded into + * buffer, many variables still need + * to be set */ + + /* + * b_filename has the full path of the file. + * b_sfilename is the name as the user typed it. + * b_xfilename is the same as b_sfilename, unless did_cd is set, then it + * is the same as b_filename. + */ + char_u *b_filename; + char_u *b_sfilename; + char_u *b_xfilename; + + int b_fnum; /* file number for this file. */ + WINLNUM *b_winlnum; /* list of last used lnum for + * each window */ + + long b_mtime; /* last change time of original file */ + long b_mtime_read; /* last change time when reading */ + + FPOS b_namedm[NMARKS]; /* current named marks (mark.c) */ + + FPOS b_last_cursor; /* cursor position when last unloading + this buffer */ + + /* + * Character table, only used in charset.c for 'iskeyword' + */ + char b_chartab[256]; + + /* + * start and end of an operator, also used for '[ and '] + */ + FPOS b_op_start; + FPOS b_op_end; + +#ifdef VIMINFO + int b_marks_read; /* Have we read viminfo marks yet? */ +#endif /* VIMINFO */ + + /* + * The following only used in undo.c. + */ + struct u_header *b_u_oldhead; /* pointer to oldest header */ + struct u_header *b_u_newhead; /* pointer to newest header */ + struct u_header *b_u_curhead; /* pointer to current header */ + int b_u_numhead; /* current number of headers */ + int b_u_synced; /* entry lists are synced */ + + /* + * variables for "U" command in undo.c + */ + char_u *b_u_line_ptr; /* saved line for "U" command */ + linenr_t b_u_line_lnum; /* line number of line in u_line */ + colnr_t b_u_line_colnr; /* optional column number */ + + /* + * The following only used in undo.c + */ + struct m_block b_block_head; /* head of allocated memory block list */ + info_t *b_m_search; /* pointer to chunk before previously + * allocated/freed chunk */ + struct m_block *b_mb_current; /* block where m_search points in */ + + /* + * Options "local" to a buffer. + * They are here because their value depends on the type of file + * or contents of the file being edited. + * The "save" options are for when the paste option is set. + */ + int b_p_initialized; /* set when options initialized */ + int b_p_ai, b_p_ro, b_p_lisp; + int b_p_inf; /* infer case of ^N/^P completions */ + int b_p_bin, b_p_eol, b_p_et, b_p_ml, b_p_tx; +#ifndef SHORT_FNAME + int b_p_sn; +#endif + + long b_p_sw, b_p_ts, b_p_tw, b_p_wm; + char_u *b_p_fo, *b_p_com, *b_p_isk; + + /* saved values for when 'bin' is set */ + long b_p_wm_nobin, b_p_tw_nobin; + int b_p_tx_nobin, b_p_ta_nobin; + int b_p_ml_nobin, b_p_et_nobin; + + /* saved values for when 'paste' is set */ + int b_p_ai_save, b_p_lisp_save; + long b_p_tw_save, b_p_wm_save; + +#ifdef SMARTINDENT + int b_p_si, b_p_si_save; +#endif +#ifdef CINDENT + int b_p_cin; /* use C progam indent mode */ + int b_p_cin_save; /* b_p_cin saved for paste mode */ + char_u *b_p_cino; /* C progam indent mode options */ + char_u *b_p_cink; /* C progam indent mode keys */ +#endif +#if defined(CINDENT) || defined(SMARTINDENT) + char_u *b_p_cinw; /* words extra indent for 'si' and 'cin' */ +#endif + + char b_did_warn; /* Set to 1 if user has been warned on + * first change of a read-only file */ + char b_help; /* buffer for help file */ + +#ifndef SHORT_FNAME + int b_shortname; /* this file has an 8.3 filename */ +#endif +}; + +/* + * Structure which contains all information that belongs to a window + * + * All row numbers are relative to the start of the window, except w_winpos. + */ + +struct window +{ + BUF *w_buffer; /* buffer we are a window into */ + + WIN *w_prev; /* link to previous window (above) */ + WIN *w_next; /* link to next window (below) */ + + FPOS w_cursor; /* cursor's position in buffer */ + + /* + * These elements are related to the cursor's position in the window. + * This is related to character positions in the window, not in the file. + */ + int w_row, w_col; /* cursor's position in window */ + + /* + * w_cline_height is set in cursupdate() to the number of physical lines + * taken by the buffer line that the cursor is on. We use this to avoid + * extra calls to plines(). Note that w_cline_height and w_cline_row are + * only valid after calling cursupdate(), until the next vertical movement + * of the cursor or change of text. + */ + int w_cline_height; /* current size of cursor line */ + + int w_cline_row; /* starting row of the cursor line */ + + colnr_t w_virtcol; /* column number of the file's actual */ + /* line, as opposed to the column */ + /* number we're at on the screen. */ + /* This makes a difference on lines */ + /* which span more than one screen */ + /* line. */ + + colnr_t w_curswant; /* The column we'd like to be at. */ + /* This is used to try to stay in */ + /* the same column through up/down */ + /* cursor motions. */ + + int w_set_curswant; /* If set, then update w_curswant */ + /* the next time through cursupdate() */ + /* to the current virtual column */ + + /* + * the next three are used to update the visual part + */ + linenr_t w_old_cursor_lnum; /* last known end of visual part */ + colnr_t w_old_cursor_vcol; /* last known end of visual part */ + linenr_t w_old_visual_lnum; /* last known start of visual part */ + colnr_t w_old_curswant; /* last known value of Curswant */ + + linenr_t w_topline; /* number of the line at the top of + * the screen */ + linenr_t w_botline; /* number of the line below the bottom + * of the screen */ + int w_empty_rows; /* number of ~ rows in window */ + + int w_winpos; /* row of topline of window in screen */ + int w_height; /* number of rows in window, excluding + status/command line */ + int w_status_height; /* number of status lines (0 or 1) */ + + int w_redr_status; /* if TRUE status line must be redrawn */ + int w_redr_type; /* type of redraw to be performed on win */ + + colnr_t w_leftcol; /* starting column of the screen */ + +/* + * The height of the lines currently in the window is remembered + * to avoid recomputing it every time. The number of entries is w_nrows. + */ + int w_lsize_valid; /* nr. of valid LineSizes */ + linenr_t *w_lsize_lnum; /* array of line numbers for w_lsize */ + char_u *w_lsize; /* array of line heights */ + + int w_alt_fnum; /* alternate file (for # and CTRL-^) */ + + int w_arg_idx; /* current index in argument list */ + int w_arg_idx_invalid; /* editing another file then w_arg_idx */ + + /* + * Variables "local" to a window. + * They are here because they influence the layout of the window or + * depend on the window layout. + */ + int w_p_list, + w_p_nu, +#ifdef RIGHTLEFT + w_p_rl, +#endif + w_p_wrap, + w_p_lbr; + long w_p_scroll; + + /* + * The w_prev_pcmark field is used to check whether we really did jump to + * a new line after setting the w_pcmark. If not, then we revert to + * using the previous w_pcmark. + */ + FPOS w_pcmark; /* previous context mark */ + FPOS w_prev_pcmark; /* previous w_pcmark */ + + /* + * the jumplist contains old cursor positions + */ + struct filemark w_jumplist[JUMPLISTSIZE]; + int w_jumplistlen; /* number of active entries */ + int w_jumplistidx; /* current position */ + + /* + * the tagstack grows from 0 upwards: + * entry 0: older + * entry 1: newer + * entry 2: newest + */ + struct taggy w_tagstack[TAGSTACKSIZE]; /* the tag stack */ + int w_tagstackidx; /* idx just below activ entry */ + int w_tagstacklen; /* number of tags on stack */ + +#ifdef USE_GUI + GuiScrollbar w_scrollbar; /* Scrollbar for this window */ +#endif /* USE_GUI */ +}; diff --git a/usr.bin/vim/tables.c b/usr.bin/vim/tables.c new file mode 100644 index 00000000000..25ff8b14c07 --- /dev/null +++ b/usr.bin/vim/tables.c @@ -0,0 +1,478 @@ +/* $OpenBSD: tables.c,v 1.1.1.1 1996/09/07 21:40:24 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * This file by Robert Webb + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * tables.c: functions that use lookup tables for various things, generally to + * do with special key codes. + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +/* + * Some useful tables. + */ + +static struct +{ + int mod_mask; /* Bit-mask for particular key modifier */ + char_u name; /* Single letter name of modifier */ +} mod_mask_table[] = +{ + {MOD_MASK_ALT, (char_u)'M'}, + {MOD_MASK_CTRL, (char_u)'C'}, + {MOD_MASK_SHIFT, (char_u)'S'}, + {MOD_MASK_2CLICK, (char_u)'2'}, + {MOD_MASK_3CLICK, (char_u)'3'}, + {MOD_MASK_4CLICK, (char_u)'4'}, + {0x0, NUL} +}; + +/* + * Shifted key terminal codes and their unshifted equivalent. + * Don't add mouse codes here, they are handled seperately! + */ +static char_u shifted_keys_table[] = +{ +/* shifted unshifted */ + '&', '9', '@', '1', /* begin */ + '&', '0', '@', '2', /* cancel */ + '*', '1', '@', '4', /* command */ + '*', '2', '@', '5', /* copy */ + '*', '3', '@', '6', /* create */ + '*', '4', 'k', 'D', /* delete char */ + '*', '5', 'k', 'L', /* delete line */ + '*', '7', '@', '7', /* end */ + '*', '9', '@', '9', /* exit */ + '*', '0', '@', '0', /* find */ + '#', '1', '%', '1', /* help */ + '#', '2', 'k', 'h', /* home */ + '#', '3', 'k', 'I', /* insert */ + '#', '4', 'k', 'l', /* left arrow */ + '%', 'a', '%', '3', /* message */ + '%', 'b', '%', '4', /* move */ + '%', 'c', '%', '5', /* next */ + '%', 'd', '%', '7', /* options */ + '%', 'e', '%', '8', /* previous */ + '%', 'f', '%', '9', /* print */ + '%', 'g', '%', '0', /* redo */ + '%', 'h', '&', '3', /* replace */ + '%', 'i', 'k', 'r', /* right arrow */ + '%', 'j', '&', '5', /* resume */ + '!', '1', '&', '6', /* save */ + '!', '2', '&', '7', /* suspend */ + '!', '3', '&', '8', /* undo */ + KS_EXTRA, KE_S_UP, 'k', 'u', /* up arrow */ + KS_EXTRA, KE_S_DOWN, 'k', 'd', /* down arrow */ + + KS_EXTRA, KE_S_F1, 'k', '1', /* F1 */ + KS_EXTRA, KE_S_F2, 'k', '2', + KS_EXTRA, KE_S_F3, 'k', '3', + KS_EXTRA, KE_S_F4, 'k', '4', + KS_EXTRA, KE_S_F5, 'k', '5', + KS_EXTRA, KE_S_F6, 'k', '6', + KS_EXTRA, KE_S_F7, 'k', '7', + KS_EXTRA, KE_S_F8, 'k', '8', + KS_EXTRA, KE_S_F9, 'k', '9', + KS_EXTRA, KE_S_F10, 'k', ';', /* F10 */ + + KS_EXTRA, KE_S_F11, 'F', '1', + KS_EXTRA, KE_S_F12, 'F', '2', + KS_EXTRA, KE_S_F13, 'F', '3', + KS_EXTRA, KE_S_F14, 'F', '4', + KS_EXTRA, KE_S_F15, 'F', '5', + KS_EXTRA, KE_S_F16, 'F', '6', + KS_EXTRA, KE_S_F17, 'F', '7', + KS_EXTRA, KE_S_F18, 'F', '8', + KS_EXTRA, KE_S_F19, 'F', '9', + KS_EXTRA, KE_S_F20, 'F', 'A', + + KS_EXTRA, KE_S_F21, 'F', 'B', + KS_EXTRA, KE_S_F22, 'F', 'C', + KS_EXTRA, KE_S_F23, 'F', 'D', + KS_EXTRA, KE_S_F24, 'F', 'E', + KS_EXTRA, KE_S_F25, 'F', 'F', + KS_EXTRA, KE_S_F26, 'F', 'G', + KS_EXTRA, KE_S_F27, 'F', 'H', + KS_EXTRA, KE_S_F28, 'F', 'I', + KS_EXTRA, KE_S_F29, 'F', 'J', + KS_EXTRA, KE_S_F30, 'F', 'K', + + KS_EXTRA, KE_S_F31, 'F', 'L', + KS_EXTRA, KE_S_F32, 'F', 'M', + KS_EXTRA, KE_S_F33, 'F', 'N', + KS_EXTRA, KE_S_F34, 'F', 'O', + KS_EXTRA, KE_S_F35, 'F', 'P', + + KS_EXTRA, KE_S_TAB, KS_EXTRA, KE_TAB, /* TAB */ + NUL +}; + +static struct key_name_entry +{ + int key; /* Special key code or ascii value */ + char_u *name; /* Name of key */ +} key_names_table[] = +{ + {' ', (char_u *)"Space"}, + {TAB, (char_u *)"Tab"}, + {K_TAB, (char_u *)"Tab"}, + {NL, (char_u *)"NL"}, + {NL, (char_u *)"NewLine"}, /* Alternative name */ + {NL, (char_u *)"LineFeed"}, /* Alternative name */ + {NL, (char_u *)"LF"}, /* Alternative name */ + {CR, (char_u *)"CR"}, + {CR, (char_u *)"Return"}, /* Alternative name */ + {ESC, (char_u *)"Esc"}, + {K_UP, (char_u *)"Up"}, + {K_DOWN, (char_u *)"Down"}, + {K_LEFT, (char_u *)"Left"}, + {K_RIGHT, (char_u *)"Right"}, + + {K_F1, (char_u *)"F1"}, + {K_F2, (char_u *)"F2"}, + {K_F3, (char_u *)"F3"}, + {K_F4, (char_u *)"F4"}, + {K_F5, (char_u *)"F5"}, + {K_F6, (char_u *)"F6"}, + {K_F7, (char_u *)"F7"}, + {K_F8, (char_u *)"F8"}, + {K_F9, (char_u *)"F9"}, + {K_F10, (char_u *)"F10"}, + + {K_F11, (char_u *)"F11"}, + {K_F12, (char_u *)"F12"}, + {K_F13, (char_u *)"F13"}, + {K_F14, (char_u *)"F14"}, + {K_F15, (char_u *)"F15"}, + {K_F16, (char_u *)"F16"}, + {K_F17, (char_u *)"F17"}, + {K_F18, (char_u *)"F18"}, + {K_F19, (char_u *)"F19"}, + {K_F20, (char_u *)"F20"}, + + {K_F21, (char_u *)"F21"}, + {K_F22, (char_u *)"F22"}, + {K_F23, (char_u *)"F23"}, + {K_F24, (char_u *)"F24"}, + {K_F25, (char_u *)"F25"}, + {K_F26, (char_u *)"F26"}, + {K_F27, (char_u *)"F27"}, + {K_F28, (char_u *)"F28"}, + {K_F29, (char_u *)"F29"}, + {K_F30, (char_u *)"F30"}, + + {K_F31, (char_u *)"F31"}, + {K_F32, (char_u *)"F32"}, + {K_F33, (char_u *)"F33"}, + {K_F34, (char_u *)"F34"}, + {K_F35, (char_u *)"F35"}, + + {K_HELP, (char_u *)"Help"}, + {K_UNDO, (char_u *)"Undo"}, + {K_BS, (char_u *)"BS"}, + {K_BS, (char_u *)"BackSpace"}, /* Alternative name */ + {K_INS, (char_u *)"Insert"}, + {K_INS, (char_u *)"Ins"}, /* Alternative name */ + {K_DEL, (char_u *)"Del"}, + {K_DEL, (char_u *)"Delete"}, /* Alternative name */ + {K_HOME, (char_u *)"Home"}, + {K_END, (char_u *)"End"}, + {K_PAGEUP, (char_u *)"PageUp"}, + {K_PAGEDOWN, (char_u *)"PageDown"}, + {K_MOUSE, (char_u *)"Mouse"}, + {K_LEFTMOUSE, (char_u *)"LeftMouse"}, + {K_LEFTDRAG, (char_u *)"LeftDrag"}, + {K_LEFTRELEASE, (char_u *)"LeftRelease"}, + {K_MIDDLEMOUSE, (char_u *)"MiddleMouse"}, + {K_MIDDLEDRAG, (char_u *)"MiddleDrag"}, + {K_MIDDLERELEASE, (char_u *)"MiddleRelease"}, + {K_RIGHTMOUSE, (char_u *)"RightMouse"}, + {K_RIGHTDRAG, (char_u *)"RightDrag"}, + {K_RIGHTRELEASE, (char_u *)"RightRelease"}, + {K_ZERO, (char_u *)"Nul"}, + {0, NULL} +}; + +#define KEY_NAMES_TABLE_LEN (sizeof(key_names_table) / sizeof(struct key_name_entry)) + +#ifdef USE_MOUSE +static struct +{ + int pseudo_code; /* Code for pseudo mouse event */ + int button; /* Which mouse button is it? */ + int is_click; /* Is it a mouse button click event? */ + int is_drag; /* Is it a mouse drag event? */ +} mouse_table[] = +{ + {KE_LEFTMOUSE, MOUSE_LEFT, TRUE, FALSE}, + {KE_LEFTDRAG, MOUSE_LEFT, FALSE, TRUE}, + {KE_LEFTRELEASE, MOUSE_LEFT, FALSE, FALSE}, + {KE_MIDDLEMOUSE, MOUSE_MIDDLE, TRUE, FALSE}, + {KE_MIDDLEDRAG, MOUSE_MIDDLE, FALSE, TRUE}, + {KE_MIDDLERELEASE, MOUSE_MIDDLE, FALSE, FALSE}, + {KE_RIGHTMOUSE, MOUSE_RIGHT, TRUE, FALSE}, + {KE_RIGHTDRAG, MOUSE_RIGHT, FALSE, TRUE}, + {KE_RIGHTRELEASE, MOUSE_RIGHT, FALSE, FALSE}, + {KE_IGNORE, MOUSE_RELEASE, FALSE, TRUE}, /* DRAG without CLICK */ + {KE_IGNORE, MOUSE_RELEASE, FALSE, FALSE}, /* RELEASE without CLICK */ + {0, 0, 0, 0}, +}; +#endif /* USE_MOUSE */ + +/* + * Return the modifier mask bit (MOD_MASK_*) which corresponds to the given + * modifier name ('S' for Shift, 'C' for Ctrl etc). + */ + int +name_to_mod_mask(c) + int c; +{ + int i; + + for (i = 0; mod_mask_table[i].mod_mask; i++) + if (TO_LOWER(c) == TO_LOWER(mod_mask_table[i].name)) + return mod_mask_table[i].mod_mask; + return 0x0; +} + +/* + * Decide whether the given key code (K_*) is a shifted special + * key (by looking at mod_mask). If it is, then return the appropriate shifted + * key code, otherwise just return the character as is. + */ + int +check_shifted_spec_key(c) + int c; +{ + int i; + int key0; + int key1; + + if (mod_mask & MOD_MASK_SHIFT) + { + if (c == TAB) /* TAB is not in the table, K_TAB is */ + return K_S_TAB; + key0 = KEY2TERMCAP0(c); + key1 = KEY2TERMCAP1(c); + for (i = 0; shifted_keys_table[i] != NUL; i += 4) + if (key0 == shifted_keys_table[i + 2] && + key1 == shifted_keys_table[i + 3]) + return TERMCAP2KEY(shifted_keys_table[i], + shifted_keys_table[i + 1]); + } + return c; +} + +/* + * Decide whether the given special key is shifted or not. If it is we + * return OK and change it to the equivalent unshifted special key code, + * otherwise we leave it as is and return FAIL. + */ + int +unshift_special_key(p) + char_u *p; +{ + int i; + + for (i = 0; shifted_keys_table[i]; i += 4) + if (p[0] == shifted_keys_table[i] && p[1] == shifted_keys_table[i + 1]) + { + p[0] = shifted_keys_table[i + 2]; + p[1] = shifted_keys_table[i + 3]; + return OK; + } + return FAIL; +} + +/* + * Return a string which contains the name of the given key when the given + * modifiers are down. + */ + char_u * +get_special_key_name(c, modifiers) + int c; + int modifiers; +{ + static char_u string[MAX_KEY_NAME_LEN + 1]; + + int i, idx; + char_u *s; + char_u name[2]; + + string[0] = '<'; + idx = 1; + + /* translate shifted keys into unshifted keys and set modifier */ + if (IS_SPECIAL(c)) + { + name[0] = KEY2TERMCAP0(c); + name[1] = KEY2TERMCAP1(c); + if (unshift_special_key(&name[0])) + modifiers |= MOD_MASK_SHIFT; + c = TERMCAP2KEY(name[0], name[1]); + } + + /* translate the modifier into a string */ + for (i = 0; mod_mask_table[i].mod_mask; i++) + if (modifiers & mod_mask_table[i].mod_mask) + { + string[idx++] = mod_mask_table[i].name; + string[idx++] = (char_u)'-'; + } + + /* try to find the key in the special key table */ + i = find_special_key_in_table(c); + if (i < 0) /* unknown special key, output t_xx */ + { + if (IS_SPECIAL(c)) + { + string[idx++] = 't'; + string[idx++] = '_'; + string[idx++] = KEY2TERMCAP0(c); + string[idx++] = KEY2TERMCAP1(c); + } + /* Not a special key, only modifiers, output directly */ + else + { + if (isprintchar(c)) + string[idx++] = c; + else + { + s = transchar(c); + while (*s) + string[idx++] = *s++; + } + } + } + else /* use name of special key */ + { + STRCPY(string + idx, key_names_table[i].name); + idx = STRLEN(string); + } + string[idx++] = '>'; + string[idx] = NUL; + return string; +} + +/* + * Try to find key "c" in the special key table. + * Return the index when found, -1 when not found. + */ + int +find_special_key_in_table(c) + int c; +{ + int i; + + for (i = 0; key_names_table[i].name != NULL; i++) + if (c == key_names_table[i].key) + break; + if (key_names_table[i].name == NULL) + i = -1; + return i; +} + +/* + * Find the special key with the given name (the given string does not have to + * end with NUL, the name is assumed to end before the first non-idchar). + * If the name starts with "t_" the next two characters are interpreted as a + * termcap name. + * Return the key code, or 0 if not found. + */ + int +get_special_key_code(name) + char_u *name; +{ + char_u *table_name; + char_u string[3]; + int i, j; + + /* + * If it's we get the code for xx from the termcap + */ + if (name[0] == 't' && name[1] == '_' && name[2] != NUL && name[3] != NUL) + { + string[0] = name[2]; + string[1] = name[3]; + string[2] = NUL; + if (add_termcap_entry(string, FALSE) == OK) + return TERMCAP2KEY(name[2], name[3]); + } + else + for (i = 0; key_names_table[i].name != NULL; i++) + { + table_name = key_names_table[i].name; + for (j = 0; isidchar(name[j]) && table_name[j] != NUL; j++) + if (TO_LOWER(table_name[j]) != TO_LOWER(name[j])) + break; + if (!isidchar(name[j]) && table_name[j] == NUL) + return key_names_table[i].key; + } + return 0; +} + + char_u * +get_key_name(i) + int i; +{ + if (i >= KEY_NAMES_TABLE_LEN) + return NULL; + return key_names_table[i].name; +} + +#ifdef USE_MOUSE +/* + * Look up the given mouse code to return the relevant information in the other + * arguments. Return which button is down or was released. + */ + int +get_mouse_button(code, is_click, is_drag) + int code; + int *is_click; + int *is_drag; +{ + int i; + + for (i = 0; mouse_table[i].pseudo_code; i++) + if (code == mouse_table[i].pseudo_code) + { + *is_click = mouse_table[i].is_click; + *is_drag = mouse_table[i].is_drag; + return mouse_table[i].button; + } + return 0; /* Shouldn't get here */ +} + +/* + * Return the appropriate pseudo mouse event token (KE_LEFTMOUSE etc) based on + * the given information about which mouse button is down, and whether the + * mouse was clicked, dragged or released. + */ + int +get_pseudo_mouse_code(button, is_click, is_drag) + int button; /* eg MOUSE_LEFT */ + int is_click; + int is_drag; +{ + int i; + + for (i = 0; mouse_table[i].pseudo_code; i++) + if (button == mouse_table[i].button + && is_click == mouse_table[i].is_click + && is_drag == mouse_table[i].is_drag) + { + return mouse_table[i].pseudo_code; + } + return KE_IGNORE; /* not recongnized, ignore it */ +} +#endif /* USE_MOUSE */ diff --git a/usr.bin/vim/tag.c b/usr.bin/vim/tag.c new file mode 100644 index 00000000000..5fbbc6d84d8 --- /dev/null +++ b/usr.bin/vim/tag.c @@ -0,0 +1,1426 @@ +/* $OpenBSD: tag.c,v 1.1.1.1 1996/09/07 21:40:24 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * Code to handle tags and the tag stack + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +static int get_tagfname __ARGS((int, char_u *)); + +#ifdef EMACS_TAGS +static int parse_tag_line __ARGS((char_u *, int, + char_u **, char_u **, char_u **, char_u **, char_u **)); +static int jumpto_tag __ARGS((char_u *, int, char_u *, char_u *)); +#else +static int parse_tag_line __ARGS((char_u *, + char_u **, char_u **, char_u **, char_u **, char_u **)); +static int jumpto_tag __ARGS((char_u *, char_u *)); +#endif +static int test_for_static __ARGS((char_u **, char_u *, char_u *, char_u *)); +static char_u *expand_rel_name __ARGS((char_u *fname, char_u *tag_fname)); +static void simplify_filename __ARGS((char_u *filename)); +#ifdef EMACS_TAGS +static int test_for_current __ARGS((int, char_u *, char_u *, char_u *)); +#else +static int test_for_current __ARGS((char_u *, char_u *, char_u *)); +#endif + +static char_u *bottommsg = (char_u *)"at bottom of tag stack"; +static char_u *topmsg = (char_u *)"at top of tag stack"; + +/* + * Jump to tag; handling of tag stack + * + * *tag != NUL (:tag): jump to new tag, add to tag stack + * type == 1 (:pop) || type == 2 (CTRL-T): jump to old position + * type == 0 (:tag): jump to old tag + */ + void +do_tag(tag, type, count) + char_u *tag; + int type; + int count; +{ + int i; + struct taggy *tagstack = curwin->w_tagstack; + int tagstackidx = curwin->w_tagstackidx; + int tagstacklen = curwin->w_tagstacklen; + int oldtagstackidx = tagstackidx; + + if (*tag != NUL) /* new pattern, add to the stack */ + { + /* + * if last used entry is not at the top, delete all tag stack entries + * above it. + */ + while (tagstackidx < tagstacklen) + vim_free(tagstack[--tagstacklen].tagname); + + /* if tagstack is full: remove oldest entry */ + if (++tagstacklen > TAGSTACKSIZE) + { + tagstacklen = TAGSTACKSIZE; + vim_free(tagstack[0].tagname); + for (i = 1; i < tagstacklen; ++i) + tagstack[i - 1] = tagstack[i]; + --tagstackidx; + } + + /* + * put the tag name in the tag stack + * the position is added below + */ + tagstack[tagstackidx].tagname = strsave(tag); + } + else if (tagstacklen == 0) /* empty stack */ + { + EMSG("tag stack empty"); + goto end_do_tag; + } + else if (type) /* go to older position */ + { + if ((tagstackidx -= count) < 0) + { + emsg(bottommsg); + if (tagstackidx + count == 0) + { + /* We did ^T (or ^T) from the bottom of the stack */ + tagstackidx = 0; + goto end_do_tag; + } + /* We weren't at the bottom of the stack, so jump all the way to + * the bottom. + */ + tagstackidx = 0; + } + else if (tagstackidx >= tagstacklen) /* must have been count == 0 */ + { + emsg(topmsg); + goto end_do_tag; + } + if (tagstack[tagstackidx].fmark.fnum != curbuf->b_fnum) + { + /* + * Jump to other file. If this fails (e.g. because the file was + * changed) keep original position in tag stack. + */ + if (buflist_getfile(tagstack[tagstackidx].fmark.fnum, + tagstack[tagstackidx].fmark.mark.lnum, GETF_SETMARK) == FAIL) + { + tagstackidx = oldtagstackidx; /* back to old position */ + goto end_do_tag; + } + } + else + curwin->w_cursor.lnum = tagstack[tagstackidx].fmark.mark.lnum; + curwin->w_cursor.col = tagstack[tagstackidx].fmark.mark.col; + curwin->w_set_curswant = TRUE; + goto end_do_tag; + } + else /* go to newer pattern */ + { + if ((tagstackidx += count - 1) >= tagstacklen) + { + /* + * beyond the last one, just give an error message and go to the + * last one + */ + tagstackidx = tagstacklen - 1; + emsg(topmsg); + } + else if (tagstackidx < 0) /* must have been count == 0 */ + { + emsg(bottommsg); + tagstackidx = 0; + goto end_do_tag; + } + } + /* + * For :tag [arg], remember position before the jump + */ + if (type == 0) + { + tagstack[tagstackidx].fmark.mark = curwin->w_cursor; + tagstack[tagstackidx].fmark.fnum = curbuf->b_fnum; + } + /* curwin will change in the call to find_tags() if ^W^] was used -- webb */ + curwin->w_tagstackidx = tagstackidx; + curwin->w_tagstacklen = tagstacklen; + if (find_tags(tagstack[tagstackidx].tagname, NULL, NULL, NULL, FALSE) > 0) + ++tagstackidx; + +end_do_tag: + curwin->w_tagstackidx = tagstackidx; + curwin->w_tagstacklen = tagstacklen; +} + +/* + * Print the tag stack + */ + void +do_tags() +{ + int i; + char_u *name; + struct taggy *tagstack = curwin->w_tagstack; + int tagstackidx = curwin->w_tagstackidx; + int tagstacklen = curwin->w_tagstacklen; + + set_highlight('t'); /* Highlight title */ + start_highlight(); + MSG_OUTSTR("\n # TO tag FROM line in file"); + stop_highlight(); + for (i = 0; i < tagstacklen; ++i) + { + if (tagstack[i].tagname != NULL) + { + name = fm_getname(&(tagstack[i].fmark)); + if (name == NULL) /* file name not available */ + continue; + + msg_outchar('\n'); + sprintf((char *)IObuff, "%c%2d %-15s %4ld %s", + i == tagstackidx ? '>' : ' ', + i + 1, + tagstack[i].tagname, + tagstack[i].fmark.mark.lnum, + name); + msg_outtrans(IObuff); + } + flushbuf(); /* show one line at a time */ + } + if (tagstackidx == tagstacklen) /* idx at top of stack */ + MSG_OUTSTR("\n>"); +} + +/* + * find_tags() - goto tag or search for tags in tags files + * + * If "tag" is not NULL, search for a single tag and jump to it. + * return FAIL for failure, OK for success + * If "tag" is NULL, find all tags matching the regexp given with 'prog'. + * return FAIL if search completely failed, OK otherwise. + * + * There is a priority in which type of tag is recognized. It is computed + * from the PRI_ defines below. + * + * 6. A static or global tag with a full matching tag for the current file. + * 5. A global tag with a full matching tag for another file. + * 4. A static tag with a full matching tag for another file. + * 2. A static or global tag with an ignore-case matching tag for the + * current file. + * 1. A global tag with an ignore-case matching tag for another file. + * 0. A static tag with an ignore-case matching tag for another file. + * + * Tags in an emacs-style tags file are always global. + */ +#define PRI_GLOBAL 1 /* global or emacs tag */ +#define PRI_CURRENT 2 /* tag for current file */ +#define PRI_FULL_MATCH 4 /* case of tag matches */ + + int +find_tags(tag, prog, num_file, file, help_only) + char_u *tag; /* NULL or tag to search for */ + regexp *prog; /* regexp program or NULL */ + int *num_file; /* return value: number of matches found */ + char_u ***file; /* return value: array of matches found */ + int help_only; /* if TRUE: only tags for help files */ +{ + FILE *fp; + char_u *lbuf; /* line buffer */ + char_u *tag_fname; /* name of tag file */ + int first_file; /* trying first tag file */ + char_u *tagname, *tagname_end; /* name of tag in current line */ + char_u *fname, *fname_end; /* fname in current line */ + int did_open = FALSE; /* did open a tag file */ + int stop_searching = FALSE; /* stop when match found or error */ + int retval = FAIL; /* return value */ + int is_static; /* current tag line is static */ + int is_current; /* file name matches */ + register char_u *p; +#ifdef EMACS_TAGS + char_u *ebuf; /* aditional buffer for etag fname */ + int is_etag; /* current file is emaces style */ +#endif + +/* + * Variables used only when "tag" != NULL + */ + int taglen = 0; /* init for GCC */ + int cmplen; + int full_match; + int icase_match; + int priority; /* priority of current line */ + + char_u *bestmatch_line = NULL; /* saved line for best match found so + far */ + char_u *bestmatch_tag_fname = NULL; + /* copy of tag_fname for best match */ + int bestmatch_priority = 0; /* best match priority */ + +#ifdef EMACS_TAGS + /* + * Stack for included emacs-tags file. + * It has a fixed size, to truncate cyclic includes. jw + */ +# define INCSTACK_SIZE 42 + struct + { + FILE *fp; + char_u *tag_fname; + } incstack[INCSTACK_SIZE]; + + int incstack_idx = 0; /* index in incstack */ + char_u *bestmatch_ebuf = NULL; /* copy of ebuf for best match */ + int bestmatch_is_etag = FALSE; /* copy of is_etag for best match */ +#endif + +/* + * Variables used when "tag" == NULL + */ + char_u **matches = NULL; /* array of matches found */ + char_u **new_matches; + int match_limit = 100; /* max. number of matches stored */ + int match_count = 0; /* number of matches found */ + int i; + int help_save; + + help_save = curbuf->b_help; +/* + * Allocate memory for the buffers that are used + */ + lbuf = alloc(LSIZE); +#ifdef EMACS_TAGS + ebuf = alloc(LSIZE); +#endif + tag_fname = alloc(LSIZE + 1); + /* check for out of memory situation */ + if ((tag == NULL && prog == NULL) || lbuf == NULL || tag_fname == NULL +#ifdef EMACS_TAGS + || ebuf == NULL +#endif + ) + goto findtag_end; + if (tag == NULL) + { + matches = (char_u **)alloc((unsigned)(match_limit * sizeof(char_u *))); + if (matches == NULL) + goto findtag_end; + } + +/* + * Initialize a few variables + */ + if (tag != NULL) + { + taglen = STRLEN(tag); + if (p_tl != 0 && taglen > p_tl) /* adjust for 'taglength' */ + taglen = p_tl; + } + else if (help_only) /* want tags from help file */ + curbuf->b_help = TRUE; + +/* + * Try tag file names from tags option one by one. + */ + for (first_file = TRUE; get_tagfname(first_file, tag_fname) == OK; + first_file = FALSE) + { + /* + * A file that doesn't exist is silently ignored. + */ + if ((fp = fopen((char *)tag_fname, "r")) == NULL) + continue; + did_open = TRUE; /* remember that we found at least one file */ + +#ifdef EMACS_TAGS + is_etag = 0; /* default is: not emacs style */ +#endif + /* + * Read and parse the lines in the file one by one + */ + while (!got_int) + { + line_breakcheck(); + + if (vim_fgets(lbuf, LSIZE, fp)) +#ifdef EMACS_TAGS + if (incstack_idx) /* this was an included file */ + { + --incstack_idx; + fclose(fp); /* end of this file ... */ + fp = incstack[incstack_idx].fp; + STRCPY(tag_fname, incstack[incstack_idx].tag_fname); + vim_free(incstack[incstack_idx].tag_fname); + is_etag = 1; /* (only etags can include) */ + continue; /* ... continue with parent file */ + } + else +#endif + break; /* end of file */ + +#ifdef EMACS_TAGS + /* + * Emacs tags line with CTRL-L: New file name on next line. + * The file name is followed by a ','. + */ + if (*lbuf == Ctrl('L')) /* remember etag filename in ebuf */ + { + is_etag = 1; + if (!vim_fgets(ebuf, LSIZE, fp)) + { + for (p = ebuf; *p && *p != ','; p++) + ; + *p = NUL; + + /* + * atoi(p+1) is the number of bytes before the next ^L + * unless it is an include statement. + */ + if (STRNCMP(p + 1, "include", 7) == 0 && + incstack_idx < INCSTACK_SIZE) + { + if ((incstack[incstack_idx].tag_fname = + strsave(tag_fname)) != NULL) + { + incstack[incstack_idx].fp = fp; + if ((fp = fopen((char *)ebuf, "r")) == NULL) + { + fp = incstack[incstack_idx].fp; + vim_free(incstack[incstack_idx].tag_fname); + } + else + { + STRCPY(tag_fname, ebuf); + ++incstack_idx; + } + is_etag = 0; /* we can include anything */ + } + } + } + continue; + } +#endif + + /* + * Figure out where the different strings are in this line. + * For "normal" tags: Do a quick check if the tag matches. + * This speeds up tag searching a lot! + */ + if (tag != NULL +#ifdef EMACS_TAGS + && !is_etag +#endif + ) + { + tagname = lbuf; + fname = NULL; + for (tagname_end = lbuf; *tagname_end && + !vim_iswhite(*tagname_end); ++tagname_end) + { + if (*tagname_end == ':') + { + if (fname == NULL) + fname = skipwhite(skiptowhite(tagname_end)); + if (fnamencmp(lbuf, fname, tagname_end - lbuf) == 0 && + vim_iswhite(fname[tagname_end - lbuf])) + tagname = tagname_end + 1; + } + } + + /* + * Skip this line if the lenght of the tag is different. + */ + cmplen = tagname_end - tagname; + if (p_tl != 0 && cmplen > p_tl) /* adjust for 'taglength' */ + cmplen = p_tl; + if (taglen != cmplen) + continue; + + /* + * Skip this line if the tag does not match (ignoring case). + */ + if (vim_strnicmp(tagname, tag, (size_t)cmplen)) + continue; + + /* + * This is a useful tag, isolate the filename. + */ + if (fname == NULL) + fname = skipwhite(skiptowhite(tagname_end)); + fname_end = skiptowhite(fname); + if (*fname_end == NUL) + i = FAIL; + else + i = OK; + } + else + i = parse_tag_line(lbuf, +#ifdef EMACS_TAGS + is_etag, +#endif + &tagname, &tagname_end, &fname, &fname_end, NULL); + if (i == FAIL) + { + EMSG2("Format error in tags file \"%s\"", tag_fname); + stop_searching = TRUE; + break; + } + +#ifdef EMACS_TAGS + is_static = FALSE; + if (!is_etag) /* emacs tags are never static */ +#endif + is_static = test_for_static(&tagname, tagname_end, + fname, fname_end); +#ifdef EMACS_TAGS + if (is_etag) + fname = ebuf; +#endif + /* + * "tag" == NULL: find tags matching regexp "prog" + */ + if (tag == NULL) + { + *tagname_end = NUL; + if (vim_regexec(prog, tagname, TRUE)) + { + is_current = test_for_current( +#ifdef EMACS_TAGS + is_etag, +#endif + fname, fname_end, tag_fname); + if (!is_static || is_current) + { + /* + * Found a match, add it to matches[]. + * May need to make matches[] larger. + */ + if (match_count == match_limit) + { + match_limit += 100; + new_matches = (char_u **)alloc( + (unsigned)(match_limit * sizeof(char_u *))); + if (new_matches == NULL) + { + /* Out of memory! Just forget about the rest + * of the matches. */ + retval = OK; + stop_searching = TRUE; + break; + } + for (i = 0; i < match_count; i++) + new_matches[i] = matches[i]; + vim_free(matches); + matches = new_matches; + } + if (help_only) + { + int len; + + /* + * Append the help-heuristic number after the + * tagname, for sorting it later. + */ + len = STRLEN(tagname); + p = alloc(len + 10); + if (p != NULL) + { + STRCPY(p, tagname); + sprintf((char *)p + len + 1, "%06d", + help_heuristic(tagname, + (int)(prog->startp[0] - tagname))); + } + matches[match_count++] = p; + } + else + matches[match_count++] = strsave(tagname); + } + } + } + /* + * "tag" != NULL: find tag and jump to it + */ + else + { + /* + * If tag length does not match, skip the rest + */ + cmplen = tagname_end - tagname; + if (p_tl != 0 && cmplen > p_tl) /* adjust for 'taglength' */ + cmplen = p_tl; + if (taglen == cmplen) + { + /* + * Check for match (ignoring case). + */ + icase_match = (vim_strnicmp(tagname, tag, + (size_t)cmplen) == 0); + if (icase_match) /* Tag matches somehow */ + { + /* + * If it's a full match for the current file, jump to + * it now. + */ + full_match = (STRNCMP(tagname, tag, cmplen) == 0); + is_current = test_for_current( +#ifdef EMACS_TAGS + is_etag, +#endif + fname, fname_end, tag_fname); + if (full_match && is_current) + { + retval = jumpto_tag(lbuf, +#ifdef EMACS_TAGS + is_etag, ebuf, +#endif + tag_fname); + stop_searching = TRUE; + break; + } + + /* + * If the priority of the current line is higher than + * the best match so far, store it as the best match + */ + if (full_match) + priority = PRI_FULL_MATCH; + else + priority = 0; + if (is_current) + priority += PRI_CURRENT; + if (!is_static) + priority += PRI_GLOBAL; + + if (priority > bestmatch_priority) + { + vim_free(bestmatch_line); + bestmatch_line = strsave(lbuf); + vim_free(bestmatch_tag_fname); + bestmatch_tag_fname = strsave(tag_fname); + bestmatch_priority = priority; +#ifdef EMACS_TAGS + bestmatch_is_etag = is_etag; + if (is_etag) + { + vim_free(bestmatch_ebuf); + bestmatch_ebuf = strsave(ebuf); + } +#endif + } + } + } + } + } + fclose(fp); +#ifdef EMACS_TAGS + while (incstack_idx) + { + --incstack_idx; + fclose(incstack[incstack_idx].fp); + vim_free(incstack[incstack_idx].tag_fname); + } +#endif + if (stop_searching) + break; + + /* + * Stop searching if a tag was found in the current tags file and + * we got a global match with matching case or 'ignorecase' is set. + */ + if (tag != NULL && bestmatch_line != NULL && + bestmatch_priority >= (p_ic ? 0 : PRI_FULL_MATCH) + PRI_GLOBAL) + break; + } + + if (!stop_searching) + { + if (!did_open) /* never opened any tags file */ + EMSG("No tags file"); + else if (tag == NULL) + { + retval = OK; /* It's OK even when no tag found */ + } + else + { + /* + * If we didn't find a static full match, use the best match found. + */ + if (bestmatch_line != NULL) + { + if (bestmatch_priority < PRI_FULL_MATCH) + { + MSG("Only found tag with different case!"); + if (!msg_scrolled) + { + flushbuf(); + mch_delay(1000L, TRUE); + } + } + retval = jumpto_tag(bestmatch_line, +#ifdef EMACS_TAGS + bestmatch_is_etag, bestmatch_ebuf, +#endif + bestmatch_tag_fname); + } + else + EMSG("tag not found"); + } + } + +findtag_end: + vim_free(lbuf); + vim_free(tag_fname); + vim_free(bestmatch_line); + vim_free(bestmatch_tag_fname); +#ifdef EMACS_TAGS + vim_free(ebuf); + vim_free(bestmatch_ebuf); +#endif + + if (tag == NULL) + { + if (retval == FAIL) /* free all matches found */ + while (match_count > 0) + vim_free(matches[--match_count]); + if (match_count == 0) /* nothing found, free matches[] */ + { + vim_free(matches); + matches = NULL; + } + *file = matches; + *num_file = match_count; + } + curbuf->b_help = help_save; + + return retval; +} + +/* + * Get the next name of a tag file from the tag file list. + * For help files, use "vim_tags" file only. + * + * Return FAIL if no more tag file names, OK otherwise. + */ + static int +get_tagfname(first, buf) + int first; /* TRUE when first file name is wanted */ + char_u *buf; /* pointer to buffer of LSIZE chars */ +{ + static char_u *np = NULL; + char_u *fname; + size_t path_len, fname_len; + /* + * A list is made of the files that have been visited. + */ + struct visited + { + struct visited *v_next; +#if defined(UNIX) + struct stat v_st; +#else + char_u v_fname[1]; /* actually longer */ +#endif + }; + static struct visited *first_visited = NULL; + struct visited *vp; +#if defined(UNIX) + struct stat st; +#endif + + if (first) + { + np = p_tags; + while (first_visited != NULL) + { + vp = first_visited->v_next; + vim_free(first_visited); + first_visited = vp; + } + } + + if (np == NULL) /* tried allready (or bogus call) */ + return FAIL; + + /* + * For a help window only try the file 'vim_tags' in the same + * directory as 'helpfile'. + */ + if (curbuf->b_help) + { + path_len = gettail(p_hf) - p_hf; + if (path_len + 9 >= LSIZE) + return FAIL; + vim_memmove(buf, p_hf, path_len); + STRCPY(buf + path_len, "vim_tags"); + + np = NULL; /* try only once */ + } + + else + { + /* + * Loop until we have found a file name that can be used. + */ + for (;;) + { + if (*np == NUL) /* tried all possibilities */ + return FAIL; + + /* + * Copy next file name into buf. + */ + (void)copy_option_part(&np, buf, LSIZE, " ,"); + + /* + * Tag file name starting with "./": Replace '.' with path of + * current file. + */ + if (buf[0] == '.' && ispathsep(buf[1])) + { + if (curbuf->b_filename == NULL) /* skip if no filename */ + continue; + + path_len = gettail(curbuf->b_filename) - curbuf->b_filename; + fname = buf + 1; + while (ispathsep(*fname)) /* skip '/' and the like */ + ++fname; + fname_len = STRLEN(fname); + if (fname_len + path_len + 1 > LSIZE) + continue; + vim_memmove(buf + path_len, fname, fname_len + 1); + vim_memmove(buf, curbuf->b_filename, path_len); + } + + /* + * Check if this tags file has been used already. + * If file doesn't exist, skip it. + */ +#if defined(UNIX) + if (stat((char *)buf, &st) < 0) +#else + if (FullName(buf, NameBuff, MAXPATHL, TRUE) == FAIL) +#endif + continue; + + for (vp = first_visited; vp != NULL; vp = vp->v_next) +#if defined(UNIX) + if (vp->v_st.st_dev == st.st_dev && + vp->v_st.st_ino == st.st_ino) +#else + if (fnamecmp(vp->v_fname, NameBuff) == 0) +#endif + break; + + if (vp != NULL) /* already visited, skip it */ + continue; + + /* + * Found the next name. Add it to the list of visited files. + */ +#if defined(UNIX) + vp = (struct visited *)alloc((unsigned)sizeof(struct visited)); +#else + vp = (struct visited *)alloc((unsigned)(sizeof(struct visited) + + STRLEN(NameBuff))); +#endif + if (vp != NULL) + { +#if defined(UNIX) + vp->v_st = st; +#else + STRCPY(vp->v_fname, NameBuff); +#endif + vp->v_next = first_visited; + first_visited = vp; + } + break; + } + } + return OK; +} + +/* + * Parse one line from the tags file. Find start/end of tag name, start/end of + * file name and start of search pattern. + * + * If is_etag is TRUE, fname and fname_end are not set. + * If command == NULL it is not set. + * + * Return FAIL if there is a format error in this line, OK otherwise. + */ + static int +parse_tag_line(lbuf, +#ifdef EMACS_TAGS + is_etag, +#endif + tagname, tagname_end, fname, fname_end, command) + char_u *lbuf; +#ifdef EMACS_TAGS + int is_etag; +#endif + char_u **tagname; + char_u **tagname_end; + char_u **fname; + char_u **fname_end; + char_u **command; +{ + char_u *p; + +#ifdef EMACS_TAGS + char_u *p_7f; + + if (is_etag) + { + /* + * There are two formats for an emacs tag line: + * 1: struct EnvBase ^?EnvBase^A139,4627 + * 2: #define ARPB_WILD_WORLD ^?153,5194 + */ + p_7f = vim_strchr(lbuf, 0x7f); + if (p_7f == NULL) + return FAIL; + /* find start of line number */ + for (p = p_7f + 1; *p < '0' || *p > '9'; ++p) + if (*p == NUL) + return FAIL; + if (command != NULL) + *command = p; + + /* first format: explicit tagname given */ + if (p[-1] == Ctrl('A')) + { + *tagname = p_7f + 1; + *tagname_end = p - 1; + } + else + /* second format: isolate tagname */ + { + /* find end of tagname */ + for (p = p_7f - 1; *p == ' ' || *p == '\t' || + *p == '(' || *p == ';'; --p) + if (p == lbuf) + return FAIL; + *tagname_end = p + 1; + while (p >= lbuf && *p != ' ' && *p != '\t') + --p; + *tagname = p + 1; + } + } + else + { +#endif + /* Isolate the tagname, from lbuf up to the first white */ + *tagname = lbuf; + p = skiptowhite(lbuf); + if (*p == NUL) + return FAIL; + *tagname_end = p; + + /* Isolate file name, from first to second white space */ + p = skipwhite(p); + *fname = p; + p = skiptowhite(p); + if (*p == NUL) + return FAIL; + *fname_end = p; + + /* find start of search command, after second white space */ + if (command != NULL) + { + p = skipwhite(p); + if (*p == NUL) + return FAIL; + *command = p; + } +#ifdef EMACS_TAGS + } +#endif + + return OK; +} + +/* + * Check if tagname is a static tag + * + * Static tags produced by the ctags program have the + * format: 'file:tag file /pattern'. + * This is only recognized when both occurences of 'file' + * are the same, to avoid recognizing "string::string" or + * ":exit". + * + * Return TRUE if it is a static tag and adjust *tagname to the real tag. + * Return FALSE if it is not a static tag. + */ + static int +test_for_static(tagname, tagname_end, fname, fname_end) + char_u **tagname; + char_u *tagname_end; + char_u *fname; + char_u *fname_end; +{ + char_u *p; + + p = *tagname + (fname_end - fname); + if (p < tagname_end && *p == ':' && + fnamencmp(*tagname, fname, fname_end - fname) == 0) + { + *tagname = p + 1; + return TRUE; + } + return FALSE; +} + +/* + * Jump to a tag that has been found in one of the tag files + */ + static int +jumpto_tag(lbuf, +#ifdef EMACS_TAGS + is_etag, etag_fname, +#endif + tag_fname) + char_u *lbuf; /* line from the tags file for this tag */ +#ifdef EMACS_TAGS + int is_etag; /* TRUE if it's from an emacs tags file */ + char_u *etag_fname; /* file name for tag if is_etag is TRUE */ +#endif + char_u *tag_fname; /* file name of the tags file itself */ +{ + int save_secure; + int save_p_ws, save_p_scs, save_p_ic; + char_u *str; + char_u *pbuf; /* search pattern buffer */ + char_u *p; + char_u *expanded_fname = NULL; + char_u *tagname, *tagname_end; + char_u *fname, *fname_end; + char_u *orig_fname; + int retval = FAIL; + int getfile_result; + int search_options; + + pbuf = alloc(LSIZE); + + if (pbuf == NULL +#ifdef EMACS_TAGS + || (is_etag && etag_fname == NULL) +#endif + || tag_fname == NULL) + goto erret; + + /* + * find the search pattern (caller should check it is there) + */ + if (parse_tag_line(lbuf, +#ifdef EMACS_TAGS + is_etag, +#endif + &tagname, &tagname_end, &fname, &fname_end, &str) == FAIL) + goto erret; + orig_fname = fname; /* remember for test_for_static() below */ + +#ifdef EMACS_TAGS + if (is_etag) + fname = etag_fname; + else +#endif + *fname_end = NUL; + + /* + * If the command is a string like "/^function fname" + * scan through the search string. If we see a magic + * char, we have to quote it. This lets us use "real" + * implementations of ctags. + */ + if (*str == '/' || *str == '?') + { + p = pbuf; + *p++ = *str++; /* copy the '/' or '?' */ + if (*str == '^') + *p++ = *str++; /* copy the '^' */ + + while (*str) + { + switch (*str) + { + /* Always remove '\' before '('. + * Remove a '\' befor '*' if 'nomagic'. + * Otherwise just copy the '\' and don't look at the + * next character + */ + case '\\': if (str[1] == '(' || (!p_magic && str[1] == '*')) + ++str; + else + *p++ = *str++; + break; + + case '\r': + case '\n': *str = pbuf[0]; /* copy '/' or '?' */ + str[1] = NUL; /* delete NL after CR */ + break; + + /* + * if string ends in search character: skip it + * else escape it with '\' + */ + case '/': + case '?': if (*str != pbuf[0]) /* not the search char */ + break; + /* last char */ + if (str[1] == '\n' || str[1] == '\r') + { + ++str; + continue; + } + case '[': + if (!p_magic) + break; + case '^': + case '*': + case '~': + case '.': *p++ = '\\'; + break; + } + *p++ = *str++; + } + } + else /* not a search command, just copy it */ + { + for (p = pbuf; *str && *str != '\n'; ) + { +#ifdef EMACS_TAGS + if (is_etag && *str == ',') /* stop at ',' after line number */ + break; +#endif + *p++ = *str++; + } + } + *p = NUL; + + /* + * expand filename (for environment variables) + */ + expanded_fname = ExpandOne((char_u *)fname, NULL, WILD_LIST_NOTFOUND, + WILD_EXPAND_FREE); + if (expanded_fname != NULL) + fname = expanded_fname; + + /* + * if 'tagrelative' option set, may change file name + */ + fname = expand_rel_name(fname, tag_fname); + + /* + * check if file for tag exists before abandoning current file + */ + if (getperm(fname) < 0) + { + EMSG2("File \"%s\" does not exist", fname); + goto erret; + } + + ++RedrawingDisabled; + /* + * if it was a CTRL-W CTRL-] command split window now + */ + if (postponed_split) + win_split(0, FALSE); + /* + * A :ta from a help file will keep the b_help flag set. + */ + keep_help_flag = curbuf->b_help; + getfile_result = getfile(0, fname, NULL, TRUE, (linenr_t)0); + + if (getfile_result <= 0) /* got to the right file */ + { + curwin->w_set_curswant = TRUE; + postponed_split = FALSE; + + save_secure = secure; + secure = 1; + /* + * If 'cpoptions' contains 't', store the search pattern for the "n" + * command. If 'cpoptions' does not contain 't', the search pattern + * is not stored. + */ + if (vim_strchr(p_cpo, CPO_TAGPAT) != NULL) + search_options = 0; + else + search_options = SEARCH_KEEP; + + /* + * if the command is a search, try here + * + * Rather than starting at line one, just turn wrap-scan + * on temporarily, this ensures that tags on line 1 will + * be found, and makes sure our guess searches search the + * whole file when repeated -- webb. + * Also reset 'smartcase' for the search, since the search + * pattern was not typed by the user. + */ + if (pbuf[0] == '/' || pbuf[0] == '?') + { + save_p_ws = p_ws; + save_p_ic = p_ic; + save_p_scs = p_scs; + p_ws = TRUE; /* Switch wrap-scan on temporarily */ + p_ic = FALSE; /* don't ignore case now */ + p_scs = FALSE; + add_to_history(1, pbuf + 1); /* put pattern in search history */ + + if (do_search(pbuf[0], pbuf + 1, (long)1, search_options)) + retval = OK; + else + { + register int notfound = FALSE; + + /* + * try again, ignore case now + */ + p_ic = TRUE; + if (!do_search(pbuf[0], pbuf + 1, (long)1, search_options)) + { + /* + * Failed to find pattern, take a guess: "^func (" + */ + (void)test_for_static(&tagname, tagname_end, + orig_fname, fname_end); + *tagname_end = NUL; + sprintf((char *)pbuf, "^%s[ \t]*(", tagname); + if (!do_search('/', pbuf, (long)1, search_options)) + { + /* Guess again: "^char * func (" */ + sprintf((char *)pbuf, "^[#a-zA-Z_].*%s[ \t]*(", + tagname); + if (!do_search('/', pbuf, (long)1, search_options)) + notfound = TRUE; + } + } + if (notfound) + EMSG("Can't find tag pattern"); + else + { + MSG("Couldn't find tag, just guessing!"); + if (!msg_scrolled) + { + flushbuf(); + mch_delay(1000L, TRUE); + } + retval = OK; + } + } + p_ws = save_p_ws; + p_ic = save_p_ic; + p_scs = save_p_scs; + } + else + { /* start command in line 1 */ + curwin->w_cursor.lnum = 1; + do_cmdline(pbuf, TRUE, TRUE); + retval = OK; + } + + if (secure == 2) /* done something that is not allowed */ + wait_return(TRUE); + secure = save_secure; + + /* + * Print the file message after redraw if jumped to another file. + * Don't do this for help files (avoid a hit-return message). + */ + if (getfile_result == -1) + { + if (!curbuf->b_help) + need_fileinfo = TRUE; + retval = OK; /* always return OK if jumped to another + file (at least we found the file!) */ + } + + /* + * For a help buffer: Put the cursor line at the top of the window, + * the help subject will be below it. + */ + if (curbuf->b_help) + { + curwin->w_topline = curwin->w_cursor.lnum; + comp_Botline(curwin); + cursupdate(); /* take care of 'scrolloff' */ + updateScreen(NOT_VALID); + } + --RedrawingDisabled; + } + else + { + --RedrawingDisabled; + if (postponed_split) /* close the window */ + { + close_window(curwin, FALSE); + postponed_split = FALSE; + } + } + +erret: + vim_free(pbuf); + vim_free(expanded_fname); + + return retval; +} + +/* + * If 'tagrelative' option set, change fname (name of file containing tag) + * according to tag_fname (name of tag file containing fname). + */ + static char_u * +expand_rel_name(fname, tag_fname) + char_u *fname; + char_u *tag_fname; +{ + char_u *p; + + if ((p_tr || curbuf->b_help) && !isFullName(fname) && + (p = gettail(tag_fname)) != tag_fname) + { + STRCPY(NameBuff, tag_fname); + STRNCPY(NameBuff + (p - tag_fname), fname, + MAXPATHL - (p - tag_fname)); + /* + * Translate names like "src/a/../b/file.c" into "src/b/file.c". + */ + simplify_filename(NameBuff); + fname = NameBuff; + } + return fname; +} + +/* + * Moves the tail part of the path (including the terminating NUL) pointed to + * by "tail" to the new location pointed to by "here". This should accomodate + * an overlapping move. + */ +#define movetail(here, tail) vim_memmove(here, tail, STRLEN(tail) + (size_t)1) + +/* + * For MS-DOS we should check for backslash too, but that is complicated. + */ +#define DIR_SEP '/' /* the directory separator character */ + +/* + * Converts a filename into a canonical form. It simplifies a filename into + * its simplest form by stripping out unneeded components, if any. The + * resulting filename is simplified in place and will either be the same + * length as that supplied, or shorter. + */ + static void +simplify_filename(filename) + char_u *filename; +{ + int absolute = FALSE; + int components = 0; + char_u *p, *tail; + + p = filename; + if (*p == DIR_SEP) + { + absolute = TRUE; + ++p; + } + do + { + /* Always leave "p" pointing to character following next "/". */ + if (*p == DIR_SEP) + movetail(p, p+1); /* strip duplicate "/" */ + else if (STRNCMP(p, "./", 2) == 0) + movetail(p, p+2); /* strip "./" */ + else if (STRNCMP(p, "../", 3) == 0) + { + if (components > 0) /* strip any prev. component */ + { + *(p - 1) = 0; /* delete "/" before "../" */ + tail = p + 2; /* skip to "/" of "../" */ + p = vim_strrchr(filename, DIR_SEP); /* find preceding sep. */ + if (p != NULL) /* none found */ + ++p; /* skip to char after "/" */ + else + { + ++tail; /* strip leading "/" from tail*/ + p = filename; /* go back to beginning */ + if (absolute) /* skip over any leading "/" */ + ++p; + } + movetail(p, tail); /* strip previous component */ + --components; + } + else if (absolute) /* no parent to root... */ + movetail(p, p+3); /* so strip "../" */ + else /* leading series of "../" */ + { + p = vim_strchr(p, DIR_SEP); /* skip to next "/" */ + if (p != NULL) + ++p; /* skip to char after "/" */ + } + } + else + { + ++components; /* simple path component */ + p = vim_strchr(p, DIR_SEP); /* skip to next "/" */ + if (p != NULL) + ++p; /* skip to char after "/" */ + } + } while (p != NULL && *p != NUL); +} + +/* + * Check if we have a tag for the current file. + * This is a bit slow, because of the full path compare in fullpathcmp(). + * Return TRUE if tag for file "fname" if tag file "tag_fname" is for current + * file. + */ + static int +#ifdef EMACS_TAGS +test_for_current(is_etag, fname, fname_end, tag_fname) + int is_etag; +#else +test_for_current(fname, fname_end, tag_fname) +#endif + char_u *fname; + char_u *fname_end; + char_u *tag_fname; +{ + int c; + int retval; + + if (curbuf->b_filename == NULL) + retval = FALSE; + else + { +#ifdef EMACS_TAGS + if (is_etag) + c = 0; /* to shut up GCC */ + else +#endif + { + c = *fname_end; + *fname_end = NUL; + } + retval = (fullpathcmp(expand_rel_name(fname, tag_fname), + curbuf->b_xfilename) == FPC_SAME); +#ifdef EMACS_TAGS + if (!is_etag) +#endif + *fname_end = c; + } + + return retval; +} diff --git a/usr.bin/vim/term.c b/usr.bin/vim/term.c new file mode 100644 index 00000000000..797caecd580 --- /dev/null +++ b/usr.bin/vim/term.c @@ -0,0 +1,3107 @@ +/* $OpenBSD: term.c,v 1.1.1.1 1996/09/07 21:40:24 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ +/* + * + * term.c: functions for controlling the terminal + * + * primitive termcap support for Amiga, MSDOS, and Win32 included + * + * NOTE: padding and variable substitution is not performed, + * when compiling without HAVE_TGETENT, we use tputs() and tgoto() dummies. + */ + +/* + * Some systems have a prototype for tgetstr() with (char *) instead of + * (char **). This define removes that prototype. We include our own prototype + * below. + */ + +#define tgetstr tgetstr_defined_wrong +#include "vim.h" + +#include "globals.h" +#include "option.h" +#include "proto.h" + +#ifdef HAVE_TGETENT +# ifdef HAVE_TERMCAP_H +# include +# endif + +/* + * A few linux systems define outfuntype in termcap.h to be used as the third + * argument for tputs(). + */ +# ifdef VMS +# define TPUTSFUNCAST +# else +# ifdef HAVE_OUTFUNTYPE +# define TPUTSFUNCAST (outfuntype) +# else +# define TPUTSFUNCAST (int (*)()) +# endif +# endif +#endif + +#undef tgetstr + +/* + * Here are the builtin termcap entries. They are not stored as complete + * Tcarr structures, as such a structure is too big. + * + * The entries are compact, therefore they normally are included even when + * HAVE_TGETENT is defined. When HAVE_TGETENT is defined, the builtin entries + * can be accessed with "builtin_amiga", "builtin_ansi", "builtin_debug", etc. + * + * Each termcap is a list of builtin_term structures. It always starts with + * KS_NAME, which separates the entries. See parse_builtin_tcap() for all + * details. + * bt_entry is either a KS_xxx code (< 0x100), or a K_xxx code. + */ +struct builtin_term +{ + int bt_entry; + char *bt_string; +}; + +/* start of keys that are not directly used by Vim but can be mapped */ +#define BT_EXTRA_KEYS 0x101 + +static struct builtin_term *find_builtin_term __ARGS((char_u *name)); +static void parse_builtin_tcap __ARGS((char_u *s)); +static void gather_termleader __ARGS((void)); +static int get_bytes_from_buf __ARGS((char_u *, char_u *, int)); +static int is_builtin_term __ARGS((char_u *)); + +#ifdef HAVE_TGETENT +static char_u *tgetent_error __ARGS((char_u *, char_u *)); + +/* + * Here is our own prototype for tgetstr(), any prototypes from the include + * files have been disabled by the define at the start of this file. + */ +char *tgetstr __PARMS((char *, char **)); + +/* + * Don't declare these variables if termcap.h contains them. + * Autoconf checks if these variables should be declared extern (not all + * systems have them). + * Some versions define ospeed to be speed_t, but that is incompatible with + * BSD, where ospeed is short and speed_t is long. + */ +# ifndef HAVE_OSPEED +# ifdef OSPEED_EXTERN +extern short ospeed; +# else +short ospeed; +# endif +# endif +# ifndef HAVE_UP_BC_PC +# ifdef UP_BC_PC_EXTERN +extern char *UP, *BC, PC; +# else +char *UP, *BC, PC; +# endif +# endif + +# define TGETSTR(s, p) (char_u *)tgetstr((s), (char **)(p)) +# define TGETENT(b, t) tgetent((char *)(b), (char *)(t)) + +#endif /* HAVE_TGETENT */ + +struct builtin_term builtin_termcaps[] = +{ + +#if defined(USE_GUI) +/* + * Motif/Athena pseudo term-cap. + */ + {KS_NAME, "gui"}, + {KS_CE, "\033|$"}, + {KS_AL, "\033|i"}, +# ifdef TERMINFO + {KS_CAL, "\033|%p1%dI"}, +# else + {KS_CAL, "\033|%dI"}, +# endif + {KS_DL, "\033|d"}, +# ifdef TERMINFO + {KS_CDL, "\033|%p1%dD"}, + {KS_CS, "\033|%p1%d;%p2%dR"}, +# else + {KS_CDL, "\033|%dD"}, + {KS_CS, "\033|%d;%dR"}, +# endif + {KS_CL, "\033|C"}, + {KS_ME, "\033|63H"}, /* 63 = HL_ALL, H = off */ + {KS_MR, "\033|1h"}, /* 1 = HL_INVERSE, h = on */ + {KS_MD, "\033|2h"}, /* 2 = HL_BOLD, h = on */ + {KS_SE, "\033|16H"}, /* 16 = HL_STANDOUT, H = off */ + {KS_SO, "\033|16h"}, /* 16 = HL_STANDOUT, h = on */ + {KS_UE, "\033|8H"}, /* 8 = HL_UNDERLINE, H = off */ + {KS_US, "\033|8h"}, /* 8 = HL_UNDERLINE, h = on */ + {KS_CZR, "\033|4H"}, /* 4 = HL_ITAL, H = off */ + {KS_CZH, "\033|4h"}, /* 4 = HL_ITAL, h = on */ + {KS_VB, "\033|f"}, +# ifdef TERMINFO + {KS_CM, "\033|%p1%d;%p2%dM"}, +# else + {KS_CM, "\033|%d;%dM"}, +# endif + /* there are no key sequences here, the GUI sequences are recognized + * in check_termcodes() */ +#endif + +#ifndef NO_BUILTIN_TCAPS + +# if defined(AMIGA) || defined(ALL_BUILTIN_TCAPS) +/* + * Amiga console window, default for Amiga + */ + {KS_NAME, "amiga"}, + {KS_CE, "\033[K"}, + {KS_CD, "\033[J"}, + {KS_AL, "\033[L"}, +# ifdef TERMINFO + {KS_CAL, "\033[%p1%dL"}, +# else + {KS_CAL, "\033[%dL"}, +# endif + {KS_DL, "\033[M"}, +# ifdef TERMINFO + {KS_CDL, "\033[%p1%dM"}, +# else + {KS_CDL, "\033[%dM"}, +# endif + {KS_CL, "\014"}, + {KS_VI, "\033[0 p"}, + {KS_VE, "\033[1 p"}, + {KS_ME, "\033[0m"}, + {KS_MR, "\033[7m"}, + {KS_MD, "\033[1m"}, + {KS_SE, "\033[0m"}, + {KS_SO, "\033[33m"}, + {KS_US, "\033[4m"}, + {KS_UE, "\033[0m"}, + {KS_CZH, "\033[3m"}, + {KS_CZR, "\033[0m"}, + {KS_MS, "\001"}, +# ifdef TERMINFO + {KS_CM, "\033[%i%p1%d;%p2%dH"}, +# else + {KS_CM, "\033[%i%d;%dH"}, +# endif +# ifdef TERMINFO + {KS_CRI, "\033[%p1%dC"}, +# else + {KS_CRI, "\033[%dC"}, +# endif + {K_UP, "\233A"}, + {K_DOWN, "\233B"}, + {K_LEFT, "\233D"}, + {K_RIGHT, "\233C"}, + {K_S_UP, "\233T"}, + {K_S_DOWN, "\233S"}, + {K_S_LEFT, "\233 A"}, + {K_S_RIGHT, "\233 @"}, + {K_S_TAB, "\233Z"}, + {K_F1, "\233\060~"},/* some compilers don't understand "\2330" */ + {K_F2, "\233\061~"}, + {K_F3, "\233\062~"}, + {K_F4, "\233\063~"}, + {K_F5, "\233\064~"}, + {K_F6, "\233\065~"}, + {K_F7, "\233\066~"}, + {K_F8, "\233\067~"}, + {K_F9, "\233\070~"}, + {K_F10, "\233\071~"}, + {K_S_F1, "\233\061\060~"}, + {K_S_F2, "\233\061\061~"}, + {K_S_F3, "\233\061\062~"}, + {K_S_F4, "\233\061\063~"}, + {K_S_F5, "\233\061\064~"}, + {K_S_F6, "\233\061\065~"}, + {K_S_F7, "\233\061\066~"}, + {K_S_F8, "\233\061\067~"}, + {K_S_F9, "\233\061\070~"}, + {K_S_F10, "\233\061\071~"}, + {K_HELP, "\233?~"}, + {K_INS, "\233\064\060~"}, /* 101 key keyboard */ + {K_PAGEUP, "\233\064\061~"}, /* 101 key keyboard */ + {K_PAGEDOWN, "\233\064\062~"}, /* 101 key keyboard */ + {K_HOME, "\233\064\064~"}, /* 101 key keyboard */ + {K_END, "\233\064\065~"}, /* 101 key keyboard */ + + {BT_EXTRA_KEYS, ""}, + {TERMCAP2KEY('#', '2'), "\233\065\064~"}, /* shifted home key */ + {TERMCAP2KEY('#', '3'), "\233\065\060~"}, /* shifted insert key */ + {TERMCAP2KEY('*', '7'), "\233\065\065~"}, /* shifted end key */ +# endif + +# if defined(UNIX) || defined(ALL_BUILTIN_TCAPS) || defined(SOME_BUILTIN_TCAPS) || defined(__EMX__) +/* + * standard ANSI terminal, default for unix + */ + {KS_NAME, "ansi"}, + {KS_CE, "\033[K"}, + {KS_AL, "\033[L"}, +# ifdef TERMINFO + {KS_CAL, "\033[%p1%dL"}, +# else + {KS_CAL, "\033[%dL"}, +# endif + {KS_DL, "\033[M"}, +# ifdef TERMINFO + {KS_CDL, "\033[%p1%dM"}, +# else + {KS_CDL, "\033[%dM"}, +# endif + {KS_CL, "\033[H\033[2J"}, + {KS_ME, "\033[0m"}, + {KS_MR, "\033[7m"}, + {KS_MS, "\001"}, +# ifdef TERMINFO + {KS_CM, "\033[%i%p1%d;%p2%dH"}, +# else + {KS_CM, "\033[%i%d;%dH"}, +# endif +# ifdef TERMINFO + {KS_CRI, "\033[%p1%dC"}, +# else + {KS_CRI, "\033[%dC"}, +# endif +# endif + +# if defined(MSDOS) || defined(ALL_BUILTIN_TCAPS) || defined(__EMX__) +/* + * These codes are valid when nansi.sys or equivalent has been installed. + * Function keys on a PC are preceded with a NUL. These are converted into + * K_NUL '\316' in mch_inchar(), because we cannot handle NULs in key codes. + * CTRL-arrow is used instead of SHIFT-arrow. + */ +#ifdef __EMX__ + {KS_NAME, "os2ansi"}, +#else + {KS_NAME, "pcansi"}, + {KS_DL, "\033[M"}, + {KS_AL, "\033[L"}, +#endif + {KS_CE, "\033[K"}, + {KS_CL, "\033[2J"}, + {KS_ME, "\033[0m"}, + {KS_MR, "\033[7m"}, + {KS_MS, "\001"}, +# ifdef TERMINFO + {KS_CM, "\033[%i%p1%d;%p2%dH"}, +# else + {KS_CM, "\033[%i%d;%dH"}, +# endif +# ifdef TERMINFO + {KS_CRI, "\033[%p1%dC"}, +# else + {KS_CRI, "\033[%dC"}, +# endif + {K_UP, "\316H"}, + {K_DOWN, "\316P"}, + {K_LEFT, "\316K"}, + {K_RIGHT, "\316M"}, + {K_S_LEFT, "\316s"}, + {K_S_RIGHT, "\316t"}, + {K_F1, "\316;"}, + {K_F2, "\316<"}, + {K_F3, "\316="}, + {K_F4, "\316>"}, + {K_F5, "\316?"}, + {K_F6, "\316@"}, + {K_F7, "\316A"}, + {K_F8, "\316B"}, + {K_F9, "\316C"}, + {K_F10, "\316D"}, + {K_F11, "\316\205"}, /* guessed */ + {K_F12, "\316\206"}, /* guessed */ + {K_S_F1, "\316T"}, + {K_S_F2, "\316U"}, + {K_S_F3, "\316V"}, + {K_S_F4, "\316W"}, + {K_S_F5, "\316X"}, + {K_S_F6, "\316Y"}, + {K_S_F7, "\316Z"}, + {K_S_F8, "\316["}, + {K_S_F9, "\316\\"}, + {K_S_F10, "\316]"}, + {K_S_F11, "\316\207"}, /* guessed */ + {K_S_F12, "\316\210"}, /* guessed */ + {K_INS, "\316R"}, + {K_DEL, "\316S"}, + {K_HOME, "\316G"}, + {K_END, "\316O"}, + {K_PAGEDOWN, "\316Q"}, + {K_PAGEUP, "\316I"}, +# endif + +# if defined(MSDOS) +/* + * These codes are valid for the pc video. The entries that start with ESC | + * are translated into conio calls in msdos.c. Default for MSDOS. + */ + {KS_NAME, "pcterm"}, + {KS_CE, "\033|K"}, + {KS_AL, "\033|L"}, + {KS_DL, "\033|M"}, +# ifdef TERMINFO + {KS_CS, "\033|%i%p1%d;%p2%dr"}, +# else + {KS_CS, "\033|%i%d;%dr"}, +# endif + {KS_CL, "\033|J"}, + {KS_ME, "\033|0m"}, + {KS_MR, "\033|112m"}, + {KS_MD, "\033|63m"}, + {KS_SE, "\033|0m"}, + {KS_SO, "\033|31m"}, + {KS_MS, "\001"}, +# ifdef TERMINFO + {KS_CM, "\033|%i%p1%d;%p2%dH"}, +# else + {KS_CM, "\033|%i%d;%dH"}, +# endif + {K_UP, "\316H"}, + {K_DOWN, "\316P"}, + {K_LEFT, "\316K"}, + {K_RIGHT, "\316M"}, + {K_S_LEFT, "\316s"}, + {K_S_RIGHT, "\316t"}, + {K_S_TAB, "\316\017"}, + {K_F1, "\316;"}, + {K_F2, "\316<"}, + {K_F3, "\316="}, + {K_F4, "\316>"}, + {K_F5, "\316?"}, + {K_F6, "\316@"}, + {K_F7, "\316A"}, + {K_F8, "\316B"}, + {K_F9, "\316C"}, + {K_F10, "\316D"}, + {K_F11, "\316\205"}, /* only when nobioskey */ + {K_F12, "\316\206"}, /* only when nobioskey */ + {K_S_F1, "\316T"}, + {K_S_F2, "\316U"}, + {K_S_F3, "\316V"}, + {K_S_F4, "\316W"}, + {K_S_F5, "\316X"}, + {K_S_F6, "\316Y"}, + {K_S_F7, "\316Z"}, + {K_S_F8, "\316["}, + {K_S_F9, "\316\\"}, + {K_S_F10, "\316]"}, + {K_S_F11, "\316\207"}, /* only when nobioskey */ + {K_S_F12, "\316\210"}, /* only when nobioskey */ + {K_INS, "\316R"}, + {K_DEL, "\316S"}, + {K_HOME, "\316G"}, + {K_END, "\316O"}, + {K_PAGEDOWN, "\316Q"}, + {K_PAGEUP, "\316I"}, +# endif + +# if defined(WIN32) || defined(ALL_BUILTIN_TCAPS) || defined(__EMX__) +/* + * These codes are valid for the Win32 Console . The entries that start with + * ESC | are translated into console calls in win32.c. The function keys + * are also translated in win32.c. + */ + {KS_NAME, "win32"}, + {KS_CE, "\033|K"}, /* clear to end of line */ + {KS_AL, "\033|L"}, /* add new blank line */ +# ifdef TERMINFO + {KS_CAL, "\033|%p1%dL"}, /* add number of new blank lines */ +# else + {KS_CAL, "\033|%dL"}, /* add number of new blank lines */ +# endif + {KS_DL, "\033|M"}, /* delete line */ +# ifdef TERMINFO + {KS_CDL, "\033|%p1%dM"}, /* delete number of lines */ +# else + {KS_CDL, "\033|%dM"}, /* delete number of lines */ +# endif + {KS_CL, "\033|J"}, /* clear screen */ + {KS_CD, "\033|j"}, /* clear to end of display */ + {KS_VI, "\033|v"}, /* cursor invisible */ + {KS_VE, "\033|V"}, /* cursor visible */ + + {KS_ME, "\033|0m"}, /* normal mode */ + {KS_MR, "\033|112m"}, /* reverse mode: black text on lightgray */ + {KS_MD, "\033|63m"}, /* bold mode: white text on cyan */ +#if 1 + {KS_SO, "\033|31m"}, /* standout mode: white text on blue */ + {KS_SE, "\033|0m"}, /* standout mode end */ +#else + {KS_SO, "\033|F"}, /* standout mode: high intensity text */ + {KS_SE, "\033|f"}, /* standout mode end */ +#endif + {KS_CZH, "\033|225m"}, /* italic mode: blue text on yellow */ + {KS_CZR, "\033|0m"}, /* italic mode end */ + {KS_US, "\033|67m"}, /* underscore mode: cyan text on red */ + {KS_UE, "\033|0m"}, /* underscore mode end */ + + {KS_MS, "\001"}, /* save to move cur in reverse mode */ +# ifdef TERMINFO + {KS_CM, "\033|%i%p1%d;%p2%dH"}, /* cursor motion */ +# else + {KS_CM, "\033|%i%d;%dH"}, /* cursor motion */ +# endif + {KS_VB, "\033|B"}, /* visual bell */ + {KS_TI, "\033|S"}, /* put terminal in termcap mode */ + {KS_TE, "\033|E"}, /* out of termcap mode */ + {KS_CS, "\033|%i%d;%dr"}, /* scroll region */ + + {K_UP, "\316H"}, + {K_DOWN, "\316P"}, + {K_LEFT, "\316K"}, + {K_RIGHT, "\316M"}, + {K_S_UP, "\316\304"}, + {K_S_DOWN, "\316\317"}, + {K_S_LEFT, "\316\311"}, + {K_S_RIGHT, "\316\313"}, + {K_S_TAB, "\316\017"}, + {K_F1, "\316;"}, + {K_F2, "\316<"}, + {K_F3, "\316="}, + {K_F4, "\316>"}, + {K_F5, "\316?"}, + {K_F6, "\316@"}, + {K_F7, "\316A"}, + {K_F8, "\316B"}, + {K_F9, "\316C"}, + {K_F10, "\316D"}, + {K_F11, "\316\205"}, + {K_F12, "\316\206"}, + {K_S_F1, "\316T"}, + {K_S_F2, "\316U"}, + {K_S_F3, "\316V"}, + {K_S_F4, "\316W"}, + {K_S_F5, "\316X"}, + {K_S_F6, "\316Y"}, + {K_S_F7, "\316Z"}, + {K_S_F8, "\316["}, + {K_S_F9, "\316\\"}, + {K_S_F10, "\316]"}, + {K_S_F11, "\316\207"}, + {K_S_F12, "\316\210"}, + {K_INS, "\316R"}, + {K_DEL, "\316S"}, + {K_HOME, "\316G"}, + {K_END, "\316O"}, + {K_PAGEDOWN, "\316Q"}, + {K_PAGEUP, "\316I"}, +# endif + +# if defined(ALL_BUILTIN_TCAPS) || defined(MINT) +/* + * Ordinary vt52 + */ + {KS_NAME, "vt52"}, + {KS_CE, "\033K"}, + {KS_CD, "\033J"}, + {KS_CM, "\033Y%+ %+ "}, +# ifdef MINT + {KS_AL, "\033L"}, + {KS_DL, "\033M"}, + {KS_CL, "\033E"}, + {KS_SR, "\033I"}, + {KS_VE, "\033e"}, + {KS_VI, "\033f"}, + {KS_SO, "\033p"}, + {KS_SE, "\033q"}, + {K_UP, "\033A"}, + {K_DOWN, "\033B"}, + {K_LEFT, "\033D"}, + {K_RIGHT, "\033C"}, + {K_S_UP, "\033a"}, + {K_S_DOWN, "\033b"}, + {K_S_LEFT, "\033d"}, + {K_S_RIGHT, "\033c"}, + {K_F1, "\033P"}, + {K_F2, "\033Q"}, + {K_F3, "\033R"}, + {K_F4, "\033S"}, + {K_F5, "\033T"}, + {K_F6, "\033U"}, + {K_F7, "\033V"}, + {K_F8, "\033W"}, + {K_F9, "\033X"}, + {K_F10, "\033Y"}, + {K_S_F1, "\033p"}, + {K_S_F2, "\033q"}, + {K_S_F3, "\033r"}, + {K_S_F4, "\033s"}, + {K_S_F5, "\033t"}, + {K_S_F6, "\033u"}, + {K_S_F7, "\033v"}, + {K_S_F8, "\033w"}, + {K_S_F9, "\033x"}, + {K_S_F10, "\033y"}, + {K_INS, "\033I"}, + {K_HOME, "\033E"}, + {K_PAGEDOWN, "\033b"}, + {K_PAGEUP, "\033a"}, +# else + {KS_AL, "\033T"}, + {KS_DL, "\033U"}, + {KS_CL, "\033H\033J"}, + {KS_ME, "\033SO"}, + {KS_MR, "\033S2"}, + {KS_MS, "\001"}, +# endif +# endif + +# if defined(UNIX) || defined(ALL_BUILTIN_TCAPS) || defined(SOME_BUILTIN_TCAPS) || defined(__EMX__) +/* + * The xterm termcap is missing F14 and F15, because they send the same + * codes as the undo and help key, although they don't work on all keyboards. + */ + {KS_NAME, "xterm"}, + {KS_CE, "\033[K"}, + {KS_AL, "\033[L"}, +# ifdef TERMINFO + {KS_CAL, "\033[%p1%dL"}, +# else + {KS_CAL, "\033[%dL"}, +# endif + {KS_DL, "\033[M"}, +# ifdef TERMINFO + {KS_CDL, "\033[%p1%dM"}, +# else + {KS_CDL, "\033[%dM"}, +# endif +# ifdef TERMINFO + {KS_CS, "\033[%i%p1%d;%p2%dr"}, +# else + {KS_CS, "\033[%i%d;%dr"}, +# endif + {KS_CL, "\033[H\033[2J"}, + {KS_CD, "\033[J"}, + {KS_ME, "\033[m"}, + {KS_MR, "\033[7m"}, + {KS_MD, "\033[1m"}, + {KS_UE, "\033[m"}, + {KS_US, "\033[4m"}, + {KS_MS, "\001"}, +# ifdef TERMINFO + {KS_CM, "\033[%i%p1%d;%p2%dH"}, +# else + {KS_CM, "\033[%i%d;%dH"}, +# endif + {KS_SR, "\033M"}, +# ifdef TERMINFO + {KS_CRI, "\033[%p1%dC"}, +# else + {KS_CRI, "\033[%dC"}, +# endif + {KS_KS, "\033[?1h\033="}, + {KS_KE, "\033[?1l\033>"}, +# ifdef SAVE_XTERM_SCREEN + {KS_TI, "\0337\033[?47h"}, + {KS_TE, "\033[2J\033[?47l\0338"}, +# endif + {K_UP, "\033OA"}, + {K_DOWN, "\033OB"}, + {K_LEFT, "\033OD"}, + {K_RIGHT, "\033OC"}, + {K_S_UP, "\033Ox"}, + {K_S_DOWN, "\033Or"}, + {K_S_LEFT, "\033Ot"}, + {K_S_RIGHT, "\033Ov"}, + {K_F1, "\033[11~"}, + {K_F2, "\033[12~"}, + {K_F3, "\033[13~"}, + {K_F4, "\033[14~"}, + {K_F5, "\033[15~"}, + {K_F6, "\033[17~"}, + {K_F7, "\033[18~"}, + {K_F8, "\033[19~"}, + {K_F9, "\033[20~"}, + {K_F10, "\033[21~"}, + {K_F11, "\033[23~"}, + {K_F12, "\033[24~"}, + {K_HELP, "\033[28~"}, + {K_UNDO, "\033[26~"}, + {K_INS, "\033[2~"}, + {K_HOME, "\033[7~"}, /* also seen: "\033[1~" */ + {K_END, "\033[8~"}, /* also seen: "\033[4~" */ + {K_PAGEUP, "\033[5~"}, + {K_PAGEDOWN, "\033[6~"}, + /* {K_DEL, "\033[3~"}, not used */ + + {BT_EXTRA_KEYS, ""}, + {TERMCAP2KEY('k', '0'), "\033[10~"}, /* F0 */ + {TERMCAP2KEY('F', '3'), "\033[25~"}, /* F13 */ + {TERMCAP2KEY('F', '6'), "\033[29~"}, /* F16 */ + {TERMCAP2KEY('F', '7'), "\033[31~"}, /* F17 */ + {TERMCAP2KEY('F', '8'), "\033[32~"}, /* F18 */ + {TERMCAP2KEY('F', '9'), "\033[33~"}, /* F19 */ + {TERMCAP2KEY('F', 'A'), "\033[34~"}, /* F20 */ +# endif + +# if defined(UNIX) || defined(ALL_BUILTIN_TCAPS) +/* + * iris-ansi for Silicon Graphics machines. + */ + {KS_NAME, "iris-ansi"}, + {KS_CE, "\033[K"}, + {KS_CD, "\033[J"}, + {KS_AL, "\033[L"}, +# ifdef TERMINFO + {KS_CAL, "\033[%p1%dL"}, +# else + {KS_CAL, "\033[%dL"}, +# endif + {KS_DL, "\033[M"}, +# ifdef TERMINFO + {KS_CDL, "\033[%p1%dM"}, +# else + {KS_CDL, "\033[%dM"}, +# endif +/* + * This "cs" is not working correctly. What is the right one? + */ +#if 0 +# ifdef TERMINFO + {KS_CS, "\033[%i%p1%d;%p2%dr"}, +# else + {KS_CS, "\033[%i%d;%dr"}, +# endif +#endif + {KS_CL, "\033[H\033[2J"}, + {KS_VE, "\033[9/y\033[12/y\033[=6l"}, + {KS_VS, "\033[10/y\033[=1h\033[=2l\033[=6h"}, + {KS_SE, "\033[m"}, + {KS_SO, "\033[1;7m"}, + {KS_ME, "\033[m"}, + {KS_MR, "\033[7m"}, + {KS_MD, "\033[1m"}, + {KS_UE, "\033[m"}, + {KS_US, "\033[4m"}, + {KS_MS, "\001"}, /* does this really work? */ +# ifdef TERMINFO + {KS_CM, "\033[%i%p1%d;%p2%dH"}, +# else + {KS_CM, "\033[%i%d;%dH"}, +# endif + {KS_SR, "\033M"}, +# ifdef TERMINFO + {KS_CRI, "\033[%p1%dC"}, +# else + {KS_CRI, "\033[%dC"}, +# endif + {K_UP, "\033[A"}, + {K_DOWN, "\033[B"}, + {K_LEFT, "\033[D"}, + {K_RIGHT, "\033[C"}, + {K_S_UP, "\033[161q"}, + {K_S_DOWN, "\033[164q"}, + {K_S_LEFT, "\033[158q"}, + {K_S_RIGHT, "\033[167q"}, + {K_F1, "\033[001q"}, + {K_F2, "\033[002q"}, + {K_F3, "\033[003q"}, + {K_F4, "\033[004q"}, + {K_F5, "\033[005q"}, + {K_F6, "\033[006q"}, + {K_F7, "\033[007q"}, + {K_F8, "\033[008q"}, + {K_F9, "\033[009q"}, + {K_F10, "\033[010q"}, + {K_F11, "\033[011q"}, + {K_F12, "\033[012q"}, + {K_S_F1, "\033[013q"}, + {K_S_F2, "\033[014q"}, + {K_S_F3, "\033[015q"}, + {K_S_F4, "\033[016q"}, + {K_S_F5, "\033[017q"}, + {K_S_F6, "\033[018q"}, + {K_S_F7, "\033[019q"}, + {K_S_F8, "\033[020q"}, + {K_S_F9, "\033[021q"}, + {K_S_F10, "\033[022q"}, + {K_S_F11, "\033[023q"}, + {K_S_F12, "\033[024q"}, + {K_INS, "\033[139q"}, + {K_HOME, "\033[H"}, + {K_END, "\033[146q"}, + {K_PAGEUP, "\033[150q"}, + {K_PAGEDOWN, "\033[154q"}, +# endif + +# if defined(DEBUG) || defined(ALL_BUILTIN_TCAPS) +/* + * for debugging + */ + {KS_NAME, "debug"}, + {KS_CE, "[CE]"}, + {KS_CD, "[CD]"}, + {KS_AL, "[AL]"}, +# ifdef TERMINFO + {KS_CAL, "[CAL%p1%d]"}, +# else + {KS_CAL, "[CAL%d]"}, +# endif + {KS_DL, "[DL]"}, +# ifdef TERMINFO + {KS_CDL, "[CDL%p1%d]"}, +# else + {KS_CDL, "[CDL%d]"}, +# endif +# ifdef TERMINFO + {KS_CS, "[%dCS%p1%d]"}, +# else + {KS_CS, "[%dCS%d]"}, +# endif + {KS_CL, "[CL]"}, + {KS_VI, "[VI]"}, + {KS_VE, "[VE]"}, + {KS_VS, "[VS]"}, + {KS_ME, "[ME]"}, + {KS_MR, "[MR]"}, + {KS_MD, "[MD]"}, + {KS_SE, "[SE]"}, + {KS_SO, "[SO]"}, + {KS_UE, "[UE]"}, + {KS_US, "[US]"}, + {KS_MS, "[MS]"}, +# ifdef TERMINFO + {KS_CM, "[%p1%dCM%p2%d]"}, +# else + {KS_CM, "[%dCM%d]"}, +# endif + {KS_SR, "[SR]"}, +# ifdef TERMINFO + {KS_CRI, "[CRI%p1%d]"}, +# else + {KS_CRI, "[CRI%d]"}, +# endif + {KS_VB, "[VB]"}, + {KS_KS, "[KS]"}, + {KS_KE, "[KE]"}, + {KS_TI, "[TI]"}, + {KS_TE, "[TE]"}, + {K_UP, "[KU]"}, + {K_DOWN, "[KD]"}, + {K_LEFT, "[KL]"}, + {K_RIGHT, "[KR]"}, + {K_S_UP, "[S-KU]"}, + {K_S_DOWN, "[S-KD]"}, + {K_S_LEFT, "[S-KL]"}, + {K_S_RIGHT, "[S-KR]"}, + {K_F1, "[F1]"}, + {K_F2, "[F2]"}, + {K_F3, "[F3]"}, + {K_F4, "[F4]"}, + {K_F5, "[F5]"}, + {K_F6, "[F6]"}, + {K_F7, "[F7]"}, + {K_F8, "[F8]"}, + {K_F9, "[F9]"}, + {K_F10, "[F10]"}, + {K_F11, "[F11]"}, + {K_F12, "[F12]"}, + {K_S_F1, "[S-F1]"}, + {K_S_F2, "[S-F2]"}, + {K_S_F3, "[S-F3]"}, + {K_S_F4, "[S-F4]"}, + {K_S_F5, "[S-F5]"}, + {K_S_F6, "[S-F6]"}, + {K_S_F7, "[S-F7]"}, + {K_S_F8, "[S-F8]"}, + {K_S_F9, "[S-F9]"}, + {K_S_F10, "[S-F10]"}, + {K_S_F11, "[S-F11]"}, + {K_S_F12, "[S-F12]"}, + {K_HELP, "[HELP]"}, + {K_UNDO, "[UNDO]"}, + {K_BS, "[BS]"}, + {K_INS, "[INS]"}, + {K_DEL, "[DEL]"}, + {K_HOME, "[HOME]"}, + {K_END, "[END]"}, + {K_PAGEUP, "[PAGEUP]"}, + {K_PAGEDOWN, "[PAGEDOWN]"}, + {K_MOUSE, "[MOUSE]"}, +# endif + +#endif /* NO_BUILTIN_TCAPS */ + +/* + * The most minimal terminal: only clear screen and cursor positioning + * Always included. + */ + {KS_NAME, "dumb"}, + {KS_CL, "\014"}, +#ifdef TERMINFO + {KS_CM, "\033[%i%p1%d;%p2%dH"}, +#else + {KS_CM, "\033[%i%d;%dH"}, +#endif + +/* + * end marker + */ + {KS_NAME, NULL} + +}; /* end of builtin_termcaps */ + +/* + * DEFAULT_TERM is used, when no terminal is specified with -T option or $TERM. + */ +#ifdef AMIGA +# define DEFAULT_TERM (char_u *)"amiga" +#endif /* AMIGA */ + +#ifdef WIN32 +# define DEFAULT_TERM (char_u *)"win32" +#endif /* WIN32 */ + +#ifdef MSDOS +# define DEFAULT_TERM (char_u *)"pcterm" +#endif /* MSDOS */ + +#if defined(UNIX) && !defined(MINT) +# define DEFAULT_TERM (char_u *)"ansi" +#endif /* UNIX */ + +#ifdef MINT +# define DEFAULT_TERM (char_u *)"vt52" +#endif /* MINT */ + +#ifdef __EMX__ +# define DEFAULT_TERM (char_u *)"os2ansi" +#endif /* __EMX__ */ + +#ifdef VMS +# define DEFAULT_TERM (char_u *)"ansi" +#endif /* VMS */ + +/* + * Term_strings contains currently used terminal output strings. + * It is initialized with the default values by parse_builtin_tcap(). + * The values can be changed by setting the option with the same name. + */ +char_u *(term_strings[KS_LAST + 1]); + +static int need_gather = FALSE; /* need to fill termleader[] */ +static char_u termleader[256 + 1]; /* for check_termcode() */ + + static struct builtin_term * +find_builtin_term(term) + char_u *term; +{ + struct builtin_term *p; + + p = builtin_termcaps; + while (p->bt_string != NULL) + { + if (p->bt_entry == KS_NAME) + { +#ifdef UNIX + if (STRCMP(p->bt_string, "iris-ansi") == 0 && is_iris_ansi(term)) + return p; + else if (STRCMP(p->bt_string, "xterm") == 0 && is_xterm(term)) + return p; + else +#endif + if (STRCMP(term, p->bt_string) == 0) + return p; + } + ++p; + } + return p; +} + +/* + * Parsing of the builtin termcap entries. + * Caller should check if 'name' is a valid builtin term. + * The terminal's name is not set, as this is already done in termcapinit(). + */ + static void +parse_builtin_tcap(term) + char_u *term; +{ + struct builtin_term *p; + char_u name[2]; + + p = find_builtin_term(term); + for (++p; p->bt_entry != KS_NAME && p->bt_entry != BT_EXTRA_KEYS; ++p) + { + if (p->bt_entry < 0x100) /* KS_xx entry */ + { + if (term_strings[p->bt_entry] == NULL || + term_strings[p->bt_entry] == empty_option) + term_strings[p->bt_entry] = (char_u *)p->bt_string; + } + else + { + name[0] = KEY2TERMCAP0(p->bt_entry); + name[1] = KEY2TERMCAP1(p->bt_entry); + if (find_termcode(name) == NULL) + add_termcode(name, (char_u *)p->bt_string); + } + } +} + +/* + * Set terminal options for terminal "term". + * Return OK if terminal 'term' was found in a termcap, FAIL otherwise. + * + * While doing this, until ttest(), some options may be NULL, be careful. + */ + int +set_termname(term) + char_u *term; +{ + struct builtin_term *termp; +#ifdef HAVE_TGETENT + int builtin_first = p_tbi; + int try; + int termcap_cleared = FALSE; +#endif + int width = 0, height = 0; + char_u *error_msg = NULL; + char_u *bs_p, *del_p; + + if (is_builtin_term(term)) + { + term += 8; +#ifdef HAVE_TGETENT + builtin_first = 1; +#endif + } + +/* + * If HAVE_TGETENT is not defined, only the builtin termcap is used, otherwise: + * If builtin_first is TRUE: + * 0. try builtin termcap + * 1. try external termcap + * 2. if both fail default to a builtin terminal + * If builtin_first is FALSE: + * 1. try external termcap + * 2. try builtin termcap, if both fail default to a builtin terminal + */ +#ifdef HAVE_TGETENT + for (try = builtin_first ? 0 : 1; try < 3; ++try) + { + /* + * Use external termcap + */ + if (try == 1) + { + char_u *p; + static char_u tstrbuf[TBUFSZ]; + int i; + char_u tbuf[TBUFSZ]; + char_u *tp = tstrbuf; + static char *(key_names[]) = + { + "ku", "kd", "kr", /* "kl" is a special case */ +# ifdef ARCHIE + "su", "sd", /* Termcap code made up! */ +# endif + "#4", "%i", + "k1", "k2", "k3", "k4", "k5", "k6", + "k7", "k8", "k9", "k;", "F1", "F2", + "%1", "&8", "kb", "kI", "kD", "kh", + "@7", "kP", "kN", + NULL + }; + static struct { + int dest; /* index in term_strings[] */ + char *name; /* termcap name for string */ + } string_names[] = + { {KS_CE, "ce"}, {KS_AL, "al"}, {KS_CAL, "AL"}, + {KS_DL, "dl"}, {KS_CDL, "DL"}, {KS_CS, "cs"}, + {KS_CL, "cl"}, {KS_CD, "cd"}, + {KS_VI, "vi"}, {KS_VE, "ve"}, + {KS_VS, "vs"}, {KS_ME, "me"}, {KS_MR, "mr"}, + {KS_MD, "md"}, {KS_SE, "se"}, {KS_SO, "so"}, + {KS_CZH, "ZH"}, {KS_CZR, "ZR"}, {KS_UE, "ue"}, + {KS_US, "us"}, {KS_CM, "cm"}, {KS_SR, "sr"}, + {KS_CRI, "RI"}, {KS_VB, "vb"}, {KS_KS, "ks"}, + {KS_KE, "ke"}, {KS_TI, "ti"}, {KS_TE, "te"}, + {0, NULL} + }; + + /* + * If the external termcap does not have a matching entry, try the + * builtin ones. + */ + if ((error_msg = tgetent_error(tbuf, term)) == NULL) + { + if (!termcap_cleared) + { + clear_termoptions(); /* clear old options */ + termcap_cleared = TRUE; + } + + /* get output strings */ + for (i = 0; string_names[i].name != NULL; ++i) + { + if (term_strings[string_names[i].dest] == NULL || + term_strings[string_names[i].dest] == empty_option) + term_strings[string_names[i].dest] = + TGETSTR(string_names[i].name, &tp); + } + + if ((T_MS == NULL || T_MS == empty_option) && tgetflag("ms")) + T_MS = (char_u *)"yes"; + if ((T_DB == NULL || T_DB == empty_option) && tgetflag("db")) + T_DB = (char_u *)"yes"; + if ((T_DA == NULL || T_DA == empty_option) && tgetflag("da")) + T_DA = (char_u *)"yes"; + + + /* get key codes */ + + for (i = 0; key_names[i] != NULL; ++i) + { + if (find_termcode((char_u *)key_names[i]) == NULL) + add_termcode((char_u *)key_names[i], + TGETSTR(key_names[i], &tp)); + } + + /* if cursor-left == backspace, ignore it (televideo 925) */ + if (find_termcode((char_u *)"kl") == NULL) + { + p = TGETSTR("kl", &tp); + if (p != NULL && *p != Ctrl('H')) + add_termcode((char_u *)"kl", p); + } + + if (height == 0) + height = tgetnum("li"); + if (width == 0) + width = tgetnum("co"); + +# ifndef hpux + BC = (char *)TGETSTR("bc", &tp); + UP = (char *)TGETSTR("up", &tp); + p = TGETSTR("pc", &tp); + if (p) + PC = *p; +# endif /* hpux */ + } + } + else /* try == 0 || try == 2 */ +#endif /* HAVE_TGETENT */ + /* + * Use builtin termcap + */ + { +#ifdef HAVE_TGETENT + /* + * If builtin termcap was already used, there is no need to search + * for the builtin termcap again, quit now. + */ + if (try == 2 && builtin_first && termcap_cleared) + break; +#endif + /* + * search for 'term' in builtin_termcaps[] + */ + termp = find_builtin_term(term); + if (termp->bt_string == NULL) /* did not find it */ + { +#ifdef HAVE_TGETENT + /* + * If try == 0, first try the external termcap. If that is not + * found we'll get back here with try == 2. + * If termcap_cleared is set we used the external termcap, + * don't complain about not finding the term in the builtin + * termcap. + */ + if (try == 0) /* try external one */ + continue; + if (termcap_cleared) /* found in external termcap */ + break; +#endif + + fprintf(stderr, "\r\n"); + if (error_msg != NULL) + { + fprintf(stderr, (char *)error_msg); + fprintf(stderr, "\r\n"); + } + fprintf(stderr, "'%s' not known. Available builtin terminals are:\r\n", term); + for (termp = &(builtin_termcaps[0]); termp->bt_string != NULL; + ++termp) + { + if (termp->bt_entry == KS_NAME) +#ifdef HAVE_TGETENT + fprintf(stderr, " builtin_%s\r\n", termp->bt_string); +#else + fprintf(stderr, " %s\r\n", termp->bt_string); +#endif + } + if (!starting) /* when user typed :set term=xxx, quit here */ + { + screen_start(); /* don't know where cursor is now */ + wait_return(TRUE); + return FAIL; + } + term = DEFAULT_TERM; + fprintf(stderr, "defaulting to '%s'\r\n", term); + screen_start(); /* don't know where cursor is now */ + mch_delay(2000L, TRUE); + set_string_option((char_u *)"term", -1, term, TRUE); + } + flushbuf(); +#ifdef HAVE_TGETENT + if (!termcap_cleared) + { +#endif + clear_termoptions(); /* clear old options */ +#ifdef HAVE_TGETENT + termcap_cleared = TRUE; + } +#endif + parse_builtin_tcap(term); +#ifdef USE_GUI + if (STRCMP(term, "gui") == 0) + { + flushbuf(); + settmode(0); + gui_init(); + if (!gui.in_use) /* failed to start GUI */ + settmode(1); + } +#endif /* USE_GUI */ + } +#ifdef HAVE_TGETENT + } +#endif + +/* + * special: There is no info in the termcap about whether the cursor + * positioning is relative to the start of the screen or to the start of the + * scrolling region. We just guess here. Only msdos pcterm is known to do it + * relative. + */ + if (STRCMP(term, "pcterm") == 0) + T_CSC = (char_u *)"yes"; + else + T_CSC = empty_option; + +#ifdef UNIX +/* + * Any "stty" settings override the default for t_kb from the termcap. + * This is in unix.c, because it depends a lot on the version of unix that is + * being used. + * Don't do this when the GUI is active, it uses "t_kb" and "t_kD" directly. + */ +#ifdef USE_GUI + if (!gui.in_use) +#endif + get_stty(); +#endif + +/* + * If the termcap has no entry for 'bs' and/or 'del' and the ioctl() also + * didn't work, use the default CTRL-H + * The default for t_kD is DEL, unless t_kb is DEL. + * The strsave'd strings are probably lost forever, well it's only two bytes. + * Don't do this when the GUI is active, it uses "t_kb" and "t_kD" directly. + */ +#ifdef USE_GUI + if (!gui.in_use) +#endif + { + bs_p = find_termcode((char_u *)"kb"); + del_p = find_termcode((char_u *)"kD"); + if (bs_p == NULL || *bs_p == NUL) + add_termcode((char_u *)"kb", (bs_p = (char_u *)"\010")); + if ((del_p == NULL || *del_p == NUL) && + (bs_p == NULL || *bs_p != '\177')) + add_termcode((char_u *)"kD", (char_u *)"\177"); + } + +#ifdef USE_MOUSE + /* + * recognize mouse events in the input stream for xterm, msdos and win32 + */ + { + char_u name[2]; + + name[0] = KS_MOUSE; + name[1] = K_FILLER; +# ifdef UNIX + if (is_xterm(term)) + add_termcode(name, (char_u *)"\033[M"); +# else + add_termcode(name, (char_u *)"\233M"); +# endif + } +#endif + +#if defined(AMIGA) || defined(MSDOS) || defined(WIN32) || defined(OS2) + /* DEFAULT_TERM indicates that it is the machine console. */ + if (STRCMP(term, DEFAULT_TERM)) + term_console = FALSE; + else + { + term_console = TRUE; +# ifdef AMIGA + win_resize_on(); /* enable window resizing reports */ +# endif + } +#endif + +#ifdef UNIX +/* + * 'ttyfast' is default on for xterm, iris-ansi and a few others. + */ + if (is_fastterm(term)) + p_tf = TRUE; +#endif +#if defined(AMIGA) || defined(MSDOS) || defined(WIN32) || defined(OS2) +/* + * 'ttyfast' is default on Amiga, MSDOS, Win32, and OS/2 consoles + */ + if (term_console) + p_tf = TRUE; +#endif + + ttest(TRUE); /* make sure we have a valid set of terminal codes */ + set_term_defaults(); /* use current values as defaults */ + + /* + * Initialize the terminal with the appropriate termcap codes. + * Set the mouse and window title if possible. + * Don't do this when starting, need to parse the .vimrc first, because it + * may redefine t_TI etc. + */ + if (!starting) + { + starttermcap(); /* may change terminal mode */ +#ifdef USE_MOUSE + setmouse(); /* may start using the mouse */ +#endif + maketitle(); /* may display window title */ + } + + /* display initial screen after ttest() checking. jw. */ + if (width <= 0 || height <= 0) + { + /* termcap failed to report size */ + /* set defaults, in case mch_get_winsize also fails */ + width = 80; +#if defined MSDOS || defined WIN32 + height = 25; /* console is often 25 lines */ +#else + height = 24; /* most terminals are 24 lines */ +#endif + } + set_winsize(width, height, FALSE); /* may change Rows */ + if (!starting) + { + if (scroll_region) + scroll_region_reset(); /* In case Rows changed */ + check_map_keycodes(); /* check mappings for terminal codes used */ + } + + return OK; +} + +#ifdef HAVE_TGETENT +/* + * Call tgetent() + * Return error message if it fails, NULL if it's OK. + */ + static char_u * +tgetent_error(tbuf, term) + char_u *tbuf; + char_u *term; +{ + int i; + + i = TGETENT(tbuf, term); + if (i == -1) + return (char_u *)"Cannot open termcap file"; + if (i == 0) +#ifdef TERMINFO + return (char_u *)"Terminal entry not found in terminfo"; +#else + return (char_u *)"Terminal entry not found in termcap"; +#endif + return NULL; +} +#endif /* HAVE_TGETENT */ + +#if defined(HAVE_TGETENT) && (defined(UNIX) || defined(__EMX__)) +/* + * Get Columns and Rows from the termcap. Used after a window signal if the + * ioctl() fails. It doesn't make sense to call tgetent each time if the "co" + * and "li" entries never change. But on some systems this works. + * Errors while getting the entries are ignored. + */ + void +getlinecol() +{ + char_u tbuf[TBUFSZ]; + + if (term_strings[KS_NAME] != NULL && TGETENT(tbuf, term_strings[KS_NAME]) > 0) + { + if (Columns == 0) + Columns = tgetnum("co"); + if (Rows == 0) + Rows = tgetnum("li"); + } +} +#endif /* defined(HAVE_TGETENT) && defined(UNIX) */ + +/* + * Get a string entry from the termcap and add it to the list of termcodes. + * Used for special keys. + * Give an error message for failure when not sourcing. + * If force given, replace an existing entry. + * Return FAIL if the entry was not found, OK if the entry was added. + */ + int +add_termcap_entry(name, force) + char_u *name; + int force; +{ + char_u *term; + int key; + struct builtin_term *termp; +#ifdef HAVE_TGETENT + char_u *string; + int i; + int builtin_first; + char_u tbuf[TBUFSZ]; + char_u tstrbuf[TBUFSZ]; + char_u *tp = tstrbuf; + char_u *error_msg = NULL; +#endif + +/* + * If the GUI is running or will start in a moment, we only support the keys + * that the GUI can produce. + */ +#ifdef USE_GUI + if (gui.in_use || gui.starting) + return gui_mch_haskey(name); +#endif + + if (!force && find_termcode(name) != NULL) /* it's already there */ + return OK; + + term = term_strings[KS_NAME]; + if (term == NULL) /* just in case */ + return FAIL; + + if (is_builtin_term(term)) /* name starts with "builtin_" */ + { + term += 8; +#ifdef HAVE_TGETENT + builtin_first = TRUE; +#endif + } +#ifdef HAVE_TGETENT + else + builtin_first = p_tbi; +#endif + +#ifdef HAVE_TGETENT +/* + * We can get the entry from the builtin termcap and from the external one. + * If 'ttybuiltin' is on or the terminal name starts with "builtin_", try + * builtin termcap first. + * If 'ttybuiltin' is off, try external termcap first. + */ + for (i = 0; i < 2; ++i) + { + if (!builtin_first == i) +#endif + /* + * Search in builtin termcap + */ + { + termp = find_builtin_term(term); + if (termp->bt_string != NULL) /* found it */ + { + key = TERMCAP2KEY(name[0], name[1]); + while (termp->bt_entry != KS_NAME) + { + if (termp->bt_entry == key) + { + add_termcode(name, (char_u *)termp->bt_string); + return OK; + } + ++termp; + } + } + } +#ifdef HAVE_TGETENT + else + /* + * Search in external termcap + */ + { + error_msg = tgetent_error(tbuf, term); + if (error_msg == NULL) + { + string = TGETSTR((char *)name, &tp); + if (string != NULL && *string != NUL) + { + add_termcode(name, string); + return OK; + } + } + } + } +#endif + + if (sourcing_name == NULL) + { +#ifdef HAVE_TGETENT + if (error_msg != NULL) + EMSG(error_msg); + else +#endif + EMSG2("No \"%s\" entry in termcap", name); + } + return FAIL; +} + + static int +is_builtin_term(name) + char_u *name; +{ + return (STRNCMP(name, "builtin_", (size_t)8) == 0); +} + +static char_u *tltoa __PARMS((unsigned long)); + + static char_u * +tltoa(i) + unsigned long i; +{ + static char_u buf[16]; + char_u *p; + + p = buf + 15; + *p = '\0'; + do + { + --p; + *p = (char_u) (i % 10 + '0'); + i /= 10; + } + while (i > 0 && p > buf); + return p; +} + +#ifndef HAVE_TGETENT + +/* + * minimal tgoto() implementation. + * no padding and we only parse for %i %d and %+char + */ +char *tgoto __ARGS((char *, int, int)); + + char * +tgoto(cm, x, y) + char *cm; + int x, y; +{ + static char buf[30]; + char *p, *s, *e; + + if (!cm) + return "OOPS"; + e = buf + 29; + for (s = buf; s < e && *cm; cm++) + { + if (*cm != '%') + { + *s++ = *cm; + continue; + } + switch (*++cm) + { + case 'd': + p = (char *)tltoa((unsigned long)y); + y = x; + while (*p) + *s++ = *p++; + break; + case 'i': + x++; + y++; + break; + case '+': + *s++ = (char)(*++cm + y); + y = x; + break; + case '%': + *s++ = *cm; + break; + default: + return "OOPS"; + } + } + *s = '\0'; + return buf; +} + +#endif /* HAVE_TGETENT */ + +/* + * Set the terminal name to "term" and initialize the terminal options. + * If "term" is NULL or empty, get the terminal name from the environment. + * If that fails, use the default terminal name. + */ + void +termcapinit(term) + char_u *term; +{ + if (!term || !*term) + term = vim_getenv((char_u *)"TERM"); + if (!term || !*term) + term = DEFAULT_TERM; + set_string_option((char_u *)"term", -1, term, TRUE); + /* + * Avoid using "term" here, because the next vim_getenv() may overwrite it. + */ + set_termname(term_strings[KS_NAME] != NULL ? term_strings[KS_NAME] : term); +} + +/* + * the number of calls to mch_write is reduced by using the buffer "outbuf" + */ +#undef BSIZE /* hpux has BSIZE in sys/option.h */ +#define BSIZE 2048 +static char_u outbuf[BSIZE]; +static int bpos = 0; /* number of chars in outbuf */ + +/* + * flushbuf(): flush the output buffer + */ + void +flushbuf() +{ + if (bpos != 0) + { + mch_write(outbuf, bpos); + bpos = 0; + } +} + +#ifdef USE_GUI +/* + * trash_output_buf(): Throw away the contents of the output buffer + */ + void +trash_output_buf() +{ + bpos = 0; +} +#endif + +/* + * outchar(c): put a character into the output buffer. + * Flush it if it becomes full. + * This should not be used for outputting text on the screen (use functions like + * msg_outstr() and screen_outchar() for that). + */ + void +outchar(c) + unsigned c; +{ +#if defined(UNIX) || defined(VMS) || defined(AMIGA) + if (c == '\n') /* turn LF into CR-LF (CRMOD doesn't seem to do this) */ + outchar('\r'); +#endif + + outbuf[bpos++] = c; + + /* For testing we flush each time. */ + if (bpos >= BSIZE || p_wd) + flushbuf(); +} + +static void outchar_nf __ARGS((unsigned)); + +/* + * outchar_nf(c): like outchar(), but don't flush when p_wd is set + */ + static void +outchar_nf(c) + unsigned c; +{ +#if defined(UNIX) || defined(VMS) || defined(AMIGA) + if (c == '\n') /* turn LF into CR-LF (CRMOD doesn't seem to do this) */ + outchar_nf('\r'); +#endif + + outbuf[bpos++] = c; + + /* For testing we flush each time. */ + if (bpos >= BSIZE) + flushbuf(); +} + +/* + * a never-padding outstr. + * use this whenever you don't want to run the string through tputs. + * tputs above is harmless, but tputs from the termcap library + * is likely to strip off leading digits, that it mistakes for padding + * information. (jw) + * This should only be used for writing terminal codes, not for outputting + * normal text (use functions like msg_outstr() and screen_outchar() for that). + */ + void +outstrn(s) + char_u *s; +{ + if (bpos > BSIZE - 20) /* avoid terminal strings being split up */ + flushbuf(); + while (*s) + outchar_nf(*s++); + + /* For testing we write one string at a time. */ + if (p_wd) + flushbuf(); +} + +/* + * outstr(s): put a string character at a time into the output buffer. + * If HAVE_TGETENT is defined use the termcap parser. (jw) + * This should only be used for writing terminal codes, not for outputting + * normal text (use functions like msg_outstr() and screen_outchar() for that). + */ + void +outstr(s) + register char_u *s; +{ + if (bpos > BSIZE - 20) /* avoid terminal strings being split up */ + flushbuf(); + if (s) +#ifdef HAVE_TGETENT + tputs((char *)s, 1, TPUTSFUNCAST outchar_nf); +#else + while (*s) + outchar_nf(*s++); +#endif + + /* For testing we write one string at a time. */ + if (p_wd) + flushbuf(); +} + +/* + * cursor positioning using termcap parser. (jw) + */ + void +windgoto(row, col) + int row; + int col; +{ + if (col != screen_cur_col || row != screen_cur_row) + { + /* + * When positioning on the same row, column 0, use CR, it's just one + * character. Don't do this when the cursor has moved past the end of + * the line, the cursor position is undefined then. + */ + if (row == screen_cur_row && col == 0 && screen_cur_col < Columns) + { + outchar('\r'); + screen_cur_col = 0; + } + else + { + OUTSTR(tgoto((char *)T_CM, col, row)); + screen_cur_col = col; + screen_cur_row = row; + } + } +} + +/* + * Set cursor to current position. + */ + + void +setcursor() +{ + if (!RedrawingDisabled) + windgoto(curwin->w_winpos + curwin->w_row, +#ifdef RIGHTLEFT + curwin->w_p_rl ? (int)Columns - 1 - curwin->w_col : +#endif + curwin->w_col); +} + +/* + * Make sure we have a valid set or terminal options. + * Replace all entries that are NULL by empty_option + */ + void +ttest(pairs) + int pairs; +{ + char *t = NULL; + + check_options(); /* make sure no options are NULL */ + + /* hard requirements */ + if (*T_CL == NUL) /* erase display */ + t = "cl"; + if (*T_CM == NUL) /* cursor motion */ + t = "cm"; + + if (t) + EMSG2("terminal capability %s required", t); + +/* + * if "cs" defined, use a scroll region, it's faster. + */ + if (*T_CS != NUL) + scroll_region = TRUE; + else + scroll_region = FALSE; + + if (pairs) + { + /* optional pairs */ + /* TP goes to normal mode for TI (invert) and TB (bold) */ + if (*T_ME == NUL) + T_ME = T_MR = T_MD = empty_option; + if (*T_SO == NUL || *T_SE == NUL) + T_SO = T_SE = empty_option; + if (*T_US == NUL || *T_UE == NUL) + T_US = T_UE = empty_option; + if (*T_CZH == NUL || *T_CZR == NUL) + T_CZH = T_CZR = empty_option; + /* T_VE is needed even though T_VI is not defined */ + if (*T_VE == NUL) + T_VI = empty_option; + /* if 'mr' or 'me' is not defined use 'so' and 'se' */ + if (*T_ME == NUL) + { + T_ME = T_SE; + T_MR = T_SO; + T_MD = T_SO; + } + /* if 'so' or 'se' is not defined use 'mr' and 'me' */ + if (*T_SO == NUL) + { + T_SE = T_ME; + if (*T_MR == NUL) + T_SO = T_MD; + else + T_SO = T_MR; + } + /* if 'ZH' or 'ZR' is not defined use 'mr' and 'me' */ + if (*T_CZH == NUL) + { + T_CZR = T_ME; + if (*T_MR == NUL) + T_CZH = T_MD; + else + T_CZH = T_MR; + } + } + need_gather = TRUE; +} + +/* + * Represent the given long_u as individual bytes, with the most significant + * byte first, and store them in dst. + */ + void +add_long_to_buf(val, dst) + long_u val; + char_u *dst; +{ + int i; + int shift; + + for (i = 1; i <= sizeof(long_u); i++) + { + shift = 8 * (sizeof(long_u) - i); + dst[i - 1] = (char_u) ((val >> shift) & 0xff); + } +} + +/* + * Interpret the next string of bytes in buf as a long integer, with the most + * significant byte first. Note that it is assumed that buf has been through + * inchar(), so that NUL and K_SPECIAL will be represented as three bytes each. + * Puts result in val, and returns the number of bytes read from buf + * (between sizeof(long_u) and 2 * sizeof(long_u)), or -1 if not enough bytes + * were present. + */ + int +get_long_from_buf(buf, val) + char_u *buf; + long_u *val; +{ + int len; + char_u bytes[sizeof(long_u)]; + int i; + int shift; + + *val = 0; + len = get_bytes_from_buf(buf, bytes, (int)sizeof(long_u)); + if (len != -1) + { + for (i = 0; i < sizeof(long_u); i++) + { + shift = 8 * (sizeof(long_u) - 1 - i); + *val += (long_u)bytes[i] << shift; + } + } + return len; +} + +/* + * Read the next num_bytes bytes from buf, and store them in bytes. Assume + * that buf has been through inchar(). Returns the actual number of bytes used + * from buf (between num_bytes and num_bytes*2), or -1 if not enough bytes were + * available. + */ + static int +get_bytes_from_buf(buf, bytes, num_bytes) + char_u *buf; + char_u *bytes; + int num_bytes; +{ + int len = 0; + int i; + char_u c; + + for (i = 0; i < num_bytes; i++) + { + if ((c = buf[len++]) == NUL) + return -1; + if (c == K_SPECIAL) + { + if (buf[len] == NUL || buf[len + 1] == NUL) /* cannot happen? */ + return -1; + if (buf[len++] == KS_ZERO) + c = NUL; + ++len; /* skip K_FILLER */ + /* else it should be KS_SPECIAL, and c already equals K_SPECIAL */ + } + bytes[i] = c; + } + return len; +} + +/* + * outnum - output a (big) number fast + */ + void +outnum(n) + register long n; +{ + OUTSTRN(tltoa((unsigned long)n)); +} + + void +check_winsize() +{ + static int old_Rows = 0; + + if (Columns < MIN_COLUMNS) + Columns = MIN_COLUMNS; + if (Rows < min_rows()) /* need room for one window and command line */ + Rows = min_rows(); + + if (old_Rows != Rows) + { + old_Rows = Rows; + screen_new_rows(); /* may need to update window sizes */ + } +} + +/* + * set window size + * If 'mustset' is TRUE, we must set Rows and Columns, do not get real + * window size (this is used for the :win command). + * If 'mustset' is FALSE, we may try to get the real window size and if + * it fails use 'width' and 'height'. + */ + void +set_winsize(width, height, mustset) + int width, height; + int mustset; +{ + register int tmp; + + if (width < 0 || height < 0) /* just checking... */ + return; + + /* postpone the resizing */ + if (State == HITRETURN || State == SETWSIZE) + { + State = SETWSIZE; + return; + } + if (State != ASKMORE && State != EXTERNCMD) + screenclear(); + else + screen_start(); /* don't know where cursor is now */ +#ifdef AMIGA + flushbuf(); /* must do this before mch_get_winsize for some + obscure reason */ +#endif /* AMIGA */ + if (mustset || (mch_get_winsize() == FAIL && height != 0)) + { + Rows = height; + Columns = width; + check_winsize(); /* always check, to get p_scroll right */ + mch_set_winsize(); + } + else + check_winsize(); /* always check, to get p_scroll right */ + if (!starting) + { + comp_Botline_all(); + if (State == ASKMORE || State == EXTERNCMD) + { + screenalloc(FALSE); /* don't redraw, just adjust screen size */ + if (State == ASKMORE) + { + msg_moremsg(FALSE); /* display --more-- message again */ + msg_row = Rows - 1; + } + else + windgoto(msg_row, msg_col); /* put cursor back */ + } + else + { + tmp = RedrawingDisabled; + RedrawingDisabled = FALSE; + updateScreen(CURSUPD); + RedrawingDisabled = tmp; + if (State == CMDLINE) + redrawcmdline(); + else + setcursor(); + } + } + flushbuf(); +} + + void +settmode(raw) + int raw; +{ + static int oldraw = FALSE; + +#ifdef USE_GUI + /* don't set the term where gvim was started in raw mode */ + if (gui.in_use) + return; +#endif + if (full_screen) + { + /* + * When returning after calling a shell we want to really set the + * terminal to raw mode, even though we think it already is, because + * the shell program may have reset the terminal mode. + * When we think the terminal is not-raw, don't try to set it to + * not-raw again, because that causes problems (logout!) on some + * machines. + */ + if (raw || oldraw) + { + flushbuf(); + mch_settmode(raw); /* machine specific function */ +#ifdef USE_MOUSE + if (!raw) + mch_setmouse(FALSE); /* switch mouse off */ + else + setmouse(); /* may switch mouse on */ +#endif + flushbuf(); + oldraw = raw; + } + } +} + + void +starttermcap() +{ + if (full_screen && !termcap_active) + { + outstr(T_TI); /* start termcap mode */ + outstr(T_KS); /* start "keypad transmit" mode */ + flushbuf(); + termcap_active = TRUE; + screen_start(); /* don't know where cursor is now */ + } +} + + void +stoptermcap() +{ + if (full_screen && termcap_active) + { + outstr(T_KE); /* stop "keypad transmit" mode */ + flushbuf(); + termcap_active = FALSE; + cursor_on(); /* just in case it is still off */ + outstr(T_TE); /* stop termcap mode */ + screen_start(); /* don't know where cursor is now */ + } +} + +#ifdef USE_MOUSE +/* + * setmouse() - switch mouse on/off depending on current mode and 'mouse' + */ + void +setmouse() +{ + int checkfor; + +# ifdef USE_GUI + if (gui.in_use) + return; +# endif + if (*p_mouse == NUL) /* be quick when mouse is off */ + return; + + if (VIsual_active) + checkfor = MOUSE_VISUAL; + else if (State == HITRETURN) + checkfor = MOUSE_RETURN; + else if (State & INSERT) + checkfor = MOUSE_INSERT; + else if (State & CMDLINE) + checkfor = MOUSE_COMMAND; + else + checkfor = MOUSE_NORMAL; /* assume normal mode */ + + if (mouse_has(checkfor)) + mch_setmouse(TRUE); + else + mch_setmouse(FALSE); +} + +/* + * Return TRUE if + * - "c" is in 'mouse', or + * - 'a' is in 'mouse' and "c" is in MOUSE_A, or + * - the current buffer is a help file and 'h' is in 'mouse' and we are in a + * normal editing mode (not at hit-return message). + */ + int +mouse_has(c) + int c; +{ + return (vim_strchr(p_mouse, c) != NULL || + (vim_strchr(p_mouse, 'a') != NULL && + vim_strchr((char_u *)MOUSE_A, c) != NULL) || + (c != MOUSE_RETURN && curbuf->b_help && + vim_strchr(p_mouse, MOUSE_HELP))); +} +#endif + +/* + * By outputting the 'cursor very visible' termcap code, for some windowed + * terminals this makes the screen scrolled to the correct position. + * Used when starting Vim or returning from a shell. + */ + void +scroll_start() +{ + if (*T_VS != NUL) + { + outstr(T_VS); + outstr(T_VE); + screen_start(); /* don't know where cursor is now */ + } +} + +/* + * enable cursor, unless in Visual mode or no inversion possible + */ +static int cursor_is_off = FALSE; + + void +cursor_on() +{ + if (full_screen) + { + if (cursor_is_off && (!VIsual_active || highlight == NULL)) + { + outstr(T_VE); + cursor_is_off = FALSE; + } + } +} + + void +cursor_off() +{ + if (full_screen) + { + if (!cursor_is_off) + outstr(T_VI); /* disable cursor */ + cursor_is_off = TRUE; + } +} + +/* + * Set scrolling region for window 'wp'. + * The region starts 'off' lines from the start of the window. + */ + void +scroll_region_set(wp, off) + WIN *wp; + int off; +{ + OUTSTR(tgoto((char *)T_CS, wp->w_winpos + wp->w_height - 1, + wp->w_winpos + off)); + screen_start(); /* don't know where cursor is now */ +} + +/* + * Reset scrolling region to the whole screen. + */ + void +scroll_region_reset() +{ + OUTSTR(tgoto((char *)T_CS, (int)Rows - 1, 0)); + screen_start(); /* don't know where cursor is now */ +} + + +/* + * List of terminal codes that are currently recognized. + */ + +struct termcode +{ + char_u name[2]; /* termcap name of entry */ + char_u *code; /* terminal code (in allocated memory) */ + int len; /* STRLEN(code) */ +} *termcodes = NULL; + +static int tc_max_len = 0; /* number of entries that termcodes[] can hold */ +static int tc_len = 0; /* current number of entries in termcodes[] */ + + void +clear_termcodes() +{ + while (tc_len > 0) + vim_free(termcodes[--tc_len].code); + vim_free(termcodes); + termcodes = NULL; + tc_max_len = 0; + +#ifdef HAVE_TGETENT + BC = (char *)empty_option; + UP = (char *)empty_option; + PC = ' '; /* set pad character to space */ + ospeed = 0; +#endif + + need_gather = TRUE; /* need to fill termleader[] */ +} + +/* + * Add a new entry to the list of terminal codes. + * The list is kept alphabetical for ":set termcap" + */ + void +add_termcode(name, string) + char_u *name; + char_u *string; +{ + struct termcode *new_tc; + int i, j; + char_u *s; + + if (string == NULL || *string == NUL) + { + del_termcode(name); + return; + } + + s = strsave(string); + if (s == NULL) + return; + + need_gather = TRUE; /* need to fill termleader[] */ + + /* + * need to make space for more entries + */ + if (tc_len == tc_max_len) + { + tc_max_len += 20; + new_tc = (struct termcode *)alloc( + (unsigned)(tc_max_len * sizeof(struct termcode))); + if (new_tc == NULL) + { + tc_max_len -= 20; + return; + } + for (i = 0; i < tc_len; ++i) + new_tc[i] = termcodes[i]; + vim_free(termcodes); + termcodes = new_tc; + } + + /* + * Look for existing entry with the same name, it is replaced. + * Look for an existing entry that is alphabetical higher, the new entry + * is inserted in front of it. + */ + for (i = 0; i < tc_len; ++i) + { + if (termcodes[i].name[0] < name[0]) + continue; + if (termcodes[i].name[0] == name[0]) + { + if (termcodes[i].name[1] < name[1]) + continue; + /* + * Exact match: Replace old code. + */ + if (termcodes[i].name[1] == name[1]) + { + vim_free(termcodes[i].code); + --tc_len; + break; + } + } + /* + * Found alphabetical larger entry, move rest to insert new entry + */ + for (j = tc_len; j > i; --j) + termcodes[j] = termcodes[j - 1]; + break; + } + + termcodes[i].name[0] = name[0]; + termcodes[i].name[1] = name[1]; + termcodes[i].code = s; + termcodes[i].len = STRLEN(s); + ++tc_len; +} + + char_u * +find_termcode(name) + char_u *name; +{ + int i; + + for (i = 0; i < tc_len; ++i) + if (termcodes[i].name[0] == name[0] && termcodes[i].name[1] == name[1]) + return termcodes[i].code; + return NULL; +} + + char_u * +get_termcode(i) + int i; +{ + if (i >= tc_len) + return NULL; + return &termcodes[i].name[0]; +} + + void +del_termcode(name) + char_u *name; +{ + int i; + + if (termcodes == NULL) /* nothing there yet */ + return; + + need_gather = TRUE; /* need to fill termleader[] */ + + for (i = 0; i < tc_len; ++i) + if (termcodes[i].name[0] == name[0] && termcodes[i].name[1] == name[1]) + { + vim_free(termcodes[i].code); + --tc_len; + while (i < tc_len) + { + termcodes[i] = termcodes[i + 1]; + ++i; + } + return; + } + /* not found. Give error message? */ +} + +/* + * Check if typebuf[] contains a terminal key code. + * Check from typebuf[typeoff] to typebuf[typeoff + max_offset]. + * Return 0 for no match, -1 for partial match, > 0 for full match. + * With a match, the match is removed, the replacement code is inserted in + * typebuf[] and the number of characters in typebuf[] is returned. + */ + int +check_termcode(max_offset) + int max_offset; +{ + register char_u *tp; + register char_u *p; + int slen = 0; /* init for GCC */ + int len; + int offset; + char_u key_name[2]; + int new_slen; + int extra; + char_u string[MAX_KEY_CODE_LEN + 1]; + int i; +#ifdef USE_GUI + long_u val; +#endif +#ifdef USE_MOUSE + char_u bytes[3]; + int num_bytes; + int mouse_code; + int modifiers; + int is_click, is_drag; + int current_button; + static int held_button = MOUSE_RELEASE; + static int orig_num_clicks = 1; + static int orig_mouse_code = 0x0; +# if defined(UNIX) && defined(HAVE_GETTIMEOFDAY) && defined(HAVE_SYS_TIME_H) + static int orig_mouse_col = 0; + static int orig_mouse_row = 0; + static linenr_t orig_topline = 0; + static struct timeval orig_mouse_time = {0, 0}; + /* time of previous mouse click */ + struct timeval mouse_time; /* time of current mouse click */ + long timediff; /* elapsed time in msec */ +# endif +#endif + + /* + * Speed up the checks for terminal codes by gathering all first bytes + * used in termleader[]. Often this is just a single . + */ + if (need_gather) + gather_termleader(); + + /* + * Check at several positions in typebuf[], to catch something like + * "x" that can be mapped. Stop at max_offset, because characters + * after that cannot be used for mapping, and with @r commands typebuf[] + * can become very long. + * This is used often, KEEP IT FAST! + */ + for (offset = 0; offset < typelen && offset < max_offset; ++offset) + { + tp = typebuf + typeoff + offset; + + /* + * Don't check characters after K_SPECIAL, those are already + * translated terminal chars (avoid translating ~@^Hx). + */ + if (*tp == K_SPECIAL) + { + offset += 2; /* there are always 2 extra characters */ + continue; + } + + /* + * Skip this position if the character does not appear as the first + * character in term_strings. This speeds up a lot, since most + * termcodes start with the same character (ESC or CSI). + */ + i = *tp; + for (p = termleader; *p && *p != i; ++p) + ; + if (*p == NUL) + continue; + + /* + * Skip this position if p_ek is not set and + * typebuf[typeoff + offset] is an ESC and we are in insert mode + */ + if (*tp == ESC && !p_ek && (State & INSERT)) + continue; + + len = typelen - offset; /* length of the input */ + new_slen = 0; /* Length of what will replace the termcode */ + key_name[0] = NUL; /* no key name found yet */ + +#ifdef USE_GUI + if (gui.in_use) + { + /* + * GUI special key codes are all of the form [CSI xx]. + */ + if (*tp == CSI) /* Special key from GUI */ + { + if (len < 3) + return -1; /* Shouldn't happen */ + slen = 3; + key_name[0] = tp[1]; + key_name[1] = tp[2]; + } + } + else +#endif /* USE_GUI */ + { + for (i = 0; i < tc_len; ++i) + { + /* + * Ignore the entry if we are not at the start of typebuf[] + * and there are not enough characters to make a match. + */ + slen = termcodes[i].len; + if (offset && len < slen) + continue; + if (STRNCMP(termcodes[i].code, tp, + (size_t)(slen > len ? len : slen)) == 0) + { + if (len < slen) /* got a partial sequence */ + return -1; /* need to get more chars */ + + key_name[0] = termcodes[i].name[0]; + key_name[1] = termcodes[i].name[1]; + + /* + * If it's a shifted special key, then include the SHIFT + * modifier + */ + if (unshift_special_key(&key_name[0])) + { + string[new_slen++] = K_SPECIAL; + string[new_slen++] = KS_MODIFIER; + string[new_slen++] = MOD_MASK_SHIFT; + } + break; + } + } + } + + if (key_name[0] == NUL) + continue; /* No match at this position, try next one */ + + /* We only get here when we have a complete termcode match */ + +#ifdef USE_MOUSE + /* + * If it is a mouse click, get the coordinates. + * we get "scr", where + * s == encoded mouse button state (0x20 = left, 0x22 = right, etc) + * c == column + ' ' + 1 == column + 33 + * r == row + ' ' + 1 == row + 33 + * + * The coordinates are passed on through global variables. Ugly, + * but this avoids trouble with mouse clicks at an unexpected + * moment and allows for mapping them. + */ + if (key_name[0] == KS_MOUSE) + { + num_bytes = get_bytes_from_buf(tp + slen, bytes, 3); + if (num_bytes == -1) /* not enough coordinates */ + return -1; + mouse_code = bytes[0]; + mouse_col = bytes[1] - ' ' - 1; + mouse_row = bytes[2] - ' ' - 1; + slen += num_bytes; + + /* Interpret the mouse code */ + is_click = is_drag = FALSE; + current_button = (mouse_code & MOUSE_CLICK_MASK); + if (current_button == MOUSE_RELEASE) + { + /* + * If we get a mouse drag or release event when + * there is no mouse button held down (held_button == + * MOUSE_RELEASE), produce a K_IGNORE below. + * (can happen when you hold down two buttons + * and then let them go, or click in the menu bar, but not + * on a menu, and drag into the text). + */ + if ((mouse_code & MOUSE_DRAG) == MOUSE_DRAG) + is_drag = TRUE; + current_button = held_button; + } + else + { +#if defined(UNIX) && defined(HAVE_GETTIMEOFDAY) && defined(HAVE_SYS_TIME_H) +# ifdef USE_GUI + /* + * Only for Unix, when GUI is not active, we handle + * multi-clicks here. + */ + if (!gui.in_use) +# endif + { + /* + * Compute the time elapsed since the previous mouse click. + */ + gettimeofday(&mouse_time, NULL); + timediff = (mouse_time.tv_usec - + orig_mouse_time.tv_usec) / 1000; + if (timediff < 0) + --orig_mouse_time.tv_sec; + timediff += (mouse_time.tv_sec - + orig_mouse_time.tv_sec) * 1000; + orig_mouse_time = mouse_time; + if (mouse_code == orig_mouse_code && + timediff < p_mouset && + orig_num_clicks != 4 && + orig_mouse_col == mouse_col && + orig_mouse_row == mouse_row && + orig_topline == curwin->w_topline) + ++orig_num_clicks; + else + orig_num_clicks = 1; + orig_mouse_col = mouse_col; + orig_mouse_row = mouse_row; + orig_topline = curwin->w_topline; + } +# ifdef USE_GUI + else + orig_num_clicks = NUM_MOUSE_CLICKS(mouse_code); +# endif +#else + orig_num_clicks = NUM_MOUSE_CLICKS(mouse_code); +#endif + is_click = TRUE; + orig_mouse_code = mouse_code; + } + if (!is_drag) + held_button = mouse_code & MOUSE_CLICK_MASK; + + /* + * Translate the actual mouse event into a pseudo mouse event. + * First work out what modifiers are to be used. + */ + modifiers = 0x0; + if (orig_mouse_code & MOUSE_SHIFT) + modifiers |= MOD_MASK_SHIFT; + if (orig_mouse_code & MOUSE_CTRL) + modifiers |= MOD_MASK_CTRL; + if (orig_mouse_code & MOUSE_ALT) + modifiers |= MOD_MASK_ALT; + if (orig_num_clicks == 2) + modifiers |= MOD_MASK_2CLICK; + else if (orig_num_clicks == 3) + modifiers |= MOD_MASK_3CLICK; + else if (orig_num_clicks == 4) + modifiers |= MOD_MASK_4CLICK; + + /* Add the modifier codes to our string */ + if (modifiers != 0) + { + string[new_slen++] = K_SPECIAL; + string[new_slen++] = KS_MODIFIER; + string[new_slen++] = modifiers; + } + + /* Work out our pseudo mouse event */ + key_name[0] = KS_EXTRA; + key_name[1] = get_pseudo_mouse_code(current_button, + is_click, is_drag); + } +#endif /* USE_MOUSE */ +#ifdef USE_GUI + /* + * If using the GUI, then we get menu and scrollbar events. + * + * A menu event is encoded as K_SPECIAL, KS_MENU, K_FILLER followed by + * four bytes which are to be taken as a pointer to the GuiMenu + * structure. + * + * A scrollbar event is K_SPECIAL, KS_SCROLLBAR, K_FILLER followed by + * one byte representing the scrollbar number, and then four bytes + * representing a long_u which is the new value of the scrollbar. + * + * A horizontal scrollbar event is K_SPECIAL, KS_HORIZ_SCROLLBAR, + * K_FILLER followed by four bytes representing a long_u which is the + * new value of the scrollbar. + */ + else if (key_name[0] == KS_MENU) + { + num_bytes = get_long_from_buf(tp + slen, &val); + if (num_bytes == -1) + return -1; + current_menu = (GuiMenu *)val; + slen += num_bytes; + } + else if (key_name[0] == KS_SCROLLBAR) + { + num_bytes = get_bytes_from_buf(tp + slen, bytes, 1); + if (num_bytes == -1) + return -1; + current_scrollbar = (int)bytes[0]; + slen += num_bytes; + num_bytes = get_long_from_buf(tp + slen, &val); + if (num_bytes == -1) + return -1; + scrollbar_value = val; + slen += num_bytes; + } + else if (key_name[0] == KS_HORIZ_SCROLLBAR) + { + num_bytes = get_long_from_buf(tp + slen, &val); + if (num_bytes == -1) + return -1; + scrollbar_value = val; + slen += num_bytes; + } +#endif /* USE_GUI */ + /* Finally, add the special key code to our string */ + string[new_slen++] = K_SPECIAL; + string[new_slen++] = key_name[0]; + string[new_slen++] = key_name[1]; + string[new_slen] = NUL; + extra = new_slen - slen; + if (extra < 0) + /* remove matched chars, taking care of noremap */ + del_typebuf(-extra, offset); + else if (extra > 0) + /* insert the extra space we need */ + ins_typebuf(string + slen, FALSE, offset, FALSE); + + /* + * Careful: del_typebuf() and ins_typebuf() may have + * reallocated typebuf[] + */ + vim_memmove(typebuf + typeoff + offset, string, (size_t)new_slen); + return (len + extra + offset); + } + return 0; /* no match found */ +} + +/* + * Replace any terminal code strings in from[] with the equivalent internal + * vim representation. This is used for the "from" and "to" part of a + * mapping, and the "to" part of a menu command. + * Any strings like "" are also replaced, unless 'cpoptions' contains + * '<'. Also unshifts shifted special keys. + * K_SPECIAL by itself is replaced by K_SPECIAL KS_SPECIAL K_FILLER. + * + * The replacement is done in result[] and finally copied into allocated + * memory. If this all works well *bufp is set to the allocated memory and a + * pointer to it is returned. If something fails *bufp is set to NULL and from + * is returned. + * + * CTRL-V characters are removed. When "from_part" is TRUE, a trailing CTRL-V + * is included, otherwise it is removed (for ":map xx ^V", maps xx to + * nothing). When 'cpoptions' does not contain 'B', a backslash can be used + * instead of a CTRL-V. + */ + char_u * +replace_termcodes(from, bufp, from_part) + char_u *from; + char_u **bufp; + int from_part; +{ + int i; + char_u key_name[2]; + char_u *bp; + char_u *last_dash; + char_u *end_of_name; + int slen; + int modifiers; + int bit; + int key; + int dlen = 0; + char_u *src; + int do_backslash; /* backslash is a special character */ + int do_special; /* recognize <> key codes */ + int do_key_code; /* recognize raw key codes */ + char_u *result; /* buffer for resulting string */ + + do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL); + do_special = (vim_strchr(p_cpo, CPO_SPECI) == NULL); + do_key_code = (vim_strchr(p_cpo, CPO_KEYCODE) == NULL); + + /* + * Allocate space for the translation. Worst case a single character is + * replaced by 6 bytes (shifted special key), plus a NUL at the end. + */ + result = alloc((unsigned)STRLEN(from) * 6 + 1); + if (result == NULL) /* out of memory */ + { + *bufp = NULL; + return from; + } + + src = from; + + /* + * Check for #n at start only: function key n + */ + if (from_part && src[0] == '#' && isdigit(src[1])) /* function key */ + { + result[dlen++] = K_SPECIAL; + result[dlen++] = 'k'; + if (src[1] == '0') + result[dlen++] = ';'; /* #0 is F10 is "k;" */ + else + result[dlen++] = src[1]; /* #3 is F3 is "k3" */ + src += 2; + } + + /* + * Copy each byte from *from to result[dlen] + */ + while (*src != NUL) + { + /* + * If 'cpoptions' does not contain '<', check for special key codes. + */ + if (do_special) + { + /* + * See if it's a string like "" + */ + if (src[0] == '<') + { + /* Find end of modifier list */ + last_dash = src; + for (bp = src + 1; *bp == '-' || isidchar(*bp); bp++) + { + if (*bp == '-') + { + last_dash = bp; + if (bp[1] != NUL && bp[2] == '>') + ++bp; /* anything accepted, like */ + } + if (bp[0] == 't' && bp[1] == '_' && bp[2] && bp[3]) + bp += 3; /* skip t_xx, xx may be '-' or '>' */ + } + + if (*bp == '>') /* found matching '>' */ + { + end_of_name = bp + 1; + + /* Which modifiers are given? */ + modifiers = 0x0; + for (bp = src + 1; bp < last_dash; bp++) + { + if (*bp != '-') + { + bit = name_to_mod_mask(*bp); + if (bit == 0x0) + break; /* Illegal modifier name */ + modifiers |= bit; + } + } + + /* + * Legal modifier name. + */ + if (bp >= last_dash) + { + /* + * Modifier with single letter + */ + if (modifiers != 0 && last_dash[2] == '>') + { + key = last_dash[1]; + if (modifiers & MOD_MASK_SHIFT) + key = TO_UPPER(key); + if (modifiers & MOD_MASK_CTRL) + key &= 0x1f; + if (modifiers & MOD_MASK_ALT) + key |= 0x80; + src = end_of_name; + result[dlen++] = key; + continue; + } + + /* + * Key name with or without modifier. + */ + else if ((key = get_special_key_code(last_dash + 1)) + != 0) + { + /* Put the appropriate modifier in a string */ + if (modifiers != 0) + { + result[dlen++] = K_SPECIAL; + result[dlen++] = KS_MODIFIER; + result[dlen++] = modifiers; + /* + * Special trick: for K_TAB is used + * instead of TAB (there are two keys for the + * same thing). + */ + if (key == TAB) + key = K_TAB; + } + + if (IS_SPECIAL(key)) + { + result[dlen++] = K_SPECIAL; + result[dlen++] = KEY2TERMCAP0(key); + result[dlen++] = KEY2TERMCAP1(key); + } + else + result[dlen++] = key; /* only modifiers */ + src = end_of_name; + continue; + } + } + } + } + } + + /* + * If 'cpoptions' does not contain 'k', see if it's an actual key-code. + * Note that this is also checked after replacing the <> form. + */ + if (do_key_code) + { + for (i = 0; i < tc_len; ++i) + { + slen = termcodes[i].len; + if (STRNCMP(termcodes[i].code, src, (size_t)slen) == 0) + { + key_name[0] = termcodes[i].name[0]; + key_name[1] = termcodes[i].name[1]; + + /* + * If it's a shifted special key, then include the SHIFT + * modifier + */ + if (unshift_special_key(&key_name[0])) + { + result[dlen++] = K_SPECIAL; + result[dlen++] = KS_MODIFIER; + result[dlen++] = MOD_MASK_SHIFT; + } + result[dlen++] = K_SPECIAL; + result[dlen++] = key_name[0]; + result[dlen++] = key_name[1]; + src += slen; + break; + } + } + + /* + * If terminal code matched, continue after it. + * If no terminal code matched and the character is K_SPECIAL, + * replace it with K_SPECIAL KS_SPECIAL K_FILLER + */ + if (i != tc_len) + continue; + } + + if (*src == K_SPECIAL) + { + result[dlen++] = K_SPECIAL; + result[dlen++] = KS_SPECIAL; + result[dlen++] = K_FILLER; + ++src; + continue; + } + + /* + * Remove CTRL-V and ignore the next character. + * For "from" side the CTRL-V at the end is included, for the "to" + * part it is removed. + * If 'cpoptions' does not contain 'B', also accept a backslash. + */ + key = *src; + if (key == Ctrl('V') || (do_backslash && key == '\\')) + { + ++src; /* skip CTRL-V or backslash */ + if (*src == NUL) + { + if (from_part) + result[dlen++] = key; + break; + } + } + result[dlen++] = *src++; + } + result[dlen] = NUL; + + /* + * Copy the new string to allocated memory. + * If this fails, just return from. + */ + if ((*bufp = strsave(result)) != NULL) + from = *bufp; + vim_free(result); + return from; +} + +/* + * Gather the first characters in the terminal key codes into a string. + * Used to speed up check_termcode(). + */ + static void +gather_termleader() +{ + int i; + int len = 0; + +#ifdef USE_GUI + if (gui.in_use) + termleader[len++] = CSI; /* the GUI codes are not in termcodes[] */ +#endif + termleader[len] = NUL; + + for (i = 0; i < tc_len; ++i) + if (vim_strchr(termleader, termcodes[i].code[0]) == NULL) + { + termleader[len++] = termcodes[i].code[0]; + termleader[len] = NUL; + } + + need_gather = FALSE; +} + +/* + * Show all termcodes (for ":set termcap") + * This code looks a lot like showoptions(), but is different. + */ + void +show_termcodes() +{ + int col; + int *items; + int item_count; + int run; + int row, rows; + int cols; + int i; + int len; + +#define INC 27 /* try to make three columns */ +#define GAP 2 /* spaces between columns */ + + if (tc_len == 0) /* no terminal codes (must be GUI) */ + return; + items = (int *)alloc((unsigned)(sizeof(int) * tc_len)); + if (items == NULL) + return; + + set_highlight('t'); /* Highlight title */ + start_highlight(); + MSG_OUTSTR("\n--- Terminal keys ---"); + stop_highlight(); + + /* + * do the loop two times: + * 1. display the short items (non-strings and short strings) + * 2. display the long items (strings) + */ + for (run = 1; run <= 2 && !got_int; ++run) + { + /* + * collect the items in items[] + */ + item_count = 0; + for (i = 0; i < tc_len; i++) + { + len = show_one_termcode(termcodes[i].name, + termcodes[i].code, FALSE); + if ((len <= INC - GAP && run == 1) || (len > INC - GAP && run == 2)) + items[item_count++] = i; + } + + /* + * display the items + */ + if (run == 1) + { + cols = (Columns + GAP) / INC; + if (cols == 0) + cols = 1; + rows = (item_count + cols - 1) / cols; + } + else /* run == 2 */ + rows = item_count; + for (row = 0; row < rows && !got_int; ++row) + { + msg_outchar('\n'); /* go to next line */ + if (got_int) /* 'q' typed in more */ + break; + col = 0; + for (i = row; i < item_count; i += rows) + { + msg_pos(-1, col); /* make columns */ + show_one_termcode(termcodes[items[i]].name, + termcodes[items[i]].code, TRUE); + col += INC; + } + flushbuf(); + mch_breakcheck(); + } + } + vim_free(items); +} + +/* + * Show one termcode entry. + * Output goes into IObuff[] + */ + int +show_one_termcode(name, code, printit) + char_u *name; + char_u *code; + int printit; +{ + char_u *p; + int len; + + if (name[0] > '~') + { + IObuff[0] = ' '; + IObuff[1] = ' '; + IObuff[2] = ' '; + IObuff[3] = ' '; + } + else + { + IObuff[0] = 't'; + IObuff[1] = '_'; + IObuff[2] = name[0]; + IObuff[3] = name[1]; + } + IObuff[4] = ' '; + + p = get_special_key_name(TERMCAP2KEY(name[0], name[1]), 0); + if (p[1] != 't') + STRCPY(IObuff + 5, p); + else + IObuff[5] = NUL; + len = STRLEN(IObuff); + do + IObuff[len++] = ' '; + while (len < 17); + IObuff[len] = NUL; + if (code == NULL) + len += 4; + else + len += strsize(code); + + if (printit) + { + msg_outstr(IObuff); + if (code == NULL) + msg_outstr((char_u *)"NULL"); + else + msg_outtrans(code); + } + return len; +} diff --git a/usr.bin/vim/term.h b/usr.bin/vim/term.h new file mode 100644 index 00000000000..f1fa8d1f0ed --- /dev/null +++ b/usr.bin/vim/term.h @@ -0,0 +1,112 @@ +/* $OpenBSD: term.h,v 1.1.1.1 1996/09/07 21:40:27 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * This file contains the machine dependent escape sequences that the editor + * needs to perform various operations. Some of the sequences here are + * optional. Anything not available should be indicated by a null string. In + * the case of insert/delete line sequences, the editor checks the capability + * and works around the deficiency, if necessary. + */ + +#ifdef SASC +/* + * the SAS C compiler has a bug that makes typedefs being forgot sometimes + */ +typedef unsigned char char_u; +#endif + +/* + * Index of the termcap codes in the term_strings array. + */ +enum SpecialKeys +{ + KS_NAME = 0, /* name of this terminal entry */ + KS_CE, /* clear to end of line */ + KS_AL, /* add new blank line */ + KS_CAL, /* add number of blank lines */ + KS_DL, /* delete line */ + KS_CDL, /* delete number of lines */ + KS_CS, /* scroll region */ + KS_CL, /* clear screen */ + KS_CD, /* clear to end of display */ + KS_DA, /* text may be scrolled down from up */ + KS_DB, /* text may be scrolled up from down */ + KS_VI, /* cursor invisible */ + KS_VE, /* cursor visible */ + KS_VS, /* cursor very visible */ + KS_ME, /* normal mode */ + KS_MR, /* reverse mode */ + KS_MD, /* bold mode */ + KS_SE, /* normal mode */ + KS_SO, /* standout mode */ + KS_CZH, /* italic mode start */ + KS_CZR, /* italic mode end */ + KS_UE, /* exit underscore mode */ + KS_US, /* underscore mode */ + KS_MS, /* save to move cur in reverse mode */ + KS_CM, /* cursor motion */ + KS_SR, /* scroll reverse (backward) */ + KS_CRI, /* cursor number of chars right */ + KS_VB, /* visual bell */ + KS_KS, /* put term in "keypad transmit" mode */ + KS_KE, /* out of "keypad transmit" mode */ + KS_TI, /* put terminal in termcap mode */ + KS_TE, /* out of termcap mode */ + + KS_CSC /* cur is relative to scroll region */ +}; + +#define KS_LAST KS_CSC + +/* + * the terminal capabilities are stored in this array + * IMPORTANT: When making changes, note the following: + * - there should be an entry for each code in the builtin termcaps + * - there should be an option for each code in option.c + * - there should be code in term.c to obtain the value from the termcap + */ + +extern char_u *(term_strings[]); /* current terminal strings */ + +/* + * strings used for terminal + */ +#define T_CE (term_strings[KS_CE]) /* clear to end of line */ +#define T_AL (term_strings[KS_AL]) /* add new blank line */ +#define T_CAL (term_strings[KS_CAL]) /* add number of blank lines */ +#define T_DL (term_strings[KS_DL]) /* delete line */ +#define T_CDL (term_strings[KS_CDL]) /* delete number of lines */ +#define T_CS (term_strings[KS_CS]) /* scroll region */ +#define T_CL (term_strings[KS_CL]) /* clear screen */ +#define T_CD (term_strings[KS_CD]) /* clear to end of display */ +#define T_DA (term_strings[KS_DA]) /* text may be scrolled down from up */ +#define T_DB (term_strings[KS_DB]) /* text may be scrolled up from down */ +#define T_VI (term_strings[KS_VI]) /* cursor invisible */ +#define T_VE (term_strings[KS_VE]) /* cursor visible */ +#define T_VS (term_strings[KS_VS]) /* cursor very visible */ +#define T_ME (term_strings[KS_ME]) /* normal mode */ +#define T_MR (term_strings[KS_MR]) /* reverse mode */ +#define T_MD (term_strings[KS_MD]) /* bold mode */ +#define T_SE (term_strings[KS_SE]) /* normal mode */ +#define T_SO (term_strings[KS_SO]) /* standout mode */ +#define T_CZH (term_strings[KS_CZH]) /* italic mode start */ +#define T_CZR (term_strings[KS_CZR]) /* italic mode end */ +#define T_UE (term_strings[KS_UE]) /* exit underscore mode */ +#define T_US (term_strings[KS_US]) /* underscore mode */ +#define T_MS (term_strings[KS_MS]) /* save to move cur in reverse mode */ +#define T_CM (term_strings[KS_CM]) /* cursor motion */ +#define T_SR (term_strings[KS_SR]) /* scroll reverse (backward) */ +#define T_CRI (term_strings[KS_CRI]) /* cursor number of chars right */ +#define T_VB (term_strings[KS_VB]) /* visual bell */ +#define T_KS (term_strings[KS_KS]) /* put term in "keypad transmit" mode */ +#define T_KE (term_strings[KS_KE]) /* out of "keypad transmit" mode */ +#define T_TI (term_strings[KS_TI]) /* put terminal in termcap mode */ +#define T_TE (term_strings[KS_TE]) /* out of termcap mode */ +#define T_CSC (term_strings[KS_CSC]) /* cur is relative to scroll region */ diff --git a/usr.bin/vim/undo.c b/usr.bin/vim/undo.c new file mode 100644 index 00000000000..e96d46efd85 --- /dev/null +++ b/usr.bin/vim/undo.c @@ -0,0 +1,1057 @@ +/* $OpenBSD: undo.c,v 1.1.1.1 1996/09/07 21:40:24 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * undo.c: multi level undo facility + * + * The saved lines are stored in a list of lists (one for each buffer): + * + * b_u_oldhead------------------------------------------------+ + * | + * V + * +--------------+ +--------------+ +--------------+ + * b_u_newhead--->| u_header | | u_header | | u_header | + * | uh_next------>| uh_next------>| uh_next---->NULL + * NULL<--------uh_prev |<---------uh_prev |<---------uh_prev | + * | uh_entry | | uh_entry | | uh_entry | + * +--------|-----+ +--------|-----+ +--------|-----+ + * | | | + * V V V + * +--------------+ +--------------+ +--------------+ + * | u_entry | | u_entry | | u_entry | + * | ue_next | | ue_next | | ue_next | + * +--------|-----+ +--------|-----+ +--------|-----+ + * | | | + * V V V + * +--------------+ NULL NULL + * | u_entry | + * | ue_next | + * +--------|-----+ + * | + * V + * etc. + * + * Each u_entry list contains the information for one undo or redo. + * curbuf->b_u_curhead points to the header of the last undo (the next redo), + * or is NULL if nothing has been undone. + * + * All data is allocated with u_alloc_line(), thus it will be freed as soon as + * we switch files! + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +static void u_getbot __ARGS((void)); +static int u_savecommon __ARGS((linenr_t, linenr_t, linenr_t)); +static void u_undoredo __ARGS((void)); +static void u_undo_end __ARGS((void)); +static void u_freelist __ARGS((struct u_header *)); +static void u_freeentry __ARGS((struct u_entry *, long)); + +static char_u *u_blockalloc __ARGS((long_u)); +static void u_free_line __ARGS((char_u *)); +static char_u *u_alloc_line __ARGS((unsigned)); +static char_u *u_save_line __ARGS((linenr_t)); + +static long u_newcount, u_oldcount; + +/* + * save the current line for both the "u" and "U" command + */ + int +u_save_cursor() +{ + return (u_save((linenr_t)(curwin->w_cursor.lnum - 1), (linenr_t)(curwin->w_cursor.lnum + 1))); +} + +/* + * Save the lines between "top" and "bot" for both the "u" and "U" command. + * "top" may be 0 and bot may be curbuf->b_ml.ml_line_count + 1. + * Returns FAIL when lines could not be saved, OK otherwise. + */ + int +u_save(top, bot) + linenr_t top, bot; +{ + if (undo_off) + return OK; + + if (top > curbuf->b_ml.ml_line_count || + top >= bot || bot > curbuf->b_ml.ml_line_count + 1) + return FALSE; /* rely on caller to do error messages */ + + if (top + 2 == bot) + u_saveline((linenr_t)(top + 1)); + + return (u_savecommon(top, bot, (linenr_t)0)); +} + +/* + * save the line "lnum" (used by :s command) + * The line is replaced, so the new bottom line is lnum + 1. + */ + int +u_savesub(lnum) + linenr_t lnum; +{ + if (undo_off) + return OK; + + return (u_savecommon(lnum - 1, lnum + 1, lnum + 1)); +} + +/* + * a new line is inserted before line "lnum" (used by :s command) + * The line is inserted, so the new bottom line is lnum + 1. + */ + int +u_inssub(lnum) + linenr_t lnum; +{ + if (undo_off) + return OK; + + return (u_savecommon(lnum - 1, lnum, lnum + 1)); +} + +/* + * save the lines "lnum" - "lnum" + nlines (used by delete command) + * The lines are deleted, so the new bottom line is lnum, unless the buffer + * becomes empty. + */ + int +u_savedel(lnum, nlines) + linenr_t lnum; + long nlines; +{ + if (undo_off) + return OK; + + return (u_savecommon(lnum - 1, lnum + nlines, + nlines == curbuf->b_ml.ml_line_count ? 2 : lnum)); +} + + static int +u_savecommon(top, bot, newbot) + linenr_t top, bot; + linenr_t newbot; +{ + linenr_t lnum; + long i; + struct u_header *uhp; + struct u_entry *uep; + long size; + + /* + * if curbuf->b_u_synced == TRUE make a new header + */ + if (curbuf->b_u_synced) + { + /* + * if we undid more than we redid, free the entry lists before and + * including curbuf->b_u_curhead + */ + while (curbuf->b_u_curhead != NULL) + u_freelist(curbuf->b_u_newhead); + + /* + * free headers to keep the size right + */ + while (curbuf->b_u_numhead > p_ul && curbuf->b_u_oldhead != NULL) + u_freelist(curbuf->b_u_oldhead); + + if (p_ul < 0) /* no undo at all */ + return OK; + + /* + * make a new header entry + */ + uhp = (struct u_header *)u_alloc_line((unsigned)sizeof(struct u_header)); + if (uhp == NULL) + goto nomem; + uhp->uh_prev = NULL; + uhp->uh_next = curbuf->b_u_newhead; + if (curbuf->b_u_newhead != NULL) + curbuf->b_u_newhead->uh_prev = uhp; + uhp->uh_entry = NULL; + uhp->uh_cursor = curwin->w_cursor; /* save cursor pos. for undo */ + + /* save changed and buffer empty flag for undo */ + uhp->uh_flags = (curbuf->b_changed ? UH_CHANGED : 0) + + ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0); + + /* save named marks for undo */ + vim_memmove((char *)uhp->uh_namedm, (char *)curbuf->b_namedm, + sizeof(FPOS) * NMARKS); + curbuf->b_u_newhead = uhp; + if (curbuf->b_u_oldhead == NULL) + curbuf->b_u_oldhead = uhp; + ++curbuf->b_u_numhead; + } + else /* find line number for ue_bot for previous u_save() */ + u_getbot(); + + size = bot - top - 1; +#if !defined(UNIX) && !defined(DJGPP) && !defined(WIN32) && !defined(__EMX__) + /* + * With Amiga and MSDOS we can't handle big undo's, because then + * u_alloc_line would have to allocate a block larger than 32K + */ + if (size >= 8000) + goto nomem; +#endif + + /* + * add lines in front of entry list + */ + uep = (struct u_entry *)u_alloc_line((unsigned)sizeof(struct u_entry)); + if (uep == NULL) + goto nomem; + + uep->ue_size = size; + uep->ue_top = top; + uep->ue_lcount = 0; + if (newbot) + uep->ue_bot = newbot; + /* + * Use 0 for ue_bot if bot is below last line. + * Otherwise we have to compute ue_bot later. + */ + else if (bot > curbuf->b_ml.ml_line_count) + uep->ue_bot = 0; + else + uep->ue_lcount = curbuf->b_ml.ml_line_count; + + if (size) + { + if ((uep->ue_array = (char_u **)u_alloc_line((unsigned)(sizeof(char_u *) * size))) == NULL) + { + u_freeentry(uep, 0L); + goto nomem; + } + for (i = 0, lnum = top + 1; i < size; ++i) + { + if ((uep->ue_array[i] = u_save_line(lnum++)) == NULL) + { + u_freeentry(uep, i); + goto nomem; + } + } + } + uep->ue_next = curbuf->b_u_newhead->uh_entry; + curbuf->b_u_newhead->uh_entry = uep; + curbuf->b_u_synced = FALSE; + return OK; + +nomem: + if (ask_yesno((char_u *)"No undo possible; continue anyway", TRUE) == 'y') + { + undo_off = TRUE; /* will be reset when character typed */ + return OK; + } + do_outofmem_msg(); + return FAIL; +} + + void +u_undo(count) + int count; +{ + /* + * If we get an undo command while executing a macro, we behave like the + * original vi. If this happens twice in one macro the result will not + * be compatible. + */ + if (curbuf->b_u_synced == FALSE) + { + u_sync(); + count = 1; + } + + u_newcount = 0; + u_oldcount = 0; + while (count--) + { + if (curbuf->b_u_curhead == NULL) /* first undo */ + curbuf->b_u_curhead = curbuf->b_u_newhead; + else if (p_ul > 0) /* multi level undo */ + /* get next undo */ + curbuf->b_u_curhead = curbuf->b_u_curhead->uh_next; + /* nothing to undo */ + if (curbuf->b_u_numhead == 0 || curbuf->b_u_curhead == NULL) + { + /* stick curbuf->b_u_curhead at end */ + curbuf->b_u_curhead = curbuf->b_u_oldhead; + beep_flush(); + break; + } + + u_undoredo(); + } + u_undo_end(); +} + + void +u_redo(count) + int count; +{ + u_newcount = 0; + u_oldcount = 0; + while (count--) + { + if (curbuf->b_u_curhead == NULL || p_ul <= 0) /* nothing to redo */ + { + beep_flush(); + break; + } + + u_undoredo(); + /* advance for next redo */ + curbuf->b_u_curhead = curbuf->b_u_curhead->uh_prev; + } + u_undo_end(); +} + +/* + * u_undoredo: common code for undo and redo + * + * The lines in the file are replaced by the lines in the entry list at + * curbuf->b_u_curhead. The replaced lines in the file are saved in the entry + * list for the next undo/redo. + */ + static void +u_undoredo() +{ + char_u **newarray = NULL; + linenr_t oldsize; + linenr_t newsize; + linenr_t top, bot; + linenr_t lnum; + linenr_t newlnum = MAXLNUM; + long i; + struct u_entry *uep, *nuep; + struct u_entry *newlist = NULL; + int old_flags; + int new_flags; + FPOS namedm[NMARKS]; + int empty_buffer; /* buffer became empty */ + + old_flags = curbuf->b_u_curhead->uh_flags; + new_flags = (curbuf->b_changed ? UH_CHANGED : 0) + + ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0); + if (old_flags & UH_CHANGED) + CHANGED; + else + UNCHANGED(curbuf); + setpcmark(); + + /* + * save marks before undo/redo + */ + vim_memmove((char *)namedm, (char *)curbuf->b_namedm, + sizeof(FPOS) * NMARKS); + curbuf->b_op_start.lnum = curbuf->b_ml.ml_line_count; + curbuf->b_op_start.col = 0; + curbuf->b_op_end.lnum = 0; + curbuf->b_op_end.col = 0; + + for (uep = curbuf->b_u_curhead->uh_entry; uep != NULL; uep = nuep) + { + top = uep->ue_top; + bot = uep->ue_bot; + if (bot == 0) + bot = curbuf->b_ml.ml_line_count + 1; + if (top > curbuf->b_ml.ml_line_count || top >= bot || bot > curbuf->b_ml.ml_line_count + 1) + { + EMSG("u_undo: line numbers wrong"); + CHANGED; /* don't want UNCHANGED now */ + return; + } + + if (top < newlnum) + { + newlnum = top; + curwin->w_cursor.lnum = top + 1; + } + oldsize = bot - top - 1; /* number of lines before undo */ + newsize = uep->ue_size; /* number of lines after undo */ + + empty_buffer = FALSE; + + /* delete the lines between top and bot and save them in newarray */ + if (oldsize) + { + if ((newarray = (char_u **)u_alloc_line((unsigned)(sizeof(char_u *) * oldsize))) == NULL) + { + do_outofmem_msg(); + /* + * We have messed up the entry list, repair is impossible. + * we have to free the rest of the list. + */ + while (uep != NULL) + { + nuep = uep->ue_next; + u_freeentry(uep, uep->ue_size); + uep = nuep; + } + break; + } + /* delete backwards, it goes faster in most cases */ + for (lnum = bot - 1, i = oldsize; --i >= 0; --lnum) + { + /* what can we do when we run out of memory? */ + if ((newarray[i] = u_save_line(lnum)) == NULL) + do_outofmem_msg(); + /* remember we deleted the last line in the buffer, and a + * dummy empty line will be inserted */ + if (curbuf->b_ml.ml_line_count == 1) + empty_buffer = TRUE; + ml_delete(lnum, FALSE); + } + } + + /* insert the lines in u_array between top and bot */ + if (newsize) + { + for (lnum = top, i = 0; i < newsize; ++i, ++lnum) + { + /* + * If the file is empty, there is an empty line 1 that we + * should get rid of, by replacing it with the new line + */ + if (empty_buffer && lnum == 0) + ml_replace((linenr_t)1, uep->ue_array[i], TRUE); + else + ml_append(lnum, uep->ue_array[i], (colnr_t)0, FALSE); + u_free_line(uep->ue_array[i]); + } + u_free_line((char_u *)uep->ue_array); + } + + /* adjust marks */ + if (oldsize != newsize) + { + mark_adjust(top + 1, top + oldsize, MAXLNUM, + (long)newsize - (long)oldsize); + if (curbuf->b_op_start.lnum > top + oldsize) + curbuf->b_op_start.lnum += newsize - oldsize; + if (curbuf->b_op_end.lnum > top + oldsize) + curbuf->b_op_end.lnum += newsize - oldsize; + } + /* set '[ and '] mark */ + if (top + 1 < curbuf->b_op_start.lnum) + curbuf->b_op_start.lnum = top + 1; + if (newsize == 0 && top + 1 > curbuf->b_op_end.lnum) + curbuf->b_op_end.lnum = top + 1; + else if (top + newsize > curbuf->b_op_end.lnum) + curbuf->b_op_end.lnum = top + newsize; + + u_newcount += newsize; + u_oldcount += oldsize; + uep->ue_size = oldsize; + uep->ue_array = newarray; + uep->ue_bot = top + newsize + 1; + + /* + * insert this entry in front of the new entry list + */ + nuep = uep->ue_next; + uep->ue_next = newlist; + newlist = uep; + } + + curbuf->b_u_curhead->uh_entry = newlist; + curbuf->b_u_curhead->uh_flags = new_flags; + if ((old_flags & UH_EMPTYBUF) && bufempty()) + curbuf->b_ml.ml_flags |= ML_EMPTY; + + /* + * restore marks from before undo/redo + */ + for (i = 0; i < NMARKS; ++i) + if (curbuf->b_u_curhead->uh_namedm[i].lnum) + { + curbuf->b_namedm[i] = curbuf->b_u_curhead->uh_namedm[i]; + curbuf->b_u_curhead->uh_namedm[i] = namedm[i]; + } + + /* + * If the cursor is only off by one line, put it at the same position as + * before starting the change (for the "o" command). + * Otherwise the cursor should go to the first undone line. + */ + if (curbuf->b_u_curhead->uh_cursor.lnum + 1 == curwin->w_cursor.lnum && + curwin->w_cursor.lnum > 1) + --curwin->w_cursor.lnum; + if (curbuf->b_u_curhead->uh_cursor.lnum == curwin->w_cursor.lnum) + curwin->w_cursor.col = curbuf->b_u_curhead->uh_cursor.col; + else if (curwin->w_cursor.lnum <= curbuf->b_ml.ml_line_count) + beginline(MAYBE); + /* We still seem to need the case below because sometimes we get here with + * the current cursor line being one past the end (eg after adding lines + * at the end of the file, and then undoing it). Is it fair enough that + * this happens? -- webb + */ + else + curwin->w_cursor.col = 0; +} + +/* + * If we deleted or added lines, report the number of less/more lines. + * Otherwise, report the number of changes (this may be incorrect + * in some cases, but it's better than nothing). + */ + static void +u_undo_end() +{ + if ((u_oldcount -= u_newcount) != 0) + msgmore(-u_oldcount); + else if (u_newcount > p_report) + smsg((char_u *)"%ld change%s", u_newcount, plural(u_newcount)); + + update_curbuf(CURSUPD); /* need to update all windows in this buffer */ +} + +/* + * u_sync: stop adding to the current entry list + */ + void +u_sync() +{ + if (curbuf->b_u_synced) + return; /* already synced */ + u_getbot(); /* compute ue_bot of previous u_save */ + curbuf->b_u_curhead = NULL; +} + +/* + * Called after writing the file and setting b_changed to FALSE. + * Now an undo means that the buffer is modified. + */ + void +u_unchanged(buf) + BUF *buf; +{ + register struct u_header *uh; + + for (uh = buf->b_u_newhead; uh; uh = uh->uh_next) + uh->uh_flags |= UH_CHANGED; + buf->b_did_warn = FALSE; +} + +/* + * u_getbot(): compute the line number of the previous u_save + * It is called only when b_u_synced is FALSE. + */ + static void +u_getbot() +{ + register struct u_entry *uep; + + if (curbuf->b_u_newhead == NULL || + (uep = curbuf->b_u_newhead->uh_entry) == NULL) + { + EMSG("undo list corrupt"); + return; + } + + if (uep->ue_lcount != 0) + { + /* + * the new ue_bot is computed from the number of lines that has been + * inserted (0 - deleted) since calling u_save. This is equal to the old + * line count subtracted from the current line count. + */ + uep->ue_bot = uep->ue_top + uep->ue_size + 1 + + (curbuf->b_ml.ml_line_count - uep->ue_lcount); + if (uep->ue_bot < 1 || uep->ue_bot > curbuf->b_ml.ml_line_count) + { + EMSG("undo line missing"); + uep->ue_bot = uep->ue_top + 1; /* assume all lines deleted, will + * get all the old lines back + * without deleting the current + * ones */ + } + uep->ue_lcount = 0; + } + + curbuf->b_u_synced = TRUE; +} + +/* + * u_freelist: free one entry list and adjust the pointers + */ + static void +u_freelist(uhp) + struct u_header *uhp; +{ + register struct u_entry *uep, *nuep; + + for (uep = uhp->uh_entry; uep != NULL; uep = nuep) + { + nuep = uep->ue_next; + u_freeentry(uep, uep->ue_size); + } + + if (curbuf->b_u_curhead == uhp) + curbuf->b_u_curhead = NULL; + + if (uhp->uh_next == NULL) + curbuf->b_u_oldhead = uhp->uh_prev; + else + uhp->uh_next->uh_prev = uhp->uh_prev; + + if (uhp->uh_prev == NULL) + curbuf->b_u_newhead = uhp->uh_next; + else + uhp->uh_prev->uh_next = uhp->uh_next; + + u_free_line((char_u *)uhp); + --curbuf->b_u_numhead; +} + +/* + * free entry 'uep' and 'n' lines in uep->ue_array[] + */ + static void +u_freeentry(uep, n) + struct u_entry *uep; + register long n; +{ + while (n) + u_free_line(uep->ue_array[--n]); + u_free_line((char_u *)uep); +} + +/* + * invalidate the undo buffer; called when storage has already been released + */ + void +u_clearall(buf) + BUF *buf; +{ + buf->b_u_newhead = buf->b_u_oldhead = buf->b_u_curhead = NULL; + buf->b_u_synced = TRUE; + buf->b_u_numhead = 0; + buf->b_u_line_ptr = NULL; + buf->b_u_line_lnum = 0; +} + +/* + * save the line "lnum" for the "U" command + */ + void +u_saveline(lnum) + linenr_t lnum; +{ + if (lnum == curbuf->b_u_line_lnum) /* line is already saved */ + return; + if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) /* should never happen */ + return; + u_clearline(); + curbuf->b_u_line_lnum = lnum; + if (curwin->w_cursor.lnum == lnum) + curbuf->b_u_line_colnr = curwin->w_cursor.col; + else + curbuf->b_u_line_colnr = 0; + if ((curbuf->b_u_line_ptr = u_save_line(lnum)) == NULL) + do_outofmem_msg(); +} + +/* + * clear the line saved for the "U" command + * (this is used externally for crossing a line while in insert mode) + */ + void +u_clearline() +{ + if (curbuf->b_u_line_ptr != NULL) + { + u_free_line(curbuf->b_u_line_ptr); + curbuf->b_u_line_ptr = NULL; + curbuf->b_u_line_lnum = 0; + } +} + +/* + * Implementation of the "U" command. + * Differentiation from vi: "U" can be undone with the next "U". + * We also allow the cursor to be in another line. + */ + void +u_undoline() +{ + colnr_t t; + char_u *oldp; + + if (undo_off) + return; + + if (curbuf->b_u_line_ptr == NULL || + curbuf->b_u_line_lnum > curbuf->b_ml.ml_line_count) + { + beep_flush(); + return; + } + /* first save the line for the 'u' command */ + if (u_savecommon(curbuf->b_u_line_lnum - 1, + curbuf->b_u_line_lnum + 1, (linenr_t)0) == FAIL) + return; + oldp = u_save_line(curbuf->b_u_line_lnum); + if (oldp == NULL) + { + do_outofmem_msg(); + return; + } + ml_replace(curbuf->b_u_line_lnum, curbuf->b_u_line_ptr, TRUE); + u_free_line(curbuf->b_u_line_ptr); + curbuf->b_u_line_ptr = oldp; + + t = curbuf->b_u_line_colnr; + if (curwin->w_cursor.lnum == curbuf->b_u_line_lnum) + curbuf->b_u_line_colnr = curwin->w_cursor.col; + curwin->w_cursor.col = t; + curwin->w_cursor.lnum = curbuf->b_u_line_lnum; + cursupdate(); + updateScreen(VALID_TO_CURSCHAR); +} + +/* + * storage allocation for the undo lines and blocks of the current file + */ + +/* + * Memory is allocated in relatively large blocks. These blocks are linked + * in the allocated block list, headed by curbuf->b_block_head. They are all freed + * when abandoning a file, so we don't have to free every single line. The + * list is kept sorted on memory address. + * block_alloc() allocates a block. + * m_blockfree() frees all blocks. + * + * The available chunks of memory are kept in free chunk lists. There is + * one free list for each block of allocated memory. The list is kept sorted + * on memory address. + * u_alloc_line() gets a chunk from the free lists. + * u_free_line() returns a chunk to the free lists. + * curbuf->b_m_search points to the chunk before the chunk that was + * freed/allocated the last time. + * curbuf->b_mb_current points to the b_head where curbuf->b_m_search + * points into the free list. + * + * + * b_block_head /---> block #1 /---> block #2 + * mb_next ---/ mb_next ---/ mb_next ---> NULL + * mb_info mb_info mb_info + * | | | + * V V V + * NULL free chunk #1.1 free chunk #2.1 + * | | + * V V + * free chunk #1.2 NULL + * | + * V + * NULL + * + * When a single free chunk list would have been used, it could take a lot + * of time in u_free_line() to find the correct place to insert a chunk in the + * free list. The single free list would become very long when many lines are + * changed (e.g. with :%s/^M$//). + */ + + /* + * this blocksize is used when allocating new lines + */ +#define MEMBLOCKSIZE 2044 + +/* + * The size field contains the size of the chunk, including the size field itself. + * + * When the chunk is not in-use it is preceded with the m_info structure. + * The m_next field links it in one of the free chunk lists. + * + * On most unix systems structures have to be longword (32 or 64 bit) aligned. + * On most other systems they are short (16 bit) aligned. + */ + +/* the structure definitions are now in structs.h */ + +#ifdef ALIGN_LONG + /* size of m_size */ +# define M_OFFSET (sizeof(long_u)) +#else + /* size of m_size */ +# define M_OFFSET (sizeof(short_u)) +#endif + +/* + * Allocate a block of memory and link it in the allocated block list. + */ + static char_u * +u_blockalloc(size) + long_u size; +{ + struct m_block *p; + struct m_block *mp, *next; + + p = (struct m_block *)lalloc(size + sizeof(struct m_block), FALSE); + if (p != NULL) + { + /* Insert the block into the allocated block list, keeping it + sorted on address. */ + for (mp = &curbuf->b_block_head; (next = mp->mb_next) != NULL && next < p; mp = next) + ; + p->mb_next = next; /* link in block list */ + mp->mb_next = p; + p->mb_info.m_next = NULL; /* clear free list */ + p->mb_info.m_size = 0; + curbuf->b_mb_current = p; /* remember current block */ + curbuf->b_m_search = NULL; + p++; /* return usable memory */ + } + return (char_u *)p; +} + +/* + * free all allocated memory blocks for the buffer 'buf' + */ + void +u_blockfree(buf) + BUF *buf; +{ + struct m_block *p, *np; + + for (p = buf->b_block_head.mb_next; p != NULL; p = np) + { + np = p->mb_next; + vim_free(p); + } + buf->b_block_head.mb_next = NULL; + buf->b_m_search = NULL; + buf->b_mb_current = NULL; +} + +/* + * Free a chunk of memory. + * Insert the chunk into the correct free list, keeping it sorted on address. + */ + static void +u_free_line(ptr) + char_u *ptr; +{ + register info_t *next; + register info_t *prev, *curr; + register info_t *mp; + struct m_block *nextb; + + if (ptr == NULL || ptr == IObuff) + return; /* illegal address can happen in out-of-memory situations */ + + mp = (info_t *)(ptr - M_OFFSET); + + /* find block where chunk could be a part off */ + /* if we change curbuf->b_mb_current, curbuf->b_m_search is set to NULL */ + if (curbuf->b_mb_current == NULL || mp < (info_t *)curbuf->b_mb_current) + { + curbuf->b_mb_current = curbuf->b_block_head.mb_next; + curbuf->b_m_search = NULL; + } + if ((nextb = curbuf->b_mb_current->mb_next) != NULL && (info_t *)nextb < mp) + { + curbuf->b_mb_current = nextb; + curbuf->b_m_search = NULL; + } + while ((nextb = curbuf->b_mb_current->mb_next) != NULL && (info_t *)nextb < mp) + curbuf->b_mb_current = nextb; + + curr = NULL; + /* + * If mp is smaller than curbuf->b_m_search->m_next go to the start of + * the free list + */ + if (curbuf->b_m_search == NULL || mp < (curbuf->b_m_search->m_next)) + next = &(curbuf->b_mb_current->mb_info); + else + next = curbuf->b_m_search; + /* + * The following loop is executed very often. + * Therefore it has been optimized at the cost of readability. + * Keep it fast! + */ +#ifdef SLOW_BUT_EASY_TO_READ + do + { + prev = curr; + curr = next; + next = next->m_next; + } + while (mp > next && next != NULL); +#else + do /* first, middle, last */ + { + prev = next->m_next; /* curr, next, prev */ + if (prev == NULL || mp <= prev) + { + prev = curr; + curr = next; + next = next->m_next; + break; + } + curr = prev->m_next; /* next, prev, curr */ + if (curr == NULL || mp <= curr) + { + prev = next; + curr = prev->m_next; + next = curr->m_next; + break; + } + next = curr->m_next; /* prev, curr, next */ + } + while (mp > next && next != NULL); +#endif + +/* if *mp and *next are concatenated, join them into one chunk */ + if ((char_u *)mp + mp->m_size == (char_u *)next) + { + mp->m_size += next->m_size; + mp->m_next = next->m_next; + } + else + mp->m_next = next; + +/* if *curr and *mp are concatenated, join them */ + if (prev != NULL && (char_u *)curr + curr->m_size == (char_u *)mp) + { + curr->m_size += mp->m_size; + curr->m_next = mp->m_next; + curbuf->b_m_search = prev; + } + else + { + curr->m_next = mp; + curbuf->b_m_search = curr; /* put curbuf->b_m_search before freed chunk */ + } +} + +/* + * Allocate and initialize a new line structure with room for at least + * 'size' characters plus a terminating NUL. + */ + static char_u * +u_alloc_line(size) + register unsigned size; +{ + register info_t *mp, *mprev, *mp2; + struct m_block *mbp; + int size_align; + +/* + * Add room for size field and trailing NUL byte. + * Adjust for minimal size (must be able to store info_t + * plus a trailing NUL, so the chunk can be released again) + */ + size += M_OFFSET + 1; + if (size < sizeof(info_t) + 1) + size = sizeof(info_t) + 1; + +/* + * round size up for alignment + */ + size_align = (size + ALIGN_MASK) & ~ALIGN_MASK; + +/* + * If curbuf->b_m_search is NULL (uninitialized free list) start at + * curbuf->b_block_head + */ + if (curbuf->b_mb_current == NULL || curbuf->b_m_search == NULL) + { + curbuf->b_mb_current = &curbuf->b_block_head; + curbuf->b_m_search = &(curbuf->b_block_head.mb_info); + } + +/* search for space in free list */ + mprev = curbuf->b_m_search; + mbp = curbuf->b_mb_current; + mp = curbuf->b_m_search->m_next; + if (mp == NULL) + { + if (mbp->mb_next) + mbp = mbp->mb_next; + else + mbp = &curbuf->b_block_head; + mp = curbuf->b_m_search = &(mbp->mb_info); + } + while (mp->m_size < size) + { + if (mp == curbuf->b_m_search) /* back where we started in free chunk list */ + { + if (mbp->mb_next) + mbp = mbp->mb_next; + else + mbp = &curbuf->b_block_head; + mp = curbuf->b_m_search = &(mbp->mb_info); + if (mbp == curbuf->b_mb_current) /* back where we started in block list */ + { + int n = (size_align > (MEMBLOCKSIZE / 4) ? size_align : MEMBLOCKSIZE); + + mp = (info_t *)u_blockalloc((long_u)n); + if (mp == NULL) + return (NULL); + mp->m_size = n; + u_free_line((char_u *)mp + M_OFFSET); + mp = curbuf->b_m_search; + mbp = curbuf->b_mb_current; + } + } + mprev = mp; + if ((mp = mp->m_next) == NULL) /* at end of the list */ + mp = &(mbp->mb_info); /* wrap around to begin */ + } + +/* if the chunk we found is large enough, split it up in two */ + if ((long)mp->m_size - size_align >= (long)(sizeof(info_t) + 1)) + { + mp2 = (info_t *)((char_u *)mp + size_align); + mp2->m_size = mp->m_size - size_align; + mp2->m_next = mp->m_next; + mprev->m_next = mp2; + mp->m_size = size_align; + } + else /* remove *mp from the free list */ + { + mprev->m_next = mp->m_next; + } + curbuf->b_m_search = mprev; + curbuf->b_mb_current = mbp; + + mp = (info_t *)((char_u *)mp + M_OFFSET); + *(char_u *)mp = NUL; /* set the first byte to NUL */ + + return ((char_u *)mp); +} + +/* + * u_save_line(): allocate memory with u_alloc_line() and copy line 'lnum' into it. + */ + static char_u * +u_save_line(lnum) + linenr_t lnum; +{ + register char_u *src; + register char_u *dst; + register unsigned len; + + src = ml_get(lnum); + len = STRLEN(src); + if ((dst = u_alloc_line(len)) != NULL) + vim_memmove(dst, src, (size_t)(len + 1)); + return (dst); +} diff --git a/usr.bin/vim/unix.c b/usr.bin/vim/unix.c new file mode 100644 index 00000000000..db568956613 --- /dev/null +++ b/usr.bin/vim/unix.c @@ -0,0 +1,2883 @@ +/* $OpenBSD: unix.c,v 1.1.1.1 1996/09/07 21:40:24 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * OS/2 port by Paul Slootman + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * unix.c -- code for all flavors of Unix (BSD, SYSV, SVR4, POSIX, ...) + * Also for OS/2, using the excellent EMX package!!! + * + * A lot of this file was originally written by Juergen Weigert and later + * changed beyond recognition. + */ + +/* + * Some systems have a prototype for select() that has (int *) instead of + * (fd_set *), which is wrong. This define removes that prototype. We include + * our own prototype in osdef.h. + */ +#define select select_declared_wrong + +#include "vim.h" +#include "globals.h" +#include "option.h" +#include "proto.h" + +#ifdef HAVE_FCNTL_H +# include +#endif + +#include "unixunix.h" /* unix includes for unix.c only */ + +/* + * Use this prototype for select, some include files have a wrong prototype + */ +#undef select + +#if defined(HAVE_SELECT) +extern int select __ARGS((int, fd_set *, fd_set *, fd_set *, struct timeval *)); +#endif + +/* + * end of autoconf section. To be extended... + */ + +/* Are the following #ifdefs still required? And why? Is that for X11? */ + +#if defined(ESIX) || defined(M_UNIX) && !defined(SCO) +# ifdef SIGWINCH +# undef SIGWINCH +# endif +# ifdef TIOCGWINSZ +# undef TIOCGWINSZ +# endif +#endif + +#if defined(SIGWINDOW) && !defined(SIGWINCH) /* hpux 9.01 has it */ +# define SIGWINCH SIGWINDOW +#endif + +#if defined(HAVE_X11) && defined(WANT_X11) +# include +# include +# include + +Window x11_window = 0; +Display *x11_display = NULL; +int got_x_error = FALSE; + +static int get_x11_windis __ARGS((void)); +static void set_x11_title __ARGS((char_u *)); +static void set_x11_icon __ARGS((char_u *)); +#endif + +static int get_x11_title __ARGS((int)); +static int get_x11_icon __ARGS((int)); + +static void may_core_dump __ARGS((void)); + +static int Read __ARGS((char_u *, long)); +static int WaitForChar __ARGS((long)); +static int RealWaitForChar __ARGS((int, long)); +static void fill_inbuf __ARGS((int)); + +#if defined(SIGWINCH) +static RETSIGTYPE sig_winch __ARGS(SIGPROTOARG); +#endif +#if defined(SIGALRM) && defined(HAVE_X11) && defined(WANT_X11) +static RETSIGTYPE sig_alarm __ARGS(SIGPROTOARG); +#endif +static RETSIGTYPE deathtrap __ARGS(SIGPROTOARG); + +static void catch_signals __ARGS((RETSIGTYPE (*func)())); +#ifndef __EMX__ +static int have_wildcard __ARGS((int, char_u **)); +static int have_dollars __ARGS((int, char_u **)); +#endif + +static int do_resize = FALSE; +static char_u *oldtitle = NULL; +static char_u *fixedtitle = (char_u *)"Thanks for flying Vim"; +static char_u *oldicon = NULL; +#ifndef __EMX__ +static char_u *extra_shell_arg = NULL; +static int show_shell_mess = TRUE; +#endif +static int core_dump = FALSE; /* core dump in mch_windexit() */ + +#ifdef SYS_SIGLIST_DECLARED +/* + * I have seen + * extern char *_sys_siglist[NSIG]; + * on Irix, Linux, NetBSD and Solaris. It contains a nice list of strings + * that describe the signals. That is nearly what we want here. But + * autoconf does only check for sys_siglist (without the underscore), I + * do not want to change everything today.... jw. + * This is why AC_DECL_SYS_SIGLIST is commented out in configure.in + */ +#endif + +static struct +{ + int sig; /* Signal number, eg. SIGSEGV etc */ + char *name; /* Signal name (not char_u!). */ + int dump; /* Should this signal cause a core dump? */ +} signal_info[] = +{ +#ifdef SIGHUP + {SIGHUP, "HUP", FALSE}, +#endif +#ifdef SIGINT + {SIGINT, "INT", FALSE}, +#endif +#ifdef SIGQUIT + {SIGQUIT, "QUIT", TRUE}, +#endif +#ifdef SIGILL + {SIGILL, "ILL", TRUE}, +#endif +#ifdef SIGTRAP + {SIGTRAP, "TRAP", TRUE}, +#endif +#ifdef SIGABRT + {SIGABRT, "ABRT", TRUE}, +#endif +#ifdef SIGEMT + {SIGEMT, "EMT", TRUE}, +#endif +#ifdef SIGFPE + {SIGFPE, "FPE", TRUE}, +#endif +#ifdef SIGBUS + {SIGBUS, "BUS", TRUE}, +#endif +#ifdef SIGSEGV + {SIGSEGV, "SEGV", TRUE}, +#endif +#ifdef SIGSYS + {SIGSYS, "SYS", TRUE}, +#endif +#ifdef SIGALRM + {SIGALRM, "ALRM", FALSE}, +#endif +#ifdef SIGTERM + {SIGTERM, "TERM", FALSE}, +#endif +#ifdef SIGVTALRM + {SIGVTALRM, "VTALRM", FALSE}, +#endif +#ifdef SIGPROF + {SIGPROF, "PROF", FALSE}, +#endif +#ifdef SIGXCPU + {SIGXCPU, "XCPU", TRUE}, +#endif +#ifdef SIGXFSZ + {SIGXFSZ, "XFSZ", TRUE}, +#endif +#ifdef SIGUSR1 + {SIGUSR1, "USR1", FALSE}, +#endif +#ifdef SIGUSR2 + {SIGUSR2, "USR2", FALSE}, +#endif + {-1, "Unknown!", -1} +}; + + void +mch_write(s, len) + char_u *s; + int len; +{ +#ifdef USE_GUI + if (gui.in_use && !gui.dying) + { + gui_write(s, len); + if (p_wd) + gui_mch_wait_for_chars(p_wd); + } + else +#endif + { + write(1, (char *)s, len); + if (p_wd) /* Unix is too fast, slow down a bit more */ + RealWaitForChar(0, p_wd); + } +} + +/* + * mch_inchar(): low level input funcion. + * Get a characters from the keyboard. + * Return the number of characters that are available. + * If wtime == 0 do not wait for characters. + * If wtime == n wait a short time for characters. + * If wtime == -1 wait forever for characters. + */ + int +mch_inchar(buf, maxlen, wtime) + char_u *buf; + int maxlen; + long wtime; /* don't use "time", MIPS cannot handle it */ +{ + int len; + +#ifdef USE_GUI + if (gui.in_use) + { + if (!gui_mch_wait_for_chars(wtime)) + return 0; + return Read(buf, (long)maxlen); + } +#endif + + if (wtime >= 0) + { + while (WaitForChar(wtime) == 0) /* no character available */ + { + if (!do_resize) /* return if not interrupted by resize */ + return 0; + set_winsize(0, 0, FALSE); + do_resize = FALSE; + } + } + else /* wtime == -1 */ + { + /* + * If there is no character available within 'updatetime' seconds + * flush all the swap files to disk + * Also done when interrupted by SIGWINCH. + */ + if (WaitForChar(p_ut) == 0) + updatescript(0); + } + + for (;;) /* repeat until we got a character */ + { + if (do_resize) /* window changed size */ + { + set_winsize(0, 0, FALSE); + do_resize = FALSE; + } + /* + * we want to be interrupted by the winch signal + */ + WaitForChar(-1L); + if (do_resize) /* interrupted by SIGWINCHsignal */ + continue; + + /* + * For some terminals we only get one character at a time. + * We want the get all available characters, so we could keep on + * trying until none is available + * For some other terminals this is quite slow, that's why we don't do + * it. + */ + len = Read(buf, (long)maxlen); + if (len > 0) + { +#ifdef OS2 + int i; + + for (i = 0; i < len; i++) + if (buf[i] == 0) + buf[i] = K_NUL; +#endif + return len; + } + } +} + +/* + * return non-zero if a character is available + */ + int +mch_char_avail() +{ +#ifdef USE_GUI + if (gui.in_use) + { + gui_mch_update(); + return !is_input_buf_empty(); + } +#endif + return WaitForChar(0L); +} + + long +mch_avail_mem(special) + int special; +{ +#ifdef __EMX__ + return ulimit(3, 0L); /* always 32MB? */ +#else + return 0x7fffffff; /* virtual memory eh */ +#endif +} + + void +mch_delay(msec, ignoreinput) + long msec; + int ignoreinput; +{ + if (ignoreinput) +#ifndef HAVE_SELECT + poll(NULL, 0, (int)msec); +#else +# ifdef __EMX__ + _sleep2(msec); +# else + { + struct timeval tv; + + tv.tv_sec = msec / 1000; + tv.tv_usec = (msec % 1000) * 1000; + select(0, NULL, NULL, NULL, &tv); + } +# endif /* __EMX__ */ +#endif /* HAVE_SELECT */ + else +#ifdef USE_GUI + if (gui.in_use) + gui_mch_wait_for_chars(msec); + else +#endif + WaitForChar(msec); +} + +#if defined(SIGWINCH) +/* + * We need correct potatotypes, otherwise mean compilers will barf when the + * second argument to signal() is ``wrong''. + * Let me try it with a few tricky defines from my own osdef.h (jw). + */ + static RETSIGTYPE +sig_winch SIGDEFARG(sigarg) +{ + /* this is not required on all systems, but it doesn't hurt anybody */ + signal(SIGWINCH, (RETSIGTYPE (*)())sig_winch); + do_resize = TRUE; + SIGRETURN; +} +#endif + +#if defined(SIGALRM) && defined(HAVE_X11) && defined(WANT_X11) +/* + * signal function for alarm(). + */ + static RETSIGTYPE +sig_alarm SIGDEFARG(sigarg) +{ + /* doesn't do anything, just to break a system call */ + SIGRETURN; +} +#endif + + void +mch_resize() +{ + do_resize = TRUE; +} + +/* + * This function handles deadly signals. + * It tries to preserve any swap file and exit properly. + * (partly from Elvis). + */ + static RETSIGTYPE +deathtrap SIGDEFARG(sigarg) +{ + static int entered = 0; +#ifdef SIGHASARG + int i; + + for (i = 0; signal_info[i].dump != -1; i++) + { + if (sigarg == signal_info[i].sig) + { + if (signal_info[i].dump) + core_dump = TRUE; + break; + } + } +#endif + + /* + * If something goes wrong after entering here, we may get here again. + * When this happens, give a message and try to exit nicely (resetting the + * terminal mode, etc.) + * When this happens twice, just exit, don't even try to give a message, + * stack may be corrupt or something weird. + */ + if (entered == 2) + { + may_core_dump(); + exit(7); + } + if (entered) + { + OUTSTR("Vim: Double signal, exiting\n"); + flushbuf(); + getout(1); + } + ++entered; + + sprintf((char *)IObuff, "Vim: Caught %s %s\n", +#ifdef SIGHASARG + "deadly signal", signal_info[i].name); +#else + "some", "deadly signal"); +#endif + + preserve_exit(); /* preserve files and exit */ + + SIGRETURN; +} + +/* + * If the machine has job control, use it to suspend the program, + * otherwise fake it by starting a new shell. + * When running the GUI iconify the window. + */ + void +mch_suspend() +{ +#ifdef USE_GUI + if (gui.in_use) + { + gui_mch_iconify(); + return; + } +#endif +#ifdef SIGTSTP + flushbuf(); /* needed to make cursor visible on some systems */ + settmode(0); + flushbuf(); /* needed to disable mouse on some systems */ + kill(0, SIGTSTP); /* send ourselves a STOP signal */ + + /* + * Set oldtitle to NULL, so the current title is obtained again. + */ + if (oldtitle != fixedtitle) + { + vim_free(oldtitle); + oldtitle = NULL; + } + settmode(1); +#else + MSG_OUTSTR("new shell started\n"); + (void)call_shell(NULL, SHELL_COOKED); +#endif + need_check_timestamps = TRUE; +} + + void +mch_windinit() +{ + Columns = 80; + Rows = 24; + + flushbuf(); + + (void)mch_get_winsize(); + +#if defined(SIGWINCH) + /* + * WINDOW CHANGE signal is handled with sig_winch(). + */ + signal(SIGWINCH, (RETSIGTYPE (*)())sig_winch); +#endif + + /* + * We want the STOP signal to work, to make mch_suspend() work + */ +#ifdef SIGTSTP + signal(SIGTSTP, SIG_DFL); +#endif + + /* + * We want to ignore breaking of PIPEs. + */ +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif + + /* + * Arrange for other signals to gracefully shutdown Vim. + */ + catch_signals(deathtrap); +} + + static void +catch_signals(func) + RETSIGTYPE (*func)(); +{ + int i; + + for (i = 0; signal_info[i].dump != -1; i++) + signal(signal_info[i].sig, func); +} + + void +reset_signals() +{ + catch_signals(SIG_DFL); +} + +/* + * Check_win checks whether we have an interactive window. + */ + int +mch_check_win(argc, argv) + int argc; + char **argv; +{ + if (isatty(1)) + return OK; + return FAIL; +} + + int +mch_check_input() +{ + if (isatty(0)) + return OK; + return FAIL; +} + +#if defined(HAVE_X11) && defined(WANT_X11) +/* + * X Error handler, otherwise X just exits! (very rude) -- webb + */ + static int +x_error_handler(dpy, error_event) + Display *dpy; + XErrorEvent *error_event; +{ + XGetErrorText(dpy, error_event->error_code, (char *)IObuff, IOSIZE); + STRCAT(IObuff, "\nVim: Got X error\n"); + +#if 1 + preserve_exit(); /* preserve files and exit */ +#else + printf(IObuff); /* print error message and continue */ + /* Makes my system hang */ +#endif + + return 0; /* NOTREACHED */ +} + +/* + * Another X Error handler, just used to check for errors. + */ + static int +x_error_check(dpy, error_event) + Display *dpy; + XErrorEvent *error_event; +{ + got_x_error = TRUE; + return 0; +} + +/* + * try to get x11 window and display + * + * return FAIL for failure, OK otherwise + */ + static int +get_x11_windis() +{ + char *winid; + XTextProperty text_prop; + int (*old_handler)(); + static int result = -1; + static int x11_display_opened_here = FALSE; + + /* X just exits if it finds an error otherwise! */ + XSetErrorHandler(x_error_handler); + +#ifdef USE_GUI_X11 + if (gui.in_use) + { + /* + * If the X11 display was opened here before, for the window where Vim + * was started, close that one now to avoid a memory leak. + */ + if (x11_display_opened_here && x11_display != NULL) + { + XCloseDisplay(x11_display); + x11_display = NULL; + x11_display_opened_here = FALSE; + } + return gui_get_x11_windis(&x11_window, &x11_display); + } +#endif + + if (result != -1) /* Have already been here and set this */ + return result; /* Don't do all these X calls again */ + + /* + * If WINDOWID not set, should try another method to find out + * what the current window number is. The only code I know for + * this is very complicated. + * We assume that zero is invalid for WINDOWID. + */ + if (x11_window == 0 && (winid = getenv("WINDOWID")) != NULL) + x11_window = (Window)atol(winid); + if (x11_window != 0 && x11_display == NULL) + { +#ifdef SIGALRM + RETSIGTYPE (*sig_save)(); + + /* + * Opening the Display may hang if the DISPLAY setting is wrong, or + * the network connection is bad. Set an alarm timer to get out. + */ + sig_save = (RETSIGTYPE (*)())signal(SIGALRM, + (RETSIGTYPE (*)())sig_alarm); + alarm(2); +#endif + x11_display = XOpenDisplay(NULL); +#ifdef SIGALRM + alarm(0); + signal(SIGALRM, (RETSIGTYPE (*)())sig_save); +#endif + if (x11_display != NULL) + { + /* + * Try to get the window title. I don't actually want it yet, so + * there may be a simpler call to use, but this will cause the + * error handler x_error_check() to be called if anything is wrong, + * such as the window pointer being invalid (as can happen when the + * user changes his DISPLAY, but not his WINDOWID) -- webb + */ + old_handler = XSetErrorHandler(x_error_check); + got_x_error = FALSE; + if (XGetWMName(x11_display, x11_window, &text_prop)) + XFree((void *)text_prop.value); + XSync(x11_display, False); + if (got_x_error) + { + /* Maybe window id is bad */ + x11_window = 0; + XCloseDisplay(x11_display); + x11_display = NULL; + } + else + x11_display_opened_here = TRUE; + XSetErrorHandler(old_handler); + } + } + if (x11_window == 0 || x11_display == NULL) + return (result = FAIL); + return (result = OK); +} + +/* + * Determine original x11 Window Title + */ + static int +get_x11_title(test_only) + int test_only; +{ + XTextProperty text_prop; + int retval = FALSE; + + if (get_x11_windis() == OK) + { + /* Get window name if any */ + if (XGetWMName(x11_display, x11_window, &text_prop)) + { + if (text_prop.value != NULL) + { + retval = TRUE; + if (!test_only) + oldtitle = strsave((char_u *)text_prop.value); + } + XFree((void *)text_prop.value); + } + } + if (oldtitle == NULL && !test_only) /* could not get old title */ + oldtitle = fixedtitle; + + return retval; +} + +/* + * Determine original x11 Window icon + */ + + static int +get_x11_icon(test_only) + int test_only; +{ + XTextProperty text_prop; + int retval = FALSE; + + if (get_x11_windis() == OK) + { + /* Get icon name if any */ + if (XGetWMIconName(x11_display, x11_window, &text_prop)) + { + if (text_prop.value != NULL) + { + retval = TRUE; + if (!test_only) + oldicon = strsave((char_u *)text_prop.value); + } + XFree((void *)text_prop.value); + } + } + + /* could not get old icon, use terminal name */ + if (oldicon == NULL && !test_only) + { + if (STRNCMP(term_strings[KS_NAME], "builtin_", 8) == 0) + oldicon = term_strings[KS_NAME] + 8; + else + oldicon = term_strings[KS_NAME]; + } + + return retval; +} + +/* + * Set x11 Window Title + * + * get_x11_windis() must be called before this and have returned OK + */ + static void +set_x11_title(title) + char_u *title; +{ +#if XtSpecificationRelease >= 4 + XTextProperty text_prop; + + text_prop.value = title; + text_prop.nitems = STRLEN(title); + text_prop.encoding = XA_STRING; + text_prop.format = 8; + XSetWMName(x11_display, x11_window, &text_prop); +#else + XStoreName(x11_display, x11_window, (char *)title); +#endif + XFlush(x11_display); +} + +/* + * Set x11 Window icon + * + * get_x11_windis() must be called before this and have returned OK + */ + static void +set_x11_icon(icon) + char_u *icon; +{ +#if XtSpecificationRelease >= 4 + XTextProperty text_prop; + + text_prop.value = icon; + text_prop.nitems = STRLEN(icon); + text_prop.encoding = XA_STRING; + text_prop.format = 8; + XSetWMIconName(x11_display, x11_window, &text_prop); +#else + XSetIconName(x11_display, x11_window, (char *)icon); +#endif + XFlush(x11_display); +} + +#else /* HAVE_X11 && WANT_X11 */ + + static int +get_x11_title(test_only) + int test_only; +{ + if (!test_only) + oldtitle = fixedtitle; + return FALSE; +} + + static int +get_x11_icon(test_only) + int test_only; +{ + if (!test_only) + { + if (STRNCMP(term_strings[KS_NAME], "builtin_", 8) == 0) + oldicon = term_strings[KS_NAME] + 8; + else + oldicon = term_strings[KS_NAME]; + } + return FALSE; +} + +#endif /* HAVE_X11 && WANT_X11 */ + + int +mch_can_restore_title() +{ +#ifdef USE_GUI + /* + * If GUI is (going to be) used, we can always set the window title. + * Saves a bit of time, because the X11 display server does not need to be + * contacted. + */ + if (gui.starting || gui.in_use) + return TRUE; +#endif + return get_x11_title(TRUE); +} + + int +mch_can_restore_icon() +{ +#ifdef USE_GUI + /* + * If GUI is (going to be) used, we can always set the icon name. + * Saves a bit of time, because the X11 display server does not need to be + * contacted. + */ + if (gui.starting || gui.in_use) + return TRUE; +#endif + return get_x11_icon(TRUE); +} + +/* + * Set the window title and icon. + * Currently only works for x11. + */ + void +mch_settitle(title, icon) + char_u *title; + char_u *icon; +{ + int type = 0; + + if (term_strings[KS_NAME] == NULL) /* no terminal name (yet) */ + return; + if (title == NULL && icon == NULL) /* nothing to do */ + return; + +/* + * if the window ID and the display is known, we may use X11 calls + */ +#if defined(HAVE_X11) && defined(WANT_X11) + if (get_x11_windis() == OK) + type = 1; +#endif + + /* + * Note: if terminal is xterm, title is set with escape sequence rather + * than x11 calls, because the x11 calls don't always work + * Check only if the start of the terminal name is "xterm", also catch + * "xterms". + */ + if (is_xterm(term_strings[KS_NAME])) + type = 2; + + if (is_iris_ansi(term_strings[KS_NAME])) + type = 3; + + if (type) + { + if (title != NULL) + { + if (oldtitle == NULL) /* first call, save title */ + (void)get_x11_title(FALSE); + + switch(type) + { +#if defined(HAVE_X11) && defined(WANT_X11) + case 1: set_x11_title(title); /* x11 */ + break; +#endif + case 2: outstrn((char_u *)"\033]2;"); /* xterm */ + outstrn(title); + outchar(Ctrl('G')); + flushbuf(); + break; + + case 3: outstrn((char_u *)"\033P1.y"); /* iris-ansi */ + outstrn(title); + outstrn((char_u *)"\234"); + flushbuf(); + break; + } + } + + if (icon != NULL) + { + if (oldicon == NULL) /* first call, save icon */ + get_x11_icon(FALSE); + + switch(type) + { +#if defined(HAVE_X11) && defined(WANT_X11) + case 1: set_x11_icon(icon); /* x11 */ + break; +#endif + case 2: outstrn((char_u *)"\033]1;"); /* xterm */ + outstrn(icon); + outchar(Ctrl('G')); + flushbuf(); + break; + + case 3: outstrn((char_u *)"\033P3.y"); /* iris-ansi */ + outstrn(icon); + outstrn((char_u *)"\234"); + flushbuf(); + break; + } + } + } +} + + int +is_xterm(name) + char_u *name; +{ + if (name == NULL) + return FALSE; + return (vim_strnicmp(name, (char_u *)"xterm", (size_t)5) == 0 || + STRCMP(name, "builtin_xterm") == 0); +} + + int +is_iris_ansi(name) + char_u *name; +{ + if (name == NULL) + return FALSE; + return (vim_strnicmp(name, (char_u *)"iris-ansi", (size_t)9) == 0 || + STRCMP(name, "builtin_iris-ansi") == 0); +} + +/* + * Return TRUE if "name" is a terminal for which 'ttyfast' should be set. + * This should include all windowed terminal emulators. + */ + int +is_fastterm(name) + char_u *name; +{ + if (name == NULL) + return FALSE; + if (is_xterm(name) || is_iris_ansi(name)) + return TRUE; + return (vim_strnicmp(name, (char_u *)"hpterm", (size_t)6) == 0 || + vim_strnicmp(name, (char_u *)"sun-cmd", (size_t)7) == 0 || + vim_strnicmp(name, (char_u *)"screen", (size_t)6) == 0 || + vim_strnicmp(name, (char_u *)"dtterm", (size_t)6) == 0); +} + +/* + * Restore the window/icon title. + * which is one of: + * 1 Just restore title + * 2 Just restore icon + * 3 Restore title and icon + */ + void +mch_restore_title(which) + int which; +{ + mch_settitle((which & 1) ? oldtitle : NULL, (which & 2) ? oldicon : NULL); +} + +/* + * Insert user name in s[len]. + * Return OK if a name found. + */ + int +mch_get_user_name(s, len) + char_u *s; + int len; +{ +#if defined(HAVE_PWD_H) && defined(HAVE_GETPWUID) + struct passwd *pw; +#endif + uid_t uid; + + uid = getuid(); +#if defined(HAVE_PWD_H) && defined(HAVE_GETPWUID) + if ((pw = getpwuid(uid)) != NULL && + pw->pw_name != NULL && *pw->pw_name != NUL) + { + STRNCPY(s, pw->pw_name, len); + return OK; + } +#endif + sprintf((char *)s, "%d", (int)uid); /* assumes s is long enough */ + return FAIL; /* a number is not a name */ +} + +/* + * Insert host name is s[len]. + */ + +#ifdef HAVE_SYS_UTSNAME_H + void +mch_get_host_name(s, len) + char_u *s; + int len; +{ + struct utsname vutsname; + + uname(&vutsname); + STRNCPY(s, vutsname.nodename, len); +} +#else /* HAVE_SYS_UTSNAME_H */ + +# ifdef HAVE_SYS_SYSTEMINFO_H +# define gethostname(nam, len) sysinfo(SI_HOSTNAME, nam, len) +# endif + + void +mch_get_host_name(s, len) + char_u *s; + int len; +{ + gethostname((char *)s, len); +} +#endif /* HAVE_SYS_UTSNAME_H */ + +/* + * return process ID + */ + long +mch_get_pid() +{ + return (long)getpid(); +} + +#if !defined(HAVE_STRERROR) && defined(USE_GETCWD) +static char *strerror __ARGS((int)); + + static char * +strerror(err) + int err; +{ + extern int sys_nerr; + extern char *sys_errlist[]; + static char er[20]; + + if (err > 0 && err < sys_nerr) + return (sys_errlist[err]); + sprintf(er, "Error %d", err); + return er; +} +#endif + +/* + * Get name of current directory into buffer 'buf' of length 'len' bytes. + * Return OK for success, FAIL for failure. + */ + int +mch_dirname(buf, len) + char_u *buf; + int len; +{ +#if defined(USE_GETCWD) + if (getcwd((char *)buf, len) == NULL) + { + STRCPY(buf, strerror(errno)); + return FAIL; + } + return OK; +#else + return (getwd((char *)buf) != NULL ? OK : FAIL); +#endif +} + +#ifdef __EMX__ +/* + * Replace all slashes by backslashes. + */ + static void +slash_adjust(p) + char_u *p; +{ + while (*p) + { + if (*p == '/') + *p = '\\'; + ++p; + } +} +#endif + +/* + * Get absolute filename into buffer 'buf' of length 'len' bytes. + * + * return FAIL for failure, OK for success + */ + int +FullName(fname, buf, len, force) + char_u *fname, *buf; + int len; + int force; /* also expand when already absolute path name */ +{ + int l; +#ifdef OS2 + int only_drive; /* only a drive letter is specified in file name */ +#endif +#ifdef HAVE_FCHDIR + int fd = -1; + static int dont_fchdir = FALSE; /* TRUE when fchdir() doesn't work */ +#endif + char_u olddir[MAXPATHL]; + char_u *p; + char_u c; + int retval = OK; + + if (fname == NULL) /* always fail */ + { + *buf = NUL; + return FAIL; + } + + *buf = 0; + if (force || !isFullName(fname)) /* if forced or not an absolute path */ + { + /* + * If the file name has a path, change to that directory for a moment, + * and then do the getwd() (and get back to where we were). + * This will get the correct path name with "../" things. + */ +#ifdef OS2 + only_drive = 0; + if (((p = vim_strrchr(fname, '/')) != NULL) || + ((p = vim_strrchr(fname, '\\')) != NULL) || + (((p = vim_strchr(fname, ':')) != NULL) && ++only_drive)) +#else + if ((p = vim_strrchr(fname, '/')) != NULL) +#endif + { +#ifdef HAVE_FCHDIR + /* + * Use fchdir() if possible, it's said to be faster and more + * reliable. But on SunOS 4 it might not work. Check this by + * doing a fchdir() right now. + */ + if (!dont_fchdir) + { + fd = open(".", O_RDONLY | O_EXTRA); + if (fd >= 0 && fchdir(fd) < 0) + { + close(fd); + fd = -1; + dont_fchdir = TRUE; /* don't try again */ + } + } +#endif + if ( +#ifdef HAVE_FCHDIR + fd < 0 && +#endif + mch_dirname(olddir, MAXPATHL) == FAIL) + { + p = NULL; /* can't get current dir: don't chdir */ + retval = FAIL; + } + else + { +#ifdef OS2 + /* + * compensate for case where ':' from "D:" was the only + * path separator detected in the file name; the _next_ + * character has to be removed, and then restored later. + */ + if (only_drive) + p++; +#endif + c = *p; + *p = NUL; + if (vim_chdir((char *)fname)) + retval = FAIL; + else + fname = p + 1; + *p = c; +#ifdef OS2 + if (only_drive) + { + p--; + if (retval != FAIL) + fname--; + } +#endif + } + } + if (mch_dirname(buf, len) == FAIL) + { + retval = FAIL; + *buf = NUL; + } + l = STRLEN(buf); + if (l && buf[l - 1] != '/') + STRCAT(buf, "/"); + if (p != NULL) + { +#ifdef HAVE_FCHDIR + if (fd >= 0) + { + fchdir(fd); + close(fd); + } + else +#endif + vim_chdir((char *)olddir); + } + } + STRCAT(buf, fname); +#ifdef OS2 + slash_adjust(buf); +#endif + return retval; +} + +/* + * return TRUE is fname is an absolute path name + */ + int +isFullName(fname) + char_u *fname; +{ +#ifdef __EMX__ + return _fnisabs(fname); +#else + return (*fname == '/' || *fname == '~'); +#endif +} + +/* + * get file permissions for 'name' + */ + long +getperm(name) + char_u *name; +{ + struct stat statb; + + if (stat((char *)name, &statb)) + return -1; + return statb.st_mode; +} + +/* + * set file permission for 'name' to 'perm' + * + * return FAIL for failure, OK otherwise + */ + int +setperm(name, perm) + char_u *name; + int perm; +{ + return (chmod((char *)name, (mode_t)perm) == 0 ? OK : FAIL); +} + +/* + * return TRUE if "name" is a directory + * return FALSE if "name" is not a directory + * return FALSE for error + */ + int +mch_isdir(name) + char_u *name; +{ + struct stat statb; + + if (stat((char *)name, &statb)) + return FALSE; +#ifdef _POSIX_SOURCE + return (S_ISDIR(statb.st_mode) ? TRUE : FALSE); +#else + return ((statb.st_mode & S_IFMT) == S_IFDIR ? TRUE : FALSE); +#endif +} + + void +mch_windexit(r) + int r; +{ + settmode(0); + exiting = TRUE; + mch_settitle(oldtitle, oldicon); /* restore xterm title */ + stoptermcap(); + flushbuf(); + ml_close_all(TRUE); /* remove all memfiles */ + may_core_dump(); + exit(r); +} + + static void +may_core_dump() +{ +#ifdef SIGQUIT + signal(SIGQUIT, SIG_DFL); + if (core_dump) + kill(getpid(), SIGQUIT); /* force a core dump */ +#endif +} + +static int curr_tmode = 0; /* contains current raw/cooked mode (0 = cooked) */ + + void +mch_settmode(raw) + int raw; +{ + static int first = TRUE; + + /* Why is NeXT excluded here (and not in unixunix.h)? */ +#if defined(ECHOE) && defined(ICANON) && (defined(HAVE_TERMIO_H) || defined(HAVE_TERMIOS_H)) && !defined(__NeXT__) + /* for "new" tty systems */ +# ifdef HAVE_TERMIOS_H + static struct termios told; + struct termios tnew; +# else + static struct termio told; + struct termio tnew; +# endif + +# ifdef TIOCLGET + static unsigned long tty_local; +# endif + + if (raw) + { + if (first) + { + first = FALSE; +# ifdef TIOCLGET + ioctl(0, TIOCLGET, &tty_local); +# endif +# if defined(HAVE_TERMIOS_H) + tcgetattr(0, &told); +# else + ioctl(0, TCGETA, &told); +# endif + } + tnew = told; + /* + * ICRNL enables typing ^V^M + */ + tnew.c_iflag &= ~ICRNL; + tnew.c_lflag &= ~(ICANON | ECHO | ISIG | ECHOE +# if defined(IEXTEN) && !defined(MINT) + | IEXTEN /* IEXTEN enables typing ^V on SOLARIS */ + /* but it breaks function keys on MINT */ +# endif + ); + tnew.c_cc[VMIN] = 1; /* return after 1 char */ + tnew.c_cc[VTIME] = 0; /* don't wait */ +# if defined(HAVE_TERMIOS_H) + tcsetattr(0, TCSANOW, &tnew); +# else + ioctl(0, TCSETA, &tnew); +# endif + } + else + { +# if defined(HAVE_TERMIOS_H) + tcsetattr(0, TCSANOW, &told); +# else + ioctl(0, TCSETA, &told); +# endif +# ifdef TIOCLGET + ioctl(0, TIOCLSET, &tty_local); +# endif + } +#else +# ifndef TIOCSETN +# define TIOCSETN TIOCSETP /* for hpux 9.0 */ +# endif + /* for "old" tty systems */ + static struct sgttyb ttybold; + struct sgttyb ttybnew; + + if (raw) + { + if (first) + { + first = FALSE; + ioctl(0, TIOCGETP, &ttybold); + } + ttybnew = ttybold; + ttybnew.sg_flags &= ~(CRMOD | ECHO); + ttybnew.sg_flags |= RAW; + ioctl(0, TIOCSETN, &ttybnew); + } + else + ioctl(0, TIOCSETN, &ttybold); +#endif + curr_tmode = raw; +} + +/* + * Try to get the code for "t_kb" from the stty setting + * + * Even if termcap claims a backspace key, the user's setting *should* + * prevail. stty knows more about reality than termcap does, and if + * somebody's usual erase key is DEL (which, for most BSD users, it will + * be), they're going to get really annoyed if their erase key starts + * doing forward deletes for no reason. (Eric Fischer) + */ + void +get_stty() +{ + char_u buf[2]; + char_u *p; + + /* Why is NeXT excluded here (and not in unixunix.h)? */ +#if defined(ECHOE) && defined(ICANON) && (defined(HAVE_TERMIO_H) || defined(HAVE_TERMIOS_H)) && !defined(__NeXT__) + /* for "new" tty systems */ +# ifdef HAVE_TERMIOS_H + struct termios keys; +# else + struct termio keys; +# endif + +# if defined(HAVE_TERMIOS_H) + if (tcgetattr(0, &keys) != -1) +# else + if (ioctl(0, TCGETA, &keys) != -1) +# endif + { + buf[0] = keys.c_cc[VERASE]; +#else + /* for "old" tty systems */ + struct sgttyb keys; + + if (ioctl(0, TIOCGETP, &keys) != -1) + { + buf[0] = keys.sg_erase; +#endif + buf[1] = NUL; + add_termcode((char_u *)"kb", buf); + + /* + * If and are now the same, redefine . + */ + p = find_termcode((char_u *)"kD"); + if (p != NULL && p[0] == buf[0] && p[1] == buf[1]) + do_fixdel(); + } +#if 0 + } /* to keep cindent happy */ +#endif +} + +#ifdef USE_MOUSE +/* + * set mouse clicks on or off (only works for xterms) + */ + void +mch_setmouse(on) + int on; +{ + static int ison = FALSE; + + if (on == ison) /* return quickly if nothing to do */ + return; + + if (is_xterm(term_strings[KS_NAME])) + { + if (on) + outstrn((char_u *)"\033[?1000h"); /* xterm: enable mouse events */ + else + outstrn((char_u *)"\033[?1000l"); /* xterm: disable mouse events */ + } + ison = on; +} +#endif + +/* + * set screen mode, always fails. + */ + int +mch_screenmode(arg) + char_u *arg; +{ + EMSG("Screen mode setting not supported"); + return FAIL; +} + +/* + * Try to get the current window size: + * 1. with an ioctl(), most accurate method + * 2. from the environment variables LINES and COLUMNS + * 3. from the termcap + * 4. keep using the old values + */ + int +mch_get_winsize() +{ + int old_Rows = Rows; + int old_Columns = Columns; + char_u *p; + +#ifdef USE_GUI + if (gui.in_use) + return gui_mch_get_winsize(); +#endif + + Columns = 0; + Rows = 0; + +/* + * For OS/2 use _scrsize(). + */ +# ifdef __EMX__ + { + int s[2]; + _scrsize(s); + Columns = s[0]; + Rows = s[1]; + } +# endif + +/* + * 1. try using an ioctl. It is the most accurate method. + * + * Try using TIOCGWINSZ first, some systems that have it also define TIOCGSIZE + * but don't have a struct ttysize. + */ +# ifdef TIOCGWINSZ + { + struct winsize ws; + + if (ioctl(0, TIOCGWINSZ, &ws) == 0) + { + Columns = ws.ws_col; + Rows = ws.ws_row; + } + } +# else /* TIOCGWINSZ */ +# ifdef TIOCGSIZE + { + struct ttysize ts; + + if (ioctl(0, TIOCGSIZE, &ts) == 0) + { + Columns = ts.ts_cols; + Rows = ts.ts_lines; + } + } +# endif /* TIOCGSIZE */ +# endif /* TIOCGWINSZ */ + +/* + * 2. get size from environment + */ + if (Columns == 0 || Rows == 0) + { + if ((p = (char_u *)getenv("LINES"))) + Rows = atoi((char *)p); + if ((p = (char_u *)getenv("COLUMNS"))) + Columns = atoi((char *)p); + } + +#ifdef HAVE_TGETENT +/* + * 3. try reading the termcap + */ + if (Columns == 0 || Rows == 0) + getlinecol(); /* get "co" and "li" entries from termcap */ +#endif + +/* + * 4. If everything fails, use the old values + */ + if (Columns <= 0 || Rows <= 0) + { + Columns = old_Columns; + Rows = old_Rows; + return FAIL; + } + + check_winsize(); + +/* if size changed: screenalloc will allocate new screen buffers */ + return OK; +} + + void +mch_set_winsize() +{ + char_u string[10]; + +#ifdef USE_GUI + if (gui.in_use) + { + gui_mch_set_winsize(); + return; + } +#endif + + /* try to set the window size to Rows and Columns */ + if (is_iris_ansi(term_strings[KS_NAME])) + { + sprintf((char *)string, "\033[203;%ld;%ld/y", Rows, Columns); + outstrn(string); + flushbuf(); + screen_start(); /* don't know where cursor is now */ + } +} + + int +call_shell(cmd, options) + char_u *cmd; + int options; /* SHELL_FILTER if called by do_filter() */ + /* SHELL_COOKED if term needs cooked mode */ + /* SHELL_EXPAND if called by ExpandWildCards() */ +{ +#ifdef USE_SYSTEM /* use system() to start the shell: simple but slow */ + + int x; +#ifndef __EMX__ + char_u newcmd[1024]; /* only needed for unix */ +#else /* __EMX__ */ + /* + * Set the preferred shell in the EMXSHELL environment variable (but + * only if it is different from what is already in the environment). + * Emx then takes care of whether to use "/c" or "-c" in an + * intelligent way. Simply pass the whole thing to emx's system() call. + * Emx also starts an interactive shell if system() is passed an empty + * string. + */ + char_u *p, *old; + + if (((old = getenv("EMXSHELL")) == NULL) || strcmp(old, p_sh)) + { + /* should check HAVE_SETENV, but I know we don't have it. */ + p = alloc(10 + strlen(p_sh)); + if (p) + { + sprintf(p, "EMXSHELL=%s", p_sh); + putenv(p); /* don't free the pointer! */ + } + } +#endif + + flushbuf(); + + if (options & SHELL_COOKED) + settmode(0); /* set to cooked mode */ + +#ifdef __EMX__ + if (cmd == NULL) + x = system(""); /* this starts an interactive shell in emx */ + else + x = system(cmd); + if (x == -1) /* system() returns -1 when error occurs in starting shell */ + { + MSG_OUTSTR("\nCannot execute shell "); + msg_outstr(p_sh); + msg_outchar('\n'); + } +#else /* not __EMX__ */ + if (cmd == NULL) + x = system(p_sh); + else + { + sprintf(newcmd, "%s %s -c \"%s\"", p_sh, + extra_shell_arg == NULL ? "" : (char *)extra_shell_arg, + (char *)cmd); + x = system(newcmd); + } + if (x == 127) + { + MSG_OUTSTR("\nCannot execute shell sh\n"); + } +#endif /* __EMX__ */ + else if (x && !expand_interactively) + { + msg_outchar('\n'); + msg_outnum((long)x); + MSG_OUTSTR(" returned\n"); + } + + settmode(1); /* set to raw mode */ +#ifdef OS2 + /* external command may change the window size in OS/2, so check it */ + mch_get_winsize(); +#endif + resettitle(); + return (x ? FAIL : OK); + +#else /* USE_SYSTEM */ /* don't use system(), use fork()/exec() */ + +#define EXEC_FAILED 122 /* Exit code when shell didn't execute. Don't use + 127, some shell use that already */ + + char_u newcmd[1024]; + int pid; +#ifdef HAVE_UNION_WAIT + union wait status; +#else + int status = -1; +#endif + int retval = FAIL; + char **argv = NULL; + int argc; + int i; + char_u *p; + int inquote; +#ifdef USE_GUI + int pty_master_fd = -1; /* for pty's */ + int pty_slave_fd = -1; + char *tty_name; + int fd_toshell[2]; /* for pipes */ + int fd_fromshell[2]; + int pipe_error = FALSE; +# ifdef HAVE_SETENV + char envbuf[50]; +# else + static char envbuf_Rows[20]; + static char envbuf_Columns[20]; +# endif +#endif + int did_settmode = FALSE; /* TRUE when settmode(1) called */ + + flushbuf(); + if (options & SHELL_COOKED) + settmode(0); /* set to cooked mode */ + + /* + * 1: find number of arguments + * 2: separate them and built argv[] + */ + STRCPY(newcmd, p_sh); + for (i = 0; i < 2; ++i) + { + p = newcmd; + inquote = FALSE; + argc = 0; + for (;;) + { + if (i == 1) + argv[argc] = (char *)p; + ++argc; + while (*p && (inquote || (*p != ' ' && *p != TAB))) + { + if (*p == '"') + inquote = !inquote; + ++p; + } + if (*p == NUL) + break; + if (i == 1) + *p++ = NUL; + p = skipwhite(p); + } + if (i == 0) + { + argv = (char **)alloc((unsigned)((argc + 4) * sizeof(char *))); + if (argv == NULL) /* out of memory */ + goto error; + } + } + if (cmd != NULL) + { + if (extra_shell_arg != NULL) + argv[argc++] = (char *)extra_shell_arg; + argv[argc++] = "-c"; + argv[argc++] = (char *)cmd; + } + argv[argc] = NULL; + +#ifdef tower32 + /* + * reap lost children (seems necessary on NCR Tower, + * although I don't have a clue why...) (Slootman) + */ + while (wait(&status) != 0 && errno != ECHILD) + ; /* do it again, if necessary */ +#endif + +#ifdef USE_GUI +/* + * First try at using a pseudo-tty to get the stdin/stdout of the executed + * command into the current window for the GUI. + */ + + if (gui.in_use && show_shell_mess) + { + /* + * Try to open a master pty. + * If this works, open the slave pty. + * If the slave can't be opened, close the master pty. + */ + if (p_guipty) + { + pty_master_fd = OpenPTY(&tty_name); /* open pty */ + if (pty_master_fd >= 0 && ((pty_slave_fd = + open(tty_name, O_RDWR | O_EXTRA)) < 0)) + { + close(pty_master_fd); + pty_master_fd = -1; + } + } + /* + * If opening a pty didn't work, try using pipes. + */ + if (pty_master_fd < 0) + { + pipe_error = (pipe(fd_toshell) < 0); + if (!pipe_error) /* pipe create OK */ + { + pipe_error = (pipe(fd_fromshell) < 0); + if (pipe_error) /* pipe create failed */ + { + close(fd_toshell[0]); + close(fd_toshell[1]); + } + } + if (pipe_error) + { + MSG_OUTSTR("\nCannot create pipes\n"); + flushbuf(); + } + } + } + + if (!pipe_error) /* pty or pipe opened or not used */ +#endif + + { + if ((pid = fork()) == -1) /* maybe we should use vfork() */ + { + MSG_OUTSTR("\nCannot fork\n"); +#ifdef USE_GUI + if (gui.in_use && show_shell_mess) + { + if (pty_master_fd >= 0) /* close the pseudo tty */ + { + close(pty_master_fd); + close(pty_slave_fd); + } + else /* close the pipes */ + { + close(fd_toshell[0]); + close(fd_toshell[1]); + close(fd_fromshell[0]); + close(fd_fromshell[1]); + } + } +#endif + } + else if (pid == 0) /* child */ + { + reset_signals(); /* handle signals normally */ + if (!show_shell_mess) + { + int fd; + + /* + * Don't want to show any message from the shell. Can't just + * close stdout and stderr though, because some systems will + * break if you try to write to them after that, so we must + * use dup() to replace them with something else -- webb + */ + fd = open("/dev/null", O_WRONLY | O_EXTRA); + fclose(stdout); + fclose(stderr); + + /* + * If any of these open()'s and dup()'s fail, we just continue + * anyway. It's not fatal, and on most systems it will make + * no difference at all. On a few it will cause the execvp() + * to exit with a non-zero status even when the completion + * could be done, which is nothing too serious. If the open() + * or dup() failed we'd just do the same thing ourselves + * anyway -- webb + */ + if (fd >= 0) + { + /* To replace stdout (file descriptor 1) */ + dup(fd); + + /* To replace stderr (file descriptor 2) */ + dup(fd); + + /* Don't need this now that we've duplicated it */ + close(fd); + } + } +#ifdef USE_GUI + else if (gui.in_use) + { + +#ifdef HAVE_SETSID + (void)setsid(); +#endif +#ifdef TIOCSCTTY + /* try to become controlling tty (probably doesn't work, + * unless run by root) */ + ioctl(pty_slave_fd, TIOCSCTTY, (char *)NULL); +#endif + /* Simulate to have a dumb terminal (for now) */ +#ifdef HAVE_SETENV + setenv("TERM", "dumb", 1); + sprintf((char *)envbuf, "%ld", Rows); + setenv("ROWS", (char *)envbuf, 1); + sprintf((char *)envbuf, "%ld", Columns); + setenv("COLUMNS", (char *)envbuf, 1); +#else + /* + * Putenv does not copy the string, it has to remain valid. + * Use a static array to avoid loosing allocated memory. + */ + putenv("TERM=dumb"); + sprintf(envbuf_Rows, "ROWS=%ld", Rows); + putenv(envbuf_Rows); + sprintf(envbuf_Columns, "COLUMNS=%ld", Columns); + putenv(envbuf_Columns); +#endif + + if (pty_master_fd >= 0) + { + close(pty_master_fd); /* close master side of pty */ + + /* set up stdin/stdout/stderr for the child */ + close(0); + dup(pty_slave_fd); + close(1); + dup(pty_slave_fd); + close(2); + dup(pty_slave_fd); + + close(pty_slave_fd); /* has been dupped, close it now */ + } + else + { + /* set up stdin for the child */ + close(fd_toshell[1]); + close(0); + dup(fd_toshell[0]); + close(fd_toshell[0]); + + /* set up stdout for the child */ + close(fd_fromshell[0]); + close(1); + dup(fd_fromshell[1]); + close(fd_fromshell[1]); + + /* set up stderr for the child */ + close(2); + dup(1); + } + } +#endif + /* + * There is no type cast for the argv, because the type may be + * different on different machines. This may cause a warning + * message with strict compilers, don't worry about it. + */ + execvp(argv[0], argv); + exit(EXEC_FAILED); /* exec failed, return failure code */ + } + else /* parent */ + { + /* + * While child is running, ignore terminating signals. + */ + catch_signals(SIG_IGN); + +#ifdef USE_GUI + + /* + * For the GUI we redirect stdin, stdout and stderr to our window. + */ + if (gui.in_use && show_shell_mess) + { +#define BUFLEN 100 /* length for buffer, pseudo tty limit is 128 */ + char_u buffer[BUFLEN]; + int len; + int p_more_save; + int old_State; + int read_count; + int c; + int toshell_fd; + int fromshell_fd; + + if (pty_master_fd >= 0) + { + close(pty_slave_fd); /* close slave side of pty */ + fromshell_fd = pty_master_fd; + toshell_fd = dup(pty_master_fd); + } + else + { + close(fd_toshell[0]); + close(fd_fromshell[1]); + toshell_fd = fd_toshell[1]; + fromshell_fd = fd_fromshell[0]; + } + + /* + * Write to the child if there are typed characters. + * Read from the child if there are characters available. + * Repeat the reading a few times if more characters are + * available. Need to check for typed keys now and then, but + * not too often (delays when no chars are available). + * This loop is quit if no characters can be read from the pty + * (WaitForChar detected special condition), or there are no + * characters available and the child has exited. + * Only check if the child has exited when there is no more + * output. The child may exit before all the output has + * been printed. + * + * Currently this busy loops! + * This can probably dead-lock when the write blocks! + */ + p_more_save = p_more; + p_more = FALSE; + old_State = State; + State = EXTERNCMD; /* don't redraw at window resize */ + + for (;;) + { + /* + * Check if keys have been typed, write them to the child + * if there are any. Don't do this if we are expanding + * wild cards (would eat typeahead). + */ + if (!(options & SHELL_EXPAND) && + (len = mch_inchar(buffer, BUFLEN - 1, 10)) != 0) + { + /* + * For pipes: + * Check for CTRL-C: sent interrupt signal to child. + * Check for CTRL-D: EOF, close pipe to child. + */ + if (len == 1 && (pty_master_fd < 0 || cmd != NULL)) + { +#ifdef SIGINT + if (buffer[0] == Ctrl('C')) + { + /* Use both kill() and killpg(), in case one + * of the two fails */ + kill(pid, SIGINT); +# ifdef HAVE_KILLPG + killpg(0, SIGINT); +# endif + } +#endif + if (pty_master_fd < 0 && toshell_fd >= 0 && + buffer[0] == Ctrl('D')) + { + close(toshell_fd); + toshell_fd = -1; + } + } + + /* replace K_BS by and K_DEL by */ + for (i = 0; i < len; ++i) + { + if (buffer[i] == CSI && len - i > 2) + { + c = TERMCAP2KEY(buffer[i + 1], buffer[i + 2]); + if (c == K_DEL || c == K_BS) + { + vim_memmove(buffer + i + 1, buffer + i + 3, + (size_t)(len - i - 2)); + if (c == K_DEL) + buffer[i] = DEL; + else + buffer[i] = Ctrl('H'); + len -= 2; + } + } + else if (buffer[i] == '\r') + buffer[i] = '\n'; + } + + /* + * For pipes: echo the typed characters. + * For a pty this does not seem to work. + */ + if (pty_master_fd < 0) + { + for (i = 0; i < len; ++i) + if (buffer[i] == '\n' || buffer[i] == '\b') + msg_outchar(buffer[i]); + else + msg_outtrans_len(buffer + i, 1); + windgoto(msg_row, msg_col); + flushbuf(); + } + + /* + * Write the characters to the child, unless EOF has + * been typed for pipes. Ignore errors. + */ + if (toshell_fd >= 0) + write(toshell_fd, (char *)buffer, (size_t)len); + } + + /* + * Check if the child has any characters to be printed. + * Read them and write them to our window. + * Repeat this a few times as long as there is something + * to do, avoid the 10ms wait for mch_inchar(). + * TODO: This should handle escape sequences. + */ + for (read_count = 0; read_count < 10 && + RealWaitForChar(fromshell_fd, 10); ++read_count) + { + len = read(fromshell_fd, (char *)buffer, + (size_t)BUFLEN); + if (len == 0) /* end of file */ + goto finished; + buffer[len] = NUL; + msg_outstr(buffer); + windgoto(msg_row, msg_col); + cursor_on(); + flushbuf(); + } + + /* + * Check if the child still exists when we finished + * outputting all characters. + */ + if (read_count == 0 && +#ifdef __NeXT__ + wait4(pid, &status, WNOHANG, (struct rusage *) 0) && +#else + waitpid(pid, &status, WNOHANG) && +#endif + WIFEXITED(status)) + break; + } +finished: + p_more = p_more_save; + State = old_State; + if (toshell_fd >= 0) + close(toshell_fd); + close(fromshell_fd); + } +#endif /* USE_GUI */ + + /* + * Wait until child has exited. + */ +#ifdef ECHILD + /* Don't stop waiting when a signal (e.g. SIGWINCH) is received. */ + while (wait(&status) == -1 && errno != ECHILD) + ; +#else + wait(&status); +#endif + /* + * Set to raw mode right now, otherwise a CTRL-C after + * catch_signals will kill Vim. + */ + settmode(1); + did_settmode = TRUE; + catch_signals(deathtrap); + + /* + * Check the window size, in case it changed while executing the + * external command. + */ + mch_get_winsize(); + + if (WIFEXITED(status)) + { + i = WEXITSTATUS(status); + if (i) + { + if (i == EXEC_FAILED) + { + MSG_OUTSTR("\nCannot execute shell "); + msg_outtrans(p_sh); + msg_outchar('\n'); + } + else if (!expand_interactively) + { + msg_outchar('\n'); + msg_outnum((long)i); + MSG_OUTSTR(" returned\n"); + } + } + else + retval = OK; + } + else + MSG_OUTSTR("\nCommand terminated\n"); + } + } + vim_free(argv); + +error: + if (!did_settmode) + settmode(1); /* always set to raw mode */ + resettitle(); + + return retval; + +#endif /* USE_SYSTEM */ +} + +/* + * The input characters are buffered to be able to check for a CTRL-C. + * This should be done with signals, but I don't know how to do that in + * a portable way for a tty in RAW mode. + */ + +/* + * Internal typeahead buffer. Includes extra space for long key code + * descriptions which would otherwise overflow. The buffer is considered full + * when only this extra space (or part of it) remains. + */ +#define INBUFLEN 250 + +static char_u inbuf[INBUFLEN + MAX_KEY_CODE_LEN]; +static int inbufcount = 0; /* number of chars in inbuf[] */ + +/* + * is_input_buf_full(), is_input_buf_empty(), add_to_input_buf(), and + * trash_input_buf() are functions for manipulating the input buffer. These + * are used by the gui_* calls when a GUI is used to handle keyboard input. + * + * NOTE: These functions will be identical in msdos.c etc, and should probably + * be taken out and put elsewhere, but at the moment inbuf is only local. + */ + + int +is_input_buf_full() +{ + return (inbufcount >= INBUFLEN); +} + + int +is_input_buf_empty() +{ + return (inbufcount == 0); +} + +/* Add the given bytes to the input buffer */ + void +add_to_input_buf(s, len) + char_u *s; + int len; +{ + if (inbufcount + len > INBUFLEN + MAX_KEY_CODE_LEN) + return; /* Shouldn't ever happen! */ + + while (len--) + inbuf[inbufcount++] = *s++; +} + +/* Remove everything from the input buffer. Called when ^C is found */ + void +trash_input_buf() +{ + inbufcount = 0; +} + + static int +Read(buf, maxlen) + char_u *buf; + long maxlen; +{ + if (inbufcount == 0) /* if the buffer is empty, fill it */ + fill_inbuf(TRUE); + if (maxlen > inbufcount) + maxlen = inbufcount; + vim_memmove(buf, inbuf, (size_t)maxlen); + inbufcount -= maxlen; + if (inbufcount) + vim_memmove(inbuf, inbuf + maxlen, (size_t)inbufcount); + return (int)maxlen; +} + + void +mch_breakcheck() +{ +#ifdef USE_GUI + if (gui.in_use) + { + gui_mch_update(); + return; + } +#endif /* USE_GUI */ + +/* + * Check for CTRL-C typed by reading all available characters. + * In cooked mode we should get SIGINT, no need to check. + */ + if (curr_tmode && RealWaitForChar(0, 0L)) /* if characters available */ + fill_inbuf(FALSE); +} + + static void +fill_inbuf(exit_on_error) + int exit_on_error; +{ + int len; + int try; + +#ifdef USE_GUI + if (gui.in_use) + { + gui_mch_update(); + return; + } +#endif + if (is_input_buf_full()) + return; + /* + * Fill_inbuf() is only called when we really need a character. + * If we can't get any, but there is some in the buffer, just return. + * If we can't get any, and there isn't any in the buffer, we give up and + * exit Vim. + */ + for (try = 0; try < 100; ++try) + { + len = read(0, (char *)inbuf + inbufcount, + (size_t)(INBUFLEN - inbufcount)); + if (len > 0) + break; + if (!exit_on_error) + return; + } + if (len <= 0) + { + windgoto((int)Rows - 1, 0); + fprintf(stderr, "Vim: Error reading input, exiting...\n"); + ml_sync_all(FALSE, TRUE); /* preserve all swap files */ + getout(1); + } + while (len-- > 0) + { + /* + * if a CTRL-C was typed, remove it from the buffer and set got_int + */ + if (inbuf[inbufcount] == 3) + { + /* remove everything typed before the CTRL-C */ + vim_memmove(inbuf, inbuf + inbufcount, (size_t)(len + 1)); + inbufcount = 0; + got_int = TRUE; + } + ++inbufcount; + } +} + +/* + * Wait "msec" msec until a character is available from the keyboard or from + * inbuf[]. msec == -1 will block forever. + * When a GUI is being used, this will never get called -- webb + */ + + static int +WaitForChar(msec) + long msec; +{ + if (inbufcount) /* something in inbuf[] */ + return 1; + return RealWaitForChar(0, msec); +} + +/* + * Wait "msec" msec until a character is available from file descriptor "fd". + * Time == -1 will block forever. + * When a GUI is being used, this will not be used for input -- webb + */ + static int +RealWaitForChar(fd, msec) + int fd; + long msec; +{ +#ifndef HAVE_SELECT + struct pollfd fds; + + fds.fd = fd; + fds.events = POLLIN; + return (poll(&fds, 1, (int)msec) > 0); /* is this correct when fd != 0?? */ +#else + struct timeval tv; + fd_set rfds, efds; + +# ifdef __EMX__ + /* don't check for incoming chars if not in raw mode, because select() + * always returns TRUE then (in some version of emx.dll) */ + if (curr_tmode == 0) + return 0; +# endif + + if (msec >= 0) + { + tv.tv_sec = msec / 1000; + tv.tv_usec = (msec % 1000) * (1000000/1000); + } + + /* + * Select on ready for reading and exceptional condition (end of file). + */ + FD_ZERO(&rfds); /* calls bzero() on a sun */ + FD_ZERO(&efds); + FD_SET(fd, &rfds); + FD_SET(fd, &efds); + return (select(fd + 1, &rfds, NULL, &efds, (msec >= 0) ? &tv : NULL) > 0); +#endif +} + +/* + * ExpandWildCards() - this code does wild-card pattern matching using the shell + * + * return OK for success, FAIL for error (you may lose some memory) and put + * an error message in *file. + * + * num_pat is number of input patterns + * pat is array of pointers to input patterns + * num_file is pointer to number of matched file names + * file is pointer to array of pointers to matched file names + * On Unix we do not check for files only yet + * list_notfound is ignored + */ + +extern char *mktemp __ARGS((char *)); +#ifndef SEEK_SET +# define SEEK_SET 0 +#endif +#ifndef SEEK_END +# define SEEK_END 2 +#endif + + int +ExpandWildCards(num_pat, pat, num_file, file, files_only, list_notfound) + int num_pat; + char_u **pat; + int *num_file; + char_u ***file; + int files_only; + int list_notfound; +{ + int i; + size_t len; + char_u *p; +#ifdef __EMX__ +# define EXPL_ALLOC_INC 16 + char_u **expl_files; + size_t files_alloced, files_free; + + *num_file = 0; /* default: no files found */ + files_alloced = EXPL_ALLOC_INC; /* how much space is allocated */ + files_free = EXPL_ALLOC_INC; /* how much space is not used */ + *file = (char_u **) alloc(sizeof(char_u **) * files_alloced); + if (!*file) + { + emsg(e_outofmem); + return FAIL; + } + + for (; num_pat > 0; num_pat--, pat++) + { + expl_files = NULL; + if (vim_strchr(*pat, '$') || vim_strchr(*pat, '~')) + { + /* expand environment var or home dir */ + char_u *buf = alloc(1024); + if (!buf) + { + emsg(e_outofmem); + return FAIL; + } + expand_env(*pat, buf, 1024); + if (mch_has_wildcard(buf)) /* still wildcards in there? */ + { + expl_files = (char_u **)_fnexplode(buf); + } + if (expl_files == NULL) + { + /* + * If no wildcard still remaining, simply add + * the pattern to the results. + * If wildcard did not match, add the pattern to + * the list of results anyway. This way, doing + * :n exist.c notexist* + * will at least edits exist.c and then say + * notexist* [new file] + */ + expl_files = (char_u **)alloc(sizeof(char_u **) * 2); + expl_files[0] = strsave(buf); + expl_files[1] = NULL; + } + vim_free(buf); + } + else + { + expl_files = (char_u **)_fnexplode(*pat); + if (expl_files == NULL) + { + /* see above for explanation */ + expl_files = (char_u **)alloc(sizeof(char_u **) * 2); + expl_files[0] = strsave(*pat); + expl_files[1] = NULL; + } + } + if (!expl_files) + { + /* Can't happen */ + char_u msg[128]; + sprintf(msg, "%s (unix.c:%d)", e_internal, __LINE__); + emsg(msg); + *file = (char_u **)""; + *num_file = 0; + return OK; + } + /* + * Count number of names resulting from expansion, + * At the same time add a backslash to the end of names that happen to be + * directories, and replace slashes with backslashes. + */ + for (i = 0; (p = expl_files[i]) != NULL; i++, (*num_file)++) + { + if (--files_free == 0) + { + /* need more room in table of pointers */ + files_alloced += EXPL_ALLOC_INC; + *file = (char_u **) realloc(*file, + sizeof(char_u **) * files_alloced); + files_free = EXPL_ALLOC_INC; + } + slash_adjust(p); + if (mch_isdir(p)) + { + len = strlen(p); + p = realloc(p, len + 2); + if (!p) + { + emsg(e_outofmem); + return FAIL; + } + (*file)[*num_file] = p; + p += len; + *p++ = '\\'; + *p = 0; + } + else + { + (*file)[*num_file] = strsave(p); + } + } + _fnexplodefree(expl_files); + } + return OK; + +#else /* __EMX__ */ + + int dir; + char_u tmpname[TMPNAMELEN]; + char_u *command; + FILE *fd; + char_u *buffer; + int use_glob = FALSE; + + *num_file = 0; /* default: no files found */ + *file = (char_u **)""; + + /* + * If there are no wildcards, just copy the names to allocated memory. + * Saves a lot of time, because we don't have to start a new shell. + */ + if (!have_wildcard(num_pat, pat)) + { + *file = (char_u **)alloc(num_pat * sizeof(char_u *)); + if (*file == NULL) + { + *file = (char_u **)""; + return FAIL; + } + for (i = 0; i < num_pat; i++) + (*file)[i] = strsave(pat[i]); + *num_file = num_pat; + return OK; + } + +/* + * get a name for the temp file + */ + STRCPY(tmpname, TMPNAME2); + if (*mktemp((char *)tmpname) == NUL) + { + emsg(e_notmp); + return FAIL; + } + +/* + * let the shell expand the patterns and write the result into the temp file + * If we use csh, glob will work better than echo. + */ + if ((len = STRLEN(p_sh)) >= 3 && STRCMP(p_sh + len - 3, "csh") == 0) + use_glob = TRUE; + + len = TMPNAMELEN + 11; + for (i = 0; i < num_pat; ++i) /* count the length of the patterns */ + len += STRLEN(pat[i]) + 3; + command = alloc(len); + if (command == NULL) + return FAIL; + if (use_glob) + STRCPY(command, "glob >"); /* build the shell command */ + else + STRCPY(command, "echo >"); /* build the shell command */ + STRCAT(command, tmpname); + for (i = 0; i < num_pat; ++i) + { +#ifdef USE_SYSTEM + STRCAT(command, " \""); /* need extra quotes because we */ + STRCAT(command, pat[i]); /* start the shell twice */ + STRCAT(command, "\""); +#else + STRCAT(command, " "); + STRCAT(command, pat[i]); +#endif + } + if (expand_interactively) + show_shell_mess = FALSE; + /* + * If we use -f then shell variables set in .cshrc won't get expanded. + * vi can do it, so we will too, but it is only necessary if there is a "$" + * in one of the patterns, otherwise we can still use the fast option. + */ + if (use_glob && !have_dollars(num_pat, pat)) /* Use csh fast option */ + extra_shell_arg = (char_u *)"-f"; + i = call_shell(command, SHELL_EXPAND); /* execute it */ + extra_shell_arg = NULL; + show_shell_mess = TRUE; + vim_free(command); + if (i == FAIL) /* call_shell failed */ + { + vim_remove(tmpname); + /* + * With interactive completion, the error message is not printed. + * However with USE_SYSTEM, I don't know how to turn off error messages + * from the shell, so screen may still get messed up -- webb. + */ +#ifndef USE_SYSTEM + if (!expand_interactively) +#endif + { + must_redraw = CLEAR; /* probably messed up screen */ + msg_outchar('\n'); /* clear bottom line quickly */ + cmdline_row = Rows - 1; /* continue on last line */ + } + return FAIL; + } + +/* + * read the names from the file into memory + */ + fd = fopen((char *)tmpname, "r"); + if (fd == NULL) + { + emsg2(e_notopen, tmpname); + return FAIL; + } + fseek(fd, 0L, SEEK_END); + len = ftell(fd); /* get size of temp file */ + fseek(fd, 0L, SEEK_SET); + buffer = alloc(len + 1); + if (buffer == NULL) + { + vim_remove(tmpname); + fclose(fd); + return FAIL; + } + i = fread((char *)buffer, 1, len, fd); + fclose(fd); + vim_remove(tmpname); + if (i != len) + { + emsg2(e_notread, tmpname); + vim_free(buffer); + return FAIL; + } + + if (use_glob) /* file names are separated with NUL */ + { + buffer[len] = NUL; /* make sure the buffers ends in NUL */ + i = 0; + for (p = buffer; p < buffer + len; ++p) + if (*p == NUL) /* count entry */ + ++i; + if (len) + ++i; /* count last entry */ + } + else /* file names are separated with SPACE */ + { + buffer[len] = '\n'; /* make sure the buffers ends in NL */ + p = buffer; + for (i = 0; *p != '\n'; ++i) /* count number of entries */ + { + while (*p != ' ' && *p != '\n') /* skip entry */ + ++p; + p = skipwhite(p); /* skip to next entry */ + } + } + if (i == 0) + { + /* + * Can happen when using /bin/sh and typing ":e $NO_SUCH_VAR^I". + * /bin/sh will happily expand it to nothing rather than returning an + * error; and hey, it's good to check anyway -- webb. + */ + vim_free(buffer); + *file = (char_u **)""; + return FAIL; + } + *num_file = i; + *file = (char_u **)alloc(sizeof(char_u *) * i); + if (*file == NULL) + { + vim_free(buffer); + *file = (char_u **)""; + return FAIL; + } + + /* + * Isolate the individual file names. + */ + p = buffer; + for (i = 0; i < *num_file; ++i) + { + (*file)[i] = p; + if (use_glob) + { + while (*p && p < buffer + len) /* skip entry */ + ++p; + ++p; /* skip NUL */ + } + else + { + while (*p != ' ' && *p != '\n') /* skip entry */ + ++p; + if (*p == '\n') /* last entry */ + *p = NUL; + else + { + *p++ = NUL; + p = skipwhite(p); /* skip to next entry */ + } + } + } + + /* + * Move the file names to allocated memory. + */ + for (i = 0; i < *num_file; ++i) + { + /* Require the files to exist. Helps when using /bin/sh */ + if (expand_interactively) + { + struct stat st; + int j; + + if (stat((char *)((*file)[i]), &st) < 0) + { + for (j = i; j + 1 < *num_file; ++j) + (*file)[j] = (*file)[j + 1]; + --*num_file; + --i; + continue; + } + } + + /* if file doesn't exist don't add '/' */ + dir = (mch_isdir((*file)[i])); + p = alloc((unsigned)(STRLEN((*file)[i]) + 1 + dir)); + if (p) + { + STRCPY(p, (*file)[i]); + if (dir) + STRCAT(p, "/"); + } + (*file)[i] = p; + } + vim_free(buffer); + + if (*num_file == 0) /* rejected all entries */ + { + vim_free(*file); + *file = (char_u **)""; + return FAIL; + } + + return OK; + +#endif /* __EMX__ */ +} + + int +mch_has_wildcard(p) + char_u *p; +{ + for ( ; *p; ++p) + { + if (*p == '\\' && p[1] != NUL) + ++p; + else if (vim_strchr((char_u *)"*?[{`~$", *p) != NULL) + return TRUE; + } + return FALSE; +} + +#ifndef __EMX__ + static int +have_wildcard(num, file) + int num; + char_u **file; +{ + register int i; + + for (i = 0; i < num; i++) + if (mch_has_wildcard(file[i])) + return 1; + return 0; +} + + static int +have_dollars(num, file) + int num; + char_u **file; +{ + register int i; + + for (i = 0; i < num; i++) + if (vim_strchr(file[i], '$') != NULL) + return TRUE; + return FALSE; +} +#endif /* ifndef __EMX__ */ + +#ifndef HAVE_RENAME +/* + * Scaled-down version of rename, which is missing in Xenix. + * This version can only move regular files and will fail if the + * destination exists. + */ + int +rename(src, dest) + const char *src, *dest; +{ + struct stat st; + + if (stat(dest, &st) >= 0) /* fail if destination exists */ + return -1; + if (link(src, dest) != 0) /* link file to new name */ + return -1; + if (vim_remove(src) == 0) /* delete link to old name */ + return 0; + return -1; +} +#endif /* !HAVE_RENAME */ diff --git a/usr.bin/vim/unix.h b/usr.bin/vim/unix.h new file mode 100644 index 00000000000..93084641532 --- /dev/null +++ b/usr.bin/vim/unix.h @@ -0,0 +1,271 @@ +/* $OpenBSD: unix.h,v 1.1.1.1 1996/09/07 21:40:27 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +#include +#include +#include +#include + +#ifdef HAVE_STDLIB_H +# include +#endif + +#ifdef HAVE_UNISTD_H +# include +#endif + +#ifdef HAVE_LIBC_H +# include /* for NeXT */ +#endif + +/* + * SVR4 may be defined for linux, but linux isn't SVR4 + */ +#if defined(SVR4) && defined(__linux__) +# undef SVR4 +#endif + +/* + * Sun defines FILE on SunOS 4.x.x, Solaris has a typedef for FILE + */ +#if defined(sun) && !defined(FILE) +# define SOLARIS +#endif + +/* + * Using getcwd() is preferred, because it checks for a buffer overflow. + * Don't use getcwd() on systems do use system("sh -c pwd"). There is an + * autoconf check for this. + * Use getcwd() anyway if getwd() isn't present. + */ +#if defined(HAVE_GETCWD) && !(defined(BAD_GETCWD) && defined(HAVE_GETWD)) +# define USE_GETCWD +#endif + +#ifndef __ARGS +# if defined(__STDC__) || defined(__GNUC__) +# define __ARGS(x) x +# else +# define __ARGS(x) () +# endif +#endif + +/* always use unlink() to remove files */ +#define vim_remove(x) unlink((char *)(x)) + +/* The number of arguments to a signal handler is configured here. */ +/* It used to be a long list of almost all systems. Any system that doesn't + * have an argument??? */ +/* #if defined(SVR4) || (defined(SYSV) && defined(ISC)) || defined(_AIX) || defined(__linux__) || defined(ultrix) || defined(__386BSD__) || defined(__FreeBSD__) || defined(__bsdi__) || defined(POSIX) || defined(NeXT) || defined(__alpha) || defined(apollo) */ +#if !defined(SOME_SYSTEM) +# define SIGHASARG +#endif + +/* List 3 arg systems here. I guess __sgi, please test and correct me. jw. */ +#if defined(__sgi) +# define SIGHAS3ARGS +#endif + +#ifdef SIGHASARG +# ifdef SIGHAS3ARGS +# define SIGPROTOARG (int, int, struct sigcontext *) +# define SIGDEFARG(s) (s, sig2, scont) int s, sig2; struct sigcontext *scont; +# define SIGDUMMYARG 0, 0, (struct sigcontext *)0 +# else +# define SIGPROTOARG (int) +# define SIGDEFARG(s) (s) int s; +# define SIGDUMMYARG 0 +# endif +#else +# define SIGPROTOARG (void) +# define SIGDEFARG(s) () +# define SIGDUMMYARG +#endif + +#if HAVE_DIRENT_H +# include +# define NAMLEN(dirent) strlen((dirent)->d_name) +#else +# define dirent direct +# define NAMLEN(dirent) (dirent)->d_namlen +# if HAVE_SYS_NDIR_H +# include +# endif +# if HAVE_SYS_DIR_H +# include +# endif +# if HAVE_NDIR_H +# include +# endif +#endif + +#if !defined(HAVE_SYS_TIME_H) || defined(TIME_WITH_SYS_TIME) +# include /* on some systems time.h should not be + included together with sys/time.h */ +#endif +#ifdef HAVE_SYS_TIME_H +# include +#endif + +#include + +#if defined(DIRSIZ) && !defined(MAXNAMLEN) +# define MAXNAMLEN DIRSIZ +#endif + +#if defined(UFS_MAXNAMLEN) && !defined(MAXNAMLEN) +# define MAXNAMLEN UFS_MAXNAMLEN /* for dynix/ptx */ +#endif + +#if defined(NAME_MAX) && !defined(MAXNAMLEN) +# define MAXNAMLEN NAME_MAX /* for Linux before .99p3 */ +#endif + +/* + * Note: if MAXNAMLEN has the wrong value, you will get error messages + * for not being able to open the swap file. + */ +#if !defined(MAXNAMLEN) +# define MAXNAMLEN 512 /* for all other Unix */ +#endif + +#ifdef HAVE_ERRNO_H +# include +#endif + +#ifdef HAVE_PWD_H +# include +#endif + +#ifdef __COHERENT__ +# undef __ARGS +#endif /* __COHERENT__ */ + +#ifndef W_OK +# define W_OK 2 /* for systems that don't have W_OK in unistd.h */ +#endif + +/* + * Unix system-dependent filenames + */ + +#ifndef USR_EXRC_FILE +# define USR_EXRC_FILE "$HOME/.exrc" +#endif + +#ifndef USR_VIMRC_FILE +# define USR_VIMRC_FILE "$HOME/.vimrc" +#endif + +#ifdef USE_GUI +# ifndef USR_GVIMRC_FILE +# define USR_GVIMRC_FILE "$HOME/.gvimrc" +# endif +#endif + +#ifndef EXRC_FILE +# define EXRC_FILE ".exrc" +#endif + +#ifndef VIMRC_FILE +# define VIMRC_FILE ".vimrc" +#endif + +#ifdef USE_GUI +# ifndef GVIMRC_FILE +# define GVIMRC_FILE ".gvimrc" +# endif +#endif + +#ifdef VIMINFO +# ifndef VIMINFO_FILE +# define VIMINFO_FILE "$HOME/.viminfo" +# endif +#endif /* VIMINFO */ + +#ifndef DEF_BDIR +# ifdef OS2 +# define DEF_BDIR ".,c:/tmp,~/tmp,~/" +# else +# define DEF_BDIR ".,~/tmp,~/" /* default for 'backupdir' */ +# endif +#endif + +#ifndef DEF_DIR +# ifdef OS2 +# define DEF_DIR ".,~/tmp,c:/tmp,/tmp" +# else +# define DEF_DIR ".,~/tmp,/tmp" /* default for 'directory' */ +# endif +#endif + +#ifdef OS2 +#define TMPNAME1 "$TMP/viXXXXXX" +#define TMPNAME2 "$TMP/voXXXXXX" +#define TMPNAMELEN 128 +#else +#define TMPNAME1 "/tmp/viXXXXXX" +#define TMPNAME2 "/tmp/voXXXXXX" +#define TMPNAMELEN 15 +#endif + +/* + * Unix has plenty of memory, use large buffers + */ +#define CMDBUFFSIZE 1024 /* size of the command processing buffer */ +#define MAXPATHL 1024 /* Unix has long paths and plenty of memory */ + +#define CHECK_INODE /* used when checking if a swap file already + exists for a file */ +#define USE_MOUSE /* include mouse support */ + +#ifndef MAXMEM +# define MAXMEM 512 /* use up to 512Kbyte for buffer */ +#endif +#ifndef MAXMEMTOT +# define MAXMEMTOT 2048 /* use up to 2048Kbyte for Vim */ +#endif + +#define BASENAMELEN (MAXNAMLEN - 5) + +/* memmove is not present on all systems, use memmove, bcopy, memcpy or our + * own version */ +/* Some systems have (void *) arguments, some (char *). If we use (char *) it + * works for all */ +#ifdef USEMEMMOVE +# define vim_memmove(to, from, len) memmove((char *)(to), (char *)(from), len) +#else +# ifdef USEBCOPY +# define vim_memmove(to, from, len) bcopy((char *)(from), (char *)(to), len) +# else +# ifdef USEMEMCPY +# define vim_memmove(to, from, len) memcpy((char *)(to), (char *)(from), len) +# else +# define VIM_MEMMOVE /* found in alloc.c */ +# endif +# endif +#endif + +/* codes for xterm mouse event */ +#define MOUSE_LEFT 0x00 +#define MOUSE_MIDDLE 0x01 +#define MOUSE_RIGHT 0x02 +#define MOUSE_RELEASE 0x03 +#define MOUSE_SHIFT 0x04 +#define MOUSE_ALT 0x08 +#define MOUSE_CTRL 0x10 +#define MOUSE_DRAG (0x40 | MOUSE_RELEASE) + +#define MOUSE_CLICK_MASK 0x03 + +#define NUM_MOUSE_CLICKS(code) \ + ((((code) & 0xff) >> 6) + 1) + +#define SET_NUM_MOUSE_CLICKS(code, num) \ + (code) = ((code) & 0x3f) + (((num) - 1) << 6) diff --git a/usr.bin/vim/unixunix.h b/usr.bin/vim/unixunix.h new file mode 100644 index 00000000000..dbdfa7bb34b --- /dev/null +++ b/usr.bin/vim/unixunix.h @@ -0,0 +1,114 @@ +/* $OpenBSD: unixunix.h,v 1.1.1.1 1996/09/07 21:40:27 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * unixunix.h -- include files that are only used in unix.c + */ + +/* + * Stuff for signals + */ +#ifdef HAVE_SIGSET +# define signal sigset +#endif + + /* sun's sys/ioctl.h redefines symbols from termio world */ +#if defined(HAVE_SYS_IOCTL_H) && !defined(sun) +# include +#endif + +#ifndef USE_SYSTEM /* use fork/exec to start the shell */ + +# if defined(HAVE_SYS_WAIT_H) || defined(HAVE_UNION_WAIT) +# include +# endif + +#if defined(HAVE_SYS_SELECT_H) && \ + (!defined(HAVE_SYS_TIME_H) || defined(SYS_SELECT_WITH_SYS_TIME)) +# include +# endif + +# ifndef WEXITSTATUS +# ifdef HAVE_UNION_WAIT +# define WEXITSTATUS(stat_val) ((stat_val).w_T.w_Retcode) +# else +# define WEXITSTATUS(stat_val) (((stat_val) >> 8) & 0377) +# endif +# endif + +# ifndef WIFEXITED +# ifdef HAVE_UNION_WAIT +# define WIFEXITED(stat_val) ((stat_val).w_T.w_Termsig == 0) +# else +# define WIFEXITED(stat_val) (((stat_val) & 255) == 0) +# endif +# endif + +#endif /* !USE_SYSTEM */ + +#ifdef HAVE_STROPTS_H +# include +#endif + +#ifdef HAVE_STRING_H +# include +#endif + +#ifndef HAVE_SELECT +# ifdef HAVE_SYS_POLL_H +# include +# else +# include +# endif +#endif + +#ifdef HAVE_SYS_STREAM_H +# include +#endif + +#ifdef HAVE_SYS_PTEM_H +# include +# ifndef _IO_PTEM_H /* For UnixWare that should check for _IO_PT_PTEM_H */ +# define _IO_PTEM_H +# endif +#endif + +#ifdef HAVE_SYS_UTSNAME_H +# include +#endif + +#ifdef HAVE_SYS_SYSTEMINFO_H +/* + * foolish Sinix uses SYS_NMLN but doesn't include + * limits.h>, where it is defined. Perhaps other systems have the same + * problem? Include it here. -- Slootman + */ +# if defined(HAVE_LIMITS_H) && !defined(_LIMITS_H) +# include /* for SYS_NMLN (Sinix 5.41 / Unix SysV.4) */ +# endif +# include /* for sysinfo */ +#endif + +/* + * We use termios.h if both termios.h and termio.h are available. + * Termios is supposed to be a superset of termio.h. Don't include them both, + * it may give problems on some systems (e.g. hpux). + * I don't understand why we don't want termios.h for apollo. + */ +#if defined(HAVE_TERMIOS_H) && !defined(apollo) +# include +#else +# ifdef HAVE_TERMIO_H +# include +# else +# ifdef HAVE_SGTTY_H +# include +# endif +# endif +#endif diff --git a/usr.bin/vim/version.c b/usr.bin/vim/version.c new file mode 100644 index 00000000000..05b81f63cab --- /dev/null +++ b/usr.bin/vim/version.c @@ -0,0 +1,361 @@ +/* $OpenBSD: version.c,v 1.1.1.1 1996/09/07 21:40:24 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" + +/* + * Vim originated from Stevie version 3.6 (Fish disk 217) by GRWalter (Fred) + * It has been changed beyond recognition since then. + * + * All the remarks about older versions have been removed, they are not very + * interesting. Differences between version 3.0 and 4.0 can be found in + * "../doc/vim_40.txt". + * + * Changes between version 4.1 and 4.2: + * - Included ctags version 1.3. + * - Included xxd.c version 1.4 (only one change since 1.3m) + * - Unix: Adjusted check for child finished again. Now use != ECHILD + * instead of == EINTR. + * - Fixed: loading compressed files (with autocommands) didn't work when + * 'textmode' was previously set. + * - Fixed: When executing a shell, a window resize was not recognized for + * Unix. + * - Fixed: For GUI, when executing an external command, a window resize + * caused the buffer to be redrawn. + * - Fixed: Error message for not being able to open the .viminfo file could + * report the wrong filename. + * - MS-DOS, Win32, OS/2: When $HOME is not set, use "C:/". + * - OS/2: Improved handling of wildcards again. Didn't ignore case, didn't + * work with backslashes. + * - Fixed: ":s/pat" could cause a core dump or other problems. + * - Fixed: When entering a hidden buffer with ":e", the BufEnter autocommands + * were not executed. + * - Fixed: Ignoring a CTRL-Z at end-of-file was only done on MS-DOS. Now it + * also works on other systems, but only in textmode. + * - Fixed: In the GUI special characters were not passed on to external + * commands, typeahead was truncated. + * - Added "gq" as an alias to "Q". Should be used when "Q" is made to be Vi + * compatible: go to Ex mode. + * - Fixed: "gu" in Visual mode could not be redone with "." correctly. + * - Fixed: ":normal" command made any typeahead executed right away, causing + * unpredictable problems. + * - Fixed: ":normal atest^[" didn't update the screen. + * - Fixed: Redoing blockwise visual delete at the end of the file could cause + * "invalid line number" errors. + * - Fixed: vim_rename() didn't open the output file in binary mode, could + * cause the .viminfo file to contain CR-LF on OS/2. + * - Fixed: OS/2 was using /tmp/xxx for temporary file name, would fail if + * there is no /tmp directory. Now use $TMP/xxx, defaulting to c:/ if $TMP + * is not set. + * - Fixed: When USE_TMPNAM was defined, was using the wrong array size for + * the viminfo temporary file. + * + * Changes between version 4.0 and 4.1: + * + * - Included xxd.c version 1.3m. + * - Included ctags version 1.2. + * - Included tools/efm_filt.er. + * - Included changes for port to Atari MiNT, including makefile.mint. + * - Included a few changes for OS/2: Improved executing external commands, + * depending on the shell; Handle resize after executing an external + * command; Handle wildcard expansion for more than one argument (e.g. + * ":n *.c *.h"). + * - Include a lot of small changes to the docs. + * - Fixed: GUI version would busy-loop and mappings didn't work. Was caused + * by gui_mch_wait_for_chars() not working properly. This fix was the main + * reason for releasing 4.1. + * - Fixed: setting 'term' while GUI is active was possible, and caused + * problems. + * - Fixed: When the "to" part of a mapping or menu command was long (more + * than 24 chars on 32 bit MS-DOS, 128 chars on other systems), any <> were + * not translated and CTRL-Vs not removed. + * - Fixed: 'modified' option was included for ":mkvimrc", it shouldn't. + * - Included a few changes for that Atari MiNT port (vt52 builtin term + * entry). + * - Fixed: on MS-DOS a file name pattern for an autocommand that contains + * "\*" or "\?" didn't work. + * - On MS-DOS and Amiga, ignore case when matching the file name pattern for + * autocommands. + * - Fixed: using :set to show the value of two options gave an error message + * (e.g. ":set so sj"). + * - Fixed: Non-printable characters in a file name caused trouble when + * displayed in a status line. + * - Pack the MS-DOS zip files with Infozip, under Unix. Preserves the long + * filenames and case. Files with two dots don't work though, the first dot + * is replaced with an underscore. + * - Fixed: Pasting more than one line with the mouse in insert mode, didn't + * put the cursor after the last pasted character. + * - When pasting linewise text, put the '] mark on the last character of the + * last line, instead of the first character of the last line. + * - Fixed: on some Unix systems, when resizing the window while in a external + * command (e.g., ":!cat"), Vim would stop waiting for the child, causing + * trouble, because the child is still running. + * - Fixed: resizing the window while executing an external command, and + * 't_ti' and 't_te' are defined to swap display buffers, Vim would redraw + * in the wrong display buffer after the "hit RETURN" message. + * - Fixed: "va", "vA", "Vp", "VP", "Vs" and "VS" didn't set the cursor + * position used for up/down movements (e.g., when using "j" after them). + * - Fixed: in GUI version, after using "cw" visual selection by dragging the + * mouse didn't work. + * - Fixed: setting 'ttyscroll' to 0 caused scrolling of message to stop + * working. + * - Fixed: the "WARNING: file changed" message caused buffers to be flushed + * and subsequent commands not to be executed. + * - Fixed: in Insert mode, the message from "^O^G" would be + * overwritten by the mode message if 'showmode' set. + * - Fixed: Using ":bdel" when there is only one buffer with two windows, + * could cause a crash. + * - Changed: the '<' flag in 'cpoptions' now only switches off the + * recognizing of the <> form of key codes. The 'k' flag is now used for + * the recognizing of raw key codes. + * - Fixed: Typing ':' at the --more-- prompt, when displaying autocommands, + * caused extra linefeeds to be produced. + * - Fixed: Using 'tagrelative' and ":set tags=./../tags", filenames would + * contain "../" as many times as CTRL-] would be used. These are removed + * now. + * - Fixed: Extremely long error message could cause a crash (e.g., when + * using ":help ^A"). + * - Added check for negative value of 'textwidth'. + * - Fixed: On MS-DOS, getting the value of $HOME would cause the current + * directory for the drive to be changed. + */ + +/* + * Version[] is copied into the swap file (max. length is 10 chars). + * longVersion[] is used for the ":version" command and "Vim -h". + * Don't forget to update the numbers in version.h for Win32!!! + */ + +char *Version = "VIM 4.2"; +#ifdef HAVE_DATE_TIME +char *longVersion = "VIM - Vi IMproved 4.2 (1996 June 17, compiled " __DATE__ " " __TIME__ ")"; +#else +char *longVersion = "VIM - Vi IMproved 4.2 (1996 June 17)"; +#endif + +static void version_msg __ARGS((char *s)); + + void +do_version(arg) + char_u *arg; +{ + long n; + + if (*arg != NUL) + { + found_version = getdigits(&arg) * 100; + if (*arg == '.' && isdigit(arg[1])) + { + /* "4.1" -> 401, "4.10" -> 410 */ + n = arg[1] - '0'; + if (isdigit(arg[2])) + found_version += (arg[2] - '0') + n * 10; + else + found_version += n; + } + if (found_version > 400) + { + MSG("Warning: Found newer version command"); + if (sourcing_name != NULL) + { + MSG_OUTSTR(" in: \""); + msg_outstr(sourcing_name); + MSG_OUTSTR("\" line: "); + msg_outnum((long)sourcing_lnum); + } + } + } + else + { + msg_outchar('\n'); + MSG(longVersion); +#ifdef WIN32 + MSG_OUTSTR("\nWindows NT / Windows 95 version"); +#endif +#ifdef MSDOS +# ifdef DJGPP + MSG_OUTSTR("\n32 bit MS-DOS version"); +# else + MSG_OUTSTR("\n16 bit MS-DOS version"); +# endif +#endif + MSG_OUTSTR("\nCompiled with (+) or without (-):\n"); +#ifdef AMIGA /* only for Amiga systems */ +# ifdef NO_ARP + version_msg("-ARP "); +# else + version_msg("+ARP "); +# endif +#endif +#ifdef AUTOCMD + version_msg("+autocmd "); +#else + version_msg("-autocmd "); +#endif +#ifdef NO_BUILTIN_TCAPS + version_msg("-builtin_terms "); +#endif +#ifdef SOME_BUILTIN_TCAPS + version_msg("+builtin_terms "); +#endif +#ifdef ALL_BUILTIN_TCAPS + version_msg("++builtin_terms "); +#endif +#ifdef CINDENT + version_msg("+cindent "); +#else + version_msg("-cindent "); +#endif +#ifdef COMPATIBLE + version_msg("+compatible "); +#else + version_msg("-compatible "); +#endif +#ifdef DEBUG + version_msg("+debug "); +#endif +#ifdef DIGRAPHS + version_msg("+digraphs "); +#else + version_msg("-digraphs "); +#endif +#ifdef EMACS_TAGS + version_msg("+emacs_tags "); +#else + version_msg("-emacs_tags "); +#endif + /* only interesting on Unix systems */ +#if !defined(USE_SYSTEM) && defined(UNIX) + version_msg("+fork() "); +#endif +#ifdef UNIX +# ifdef USE_GUI_MOTIF + version_msg("+GUI_Motif "); +# else +# ifdef USE_GUI_ATHENA + version_msg("+GUI_Athena "); +# else + version_msg("-GUI "); +# endif +# endif +#endif +#ifdef INSERT_EXPAND + version_msg("+insert_expand "); +#else + version_msg("-insert_expand "); +#endif +#ifdef HAVE_LANGMAP + version_msg("+langmap "); +#else + version_msg("-langmap "); +#endif +#ifdef LISPINDENT + version_msg("+lispindent "); +#else + version_msg("-lispindent "); +#endif +#ifdef RIGHTLEFT + version_msg("+rightleft "); +#else + version_msg("-rightleft "); +#endif +#ifdef SMARTINDENT + version_msg("+smartindent "); +#else + version_msg("-smartindent "); +#endif + /* only interesting on Unix systems */ +#if defined(USE_SYSTEM) && (defined(UNIX) || defined(__EMX__)) + version_msg("+system() "); +#endif +#if defined(UNIX) || defined(__EMX__) +/* only unix (or OS/2 with EMX!) can have terminfo instead of termcap */ +# ifdef TERMINFO + version_msg("+terminfo "); +# else + version_msg("-terminfo "); +# endif +#else /* unix always includes termcap support */ +# ifdef HAVE_TGETENT + version_msg("+tgetent "); +# else + version_msg("-tgetent "); +# endif +#endif +#ifdef VIMINFO + version_msg("+viminfo "); +#else + version_msg("-viminfo "); +#endif +#ifdef WRITEBACKUP + version_msg("+writebackup "); +#else + version_msg("-writebackup "); +#endif +#ifdef UNIX +# if defined(WANT_X11) && defined(HAVE_X11) + version_msg("+X11 "); +# else + version_msg("-X11 "); +# endif +#endif + msg_outchar('\n'); +#ifdef USR_VIMRC_FILE + version_msg("user vimrc file: \""); + version_msg(USR_VIMRC_FILE); + version_msg("\" "); +#endif +#ifdef USR_EXRC_FILE + version_msg("user exrc file: \""); + version_msg(USR_EXRC_FILE); + version_msg("\" "); +#endif +#ifdef USE_GUI + version_msg("user gvimrc file: \""); + version_msg(USR_GVIMRC_FILE); + version_msg("\" "); +#endif +#if defined(HAVE_CONFIG_H) || defined(OS2) + msg_outchar('\n'); + version_msg("system vimrc file: \""); + version_msg((char *)sys_vimrc_fname); + version_msg("\""); + msg_outchar('\n'); + version_msg("system compatrc file: \""); + version_msg((char *)sys_compatrc_fname); + version_msg("\""); +# ifdef USE_GUI + msg_outchar('\n'); + version_msg("system gvimrc file: \""); + version_msg((char *)sys_gvimrc_fname); + MSG_OUTSTR("\""); +# endif + msg_outchar('\n'); + version_msg("Compilation: "); + version_msg((char *)all_cflags); +#endif + } +} + +/* + * Output a string for the version message. If it's going to wrap, output a + * newline, unless the message is too long to fit on the screen anyway. + */ + static void +version_msg(s) + char *s; +{ + int len = strlen(s); + + if (len < (int)Columns && msg_col + len >= (int)Columns) + msg_outchar('\n'); + MSG_OUTSTR(s); +} diff --git a/usr.bin/vim/version.h b/usr.bin/vim/version.h new file mode 100644 index 00000000000..c162463ca13 --- /dev/null +++ b/usr.bin/vim/version.h @@ -0,0 +1,32 @@ +/* $OpenBSD: version.h,v 1.1.1.1 1996/09/07 21:40:27 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * This file is currently only used for the Win32 version. + * Should probably generate all of this from the Makefile or in a separate + * little C program that reads a small file; e.g., version.dat: + * major=3 + * minor=29 + * build=101 + * patchlevel=0 + * date=1996 May 23 + */ + + +#define VIM_VERSION_MAJOR 4 +#define VIM_VERSION_MAJOR_STR "4" + +#define VIM_VERSION_MINOR 2 +#define VIM_VERSION_MINOR_STR "2" + +#define VIM_VERSION_BUILD 1 +#define VIM_VERSION_BUILD_STR "1" + +#define VIM_VERSION_PATCHLEVEL 0 +#define VIM_VERSION_PATCHLEVEL_STR "0" diff --git a/usr.bin/vim/vim.h b/usr.bin/vim/vim.h new file mode 100644 index 00000000000..e94a6f19c75 --- /dev/null +++ b/usr.bin/vim/vim.h @@ -0,0 +1,537 @@ +/* $OpenBSD: vim.h,v 1.1.1.1 1996/09/07 21:40:28 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* ============ the header file puzzle (ca. 50-100 pieces) ========= */ + +#ifdef HAVE_CONFIG_H /* GNU autoconf (or something else) was here */ +# include "config.h" +#endif + +#ifdef __EMX__ /* hand-edited config.h for OS/2 with EMX */ +# include "conf_os2.h" +#endif + +/* + * This is a bit of a wishlist. Currently we only have the Motif and Athena + * GUI. + */ +#if defined(USE_GUI_MOTIF) \ + || defined(USE_GUI_ATHENA) \ + || defined(USE_GUI_MAC) \ + || defined(USE_GUI_WINDOWS31) \ + || defined(USE_GUI_WIN32) \ + || defined(USE_GUI_OS2) +# ifndef USE_GUI +# define USE_GUI +# endif +#endif + +#include "feature.h" /* #defines for optionals and features */ + +/* + * Find out if function definitions should include argument types + */ +#ifdef AZTEC_C +# include +# define __ARGS(x) x +# define __PARMS(x) x +#endif + +#ifdef SASC +# include +# define __ARGS(x) x +# define __PARMS(x) x +#endif + +#ifdef _DCC +# include +# define __ARGS(x) x +# define __PARMS(x) x +#endif + +#ifdef __TURBOC__ +# define __ARGS(x) x +#endif + +#if defined(UNIX) || defined(__EMX__) +# include "unix.h" /* bring lots of system header files */ +#endif + +#ifdef VMS +# include "vms.h" +#endif + +#ifndef __ARGS +# if defined(__STDC__) || defined(__GNUC__) || defined(WIN32) +# define __ARGS(x) x +# else +# define __ARGS(x) () +# endif +#endif + +/* __ARGS and __PARMS are the same thing. */ +#ifndef __PARMS +# define __PARMS(x) __ARGS(x) +#endif + +#ifdef UNIX +# include "osdef.h" /* bring missing declarations in */ +#endif + +#ifdef __EMX__ +# define getcwd _getcwd2 +# define chdir _chdir2 +# undef CHECK_INODE +#endif + +#ifdef AMIGA +# include "amiga.h" +#endif + +#ifdef ARCHIE +# include "archie.h" +#endif + +#ifdef MSDOS +# include "msdos.h" +#endif + +#ifdef WIN32 +# include "win32.h" +#endif + +#ifdef MINT +# include "mint.h" +#endif + +/* + * maximum length of a file name and a path (for non-unix systems) + */ +#ifndef MAXNAMLEN +# define MAXNAMLEN 31 +#endif + +#ifndef MAXPATHL +# define MAXPATHL 128 /* not too long to put name on stack */ +#endif + +/* + * Shorthand for unsigned variables. Many systems, but not all, have u_char + * already defined, so we use char_u to avoid trouble. + */ +typedef unsigned char char_u; +typedef unsigned short short_u; +typedef unsigned int int_u; +typedef unsigned long long_u; + +#ifndef UNIX /* For Unix this is included in unix.h */ +#include +#include +#endif + +#if defined(HAVE_STRING_H) +# include +#else +# ifdef HAVE_STRINGS_H +# include +# endif +#endif + +#include "ascii.h" +#include "keymap.h" +#include "term.h" +#include "macros.h" + +#ifdef LATTICE +# include +# include +#endif +#ifdef _DCC +# include +#endif +#if defined MSDOS || defined WIN32 +# include +#endif + +/* allow other (non-unix) systems to configure themselves now */ +#ifndef UNIX +# ifdef HAVE_STAT_H +# include +# endif +# ifdef HAVE_STDLIB_H +# include +# endif +#endif /* NON-UNIX */ + +/* ================ end of the header file puzzle =============== */ + +/* + * flags for updateScreen() + * The higher the value, the higher the priority + */ +#define VALID 10 /* buffer not changed */ +#define INVERTED 20 /* redisplay inverted part */ +#define VALID_TO_CURSCHAR 30 /* buffer at/below cursor changed */ +#define NOT_VALID 40 /* buffer changed */ +#define CURSUPD 50 /* buffer changed, update cursor first */ +#define CLEAR 60 /* screen messed up, clear it */ + +/* + * Attributes for NextScreen. + */ +#define CHAR_NORMAL 0 +#define CHAR_INVERT 1 +#define CHAR_UNDERL 2 +#define CHAR_BOLD 3 +#define CHAR_STDOUT 4 +#define CHAR_ITALIC 5 + +/* + * values for State + * + * The lowest four bits are used to distinguish normal/visual/cmdline/ + * insert+replace mode. This is used for mapping. If none of these bits are + * set, no mapping is done. + * The upper four bits are used to distinguish between other states. + */ +#define NORMAL 0x01 +#define VISUAL 0x02 +#define CMDLINE 0x04 +#define INSERT 0x08 +#define NORMAL_BUSY 0x11 /* busy interpreting a command */ +#define REPLACE 0x28 /* replace mode */ +#define HITRETURN 0x61 /* waiting for a return */ +#define ASKMORE 0x70 /* Asking if you want --more-- */ +#define SETWSIZE 0x80 /* window size has changed */ +#define ABBREV 0x90 /* abbreviation instead of mapping */ +#define EXTERNCMD 0xa0 /* executing an external command */ + +/* directions */ +#define FORWARD 1 +#define BACKWARD (-1) +#define BOTH_DIRECTIONS 2 + +/* return values for functions */ +#define OK 1 +#define FAIL 0 + +/* + * values for command line completion + */ +#define CONTEXT_UNKNOWN (-2) +#define EXPAND_UNSUCCESSFUL (-1) +#define EXPAND_NOTHING 0 +#define EXPAND_COMMANDS 1 +#define EXPAND_FILES 2 +#define EXPAND_DIRECTORIES 3 +#define EXPAND_SETTINGS 4 +#define EXPAND_BOOL_SETTINGS 5 +#define EXPAND_TAGS 6 +#define EXPAND_OLD_SETTING 7 +#define EXPAND_HELP 8 +#define EXPAND_BUFFERS 9 +#define EXPAND_EVENTS 10 +#define EXPAND_MENUS 11 + +/* Values for nextwild() and ExpandOne(). See ExpandOne() for meaning. */ +#define WILD_FREE 1 +#define WILD_EXPAND_FREE 2 +#define WILD_EXPAND_KEEP 3 +#define WILD_NEXT 4 +#define WILD_PREV 5 +#define WILD_ALL 6 +#define WILD_LONGEST 7 + +#define WILD_LIST_NOTFOUND 1 +#define WILD_HOME_REPLACE 2 + +/* Values for the find_pattern_in_path() function args 'type' and 'action': */ +#define FIND_ANY 1 +#define FIND_DEFINE 2 +#define CHECK_PATH 3 + +#define ACTION_SHOW 1 +#define ACTION_GOTO 2 +#define ACTION_SPLIT 3 +#define ACTION_SHOW_ALL 4 +#ifdef INSERT_EXPAND +# define ACTION_EXPAND 5 +#endif + +/* Values for 'options' argument in do_search() and searchit() */ +#define SEARCH_REV 0x01 /* go in reverse of previous dir. */ +#define SEARCH_ECHO 0x02 /* echo the search command and handle options */ +#define SEARCH_MSG 0x0c /* give messages (yes, it's not 0x04) */ +#define SEARCH_NFMSG 0x08 /* give all messages except not found */ +#define SEARCH_OPT 0x10 /* interpret optional flags */ +#define SEARCH_HIS 0x20 /* put search pattern in history */ +#define SEARCH_END 0x40 /* put cursor at end of match */ +#define SEARCH_NOOF 0x80 /* don't add offset to position */ +#define SEARCH_START 0x100 /* start search without col offset */ +#define SEARCH_MARK 0x200 /* set previous context mark */ +#define SEARCH_KEEP 0x400 /* keep previous search pattern */ + +/* Values for find_ident_under_cursor() */ +#define FIND_IDENT 1 /* find identifier (word) */ +#define FIND_STRING 2 /* find any string (WORD) */ + +/* Values for get_file_name_in_path() */ +#define FNAME_MESS 1 /* give error message */ +#define FNAME_EXP 2 /* expand to path */ +#define FNAME_HYP 4 /* check for hypertext link */ + +/* Values for buflist_getfile() */ +#define GETF_SETMARK 0x01 /* set pcmark before jumping */ +#define GETF_ALT 0x02 /* jumping to alternate file (not buf num) */ + +/* Values for in_indentkeys() */ +#define KEY_OPEN_FORW 0x101 +#define KEY_OPEN_BACK 0x102 + +/* Values for call_shell() second argument */ +#define SHELL_FILTER 1 /* filtering text */ +#define SHELL_EXPAND 2 /* expanding wildcards */ +#define SHELL_COOKED 4 /* set term to cooked mode */ + +/* Values for change_indent() */ +#define INDENT_SET 1 /* set indent */ +#define INDENT_INC 2 /* increase indent */ +#define INDENT_DEC 3 /* decrease indent */ + +/* Values for flags argument for findmatchlimit() */ +#define FM_BACKWARD 0x01 /* search backwards */ +#define FM_FORWARD 0x02 /* search forwards */ +#define FM_BLOCKSTOP 0x04 /* stop at start/end of block */ +#define FM_SKIPCOMM 0x08 /* skip comments */ + +/* Values for action argument for do_buffer() */ +#define DOBUF_GOTO 0 /* go to specified buffer */ +#define DOBUF_SPLIT 1 /* split window and go to specified buffer */ +#define DOBUF_UNLOAD 2 /* unload specified buffer(s) */ +#define DOBUF_DEL 3 /* delete specified buffer(s) */ + +/* Values for start argument for do_buffer() */ +#define DOBUF_CURRENT 0 /* "count" buffer from current buffer */ +#define DOBUF_FIRST 1 /* "count" buffer from first buffer */ +#define DOBUF_LAST 2 /* "count" buffer from last buffer */ +#define DOBUF_MOD 3 /* "count" mod. buffer from current buffer */ + +/* Values for sub_cmd and which_pat argument for myregcomp() */ +/* Also used for which_pat argument for searchit() */ +#define RE_SEARCH 0 /* save/use pat in/from search_pattern */ +#define RE_SUBST 1 /* save/use pat in/from subst_pattern */ +#define RE_BOTH 2 /* save pat in both patterns */ +#define RE_LAST 2 /* use last used pattern if "pat" is NULL */ + +/* Return values for fullpathcmp() */ +#define FPC_SAME 1 /* both exist and are the same file. */ +#define FPC_DIFF 2 /* both exist and are different files. */ +#define FPC_NOTX 3 /* both don't exist. */ +#define FPC_DIFFX 4 /* one of them doesn't exist. */ + +/* + * Events for autocommands. + */ +enum auto_events +{ + EVENT_BUFENTER = 0, /* after entering a buffer */ + EVENT_BUFLEAVE, /* before leaving a buffer */ + EVENT_BUFNEWFILE, /* when creating a buffer for a new file */ + EVENT_BUFREADPOST, /* after reading a buffer */ + EVENT_BUFREADPRE, /* before reading a buffer */ + EVENT_BUFWRITEPOST, /* after writing a buffer */ + EVENT_BUFWRITEPRE, /* before writing a buffer */ + EVENT_FILEAPPENDPOST, /* after appending to a file */ + EVENT_FILEAPPENDPRE, /* before appending to a file */ + EVENT_FILEREADPOST, /* after reading a file */ + EVENT_FILEREADPRE, /* before reading a file */ + EVENT_FILEWRITEPOST, /* after writing a file */ + EVENT_FILEWRITEPRE, /* before writing a file */ + EVENT_FILTERREADPOST, /* after reading from a filter */ + EVENT_FILTERREADPRE, /* before reading from a filter */ + EVENT_FILTERWRITEPOST, /* after writing to a filter */ + EVENT_FILTERWRITEPRE, /* before writing to a filter */ + EVENT_VIMLEAVE, /* before exiting Vim */ + EVENT_WINENTER, /* after entering a window */ + EVENT_WINLEAVE, /* before leaving a window */ + NUM_EVENTS /* MUST be the last one */ +}; + +/* + * Boolean constants + */ +#ifndef TRUE +# define FALSE 0 /* note: this is an int, not a long! */ +# define TRUE 1 +#endif + +#define MAYBE 2 /* for beginline() and the 'sol' option */ + +/* May be returned by add_new_completion(): */ +#define RET_ERROR (-1) + +/* + * jump_to_mouse() returns one of these values, possibly with + * CURSOR_MOVED added + */ +#define IN_UNKNOWN 1 +#define IN_BUFFER 2 +#define IN_STATUS_LINE 3 /* Or in command line */ +#define CURSOR_MOVED 0x100 + +/* flags for jump_to_mouse() */ +#define MOUSE_FOCUS 0x1 /* if used, need to stay in this window */ +#define MOUSE_MAY_VIS 0x2 /* if used, may set visual mode */ +#define MOUSE_DID_MOVE 0x4 /* if used, only act when mouse has moved */ +#define MOUSE_SETPOS 0x8 /* if used, only set current mouse position */ + +/* + * Minimum screen size + */ +#define MIN_COLUMNS 12 /* minimal columns for screen */ +#define MIN_ROWS 1 /* minimal rows for one window */ +#define STATUS_HEIGHT 1 /* height of a status line under a window */ + +/* + * Buffer sizes + */ +#ifndef CMDBUFFSIZE +# define CMDBUFFSIZE 256 /* size of the command processing buffer */ +#endif + +#define LSIZE 512 /* max. size of a line in the tags file */ + +#define IOSIZE (1024+1) /* file i/o and sprintf buffer size */ +#define MSG_BUF_LEN 80 /* length of buffer for small messages */ + +#define TERMBUFSIZE 1024 + +#if defined(AMIGA) || defined(__linux__) +# define TBUFSZ 2048 /* buffer size for termcap entry */ +#else +# define TBUFSZ 1024 /* buffer size for termcap entry */ +#endif + +/* + * Maximum length of key sequence to be mapped. + * Must be able to hold an Amiga resize report. + */ +#define MAXMAPLEN 50 + +#ifdef BINARY_FILE_IO +# define WRITEBIN "wb" /* no CR-LF translation */ +# define READBIN "rb" +# define APPENDBIN "ab" +#else +# define WRITEBIN "w" +# define READBIN "r" +# define APPENDBIN "a" +#endif + +/* + * EMX doesn't have a global way of making open() use binary I/O. + * Use O_BINARY for all open() calls. + */ +#ifdef __EMX__ +# define O_EXTRA O_BINARY +#else +# define O_EXTRA 0 +#endif + +#define CHANGED set_Changed() +#define UNCHANGED(buf) unset_Changed(buf) + +/* + * defines to avoid typecasts from (char_u *) to (char *) and back + * (vim_strchr() and vim_strrchr() are now in alloc.c) + */ +#define STRLEN(s) strlen((char *)(s)) +#define STRCPY(d, s) strcpy((char *)(d), (char *)(s)) +#define STRNCPY(d, s, n) strncpy((char *)(d), (char *)(s), (size_t)(n)) +#define STRCMP(d, s) strcmp((char *)(d), (char *)(s)) +#define STRNCMP(d, s, n) strncmp((char *)(d), (char *)(s), (size_t)(n)) +#define STRCAT(d, s) strcat((char *)(d), (char *)(s)) +#define STRNCAT(d, s, n) strncat((char *)(d), (char *)(s), (size_t)(n)) + +#define MSG(s) msg((char_u *)(s)) +#define EMSG(s) emsg((char_u *)(s)) +#define EMSG2(s, p) emsg2((char_u *)(s), (char_u *)(p)) +#define EMSGN(s, n) emsgn((char_u *)(s), (long)(n)) +#define OUTSTR(s) outstr((char_u *)(s)) +#define OUTSTRN(s) outstrn((char_u *)(s)) +#define MSG_OUTSTR(s) msg_outstr((char_u *)(s)) + +typedef long linenr_t; /* line number type */ +typedef unsigned colnr_t; /* column number type */ + +#define MAXLNUM (0x7fffffff) /* maximum (invalid) line number */ + +#if SIZEOF_INT >= 4 +# define MAXCOL (0x7fffffff) /* maximum column number, 31 bits */ +#else +# define MAXCOL (0x7fff) /* maximum column number, 15 bits */ +#endif + +#define SHOWCMD_COLS 10 /* columns needed by shown command */ + +/* + * Include a prototype for vim_memmove(), it may not be in alloc.pro. + */ +#ifdef VIM_MEMMOVE +void vim_memmove __ARGS((void *, void *, size_t)); +#else +# ifndef vim_memmove +# define vim_memmove(to, from, len) memmove(to, from, len) +# endif +#endif + +/* + * For the Amiga we use a version of getenv that does local variables under 2.0 + * For Win32 and MSDOS we also check $HOME when $VIM is used. + */ +#if !defined(AMIGA) && !defined(WIN32) && !defined(MSDOS) && !defined(VMS) +# define vim_getenv(x) (char_u *)getenv((char *)x) +#endif + +/* + * fnamecmp() is used to compare filenames. + * On some systems case in a filename does not matter, on others it does. + * (this does not account for maximum name lengths and things like "../dir", + * thus it is not 100% accurate!) + */ +#ifdef CASE_INSENSITIVE_FILENAME +# define fnamecmp(x, y) stricmp((char *)(x), (char *)(y)) +# define fnamencmp(x, y, n) strnicmp((char *)(x), (char *)(y), (size_t)(n)) +#else +# define fnamecmp(x, y) strcmp((char *)(x), (char *)(y)) +# define fnamencmp(x, y, n) strncmp((char *)(x), (char *)(y), (size_t)(n)) +#endif + +#ifdef HAVE_MEMSET +# define vim_memset(ptr, c, size) memset((ptr), (c), (size)) +#else +void *vim_memset __ARGS((void *, int, size_t)); +#endif + +/* for MS-DOS and Win32: use chdir() that also changes the default drive */ +#ifdef USE_VIM_CHDIR +int vim_chdir __ARGS((char *)); +#else +# define vim_chdir chdir +#endif + +/* + * vim_iswhite() is used for "^" and the like. It differs from isspace() + * because it doesn't include and and the like. + */ +#define vim_iswhite(x) ((x) == ' ' || (x) == '\t') + +/* Note that gui.h is included by structs.h */ + +#include "structs.h" /* file that defines many structures */ diff --git a/usr.bin/vim/window.c b/usr.bin/vim/window.c new file mode 100644 index 00000000000..778db3eab83 --- /dev/null +++ b/usr.bin/vim/window.c @@ -0,0 +1,1570 @@ +/* $OpenBSD: window.c,v 1.1.1.1 1996/09/07 21:40:24 downsj Exp $ */ +/* vi:set ts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read a list of people who contributed. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +static void reset_VIsual __ARGS((void)); +static int win_comp_pos __ARGS((void)); +static void win_exchange __ARGS((long)); +static void win_rotate __ARGS((int, int)); +static void win_append __ARGS((WIN *, WIN *)); +static void win_remove __ARGS((WIN *)); +static void win_new_height __ARGS((WIN *, int)); + +static WIN *prevwin = NULL; /* previous window */ + +/* + * all CTRL-W window commands are handled here, called from normal(). + */ + void +do_window(nchar, Prenum) + int nchar; + long Prenum; +{ + long Prenum1; + WIN *wp; + char_u *ptr; + int len; + int type = -1; + WIN *wp2; + + if (Prenum == 0) + Prenum1 = 1; + else + Prenum1 = Prenum; + + switch (nchar) + { +/* split current window in two parts */ + case 'S': + case Ctrl('S'): + case 's': reset_VIsual(); /* stop Visual mode */ + win_split((int)Prenum, TRUE); + break; + +/* split current window and edit alternate file */ + case K_CCIRCM: + case '^': + reset_VIsual(); /* stop Visual mode */ + stuffReadbuff((char_u *)":split #"); + if (Prenum) + stuffnumReadbuff(Prenum); /* buffer number */ + stuffcharReadbuff('\n'); + break; + +/* open new window */ + case Ctrl('N'): + case 'n': reset_VIsual(); /* stop Visual mode */ + stuffcharReadbuff(':'); + if (Prenum) + stuffnumReadbuff(Prenum); /* window height */ + stuffReadbuff((char_u *)"new\n"); /* it is cmdline.c */ + break; + +/* quit current window */ + case Ctrl('Q'): + case 'q': reset_VIsual(); /* stop Visual mode */ + stuffReadbuff((char_u *)":quit\n"); /* it is cmdline.c */ + break; + +/* close current window */ + case Ctrl('C'): + case 'c': reset_VIsual(); /* stop Visual mode */ + stuffReadbuff((char_u *)":close\n"); /* it is cmdline.c */ + break; + +/* close all but current window */ + case Ctrl('O'): + case 'o': reset_VIsual(); /* stop Visual mode */ + stuffReadbuff((char_u *)":only\n"); /* it is cmdline.c */ + break; + +/* cursor to next window */ + case 'j': + case K_DOWN: + case Ctrl('J'): + for (wp = curwin; wp->w_next != NULL && Prenum1-- > 0; + wp = wp->w_next) + ; +new_win: + /* + * When jumping to another buffer, stop visual mode + * Do this before changing windows so we can yank the + * selection into the '"*' register. + */ + if (wp->w_buffer != curbuf && VIsual_active) + { + end_visual_mode(); + for (wp2 = firstwin; wp2 != NULL; wp2 = wp2->w_next) + if (wp2->w_buffer == curbuf && + wp2->w_redr_type < NOT_VALID) + { + wp2->w_redr_type = NOT_VALID; + redraw_later(NOT_VALID); + } + } + win_enter(wp, TRUE); + cursupdate(); + break; + +/* cursor to next window with wrap around */ + case Ctrl('W'): + case 'w': +/* cursor to previous window with wrap around */ + case 'W': + if (lastwin == firstwin) /* just one window */ + beep_flush(); + else + { + if (Prenum) /* go to specified window */ + { + for (wp = firstwin; --Prenum > 0; ) + { + if (wp->w_next == NULL) + break; + else + wp = wp->w_next; + } + } + else + { + if (nchar == 'W') /* go to previous window */ + { + wp = curwin->w_prev; + if (wp == NULL) + wp = lastwin; /* wrap around */ + } + else /* go to next window */ + { + wp = curwin->w_next; + if (wp == NULL) + wp = firstwin; /* wrap around */ + } + } + goto new_win; + } + break; + +/* cursor to window above */ + case 'k': + case K_UP: + case Ctrl('K'): + for (wp = curwin; wp->w_prev != NULL && Prenum1-- > 0; + wp = wp->w_prev) + ; + goto new_win; + +/* cursor to top window */ + case 't': + case Ctrl('T'): + wp = firstwin; + goto new_win; + +/* cursor to bottom window */ + case 'b': + case Ctrl('B'): + wp = lastwin; + goto new_win; + +/* cursor to last accessed (previous) window */ + case 'p': + case Ctrl('P'): + if (prevwin == NULL) + beep_flush(); + else + { + wp = prevwin; + goto new_win; + } + break; + +/* exchange current and next window */ + case 'x': + case Ctrl('X'): + win_exchange(Prenum); + break; + +/* rotate windows downwards */ + case Ctrl('R'): + case 'r': reset_VIsual(); /* stop Visual mode */ + win_rotate(FALSE, (int)Prenum1); /* downwards */ + break; + +/* rotate windows upwards */ + case 'R': reset_VIsual(); /* stop Visual mode */ + win_rotate(TRUE, (int)Prenum1); /* upwards */ + break; + +/* make all windows the same height */ + case '=': win_equal(NULL, TRUE); + break; + +/* increase current window height */ + case '+': win_setheight(curwin->w_height + (int)Prenum1); + break; + +/* decrease current window height */ + case '-': win_setheight(curwin->w_height - (int)Prenum1); + break; + +/* set current window height */ + case Ctrl('_'): + case '_': win_setheight(Prenum ? (int)Prenum : 9999); + break; + +/* jump to tag and split window if tag exists */ + case ']': + case Ctrl(']'): + reset_VIsual(); /* stop Visual mode */ + postponed_split = TRUE; + stuffcharReadbuff(Ctrl(']')); + break; + +/* edit file name under cursor in a new window */ + case 'f': + case Ctrl('F'): + reset_VIsual(); /* stop Visual mode */ + ptr = file_name_at_cursor(FNAME_MESS|FNAME_HYP|FNAME_EXP); + if (ptr != NULL) + { + setpcmark(); + if (win_split(0, FALSE) == OK) + (void)do_ecmd(0, ptr, NULL, NULL, p_hid, (linenr_t)0, FALSE); + vim_free(ptr); + } + break; + +/* Go to the first occurence of the identifier under cursor along path in a + * new window -- webb + */ + case 'i': /* Go to any match */ + case Ctrl('I'): + type = FIND_ANY; + /* FALLTHROUGH */ + case 'd': /* Go to definition, using p_def */ + case Ctrl('D'): + if (type == -1) + type = FIND_DEFINE; + + if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) + break; + find_pattern_in_path(ptr, len, TRUE, TRUE, type, + Prenum1, ACTION_SPLIT, (linenr_t)1, (linenr_t)MAXLNUM); + curwin->w_set_curswant = TRUE; + break; + + default: beep_flush(); + break; + } +} + + static void +reset_VIsual() +{ + if (VIsual_active) + { + end_visual_mode(); + update_curbuf(NOT_VALID); /* delete the inversion */ + } +} + +/* + * split the current window, implements CTRL-W s and :split + * + * new_height is the height for the new window, 0 to make half of current + * height redraw is TRUE when redraw now + * + * return FAIL for failure, OK otherwise + */ + int +win_split(new_height, redraw) + int new_height; + int redraw; +{ + WIN *wp; + linenr_t lnum; + int h; + int i; + int need_status; + int do_equal = (p_ea && new_height == 0); + int needed; + int available; + int curwin_height; + + /* add a status line when p_ls == 1 and splitting the first window */ + if (lastwin == firstwin && p_ls == 1 && curwin->w_status_height == 0) + need_status = STATUS_HEIGHT; + else + need_status = 0; + +/* + * check if we are able to split the current window and compute its height + */ + available = curwin->w_height; + needed = 2 * MIN_ROWS + STATUS_HEIGHT + need_status; + if (p_ea) + { + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp != curwin) + { + available += wp->w_height; + needed += MIN_ROWS; + } + } + if (available < needed) + { + EMSG(e_noroom); + return FAIL; + } + curwin_height = curwin->w_height; + if (need_status) + { + curwin->w_status_height = STATUS_HEIGHT; + curwin_height -= STATUS_HEIGHT; + } + if (new_height == 0) + new_height = curwin_height / 2; + + if (new_height > curwin_height - MIN_ROWS - STATUS_HEIGHT) + new_height = curwin_height - MIN_ROWS - STATUS_HEIGHT; + + if (new_height < MIN_ROWS) + new_height = MIN_ROWS; + + /* if it doesn't fit in the current window, need win_equal() */ + if (curwin_height - new_height - STATUS_HEIGHT < MIN_ROWS) + do_equal = TRUE; +/* + * allocate new window structure and link it in the window list + */ + if (p_sb) /* new window below current one */ + wp = win_alloc(curwin); + else + wp = win_alloc(curwin->w_prev); + if (wp == NULL) + return FAIL; +/* + * compute the new screen positions + */ + win_new_height(wp, new_height); + win_new_height(curwin, curwin_height - (new_height + STATUS_HEIGHT)); + if (p_sb) /* new window below current one */ + { + wp->w_winpos = curwin->w_winpos + curwin->w_height + STATUS_HEIGHT; + wp->w_status_height = curwin->w_status_height; + curwin->w_status_height = STATUS_HEIGHT; + } + else /* new window above current one */ + { + wp->w_winpos = curwin->w_winpos; + wp->w_status_height = STATUS_HEIGHT; + curwin->w_winpos = wp->w_winpos + wp->w_height + STATUS_HEIGHT; + } +/* + * make the contents of the new window the same as the current one + */ + wp->w_buffer = curbuf; + curbuf->b_nwindows++; + wp->w_cursor = curwin->w_cursor; + wp->w_row = curwin->w_row; + wp->w_col = curwin->w_col; + wp->w_virtcol = curwin->w_virtcol; + wp->w_curswant = curwin->w_curswant; + wp->w_set_curswant = curwin->w_set_curswant; + wp->w_empty_rows = curwin->w_empty_rows; + wp->w_leftcol = curwin->w_leftcol; + wp->w_pcmark = curwin->w_pcmark; + wp->w_prev_pcmark = curwin->w_prev_pcmark; + wp->w_alt_fnum = curwin->w_alt_fnum; + + wp->w_arg_idx = curwin->w_arg_idx; + /* + * copy tagstack and options from existing window + */ + for (i = 0; i < curwin->w_tagstacklen; i++) + { + wp->w_tagstack[i].fmark = curwin->w_tagstack[i].fmark; + wp->w_tagstack[i].tagname = strsave(curwin->w_tagstack[i].tagname); + } + wp->w_tagstackidx = curwin->w_tagstackidx; + wp->w_tagstacklen = curwin->w_tagstacklen; + win_copy_options(curwin, wp); +/* + * Both windows need redrawing + */ + wp->w_redr_type = NOT_VALID; + wp->w_redr_status = TRUE; + curwin->w_redr_type = NOT_VALID; + curwin->w_redr_status = TRUE; +/* + * Cursor is put in middle of window in both windows + */ + if (wp->w_height < curwin->w_height) /* use smallest of two heights */ + h = wp->w_height; + else + h = curwin->w_height; + h >>= 1; + for (lnum = wp->w_cursor.lnum; lnum > 1; --lnum) + { + h -= plines(lnum); + if (h <= 0) + break; + } + wp->w_topline = lnum; + curwin->w_topline = lnum; + if (need_status) + { + msg_pos((int)Rows - 1, sc_col); + msg_clr_eos(); /* Old command/ruler may still be there -- webb */ + comp_col(); + msg_pos((int)Rows - 1, 0); /* put position back at start of line */ + } +/* + * make the new window the current window and redraw + */ + if (do_equal) + win_equal(wp, FALSE); + win_enter(wp, FALSE); + + if (redraw) + updateScreen(NOT_VALID); + + return OK; +} + +/* + * Return the number of windows. + */ + int +win_count() +{ + WIN *wp; + int count = 0; + + for (wp = firstwin; wp != NULL; wp = wp->w_next) + ++count; + return count; +} + +/* + * Make 'count' windows on the screen. + * Return actual number of windows on the screen. + * Must be called when there is just one window, filling the whole screen + * (excluding the command line). + */ + int +make_windows(count) + int count; +{ + int maxcount; + int todo; + int p_sb_save; + +/* + * Each window needs at least MIN_ROWS lines and a status line. + * Add 4 lines for one window, otherwise we may end up with all one-line + * windows. Use value of 'winheight' if it is set + */ + maxcount = (curwin->w_height + curwin->w_status_height - + (p_wh ? (p_wh - 1) : 4)) / (MIN_ROWS + STATUS_HEIGHT); + if (maxcount < 2) + maxcount = 2; + if (count > maxcount) + count = maxcount; + + /* + * add status line now, otherwise first window will be too big + */ + if ((p_ls == 2 || (count > 1 && p_ls == 1)) && curwin->w_status_height == 0) + { + curwin->w_status_height = STATUS_HEIGHT; + win_new_height(curwin, curwin->w_height - STATUS_HEIGHT); + } + +/* + * set 'splitbelow' off for a moment, don't want that now + */ + p_sb_save = p_sb; + p_sb = FALSE; + /* todo is number of windows left to create */ + for (todo = count - 1; todo > 0; --todo) + if (win_split(curwin->w_height - (curwin->w_height - todo + * STATUS_HEIGHT) / (todo + 1) - STATUS_HEIGHT, FALSE) == FAIL) + break; + p_sb = p_sb_save; + + /* return actual number of windows */ + return (count - todo); +} + +/* + * Exchange current and next window + */ + static void +win_exchange(Prenum) + long Prenum; +{ + WIN *wp; + WIN *wp2; + int temp; + + if (lastwin == firstwin) /* just one window */ + { + beep_flush(); + return; + } + +/* + * find window to exchange with + */ + if (Prenum) + { + wp = firstwin; + while (wp != NULL && --Prenum > 0) + wp = wp->w_next; + } + else if (curwin->w_next != NULL) /* Swap with next */ + wp = curwin->w_next; + else /* Swap last window with previous */ + wp = curwin->w_prev; + + if (wp == curwin || wp == NULL) + return; + +/* + * 1. remove curwin from the list. Remember after which window it was in wp2 + * 2. insert curwin before wp in the list + * if wp != wp2 + * 3. remove wp from the list + * 4. insert wp after wp2 + * 5. exchange the status line height + */ + wp2 = curwin->w_prev; + win_remove(curwin); + win_append(wp->w_prev, curwin); + if (wp != wp2) + { + win_remove(wp); + win_append(wp2, wp); + } + temp = curwin->w_status_height; + curwin->w_status_height = wp->w_status_height; + wp->w_status_height = temp; + + win_comp_pos(); /* recompute window positions */ + + win_enter(wp, TRUE); + cursupdate(); + updateScreen(CLEAR); + +#ifdef USE_GUI + if (gui.in_use) + { + if (gui.which_scrollbars[SB_LEFT]) + gui_mch_reorder_scrollbars(SB_LEFT); + if (gui.which_scrollbars[SB_RIGHT]) + gui_mch_reorder_scrollbars(SB_RIGHT); + } +#endif +} + +/* + * rotate windows: if upwards TRUE the second window becomes the first one + * if upwards FALSE the first window becomes the second one + */ + static void +win_rotate(upwards, count) + int upwards; + int count; +{ + WIN *wp; + int height; + + if (firstwin == lastwin) /* nothing to do */ + { + beep_flush(); + return; + } + while (count--) + { + if (upwards) /* first window becomes last window */ + { + wp = firstwin; + win_remove(wp); + win_append(lastwin, wp); + wp = lastwin->w_prev; /* previously last window */ + } + else /* last window becomes first window */ + { + wp = lastwin; + win_remove(lastwin); + win_append(NULL, wp); + wp = firstwin; /* previously last window */ + } + /* exchange status height of old and new last window */ + height = lastwin->w_status_height; + lastwin->w_status_height = wp->w_status_height; + wp->w_status_height = height; + + /* recompute w_winpos for all windows */ + (void) win_comp_pos(); + } + + cursupdate(); + updateScreen(CLEAR); + +#ifdef USE_GUI + if (gui.in_use) + { + if (gui.which_scrollbars[SB_LEFT]) + gui_mch_reorder_scrollbars(SB_LEFT); + if (gui.which_scrollbars[SB_RIGHT]) + gui_mch_reorder_scrollbars(SB_RIGHT); + } +#endif +} + +/* + * Make all windows the same height. + * 'next_curwin' will soon be the current window, make sure it has enough + * rows. + */ + void +win_equal(next_curwin, redraw) + WIN *next_curwin; /* pointer to current window to be */ + int redraw; +{ + int total; + int less; + int wincount; + int winpos; + int temp; + WIN *wp; + int new_height; + +/* + * count the number of lines available + */ + total = 0; + wincount = 0; + for (wp = firstwin; wp; wp = wp->w_next) + { + total += wp->w_height - MIN_ROWS; + wincount++; + } + +/* + * If next_curwin given and 'winheight' set, make next_curwin p_wh lines. + */ + less = 0; + if (next_curwin != NULL) + { + if (p_wh) + { + if (p_wh - MIN_ROWS > total) /* all lines go to current window */ + less = total; + else + { + less = p_wh - MIN_ROWS - total / wincount; + if (less < 0) + less = 0; + } + } + } + +/* + * spread the available lines over the windows + */ + winpos = 0; + for (wp = firstwin; wp != NULL; wp = wp->w_next) + { + if (wp == next_curwin && less) + { + less = 0; + temp = p_wh - MIN_ROWS; + if (temp > total) + temp = total; + } + else + temp = (total - less + (wincount >> 1)) / wincount; + new_height = MIN_ROWS + temp; + if (wp->w_winpos != winpos || wp->w_height != new_height) + { + wp->w_redr_type = NOT_VALID; + wp->w_redr_status = TRUE; + } + wp->w_winpos = winpos; + win_new_height(wp, new_height); + total -= temp; + --wincount; + winpos += wp->w_height + wp->w_status_height; + } + if (redraw) + { + cursupdate(); + updateScreen(CLEAR); + } +} + +/* + * close all windows for buffer 'buf' + */ + void +close_windows(buf) + BUF *buf; +{ + WIN *win; + + ++RedrawingDisabled; + for (win = firstwin; win != NULL && lastwin != firstwin; ) + { + if (win->w_buffer == buf) + { + close_window(win, FALSE); + win = firstwin; /* go back to the start */ + } + else + win = win->w_next; + } + --RedrawingDisabled; +} + +/* + * close window "win" + * If "free_buf" is TRUE related buffer may be freed. + * + * called by :quit, :close, :xit, :wq and findtag() + */ + void +close_window(win, free_buf) + WIN *win; + int free_buf; +{ + WIN *wp; + + if (lastwin == firstwin) + { + EMSG("Cannot close last window"); + return; + } + +/* + * Remove the window. + * if 'splitbelow' the free space goes to the window above it. + * if 'nosplitbelow' the free space goes to the window below it. + * This makes opening a window and closing it immediately keep the same window + * layout. + */ + /* freed space goes to next window */ + if ((!p_sb && win->w_next != NULL) || win->w_prev == NULL) + { + wp = win->w_next; + wp->w_winpos = win->w_winpos; + } + else /* freed space goes to previous window */ + wp = win->w_prev; + win_new_height(wp, wp->w_height + win->w_height + win->w_status_height); + +#ifdef AUTOCMD + if (win == curwin) + { + if (wp->w_buffer != curbuf) + apply_autocmds(EVENT_BUFLEAVE, NULL, NULL); + apply_autocmds(EVENT_WINLEAVE, NULL, NULL); + } +#endif + +/* + * Close the link to the buffer. + */ + close_buffer(win, win->w_buffer, free_buf, FALSE); + + win_free(win); + if (win == curwin) + curwin = NULL; + if (p_ea) + win_equal(wp, FALSE); + if (curwin == NULL) + win_enter(wp, FALSE); + /* + * if last window has status line now and we don't want one, + * remove the status line + */ + if (lastwin->w_status_height && + (p_ls == 0 || (p_ls == 1 && firstwin == lastwin))) + { + win_new_height(lastwin, lastwin->w_height + lastwin->w_status_height); + lastwin->w_status_height = 0; + comp_col(); + } + + updateScreen(NOT_VALID); + if (RedrawingDisabled) + comp_Botline(wp); /* need to do this before cursupdate() */ +} + +/* + * close all windows except current one + * buffers in the windows become hidden + * + * called by :only and do_arg_all(); + */ + void +close_others(message) + int message; +{ + WIN *wp; + WIN *nextwp; + + if (lastwin == firstwin) + { + if (message +#ifdef AUTOCMD + && !autocmd_busy +#endif + ) + MSG("Already only one window"); + return; + } + + for (wp = firstwin; wp != NULL; wp = nextwp) + { + nextwp = wp->w_next; + if (wp == curwin) /* don't close current window */ + continue; + /* + * Close the link to the buffer. + */ + close_buffer(wp, wp->w_buffer, FALSE, FALSE); + + /* + * Remove the window. All lines go to current window. + */ + win_new_height(curwin, + curwin->w_height + wp->w_height + wp->w_status_height); + win_free(wp); + } + + /* + * If current window has status line and we don't want one, + * remove the status line. + */ + if (curwin->w_status_height && p_ls != 2) + { + win_new_height(curwin, curwin->w_height + curwin->w_status_height); + curwin->w_status_height = 0; + } + curwin->w_winpos = 0; /* put current window at top of the screen */ + if (message) + updateScreen(NOT_VALID); +} + +/* + * init the cursor in the window + * + * called when a new file is being edited + */ + void +win_init(wp) + WIN *wp; +{ + wp->w_redr_type = NOT_VALID; + wp->w_cursor.lnum = 1; + wp->w_curswant = wp->w_cursor.col = 0; + wp->w_pcmark.lnum = 1; /* pcmark not cleared but set to line 1 */ + wp->w_pcmark.col = 0; + wp->w_prev_pcmark.lnum = 0; + wp->w_prev_pcmark.col = 0; + wp->w_topline = 1; + wp->w_botline = 2; +} + +/* + * make window wp the current window + */ + void +win_enter(wp, undo_sync) + WIN *wp; + int undo_sync; +{ +#ifdef AUTOCMD + int other_buffer = FALSE; +#endif + + if (wp == curwin) /* nothing to do */ + return; + +#ifdef AUTOCMD + if (curwin != NULL) + { + if (wp->w_buffer != curbuf) + { + apply_autocmds(EVENT_BUFLEAVE, NULL, NULL); + other_buffer = TRUE; + } + apply_autocmds(EVENT_WINLEAVE, NULL, NULL); + } +#endif + + /* sync undo before leaving the current buffer */ + if (undo_sync && curbuf != wp->w_buffer) + u_sync(); + /* may have to copy the buffer options when 'cpo' contains 'S' */ + if (wp->w_buffer != curbuf) + buf_copy_options(curbuf, wp->w_buffer, TRUE); + if (curwin != NULL) + prevwin = curwin; /* remember for CTRL-W p */ + curwin = wp; + curbuf = wp->w_buffer; + +#ifdef AUTOCMD + apply_autocmds(EVENT_WINENTER, NULL, NULL); + if (other_buffer) + apply_autocmds(EVENT_BUFENTER, NULL, NULL); +#endif + + maketitle(); + /* set window height to desired minimal value */ + if (p_wh && curwin->w_height < p_wh) + win_setheight((int)p_wh); +#ifdef USE_MOUSE + setmouse(); /* in case jumped to/from help buffer */ +#endif +} + +/* + * allocate a window structure and link it in the window list + */ + WIN * +win_alloc(after) + WIN *after; +{ + WIN *newwin; + +/* + * allocate window structure and linesizes arrays + */ + newwin = (WIN *)alloc((unsigned)sizeof(WIN)); + if (newwin) + { +/* + * most stucture members have to be zero + */ + (void)vim_memset(newwin, 0, sizeof(WIN)); +/* + * link the window in the window list + */ + win_append(after, newwin); + + win_alloc_lsize(newwin); + + /* position the display and the cursor at the top of the file. */ + newwin->w_topline = 1; + newwin->w_botline = 2; + newwin->w_cursor.lnum = 1; + +#ifdef USE_GUI + /* Let the GUI know this is a new scrollbar */ + newwin->w_scrollbar.height = 0; +#endif /* USE_GUI */ + } + return newwin; +} + +/* + * remove window 'wp' from the window list and free the structure + */ + void +win_free(wp) + WIN *wp; +{ + int i; + + if (prevwin == wp) + prevwin = NULL; + win_free_lsize(wp); + + for (i = 0; i < wp->w_tagstacklen; ++i) + free(wp->w_tagstack[i].tagname); + +#ifdef USE_GUI + if (gui.in_use) + gui_mch_destroy_scrollbar(wp); +#endif /* USE_GUI */ + + win_remove(wp); + vim_free(wp); +} + + static void +win_append(after, wp) + WIN *after, *wp; +{ + WIN *before; + + if (after == NULL) /* after NULL is in front of the first */ + before = firstwin; + else + before = after->w_next; + + wp->w_next = before; + wp->w_prev = after; + if (after == NULL) + firstwin = wp; + else + after->w_next = wp; + if (before == NULL) + lastwin = wp; + else + before->w_prev = wp; +} + +/* + * remove window from the window list + */ + static void +win_remove(wp) + WIN *wp; +{ + if (wp->w_prev) + wp->w_prev->w_next = wp->w_next; + else + firstwin = wp->w_next; + if (wp->w_next) + wp->w_next->w_prev = wp->w_prev; + else + lastwin = wp->w_prev; +} + +/* + * allocate lsize arrays for a window + * return FAIL for failure, OK for success + */ + int +win_alloc_lsize(wp) + WIN *wp; +{ + wp->w_lsize_valid = 0; + wp->w_lsize_lnum = (linenr_t *) malloc((size_t) (Rows * sizeof(linenr_t))); + wp->w_lsize = (char_u *)malloc((size_t) Rows); + if (wp->w_lsize_lnum == NULL || wp->w_lsize == NULL) + { + win_free_lsize(wp); /* one of the two may have worked */ + wp->w_lsize_lnum = NULL; + wp->w_lsize = NULL; + return FAIL; + } + return OK; +} + +/* + * free lsize arrays for a window + */ + void +win_free_lsize(wp) + WIN *wp; +{ + vim_free(wp->w_lsize_lnum); + vim_free(wp->w_lsize); +} + +/* + * call this fuction whenever Rows changes value + */ + void +screen_new_rows() +{ + WIN *wp; + int extra_lines; + + if (firstwin == NULL) /* not initialized yet */ + return; +/* + * the number of extra lines is the difference between the position where + * the command line should be and where it is now + */ + compute_cmdrow(); + extra_lines = Rows - p_ch - cmdline_row; + if (extra_lines < 0) /* reduce windows height */ + { + for (wp = lastwin; wp; wp = wp->w_prev) + { + if (wp->w_height - MIN_ROWS < -extra_lines) + { + extra_lines += wp->w_height - MIN_ROWS; + win_new_height(wp, MIN_ROWS); + } + else + { + win_new_height(wp, wp->w_height + extra_lines); + break; + } + } + (void)win_comp_pos(); /* compute w_winpos */ + } + else if (extra_lines > 0) /* increase height of last window */ + win_new_height(lastwin, lastwin->w_height + extra_lines); + + compute_cmdrow(); + + if (p_ea) + win_equal(curwin, FALSE); +} + +/* + * update the w_winpos field for all windows + * returns the row just after the last window + */ + static int +win_comp_pos() +{ + WIN *wp; + int row; + + row = 0; + for (wp = firstwin; wp != NULL; wp = wp->w_next) + { + if (wp->w_winpos != row) /* if position changes, redraw */ + { + wp->w_winpos = row; + wp->w_redr_type = NOT_VALID; + wp->w_redr_status = TRUE; + } + row += wp->w_height + wp->w_status_height; + } + return row; +} + +/* + * set current window height + */ + void +win_setheight(height) + int height; +{ + WIN *wp; + int room; /* total number of lines available */ + int take; /* number of lines taken from other windows */ + int room_cmdline; /* lines available from cmdline */ + int row; + int run; + + if (height < MIN_ROWS) /* need at least some lines */ + height = MIN_ROWS; +/* + * compute the room we have from all the windows + */ + room = MIN_ROWS; /* count the MIN_ROWS for the current window */ + for (wp = firstwin; wp != NULL; wp = wp->w_next) + room += wp->w_height - MIN_ROWS; +/* + * compute the room available from the command line + */ + room_cmdline = Rows - p_ch - cmdline_row; +/* + * limit new height to the room available + */ + if (height > room + room_cmdline) /* can't make it that large */ + height = room + room_cmdline; /* use all available room */ +/* + * compute the number of lines we will take from the windows (can be negative) + */ + take = height - curwin->w_height; + if (take == 0) /* no change, nothing to do */ + return; + + if (take > 0) + { + take -= room_cmdline; /* use lines from cmdline first */ + if (take < 0) + take = 0; + } +/* + * set the current window to the new height + */ + win_new_height(curwin, height); + +/* + * First take lines from the windows below the current window. + * If that is not enough, takes lines from windows above the current window. + */ + for (run = 0; run < 2; ++run) + { + if (run == 0) + wp = curwin->w_next; /* 1st run: start with next window */ + else + wp = curwin->w_prev; /* 2nd run: start with prev window */ + while (wp != NULL && take != 0) + { + if (wp->w_height - take < MIN_ROWS) + { + take -= wp->w_height - MIN_ROWS; + win_new_height(wp, MIN_ROWS); + } + else + { + win_new_height(wp, wp->w_height - take); + take = 0; + } + if (run == 0) + wp = wp->w_next; + else + wp = wp->w_prev; + } + } + +/* recompute the window positions */ + row = win_comp_pos(); + +/* + * If there is extra space created between the last window and the command line, + * clear it. + */ + screen_fill(row, cmdline_row, 0, (int)Columns, ' ', ' '); + cmdline_row = row; + + updateScreen(NOT_VALID); +} + +#ifdef USE_MOUSE + void +win_drag_status_line(offset) + int offset; +{ + WIN *wp; + int room; + int row; + int up; /* if TRUE, drag status line up, otherwise down */ + + if (offset < 0) + { + up = TRUE; + offset = -offset; + } + else + up = FALSE; + + if (up) /* drag up */ + { + room = 0; + for (wp = curwin; wp != NULL && room < offset; wp = wp->w_prev) + room += wp->w_height - MIN_ROWS; + wp = curwin->w_next; /* put wp at window that grows */ + } + else /* drag down */ + { + /* + * Only dragging the last status line can reduce p_ch. + */ + room = Rows - cmdline_row; + if (curwin->w_next == NULL) + room -= 1; + else + room -= p_ch; + for (wp = curwin->w_next; wp != NULL && room < offset; wp = wp->w_next) + room += wp->w_height - MIN_ROWS; + wp = curwin; /* put wp at window that grows */ + } + + if (room < offset) /* Not enough room */ + offset = room; /* Move as far as we can */ + if (offset <= 0) + return; + + if (wp != NULL) /* grow window wp by offset lines */ + win_new_height(wp, wp->w_height + offset); + + if (up) + wp = curwin; /* current window gets smaller */ + else + wp = curwin->w_next; /* next window gets smaller */ + + while (wp != NULL && offset > 0) + { + if (wp->w_height - offset < MIN_ROWS) + { + offset -= wp->w_height - MIN_ROWS; + win_new_height(wp, MIN_ROWS); + } + else + { + win_new_height(wp, wp->w_height - offset); + offset = 0; + } + if (up) + wp = wp->w_prev; + else + wp = wp->w_next; + } + row = win_comp_pos(); + screen_fill(row, cmdline_row, 0, (int)Columns, ' ', ' '); + cmdline_row = row; + p_ch = Rows - cmdline_row; + updateScreen(NOT_VALID); + showmode(); +} +#endif /* USE_MOUSE */ + +/* + * Set new window height. + */ + static void +win_new_height(wp, height) + WIN *wp; + int height; +{ + /* should adjust topline to keep cursor at same relative postition */ + + wp->w_height = height; + win_comp_scroll(wp); + wp->w_redr_type = NOT_VALID; + wp->w_redr_status = TRUE; +} + + void +win_comp_scroll(wp) + WIN *wp; +{ + wp->w_p_scroll = (wp->w_height >> 1); + if (wp->w_p_scroll == 0) + wp->w_p_scroll = 1; +} + +/* + * command_height: called whenever p_ch has been changed + */ + void +command_height() +{ + int current; + + current = Rows - cmdline_row; + if (p_ch > current) /* p_ch got bigger */ + { + if (lastwin->w_height - (p_ch - current) < MIN_ROWS) + { + emsg(e_noroom); + p_ch = lastwin->w_height - MIN_ROWS + current; + } + /* clear the lines added to cmdline */ + screen_fill((int)(Rows - p_ch), (int)Rows, 0, (int)Columns, ' ', ' '); + } + win_new_height(lastwin, lastwin->w_height + current - (int)p_ch); + cmdline_row = Rows - p_ch; + redraw_cmdline = TRUE; +} + + void +last_status() +{ + if (lastwin->w_status_height) + { + /* remove status line */ + if (p_ls == 0 || (p_ls == 1 && firstwin == lastwin)) + { + win_new_height(lastwin, lastwin->w_height + 1); + lastwin->w_status_height = 0; + } + } + else + { + /* add status line */ + if (p_ls == 2 || (p_ls == 1 && firstwin != lastwin)) + { + if (lastwin->w_height <= MIN_ROWS) /* can't do it */ + emsg(e_noroom); + else + { + win_new_height(lastwin, lastwin->w_height - 1); + lastwin->w_status_height = 1; + } + } + } +} + +/* + * file_name_at_cursor() + * + * Return the name of the file under (or to the right of) the cursor. The + * p_path variable is searched if the file name does not start with '/'. + * The string returned has been alloc'ed and should be freed by the caller. + * NULL is returned if the file name or file is not found. + */ + char_u * +file_name_at_cursor(options) + int options; +{ + return get_file_name_in_path(ml_get_curline(), + curwin->w_cursor.col, options); +} +/* options: + * FNAME_MESS give error messages + * FNAME_EXP expand to path + * FNAME_HYP check for hypertext link + */ + char_u * +get_file_name_in_path(ptr, col, options) + char_u *ptr; + int col; + int options; +{ + char_u *dir; + char_u *file_name; + char_u save_char; + char_u *curr_path = NULL; + int curr_path_len; + int len; + + /* search forward for what could be the start of a file name */ + while (ptr[col] != NUL && !isfilechar(ptr[col])) + ++col; + if (ptr[col] == NUL) /* nothing found */ + { + if (options & FNAME_MESS) + EMSG("No file name under cursor"); + return NULL; + } + + /* search backward for char that cannot be in a file name */ + while (col >= 0 && isfilechar(ptr[col])) + --col; + ptr += col + 1; + col = 0; + + /* search forward for a char that cannot be in a file name */ + while (isfilechar(ptr[col])) + ++col; + + if (options & FNAME_HYP) + { + /* For hypertext links, ignore the name of the machine. + * Such a link looks like "type://machine/path". Only "/path" is used. + * First search for the string "://", then for the extra '/' + */ + if ((file_name = vim_strchr(ptr, ':')) != NULL && + STRNCMP(file_name, "://", (size_t)3) == 0 && + (file_name = vim_strchr(file_name + 3, '/')) != NULL && + file_name < ptr + col) + { + col -= file_name - ptr; + ptr = file_name; + if (ptr[1] == '~') /* skip '/' for /~user/path */ + { + ++ptr; + --col; + } + } + } + + if (!(options & FNAME_EXP)) + return strnsave(ptr, col); + + /* copy file name into NameBuff, expanding environment variables */ + save_char = ptr[col]; + ptr[col] = NUL; + expand_env(ptr, NameBuff, MAXPATHL); + ptr[col] = save_char; + + if (isFullName(NameBuff)) /* absolute path */ + { + if ((file_name = strsave(NameBuff)) == NULL) + return NULL; + if (getperm(file_name) >= 0) + return file_name; + if (options & FNAME_MESS) + { + sprintf((char *)IObuff, "Can't find file `%s'", NameBuff); + emsg(IObuff); + } + } + else /* relative path, use 'path' option */ + { + if (curbuf->b_xfilename != NULL) + { + curr_path = curbuf->b_xfilename; + ptr = gettail(curr_path); + curr_path_len = ptr - curr_path; + } + else + curr_path_len = 0; + if ((file_name = alloc((int)(curr_path_len + STRLEN(p_path) + + STRLEN(NameBuff) + 3))) == NULL) + return NULL; + + for (dir = p_path; *dir;) + { + len = copy_option_part(&dir, file_name, 31000, " ,"); + /* len == 0 means: use current directory */ + if (len != 0) + { + /* Look for file relative to current file */ + if (file_name[0] == '.' && curr_path_len > 0) + { + if (len == 1) /* just a "." */ + len = 0; + else /* "./path": move "path" */ + { + len -= 2; + vim_memmove(file_name + curr_path_len, file_name + 2, + (size_t)len); + } + STRNCPY(file_name, curr_path, curr_path_len); + len += curr_path_len; + } + if (!ispathsep(file_name[len - 1])) + file_name[len++] = PATHSEP; + } + STRCPY(file_name + len, NameBuff); + if (getperm(file_name) >= 0) + return file_name; + } + if (options & FNAME_MESS) + EMSG2("Can't find file \"%s\" in path", NameBuff); + } + vim_free(file_name); /* file doesn't exist */ + return NULL; +} + +/* + * Return the minimal number of rows that is needed on the screen to display + * the current number of windows. + */ + int +min_rows() +{ + WIN *wp; + int total; + + if (firstwin == NULL) /* not initialized yet */ + return MIN_ROWS + 1; /* one window plus command line */ + + total = p_ch; /* count the room for the status line */ + for (wp = firstwin; wp != NULL; wp = wp->w_next) + total += MIN_ROWS + wp->w_status_height; + return total; +} + +/* + * Return TRUE if there is only one window, not counting a help window, unless + * it is the current window. + */ + int +only_one_window() +{ + int count = 0; + WIN *wp; + + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (!wp->w_buffer->b_help || wp == curwin) + ++count; + return (count <= 1); +}