diff --git a/initrd/etc/vim/vimrc b/initrd/etc/vim/vimrc new file mode 100644 index 00000000..77e9172e --- /dev/null +++ b/initrd/etc/vim/vimrc @@ -0,0 +1,3 @@ +" Stellux system defaults +set nocompatible +set backspace=indent,eol,start diff --git a/userland/apps/Makefile b/userland/apps/Makefile index 06c3fb73..12dde550 100644 --- a/userland/apps/Makefile +++ b/userland/apps/Makefile @@ -5,7 +5,7 @@ APP_DIRS := init hello shell ls cat rm stat touch sleep true false clear ptytest date \ clockbench stlxdm stlxterm doom ping ifconfig nslookup arp udpecho tcpecho \ fetch polltest dropbear blackjack wordle hangman snake tetris \ - grep wc head threadtest uname kill cxxtest synctest python + grep wc head threadtest uname kill cxxtest synctest python vim APP_COUNT := $(words $(APP_DIRS)) all: diff --git a/userland/apps/stlxterm/src/keymap.c b/userland/apps/stlxterm/src/keymap.c index c83d1895..2c394063 100644 --- a/userland/apps/stlxterm/src/keymap.c +++ b/userland/apps/stlxterm/src/keymap.c @@ -20,7 +20,7 @@ static int emit(char *out, int out_size, const char *seq, int len) { return len; } -int keymap_translate(uint16_t usage, uint8_t modifiers, +int keymap_translate(uint16_t usage, uint8_t modifiers, int app_cursor, char *out, int out_size) { if (!out || out_size < 1) return 0; @@ -57,16 +57,17 @@ int keymap_translate(uint16_t usage, uint8_t modifiers, switch (usage) { case 0x28: out[0] = '\r'; return 1; case 0x29: out[0] = '\x1b'; return 1; - case 0x2A: out[0] = '\x7f'; return 1; + case 0x2A: out[0] = '\b'; return 1; case 0x2B: out[0] = '\t'; return 1; case 0x2C: out[0] = ' '; return 1; - case 0x4F: return emit(out, out_size, "\x1b[C", 3); - case 0x50: return emit(out, out_size, "\x1b[D", 3); - case 0x51: return emit(out, out_size, "\x1b[B", 3); - case 0x52: return emit(out, out_size, "\x1b[A", 3); - case 0x4A: return emit(out, out_size, "\x1b[H", 3); - case 0x4D: return emit(out, out_size, "\x1b[F", 3); + /* Arrows follow DECCKM: CSI form normally, SS3 form in application mode */ + case 0x4F: return emit(out, out_size, app_cursor ? "\x1bOC" : "\x1b[C", 3); + case 0x50: return emit(out, out_size, app_cursor ? "\x1bOD" : "\x1b[D", 3); + case 0x51: return emit(out, out_size, app_cursor ? "\x1bOB" : "\x1b[B", 3); + case 0x52: return emit(out, out_size, app_cursor ? "\x1bOA" : "\x1b[A", 3); + case 0x4A: return emit(out, out_size, app_cursor ? "\x1bOH" : "\x1b[H", 3); + case 0x4D: return emit(out, out_size, app_cursor ? "\x1bOF" : "\x1b[F", 3); case 0x4C: return emit(out, out_size, "\x1b[3~", 4); case 0x49: return emit(out, out_size, "\x1b[2~", 4); /* Insert */ case 0x4B: return emit(out, out_size, "\x1b[5~", 4); /* Page Up */ diff --git a/userland/apps/stlxterm/src/keymap.h b/userland/apps/stlxterm/src/keymap.h index f04ef7c1..0831b99d 100644 --- a/userland/apps/stlxterm/src/keymap.h +++ b/userland/apps/stlxterm/src/keymap.h @@ -3,7 +3,7 @@ #include -int keymap_translate(uint16_t usage, uint8_t modifiers, +int keymap_translate(uint16_t usage, uint8_t modifiers, int app_cursor, char *out, int out_size); #endif // STLXTERM_KEYMAP_H diff --git a/userland/apps/stlxterm/src/stlxterm.c b/userland/apps/stlxterm/src/stlxterm.c index 76053fc1..3597beb0 100644 --- a/userland/apps/stlxterm/src/stlxterm.c +++ b/userland/apps/stlxterm/src/stlxterm.c @@ -206,6 +206,7 @@ int main(void) { if (evt.type == STLXGFX_EVT_KEY_DOWN) { char seq[8]; int len = keymap_translate(evt.key.usage, evt.key.modifiers, + term->app_cursor_keys, seq, sizeof(seq)); if (len > 0) { write(master_fd, seq, (size_t)len); diff --git a/userland/apps/stlxterm/src/term.c b/userland/apps/stlxterm/src/term.c index 7dc781ca..e99ade30 100644 --- a/userland/apps/stlxterm/src/term.c +++ b/userland/apps/stlxterm/src/term.c @@ -184,6 +184,9 @@ static void csi_dispatch_private(term_state_t *t, char cmd) { if (cmd == 'h') { switch (p0) { + case 1: /* DECCKM: application cursor keys */ + t->app_cursor_keys = 1; + break; case 25: /* DECTCEM: show cursor */ t->cursor_visible = 1; break; @@ -200,6 +203,9 @@ static void csi_dispatch_private(term_state_t *t, char cmd) { } } else if (cmd == 'l') { switch (p0) { + case 1: /* DECCKM: normal cursor keys */ + t->app_cursor_keys = 0; + break; case 25: /* DECTCEM: hide cursor */ t->cursor_visible = 0; break; @@ -450,7 +456,10 @@ static void feed_byte(term_state_t *t, unsigned char ch) { t->csi_param_count = 0; t->csi_current_param = 0; t->csi_private = 0; + t->csi_discard = 0; t->state = TERM_ST_CSI; + } else if (ch == ']') { /* OSC: consume until BEL or ST */ + t->state = TERM_ST_OSC; } else if (ch == '7') { /* DECSC: save cursor */ save_cursor(t); t->state = TERM_ST_NORMAL; @@ -477,13 +486,21 @@ static void feed_byte(term_state_t *t, unsigned char ch) { case TERM_ST_CSI: if (ch == '?' && t->csi_param_count == 0 && t->csi_current_param == 0) { t->csi_private = 1; + } else if (ch == '<' || ch == '=' || ch == '>') { + /* Prefixed protocol sequences are consumed and ignored */ + t->csi_discard = 1; } else if (ch >= '0' && ch <= '9') { t->csi_current_param = t->csi_current_param * 10 + (ch - '0'); } else if (ch == ';') { csi_push_param(t); + } else if (ch >= 0x20 && ch <= 0x2F) { + /* Intermediate bytes mark sequences we do not implement */ + t->csi_discard = 1; } else if (ch >= 0x40 && ch <= 0x7E) { csi_push_param(t); - if (t->csi_private) { + if (t->csi_discard) { + /* Fully consumed, deliberately ignored */ + } else if (t->csi_private) { csi_dispatch_private(t, (char)ch); } else { csi_dispatch(t, (char)ch); @@ -495,6 +512,25 @@ static void feed_byte(term_state_t *t, unsigned char ch) { t->state = TERM_ST_NORMAL; } break; + + case TERM_ST_OSC: + if (ch == '\x07') { + t->state = TERM_ST_NORMAL; + } else if (ch == '\x1b') { + t->state = TERM_ST_OSC_ESC; + } + break; + + case TERM_ST_OSC_ESC: + /* ESC backslash is ST, anything else starts a new escape whose + follower byte must be reprocessed */ + if (ch == '\\') { + t->state = TERM_ST_NORMAL; + } else { + t->state = TERM_ST_ESC; + feed_byte(t, ch); + } + break; } } @@ -520,10 +556,12 @@ void term_init(term_state_t *t, int rows, int cols) { t->saved_bold = 0; t->saved_reverse = 0; t->using_alt_screen = 0; + t->app_cursor_keys = 0; t->state = TERM_ST_NORMAL; t->csi_param_count = 0; t->csi_current_param = 0; t->csi_private = 0; + t->csi_discard = 0; for (int r = 0; r < TERM_MAX_ROWS; r++) { memset(&t->cells[r], ' ', TERM_MAX_COLS); memset(&t->attrs[r], 0, TERM_MAX_COLS * sizeof(term_attr_t)); diff --git a/userland/apps/stlxterm/src/term.h b/userland/apps/stlxterm/src/term.h index a9ad8ffe..f303ff14 100644 --- a/userland/apps/stlxterm/src/term.h +++ b/userland/apps/stlxterm/src/term.h @@ -46,12 +46,16 @@ typedef struct { int alt_cursor_col; int using_alt_screen; + // DECCKM application cursor keys, consulted by the key encoder + int app_cursor_keys; + // Parser state - enum { TERM_ST_NORMAL, TERM_ST_ESC, TERM_ST_CSI } state; + enum { TERM_ST_NORMAL, TERM_ST_ESC, TERM_ST_CSI, TERM_ST_OSC, TERM_ST_OSC_ESC } state; int csi_params[TERM_CSI_MAX_PARAMS]; int csi_param_count; int csi_current_param; int csi_private; + int csi_discard; } term_state_t; void term_init(term_state_t *t, int rows, int cols); diff --git a/userland/apps/vim/.gitignore b/userland/apps/vim/.gitignore new file mode 100644 index 00000000..567609b1 --- /dev/null +++ b/userland/apps/vim/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/userland/apps/vim/Makefile b/userland/apps/vim/Makefile new file mode 100644 index 00000000..5de0f1bd --- /dev/null +++ b/userland/apps/vim/Makefile @@ -0,0 +1,91 @@ +# +# Stellux Vim Build +# +# Cross-compiles Vim with --features=tiny for Stellux OS. +# Uses musl libc, no ncurses (builtin termcap), no GUI. +# +# Sources are a minimal vendored subset of upstream vim tag v9.1.0000. +# regexp_bt.c and regexp_nfa.c are textually included by regexp.c and +# must not be compiled standalone. +# + +USERLAND_ROOT ?= $(shell cd ../.. && pwd) +include ../../mk/toolchain.mk + +SRC_DIR := src +BUILD_DIR := build/$(ARCH) +BIN_DIR := $(USERLAND_ROOT)/build/$(ARCH)/bin + +# Vim-specific CFLAGS +VIM_CFLAGS := \ + --target=$(TARGET_TRIPLE) \ + --sysroot=$(SYSROOT) \ + -nostdlibinc \ + -isystem $(SYSROOT)/include \ + -std=gnu11 -O2 -g \ + -Wall -Wno-unused-parameter -Wno-unused-variable \ + -Wno-sign-compare -Wno-missing-field-initializers \ + -Wno-implicit-fallthrough -Wno-string-plus-int \ + -Wno-unused-function -Wno-parentheses \ + -Wno-missing-braces -Wno-tautological-compare \ + -Wno-pointer-sign -Wno-empty-body \ + -fno-stack-protector \ + -I$(SRC_DIR) -I$(SRC_DIR)/proto \ + -DHAVE_CONFIG_H \ + -DFEAT_TINY + +# All Vim source files (from BASIC_SRC + required extras) +VIM_SOURCES := \ + alloc.c arabic.c arglist.c autocmd.c beval.c blob.c blowfish.c \ + buffer.c bufwrite.c change.c charset.c cindent.c clientserver.c \ + clipboard.c cmdexpand.c cmdhist.c crypt.c crypt_zip.c debugger.c \ + dict.c diff.c digraph.c drawline.c drawscreen.c edit.c eval.c \ + evalbuffer.c evalfunc.c evalvars.c evalwindow.c ex_cmds.c \ + ex_cmds2.c ex_docmd.c ex_eval.c ex_getln.c fileio.c filepath.c \ + findfile.c float.c fold.c getchar.c hardcopy.c hashtab.c help.c \ + highlight.c if_cscope.c indent.c insexpand.c json.c list.c \ + locale.c logfile.c main.c map.c mark.c match.c mbyte.c memfile.c \ + memline.c menu.c message.c misc1.c misc2.c mouse.c move.c \ + normal.c ops.c option.c optionstr.c os_unix.c popupmenu.c \ + popupwin.c profiler.c quickfix.c regexp.c register.c screen.c \ + scriptfile.c search.c session.c sha256.c sign.c sound.c spell.c \ + spellfile.c spellsuggest.c strings.c syntax.c tag.c term.c \ + testing.c textformat.c textobject.c textprop.c time.c typval.c \ + ui.c undo.c usercmd.c userfunc.c version.c viminfo.c window.c \ + auto/pathdef.c \ + stellux_stubs.c + +# Note: gui_xim.c, if_xcmdsrv.c, pty.c, terminal.c, vim9*.c excluded +# (not needed for tiny build on Stellux) + +VIM_OBJECTS := $(VIM_SOURCES:%.c=$(BUILD_DIR)/%.o) + +TARGET := $(BIN_DIR)/vim + +all: $(TARGET) + +$(TARGET): $(VIM_OBJECTS) + $(UQ)mkdir -p $(dir $@) + @echo "[LD] vim ($(ARCH))" + $(UQ)$(CC) -nostdlib -fuse-ld=lld --target=$(TARGET_TRIPLE) \ + -static -o $@ \ + $(SYSROOT)/lib/crt1.o $(SYSROOT)/lib/crti.o \ + $(VIM_OBJECTS) \ + -L$(SYSROOT)/lib -Wl,--start-group -lstlx -lc -lm $(BUILTINS_LIB) -Wl,--end-group \ + $(SYSROOT)/lib/crtn.o + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c + $(UQ)mkdir -p $(dir $@) + @echo "[CC] $< ($(ARCH))" + $(UQ)$(CC) $(VIM_CFLAGS) -c $< -o $@ + +$(BUILD_DIR)/auto/%.o: $(SRC_DIR)/auto/%.c + $(UQ)mkdir -p $(dir $@) + @echo "[CC] $< ($(ARCH))" + $(UQ)$(CC) $(VIM_CFLAGS) -c $< -o $@ + +clean: + $(UQ)rm -rf build + $(UQ)rm -f $(TARGET) + +.PHONY: all clean diff --git a/userland/apps/vim/src/alloc.c b/userland/apps/vim/src/alloc.c new file mode 100644 index 00000000..5f39085b --- /dev/null +++ b/userland/apps/vim/src/alloc.c @@ -0,0 +1,895 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * alloc.c: functions for memory management + */ + +#include "vim.h" + +/********************************************************************** + * Various routines dealing with allocation and deallocation of memory. + */ + +#if defined(MEM_PROFILE) || defined(PROTO) + +# define MEM_SIZES 8200 +static long_u mem_allocs[MEM_SIZES]; +static long_u mem_frees[MEM_SIZES]; +static long_u mem_allocated; +static long_u mem_freed; +static long_u mem_peak; +static long_u num_alloc; +static long_u num_freed; + + static void +mem_pre_alloc_s(size_t *sizep) +{ + *sizep += sizeof(size_t); +} + + static void +mem_pre_alloc_l(size_t *sizep) +{ + *sizep += sizeof(size_t); +} + + static void +mem_post_alloc( + void **pp, + size_t size) +{ + if (*pp == NULL) + return; + size -= sizeof(size_t); + *(long_u *)*pp = size; + if (size <= MEM_SIZES-1) + mem_allocs[size-1]++; + else + mem_allocs[MEM_SIZES-1]++; + mem_allocated += size; + if (mem_allocated - mem_freed > mem_peak) + mem_peak = mem_allocated - mem_freed; + num_alloc++; + *pp = (void *)((char *)*pp + sizeof(size_t)); +} + + static void +mem_pre_free(void **pp) +{ + long_u size; + + *pp = (void *)((char *)*pp - sizeof(size_t)); + size = *(size_t *)*pp; + if (size <= MEM_SIZES-1) + mem_frees[size-1]++; + else + mem_frees[MEM_SIZES-1]++; + mem_freed += size; + num_freed++; +} + +/* + * called on exit via atexit() + */ + void +vim_mem_profile_dump(void) +{ + int i, j; + + printf("\r\n"); + j = 0; + for (i = 0; i < MEM_SIZES - 1; i++) + { + if (mem_allocs[i] == 0 && mem_frees[i] == 0) + continue; + + if (mem_frees[i] > mem_allocs[i]) + printf("\r\n%s", _("ERROR: ")); + printf("[%4d / %4lu-%-4lu] ", i + 1, mem_allocs[i], mem_frees[i]); + j++; + if (j > 3) + { + j = 0; + printf("\r\n"); + } + } + + i = MEM_SIZES - 1; + if (mem_allocs[i]) + { + printf("\r\n"); + if (mem_frees[i] > mem_allocs[i]) + puts(_("ERROR: ")); + printf("[>%d / %4lu-%-4lu]", i, mem_allocs[i], mem_frees[i]); + } + + printf(_("\n[bytes] total alloc-freed %lu-%lu, in use %lu, peak use %lu\n"), + mem_allocated, mem_freed, mem_allocated - mem_freed, mem_peak); + printf(_("[calls] total re/malloc()'s %lu, total free()'s %lu\n\n"), + num_alloc, num_freed); +} + +#endif // MEM_PROFILE + +#ifdef FEAT_EVAL + int +alloc_does_fail(size_t size) +{ + if (alloc_fail_countdown == 0) + { + if (--alloc_fail_repeat <= 0) + alloc_fail_id = 0; + do_outofmem_msg(size); + return TRUE; + } + --alloc_fail_countdown; + return FALSE; +} +#endif + +/* + * 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 (2 * 8192L) +#define KEEP_ROOM_KB (KEEP_ROOM / 1024L) + +/* + * The normal way to allocate memory. This handles an out-of-memory situation + * as well as possible, still returns NULL when we're completely out. + */ + void * +alloc(size_t size) +{ + return lalloc(size, TRUE); +} + +#if defined(FEAT_QUICKFIX) || defined(PROTO) +/* + * alloc() with an ID for alloc_fail(). + */ + void * +alloc_id(size_t size, alloc_id_T id UNUSED) +{ +# ifdef FEAT_EVAL + if (alloc_fail_id == id && alloc_does_fail(size)) + return NULL; +# endif + return lalloc(size, TRUE); +} +#endif + +/* + * Allocate memory and set all bytes to zero. + */ + void * +alloc_clear(size_t size) +{ + void *p; + + p = lalloc(size, TRUE); + if (p != NULL) + (void)vim_memset(p, 0, size); + return p; +} + +/* + * Same as alloc_clear() but with allocation id for testing + */ + void * +alloc_clear_id(size_t size, alloc_id_T id UNUSED) +{ +#ifdef FEAT_EVAL + if (alloc_fail_id == id && alloc_does_fail(size)) + return NULL; +#endif + return alloc_clear(size); +} + +/* + * Allocate memory like lalloc() and set all bytes to zero. + */ + void * +lalloc_clear(size_t size, int message) +{ + void *p; + + p = lalloc(size, message); + if (p != NULL) + (void)vim_memset(p, 0, size); + return p; +} + +/* + * Low level memory allocation function. + * This is used often, KEEP IT FAST! + */ + void * +lalloc(size_t size, int message) +{ + void *p; // pointer to new storage space + static int releasing = FALSE; // don't do mf_release_all() recursive + int try_again; +#if defined(HAVE_AVAIL_MEM) + static size_t allocated = 0; // allocated since last avail check +#endif + + // Safety check for allocating zero bytes + if (size == 0) + { + // Don't hide this message + emsg_silent = 0; + iemsg(e_internal_error_lalloc_zero); + return NULL; + } + +#ifdef MEM_PROFILE + mem_pre_alloc_l(&size); +#endif + + // Loop when out of memory: Try to release some memfile blocks and + // if some blocks are released call malloc again. + for (;;) + { + // Handle three kinds of systems: + // 1. No check for available memory: Just return. + // 2. Slow check for available memory: call mch_avail_mem() after + // allocating KEEP_ROOM amount of memory. + // 3. Strict check for available memory: call mch_avail_mem() + if ((p = malloc(size)) != NULL) + { +#ifndef HAVE_AVAIL_MEM + // 1. No check for available memory: Just return. + goto theend; +#else + // 2. Slow check for available memory: call mch_avail_mem() after + // allocating (KEEP_ROOM / 2) amount of memory. + allocated += size; + if (allocated < KEEP_ROOM / 2) + goto theend; + allocated = 0; + + // 3. check for available memory: call mch_avail_mem() + if (mch_avail_mem(TRUE) < KEEP_ROOM_KB && !releasing) + { + free(p); // System is low... no go! + p = NULL; + } + else + goto theend; +#endif + } + // Remember that mf_release_all() is being called to avoid an endless + // loop, because mf_release_all() may call alloc() recursively. + if (releasing) + break; + releasing = TRUE; + + clear_sb_text(TRUE); // free any scrollback text + try_again = mf_release_all(); // release as many blocks as possible + + releasing = FALSE; + if (!try_again) + break; + } + + if (message && p == NULL) + do_outofmem_msg(size); + +theend: +#ifdef MEM_PROFILE + mem_post_alloc(&p, size); +#endif + return p; +} + +/* + * lalloc() with an ID for alloc_fail(). + */ +#if defined(FEAT_SIGNS) || defined(PROTO) + void * +lalloc_id(size_t size, int message, alloc_id_T id UNUSED) +{ +#ifdef FEAT_EVAL + if (alloc_fail_id == id && alloc_does_fail(size)) + return NULL; +#endif + return (lalloc(size, message)); +} +#endif + +#if defined(MEM_PROFILE) || defined(PROTO) +/* + * realloc() with memory profiling. + */ + void * +mem_realloc(void *ptr, size_t size) +{ + void *p; + + mem_pre_free(&ptr); + mem_pre_alloc_s(&size); + + p = realloc(ptr, size); + + mem_post_alloc(&p, size); + + return p; +} +#endif + +/* +* Avoid repeating the error message many times (they take 1 second each). +* Did_outofmem_msg is reset when a character is read. +*/ + void +do_outofmem_msg(size_t size) +{ + if (did_outofmem_msg) + return; + + // Don't hide this message + emsg_silent = 0; + + // Must come first to avoid coming back here when printing the error + // message fails, e.g. when setting v:errmsg. + did_outofmem_msg = TRUE; + + semsg(_(e_out_of_memory_allocating_nr_bytes), (long_u)size); + + if (starting == NO_SCREEN) + // Not even finished with initializations and already out of + // memory? Then nothing is going to work, exit. + mch_exit(123); +} + +#if defined(EXITFREE) || defined(PROTO) + +/* + * Free everything that we allocated. + * Can be used to detect memory leaks, e.g., with ccmalloc. + * NOTE: This is tricky! Things are freed that functions depend on. Don't be + * surprised if Vim crashes... + * Some things can't be freed, esp. things local to a library function. + */ + void +free_all_mem(void) +{ + buf_T *buf, *nextbuf; + + // When we cause a crash here it is caught and Vim tries to exit cleanly. + // Don't try freeing everything again. + if (entered_free_all_mem) + return; + entered_free_all_mem = TRUE; + // Don't want to trigger autocommands from here on. + block_autocmds(); + + // Close all tabs and windows. Reset 'equalalways' to avoid redraws. + p_ea = FALSE; + if (first_tabpage != NULL && first_tabpage->tp_next != NULL) + do_cmdline_cmd((char_u *)"tabonly!"); + if (!ONE_WINDOW) + do_cmdline_cmd((char_u *)"only!"); + +# if defined(FEAT_SPELL) + // Free all spell info. + spell_free_all(); +# endif + +# if defined(FEAT_BEVAL_TERM) + ui_remove_balloon(); +# endif +# ifdef FEAT_PROP_POPUP + if (curwin != NULL) + close_all_popups(TRUE); +# endif + + // Clear user commands (before deleting buffers). + ex_comclear(NULL); + + // When exiting from mainerr_arg_missing curbuf has not been initialized, + // and not much else. + if (curbuf != NULL) + { +# ifdef FEAT_MENU + // Clear menus. + do_cmdline_cmd((char_u *)"aunmenu *"); + do_cmdline_cmd((char_u *)"tlunmenu *"); +# ifdef FEAT_MULTI_LANG + do_cmdline_cmd((char_u *)"menutranslate clear"); +# endif +# endif + // Clear mappings, abbreviations, breakpoints. + do_cmdline_cmd((char_u *)"lmapclear"); + do_cmdline_cmd((char_u *)"xmapclear"); + do_cmdline_cmd((char_u *)"mapclear"); + do_cmdline_cmd((char_u *)"mapclear!"); + do_cmdline_cmd((char_u *)"abclear"); +# if defined(FEAT_EVAL) + do_cmdline_cmd((char_u *)"breakdel *"); +# endif +# if defined(FEAT_PROFILE) + do_cmdline_cmd((char_u *)"profdel *"); +# endif +# if defined(FEAT_KEYMAP) + do_cmdline_cmd((char_u *)"set keymap="); +# endif + } + + free_titles(); + free_findfile(); + + // Obviously named calls. + free_all_autocmds(); + clear_termcodes(); + free_all_marks(); + alist_clear(&global_alist); + free_homedir(); + free_users(); + free_search_patterns(); + free_old_sub(); + free_last_insert(); + free_insexpand_stuff(); + free_prev_shellcmd(); + free_regexp_stuff(); + free_tag_stuff(); + free_xim_stuff(); + free_cd_dir(); +# ifdef FEAT_SIGNS + free_signs(); +# endif +# ifdef FEAT_EVAL + set_expr_line(NULL, NULL); +# endif +# ifdef FEAT_DIFF + if (curtab != NULL) + diff_clear(curtab); +# endif + clear_sb_text(TRUE); // free any scrollback text + + // Free some global vars. + free_username(); +# ifdef FEAT_CLIPBOARD + vim_regfree(clip_exclude_prog); +# endif + vim_free(last_cmdline); + vim_free(new_last_cmdline); + set_keep_msg(NULL, 0); + + // Clear cmdline history. + p_hi = 0; + init_history(); +# ifdef FEAT_PROP_POPUP + clear_global_prop_types(); +# endif + +# ifdef FEAT_QUICKFIX + free_quickfix(); +# endif + + // Close all script inputs. + close_all_scripts(); + + if (curwin != NULL) + // Destroy all windows. Must come before freeing buffers. + win_free_all(); + + // Free all option values. Must come after closing windows. + free_all_options(); + + // Free all buffers. Reset 'autochdir' to avoid accessing things that + // were freed already. +# ifdef FEAT_AUTOCHDIR + p_acd = FALSE; +# endif + for (buf = firstbuf; buf != NULL; ) + { + bufref_T bufref; + + set_bufref(&bufref, buf); + nextbuf = buf->b_next; + close_buffer(NULL, buf, DOBUF_WIPE, FALSE, FALSE); + if (bufref_valid(&bufref)) + buf = nextbuf; // didn't work, try next one + else + buf = firstbuf; + } + +# ifdef FEAT_ARABIC + free_arshape_buf(); +# endif + + // Clear registers. + clear_registers(); + ResetRedobuff(); + ResetRedobuff(); + +# if defined(FEAT_CLIENTSERVER) && defined(FEAT_X11) + vim_free(serverDelayedStartName); +# endif + + // highlight info + free_highlight(); + + reset_last_sourcing(); + + if (first_tabpage != NULL) + { + free_tabpage(first_tabpage); + first_tabpage = NULL; + } + +# ifdef UNIX + // Machine-specific free. + mch_free_mem(); +# endif + + // message history + for (;;) + if (delete_first_msg() == FAIL) + break; + +# ifdef FEAT_JOB_CHANNEL + channel_free_all(); +# endif +# ifdef FEAT_TIMERS + timer_free_all(); +# endif +# ifdef FEAT_EVAL + // must be after channel_free_all() with unrefs partials + eval_clear(); +# endif +# ifdef FEAT_JOB_CHANNEL + // must be after eval_clear() with unrefs jobs + job_free_all(); +# endif + + free_termoptions(); + free_cur_term(); + + // screenlines (can't display anything now!) + free_screenlines(); + +# if defined(FEAT_SOUND) + sound_free(); +# endif +# if defined(USE_XSMP) + xsmp_close(); +# endif +# ifdef FEAT_GUI_GTK + gui_mch_free_all(); +# endif +# ifdef FEAT_TCL + vim_tcl_finalize(); +# endif + clear_hl_tables(); + + vim_free(IObuff); + vim_free(NameBuff); +# ifdef FEAT_QUICKFIX + check_quickfix_busy(); +# endif +# ifdef FEAT_EVAL + free_resub_eval_result(); +# endif + free_vbuf(); +} +#endif + +/* + * Copy "p[len]" into allocated memory, ignoring NUL characters. + * Returns NULL when out of memory. + */ + char_u * +vim_memsave(char_u *p, size_t len) +{ + char_u *ret = alloc(len); + + if (ret != NULL) + mch_memmove(ret, p, len); + return ret; +} + +/* + * Replacement for free() that ignores NULL pointers. + * Also skip free() when exiting for sure, this helps when we caught a deadly + * signal that was caused by a crash in free(). + * If you want to set NULL after calling this function, you should use + * VIM_CLEAR() instead. + */ + void +vim_free(void *x) +{ + if (x != NULL && !really_exiting) + { +#ifdef MEM_PROFILE + mem_pre_free(&x); +#endif + free(x); + } +} + +/************************************************************************ + * Functions for handling growing arrays. + */ + +/* + * Clear an allocated growing array. + */ + void +ga_clear(garray_T *gap) +{ + vim_free(gap->ga_data); + ga_init(gap); +} + +/* + * Clear a growing array that contains a list of strings. + */ + void +ga_clear_strings(garray_T *gap) +{ + int i; + + if (gap->ga_data != NULL) + for (i = 0; i < gap->ga_len; ++i) + vim_free(((char_u **)(gap->ga_data))[i]); + ga_clear(gap); +} + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * Copy a growing array that contains a list of strings. + */ + int +ga_copy_strings(garray_T *from, garray_T *to) +{ + int i; + + ga_init2(to, sizeof(char_u *), 1); + if (ga_grow(to, from->ga_len) == FAIL) + return FAIL; + + for (i = 0; i < from->ga_len; ++i) + { + char_u *orig = ((char_u **)from->ga_data)[i]; + char_u *copy; + + if (orig == NULL) + copy = NULL; + else + { + copy = vim_strsave(orig); + if (copy == NULL) + { + to->ga_len = i; + ga_clear_strings(to); + return FAIL; + } + } + ((char_u **)to->ga_data)[i] = copy; + } + to->ga_len = from->ga_len; + return OK; +} +#endif + +/* + * Initialize a growing array. Don't forget to set ga_itemsize and + * ga_growsize! Or use ga_init2(). + */ + void +ga_init(garray_T *gap) +{ + gap->ga_data = NULL; + gap->ga_maxlen = 0; + gap->ga_len = 0; +} + + void +ga_init2(garray_T *gap, size_t itemsize, int growsize) +{ + ga_init(gap); + gap->ga_itemsize = (int)itemsize; + gap->ga_growsize = growsize; +} + +/* + * Make room in growing array "gap" for at least "n" items. + * Return FAIL for failure, OK otherwise. + */ + int +ga_grow(garray_T *gap, int n) +{ + if (gap->ga_maxlen - gap->ga_len < n) + return ga_grow_inner(gap, n); + return OK; +} + +/* + * Same as ga_grow() but uses an allocation id for testing. + */ + int +ga_grow_id(garray_T *gap, int n, alloc_id_T id UNUSED) +{ +#ifdef FEAT_EVAL + if (alloc_fail_id == id && alloc_does_fail(sizeof(list_T))) + return FAIL; +#endif + + return ga_grow(gap, n); +} + + int +ga_grow_inner(garray_T *gap, int n) +{ + size_t old_len; + size_t new_len; + char_u *pp; + + if (n < gap->ga_growsize) + n = gap->ga_growsize; + + // A linear growth is very inefficient when the array grows big. This + // is a compromise between allocating memory that won't be used and too + // many copy operations. A factor of 1.5 seems reasonable. + if (n < gap->ga_len / 2) + n = gap->ga_len / 2; + + new_len = (size_t)gap->ga_itemsize * (gap->ga_len + n); + pp = vim_realloc(gap->ga_data, new_len); + if (pp == NULL) + return FAIL; + old_len = (size_t)gap->ga_itemsize * gap->ga_maxlen; + vim_memset(pp + old_len, 0, new_len - old_len); + gap->ga_maxlen = gap->ga_len + n; + gap->ga_data = pp; + return OK; +} + +/* + * For a growing array that contains a list of strings: concatenate all the + * strings with a separating "sep". + * Returns NULL when out of memory. + */ + char_u * +ga_concat_strings(garray_T *gap, char *sep) +{ + int i; + int len = 0; + int sep_len = (int)STRLEN(sep); + char_u *s; + char_u *p; + + for (i = 0; i < gap->ga_len; ++i) + len += (int)STRLEN(((char_u **)(gap->ga_data))[i]) + sep_len; + + s = alloc(len + 1); + if (s == NULL) + return NULL; + + *s = NUL; + p = s; + for (i = 0; i < gap->ga_len; ++i) + { + if (p != s) + { + STRCPY(p, sep); + p += sep_len; + } + STRCPY(p, ((char_u **)(gap->ga_data))[i]); + p += STRLEN(p); + } + return s; +} + +/* + * Make a copy of string "p" and add it to "gap". + * When out of memory nothing changes and FAIL is returned. + */ + int +ga_copy_string(garray_T *gap, char_u *p) +{ + char_u *cp = vim_strsave(p); + + if (cp == NULL) + return FAIL; + + if (ga_grow(gap, 1) == FAIL) + { + vim_free(cp); + return FAIL; + } + ((char_u **)(gap->ga_data))[gap->ga_len++] = cp; + return OK; +} + +/* + * Add string "p" to "gap". + * When out of memory FAIL is returned (caller may want to free "p"). + */ + int +ga_add_string(garray_T *gap, char_u *p) +{ + if (ga_grow(gap, 1) == FAIL) + return FAIL; + ((char_u **)(gap->ga_data))[gap->ga_len++] = p; + return OK; +} + +/* + * Concatenate a string to a growarray which contains bytes. + * When "s" is NULL memory allocation fails does not do anything. + * Note: Does NOT copy the NUL at the end! + */ + void +ga_concat(garray_T *gap, char_u *s) +{ + int len; + + if (s == NULL || *s == NUL) + return; + len = (int)STRLEN(s); + if (ga_grow(gap, len) == OK) + { + mch_memmove((char *)gap->ga_data + gap->ga_len, s, (size_t)len); + gap->ga_len += len; + } +} + +/* + * Concatenate 'len' bytes from string 's' to a growarray. + * When "s" is NULL does not do anything. + */ + void +ga_concat_len(garray_T *gap, char_u *s, size_t len) +{ + if (s == NULL || *s == NUL || len == 0) + return; + if (ga_grow(gap, (int)len) == OK) + { + mch_memmove((char *)gap->ga_data + gap->ga_len, s, len); + gap->ga_len += (int)len; + } +} + +/* + * Append one byte to a growarray which contains bytes. + */ + int +ga_append(garray_T *gap, int c) +{ + if (ga_grow(gap, 1) == FAIL) + return FAIL; + *((char *)gap->ga_data + gap->ga_len) = c; + ++gap->ga_len; + return OK; +} + +#if (defined(UNIX) && !defined(USE_SYSTEM)) || defined(MSWIN) \ + || defined(PROTO) +/* + * Append the text in "gap" below the cursor line and clear "gap". + */ + void +append_ga_line(garray_T *gap) +{ + // Remove trailing CR. + if (gap->ga_len > 0 + && !curbuf->b_p_bin + && ((char_u *)gap->ga_data)[gap->ga_len - 1] == CAR) + --gap->ga_len; + ga_append(gap, NUL); + ml_append(curwin->w_cursor.lnum++, gap->ga_data, 0, FALSE); + gap->ga_len = 0; +} +#endif + diff --git a/userland/apps/vim/src/alloc.h b/userland/apps/vim/src/alloc.h new file mode 100644 index 00000000..651e01f2 --- /dev/null +++ b/userland/apps/vim/src/alloc.h @@ -0,0 +1,49 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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.h: enumeration of alloc IDs. + * Used by test_alloc_fail() to test memory allocation failures. + * Each entry must be on exactly one line, GetAllocId() depends on that. + */ +typedef enum { + aid_none = 0, + aid_qf_dirname_start, + aid_qf_dirname_now, + aid_qf_namebuf, + aid_qf_module, + aid_qf_errmsg, + aid_qf_pattern, + aid_qf_efm_fmtstr, + aid_qf_efm_fmtpart, + aid_qf_title, + aid_qf_mef_name, + aid_qf_qfline, + aid_qf_qfinfo, + aid_qf_dirstack, + aid_qf_multiline_pfx, + aid_qf_makecmd, + aid_qf_linebuf, + aid_tagstack_items, + aid_tagstack_from, + aid_tagstack_details, + aid_sign_getdefined, + aid_sign_getplaced, + aid_sign_define_by_name, + aid_sign_getlist, + aid_sign_getplaced_dict, + aid_sign_getplaced_list, + aid_insert_sign, + aid_sign_getinfo, + aid_newbuf_bvars, + aid_newwin_wvars, + aid_newtabpage_tvars, + aid_blob_alloc, + aid_get_func, + aid_last +} alloc_id_T; diff --git a/userland/apps/vim/src/arabic.c b/userland/apps/vim/src/arabic.c new file mode 100644 index 00000000..f64dab24 --- /dev/null +++ b/userland/apps/vim/src/arabic.c @@ -0,0 +1,396 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * arabic.c: functions for Arabic language + * + * Author: Nadim Shaikli & Isam Bayazidi + * Farsi support and restructuring to make adding new letters easier by Ali + * Gholami Rudi. Further work by Ameretat Reith. + */ + +/* + * Sorted list of unicode Arabic characters. Each entry holds the + * presentation forms of a letter. + * + * Arabic characters are categorized into following types: + * + * Isolated - iso-8859-6 form + * Initial - unicode form-B start + * Medial - unicode form-B middle + * Final - unicode form-B final + * Stand-Alone - unicode form-B isolated + */ + +#include "vim.h" + +#if defined(FEAT_ARABIC) || defined(PROTO) + +// Unicode values for Arabic characters. +#define a_HAMZA 0x0621 +#define a_ALEF_MADDA 0x0622 +#define a_ALEF_HAMZA_ABOVE 0x0623 +#define a_WAW_HAMZA 0x0624 +#define a_ALEF_HAMZA_BELOW 0x0625 +#define a_YEH_HAMZA 0x0626 +#define a_ALEF 0x0627 +#define a_BEH 0x0628 +#define a_TEH_MARBUTA 0x0629 +#define a_TEH 0x062a +#define a_THEH 0x062b +#define a_JEEM 0x062c +#define a_HAH 0x062d +#define a_KHAH 0x062e +#define a_DAL 0x062f +#define a_THAL 0x0630 +#define a_REH 0x0631 +#define a_ZAIN 0x0632 +#define a_SEEN 0x0633 +#define a_SHEEN 0x0634 +#define a_SAD 0x0635 +#define a_DAD 0x0636 +#define a_TAH 0x0637 +#define a_ZAH 0x0638 +#define a_AIN 0x0639 +#define a_GHAIN 0x063a +#define a_TATWEEL 0x0640 +#define a_FEH 0x0641 +#define a_QAF 0x0642 +#define a_KAF 0x0643 +#define a_LAM 0x0644 +#define a_MEEM 0x0645 +#define a_NOON 0x0646 +#define a_HEH 0x0647 +#define a_WAW 0x0648 +#define a_ALEF_MAKSURA 0x0649 +#define a_YEH 0x064a +#define a_FATHATAN 0x064b +#define a_DAMMATAN 0x064c +#define a_KASRATAN 0x064d +#define a_FATHA 0x064e +#define a_DAMMA 0x064f +#define a_KASRA 0x0650 +#define a_SHADDA 0x0651 +#define a_SUKUN 0x0652 +#define a_MADDA_ABOVE 0x0653 +#define a_HAMZA_ABOVE 0x0654 +#define a_HAMZA_BELOW 0x0655 + +#define a_PEH 0x067e +#define a_TCHEH 0x0686 +#define a_JEH 0x0698 +#define a_FKAF 0x06a9 +#define a_GAF 0x06af +#define a_FYEH 0x06cc + +#define a_s_LAM_ALEF_MADDA_ABOVE 0xfef5 +#define a_f_LAM_ALEF_MADDA_ABOVE 0xfef6 +#define a_s_LAM_ALEF_HAMZA_ABOVE 0xfef7 +#define a_f_LAM_ALEF_HAMZA_ABOVE 0xfef8 +#define a_s_LAM_ALEF_HAMZA_BELOW 0xfef9 +#define a_f_LAM_ALEF_HAMZA_BELOW 0xfefa +#define a_s_LAM_ALEF 0xfefb +#define a_f_LAM_ALEF 0xfefc + +static struct achar { + unsigned c; + unsigned isolated; + unsigned initial; + unsigned medial; + unsigned final; +} achars[] = { + {a_HAMZA, 0xfe80, 0, 0, 0}, + {a_ALEF_MADDA, 0xfe81, 0, 0, 0xfe82}, + {a_ALEF_HAMZA_ABOVE, 0xfe83, 0, 0, 0xfe84}, + {a_WAW_HAMZA, 0xfe85, 0, 0, 0xfe86}, + {a_ALEF_HAMZA_BELOW, 0xfe87, 0, 0, 0xfe88}, + {a_YEH_HAMZA, 0xfe89, 0xfe8b, 0xfe8c, 0xfe8a}, + {a_ALEF, 0xfe8d, 0, 0, 0xfe8e}, + {a_BEH, 0xfe8f, 0xfe91, 0xfe92, 0xfe90}, + {a_TEH_MARBUTA, 0xfe93, 0, 0, 0xfe94}, + {a_TEH, 0xfe95, 0xfe97, 0xfe98, 0xfe96}, + {a_THEH, 0xfe99, 0xfe9b, 0xfe9c, 0xfe9a}, + {a_JEEM, 0xfe9d, 0xfe9f, 0xfea0, 0xfe9e}, + {a_HAH, 0xfea1, 0xfea3, 0xfea4, 0xfea2}, + {a_KHAH, 0xfea5, 0xfea7, 0xfea8, 0xfea6}, + {a_DAL, 0xfea9, 0, 0, 0xfeaa}, + {a_THAL, 0xfeab, 0, 0, 0xfeac}, + {a_REH, 0xfead, 0, 0, 0xfeae}, + {a_ZAIN, 0xfeaf, 0, 0, 0xfeb0}, + {a_SEEN, 0xfeb1, 0xfeb3, 0xfeb4, 0xfeb2}, + {a_SHEEN, 0xfeb5, 0xfeb7, 0xfeb8, 0xfeb6}, + {a_SAD, 0xfeb9, 0xfebb, 0xfebc, 0xfeba}, + {a_DAD, 0xfebd, 0xfebf, 0xfec0, 0xfebe}, + {a_TAH, 0xfec1, 0xfec3, 0xfec4, 0xfec2}, + {a_ZAH, 0xfec5, 0xfec7, 0xfec8, 0xfec6}, + {a_AIN, 0xfec9, 0xfecb, 0xfecc, 0xfeca}, + {a_GHAIN, 0xfecd, 0xfecf, 0xfed0, 0xfece}, + {a_TATWEEL, 0, 0x0640, 0x0640, 0x0640}, + {a_FEH, 0xfed1, 0xfed3, 0xfed4, 0xfed2}, + {a_QAF, 0xfed5, 0xfed7, 0xfed8, 0xfed6}, + {a_KAF, 0xfed9, 0xfedb, 0xfedc, 0xfeda}, + {a_LAM, 0xfedd, 0xfedf, 0xfee0, 0xfede}, + {a_MEEM, 0xfee1, 0xfee3, 0xfee4, 0xfee2}, + {a_NOON, 0xfee5, 0xfee7, 0xfee8, 0xfee6}, + {a_HEH, 0xfee9, 0xfeeb, 0xfeec, 0xfeea}, + {a_WAW, 0xfeed, 0, 0, 0xfeee}, + {a_ALEF_MAKSURA, 0xfeef, 0, 0, 0xfef0}, + {a_YEH, 0xfef1, 0xfef3, 0xfef4, 0xfef2}, + {a_FATHATAN, 0xfe70, 0, 0, 0}, + {a_DAMMATAN, 0xfe72, 0, 0, 0}, + {a_KASRATAN, 0xfe74, 0, 0, 0}, + {a_FATHA, 0xfe76, 0, 0xfe77, 0}, + {a_DAMMA, 0xfe78, 0, 0xfe79, 0}, + {a_KASRA, 0xfe7a, 0, 0xfe7b, 0}, + {a_SHADDA, 0xfe7c, 0, 0xfe7c, 0}, + {a_SUKUN, 0xfe7e, 0, 0xfe7f, 0}, + {a_MADDA_ABOVE, 0, 0, 0, 0}, + {a_HAMZA_ABOVE, 0, 0, 0, 0}, + {a_HAMZA_BELOW, 0, 0, 0, 0}, + {a_PEH, 0xfb56, 0xfb58, 0xfb59, 0xfb57}, + {a_TCHEH, 0xfb7a, 0xfb7c, 0xfb7d, 0xfb7b}, + {a_JEH, 0xfb8a, 0, 0, 0xfb8b}, + {a_FKAF, 0xfb8e, 0xfb90, 0xfb91, 0xfb8f}, + {a_GAF, 0xfb92, 0xfb94, 0xfb95, 0xfb93}, + {a_FYEH, 0xfbfc, 0xfbfe, 0xfbff, 0xfbfd}, +}; + +#define a_BYTE_ORDER_MARK 0xfeff + +/* + * Find the struct achar pointer to the given Arabic char. + * Returns NULL if not found. + */ + static struct achar * +find_achar(int c) +{ + int h, m, l; + + // using binary search to find c + h = ARRAY_LENGTH(achars); + l = 0; + while (l < h) + { + m = (h + l) / 2; + if (achars[m].c == (unsigned)c) + return &achars[m]; + if ((unsigned)c < achars[m].c) + h = m; + else + l = m + 1; + } + return NULL; +} + +/* + * Change shape - from Combination (2 char) to an Isolated + */ + static int +chg_c_laa2i(int hid_c) +{ + int tempc; + + switch (hid_c) + { + case a_ALEF_MADDA: + tempc = a_s_LAM_ALEF_MADDA_ABOVE; + break; + case a_ALEF_HAMZA_ABOVE: + tempc = a_s_LAM_ALEF_HAMZA_ABOVE; + break; + case a_ALEF_HAMZA_BELOW: + tempc = a_s_LAM_ALEF_HAMZA_BELOW; + break; + case a_ALEF: + tempc = a_s_LAM_ALEF; + break; + default: + tempc = 0; + } + + return tempc; +} + +/* + * Change shape - from Combination-Isolated to Final + */ + static int +chg_c_laa2f(int hid_c) +{ + int tempc; + + switch (hid_c) + { + case a_ALEF_MADDA: + tempc = a_f_LAM_ALEF_MADDA_ABOVE; + break; + case a_ALEF_HAMZA_ABOVE: + tempc = a_f_LAM_ALEF_HAMZA_ABOVE; + break; + case a_ALEF_HAMZA_BELOW: + tempc = a_f_LAM_ALEF_HAMZA_BELOW; + break; + case a_ALEF: + tempc = a_f_LAM_ALEF; + break; + default: + tempc = 0; + } + + return tempc; +} + +/* + * Returns whether it is possible to join the given letters + */ + static int +can_join(int c1, int c2) +{ + struct achar *a1 = find_achar(c1); + struct achar *a2 = find_achar(c2); + + return a1 && a2 && (a1->initial || a1->medial) && (a2->final || a2->medial); +} + +/* + * Check whether we are dealing with a character that could be regarded as an + * Arabic combining character, need to check the character before this. + */ + int +arabic_maycombine(int two) +{ + if (p_arshape && !p_tbidi) + return (two == a_ALEF_MADDA + || two == a_ALEF_HAMZA_ABOVE + || two == a_ALEF_HAMZA_BELOW + || two == a_ALEF); + return FALSE; +} + +/* + * Check whether we are dealing with Arabic combining characters. + * Note: these are NOT really composing characters! + */ + int +arabic_combine( + int one, // first character + int two) // character just after "one" +{ + if (one == a_LAM) + return arabic_maycombine(two); + return FALSE; +} + +/* + * A_is_iso returns true if 'c' is an Arabic ISO-8859-6 character + * (alphabet/number/punctuation) + */ + static int +A_is_iso(int c) +{ + return find_achar(c) != NULL; +} + +/* + * A_is_ok returns true if 'c' is an Arabic 10646 (8859-6 or Form-B) + */ + static int +A_is_ok(int c) +{ + return (A_is_iso(c) || c == a_BYTE_ORDER_MARK); +} + +/* + * A_is_valid returns true if 'c' is an Arabic 10646 (8859-6 or Form-B) + * with some exceptions/exclusions + */ + static int +A_is_valid(int c) +{ + return (A_is_ok(c) && c != a_HAMZA); +} + +/* + * Do Arabic shaping on character "c". Returns the shaped character. + * out: "ccp" points to the first byte of the character to be shaped. + * in/out: "c1p" points to the first composing char for "c". + * in: "prev_c" is the previous character (not shaped) + * in: "prev_c1" is the first composing char for the previous char + * (not shaped) + * in: "next_c" is the next character (not shaped). + */ + int +arabic_shape( + int c, + int *ccp, + int *c1p, + int prev_c, + int prev_c1, + int next_c) +{ + int curr_c; + int curr_laa; + int prev_laa; + + // Deal only with Arabic characters, pass back all others + if (!A_is_ok(c)) + return c; + + curr_laa = arabic_combine(c, *c1p); + prev_laa = arabic_combine(prev_c, prev_c1); + + if (curr_laa) + { + if (A_is_valid(prev_c) && can_join(prev_c, a_LAM) && !prev_laa) + curr_c = chg_c_laa2f(*c1p); + else + curr_c = chg_c_laa2i(*c1p); + + // Remove the composing character + *c1p = 0; + } + else + { + struct achar *curr_a = find_achar(c); + int backward_combine = !prev_laa && can_join(prev_c, c); + int forward_combine = can_join(c, next_c); + + if (backward_combine) + { + if (forward_combine) + curr_c = curr_a->medial; + else + curr_c = curr_a->final; + } + else + { + if (forward_combine) + curr_c = curr_a->initial; + else + curr_c = curr_a->isolated; + } + } + + // Character missing from the table means using original character. + if (curr_c == NUL) + curr_c = c; + + if (curr_c != c && ccp != NULL) + { + char_u buf[MB_MAXBYTES + 1]; + + // Update the first byte of the character. + (*mb_char2bytes)(curr_c, buf); + *ccp = buf[0]; + } + + // Return the shaped character + return curr_c; +} +#endif // FEAT_ARABIC diff --git a/userland/apps/vim/src/arglist.c b/userland/apps/vim/src/arglist.c new file mode 100644 index 00000000..d28b31da --- /dev/null +++ b/userland/apps/vim/src/arglist.c @@ -0,0 +1,1506 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * arglist.c: functions for dealing with the argument list + */ + +#include "vim.h" + +#define AL_SET 1 +#define AL_ADD 2 +#define AL_DEL 3 + +// This flag is set whenever the argument list is being changed and calling a +// function that might trigger an autocommand. +static int arglist_locked = FALSE; + + static int +check_arglist_locked(void) +{ + if (arglist_locked) + { + emsg(_(e_cannot_change_arglist_recursively)); + return FAIL; + } + return OK; +} + +/* + * Clear an argument list: free all file names and reset it to zero entries. + */ + void +alist_clear(alist_T *al) +{ + if (check_arglist_locked() == FAIL) + return; + while (--al->al_ga.ga_len >= 0) + vim_free(AARGLIST(al)[al->al_ga.ga_len].ae_fname); + ga_clear(&al->al_ga); +} + +/* + * Init an argument list. + */ + void +alist_init(alist_T *al) +{ + ga_init2(&al->al_ga, sizeof(aentry_T), 5); +} + +/* + * Remove a reference from an argument list. + * Ignored when the argument list is the global one. + * If the argument list is no longer used by any window, free it. + */ + void +alist_unlink(alist_T *al) +{ + if (al != &global_alist && --al->al_refcount <= 0) + { + alist_clear(al); + vim_free(al); + } +} + +/* + * Create a new argument list and use it for the current window. + */ + void +alist_new(void) +{ + curwin->w_alist = ALLOC_ONE(alist_T); + if (curwin->w_alist == NULL) + { + curwin->w_alist = &global_alist; + ++global_alist.al_refcount; + } + else + { + curwin->w_alist->al_refcount = 1; + curwin->w_alist->id = ++max_alist_id; + alist_init(curwin->w_alist); + } +} + +#if !defined(UNIX) || defined(PROTO) +/* + * Expand the file names in the global argument list. + * If "fnum_list" is not NULL, use "fnum_list[fnum_len]" as a list of buffer + * numbers to be re-used. + */ + void +alist_expand(int *fnum_list, int fnum_len) +{ + char_u **old_arg_files; + int old_arg_count; + char_u **new_arg_files; + int new_arg_file_count; + char_u *save_p_su = p_su; + int i; + + old_arg_files = ALLOC_MULT(char_u *, GARGCOUNT); + if (old_arg_files == NULL) + return; + + // Don't use 'suffixes' here. This should work like the shell did the + // expansion. Also, the vimrc file isn't read yet, thus the user + // can't set the options. + p_su = empty_option; + for (i = 0; i < GARGCOUNT; ++i) + old_arg_files[i] = vim_strsave(GARGLIST[i].ae_fname); + old_arg_count = GARGCOUNT; + if (expand_wildcards(old_arg_count, old_arg_files, + &new_arg_file_count, &new_arg_files, + EW_FILE|EW_NOTFOUND|EW_ADDSLASH|EW_NOERROR) == OK + && new_arg_file_count > 0) + { + alist_set(&global_alist, new_arg_file_count, new_arg_files, + TRUE, fnum_list, fnum_len); + FreeWild(old_arg_count, old_arg_files); + } + p_su = save_p_su; +} +#endif + +/* + * Set the argument list for the current window. + * Takes over the allocated files[] and the allocated fnames in it. + */ + void +alist_set( + alist_T *al, + int count, + char_u **files, + int use_curbuf, + int *fnum_list, + int fnum_len) +{ + int i; + + if (check_arglist_locked() == FAIL) + return; + + alist_clear(al); + if (GA_GROW_OK(&al->al_ga, count)) + { + for (i = 0; i < count; ++i) + { + if (got_int) + { + // When adding many buffers this can take a long time. Allow + // interrupting here. + while (i < count) + vim_free(files[i++]); + break; + } + + // May set buffer name of a buffer previously used for the + // argument list, so that it's re-used by alist_add. + if (fnum_list != NULL && i < fnum_len) + { + arglist_locked = TRUE; + buf_set_name(fnum_list[i], files[i]); + arglist_locked = FALSE; + } + + alist_add(al, files[i], use_curbuf ? 2 : 1); + ui_breakcheck(); + } + vim_free(files); + } + else + FreeWild(count, files); + if (al == &global_alist) + arg_had_last = FALSE; +} + +/* + * Add file "fname" to argument list "al". + * "fname" must have been allocated and "al" must have been checked for room. + */ + void +alist_add( + alist_T *al, + char_u *fname, + int set_fnum) // 1: set buffer number; 2: re-use curbuf +{ + if (fname == NULL) // don't add NULL file names + return; + if (check_arglist_locked() == FAIL) + return; + arglist_locked = TRUE; + +#ifdef BACKSLASH_IN_FILENAME + slash_adjust(fname); +#endif + AARGLIST(al)[al->al_ga.ga_len].ae_fname = fname; + if (set_fnum > 0) + AARGLIST(al)[al->al_ga.ga_len].ae_fnum = + buflist_add(fname, BLN_LISTED | (set_fnum == 2 ? BLN_CURBUF : 0)); + ++al->al_ga.ga_len; + + arglist_locked = FALSE; +} + +#if defined(BACKSLASH_IN_FILENAME) || defined(PROTO) +/* + * Adjust slashes in file names. Called after 'shellslash' was set. + */ + void +alist_slash_adjust(void) +{ + int i; + win_T *wp; + tabpage_T *tp; + + for (i = 0; i < GARGCOUNT; ++i) + if (GARGLIST[i].ae_fname != NULL) + slash_adjust(GARGLIST[i].ae_fname); + FOR_ALL_TAB_WINDOWS(tp, wp) + if (wp->w_alist != &global_alist) + for (i = 0; i < WARGCOUNT(wp); ++i) + if (WARGLIST(wp)[i].ae_fname != NULL) + slash_adjust(WARGLIST(wp)[i].ae_fname); +} +#endif + +/* + * Isolate one argument, taking backticks. + * Changes the argument in-place, puts a NUL after it. Backticks remain. + * Return a pointer to the start of the next argument. + */ + static char_u * +do_one_arg(char_u *str) +{ + char_u *p; + int inbacktick; + + inbacktick = FALSE; + for (p = str; *str; ++str) + { + // When the backslash is used for escaping the special meaning of a + // character we need to keep it until wildcard expansion. + if (rem_backslash(str)) + { + *p++ = *str++; + *p++ = *str; + } + else + { + // An item ends at a space not in backticks + if (!inbacktick && vim_isspace(*str)) + break; + if (*str == '`') + inbacktick ^= TRUE; + *p++ = *str; + } + } + str = skipwhite(str); + *p = NUL; + + return str; +} + +/* + * Separate the arguments in "str" and return a list of pointers in the + * growarray "gap". + */ + static int +get_arglist(garray_T *gap, char_u *str, int escaped) +{ + ga_init2(gap, sizeof(char_u *), 20); + while (*str != NUL) + { + if (ga_grow(gap, 1) == FAIL) + { + ga_clear(gap); + return FAIL; + } + ((char_u **)gap->ga_data)[gap->ga_len++] = str; + + // If str is escaped, don't handle backslashes or spaces + if (!escaped) + return OK; + + // Isolate one argument, change it in-place, put a NUL after it. + str = do_one_arg(str); + } + return OK; +} + +#if defined(FEAT_QUICKFIX) || defined(FEAT_SYN_HL) || defined(FEAT_SPELL) || defined(PROTO) +/* + * Parse a list of arguments (file names), expand them and return in + * "fnames[fcountp]". When "wig" is TRUE, removes files matching 'wildignore'. + * Return FAIL or OK. + */ + int +get_arglist_exp( + char_u *str, + int *fcountp, + char_u ***fnamesp, + int wig) +{ + garray_T ga; + int i; + + if (get_arglist(&ga, str, TRUE) == FAIL) + return FAIL; + if (wig == TRUE) + i = expand_wildcards(ga.ga_len, (char_u **)ga.ga_data, + fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD); + else + i = gen_expand_wildcards(ga.ga_len, (char_u **)ga.ga_data, + fcountp, fnamesp, EW_FILE|EW_NOTFOUND|EW_NOTWILD); + + ga_clear(&ga); + return i; +} +#endif + +/* + * Check the validity of the arg_idx for each other window. + */ + static void +alist_check_arg_idx(void) +{ + win_T *win; + tabpage_T *tp; + + FOR_ALL_TAB_WINDOWS(tp, win) + if (win->w_alist == curwin->w_alist) + check_arg_idx(win); +} + +/* + * Add files[count] to the arglist of the current window after arg "after". + * The file names in files[count] must have been allocated and are taken over. + * Files[] itself is not taken over. + */ + static void +alist_add_list( + int count, + char_u **files, + int after, // where to add: 0 = before first one + int will_edit) // will edit adding argument +{ + int i; + int old_argcount = ARGCOUNT; + + if (check_arglist_locked() != FAIL + && GA_GROW_OK(&ALIST(curwin)->al_ga, count)) + { + if (after < 0) + after = 0; + if (after > ARGCOUNT) + after = ARGCOUNT; + if (after < ARGCOUNT) + mch_memmove(&(ARGLIST[after + count]), &(ARGLIST[after]), + (ARGCOUNT - after) * sizeof(aentry_T)); + arglist_locked = TRUE; + for (i = 0; i < count; ++i) + { + int flags = BLN_LISTED | (will_edit ? BLN_CURBUF : 0); + + ARGLIST[after + i].ae_fname = files[i]; + ARGLIST[after + i].ae_fnum = buflist_add(files[i], flags); + } + arglist_locked = FALSE; + ALIST(curwin)->al_ga.ga_len += count; + if (old_argcount > 0 && curwin->w_arg_idx >= after) + curwin->w_arg_idx += count; + return; + } + + for (i = 0; i < count; ++i) + vim_free(files[i]); +} + +/* + * Delete the file names in 'alist_ga' from the argument list. + */ + static void +arglist_del_files(garray_T *alist_ga) +{ + regmatch_T regmatch; + int didone; + int i; + char_u *p; + int match; + + // Delete the items: use each item as a regexp and find a match in the + // argument list. + regmatch.rm_ic = p_fic; // ignore case when 'fileignorecase' is set + for (i = 0; i < alist_ga->ga_len && !got_int; ++i) + { + p = ((char_u **)alist_ga->ga_data)[i]; + p = file_pat_to_reg_pat(p, NULL, NULL, FALSE); + if (p == NULL) + break; + regmatch.regprog = vim_regcomp(p, magic_isset() ? RE_MAGIC : 0); + if (regmatch.regprog == NULL) + { + vim_free(p); + break; + } + + didone = FALSE; + for (match = 0; match < ARGCOUNT; ++match) + if (vim_regexec(®match, alist_name(&ARGLIST[match]), (colnr_T)0)) + { + didone = TRUE; + vim_free(ARGLIST[match].ae_fname); + mch_memmove(ARGLIST + match, ARGLIST + match + 1, + (ARGCOUNT - match - 1) * sizeof(aentry_T)); + --ALIST(curwin)->al_ga.ga_len; + if (curwin->w_arg_idx > match) + --curwin->w_arg_idx; + --match; + } + + vim_regfree(regmatch.regprog); + vim_free(p); + if (!didone) + semsg(_(e_no_match_str_2), ((char_u **)alist_ga->ga_data)[i]); + } + ga_clear(alist_ga); +} + +/* + * "what" == AL_SET: Redefine the argument list to 'str'. + * "what" == AL_ADD: add files in 'str' to the argument list after "after". + * "what" == AL_DEL: remove files in 'str' from the argument list. + * + * Return FAIL for failure, OK otherwise. + */ + static int +do_arglist( + char_u *str, + int what, + int after UNUSED, // 0 means before first one + int will_edit) // will edit added argument +{ + garray_T new_ga; + int exp_count; + char_u **exp_files; + int i; + int arg_escaped = TRUE; + + if (check_arglist_locked() == FAIL) + return FAIL; + + // Set default argument for ":argadd" command. + if (what == AL_ADD && *str == NUL) + { + if (curbuf->b_ffname == NULL) + return FAIL; + str = curbuf->b_fname; + arg_escaped = FALSE; + } + + // Collect all file name arguments in "new_ga". + if (get_arglist(&new_ga, str, arg_escaped) == FAIL) + return FAIL; + + if (what == AL_DEL) + arglist_del_files(&new_ga); + else + { + i = expand_wildcards(new_ga.ga_len, (char_u **)new_ga.ga_data, + &exp_count, &exp_files, EW_DIR|EW_FILE|EW_ADDSLASH|EW_NOTFOUND); + ga_clear(&new_ga); + if (i == FAIL || exp_count == 0) + { + emsg(_(e_no_match)); + return FAIL; + } + + if (what == AL_ADD) + { + alist_add_list(exp_count, exp_files, after, will_edit); + vim_free(exp_files); + } + else // what == AL_SET + alist_set(ALIST(curwin), exp_count, exp_files, will_edit, NULL, 0); + } + + alist_check_arg_idx(); + + return OK; +} + +/* + * Redefine the argument list. + */ + void +set_arglist(char_u *str) +{ + do_arglist(str, AL_SET, 0, FALSE); +} + +/* + * Return TRUE if window "win" is editing the file at the current argument + * index. + */ + int +editing_arg_idx(win_T *win) +{ + return !(win->w_arg_idx >= WARGCOUNT(win) + || (win->w_buffer->b_fnum + != WARGLIST(win)[win->w_arg_idx].ae_fnum + && (win->w_buffer->b_ffname == NULL + || !(fullpathcmp( + alist_name(&WARGLIST(win)[win->w_arg_idx]), + win->w_buffer->b_ffname, TRUE, TRUE) & FPC_SAME)))); +} + +/* + * Check if window "win" is editing the w_arg_idx file in its argument list. + */ + void +check_arg_idx(win_T *win) +{ + if (WARGCOUNT(win) > 1 && !editing_arg_idx(win)) + { + // We are not editing the current entry in the argument list. + // Set "arg_had_last" if we are editing the last one. + win->w_arg_idx_invalid = TRUE; + if (win->w_arg_idx != WARGCOUNT(win) - 1 + && arg_had_last == FALSE + && ALIST(win) == &global_alist + && GARGCOUNT > 0 + && win->w_arg_idx < GARGCOUNT + && (win->w_buffer->b_fnum == GARGLIST[GARGCOUNT - 1].ae_fnum + || (win->w_buffer->b_ffname != NULL + && (fullpathcmp(alist_name(&GARGLIST[GARGCOUNT - 1]), + win->w_buffer->b_ffname, TRUE, TRUE) & FPC_SAME)))) + arg_had_last = TRUE; + } + else + { + // We are editing the current entry in the argument list. + // Set "arg_had_last" if it's also the last one + win->w_arg_idx_invalid = FALSE; + if (win->w_arg_idx == WARGCOUNT(win) - 1 + && win->w_alist == &global_alist) + arg_had_last = TRUE; + } +} + +/* + * ":args", ":argslocal" and ":argsglobal". + */ + void +ex_args(exarg_T *eap) +{ + int i; + + if (eap->cmdidx != CMD_args) + { + if (check_arglist_locked() == FAIL) + return; + alist_unlink(ALIST(curwin)); + if (eap->cmdidx == CMD_argglobal) + ALIST(curwin) = &global_alist; + else // eap->cmdidx == CMD_arglocal + alist_new(); + } + + // ":args file ..": define new argument list, handle like ":next" + // Also for ":argslocal file .." and ":argsglobal file ..". + if (*eap->arg != NUL) + { + if (check_arglist_locked() == FAIL) + return; + ex_next(eap); + return; + } + + // ":args": list arguments. + if (eap->cmdidx == CMD_args) + { + char_u **items; + + if (ARGCOUNT <= 0) + return; // empty argument list + + items = ALLOC_MULT(char_u *, ARGCOUNT); + if (items == NULL) + return; + + // Overwrite the command, for a short list there is no scrolling + // required and no wait_return(). + gotocmdline(TRUE); + + for (i = 0; i < ARGCOUNT; ++i) + items[i] = alist_name(&ARGLIST[i]); + list_in_columns(items, ARGCOUNT, curwin->w_arg_idx); + vim_free(items); + + return; + } + + // ":argslocal": make a local copy of the global argument list. + if (eap->cmdidx == CMD_arglocal) + { + garray_T *gap = &curwin->w_alist->al_ga; + + if (GA_GROW_FAILS(gap, GARGCOUNT)) + return; + + for (i = 0; i < GARGCOUNT; ++i) + if (GARGLIST[i].ae_fname != NULL) + { + AARGLIST(curwin->w_alist)[gap->ga_len].ae_fname = + vim_strsave(GARGLIST[i].ae_fname); + AARGLIST(curwin->w_alist)[gap->ga_len].ae_fnum = + GARGLIST[i].ae_fnum; + ++gap->ga_len; + } + } +} + +/* + * ":previous", ":sprevious", ":Next" and ":sNext". + */ + void +ex_previous(exarg_T *eap) +{ + // If past the last one already, go to the last one. + if (curwin->w_arg_idx - (int)eap->line2 >= ARGCOUNT) + do_argfile(eap, ARGCOUNT - 1); + else + do_argfile(eap, curwin->w_arg_idx - (int)eap->line2); +} + +/* + * ":rewind", ":first", ":sfirst" and ":srewind". + */ + void +ex_rewind(exarg_T *eap) +{ + do_argfile(eap, 0); +} + +/* + * ":last" and ":slast". + */ + void +ex_last(exarg_T *eap) +{ + do_argfile(eap, ARGCOUNT - 1); +} + +/* + * ":argument" and ":sargument". + */ + void +ex_argument(exarg_T *eap) +{ + int i; + + if (eap->addr_count > 0) + i = eap->line2 - 1; + else + i = curwin->w_arg_idx; + do_argfile(eap, i); +} + +/* + * Edit file "argn" of the argument lists. + */ + void +do_argfile(exarg_T *eap, int argn) +{ + int other; + char_u *p; + int old_arg_idx = curwin->w_arg_idx; + + if (ERROR_IF_ANY_POPUP_WINDOW) + return; + if (argn < 0 || argn >= ARGCOUNT) + { + if (ARGCOUNT <= 1) + emsg(_(e_there_is_only_one_file_to_edit)); + else if (argn < 0) + emsg(_(e_cannot_go_before_first_file)); + else + emsg(_(e_cannot_go_beyond_last_file)); + + return; + } + + setpcmark(); +#ifdef FEAT_GUI + need_mouse_correct = TRUE; +#endif + + // split window or create new tab page first + if (*eap->cmd == 's' || cmdmod.cmod_tab != 0) + { + if (win_split(0, 0) == FAIL) + return; + RESET_BINDING(curwin); + } + else + { + // if 'hidden' set, only check for changed file when re-editing + // the same buffer + other = TRUE; + if (buf_hide(curbuf)) + { + p = fix_fname(alist_name(&ARGLIST[argn])); + other = otherfile(p); + vim_free(p); + } + if ((!buf_hide(curbuf) || !other) + && check_changed(curbuf, CCGD_AW + | (other ? 0 : CCGD_MULTWIN) + | (eap->forceit ? CCGD_FORCEIT : 0) + | CCGD_EXCMD)) + return; + } + + curwin->w_arg_idx = argn; + if (argn == ARGCOUNT - 1 && curwin->w_alist == &global_alist) + arg_had_last = TRUE; + + // Edit the file; always use the last known line number. + // When it fails (e.g. Abort for already edited file) restore the + // argument index. + if (do_ecmd(0, alist_name(&ARGLIST[curwin->w_arg_idx]), NULL, + eap, ECMD_LAST, + (buf_hide(curwin->w_buffer) ? ECMD_HIDE : 0) + + (eap->forceit ? ECMD_FORCEIT : 0), curwin) == FAIL) + curwin->w_arg_idx = old_arg_idx; + // like Vi: set the mark where the cursor is in the file. + else if (eap->cmdidx != CMD_argdo) + setmark('\''); +} + +/* + * ":next", and commands that behave like it. + */ + void +ex_next(exarg_T *eap) +{ + int i; + + // check for changed buffer now, if this fails the argument list is not + // redefined. + if ( buf_hide(curbuf) + || eap->cmdidx == CMD_snext + || !check_changed(curbuf, CCGD_AW + | (eap->forceit ? CCGD_FORCEIT : 0) + | CCGD_EXCMD)) + { + if (*eap->arg != NUL) // redefine file list + { + if (do_arglist(eap->arg, AL_SET, 0, TRUE) == FAIL) + return; + i = 0; + } + else + i = curwin->w_arg_idx + (int)eap->line2; + do_argfile(eap, i); + } +} + +/* + * ":argdedupe" + */ + void +ex_argdedupe(exarg_T *eap UNUSED) +{ + int i; + int j; + + for (i = 0; i < ARGCOUNT; ++i) + { + // Expand each argument to a full path to catch different paths leading + // to the same file. + char_u *firstFullname = FullName_save(ARGLIST[i].ae_fname, FALSE); + if (firstFullname == NULL) + return; // out of memory + + for (j = i + 1; j < ARGCOUNT; ++j) + { + char_u *secondFullname = FullName_save(ARGLIST[j].ae_fname, FALSE); + if (secondFullname == NULL) + break; // out of memory + int areNamesDuplicate = + fnamecmp(firstFullname, secondFullname) == 0; + vim_free(secondFullname); + + if (areNamesDuplicate) + { + // remove one duplicate argument + vim_free(ARGLIST[j].ae_fname); + mch_memmove(ARGLIST + j, ARGLIST + j + 1, + (ARGCOUNT - j - 1) * sizeof(aentry_T)); + --ARGCOUNT; + + if (curwin->w_arg_idx == j) + curwin->w_arg_idx = i; + else if (curwin->w_arg_idx > j) + --curwin->w_arg_idx; + + --j; + } + } + + vim_free(firstFullname); + } +} + +/* + * ":argedit" + */ + void +ex_argedit(exarg_T *eap) +{ + int i = eap->addr_count ? (int)eap->line2 : curwin->w_arg_idx + 1; + // Whether curbuf will be reused, curbuf->b_ffname will be set. + int curbuf_is_reusable = curbuf_reusable(); + + if (do_arglist(eap->arg, AL_ADD, i, TRUE) == FAIL) + return; + maketitle(); + + if (curwin->w_arg_idx == 0 + && (curbuf->b_ml.ml_flags & ML_EMPTY) + && (curbuf->b_ffname == NULL || curbuf_is_reusable)) + i = 0; + // Edit the argument. + if (i < ARGCOUNT) + do_argfile(eap, i); +} + +/* + * ":argadd" + */ + void +ex_argadd(exarg_T *eap) +{ + do_arglist(eap->arg, AL_ADD, + eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1, + FALSE); + maketitle(); +} + +/* + * ":argdelete" + */ + void +ex_argdelete(exarg_T *eap) +{ + int i; + int n; + + if (check_arglist_locked() == FAIL) + return; + + if (eap->addr_count > 0 || *eap->arg == NUL) + { + // ":argdel" works like ":.argdel" + if (eap->addr_count == 0) + { + if (curwin->w_arg_idx >= ARGCOUNT) + { + emsg(_(e_no_argument_to_delete)); + return; + } + eap->line1 = eap->line2 = curwin->w_arg_idx + 1; + } + else if (eap->line2 > ARGCOUNT) + // ":1,4argdel": Delete all arguments in the range. + eap->line2 = ARGCOUNT; + n = eap->line2 - eap->line1 + 1; + if (*eap->arg != NUL) + // Can't have both a range and an argument. + emsg(_(e_invalid_argument)); + else if (n <= 0) + { + // Don't give an error for ":%argdel" if the list is empty. + if (eap->line1 != 1 || eap->line2 != 0) + emsg(_(e_invalid_range)); + } + else + { + for (i = eap->line1; i <= eap->line2; ++i) + vim_free(ARGLIST[i - 1].ae_fname); + mch_memmove(ARGLIST + eap->line1 - 1, ARGLIST + eap->line2, + (size_t)((ARGCOUNT - eap->line2) * sizeof(aentry_T))); + ALIST(curwin)->al_ga.ga_len -= n; + if (curwin->w_arg_idx >= eap->line2) + curwin->w_arg_idx -= n; + else if (curwin->w_arg_idx > eap->line1) + curwin->w_arg_idx = eap->line1; + if (ARGCOUNT == 0) + curwin->w_arg_idx = 0; + else if (curwin->w_arg_idx >= ARGCOUNT) + curwin->w_arg_idx = ARGCOUNT - 1; + } + } + else + do_arglist(eap->arg, AL_DEL, 0, FALSE); + maketitle(); +} + +/* + * Function given to ExpandGeneric() to obtain the possible arguments of the + * argedit and argdelete commands. + */ + char_u * +get_arglist_name(expand_T *xp UNUSED, int idx) +{ + if (idx >= ARGCOUNT) + return NULL; + + return alist_name(&ARGLIST[idx]); +} + +/* + * Get the file name for an argument list entry. + */ + char_u * +alist_name(aentry_T *aep) +{ + buf_T *bp; + + // Use the name from the associated buffer if it exists. + bp = buflist_findnr(aep->ae_fnum); + if (bp == NULL || bp->b_fname == NULL) + return aep->ae_fname; + return bp->b_fname; +} + +/* + * State used by the :all command to open all the files in the argument list in + * separate windows. + */ +typedef struct { + alist_T *alist; // argument list to be used + int had_tab; + int keep_tabs; + int forceit; + + int use_firstwin; // use first window for arglist + char_u *opened; // Array of weight for which args are open: + // 0: not opened + // 1: opened in other tab + // 2: opened in curtab + // 3: opened in curtab and curwin + int opened_len; // length of opened[] + win_T *new_curwin; + tabpage_T *new_curtab; +} arg_all_state_T; + +/* + * Close all the windows containing files which are not in the argument list. + * Used by the ":all" command. + */ + static void +arg_all_close_unused_windows(arg_all_state_T *aall) +{ + win_T *wp; + win_T *wpnext; + tabpage_T *tpnext; + buf_T *buf; + int i; + win_T *old_curwin; + tabpage_T *old_curtab; + + old_curwin = curwin; + old_curtab = curtab; + + if (aall->had_tab > 0) + goto_tabpage_tp(first_tabpage, TRUE, TRUE); + + // moving tabpages around in an autocommand may cause an endless loop + tabpage_move_disallowed++; + for (;;) + { + tpnext = curtab->tp_next; + for (wp = firstwin; wp != NULL; wp = wpnext) + { + wpnext = wp->w_next; + buf = wp->w_buffer; + if (buf->b_ffname == NULL + || (!aall->keep_tabs && (buf->b_nwindows > 1 + || wp->w_width != Columns))) + i = aall->opened_len; + else + { + // check if the buffer in this window is in the arglist + for (i = 0; i < aall->opened_len; ++i) + { + if (i < aall->alist->al_ga.ga_len + && (AARGLIST(aall->alist)[i].ae_fnum == buf->b_fnum + || fullpathcmp(alist_name( + &AARGLIST(aall->alist)[i]), + buf->b_ffname, TRUE, TRUE) & FPC_SAME)) + { + int weight = 1; + + if (old_curtab == curtab) + { + ++weight; + if (old_curwin == wp) + ++weight; + } + + if (weight > (int)aall->opened[i]) + { + aall->opened[i] = (char_u)weight; + if (i == 0) + { + if (aall->new_curwin != NULL) + aall->new_curwin->w_arg_idx = + aall->opened_len; + aall->new_curwin = wp; + aall->new_curtab = curtab; + } + } + else if (aall->keep_tabs) + i = aall->opened_len; + + if (wp->w_alist != aall->alist) + { + // Use the current argument list for all windows + // containing a file from it. + alist_unlink(wp->w_alist); + wp->w_alist = aall->alist; + ++wp->w_alist->al_refcount; + } + break; + } + } + } + wp->w_arg_idx = i; + + if (i == aall->opened_len && !aall->keep_tabs)// close this window + { + if (buf_hide(buf) || aall->forceit || buf->b_nwindows > 1 + || !bufIsChanged(buf)) + { + // If the buffer was changed, and we would like to hide it, + // try autowriting. + if (!buf_hide(buf) && buf->b_nwindows <= 1 + && bufIsChanged(buf)) + { + bufref_T bufref; + + set_bufref(&bufref, buf); + + (void)autowrite(buf, FALSE); + + // check if autocommands removed the window + if (!win_valid(wp) || !bufref_valid(&bufref)) + { + wpnext = firstwin; // start all over... + continue; + } + } + // don't close last window + if (ONE_WINDOW + && (first_tabpage->tp_next == NULL + || !aall->had_tab)) + aall->use_firstwin = TRUE; + else + { + win_close(wp, !buf_hide(buf) && !bufIsChanged(buf)); + + // check if autocommands removed the next window + if (!win_valid(wpnext)) + wpnext = firstwin; // start all over... + } + } + } + } + + // Without the ":tab" modifier only do the current tab page. + if (aall->had_tab == 0 || tpnext == NULL) + break; + + // check if autocommands removed the next tab page + if (!valid_tabpage(tpnext)) + tpnext = first_tabpage; // start all over... + + goto_tabpage_tp(tpnext, TRUE, TRUE); + } + tabpage_move_disallowed--; +} + +/* + * Open upto "count" windows for the files in the argument list 'aall->alist'. + */ + static void +arg_all_open_windows(arg_all_state_T *aall, int count) +{ + win_T *wp; + int tab_drop_empty_window = FALSE; + int i; + int split_ret = OK; + int p_ea_save; + + // ":tab drop file" should re-use an empty window to avoid "--remote-tab" + // leaving an empty tab page when executed locally. + if (aall->keep_tabs && BUFEMPTY() && curbuf->b_nwindows == 1 + && curbuf->b_ffname == NULL && !curbuf->b_changed) + { + aall->use_firstwin = TRUE; + tab_drop_empty_window = TRUE; + } + + for (i = 0; i < count && !got_int; ++i) + { + if (aall->alist == &global_alist && i == global_alist.al_ga.ga_len - 1) + arg_had_last = TRUE; + if (aall->opened[i] > 0) + { + // Move the already present window to below the current window + if (curwin->w_arg_idx != i) + { + FOR_ALL_WINDOWS(wp) + { + if (wp->w_arg_idx == i) + { + if (aall->keep_tabs) + { + aall->new_curwin = wp; + aall->new_curtab = curtab; + } + else if (wp->w_frame->fr_parent + != curwin->w_frame->fr_parent) + { + emsg(_(e_window_layout_changed_unexpectedly)); + i = count; + break; + } + else + win_move_after(wp, curwin); + break; + } + } + } + } + else if (split_ret == OK) + { + // trigger events for tab drop + if (tab_drop_empty_window && i == count - 1) + --autocmd_no_enter; + if (!aall->use_firstwin) // split current window + { + p_ea_save = p_ea; + p_ea = TRUE; // use space from all windows + split_ret = win_split(0, WSP_ROOM | WSP_BELOW); + p_ea = p_ea_save; + if (split_ret == FAIL) + continue; + } + else // first window: do autocmd for leaving this buffer + --autocmd_no_leave; + + // edit file "i" + curwin->w_arg_idx = i; + if (i == 0) + { + aall->new_curwin = curwin; + aall->new_curtab = curtab; + } + (void)do_ecmd(0, alist_name(&AARGLIST(aall->alist)[i]), NULL, NULL, + ECMD_ONE, + ((buf_hide(curwin->w_buffer) + || bufIsChanged(curwin->w_buffer)) ? ECMD_HIDE : 0) + + ECMD_OLDBUF, curwin); + if (tab_drop_empty_window && i == count - 1) + ++autocmd_no_enter; + if (aall->use_firstwin) + ++autocmd_no_leave; + aall->use_firstwin = FALSE; + } + ui_breakcheck(); + + // When ":tab" was used open a new tab for a new window repeatedly. + if (aall->had_tab > 0 && tabpage_index(NULL) <= p_tpm) + cmdmod.cmod_tab = 9999; + } +} + +/* + * do_arg_all(): Open up to "count" windows, one for each argument. + */ + static void +do_arg_all( + int count, + int forceit, // hide buffers in current windows + int keep_tabs) // keep current tabs, for ":tab drop file" +{ + arg_all_state_T aall; + win_T *last_curwin; + tabpage_T *last_curtab; + int prev_arglist_locked = arglist_locked; + + if (cmdwin_type != 0) + { + emsg(_(e_invalid_in_cmdline_window)); + return; + } + if (ARGCOUNT <= 0) + { + // Don't give an error message. We don't want it when the ":all" + // command is in the .vimrc. + return; + } + setpcmark(); + + aall.use_firstwin = FALSE; + aall.had_tab = cmdmod.cmod_tab; + aall.new_curwin = NULL; + aall.new_curtab = NULL; + aall.forceit = forceit; + aall.keep_tabs = keep_tabs; + aall.opened_len = ARGCOUNT; + aall.opened = alloc_clear(aall.opened_len); + if (aall.opened == NULL) + return; + + // Autocommands may do anything to the argument list. Make sure it's not + // freed while we are working here by "locking" it. We still have to + // watch out for its size being changed. + aall.alist = curwin->w_alist; + ++aall.alist->al_refcount; + arglist_locked = TRUE; + +#ifdef FEAT_GUI + need_mouse_correct = TRUE; +#endif + + tabpage_T *new_lu_tp = curtab; + + // Try closing all windows that are not in the argument list. + // Also close windows that are not full width; + // When 'hidden' or "forceit" set the buffer becomes hidden. + // Windows that have a changed buffer and can't be hidden won't be closed. + // When the ":tab" modifier was used do this for all tab pages. + arg_all_close_unused_windows(&aall); + + // Now set the last used tabpage to where we started. + if (valid_tabpage(new_lu_tp)) + lastused_tabpage = new_lu_tp; + + // Open a window for files in the argument list that don't have one. + // ARGCOUNT may change while doing this, because of autocommands. + if (count > aall.opened_len || count <= 0) + count = aall.opened_len; + + // Don't execute Win/Buf Enter/Leave autocommands here. + ++autocmd_no_enter; + ++autocmd_no_leave; + last_curwin = curwin; + last_curtab = curtab; + win_enter(lastwin, FALSE); + + /* + * Open upto "count" windows. + */ + arg_all_open_windows(&aall, count); + + // Remove the "lock" on the argument list. + alist_unlink(aall.alist); + arglist_locked = prev_arglist_locked; + + --autocmd_no_enter; + + // restore last referenced tabpage's curwin + if (last_curtab != aall.new_curtab) + { + if (valid_tabpage(last_curtab)) + goto_tabpage_tp(last_curtab, TRUE, TRUE); + if (win_valid(last_curwin)) + win_enter(last_curwin, FALSE); + } + // to window with first arg + if (valid_tabpage(aall.new_curtab)) + goto_tabpage_tp(aall.new_curtab, TRUE, TRUE); + if (win_valid(aall.new_curwin)) + win_enter(aall.new_curwin, FALSE); + + --autocmd_no_leave; + vim_free(aall.opened); +} + +/* + * ":all" and ":sall". + * Also used for ":tab drop file ..." after setting the argument list. + */ + void +ex_all(exarg_T *eap) +{ + if (eap->addr_count == 0) + eap->line2 = 9999; + do_arg_all((int)eap->line2, eap->forceit, eap->cmdidx == CMD_drop); +} + +/* + * Concatenate all files in the argument list, separated by spaces, and return + * it in one allocated string. + * Spaces and backslashes in the file names are escaped with a backslash. + * Returns NULL when out of memory. + */ + char_u * +arg_all(void) +{ + int len; + int idx; + char_u *retval = NULL; + char_u *p; + + // Do this loop two times: + // first time: compute the total length + // second time: concatenate the names + for (;;) + { + len = 0; + for (idx = 0; idx < ARGCOUNT; ++idx) + { + p = alist_name(&ARGLIST[idx]); + if (p == NULL) + continue; + if (len > 0) + { + // insert a space in between names + if (retval != NULL) + retval[len] = ' '; + ++len; + } + for ( ; *p != NUL; ++p) + { + if (*p == ' ' +#ifndef BACKSLASH_IN_FILENAME + || *p == '\\' +#endif + || *p == '`') + { + // insert a backslash + if (retval != NULL) + retval[len] = '\\'; + ++len; + } + if (retval != NULL) + retval[len] = *p; + ++len; + } + } + + // second time: break here + if (retval != NULL) + { + retval[len] = NUL; + break; + } + + // allocate memory + retval = alloc(len + 1); + if (retval == NULL) + break; + } + + return retval; +} + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * "argc([window id])" function + */ + void +f_argc(typval_T *argvars, typval_T *rettv) +{ + win_T *wp; + + if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL) + return; + + if (argvars[0].v_type == VAR_UNKNOWN) + // use the current window + rettv->vval.v_number = ARGCOUNT; + else if (argvars[0].v_type == VAR_NUMBER + && tv_get_number(&argvars[0]) == -1) + // use the global argument list + rettv->vval.v_number = GARGCOUNT; + else + { + // use the argument list of the specified window + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp != NULL) + rettv->vval.v_number = WARGCOUNT(wp); + else + rettv->vval.v_number = -1; + } +} + +/* + * "argidx()" function + */ + void +f_argidx(typval_T *argvars UNUSED, typval_T *rettv) +{ + rettv->vval.v_number = curwin->w_arg_idx; +} + +/* + * "arglistid()" function + */ + void +f_arglistid(typval_T *argvars, typval_T *rettv) +{ + win_T *wp; + + if (in_vim9script() + && (check_for_opt_number_arg(argvars, 0) == FAIL + || (argvars[0].v_type != VAR_UNKNOWN + && check_for_opt_number_arg(argvars, 1) == FAIL))) + return; + + rettv->vval.v_number = -1; + wp = find_tabwin(&argvars[0], &argvars[1], NULL); + if (wp != NULL) + rettv->vval.v_number = wp->w_alist->id; +} + +/* + * Get the argument list for a given window + */ + static void +get_arglist_as_rettv(aentry_T *arglist, int argcount, typval_T *rettv) +{ + int idx; + + if (rettv_list_alloc(rettv) == OK && arglist != NULL) + for (idx = 0; idx < argcount; ++idx) + list_append_string(rettv->vval.v_list, + alist_name(&arglist[idx]), -1); +} + +/* + * "argv(nr)" function + */ + void +f_argv(typval_T *argvars, typval_T *rettv) +{ + int idx; + aentry_T *arglist = NULL; + int argcount = -1; + + if (in_vim9script() + && (check_for_opt_number_arg(argvars, 0) == FAIL + || (argvars[0].v_type != VAR_UNKNOWN + && check_for_opt_number_arg(argvars, 1) == FAIL))) + return; + + if (argvars[0].v_type == VAR_UNKNOWN) + { + get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv); + return; + } + + if (argvars[1].v_type == VAR_UNKNOWN) + { + arglist = ARGLIST; + argcount = ARGCOUNT; + } + else if (argvars[1].v_type == VAR_NUMBER + && tv_get_number(&argvars[1]) == -1) + { + arglist = GARGLIST; + argcount = GARGCOUNT; + } + else + { + win_T *wp = find_win_by_nr_or_id(&argvars[1]); + + if (wp != NULL) + { + // Use the argument list of the specified window + arglist = WARGLIST(wp); + argcount = WARGCOUNT(wp); + } + } + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + idx = tv_get_number_chk(&argvars[0], NULL); + if (arglist != NULL && idx >= 0 && idx < argcount) + rettv->vval.v_string = vim_strsave(alist_name(&arglist[idx])); + else if (idx == -1) + get_arglist_as_rettv(arglist, argcount, rettv); +} +#endif diff --git a/userland/apps/vim/src/ascii.h b/userland/apps/vim/src/ascii.h new file mode 100644 index 00000000..ec839704 --- /dev/null +++ b/userland/apps/vim/src/ascii.h @@ -0,0 +1,92 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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 CharOrd(x) ((x) < 'a' ? (x) - 'A' : (x) - 'a') +#define CharOrdLow(x) ((x) - 'a') +#define CharOrdUp(x) ((x) - 'A') +#define ROT13(c, a) (((((c) - (a)) + 13) % 26) + (a)) + +#define NUL '\000' +#define BELL '\007' +#define BS '\010' +#define TAB '\011' +#define NL '\012' +#define NL_STR (char_u *)"\012" +#define FF '\014' +#define CAR '\015' // CR is used by Mac OS X +#define ESC '\033' +#define ESC_STR (char_u *)"\033" +#define ESC_STR_nc "\033" +#define DEL 0x7f +#define DEL_STR (char_u *)"\177" + +#define POUND 0xA3 + +#define Ctrl_chr(x) (TOUPPER_ASC(x) ^ 0x40) // '?' -> DEL, '@' -> ^@, etc. +#define Meta(x) ((x) | 0x80) + +#define CTRL_F_STR "\006" +#define CTRL_H_STR "\010" +#define CTRL_V_STR "\026" + +#define Ctrl_AT 0 // @ +#define Ctrl_A 1 +#define Ctrl_B 2 +#define Ctrl_C 3 +#define Ctrl_D 4 +#define Ctrl_E 5 +#define Ctrl_F 6 +#define Ctrl_G 7 +#define Ctrl_H 8 +#define Ctrl_I 9 +#define Ctrl_J 10 +#define Ctrl_K 11 +#define Ctrl_L 12 +#define Ctrl_M 13 +#define Ctrl_N 14 +#define Ctrl_O 15 +#define Ctrl_P 16 +#define Ctrl_Q 17 +#define Ctrl_R 18 +#define Ctrl_S 19 +#define Ctrl_T 20 +#define Ctrl_U 21 +#define Ctrl_V 22 +#define Ctrl_W 23 +#define Ctrl_X 24 +#define Ctrl_Y 25 +#define Ctrl_Z 26 + // CTRL- [ Left Square Bracket == ESC +#define Ctrl_BSL 28 // \ BackSLash +#define Ctrl_RSB 29 // ] Right Square Bracket +#define Ctrl_HAT 30 // ^ +#define Ctrl__ 31 + +#define CSI 0x9b // Control Sequence Introducer +#define CSI_STR "\233" +#define DCS 0x90 // Device Control String +#define OSC 0x9d // Operating System Command +#define STERM 0x9c // String Terminator + +/* + * Character that separates dir names in a path. + * For MS-DOS, WIN32 and OS/2 we use a backslash. A slash mostly works + * fine, but there are places where it doesn't (e.g. in a command name). + * For Acorn we use a dot. + */ +#ifdef BACKSLASH_IN_FILENAME +# define PATHSEP psepc +# define PATHSEPSTR pseps +#else +# define PATHSEP '/' +# define PATHSEPSTR "/" +#endif diff --git a/userland/apps/vim/src/auto/config.h b/userland/apps/vim/src/auto/config.h new file mode 100644 index 00000000..a09976c3 --- /dev/null +++ b/userland/apps/vim/src/auto/config.h @@ -0,0 +1,174 @@ +/* + * auto/config.h - Stellux-specific Vim configuration + * + * Generated for cross-compilation to Stellux OS with musl libc. + * Tiny feature set, no GUI, no shell commands, no ncurses. + */ + +/* Feature level */ +#define FEAT_TINY 1 + +/* Basic OS */ +#define UNIX 1 +#define HAVE_DATE_TIME 1 +#define HAVE_ATTRIBUTE_UNUSED 1 + +/* Data type sizes */ +#define VIM_SIZEOF_INT 4 +#define VIM_SIZEOF_LONG 8 +#define SIZEOF_OFF_T 8 +#define SIZEOF_TIME_T 8 + +/* Memory */ +#define USEMEMMOVE 1 + +/* Terminal - use builtin termcap, no ncurses/terminfo */ +/* #undef TERMINFO */ +/* #undef HAVE_OSPEED */ +/* #undef HAVE_UP_BC_PC */ +/* #undef HAVE_DEL_CURTERM */ +/* #undef HAVE_TERMCAP_H */ +#define TGETENT_ZERO_ERR 0 + +/* File operations available in Stellux */ +#define HAVE_FCHDIR 1 +/* #undef HAVE_FCHOWN */ +/* #undef HAVE_FCHMOD */ +#define HAVE_FSEEKO 1 +#define HAVE_FSYNC 1 +#define HAVE_FTRUNCATE 1 +#define HAVE_GETCWD 1 +/* #undef HAVE_GETPGID */ +/* #undef HAVE_GETPWENT */ +/* #undef HAVE_GETPWNAM */ +/* #undef HAVE_GETPWUID */ +/* #undef HAVE_GETRLIMIT */ +#define HAVE_GETTIMEOFDAY 1 +/* #undef HAVE_ICONV */ +/* #undef HAVE_LOCALTIME_R */ +/* #undef HAVE_LSTAT */ +#define HAVE_MEMSET 1 +/* #undef HAVE_MKDTEMP */ +#define HAVE_NANOSLEEP 1 +/* #undef HAVE_NL_LANGINFO_CODESET */ +#define HAVE_OPENDIR 1 +/* #undef HAVE_POSIX_OPENPT */ +#define HAVE_PUTENV 1 +#define HAVE_QSORT 1 +/* #undef HAVE_READLINK */ +/* #undef HAVE_RENAME */ +#define HAVE_SELECT 1 +/* #undef HAVE_SELINUX */ +#define HAVE_SETENV 1 +/* #undef HAVE_SETPGID */ +/* #undef HAVE_SETSID */ +#define HAVE_SIGACTION 1 +/* #undef HAVE_SIGALTSTACK */ +/* #undef HAVE_SIGSET */ +/* #undef HAVE_SIGSTACK */ +#define HAVE_SIGPROCMASK 1 +#define HAVE_STRCASECMP 1 +/* #undef HAVE_STRCOLL */ +#define HAVE_STRERROR 1 +#define HAVE_STRFTIME 1 +#define HAVE_STRNCASECMP 1 +#define HAVE_STRTOL 1 +#define HAVE_TOWLOWER 1 +#define HAVE_TOWUPPER 1 +#define HAVE_ISWUPPER 1 +/* #undef HAVE_TZSET */ +/* #undef HAVE_UNSETENV */ +#define HAVE_USLEEP 1 +/* #undef HAVE_UTIME */ +#define HAVE_MBLEN 1 +/* #undef HAVE_TIMER_CREATE */ +#define HAVE_CLOCK_GETTIME 1 +/* #undef HAVE_XATTR */ +/* #undef HAVE_UTIMES */ + +/* Headers available via musl */ +#define HAVE_DIRENT_H 1 +#define HAVE_ERRNO_H 1 +#define HAVE_FCNTL_H 1 +/* #undef HAVE_ICONV_H */ +#define HAVE_INTTYPES_H 1 +/* #undef HAVE_LANGINFO_H */ +/* #undef HAVE_LIBGEN_H */ +/* #undef HAVE_LIBINTL_H */ +#define HAVE_LOCALE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_POLL_H 1 +/* #undef HAVE_PWD_H */ +#define HAVE_SETJMP_H 1 +/* #undef HAVE_SGTTY_H */ +#define HAVE_STDINT_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_SYS_IOCTL_H 1 +/* #undef HAVE_SYS_PARAM_H */ +#define HAVE_SYS_POLL_H 1 +/* #undef HAVE_SYS_RESOURCE_H */ +#define HAVE_SYS_SELECT_H 1 +/* #undef HAVE_SYS_STATFS_H */ +/* #undef HAVE_SYS_SYSINFO_H */ +#define HAVE_SYS_TIME_H 1 +#define HAVE_SYS_TYPES_H 1 +/* #undef HAVE_SYS_UTSNAME_H */ +#define HAVE_TERMIOS_H 1 +/* #undef HAVE_TERMIO_H */ +#define HAVE_WCHAR_H 1 +#define HAVE_WCTYPE_H 1 +#define HAVE_UNISTD_H 1 +/* #undef HAVE_UTIME_H */ +#define HAVE_SYS_WAIT_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STRING_H 1 + +/* PTY - we have our own custom PTY, not SVR4/BSD */ +/* #undef HAVE_SVR4_PTYS */ + +/* Signals */ +/* #undef HAVE_SIGCONTEXT */ + +/* select() */ +#define SYS_SELECT_WITH_SYS_TIME 1 +#define SELECT_TYPE_ARG234 (fd_set *) + +/* Disabled features */ +/* #undef HAVE_DLFCN_H */ +/* #undef HAVE_DLOPEN */ +/* #undef HAVE_DLSYM */ +/* #undef USE_XSMP_INTERACT */ +/* #undef HAVE_FD_CLOEXEC */ +/* #undef HAVE_ISINF */ +/* #undef HAVE_ISNAN */ +/* #undef HAVE_DIRFD */ +/* #undef HAVE_FLOCK */ +/* #undef HAVE_SYSCONF_SIGSTKSZ */ + +/* No process stuff */ +/* #undef HAVE_FORK */ +/* #undef HAVE_WAIT */ + +/* Feature flags */ +/* #undef FEAT_NORMAL */ +/* #undef FEAT_HUGE */ +/* #undef FEAT_LUA */ +/* #undef FEAT_MZSCHEME */ +/* #undef FEAT_PERL */ +/* #undef FEAT_PYTHON */ +/* #undef FEAT_PYTHON3 */ +/* #undef FEAT_RUBY */ +/* #undef FEAT_TCL */ +/* #undef FEAT_CSCOPE */ +/* #undef FEAT_AUTOSERVERNAME */ +/* #undef FEAT_XFONTSET */ +/* #undef FEAT_XIM */ +/* #undef FEAT_GUI_GNOME */ +/* #undef FEAT_IPV6 */ +/* #undef FEAT_NETBEANS_INTG */ +/* #undef FEAT_JOB_CHANNEL */ +/* #undef FEAT_TERMINAL */ +/* #undef FEAT_SOUND */ + +/* System-wide vimrc shipped in the initrd */ +#define SYS_VIMRC_FILE "/etc/vim/vimrc" diff --git a/userland/apps/vim/src/auto/osdef.h b/userland/apps/vim/src/auto/osdef.h new file mode 100644 index 00000000..33699c14 --- /dev/null +++ b/userland/apps/vim/src/auto/osdef.h @@ -0,0 +1,4 @@ +/* + * Normally generated by osdef.sh to declare prototypes missing from + * system headers. musl declares everything vim uses, so this is empty. + */ diff --git a/userland/apps/vim/src/auto/pathdef.c b/userland/apps/vim/src/auto/pathdef.c new file mode 100644 index 00000000..62dab71d --- /dev/null +++ b/userland/apps/vim/src/auto/pathdef.c @@ -0,0 +1,9 @@ +/* auto/pathdef.c - Stellux Vim path definitions */ +#include "vim.h" + +char_u *default_vim_dir = (char_u *)"/etc/vim"; +char_u *default_vimruntime_dir = (char_u *)""; +char_u *all_cflags = (char_u *)""; +char_u *all_lflags = (char_u *)""; +char_u *compiled_user = (char_u *)"stellux"; +char_u *compiled_sys = (char_u *)"stellux"; diff --git a/userland/apps/vim/src/autocmd.c b/userland/apps/vim/src/autocmd.c new file mode 100644 index 00000000..a78e78b0 --- /dev/null +++ b/userland/apps/vim/src/autocmd.c @@ -0,0 +1,3294 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * autocmd.c: Autocommand related functions + */ + +#include "vim.h" + +/* + * The autocommands are stored in a list for each event. + * Autocommands for the same pattern, that are consecutive, are joined + * together, to avoid having to match the pattern too often. + * The result is an array of Autopat lists, which point to AutoCmd lists: + * + * last_autopat[0] -----------------------------+ + * V + * first_autopat[0] --> Autopat.next --> Autopat.next --> NULL + * Autopat.cmds Autopat.cmds + * | | + * V V + * AutoCmd.next AutoCmd.next + * | | + * V V + * AutoCmd.next NULL + * | + * V + * NULL + * + * last_autopat[1] --------+ + * V + * first_autopat[1] --> Autopat.next --> NULL + * Autopat.cmds + * | + * V + * AutoCmd.next + * | + * V + * NULL + * etc. + * + * The order of AutoCmds is important, this is the order in which they were + * defined and will have to be executed. + */ +typedef struct AutoCmd +{ + char_u *cmd; // The command to be executed (NULL + // when command has been removed). + char once; // "One shot": removed after execution + char nested; // If autocommands nest here. + char last; // last command in list + sctx_T script_ctx; // script context where it is defined + struct AutoCmd *next; // next AutoCmd in list +} AutoCmd; + +typedef struct AutoPat +{ + struct AutoPat *next; // Next AutoPat in AutoPat list; MUST + // be the first entry. + char_u *pat; // pattern as typed (NULL when pattern + // has been removed) + regprog_T *reg_prog; // compiled regprog for pattern + AutoCmd *cmds; // list of commands to do + int group; // group ID + int patlen; // strlen() of pat + int buflocal_nr; // !=0 for buffer-local AutoPat + char allow_dirs; // Pattern may match whole path + char last; // last pattern for apply_autocmds() +} AutoPat; + +static struct event_name +{ + char *name; // event name + event_T event; // event number +} event_names[] = +{ + {"BufAdd", EVENT_BUFADD}, + {"BufCreate", EVENT_BUFADD}, + {"BufDelete", EVENT_BUFDELETE}, + {"BufEnter", EVENT_BUFENTER}, + {"BufFilePost", EVENT_BUFFILEPOST}, + {"BufFilePre", EVENT_BUFFILEPRE}, + {"BufHidden", EVENT_BUFHIDDEN}, + {"BufLeave", EVENT_BUFLEAVE}, + {"BufNew", EVENT_BUFNEW}, + {"BufNewFile", EVENT_BUFNEWFILE}, + {"BufRead", EVENT_BUFREADPOST}, + {"BufReadCmd", EVENT_BUFREADCMD}, + {"BufReadPost", EVENT_BUFREADPOST}, + {"BufReadPre", EVENT_BUFREADPRE}, + {"BufUnload", EVENT_BUFUNLOAD}, + {"BufWinEnter", EVENT_BUFWINENTER}, + {"BufWinLeave", EVENT_BUFWINLEAVE}, + {"BufWipeout", EVENT_BUFWIPEOUT}, + {"BufWrite", EVENT_BUFWRITEPRE}, + {"BufWritePost", EVENT_BUFWRITEPOST}, + {"BufWritePre", EVENT_BUFWRITEPRE}, + {"BufWriteCmd", EVENT_BUFWRITECMD}, + {"CmdlineChanged", EVENT_CMDLINECHANGED}, + {"CmdlineEnter", EVENT_CMDLINEENTER}, + {"CmdlineLeave", EVENT_CMDLINELEAVE}, + {"CmdwinEnter", EVENT_CMDWINENTER}, + {"CmdwinLeave", EVENT_CMDWINLEAVE}, + {"CmdUndefined", EVENT_CMDUNDEFINED}, + {"ColorScheme", EVENT_COLORSCHEME}, + {"ColorSchemePre", EVENT_COLORSCHEMEPRE}, + {"CompleteChanged", EVENT_COMPLETECHANGED}, + {"CompleteDone", EVENT_COMPLETEDONE}, + {"CompleteDonePre", EVENT_COMPLETEDONEPRE}, + {"CursorHold", EVENT_CURSORHOLD}, + {"CursorHoldI", EVENT_CURSORHOLDI}, + {"CursorMoved", EVENT_CURSORMOVED}, + {"CursorMovedI", EVENT_CURSORMOVEDI}, + {"DiffUpdated", EVENT_DIFFUPDATED}, + {"DirChanged", EVENT_DIRCHANGED}, + {"DirChangedPre", EVENT_DIRCHANGEDPRE}, + {"EncodingChanged", EVENT_ENCODINGCHANGED}, + {"ExitPre", EVENT_EXITPRE}, + {"FileEncoding", EVENT_ENCODINGCHANGED}, + {"FileAppendPost", EVENT_FILEAPPENDPOST}, + {"FileAppendPre", EVENT_FILEAPPENDPRE}, + {"FileAppendCmd", EVENT_FILEAPPENDCMD}, + {"FileChangedShell",EVENT_FILECHANGEDSHELL}, + {"FileChangedShellPost",EVENT_FILECHANGEDSHELLPOST}, + {"FileChangedRO", EVENT_FILECHANGEDRO}, + {"FileReadPost", EVENT_FILEREADPOST}, + {"FileReadPre", EVENT_FILEREADPRE}, + {"FileReadCmd", EVENT_FILEREADCMD}, + {"FileType", EVENT_FILETYPE}, + {"FileWritePost", EVENT_FILEWRITEPOST}, + {"FileWritePre", EVENT_FILEWRITEPRE}, + {"FileWriteCmd", EVENT_FILEWRITECMD}, + {"FilterReadPost", EVENT_FILTERREADPOST}, + {"FilterReadPre", EVENT_FILTERREADPRE}, + {"FilterWritePost", EVENT_FILTERWRITEPOST}, + {"FilterWritePre", EVENT_FILTERWRITEPRE}, + {"FocusGained", EVENT_FOCUSGAINED}, + {"FocusLost", EVENT_FOCUSLOST}, + {"FuncUndefined", EVENT_FUNCUNDEFINED}, + {"GUIEnter", EVENT_GUIENTER}, + {"GUIFailed", EVENT_GUIFAILED}, + {"InsertChange", EVENT_INSERTCHANGE}, + {"InsertEnter", EVENT_INSERTENTER}, + {"InsertLeave", EVENT_INSERTLEAVE}, + {"InsertLeavePre", EVENT_INSERTLEAVEPRE}, + {"InsertCharPre", EVENT_INSERTCHARPRE}, + {"MenuPopup", EVENT_MENUPOPUP}, + {"ModeChanged", EVENT_MODECHANGED}, + {"OptionSet", EVENT_OPTIONSET}, + {"QuickFixCmdPost", EVENT_QUICKFIXCMDPOST}, + {"QuickFixCmdPre", EVENT_QUICKFIXCMDPRE}, + {"QuitPre", EVENT_QUITPRE}, + {"RemoteReply", EVENT_REMOTEREPLY}, + {"SafeState", EVENT_SAFESTATE}, + {"SafeStateAgain", EVENT_SAFESTATEAGAIN}, + {"SessionLoadPost", EVENT_SESSIONLOADPOST}, + {"ShellCmdPost", EVENT_SHELLCMDPOST}, + {"ShellFilterPost", EVENT_SHELLFILTERPOST}, + {"SigUSR1", EVENT_SIGUSR1}, + {"SourceCmd", EVENT_SOURCECMD}, + {"SourcePre", EVENT_SOURCEPRE}, + {"SourcePost", EVENT_SOURCEPOST}, + {"SpellFileMissing",EVENT_SPELLFILEMISSING}, + {"StdinReadPost", EVENT_STDINREADPOST}, + {"StdinReadPre", EVENT_STDINREADPRE}, + {"SwapExists", EVENT_SWAPEXISTS}, + {"Syntax", EVENT_SYNTAX}, + {"TabNew", EVENT_TABNEW}, + {"TabClosed", EVENT_TABCLOSED}, + {"TabEnter", EVENT_TABENTER}, + {"TabLeave", EVENT_TABLEAVE}, + {"TermChanged", EVENT_TERMCHANGED}, + {"TerminalOpen", EVENT_TERMINALOPEN}, + {"TerminalWinOpen", EVENT_TERMINALWINOPEN}, + {"TermResponse", EVENT_TERMRESPONSE}, + {"TextChanged", EVENT_TEXTCHANGED}, + {"TextChangedI", EVENT_TEXTCHANGEDI}, + {"TextChangedP", EVENT_TEXTCHANGEDP}, + {"TextChangedT", EVENT_TEXTCHANGEDT}, + {"User", EVENT_USER}, + {"VimEnter", EVENT_VIMENTER}, + {"VimLeave", EVENT_VIMLEAVE}, + {"VimLeavePre", EVENT_VIMLEAVEPRE}, + {"WinNew", EVENT_WINNEW}, + {"WinClosed", EVENT_WINCLOSED}, + {"WinEnter", EVENT_WINENTER}, + {"WinLeave", EVENT_WINLEAVE}, + {"WinResized", EVENT_WINRESIZED}, + {"WinScrolled", EVENT_WINSCROLLED}, + {"VimResized", EVENT_VIMRESIZED}, + {"TextYankPost", EVENT_TEXTYANKPOST}, + {"VimSuspend", EVENT_VIMSUSPEND}, + {"VimResume", EVENT_VIMRESUME}, + {NULL, (event_T)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, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + +static AutoPat *last_autopat[NUM_EVENTS] = +{ + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + +#define AUGROUP_DEFAULT (-1) // default autocmd group +#define AUGROUP_ERROR (-2) // erroneous autocmd group +#define AUGROUP_ALL (-3) // all autocmd groups + +/* + * struct used to keep status while executing autocommands for an event. + */ +struct AutoPatCmd_S +{ + AutoPat *curpat; // next AutoPat to examine + AutoCmd *nextcmd; // next AutoCmd to execute + int group; // group being used + char_u *fname; // fname to match with + char_u *sfname; // sfname to match with + char_u *tail; // tail of fname + event_T event; // current event + sctx_T script_ctx; // script context where it is defined + int arg_bufnr; // Initially equal to , set to zero when + // buf is deleted. + AutoPatCmd_T *next; // chain of active apc-s for auto-invalidation +}; + +static AutoPatCmd_T *active_apc_list = NULL; // stack of active autocommands + +// Macro to loop over all the patterns for an autocmd event +#define FOR_ALL_AUTOCMD_PATTERNS(event, ap) \ + for ((ap) = first_autopat[(int)(event)]; (ap) != NULL; (ap) = (ap)->next) + +/* + * augroups stores a list of autocmd group names. + */ +static garray_T augroups = {0, 0, sizeof(char_u *), 10, NULL}; +#define AUGROUP_NAME(i) (((char_u **)augroups.ga_data)[i]) +// use get_deleted_augroup() to get this +static char_u *deleted_augroup = NULL; + +/* + * The ID of the current group. Group 0 is the default one. + */ +static int current_augroup = AUGROUP_DEFAULT; + +static int au_need_clean = FALSE; // need to delete marked patterns + +static char_u *event_nr2name(event_T event); +static int au_get_grouparg(char_u **argp); +static int do_autocmd_event(event_T event, char_u *pat, int once, int nested, char_u *cmd, int forceit, int group, int flags); +static int apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, int force, int group, buf_T *buf, exarg_T *eap); +static void auto_next_pat(AutoPatCmd_T *apc, int stop_at_last); +static int au_find_group(char_u *name); + +static event_T last_event; +static int last_group; +static int autocmd_blocked = 0; // block all autocmds + + static char_u * +get_deleted_augroup(void) +{ + if (deleted_augroup == NULL) + deleted_augroup = (char_u *)_("--Deleted--"); + return deleted_augroup; +} + +/* + * Show the autocommands for one AutoPat. + */ + static void +show_autocmd(AutoPat *ap, event_T event) +{ + AutoCmd *ac; + + // Check for "got_int" (here and at various places below), which is set + // when "q" has been hit for the "--more--" prompt + if (got_int) + return; + if (ap->pat == NULL) // pattern has been removed + return; + + // Make sure no info referenced by "ap" is cleared, e.g. when a timer + // clears an augroup. Jump to "theend" after this! + // "ap->pat" may be cleared anyway. + ++autocmd_busy; + + msg_putchar('\n'); + if (got_int) + goto theend; + if (event != last_event || ap->group != last_group) + { + if (ap->group != AUGROUP_DEFAULT) + { + if (AUGROUP_NAME(ap->group) == NULL) + msg_puts_attr((char *)get_deleted_augroup(), HL_ATTR(HLF_E)); + else + msg_puts_attr((char *)AUGROUP_NAME(ap->group), HL_ATTR(HLF_T)); + msg_puts(" "); + } + msg_puts_attr((char *)event_nr2name(event), HL_ATTR(HLF_T)); + last_event = event; + last_group = ap->group; + msg_putchar('\n'); + if (got_int) + goto theend; + } + + if (ap->pat == NULL) + goto theend; // timer might have cleared the pattern or group + + msg_col = 4; + msg_outtrans(ap->pat); + + for (ac = ap->cmds; ac != NULL; ac = ac->next) + { + if (ac->cmd == NULL) // skip removed commands + continue; + + if (msg_col >= 14) + msg_putchar('\n'); + msg_col = 14; + if (got_int) + goto theend; + msg_outtrans(ac->cmd); +#ifdef FEAT_EVAL + if (p_verbose > 0) + last_set_msg(ac->script_ctx); +#endif + if (got_int) + goto theend; + if (ac->next != NULL) + { + msg_putchar('\n'); + if (got_int) + goto theend; + } + } + +theend: + --autocmd_busy; +} + +/* + * Mark an autocommand pattern for deletion. + */ + static void +au_remove_pat(AutoPat *ap) +{ + VIM_CLEAR(ap->pat); + ap->buflocal_nr = -1; + au_need_clean = TRUE; +} + +/* + * Mark all commands for a pattern for deletion. + */ + static void +au_remove_cmds(AutoPat *ap) +{ + AutoCmd *ac; + + for (ac = ap->cmds; ac != NULL; ac = ac->next) + VIM_CLEAR(ac->cmd); + au_need_clean = TRUE; +} + +// Delete one command from an autocmd pattern. +static void au_del_cmd(AutoCmd *ac) +{ + VIM_CLEAR(ac->cmd); + au_need_clean = TRUE; +} + +/* + * Cleanup autocommands and patterns that have been deleted. + * This is only done when not executing autocommands. + */ + static void +au_cleanup(void) +{ + AutoPat *ap, **prev_ap; + AutoCmd *ac, **prev_ac; + event_T event; + + if (autocmd_busy || !au_need_clean) + return; + + // loop over all events + for (event = (event_T)0; (int)event < NUM_EVENTS; + event = (event_T)((int)event + 1)) + { + // loop over all autocommand patterns + prev_ap = &(first_autopat[(int)event]); + for (ap = *prev_ap; ap != NULL; ap = *prev_ap) + { + int has_cmd = FALSE; + + // loop over all commands for this pattern + prev_ac = &(ap->cmds); + for (ac = *prev_ac; ac != NULL; ac = *prev_ac) + { + // remove the command if the pattern is to be deleted or when + // the command has been marked for deletion + if (ap->pat == NULL || ac->cmd == NULL) + { + *prev_ac = ac->next; + vim_free(ac->cmd); + vim_free(ac); + } + else + { + has_cmd = TRUE; + prev_ac = &(ac->next); + } + } + + if (ap->pat != NULL && !has_cmd) + // Pattern was not marked for deletion, but all of its + // commands were. So mark the pattern for deletion. + au_remove_pat(ap); + + // remove the pattern if it has been marked for deletion + if (ap->pat == NULL) + { + if (ap->next == NULL) + { + if (prev_ap == &(first_autopat[(int)event])) + last_autopat[(int)event] = NULL; + else + // this depends on the "next" field being the first in + // the struct + last_autopat[(int)event] = (AutoPat *)prev_ap; + } + *prev_ap = ap->next; + vim_regfree(ap->reg_prog); + vim_free(ap); + } + else + prev_ap = &(ap->next); + } + } + + au_need_clean = FALSE; +} + +/* + * Called when buffer is freed, to remove/invalidate related buffer-local + * autocmds. + */ + void +aubuflocal_remove(buf_T *buf) +{ + AutoPat *ap; + event_T event; + AutoPatCmd_T *apc; + + // invalidate currently executing autocommands + for (apc = active_apc_list; apc; apc = apc->next) + if (buf->b_fnum == apc->arg_bufnr) + apc->arg_bufnr = 0; + + // invalidate buflocals looping through events + for (event = (event_T)0; (int)event < NUM_EVENTS; + event = (event_T)((int)event + 1)) + // loop over all autocommand patterns + FOR_ALL_AUTOCMD_PATTERNS(event, ap) + if (ap->buflocal_nr == buf->b_fnum) + { + au_remove_pat(ap); + if (p_verbose >= 6) + { + verbose_enter(); + smsg(_("auto-removing autocommand: %s "), + event_nr2name(event), buf->b_fnum); + verbose_leave(); + } + } + au_cleanup(); +} + +/* + * Add an autocmd group name. + * Return its ID. Returns AUGROUP_ERROR (< 0) for error. + */ + static int +au_new_group(char_u *name) +{ + int i; + + i = au_find_group(name); + if (i != AUGROUP_ERROR) + return i; + + // the group doesn't exist yet, add it. First try using a free entry. + for (i = 0; i < augroups.ga_len; ++i) + if (AUGROUP_NAME(i) == NULL) + break; + if (i == augroups.ga_len && ga_grow(&augroups, 1) == FAIL) + return AUGROUP_ERROR; + + AUGROUP_NAME(i) = vim_strsave(name); + if (AUGROUP_NAME(i) == NULL) + return AUGROUP_ERROR; + if (i == augroups.ga_len) + ++augroups.ga_len; + + return i; +} + + static void +au_del_group(char_u *name) +{ + int i; + event_T event; + AutoPat *ap; + int in_use = FALSE; + + + i = au_find_group(name); + if (i == AUGROUP_ERROR) // the group doesn't exist + { + semsg(_(e_no_such_group_str), name); + return; + } + if (i == current_augroup) + { + emsg(_(e_cannot_delete_current_group)); + return; + } + + for (event = (event_T)0; (int)event < NUM_EVENTS; + event = (event_T)((int)event + 1)) + { + FOR_ALL_AUTOCMD_PATTERNS(event, ap) + if (ap->group == i && ap->pat != NULL) + { + give_warning((char_u *)_("W19: Deleting augroup that is still in use"), TRUE); + in_use = TRUE; + event = NUM_EVENTS; + break; + } + } + vim_free(AUGROUP_NAME(i)); + if (in_use) + AUGROUP_NAME(i) = get_deleted_augroup(); + else + AUGROUP_NAME(i) = NULL; +} + +/* + * Find the ID of an autocmd group name. + * Return its ID. Returns AUGROUP_ERROR (< 0) for error. + */ + static int +au_find_group(char_u *name) +{ + int i; + + for (i = 0; i < augroups.ga_len; ++i) + if (AUGROUP_NAME(i) != NULL && AUGROUP_NAME(i) != get_deleted_augroup() + && STRCMP(AUGROUP_NAME(i), name) == 0) + return i; + return AUGROUP_ERROR; +} + +/* + * Return TRUE if augroup "name" exists. + */ + int +au_has_group(char_u *name) +{ + return au_find_group(name) != AUGROUP_ERROR; +} + +/* + * ":augroup {name}". + */ + void +do_augroup(char_u *arg, int del_group) +{ + int i; + + if (del_group) + { + if (*arg == NUL) + emsg(_(e_argument_required)); + else + au_del_group(arg); + } + else if (STRICMP(arg, "end") == 0) // ":aug end": back to group 0 + current_augroup = AUGROUP_DEFAULT; + else if (*arg) // ":aug xxx": switch to group xxx + { + i = au_new_group(arg); + if (i != AUGROUP_ERROR) + current_augroup = i; + } + else // ":aug": list the group names + { + msg_start(); + for (i = 0; i < augroups.ga_len; ++i) + { + if (AUGROUP_NAME(i) != NULL) + { + msg_puts((char *)AUGROUP_NAME(i)); + msg_puts(" "); + } + } + msg_clr_eos(); + msg_end(); + } +} + + void +autocmd_init(void) +{ + CLEAR_FIELD(aucmd_win); +} + +#if defined(EXITFREE) || defined(PROTO) + void +free_all_autocmds(void) +{ + char_u *s; + + for (current_augroup = -1; current_augroup < augroups.ga_len; + ++current_augroup) + do_autocmd(NULL, (char_u *)"", TRUE); + + for (int i = 0; i < augroups.ga_len; ++i) + { + s = ((char_u **)(augroups.ga_data))[i]; + if (s != get_deleted_augroup()) + vim_free(s); + } + ga_clear(&augroups); + + // aucmd_win[] is freed in win_free_all() +} +#endif + +/* + * Return TRUE if "win" is an active entry in aucmd_win[]. + */ + int +is_aucmd_win(win_T *win) +{ + for (int i = 0; i < AUCMD_WIN_COUNT; ++i) + if (aucmd_win[i].auc_win_used && aucmd_win[i].auc_win == win) + return TRUE; + return FALSE; +} + +/* + * Return the event number for event name "start". + * Return NUM_EVENTS if the event name was not found. + * Return a pointer to the next event name in "end". + */ + static event_T +event_name2nr(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 != '|'; ++p) + ; + for (i = 0; event_names[i].name != NULL; ++i) + { + len = (int)STRLEN(event_names[i].name); + if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0) + break; + } + if (*p == ',') + ++p; + *end = p; + if (event_names[i].name == NULL) + return NUM_EVENTS; + return event_names[i].event; +} + +/* + * Return the name for event "event". + */ + static char_u * +event_nr2name(event_T event) +{ + int i; + + for (i = 0; event_names[i].name != NULL; ++i) + if (event_names[i].event == event) + return (char_u *)event_names[i].name; + return (char_u *)"Unknown"; +} + +/* + * Scan over the events. "*" stands for all events. + */ + static char_u * +find_end_event( + char_u *arg, + int have_group) // TRUE when group name was found +{ + char_u *pat; + char_u *p; + + if (*arg == '*') + { + if (arg[1] && !VIM_ISWHITE(arg[1])) + { + semsg(_(e_illegal_character_after_star_str), arg); + return NULL; + } + pat = arg + 1; + } + else + { + for (pat = arg; *pat && *pat != '|' && !VIM_ISWHITE(*pat); pat = p) + { + if ((int)event_name2nr(pat, &p) >= NUM_EVENTS) + { + if (have_group) + semsg(_(e_no_such_event_str), pat); + else + semsg(_(e_no_such_group_or_event_str), pat); + return NULL; + } + } + } + return pat; +} + +/* + * Return TRUE if "event" is included in 'eventignore'. + */ + static int +event_ignored(event_T event) +{ + char_u *p = p_ei; + + while (*p != NUL) + { + if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) + return TRUE; + if (event_name2nr(p, &p) == event) + return TRUE; + } + + return FALSE; +} + +/* + * Return OK when the contents of p_ei is valid, FAIL otherwise. + */ + int +check_ei(void) +{ + char_u *p = p_ei; + + while (*p) + { + if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) + { + p += 3; + if (*p == ',') + ++p; + } + else if (event_name2nr(p, &p) == NUM_EVENTS) + return FAIL; + } + + return OK; +} + +# if defined(FEAT_SYN_HL) || defined(PROTO) + +/* + * Add "what" to 'eventignore' to skip loading syntax highlighting for every + * buffer loaded into the window. "what" must start with a comma. + * Returns the old value of 'eventignore' in allocated memory. + */ + char_u * +au_event_disable(char *what) +{ + char_u *new_ei; + char_u *save_ei; + + save_ei = vim_strsave(p_ei); + if (save_ei == NULL) + return NULL; + + new_ei = vim_strnsave(p_ei, STRLEN(p_ei) + STRLEN(what)); + if (new_ei == NULL) + { + vim_free(save_ei); + return NULL; + } + + if (*what == ',' && *p_ei == NUL) + STRCPY(new_ei, what + 1); + else + STRCAT(new_ei, what); + set_string_option_direct((char_u *)"ei", -1, new_ei, + OPT_FREE, SID_NONE); + vim_free(new_ei); + return save_ei; +} + + void +au_event_restore(char_u *old_ei) +{ + if (old_ei != NULL) + { + set_string_option_direct((char_u *)"ei", -1, old_ei, + OPT_FREE, SID_NONE); + vim_free(old_ei); + } +} +# endif // FEAT_SYN_HL + +/* + * 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 , in + * the current group. + * :autocmd Show the autocommands associated with + * and . + * :autocmd Show the autocommands associated with + * . + * :autocmd Show all autocommands. + * :autocmd! Remove all autocommands associated with + * and , and add the command + * , for the current group. + * :autocmd! Remove all autocommands associated with + * and for the current group. + * :autocmd! Remove all autocommands associated with + * for the current group. + * :autocmd! Remove ALL autocommands for the current + * group. + * + * 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. + * + * Mostly a {group} argument can optionally appear before . + * "eap" can be NULL. + */ + void +do_autocmd(exarg_T *eap, char_u *arg_in, int forceit) +{ + char_u *arg = arg_in; + char_u *pat; + char_u *envpat = NULL; + char_u *cmd; + int cmd_need_free = FALSE; + event_T event; + char_u *tofree = NULL; + int nested = FALSE; + int once = FALSE; + int group; + int i; + int flags = 0; + + if (*arg == '|') + { + eap->nextcmd = arg + 1; + arg = (char_u *)""; + group = AUGROUP_ALL; // no argument, use all groups + } + else + { + /* + * Check for a legal group name. If not, use AUGROUP_ALL. + */ + group = au_get_grouparg(&arg); + if (arg == NULL) // out of memory + return; + } + + /* + * Scan over the events. + * If we find an illegal name, return here, don't do anything. + */ + pat = find_end_event(arg, group != AUGROUP_ALL); + if (pat == NULL) + return; + + pat = skipwhite(pat); + if (*pat == '|') + { + eap->nextcmd = pat + 1; + pat = (char_u *)""; + cmd = (char_u *)""; + } + else + { + /* + * Scan over the pattern. Put a NUL at the end. + */ + cmd = pat; + while (*cmd && (!VIM_ISWHITE(*cmd) || cmd[-1] == '\\')) + cmd++; + if (*cmd) + *cmd++ = NUL; + + // Expand environment variables in the pattern. Set 'shellslash', we + // want forward slashes here. + if (vim_strchr(pat, '$') != NULL || vim_strchr(pat, '~') != NULL) + { +#ifdef BACKSLASH_IN_FILENAME + int p_ssl_save = p_ssl; + + p_ssl = TRUE; +#endif + envpat = expand_env_save(pat); +#ifdef BACKSLASH_IN_FILENAME + p_ssl = p_ssl_save; +#endif + if (envpat != NULL) + pat = envpat; + } + + cmd = skipwhite(cmd); + for (i = 0; i < 2; i++) + { + if (*cmd == NUL) + continue; + + // Check for "++once" flag. + if (STRNCMP(cmd, "++once", 6) == 0 && VIM_ISWHITE(cmd[6])) + { + if (once) + semsg(_(e_duplicate_argument_str), "++once"); + once = TRUE; + cmd = skipwhite(cmd + 6); + } + + // Check for "++nested" flag. + if ((STRNCMP(cmd, "++nested", 8) == 0 && VIM_ISWHITE(cmd[8]))) + { + if (nested) + { + semsg(_(e_duplicate_argument_str), "++nested"); + return; + } + nested = TRUE; + cmd = skipwhite(cmd + 8); + } + + // Check for the old "nested" flag in legacy script. + if (STRNCMP(cmd, "nested", 6) == 0 && VIM_ISWHITE(cmd[6])) + { + if (in_vim9script()) + { + // If there ever is a :nested command this error should + // be removed and "nested" accepted as the start of the + // command. + emsg(_(e_invalid_command_nested_did_you_mean_plusplus_nested)); + return; + } + if (nested) + { + semsg(_(e_duplicate_argument_str), "nested"); + return; + } + nested = TRUE; + cmd = skipwhite(cmd + 6); + } + } + + /* + * Find the start of the commands. + * Expand in it. + */ + if (*cmd != NUL) + { + if (eap != NULL) + // Read a {} block if it follows. + cmd = may_get_cmd_block(eap, cmd, &tofree, &flags); + + cmd = expand_sfile(cmd); + if (cmd == NULL) // some error + return; + cmd_need_free = TRUE; + } + } + + /* + * Print header when showing autocommands. + */ + if (!forceit && *cmd == NUL) + // Highlight title + msg_puts_title(_("\n--- Autocommands ---")); + + /* + * Loop over the events. + */ + last_event = (event_T)-1; // for listing the event name + last_group = AUGROUP_ERROR; // for listing the group name + if (*arg == '*' || *arg == NUL || *arg == '|') + { + if (*cmd != NUL) + emsg(_(e_cannot_define_autocommands_for_all_events)); + else + for (event = (event_T)0; (int)event < NUM_EVENTS; + event = (event_T)((int)event + 1)) + if (do_autocmd_event(event, pat, + once, nested, cmd, forceit, group, flags) == FAIL) + break; + } + else + { + while (*arg && *arg != '|' && !VIM_ISWHITE(*arg)) + if (do_autocmd_event(event_name2nr(arg, &arg), pat, + once, nested, cmd, forceit, group, flags) == FAIL) + break; + } + + if (cmd_need_free) + vim_free(cmd); + vim_free(tofree); + vim_free(envpat); +} + +/* + * Find the group ID in a ":autocmd" or ":doautocmd" argument. + * The "argp" argument is advanced to the following argument. + * + * Returns the group ID, AUGROUP_ERROR for error (out of memory). + */ + static int +au_get_grouparg(char_u **argp) +{ + char_u *group_name; + char_u *p; + char_u *arg = *argp; + int group = AUGROUP_ALL; + + for (p = arg; *p && !VIM_ISWHITE(*p) && *p != '|'; ++p) + ; + if (p <= arg) + return AUGROUP_ALL; + + group_name = vim_strnsave(arg, p - arg); + if (group_name == NULL) // out of memory + return AUGROUP_ERROR; + group = au_find_group(group_name); + if (group == AUGROUP_ERROR) + group = AUGROUP_ALL; // no match, use all groups + else + *argp = skipwhite(p); // match, skip over group name + vim_free(group_name); + return group; +} + +/* + * do_autocmd() for one event. + * If *pat == NUL do for all patterns. + * If *cmd == NUL show entries. + * If forceit == TRUE delete entries. + * If group is not AUGROUP_ALL, only use this group. + */ + static int +do_autocmd_event( + event_T event, + char_u *pat, + int once, + int nested, + char_u *cmd, + int forceit, + int group, + int flags) +{ + AutoPat *ap; + AutoPat **prev_ap; + AutoCmd *ac; + AutoCmd **prev_ac; + int brace_level; + char_u *endpat; + int findgroup; + int allgroups; + int patlen; + int is_buflocal; + int buflocal_nr; + char_u buflocal_pat[25]; // for "" + + if (group == AUGROUP_ALL) + findgroup = current_augroup; + else + findgroup = group; + allgroups = (group == AUGROUP_ALL && !forceit && *cmd == NUL); + + /* + * Show or delete all patterns for an event. + */ + if (*pat == NUL) + { + FOR_ALL_AUTOCMD_PATTERNS(event, ap) + { + if (forceit) // delete the AutoPat, if it's in the current group + { + if (ap->group == findgroup) + au_remove_pat(ap); + } + else if (group == AUGROUP_ALL || ap->group == group) + show_autocmd(ap, event); + } + } + + /* + * 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\}". + */ + brace_level = 0; + for (endpat = pat; *endpat && (*endpat != ',' || brace_level + || (endpat > pat && endpat[-1] == '\\')); ++endpat) + { + if (*endpat == '{') + brace_level++; + else if (*endpat == '}') + brace_level--; + } + if (pat == endpat) // ignore single comma + continue; + patlen = (int)(endpat - pat); + + /* + * detect special buffer-local patterns + */ + is_buflocal = FALSE; + buflocal_nr = 0; + + if (patlen >= 8 && STRNCMP(pat, "') + { + // "": Error will be printed only for addition. + // printing and removing will proceed silently. + is_buflocal = TRUE; + if (patlen == 8) + // "" + buflocal_nr = curbuf->b_fnum; + else if (patlen > 9 && pat[7] == '=') + { + if (patlen == 13 && STRNICMP(pat, "", 13) == 0) + // "" + buflocal_nr = autocmd_bufnr; + else if (skipdigits(pat + 8) == pat + patlen - 1) + // "" + buflocal_nr = atoi((char *)pat + 8); + } + } + + if (is_buflocal) + { + // normalize pat into standard "#N" form + sprintf((char *)buflocal_pat, "", buflocal_nr); + pat = buflocal_pat; // can modify pat and patlen + patlen = (int)STRLEN(buflocal_pat); // but not endpat + } + + /* + * Find AutoPat entries with this pattern. When adding a command it + * always goes at or after the last one, so start at the end. + */ + if (!forceit && *cmd != NUL && last_autopat[(int)event] != NULL) + prev_ap = &last_autopat[(int)event]; + else + prev_ap = &first_autopat[(int)event]; + while ((ap = *prev_ap) != NULL) + { + if (ap->pat != NULL) + { + /* + * Accept a pattern when: + * - a group was specified and it's that group, or a group was + * not specified and it's the current group, or a group was + * not specified and we are listing + * - the length of the pattern matches + * - the pattern matches. + * For , this condition works because we normalize + * all buffer-local patterns. + */ + if ((allgroups || ap->group == findgroup) + && ap->patlen == patlen + && STRNCMP(pat, ap->pat, patlen) == 0) + { + /* + * Remove existing autocommands. + * If adding any new autocmd's for this AutoPat, don't + * delete the pattern from the autopat list, append to + * this list. + */ + if (forceit) + { + if (*cmd != NUL && ap->next == NULL) + { + au_remove_cmds(ap); + break; + } + au_remove_pat(ap); + } + + /* + * Show autocmd's for this autopat, or buflocals + */ + else if (*cmd == NUL) + show_autocmd(ap, event); + + /* + * Add autocmd to this autopat, if it's the last one. + */ + else if (ap->next == NULL) + break; + } + } + prev_ap = &ap->next; + } + + /* + * Add a new command. + */ + if (*cmd != NUL) + { + /* + * If the pattern we want to add a command to does appear at the + * end of the list (or not is not in the list at all), add the + * pattern at the end of the list. + */ + if (ap == NULL) + { + // refuse to add buffer-local ap if buffer number is invalid + if (is_buflocal && (buflocal_nr == 0 + || buflist_findnr(buflocal_nr) == NULL)) + { + semsg(_(e_buffer_nr_invalid_buffer_number), buflocal_nr); + return FAIL; + } + + ap = ALLOC_ONE(AutoPat); + if (ap == NULL) + return FAIL; + ap->pat = vim_strnsave(pat, patlen); + ap->patlen = patlen; + if (ap->pat == NULL) + { + vim_free(ap); + return FAIL; + } + +#ifdef FEAT_EVAL + // need to initialize last_mode for the first ModeChanged + // autocmd + if (event == EVENT_MODECHANGED && !has_modechanged()) + get_mode(last_mode); +#endif + // Initialize the fields checked by the WinScrolled and + // WinResized trigger to prevent them from firing right after + // the first autocmd is defined. + if ((event == EVENT_WINSCROLLED || event == EVENT_WINRESIZED) + && !(has_winscrolled() || has_winresized())) + { + tabpage_T *save_curtab = curtab; + tabpage_T *tp; + FOR_ALL_TABPAGES(tp) + { + unuse_tabpage(curtab); + use_tabpage(tp); + snapshot_windows_scroll_size(); + } + unuse_tabpage(curtab); + use_tabpage(save_curtab); + } + + if (is_buflocal) + { + ap->buflocal_nr = buflocal_nr; + ap->reg_prog = NULL; + } + else + { + char_u *reg_pat; + + ap->buflocal_nr = 0; + reg_pat = file_pat_to_reg_pat(pat, endpat, + &ap->allow_dirs, TRUE); + if (reg_pat != NULL) + ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC); + vim_free(reg_pat); + if (reg_pat == NULL || ap->reg_prog == NULL) + { + vim_free(ap->pat); + vim_free(ap); + return FAIL; + } + } + ap->cmds = NULL; + *prev_ap = ap; + last_autopat[(int)event] = ap; + ap->next = NULL; + if (group == AUGROUP_ALL) + ap->group = current_augroup; + else + ap->group = group; + } + + /* + * Add the autocmd at the end of the AutoCmd list. + */ + prev_ac = &(ap->cmds); + while ((ac = *prev_ac) != NULL) + prev_ac = &ac->next; + ac = ALLOC_ONE(AutoCmd); + if (ac == NULL) + return FAIL; + ac->cmd = vim_strsave(cmd); + ac->script_ctx = current_sctx; + if (flags & UC_VIM9) + ac->script_ctx.sc_version = SCRIPT_VERSION_VIM9; +#ifdef FEAT_EVAL + ac->script_ctx.sc_lnum += SOURCING_LNUM; +#endif + if (ac->cmd == NULL) + { + vim_free(ac); + return FAIL; + } + ac->next = NULL; + *prev_ac = ac; + ac->once = once; + ac->nested = nested; + } + } + + au_cleanup(); // may really delete removed patterns/commands now + return OK; +} + +/* + * Implementation of ":doautocmd [group] event [fname]". + * Return OK for success, FAIL for failure; + */ + int +do_doautocmd( + char_u *arg_start, + int do_msg, // give message for no matching autocmds? + int *did_something) +{ + char_u *arg = arg_start; + char_u *fname; + int nothing_done = TRUE; + int group; + + if (did_something != NULL) + *did_something = FALSE; + + /* + * Check for a legal group name. If not, use AUGROUP_ALL. + */ + group = au_get_grouparg(&arg); + if (arg == NULL) // out of memory + return FAIL; + + if (*arg == '*') + { + emsg(_(e_cant_execute_autocommands_for_all_events)); + return FAIL; + } + + /* + * Scan over the events. + * If we find an illegal name, return here, don't do anything. + */ + fname = find_end_event(arg, group != AUGROUP_ALL); + if (fname == NULL) + return FAIL; + + fname = skipwhite(fname); + + /* + * Loop over the events. + */ + while (*arg && !ends_excmd(*arg) && !VIM_ISWHITE(*arg)) + if (apply_autocmds_group(event_name2nr(arg, &arg), + fname, NULL, TRUE, group, curbuf, NULL)) + nothing_done = FALSE; + + if (nothing_done && do_msg +#ifdef FEAT_EVAL + && !aborting() +#endif + ) + smsg(_("No matching autocommands: %s"), arg_start); + if (did_something != NULL) + *did_something = !nothing_done; + +#ifdef FEAT_EVAL + return aborting() ? FAIL : OK; +#else + return OK; +#endif +} + +/* + * ":doautoall": execute autocommands for each loaded buffer. + */ + void +ex_doautoall(exarg_T *eap) +{ + int retval = OK; + aco_save_T aco; + buf_T *buf; + bufref_T bufref; + char_u *arg = eap->arg; + int call_do_modelines = check_nomodeline(&arg); + int did_aucmd; + + /* + * This is a bit tricky: For some commands curwin->w_buffer needs to be + * equal to curbuf, but for some buffers there may not be a window. + * So we change the buffer for the current window for a moment. This + * gives problems when the autocommands make changes to the list of + * buffers or windows... + */ + FOR_ALL_BUFFERS(buf) + { + // Only do loaded buffers and skip the current buffer, it's done last. + if (buf->b_ml.ml_mfp == NULL || buf == curbuf) + continue; + + // Find a window for this buffer and save some values. + aucmd_prepbuf(&aco, buf); + if (curbuf != buf) + { + // Failed to find a window for this buffer. Better not execute + // autocommands then. + retval = FAIL; + break; + } + + set_bufref(&bufref, buf); + + // execute the autocommands for this buffer + retval = do_doautocmd(arg, FALSE, &did_aucmd); + + if (call_do_modelines && did_aucmd) + // Execute the modeline settings, but don't set window-local + // options if we are using the current window for another + // buffer. + do_modelines(is_aucmd_win(curwin) ? OPT_NOWIN : 0); + + // restore the current window + aucmd_restbuf(&aco); + + // stop if there is some error or buffer was deleted + if (retval == FAIL || !bufref_valid(&bufref)) + { + retval = FAIL; + break; + } + } + + // Execute autocommands for the current buffer last. + if (retval == OK) + { + do_doautocmd(arg, FALSE, &did_aucmd); + if (call_do_modelines && did_aucmd) + do_modelines(0); + } +} + +/* + * Check *argp for . When it is present return FALSE, otherwise + * return TRUE and advance *argp to after it. + * Thus return TRUE when do_modelines() should be called. + */ + int +check_nomodeline(char_u **argp) +{ + if (STRNCMP(*argp, "", 12) == 0) + { + *argp = skipwhite(*argp + 12); + return FALSE; + } + return TRUE; +} + +/* + * Prepare for executing autocommands for (hidden) buffer "buf". + * Search for a visible window containing the current buffer. If there isn't + * one then use an entry in "aucmd_win[]". + * Set "curbuf" and "curwin" to match "buf". + * When this fails "curbuf" is not equal "buf". + */ + void +aucmd_prepbuf( + aco_save_T *aco, // structure to save values in + buf_T *buf) // new curbuf +{ + win_T *win; + int save_ea; +#ifdef FEAT_AUTOCHDIR + int save_acd; +#endif + + // Find a window that is for the new buffer + if (buf == curbuf) // be quick when buf is curbuf + win = curwin; + else + FOR_ALL_WINDOWS(win) + if (win->w_buffer == buf) + break; + + // Allocate a window when needed. + win_T *auc_win = NULL; + int auc_idx = AUCMD_WIN_COUNT; + if (win == NULL) + { + for (auc_idx = 0; auc_idx < AUCMD_WIN_COUNT; ++auc_idx) + if (!aucmd_win[auc_idx].auc_win_used) + { + if (aucmd_win[auc_idx].auc_win == NULL) + aucmd_win[auc_idx].auc_win = win_alloc_popup_win(); + auc_win = aucmd_win[auc_idx].auc_win; + if (auc_win != NULL) + aucmd_win[auc_idx].auc_win_used = TRUE; + break; + } + + // If this fails (out of memory or using all AUCMD_WIN_COUNT + // entries) then we can't reliable execute the autocmd, return with + // "curbuf" unequal "buf". + if (auc_win == NULL) + return; + } + + aco->save_curwin_id = curwin->w_id; + aco->save_curbuf = curbuf; + aco->save_prevwin_id = prevwin == NULL ? 0 : prevwin->w_id; + aco->save_State = State; + + if (win != NULL) + { + // There is a window for "buf" in the current tab page, make it the + // curwin. This is preferred, it has the least side effects (esp. if + // "buf" is curbuf). + aco->use_aucmd_win_idx = -1; + curwin = win; + } + else + { + // There is no window for "buf", use "auc_win". To minimize the side + // effects, insert it in the current tab page. + // Anything related to a window (e.g., setting folds) may have + // unexpected results. + aco->use_aucmd_win_idx = auc_idx; + + win_init_popup_win(auc_win, buf); + + aco->globaldir = globaldir; + globaldir = NULL; + + // Split the current window, put the auc_win in the upper half. + // We don't want the BufEnter or WinEnter autocommands. + block_autocmds(); + make_snapshot(SNAP_AUCMD_IDX); + save_ea = p_ea; + p_ea = FALSE; + +#ifdef FEAT_AUTOCHDIR + // Prevent chdir() call in win_enter_ext(), through do_autochdir(). + save_acd = p_acd; + p_acd = FALSE; +#endif + + (void)win_split_ins(0, WSP_TOP, auc_win, 0); + (void)win_comp_pos(); // recompute window positions + p_ea = save_ea; +#ifdef FEAT_AUTOCHDIR + p_acd = save_acd; +#endif + unblock_autocmds(); + curwin = auc_win; + } + curbuf = buf; + aco->new_curwin_id = curwin->w_id; + set_bufref(&aco->new_curbuf, curbuf); + + // disable the Visual area, the position may be invalid in another buffer + aco->save_VIsual_active = VIsual_active; + VIsual_active = FALSE; +} + +/* + * Cleanup after executing autocommands for a (hidden) buffer. + * Restore the window as it was (if possible). + */ + void +aucmd_restbuf( + aco_save_T *aco) // structure holding saved values +{ + int dummy; + win_T *save_curwin; + + if (aco->use_aucmd_win_idx >= 0) + { + win_T *awp = aucmd_win[aco->use_aucmd_win_idx].auc_win; + + // Find "awp", it can't be closed, but it may be in another tab + // page. Do not trigger autocommands here. + block_autocmds(); + if (curwin != awp) + { + tabpage_T *tp; + win_T *wp; + + FOR_ALL_TAB_WINDOWS(tp, wp) + { + if (wp == awp) + { + if (tp != curtab) + goto_tabpage_tp(tp, TRUE, TRUE); + win_goto(awp); + goto win_found; + } + } + } +win_found: + --curbuf->b_nwindows; +#ifdef FEAT_JOB_CHANNEL + int save_stop_insert_mode = stop_insert_mode; + // May need to stop Insert mode if we were in a prompt buffer. + leaving_window(curwin); + // Do not stop Insert mode when already in Insert mode before. + if (aco->save_State & MODE_INSERT) + stop_insert_mode = save_stop_insert_mode; +#endif + // Remove the window and frame from the tree of frames. + (void)winframe_remove(curwin, &dummy, NULL); + win_remove(curwin, NULL); + + // The window is marked as not used, but it is not freed, it can be + // used again. + aucmd_win[aco->use_aucmd_win_idx].auc_win_used = FALSE; + last_status(FALSE); // may need to remove last status line + + if (!valid_tabpage_win(curtab)) + // no valid window in current tabpage + close_tabpage(curtab); + + restore_snapshot(SNAP_AUCMD_IDX, FALSE); + (void)win_comp_pos(); // recompute window positions + unblock_autocmds(); + + save_curwin = win_find_by_id(aco->save_curwin_id); + if (save_curwin != NULL) + curwin = save_curwin; + else + // Hmm, original window disappeared. Just use the first one. + curwin = firstwin; + curbuf = curwin->w_buffer; +#ifdef FEAT_JOB_CHANNEL + // May need to restore insert mode for a prompt buffer. + entering_window(curwin); +#endif + prevwin = win_find_by_id(aco->save_prevwin_id); +#ifdef FEAT_EVAL + vars_clear(&awp->w_vars->dv_hashtab); // free all w: variables + hash_init(&awp->w_vars->dv_hashtab); // re-use the hashtab +#endif + vim_free(globaldir); + globaldir = aco->globaldir; + + // the buffer contents may have changed + VIsual_active = aco->save_VIsual_active; + check_cursor(); + if (curwin->w_topline > curbuf->b_ml.ml_line_count) + { + curwin->w_topline = curbuf->b_ml.ml_line_count; +#ifdef FEAT_DIFF + curwin->w_topfill = 0; +#endif + } +#if defined(FEAT_GUI) + if (gui.in_use) + { + // Hide the scrollbars from the "awp" and update. + gui_mch_enable_scrollbar(&awp->w_scrollbars[SBAR_LEFT], FALSE); + gui_mch_enable_scrollbar(&awp->w_scrollbars[SBAR_RIGHT], FALSE); + gui_may_update_scrollbars(); + } +#endif + } + else + { + // Restore curwin. Use the window ID, a window may have been closed + // and the memory re-used for another one. + save_curwin = win_find_by_id(aco->save_curwin_id); + if (save_curwin != NULL) + { + // Restore the buffer which was previously edited by curwin, if + // it was changed, we are still the same window and the buffer is + // valid. + if (curwin->w_id == aco->new_curwin_id + && curbuf != aco->new_curbuf.br_buf + && bufref_valid(&aco->new_curbuf) + && aco->new_curbuf.br_buf->b_ml.ml_mfp != NULL) + { +# if defined(FEAT_SYN_HL) || defined(FEAT_SPELL) + if (curwin->w_s == &curbuf->b_s) + curwin->w_s = &aco->new_curbuf.br_buf->b_s; +# endif + --curbuf->b_nwindows; + curbuf = aco->new_curbuf.br_buf; + curwin->w_buffer = curbuf; + ++curbuf->b_nwindows; + } + + curwin = save_curwin; + curbuf = curwin->w_buffer; + prevwin = win_find_by_id(aco->save_prevwin_id); + + // In case the autocommand moves the cursor to a position that + // does not exist in curbuf. + VIsual_active = aco->save_VIsual_active; + check_cursor(); + } + } + + VIsual_active = aco->save_VIsual_active; + check_cursor(); // just in case lines got deleted + if (VIsual_active) + check_pos(curbuf, &VIsual); +} + +static int autocmd_nested = FALSE; + +/* + * Execute autocommands for "event" and file name "fname". + * Return TRUE if some commands were executed. + */ + int +apply_autocmds( + event_T event, + char_u *fname, // NULL or empty means use actual file name + char_u *fname_io, // fname to use for on cmdline + int force, // when TRUE, ignore autocmd_busy + buf_T *buf) // buffer for +{ + return apply_autocmds_group(event, fname, fname_io, force, + AUGROUP_ALL, buf, NULL); +} + +/* + * Like apply_autocmds(), but with extra "eap" argument. This takes care of + * setting v:filearg. + */ + int +apply_autocmds_exarg( + event_T event, + char_u *fname, + char_u *fname_io, + int force, + buf_T *buf, + exarg_T *eap) +{ + return apply_autocmds_group(event, fname, fname_io, force, + AUGROUP_ALL, buf, eap); +} + +/* + * Like apply_autocmds(), but handles the caller's retval. If the script + * processing is being aborted or if retval is FAIL when inside a try + * conditional, no autocommands are executed. If otherwise the autocommands + * cause the script to be aborted, retval is set to FAIL. + */ + int +apply_autocmds_retval( + event_T event, + char_u *fname, // NULL or empty means use actual file name + char_u *fname_io, // fname to use for on cmdline + int force, // when TRUE, ignore autocmd_busy + buf_T *buf, // buffer for + int *retval) // pointer to caller's retval +{ + int did_cmd; + +#ifdef FEAT_EVAL + if (should_abort(*retval)) + return FALSE; +#endif + + did_cmd = apply_autocmds_group(event, fname, fname_io, force, + AUGROUP_ALL, buf, NULL); + if (did_cmd +#ifdef FEAT_EVAL + && aborting() +#endif + ) + *retval = FAIL; + return did_cmd; +} + +/* + * Return TRUE when there is a CursorHold autocommand defined. + */ + static int +has_cursorhold(void) +{ + return (first_autopat[(int)(get_real_state() == MODE_NORMAL_BUSY + ? EVENT_CURSORHOLD : EVENT_CURSORHOLDI)] != NULL); +} + +/* + * Return TRUE if the CursorHold event can be triggered. + */ + int +trigger_cursorhold(void) +{ + int state; + + if (!did_cursorhold + && has_cursorhold() + && reg_recording == 0 + && typebuf.tb_len == 0 + && !ins_compl_active()) + { + state = get_real_state(); + if (state == MODE_NORMAL_BUSY || (state & MODE_INSERT) != 0) + return TRUE; + } + return FALSE; +} + +/* + * Return TRUE when there is a WinResized autocommand defined. + */ + int +has_winresized(void) +{ + return (first_autopat[(int)EVENT_WINRESIZED] != NULL); +} + +/* + * Return TRUE when there is a WinScrolled autocommand defined. + */ + int +has_winscrolled(void) +{ + return (first_autopat[(int)EVENT_WINSCROLLED] != NULL); +} + +/* + * Return TRUE when there is a CursorMoved autocommand defined. + */ + int +has_cursormoved(void) +{ + return (first_autopat[(int)EVENT_CURSORMOVED] != NULL); +} + +/* + * Return TRUE when there is a CursorMovedI autocommand defined. + */ + int +has_cursormovedI(void) +{ + return (first_autopat[(int)EVENT_CURSORMOVEDI] != NULL); +} + +/* + * Return TRUE when there is a TextChanged autocommand defined. + */ + int +has_textchanged(void) +{ + return (first_autopat[(int)EVENT_TEXTCHANGED] != NULL); +} + +/* + * Return TRUE when there is a TextChangedI autocommand defined. + */ + int +has_textchangedI(void) +{ + return (first_autopat[(int)EVENT_TEXTCHANGEDI] != NULL); +} + +/* + * Return TRUE when there is a TextChangedP autocommand defined. + */ + int +has_textchangedP(void) +{ + return (first_autopat[(int)EVENT_TEXTCHANGEDP] != NULL); +} + +/* + * Return TRUE when there is an InsertCharPre autocommand defined. + */ + int +has_insertcharpre(void) +{ + return (first_autopat[(int)EVENT_INSERTCHARPRE] != NULL); +} + +/* + * Return TRUE when there is an CmdUndefined autocommand defined. + */ + int +has_cmdundefined(void) +{ + return (first_autopat[(int)EVENT_CMDUNDEFINED] != NULL); +} + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * Return TRUE when there is a TextYankPost autocommand defined. + */ + int +has_textyankpost(void) +{ + return (first_autopat[(int)EVENT_TEXTYANKPOST] != NULL); +} +#endif + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * Return TRUE when there is a CompleteChanged autocommand defined. + */ + int +has_completechanged(void) +{ + return (first_autopat[(int)EVENT_COMPLETECHANGED] != NULL); +} +#endif + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * Return TRUE when there is a ModeChanged autocommand defined. + */ + int +has_modechanged(void) +{ + return (first_autopat[(int)EVENT_MODECHANGED] != NULL); +} +#endif + +/* + * Execute autocommands for "event" and file name "fname". + * Return TRUE if some commands were executed. + */ + static int +apply_autocmds_group( + event_T event, + char_u *fname, // NULL or empty means use actual file name + char_u *fname_io, // fname to use for on cmdline, NULL means + // use fname + int force, // when TRUE, ignore autocmd_busy + int group, // group ID, or AUGROUP_ALL + buf_T *buf, // buffer for + exarg_T *eap UNUSED) // command arguments +{ + char_u *sfname = NULL; // short file name + char_u *tail; + int save_changed; + buf_T *old_curbuf; + int retval = FALSE; + char_u *save_autocmd_fname; + int save_autocmd_fname_full; + int save_autocmd_bufnr; + char_u *save_autocmd_match; + int save_autocmd_busy; + int save_autocmd_nested; + static int nesting = 0; + AutoPatCmd_T patcmd; + AutoPat *ap; + sctx_T save_current_sctx; +#ifdef FEAT_EVAL + funccal_entry_T funccal_entry; + char_u *save_cmdarg; + long save_cmdbang; +#endif + static int filechangeshell_busy = FALSE; +#ifdef FEAT_PROFILE + proftime_T wait_time; +#endif + int did_save_redobuff = FALSE; + save_redo_T save_redo; + int save_KeyTyped = KeyTyped; + ESTACK_CHECK_DECLARATION; + + /* + * Quickly return if there are no autocommands for this event or + * autocommands are blocked. + */ + if (event == NUM_EVENTS || first_autopat[(int)event] == NULL + || autocmd_blocked > 0) + goto BYPASS_AU; + + /* + * When autocommands are busy, new autocommands are only executed when + * explicitly enabled with the "nested" flag. + */ + if (autocmd_busy && !(force || autocmd_nested)) + goto BYPASS_AU; + +#ifdef FEAT_EVAL + /* + * Quickly return when immediately aborting on error, or when an interrupt + * occurred or an exception was thrown but not caught. + */ + if (aborting()) + goto BYPASS_AU; +#endif + + /* + * FileChangedShell never nests, because it can create an endless loop. + */ + if (filechangeshell_busy && (event == EVENT_FILECHANGEDSHELL + || event == EVENT_FILECHANGEDSHELLPOST)) + goto BYPASS_AU; + + /* + * Ignore events in 'eventignore'. + */ + if (event_ignored(event)) + goto BYPASS_AU; + + /* + * Allow nesting of autocommands, but restrict the depth, because it's + * possible to create an endless loop. + */ + if (nesting == 10) + { + emsg(_(e_autocommand_nesting_too_deep)); + goto BYPASS_AU; + } + + /* + * Check if these autocommands are disabled. Used when doing ":all" or + * ":ball". + */ + if ( (autocmd_no_enter + && (event == EVENT_WINENTER || event == EVENT_BUFENTER)) + || (autocmd_no_leave + && (event == EVENT_WINLEAVE || event == EVENT_BUFLEAVE))) + goto BYPASS_AU; + + if (event == EVENT_CMDLINECHANGED) + ++aucmd_cmdline_changed_count; + + /* + * Save the autocmd_* variables and info about the current buffer. + */ + save_autocmd_fname = autocmd_fname; + save_autocmd_fname_full = autocmd_fname_full; + save_autocmd_bufnr = autocmd_bufnr; + save_autocmd_match = autocmd_match; + save_autocmd_busy = autocmd_busy; + save_autocmd_nested = autocmd_nested; + save_changed = curbuf->b_changed; + old_curbuf = curbuf; + + /* + * Set the file name to be used for . + * Make a copy to avoid that changing a buffer name or directory makes it + * invalid. + */ + if (fname_io == NULL) + { + if (event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE + || event == EVENT_OPTIONSET + || event == EVENT_MODECHANGED) + autocmd_fname = NULL; + else if (fname != NULL && !ends_excmd(*fname)) + autocmd_fname = fname; + else if (buf != NULL) + autocmd_fname = buf->b_ffname; + else + autocmd_fname = NULL; + } + else + autocmd_fname = fname_io; + if (autocmd_fname != NULL) + autocmd_fname = vim_strsave(autocmd_fname); + autocmd_fname_full = FALSE; // call FullName_save() later + + /* + * Set the buffer number to be used for . + */ + if (buf == NULL) + autocmd_bufnr = 0; + else + autocmd_bufnr = buf->b_fnum; + + /* + * When the file name is NULL or empty, use the file name of buffer "buf". + * Always use the full path of the file name to match with, in case + * "allow_dirs" is set. + */ + if (fname == NULL || *fname == NUL) + { + if (buf == NULL) + fname = NULL; + else + { +#ifdef FEAT_SYN_HL + if (event == EVENT_SYNTAX) + fname = buf->b_p_syn; + else +#endif + if (event == EVENT_FILETYPE) + fname = buf->b_p_ft; + else + { + if (buf->b_sfname != NULL) + sfname = vim_strsave(buf->b_sfname); + fname = buf->b_ffname; + } + } + if (fname == NULL) + fname = (char_u *)""; + fname = vim_strsave(fname); // make a copy, so we can change it + } + else + { + sfname = vim_strsave(fname); + // Don't try expanding FileType, Syntax, FuncUndefined, WindowID, + // ColorScheme, QuickFixCmd*, DirChanged and similar. + if (event == EVENT_FILETYPE + || event == EVENT_SYNTAX + || event == EVENT_CMDLINECHANGED + || event == EVENT_CMDLINEENTER + || event == EVENT_CMDLINELEAVE + || event == EVENT_CMDWINENTER + || event == EVENT_CMDWINLEAVE + || event == EVENT_CMDUNDEFINED + || event == EVENT_FUNCUNDEFINED + || event == EVENT_REMOTEREPLY + || event == EVENT_SPELLFILEMISSING + || event == EVENT_QUICKFIXCMDPRE + || event == EVENT_COLORSCHEME + || event == EVENT_COLORSCHEMEPRE + || event == EVENT_OPTIONSET + || event == EVENT_QUICKFIXCMDPOST + || event == EVENT_DIRCHANGED + || event == EVENT_DIRCHANGEDPRE + || event == EVENT_MODECHANGED + || event == EVENT_MENUPOPUP + || event == EVENT_USER + || event == EVENT_WINCLOSED + || event == EVENT_WINRESIZED + || event == EVENT_WINSCROLLED) + { + fname = vim_strsave(fname); + autocmd_fname_full = TRUE; // don't expand it later + } + else + fname = FullName_save(fname, FALSE); + } + if (fname == NULL) // out of memory + { + vim_free(sfname); + retval = FALSE; + goto BYPASS_AU; + } + +#ifdef BACKSLASH_IN_FILENAME + /* + * Replace all backslashes with forward slashes. This makes the + * autocommand patterns portable between Unix and MS-DOS. + */ + if (sfname != NULL) + forward_slash(sfname); + forward_slash(fname); +#endif + +#ifdef VMS + // remove version for correct match + if (sfname != NULL) + vms_remove_version(sfname); + vms_remove_version(fname); +#endif + + /* + * Set the name to be used for . + */ + autocmd_match = fname; + + + // Don't redraw while doing autocommands. + ++RedrawingDisabled; + + // name and lnum are filled in later + estack_push(ETYPE_AUCMD, NULL, 0); + ESTACK_CHECK_SETUP; + + save_current_sctx = current_sctx; + +#ifdef FEAT_EVAL +# ifdef FEAT_PROFILE + if (do_profiling == PROF_YES) + prof_child_enter(&wait_time); // doesn't count for the caller itself +# endif + + // Don't use local function variables, if called from a function. + save_funccal(&funccal_entry); +#endif + + /* + * When starting to execute autocommands, save the search patterns. + */ + if (!autocmd_busy) + { + save_search_patterns(); + if (!ins_compl_active()) + { + saveRedobuff(&save_redo); + did_save_redobuff = TRUE; + } + did_filetype = keep_filetype; + } + + /* + * Note that we are applying autocmds. Some commands need to know. + */ + autocmd_busy = TRUE; + filechangeshell_busy = (event == EVENT_FILECHANGEDSHELL); + ++nesting; // see matching decrement below + + // Remember that FileType was triggered. Used for did_filetype(). + if (event == EVENT_FILETYPE) + did_filetype = TRUE; + + tail = gettail(fname); + + // Find first autocommand that matches + CLEAR_FIELD(patcmd); + patcmd.curpat = first_autopat[(int)event]; + patcmd.group = group; + patcmd.fname = fname; + patcmd.sfname = sfname; + patcmd.tail = tail; + patcmd.event = event; + patcmd.arg_bufnr = autocmd_bufnr; + auto_next_pat(&patcmd, FALSE); + + // found one, start executing the autocommands + if (patcmd.curpat != NULL) + { + // add to active_apc_list + patcmd.next = active_apc_list; + active_apc_list = &patcmd; + +#ifdef FEAT_EVAL + // set v:cmdarg (only when there is a matching pattern) + save_cmdbang = (long)get_vim_var_nr(VV_CMDBANG); + if (eap != NULL) + { + save_cmdarg = set_cmdarg(eap, NULL); + set_vim_var_nr(VV_CMDBANG, (long)eap->forceit); + } + else + save_cmdarg = NULL; // avoid gcc warning +#endif + retval = TRUE; + // mark the last pattern, to avoid an endless loop when more patterns + // are added when executing autocommands + for (ap = patcmd.curpat; ap->next != NULL; ap = ap->next) + ap->last = FALSE; + ap->last = TRUE; + + // Make sure cursor and topline are valid. The first time the current + // values are saved, restored by reset_lnums(). When nested only the + // values are corrected when needed. + if (nesting == 1) + check_lnums(TRUE); + else + check_lnums_nested(TRUE); + + int save_did_emsg = did_emsg; + int save_ex_pressedreturn = get_pressedreturn(); + + do_cmdline(NULL, getnextac, (void *)&patcmd, + DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); + + did_emsg += save_did_emsg; + set_pressedreturn(save_ex_pressedreturn); + + if (nesting == 1) + // restore cursor and topline, unless they were changed + reset_lnums(); + +#ifdef FEAT_EVAL + if (eap != NULL) + { + (void)set_cmdarg(NULL, save_cmdarg); + set_vim_var_nr(VV_CMDBANG, save_cmdbang); + } +#endif + // delete from active_apc_list + if (active_apc_list == &patcmd) // just in case + active_apc_list = patcmd.next; + } + + if (RedrawingDisabled > 0) + --RedrawingDisabled; + autocmd_busy = save_autocmd_busy; + filechangeshell_busy = FALSE; + autocmd_nested = save_autocmd_nested; + vim_free(SOURCING_NAME); + ESTACK_CHECK_NOW; + estack_pop(); + vim_free(autocmd_fname); + autocmd_fname = save_autocmd_fname; + autocmd_fname_full = save_autocmd_fname_full; + autocmd_bufnr = save_autocmd_bufnr; + autocmd_match = save_autocmd_match; + current_sctx = save_current_sctx; +#ifdef FEAT_EVAL + restore_funccal(); +# ifdef FEAT_PROFILE + if (do_profiling == PROF_YES) + prof_child_exit(&wait_time); +# endif +#endif + KeyTyped = save_KeyTyped; + vim_free(fname); + vim_free(sfname); + --nesting; // see matching increment above + + /* + * When stopping to execute autocommands, restore the search patterns and + * the redo buffer. Free any buffers in the au_pending_free_buf list and + * free any windows in the au_pending_free_win list. + */ + if (!autocmd_busy) + { + restore_search_patterns(); + if (did_save_redobuff) + restoreRedobuff(&save_redo); + did_filetype = FALSE; + while (au_pending_free_buf != NULL) + { + buf_T *b = au_pending_free_buf->b_next; + + vim_free(au_pending_free_buf); + au_pending_free_buf = b; + } + while (au_pending_free_win != NULL) + { + win_T *w = au_pending_free_win->w_next; + + vim_free(au_pending_free_win); + au_pending_free_win = w; + } + } + + /* + * Some events don't set or reset the Changed flag. + * Check if still in the same buffer! + */ + if (curbuf == old_curbuf + && (event == EVENT_BUFREADPOST + || event == EVENT_BUFWRITEPOST + || event == EVENT_FILEAPPENDPOST + || event == EVENT_VIMLEAVE + || event == EVENT_VIMLEAVEPRE)) + { + if (curbuf->b_changed != save_changed) + need_maketitle = TRUE; + curbuf->b_changed = save_changed; + } + + au_cleanup(); // may really delete removed patterns/commands now + +BYPASS_AU: + // When wiping out a buffer make sure all its buffer-local autocommands + // are deleted. + if (event == EVENT_BUFWIPEOUT && buf != NULL) + aubuflocal_remove(buf); + + if (retval == OK && event == EVENT_FILETYPE) + au_did_filetype = TRUE; + + return retval; +} + +# ifdef FEAT_EVAL +static char_u *old_termresponse = NULL; +# endif + +/* + * Block triggering autocommands until unblock_autocmd() is called. + * Can be used recursively, so long as it's symmetric. + */ + void +block_autocmds(void) +{ +# ifdef FEAT_EVAL + // Remember the value of v:termresponse. + if (autocmd_blocked == 0) + old_termresponse = get_vim_var_str(VV_TERMRESPONSE); +# endif + ++autocmd_blocked; +} + + void +unblock_autocmds(void) +{ + --autocmd_blocked; + +# ifdef FEAT_EVAL + // When v:termresponse was set while autocommands were blocked, trigger + // the autocommands now. Esp. useful when executing a shell command + // during startup (vimdiff). + if (autocmd_blocked == 0 + && get_vim_var_str(VV_TERMRESPONSE) != old_termresponse) + apply_autocmds(EVENT_TERMRESPONSE, NULL, NULL, FALSE, curbuf); +# endif +} + + int +is_autocmd_blocked(void) +{ + return autocmd_blocked != 0; +} + +/* + * Find next autocommand pattern that matches. + */ + static void +auto_next_pat( + AutoPatCmd_T *apc, + int stop_at_last) // stop when 'last' flag is set +{ + AutoPat *ap; + AutoCmd *cp; + char_u *name; + char *s; + estack_T *entry; + char_u *namep; + + entry = ((estack_T *)exestack.ga_data) + exestack.ga_len - 1; + + // Clear the exestack entry for this ETYPE_AUCMD entry. + VIM_CLEAR(entry->es_name); + entry->es_info.aucmd = NULL; + + for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next) + { + apc->curpat = NULL; + + // Only use a pattern when it has not been removed, has commands and + // the group matches. For buffer-local autocommands only check the + // buffer number. + if (ap->pat != NULL && ap->cmds != NULL + && (apc->group == AUGROUP_ALL || apc->group == ap->group)) + { + // execution-condition + if (ap->buflocal_nr == 0 + ? (match_file_pat(NULL, &ap->reg_prog, apc->fname, + apc->sfname, apc->tail, ap->allow_dirs)) + : ap->buflocal_nr == apc->arg_bufnr) + { + name = event_nr2name(apc->event); + s = _("%s Autocommands for \"%s\""); + namep = alloc(STRLEN(s) + STRLEN(name) + ap->patlen + 1); + if (namep != NULL) + { + sprintf((char *)namep, s, (char *)name, (char *)ap->pat); + if (p_verbose >= 8) + { + verbose_enter(); + smsg(_("Executing %s"), namep); + verbose_leave(); + } + } + + // Update the exestack entry for this autocmd. + entry->es_name = namep; + entry->es_info.aucmd = apc; + + apc->curpat = ap; + apc->nextcmd = ap->cmds; + // mark last command + for (cp = ap->cmds; cp->next != NULL; cp = cp->next) + cp->last = FALSE; + cp->last = TRUE; + } + line_breakcheck(); + if (apc->curpat != NULL) // found a match + break; + } + if (stop_at_last && ap->last) + break; + } +} + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * Get the script context where autocommand "acp" is defined. + */ + sctx_T * +acp_script_ctx(AutoPatCmd_T *acp) +{ + return &acp->script_ctx; +} +#endif + +/* + * Get next autocommand command. + * Called by do_cmdline() to get the next line for ":if". + * Returns allocated string, or NULL for end of autocommands. + */ + char_u * +getnextac( + int c UNUSED, + void *cookie, + int indent UNUSED, + getline_opt_T options UNUSED) +{ + AutoPatCmd_T *acp = (AutoPatCmd_T *)cookie; + char_u *retval; + AutoCmd *ac; + + // Can be called again after returning the last line. + if (acp->curpat == NULL) + return NULL; + + // repeat until we find an autocommand to execute + for (;;) + { + // skip removed commands + while (acp->nextcmd != NULL && acp->nextcmd->cmd == NULL) + if (acp->nextcmd->last) + acp->nextcmd = NULL; + else + acp->nextcmd = acp->nextcmd->next; + + if (acp->nextcmd != NULL) + break; + + // at end of commands, find next pattern that matches + if (acp->curpat->last) + acp->curpat = NULL; + else + acp->curpat = acp->curpat->next; + if (acp->curpat != NULL) + auto_next_pat(acp, TRUE); + if (acp->curpat == NULL) + return NULL; + } + + ac = acp->nextcmd; + + if (p_verbose >= 9) + { + verbose_enter_scroll(); + smsg(_("autocommand %s"), ac->cmd); + msg_puts("\n"); // don't overwrite this either + verbose_leave_scroll(); + } + retval = vim_strsave(ac->cmd); + // Remove one-shot ("once") autocmd in anticipation of its execution. + if (ac->once) + au_del_cmd(ac); + autocmd_nested = ac->nested; + current_sctx = ac->script_ctx; + acp->script_ctx = current_sctx; + if (ac->last) + acp->nextcmd = NULL; + else + acp->nextcmd = ac->next; + return retval; +} + +/* + * Return TRUE if there is a matching autocommand for "fname". + * To account for buffer-local autocommands, function needs to know + * in which buffer the file will be opened. + */ + int +has_autocmd(event_T event, char_u *sfname, buf_T *buf) +{ + AutoPat *ap; + char_u *fname; + char_u *tail = gettail(sfname); + int retval = FALSE; + + fname = FullName_save(sfname, FALSE); + if (fname == NULL) + return FALSE; + +#ifdef BACKSLASH_IN_FILENAME + /* + * Replace all backslashes with forward slashes. This makes the + * autocommand patterns portable between Unix and MS-DOS. + */ + sfname = vim_strsave(sfname); + if (sfname != NULL) + forward_slash(sfname); + forward_slash(fname); +#endif + + FOR_ALL_AUTOCMD_PATTERNS(event, ap) + if (ap->pat != NULL && ap->cmds != NULL + && (ap->buflocal_nr == 0 + ? match_file_pat(NULL, &ap->reg_prog, + fname, sfname, tail, ap->allow_dirs) + : buf != NULL && ap->buflocal_nr == buf->b_fnum + )) + { + retval = TRUE; + break; + } + + vim_free(fname); +#ifdef BACKSLASH_IN_FILENAME + vim_free(sfname); +#endif + + return retval; +} + +/* + * Function given to ExpandGeneric() to obtain the list of autocommand group + * names. + */ + char_u * +get_augroup_name(expand_T *xp UNUSED, int idx) +{ + if (idx == augroups.ga_len) // add "END" add the end + return (char_u *)"END"; + if (idx < 0 || idx >= augroups.ga_len) // end of list + return NULL; + if (AUGROUP_NAME(idx) == NULL || AUGROUP_NAME(idx) == get_deleted_augroup()) + // skip deleted entries + return (char_u *)""; + return AUGROUP_NAME(idx); // return a name +} + +static int include_groups = FALSE; + + char_u * +set_context_in_autocmd( + expand_T *xp, + char_u *arg, + int doautocmd) // TRUE for :doauto*, FALSE for :autocmd +{ + char_u *p; + int group; + + // check for a group name, skip it if present + include_groups = FALSE; + p = arg; + group = au_get_grouparg(&arg); + if (group == AUGROUP_ERROR) + return NULL; + // If there only is a group name that's what we expand. + if (*arg == NUL && group != AUGROUP_ALL && !VIM_ISWHITE(arg[-1])) + { + arg = p; + group = AUGROUP_ALL; + } + + // skip over event name + for (p = arg; *p != NUL && !VIM_ISWHITE(*p); ++p) + if (*p == ',') + arg = p + 1; + if (*p == NUL) + { + if (group == AUGROUP_ALL) + include_groups = TRUE; + xp->xp_context = EXPAND_EVENTS; // expand event name + xp->xp_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) + xp->xp_context = EXPAND_FILES; // expand file names + else + xp->xp_context = EXPAND_NOTHING; // pattern is not expanded + return NULL; +} + +/* + * Function given to ExpandGeneric() to obtain the list of event names. + */ + char_u * +get_event_name(expand_T *xp UNUSED, int idx) +{ + if (idx < augroups.ga_len) // First list group names, if wanted + { + if (!include_groups || AUGROUP_NAME(idx) == NULL + || AUGROUP_NAME(idx) == get_deleted_augroup()) + return (char_u *)""; // skip deleted entries + return AUGROUP_NAME(idx); // return a name + } + return (char_u *)event_names[idx - augroups.ga_len].name; +} + +/* + * Function given to ExpandGeneric() to obtain the list of event names. Don't + * include groups. + */ + char_u * +get_event_name_no_group(expand_T *xp UNUSED, int idx) +{ + return (char_u *)event_names[idx].name; +} + + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * Return TRUE if autocmd is supported. + */ + int +autocmd_supported(char_u *name) +{ + char_u *p; + + return (event_name2nr(name, &p) != NUM_EVENTS); +} + +/* + * Return TRUE if an autocommand is defined for a group, event and + * pattern: The group can be omitted to accept any group. "event" and "pattern" + * can be NULL to accept any event and pattern. "pattern" can be NULL to accept + * any pattern. Buffer-local patterns or are accepted. + * Used for: + * exists("#Group") or + * exists("#Group#Event") or + * exists("#Group#Event#pat") or + * exists("#Event") or + * exists("#Event#pat") + */ + int +au_exists(char_u *arg) +{ + char_u *arg_save; + char_u *pattern = NULL; + char_u *event_name; + char_u *p; + event_T event; + AutoPat *ap; + buf_T *buflocal_buf = NULL; + int group; + int retval = FALSE; + + // Make a copy so that we can change the '#' chars to a NUL. + arg_save = vim_strsave(arg); + if (arg_save == NULL) + return FALSE; + p = vim_strchr(arg_save, '#'); + if (p != NULL) + *p++ = NUL; + + // First, look for an autocmd group name + group = au_find_group(arg_save); + if (group == AUGROUP_ERROR) + { + // Didn't match a group name, assume the first argument is an event. + group = AUGROUP_ALL; + event_name = arg_save; + } + else + { + if (p == NULL) + { + // "Group": group name is present and it's recognized + retval = TRUE; + goto theend; + } + + // Must be "Group#Event" or "Group#Event#pat". + event_name = p; + p = vim_strchr(event_name, '#'); + if (p != NULL) + *p++ = NUL; // "Group#Event#pat" + } + + pattern = p; // "pattern" is NULL when there is no pattern + + // find the index (enum) for the event name + event = event_name2nr(event_name, &p); + + // return FALSE if the event name is not recognized + if (event == NUM_EVENTS) + goto theend; + + // Find the first autocommand for this event. + // If there isn't any, return FALSE; + // If there is one and no pattern given, return TRUE; + ap = first_autopat[(int)event]; + if (ap == NULL) + goto theend; + + // if pattern is "", special handling is needed which uses curbuf + // for pattern ", fnamecmp() will work fine + if (pattern != NULL && STRICMP(pattern, "") == 0) + buflocal_buf = curbuf; + + // Check if there is an autocommand with the given pattern. + for ( ; ap != NULL; ap = ap->next) + // only use a pattern when it has not been removed and has commands. + // For buffer-local autocommands, fnamecmp() works fine. + if (ap->pat != NULL && ap->cmds != NULL + && (group == AUGROUP_ALL || ap->group == group) + && (pattern == NULL + || (buflocal_buf == NULL + ? fnamecmp(ap->pat, pattern) == 0 + : ap->buflocal_nr == buflocal_buf->b_fnum))) + { + retval = TRUE; + break; + } + +theend: + vim_free(arg_save); + return retval; +} + +/* + * autocmd_add() and autocmd_delete() functions + */ + static void +autocmd_add_or_delete(typval_T *argvars, typval_T *rettv, int delete) +{ + list_T *aucmd_list; + listitem_T *li; + dict_T *event_dict; + dictitem_T *di; + char_u *event_name = NULL; + list_T *event_list; + listitem_T *eli; + event_T event; + char_u *group_name = NULL; + int group; + char_u *pat = NULL; + list_T *pat_list; + listitem_T *pli; + char_u *cmd = NULL; + char_u *end; + int once; + int nested; + int replace; // replace the cmd for a group/event + int retval = VVAL_TRUE; + int save_augroup = current_augroup; + + rettv->v_type = VAR_BOOL; + rettv->vval.v_number = VVAL_FALSE; + + if (check_for_list_arg(argvars, 0) == FAIL) + return; + + aucmd_list = argvars[0].vval.v_list; + if (aucmd_list == NULL) + return; + + FOR_ALL_LIST_ITEMS(aucmd_list, li) + { + VIM_CLEAR(group_name); + VIM_CLEAR(cmd); + event_name = NULL; + event_list = NULL; + pat = NULL; + pat_list = NULL; + + if (li->li_tv.v_type != VAR_DICT) + continue; + + event_dict = li->li_tv.vval.v_dict; + if (event_dict == NULL) + continue; + + di = dict_find(event_dict, (char_u *)"event", -1); + if (di != NULL) + { + if (di->di_tv.v_type == VAR_STRING) + { + event_name = di->di_tv.vval.v_string; + if (event_name == NULL) + { + emsg(_(e_string_required)); + continue; + } + } + else if (di->di_tv.v_type == VAR_LIST) + { + event_list = di->di_tv.vval.v_list; + if (event_list == NULL) + { + emsg(_(e_list_required)); + continue; + } + } + else + { + emsg(_(e_string_or_list_expected)); + continue; + } + } + + group_name = dict_get_string(event_dict, "group", TRUE); + if (group_name == NULL || *group_name == NUL) + // if the autocmd group name is not specified, then use the current + // autocmd group + group = current_augroup; + else + { + group = au_find_group(group_name); + if (group == AUGROUP_ERROR) + { + if (delete) + { + semsg(_(e_no_such_group_str), group_name); + retval = VVAL_FALSE; + break; + } + // group is not found, create it now + group = au_new_group(group_name); + if (group == AUGROUP_ERROR) + { + semsg(_(e_no_such_group_str), group_name); + retval = VVAL_FALSE; + break; + } + + current_augroup = group; + } + } + + // if a buffer number is specified, then generate a pattern of the form + // ". Otherwise, use the pattern supplied by the user. + if (dict_has_key(event_dict, "bufnr")) + { + varnumber_T bnum; + + bnum = dict_get_number_def(event_dict, "bufnr", -1); + if (bnum == -1) + continue; + + vim_snprintf((char *)IObuff, IOSIZE, "", (int)bnum); + pat = IObuff; + } + else + { + di = dict_find(event_dict, (char_u *)"pattern", -1); + if (di != NULL) + { + if (di->di_tv.v_type == VAR_STRING) + { + pat = di->di_tv.vval.v_string; + if (pat == NULL) + { + emsg(_(e_string_required)); + continue; + } + } + else if (di->di_tv.v_type == VAR_LIST) + { + pat_list = di->di_tv.vval.v_list; + if (pat_list == NULL) + { + emsg(_(e_list_required)); + continue; + } + } + else + { + emsg(_(e_string_or_list_expected)); + continue; + } + } + else if (delete) + pat = (char_u *)""; + } + + once = dict_get_bool(event_dict, "once", FALSE); + nested = dict_get_bool(event_dict, "nested", FALSE); + // if 'replace' is true, then remove all the commands associated with + // this autocmd event/group and add the new command. + replace = dict_get_bool(event_dict, "replace", FALSE); + + cmd = dict_get_string(event_dict, "cmd", TRUE); + if (cmd == NULL) + { + if (delete) + cmd = vim_strsave((char_u *)""); + else + continue; + } + + if (delete && (event_name == NULL + || (event_name[0] == '*' && event_name[1] == NUL))) + { + // if the event name is not specified or '*', delete all the events + for (event = (event_T)0; (int)event < NUM_EVENTS; + event = (event_T)((int)event + 1)) + { + if (do_autocmd_event(event, pat, once, nested, cmd, delete, + group, 0) == FAIL) + { + retval = VVAL_FALSE; + break; + } + } + } + else + { + char_u *p = NULL; + + eli = NULL; + end = NULL; + while (TRUE) + { + if (event_list != NULL) + { + if (eli == NULL) + eli = event_list->lv_first; + else + eli = eli->li_next; + if (eli == NULL) + break; + if (eli->li_tv.v_type != VAR_STRING + || (p = eli->li_tv.vval.v_string) == NULL) + { + emsg(_(e_string_required)); + break; + } + } + else + { + if (p == NULL) + p = event_name; + if (p == NULL || *p == NUL) + break; + } + + event = event_name2nr(p, &end); + if (event == NUM_EVENTS || *end != NUL) + { + // this also catches something following a valid event name + semsg(_(e_no_such_event_str), p); + retval = VVAL_FALSE; + break; + } + if (pat != NULL) + { + if (do_autocmd_event(event, pat, once, nested, cmd, + delete | replace, group, 0) == FAIL) + { + retval = VVAL_FALSE; + break; + } + } + else if (pat_list != NULL) + { + FOR_ALL_LIST_ITEMS(pat_list, pli) + { + if (pli->li_tv.v_type != VAR_STRING + || pli->li_tv.vval.v_string == NULL) + { + emsg(_(e_string_required)); + continue; + } + if (do_autocmd_event(event, + pli->li_tv.vval.v_string, once, nested, + cmd, delete | replace, group, 0) == + FAIL) + { + retval = VVAL_FALSE; + break; + } + } + if (retval == VVAL_FALSE) + break; + } + if (event_name != NULL) + p = end; + } + } + + // if only the autocmd group name is specified for delete and the + // autocmd event, pattern and cmd are not specified, then delete the + // autocmd group. + if (delete && group_name != NULL && + (event_name == NULL || event_name[0] == NUL) + && (pat == NULL || pat[0] == NUL) + && (cmd == NULL || cmd[0] == NUL)) + au_del_group(group_name); + } + + VIM_CLEAR(group_name); + VIM_CLEAR(cmd); + + current_augroup = save_augroup; + rettv->vval.v_number = retval; +} + +/* + * autocmd_add() function + */ + void +f_autocmd_add(typval_T *argvars, typval_T *rettv) +{ + autocmd_add_or_delete(argvars, rettv, FALSE); +} + +/* + * autocmd_delete() function + */ + void +f_autocmd_delete(typval_T *argvars, typval_T *rettv) +{ + autocmd_add_or_delete(argvars, rettv, TRUE); +} + +/* + * autocmd_get() function + * Returns a List of autocmds. + */ + void +f_autocmd_get(typval_T *argvars, typval_T *rettv) +{ + event_T event_arg = NUM_EVENTS; + event_T event; + AutoPat *ap; + AutoCmd *ac; + list_T *event_list; + dict_T *event_dict; + char_u *event_name = NULL; + char_u *pat = NULL; + char_u *name = NULL; + int group = AUGROUP_ALL; + + if (rettv_list_alloc(rettv) == FAIL) + return; + if (check_for_opt_dict_arg(argvars, 0) == FAIL) + return; + + if (argvars[0].v_type == VAR_DICT) + { + // return only the autocmds in the specified group + if (dict_has_key(argvars[0].vval.v_dict, "group")) + { + name = dict_get_string(argvars[0].vval.v_dict, "group", TRUE); + if (name == NULL) + return; + + if (*name == NUL) + group = AUGROUP_DEFAULT; + else + { + group = au_find_group(name); + if (group == AUGROUP_ERROR) + { + semsg(_(e_no_such_group_str), name); + vim_free(name); + return; + } + } + vim_free(name); + } + + // return only the autocmds for the specified event + if (dict_has_key(argvars[0].vval.v_dict, "event")) + { + int i; + + name = dict_get_string(argvars[0].vval.v_dict, "event", TRUE); + if (name == NULL) + return; + + if (name[0] == '*' && name[1] == NUL) + event_arg = NUM_EVENTS; + else + { + for (i = 0; event_names[i].name != NULL; i++) + if (STRICMP(event_names[i].name, name) == 0) + break; + if (event_names[i].name == NULL) + { + semsg(_(e_no_such_event_str), name); + vim_free(name); + return; + } + event_arg = event_names[i].event; + } + vim_free(name); + } + + // return only the autocmds for the specified pattern + if (dict_has_key(argvars[0].vval.v_dict, "pattern")) + { + pat = dict_get_string(argvars[0].vval.v_dict, "pattern", TRUE); + if (pat == NULL) + return; + } + } + + event_list = rettv->vval.v_list; + + // iterate through all the autocmd events + for (event = (event_T)0; (int)event < NUM_EVENTS; + event = (event_T)((int)event + 1)) + { + if (event_arg != NUM_EVENTS && event != event_arg) + continue; + + event_name = event_nr2name(event); + + // iterate through all the patterns for this autocmd event + FOR_ALL_AUTOCMD_PATTERNS(event, ap) + { + char_u *group_name; + + if (group != AUGROUP_ALL && group != ap->group) + continue; + + if (pat != NULL && STRCMP(pat, ap->pat) != 0) + continue; + + group_name = get_augroup_name(NULL, ap->group); + + // iterate through all the commands for this pattern and add one + // item for each cmd. + for (ac = ap->cmds; ac != NULL; ac = ac->next) + { + event_dict = dict_alloc(); + if (event_dict == NULL + || list_append_dict(event_list, event_dict) == FAIL) + return; + + if (dict_add_string(event_dict, "event", event_name) == FAIL + || dict_add_string(event_dict, "group", + group_name == NULL ? (char_u *)"" + : group_name) == FAIL + || (ap->buflocal_nr != 0 + && (dict_add_number(event_dict, "bufnr", + ap->buflocal_nr) == FAIL)) + || dict_add_string(event_dict, "pattern", + ap->pat) == FAIL + || dict_add_string(event_dict, "cmd", ac->cmd) == FAIL + || dict_add_bool(event_dict, "once", ac->once) == FAIL + || dict_add_bool(event_dict, "nested", + ac->nested) == FAIL) + return; + } + } + } + + vim_free(pat); +} + +#endif diff --git a/userland/apps/vim/src/beval.c b/userland/apps/vim/src/beval.c new file mode 100644 index 00000000..65dfbc2a --- /dev/null +++ b/userland/apps/vim/src/beval.c @@ -0,0 +1,356 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * Visual Workshop integration by Gordon Prieur + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +#include "vim.h" + +#if defined(FEAT_BEVAL) || defined(FEAT_PROP_POPUP) || defined(PROTO) +/* + * Find text under the mouse position "row" / "col". + * If "getword" is TRUE the returned text in "*textp" is not the whole line but + * the relevant word in allocated memory. + * Return OK if found. + * Return FAIL if not found, no text at the mouse position. + */ + int +find_word_under_cursor( + int mouserow, + int mousecol, + int getword, + int flags, // flags for find_ident_at_pos() + win_T **winp, // can be NULL + linenr_T *lnump, // can be NULL + char_u **textp, + int *colp, // column where mouse hovers, can be NULL + int *startcolp) // column where text starts, can be NULL +{ + int row = mouserow; + int col = mousecol; + int scol; + win_T *wp; + char_u *lbuf; + linenr_T lnum; + + *textp = NULL; + wp = mouse_find_win(&row, &col, FAIL_POPUP); + if (wp == NULL || row < 0 || row >= wp->w_height || col >= wp->w_width) + return FAIL; + + // Found a window and the cursor is in the text. Now find the line + // number. + if (mouse_comp_pos(wp, &row, &col, &lnum, NULL)) + return FAIL; // position is below the last line + + // Not past end of the file. + lbuf = ml_get_buf(wp->w_buffer, lnum, FALSE); + if (col > win_linetabsize(wp, lnum, lbuf, (colnr_T)MAXCOL)) + return FAIL; // past end of line + + // Not past end of line. + if (getword) + { + // For Netbeans we get the relevant part of the line + // instead of the whole line. + int len; + pos_T *spos = NULL, *epos = NULL; + + if (VIsual_active) + { + if (LT_POS(VIsual, curwin->w_cursor)) + { + spos = &VIsual; + epos = &curwin->w_cursor; + } + else + { + spos = &curwin->w_cursor; + epos = &VIsual; + } + } + + col = vcol2col(wp, lnum, col, NULL); + scol = col; + + if (VIsual_active + && wp->w_buffer == curwin->w_buffer + && (lnum == spos->lnum + ? col >= (int)spos->col + : lnum > spos->lnum) + && (lnum == epos->lnum + ? col <= (int)epos->col + : lnum < epos->lnum)) + { + // Visual mode and pointing to the line with the + // Visual selection: return selected text, with a + // maximum of one line. + if (spos->lnum != epos->lnum || spos->col == epos->col) + return FAIL; + + lbuf = ml_get_buf(curwin->w_buffer, VIsual.lnum, FALSE); + len = epos->col - spos->col; + if (*p_sel != 'e') + len += mb_ptr2len(lbuf + epos->col); + lbuf = vim_strnsave(lbuf + spos->col, len); + lnum = spos->lnum; + col = spos->col; + scol = col; + } + else + { + // Find the word under the cursor. + ++emsg_off; + len = find_ident_at_pos(wp, lnum, (colnr_T)col, + &lbuf, &scol, flags); + --emsg_off; + if (len == 0) + return FAIL; + lbuf = vim_strnsave(lbuf, len); + } + } + else + scol = col; + + if (winp != NULL) + *winp = wp; + if (lnump != NULL) + *lnump = lnum; + *textp = lbuf; + if (colp != NULL) + *colp = col; + if (startcolp != NULL) + *startcolp = scol; + + return OK; +} +#endif + +#if defined(FEAT_BEVAL) || defined(PROTO) + +/* + * Get the text and position to be evaluated for "beval". + * If "getword" is TRUE the returned text is not the whole line but the + * relevant word in allocated memory. + * Returns OK or FAIL. + */ + int +get_beval_info( + BalloonEval *beval, + int getword, + win_T **winp, + linenr_T *lnump, + char_u **textp, + int *colp) +{ + int row = mouse_row; + int col = mouse_col; + +# ifdef FEAT_BEVAL_GUI + if (gui.in_use) + { + row = Y_2_ROW(beval->y); + col = X_2_COL(beval->x); + } +# endif + if (find_word_under_cursor(row, col, getword, + FIND_IDENT + FIND_STRING + FIND_EVAL, + winp, lnump, textp, colp, NULL) == OK) + { +# ifdef FEAT_VARTABS + vim_free(beval->vts); + beval->vts = tabstop_copy((*winp)->w_buffer->b_p_vts_array); + if ((*winp)->w_buffer->b_p_vts_array != NULL && beval->vts == NULL) + { + if (getword) + vim_free(*textp); + return FAIL; + } +# endif + beval->ts = (*winp)->w_buffer->b_p_ts; + return OK; + } + + return FAIL; +} + +/* + * Show a balloon with "mesg" or "list". + * Hide the balloon when both are NULL. + */ + void +post_balloon(BalloonEval *beval UNUSED, char_u *mesg, list_T *list UNUSED) +{ +# ifdef FEAT_BEVAL_TERM +# ifdef FEAT_GUI + if (!gui.in_use) +# endif + ui_post_balloon(mesg, list); +# endif +# ifdef FEAT_BEVAL_GUI + if (gui.in_use) + // GUI can't handle a list + gui_mch_post_balloon(beval, mesg); +# endif +} + +/* + * Returns TRUE if the balloon eval has been enabled: + * 'ballooneval' for the GUI and 'balloonevalterm' for the terminal. + * Also checks if the screen isn't scrolled up. + */ + int +can_use_beval(void) +{ + return (0 +#ifdef FEAT_BEVAL_GUI + || (gui.in_use && p_beval) +#endif +#ifdef FEAT_BEVAL_TERM + || ( +# ifdef FEAT_GUI + !gui.in_use && +# endif + p_bevalterm) +#endif + ) && msg_scrolled == 0; +} + +# ifdef FEAT_EVAL +/* + * Evaluate the expression 'bexpr' and set the text in the balloon 'beval'. + */ + static void +bexpr_eval( + BalloonEval *beval, + char_u *bexpr, + win_T *wp, + linenr_T lnum, + int col, + char_u *text) +{ + win_T *cw; + long winnr = 0; + buf_T *save_curbuf; + int use_sandbox; + static char_u *result = NULL; + size_t len; + + sctx_T save_sctx = current_sctx; + + // Convert window pointer to number. + for (cw = firstwin; cw != wp; cw = cw->w_next) + ++winnr; + + set_vim_var_nr(VV_BEVAL_BUFNR, (long)wp->w_buffer->b_fnum); + set_vim_var_nr(VV_BEVAL_WINNR, winnr); + set_vim_var_nr(VV_BEVAL_WINID, wp->w_id); + set_vim_var_nr(VV_BEVAL_LNUM, (long)lnum); + set_vim_var_nr(VV_BEVAL_COL, (long)(col + 1)); + set_vim_var_string(VV_BEVAL_TEXT, text, -1); + vim_free(text); + + /* + * Temporarily change the curbuf, so that we can determine whether + * the buffer-local balloonexpr option was set insecurely. + */ + save_curbuf = curbuf; + curbuf = wp->w_buffer; + use_sandbox = was_set_insecurely((char_u *)"balloonexpr", + *curbuf->b_p_bexpr == NUL ? 0 : OPT_LOCAL); + curbuf = save_curbuf; + if (use_sandbox) + ++sandbox; + ++textlock; + + if (bexpr == p_bexpr) + { + sctx_T *sp = get_option_sctx("balloonexpr"); + + if (sp != NULL) + current_sctx = *sp; + } + else + current_sctx = curbuf->b_p_script_ctx[BV_BEXPR]; + + vim_free(result); + result = eval_to_string(bexpr, TRUE, TRUE); + + // Remove one trailing newline, it is added when the result was a + // list and it's hardly ever useful. If the user really wants a + // trailing newline he can add two and one remains. + if (result != NULL) + { + len = STRLEN(result); + if (len > 0 && result[len - 1] == NL) + result[len - 1] = NUL; + } + + if (use_sandbox) + --sandbox; + --textlock; + current_sctx = save_sctx; + + set_vim_var_string(VV_BEVAL_TEXT, NULL, -1); + if (result != NULL && result[0] != NUL) + post_balloon(beval, result, NULL); + + // The 'balloonexpr' evaluation may show something on the screen + // that requires a screen update. + if (must_redraw) + redraw_after_callback(FALSE, FALSE); +} +# endif + +/* + * Common code, invoked when the mouse is resting for a moment. + */ + void +general_beval_cb(BalloonEval *beval, int state UNUSED) +{ +#ifdef FEAT_EVAL + win_T *wp; + int col; + linenr_T lnum; + char_u *text; + char_u *bexpr; +#endif + static int recursive = FALSE; + + // Don't do anything when 'ballooneval' is off, messages scrolled the + // windows up or we have no beval area. + if (!can_use_beval() || beval == NULL) + return; + + // Don't do this recursively. Happens when the expression evaluation + // takes a long time and invokes something that checks for CTRL-C typed. + if (recursive) + return; + recursive = TRUE; + +#ifdef FEAT_EVAL + if (get_beval_info(beval, TRUE, &wp, &lnum, &text, &col) == OK) + { + bexpr = (*wp->w_buffer->b_p_bexpr == NUL) ? p_bexpr + : wp->w_buffer->b_p_bexpr; + if (*bexpr != NUL) + { + bexpr_eval(beval, bexpr, wp, lnum, col, text); + recursive = FALSE; + return; + } + } +#endif +#ifdef FEAT_NETBEANS_INTG + if (bevalServers & BEVAL_NETBEANS) + netbeans_beval_cb(beval, state); +#endif + + recursive = FALSE; +} + +#endif diff --git a/userland/apps/vim/src/beval.h b/userland/apps/vim/src/beval.h new file mode 100644 index 00000000..43323896 --- /dev/null +++ b/userland/apps/vim/src/beval.h @@ -0,0 +1,95 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * Visual Workshop integration by Gordon Prieur + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +#if !defined(BEVAL__H) && (defined(FEAT_BEVAL) || defined(PROTO)) +#define BEVAL__H + +#ifdef FEAT_GUI_GTK +# ifdef USE_GTK3 +# include +# else +# include +# endif +#else +# if defined(FEAT_GUI_X11) +# include +# endif +#endif + +typedef enum +{ + ShS_NEUTRAL, // nothing showing or pending + ShS_PENDING, // data requested from debugger + ShS_UPDATE_PENDING, // switching information displayed + ShS_SHOWING // the balloon is being displayed +} BeState; + +typedef struct BalloonEvalStruct +{ +#ifdef FEAT_BEVAL_GUI +# ifdef FEAT_GUI_GTK + GtkWidget *target; // widget we are monitoring + GtkWidget *balloonShell; + GtkWidget *balloonLabel; + unsigned int timerID; // timer for run + BeState showState; // tells us what's currently going on + int x; + int y; + unsigned int state; // Button/Modifier key state +# else +# if !defined(FEAT_GUI_MSWIN) + Widget target; // widget we are monitoring + Widget balloonShell; + Widget balloonLabel; + XtIntervalId timerID; // timer for run + BeState showState; // tells us what's currently going on + XtAppContext appContext; // used in event handler + Position x; + Position y; + Position x_root; + Position y_root; + int state; // Button/Modifier key state +# else + HWND target; + HWND balloon; + int x; + int y; + BeState showState; // tells us what's currently going on +# endif +# endif +# if !defined(FEAT_GUI_GTK) && !defined(FEAT_GUI_MSWIN) + Dimension screen_width; // screen width in pixels + Dimension screen_height; // screen height in pixels +# endif + void (*msgCB)(struct BalloonEvalStruct *, int); + void *clientData; // For callback +#endif + + int ts; // tabstop setting for this buffer +#ifdef FEAT_VARTABS + int *vts; // vartabstop setting for this buffer +#endif + char_u *msg; // allocated: current text +#ifdef FEAT_GUI_MSWIN + void *tofree; +#endif +#ifdef FEAT_GUI_HAIKU + int x; + int y; +#endif +} BalloonEval; + +#define EVAL_OFFSET_X 15 // displacement of beval topleft corner from pointer +#define EVAL_OFFSET_Y 10 + +#ifdef FEAT_BEVAL_GUI +# include "gui_beval.pro" +#endif + +#endif // BEVAL__H and FEAT_BEVAL_GUI diff --git a/userland/apps/vim/src/blob.c b/userland/apps/vim/src/blob.c new file mode 100644 index 00000000..c5d7eaed --- /dev/null +++ b/userland/apps/vim/src/blob.c @@ -0,0 +1,860 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * blob.c: Blob support by Yasuhiro Matsumoto + */ + +#include "vim.h" + +#if defined(FEAT_EVAL) || defined(PROTO) + +/* + * Allocate an empty blob. + * Caller should take care of the reference count. + */ + blob_T * +blob_alloc(void) +{ + blob_T *blob = ALLOC_CLEAR_ONE_ID(blob_T, aid_blob_alloc); + + if (blob != NULL) + ga_init2(&blob->bv_ga, 1, 100); + return blob; +} + +/* + * Allocate an empty blob for a return value, with reference count set. + * Returns OK or FAIL. + */ + int +rettv_blob_alloc(typval_T *rettv) +{ + blob_T *b = blob_alloc(); + + if (b == NULL) + return FAIL; + + rettv_blob_set(rettv, b); + return OK; +} + +/* + * Set a blob as the return value. + */ + void +rettv_blob_set(typval_T *rettv, blob_T *b) +{ + rettv->v_type = VAR_BLOB; + rettv->vval.v_blob = b; + if (b != NULL) + ++b->bv_refcount; +} + + int +blob_copy(blob_T *from, typval_T *to) +{ + int len; + + to->v_type = VAR_BLOB; + to->v_lock = 0; + if (from == NULL) + { + to->vval.v_blob = NULL; + return OK; + } + + if (rettv_blob_alloc(to) == FAIL) + return FAIL; + + len = from->bv_ga.ga_len; + if (len > 0) + { + to->vval.v_blob->bv_ga.ga_data = + vim_memsave(from->bv_ga.ga_data, len); + if (to->vval.v_blob->bv_ga.ga_data == NULL) + len = 0; + } + to->vval.v_blob->bv_ga.ga_len = len; + to->vval.v_blob->bv_ga.ga_maxlen = len; + + return OK; +} + + void +blob_free(blob_T *b) +{ + ga_clear(&b->bv_ga); + vim_free(b); +} + +/* + * Unreference a blob: decrement the reference count and free it when it + * becomes zero. + */ + void +blob_unref(blob_T *b) +{ + if (b != NULL && --b->bv_refcount <= 0) + blob_free(b); +} + +/* + * Get the length of data. + */ + long +blob_len(blob_T *b) +{ + if (b == NULL) + return 0L; + return b->bv_ga.ga_len; +} + +/* + * Get byte "idx" in blob "b". + * Caller must check that "idx" is valid. + */ + int +blob_get(blob_T *b, int idx) +{ + return ((char_u*)b->bv_ga.ga_data)[idx]; +} + +/* + * Store one byte "byte" in blob "blob" at "idx". + * Caller must make sure that "idx" is valid. + */ + void +blob_set(blob_T *blob, int idx, int byte) +{ + ((char_u*)blob->bv_ga.ga_data)[idx] = byte; +} + +/* + * Store one byte "byte" in blob "blob" at "idx". + * Append one byte if needed. + */ + void +blob_set_append(blob_T *blob, int idx, int byte) +{ + garray_T *gap = &blob->bv_ga; + + // Allow for appending a byte. Setting a byte beyond + // the end is an error otherwise. + if (idx < gap->ga_len + || (idx == gap->ga_len && ga_grow(gap, 1) == OK)) + { + blob_set(blob, idx, byte); + if (idx == gap->ga_len) + ++gap->ga_len; + } +} + +/* + * Return TRUE when two blobs have exactly the same values. + */ + int +blob_equal( + blob_T *b1, + blob_T *b2) +{ + int i; + int len1 = blob_len(b1); + int len2 = blob_len(b2); + + // empty and NULL are considered the same + if (len1 == 0 && len2 == 0) + return TRUE; + if (b1 == b2) + return TRUE; + if (len1 != len2) + return FALSE; + + for (i = 0; i < b1->bv_ga.ga_len; i++) + if (blob_get(b1, i) != blob_get(b2, i)) return FALSE; + return TRUE; +} + +/* + * Read blob from file "fd". + * Caller has allocated a blob in "rettv". + * Return OK or FAIL. + */ + int +read_blob(FILE *fd, typval_T *rettv, off_T offset, off_T size_arg) +{ + blob_T *blob = rettv->vval.v_blob; + struct stat st; + int whence; + off_T size = size_arg; + + if (fstat(fileno(fd), &st) < 0) + return FAIL; // can't read the file, error + + if (offset >= 0) + { + // The size defaults to the whole file. If a size is given it is + // limited to not go past the end of the file. + if (size == -1 || (size > st.st_size - offset +#ifdef S_ISCHR + && !S_ISCHR(st.st_mode) +#endif + )) + // size may become negative, checked below + size = st.st_size - offset; + whence = SEEK_SET; + } + else + { + // limit the offset to not go before the start of the file + if (-offset > st.st_size +#ifdef S_ISCHR + && !S_ISCHR(st.st_mode) +#endif + ) + offset = -st.st_size; + // Size defaults to reading until the end of the file. + if (size == -1 || size > -offset) + size = -offset; + whence = SEEK_END; + } + if (size <= 0) + return OK; + if (offset != 0 && vim_fseek(fd, offset, whence) != 0) + return OK; + + if (ga_grow(&blob->bv_ga, (int)size) == FAIL) + return FAIL; + blob->bv_ga.ga_len = (int)size; + if (fread(blob->bv_ga.ga_data, 1, blob->bv_ga.ga_len, fd) + < (size_t)blob->bv_ga.ga_len) + { + // An empty blob is returned on error. + blob_free(rettv->vval.v_blob); + rettv->vval.v_blob = NULL; + return FAIL; + } + return OK; +} + +/* + * Write "blob" to file "fd". + * Return OK or FAIL. + */ + int +write_blob(FILE *fd, blob_T *blob) +{ + if (fwrite(blob->bv_ga.ga_data, 1, blob->bv_ga.ga_len, fd) + < (size_t)blob->bv_ga.ga_len) + { + emsg(_(e_error_while_writing)); + return FAIL; + } + return OK; +} + +/* + * Convert a blob to a readable form: "0z00112233.44556677.8899" + */ + char_u * +blob2string(blob_T *blob, char_u **tofree, char_u *numbuf) +{ + int i; + garray_T ga; + + if (blob == NULL) + { + *tofree = NULL; + return (char_u *)"0z"; + } + + // Store bytes in the growarray. + ga_init2(&ga, 1, 4000); + ga_concat(&ga, (char_u *)"0z"); + for (i = 0; i < blob_len(blob); i++) + { + if (i > 0 && (i & 3) == 0) + ga_concat(&ga, (char_u *)"."); + vim_snprintf((char *)numbuf, NUMBUFLEN, "%02X", blob_get(blob, i)); + ga_concat(&ga, numbuf); + } + ga_append(&ga, NUL); // append a NUL at the end + *tofree = ga.ga_data; + return *tofree; +} + +/* + * Convert a string variable, in the format of blob2string(), to a blob. + * Return NULL when conversion failed. + */ + blob_T * +string2blob(char_u *str) +{ + blob_T *blob = blob_alloc(); + char_u *s = str; + + if (blob == NULL) + return NULL; + if (s[0] != '0' || (s[1] != 'z' && s[1] != 'Z')) + goto failed; + s += 2; + while (vim_isxdigit(*s)) + { + if (!vim_isxdigit(s[1])) + goto failed; + ga_append(&blob->bv_ga, (hex2nr(s[0]) << 4) + hex2nr(s[1])); + s += 2; + if (*s == '.' && vim_isxdigit(s[1])) + ++s; + } + if (*skipwhite(s) != NUL) + goto failed; // text after final digit + + ++blob->bv_refcount; + return blob; + +failed: + blob_free(blob); + return NULL; +} + +/* + * Returns a slice of 'blob' from index 'n1' to 'n2' in 'rettv'. The length of + * the blob is 'len'. Returns an empty blob if the indexes are out of range. + */ + static int +blob_slice( + blob_T *blob, + long len, + varnumber_T n1, + varnumber_T n2, + int exclusive, + typval_T *rettv) +{ + if (n1 < 0) + { + n1 = len + n1; + if (n1 < 0) + n1 = 0; + } + if (n2 < 0) + n2 = len + n2; + else if (n2 >= len) + n2 = len - (exclusive ? 0 : 1); + if (exclusive) + --n2; + if (n1 >= len || n2 < 0 || n1 > n2) + { + clear_tv(rettv); + rettv->v_type = VAR_BLOB; + rettv->vval.v_blob = NULL; + } + else + { + blob_T *new_blob = blob_alloc(); + long i; + + if (new_blob != NULL) + { + if (ga_grow(&new_blob->bv_ga, n2 - n1 + 1) == FAIL) + { + blob_free(new_blob); + return FAIL; + } + new_blob->bv_ga.ga_len = n2 - n1 + 1; + for (i = n1; i <= n2; i++) + blob_set(new_blob, i - n1, blob_get(blob, i)); + + clear_tv(rettv); + rettv_blob_set(rettv, new_blob); + } + } + + return OK; +} + +/* + * Return the byte value in 'blob' at index 'idx' in 'rettv'. If the index is + * too big or negative that is an error. The length of the blob is 'len'. + */ + static int +blob_index( + blob_T *blob, + int len, + varnumber_T idx, + typval_T *rettv) +{ + // The resulting variable is a byte value. + // If the index is too big or negative that is an error. + if (idx < 0) + idx = len + idx; + if (idx < len && idx >= 0) + { + int v = blob_get(blob, idx); + + clear_tv(rettv); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = v; + } + else + { + semsg(_(e_blob_index_out_of_range_nr), idx); + return FAIL; + } + + return OK; +} + + int +blob_slice_or_index( + blob_T *blob, + int is_range, + varnumber_T n1, + varnumber_T n2, + int exclusive, + typval_T *rettv) +{ + long len = blob_len(blob); + + if (is_range) + return blob_slice(blob, len, n1, n2, exclusive, rettv); + else + return blob_index(blob, len, n1, rettv); + return OK; +} + +/* + * Check if "n1"- is a valid index for a blobl with length "bloblen". + */ + int +check_blob_index(long bloblen, varnumber_T n1, int quiet) +{ + if (n1 < 0 || n1 > bloblen) + { + if (!quiet) + semsg(_(e_blob_index_out_of_range_nr), n1); + return FAIL; + } + return OK; +} + +/* + * Check if "n1"-"n2" is a valid range for a blob with length "bloblen". + */ + int +check_blob_range(long bloblen, varnumber_T n1, varnumber_T n2, int quiet) +{ + if (n2 < 0 || n2 >= bloblen || n2 < n1) + { + if (!quiet) + semsg(_(e_blob_index_out_of_range_nr), n2); + return FAIL; + } + return OK; +} + +/* + * Set bytes "n1" to "n2" (inclusive) in "dest" to the value of "src". + * Caller must make sure "src" is a blob. + * Returns FAIL if the number of bytes does not match. + */ + int +blob_set_range(blob_T *dest, long n1, long n2, typval_T *src) +{ + int il, ir; + + if (n2 - n1 + 1 != blob_len(src->vval.v_blob)) + { + emsg(_(e_blob_value_does_not_have_right_number_of_bytes)); + return FAIL; + } + + ir = 0; + for (il = n1; il <= n2; il++) + blob_set(dest, il, blob_get(src->vval.v_blob, ir++)); + return OK; +} + +/* + * "add(blob, item)" function + */ + void +blob_add(typval_T *argvars, typval_T *rettv) +{ + blob_T *b = argvars[0].vval.v_blob; + int error = FALSE; + varnumber_T n; + + if (b == NULL) + { + if (in_vim9script()) + emsg(_(e_cannot_add_to_null_blob)); + return; + } + + if (value_check_lock(b->bv_lock, (char_u *)N_("add() argument"), TRUE)) + return; + + n = tv_get_number_chk(&argvars[1], &error); + if (error) + return; + + ga_append(&b->bv_ga, (int)n); + copy_tv(&argvars[0], rettv); +} + +/* + * "remove({blob}, {idx} [, {end}])" function + */ + void +blob_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg) +{ + blob_T *b = argvars[0].vval.v_blob; + blob_T *newblob; + int error = FALSE; + long idx; + long end; + int len; + char_u *p; + + if (b != NULL && value_check_lock(b->bv_lock, arg_errmsg, TRUE)) + return; + + idx = (long)tv_get_number_chk(&argvars[1], &error); + if (error) + return; + + len = blob_len(b); + + if (idx < 0) + // count from the end + idx = len + idx; + if (idx < 0 || idx >= len) + { + semsg(_(e_blob_index_out_of_range_nr), idx); + return; + } + if (argvars[2].v_type == VAR_UNKNOWN) + { + // Remove one item, return its value. + p = (char_u *)b->bv_ga.ga_data; + rettv->vval.v_number = (varnumber_T) *(p + idx); + mch_memmove(p + idx, p + idx + 1, (size_t)len - idx - 1); + --b->bv_ga.ga_len; + return; + } + + // Remove range of items, return blob with values. + end = (long)tv_get_number_chk(&argvars[2], &error); + if (error) + return; + if (end < 0) + // count from the end + end = len + end; + if (end >= len || idx > end) + { + semsg(_(e_blob_index_out_of_range_nr), end); + return; + } + newblob = blob_alloc(); + if (newblob == NULL) + return; + newblob->bv_ga.ga_len = end - idx + 1; + if (ga_grow(&newblob->bv_ga, end - idx + 1) == FAIL) + { + vim_free(newblob); + return; + } + p = (char_u *)b->bv_ga.ga_data; + mch_memmove((char_u *)newblob->bv_ga.ga_data, p + idx, + (size_t)(end - idx + 1)); + ++newblob->bv_refcount; + rettv->v_type = VAR_BLOB; + rettv->vval.v_blob = newblob; + + if (len - end - 1 > 0) + mch_memmove(p + idx, p + end + 1, (size_t)(len - end - 1)); + b->bv_ga.ga_len -= end - idx + 1; +} + +/* + * Implementation of map() and filter() for a Blob. Apply "expr" to every + * number in Blob "blob_arg" and return the result in "rettv". + */ + void +blob_filter_map( + blob_T *blob_arg, + filtermap_T filtermap, + typval_T *expr, + char_u *arg_errmsg, + typval_T *rettv) +{ + blob_T *b = blob_arg; + int i; + typval_T tv; + varnumber_T val; + blob_T *b_ret; + int idx = 0; + int rem; + typval_T newtv; + funccall_T *fc; + + if (filtermap == FILTERMAP_MAPNEW) + { + rettv->v_type = VAR_BLOB; + rettv->vval.v_blob = NULL; + } + if (b == NULL || (filtermap == FILTERMAP_FILTER + && value_check_lock(b->bv_lock, arg_errmsg, TRUE))) + return; + + b_ret = b; + if (filtermap == FILTERMAP_MAPNEW) + { + if (blob_copy(b, rettv) == FAIL) + return; + b_ret = rettv->vval.v_blob; + } + + // set_vim_var_nr() doesn't set the type + set_vim_var_type(VV_KEY, VAR_NUMBER); + + int prev_lock = b->bv_lock; + if (b->bv_lock == 0) + b->bv_lock = VAR_LOCKED; + + // Create one funccall_T for all eval_expr_typval() calls. + fc = eval_expr_get_funccal(expr, &newtv); + + for (i = 0; i < b->bv_ga.ga_len; i++) + { + tv.v_type = VAR_NUMBER; + val = blob_get(b, i); + tv.vval.v_number = val; + set_vim_var_nr(VV_KEY, idx); + if (filter_map_one(&tv, expr, filtermap, fc, &newtv, &rem) == FAIL + || did_emsg) + break; + if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL) + { + clear_tv(&newtv); + emsg(_(e_invalid_operation_for_blob)); + break; + } + if (filtermap != FILTERMAP_FILTER) + { + if (newtv.vval.v_number != val) + blob_set(b_ret, i, newtv.vval.v_number); + } + else if (rem) + { + char_u *p = (char_u *)blob_arg->bv_ga.ga_data; + + mch_memmove(p + i, p + i + 1, + (size_t)b->bv_ga.ga_len - i - 1); + --b->bv_ga.ga_len; + --i; + } + ++idx; + } + + b->bv_lock = prev_lock; + if (fc != NULL) + remove_funccal(); +} + +/* + * "insert(blob, {item} [, {idx}])" function + */ + void +blob_insert_func(typval_T *argvars, typval_T *rettv) +{ + blob_T *b = argvars[0].vval.v_blob; + long before = 0; + int error = FALSE; + int val, len; + char_u *p; + + if (b == NULL) + { + if (in_vim9script()) + emsg(_(e_cannot_add_to_null_blob)); + return; + } + + if (value_check_lock(b->bv_lock, (char_u *)N_("insert() argument"), TRUE)) + return; + + len = blob_len(b); + if (argvars[2].v_type != VAR_UNKNOWN) + { + before = (long)tv_get_number_chk(&argvars[2], &error); + if (error) + return; // type error; errmsg already given + if (before < 0 || before > len) + { + semsg(_(e_invalid_argument_str), tv_get_string(&argvars[2])); + return; + } + } + val = tv_get_number_chk(&argvars[1], &error); + if (error) + return; + if (val < 0 || val > 255) + { + semsg(_(e_invalid_argument_str), tv_get_string(&argvars[1])); + return; + } + + if (ga_grow(&b->bv_ga, 1) == FAIL) + return; + p = (char_u *)b->bv_ga.ga_data; + mch_memmove(p + before + 1, p + before, (size_t)len - before); + *(p + before) = val; + ++b->bv_ga.ga_len; + + copy_tv(&argvars[0], rettv); +} + +/* + * Implementation of reduce() for Blob "argvars[0]" using the function "expr" + * starting with the optional initial value "argvars[2]" and return the result + * in "rettv". + */ + void +blob_reduce( + typval_T *argvars, + typval_T *expr, + typval_T *rettv) +{ + blob_T *b = argvars[0].vval.v_blob; + int called_emsg_start = called_emsg; + int r; + typval_T initial; + typval_T argv[3]; + int i; + + if (argvars[2].v_type == VAR_UNKNOWN) + { + if (b == NULL || b->bv_ga.ga_len == 0) + { + semsg(_(e_reduce_of_an_empty_str_with_no_initial_value), "Blob"); + return; + } + initial.v_type = VAR_NUMBER; + initial.vval.v_number = blob_get(b, 0); + i = 1; + } + else if (check_for_number_arg(argvars, 2) == FAIL) + return; + else + { + initial = argvars[2]; + i = 0; + } + + copy_tv(&initial, rettv); + if (b == NULL) + return; + + for ( ; i < b->bv_ga.ga_len; i++) + { + argv[0] = *rettv; + argv[1].v_type = VAR_NUMBER; + argv[1].vval.v_number = blob_get(b, i); + + r = eval_expr_typval(expr, TRUE, argv, 2, NULL, rettv); + + clear_tv(&argv[0]); + if (r == FAIL || called_emsg != called_emsg_start) + return; + } +} + +/* + * "reverse({blob})" function + */ + void +blob_reverse(blob_T *b, typval_T *rettv) +{ + int i, len = blob_len(b); + + for (i = 0; i < len / 2; i++) + { + int tmp = blob_get(b, i); + + blob_set(b, i, blob_get(b, len - i - 1)); + blob_set(b, len - i - 1, tmp); + } + rettv_blob_set(rettv, b); +} + +/* + * blob2list() function + */ + void +f_blob2list(typval_T *argvars, typval_T *rettv) +{ + blob_T *blob; + list_T *l; + int i; + + if (rettv_list_alloc(rettv) == FAIL) + return; + + if (check_for_blob_arg(argvars, 0) == FAIL) + return; + + blob = argvars->vval.v_blob; + l = rettv->vval.v_list; + for (i = 0; i < blob_len(blob); i++) + list_append_number(l, blob_get(blob, i)); +} + +/* + * list2blob() function + */ + void +f_list2blob(typval_T *argvars, typval_T *rettv) +{ + list_T *l; + listitem_T *li; + blob_T *blob; + + if (rettv_blob_alloc(rettv) == FAIL) + return; + blob = rettv->vval.v_blob; + + if (check_for_list_arg(argvars, 0) == FAIL) + return; + + l = argvars->vval.v_list; + if (l == NULL) + return; + + CHECK_LIST_MATERIALIZE(l); + FOR_ALL_LIST_ITEMS(l, li) + { + int error; + varnumber_T n; + + error = FALSE; + n = tv_get_number_chk(&li->li_tv, &error); + if (error == TRUE || n < 0 || n > 255) + { + if (!error) + semsg(_(e_invalid_value_for_blob_nr), n); + ga_clear(&blob->bv_ga); + return; + } + ga_append(&blob->bv_ga, n); + } +} + +#endif // defined(FEAT_EVAL) diff --git a/userland/apps/vim/src/blowfish.c b/userland/apps/vim/src/blowfish.c new file mode 100644 index 00000000..95d14cca --- /dev/null +++ b/userland/apps/vim/src/blowfish.c @@ -0,0 +1,685 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + * + * Blowfish encryption for Vim; in Blowfish cipher feedback mode. + * Contributed by Mohsin Ahmed, http://www.cs.albany.edu/~mosh + * Based on http://www.schneier.com/blowfish.html by Bruce Schneier. + * + * There are two variants: + * - The old one "blowfish" has a flaw which makes it much easier to crack the + * key. To see this, make a text file with one line of 1000 "x" characters + * and write it encrypted. Use "xxd" to inspect the bytes in the file. You + * will see that a block of 8 bytes repeats 8 times. + * - The new one "blowfish2" is better. It uses an 8 byte CFB to avoid the + * repeats. + */ + +#include "vim.h" + +#if defined(FEAT_CRYPT) || defined(PROTO) + +#define BF_BLOCK 8 +#define BF_BLOCK_MASK 7 +#define BF_MAX_CFB_LEN (8 * BF_BLOCK) + +typedef union { + UINT32_T ul[2]; + char_u uc[8]; +} block8; + +#if defined(MSWIN) + // MS-Windows is always little endian +#else +# ifdef HAVE_CONFIG_H + // in configure.ac AC_C_BIGENDIAN() defines WORDS_BIGENDIAN when needed +# else +# error Please change this code to define WORDS_BIGENDIAN for big-endian machines. +# endif +#endif + +// The state of encryption, referenced by cryptstate_T. +typedef struct { + UINT32_T pax[18]; // P-array + UINT32_T sbx[4][256]; // S-boxes + int randbyte_offset; + int update_offset; + char_u cfb_buffer[BF_MAX_CFB_LEN]; // up to 64 bytes used + int cfb_len; // size of cfb_buffer actually used +} bf_state_T; + + +// Blowfish code +static UINT32_T pax_init[18] = { + 0x243f6a88u, 0x85a308d3u, 0x13198a2eu, + 0x03707344u, 0xa4093822u, 0x299f31d0u, + 0x082efa98u, 0xec4e6c89u, 0x452821e6u, + 0x38d01377u, 0xbe5466cfu, 0x34e90c6cu, + 0xc0ac29b7u, 0xc97c50ddu, 0x3f84d5b5u, + 0xb5470917u, 0x9216d5d9u, 0x8979fb1bu +}; + +static UINT32_T sbx_init[4][256] = { + {0xd1310ba6u, 0x98dfb5acu, 0x2ffd72dbu, 0xd01adfb7u, + 0xb8e1afedu, 0x6a267e96u, 0xba7c9045u, 0xf12c7f99u, + 0x24a19947u, 0xb3916cf7u, 0x0801f2e2u, 0x858efc16u, + 0x636920d8u, 0x71574e69u, 0xa458fea3u, 0xf4933d7eu, + 0x0d95748fu, 0x728eb658u, 0x718bcd58u, 0x82154aeeu, + 0x7b54a41du, 0xc25a59b5u, 0x9c30d539u, 0x2af26013u, + 0xc5d1b023u, 0x286085f0u, 0xca417918u, 0xb8db38efu, + 0x8e79dcb0u, 0x603a180eu, 0x6c9e0e8bu, 0xb01e8a3eu, + 0xd71577c1u, 0xbd314b27u, 0x78af2fdau, 0x55605c60u, + 0xe65525f3u, 0xaa55ab94u, 0x57489862u, 0x63e81440u, + 0x55ca396au, 0x2aab10b6u, 0xb4cc5c34u, 0x1141e8ceu, + 0xa15486afu, 0x7c72e993u, 0xb3ee1411u, 0x636fbc2au, + 0x2ba9c55du, 0x741831f6u, 0xce5c3e16u, 0x9b87931eu, + 0xafd6ba33u, 0x6c24cf5cu, 0x7a325381u, 0x28958677u, + 0x3b8f4898u, 0x6b4bb9afu, 0xc4bfe81bu, 0x66282193u, + 0x61d809ccu, 0xfb21a991u, 0x487cac60u, 0x5dec8032u, + 0xef845d5du, 0xe98575b1u, 0xdc262302u, 0xeb651b88u, + 0x23893e81u, 0xd396acc5u, 0x0f6d6ff3u, 0x83f44239u, + 0x2e0b4482u, 0xa4842004u, 0x69c8f04au, 0x9e1f9b5eu, + 0x21c66842u, 0xf6e96c9au, 0x670c9c61u, 0xabd388f0u, + 0x6a51a0d2u, 0xd8542f68u, 0x960fa728u, 0xab5133a3u, + 0x6eef0b6cu, 0x137a3be4u, 0xba3bf050u, 0x7efb2a98u, + 0xa1f1651du, 0x39af0176u, 0x66ca593eu, 0x82430e88u, + 0x8cee8619u, 0x456f9fb4u, 0x7d84a5c3u, 0x3b8b5ebeu, + 0xe06f75d8u, 0x85c12073u, 0x401a449fu, 0x56c16aa6u, + 0x4ed3aa62u, 0x363f7706u, 0x1bfedf72u, 0x429b023du, + 0x37d0d724u, 0xd00a1248u, 0xdb0fead3u, 0x49f1c09bu, + 0x075372c9u, 0x80991b7bu, 0x25d479d8u, 0xf6e8def7u, + 0xe3fe501au, 0xb6794c3bu, 0x976ce0bdu, 0x04c006bau, + 0xc1a94fb6u, 0x409f60c4u, 0x5e5c9ec2u, 0x196a2463u, + 0x68fb6fafu, 0x3e6c53b5u, 0x1339b2ebu, 0x3b52ec6fu, + 0x6dfc511fu, 0x9b30952cu, 0xcc814544u, 0xaf5ebd09u, + 0xbee3d004u, 0xde334afdu, 0x660f2807u, 0x192e4bb3u, + 0xc0cba857u, 0x45c8740fu, 0xd20b5f39u, 0xb9d3fbdbu, + 0x5579c0bdu, 0x1a60320au, 0xd6a100c6u, 0x402c7279u, + 0x679f25feu, 0xfb1fa3ccu, 0x8ea5e9f8u, 0xdb3222f8u, + 0x3c7516dfu, 0xfd616b15u, 0x2f501ec8u, 0xad0552abu, + 0x323db5fau, 0xfd238760u, 0x53317b48u, 0x3e00df82u, + 0x9e5c57bbu, 0xca6f8ca0u, 0x1a87562eu, 0xdf1769dbu, + 0xd542a8f6u, 0x287effc3u, 0xac6732c6u, 0x8c4f5573u, + 0x695b27b0u, 0xbbca58c8u, 0xe1ffa35du, 0xb8f011a0u, + 0x10fa3d98u, 0xfd2183b8u, 0x4afcb56cu, 0x2dd1d35bu, + 0x9a53e479u, 0xb6f84565u, 0xd28e49bcu, 0x4bfb9790u, + 0xe1ddf2dau, 0xa4cb7e33u, 0x62fb1341u, 0xcee4c6e8u, + 0xef20cadau, 0x36774c01u, 0xd07e9efeu, 0x2bf11fb4u, + 0x95dbda4du, 0xae909198u, 0xeaad8e71u, 0x6b93d5a0u, + 0xd08ed1d0u, 0xafc725e0u, 0x8e3c5b2fu, 0x8e7594b7u, + 0x8ff6e2fbu, 0xf2122b64u, 0x8888b812u, 0x900df01cu, + 0x4fad5ea0u, 0x688fc31cu, 0xd1cff191u, 0xb3a8c1adu, + 0x2f2f2218u, 0xbe0e1777u, 0xea752dfeu, 0x8b021fa1u, + 0xe5a0cc0fu, 0xb56f74e8u, 0x18acf3d6u, 0xce89e299u, + 0xb4a84fe0u, 0xfd13e0b7u, 0x7cc43b81u, 0xd2ada8d9u, + 0x165fa266u, 0x80957705u, 0x93cc7314u, 0x211a1477u, + 0xe6ad2065u, 0x77b5fa86u, 0xc75442f5u, 0xfb9d35cfu, + 0xebcdaf0cu, 0x7b3e89a0u, 0xd6411bd3u, 0xae1e7e49u, + 0x00250e2du, 0x2071b35eu, 0x226800bbu, 0x57b8e0afu, + 0x2464369bu, 0xf009b91eu, 0x5563911du, 0x59dfa6aau, + 0x78c14389u, 0xd95a537fu, 0x207d5ba2u, 0x02e5b9c5u, + 0x83260376u, 0x6295cfa9u, 0x11c81968u, 0x4e734a41u, + 0xb3472dcau, 0x7b14a94au, 0x1b510052u, 0x9a532915u, + 0xd60f573fu, 0xbc9bc6e4u, 0x2b60a476u, 0x81e67400u, + 0x08ba6fb5u, 0x571be91fu, 0xf296ec6bu, 0x2a0dd915u, + 0xb6636521u, 0xe7b9f9b6u, 0xff34052eu, 0xc5855664u, + 0x53b02d5du, 0xa99f8fa1u, 0x08ba4799u, 0x6e85076au}, + {0x4b7a70e9u, 0xb5b32944u, 0xdb75092eu, 0xc4192623u, + 0xad6ea6b0u, 0x49a7df7du, 0x9cee60b8u, 0x8fedb266u, + 0xecaa8c71u, 0x699a17ffu, 0x5664526cu, 0xc2b19ee1u, + 0x193602a5u, 0x75094c29u, 0xa0591340u, 0xe4183a3eu, + 0x3f54989au, 0x5b429d65u, 0x6b8fe4d6u, 0x99f73fd6u, + 0xa1d29c07u, 0xefe830f5u, 0x4d2d38e6u, 0xf0255dc1u, + 0x4cdd2086u, 0x8470eb26u, 0x6382e9c6u, 0x021ecc5eu, + 0x09686b3fu, 0x3ebaefc9u, 0x3c971814u, 0x6b6a70a1u, + 0x687f3584u, 0x52a0e286u, 0xb79c5305u, 0xaa500737u, + 0x3e07841cu, 0x7fdeae5cu, 0x8e7d44ecu, 0x5716f2b8u, + 0xb03ada37u, 0xf0500c0du, 0xf01c1f04u, 0x0200b3ffu, + 0xae0cf51au, 0x3cb574b2u, 0x25837a58u, 0xdc0921bdu, + 0xd19113f9u, 0x7ca92ff6u, 0x94324773u, 0x22f54701u, + 0x3ae5e581u, 0x37c2dadcu, 0xc8b57634u, 0x9af3dda7u, + 0xa9446146u, 0x0fd0030eu, 0xecc8c73eu, 0xa4751e41u, + 0xe238cd99u, 0x3bea0e2fu, 0x3280bba1u, 0x183eb331u, + 0x4e548b38u, 0x4f6db908u, 0x6f420d03u, 0xf60a04bfu, + 0x2cb81290u, 0x24977c79u, 0x5679b072u, 0xbcaf89afu, + 0xde9a771fu, 0xd9930810u, 0xb38bae12u, 0xdccf3f2eu, + 0x5512721fu, 0x2e6b7124u, 0x501adde6u, 0x9f84cd87u, + 0x7a584718u, 0x7408da17u, 0xbc9f9abcu, 0xe94b7d8cu, + 0xec7aec3au, 0xdb851dfau, 0x63094366u, 0xc464c3d2u, + 0xef1c1847u, 0x3215d908u, 0xdd433b37u, 0x24c2ba16u, + 0x12a14d43u, 0x2a65c451u, 0x50940002u, 0x133ae4ddu, + 0x71dff89eu, 0x10314e55u, 0x81ac77d6u, 0x5f11199bu, + 0x043556f1u, 0xd7a3c76bu, 0x3c11183bu, 0x5924a509u, + 0xf28fe6edu, 0x97f1fbfau, 0x9ebabf2cu, 0x1e153c6eu, + 0x86e34570u, 0xeae96fb1u, 0x860e5e0au, 0x5a3e2ab3u, + 0x771fe71cu, 0x4e3d06fau, 0x2965dcb9u, 0x99e71d0fu, + 0x803e89d6u, 0x5266c825u, 0x2e4cc978u, 0x9c10b36au, + 0xc6150ebau, 0x94e2ea78u, 0xa5fc3c53u, 0x1e0a2df4u, + 0xf2f74ea7u, 0x361d2b3du, 0x1939260fu, 0x19c27960u, + 0x5223a708u, 0xf71312b6u, 0xebadfe6eu, 0xeac31f66u, + 0xe3bc4595u, 0xa67bc883u, 0xb17f37d1u, 0x018cff28u, + 0xc332ddefu, 0xbe6c5aa5u, 0x65582185u, 0x68ab9802u, + 0xeecea50fu, 0xdb2f953bu, 0x2aef7dadu, 0x5b6e2f84u, + 0x1521b628u, 0x29076170u, 0xecdd4775u, 0x619f1510u, + 0x13cca830u, 0xeb61bd96u, 0x0334fe1eu, 0xaa0363cfu, + 0xb5735c90u, 0x4c70a239u, 0xd59e9e0bu, 0xcbaade14u, + 0xeecc86bcu, 0x60622ca7u, 0x9cab5cabu, 0xb2f3846eu, + 0x648b1eafu, 0x19bdf0cau, 0xa02369b9u, 0x655abb50u, + 0x40685a32u, 0x3c2ab4b3u, 0x319ee9d5u, 0xc021b8f7u, + 0x9b540b19u, 0x875fa099u, 0x95f7997eu, 0x623d7da8u, + 0xf837889au, 0x97e32d77u, 0x11ed935fu, 0x16681281u, + 0x0e358829u, 0xc7e61fd6u, 0x96dedfa1u, 0x7858ba99u, + 0x57f584a5u, 0x1b227263u, 0x9b83c3ffu, 0x1ac24696u, + 0xcdb30aebu, 0x532e3054u, 0x8fd948e4u, 0x6dbc3128u, + 0x58ebf2efu, 0x34c6ffeau, 0xfe28ed61u, 0xee7c3c73u, + 0x5d4a14d9u, 0xe864b7e3u, 0x42105d14u, 0x203e13e0u, + 0x45eee2b6u, 0xa3aaabeau, 0xdb6c4f15u, 0xfacb4fd0u, + 0xc742f442u, 0xef6abbb5u, 0x654f3b1du, 0x41cd2105u, + 0xd81e799eu, 0x86854dc7u, 0xe44b476au, 0x3d816250u, + 0xcf62a1f2u, 0x5b8d2646u, 0xfc8883a0u, 0xc1c7b6a3u, + 0x7f1524c3u, 0x69cb7492u, 0x47848a0bu, 0x5692b285u, + 0x095bbf00u, 0xad19489du, 0x1462b174u, 0x23820e00u, + 0x58428d2au, 0x0c55f5eau, 0x1dadf43eu, 0x233f7061u, + 0x3372f092u, 0x8d937e41u, 0xd65fecf1u, 0x6c223bdbu, + 0x7cde3759u, 0xcbee7460u, 0x4085f2a7u, 0xce77326eu, + 0xa6078084u, 0x19f8509eu, 0xe8efd855u, 0x61d99735u, + 0xa969a7aau, 0xc50c06c2u, 0x5a04abfcu, 0x800bcadcu, + 0x9e447a2eu, 0xc3453484u, 0xfdd56705u, 0x0e1e9ec9u, + 0xdb73dbd3u, 0x105588cdu, 0x675fda79u, 0xe3674340u, + 0xc5c43465u, 0x713e38d8u, 0x3d28f89eu, 0xf16dff20u, + 0x153e21e7u, 0x8fb03d4au, 0xe6e39f2bu, 0xdb83adf7u}, + {0xe93d5a68u, 0x948140f7u, 0xf64c261cu, 0x94692934u, + 0x411520f7u, 0x7602d4f7u, 0xbcf46b2eu, 0xd4a20068u, + 0xd4082471u, 0x3320f46au, 0x43b7d4b7u, 0x500061afu, + 0x1e39f62eu, 0x97244546u, 0x14214f74u, 0xbf8b8840u, + 0x4d95fc1du, 0x96b591afu, 0x70f4ddd3u, 0x66a02f45u, + 0xbfbc09ecu, 0x03bd9785u, 0x7fac6dd0u, 0x31cb8504u, + 0x96eb27b3u, 0x55fd3941u, 0xda2547e6u, 0xabca0a9au, + 0x28507825u, 0x530429f4u, 0x0a2c86dau, 0xe9b66dfbu, + 0x68dc1462u, 0xd7486900u, 0x680ec0a4u, 0x27a18deeu, + 0x4f3ffea2u, 0xe887ad8cu, 0xb58ce006u, 0x7af4d6b6u, + 0xaace1e7cu, 0xd3375fecu, 0xce78a399u, 0x406b2a42u, + 0x20fe9e35u, 0xd9f385b9u, 0xee39d7abu, 0x3b124e8bu, + 0x1dc9faf7u, 0x4b6d1856u, 0x26a36631u, 0xeae397b2u, + 0x3a6efa74u, 0xdd5b4332u, 0x6841e7f7u, 0xca7820fbu, + 0xfb0af54eu, 0xd8feb397u, 0x454056acu, 0xba489527u, + 0x55533a3au, 0x20838d87u, 0xfe6ba9b7u, 0xd096954bu, + 0x55a867bcu, 0xa1159a58u, 0xcca92963u, 0x99e1db33u, + 0xa62a4a56u, 0x3f3125f9u, 0x5ef47e1cu, 0x9029317cu, + 0xfdf8e802u, 0x04272f70u, 0x80bb155cu, 0x05282ce3u, + 0x95c11548u, 0xe4c66d22u, 0x48c1133fu, 0xc70f86dcu, + 0x07f9c9eeu, 0x41041f0fu, 0x404779a4u, 0x5d886e17u, + 0x325f51ebu, 0xd59bc0d1u, 0xf2bcc18fu, 0x41113564u, + 0x257b7834u, 0x602a9c60u, 0xdff8e8a3u, 0x1f636c1bu, + 0x0e12b4c2u, 0x02e1329eu, 0xaf664fd1u, 0xcad18115u, + 0x6b2395e0u, 0x333e92e1u, 0x3b240b62u, 0xeebeb922u, + 0x85b2a20eu, 0xe6ba0d99u, 0xde720c8cu, 0x2da2f728u, + 0xd0127845u, 0x95b794fdu, 0x647d0862u, 0xe7ccf5f0u, + 0x5449a36fu, 0x877d48fau, 0xc39dfd27u, 0xf33e8d1eu, + 0x0a476341u, 0x992eff74u, 0x3a6f6eabu, 0xf4f8fd37u, + 0xa812dc60u, 0xa1ebddf8u, 0x991be14cu, 0xdb6e6b0du, + 0xc67b5510u, 0x6d672c37u, 0x2765d43bu, 0xdcd0e804u, + 0xf1290dc7u, 0xcc00ffa3u, 0xb5390f92u, 0x690fed0bu, + 0x667b9ffbu, 0xcedb7d9cu, 0xa091cf0bu, 0xd9155ea3u, + 0xbb132f88u, 0x515bad24u, 0x7b9479bfu, 0x763bd6ebu, + 0x37392eb3u, 0xcc115979u, 0x8026e297u, 0xf42e312du, + 0x6842ada7u, 0xc66a2b3bu, 0x12754cccu, 0x782ef11cu, + 0x6a124237u, 0xb79251e7u, 0x06a1bbe6u, 0x4bfb6350u, + 0x1a6b1018u, 0x11caedfau, 0x3d25bdd8u, 0xe2e1c3c9u, + 0x44421659u, 0x0a121386u, 0xd90cec6eu, 0xd5abea2au, + 0x64af674eu, 0xda86a85fu, 0xbebfe988u, 0x64e4c3feu, + 0x9dbc8057u, 0xf0f7c086u, 0x60787bf8u, 0x6003604du, + 0xd1fd8346u, 0xf6381fb0u, 0x7745ae04u, 0xd736fcccu, + 0x83426b33u, 0xf01eab71u, 0xb0804187u, 0x3c005e5fu, + 0x77a057beu, 0xbde8ae24u, 0x55464299u, 0xbf582e61u, + 0x4e58f48fu, 0xf2ddfda2u, 0xf474ef38u, 0x8789bdc2u, + 0x5366f9c3u, 0xc8b38e74u, 0xb475f255u, 0x46fcd9b9u, + 0x7aeb2661u, 0x8b1ddf84u, 0x846a0e79u, 0x915f95e2u, + 0x466e598eu, 0x20b45770u, 0x8cd55591u, 0xc902de4cu, + 0xb90bace1u, 0xbb8205d0u, 0x11a86248u, 0x7574a99eu, + 0xb77f19b6u, 0xe0a9dc09u, 0x662d09a1u, 0xc4324633u, + 0xe85a1f02u, 0x09f0be8cu, 0x4a99a025u, 0x1d6efe10u, + 0x1ab93d1du, 0x0ba5a4dfu, 0xa186f20fu, 0x2868f169u, + 0xdcb7da83u, 0x573906feu, 0xa1e2ce9bu, 0x4fcd7f52u, + 0x50115e01u, 0xa70683fau, 0xa002b5c4u, 0x0de6d027u, + 0x9af88c27u, 0x773f8641u, 0xc3604c06u, 0x61a806b5u, + 0xf0177a28u, 0xc0f586e0u, 0x006058aau, 0x30dc7d62u, + 0x11e69ed7u, 0x2338ea63u, 0x53c2dd94u, 0xc2c21634u, + 0xbbcbee56u, 0x90bcb6deu, 0xebfc7da1u, 0xce591d76u, + 0x6f05e409u, 0x4b7c0188u, 0x39720a3du, 0x7c927c24u, + 0x86e3725fu, 0x724d9db9u, 0x1ac15bb4u, 0xd39eb8fcu, + 0xed545578u, 0x08fca5b5u, 0xd83d7cd3u, 0x4dad0fc4u, + 0x1e50ef5eu, 0xb161e6f8u, 0xa28514d9u, 0x6c51133cu, + 0x6fd5c7e7u, 0x56e14ec4u, 0x362abfceu, 0xddc6c837u, + 0xd79a3234u, 0x92638212u, 0x670efa8eu, 0x406000e0u}, + {0x3a39ce37u, 0xd3faf5cfu, 0xabc27737u, 0x5ac52d1bu, + 0x5cb0679eu, 0x4fa33742u, 0xd3822740u, 0x99bc9bbeu, + 0xd5118e9du, 0xbf0f7315u, 0xd62d1c7eu, 0xc700c47bu, + 0xb78c1b6bu, 0x21a19045u, 0xb26eb1beu, 0x6a366eb4u, + 0x5748ab2fu, 0xbc946e79u, 0xc6a376d2u, 0x6549c2c8u, + 0x530ff8eeu, 0x468dde7du, 0xd5730a1du, 0x4cd04dc6u, + 0x2939bbdbu, 0xa9ba4650u, 0xac9526e8u, 0xbe5ee304u, + 0xa1fad5f0u, 0x6a2d519au, 0x63ef8ce2u, 0x9a86ee22u, + 0xc089c2b8u, 0x43242ef6u, 0xa51e03aau, 0x9cf2d0a4u, + 0x83c061bau, 0x9be96a4du, 0x8fe51550u, 0xba645bd6u, + 0x2826a2f9u, 0xa73a3ae1u, 0x4ba99586u, 0xef5562e9u, + 0xc72fefd3u, 0xf752f7dau, 0x3f046f69u, 0x77fa0a59u, + 0x80e4a915u, 0x87b08601u, 0x9b09e6adu, 0x3b3ee593u, + 0xe990fd5au, 0x9e34d797u, 0x2cf0b7d9u, 0x022b8b51u, + 0x96d5ac3au, 0x017da67du, 0xd1cf3ed6u, 0x7c7d2d28u, + 0x1f9f25cfu, 0xadf2b89bu, 0x5ad6b472u, 0x5a88f54cu, + 0xe029ac71u, 0xe019a5e6u, 0x47b0acfdu, 0xed93fa9bu, + 0xe8d3c48du, 0x283b57ccu, 0xf8d56629u, 0x79132e28u, + 0x785f0191u, 0xed756055u, 0xf7960e44u, 0xe3d35e8cu, + 0x15056dd4u, 0x88f46dbau, 0x03a16125u, 0x0564f0bdu, + 0xc3eb9e15u, 0x3c9057a2u, 0x97271aecu, 0xa93a072au, + 0x1b3f6d9bu, 0x1e6321f5u, 0xf59c66fbu, 0x26dcf319u, + 0x7533d928u, 0xb155fdf5u, 0x03563482u, 0x8aba3cbbu, + 0x28517711u, 0xc20ad9f8u, 0xabcc5167u, 0xccad925fu, + 0x4de81751u, 0x3830dc8eu, 0x379d5862u, 0x9320f991u, + 0xea7a90c2u, 0xfb3e7bceu, 0x5121ce64u, 0x774fbe32u, + 0xa8b6e37eu, 0xc3293d46u, 0x48de5369u, 0x6413e680u, + 0xa2ae0810u, 0xdd6db224u, 0x69852dfdu, 0x09072166u, + 0xb39a460au, 0x6445c0ddu, 0x586cdecfu, 0x1c20c8aeu, + 0x5bbef7ddu, 0x1b588d40u, 0xccd2017fu, 0x6bb4e3bbu, + 0xdda26a7eu, 0x3a59ff45u, 0x3e350a44u, 0xbcb4cdd5u, + 0x72eacea8u, 0xfa6484bbu, 0x8d6612aeu, 0xbf3c6f47u, + 0xd29be463u, 0x542f5d9eu, 0xaec2771bu, 0xf64e6370u, + 0x740e0d8du, 0xe75b1357u, 0xf8721671u, 0xaf537d5du, + 0x4040cb08u, 0x4eb4e2ccu, 0x34d2466au, 0x0115af84u, + 0xe1b00428u, 0x95983a1du, 0x06b89fb4u, 0xce6ea048u, + 0x6f3f3b82u, 0x3520ab82u, 0x011a1d4bu, 0x277227f8u, + 0x611560b1u, 0xe7933fdcu, 0xbb3a792bu, 0x344525bdu, + 0xa08839e1u, 0x51ce794bu, 0x2f32c9b7u, 0xa01fbac9u, + 0xe01cc87eu, 0xbcc7d1f6u, 0xcf0111c3u, 0xa1e8aac7u, + 0x1a908749u, 0xd44fbd9au, 0xd0dadecbu, 0xd50ada38u, + 0x0339c32au, 0xc6913667u, 0x8df9317cu, 0xe0b12b4fu, + 0xf79e59b7u, 0x43f5bb3au, 0xf2d519ffu, 0x27d9459cu, + 0xbf97222cu, 0x15e6fc2au, 0x0f91fc71u, 0x9b941525u, + 0xfae59361u, 0xceb69cebu, 0xc2a86459u, 0x12baa8d1u, + 0xb6c1075eu, 0xe3056a0cu, 0x10d25065u, 0xcb03a442u, + 0xe0ec6e0eu, 0x1698db3bu, 0x4c98a0beu, 0x3278e964u, + 0x9f1f9532u, 0xe0d392dfu, 0xd3a0342bu, 0x8971f21eu, + 0x1b0a7441u, 0x4ba3348cu, 0xc5be7120u, 0xc37632d8u, + 0xdf359f8du, 0x9b992f2eu, 0xe60b6f47u, 0x0fe3f11du, + 0xe54cda54u, 0x1edad891u, 0xce6279cfu, 0xcd3e7e6fu, + 0x1618b166u, 0xfd2c1d05u, 0x848fd2c5u, 0xf6fb2299u, + 0xf523f357u, 0xa6327623u, 0x93a83531u, 0x56cccd02u, + 0xacf08162u, 0x5a75ebb5u, 0x6e163697u, 0x88d273ccu, + 0xde966292u, 0x81b949d0u, 0x4c50901bu, 0x71c65614u, + 0xe6c6c7bdu, 0x327a140au, 0x45e1d006u, 0xc3f27b9au, + 0xc9aa53fdu, 0x62a80f00u, 0xbb25bfe2u, 0x35bdd2f6u, + 0x71126905u, 0xb2040222u, 0xb6cbcf7cu, 0xcd769c2bu, + 0x53113ec0u, 0x1640e3d3u, 0x38abbd60u, 0x2547adf0u, + 0xba38209cu, 0xf746ce76u, 0x77afa1c5u, 0x20756060u, + 0x85cbfe4eu, 0x8ae88dd8u, 0x7aaaf9b0u, 0x4cf9aa7eu, + 0x1948c25cu, 0x02fb8a8cu, 0x01c36ae4u, 0xd6ebe1f9u, + 0x90d4f869u, 0xa65cdea0u, 0x3f09252du, 0xc208e69fu, + 0xb74e6132u, 0xce77e25bu, 0x578fdfe3u, 0x3ac372e6u + } +}; + +#define F1(i) \ + xl ^= bfs->pax[i]; \ + xr ^= ((bfs->sbx[0][xl >> 24] + \ + bfs->sbx[1][(xl & 0xFF0000) >> 16]) ^ \ + bfs->sbx[2][(xl & 0xFF00) >> 8]) + \ + bfs->sbx[3][xl & 0xFF]; + +#define F2(i) \ + xr ^= bfs->pax[i]; \ + xl ^= ((bfs->sbx[0][xr >> 24] + \ + bfs->sbx[1][(xr & 0xFF0000) >> 16]) ^ \ + bfs->sbx[2][(xr & 0xFF00) >> 8]) + \ + bfs->sbx[3][xr & 0xFF]; + + static void +bf_e_block( + bf_state_T *bfs, + UINT32_T *p_xl, + UINT32_T *p_xr) +{ + UINT32_T temp; + UINT32_T xl = *p_xl; + UINT32_T xr = *p_xr; + + F1(0) F2(1) + F1(2) F2(3) + F1(4) F2(5) + F1(6) F2(7) + F1(8) F2(9) + F1(10) F2(11) + F1(12) F2(13) + F1(14) F2(15) + xl ^= bfs->pax[16]; + xr ^= bfs->pax[17]; + temp = xl; + xl = xr; + xr = temp; + *p_xl = xl; + *p_xr = xr; +} + + +#ifdef WORDS_BIGENDIAN +# define htonl2(x) \ + x = ((((x) & 0xffL) << 24) | (((x) & 0xff00L) << 8) | \ + (((x) & 0xff0000L) >> 8) | (((x) & 0xff000000L) >> 24)) +#else +# define htonl2(x) +#endif + + static void +bf_e_cblock( + bf_state_T *bfs, + char_u *block) +{ + block8 bk; + + memcpy(bk.uc, block, 8); + htonl2(bk.ul[0]); + htonl2(bk.ul[1]); + bf_e_block(bfs, &bk.ul[0], &bk.ul[1]); + htonl2(bk.ul[0]); + htonl2(bk.ul[1]); + memcpy(block, bk.uc, 8); +} + +/* + * Initialize the crypt method using "password" as the encryption key and + * "salt[salt_len]" as the salt. + */ + static void +bf_key_init( + bf_state_T *bfs, + char_u *password, + char_u *salt, + int salt_len) +{ + int i, j, keypos = 0; + unsigned u; + UINT32_T val, data_l, data_r; + char_u *key; + int keylen; + + // Process the key 1001 times. + // See http://en.wikipedia.org/wiki/Key_strengthening. + key = sha256_key(password, salt, salt_len); + for (i = 0; i < 1000; i++) + key = sha256_key(key, salt, salt_len); + + // Convert the key from 64 hex chars to 32 binary chars. + keylen = (int)STRLEN(key) / 2; + if (keylen == 0) + { + iemsg(e_bf_key_init_called_with_empty_password); + return; + } + for (i = 0; i < keylen; i++) + { + sscanf((char *)&key[i * 2], "%2x", &u); + key[i] = u; + } + + // Use "key" to initialize the P-array ("pax") and S-boxes ("sbx") of + // Blowfish. + mch_memmove(bfs->sbx, sbx_init, 4 * 4 * 256); + + for (i = 0; i < 18; ++i) + { + val = 0; + for (j = 0; j < 4; ++j) + val = (val << 8) | key[keypos++ % keylen]; + bfs->pax[i] = pax_init[i] ^ val; + } + + data_l = data_r = 0; + for (i = 0; i < 18; i += 2) + { + bf_e_block(bfs, &data_l, &data_r); + bfs->pax[i + 0] = data_l; + bfs->pax[i + 1] = data_r; + } + + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 256; j += 2) + { + bf_e_block(bfs, &data_l, &data_r); + bfs->sbx[i][j + 0] = data_l; + bfs->sbx[i][j + 1] = data_r; + } + } +} + +/* + * Blowfish self-test for corrupted tables or instructions. + */ + static int +bf_check_tables( + UINT32_T pax[18], + UINT32_T sbx[4][256], + UINT32_T val) +{ + int i, j; + UINT32_T c = 0; + + for (i = 0; i < 18; i++) + c ^= pax[i]; + for (i = 0; i < 4; i++) + for (j = 0; j < 256; j++) + c ^= sbx[i][j]; + return c == val; +} + +typedef struct { + char_u password[64]; + char_u salt[9]; + char_u plaintxt[9]; + char_u cryptxt[9]; + char_u badcryptxt[9]; // cryptxt when big/little endian is wrong + UINT32_T keysum; +} struct_bf_test_data; + +/* + * Assert bf(password, plaintxt) is cryptxt. + * Assert csum(pax sbx(password)) is keysum. + */ +static struct_bf_test_data bf_test_data[] = { + { + "password", + "salt", + "plaintxt", + "\xad\x3d\xfa\x7f\xe8\xea\x40\xf6", // cryptxt + "\x72\x50\x3b\x38\x10\x60\x22\xa7", // badcryptxt + 0x56701b5du // keysum + }, +}; + +/* + * Return FAIL when there is something wrong with blowfish encryption. + */ + static int +bf_self_test(void) +{ + int i, bn; + int err = 0; + block8 bk; + UINT32_T ui = 0xffffffffUL; + bf_state_T state; + + CLEAR_FIELD(state); + state.cfb_len = BF_MAX_CFB_LEN; + + // We can't simply use sizeof(UINT32_T), it would generate a compiler + // warning. + if (ui != 0xffffffffUL || ui + 1 != 0) + { + err++; + emsg(_(e_sizeof_uint32_isnot_four)); + } + + if (!bf_check_tables(pax_init, sbx_init, 0x6ffa520a)) + err++; + + bn = ARRAY_LENGTH(bf_test_data); + for (i = 0; i < bn; i++) + { + bf_key_init(&state, (char_u *)(bf_test_data[i].password), + bf_test_data[i].salt, + (int)STRLEN(bf_test_data[i].salt)); + if (!bf_check_tables(state.pax, state.sbx, bf_test_data[i].keysum)) + err++; + + // Don't modify bf_test_data[i].plaintxt, self test is idempotent. + memcpy(bk.uc, bf_test_data[i].plaintxt, 8); + bf_e_cblock(&state, bk.uc); + if (memcmp(bk.uc, bf_test_data[i].cryptxt, 8) != 0) + { + if (err == 0 && memcmp(bk.uc, bf_test_data[i].badcryptxt, 8) == 0) + emsg(_(e_blowfish_big_little_endian_use_wrong)); + err++; + } + } + + return err > 0 ? FAIL : OK; +} + +/* + * CFB: Cipher Feedback Mode. + */ + +/* + * Initialize with seed "seed[seed_len]". + */ + static void +bf_cfb_init( + bf_state_T *bfs, + char_u *seed, + int seed_len) +{ + int i, mi; + + bfs->randbyte_offset = bfs->update_offset = 0; + vim_memset(bfs->cfb_buffer, 0, bfs->cfb_len); + if (seed_len > 0) + { + mi = seed_len > bfs->cfb_len ? seed_len : bfs->cfb_len; + for (i = 0; i < mi; i++) + bfs->cfb_buffer[i % bfs->cfb_len] ^= seed[i % seed_len]; + } +} + +#define BF_CFB_UPDATE(bfs, c) \ +{ \ + bfs->cfb_buffer[bfs->update_offset] ^= (char_u)c; \ + if (++bfs->update_offset == bfs->cfb_len) \ + bfs->update_offset = 0; \ +} + +#define BF_RANBYTE(bfs, t) \ +{ \ + if ((bfs->randbyte_offset & BF_BLOCK_MASK) == 0) \ + bf_e_cblock(bfs, &(bfs->cfb_buffer[bfs->randbyte_offset])); \ + t = bfs->cfb_buffer[bfs->randbyte_offset]; \ + if (++bfs->randbyte_offset == bfs->cfb_len) \ + bfs->randbyte_offset = 0; \ +} + +/* + * Encrypt "from[len]" into "to[len]". + * "from" and "to" can be equal to encrypt in place. + */ + void +crypt_blowfish_encode( + cryptstate_T *state, + char_u *from, + size_t len, + char_u *to, + int last UNUSED) +{ + bf_state_T *bfs = state->method_state; + size_t i; + int ztemp, t; + + for (i = 0; i < len; ++i) + { + ztemp = from[i]; + BF_RANBYTE(bfs, t); + BF_CFB_UPDATE(bfs, ztemp); + to[i] = t ^ ztemp; + } +} + +/* + * Decrypt "from[len]" into "to[len]". + */ + void +crypt_blowfish_decode( + cryptstate_T *state, + char_u *from, + size_t len, + char_u *to, + int last UNUSED) +{ + bf_state_T *bfs = state->method_state; + size_t i; + int t; + + for (i = 0; i < len; ++i) + { + BF_RANBYTE(bfs, t); + to[i] = from[i] ^ t; + BF_CFB_UPDATE(bfs, to[i]); + } +} + + int +crypt_blowfish_init( + cryptstate_T *state, + char_u *key, + crypt_arg_T *arg) +{ + bf_state_T *bfs = ALLOC_CLEAR_ONE(bf_state_T); + + if (bfs == NULL) + return FAIL; + state->method_state = bfs; + + // "blowfish" uses a 64 byte buffer, causing it to repeat 8 byte groups 8 + // times. "blowfish2" uses an 8 byte buffer to avoid repeating. + bfs->cfb_len = state->method_nr == CRYPT_M_BF ? BF_MAX_CFB_LEN : BF_BLOCK; + + if (blowfish_self_test() == FAIL) + return FAIL; + + bf_key_init(bfs, key, arg->cat_salt, arg->cat_salt_len); + bf_cfb_init(bfs, arg->cat_seed, arg->cat_seed_len); + + return OK; +} + +/* + * Run a test to check if the encryption works as expected. + * Give an error and return FAIL when not. + */ + int +blowfish_self_test(void) +{ + if (sha256_self_test() == FAIL) + { + emsg(_(e_sha256_test_failed)); + return FAIL; + } + if (bf_self_test() == FAIL) + { + emsg(_(e_blowfish_test_failed)); + return FAIL; + } + return OK; +} +#endif // FEAT_CRYPT diff --git a/userland/apps/vim/src/buffer.c b/userland/apps/vim/src/buffer.c new file mode 100644 index 00000000..9ee74f54 --- /dev/null +++ b/userland/apps/vim/src/buffer.c @@ -0,0 +1,6069 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * 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: BF_NEVERLOADED is set, 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" + + +#ifdef FEAT_EVAL +// Determines how deeply nested %{} blocks will be evaluated in statusline. +# define MAX_STL_EVAL_DEPTH 100 +#endif + +static void enter_buffer(buf_T *buf); +static void buflist_getfpos(void); +static char_u *buflist_match(regmatch_T *rmp, buf_T *buf, int ignore_case); +static char_u *fname_match(regmatch_T *rmp, char_u *name, int ignore_case); +#ifdef UNIX +static buf_T *buflist_findname_stat(char_u *ffname, stat_T *st); +static int otherfile_buf(buf_T *buf, char_u *ffname, stat_T *stp); +static int buf_same_ino(buf_T *buf, stat_T *stp); +#else +static int otherfile_buf(buf_T *buf, char_u *ffname); +#endif +static int value_changed(char_u *str, char_u **last); +static int append_arg_number(win_T *wp, char_u *buf, int buflen, int add_file); +static void free_buffer(buf_T *); +static void free_buffer_stuff(buf_T *buf, int free_options); +static int bt_nofileread(buf_T *buf); +static void no_write_message_buf(buf_T *buf); + +#ifdef UNIX +# define dev_T dev_t +#else +# define dev_T unsigned +#endif + +#define FOR_ALL_BUFS_FROM_LAST(buf) \ + for ((buf) = lastbuf; (buf) != NULL; (buf) = (buf)->b_prev) + +#if defined(FEAT_QUICKFIX) +static char *msg_loclist = N_("[Location List]"); +static char *msg_qflist = N_("[Quickfix List]"); +#endif + +// Number of times free_buffer() was called. +static int buf_free_count = 0; + +static int top_file_num = 1; // highest file number +static garray_T buf_reuse = GA_EMPTY; // file numbers to recycle + +/* + * Return the highest possible buffer number. + */ + int +get_highest_fnum(void) +{ + return top_file_num - 1; +} + +/* + * Read data from buffer for retrying. + */ + static int +read_buffer( + int read_stdin, // read file from stdin, otherwise fifo + exarg_T *eap, // for forced 'ff' and 'fenc' or NULL + int flags) // extra flags for readfile() +{ + int retval = OK; + linenr_T line_count; + + // Read from the buffer which the text is already filled in and append at + // the end. This makes it possible to retry when 'fileformat' or + // 'fileencoding' was guessed wrong. + line_count = curbuf->b_ml.ml_line_count; + retval = readfile( + read_stdin ? NULL : curbuf->b_ffname, + read_stdin ? NULL : curbuf->b_fname, + line_count, (linenr_T)0, (linenr_T)MAXLNUM, eap, + flags | READ_BUFFER); + if (retval == OK) + { + // Delete the binary lines. + while (--line_count >= 0) + ml_delete((linenr_T)1); + } + else + { + // Delete the converted lines. + while (curbuf->b_ml.ml_line_count > line_count) + ml_delete(line_count); + } + // Put the cursor on the first line. + curwin->w_cursor.lnum = 1; + curwin->w_cursor.col = 0; + + if (read_stdin) + { + // Set or reset 'modified' before executing autocommands, so that + // it can be changed there. + if (!readonlymode && !BUFEMPTY()) + changed(); + else if (retval == OK) + unchanged(curbuf, FALSE, TRUE); + + if (retval == OK) + { +#ifdef FEAT_EVAL + apply_autocmds_retval(EVENT_STDINREADPOST, NULL, NULL, FALSE, + curbuf, &retval); +#else + apply_autocmds(EVENT_STDINREADPOST, NULL, NULL, FALSE, curbuf); +#endif + } + } + return retval; +} + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * Ensure buffer "buf" is loaded. Does not trigger the swap-exists action. + */ + void +buffer_ensure_loaded(buf_T *buf) +{ + if (buf->b_ml.ml_mfp != NULL) + return; + + aco_save_T aco; + + // Make sure the buffer is in a window. If not then skip it. + aucmd_prepbuf(&aco, buf); + if (curbuf == buf) + { + if (swap_exists_action != SEA_READONLY) + swap_exists_action = SEA_NONE; + open_buffer(FALSE, NULL, 0); + aucmd_restbuf(&aco); + } +} +#endif + +/* + * Open current buffer, that is: open the memfile and read the file into + * memory. + * Return FAIL for failure, OK otherwise. + */ + int +open_buffer( + int read_stdin, // read file from stdin + exarg_T *eap, // for forced 'ff' and 'fenc' or NULL + int flags_arg) // extra flags for readfile() +{ + int flags = flags_arg; + int retval = OK; + bufref_T old_curbuf; +#ifdef FEAT_SYN_HL + long old_tw = curbuf->b_p_tw; +#endif + int read_fifo = FALSE; + + // The 'readonly' flag is only set when BF_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_ffname != NULL + && (curbuf->b_flags & BF_NEVERLOADED)) + curbuf->b_p_ro = TRUE; + + if (ml_open(curbuf) == 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, 0, FALSE, FALSE); + FOR_ALL_BUFFERS(curbuf) + 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 lose. + if (curbuf == NULL) + { + emsg(_(e_cannot_allocate_any_buffer_exiting)); + + // Don't try to do any saving, with "curbuf" NULL almost nothing + // will work. + v_dying = 2; + getout(2); + } + + emsg(_(e_cannot_allocate_buffer_using_other_one)); + enter_buffer(curbuf); +#ifdef FEAT_SYN_HL + if (old_tw != curbuf->b_p_tw) + check_colorcolumn(curwin); +#endif + return FAIL; + } + + // Do not sync this buffer yet, may first want to read the file. + if (curbuf->b_ml.ml_mfp != NULL) + curbuf->b_ml.ml_mfp->mf_dirty = MF_DIRTY_YES_NOSYNC; + + // The autocommands in readfile() may change the buffer, but only AFTER + // reading the file. + set_bufref(&old_curbuf, curbuf); + modified_was_set = FALSE; + + // mark cursor position as being invalid + curwin->w_valid = 0; + + // A buffer without an actual file should not use the buffer name to read a + // file. + if (bt_nofileread(curbuf)) + flags |= READ_NOFILE; + + // Read the file if there is one. + if (curbuf->b_ffname != NULL +#ifdef FEAT_NETBEANS_INTG + && netbeansReadFile +#endif + ) + { + int old_msg_silent = msg_silent; +#ifdef UNIX + int save_bin = curbuf->b_p_bin; + int perm; +#endif +#ifdef FEAT_NETBEANS_INTG + int oldFire = netbeansFireChanges; + + netbeansFireChanges = 0; +#endif +#ifdef UNIX + perm = mch_getperm(curbuf->b_ffname); + if (perm >= 0 && (S_ISFIFO(perm) + || S_ISSOCK(perm) +# ifdef OPEN_CHR_FILES + || (S_ISCHR(perm) && is_dev_fd_file(curbuf->b_ffname)) +# endif + )) + read_fifo = TRUE; + if (read_fifo) + curbuf->b_p_bin = TRUE; +#endif + if (shortmess(SHM_FILEINFO)) + msg_silent = 1; + retval = readfile(curbuf->b_ffname, curbuf->b_fname, + (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, eap, + flags | READ_NEW | (read_fifo ? READ_FIFO : 0)); +#ifdef UNIX + if (read_fifo) + { + curbuf->b_p_bin = save_bin; + if (retval == OK) + retval = read_buffer(FALSE, eap, flags); + } +#endif + msg_silent = old_msg_silent; +#ifdef FEAT_NETBEANS_INTG + netbeansFireChanges = oldFire; +#endif + // Help buffer is filtered. + if (bt_help(curbuf)) + fix_help_buffer(); + } + else if (read_stdin) + { + int save_bin = curbuf->b_p_bin; + + // First read the text in binary mode into the buffer. + // Then read from that same buffer and append at the end. This makes + // it possible to retry when 'fileformat' or 'fileencoding' was + // guessed wrong. + curbuf->b_p_bin = TRUE; + retval = readfile(NULL, NULL, (linenr_T)0, + (linenr_T)0, (linenr_T)MAXLNUM, NULL, + flags | (READ_NEW + READ_STDIN)); + curbuf->b_p_bin = save_bin; + if (retval == OK) + retval = read_buffer(TRUE, eap, flags); + } + + // Can now sync this buffer in ml_sync_all(). + if (curbuf->b_ml.ml_mfp != NULL + && curbuf->b_ml.ml_mfp->mf_dirty == MF_DIRTY_YES_NOSYNC) + curbuf->b_ml.ml_mfp->mf_dirty = MF_DIRTY_YES; + + // if first time loading this buffer, init b_chartab[] + if (curbuf->b_flags & BF_NEVERLOADED) + { + (void)buf_init_chartab(curbuf, FALSE); + parse_cino(curbuf); + } + + // Set/reset the Changed flag first, autocmds may change the buffer. + // Apply the automatic commands, before processing the modelines. + // So the modelines have priority over autocommands. + // + // When reading stdin, the buffer contents always needs writing, so set + // the changed flag. Unless in readonly mode: "ls | gview -". + // When interrupted and 'cpoptions' contains 'i' set changed flag. + if ((got_int && vim_strchr(p_cpo, CPO_INTMOD) != NULL) + || modified_was_set // ":set modified" used in autocmd +#ifdef FEAT_EVAL + || (aborting() && vim_strchr(p_cpo, CPO_INTMOD) != NULL) +#endif + ) + changed(); + else if (retval == OK && !read_stdin && !read_fifo) + unchanged(curbuf, FALSE, TRUE); + save_file_ff(curbuf); // keep this fileformat + + // Set last_changedtick to avoid triggering a TextChanged autocommand right + // after it was added. + curbuf->b_last_changedtick = CHANGEDTICK(curbuf); + curbuf->b_last_changedtick_i = CHANGEDTICK(curbuf); + curbuf->b_last_changedtick_pum = CHANGEDTICK(curbuf); + + // require "!" to overwrite the file, because it wasn't read completely +#ifdef FEAT_EVAL + if (aborting()) +#else + if (got_int) +#endif + curbuf->b_flags |= BF_READERR; + +#ifdef FEAT_FOLDING + // Need to update automatic folding. Do this before the autocommands, + // they may use the fold info. + foldUpdateAll(curwin); +#endif + + // need to set w_topline, unless some autocommand already did that. + if (!(curwin->w_valid & VALID_TOPLINE)) + { + curwin->w_topline = 1; +#ifdef FEAT_DIFF + curwin->w_topfill = 0; +#endif + } +#ifdef FEAT_EVAL + apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf, &retval); +#else + apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf); +#endif + + if (retval != OK) + return retval; + + // The autocommands may have changed the current buffer. Apply the + // modelines to the correct buffer, if it still exists and is loaded. + if (bufref_valid(&old_curbuf) && old_curbuf.br_buf->b_ml.ml_mfp != NULL) + { + aco_save_T aco; + + // Go to the buffer that was opened, make sure it is in a window. + // If not then skip it. + aucmd_prepbuf(&aco, old_curbuf.br_buf); + if (curbuf == old_curbuf.br_buf) + { + do_modelines(0); + curbuf->b_flags &= ~(BF_CHECK_RO | BF_NEVERLOADED); + + if ((flags & READ_NOWINENTER) == 0) +#ifdef FEAT_EVAL + apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL, + FALSE, curbuf, &retval); +#else + apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, + FALSE, curbuf); +#endif + + // restore curwin/curbuf and a few other things + aucmd_restbuf(&aco); + } + } + + return retval; +} + +/* + * Store "buf" in "bufref" and set the free count. + */ + void +set_bufref(bufref_T *bufref, buf_T *buf) +{ + bufref->br_buf = buf; + bufref->br_fnum = buf == NULL ? 0 : buf->b_fnum; + bufref->br_buf_free_count = buf_free_count; +} + +/* + * Return TRUE if "bufref->br_buf" points to the same buffer as when + * set_bufref() was called and it is a valid buffer. + * Only goes through the buffer list if buf_free_count changed. + * Also checks if b_fnum is still the same, a :bwipe followed by :new might get + * the same allocated memory, but it's a different buffer. + */ + int +bufref_valid(bufref_T *bufref) +{ + return bufref->br_buf_free_count == buf_free_count + ? TRUE : buf_valid(bufref->br_buf) + && bufref->br_fnum == bufref->br_buf->b_fnum; +} + +/* + * Return TRUE if "buf" points to a valid buffer (in the buffer list). + * This can be slow if there are many buffers, prefer using bufref_valid(). + */ + int +buf_valid(buf_T *buf) +{ + buf_T *bp; + + // Assume that we more often have a recent buffer, start with the last + // one. + FOR_ALL_BUFS_FROM_LAST(bp) + if (bp == buf) + return TRUE; + return FALSE; +} + +/* + * A hash table used to quickly lookup a buffer by its number. + */ +static hashtab_T buf_hashtab; + + static void +buf_hashtab_add(buf_T *buf) +{ + sprintf((char *)buf->b_key, "%x", buf->b_fnum); + if (hash_add(&buf_hashtab, buf->b_key, "create buffer") == FAIL) + emsg(_(e_buffer_cannot_be_registered)); +} + + static void +buf_hashtab_remove(buf_T *buf) +{ + hashitem_T *hi = hash_find(&buf_hashtab, buf->b_key); + + if (!HASHITEM_EMPTY(hi)) + hash_remove(&buf_hashtab, hi, "close buffer"); +} + +/* + * Return TRUE when buffer "buf" can be unloaded. + * Give an error message and return FALSE when the buffer is locked or the + * screen is being redrawn and the buffer is in a window. + */ + static int +can_unload_buffer(buf_T *buf) +{ + int can_unload = !buf->b_locked; + + if (can_unload && updating_screen) + { + win_T *wp; + + FOR_ALL_WINDOWS(wp) + if (wp->w_buffer == buf) + { + can_unload = FALSE; + break; + } + } + if (!can_unload) + { + char_u *fname = buf->b_fname != NULL ? buf->b_fname : buf->b_ffname; + + semsg(_(e_attempt_to_delete_buffer_that_is_in_use_str), + fname != NULL ? fname : (char_u *)"[No Name]"); + } + return can_unload; +} + +/* + * Close the link to a buffer. + * "action" is used when there is no longer a window for the buffer. + * It can be: + * 0 buffer becomes hidden + * DOBUF_UNLOAD buffer is unloaded + * DOBUF_DEL buffer is unloaded and removed from buffer list + * DOBUF_WIPE buffer is unloaded and really deleted + * DOBUF_WIPE_REUSE idem, and add to buf_reuse list + * When doing all but the first one on the current buffer, the caller should + * get a new buffer very soon! + * + * The 'bufhidden' option can force freeing and deleting. + * + * When "abort_if_last" is TRUE then do not close the buffer if autocommands + * cause there to be only one window with this buffer. e.g. when ":quit" is + * supposed to close the window but autocommands close all other windows. + * + * When "ignore_abort" is TRUE don't abort even when aborting() returns TRUE. + * + * Return TRUE when we got to the end and b_nwindows was decremented. + */ + int +close_buffer( + win_T *win, // if not NULL, set b_last_cursor + buf_T *buf, + int action, + int abort_if_last, + int ignore_abort) +{ + int is_curbuf; + int nwindows; + bufref_T bufref; + int is_curwin = (curwin != NULL && curwin->w_buffer == buf); + win_T *the_curwin = curwin; + tabpage_T *the_curtab = curtab; + int unload_buf = (action != 0); + int wipe_buf = (action == DOBUF_WIPE || action == DOBUF_WIPE_REUSE); + int del_buf = (action == DOBUF_DEL || wipe_buf); + + CHECK_CURBUF; + + // Force unloading or deleting when 'bufhidden' says so. + // The caller must take care of NOT deleting/freeing when 'bufhidden' is + // "hide" (otherwise we could never free or delete a buffer). + if (buf->b_p_bh[0] == 'd') // 'bufhidden' == "delete" + { + del_buf = TRUE; + unload_buf = TRUE; + } + else if (buf->b_p_bh[0] == 'w') // 'bufhidden' == "wipe" + { + del_buf = TRUE; + unload_buf = TRUE; + wipe_buf = TRUE; + } + else if (buf->b_p_bh[0] == 'u') // 'bufhidden' == "unload" + unload_buf = TRUE; + +#ifdef FEAT_TERMINAL + // depending on how we get here b_nwindows may already be zero + if (bt_terminal(buf) && (buf->b_nwindows <= 1 || del_buf)) + { + CHECK_CURBUF; + if (term_job_running(buf->b_term)) + { + if (wipe_buf || unload_buf) + { + if (!can_unload_buffer(buf)) + return FALSE; + + // Wiping out or unloading a terminal buffer kills the job. + free_terminal(buf); + + // A terminal buffer is wiped out when job has finished. + del_buf = TRUE; + unload_buf = TRUE; + wipe_buf = TRUE; + } + else + { + // The job keeps running, hide the buffer. + del_buf = FALSE; + unload_buf = FALSE; + } + } + else if (buf->b_p_bh[0] == 'h' && !del_buf) + { + // Hide a terminal buffer. + unload_buf = FALSE; + } + else + { + if (del_buf || unload_buf) + { + // A terminal buffer is wiped out if the job has finished. + // We only do this when there's an intention to unload the + // buffer. This way, :hide and other similar commands won't + // wipe the buffer. + del_buf = TRUE; + unload_buf = TRUE; + wipe_buf = TRUE; + } + } + CHECK_CURBUF; + } +#endif + + // Disallow deleting the buffer when it is locked (already being closed or + // halfway a command that relies on it). Unloading is allowed. + if ((del_buf || wipe_buf) && !can_unload_buffer(buf)) + return FALSE; + + // check no autocommands closed the window + if (win != NULL && win_valid_any_tab(win)) + { + // Set b_last_cursor when closing the last window for the buffer. + // Remember the last cursor position and window options of the buffer. + // This used to be only for the current window, but then options like + // 'foldmethod' may be lost with a ":only" command. + if (buf->b_nwindows == 1) + set_last_cursor(win); + buflist_setfpos(buf, win, + win->w_cursor.lnum == 1 ? 0 : win->w_cursor.lnum, + win->w_cursor.col, TRUE); + } + + set_bufref(&bufref, buf); + + // When the buffer is no longer in a window, trigger BufWinLeave + if (buf->b_nwindows == 1) + { + ++buf->b_locked; + ++buf->b_locked_split; + if (apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname, + FALSE, buf) + && !bufref_valid(&bufref)) + { + // Autocommands deleted the buffer. +aucmd_abort: + emsg(_(e_autocommands_caused_command_to_abort)); + return FALSE; + } + --buf->b_locked; + --buf->b_locked_split; + if (abort_if_last && one_window()) + // Autocommands made this the only window. + goto aucmd_abort; + + // When the buffer becomes hidden, but is not unloaded, trigger + // BufHidden + if (!unload_buf) + { + ++buf->b_locked; + ++buf->b_locked_split; + if (apply_autocmds(EVENT_BUFHIDDEN, buf->b_fname, buf->b_fname, + FALSE, buf) + && !bufref_valid(&bufref)) + // Autocommands deleted the buffer. + goto aucmd_abort; + --buf->b_locked; + --buf->b_locked_split; + if (abort_if_last && one_window()) + // Autocommands made this the only window. + goto aucmd_abort; + } +#ifdef FEAT_EVAL + // autocmds may abort script processing + if (!ignore_abort && aborting()) + return FALSE; +#endif + } + + // If the buffer was in curwin and the window has changed, go back to that + // window, if it still exists. This avoids that ":edit x" triggering a + // "tabnext" BufUnload autocmd leaves a window behind without a buffer. + if (is_curwin && curwin != the_curwin && win_valid_any_tab(the_curwin)) + { + block_autocmds(); + goto_tabpage_win(the_curtab, the_curwin); + unblock_autocmds(); + } + + nwindows = buf->b_nwindows; + + // decrease the link count from windows (unless not in any window) + if (buf->b_nwindows > 0) + --buf->b_nwindows; + +#ifdef FEAT_DIFF + if (diffopt_hiddenoff() && !unload_buf && buf->b_nwindows == 0) + diff_buf_delete(buf); // Clear 'diff' for hidden buffer. +#endif + + // Return when a window is displaying the buffer or when it's not + // unloaded. + if (buf->b_nwindows > 0 || !unload_buf) + return FALSE; + + // Always remove the buffer when there is no file name. + if (buf->b_ffname == NULL) + del_buf = TRUE; + + // When closing the current buffer stop Visual mode before freeing + // anything. + if (buf == curbuf && VIsual_active +#if defined(EXITFREE) + && !entered_free_all_mem +#endif + ) + end_visual_mode(); + + // Free all things allocated for this buffer. + // Also calls the "BufDelete" autocommands when del_buf is TRUE. + // + // Remember if we are closing the current buffer. Restore the number of + // windows, so that autocommands in buf_freeall() don't get confused. + is_curbuf = (buf == curbuf); + buf->b_nwindows = nwindows; + + buf_freeall(buf, (del_buf ? BFA_DEL : 0) + + (wipe_buf ? BFA_WIPE : 0) + + (ignore_abort ? BFA_IGNORE_ABORT : 0)); + + // Autocommands may have deleted the buffer. + if (!bufref_valid(&bufref)) + return FALSE; +#ifdef FEAT_EVAL + // autocmds may abort script processing + if (!ignore_abort && aborting()) + return FALSE; +#endif + + // It's possible that autocommands change curbuf to the one being deleted. + // This might cause the previous curbuf to be deleted unexpectedly. But + // in some cases it's OK to delete the curbuf, because a new one is + // obtained anyway. Therefore only return if curbuf changed to the + // deleted buffer. + if (buf == curbuf && !is_curbuf) + return FALSE; + + if (win_valid_any_tab(win) && win->w_buffer == buf) + win->w_buffer = NULL; // make sure we don't use the buffer now + + // Autocommands may have opened or closed windows for this buffer. + // Decrement the count for the close we do here. + if (buf->b_nwindows > 0) + --buf->b_nwindows; + + /* + * Remove the buffer from the list. + */ + if (wipe_buf) + { + // Do not wipe out the buffer if it is used in a window. + if (buf->b_nwindows > 0) + return FALSE; + + if (action == DOBUF_WIPE_REUSE) + { + // we can re-use this buffer number, store it + if (buf_reuse.ga_itemsize == 0) + ga_init2(&buf_reuse, sizeof(int), 50); + if (ga_grow(&buf_reuse, 1) == OK) + ((int *)buf_reuse.ga_data)[buf_reuse.ga_len++] = buf->b_fnum; + } + if (buf->b_sfname != buf->b_ffname) + VIM_CLEAR(buf->b_sfname); + else + buf->b_sfname = NULL; + VIM_CLEAR(buf->b_ffname); + 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_buffer(buf); + } + else + { + if (del_buf) + { + // Free all internal variables and reset option values, to make + // ":bdel" compatible with Vim 5.7. + free_buffer_stuff(buf, TRUE); + + // Make it look like a new buffer. + buf->b_flags = BF_CHECK_RO | BF_NEVERLOADED; + + // Init the options when loaded again. + buf->b_p_initialized = FALSE; + } + buf_clear_file(buf); + if (del_buf) + buf->b_p_bl = FALSE; + } + // NOTE: at this point "curbuf" may be invalid! + return TRUE; +} + +/* + * Make buffer not contain a file. + */ + void +buf_clear_file(buf_T *buf) +{ + buf->b_ml.ml_line_count = 1; + unchanged(buf, TRUE, TRUE); + buf->b_shortname = FALSE; + buf->b_p_eof = FALSE; + buf->b_start_eof = FALSE; + buf->b_p_eol = TRUE; + buf->b_start_eol = TRUE; + buf->b_p_bomb = FALSE; + buf->b_start_bomb = FALSE; + buf->b_ml.ml_mfp = NULL; + buf->b_ml.ml_flags = ML_EMPTY; // empty buffer +#ifdef FEAT_NETBEANS_INTG + netbeans_deleted_all_lines(buf); +#endif +} + +/* + * buf_freeall() - free all things allocated for a buffer that are related to + * the file. Careful: get here with "curwin" NULL when exiting. + * flags: + * BFA_DEL buffer is going to be deleted + * BFA_WIPE buffer is going to be wiped out + * BFA_KEEP_UNDO do not free undo information + * BFA_IGNORE_ABORT don't abort even when aborting() returns TRUE + */ + void +buf_freeall(buf_T *buf, int flags) +{ + int is_curbuf = (buf == curbuf); + bufref_T bufref; + int is_curwin = (curwin != NULL && curwin->w_buffer == buf); + win_T *the_curwin = curwin; + tabpage_T *the_curtab = curtab; + + // Make sure the buffer isn't closed by autocommands. + ++buf->b_locked; + ++buf->b_locked_split; + set_bufref(&bufref, buf); + if (buf->b_ml.ml_mfp != NULL) + { + if (apply_autocmds(EVENT_BUFUNLOAD, buf->b_fname, buf->b_fname, + FALSE, buf) + && !bufref_valid(&bufref)) + // autocommands deleted the buffer + return; + } + if ((flags & BFA_DEL) && buf->b_p_bl) + { + if (apply_autocmds(EVENT_BUFDELETE, buf->b_fname, buf->b_fname, + FALSE, buf) + && !bufref_valid(&bufref)) + // autocommands deleted the buffer + return; + } + if (flags & BFA_WIPE) + { + if (apply_autocmds(EVENT_BUFWIPEOUT, buf->b_fname, buf->b_fname, + FALSE, buf) + && !bufref_valid(&bufref)) + // autocommands deleted the buffer + return; + } + --buf->b_locked; + --buf->b_locked_split; + + // If the buffer was in curwin and the window has changed, go back to that + // window, if it still exists. This avoids that ":edit x" triggering a + // "tabnext" BufUnload autocmd leaves a window behind without a buffer. + if (is_curwin && curwin != the_curwin && win_valid_any_tab(the_curwin)) + { + block_autocmds(); + goto_tabpage_win(the_curtab, the_curwin); + unblock_autocmds(); + } + +#ifdef FEAT_EVAL + // autocmds may abort script processing + if ((flags & BFA_IGNORE_ABORT) == 0 && aborting()) + return; +#endif + + // It's possible that autocommands change curbuf to the one being deleted. + // This might cause curbuf to be deleted unexpectedly. But in some cases + // it's OK to delete the curbuf, because a new one is obtained anyway. + // Therefore only return if curbuf changed to the deleted buffer. + if (buf == curbuf && !is_curbuf) + return; +#ifdef FEAT_DIFF + diff_buf_delete(buf); // Can't use 'diff' for unloaded buffer. +#endif +#ifdef FEAT_SYN_HL + // Remove any ownsyntax, unless exiting. + if (curwin != NULL && curwin->w_buffer == buf) + reset_synblock(curwin); +#endif + +#ifdef FEAT_FOLDING + // No folds in an empty buffer. + { + win_T *win; + tabpage_T *tp; + + FOR_ALL_TAB_WINDOWS(tp, win) + if (win->w_buffer == buf) + clearFolding(win); + } +#endif + +#ifdef FEAT_TCL + tcl_buffer_free(buf); +#endif + ml_close(buf, TRUE); // close and delete the memline/memfile + buf->b_ml.ml_line_count = 0; // no lines in buffer + if ((flags & BFA_KEEP_UNDO) == 0) + { + u_blockfree(buf); // free the memory allocated for undo + u_clearall(buf); // reset all undo information + } +#ifdef FEAT_SYN_HL + syntax_clear(&buf->b_s); // reset syntax info +#endif +#ifdef FEAT_PROP_POPUP + clear_buf_prop_types(buf); +#endif + buf->b_flags &= ~BF_READERR; // a read error is no longer relevant +} + +/* + * Free a buffer structure and the things it contains related to the buffer + * itself (not the file, that must have been done already). + */ + static void +free_buffer(buf_T *buf) +{ + ++buf_free_count; + free_buffer_stuff(buf, TRUE); +#ifdef FEAT_EVAL + // b:changedtick uses an item in buf_T, remove it now + dictitem_remove(buf->b_vars, (dictitem_T *)&buf->b_ct_di, "free buffer"); + unref_var_dict(buf->b_vars); + remove_listeners(buf); +#endif +#ifdef FEAT_LUA + lua_buffer_free(buf); +#endif +#ifdef FEAT_MZSCHEME + mzscheme_buffer_free(buf); +#endif +#ifdef FEAT_PERL + perl_buf_free(buf); +#endif +#ifdef FEAT_PYTHON + python_buffer_free(buf); +#endif +#ifdef FEAT_PYTHON3 + python3_buffer_free(buf); +#endif +#ifdef FEAT_RUBY + ruby_buffer_free(buf); +#endif +#ifdef FEAT_JOB_CHANNEL + channel_buffer_free(buf); +#endif +#ifdef FEAT_TERMINAL + free_terminal(buf); +#endif +#ifdef FEAT_JOB_CHANNEL + vim_free(buf->b_prompt_text); + free_callback(&buf->b_prompt_callback); + free_callback(&buf->b_prompt_interrupt); +#endif + + buf_hashtab_remove(buf); + + aubuflocal_remove(buf); + + if (autocmd_busy) + { + // Do not free the buffer structure while autocommands are executing, + // it's still needed. Free it when autocmd_busy is reset. + buf->b_next = au_pending_free_buf; + au_pending_free_buf = buf; + } + else + { + vim_free(buf); + if (curbuf == buf) + curbuf = NULL; // make clear it's not to be used + } +} + +/* + * Initializes b:changedtick. + */ + static void +init_changedtick(buf_T *buf) +{ + dictitem_T *di = (dictitem_T *)&buf->b_ct_di; + + di->di_flags = DI_FLAGS_FIX | DI_FLAGS_RO; + di->di_tv.v_type = VAR_NUMBER; + di->di_tv.v_lock = VAR_FIXED; + di->di_tv.vval.v_number = 0; + +#ifdef FEAT_EVAL + STRCPY(buf->b_ct_di.di_key, "changedtick"); + (void)dict_add(buf->b_vars, di); +#endif +} + +/* + * Free the b_wininfo list for buffer "buf". + */ + static void +clear_wininfo(buf_T *buf) +{ + wininfo_T *wip; + + while (buf->b_wininfo != NULL) + { + wip = buf->b_wininfo; + buf->b_wininfo = wip->wi_next; + free_wininfo(wip); + } +} + +/* + * Free stuff in the buffer for ":bdel" and when wiping out the buffer. + */ + static void +free_buffer_stuff( + buf_T *buf, + int free_options) // free options as well +{ + if (free_options) + { + clear_wininfo(buf); // including window-local options + free_buf_options(buf, TRUE); +#ifdef FEAT_SPELL + ga_clear(&buf->b_s.b_langp); +#endif + } +#ifdef FEAT_EVAL + { + varnumber_T tick = CHANGEDTICK(buf); + + vars_clear(&buf->b_vars->dv_hashtab); // free all buffer variables + hash_init(&buf->b_vars->dv_hashtab); + init_changedtick(buf); + CHANGEDTICK(buf) = tick; + remove_listeners(buf); + } +#endif + uc_clear(&buf->b_ucmds); // clear local user commands +#ifdef FEAT_SIGNS + buf_delete_signs(buf, (char_u *)"*"); // delete any signs +#endif +#ifdef FEAT_NETBEANS_INTG + netbeans_file_killed(buf); +#endif +#ifdef FEAT_PROP_POPUP + ga_clear_strings(&buf->b_textprop_text); +#endif + map_clear_mode(buf, MAP_ALL_MODES, TRUE, FALSE); // clear local mappings + map_clear_mode(buf, MAP_ALL_MODES, TRUE, TRUE); // clear local abbrevs + VIM_CLEAR(buf->b_start_fenc); +} + +/* + * Free one wininfo_T. + */ + void +free_wininfo(wininfo_T *wip) +{ + if (wip->wi_optset) + { + clear_winopt(&wip->wi_opt); +#ifdef FEAT_FOLDING + deleteFoldRecurse(&wip->wi_folds); +#endif + } + vim_free(wip); +} + +/* + * Go to another buffer. Handles the result of the ATTENTION dialog. + */ + void +goto_buffer( + exarg_T *eap, + int start, + int dir, + int count) +{ + bufref_T old_curbuf; + int save_sea = swap_exists_action; + + set_bufref(&old_curbuf, curbuf); + + if (swap_exists_action == SEA_NONE) + swap_exists_action = SEA_DIALOG; + (void)do_buffer(*eap->cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO, + start, dir, count, eap->forceit); + if (swap_exists_action == SEA_QUIT && *eap->cmd == 's') + { +#if defined(FEAT_EVAL) + cleanup_T cs; + + // Reset the error/interrupt/exception state here so that + // aborting() returns FALSE when closing a window. + enter_cleanup(&cs); +#endif + + // Quitting means closing the split window, nothing else. + win_close(curwin, TRUE); + swap_exists_action = save_sea; + swap_exists_did_quit = TRUE; + +#if defined(FEAT_EVAL) + // Restore the error/interrupt/exception state if not discarded by a + // new aborting error, interrupt, or uncaught exception. + leave_cleanup(&cs); +#endif + } + else + handle_swap_exists(&old_curbuf); +} + +/* + * Handle the situation of swap_exists_action being set. + * It is allowed for "old_curbuf" to be NULL or invalid. + */ + void +handle_swap_exists(bufref_T *old_curbuf) +{ +#if defined(FEAT_EVAL) + cleanup_T cs; +#endif +#ifdef FEAT_SYN_HL + long old_tw = curbuf->b_p_tw; +#endif + buf_T *buf; + + if (swap_exists_action == SEA_QUIT) + { +#if defined(FEAT_EVAL) + // Reset the error/interrupt/exception state here so that + // aborting() returns FALSE when closing a buffer. + enter_cleanup(&cs); +#endif + + // User selected Quit at ATTENTION prompt. Go back to previous + // buffer. If that buffer is gone or the same as the current one, + // open a new, empty buffer. + swap_exists_action = SEA_NONE; // don't want it again + swap_exists_did_quit = TRUE; + close_buffer(curwin, curbuf, DOBUF_UNLOAD, FALSE, FALSE); + if (old_curbuf == NULL || !bufref_valid(old_curbuf) + || old_curbuf->br_buf == curbuf) + { + // Block autocommands here because curwin->w_buffer is NULL. + block_autocmds(); + buf = buflist_new(NULL, NULL, 1L, BLN_CURBUF | BLN_LISTED); + unblock_autocmds(); + } + else + buf = old_curbuf->br_buf; + if (buf != NULL) + { + int old_msg_silent = msg_silent; + + if (shortmess(SHM_FILEINFO)) + msg_silent = 1; // prevent fileinfo message + enter_buffer(buf); + // restore msg_silent, so that the command line will be shown + msg_silent = old_msg_silent; + +#ifdef FEAT_SYN_HL + if (old_tw != curbuf->b_p_tw) + check_colorcolumn(curwin); +#endif + } + // If "old_curbuf" is NULL we are in big trouble here... + +#if defined(FEAT_EVAL) + // Restore the error/interrupt/exception state if not discarded by a + // new aborting error, interrupt, or uncaught exception. + leave_cleanup(&cs); +#endif + } + else if (swap_exists_action == SEA_RECOVER) + { +#if defined(FEAT_EVAL) + // Reset the error/interrupt/exception state here so that + // aborting() returns FALSE when closing a buffer. + enter_cleanup(&cs); +#endif + + // User selected Recover at ATTENTION prompt. + msg_scroll = TRUE; + ml_recover(FALSE); + msg_puts("\n"); // don't overwrite the last message + cmdline_row = msg_row; + do_modelines(0); + +#if defined(FEAT_EVAL) + // Restore the error/interrupt/exception state if not discarded by a + // new aborting error, interrupt, or uncaught exception. + leave_cleanup(&cs); +#endif + } + swap_exists_action = SEA_NONE; +} + +/* + * Make the current buffer empty. + * Used when it is wiped out and it's the last buffer. + */ + static int +empty_curbuf( + int close_others, + int forceit, + int action) +{ + int retval; + buf_T *buf = curbuf; + bufref_T bufref; + + if (action == DOBUF_UNLOAD) + { + emsg(_(e_cannot_unload_last_buffer)); + return FAIL; + } + + set_bufref(&bufref, buf); + if (close_others) + // Close any other windows on this buffer, then make it empty. + close_windows(buf, TRUE); + + setpcmark(); + retval = do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, + forceit ? ECMD_FORCEIT : 0, curwin); + + // 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. + if (buf != curbuf && bufref_valid(&bufref) && buf->b_nwindows == 0) + close_buffer(NULL, buf, action, FALSE, FALSE); + if (!close_others) + need_fileinfo = FALSE; + return retval; +} + +/* + * Implementation of the commands 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) from buffer list + * action == DOBUF_WIPE delete specified buffer(s) really + * action == DOBUF_WIPE_REUSE idem, and add number to "buf_reuse" + * + * 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. + */ + static int +do_buffer_ext( + int action, + int start, + int dir, // FORWARD or BACKWARD + int count, // buffer number or number of buffers + int flags) // DOBUF_FORCEIT etc. +{ + buf_T *buf; + buf_T *bp; + int unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL + || action == DOBUF_WIPE || action == DOBUF_WIPE_REUSE); + + 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 && !bufIsChanged(buf)); + } + if (!bufIsChanged(buf)) + { + emsg(_(e_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 + { + bp = NULL; + while (count > 0 || (!unload && !buf->b_p_bl && bp != buf)) + { + // remember the buffer where we start, we come back there when all + // buffers are unlisted. + if (bp == NULL) + bp = buf; + if (dir == FORWARD) + { + buf = buf->b_next; + if (buf == NULL) + buf = firstbuf; + } + else + { + buf = buf->b_prev; + if (buf == NULL) + buf = lastbuf; + } + // don't count unlisted buffers + if (unload || buf->b_p_bl) + { + --count; + bp = NULL; // use this buffer as new starting point + } + if (bp == buf) + { + // back where we started, didn't find anything. + emsg(_(e_there_is_no_listed_buffer)); + return FAIL; + } + } + } + + if (buf == NULL) // could not find it + { + if (start == DOBUF_FIRST) + { + // don't warn when deleting + if (!unload) + semsg(_(e_buffer_nr_does_not_exist), count); + } + else if (dir == FORWARD) + emsg(_(e_cannot_go_beyond_last_buffer)); + else + emsg(_(e_cannot_go_before_first_buffer)); + return FAIL; + } +#ifdef FEAT_PROP_POPUP + if ((flags & DOBUF_NOPOPUP) && bt_popup(buf) && !bt_terminal(buf)) + return OK; +#endif + if ((action == DOBUF_GOTO || action == DOBUF_SPLIT) + && (buf->b_flags & BF_DUMMY)) + { + // disallow navigating to the dummy buffer + semsg(_(e_buffer_nr_does_not_exist), count); + return FAIL; + } + +#ifdef FEAT_GUI + need_mouse_correct = TRUE; +#endif + + /* + * delete buffer "buf" from memory and/or the list + */ + if (unload) + { + int forward; + bufref_T bufref; + + if (!can_unload_buffer(buf)) + return FAIL; + + set_bufref(&bufref, buf); + + // When unloading or deleting a buffer that's already unloaded and + // unlisted: fail silently. + if (action != DOBUF_WIPE && action != DOBUF_WIPE_REUSE + && buf->b_ml.ml_mfp == NULL && !buf->b_p_bl) + return FAIL; + + if ((flags & DOBUF_FORCEIT) == 0 && bufIsChanged(buf)) + { +#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) + if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write) + { +# ifdef FEAT_TERMINAL + if (term_job_running(buf->b_term)) + { + if (term_confirm_stop(buf) == FAIL) + return FAIL; + } + else +# endif + { + dialog_changed(buf, FALSE); + if (!bufref_valid(&bufref)) + // Autocommand deleted buffer, oops! It's not changed + // now. + return FAIL; + // If it's still changed fail silently, the dialog already + // mentioned why it fails. + if (bufIsChanged(buf)) + return FAIL; + } + } + else +#endif + { + no_write_message_buf(buf); + return FAIL; + } + } + + // When closing the current buffer stop Visual mode. + if (buf == curbuf && VIsual_active) + end_visual_mode(); + + // If deleting the last (listed) buffer, make it empty. + // The last (listed) buffer cannot be unloaded. + FOR_ALL_BUFFERS(bp) + if (bp->b_p_bl && bp != buf) + break; + if (bp == NULL && buf == curbuf) + return empty_curbuf(TRUE, (flags & DOBUF_FORCEIT), action); + + // If the deleted buffer is the current one, close the current window + // (unless it's the only window). Repeat this so long as we end up in + // a window with this buffer. + while (buf == curbuf + && !(curwin->w_closing || curwin->w_buffer->b_locked > 0) + && (!ONE_WINDOW || first_tabpage->tp_next != NULL)) + { + if (win_close(curwin, FALSE) == FAIL) + break; + } + + // If the buffer to be deleted is not the current one, delete it here. + if (buf != curbuf) + { + close_windows(buf, FALSE); + if (buf != curbuf && bufref_valid(&bufref) && buf->b_nwindows <= 0) + close_buffer(NULL, buf, action, FALSE, FALSE); + return OK; + } + + /* + * Deleting the current buffer: Need to find another buffer to go to. + * There should be another, otherwise it would have been handled + * above. However, autocommands may have deleted all buffers. + * First use au_new_curbuf.br_buf, if it is valid. + * Then prefer the buffer we most recently visited. + * Else try to find one that is loaded, after the current buffer, + * then before the current buffer. + * Finally use any buffer. + */ + buf = NULL; // selected buffer + bp = NULL; // used when no loaded buffer found + if (au_new_curbuf.br_buf != NULL && bufref_valid(&au_new_curbuf)) + buf = au_new_curbuf.br_buf; + else if (curwin->w_jumplistlen > 0) + { + int jumpidx; + + jumpidx = curwin->w_jumplistidx - 1; + if (jumpidx < 0) + jumpidx = curwin->w_jumplistlen - 1; + + forward = jumpidx; + while (jumpidx != curwin->w_jumplistidx) + { + buf = buflist_findnr(curwin->w_jumplist[jumpidx].fmark.fnum); + if (buf != NULL) + { + // Skip current and unlisted bufs. Also skip a quickfix + // buffer, it might be deleted soon. + if (buf == curbuf || !buf->b_p_bl || bt_quickfix(buf)) + buf = NULL; + else if (buf->b_ml.ml_mfp == NULL) + { + // skip unloaded buf, but may keep it for later + if (bp == NULL) + bp = buf; + buf = NULL; + } + } + if (buf != NULL) // found a valid buffer: stop searching + break; + // advance to older entry in jump list + if (!jumpidx && curwin->w_jumplistidx == curwin->w_jumplistlen) + break; + if (--jumpidx < 0) + jumpidx = curwin->w_jumplistlen - 1; + if (jumpidx == forward) // List exhausted for sure + break; + } + } + + if (buf == NULL) // No previous buffer, Try 2'nd approach + { + forward = TRUE; + buf = curbuf->b_next; + for (;;) + { + if (buf == NULL) + { + if (!forward) // tried both directions + break; + buf = curbuf->b_prev; + forward = FALSE; + continue; + } + // in non-help buffer, try to skip help buffers, and vv + if (buf->b_help == curbuf->b_help && buf->b_p_bl + && !bt_quickfix(buf)) + { + if (buf->b_ml.ml_mfp != NULL) // found loaded buffer + break; + if (bp == NULL) // remember unloaded buf for later + bp = buf; + } + if (forward) + buf = buf->b_next; + else + buf = buf->b_prev; + } + } + if (buf == NULL) // No loaded buffer, use unloaded one + buf = bp; + if (buf == NULL) // No loaded buffer, find listed one + { + FOR_ALL_BUFFERS(buf) + if (buf->b_p_bl && buf != curbuf && !bt_quickfix(buf)) + break; + } + if (buf == NULL) // Still no buffer, just take one + { + if (curbuf->b_next != NULL) + buf = curbuf->b_next; + else + buf = curbuf->b_prev; + if (bt_quickfix(buf)) + buf = NULL; + } + } + + if (buf == NULL) + { + // Autocommands must have wiped out all other buffers. Only option + // now is to make the current buffer empty. + return empty_curbuf(FALSE, (flags & DOBUF_FORCEIT), action); + } + + /* + * make "buf" the current buffer + */ + if (action == DOBUF_SPLIT) // split window first + { + // If 'switchbuf' is set jump to the window containing "buf". + if (swbuf_goto_win_with_buf(buf) != NULL) + return OK; + + if (win_split(0, 0) == FAIL) + return FAIL; + } + + // go to current buffer - nothing to do + if (buf == curbuf) + return OK; + + // Check if the current buffer may be abandoned. + if (action == DOBUF_GOTO && !can_abandon(curbuf, (flags & DOBUF_FORCEIT))) + { +#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) + if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write) + { +# ifdef FEAT_TERMINAL + if (term_job_running(curbuf->b_term)) + { + if (term_confirm_stop(curbuf) == FAIL) + return FAIL; + // Manually kill the terminal here because this command will + // hide it otherwise. + free_terminal(curbuf); + } + else +# endif + { + bufref_T bufref; + + set_bufref(&bufref, buf); + dialog_changed(curbuf, FALSE); + if (!bufref_valid(&bufref)) + // Autocommand deleted buffer, oops! + return FAIL; + + if (bufIsChanged(curbuf)) + { + no_write_message(); + return FAIL; + } + } + } + else +#endif + { + no_write_message(); + return FAIL; + } + } + + // Go to the other buffer. + set_curbuf(buf, action); + + if (action == DOBUF_SPLIT) + RESET_BINDING(curwin); // reset 'scrollbind' and 'cursorbind' + +#if defined(FEAT_EVAL) + if (aborting()) // autocmds may abort script processing + return FAIL; +#endif + + return OK; +} + + int +do_buffer( + int action, + int start, + int dir, // FORWARD or BACKWARD + int count, // buffer number or number of buffers + int forceit) // TRUE when using ! +{ + return do_buffer_ext(action, start, dir, count, + forceit ? DOBUF_FORCEIT : 0); +} + +/* + * 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"), DOBUF_WIPE (":bwipeout") or + * DOBUF_DEL (":bdel") + * + * Returns error message or NULL + */ + char * +do_bufdel( + 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 nr or last buffer nr in a range + int forceit) +{ + int do_current = 0; // delete current buffer? + int deleted = 0; // number of buffers deleted + char *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 ex_errmsg(e_trailing_characters_str, arg); + bnr = start_bnr; + } + else // addr_count == 1 + bnr = end_bnr; + + for ( ;!got_int; ui_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_ext(command, DOBUF_FIRST, FORWARD, bnr, + DOBUF_NOPOPUP | (forceit ? DOBUF_FORCEIT : 0)) == 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 (!VIM_ISDIGIT(*arg)) + { + p = skiptowhite_esc(arg); + bnr = buflist_findpat(arg, p, + command == DOBUF_WIPE || command == DOBUF_WIPE_REUSE, + FALSE, FALSE); + 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) + { + if (command == DOBUF_UNLOAD) + STRCPY(IObuff, _(e_no_buffers_were_unloaded)); + else if (command == DOBUF_DEL) + STRCPY(IObuff, _(e_no_buffers_were_deleted)); + else + STRCPY(IObuff, _(e_no_buffers_were_wiped_out)); + errormsg = (char *)IObuff; + } + else if (deleted >= p_report) + { + if (command == DOBUF_UNLOAD) + smsg(NGETTEXT("%d buffer unloaded", + "%d buffers unloaded", deleted), deleted); + else if (command == DOBUF_DEL) + smsg(NGETTEXT("%d buffer deleted", + "%d buffers deleted", deleted), deleted); + else + smsg(NGETTEXT("%d buffer wiped out", + "%d buffers wiped out", deleted), deleted); + } + } + + return errormsg; +} + +/* + * Set current buffer to "buf". Executes autocommands and closes current + * buffer. "action" tells how to close the current buffer: + * DOBUF_GOTO free or hide it + * DOBUF_SPLIT nothing + * DOBUF_UNLOAD unload it + * DOBUF_DEL delete it + * DOBUF_WIPE wipe it out + * DOBUF_WIPE_REUSE wipe it out and add to "buf_reuse" + */ + void +set_curbuf(buf_T *buf, int action) +{ + buf_T *prevbuf; + int unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL + || action == DOBUF_WIPE || action == DOBUF_WIPE_REUSE); +#ifdef FEAT_SYN_HL + long old_tw = curbuf->b_p_tw; +#endif + bufref_T newbufref; + bufref_T prevbufref; + int valid; + + setpcmark(); + if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0) + curwin->w_alt_fnum = curbuf->b_fnum; // remember alternate file + buflist_altfpos(curwin); // remember curpos + + // Don't restart Select mode after switching to another buffer. + VIsual_reselect = FALSE; + + // close_windows() or apply_autocmds() may change curbuf and wipe out "buf" + prevbuf = curbuf; + set_bufref(&prevbufref, prevbuf); + set_bufref(&newbufref, buf); + + // Autocommands may delete the current buffer and/or the buffer we want to + // go to. In those cases don't close the buffer. + if (!apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, FALSE, curbuf) + || (bufref_valid(&prevbufref) + && bufref_valid(&newbufref) +#ifdef FEAT_EVAL + && !aborting() +#endif + )) + { +#ifdef FEAT_SYN_HL + if (prevbuf == curwin->w_buffer) + reset_synblock(curwin); +#endif + if (unload) + close_windows(prevbuf, FALSE); +#if defined(FEAT_EVAL) + if (bufref_valid(&prevbufref) && !aborting()) +#else + if (bufref_valid(&prevbufref)) +#endif + { + win_T *previouswin = curwin; + + // Do not sync when in Insert mode and the buffer is open in + // another window, might be a timer doing something in another + // window. + if (prevbuf == curbuf + && ((State & MODE_INSERT) == 0 || curbuf->b_nwindows <= 1)) + u_sync(FALSE); + close_buffer(prevbuf == curwin->w_buffer ? curwin : NULL, prevbuf, + unload ? action : (action == DOBUF_GOTO + && !buf_hide(prevbuf) + && !bufIsChanged(prevbuf)) ? DOBUF_UNLOAD : 0, + FALSE, FALSE); + if (curwin != previouswin && win_valid(previouswin)) + // autocommands changed curwin, Grr! + curwin = previouswin; + } + } + // An autocommand may have deleted "buf", already entered it (e.g., when + // it did ":bunload") or aborted the script processing. + // If curwin->w_buffer is null, enter_buffer() will make it valid again + valid = buf_valid(buf); + if ((valid && buf != curbuf +#ifdef FEAT_EVAL + && !aborting() +#endif + ) || curwin->w_buffer == NULL) + { + // If the buffer is not valid but curwin->w_buffer is NULL we must + // enter some buffer. Using the last one is hopefully OK. + if (!valid) + enter_buffer(lastbuf); + else + enter_buffer(buf); +#ifdef FEAT_SYN_HL + if (old_tw != curbuf->b_p_tw) + check_colorcolumn(curwin); +#endif + } +} + +/* + * Enter a new current buffer. + * Old curbuf must have been abandoned already! This also means "curbuf" may + * be pointing to freed memory. + */ + static void +enter_buffer(buf_T *buf) +{ + // when closing the current buffer stop Visual mode + if (VIsual_active +#if defined(EXITFREE) + && !entered_free_all_mem +#endif + ) + end_visual_mode(); + + // Get the buffer in the current window. + curwin->w_buffer = buf; + curbuf = buf; + ++curbuf->b_nwindows; + + // Copy buffer and window local option values. Not for a help buffer. + buf_copy_options(buf, BCO_ENTER | BCO_NOHELP); + if (!buf->b_help) + get_winopts(buf); +#ifdef FEAT_FOLDING + else + // Remove all folds in the window. + clearFolding(curwin); + foldUpdateAll(curwin); // update folds (later). +#endif + +#ifdef FEAT_DIFF + if (curwin->w_p_diff) + diff_buf_add(curbuf); +#endif + +#ifdef FEAT_SYN_HL + curwin->w_s = &(curbuf->b_s); +#endif + + // Cursor on first line by default. + curwin->w_cursor.lnum = 1; + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + curwin->w_set_curswant = TRUE; + curwin->w_topline_was_set = FALSE; + + // mark cursor position as being invalid + curwin->w_valid = 0; + + buflist_setfpos(curbuf, curwin, curbuf->b_last_cursor.lnum, + curbuf->b_last_cursor.col, TRUE); + + // Make sure the buffer is loaded. + if (curbuf->b_ml.ml_mfp == NULL) // need to load the file + { + // If there is no filetype, allow for detecting one. Esp. useful for + // ":ball" used in an autocommand. If there already is a filetype we + // might prefer to keep it. + if (*curbuf->b_p_ft == NUL) + did_filetype = FALSE; + + open_buffer(FALSE, NULL, 0); + } + else + { + if (!msg_silent && !shortmess(SHM_FILEINFO)) + need_fileinfo = TRUE; // display file info after redraw + + // check if file changed + (void)buf_check_timestamp(curbuf, FALSE); + + curwin->w_topline = 1; +#ifdef FEAT_DIFF + curwin->w_topfill = 0; +#endif + apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf); + apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf); + } + + // If autocommands did not change the cursor position, restore cursor lnum + // and possibly cursor col. + if (curwin->w_cursor.lnum == 1 && inindent(0)) + buflist_getfpos(); + + check_arg_idx(curwin); // check for valid arg_idx + maketitle(); + // when autocmds didn't change it + if (curwin->w_topline == 1 && !curwin->w_topline_was_set) + scroll_cursor_halfway(FALSE, FALSE); // redisplay at correct position + +#ifdef FEAT_NETBEANS_INTG + // Send fileOpened event because we've changed buffers. + netbeans_file_activated(curbuf); +#endif + + // Change directories when the 'acd' option is set. + DO_AUTOCHDIR; + +#ifdef FEAT_KEYMAP + if (curbuf->b_kmap_state & KEYMAP_INIT) + (void)keymap_init(); +#endif +#ifdef FEAT_SPELL + // May need to set the spell language. Can only do this after the buffer + // has been properly setup. + if (!curbuf->b_help && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) + (void)parse_spelllang(curwin); +#endif +#ifdef FEAT_VIMINFO + curbuf->b_last_used = vim_time(); +#endif + + redraw_later(UPD_NOT_VALID); +} + +#if defined(FEAT_AUTOCHDIR) || defined(PROTO) +/* + * Change to the directory of the current buffer. + * Don't do this while still starting up. + */ + void +do_autochdir(void) +{ + if ((starting == 0 || test_autochdir) + && curbuf->b_ffname != NULL + && vim_chdirfile(curbuf->b_ffname, "auto") == OK) + { + shorten_fnames(TRUE); + last_chdir_reason = "autochdir"; + } +} +#endif + + static void +no_write_message_buf(buf_T *buf UNUSED) +{ +#ifdef FEAT_TERMINAL + if (term_job_running(buf->b_term)) + emsg(_(e_job_still_running_add_bang_to_end_the_job)); + else +#endif + semsg(_(e_no_write_since_last_change_for_buffer_nr_add_bang_to_override), + buf->b_fnum); +} + + void +no_write_message(void) +{ +#ifdef FEAT_TERMINAL + if (term_job_running(curbuf->b_term)) + emsg(_(e_job_still_running_add_bang_to_end_the_job)); + else +#endif + emsg(_(e_no_write_since_last_change_add_bang_to_override)); +} + + void +no_write_message_nobang(buf_T *buf UNUSED) +{ +#ifdef FEAT_TERMINAL + if (term_job_running(buf->b_term)) + emsg(_(e_job_still_running)); + else +#endif + emsg(_(e_no_write_since_last_change)); +} + +/* + * functions for dealing with the buffer list + */ + +/* + * Return TRUE if the current buffer is empty, unnamed, unmodified and used in + * only one window. That means it can be re-used. + */ + int +curbuf_reusable(void) +{ + return (curbuf != NULL + && curbuf->b_ffname == NULL + && curbuf->b_nwindows <= 1 + && (curbuf->b_ml.ml_mfp == NULL || BUFEMPTY()) + && !bt_quickfix(curbuf) + && !curbufIsChanged()); +} + +/* + * 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 (flags & BLN_CURBUF) is TRUE, may use current buffer. + * If (flags & BLN_LISTED) is TRUE, add new buffer to buffer list. + * If (flags & BLN_DUMMY) is TRUE, don't count it as a real buffer. + * If (flags & BLN_NEW) is TRUE, don't use an existing buffer. + * If (flags & BLN_NOOPT) is TRUE, don't copy options from the current buffer + * if the buffer already exists. + * If (flags & BLN_REUSE) is TRUE, may use buffer number from "buf_reuse". + * This is the ONLY way to create a new buffer. + */ + buf_T * +buflist_new( + char_u *ffname_arg, // full path of fname or relative + char_u *sfname_arg, // short fname or NULL + linenr_T lnum, // preferred cursor line + int flags) // BLN_ defines +{ + char_u *ffname = ffname_arg; + char_u *sfname = sfname_arg; + buf_T *buf; +#ifdef UNIX + stat_T st; +#endif + + if (top_file_num == 1) + hash_init(&buf_hashtab); + + fname_expand(curbuf, &ffname, &sfname); // will allocate ffname + + /* + * If the file name already exists in the list, update the entry. + */ +#ifdef UNIX + // On Unix we can use inode numbers when the file exists. Works better + // for hard links. + if (sfname == NULL || mch_stat((char *)sfname, &st) < 0) + st.st_dev = (dev_T)-1; +#endif + if (ffname != NULL && !(flags & (BLN_DUMMY | BLN_NEW)) && (buf = +#ifdef UNIX + buflist_findname_stat(ffname, &st) +#else + buflist_findname(ffname) +#endif + ) != NULL) + { + vim_free(ffname); + if (lnum != 0) + buflist_setfpos(buf, (flags & BLN_NOCURWIN) ? NULL : curwin, + lnum, (colnr_T)0, FALSE); + + if ((flags & BLN_NOOPT) == 0) + // copy the options now, if 'cpo' doesn't have 's' and not done + // already + buf_copy_options(buf, 0); + + if ((flags & BLN_LISTED) && !buf->b_p_bl) + { + bufref_T bufref; + + buf->b_p_bl = TRUE; + set_bufref(&bufref, buf); + if (!(flags & BLN_DUMMY)) + { + if (apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, buf) + && !bufref_valid(&bufref)) + return NULL; + } + } + 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! + * (A spell file buffer is allocated in spell.c, but that's not a normal + * buffer.) + */ + buf = NULL; + if ((flags & BLN_CURBUF) && curbuf_reusable()) + { + buf = curbuf; + // It's like this buffer is deleted. Watch out for autocommands that + // change curbuf! If that happens, allocate a new buffer anyway. + buf_freeall(buf, BFA_WIPE | BFA_DEL); + if (buf != curbuf) // autocommands deleted the buffer! + return NULL; +#ifdef FEAT_EVAL + if (aborting()) // autocmds may abort script processing + { + vim_free(ffname); + return NULL; + } +#endif + } + if (buf != curbuf || curbuf == NULL) + { + buf = ALLOC_CLEAR_ONE(buf_T); + if (buf == NULL) + { + vim_free(ffname); + return NULL; + } +#ifdef FEAT_EVAL + // init b: variables + buf->b_vars = dict_alloc_id(aid_newbuf_bvars); + if (buf->b_vars == NULL) + { + vim_free(ffname); + vim_free(buf); + return NULL; + } + init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE); +#endif + init_changedtick(buf); + } + + if (ffname != NULL) + { + buf->b_ffname = ffname; + buf->b_sfname = vim_strsave(sfname); + } + + clear_wininfo(buf); + buf->b_wininfo = ALLOC_CLEAR_ONE(wininfo_T); + + if ((ffname != NULL && (buf->b_ffname == NULL || buf->b_sfname == NULL)) + || buf->b_wininfo == NULL) + { + if (buf->b_sfname != buf->b_ffname) + VIM_CLEAR(buf->b_sfname); + else + buf->b_sfname = NULL; + VIM_CLEAR(buf->b_ffname); + if (buf != curbuf) + free_buffer(buf); + return NULL; + } + + if (buf == curbuf) + { + free_buffer_stuff(buf, FALSE); // delete local variables et al. + + // Init the options. + buf->b_p_initialized = FALSE; + buf_copy_options(buf, BCO_ENTER); + +#ifdef FEAT_KEYMAP + // need to reload lmaps and set b:keymap_name + curbuf->b_kmap_state |= KEYMAP_INIT; +#endif + } + else + { + // put the 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; + + if ((flags & BLN_REUSE) && buf_reuse.ga_len > 0) + { + // Recycle a previously used buffer number. Used for buffers which + // are normally hidden, e.g. in a popup window. Avoids that the + // buffer number grows rapidly. + --buf_reuse.ga_len; + buf->b_fnum = ((int *)buf_reuse.ga_data)[buf_reuse.ga_len]; + + // Move buffer to the right place in the buffer list. + while (buf->b_prev != NULL && buf->b_fnum < buf->b_prev->b_fnum) + { + buf_T *prev = buf->b_prev; + + prev->b_next = buf->b_next; + if (prev->b_next != NULL) + prev->b_next->b_prev = prev; + buf->b_next = prev; + buf->b_prev = prev->b_prev; + if (buf->b_prev != NULL) + buf->b_prev->b_next = buf; + prev->b_prev = buf; + if (lastbuf == buf) + lastbuf = prev; + if (firstbuf == prev) + firstbuf = buf; + } + } + else + buf->b_fnum = top_file_num++; + if (top_file_num < 0) // wrap around (may cause duplicates) + { + emsg(_("W14: Warning: List of file names overflow")); + if (emsg_silent == 0 && !in_assert_fails) + { + out_flush(); + ui_delay(3001L, TRUE); // make sure it is noticed + } + top_file_num = 1; + } + buf_hashtab_add(buf); + + // Always copy the options from the current buffer. + buf_copy_options(buf, BCO_ALWAYS); + } + + buf->b_wininfo->wi_fpos.lnum = lnum; + buf->b_wininfo->wi_win = curwin; + +#ifdef FEAT_SYN_HL + hash_init(&buf->b_s.b_keywtab); + hash_init(&buf->b_s.b_keywtab_ic); +#endif + + buf->b_fname = buf->b_sfname; +#ifdef UNIX + if (st.st_dev == (dev_T)-1) + buf->b_dev_valid = FALSE; + else + { + buf->b_dev_valid = TRUE; + buf->b_dev = st.st_dev; + buf->b_ino = st.st_ino; + } +#endif + buf->b_u_synced = TRUE; + buf->b_flags = BF_CHECK_RO | BF_NEVERLOADED; + if (flags & BLN_DUMMY) + buf->b_flags |= BF_DUMMY; + buf_clear_file(buf); + clrallmarks(buf); // clear marks + fmarks_check_names(buf); // check file marks for this file + buf->b_p_bl = (flags & BLN_LISTED) ? TRUE : FALSE; // init 'buflisted' + if (!(flags & BLN_DUMMY)) + { + bufref_T bufref; + + // Tricky: these autocommands may change the buffer list. They could + // also split the window with re-using the one empty buffer. This may + // result in unexpectedly losing the empty buffer. + set_bufref(&bufref, buf); + if (apply_autocmds(EVENT_BUFNEW, NULL, NULL, FALSE, buf) + && !bufref_valid(&bufref)) + return NULL; + if (flags & BLN_LISTED) + { + if (apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, buf) + && !bufref_valid(&bufref)) + return NULL; + } +#ifdef FEAT_EVAL + if (aborting()) // autocmds may abort script processing + return NULL; +#endif + } + + return buf; +} + +/* + * Free the memory for the options of a buffer. + * If "free_p_ff" is TRUE also free 'fileformat', 'buftype' and + * 'fileencoding'. + */ + void +free_buf_options( + buf_T *buf, + int free_p_ff) +{ + if (free_p_ff) + { + clear_string_option(&buf->b_p_fenc); + clear_string_option(&buf->b_p_ff); + clear_string_option(&buf->b_p_bh); + clear_string_option(&buf->b_p_bt); + } +#ifdef FEAT_FIND_ID + clear_string_option(&buf->b_p_def); + clear_string_option(&buf->b_p_inc); +# ifdef FEAT_EVAL + clear_string_option(&buf->b_p_inex); +# endif +#endif +#if defined(FEAT_EVAL) + clear_string_option(&buf->b_p_inde); + clear_string_option(&buf->b_p_indk); +#endif +#if defined(FEAT_BEVAL) && defined(FEAT_EVAL) + clear_string_option(&buf->b_p_bexpr); +#endif +#if defined(FEAT_CRYPT) + clear_string_option(&buf->b_p_cm); +#endif + clear_string_option(&buf->b_p_fp); +#if defined(FEAT_EVAL) + clear_string_option(&buf->b_p_fex); +#endif +#ifdef FEAT_CRYPT +# ifdef FEAT_SODIUM + if (buf->b_p_key != NULL && *buf->b_p_key != NUL + && crypt_method_is_sodium(crypt_get_method_nr(buf))) + crypt_sodium_munlock(buf->b_p_key, STRLEN(buf->b_p_key)); +# endif + clear_string_option(&buf->b_p_key); +#endif + clear_string_option(&buf->b_p_kp); + clear_string_option(&buf->b_p_mps); + clear_string_option(&buf->b_p_fo); + clear_string_option(&buf->b_p_flp); + clear_string_option(&buf->b_p_isk); +#ifdef FEAT_VARTABS + clear_string_option(&buf->b_p_vsts); + VIM_CLEAR(buf->b_p_vsts_nopaste); + VIM_CLEAR(buf->b_p_vsts_array); + clear_string_option(&buf->b_p_vts); + VIM_CLEAR(buf->b_p_vts_array); +#endif +#ifdef FEAT_KEYMAP + clear_string_option(&buf->b_p_keymap); + keymap_clear(&buf->b_kmap_ga); + ga_clear(&buf->b_kmap_ga); +#endif + clear_string_option(&buf->b_p_com); +#ifdef FEAT_FOLDING + clear_string_option(&buf->b_p_cms); +#endif + clear_string_option(&buf->b_p_nf); +#ifdef FEAT_SYN_HL + clear_string_option(&buf->b_p_syn); + clear_string_option(&buf->b_s.b_syn_isk); +#endif +#ifdef FEAT_SPELL + clear_string_option(&buf->b_s.b_p_spc); + clear_string_option(&buf->b_s.b_p_spf); + vim_regfree(buf->b_s.b_cap_prog); + buf->b_s.b_cap_prog = NULL; + clear_string_option(&buf->b_s.b_p_spl); + clear_string_option(&buf->b_s.b_p_spo); +#endif + clear_string_option(&buf->b_p_sua); + clear_string_option(&buf->b_p_ft); + clear_string_option(&buf->b_p_cink); + clear_string_option(&buf->b_p_cino); + clear_string_option(&buf->b_p_lop); + clear_string_option(&buf->b_p_cinsd); + clear_string_option(&buf->b_p_cinw); + clear_string_option(&buf->b_p_cpt); +#ifdef FEAT_COMPL_FUNC + clear_string_option(&buf->b_p_cfu); + free_callback(&buf->b_cfu_cb); + clear_string_option(&buf->b_p_ofu); + free_callback(&buf->b_ofu_cb); + clear_string_option(&buf->b_p_tsrfu); + free_callback(&buf->b_tsrfu_cb); +#endif +#ifdef FEAT_QUICKFIX + clear_string_option(&buf->b_p_gp); + clear_string_option(&buf->b_p_mp); + clear_string_option(&buf->b_p_efm); +#endif + clear_string_option(&buf->b_p_ep); + clear_string_option(&buf->b_p_path); + clear_string_option(&buf->b_p_tags); + clear_string_option(&buf->b_p_tc); +#ifdef FEAT_EVAL + clear_string_option(&buf->b_p_tfu); + free_callback(&buf->b_tfu_cb); +#endif + clear_string_option(&buf->b_p_dict); + clear_string_option(&buf->b_p_tsr); + clear_string_option(&buf->b_p_qe); + buf->b_p_ar = -1; + buf->b_p_ul = NO_LOCAL_UNDOLEVEL; + clear_string_option(&buf->b_p_lw); + clear_string_option(&buf->b_p_bkc); + clear_string_option(&buf->b_p_menc); +} + +/* + * Get alternate file "n". + * Set linenr to "lnum" or altfpos.lnum if "lnum" == 0. + * Also set cursor column to altfpos.col if 'startofline' is not set. + * if (options & GETF_SETMARK) call setpcmark() + * if (options & GETF_ALT) we are jumping to an alternate file. + * if (options & GETF_SWITCH) respect 'switchbuf' settings when jumping + * + * Return FAIL for failure, OK for success. + */ + int +buflist_getfile( + int n, + linenr_T lnum, + int options, + int forceit) +{ + buf_T *buf; + win_T *wp = NULL; + pos_T *fpos; + colnr_T col; + + buf = buflist_findnr(n); + if (buf == NULL) + { + if ((options & GETF_ALT) && n == 0) + emsg(_(e_no_alternate_file)); + else + semsg(_(e_buffer_nr_not_found), n); + return FAIL; + } + + // if alternate file is the current buffer, nothing to do + if (buf == curbuf) + return OK; + + if (text_or_buf_locked()) + return FAIL; + + // altfpos may be changed by getfile(), get it now + if (lnum == 0) + { + fpos = buflist_findfpos(buf); + lnum = fpos->lnum; + col = fpos->col; + } + else + col = 0; + + if (options & GETF_SWITCH) + { + // If 'switchbuf' is set jump to the window containing "buf". + wp = swbuf_goto_win_with_buf(buf); + + // If 'switchbuf' contains "split", "vsplit" or "newtab" and the + // current buffer isn't empty: open new tab or window + if (wp == NULL && (swb_flags & (SWB_VSPLIT | SWB_SPLIT | SWB_NEWTAB)) + && !BUFEMPTY()) + { + if (swb_flags & SWB_NEWTAB) + tabpage_new(); + else if (win_split(0, (swb_flags & SWB_VSPLIT) ? WSP_VERT : 0) + == FAIL) + return FAIL; + RESET_BINDING(curwin); + } + } + + ++RedrawingDisabled; + int retval = FAIL; + if (GETFILE_SUCCESS(getfile(buf->b_fnum, NULL, NULL, + (options & GETF_SETMARK), lnum, forceit))) + { + // cursor is at to BOL and w_cursor.lnum is checked due to getfile() + if (!p_sol && col != 0) + { + curwin->w_cursor.col = col; + check_cursor_col(); + curwin->w_cursor.coladd = 0; + curwin->w_set_curswant = TRUE; + } + retval = OK; + } + + if (RedrawingDisabled > 0) + --RedrawingDisabled; + return retval; +} + +/* + * go to the last know line number for the current buffer + */ + static void +buflist_getfpos(void) +{ + pos_T *fpos; + + fpos = buflist_findfpos(curbuf); + + curwin->w_cursor.lnum = fpos->lnum; + check_cursor_lnum(); + + if (p_sol) + curwin->w_cursor.col = 0; + else + { + curwin->w_cursor.col = fpos->col; + check_cursor_col(); + curwin->w_cursor.coladd = 0; + curwin->w_set_curswant = TRUE; + } +} + +/* + * Find file in buffer list by name (it has to be for the current window). + * Returns NULL if not found. + */ + buf_T * +buflist_findname_exp(char_u *fname) +{ + char_u *ffname; + buf_T *buf = NULL; + + // First make the name into a full path name + ffname = FullName_save(fname, +#ifdef UNIX + TRUE // force expansion, get rid of symbolic links +#else + FALSE +#endif + ); + if (ffname != NULL) + { + buf = buflist_findname(ffname); + vim_free(ffname); + } + return buf; +} + +/* + * Find file in buffer list by name (it has to be for the current window). + * "ffname" must have a full path. + * Skips dummy buffers. + * Returns NULL if not found. + */ + buf_T * +buflist_findname(char_u *ffname) +{ +#ifdef UNIX + stat_T st; + + if (mch_stat((char *)ffname, &st) < 0) + st.st_dev = (dev_T)-1; + return buflist_findname_stat(ffname, &st); +} + +/* + * Same as buflist_findname(), but pass the stat structure to avoid getting it + * twice for the same file. + * Returns NULL if not found. + */ + static buf_T * +buflist_findname_stat( + char_u *ffname, + stat_T *stp) +{ +#endif + buf_T *buf; + + // Start at the last buffer, expect to find a match sooner. + FOR_ALL_BUFS_FROM_LAST(buf) + if ((buf->b_flags & BF_DUMMY) == 0 && !otherfile_buf(buf, ffname +#ifdef UNIX + , stp +#endif + )) + return buf; + return NULL; +} + +/* + * Find file in buffer list by a regexp pattern. + * Return fnum of the found buffer. + * Return < 0 for error. + */ + int +buflist_findpat( + char_u *pattern, + char_u *pattern_end, // pointer to first char after pattern + int unlisted, // find unlisted buffers + int diffmode UNUSED, // find diff-mode buffers only + int curtab_only) // find buffers in current tab only +{ + buf_T *buf; + int match = -1; + int find_listed; + char_u *pat; + char_u *patend; + int attempt; + char_u *p; + int toggledollar; + + // "%" is current file, "%%" or "#" is alternate file + if ((pattern_end == pattern + 1 && (*pattern == '%' || *pattern == '#')) + || (in_vim9script() && pattern_end == pattern + 2 + && pattern[0] == '%' && pattern[1] == '%')) + { + if (*pattern == '#' || pattern_end == pattern + 2) + match = curwin->w_alt_fnum; + else + match = curbuf->b_fnum; +#ifdef FEAT_DIFF + if (diffmode && !diff_mode_buf(buflist_findnr(match))) + match = -1; +#endif + } + + /* + * Try four ways of matching a listed buffer: + * attempt == 0: without '^' or '$' (at any position) + * attempt == 1: with '^' at start (only at position 0) + * attempt == 2: with '$' at end (only match at end) + * attempt == 3: with '^' at start and '$' at end (only full match) + * Repeat this for finding an unlisted buffer if there was no matching + * listed buffer. + */ + else + { + pat = file_pat_to_reg_pat(pattern, pattern_end, NULL, FALSE); + if (pat == NULL) + return -1; + patend = pat + STRLEN(pat) - 1; + toggledollar = (patend > pat && *patend == '$'); + + // First try finding a listed buffer. If not found and "unlisted" + // is TRUE, try finding an unlisted buffer. + find_listed = TRUE; + for (;;) + { + for (attempt = 0; attempt <= 3; ++attempt) + { + regmatch_T regmatch; + + // may add '^' and '$' + if (toggledollar) + *patend = (attempt < 2) ? NUL : '$'; // add/remove '$' + p = pat; + if (*p == '^' && !(attempt & 1)) // add/remove '^' + ++p; + regmatch.regprog = vim_regcomp(p, magic_isset() ? RE_MAGIC : 0); + + FOR_ALL_BUFS_FROM_LAST(buf) + { + if (regmatch.regprog == NULL) + { + // invalid pattern, possibly after switching engine + vim_free(pat); + return -1; + } + if (buf->b_p_bl == find_listed +#ifdef FEAT_DIFF + && (!diffmode || diff_mode_buf(buf)) +#endif + && buflist_match(®match, buf, FALSE) != NULL) + { + if (curtab_only) + { + // Ignore the match if the buffer is not open in + // the current tab. + win_T *wp; + + FOR_ALL_WINDOWS(wp) + if (wp->w_buffer == buf) + break; + if (wp == NULL) + continue; + } + if (match >= 0) // already found a match + { + match = -2; + break; + } + match = buf->b_fnum; // remember first match + } + } + + vim_regfree(regmatch.regprog); + if (match >= 0) // found one match + break; + } + + // Only search for unlisted buffers if there was no match with + // a listed buffer. + if (!unlisted || !find_listed || match != -1) + break; + find_listed = FALSE; + } + + vim_free(pat); + } + + if (match == -2) + semsg(_(e_more_than_one_match_for_str), pattern); + else if (match < 0) + semsg(_(e_no_matching_buffer_for_str), pattern); + return match; +} + +#ifdef FEAT_VIMINFO +typedef struct { + buf_T *buf; + char_u *match; +} bufmatch_T; +#endif + +/* + * Find all buffer names that match. + * For command line expansion of ":buf" and ":sbuf". + * Return OK if matches found, FAIL otherwise. + */ + int +ExpandBufnames( + char_u *pat, + int *num_file, + char_u ***file, + int options) +{ + int count = 0; + buf_T *buf; + int round; + char_u *p; + int attempt; + char_u *patc = NULL; +#ifdef FEAT_VIMINFO + bufmatch_T *matches = NULL; +#endif + int fuzzy; + fuzmatch_str_T *fuzmatch = NULL; + + *num_file = 0; // return values in case of FAIL + *file = NULL; + +#ifdef FEAT_DIFF + if ((options & BUF_DIFF_FILTER) && !curwin->w_p_diff) + return FAIL; +#endif + + fuzzy = cmdline_fuzzy_complete(pat); + + // Make a copy of "pat" and change "^" to "\(^\|[\/]\)" (if doing regular + // expression matching) + if (!fuzzy) + { + if (*pat == '^') + { + patc = alloc(STRLEN(pat) + 11); + if (patc == NULL) + return FAIL; + STRCPY(patc, "\\(^\\|[\\/]\\)"); + STRCPY(patc + 11, pat + 1); + } + else + patc = pat; + } + + // attempt == 0: try match with '\<', match at start of word + // attempt == 1: try match without '\<', match anywhere + for (attempt = 0; attempt <= (fuzzy ? 0 : 1); ++attempt) + { + regmatch_T regmatch; + int score = 0; + + if (!fuzzy) + { + if (attempt > 0 && patc == pat) + break; // there was no anchor, no need to try again + regmatch.regprog = vim_regcomp(patc + attempt * 11, RE_MAGIC); + } + + // round == 1: Count the matches. + // round == 2: Build the array to keep the matches. + for (round = 1; round <= 2; ++round) + { + count = 0; + FOR_ALL_BUFFERS(buf) + { + if (!buf->b_p_bl) // skip unlisted buffers + continue; +#ifdef FEAT_DIFF + if (options & BUF_DIFF_FILTER) + // Skip buffers not suitable for + // :diffget or :diffput completion. + if (buf == curbuf || !diff_mode_buf(buf)) + continue; +#endif + + if (!fuzzy) + { + if (regmatch.regprog == NULL) + { + // invalid pattern, possibly after recompiling + if (patc != pat) + vim_free(patc); + return FAIL; + } + p = buflist_match(®match, buf, p_wic); + } + else + { + p = NULL; + // first try matching with the short file name + if ((score = fuzzy_match_str(buf->b_sfname, pat)) != 0) + p = buf->b_sfname; + if (p == NULL) + { + // next try matching with the full path file name + if ((score = fuzzy_match_str(buf->b_ffname, pat)) != 0) + p = buf->b_ffname; + } + } + + if (p == NULL) + continue; + + if (round == 1) + { + ++count; + continue; + } + + if (options & WILD_HOME_REPLACE) + p = home_replace_save(buf, p); + else + p = vim_strsave(p); + + if (!fuzzy) + { +#ifdef FEAT_VIMINFO + if (matches != NULL) + { + matches[count].buf = buf; + matches[count].match = p; + count++; + } + else +#endif + (*file)[count++] = p; + } + else + { + fuzmatch[count].idx = count; + fuzmatch[count].str = p; + fuzmatch[count].score = score; + count++; + } + } + if (count == 0) // no match found, break here + break; + if (round == 1) + { + if (!fuzzy) + { + *file = ALLOC_MULT(char_u *, count); + if (*file == NULL) + { + vim_regfree(regmatch.regprog); + if (patc != pat) + vim_free(patc); + return FAIL; + } +#ifdef FEAT_VIMINFO + if (options & WILD_BUFLASTUSED) + matches = ALLOC_MULT(bufmatch_T, count); +#endif + } + else + { + fuzmatch = ALLOC_MULT(fuzmatch_str_T, count); + if (fuzmatch == NULL) + { + *num_file = 0; + *file = NULL; + return FAIL; + } + } + } + } + + if (!fuzzy) + { + vim_regfree(regmatch.regprog); + if (count) // match(es) found, break here + break; + } + } + + if (!fuzzy && patc != pat) + vim_free(patc); + +#ifdef FEAT_VIMINFO + if (!fuzzy) + { + if (matches != NULL) + { + int i; + if (count > 1) + qsort(matches, count, sizeof(bufmatch_T), buf_compare); + // if the current buffer is first in the list, place it at the end + if (matches[0].buf == curbuf) + { + for (i = 1; i < count; i++) + (*file)[i-1] = matches[i].match; + (*file)[count-1] = matches[0].match; + } + else + { + for (i = 0; i < count; i++) + (*file)[i] = matches[i].match; + } + vim_free(matches); + } + } + else + { + if (fuzzymatches_to_strmatches(fuzmatch, file, count, FALSE) == FAIL) + return FAIL; + } +#endif + + *num_file = count; + return (count == 0 ? FAIL : OK); +} + +/* + * Check for a match on the file name for buffer "buf" with regprog "prog". + * Note that rmp->regprog may become NULL when switching regexp engine. + */ + static char_u * +buflist_match( + regmatch_T *rmp, + buf_T *buf, + int ignore_case) // when TRUE ignore case, when FALSE use 'fic' +{ + char_u *match; + + // First try the short file name, then the long file name. + match = fname_match(rmp, buf->b_sfname, ignore_case); + if (match == NULL && rmp->regprog != NULL) + match = fname_match(rmp, buf->b_ffname, ignore_case); + + return match; +} + +/* + * Try matching the regexp in "rmp->regprog" with file name "name". + * Note that rmp->regprog may become NULL when switching regexp engine. + * Return "name" when there is a match, NULL when not. + */ + static char_u * +fname_match( + regmatch_T *rmp, + char_u *name, + int ignore_case) // when TRUE ignore case, when FALSE use 'fic' +{ + char_u *match = NULL; + char_u *p; + + // extra check for valid arguments + if (name == NULL || rmp->regprog == NULL) + return NULL; + + // Ignore case when 'fileignorecase' or the argument is set. + rmp->rm_ic = p_fic || ignore_case; + if (vim_regexec(rmp, name, (colnr_T)0)) + match = name; + else if (rmp->regprog != NULL) + { + // Replace $(HOME) with '~' and try matching again. + p = home_replace_save(NULL, name); + if (p != NULL && vim_regexec(rmp, p, (colnr_T)0)) + match = name; + vim_free(p); + } + + return match; +} + +/* + * Find a file in the buffer list by buffer number. + */ + buf_T * +buflist_findnr(int nr) +{ + char_u key[VIM_SIZEOF_INT * 2 + 1]; + hashitem_T *hi; + + if (nr == 0) + nr = curwin->w_alt_fnum; + sprintf((char *)key, "%x", nr); + hi = hash_find(&buf_hashtab, key); + + if (!HASHITEM_EMPTY(hi)) + return (buf_T *)(hi->hi_key + - ((unsigned)(curbuf->b_key - (char_u *)curbuf))); + return NULL; +} + +/* + * Get name of file 'n' in the buffer list. + * When the file has no name an empty string is returned. + * home_replace() is used to shorten the file name (used for marks). + * Returns a pointer to allocated memory, of NULL when failed. + */ + char_u * +buflist_nr2name( + int n, + int fullname, + int helptail) // for help buffers return tail only +{ + buf_T *buf; + + buf = buflist_findnr(n); + if (buf == NULL) + return NULL; + return home_replace_save(helptail ? buf : NULL, + fullname ? buf->b_ffname : buf->b_fname); +} + +/* + * Set the "lnum" and "col" for the buffer "buf" and the current window. + * When "copy_options" is TRUE save the local window option values. + * When "lnum" is 0 only do the options. + */ + void +buflist_setfpos( + buf_T *buf, + win_T *win, // may be NULL when using :badd + linenr_T lnum, + colnr_T col, + int copy_options) +{ + wininfo_T *wip; + + FOR_ALL_BUF_WININFO(buf, wip) + if (wip->wi_win == win) + break; + if (wip == NULL) + { + // allocate a new entry + wip = ALLOC_CLEAR_ONE(wininfo_T); + if (wip == NULL) + return; + wip->wi_win = win; + if (lnum == 0) // set lnum even when it's 0 + lnum = 1; + } + else + { + // remove the entry from the list + if (wip->wi_prev) + wip->wi_prev->wi_next = wip->wi_next; + else + buf->b_wininfo = wip->wi_next; + if (wip->wi_next) + wip->wi_next->wi_prev = wip->wi_prev; + if (copy_options && wip->wi_optset) + { + clear_winopt(&wip->wi_opt); +#ifdef FEAT_FOLDING + deleteFoldRecurse(&wip->wi_folds); +#endif + } + } + if (lnum != 0) + { + wip->wi_fpos.lnum = lnum; + wip->wi_fpos.col = col; + } + if (win != NULL) + wip->wi_changelistidx = win->w_changelistidx; + if (copy_options && win != NULL) + { + // Save the window-specific option values. + copy_winopt(&win->w_onebuf_opt, &wip->wi_opt); +#ifdef FEAT_FOLDING + wip->wi_fold_manual = win->w_fold_manual; + cloneFoldGrowArray(&win->w_folds, &wip->wi_folds); +#endif + wip->wi_optset = TRUE; + } + + // insert the entry in front of the list + wip->wi_next = buf->b_wininfo; + buf->b_wininfo = wip; + wip->wi_prev = NULL; + if (wip->wi_next) + wip->wi_next->wi_prev = wip; +} + +#ifdef FEAT_DIFF +/* + * Return TRUE when "wip" has 'diff' set and the diff is only for another tab + * page. That's because a diff is local to a tab page. + */ + static int +wininfo_other_tab_diff(wininfo_T *wip) +{ + win_T *wp; + + if (!wip->wi_opt.wo_diff) + return FALSE; + + FOR_ALL_WINDOWS(wp) + // return FALSE when it's a window in the current tab page, thus + // the buffer was in diff mode here + if (wip->wi_win == wp) + return FALSE; + return TRUE; +} +#endif + +/* + * Find info for the current window in buffer "buf". + * If not found, return the info for the most recently used window. + * When "need_options" is TRUE skip entries where wi_optset is FALSE. + * When "skip_diff_buffer" is TRUE avoid windows with 'diff' set that is in + * another tab page. + * Returns NULL when there isn't any info. + */ + static wininfo_T * +find_wininfo( + buf_T *buf, + int need_options, + int skip_diff_buffer UNUSED) +{ + wininfo_T *wip; + + FOR_ALL_BUF_WININFO(buf, wip) + if (wip->wi_win == curwin +#ifdef FEAT_DIFF + && (!skip_diff_buffer || !wininfo_other_tab_diff(wip)) +#endif + + && (!need_options || wip->wi_optset)) + break; + + if (wip != NULL) + return wip; + + // If no wininfo for curwin, use the first in the list (that doesn't have + // 'diff' set and is in another tab page). + // If "need_options" is TRUE skip entries that don't have options set, + // unless the window is editing "buf", so we can copy from the window + // itself. +#ifdef FEAT_DIFF + if (skip_diff_buffer) + { + FOR_ALL_BUF_WININFO(buf, wip) + if (!wininfo_other_tab_diff(wip) + && (!need_options || wip->wi_optset + || (wip->wi_win != NULL + && wip->wi_win->w_buffer == buf))) + break; + } + else +#endif + wip = buf->b_wininfo; + return wip; +} + +/* + * Reset the local window options to the values last used in this window. + * If the buffer wasn't used in this window before, use the values from + * the most recently used window. If the values were never set, use the + * global values for the window. + */ + void +get_winopts(buf_T *buf) +{ + wininfo_T *wip; + + clear_winopt(&curwin->w_onebuf_opt); +#ifdef FEAT_FOLDING + clearFolding(curwin); +#endif + + wip = find_wininfo(buf, TRUE, TRUE); + if (wip != NULL && wip->wi_win != NULL + && wip->wi_win != curwin && wip->wi_win->w_buffer == buf) + { + // The buffer is currently displayed in the window: use the actual + // option values instead of the saved (possibly outdated) values. + win_T *wp = wip->wi_win; + + copy_winopt(&wp->w_onebuf_opt, &curwin->w_onebuf_opt); +#ifdef FEAT_FOLDING + curwin->w_fold_manual = wp->w_fold_manual; + curwin->w_foldinvalid = TRUE; + cloneFoldGrowArray(&wp->w_folds, &curwin->w_folds); +#endif + } + else if (wip != NULL && wip->wi_optset) + { + // the buffer was displayed in the current window earlier + copy_winopt(&wip->wi_opt, &curwin->w_onebuf_opt); +#ifdef FEAT_FOLDING + curwin->w_fold_manual = wip->wi_fold_manual; + curwin->w_foldinvalid = TRUE; + cloneFoldGrowArray(&wip->wi_folds, &curwin->w_folds); +#endif + } + else + copy_winopt(&curwin->w_allbuf_opt, &curwin->w_onebuf_opt); + if (wip != NULL) + curwin->w_changelistidx = wip->wi_changelistidx; + +#ifdef FEAT_FOLDING + // Set 'foldlevel' to 'foldlevelstart' if it's not negative. + if (p_fdls >= 0) + curwin->w_p_fdl = p_fdls; +#endif + after_copy_winopt(curwin); +} + +/* + * Find the position (lnum and col) for the buffer 'buf' for the current + * window. + * Returns a pointer to no_position if no position is found. + */ + pos_T * +buflist_findfpos(buf_T *buf) +{ + wininfo_T *wip; + static pos_T no_position = {1, 0, 0}; + + wip = find_wininfo(buf, FALSE, FALSE); + if (wip != NULL) + return &(wip->wi_fpos); + else + return &no_position; +} + +/* + * Find the lnum for the buffer 'buf' for the current window. + */ + linenr_T +buflist_findlnum(buf_T *buf) +{ + return buflist_findfpos(buf)->lnum; +} + +/* + * List all known file names (for :files and :buffers command). + */ + void +buflist_list(exarg_T *eap) +{ + buf_T *buf = firstbuf; + int len; + int i; + int ro_char; + int changed_char; +#ifdef FEAT_TERMINAL + int job_running; + int job_none_open; +#endif + +#ifdef FEAT_VIMINFO + garray_T buflist; + buf_T **buflist_data = NULL, **p; + + if (vim_strchr(eap->arg, 't')) + { + ga_init2(&buflist, sizeof(buf_T *), 50); + FOR_ALL_BUFFERS(buf) + { + if (ga_grow(&buflist, 1) == OK) + ((buf_T **)buflist.ga_data)[buflist.ga_len++] = buf; + } + + qsort(buflist.ga_data, (size_t)buflist.ga_len, + sizeof(buf_T *), buf_compare); + + buflist_data = (buf_T **)buflist.ga_data; + buf = *buflist_data; + } + p = buflist_data; + + for (; buf != NULL && !got_int; buf = buflist_data != NULL + ? (++p < buflist_data + buflist.ga_len ? *p : NULL) + : buf->b_next) +#else + for (buf = firstbuf; buf != NULL && !got_int; buf = buf->b_next) +#endif + { +#ifdef FEAT_TERMINAL + job_running = term_job_running(buf->b_term); + job_none_open = term_none_open(buf->b_term); +#endif + // skip unlisted buffers, unless ! was used + if ((!buf->b_p_bl && !eap->forceit && !vim_strchr(eap->arg, 'u')) + || (vim_strchr(eap->arg, 'u') && buf->b_p_bl) + || (vim_strchr(eap->arg, '+') + && ((buf->b_flags & BF_READERR) || !bufIsChanged(buf))) + || (vim_strchr(eap->arg, 'a') + && (buf->b_ml.ml_mfp == NULL || buf->b_nwindows == 0)) + || (vim_strchr(eap->arg, 'h') + && (buf->b_ml.ml_mfp == NULL || buf->b_nwindows != 0)) +#ifdef FEAT_TERMINAL + || (vim_strchr(eap->arg, 'R') + && (!job_running || (job_running && job_none_open))) + || (vim_strchr(eap->arg, '?') + && (!job_running || (job_running && !job_none_open))) + || (vim_strchr(eap->arg, 'F') + && (job_running || buf->b_term == NULL)) +#endif + || (vim_strchr(eap->arg, '-') && buf->b_p_ma) + || (vim_strchr(eap->arg, '=') && !buf->b_p_ro) + || (vim_strchr(eap->arg, 'x') && !(buf->b_flags & BF_READERR)) + || (vim_strchr(eap->arg, '%') && buf != curbuf) + || (vim_strchr(eap->arg, '#') + && (buf == curbuf || curwin->w_alt_fnum != buf->b_fnum))) + continue; + if (buf_spname(buf) != NULL) + vim_strncpy(NameBuff, buf_spname(buf), MAXPATHL - 1); + else + home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, TRUE); + if (message_filtered(NameBuff)) + continue; + + changed_char = (buf->b_flags & BF_READERR) ? 'x' + : (bufIsChanged(buf) ? '+' : ' '); +#ifdef FEAT_TERMINAL + if (job_running) + { + if (job_none_open) + ro_char = '?'; + else + ro_char = 'R'; + changed_char = ' '; // bufIsChanged() returns TRUE to avoid + // closing, but it's not actually changed. + } + else if (buf->b_term != NULL) + ro_char = 'F'; + else +#endif + ro_char = !buf->b_p_ma ? '-' : (buf->b_p_ro ? '=' : ' '); + + msg_putchar('\n'); + len = vim_snprintf((char *)IObuff, IOSIZE - 20, "%3d%c%c%c%c%c \"%s\"", + buf->b_fnum, + buf->b_p_bl ? ' ' : 'u', + buf == curbuf ? '%' : + (curwin->w_alt_fnum == buf->b_fnum ? '#' : ' '), + buf->b_ml.ml_mfp == NULL ? ' ' : + (buf->b_nwindows == 0 ? 'h' : 'a'), + ro_char, + changed_char, + NameBuff); + if (len > IOSIZE - 20) + len = IOSIZE - 20; + + // put "line 999" in column 40 or after the file name + i = 40 - vim_strsize(IObuff); + do + IObuff[len++] = ' '; + while (--i > 0 && len < IOSIZE - 18); +#ifdef FEAT_VIMINFO + if (vim_strchr(eap->arg, 't') && buf->b_last_used) + add_time(IObuff + len, (size_t)(IOSIZE - len), buf->b_last_used); + else +#endif + vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), + _("line %ld"), buf == curbuf ? curwin->w_cursor.lnum + : (long)buflist_findlnum(buf)); + msg_outtrans(IObuff); + out_flush(); // output one line at a time + ui_breakcheck(); + } + +#ifdef FEAT_VIMINFO + if (buflist_data) + ga_clear(&buflist); +#endif +} + +/* + * Get file name and line number for file 'fnum'. + * Used by DoOneCmd() for translating '%' and '#'. + * Used by insert_reg() and cmdline_paste() for '#' register. + * Return FAIL if not found, OK for success. + */ + int +buflist_name_nr( + int fnum, + char_u **fname, + linenr_T *lnum) +{ + buf_T *buf; + + buf = buflist_findnr(fnum); + if (buf == NULL || buf->b_fname == NULL) + return FAIL; + + *fname = buf->b_fname; + *lnum = buflist_findlnum(buf); + + return OK; +} + +/* + * Set the file name for "buf"' to "ffname_arg", short file name to + * "sfname_arg". + * 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( + buf_T *buf, + char_u *ffname_arg, + char_u *sfname_arg, + int message) // give message when buffer already exists +{ + char_u *ffname = ffname_arg; + char_u *sfname = sfname_arg; + buf_T *obuf = NULL; +#ifdef UNIX + stat_T st; +#endif + + if (ffname == NULL || *ffname == NUL) + { + // Removing the name. + if (buf->b_sfname != buf->b_ffname) + VIM_CLEAR(buf->b_sfname); + else + buf->b_sfname = NULL; + VIM_CLEAR(buf->b_ffname); +#ifdef UNIX + st.st_dev = (dev_T)-1; +#endif + } + else + { + fname_expand(buf, &ffname, &sfname); // will allocate ffname + if (ffname == NULL) // out of memory + return FAIL; + + /* + * 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 + */ +#ifdef UNIX + if (mch_stat((char *)ffname, &st) < 0) + st.st_dev = (dev_T)-1; +#endif + if (!(buf->b_flags & BF_DUMMY)) +#ifdef UNIX + obuf = buflist_findname_stat(ffname, &st); +#else + obuf = buflist_findname(ffname); +#endif + if (obuf != NULL && obuf != buf) + { + win_T *win; + tabpage_T *tab; + int in_use = FALSE; + + // during startup a window may use a buffer that is not loaded yet + FOR_ALL_TAB_WINDOWS(tab, win) + if (win->w_buffer == obuf) + in_use = TRUE; + + // it's loaded or used in a window, fail + if (obuf->b_ml.ml_mfp != NULL || in_use) + { + if (message) + emsg(_(e_buffer_with_this_name_already_exists)); + vim_free(ffname); + return FAIL; + } + // delete from the list + close_buffer(NULL, obuf, DOBUF_WIPE, FALSE, FALSE); + } + sfname = vim_strsave(sfname); + if (ffname == NULL || sfname == NULL) + { + vim_free(sfname); + vim_free(ffname); + return FAIL; + } +#ifdef USE_FNAME_CASE + fname_case(sfname, 0); // set correct case for short file name +#endif + if (buf->b_sfname != buf->b_ffname) + vim_free(buf->b_sfname); + vim_free(buf->b_ffname); + buf->b_ffname = ffname; + buf->b_sfname = sfname; + } + buf->b_fname = buf->b_sfname; +#ifdef UNIX + if (st.st_dev == (dev_T)-1) + buf->b_dev_valid = FALSE; + else + { + buf->b_dev_valid = TRUE; + buf->b_dev = st.st_dev; + buf->b_ino = st.st_ino; + } +#endif + + buf->b_shortname = FALSE; + + buf_name_changed(buf); + return OK; +} + +/* + * Crude way of changing the name of a buffer. Use with care! + * The name should be relative to the current directory. + */ + void +buf_set_name(int fnum, char_u *name) +{ + buf_T *buf; + + buf = buflist_findnr(fnum); + if (buf == NULL) + return; + + if (buf->b_sfname != buf->b_ffname) + vim_free(buf->b_sfname); + vim_free(buf->b_ffname); + buf->b_ffname = vim_strsave(name); + buf->b_sfname = NULL; + // Allocate ffname and expand into full path. Also resolves .lnk + // files on Win32. + fname_expand(buf, &buf->b_ffname, &buf->b_sfname); + buf->b_fname = buf->b_sfname; +} + +/* + * Take care of what needs to be done when the name of buffer "buf" has + * changed. + */ + void +buf_name_changed(buf_T *buf) +{ + /* + * If the file name changed, also change the name of the swapfile + */ + if (buf->b_ml.ml_mfp != NULL) + ml_setname(buf); + +#ifdef FEAT_TERMINAL + if (buf->b_term != NULL) + term_clear_status_text(buf->b_term); +#endif + + if (curwin->w_buffer == buf) + check_arg_idx(curwin); // check file name for arg list + maketitle(); // set window title + status_redraw_all(); // status lines need to be redrawn + fmarks_check_names(buf); // check named file marks + ml_timestamp(buf); // reset timestamp +} + +/* + * set alternate file name for current window + * + * Used by do_one_cmd(), do_write() and do_ecmd(). + * Return the buffer. + */ + buf_T * +setaltfname( + char_u *ffname, + char_u *sfname, + linenr_T lnum) +{ + buf_T *buf; + + // Create a buffer. 'buflisted' is not set if it's a new buffer + buf = buflist_new(ffname, sfname, lnum, 0); + if (buf != NULL && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) + curwin->w_alt_fnum = buf->b_fnum; + return buf; +} + +/* + * Get alternate file name for current window. + * Return NULL if there isn't any, and give error message if requested. + */ + char_u * +getaltfname( + int errmsg) // give error message +{ + char_u *fname; + linenr_T dummy; + + if (buflist_name_nr(0, &fname, &dummy) == FAIL) + { + if (errmsg) + emsg(_(e_no_alternate_file)); + return NULL; + } + return fname; +} + +/* + * Add a file name to the buflist and return its number. + * Uses same flags as buflist_new(), except BLN_DUMMY. + * + * used by qf_init(), main() and doarglist() + */ + int +buflist_add(char_u *fname, int flags) +{ + buf_T *buf; + + buf = buflist_new(fname, NULL, (linenr_T)0, flags); + if (buf != NULL) + return buf->b_fnum; + return 0; +} + +#if defined(BACKSLASH_IN_FILENAME) || defined(PROTO) +/* + * Adjust slashes in file names. Called after 'shellslash' was set. + */ + void +buflist_slash_adjust(void) +{ + buf_T *bp; + + FOR_ALL_BUFFERS(bp) + { + if (bp->b_ffname != NULL) + slash_adjust(bp->b_ffname); + if (bp->b_sfname != NULL) + slash_adjust(bp->b_sfname); + } +} +#endif + +/* + * Set alternate cursor position for the current buffer and window "win". + * Also save the local window option values. + */ + void +buflist_altfpos(win_T *win) +{ + buflist_setfpos(curbuf, win, win->w_cursor.lnum, win->w_cursor.col, TRUE); +} + +/* + * Return TRUE if 'ffname' is not the same file as current file. + * Fname must have a full path (expanded by mch_FullName()). + */ + int +otherfile(char_u *ffname) +{ + return otherfile_buf(curbuf, ffname +#ifdef UNIX + , NULL +#endif + ); +} + + static int +otherfile_buf( + buf_T *buf, + char_u *ffname +#ifdef UNIX + , stat_T *stp +#endif + ) +{ + // no name is different + if (ffname == NULL || *ffname == NUL || buf->b_ffname == NULL) + return TRUE; + if (fnamecmp(ffname, buf->b_ffname) == 0) + return FALSE; +#ifdef UNIX + { + stat_T st; + + // If no stat_T given, get it now + if (stp == NULL) + { + if (!buf->b_dev_valid || mch_stat((char *)ffname, &st) < 0) + st.st_dev = (dev_T)-1; + stp = &st; + } + // Use dev/ino to check if the files are the same, even when the names + // are different (possible with links). Still need to compare the + // name above, for when the file doesn't exist yet. + // Problem: The dev/ino changes when a file is deleted (and created + // again) and remains the same when renamed/moved. We don't want to + // mch_stat() each buffer each time, that would be too slow. Get the + // dev/ino again when they appear to match, but not when they appear + // to be different: Could skip a buffer when it's actually the same + // file. + if (buf_same_ino(buf, stp)) + { + buf_setino(buf); + if (buf_same_ino(buf, stp)) + return FALSE; + } + } +#endif + return TRUE; +} + +#if defined(UNIX) || defined(PROTO) +/* + * Set inode and device number for a buffer. + * Must always be called when b_fname is changed!. + */ + void +buf_setino(buf_T *buf) +{ + stat_T st; + + if (buf->b_fname != NULL && mch_stat((char *)buf->b_fname, &st) >= 0) + { + buf->b_dev_valid = TRUE; + buf->b_dev = st.st_dev; + buf->b_ino = st.st_ino; + } + else + buf->b_dev_valid = FALSE; +} + +/* + * Return TRUE if dev/ino in buffer "buf" matches with "stp". + */ + static int +buf_same_ino( + buf_T *buf, + stat_T *stp) +{ + return (buf->b_dev_valid + && stp->st_dev == buf->b_dev + && stp->st_ino == buf->b_ino); +} +#endif + +/* + * Print info about the current buffer. + */ + void +fileinfo( + int fullname, // when non-zero print full path + int shorthelp, + int dont_truncate) +{ + char_u *name; + int n; + char *p; + char *buffer; + size_t len; + + buffer = alloc(IOSIZE); + if (buffer == NULL) + return; + + if (fullname > 1) // 2 CTRL-G: include buffer number + { + vim_snprintf(buffer, IOSIZE, "buf %d: ", curbuf->b_fnum); + p = buffer + STRLEN(buffer); + } + else + p = buffer; + + *p++ = '"'; + if (buf_spname(curbuf) != NULL) + vim_strncpy((char_u *)p, buf_spname(curbuf), IOSIZE - (p - buffer) - 1); + else + { + if (!fullname && curbuf->b_fname != NULL) + name = curbuf->b_fname; + else + name = curbuf->b_ffname; + home_replace(shorthelp ? curbuf : NULL, name, (char_u *)p, + (int)(IOSIZE - (p - buffer)), TRUE); + } + + vim_snprintf_add(buffer, IOSIZE, "\"%s%s%s%s%s%s", + curbufIsChanged() ? (shortmess(SHM_MOD) + ? " [+]" : _(" [Modified]")) : " ", + (curbuf->b_flags & BF_NOTEDITED) && !bt_dontwrite(curbuf) + ? _("[Not edited]") : "", + (curbuf->b_flags & BF_NEW) && !bt_dontwrite(curbuf) + ? new_file_message() : "", + (curbuf->b_flags & BF_READERR) ? _("[Read errors]") : "", + curbuf->b_p_ro ? (shortmess(SHM_RO) ? _("[RO]") + : _("[readonly]")) : "", + (curbufIsChanged() || (curbuf->b_flags & BF_WRITE_MASK) + || curbuf->b_p_ro) ? + " " : ""); + // With 32 bit longs and more than 21,474,836 lines multiplying by 100 + // causes an overflow, thus for large numbers divide instead. + if (curwin->w_cursor.lnum > 1000000L) + n = (int)(((long)curwin->w_cursor.lnum) / + ((long)curbuf->b_ml.ml_line_count / 100L)); + else + n = (int)(((long)curwin->w_cursor.lnum * 100L) / + (long)curbuf->b_ml.ml_line_count); + if (curbuf->b_ml.ml_flags & ML_EMPTY) + vim_snprintf_add(buffer, IOSIZE, "%s", _(no_lines_msg)); + else if (p_ru) + // Current line and column are already on the screen -- webb + vim_snprintf_add(buffer, IOSIZE, + NGETTEXT("%ld line --%d%%--", "%ld lines --%d%%--", + curbuf->b_ml.ml_line_count), + (long)curbuf->b_ml.ml_line_count, n); + else + { + vim_snprintf_add(buffer, IOSIZE, + _("line %ld of %ld --%d%%-- col "), + (long)curwin->w_cursor.lnum, + (long)curbuf->b_ml.ml_line_count, + n); + validate_virtcol(); + len = STRLEN(buffer); + col_print((char_u *)buffer + len, IOSIZE - len, + (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); + } + + (void)append_arg_number(curwin, (char_u *)buffer, IOSIZE, + !shortmess(SHM_FILE)); + + if (dont_truncate) + { + // Temporarily set msg_scroll to avoid the message being truncated. + // First call msg_start() to get the message in the right place. + msg_start(); + n = msg_scroll; + msg_scroll = TRUE; + msg(buffer); + msg_scroll = n; + } + else + { + p = msg_trunc_attr(buffer, FALSE, 0); + if (restart_edit != 0 || (msg_scrolled && !need_wait_return)) + // Need to repeat the message after redrawing when: + // - When restart_edit is set (otherwise there will be a delay + // before redrawing). + // - When the screen was scrolled but there is no wait-return + // prompt. + set_keep_msg((char_u *)p, 0); + } + + vim_free(buffer); +} + + void +col_print( + char_u *buf, + size_t buflen, + int col, + int vcol) +{ + if (col == vcol) + vim_snprintf((char *)buf, buflen, "%d", col); + else + vim_snprintf((char *)buf, buflen, "%d-%d", col, vcol); +} + +static char_u *lasttitle = NULL; +static char_u *lasticon = NULL; + +/* + * Put the file name in the title bar and icon of the window. + */ + void +maketitle(void) +{ + char_u *p; + char_u *title_str = NULL; + char_u *icon_str = NULL; + int maxlen = 0; + int len; + int mustset; + char_u buf[IOSIZE]; + int off; + + if (!redrawing()) + { + // Postpone updating the title when 'lazyredraw' is set. + need_maketitle = TRUE; + return; + } + + need_maketitle = FALSE; + if (!p_title && !p_icon && lasttitle == NULL && lasticon == NULL) + return; // nothing to do + + if (p_title) + { + if (p_titlelen > 0) + { + maxlen = p_titlelen * Columns / 100; + if (maxlen < 10) + maxlen = 10; + } + + title_str = buf; + if (*p_titlestring != NUL) + { +#ifdef FEAT_STL_OPT + if (stl_syntax & STL_IN_TITLE) + build_stl_str_hl(curwin, title_str, sizeof(buf), p_titlestring, + (char_u *)"titlestring", 0, + 0, maxlen, NULL, NULL); + else +#endif + title_str = p_titlestring; + } + else + { + // format: "fname + (path) (1 of 2) - VIM" + +#define SPACE_FOR_FNAME (IOSIZE - 100) +#define SPACE_FOR_DIR (IOSIZE - 20) +#define SPACE_FOR_ARGNR (IOSIZE - 10) // at least room for " - VIM" + if (curbuf->b_fname == NULL) + vim_strncpy(buf, (char_u *)_("[No Name]"), SPACE_FOR_FNAME); +#ifdef FEAT_TERMINAL + else if (curbuf->b_term != NULL) + { + vim_strncpy(buf, term_get_status_text(curbuf->b_term), + SPACE_FOR_FNAME); + } +#endif + else + { + p = transstr(gettail(curbuf->b_fname)); + vim_strncpy(buf, p, SPACE_FOR_FNAME); + vim_free(p); + } + +#ifdef FEAT_TERMINAL + if (curbuf->b_term == NULL) +#endif + switch (bufIsChanged(curbuf) + + (curbuf->b_p_ro * 2) + + (!curbuf->b_p_ma * 4)) + { + case 1: STRCAT(buf, " +"); break; + case 2: STRCAT(buf, " ="); break; + case 3: STRCAT(buf, " =+"); break; + case 4: + case 6: STRCAT(buf, " -"); break; + case 5: + case 7: STRCAT(buf, " -+"); break; + } + + if (curbuf->b_fname != NULL +#ifdef FEAT_TERMINAL + && curbuf->b_term == NULL +#endif + ) + { + // Get path of file, replace home dir with ~ + off = (int)STRLEN(buf); + buf[off++] = ' '; + buf[off++] = '('; + home_replace(curbuf, curbuf->b_ffname, + buf + off, SPACE_FOR_DIR - off, TRUE); +#ifdef BACKSLASH_IN_FILENAME + // avoid "c:/name" to be reduced to "c" + if (isalpha(buf[off]) && buf[off + 1] == ':') + off += 2; +#endif + // remove the file name + p = gettail_sep(buf + off); + if (p == buf + off) + { + // must be a help buffer + vim_strncpy(buf + off, (char_u *)_("help"), + (size_t)(SPACE_FOR_DIR - off - 1)); + } + else + *p = NUL; + + // Translate unprintable chars and concatenate. Keep some + // room for the server name. When there is no room (very long + // file name) use (...). + if (off < SPACE_FOR_DIR) + { + p = transstr(buf + off); + vim_strncpy(buf + off, p, (size_t)(SPACE_FOR_DIR - off)); + vim_free(p); + } + else + { + vim_strncpy(buf + off, (char_u *)"...", + (size_t)(SPACE_FOR_ARGNR - off)); + } + STRCAT(buf, ")"); + } + + append_arg_number(curwin, buf, SPACE_FOR_ARGNR, FALSE); + +#if defined(FEAT_CLIENTSERVER) + if (serverName != NULL) + { + STRCAT(buf, " - "); + vim_strcat(buf, serverName, IOSIZE); + } + else +#endif + STRCAT(buf, " - VIM"); + + if (maxlen > 0) + { + // make it shorter by removing a bit in the middle + if (vim_strsize(buf) > maxlen) + trunc_string(buf, buf, maxlen, IOSIZE); + } + } + } + mustset = value_changed(title_str, &lasttitle); + + if (p_icon) + { + icon_str = buf; + if (*p_iconstring != NUL) + { +#ifdef FEAT_STL_OPT + if (stl_syntax & STL_IN_ICON) + build_stl_str_hl(curwin, icon_str, sizeof(buf), p_iconstring, + (char_u *)"iconstring", 0, 0, 0, NULL, NULL); + else +#endif + icon_str = p_iconstring; + } + else + { + if (buf_spname(curbuf) != NULL) + p = buf_spname(curbuf); + else // use file name only in icon + p = gettail(curbuf->b_ffname); + *icon_str = NUL; + // Truncate name at 100 bytes. + len = (int)STRLEN(p); + if (len > 100) + { + len -= 100; + if (has_mbyte) + len += (*mb_tail_off)(p, p + len) + 1; + p += len; + } + STRCPY(icon_str, p); + trans_characters(icon_str, IOSIZE); + } + } + + mustset |= value_changed(icon_str, &lasticon); + + if (mustset) + resettitle(); +} + +/* + * Used for title and icon: Check if "str" differs from "*last". Set "*last" + * from "str" if it does. + * Return TRUE if resettitle() is to be called. + */ + static int +value_changed(char_u *str, char_u **last) +{ + if ((str == NULL) != (*last == NULL) + || (str != NULL && *last != NULL && STRCMP(str, *last) != 0)) + { + vim_free(*last); + if (str == NULL) + { + *last = NULL; + mch_restore_title( + last == &lasttitle ? SAVE_RESTORE_TITLE : SAVE_RESTORE_ICON); + } + else + { + *last = vim_strsave(str); + return TRUE; + } + } + return FALSE; +} + +/* + * Put current window title back (used after calling a shell) + */ + void +resettitle(void) +{ + mch_settitle(lasttitle, lasticon); +} + +# if defined(EXITFREE) || defined(PROTO) + void +free_titles(void) +{ + vim_free(lasttitle); + vim_free(lasticon); +} +# endif + + +#if defined(FEAT_STL_OPT) || defined(FEAT_GUI_TABLINE) || defined(PROTO) + +/* + * Used for building in the status line. + */ +typedef struct +{ + char_u *stl_start; + int stl_minwid; + int stl_maxwid; + enum { + Normal, + Empty, + Group, + Separate, + Highlight, + TabPage, + Trunc + } stl_type; +} stl_item_T; + +static size_t stl_items_len = 20; // Initial value, grows as needed. +static stl_item_T *stl_items = NULL; +static int *stl_groupitem = NULL; +static stl_hlrec_T *stl_hltab = NULL; +static stl_hlrec_T *stl_tabtab = NULL; +static int *stl_separator_locations = NULL; + +/* + * Build a string from the status line items in "fmt". + * Return length of string in screen cells. + * + * Normally works for window "wp", except when working for 'tabline' then it + * is "curwin". + * + * Items are drawn interspersed with the text that surrounds it + * Specials: %-(xxx%) => group, %= => separation marker, %< => truncation + * Item: %-. All but are optional + * + * If maxwidth is not zero, the string will be filled at any middle marker + * or truncated if too long, fillchar is used for all whitespace. + */ + int +build_stl_str_hl( + win_T *wp, + char_u *out, // buffer to write into != NameBuff + size_t outlen, // length of out[] + char_u *fmt, + char_u *opt_name, // option name corresponding to "fmt" + int opt_scope, // scope for "opt_name" + int fillchar, + int maxwidth, + stl_hlrec_T **hltab, // return: HL attributes (can be NULL) + stl_hlrec_T **tabtab) // return: tab page nrs (can be NULL) +{ + linenr_T lnum; + size_t len; + char_u *p; + char_u *s; + char_u *t; + int byteval; +#ifdef FEAT_EVAL + int use_sandbox; + win_T *save_curwin; + buf_T *save_curbuf; + int save_VIsual_active; +#endif + int empty_line; + colnr_T virtcol; + long l; + long n; + int prevchar_isflag; + int prevchar_isitem; + int itemisflag; + int fillable; + char_u *str; + long num; + int width; + int itemcnt; + int curitem; + int group_end_userhl; + int group_start_userhl; + int groupdepth; +#ifdef FEAT_EVAL + int evaldepth; +#endif + int minwid; + int maxwid; + int zeropad; + char_u base; + char_u opt; +#define TMPLEN 70 + char_u buf_tmp[TMPLEN]; + char_u win_tmp[TMPLEN]; + char_u *usefmt = fmt; + stl_hlrec_T *sp; + int save_redraw_not_allowed = redraw_not_allowed; + int save_KeyTyped = KeyTyped; + // TODO: find out why using called_emsg_before makes tests fail, does it + // matter? + // int called_emsg_before = called_emsg; + int did_emsg_before = did_emsg; + + // When inside update_screen() we do not want redrawing a statusline, + // ruler, title, etc. to trigger another redraw, it may cause an endless + // loop. + if (updating_screen) + redraw_not_allowed = TRUE; + + if (stl_items == NULL) + { + stl_items = ALLOC_MULT(stl_item_T, stl_items_len); + stl_groupitem = ALLOC_MULT(int, stl_items_len); + + // Allocate one more, because the last element is used to indicate the + // end of the list. + stl_hltab = ALLOC_MULT(stl_hlrec_T, stl_items_len + 1); + stl_tabtab = ALLOC_MULT(stl_hlrec_T, stl_items_len + 1); + + stl_separator_locations = ALLOC_MULT(int, stl_items_len); + } + +#ifdef FEAT_EVAL + // if "fmt" was set insecurely it needs to be evaluated in the sandbox + use_sandbox = was_set_insecurely(opt_name, opt_scope); + + // When the format starts with "%!" then evaluate it as an expression and + // use the result as the actual format string. + if (fmt[0] == '%' && fmt[1] == '!') + { + typval_T tv; + + tv.v_type = VAR_NUMBER; + tv.vval.v_number = wp->w_id; + set_var((char_u *)"g:statusline_winid", &tv, FALSE); + + usefmt = eval_to_string_safe(fmt + 2, use_sandbox, FALSE, FALSE); + if (usefmt == NULL) + usefmt = fmt; + + do_unlet((char_u *)"g:statusline_winid", TRUE); + } +#endif + + if (fillchar == 0) + fillchar = ' '; + + // The cursor in windows other than the current one isn't always + // up-to-date, esp. because of autocommands and timers. + lnum = wp->w_cursor.lnum; + if (lnum > wp->w_buffer->b_ml.ml_line_count) + { + lnum = wp->w_buffer->b_ml.ml_line_count; + wp->w_cursor.lnum = lnum; + } + + // Get line & check if empty (cursorpos will show "0-1"). Note that + // p will become invalid when getting another buffer line. + p = ml_get_buf(wp->w_buffer, lnum, FALSE); + empty_line = (*p == NUL); + + // Get the byte value now, in case we need it below. This is more efficient + // than making a copy of the line. + len = STRLEN(p); + if (wp->w_cursor.col > (colnr_T)len) + { + // Line may have changed since checking the cursor column, or the lnum + // was adjusted above. + wp->w_cursor.col = (colnr_T)len; + wp->w_cursor.coladd = 0; + byteval = 0; + } + else + byteval = (*mb_ptr2char)(p + wp->w_cursor.col); + + groupdepth = 0; +#ifdef FEAT_EVAL + evaldepth = 0; +#endif + p = out; + curitem = 0; + prevchar_isflag = TRUE; + prevchar_isitem = FALSE; + for (s = usefmt; *s != NUL; ) + { + if (curitem == (int)stl_items_len) + { + size_t new_len = stl_items_len * 3 / 2; + + stl_item_T *new_items = + vim_realloc(stl_items, sizeof(stl_item_T) * new_len); + if (new_items == NULL) + break; + stl_items = new_items; + + int *new_groupitem = + vim_realloc(stl_groupitem, sizeof(int) * new_len); + if (new_groupitem == NULL) + break; + stl_groupitem = new_groupitem; + + stl_hlrec_T *new_hlrec = vim_realloc(stl_hltab, + sizeof(stl_hlrec_T) * (new_len + 1)); + if (new_hlrec == NULL) + break; + stl_hltab = new_hlrec; + new_hlrec = vim_realloc(stl_tabtab, + sizeof(stl_hlrec_T) * (new_len + 1)); + if (new_hlrec == NULL) + break; + stl_tabtab = new_hlrec; + + int *new_separator_locs = vim_realloc(stl_separator_locations, + sizeof(int) * new_len); + if (new_separator_locs == NULL) + break; + stl_separator_locations = new_separator_locs;; + + stl_items_len = new_len; + } + + if (*s != '%') + prevchar_isflag = prevchar_isitem = FALSE; + + /* + * Handle up to the next '%' or the end. + */ + while (*s != NUL && *s != '%' && p + 1 < out + outlen) + *p++ = *s++; + if (*s == NUL || p + 1 >= out + outlen) + break; + + /* + * Handle one '%' item. + */ + s++; + if (*s == NUL) // ignore trailing % + break; + if (*s == '%') + { + if (p + 1 >= out + outlen) + break; + *p++ = *s++; + prevchar_isflag = prevchar_isitem = FALSE; + continue; + } + // STL_SEPARATE: Separation between items, filled with white space. + if (*s == STL_SEPARATE) + { + s++; + if (groupdepth > 0) + continue; + stl_items[curitem].stl_type = Separate; + stl_items[curitem++].stl_start = p; + continue; + } + if (*s == STL_TRUNCMARK) + { + s++; + stl_items[curitem].stl_type = Trunc; + stl_items[curitem++].stl_start = p; + continue; + } + if (*s == ')') + { + s++; + if (groupdepth < 1) + continue; + groupdepth--; + + t = stl_items[stl_groupitem[groupdepth]].stl_start; + *p = NUL; + l = vim_strsize(t); + if (curitem > stl_groupitem[groupdepth] + 1 + && stl_items[stl_groupitem[groupdepth]].stl_minwid == 0) + { + // remove group if all items are empty and highlight group + // doesn't change + group_start_userhl = group_end_userhl = 0; + for (n = stl_groupitem[groupdepth] - 1; n >= 0; n--) + { + if (stl_items[n].stl_type == Highlight) + { + group_start_userhl = group_end_userhl = + stl_items[n].stl_minwid; + break; + } + } + for (n = stl_groupitem[groupdepth] + 1; n < curitem; n++) + { + if (stl_items[n].stl_type == Normal) + break; + if (stl_items[n].stl_type == Highlight) + group_end_userhl = stl_items[n].stl_minwid; + } + if (n == curitem && group_start_userhl == group_end_userhl) + { + // empty group + p = t; + l = 0; + for (n = stl_groupitem[groupdepth] + 1; n < curitem; n++) + { + // do not use the highlighting from the removed group + if (stl_items[n].stl_type == Highlight) + stl_items[n].stl_type = Empty; + // adjust the start position of TabPage to the next + // item position + if (stl_items[n].stl_type == TabPage) + stl_items[n].stl_start = p; + } + } + } + if (l > stl_items[stl_groupitem[groupdepth]].stl_maxwid) + { + // truncate, remove n bytes of text at the start + if (has_mbyte) + { + // Find the first character that should be included. + n = 0; + while (l >= stl_items[stl_groupitem[groupdepth]].stl_maxwid) + { + l -= ptr2cells(t + n); + n += (*mb_ptr2len)(t + n); + } + } + else + n = (long)(p - t) - stl_items[stl_groupitem[groupdepth]] + .stl_maxwid + 1; + + *t = '<'; + mch_memmove(t + 1, t + n, (size_t)(p - (t + n))); + p = p - n + 1; + + // Fill up space left over by half a double-wide char. + while (++l < stl_items[stl_groupitem[groupdepth]].stl_minwid) + MB_CHAR2BYTES(fillchar, p); + + // correct the start of the items for the truncation + for (l = stl_groupitem[groupdepth] + 1; l < curitem; l++) + { + // Minus one for the leading '<' added above. + stl_items[l].stl_start -= n - 1; + if (stl_items[l].stl_start < t) + stl_items[l].stl_start = t; + } + } + else if (abs(stl_items[stl_groupitem[groupdepth]].stl_minwid) > l) + { + // fill + n = stl_items[stl_groupitem[groupdepth]].stl_minwid; + if (n < 0) + { + // fill by appending characters + n = 0 - n; + while (l++ < n && p + 1 < out + outlen) + MB_CHAR2BYTES(fillchar, p); + } + else + { + // fill by inserting characters + l = (n - l) * MB_CHAR2LEN(fillchar); + mch_memmove(t + l, t, (size_t)(p - t)); + if (p + l >= out + outlen) + l = (long)((out + outlen) - p - 1); + p += l; + for (n = stl_groupitem[groupdepth] + 1; n < curitem; n++) + stl_items[n].stl_start += l; + for ( ; l > 0; l--) + MB_CHAR2BYTES(fillchar, t); + } + } + continue; + } + minwid = 0; + maxwid = 9999; + zeropad = FALSE; + l = 1; + if (*s == '0') + { + s++; + zeropad = TRUE; + } + if (*s == '-') + { + s++; + l = -1; + } + if (VIM_ISDIGIT(*s)) + { + minwid = (int)getdigits(&s); + if (minwid < 0) // overflow + minwid = 0; + } + if (*s == STL_USER_HL) + { + stl_items[curitem].stl_type = Highlight; + stl_items[curitem].stl_start = p; + stl_items[curitem].stl_minwid = minwid > 9 ? 1 : minwid; + s++; + curitem++; + continue; + } + if (*s == STL_TABPAGENR || *s == STL_TABCLOSENR) + { + if (*s == STL_TABCLOSENR) + { + if (minwid == 0) + { + // %X ends the close label, go back to the previously + // define tab label nr. + for (n = curitem - 1; n >= 0; --n) + if (stl_items[n].stl_type == TabPage + && stl_items[n].stl_minwid >= 0) + { + minwid = stl_items[n].stl_minwid; + break; + } + } + else + // close nrs are stored as negative values + minwid = - minwid; + } + stl_items[curitem].stl_type = TabPage; + stl_items[curitem].stl_start = p; + stl_items[curitem].stl_minwid = minwid; + s++; + curitem++; + continue; + } + if (*s == '.') + { + s++; + if (VIM_ISDIGIT(*s)) + { + maxwid = (int)getdigits(&s); + if (maxwid <= 0) // overflow + maxwid = 50; + } + } + minwid = (minwid > 50 ? 50 : minwid) * l; + if (*s == '(') + { + stl_groupitem[groupdepth++] = curitem; + stl_items[curitem].stl_type = Group; + stl_items[curitem].stl_start = p; + stl_items[curitem].stl_minwid = minwid; + stl_items[curitem].stl_maxwid = maxwid; + s++; + curitem++; + continue; + } +#ifdef FEAT_EVAL + // Denotes end of expanded %{} block + if (*s == '}' && evaldepth > 0) + { + s++; + evaldepth--; + continue; + } +#endif + if (vim_strchr(STL_ALL, *s) == NULL) + { + if (*s == NUL) // can happen with "%0" + break; + s++; + continue; + } + opt = *s++; + + // OK - now for the real work + base = 'D'; + itemisflag = FALSE; + fillable = TRUE; + num = -1; + str = NULL; + switch (opt) + { + case STL_FILEPATH: + case STL_FULLPATH: + case STL_FILENAME: + fillable = FALSE; // don't change ' ' to fillchar + if (buf_spname(wp->w_buffer) != NULL) + vim_strncpy(NameBuff, buf_spname(wp->w_buffer), MAXPATHL - 1); + else + { + t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname + : wp->w_buffer->b_fname; + home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, TRUE); + } + trans_characters(NameBuff, MAXPATHL); + if (opt != STL_FILENAME) + str = NameBuff; + else + str = gettail(NameBuff); + break; + + case STL_VIM_EXPR: // '{' + { +#ifdef FEAT_EVAL + char_u *block_start = s - 1; +#endif + int reevaluate = (*s == '%'); + + if (reevaluate) + s++; + itemisflag = TRUE; + t = p; + while ((*s != '}' || (reevaluate && s[-1] != '%')) + && *s != NUL && p + 1 < out + outlen) + *p++ = *s++; + if (*s != '}') // missing '}' or out of space + break; + s++; + if (reevaluate) + p[-1] = 0; // remove the % at the end of %{% expr %} + else + *p = 0; + p = t; +#ifdef FEAT_EVAL + vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), + "%d", curbuf->b_fnum); + set_internal_string_var((char_u *)"g:actual_curbuf", buf_tmp); + vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->w_id); + set_internal_string_var((char_u *)"g:actual_curwin", win_tmp); + + save_curbuf = curbuf; + save_curwin = curwin; + save_VIsual_active = VIsual_active; + curwin = wp; + curbuf = wp->w_buffer; + // Visual mode is only valid in the current window. + if (curwin != save_curwin) + VIsual_active = FALSE; + + str = eval_to_string_safe(p, use_sandbox, FALSE, FALSE); + + curwin = save_curwin; + curbuf = save_curbuf; + VIsual_active = save_VIsual_active; + do_unlet((char_u *)"g:actual_curbuf", TRUE); + do_unlet((char_u *)"g:actual_curwin", TRUE); + + if (str != NULL && *str != 0) + { + if (*skipdigits(str) == NUL) + { + num = atoi((char *)str); + VIM_CLEAR(str); + itemisflag = FALSE; + } + } + + // If the output of the expression needs to be evaluated + // replace the %{} block with the result of evaluation + if (reevaluate && str != NULL && *str != 0 + && strchr((const char *)str, '%') != NULL + && evaldepth < MAX_STL_EVAL_DEPTH) + { + size_t parsed_usefmt = (size_t)(block_start - usefmt); + size_t str_length = strlen((const char *)str); + size_t fmt_length = strlen((const char *)s); + size_t new_fmt_len = parsed_usefmt + + str_length + fmt_length + 3; + char_u *new_fmt = (char_u *)alloc(new_fmt_len * sizeof(char_u)); + char_u *new_fmt_p = new_fmt; + + new_fmt_p = (char_u *)memcpy(new_fmt_p, usefmt, parsed_usefmt) + + parsed_usefmt; + new_fmt_p = (char_u *)memcpy(new_fmt_p , str, str_length) + + str_length; + new_fmt_p = (char_u *)memcpy(new_fmt_p, "%}", 2) + 2; + new_fmt_p = (char_u *)memcpy(new_fmt_p , s, fmt_length) + + fmt_length; + *new_fmt_p = 0; + new_fmt_p = NULL; + + if (usefmt != fmt) + vim_free(usefmt); + VIM_CLEAR(str); + usefmt = new_fmt; + s = usefmt + parsed_usefmt; + evaldepth++; + continue; + } +#endif + break; + } + case STL_LINE: + num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) + ? 0L : (long)(wp->w_cursor.lnum); + break; + + case STL_NUMLINES: + num = wp->w_buffer->b_ml.ml_line_count; + break; + + case STL_COLUMN: + num = (State & MODE_INSERT) == 0 && empty_line + ? 0 : (int)wp->w_cursor.col + 1; + break; + + case STL_VIRTCOL: + case STL_VIRTCOL_ALT: + virtcol = wp->w_virtcol + 1; + // Don't display %V if it's the same as %c. + if (opt == STL_VIRTCOL_ALT + && (virtcol == (colnr_T)((State & MODE_INSERT) == 0 + && empty_line ? 0 : (int)wp->w_cursor.col + 1))) + break; + num = (long)virtcol; + break; + + case STL_PERCENTAGE: + num = (int)(((long)wp->w_cursor.lnum * 100L) / + (long)wp->w_buffer->b_ml.ml_line_count); + break; + + case STL_ALTPERCENT: + str = buf_tmp; + get_rel_pos(wp, str, TMPLEN); + break; + + case STL_SHOWCMD: + if (p_sc && STRCMP(opt_name, p_sloc) == 0) + str = showcmd_buf; + break; + + case STL_ARGLISTSTAT: + fillable = FALSE; + buf_tmp[0] = 0; + if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), FALSE)) + str = buf_tmp; + break; + + case STL_KEYMAP: + fillable = FALSE; + if (get_keymap_str(wp, (char_u *)"<%s>", buf_tmp, TMPLEN)) + str = buf_tmp; + break; + case STL_PAGENUM: +#if defined(FEAT_PRINTER) || defined(FEAT_GUI_TABLINE) + num = printer_page_num; +#else + num = 0; +#endif + break; + + case STL_BUFNO: + num = wp->w_buffer->b_fnum; + break; + + case STL_OFFSET_X: + base = 'X'; + // FALLTHROUGH + case STL_OFFSET: +#ifdef FEAT_BYTEOFF + l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL); + num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 + ? 0L : l + 1 + ((State & MODE_INSERT) == 0 && empty_line + ? 0 : (int)wp->w_cursor.col); +#endif + break; + + case STL_BYTEVAL_X: + base = 'X'; + // FALLTHROUGH + case STL_BYTEVAL: + num = byteval; + if (num == NL) + num = 0; + else if (num == CAR && get_fileformat(wp->w_buffer) == EOL_MAC) + num = NL; + break; + + case STL_ROFLAG: + case STL_ROFLAG_ALT: + itemisflag = TRUE; + if (wp->w_buffer->b_p_ro) + str = (char_u *)((opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]")); + break; + + case STL_HELPFLAG: + case STL_HELPFLAG_ALT: + itemisflag = TRUE; + if (wp->w_buffer->b_help) + str = (char_u *)((opt == STL_HELPFLAG_ALT) ? ",HLP" + : _("[Help]")); + break; + + case STL_FILETYPE: + if (*wp->w_buffer->b_p_ft != NUL + && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3) + { + vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), "[%s]", + wp->w_buffer->b_p_ft); + str = buf_tmp; + } + break; + + case STL_FILETYPE_ALT: + itemisflag = TRUE; + if (*wp->w_buffer->b_p_ft != NUL + && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2) + { + vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), ",%s", + wp->w_buffer->b_p_ft); + for (t = buf_tmp; *t != 0; t++) + *t = TOUPPER_LOC(*t); + str = buf_tmp; + } + break; + +#if defined(FEAT_QUICKFIX) + case STL_PREVIEWFLAG: + case STL_PREVIEWFLAG_ALT: + itemisflag = TRUE; + if (wp->w_p_pvw) + str = (char_u *)((opt == STL_PREVIEWFLAG_ALT) ? ",PRV" + : _("[Preview]")); + break; + + case STL_QUICKFIX: + if (bt_quickfix(wp->w_buffer)) + str = (char_u *)(wp->w_llist_ref + ? _(msg_loclist) + : _(msg_qflist)); + break; +#endif + + case STL_MODIFIED: + case STL_MODIFIED_ALT: + itemisflag = TRUE; + switch ((opt == STL_MODIFIED_ALT) + + bufIsChanged(wp->w_buffer) * 2 + + (!wp->w_buffer->b_p_ma) * 4) + { + case 2: str = (char_u *)"[+]"; break; + case 3: str = (char_u *)",+"; break; + case 4: str = (char_u *)"[-]"; break; + case 5: str = (char_u *)",-"; break; + case 6: str = (char_u *)"[+-]"; break; + case 7: str = (char_u *)",+-"; break; + } + break; + + case STL_HIGHLIGHT: + t = s; + while (*s != '#' && *s != NUL) + ++s; + if (*s == '#') + { + stl_items[curitem].stl_type = Highlight; + stl_items[curitem].stl_start = p; + stl_items[curitem].stl_minwid = -syn_namen2id(t, (int)(s - t)); + curitem++; + } + if (*s != NUL) + ++s; + continue; + } + + stl_items[curitem].stl_start = p; + stl_items[curitem].stl_type = Normal; + if (str != NULL && *str) + { + t = str; + if (itemisflag) + { + if ((t[0] && t[1]) + && ((!prevchar_isitem && *t == ',') + || (prevchar_isflag && *t == ' '))) + t++; + prevchar_isflag = TRUE; + } + l = vim_strsize(t); + if (l > 0) + prevchar_isitem = TRUE; + if (l > maxwid) + { + while (l >= maxwid) + if (has_mbyte) + { + l -= ptr2cells(t); + t += (*mb_ptr2len)(t); + } + else + l -= byte2cells(*t++); + if (p + 1 >= out + outlen) + break; + *p++ = '<'; + } + if (minwid > 0) + { + for (; l < minwid && p + 1 < out + outlen; l++) + { + // Don't put a "-" in front of a digit. + if (l + 1 == minwid && fillchar == '-' && VIM_ISDIGIT(*t)) + *p++ = ' '; + else + MB_CHAR2BYTES(fillchar, p); + } + minwid = 0; + } + else + minwid *= -1; + for (; *t && p + 1 < out + outlen; t++) + { + // Change a space by fillchar, unless fillchar is '-' and a + // digit follows. + if (fillable && *t == ' ' + && (!VIM_ISDIGIT(*(t + 1)) || fillchar != '-')) + MB_CHAR2BYTES(fillchar, p); + else + *p++ = *t; + } + for (; l < minwid && p + 1 < out + outlen; l++) + MB_CHAR2BYTES(fillchar, p); + } + else if (num >= 0) + { + int nbase = (base == 'D' ? 10 : (base == 'O' ? 8 : 16)); + char_u nstr[20]; + + if (p + 20 >= out + outlen) + break; // not sufficient space + prevchar_isitem = TRUE; + t = nstr; + if (opt == STL_VIRTCOL_ALT) + { + *t++ = '-'; + minwid--; + } + *t++ = '%'; + if (zeropad) + *t++ = '0'; + *t++ = '*'; + *t++ = nbase == 16 ? base : (char_u)(nbase == 8 ? 'o' : 'd'); + *t = 0; + + for (n = num, l = 1; n >= nbase; n /= nbase) + l++; + if (opt == STL_VIRTCOL_ALT) + l++; + if (l > maxwid) + { + l += 2; + n = l - maxwid; + while (l-- > maxwid) + num /= nbase; + *t++ = '>'; + *t++ = '%'; + *t = t[-3]; + *++t = 0; + vim_snprintf((char *)p, outlen - (p - out), (char *)nstr, + 0, num, n); + } + else + vim_snprintf((char *)p, outlen - (p - out), (char *)nstr, + minwid, num); + p += STRLEN(p); + } + else + stl_items[curitem].stl_type = Empty; + + if (num >= 0 || (!itemisflag && str != NULL && *str != NUL)) + prevchar_isflag = FALSE; // Item not NULL, but not a flag + // + if (opt == STL_VIM_EXPR) + vim_free(str); + curitem++; + } + *p = NUL; + itemcnt = curitem; + +#ifdef FEAT_EVAL + if (usefmt != fmt) + vim_free(usefmt); +#endif + + width = vim_strsize(out); + if (maxwidth > 0 && width > maxwidth) + { + // Result is too long, must truncate somewhere. + l = 0; + if (itemcnt == 0) + s = out; + else + { + for ( ; l < itemcnt; l++) + if (stl_items[l].stl_type == Trunc) + { + // Truncate at %< item. + s = stl_items[l].stl_start; + break; + } + if (l == itemcnt) + { + // No %< item, truncate first item. + s = stl_items[0].stl_start; + l = 0; + } + } + + if (width - vim_strsize(s) >= maxwidth) + { + // Truncation mark is beyond max length + if (has_mbyte) + { + s = out; + width = 0; + for (;;) + { + width += ptr2cells(s); + if (width >= maxwidth) + break; + s += (*mb_ptr2len)(s); + } + // Fill up for half a double-wide character. + while (++width < maxwidth) + MB_CHAR2BYTES(fillchar, s); + } + else + s = out + maxwidth - 1; + for (l = 0; l < itemcnt; l++) + if (stl_items[l].stl_start > s) + break; + itemcnt = l; + *s++ = '>'; + *s = 0; + } + else + { + if (has_mbyte) + { + n = 0; + while (width >= maxwidth) + { + width -= ptr2cells(s + n); + n += (*mb_ptr2len)(s + n); + } + } + else + n = width - maxwidth + 1; + p = s + n; + STRMOVE(s + 1, p); + *s = '<'; + + --n; // count the '<' + for (; l < itemcnt; l++) + { + if (stl_items[l].stl_start - n >= s) + stl_items[l].stl_start -= n; + else + stl_items[l].stl_start = s; + } + + // Fill up for half a double-wide character. + while (++width < maxwidth) + { + s = s + STRLEN(s); + MB_CHAR2BYTES(fillchar, s); + *s = NUL; + } + } + width = maxwidth; + } + else if (width < maxwidth && STRLEN(out) + maxwidth - width + 1 < outlen) + { + // Find how many separators there are, which we will use when + // figuring out how many groups there are. + int num_separators = 0; + + for (l = 0; l < itemcnt; l++) + { + if (stl_items[l].stl_type == Separate) + { + // Create an array of the start location for each separator + // mark. + stl_separator_locations[num_separators] = l; + num_separators++; + } + } + + // If we have separated groups, then we deal with it now + if (num_separators) + { + int standard_spaces; + int final_spaces; + + standard_spaces = (maxwidth - width) / num_separators; + final_spaces = (maxwidth - width) - + standard_spaces * (num_separators - 1); + for (l = 0; l < num_separators; l++) + { + int dislocation = (l == (num_separators - 1)) ? + final_spaces : standard_spaces; + dislocation *= MB_CHAR2LEN(fillchar); + char_u *start = stl_items[stl_separator_locations[l]].stl_start; + char_u *seploc = start + dislocation; + STRMOVE(seploc, start); + for (s = start; s < seploc;) + MB_CHAR2BYTES(fillchar, s); + + for (int i = stl_separator_locations[l] + 1; i < itemcnt; i++) + stl_items[i].stl_start += dislocation; + } + + width = maxwidth; + } + } + + // Store the info about highlighting. + if (hltab != NULL) + { + *hltab = stl_hltab; + sp = stl_hltab; + for (l = 0; l < itemcnt; l++) + { + if (stl_items[l].stl_type == Highlight) + { + sp->start = stl_items[l].stl_start; + sp->userhl = stl_items[l].stl_minwid; + sp++; + } + } + sp->start = NULL; + sp->userhl = 0; + } + + // Store the info about tab pages labels. + if (tabtab != NULL) + { + *tabtab = stl_tabtab; + sp = stl_tabtab; + for (l = 0; l < itemcnt; l++) + { + if (stl_items[l].stl_type == TabPage) + { + sp->start = stl_items[l].stl_start; + sp->userhl = stl_items[l].stl_minwid; + sp++; + } + } + sp->start = NULL; + sp->userhl = 0; + } + + redraw_not_allowed = save_redraw_not_allowed; + + // A user function may reset KeyTyped, restore it. + KeyTyped = save_KeyTyped; + + // Check for an error. If there is one the display will be messed up and + // might loop redrawing. Avoid that by making the corresponding option + // empty. + // TODO: find out why using called_emsg_before makes tests fail, does it + // matter? + // if (called_emsg > called_emsg_before) + if (did_emsg > did_emsg_before) + set_string_option_direct(opt_name, -1, (char_u *)"", + OPT_FREE | opt_scope, SID_ERROR); + + return width; +} +#endif // FEAT_STL_OPT + +/* + * Get relative cursor position in window into "buf[buflen]", in the localized + * percentage form like %99, 99%; using "Top", "Bot" or "All" when appropriate. + */ + void +get_rel_pos( + win_T *wp, + char_u *buf, + int buflen) +{ + long above; // number of lines above window + long below; // number of lines below window + + if (buflen < 3) // need at least 3 chars for writing + return; + above = wp->w_topline - 1; +#ifdef FEAT_DIFF + above += diff_check_fill(wp, wp->w_topline) - wp->w_topfill; + if (wp->w_topline == 1 && wp->w_topfill >= 1) + above = 0; // All buffer lines are displayed and there is an + // indication of filler lines, that can be considered + // seeing all lines. +#endif + below = wp->w_buffer->b_ml.ml_line_count - wp->w_botline + 1; + if (below <= 0) + vim_strncpy(buf, (char_u *)(above == 0 ? _("All") : _("Bot")), + (size_t)(buflen - 1)); + else if (above <= 0) + vim_strncpy(buf, (char_u *)_("Top"), (size_t)(buflen - 1)); + else + { + int perc = (above > 1000000L) + ? (int)(above / ((above + below) / 100L)) + : (int)(above * 100L / (above + below)); + + char *p = (char *)buf; + size_t l = buflen; + if (perc < 10) + { + // prepend one space + buf[0] = ' '; + ++p; + --l; + } + // localized percentage value + vim_snprintf(p, l, _("%d%%"), perc); + } +} + +/* + * Append (file 2 of 8) to "buf[buflen]", if editing more than one file. + * Return TRUE if it was appended. + */ + static int +append_arg_number( + win_T *wp, + char_u *buf, + int buflen, + int add_file) // Add "file" before the arg number +{ + if (ARGCOUNT <= 1) // nothing to do + return FALSE; + + char *msg; + switch ((wp->w_arg_idx_invalid ? 1 : 0) + (add_file ? 2 : 0)) + { + case 0: msg = _(" (%d of %d)"); break; + case 1: msg = _(" ((%d) of %d)"); break; + case 2: msg = _(" (file %d of %d)"); break; + case 3: msg = _(" (file (%d) of %d)"); break; + } + + char_u *p = buf + STRLEN(buf); // go to the end of the buffer + vim_snprintf((char *)p, (size_t)(buflen - (p - buf)), msg, + wp->w_arg_idx + 1, ARGCOUNT); + return TRUE; +} + +/* + * If fname is not a full path, make it a full path. + * Returns pointer to allocated memory (NULL for failure). + */ + char_u * +fix_fname(char_u *fname) +{ + /* + * Force expanding the path always for Unix, because symbolic links may + * mess up the full path name, even though it starts with a '/'. + * Also expand when there is ".." in the file name, try to remove it, + * because "c:/src/../README" is equal to "c:/README". + * Similarly "c:/src//file" is equal to "c:/src/file". + * For MS-Windows also expand names like "longna~1" to "longname". + */ +#ifdef UNIX + return FullName_save(fname, TRUE); +#else + if (!vim_isAbsName(fname) + || strstr((char *)fname, "..") != NULL + || strstr((char *)fname, "//") != NULL +# ifdef BACKSLASH_IN_FILENAME + || strstr((char *)fname, "\\\\") != NULL +# endif +# if defined(MSWIN) + || vim_strchr(fname, '~') != NULL +# endif + ) + return FullName_save(fname, FALSE); + + fname = vim_strsave(fname); + +# ifdef USE_FNAME_CASE + if (fname != NULL) + fname_case(fname, 0); // set correct case for file name +# endif + + return fname; +#endif +} + +/* + * Make "*ffname" a full file name, set "*sfname" to "*ffname" if not NULL. + * "*ffname" becomes a pointer to allocated memory (or NULL). + * When resolving a link both "*sfname" and "*ffname" will point to the same + * allocated memory. + * The "*ffname" and "*sfname" pointer values on call will not be freed. + * Note that the resulting "*ffname" pointer should be considered not allocated. + */ + void +fname_expand( + buf_T *buf UNUSED, + char_u **ffname, + char_u **sfname) +{ + if (*ffname == NULL) // no file name given, nothing to do + return; + if (*sfname == NULL) // no short file name given, use ffname + *sfname = *ffname; + *ffname = fix_fname(*ffname); // expand to full path + +#ifdef FEAT_SHORTCUT + if (!buf->b_p_bin) + { + char_u *rfname; + + // If the file name is a shortcut file, use the file it links to. + rfname = mch_resolve_path(*ffname, FALSE); + if (rfname != NULL) + { + vim_free(*ffname); + *ffname = rfname; + *sfname = rfname; + } + } +#endif +} + +/* + * Open a window for a number of buffers. + */ + void +ex_buffer_all(exarg_T *eap) +{ + buf_T *buf; + win_T *wp, *wpnext; + int split_ret = OK; + int p_ea_save; + int open_wins = 0; + int r; + int count; // Maximum number of windows to open. + int all; // When TRUE also load inactive buffers. + int had_tab = cmdmod.cmod_tab; + tabpage_T *tpnext; + + if (eap->addr_count == 0) // make as many windows as possible + count = 9999; + else + count = eap->line2; // make as many windows as specified + if (eap->cmdidx == CMD_unhide || eap->cmdidx == CMD_sunhide) + all = FALSE; + else + all = TRUE; + + // Stop Visual mode, the cursor and "VIsual" may very well be invalid after + // switching to another buffer. + reset_VIsual_and_resel(); + + setpcmark(); + +#ifdef FEAT_GUI + need_mouse_correct = TRUE; +#endif + + /* + * Close superfluous windows (two windows for the same buffer). + * Also close windows that are not full-width. + */ + if (had_tab > 0) + goto_tabpage_tp(first_tabpage, TRUE, TRUE); + for (;;) + { + tpnext = curtab->tp_next; + for (wp = firstwin; wp != NULL; wp = wpnext) + { + wpnext = wp->w_next; + if ((wp->w_buffer->b_nwindows > 1 + || ((cmdmod.cmod_split & WSP_VERT) + ? wp->w_height + wp->w_status_height < Rows - p_ch + - tabline_height() + : wp->w_width != Columns) + || (had_tab > 0 && wp != firstwin)) + && !ONE_WINDOW + && !(wp->w_closing || wp->w_buffer->b_locked > 0) + && !win_unlisted(wp)) + { + if (win_close(wp, FALSE) == FAIL) + break; + // Just in case an autocommand does something strange with + // windows: start all over... + wpnext = firstwin; + tpnext = first_tabpage; + open_wins = 0; + } + else + ++open_wins; + } + + // Without the ":tab" modifier only do the current tab page. + if (had_tab == 0 || tpnext == NULL) + break; + goto_tabpage_tp(tpnext, TRUE, TRUE); + } + + /* + * Go through the buffer list. When a buffer doesn't have a window yet, + * open one. Otherwise move the window to the right position. + * Watch out for autocommands that delete buffers or windows! + */ + // Don't execute Win/Buf Enter/Leave autocommands here. + ++autocmd_no_enter; + win_enter(lastwin, FALSE); + ++autocmd_no_leave; + for (buf = firstbuf; buf != NULL && open_wins < count; buf = buf->b_next) + { + // Check if this buffer needs a window + if ((!all && buf->b_ml.ml_mfp == NULL) || !buf->b_p_bl) + continue; + + if (had_tab != 0) + { + // With the ":tab" modifier don't move the window. + if (buf->b_nwindows > 0) + wp = lastwin; // buffer has a window, skip it + else + wp = NULL; + } + else + { + // Check if this buffer already has a window + FOR_ALL_WINDOWS(wp) + if (wp->w_buffer == buf) + break; + // If the buffer already has a window, move it + if (wp != NULL) + win_move_after(wp, curwin); + } + + if (wp == NULL && split_ret == OK) + { + bufref_T bufref; + + set_bufref(&bufref, buf); + + // Split the window and put the buffer in it + p_ea_save = p_ea; + p_ea = TRUE; // use space from all windows + split_ret = win_split(0, WSP_ROOM | WSP_BELOW); + ++open_wins; + p_ea = p_ea_save; + if (split_ret == FAIL) + continue; + + // Open the buffer in this window. + swap_exists_action = SEA_DIALOG; + set_curbuf(buf, DOBUF_GOTO); + if (!bufref_valid(&bufref)) + { + // autocommands deleted the buffer!!! + swap_exists_action = SEA_NONE; + break; + } + if (swap_exists_action == SEA_QUIT) + { +#if defined(FEAT_EVAL) + cleanup_T cs; + + // Reset the error/interrupt/exception state here so that + // aborting() returns FALSE when closing a window. + enter_cleanup(&cs); +#endif + + // User selected Quit at ATTENTION prompt; close this window. + win_close(curwin, TRUE); + --open_wins; + swap_exists_action = SEA_NONE; + swap_exists_did_quit = TRUE; + +#if defined(FEAT_EVAL) + // Restore the error/interrupt/exception state if not + // discarded by a new aborting error, interrupt, or uncaught + // exception. + leave_cleanup(&cs); +#endif + } + else + handle_swap_exists(NULL); + } + + ui_breakcheck(); + if (got_int) + { + (void)vgetc(); // only break the file loading, not the rest + break; + } +#ifdef FEAT_EVAL + // Autocommands deleted the buffer or aborted script processing!!! + if (aborting()) + break; +#endif + // When ":tab" was used open a new tab for a new window repeatedly. + if (had_tab > 0 && tabpage_index(NULL) <= p_tpm) + cmdmod.cmod_tab = 9999; + } + --autocmd_no_enter; + win_enter(firstwin, FALSE); // back to first window + --autocmd_no_leave; + + /* + * Close superfluous windows. + */ + for (wp = lastwin; open_wins > count; ) + { + r = (buf_hide(wp->w_buffer) || !bufIsChanged(wp->w_buffer) + || autowrite(wp->w_buffer, FALSE) == OK); + if (!win_valid(wp)) + { + // BufWrite Autocommands made the window invalid, start over + wp = lastwin; + } + else if (r) + { + win_close(wp, !buf_hide(wp->w_buffer)); + --open_wins; + wp = lastwin; + } + else + { + wp = wp->w_prev; + if (wp == NULL) + break; + } + } +} + + +static int chk_modeline(linenr_T, int); + +/* + * do_modelines() - process mode lines for the current file + * + * "flags" can be: + * OPT_WINONLY only set options local to window + * OPT_NOWIN don't set options local to window + * + * Returns immediately if the "ml" option isn't set. + */ + void +do_modelines(int flags) +{ + linenr_T lnum; + int nmlines; + static int entered = 0; + + if (!curbuf->b_p_ml || (nmlines = (int)p_mls) == 0) + return; + + // Disallow recursive entry here. Can happen when executing a modeline + // triggers an autocommand, which reloads modelines with a ":do". + if (entered) + return; + + ++entered; + for (lnum = 1; curbuf->b_p_ml && lnum <= curbuf->b_ml.ml_line_count && lnum <= nmlines; + ++lnum) + if (chk_modeline(lnum, flags) == FAIL) + nmlines = 0; + + for (lnum = curbuf->b_ml.ml_line_count; curbuf->b_p_ml && lnum > 0 && lnum > nmlines + && lnum > curbuf->b_ml.ml_line_count - nmlines; --lnum) + if (chk_modeline(lnum, flags) == FAIL) + nmlines = 0; + --entered; +} + +#include "version.h" // for version number + +/* + * chk_modeline() - check a single line for a mode string + * Return FAIL if an error encountered. + */ + static int +chk_modeline( + linenr_T lnum, + int flags) // Same as for do_modelines(). +{ + char_u *s; + char_u *e; + char_u *linecopy; // local copy of any modeline found + int prev; + int vers; + int end; + int retval = OK; + sctx_T save_current_sctx; + ESTACK_CHECK_DECLARATION; + + 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) + break; + // Accept both "vim" and "Vim". + if ((s[0] == 'v' || s[0] == 'V') && s[1] == 'i' && s[2] == 'm') + { + if (s[3] == '<' || s[3] == '=' || s[3] == '>') + e = s + 4; + else + e = s + 3; + vers = getdigits(&e); + if (*e == ':' + && (s[0] != 'V' + || STRNCMP(skipwhite(e + 1), "set", 3) == 0) + && (s[3] == ':' + || (VIM_VERSION_100 >= vers && isdigit(s[3])) + || (VIM_VERSION_100 < vers && s[3] == '<') + || (VIM_VERSION_100 > vers && s[3] == '>') + || (VIM_VERSION_100 == vers && s[3] == '='))) + break; + } + } + prev = *s; + } + + if (*s) + { + do // skip over "ex:", "vi:" or "vim:" + ++s; + while (s[-1] != ':'); + + s = linecopy = vim_strsave(s); // copy the line, it will change + if (linecopy == NULL) + return FAIL; + + // prepare for emsg() + estack_push(ETYPE_MODELINE, (char_u *)"modelines", lnum); + ESTACK_CHECK_SETUP; + + end = FALSE; + while (end == FALSE) + { + s = skipwhite(s); + if (*s == NUL) + break; + + /* + * Find end of set command: ':' or end of line. + * Skip over "\:", replacing it with ":". + */ + for (e = s; *e != ':' && *e != NUL; ++e) + if (e[0] == '\\' && e[1] == ':') + STRMOVE(e, e + 1); + 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 + * Accept "se" for compatibility with Elvis. + */ + if (STRNCMP(s, "set ", (size_t)4) == 0 + || STRNCMP(s, "se ", (size_t)3) == 0) + { + if (*e != ':') // no terminating ':'? + break; + end = TRUE; + s = vim_strchr(s, ' ') + 1; + } + *e = NUL; // truncate the set command + + if (*s != NUL) // skip over an empty "::" + { + int secure_save = secure; + + save_current_sctx = current_sctx; + current_sctx.sc_version = 1; +#ifdef FEAT_EVAL + current_sctx.sc_sid = SID_MODELINE; + current_sctx.sc_seq = 0; + current_sctx.sc_lnum = lnum; +#endif + + // Make sure no risky things are executed as a side effect. + secure = 1; + + retval = do_set(s, OPT_MODELINE | OPT_LOCAL | flags); + + secure = secure_save; + current_sctx = save_current_sctx; + if (retval == FAIL) // stop if error found + break; + } + s = e + 1; // advance to next part + } + + ESTACK_CHECK_NOW; + estack_pop(); + vim_free(linecopy); + } + return retval; +} + +/* + * Return TRUE if "buf" is a normal buffer, 'buftype' is empty. + */ + int +bt_normal(buf_T *buf) +{ + return buf != NULL && buf->b_p_bt[0] == NUL; +} + +/* + * Return TRUE if "buf" is the quickfix buffer. + */ + int +bt_quickfix(buf_T *buf UNUSED) +{ +#ifdef FEAT_QUICKFIX + return buf != NULL && buf_valid(buf) && buf->b_p_bt[0] == 'q'; +#else + return FALSE; +#endif +} + +/* + * Return TRUE if "buf" is a terminal buffer. + */ + int +bt_terminal(buf_T *buf UNUSED) +{ +#if defined(FEAT_TERMINAL) + return buf != NULL && buf->b_p_bt[0] == 't'; +#else + return FALSE; +#endif +} + +/* + * Return TRUE if "buf" is a help buffer. + */ + int +bt_help(buf_T *buf) +{ + return buf != NULL && buf->b_help; +} + +/* + * Return TRUE if "buf" is a prompt buffer. + */ + int +bt_prompt(buf_T *buf) +{ + return buf != NULL && buf->b_p_bt[0] == 'p' && buf->b_p_bt[1] == 'r'; +} + +#if defined(FEAT_PROP_POPUP) || defined(PROTO) +/* + * Return TRUE if "buf" is a buffer for a popup window. + */ + int +bt_popup(buf_T *buf) +{ + return buf != NULL && buf->b_p_bt != NULL + && buf->b_p_bt[0] == 'p' && buf->b_p_bt[1] == 'o'; +} +#endif + +/* + * Return TRUE if "buf" is a "nofile", "acwrite", "terminal" or "prompt" + * buffer. This means the buffer name may not be a file name, at least not for + * writing the buffer. + */ + int +bt_nofilename(buf_T *buf) +{ + return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f') + || buf->b_p_bt[0] == 'a' + || buf->b_p_bt[0] == 't' + || buf->b_p_bt[0] == 'p'); +} + +/* + * Return TRUE if "buf" is a "nofile", "quickfix", "terminal" or "prompt" + * buffer. This means the buffer is not to be read from a file. + */ + static int +bt_nofileread(buf_T *buf) +{ + return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f') + || buf->b_p_bt[0] == 't' + || buf->b_p_bt[0] == 'q' + || buf->b_p_bt[0] == 'p'); +} + +#if defined(FEAT_QUICKFIX) || defined(PROTO) +/* + * Return TRUE if "buf" has 'buftype' set to "nofile". + */ + int +bt_nofile(buf_T *buf) +{ + return buf != NULL && buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f'; +} +#endif + +/* + * Return TRUE if "buf" is a "nowrite", "nofile", "terminal", "prompt", or + * "popup" buffer. + */ + int +bt_dontwrite(buf_T *buf) +{ + return buf != NULL && (buf->b_p_bt[0] == 'n' + || buf->b_p_bt[0] == 't' + || buf->b_p_bt[0] == 'p'); +} + + int +bt_dontwrite_msg(buf_T *buf) +{ + if (bt_dontwrite(buf)) + { + emsg(_(e_cannot_write_buftype_option_is_set)); + return TRUE; + } + return FALSE; +} + +/* + * Return TRUE if the buffer should be hidden, according to 'hidden', ":hide" + * and 'bufhidden'. + */ + int +buf_hide(buf_T *buf) +{ + // 'bufhidden' overrules 'hidden' and ":hide", check it first + switch (buf->b_p_bh[0]) + { + case 'u': // "unload" + case 'w': // "wipe" + case 'd': return FALSE; // "delete" + case 'h': return TRUE; // "hide" + } + return (p_hid || (cmdmod.cmod_flags & CMOD_HIDE)); +} + +/* + * Return special buffer name. + * Returns NULL when the buffer has a normal file name. + */ + char_u * +buf_spname(buf_T *buf) +{ +#if defined(FEAT_QUICKFIX) + if (bt_quickfix(buf)) + { + /* + * Differentiate between the quickfix and location list buffers using + * the buffer number stored in the global quickfix stack. + */ + if (buf->b_fnum == qf_stack_get_bufnr()) + return (char_u *)_(msg_qflist); + else + return (char_u *)_(msg_loclist); + } +#endif + + // There is no _file_ when 'buftype' is "nofile", b_sfname + // contains the name as specified by the user. + if (bt_nofilename(buf)) + { +#ifdef FEAT_TERMINAL + if (buf->b_term != NULL) + return term_get_status_text(buf->b_term); +#endif + if (buf->b_fname != NULL) + return buf->b_fname; +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(buf)) + return (char_u *)_("[Prompt]"); +#endif +#ifdef FEAT_PROP_POPUP + if (bt_popup(buf)) + return (char_u *)_("[Popup]"); +#endif + return (char_u *)_("[Scratch]"); + } + + if (buf->b_fname == NULL) + return buf_get_fname(buf); + return NULL; +} + +/* + * Get "buf->b_fname", use "[No Name]" if it is NULL. + */ + char_u * +buf_get_fname(buf_T *buf) +{ + if (buf->b_fname == NULL) + return (char_u *)_("[No Name]"); + return buf->b_fname; +} + +/* + * Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed. + */ + void +set_buflisted(int on) +{ + if (on == curbuf->b_p_bl) + return; + + curbuf->b_p_bl = on; + if (on) + apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, curbuf); + else + apply_autocmds(EVENT_BUFDELETE, NULL, NULL, FALSE, curbuf); +} + +/* + * Read the file for "buf" again and check if the contents changed. + * Return TRUE if it changed or this could not be checked. + */ + int +buf_contents_changed(buf_T *buf) +{ + buf_T *newbuf; + int differ = TRUE; + linenr_T lnum; + aco_save_T aco; + exarg_T ea; + + // Allocate a buffer without putting it in the buffer list. + newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY); + if (newbuf == NULL) + return TRUE; + + // Force the 'fileencoding' and 'fileformat' to be equal. + if (prep_exarg(&ea, buf) == FAIL) + { + wipe_buffer(newbuf, FALSE); + return TRUE; + } + + // Set curwin/curbuf to buf and save a few things. + aucmd_prepbuf(&aco, newbuf); + if (curbuf != newbuf) + { + // Failed to find a window for "newbuf". + wipe_buffer(newbuf, FALSE); + return TRUE; + } + + // We don't want to trigger autocommands now, they may have nasty + // side-effects like wiping buffers + block_autocmds(); + if (ml_open(curbuf) == OK + && readfile(buf->b_ffname, buf->b_fname, + (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, + &ea, READ_NEW | READ_DUMMY) == OK) + { + // compare the two files line by line + if (buf->b_ml.ml_line_count == curbuf->b_ml.ml_line_count) + { + differ = FALSE; + for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) + if (STRCMP(ml_get_buf(buf, lnum, FALSE), ml_get(lnum)) != 0) + { + differ = TRUE; + break; + } + } + } + vim_free(ea.cmd); + + // restore curwin/curbuf and a few other things + aucmd_restbuf(&aco); + + if (curbuf != newbuf) // safety check + wipe_buffer(newbuf, FALSE); + + unblock_autocmds(); + + return differ; +} + +/* + * Wipe out a buffer and decrement the last buffer number if it was used for + * this buffer. Call this to wipe out a temp buffer that does not contain any + * marks. + */ + void +wipe_buffer( + buf_T *buf, + int aucmd) // When TRUE trigger autocommands. +{ + if (buf->b_fnum == top_file_num - 1) + --top_file_num; + + if (!aucmd) // Don't trigger BufDelete autocommands here. + block_autocmds(); + + close_buffer(NULL, buf, DOBUF_WIPE, FALSE, TRUE); + + if (!aucmd) + unblock_autocmds(); +} diff --git a/userland/apps/vim/src/bufwrite.c b/userland/apps/vim/src/bufwrite.c new file mode 100644 index 00000000..bf79ad5b --- /dev/null +++ b/userland/apps/vim/src/bufwrite.c @@ -0,0 +1,2651 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * bufwrite.c: functions for writing a buffer + */ + +#include "vim.h" + +#if defined(HAVE_UTIME) && defined(HAVE_UTIME_H) +# include // for struct utimbuf +#endif + +#define SMALLBUFSIZE 256 // size of emergency write buffer + +/* + * Structure to pass arguments from buf_write() to buf_write_bytes(). + */ +struct bw_info +{ + int bw_fd; // file descriptor + char_u *bw_buf; // buffer with data to be written + int bw_len; // length of data + int bw_flags; // FIO_ flags +#ifdef FEAT_CRYPT + buf_T *bw_buffer; // buffer being written + int bw_finish; // finish encrypting +#endif + char_u bw_rest[CONV_RESTLEN]; // not converted bytes + int bw_restlen; // nr of bytes in bw_rest[] + int bw_first; // first write call + char_u *bw_conv_buf; // buffer for writing converted chars + size_t bw_conv_buflen; // size of bw_conv_buf + int bw_conv_error; // set for conversion error + linenr_T bw_conv_error_lnum; // first line with error or zero + linenr_T bw_start_lnum; // line number at start of buffer +#ifdef USE_ICONV + iconv_t bw_iconv_fd; // descriptor for iconv() or -1 +#endif +}; + +/* + * Convert a Unicode character to bytes. + * Return TRUE for an error, FALSE when it's OK. + */ + static int +ucs2bytes( + unsigned c, // in: character + char_u **pp, // in/out: pointer to result + int flags) // FIO_ flags +{ + char_u *p = *pp; + int error = FALSE; + int cc; + + + if (flags & FIO_UCS4) + { + if (flags & FIO_ENDIAN_L) + { + *p++ = c; + *p++ = (c >> 8); + *p++ = (c >> 16); + *p++ = (c >> 24); + } + else + { + *p++ = (c >> 24); + *p++ = (c >> 16); + *p++ = (c >> 8); + *p++ = c; + } + } + else if (flags & (FIO_UCS2 | FIO_UTF16)) + { + if (c >= 0x10000) + { + if (flags & FIO_UTF16) + { + // Make two words, ten bits of the character in each. First + // word is 0xd800 - 0xdbff, second one 0xdc00 - 0xdfff + c -= 0x10000; + if (c >= 0x100000) + error = TRUE; + cc = ((c >> 10) & 0x3ff) + 0xd800; + if (flags & FIO_ENDIAN_L) + { + *p++ = cc; + *p++ = ((unsigned)cc >> 8); + } + else + { + *p++ = ((unsigned)cc >> 8); + *p++ = cc; + } + c = (c & 0x3ff) + 0xdc00; + } + else + error = TRUE; + } + if (flags & FIO_ENDIAN_L) + { + *p++ = c; + *p++ = (c >> 8); + } + else + { + *p++ = (c >> 8); + *p++ = c; + } + } + else // Latin1 + { + if (c >= 0x100) + { + error = TRUE; + *p++ = 0xBF; + } + else + *p++ = c; + } + + *pp = p; + return error; +} + +/* + * Call write() to write a number of bytes to the file. + * Handles encryption and 'encoding' conversion. + * + * Return FAIL for failure, OK otherwise. + */ + static int +buf_write_bytes(struct bw_info *ip) +{ + int wlen; + char_u *buf = ip->bw_buf; // data to write + int len = ip->bw_len; // length of data + int flags = ip->bw_flags; // extra flags + + // Skip conversion when writing the crypt magic number or the BOM. + if (!(flags & FIO_NOCONVERT)) + { + char_u *p; + unsigned c; + int n; + + if (flags & FIO_UTF8) + { + // Convert latin1 in the buffer to UTF-8 in the file. + p = ip->bw_conv_buf; // translate to buffer + for (wlen = 0; wlen < len; ++wlen) + p += utf_char2bytes(buf[wlen], p); + buf = ip->bw_conv_buf; + len = (int)(p - ip->bw_conv_buf); + } + else if (flags & (FIO_UCS4 | FIO_UTF16 | FIO_UCS2 | FIO_LATIN1)) + { + // Convert UTF-8 bytes in the buffer to UCS-2, UCS-4, UTF-16 or + // Latin1 chars in the file. + if (flags & FIO_LATIN1) + p = buf; // translate in-place (can only get shorter) + else + p = ip->bw_conv_buf; // translate to buffer + for (wlen = 0; wlen < len; wlen += n) + { + if (wlen == 0 && ip->bw_restlen != 0) + { + int l; + + // Use remainder of previous call. Append the start of + // buf[] to get a full sequence. Might still be too + // short! + l = CONV_RESTLEN - ip->bw_restlen; + if (l > len) + l = len; + mch_memmove(ip->bw_rest + ip->bw_restlen, buf, (size_t)l); + n = utf_ptr2len_len(ip->bw_rest, ip->bw_restlen + l); + if (n > ip->bw_restlen + len) + { + // We have an incomplete byte sequence at the end to + // be written. We can't convert it without the + // remaining bytes. Keep them for the next call. + if (ip->bw_restlen + len > CONV_RESTLEN) + return FAIL; + ip->bw_restlen += len; + break; + } + if (n > 1) + c = utf_ptr2char(ip->bw_rest); + else + c = ip->bw_rest[0]; + if (n >= ip->bw_restlen) + { + n -= ip->bw_restlen; + ip->bw_restlen = 0; + } + else + { + ip->bw_restlen -= n; + mch_memmove(ip->bw_rest, ip->bw_rest + n, + (size_t)ip->bw_restlen); + n = 0; + } + } + else + { + n = utf_ptr2len_len(buf + wlen, len - wlen); + if (n > len - wlen) + { + // We have an incomplete byte sequence at the end to + // be written. We can't convert it without the + // remaining bytes. Keep them for the next call. + if (len - wlen > CONV_RESTLEN) + return FAIL; + ip->bw_restlen = len - wlen; + mch_memmove(ip->bw_rest, buf + wlen, + (size_t)ip->bw_restlen); + break; + } + if (n > 1) + c = utf_ptr2char(buf + wlen); + else + c = buf[wlen]; + } + + if (ucs2bytes(c, &p, flags) && !ip->bw_conv_error) + { + ip->bw_conv_error = TRUE; + ip->bw_conv_error_lnum = ip->bw_start_lnum; + } + if (c == NL) + ++ip->bw_start_lnum; + } + if (flags & FIO_LATIN1) + len = (int)(p - buf); + else + { + buf = ip->bw_conv_buf; + len = (int)(p - ip->bw_conv_buf); + } + } + +#ifdef MSWIN + else if (flags & FIO_CODEPAGE) + { + // Convert UTF-8 or codepage to UCS-2 and then to MS-Windows + // codepage. + char_u *from; + size_t fromlen; + char_u *to; + int u8c; + BOOL bad = FALSE; + int needed; + + if (ip->bw_restlen > 0) + { + // Need to concatenate the remainder of the previous call and + // the bytes of the current call. Use the end of the + // conversion buffer for this. + fromlen = len + ip->bw_restlen; + from = ip->bw_conv_buf + ip->bw_conv_buflen - fromlen; + mch_memmove(from, ip->bw_rest, (size_t)ip->bw_restlen); + mch_memmove(from + ip->bw_restlen, buf, (size_t)len); + } + else + { + from = buf; + fromlen = len; + } + + to = ip->bw_conv_buf; + if (enc_utf8) + { + // Convert from UTF-8 to UCS-2, to the start of the buffer. + // The buffer has been allocated to be big enough. + while (fromlen > 0) + { + n = (int)utf_ptr2len_len(from, (int)fromlen); + if (n > (int)fromlen) // incomplete byte sequence + break; + u8c = utf_ptr2char(from); + *to++ = (u8c & 0xff); + *to++ = (u8c >> 8); + fromlen -= n; + from += n; + } + + // Copy remainder to ip->bw_rest[] to be used for the next + // call. + if (fromlen > CONV_RESTLEN) + { + // weird overlong sequence + ip->bw_conv_error = TRUE; + return FAIL; + } + mch_memmove(ip->bw_rest, from, fromlen); + ip->bw_restlen = (int)fromlen; + } + else + { + // Convert from enc_codepage to UCS-2, to the start of the + // buffer. The buffer has been allocated to be big enough. + ip->bw_restlen = 0; + needed = MultiByteToWideChar(enc_codepage, + MB_ERR_INVALID_CHARS, (LPCSTR)from, (int)fromlen, + NULL, 0); + if (needed == 0) + { + // When conversion fails there may be a trailing byte. + needed = MultiByteToWideChar(enc_codepage, + MB_ERR_INVALID_CHARS, (LPCSTR)from, (int)fromlen - 1, + NULL, 0); + if (needed == 0) + { + // Conversion doesn't work. + ip->bw_conv_error = TRUE; + return FAIL; + } + // Save the trailing byte for the next call. + ip->bw_rest[0] = from[fromlen - 1]; + ip->bw_restlen = 1; + } + needed = MultiByteToWideChar(enc_codepage, MB_ERR_INVALID_CHARS, + (LPCSTR)from, (int)(fromlen - ip->bw_restlen), + (LPWSTR)to, needed); + if (needed == 0) + { + // Safety check: Conversion doesn't work. + ip->bw_conv_error = TRUE; + return FAIL; + } + to += needed * 2; + } + + fromlen = to - ip->bw_conv_buf; + buf = to; +# ifdef CP_UTF8 // VC 4.1 doesn't define CP_UTF8 + if (FIO_GET_CP(flags) == CP_UTF8) + { + // Convert from UCS-2 to UTF-8, using the remainder of the + // conversion buffer. Fails when out of space. + for (from = ip->bw_conv_buf; fromlen > 1; fromlen -= 2) + { + u8c = *from++; + u8c += (*from++ << 8); + to += utf_char2bytes(u8c, to); + if (to + 6 >= ip->bw_conv_buf + ip->bw_conv_buflen) + { + ip->bw_conv_error = TRUE; + return FAIL; + } + } + len = (int)(to - buf); + } + else +# endif + { + // Convert from UCS-2 to the codepage, using the remainder of + // the conversion buffer. If the conversion uses the default + // character "0", the data doesn't fit in this encoding, so + // fail. + len = WideCharToMultiByte(FIO_GET_CP(flags), 0, + (LPCWSTR)ip->bw_conv_buf, (int)fromlen / sizeof(WCHAR), + (LPSTR)to, (int)(ip->bw_conv_buflen - fromlen), 0, + &bad); + if (bad) + { + ip->bw_conv_error = TRUE; + return FAIL; + } + } + } +#endif + +#ifdef MACOS_CONVERT + else if (flags & FIO_MACROMAN) + { + // Convert UTF-8 or latin1 to Apple MacRoman. + char_u *from; + size_t fromlen; + + if (ip->bw_restlen > 0) + { + // Need to concatenate the remainder of the previous call and + // the bytes of the current call. Use the end of the + // conversion buffer for this. + fromlen = len + ip->bw_restlen; + from = ip->bw_conv_buf + ip->bw_conv_buflen - fromlen; + mch_memmove(from, ip->bw_rest, (size_t)ip->bw_restlen); + mch_memmove(from + ip->bw_restlen, buf, (size_t)len); + } + else + { + from = buf; + fromlen = len; + } + + if (enc2macroman(from, fromlen, + ip->bw_conv_buf, &len, ip->bw_conv_buflen, + ip->bw_rest, &ip->bw_restlen) == FAIL) + { + ip->bw_conv_error = TRUE; + return FAIL; + } + buf = ip->bw_conv_buf; + } +#endif + +#ifdef USE_ICONV + if (ip->bw_iconv_fd != (iconv_t)-1) + { + const char *from; + size_t fromlen; + char *to; + size_t tolen; + + // Convert with iconv(). + if (ip->bw_restlen > 0) + { + char *fp; + + // Need to concatenate the remainder of the previous call and + // the bytes of the current call. Use the end of the + // conversion buffer for this. + fromlen = len + ip->bw_restlen; + fp = (char *)ip->bw_conv_buf + ip->bw_conv_buflen - fromlen; + mch_memmove(fp, ip->bw_rest, (size_t)ip->bw_restlen); + mch_memmove(fp + ip->bw_restlen, buf, (size_t)len); + from = fp; + tolen = ip->bw_conv_buflen - fromlen; + } + else + { + from = (const char *)buf; + fromlen = len; + tolen = ip->bw_conv_buflen; + } + to = (char *)ip->bw_conv_buf; + + if (ip->bw_first) + { + size_t save_len = tolen; + + // output the initial shift state sequence + (void)iconv(ip->bw_iconv_fd, NULL, NULL, &to, &tolen); + + // There is a bug in iconv() on Linux (which appears to be + // wide-spread) which sets "to" to NULL and messes up "tolen". + if (to == NULL) + { + to = (char *)ip->bw_conv_buf; + tolen = save_len; + } + ip->bw_first = FALSE; + } + + // If iconv() has an error or there is not enough room, fail. + if ((iconv(ip->bw_iconv_fd, (void *)&from, &fromlen, &to, &tolen) + == (size_t)-1 && ICONV_ERRNO != ICONV_EINVAL) + || fromlen > CONV_RESTLEN) + { + ip->bw_conv_error = TRUE; + return FAIL; + } + + // copy remainder to ip->bw_rest[] to be used for the next call. + if (fromlen > 0) + mch_memmove(ip->bw_rest, (void *)from, fromlen); + ip->bw_restlen = (int)fromlen; + + buf = ip->bw_conv_buf; + len = (int)((char_u *)to - ip->bw_conv_buf); + } +#endif + } + + if (ip->bw_fd < 0) + // Only checking conversion, which is OK if we get here. + return OK; + +#ifdef FEAT_CRYPT + if (flags & FIO_ENCRYPTED) + { + // Encrypt the data. Do it in-place if possible, otherwise use an + // allocated buffer. +# ifdef CRYPT_NOT_INPLACE + if (crypt_works_inplace(ip->bw_buffer->b_cryptstate)) + { +# endif + crypt_encode_inplace(ip->bw_buffer->b_cryptstate, buf, len, + ip->bw_finish); +# ifdef CRYPT_NOT_INPLACE + } + else + { + char_u *outbuf; + + len = crypt_encode_alloc(curbuf->b_cryptstate, buf, len, &outbuf, + ip->bw_finish); + if (len == 0) + return OK; // Crypt layer is buffering, will flush later. + wlen = write_eintr(ip->bw_fd, outbuf, len); + vim_free(outbuf); + return (wlen < len) ? FAIL : OK; + } +# endif + } +#endif + + wlen = write_eintr(ip->bw_fd, buf, len); + return (wlen < len) ? FAIL : OK; +} + +/* + * Check modification time of file, before writing to it. + * The size isn't checked, because using a tool like "gzip" takes care of + * using the same timestamp but can't set the size. + */ + static int +check_mtime(buf_T *buf, stat_T *st) +{ + if (buf->b_mtime_read != 0 + && time_differs(st, buf->b_mtime_read, buf->b_mtime_read_ns)) + { + msg_scroll = TRUE; // don't overwrite messages here + msg_silent = 0; // must give this prompt + // don't use emsg() here, don't want to flush the buffers + msg_attr(_("WARNING: The file has been changed since reading it!!!"), + HL_ATTR(HLF_E)); + if (ask_yesno((char_u *)_("Do you really want to write to it"), + TRUE) == 'n') + return FAIL; + msg_scroll = FALSE; // always overwrite the file message now + } + return OK; +} + +/* + * Generate a BOM in "buf[4]" for encoding "name". + * Return the length of the BOM (zero when no BOM). + */ + static int +make_bom(char_u *buf, char_u *name) +{ + int flags; + char_u *p; + + flags = get_fio_flags(name); + + // Can't put a BOM in a non-Unicode file. + if (flags == FIO_LATIN1 || flags == 0) + return 0; + + if (flags == FIO_UTF8) // UTF-8 + { + buf[0] = 0xef; + buf[1] = 0xbb; + buf[2] = 0xbf; + return 3; + } + p = buf; + (void)ucs2bytes(0xfeff, &p, flags); + return (int)(p - buf); +} + +#ifdef UNIX + static void +set_file_time( + char_u *fname, + time_t atime, // access time + time_t mtime) // modification time +{ +# if defined(HAVE_UTIME) && defined(HAVE_UTIME_H) + struct utimbuf buf; + + buf.actime = atime; + buf.modtime = mtime; + (void)utime((char *)fname, &buf); +# else +# if defined(HAVE_UTIMES) + struct timeval tvp[2]; + + tvp[0].tv_sec = atime; + tvp[0].tv_usec = 0; + tvp[1].tv_sec = mtime; + tvp[1].tv_usec = 0; +# ifdef NeXT + (void)utimes((char *)fname, tvp); +# else + (void)utimes((char *)fname, (const struct timeval *)&tvp); +# endif +# endif +# endif +} +#endif // UNIX + + char * +new_file_message(void) +{ + return shortmess(SHM_NEW) ? _("[New]") : _("[New File]"); +} + +/* + * buf_write() - 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. + * In case of an error everything possible is done to restore the original + * file. But when "forceit" is TRUE, we risk losing it. + * + * When "reset_changed" is TRUE and "append" == FALSE 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_T *buf, + char_u *fname, + char_u *sfname, + linenr_T start, + linenr_T end, + exarg_T *eap, // for forced 'ff' and 'fenc', can be + // NULL! + int append, // append to the file + int forceit, + int reset_changed, + int filtering) +{ + int fd; + char_u *backup = NULL; + int backup_copy = FALSE; // copy the original file? + int dobackup; + char_u *ffname; + char_u *wfname = NULL; // name of file to write to + char_u *s; + char_u *ptr; + char_u c; + int len; + linenr_T lnum; + long nchars; + char_u *errmsg = NULL; + int errmsg_allocated = FALSE; + char_u *errnum = NULL; + char_u *buffer; + char_u smallbuf[SMALLBUFSIZE]; + char_u *backup_ext; + int bufsize; + long perm; // 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 + int no_eol = FALSE; // no end-of-line written + int device = FALSE; // writing to a device + stat_T st_old; + int prev_got_int = got_int; + int checking_conversion; + int file_readonly = FALSE; // overwritten file is read-only +#if defined(UNIX) // XXX fix me sometime? + int made_writable = FALSE; // 'w' bit has been set +#endif + // writing everything + int whole = (start == 1 && end == buf->b_ml.ml_line_count); + linenr_T old_line_count = buf->b_ml.ml_line_count; + int attr; + int fileformat; + int write_bin; + struct bw_info write_info; // info for buf_write_bytes() + int converted = FALSE; + int notconverted = FALSE; + char_u *fenc; // effective 'fileencoding' + char_u *fenc_tofree = NULL; // allocated "fenc" + int wb_flags = 0; +#ifdef HAVE_ACL + vim_acl_T acl = NULL; // ACL copied from original file to + // backup or new file +#endif +#ifdef FEAT_PERSISTENT_UNDO + int write_undo_file = FALSE; + context_sha256_T sha_ctx; +#endif + unsigned int bkc = get_bkc_value(buf); + pos_T orig_start = buf->b_op_start; + pos_T orig_end = buf->b_op_end; + + if (fname == NULL || *fname == NUL) // safety check + return FAIL; + if (buf->b_ml.ml_mfp == NULL) + { + // This can happen during startup when there is a stray "w" in the + // vimrc file. + emsg(_(e_empty_buffer)); + return FAIL; + } + + // Disallow writing from .exrc and .vimrc in current directory for + // security reasons. + if (check_secure()) + return FAIL; + + // Avoid a crash for a long name. + if (STRLEN(fname) >= MAXPATHL) + { + emsg(_(e_name_too_long)); + return FAIL; + } + + // must init bw_conv_buf and bw_iconv_fd before jumping to "fail" + write_info.bw_conv_buf = NULL; + write_info.bw_conv_error = FALSE; + write_info.bw_conv_error_lnum = 0; + write_info.bw_restlen = 0; +#ifdef USE_ICONV + write_info.bw_iconv_fd = (iconv_t)-1; +#endif +#ifdef FEAT_CRYPT + write_info.bw_buffer = buf; + write_info.bw_finish = FALSE; +#endif + + // After writing a file changedtick changes but we don't want to display + // the line. + ex_no_reprint = TRUE; + + // If there is no file name yet, use the one for the written file. + // BF_NOTEDITED is set to reflect this (in case the write fails). + // Don't do this when the write is for a filter command. + // Don't do this when appending. + // Only do this when 'cpoptions' contains the 'F' flag. + if (buf->b_ffname == NULL + && reset_changed + && whole + && buf == curbuf + && !bt_nofilename(buf) + && !filtering + && (!append || vim_strchr(p_cpo, CPO_FNAMEAPP) != NULL) + && vim_strchr(p_cpo, CPO_FNAMEW) != NULL) + { + if (set_rw_fname(fname, sfname) == FAIL) + return FAIL; + buf = curbuf; // just in case autocmds made "buf" invalid + } + + if (sfname == NULL) + sfname = fname; + // For Unix: Use the short file name 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 + fname = sfname; +#endif + + if (buf->b_ffname != NULL && fnamecmp(ffname, buf->b_ffname) == 0) + overwriting = TRUE; + else + overwriting = FALSE; + + if (exiting) + settmode(TMODE_COOK); // when exiting allow typeahead 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; + + { + aco_save_T aco; + int buf_ffname = FALSE; + int buf_sfname = FALSE; + int buf_fname_f = FALSE; + int buf_fname_s = FALSE; + int did_cmd = FALSE; + int nofile_err = FALSE; + int empty_memline = (buf->b_ml.ml_mfp == NULL); + bufref_T bufref; + + // Apply PRE autocommands. + // Set curbuf to the buffer to be written. + // Careful: The autocommands may call buf_write() recursively! + if (ffname == buf->b_ffname) + buf_ffname = TRUE; + if (sfname == buf->b_sfname) + buf_sfname = TRUE; + if (fname == buf->b_ffname) + buf_fname_f = TRUE; + if (fname == buf->b_sfname) + buf_fname_s = TRUE; + + // Set curwin/curbuf to buf and save a few things. + aucmd_prepbuf(&aco, buf); + if (curbuf != buf) + { + // Could not find a window for "buf". Doing more might cause + // problems, better bail out. + return FAIL; + } + + set_bufref(&bufref, buf); + + if (append) + { + if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEAPPENDCMD, + sfname, sfname, FALSE, curbuf, eap))) + { + if (overwriting && bt_nofilename(curbuf)) + nofile_err = TRUE; + else + apply_autocmds_exarg(EVENT_FILEAPPENDPRE, + sfname, sfname, FALSE, curbuf, eap); + } + } + else if (filtering) + { + apply_autocmds_exarg(EVENT_FILTERWRITEPRE, + NULL, sfname, FALSE, curbuf, eap); + } + else if (reset_changed && whole) + { + int was_changed = curbufIsChanged(); + + did_cmd = apply_autocmds_exarg(EVENT_BUFWRITECMD, + sfname, sfname, FALSE, curbuf, eap); + if (did_cmd) + { + if (was_changed && !curbufIsChanged()) + { + // Written everything correctly and BufWriteCmd has reset + // 'modified': Correct the undo information so that an + // undo now sets 'modified'. + u_unchanged(curbuf); + u_update_save_nr(curbuf); + } + } + else + { + if (overwriting && bt_nofilename(curbuf)) + nofile_err = TRUE; + else + apply_autocmds_exarg(EVENT_BUFWRITEPRE, + sfname, sfname, FALSE, curbuf, eap); + } + } + else + { + if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEWRITECMD, + sfname, sfname, FALSE, curbuf, eap))) + { + if (overwriting && bt_nofilename(curbuf)) + nofile_err = TRUE; + else + apply_autocmds_exarg(EVENT_FILEWRITEPRE, + sfname, sfname, FALSE, curbuf, eap); + } + } + + // restore curwin/curbuf and a few other things + aucmd_restbuf(&aco); + + // In three situations we return here and don't write the file: + // 1. the autocommands deleted or unloaded the buffer. + // 2. The autocommands abort script processing. + // 3. If one of the "Cmd" autocommands was executed. + if (!bufref_valid(&bufref)) + buf = NULL; + if (buf == NULL || (buf->b_ml.ml_mfp == NULL && !empty_memline) + || did_cmd || nofile_err +#ifdef FEAT_EVAL + || aborting() +#endif + ) + { + if (buf != NULL && (cmdmod.cmod_flags & CMOD_LOCKMARKS)) + { + // restore the original '[ and '] positions + buf->b_op_start = orig_start; + buf->b_op_end = orig_end; + } + + --no_wait_return; + msg_scroll = msg_save; + if (nofile_err) + semsg(_(e_no_matching_autocommands_for_buftype_str_buffer), + curbuf->b_p_bt); + + if (nofile_err +#ifdef FEAT_EVAL + || aborting() +#endif + ) + // An aborting error, interrupt or exception in the + // autocommands. + return FAIL; + if (did_cmd) + { + if (buf == NULL) + // The buffer was deleted. We assume it was written + // (can't retry anyway). + return OK; + if (overwriting) + { + // Assume the buffer was written, update the timestamp. + ml_timestamp(buf); + if (append) + buf->b_flags &= ~BF_NEW; + else + buf->b_flags &= ~BF_WRITE_MASK; + } + if (reset_changed && buf->b_changed && !append + && (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)) + // Buffer still changed, the autocommands didn't work + // properly. + return FAIL; + return OK; + } +#ifdef FEAT_EVAL + if (!aborting()) +#endif + emsg(_(e_autocommands_deleted_or_unloaded_buffer_to_be_written)); + return FAIL; + } + + // 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) // write 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; + msg_scroll = msg_save; + emsg(_(e_autocommands_changed_number_of_lines_in_unexpected_way)); + return FAIL; + } + } + } + + // The autocommands may have changed the name of the buffer, which may + // be kept in fname, ffname and sfname. + if (buf_ffname) + ffname = buf->b_ffname; + if (buf_sfname) + sfname = buf->b_sfname; + if (buf_fname_f) + fname = buf->b_ffname; + if (buf_fname_s) + fname = buf->b_sfname; + } + + if (cmdmod.cmod_flags & CMOD_LOCKMARKS) + { + // restore the original '[ and '] positions + buf->b_op_start = orig_start; + buf->b_op_end = orig_end; + } + +#ifdef FEAT_NETBEANS_INTG + if (netbeans_active() && isNetbeansBuffer(buf)) + { + if (whole) + { + // b_changed can be 0 after an undo, but we still need to write + // the buffer to NetBeans. + if (buf->b_changed || isNetbeansModified(buf)) + { + --no_wait_return; // may wait for return now + msg_scroll = msg_save; + netbeans_save_buffer(buf); // no error checking... + return retval; + } + else + { + errnum = (char_u *)"E656: "; + errmsg = (char_u *)_(e_netbeans_disallows_writes_of_unmodified_buffers); + buffer = NULL; + goto fail; + } + } + else + { + errnum = (char_u *)"E657: "; + errmsg = (char_u *)_(e_partial_writes_disallowed_for_netbeans_buffers); + buffer = NULL; + goto fail; + } + } +#endif + + if (shortmess(SHM_OVER) && !exiting) + msg_scroll = FALSE; // overwrite previous file message + else + msg_scroll = TRUE; // don't overwrite previous file message + if (!filtering) + filemess(buf, +#ifndef UNIX + sfname, +#else + fname, +#endif + (char_u *)"", 0); // show that we are busy + msg_scroll = FALSE; // always overwrite the file message now + + buffer = alloc(WRITEBUFSIZE); + if (buffer == NULL) // can't allocate big buffer, use small + // one (to be able to write when out of + // memory) + { + buffer = smallbuf; + bufsize = SMALLBUFSIZE; + } + else + bufsize = WRITEBUFSIZE; + + // Get information about original file (if there is one). +#if defined(UNIX) + st_old.st_dev = 0; + st_old.st_ino = 0; + perm = -1; + if (mch_stat((char *)fname, &st_old) < 0) + newfile = TRUE; + else + { + perm = st_old.st_mode; + if (!S_ISREG(st_old.st_mode)) // not a file + { + if (S_ISDIR(st_old.st_mode)) + { + errnum = (char_u *)"E502: "; + errmsg = (char_u *)_(e_is_a_directory); + goto fail; + } + if (mch_nodetype(fname) != NODE_WRITABLE) + { + errnum = (char_u *)"E503: "; + errmsg = (char_u *)_(e_is_not_file_or_writable_device); + goto fail; + } + // It's a device of some kind (or a fifo) which we can write to + // but for which we can't make a backup. + device = TRUE; + newfile = TRUE; + perm = -1; + } + } +#else // !UNIX + // Check for a writable device name. + c = mch_nodetype(fname); + if (c == NODE_OTHER) + { + errnum = (char_u *)"E503: "; + errmsg = (char_u *)_(e_is_not_file_or_writable_device); + goto fail; + } + if (c == NODE_WRITABLE) + { +# if defined(MSWIN) + // MS-Windows allows opening a device, but we will probably get stuck + // trying to write to it. + if (!p_odev) + { + errnum = (char_u *)"E796: "; + errmsg = (char_u *)_(e_writing_to_device_disabled_with_opendevice_option); + goto fail; + } +# endif + device = TRUE; + newfile = TRUE; + perm = -1; + } + else + { + perm = mch_getperm(fname); + if (perm < 0) + newfile = TRUE; + else if (mch_isdir(fname)) + { + errnum = (char_u *)"E502: "; + errmsg = (char_u *)_(e_is_a_directory); + goto fail; + } + if (overwriting) + (void)mch_stat((char *)fname, &st_old); + } +#endif // !UNIX + + if (!device && !newfile) + { + // Check if the file is really writable (when renaming the file to + // make a backup we won't discover it later). + file_readonly = check_file_readonly(fname, (int)perm); + + if (!forceit && file_readonly) + { + if (vim_strchr(p_cpo, CPO_FWRITE) != NULL) + { + errnum = (char_u *)"E504: "; + errmsg = (char_u *)_(e_is_read_only_cannot_override_W_in_cpoptions); + } + else + { + errnum = (char_u *)"E505: "; + errmsg = (char_u *)_(e_is_read_only_add_bang_to_override); + } + goto fail; + } + + // Check if the timestamp hasn't changed since reading the file. + if (overwriting) + { + retval = check_mtime(buf, &st_old); + if (retval == FAIL) + goto fail; + } + } + +#ifdef HAVE_ACL + // For systems that support ACL: get the ACL from the original file. + if (!newfile) + acl = mch_get_acl(fname); +#endif + + // If 'backupskip' is not empty, don't make a backup for some files. + dobackup = (p_wb || p_bk || *p_pm != NUL); + if (dobackup && *p_bsk != NUL && match_file_list(p_bsk, sfname, ffname)) + dobackup = FALSE; + + // Save the value of got_int and reset it. We don't want a previous + // interruption cancel writing, only hitting CTRL-C while writing should + // abort it. + prev_got_int = got_int; + got_int = FALSE; + + // Mark the buffer as 'being saved' to prevent changed buffer warnings + buf->b_saving = TRUE; + + // If we are not appending or filtering, the file exists, and the + // 'writebackup', 'backup' or 'patchmode' option is set, need a backup. + // When 'patchmode' is set also make a backup when appending. + // + // Do not make any backup, if 'writebackup' and 'backup' are both switched + // off. This helps when editing large files on almost-full disks. + if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) + { +#if defined(UNIX) || defined(MSWIN) + stat_T st; +#endif + + if ((bkc & BKC_YES) || append) // "yes" + backup_copy = TRUE; +#if defined(UNIX) || defined(MSWIN) + else if ((bkc & BKC_AUTO)) // "auto" + { + int i; + +# ifdef UNIX + // Don't rename the file when: + // - it's a hard link + // - it's a symbolic link + // - we don't have write permission in the directory + // - we can't set the owner/group of the new file + if (st_old.st_nlink > 1 + || mch_lstat((char *)fname, &st) < 0 + || st.st_dev != st_old.st_dev + || st.st_ino != st_old.st_ino +# ifndef HAVE_FCHOWN + || st.st_uid != st_old.st_uid + || st.st_gid != st_old.st_gid +# endif + ) + backup_copy = TRUE; + else +# else +# ifdef MSWIN + // On NTFS file systems hard links are possible. + if (mch_is_linked(fname)) + backup_copy = TRUE; + else +# endif +# endif + { + // Check if we can create a file and set the owner/group to + // the ones from the original file. + // First find a file name that doesn't exist yet (use some + // arbitrary numbers). + STRCPY(IObuff, fname); + fd = -1; + for (i = 4913; ; i += 123) + { + sprintf((char *)gettail(IObuff), "%d", i); + if (mch_lstat((char *)IObuff, &st) < 0) + { + fd = mch_open((char *)IObuff, + O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm); + if (fd < 0 && errno == EEXIST) + // If the same file name is created by another + // process between lstat() and open(), find another + // name. + continue; + break; + } + } + if (fd < 0) // can't write in directory + backup_copy = TRUE; + else + { +# ifdef UNIX +# ifdef HAVE_FCHOWN + vim_ignored = fchown(fd, st_old.st_uid, st_old.st_gid); +# endif + if (mch_stat((char *)IObuff, &st) < 0 + || st.st_uid != st_old.st_uid + || st.st_gid != st_old.st_gid + || (long)st.st_mode != perm) + backup_copy = TRUE; +# endif + // Close the file before removing it, on MS-Windows we + // can't delete an open file. + close(fd); + mch_remove(IObuff); +# ifdef MSWIN + // MS-Windows may trigger a virus scanner to open the + // file, we can't delete it then. Keep trying for half a + // second. + { + int try; + + for (try = 0; try < 10; ++try) + { + if (mch_lstat((char *)IObuff, &st) < 0) + break; + ui_delay(50L, TRUE); // wait 50 msec + mch_remove(IObuff); + } + } +# endif + } + } + } + + // Break symlinks and/or hardlinks if we've been asked to. + if ((bkc & BKC_BREAKSYMLINK) || (bkc & BKC_BREAKHARDLINK)) + { +# ifdef UNIX + int lstat_res; + + lstat_res = mch_lstat((char *)fname, &st); + + // Symlinks. + if ((bkc & BKC_BREAKSYMLINK) + && lstat_res == 0 + && st.st_ino != st_old.st_ino) + backup_copy = FALSE; + + // Hardlinks. + if ((bkc & BKC_BREAKHARDLINK) + && st_old.st_nlink > 1 + && (lstat_res != 0 || st.st_ino == st_old.st_ino)) + backup_copy = FALSE; +# else +# if defined(MSWIN) + // Symlinks. + if ((bkc & BKC_BREAKSYMLINK) && mch_is_symbolic_link(fname)) + backup_copy = FALSE; + + // Hardlinks. + if ((bkc & BKC_BREAKHARDLINK) && mch_is_hard_link(fname)) + backup_copy = FALSE; +# endif +# endif + } + +#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 (backup_copy + && (fd = mch_open((char *)fname, O_RDONLY | O_EXTRA, 0)) >= 0) + { + int bfd; + char_u *copybuf, *wp; + int some_error = FALSE; + stat_T st_new; + char_u *dirp; + char_u *rootname; +#if defined(UNIX) || defined(MSWIN) + char_u *p; +#endif +#if defined(UNIX) + int did_set_shortname; + mode_t umask_save; +#endif + + copybuf = alloc(WRITEBUFSIZE + 1); + if (copybuf == NULL) + { + some_error = TRUE; // out of memory + goto nobackup; + } + + // 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) + { +#ifdef UNIX + st_new.st_ino = 0; + st_new.st_dev = 0; + st_new.st_gid = 0; +#endif + + // Isolate one directory name, using an entry in 'bdir'. + (void)copy_option_part(&dirp, copybuf, WRITEBUFSIZE, ","); + +#if defined(UNIX) || defined(MSWIN) + p = copybuf + STRLEN(copybuf); + if (after_pathsep(copybuf, p) && p[-1] == p[-2]) + // Ends with '//', use full path + if ((p = make_percent_swname(copybuf, fname)) != NULL) + { + backup = modname(p, backup_ext, FALSE); + vim_free(p); + } +#endif + rootname = get_file_in_dir(fname, copybuf); + if (rootname == NULL) + { + some_error = TRUE; // out of memory + goto nobackup; + } + +#if defined(UNIX) + did_set_shortname = FALSE; +#endif + + // May try twice if 'shortname' not set. + for (;;) + { + // Make the backup file name. + if (backup == NULL) + backup = buf_modname((buf->b_p_sn || buf->b_shortname), + rootname, backup_ext, FALSE); + if (backup == NULL) + { + vim_free(rootname); + some_error = TRUE; // out of memory + goto nobackup; + } + + // Check if backup file already exists. + if (mch_stat((char *)backup, &st_new) >= 0) + { +#ifdef UNIX + // Check if backup file is same as original file. + // May happen when modname() gave the same file back. + // E.g. silly link, or file name-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_CLEAR(backup); // no backup file to delete + // 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; + break; + } +#endif + + // 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' + && mch_stat((char *)backup, &st_new) >= 0) + --*wp; + // They all exist??? Must be something wrong. + if (*wp == 'a') + VIM_CLEAR(backup); + } + } + break; + } + vim_free(rootname); + + // Try to create the backup file + if (backup != NULL) + { + // remove old backup, if present + mch_remove(backup); + // Open with O_EXCL to avoid the file being created while + // we were sleeping (symlink hacker attack?). Reset umask + // if possible to avoid mch_setperm() below. +#ifdef UNIX + umask_save = umask(0); +#endif + bfd = mch_open((char *)backup, + O_WRONLY|O_CREAT|O_EXTRA|O_EXCL|O_NOFOLLOW, + perm & 0777); +#ifdef UNIX + (void)umask(umask_save); +#endif + if (bfd < 0) + VIM_CLEAR(backup); + else + { + // Set file protection same as original file, but + // strip s-bit. Only needed if umask() wasn't used + // above. +#ifndef UNIX + (void)mch_setperm(backup, perm & 0777); +#else + // 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, (uid_t)-1, st_old.st_gid) != 0 +# endif + ) + mch_setperm(backup, + (perm & 0707) | ((perm & 07) << 3)); +# if defined(HAVE_SELINUX) || defined(HAVE_SMACK) + mch_copy_sec(fname, backup); +# endif +# ifdef FEAT_XATTR + mch_copy_xattr(fname, backup); +# endif +#endif + + // copy the file. + write_info.bw_fd = bfd; + write_info.bw_buf = copybuf; + write_info.bw_flags = FIO_NOCONVERT; + while ((write_info.bw_len = read_eintr(fd, copybuf, + WRITEBUFSIZE)) > 0) + { + if (buf_write_bytes(&write_info) == FAIL) + { + errmsg = (char_u *)_(e_cant_write_to_backup_file_add_bang_to_override); + break; + } + ui_breakcheck(); + if (got_int) + { + errmsg = (char_u *)_(e_interrupted); + break; + } + } + + if (close(bfd) < 0 && errmsg == NULL) + errmsg = (char_u *)_(e_close_error_for_backup_file_add_bang_to_write_anyway); + if (write_info.bw_len < 0) + errmsg = (char_u *)_(e_cant_read_file_for_backup_add_bang_to_write_anyway); +#ifdef UNIX + set_file_time(backup, st_old.st_atime, st_old.st_mtime); +#endif +#ifdef HAVE_ACL + mch_set_acl(backup, acl); +#endif +#if defined(HAVE_SELINUX) || defined(HAVE_SMACK) + mch_copy_sec(fname, backup); +#endif +#ifdef FEAT_XATTR + mch_copy_xattr(fname, backup); +#endif +#ifdef MSWIN + (void)mch_copy_file_attribute(fname, backup); +#endif + break; + } + } + } + nobackup: + close(fd); // ignore errors for closing read file + vim_free(copybuf); + + if (backup == NULL && errmsg == NULL) + errmsg = (char_u *)_(e_cannot_create_backup_file_add_bang_to_write_anyway); + // ignore errors when forceit is TRUE + if ((some_error || errmsg != NULL) && !forceit) + { + retval = FAIL; + goto fail; + } + errmsg = NULL; + } + else + { + char_u *dirp; + char_u *p; + char_u *rootname; + + // Make a backup by renaming the original file. + + // If 'cpoptions' includes the "W" flag, we don't want to + // overwrite a read-only file. But rename may be possible + // anyway, thus we need an extra check here. + if (file_readonly && vim_strchr(p_cpo, CPO_FWRITE) != NULL) + { + errnum = (char_u *)"E504: "; + errmsg = (char_u *)_(e_is_read_only_cannot_override_W_in_cpoptions); + goto fail; + } + + // 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 and make the backup file name. + (void)copy_option_part(&dirp, IObuff, IOSIZE, ","); + +#if defined(UNIX) || defined(MSWIN) + p = IObuff + STRLEN(IObuff); + if (after_pathsep(IObuff, p) && p[-1] == p[-2]) + // path ends with '//', use full path + if ((p = make_percent_swname(IObuff, fname)) != NULL) + { + backup = modname(p, backup_ext, FALSE); + vim_free(p); + } +#endif + if (backup == NULL) + { + rootname = get_file_in_dir(fname, IObuff); + if (rootname == NULL) + backup = NULL; + else + { + backup = buf_modname( + (buf->b_p_sn || buf->b_shortname), + rootname, backup_ext, FALSE); + vim_free(rootname); + } + } + + 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 && mch_getperm(backup) >= 0) + { + p = backup + STRLEN(backup) - 1 - STRLEN(backup_ext); + if (p < backup) // empty file name ??? + p = backup; + *p = 'z'; + while (*p > 'a' && mch_getperm(backup) >= 0) + --*p; + // They all exist??? Must be something wrong! + if (*p == 'a') + VIM_CLEAR(backup); + } + } + 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. + + // If the renaming of the original file to the backup file + // works, quit here. + if (vim_rename(fname, backup) == 0) + break; + + VIM_CLEAR(backup); // don't do the rename below + } + } + if (backup == NULL && !forceit) + { + errmsg = (char_u *)_(e_cant_make_backup_file_add_bang_to_write_anyway); + goto fail; + } + } + } + +#if defined(UNIX) + // When using ":w!" and the file was read-only: make it writable + if (forceit && perm >= 0 && !(perm & 0200) && st_old.st_uid == getuid() + && vim_strchr(p_cpo, CPO_FWRITE) == NULL) + { + perm |= 0200; + (void)mch_setperm(fname, perm); + made_writable = TRUE; + } +#endif + + // When using ":w!" and writing to the current file, 'readonly' makes no + // sense, reset it, unless 'Z' appears in 'cpoptions'. + if (forceit && overwriting && vim_strchr(p_cpo, CPO_KEEPRO) == NULL) + { + buf->b_p_ro = FALSE; + need_maketitle = TRUE; // set window title later + status_redraw_all(); // redraw status lines later + } + + if (end > buf->b_ml.ml_line_count) + end = buf->b_ml.ml_line_count; + if (buf->b_ml.ml_flags & ML_EMPTY) + start = end + 1; + + // 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 && overwriting + && !(exiting && backup != NULL)) + { + ml_preserve(buf, FALSE); + if (got_int) + { + errmsg = (char_u *)_(e_interrupted); + goto restore_backup; + } + } + +#ifdef VMS + vms_remove_version(fname); // remove version +#endif + // Default: write the file directly. May write to a temp file for + // multi-byte conversion. + wfname = fname; + + // Check for forced 'fileencoding' from "++opt=val" argument. + if (eap != NULL && eap->force_enc != 0) + { + fenc = eap->cmd + eap->force_enc; + fenc = enc_canonize(fenc); + fenc_tofree = fenc; + } + else + fenc = buf->b_p_fenc; + + // Check if the file needs to be converted. + converted = need_conversion(fenc); + + // Check if UTF-8 to UCS-2/4 or Latin1 conversion needs to be done. Or + // Latin1 to Unicode conversion. This is handled in buf_write_bytes(). + // Prepare the flags for it and allocate bw_conv_buf when needed. + if (converted && (enc_utf8 || STRCMP(p_enc, "latin1") == 0)) + { + wb_flags = get_fio_flags(fenc); + if (wb_flags & (FIO_UCS2 | FIO_UCS4 | FIO_UTF16 | FIO_UTF8)) + { + // Need to allocate a buffer to translate into. + if (wb_flags & (FIO_UCS2 | FIO_UTF16 | FIO_UTF8)) + write_info.bw_conv_buflen = bufsize * 2; + else // FIO_UCS4 + write_info.bw_conv_buflen = bufsize * 4; + write_info.bw_conv_buf = alloc(write_info.bw_conv_buflen); + if (write_info.bw_conv_buf == NULL) + end = 0; + } + } + +#ifdef MSWIN + if (converted && wb_flags == 0 && (wb_flags = get_win_fio_flags(fenc)) != 0) + { + // Convert UTF-8 -> UCS-2 and UCS-2 -> DBCS. Worst-case * 4: + write_info.bw_conv_buflen = bufsize * 4; + write_info.bw_conv_buf = alloc(write_info.bw_conv_buflen); + if (write_info.bw_conv_buf == NULL) + end = 0; + } +#endif + +#ifdef MACOS_CONVERT + if (converted && wb_flags == 0 && (wb_flags = get_mac_fio_flags(fenc)) != 0) + { + write_info.bw_conv_buflen = bufsize * 3; + write_info.bw_conv_buf = alloc(write_info.bw_conv_buflen); + if (write_info.bw_conv_buf == NULL) + end = 0; + } +#endif + +#if defined(FEAT_EVAL) || defined(USE_ICONV) + if (converted && wb_flags == 0) + { +# ifdef USE_ICONV + // Use iconv() conversion when conversion is needed and it's not done + // internally. + write_info.bw_iconv_fd = (iconv_t)my_iconv_open(fenc, + enc_utf8 ? (char_u *)"utf-8" : p_enc); + if (write_info.bw_iconv_fd != (iconv_t)-1) + { + // We're going to use iconv(), allocate a buffer to convert in. + write_info.bw_conv_buflen = bufsize * ICONV_MULT; + write_info.bw_conv_buf = alloc(write_info.bw_conv_buflen); + if (write_info.bw_conv_buf == NULL) + end = 0; + write_info.bw_first = TRUE; + } +# ifdef FEAT_EVAL + else +# endif +# endif + +# ifdef FEAT_EVAL + // When the file needs to be converted with 'charconvert' after + // writing, write to a temp file instead and let the conversion + // overwrite the original file. + if (*p_ccv != NUL) + { + wfname = vim_tempname('w', FALSE); + if (wfname == NULL) // Can't write without a tempfile! + { + errmsg = (char_u *)_(e_cant_find_temp_file_for_writing); + goto restore_backup; + } + } +# endif + } +#endif + if (converted && wb_flags == 0 +#ifdef USE_ICONV + && write_info.bw_iconv_fd == (iconv_t)-1 +# endif +# ifdef FEAT_EVAL + && wfname == fname +# endif + ) + { + if (!forceit) + { + errmsg = (char_u *)_(e_cannot_convert_add_bang_to_write_without_conversion); + goto restore_backup; + } + notconverted = TRUE; + } + + // If conversion is taking place, we may first pretend to write and check + // for conversion errors. Then loop again to write for real. + // When not doing conversion this writes for real right away. + for (checking_conversion = TRUE; ; checking_conversion = FALSE) + { + // There is no need to check conversion when: + // - there is no conversion + // - we make a backup file, that can be restored in case of conversion + // failure. + if (!converted || dobackup) + checking_conversion = FALSE; + + if (checking_conversion) + { + // Make sure we don't write anything. + fd = -1; + write_info.bw_fd = fd; + } + else + { +#ifdef HAVE_FTRUNCATE +# define TRUNC_ON_OPEN 0 +#else +# define TRUNC_ON_OPEN O_TRUNC +#endif + // Open the file "wfname" for writing. + // 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 = mch_open((char *)wfname, O_WRONLY | O_EXTRA | (append + ? (forceit ? (O_APPEND | O_CREAT) : O_APPEND) + : (O_CREAT | TRUNC_ON_OPEN)) + , perm < 0 ? 0666 : (perm & 0777))) < 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 mch_remove() will fail. + if (errmsg == NULL) + { +#ifdef UNIX + stat_T st; + + // Don't delete the file when it's a hard or symbolic link. + if ((!newfile && st_old.st_nlink > 1) + || (mch_lstat((char *)fname, &st) == 0 + && (st.st_dev != st_old.st_dev + || st.st_ino != st_old.st_ino))) + errmsg = + (char_u *)_(e_cant_open_linked_file_for_writing); + else +#endif + { + errmsg = (char_u *)_(e_cant_open_file_for_writing); + if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL + && perm >= 0) + { +#ifdef UNIX + // we write to the file, thus it should be marked + // writable after all + if (!(perm & 0200)) + made_writable = TRUE; + perm |= 0200; + if (st_old.st_uid != getuid() + || st_old.st_gid != getgid()) + perm &= 0777; +#endif + if (!append) // don't remove when appending + mch_remove(wfname); + continue; + } + } + } + +restore_backup: + { + stat_T st; + + // 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 && wfname == fname) + { + if (backup_copy) + { + // 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. + + // If file does not exist, put the copy in its + // place + if (mch_stat((char *)fname, &st) < 0) + vim_rename(backup, fname); + // if original file does exist throw away the copy + if (mch_stat((char *)fname, &st) >= 0) + mch_remove(backup); + } + else + { + // try to put the original file back + vim_rename(backup, fname); + } + } + + // if original file no longer exists give an extra warning + if (!newfile && mch_stat((char *)fname, &st) < 0) + end = 0; + } + + if (wfname != fname) + vim_free(wfname); + goto fail; + } + write_info.bw_fd = fd; + +#if defined(UNIX) + { + stat_T st; + + // Double check we are writing the intended file before making + // any changes. + if (overwriting + && (!dobackup || backup_copy) + && fname == wfname + && perm >= 0 + && mch_fstat(fd, &st) == 0 + && st.st_ino != st_old.st_ino) + { + close(fd); + errmsg = (char_u *)_(e_file_changed_while_writing); + goto fail; + } + } +#endif +#ifdef HAVE_FTRUNCATE + if (!append) + vim_ignored = ftruncate(fd, (off_t)0); +#endif + +#if defined(MSWIN) + if (backup != NULL && overwriting && !append) + (void)mch_copy_file_attribute(backup, wfname); + + if (!overwriting && !append) + { + if (buf->b_ffname != NULL) + (void)mch_copy_file_attribute(buf->b_ffname, wfname); + // Should copy resource fork + } +#endif + +#ifdef FEAT_CRYPT + if (*buf->b_p_key != NUL && !filtering) + { + char_u *header; + int header_len; + + buf->b_cryptstate = crypt_create_for_writing( + crypt_get_method_nr(buf), + buf->b_p_key, &header, &header_len); + if (buf->b_cryptstate == NULL || header == NULL) + end = 0; + else + { + // Write magic number, so that Vim knows how this file is + // encrypted when reading it back. + write_info.bw_buf = header; + write_info.bw_len = header_len; + write_info.bw_flags = FIO_NOCONVERT; + if (buf_write_bytes(&write_info) == FAIL) + end = 0; + wb_flags |= FIO_ENCRYPTED; + vim_free(header); + } + } +#endif + } + errmsg = NULL; + + write_info.bw_buf = buffer; + nchars = 0; + + // use "++bin", "++nobin" or 'binary' + if (eap != NULL && eap->force_bin != 0) + write_bin = (eap->force_bin == FORCE_BIN); + else + write_bin = buf->b_p_bin; + + // The BOM is written just after the encryption magic number. + // Skip it when appending and the file already existed, the BOM only + // makes sense at the start of the file. + if (buf->b_p_bomb && !write_bin && (!append || perm < 0)) + { + write_info.bw_len = make_bom(buffer, fenc); + if (write_info.bw_len > 0) + { + // don't convert, do encryption + write_info.bw_flags = FIO_NOCONVERT | wb_flags; + if (buf_write_bytes(&write_info) == FAIL) + end = 0; + else + nchars += write_info.bw_len; + } + } + write_info.bw_start_lnum = start; + +#ifdef FEAT_PERSISTENT_UNDO + write_undo_file = (buf->b_p_udf + && overwriting + && !append + && !filtering +# ifdef CRYPT_NOT_INPLACE + // writing undo file requires + // crypt_encode_inplace() + && (buf->b_cryptstate == NULL + || crypt_works_inplace(buf->b_cryptstate)) +# endif + && reset_changed + && !checking_conversion); +# ifdef CRYPT_NOT_INPLACE + // remove undo file if encrypting it is not possible + if (buf->b_p_udf + && overwriting + && !append + && !filtering + && !checking_conversion + && buf->b_cryptstate != NULL + && !crypt_works_inplace(buf->b_cryptstate)) + u_undofile_reset_and_delete(buf); +# endif + if (write_undo_file) + // Prepare for computing the hash value of the text. + sha256_start(&sha_ctx); +#endif + + write_info.bw_len = bufsize; + write_info.bw_flags = wb_flags; + fileformat = get_fileformat_force(buf, eap); + s = buffer; + len = 0; + 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; +#ifdef FEAT_PERSISTENT_UNDO + if (write_undo_file) + sha256_update(&sha_ctx, ptr + 1, + (UINT32_T)(STRLEN(ptr + 1) + 1)); +#endif + while ((c = *++ptr) != NUL) + { + if (c == NL) + *s = NUL; // replace newlines with NULs + else if (c == CAR && fileformat == EOL_MAC) + *s = NL; // Mac: replace CRs with NLs + else + *s = c; + ++s; + if (++len != bufsize) + continue; +#ifdef FEAT_CRYPT + if (write_info.bw_fd > 0 && lnum == end + && (write_info.bw_flags & FIO_ENCRYPTED) + && *buf->b_p_key != NUL && !filtering + && *ptr == NUL) + write_info.bw_finish = TRUE; + #endif + if (buf_write_bytes(&write_info) == FAIL) + { + end = 0; // write error: break loop + break; + } + nchars += bufsize; + s = buffer; + len = 0; + write_info.bw_start_lnum = lnum; + } + // write failed or last line has no EOL: stop here + if (end == 0 + || (lnum == end + && (write_bin || !buf->b_p_fixeol) + && ((write_bin && lnum == buf->b_no_eol_lnum) + || (lnum == buf->b_ml.ml_line_count + && !buf->b_p_eol)))) + { + ++lnum; // written the line, count it + no_eol = TRUE; + break; + } + if (fileformat == EOL_UNIX) + *s++ = NL; + else + { + *s++ = CAR; // EOL_MAC or EOL_DOS: write CR + if (fileformat == EOL_DOS) // write CR-NL + { + if (++len == bufsize) + { + if (buf_write_bytes(&write_info) == FAIL) + { + end = 0; // write error: break loop + break; + } + nchars += bufsize; + s = buffer; + len = 0; + } + *s++ = NL; + } + } + if (++len == bufsize && end) + { + if (buf_write_bytes(&write_info) == FAIL) + { + end = 0; // write error: break loop + break; + } + nchars += bufsize; + s = buffer; + len = 0; + + ui_breakcheck(); + if (got_int) + { + end = 0; // Interrupted, break loop + break; + } + } +#ifdef VMS + // On VMS there is a problem: newlines get added when writing + // blocks at a time. Fix it by writing a line at a time. + // This is much slower! + // Explanation: VAX/DECC RTL insists that records in some RMS + // structures end with a newline (carriage return) character, and + // if they don't it adds one. + // With other RMS structures it works perfect without this fix. +# ifndef MIN +// Older DECC compiler for VAX doesn't define MIN() +# define MIN(a, b) ((a) < (b) ? (a) : (b)) +# endif + if (buf->b_fab_rfm == FAB$C_VFC + || ((buf->b_fab_rat & (FAB$M_FTN | FAB$M_CR)) != 0)) + { + int b2write; + + buf->b_fab_mrs = (buf->b_fab_mrs == 0 + ? MIN(4096, bufsize) + : MIN(buf->b_fab_mrs, bufsize)); + + b2write = len; + while (b2write > 0) + { + write_info.bw_len = MIN(b2write, buf->b_fab_mrs); + if (buf_write_bytes(&write_info) == FAIL) + { + end = 0; + break; + } + b2write -= MIN(b2write, buf->b_fab_mrs); + } + write_info.bw_len = bufsize; + nchars += len; + s = buffer; + len = 0; + } +#endif + } + if (len > 0 && end > 0) + { + write_info.bw_len = len; +#ifdef FEAT_CRYPT + if (write_info.bw_fd > 0 && lnum >= end + && (write_info.bw_flags & FIO_ENCRYPTED) + && *buf->b_p_key != NUL && !filtering) + write_info.bw_finish = TRUE; + #endif + if (buf_write_bytes(&write_info) == FAIL) + end = 0; // write error + nchars += len; + } + + if (!buf->b_p_fixeol && buf->b_p_eof) + { + // write trailing CTRL-Z + (void)write_eintr(write_info.bw_fd, "\x1a", 1); + nchars++; + } + + // Stop when writing done or an error was encountered. + if (!checking_conversion || end == 0) + break; + + // If no error happened until now, writing should be ok, so loop to + // really write the buffer. + } + + // If we started writing, finish writing. Also when an error was + // encountered. + if (!checking_conversion) + { +#if defined(UNIX) && defined(HAVE_FSYNC) + // On many journaling file systems there is a bug that causes both the + // original and the backup file to be lost when halting the system + // right after writing the file. That's because only the meta-data is + // journalled. Syncing the file slows down the system, but assures it + // has been written to disk and we don't lose it. + // For a device do try the fsync() but don't complain if it does not + // work (could be a pipe). + // If the 'fsync' option is FALSE, don't fsync(). Useful for laptops. + if (p_fs && vim_fsync(fd) != 0 && !device) + { + errmsg = (char_u *)_(e_fsync_failed); + end = 0; + } +#endif + +#if defined(HAVE_SELINUX) || defined(HAVE_SMACK) || defined(FEAT_XATTR) + // Probably need to set the security context. + if (!backup_copy) + { +#if defined(HAVE_SELINUX) || defined(HAVE_SMACK) + mch_copy_sec(backup, wfname); +#endif +#ifdef FEAT_XATTR + mch_copy_xattr(backup, wfname); +#endif + } +#endif + +#ifdef UNIX + // When creating a new file, set its owner/group to that of the + // original file. Get the new device and inode number. + if (backup != NULL && !backup_copy) + { +# ifdef HAVE_FCHOWN + stat_T st; + + // Don't change the owner when it's already OK, some systems remove + // permission or ACL stuff. + if (mch_stat((char *)wfname, &st) < 0 + || st.st_uid != st_old.st_uid + || st.st_gid != st_old.st_gid) + { + // changing owner might not be possible + vim_ignored = fchown(fd, st_old.st_uid, -1); + // if changing group fails clear the group permissions + if (fchown(fd, -1, st_old.st_gid) == -1 && perm > 0) + perm &= ~070; + } +# endif + buf_setino(buf); + } + else if (!buf->b_dev_valid) + // Set the inode when creating a new file. + buf_setino(buf); +#endif + +#ifdef UNIX + if (made_writable) + perm &= ~0200; // reset 'w' bit for security reasons +#endif +#ifdef HAVE_FCHMOD + // set permission of new file same as old file + if (perm >= 0) + (void)mch_fsetperm(fd, perm); +#endif + if (close(fd) != 0) + { + errmsg = (char_u *)_(e_close_failed); + end = 0; + } + +#ifndef HAVE_FCHMOD + // set permission of new file same as old file + if (perm >= 0) + (void)mch_setperm(wfname, perm); +#endif +#ifdef HAVE_ACL + // Probably need to set the ACL before changing the user (can't set the + // ACL on a file the user doesn't own). + // On Solaris, with ZFS and the aclmode property set to "discard" (the + // default), chmod() discards all part of a file's ACL that don't + // represent the mode of the file. It's non-trivial for us to discover + // whether we're in that situation, so we simply always re-set the ACL. +# ifndef HAVE_SOLARIS_ZFS_ACL + if (!backup_copy) +# endif + mch_set_acl(wfname, acl); +#endif +#ifdef FEAT_CRYPT + if (buf->b_cryptstate != NULL) + { + crypt_free_state(buf->b_cryptstate); + buf->b_cryptstate = NULL; + } +#endif + +#if defined(FEAT_EVAL) + if (wfname != fname) + { + // The file was written to a temp file, now it needs to be + // converted with 'charconvert' to (overwrite) the output file. + if (end != 0) + { + if (eval_charconvert(enc_utf8 ? (char_u *)"utf-8" : p_enc, + fenc, wfname, fname) == FAIL) + { + write_info.bw_conv_error = TRUE; + end = 0; + } + } + mch_remove(wfname); + vim_free(wfname); + } +#endif + } + + if (end == 0) + { + // Error encountered. + if (errmsg == NULL) + { + if (write_info.bw_conv_error) + { + if (write_info.bw_conv_error_lnum == 0) + errmsg = (char_u *)_(e_write_error_conversion_failed_make_fenc_empty_to_override); + else + { + errmsg_allocated = TRUE; + errmsg = alloc(300); + vim_snprintf((char *)errmsg, 300, _(e_write_error_conversion_failed_in_line_nr_make_fenc_empty_to_override), + (long)write_info.bw_conv_error_lnum); + } + } + else if (got_int) + errmsg = (char_u *)_(e_interrupted); + else + errmsg = (char_u *)_(e_write_error_file_system_full); + } + + // If we have a backup file, try to put it in place of the new file, + // because the new file is probably corrupt. This avoids losing the + // original file when trying to make a backup when writing the file a + // second time. + // When "backup_copy" is set we need to copy the backup over the new + // file. Otherwise rename the backup file. + // If this is OK, don't give the extra warning message. + if (backup != NULL) + { + if (backup_copy) + { + // This may take a while, if we were interrupted let the user + // know we got the message. + if (got_int) + { + msg(_(e_interrupted)); + out_flush(); + } + if ((fd = mch_open((char *)backup, O_RDONLY | O_EXTRA, 0)) >= 0) + { + if ((write_info.bw_fd = mch_open((char *)fname, + O_WRONLY | O_CREAT | O_TRUNC | O_EXTRA, + perm & 0777)) >= 0) + { + // copy the file. + write_info.bw_buf = smallbuf; + write_info.bw_flags = FIO_NOCONVERT; + while ((write_info.bw_len = read_eintr(fd, smallbuf, + SMALLBUFSIZE)) > 0) + if (buf_write_bytes(&write_info) == FAIL) + break; + + if (close(write_info.bw_fd) >= 0 + && write_info.bw_len == 0) + end = 1; // success + } + close(fd); // ignore errors for closing read file + } + } + else + { + if (vim_rename(backup, fname) == 0) + end = 1; + } + } + goto fail; + } + + lnum -= start; // compute number of written lines + --no_wait_return; // may wait for return now + +#if !(defined(UNIX) || defined(VMS)) + fname = sfname; // use shortname now, for the messages +#endif + if (!filtering) + { + msg_add_fname(buf, fname); // put fname in IObuff with quotes + c = FALSE; + if (write_info.bw_conv_error) + { + STRCAT(IObuff, _(" CONVERSION ERROR")); + c = TRUE; + if (write_info.bw_conv_error_lnum != 0) + vim_snprintf_add((char *)IObuff, IOSIZE, _(" in line %ld;"), + (long)write_info.bw_conv_error_lnum); + } + else if (notconverted) + { + STRCAT(IObuff, _("[NOT converted]")); + c = TRUE; + } + else if (converted) + { + STRCAT(IObuff, _("[converted]")); + c = TRUE; + } + if (device) + { + STRCAT(IObuff, _("[Device]")); + c = TRUE; + } + else if (newfile) + { + STRCAT(IObuff, new_file_message()); + c = TRUE; + } + if (no_eol) + { + msg_add_eol(); + c = TRUE; + } + // may add [unix/dos/mac] + if (msg_add_fileformat(fileformat)) + c = TRUE; +#ifdef FEAT_CRYPT + if (wb_flags & FIO_ENCRYPTED) + { + crypt_append_msg(buf); + c = TRUE; + } +#endif + msg_add_lines(c, (long)lnum, nchars); // add line/char count + if (!shortmess(SHM_WRITE)) + { + if (append) + STRCAT(IObuff, shortmess(SHM_WRI) ? _(" [a]") : _(" appended")); + else + STRCAT(IObuff, shortmess(SHM_WRI) ? _(" [w]") : _(" written")); + } + + set_keep_msg((char_u *)msg_trunc_attr((char *)IObuff, FALSE, 0), 0); + } + + // When written everything correctly: reset 'modified'. Unless not + // writing to the original file and '+' is not in 'cpoptions'. + if (reset_changed && whole && !append + && !write_info.bw_conv_error + && (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)) + { + unchanged(buf, TRUE, FALSE); + // b:changedtick may be incremented in unchanged() but that should not + // trigger a TextChanged event. + if (buf->b_last_changedtick + 1 == CHANGEDTICK(buf)) + buf->b_last_changedtick = CHANGEDTICK(buf); + u_unchanged(buf); + u_update_save_nr(buf); + } + + // If written to the current file, update the timestamp of the swap file + // and reset the BF_WRITE_MASK flags. Also sets buf->b_mtime. + if (overwriting) + { + ml_timestamp(buf); + if (append) + buf->b_flags &= ~BF_NEW; + else + buf->b_flags &= ~BF_WRITE_MASK; + } + + // 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 && dobackup) + { + char *org = (char *)buf_modname((buf->b_p_sn || buf->b_shortname), + fname, p_pm, FALSE); + + if (backup != NULL) + { + stat_T st; + + // If the original file does not exist yet + // the current backup file becomes the original file + if (org == NULL) + emsg(_(e_patchmode_cant_save_original_file)); + else if (mch_stat(org, &st) < 0) + { + vim_rename(backup, (char_u *)org); + VIM_CLEAR(backup); // don't delete the file +#ifdef UNIX + set_file_time((char_u *)org, st_old.st_atime, st_old.st_mtime); +#endif + } + } + // If there is no backup file, remember that a (new) file was + // created. + else + { + int empty_fd; + + if (org == NULL + || (empty_fd = mch_open(org, + O_CREAT | O_EXTRA | O_EXCL | O_NOFOLLOW, + perm < 0 ? 0666 : (perm & 0777))) < 0) + emsg(_(e_patchmode_cant_touch_empty_original_file)); + else + close(empty_fd); + } + if (org != NULL) + { + mch_setperm((char_u *)org, mch_getperm(fname) & 0777); + vim_free(org); + } + } + + // Remove the backup unless 'backup' option is set or there was a + // conversion error. + if (!p_bk && backup != NULL && !write_info.bw_conv_error + && mch_remove(backup) != 0) + emsg(_(e_cant_delete_backup_file)); + + goto nofail; + + // Finish up. We get here either after failure or success. +fail: + --no_wait_return; // may wait for return now +nofail: + + // Done saving, we accept changed buffer warnings again + buf->b_saving = FALSE; + + vim_free(backup); + if (buffer != smallbuf) + vim_free(buffer); + vim_free(fenc_tofree); + vim_free(write_info.bw_conv_buf); +#ifdef USE_ICONV + if (write_info.bw_iconv_fd != (iconv_t)-1) + { + iconv_close(write_info.bw_iconv_fd); + write_info.bw_iconv_fd = (iconv_t)-1; + } +#endif +#ifdef HAVE_ACL + mch_free_acl(acl); +#endif + + if (errmsg != NULL) + { + int numlen = errnum != NULL ? (int)STRLEN(errnum) : 0; + + attr = HL_ATTR(HLF_E); // set highlight for error messages + msg_add_fname(buf, +#ifndef UNIX + sfname +#else + fname +#endif + ); // put file name in IObuff with quotes + if (STRLEN(IObuff) + STRLEN(errmsg) + numlen >= IOSIZE) + IObuff[IOSIZE - STRLEN(errmsg) - numlen - 1] = NUL; + // If the error message has the form "is ...", put the error number in + // front of the file name. + if (errnum != NULL) + { + STRMOVE(IObuff + numlen, IObuff); + mch_memmove(IObuff, errnum, (size_t)numlen); + } + STRCAT(IObuff, errmsg); + emsg((char *)IObuff); + if (errmsg_allocated) + vim_free(errmsg); + + retval = FAIL; + if (end == 0) + { + msg_puts_attr(_("\nWARNING: Original file may be lost or damaged\n"), + attr | MSG_HIST); + msg_puts_attr(_("don't quit the editor until the file is successfully written!"), + attr | MSG_HIST); + + // Update the timestamp to avoid an "overwrite changed file" + // prompt when writing again. + if (mch_stat((char *)fname, &st_old) >= 0) + { + buf_store_time(buf, &st_old, fname); + buf->b_mtime_read = buf->b_mtime; + buf->b_mtime_read_ns = buf->b_mtime_ns; + } + } + } + msg_scroll = msg_save; + +#ifdef FEAT_PERSISTENT_UNDO + // When writing the whole file and 'undofile' is set, also write the undo + // file. + if (retval == OK && write_undo_file) + { + char_u hash[UNDO_HASH_SIZE]; + + sha256_finish(&sha_ctx, hash); + u_write_undo(NULL, FALSE, buf, hash); + } +#endif + +#ifdef FEAT_EVAL + if (!should_abort(retval)) +#else + if (!got_int) +#endif + { + aco_save_T aco; + + curbuf->b_no_eol_lnum = 0; // in case it was set by the previous read + + // Apply POST autocommands. + // Careful: The autocommands may call buf_write() recursively! + // Only do this when a window was found for "buf". + aucmd_prepbuf(&aco, buf); + if (curbuf == buf) + { + if (append) + apply_autocmds_exarg(EVENT_FILEAPPENDPOST, fname, fname, + FALSE, curbuf, eap); + else if (filtering) + apply_autocmds_exarg(EVENT_FILTERWRITEPOST, NULL, fname, + FALSE, curbuf, eap); + else if (reset_changed && whole) + apply_autocmds_exarg(EVENT_BUFWRITEPOST, fname, fname, + FALSE, curbuf, eap); + else + apply_autocmds_exarg(EVENT_FILEWRITEPOST, fname, fname, + FALSE, curbuf, eap); + + // restore curwin/curbuf and a few other things + aucmd_restbuf(&aco); + } + +#ifdef FEAT_EVAL + if (aborting()) // autocmds may abort script processing + retval = FALSE; +#endif + } + +#ifdef FEAT_VIMINFO + // Make sure marks will be written out to the viminfo file later, even when + // the file is new. + curbuf->b_marks_read = TRUE; +#endif + + got_int |= prev_got_int; + + return retval; +} diff --git a/userland/apps/vim/src/change.c b/userland/apps/vim/src/change.c new file mode 100644 index 00000000..c0b8785b --- /dev/null +++ b/userland/apps/vim/src/change.c @@ -0,0 +1,2404 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * change.c: functions related to changing text + */ + +#include "vim.h" + +/* + * If the file is readonly, give a warning message with the first change. + * Don't do this for autocommands. + * Doesn'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. + * "col" is the column for the message; non-zero when in insert mode and + * 'showmode' is on. + * Careful: may trigger autocommands that reload the buffer. + */ + void +change_warning(int col) +{ + static char *w_readonly = N_("W10: Warning: Changing a readonly file"); + + if (curbuf->b_did_warn + || curbufIsChanged() + || autocmd_busy + || !curbuf->b_p_ro) + return; + + ++curbuf_lock; + apply_autocmds(EVENT_FILECHANGEDRO, NULL, NULL, FALSE, curbuf); + --curbuf_lock; + if (!curbuf->b_p_ro) + return; + + // Do what msg() does, but with a column offset if the warning should + // be after the mode message. + msg_start(); + if (msg_row == Rows - 1) + msg_col = col; + msg_source(HL_ATTR(HLF_W)); + msg_puts_attr(_(w_readonly), HL_ATTR(HLF_W) | MSG_HIST); +#ifdef FEAT_EVAL + set_vim_var_string(VV_WARNINGMSG, (char_u *)_(w_readonly), -1); +#endif + msg_clr_eos(); + (void)msg_end(); + if (msg_silent == 0 && !silent_mode +#ifdef FEAT_EVAL + && time_for_testing != 1 +#endif + ) + { + out_flush(); + ui_delay(1002L, TRUE); // give the user time to think about it + } + curbuf->b_did_warn = TRUE; + redraw_cmdline = FALSE; // don't redraw and erase the message + if (msg_row < Rows - 1) + showmode(); +} + +/* + * Call this function when something in the current buffer is changed. + * + * Most often called through changed_bytes() and changed_lines(), which also + * mark the area of the display to be redrawn. + * + * Careful: may trigger autocommands that reload the buffer. + */ + void +changed(void) +{ +#if defined(FEAT_XIM) && defined(FEAT_GUI_GTK) + if (p_imst == IM_ON_THE_SPOT) + { + // The text of the preediting area is inserted, but this doesn't + // mean a change of the buffer yet. That is delayed until the + // text is committed. (this means preedit becomes empty) + if (im_is_preediting() && !xim_changed_while_preediting) + return; + xim_changed_while_preediting = FALSE; + } +#endif + + if (!curbuf->b_changed) + { + int save_msg_scroll = msg_scroll; + + // Give a warning about changing a read-only file. This may also + // check-out the file, thus change "curbuf"! + change_warning(0); + + // Create a swap file if that is wanted. + // Don't do this for "nofile" and "nowrite" buffer types. + if (curbuf->b_may_swap && !bt_dontwrite(curbuf)) + { + int save_need_wait_return = need_wait_return; + + need_wait_return = FALSE; + ml_open_file(curbuf); + + // The ml_open_file() can cause an ATTENTION message. + // Wait two seconds, to make sure the user reads this unexpected + // message. Since we could be anywhere, call wait_return() now, + // and don't let the emsg() set msg_scroll. + if (need_wait_return && emsg_silent == 0 && !in_assert_fails) + { + out_flush(); + ui_delay(2002L, TRUE); + wait_return(TRUE); + msg_scroll = save_msg_scroll; + } + else + need_wait_return = save_need_wait_return; + } + changed_internal(); + } + ++CHANGEDTICK(curbuf); + +#ifdef FEAT_SEARCH_EXTRA + // If a pattern is highlighted, the position may now be invalid. + highlight_match = FALSE; +#endif +} + +/* + * Internal part of changed(), no user interaction. + * Also used for recovery. + */ + void +changed_internal(void) +{ + curbuf->b_changed = TRUE; + ml_setflags(curbuf); + check_status(curbuf); + redraw_tabline = TRUE; + need_maketitle = TRUE; // set window title later +} + +#ifdef FEAT_EVAL +static long next_listener_id = 0; + +/* + * Check if the change at "lnum" is above or overlaps with an existing + * change. If above then flush changes and invoke listeners. + */ + static void +check_recorded_changes( + buf_T *buf, + linenr_T lnum, + linenr_T lnume, + long xtra) +{ + if (buf->b_recorded_changes == NULL || xtra == 0) + return; + + listitem_T *li; + linenr_T prev_lnum; + linenr_T prev_lnume; + + FOR_ALL_LIST_ITEMS(buf->b_recorded_changes, li) + { + prev_lnum = (linenr_T)dict_get_number( + li->li_tv.vval.v_dict, "lnum"); + prev_lnume = (linenr_T)dict_get_number( + li->li_tv.vval.v_dict, "end"); + if (prev_lnum >= lnum || prev_lnum > lnume || prev_lnume >= lnum) + { + // the current change is going to make the line number in + // the older change invalid, flush now + invoke_listeners(curbuf); + break; + } + } +} + +/* + * Record a change for listeners added with listener_add(). + * Always for the current buffer. + */ + static void +may_record_change( + linenr_T lnum, + colnr_T col, + linenr_T lnume, + long xtra) +{ + dict_T *dict; + + if (curbuf->b_listener == NULL) + return; + + // If the new change is going to change the line numbers in already listed + // changes, then flush. + check_recorded_changes(curbuf, lnum, lnume, xtra); + + if (curbuf->b_recorded_changes == NULL) + { + curbuf->b_recorded_changes = list_alloc(); + if (curbuf->b_recorded_changes == NULL) // out of memory + return; + ++curbuf->b_recorded_changes->lv_refcount; + curbuf->b_recorded_changes->lv_lock = VAR_FIXED; + } + + dict = dict_alloc(); + if (dict == NULL) + return; + dict_add_number(dict, "lnum", (varnumber_T)lnum); + dict_add_number(dict, "end", (varnumber_T)lnume); + dict_add_number(dict, "added", (varnumber_T)xtra); + dict_add_number(dict, "col", (varnumber_T)col + 1); + + list_append_dict(curbuf->b_recorded_changes, dict); +} + +/* + * listener_add() function + */ + void +f_listener_add(typval_T *argvars, typval_T *rettv) +{ + callback_T callback; + listener_T *lnr; + buf_T *buf = curbuf; + + if (in_vim9script() && check_for_opt_buffer_arg(argvars, 1) == FAIL) + return; + + callback = get_callback(&argvars[0]); + if (callback.cb_name == NULL) + return; + + if (argvars[1].v_type != VAR_UNKNOWN) + { + buf = get_buf_arg(&argvars[1]); + if (buf == NULL) + { + free_callback(&callback); + return; + } + } + + lnr = ALLOC_CLEAR_ONE(listener_T); + if (lnr == NULL) + { + free_callback(&callback); + return; + } + lnr->lr_next = buf->b_listener; + buf->b_listener = lnr; + + set_callback(&lnr->lr_callback, &callback); + if (callback.cb_free_name) + vim_free(callback.cb_name); + + lnr->lr_id = ++next_listener_id; + rettv->vval.v_number = lnr->lr_id; +} + +/* + * listener_flush() function + */ + void +f_listener_flush(typval_T *argvars, typval_T *rettv UNUSED) +{ + buf_T *buf = curbuf; + + if (in_vim9script() && check_for_opt_buffer_arg(argvars, 0) == FAIL) + return; + + if (argvars[0].v_type != VAR_UNKNOWN) + { + buf = get_buf_arg(&argvars[0]); + if (buf == NULL) + return; + } + invoke_listeners(buf); +} + + + static void +remove_listener(buf_T *buf, listener_T *lnr, listener_T *prev) +{ + if (prev != NULL) + prev->lr_next = lnr->lr_next; + else + buf->b_listener = lnr->lr_next; + free_callback(&lnr->lr_callback); + vim_free(lnr); +} + +/* + * listener_remove() function + */ + void +f_listener_remove(typval_T *argvars, typval_T *rettv) +{ + listener_T *lnr; + listener_T *next; + listener_T *prev; + int id; + buf_T *buf; + + if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL) + return; + + id = tv_get_number(argvars); + FOR_ALL_BUFFERS(buf) + { + prev = NULL; + for (lnr = buf->b_listener; lnr != NULL; lnr = next) + { + next = lnr->lr_next; + if (lnr->lr_id == id) + { + if (textlock > 0) + { + // in invoke_listeners(), clear ID and delete later + lnr->lr_id = 0; + return; + } + remove_listener(buf, lnr, prev); + rettv->vval.v_number = 1; + return; + } + prev = lnr; + } + } +} + +/* + * Called before inserting a line above "lnum"/"lnum3" or deleting line "lnum" + * to "lnume". + */ + void +may_invoke_listeners(buf_T *buf, linenr_T lnum, linenr_T lnume, int added) +{ + check_recorded_changes(buf, lnum, lnume, added); +} + +/* + * Called when a sequence of changes is done: invoke listeners added with + * listener_add(). + */ + void +invoke_listeners(buf_T *buf) +{ + listener_T *lnr; + typval_T rettv; + typval_T argv[6]; + listitem_T *li; + linenr_T start = MAXLNUM; + linenr_T end = 0; + linenr_T added = 0; + int save_updating_screen = updating_screen; + static int recursive = FALSE; + listener_T *next; + listener_T *prev; + + if (buf->b_recorded_changes == NULL // nothing changed + || buf->b_listener == NULL // no listeners + || recursive) // already busy + return; + recursive = TRUE; + + // Block messages on channels from being handled, so that they don't make + // text changes here. + ++updating_screen; + + argv[0].v_type = VAR_NUMBER; + argv[0].vval.v_number = buf->b_fnum; // a:bufnr + + FOR_ALL_LIST_ITEMS(buf->b_recorded_changes, li) + { + varnumber_T lnum; + + lnum = dict_get_number(li->li_tv.vval.v_dict, "lnum"); + if (start > lnum) + start = lnum; + lnum = dict_get_number(li->li_tv.vval.v_dict, "end"); + if (end < lnum) + end = lnum; + added += dict_get_number(li->li_tv.vval.v_dict, "added"); + } + argv[1].v_type = VAR_NUMBER; + argv[1].vval.v_number = start; + argv[2].v_type = VAR_NUMBER; + argv[2].vval.v_number = end; + argv[3].v_type = VAR_NUMBER; + argv[3].vval.v_number = added; + + argv[4].v_type = VAR_LIST; + argv[4].vval.v_list = buf->b_recorded_changes; + ++textlock; + + for (lnr = buf->b_listener; lnr != NULL; lnr = lnr->lr_next) + { + call_callback(&lnr->lr_callback, -1, &rettv, 5, argv); + clear_tv(&rettv); + } + + // If f_listener_remove() was called may have to remove a listener now. + prev = NULL; + for (lnr = buf->b_listener; lnr != NULL; lnr = next) + { + next = lnr->lr_next; + if (lnr->lr_id == 0) + remove_listener(buf, lnr, prev); + else + prev = lnr; + } + + --textlock; + list_unref(buf->b_recorded_changes); + buf->b_recorded_changes = NULL; + + if (save_updating_screen) + updating_screen = TRUE; + else + after_updating_screen(TRUE); + recursive = FALSE; +} + +/* + * Remove all listeners associated with "buf". + */ + void +remove_listeners(buf_T *buf) +{ + listener_T *lnr; + listener_T *next; + + for (lnr = buf->b_listener; lnr != NULL; lnr = next) + { + next = lnr->lr_next; + free_callback(&lnr->lr_callback); + vim_free(lnr); + } + buf->b_listener = NULL; +} +#endif + +/* + * Common code for when a change was made. + * See changed_lines() for the arguments. + * Careful: may trigger autocommands that reload the buffer. + */ + static void +changed_common( + linenr_T lnum, + colnr_T col, + linenr_T lnume, + long xtra) +{ + win_T *wp; + tabpage_T *tp; + int i; + int cols; + pos_T *p; + int add; + + // mark the buffer as modified + changed(); + +#ifdef FEAT_EVAL + may_record_change(lnum, col, lnume, xtra); +#endif +#ifdef FEAT_DIFF + if (curwin->w_p_diff && diff_internal()) + curtab->tp_diff_update = TRUE; +#endif + + // set the '. mark + if ((cmdmod.cmod_flags & CMOD_KEEPJUMPS) == 0) + { + curbuf->b_last_change.lnum = lnum; + curbuf->b_last_change.col = col; + + // Create a new entry if a new undo-able change was started or we + // don't have an entry yet. + if (curbuf->b_new_change || curbuf->b_changelistlen == 0) + { + if (curbuf->b_changelistlen == 0) + add = TRUE; + else + { + // Don't create a new entry when the line number is the same + // as the last one and the column is not too far away. Avoids + // creating many entries for typing "xxxxx". + p = &curbuf->b_changelist[curbuf->b_changelistlen - 1]; + if (p->lnum != lnum) + add = TRUE; + else + { + cols = comp_textwidth(FALSE); + if (cols == 0) + cols = 79; + add = (p->col + cols < col || col + cols < p->col); + } + } + if (add) + { + // This is the first of a new sequence of undo-able changes + // and it's at some distance of the last change. Use a new + // position in the changelist. + curbuf->b_new_change = FALSE; + + if (curbuf->b_changelistlen == JUMPLISTSIZE) + { + // changelist is full: remove oldest entry + curbuf->b_changelistlen = JUMPLISTSIZE - 1; + mch_memmove(curbuf->b_changelist, curbuf->b_changelist + 1, + sizeof(pos_T) * (JUMPLISTSIZE - 1)); + FOR_ALL_TAB_WINDOWS(tp, wp) + { + // Correct position in changelist for other windows on + // this buffer. + if (wp->w_buffer == curbuf && wp->w_changelistidx > 0) + --wp->w_changelistidx; + } + } + FOR_ALL_TAB_WINDOWS(tp, wp) + { + // For other windows, if the position in the changelist is + // at the end it stays at the end. + if (wp->w_buffer == curbuf + && wp->w_changelistidx == curbuf->b_changelistlen) + ++wp->w_changelistidx; + } + ++curbuf->b_changelistlen; + } + } + curbuf->b_changelist[curbuf->b_changelistlen - 1] = + curbuf->b_last_change; + // The current window is always after the last change, so that "g," + // takes you back to it. + curwin->w_changelistidx = curbuf->b_changelistlen; + } + + if (VIsual_active) + check_visual_pos(); + + FOR_ALL_TAB_WINDOWS(tp, wp) + { + if (wp->w_buffer == curbuf) + { + linenr_T last = lnume + xtra - 1; // last line after the change + + // Mark this window to be redrawn later. + if (!redraw_not_allowed && wp->w_redr_type < UPD_VALID) + wp->w_redr_type = UPD_VALID; + + // Reset "w_skipcol" if the topline length has become smaller to + // such a degree that nothing will be visible anymore, accounting + // for 'smoothscroll' <<< or 'listchars' "precedes" marker. + if (wp->w_skipcol > 0 + && (last < wp->w_topline + || (wp->w_topline >= lnum + && wp->w_topline < lnume + && win_linetabsize(wp, wp->w_topline, + ml_get(wp->w_topline), (colnr_T)MAXCOL) + <= wp->w_skipcol + sms_marker_overlap(wp, + win_col_off(wp) - win_col_off2(wp))))) + wp->w_skipcol = 0; + + // Check if a change in the buffer has invalidated the cached + // values for the cursor. +#ifdef FEAT_FOLDING + // Update the folds for this window. Can't postpone this, because + // a following operator might work on the whole fold: ">>dd". + foldUpdate(wp, lnum, last); + + // The change may cause lines above or below the change to become + // included in a fold. Set lnum/lnume to the first/last line that + // might be displayed differently. + // Set w_cline_folded here as an efficient way to update it when + // inserting lines just above a closed fold. + i = hasFoldingWin(wp, lnum, &lnum, NULL, FALSE, NULL); + if (wp->w_cursor.lnum == lnum) + wp->w_cline_folded = i; + i = hasFoldingWin(wp, last, NULL, &last, FALSE, NULL); + if (wp->w_cursor.lnum == last) + wp->w_cline_folded = i; + + // If the changed line is in a range of previously folded lines, + // compare with the first line in that range. + if (wp->w_cursor.lnum <= lnum) + { + i = find_wl_entry(wp, lnum); + if (i >= 0 && wp->w_cursor.lnum > wp->w_lines[i].wl_lnum) + changed_line_abv_curs_win(wp); + } +#endif + if (wp->w_cursor.lnum > lnum) + changed_line_abv_curs_win(wp); + else if (wp->w_cursor.lnum == lnum && wp->w_cursor.col >= col) + changed_cline_bef_curs_win(wp); + if (wp->w_botline >= lnum) + { + if (xtra < 0) + invalidate_botline_win(wp); + else + // Assume that botline doesn't change (inserted lines make + // other lines scroll down below botline). + approximate_botline_win(wp); + } + + // Check if any w_lines[] entries have become invalid. + // For entries below the change: Correct the lnums for + // inserted/deleted lines. Makes it possible to stop displaying + // after the change. + for (i = 0; i < wp->w_lines_valid; ++i) + if (wp->w_lines[i].wl_valid) + { + if (wp->w_lines[i].wl_lnum >= lnum) + { + // Do not change wl_lnum at index zero, it is used to + // compare with w_topline. Invalidate it instead. + if (wp->w_lines[i].wl_lnum < lnume || i == 0) + { + // line included in change + wp->w_lines[i].wl_valid = FALSE; + } + else if (xtra != 0) + { + // line below change + wp->w_lines[i].wl_lnum += xtra; +#ifdef FEAT_FOLDING + wp->w_lines[i].wl_lastlnum += xtra; +#endif + } + } +#ifdef FEAT_FOLDING + else if (wp->w_lines[i].wl_lastlnum >= lnum) + { + // change somewhere inside this range of folded lines, + // may need to be redrawn + wp->w_lines[i].wl_valid = FALSE; + } +#endif + } + +#ifdef FEAT_FOLDING + // Take care of side effects for setting w_topline when folds have + // changed. Esp. when the buffer was changed in another window. + if (hasAnyFolding(wp)) + set_topline(wp, wp->w_topline); +#endif + // If lines have been added or removed, relative numbering always + // requires a redraw. + if (wp->w_p_rnu && xtra != 0) + { + wp->w_last_cursor_lnum_rnu = 0; + redraw_win_later(wp, UPD_VALID); + } +#ifdef FEAT_SYN_HL + // Cursor line highlighting probably need to be updated with + // "UPD_VALID" if it's below the change. + // If the cursor line is inside the change we need to redraw more. + if (wp->w_p_cul) + { + if (xtra == 0) + redraw_win_later(wp, UPD_VALID); + else if (lnum <= wp->w_last_cursorline) + redraw_win_later(wp, UPD_SOME_VALID); + } +#endif + } +#ifdef FEAT_SEARCH_EXTRA + if (wp == curwin && xtra != 0 && search_hl_has_cursor_lnum >= lnum) + search_hl_has_cursor_lnum += xtra; +#endif + } + + // Call update_screen() later, which checks out what needs to be redrawn, + // since it notices b_mod_set and then uses b_mod_*. + set_must_redraw(UPD_VALID); + + // when the cursor line is changed always trigger CursorMoved + if (lnum <= curwin->w_cursor.lnum + && lnume + (xtra < 0 ? -xtra : xtra) > curwin->w_cursor.lnum) + last_cursormoved.lnum = 0; +} + + static void +changedOneline(buf_T *buf, linenr_T lnum) +{ + if (buf->b_mod_set) + { + // find the maximum area that must be redisplayed + if (lnum < buf->b_mod_top) + buf->b_mod_top = lnum; + else if (lnum >= buf->b_mod_bot) + buf->b_mod_bot = lnum + 1; + } + else + { + // set the area that must be redisplayed to one line + buf->b_mod_set = TRUE; + buf->b_mod_top = lnum; + buf->b_mod_bot = lnum + 1; + buf->b_mod_xlines = 0; + } +} + +/* + * Changed bytes within a single line for the current buffer. + * - marks the windows on this buffer to be redisplayed + * - marks the buffer changed by calling changed() + * - invalidates cached values + * Careful: may trigger autocommands that reload the buffer. + */ + void +changed_bytes(linenr_T lnum, colnr_T col) +{ + changedOneline(curbuf, lnum); + changed_common(lnum, col, lnum + 1, 0L); + +#ifdef FEAT_SPELL + // When text has been changed at the end of the line, possibly the start of + // the next line may have SpellCap that should be removed or it needs to be + // displayed. Schedule the next line for redrawing just in case. + // Don't do this when displaying '$' at the end of changed text. + if (spell_check_window(curwin) + && lnum < curbuf->b_ml.ml_line_count + && vim_strchr(p_cpo, CPO_DOLLAR) == NULL) + redrawWinline(curwin, lnum + 1); +#endif +#ifdef FEAT_DIFF + // Diff highlighting in other diff windows may need to be updated too. + if (curwin->w_p_diff) + { + win_T *wp; + linenr_T wlnum; + + FOR_ALL_WINDOWS(wp) + if (wp->w_p_diff && wp != curwin) + { + redraw_win_later(wp, UPD_VALID); + wlnum = diff_lnum_win(lnum, wp); + if (wlnum > 0) + changedOneline(wp->w_buffer, wlnum); + } + } +#endif +} + +/* + * Like changed_bytes() but also adjust text properties for "added" bytes. + * When "added" is negative text was deleted. + */ + void +inserted_bytes(linenr_T lnum, colnr_T col, int added UNUSED) +{ +#ifdef FEAT_PROP_POPUP + if (curbuf->b_has_textprop && added != 0) + adjust_prop_columns(lnum, col, added, 0); +#endif + + changed_bytes(lnum, col); +} + +/* + * Appended "count" lines below line "lnum" in the current buffer. + * Must be called AFTER the change and after mark_adjust(). + * Takes care of marking the buffer to be redrawn and sets the changed flag. + */ + void +appended_lines(linenr_T lnum, long count) +{ + changed_lines(lnum + 1, 0, lnum + 1, count); +} + +/* + * Like appended_lines(), but adjust marks first. + */ + void +appended_lines_mark(linenr_T lnum, long count) +{ + mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L); + changed_lines(lnum + 1, 0, lnum + 1, count); +} + +/* + * Deleted "count" lines at line "lnum" in the current buffer. + * Must be called AFTER the change and after mark_adjust(). + * Takes care of marking the buffer to be redrawn and sets the changed flag. + */ + void +deleted_lines(linenr_T lnum, long count) +{ + changed_lines(lnum, 0, lnum + count, -count); +} + +/* + * Like deleted_lines(), but adjust marks first. + * Make sure the cursor is on a valid line before calling, a GUI callback may + * be triggered to display the cursor. + */ + void +deleted_lines_mark(linenr_T lnum, long count) +{ + mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count); + changed_lines(lnum, 0, lnum + count, -count); +} + +/* + * Marks the area to be redrawn after a change. + * Consider also calling changed_line_display_buf(). + */ + void +changed_lines_buf( + buf_T *buf, + linenr_T lnum, // first line with change + linenr_T lnume, // line below last changed line + long xtra) // number of extra lines (negative when deleting) +{ + if (buf->b_mod_set) + { + // find the maximum area that must be redisplayed + if (lnum < buf->b_mod_top) + buf->b_mod_top = lnum; + if (lnum < buf->b_mod_bot) + { + // adjust old bot position for xtra lines + buf->b_mod_bot += xtra; + if (buf->b_mod_bot < lnum) + buf->b_mod_bot = lnum; + } + if (lnume + xtra > buf->b_mod_bot) + buf->b_mod_bot = lnume + xtra; + buf->b_mod_xlines += xtra; + } + else + { + // set the area that must be redisplayed + buf->b_mod_set = TRUE; + buf->b_mod_top = lnum; + buf->b_mod_bot = lnume + xtra; + buf->b_mod_xlines = xtra; + } +} + +/* + * Changed lines for the current buffer. + * Must be called AFTER the change and after mark_adjust(). + * - mark the buffer changed by calling changed() + * - mark the windows on this buffer to be redisplayed + * - invalidate cached values + * "lnum" is the first line that needs displaying, "lnume" the first line + * below the changed lines (BEFORE the change). + * When only inserting lines, "lnum" and "lnume" are equal. + * Takes care of calling changed() and updating b_mod_*. + * Careful: may trigger autocommands that reload the buffer. + */ + void +changed_lines( + linenr_T lnum, // first line with change + colnr_T col, // column in first line with change + linenr_T lnume, // line below last changed line + long xtra) // number of extra lines (negative when deleting) +{ + changed_lines_buf(curbuf, lnum, lnume, xtra); + +#ifdef FEAT_DIFF + if (xtra == 0 && curwin->w_p_diff && !diff_internal()) + { + // When the number of lines doesn't change then mark_adjust() isn't + // called and other diff buffers still need to be marked for + // displaying. + win_T *wp; + linenr_T wlnum; + + FOR_ALL_WINDOWS(wp) + if (wp->w_p_diff && wp != curwin) + { + redraw_win_later(wp, UPD_VALID); + wlnum = diff_lnum_win(lnum, wp); + if (wlnum > 0) + changed_lines_buf(wp->w_buffer, wlnum, + lnume - lnum + wlnum, 0L); + } + } +#endif + + changed_common(lnum, col, lnume, xtra); +} + +/* + * Called when the changed flag must be reset for buffer "buf". + * When "ff" is TRUE also reset 'fileformat'. + * When "always_inc_changedtick" is TRUE b:changedtick is incremented also when + * the changed flag was off. + */ + void +unchanged(buf_T *buf, int ff, int always_inc_changedtick) +{ + if (buf->b_changed || (ff && file_ff_differs(buf, FALSE))) + { + buf->b_changed = 0; + ml_setflags(buf); + if (ff) + save_file_ff(buf); + check_status(buf); + redraw_tabline = TRUE; + need_maketitle = TRUE; // set window title later + ++CHANGEDTICK(buf); + } + else if (always_inc_changedtick) + ++CHANGEDTICK(buf); +#ifdef FEAT_NETBEANS_INTG + netbeans_unmodified(buf); +#endif +} + +/* + * Save the current values of 'fileformat' and 'fileencoding', so that we know + * the file must be considered changed when the value is different. + */ + void +save_file_ff(buf_T *buf) +{ + buf->b_start_ffc = *buf->b_p_ff; + buf->b_start_eof = buf->b_p_eof; + buf->b_start_eol = buf->b_p_eol; + buf->b_start_bomb = buf->b_p_bomb; + + // Only use free/alloc when necessary, they take time. + if (buf->b_start_fenc == NULL + || STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0) + { + vim_free(buf->b_start_fenc); + buf->b_start_fenc = vim_strsave(buf->b_p_fenc); + } +} + +/* + * Return TRUE if 'fileformat' and/or 'fileencoding' has a different value + * from when editing started (save_file_ff() called). + * Also when 'endofline' was changed and 'binary' is set, or when 'bomb' was + * changed and 'binary' is not set. + * Also when 'endofline' was changed and 'fixeol' is not set. + * When "ignore_empty" is true don't consider a new, empty buffer to be + * changed. + */ + int +file_ff_differs(buf_T *buf, int ignore_empty) +{ + // In a buffer that was never loaded the options are not valid. + if (buf->b_flags & BF_NEVERLOADED) + return FALSE; + if (ignore_empty + && (buf->b_flags & BF_NEW) + && buf->b_ml.ml_line_count == 1 + && *ml_get_buf(buf, (linenr_T)1, FALSE) == NUL) + return FALSE; + if (buf->b_start_ffc != *buf->b_p_ff) + return TRUE; + if ((buf->b_p_bin || !buf->b_p_fixeol) + && (buf->b_start_eof != buf->b_p_eof + || buf->b_start_eol != buf->b_p_eol)) + return TRUE; + if (!buf->b_p_bin && buf->b_start_bomb != buf->b_p_bomb) + return TRUE; + if (buf->b_start_fenc == NULL) + return (*buf->b_p_fenc != NUL); + return (STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0); +} + +/* + * Insert string "p" at the cursor position. Stops at a NUL byte. + * Handles Replace mode and multi-byte characters. + */ + void +ins_bytes(char_u *p) +{ + ins_bytes_len(p, (int)STRLEN(p)); +} + +/* + * Insert string "p" with length "len" at the cursor position. + * Handles Replace mode and multi-byte characters. + */ + void +ins_bytes_len(char_u *p, int len) +{ + int i; + int n; + + if (has_mbyte) + for (i = 0; i < len; i += n) + { + if (enc_utf8) + // avoid reading past p[len] + n = utfc_ptr2len_len(p + i, len - i); + else + n = (*mb_ptr2len)(p + i); + ins_char_bytes(p + i, n); + } + else + for (i = 0; i < len; ++i) + ins_char(p[i]); +} + +/* + * Insert or replace a single character at the cursor position. + * When in MODE_REPLACE or MODE_VREPLACE state, replace any existing character. + * Caller must have prepared for undo. + * For multi-byte characters we get the whole character, the caller must + * convert bytes to a character. + */ + void +ins_char(int c) +{ + char_u buf[MB_MAXBYTES + 1]; + int n = (*mb_char2bytes)(c, buf); + + // When "c" is 0x100, 0x200, etc. we don't want to insert a NUL byte. + // Happens for CTRL-Vu9900. + if (buf[0] == 0) + buf[0] = '\n'; + + ins_char_bytes(buf, n); +} + + void +ins_char_bytes(char_u *buf, int charlen) +{ + int c = buf[0]; + int newlen; // nr of bytes inserted + int oldlen; // nr of bytes deleted (0 when not replacing) + char_u *p; + char_u *newp; + char_u *oldp; + int linelen; // length of old line including NUL + colnr_T col; + linenr_T lnum = curwin->w_cursor.lnum; + int i; + + // Break tabs if needed. + if (virtual_active() && curwin->w_cursor.coladd > 0) + coladvance_force(getviscol()); + + col = curwin->w_cursor.col; + oldp = ml_get(lnum); + linelen = (int)STRLEN(oldp) + 1; + + // The lengths default to the values for when not replacing. + oldlen = 0; + newlen = charlen; + + if (State & REPLACE_FLAG) + { + if (State & VREPLACE_FLAG) + { + colnr_T new_vcol = 0; // init for GCC + colnr_T vcol; + int old_list; + + // Disable 'list' temporarily, unless 'cpo' contains the 'L' flag. + // Returns the old value of list, so when finished, + // curwin->w_p_list should be set back to this. + old_list = curwin->w_p_list; + if (old_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) + curwin->w_p_list = FALSE; + + // In virtual replace mode each character may replace one or more + // characters (zero if it's a TAB). Count the number of bytes to + // be deleted to make room for the new character, counting screen + // cells. May result in adding spaces to fill a gap. + getvcol(curwin, &curwin->w_cursor, NULL, &vcol, NULL); + new_vcol = vcol + chartabsize(buf, vcol); + while (oldp[col + oldlen] != NUL && vcol < new_vcol) + { + vcol += chartabsize(oldp + col + oldlen, vcol); + // Don't need to remove a TAB that takes us to the right + // position. + if (vcol > new_vcol && oldp[col + oldlen] == TAB) + break; + oldlen += (*mb_ptr2len)(oldp + col + oldlen); + // Deleted a bit too much, insert spaces. + if (vcol > new_vcol) + newlen += vcol - new_vcol; + } + curwin->w_p_list = old_list; + } + else if (oldp[col] != NUL) + { + // normal replace + oldlen = (*mb_ptr2len)(oldp + col); + } + + + // Push the replaced bytes onto the replace stack, so that they can be + // put back when BS is used. The bytes of a multi-byte character are + // done the other way around, so that the first byte is popped off + // first (it tells the byte length of the character). + replace_push(NUL); + for (i = 0; i < oldlen; ++i) + { + if (has_mbyte) + i += replace_push_mb(oldp + col + i) - 1; + else + replace_push(oldp[col + i]); + } + } + + newp = alloc(linelen + newlen - oldlen); + if (newp == NULL) + return; + + // Copy bytes before the cursor. + if (col > 0) + mch_memmove(newp, oldp, (size_t)col); + + // Copy bytes after the changed character(s). + p = newp + col; + if (linelen > col + oldlen) + mch_memmove(p + newlen, oldp + col + oldlen, + (size_t)(linelen - col - oldlen)); + + // Insert or overwrite the new character. + mch_memmove(p, buf, charlen); + i = charlen; + + // Fill with spaces when necessary. + while (i < newlen) + p[i++] = ' '; + + // Replace the line in the buffer. + ml_replace(lnum, newp, FALSE); + + // mark the buffer as changed and prepare for displaying + changed_bytes(lnum, col); +#ifdef FEAT_PROP_POPUP + if (curbuf->b_has_textprop && newlen != oldlen) + adjust_prop_columns(lnum, col, newlen - oldlen, + State & REPLACE_FLAG ? APC_SUBSTITUTE : 0); +#endif + + // If we're in Insert or Replace mode and 'showmatch' is set, then briefly + // show the match for right parens and braces. + if (p_sm && (State & MODE_INSERT) + && msg_silent == 0 + && !ins_compl_active()) + { + if (has_mbyte) + showmatch(mb_ptr2char(buf)); + else + showmatch(c); + } + +#ifdef FEAT_RIGHTLEFT + if (!p_ri || (State & REPLACE_FLAG)) +#endif + { + // Normal insert: move cursor right + curwin->w_cursor.col += charlen; + } + + // TODO: should try to update w_row here, to avoid recomputing it later. +} + +/* + * Insert a string at the cursor position. + * Note: Does NOT handle Replace mode. + * Caller must have prepared for undo. + */ + void +ins_str(char_u *s) +{ + char_u *oldp, *newp; + int newlen = (int)STRLEN(s); + int oldlen; + colnr_T col; + linenr_T lnum = curwin->w_cursor.lnum; + + if (virtual_active() && curwin->w_cursor.coladd > 0) + coladvance_force(getviscol()); + + col = curwin->w_cursor.col; + oldp = ml_get(lnum); + oldlen = (int)STRLEN(oldp); + + newp = alloc(oldlen + newlen + 1); + if (newp == NULL) + return; + if (col > 0) + mch_memmove(newp, oldp, (size_t)col); + mch_memmove(newp + col, s, (size_t)newlen); + mch_memmove(newp + col + newlen, oldp + col, (size_t)(oldlen - col + 1)); + ml_replace(lnum, newp, FALSE); + inserted_bytes(lnum, col, newlen); + curwin->w_cursor.col += newlen; +} + +/* + * Delete one character under the cursor. + * If "fixpos" is TRUE, don't leave the cursor on the NUL after the line. + * Caller must have prepared for undo. + * + * return FAIL for failure, OK otherwise + */ + int +del_char(int fixpos) +{ + if (has_mbyte) + { + // Make sure the cursor is at the start of a character. + mb_adjust_cursor(); + if (*ml_get_cursor() == NUL) + return FAIL; + return del_chars(1L, fixpos); + } + return del_bytes(1L, fixpos, TRUE); +} + +/* + * Like del_bytes(), but delete characters instead of bytes. + */ + int +del_chars(long count, int fixpos) +{ + long bytes = 0; + long i; + char_u *p; + int l; + + p = ml_get_cursor(); + for (i = 0; i < count && *p != NUL; ++i) + { + l = (*mb_ptr2len)(p); + bytes += l; + p += l; + } + return del_bytes(bytes, fixpos, TRUE); +} + +/* + * Delete "count" bytes under the cursor. + * If "fixpos" is TRUE, don't leave the cursor on the NUL after the line. + * Caller must have prepared for undo. + * + * Return FAIL for failure, OK otherwise. + */ + int +del_bytes( + long count, + int fixpos_arg, + int use_delcombine UNUSED) // 'delcombine' option applies +{ + char_u *oldp, *newp; + colnr_T oldlen; + colnr_T newlen; + linenr_T lnum = curwin->w_cursor.lnum; + colnr_T col = curwin->w_cursor.col; + int alloc_newp; + long movelen; + int fixpos = fixpos_arg; + + oldp = ml_get(lnum); + oldlen = (int)STRLEN(oldp); + + // Can't do anything when the cursor is on the NUL after the line. + if (col >= oldlen) + return FAIL; + + // If "count" is zero there is nothing to do. + if (count == 0) + return OK; + + // If "count" is negative the caller must be doing something wrong. + if (count < 1) + { + siemsg(e_invalid_count_for_del_bytes_nr, count); + return FAIL; + } + + // If 'delcombine' is set and deleting (less than) one character, only + // delete the last combining character. + if (p_deco && use_delcombine && enc_utf8 + && utfc_ptr2len(oldp + col) >= count) + { + int cc[MAX_MCO]; + int n; + + (void)utfc_ptr2char(oldp + col, cc); + if (cc[0] != NUL) + { + // Find the last composing char, there can be several. + n = col; + do + { + col = n; + count = utf_ptr2len(oldp + n); + n += count; + } while (UTF_COMPOSINGLIKE(oldp + col, oldp + n)); + fixpos = 0; + } + } + + // When count is too big, reduce it. + movelen = (long)oldlen - (long)col - count + 1; // includes trailing NUL + if (movelen <= 1) + { + // If we just took off the last character of a non-blank line, and + // fixpos is TRUE, we don't want to end up positioned at the NUL, + // unless "restart_edit" is set or 'virtualedit' contains "onemore". + if (col > 0 && fixpos && restart_edit == 0 + && (get_ve_flags() & VE_ONEMORE) == 0) + { + --curwin->w_cursor.col; + curwin->w_cursor.coladd = 0; + if (has_mbyte) + curwin->w_cursor.col -= + (*mb_head_off)(oldp, oldp + curwin->w_cursor.col); + } + count = oldlen - col; + movelen = 1; + } + newlen = oldlen - count; + + // If the old line has been allocated the deletion can be done in the + // existing line. Otherwise a new line has to be allocated + // Can't do this when using Netbeans, because we would need to invoke + // netbeans_removed(), which deallocates the line. Let ml_replace() take + // care of notifying Netbeans. +#ifdef FEAT_NETBEANS_INTG + if (netbeans_active()) + alloc_newp = TRUE; + else +#endif + alloc_newp = !ml_line_alloced(); // check if oldp was allocated + if (!alloc_newp) + newp = oldp; // use same allocated memory + else + { // need to allocate a new line + newp = alloc(newlen + 1); + if (newp == NULL) + return FAIL; + mch_memmove(newp, oldp, (size_t)col); + } + mch_memmove(newp + col, oldp + col + count, (size_t)movelen); + if (alloc_newp) + ml_replace(lnum, newp, FALSE); +#ifdef FEAT_PROP_POPUP + else + { + // Also move any following text properties. + if (oldlen + 1 < curbuf->b_ml.ml_line_len) + mch_memmove(newp + newlen + 1, oldp + oldlen + 1, + (size_t)curbuf->b_ml.ml_line_len - oldlen - 1); + curbuf->b_ml.ml_line_len -= count; + } +#endif + + // mark the buffer as changed and prepare for displaying + inserted_bytes(lnum, col, -count); + + return OK; +} + +/* + * open_line: Add a new line below or above the current line. + * + * For MODE_VREPLACE state, we only add a new line when we get to the end of + * the file, otherwise we just start replacing the next line. + * + * Caller must take care of undo. Since MODE_VREPLACE may affect any number of + * lines however, it may call u_save_cursor() again when starting to change a + * new line. + * "flags": OPENLINE_DELSPACES delete spaces after cursor + * OPENLINE_DO_COM format comments + * OPENLINE_KEEPTRAIL keep trailing spaces + * OPENLINE_MARKFIX adjust mark positions after the line break + * OPENLINE_COM_LIST format comments with list or 2nd line indent + * + * "second_line_indent": indent for after ^^D in Insert mode or if flag + * OPENLINE_COM_LIST + * "did_do_comment" is set to TRUE when intentionally putting the comment + * leader in front of the new line. + * + * Return OK for success, FAIL for failure + */ + int +open_line( + int dir, // FORWARD or BACKWARD + int flags, + int second_line_indent, + int *did_do_comment UNUSED) +{ + char_u *saved_line; // copy of the original line + char_u *next_line = NULL; // copy of the next line + char_u *p_extra = NULL; // what goes to next line + int less_cols = 0; // less columns for mark in new line + int less_cols_off = 0; // columns to skip for mark and + // textprop adjustment + pos_T 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 = FAIL; // return value + int extra_len = 0; // length of p_extra string + int lead_len; // length of comment leader + int comment_start = 0; // start index of the 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 + pos_T *pos; + int do_cindent; + int do_si = may_do_si(); + int no_si = FALSE; // reset did_si afterwards + int first_char = NUL; // init for GCC + int vreplace_mode; + int did_append; // appended a new line + int saved_pi = curbuf->b_p_pi; // copy of preserveindent setting +#ifdef FEAT_PROP_POPUP + int at_eol; // cursor after last character +#endif + + // make a copy of the current line so we can mess with it + saved_line = vim_strsave(ml_get_curline()); + if (saved_line == NULL) // out of memory! + return FALSE; + +#ifdef FEAT_PROP_POPUP + at_eol = curwin->w_cursor.col >= (int)STRLEN(saved_line); +#endif + + if (State & VREPLACE_FLAG) + { + // With MODE_VREPLACE we make a copy of the next line, which we will be + // starting to replace. First make the new line empty and let vim play + // with the indenting and comment leader to its heart's content. Then + // we grab what it ended up putting on the new line, put back the + // original line, and call ins_char() to put each new character onto + // the line, replacing what was there before and pushing the right + // stuff onto the replace stack. -- webb. + if (curwin->w_cursor.lnum < orig_line_count) + next_line = vim_strsave(ml_get(curwin->w_cursor.lnum + 1)); + else + next_line = vim_strsave((char_u *)""); + if (next_line == NULL) // out of memory! + goto theend; + + // In MODE_VREPLACE state, a NL replaces the rest of the line, and + // starts replacing the next line, so push all of the characters left + // on the line onto the replace stack. We'll push any other characters + // that might be replaced at the start of the next line (due to + // autoindent etc) a bit later. + replace_push(NUL); // Call twice because BS over NL expects it + replace_push(NUL); + p = saved_line + curwin->w_cursor.col; + while (*p != NUL) + { + if (has_mbyte) + p += replace_push_mb(p); + else + replace_push(*p++); + } + saved_line[curwin->w_cursor.col] = NUL; + } + + if ((State & MODE_INSERT) && (State & VREPLACE_FLAG) == 0) + { + p_extra = saved_line + curwin->w_cursor.col; + if (do_si) // need first char after new line break + { + p = skipwhite(p_extra); + first_char = *p; + } + extra_len = (int)STRLEN(p_extra); + saved_char = *p_extra; + *p_extra = NUL; + } + + u_clearline(); // cannot do "U" command when adding lines + did_si = FALSE; + ai_col = 0; + + // If we just did an auto-indent, then we didn't type anything on + // the prior line, and it should be truncated. Do this even if 'ai' is not + // set because automatically inserting a comment leader also sets did_ai. + if (dir == FORWARD && did_ai) + trunc_line = TRUE; + + // If 'autoindent' and/or 'smartindent' is set, try to figure out what + // indent to use for the new line. + if (curbuf->b_p_ai || do_si) + { + // count white space on current line +#ifdef FEAT_VARTABS + newindent = get_indent_str_vtab(saved_line, curbuf->b_p_ts, + curbuf->b_p_vts_array, FALSE); +#else + newindent = get_indent_str(saved_line, (int)curbuf->b_p_ts, FALSE); +#endif + if (newindent == 0 && !(flags & OPENLINE_COM_LIST)) + newindent = second_line_indent; // for ^^D command in insert mode + + // 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) {" + if (!trunc_line && do_si && *saved_line != NUL + && (p_extra == NULL || first_char != '{')) + { + char_u *ptr; + char_u last_char; + + old_cursor = curwin->w_cursor; + ptr = saved_line; + if (flags & OPENLINE_DO_COM) + lead_len = get_leader_len(ptr, NULL, FALSE, TRUE); + else + lead_len = 0; + 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(); + } + if (flags & OPENLINE_DO_COM) + lead_len = get_leader_len(ptr, NULL, FALSE, TRUE); + else + lead_len = 0; + 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 = (colnr_T)(p - ptr); + if ((pos = findmatch(NULL, NUL)) != NULL) + { + curwin->w_cursor.lnum = pos->lnum; + newindent = get_indent(); + break; + } + // this may make "ptr" invalid, get it again + ptr = ml_get(curwin->w_cursor.lnum); + p = ptr + curwin->w_cursor.col; + } + } + } + } + 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 = (colnr_T)(p - ptr); + if ((pos = findmatch(NULL, '(')) != 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 != '}' + && cin_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 (do_si) + can_si = TRUE; + + did_ai = TRUE; + } + + // May do indenting after opening a new line. + do_cindent = !p_paste && (curbuf->b_p_cin +#ifdef FEAT_EVAL + || *curbuf->b_p_inde != NUL +#endif + ) + && in_cinkeys(dir == FORWARD + ? KEY_OPEN_FORW + : KEY_OPEN_BACK, ' ', linewhite(curwin->w_cursor.lnum)); + + // Find out if the current line starts with a comment leader. + // This may then be inserted in front of the new line. + end_comment_pending = NUL; + if (flags & OPENLINE_DO_COM) + { + lead_len = get_leader_len(saved_line, &lead_flags, + dir == BACKWARD, TRUE); + if (lead_len == 0 && curbuf->b_p_cin && do_cindent && dir == FORWARD + && (!has_format_option(FO_NO_OPEN_COMS) + || (flags & OPENLINE_FORMAT))) + { + // Check for a line comment after code. + comment_start = check_linecomment(saved_line); + if (comment_start != MAXCOL) + { + lead_len = get_leader_len(saved_line + comment_start, + &lead_flags, FALSE, TRUE); + if (lead_len != 0) + { + lead_len += comment_start; + if (did_do_comment != NULL) + *did_do_comment = TRUE; + } + } + } + } + else + lead_len = 0; + 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; + int require_blank = FALSE; // requires blank after middle + char_u *p2; + + // 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_BLANK) + { + require_blank = TRUE; + continue; + } + 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, ","); + require_blank = FALSE; + } + + // Isolate the strings of the middle and end leader. + while (*p && p[-1] != ':') // find end of middle flags + { + if (*p == COM_BLANK) + require_blank = TRUE; + ++p; + } + (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ","); + + while (*p && p[-1] != ':') // find end of end flags + { + // Check whether we allow automatic ending of comments + if (*p == COM_AUTO_END) + end_comment_pending = -1; // means we want to set it + ++p; + } + n = copy_option_part(&p, lead_end, COM_MAX_LEN, ","); + + if (end_comment_pending == -1) // we can set it now + end_comment_pending = lead_end[n - 1]; + + // If the end of the comment is in the same line, don't use + // the comment leader. + if (dir == FORWARD) + { + 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 > 0) + { + if (current_flag == COM_START) + { + lead_repl = lead_middle; + lead_repl_len = (int)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) + || require_blank)) + 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 = (int)(p - lead_repl); + + // We can probably always add an extra space when doing "O" on + // the comment-end + extra_space = TRUE; + + // Check whether we allow automatic ending of comments + for (p2 = p; *p2 && *p2 != ':'; p2++) + { + if (*p2 == COM_AUTO_END) + end_comment_pending = -1; // means we want to set it + } + if (end_comment_pending == -1) + { + // Find last character in end-comment string + while (*p2 && *p2 != ',') + p2++; + end_comment_pending = p2[-1]; + } + 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_extra later) + leader = alloc(lead_len + lead_repl_len + extra_space + extra_len + + (second_line_indent > 0 ? second_line_indent : 0) + 1); + allocated = leader; // remember to free it later + + if (leader == NULL) + lead_len = 0; + else + { + int li; + + vim_strncpy(leader, saved_line, lead_len); + + // TODO: handle multi-byte and double width chars + for (li = 0; li < comment_start; ++li) + if (!VIM_ISWHITE(leader[li])) + leader[li] = ' '; + + // Replace leader with lead_repl, right or left adjusted + if (lead_repl != NULL) + { + int c = 0; + int off = 0; + + for (p = lead_flags; *p != NUL && *p != ':'; ) + { + if (*p == COM_RIGHT || *p == COM_LEFT) + c = *p++; + else if (VIM_ISDIGIT(*p) || *p == '-') + off = getdigits(&p); + else + ++p; + } + if (c == 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; + + // Compute the length of the replaced characters in + // screen characters, not bytes. + { + int repl_size = vim_strnsize(lead_repl, + lead_repl_len); + int old_size = 0; + char_u *endp = p; + int l; + + while (old_size < repl_size && p > leader) + { + MB_PTR_BACK(leader, p); + old_size += ptr2cells(p); + } + l = lead_repl_len - (int)(endp - p); + if (l != 0) + mch_memmove(endp + l, endp, + (size_t)((leader + lead_len) - endp)); + lead_len += l; + } + mch_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) + { + int l = mb_head_off(leader, p); + + if (l > 1) + { + p -= l; + if (ptr2cells(p) > 1) + { + p[1] = ' '; + --l; + } + mch_memmove(p + 1, p + l + 1, + (size_t)((leader + lead_len) - (p + l + 1))); + lead_len -= l; + *p = ' '; + } + else if (!VIM_ISWHITE(*p)) + *p = ' '; + } + } + else // left adjusted leader + { + p = skipwhite(leader); + + // Compute the length of the replaced characters in + // screen characters, not bytes. Move the part that is + // not to be overwritten. + { + int repl_size = vim_strnsize(lead_repl, + lead_repl_len); + int i; + int l; + + for (i = 0; i < lead_len && p[i] != NUL; i += l) + { + l = (*mb_ptr2len)(p + i); + if (vim_strnsize(p, i + l) > repl_size) + break; + } + if (i != lead_repl_len) + { + mch_memmove(p + lead_repl_len, p + i, + (size_t)(lead_len - i - (p - leader))); + lead_len += lead_repl_len - i; + } + } + mch_memmove(p, lead_repl, (size_t)lead_repl_len); + + // Replace any remaining non-white chars in the old + // leader by spaces. Keep Tabs, the indent must + // remain the same. + for (p += lead_repl_len; p < leader + lead_len; ++p) + if (!VIM_ISWHITE(*p)) + { + // Don't put a space before a TAB. + if (p + 1 < leader + lead_len && p[1] == TAB) + { + --lead_len; + mch_memmove(p, p + 1, + (leader + lead_len) - p); + } + else + { + int l = (*mb_ptr2len)(p); + + if (l > 1) + { + if (ptr2cells(p) > 1) + { + // Replace a double-wide char with + // two spaces + --l; + *p++ = ' '; + } + mch_memmove(p + 1, p + l, + (leader + lead_len) - p); + lead_len -= l - 1; + } + *p = ' '; + } + } + *p = NUL; + } + + // Recompute the indent, it may have changed. + if (curbuf->b_p_ai || do_si) +#ifdef FEAT_VARTABS + newindent = get_indent_str_vtab(leader, curbuf->b_p_ts, + curbuf->b_p_vts_array, FALSE); +#else + newindent = get_indent_str(leader, + (int)curbuf->b_p_ts, FALSE); +#endif + + // Add the indent offset + if (newindent + off < 0) + { + off = -newindent; + newindent = 0; + } + else + newindent += off; + + // Correct trailing spaces for the shift, so that + // alignment remains equal. + while (off > 0 && lead_len > 0 + && leader[lead_len - 1] == ' ') + { + // Don't do it when there is a tab before the space + if (vim_strchr(skipwhite(leader), '\t') != NULL) + break; + --lead_len; + --off; + } + + // If the leader ends in white space, don't add an + // extra space + if (lead_len > 0 && VIM_ISWHITE(leader[lead_len - 1])) + extra_space = FALSE; + leader[lead_len] = NUL; + } + + 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 || did_si) + { + while (lead_len && VIM_ISWHITE(*leader)) + { + --lead_len; + --newcol; + ++leader; + } + } + + } + did_si = can_si = FALSE; + } + 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 || do_si)) + { + old_cursor = curwin->w_cursor; + curwin->w_cursor.col = (colnr_T)(comment_end - saved_line); + if ((pos = findmatch(NULL, NUL)) != NULL) + { + curwin->w_cursor.lnum = pos->lnum; + newindent = get_indent(); + } + curwin->w_cursor = old_cursor; + } + } + } + + // (State == MODE_INSERT || State == MODE_REPLACE), only when dir == FORWARD + if (p_extra != NULL) + { + *p_extra = saved_char; // restore char that NUL replaced + + // When 'ai' set or "flags" has OPENLINE_DELSPACES, skip to the first + // non-blank. + // + // When in MODE_REPLACE state, put the deleted blanks on the replace + // stack, preceded by a NUL, so they can be put back when a BS is + // entered. + if (REPLACE_NORMAL(State)) + replace_push(NUL); // end of extra blanks + if (curbuf->b_p_ai || (flags & OPENLINE_DELSPACES)) + { + while ((*p_extra == ' ' || *p_extra == '\t') + && (!enc_utf8 + || !utf_iscomposing(utf_ptr2char(p_extra + 1)))) + { + if (REPLACE_NORMAL(State)) + replace_push(*p_extra); + ++p_extra; + ++less_cols_off; + } + } + + // columns for marks adjusted for removed columns + less_cols = (int)(p_extra - saved_line); + } + + if (p_extra == NULL) + p_extra = (char_u *)""; // append empty line + + // concatenate leader and p_extra, if there is a leader + if (lead_len) + { + if (flags & OPENLINE_COM_LIST && second_line_indent > 0) + { + int i; + int padding = second_line_indent + - (newindent + (int)STRLEN(leader)); + + // Here whitespace is inserted after the comment char. + // Below, set_indent(newindent, SIN_INSERT) will insert the + // whitespace needed before the comment char. + for (i = 0; i < padding; i++) + { + STRCAT(leader, " "); + less_cols--; + newcol++; + } + } + STRCAT(leader, p_extra); + p_extra = leader; + did_ai = TRUE; // So truncating blanks works with comments + less_cols -= lead_len; + } + else + end_comment_pending = NUL; // turns out there was no leader + + old_cursor = curwin->w_cursor; + if (dir == BACKWARD) + --curwin->w_cursor.lnum; + if (!(State & VREPLACE_FLAG) || old_cursor.lnum >= orig_line_count) + { + if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, FALSE) + == FAIL) + goto theend; + // Postpone calling changed_lines(), because it would mess up folding + // with markers. + mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L); + did_append = TRUE; +#ifdef FEAT_PROP_POPUP + if ((State & MODE_INSERT) && (State & VREPLACE_FLAG) == 0) + // Properties after the split move to the next line. + adjust_props_for_split(curwin->w_cursor.lnum, curwin->w_cursor.lnum, + curwin->w_cursor.col + 1, 0, at_eol); +#endif + } + else + { + // In MODE_VREPLACE state we are starting to replace the next line. + curwin->w_cursor.lnum++; + if (curwin->w_cursor.lnum >= Insstart.lnum + vr_lines_changed) + { + // In case we NL to a new line, BS to the previous one, and NL + // again, we don't want to save the new line for undo twice. + (void)u_save_cursor(); // errors are ignored! + vr_lines_changed++; + } + ml_replace(curwin->w_cursor.lnum, p_extra, TRUE); + changed_bytes(curwin->w_cursor.lnum, 0); + curwin->w_cursor.lnum--; + did_append = FALSE; + } + + if (newindent || did_si) + { + ++curwin->w_cursor.lnum; + if (did_si) + { + int sw = (int)get_sw_value(curbuf); + + if (p_sr) + newindent -= newindent % sw; + newindent += sw; + } + // Copy the indent + if (curbuf->b_p_ci) + { + (void)copy_indent(newindent, saved_line); + + // Set the 'preserveindent' option so that any further screwing + // with the line doesn't entirely destroy our efforts to preserve + // it. It gets restored at the function end. + curbuf->b_p_pi = TRUE; + } + else + (void)set_indent(newindent, SIN_INSERT); + less_cols -= curwin->w_cursor.col; + + ai_col = curwin->w_cursor.col; + + // In MODE_REPLACE state, for each character in the new indent, there + // must be a NUL on the replace stack, for when it is deleted with BS + if (REPLACE_NORMAL(State)) + for (n = 0; n < (int)curwin->w_cursor.col; ++n) + replace_push(NUL); + newcol += curwin->w_cursor.col; + if (no_si) + did_si = FALSE; + } + + // In MODE_REPLACE state, for each character in the extra leader, there + // must be a NUL on the replace stack, for when it is deleted with BS. + if (REPLACE_NORMAL(State)) + while (lead_len-- > 0) + replace_push(NUL); + + curwin->w_cursor = old_cursor; + + if (dir == FORWARD) + { + if (trunc_line || (State & MODE_INSERT)) + { + // truncate current line at cursor + saved_line[curwin->w_cursor.col] = NUL; + // Remove trailing white space, unless OPENLINE_KEEPTRAIL used. + if (trunc_line && !(flags & OPENLINE_KEEPTRAIL)) + truncate_spaces(saved_line); + ml_replace(curwin->w_cursor.lnum, saved_line, FALSE); + saved_line = NULL; + if (did_append) + { + changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col, + curwin->w_cursor.lnum + 1, 1L); + did_append = FALSE; + + // Move marks after the line break to the new line. + if (flags & OPENLINE_MARKFIX) + mark_col_adjust(curwin->w_cursor.lnum, + curwin->w_cursor.col + less_cols_off, + 1L, (long)-less_cols, 0); +#ifdef FEAT_PROP_POPUP + // Keep into account the deleted blanks on the new line. + if (curbuf->b_has_textprop && less_cols_off != 0) + adjust_prop_columns(curwin->w_cursor.lnum + 1, 0, + -less_cols_off, 0); +#endif + } + else + changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); + } + + // Put the cursor on the new line. Careful: the scrollup() above may + // have moved w_cursor, we must use old_cursor. + curwin->w_cursor.lnum = old_cursor.lnum + 1; + } + if (did_append) + changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L); + + curwin->w_cursor.col = newcol; + curwin->w_cursor.coladd = 0; + + // In MODE_VREPLACE state, we are handling the replace stack ourselves, so + // stop fixthisline() from doing it (via change_indent()) by telling it + // we're in normal MODE_INSERT state. + if (State & VREPLACE_FLAG) + { + vreplace_mode = State; // So we know to put things right later + State = MODE_INSERT; + } + else + vreplace_mode = 0; + + if (!p_paste) + { + if (leader == NULL + && !use_indentexpr_for_lisp() + && curbuf->b_p_lisp + && curbuf->b_p_ai) + { + // do lisp indenting + fixthisline(get_lisp_indent); + ai_col = (colnr_T)getwhitecols_curline(); + } + else if (do_cindent || (curbuf->b_p_ai && use_indentexpr_for_lisp())) + { + // do 'cindent' or 'indentexpr' indenting + do_c_expr_indent(); + ai_col = (colnr_T)getwhitecols_curline(); + } + } + + if (vreplace_mode != 0) + State = vreplace_mode; + + // Finally, MODE_VREPLACE gets the stuff on the new line, then puts back + // the original line, and inserts the new stuff char by char, pushing old + // stuff onto the replace stack (via ins_char()). + if (State & VREPLACE_FLAG) + { + // Put new line in p_extra + p_extra = vim_strsave(ml_get_curline()); + if (p_extra == NULL) + goto theend; + + // Put back original line + ml_replace(curwin->w_cursor.lnum, next_line, FALSE); + + // Insert new stuff into line again + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + ins_bytes(p_extra); // will call changed_bytes() + vim_free(p_extra); + next_line = NULL; + } + + retval = OK; // success! +theend: + curbuf->b_p_pi = saved_pi; + vim_free(saved_line); + vim_free(next_line); + vim_free(allocated); + return retval; +} + +/* + * Delete from cursor to end of line. + * Caller must have prepared for undo. + * If "fixpos" is TRUE fix the cursor position when done. + * + * Return FAIL for failure, OK otherwise. + */ + int +truncate_line(int fixpos) +{ + char_u *newp; + linenr_T lnum = curwin->w_cursor.lnum; + colnr_T col = curwin->w_cursor.col; + char_u *old_line; + int deleted; + + old_line = ml_get(lnum); + if (col == 0) + newp = vim_strsave((char_u *)""); + else + newp = vim_strnsave(old_line, col); + deleted = (int)STRLEN(old_line) - col; + + if (newp == NULL) + return FAIL; + + ml_replace(lnum, newp, FALSE); + + // mark the buffer as changed and prepare for displaying + inserted_bytes(lnum, curwin->w_cursor.col, -deleted); + + // 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; + + return OK; +} + +/* + * Delete "nlines" lines at the cursor. + * Saves the lines for undo first if "undo" is TRUE. + */ + void +del_lines(long nlines, int undo) +{ + long n; + linenr_T first = curwin->w_cursor.lnum; + + if (nlines <= 0) + return; + + // save the deleted lines for undo + if (undo && u_savedel(first, nlines) == FAIL) + return; + + for (n = 0; n < nlines; ) + { + if (curbuf->b_ml.ml_flags & ML_EMPTY) // nothing to delete + break; + + ml_delete_flags(first, ML_DEL_MESSAGE); + ++n; + + // If we delete the last line in the file, stop + if (first > curbuf->b_ml.ml_line_count) + break; + } + + // Correct the cursor position before calling deleted_lines_mark(), it may + // trigger a callback to display the cursor. + curwin->w_cursor.col = 0; + check_cursor_lnum(); + + // adjust marks, mark the buffer as changed and prepare for displaying + deleted_lines_mark(first, n); +} diff --git a/userland/apps/vim/src/charset.c b/userland/apps/vim/src/charset.c new file mode 100644 index 00000000..bda3f911 --- /dev/null +++ b/userland/apps/vim/src/charset.c @@ -0,0 +1,2464 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +#include "vim.h" + +#if defined(HAVE_WCHAR_H) +# include // for towupper() and towlower() +#endif + +static int win_nolbr_chartabsize(chartabsize_T *cts, int *headp); +static unsigned nr2hex(unsigned c); + +static int chartab_initialized = FALSE; + +// b_chartab[] is an array of 32 bytes, each bit representing one of the +// characters 0-255. +#define SET_CHARTAB(buf, c) (buf)->b_chartab[(unsigned)(c) >> 3] |= (1 << ((c) & 0x7)) +#define RESET_CHARTAB(buf, c) (buf)->b_chartab[(unsigned)(c) >> 3] &= ~(1 << ((c) & 0x7)) +#define GET_CHARTAB(buf, c) ((buf)->b_chartab[(unsigned)(c) >> 3] & (1 << ((c) & 0x7))) + +// table used below, see init_chartab() for an explanation +static char_u g_chartab[256]; + +/* + * Flags for g_chartab[]. + */ +#define CT_CELL_MASK 0x07 // mask: nr of display cells (1, 2 or 4) +#define CT_PRINT_CHAR 0x10 // flag: set for printable chars +#define CT_ID_CHAR 0x20 // flag: set for ID chars +#define CT_FNAME_CHAR 0x40 // flag: set for file name chars + +static int in_win_border(win_T *wp, colnr_T vcol); + +/* + * Fill g_chartab[]. Also fills curbuf->b_chartab[] with flags for keyword + * characters for current buffer. + * + * Depends on the option settings 'iskeyword', 'isident', 'isfname', + * 'isprint' and 'encoding'. + * + * The index in g_chartab[] depends on 'encoding': + * - For non-multi-byte index with the byte (same as the character). + * - For DBCS index with the first byte. + * - For UTF-8 index with the character (when first byte is up to 0x80 it is + * the same as the character, if the first byte is 0x80 and above it depends + * on further bytes). + * + * The contents of g_chartab[]: + * - The lower two bits, masked by CT_CELL_MASK, give the number of display + * cells the character occupies (1 or 2). Not valid for UTF-8 above 0x80. + * - CT_PRINT_CHAR bit is set when the character is printable (no need to + * translate the character before displaying it). Note that only DBCS + * characters can have 2 display cells and still be printable. + * - CT_FNAME_CHAR bit is set when the character can be in a file name. + * - CT_ID_CHAR bit is set when the character can be in an identifier. + * + * Return FAIL if 'iskeyword', 'isident', 'isfname' or 'isprint' option has an + * error, OK otherwise. + */ + int +init_chartab(void) +{ + return buf_init_chartab(curbuf, TRUE); +} + + int +buf_init_chartab( + buf_T *buf, + int global) // FALSE: only set buf->b_chartab[] +{ + int c; + int c2; + char_u *p; + int i; + int tilde; + int do_isalpha; + + if (global) + { + /* + * 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 < ' ') + g_chartab[c++] = (dy_flags & DY_UHEX) ? 4 : 2; + while (c <= '~') + g_chartab[c++] = 1 + CT_PRINT_CHAR; + while (c < 256) + { + // UTF-8: bytes 0xa0 - 0xff are printable (latin1) + if (enc_utf8 && c >= 0xa0) + g_chartab[c++] = CT_PRINT_CHAR + 1; + // euc-jp characters starting with 0x8e are single width + else if (enc_dbcs == DBCS_JPNU && c == 0x8e) + g_chartab[c++] = CT_PRINT_CHAR + 1; + // other double-byte chars can be printable AND double-width + else if (enc_dbcs != 0 && MB_BYTE2LEN(c) == 2) + g_chartab[c++] = CT_PRINT_CHAR + 2; + else + // the rest is unprintable by default + g_chartab[c++] = (dy_flags & DY_UHEX) ? 4 : 2; + } + + // Assume that every multi-byte char is a filename character. + for (c = 1; c < 256; ++c) + if ((enc_dbcs != 0 && MB_BYTE2LEN(c) > 1) + || (enc_dbcs == DBCS_JPNU && c == 0x8e) + || (enc_utf8 && c >= 0xa0)) + g_chartab[c] |= CT_FNAME_CHAR; + } + + /* + * Init word char flags all to FALSE + */ + CLEAR_FIELD(buf->b_chartab); + if (enc_dbcs != 0) + for (c = 0; c < 256; ++c) + { + // double-byte characters are probably word characters + if (MB_BYTE2LEN(c) == 2) + SET_CHARTAB(buf, c); + } + + /* + * In lisp mode the '-' character is included in keywords. + */ + if (buf->b_p_lisp) + SET_CHARTAB(buf, '-'); + + // 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 = global ? 0 : 3; i <= 3; ++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 = buf->b_p_isk; // fourth round: 'iskeyword' + + while (*p) + { + tilde = FALSE; + do_isalpha = FALSE; + if (*p == '^' && p[1] != NUL) + { + tilde = TRUE; + ++p; + } + if (VIM_ISDIGIT(*p)) + c = getdigits(&p); + else if (has_mbyte) + c = mb_ptr2char_adv(&p); + else + c = *p++; + c2 = -1; + if (*p == '-' && p[1] != NUL) + { + ++p; + if (VIM_ISDIGIT(*p)) + c2 = getdigits(&p); + else if (has_mbyte) + c2 = mb_ptr2char_adv(&p); + else + c2 = *p++; + } + if (c <= 0 || c >= 256 || (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 for + * single-byte characters). + */ + if (c == '@') + { + do_isalpha = TRUE; + c = 1; + c2 = 255; + } + else + c2 = c; + } + while (c <= c2) + { + // Use the MB_ functions here, because isalpha() doesn't + // work properly when 'encoding' is "latin1" and the locale is + // "C". + if (!do_isalpha || MB_ISLOWER(c) || MB_ISUPPER(c)) + { + if (i == 0) // (re)set ID flag + { + if (tilde) + g_chartab[c] &= ~CT_ID_CHAR; + else + g_chartab[c] |= CT_ID_CHAR; + } + else if (i == 1) // (re)set printable + { + if ((c < ' ' || c > '~' + // For double-byte we keep the cell width, so + // that we can detect it from the first byte. + ) && !(enc_dbcs && MB_BYTE2LEN(c) == 2)) + { + if (tilde) + { + g_chartab[c] = (g_chartab[c] & ~CT_CELL_MASK) + + ((dy_flags & DY_UHEX) ? 4 : 2); + g_chartab[c] &= ~CT_PRINT_CHAR; + } + else + { + g_chartab[c] = (g_chartab[c] & ~CT_CELL_MASK) + + 1; + g_chartab[c] |= CT_PRINT_CHAR; + } + } + } + else if (i == 2) // (re)set fname flag + { + if (tilde) + g_chartab[c] &= ~CT_FNAME_CHAR; + else + g_chartab[c] |= CT_FNAME_CHAR; + } + else // i == 3 (re)set keyword flag + { + if (tilde) + RESET_CHARTAB(buf, c); + else + SET_CHARTAB(buf, c); + } + } + ++c; + } + + c = *p; + p = skip_to_option_part(p); + if (c == ',' && *p == NUL) + // Trailing comma is not allowed. + return FAIL; + } + } + chartab_initialized = TRUE; + return OK; +} + +/* + * Translate any special characters in buf[bufsize] in-place. + * The result is a string with only printable characters, but if there is not + * enough room, not all characters will be translated. + */ + void +trans_characters( + char_u *buf, + int bufsize) +{ + int len; // length of string needing translation + int room; // room in buffer after string + char_u *trs; // translated character + int trs_len; // length of trs[] + + len = (int)STRLEN(buf); + room = bufsize - len; + while (*buf != 0) + { + // Assume a multi-byte character doesn't need translation. + if (has_mbyte && (trs_len = (*mb_ptr2len)(buf)) > 1) + len -= trs_len; + else + { + trs = transchar_byte(*buf); + trs_len = (int)STRLEN(trs); + if (trs_len > 1) + { + room -= trs_len - 1; + if (room <= 0) + return; + mch_memmove(buf + trs_len, buf + 1, (size_t)len); + } + mch_memmove(buf, trs, (size_t)trs_len); + --len; + } + buf += trs_len; + } +} + +/* + * Translate a string into allocated memory, replacing special chars with + * printable chars. Returns NULL when out of memory. + */ + char_u * +transstr(char_u *s) +{ + char_u *res; + char_u *p; + int l, len, c; + char_u hexbuf[11]; + + if (has_mbyte) + { + // Compute the length of the result, taking account of unprintable + // multi-byte characters. + len = 0; + p = s; + while (*p != NUL) + { + if ((l = (*mb_ptr2len)(p)) > 1) + { + c = (*mb_ptr2char)(p); + p += l; + if (vim_isprintc(c)) + len += l; + else + { + transchar_hex(hexbuf, c); + len += (int)STRLEN(hexbuf); + } + } + else + { + l = byte2cells(*p++); + if (l > 0) + len += l; + else + len += 4; // illegal byte sequence + } + } + res = alloc(len + 1); + } + else + res = alloc(vim_strsize(s) + 1); + + if (res == NULL) + return NULL; + + *res = NUL; + p = s; + while (*p != NUL) + { + if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1) + { + c = (*mb_ptr2char)(p); + if (vim_isprintc(c)) + STRNCAT(res, p, l); // append printable multi-byte char + else + transchar_hex(res + STRLEN(res), c); + p += l; + } + else + STRCAT(res, transchar_byte(*p++)); + } + return res; +} + +/* + * Convert the string "str[orglen]" to do ignore-case comparing. Uses the + * current locale. + * When "buf" is NULL returns an allocated string (NULL for out-of-memory). + * Otherwise puts the result in "buf[buflen]". + */ + char_u * +str_foldcase( + char_u *str, + int orglen, + char_u *buf, + int buflen) +{ + garray_T ga; + int i; + int len = orglen; + +#define GA_CHAR(i) ((char_u *)ga.ga_data)[i] +#define GA_PTR(i) ((char_u *)ga.ga_data + (i)) +#define STR_CHAR(i) (buf == NULL ? GA_CHAR(i) : buf[i]) +#define STR_PTR(i) (buf == NULL ? GA_PTR(i) : buf + (i)) + + // Copy "str" into "buf" or allocated memory, unmodified. + if (buf == NULL) + { + ga_init2(&ga, 1, 10); + if (ga_grow(&ga, len + 1) == FAIL) + return NULL; + mch_memmove(ga.ga_data, str, (size_t)len); + ga.ga_len = len; + } + else + { + if (len >= buflen) // Ugly! + len = buflen - 1; + mch_memmove(buf, str, (size_t)len); + } + if (buf == NULL) + GA_CHAR(len) = NUL; + else + buf[len] = NUL; + + // Make each character lower case. + i = 0; + while (STR_CHAR(i) != NUL) + { + if (enc_utf8 || (has_mbyte && MB_BYTE2LEN(STR_CHAR(i)) > 1)) + { + if (enc_utf8) + { + int c = utf_ptr2char(STR_PTR(i)); + int olen = utf_ptr2len(STR_PTR(i)); + int lc = utf_tolower(c); + + // Only replace the character when it is not an invalid + // sequence (ASCII character or more than one byte) and + // utf_tolower() doesn't return the original character. + if ((c < 0x80 || olen > 1) && c != lc) + { + int nlen = utf_char2len(lc); + + // If the byte length changes need to shift the following + // characters forward or backward. + if (olen != nlen) + { + if (nlen > olen) + { + if (buf == NULL + ? ga_grow(&ga, nlen - olen + 1) == FAIL + : len + nlen - olen >= buflen) + { + // out of memory, keep old char + lc = c; + nlen = olen; + } + } + if (olen != nlen) + { + if (buf == NULL) + { + STRMOVE(GA_PTR(i) + nlen, GA_PTR(i) + olen); + ga.ga_len += nlen - olen; + } + else + { + STRMOVE(buf + i + nlen, buf + i + olen); + len += nlen - olen; + } + } + } + (void)utf_char2bytes(lc, STR_PTR(i)); + } + } + // skip to next multi-byte char + i += (*mb_ptr2len)(STR_PTR(i)); + } + else + { + if (buf == NULL) + GA_CHAR(i) = TOLOWER_LOC(GA_CHAR(i)); + else + buf[i] = TOLOWER_LOC(buf[i]); + ++i; + } + } + + if (buf == NULL) + return (char_u *)ga.ga_data; + return buf; +} + +/* + * Catch 22: g_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 g_chartab[]. + * Does NOT work for multi-byte characters, c must be <= 255. + * Also doesn't work for the first byte of a multi-byte, "c" must be a + * character! + */ +static char_u transchar_charbuf[7]; + + char_u * +transchar(int c) +{ + return transchar_buf(curbuf, c); +} + + char_u * +transchar_buf(buf_T *buf, int c) +{ + int i; + + i = 0; + if (IS_SPECIAL(c)) // special key code, display as ~@ char + { + transchar_charbuf[0] = '~'; + transchar_charbuf[1] = '@'; + i = 2; + c = K_SECOND(c); + } + + if ((!chartab_initialized && ((c >= ' ' && c <= '~'))) + || (c < 256 && vim_isprintc_strict(c))) + { + // printable character + transchar_charbuf[i] = c; + transchar_charbuf[i + 1] = NUL; + } + else + transchar_nonprint(buf, transchar_charbuf + i, c); + return transchar_charbuf; +} + +/* + * Like transchar(), but called with a byte instead of a character. Checks + * for an illegal UTF-8 byte. Uses 'fileformat' of the current buffer. + */ + char_u * +transchar_byte(int c) +{ + return transchar_byte_buf(curbuf, c); +} + +/* + * Like transchar_buf(), but called with a byte instead of a character. Checks + * for an illegal UTF-8 byte. Uses 'fileformat' of "buf", unless it is NULL. + */ + char_u * +transchar_byte_buf(buf_T *buf, int c) +{ + if (enc_utf8 && c >= 0x80) + { + transchar_nonprint(buf, transchar_charbuf, c); + return transchar_charbuf; + } + return transchar_buf(buf, c); +} +/* + * Convert non-printable character to two or more printable characters in + * "charbuf[]". "charbuf" needs to be able to hold five bytes. + * Does NOT work for multi-byte characters, c must be <= 255. + */ + void +transchar_nonprint(buf_T *buf, char_u *charbuf, int c) +{ + if (c == NL) + c = NUL; // we use newline in place of a NUL + else if (buf != NULL && c == CAR && get_fileformat(buf) == EOL_MAC) + c = NL; // we use CR in place of NL in this case + + if (dy_flags & DY_UHEX) // 'display' has "uhex" + transchar_hex(charbuf, c); + + else if (c <= 0x7f) // 0x00 - 0x1f and 0x7f + { + charbuf[0] = '^'; + charbuf[1] = c ^ 0x40; // DEL displayed as ^? + charbuf[2] = NUL; + } + else if (enc_utf8) + { + transchar_hex(charbuf, c); + } + else if (c >= ' ' + 0x80 && c <= '~' + 0x80) // 0xa0 - 0xfe + { + charbuf[0] = '|'; + charbuf[1] = c - 0x80; + charbuf[2] = NUL; + } + else // 0x80 - 0x9f and 0xff + { + charbuf[0] = '~'; + charbuf[1] = (c - 0x80) ^ 0x40; // 0xff displayed as ~? + charbuf[2] = NUL; + } +} + + void +transchar_hex(char_u *buf, int c) +{ + int i = 0; + + buf[0] = '<'; + if (c > 255) + { + buf[++i] = nr2hex((unsigned)c >> 12); + buf[++i] = nr2hex((unsigned)c >> 8); + } + buf[++i] = nr2hex((unsigned)c >> 4); + buf[++i] = nr2hex((unsigned)c); + buf[++i] = '>'; + buf[++i] = NUL; +} + +/* + * Convert the lower 4 bits of byte "c" to its hex character. + * Lower case letters are used to avoid the confusion of being 0xf1 or + * function key 1. + */ + static unsigned +nr2hex(unsigned c) +{ + if ((c & 0xf) <= 9) + return (c & 0xf) + '0'; + return (c & 0xf) - 10 + 'a'; +} + +/* + * Return number of display cells occupied by byte "b". + * Caller must make sure 0 <= b <= 255. + * For multi-byte mode "b" must be the first byte of a character. + * A TAB is counted as two cells: "^I". + * For UTF-8 mode this will return 0 for bytes >= 0x80, because the number of + * cells depends on further bytes. + */ + int +byte2cells(int b) +{ + if (enc_utf8 && b >= 0x80) + return 0; + return (g_chartab[b] & CT_CELL_MASK); +} + +/* + * Return number of display cells occupied by character "c". + * "c" can be a special key (negative number) in which case 3 or 4 is returned. + * A TAB is counted as two cells: "^I" or four: "<09>". + */ + int +char2cells(int c) +{ + if (IS_SPECIAL(c)) + return char2cells(K_SECOND(c)) + 2; + if (c >= 0x80) + { + // UTF-8: above 0x80 need to check the value + if (enc_utf8) + return utf_char2cells(c); + // DBCS: double-byte means double-width, except for euc-jp with first + // byte 0x8e + if (enc_dbcs != 0 && c >= 0x100) + { + if (enc_dbcs == DBCS_JPNU && ((unsigned)c >> 8) == 0x8e) + return 1; + return 2; + } + } + return (g_chartab[c & 0xff] & CT_CELL_MASK); +} + +/* + * Return number of display cells occupied by character at "*p". + * A TAB is counted as two cells: "^I" or four: "<09>". + */ + int +ptr2cells(char_u *p) +{ + if (!has_mbyte) + return byte2cells(*p); + + // For UTF-8 we need to look at more bytes if the first byte is >= 0x80. + if (enc_utf8 && *p >= 0x80) + return utf_ptr2cells(p); + // For DBCS we can tell the cell count from the first byte. + return (g_chartab[*p] & CT_CELL_MASK); +} + +/* + * Return the number of character cells string "s" will take on the screen, + * counting TABs as two characters: "^I". + */ + int +vim_strsize(char_u *s) +{ + return vim_strnsize(s, (int)MAXCOL); +} + +/* + * Return the number of character cells string "s[len]" will take on the + * screen, counting TABs as two characters: "^I". + */ + int +vim_strnsize(char_u *s, int len) +{ + int size = 0; + + while (*s != NUL && --len >= 0) + { + int l = (*mb_ptr2len)(s); + + size += ptr2cells(s); + s += l; + len -= l - 1; + } + + return size; +} + +/* + * 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. + */ + +#ifdef FEAT_VARTABS +# define RET_WIN_BUF_CHARTABSIZE(wp, buf, p, col) \ + if (*(p) == TAB && (!(wp)->w_p_list || (wp)->w_lcs_chars.tab1)) \ + { \ + return tabstop_padding(col, (buf)->b_p_ts, (buf)->b_p_vts_array); \ + } \ + else \ + return ptr2cells(p); +#else +# define RET_WIN_BUF_CHARTABSIZE(wp, buf, p, col) \ + if (*(p) == TAB && (!(wp)->w_p_list || wp->w_lcs_chars.tab1)) \ + { \ + int ts; \ + ts = (buf)->b_p_ts; \ + return (int)(ts - (col % ts)); \ + } \ + else \ + return ptr2cells(p); +#endif + + int +chartabsize(char_u *p, colnr_T col) +{ + RET_WIN_BUF_CHARTABSIZE(curwin, curbuf, p, col) +} + +#ifdef FEAT_LINEBREAK + static int +win_chartabsize(win_T *wp, char_u *p, colnr_T col) +{ + RET_WIN_BUF_CHARTABSIZE(wp, wp->w_buffer, p, col) +} +#endif + +/* + * Return the number of characters the string "s" will take on the screen, + * taking into account the size of a tab. + * Does not handle text properties, since "s" is not a buffer line. + */ + int +linetabsize_str(char_u *s) +{ + return linetabsize_col(0, s); +} + +/* + * Like linetabsize_str(), but "s" starts at column "startcol". + */ + int +linetabsize_col(int startcol, char_u *s) +{ + chartabsize_T cts; + + init_chartabsize_arg(&cts, curwin, 0, startcol, s, s); + while (*cts.cts_ptr != NUL) + cts.cts_vcol += lbr_chartabsize_adv(&cts); + clear_chartabsize_arg(&cts); + return (int)cts.cts_vcol; +} + +/* + * Like linetabsize_str(), but for a given window instead of the current one. + */ + int +win_linetabsize(win_T *wp, linenr_T lnum, char_u *line, colnr_T len) +{ + chartabsize_T cts; + + init_chartabsize_arg(&cts, wp, lnum, 0, line, line); + win_linetabsize_cts(&cts, len); + clear_chartabsize_arg(&cts); + return (int)cts.cts_vcol; +} + +/* + * Return the number of cells line "lnum" of window "wp" will take on the + * screen, taking into account the size of a tab and text properties. + */ + int +linetabsize(win_T *wp, linenr_T lnum) +{ + return win_linetabsize(wp, lnum, + ml_get_buf(wp->w_buffer, lnum, FALSE), (colnr_T)MAXCOL); +} + + void +win_linetabsize_cts(chartabsize_T *cts, colnr_T len) +{ +#ifdef FEAT_PROP_POPUP + cts->cts_with_trailing = len == MAXCOL; +#endif + for ( ; *cts->cts_ptr != NUL && (len == MAXCOL || cts->cts_ptr < cts->cts_line + len); + MB_PTR_ADV(cts->cts_ptr)) + cts->cts_vcol += win_lbr_chartabsize(cts, NULL); +#ifdef FEAT_PROP_POPUP + // check for a virtual text at the end of a line or on an empty line + if (len == MAXCOL && cts->cts_has_prop_with_text && *cts->cts_ptr == NUL) + { + (void)win_lbr_chartabsize(cts, NULL); + cts->cts_vcol += cts->cts_cur_text_width; + // when properties are above or below the empty line must also be + // counted + if (cts->cts_ptr == cts->cts_line && cts->cts_prop_lines > 0) + ++cts->cts_vcol; + } +#endif +} + +/* + * Return TRUE if 'c' is a normal identifier character: + * Letters and characters from the 'isident' option. + */ + int +vim_isIDc(int c) +{ + return (c > 0 && c < 0x100 && (g_chartab[c] & CT_ID_CHAR)); +} + +/* + * Like vim_isIDc() but not using the 'isident' option: letters, numbers and + * underscore. + */ + int +vim_isNormalIDc(int c) +{ + return ASCII_ISALNUM(c) || c == '_'; +} + +/* + * return TRUE if 'c' is a keyword character: Letters and characters from + * 'iskeyword' option for the current buffer. + * For multi-byte characters mb_get_class() is used (builtin rules). + */ + int +vim_iswordc(int c) +{ + return vim_iswordc_buf(c, curbuf); +} + + int +vim_iswordc_buf(int c, buf_T *buf) +{ + if (c >= 0x100) + { + if (enc_dbcs != 0) + return dbcs_class((unsigned)c >> 8, (unsigned)(c & 0xff)) >= 2; + if (enc_utf8) + return utf_class_buf(c, buf) >= 2; + return FALSE; + } + return (c > 0 && GET_CHARTAB(buf, c) != 0); +} + +/* + * Just like vim_iswordc() but uses a pointer to the (multi-byte) character. + */ + int +vim_iswordp(char_u *p) +{ + return vim_iswordp_buf(p, curbuf); +} + + int +vim_iswordp_buf(char_u *p, buf_T *buf) +{ + int c = *p; + + if (has_mbyte && MB_BYTE2LEN(c) > 1) + c = (*mb_ptr2char)(p); + return vim_iswordc_buf(c, buf); +} + +/* + * Return TRUE if 'c' is a valid file-name character as specified with the + * 'isfname' option. + * Assume characters above 0x100 are valid (multi-byte). + * To be used for commands like "gf". + */ + int +vim_isfilec(int c) +{ + return (c >= 0x100 || (c > 0 && (g_chartab[c] & CT_FNAME_CHAR))); +} + +#if defined(FEAT_SPELL) || defined(PROTO) +/* + * Return TRUE if 'c' is a valid file-name character, including characters left + * out of 'isfname' to make "gf" work, such as comma, space, '@', etc. + */ + int +vim_is_fname_char(int c) +{ + return vim_isfilec(c) || c == ',' || c == ' ' || c == '@'; +} +#endif + +/* + * return TRUE if 'c' is a valid file-name character or a wildcard character + * Assume characters above 0x100 are valid (multi-byte). + * Explicitly interpret ']' as a wildcard character as mch_has_wildcard("]") + * returns false. + */ + int +vim_isfilec_or_wc(int c) +{ + char_u buf[2]; + + buf[0] = (char_u)c; + buf[1] = NUL; + return vim_isfilec(c) || c == ']' || mch_has_wildcard(buf); +} + +/* + * Return TRUE if 'c' is a printable character. + * Assume characters above 0x100 are printable (multi-byte), except for + * Unicode. + */ + int +vim_isprintc(int c) +{ + if (enc_utf8 && c >= 0x100) + return utf_printable(c); + return (c >= 0x100 || (c > 0 && (g_chartab[c] & CT_PRINT_CHAR))); +} + +/* + * Strict version of vim_isprintc(c), don't return TRUE if "c" is the head + * byte of a double-byte character. + */ + int +vim_isprintc_strict(int c) +{ + if (enc_dbcs != 0 && c < 0x100 && MB_BYTE2LEN(c) > 1) + return FALSE; + if (enc_utf8 && c >= 0x100) + return utf_printable(c); + return (c >= 0x100 || (c > 0 && (g_chartab[c] & CT_PRINT_CHAR))); +} + +/* + * Prepare the structure passed to chartabsize functions. + * "line" is the start of the line, "ptr" is the first relevant character. + * When "lnum" is zero do not use text properties that insert text. + */ + void +init_chartabsize_arg( + chartabsize_T *cts, + win_T *wp, + linenr_T lnum UNUSED, + colnr_T col, + char_u *line, + char_u *ptr) +{ + CLEAR_POINTER(cts); + cts->cts_win = wp; + cts->cts_vcol = col; + cts->cts_line = line; + cts->cts_ptr = ptr; +#ifdef FEAT_PROP_POPUP + if (lnum > 0 && !ignore_text_props) + { + char_u *prop_start; + int count; + + count = get_text_props(wp->w_buffer, lnum, &prop_start, FALSE); + cts->cts_text_prop_count = count; + if (count > 0) + { + // Make a copy of the properties, so that they are properly + // aligned. Make it twice as long for the sorting below. + cts->cts_text_props = ALLOC_MULT(textprop_T, count * 2); + if (cts->cts_text_props == NULL) + cts->cts_text_prop_count = 0; + else + { + int i; + + mch_memmove(cts->cts_text_props + count, prop_start, + count * sizeof(textprop_T)); + for (i = 0; i < count; ++i) + { + textprop_T *tp = cts->cts_text_props + i + count; + if (tp->tp_id < 0 + && text_prop_type_valid(wp->w_buffer, tp)) + { + cts->cts_has_prop_with_text = TRUE; + break; + } + } + if (!cts->cts_has_prop_with_text) + { + // won't use the text properties, free them + VIM_CLEAR(cts->cts_text_props); + cts->cts_text_prop_count = 0; + } + else + { + int *text_prop_idxs; + + // Need to sort the array to get any truncation right. + // Do the sorting in the second part of the array, then + // move the sorted props to the first part of the array. + text_prop_idxs = ALLOC_MULT(int, count); + if (text_prop_idxs != NULL) + { + for (i = 0; i < count; ++i) + text_prop_idxs[i] = i + count; + sort_text_props(curbuf, cts->cts_text_props, + text_prop_idxs, count); + // Here we want the reverse order. + for (i = 0; i < count; ++i) + cts->cts_text_props[count - i - 1] = + cts->cts_text_props[text_prop_idxs[i]]; + vim_free(text_prop_idxs); + } + } + } + } + } +#endif +} + +/* + * Free any allocated item in "cts". + */ + void +clear_chartabsize_arg(chartabsize_T *cts UNUSED) +{ +#ifdef FEAT_PROP_POPUP + if (cts->cts_text_prop_count > 0) + { + VIM_CLEAR(cts->cts_text_props); + cts->cts_text_prop_count = 0; + } +#endif +} + +/* + * Like chartabsize(), but also check for line breaks on the screen and text + * properties that insert text. + */ + int +lbr_chartabsize(chartabsize_T *cts) +{ +#if defined(FEAT_LINEBREAK) || defined(FEAT_PROP_POPUP) + if (1 +# ifdef FEAT_LINEBREAK + && !curwin->w_p_lbr && *get_showbreak_value(curwin) == NUL + && !curwin->w_p_bri +# endif +# ifdef FEAT_PROP_POPUP + && !cts->cts_has_prop_with_text +#endif + ) + { +#endif + if (curwin->w_p_wrap) + return win_nolbr_chartabsize(cts, NULL); + RET_WIN_BUF_CHARTABSIZE(curwin, curbuf, cts->cts_ptr, cts->cts_vcol) +#if defined(FEAT_LINEBREAK) || defined(FEAT_PROP_POPUP) + } + return win_lbr_chartabsize(cts, NULL); +#endif +} + +/* + * Call lbr_chartabsize() and advance the pointer. + */ + int +lbr_chartabsize_adv(chartabsize_T *cts) +{ + int retval; + + retval = lbr_chartabsize(cts); + MB_PTR_ADV(cts->cts_ptr); + return retval; +} + +/* + * Return the screen size of the character indicated by "cts". + * "cts->cts_cur_text_width" is set to the extra size for a text property that + * inserts text. + * This function is used very often, keep it fast!!!! + * + * If "headp" not NULL, set "*headp" to the size of 'showbreak'/'breakindent' + * included in the return value. + * When "cts->cts_max_head_vcol" is positive, only count in "*headp" the size + * of 'showbreak'/'breakindent' before "cts->cts_max_head_vcol". + * When "cts->cts_max_head_vcol" is negative, only count in "*headp" the size + * of 'showbreak'/'breakindent' before where cursor should be placed. + * + * Warning: "*headp" may not be set if it's 0, init to 0 before calling. + */ + int +win_lbr_chartabsize( + chartabsize_T *cts, + int *headp UNUSED) +{ + win_T *wp = cts->cts_win; +#if defined(FEAT_PROP_POPUP) || defined(FEAT_LINEBREAK) + char_u *line = cts->cts_line; // start of the line +#endif + char_u *s = cts->cts_ptr; + colnr_T vcol = cts->cts_vcol; +#ifdef FEAT_LINEBREAK + int size; + int mb_added = 0; + int n; + char_u *sbr; + int no_sbr = FALSE; + colnr_T vcol_start = 0; // start from where to consider linebreak +#endif + +#if defined(FEAT_PROP_POPUP) + cts->cts_cur_text_width = 0; + cts->cts_first_char = 0; +#endif + +#if defined(FEAT_LINEBREAK) || defined(FEAT_PROP_POPUP) + /* + * No 'linebreak', 'showbreak', 'breakindent' and text properties that + * insert text: return quickly. + */ + if (1 +# ifdef FEAT_LINEBREAK + && !wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL +# endif +# ifdef FEAT_PROP_POPUP + && !cts->cts_has_prop_with_text +# endif + ) +#endif + { + if (wp->w_p_wrap) + return win_nolbr_chartabsize(cts, headp); + RET_WIN_BUF_CHARTABSIZE(wp, wp->w_buffer, s, vcol) + } + +#if defined(FEAT_LINEBREAK) || defined(FEAT_PROP_POPUP) + int has_lcs_eol = wp->w_p_list && wp->w_lcs_chars.eol != NUL; + + /* + * First get the normal size, without 'linebreak' or text properties + */ + size = win_chartabsize(wp, s, vcol); + if (*s == NUL && !has_lcs_eol) + size = 0; // NUL is not displayed +# ifdef FEAT_LINEBREAK + int is_doublewidth = has_mbyte && size == 2 && MB_BYTE2LEN(*s) > 1; +# endif + +# ifdef FEAT_PROP_POPUP + if (cts->cts_has_prop_with_text) + { + int tab_size = size; + int charlen = *s == NUL ? 1 : mb_ptr2len(s); + int i; + int col = (int)(s - line); + garray_T *gap = &wp->w_buffer->b_textprop_text; + + // The "$" for 'list' mode will go between the EOL and + // the text prop, account for that. + if (has_lcs_eol) + { + ++vcol; + --size; + } + + for (i = 0; i < cts->cts_text_prop_count; ++i) + { + textprop_T *tp = cts->cts_text_props + i; + int col_off = win_col_off(wp); + + // Watch out for the text being deleted. "cts_text_props" is a + // copy, the text prop may actually have been removed from the line. + if (tp->tp_id < 0 + && ((tp->tp_col - 1 >= col + && tp->tp_col - 1 < col + charlen) + || (tp->tp_col == MAXCOL + && ((tp->tp_flags & TP_FLAG_ALIGN_ABOVE) + ? col == 0 + : s[0] == NUL && cts->cts_with_trailing))) + && -tp->tp_id - 1 < gap->ga_len) + { + char_u *p = ((char_u **)gap->ga_data)[-tp->tp_id - 1]; + + if (p != NULL) + { + int cells; + + if (tp->tp_col == MAXCOL) + { + int n_extra = (int)STRLEN(p); + + cells = text_prop_position(wp, tp, vcol, + (vcol + size) % (wp->w_width - col_off) + col_off, + &n_extra, &p, NULL, NULL, FALSE); +# ifdef FEAT_LINEBREAK + no_sbr = TRUE; // don't use 'showbreak' now +# endif + } + else + cells = vim_strsize(p); + cts->cts_cur_text_width += cells; + if (tp->tp_flags & TP_FLAG_ALIGN_ABOVE) + cts->cts_first_char += cells; + else + size += cells; + cts->cts_start_incl = tp->tp_flags & TP_FLAG_START_INCL; + if (*s == TAB) + { + // tab size changes because of the inserted text + size -= tab_size; + tab_size = win_chartabsize(wp, s, vcol + size); + size += tab_size; + } + if (tp->tp_col == MAXCOL && (tp->tp_flags + & (TP_FLAG_ALIGN_ABOVE | TP_FLAG_ALIGN_BELOW))) + // count extra line for property above/below + ++cts->cts_prop_lines; + } + } + if (tp->tp_col != MAXCOL && tp->tp_col - 1 > col) + break; + } + if (has_lcs_eol) + { + --vcol; + ++size; + } + } +# endif + +# ifdef FEAT_LINEBREAK + if (is_doublewidth && wp->w_p_wrap && in_win_border(wp, vcol + size - 2)) + { + ++size; // Count the ">" in the last column. + mb_added = 1; + } + + /* + * May have to add something for 'breakindent' and/or 'showbreak' + * string at the start of a screen line. + */ + int head = mb_added; + sbr = no_sbr ? empty_option : get_showbreak_value(wp); + // When "size" is 0, no new screen line is started. + if (size > 0 && wp->w_p_wrap && (*sbr != NUL || wp->w_p_bri)) + { + int col_off_prev = win_col_off(wp); + int width2 = wp->w_width - col_off_prev + win_col_off2(wp); + colnr_T wcol = vcol + col_off_prev; +# ifdef FEAT_PROP_POPUP + wcol -= wp->w_virtcol_first_char; +# endif + colnr_T max_head_vcol = cts->cts_max_head_vcol; + int added = 0; + + // cells taken by 'showbreak'/'breakindent' before current char + int head_prev = 0; + if (wcol >= wp->w_width) + { + wcol -= wp->w_width; + col_off_prev = wp->w_width - width2; + if (wcol >= width2 && width2 > 0) + wcol %= width2; + if (*sbr != NUL) + head_prev += vim_strsize(sbr); + if (wp->w_p_bri) + head_prev += get_breakindent_win(wp, line); + if (wcol < head_prev) + { + head_prev -= wcol; + wcol += head_prev; + added += head_prev; + if (max_head_vcol <= 0 || vcol < max_head_vcol) + head += head_prev; + } + else + head_prev = 0; + wcol += col_off_prev; + } + + if (wcol + size > wp->w_width) + { + // cells taken by 'showbreak'/'breakindent' halfway current char + int head_mid = 0; + if (*sbr != NUL) + head_mid += vim_strsize(sbr); + if (wp->w_p_bri) + head_mid += get_breakindent_win(wp, line); + if (head_mid > 0 && wcol + size > wp->w_width) + { + // Calculate effective window width. + int prev_rem = wp->w_width - wcol; + int width = width2 - head_mid; + + if (width <= 0) + width = 1; + // Divide "size - prev_rem" by "width", rounding up. + int cnt = (size - prev_rem + width - 1) / width; + added += cnt * head_mid; + + if (max_head_vcol == 0 || vcol + size + added < max_head_vcol) + head += cnt * head_mid; + else if (max_head_vcol > vcol + head_prev + prev_rem) + head += (max_head_vcol - (vcol + head_prev + prev_rem) + + width2 - 1) / width2 * head_mid; +# ifdef FEAT_PROP_POPUP + else if (max_head_vcol < 0) + { + int off = 0; + if (*s != NUL + && ((State & MODE_NORMAL) || cts->cts_start_incl)) + off += cts->cts_cur_text_width; + if (off >= prev_rem) + head += (1 + (off - prev_rem) / width) * head_mid; + } +# endif + } + } + + size += added; + } + + if (headp != NULL) + *headp = head; + + /* + * If 'linebreak' set check at a blank before a non-blank if the line + * needs a break here + */ + if (wp->w_p_lbr && wp->w_p_wrap && wp->w_width != 0) + { + char_u *t = cts->cts_line; + while (VIM_ISBREAK((int)t[0])) + t++; + vcol_start = t - cts->cts_line; + } + if (wp->w_p_lbr && vcol_start <= vcol + && VIM_ISBREAK((int)s[0]) + && !VIM_ISBREAK((int)s[1]) + && wp->w_p_wrap + && wp->w_width != 0) + { + /* + * Count all characters from first non-blank after a blank up to next + * non-blank after a blank. + */ + int numberextra = win_col_off(wp); + colnr_T col_adj = size - 1; + colnr_T colmax = (colnr_T)(wp->w_width - numberextra - col_adj); + if (vcol >= colmax) + { + colmax += col_adj; + n = colmax + win_col_off2(wp); + if (n > 0) + colmax += (((vcol - colmax) / n) + 1) * n - col_adj; + } + + colnr_T vcol2 = vcol; + for (;;) + { + char_u *ps = s; + MB_PTR_ADV(s); + int c = *s; + if (!(c != NUL + && (VIM_ISBREAK(c) + || (!VIM_ISBREAK(c) + && (vcol2 == vcol || !VIM_ISBREAK((int)*ps)))))) + break; + + vcol2 += win_chartabsize(wp, s, vcol2); + if (vcol2 >= colmax) // doesn't fit + { + size = colmax - vcol + col_adj; + break; + } + } + } + +# ifdef FEAT_PROP_POPUP + size += cts->cts_first_char; +# endif + return size; +# endif +#endif +} + +/* + * Like win_lbr_chartabsize(), except that we know 'linebreak' is off, 'wrap' + * is on and there are no properties that insert text. This means we need to + * check for a double-byte character that doesn't fit at the end of the screen + * line. + * Only uses "cts_win", "cts_ptr" and "cts_vcol" from "cts". + */ + static int +win_nolbr_chartabsize( + chartabsize_T *cts, + int *headp) +{ + win_T *wp = cts->cts_win; + char_u *s = cts->cts_ptr; + colnr_T col = cts->cts_vcol; + int n; + + if (*s == TAB && (!wp->w_p_list || wp->w_lcs_chars.tab1)) + { +# ifdef FEAT_VARTABS + return tabstop_padding(col, wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array); +# else + n = wp->w_buffer->b_p_ts; + return (int)(n - (col % n)); +# endif + } + n = ptr2cells(s); + // Add one cell for a double-width character in the last column of the + // window, displayed with a ">". + if (n == 2 && MB_BYTE2LEN(*s) > 1 && in_win_border(wp, col)) + { + if (headp != NULL) + *headp = 1; + return 3; + } + return n; +} + +/* + * Return TRUE if virtual column "vcol" is in the rightmost column of window + * "wp". + */ + static int +in_win_border(win_T *wp, colnr_T vcol) +{ + int width1; // width of first line (after line number) + int width2; // width of further lines + + if (wp->w_width == 0) // there is no border + return FALSE; + width1 = wp->w_width - win_col_off(wp); + if ((int)vcol < width1 - 1) + return FALSE; + if ((int)vcol == width1 - 1) + return TRUE; + width2 = width1 + win_col_off2(wp); + if (width2 <= 0) + return FALSE; + return ((vcol - width1) % width2 == width2 - 1); +} + +/* + * 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) + * + * This is used very often, keep it fast! + */ + void +getvcol( + win_T *wp, + pos_T *pos, + colnr_T *start, + colnr_T *cursor, + colnr_T *end) +{ + colnr_T vcol; + char_u *ptr; // points to current char + char_u *posptr; // points to char at pos->col + char_u *line; // start of the line + int incr; + int head; +#ifdef FEAT_VARTABS + int *vts = wp->w_buffer->b_p_vts_array; +#endif + int ts = wp->w_buffer->b_p_ts; + int c; + chartabsize_T cts; +#ifdef FEAT_PROP_POPUP + int on_NUL = FALSE; +#endif + + vcol = 0; + line = ptr = ml_get_buf(wp->w_buffer, pos->lnum, FALSE); + if (pos->col == MAXCOL) + posptr = NULL; // continue until the NUL + else + { + colnr_T i; + + // In a few cases the position can be beyond the end of the line. + for (i = 0; i < pos->col; ++i) + if (ptr[i] == NUL) + { + pos->col = i; + break; + } + posptr = ptr + pos->col; + if (has_mbyte) + // always start on the first byte + posptr -= (*mb_head_off)(line, posptr); + } + + init_chartabsize_arg(&cts, wp, pos->lnum, 0, line, line); + cts.cts_max_head_vcol = -1; + + /* + * This function is used very often, do some speed optimizations. + * When 'list', 'linebreak', 'showbreak' and 'breakindent' are not set + * and there are no text properties with "text" use a simple loop. + * Also use this when 'list' is set but tabs take their normal size. + */ + if ((!wp->w_p_list || wp->w_lcs_chars.tab1 != NUL) +#ifdef FEAT_LINEBREAK + && !wp->w_p_lbr && *get_showbreak_value(wp) == NUL && !wp->w_p_bri +#endif +#ifdef FEAT_PROP_POPUP + && !cts.cts_has_prop_with_text +#endif + ) + { + for (;;) + { + head = 0; + 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) +#ifdef FEAT_VARTABS + incr = tabstop_padding(vcol, ts, vts); +#else + incr = ts - (vcol % ts); +#endif + else + { + if (has_mbyte) + { + // For utf-8, if the byte is >= 0x80, need to look at + // further bytes to find the cell width. + if (enc_utf8 && c >= 0x80) + incr = utf_ptr2cells(ptr); + else + incr = g_chartab[c] & CT_CELL_MASK; + + // If a double-cell char doesn't fit at the end of a line + // it wraps to the next line, it's like this char is three + // cells wide. + if (incr == 2 && wp->w_p_wrap && MB_BYTE2LEN(*ptr) > 1 + && in_win_border(wp, vcol)) + { + ++incr; + head = 1; + } + } + else + incr = g_chartab[c] & CT_CELL_MASK; + } + + if (posptr != NULL && ptr >= posptr) // character at pos->col + break; + + vcol += incr; + MB_PTR_ADV(ptr); + } + } + else + { + for (;;) + { + // A tab gets expanded, depending on the current column. + // Other things also take up space. + head = 0; + incr = win_lbr_chartabsize(&cts, &head); + // make sure we don't go past the end of the line + if (*cts.cts_ptr == NUL) + { + incr = 1; // NUL at end of line only takes one column +#ifdef FEAT_PROP_POPUP + if (cts.cts_cur_text_width > 0) + incr = cts.cts_cur_text_width; + on_NUL = TRUE; +#endif + break; + } +#ifdef FEAT_PROP_POPUP + if (cursor == &wp->w_virtcol && cts.cts_ptr == cts.cts_line) + // do not count the virtual text above for w_curswant + wp->w_virtcol_first_char = cts.cts_first_char; +#endif + + if (posptr != NULL && cts.cts_ptr >= posptr) + // character at pos->col + break; + + cts.cts_vcol += incr; + MB_PTR_ADV(cts.cts_ptr); + } + vcol = cts.cts_vcol; + ptr = cts.cts_ptr; + } + clear_chartabsize_arg(&cts); + + if (start != NULL) + *start = vcol + head; + if (end != NULL) + *end = vcol + incr - 1; + if (cursor != NULL) + { + if (*ptr == TAB + && (State & MODE_NORMAL) + && !wp->w_p_list + && !virtual_active() + && !(VIsual_active + && (*p_sel == 'e' || LTOREQ_POS(*pos, VIsual))) + ) + *cursor = vcol + incr - 1; // cursor at end + else + { +#ifdef FEAT_PROP_POPUP + // in Insert mode, if "start_incl" is true the text gets inserted + // after the virtual text, thus add its width + if (((State & MODE_INSERT) == 0 || cts.cts_start_incl) && !on_NUL) + // cursor is after inserted text, unless on the NUL + vcol += cts.cts_cur_text_width; + else + // insertion also happens after the "above" virtual text + vcol += cts.cts_first_char; +#endif + *cursor = vcol + head; // cursor at start + } + } +} + +/* + * Get virtual cursor column in the current window, pretending 'list' is off. + */ + colnr_T +getvcol_nolist(pos_T *posp) +{ + int list_save = curwin->w_p_list; + colnr_T vcol; + + curwin->w_p_list = FALSE; + if (posp->coladd) + getvvcol(curwin, posp, NULL, &vcol, NULL); + else + getvcol(curwin, posp, NULL, &vcol, NULL); + curwin->w_p_list = list_save; + return vcol; +} + +/* + * Get virtual column in virtual mode. + */ + void +getvvcol( + win_T *wp, + pos_T *pos, + colnr_T *start, + colnr_T *cursor, + colnr_T *end) +{ + colnr_T col; + colnr_T coladd; + colnr_T endadd; + char_u *ptr; + + if (virtual_active()) + { + // For virtual mode, only want one value + getvcol(wp, pos, &col, NULL, NULL); + + coladd = pos->coladd; + endadd = 0; + // Cannot put the cursor on part of a wide character. + ptr = ml_get_buf(wp->w_buffer, pos->lnum, FALSE); + if (pos->col < (colnr_T)STRLEN(ptr)) + { + int c = (*mb_ptr2char)(ptr + pos->col); + + if (c != TAB && vim_isprintc(c)) + { + endadd = (colnr_T)(char2cells(c) - 1); + if (coladd > endadd) // past end of line + endadd = 0; + else + coladd = 0; + } + } + col += coladd; + if (start != NULL) + *start = col; + if (cursor != NULL) + *cursor = col; + if (end != NULL) + *end = col + endadd; + } + else + getvcol(wp, pos, start, cursor, end); +} + +/* + * Get the leftmost and rightmost virtual column of pos1 and pos2. + * Used for Visual block mode. + */ + void +getvcols( + win_T *wp, + pos_T *pos1, + pos_T *pos2, + colnr_T *left, + colnr_T *right) +{ + colnr_T from1, from2, to1, to2; + + if (LT_POSP(pos1, pos2)) + { + getvvcol(wp, pos1, &from1, NULL, &to1); + getvvcol(wp, pos2, &from2, NULL, &to2); + } + else + { + getvvcol(wp, pos2, &from1, NULL, &to1); + getvvcol(wp, pos1, &from2, NULL, &to2); + } + if (from2 < from1) + *left = from2; + else + *left = from1; + if (to2 > to1) + { + if (*p_sel == 'e' && from2 - 1 >= to1) + *right = from2 - 1; + else + *right = to2; + } + else + *right = to1; +} + +/* + * Skip over ' ' and '\t'. + */ + char_u * +skipwhite(char_u *q) +{ + char_u *p = q; + + while (VIM_ISWHITE(*p)) + ++p; + return p; +} + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * skip over ' ', '\t' and '\n'. + */ + char_u * +skipwhite_and_nl(char_u *q) +{ + char_u *p = q; + + while (VIM_ISWHITE(*p) || *p == NL) + ++p; + return p; +} +#endif + +/* + * getwhitecols: return the number of whitespace + * columns (bytes) at the start of a given line + */ + int +getwhitecols_curline(void) +{ + return getwhitecols(ml_get_curline()); +} + + int +getwhitecols(char_u *p) +{ + return skipwhite(p) - p; +} + +/* + * skip over digits + */ + char_u * +skipdigits(char_u *q) +{ + char_u *p = q; + + while (VIM_ISDIGIT(*p)) // skip to next non-digit + ++p; + return p; +} + +#if defined(FEAT_SYN_HL) || defined(FEAT_SPELL) || defined(PROTO) +/* + * skip over binary digits + */ + char_u * +skipbin(char_u *q) +{ + char_u *p = q; + + while (vim_isbdigit(*p)) // skip to next non-digit + ++p; + return p; +} + +/* + * skip over digits and hex characters + */ + char_u * +skiphex(char_u *q) +{ + char_u *p = q; + + while (vim_isxdigit(*p)) // skip to next non-digit + ++p; + return p; +} +#endif + +/* + * skip to bin digit (or NUL after the string) + */ + char_u * +skiptobin(char_u *q) +{ + char_u *p = q; + + while (*p != NUL && !vim_isbdigit(*p)) // skip to next digit + ++p; + return p; +} + +/* + * skip to digit (or NUL after the string) + */ + char_u * +skiptodigit(char_u *q) +{ + char_u *p = q; + + while (*p != NUL && !VIM_ISDIGIT(*p)) // skip to next digit + ++p; + return p; +} + +/* + * skip to hex character (or NUL after the string) + */ + char_u * +skiptohex(char_u *q) +{ + char_u *p = q; + + while (*p != NUL && !vim_isxdigit(*p)) // skip to next digit + ++p; + return p; +} + +/* + * Variant of isdigit() that can handle characters > 0x100. + * We don't use isdigit() here, because on some systems it also considers + * superscript 1 to be a digit. + * Use the VIM_ISDIGIT() macro for simple arguments. + */ + int +vim_isdigit(int c) +{ + return (c >= '0' && c <= '9'); +} + +/* + * Variant of isxdigit() that can handle characters > 0x100. + * We don't use isxdigit() here, because on some systems it also considers + * superscript 1 to be a digit. + */ + int +vim_isxdigit(int c) +{ + return (c >= '0' && c <= '9') + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F'); +} + +/* + * Corollary of vim_isdigit and vim_isxdigit() that can handle + * characters > 0x100. + */ + int +vim_isbdigit(int c) +{ + return (c == '0' || c == '1'); +} + + static int +vim_isodigit(int c) +{ + return (c >= '0' && c <= '7'); +} + +/* + * Vim's own character class functions. These exist because many library + * islower()/toupper() etc. do not work properly: they crash when used with + * invalid values or can't handle latin1 when the locale is C. + * Speed is most important here. + */ +#define LATIN1LOWER 'l' +#define LATIN1UPPER 'U' + +static char_u latin1flags[257] = " UUUUUUUUUUUUUUUUUUUUUUUUUU llllllllllllllllllllllllll UUUUUUUUUUUUUUUUUUUUUUU UUUUUUUllllllllllllllllllllllll llllllll"; +static char_u latin1upper[257] = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xf7\xd8\xd9\xda\xdb\xdc\xdd\xde\xff"; +static char_u latin1lower[257] = " !\"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xd7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"; + + int +vim_islower(int c) +{ + if (c <= '@') + return FALSE; + if (c >= 0x80) + { + if (enc_utf8) + return utf_islower(c); + if (c >= 0x100) + { +#ifdef HAVE_ISWLOWER + if (has_mbyte) + return iswlower(c); +#endif + // islower() can't handle these chars and may crash + return FALSE; + } + if (enc_latin1like) + return (latin1flags[c] & LATIN1LOWER) == LATIN1LOWER; + } + return islower(c); +} + + int +vim_isupper(int c) +{ + if (c <= '@') + return FALSE; + if (c >= 0x80) + { + if (enc_utf8) + return utf_isupper(c); + if (c >= 0x100) + { +#ifdef HAVE_ISWUPPER + if (has_mbyte) + return iswupper(c); +#endif + // islower() can't handle these chars and may crash + return FALSE; + } + if (enc_latin1like) + return (latin1flags[c] & LATIN1UPPER) == LATIN1UPPER; + } + return isupper(c); +} + + int +vim_isalpha(int c) +{ + return vim_islower(c) || vim_isupper(c); +} + + int +vim_toupper(int c) +{ + if (c <= '@') + return c; + if (c >= 0x80 || !(cmp_flags & CMP_KEEPASCII)) + { + if (enc_utf8) + return utf_toupper(c); + if (c >= 0x100) + { +#ifdef HAVE_TOWUPPER + if (has_mbyte) + return towupper(c); +#endif + // toupper() can't handle these chars and may crash + return c; + } + if (enc_latin1like) + return latin1upper[c]; + } + if (c < 0x80 && (cmp_flags & CMP_KEEPASCII)) + return TOUPPER_ASC(c); + return TOUPPER_LOC(c); +} + + int +vim_tolower(int c) +{ + if (c <= '@') + return c; + if (c >= 0x80 || !(cmp_flags & CMP_KEEPASCII)) + { + if (enc_utf8) + return utf_tolower(c); + if (c >= 0x100) + { +#ifdef HAVE_TOWLOWER + if (has_mbyte) + return towlower(c); +#endif + // tolower() can't handle these chars and may crash + return c; + } + if (enc_latin1like) + return latin1lower[c]; + } + if (c < 0x80 && (cmp_flags & CMP_KEEPASCII)) + return TOLOWER_ASC(c); + return TOLOWER_LOC(c); +} + +/* + * skiptowhite: skip over text until ' ' or '\t' or NUL. + */ + char_u * +skiptowhite(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(char_u *p) +{ + while (*p != ' ' && *p != '\t' && *p != NUL) + { + if ((*p == '\\' || *p == Ctrl_V) && *(p + 1) != NUL) + ++p; + ++p; + } + return p; +} + +/* + * Get a number from a string and skip over it. + * Note: the argument is a pointer to a char_u pointer! + */ + long +getdigits(char_u **pp) +{ + char_u *p; + long retval; + + p = *pp; + retval = atol((char *)p); + if (*p == '-') // skip negative sign + ++p; + p = skipdigits(p); // skip to next non-digit + *pp = p; + return retval; +} + +/* + * Like getdigits() but allow for embedded single quotes. + */ + long +getdigits_quoted(char_u **pp) +{ + char_u *p = *pp; + long retval = 0; + + if (*p == '-') + ++p; + while (VIM_ISDIGIT(*p)) + { + if (retval >= LONG_MAX / 10 - 10) + retval = LONG_MAX; + else + retval = retval * 10 - '0' + *p; + ++p; + if (in_vim9script() && *p == '\'' && VIM_ISDIGIT(p[1])) + ++p; + } + if (**pp == '-') + { + if (retval == LONG_MAX) + retval = LONG_MIN; + else + retval = -retval; + } + *pp = p; + return retval; +} + +/* + * Return TRUE if "lbuf" is empty or only contains blanks. + */ + int +vim_isblankline(char_u *lbuf) +{ + char_u *p; + + p = skipwhite(lbuf); + return (*p == NUL || *p == '\r' || *p == '\n'); +} + +/* + * Convert a string into a long and/or unsigned long, taking care of + * hexadecimal, octal, and binary numbers. Accepts a '-' sign. + * If "prep" is not NULL, returns a flag to indicate the type of the number: + * 0 decimal + * '0' octal + * 'O' octal + * 'o' octal + * 'B' bin + * 'b' bin + * 'X' hex + * 'x' hex + * If "len" is not NULL, the length of the number in characters is returned. + * If "nptr" is not NULL, the signed result is returned in it. + * If "unptr" is not NULL, the unsigned result is returned in it. + * If "what" contains STR2NR_BIN recognize binary numbers + * If "what" contains STR2NR_OCT recognize octal numbers + * If "what" contains STR2NR_HEX recognize hex numbers + * If "what" contains STR2NR_FORCE always assume bin/oct/hex. + * If "what" contains STR2NR_QUOTE ignore embedded single quotes + * If maxlen > 0, check at a maximum maxlen chars. + * If strict is TRUE, check the number strictly. return *len = 0 if fail. + */ + void +vim_str2nr( + char_u *start, + int *prep, // return: type of number 0 = decimal, 'x' + // or 'X' is hex, '0', 'o' or 'O' is octal, + // 'b' or 'B' is bin + int *len, // return: detected length of number + int what, // what numbers to recognize + varnumber_T *nptr, // return: signed result + uvarnumber_T *unptr, // return: unsigned result + int maxlen, // max length of string to check + int strict, // check strictly + int *overflow) // when not NULL set to TRUE for overflow +{ + char_u *ptr = start; + int pre = 0; // default is decimal + int negative = FALSE; + uvarnumber_T un = 0; + int n; + + if (len != NULL) + *len = 0; + + if (ptr[0] == '-') + { + negative = TRUE; + ++ptr; + } + + // Recognize hex, octal, and bin. + if (ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9' + && (maxlen == 0 || maxlen > 1)) + { + pre = ptr[1]; + if ((what & STR2NR_HEX) + && (pre == 'X' || pre == 'x') && vim_isxdigit(ptr[2]) + && (maxlen == 0 || maxlen > 2)) + // hexadecimal + ptr += 2; + else if ((what & STR2NR_BIN) + && (pre == 'B' || pre == 'b') && vim_isbdigit(ptr[2]) + && (maxlen == 0 || maxlen > 2)) + // binary + ptr += 2; + else if ((what & STR2NR_OOCT) + && (pre == 'O' || pre == 'o') && vim_isodigit(ptr[2]) + && (maxlen == 0 || maxlen > 2)) + // octal with prefix "0o" + ptr += 2; + else + { + // decimal or octal, default is decimal + pre = 0; + if (what & STR2NR_OCT) + { + // Don't interpret "0", "08" or "0129" as octal. + for (n = 1; n != maxlen && VIM_ISDIGIT(ptr[n]); ++n) + { + if (ptr[n] > '7') + { + pre = 0; // can't be octal + break; + } + pre = '0'; // assume octal + } + } + } + } + + // Do the conversion manually to avoid sscanf() quirks. + n = 1; + if (pre == 'B' || pre == 'b' + || ((what & STR2NR_BIN) && (what & STR2NR_FORCE))) + { + // bin + if (pre != 0) + n += 2; // skip over "0b" + while ('0' <= *ptr && *ptr <= '1') + { + // avoid ubsan error for overflow + if (un <= UVARNUM_MAX / 2) + un = 2 * un + (uvarnumber_T)(*ptr - '0'); + else + { + un = UVARNUM_MAX; + if (overflow != NULL) + *overflow = TRUE; + } + ++ptr; + if (n++ == maxlen) + break; + if ((what & STR2NR_QUOTE) && *ptr == '\'' + && '0' <= ptr[1] && ptr[1] <= '1') + { + ++ptr; + if (n++ == maxlen) + break; + } + } + } + else if (pre == 'O' || pre == 'o' || + pre == '0' || ((what & STR2NR_OCT) && (what & STR2NR_FORCE))) + { + // octal + if (pre != 0 && pre != '0') + n += 2; // skip over "0o" + while ('0' <= *ptr && *ptr <= '7') + { + // avoid ubsan error for overflow + if (un <= UVARNUM_MAX / 8) + un = 8 * un + (uvarnumber_T)(*ptr - '0'); + else + { + un = UVARNUM_MAX; + if (overflow != NULL) + *overflow = TRUE; + } + ++ptr; + if (n++ == maxlen) + break; + if ((what & STR2NR_QUOTE) && *ptr == '\'' + && '0' <= ptr[1] && ptr[1] <= '7') + { + ++ptr; + if (n++ == maxlen) + break; + } + } + } + else if (pre != 0 || ((what & STR2NR_HEX) && (what & STR2NR_FORCE))) + { + // hex + if (pre != 0) + n += 2; // skip over "0x" + while (vim_isxdigit(*ptr)) + { + // avoid ubsan error for overflow + if (un <= UVARNUM_MAX / 16) + un = 16 * un + (uvarnumber_T)hex2nr(*ptr); + else + { + un = UVARNUM_MAX; + if (overflow != NULL) + *overflow = TRUE; + } + ++ptr; + if (n++ == maxlen) + break; + if ((what & STR2NR_QUOTE) && *ptr == '\'' && vim_isxdigit(ptr[1])) + { + ++ptr; + if (n++ == maxlen) + break; + } + } + } + else + { + // decimal + while (VIM_ISDIGIT(*ptr)) + { + uvarnumber_T digit = (uvarnumber_T)(*ptr - '0'); + + // avoid ubsan error for overflow + if (un < UVARNUM_MAX / 10 + || (un == UVARNUM_MAX / 10 && digit <= UVARNUM_MAX % 10)) + un = 10 * un + digit; + else + { + un = UVARNUM_MAX; + if (overflow != NULL) + *overflow = TRUE; + } + ++ptr; + if (n++ == maxlen) + break; + if ((what & STR2NR_QUOTE) && *ptr == '\'' && VIM_ISDIGIT(ptr[1])) + { + ++ptr; + if (n++ == maxlen) + break; + } + } + } + + // Check for an alphanumeric character immediately following, that is + // most likely a typo. + if (strict && n - 1 != maxlen && ASCII_ISALNUM(*ptr)) + return; + + if (prep != NULL) + *prep = pre; + if (len != NULL) + *len = (int)(ptr - start); + if (nptr != NULL) + { + if (negative) // account for leading '-' for decimal numbers + { + // avoid ubsan error for overflow + if (un > VARNUM_MAX) + { + *nptr = VARNUM_MIN; + if (overflow != NULL) + *overflow = TRUE; + } + else + *nptr = -(varnumber_T)un; + } + else + { + // prevent a large unsigned number to become negative + if (un > VARNUM_MAX) + { + un = VARNUM_MAX; + if (overflow != NULL) + *overflow = TRUE; + } + *nptr = (varnumber_T)un; + } + } + if (unptr != NULL) + *unptr = un; +} + +/* + * Return the value of a single hex character. + * Only valid when the argument is '0' - '9', 'A' - 'F' or 'a' - 'f'. + */ + int +hex2nr(int c) +{ + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return c - '0'; +} + +/* + * Convert two hex characters to a byte. + * Return -1 if one of the characters is not hex. + */ + int +hexhex2nr(char_u *p) +{ + if (!vim_isxdigit(p[0]) || !vim_isxdigit(p[1])) + return -1; + return (hex2nr(p[0]) << 4) + hex2nr(p[1]); +} + +/* + * Return TRUE if "str" starts with a backslash that should be removed. + * For MS-DOS, MSWIN and OS/2 this is only done when the character after the + * backslash is not a normal file name character. + * '$' is a valid file name character, we don't remove the backslash before + * it. This means it is not possible to use an environment variable after a + * backslash. "C:\$VIM\doc" is taken literally, only "$VIM\doc" works. + * Although "\ name" is valid, the backslash in "Program\ files" must be + * removed. Assume a file name doesn't start with a space. + * For multi-byte names, never remove a backslash before a non-ascii + * character, assume that all multi-byte characters are valid file name + * characters. + */ + int +rem_backslash(char_u *str) +{ +#ifdef BACKSLASH_IN_FILENAME + return (str[0] == '\\' + && str[1] < 0x80 + && (str[1] == ' ' + || (str[1] != NUL + && str[1] != '*' + && str[1] != '?' + && !vim_isfilec(str[1])))); +#else + return (str[0] == '\\' && str[1] != NUL); +#endif +} + +/* + * 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. + */ + void +backslash_halve(char_u *p) +{ + for ( ; *p; ++p) + if (rem_backslash(p)) + STRMOVE(p, p + 1); +} + +/* + * backslash_halve() plus save the result in allocated memory. + * However, returns "p" when out of memory. + */ + char_u * +backslash_halve_save(char_u *p) +{ + char_u *res; + + res = vim_strsave(p); + if (res == NULL) + return p; + backslash_halve(res); + return res; +} diff --git a/userland/apps/vim/src/cindent.c b/userland/apps/vim/src/cindent.c new file mode 100644 index 00000000..62894228 --- /dev/null +++ b/userland/apps/vim/src/cindent.c @@ -0,0 +1,4190 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * cindent.c: C indentation related functions + * + * Many of C-indenting functions originally come from Eric Fischer. + * + * Below "XXX" means that this function may unlock the current line. + */ + +#include "vim.h" + +// values for the "lookfor" state +#define LOOKFOR_INITIAL 0 +#define LOOKFOR_IF 1 +#define LOOKFOR_DO 2 +#define LOOKFOR_CASE 3 +#define LOOKFOR_ANY 4 +#define LOOKFOR_TERM 5 +#define LOOKFOR_UNTERM 6 +#define LOOKFOR_SCOPEDECL 7 +#define LOOKFOR_NOBREAK 8 +#define LOOKFOR_CPP_BASECLASS 9 +#define LOOKFOR_ENUM_OR_INIT 10 +#define LOOKFOR_JS_KEY 11 +#define LOOKFOR_COMMA 12 + +/* + * Return TRUE if the string "line" starts with a word from 'cinwords'. + */ + int +cin_is_cinword(char_u *line) +{ + char_u *cinw; + char_u *cinw_buf; + int cinw_len; + int retval = FALSE; + int len; + + cinw_len = (int)STRLEN(curbuf->b_p_cinw) + 1; + cinw_buf = alloc(cinw_len); + if (cinw_buf == NULL) + return FALSE; + + 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 + && (!vim_iswordc(line[len]) || !vim_iswordc(line[len - 1]))) + { + retval = TRUE; + break; + } + } + vim_free(cinw_buf); + return retval; +} + +/* + * Skip to the end of a "string" and a 'c' character. + * If there is no string or character, return argument unmodified. + */ + static char_u * +skip_string(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] == NUL) // ' at end of line + break; + i = 2; + if (p[1] == '\\' && p[2] != NUL) // '\n' or '\000' + { + ++i; + while (vim_isdigit(p[i - 1])) // '\000' + ++i; + } + if (p[i - 1] != NUL && 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] != NUL) + ++p; + else if (p[0] == '"') // end of string + break; + } + if (p[0] == '"') + continue; // continue for another string + } + else if (p[0] == 'R' && p[1] == '"') + { + // Raw string: R"[delim](...)[delim]" + char_u *delim = p + 2; + char_u *paren = vim_strchr(delim, '('); + + if (paren != NULL) + { + size_t delim_len = paren - delim; + + for (p += 3; *p; ++p) + if (p[0] == ')' && STRNCMP(p + 1, delim, delim_len) == 0 + && p[delim_len + 1] == '"') + { + p += delim_len + 1; + break; + } + if (p[0] == '"') + continue; // continue for another string + } + } + break; // no string found + } + if (!*p) + --p; // backup from NUL + return p; +} + +/* + * Return TRUE if "line[col]" is inside a C string. + */ + int +is_pos_in_string(char_u *line, colnr_T col) +{ + char_u *p; + + for (p = line; *p && (colnr_T)(p - line) < col; ++p) + p = skip_string(p); + return !((colnr_T)(p - line) <= col); +} + +/* + * 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. + * Return NULL when not inside a comment. + */ + static pos_T * +ind_find_start_comment(void) // XXX +{ + return find_start_comment(curbuf->b_ind_maxcomment); +} + + pos_T * +find_start_comment(int ind_maxcomment) // XXX +{ + pos_T *pos; + int cur_maxcomment = ind_maxcomment; + + for (;;) + { + pos = findmatchlimit(NULL, '*', FM_BACKWARD, cur_maxcomment); + if (pos == NULL) + break; + + // Check if the comment start we found is inside a string. + // If it is then restrict the search to below this line and try again. + if (!is_pos_in_string(ml_get(pos->lnum), pos->col)) + break; + cur_maxcomment = curwin->w_cursor.lnum - pos->lnum - 1; + if (cur_maxcomment <= 0) + { + pos = NULL; + break; + } + } + return pos; +} + +/* + * Find the start of a raw string, not knowing if we are in one right now. + * Search starts at w_cursor.lnum and goes backwards. + * Return NULL when not inside a raw string. + */ + static pos_T * +find_start_rawstring(int ind_maxcomment) // XXX +{ + pos_T *pos; + int cur_maxcomment = ind_maxcomment; + + for (;;) + { + pos = findmatchlimit(NULL, 'R', FM_BACKWARD, cur_maxcomment); + if (pos == NULL) + break; + + // Check if the raw string start we found is inside a string. + // If it is then restrict the search to below this line and try again. + if (!is_pos_in_string(ml_get(pos->lnum), pos->col)) + break; + cur_maxcomment = curwin->w_cursor.lnum - pos->lnum - 1; + if (cur_maxcomment <= 0) + { + pos = NULL; + break; + } + } + return pos; +} + +/* + * Find the start of a comment or raw string, not knowing if we are in a + * comment or raw string right now. + * Search starts at w_cursor.lnum and goes backwards. + * If is_raw is given and returns start of raw_string, sets it to true. + * Return NULL when not inside a comment or raw string. + * "CORS" -> Comment Or Raw String + */ + static pos_T * +ind_find_start_CORS(linenr_T *is_raw) // XXX +{ + static pos_T comment_pos_copy; + pos_T *comment_pos; + pos_T *rs_pos; + + comment_pos = find_start_comment(curbuf->b_ind_maxcomment); + if (comment_pos != NULL) + { + // Need to make a copy of the static pos in findmatchlimit(), + // calling find_start_rawstring() may change it. + comment_pos_copy = *comment_pos; + comment_pos = &comment_pos_copy; + } + rs_pos = find_start_rawstring(curbuf->b_ind_maxcomment); + + // If comment_pos is before rs_pos the raw string is inside the comment. + // If rs_pos is before comment_pos the comment is inside the raw string. + if (comment_pos == NULL || (rs_pos != NULL + && LT_POS(*rs_pos, *comment_pos))) + { + if (is_raw != NULL && rs_pos != NULL) + *is_raw = rs_pos->lnum; + return rs_pos; + } + return comment_pos; +} + + +/* + * Return TRUE if C-indenting is on. + */ + int +cindent_on(void) +{ + return (!p_paste && (curbuf->b_p_cin +#ifdef FEAT_EVAL + || *curbuf->b_p_inde != NUL +#endif + )); +} + +// Find result cache for cpp_baseclass +typedef struct { + int found; + lpos_T lpos; +} cpp_baseclass_cache_T; + +/* + * Skip over white space and C comments within the line. + * Also skip over Perl/shell comments if desired. + */ + static char_u * +cin_skipcomment(char_u *s) +{ + while (*s) + { + char_u *prev_s = s; + + s = skipwhite(s); + + // Perl/shell # comment comment continues until eol. Require a space + // before # to avoid recognizing $#array. + if (curbuf->b_ind_hash_comment != 0 && s != prev_s && *s == '#') + { + s += STRLEN(s); + break; + } + if (*s != '/') + break; + ++s; + if (*s == '/') // slash-slash comment continues till eol + { + s += STRLEN(s); + break; + } + if (*s != '*') + break; + for (++s; *s; ++s) // skip slash-star comment + if (s[0] == '*' && s[1] == '/') + { + s += 2; + break; + } + } + return s; +} + +/* + * Return TRUE if there is no code at *s. White space and comments are + * not considered code. + */ + static int +cin_nocode(char_u *s) +{ + return *cin_skipcomment(s) == NUL; +} + +/* + * Recognize the start of a C or C++ comment. + */ + static int +cin_iscomment(char_u *p) +{ + return (p[0] == '/' && (p[1] == '*' || p[1] == '/')); +} + +/* + * Recognize the start of a "//" comment. + */ + static int +cin_islinecomment(char_u *p) +{ + return (p[0] == '/' && p[1] == '/'); +} + +/* + * Check previous lines for a "//" line comment, skipping over blank lines. + */ + static pos_T * +find_line_comment(void) // XXX +{ + static pos_T pos; + char_u *line; + char_u *p; + + pos = curwin->w_cursor; + while (--pos.lnum > 0) + { + line = ml_get(pos.lnum); + p = skipwhite(line); + if (cin_islinecomment(p)) + { + pos.col = (int)(p - line); + return &pos; + } + if (*p != NUL) + break; + } + return NULL; +} + +/* + * Return TRUE if "text" starts with "key:". + */ + static int +cin_has_js_key(char_u *text) +{ + char_u *s = skipwhite(text); + int quote = -1; + + if (*s == '\'' || *s == '"') + { + // can be 'key': or "key": + quote = *s; + ++s; + } + if (!vim_isIDc(*s)) // need at least one ID character + return FALSE; + + while (vim_isIDc(*s)) + ++s; + if (*s == quote) + ++s; + + s = cin_skipcomment(s); + + // "::" is not a label, it's C++ + return (*s == ':' && s[1] != ':'); +} + +/* + * Check if string matches "label:"; move to character after ':' if true. + * "*s" must point to the start of the label, if there is one. + */ + static int +cin_islabel_skip(char_u **s) +{ + if (!vim_isIDc(**s)) // need at least one ID character + return FALSE; + + while (vim_isIDc(**s)) + (*s)++; + + *s = cin_skipcomment(*s); + + // "::" is not a label, it's C++ + return (**s == ':' && *++*s != ':'); +} + +/* + * Recognize a scope declaration label from the 'cinscopedecls' option. + */ + static int +cin_isscopedecl(char_u *p) +{ + size_t cinsd_len; + char_u *cinsd_buf; + char_u *cinsd; + size_t len; + char_u *skip; + char_u *s = cin_skipcomment(p); + int found = FALSE; + + cinsd_len = STRLEN(curbuf->b_p_cinsd) + 1; + cinsd_buf = alloc(cinsd_len); + if (cinsd_buf == NULL) + return FALSE; + + for (cinsd = curbuf->b_p_cinsd; *cinsd; ) + { + len = copy_option_part(&cinsd, cinsd_buf, (int)cinsd_len, ","); + if (STRNCMP(s, cinsd_buf, len) == 0) + { + skip = cin_skipcomment(s + len); + if (*skip == ':' && skip[1] != ':') + { + found = TRUE; + break; + } + } + } + + vim_free(cinsd_buf); + return found; +} + +/* + * Recognize a preprocessor statement: Any line that starts with '#'. + */ + static int +cin_ispreproc(char_u *s) +{ + if (*skipwhite(s) == '#') + return TRUE; + return FALSE; +} + +/* + * Return TRUE if line "*pp" at "*lnump" is a preprocessor statement or a + * continuation line of a preprocessor statement. Decrease "*lnump" to the + * start and return the line in "*pp". + * Put the amount of indent in "*amount". + */ + static int +cin_ispreproc_cont(char_u **pp, linenr_T *lnump, int *amount) +{ + char_u *line = *pp; + linenr_T lnum = *lnump; + int retval = FALSE; + int candidate_amount = *amount; + + if (*line != NUL && line[STRLEN(line) - 1] == '\\') + candidate_amount = get_indent_lnum(lnum); + + for (;;) + { + if (cin_ispreproc(line)) + { + retval = TRUE; + *lnump = lnum; + break; + } + if (lnum == 1) + break; + line = ml_get(--lnum); + if (*line == NUL || line[STRLEN(line) - 1] != '\\') + break; + } + + if (lnum != *lnump) + *pp = ml_get(*lnump); + if (retval) + *amount = candidate_amount; + return retval; +} + + static int +cin_iselse( + char_u *p) +{ + if (*p == '}') // accept "} else" + p = cin_skipcomment(p + 1); + return (STRNCMP(p, "else", 4) == 0 && !vim_isIDc(p[4])); +} + +/* + * Recognize a line that starts with '{' or '}', or ends with ';', ',', '{' or + * '}'. + * Don't consider "} else" a terminated line. + * If a line begins with an "else", only consider it terminated if no unmatched + * opening braces follow (handle "else { foo();" correctly). + * Return the character terminating the line (ending char's have precedence if + * both apply in order to determine initializations). + */ + static int +cin_isterminated( + char_u *s, + int incl_open, // include '{' at the end as terminator + int incl_comma) // recognize a trailing comma +{ + char_u found_start = 0; + unsigned n_open = 0; + int is_else = FALSE; + + s = cin_skipcomment(s); + + if (*s == '{' || (*s == '}' && !cin_iselse(s))) + found_start = *s; + + if (!found_start) + is_else = cin_iselse(s); + + while (*s) + { + // skip over comments, "" strings and 'c'haracters + s = skip_string(cin_skipcomment(s)); + if (*s == '}' && n_open > 0) + --n_open; + if ((!is_else || n_open == 0) + && (*s == ';' || *s == '}' || (incl_comma && *s == ',')) + && cin_nocode(s + 1)) + return *s; + else if (*s == '{') + { + if (incl_open && cin_nocode(s + 1)) + return *s; + else + ++n_open; + } + + if (*s) + s++; + } + return found_start; +} + +/* + * Return TRUE when "s" starts with "word" and then a non-ID character. + */ + static int +cin_starts_with(char_u *s, char *word) +{ + int l = (int)STRLEN(word); + + return (STRNCMP(s, word, l) == 0 && !vim_isIDc(s[l])); +} + +/* + * Recognize a "default" switch label. + */ + static int +cin_isdefault(char_u *s) +{ + return (STRNCMP(s, "default", 7) == 0 + && *(s = cin_skipcomment(s + 7)) == ':' + && s[1] != ':'); +} + +/* + * Recognize a switch label: "case .*:" or "default:". + */ + static int +cin_iscase( + char_u *s, + int strict) // Allow relaxed check of case statement for JS +{ + s = cin_skipcomment(s); + if (cin_starts_with(s, "case")) + { + for (s += 4; *s; ++s) + { + s = cin_skipcomment(s); + if (*s == NUL) + break; + if (*s == ':') + { + if (s[1] == ':') // skip over "::" for C++ + ++s; + else + return TRUE; + } + if (*s == '\'' && s[1] && s[2] == '\'') + s += 2; // skip over ':' + else if (*s == '/' && (s[1] == '*' || s[1] == '/')) + return FALSE; // stop at comment + else if (*s == '"') + { + // JS etc. + if (strict) + return FALSE; // stop at string + else + return TRUE; + } + } + return FALSE; + } + + if (cin_isdefault(s)) + return TRUE; + return FALSE; +} + +/* + * Recognize a label: "label:". + * Note: curwin->w_cursor must be where we are looking for the label. + */ + static int +cin_islabel(void) // XXX +{ + char_u *s; + + s = cin_skipcomment(ml_get_curline()); + + // Exclude "default" from labels, since it should be indented + // like a switch label. Same for C++ scope declarations. + if (cin_isdefault(s)) + return FALSE; + if (cin_isscopedecl(s)) + return FALSE; + + if (!cin_islabel_skip(&s)) + return FALSE; + + // Only accept a label if the previous line is terminated or is a case + // label. + pos_T cursor_save; + pos_T *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 or raw string now, skip to the start of + // it. + curwin->w_cursor.col = 0; + if ((trypos = ind_find_start_CORS(NULL)) != NULL) // XXX + curwin->w_cursor = *trypos; + + line = ml_get_curline(); + if (cin_ispreproc(line)) // ignore #defines, #if, etc. + continue; + if (*(line = cin_skipcomment(line)) == NUL) + continue; + + curwin->w_cursor = cursor_save; + if (cin_isterminated(line, TRUE, FALSE) + || cin_isscopedecl(line) + || cin_iscase(line, TRUE) + || (cin_islabel_skip(&line) && cin_nocode(line))) + return TRUE; + return FALSE; + } + curwin->w_cursor = cursor_save; + return TRUE; // label at start of file??? +} + +/* + * Return TRUE if string "s" ends with the string "find", possibly followed by + * white space and comments. Skip strings and comments. + * Ignore "ignore" after "find" if it's not NULL. + */ + static int +cin_ends_in(char_u *s, char_u *find, char_u *ignore) +{ + char_u *p = s; + char_u *r; + int len = (int)STRLEN(find); + + while (*p != NUL) + { + p = cin_skipcomment(p); + if (STRNCMP(p, find, len) == 0) + { + r = skipwhite(p + len); + if (ignore != NULL && STRNCMP(r, ignore, STRLEN(ignore)) == 0) + r = skipwhite(r + STRLEN(ignore)); + if (cin_nocode(r)) + return TRUE; + } + if (*p != NUL) + ++p; + } + return FALSE; +} + +/* + * Recognize structure initialization and enumerations: + * "[typedef] [static|public|protected|private] enum" + * "[typedef] [static|public|protected|private] = {" + */ + static int +cin_isinit(void) +{ + char_u *s; + static char *skip[] = {"static", "public", "protected", "private"}; + + s = cin_skipcomment(ml_get_curline()); + + if (cin_starts_with(s, "typedef")) + s = cin_skipcomment(s + 7); + + for (;;) + { + int i, l; + + for (i = 0; i < (int)ARRAY_LENGTH(skip); ++i) + { + l = (int)strlen(skip[i]); + if (cin_starts_with(s, skip[i])) + { + s = cin_skipcomment(s + l); + l = 0; + break; + } + } + if (l != 0) + break; + } + + if (cin_starts_with(s, "enum")) + return TRUE; + + if (cin_ends_in(s, (char_u *)"=", (char_u *)"{")) + return TRUE; + + return FALSE; +} + +// Maximum number of lines to search back for a "namespace" line. +#define FIND_NAMESPACE_LIM 20 + +/* + * Recognize a "namespace" scope declaration. + */ + static int +cin_is_cpp_namespace(char_u *s) +{ + char_u *p; + int has_name = FALSE; + int has_name_start = FALSE; + + s = cin_skipcomment(s); + + // skip over "inline" and "export" in any order + while ((STRNCMP(s, "inline", 6) == 0 || STRNCMP(s, "export", 6) == 0) + && (s[6] == NUL || !vim_iswordc(s[6]))) + s = cin_skipcomment(skipwhite(s + 6)); + + if (STRNCMP(s, "namespace", 9) == 0 && (s[9] == NUL || !vim_iswordc(s[9]))) + { + p = cin_skipcomment(skipwhite(s + 9)); + while (*p != NUL) + { + if (VIM_ISWHITE(*p)) + { + has_name = TRUE; // found end of a name + p = cin_skipcomment(skipwhite(p)); + } + else if (*p == '{') + { + break; + } + else if (vim_iswordc(*p)) + { + has_name_start = TRUE; + if (has_name) + return FALSE; // word character after skipping past name + ++p; + } + else if (p[0] == ':' && p[1] == ':' && vim_iswordc(p[2])) + { + if (!has_name_start || has_name) + return FALSE; + // C++ 17 nested namespace + p += 3; + } + else + { + return FALSE; + } + } + return TRUE; + } + return FALSE; +} + +/* + * Recognize a `extern "C"` or `extern "C++"` linkage specifications. + */ + static int +cin_is_cpp_extern_c(char_u *s) +{ + char_u *p; + int has_string_literal = FALSE; + + s = cin_skipcomment(s); + if (STRNCMP(s, "extern", 6) == 0 && (s[6] == NUL || !vim_iswordc(s[6]))) + { + p = cin_skipcomment(skipwhite(s + 6)); + while (*p != NUL) + { + if (VIM_ISWHITE(*p)) + { + p = cin_skipcomment(skipwhite(p)); + } + else if (*p == '{') + { + break; + } + else if (p[0] == '"' && p[1] == 'C' && p[2] == '"') + { + if (has_string_literal) + return FALSE; + has_string_literal = TRUE; + p += 3; + } + else if (p[0] == '"' && p[1] == 'C' && p[2] == '+' && p[3] == '+' + && p[4] == '"') + { + if (has_string_literal) + return FALSE; + has_string_literal = TRUE; + p += 5; + } + else + { + return FALSE; + } + } + return has_string_literal ? TRUE : FALSE; + } + return FALSE; +} + +/* + * 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(char_u *l) +{ + for ( ; *l; ++l) + { + if (*l == ':') + { + if (l[1] == ':') // skip over "::" for C++ + ++l; + else if (!cin_iscase(l + 1, FALSE)) + break; + } + else if (*l == '\'' && l[1] && l[2] == '\'') + l += 2; // skip over 'x' + } + if (*l == NUL) + return NULL; + l = cin_skipcomment(l + 1); + if (*l == NUL) + 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 (linenr_T lnum) // XXX +{ + char_u *l; + pos_T fp; + colnr_T col; + char_u *p; + + l = ml_get(lnum); + p = after_label(l); + if (p == NULL) + return 0; + + fp.col = (colnr_T)(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) in "pp". + * label: if (asdf && asdfasdf) + * ^ + */ + static int +skip_label(linenr_T lnum, char_u **pp) +{ + char_u *l; + int amount; + pos_T cursor_save; + + cursor_save = curwin->w_cursor; + curwin->w_cursor.lnum = lnum; + l = ml_get_curline(); + // XXX + if (cin_iscase(l, FALSE) || cin_isscopedecl(l) || cin_islabel()) + { + 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; +} + +/* + * Return the indent of the first variable name after a type in a declaration. + * int a, indent of "a" + * static struct foo b, indent of "b" + * enum bla c, indent of "c" + * Returns zero when it doesn't look like a declaration. + */ + static int +cin_first_id_amount(void) +{ + char_u *line, *p, *s; + int len; + pos_T fp; + colnr_T col; + + line = ml_get_curline(); + p = skipwhite(line); + len = (int)(skiptowhite(p) - p); + if (len == 6 && STRNCMP(p, "static", 6) == 0) + { + p = skipwhite(p + 6); + len = (int)(skiptowhite(p) - p); + } + if (len == 6 && STRNCMP(p, "struct", 6) == 0) + p = skipwhite(p + 6); + else if (len == 4 && STRNCMP(p, "enum", 4) == 0) + p = skipwhite(p + 4); + else if ((len == 8 && STRNCMP(p, "unsigned", 8) == 0) + || (len == 6 && STRNCMP(p, "signed", 6) == 0)) + { + s = skipwhite(p + len); + if ((STRNCMP(s, "int", 3) == 0 && VIM_ISWHITE(s[3])) + || (STRNCMP(s, "long", 4) == 0 && VIM_ISWHITE(s[4])) + || (STRNCMP(s, "short", 5) == 0 && VIM_ISWHITE(s[5])) + || (STRNCMP(s, "char", 4) == 0 && VIM_ISWHITE(s[4]))) + p = s; + } + for (len = 0; vim_isIDc(p[len]); ++len) + ; + if (len == 0 || !VIM_ISWHITE(p[len]) || cin_nocode(p)) + return 0; + + p = skipwhite(p + len); + fp.lnum = curwin->w_cursor.lnum; + fp.col = (colnr_T)(p - line); + getvcol(curwin, &fp, &col, NULL, NULL); + return (int)col; +} + +/* + * Return the indent of the first non-blank after an equal sign. + * char *foo = "here"; + * Return zero if no (useful) equal sign found. + * Return -1 if the line above "lnum" ends in a backslash. + * foo = "asdf\ + * asdf\ + * here"; + */ + static int +cin_get_equal_amount(linenr_T lnum) +{ + char_u *line; + char_u *s; + colnr_T col; + pos_T fp; + + if (lnum > 1) + { + line = ml_get(lnum - 1); + if (*line != NUL && line[STRLEN(line) - 1] == '\\') + return -1; + } + + line = s = ml_get(lnum); + while (*s != NUL && vim_strchr((char_u *)"=;{}\"'", *s) == NULL) + { + if (cin_iscomment(s)) // ignore comments + s = cin_skipcomment(s); + else + ++s; + } + if (*s != '=') + return 0; + + s = skipwhite(s + 1); + if (cin_nocode(s)) + return 0; + + if (*s == '"') // nice alignment for continued strings + ++s; + + fp.lnum = lnum; + fp.col = (colnr_T)(s - line); + getvcol(curwin, &fp, &col, NULL, NULL); + return (int)col; +} + +/* + * Skip strings, chars and comments until at or past "trypos". + * Return the column found. + */ + static int +cin_skip2pos(pos_T *trypos) +{ + char_u *line; + char_u *p; + char_u *new_p; + + p = line = ml_get(trypos->lnum); + while (*p && (colnr_T)(p - line) < trypos->col) + { + if (cin_iscomment(p)) + p = cin_skipcomment(p); + else + { + new_p = skip_string(p); + if (new_p == p) + ++p; + else + p = new_p; + } + } + return (int)(p - line); +} + + static pos_T * +find_match_char(int c, int ind_maxparen) // XXX +{ + pos_T cursor_save; + pos_T *trypos; + static pos_T pos_copy; + int ind_maxp_wk; + + cursor_save = curwin->w_cursor; + ind_maxp_wk = ind_maxparen; +retry: + if ((trypos = findmatchlimit(NULL, c, 0, ind_maxp_wk)) != NULL) + { + // check if the ( is in a // comment + if ((colnr_T)cin_skip2pos(trypos) > trypos->col) + { + ind_maxp_wk = ind_maxparen - (int)(cursor_save.lnum - trypos->lnum); + if (ind_maxp_wk > 0) + { + curwin->w_cursor = *trypos; + curwin->w_cursor.col = 0; // XXX + goto retry; + } + trypos = NULL; + } + else + { + pos_T *trypos_wk; + + pos_copy = *trypos; // copy trypos, findmatch will change it + trypos = &pos_copy; + curwin->w_cursor = *trypos; + if ((trypos_wk = ind_find_start_CORS(NULL)) != NULL) // XXX + { + ind_maxp_wk = ind_maxparen - (int)(cursor_save.lnum + - trypos_wk->lnum); + if (ind_maxp_wk > 0) + { + curwin->w_cursor = *trypos_wk; + goto retry; + } + trypos = NULL; + } + } + } + curwin->w_cursor = cursor_save; + return trypos; +} + +/* + * Find the matching '(', ignoring it if it is in a comment. + * Return NULL if no match found. + */ + static pos_T * +find_match_paren(int ind_maxparen) // XXX +{ + return find_match_char('(', ind_maxparen); +} + +/* + * Set w_cursor.col to the column number of the last unmatched ')' or '{' in + * line "l". "l" must point to the start of the line. + */ + static int +find_last_paren(char_u *l, int start, int end) +{ + int i; + int retval = FALSE; + int open_count = 0; + + curwin->w_cursor.col = 0; // default is start of line + + for (i = 0; l[i] != NUL; i++) + { + i = (int)(cin_skipcomment(l + i) - l); // ignore parens in comments + i = (int)(skip_string(l + i) - l); // ignore parens in quotes + if (l[i] == start) + ++open_count; + else if (l[i] == end) + { + if (open_count > 0) + --open_count; + else + { + curwin->w_cursor.col = i; + retval = TRUE; + } + } + } + return retval; +} + +/* + * 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. + * When a line ends in a comma we continue looking in the next line. + * "sp" points to a string with the line. When looking at other lines it must + * be restored to the line. When it's NULL fetch lines here. + * "first_lnum" is where we start looking. + * "min_lnum" is the line before which we will not be looking. + */ + static int +cin_isfuncdecl( + char_u **sp, + linenr_T first_lnum, + linenr_T min_lnum) +{ + char_u *s; + linenr_T lnum = first_lnum; + linenr_T save_lnum = curwin->w_cursor.lnum; + int retval = FALSE; + pos_T *trypos; + int just_started = TRUE; + + if (sp == NULL) + s = ml_get(lnum); + else + s = *sp; + + curwin->w_cursor.lnum = lnum; + if (find_last_paren(s, '(', ')') + && (trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) + { + lnum = trypos->lnum; + if (lnum < min_lnum) + { + curwin->w_cursor.lnum = save_lnum; + return FALSE; + } + + s = ml_get(lnum); + } + curwin->w_cursor.lnum = save_lnum; + + // Ignore line starting with #. + if (cin_ispreproc(s)) + return FALSE; + + while (*s && *s != '(' && *s != ';' && *s != '\'' && *s != '"') + { + if (cin_iscomment(s)) // ignore comments + s = cin_skipcomment(s); + else if (*s == ':') + { + if (*(s + 1) == ':') + s += 2; + else + // To avoid a mistake in the following situation: + // A::A(int a, int b) + // : a(0) // <--not a function decl + // , b(0) + // {... + return FALSE; + } + else + ++s; + } + if (*s != '(') + return FALSE; // ';', ' or " before any () or no '(' + + while (*s && *s != ';' && *s != '\'' && *s != '"') + { + if (*s == ')' && cin_nocode(s + 1)) + { + // ')' at the end: may have found a match + // Check for the previous line not to end in a backslash: + // #if defined(x) && {backslash} + // defined(y) + lnum = first_lnum - 1; + s = ml_get(lnum); + if (*s == NUL || s[STRLEN(s) - 1] != '\\') + retval = TRUE; + goto done; + } + if ((*s == ',' && cin_nocode(s + 1)) || s[1] == NUL || cin_nocode(s)) + { + int comma = (*s == ','); + + // ',' at the end: continue looking in the next line. + // At the end: check for ',' in the next line, for this style: + // func(arg1 + // , arg2) + for (;;) + { + if (lnum >= curbuf->b_ml.ml_line_count) + break; + s = ml_get(++lnum); + if (!cin_ispreproc(s)) + break; + } + if (lnum >= curbuf->b_ml.ml_line_count) + break; + // Require a comma at end of the line or a comma or ')' at the + // start of next line. + s = skipwhite(s); + if (!just_started && (!comma && *s != ',' && *s != ')')) + break; + just_started = FALSE; + } + else if (cin_iscomment(s)) // ignore comments + s = cin_skipcomment(s); + else + { + ++s; + just_started = FALSE; + } + } + +done: + if (lnum != first_lnum && sp != NULL) + *sp = ml_get(first_lnum); + + return retval; +} + + static int +cin_isif(char_u *p) +{ + return (STRNCMP(p, "if", 2) == 0 && !vim_isIDc(p[2])); +} + + static int +cin_isdo(char_u *p) +{ + return (STRNCMP(p, "do", 2) == 0 && !vim_isIDc(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 +cin_iswhileofdo (char_u *p, linenr_T lnum) // XXX +{ + pos_T cursor_save; + pos_T *trypos; + int retval = FALSE; + + p = cin_skipcomment(p); + if (*p == '}') // accept "} while (cond);" + p = cin_skipcomment(p + 1); + if (cin_starts_with(p, "while")) + { + cursor_save = curwin->w_cursor; + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + p = ml_get_curline(); + while (*p && *p != 'w') // skip any '}', until the 'w' of the "while" + { + ++p; + ++curwin->w_cursor.col; + } + if ((trypos = findmatchlimit(NULL, 0, 0, + curbuf->b_ind_maxparen)) != NULL + && *cin_skipcomment(ml_get_pos(trypos) + 1) == ';') + retval = TRUE; + curwin->w_cursor = cursor_save; + } + return retval; +} + +/* + * Check whether in "p" there is an "if", "for" or "while" before "*poffset". + * Return 0 if there is none. + * Otherwise return !0 and update "*poffset" to point to the place where the + * string was found. + */ + static int +cin_is_if_for_while_before_offset(char_u *line, int *poffset) +{ + int offset = *poffset; + + if (offset-- < 2) + return 0; + while (offset > 2 && VIM_ISWHITE(line[offset])) + --offset; + + offset -= 1; + if (!STRNCMP(line + offset, "if", 2)) + goto probablyFound; + + if (offset >= 1) + { + offset -= 1; + if (!STRNCMP(line + offset, "for", 3)) + goto probablyFound; + + if (offset >= 2) + { + offset -= 2; + if (!STRNCMP(line + offset, "while", 5)) + goto probablyFound; + } + } + return 0; + +probablyFound: + if (!offset || !vim_isIDc(line[offset - 1])) + { + *poffset = offset; + return 1; + } + return 0; +} + +/* + * Return TRUE if we are at the end of a do-while. + * do + * nothing; + * while (foo + * && bar); <-- here + * Adjust the cursor to the line with "while". + */ + static int +cin_iswhileofdo_end(int terminated) +{ + char_u *line; + char_u *p; + char_u *s; + pos_T *trypos; + int i; + + if (terminated != ';') // there must be a ';' at the end + return FALSE; + + p = line = ml_get_curline(); + while (*p != NUL) + { + p = cin_skipcomment(p); + if (*p == ')') + { + s = skipwhite(p + 1); + if (*s == ';' && cin_nocode(s + 1)) + { + // Found ");" at end of the line, now check there is "while" + // before the matching '('. XXX + i = (int)(p - line); + curwin->w_cursor.col = i; + trypos = find_match_paren(curbuf->b_ind_maxparen); + if (trypos != NULL) + { + s = cin_skipcomment(ml_get(trypos->lnum)); + if (*s == '}') // accept "} while (cond);" + s = cin_skipcomment(s + 1); + if (cin_starts_with(s, "while")) + { + curwin->w_cursor.lnum = trypos->lnum; + return TRUE; + } + } + + // Searching may have made "line" invalid, get it again. + line = ml_get_curline(); + p = line + i; + } + } + if (*p != NUL) + ++p; + } + return FALSE; +} + + static int +cin_isbreak(char_u *p) +{ + return (STRNCMP(p, "break", 5) == 0 && !vim_isIDc(p[5])); +} + +/* + * Find the position of a C++ base-class declaration or + * constructor-initialization. eg: + * + * class MyClass : + * baseClass <-- here + * class MyClass : public baseClass, + * anotherBaseClass <-- here (should probably lineup ??) + * MyClass::MyClass(...) : + * baseClass(...) <-- here (constructor-initialization) + * + * This is a lot of guessing. Watch out for "cond ? func() : foo". + */ + static int +cin_is_cpp_baseclass( + cpp_baseclass_cache_T *cached) // input and output +{ + lpos_T *pos = &cached->lpos; // find position + char_u *s; + int class_or_struct, lookfor_ctor_init, cpp_base_class; + linenr_T lnum = curwin->w_cursor.lnum; + char_u *line = ml_get_curline(); + + if (pos->lnum <= lnum) + return cached->found; // Use the cached result + + pos->col = 0; + + s = skipwhite(line); + if (*s == '#') // skip #define FOO x ? (x) : x + return FALSE; + s = cin_skipcomment(s); + if (*s == NUL) + return FALSE; + + cpp_base_class = lookfor_ctor_init = class_or_struct = FALSE; + + // Search for a line starting with '#', empty, ending in ';' or containing + // '{' or '}' and start below it. This handles the following situations: + // a = cond ? + // func() : + // asdf; + // func::foo() + // : something + // {} + // Foo::Foo (int one, int two) + // : something(4), + // somethingelse(3) + // {} + while (lnum > 1) + { + line = ml_get(lnum - 1); + s = skipwhite(line); + if (*s == '#' || *s == NUL) + break; + while (*s != NUL) + { + s = cin_skipcomment(s); + if (*s == '{' || *s == '}' + || (*s == ';' && cin_nocode(s + 1))) + break; + if (*s != NUL) + ++s; + } + if (*s != NUL) + break; + --lnum; + } + + pos->lnum = lnum; + line = ml_get(lnum); + s = line; + for (;;) + { + if (*s == NUL) + { + if (lnum == curwin->w_cursor.lnum) + break; + // Continue in the cursor line. + line = ml_get(++lnum); + s = line; + } + if (s == line) + { + // don't recognize "case (foo):" as a baseclass + if (cin_iscase(s, FALSE)) + break; + s = cin_skipcomment(line); + if (*s == NUL) + continue; + } + + if (s[0] == '"' || (s[0] == 'R' && s[1] == '"')) + s = skip_string(s) + 1; + else if (s[0] == ':') + { + if (s[1] == ':') + { + // skip double colon. It can't be a constructor + // initialization any more + lookfor_ctor_init = FALSE; + s = cin_skipcomment(s + 2); + } + else if (lookfor_ctor_init || class_or_struct) + { + // we have something found, that looks like the start of + // cpp-base-class-declaration or constructor-initialization + cpp_base_class = TRUE; + lookfor_ctor_init = class_or_struct = FALSE; + pos->col = 0; + s = cin_skipcomment(s + 1); + } + else + s = cin_skipcomment(s + 1); + } + else if ((STRNCMP(s, "class", 5) == 0 && !vim_isIDc(s[5])) + || (STRNCMP(s, "struct", 6) == 0 && !vim_isIDc(s[6]))) + { + class_or_struct = TRUE; + lookfor_ctor_init = FALSE; + + if (*s == 'c') + s = cin_skipcomment(s + 5); + else + s = cin_skipcomment(s + 6); + } + else + { + if (s[0] == '{' || s[0] == '}' || s[0] == ';') + { + cpp_base_class = lookfor_ctor_init = class_or_struct = FALSE; + } + else if (s[0] == ')') + { + // Constructor-initialization is assumed if we come across + // something like "):" + class_or_struct = FALSE; + lookfor_ctor_init = TRUE; + } + else if (s[0] == '?') + { + // Avoid seeing '() :' after '?' as constructor init. + return FALSE; + } + else if (!vim_isIDc(s[0])) + { + // if it is not an identifier, we are wrong + class_or_struct = FALSE; + lookfor_ctor_init = FALSE; + } + else if (pos->col == 0) + { + // it can't be a constructor-initialization any more + lookfor_ctor_init = FALSE; + + // the first statement starts here: lineup with this one... + if (cpp_base_class) + pos->col = (colnr_T)(s - line); + } + + // When the line ends in a comma don't align with it. + if (lnum == curwin->w_cursor.lnum && *s == ',' && cin_nocode(s + 1)) + pos->col = 0; + + s = cin_skipcomment(s + 1); + } + } + + cached->found = cpp_base_class; + if (cpp_base_class) + pos->lnum = lnum; + return cpp_base_class; +} + + static int +get_baseclass_amount(int col) +{ + int amount; + colnr_T vcol; + pos_T *trypos; + + if (col == 0) + { + amount = get_indent(); + if (find_last_paren(ml_get_curline(), '(', ')') + && (trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) + amount = get_indent_lnum(trypos->lnum); // XXX + if (!cin_ends_in(ml_get_curline(), (char_u *)",", NULL)) + amount += curbuf->b_ind_cpp_baseclass; + } + else + { + curwin->w_cursor.col = col; + getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL); + amount = (int)vcol; + } + if (amount < curbuf->b_ind_cpp_baseclass) + amount = curbuf->b_ind_cpp_baseclass; + return amount; +} + +/* + * Find the '{' at the start of the block we are in. + * Return NULL if no match found. + * Ignore a '{' that is in a comment, makes indenting the next three lines + * work. + */ +// foo() +// { +// } + + static pos_T * +find_start_brace(void) // XXX +{ + pos_T cursor_save; + pos_T *trypos; + pos_T *pos; + static pos_T pos_copy; + + cursor_save = curwin->w_cursor; + while ((trypos = findmatchlimit(NULL, '{', FM_BLOCKSTOP, 0)) != NULL) + { + pos_copy = *trypos; // copy pos_T, next findmatch will change it + trypos = &pos_copy; + curwin->w_cursor = *trypos; + pos = NULL; + // ignore the { if it's in a // or / * * / comment + if ((colnr_T)cin_skip2pos(trypos) == trypos->col + && (pos = ind_find_start_CORS(NULL)) == NULL) // XXX + break; + if (pos != NULL) + curwin->w_cursor = *pos; + } + curwin->w_cursor = cursor_save; + return trypos; +} + +/* + * Find the matching '(', ignoring it if it is in a comment or before an + * unmatched {. + * Return NULL if no match found. + */ + static pos_T * +find_match_paren_after_brace (int ind_maxparen) // XXX +{ + pos_T *trypos = find_match_paren(ind_maxparen); + + if (trypos == NULL) + return NULL; + + pos_T *tryposBrace = find_start_brace(); + + // If both an unmatched '(' and '{' is found. Ignore the '(' + // position if the '{' is further down. + if (tryposBrace != NULL + && (trypos->lnum != tryposBrace->lnum + ? trypos->lnum < tryposBrace->lnum + : trypos->col < tryposBrace->col)) + trypos = NULL; + return trypos; +} + +/* + * Return ind_maxparen corrected for the difference in line number between the + * cursor position and "startpos". This makes sure that searching for a + * matching paren above the cursor line doesn't find a match because of + * looking a few lines further. + */ + static int +corr_ind_maxparen(pos_T *startpos) +{ + long n = (long)startpos->lnum - (long)curwin->w_cursor.lnum; + + if (n > 0 && n < curbuf->b_ind_maxparen / 2) + return curbuf->b_ind_maxparen - (int)n; + return curbuf->b_ind_maxparen; +} + +/* + * Parse 'cinoptions' and set the values in "curbuf". + * Must be called when 'cinoptions', 'shiftwidth' and/or 'tabstop' changes. + */ + void +parse_cino(buf_T *buf) +{ + char_u *p; + char_u *l; + char_u *digits; + vimlong_T n; + int divider; + int fraction = 0; + int sw; + long t = get_sw_value(buf); + + // needed for cino-(, it will be multiplied by 2 again + if (t > INT_MAX / 2) + sw = INT_MAX / 2; + else + sw = (int)t; + + // Set the default values. + + // Spaces from a block's opening brace the prevailing indent for that + // block should be. + buf->b_ind_level = sw; + + // Spaces from the edge of the line an open brace that's at the end of a + // line is imagined to be. + buf->b_ind_open_imag = 0; + + // Spaces from the prevailing indent for a line that is not preceded by + // an opening brace. + buf->b_ind_no_brace = 0; + + // Column where the first { of a function should be located }. + buf->b_ind_first_open = 0; + + // Spaces from the prevailing indent a leftmost open brace should be + // located. + buf->b_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. + buf->b_ind_close_extra = 0; + + // Spaces from the edge of the line an open brace sitting in the leftmost + // column is imagined to be. + buf->b_ind_open_left_imag = 0; + + // Spaces jump labels should be shifted to the left if N is non-negative, + // otherwise the jump label will be put to column 1. + buf->b_ind_jump_label = -1; + + // Spaces from the switch() indent a "case xx" label should be located. + buf->b_ind_case = sw; + + // Spaces from the "case xx:" code after a switch() should be located. + buf->b_ind_case_code = sw; + + // Lineup break at end of case in switch() with case label. + buf->b_ind_case_break = 0; + + // Spaces from the class declaration indent a scope declaration label + // should be located. + buf->b_ind_scopedecl = sw; + + // Spaces from the scope declaration label code should be located. + buf->b_ind_scopedecl_code = sw; + + // Amount K&R-style parameters should be indented. + buf->b_ind_param = sw; + + // Amount a function type spec should be indented. + buf->b_ind_func_type = sw; + + // Amount a cpp base class declaration or constructor initialization + // should be indented. + buf->b_ind_cpp_baseclass = sw; + + // additional spaces beyond the prevailing indent a continuation line + // should be located. + buf->b_ind_continuation = sw; + + // Spaces from the indent of the line with an unclosed parenthesis. + buf->b_ind_unclosed = sw * 2; + + // Spaces from the indent of the line with an unclosed parenthesis, which + // itself is also unclosed. + buf->b_ind_unclosed2 = sw; + + // Suppress ignoring spaces from the indent of a line starting with an + // unclosed parenthesis. + buf->b_ind_unclosed_noignore = 0; + + // If the opening paren is the last nonwhite character on the line, and + // b_ind_unclosed_wrapped is nonzero, use this indent relative to the outer + // context (for very long lines). + buf->b_ind_unclosed_wrapped = 0; + + // Suppress ignoring white space when lining up with the character after + // an unclosed parenthesis. + buf->b_ind_unclosed_whiteok = 0; + + // Indent a closing parenthesis under the line start of the matching + // opening parenthesis. + buf->b_ind_matching_paren = 0; + + // Indent a closing parenthesis under the previous line. + buf->b_ind_paren_prev = 0; + + // Extra indent for comments. + buf->b_ind_comment = 0; + + // Spaces from the comment opener when there is nothing after it. + buf->b_ind_in_comment = 3; + + // Boolean: if non-zero, use b_ind_in_comment even if there is something + // after the comment opener. + buf->b_ind_in_comment2 = 0; + + // Max lines to search for an open paren. + buf->b_ind_maxparen = 20; + + // Max lines to search for an open comment. + buf->b_ind_maxcomment = 70; + + // Handle braces for java code. + buf->b_ind_java = 0; + + // Not to confuse JS object properties with labels. + buf->b_ind_js = 0; + + // Handle blocked cases correctly. + buf->b_ind_keep_case_label = 0; + + // Handle C++ namespace. + buf->b_ind_cpp_namespace = 0; + + // Handle continuation lines containing conditions of if (), for () and + // while (). + buf->b_ind_if_for_while = 0; + + // indentation for # comments + buf->b_ind_hash_comment = 0; + + // Handle C++ extern "C" or "C++" + buf->b_ind_cpp_extern_c = 0; + + // Handle C #pragma directives + buf->b_ind_pragma = 0; + + for (p = buf->b_p_cino; *p; ) + { + l = p++; + if (*p == '-') + ++p; + digits = p; // remember where the digits start + n = getdigits(&p); + divider = 0; + if (*p == '.') // ".5s" means a fraction + { + fraction = atol((char *)++p); + while (VIM_ISDIGIT(*p)) + { + ++p; + if (divider) + divider *= 10; + else + divider = 10; + } + } + if (*p == 's') // "2s" means two times 'shiftwidth' + { + if (p == digits) + n = sw; // just "s" is one 'shiftwidth' + else + { + n *= sw; + if (divider) + n += ((vimlong_T)sw * fraction + divider / 2) / divider; + } + ++p; + } + if (l[1] == '-') + n = -n; + + n = trim_to_int(n); + + // When adding an entry here, also update the default 'cinoptions' in + // doc/indent.txt, and add explanation for it! + switch (*l) + { + case '>': buf->b_ind_level = (int)n; break; + case 'e': buf->b_ind_open_imag = (int)n; break; + case 'n': buf->b_ind_no_brace = (int)n; break; + case 'f': buf->b_ind_first_open = (int)n; break; + case '{': buf->b_ind_open_extra = (int)n; break; + case '}': buf->b_ind_close_extra = (int)n; break; + case '^': buf->b_ind_open_left_imag = (int)n; break; + case 'L': buf->b_ind_jump_label = (int)n; break; + case ':': buf->b_ind_case = (int)n; break; + case '=': buf->b_ind_case_code = (int)n; break; + case 'b': buf->b_ind_case_break = (int)n; break; + case 'p': buf->b_ind_param = (int)n; break; + case 't': buf->b_ind_func_type = (int)n; break; + case '/': buf->b_ind_comment = (int)n; break; + case 'c': buf->b_ind_in_comment = (int)n; break; + case 'C': buf->b_ind_in_comment2 = (int)n; break; + case 'i': buf->b_ind_cpp_baseclass = (int)n; break; + case '+': buf->b_ind_continuation = (int)n; break; + case '(': buf->b_ind_unclosed = (int)n; break; + case 'u': buf->b_ind_unclosed2 = (int)n; break; + case 'U': buf->b_ind_unclosed_noignore = (int)n; break; + case 'W': buf->b_ind_unclosed_wrapped = (int)n; break; + case 'w': buf->b_ind_unclosed_whiteok = (int)n; break; + case 'm': buf->b_ind_matching_paren = (int)n; break; + case 'M': buf->b_ind_paren_prev = (int)n; break; + case ')': buf->b_ind_maxparen = (int)n; break; + case '*': buf->b_ind_maxcomment = (int)n; break; + case 'g': buf->b_ind_scopedecl = (int)n; break; + case 'h': buf->b_ind_scopedecl_code = (int)n; break; + case 'j': buf->b_ind_java = (int)n; break; + case 'J': buf->b_ind_js = (int)n; break; + case 'l': buf->b_ind_keep_case_label = (int)n; break; + case '#': buf->b_ind_hash_comment = (int)n; break; + case 'N': buf->b_ind_cpp_namespace = (int)n; break; + case 'k': buf->b_ind_if_for_while = (int)n; break; + case 'E': buf->b_ind_cpp_extern_c = (int)n; break; + case 'P': buf->b_ind_pragma = (int)n; break; + } + if (*p == ',') + ++p; + } +} + + static int +find_match(int lookfor, linenr_T ourscope) +{ + char_u *look; + pos_T *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 = cin_skipcomment(ml_get_curline()); + if (cin_iselse(look) + || cin_isif(look) + || cin_isdo(look) // XXX + || cin_iswhileofdo(look, curwin->w_cursor.lnum)) + { + // if we've gone outside the braces entirely, + // we must be out of scope... + theirscope = find_start_brace(); // 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 = cin_skipcomment(ml_get_curline()); + if (cin_iselse(look)) + { + mightbeif = cin_skipcomment(look + 4); + if (!cin_isif(mightbeif)) + ++elselevel; + continue; + } + + // if it was a "while" then we need to go back to + // another "do", so increment whilelevel. XXX + if (cin_iswhileofdo(look, curwin->w_cursor.lnum)) + { + ++whilelevel; + continue; + } + + // If it's an "if" decrement elselevel + look = cin_skipcomment(ml_get_curline()); + if (cin_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 (cin_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; +} + +/* + * Return the desired indent for C code. + * Return -1 if the indent should be left alone (inside a raw string). + */ + int +get_c_indent(void) +{ + pos_T cur_curpos; + int amount; + int scope_amount; + int cur_amount = MAXCOL; + colnr_T col; + char_u *theline; + char_u *linecopy; + pos_T *trypos; + pos_T *comment_pos; + pos_T *tryposBrace = NULL; + pos_T tryposCopy; + pos_T our_paren_pos; + char_u *start; + int start_brace; +#define BRACE_IN_COL0 1 // '{' is in column 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; + char_u terminated; + int lookfor; + int whilelevel; + linenr_T lnum; + int n; + int iscase; + int lookfor_break; + int lookfor_cpp_namespace = FALSE; + int cont_amount = 0; // amount for continuation line + int original_line_islabel; + int added_to_amount = 0; + int js_cur_has_key = 0; + linenr_T raw_string_start = 0; + cpp_baseclass_cache_T cache_cpp_baseclass = { FALSE, { MAXLNUM, 0 } }; + + // make a copy, value is changed below + int ind_continuation = curbuf->b_ind_continuation; + + // remember where the cursor was when we started + cur_curpos = curwin->w_cursor; + + // if we are at line 1 zero indent is fine, right? + if (cur_curpos.lnum == 1) + return 0; + + // Get a copy of the current contents of the line. + // This is required, because only the most recent line obtained with + // ml_get is valid! + linecopy = vim_strsave(ml_get(cur_curpos.lnum)); + if (linecopy == NULL) + return 0; + + // In insert mode and the cursor is on a ')' truncate the line at the + // cursor position. We don't want to line up with the matching '(' when + // inserting new stuff. + // For unknown reasons the cursor might be past the end of the line, thus + // check for that. + if ((State & MODE_INSERT) + && curwin->w_cursor.col < (colnr_T)STRLEN(linecopy) + && 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; + + original_line_islabel = cin_islabel(); // XXX + + // If we are inside a raw string don't change the indent. + // Ignore a raw string inside a comment. + comment_pos = ind_find_start_comment(); + if (comment_pos != NULL) + { + // findmatchlimit() static pos is overwritten, make a copy + tryposCopy = *comment_pos; + comment_pos = &tryposCopy; + } + trypos = find_start_rawstring(curbuf->b_ind_maxcomment); + if (trypos != NULL && (comment_pos == NULL + || LT_POS(*trypos, *comment_pos))) + { + amount = -1; + goto laterend; + } + + // #defines and so on go at the left when included in 'cinkeys', + // excluding pragmas when customized in 'cinoptions' + if (*theline == '#' && (*linecopy == '#' || in_cinkeys('#', ' ', TRUE))) + { + char_u *directive = skipwhite(theline + 1); + if (curbuf->b_ind_pragma == 0 || STRNCMP(directive, "pragma", 6) != 0) + { + amount = curbuf->b_ind_hash_comment; + goto theend; + } + } + + // Is it a non-case label? Then that goes at the left margin too unless: + // - JS flag is set. + // - 'L' item has a positive value. + if (original_line_islabel && !curbuf->b_ind_js + && curbuf->b_ind_jump_label < 0) + { + amount = 0; + goto theend; + } + + // If we're inside a "//" comment and there is a "//" comment in a + // previous line, lineup with that one. + if (cin_islinecomment(theline)) + { + pos_T linecomment_pos; + + trypos = find_line_comment(); // XXX + if (trypos == NULL && curwin->w_cursor.lnum > 1) + { + // There may be a statement before the comment, search from the end + // of the line for a comment start. + linecomment_pos.col = + check_linecomment(ml_get(curwin->w_cursor.lnum - 1)); + if (linecomment_pos.col != MAXCOL) + { + trypos = &linecomment_pos; + trypos->lnum = curwin->w_cursor.lnum - 1; + } + } + if (trypos != NULL) + { + // find how indented the line beginning the comment is + getvcol(curwin, trypos, &col, NULL, NULL); + amount = col; + goto theend; + } + } + + // If we're inside a comment and not looking at the start of the + // comment, try using the 'comments' option. + if (!cin_iscomment(theline) && comment_pos != NULL) // XXX + { + int lead_start_len = 2; + int lead_middle_len = 1; + char_u lead_start[COM_MAX_LEN]; // start-comment string + char_u lead_middle[COM_MAX_LEN]; // middle-comment string + char_u lead_end[COM_MAX_LEN]; // end-comment string + char_u *p; + int start_align = 0; + int start_off = 0; + int done = FALSE; + + // find how indented the line beginning the comment is + getvcol(curwin, comment_pos, &col, NULL, NULL); + amount = col; + *lead_start = NUL; + *lead_middle = NUL; + + p = curbuf->b_p_com; + while (*p != NUL) + { + int align = 0; + int off = 0; + int what = 0; + + while (*p != NUL && *p != ':') + { + if (*p == COM_START || *p == COM_END || *p == COM_MIDDLE) + what = *p++; + else if (*p == COM_LEFT || *p == COM_RIGHT) + align = *p++; + else if (VIM_ISDIGIT(*p) || *p == '-') + off = getdigits(&p); + else + ++p; + } + + if (*p == ':') + ++p; + (void)copy_option_part(&p, lead_end, COM_MAX_LEN, ","); + if (what == COM_START) + { + STRCPY(lead_start, lead_end); + lead_start_len = (int)STRLEN(lead_start); + start_off = off; + start_align = align; + } + else if (what == COM_MIDDLE) + { + STRCPY(lead_middle, lead_end); + lead_middle_len = (int)STRLEN(lead_middle); + } + else if (what == COM_END) + { + // If our line starts with the middle comment string, line it + // up with the comment opener per the 'comments' option. + if (STRNCMP(theline, lead_middle, lead_middle_len) == 0 + && STRNCMP(theline, lead_end, STRLEN(lead_end)) != 0) + { + done = TRUE; + if (curwin->w_cursor.lnum > 1) + { + // If the start comment string matches in the previous + // line, use the indent of that line plus offset. If + // the middle comment string matches in the previous + // line, use the indent of that line. XXX + look = skipwhite(ml_get(curwin->w_cursor.lnum - 1)); + if (STRNCMP(look, lead_start, lead_start_len) == 0) + amount = get_indent_lnum(curwin->w_cursor.lnum - 1); + else if (STRNCMP(look, lead_middle, + lead_middle_len) == 0) + { + amount = get_indent_lnum(curwin->w_cursor.lnum - 1); + break; + } + // If the start comment string doesn't match with the + // start of the comment, skip this entry. XXX + else if (STRNCMP(ml_get(comment_pos->lnum) + + comment_pos->col, + lead_start, lead_start_len) != 0) + continue; + } + if (start_off != 0) + amount += start_off; + else if (start_align == COM_RIGHT) + amount += vim_strsize(lead_start) + - vim_strsize(lead_middle); + break; + } + + // If our line starts with the end comment string, line it up + // with the middle comment + if (STRNCMP(theline, lead_middle, lead_middle_len) != 0 + && STRNCMP(theline, lead_end, STRLEN(lead_end)) == 0) + { + amount = get_indent_lnum(curwin->w_cursor.lnum - 1); + // XXX + if (off != 0) + amount += off; + else if (align == COM_RIGHT) + amount += vim_strsize(lead_start) + - vim_strsize(lead_middle); + done = TRUE; + break; + } + } + } + + // 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 (done) + ; + else 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 'cino' has "CO" + // and we are just below the comment opener and there are any + // white characters after it line up with the text after it; + // otherwise, add the amount specified by "c" in 'cino' + amount = -1; + for (lnum = cur_curpos.lnum - 1; lnum > comment_pos->lnum; --lnum) + { + if (linewhite(lnum)) // skip blank lines + continue; + amount = get_indent_lnum(lnum); // XXX + break; + } + if (amount == -1) // use the comment opener + { + if (!curbuf->b_ind_in_comment2) + { + start = ml_get(comment_pos->lnum); + look = start + comment_pos->col + 2; // skip / and * + if (*look != NUL) // if something after it + comment_pos->col = (colnr_T)(skipwhite(look) - start); + } + getvcol(curwin, comment_pos, &col, NULL, NULL); + amount = col; + if (curbuf->b_ind_in_comment2 || *look == NUL) + amount += curbuf->b_ind_in_comment; + } + } + goto theend; + } + + // Are we looking at a ']' that has a match? + if (*skipwhite(theline) == ']' + && (trypos = find_match_char('[', curbuf->b_ind_maxparen)) != NULL) + { + // align with the line containing the '['. + amount = get_indent_lnum(trypos->lnum); + goto theend; + } + + // Are we inside parentheses or braces? XXX + if (((trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL + && curbuf->b_ind_java == 0) + || (tryposBrace = find_start_brace()) != NULL + || trypos != NULL) + { + if (trypos != NULL && tryposBrace != NULL) + { + // Both an unmatched '(' and '{' is found. Use the one which is + // closer to the current cursor position, set the other to NULL. + if (trypos->lnum != tryposBrace->lnum + ? trypos->lnum < tryposBrace->lnum + : trypos->col < tryposBrace->col) + trypos = NULL; + else + tryposBrace = NULL; + } + + if (trypos != 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. + if (theline[0] == ')' && curbuf->b_ind_paren_prev) + { + // Line up with the start of the matching paren line. + amount = get_indent_lnum(curwin->w_cursor.lnum - 1); // XXX + } + else + { + amount = -1; + our_paren_pos = *trypos; + for (lnum = cur_curpos.lnum - 1; lnum > our_paren_pos.lnum; --lnum) + { + l = skipwhite(ml_get(lnum)); + if (cin_nocode(l)) // skip comment lines + continue; + if (cin_ispreproc_cont(&l, &lnum, &amount)) + continue; // ignore #define, #if, etc. + curwin->w_cursor.lnum = lnum; + + // Skip a comment or raw string. XXX + if ((trypos = ind_find_start_CORS(NULL)) != NULL) + { + lnum = trypos->lnum + 1; + continue; + } + + // XXX + if ((trypos = find_match_paren( + corr_ind_maxparen(&cur_curpos))) != NULL + && trypos->lnum == our_paren_pos.lnum + && trypos->col == our_paren_pos.col) + { + amount = get_indent_lnum(lnum); // XXX + + if (theline[0] == ')') + { + if (our_paren_pos.lnum != lnum + && cur_amount > amount) + cur_amount = amount; + amount = -1; + } + break; + } + } + } + + // Line up with line where the matching paren is. XXX + // If the line starts with a '(' or the indent for unclosed + // parentheses is zero, line up with the unclosed parentheses. + if (amount == -1) + { + int ignore_paren_col = 0; + int is_if_for_while = 0; + + if (curbuf->b_ind_if_for_while) + { + // Look for the outermost opening parenthesis on this line + // and check whether it belongs to an "if", "for" or "while". + + pos_T cursor_save = curwin->w_cursor; + pos_T outermost; + char_u *line; + + trypos = &our_paren_pos; + do { + outermost = *trypos; + curwin->w_cursor.lnum = outermost.lnum; + curwin->w_cursor.col = outermost.col; + + trypos = find_match_paren(curbuf->b_ind_maxparen); + } while (trypos && trypos->lnum == outermost.lnum); + + curwin->w_cursor = cursor_save; + + line = ml_get(outermost.lnum); + + is_if_for_while = + cin_is_if_for_while_before_offset(line, &outermost.col); + } + + amount = skip_label(our_paren_pos.lnum, &look); + look = skipwhite(look); + if (*look == '(') + { + linenr_T save_lnum = curwin->w_cursor.lnum; + char_u *line; + int look_col; + + // Ignore a '(' in front of the line that has a match before + // our matching '('. + curwin->w_cursor.lnum = our_paren_pos.lnum; + line = ml_get_curline(); + look_col = (int)(look - line); + curwin->w_cursor.col = look_col + 1; + if ((trypos = findmatchlimit(NULL, ')', 0, + curbuf->b_ind_maxparen)) + != NULL + && trypos->lnum == our_paren_pos.lnum + && trypos->col < our_paren_pos.col) + ignore_paren_col = trypos->col + 1; + + curwin->w_cursor.lnum = save_lnum; + look = ml_get(our_paren_pos.lnum) + look_col; + } + if (theline[0] == ')' || (curbuf->b_ind_unclosed == 0 + && is_if_for_while == 0) + || (!curbuf->b_ind_unclosed_noignore && *look == '(' + && ignore_paren_col == 0)) + { + // If we're looking at a close paren, line up right there; + // otherwise, line up with the next (non-white) character. + // When b_ind_unclosed_wrapped is set and the matching paren is + // the last nonwhite character of the line, use either the + // indent of the current line or the indentation of the next + // outer paren and add b_ind_unclosed_wrapped (for very long + // lines). + if (theline[0] != ')') + { + cur_amount = MAXCOL; + l = ml_get(our_paren_pos.lnum); + if (curbuf->b_ind_unclosed_wrapped + && cin_ends_in(l, (char_u *)"(", NULL)) + { + // look for opening unmatched paren, indent one level + // for each additional level + n = 1; + for (col = 0; col < our_paren_pos.col; ++col) + { + switch (l[col]) + { + case '(': + case '{': ++n; + break; + + case ')': + case '}': if (n > 1) + --n; + break; + } + } + + our_paren_pos.col = 0; + amount += n * curbuf->b_ind_unclosed_wrapped; + } + else if (curbuf->b_ind_unclosed_whiteok) + our_paren_pos.col++; + else + { + col = our_paren_pos.col + 1; + while (VIM_ISWHITE(l[col])) + col++; + if (l[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". + if (our_paren_pos.col > 0) + { + getvcol(curwin, &our_paren_pos, &col, NULL, NULL); + if (cur_amount > (int)col) + cur_amount = col; + } + } + + if (theline[0] == ')' && curbuf->b_ind_matching_paren) + { + // Line up with the start of the matching paren line. + } + else if ((curbuf->b_ind_unclosed == 0 && is_if_for_while == 0) + || (!curbuf->b_ind_unclosed_noignore + && *look == '(' && ignore_paren_col == 0)) + { + if (cur_amount != MAXCOL) + amount = cur_amount; + } + else + { + // Add b_ind_unclosed2 for each '(' before our matching one, + // but ignore (void) before the line (ignore_paren_col). + col = our_paren_pos.col; + while ((int)our_paren_pos.col > ignore_paren_col) + { + --our_paren_pos.col; + switch (*ml_get_pos(&our_paren_pos)) + { + case '(': amount += curbuf->b_ind_unclosed2; + col = our_paren_pos.col; + break; + case ')': amount -= curbuf->b_ind_unclosed2; + col = MAXCOL; + break; + } + } + + // Use b_ind_unclosed once, when the first '(' is not inside + // braces + if (col == MAXCOL) + amount += curbuf->b_ind_unclosed; + else + { + curwin->w_cursor.lnum = our_paren_pos.lnum; + curwin->w_cursor.col = col; + if (find_match_paren_after_brace(curbuf->b_ind_maxparen) + != NULL) + amount += curbuf->b_ind_unclosed2; + else + { + if (is_if_for_while) + amount += curbuf->b_ind_if_for_while; + else + amount += curbuf->b_ind_unclosed; + } + } + // For a line starting with ')' use the minimum of the two + // positions, to avoid giving it more indent than the previous + // lines: + // func_long_name( if (x + // arg && yy + // ) ^ not here ) ^ not here + if (cur_amount < amount) + amount = cur_amount; + } + } + + // add extra indent for a comment + if (cin_iscomment(theline)) + amount += curbuf->b_ind_comment; + } + else + { + // We are inside braces, there is a { before this line at the position + // stored in tryposBrace. + // Make a copy of tryposBrace, it may point to pos_copy inside + // find_start_brace(), which may be changed somewhere. + tryposCopy = *tryposBrace; + tryposBrace = &tryposCopy; + trypos = tryposBrace; + 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(curbuf->b_ind_maxparen)) + != NULL) + lnum = trypos->lnum; + + // It could have been something like + // case 1: if (asdf && + // condition) { + // } + if ((curbuf->b_ind_js || curbuf->b_ind_keep_case_label) + && cin_iscase(skipwhite(ml_get_curline()), FALSE)) + amount = get_indent(); + else if (curbuf->b_ind_js) + amount = get_indent_lnum(lnum); + else + amount = skip_label(lnum, &l); + + start_brace = BRACE_AT_END; + } + + // For Javascript check if the line starts with "key:". + if (curbuf->b_ind_js) + js_cur_has_key = cin_has_js_key(theline); + + // 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 += curbuf->b_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 = LOOKFOR_INITIAL; + if (cin_iselse(theline)) + lookfor = LOOKFOR_IF; + else if (cin_iswhileofdo(theline, cur_curpos.lnum)) // XXX + lookfor = LOOKFOR_DO; + if (lookfor != LOOKFOR_INITIAL) + { + curwin->w_cursor.lnum = cur_curpos.lnum; + if (find_match(lookfor, ourscope) == 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 b_ind_open_extra. + + if (start_brace == BRACE_IN_COL0) // '{' is in column 0 + { + amount = curbuf->b_ind_open_left_imag; + lookfor_cpp_namespace = TRUE; + } + else if (start_brace == BRACE_AT_START && + lookfor_cpp_namespace) // '{' is at start + { + + lookfor_cpp_namespace = TRUE; + } + else + { + if (start_brace == BRACE_AT_END) // '{' is at end of line + { + amount += curbuf->b_ind_open_imag; + + l = skipwhite(ml_get_curline()); + if (cin_is_cpp_namespace(l)) + amount += curbuf->b_ind_cpp_namespace; + else if (cin_is_cpp_extern_c(l)) + amount += curbuf->b_ind_cpp_extern_c; + } + else + { + // Compensate for adding b_ind_open_extra later. + amount -= curbuf->b_ind_open_extra; + if (amount < 0) + amount = 0; + } + } + + lookfor_break = FALSE; + + if (cin_iscase(theline, FALSE)) // it's a switch() label + { + lookfor = LOOKFOR_CASE; // find a previous switch() label + amount += curbuf->b_ind_case; + } + else if (cin_isscopedecl(theline)) // private:, ... + { + lookfor = LOOKFOR_SCOPEDECL; // class decl is this block + amount += curbuf->b_ind_scopedecl; + } + else + { + if (curbuf->b_ind_case_break && cin_isbreak(theline)) + // break; ... + lookfor_break = TRUE; + + lookfor = LOOKFOR_INITIAL; + // b_ind_level from start of block + amount += curbuf->b_ind_level; + } + 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) + { + // We reached end of scope: + // If looking for an enum or structure initialization + // go further back: + // If it is an initializer (enum xxx or xxx =), then + // don't add ind_continuation, otherwise it is a variable + // declaration: + // int x, + // here; <-- add ind_continuation + if (lookfor == LOOKFOR_ENUM_OR_INIT) + { + if (curwin->w_cursor.lnum == 0 + || curwin->w_cursor.lnum + < ourscope - curbuf->b_ind_maxparen) + { + // nothing found (abuse curbuf->b_ind_maxparen as + // limit) assume terminated line (i.e. a variable + // initialization) + if (cont_amount > 0) + amount = cont_amount; + else if (!curbuf->b_ind_js) + amount += ind_continuation; + break; + } + + // If we're in a comment or raw string now, skip to + // the start of it. + trypos = ind_find_start_CORS(NULL); + if (trypos != NULL) + { + curwin->w_cursor.lnum = trypos->lnum + 1; + curwin->w_cursor.col = 0; + continue; + } + + l = ml_get_curline(); + + // Skip preprocessor directives and blank lines. + if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, + &amount)) + continue; + + if (cin_nocode(l)) + continue; + + terminated = cin_isterminated(l, FALSE, TRUE); + + // If we are at top level and the line looks like a + // function declaration, we are done + // (it's a variable declaration). + if (start_brace != BRACE_IN_COL0 + || !cin_isfuncdecl(&l, curwin->w_cursor.lnum, 0)) + { + // if the line is terminated with another ',' + // it is a continued variable initialization. + // don't add extra indent. + // TODO: does not work, if a function + // declaration is split over multiple lines: + // cin_isfuncdecl returns FALSE then. + if (terminated == ',') + break; + + // if it is an enum declaration or an assignment, + // we are done. + if (terminated != ';' && cin_isinit()) + break; + + // nothing useful found + if (terminated == 0 || terminated == '{') + continue; + } + + if (terminated != ';') + { + // Skip parens and braces. Position the cursor + // over the rightmost paren, so that matching it + // will take us back to the start of the line. + // XXX + trypos = NULL; + if (find_last_paren(l, '(', ')')) + trypos = find_match_paren( + curbuf->b_ind_maxparen); + + if (trypos == NULL && find_last_paren(l, '{', '}')) + trypos = find_start_brace(); + + if (trypos != NULL) + { + curwin->w_cursor.lnum = trypos->lnum + 1; + curwin->w_cursor.col = 0; + continue; + } + } + + // it's a variable declaration, add indentation + // like in + // int a, + // b; + if (cont_amount > 0) + amount = cont_amount; + else + amount += ind_continuation; + } + else if (lookfor == LOOKFOR_UNTERM) + { + if (cont_amount > 0) + amount = cont_amount; + else + amount += ind_continuation; + } + else + { + if (lookfor != LOOKFOR_TERM + && lookfor != LOOKFOR_CPP_BASECLASS + && lookfor != LOOKFOR_COMMA) + { + amount = scope_amount; + if (theline[0] == '{') + { + amount += curbuf->b_ind_open_extra; + added_to_amount = curbuf->b_ind_open_extra; + } + } + + if (lookfor_cpp_namespace) + { + // Looking for C++ namespace, need to look further + // back. + if (curwin->w_cursor.lnum == ourscope) + continue; + + if (curwin->w_cursor.lnum == 0 + || curwin->w_cursor.lnum + < ourscope - FIND_NAMESPACE_LIM) + break; + + // If we're in a comment or raw string now, skip + // to the start of it. + trypos = ind_find_start_CORS(NULL); + if (trypos != NULL) + { + curwin->w_cursor.lnum = trypos->lnum + 1; + curwin->w_cursor.col = 0; + continue; + } + + l = ml_get_curline(); + + // Skip preprocessor directives and blank lines. + if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, + &amount)) + continue; + + // Finally the actual check for "namespace". + if (cin_is_cpp_namespace(l)) + { + amount += curbuf->b_ind_cpp_namespace + - added_to_amount; + break; + } + else if (cin_is_cpp_extern_c(l)) + { + amount += curbuf->b_ind_cpp_extern_c + - added_to_amount; + break; + } + + if (cin_nocode(l)) + continue; + } + } + break; + } + + // If we're in a comment or raw string now, skip to the start + // of it. XXX + if ((trypos = ind_find_start_CORS(&raw_string_start)) != NULL) + { + curwin->w_cursor.lnum = trypos->lnum + 1; + curwin->w_cursor.col = 0; + continue; + } + + l = ml_get_curline(); + + // If this is a switch() label, may line up relative to that. + // If this is a C++ scope declaration, do the same. + iscase = cin_iscase(l, FALSE); + if (iscase || cin_isscopedecl(l)) + { + // we are only looking for cpp base class + // declaration/initialization any longer + if (lookfor == LOOKFOR_CPP_BASECLASS) + break; + + // When looking for a "do" we are not interested in + // labels. + if (whilelevel > 0) + continue; + + // case xx: + // c = 99 + <- this indent plus continuation + //-> here; + if (lookfor == LOOKFOR_UNTERM + || lookfor == LOOKFOR_ENUM_OR_INIT) + { + if (cont_amount > 0) + amount = cont_amount; + else + amount += ind_continuation; + break; + } + + // case xx: <- line up with this case + // x = 333; + // case yy: + if ( (iscase && lookfor == LOOKFOR_CASE) + || (iscase && lookfor_break) + || (!iscase && lookfor == LOOKFOR_SCOPEDECL)) + { + // Check that this case label is not for another + // switch() XXX + if ((trypos = find_start_brace()) == 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; + + if (!lookfor_break) + 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 && cin_is_cinword(l)) + { + if (theline[0] == '{') + amount += curbuf->b_ind_open_extra; + else + amount += curbuf->b_ind_level + + curbuf->b_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() + (iscase // XXX + ? curbuf->b_ind_case_code + : curbuf->b_ind_scopedecl_code); + lookfor = curbuf->b_ind_case_break + ? LOOKFOR_NOBREAK : LOOKFOR_ANY; + continue; + } + + // Looking for a switch() label or C++ scope declaration, + // ignore other lines, skip {}-blocks. + if (lookfor == LOOKFOR_CASE || lookfor == LOOKFOR_SCOPEDECL) + { + if (find_last_paren(l, '{', '}') + && (trypos = find_start_brace()) != NULL) + { + curwin->w_cursor.lnum = trypos->lnum + 1; + curwin->w_cursor.col = 0; + } + continue; + } + + // Ignore jump labels with nothing after them. + if (!curbuf->b_ind_js && cin_islabel()) + { + l = after_label(ml_get_curline()); + if (l == NULL || cin_nocode(l)) + continue; + } + + // Ignore #defines, #if, etc. + // Ignore comment and empty lines. + // (need to get the line again, cin_islabel() may have + // unlocked it) + l = ml_get_curline(); + if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount) + || cin_nocode(l)) + continue; + + // Are we at the start of a cpp base class declaration or + // constructor initialization? XXX + n = FALSE; + if (lookfor != LOOKFOR_TERM && curbuf->b_ind_cpp_baseclass > 0) + { + n = cin_is_cpp_baseclass(&cache_cpp_baseclass); + l = ml_get_curline(); + } + if (n) + { + if (lookfor == LOOKFOR_UNTERM) + { + if (cont_amount > 0) + amount = cont_amount; + else + amount += ind_continuation; + } + else if (theline[0] == '{') + { + // Need to find start of the declaration. + lookfor = LOOKFOR_UNTERM; + ind_continuation = 0; + continue; + } + else + // XXX + amount = get_baseclass_amount( + cache_cpp_baseclass.lpos.col); + break; + } + else if (lookfor == LOOKFOR_CPP_BASECLASS) + { + // only look, whether there is a cpp base class + // declaration or initialization before the opening brace. + if (cin_isterminated(l, TRUE, FALSE)) + break; + else + continue; + } + + // What happens next depends on the line being terminated. + // If terminated with a ',' only consider it terminating if + // there is another unterminated statement behind, eg: + // 123, + // sizeof + // here + // Otherwise check whether it is an enumeration or structure + // initialisation (not indented) or a variable declaration + // (indented). + terminated = cin_isterminated(l, FALSE, TRUE); + + if (js_cur_has_key) + { + js_cur_has_key = 0; // only check the first line + if (curbuf->b_ind_js && terminated == ',') + { + // For Javascript we might be inside an object: + // key: something, <- align with this + // key: something + // or: + // key: something + <- align with this + // something, + // key: something + lookfor = LOOKFOR_JS_KEY; + } + } + if (lookfor == LOOKFOR_JS_KEY && cin_has_js_key(l)) + { + amount = get_indent(); + break; + } + if (lookfor == LOOKFOR_COMMA) + { + if (tryposBrace != NULL && tryposBrace->lnum + >= curwin->w_cursor.lnum) + break; + if (terminated == ',') + // line below current line is the one that starts a + // (possibly broken) line ending in a comma. + break; + else + { + amount = get_indent(); + if (curwin->w_cursor.lnum - 1 == ourscope) + // line above is start of the scope, thus current + // line is the one that stars a (possibly broken) + // line ending in a comma. + break; + } + } + + if (terminated == 0 || (lookfor != LOOKFOR_UNTERM + && terminated == ',')) + { + if (lookfor != LOOKFOR_ENUM_OR_INIT && + (*skipwhite(l) == '[' || l[STRLEN(l) - 1] == '[')) + amount += ind_continuation; + // 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. + // Ignore a match before the start of the block. + (void)find_last_paren(l, '(', ')'); + trypos = find_match_paren(corr_ind_maxparen(&cur_curpos)); + if (trypos != NULL && (trypos->lnum < tryposBrace->lnum + || (trypos->lnum == tryposBrace->lnum + && trypos->col < tryposBrace->col))) + trypos = NULL; + + l = ml_get_curline(); + + // If we are looking for ',', we also look for matching + // braces. + if (trypos == NULL && terminated == ',') + { + if (find_last_paren(l, '{', '}')) + trypos = find_start_brace(); + l = ml_get_curline(); + } + + if (trypos != NULL) + { + // Check if we are on a case label now. This is + // handled above. + // case xx: if ( asdf && + // asdf) + curwin->w_cursor = *trypos; + l = ml_get_curline(); + if (cin_iscase(l, FALSE) || cin_isscopedecl(l)) + { + ++curwin->w_cursor.lnum; + curwin->w_cursor.col = 0; + continue; + } + } + + // Skip over continuation lines to find the one to get the + // indent from + // char *usethis = "bla{backslash} + // bla", + // here; + if (terminated == ',') + { + while (curwin->w_cursor.lnum > 1) + { + l = ml_get(curwin->w_cursor.lnum - 1); + if (*l == NUL || l[STRLEN(l) - 1] != '\\') + break; + --curwin->w_cursor.lnum; + curwin->w_cursor.col = 0; + } + l = ml_get_curline(); + } + + // Get indent and pointer to text for current line, + // ignoring any jump label. XXX + if (curbuf->b_ind_js) + cur_amount = get_indent(); + else + cur_amount = skip_label(curwin->w_cursor.lnum, &l); + // If this is just above the line we are indenting, and it + // starts with a '{', line it up with this line. + // while (not) + // -> { + // } + if (terminated != ',' && lookfor != LOOKFOR_TERM + && theline[0] == '{') + { + amount = cur_amount; + // Only add b_ind_open_extra when the current line + // doesn't start with a '{', which must have a match + // in the same line (scope is the same). Probably: + // { 1, 2 }, + // -> { 3, 4 } + if (*skipwhite(l) != '{') + amount += curbuf->b_ind_open_extra; + + if (curbuf->b_ind_cpp_baseclass && !curbuf->b_ind_js) + { + // have to look back, whether it is a cpp base + // class declaration or initialization + lookfor = LOOKFOR_CPP_BASECLASS; + continue; + } + break; + } + + // Check if we are after an "if", "while", etc. + // Also allow "} else". + if (cin_is_cinword(l) || cin_iselse(skipwhite(l))) + { + // Found an unterminated line after an if (), line up + // with the last one. + // if (cond) + // 100 + + // -> here; + if (lookfor == LOOKFOR_UNTERM + || lookfor == LOOKFOR_ENUM_OR_INIT) + { + if (cont_amount > 0) + amount = cont_amount; + else + 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 (theline[0] == '{') + amount += curbuf->b_ind_open_extra; + if (lookfor != LOOKFOR_TERM) + { + amount += curbuf->b_ind_level + + curbuf->b_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 (cin_isdo(l)) + { + if (whilelevel == 0) + break; + --whilelevel; + } + + // When searching for a terminated line, don't use the + // one between the "if" and the matching "else". + // Need to use the scope of this "else". XXX + // If whilelevel != 0 continue looking for a "do {". + if (cin_iselse(l) && whilelevel == 0) + { + // If we're looking at "} else", let's make sure we + // find the opening brace of the enclosing scope, + // not the one from "if (condition) {". + if (*l == '}') + curwin->w_cursor.col = + (colnr_T)(l - ml_get_curline()) + 1; + + if ((trypos = find_start_brace()) == NULL + || find_match(LOOKFOR_IF, trypos->lnum) + == 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 something 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) + { + // When line ends in a comma add extra indent + if (terminated == ',') + amount += ind_continuation; + break; + } + + if (lookfor == LOOKFOR_ENUM_OR_INIT) + { + // Found two lines ending in ',', lineup with the + // lowest one, but check for cpp base class + // declaration/initialization, if it is an + // opening brace or we are looking just for + // enumerations/initializations. + if (terminated == ',') + { + if (curbuf->b_ind_cpp_baseclass == 0) + break; + + lookfor = LOOKFOR_CPP_BASECLASS; + continue; + } + + // Ignore unterminated lines in between, but + // reduce indent. + if (amount > cur_amount) + amount = cur_amount; + } + else + { + // Found first unterminated line on a row, may + // line up with this line, remember its indent + // 100 + + // -> here; + l = ml_get_curline(); + amount = cur_amount; + + n = (int)STRLEN(l); + if (terminated == ',' && (*skipwhite(l) == ']' + || (n >=2 && l[n - 2] == ']'))) + break; + + // If previous line ends in ',', check whether we + // are in an initialization or enum + // struct xxx = + // { + // sizeof a, + // 124 }; + // or a normal possible continuation line. + // but only, of no other statement has been found + // yet. + if (lookfor == LOOKFOR_INITIAL && terminated == ',') + { + if (curbuf->b_ind_js) + { + // Search for a line ending in a comma + // and line up with the line below it + // (could be the current line). + // some = [ + // 1, <- line up here + // 2, + // some = [ + // 3 + <- line up here + // 4 * + // 5, + // 6, + if (cin_iscomment(skipwhite(l))) + break; + lookfor = LOOKFOR_COMMA; + trypos = find_match_char('[', + curbuf->b_ind_maxparen); + if (trypos != NULL) + { + if (trypos->lnum + == curwin->w_cursor.lnum - 1) + { + // Current line is first inside + // [], line up with it. + break; + } + ourscope = trypos->lnum; + } + } + else + { + lookfor = LOOKFOR_ENUM_OR_INIT; + cont_amount = cin_first_id_amount(); + } + } + else + { + if (lookfor == LOOKFOR_INITIAL + && *l != NUL + && l[STRLEN(l) - 1] == '\\') + // XXX + cont_amount = cin_get_equal_amount( + curwin->w_cursor.lnum); + if (lookfor != LOOKFOR_TERM + && lookfor != LOOKFOR_JS_KEY + && lookfor != LOOKFOR_COMMA + && raw_string_start != curwin->w_cursor.lnum) + lookfor = LOOKFOR_UNTERM; + } + } + } + } + + // Check if we are after a while (cond); + // If so: Ignore until the matching "do". + else if (cin_iswhileofdo_end(terminated)) // XXX + { + // 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 + || lookfor == LOOKFOR_ENUM_OR_INIT) + { + if (cont_amount > 0) + amount = cont_amount; + else + amount += ind_continuation; + break; + } + + if (whilelevel == 0) + { + lookfor = LOOKFOR_TERM; + amount = get_indent(); // XXX + if (theline[0] == '{') + amount += curbuf->b_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 + { + // Skip single break line, if before a switch label. It + // may be lined up with the case label. + if (lookfor == LOOKFOR_NOBREAK + && cin_isbreak(skipwhite(ml_get_curline()))) + { + lookfor = LOOKFOR_ANY; + continue; + } + + // Handle "do {" line. + if (whilelevel > 0) + { + l = cin_skipcomment(ml_get_curline()); + if (cin_isdo(l)) + { + amount = get_indent(); // XXX + --whilelevel; + continue; + } + } + + // Found a terminated line above an unterminated line. Add + // the amount for a continuation line. + // x = 1; + // y = foo + + // -> here; + // or + // int x = 1; + // int foo, + // -> here; + if (lookfor == LOOKFOR_UNTERM + || lookfor == LOOKFOR_ENUM_OR_INIT) + { + if (cont_amount > 0) + amount = cont_amount; + else + 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 (!lookfor_break && 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; +term_again: + l = ml_get_curline(); + if (find_last_paren(l, '(', ')') + && (trypos = find_match_paren( + curbuf->b_ind_maxparen)) != NULL) + { + // Check if we are on a case label now. This is + // handled above. + // case xx: if ( asdf && + // asdf) + curwin->w_cursor = *trypos; + l = ml_get_curline(); + if (cin_iscase(l, FALSE) || cin_isscopedecl(l)) + { + ++curwin->w_cursor.lnum; + curwin->w_cursor.col = 0; + continue; + } + } + + // When aligning with the case statement, don't align + // with a statement after it. + // case 1: { <-- don't use this { position + // stat; + // } + // case 2: + // stat; + // } + iscase = (curbuf->b_ind_keep_case_label + && cin_iscase(l, FALSE)); + + // Get indent and pointer to text for current line, + // ignoring any jump label. + amount = skip_label(curwin->w_cursor.lnum, &l); + + if (theline[0] == '{') + amount += curbuf->b_ind_open_extra; + // See remark above: "Only add b_ind_open_extra.." + l = skipwhite(l); + if (*l == '{') + amount -= curbuf->b_ind_open_extra; + lookfor = iscase ? LOOKFOR_ANY : LOOKFOR_TERM; + + // When a terminated line starts with "else" skip to + // the matching "if": + // else 3; + // indent this; + // Need to use the scope of this "else". XXX + // If whilelevel != 0 continue looking for a "do {". + if (lookfor == LOOKFOR_TERM + && *l != '}' + && cin_iselse(l) + && whilelevel == 0) + { + if ((trypos = find_start_brace()) == NULL + || find_match(LOOKFOR_IF, trypos->lnum) + == FAIL) + break; + continue; + } + + // If we're at the end of a block, skip to the start of + // that block. + l = ml_get_curline(); + if (find_last_paren(l, '{', '}') // XXX + && (trypos = find_start_brace()) != NULL) + { + curwin->w_cursor = *trypos; + // if not "else {" check for terminated again + // but skip block for "} else {" + l = cin_skipcomment(ml_get_curline()); + if (*l == '}' || !cin_iselse(l)) + goto term_again; + ++curwin->w_cursor.lnum; + curwin->w_cursor.col = 0; + } + } + } + } + } + } + + // add extra indent for a comment + if (cin_iscomment(theline)) + amount += curbuf->b_ind_comment; + + // subtract extra left-shift for jump labels + if (curbuf->b_ind_jump_label > 0 && original_line_islabel) + amount -= curbuf->b_ind_jump_label; + + goto theend; + } + + // 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. + // + // 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 = curbuf->b_ind_first_open; + goto theend; + } + + // 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 or if the + // current line is terminated, ie. ends in ';', or if the current line + // contains { or }: "void f(condition) {\n if (1)" + if (cur_curpos.lnum < curbuf->b_ml.ml_line_count + && !cin_nocode(theline) + && vim_strchr(theline, '{') == NULL + && vim_strchr(theline, '}') == NULL + && !cin_ends_in(theline, (char_u *)":", NULL) + && !cin_ends_in(theline, (char_u *)",", NULL) + && cin_isfuncdecl(NULL, cur_curpos.lnum + 1, + cur_curpos.lnum + 1) + && !cin_isterminated(theline, FALSE, TRUE)) + { + amount = curbuf->b_ind_func_type; + goto theend; + } + + // search backwards until we find something we recognize + amount = 0; + curwin->w_cursor = cur_curpos; + 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 or raw string now, skip to the start + // of it. XXX + if ((trypos = ind_find_start_CORS(NULL)) != NULL) + { + curwin->w_cursor.lnum = trypos->lnum + 1; + curwin->w_cursor.col = 0; + continue; + } + + // Are we at the start of a cpp base class declaration or + // constructor initialization? XXX + n = FALSE; + if (curbuf->b_ind_cpp_baseclass != 0) + { + n = cin_is_cpp_baseclass(&cache_cpp_baseclass); + l = ml_get_curline(); + } + if (n) + { + // XXX + amount = get_baseclass_amount(cache_cpp_baseclass.lpos.col); + break; + } + + // Skip preprocessor directives and blank lines. + if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount)) + continue; + + if (cin_nocode(l)) + continue; + + // If the previous line ends in ',', use one level of + // indentation: + // int foo, + // bar; + // do this before checking for '}' in case of eg. + // enum foobar + // { + // ... + // } foo, + // bar; + n = 0; + if (cin_ends_in(l, (char_u *)",", NULL) + || (*l != NUL && (n = l[STRLEN(l) - 1]) == '\\')) + { + // take us back to opening paren + if (find_last_paren(l, '(', ')') + && (trypos = find_match_paren( + curbuf->b_ind_maxparen)) != NULL) + curwin->w_cursor = *trypos; + + // For a line ending in ',' that is a continuation line go + // back to the first line with a backslash: + // char *foo = "bla{backslash} + // bla", + // here; + while (n == 0 && curwin->w_cursor.lnum > 1) + { + l = ml_get(curwin->w_cursor.lnum - 1); + if (*l == NUL || l[STRLEN(l) - 1] != '\\') + break; + --curwin->w_cursor.lnum; + curwin->w_cursor.col = 0; + } + + amount = get_indent(); // XXX + + if (amount == 0) + amount = cin_first_id_amount(); + if (amount == 0) + amount = ind_continuation; + break; + } + + // If the line looks like a function declaration, and we're + // not in a comment, put it the left margin. + if (cin_isfuncdecl(NULL, cur_curpos.lnum, 0)) // XXX + break; + l = ml_get_curline(); + + // Finding the closing '}' of a previous function. Put + // current line at the left margin. For when 'cino' has "fs". + if (*skipwhite(l) == '}') + break; + + // (matching {) + // If the previous line ends on '};' (maybe followed by + // comments) align at column 0. For example: + // char *string_array[] = { "foo", + // / * x * / "b};ar" }; / * foobar * / + if (cin_ends_in(l, (char_u *)"};", NULL)) + break; + + // If the previous line ends on '[' we are probably in an + // array constant: + // something = [ + // 234, <- extra indent + if (cin_ends_in(l, (char_u *)"[", NULL)) + { + amount = get_indent() + ind_continuation; + break; + } + + // Find a line only has a semicolon that belongs to a previous + // line ending in '}', e.g. before an #endif. Don't increase + // indent then. + if (*(look = skipwhite(l)) == ';' && cin_nocode(look + 1)) + { + pos_T curpos_save = curwin->w_cursor; + + while (curwin->w_cursor.lnum > 1) + { + look = ml_get(--curwin->w_cursor.lnum); + if (!(cin_nocode(look) || cin_ispreproc_cont( + &look, &curwin->w_cursor.lnum, &amount))) + break; + } + if (curwin->w_cursor.lnum > 0 + && cin_ends_in(look, (char_u *)"}", NULL)) + break; + + curwin->w_cursor = curpos_save; + } + + // If the PREVIOUS line is a function declaration, the current + // line (and the ones that follow) needs to be indented as + // parameters. + if (cin_isfuncdecl(&l, curwin->w_cursor.lnum, 0)) + { + amount = curbuf->b_ind_param; + break; + } + + // If the previous line ends in ';' and the line before the + // previous line ends in ',' or '\', ident to column zero: + // int foo, + // bar; + // indent_to_0 here; + if (cin_ends_in(l, (char_u *)";", NULL)) + { + l = ml_get(curwin->w_cursor.lnum - 1); + if (cin_ends_in(l, (char_u *)",", NULL) + || (*l != NUL && l[STRLEN(l) - 1] == '\\')) + break; + l = ml_get_curline(); + } + + // 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(curbuf->b_ind_maxparen)) != NULL) + curwin->w_cursor = *trypos; + amount = get_indent(); // XXX + break; + } + + // add extra indent for a comment + if (cin_iscomment(theline)) + amount += curbuf->b_ind_comment; + + // add extra indent if the previous line ended in a backslash: + // "asdfasdf{backslash} + // here"; + // char *foo = "asdf{backslash} + // here"; + if (cur_curpos.lnum > 1) + { + l = ml_get(cur_curpos.lnum - 1); + if (*l != NUL && l[STRLEN(l) - 1] == '\\') + { + cur_amount = cin_get_equal_amount(cur_curpos.lnum - 1); + if (cur_amount > 0) + amount = cur_amount; + else if (cur_amount == 0) + amount += ind_continuation; + } + } + +theend: + if (amount < 0) + amount = 0; + +laterend: + // put the cursor back where it belongs + curwin->w_cursor = cur_curpos; + + vim_free(linecopy); + + return amount; +} + +/* + * return TRUE if 'cinkeys' contains the key "keytyped", + * when == '*': Only if key is preceded with '*' (indent before insert) + * when == '!': Only if key is preceded with '!' (don't insert) + * when == ' ': Only if key is not preceded with '*'(indent afterwards) + * + * "keytyped" can have a few special values: + * KEY_OPEN_FORW + * KEY_OPEN_BACK + * KEY_COMPLETE just finished completion. + * + * If line_is_empty is TRUE accept keys with '0' before them. + */ + int +in_cinkeys( + int keytyped, + int when, + int line_is_empty) +{ + char_u *look; + int try_match; + int try_match_word; + char_u *p; + char_u *line; + int icase; + int i; + + if (keytyped == NUL) + // Can happen with CTRL-Y and CTRL-E on a short line. + return FALSE; + +#ifdef FEAT_EVAL + if (*curbuf->b_p_inde != NUL) + look = curbuf->b_p_indk; // 'indentexpr' set: use 'indentkeys' + else +#endif + look = curbuf->b_p_cink; // 'indentexpr' empty: use 'cinkeys' + while (*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. + // But may still match when typing last char of a word. + if (*look == '0') + { + try_match_word = try_match; + if (!line_is_empty) + try_match = FALSE; + ++look; + } + else + try_match_word = FALSE; + + // does it look like a control character? + if (*look == '^' && look[1] >= '?' && look[1] <= '_') + { + if (try_match && keytyped == Ctrl_chr(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, or when it was before typing the ':' (to fix + // class::method for C++). + else if (*look == ':') + { + if (try_match && keytyped == ':') + { + p = ml_get_curline(); + if (cin_iscase(p, FALSE) || cin_isscopedecl(p) || cin_islabel()) + return TRUE; + // Need to get the line again after cin_islabel(). + p = ml_get_curline(); + if (curwin->w_cursor.col > 2 + && p[curwin->w_cursor.col - 1] == ':' + && p[curwin->w_cursor.col - 2] == ':') + { + p[curwin->w_cursor.col - 1] = ' '; + i = (cin_iscase(p, FALSE) || cin_isscopedecl(p) + || cin_islabel()); + p = ml_get_curline(); + p[curwin->w_cursor.col - 1] = ':'; + if (i) + 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++; + } + + // Is it a word: "=word"? + else if (*look == '=' && look[1] != ',' && look[1] != NUL) + { + ++look; + if (*look == '~') + { + icase = TRUE; + ++look; + } + else + icase = FALSE; + p = vim_strchr(look, ','); + if (p == NULL) + p = look + STRLEN(look); + if ((try_match || try_match_word) + && curwin->w_cursor.col >= (colnr_T)(p - look)) + { + int match = FALSE; + + if (keytyped == KEY_COMPLETE) + { + char_u *s; + + // Just completed a word, check if it starts with "look". + // search back for the start of a word. + line = ml_get_curline(); + if (has_mbyte) + { + char_u *n; + + for (s = line + curwin->w_cursor.col; s > line; s = n) + { + n = mb_prevptr(line, s); + if (!vim_iswordp(n)) + break; + } + } + else + for (s = line + curwin->w_cursor.col; s > line; --s) + if (!vim_iswordc(s[-1])) + break; + if (s + (p - look) <= line + curwin->w_cursor.col + && (icase + ? MB_STRNICMP(s, look, p - look) + : STRNCMP(s, look, p - look)) == 0) + match = TRUE; + } + else + // TODO: multi-byte + if (keytyped == (int)p[-1] || (icase && keytyped < 256 + && TOLOWER_LOC(keytyped) == TOLOWER_LOC((int)p[-1]))) + { + line = ml_get_cursor(); + if ((curwin->w_cursor.col == (colnr_T)(p - look) + || !vim_iswordc(line[-(p - look) - 1])) + && (icase + ? MB_STRNICMP(line - (p - look), look, p - look) + : STRNCMP(line - (p - look), look, p - look)) + == 0) + match = TRUE; + } + if (match && try_match_word && !try_match) + { + // "0=word": Check if there are only blanks before the + // word. + if (getwhitecols_curline() != + (int)(curwin->w_cursor.col - (p - look))) + match = FALSE; + } + if (match) + return TRUE; + } + look = p; + } + + // ok, it's a boring generic character. + else + { + if (try_match && *look == keytyped) + return TRUE; + if (*look != NUL) + ++look; + } + + // Skip over ", ". + look = skip_to_option_part(look); + } + return FALSE; +} + +/* + * Do C or expression indenting on the current line. + */ + void +do_c_expr_indent(void) +{ +#ifdef FEAT_EVAL + if (*curbuf->b_p_inde != NUL) + fixthisline(get_expr_indent); + else +#endif + fixthisline(get_c_indent); +} + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * "cindent(lnum)" function + */ + void +f_cindent(typval_T *argvars UNUSED, typval_T *rettv) +{ + pos_T pos; + linenr_T lnum; + + if (in_vim9script() && check_for_lnum_arg(argvars, 0) == FAIL) + return; + + pos = curwin->w_cursor; + lnum = tv_get_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) + { + curwin->w_cursor.lnum = lnum; + rettv->vval.v_number = get_c_indent(); + curwin->w_cursor = pos; + } + else + rettv->vval.v_number = -1; +} +#endif diff --git a/userland/apps/vim/src/clientserver.c b/userland/apps/vim/src/clientserver.c new file mode 100644 index 00000000..cfc0ab66 --- /dev/null +++ b/userland/apps/vim/src/clientserver.c @@ -0,0 +1,1048 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * clientserver.c: functions for Client Server functionality + */ + +#include "vim.h" + +#if defined(FEAT_CLIENTSERVER) || defined(PROTO) + +static void cmdsrv_main(int *argc, char **argv, char_u *serverName_arg, char_u **serverStr); +static char_u *serverMakeName(char_u *arg, char *cmd); + +/* + * Replace termcodes such as and insert as key presses if there is room. + */ + void +server_to_input_buf(char_u *str) +{ + char_u *ptr = NULL; + char_u *cpo_save = p_cpo; + + // Set 'cpoptions' the way we want it. + // B set - backslashes are *not* treated specially + // k set - keycodes are *not* reverse-engineered + // < unset - sequences *are* interpreted + // The last but one parameter of replace_termcodes() is TRUE so that the + // sequence is recognised - needed for a real backslash. + p_cpo = (char_u *)"Bk"; + str = replace_termcodes(str, &ptr, 0, REPTERM_DO_LT, NULL); + p_cpo = cpo_save; + + if (*ptr != NUL) // trailing CTRL-V results in nothing + { + /* + * Add the string to the input stream. + * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes. + * + * First clear typed characters from the typeahead buffer, there could + * be half a mapping there. Then append to the existing string, so + * that multiple commands from a client are concatenated. + */ + if (typebuf.tb_maplen < typebuf.tb_len) + del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen); + (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, FALSE); + + // Let input_available() know we inserted text in the typeahead + // buffer. + typebuf_was_filled = TRUE; + } + vim_free(ptr); +} + +/* + * Evaluate an expression that the client sent to a string. + */ + char_u * +eval_client_expr_to_string(char_u *expr) +{ + char_u *res; + int save_dbl = debug_break_level; + int save_ro = redir_off; + funccal_entry_T funccal_entry; + int did_save_funccal = FALSE; + +#if defined(FEAT_EVAL) + ch_log(NULL, "eval_client_expr_to_string(\"%s\")", expr); +#endif + + // Evaluate the expression at the toplevel, don't use variables local to + // the calling function. Except when in debug mode. + if (!debug_mode) + { + save_funccal(&funccal_entry); + did_save_funccal = TRUE; + } + + // Disable debugging, otherwise Vim hangs, waiting for "cont" to be + // typed. + debug_break_level = -1; + redir_off = 0; + // Do not display error message, otherwise Vim hangs, waiting for "cont" + // to be typed. Do generate errors so that try/catch works. + ++emsg_silent; + + res = eval_to_string(expr, TRUE, FALSE); + + debug_break_level = save_dbl; + redir_off = save_ro; + --emsg_silent; + if (emsg_silent < 0) + emsg_silent = 0; + if (did_save_funccal) + restore_funccal(); + + // A client can tell us to redraw, but not to display the cursor, so do + // that here. + setcursor(); + out_flush_cursor(FALSE, FALSE); + + return res; +} + +/* + * Evaluate a command or expression sent to ourselves. + */ + int +sendToLocalVim(char_u *cmd, int asExpr, char_u **result) +{ + if (asExpr) + { + char_u *ret; + + ret = eval_client_expr_to_string(cmd); + if (result != NULL) + { + if (ret == NULL) + { + char *err = _(e_invalid_expression_received); + size_t len = STRLEN(cmd) + STRLEN(err) + 5; + char_u *msg; + + msg = alloc(len); + if (msg != NULL) + vim_snprintf((char *)msg, len, "%s: \"%s\"", err, cmd); + *result = msg; + } + else + *result = ret; + } + else + vim_free(ret); + return ret == NULL ? -1 : 0; + } + server_to_input_buf(cmd); + return 0; +} + +/* + * If conversion is needed, convert "data" from "client_enc" to 'encoding' and + * return an allocated string. Otherwise return "data". + * "*tofree" is set to the result when it needs to be freed later. + */ + char_u * +serverConvert( + char_u *client_enc UNUSED, + char_u *data, + char_u **tofree) +{ + char_u *res = data; + + *tofree = NULL; + if (client_enc == NULL || p_enc == NULL) + return res; + + vimconv_T vimconv; + + vimconv.vc_type = CONV_NONE; + if (convert_setup(&vimconv, client_enc, p_enc) != FAIL + && vimconv.vc_type != CONV_NONE) + { + res = string_convert(&vimconv, data, NULL); + if (res == NULL) + res = data; + else + *tofree = res; + } + convert_setup(&vimconv, NULL, NULL); + return res; +} +#endif + +#if (defined(FEAT_CLIENTSERVER) && !defined(NO_VIM_MAIN)) || defined(PROTO) + +/* + * Common code for the X command server and the Win32 command server. + */ + +static char_u *build_drop_cmd(int filec, char **filev, int tabs, int sendReply); + +/* + * Do the client-server stuff, unless "--servername ''" was used. + */ + void +exec_on_server(mparm_T *parmp) +{ + if (parmp->serverName_arg != NULL && *parmp->serverName_arg == NUL) + return; + +# ifdef MSWIN + // Initialise the client/server messaging infrastructure. + serverInitMessaging(); +# endif + + /* + * When a command server argument was found, execute it. This may + * exit Vim when it was successful. Otherwise it's executed further + * on. Remember the encoding used here in "serverStrEnc". + */ + if (parmp->serverArg) + { + cmdsrv_main(&parmp->argc, parmp->argv, + parmp->serverName_arg, &parmp->serverStr); + parmp->serverStrEnc = vim_strsave(p_enc); + } + + // If we're still running, get the name to register ourselves. + // On Win32 can register right now, for X11 need to setup the + // clipboard first, it's further down. + parmp->servername = serverMakeName(parmp->serverName_arg, + parmp->argv[0]); +# ifdef MSWIN + if (parmp->servername != NULL) + { + serverSetName(parmp->servername); + vim_free(parmp->servername); + } +# endif +} + +/* + * Prepare for running as a Vim server. + */ + void +prepare_server(mparm_T *parmp) +{ +# if defined(FEAT_X11) + /* + * Register for remote command execution with :serversend and --remote + * unless there was a -X or a --servername '' on the command line. + * Only register nongui-vim's with an explicit --servername argument, + * or when compiling with autoservername. + * When running as root --servername is also required. + */ + if (X_DISPLAY != NULL && parmp->servername != NULL && ( +# if defined(FEAT_AUTOSERVERNAME) || defined(FEAT_GUI) + ( +# if defined(FEAT_AUTOSERVERNAME) + 1 +# else + gui.in_use +# endif +# ifdef UNIX + && getuid() != ROOT_UID +# endif + ) || +# endif + parmp->serverName_arg != NULL)) + { + (void)serverRegisterName(X_DISPLAY, parmp->servername); + vim_free(parmp->servername); + TIME_MSG("register server name"); + } + else + serverDelayedStartName = parmp->servername; +# endif + + /* + * Execute command ourselves if we're here because the send failed (or + * else we would have exited above). + */ + if (parmp->serverStr != NULL) + { + char_u *p; + + server_to_input_buf(serverConvert(parmp->serverStrEnc, + parmp->serverStr, &p)); + vim_free(p); + } +} + + static void +cmdsrv_main( + int *argc, + char **argv, + char_u *serverName_arg, + char_u **serverStr) +{ + char_u *res; + int i; + char_u *sname; + int ret; + int didone = FALSE; + int exiterr = 0; + char **newArgV = argv + 1; + int newArgC = 1, + Argc = *argc; + int argtype; +#define ARGTYPE_OTHER 0 +#define ARGTYPE_EDIT 1 +#define ARGTYPE_EDIT_WAIT 2 +#define ARGTYPE_SEND 3 + int silent = FALSE; + int tabs = FALSE; +# ifndef FEAT_X11 + HWND srv; +# else + Window srv; + + setup_term_clip(); +# endif + + sname = serverMakeName(serverName_arg, argv[0]); + if (sname == NULL) + return; + + /* + * Execute the command server related arguments and remove them + * from the argc/argv array; We may have to return into main() + */ + for (i = 1; i < Argc; i++) + { + res = NULL; + if (STRCMP(argv[i], "--") == 0) // end of option arguments + { + for (; i < *argc; i++) + { + *newArgV++ = argv[i]; + newArgC++; + } + break; + } + + if (STRICMP(argv[i], "--remote-send") == 0) + argtype = ARGTYPE_SEND; + else if (STRNICMP(argv[i], "--remote", 8) == 0) + { + char *p = argv[i] + 8; + + argtype = ARGTYPE_EDIT; + while (*p != NUL) + { + if (STRNICMP(p, "-wait", 5) == 0) + { + argtype = ARGTYPE_EDIT_WAIT; + p += 5; + } + else if (STRNICMP(p, "-silent", 7) == 0) + { + silent = TRUE; + p += 7; + } + else if (STRNICMP(p, "-tab", 4) == 0) + { + tabs = TRUE; + p += 4; + } + else + { + argtype = ARGTYPE_OTHER; + break; + } + } + } + else + argtype = ARGTYPE_OTHER; + + if (argtype != ARGTYPE_OTHER) + { + if (i == *argc - 1) + mainerr_arg_missing((char_u *)argv[i]); + if (argtype == ARGTYPE_SEND) + { + *serverStr = (char_u *)argv[i + 1]; + i++; + } + else + { + *serverStr = build_drop_cmd(*argc - i - 1, argv + i + 1, + tabs, argtype == ARGTYPE_EDIT_WAIT); + if (*serverStr == NULL) + { + // Probably out of memory, exit. + didone = TRUE; + exiterr = 1; + break; + } + Argc = i; + } +# ifdef FEAT_X11 + if (xterm_dpy == NULL) + { + mch_errmsg(_("No display")); + ret = -1; + } + else + ret = serverSendToVim(xterm_dpy, sname, *serverStr, + NULL, &srv, 0, 0, 0, silent); +# else + // Win32 always works? + ret = serverSendToVim(sname, *serverStr, NULL, &srv, 0, 0, silent); +# endif + if (ret < 0) + { + if (argtype == ARGTYPE_SEND) + { + // Failed to send, abort. + mch_errmsg(_(": Send failed.\n")); + didone = TRUE; + exiterr = 1; + } + else if (!silent) + // Let vim start normally. + mch_errmsg(_(": Send failed. Trying to execute locally\n")); + break; + } + +# ifdef FEAT_GUI_MSWIN + // Guess that when the server name starts with "g" it's a GUI + // server, which we can bring to the foreground here. + // Foreground() in the server doesn't work very well. + if (argtype != ARGTYPE_SEND && TOUPPER_ASC(*sname) == 'G') + SetForegroundWindow(srv); +# endif + + /* + * For --remote-wait: Wait until the server did edit each + * file. Also detect that the server no longer runs. + */ + if (argtype == ARGTYPE_EDIT_WAIT) + { + int numFiles = *argc - i - 1; + char_u *done = alloc(numFiles); +# ifdef FEAT_GUI_MSWIN + NOTIFYICONDATA ni; + int count = 0; + extern HWND message_window; +# endif + + if (numFiles > 0 && argv[i + 1][0] == '+') + // Skip "+cmd" argument, don't wait for it to be edited. + --numFiles; + +# ifdef FEAT_GUI_MSWIN + ni.cbSize = sizeof(ni); + ni.hWnd = message_window; + ni.uID = 0; + ni.uFlags = NIF_ICON|NIF_TIP; + ni.hIcon = LoadIcon((HINSTANCE)GetModuleHandle(0), "IDR_VIM"); + sprintf(ni.szTip, _("%d of %d edited"), count, numFiles); + Shell_NotifyIcon(NIM_ADD, &ni); +# endif + + // Wait for all files to unload in remote + vim_memset(done, 0, numFiles); + while (memchr(done, 0, numFiles) != NULL) + { + char_u *p; + int j; +# ifdef MSWIN + p = serverGetReply(srv, NULL, TRUE, TRUE, 0); + if (p == NULL) + break; +# else + if (serverReadReply(xterm_dpy, srv, &p, TRUE, -1) < 0) + break; +# endif + j = atoi((char *)p); + vim_free(p); + if (j >= 0 && j < numFiles) + { +# ifdef FEAT_GUI_MSWIN + ++count; + sprintf(ni.szTip, _("%d of %d edited"), + count, numFiles); + Shell_NotifyIcon(NIM_MODIFY, &ni); +# endif + done[j] = 1; + } + } +# ifdef FEAT_GUI_MSWIN + Shell_NotifyIcon(NIM_DELETE, &ni); +# endif + vim_free(done); + } + } + else if (STRICMP(argv[i], "--remote-expr") == 0) + { + if (i == *argc - 1) + mainerr_arg_missing((char_u *)argv[i]); +# ifdef MSWIN + // Win32 always works? + if (serverSendToVim(sname, (char_u *)argv[i + 1], + &res, NULL, 1, 0, FALSE) < 0) +# else + if (xterm_dpy == NULL) + mch_errmsg(_("No display: Send expression failed.\n")); + else if (serverSendToVim(xterm_dpy, sname, (char_u *)argv[i + 1], + &res, NULL, 1, 0, 1, FALSE) < 0) +# endif + { + if (res != NULL && *res != NUL) + { + // Output error from remote + mch_errmsg((char *)res); + VIM_CLEAR(res); + } + mch_errmsg(_(": Send expression failed.\n")); + } + } + else if (STRICMP(argv[i], "--serverlist") == 0) + { +# ifdef MSWIN + // Win32 always works? + res = serverGetVimNames(); +# else + if (xterm_dpy != NULL) + res = serverGetVimNames(xterm_dpy); +# endif + if (did_emsg) + mch_errmsg("\n"); + } + else if (STRICMP(argv[i], "--servername") == 0) + { + // Already processed. Take it out of the command line + i++; + continue; + } + else + { + *newArgV++ = argv[i]; + newArgC++; + continue; + } + didone = TRUE; + if (res != NULL && *res != NUL) + { + mch_msg((char *)res); + if (res[STRLEN(res) - 1] != '\n') + mch_msg("\n"); + } + vim_free(res); + } + + if (didone) + { + display_errors(); // display any collected messages + exit(exiterr); // Mission accomplished - get out + } + + // Return back into main() + *argc = newArgC; + vim_free(sname); +} + +/* + * Build a ":drop" command to send to a Vim server. + */ + static char_u * +build_drop_cmd( + int filec, + char **filev, + int tabs, // Use ":tab drop" instead of ":drop". + int sendReply) +{ + garray_T ga; + int i; + char_u *inicmd = NULL; + char_u *p; + char_u *cdp; + char_u *cwd; + + if (filec > 0 && filev[0][0] == '+') + { + inicmd = (char_u *)filev[0] + 1; + filev++; + filec--; + } + // Check if we have at least one argument. + if (filec <= 0) + mainerr_arg_missing((char_u *)filev[-1]); + + // Temporarily cd to the current directory to handle relative file names. + cwd = alloc(MAXPATHL); + if (cwd == NULL) + return NULL; + if (mch_dirname(cwd, MAXPATHL) != OK) + { + vim_free(cwd); + return NULL; + } + cdp = vim_strsave_escaped_ext(cwd, +#ifdef BACKSLASH_IN_FILENAME + (char_u *)"", // rem_backslash() will tell what chars to escape +#else + PATH_ESC_CHARS, +#endif + '\\', TRUE); + vim_free(cwd); + if (cdp == NULL) + return NULL; + ga_init2(&ga, 1, 100); + ga_concat(&ga, (char_u *)":cd "); + ga_concat(&ga, cdp); + + // Call inputsave() so that a prompt for an encryption key works. + ga_concat(&ga, (char_u *) + ":if exists('*inputsave')|call inputsave()|endif|"); + if (tabs) + ga_concat(&ga, (char_u *)"tab "); + ga_concat(&ga, (char_u *)"drop"); + for (i = 0; i < filec; i++) + { + // On Unix the shell has already expanded the wildcards, don't want to + // do it again in the Vim server. On MS-Windows only escape + // non-wildcard characters. + p = vim_strsave_escaped((char_u *)filev[i], +#ifdef UNIX + PATH_ESC_CHARS +#else + (char_u *)" \t%#" +#endif + ); + if (p == NULL) + { + vim_free(ga.ga_data); + return NULL; + } + ga_concat(&ga, (char_u *)" "); + ga_concat(&ga, p); + vim_free(p); + } + ga_concat(&ga, (char_u *) + "|if exists('*inputrestore')|call inputrestore()|endif"); + + // The :drop commands goes to Insert mode when 'insertmode' is set, use + // CTRL-\ CTRL-N again. + ga_concat(&ga, (char_u *)""); + + // Switch back to the correct current directory (prior to temporary path + // switch) unless 'autochdir' is set, in which case it will already be + // correct after the :drop command. With line breaks and spaces: + // if !exists('+acd') || !&acd + // if haslocaldir() + // cd - + // lcd - + // elseif getcwd() ==# 'current path' + // cd - + // endif + // endif + ga_concat(&ga, (char_u *)":if !exists('+acd')||!&acd|if haslocaldir()|"); + ga_concat(&ga, (char_u *)"cd -|lcd -|elseif getcwd() ==# '"); + ga_concat(&ga, cdp); + ga_concat(&ga, (char_u *)"'|cd -|endif|endif"); + vim_free(cdp); + + if (sendReply) + ga_concat(&ga, (char_u *)":call SetupRemoteReplies()"); + ga_concat(&ga, (char_u *)":"); + if (inicmd != NULL) + { + // Can't use after "inicmd", because a "startinsert" would cause + // the following commands to be inserted as text. Use a "|", + // hopefully "inicmd" does allow this... + ga_concat(&ga, inicmd); + ga_concat(&ga, (char_u *)"|"); + } + // Bring the window to the foreground, goto Insert mode when 'im' set and + // clear command line. + ga_concat(&ga, (char_u *)"cal foreground()|if &im|star|en|redr|f"); + ga_append(&ga, NUL); + return ga.ga_data; +} + +/* + * Make our basic server name: use the specified "arg" if given, otherwise use + * the tail of the command "cmd" we were started with. + * Return the name in allocated memory. This doesn't include a serial number. + */ + static char_u * +serverMakeName(char_u *arg, char *cmd) +{ + char_u *p; + + if (arg != NULL && *arg != NUL) + p = vim_strsave_up(arg); + else + { + p = vim_strsave_up(gettail((char_u *)cmd)); + // Remove .exe or .bat from the name. + if (p != NULL && vim_strchr(p, '.') != NULL) + *vim_strchr(p, '.') = NUL; + } + return p; +} +#endif // FEAT_CLIENTSERVER + +#if defined(FEAT_CLIENTSERVER) && defined(FEAT_X11) + static void +make_connection(void) +{ + if (X_DISPLAY == NULL +# ifdef FEAT_GUI + && !gui.in_use +# endif + ) + { + x_force_connect = TRUE; + setup_term_clip(); + x_force_connect = FALSE; + } +} + + static int +check_connection(void) +{ + make_connection(); + if (X_DISPLAY == NULL) + { + emsg(_(e_no_connection_to_x_server)); + return FAIL; + } + return OK; +} +#endif + +#ifdef FEAT_CLIENTSERVER + static void +remote_common(typval_T *argvars, typval_T *rettv, int expr) +{ + char_u *server_name; + char_u *keys; + char_u *r = NULL; + char_u buf[NUMBUFLEN]; + int timeout = 0; +# ifdef MSWIN + HWND w; +# else + Window w; +# endif + + if (check_restricted() || check_secure()) + return; + +# ifdef FEAT_X11 + if (check_connection() == FAIL) + return; +# endif + if (argvars[2].v_type != VAR_UNKNOWN + && argvars[3].v_type != VAR_UNKNOWN) + timeout = tv_get_number(&argvars[3]); + + server_name = tv_get_string_chk(&argvars[0]); + if (server_name == NULL) + return; // type error; errmsg already given + keys = tv_get_string_buf(&argvars[1], buf); +# ifdef MSWIN + if (serverSendToVim(server_name, keys, &r, &w, expr, timeout, TRUE) < 0) +# else + if (serverSendToVim(X_DISPLAY, server_name, keys, &r, &w, expr, timeout, + 0, TRUE) < 0) +# endif + { + if (r != NULL) + { + emsg((char *)r); // sending worked but evaluation failed + vim_free(r); + } + else + semsg(_(e_unable_to_send_to_str), server_name); + return; + } + + rettv->vval.v_string = r; + + if (argvars[2].v_type != VAR_UNKNOWN) + { + dictitem_T v; + char_u str[30]; + char_u *idvar; + + idvar = tv_get_string_chk(&argvars[2]); + if (idvar != NULL && *idvar != NUL) + { + sprintf((char *)str, PRINTF_HEX_LONG_U, (long_u)w); + v.di_tv.v_type = VAR_STRING; + v.di_tv.vval.v_string = vim_strsave(str); + set_var(idvar, &v.di_tv, FALSE); + vim_free(v.di_tv.vval.v_string); + } + } +} +#endif + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * "remote_expr()" function + */ + void +f_remote_expr(typval_T *argvars UNUSED, typval_T *rettv) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + +#ifdef FEAT_CLIENTSERVER + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_string_arg(argvars, 1) == FAIL + || check_for_opt_string_arg(argvars, 2) == FAIL + || (argvars[2].v_type != VAR_UNKNOWN + && check_for_opt_number_arg(argvars, 3) == FAIL))) + return; + + remote_common(argvars, rettv, TRUE); +#endif +} + +/* + * "remote_foreground()" function + */ + void +f_remote_foreground(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ +#ifdef FEAT_CLIENTSERVER + if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL) + return; + +# ifdef MSWIN + // On Win32 it's done in this application. + { + char_u *server_name = tv_get_string_chk(&argvars[0]); + + if (server_name != NULL) + serverForeground(server_name); + } +# else + // Send a foreground() expression to the server. + argvars[1].v_type = VAR_STRING; + argvars[1].vval.v_string = vim_strsave((char_u *)"foreground()"); + argvars[2].v_type = VAR_UNKNOWN; + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + remote_common(argvars, rettv, TRUE); + vim_free(argvars[1].vval.v_string); +# endif +#endif +} + + void +f_remote_peek(typval_T *argvars UNUSED, typval_T *rettv) +{ +#ifdef FEAT_CLIENTSERVER + dictitem_T v; + char_u *s = NULL; +# ifdef MSWIN + long_u n = 0; +# endif + char_u *serverid; + + rettv->vval.v_number = -1; + if (check_restricted() || check_secure()) + return; + + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_opt_string_arg(argvars, 1) == FAIL)) + return; + + serverid = tv_get_string_chk(&argvars[0]); + if (serverid == NULL) + return; // type error; errmsg already given +# ifdef MSWIN + sscanf((const char *)serverid, SCANF_HEX_LONG_U, &n); + if (n == 0) + rettv->vval.v_number = -1; + else + { + s = serverGetReply((HWND)n, FALSE, FALSE, FALSE, 0); + rettv->vval.v_number = (s != NULL); + } +# else + if (check_connection() == FAIL) + return; + + rettv->vval.v_number = serverPeekReply(X_DISPLAY, + serverStrToWin(serverid), &s); +# endif + + if (argvars[1].v_type != VAR_UNKNOWN && rettv->vval.v_number > 0) + { + char_u *retvar; + + v.di_tv.v_type = VAR_STRING; + v.di_tv.vval.v_string = vim_strsave(s); + retvar = tv_get_string_chk(&argvars[1]); + if (retvar != NULL) + set_var(retvar, &v.di_tv, FALSE); + vim_free(v.di_tv.vval.v_string); + } +#else + rettv->vval.v_number = -1; +#endif +} + + void +f_remote_read(typval_T *argvars UNUSED, typval_T *rettv) +{ + char_u *r = NULL; + +#ifdef FEAT_CLIENTSERVER + char_u *serverid; + + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_opt_number_arg(argvars, 1) == FAIL)) + return; + + serverid = tv_get_string_chk(&argvars[0]); + if (serverid != NULL && !check_restricted() && !check_secure()) + { + int timeout = 0; +# ifdef MSWIN + // The server's HWND is encoded in the 'id' parameter + long_u n = 0; +# endif + + if (argvars[1].v_type != VAR_UNKNOWN) + timeout = tv_get_number(&argvars[1]); + +# ifdef MSWIN + sscanf((char *)serverid, SCANF_HEX_LONG_U, &n); + if (n != 0) + r = serverGetReply((HWND)n, FALSE, TRUE, TRUE, timeout); + if (r == NULL) +# else + if (check_connection() == FAIL + || serverReadReply(X_DISPLAY, serverStrToWin(serverid), + &r, FALSE, timeout) < 0) +# endif + emsg(_(e_unable_to_read_server_reply)); + } +#endif + rettv->v_type = VAR_STRING; + rettv->vval.v_string = r; +} + +/* + * "remote_send()" function + */ + void +f_remote_send(typval_T *argvars UNUSED, typval_T *rettv) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + +#ifdef FEAT_CLIENTSERVER + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_string_arg(argvars, 1) == FAIL + || check_for_opt_string_arg(argvars, 2) == FAIL)) + return; + + remote_common(argvars, rettv, FALSE); +#endif +} + +/* + * "remote_startserver()" function + */ + void +f_remote_startserver(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ +#ifdef FEAT_CLIENTSERVER + if (check_for_nonempty_string_arg(argvars, 0) == FAIL) + return; + + if (serverName != NULL) + { + emsg(_(e_already_started_server)); + return; + } + + char_u *server = tv_get_string_chk(&argvars[0]); +# ifdef FEAT_X11 + if (check_connection() == OK) + serverRegisterName(X_DISPLAY, server); +# else + serverSetName(server); +# endif + +#else + emsg(_(e_clientserver_feature_not_available)); +#endif +} + + void +f_server2client(typval_T *argvars UNUSED, typval_T *rettv) +{ +#ifdef FEAT_CLIENTSERVER + char_u buf[NUMBUFLEN]; + char_u *server; + char_u *reply; + + rettv->vval.v_number = -1; + if (check_restricted() || check_secure()) + return; + + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_string_arg(argvars, 1) == FAIL)) + return; + + server = tv_get_string_chk(&argvars[0]); + reply = tv_get_string_buf_chk(&argvars[1], buf); + if (server == NULL || reply == NULL) + return; + +# ifdef FEAT_X11 + if (check_connection() == FAIL) + return; +# endif + + if (serverSendReply(server, reply) < 0) + { + emsg(_(e_unable_to_send_to_client)); + return; + } + rettv->vval.v_number = 0; +#else + rettv->vval.v_number = -1; +#endif +} + + void +f_serverlist(typval_T *argvars UNUSED, typval_T *rettv) +{ + char_u *r = NULL; + +#ifdef FEAT_CLIENTSERVER +# ifdef MSWIN + r = serverGetVimNames(); +# else + make_connection(); + if (X_DISPLAY != NULL) + r = serverGetVimNames(X_DISPLAY); +# endif +#endif + rettv->v_type = VAR_STRING; + rettv->vval.v_string = r; +} +#endif diff --git a/userland/apps/vim/src/clipboard.c b/userland/apps/vim/src/clipboard.c new file mode 100644 index 00000000..d80699b0 --- /dev/null +++ b/userland/apps/vim/src/clipboard.c @@ -0,0 +1,2228 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * clipboard.c: Functions to handle the clipboard + */ + +#include "vim.h" + +#ifdef FEAT_CYGWIN_WIN32_CLIPBOARD +# define WIN32_LEAN_AND_MEAN +# include +# include "winclip.pro" +#endif + +// Functions for copying and pasting text between applications. +// This is always included in a GUI version, but may also be included when the +// clipboard and mouse is available to a terminal version such as xterm. +// Note: there are some more functions in ops.c that handle selection stuff. +// +// Also note that the majority of functions here deal with the X 'primary' +// (visible - for Visual mode use) selection, and only that. There are no +// versions of these for the 'clipboard' selection, as Visual mode has no use +// for them. + +#if defined(FEAT_CLIPBOARD) || defined(PROTO) + +/* + * Selection stuff using Visual mode, for cutting and pasting text to other + * windows. + */ + +/* + * Call this to initialise the clipboard. Pass it FALSE if the clipboard code + * is included, but the clipboard can not be used, or TRUE if the clipboard can + * be used. Eg unix may call this with FALSE, then call it again with TRUE if + * the GUI starts. + */ + void +clip_init(int can_use) +{ + Clipboard_T *cb; + + cb = &clip_star; + for (;;) + { + cb->available = can_use; + cb->owned = FALSE; + cb->start.lnum = 0; + cb->start.col = 0; + cb->end.lnum = 0; + cb->end.col = 0; + cb->state = SELECT_CLEARED; + + if (cb == &clip_plus) + break; + cb = &clip_plus; + } +} + +/* + * 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. + */ + void +clip_update_selection(Clipboard_T *clip) +{ + pos_T start, end; + + // If visual mode is only due to a redo command ("."), then ignore it + if (!redo_VIsual_busy && VIsual_active && (State & MODE_NORMAL)) + { + if (LT_POS(VIsual, curwin->w_cursor)) + { + start = VIsual; + end = curwin->w_cursor; + if (has_mbyte) + end.col += (*mb_ptr2len)(ml_get_cursor()) - 1; + } + else + { + start = curwin->w_cursor; + end = VIsual; + } + if (!EQUAL_POS(clip->start, start) + || !EQUAL_POS(clip->end, end) + || clip->vmode != VIsual_mode) + { + clip_clear_selection(clip); + clip->start = start; + clip->end = end; + clip->vmode = VIsual_mode; + clip_free_selection(clip); + clip_own_selection(clip); + clip_gen_set_selection(clip); + } + } +} + + static int +clip_gen_own_selection(Clipboard_T *cbd) +{ +#ifdef FEAT_XCLIPBOARD +# ifdef FEAT_GUI + if (gui.in_use) + return clip_mch_own_selection(cbd); + else +# endif + return clip_xterm_own_selection(cbd); +#else + return clip_mch_own_selection(cbd); +#endif +} + + void +clip_own_selection(Clipboard_T *cbd) +{ + /* + * Also want to check somehow that we are reading from the keyboard rather + * than a mapping etc. + */ +#ifdef FEAT_X11 + // Always own the selection, we might have lost it without being + // notified, e.g. during a ":sh" command. + if (cbd->available) + { + int was_owned = cbd->owned; + + cbd->owned = (clip_gen_own_selection(cbd) == OK); + if (!was_owned && (cbd == &clip_star || cbd == &clip_plus)) + { + // May have to show a different kind of highlighting for the + // selected area. There is no specific redraw command for this, + // just redraw all windows on the current buffer. + if (cbd->owned + && (get_real_state() == MODE_VISUAL + || get_real_state() == MODE_SELECT) + && (cbd == &clip_star ? clip_isautosel_star() + : clip_isautosel_plus()) + && HL_ATTR(HLF_V) != HL_ATTR(HLF_VNC)) + redraw_curbuf_later(UPD_INVERTED_ALL); + } + } +#else + // Only own the clipboard when we didn't own it yet. + if (!cbd->owned && cbd->available) + cbd->owned = (clip_gen_own_selection(cbd) == OK); +#endif +} + + static void +clip_gen_lose_selection(Clipboard_T *cbd) +{ +#ifdef FEAT_XCLIPBOARD +# ifdef FEAT_GUI + if (gui.in_use) + clip_mch_lose_selection(cbd); + else +# endif + clip_xterm_lose_selection(cbd); +#else + clip_mch_lose_selection(cbd); +#endif +} + + void +clip_lose_selection(Clipboard_T *cbd) +{ +#ifdef FEAT_X11 + int was_owned = cbd->owned; +#endif + int visual_selection = FALSE; + + if (cbd == &clip_star || cbd == &clip_plus) + visual_selection = TRUE; + + clip_free_selection(cbd); + cbd->owned = FALSE; + if (visual_selection) + clip_clear_selection(cbd); + clip_gen_lose_selection(cbd); +#ifdef FEAT_X11 + if (visual_selection) + { + // May have to show a different kind of highlighting for the selected + // area. There is no specific redraw command for this, just redraw all + // windows on the current buffer. + if (was_owned + && (get_real_state() == MODE_VISUAL + || get_real_state() == MODE_SELECT) + && (cbd == &clip_star ? + clip_isautosel_star() : clip_isautosel_plus()) + && HL_ATTR(HLF_V) != HL_ATTR(HLF_VNC) + && !exiting) + { + update_curbuf(UPD_INVERTED_ALL); + setcursor(); + cursor_on(); + out_flush_cursor(TRUE, FALSE); + } + } +#endif +} + + static void +clip_copy_selection(Clipboard_T *clip) +{ + if (VIsual_active && (State & MODE_NORMAL) && clip->available) + { + clip_update_selection(clip); + clip_free_selection(clip); + clip_own_selection(clip); + if (clip->owned) + clip_get_selection(clip); + clip_gen_set_selection(clip); + } +} + +/* + * Save and restore clip_unnamed before doing possibly many changes. This + * prevents accessing the clipboard very often which might slow down Vim + * considerably. + */ +static int global_change_count = 0; // if set, inside a start_global_changes +static int clipboard_needs_update = FALSE; // clipboard needs to be updated +static int clip_did_set_selection = TRUE; + +/* + * Save clip_unnamed and reset it. + */ + void +start_global_changes(void) +{ + if (++global_change_count > 1) + return; + clip_unnamed_saved = clip_unnamed; + clipboard_needs_update = FALSE; + + if (clip_did_set_selection) + { + clip_unnamed = 0; + clip_did_set_selection = FALSE; + } +} + +/* + * Return TRUE if setting the clipboard was postponed, it already contains the + * right text. + */ + static int +is_clipboard_needs_update(void) +{ + return clipboard_needs_update; +} + +/* + * Restore clip_unnamed and set the selection when needed. + */ + void +end_global_changes(void) +{ + if (--global_change_count > 0) + // recursive + return; + if (!clip_did_set_selection) + { + clip_did_set_selection = TRUE; + clip_unnamed = clip_unnamed_saved; + clip_unnamed_saved = 0; + if (clipboard_needs_update) + { + // only store something in the clipboard, + // if we have yanked anything to it + if (clip_unnamed & CLIP_UNNAMED) + { + clip_own_selection(&clip_star); + clip_gen_set_selection(&clip_star); + } + if (clip_unnamed & CLIP_UNNAMED_PLUS) + { + clip_own_selection(&clip_plus); + clip_gen_set_selection(&clip_plus); + } + } + } + clipboard_needs_update = FALSE; +} + +/* + * Called when Visual mode is ended: update the selection. + */ + void +clip_auto_select(void) +{ + if (clip_isautosel_star()) + clip_copy_selection(&clip_star); + if (clip_isautosel_plus()) + clip_copy_selection(&clip_plus); +} + +/* + * Return TRUE if automatic selection of Visual area is desired for the * + * register. + */ + int +clip_isautosel_star(void) +{ + return ( +#ifdef FEAT_GUI + gui.in_use ? (vim_strchr(p_go, GO_ASEL) != NULL) : +#endif + clip_autoselect_star); +} + +/* + * Return TRUE if automatic selection of Visual area is desired for the + + * register. + */ + int +clip_isautosel_plus(void) +{ + return ( +#ifdef FEAT_GUI + gui.in_use ? (vim_strchr(p_go, GO_ASELPLUS) != NULL) : +#endif + clip_autoselect_plus); +} + + +/* + * Stuff for general mouse selection, without using Visual mode. + */ + +/* + * Compare two screen positions ala strcmp() + */ + static int +clip_compare_pos( + int row1, + int col1, + int row2, + int col2) +{ + if (row1 > row2) return(1); + if (row1 < row2) return(-1); + if (col1 > col2) return(1); + if (col1 < col2) return(-1); + return(0); +} + +// "how" flags for clip_invert_area() +#define CLIP_CLEAR 1 +#define CLIP_SET 2 +#define CLIP_TOGGLE 3 + +/* + * Invert or un-invert a rectangle of the screen. + * "invert" is true if the result is inverted. + */ + static void +clip_invert_rectangle( + Clipboard_T *cbd UNUSED, + int row_arg, + int col_arg, + int height_arg, + int width_arg, + int invert) +{ + int row = row_arg; + int col = col_arg; + int height = height_arg; + int width = width_arg; + +#ifdef FEAT_PROP_POPUP + // this goes on top of all popup windows + screen_zindex = CLIP_ZINDEX; + + if (col < cbd->min_col) + { + width -= cbd->min_col - col; + col = cbd->min_col; + } + if (width > cbd->max_col - col) + width = cbd->max_col - col; + if (row < cbd->min_row) + { + height -= cbd->min_row - row; + row = cbd->min_row; + } + if (height > cbd->max_row - row + 1) + height = cbd->max_row - row + 1; +#endif +#ifdef FEAT_GUI + if (gui.in_use) + gui_mch_invert_rectangle(row, col, height, width); + else +#endif + screen_draw_rectangle(row, col, height, width, invert); +#ifdef FEAT_PROP_POPUP + screen_zindex = 0; +#endif +} + +/* + * Invert a region of the display between a starting and ending row and column + * Values for "how": + * CLIP_CLEAR: undo inversion + * CLIP_SET: set inversion + * CLIP_TOGGLE: set inversion if pos1 < pos2, undo inversion otherwise. + * 0: invert (GUI only). + */ + static void +clip_invert_area( + Clipboard_T *cbd, + int row1, + int col1, + int row2, + int col2, + int how) +{ + int invert = FALSE; + int max_col; + +#ifdef FEAT_PROP_POPUP + max_col = cbd->max_col - 1; +#else + max_col = Columns - 1; +#endif + + if (how == CLIP_SET) + invert = TRUE; + + // Swap the from and to positions so the from is always before + if (clip_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; + } + else if (how == CLIP_TOGGLE) + invert = TRUE; + + // If all on the same line, do it the easy way + if (row1 == row2) + { + clip_invert_rectangle(cbd, row1, col1, 1, col2 - col1, invert); + } + else + { + // Handle a piece of the first line + if (col1 > 0) + { + clip_invert_rectangle(cbd, row1, col1, 1, + (int)Columns - col1, invert); + row1++; + } + + // Handle a piece of the last line + if (col2 < max_col) + { + clip_invert_rectangle(cbd, row2, 0, 1, col2, invert); + row2--; + } + + // Handle the rectangle that's left + if (row2 >= row1) + clip_invert_rectangle(cbd, row1, 0, row2 - row1 + 1, + (int)Columns, invert); + } +} + +/* + * Start, continue or end a modeless selection. Used when editing the + * command-line, in the cmdline window and when the mouse is in a popup window. + */ + void +clip_modeless(int button, int is_click, int is_drag) +{ + int repeat; + + repeat = ((clip_star.mode == SELECT_MODE_CHAR + || clip_star.mode == SELECT_MODE_LINE) + && (mod_mask & MOD_MASK_2CLICK)) + || (clip_star.mode == SELECT_MODE_WORD + && (mod_mask & MOD_MASK_3CLICK)); + if (is_click && button == MOUSE_RIGHT) + { + // Right mouse button: If there was no selection, start one. + // Otherwise extend the existing selection. + if (clip_star.state == SELECT_CLEARED) + clip_start_selection(mouse_col, mouse_row, FALSE); + clip_process_selection(button, mouse_col, mouse_row, repeat); + } + else if (is_click) + clip_start_selection(mouse_col, mouse_row, repeat); + else if (is_drag) + { + // Don't try extending a selection if there isn't one. Happens when + // button-down is in the cmdline and them moving mouse upwards. + if (clip_star.state != SELECT_CLEARED) + clip_process_selection(button, mouse_col, mouse_row, repeat); + } + else // release + clip_process_selection(MOUSE_RELEASE, mouse_col, mouse_row, FALSE); +} + +/* + * Update the currently selected region by adding and/or subtracting from the + * beginning or end and inverting the changed area(s). + */ + static void +clip_update_modeless_selection( + Clipboard_T *cb, + int row1, + int col1, + int row2, + int col2) +{ + // See if we changed at the beginning of the selection + if (row1 != cb->start.lnum || col1 != (int)cb->start.col) + { + clip_invert_area(cb, row1, col1, (int)cb->start.lnum, cb->start.col, + CLIP_TOGGLE); + cb->start.lnum = row1; + cb->start.col = col1; + } + + // See if we changed at the end of the selection + if (row2 != cb->end.lnum || col2 != (int)cb->end.col) + { + clip_invert_area(cb, (int)cb->end.lnum, cb->end.col, row2, col2, + CLIP_TOGGLE); + cb->end.lnum = row2; + cb->end.col = col2; + } +} + +/* + * Find the starting and ending positions of the word at the given row and + * column. Only white-separated words are recognized here. + */ +#define CHAR_CLASS(c) (c <= ' ' ? ' ' : vim_iswordc(c)) + + static void +clip_get_word_boundaries(Clipboard_T *cb, int row, int col) +{ + int start_class; + int temp_col; + char_u *p; + int mboff; + + if (row >= screen_Rows || col >= screen_Columns || ScreenLines == NULL) + return; + + p = ScreenLines + LineOffset[row]; + // Correct for starting in the right half of a double-wide char + if (enc_dbcs != 0) + col -= dbcs_screen_head_off(p, p + col); + else if (enc_utf8 && p[col] == 0) + --col; + start_class = CHAR_CLASS(p[col]); + + temp_col = col; + for ( ; temp_col > 0; temp_col--) + if (enc_dbcs != 0 + && (mboff = dbcs_screen_head_off(p, p + temp_col - 1)) > 0) + temp_col -= mboff; + else if (CHAR_CLASS(p[temp_col - 1]) != start_class + && !(enc_utf8 && p[temp_col - 1] == 0)) + break; + cb->word_start_col = temp_col; + + temp_col = col; + for ( ; temp_col < screen_Columns; temp_col++) + if (enc_dbcs != 0 && dbcs_ptr2cells(p + temp_col) == 2) + ++temp_col; + else if (CHAR_CLASS(p[temp_col]) != start_class + && !(enc_utf8 && p[temp_col] == 0)) + break; + cb->word_end_col = temp_col; +} + +/* + * Find the column position for the last non-whitespace character on the given + * line at or before start_col. + */ + static int +clip_get_line_end(Clipboard_T *cbd UNUSED, int row) +{ + int i; + + if (row >= screen_Rows || ScreenLines == NULL) + return 0; + for (i = +#ifdef FEAT_PROP_POPUP + cbd->max_col; +#else + screen_Columns; +#endif + i > 0; i--) + if (ScreenLines[LineOffset[row] + i - 1] != ' ') + break; + return i; +} + +/* + * Start the selection + */ + void +clip_start_selection(int col, int row, int repeated_click) +{ + Clipboard_T *cb = &clip_star; +#ifdef FEAT_PROP_POPUP + win_T *wp; + int row_cp = row; + int col_cp = col; + + wp = mouse_find_win(&row_cp, &col_cp, FIND_POPUP); + if (wp != NULL && WIN_IS_POPUP(wp) + && popup_is_in_scrollbar(wp, row_cp, col_cp)) + // click or double click in scrollbar does not start a selection + return; +#endif + + if (cb->state == SELECT_DONE) + clip_clear_selection(cb); + + row = check_row(row); + col = check_col(col); + col = mb_fix_col(col, row); + + cb->start.lnum = row; + cb->start.col = col; + cb->end = cb->start; + cb->origin_row = (short_u)cb->start.lnum; + cb->state = SELECT_IN_PROGRESS; +#ifdef FEAT_PROP_POPUP + if (wp != NULL && WIN_IS_POPUP(wp)) + { + // Click in a popup window restricts selection to that window, + // excluding the border. + cb->min_col = wp->w_wincol + wp->w_popup_border[3]; + cb->max_col = wp->w_wincol + popup_width(wp) + - wp->w_popup_border[1] - wp->w_has_scrollbar; + if (cb->max_col > screen_Columns) + cb->max_col = screen_Columns; + cb->min_row = wp->w_winrow + wp->w_popup_border[0]; + cb->max_row = wp->w_winrow + popup_height(wp) - 1 + - wp->w_popup_border[2]; + } + else + { + cb->min_col = 0; + cb->max_col = screen_Columns; + cb->min_row = 0; + cb->max_row = screen_Rows; + } +#endif + + if (repeated_click) + { + if (++cb->mode > SELECT_MODE_LINE) + cb->mode = SELECT_MODE_CHAR; + } + else + cb->mode = SELECT_MODE_CHAR; + +#ifdef FEAT_GUI + // clear the cursor until the selection is made + if (gui.in_use) + gui_undraw_cursor(); +#endif + + switch (cb->mode) + { + case SELECT_MODE_CHAR: + cb->origin_start_col = cb->start.col; + cb->word_end_col = clip_get_line_end(cb, (int)cb->start.lnum); + break; + + case SELECT_MODE_WORD: + clip_get_word_boundaries(cb, (int)cb->start.lnum, cb->start.col); + cb->origin_start_col = cb->word_start_col; + cb->origin_end_col = cb->word_end_col; + + clip_invert_area(cb, (int)cb->start.lnum, cb->word_start_col, + (int)cb->end.lnum, cb->word_end_col, CLIP_SET); + cb->start.col = cb->word_start_col; + cb->end.col = cb->word_end_col; + break; + + case SELECT_MODE_LINE: + clip_invert_area(cb, (int)cb->start.lnum, 0, (int)cb->start.lnum, + (int)Columns, CLIP_SET); + cb->start.col = 0; + cb->end.col = Columns; + break; + } + + cb->prev = cb->start; + +#ifdef DEBUG_SELECTION + printf("Selection started at (%ld,%d)\n", cb->start.lnum, cb->start.col); +#endif +} + +/* + * Continue processing the selection + */ + void +clip_process_selection( + int button, + int col, + int row, + int_u repeated_click) +{ + Clipboard_T *cb = &clip_star; + int diff; + int slen = 1; // cursor shape width + + if (button == MOUSE_RELEASE) + { + if (cb->state != SELECT_IN_PROGRESS) + return; + + // Check to make sure we have something selected + if (cb->start.lnum == cb->end.lnum && cb->start.col == cb->end.col) + { +#ifdef FEAT_GUI + if (gui.in_use) + gui_update_cursor(FALSE, FALSE); +#endif + cb->state = SELECT_CLEARED; + return; + } + +#ifdef DEBUG_SELECTION + printf("Selection ended: (%ld,%d) to (%ld,%d)\n", cb->start.lnum, + cb->start.col, cb->end.lnum, cb->end.col); +#endif + if (clip_isautosel_star() + || ( +#ifdef FEAT_GUI + gui.in_use ? (vim_strchr(p_go, GO_ASELML) != NULL) : +#endif + clip_autoselectml)) + clip_copy_modeless_selection(FALSE); +#ifdef FEAT_GUI + if (gui.in_use) + gui_update_cursor(FALSE, FALSE); +#endif + + cb->state = SELECT_DONE; + return; + } + + row = check_row(row); + col = check_col(col); + col = mb_fix_col(col, row); + + if (col == (int)cb->prev.col && row == cb->prev.lnum && !repeated_click) + return; + + /* + * When extending the selection with the right mouse button, swap the + * start and end if the position is before half the selection + */ + if (cb->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 (clip_compare_pos(row, col, (int)cb->start.lnum, cb->start.col) < 0 + || (clip_compare_pos(row, col, + (int)cb->end.lnum, cb->end.col) < 0 + && (((cb->start.lnum == cb->end.lnum + && cb->end.col - col > col - cb->start.col)) + || ((diff = (cb->end.lnum - row) - + (row - cb->start.lnum)) > 0 + || (diff == 0 && col < (int)(cb->start.col + + cb->end.col) / 2))))) + { + cb->origin_row = (short_u)cb->end.lnum; + cb->origin_start_col = cb->end.col - 1; + cb->origin_end_col = cb->end.col; + } + else + { + cb->origin_row = (short_u)cb->start.lnum; + cb->origin_start_col = cb->start.col; + cb->origin_end_col = cb->start.col; + } + if (cb->mode == SELECT_MODE_WORD && !repeated_click) + cb->mode = SELECT_MODE_CHAR; + } + + // set state, for when using the right mouse button + cb->state = SELECT_IN_PROGRESS; + +#ifdef DEBUG_SELECTION + printf("Selection extending to (%d,%d)\n", row, col); +#endif + + if (repeated_click && ++cb->mode > SELECT_MODE_LINE) + cb->mode = SELECT_MODE_CHAR; + + switch (cb->mode) + { + case SELECT_MODE_CHAR: + // If we're on a different line, find where the line ends + if (row != cb->prev.lnum) + cb->word_end_col = clip_get_line_end(cb, row); + + // See if we are before or after the origin of the selection + if (clip_compare_pos(row, col, cb->origin_row, + cb->origin_start_col) >= 0) + { + if (col >= (int)cb->word_end_col) + clip_update_modeless_selection(cb, cb->origin_row, + cb->origin_start_col, row, (int)Columns); + else + { + if (has_mbyte && mb_lefthalve(row, col)) + slen = 2; + clip_update_modeless_selection(cb, cb->origin_row, + cb->origin_start_col, row, col + slen); + } + } + else + { + if (has_mbyte + && mb_lefthalve(cb->origin_row, cb->origin_start_col)) + slen = 2; + if (col >= (int)cb->word_end_col) + clip_update_modeless_selection(cb, row, cb->word_end_col, + cb->origin_row, cb->origin_start_col + slen); + else + clip_update_modeless_selection(cb, row, col, + cb->origin_row, cb->origin_start_col + slen); + } + break; + + case SELECT_MODE_WORD: + // If we are still within the same word, do nothing + if (row == cb->prev.lnum && col >= (int)cb->word_start_col + && col < (int)cb->word_end_col && !repeated_click) + return; + + // Get new word boundaries + clip_get_word_boundaries(cb, row, col); + + // Handle being after the origin point of selection + if (clip_compare_pos(row, col, cb->origin_row, + cb->origin_start_col) >= 0) + clip_update_modeless_selection(cb, cb->origin_row, + cb->origin_start_col, row, cb->word_end_col); + else + clip_update_modeless_selection(cb, row, cb->word_start_col, + cb->origin_row, cb->origin_end_col); + break; + + case SELECT_MODE_LINE: + if (row == cb->prev.lnum && !repeated_click) + return; + + if (clip_compare_pos(row, col, cb->origin_row, + cb->origin_start_col) >= 0) + clip_update_modeless_selection(cb, cb->origin_row, 0, row, + (int)Columns); + else + clip_update_modeless_selection(cb, row, 0, cb->origin_row, + (int)Columns); + break; + } + + cb->prev.lnum = row; + cb->prev.col = col; + +#ifdef DEBUG_SELECTION + printf("Selection is: (%ld,%d) to (%ld,%d)\n", cb->start.lnum, + cb->start.col, cb->end.lnum, cb->end.col); +#endif +} + +# if defined(FEAT_GUI) || defined(PROTO) +/* + * Redraw part of the selection if character at "row,col" is inside of it. + * Only used for the GUI. + */ + void +clip_may_redraw_selection(int row, int col, int len) +{ + int start = col; + int end = col + len; + + if (clip_star.state != SELECT_CLEARED + && row >= clip_star.start.lnum + && row <= clip_star.end.lnum) + { + if (row == clip_star.start.lnum && start < (int)clip_star.start.col) + start = clip_star.start.col; + if (row == clip_star.end.lnum && end > (int)clip_star.end.col) + end = clip_star.end.col; + if (end > start) + clip_invert_area(&clip_star, row, start, row, end, 0); + } +} +# endif + +/* + * Called from outside to clear selected region from the display + */ + void +clip_clear_selection(Clipboard_T *cbd) +{ + + if (cbd->state == SELECT_CLEARED) + return; + + clip_invert_area(cbd, (int)cbd->start.lnum, cbd->start.col, + (int)cbd->end.lnum, cbd->end.col, CLIP_CLEAR); + cbd->state = SELECT_CLEARED; +} + +/* + * Clear the selection if any lines from "row1" to "row2" are inside of it. + */ + void +clip_may_clear_selection(int row1, int row2) +{ + if (clip_star.state == SELECT_DONE + && row2 >= clip_star.start.lnum + && row1 <= clip_star.end.lnum) + clip_clear_selection(&clip_star); +} + +/* + * Called before the screen is scrolled up or down. Adjusts the line numbers + * of the selection. Call with big number when clearing the screen. + */ + void +clip_scroll_selection( + int rows) // negative for scroll down +{ + int lnum; + + if (clip_star.state == SELECT_CLEARED) + return; + + lnum = clip_star.start.lnum - rows; + if (lnum <= 0) + clip_star.start.lnum = 0; + else if (lnum >= screen_Rows) // scrolled off of the screen + clip_star.state = SELECT_CLEARED; + else + clip_star.start.lnum = lnum; + + lnum = clip_star.end.lnum - rows; + if (lnum < 0) // scrolled off of the screen + clip_star.state = SELECT_CLEARED; + else if (lnum >= screen_Rows) + clip_star.end.lnum = screen_Rows - 1; + else + clip_star.end.lnum = lnum; +} + +/* + * Copy the currently selected area into the '*' register so it will be + * available for pasting. + * When "both" is TRUE also copy to the '+' register. + */ + void +clip_copy_modeless_selection(int both UNUSED) +{ + char_u *buffer; + char_u *bufp; + int row; + int start_col; + int end_col; + int line_end_col; + int add_newline_flag = FALSE; + int len; + char_u *p; + int row1 = clip_star.start.lnum; + int col1 = clip_star.start.col; + int row2 = clip_star.end.lnum; + int col2 = clip_star.end.col; + + // Can't use ScreenLines unless initialized + if (ScreenLines == NULL) + return; + + /* + * Make sure row1 <= row2, and if row1 == row2 that col1 <= col2. + */ + if (row1 > row2) + { + row = row1; row1 = row2; row2 = row; + row = col1; col1 = col2; col2 = row; + } + else if (row1 == row2 && col1 > col2) + { + row = col1; col1 = col2; col2 = row; + } +#ifdef FEAT_PROP_POPUP + if (col1 < clip_star.min_col) + col1 = clip_star.min_col; + if (col2 > clip_star.max_col) + col2 = clip_star.max_col; + if (row1 > clip_star.max_row || row2 < clip_star.min_row) + return; + if (row1 < clip_star.min_row) + row1 = clip_star.min_row; + if (row2 > clip_star.max_row) + row2 = clip_star.max_row; +#endif + // correct starting point for being on right half of double-wide char + p = ScreenLines + LineOffset[row1]; + if (enc_dbcs != 0) + col1 -= (*mb_head_off)(p, p + col1); + else if (enc_utf8 && p[col1] == 0) + --col1; + + // Create a temporary buffer for storing the text + len = (row2 - row1 + 1) * Columns + 1; + if (enc_dbcs != 0) + len *= 2; // max. 2 bytes per display cell + else if (enc_utf8) + len *= MB_MAXBYTES; + buffer = alloc(len); + if (buffer == NULL) // out of memory + return; + + // Process each row in the selection + for (bufp = buffer, row = row1; row <= row2; row++) + { + if (row == row1) + start_col = col1; + else +#ifdef FEAT_PROP_POPUP + start_col = clip_star.min_col; +#else + start_col = 0; +#endif + + if (row == row2) + end_col = col2; + else +#ifdef FEAT_PROP_POPUP + end_col = clip_star.max_col; +#else + end_col = Columns; +#endif + + line_end_col = clip_get_line_end(&clip_star, row); + + // See if we need to nuke some trailing whitespace + if (end_col >= +#ifdef FEAT_PROP_POPUP + clip_star.max_col +#else + Columns +#endif + && (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 && !LineWraps[row - 1]) + *bufp++ = NL; + + // Safetey check for in case resizing went wrong + if (row < screen_Rows && end_col <= screen_Columns) + { + if (enc_dbcs != 0) + { + int i; + + p = ScreenLines + LineOffset[row]; + for (i = start_col; i < end_col; ++i) + if (enc_dbcs == DBCS_JPNU && p[i] == 0x8e) + { + // single-width double-byte char + *bufp++ = 0x8e; + *bufp++ = ScreenLines2[LineOffset[row] + i]; + } + else + { + *bufp++ = p[i]; + if (MB_BYTE2LEN(p[i]) == 2) + *bufp++ = p[++i]; + } + } + else if (enc_utf8) + { + int off; + int i; + int ci; + + off = LineOffset[row]; + for (i = start_col; i < end_col; ++i) + { + // The base character is either in ScreenLinesUC[] or + // ScreenLines[]. + if (ScreenLinesUC[off + i] == 0) + *bufp++ = ScreenLines[off + i]; + else + { + bufp += utf_char2bytes(ScreenLinesUC[off + i], bufp); + for (ci = 0; ci < Screen_mco; ++ci) + { + // Add a composing character. + if (ScreenLinesC[ci][off + i] == 0) + break; + bufp += utf_char2bytes(ScreenLinesC[ci][off + i], + bufp); + } + } + // Skip right half of double-wide character. + if (ScreenLines[off + i + 1] == 0) + ++i; + } + } + else + { + STRNCPY(bufp, ScreenLines + LineOffset[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; + + // First cleanup any old selection and become the owner. + clip_free_selection(&clip_star); + clip_own_selection(&clip_star); + + // Yank the text into the '*' register. + clip_yank_selection(MCHAR, buffer, (long)(bufp - buffer), &clip_star); + + // Make the register contents available to the outside world. + clip_gen_set_selection(&clip_star); + +#ifdef FEAT_X11 + if (both) + { + // Do the same for the '+' register. + clip_free_selection(&clip_plus); + clip_own_selection(&clip_plus); + clip_yank_selection(MCHAR, buffer, (long)(bufp - buffer), &clip_plus); + clip_gen_set_selection(&clip_plus); + } +#endif + vim_free(buffer); +} + + void +clip_gen_set_selection(Clipboard_T *cbd) +{ + if (!clip_did_set_selection) + { + // Updating postponed, so that accessing the system clipboard won't + // hang Vim when accessing it many times (e.g. on a :g command). + if ((cbd == &clip_plus && (clip_unnamed_saved & CLIP_UNNAMED_PLUS)) + || (cbd == &clip_star && (clip_unnamed_saved & CLIP_UNNAMED))) + { + clipboard_needs_update = TRUE; + return; + } + } +#ifdef FEAT_XCLIPBOARD +# ifdef FEAT_GUI + if (gui.in_use) + clip_mch_set_selection(cbd); + else +# endif + clip_xterm_set_selection(cbd); +#else + clip_mch_set_selection(cbd); +#endif +} + + static void +clip_gen_request_selection(Clipboard_T *cbd) +{ +#ifdef FEAT_XCLIPBOARD +# ifdef FEAT_GUI + if (gui.in_use) + clip_mch_request_selection(cbd); + else +# endif + clip_xterm_request_selection(cbd); +#else + clip_mch_request_selection(cbd); +#endif +} + +#if (defined(FEAT_X11) && defined(FEAT_XCLIPBOARD) && defined(USE_SYSTEM)) \ + || defined(PROTO) + static int +clip_x11_owner_exists(Clipboard_T *cbd) +{ + return XGetSelectionOwner(X_DISPLAY, cbd->sel_atom) != None; +} +#endif + +#if (defined(FEAT_X11) && defined(USE_SYSTEM)) || defined(PROTO) + int +clip_gen_owner_exists(Clipboard_T *cbd UNUSED) +{ +#ifdef FEAT_XCLIPBOARD +# ifdef FEAT_GUI_GTK + if (gui.in_use) + return clip_gtk_owner_exists(cbd); + else +# endif + return clip_x11_owner_exists(cbd); +#else + return TRUE; +#endif +} +#endif + +/* + * Extract the items in the 'clipboard' option and set global values. + * Return an error message or NULL for success. + */ + char * +did_set_clipboard(optset_T *args UNUSED) +{ + int new_unnamed = 0; + int new_autoselect_star = FALSE; + int new_autoselect_plus = FALSE; + int new_autoselectml = FALSE; + int new_html = FALSE; + regprog_T *new_exclude_prog = NULL; + char *errmsg = NULL; + char_u *p; + + for (p = p_cb; *p != NUL; ) + { + // Note: Keep this in sync with p_cb_values. + if (STRNCMP(p, "unnamed", 7) == 0 && (p[7] == ',' || p[7] == NUL)) + { + new_unnamed |= CLIP_UNNAMED; + p += 7; + } + else if (STRNCMP(p, "unnamedplus", 11) == 0 + && (p[11] == ',' || p[11] == NUL)) + { + new_unnamed |= CLIP_UNNAMED_PLUS; + p += 11; + } + else if (STRNCMP(p, "autoselect", 10) == 0 + && (p[10] == ',' || p[10] == NUL)) + { + new_autoselect_star = TRUE; + p += 10; + } + else if (STRNCMP(p, "autoselectplus", 14) == 0 + && (p[14] == ',' || p[14] == NUL)) + { + new_autoselect_plus = TRUE; + p += 14; + } + else if (STRNCMP(p, "autoselectml", 12) == 0 + && (p[12] == ',' || p[12] == NUL)) + { + new_autoselectml = TRUE; + p += 12; + } + else if (STRNCMP(p, "html", 4) == 0 && (p[4] == ',' || p[4] == NUL)) + { + new_html = TRUE; + p += 4; + } + else if (STRNCMP(p, "exclude:", 8) == 0 && new_exclude_prog == NULL) + { + p += 8; + new_exclude_prog = vim_regcomp(p, RE_MAGIC); + if (new_exclude_prog == NULL) + errmsg = e_invalid_argument; + break; + } + else + { + errmsg = e_invalid_argument; + break; + } + if (*p == ',') + ++p; + } + if (errmsg == NULL) + { + if (global_busy) + // clip_unnamed will be reset to clip_unnamed_saved + // at end_global_changes + clip_unnamed_saved = new_unnamed; + else + clip_unnamed = new_unnamed; + clip_autoselect_star = new_autoselect_star; + clip_autoselect_plus = new_autoselect_plus; + clip_autoselectml = new_autoselectml; + clip_html = new_html; + vim_regfree(clip_exclude_prog); + clip_exclude_prog = new_exclude_prog; +#ifdef FEAT_GUI_GTK + if (gui.in_use) + { + gui_gtk_set_selection_targets(); + gui_gtk_set_dnd_targets(); + } +#endif + } + else + vim_regfree(new_exclude_prog); + + return errmsg; +} + +/* + * Stuff for the X clipboard. Shared between VMS and Unix. + */ + +#if defined(FEAT_XCLIPBOARD) || defined(FEAT_GUI_X11) || defined(PROTO) +# include +# include + +/* + * Open the application context (if it hasn't been opened yet). + * Used for Motif GUI and the xterm clipboard. + */ + void +open_app_context(void) +{ + if (app_context == NULL) + { + XtToolkitInitialize(); + app_context = XtCreateApplicationContext(); + } +} + +static Atom vim_atom; // Vim's own special selection format +static Atom vimenc_atom; // Vim's extended selection format +static Atom utf8_atom; +static Atom compound_text_atom; +static Atom text_atom; +static Atom targets_atom; +static Atom timestamp_atom; // Used to get a timestamp + + void +x11_setup_atoms(Display *dpy) +{ + vim_atom = XInternAtom(dpy, VIM_ATOM_NAME, False); + vimenc_atom = XInternAtom(dpy, VIMENC_ATOM_NAME,False); + utf8_atom = XInternAtom(dpy, "UTF8_STRING", False); + compound_text_atom = XInternAtom(dpy, "COMPOUND_TEXT", False); + text_atom = XInternAtom(dpy, "TEXT", False); + targets_atom = XInternAtom(dpy, "TARGETS", False); + clip_star.sel_atom = XA_PRIMARY; + clip_plus.sel_atom = XInternAtom(dpy, "CLIPBOARD", False); + timestamp_atom = XInternAtom(dpy, "TIMESTAMP", False); +} + +/* + * X Selection stuff, for cutting and pasting text to other windows. + */ + + static Boolean +clip_x11_convert_selection_cb( + Widget w UNUSED, + Atom *sel_atom, + Atom *target, + Atom *type, + XtPointer *value, + long_u *length, + int *format) +{ + static char_u *save_result = NULL; + static long_u save_length = 0; + char_u *string; + int motion_type; + Clipboard_T *cbd; + int i; + + if (*sel_atom == clip_plus.sel_atom) + cbd = &clip_plus; + else + cbd = &clip_star; + + if (!cbd->owned) + return False; // Shouldn't ever happen + + // requestor wants to know what target types we support + if (*target == targets_atom) + { + static Atom array[7]; + + *value = (XtPointer)array; + i = 0; + array[i++] = targets_atom; + array[i++] = vimenc_atom; + array[i++] = vim_atom; + if (enc_utf8) + array[i++] = utf8_atom; + array[i++] = XA_STRING; + array[i++] = text_atom; + array[i++] = compound_text_atom; + + *type = XA_ATOM; + // This used to be: *format = sizeof(Atom) * 8; but that caused + // crashes on 64 bit machines. (Peter Derr) + *format = 32; + *length = i; + return True; + } + + if ( *target != XA_STRING + && *target != vimenc_atom + && (*target != utf8_atom || !enc_utf8) + && *target != vim_atom + && *target != text_atom + && *target != compound_text_atom) + return False; + + clip_get_selection(cbd); + motion_type = clip_convert_selection(&string, length, cbd); + if (motion_type < 0) + return False; + + // For our own format, the first byte contains the motion type + if (*target == vim_atom) + (*length)++; + + // Our own format with encoding: motion 'encoding' NUL text + if (*target == vimenc_atom) + *length += STRLEN(p_enc) + 2; + + if (save_length < *length || save_length / 2 >= *length) + *value = XtRealloc((char *)save_result, (Cardinal)*length + 1); + else + *value = save_result; + if (*value == NULL) + { + vim_free(string); + return False; + } + save_result = (char_u *)*value; + save_length = *length; + + if (*target == XA_STRING || (*target == utf8_atom && enc_utf8)) + { + mch_memmove(save_result, string, (size_t)(*length)); + *type = *target; + } + else if (*target == compound_text_atom || *target == text_atom) + { + XTextProperty text_prop; + char *string_nt = (char *)save_result; + int conv_result; + + // create NUL terminated string which XmbTextListToTextProperty wants + mch_memmove(string_nt, string, (size_t)*length); + string_nt[*length] = NUL; + conv_result = XmbTextListToTextProperty(X_DISPLAY, &string_nt, + 1, XCompoundTextStyle, &text_prop); + if (conv_result != Success) + { + vim_free(string); + return False; + } + *value = (XtPointer)(text_prop.value); // from plain text + *length = text_prop.nitems; + *type = compound_text_atom; + XtFree((char *)save_result); + save_result = (char_u *)*value; + save_length = *length; + } + else if (*target == vimenc_atom) + { + int l = STRLEN(p_enc); + + save_result[0] = motion_type; + STRCPY(save_result + 1, p_enc); + mch_memmove(save_result + l + 2, string, (size_t)(*length - l - 2)); + *type = vimenc_atom; + } + else + { + save_result[0] = motion_type; + mch_memmove(save_result + 1, string, (size_t)(*length - 1)); + *type = vim_atom; + } + *format = 8; // 8 bits per char + vim_free(string); + return True; +} + + static void +clip_x11_lose_ownership_cb(Widget w UNUSED, Atom *sel_atom) +{ + if (*sel_atom == clip_plus.sel_atom) + clip_lose_selection(&clip_plus); + else + clip_lose_selection(&clip_star); +} + + static void +clip_x11_notify_cb(Widget w UNUSED, Atom *sel_atom UNUSED, Atom *target UNUSED) +{ + // To prevent automatically freeing the selection value. +} + +/* + * Property callback to get a timestamp for XtOwnSelection. + */ +# if (defined(FEAT_X11) && defined(FEAT_XCLIPBOARD)) || defined(PROTO) + static void +clip_x11_timestamp_cb( + Widget w, + XtPointer n UNUSED, + XEvent *event, + Boolean *cont UNUSED) +{ + Atom actual_type; + int format; + unsigned long nitems, bytes_after; + unsigned char *prop=NULL; + XPropertyEvent *xproperty=&event->xproperty; + + // Must be a property notify, state can't be Delete (True), has to be + // one of the supported selection types. + if (event->type != PropertyNotify || xproperty->state + || (xproperty->atom != clip_star.sel_atom + && xproperty->atom != clip_plus.sel_atom)) + return; + + if (XGetWindowProperty(xproperty->display, xproperty->window, + xproperty->atom, 0, 0, False, timestamp_atom, &actual_type, &format, + &nitems, &bytes_after, &prop)) + return; + + if (prop) + XFree(prop); + + // Make sure the property type is "TIMESTAMP" and it's 32 bits. + if (actual_type != timestamp_atom || format != 32) + return; + + // Get the selection, using the event timestamp. + if (XtOwnSelection(w, xproperty->atom, xproperty->time, + clip_x11_convert_selection_cb, clip_x11_lose_ownership_cb, + clip_x11_notify_cb) == OK) + { + // Set the "owned" flag now, there may have been a call to + // lose_ownership_cb in between. + if (xproperty->atom == clip_plus.sel_atom) + clip_plus.owned = TRUE; + else + clip_star.owned = TRUE; + } +} + + void +x11_setup_selection(Widget w) +{ + XtAddEventHandler(w, PropertyChangeMask, False, + /*(XtEventHandler)*/clip_x11_timestamp_cb, (XtPointer)NULL); +} +# endif + + static void +clip_x11_request_selection_cb( + Widget w UNUSED, + XtPointer success, + Atom *sel_atom, + Atom *type, + XtPointer value, + long_u *length, + int *format) +{ + int motion_type = MAUTO; + long_u len; + char_u *p; + char **text_list = NULL; + Clipboard_T *cbd; + char_u *tmpbuf = NULL; + + if (*sel_atom == clip_plus.sel_atom) + cbd = &clip_plus; + else + cbd = &clip_star; + + if (value == NULL || *length == 0) + { + clip_free_selection(cbd); // nothing received, clear register + *(int *)success = FALSE; + return; + } + p = (char_u *)value; + len = *length; + if (*type == vim_atom) + { + motion_type = *p++; + len--; + } + + else if (*type == vimenc_atom) + { + char_u *enc; + vimconv_T conv; + int convlen; + + motion_type = *p++; + --len; + + enc = p; + p += STRLEN(p) + 1; + len -= p - enc; + + // If the encoding of the text is different from 'encoding', attempt + // converting it. + conv.vc_type = CONV_NONE; + convert_setup(&conv, enc, p_enc); + if (conv.vc_type != CONV_NONE) + { + convlen = len; // Need to use an int here. + tmpbuf = string_convert(&conv, p, &convlen); + len = convlen; + if (tmpbuf != NULL) + p = tmpbuf; + convert_setup(&conv, NULL, NULL); + } + } + + else if (*type == compound_text_atom + || *type == utf8_atom + || (enc_dbcs != 0 && *type == text_atom)) + { + XTextProperty text_prop; + int n_text = 0; + int status; + + text_prop.value = (unsigned char *)value; + text_prop.encoding = *type; + text_prop.format = *format; + text_prop.nitems = len; +#if defined(X_HAVE_UTF8_STRING) + if (*type == utf8_atom) + status = Xutf8TextPropertyToTextList(X_DISPLAY, &text_prop, + &text_list, &n_text); + else +#endif + status = XmbTextPropertyToTextList(X_DISPLAY, &text_prop, + &text_list, &n_text); + if (status != Success || n_text < 1) + { + *(int *)success = FALSE; + return; + } + p = (char_u *)text_list[0]; + len = STRLEN(p); + } + clip_yank_selection(motion_type, p, (long)len, cbd); + + if (text_list != NULL) + XFreeStringList(text_list); + vim_free(tmpbuf); + XtFree((char *)value); + *(int *)success = TRUE; +} + + void +clip_x11_request_selection( + Widget myShell, + Display *dpy, + Clipboard_T *cbd) +{ + XEvent event; + Atom type; + static int success; + int i; + time_t start_time; + int timed_out = FALSE; + + for (i = 0; i < 6; i++) + { + switch (i) + { + case 0: type = vimenc_atom; break; + case 1: type = vim_atom; break; + case 2: type = utf8_atom; break; + case 3: type = compound_text_atom; break; + case 4: type = text_atom; break; + default: type = XA_STRING; + } + if (type == utf8_atom +# if defined(X_HAVE_UTF8_STRING) + && !enc_utf8 +# endif + ) + // Only request utf-8 when 'encoding' is utf8 and + // Xutf8TextPropertyToTextList is available. + continue; + success = MAYBE; + XtGetSelectionValue(myShell, cbd->sel_atom, type, + clip_x11_request_selection_cb, (XtPointer)&success, CurrentTime); + + // Make sure the request for the selection goes out before waiting for + // a response. + XFlush(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. + */ + start_time = time(NULL); + while (success == MAYBE) + { + if (XCheckTypedEvent(dpy, PropertyNotify, &event) + || XCheckTypedEvent(dpy, SelectionNotify, &event) + || XCheckTypedEvent(dpy, SelectionRequest, &event)) + { + // This is where clip_x11_request_selection_cb() should be + // called. It may actually happen a bit later, so we loop + // until "success" changes. + // We may get a SelectionRequest here and if we don't handle + // it we hang. KDE klipper does this, for example. + // We need to handle a PropertyNotify for large selections. + XtDispatchEvent(&event); + continue; + } + + // Time out after 2 to 3 seconds to avoid that we hang when the + // other process doesn't respond. Note that the SelectionNotify + // event may still come later when the selection owner comes back + // to life and the text gets inserted unexpectedly. Don't know + // why that happens or how to avoid that :-(. + if (time(NULL) > start_time + 2) + { + timed_out = TRUE; + break; + } + + // Do we need this? Probably not. + XSync(dpy, False); + + // Wait for 1 msec to avoid that we eat up all CPU time. + ui_delay(1L, TRUE); + } + + if (success == TRUE) + return; + + // don't do a retry with another type after timing out, otherwise we + // hang for 15 seconds. + if (timed_out) + break; + } + + // Final fallback position - use the X CUT_BUFFER0 store + yank_cut_buffer0(dpy, cbd); +} + + void +clip_x11_lose_selection(Widget myShell, Clipboard_T *cbd) +{ + XtDisownSelection(myShell, cbd->sel_atom, + XtLastTimestampProcessed(XtDisplay(myShell))); +} + + int +clip_x11_own_selection(Widget myShell, Clipboard_T *cbd) +{ + // When using the GUI we have proper timestamps, use the one of the last + // event. When in the console we don't get events (the terminal gets + // them), Get the time by a zero-length append, clip_x11_timestamp_cb will + // be called with the current timestamp. +#ifdef FEAT_GUI + if (gui.in_use) + { + if (XtOwnSelection(myShell, cbd->sel_atom, + XtLastTimestampProcessed(XtDisplay(myShell)), + clip_x11_convert_selection_cb, clip_x11_lose_ownership_cb, + clip_x11_notify_cb) == False) + return FAIL; + } + else +#endif + { + if (!XChangeProperty(XtDisplay(myShell), XtWindow(myShell), + cbd->sel_atom, timestamp_atom, 32, PropModeAppend, NULL, 0)) + return FAIL; + } + // Flush is required in a terminal as nothing else is doing it. + XFlush(XtDisplay(myShell)); + return OK; +} + +/* + * Send the current selection to the clipboard. Do nothing for X because we + * will fill in the selection only when requested by another app. + */ + void +clip_x11_set_selection(Clipboard_T *cbd UNUSED) +{ +} + +#endif + +#if defined(FEAT_XCLIPBOARD) || defined(FEAT_GUI_X11) \ + || defined(FEAT_GUI_GTK) || defined(PROTO) +/* + * Get the contents of the X CUT_BUFFER0 and put it in "cbd". + */ + void +yank_cut_buffer0(Display *dpy, Clipboard_T *cbd) +{ + int nbytes = 0; + char_u *buffer = (char_u *)XFetchBuffer(dpy, &nbytes, 0); + + if (nbytes > 0) + { + int done = FALSE; + + // CUT_BUFFER0 is supposed to be always latin1. Convert to 'enc' when + // using a multi-byte encoding. Conversion between two 8-bit + // character sets usually fails and the text might actually be in + // 'enc' anyway. + if (has_mbyte) + { + char_u *conv_buf; + vimconv_T vc; + + vc.vc_type = CONV_NONE; + if (convert_setup(&vc, (char_u *)"latin1", p_enc) == OK) + { + conv_buf = string_convert(&vc, buffer, &nbytes); + if (conv_buf != NULL) + { + clip_yank_selection(MCHAR, conv_buf, (long)nbytes, cbd); + vim_free(conv_buf); + done = TRUE; + } + convert_setup(&vc, NULL, NULL); + } + } + if (!done) // use the text without conversion + clip_yank_selection(MCHAR, buffer, (long)nbytes, cbd); + XFree((void *)buffer); + if (p_verbose > 0) + { + verbose_enter(); + verb_msg(_("Used CUT_BUFFER0 instead of empty selection")); + verbose_leave(); + } + } +} +#endif + +/* + * SELECTION / PRIMARY ('*') + * + * 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>. " + * If not under X, it is synonymous with the clipboard register '+'. + * + * X CLIPBOARD ('+') + * + * Text selection stuff that uses the GUI clipboard register '+'. + * Under X, this matches the standard cut/paste buffer CLIPBOARD selection. + * It will be used for unnamed cut/pasting is 'clipboard' contains "unnamed", + * otherwise you will need to do <"+p>. " + * If not under X, it is synonymous with the selection register '*'. + */ + +/* + * Routine to export any final X selection we had to the environment + * so that the text is still available after Vim has exited. X selections + * only exist while the owning application exists, so we write to the + * permanent (while X runs) store CUT_BUFFER0. + * Dump the CLIPBOARD selection if we own it (it's logically the more + * 'permanent' of the two), otherwise the PRIMARY one. + * For now, use a hard-coded sanity limit of 1Mb of data. + */ +#if (defined(FEAT_X11) && defined(FEAT_CLIPBOARD)) || defined(PROTO) + void +x11_export_final_selection(void) +{ + Display *dpy; + char_u *str = NULL; + long_u len = 0; + int motion_type = -1; + +# ifdef FEAT_GUI + if (gui.in_use) + dpy = X_DISPLAY; + else +# endif +# ifdef FEAT_XCLIPBOARD + dpy = xterm_dpy; +# else + return; +# endif + + // Get selection to export + if (clip_plus.owned) + motion_type = clip_convert_selection(&str, &len, &clip_plus); + else if (clip_star.owned) + motion_type = clip_convert_selection(&str, &len, &clip_star); + + // Check it's OK + if (dpy != NULL && str != NULL && motion_type >= 0 + && len < 1024*1024 && len > 0) + { + int ok = TRUE; + + // The CUT_BUFFER0 is supposed to always contain latin1. Convert from + // 'enc' when it is a multi-byte encoding. When 'enc' is an 8-bit + // encoding conversion usually doesn't work, so keep the text as-is. + if (has_mbyte) + { + vimconv_T vc; + + vc.vc_type = CONV_NONE; + if (convert_setup(&vc, p_enc, (char_u *)"latin1") == OK) + { + int intlen = len; + char_u *conv_str; + + vc.vc_fail = TRUE; + conv_str = string_convert(&vc, str, &intlen); + len = intlen; + if (conv_str != NULL) + { + vim_free(str); + str = conv_str; + } + else + { + ok = FALSE; + } + convert_setup(&vc, NULL, NULL); + } + else + { + ok = FALSE; + } + } + + // Do not store the string if conversion failed. Better to use any + // other selection than garbled text. + if (ok) + { + XStoreBuffer(dpy, (char *)str, (int)len, 0); + XFlush(dpy); + } + } + + vim_free(str); +} +#endif + + void +clip_free_selection(Clipboard_T *cbd) +{ + yankreg_T *y_ptr = get_y_current(); + + if (cbd == &clip_plus) + set_y_current(get_y_register(PLUS_REGISTER)); + else + set_y_current(get_y_register(STAR_REGISTER)); + free_yank_all(); + get_y_current()->y_size = 0; + set_y_current(y_ptr); +} + +/* + * Get the selected text and put it in register '*' or '+'. + */ + void +clip_get_selection(Clipboard_T *cbd) +{ + yankreg_T *old_y_previous, *old_y_current; + pos_T old_cursor; + pos_T old_visual; + int old_visual_mode; + colnr_T old_curswant; + int old_set_curswant; + pos_T old_op_start, old_op_end; + oparg_T oa; + cmdarg_T ca; + + if (cbd->owned) + { + if ((cbd == &clip_plus + && get_y_register(PLUS_REGISTER)->y_array != NULL) + || (cbd == &clip_star + && get_y_register(STAR_REGISTER)->y_array != NULL)) + return; + + // Avoid triggering autocmds such as TextYankPost. + block_autocmds(); + + // Get the text between clip_star.start & clip_star.end + old_y_previous = get_y_previous(); + old_y_current = get_y_current(); + old_cursor = curwin->w_cursor; + old_curswant = curwin->w_curswant; + old_set_curswant = curwin->w_set_curswant; + old_op_start = curbuf->b_op_start; + old_op_end = curbuf->b_op_end; + old_visual = VIsual; + old_visual_mode = VIsual_mode; + clear_oparg(&oa); + oa.regname = (cbd == &clip_plus ? '+' : '*'); + oa.op_type = OP_YANK; + CLEAR_FIELD(ca); + ca.oap = &oa; + ca.cmdchar = 'y'; + ca.count1 = 1; + ca.retval = CA_NO_ADJ_OP_END; + do_pending_operator(&ca, 0, TRUE); + + // restore things + set_y_previous(old_y_previous); + set_y_current(old_y_current); + curwin->w_cursor = old_cursor; + changed_cline_bef_curs(); // need to update w_virtcol et al + curwin->w_curswant = old_curswant; + curwin->w_set_curswant = old_set_curswant; + curbuf->b_op_start = old_op_start; + curbuf->b_op_end = old_op_end; + VIsual = old_visual; + VIsual_mode = old_visual_mode; + + unblock_autocmds(); + } + else if (!is_clipboard_needs_update()) + { + clip_free_selection(cbd); + + // Try to get selected text from another window + clip_gen_request_selection(cbd); + } +} + +/* + * Convert from the GUI selection string into the '*'/'+' register. + */ + void +clip_yank_selection( + int type, + char_u *str, + long len, + Clipboard_T *cbd) +{ + yankreg_T *y_ptr; + + if (cbd == &clip_plus) + y_ptr = get_y_register(PLUS_REGISTER); + else + y_ptr = get_y_register(STAR_REGISTER); + + clip_free_selection(cbd); + + str_to_reg(y_ptr, type, str, len, -1, FALSE); +} + +/* + * Convert the '*'/'+' register into a GUI selection string returned in *str + * with length *len. + * Returns the motion type, or -1 for failure. + */ + int +clip_convert_selection(char_u **str, long_u *len, Clipboard_T *cbd) +{ + char_u *p; + int lnum; + int i, j; + int_u eolsize; + yankreg_T *y_ptr; + + if (cbd == &clip_plus) + y_ptr = get_y_register(PLUS_REGISTER); + else + y_ptr = get_y_register(STAR_REGISTER); + +# ifdef USE_CRNL + eolsize = 2; +# else + eolsize = 1; +# endif + + *str = NULL; + *len = 0; + if (y_ptr->y_array == NULL) + return -1; + + for (i = 0; i < y_ptr->y_size; i++) + *len += (long_u)STRLEN(y_ptr->y_array[i]) + eolsize; + + // Don't want newline character at end of last line if we're in MCHAR mode. + if (y_ptr->y_type == MCHAR && *len >= eolsize) + *len -= eolsize; + + p = *str = alloc(*len + 1); // add one to avoid zero + if (p == NULL) + return -1; + lnum = 0; + for (i = 0, j = 0; i < (int)*len; i++, j++) + { + if (y_ptr->y_array[lnum][j] == '\n') + p[i] = NUL; + else if (y_ptr->y_array[lnum][j] == NUL) + { +# ifdef USE_CRNL + p[i++] = '\r'; +# endif + p[i] = '\n'; + lnum++; + j = -1; + } + else + p[i] = y_ptr->y_array[lnum][j]; + } + return y_ptr->y_type; +} + +/* + * When "regname" is a clipboard register, obtain the selection. If it's not + * available return zero, otherwise return "regname". + */ + int +may_get_selection(int regname) +{ + if (regname == '*') + { + if (!clip_star.available) + regname = 0; + else + clip_get_selection(&clip_star); + } + else if (regname == '+') + { + if (!clip_plus.available) + regname = 0; + else + clip_get_selection(&clip_plus); + } + return regname; +} + +/* + * If we have written to a clipboard register, send the text to the clipboard. + */ + void +may_set_selection(void) +{ + if ((get_y_current() == get_y_register(STAR_REGISTER)) + && clip_star.available) + { + clip_own_selection(&clip_star); + clip_gen_set_selection(&clip_star); + } + else if ((get_y_current() == get_y_register(PLUS_REGISTER)) + && clip_plus.available) + { + clip_own_selection(&clip_plus); + clip_gen_set_selection(&clip_plus); + } +} + +/* + * Adjust the register name pointed to with "rp" for the clipboard being + * used always and the clipboard being available. + */ + void +adjust_clip_reg(int *rp) +{ + // If no reg. specified, and "unnamed" or "unnamedplus" is in 'clipboard', + // use '*' or '+' reg, respectively. "unnamedplus" prevails. + if (*rp == 0 && (clip_unnamed != 0 || clip_unnamed_saved != 0)) + { + if (clip_unnamed != 0) + *rp = ((clip_unnamed & CLIP_UNNAMED_PLUS) && clip_plus.available) + ? '+' : '*'; + else + *rp = ((clip_unnamed_saved & CLIP_UNNAMED_PLUS) + && clip_plus.available) ? '+' : '*'; + } + if (!clip_star.available && *rp == '*') + *rp = 0; + if (!clip_plus.available && *rp == '+') + *rp = 0; +} + +#endif // FEAT_CLIPBOARD diff --git a/userland/apps/vim/src/cmdexpand.c b/userland/apps/vim/src/cmdexpand.c new file mode 100644 index 00000000..8bccaa3b --- /dev/null +++ b/userland/apps/vim/src/cmdexpand.c @@ -0,0 +1,4191 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * cmdexpand.c: functions for command-line completion + */ + +#include "vim.h" + +static int cmd_showtail; // Only show path tail in lists ? + +static int ExpandFromContext(expand_T *xp, char_u *, char_u ***, int *, int); +static char_u *showmatches_gettail(char_u *s); +static int expand_showtail(expand_T *xp); +static int expand_shellcmd(char_u *filepat, char_u ***matches, int *numMatches, int flagsarg); +#if defined(FEAT_EVAL) +static int ExpandUserDefined(char_u *pat, expand_T *xp, regmatch_T *regmatch, char_u ***matches, int *numMatches); +static int ExpandUserList(expand_T *xp, char_u ***matches, int *numMatches); +#endif + +// "compl_match_array" points the currently displayed list of entries in the +// popup menu. It is NULL when there is no popup menu. +static pumitem_T *compl_match_array = NULL; +static int compl_match_arraysize; +// First column in cmdline of the matched item for completion. +static int compl_startcol; +static int compl_selected; + +#define SHOW_MATCH(m) (showtail ? showmatches_gettail(matches[m]) : matches[m]) + +/* + * Returns TRUE if fuzzy completion is supported for a given cmdline completion + * context. + */ + static int +cmdline_fuzzy_completion_supported(expand_T *xp) +{ + return (vim_strchr(p_wop, WOP_FUZZY) != NULL + && xp->xp_context != EXPAND_BOOL_SETTINGS + && xp->xp_context != EXPAND_COLORS + && xp->xp_context != EXPAND_COMPILER + && xp->xp_context != EXPAND_DIRECTORIES + && xp->xp_context != EXPAND_FILES + && xp->xp_context != EXPAND_FILES_IN_PATH + && xp->xp_context != EXPAND_FILETYPE + && xp->xp_context != EXPAND_HELP + && xp->xp_context != EXPAND_OLD_SETTING + && xp->xp_context != EXPAND_STRING_SETTING + && xp->xp_context != EXPAND_SETTING_SUBTRACT + && xp->xp_context != EXPAND_OWNSYNTAX + && xp->xp_context != EXPAND_PACKADD + && xp->xp_context != EXPAND_RUNTIME + && xp->xp_context != EXPAND_SHELLCMD + && xp->xp_context != EXPAND_TAGS + && xp->xp_context != EXPAND_TAGS_LISTFILES + && xp->xp_context != EXPAND_USER_LIST); +} + +/* + * Returns TRUE if fuzzy completion for cmdline completion is enabled and + * 'fuzzystr' is not empty. If search pattern is empty, then don't use fuzzy + * matching. + */ + int +cmdline_fuzzy_complete(char_u *fuzzystr) +{ + return vim_strchr(p_wop, WOP_FUZZY) != NULL && *fuzzystr != NUL; +} + +/* + * sort function for the completion matches. + * functions should be sorted to the end. + */ + static int +sort_func_compare(const void *s1, const void *s2) +{ + char_u *p1 = *(char_u **)s1; + char_u *p2 = *(char_u **)s2; + + if (*p1 != '<' && *p2 == '<') return -1; + if (*p1 == '<' && *p2 != '<') return 1; + return STRCMP(p1, p2); +} + +/* + * Escape special characters in the cmdline completion matches. + */ + static void +wildescape( + expand_T *xp, + char_u *str, + int numfiles, + char_u **files) +{ + char_u *p; + int vse_what = xp->xp_context == EXPAND_BUFFERS + ? VSE_BUFFER : VSE_NONE; + + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_FILES_IN_PATH + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS + || xp->xp_context == EXPAND_DIRECTORIES) + { + // Insert a backslash into a file name before a space, \, %, # + // and wildmatch characters, except '~'. + for (int i = 0; i < numfiles; ++i) + { + // for ":set path=" we need to escape spaces twice + if (xp->xp_backslash & XP_BS_THREE) + { + char *pat = (xp->xp_backslash & XP_BS_COMMA) ? " ," : " "; + p = vim_strsave_escaped(files[i], (char_u *)pat); + if (p != NULL) + { + vim_free(files[i]); + files[i] = p; +#if defined(BACKSLASH_IN_FILENAME) + p = vim_strsave_escaped(files[i], (char_u *)" "); + if (p != NULL) + { + vim_free(files[i]); + files[i] = p; + } +#endif + } + } + else if (xp->xp_backslash & XP_BS_COMMA) + { + if (vim_strchr(files[i], ',') != NULL) + { + p = vim_strsave_escaped(files[i], (char_u *)","); + if (p != NULL) + { + vim_free(files[i]); + files[i] = p; + } + } + } +#ifdef BACKSLASH_IN_FILENAME + p = vim_strsave_fnameescape(files[i], vse_what); +#else + p = vim_strsave_fnameescape(files[i], + xp->xp_shell ? VSE_SHELL : vse_what); +#endif + if (p != NULL) + { + vim_free(files[i]); + files[i] = p; + } + + // If 'str' starts with "\~", replace "~" at start of + // files[i] with "\~". + if (str[0] == '\\' && str[1] == '~' && files[i][0] == '~') + escape_fname(&files[i]); + } + xp->xp_backslash = XP_BS_NONE; + + // If the first file starts with a '+' escape it. Otherwise it + // could be seen as "+cmd". + if (*files[0] == '+') + escape_fname(&files[0]); + } + else if (xp->xp_context == EXPAND_TAGS) + { + // Insert a backslash before characters in a tag name that + // would terminate the ":tag" command. + for (int i = 0; i < numfiles; ++i) + { + p = vim_strsave_escaped(files[i], (char_u *)"\\|\""); + if (p != NULL) + { + vim_free(files[i]); + files[i] = p; + } + } + } +} + +/* + * Escape special characters in the cmdline completion matches. + */ + static void +ExpandEscape( + expand_T *xp, + char_u *str, + int numfiles, + char_u **files, + int options) +{ + // May change home directory back to "~" + if (options & WILD_HOME_REPLACE) + tilde_replace(str, numfiles, files); + + if (options & WILD_ESCAPE) + wildescape(xp, str, numfiles, files); +} + +/* + * Return FAIL if this is not an appropriate context in which to do + * completion of anything, return OK 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. + */ + int +nextwild( + expand_T *xp, + int type, + int options, // extra options for ExpandOne() + int escape) // if TRUE, escape the returned matches +{ + cmdline_info_T *ccline = get_cmdline_info(); + int i, j; + char_u *p1; + char_u *p2; + int difflen; + int v; + + if (xp->xp_numfiles == -1) + { + set_expand_context(xp); + cmd_showtail = expand_showtail(xp); + } + + if (xp->xp_context == EXPAND_UNSUCCESSFUL) + { + beep_flush(); + return OK; // Something illegal on command line + } + if (xp->xp_context == EXPAND_NOTHING) + { + // Caller can use the character as a normal char instead + return FAIL; + } + + // If cmd_silent is set then don't show the dots, because redrawcmd() below + // won't remove them. + if (!cmd_silent) + { + msg_puts("..."); // show that we are busy + out_flush(); + } + + i = (int)(xp->xp_pattern - ccline->cmdbuff); + xp->xp_pattern_len = ccline->cmdpos - i; + + if (type == WILD_NEXT || type == WILD_PREV + || type == WILD_PAGEUP || type == WILD_PAGEDOWN) + { + // Get next/previous match for a previous expanded pattern. + p2 = ExpandOne(xp, NULL, NULL, 0, type); + } + else + { + if (cmdline_fuzzy_completion_supported(xp)) + // If fuzzy matching, don't modify the search string + p1 = vim_strnsave(xp->xp_pattern, xp->xp_pattern_len); + else + p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + + // Translate string into pattern and expand it. + if (p1 == NULL) + p2 = NULL; + else + { + int use_options = options | + WILD_HOME_REPLACE|WILD_ADD_SLASH|WILD_SILENT; + if (escape) + use_options |= WILD_ESCAPE; + + if (p_wic) + use_options += WILD_ICASE; + p2 = ExpandOne(xp, p1, + vim_strnsave(&ccline->cmdbuff[i], xp->xp_pattern_len), + use_options, type); + vim_free(p1); + // longest match: make sure it is not shorter, happens with :help + if (p2 != NULL && type == WILD_LONGEST) + { + for (j = 0; j < xp->xp_pattern_len; ++j) + if (ccline->cmdbuff[i + j] == '*' + || ccline->cmdbuff[i + j] == '?') + break; + if ((int)STRLEN(p2) < j) + VIM_CLEAR(p2); + } + } + } + + if (p2 != NULL && !got_int) + { + difflen = (int)STRLEN(p2) - xp->xp_pattern_len; + if (ccline->cmdlen + difflen + 4 > ccline->cmdbufflen) + { + v = realloc_cmdbuff(ccline->cmdlen + difflen + 4); + xp->xp_pattern = ccline->cmdbuff + i; + } + else + v = OK; + if (v == OK) + { + mch_memmove(&ccline->cmdbuff[ccline->cmdpos + difflen], + &ccline->cmdbuff[ccline->cmdpos], + (size_t)(ccline->cmdlen - ccline->cmdpos + 1)); + mch_memmove(&ccline->cmdbuff[i], p2, STRLEN(p2)); + ccline->cmdlen += difflen; + ccline->cmdpos += difflen; + } + } + vim_free(p2); + + redrawcmd(); + cursorcmd(); + + // When expanding a ":map" command and no matches are found, assume that + // the key is supposed to be inserted literally + if (xp->xp_context == EXPAND_MAPPINGS && p2 == NULL) + return FAIL; + + if (xp->xp_numfiles <= 0 && p2 == NULL) + beep_flush(); + else if (xp->xp_numfiles == 1) + // free expanded pattern + (void)ExpandOne(xp, NULL, NULL, 0, WILD_FREE); + + return OK; +} + +/* + * Create and display a cmdline completion popup menu with items from + * 'matches'. + */ + static int +cmdline_pum_create( + cmdline_info_T *ccline, + expand_T *xp, + char_u **matches, + int numMatches, + int showtail) +{ + int i; + int columns; + + // Add all the completion matches + compl_match_arraysize = numMatches; + compl_match_array = ALLOC_MULT(pumitem_T, compl_match_arraysize); + for (i = 0; i < numMatches; i++) + { + compl_match_array[i].pum_text = SHOW_MATCH(i); + compl_match_array[i].pum_info = NULL; + compl_match_array[i].pum_extra = NULL; + compl_match_array[i].pum_kind = NULL; + } + + // Compute the popup menu starting column + compl_startcol = vim_strsize(ccline->cmdbuff) + 1; + columns = vim_strsize(xp->xp_pattern); + if (showtail) + { + columns += vim_strsize(showmatches_gettail(matches[0])); + columns -= vim_strsize(matches[0]); + } + if (columns >= compl_startcol) + compl_startcol = 0; + else + compl_startcol -= columns; + + // no default selection + compl_selected = -1; + + cmdline_pum_display(); + + return EXPAND_OK; +} + +/* + * Display the cmdline completion matches in a popup menu + */ + void +cmdline_pum_display(void) +{ + pum_display(compl_match_array, compl_match_arraysize, compl_selected); +} + +/* + * Returns TRUE if the cmdline completion popup menu is being displayed. + */ + int +cmdline_pum_active(void) +{ + return pum_visible() && compl_match_array != NULL; +} + +/* + * Remove the cmdline completion popup menu (if present), free the list of + * items and refresh the screen. + */ + void +cmdline_pum_remove(void) +{ + int save_p_lz = p_lz; + int save_KeyTyped = KeyTyped; + + pum_undisplay(); + VIM_CLEAR(compl_match_array); + p_lz = FALSE; // avoid the popup menu hanging around + update_screen(0); + p_lz = save_p_lz; + redrawcmd(); + + // When a function is called (e.g. for 'foldtext') KeyTyped might be reset + // as a side effect. + KeyTyped = save_KeyTyped; +} + + void +cmdline_pum_cleanup(cmdline_info_T *cclp) +{ + cmdline_pum_remove(); + wildmenu_cleanup(cclp); +} + +/* + * Returns the starting column number to use for the cmdline completion popup + * menu. + */ + int +cmdline_compl_startcol(void) +{ + return compl_startcol; +} + +/* + * Return the number of characters that should be skipped in a status match. + * These are backslashes used for escaping. Do show backslashes in help tags. + */ + static int +skip_status_match_char(expand_T *xp, char_u *s) +{ + if ((rem_backslash(s) && xp->xp_context != EXPAND_HELP) +#ifdef FEAT_MENU + || ((xp->xp_context == EXPAND_MENUS + || xp->xp_context == EXPAND_MENUNAMES) + && (s[0] == '\t' || (s[0] == '\\' && s[1] != NUL))) +#endif + ) + { +#ifndef BACKSLASH_IN_FILENAME + if (xp->xp_shell && csh_like_shell() && s[1] == '\\' && s[2] == '!') + return 2; +#endif + return 1; + } + return 0; +} + +/* + * Get the length of an item as it will be shown in the status line. + */ + static int +status_match_len(expand_T *xp, char_u *s) +{ + int len = 0; + +#ifdef FEAT_MENU + int emenu = xp->xp_context == EXPAND_MENUS + || xp->xp_context == EXPAND_MENUNAMES; + + // Check for menu separators - replace with '|'. + if (emenu && menu_is_separator(s)) + return 1; +#endif + + while (*s != NUL) + { + s += skip_status_match_char(xp, s); + len += ptr2cells(s); + MB_PTR_ADV(s); + } + + return len; +} + +/* + * Show wildchar matches in the status line. + * Show at least the "match" item. + * We start at item 'first_match' in the list and show all matches that fit. + * + * If inversion is possible we use it. Else '=' characters are used. + */ + static void +win_redr_status_matches( + expand_T *xp, + int num_matches, + char_u **matches, // list of matches + int match, + int showtail) +{ + int row; + char_u *buf; + int len; + int clen; // length in screen cells + int fillchar; + int attr; + int i; + int highlight = TRUE; + char_u *selstart = NULL; + int selstart_col = 0; + char_u *selend = NULL; + static int first_match = 0; + int add_left = FALSE; + char_u *s; +#ifdef FEAT_MENU + int emenu; +#endif + int l; + + if (matches == NULL) // interrupted completion? + return; + + if (has_mbyte) + buf = alloc(Columns * MB_MAXBYTES + 1); + else + buf = alloc(Columns + 1); + if (buf == NULL) + return; + + if (match == -1) // don't show match but original text + { + match = 0; + highlight = FALSE; + } + // count 1 for the ending ">" + clen = status_match_len(xp, SHOW_MATCH(match)) + 3; + if (match == 0) + first_match = 0; + else if (match < first_match) + { + // jumping left, as far as we can go + first_match = match; + add_left = TRUE; + } + else + { + // check if match fits on the screen + for (i = first_match; i < match; ++i) + clen += status_match_len(xp, SHOW_MATCH(i)) + 2; + if (first_match > 0) + clen += 2; + // jumping right, put match at the left + if ((long)clen > Columns) + { + first_match = match; + // if showing the last match, we can add some on the left + clen = 2; + for (i = match; i < num_matches; ++i) + { + clen += status_match_len(xp, SHOW_MATCH(i)) + 2; + if ((long)clen >= Columns) + break; + } + if (i == num_matches) + add_left = TRUE; + } + } + if (add_left) + while (first_match > 0) + { + clen += status_match_len(xp, SHOW_MATCH(first_match - 1)) + 2; + if ((long)clen >= Columns) + break; + --first_match; + } + + fillchar = fillchar_status(&attr, curwin); + + if (first_match == 0) + { + *buf = NUL; + len = 0; + } + else + { + STRCPY(buf, "< "); + len = 2; + } + clen = len; + + i = first_match; + while ((long)(clen + status_match_len(xp, SHOW_MATCH(i)) + 2) < Columns) + { + if (i == match) + { + selstart = buf + len; + selstart_col = clen; + } + + s = SHOW_MATCH(i); + // Check for menu separators - replace with '|' +#ifdef FEAT_MENU + emenu = (xp->xp_context == EXPAND_MENUS + || xp->xp_context == EXPAND_MENUNAMES); + if (emenu && menu_is_separator(s)) + { + STRCPY(buf + len, transchar('|')); + l = (int)STRLEN(buf + len); + len += l; + clen += l; + } + else +#endif + for ( ; *s != NUL; ++s) + { + s += skip_status_match_char(xp, s); + clen += ptr2cells(s); + if (has_mbyte && (l = (*mb_ptr2len)(s)) > 1) + { + STRNCPY(buf + len, s, l); + s += l - 1; + len += l; + } + else + { + STRCPY(buf + len, transchar_byte(*s)); + len += (int)STRLEN(buf + len); + } + } + if (i == match) + selend = buf + len; + + *(buf + len++) = ' '; + *(buf + len++) = ' '; + clen += 2; + if (++i == num_matches) + break; + } + + if (i != num_matches) + { + *(buf + len++) = '>'; + ++clen; + } + + buf[len] = NUL; + + row = cmdline_row - 1; + if (row >= 0) + { + if (wild_menu_showing == 0) + { + if (msg_scrolled > 0) + { + // Put the wildmenu just above the command line. If there is + // no room, scroll the screen one line up. + if (cmdline_row == Rows - 1) + { + screen_del_lines(0, 0, 1, (int)Rows, TRUE, 0, NULL); + ++msg_scrolled; + } + else + { + ++cmdline_row; + ++row; + } + wild_menu_showing = WM_SCROLLED; + } + else + { + // Create status line if needed by setting 'laststatus' to 2. + // Set 'winminheight' to zero to avoid that the window is + // resized. + if (lastwin->w_status_height == 0) + { + save_p_ls = p_ls; + save_p_wmh = p_wmh; + p_ls = 2; + p_wmh = 0; + last_status(FALSE); + } + wild_menu_showing = WM_SHOWN; + } + } + + screen_puts(buf, row, 0, attr); + if (selstart != NULL && highlight) + { + *selend = NUL; + screen_puts(selstart, row, selstart_col, HL_ATTR(HLF_WM)); + } + + screen_fill(row, row + 1, clen, (int)Columns, fillchar, fillchar, attr); + } + + win_redraw_last_status(topframe); + vim_free(buf); +} + +/* + * Get the next or prev cmdline completion match. The index of the match is set + * in "xp->xp_selected" + */ + static char_u * +get_next_or_prev_match( + int mode, + expand_T *xp) +{ + int findex = xp->xp_selected; + int ht; + + if (xp->xp_numfiles <= 0) + return NULL; + + if (mode == WILD_PREV) + { + if (findex == -1) + findex = xp->xp_numfiles; + --findex; + } + else if (mode == WILD_NEXT) + ++findex; + else if (mode == WILD_PAGEUP) + { + if (findex == 0) + // at the first entry, don't select any entries + findex = -1; + else if (findex == -1) + // no entry is selected. select the last entry + findex = xp->xp_numfiles - 1; + else + { + // go up by the pum height + ht = pum_get_height(); + if (ht > 3) + ht -= 2; + findex -= ht; + if (findex < 0) + // few entries left, select the first entry + findex = 0; + } + } + else // mode == WILD_PAGEDOWN + { + if (findex == xp->xp_numfiles - 1) + // at the last entry, don't select any entries + findex = -1; + else if (findex == -1) + // no entry is selected. select the first entry + findex = 0; + else + { + // go down by the pum height + ht = pum_get_height(); + if (ht > 3) + ht -= 2; + findex += ht; + if (findex >= xp->xp_numfiles) + // few entries left, select the last entry + findex = xp->xp_numfiles - 1; + } + } + + // When wrapping around, return the original string, set findex to -1. + if (findex < 0) + { + if (xp->xp_orig == NULL) + findex = xp->xp_numfiles - 1; + else + findex = -1; + } + if (findex >= xp->xp_numfiles) + { + if (xp->xp_orig == NULL) + findex = 0; + else + findex = -1; + } + if (compl_match_array) + { + compl_selected = findex; + cmdline_pum_display(); + } + else if (p_wmnu) + win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files, + findex, cmd_showtail); + xp->xp_selected = findex; + + if (findex == -1) + return vim_strsave(xp->xp_orig); + + return vim_strsave(xp->xp_files[findex]); +} + +/* + * Start the command-line expansion and get the matches. + */ + static char_u * +ExpandOne_start(int mode, expand_T *xp, char_u *str, int options) +{ + int non_suf_match; // number without matching suffix + int i; + char_u *ss = NULL; + + // Do the expansion. + if (ExpandFromContext(xp, str, &xp->xp_files, &xp->xp_numfiles, + options) == FAIL) + { +#ifdef FNAME_ILLEGAL + // Illegal file name has been silently skipped. But when there + // are wildcards, the real problem is that there was no match, + // causing the pattern to be added, which has illegal characters. + if (!(options & WILD_SILENT) && (options & WILD_LIST_NOTFOUND)) + semsg(_(e_no_match_str_2), str); +#endif + } + else if (xp->xp_numfiles == 0) + { + if (!(options & WILD_SILENT)) + semsg(_(e_no_match_str_2), str); + } + else + { + // Escape the matches for use on the command line. + ExpandEscape(xp, str, xp->xp_numfiles, xp->xp_files, options); + + // Check for matching suffixes in file names. + if (mode != WILD_ALL && mode != WILD_ALL_KEEP && mode != WILD_LONGEST) + { + if (xp->xp_numfiles) + non_suf_match = xp->xp_numfiles; + else + non_suf_match = 1; + if ((xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_DIRECTORIES) + && xp->xp_numfiles > 1) + { + // More than one match; check suffix. + // The files will have been sorted on matching suffix in + // expand_wildcards, only need to check the first two. + non_suf_match = 0; + for (i = 0; i < 2; ++i) + if (match_suffix(xp->xp_files[i])) + ++non_suf_match; + } + if (non_suf_match != 1) + { + // 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 (!(options & WILD_SILENT)) + emsg(_(e_too_many_file_names)); + else if (!(options & WILD_NO_BEEP)) + beep_flush(); + } + if (!(non_suf_match != 1 && mode == WILD_EXPAND_FREE)) + ss = vim_strsave(xp->xp_files[0]); + } + } + + return ss; +} + +/* + * Return the longest common part in the list of cmdline completion matches. + */ + static char_u * +find_longest_match(expand_T *xp, int options) +{ + long_u len; + int mb_len = 1; + int c0, ci; + int i; + char_u *ss; + + for (len = 0; xp->xp_files[0][len]; len += mb_len) + { + if (has_mbyte) + { + mb_len = (*mb_ptr2len)(&xp->xp_files[0][len]); + c0 =(* mb_ptr2char)(&xp->xp_files[0][len]); + } + else + c0 = xp->xp_files[0][len]; + for (i = 1; i < xp->xp_numfiles; ++i) + { + if (has_mbyte) + ci =(* mb_ptr2char)(&xp->xp_files[i][len]); + else + ci = xp->xp_files[i][len]; + if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES + || xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS)) + { + if (MB_TOLOWER(c0) != MB_TOLOWER(ci)) + break; + } + else if (c0 != ci) + break; + } + if (i < xp->xp_numfiles) + { + if (!(options & WILD_NO_BEEP)) + vim_beep(BO_WILD); + break; + } + } + + ss = alloc(len + 1); + if (ss) + vim_strncpy(ss, xp->xp_files[0], (size_t)len); + + return ss; +} + +/* + * Do wildcard expansion on the string "str". + * Chars that should not be expanded must be preceded with a backslash. + * Return a pointer to allocated memory containing the new string. + * Return NULL for failure. + * + * "orig" is the originally expanded string, copied to allocated memory. It + * should either be kept in "xp->xp_orig" or freed. When "mode" is WILD_NEXT + * or WILD_PREV "orig" should be NULL. + * + * Results are cached in xp->xp_files and xp->xp_numfiles, except when "mode" + * is WILD_EXPAND_FREE or WILD_ALL. + * + * 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 + * mode = WILD_ALL_KEEP: get all matches, keep matches + * mode = WILD_APPLY: apply the item selected in the cmdline completion + * popup menu and close the menu. + * mode = WILD_CANCEL: cancel and close the cmdline completion popup and + * use the original text. + * + * options = WILD_LIST_NOTFOUND: list entries without a match + * options = WILD_HOME_REPLACE: do home_replace() for buffer names + * options = WILD_USE_NL: Use '\n' for WILD_ALL + * options = WILD_NO_BEEP: Don't beep for multiple matches + * options = WILD_ADD_SLASH: add a slash after directory names + * options = WILD_KEEP_ALL: don't remove 'wildignore' entries + * options = WILD_SILENT: don't print warning messages + * options = WILD_ESCAPE: put backslash before special chars + * options = WILD_ICASE: ignore case for files + * options = WILD_ALLLINKS; keep broken links + * + * The variables xp->xp_context and xp->xp_backslash must have been set! + */ + char_u * +ExpandOne( + expand_T *xp, + char_u *str, + char_u *orig, // allocated copy of original of expanded string + int options, + int mode) +{ + char_u *ss = NULL; + int orig_saved = FALSE; + int i; + long_u len; + + // first handle the case of using an old match + if (mode == WILD_NEXT || mode == WILD_PREV + || mode == WILD_PAGEUP || mode == WILD_PAGEDOWN) + return get_next_or_prev_match(mode, xp); + + if (mode == WILD_CANCEL) + ss = vim_strsave(xp->xp_orig ? xp->xp_orig : (char_u *)""); + else if (mode == WILD_APPLY) + ss = vim_strsave(xp->xp_selected == -1 + ? (xp->xp_orig ? xp->xp_orig : (char_u *)"") + : xp->xp_files[xp->xp_selected]); + + // free old names + if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) + { + FreeWild(xp->xp_numfiles, xp->xp_files); + xp->xp_numfiles = -1; + VIM_CLEAR(xp->xp_orig); + + // The entries from xp_files may be used in the PUM, remove it. + if (compl_match_array != NULL) + cmdline_pum_remove(); + } + xp->xp_selected = 0; + + if (mode == WILD_FREE) // only release file name + return NULL; + + if (xp->xp_numfiles == -1 && mode != WILD_APPLY && mode != WILD_CANCEL) + { + vim_free(xp->xp_orig); + xp->xp_orig = orig; + orig_saved = TRUE; + + ss = ExpandOne_start(mode, xp, str, options); + } + + // Find longest common part + if (mode == WILD_LONGEST && xp->xp_numfiles > 0) + { + ss = find_longest_match(xp, options); + xp->xp_selected = -1; // next p_wc gets first one + } + + // Concatenate all matching names. Unless interrupted, this can be slow + // and the result probably won't be used. + if (mode == WILD_ALL && xp->xp_numfiles > 0 && !got_int) + { + len = 0; + for (i = 0; i < xp->xp_numfiles; ++i) + { + if (i > 0) + { + if (xp->xp_prefix == XP_PREFIX_NO) + len += 2; // prefix "no" + else if (xp->xp_prefix == XP_PREFIX_INV) + len += 3; // prefix "inv" + } + len += (long_u)STRLEN(xp->xp_files[i]) + 1; + } + ss = alloc(len); + if (ss != NULL) + { + *ss = NUL; + for (i = 0; i < xp->xp_numfiles; ++i) + { + if (i > 0) + { + if (xp->xp_prefix == XP_PREFIX_NO) + STRCAT(ss, "no"); + else if (xp->xp_prefix == XP_PREFIX_INV) + STRCAT(ss, "inv"); + } + STRCAT(ss, xp->xp_files[i]); + + if (i != xp->xp_numfiles - 1) + STRCAT(ss, (options & WILD_USE_NL) ? "\n" : " "); + } + } + } + + if (mode == WILD_EXPAND_FREE || mode == WILD_ALL) + ExpandCleanup(xp); + + // Free "orig" if it wasn't stored in "xp->xp_orig". + if (!orig_saved) + vim_free(orig); + + return ss; +} + +/* + * Prepare an expand structure for use. + */ + void +ExpandInit(expand_T *xp) +{ + CLEAR_POINTER(xp); + xp->xp_backslash = XP_BS_NONE; + xp->xp_prefix = XP_PREFIX_NONE; + xp->xp_numfiles = -1; +} + +/* + * Cleanup an expand structure after use. + */ + void +ExpandCleanup(expand_T *xp) +{ + if (xp->xp_numfiles >= 0) + { + FreeWild(xp->xp_numfiles, xp->xp_files); + xp->xp_numfiles = -1; + } + VIM_CLEAR(xp->xp_orig); +} + +/* + * Display one line of completion matches. Multiple matches are displayed in + * each line (used by wildmode=list and CTRL-D) + * matches - list of completion match names + * numMatches - number of completion matches in "matches" + * lines - number of output lines + * linenr - line number of matches to display + * maxlen - maximum number of characters in each line + * showtail - display only the tail of the full path of a file name + * dir_attr - highlight attribute to use for directory names + */ + static void +showmatches_oneline( + expand_T *xp, + char_u **matches, + int numMatches, + int lines, + int linenr, + int maxlen, + int showtail, + int dir_attr) +{ + int i, j; + int isdir; + int lastlen; + char_u *p; + + lastlen = 999; + for (j = linenr; j < numMatches; j += lines) + { + if (xp->xp_context == EXPAND_TAGS_LISTFILES) + { + msg_outtrans_attr(matches[j], HL_ATTR(HLF_D)); + p = matches[j] + STRLEN(matches[j]) + 1; + msg_advance(maxlen + 1); + msg_puts((char *)p); + msg_advance(maxlen + 3); + msg_outtrans_long_attr(p + 2, HL_ATTR(HLF_D)); + break; + } + for (i = maxlen - lastlen; --i >= 0; ) + msg_putchar(' '); + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS) + { + // highlight directories + if (xp->xp_numfiles != -1) + { + char_u *halved_slash; + char_u *exp_path; + char_u *path; + + // Expansion was done before and special characters + // were escaped, need to halve backslashes. Also + // $HOME has been replaced with ~/. + exp_path = expand_env_save_opt(matches[j], TRUE); + path = exp_path != NULL ? exp_path : matches[j]; + halved_slash = backslash_halve_save(path); + isdir = mch_isdir(halved_slash != NULL ? halved_slash + : matches[j]); + vim_free(exp_path); + if (halved_slash != path) + vim_free(halved_slash); + } + else + // Expansion was done here, file names are literal. + isdir = mch_isdir(matches[j]); + if (showtail) + p = SHOW_MATCH(j); + else + { + home_replace(NULL, matches[j], NameBuff, MAXPATHL, + TRUE); + p = NameBuff; + } + } + else + { + isdir = FALSE; + p = SHOW_MATCH(j); + } + lastlen = msg_outtrans_attr(p, isdir ? dir_attr : 0); + } + if (msg_col > 0) // when not wrapped around + { + msg_clr_eos(); + msg_putchar('\n'); + } + out_flush(); // show one line at a time +} + +/* + * Show all matches for completion on the command line. + * Returns EXPAND_NOTHING when the character that triggered expansion should + * be inserted like a normal character. + */ + int +showmatches(expand_T *xp, int wildmenu UNUSED) +{ + cmdline_info_T *ccline = get_cmdline_info(); + int numMatches; + char_u **matches; + int i, j; + int maxlen; + int lines; + int columns; + int attr; + int showtail; + + if (xp->xp_numfiles == -1) + { + set_expand_context(xp); + i = expand_cmdline(xp, ccline->cmdbuff, ccline->cmdpos, + &numMatches, &matches); + showtail = expand_showtail(xp); + if (i != EXPAND_OK) + return i; + } + else + { + numMatches = xp->xp_numfiles; + matches = xp->xp_files; + showtail = cmd_showtail; + } + + if (wildmenu && vim_strchr(p_wop, WOP_PUM) != NULL) + // cmdline completion popup menu (with wildoptions=pum) + return cmdline_pum_create(ccline, xp, matches, numMatches, showtail); + + if (!wildmenu) + { + msg_didany = FALSE; // lines_left will be set + msg_start(); // prepare for paging + msg_putchar('\n'); + out_flush(); + cmdline_row = msg_row; + msg_didany = FALSE; // lines_left will be set again + msg_start(); // prepare for paging + } + + if (got_int) + got_int = FALSE; // only int. the completion, not the cmd line + else if (wildmenu) + win_redr_status_matches(xp, numMatches, matches, -1, showtail); + else + { + // find the length of the longest file name + maxlen = 0; + for (i = 0; i < numMatches; ++i) + { + if (!showtail && (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS)) + { + home_replace(NULL, matches[i], NameBuff, MAXPATHL, TRUE); + j = vim_strsize(NameBuff); + } + else + j = vim_strsize(SHOW_MATCH(i)); + if (j > maxlen) + maxlen = j; + } + + if (xp->xp_context == EXPAND_TAGS_LISTFILES) + lines = numMatches; + else + { + // 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 = (numMatches + columns - 1) / columns; + } + + attr = HL_ATTR(HLF_D); // find out highlighting for directories + + if (xp->xp_context == EXPAND_TAGS_LISTFILES) + { + msg_puts_attr(_("tagname"), HL_ATTR(HLF_T)); + msg_clr_eos(); + msg_advance(maxlen - 3); + msg_puts_attr(_(" kind file\n"), HL_ATTR(HLF_T)); + } + + // list the files line by line + for (i = 0; i < lines; ++i) + { + showmatches_oneline(xp, matches, numMatches, lines, i, + maxlen, showtail, attr); + if (got_int) + { + got_int = FALSE; + break; + } + } + + // 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 + } + + if (xp->xp_numfiles == -1) + FreeWild(numMatches, matches); + + return EXPAND_OK; +} + +/* + * gettail() version for showmatches() and win_redr_status_matches(): + * Return the tail of file name path "s", ignoring a trailing "/". + */ + static char_u * +showmatches_gettail(char_u *s) +{ + char_u *p; + char_u *t = s; + int had_sep = FALSE; + + for (p = s; *p != NUL; ) + { + if (vim_ispathsep(*p) +#ifdef BACKSLASH_IN_FILENAME + && !rem_backslash(p) +#endif + ) + had_sep = TRUE; + else if (had_sep) + { + t = p; + had_sep = FALSE; + } + MB_PTR_ADV(p); + } + return t; +} + +/* + * Return TRUE if we only need to show the tail of completion matches. + * When not completing file names or there is a wildcard in the path FALSE is + * returned. + */ + static int +expand_showtail(expand_T *xp) +{ + char_u *s; + char_u *end; + + // When not completing file names a "/" may mean something different. + if (xp->xp_context != EXPAND_FILES + && xp->xp_context != EXPAND_SHELLCMD + && xp->xp_context != EXPAND_DIRECTORIES) + return FALSE; + + end = gettail(xp->xp_pattern); + if (end == xp->xp_pattern) // there is no path separator + return FALSE; + + for (s = xp->xp_pattern; s < end; s++) + { + // Skip escaped wildcards. Only when the backslash is not a path + // separator, on DOS the '*' "path\*\file" must not be skipped. + if (rem_backslash(s)) + ++s; + else if (vim_strchr((char_u *)"*?[", *s) != NULL) + return FALSE; + } + return TRUE; +} + +/* + * Prepare a string for expansion. + * When expanding file names: The string will be used with expand_wildcards(). + * Copy "fname[len]" 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 prepend "^". + */ + char_u * +addstar( + char_u *fname, + int len, + int context) // EXPAND_FILES etc. +{ + char_u *retval; + int i, j; + int new_len; + char_u *tail; + int ends_in_star; + + if (context != EXPAND_FILES + && context != EXPAND_FILES_IN_PATH + && context != EXPAND_SHELLCMD + && 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(). + // For a tag pattern starting with "/" no translation is needed. + if (context == EXPAND_HELP + || context == EXPAND_COLORS + || context == EXPAND_COMPILER + || context == EXPAND_OWNSYNTAX + || context == EXPAND_FILETYPE + || context == EXPAND_PACKADD + || context == EXPAND_RUNTIME + || ((context == EXPAND_TAGS_LISTFILES + || context == EXPAND_TAGS) + && fname[0] == '/')) + retval = vim_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 (context == EXPAND_BUFFERS && fname[i] == '.') + new_len++; // "." becomes "\." + + // Custom expansion takes care of special things, match + // backslashes literally (perhaps also for other types?) + if ((context == EXPAND_USER_DEFINED + || context == EXPAND_USER_LIST) && fname[i] == '\\') + new_len++; // '\' becomes "\\" + } + retval = alloc(new_len); + if (retval != NULL) + { + retval[0] = '^'; + j = 1; + for (i = 0; i < len; i++, j++) + { + // Skip backslash. But why? At least keep it for custom + // expansion. + if (context != EXPAND_USER_DEFINED + && context != EXPAND_USER_LIST + && fname[i] == '\\' + && ++i == len) + break; + + switch (fname[i]) + { + case '*': retval[j++] = '.'; + break; + case '~': retval[j++] = '\\'; + break; + case '?': retval[j] = '.'; + continue; + case '.': if (context == EXPAND_BUFFERS) + retval[j++] = '\\'; + break; + case '\\': if (context == EXPAND_USER_DEFINED + || context == EXPAND_USER_LIST) + retval[j++] = '\\'; + break; + } + retval[j] = fname[i]; + } + retval[j] = NUL; + } + } + } + else + { + retval = alloc(len + 4); + if (retval != NULL) + { + vim_strncpy(retval, fname, len); + + // Don't add a star to *, ~, ~user, $var or `cmd`. + // * would become **, which walks the whole tree. + // ~ would be at the start of the file name, but not the tail. + // $ could be anywhere in the tail. + // ` could be anywhere in the file name. + // When the name ends in '$' don't add a star, remove the '$'. + tail = gettail(retval); + ends_in_star = (len > 0 && retval[len - 1] == '*'); +#ifndef BACKSLASH_IN_FILENAME + for (i = len - 2; i >= 0; --i) + { + if (retval[i] != '\\') + break; + ends_in_star = !ends_in_star; + } +#endif + if ((*retval != '~' || tail != retval) + && !ends_in_star + && vim_strchr(tail, '$') == NULL + && vim_strchr(retval, '`') == NULL) + retval[len++] = '*'; + else if (len > 0 && retval[len - 1] == '$') + --len; + retval[len] = NUL; + } + } + return retval; +} + +/* + * 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 the variables: + * xp->xp_pattern The start of the pattern to be expanded within + * the command line (ends at the cursor). + * xp->xp_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 EX_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. + * E.g. :set dir=^I and :cd ^I + * EXPAND_SHELLCMD After ":!cmd", ":r !cmd" or ":w !cmd". + * 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_TAGS_LISTFILES As above, but list filenames on ^D, after :tselect + * EXPAND_HELP Complete tags from the file 'helpfile'/tags + * EXPAND_EVENTS Complete event names + * EXPAND_SYNTAX Complete :syntax command arguments + * EXPAND_HIGHLIGHT Complete highlight (syntax) group names + * EXPAND_AUGROUP Complete autocommand group names + * EXPAND_USER_VARS Complete user defined variable names, eg :unlet a^I + * EXPAND_MAPPINGS Complete mapping and abbreviation names, + * eg :unmap a^I , :cunab x^I + * EXPAND_FUNCTIONS Complete internal or user defined function names, + * eg :call sub^I + * EXPAND_USER_FUNC Complete user defined function names, eg :delf F^I + * EXPAND_EXPRESSION Complete internal or user defined function/variable + * names in expressions, eg :while s^I + * EXPAND_ENV_VARS Complete environment variable names + * EXPAND_USER Complete user names + */ + void +set_expand_context(expand_T *xp) +{ + cmdline_info_T *ccline = get_cmdline_info(); + + // only expansion for ':', '>' and '=' command-lines + if (ccline->cmdfirstc != ':' +#ifdef FEAT_EVAL + && ccline->cmdfirstc != '>' && ccline->cmdfirstc != '=' + && !ccline->input_fn +#endif + ) + { + xp->xp_context = EXPAND_NOTHING; + return; + } + set_cmd_context(xp, ccline->cmdbuff, ccline->cmdlen, ccline->cmdpos, TRUE); +} + +/* + * Sets the index of a built-in or user defined command 'cmd' in eap->cmdidx. + * For user defined commands, the completion context is set in 'xp' and the + * completion flags in 'complp'. + * + * Returns a pointer to the text after the command or NULL for failure. + */ + static char_u * +set_cmd_index(char_u *cmd, exarg_T *eap, expand_T *xp, int *complp) +{ + char_u *p = NULL; + int len = 0; + int fuzzy = cmdline_fuzzy_complete(cmd); + + // Isolate the command and search for it in the command table. + // Exceptions: + // - the 'k' command can directly be followed by any character, but do + // accept "keepmarks", "keepalt" and "keepjumps". As fuzzy matching can + // find matches anywhere in the command name, do this only for command + // expansion based on regular expression and not for fuzzy matching. + // - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' + if (!fuzzy && (*cmd == 'k' && cmd[1] != 'e')) + { + eap->cmdidx = CMD_k; + p = cmd + 1; + } + else + { + p = cmd; + while (ASCII_ISALPHA(*p) || *p == '*') // Allow * wild card + ++p; + // A user command may contain digits. + // Include "9" for "vim9*" commands; "vim9cmd" and "vim9script". + if (ASCII_ISUPPER(cmd[0]) || STRNCMP("vim9", cmd, 4) == 0) + while (ASCII_ISALNUM(*p) || *p == '*') + ++p; + // for python 3.x: ":py3*" commands completion + if (cmd[0] == 'p' && cmd[1] == 'y' && p == cmd + 2 && *p == '3') + { + ++p; + while (ASCII_ISALPHA(*p) || *p == '*') + ++p; + } + // check for non-alpha command + if (p == cmd && vim_strchr((char_u *)"@*!=><&~#", *p) != NULL) + ++p; + len = (int)(p - cmd); + + if (len == 0) + { + xp->xp_context = EXPAND_UNSUCCESSFUL; + return NULL; + } + + eap->cmdidx = excmd_get_cmdidx(cmd, len); + + // User defined commands support alphanumeric characters. + // Also when doing fuzzy expansion for non-shell commands, support + // alphanumeric characters. + if ((cmd[0] >= 'A' && cmd[0] <= 'Z') + || (fuzzy && eap->cmdidx != CMD_bang && *p != NUL)) + while (ASCII_ISALNUM(*p) || *p == '*') // Allow * wild card + ++p; + } + + // If the cursor is touching the command, and it ends in an alphanumeric + // character, complete the command name. + if (*p == NUL && ASCII_ISALNUM(p[-1])) + return NULL; + + if (eap->cmdidx == CMD_SIZE) + { + if (*cmd == 's' && vim_strchr((char_u *)"cgriI", cmd[1]) != NULL) + { + eap->cmdidx = CMD_substitute; + p = cmd + 1; + } + else if (cmd[0] >= 'A' && cmd[0] <= 'Z') + { + eap->cmd = cmd; + p = find_ucmd(eap, p, NULL, xp, complp); + if (p == NULL) + eap->cmdidx = CMD_SIZE; // ambiguous user command + } + } + if (eap->cmdidx == CMD_SIZE) + { + // Not still touching the command and it was an illegal one + xp->xp_context = EXPAND_UNSUCCESSFUL; + return NULL; + } + + return p; +} + +/* + * Set the completion context for a command argument with wild card characters. + */ + static void +set_context_for_wildcard_arg( + exarg_T *eap, + char_u *arg, + int usefilter, + expand_T *xp, + int *complp) +{ + char_u *p; + int c; + int in_quote = FALSE; + char_u *bow = NULL; // Beginning of word + int len = 0; + + // Allow spaces within back-quotes to count as part of the argument + // being expanded. + xp->xp_pattern = skipwhite(arg); + p = xp->xp_pattern; + while (*p != NUL) + { + if (has_mbyte) + c = mb_ptr2char(p); + else + c = *p; + if (c == '\\' && p[1] != NUL) + ++p; + else if (c == '`') + { + if (!in_quote) + { + xp->xp_pattern = p; + bow = p + 1; + } + in_quote = !in_quote; + } + // An argument can contain just about everything, except + // characters that end the command and white space. + else if (c == '|' || c == '\n' || c == '"' || (VIM_ISWHITE(c) +#ifdef SPACE_IN_FILENAME + && (!(eap->argt & EX_NOSPC) || usefilter) +#endif + )) + { + len = 0; // avoid getting stuck when space is in 'isfname' + while (*p != NUL) + { + if (has_mbyte) + c = mb_ptr2char(p); + else + c = *p; + if (c == '`' || vim_isfilec_or_wc(c)) + break; + if (has_mbyte) + len = (*mb_ptr2len)(p); + else + len = 1; + MB_PTR_ADV(p); + } + if (in_quote) + bow = p; + else + xp->xp_pattern = p; + p -= len; + } + MB_PTR_ADV(p); + } + + // If we are still inside the quotes, and we passed a space, just + // expand from there. + if (bow != NULL && in_quote) + xp->xp_pattern = bow; + xp->xp_context = EXPAND_FILES; + + // For a shell command more chars need to be escaped. + if (usefilter || eap->cmdidx == CMD_bang || eap->cmdidx == CMD_terminal) + { +#ifndef BACKSLASH_IN_FILENAME + xp->xp_shell = TRUE; +#endif + // When still after the command name expand executables. + if (xp->xp_pattern == skipwhite(arg)) + xp->xp_context = EXPAND_SHELLCMD; + } + + // Check for environment variable. + if (*xp->xp_pattern == '$') + { + for (p = xp->xp_pattern + 1; *p != NUL; ++p) + if (!vim_isIDc(*p)) + break; + if (*p == NUL) + { + xp->xp_context = EXPAND_ENV_VARS; + ++xp->xp_pattern; + // Avoid that the assignment uses EXPAND_FILES again. + if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST) + *complp = EXPAND_ENV_VARS; + } + } + // Check for user names. + if (*xp->xp_pattern == '~') + { + for (p = xp->xp_pattern + 1; *p != NUL && *p != '/'; ++p) + ; + // Complete ~user only if it partially matches a user name. + // A full match ~user will be replaced by user's home + // directory i.e. something like ~user -> /home/user/ + if (*p == NUL && p > xp->xp_pattern + 1 + && match_user(xp->xp_pattern + 1) >= 1) + { + xp->xp_context = EXPAND_USER; + ++xp->xp_pattern; + } + } +} + +/* + * Set the completion context for the "++opt=arg" argument. Always returns + * NULL. + */ + static char_u * +set_context_in_argopt(expand_T *xp, char_u *arg) +{ + char_u *p; + + p = vim_strchr(arg, '='); + if (p == NULL) + xp->xp_pattern = arg; + else + xp->xp_pattern = p + 1; + + xp->xp_context = EXPAND_ARGOPT; + return NULL; +} + +#ifdef FEAT_TERMINAL +/* + * Set the completion context for :terminal's [options]. Always returns NULL. + */ + static char_u * +set_context_in_terminalopt(expand_T *xp, char_u *arg) +{ + char_u *p; + + p = vim_strchr(arg, '='); + if (p == NULL) + xp->xp_pattern = arg; + else + xp->xp_pattern = p + 1; + + xp->xp_context = EXPAND_TERMINALOPT; + return NULL; +} +#endif + +/* + * Set the completion context for the :filter command. Returns a pointer to the + * next command after the :filter command. + */ + static char_u * +set_context_in_filter_cmd(expand_T *xp, char_u *arg) +{ + if (*arg != NUL) + arg = skip_vimgrep_pat(arg, NULL, NULL); + if (arg == NULL || *arg == NUL) + { + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + return skipwhite(arg); +} + +#ifdef FEAT_SEARCH_EXTRA +/* + * Set the completion context for the :match command. Returns a pointer to the + * next command after the :match command. + */ + static char_u * +set_context_in_match_cmd(expand_T *xp, char_u *arg) +{ + if (*arg == NUL || !ends_excmd(*arg)) + { + // also complete "None" + set_context_in_echohl_cmd(xp, arg); + arg = skipwhite(skiptowhite(arg)); + if (*arg != NUL) + { + xp->xp_context = EXPAND_NOTHING; + arg = skip_regexp(arg + 1, *arg, magic_isset()); + } + } + return find_nextcmd(arg); +} +#endif + +/* + * Returns a pointer to the next command after a :global or a :v command. + * Returns NULL if there is no next command. + */ + static char_u * +find_cmd_after_global_cmd(char_u *arg) +{ + int delim; + + 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; + + return NULL; +} + +/* + * Returns a pointer to the next command after a :substitute or a :& command. + * Returns NULL if there is no next command. + */ + static char_u * +find_cmd_after_substitute_cmd(char_u *arg) +{ + int delim; + + delim = *arg; + if (delim) + { + // skip "from" part + ++arg; + arg = skip_regexp(arg, delim, magic_isset()); + + if (arg[0] != NUL && arg[0] == delim) + { + // skip "to" part + ++arg; + 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; + + return NULL; +} + +/* + * Returns a pointer to the next command after a :isearch/:dsearch/:ilist + * :dlist/:ijump/:psearch/:djump/:isplit/:dsplit command. + * Returns NULL if there is no next command. + */ + static char_u * +find_cmd_after_isearch_cmd(expand_T *xp, char_u *arg) +{ + arg = skipwhite(skipdigits(arg)); // skip count + if (*arg != '/') + return NULL; + + // 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 == NUL || vim_strchr((char_u *)"|\"\n", *arg) == NULL) + xp->xp_context = EXPAND_NOTHING; + else + return arg; + } + + return NULL; +} + +#ifdef FEAT_EVAL +/* + * Set the completion context for the :unlet command. Always returns NULL. + */ + static char_u * +set_context_in_unlet_cmd(expand_T *xp, char_u *arg) +{ + while ((xp->xp_pattern = vim_strchr(arg, ' ')) != NULL) + arg = xp->xp_pattern + 1; + + xp->xp_context = EXPAND_USER_VARS; + xp->xp_pattern = arg; + + if (*xp->xp_pattern == '$') + { + xp->xp_context = EXPAND_ENV_VARS; + ++xp->xp_pattern; + } + + return NULL; +} +#endif + +#if defined(HAVE_LOCALE_H) || defined(X_LOCALE) +/* + * Set the completion context for the :language command. Always returns NULL. + */ + static char_u * +set_context_in_lang_cmd(expand_T *xp, char_u *arg) +{ + char_u *p; + + p = skiptowhite(arg); + if (*p == NUL) + { + xp->xp_context = EXPAND_LANGUAGE; + xp->xp_pattern = arg; + } + else + { + if ( STRNCMP(arg, "messages", p - arg) == 0 + || STRNCMP(arg, "ctype", p - arg) == 0 + || STRNCMP(arg, "time", p - arg) == 0 + || STRNCMP(arg, "collate", p - arg) == 0) + { + xp->xp_context = EXPAND_LOCALES; + xp->xp_pattern = skipwhite(p); + } + else + xp->xp_context = EXPAND_NOTHING; + } + + return NULL; +} +#endif + +#ifdef FEAT_EVAL +static enum +{ + EXP_BREAKPT_ADD, // expand ":breakadd" sub-commands + EXP_BREAKPT_DEL, // expand ":breakdel" sub-commands + EXP_PROFDEL // expand ":profdel" sub-commands +} breakpt_expand_what; + +/* + * Set the completion context for the :breakadd command. Always returns NULL. + */ + static char_u * +set_context_in_breakadd_cmd(expand_T *xp, char_u *arg, cmdidx_T cmdidx) +{ + char_u *p; + char_u *subcmd_start; + + xp->xp_context = EXPAND_BREAKPOINT; + xp->xp_pattern = arg; + + if (cmdidx == CMD_breakadd) + breakpt_expand_what = EXP_BREAKPT_ADD; + else if (cmdidx == CMD_breakdel) + breakpt_expand_what = EXP_BREAKPT_DEL; + else + breakpt_expand_what = EXP_PROFDEL; + + p = skipwhite(arg); + if (*p == NUL) + return NULL; + subcmd_start = p; + + if (STRNCMP("file ", p, 5) == 0 || STRNCMP("func ", p, 5) == 0) + { + // :breakadd file [lnum] + // :breakadd func [lnum] + p += 4; + p = skipwhite(p); + + // skip line number (if specified) + if (VIM_ISDIGIT(*p)) + { + p = skipdigits(p); + if (*p != ' ') + { + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + p = skipwhite(p); + } + if (STRNCMP("file", subcmd_start, 4) == 0) + xp->xp_context = EXPAND_FILES; + else + xp->xp_context = EXPAND_USER_FUNC; + xp->xp_pattern = p; + } + else if (STRNCMP("expr ", p, 5) == 0) + { + // :breakadd expr + xp->xp_context = EXPAND_EXPRESSION; + xp->xp_pattern = skipwhite(p + 5); + } + + return NULL; +} + + static char_u * +set_context_in_scriptnames_cmd(expand_T *xp, char_u *arg) +{ + char_u *p; + + xp->xp_context = EXPAND_NOTHING; + xp->xp_pattern = NULL; + + p = skipwhite(arg); + if (VIM_ISDIGIT(*p)) + return NULL; + + xp->xp_context = EXPAND_SCRIPTNAMES; + xp->xp_pattern = p; + + return NULL; +} +#endif + +/* + * Set the completion context in 'xp' for command 'cmd' with index 'cmdidx'. + * The argument to the command is 'arg' and the argument flags is 'argt'. + * For user-defined commands and for environment variables, 'compl' has the + * completion type. + * Returns a pointer to the next command. Returns NULL if there is no next + * command. + */ + static char_u * +set_context_by_cmdname( + char_u *cmd, + cmdidx_T cmdidx, + expand_T *xp, + char_u *arg, + long argt, + int compl, + int forceit) +{ + switch (cmdidx) + { + case CMD_find: + case CMD_sfind: + case CMD_tabfind: + if (xp->xp_context == EXPAND_FILES) + xp->xp_context = EXPAND_FILES_IN_PATH; + break; + case CMD_cd: + case CMD_chdir: + case CMD_tcd: + case CMD_tchdir: + case CMD_lcd: + case CMD_lchdir: + if (xp->xp_context == EXPAND_FILES) + xp->xp_context = EXPAND_DIRECTORIES; + break; + case CMD_help: + xp->xp_context = EXPAND_HELP; + xp->xp_pattern = arg; + break; + + // Command modifiers: return the argument. + // Also for commands with an argument that is a command. + case CMD_aboveleft: + case CMD_argdo: + case CMD_belowright: + case CMD_botright: + case CMD_browse: + case CMD_bufdo: + case CMD_cdo: + case CMD_cfdo: + case CMD_confirm: + case CMD_debug: + case CMD_folddoclosed: + case CMD_folddoopen: + case CMD_hide: + case CMD_horizontal: + case CMD_keepalt: + case CMD_keepjumps: + case CMD_keepmarks: + case CMD_keeppatterns: + case CMD_ldo: + case CMD_leftabove: + case CMD_lfdo: + case CMD_lockmarks: + case CMD_noautocmd: + case CMD_noswapfile: + case CMD_rightbelow: + case CMD_sandbox: + case CMD_silent: + case CMD_tab: + case CMD_tabdo: + case CMD_topleft: + case CMD_verbose: + case CMD_vertical: + case CMD_windo: + case CMD_vim9cmd: + case CMD_legacy: + return arg; + + case CMD_filter: + return set_context_in_filter_cmd(xp, arg); + +#ifdef FEAT_SEARCH_EXTRA + case CMD_match: + return set_context_in_match_cmd(xp, arg); +#endif + + // All completion for the +cmdline_compl feature goes here. + + case CMD_command: + return set_context_in_user_cmd(xp, arg); + + case CMD_delcommand: + xp->xp_context = EXPAND_USER_COMMANDS; + xp->xp_pattern = arg; + break; + + case CMD_global: + case CMD_vglobal: + return find_cmd_after_global_cmd(arg); + case CMD_and: + case CMD_substitute: + return find_cmd_after_substitute_cmd(arg); + case CMD_isearch: + case CMD_dsearch: + case CMD_ilist: + case CMD_dlist: + case CMD_ijump: + case CMD_psearch: + case CMD_djump: + case CMD_isplit: + case CMD_dsplit: + return find_cmd_after_isearch_cmd(xp, arg); + case CMD_autocmd: + return set_context_in_autocmd(xp, arg, FALSE); + case CMD_doautocmd: + case CMD_doautoall: + return set_context_in_autocmd(xp, arg, TRUE); + case CMD_set: + set_context_in_set_cmd(xp, arg, 0); + break; + case CMD_setglobal: + set_context_in_set_cmd(xp, arg, OPT_GLOBAL); + break; + case CMD_setlocal: + set_context_in_set_cmd(xp, arg, OPT_LOCAL); + break; + case CMD_tag: + case CMD_stag: + case CMD_ptag: + case CMD_ltag: + case CMD_tselect: + case CMD_stselect: + case CMD_ptselect: + case CMD_tjump: + case CMD_stjump: + case CMD_ptjump: + if (vim_strchr(p_wop, WOP_TAGFILE) != NULL) + xp->xp_context = EXPAND_TAGS_LISTFILES; + else + xp->xp_context = EXPAND_TAGS; + xp->xp_pattern = arg; + break; + case CMD_augroup: + xp->xp_context = EXPAND_AUGROUP; + xp->xp_pattern = arg; + break; +#ifdef FEAT_SYN_HL + case CMD_syntax: + set_context_in_syntax_cmd(xp, arg); + break; +#endif +#ifdef FEAT_EVAL + case CMD_final: + case CMD_const: + case CMD_let: + case CMD_var: + case CMD_if: + case CMD_elseif: + case CMD_while: + case CMD_for: + case CMD_echo: + case CMD_echon: + case CMD_execute: + case CMD_echomsg: + case CMD_echoerr: + case CMD_call: + case CMD_return: + case CMD_cexpr: + case CMD_caddexpr: + case CMD_cgetexpr: + case CMD_lexpr: + case CMD_laddexpr: + case CMD_lgetexpr: + set_context_for_expression(xp, arg, cmdidx); + break; + + case CMD_unlet: + return set_context_in_unlet_cmd(xp, arg); + case CMD_function: + case CMD_delfunction: + xp->xp_context = EXPAND_USER_FUNC; + xp->xp_pattern = arg; + break; + case CMD_disassemble: + set_context_in_disassemble_cmd(xp, arg); + break; + + case CMD_echohl: + set_context_in_echohl_cmd(xp, arg); + break; +#endif + case CMD_highlight: + set_context_in_highlight_cmd(xp, arg); + break; +#ifdef FEAT_CSCOPE + case CMD_cscope: + case CMD_lcscope: + case CMD_scscope: + set_context_in_cscope_cmd(xp, arg, cmdidx); + break; +#endif +#ifdef FEAT_SIGNS + case CMD_sign: + set_context_in_sign_cmd(xp, arg); + break; +#endif + case CMD_bdelete: + case CMD_bwipeout: + case CMD_bunload: + while ((xp->xp_pattern = vim_strchr(arg, ' ')) != NULL) + arg = xp->xp_pattern + 1; + // FALLTHROUGH + case CMD_buffer: + case CMD_sbuffer: + case CMD_checktime: + xp->xp_context = EXPAND_BUFFERS; + xp->xp_pattern = arg; + break; +#ifdef FEAT_DIFF + case CMD_diffget: + case CMD_diffput: + // If current buffer is in diff mode, complete buffer names + // which are in diff mode, and different than current buffer. + xp->xp_context = EXPAND_DIFF_BUFFERS; + xp->xp_pattern = arg; + break; +#endif + case CMD_USER: + case CMD_USER_BUF: + return set_context_in_user_cmdarg(cmd, arg, argt, compl, xp, + forceit); + + case CMD_map: case CMD_noremap: + case CMD_nmap: case CMD_nnoremap: + case CMD_vmap: case CMD_vnoremap: + case CMD_omap: case CMD_onoremap: + case CMD_imap: case CMD_inoremap: + case CMD_cmap: case CMD_cnoremap: + case CMD_lmap: case CMD_lnoremap: + case CMD_smap: case CMD_snoremap: + case CMD_tmap: case CMD_tnoremap: + case CMD_xmap: case CMD_xnoremap: + return set_context_in_map_cmd(xp, cmd, arg, forceit, + FALSE, FALSE, cmdidx); + case CMD_unmap: + case CMD_nunmap: + case CMD_vunmap: + case CMD_ounmap: + case CMD_iunmap: + case CMD_cunmap: + case CMD_lunmap: + case CMD_sunmap: + case CMD_tunmap: + case CMD_xunmap: + return set_context_in_map_cmd(xp, cmd, arg, forceit, + FALSE, TRUE, cmdidx); + case CMD_mapclear: + case CMD_nmapclear: + case CMD_vmapclear: + case CMD_omapclear: + case CMD_imapclear: + case CMD_cmapclear: + case CMD_lmapclear: + case CMD_smapclear: + case CMD_tmapclear: + case CMD_xmapclear: + xp->xp_context = EXPAND_MAPCLEAR; + xp->xp_pattern = arg; + break; + + case CMD_abbreviate: case CMD_noreabbrev: + case CMD_cabbrev: case CMD_cnoreabbrev: + case CMD_iabbrev: case CMD_inoreabbrev: + return set_context_in_map_cmd(xp, cmd, arg, forceit, + TRUE, FALSE, cmdidx); + case CMD_unabbreviate: + case CMD_cunabbrev: + case CMD_iunabbrev: + return set_context_in_map_cmd(xp, cmd, arg, forceit, + TRUE, TRUE, cmdidx); +#ifdef FEAT_MENU + case CMD_menu: case CMD_noremenu: case CMD_unmenu: + case CMD_amenu: case CMD_anoremenu: case CMD_aunmenu: + case CMD_nmenu: case CMD_nnoremenu: case CMD_nunmenu: + case CMD_vmenu: case CMD_vnoremenu: case CMD_vunmenu: + case CMD_omenu: case CMD_onoremenu: case CMD_ounmenu: + case CMD_imenu: case CMD_inoremenu: case CMD_iunmenu: + case CMD_cmenu: case CMD_cnoremenu: case CMD_cunmenu: + case CMD_tlmenu: case CMD_tlnoremenu: case CMD_tlunmenu: + case CMD_tmenu: case CMD_tunmenu: + case CMD_popup: case CMD_tearoff: case CMD_emenu: + return set_context_in_menu_cmd(xp, cmd, arg, forceit); +#endif + + case CMD_colorscheme: + xp->xp_context = EXPAND_COLORS; + xp->xp_pattern = arg; + break; + + case CMD_compiler: + xp->xp_context = EXPAND_COMPILER; + xp->xp_pattern = arg; + break; + + case CMD_ownsyntax: + xp->xp_context = EXPAND_OWNSYNTAX; + xp->xp_pattern = arg; + break; + + case CMD_setfiletype: + xp->xp_context = EXPAND_FILETYPE; + xp->xp_pattern = arg; + break; + + case CMD_packadd: + xp->xp_context = EXPAND_PACKADD; + xp->xp_pattern = arg; + break; + + case CMD_runtime: + set_context_in_runtime_cmd(xp, arg); + break; + +#if defined(HAVE_LOCALE_H) || defined(X_LOCALE) + case CMD_language: + return set_context_in_lang_cmd(xp, arg); +#endif +#if defined(FEAT_PROFILE) + case CMD_profile: + set_context_in_profile_cmd(xp, arg); + break; +#endif + case CMD_behave: + xp->xp_context = EXPAND_BEHAVE; + xp->xp_pattern = arg; + break; + + case CMD_messages: + xp->xp_context = EXPAND_MESSAGES; + xp->xp_pattern = arg; + break; + + case CMD_history: + xp->xp_context = EXPAND_HISTORY; + xp->xp_pattern = arg; + break; +#if defined(FEAT_PROFILE) + case CMD_syntime: + xp->xp_context = EXPAND_SYNTIME; + xp->xp_pattern = arg; + break; +#endif + + case CMD_argdelete: + while ((xp->xp_pattern = vim_strchr(arg, ' ')) != NULL) + arg = xp->xp_pattern + 1; + xp->xp_context = EXPAND_ARGLIST; + xp->xp_pattern = arg; + break; + +#ifdef FEAT_EVAL + case CMD_breakadd: + case CMD_profdel: + case CMD_breakdel: + return set_context_in_breakadd_cmd(xp, arg, cmdidx); + + case CMD_scriptnames: + return set_context_in_scriptnames_cmd(xp, arg); +#endif + + default: + break; + } + return NULL; +} + +/* + * 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( + expand_T *xp, + char_u *buff) // buffer for command string +{ + char_u *p; + char_u *cmd, *arg; + int len = 0; + exarg_T ea; + int compl = EXPAND_NOTHING; + int forceit = FALSE; + int usefilter = FALSE; // filter instead of file name + + ExpandInit(xp); + xp->xp_pattern = buff; + xp->xp_line = buff; + xp->xp_context = EXPAND_COMMANDS; // Default until we get past command + ea.argt = 0; + + // 1. skip comment lines and leading space, colons or bars + for (cmd = buff; vim_strchr((char_u *)" \t:|", *cmd) != NULL; cmd++) + ; + xp->xp_pattern = cmd; + + if (*cmd == NUL) + return NULL; + if (*cmd == '"') // ignore comment lines + { + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + + // 3. Skip over the range to find the command. + cmd = skip_range(cmd, TRUE, &xp->xp_context); + xp->xp_pattern = cmd; + if (*cmd == NUL) + return NULL; + if (*cmd == '"') + { + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + + if (*cmd == '|' || *cmd == '\n') + return cmd + 1; // There's another command + + // Get the command index. + p = set_cmd_index(cmd, &ea, xp, &compl); + if (p == NULL) + return NULL; + + xp->xp_context = EXPAND_NOTHING; // Default now that we're past command + + if (*p == '!') // forced commands + { + forceit = TRUE; + ++p; + } + + // 6. parse arguments + if (!IS_USER_CMDIDX(ea.cmdidx)) + ea.argt = excmd_get_argt(ea.cmdidx); + + arg = skipwhite(p); + + // Does command allow "++argopt" argument? + if ((ea.argt & EX_ARGOPT) || ea.cmdidx == CMD_terminal) + { + while (*arg != NUL && STRNCMP(arg, "++", 2) == 0) + { + p = arg + 2; + while (*p && !vim_isspace(*p)) + MB_PTR_ADV(p); + + // Still touching the command after "++"? + if (*p == NUL) + { + if (ea.argt & EX_ARGOPT) + return set_context_in_argopt(xp, arg + 2); +#ifdef FEAT_TERMINAL + if (ea.cmdidx == CMD_terminal) + return set_context_in_terminalopt(xp, arg + 2); +#endif + } + + arg = skipwhite(p); + } + } + + if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) + { + if (*arg == '>') // append + { + if (*++arg == '>') + ++arg; + arg = skipwhite(arg); + } + else if (*arg == '!' && ea.cmdidx == CMD_write) // :w !filter + { + ++arg; + usefilter = TRUE; + } + } + + if (ea.cmdidx == CMD_read) + { + usefilter = forceit; // :r! filter if forced + if (*arg == '!') // :r !filter + { + ++arg; + usefilter = TRUE; + } + } + + if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) + { + while (*arg == *cmd) // allow any number of '>' or '<' + ++arg; + arg = skipwhite(arg); + } + + // Does command allow "+command"? + if ((ea.argt & EX_CMDARG) && !usefilter && *arg == '+') + { + // Check if we're in the +command + p = arg + 1; + arg = skip_cmd_arg(arg, FALSE); + + // Still touching the command after '+'? + if (*arg == NUL) + return p; + + // Skip space(s) 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 ((ea.argt & EX_TRLBAR) && !usefilter) + { + p = arg; + // ":redir @" is not the start of a comment + if (ea.cmdidx == CMD_redir && p[0] == '@' && p[1] == '"') + p += 2; + while (*p) + { + if (*p == Ctrl_V) + { + if (p[1] != NUL) + ++p; + } + else if ( (*p == '"' && !(ea.argt & EX_NOTRLCOM)) + || *p == '|' || *p == '\n') + { + if (*(p - 1) != '\\') + { + if (*p == '|' || *p == '\n') + return p + 1; + return NULL; // It's a comment + } + } + MB_PTR_ADV(p); + } + } + + if (!(ea.argt & EX_EXTRA) && *arg != NUL + && vim_strchr((char_u *)"|\"", *arg) == NULL) + // no arguments allowed but there is something + return NULL; + + // Find start of last argument (argument just before cursor): + p = buff; + xp->xp_pattern = p; + len = (int)STRLEN(buff); + while (*p && p < buff + len) + { + if (*p == ' ' || *p == TAB) + { + // argument starts after a space + xp->xp_pattern = ++p; + } + else + { + if (*p == '\\' && *(p + 1) != NUL) + ++p; // skip over escaped character + MB_PTR_ADV(p); + } + } + + if (ea.argt & EX_XFILE) + set_context_for_wildcard_arg(&ea, arg, usefilter, xp, &compl); + + // 6. Switch on command name. + return set_context_by_cmdname(cmd, ea.cmdidx, xp, arg, ea.argt, compl, + forceit); +} + +/* + * Set the completion context in 'xp' for command 'str' + */ + void +set_cmd_context( + expand_T *xp, + char_u *str, // start of command line + int len, // length of command line (excl. NUL) + int col, // position of cursor + int use_ccline UNUSED) // use ccline for info +{ +#ifdef FEAT_EVAL + cmdline_info_T *ccline = get_cmdline_info(); +#endif + int old_char = NUL; + char_u *nextcomm; + + // Avoid a UMR warning from Purify, only save the character if it has been + // written before. + if (col < len) + old_char = str[col]; + str[col] = NUL; + nextcomm = str; + +#ifdef FEAT_EVAL + if (use_ccline && ccline->cmdfirstc == '=') + { + // pass CMD_SIZE because there is no real command + set_context_for_expression(xp, str, CMD_SIZE); + } + else if (use_ccline && ccline->input_fn) + { + xp->xp_context = ccline->xp_context; + xp->xp_pattern = ccline->cmdbuff; + xp->xp_arg = ccline->xp_arg; + } + else +#endif + while (nextcomm != NULL) + nextcomm = set_one_cmd_context(xp, nextcomm); + + // Store the string here so that call_user_expand_func() can get to them + // easily. + xp->xp_line = str; + xp->xp_col = col; + + str[col] = old_char; +} + +/* + * Expand the command line "str" from context "xp". + * "xp" must have been set by set_cmd_context(). + * xp->xp_pattern points into "str", to where the text that is to be expanded + * starts. + * Returns EXPAND_UNSUCCESSFUL when there is something illegal before the + * cursor. + * Returns EXPAND_NOTHING when there is nothing to expand, might insert the + * key that triggered expansion literally. + * Returns EXPAND_OK otherwise. + */ + int +expand_cmdline( + expand_T *xp, + char_u *str, // start of command line + int col, // position of cursor + int *matchcount, // return: nr of matches + char_u ***matches) // return: array of pointers to matches +{ + char_u *file_str = NULL; + int options = WILD_ADD_SLASH|WILD_SILENT; + + if (xp->xp_context == EXPAND_UNSUCCESSFUL) + { + beep_flush(); + return EXPAND_UNSUCCESSFUL; // Something illegal on command line + } + if (xp->xp_context == EXPAND_NOTHING) + { + // Caller can use the character as a normal char instead + return EXPAND_NOTHING; + } + + // add star to file name, or convert to regexp if not exp. files. + xp->xp_pattern_len = (int)(str + col - xp->xp_pattern); + if (cmdline_fuzzy_completion_supported(xp)) + // If fuzzy matching, don't modify the search string + file_str = vim_strsave(xp->xp_pattern); + else + { + file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + if (file_str == NULL) + return EXPAND_UNSUCCESSFUL; + } + + if (p_wic) + options += WILD_ICASE; + + // find all files that match the description + if (ExpandFromContext(xp, file_str, matches, matchcount, options) == FAIL) + { + *matchcount = 0; + *matches = NULL; + } + vim_free(file_str); + + return EXPAND_OK; +} + +/* + * Expand file or directory names. + * Returns OK or FAIL. + */ + static int +expand_files_and_dirs( + expand_T *xp, + char_u *pat, + char_u ***matches, + int *numMatches, + int flags, + int options) +{ + int free_pat = FALSE; + int i; + int ret; + + // for ":set path=" and ":set tags=" halve backslashes for escaped + // space + if (xp->xp_backslash != XP_BS_NONE) + { + free_pat = TRUE; + pat = vim_strsave(pat); + for (i = 0; pat[i]; ++i) + if (pat[i] == '\\') + { + if (xp->xp_backslash & XP_BS_THREE + && pat[i + 1] == '\\' + && pat[i + 2] == '\\' + && pat[i + 3] == ' ') + STRMOVE(pat + i, pat + i + 3); + else if (xp->xp_backslash & XP_BS_ONE + && pat[i + 1] == ' ') + STRMOVE(pat + i, pat + i + 1); + else if ((xp->xp_backslash & XP_BS_COMMA) + && pat[i + 1] == '\\' + && pat[i + 2] == ',') + STRMOVE(pat + i, pat + i + 2); +#ifdef BACKSLASH_IN_FILENAME + else if ((xp->xp_backslash & XP_BS_COMMA) + && pat[i + 1] == ',') + STRMOVE(pat + i, pat + i + 1); +#endif + } + } + + if (xp->xp_context == EXPAND_FILES) + flags |= EW_FILE; + else if (xp->xp_context == EXPAND_FILES_IN_PATH) + flags |= (EW_FILE | EW_PATH); + else + flags = (flags | EW_DIR) & ~EW_FILE; + if (options & WILD_ICASE) + flags |= EW_ICASE; + + // Expand wildcards, supporting %:h and the like. + ret = expand_wildcards_eval(&pat, numMatches, matches, flags); + if (free_pat) + vim_free(pat); +#ifdef BACKSLASH_IN_FILENAME + if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) + { + int j; + + for (j = 0; j < *numMatches; ++j) + { + char_u *ptr = (*matches)[j]; + + while (*ptr != NUL) + { + if (p_csl[0] == 's' && *ptr == '\\') + *ptr = '/'; + else if (p_csl[0] == 'b' && *ptr == '/') + *ptr = '\\'; + ptr += (*mb_ptr2len)(ptr); + } + } + } +#endif + return ret; +} + +/* + * Function given to ExpandGeneric() to obtain the possible arguments of the + * ":behave {mswin,xterm}" command. + */ + static char_u * +get_behave_arg(expand_T *xp UNUSED, int idx) +{ + if (idx == 0) + return (char_u *)"mswin"; + if (idx == 1) + return (char_u *)"xterm"; + return NULL; +} + +#ifdef FEAT_EVAL +/* + * Function given to ExpandGeneric() to obtain the possible arguments of the + * ":breakadd {expr, file, func, here}" command. + * ":breakdel {func, file, here}" command. + */ + static char_u * +get_breakadd_arg(expand_T *xp UNUSED, int idx) +{ + char *opts[] = {"expr", "file", "func", "here"}; + + if (idx >= 0 && idx <= 3) + { + // breakadd {expr, file, func, here} + if (breakpt_expand_what == EXP_BREAKPT_ADD) + return (char_u *)opts[idx]; + else if (breakpt_expand_what == EXP_BREAKPT_DEL) + { + // breakdel {func, file, here} + if (idx <= 2) + return (char_u *)opts[idx + 1]; + } + else + { + // profdel {func, file} + if (idx <= 1) + return (char_u *)opts[idx + 1]; + } + } + return NULL; +} + +/* + * Function given to ExpandGeneric() to obtain the possible arguments for the + * ":scriptnames" command. + */ + static char_u * +get_scriptnames_arg(expand_T *xp UNUSED, int idx) +{ + scriptitem_T *si; + + if (!SCRIPT_ID_VALID(idx + 1)) + return NULL; + + si = SCRIPT_ITEM(idx + 1); + home_replace(NULL, si->sn_name, NameBuff, MAXPATHL, TRUE); + return NameBuff; +} +#endif + +/* + * Function given to ExpandGeneric() to obtain the possible arguments of the + * ":messages {clear}" command. + */ + static char_u * +get_messages_arg(expand_T *xp UNUSED, int idx) +{ + if (idx == 0) + return (char_u *)"clear"; + return NULL; +} + + static char_u * +get_mapclear_arg(expand_T *xp UNUSED, int idx) +{ + if (idx == 0) + return (char_u *)""; + return NULL; +} + +/* + * Do the expansion based on xp->xp_context and 'rmp'. + */ + static int +ExpandOther( + char_u *pat, + expand_T *xp, + regmatch_T *rmp, + char_u ***matches, + int *numMatches) +{ + static struct expgen + { + int context; + char_u *((*func)(expand_T *, int)); + int ic; + int escaped; + } tab[] = + { + {EXPAND_COMMANDS, get_command_name, FALSE, TRUE}, + {EXPAND_BEHAVE, get_behave_arg, TRUE, TRUE}, + {EXPAND_MAPCLEAR, get_mapclear_arg, TRUE, TRUE}, + {EXPAND_MESSAGES, get_messages_arg, TRUE, TRUE}, + {EXPAND_HISTORY, get_history_arg, TRUE, TRUE}, + {EXPAND_USER_COMMANDS, get_user_commands, FALSE, TRUE}, + {EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, FALSE, TRUE}, + {EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, FALSE, TRUE}, + {EXPAND_USER_NARGS, get_user_cmd_nargs, FALSE, TRUE}, + {EXPAND_USER_COMPLETE, get_user_cmd_complete, FALSE, TRUE}, +#ifdef FEAT_EVAL + {EXPAND_USER_VARS, get_user_var_name, FALSE, TRUE}, + {EXPAND_FUNCTIONS, get_function_name, FALSE, TRUE}, + {EXPAND_USER_FUNC, get_user_func_name, FALSE, TRUE}, + {EXPAND_DISASSEMBLE, get_disassemble_argument, FALSE, TRUE}, + {EXPAND_EXPRESSION, get_expr_name, FALSE, TRUE}, +#endif +#ifdef FEAT_MENU + {EXPAND_MENUS, get_menu_name, FALSE, TRUE}, + {EXPAND_MENUNAMES, get_menu_names, FALSE, TRUE}, +#endif +#ifdef FEAT_SYN_HL + {EXPAND_SYNTAX, get_syntax_name, TRUE, TRUE}, +#endif +#ifdef FEAT_PROFILE + {EXPAND_SYNTIME, get_syntime_arg, TRUE, TRUE}, +#endif + {EXPAND_HIGHLIGHT, get_highlight_name, TRUE, TRUE}, + {EXPAND_EVENTS, get_event_name, TRUE, FALSE}, + {EXPAND_AUGROUP, get_augroup_name, TRUE, FALSE}, +#ifdef FEAT_CSCOPE + {EXPAND_CSCOPE, get_cscope_name, TRUE, TRUE}, +#endif +#ifdef FEAT_SIGNS + {EXPAND_SIGN, get_sign_name, TRUE, TRUE}, +#endif +#ifdef FEAT_PROFILE + {EXPAND_PROFILE, get_profile_name, TRUE, TRUE}, +#endif +#if defined(HAVE_LOCALE_H) || defined(X_LOCALE) + {EXPAND_LANGUAGE, get_lang_arg, TRUE, FALSE}, + {EXPAND_LOCALES, get_locales, TRUE, FALSE}, +#endif + {EXPAND_ENV_VARS, get_env_name, TRUE, TRUE}, + {EXPAND_USER, get_users, TRUE, FALSE}, + {EXPAND_ARGLIST, get_arglist_name, TRUE, FALSE}, +#ifdef FEAT_EVAL + {EXPAND_BREAKPOINT, get_breakadd_arg, TRUE, TRUE}, + {EXPAND_SCRIPTNAMES, get_scriptnames_arg, TRUE, FALSE}, +#endif + }; + int i; + int ret = FAIL; + + // Find a context in the table and call the ExpandGeneric() with the + // right function to do the expansion. + for (i = 0; i < (int)ARRAY_LENGTH(tab); ++i) + { + if (xp->xp_context == tab[i].context) + { + if (tab[i].ic) + rmp->rm_ic = TRUE; + ret = ExpandGeneric(pat, xp, rmp, matches, numMatches, + tab[i].func, tab[i].escaped); + break; + } + } + + return ret; +} + +/* + * Map wild expand options to flags for expand_wildcards() + */ + static int +map_wildopts_to_ewflags(int options) +{ + int flags; + + flags = EW_DIR; // include directories + if (options & WILD_LIST_NOTFOUND) + flags |= EW_NOTFOUND; + if (options & WILD_ADD_SLASH) + flags |= EW_ADDSLASH; + if (options & WILD_KEEP_ALL) + flags |= EW_KEEPALL; + if (options & WILD_SILENT) + flags |= EW_SILENT; + if (options & WILD_NOERROR) + flags |= EW_NOERROR; + if (options & WILD_ALLLINKS) + flags |= EW_ALLLINKS; + + return flags; +} + +/* + * Do the expansion based on xp->xp_context and "pat". + */ + static int +ExpandFromContext( + expand_T *xp, + char_u *pat, + char_u ***matches, + int *numMatches, + int options) // WILD_ flags +{ + regmatch_T regmatch; + int ret; + int flags; + char_u *tofree = NULL; + int fuzzy = cmdline_fuzzy_complete(pat) + && cmdline_fuzzy_completion_supported(xp); + + flags = map_wildopts_to_ewflags(options); + + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_DIRECTORIES + || xp->xp_context == EXPAND_FILES_IN_PATH) + return expand_files_and_dirs(xp, pat, matches, numMatches, flags, + options); + + *matches = (char_u **)""; + *numMatches = 0; + if (xp->xp_context == EXPAND_HELP) + { + // With an empty argument we would get all the help tags, which is + // very slow. Get matches for "help" instead. + if (find_help_tags(*pat == NUL ? (char_u *)"help" : pat, + numMatches, matches, FALSE) == OK) + { +#ifdef FEAT_MULTI_LANG + cleanup_help_tags(*numMatches, *matches); +#endif + return OK; + } + return FAIL; + } + + if (xp->xp_context == EXPAND_SHELLCMD) + return expand_shellcmd(pat, matches, numMatches, flags); + if (xp->xp_context == EXPAND_OLD_SETTING) + return ExpandOldSetting(numMatches, matches); + if (xp->xp_context == EXPAND_BUFFERS) + return ExpandBufnames(pat, numMatches, matches, options); +#ifdef FEAT_DIFF + if (xp->xp_context == EXPAND_DIFF_BUFFERS) + return ExpandBufnames(pat, numMatches, matches, + options | BUF_DIFF_FILTER); +#endif + if (xp->xp_context == EXPAND_TAGS + || xp->xp_context == EXPAND_TAGS_LISTFILES) + return expand_tags(xp->xp_context == EXPAND_TAGS, pat, numMatches, + matches); + if (xp->xp_context == EXPAND_COLORS) + { + char *directories[] = {"colors", NULL}; + return ExpandRTDir(pat, DIP_START + DIP_OPT, numMatches, matches, + directories); + } + if (xp->xp_context == EXPAND_COMPILER) + { + char *directories[] = {"compiler", NULL}; + return ExpandRTDir(pat, 0, numMatches, matches, directories); + } + if (xp->xp_context == EXPAND_OWNSYNTAX) + { + char *directories[] = {"syntax", NULL}; + return ExpandRTDir(pat, 0, numMatches, matches, directories); + } + if (xp->xp_context == EXPAND_FILETYPE) + { + char *directories[] = {"syntax", "indent", "ftplugin", NULL}; + return ExpandRTDir(pat, 0, numMatches, matches, directories); + } +#if defined(FEAT_EVAL) + if (xp->xp_context == EXPAND_USER_LIST) + return ExpandUserList(xp, matches, numMatches); +#endif + if (xp->xp_context == EXPAND_PACKADD) + return ExpandPackAddDir(pat, numMatches, matches); + if (xp->xp_context == EXPAND_RUNTIME) + return expand_runtime_cmd(pat, numMatches, matches); + + // When expanding a function name starting with s:, match the nr_ + // prefix. + if ((xp->xp_context == EXPAND_USER_FUNC + || xp->xp_context == EXPAND_DISASSEMBLE) + && STRNCMP(pat, "^s:", 3) == 0) + { + int len = (int)STRLEN(pat) + 20; + + tofree = alloc(len); + if (tofree == NULL) + return FAIL; + vim_snprintf((char *)tofree, len, "^\\d\\+_%s", pat + 3); + pat = tofree; + } + + if (!fuzzy) + { + regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0); + if (regmatch.regprog == NULL) + return FAIL; + + // set ignore-case according to p_ic, p_scs and pat + regmatch.rm_ic = ignorecase(pat); + } + + if (xp->xp_context == EXPAND_SETTINGS + || xp->xp_context == EXPAND_BOOL_SETTINGS) + ret = ExpandSettings(xp, ®match, pat, numMatches, matches, fuzzy); + else if (xp->xp_context == EXPAND_STRING_SETTING) + ret = ExpandStringSetting(xp, ®match, numMatches, matches); + else if (xp->xp_context == EXPAND_SETTING_SUBTRACT) + ret = ExpandSettingSubtract(xp, ®match, numMatches, matches); + else if (xp->xp_context == EXPAND_MAPPINGS) + ret = ExpandMappings(pat, ®match, numMatches, matches); + else if (xp->xp_context == EXPAND_ARGOPT) + ret = expand_argopt(pat, xp, ®match, matches, numMatches); +#if defined(FEAT_TERMINAL) + else if (xp->xp_context == EXPAND_TERMINALOPT) + ret = expand_terminal_opt(pat, xp, ®match, matches, numMatches); +#endif +#if defined(FEAT_EVAL) + else if (xp->xp_context == EXPAND_USER_DEFINED) + ret = ExpandUserDefined(pat, xp, ®match, matches, numMatches); +#endif + else + ret = ExpandOther(pat, xp, ®match, matches, numMatches); + + if (!fuzzy) + vim_regfree(regmatch.regprog); + vim_free(tofree); + + return ret; +} + +/* + * Expand a list of names. + * + * Generic function for command line completion. It calls a function to + * obtain strings, one by one. The strings are matched against a regexp + * program. Matching strings are copied into an array, which is returned. + * + * If 'fuzzy' is TRUE, then fuzzy matching is used. Otherwise, regex matching + * is used. + * + * Returns OK when no problems encountered, FAIL for error (out of memory). + */ + int +ExpandGeneric( + char_u *pat, + expand_T *xp, + regmatch_T *regmatch, + char_u ***matches, + int *numMatches, + char_u *((*func)(expand_T *, int)), + // returns a string from the list + int escaped) +{ + int i; + garray_T ga; + char_u *str; + fuzmatch_str_T *fuzmatch = NULL; + int score = 0; + int fuzzy; + int match; + int sort_matches = FALSE; + int funcsort = FALSE; + + fuzzy = cmdline_fuzzy_complete(pat); + *matches = NULL; + *numMatches = 0; + + if (!fuzzy) + ga_init2(&ga, sizeof(char *), 30); + else + ga_init2(&ga, sizeof(fuzmatch_str_T), 30); + + for (i = 0; ; ++i) + { + str = (*func)(xp, i); + if (str == NULL) // end of list + break; + if (*str == NUL) // skip empty strings + continue; + + if (xp->xp_pattern[0] != NUL) + { + if (!fuzzy) + match = vim_regexec(regmatch, str, (colnr_T)0); + else + { + score = fuzzy_match_str(str, pat); + match = (score != 0); + } + } + else + match = TRUE; + + if (!match) + continue; + + if (escaped) + str = vim_strsave_escaped(str, (char_u *)" \t\\."); + else + str = vim_strsave(str); + if (str == NULL) + { + if (!fuzzy) + { + ga_clear_strings(&ga); + return FAIL; + } + fuzmatch_str_free(ga.ga_data, ga.ga_len); + return FAIL; + } + + if (ga_grow(&ga, 1) == FAIL) + { + vim_free(str); + break; + } + + if (fuzzy) + { + fuzmatch = &((fuzmatch_str_T *)ga.ga_data)[ga.ga_len]; + fuzmatch->idx = ga.ga_len; + fuzmatch->str = str; + fuzmatch->score = score; + } + else + ((char_u **)ga.ga_data)[ga.ga_len] = str; + +#ifdef FEAT_MENU + if (func == get_menu_names) + { + // test for separator added by get_menu_names() + str += STRLEN(str) - 1; + if (*str == '\001') + *str = '.'; + } +#endif + + ++ga.ga_len; + } + + if (ga.ga_len == 0) + return OK; + + // sort the matches when using regular expression matching and sorting + // applies to the completion context. Menus and scriptnames should be kept + // in the specified order. + if (!fuzzy && xp->xp_context != EXPAND_MENUNAMES + && xp->xp_context != EXPAND_STRING_SETTING + && xp->xp_context != EXPAND_MENUS + && xp->xp_context != EXPAND_SCRIPTNAMES + && xp->xp_context != EXPAND_ARGOPT + && xp->xp_context != EXPAND_TERMINALOPT) + sort_matches = TRUE; + + // functions should be sorted to the end. + if (xp->xp_context == EXPAND_EXPRESSION + || xp->xp_context == EXPAND_FUNCTIONS + || xp->xp_context == EXPAND_USER_FUNC + || xp->xp_context == EXPAND_DISASSEMBLE) + funcsort = TRUE; + + // Sort the matches. + if (sort_matches) + { + if (funcsort) + // functions should be sorted to the end. + qsort((void *)ga.ga_data, (size_t)ga.ga_len, sizeof(char_u *), + sort_func_compare); + else + sort_strings((char_u **)ga.ga_data, ga.ga_len); + } + + if (!fuzzy) + { + *matches = ga.ga_data; + *numMatches = ga.ga_len; + } + else + { + if (fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len, + funcsort) == FAIL) + return FAIL; + *numMatches = ga.ga_len; + } + +#if defined(FEAT_SYN_HL) + // Reset the variables used for special highlight names expansion, so that + // they don't show up when getting normal highlight names by ID. + reset_expand_highlight(); +#endif + + return OK; +} + +/* + * Expand shell command matches in one directory of $PATH. + */ + static void +expand_shellcmd_onedir( + char_u *buf, + char_u *s, + size_t l, + char_u *pat, + char_u ***matches, + int *numMatches, + int flags, + hashtab_T *ht, + garray_T *gap) +{ + int ret; + hash_T hash; + hashitem_T *hi; + + vim_strncpy(buf, s, l); + add_pathsep(buf); + l = STRLEN(buf); + vim_strncpy(buf + l, pat, MAXPATHL - 1 - l); + + // Expand matches in one directory of $PATH. + ret = expand_wildcards(1, &buf, numMatches, matches, flags); + if (ret != OK) + return; + + if (ga_grow(gap, *numMatches) == FAIL) + { + FreeWild(*numMatches, *matches); + return; + } + + for (int i = 0; i < *numMatches; ++i) + { + char_u *name = (*matches)[i]; + + if (STRLEN(name) > l) + { + // Check if this name was already found. + hash = hash_hash(name + l); + hi = hash_lookup(ht, name + l, hash); + if (HASHITEM_EMPTY(hi)) + { + // Remove the path that was prepended. + STRMOVE(name, name + l); + ((char_u **)gap->ga_data)[gap->ga_len++] = name; + hash_add_item(ht, hi, name, hash); + name = NULL; + } + } + vim_free(name); + } + vim_free(*matches); +} + +/* + * Complete a shell command. + * Returns FAIL or OK; + */ + static int +expand_shellcmd( + char_u *filepat, // pattern to match with command names + char_u ***matches, // return: array with matches + int *numMatches, // return: number of matches + int flagsarg) // EW_ flags +{ + char_u *pat; + int i; + char_u *path = NULL; + int mustfree = FALSE; + garray_T ga; + char_u *buf; + size_t l; + char_u *s, *e; + int flags = flagsarg; + int did_curdir = FALSE; + hashtab_T found_ht; + + buf = alloc(MAXPATHL); + if (buf == NULL) + return FAIL; + + // for ":set path=" and ":set tags=" halve backslashes for escaped space + pat = vim_strsave(filepat); + if (pat == NULL) + { + vim_free(buf); + return FAIL; + } + + for (i = 0; pat[i]; ++i) + if (pat[i] == '\\' && pat[i + 1] == ' ') + STRMOVE(pat + i, pat + i + 1); + + flags |= EW_FILE | EW_EXEC | EW_SHELLCMD; + + if (pat[0] == '.' && (vim_ispathsep(pat[1]) + || (pat[1] == '.' && vim_ispathsep(pat[2])))) + path = (char_u *)"."; + else + { + // For an absolute name we don't use $PATH. + if (!mch_isFullName(pat)) + path = vim_getenv((char_u *)"PATH", &mustfree); + if (path == NULL) + path = (char_u *)""; + } + + // Go over all directories in $PATH. Expand matches in that directory and + // collect them in "ga". When "." is not in $PATH also expand for the + // current directory, to find "subdir/cmd". + ga_init2(&ga, sizeof(char *), 10); + hash_init(&found_ht); + for (s = path; ; s = e) + { +#if defined(MSWIN) + e = vim_strchr(s, ';'); +#else + e = vim_strchr(s, ':'); +#endif + if (e == NULL) + e = s + STRLEN(s); + + if (*s == NUL) + { + if (did_curdir) + break; + // Find directories in the current directory, path is empty. + did_curdir = TRUE; + flags |= EW_DIR; + } + else if (STRNCMP(s, ".", (int)(e - s)) == 0) + { + did_curdir = TRUE; + flags |= EW_DIR; + } + else + // Do not match directories inside a $PATH item. + flags &= ~EW_DIR; + + l = e - s; + if (l > MAXPATHL - 5) + break; + + expand_shellcmd_onedir(buf, s, l, pat, matches, numMatches, flags, + &found_ht, &ga); + + if (*e != NUL) + ++e; + } + *matches = ga.ga_data; + *numMatches = ga.ga_len; + + vim_free(buf); + vim_free(pat); + if (mustfree) + vim_free(path); + hash_clear(&found_ht); + return OK; +} + +#if defined(FEAT_EVAL) +/* + * Call "user_expand_func()" to invoke a user defined Vim script function and + * return the result (either a string, a List or NULL). + */ + static void * +call_user_expand_func( + void *(*user_expand_func)(char_u *, int, typval_T *), + expand_T *xp) +{ + cmdline_info_T *ccline = get_cmdline_info(); + int keep = 0; + typval_T args[4]; + sctx_T save_current_sctx = current_sctx; + char_u *pat = NULL; + void *ret; + + if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) + return NULL; + + if (ccline->cmdbuff != NULL) + { + keep = ccline->cmdbuff[ccline->cmdlen]; + ccline->cmdbuff[ccline->cmdlen] = 0; + } + + pat = vim_strnsave(xp->xp_pattern, xp->xp_pattern_len); + + args[0].v_type = VAR_STRING; + args[0].vval.v_string = pat; + args[1].v_type = VAR_STRING; + args[1].vval.v_string = xp->xp_line; + args[2].v_type = VAR_NUMBER; + args[2].vval.v_number = xp->xp_col; + args[3].v_type = VAR_UNKNOWN; + + current_sctx = xp->xp_script_ctx; + + ret = user_expand_func(xp->xp_arg, 3, args); + + current_sctx = save_current_sctx; + if (ccline->cmdbuff != NULL) + ccline->cmdbuff[ccline->cmdlen] = keep; + + vim_free(pat); + return ret; +} + +/* + * Expand names with a function defined by the user (EXPAND_USER_DEFINED and + * EXPAND_USER_LIST). + */ + static int +ExpandUserDefined( + char_u *pat, + expand_T *xp, + regmatch_T *regmatch, + char_u ***matches, + int *numMatches) +{ + char_u *retstr; + char_u *s; + char_u *e; + int keep; + garray_T ga; + int fuzzy; + int match; + int score = 0; + + fuzzy = cmdline_fuzzy_complete(pat); + *matches = NULL; + *numMatches = 0; + + retstr = call_user_expand_func(call_func_retstr, xp); + if (retstr == NULL) + return FAIL; + + if (!fuzzy) + ga_init2(&ga, sizeof(char *), 3); + else + ga_init2(&ga, sizeof(fuzmatch_str_T), 3); + + for (s = retstr; *s != NUL; s = e) + { + e = vim_strchr(s, '\n'); + if (e == NULL) + e = s + STRLEN(s); + keep = *e; + *e = NUL; + + if (xp->xp_pattern[0] != NUL) + { + if (!fuzzy) + match = vim_regexec(regmatch, s, (colnr_T)0); + else + { + score = fuzzy_match_str(s, pat); + match = (score != 0); + } + } + else + match = TRUE; // match everything + + *e = keep; + + if (match) + { + if (ga_grow(&ga, 1) == FAIL) + break; + if (!fuzzy) + ((char_u **)ga.ga_data)[ga.ga_len] = vim_strnsave(s, e - s); + else + { + fuzmatch_str_T *fuzmatch = + &((fuzmatch_str_T *)ga.ga_data)[ga.ga_len]; + fuzmatch->idx = ga.ga_len; + fuzmatch->str = vim_strnsave(s, e - s); + fuzmatch->score = score; + } + ++ga.ga_len; + } + + if (*e != NUL) + ++e; + } + vim_free(retstr); + + if (ga.ga_len == 0) + return OK; + + if (!fuzzy) + { + *matches = ga.ga_data; + *numMatches = ga.ga_len; + } + else + { + if (fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len, + FALSE) == FAIL) + return FAIL; + *numMatches = ga.ga_len; + } + return OK; +} + +/* + * Expand names with a list returned by a function defined by the user. + */ + static int +ExpandUserList( + expand_T *xp, + char_u ***matches, + int *numMatches) +{ + list_T *retlist; + listitem_T *li; + garray_T ga; + + *matches = NULL; + *numMatches = 0; + retlist = call_user_expand_func(call_func_retlist, xp); + if (retlist == NULL) + return FAIL; + + ga_init2(&ga, sizeof(char *), 3); + // Loop over the items in the list. + FOR_ALL_LIST_ITEMS(retlist, li) + { + if (li->li_tv.v_type != VAR_STRING || li->li_tv.vval.v_string == NULL) + continue; // Skip non-string items and empty strings + + if (ga_grow(&ga, 1) == FAIL) + break; + + ((char_u **)ga.ga_data)[ga.ga_len] = + vim_strsave(li->li_tv.vval.v_string); + ++ga.ga_len; + } + list_unref(retlist); + + *matches = ga.ga_data; + *numMatches = ga.ga_len; + return OK; +} +#endif + +/* + * Expand "file" for all comma-separated directories in "path". + * Adds the matches to "ga". Caller must init "ga". + * If "dirs" is TRUE only expand directory names. + */ + void +globpath( + char_u *path, + char_u *file, + garray_T *ga, + int expand_options, + int dirs) +{ + expand_T xpc; + char_u *buf; + int i; + int num_p; + char_u **p; + + buf = alloc(MAXPATHL); + if (buf == NULL) + return; + + ExpandInit(&xpc); + xpc.xp_context = dirs ? EXPAND_DIRECTORIES : EXPAND_FILES; + + // Loop over all entries in {path}. + while (*path != NUL) + { + // Copy one item of the path to buf[] and concatenate the file name. + copy_option_part(&path, buf, MAXPATHL, ","); + if (STRLEN(buf) + STRLEN(file) + 2 < MAXPATHL) + { +#if defined(MSWIN) + // Using the platform's path separator (\) makes vim incorrectly + // treat it as an escape character, use '/' instead. + if (*buf != NUL && !after_pathsep(buf, buf + STRLEN(buf))) + STRCAT(buf, "/"); +#else + add_pathsep(buf); +#endif + STRCAT(buf, file); + if (ExpandFromContext(&xpc, buf, &p, &num_p, + WILD_SILENT|expand_options) != FAIL && num_p > 0) + { + ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT|expand_options); + + if (ga_grow(ga, num_p) == OK) + // take over the pointers and put them in "ga" + for (i = 0; i < num_p; ++i) + { + ((char_u **)ga->ga_data)[ga->ga_len] = p[i]; + ++ga->ga_len; + } + vim_free(p); + } + } + } + + vim_free(buf); +} + +/* + * Translate some keys pressed when 'wildmenu' is used. + */ + int +wildmenu_translate_key( + cmdline_info_T *cclp, + int key, + expand_T *xp, + int did_wild_list) +{ + int c = key; + + if (cmdline_pum_active()) + { + // When the popup menu is used for cmdline completion: + // Up : go to the previous item in the menu + // Down : go to the next item in the menu + // Left : go to the parent directory + // Right: list the files in the selected directory + switch (c) + { + case K_UP: c = K_LEFT; break; + case K_DOWN: c = K_RIGHT; break; + case K_LEFT: c = K_UP; break; + case K_RIGHT: c = K_DOWN; break; + default: break; + } + } + + if (did_wild_list) + { + if (c == K_LEFT) + c = Ctrl_P; + else if (c == K_RIGHT) + c = Ctrl_N; + } + + // Hitting CR after "emenu Name.": complete submenu + if (xp->xp_context == EXPAND_MENUNAMES + && cclp->cmdpos > 1 + && cclp->cmdbuff[cclp->cmdpos - 1] == '.' + && cclp->cmdbuff[cclp->cmdpos - 2] != '\\' + && (c == '\n' || c == '\r' || c == K_KENTER)) + c = K_DOWN; + + return c; +} + +/* + * Delete characters on the command line, from "from" to the current + * position. + */ + static void +cmdline_del(cmdline_info_T *cclp, int from) +{ + mch_memmove(cclp->cmdbuff + from, cclp->cmdbuff + cclp->cmdpos, + (size_t)(cclp->cmdlen - cclp->cmdpos + 1)); + cclp->cmdlen -= cclp->cmdpos - from; + cclp->cmdpos = from; +} + +/* + * Handle a key pressed when the wild menu for the menu names + * (EXPAND_MENUNAMES) is displayed. + */ + static int +wildmenu_process_key_menunames(cmdline_info_T *cclp, int key, expand_T *xp) +{ + int i; + int j; + + // Hitting after "emenu Name.": complete submenu + if (key == K_DOWN && cclp->cmdpos > 0 + && cclp->cmdbuff[cclp->cmdpos - 1] == '.') + { + key = p_wc; + KeyTyped = TRUE; // in case the key was mapped + } + else if (key == K_UP) + { + // Hitting : Remove one submenu name in front of the + // cursor + int found = FALSE; + + j = (int)(xp->xp_pattern - cclp->cmdbuff); + i = 0; + while (--j > 0) + { + // check for start of menu name + if (cclp->cmdbuff[j] == ' ' + && cclp->cmdbuff[j - 1] != '\\') + { + i = j + 1; + break; + } + // check for start of submenu name + if (cclp->cmdbuff[j] == '.' + && cclp->cmdbuff[j - 1] != '\\') + { + if (found) + { + i = j + 1; + break; + } + else + found = TRUE; + } + } + if (i > 0) + cmdline_del(cclp, i); + key = p_wc; + KeyTyped = TRUE; // in case the key was mapped + xp->xp_context = EXPAND_NOTHING; + } + + return key; +} + +/* + * Handle a key pressed when the wild menu for file names (EXPAND_FILES) or + * directory names (EXPAND_DIRECTORIES) or shell command names + * (EXPAND_SHELLCMD) is displayed. + */ + static int +wildmenu_process_key_filenames(cmdline_info_T *cclp, int key, expand_T *xp) +{ + int i; + int j; + char_u upseg[5]; + + upseg[0] = PATHSEP; + upseg[1] = '.'; + upseg[2] = '.'; + upseg[3] = PATHSEP; + upseg[4] = NUL; + + if (key == K_DOWN + && cclp->cmdpos > 0 + && cclp->cmdbuff[cclp->cmdpos - 1] == PATHSEP + && (cclp->cmdpos < 3 + || cclp->cmdbuff[cclp->cmdpos - 2] != '.' + || cclp->cmdbuff[cclp->cmdpos - 3] != '.')) + { + // go down a directory + key = p_wc; + KeyTyped = TRUE; // in case the key was mapped + } + else if (STRNCMP(xp->xp_pattern, upseg + 1, 3) == 0 && key == K_DOWN) + { + // If in a direct ancestor, strip off one ../ to go down + int found = FALSE; + + j = cclp->cmdpos; + i = (int)(xp->xp_pattern - cclp->cmdbuff); + while (--j > i) + { + if (has_mbyte) + j -= (*mb_head_off)(cclp->cmdbuff, cclp->cmdbuff + j); + if (vim_ispathsep(cclp->cmdbuff[j])) + { + found = TRUE; + break; + } + } + if (found + && cclp->cmdbuff[j - 1] == '.' + && cclp->cmdbuff[j - 2] == '.' + && (vim_ispathsep(cclp->cmdbuff[j - 3]) || j == i + 2)) + { + cmdline_del(cclp, j - 2); + key = p_wc; + KeyTyped = TRUE; // in case the key was mapped + } + } + else if (key == K_UP) + { + // go up a directory + int found = FALSE; + + j = cclp->cmdpos - 1; + i = (int)(xp->xp_pattern - cclp->cmdbuff); + while (--j > i) + { + if (has_mbyte) + j -= (*mb_head_off)(cclp->cmdbuff, cclp->cmdbuff + j); + if (vim_ispathsep(cclp->cmdbuff[j]) +#ifdef BACKSLASH_IN_FILENAME + && vim_strchr((char_u *)" *?[{`$%#", + cclp->cmdbuff[j + 1]) == NULL +#endif + ) + { + if (found) + { + i = j + 1; + break; + } + else + found = TRUE; + } + } + + if (!found) + j = i; + else if (STRNCMP(cclp->cmdbuff + j, upseg, 4) == 0) + j += 4; + else if (STRNCMP(cclp->cmdbuff + j, upseg + 1, 3) == 0 + && j == i) + j += 3; + else + j = 0; + if (j > 0) + { + // TODO this is only for DOS/UNIX systems - need to put in + // machine-specific stuff here and in upseg init + cmdline_del(cclp, j); + put_on_cmdline(upseg + 1, 3, FALSE); + } + else if (cclp->cmdpos > i) + cmdline_del(cclp, i); + + // Now complete in the new directory. Set KeyTyped in case the + // Up key came from a mapping. + key = p_wc; + KeyTyped = TRUE; + } + + return key; +} + +/* + * Handle a key pressed when the wild menu is displayed + */ + int +wildmenu_process_key(cmdline_info_T *cclp, int key, expand_T *xp) +{ + if (xp->xp_context == EXPAND_MENUNAMES) + return wildmenu_process_key_menunames(cclp, key, xp); + else if ((xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_DIRECTORIES + || xp->xp_context == EXPAND_SHELLCMD)) + return wildmenu_process_key_filenames(cclp, key, xp); + + return key; +} + +/* + * Free expanded names when finished walking through the matches + */ + void +wildmenu_cleanup(cmdline_info_T *cclp UNUSED) +{ + int skt = KeyTyped; + + if (!p_wmnu || wild_menu_showing == 0) + return; + +#ifdef FEAT_EVAL + int save_RedrawingDisabled = RedrawingDisabled; + if (cclp->input_fn) + RedrawingDisabled = 0; +#endif + + if (wild_menu_showing == WM_SCROLLED) + { + // Entered command line, move it up + cmdline_row--; + redrawcmd(); + } + else if (save_p_ls != -1) + { + // restore 'laststatus' and 'winminheight' + p_ls = save_p_ls; + p_wmh = save_p_wmh; + last_status(FALSE); + update_screen(UPD_VALID); // redraw the screen NOW + redrawcmd(); + save_p_ls = -1; + } + else + { + win_redraw_last_status(topframe); + redraw_statuslines(); + } + KeyTyped = skt; + wild_menu_showing = 0; +#ifdef FEAT_EVAL + if (cclp->input_fn) + RedrawingDisabled = save_RedrawingDisabled; +#endif +} + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * "getcompletion()" function + */ + void +f_getcompletion(typval_T *argvars, typval_T *rettv) +{ + char_u *pat; + char_u *type; + expand_T xpc; + int filtered = FALSE; + int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH + | WILD_NO_BEEP | WILD_HOME_REPLACE; + + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_string_arg(argvars, 1) == FAIL + || check_for_opt_bool_arg(argvars, 2) == FAIL)) + return; + + pat = tv_get_string(&argvars[0]); + if (check_for_string_arg(argvars, 1) == FAIL) + return; + type = tv_get_string(&argvars[1]); + + if (argvars[2].v_type != VAR_UNKNOWN) + filtered = tv_get_bool_chk(&argvars[2], NULL); + + if (p_wic) + options |= WILD_ICASE; + + // For filtered results, 'wildignore' is used + if (!filtered) + options |= WILD_KEEP_ALL; + + ExpandInit(&xpc); + if (STRCMP(type, "cmdline") == 0) + { + int cmdline_len = (int)STRLEN(pat); + set_cmd_context(&xpc, pat, cmdline_len, cmdline_len, FALSE); + xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); + xpc.xp_col = cmdline_len; + } + else + { + xpc.xp_pattern = pat; + xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); + xpc.xp_line = pat; + + xpc.xp_context = cmdcomplete_str_to_type(type); + if (xpc.xp_context == EXPAND_NOTHING) + { + semsg(_(e_invalid_argument_str), type); + return; + } + + if (xpc.xp_context == EXPAND_USER_DEFINED) + { + // Must be "custom,funcname" pattern + if (STRNCMP(type, "custom,", 7) != 0) + { + semsg(_(e_invalid_argument_str), type); + return; + } + + xpc.xp_arg = type + 7; + } + + if (xpc.xp_context == EXPAND_USER_LIST) + { + // Must be "customlist,funcname" pattern + if (STRNCMP(type, "customlist,", 11) != 0) + { + semsg(_(e_invalid_argument_str), type); + return; + } + + xpc.xp_arg = type + 11; + } + +# if defined(FEAT_MENU) + if (xpc.xp_context == EXPAND_MENUS) + { + set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, FALSE); + xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); + } +# endif +# ifdef FEAT_CSCOPE + if (xpc.xp_context == EXPAND_CSCOPE) + { + set_context_in_cscope_cmd(&xpc, xpc.xp_pattern, CMD_cscope); + xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); + } +# endif +# ifdef FEAT_SIGNS + if (xpc.xp_context == EXPAND_SIGN) + { + set_context_in_sign_cmd(&xpc, xpc.xp_pattern); + xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); + } +# endif + if (xpc.xp_context == EXPAND_RUNTIME) + { + set_context_in_runtime_cmd(&xpc, xpc.xp_pattern); + xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); + } + } + + if (cmdline_fuzzy_completion_supported(&xpc)) + // when fuzzy matching, don't modify the search string + pat = vim_strsave(xpc.xp_pattern); + else + pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); + + if (rettv_list_alloc(rettv) == OK && pat != NULL) + { + int i; + + ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); + + for (i = 0; i < xpc.xp_numfiles; i++) + list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1); + } + vim_free(pat); + ExpandCleanup(&xpc); +} +#endif // FEAT_EVAL diff --git a/userland/apps/vim/src/cmdhist.c b/userland/apps/vim/src/cmdhist.c new file mode 100644 index 00000000..96a9b3e9 --- /dev/null +++ b/userland/apps/vim/src/cmdhist.c @@ -0,0 +1,788 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * cmdhist.c: Functions for the history of the command-line. + */ + +#include "vim.h" + +static histentry_T *(history[HIST_COUNT]) = {NULL, NULL, NULL, NULL, NULL}; +static int hisidx[HIST_COUNT] = {-1, -1, -1, -1, -1}; // lastused entry +static int hisnum[HIST_COUNT] = {0, 0, 0, 0, 0}; + // identifying (unique) number of newest history entry +static int hislen = 0; // actual length of history tables + +/* + * Return the length of the history tables + */ + int +get_hislen(void) +{ + return hislen; +} + +/* + * Return a pointer to a specified history table + */ + histentry_T * +get_histentry(int hist_type) +{ + return history[hist_type]; +} + +#if defined(FEAT_VIMINFO) || defined(PROTO) + void +set_histentry(int hist_type, histentry_T *entry) +{ + history[hist_type] = entry; +} +#endif + + int * +get_hisidx(int hist_type) +{ + return &hisidx[hist_type]; +} + +#if defined(FEAT_VIMINFO) || defined(PROTO) + int * +get_hisnum(int hist_type) +{ + return &hisnum[hist_type]; +} +#endif + +/* + * Translate a history character to the associated type number. + */ + int +hist_char2type(int c) +{ + if (c == ':') + return HIST_CMD; + if (c == '=') + return HIST_EXPR; + if (c == '@') + return HIST_INPUT; + if (c == '>') + return HIST_DEBUG; + return HIST_SEARCH; // must be '?' or '/' +} + +/* + * Table of history names. + * These names are used in :history and various hist...() functions. + * It is sufficient to give the significant prefix of a history name. + */ + +static char *(history_names[]) = +{ + "cmd", + "search", + "expr", + "input", + "debug", + NULL +}; + +/* + * Function given to ExpandGeneric() to obtain the possible first + * arguments of the ":history command. + */ + char_u * +get_history_arg(expand_T *xp UNUSED, int idx) +{ + char *short_names = ":=@>?/"; + int short_names_count = (int)STRLEN(short_names); + int history_name_count = ARRAY_LENGTH(history_names) - 1; + + if (idx < short_names_count) + { + xp->xp_buf[0] = (char_u)short_names[idx]; + xp->xp_buf[1] = NUL; + return xp->xp_buf; + } + if (idx < short_names_count + history_name_count) + return (char_u *)history_names[idx - short_names_count]; + if (idx == short_names_count + history_name_count) + return (char_u *)"all"; + return NULL; +} + +/* + * init_history() - Initialize the command line history. + * Also used to re-allocate the history when the size changes. + */ + void +init_history(void) +{ + int newlen; // new length of history table + histentry_T *temp; + int i; + int j; + int type; + + // If size of history table changed, reallocate it + newlen = (int)p_hi; + if (newlen == hislen) // history length didn't change + return; + + // history length changed + for (type = 0; type < HIST_COUNT; ++type) // adjust the tables + { + if (newlen > 0) + { + temp = ALLOC_MULT(histentry_T, newlen); + if (temp == NULL) // out of memory! + { + if (type == 0) // first one: just keep the old length + { + newlen = hislen; + break; + } + // Already changed one table, now we can only have zero + // length for all tables. + newlen = 0; + type = -1; + continue; + } + } + else + temp = NULL; + + if (hisidx[type] < 0) // there are no entries yet + { + for (i = 0; i < newlen; ++i) + clear_hist_entry(&temp[i]); + } + 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) + clear_hist_entry(&temp[i]); + 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].hisstr); + if (--j < 0) + j = hislen - 1; + if (j == hisidx[type]) + break; + } + hisidx[type] = newlen - 1; + } + vim_free(history[type]); + history[type] = temp; + } + hislen = newlen; +} + + void +clear_hist_entry(histentry_T *hisptr) +{ + hisptr->hisnum = 0; + hisptr->viminfo = FALSE; + hisptr->hisstr = NULL; + hisptr->time_set = 0; +} + +/* + * Check if command line 'str' is already in history. + * If 'move_to_front' is TRUE, matching entry is moved to end of history. + */ + int +in_history( + int type, + char_u *str, + int move_to_front, // Move the entry to the front if it exists + int sep, + int writing) // ignore entries read from viminfo +{ + int i; + int last_i = -1; + char_u *p; + + if (hisidx[type] < 0) + return FALSE; + i = hisidx[type]; + do + { + if (history[type][i].hisstr == NULL) + return FALSE; + + // For search history, check that the separator character matches as + // well. + p = history[type][i].hisstr; + if (STRCMP(str, p) == 0 + && !(writing && history[type][i].viminfo) + && (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) + { + if (!move_to_front) + return TRUE; + last_i = i; + break; + } + if (--i < 0) + i = hislen - 1; + } while (i != hisidx[type]); + + if (last_i < 0) + return FALSE; + + str = history[type][i].hisstr; + while (i != hisidx[type]) + { + if (++i >= hislen) + i = 0; + history[type][last_i] = history[type][i]; + last_i = i; + } + history[type][i].hisnum = ++hisnum[type]; + history[type][i].viminfo = FALSE; + history[type][i].hisstr = str; + history[type][i].time_set = vim_time(); + return TRUE; +} + +/* + * Convert history name (from table above) to its HIST_ equivalent. + * When "name" is empty, return "cmd" history. + * Returns -1 for unknown history name. + */ + static int +get_histtype(char_u *name) +{ + int i; + int len = (int)STRLEN(name); + + // No argument: use current history. + if (len == 0) + return hist_char2type(get_cmdline_firstc()); + + for (i = 0; history_names[i] != NULL; ++i) + if (STRNICMP(name, history_names[i], len) == 0) + return i; + + if (vim_strchr((char_u *)":=@>?/", name[0]) != NULL && name[1] == NUL) + return hist_char2type(name[0]); + + return -1; +} + +static int last_maptick = -1; // last seen maptick + +/* + * 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 one of he HIST_ + * values. + */ + void +add_to_history( + int histype, + char_u *new_entry, + int in_map, // consider maptick when inside a mapping + int sep) // separator character used (search hist) +{ + histentry_T *hisptr; + int len; + + if (hislen == 0) // no history + return; + + if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) && histype == HIST_SEARCH) + return; + + // Searches inside the same mapping overwrite each other, so that only + // the last line is kept. Be careful not to remove a line that was moved + // down, only lines that were added. + if (histype == HIST_SEARCH && in_map) + { + if (maptick == last_maptick && hisidx[HIST_SEARCH] >= 0) + { + // Current line is from the same mapping, remove it + hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]]; + vim_free(hisptr->hisstr); + clear_hist_entry(hisptr); + --hisnum[histype]; + if (--hisidx[HIST_SEARCH] < 0) + hisidx[HIST_SEARCH] = hislen - 1; + } + last_maptick = -1; + } + + if (in_history(histype, new_entry, TRUE, sep, FALSE)) + return; + + if (++hisidx[histype] == hislen) + hisidx[histype] = 0; + hisptr = &history[histype][hisidx[histype]]; + vim_free(hisptr->hisstr); + + // Store the separator after the NUL of the string. + len = (int)STRLEN(new_entry); + hisptr->hisstr = vim_strnsave(new_entry, len + 2); + if (hisptr->hisstr != NULL) + hisptr->hisstr[len + 1] = sep; + + hisptr->hisnum = ++hisnum[histype]; + hisptr->viminfo = FALSE; + hisptr->time_set = vim_time(); + if (histype == HIST_SEARCH && in_map) + last_maptick = maptick; +} + +#if defined(FEAT_EVAL) || defined(PROTO) + +/* + * Get identifier of newest history entry. + * "histype" may be one of the HIST_ values. + */ + static int +get_history_idx(int histype) +{ + if (hislen == 0 || histype < 0 || histype >= HIST_COUNT + || hisidx[histype] < 0) + return -1; + + return history[histype][hisidx[histype]].hisnum; +} + +/* + * Calculate history index from a number: + * num > 0: seen as identifying number of a history entry + * num < 0: relative position in history wrt newest entry + * "histype" may be one of the HIST_ values. + */ + static int +calc_hist_idx(int histype, int num) +{ + int i; + histentry_T *hist; + int wrapped = FALSE; + + if (hislen == 0 || histype < 0 || histype >= HIST_COUNT + || (i = hisidx[histype]) < 0 || num == 0) + return -1; + + hist = history[histype]; + if (num > 0) + { + while (hist[i].hisnum > num) + if (--i < 0) + { + if (wrapped) + break; + i += hislen; + wrapped = TRUE; + } + if (i >= 0 && hist[i].hisnum == num && hist[i].hisstr != NULL) + return i; + } + else if (-num <= hislen) + { + i += num + 1; + if (i < 0) + i += hislen; + if (hist[i].hisstr != NULL) + return i; + } + return -1; +} + +/* + * Get a history entry by its index. + * "histype" may be one of the HIST_ values. + */ + static char_u * +get_history_entry(int histype, int idx) +{ + idx = calc_hist_idx(histype, idx); + if (idx >= 0) + return history[histype][idx].hisstr; + else + return (char_u *)""; +} + +/* + * Clear all entries of a history. + * "histype" may be one of the HIST_ values. + */ + static int +clr_history(int histype) +{ + int i; + histentry_T *hisptr; + + if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) + { + hisptr = history[histype]; + for (i = hislen; i--;) + { + vim_free(hisptr->hisstr); + clear_hist_entry(hisptr); + hisptr++; + } + hisidx[histype] = -1; // mark history as cleared + hisnum[histype] = 0; // reset identifier counter + return OK; + } + return FAIL; +} + +/* + * Remove all entries matching {str} from a history. + * "histype" may be one of the HIST_ values. + */ + static int +del_history_entry(int histype, char_u *str) +{ + regmatch_T regmatch; + histentry_T *hisptr; + int idx; + int i; + int last; + int found = FALSE; + + if (hislen == 0 || histype < 0 || histype >= HIST_COUNT || *str == NUL + || hisidx[histype] < 0) + return FALSE; + + idx = hisidx[histype]; + regmatch.regprog = vim_regcomp(str, RE_MAGIC + RE_STRING); + if (regmatch.regprog == NULL) + return FALSE; + + regmatch.rm_ic = FALSE; // always match case + + i = last = idx; + do + { + hisptr = &history[histype][i]; + if (hisptr->hisstr == NULL) + break; + if (vim_regexec(®match, hisptr->hisstr, (colnr_T)0)) + { + found = TRUE; + vim_free(hisptr->hisstr); + clear_hist_entry(hisptr); + } + else + { + if (i != last) + { + history[histype][last] = *hisptr; + clear_hist_entry(hisptr); + } + if (--last < 0) + last += hislen; + } + if (--i < 0) + i += hislen; + } while (i != idx); + + if (history[histype][idx].hisstr == NULL) + hisidx[histype] = -1; + + vim_regfree(regmatch.regprog); + return found; +} + +/* + * Remove an indexed entry from a history. + * "histype" may be one of the HIST_ values. + */ + static int +del_history_idx(int histype, int idx) +{ + int i, j; + + i = calc_hist_idx(histype, idx); + if (i < 0) + return FALSE; + idx = hisidx[histype]; + vim_free(history[histype][i].hisstr); + + // When deleting the last added search string in a mapping, reset + // last_maptick, so that the last added search string isn't deleted again. + if (histype == HIST_SEARCH && maptick == last_maptick && i == idx) + last_maptick = -1; + + while (i != idx) + { + j = (i + 1) % hislen; + history[histype][i] = history[histype][j]; + i = j; + } + clear_hist_entry(&history[histype][i]); + if (--i < 0) + i += hislen; + hisidx[histype] = i; + return TRUE; +} + +/* + * "histadd()" function + */ + void +f_histadd(typval_T *argvars UNUSED, typval_T *rettv) +{ + int histype; + char_u *str; + char_u buf[NUMBUFLEN]; + + rettv->vval.v_number = FALSE; + if (check_secure()) + return; + + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_string_arg(argvars, 1) == FAIL)) + return; + + str = tv_get_string_chk(&argvars[0]); // NULL on type error + histype = str != NULL ? get_histtype(str) : -1; + if (histype < 0) + return; + + str = tv_get_string_buf(&argvars[1], buf); + if (*str == NUL) + return; + + init_history(); + add_to_history(histype, str, FALSE, NUL); + rettv->vval.v_number = TRUE; +} + +/* + * "histdel()" function + */ + void +f_histdel(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ + int n; + char_u buf[NUMBUFLEN]; + char_u *str; + + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_opt_string_or_number_arg(argvars, 1) == FAIL)) + return; + + str = tv_get_string_chk(&argvars[0]); // NULL on type error + if (str == NULL) + n = 0; + else if (argvars[1].v_type == VAR_UNKNOWN) + // only one argument: clear entire history + n = clr_history(get_histtype(str)); + else if (argvars[1].v_type == VAR_NUMBER) + // index given: remove that entry + n = del_history_idx(get_histtype(str), + (int)tv_get_number(&argvars[1])); + else + // string given: remove all matching entries + n = del_history_entry(get_histtype(str), + tv_get_string_buf(&argvars[1], buf)); + rettv->vval.v_number = n; +} + +/* + * "histget()" function + */ + void +f_histget(typval_T *argvars UNUSED, typval_T *rettv) +{ + int type; + int idx; + char_u *str; + + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_opt_number_arg(argvars, 1) == FAIL)) + return; + + str = tv_get_string_chk(&argvars[0]); // NULL on type error + if (str == NULL) + rettv->vval.v_string = NULL; + else + { + type = get_histtype(str); + if (argvars[1].v_type == VAR_UNKNOWN) + idx = get_history_idx(type); + else + idx = (int)tv_get_number_chk(&argvars[1], NULL); + // -1 on type error + rettv->vval.v_string = vim_strsave(get_history_entry(type, idx)); + } + rettv->v_type = VAR_STRING; +} + +/* + * "histnr()" function + */ + void +f_histnr(typval_T *argvars UNUSED, typval_T *rettv) +{ + int i; + char_u *histname; + + if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL) + return; + + histname = tv_get_string_chk(&argvars[0]); + i = histname == NULL ? HIST_CMD - 1 : get_histtype(histname); + if (i >= HIST_CMD && i < HIST_COUNT) + i = get_history_idx(i); + else + i = -1; + rettv->vval.v_number = i; +} +#endif // FEAT_EVAL + +#if defined(FEAT_CRYPT) || defined(PROTO) +/* + * Very specific function to remove the value in ":set key=val" from the + * history. + */ + void +remove_key_from_history(void) +{ + char_u *p; + int i; + + i = hisidx[HIST_CMD]; + if (i < 0) + return; + p = history[HIST_CMD][i].hisstr; + if (p == NULL) + return; + + for ( ; *p; ++p) + if (STRNCMP(p, "key", 3) == 0 && !isalpha(p[3])) + { + p = vim_strchr(p + 3, '='); + if (p == NULL) + break; + ++p; + for (i = 0; p[i] && !VIM_ISWHITE(p[i]); ++i) + if (p[i] == '\\' && p[i + 1]) + ++i; + STRMOVE(p, p + i); + --p; + } +} +#endif + +/* + * :history command - print a history + */ + void +ex_history(exarg_T *eap) +{ + histentry_T *hist; + int histype1 = HIST_CMD; + int histype2 = HIST_CMD; + int hisidx1 = 1; + int hisidx2 = -1; + int idx; + int i, j, k; + char_u *end; + char_u *arg = eap->arg; + + if (hislen == 0) + { + msg(_("'history' option is zero")); + return; + } + + if (!(VIM_ISDIGIT(*arg) || *arg == '-' || *arg == ',')) + { + end = arg; + while (ASCII_ISALPHA(*end) + || vim_strchr((char_u *)":=@>/?", *end) != NULL) + end++; + i = *end; + *end = NUL; + histype1 = get_histtype(arg); + if (histype1 == -1) + { + if (STRNICMP(arg, "all", STRLEN(arg)) == 0) + { + histype1 = 0; + histype2 = HIST_COUNT-1; + } + else + { + *end = i; + semsg(_(e_trailing_characters_str), arg); + return; + } + } + else + histype2 = histype1; + *end = i; + } + else + end = arg; + if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) + { + if (*end != NUL) + semsg(_(e_trailing_characters_str), end); + else + semsg(_(e_val_too_large), arg); + return; + } + + for (; !got_int && histype1 <= histype2; ++histype1) + { + STRCPY(IObuff, "\n # "); + STRCAT(STRCAT(IObuff, history_names[histype1]), " history"); + msg_puts_title((char *)IObuff); + idx = hisidx[histype1]; + hist = history[histype1]; + j = hisidx1; + k = hisidx2; + if (j < 0) + j = (-j > hislen) ? 0 : hist[(hislen+j+idx+1) % hislen].hisnum; + if (k < 0) + k = (-k > hislen) ? 0 : hist[(hislen+k+idx+1) % hislen].hisnum; + if (idx >= 0 && j <= k) + for (i = idx + 1; !got_int; ++i) + { + if (i == hislen) + i = 0; + if (hist[i].hisstr != NULL + && hist[i].hisnum >= j && hist[i].hisnum <= k) + { + msg_putchar('\n'); + sprintf((char *)IObuff, "%c%6d ", i == idx ? '>' : ' ', + hist[i].hisnum); + if (vim_strsize(hist[i].hisstr) > (int)Columns - 10) + trunc_string(hist[i].hisstr, IObuff + STRLEN(IObuff), + (int)Columns - 10, IOSIZE - (int)STRLEN(IObuff)); + else + STRCAT(IObuff, hist[i].hisstr); + msg_outtrans(IObuff); + out_flush(); + } + if (i == idx) + break; + } + } +} diff --git a/userland/apps/vim/src/crypt.c b/userland/apps/vim/src/crypt.c new file mode 100644 index 00000000..09e90119 --- /dev/null +++ b/userland/apps/vim/src/crypt.c @@ -0,0 +1,1405 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * crypt.c: Generic encryption support. + */ +#include "vim.h" + +#if defined(FEAT_CRYPT) || defined(PROTO) +/* + * Optional encryption support. + * Mohsin Ahmed, mosh@sasi.com, 1998-09-24 + * Based on zip/crypt sources. + * Refactored by David Leadbeater, 2014. + * + * NOTE FOR USA: Since 2000 exporting this code from the USA is allowed to + * most countries. There are a few exceptions, but that still should not be a + * problem since this code was originally created in Europe and India. + * + * Blowfish addition originally made by Mohsin Ahmed, + * http://www.cs.albany.edu/~mosh 2010-03-14 + * Based on blowfish by Bruce Schneier (http://www.schneier.com/blowfish.html) + * and sha256 by Christophe Devine. + */ + +typedef struct { + char *name; // encryption name as used in 'cryptmethod' + char *magic; // magic bytes stored in file header + int salt_len; // length of salt, or 0 when not using salt + int seed_len; // length of seed, or 0 when not using seed + int add_len; // additional length in the header needed for storing + // custom data +#ifdef CRYPT_NOT_INPLACE + int works_inplace; // encryption/decryption can be done in-place +#endif + int whole_undofile; // whole undo file is encrypted + + // Optional function pointer for a self-test. + int (*self_test_fn)(void); + + // Function pointer for initializing encryption/decryption. + int (* init_fn)(cryptstate_T *state, char_u *key, crypt_arg_T *arg); + + // Function pointers for encoding/decoding from one buffer into another. + // Optional, however, these or the _buffer ones should be configured. + void (*encode_fn)(cryptstate_T *state, char_u *from, size_t len, + char_u *to, int last); + void (*decode_fn)(cryptstate_T *state, char_u *from, size_t len, + char_u *to, int last); + + // Function pointers for encoding and decoding, can buffer data if needed. + // Optional (however, these or the above should be configured). + long (*encode_buffer_fn)(cryptstate_T *state, char_u *from, size_t len, + char_u **newptr, int last); + long (*decode_buffer_fn)(cryptstate_T *state, char_u *from, size_t len, + char_u **newptr, int last); + + // Function pointers for in-place encoding and decoding, used for + // crypt_*_inplace(). "from" and "to" arguments will be equal. + // These may be the same as decode_fn and encode_fn above, however an + // algorithm may implement them in a way that is not interchangeable with + // the crypt_(en|de)code() interface (for example because it wishes to add + // padding to files). + // This method is used for swap and undo files which have a rigid format. + void (*encode_inplace_fn)(cryptstate_T *state, char_u *p1, size_t len, + char_u *p2, int last); + void (*decode_inplace_fn)(cryptstate_T *state, char_u *p1, size_t len, + char_u *p2, int last); +} cryptmethod_T; + +static int crypt_sodium_init_(cryptstate_T *state, char_u *key, crypt_arg_T *arg); +static long crypt_sodium_buffer_decode(cryptstate_T *state, char_u *from, size_t len, char_u **buf_out, int last); +static long crypt_sodium_buffer_encode(cryptstate_T *state, char_u *from, size_t len, char_u **buf_out, int last); +# if defined(FEAT_SODIUM) || defined(PROTO) +static void crypt_long_long_to_char(long long n, char_u *s); +static void crypt_int_to_char(int n, char_u *s); +static long long crypt_char_to_long_long(char_u *s); +static int crypt_char_to_int(char_u *s); +#endif +#if defined(FEAT_EVAL) && defined(FEAT_SODIUM) +static void crypt_sodium_report_hash_params(unsigned long long opslimit, unsigned long long ops_def, size_t memlimit, size_t mem_def, int alg, int alg_def); +#endif + +// index is method_nr of cryptstate_T, CRYPT_M_* +static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = { + // PK_Zip; very weak + { + "zip", + "VimCrypt~01!", + 0, + 0, + 0, +#ifdef CRYPT_NOT_INPLACE + TRUE, +#endif + FALSE, + NULL, + crypt_zip_init, + crypt_zip_encode, crypt_zip_decode, + NULL, NULL, + crypt_zip_encode, crypt_zip_decode, + }, + + // Blowfish/CFB + SHA-256 custom key derivation; implementation issues. + { + "blowfish", + "VimCrypt~02!", + 8, + 8, + 0, +#ifdef CRYPT_NOT_INPLACE + TRUE, +#endif + FALSE, + blowfish_self_test, + crypt_blowfish_init, + crypt_blowfish_encode, crypt_blowfish_decode, + NULL, NULL, + crypt_blowfish_encode, crypt_blowfish_decode, + }, + + // Blowfish/CFB + SHA-256 custom key derivation; fixed. + { + "blowfish2", + "VimCrypt~03!", + 8, + 8, + 0, +#ifdef CRYPT_NOT_INPLACE + TRUE, +#endif + TRUE, + blowfish_self_test, + crypt_blowfish_init, + crypt_blowfish_encode, crypt_blowfish_decode, + NULL, NULL, + crypt_blowfish_encode, crypt_blowfish_decode, + }, + + // XChaCha20 using libsodium; implementation issues + { + "xchacha20", + "VimCrypt~04!", +#ifdef FEAT_SODIUM + crypto_pwhash_argon2id_SALTBYTES, // 16 +#else + 16, +#endif + 8, + 0, +#ifdef CRYPT_NOT_INPLACE + FALSE, +#endif + FALSE, + NULL, + crypt_sodium_init_, + NULL, NULL, + crypt_sodium_buffer_encode, crypt_sodium_buffer_decode, + NULL, NULL, + }, + // XChaCha20 using libsodium; stores parameters in header + { + "xchacha20v2", + "VimCrypt~05!", +#ifdef FEAT_SODIUM + crypto_pwhash_argon2id_SALTBYTES, // 16 +#else + 16, +#endif + 8, + // sizeof(crypto_pwhash_OPSLIMIT_INTERACTIVE + crypto_pwhash_MEMLIMIT_INTERACTIVE + crypto_pwhash_ALG_DEFAULT) + 20, +#ifdef CRYPT_NOT_INPLACE + FALSE, +#endif + FALSE, + NULL, + crypt_sodium_init_, + NULL, NULL, + crypt_sodium_buffer_encode, crypt_sodium_buffer_decode, + NULL, NULL, + }, + + // NOTE: when adding a new method, use some random bytes for the magic key, + // to avoid that a text file is recognized as encrypted. +}; + +#if defined(FEAT_SODIUM) || defined(PROTO) +typedef struct { + size_t count; + unsigned char key[crypto_box_SEEDBYTES]; + // 32, same as crypto_secretstream_xchacha20poly1305_KEYBYTES + crypto_secretstream_xchacha20poly1305_state + state; +} sodium_state_T; + + +# ifdef DYNAMIC_SODIUM +# ifdef MSWIN +# define SODIUM_PROC FARPROC +# define load_dll vimLoadLib +# define symbol_from_dll GetProcAddress +# define close_dll FreeLibrary +# define load_dll_error GetWin32Error +# else +# error Dynamic loading of libsodium is not supported for now. +//# define HINSTANCE void* +//# define SODIUM_PROC void* +//# define load_dll(n) dlopen((n), RTLD_LAZY|RTLD_GLOBAL) +//# define symbol_from_dll dlsym +//# define close_dll dlclose +//# define load_dll_error dlerror +# endif + +# define sodium_init load_sodium +# define sodium_free dll_sodium_free +# define sodium_malloc dll_sodium_malloc +# define sodium_memzero dll_sodium_memzero +# define sodium_mlock dll_sodium_mlock +# define sodium_munlock dll_sodium_munlock +# define crypto_secretstream_xchacha20poly1305_init_push \ + dll_crypto_secretstream_xchacha20poly1305_init_push +# define crypto_secretstream_xchacha20poly1305_push \ + dll_crypto_secretstream_xchacha20poly1305_push +# define crypto_secretstream_xchacha20poly1305_init_pull \ + dll_crypto_secretstream_xchacha20poly1305_init_pull +# define crypto_secretstream_xchacha20poly1305_pull \ + dll_crypto_secretstream_xchacha20poly1305_pull +# define crypto_pwhash dll_crypto_pwhash +# define randombytes_buf dll_randombytes_buf +# define randombytes_random dll_randombytes_random + +static int (*dll_sodium_init)(void) = NULL; +static void (*dll_sodium_free)(void *) = NULL; +static void *(*dll_sodium_malloc)(const size_t) = NULL; +static void (*dll_sodium_memzero)(void * const, const size_t) = NULL; +static int (*dll_sodium_mlock)(void * const, const size_t) = NULL; +static int (*dll_sodium_munlock)(void * const, const size_t) = NULL; +static int (*dll_crypto_secretstream_xchacha20poly1305_init_push) + (crypto_secretstream_xchacha20poly1305_state *state, + unsigned char [], + const unsigned char []) = NULL; +static int (*dll_crypto_secretstream_xchacha20poly1305_push) + (crypto_secretstream_xchacha20poly1305_state *state, + unsigned char *c, unsigned long long *clen_p, + const unsigned char *m, unsigned long long mlen, + const unsigned char *ad, unsigned long long adlen, unsigned char tag) + = NULL; +static int (*dll_crypto_secretstream_xchacha20poly1305_init_pull) + (crypto_secretstream_xchacha20poly1305_state *state, + const unsigned char [], + const unsigned char []) = NULL; +static int (*dll_crypto_secretstream_xchacha20poly1305_pull) + (crypto_secretstream_xchacha20poly1305_state *state, + unsigned char *m, unsigned long long *mlen_p, unsigned char *tag_p, + const unsigned char *c, unsigned long long clen, + const unsigned char *ad, unsigned long long adlen) = NULL; +static int (*dll_crypto_pwhash)(unsigned char * const out, + unsigned long long outlen, + const char * const passwd, unsigned long long passwdlen, + const unsigned char * const salt, + unsigned long long opslimit, size_t memlimit, int alg) + = NULL; +static void (*dll_randombytes_buf)(void * const buf, const size_t size); +static uint32_t (*dll_randombytes_random)(void); + +static struct { + const char *name; + SODIUM_PROC *ptr; +} sodium_funcname_table[] = { + {"sodium_init", (SODIUM_PROC*)&dll_sodium_init}, + {"sodium_free", (SODIUM_PROC*)&dll_sodium_free}, + {"sodium_malloc", (SODIUM_PROC*)&dll_sodium_malloc}, + {"sodium_memzero", (SODIUM_PROC*)&dll_sodium_memzero}, + {"sodium_mlock", (SODIUM_PROC*)&dll_sodium_mlock}, + {"sodium_munlock", (SODIUM_PROC*)&dll_sodium_munlock}, + {"crypto_secretstream_xchacha20poly1305_init_push", (SODIUM_PROC*)&dll_crypto_secretstream_xchacha20poly1305_init_push}, + {"crypto_secretstream_xchacha20poly1305_push", (SODIUM_PROC*)&dll_crypto_secretstream_xchacha20poly1305_push}, + {"crypto_secretstream_xchacha20poly1305_init_pull", (SODIUM_PROC*)&dll_crypto_secretstream_xchacha20poly1305_init_pull}, + {"crypto_secretstream_xchacha20poly1305_pull", (SODIUM_PROC*)&dll_crypto_secretstream_xchacha20poly1305_pull}, + {"crypto_pwhash", (SODIUM_PROC*)&dll_crypto_pwhash}, + {"randombytes_buf", (SODIUM_PROC*)&dll_randombytes_buf}, + {"randombytes_random", (SODIUM_PROC*)&dll_randombytes_random}, + {NULL, NULL} +}; + + static int +sodium_runtime_link_init(int verbose) +{ + static HINSTANCE hsodium = NULL; + const char *libname = DYNAMIC_SODIUM_DLL; + int i; + + if (hsodium != NULL) + return OK; + + hsodium = load_dll(libname); + if (hsodium == NULL) + { + if (verbose) + semsg(_(e_could_not_load_library_str_str), libname, load_dll_error()); + return FAIL; + } + + for (i = 0; sodium_funcname_table[i].ptr; ++i) + { + if ((*sodium_funcname_table[i].ptr = symbol_from_dll(hsodium, + sodium_funcname_table[i].name)) == NULL) + { + close_dll(hsodium); + hsodium = NULL; + if (verbose) + semsg(_(e_could_not_load_library_function_str), sodium_funcname_table[i].name); + return FAIL; + } + } + return OK; +} + + static int +load_sodium(void) +{ + if (sodium_runtime_link_init(TRUE) == FAIL) + return -1; + return dll_sodium_init(); +} +# endif + +# if defined(DYNAMIC_SODIUM) || defined(PROTO) + int +sodium_enabled(int verbose) +{ + return sodium_runtime_link_init(verbose) == OK; +} +# endif +#endif + +#define CRYPT_MAGIC_LEN 12 // cannot change +static char crypt_magic_head[] = "VimCrypt~"; + +/* + * Return int value for crypt method name. + * 0 for "zip", the old method. Also for any non-valid value. + * 1 for "blowfish". + * 2 for "blowfish2". + */ + int +crypt_method_nr_from_name(char_u *name) +{ + int i; + + for (i = 0; i < CRYPT_M_COUNT; ++i) + if (STRCMP(name, cryptmethods[i].name) == 0) + return i; + return 0; +} + +/* + * Get the crypt method used for a file from "ptr[len]", the magic text at the + * start of the file. + * Returns -1 when no encryption used. + */ + int +crypt_method_nr_from_magic(char *ptr, int len) +{ + int i; + + if (len < CRYPT_MAGIC_LEN) + return -1; + + for (i = 0; i < CRYPT_M_COUNT; i++) + if (memcmp(ptr, cryptmethods[i].magic, CRYPT_MAGIC_LEN) == 0) + return i; + + i = (int)STRLEN(crypt_magic_head); + if (len >= i && memcmp(ptr, crypt_magic_head, i) == 0) + emsg(_(e_file_is_encrypted_with_unknown_method)); + + return -1; +} + +#ifdef CRYPT_NOT_INPLACE +/* + * Return TRUE if the crypt method for "method_nr" can be done in-place. + */ + int +crypt_works_inplace(cryptstate_T *state) +{ + return cryptmethods[state->method_nr].works_inplace; +} +#endif + +/* + * Get the crypt method for buffer "buf" as a number. + */ + int +crypt_get_method_nr(buf_T *buf) +{ + return crypt_method_nr_from_name(*buf->b_p_cm == NUL ? p_cm : buf->b_p_cm); +} + +/* + * Returns True for Sodium Encryption. + */ + int +crypt_method_is_sodium(int method) +{ + return method == CRYPT_M_SOD || method == CRYPT_M_SOD2; +} + +/* + * Return TRUE when the buffer uses an encryption method that encrypts the + * whole undo file, not only the text. + */ + int +crypt_whole_undofile(int method_nr) +{ + return cryptmethods[method_nr].whole_undofile; +} + +/* + * Get crypt method specific length of the file header in bytes. + */ + int +crypt_get_header_len(int method_nr) +{ + return CRYPT_MAGIC_LEN + + cryptmethods[method_nr].salt_len + + cryptmethods[method_nr].seed_len + + cryptmethods[method_nr].add_len; +} + + +#if defined(FEAT_SODIUM) || defined(PROTO) +/* + * Get maximum crypt method specific length of the file header in bytes. + */ + int +crypt_get_max_header_len(void) +{ + int i; + int max = 0; + int temp = 0; + + for (i = 0; i < CRYPT_M_COUNT; ++i) + { + temp = crypt_get_header_len(i); + if (temp > max) + max = temp; + } + return max; +} +#endif + +/* + * Set the crypt method for buffer "buf" to "method_nr" using the int value as + * returned by crypt_method_nr_from_name(). + */ + void +crypt_set_cm_option(buf_T *buf, int method_nr) +{ + free_string_option(buf->b_p_cm); + buf->b_p_cm = vim_strsave((char_u *)cryptmethods[method_nr].name); +} + +/* + * If the crypt method for the current buffer has a self-test, run it and + * return OK/FAIL. + */ + int +crypt_self_test(void) +{ + int method_nr = crypt_get_method_nr(curbuf); + + if (cryptmethods[method_nr].self_test_fn == NULL) + return OK; + return cryptmethods[method_nr].self_test_fn(); +} + +/* + * Allocate a crypt state and initialize it. + * Return NULL for failure. + */ + cryptstate_T * +crypt_create( + int method_nr, + char_u *key, + crypt_arg_T *crypt_arg) +{ + cryptstate_T *state = ALLOC_ONE(cryptstate_T); + + if (state == NULL) + return state; + + state->method_nr = method_nr; + if (cryptmethods[method_nr].init_fn(state, key, crypt_arg) == FAIL) + { + vim_free(state); + return NULL; + } + return state; +} + +/* + * Allocate a crypt state from a file header and initialize it. + * Assumes that header contains at least the number of bytes that + * crypt_get_header_len() returns for "method_nr". + */ + cryptstate_T * +crypt_create_from_header( + int method_nr, + char_u *key, + char_u *header) +{ + crypt_arg_T arg; + + CLEAR_FIELD(arg); + arg.cat_init_from_file = TRUE; + + arg.cat_salt_len = cryptmethods[method_nr].salt_len; + arg.cat_seed_len = cryptmethods[method_nr].seed_len; + arg.cat_add_len = cryptmethods[method_nr].add_len; + if (arg.cat_salt_len > 0) + arg.cat_salt = header + CRYPT_MAGIC_LEN; + if (arg.cat_seed_len > 0) + arg.cat_seed = header + CRYPT_MAGIC_LEN + arg.cat_salt_len; + if (arg.cat_add_len > 0) + arg.cat_add = header + CRYPT_MAGIC_LEN + + arg.cat_salt_len + arg.cat_seed_len; + + return crypt_create(method_nr, key, &arg); +} + +/* + * Read the crypt method specific header data from "fp". + * Return an allocated cryptstate_T or NULL on error. + */ + cryptstate_T * +crypt_create_from_file(FILE *fp, char_u *key) +{ + int method_nr; + int header_len; + char magic_buffer[CRYPT_MAGIC_LEN]; + char_u *buffer; + cryptstate_T *state; + + if (fread(magic_buffer, CRYPT_MAGIC_LEN, 1, fp) != 1) + return NULL; + method_nr = crypt_method_nr_from_magic(magic_buffer, CRYPT_MAGIC_LEN); + if (method_nr < 0) + return NULL; + + header_len = crypt_get_header_len(method_nr); + if ((buffer = alloc(header_len)) == NULL) + return NULL; + mch_memmove(buffer, magic_buffer, CRYPT_MAGIC_LEN); + if (header_len > CRYPT_MAGIC_LEN + && fread(buffer + CRYPT_MAGIC_LEN, + header_len - CRYPT_MAGIC_LEN, 1, fp) != 1) + { + vim_free(buffer); + return NULL; + } + + state = crypt_create_from_header(method_nr, key, buffer); + vim_free(buffer); + return state; +} + +/* + * Allocate a cryptstate_T for writing and initialize it with "key". + * Allocates and fills in the header and stores it in "header", setting + * "header_len". The header may include salt and seed, depending on + * cryptmethod. Caller must free header. + * Returns the state or NULL on failure. + */ + cryptstate_T * +crypt_create_for_writing( + int method_nr, + char_u *key, + char_u **header, + int *header_len) +{ + int len = crypt_get_header_len(method_nr); + crypt_arg_T arg; + cryptstate_T *state; + + CLEAR_FIELD(arg); + arg.cat_salt_len = cryptmethods[method_nr].salt_len; + arg.cat_seed_len = cryptmethods[method_nr].seed_len; + arg.cat_add_len = cryptmethods[method_nr].add_len; + arg.cat_init_from_file = FALSE; + + *header_len = len; + *header = alloc(len); + if (*header == NULL) + return NULL; + + mch_memmove(*header, cryptmethods[method_nr].magic, CRYPT_MAGIC_LEN); + if (arg.cat_salt_len > 0 || arg.cat_seed_len > 0 || arg.cat_add_len > 0) + { + if (arg.cat_salt_len > 0) + arg.cat_salt = *header + CRYPT_MAGIC_LEN; + if (arg.cat_seed_len > 0) + arg.cat_seed = *header + CRYPT_MAGIC_LEN + arg.cat_salt_len; + if (arg.cat_add_len > 0) + arg.cat_add = *header + CRYPT_MAGIC_LEN + + arg.cat_salt_len + arg.cat_seed_len; + + // TODO: Should this be crypt method specific? (Probably not worth + // it). sha2_seed is pretty bad for large amounts of entropy, so make + // that into something which is suitable for anything. +#ifdef FEAT_SODIUM + if (sodium_init() >= 0) + { + if (arg.cat_salt_len > 0) + randombytes_buf(arg.cat_salt, arg.cat_salt_len); + if (arg.cat_seed_len > 0) + randombytes_buf(arg.cat_seed, arg.cat_seed_len); + } + else +#endif + sha2_seed(arg.cat_salt, arg.cat_salt_len, arg.cat_seed, arg.cat_seed_len); + } + state = crypt_create(method_nr, key, &arg); + if (state == NULL) + VIM_CLEAR(*header); + return state; +} + +/* + * Free the crypt state. + */ + void +crypt_free_state(cryptstate_T *state) +{ +#ifdef FEAT_SODIUM + if (crypt_method_is_sodium(state->method_nr)) + { + sodium_munlock(((sodium_state_T *)state->method_state)->key, + crypto_box_SEEDBYTES); + sodium_memzero(state->method_state, sizeof(sodium_state_T)); + sodium_free(state->method_state); + } + else +#endif + vim_free(state->method_state); + vim_free(state); +} + +#ifdef CRYPT_NOT_INPLACE +/* + * Encode "from[len]" and store the result in a newly allocated buffer, which + * is stored in "newptr". + * Return number of bytes in "newptr", 0 for need more or -1 on error. + */ + long +crypt_encode_alloc( + cryptstate_T *state, + char_u *from, + size_t len, + char_u **newptr, + int last) +{ + cryptmethod_T *method = &cryptmethods[state->method_nr]; + + if (method->encode_buffer_fn != NULL) + // Has buffer function, pass through. + return method->encode_buffer_fn(state, from, len, newptr, last); + if (len == 0) + // Not buffering, just return EOF. + return (long)len; + + *newptr = alloc(len + 50); + if (*newptr == NULL) + return -1; + method->encode_fn(state, from, len, *newptr, last); + return (long)len; +} + +/* + * Decrypt "ptr[len]" and store the result in a newly allocated buffer, which + * is stored in "newptr". + * Return number of bytes in "newptr", 0 for need more or -1 on error. + */ + long +crypt_decode_alloc( + cryptstate_T *state, + char_u *ptr, + long len, + char_u **newptr, + int last) +{ + cryptmethod_T *method = &cryptmethods[state->method_nr]; + + if (method->decode_buffer_fn != NULL) + // Has buffer function, pass through. + return method->decode_buffer_fn(state, ptr, len, newptr, last); + + if (len == 0) + // Not buffering, just return EOF. + return len; + + *newptr = alloc(len); + if (*newptr == NULL) + return -1; + method->decode_fn(state, ptr, len, *newptr, last); + return len; +} +#endif + +/* + * Encrypting "from[len]" into "to[len]". + */ + void +crypt_encode( + cryptstate_T *state, + char_u *from, + size_t len, + char_u *to, + int last) +{ + cryptmethods[state->method_nr].encode_fn(state, from, len, to, last); +} + +#if 0 // unused +/* + * decrypting "from[len]" into "to[len]". + */ + void +crypt_decode( + cryptstate_T *state, + char_u *from, + size_t len, + char_u *to, + int last) +{ + cryptmethods[state->method_nr].decode_fn(state, from, len, to, last); +} +#endif + +/* + * Simple inplace encryption, modifies "buf[len]" in place. + */ + void +crypt_encode_inplace( + cryptstate_T *state, + char_u *buf, + size_t len, + int last) +{ + cryptmethods[state->method_nr].encode_inplace_fn(state, buf, len, + buf, last); +} + +/* + * Simple inplace decryption, modifies "buf[len]" in place. + */ + void +crypt_decode_inplace( + cryptstate_T *state, + char_u *buf, + size_t len, + int last) +{ + cryptmethods[state->method_nr].decode_inplace_fn(state, buf, len, + buf, last); +} + +/* + * Free an allocated crypt key. Clear the text to make sure it doesn't stay + * in memory anywhere. + */ + void +crypt_free_key(char_u *key) +{ + char_u *p; + + if (key != NULL) + { + for (p = key; *p != NUL; ++p) + *p = 0; + vim_free(key); + } +} + +/* + * Check the crypt method and give a warning if it's outdated. + */ + void +crypt_check_method(int method) +{ + if (method < CRYPT_M_BF2 || method == CRYPT_M_SOD) + { + msg_scroll = TRUE; + msg(_("Warning: Using a weak encryption method; see :help 'cm'")); + } +} + +/* + * If the crypt method for "curbuf" does not support encrypting the swap file + * then disable the swap file. + */ + void +crypt_check_swapfile_curbuf(void) +{ +#ifdef FEAT_SODIUM + int method = crypt_get_method_nr(curbuf); + if (crypt_method_is_sodium(method)) + { + // encryption uses padding and MAC, that does not work very well with + // swap and undo files, so disable them + mf_close_file(curbuf, TRUE); // remove the swap file + set_option_value_give_err((char_u *)"swf", 0, NULL, OPT_LOCAL); + msg_scroll = TRUE; + msg(_("Note: Encryption of swapfile not supported, disabling swap file")); + } +#endif +} + + void +crypt_check_current_method(void) +{ + crypt_check_method(crypt_get_method_nr(curbuf)); +} + +/* + * Ask the user for a crypt key. + * When "store" is TRUE, the new key is stored in the 'key' option, and the + * 'key' option value is returned: Don't free it. + * When "store" is FALSE, the typed key is returned in allocated memory. + * Returns NULL on failure. + */ + char_u * +crypt_get_key( + int store, + int twice) // Ask for the key twice. +{ + char_u *p1, *p2 = NULL; + int round; + + for (round = 0; ; ++round) + { + cmdline_star = TRUE; + cmdline_row = msg_row; + p1 = getcmdline_prompt(NUL, round == 0 + ? (char_u *)_("Enter encryption key: ") + : (char_u *)_("Enter same key again: "), 0, EXPAND_NOTHING, + NULL); + cmdline_star = FALSE; + + if (p1 == NULL) + break; + + if (round == twice) + { + if (p2 != NULL && STRCMP(p1, p2) != 0) + { + msg(_("Keys don't match!")); + crypt_free_key(p1); + crypt_free_key(p2); + p2 = NULL; + round = -1; // do it again + continue; + } + + if (store) + { + set_option_value_give_err((char_u *)"key", 0L, p1, OPT_LOCAL); + crypt_free_key(p1); + p1 = curbuf->b_p_key; + crypt_check_swapfile_curbuf(); + } + break; + } + p2 = p1; + } + + // since the user typed this, no need to wait for return + if (!crypt_method_is_sodium(crypt_get_method_nr(curbuf))) + { + if (msg_didout) + msg_putchar('\n'); + need_wait_return = FALSE; + msg_didout = FALSE; + } + + crypt_free_key(p2); + return p1; +} + + +/* + * Append a message to IObuff for the encryption/decryption method being used. + */ + void +crypt_append_msg( + buf_T *buf) +{ + if (crypt_get_method_nr(buf) == 0) + STRCAT(IObuff, _("[crypted]")); + else + { + STRCAT(IObuff, "["); + STRCAT(IObuff, *buf->b_p_cm == NUL ? p_cm : buf->b_p_cm); + STRCAT(IObuff, "]"); + } +} + + static int +crypt_sodium_init_( + cryptstate_T *state UNUSED, + char_u *key UNUSED, + crypt_arg_T *arg UNUSED) +{ +# ifdef FEAT_SODIUM + // crypto_box_SEEDBYTES == crypto_secretstream_xchacha20poly1305_KEYBYTES + unsigned char dkey[crypto_box_SEEDBYTES]; // 32 + sodium_state_T *sd_state; + int retval = 0; + unsigned long long opslimit; + unsigned long long memlimit; + int alg; + + if (sodium_init() < 0) + return FAIL; + + sd_state = (sodium_state_T *)sodium_malloc(sizeof(sodium_state_T)); + sodium_memzero(sd_state, sizeof(sodium_state_T)); + + if ((state->method_nr == CRYPT_M_SOD2 && !arg->cat_init_from_file) + || state->method_nr == CRYPT_M_SOD) + { + opslimit = crypto_pwhash_OPSLIMIT_INTERACTIVE; + memlimit = crypto_pwhash_MEMLIMIT_INTERACTIVE; + alg = crypto_pwhash_ALG_DEFAULT; + +#if 0 + // For testing + if (state->method_nr == CRYPT_M_SOD2) + { + opslimit = crypto_pwhash_OPSLIMIT_MODERATE; + memlimit = crypto_pwhash_MEMLIMIT_MODERATE; + } +#endif + + // derive a key from the password + if (crypto_pwhash(dkey, sizeof(dkey), (const char *)key, STRLEN(key), + arg->cat_salt, opslimit, (size_t)memlimit, alg) != 0) + { + // out of memory + sodium_free(sd_state); + return FAIL; + } + memcpy(sd_state->key, dkey, crypto_box_SEEDBYTES); + + retval += sodium_mlock(sd_state->key, crypto_box_SEEDBYTES); + retval += sodium_mlock(key, STRLEN(key)); + + if (retval < 0) + { + emsg(_(e_encryption_sodium_mlock_failed)); + sodium_free(sd_state); + return FAIL; + } + // "cat_add" should not be NULL, check anyway for safety + if (state->method_nr == CRYPT_M_SOD2 && arg->cat_add != NULL) + { + char_u buffer[20]; + char_u *p = buffer; + vim_memset(buffer, 0, 20); + + crypt_long_long_to_char(opslimit, p); + p += sizeof(opslimit); + + crypt_long_long_to_char(memlimit, p); + p += sizeof(memlimit); + + crypt_int_to_char(alg, p); + memcpy(arg->cat_add, buffer, sizeof(opslimit) + sizeof(memlimit) + sizeof(alg)); + } + } + else + { + char_u buffer[20]; + char_u *p = buffer; + vim_memset(buffer, 0, 20); + int size = sizeof(opslimit) + + sizeof(memlimit) + sizeof(alg); + + // Reading parameters from file + if (arg->cat_add_len < size) + { + sodium_free(sd_state); + return FAIL; + } + + // derive the key from the file header + memcpy(p, arg->cat_add, size); + arg->cat_add += size; + + opslimit = crypt_char_to_long_long(p); + p += sizeof(opslimit); + memlimit = crypt_char_to_long_long(p); + p += sizeof(memlimit); + alg = crypt_char_to_int(p); + p += sizeof(alg); + +#ifdef FEAT_EVAL + crypt_sodium_report_hash_params(opslimit, + crypto_pwhash_OPSLIMIT_INTERACTIVE, + (size_t)memlimit, crypto_pwhash_MEMLIMIT_INTERACTIVE, + alg, crypto_pwhash_ALG_DEFAULT); +#endif + + if (crypto_pwhash(dkey, sizeof(dkey), (const char *)key, STRLEN(key), + arg->cat_salt, opslimit, (size_t)memlimit, alg) != 0) + { + // out of memory + sodium_free(sd_state); + return FAIL; + } + memcpy(sd_state->key, dkey, crypto_box_SEEDBYTES); + + retval += sodium_mlock(sd_state->key, crypto_box_SEEDBYTES); + retval += sodium_mlock(key, STRLEN(key)); + + if (retval < 0) + { + emsg(_(e_encryption_sodium_mlock_failed)); + sodium_free(sd_state); + return FAIL; + } + } + sd_state->count = 0; + state->method_state = sd_state; + + return OK; +# else + emsg(_(e_libsodium_not_built_in)); + return FAIL; +# endif +} + +/* + * Encrypt "from[len]" into "to[len]". + * "from" and "to" can be equal to encrypt in place. + * Call needs to ensure that there is enough space in to (for the header) + */ +#if 0 // Currently unused + void +crypt_sodium_encode( + cryptstate_T *state UNUSED, + char_u *from UNUSED, + size_t len UNUSED, + char_u *to UNUSED, + int last UNUSED) +{ +# ifdef FEAT_SODIUM + // crypto_box_SEEDBYTES == crypto_secretstream_xchacha20poly1305_KEYBYTES + sodium_state_T *sod_st = state->method_state; + unsigned char tag = last + ? crypto_secretstream_xchacha20poly1305_TAG_FINAL : 0; + + if (sod_st->count == 0) + { + if (len <= crypto_secretstream_xchacha20poly1305_HEADERBYTES) + { + emsg(_(e_libsodium_cannot_encrypt_header)); + return; + } + crypto_secretstream_xchacha20poly1305_init_push(&sod_st->state, + to, sod_st->key); + to += crypto_secretstream_xchacha20poly1305_HEADERBYTES; + } + + if (sod_st->count && len <= crypto_secretstream_xchacha20poly1305_ABYTES) + { + emsg(_(e_libsodium_cannot_encrypt_buffer)); + return; + } + + crypto_secretstream_xchacha20poly1305_push(&sod_st->state, to, NULL, + from, len, NULL, 0, tag); + + sod_st->count++; +# endif +} +#endif + +/* + * Decrypt "from[len]" into "to[len]". + * "from" and "to" can be equal to encrypt in place. + */ +#if 0 // Currently unused + void +crypt_sodium_decode( + cryptstate_T *state UNUSED, + char_u *from UNUSED, + size_t len UNUSED, + char_u *to UNUSED, + int last UNUSED) +{ +# ifdef FEAT_SODIUM + // crypto_box_SEEDBYTES == crypto_secretstream_xchacha20poly1305_KEYBYTES + sodium_state_T *sod_st = state->method_state; + unsigned char tag; + unsigned long long buf_len; + char_u *p1 = from; + char_u *p2 = to; + char_u *buf_out; + + if (sod_st->count == 0 + && len <= crypto_secretstream_xchacha20poly1305_HEADERBYTES) + { + emsg(_(e_libsodium_cannot_decrypt_header)); + return; + } + + buf_out = (char_u *)alloc(len); + + if (buf_out == NULL) + { + emsg(_(e_libsodium_cannot_allocate_buffer)); + return; + } + if (sod_st->count == 0) + { + if (crypto_secretstream_xchacha20poly1305_init_pull( + &sod_st->state, from, sod_st->key) != 0) + { + emsg(_(e_libsodium_decryption_failed_header_incomplete)); + goto fail; + } + + from += crypto_secretstream_xchacha20poly1305_HEADERBYTES; + len -= crypto_secretstream_xchacha20poly1305_HEADERBYTES; + + if (p1 == p2) + to += crypto_secretstream_xchacha20poly1305_HEADERBYTES; + } + + if (sod_st->count && len <= crypto_secretstream_xchacha20poly1305_ABYTES) + { + emsg(_(e_libsodium_cannot_decrypt_buffer)); + goto fail; + } + if (crypto_secretstream_xchacha20poly1305_pull(&sod_st->state, + buf_out, &buf_len, &tag, from, len, NULL, 0) != 0) + { + emsg(_(e_libsodium_decryption_failed)); + goto fail; + } + sod_st->count++; + + if (tag == crypto_secretstream_xchacha20poly1305_TAG_FINAL && !last) + { + emsg(_(e_libsodium_decryption_failed_premature)); + goto fail; + } + if (p1 == p2) + mch_memmove(p2, buf_out, buf_len); + +fail: + vim_free(buf_out); +# endif +} +#endif + +/* + * Encrypt "from[len]" into "to[len]". + * "from" and "to" can be equal to encrypt in place. + */ + static long +crypt_sodium_buffer_encode( + cryptstate_T *state UNUSED, + char_u *from UNUSED, + size_t len UNUSED, + char_u **buf_out UNUSED, + int last UNUSED) +{ +# ifdef FEAT_SODIUM + // crypto_box_SEEDBYTES == crypto_secretstream_xchacha20poly1305_KEYBYTES + unsigned long long out_len; + char_u *ptr; + unsigned char tag = last + ? crypto_secretstream_xchacha20poly1305_TAG_FINAL : 0; + int length; + sodium_state_T *sod_st = state->method_state; + int first = (sod_st->count == 0); + + length = (int)len + crypto_secretstream_xchacha20poly1305_ABYTES + + (first ? crypto_secretstream_xchacha20poly1305_HEADERBYTES : 0); + *buf_out = alloc_clear(length); + if (*buf_out == NULL) + { + emsg(_(e_libsodium_cannot_allocate_buffer)); + return -1; + } + ptr = *buf_out; + + if (first) + { + crypto_secretstream_xchacha20poly1305_init_push(&sod_st->state, + ptr, sod_st->key); + ptr += crypto_secretstream_xchacha20poly1305_HEADERBYTES; + } + + crypto_secretstream_xchacha20poly1305_push(&sod_st->state, ptr, + &out_len, from, len, NULL, 0, tag); + + sod_st->count++; + return out_len + (first + ? crypto_secretstream_xchacha20poly1305_HEADERBYTES : 0); +# else + return -1; +# endif +} + +/* + * Decrypt "from[len]" into "to[len]". + * "from" and "to" can be equal to encrypt in place. + */ + static long +crypt_sodium_buffer_decode( + cryptstate_T *state UNUSED, + char_u *from UNUSED, + size_t len UNUSED, + char_u **buf_out UNUSED, + int last UNUSED) +{ +# ifdef FEAT_SODIUM + // crypto_box_SEEDBYTES == crypto_secretstream_xchacha20poly1305_KEYBYTES + sodium_state_T *sod_st = state->method_state; + unsigned char tag; + unsigned long long out_len; + + if (sod_st->count == 0 + && state->method_nr == CRYPT_M_SOD + && len > WRITEBUFSIZE + + crypto_secretstream_xchacha20poly1305_HEADERBYTES + + crypto_secretstream_xchacha20poly1305_ABYTES) + len -= cryptmethods[CRYPT_M_SOD2].add_len; + + *buf_out = alloc_clear(len); + if (*buf_out == NULL) + { + emsg(_(e_libsodium_cannot_allocate_buffer)); + return -1; + } + + if (sod_st->count == 0) + { + if (crypto_secretstream_xchacha20poly1305_init_pull(&sod_st->state, + from, sod_st->key) != 0) + { + emsg(_(e_libsodium_decryption_failed_header_incomplete)); + return -1; + } + from += crypto_secretstream_xchacha20poly1305_HEADERBYTES; + len -= crypto_secretstream_xchacha20poly1305_HEADERBYTES; + sod_st->count++; + } + if (crypto_secretstream_xchacha20poly1305_pull(&sod_st->state, + *buf_out, &out_len, &tag, from, len, NULL, 0) != 0) + { + emsg(_(e_libsodium_decryption_failed)); + return -1; + } + + if (tag == crypto_secretstream_xchacha20poly1305_TAG_FINAL && !last) + emsg(_(e_libsodium_decryption_failed_premature)); + return (long) out_len; +# else + return -1; +# endif +} + +# if defined(FEAT_SODIUM) || defined(PROTO) + void +crypt_sodium_lock_key(char_u *key) +{ + if (sodium_init() >= 0) + sodium_mlock(key, STRLEN(key)); +} + + int +crypt_sodium_munlock(void *const addr, const size_t len) +{ + return sodium_munlock(addr, len); +} + + void +crypt_sodium_randombytes_buf(void *const buf, const size_t size) +{ + randombytes_buf(buf, size); +} + + int +crypt_sodium_init(void) +{ + return sodium_init(); +} + + UINT32_T +crypt_sodium_randombytes_random(void) +{ + return randombytes_random(); +} + +#if defined(FEAT_EVAL) || defined(PROTO) + static void +crypt_sodium_report_hash_params( + unsigned long long opslimit, + unsigned long long ops_def, + size_t memlimit, + size_t mem_def, + int alg, + int alg_def) +{ + if (p_verbose > 0) + { + verbose_enter(); + if (opslimit != ops_def) + smsg(_("xchacha20v2: using custom opslimit \"%llu\" for Key derivation."), opslimit); + else + smsg(_("xchacha20v2: using default opslimit \"%llu\" for Key derivation."), opslimit); + if (memlimit != mem_def) + smsg(_("xchacha20v2: using custom memlimit \"%lu\" for Key derivation."), (unsigned long)memlimit); + else + smsg(_("xchacha20v2: using default memlimit \"%lu\" for Key derivation."), (unsigned long)memlimit); + if (alg != alg_def) + smsg(_("xchacha20v2: using custom algorithm \"%d\" for Key derivation."), alg); + else + smsg(_("xchacha20v2: using default algorithm \"%d\" for Key derivation."), alg); + verbose_leave(); + } +} +#endif + + static void +crypt_long_long_to_char(long long n, char_u *s) +{ + int i; + for (i = 0; i < 8; i++) + { + s[i] = (char_u)(n & 0xff); + n = (unsigned)n >> 8; + } +} + + static void +crypt_int_to_char(int n, char_u *s) +{ + int i; + for (i = 0; i < 4; i++) + { + s[i] = (char_u)(n & 0xff); + n = (unsigned)n >> 8; + } +} + + static long long +crypt_char_to_long_long(char_u *s) +{ + unsigned long long retval = 0; + int i; + for (i = 7; i >= 0; i--) + { + if (i == 7) + retval = s[i]; + else + retval |= s[i]; + if (i > 0) + retval <<= 8; + } + return retval; +} + + static int +crypt_char_to_int(char_u *s) +{ + int retval = 0; + int i; + + for (i = 3; i >= 0; i--) + { + if (i == 3) + retval = s[i]; + else + retval |= s[i]; + if (i > 0) + retval <<= 8; + } + return retval; +} +# endif + +#endif // FEAT_CRYPT diff --git a/userland/apps/vim/src/crypt_zip.c b/userland/apps/vim/src/crypt_zip.c new file mode 100644 index 00000000..89e45951 --- /dev/null +++ b/userland/apps/vim/src/crypt_zip.c @@ -0,0 +1,154 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * crypt_zip.c: Zip encryption support. + */ +#include "vim.h" + +#if defined(FEAT_CRYPT) || defined(PROTO) +/* + * Optional encryption support. + * Mohsin Ahmed, mosh@sasi.com, 98-09-24 + * Based on zip/crypt sources. + * + * NOTE FOR USA: Since 2000 exporting this code from the USA is allowed to + * most countries. There are a few exceptions, but that still should not be a + * problem since this code was originally created in Europe and India. + */ + +// Need a type that should be 32 bits. 64 also works but wastes space. +typedef unsigned int u32_T; // int is at least 32 bits + +// The state of encryption, referenced by cryptstate_T. +typedef struct { + u32_T keys[3]; +} zip_state_T; + + +static u32_T crc_32_table[256]; + +/* + * Fill the CRC table, if not done already. + */ + static void +make_crc_tab(void) +{ + u32_T s, t, v; + static int done = FALSE; + + if (done) + return; + for (t = 0; t < 256; t++) + { + v = t; + for (s = 0; s < 8; s++) + v = (v >> 1) ^ ((v & 1) * (u32_T)0xedb88320L); + crc_32_table[t] = v; + } + done = TRUE; +} + +#define CRC32(c, b) (crc_32_table[((int)(c) ^ (b)) & 0xff] ^ ((c) >> 8)) + +/* + * Return the next byte in the pseudo-random sequence. + */ +#define DECRYPT_BYTE_ZIP(keys, t) \ +{ \ + short_u temp = (short_u)keys[2] | 2; \ + t = (int)(((unsigned)(temp * (temp ^ 1U)) >> 8) & 0xff); \ +} + +/* + * Update the encryption keys with the next byte of plain text. + */ +#define UPDATE_KEYS_ZIP(keys, c) do { \ + keys[0] = CRC32(keys[0], (c)); \ + keys[1] += keys[0] & 0xff; \ + keys[1] = keys[1] * 134775813L + 1; \ + keys[2] = CRC32(keys[2], (int)(keys[1] >> 24)); \ +} while (0) + +/* + * Initialize for encryption/decryption. + */ + int +crypt_zip_init( + cryptstate_T *state, + char_u *key, + crypt_arg_T *arg UNUSED) +{ + char_u *p; + zip_state_T *zs; + + zs = ALLOC_ONE(zip_state_T); + if (zs == NULL) + return FAIL; + state->method_state = zs; + + make_crc_tab(); + zs->keys[0] = 305419896L; + zs->keys[1] = 591751049L; + zs->keys[2] = 878082192L; + for (p = key; *p != NUL; ++p) + UPDATE_KEYS_ZIP(zs->keys, (int)*p); + + return OK; +} + +/* + * Encrypt "from[len]" into "to[len]". + * "from" and "to" can be equal to encrypt in place. + */ + void +crypt_zip_encode( + cryptstate_T *state, + char_u *from, + size_t len, + char_u *to, + int last UNUSED) +{ + zip_state_T *zs = state->method_state; + size_t i; + int ztemp, t; + + for (i = 0; i < len; ++i) + { + ztemp = from[i]; + DECRYPT_BYTE_ZIP(zs->keys, t); + UPDATE_KEYS_ZIP(zs->keys, ztemp); + to[i] = t ^ ztemp; + } +} + +/* + * Decrypt "from[len]" into "to[len]". + */ + void +crypt_zip_decode( + cryptstate_T *state, + char_u *from, + size_t len, + char_u *to, + int last UNUSED) +{ + zip_state_T *zs = state->method_state; + size_t i; + short_u temp; + + for (i = 0; i < len; ++i) + { + temp = (short_u)zs->keys[2] | 2; + temp = (int)(((unsigned)(temp * (temp ^ 1U)) >> 8) & 0xff); + UPDATE_KEYS_ZIP(zs->keys, to[i] = from[i] ^ temp); + } +} + +#endif // FEAT_CRYPT diff --git a/userland/apps/vim/src/debugger.c b/userland/apps/vim/src/debugger.c new file mode 100644 index 00000000..5508ea33 --- /dev/null +++ b/userland/apps/vim/src/debugger.c @@ -0,0 +1,1176 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * debugger.c: Vim script debugger functions + */ + +#include "vim.h" + +#if defined(FEAT_EVAL) || defined(PROTO) +static int debug_greedy = FALSE; // batch mode debugging: don't save + // and restore typeahead. +static void do_setdebugtracelevel(char_u *arg); +static void do_checkbacktracelevel(void); +static void do_showbacktrace(char_u *cmd); + +static char_u *debug_oldval = NULL; // old and newval for debug expressions +static char_u *debug_newval = NULL; +static int debug_expr = 0; // use debug_expr + + int +has_watchexpr(void) +{ + return debug_expr; +} + +/* + * do_debug(): Debug mode. + * Repeatedly get Ex commands, until told to continue normal execution. + */ + void +do_debug(char_u *cmd) +{ + int save_msg_scroll = msg_scroll; + int save_State = State; + int save_did_emsg = did_emsg; + int save_cmd_silent = cmd_silent; + int save_msg_silent = msg_silent; + int save_emsg_silent = emsg_silent; + int save_redir_off = redir_off; + tasave_T typeaheadbuf; + int typeahead_saved = FALSE; + int save_ignore_script = 0; + int save_ex_normal_busy; + int n; + char_u *cmdline = NULL; + char_u *p; + char_u *sname; + char *tail = NULL; + static int last_cmd = 0; +#define CMD_CONT 1 +#define CMD_NEXT 2 +#define CMD_STEP 3 +#define CMD_FINISH 4 +#define CMD_QUIT 5 +#define CMD_INTERRUPT 6 +#define CMD_BACKTRACE 7 +#define CMD_FRAME 8 +#define CMD_UP 9 +#define CMD_DOWN 10 + +#ifdef ALWAYS_USE_GUI + // Can't do this when there is no terminal for input/output. + if (!gui.in_use) + { + // Break as soon as possible. + debug_break_level = 9999; + return; + } +#endif + + // Make sure we are in raw mode and start termcap mode. Might have side + // effects... + settmode(TMODE_RAW); + starttermcap(); + + ++RedrawingDisabled; // don't redisplay the window + ++no_wait_return; // don't wait for return + did_emsg = FALSE; // don't use error from debugged stuff + cmd_silent = FALSE; // display commands + msg_silent = FALSE; // display messages + emsg_silent = FALSE; // display error messages + redir_off = TRUE; // don't redirect debug commands + save_timeout_for_debugging(); // disable regexp timeout flag + + State = MODE_NORMAL; + debug_mode = TRUE; + + if (!debug_did_msg) + msg(_("Entering Debug mode. Type \"cont\" to continue.")); + if (debug_oldval != NULL) + { + smsg(_("Oldval = \"%s\""), debug_oldval); + VIM_CLEAR(debug_oldval); + } + if (debug_newval != NULL) + { + smsg(_("Newval = \"%s\""), debug_newval); + VIM_CLEAR(debug_newval); + } + sname = estack_sfile(ESTACK_NONE); + if (sname != NULL) + msg((char *)sname); + vim_free(sname); + if (SOURCING_LNUM != 0) + smsg(_("line %ld: %s"), SOURCING_LNUM, cmd); + else + smsg(_("cmd: %s"), cmd); + + // Repeat getting a command and executing it. + for (;;) + { + msg_scroll = TRUE; + need_wait_return = FALSE; + + // Save the current typeahead buffer and replace it with an empty one. + // This makes sure we get input from the user here and don't interfere + // with the commands being executed. Reset "ex_normal_busy" to avoid + // the side effects of using ":normal". Save the stuff buffer and make + // it empty. Set ignore_script to avoid reading from script input. + save_ex_normal_busy = ex_normal_busy; + ex_normal_busy = 0; + if (!debug_greedy) + { + save_typeahead(&typeaheadbuf); + typeahead_saved = TRUE; + save_ignore_script = ignore_script; + ignore_script = TRUE; + } + + // don't debug any function call, e.g. from an expression mapping + n = debug_break_level; + debug_break_level = -1; + + vim_free(cmdline); + cmdline = getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL); + + debug_break_level = n; + if (typeahead_saved) + { + restore_typeahead(&typeaheadbuf, TRUE); + ignore_script = save_ignore_script; + } + ex_normal_busy = save_ex_normal_busy; + + cmdline_row = msg_row; + msg_starthere(); + if (cmdline != NULL) + { + // If this is a debug command, set "last_cmd". + // If not, reset "last_cmd". + // For a blank line use previous command. + p = skipwhite(cmdline); + if (*p != NUL) + { + switch (*p) + { + case 'c': last_cmd = CMD_CONT; + tail = "ont"; + break; + case 'n': last_cmd = CMD_NEXT; + tail = "ext"; + break; + case 's': last_cmd = CMD_STEP; + tail = "tep"; + break; + case 'f': + last_cmd = 0; + if (p[1] == 'r') + { + last_cmd = CMD_FRAME; + tail = "rame"; + } + else + { + last_cmd = CMD_FINISH; + tail = "inish"; + } + break; + case 'q': last_cmd = CMD_QUIT; + tail = "uit"; + break; + case 'i': last_cmd = CMD_INTERRUPT; + tail = "nterrupt"; + break; + case 'b': last_cmd = CMD_BACKTRACE; + if (p[1] == 't') + tail = "t"; + else + tail = "acktrace"; + break; + case 'w': last_cmd = CMD_BACKTRACE; + tail = "here"; + break; + case 'u': last_cmd = CMD_UP; + tail = "p"; + break; + case 'd': last_cmd = CMD_DOWN; + tail = "own"; + break; + default: last_cmd = 0; + } + if (last_cmd != 0) + { + // Check that the tail matches. + ++p; + while (*p != NUL && *p == *tail) + { + ++p; + ++tail; + } + if (ASCII_ISALPHA(*p) && last_cmd != CMD_FRAME) + last_cmd = 0; + } + } + + if (last_cmd != 0) + { + // Execute debug command: decide where to break next and + // return. + switch (last_cmd) + { + case CMD_CONT: + debug_break_level = -1; + break; + case CMD_NEXT: + debug_break_level = ex_nesting_level; + break; + case CMD_STEP: + debug_break_level = 9999; + break; + case CMD_FINISH: + debug_break_level = ex_nesting_level - 1; + break; + case CMD_QUIT: + got_int = TRUE; + debug_break_level = -1; + break; + case CMD_INTERRUPT: + got_int = TRUE; + debug_break_level = 9999; + // Do not repeat ">interrupt" cmd, continue stepping. + last_cmd = CMD_STEP; + break; + case CMD_BACKTRACE: + do_showbacktrace(cmd); + continue; + case CMD_FRAME: + if (*p == NUL) + { + do_showbacktrace(cmd); + } + else + { + p = skipwhite(p); + do_setdebugtracelevel(p); + } + continue; + case CMD_UP: + debug_backtrace_level++; + do_checkbacktracelevel(); + continue; + case CMD_DOWN: + debug_backtrace_level--; + do_checkbacktracelevel(); + continue; + } + // Going out reset backtrace_level + debug_backtrace_level = 0; + break; + } + + // don't debug this command + n = debug_break_level; + debug_break_level = -1; + (void)do_cmdline(cmdline, getexline, NULL, + DOCMD_VERBOSE|DOCMD_EXCRESET); + debug_break_level = n; + } + lines_left = Rows - 1; + } + vim_free(cmdline); + + if (RedrawingDisabled > 0) + --RedrawingDisabled; + --no_wait_return; + redraw_all_later(UPD_NOT_VALID); + need_wait_return = FALSE; + msg_scroll = save_msg_scroll; + restore_timeout_for_debugging(); + lines_left = Rows - 1; + State = save_State; + debug_mode = FALSE; + did_emsg = save_did_emsg; + cmd_silent = save_cmd_silent; + msg_silent = save_msg_silent; + emsg_silent = save_emsg_silent; + redir_off = save_redir_off; + + // Only print the message again when typing a command before coming back + // here. + debug_did_msg = TRUE; +} + + static int +get_maxbacktrace_level(char_u *sname) +{ + char *p, *q; + int maxbacktrace = 0; + + if (sname == NULL) + return 0; + + p = (char *)sname; + while ((q = strstr(p, "..")) != NULL) + { + p = q + 2; + maxbacktrace++; + } + return maxbacktrace; +} + + static void +do_setdebugtracelevel(char_u *arg) +{ + int level; + + level = atoi((char *)arg); + if (*arg == '+' || level < 0) + debug_backtrace_level += level; + else + debug_backtrace_level = level; + + do_checkbacktracelevel(); +} + + static void +do_checkbacktracelevel(void) +{ + if (debug_backtrace_level < 0) + { + debug_backtrace_level = 0; + msg(_("frame is zero")); + } + else + { + char_u *sname = estack_sfile(ESTACK_NONE); + int max = get_maxbacktrace_level(sname); + + if (debug_backtrace_level > max) + { + debug_backtrace_level = max; + smsg(_("frame at highest level: %d"), max); + } + vim_free(sname); + } +} + + static void +do_showbacktrace(char_u *cmd) +{ + char_u *sname; + char *cur; + char *next; + int i = 0; + int max; + + sname = estack_sfile(ESTACK_NONE); + max = get_maxbacktrace_level(sname); + if (sname != NULL) + { + cur = (char *)sname; + while (!got_int) + { + next = strstr(cur, ".."); + if (next != NULL) + *next = NUL; + if (i == max - debug_backtrace_level) + smsg("->%d %s", max - i, cur); + else + smsg(" %d %s", max - i, cur); + ++i; + if (next == NULL) + break; + *next = '.'; + cur = next + 2; + } + vim_free(sname); + } + + if (SOURCING_LNUM != 0) + smsg(_("line %ld: %s"), (long)SOURCING_LNUM, cmd); + else + smsg(_("cmd: %s"), cmd); +} + +/* + * ":debug". + */ + void +ex_debug(exarg_T *eap) +{ + int debug_break_level_save = debug_break_level; + + debug_break_level = 9999; + do_cmdline_cmd(eap->arg); + debug_break_level = debug_break_level_save; +} + +static char_u *debug_breakpoint_name = NULL; +static linenr_T debug_breakpoint_lnum; + +/* + * When debugging or a breakpoint is set on a skipped command, no debug prompt + * is shown by do_one_cmd(). This situation is indicated by debug_skipped, and + * debug_skipped_name is then set to the source name in the breakpoint case. If + * a skipped command decides itself that a debug prompt should be displayed, it + * can do so by calling dbg_check_skipped(). + */ +static int debug_skipped; +static char_u *debug_skipped_name; + +/* + * Go to debug mode when a breakpoint was encountered or "ex_nesting_level" is + * at or below the break level. But only when the line is actually + * executed. Return TRUE and set breakpoint_name for skipped commands that + * decide to execute something themselves. + * Called from do_one_cmd() before executing a command. + */ + void +dbg_check_breakpoint(exarg_T *eap) +{ + char_u *p; + + debug_skipped = FALSE; + if (debug_breakpoint_name != NULL) + { + if (!eap->skip) + { + // replace K_SNR with "" + if (debug_breakpoint_name[0] == K_SPECIAL + && debug_breakpoint_name[1] == KS_EXTRA + && debug_breakpoint_name[2] == KE_SNR) + p = (char_u *)""; + else + p = (char_u *)""; + smsg(_("Breakpoint in \"%s%s\" line %ld"), + p, + debug_breakpoint_name + (*p == NUL ? 0 : 3), + (long)debug_breakpoint_lnum); + debug_breakpoint_name = NULL; + do_debug(eap->cmd); + } + else + { + debug_skipped = TRUE; + debug_skipped_name = debug_breakpoint_name; + debug_breakpoint_name = NULL; + } + } + else if (ex_nesting_level <= debug_break_level) + { + if (!eap->skip) + do_debug(eap->cmd); + else + { + debug_skipped = TRUE; + debug_skipped_name = NULL; + } + } +} + +/* + * Go to debug mode if skipped by dbg_check_breakpoint() because eap->skip was + * set. Return TRUE when the debug mode is entered this time. + */ + int +dbg_check_skipped(exarg_T *eap) +{ + int prev_got_int; + + if (!debug_skipped) + return FALSE; + + // Save the value of got_int and reset it. We don't want a previous + // interruption cause flushing the input buffer. + prev_got_int = got_int; + got_int = FALSE; + debug_breakpoint_name = debug_skipped_name; + // eap->skip is TRUE + eap->skip = FALSE; + (void)dbg_check_breakpoint(eap); + eap->skip = TRUE; + got_int |= prev_got_int; + return TRUE; +} + +/* + * The list of breakpoints: dbg_breakp. + * This is a grow-array of structs. + */ +struct debuggy +{ + int dbg_nr; // breakpoint number + int dbg_type; // DBG_FUNC, DBG_FILE or DBG_EXPR + char_u *dbg_name; // function, expression or file name + regprog_T *dbg_prog; // regexp program + linenr_T dbg_lnum; // line number in function or file + int dbg_forceit; // ! used +#ifdef FEAT_EVAL + typval_T *dbg_val; // last result of watchexpression +#endif + int dbg_level; // stored nested level for expr +}; + +static garray_T dbg_breakp = {0, 0, sizeof(struct debuggy), 4, NULL}; +#define BREAKP(idx) (((struct debuggy *)dbg_breakp.ga_data)[idx]) +#define DEBUGGY(gap, idx) (((struct debuggy *)gap->ga_data)[idx]) +static int last_breakp = 0; // nr of last defined breakpoint +static int has_expr_breakpoint = FALSE; + +#ifdef FEAT_PROFILE +// Profiling uses file and func names similar to breakpoints. +static garray_T prof_ga = {0, 0, sizeof(struct debuggy), 4, NULL}; + +// Profiling caches results of regexp lookups for function/script name. +#define N_PROF_HTAB 2 +static hashtab_T prof_cache[N_PROF_HTAB]; +#define PROF_HTAB_FUNCS 0 +#define PROF_HTAB_FILES 1 +static int prof_cache_initialized; +typedef struct profentry_S +{ + char pen_flags; // cache data booleans: profiling, forceit + char_u pen_name[1]; // actually longer +} profentry_T; +#define PEN_FLAG_PROFILING 1 +#define PEN_FLAG_FORCEIT 2 +#define PEN_SET_PROFILING(pe) ((pe)->pen_flags |= PEN_FLAG_PROFILING) +#define PEN_SET_FORCEIT(pe) ((pe)->pen_flags |= PEN_FLAG_FORCEIT) +#define PEN_IS_PROFILING(pe) (((pe)->pen_flags & PEN_FLAG_PROFILING) != 0) +#define PEN_IS_FORCEIT(pe) (((pe)->pen_flags & PEN_FLAG_FORCEIT) != 0) + +#define PE2HIKEY(pe) ((pe)->pen_name) +#define HIKEY2PE(p) ((profentry_T *)((p) - (offsetof(profentry_T, pen_name)))) +#define HI2PE(hi) HIKEY2PE((hi)->hi_key) + +static void prof_clear_cache(void); +#define PROF_CLEAR_CACHE(gap) do {if ((gap) == &prof_ga) prof_clear_cache();} while (0) +// Can enable to get some info about profile caching +// #define PROF_CACHE_LOG +#else +#define PROF_CLEAR_CACHE(gap) do {} while (0) +#endif +#define DBG_FUNC 1 +#define DBG_FILE 2 +#define DBG_EXPR 3 + +static linenr_T debuggy_find(int file,char_u *fname, linenr_T after, garray_T *gap, int *fp); + +/* + * Evaluate the "bp->dbg_name" expression and return the result. + * Disables error messages. + */ + static typval_T * +eval_expr_no_emsg(struct debuggy *bp) +{ + typval_T *tv; + + // Disable error messages, a bad expression would make Vim unusable. + ++emsg_off; + tv = eval_expr(bp->dbg_name, NULL); + --emsg_off; + + return tv; +} + +/* + * Parse the arguments of ":profile", ":breakadd" or ":breakdel" and put them + * in the entry just after the last one in dbg_breakp. Note that "dbg_name" + * is allocated. + * Returns FAIL for failure. + */ + static int +dbg_parsearg( + char_u *arg, + garray_T *gap) // either &dbg_breakp or &prof_ga +{ + char_u *p = arg; + char_u *q; + struct debuggy *bp; + int here = FALSE; + + if (ga_grow(gap, 1) == FAIL) + return FAIL; + bp = &DEBUGGY(gap, gap->ga_len); + + // Find "func" or "file". + if (STRNCMP(p, "func", 4) == 0) + bp->dbg_type = DBG_FUNC; + else if (STRNCMP(p, "file", 4) == 0) + bp->dbg_type = DBG_FILE; + else if ( +#ifdef FEAT_PROFILE + gap != &prof_ga && +#endif + STRNCMP(p, "here", 4) == 0) + { + if (curbuf->b_ffname == NULL) + { + emsg(_(e_no_file_name)); + return FAIL; + } + bp->dbg_type = DBG_FILE; + here = TRUE; + } + else if ( +#ifdef FEAT_PROFILE + gap != &prof_ga && +#endif + STRNCMP(p, "expr", 4) == 0) + bp->dbg_type = DBG_EXPR; + else + { + semsg(_(e_invalid_argument_str), p); + return FAIL; + } + p = skipwhite(p + 4); + + // Find optional line number. + if (here) + bp->dbg_lnum = curwin->w_cursor.lnum; + else if ( +#ifdef FEAT_PROFILE + gap != &prof_ga && +#endif + VIM_ISDIGIT(*p)) + { + bp->dbg_lnum = getdigits(&p); + p = skipwhite(p); + } + else + bp->dbg_lnum = 0; + + // Find the function or file name. Don't accept a function name with (). + if ((!here && *p == NUL) + || (here && *p != NUL) + || (bp->dbg_type == DBG_FUNC && strstr((char *)p, "()") != NULL)) + { + semsg(_(e_invalid_argument_str), arg); + return FAIL; + } + + if (bp->dbg_type == DBG_FUNC) + bp->dbg_name = vim_strsave(STRNCMP(p, "g:", 2) == 0 ? p + 2 : p); + else if (here) + bp->dbg_name = vim_strsave(curbuf->b_ffname); + else if (bp->dbg_type == DBG_EXPR) + { + bp->dbg_name = vim_strsave(p); + if (bp->dbg_name != NULL) + bp->dbg_val = eval_expr_no_emsg(bp); + } + else + { + // Expand the file name in the same way as do_source(). This means + // doing it twice, so that $DIR/file gets expanded when $DIR is + // "~/dir". + q = expand_env_save(p); + if (q == NULL) + return FAIL; + p = expand_env_save(q); + vim_free(q); + if (p == NULL) + return FAIL; + if (*p != '*') + { + bp->dbg_name = fix_fname(p); + vim_free(p); + } + else + bp->dbg_name = p; + } + + if (bp->dbg_name == NULL) + return FAIL; + return OK; +} + +/* + * ":breakadd". Also used for ":profile". + */ + void +ex_breakadd(exarg_T *eap) +{ + struct debuggy *bp; + char_u *pat; + garray_T *gap; + + gap = &dbg_breakp; +#ifdef FEAT_PROFILE + if (eap->cmdidx == CMD_profile) + gap = &prof_ga; +#endif + + if (dbg_parsearg(eap->arg, gap) != OK) + return; + + bp = &DEBUGGY(gap, gap->ga_len); + bp->dbg_forceit = eap->forceit; + + if (bp->dbg_type != DBG_EXPR) + { + pat = file_pat_to_reg_pat(bp->dbg_name, NULL, NULL, FALSE); + if (pat != NULL) + { + bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING); + vim_free(pat); + } + if (pat == NULL || bp->dbg_prog == NULL) + vim_free(bp->dbg_name); + else + { + if (bp->dbg_lnum == 0) // default line number is 1 + bp->dbg_lnum = 1; +#ifdef FEAT_PROFILE + if (eap->cmdidx != CMD_profile) +#endif + { + DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp; + ++debug_tick; + } + ++gap->ga_len; + PROF_CLEAR_CACHE(gap); + } + } + else + { + // DBG_EXPR + DEBUGGY(gap, gap->ga_len++).dbg_nr = ++last_breakp; + ++debug_tick; + if (gap == &dbg_breakp) + has_expr_breakpoint = TRUE; + } +} + +/* + * ":debuggreedy". + */ + void +ex_debuggreedy(exarg_T *eap) +{ + if (eap->addr_count == 0 || eap->line2 != 0) + debug_greedy = TRUE; + else + debug_greedy = FALSE; +} + + static void +update_has_expr_breakpoint(void) +{ + int i; + + has_expr_breakpoint = FALSE; + for (i = 0; i < dbg_breakp.ga_len; ++i) + if (BREAKP(i).dbg_type == DBG_EXPR) + { + has_expr_breakpoint = TRUE; + break; + } +} + +/* + * Return TRUE if there is any expression breakpoint. + */ + int +debug_has_expr_breakpoint(void) +{ + return has_expr_breakpoint; +} + +/* + * ":breakdel" and ":profdel". + */ + void +ex_breakdel(exarg_T *eap) +{ + struct debuggy *bp, *bpi; + int nr; + int todel = -1; + int del_all = FALSE; + int i; + linenr_T best_lnum = 0; + garray_T *gap; + + gap = &dbg_breakp; + if (eap->cmdidx == CMD_profdel) + { +#ifdef FEAT_PROFILE + gap = &prof_ga; +#else + ex_ni(eap); + return; +#endif + } + + if (vim_isdigit(*eap->arg)) + { + // ":breakdel {nr}" + nr = atol((char *)eap->arg); + for (i = 0; i < gap->ga_len; ++i) + if (DEBUGGY(gap, i).dbg_nr == nr) + { + todel = i; + break; + } + } + else if (*eap->arg == '*') + { + todel = 0; + del_all = TRUE; + } + else + { + // ":breakdel {func|file|expr} [lnum] {name}" + if (dbg_parsearg(eap->arg, gap) == FAIL) + return; + bp = &DEBUGGY(gap, gap->ga_len); + for (i = 0; i < gap->ga_len; ++i) + { + bpi = &DEBUGGY(gap, i); + if (bp->dbg_type == bpi->dbg_type + && STRCMP(bp->dbg_name, bpi->dbg_name) == 0 + && (bp->dbg_lnum == bpi->dbg_lnum + || (bp->dbg_lnum == 0 + && (best_lnum == 0 + || bpi->dbg_lnum < best_lnum)))) + { + todel = i; + best_lnum = bpi->dbg_lnum; + } + } + vim_free(bp->dbg_name); + } + + if (todel < 0) + { + semsg(_(e_breakpoint_not_found_str), eap->arg); + return; + } + + while (gap->ga_len > 0) + { + vim_free(DEBUGGY(gap, todel).dbg_name); +#ifdef FEAT_EVAL + if (DEBUGGY(gap, todel).dbg_type == DBG_EXPR + && DEBUGGY(gap, todel).dbg_val != NULL) + free_tv(DEBUGGY(gap, todel).dbg_val); +#endif + vim_regfree(DEBUGGY(gap, todel).dbg_prog); + --gap->ga_len; + if (todel < gap->ga_len) + mch_memmove(&DEBUGGY(gap, todel), &DEBUGGY(gap, todel + 1), + (gap->ga_len - todel) * sizeof(struct debuggy)); +#ifdef FEAT_PROFILE + if (eap->cmdidx == CMD_breakdel) +#endif + ++debug_tick; + if (!del_all) + break; + } + PROF_CLEAR_CACHE(gap); + + // If all breakpoints were removed clear the array. + if (gap->ga_len == 0) + ga_clear(gap); + if (gap == &dbg_breakp) + update_has_expr_breakpoint(); +} + +/* + * ":breaklist". + */ + void +ex_breaklist(exarg_T *eap UNUSED) +{ + struct debuggy *bp; + int i; + + if (dbg_breakp.ga_len == 0) + { + msg(_("No breakpoints defined")); + return; + } + + for (i = 0; i < dbg_breakp.ga_len; ++i) + { + bp = &BREAKP(i); + if (bp->dbg_type == DBG_FILE) + home_replace(NULL, bp->dbg_name, NameBuff, MAXPATHL, TRUE); + if (bp->dbg_type != DBG_EXPR) + smsg(_("%3d %s %s line %ld"), + bp->dbg_nr, + bp->dbg_type == DBG_FUNC ? "func" : "file", + bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff, + (long)bp->dbg_lnum); + else + smsg(_("%3d expr %s"), + bp->dbg_nr, bp->dbg_name); + } +} + +/* + * Find a breakpoint for a function or sourced file. + * Returns line number at which to break; zero when no matching breakpoint. + */ + linenr_T +dbg_find_breakpoint( + int file, // TRUE for a file, FALSE for a function + char_u *fname, // file or function name + linenr_T after) // after this line number +{ + return debuggy_find(file, fname, after, &dbg_breakp, NULL); +} + +#if defined(FEAT_PROFILE) || defined(PROTO) +#if defined(PROF_CACHE_LOG) +static int count_lookups[2]; +#endif +/* + * Return TRUE if profiling is on for a function or sourced file. + * Cache the results of debuggy_find(). + * Cache is cleared whenever prof_ga.ga_len is changed. + */ + int +has_profiling( + int file, // TRUE for a file, FALSE for a function + char_u *fname, // file or function name + int *fp, // return: forceit + hash_T *hashp) // use/return fname hash, may be NULL +{ + if (prof_ga.ga_len == 0 || !prof_cache_initialized) + return debuggy_find(file, fname, (linenr_T)0, &prof_ga, fp) + != (linenr_T)0; + + hash_T hash; + if (hashp != NULL) + { + hash = *hashp; + if (hash == 0) + { + hash = hash_hash(fname); + *hashp = hash; + } + } + else + hash = hash_hash(fname); + + hashtab_T *ht = &prof_cache[file ? PROF_HTAB_FILES : PROF_HTAB_FUNCS]; + hashitem_T *hi = hash_lookup(ht, fname, hash); + profentry_T *pe; + if (HASHITEM_EMPTY(hi)) + { + pe = alloc(offsetof(profentry_T, pen_name) + STRLEN(fname) + 1); + if (pe == NULL) + return FALSE; + STRCPY(pe->pen_name, fname); + pe->pen_flags = 0; + // run debuggy_find and capture return and forceit + int f; + int lnum = debuggy_find(file, fname, (linenr_T)0, &prof_ga, &f); + if (lnum) + { + PEN_SET_PROFILING(pe); + if (f) + PEN_SET_FORCEIT(pe); + } + hash_add_item(ht, hi, pe->pen_name, hash); +#if defined(PROF_CACHE_LOG) + ch_log(NULL, "has_profiling: %s %s forceit %s, profile %s", + file ? "file" : "func", fname, + PEN_IS_FORCEIT(pe) ? "true" : "false", + PEN_IS_PROFILING(pe) ? "true" : "false"); +#endif + } + else + pe = HI2PE(hi); + if (fp) + *fp = PEN_IS_FORCEIT(pe); +#if defined(PROF_CACHE_LOG) + count_lookups[file ? PROF_HTAB_FILES : PROF_HTAB_FUNCS]++; +#endif + return PEN_IS_PROFILING(pe); +} + + static void +prof_clear_cache(void) +{ + if (!prof_cache_initialized) + { + hash_init(&prof_cache[PROF_HTAB_FUNCS]); + hash_init(&prof_cache[PROF_HTAB_FILES]); + prof_cache_initialized = TRUE; + } + + hashtab_T *ht; + for (ht = &prof_cache[0]; ht < &prof_cache[N_PROF_HTAB]; ht++) + { + if (ht->ht_used > 0) + { +#if defined(PROF_CACHE_LOG) + int idx = ht == &prof_cache[PROF_HTAB_FUNCS] + ? PROF_HTAB_FUNCS : PROF_HTAB_FILES; + ch_log(NULL, "prof_clear_cache: %s, used: %ld, lookups: %d", + idx == PROF_HTAB_FUNCS ? "function" : "script", + ht->ht_used, count_lookups[idx]); + count_lookups[idx] = 0; +#endif + hash_clear_all(ht, offsetof(profentry_T, pen_name)); + hash_init(ht); + } + } +} +#endif + +/* + * Common code for dbg_find_breakpoint() and has_profiling(). + */ + static linenr_T +debuggy_find( + int is_file, // TRUE for a file, FALSE for a function + char_u *fname, // file or function name + linenr_T after, // after this line number + garray_T *gap, // either &dbg_breakp or &prof_ga + int *fp) // if not NULL: return forceit +{ + struct debuggy *bp; + int i; + linenr_T lnum = 0; + char_u *name = NULL; + char_u *short_name = fname; + int prev_got_int; + + // Return quickly when there are no breakpoints. + if (gap->ga_len == 0) + return (linenr_T)0; + + // For a script-local function remove the prefix, so that + // "profile func Func" matches "Func" in any script. Otherwise it's very + // difficult to profile/debug a script-local function. It may match a + // function in the wrong script, but that is much better than not being + // able to profile/debug a function in a script with unknown ID. + // Also match a script-specific name. + if (!is_file && fname[0] == K_SPECIAL) + { + short_name = vim_strchr(fname, '_') + 1; + name = alloc(STRLEN(fname) + 3); + if (name != NULL) + { + STRCPY(name, ""); + STRCPY(name + 5, fname + 3); + } + } + + for (i = 0; i < gap->ga_len; ++i) + { + // Skip entries that are not useful or are for a line that is beyond + // an already found breakpoint. + bp = &DEBUGGY(gap, i); + if (((bp->dbg_type == DBG_FILE) == is_file + && bp->dbg_type != DBG_EXPR && ( +#ifdef FEAT_PROFILE + gap == &prof_ga || +#endif + (bp->dbg_lnum > after && (lnum == 0 || bp->dbg_lnum < lnum))))) + { + // Save the value of got_int and reset it. We don't want a + // previous interruption cancel matching, only hitting CTRL-C + // while matching should abort it. + prev_got_int = got_int; + got_int = FALSE; + if ((name != NULL + && vim_regexec_prog(&bp->dbg_prog, FALSE, name, (colnr_T)0)) + || vim_regexec_prog(&bp->dbg_prog, FALSE, + short_name, (colnr_T)0)) + { + lnum = bp->dbg_lnum; + if (fp != NULL) + *fp = bp->dbg_forceit; + } + got_int |= prev_got_int; +#ifdef FEAT_PROFILE + if (lnum && gap == &prof_ga) + break; +#endif + } +#ifdef FEAT_EVAL + else if (bp->dbg_type == DBG_EXPR) + { + typval_T *tv; + int line = FALSE; + + tv = eval_expr_no_emsg(bp); + if (tv != NULL) + { + if (bp->dbg_val == NULL) + { + debug_oldval = typval_tostring(NULL, TRUE); + bp->dbg_val = tv; + debug_newval = typval_tostring(bp->dbg_val, TRUE); + line = TRUE; + } + else + { + // Use "==" instead of "is" for strings, that is what we + // always have done. + exprtype_T type = tv->v_type == VAR_STRING + ? EXPR_EQUAL : EXPR_IS; + + if (typval_compare(tv, bp->dbg_val, type, FALSE) == OK + && tv->vval.v_number == FALSE) + { + typval_T *v; + + line = TRUE; + debug_oldval = typval_tostring(bp->dbg_val, TRUE); + // Need to evaluate again, typval_compare() overwrites + // "tv". + v = eval_expr_no_emsg(bp); + debug_newval = typval_tostring(v, TRUE); + free_tv(bp->dbg_val); + bp->dbg_val = v; + } + free_tv(tv); + } + } + else if (bp->dbg_val != NULL) + { + debug_oldval = typval_tostring(bp->dbg_val, TRUE); + debug_newval = typval_tostring(NULL, TRUE); + free_tv(bp->dbg_val); + bp->dbg_val = NULL; + line = TRUE; + } + + if (line) + { + lnum = after > 0 ? after : 1; + break; + } + } +#endif + } + if (name != fname) + vim_free(name); + + return lnum; +} + +/* + * Called when a breakpoint was encountered. + */ + void +dbg_breakpoint(char_u *name, linenr_T lnum) +{ + // We need to check if this line is actually executed in do_one_cmd() + debug_breakpoint_name = name; + debug_breakpoint_lnum = lnum; +} +#endif diff --git a/userland/apps/vim/src/dict.c b/userland/apps/vim/src/dict.c new file mode 100644 index 00000000..cb22b42c --- /dev/null +++ b/userland/apps/vim/src/dict.c @@ -0,0 +1,1628 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * dict.c: Dictionary support + */ + +#include "vim.h" + +#if defined(FEAT_EVAL) || defined(PROTO) + +// List head for garbage collection. Although there can be a reference loop +// from partial to dict to partial, we don't need to keep track of the partial, +// since it will get freed when the dict is unused and gets freed. +static dict_T *first_dict = NULL; + +/* + * Allocate an empty header for a dictionary. + * Caller should take care of the reference count. + */ + dict_T * +dict_alloc(void) +{ + dict_T *d; + + d = ALLOC_CLEAR_ONE(dict_T); + if (d == NULL) + return NULL; + + // Add the dict to the list of dicts for garbage collection. + if (first_dict != NULL) + first_dict->dv_used_prev = d; + d->dv_used_next = first_dict; + d->dv_used_prev = NULL; + first_dict = d; + + hash_init(&d->dv_hashtab); + d->dv_lock = 0; + d->dv_scope = 0; + d->dv_refcount = 0; + d->dv_copyID = 0; + return d; +} + +/* + * dict_alloc() with an ID for alloc_fail(). + */ + dict_T * +dict_alloc_id(alloc_id_T id UNUSED) +{ +#ifdef FEAT_EVAL + if (alloc_fail_id == id && alloc_does_fail(sizeof(list_T))) + return NULL; +#endif + return (dict_alloc()); +} + + dict_T * +dict_alloc_lock(int lock) +{ + dict_T *d = dict_alloc(); + + if (d != NULL) + d->dv_lock = lock; + return d; +} + +/* + * Allocate an empty dict for a return value. + * Returns OK or FAIL. + */ + int +rettv_dict_alloc(typval_T *rettv) +{ + dict_T *d = dict_alloc_lock(0); + + if (d == NULL) + return FAIL; + + rettv_dict_set(rettv, d); + return OK; +} + +/* + * Set a dictionary as the return value + */ + void +rettv_dict_set(typval_T *rettv, dict_T *d) +{ + rettv->v_type = VAR_DICT; + rettv->vval.v_dict = d; + if (d != NULL) + ++d->dv_refcount; +} + +/* + * Free a Dictionary, including all non-container items it contains. + * Ignores the reference count. + */ + void +dict_free_contents(dict_T *d) +{ + hashtab_free_contents(&d->dv_hashtab); + free_type(d->dv_type); + d->dv_type = NULL; +} + +/* + * Clear hashtab "ht" and dict items it contains. + * If "ht" is not freed then you should call hash_init() next! + */ + void +hashtab_free_contents(hashtab_T *ht) +{ + int todo; + hashitem_T *hi; + dictitem_T *di; + + if (check_hashtab_frozen(ht, "clear dict")) + return; + + // Lock the hashtab, we don't want it to resize while freeing items. + hash_lock(ht); + todo = (int)ht->ht_used; + FOR_ALL_HASHTAB_ITEMS(ht, hi, todo) + { + if (!HASHITEM_EMPTY(hi)) + { + // Remove the item before deleting it, just in case there is + // something recursive causing trouble. + di = HI2DI(hi); + hash_remove(ht, hi, "clear dict"); + dictitem_free(di); + --todo; + } + } + + // The hashtab is still locked, it has to be re-initialized anyway. + hash_clear(ht); +} + + static void +dict_free_dict(dict_T *d) +{ + // Remove the dict from the list of dicts for garbage collection. + if (d->dv_used_prev == NULL) + first_dict = d->dv_used_next; + else + d->dv_used_prev->dv_used_next = d->dv_used_next; + if (d->dv_used_next != NULL) + d->dv_used_next->dv_used_prev = d->dv_used_prev; + vim_free(d); +} + + static void +dict_free(dict_T *d) +{ + if (!in_free_unref_items) + { + dict_free_contents(d); + dict_free_dict(d); + } +} + +/* + * Unreference a Dictionary: decrement the reference count and free it when it + * becomes zero. + */ + void +dict_unref(dict_T *d) +{ + if (d != NULL && --d->dv_refcount <= 0) + dict_free(d); +} + +/* + * Go through the list of dicts and free items without the copyID. + * Returns TRUE if something was freed. + */ + int +dict_free_nonref(int copyID) +{ + dict_T *dd; + int did_free = FALSE; + + for (dd = first_dict; dd != NULL; dd = dd->dv_used_next) + if ((dd->dv_copyID & COPYID_MASK) != (copyID & COPYID_MASK)) + { + // Free the Dictionary and ordinary items it contains, but don't + // recurse into Lists and Dictionaries, they will be in the list + // of dicts or list of lists. + dict_free_contents(dd); + did_free = TRUE; + } + return did_free; +} + + void +dict_free_items(int copyID) +{ + dict_T *dd, *dd_next; + + for (dd = first_dict; dd != NULL; dd = dd_next) + { + dd_next = dd->dv_used_next; + if ((dd->dv_copyID & COPYID_MASK) != (copyID & COPYID_MASK)) + dict_free_dict(dd); + } +} + +/* + * Allocate a Dictionary item. + * The "key" is copied to the new item. + * Note that the type and value of the item "di_tv" still needs to be + * initialized! + * Returns NULL when out of memory. + */ + dictitem_T * +dictitem_alloc(char_u *key) +{ + dictitem_T *di; + size_t len = STRLEN(key); + + di = alloc(offsetof(dictitem_T, di_key) + len + 1); + if (di == NULL) + return NULL; + + mch_memmove(di->di_key, key, len + 1); + di->di_flags = DI_FLAGS_ALLOC; + di->di_tv.v_lock = 0; + di->di_tv.v_type = VAR_UNKNOWN; + return di; +} + +/* + * Make a copy of a Dictionary item. + */ + static dictitem_T * +dictitem_copy(dictitem_T *org) +{ + dictitem_T *di; + size_t len = STRLEN(org->di_key); + + di = alloc(offsetof(dictitem_T, di_key) + len + 1); + if (di == NULL) + return NULL; + + mch_memmove(di->di_key, org->di_key, len + 1); + di->di_flags = DI_FLAGS_ALLOC; + copy_tv(&org->di_tv, &di->di_tv); + return di; +} + +/* + * Remove item "item" from Dictionary "dict" and free it. + * "command" is used for the error message when the hashtab if frozen. + */ + void +dictitem_remove(dict_T *dict, dictitem_T *item, char *command) +{ + hashitem_T *hi; + + hi = hash_find(&dict->dv_hashtab, item->di_key); + if (HASHITEM_EMPTY(hi)) + internal_error("dictitem_remove()"); + else + hash_remove(&dict->dv_hashtab, hi, command); + dictitem_free(item); +} + +/* + * Free a dict item. Also clears the value. + */ + void +dictitem_free(dictitem_T *item) +{ + clear_tv(&item->di_tv); + if (item->di_flags & DI_FLAGS_ALLOC) + vim_free(item); +} + +/* + * Make a copy of dict "d". Shallow if "deep" is FALSE. + * The refcount of the new dict is set to 1. + * See item_copy() for "top" and "copyID". + * Returns NULL when out of memory. + */ + dict_T * +dict_copy(dict_T *orig, int deep, int top, int copyID) +{ + dict_T *copy; + dictitem_T *di; + int todo; + hashitem_T *hi; + + if (orig == NULL) + return NULL; + + copy = dict_alloc(); + if (copy == NULL) + return NULL; + + if (copyID != 0) + { + orig->dv_copyID = copyID; + orig->dv_copydict = copy; + } + if (orig->dv_type == NULL || top || deep) + copy->dv_type = NULL; + else + copy->dv_type = alloc_type(orig->dv_type); + + todo = (int)orig->dv_hashtab.ht_used; + for (hi = orig->dv_hashtab.ht_array; todo > 0 && !got_int; ++hi) + { + if (!HASHITEM_EMPTY(hi)) + { + --todo; + + di = dictitem_alloc(hi->hi_key); + if (di == NULL) + break; + if (deep) + { + if (item_copy(&HI2DI(hi)->di_tv, &di->di_tv, + deep, FALSE, copyID) == FAIL) + { + vim_free(di); + break; + } + } + else + copy_tv(&HI2DI(hi)->di_tv, &di->di_tv); + if (dict_add(copy, di) == FAIL) + { + dictitem_free(di); + break; + } + } + } + + ++copy->dv_refcount; + if (todo > 0) + { + dict_unref(copy); + copy = NULL; + } + + return copy; +} + +/* + * Check for adding a function to g: or s: (in Vim9 script) or l:. + * If the name is wrong give an error message and return TRUE. + */ + int +dict_wrong_func_name(dict_T *d, typval_T *tv, char_u *name) +{ + return (d == get_globvar_dict() + || (in_vim9script() && SCRIPT_ID_VALID(current_sctx.sc_sid) + && d == &SCRIPT_ITEM(current_sctx.sc_sid)->sn_vars->sv_dict) + || &d->dv_hashtab == get_funccal_local_ht()) + && (tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL) + && var_wrong_func_name(name, TRUE); +} + +/* + * Add item "item" to Dictionary "d". + * Returns FAIL when out of memory and when key already exists. + */ + int +dict_add(dict_T *d, dictitem_T *item) +{ + if (dict_wrong_func_name(d, &item->di_tv, item->di_key)) + return FAIL; + return hash_add(&d->dv_hashtab, item->di_key, "add to dictionary"); +} + +/* + * Add a number or special entry to dictionary "d". + * Returns FAIL when out of memory and when key already exists. + */ + static int +dict_add_number_special(dict_T *d, char *key, varnumber_T nr, vartype_T vartype) +{ + dictitem_T *item; + + item = dictitem_alloc((char_u *)key); + if (item == NULL) + return FAIL; + item->di_tv.v_type = vartype; + item->di_tv.vval.v_number = nr; + if (dict_add(d, item) == FAIL) + { + dictitem_free(item); + return FAIL; + } + return OK; +} + +/* + * Add a number entry to dictionary "d". + * Returns FAIL when out of memory and when key already exists. + */ + int +dict_add_number(dict_T *d, char *key, varnumber_T nr) +{ + return dict_add_number_special(d, key, nr, VAR_NUMBER); +} + +/* + * Add a special entry to dictionary "d". + * Returns FAIL when out of memory and when key already exists. + */ + int +dict_add_bool(dict_T *d, char *key, varnumber_T nr) +{ + return dict_add_number_special(d, key, nr, VAR_BOOL); +} + +/* + * Add a string entry to dictionary "d". + * Returns FAIL when out of memory and when key already exists. + */ + int +dict_add_string(dict_T *d, char *key, char_u *str) +{ + return dict_add_string_len(d, key, str, -1); +} + +/* + * Add a string entry to dictionary "d". + * "str" will be copied to allocated memory. + * When "len" is -1 use the whole string, otherwise only this many bytes. + * Returns FAIL when out of memory and when key already exists. + */ + int +dict_add_string_len(dict_T *d, char *key, char_u *str, int len) +{ + dictitem_T *item; + char_u *val = NULL; + + item = dictitem_alloc((char_u *)key); + if (item == NULL) + return FAIL; + item->di_tv.v_type = VAR_STRING; + if (str != NULL) + { + if (len == -1) + val = vim_strsave(str); + else + val = vim_strnsave(str, len); + } + item->di_tv.vval.v_string = val; + if (dict_add(d, item) == FAIL) + { + dictitem_free(item); + return FAIL; + } + return OK; +} + +/* + * Add a list entry to dictionary "d". + * Returns FAIL when out of memory and when key already exists. + */ + int +dict_add_list(dict_T *d, char *key, list_T *list) +{ + dictitem_T *item; + + item = dictitem_alloc((char_u *)key); + if (item == NULL) + return FAIL; + item->di_tv.v_type = VAR_LIST; + item->di_tv.vval.v_list = list; + ++list->lv_refcount; + if (dict_add(d, item) == FAIL) + { + dictitem_free(item); + return FAIL; + } + return OK; +} + +/* + * Add a typval_T entry to dictionary "d". + * Returns FAIL when out of memory and when key already exists. + */ + int +dict_add_tv(dict_T *d, char *key, typval_T *tv) +{ + dictitem_T *item; + + item = dictitem_alloc((char_u *)key); + if (item == NULL) + return FAIL; + copy_tv(tv, &item->di_tv); + if (dict_add(d, item) == FAIL) + { + dictitem_free(item); + return FAIL; + } + return OK; +} + +/* + * Add a callback to dictionary "d". + * Returns FAIL when out of memory and when key already exists. + */ + int +dict_add_callback(dict_T *d, char *key, callback_T *cb) +{ + dictitem_T *item; + + item = dictitem_alloc((char_u *)key); + if (item == NULL) + return FAIL; + put_callback(cb, &item->di_tv); + if (dict_add(d, item) == FAIL) + { + dictitem_free(item); + return FAIL; + } + return OK; +} + +/* + * Initializes "iter" for iterating over dictionary items with + * dict_iterate_next(). + * If "var" is not a Dict or an empty Dict then there will be nothing to + * iterate over, no error is given. + * NOTE: The dictionary must not change until iterating is finished! + */ + void +dict_iterate_start(typval_T *var, dict_iterator_T *iter) +{ + if (var->v_type != VAR_DICT || var->vval.v_dict == NULL) + iter->dit_todo = 0; + else + { + dict_T *d = var->vval.v_dict; + + iter->dit_todo = d->dv_hashtab.ht_used; + iter->dit_hi = d->dv_hashtab.ht_array; + } +} + +/* + * Iterate over the items referred to by "iter". It should be initialized with + * dict_iterate_start(). + * Returns a pointer to the key. + * "*tv_result" is set to point to the value for that key. + * If there are no more items, NULL is returned. + */ + char_u * +dict_iterate_next(dict_iterator_T *iter, typval_T **tv_result) +{ + dictitem_T *di; + char_u *result; + + if (iter->dit_todo == 0) + return NULL; + + while (HASHITEM_EMPTY(iter->dit_hi)) + ++iter->dit_hi; + + di = HI2DI(iter->dit_hi); + result = di->di_key; + *tv_result = &di->di_tv; + + --iter->dit_todo; + ++iter->dit_hi; + return result; +} + +/* + * Add a dict entry to dictionary "d". + * Returns FAIL when out of memory and when key already exists. + */ + int +dict_add_dict(dict_T *d, char *key, dict_T *dict) +{ + dictitem_T *item; + + item = dictitem_alloc((char_u *)key); + if (item == NULL) + return FAIL; + item->di_tv.v_type = VAR_DICT; + item->di_tv.vval.v_dict = dict; + ++dict->dv_refcount; + if (dict_add(d, item) == FAIL) + { + dictitem_free(item); + return FAIL; + } + return OK; +} + +/* + * Get the number of items in a Dictionary. + */ + long +dict_len(dict_T *d) +{ + if (d == NULL) + return 0L; + return (long)d->dv_hashtab.ht_used; +} + +/* + * Find item "key[len]" in Dictionary "d". + * If "len" is negative use strlen(key). + * Returns NULL when not found. + */ + dictitem_T * +dict_find(dict_T *d, char_u *key, int len) +{ +#define AKEYLEN 200 + char_u buf[AKEYLEN]; + char_u *akey; + char_u *tofree = NULL; + hashitem_T *hi; + + if (d == NULL) + return NULL; + if (len < 0) + akey = key; + else if (len >= AKEYLEN) + { + tofree = akey = vim_strnsave(key, len); + if (akey == NULL) + return NULL; + } + else + { + // Avoid a malloc/free by using buf[]. + vim_strncpy(buf, key, len); + akey = buf; + } + + hi = hash_find(&d->dv_hashtab, akey); + vim_free(tofree); + if (HASHITEM_EMPTY(hi)) + return NULL; + return HI2DI(hi); +} + +/* + * Returns TRUE if "key" is present in Dictionary "d". + */ + int +dict_has_key(dict_T *d, char *key) +{ + return dict_find(d, (char_u *)key, -1) != NULL; +} + +/* + * Get a typval_T item from a dictionary and copy it into "rettv". + * Returns FAIL if the entry doesn't exist or out of memory. + */ + int +dict_get_tv(dict_T *d, char *key, typval_T *rettv) +{ + dictitem_T *di; + + di = dict_find(d, (char_u *)key, -1); + if (di == NULL) + return FAIL; + copy_tv(&di->di_tv, rettv); + return OK; +} + +/* + * Get a string item from a dictionary. + * When "save" is TRUE allocate memory for it. + * When FALSE a shared buffer is used, can only be used once! + * Returns NULL if the entry doesn't exist or out of memory. + */ + char_u * +dict_get_string(dict_T *d, char *key, int save) +{ + dictitem_T *di; + char_u *s; + + di = dict_find(d, (char_u *)key, -1); + if (di == NULL) + return NULL; + s = tv_get_string(&di->di_tv); + if (save && s != NULL) + s = vim_strsave(s); + return s; +} + +/* + * Get a number item from a dictionary. + * Returns 0 if the entry doesn't exist. + */ + varnumber_T +dict_get_number(dict_T *d, char *key) +{ + return dict_get_number_def(d, key, 0); +} + +/* + * Get a number item from a dictionary. + * Returns "def" if the entry doesn't exist. + */ + varnumber_T +dict_get_number_def(dict_T *d, char *key, int def) +{ + dictitem_T *di; + + di = dict_find(d, (char_u *)key, -1); + if (di == NULL) + return def; + return tv_get_number(&di->di_tv); +} + +/* + * Get a number item from a dictionary. + * Returns 0 if the entry doesn't exist. + * Give an error if the entry is not a number. + */ + varnumber_T +dict_get_number_check(dict_T *d, char_u *key) +{ + dictitem_T *di; + + di = dict_find(d, key, -1); + if (di == NULL) + return 0; + if (di->di_tv.v_type != VAR_NUMBER) + { + semsg(_(e_invalid_argument_str), tv_get_string(&di->di_tv)); + return 0; + } + return tv_get_number(&di->di_tv); +} + +/* + * Get a bool item (number or true/false) from a dictionary. + * Returns "def" if the entry doesn't exist. + */ + varnumber_T +dict_get_bool(dict_T *d, char *key, int def) +{ + dictitem_T *di; + + di = dict_find(d, (char_u *)key, -1); + if (di == NULL) + return def; + return tv_get_bool(&di->di_tv); +} + +/* + * Return an allocated string with the string representation of a Dictionary. + * May return NULL. + */ + char_u * +dict2string(typval_T *tv, int copyID, int restore_copyID) +{ + garray_T ga; + int first = TRUE; + char_u *tofree; + char_u numbuf[NUMBUFLEN]; + hashitem_T *hi; + char_u *s; + dict_T *d; + int todo; + + if ((d = tv->vval.v_dict) == NULL) + return NULL; + ga_init2(&ga, sizeof(char), 80); + ga_append(&ga, '{'); + + todo = (int)d->dv_hashtab.ht_used; + FOR_ALL_HASHTAB_ITEMS(&d->dv_hashtab, hi, todo) + { + if (!HASHITEM_EMPTY(hi)) + { + --todo; + + if (first) + first = FALSE; + else + ga_concat(&ga, (char_u *)", "); + + tofree = string_quote(hi->hi_key, FALSE); + if (tofree != NULL) + { + ga_concat(&ga, tofree); + vim_free(tofree); + } + ga_concat(&ga, (char_u *)": "); + s = echo_string_core(&HI2DI(hi)->di_tv, &tofree, numbuf, copyID, + FALSE, restore_copyID, TRUE); + if (s != NULL) + ga_concat(&ga, s); + vim_free(tofree); + if (s == NULL || did_echo_string_emsg) + break; + line_breakcheck(); + + } + } + if (todo > 0) + { + vim_free(ga.ga_data); + return NULL; + } + + ga_append(&ga, '}'); + ga_append(&ga, NUL); + return (char_u *)ga.ga_data; +} + +/* + * Advance over a literal key, including "-". If the first character is not a + * literal key character then "key" is returned. + */ + static char_u * +skip_literal_key(char_u *key) +{ + char_u *p; + + for (p = key; ASCII_ISALNUM(*p) || *p == '_' || *p == '-'; ++p) + ; + return p; +} + +/* + * Get the key for #{key: val} into "tv" and advance "arg". + * Return FAIL when there is no valid key. + */ + static int +get_literal_key_tv(char_u **arg, typval_T *tv) +{ + char_u *p = skip_literal_key(*arg); + + if (p == *arg) + return FAIL; + tv->v_type = VAR_STRING; + tv->vval.v_string = vim_strnsave(*arg, p - *arg); + + *arg = p; + return OK; +} + +/* + * Get a literal key for a Vim9 dict: + * {"name": value}, + * {'name': value}, + * {name: value} use "name" as a literal key + * Return the key in allocated memory or NULL in the case of an error. + * "arg" is advanced to just after the key. + */ + char_u * +get_literal_key(char_u **arg) +{ + char_u *key; + char_u *end; + typval_T rettv; + + if (**arg == '\'') + { + if (eval_lit_string(arg, &rettv, TRUE, FALSE) == FAIL) + return NULL; + key = rettv.vval.v_string; + } + else if (**arg == '"') + { + if (eval_string(arg, &rettv, TRUE, FALSE) == FAIL) + return NULL; + key = rettv.vval.v_string; + } + else + { + end = skip_literal_key(*arg); + if (end == *arg) + { + semsg(_(e_invalid_key_str), *arg); + return NULL; + } + key = vim_strnsave(*arg, end - *arg); + *arg = end; + } + return key; +} + +/* + * Allocate a variable for a Dictionary and fill it from "*arg". + * "*arg" points to the "{". + * "literal" is TRUE for #{key: val} + * Return OK or FAIL. Returns NOTDONE for {expr}. + */ + int +eval_dict(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int literal) +{ + int evaluate = evalarg == NULL ? FALSE + : (evalarg->eval_flags & EVAL_EVALUATE); + dict_T *d = NULL; + typval_T tvkey; + typval_T tv; + char_u *key = NULL; + dictitem_T *item; + char_u *curly_expr = skipwhite(*arg + 1); + char_u buf[NUMBUFLEN]; + int vim9script = in_vim9script(); + int had_comma; + + // First check if it's not a curly-braces expression: {expr}. + // Must do this without evaluating, otherwise a function may be called + // twice. Unfortunately this means we need to call eval1() twice for the + // first item. + // "{}" is an empty Dictionary. + // "#{abc}" is never a curly-braces expression. + if (!vim9script + && *curly_expr != '}' + && !literal + && eval1(&curly_expr, &tv, NULL) == OK + && *skipwhite(curly_expr) == '}') + return NOTDONE; + + if (evaluate) + { + d = dict_alloc(); + if (d == NULL) + return FAIL; + } + tvkey.v_type = VAR_UNKNOWN; + tv.v_type = VAR_UNKNOWN; + + *arg = skipwhite_and_linebreak(*arg + 1, evalarg); + while (**arg != '}' && **arg != NUL) + { + int has_bracket = vim9script && **arg == '['; + + if (literal) + { + if (get_literal_key_tv(arg, &tvkey) == FAIL) + goto failret; + } + else if (vim9script && !has_bracket) + { + tvkey.vval.v_string = get_literal_key(arg); + if (tvkey.vval.v_string == NULL) + goto failret; + tvkey.v_type = VAR_STRING; + } + else + { + if (has_bracket) + *arg = skipwhite(*arg + 1); + if (eval1(arg, &tvkey, evalarg) == FAIL) // recursive! + goto failret; + if (has_bracket) + { + *arg = skipwhite(*arg); + if (**arg != ']') + { + emsg(_(e_missing_matching_bracket_after_dict_key)); + clear_tv(&tvkey); + return FAIL; + } + ++*arg; + } + } + + // the colon should come right after the key, but this wasn't checked + // previously, so only require it in Vim9 script. + if (!vim9script) + *arg = skipwhite(*arg); + if (**arg != ':') + { + if (*skipwhite(*arg) == ':') + semsg(_(e_no_white_space_allowed_before_str_str), ":", *arg); + else + semsg(_(e_missing_colon_in_dictionary_str), *arg); + clear_tv(&tvkey); + goto failret; + } + if (evaluate) + { + if (tvkey.v_type == VAR_FLOAT) + { + tvkey.vval.v_string = typval_tostring(&tvkey, TRUE); + tvkey.v_type = VAR_STRING; + } + key = tv_get_string_buf_chk(&tvkey, buf); + if (key == NULL) + { + // "key" is NULL when tv_get_string_buf_chk() gave an errmsg + clear_tv(&tvkey); + goto failret; + } + } + if (vim9script && (*arg)[1] != NUL && !VIM_ISWHITE((*arg)[1])) + { + semsg(_(e_white_space_required_after_str_str), ":", *arg); + clear_tv(&tvkey); + goto failret; + } + + *arg = skipwhite_and_linebreak(*arg + 1, evalarg); + if (eval1(arg, &tv, evalarg) == FAIL) // recursive! + { + if (evaluate) + clear_tv(&tvkey); + goto failret; + } + if (check_typval_is_value(&tv) == FAIL) + { + if (evaluate) + { + clear_tv(&tvkey); + clear_tv(&tv); + } + goto failret; + } + if (evaluate) + { + item = dict_find(d, key, -1); + if (item != NULL) + { + semsg(_(e_duplicate_key_in_dictionary_str), key); + clear_tv(&tvkey); + clear_tv(&tv); + goto failret; + } + item = dictitem_alloc(key); + if (item != NULL) + { + item->di_tv = tv; + item->di_tv.v_lock = 0; + if (dict_add(d, item) == FAIL) + dictitem_free(item); + } + } + clear_tv(&tvkey); + + // the comma should come right after the value, but this wasn't checked + // previously, so only require it in Vim9 script. + if (!vim9script) + *arg = skipwhite(*arg); + had_comma = **arg == ','; + if (had_comma) + { + if (vim9script && (*arg)[1] != NUL && !VIM_ISWHITE((*arg)[1])) + { + semsg(_(e_white_space_required_after_str_str), ",", *arg); + goto failret; + } + *arg = skipwhite(*arg + 1); + } + + // the "}" can be on the next line + *arg = skipwhite_and_linebreak(*arg, evalarg); + if (**arg == '}') + break; + if (!had_comma) + { + if (**arg == ',') + semsg(_(e_no_white_space_allowed_before_str_str), ",", *arg); + else + semsg(_(e_missing_comma_in_dictionary_str), *arg); + goto failret; + } + } + + if (**arg != '}') + { + if (evalarg != NULL) + semsg(_(e_missing_dict_end_str), *arg); +failret: + if (d != NULL) + dict_free(d); + return FAIL; + } + + *arg = *arg + 1; + if (evaluate) + rettv_dict_set(rettv, d); + + return OK; +} + +/* + * Go over all entries in "d2" and add them to "d1". + * When "action" is "error" then a duplicate key is an error. + * When "action" is "force" then a duplicate key is overwritten. + * When "action" is "move" then move items instead of copying. + * Otherwise duplicate keys are ignored ("action" is "keep"). + * "func_name" is used for reporting where an error occurred. + */ + void +dict_extend(dict_T *d1, dict_T *d2, char_u *action, char *func_name) +{ + dictitem_T *di1; + int todo; + char_u *arg_errmsg = (char_u *)N_("extend() argument"); + type_T *type; + + if (check_hashtab_frozen(&d1->dv_hashtab, "extend")) + return; + + if (*action == 'm') + { + if (check_hashtab_frozen(&d2->dv_hashtab, "extend")) + return; + hash_lock(&d2->dv_hashtab); // don't rehash on hash_remove() + } + + if (d1->dv_type != NULL && d1->dv_type->tt_member != NULL) + type = d1->dv_type->tt_member; + else + type = NULL; + + todo = (int)d2->dv_hashtab.ht_used; + hashitem_T *hi2; + FOR_ALL_HASHTAB_ITEMS(&d2->dv_hashtab, hi2, todo) + { + if (!HASHITEM_EMPTY(hi2)) + { + --todo; + di1 = dict_find(d1, hi2->hi_key, -1); + // Check the key to be valid when adding to any scope. + if (d1->dv_scope != 0 && !valid_varname(hi2->hi_key, -1, TRUE)) + break; + + if (type != NULL + && check_typval_arg_type(type, &HI2DI(hi2)->di_tv, + func_name, 0) == FAIL) + break; + + if (di1 == NULL) + { + if (*action == 'm') + { + // Cheap way to move a dict item from "d2" to "d1". + // If dict_add() fails then "d2" won't be empty. + di1 = HI2DI(hi2); + if (dict_add(d1, di1) == OK) + hash_remove(&d2->dv_hashtab, hi2, "extend"); + } + else + { + di1 = dictitem_copy(HI2DI(hi2)); + if (di1 != NULL && dict_add(d1, di1) == FAIL) + dictitem_free(di1); + } + } + else if (*action == 'e') + { + semsg(_(e_key_already_exists_str), hi2->hi_key); + break; + } + else if (*action == 'f' && HI2DI(hi2) != di1) + { + if (value_check_lock(di1->di_tv.v_lock, arg_errmsg, TRUE) + || var_check_ro(di1->di_flags, arg_errmsg, TRUE)) + break; + // Disallow replacing a builtin function. + if (dict_wrong_func_name(d1, &HI2DI(hi2)->di_tv, hi2->hi_key)) + break; + clear_tv(&di1->di_tv); + copy_tv(&HI2DI(hi2)->di_tv, &di1->di_tv); + } + } + } + + if (*action == 'm') + hash_unlock(&d2->dv_hashtab); +} + +/* + * Return the dictitem that an entry in a hashtable points to. + */ + dictitem_T * +dict_lookup(hashitem_T *hi) +{ + return HI2DI(hi); +} + +/* + * Return TRUE when two dictionaries have exactly the same key/values. + */ + int +dict_equal( + dict_T *d1, + dict_T *d2, + int ic, // ignore case for strings + int recursive) // TRUE when used recursively +{ + hashitem_T *hi; + dictitem_T *item2; + int todo; + + if (d1 == d2) + return TRUE; + if (dict_len(d1) != dict_len(d2)) + return FALSE; + if (dict_len(d1) == 0) + // empty and NULL dicts are considered equal + return TRUE; + if (d1 == NULL || d2 == NULL) + return FALSE; + + todo = (int)d1->dv_hashtab.ht_used; + FOR_ALL_HASHTAB_ITEMS(&d1->dv_hashtab, hi, todo) + { + if (!HASHITEM_EMPTY(hi)) + { + item2 = dict_find(d2, hi->hi_key, -1); + if (item2 == NULL) + return FALSE; + if (!tv_equal(&HI2DI(hi)->di_tv, &item2->di_tv, ic, recursive)) + return FALSE; + --todo; + } + } + return TRUE; +} + +/* + * Count the number of times item "needle" occurs in Dict "d". Case is ignored + * if "ic" is TRUE. + */ + long +dict_count(dict_T *d, typval_T *needle, int ic) +{ + int todo; + hashitem_T *hi; + long n = 0; + + if (d == NULL) + return 0; + + todo = (int)d->dv_hashtab.ht_used; + FOR_ALL_HASHTAB_ITEMS(&d->dv_hashtab, hi, todo) + { + if (!HASHITEM_EMPTY(hi)) + { + --todo; + if (tv_equal(&HI2DI(hi)->di_tv, needle, ic, FALSE)) + ++n; + } + } + + return n; +} + +/* + * extend() a Dict. Append Dict argvars[1] to Dict argvars[0] and return the + * resulting Dict in "rettv". "is_new" is TRUE for extendnew(). + */ + void +dict_extend_func( + typval_T *argvars, + type_T *type, + char *func_name, + char_u *arg_errmsg, + int is_new, + typval_T *rettv) +{ + dict_T *d1, *d2; + char_u *action; + int i; + + d1 = argvars[0].vval.v_dict; + if (d1 == NULL) + { + emsg(_(e_cannot_extend_null_dict)); + return; + } + d2 = argvars[1].vval.v_dict; + if (d2 == NULL) + return; + + if (!is_new && value_check_lock(d1->dv_lock, arg_errmsg, TRUE)) + return; + + if (is_new) + { + d1 = dict_copy(d1, FALSE, TRUE, get_copyID()); + if (d1 == NULL) + return; + } + + // Check the third argument. + if (argvars[2].v_type != VAR_UNKNOWN) + { + static char *(av[]) = {"keep", "force", "error"}; + + action = tv_get_string_chk(&argvars[2]); + if (action == NULL) + return; + for (i = 0; i < 3; ++i) + if (STRCMP(action, av[i]) == 0) + break; + if (i == 3) + { + semsg(_(e_invalid_argument_str), action); + return; + } + } + else + action = (char_u *)"force"; + + if (type != NULL && check_typval_arg_type(type, &argvars[1], + func_name, 2) == FAIL) + return; + dict_extend(d1, d2, action, func_name); + + if (is_new) + { + rettv->v_type = VAR_DICT; + rettv->vval.v_dict = d1; + rettv->v_lock = FALSE; + } + else + copy_tv(&argvars[0], rettv); +} + +/* + * Implementation of map() and filter() for a Dict. Apply "expr" to every + * item in Dict "d" and return the result in "rettv". + */ + void +dict_filter_map( + dict_T *d, + filtermap_T filtermap, + type_T *argtype, + char *func_name, + char_u *arg_errmsg, + typval_T *expr, + typval_T *rettv) +{ + dict_T *d_ret = NULL; + hashtab_T *ht; + hashitem_T *hi; + dictitem_T *di; + int todo; + int rem; + typval_T newtv; + funccall_T *fc; + + if (filtermap == FILTERMAP_MAPNEW) + { + rettv->v_type = VAR_DICT; + rettv->vval.v_dict = NULL; + } + if (d == NULL + || (filtermap == FILTERMAP_FILTER + && value_check_lock(d->dv_lock, arg_errmsg, TRUE))) + return; + + if (filtermap == FILTERMAP_MAPNEW) + { + if (rettv_dict_alloc(rettv) == FAIL) + return; + d_ret = rettv->vval.v_dict; + } + + // Create one funccall_T for all eval_expr_typval() calls. + fc = eval_expr_get_funccal(expr, &newtv); + + int prev_lock = d->dv_lock; + if (d->dv_lock == 0) + d->dv_lock = VAR_LOCKED; + ht = &d->dv_hashtab; + hash_lock(ht); + todo = (int)ht->ht_used; + FOR_ALL_HASHTAB_ITEMS(ht, hi, todo) + { + if (!HASHITEM_EMPTY(hi)) + { + int r; + + --todo; + di = HI2DI(hi); + if (filtermap == FILTERMAP_MAP + && (value_check_lock(di->di_tv.v_lock, + arg_errmsg, TRUE) + || var_check_ro(di->di_flags, + arg_errmsg, TRUE))) + break; + set_vim_var_string(VV_KEY, di->di_key, -1); + newtv.v_type = VAR_UNKNOWN; + r = filter_map_one(&di->di_tv, expr, filtermap, fc, &newtv, &rem); + clear_tv(get_vim_var_tv(VV_KEY)); + if (r == FAIL || did_emsg) + { + clear_tv(&newtv); + break; + } + if (filtermap == FILTERMAP_MAP) + { + if (argtype != NULL && check_typval_arg_type( + argtype->tt_member, &newtv, func_name, 0) == FAIL) + { + clear_tv(&newtv); + break; + } + // map(): replace the dict item value + clear_tv(&di->di_tv); + newtv.v_lock = 0; + di->di_tv = newtv; + } + else if (filtermap == FILTERMAP_MAPNEW) + { + // mapnew(): add the item value to the new dict + r = dict_add_tv(d_ret, (char *)di->di_key, &newtv); + clear_tv(&newtv); + if (r == FAIL) + break; + } + else if (filtermap == FILTERMAP_FILTER && rem) + { + // filter(false): remove the item from the dict + if (var_check_fixed(di->di_flags, arg_errmsg, TRUE) + || var_check_ro(di->di_flags, arg_errmsg, TRUE)) + break; + dictitem_remove(d, di, "filter"); + } + } + } + hash_unlock(ht); + d->dv_lock = prev_lock; + if (fc != NULL) + remove_funccal(); +} + +/* + * "remove({dict})" function + */ + void +dict_remove(typval_T *argvars, typval_T *rettv, char_u *arg_errmsg) +{ + dict_T *d; + char_u *key; + dictitem_T *di; + + if (argvars[2].v_type != VAR_UNKNOWN) + { + semsg(_(e_too_many_arguments_for_function_str), "remove()"); + return; + } + + d = argvars[0].vval.v_dict; + if (d == NULL || value_check_lock(d->dv_lock, arg_errmsg, TRUE)) + return; + + key = tv_get_string_chk(&argvars[1]); + if (key == NULL) + return; + + di = dict_find(d, key, -1); + if (di == NULL) + { + semsg(_(e_key_not_present_in_dictionary_str), key); + return; + } + + if (var_check_fixed(di->di_flags, arg_errmsg, TRUE) + || var_check_ro(di->di_flags, arg_errmsg, TRUE)) + return; + + *rettv = di->di_tv; + init_tv(&di->di_tv); + dictitem_remove(d, di, "remove()"); +} + +typedef enum { + DICT2LIST_KEYS, + DICT2LIST_VALUES, + DICT2LIST_ITEMS, +} dict2list_T; + +/* + * Turn a dict into a list. + */ + static void +dict2list(typval_T *argvars, typval_T *rettv, dict2list_T what) +{ + list_T *l2; + dictitem_T *di; + hashitem_T *hi; + listitem_T *li; + dict_T *d; + int todo; + + if (rettv_list_alloc(rettv) == FAIL) + return; + + if ((what == DICT2LIST_ITEMS + ? check_for_string_or_list_or_dict_arg(argvars, 0) + : check_for_dict_arg(argvars, 0)) == FAIL) + return; + + d = argvars[0].vval.v_dict; + if (d == NULL) + // NULL dict behaves like an empty dict + return; + + todo = (int)d->dv_hashtab.ht_used; + FOR_ALL_HASHTAB_ITEMS(&d->dv_hashtab, hi, todo) + { + if (!HASHITEM_EMPTY(hi)) + { + --todo; + di = HI2DI(hi); + + li = listitem_alloc(); + if (li == NULL) + break; + list_append(rettv->vval.v_list, li); + + if (what == DICT2LIST_KEYS) + { + // keys() + li->li_tv.v_type = VAR_STRING; + li->li_tv.v_lock = 0; + li->li_tv.vval.v_string = vim_strsave(di->di_key); + } + else if (what == DICT2LIST_VALUES) + { + // values() + copy_tv(&di->di_tv, &li->li_tv); + } + else + { + // items() + l2 = list_alloc(); + li->li_tv.v_type = VAR_LIST; + li->li_tv.v_lock = 0; + li->li_tv.vval.v_list = l2; + if (l2 == NULL) + break; + ++l2->lv_refcount; + + if (list_append_string(l2, di->di_key, -1) == FAIL + || list_append_tv(l2, &di->di_tv) == FAIL) + break; + } + } + } +} + +/* + * "items(dict)" function + */ + void +f_items(typval_T *argvars, typval_T *rettv) +{ + if (argvars[0].v_type == VAR_STRING) + string2items(argvars, rettv); + else if (argvars[0].v_type == VAR_LIST) + list2items(argvars, rettv); + else + dict2list(argvars, rettv, DICT2LIST_ITEMS); +} + +/* + * "keys()" function + */ + void +f_keys(typval_T *argvars, typval_T *rettv) +{ + dict2list(argvars, rettv, DICT2LIST_KEYS); +} + +/* + * "values(dict)" function + */ + void +f_values(typval_T *argvars, typval_T *rettv) +{ + dict2list(argvars, rettv, DICT2LIST_VALUES); +} + +/* + * Make each item in the dict readonly (not the value of the item). + */ + void +dict_set_items_ro(dict_T *di) +{ + int todo = (int)di->dv_hashtab.ht_used; + hashitem_T *hi; + + // Set readonly + FOR_ALL_HASHTAB_ITEMS(&di->dv_hashtab, hi, todo) + { + if (HASHITEM_EMPTY(hi)) + continue; + --todo; + HI2DI(hi)->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX; + } +} + +/* + * "has_key()" function + */ + void +f_has_key(typval_T *argvars, typval_T *rettv) +{ + if (in_vim9script() + && (check_for_dict_arg(argvars, 0) == FAIL + || check_for_string_or_number_arg(argvars, 1) == FAIL)) + return; + + if (check_for_dict_arg(argvars, 0) == FAIL) + return; + + if (argvars[0].vval.v_dict == NULL) + return; + + rettv->vval.v_number = dict_has_key(argvars[0].vval.v_dict, + (char *)tv_get_string(&argvars[1])); +} + +#endif // defined(FEAT_EVAL) diff --git a/userland/apps/vim/src/diff.c b/userland/apps/vim/src/diff.c new file mode 100644 index 00000000..15887040 --- /dev/null +++ b/userland/apps/vim/src/diff.c @@ -0,0 +1,3442 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * diff.c: code for diff'ing two, three or four buffers. + * + * There are three ways to diff: + * - Shell out to an external diff program, using files. + * - Use the compiled-in xdiff library. + * - Let 'diffexpr' do the work, using files. + */ + +#include "vim.h" +#include "xdiff/xdiff.h" + +#if defined(FEAT_DIFF) || defined(PROTO) + +static int diff_busy = FALSE; // using diff structs, don't change them +static int diff_need_update = FALSE; // ex_diffupdate needs to be called + +// flags obtained from the 'diffopt' option +#define DIFF_FILLER 0x001 // display filler lines +#define DIFF_IBLANK 0x002 // ignore empty lines +#define DIFF_ICASE 0x004 // ignore case +#define DIFF_IWHITE 0x008 // ignore change in white space +#define DIFF_IWHITEALL 0x010 // ignore all white space changes +#define DIFF_IWHITEEOL 0x020 // ignore change in white space at EOL +#define DIFF_HORIZONTAL 0x040 // horizontal splits +#define DIFF_VERTICAL 0x080 // vertical splits +#define DIFF_HIDDEN_OFF 0x100 // diffoff when hidden +#define DIFF_INTERNAL 0x200 // use internal xdiff algorithm +#define DIFF_CLOSE_OFF 0x400 // diffoff when closing window +#define DIFF_FOLLOWWRAP 0x800 // follow the wrap option +#define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL) +static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF; + +static long diff_algorithm = 0; + +#define LBUFLEN 50 // length of line in diff file + +static int diff_a_works = MAYBE; // TRUE when "diff -a" works, FALSE when it + // doesn't work, MAYBE when not checked yet +#if defined(MSWIN) +static int diff_bin_works = MAYBE; // TRUE when "diff --binary" works, FALSE + // when it doesn't work, MAYBE when not + // checked yet +#endif + +// used for diff input +typedef struct { + char_u *din_fname; // used for external diff + mmfile_t din_mmfile; // used for internal diff +} diffin_T; + +// used for diff result +typedef struct { + char_u *dout_fname; // used for external diff + garray_T dout_ga; // used for internal diff +} diffout_T; + +// used for recording hunks from xdiff +typedef struct { + linenr_T lnum_orig; + long count_orig; + linenr_T lnum_new; + long count_new; +} diffhunk_T; + +// two diff inputs and one result +typedef struct { + diffin_T dio_orig; // original file input + diffin_T dio_new; // new file input + diffout_T dio_diff; // diff result + int dio_internal; // using internal diff +} diffio_T; + +static int diff_buf_idx(buf_T *buf); +static int diff_buf_idx_tp(buf_T *buf, tabpage_T *tp); +static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T line2, long amount, long amount_after); +static void diff_check_unchanged(tabpage_T *tp, diff_T *dp); +static int diff_check_sanity(tabpage_T *tp, diff_T *dp); +static int check_external_diff(diffio_T *diffio); +static int diff_file(diffio_T *diffio); +static int diff_equal_entry(diff_T *dp, int idx1, int idx2); +static int diff_cmp(char_u *s1, char_u *s2); +#ifdef FEAT_FOLDING +static void diff_fold_update(diff_T *dp, int skip_idx); +#endif +static void diff_read(int idx_orig, int idx_new, diffio_T *dio); +static void diff_copy_entry(diff_T *dprev, diff_T *dp, int idx_orig, int idx_new); +static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp); +static int parse_diff_ed(char_u *line, diffhunk_T *hunk); +static int parse_diff_unified(char_u *line, diffhunk_T *hunk); +static int xdiff_out(long start_a, long count_a, long start_b, long count_b, void *priv); + +#define FOR_ALL_DIFFBLOCKS_IN_TAB(tp, dp) \ + for ((dp) = (tp)->tp_first_diff; (dp) != NULL; (dp) = (dp)->df_next) + +/* + * Called when deleting or unloading a buffer: No longer make a diff with it. + */ + void +diff_buf_delete(buf_T *buf) +{ + int i; + tabpage_T *tp; + + FOR_ALL_TABPAGES(tp) + { + i = diff_buf_idx_tp(buf, tp); + if (i != DB_COUNT) + { + tp->tp_diffbuf[i] = NULL; + tp->tp_diff_invalid = TRUE; + if (tp == curtab) + { + // don't redraw right away, more might change or buffer state + // is invalid right now + need_diff_redraw = TRUE; + redraw_later(UPD_VALID); + } + } + } +} + +/* + * Check if the current buffer should be added to or removed from the list of + * diff buffers. + */ + void +diff_buf_adjust(win_T *win) +{ + win_T *wp; + int i; + + if (!win->w_p_diff) + { + // When there is no window showing a diff for this buffer, remove + // it from the diffs. + FOR_ALL_WINDOWS(wp) + if (wp->w_buffer == win->w_buffer && wp->w_p_diff) + break; + if (wp == NULL) + { + i = diff_buf_idx(win->w_buffer); + if (i != DB_COUNT) + { + curtab->tp_diffbuf[i] = NULL; + curtab->tp_diff_invalid = TRUE; + diff_redraw(TRUE); + } + } + } + else + diff_buf_add(win->w_buffer); +} + +/* + * Add a buffer to make diffs for. + * Call this when a new buffer is being edited in the current window where + * 'diff' is set. + * Marks the current buffer as being part of the diff and requiring updating. + * This must be done before any autocmd, because a command may use info + * about the screen contents. + */ + void +diff_buf_add(buf_T *buf) +{ + int i; + + if (diff_buf_idx(buf) != DB_COUNT) + return; // It's already there. + + for (i = 0; i < DB_COUNT; ++i) + if (curtab->tp_diffbuf[i] == NULL) + { + curtab->tp_diffbuf[i] = buf; + curtab->tp_diff_invalid = TRUE; + diff_redraw(TRUE); + return; + } + + semsg(_(e_cannot_diff_more_than_nr_buffers), DB_COUNT); +} + +/* + * Remove all buffers to make diffs for. + */ + static void +diff_buf_clear(void) +{ + int i; + + for (i = 0; i < DB_COUNT; ++i) + if (curtab->tp_diffbuf[i] != NULL) + { + curtab->tp_diffbuf[i] = NULL; + curtab->tp_diff_invalid = TRUE; + diff_redraw(TRUE); + } +} + +/* + * Find buffer "buf" in the list of diff buffers for the current tab page. + * Return its index or DB_COUNT if not found. + */ + static int +diff_buf_idx(buf_T *buf) +{ + int idx; + + for (idx = 0; idx < DB_COUNT; ++idx) + if (curtab->tp_diffbuf[idx] == buf) + break; + return idx; +} + +/* + * Find buffer "buf" in the list of diff buffers for tab page "tp". + * Return its index or DB_COUNT if not found. + */ + static int +diff_buf_idx_tp(buf_T *buf, tabpage_T *tp) +{ + int idx; + + for (idx = 0; idx < DB_COUNT; ++idx) + if (tp->tp_diffbuf[idx] == buf) + break; + return idx; +} + +/* + * Mark the diff info involving buffer "buf" as invalid, it will be updated + * when info is requested. + */ + void +diff_invalidate(buf_T *buf) +{ + tabpage_T *tp; + int i; + + FOR_ALL_TABPAGES(tp) + { + i = diff_buf_idx_tp(buf, tp); + if (i != DB_COUNT) + { + tp->tp_diff_invalid = TRUE; + if (tp == curtab) + diff_redraw(TRUE); + } + } +} + +/* + * Called by mark_adjust(): update line numbers in "curbuf". + */ + void +diff_mark_adjust( + linenr_T line1, + linenr_T line2, + long amount, + long amount_after) +{ + int idx; + tabpage_T *tp; + + // Handle all tab pages that use the current buffer in a diff. + FOR_ALL_TABPAGES(tp) + { + idx = diff_buf_idx_tp(curbuf, tp); + if (idx != DB_COUNT) + diff_mark_adjust_tp(tp, idx, line1, line2, amount, amount_after); + } +} + +/* + * Update line numbers in tab page "tp" for "curbuf" with index "idx". + * This attempts to update the changes as much as possible: + * When inserting/deleting lines outside of existing change blocks, create a + * new change block and update the line numbers in following blocks. + * When inserting/deleting lines in existing change blocks, update them. + */ + static void +diff_mark_adjust_tp( + tabpage_T *tp, + int idx, + linenr_T line1, + linenr_T line2, + long amount, + long amount_after) +{ + diff_T *dp; + diff_T *dprev; + diff_T *dnext; + int i; + int inserted, deleted; + int n, off; + linenr_T last; + linenr_T lnum_deleted = line1; // lnum of remaining deletion + int check_unchanged; + + if (diff_internal()) + { + // Will update diffs before redrawing. Set _invalid to update the + // diffs themselves, set _update to also update folds properly just + // before redrawing. + // Do update marks here, it is needed for :%diffput. + tp->tp_diff_invalid = TRUE; + tp->tp_diff_update = TRUE; + } + + if (line2 == MAXLNUM) + { + // mark_adjust(99, MAXLNUM, 9, 0): insert lines + inserted = amount; + deleted = 0; + } + else if (amount_after > 0) + { + // mark_adjust(99, 98, MAXLNUM, 9): a change that inserts lines + inserted = amount_after; + deleted = 0; + } + else + { + // mark_adjust(98, 99, MAXLNUM, -2): delete lines + inserted = 0; + deleted = -amount_after; + } + + dprev = NULL; + dp = tp->tp_first_diff; + for (;;) + { + // If the change is after the previous diff block and before the next + // diff block, thus not touching an existing change, create a new diff + // block. Don't do this when ex_diffgetput() is busy. + if ((dp == NULL || dp->df_lnum[idx] - 1 > line2 + || (line2 == MAXLNUM && dp->df_lnum[idx] > line1)) + && (dprev == NULL + || dprev->df_lnum[idx] + dprev->df_count[idx] < line1) + && !diff_busy) + { + dnext = diff_alloc_new(tp, dprev, dp); + if (dnext == NULL) + return; + + dnext->df_lnum[idx] = line1; + dnext->df_count[idx] = inserted; + for (i = 0; i < DB_COUNT; ++i) + if (tp->tp_diffbuf[i] != NULL && i != idx) + { + if (dprev == NULL) + dnext->df_lnum[i] = line1; + else + dnext->df_lnum[i] = line1 + + (dprev->df_lnum[i] + dprev->df_count[i]) + - (dprev->df_lnum[idx] + dprev->df_count[idx]); + dnext->df_count[i] = deleted; + } + } + + // if at end of the list, quit + if (dp == NULL) + break; + + /* + * Check for these situations: + * 1 2 3 + * 1 2 3 + * line1 2 3 4 5 + * 2 3 4 5 + * 2 3 4 5 + * line2 2 3 4 5 + * 3 5 6 + * 3 5 6 + */ + // compute last line of this change + last = dp->df_lnum[idx] + dp->df_count[idx] - 1; + + // 1. change completely above line1: nothing to do + if (last >= line1 - 1) + { + // 6. change below line2: only adjust for amount_after; also when + // "deleted" became zero when deleted all lines between two diffs + if (dp->df_lnum[idx] - (deleted + inserted != 0) > line2) + { + if (amount_after == 0) + break; // nothing left to change + dp->df_lnum[idx] += amount_after; + } + else + { + check_unchanged = FALSE; + + // 2. 3. 4. 5.: inserted/deleted lines touching this diff. + if (deleted > 0) + { + off = 0; + if (dp->df_lnum[idx] >= line1) + { + if (last <= line2) + { + // 4. delete all lines of diff + if (dp->df_next != NULL + && dp->df_next->df_lnum[idx] - 1 <= line2) + { + // delete continues in next diff, only do + // lines until that one + n = dp->df_next->df_lnum[idx] - lnum_deleted; + deleted -= n; + n -= dp->df_count[idx]; + lnum_deleted = dp->df_next->df_lnum[idx]; + } + else + n = deleted - dp->df_count[idx]; + dp->df_count[idx] = 0; + } + else + { + // 5. delete lines at or just before top of diff + off = dp->df_lnum[idx] - lnum_deleted; + n = off; + dp->df_count[idx] -= line2 - dp->df_lnum[idx] + 1; + check_unchanged = TRUE; + } + dp->df_lnum[idx] = line1; + } + else + { + if (last < line2) + { + // 2. delete at end of diff + dp->df_count[idx] -= last - lnum_deleted + 1; + if (dp->df_next != NULL + && dp->df_next->df_lnum[idx] - 1 <= line2) + { + // delete continues in next diff, only do + // lines until that one + n = dp->df_next->df_lnum[idx] - 1 - last; + deleted -= dp->df_next->df_lnum[idx] + - lnum_deleted; + lnum_deleted = dp->df_next->df_lnum[idx]; + } + else + n = line2 - last; + check_unchanged = TRUE; + } + else + { + // 3. delete lines inside the diff + n = 0; + dp->df_count[idx] -= deleted; + } + } + + for (i = 0; i < DB_COUNT; ++i) + if (tp->tp_diffbuf[i] != NULL && i != idx) + { + if (dp->df_lnum[i] > off) + dp->df_lnum[i] -= off; + else + dp->df_lnum[i] = 1; + dp->df_count[i] += n; + } + } + else + { + if (dp->df_lnum[idx] <= line1) + { + // inserted lines somewhere in this diff + dp->df_count[idx] += inserted; + check_unchanged = TRUE; + } + else + // inserted lines somewhere above this diff + dp->df_lnum[idx] += inserted; + } + + if (check_unchanged) + // Check if inserted lines are equal, may reduce the + // size of the diff. TODO: also check for equal lines + // in the middle and perhaps split the block. + diff_check_unchanged(tp, dp); + } + } + + // check if this block touches the previous one, may merge them. + if (dprev != NULL && dprev->df_lnum[idx] + dprev->df_count[idx] + == dp->df_lnum[idx]) + { + for (i = 0; i < DB_COUNT; ++i) + if (tp->tp_diffbuf[i] != NULL) + dprev->df_count[i] += dp->df_count[i]; + dprev->df_next = dp->df_next; + vim_free(dp); + dp = dprev->df_next; + } + else + { + // Advance to next entry. + dprev = dp; + dp = dp->df_next; + } + } + + dprev = NULL; + dp = tp->tp_first_diff; + while (dp != NULL) + { + // All counts are zero, remove this entry. + for (i = 0; i < DB_COUNT; ++i) + if (tp->tp_diffbuf[i] != NULL && dp->df_count[i] != 0) + break; + if (i == DB_COUNT) + { + dnext = dp->df_next; + vim_free(dp); + dp = dnext; + if (dprev == NULL) + tp->tp_first_diff = dnext; + else + dprev->df_next = dnext; + } + else + { + // Advance to next entry. + dprev = dp; + dp = dp->df_next; + } + + } + + if (tp == curtab) + { + // Don't redraw right away, this updates the diffs, which can be slow. + need_diff_redraw = TRUE; + + // Need to recompute the scroll binding, may remove or add filler + // lines (e.g., when adding lines above w_topline). But it's slow when + // making many changes, postpone until redrawing. + diff_need_scrollbind = TRUE; + } +} + +/* + * Allocate a new diff block and link it between "dprev" and "dp". + */ + static diff_T * +diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp) +{ + diff_T *dnew; + + dnew = ALLOC_ONE(diff_T); + if (dnew == NULL) + return NULL; + + dnew->df_next = dp; + if (dprev == NULL) + tp->tp_first_diff = dnew; + else + dprev->df_next = dnew; + return dnew; +} + +/* + * Check if the diff block "dp" can be made smaller for lines at the start and + * end that are equal. Called after inserting lines. + * This may result in a change where all buffers have zero lines, the caller + * must take care of removing it. + */ + static void +diff_check_unchanged(tabpage_T *tp, diff_T *dp) +{ + int i_org; + int i_new; + int off_org, off_new; + char_u *line_org; + int dir = FORWARD; + + // Find the first buffers, use it as the original, compare the other + // buffer lines against this one. + for (i_org = 0; i_org < DB_COUNT; ++i_org) + if (tp->tp_diffbuf[i_org] != NULL) + break; + if (i_org == DB_COUNT) // safety check + return; + + if (diff_check_sanity(tp, dp) == FAIL) + return; + + // First check lines at the top, then at the bottom. + off_org = 0; + off_new = 0; + for (;;) + { + // Repeat until a line is found which is different or the number of + // lines has become zero. + while (dp->df_count[i_org] > 0) + { + // Copy the line, the next ml_get() will invalidate it. + if (dir == BACKWARD) + off_org = dp->df_count[i_org] - 1; + line_org = vim_strsave(ml_get_buf(tp->tp_diffbuf[i_org], + dp->df_lnum[i_org] + off_org, FALSE)); + if (line_org == NULL) + return; + for (i_new = i_org + 1; i_new < DB_COUNT; ++i_new) + { + if (tp->tp_diffbuf[i_new] == NULL) + continue; + if (dir == BACKWARD) + off_new = dp->df_count[i_new] - 1; + // if other buffer doesn't have this line, it was inserted + if (off_new < 0 || off_new >= dp->df_count[i_new]) + break; + if (diff_cmp(line_org, ml_get_buf(tp->tp_diffbuf[i_new], + dp->df_lnum[i_new] + off_new, FALSE)) != 0) + break; + } + vim_free(line_org); + + // Stop when a line isn't equal in all diff buffers. + if (i_new != DB_COUNT) + break; + + // Line matched in all buffers, remove it from the diff. + for (i_new = i_org; i_new < DB_COUNT; ++i_new) + if (tp->tp_diffbuf[i_new] != NULL) + { + if (dir == FORWARD) + ++dp->df_lnum[i_new]; + --dp->df_count[i_new]; + } + } + if (dir == BACKWARD) + break; + dir = BACKWARD; + } +} + +/* + * Check if a diff block doesn't contain invalid line numbers. + * This can happen when the diff program returns invalid results. + */ + static int +diff_check_sanity(tabpage_T *tp, diff_T *dp) +{ + int i; + + for (i = 0; i < DB_COUNT; ++i) + if (tp->tp_diffbuf[i] != NULL) + if (dp->df_lnum[i] + dp->df_count[i] - 1 + > tp->tp_diffbuf[i]->b_ml.ml_line_count) + return FAIL; + return OK; +} + +/* + * Mark all diff buffers in the current tab page for redraw. + */ + void +diff_redraw( + int dofold) // also recompute the folds +{ + win_T *wp; + win_T *wp_other = NULL; + int used_max_fill_other = FALSE; + int used_max_fill_curwin = FALSE; + int n; + + need_diff_redraw = FALSE; + FOR_ALL_WINDOWS(wp) + { + // when closing windows or wiping buffers skip invalid window + if (!wp->w_p_diff || !buf_valid(wp->w_buffer)) + continue; + + redraw_win_later(wp, UPD_SOME_VALID); + if (wp != curwin) + wp_other = wp; +#ifdef FEAT_FOLDING + if (dofold && foldmethodIsDiff(wp)) + foldUpdateAll(wp); +#endif + // A change may have made filler lines invalid, need to take care of + // that for other windows. + n = diff_check(wp, wp->w_topline); + if ((wp != curwin && wp->w_topfill > 0) || n > 0) + { + if (wp->w_topfill > n) + wp->w_topfill = (n < 0 ? 0 : n); + else if (n > 0 && n > wp->w_topfill) + { + wp->w_topfill = n; + if (wp == curwin) + used_max_fill_curwin = TRUE; + else if (wp_other != NULL) + used_max_fill_other = TRUE; + } + check_topfill(wp, FALSE); + } + } + + if (wp_other != NULL && curwin->w_p_scb) + { + if (used_max_fill_curwin) + // The current window was set to use the maximum number of filler + // lines, may need to reduce them. + diff_set_topline(wp_other, curwin); + else if (used_max_fill_other) + // The other window was set to use the maximum number of filler + // lines, may need to reduce them. + diff_set_topline(curwin, wp_other); + } +} + + static void +clear_diffin(diffin_T *din) +{ + if (din->din_fname == NULL) + VIM_CLEAR(din->din_mmfile.ptr); + else + mch_remove(din->din_fname); +} + + static void +clear_diffout(diffout_T *dout) +{ + if (dout->dout_fname == NULL) + ga_clear_strings(&dout->dout_ga); + else + mch_remove(dout->dout_fname); +} + +/* + * Write buffer "buf" to a memory buffer. + * Return FAIL for failure. + */ + static int +diff_write_buffer(buf_T *buf, diffin_T *din) +{ + linenr_T lnum; + char_u *s; + long len = 0; + char_u *ptr; + + // xdiff requires one big block of memory with all the text. + for (lnum = 1; lnum <= buf->b_ml.ml_line_count; ++lnum) + len += (long)STRLEN(ml_get_buf(buf, lnum, FALSE)) + 1; + ptr = alloc(len); + if (ptr == NULL) + { + // Allocating memory failed. This can happen, because we try to read + // the whole buffer text into memory. Set the failed flag, the diff + // will be retried with external diff. The flag is never reset. + buf->b_diff_failed = TRUE; + if (p_verbose > 0) + { + verbose_enter(); + smsg(_("Not enough memory to use internal diff for buffer \"%s\""), + buf->b_fname); + verbose_leave(); + } + return FAIL; + } + din->din_mmfile.ptr = (char *)ptr; + din->din_mmfile.size = len; + + len = 0; + for (lnum = 1; lnum <= buf->b_ml.ml_line_count; ++lnum) + { + for (s = ml_get_buf(buf, lnum, FALSE); *s != NUL; ) + { + if (diff_flags & DIFF_ICASE) + { + int c; + int orig_len; + char_u cbuf[MB_MAXBYTES + 1]; + + if (*s == NL) + c = NUL; + else + { + // xdiff doesn't support ignoring case, fold-case the text. + c = PTR2CHAR(s); + c = MB_CASEFOLD(c); + } + orig_len = mb_ptr2len(s); + if (mb_char2bytes(c, cbuf) != orig_len) + // TODO: handle byte length difference + mch_memmove(ptr + len, s, orig_len); + else + mch_memmove(ptr + len, cbuf, orig_len); + + s += orig_len; + len += orig_len; + } + else + { + ptr[len++] = *s == NL ? NUL : *s; + s++; + } + } + ptr[len++] = NL; + } + return OK; +} + +/* + * Write buffer "buf" to file or memory buffer. + * Return FAIL for failure. + */ + static int +diff_write(buf_T *buf, diffin_T *din) +{ + int r; + char_u *save_ff; + int save_cmod_flags; + + if (din->din_fname == NULL) + return diff_write_buffer(buf, din); + + // Always use 'fileformat' set to "unix". + save_ff = buf->b_p_ff; + buf->b_p_ff = vim_strsave((char_u *)FF_UNIX); + save_cmod_flags = cmdmod.cmod_flags; + // Writing the buffer is an implementation detail of performing the diff, + // so it shouldn't update the '[ and '] marks. + cmdmod.cmod_flags |= CMOD_LOCKMARKS; + r = buf_write(buf, din->din_fname, NULL, + (linenr_T)1, buf->b_ml.ml_line_count, + NULL, FALSE, FALSE, FALSE, TRUE); + cmdmod.cmod_flags = save_cmod_flags; + free_string_option(buf->b_p_ff); + buf->b_p_ff = save_ff; + return r; +} + +/* + * Update the diffs for all buffers involved. + */ + static void +diff_try_update( + diffio_T *dio, + int idx_orig, + exarg_T *eap) // "eap" can be NULL +{ + buf_T *buf; + int idx_new; + + if (dio->dio_internal) + { + ga_init2(&dio->dio_diff.dout_ga, sizeof(char *), 1000); + } + else + { + // We need three temp file names. + dio->dio_orig.din_fname = vim_tempname('o', TRUE); + dio->dio_new.din_fname = vim_tempname('n', TRUE); + dio->dio_diff.dout_fname = vim_tempname('d', TRUE); + if (dio->dio_orig.din_fname == NULL + || dio->dio_new.din_fname == NULL + || dio->dio_diff.dout_fname == NULL) + goto theend; + } + + // Check external diff is actually working. + if (!dio->dio_internal && check_external_diff(dio) == FAIL) + goto theend; + + // :diffupdate! + if (eap != NULL && eap->forceit) + for (idx_new = idx_orig; idx_new < DB_COUNT; ++idx_new) + { + buf = curtab->tp_diffbuf[idx_new]; + if (buf_valid(buf)) + buf_check_timestamp(buf, FALSE); + } + + // Write the first buffer to a tempfile or mmfile_t. + buf = curtab->tp_diffbuf[idx_orig]; + if (diff_write(buf, &dio->dio_orig) == FAIL) + goto theend; + + // Make a difference between the first buffer and every other. + for (idx_new = idx_orig + 1; idx_new < DB_COUNT; ++idx_new) + { + buf = curtab->tp_diffbuf[idx_new]; + if (buf == NULL || buf->b_ml.ml_mfp == NULL) + continue; // skip buffer that isn't loaded + + // Write the other buffer and diff with the first one. + if (diff_write(buf, &dio->dio_new) == FAIL) + continue; + if (diff_file(dio) == FAIL) + continue; + + // Read the diff output and add each entry to the diff list. + diff_read(idx_orig, idx_new, dio); + + clear_diffin(&dio->dio_new); + clear_diffout(&dio->dio_diff); + } + clear_diffin(&dio->dio_orig); + +theend: + vim_free(dio->dio_orig.din_fname); + vim_free(dio->dio_new.din_fname); + vim_free(dio->dio_diff.dout_fname); +} + +/* + * Return TRUE if the options are set to use the internal diff library. + * Note that if the internal diff failed for one of the buffers, the external + * diff will be used anyway. + */ + int +diff_internal(void) +{ + return (diff_flags & DIFF_INTERNAL) != 0 +#ifdef FEAT_EVAL + && *p_dex == NUL +#endif + ; +} + +/* + * Return TRUE if the internal diff failed for one of the diff buffers. + */ + static int +diff_internal_failed(void) +{ + int idx; + + // Only need to do something when there is another buffer. + for (idx = 0; idx < DB_COUNT; ++idx) + if (curtab->tp_diffbuf[idx] != NULL + && curtab->tp_diffbuf[idx]->b_diff_failed) + return TRUE; + return FALSE; +} + +/* + * Completely update the diffs for the buffers involved. + * When using the external "diff" command the buffers are written to a file, + * also for unmodified buffers (the file could have been produced by + * autocommands, e.g. the netrw plugin). + */ + void +ex_diffupdate(exarg_T *eap) // "eap" can be NULL +{ + int idx_orig; + int idx_new; + diffio_T diffio; + int had_diffs = curtab->tp_first_diff != NULL; + + if (diff_busy) + { + diff_need_update = TRUE; + return; + } + + // Delete all diffblocks. + diff_clear(curtab); + curtab->tp_diff_invalid = FALSE; + + // Use the first buffer as the original text. + for (idx_orig = 0; idx_orig < DB_COUNT; ++idx_orig) + if (curtab->tp_diffbuf[idx_orig] != NULL) + break; + if (idx_orig == DB_COUNT) + goto theend; + + // Only need to do something when there is another buffer. + for (idx_new = idx_orig + 1; idx_new < DB_COUNT; ++idx_new) + if (curtab->tp_diffbuf[idx_new] != NULL) + break; + if (idx_new == DB_COUNT) + goto theend; + + // Only use the internal method if it did not fail for one of the buffers. + CLEAR_FIELD(diffio); + diffio.dio_internal = diff_internal() && !diff_internal_failed(); + + diff_try_update(&diffio, idx_orig, eap); + if (diffio.dio_internal && diff_internal_failed()) + { + // Internal diff failed, use external diff instead. + CLEAR_FIELD(diffio); + diff_try_update(&diffio, idx_orig, eap); + } + + // force updating cursor position on screen + curwin->w_valid_cursor.lnum = 0; + +theend: + // A redraw is needed if there were diffs and they were cleared, or there + // are diffs now, which means they got updated. + if (had_diffs || curtab->tp_first_diff != NULL) + { + diff_redraw(TRUE); + apply_autocmds(EVENT_DIFFUPDATED, NULL, NULL, FALSE, curbuf); + } +} + +/* + * Do a quick test if "diff" really works. Otherwise it looks like there + * are no differences. Can't use the return value, it's non-zero when + * there are differences. + */ + static int +check_external_diff(diffio_T *diffio) +{ + FILE *fd; + int ok; + int io_error = FALSE; + + // May try twice, first with "-a" and then without. + for (;;) + { + ok = FALSE; + fd = mch_fopen((char *)diffio->dio_orig.din_fname, "w"); + if (fd == NULL) + io_error = TRUE; + else + { + if (fwrite("line1\n", (size_t)6, (size_t)1, fd) != 1) + io_error = TRUE; + fclose(fd); + fd = mch_fopen((char *)diffio->dio_new.din_fname, "w"); + if (fd == NULL) + io_error = TRUE; + else + { + if (fwrite("line2\n", (size_t)6, (size_t)1, fd) != 1) + io_error = TRUE; + fclose(fd); + fd = NULL; + if (diff_file(diffio) == OK) + fd = mch_fopen((char *)diffio->dio_diff.dout_fname, "r"); + if (fd == NULL) + io_error = TRUE; + else + { + char_u linebuf[LBUFLEN]; + + for (;;) + { + // For normal diff there must be a line that contains + // "1c1". For unified diff "@@ -1 +1 @@". + if (vim_fgets(linebuf, LBUFLEN, fd)) + break; + if (STRNCMP(linebuf, "1c1", 3) == 0 + || STRNCMP(linebuf, "@@ -1 +1 @@", 11) == 0) + ok = TRUE; + } + fclose(fd); + } + mch_remove(diffio->dio_diff.dout_fname); + mch_remove(diffio->dio_new.din_fname); + } + mch_remove(diffio->dio_orig.din_fname); + } + +#ifdef FEAT_EVAL + // When using 'diffexpr' break here. + if (*p_dex != NUL) + break; +#endif + +#if defined(MSWIN) + // If the "-a" argument works, also check if "--binary" works. + if (ok && diff_a_works == MAYBE && diff_bin_works == MAYBE) + { + diff_a_works = TRUE; + diff_bin_works = TRUE; + continue; + } + if (!ok && diff_a_works == TRUE && diff_bin_works == TRUE) + { + // Tried --binary, but it failed. "-a" works though. + diff_bin_works = FALSE; + ok = TRUE; + } +#endif + + // If we checked if "-a" works already, break here. + if (diff_a_works != MAYBE) + break; + diff_a_works = ok; + + // If "-a" works break here, otherwise retry without "-a". + if (ok) + break; + } + if (!ok) + { + if (io_error) + emsg(_(e_cannot_read_or_write_temp_files)); + emsg(_(e_cannot_create_diffs)); + diff_a_works = MAYBE; +#if defined(MSWIN) + diff_bin_works = MAYBE; +#endif + return FAIL; + } + return OK; +} + +/* + * Invoke the xdiff function. + */ + static int +diff_file_internal(diffio_T *diffio) +{ + xpparam_t param; + xdemitconf_t emit_cfg; + xdemitcb_t emit_cb; + + CLEAR_FIELD(param); + CLEAR_FIELD(emit_cfg); + CLEAR_FIELD(emit_cb); + + param.flags = diff_algorithm; + + if (diff_flags & DIFF_IWHITE) + param.flags |= XDF_IGNORE_WHITESPACE_CHANGE; + if (diff_flags & DIFF_IWHITEALL) + param.flags |= XDF_IGNORE_WHITESPACE; + if (diff_flags & DIFF_IWHITEEOL) + param.flags |= XDF_IGNORE_WHITESPACE_AT_EOL; + if (diff_flags & DIFF_IBLANK) + param.flags |= XDF_IGNORE_BLANK_LINES; + + emit_cfg.ctxlen = 0; // don't need any diff_context here + emit_cb.priv = &diffio->dio_diff; + emit_cfg.hunk_func = xdiff_out; + if (xdl_diff(&diffio->dio_orig.din_mmfile, + &diffio->dio_new.din_mmfile, + ¶m, &emit_cfg, &emit_cb) < 0) + { + emsg(_(e_problem_creating_internal_diff)); + return FAIL; + } + return OK; +} + +/* + * Make a diff between files "tmp_orig" and "tmp_new", results in "tmp_diff". + * return OK or FAIL; + */ + static int +diff_file(diffio_T *dio) +{ + char_u *cmd; + size_t len; + char_u *tmp_orig = dio->dio_orig.din_fname; + char_u *tmp_new = dio->dio_new.din_fname; + char_u *tmp_diff = dio->dio_diff.dout_fname; + +#ifdef FEAT_EVAL + if (*p_dex != NUL) + { + // Use 'diffexpr' to generate the diff file. + eval_diff(tmp_orig, tmp_new, tmp_diff); + return OK; + } + else +#endif + // Use xdiff for generating the diff. + if (dio->dio_internal) + return diff_file_internal(dio); + + len = STRLEN(tmp_orig) + STRLEN(tmp_new) + + STRLEN(tmp_diff) + STRLEN(p_srr) + 27; + cmd = alloc(len); + if (cmd == NULL) + return FAIL; + + // We don't want $DIFF_OPTIONS to get in the way. + if (getenv("DIFF_OPTIONS")) + vim_setenv((char_u *)"DIFF_OPTIONS", (char_u *)""); + + // Build the diff command and execute it. Always use -a, binary + // differences are of no use. Ignore errors, diff returns + // non-zero when differences have been found. + vim_snprintf((char *)cmd, len, "diff %s%s%s%s%s%s%s%s %s", + diff_a_works == FALSE ? "" : "-a ", +#if defined(MSWIN) + diff_bin_works == TRUE ? "--binary " : "", +#else + "", +#endif + (diff_flags & DIFF_IWHITE) ? "-b " : "", + (diff_flags & DIFF_IWHITEALL) ? "-w " : "", + (diff_flags & DIFF_IWHITEEOL) ? "-Z " : "", + (diff_flags & DIFF_IBLANK) ? "-B " : "", + (diff_flags & DIFF_ICASE) ? "-i " : "", + tmp_orig, tmp_new); + append_redir(cmd, (int)len, p_srr, tmp_diff); + block_autocmds(); // avoid ShellCmdPost stuff + (void)call_shell(cmd, SHELL_FILTER|SHELL_SILENT|SHELL_DOOUT); + unblock_autocmds(); + vim_free(cmd); + return OK; +} + +/* + * Create a new version of a file from the current buffer and a diff file. + * The buffer is written to a file, also for unmodified buffers (the file + * could have been produced by autocommands, e.g. the netrw plugin). + */ + void +ex_diffpatch(exarg_T *eap) +{ + char_u *tmp_orig; // name of original temp file + char_u *tmp_new; // name of patched temp file + char_u *buf = NULL; + size_t buflen; + win_T *old_curwin = curwin; + char_u *newname = NULL; // name of patched file buffer +#ifdef UNIX + char_u dirbuf[MAXPATHL]; + char_u *fullname = NULL; +#endif +#ifdef FEAT_BROWSE + char_u *browseFile = NULL; + int save_cmod_flags = cmdmod.cmod_flags; +#endif + stat_T st; + char_u *esc_name = NULL; + +#ifdef FEAT_BROWSE + if (cmdmod.cmod_flags & CMOD_BROWSE) + { + browseFile = do_browse(0, (char_u *)_("Patch file"), + eap->arg, NULL, NULL, + (char_u *)_(BROWSE_FILTER_ALL_FILES), NULL); + if (browseFile == NULL) + return; // operation cancelled + eap->arg = browseFile; + cmdmod.cmod_flags &= ~CMOD_BROWSE; // don't let do_ecmd() browse again + } +#endif + + // We need two temp file names. + tmp_orig = vim_tempname('o', FALSE); + tmp_new = vim_tempname('n', FALSE); + if (tmp_orig == NULL || tmp_new == NULL) + goto theend; + + // Write the current buffer to "tmp_orig". + if (buf_write(curbuf, tmp_orig, NULL, + (linenr_T)1, curbuf->b_ml.ml_line_count, + NULL, FALSE, FALSE, FALSE, TRUE) == FAIL) + goto theend; + +#ifdef UNIX + // Get the absolute path of the patchfile, changing directory below. + fullname = FullName_save(eap->arg, FALSE); +#endif + esc_name = vim_strsave_shellescape( +# ifdef UNIX + fullname != NULL ? fullname : +# endif + eap->arg, TRUE, TRUE); + if (esc_name == NULL) + goto theend; + buflen = STRLEN(tmp_orig) + STRLEN(esc_name) + STRLEN(tmp_new) + 16; + buf = alloc(buflen); + if (buf == NULL) + goto theend; + +#ifdef UNIX + // Temporarily chdir to /tmp, to avoid patching files in the current + // directory when the patch file contains more than one patch. When we + // have our own temp dir use that instead, it will be cleaned up when we + // exit (any .rej files created). Don't change directory if we can't + // return to the current. + if (mch_dirname(dirbuf, MAXPATHL) != OK || mch_chdir((char *)dirbuf) != 0) + dirbuf[0] = NUL; + else + { +# ifdef TEMPDIRNAMES + if (vim_tempdir != NULL) + vim_ignored = mch_chdir((char *)vim_tempdir); + else +# endif + vim_ignored = mch_chdir("/tmp"); + shorten_fnames(TRUE); + } +#endif + +#ifdef FEAT_EVAL + if (*p_pex != NUL) + // Use 'patchexpr' to generate the new file. + eval_patch(tmp_orig, +# ifdef UNIX + fullname != NULL ? fullname : +# endif + eap->arg, tmp_new); + else +#endif + { + if (check_restricted()) + goto theend; + + // Build the patch command and execute it. Ignore errors. Switch to + // cooked mode to allow the user to respond to prompts. + vim_snprintf((char *)buf, buflen, "patch -o %s %s < %s", + tmp_new, tmp_orig, esc_name); + block_autocmds(); // Avoid ShellCmdPost stuff + (void)call_shell(buf, SHELL_FILTER | SHELL_COOKED); + unblock_autocmds(); + } + +#ifdef UNIX + if (dirbuf[0] != NUL) + { + if (mch_chdir((char *)dirbuf) != 0) + emsg(_(e_cannot_go_back_to_previous_directory)); + shorten_fnames(TRUE); + } +#endif + + // patch probably has written over the screen + redraw_later(UPD_CLEAR); + + // Delete any .orig or .rej file created. + STRCPY(buf, tmp_new); + STRCAT(buf, ".orig"); + mch_remove(buf); + STRCPY(buf, tmp_new); + STRCAT(buf, ".rej"); + mch_remove(buf); + + // Only continue if the output file was created. + if (mch_stat((char *)tmp_new, &st) < 0 || st.st_size == 0) + emsg(_(e_cannot_read_patch_output)); + else + { + if (curbuf->b_fname != NULL) + { + newname = vim_strnsave(curbuf->b_fname, + STRLEN(curbuf->b_fname) + 4); + if (newname != NULL) + STRCAT(newname, ".new"); + } + +#ifdef FEAT_GUI + need_mouse_correct = TRUE; +#endif + // don't use a new tab page, each tab page has its own diffs + cmdmod.cmod_tab = 0; + + if (win_split(0, (diff_flags & DIFF_VERTICAL) ? WSP_VERT : 0) != FAIL) + { + // Pretend it was a ":split fname" command + eap->cmdidx = CMD_split; + eap->arg = tmp_new; + do_exedit(eap, old_curwin); + + // check that split worked and editing tmp_new + if (curwin != old_curwin && win_valid(old_curwin)) + { + // Set 'diff', 'scrollbind' on and 'wrap' off. + diff_win_options(curwin, TRUE); + diff_win_options(old_curwin, TRUE); + + if (newname != NULL) + { + // do a ":file filename.new" on the patched buffer + eap->arg = newname; + ex_file(eap); + + // Do filetype detection with the new name. + if (au_has_group((char_u *)"filetypedetect")) + do_cmdline_cmd( + (char_u *)":doau filetypedetect BufRead"); + } + } + } + } + +theend: + if (tmp_orig != NULL) + mch_remove(tmp_orig); + vim_free(tmp_orig); + if (tmp_new != NULL) + mch_remove(tmp_new); + vim_free(tmp_new); + vim_free(newname); + vim_free(buf); +#ifdef UNIX + vim_free(fullname); +#endif + vim_free(esc_name); +#ifdef FEAT_BROWSE + vim_free(browseFile); + cmdmod.cmod_flags = save_cmod_flags; +#endif +} + +/* + * Split the window and edit another file, setting options to show the diffs. + */ + void +ex_diffsplit(exarg_T *eap) +{ + win_T *old_curwin = curwin; + bufref_T old_curbuf; + + set_bufref(&old_curbuf, curbuf); +#ifdef FEAT_GUI + need_mouse_correct = TRUE; +#endif + // Need to compute w_fraction when no redraw happened yet. + validate_cursor(); + set_fraction(curwin); + + // don't use a new tab page, each tab page has its own diffs + cmdmod.cmod_tab = 0; + + if (win_split(0, (diff_flags & DIFF_VERTICAL) ? WSP_VERT : 0) == FAIL) + return; + + // Pretend it was a ":split fname" command + eap->cmdidx = CMD_split; + curwin->w_p_diff = TRUE; + do_exedit(eap, old_curwin); + + if (curwin == old_curwin) // split didn't work + return; + + // Set 'diff', 'scrollbind' on and 'wrap' off. + diff_win_options(curwin, TRUE); + if (win_valid(old_curwin)) + { + diff_win_options(old_curwin, TRUE); + + if (bufref_valid(&old_curbuf)) + // Move the cursor position to that of the old window. + curwin->w_cursor.lnum = diff_get_corresponding_line( + old_curbuf.br_buf, old_curwin->w_cursor.lnum); + } + // Now that lines are folded scroll to show the cursor at the same + // relative position. + scroll_to_fraction(curwin, curwin->w_height); +} + +/* + * Set options to show diffs for the current window. + */ + void +ex_diffthis(exarg_T *eap UNUSED) +{ + // Set 'diff', 'scrollbind' on and 'wrap' off. + diff_win_options(curwin, TRUE); +} + + static void +set_diff_option(win_T *wp, int value) +{ + win_T *old_curwin = curwin; + + curwin = wp; + curbuf = curwin->w_buffer; + ++curbuf_lock; + set_option_value_give_err((char_u *)"diff", (long)value, NULL, OPT_LOCAL); + --curbuf_lock; + curwin = old_curwin; + curbuf = curwin->w_buffer; +} + +/* + * Set options in window "wp" for diff mode. + */ + void +diff_win_options( + win_T *wp, + int addbuf) // Add buffer to diff. +{ +# ifdef FEAT_FOLDING + win_T *old_curwin = curwin; + + // close the manually opened folds + curwin = wp; + newFoldLevel(); + curwin = old_curwin; +# endif + + // Use 'scrollbind' and 'cursorbind' when available + if (!wp->w_p_diff) + wp->w_p_scb_save = wp->w_p_scb; + wp->w_p_scb = TRUE; + if (!wp->w_p_diff) + wp->w_p_crb_save = wp->w_p_crb; + wp->w_p_crb = TRUE; + if (!(diff_flags & DIFF_FOLLOWWRAP)) + { + if (!wp->w_p_diff) + wp->w_p_wrap_save = wp->w_p_wrap; + wp->w_p_wrap = FALSE; + } +# ifdef FEAT_FOLDING + if (!wp->w_p_diff) + { + if (wp->w_p_diff_saved) + free_string_option(wp->w_p_fdm_save); + wp->w_p_fdm_save = vim_strsave(wp->w_p_fdm); + } + set_string_option_direct_in_win(wp, (char_u *)"fdm", -1, (char_u *)"diff", + OPT_LOCAL|OPT_FREE, 0); + if (!wp->w_p_diff) + { + wp->w_p_fdc_save = wp->w_p_fdc; + wp->w_p_fen_save = wp->w_p_fen; + wp->w_p_fdl_save = wp->w_p_fdl; + } + wp->w_p_fdc = diff_foldcolumn; + wp->w_p_fen = TRUE; + wp->w_p_fdl = 0; + foldUpdateAll(wp); + // make sure topline is not halfway a fold + changed_window_setting_win(wp); +# endif + if (vim_strchr(p_sbo, 'h') == NULL) + do_cmdline_cmd((char_u *)"set sbo+=hor"); + // Save the current values, to be restored in ex_diffoff(). + wp->w_p_diff_saved = TRUE; + + set_diff_option(wp, TRUE); + + if (addbuf) + diff_buf_add(wp->w_buffer); + redraw_win_later(wp, UPD_NOT_VALID); +} + +/* + * Set options not to show diffs. For the current window or all windows. + * Only in the current tab page. + */ + void +ex_diffoff(exarg_T *eap) +{ + win_T *wp; + int diffwin = FALSE; + + FOR_ALL_WINDOWS(wp) + { + if (eap->forceit ? wp->w_p_diff : wp == curwin) + { + // Set 'diff' off. If option values were saved in + // diff_win_options(), restore the ones whose settings seem to have + // been left over from diff mode. + set_diff_option(wp, FALSE); + + if (wp->w_p_diff_saved) + { + + if (wp->w_p_scb) + wp->w_p_scb = wp->w_p_scb_save; + if (wp->w_p_crb) + wp->w_p_crb = wp->w_p_crb_save; + if (!(diff_flags & DIFF_FOLLOWWRAP)) + { + if (!wp->w_p_wrap) + wp->w_p_wrap = wp->w_p_wrap_save; + } +#ifdef FEAT_FOLDING + free_string_option(wp->w_p_fdm); + wp->w_p_fdm = vim_strsave( + *wp->w_p_fdm_save ? wp->w_p_fdm_save : (char_u*)"manual"); + + if (wp->w_p_fdc == diff_foldcolumn) + wp->w_p_fdc = wp->w_p_fdc_save; + if (wp->w_p_fdl == 0) + wp->w_p_fdl = wp->w_p_fdl_save; + + // Only restore 'foldenable' when 'foldmethod' is not + // "manual", otherwise we continue to show the diff folds. + if (wp->w_p_fen) + wp->w_p_fen = foldmethodIsManual(wp) ? FALSE + : wp->w_p_fen_save; + + foldUpdateAll(wp); +#endif + } + // remove filler lines + wp->w_topfill = 0; + + // make sure topline is not halfway a fold and cursor is + // invalidated + changed_window_setting_win(wp); + + // Note: 'sbo' is not restored, it's a global option. + diff_buf_adjust(wp); + } + diffwin |= wp->w_p_diff; + } + + // Also remove hidden buffers from the list. + if (eap->forceit) + diff_buf_clear(); + + if (!diffwin) + { + diff_need_update = FALSE; + curtab->tp_diff_invalid = FALSE; + curtab->tp_diff_update = FALSE; + diff_clear(curtab); + } + + // Remove "hor" from 'scrollopt' if there are no diff windows left. + if (!diffwin && vim_strchr(p_sbo, 'h') != NULL) + do_cmdline_cmd((char_u *)"set sbo-=hor"); +} + +/* + * Read the diff output and add each entry to the diff list. + */ + static void +diff_read( + int idx_orig, // idx of original file + int idx_new, // idx of new file + diffio_T *dio) // diff output +{ + FILE *fd = NULL; + int line_idx = 0; + diff_T *dprev = NULL; + diff_T *dp = curtab->tp_first_diff; + diff_T *dn, *dpl; + diffout_T *dout = &dio->dio_diff; + char_u linebuf[LBUFLEN]; // only need to hold the diff line + char_u *line; + long off; + int i; + int notset = TRUE; // block "*dp" not set yet + diffhunk_T *hunk = NULL; // init to avoid gcc warning + + enum { + DIFF_ED, + DIFF_UNIFIED, + DIFF_NONE + } diffstyle = DIFF_NONE; + + if (dout->dout_fname == NULL) + { + diffstyle = DIFF_UNIFIED; + } + else + { + fd = mch_fopen((char *)dout->dout_fname, "r"); + if (fd == NULL) + { + emsg(_(e_cannot_read_diff_output)); + return; + } + } + + if (!dio->dio_internal) + { + hunk = ALLOC_ONE(diffhunk_T); + if (hunk == NULL) + { + if (fd != NULL) + fclose(fd); + return; + } + } + + for (;;) + { + if (dio->dio_internal) + { + if (line_idx >= dout->dout_ga.ga_len) + break; // did last line + hunk = ((diffhunk_T **)dout->dout_ga.ga_data)[line_idx++]; + } + else + { + if (fd == NULL) + { + if (line_idx >= dout->dout_ga.ga_len) + break; // did last line + line = ((char_u **)dout->dout_ga.ga_data)[line_idx++]; + } + else + { + if (vim_fgets(linebuf, LBUFLEN, fd)) + break; // end of file + line = linebuf; + } + + if (diffstyle == DIFF_NONE) + { + // Determine diff style. + // ed like diff looks like this: + // {first}[,{last}]c{first}[,{last}] + // {first}a{first}[,{last}] + // {first}[,{last}]d{first} + // + // unified diff looks like this: + // --- file1 2018-03-20 13:23:35.783153140 +0100 + // +++ file2 2018-03-20 13:23:41.183156066 +0100 + // @@ -1,3 +1,5 @@ + if (isdigit(*line)) + diffstyle = DIFF_ED; + else if ((STRNCMP(line, "@@ ", 3) == 0)) + diffstyle = DIFF_UNIFIED; + else if ((STRNCMP(line, "--- ", 4) == 0) + && (vim_fgets(linebuf, LBUFLEN, fd) == 0) + && (STRNCMP(line, "+++ ", 4) == 0) + && (vim_fgets(linebuf, LBUFLEN, fd) == 0) + && (STRNCMP(line, "@@ ", 3) == 0)) + diffstyle = DIFF_UNIFIED; + else + // Format not recognized yet, skip over this line. Cygwin + // diff may put a warning at the start of the file. + continue; + } + + if (diffstyle == DIFF_ED) + { + if (!isdigit(*line)) + continue; // not the start of a diff block + if (parse_diff_ed(line, hunk) == FAIL) + continue; + } + else if (diffstyle == DIFF_UNIFIED) + { + if (STRNCMP(line, "@@ ", 3) != 0) + continue; // not the start of a diff block + if (parse_diff_unified(line, hunk) == FAIL) + continue; + } + else + { + emsg(_(e_invalid_diff_format)); + break; + } + } + + // Go over blocks before the change, for which orig and new are equal. + // Copy blocks from orig to new. + while (dp != NULL + && hunk->lnum_orig > dp->df_lnum[idx_orig] + + dp->df_count[idx_orig]) + { + if (notset) + diff_copy_entry(dprev, dp, idx_orig, idx_new); + dprev = dp; + dp = dp->df_next; + notset = TRUE; + } + + if (dp != NULL + && hunk->lnum_orig <= dp->df_lnum[idx_orig] + + dp->df_count[idx_orig] + && hunk->lnum_orig + hunk->count_orig >= dp->df_lnum[idx_orig]) + { + // New block overlaps with existing block(s). + // First find last block that overlaps. + for (dpl = dp; dpl->df_next != NULL; dpl = dpl->df_next) + if (hunk->lnum_orig + hunk->count_orig + < dpl->df_next->df_lnum[idx_orig]) + break; + + // If the newly found block starts before the old one, set the + // start back a number of lines. + off = dp->df_lnum[idx_orig] - hunk->lnum_orig; + if (off > 0) + { + for (i = idx_orig; i < idx_new; ++i) + if (curtab->tp_diffbuf[i] != NULL) + dp->df_lnum[i] -= off; + dp->df_lnum[idx_new] = hunk->lnum_new; + dp->df_count[idx_new] = hunk->count_new; + } + else if (notset) + { + // new block inside existing one, adjust new block + dp->df_lnum[idx_new] = hunk->lnum_new + off; + dp->df_count[idx_new] = hunk->count_new - off; + } + else + // second overlap of new block with existing block + dp->df_count[idx_new] += hunk->count_new - hunk->count_orig + + dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig] + - (dp->df_lnum[idx_orig] + dp->df_count[idx_orig]); + + // Adjust the size of the block to include all the lines to the + // end of the existing block or the new diff, whatever ends last. + off = (hunk->lnum_orig + hunk->count_orig) + - (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]); + if (off < 0) + { + // new change ends in existing block, adjust the end if not + // done already + if (notset) + dp->df_count[idx_new] += -off; + off = 0; + } + for (i = idx_orig; i < idx_new; ++i) + if (curtab->tp_diffbuf[i] != NULL) + dp->df_count[i] = dpl->df_lnum[i] + dpl->df_count[i] + - dp->df_lnum[i] + off; + + // Delete the diff blocks that have been merged into one. + dn = dp->df_next; + dp->df_next = dpl->df_next; + while (dn != dp->df_next) + { + dpl = dn->df_next; + vim_free(dn); + dn = dpl; + } + } + else + { + // Allocate a new diffblock. + dp = diff_alloc_new(curtab, dprev, dp); + if (dp == NULL) + goto done; + + dp->df_lnum[idx_orig] = hunk->lnum_orig; + dp->df_count[idx_orig] = hunk->count_orig; + dp->df_lnum[idx_new] = hunk->lnum_new; + dp->df_count[idx_new] = hunk->count_new; + + // Set values for other buffers, these must be equal to the + // original buffer, otherwise there would have been a change + // already. + for (i = idx_orig + 1; i < idx_new; ++i) + if (curtab->tp_diffbuf[i] != NULL) + diff_copy_entry(dprev, dp, idx_orig, i); + } + notset = FALSE; // "*dp" has been set + } + + // for remaining diff blocks orig and new are equal + while (dp != NULL) + { + if (notset) + diff_copy_entry(dprev, dp, idx_orig, idx_new); + dprev = dp; + dp = dp->df_next; + notset = TRUE; + } + +done: + if (!dio->dio_internal) + vim_free(hunk); + + if (fd != NULL) + fclose(fd); +} + +/* + * Copy an entry at "dp" from "idx_orig" to "idx_new". + */ + static void +diff_copy_entry( + diff_T *dprev, + diff_T *dp, + int idx_orig, + int idx_new) +{ + long off; + + if (dprev == NULL) + off = 0; + else + off = (dprev->df_lnum[idx_orig] + dprev->df_count[idx_orig]) + - (dprev->df_lnum[idx_new] + dprev->df_count[idx_new]); + dp->df_lnum[idx_new] = dp->df_lnum[idx_orig] - off; + dp->df_count[idx_new] = dp->df_count[idx_orig]; +} + +/* + * Clear the list of diffblocks for tab page "tp". + */ + void +diff_clear(tabpage_T *tp) +{ + diff_T *p, *next_p; + + for (p = tp->tp_first_diff; p != NULL; p = next_p) + { + next_p = p->df_next; + vim_free(p); + } + tp->tp_first_diff = NULL; +} + +/* + * Check diff status for line "lnum" in buffer "buf": + * Returns 0 for nothing special + * Returns -1 for a line that should be highlighted as changed. + * Returns -2 for a line that should be highlighted as added/deleted. + * Returns > 0 for inserting that many filler lines above it (never happens + * when 'diffopt' doesn't contain "filler"). + * This should only be used for windows where 'diff' is set. + */ + int +diff_check(win_T *wp, linenr_T lnum) +{ + int idx; // index in tp_diffbuf[] for this buffer + diff_T *dp; + int maxcount; + int i; + buf_T *buf = wp->w_buffer; + int cmp; + + if (curtab->tp_diff_invalid) + ex_diffupdate(NULL); // update after a big change + + if (curtab->tp_first_diff == NULL || !wp->w_p_diff) // no diffs at all + return 0; + + // safety check: "lnum" must be a buffer line + if (lnum < 1 || lnum > buf->b_ml.ml_line_count + 1) + return 0; + + idx = diff_buf_idx(buf); + if (idx == DB_COUNT) + return 0; // no diffs for buffer "buf" + +#ifdef FEAT_FOLDING + // A closed fold never has filler lines. + if (hasFoldingWin(wp, lnum, NULL, NULL, TRUE, NULL)) + return 0; +#endif + + // search for a change that includes "lnum" in the list of diffblocks. + FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp) + if (lnum <= dp->df_lnum[idx] + dp->df_count[idx]) + break; + if (dp == NULL || lnum < dp->df_lnum[idx]) + return 0; + + if (lnum < dp->df_lnum[idx] + dp->df_count[idx]) + { + int zero = FALSE; + + // Changed or inserted line. If the other buffers have a count of + // zero, the lines were inserted. If the other buffers have the same + // count, check if the lines are identical. + cmp = FALSE; + for (i = 0; i < DB_COUNT; ++i) + if (i != idx && curtab->tp_diffbuf[i] != NULL) + { + if (dp->df_count[i] == 0) + zero = TRUE; + else + { + if (dp->df_count[i] != dp->df_count[idx]) + return -1; // nr of lines changed. + cmp = TRUE; + } + } + if (cmp) + { + // Compare all lines. If they are equal the lines were inserted + // in some buffers, deleted in others, but not changed. + for (i = 0; i < DB_COUNT; ++i) + if (i != idx && curtab->tp_diffbuf[i] != NULL + && dp->df_count[i] != 0) + if (!diff_equal_entry(dp, idx, i)) + return -1; + } + // If there is no buffer with zero lines then there is no difference + // any longer. Happens when making a change (or undo) that removes + // the difference. Can't remove the entry here, we might be halfway + // updating the window. Just report the text as unchanged. Other + // windows might still show the change though. + if (zero == FALSE) + return 0; + return -2; + } + + // If 'diffopt' doesn't contain "filler", return 0. + if (!(diff_flags & DIFF_FILLER)) + return 0; + + // Insert filler lines above the line just below the change. Will return + // 0 when this buf had the max count. + maxcount = 0; + for (i = 0; i < DB_COUNT; ++i) + if (curtab->tp_diffbuf[i] != NULL && dp->df_count[i] > maxcount) + maxcount = dp->df_count[i]; + return maxcount - dp->df_count[idx]; +} + +/* + * Compare two entries in diff "*dp" and return TRUE if they are equal. + */ + static int +diff_equal_entry(diff_T *dp, int idx1, int idx2) +{ + int i; + char_u *line; + int cmp; + + if (dp->df_count[idx1] != dp->df_count[idx2]) + return FALSE; + if (diff_check_sanity(curtab, dp) == FAIL) + return FALSE; + for (i = 0; i < dp->df_count[idx1]; ++i) + { + line = vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx1], + dp->df_lnum[idx1] + i, FALSE)); + if (line == NULL) + return FALSE; + cmp = diff_cmp(line, ml_get_buf(curtab->tp_diffbuf[idx2], + dp->df_lnum[idx2] + i, FALSE)); + vim_free(line); + if (cmp != 0) + return FALSE; + } + return TRUE; +} + +/* + * Compare the characters at "p1" and "p2". If they are equal (possibly + * ignoring case) return TRUE and set "len" to the number of bytes. + */ + static int +diff_equal_char(char_u *p1, char_u *p2, int *len) +{ + int l = (*mb_ptr2len)(p1); + + if (l != (*mb_ptr2len)(p2)) + return FALSE; + if (l > 1) + { + if (STRNCMP(p1, p2, l) != 0 + && (!enc_utf8 + || !(diff_flags & DIFF_ICASE) + || utf_fold(utf_ptr2char(p1)) + != utf_fold(utf_ptr2char(p2)))) + return FALSE; + *len = l; + } + else + { + if ((*p1 != *p2) + && (!(diff_flags & DIFF_ICASE) + || TOLOWER_LOC(*p1) != TOLOWER_LOC(*p2))) + return FALSE; + *len = 1; + } + return TRUE; +} + +/* + * Compare strings "s1" and "s2" according to 'diffopt'. + * Return non-zero when they are different. + */ + static int +diff_cmp(char_u *s1, char_u *s2) +{ + char_u *p1, *p2; + int l; + + if ((diff_flags & DIFF_IBLANK) + && (*skipwhite(s1) == NUL || *skipwhite(s2) == NUL)) + return 0; + + if ((diff_flags & (DIFF_ICASE | ALL_WHITE_DIFF)) == 0) + return STRCMP(s1, s2); + if ((diff_flags & DIFF_ICASE) && !(diff_flags & ALL_WHITE_DIFF)) + return MB_STRICMP(s1, s2); + + p1 = s1; + p2 = s2; + + // Ignore white space changes and possibly ignore case. + while (*p1 != NUL && *p2 != NUL) + { + if (((diff_flags & DIFF_IWHITE) + && VIM_ISWHITE(*p1) && VIM_ISWHITE(*p2)) + || ((diff_flags & DIFF_IWHITEALL) + && (VIM_ISWHITE(*p1) || VIM_ISWHITE(*p2)))) + { + p1 = skipwhite(p1); + p2 = skipwhite(p2); + } + else + { + if (!diff_equal_char(p1, p2, &l)) + break; + p1 += l; + p2 += l; + } + } + + // Ignore trailing white space. + p1 = skipwhite(p1); + p2 = skipwhite(p2); + if (*p1 != NUL || *p2 != NUL) + return 1; + return 0; +} + +/* + * Return the number of filler lines above "lnum". + */ + int +diff_check_fill(win_T *wp, linenr_T lnum) +{ + int n; + + // be quick when there are no filler lines + if (!(diff_flags & DIFF_FILLER)) + return 0; + n = diff_check(wp, lnum); + if (n <= 0) + return 0; + return n; +} + +/* + * Set the topline of "towin" to match the position in "fromwin", so that they + * show the same diff'ed lines. + */ + void +diff_set_topline(win_T *fromwin, win_T *towin) +{ + buf_T *frombuf = fromwin->w_buffer; + linenr_T lnum = fromwin->w_topline; + int fromidx; + int toidx; + diff_T *dp; + int max_count; + int i; + + fromidx = diff_buf_idx(frombuf); + if (fromidx == DB_COUNT) + return; // safety check + + if (curtab->tp_diff_invalid) + ex_diffupdate(NULL); // update after a big change + + towin->w_topfill = 0; + + // search for a change that includes "lnum" in the list of diffblocks. + FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp) + if (lnum <= dp->df_lnum[fromidx] + dp->df_count[fromidx]) + break; + if (dp == NULL) + { + // After last change, compute topline relative to end of file; no + // filler lines. + towin->w_topline = towin->w_buffer->b_ml.ml_line_count + - (frombuf->b_ml.ml_line_count - lnum); + } + else + { + // Find index for "towin". + toidx = diff_buf_idx(towin->w_buffer); + if (toidx == DB_COUNT) + return; // safety check + + towin->w_topline = lnum + (dp->df_lnum[toidx] - dp->df_lnum[fromidx]); + if (lnum >= dp->df_lnum[fromidx]) + { + // Inside a change: compute filler lines. With three or more + // buffers we need to know the largest count. + max_count = 0; + for (i = 0; i < DB_COUNT; ++i) + if (curtab->tp_diffbuf[i] != NULL + && max_count < dp->df_count[i]) + max_count = dp->df_count[i]; + + if (dp->df_count[toidx] == dp->df_count[fromidx]) + { + // same number of lines: use same filler count + towin->w_topfill = fromwin->w_topfill; + } + else if (dp->df_count[toidx] > dp->df_count[fromidx]) + { + if (lnum == dp->df_lnum[fromidx] + dp->df_count[fromidx]) + { + // more lines in towin and fromwin doesn't show diff + // lines, only filler lines + if (max_count - fromwin->w_topfill >= dp->df_count[toidx]) + { + // towin also only shows filler lines + towin->w_topline = dp->df_lnum[toidx] + + dp->df_count[toidx]; + towin->w_topfill = fromwin->w_topfill; + } + else + // towin still has some diff lines to show + towin->w_topline = dp->df_lnum[toidx] + + max_count - fromwin->w_topfill; + } + } + else if (towin->w_topline >= dp->df_lnum[toidx] + + dp->df_count[toidx]) + { + // less lines in towin and no diff lines to show: compute + // filler lines + towin->w_topline = dp->df_lnum[toidx] + dp->df_count[toidx]; + if (diff_flags & DIFF_FILLER) + { + if (lnum == dp->df_lnum[fromidx] + dp->df_count[fromidx]) + // fromwin is also out of diff lines + towin->w_topfill = fromwin->w_topfill; + else + // fromwin has some diff lines + towin->w_topfill = dp->df_lnum[fromidx] + + max_count - lnum; + } + } + } + } + + // safety check (if diff info gets outdated strange things may happen) + towin->w_botfill = FALSE; + if (towin->w_topline > towin->w_buffer->b_ml.ml_line_count) + { + towin->w_topline = towin->w_buffer->b_ml.ml_line_count; + towin->w_botfill = TRUE; + } + if (towin->w_topline < 1) + { + towin->w_topline = 1; + towin->w_topfill = 0; + } + + // When w_topline changes need to recompute w_botline and cursor position + invalidate_botline_win(towin); + changed_line_abv_curs_win(towin); + + check_topfill(towin, FALSE); +#ifdef FEAT_FOLDING + (void)hasFoldingWin(towin, towin->w_topline, &towin->w_topline, + NULL, TRUE, NULL); +#endif +} + +/* + * This is called when 'diffopt' is changed. + */ + int +diffopt_changed(void) +{ + char_u *p; + int diff_context_new = 6; + int diff_flags_new = 0; + int diff_foldcolumn_new = 2; + long diff_algorithm_new = 0; + long diff_indent_heuristic = 0; + tabpage_T *tp; + + p = p_dip; + while (*p != NUL) + { + // Note: Keep this in sync with p_dip_values + if (STRNCMP(p, "filler", 6) == 0) + { + p += 6; + diff_flags_new |= DIFF_FILLER; + } + else if (STRNCMP(p, "context:", 8) == 0 && VIM_ISDIGIT(p[8])) + { + p += 8; + diff_context_new = getdigits(&p); + } + else if (STRNCMP(p, "iblank", 6) == 0) + { + p += 6; + diff_flags_new |= DIFF_IBLANK; + } + else if (STRNCMP(p, "icase", 5) == 0) + { + p += 5; + diff_flags_new |= DIFF_ICASE; + } + else if (STRNCMP(p, "iwhiteall", 9) == 0) + { + p += 9; + diff_flags_new |= DIFF_IWHITEALL; + } + else if (STRNCMP(p, "iwhiteeol", 9) == 0) + { + p += 9; + diff_flags_new |= DIFF_IWHITEEOL; + } + else if (STRNCMP(p, "iwhite", 6) == 0) + { + p += 6; + diff_flags_new |= DIFF_IWHITE; + } + else if (STRNCMP(p, "horizontal", 10) == 0) + { + p += 10; + diff_flags_new |= DIFF_HORIZONTAL; + } + else if (STRNCMP(p, "vertical", 8) == 0) + { + p += 8; + diff_flags_new |= DIFF_VERTICAL; + } + else if (STRNCMP(p, "foldcolumn:", 11) == 0 && VIM_ISDIGIT(p[11])) + { + p += 11; + diff_foldcolumn_new = getdigits(&p); + } + else if (STRNCMP(p, "hiddenoff", 9) == 0) + { + p += 9; + diff_flags_new |= DIFF_HIDDEN_OFF; + } + else if (STRNCMP(p, "closeoff", 8) == 0) + { + p += 8; + diff_flags_new |= DIFF_CLOSE_OFF; + } + else if (STRNCMP(p, "followwrap", 10) == 0) + { + p += 10; + diff_flags_new |= DIFF_FOLLOWWRAP; + } + else if (STRNCMP(p, "indent-heuristic", 16) == 0) + { + p += 16; + diff_indent_heuristic = XDF_INDENT_HEURISTIC; + } + else if (STRNCMP(p, "internal", 8) == 0) + { + p += 8; + diff_flags_new |= DIFF_INTERNAL; + } + else if (STRNCMP(p, "algorithm:", 10) == 0) + { + // Note: Keep this in sync with p_dip_algorithm_values. + p += 10; + if (STRNCMP(p, "myers", 5) == 0) + { + p += 5; + diff_algorithm_new = 0; + } + else if (STRNCMP(p, "minimal", 7) == 0) + { + p += 7; + diff_algorithm_new = XDF_NEED_MINIMAL; + } + else if (STRNCMP(p, "patience", 8) == 0) + { + p += 8; + diff_algorithm_new = XDF_PATIENCE_DIFF; + } + else if (STRNCMP(p, "histogram", 9) == 0) + { + p += 9; + diff_algorithm_new = XDF_HISTOGRAM_DIFF; + } + else + return FAIL; + } + + if (*p != ',' && *p != NUL) + return FAIL; + if (*p == ',') + ++p; + } + + diff_algorithm_new |= diff_indent_heuristic; + + // Can't have both "horizontal" and "vertical". + if ((diff_flags_new & DIFF_HORIZONTAL) && (diff_flags_new & DIFF_VERTICAL)) + return FAIL; + + // If flags were added or removed, or the algorithm was changed, need to + // update the diff. + if (diff_flags != diff_flags_new || diff_algorithm != diff_algorithm_new) + FOR_ALL_TABPAGES(tp) + tp->tp_diff_invalid = TRUE; + + diff_flags = diff_flags_new; + diff_context = diff_context_new == 0 ? 1 : diff_context_new; + diff_foldcolumn = diff_foldcolumn_new; + diff_algorithm = diff_algorithm_new; + + diff_redraw(TRUE); + + // recompute the scroll binding with the new option value, may + // remove or add filler lines + check_scrollbind((linenr_T)0, 0L); + + return OK; +} + +/* + * Return TRUE if 'diffopt' contains "horizontal". + */ + int +diffopt_horizontal(void) +{ + return (diff_flags & DIFF_HORIZONTAL) != 0; +} + +/* + * Return TRUE if 'diffopt' contains "hiddenoff". + */ + int +diffopt_hiddenoff(void) +{ + return (diff_flags & DIFF_HIDDEN_OFF) != 0; +} + +/* + * Return TRUE if 'diffopt' contains "closeoff". + */ + int +diffopt_closeoff(void) +{ + return (diff_flags & DIFF_CLOSE_OFF) != 0; +} + +/* + * Find the difference within a changed line. + * Returns TRUE if the line was added, no other buffer has it. + */ + int +diff_find_change( + win_T *wp, + linenr_T lnum, + int *startp, // first char of the change + int *endp) // last char of the change +{ + char_u *line_org; + char_u *line_new; + int i; + int si_org, si_new; + int ei_org, ei_new; + diff_T *dp; + int idx; + int off; + int added = TRUE; + char_u *p1, *p2; + int l; + + // Make a copy of the line, the next ml_get() will invalidate it. + line_org = vim_strsave(ml_get_buf(wp->w_buffer, lnum, FALSE)); + if (line_org == NULL) + return FALSE; + + idx = diff_buf_idx(wp->w_buffer); + if (idx == DB_COUNT) // cannot happen + { + vim_free(line_org); + return FALSE; + } + + // search for a change that includes "lnum" in the list of diffblocks. + FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp) + if (lnum <= dp->df_lnum[idx] + dp->df_count[idx]) + break; + if (dp == NULL || diff_check_sanity(curtab, dp) == FAIL) + { + vim_free(line_org); + return FALSE; + } + + off = lnum - dp->df_lnum[idx]; + + for (i = 0; i < DB_COUNT; ++i) + if (curtab->tp_diffbuf[i] != NULL && i != idx) + { + // Skip lines that are not in the other change (filler lines). + if (off >= dp->df_count[i]) + continue; + added = FALSE; + line_new = ml_get_buf(curtab->tp_diffbuf[i], + dp->df_lnum[i] + off, FALSE); + + // Search for start of difference + si_org = si_new = 0; + while (line_org[si_org] != NUL) + { + if (((diff_flags & DIFF_IWHITE) + && VIM_ISWHITE(line_org[si_org]) + && VIM_ISWHITE(line_new[si_new])) + || ((diff_flags & DIFF_IWHITEALL) + && (VIM_ISWHITE(line_org[si_org]) + || VIM_ISWHITE(line_new[si_new])))) + { + si_org = (int)(skipwhite(line_org + si_org) - line_org); + si_new = (int)(skipwhite(line_new + si_new) - line_new); + } + else + { + if (!diff_equal_char(line_org + si_org, line_new + si_new, + &l)) + break; + si_org += l; + si_new += l; + } + } + if (has_mbyte) + { + // Move back to first byte of character in both lines (may + // have "nn^" in line_org and "n^ in line_new). + si_org -= (*mb_head_off)(line_org, line_org + si_org); + si_new -= (*mb_head_off)(line_new, line_new + si_new); + } + if (*startp > si_org) + *startp = si_org; + + // Search for end of difference, if any. + if (line_org[si_org] != NUL || line_new[si_new] != NUL) + { + ei_org = (int)STRLEN(line_org); + ei_new = (int)STRLEN(line_new); + while (ei_org >= *startp && ei_new >= si_new + && ei_org >= 0 && ei_new >= 0) + { + if (((diff_flags & DIFF_IWHITE) + && VIM_ISWHITE(line_org[ei_org]) + && VIM_ISWHITE(line_new[ei_new])) + || ((diff_flags & DIFF_IWHITEALL) + && (VIM_ISWHITE(line_org[ei_org]) + || VIM_ISWHITE(line_new[ei_new])))) + { + while (ei_org >= *startp + && VIM_ISWHITE(line_org[ei_org])) + --ei_org; + while (ei_new >= si_new + && VIM_ISWHITE(line_new[ei_new])) + --ei_new; + } + else + { + p1 = line_org + ei_org; + p2 = line_new + ei_new; + p1 -= (*mb_head_off)(line_org, p1); + p2 -= (*mb_head_off)(line_new, p2); + if (!diff_equal_char(p1, p2, &l)) + break; + ei_org -= l; + ei_new -= l; + } + } + if (*endp < ei_org) + *endp = ei_org; + } + } + + vim_free(line_org); + return added; +} + +#if defined(FEAT_FOLDING) || defined(PROTO) +/* + * Return TRUE if line "lnum" is not close to a diff block, this line should + * be in a fold. + * Return FALSE if there are no diff blocks at all in this window. + */ + int +diff_infold(win_T *wp, linenr_T lnum) +{ + int i; + int idx = -1; + int other = FALSE; + diff_T *dp; + + // Return if 'diff' isn't set. + if (!wp->w_p_diff) + return FALSE; + + for (i = 0; i < DB_COUNT; ++i) + { + if (curtab->tp_diffbuf[i] == wp->w_buffer) + idx = i; + else if (curtab->tp_diffbuf[i] != NULL) + other = TRUE; + } + + // return here if there are no diffs in the window + if (idx == -1 || !other) + return FALSE; + + if (curtab->tp_diff_invalid) + ex_diffupdate(NULL); // update after a big change + + // Return if there are no diff blocks. All lines will be folded. + if (curtab->tp_first_diff == NULL) + return TRUE; + + FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp) + { + // If this change is below the line there can't be any further match. + if (dp->df_lnum[idx] - diff_context > lnum) + break; + // If this change ends before the line we have a match. + if (dp->df_lnum[idx] + dp->df_count[idx] + diff_context > lnum) + return FALSE; + } + return TRUE; +} +#endif + +/* + * "dp" and "do" commands. + */ + void +nv_diffgetput(int put, long count) +{ + exarg_T ea; + char_u buf[30]; + +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(curbuf)) + { + vim_beep(BO_OPER); + return; + } +#endif + if (count == 0) + ea.arg = (char_u *)""; + else + { + vim_snprintf((char *)buf, 30, "%ld", count); + ea.arg = buf; + } + if (put) + ea.cmdidx = CMD_diffput; + else + ea.cmdidx = CMD_diffget; + ea.addr_count = 0; + ea.line1 = curwin->w_cursor.lnum; + ea.line2 = curwin->w_cursor.lnum; + ex_diffgetput(&ea); +} + +/* + * Return TRUE if "diff" appears in the list of diff blocks of the current tab. + */ + static int +valid_diff(diff_T *diff) +{ + diff_T *dp; + + FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp) + if (dp == diff) + return TRUE; + return FALSE; +} + +/* + * ":diffget" + * ":diffput" + */ + void +ex_diffgetput(exarg_T *eap) +{ + linenr_T lnum; + int count; + linenr_T off = 0; + diff_T *dp; + diff_T *dprev; + diff_T *dfree; + int idx_cur; + int idx_other; + int idx_from; + int idx_to; + int i; + int added; + char_u *p; + aco_save_T aco; + buf_T *buf; + int start_skip, end_skip; + int new_count; + int buf_empty; + int found_not_ma = FALSE; + + // Find the current buffer in the list of diff buffers. + idx_cur = diff_buf_idx(curbuf); + if (idx_cur == DB_COUNT) + { + emsg(_(e_current_buffer_is_not_in_diff_mode)); + return; + } + + if (*eap->arg == NUL) + { + // No argument: Find the other buffer in the list of diff buffers. + for (idx_other = 0; idx_other < DB_COUNT; ++idx_other) + if (curtab->tp_diffbuf[idx_other] != curbuf + && curtab->tp_diffbuf[idx_other] != NULL) + { + if (eap->cmdidx != CMD_diffput + || curtab->tp_diffbuf[idx_other]->b_p_ma) + break; + found_not_ma = TRUE; + } + if (idx_other == DB_COUNT) + { + if (found_not_ma) + emsg(_(e_no_other_buffer_in_diff_mode_is_modifiable)); + else + emsg(_(e_no_other_buffer_in_diff_mode)); + return; + } + + // Check that there isn't a third buffer in the list + for (i = idx_other + 1; i < DB_COUNT; ++i) + if (curtab->tp_diffbuf[i] != curbuf + && curtab->tp_diffbuf[i] != NULL + && (eap->cmdidx != CMD_diffput || curtab->tp_diffbuf[i]->b_p_ma)) + { + emsg(_(e_more_than_two_buffers_in_diff_mode_dont_know_which_one_to_use)); + return; + } + } + else + { + // Buffer number or pattern given. Ignore trailing white space. + p = eap->arg + STRLEN(eap->arg); + while (p > eap->arg && VIM_ISWHITE(p[-1])) + --p; + for (i = 0; vim_isdigit(eap->arg[i]) && eap->arg + i < p; ++i) + ; + if (eap->arg + i == p) // digits only + i = atol((char *)eap->arg); + else + { + i = buflist_findpat(eap->arg, p, FALSE, TRUE, FALSE); + if (i < 0) + return; // error message already given + } + buf = buflist_findnr(i); + if (buf == NULL) + { + semsg(_(e_cant_find_buffer_str), eap->arg); + return; + } + if (buf == curbuf) + return; // nothing to do + idx_other = diff_buf_idx(buf); + if (idx_other == DB_COUNT) + { + semsg(_(e_buffer_str_is_not_in_diff_mode), eap->arg); + return; + } + } + + diff_busy = TRUE; + + // When no range given include the line above or below the cursor. + if (eap->addr_count == 0) + { + // Make it possible that ":diffget" on the last line gets line below + // the cursor line when there is no difference above the cursor. + if (eap->cmdidx == CMD_diffget + && eap->line1 == curbuf->b_ml.ml_line_count + && diff_check(curwin, eap->line1) == 0 + && (eap->line1 == 1 || diff_check(curwin, eap->line1 - 1) == 0)) + ++eap->line2; + else if (eap->line1 > 0) + --eap->line1; + } + + if (eap->cmdidx == CMD_diffget) + { + idx_from = idx_other; + idx_to = idx_cur; + } + else + { + idx_from = idx_cur; + idx_to = idx_other; + // Need to make the other buffer the current buffer to be able to make + // changes in it. + // Set curwin/curbuf to buf and save a few things. + aucmd_prepbuf(&aco, curtab->tp_diffbuf[idx_other]); + if (curbuf != curtab->tp_diffbuf[idx_other]) + // Could not find a window for this buffer, the rest is likely to + // fail. + goto theend; + } + + // May give the warning for a changed buffer here, which can trigger the + // FileChangedRO autocommand, which may do nasty things and mess + // everything up. + if (!curbuf->b_changed) + { + change_warning(0); + if (diff_buf_idx(curbuf) != idx_to) + { + emsg(_(e_buffer_changed_unexpectedly)); + goto theend; + } + } + + dprev = NULL; + for (dp = curtab->tp_first_diff; dp != NULL; ) + { + if (dp->df_lnum[idx_cur] > eap->line2 + off) + break; // past the range that was specified + + dfree = NULL; + lnum = dp->df_lnum[idx_to]; + count = dp->df_count[idx_to]; + if (dp->df_lnum[idx_cur] + dp->df_count[idx_cur] > eap->line1 + off + && u_save(lnum - 1, lnum + count) != FAIL) + { + // Inside the specified range and saving for undo worked. + start_skip = 0; + end_skip = 0; + if (eap->addr_count > 0) + { + // A range was specified: check if lines need to be skipped. + start_skip = eap->line1 + off - dp->df_lnum[idx_cur]; + if (start_skip > 0) + { + // range starts below start of current diff block + if (start_skip > count) + { + lnum += count; + count = 0; + } + else + { + count -= start_skip; + lnum += start_skip; + } + } + else + start_skip = 0; + + end_skip = dp->df_lnum[idx_cur] + dp->df_count[idx_cur] - 1 + - (eap->line2 + off); + if (end_skip > 0) + { + // range ends above end of current/from diff block + if (idx_cur == idx_from) // :diffput + { + i = dp->df_count[idx_cur] - start_skip - end_skip; + if (count > i) + count = i; + } + else // :diffget + { + count -= end_skip; + end_skip = dp->df_count[idx_from] - start_skip - count; + if (end_skip < 0) + end_skip = 0; + } + } + else + end_skip = 0; + } + + buf_empty = BUFEMPTY(); + added = 0; + for (i = 0; i < count; ++i) + { + // remember deleting the last line of the buffer + buf_empty = curbuf->b_ml.ml_line_count == 1; + if (ml_delete(lnum) == OK) + --added; + } + for (i = 0; i < dp->df_count[idx_from] - start_skip - end_skip; ++i) + { + linenr_T nr; + + nr = dp->df_lnum[idx_from] + start_skip + i; + if (nr > curtab->tp_diffbuf[idx_from]->b_ml.ml_line_count) + break; + p = vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx_from], + nr, FALSE)); + if (p != NULL) + { + ml_append(lnum + i - 1, p, 0, FALSE); + vim_free(p); + ++added; + if (buf_empty && curbuf->b_ml.ml_line_count == 2) + { + // Added the first line into an empty buffer, need to + // delete the dummy empty line. + buf_empty = FALSE; + ml_delete((linenr_T)2); + } + } + } + new_count = dp->df_count[idx_to] + added; + dp->df_count[idx_to] = new_count; + + if (start_skip == 0 && end_skip == 0) + { + // Check if there are any other buffers and if the diff is + // equal in them. + for (i = 0; i < DB_COUNT; ++i) + if (curtab->tp_diffbuf[i] != NULL && i != idx_from + && i != idx_to + && !diff_equal_entry(dp, idx_from, i)) + break; + if (i == DB_COUNT) + { + // delete the diff entry, the buffers are now equal here + dfree = dp; + dp = dp->df_next; + if (dprev == NULL) + curtab->tp_first_diff = dp; + else + dprev->df_next = dp; + } + } + + if (added != 0) + { + // Adjust marks. This will change the following entries! + mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added); + if (curwin->w_cursor.lnum >= lnum) + { + // Adjust the cursor position if it's in/after the changed + // lines. + if (curwin->w_cursor.lnum >= lnum + count) + curwin->w_cursor.lnum += added; + else if (added < 0) + curwin->w_cursor.lnum = lnum; + } + } + changed_lines(lnum, 0, lnum + count, (long)added); + + if (dfree != NULL) + { + // Diff is deleted, update folds in other windows. +#ifdef FEAT_FOLDING + diff_fold_update(dfree, idx_to); +#endif + vim_free(dfree); + } + + // mark_adjust() may have made "dp" invalid. We don't know where + // to continue then, bail out. + if (added != 0 && !valid_diff(dp)) + break; + + if (dfree == NULL) + // mark_adjust() may have changed the count in a wrong way + dp->df_count[idx_to] = new_count; + + // When changing the current buffer, keep track of line numbers + if (idx_cur == idx_to) + off += added; + } + + // If before the range or not deleted, go to next diff. + if (dfree == NULL) + { + dprev = dp; + dp = dp->df_next; + } + } + + // restore curwin/curbuf and a few other things + if (eap->cmdidx != CMD_diffget) + { + // Syncing undo only works for the current buffer, but we change + // another buffer. Sync undo if the command was typed. This isn't + // 100% right when ":diffput" is used in a function or mapping. + if (KeyTyped) + u_sync(FALSE); + aucmd_restbuf(&aco); + } + +theend: + diff_busy = FALSE; + if (diff_need_update) + ex_diffupdate(NULL); + + // Check that the cursor is on a valid character and update its + // position. When there were filler lines the topline has become + // invalid. + check_cursor(); + changed_line_abv_curs(); + + if (diff_need_update) + // redraw already done by ex_diffupdate() + diff_need_update = FALSE; + else + { + // Also need to redraw the other buffers. + diff_redraw(FALSE); + apply_autocmds(EVENT_DIFFUPDATED, NULL, NULL, FALSE, curbuf); + } +} + +#ifdef FEAT_FOLDING +/* + * Update folds for all diff buffers for entry "dp". + * Skip buffer with index "skip_idx". + * When there are no diffs, all folds are removed. + */ + static void +diff_fold_update(diff_T *dp, int skip_idx) +{ + int i; + win_T *wp; + + FOR_ALL_WINDOWS(wp) + for (i = 0; i < DB_COUNT; ++i) + if (curtab->tp_diffbuf[i] == wp->w_buffer && i != skip_idx) + foldUpdate(wp, dp->df_lnum[i], + dp->df_lnum[i] + dp->df_count[i]); +} +#endif + +/* + * Return TRUE if buffer "buf" is in diff-mode. + */ + int +diff_mode_buf(buf_T *buf) +{ + tabpage_T *tp; + + FOR_ALL_TABPAGES(tp) + if (diff_buf_idx_tp(buf, tp) != DB_COUNT) + return TRUE; + return FALSE; +} + +/* + * Move "count" times in direction "dir" to the next diff block. + * Return FAIL if there isn't such a diff block. + */ + int +diff_move_to(int dir, long count) +{ + int idx; + linenr_T lnum = curwin->w_cursor.lnum; + diff_T *dp; + + idx = diff_buf_idx(curbuf); + if (idx == DB_COUNT || curtab->tp_first_diff == NULL) + return FAIL; + + if (curtab->tp_diff_invalid) + ex_diffupdate(NULL); // update after a big change + + if (curtab->tp_first_diff == NULL) // no diffs today + return FAIL; + + while (--count >= 0) + { + // Check if already before first diff. + if (dir == BACKWARD && lnum <= curtab->tp_first_diff->df_lnum[idx]) + break; + + for (dp = curtab->tp_first_diff; ; dp = dp->df_next) + { + if (dp == NULL) + break; + if ((dir == FORWARD && lnum < dp->df_lnum[idx]) + || (dir == BACKWARD + && (dp->df_next == NULL + || lnum <= dp->df_next->df_lnum[idx]))) + { + lnum = dp->df_lnum[idx]; + break; + } + } + } + + // don't end up past the end of the file + if (lnum > curbuf->b_ml.ml_line_count) + lnum = curbuf->b_ml.ml_line_count; + + // When the cursor didn't move at all we fail. + if (lnum == curwin->w_cursor.lnum) + return FAIL; + + setpcmark(); + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + + return OK; +} + +/* + * Return the line number in the current window that is closest to "lnum1" in + * "buf1" in diff mode. + */ + static linenr_T +diff_get_corresponding_line_int( + buf_T *buf1, + linenr_T lnum1) +{ + int idx1; + int idx2; + diff_T *dp; + int baseline = 0; + + idx1 = diff_buf_idx(buf1); + idx2 = diff_buf_idx(curbuf); + if (idx1 == DB_COUNT || idx2 == DB_COUNT || curtab->tp_first_diff == NULL) + return lnum1; + + if (curtab->tp_diff_invalid) + ex_diffupdate(NULL); // update after a big change + + if (curtab->tp_first_diff == NULL) // no diffs today + return lnum1; + + FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp) + { + if (dp->df_lnum[idx1] > lnum1) + return lnum1 - baseline; + if ((dp->df_lnum[idx1] + dp->df_count[idx1]) > lnum1) + { + // Inside the diffblock + baseline = lnum1 - dp->df_lnum[idx1]; + if (baseline > dp->df_count[idx2]) + baseline = dp->df_count[idx2]; + + return dp->df_lnum[idx2] + baseline; + } + if ( (dp->df_lnum[idx1] == lnum1) + && (dp->df_count[idx1] == 0) + && (dp->df_lnum[idx2] <= curwin->w_cursor.lnum) + && ((dp->df_lnum[idx2] + dp->df_count[idx2]) + > curwin->w_cursor.lnum)) + /* + * Special case: if the cursor is just after a zero-count + * block (i.e. all filler) and the target cursor is already + * inside the corresponding block, leave the target cursor + * unmoved. This makes repeated CTRL-W W operations work + * as expected. + */ + return curwin->w_cursor.lnum; + baseline = (dp->df_lnum[idx1] + dp->df_count[idx1]) + - (dp->df_lnum[idx2] + dp->df_count[idx2]); + } + + // If we get here then the cursor is after the last diff + return lnum1 - baseline; +} + +/* + * Return the line number in the current window that is closest to "lnum1" in + * "buf1" in diff mode. Checks the line number to be valid. + */ + linenr_T +diff_get_corresponding_line(buf_T *buf1, linenr_T lnum1) +{ + linenr_T lnum = diff_get_corresponding_line_int(buf1, lnum1); + + // don't end up past the end of the file + if (lnum > curbuf->b_ml.ml_line_count) + return curbuf->b_ml.ml_line_count; + return lnum; +} + +/* + * For line "lnum" in the current window find the equivalent lnum in window + * "wp", compensating for inserted/deleted lines. + */ + linenr_T +diff_lnum_win(linenr_T lnum, win_T *wp) +{ + diff_T *dp; + int idx; + int i; + linenr_T n; + + idx = diff_buf_idx(curbuf); + if (idx == DB_COUNT) // safety check + return (linenr_T)0; + + if (curtab->tp_diff_invalid) + ex_diffupdate(NULL); // update after a big change + + // search for a change that includes "lnum" in the list of diffblocks. + FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp) + if (lnum <= dp->df_lnum[idx] + dp->df_count[idx]) + break; + + // When after the last change, compute relative to the last line number. + if (dp == NULL) + return wp->w_buffer->b_ml.ml_line_count + - (curbuf->b_ml.ml_line_count - lnum); + + // Find index for "wp". + i = diff_buf_idx(wp->w_buffer); + if (i == DB_COUNT) // safety check + return (linenr_T)0; + + n = lnum + (dp->df_lnum[i] - dp->df_lnum[idx]); + if (n > dp->df_lnum[i] + dp->df_count[i]) + n = dp->df_lnum[i] + dp->df_count[i]; + return n; +} + +/* + * Handle an ED style diff line. + * Return FAIL if the line does not contain diff info. + */ + static int +parse_diff_ed( + char_u *line, + diffhunk_T *hunk) +{ + char_u *p; + long f1, l1, f2, l2; + int difftype; + + // The line must be one of three formats: + // change: {first}[,{last}]c{first}[,{last}] + // append: {first}a{first}[,{last}] + // delete: {first}[,{last}]d{first} + p = line; + f1 = getdigits(&p); + if (*p == ',') + { + ++p; + l1 = getdigits(&p); + } + else + l1 = f1; + if (*p != 'a' && *p != 'c' && *p != 'd') + return FAIL; // invalid diff format + difftype = *p++; + f2 = getdigits(&p); + if (*p == ',') + { + ++p; + l2 = getdigits(&p); + } + else + l2 = f2; + if (l1 < f1 || l2 < f2) + return FAIL; + + if (difftype == 'a') + { + hunk->lnum_orig = f1 + 1; + hunk->count_orig = 0; + } + else + { + hunk->lnum_orig = f1; + hunk->count_orig = l1 - f1 + 1; + } + if (difftype == 'd') + { + hunk->lnum_new = f2 + 1; + hunk->count_new = 0; + } + else + { + hunk->lnum_new = f2; + hunk->count_new = l2 - f2 + 1; + } + return OK; +} + +/* + * Parses unified diff with zero(!) context lines. + * Return FAIL if there is no diff information in "line". + */ + static int +parse_diff_unified( + char_u *line, + diffhunk_T *hunk) +{ + char_u *p; + long oldline, oldcount, newline, newcount; + + // Parse unified diff hunk header: + // @@ -oldline,oldcount +newline,newcount @@ + p = line; + if (*p++ == '@' && *p++ == '@' && *p++ == ' ' && *p++ == '-') + { + oldline = getdigits(&p); + if (*p == ',') + { + ++p; + oldcount = getdigits(&p); + } + else + oldcount = 1; + if (*p++ == ' ' && *p++ == '+') + { + newline = getdigits(&p); + if (*p == ',') + { + ++p; + newcount = getdigits(&p); + } + else + newcount = 1; + } + else + return FAIL; // invalid diff format + + if (oldcount == 0) + oldline += 1; + if (newcount == 0) + newline += 1; + if (newline == 0) + newline = 1; + + hunk->lnum_orig = oldline; + hunk->count_orig = oldcount; + hunk->lnum_new = newline; + hunk->count_new = newcount; + + return OK; + } + + return FAIL; +} + +/* + * Callback function for the xdl_diff() function. + * Stores the diff output in a grow array. + */ + static int +xdiff_out( + long start_a, + long count_a, + long start_b, + long count_b, + void *priv) +{ + diffout_T *dout = (diffout_T *)priv; + diffhunk_T *p = ALLOC_ONE(diffhunk_T); + + if (p == NULL) + return -1; + + if (ga_grow(&dout->dout_ga, 1) == FAIL) + { + vim_free(p); + return -1; + } + + p->lnum_orig = start_a + 1; + p->count_orig = count_a; + p->lnum_new = start_b + 1; + p->count_new = count_b; + ((diffhunk_T **)dout->dout_ga.ga_data)[dout->dout_ga.ga_len++] = p; + return 0; +} + +#endif // FEAT_DIFF + +#if defined(FEAT_EVAL) || defined(PROTO) + +/* + * "diff_filler()" function + */ + void +f_diff_filler(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ +#ifdef FEAT_DIFF + if (in_vim9script() && check_for_lnum_arg(argvars, 0) == FAIL) + return; + + rettv->vval.v_number = diff_check_fill(curwin, tv_get_lnum(argvars)); +#endif +} + +/* + * "diff_hlID()" function + */ + void +f_diff_hlID(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ +#ifdef FEAT_DIFF + linenr_T lnum; + static linenr_T prev_lnum = 0; + static varnumber_T changedtick = 0; + static int fnum = 0; + static int change_start = 0; + static int change_end = 0; + static hlf_T hlID = (hlf_T)0; + int filler_lines; + int col; + + if (in_vim9script() + && (check_for_lnum_arg(argvars,0) == FAIL + || check_for_number_arg(argvars, 1) == FAIL)) + return; + + lnum = tv_get_lnum(argvars); + if (lnum < 0) // ignore type error in {lnum} arg + lnum = 0; + if (lnum != prev_lnum + || changedtick != CHANGEDTICK(curbuf) + || fnum != curbuf->b_fnum) + { + // New line, buffer, change: need to get the values. + filler_lines = diff_check(curwin, lnum); + if (filler_lines < 0) + { + if (filler_lines == -1) + { + change_start = MAXCOL; + change_end = -1; + if (diff_find_change(curwin, lnum, &change_start, &change_end)) + hlID = HLF_ADD; // added line + else + hlID = HLF_CHD; // changed line + } + else + hlID = HLF_ADD; // added line + } + else + hlID = (hlf_T)0; + prev_lnum = lnum; + changedtick = CHANGEDTICK(curbuf); + fnum = curbuf->b_fnum; + } + + if (hlID == HLF_CHD || hlID == HLF_TXD) + { + col = tv_get_number(&argvars[1]) - 1; // ignore type error in {col} + if (col >= change_start && col <= change_end) + hlID = HLF_TXD; // changed text + else + hlID = HLF_CHD; // changed line + } + rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)hlID; +#endif +} + +#endif diff --git a/userland/apps/vim/src/digraph.c b/userland/apps/vim/src/digraph.c new file mode 100644 index 00000000..f19e58ec --- /dev/null +++ b/userland/apps/vim/src/digraph.c @@ -0,0 +1,2409 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * digraph.c: code for digraphs + */ + +#include "vim.h" + +#if defined(FEAT_DIGRAPHS) || defined(PROTO) + +typedef int result_T; + +typedef struct digraph +{ + char_u char1; + char_u char2; + result_T result; +} digr_T; + +static void printdigraph(digr_T *dp, result_T *previous); + +// digraphs added by the user +static garray_T user_digraphs = {0, 0, (int)sizeof(digr_T), 10, NULL}; + +/* + * digraphs for Unicode from RFC1345 + * (also work for ISO-8859-1 aka latin1) + * + * Note: Characters marked with XX are not included literally, because some + * compilers cannot handle them (Amiga SAS/C is the most picky one). + */ +static digr_T digraphdefault[] = { + {'N', 'U', 0x0a}, // LF for NUL + {'S', 'H', 0x01}, + {'S', 'X', 0x02}, + {'E', 'X', 0x03}, + {'E', 'T', 0x04}, + {'E', 'Q', 0x05}, + {'A', 'K', 0x06}, + {'B', 'L', 0x07}, + {'B', 'S', 0x08}, + {'H', 'T', 0x09}, + {'L', 'F', 0x0a}, + {'V', 'T', 0x0b}, + {'F', 'F', 0x0c}, + {'C', 'R', 0x0d}, + {'S', 'O', 0x0e}, + {'S', 'I', 0x0f}, + {'D', 'L', 0x10}, + {'D', '1', 0x11}, + {'D', '2', 0x12}, + {'D', '3', 0x13}, + {'D', '4', 0x14}, + {'N', 'K', 0x15}, + {'S', 'Y', 0x16}, + {'E', 'B', 0x17}, + {'C', 'N', 0x18}, + {'E', 'M', 0x19}, + {'S', 'B', 0x1a}, + {'E', 'C', 0x1b}, + {'F', 'S', 0x1c}, + {'G', 'S', 0x1d}, + {'R', 'S', 0x1e}, + {'U', 'S', 0x1f}, + {'S', 'P', 0x20}, + {'N', 'b', 0x23}, + {'D', 'O', 0x24}, + {'A', 't', 0x40}, + {'<', '(', 0x5b}, + {'/', '/', 0x5c}, + {')', '>', 0x5d}, + {'\'', '>', 0x5e}, + {'\'', '!', 0x60}, + {'(', '!', 0x7b}, + {'!', '!', 0x7c}, + {'!', ')', 0x7d}, + {'\'', '?', 0x7e}, + {'D', 'T', 0x7f}, + {'P', 'A', 0x80}, + {'H', 'O', 0x81}, + {'B', 'H', 0x82}, + {'N', 'H', 0x83}, + {'I', 'N', 0x84}, + {'N', 'L', 0x85}, + {'S', 'A', 0x86}, + {'E', 'S', 0x87}, + {'H', 'S', 0x88}, + {'H', 'J', 0x89}, + {'V', 'S', 0x8a}, + {'P', 'D', 0x8b}, + {'P', 'U', 0x8c}, + {'R', 'I', 0x8d}, + {'S', '2', 0x8e}, + {'S', '3', 0x8f}, + {'D', 'C', 0x90}, + {'P', '1', 0x91}, + {'P', '2', 0x92}, + {'T', 'S', 0x93}, + {'C', 'C', 0x94}, + {'M', 'W', 0x95}, + {'S', 'G', 0x96}, + {'E', 'G', 0x97}, + {'S', 'S', 0x98}, + {'G', 'C', 0x99}, + {'S', 'C', 0x9a}, + {'C', 'I', 0x9b}, + {'S', 'T', 0x9c}, + {'O', 'C', 0x9d}, + {'P', 'M', 0x9e}, + {'A', 'C', 0x9f}, + {'N', 'S', 0xa0}, +# define DG_START_LATIN 0xa1 + {'!', 'I', 0xa1}, + {'~', '!', 0xa1}, // ¡ Vim 5.x compatible + {'C', 't', 0xa2}, + {'c', '|', 0xa2}, // ¢ Vim 5.x compatible + {'P', 'd', 0xa3}, + {'$', '$', 0xa3}, // £ Vim 5.x compatible + {'C', 'u', 0xa4}, + {'o', 'x', 0xa4}, // ¤ Vim 5.x compatible + {'Y', 'e', 0xa5}, + {'Y', '-', 0xa5}, // ¥ Vim 5.x compatible + {'B', 'B', 0xa6}, + {'|', '|', 0xa6}, // ¦ Vim 5.x compatible + {'S', 'E', 0xa7}, + {'\'', ':', 0xa8}, + {'C', 'o', 0xa9}, + {'c', 'O', 0xa9}, // © Vim 5.x compatible + {'-', 'a', 0xaa}, + {'<', '<', 0xab}, + {'N', 'O', 0xac}, + {'-', ',', 0xac}, // ¬ Vim 5.x compatible + {'-', '-', 0xad}, + {'R', 'g', 0xae}, + {'\'', 'm', 0xaf}, + {'-', '=', 0xaf}, // ¯ Vim 5.x compatible + {'D', 'G', 0xb0}, + {'~', 'o', 0xb0}, // ° Vim 5.x compatible + {'+', '-', 0xb1}, + {'2', 'S', 0xb2}, + {'2', '2', 0xb2}, // ² Vim 5.x compatible + {'3', 'S', 0xb3}, + {'3', '3', 0xb3}, // ³ Vim 5.x compatible + {'\'', '\'', 0xb4}, + {'M', 'y', 0xb5}, + {'P', 'I', 0xb6}, + {'p', 'p', 0xb6}, // ¶ Vim 5.x compatible + {'.', 'M', 0xb7}, + {'~', '.', 0xb7}, // · Vim 5.x compatible + {'\'', ',', 0xb8}, + {'1', 'S', 0xb9}, + {'1', '1', 0xb9}, // ¹ Vim 5.x compatible + {'-', 'o', 0xba}, + {'>', '>', 0xbb}, + {'1', '4', 0xbc}, + {'1', '2', 0xbd}, + {'3', '4', 0xbe}, + {'?', 'I', 0xbf}, + {'~', '?', 0xbf}, // ¿ Vim 5.x compatible + {'A', '!', 0xc0}, + {'A', '`', 0xc0}, // À Vim 5.x compatible + {'A', '\'', 0xc1}, + {'A', '>', 0xc2}, + {'A', '^', 0xc2}, // Â Vim 5.x compatible + {'A', '?', 0xc3}, + {'A', '~', 0xc3}, // Ã Vim 5.x compatible + {'A', ':', 0xc4}, + {'A', '"', 0xc4}, // Ä Vim 5.x compatible + {'A', 'A', 0xc5}, + {'A', '@', 0xc5}, // Å Vim 5.x compatible + {'A', 'E', 0xc6}, + {'C', ',', 0xc7}, + {'E', '!', 0xc8}, + {'E', '`', 0xc8}, // È Vim 5.x compatible + {'E', '\'', 0xc9}, + {'E', '>', 0xca}, + {'E', '^', 0xca}, // Ê Vim 5.x compatible + {'E', ':', 0xcb}, + {'E', '"', 0xcb}, // Ë Vim 5.x compatible + {'I', '!', 0xcc}, + {'I', '`', 0xcc}, // Ì Vim 5.x compatible + {'I', '\'', 0xcd}, + {'I', '>', 0xce}, + {'I', '^', 0xce}, // Î Vim 5.x compatible + {'I', ':', 0xcf}, + {'I', '"', 0xcf}, // Ï Vim 5.x compatible + {'D', '-', 0xd0}, + {'N', '?', 0xd1}, + {'N', '~', 0xd1}, // Ñ Vim 5.x compatible + {'O', '!', 0xd2}, + {'O', '`', 0xd2}, // Ò Vim 5.x compatible + {'O', '\'', 0xd3}, + {'O', '>', 0xd4}, + {'O', '^', 0xd4}, // Ô Vim 5.x compatible + {'O', '?', 0xd5}, + {'O', '~', 0xd5}, // Õ Vim 5.x compatible + {'O', ':', 0xd6}, + {'*', 'X', 0xd7}, + {'/', '\\', 0xd7}, // × Vim 5.x compatible + {'O', '/', 0xd8}, + {'U', '!', 0xd9}, + {'U', '`', 0xd9}, // Ù Vim 5.x compatible + {'U', '\'', 0xda}, + {'U', '>', 0xdb}, + {'U', '^', 0xdb}, // Û Vim 5.x compatible + {'U', ':', 0xdc}, + {'Y', '\'', 0xdd}, + {'T', 'H', 0xde}, + {'I', 'p', 0xde}, // Þ Vim 5.x compatible + {'s', 's', 0xdf}, + {'a', '!', 0xe0}, + {'a', '`', 0xe0}, // à Vim 5.x compatible + {'a', '\'', 0xe1}, + {'a', '>', 0xe2}, + {'a', '^', 0xe2}, // â Vim 5.x compatible + {'a', '?', 0xe3}, + {'a', '~', 0xe3}, // ã Vim 5.x compatible + {'a', ':', 0xe4}, + {'a', '"', 0xe4}, // ä Vim 5.x compatible + {'a', 'a', 0xe5}, + {'a', '@', 0xe5}, // å Vim 5.x compatible + {'a', 'e', 0xe6}, + {'c', ',', 0xe7}, + {'e', '!', 0xe8}, + {'e', '`', 0xe8}, // è Vim 5.x compatible + {'e', '\'', 0xe9}, + {'e', '>', 0xea}, + {'e', '^', 0xea}, // ê Vim 5.x compatible + {'e', ':', 0xeb}, + {'e', '"', 0xeb}, // ë Vim 5.x compatible + {'i', '!', 0xec}, + {'i', '`', 0xec}, // ì Vim 5.x compatible + {'i', '\'', 0xed}, + {'i', '>', 0xee}, + {'i', '^', 0xee}, // î Vim 5.x compatible + {'i', ':', 0xef}, + {'d', '-', 0xf0}, + {'n', '?', 0xf1}, + {'n', '~', 0xf1}, // ñ Vim 5.x compatible + {'o', '!', 0xf2}, + {'o', '`', 0xf2}, // ò Vim 5.x compatible + {'o', '\'', 0xf3}, + {'o', '>', 0xf4}, + {'o', '^', 0xf4}, // ô Vim 5.x compatible + {'o', '?', 0xf5}, + {'o', '~', 0xf5}, // õ Vim 5.x compatible + {'o', ':', 0xf6}, + {'-', ':', 0xf7}, + {'o', '/', 0xf8}, + {'u', '!', 0xf9}, + {'u', '`', 0xf9}, // ù Vim 5.x compatible + {'u', '\'', 0xfa}, + {'u', '>', 0xfb}, + {'u', '^', 0xfb}, // û Vim 5.x compatible + {'u', ':', 0xfc}, + {'y', '\'', 0xfd}, + {'t', 'h', 0xfe}, + {'y', ':', 0xff}, + {'y', '"', 0xff}, // x XX Vim 5.x compatible + +# define USE_UNICODE_DIGRAPHS + + {'A', '-', 0x0100}, + {'a', '-', 0x0101}, + {'A', '(', 0x0102}, + {'a', '(', 0x0103}, + {'A', ';', 0x0104}, + {'a', ';', 0x0105}, + {'C', '\'', 0x0106}, + {'c', '\'', 0x0107}, + {'C', '>', 0x0108}, + {'c', '>', 0x0109}, + {'C', '.', 0x010a}, + {'c', '.', 0x010b}, + {'C', '<', 0x010c}, + {'c', '<', 0x010d}, + {'D', '<', 0x010e}, + {'d', '<', 0x010f}, + {'D', '/', 0x0110}, + {'d', '/', 0x0111}, + {'E', '-', 0x0112}, + {'e', '-', 0x0113}, + {'E', '(', 0x0114}, + {'e', '(', 0x0115}, + {'E', '.', 0x0116}, + {'e', '.', 0x0117}, + {'E', ';', 0x0118}, + {'e', ';', 0x0119}, + {'E', '<', 0x011a}, + {'e', '<', 0x011b}, + {'G', '>', 0x011c}, + {'g', '>', 0x011d}, + {'G', '(', 0x011e}, + {'g', '(', 0x011f}, + {'G', '.', 0x0120}, + {'g', '.', 0x0121}, + {'G', ',', 0x0122}, + {'g', ',', 0x0123}, + {'H', '>', 0x0124}, + {'h', '>', 0x0125}, + {'H', '/', 0x0126}, + {'h', '/', 0x0127}, + {'I', '?', 0x0128}, + {'i', '?', 0x0129}, + {'I', '-', 0x012a}, + {'i', '-', 0x012b}, + {'I', '(', 0x012c}, + {'i', '(', 0x012d}, + {'I', ';', 0x012e}, + {'i', ';', 0x012f}, + {'I', '.', 0x0130}, + {'i', '.', 0x0131}, + {'I', 'J', 0x0132}, + {'i', 'j', 0x0133}, + {'J', '>', 0x0134}, + {'j', '>', 0x0135}, + {'K', ',', 0x0136}, + {'k', ',', 0x0137}, + {'k', 'k', 0x0138}, + {'L', '\'', 0x0139}, + {'l', '\'', 0x013a}, + {'L', ',', 0x013b}, + {'l', ',', 0x013c}, + {'L', '<', 0x013d}, + {'l', '<', 0x013e}, + {'L', '.', 0x013f}, + {'l', '.', 0x0140}, + {'L', '/', 0x0141}, + {'l', '/', 0x0142}, + {'N', '\'', 0x0143}, + {'n', '\'', 0x0144}, + {'N', ',', 0x0145}, + {'n', ',', 0x0146}, + {'N', '<', 0x0147}, + {'n', '<', 0x0148}, + {'\'', 'n', 0x0149}, + {'N', 'G', 0x014a}, + {'n', 'g', 0x014b}, + {'O', '-', 0x014c}, + {'o', '-', 0x014d}, + {'O', '(', 0x014e}, + {'o', '(', 0x014f}, + {'O', '"', 0x0150}, + {'o', '"', 0x0151}, + {'O', 'E', 0x0152}, + {'o', 'e', 0x0153}, + {'R', '\'', 0x0154}, + {'r', '\'', 0x0155}, + {'R', ',', 0x0156}, + {'r', ',', 0x0157}, + {'R', '<', 0x0158}, + {'r', '<', 0x0159}, + {'S', '\'', 0x015a}, + {'s', '\'', 0x015b}, + {'S', '>', 0x015c}, + {'s', '>', 0x015d}, + {'S', ',', 0x015e}, + {'s', ',', 0x015f}, + {'S', '<', 0x0160}, + {'s', '<', 0x0161}, + {'T', ',', 0x0162}, + {'t', ',', 0x0163}, + {'T', '<', 0x0164}, + {'t', '<', 0x0165}, + {'T', '/', 0x0166}, + {'t', '/', 0x0167}, + {'U', '?', 0x0168}, + {'u', '?', 0x0169}, + {'U', '-', 0x016a}, + {'u', '-', 0x016b}, + {'U', '(', 0x016c}, + {'u', '(', 0x016d}, + {'U', '0', 0x016e}, + {'u', '0', 0x016f}, + {'U', '"', 0x0170}, + {'u', '"', 0x0171}, + {'U', ';', 0x0172}, + {'u', ';', 0x0173}, + {'W', '>', 0x0174}, + {'w', '>', 0x0175}, + {'Y', '>', 0x0176}, + {'y', '>', 0x0177}, + {'Y', ':', 0x0178}, + {'Z', '\'', 0x0179}, + {'z', '\'', 0x017a}, + {'Z', '.', 0x017b}, + {'z', '.', 0x017c}, + {'Z', '<', 0x017d}, + {'z', '<', 0x017e}, + {'O', '9', 0x01a0}, + {'o', '9', 0x01a1}, + {'O', 'I', 0x01a2}, + {'o', 'i', 0x01a3}, + {'y', 'r', 0x01a6}, + {'U', '9', 0x01af}, + {'u', '9', 0x01b0}, + {'Z', '/', 0x01b5}, + {'z', '/', 0x01b6}, + {'E', 'D', 0x01b7}, + {'A', '<', 0x01cd}, + {'a', '<', 0x01ce}, + {'I', '<', 0x01cf}, + {'i', '<', 0x01d0}, + {'O', '<', 0x01d1}, + {'o', '<', 0x01d2}, + {'U', '<', 0x01d3}, + {'u', '<', 0x01d4}, + {'A', '1', 0x01de}, + {'a', '1', 0x01df}, + {'A', '7', 0x01e0}, + {'a', '7', 0x01e1}, + {'A', '3', 0x01e2}, + {'a', '3', 0x01e3}, + {'G', '/', 0x01e4}, + {'g', '/', 0x01e5}, + {'G', '<', 0x01e6}, + {'g', '<', 0x01e7}, + {'K', '<', 0x01e8}, + {'k', '<', 0x01e9}, + {'O', ';', 0x01ea}, + {'o', ';', 0x01eb}, + {'O', '1', 0x01ec}, + {'o', '1', 0x01ed}, + {'E', 'Z', 0x01ee}, + {'e', 'z', 0x01ef}, + {'j', '<', 0x01f0}, + {'G', '\'', 0x01f4}, + {'g', '\'', 0x01f5}, + {';', 'S', 0x02bf}, + {'\'', '<', 0x02c7}, + {'\'', '(', 0x02d8}, + {'\'', '.', 0x02d9}, + {'\'', '0', 0x02da}, + {'\'', ';', 0x02db}, + {'\'', '"', 0x02dd}, +# define DG_START_GREEK 0x0386 + {'A', '%', 0x0386}, + {'E', '%', 0x0388}, + {'Y', '%', 0x0389}, + {'I', '%', 0x038a}, + {'O', '%', 0x038c}, + {'U', '%', 0x038e}, + {'W', '%', 0x038f}, + {'i', '3', 0x0390}, + {'A', '*', 0x0391}, + {'B', '*', 0x0392}, + {'G', '*', 0x0393}, + {'D', '*', 0x0394}, + {'E', '*', 0x0395}, + {'Z', '*', 0x0396}, + {'Y', '*', 0x0397}, + {'H', '*', 0x0398}, + {'I', '*', 0x0399}, + {'K', '*', 0x039a}, + {'L', '*', 0x039b}, + {'M', '*', 0x039c}, + {'N', '*', 0x039d}, + {'C', '*', 0x039e}, + {'O', '*', 0x039f}, + {'P', '*', 0x03a0}, + {'R', '*', 0x03a1}, + {'S', '*', 0x03a3}, + {'T', '*', 0x03a4}, + {'U', '*', 0x03a5}, + {'F', '*', 0x03a6}, + {'X', '*', 0x03a7}, + {'Q', '*', 0x03a8}, + {'W', '*', 0x03a9}, + {'J', '*', 0x03aa}, + {'V', '*', 0x03ab}, + {'a', '%', 0x03ac}, + {'e', '%', 0x03ad}, + {'y', '%', 0x03ae}, + {'i', '%', 0x03af}, + {'u', '3', 0x03b0}, + {'a', '*', 0x03b1}, + {'b', '*', 0x03b2}, + {'g', '*', 0x03b3}, + {'d', '*', 0x03b4}, + {'e', '*', 0x03b5}, + {'z', '*', 0x03b6}, + {'y', '*', 0x03b7}, + {'h', '*', 0x03b8}, + {'i', '*', 0x03b9}, + {'k', '*', 0x03ba}, + {'l', '*', 0x03bb}, + {'m', '*', 0x03bc}, + {'n', '*', 0x03bd}, + {'c', '*', 0x03be}, + {'o', '*', 0x03bf}, + {'p', '*', 0x03c0}, + {'r', '*', 0x03c1}, + {'*', 's', 0x03c2}, + {'s', '*', 0x03c3}, + {'t', '*', 0x03c4}, + {'u', '*', 0x03c5}, + {'f', '*', 0x03c6}, + {'x', '*', 0x03c7}, + {'q', '*', 0x03c8}, + {'w', '*', 0x03c9}, + {'j', '*', 0x03ca}, + {'v', '*', 0x03cb}, + {'o', '%', 0x03cc}, + {'u', '%', 0x03cd}, + {'w', '%', 0x03ce}, + {'\'', 'G', 0x03d8}, + {',', 'G', 0x03d9}, + {'T', '3', 0x03da}, + {'t', '3', 0x03db}, + {'M', '3', 0x03dc}, + {'m', '3', 0x03dd}, + {'K', '3', 0x03de}, + {'k', '3', 0x03df}, + {'P', '3', 0x03e0}, + {'p', '3', 0x03e1}, + {'\'', '%', 0x03f4}, + {'j', '3', 0x03f5}, +# define DG_START_CYRILLIC 0x0401 + {'I', 'O', 0x0401}, + {'D', '%', 0x0402}, + {'G', '%', 0x0403}, + {'I', 'E', 0x0404}, + {'D', 'S', 0x0405}, + {'I', 'I', 0x0406}, + {'Y', 'I', 0x0407}, + {'J', '%', 0x0408}, + {'L', 'J', 0x0409}, + {'N', 'J', 0x040a}, + {'T', 's', 0x040b}, + {'K', 'J', 0x040c}, + {'V', '%', 0x040e}, + {'D', 'Z', 0x040f}, + {'A', '=', 0x0410}, + {'B', '=', 0x0411}, + {'V', '=', 0x0412}, + {'G', '=', 0x0413}, + {'D', '=', 0x0414}, + {'E', '=', 0x0415}, + {'Z', '%', 0x0416}, + {'Z', '=', 0x0417}, + {'I', '=', 0x0418}, + {'J', '=', 0x0419}, + {'K', '=', 0x041a}, + {'L', '=', 0x041b}, + {'M', '=', 0x041c}, + {'N', '=', 0x041d}, + {'O', '=', 0x041e}, + {'P', '=', 0x041f}, + {'R', '=', 0x0420}, + {'S', '=', 0x0421}, + {'T', '=', 0x0422}, + {'U', '=', 0x0423}, + {'F', '=', 0x0424}, + {'H', '=', 0x0425}, + {'C', '=', 0x0426}, + {'C', '%', 0x0427}, + {'S', '%', 0x0428}, + {'S', 'c', 0x0429}, + {'=', '"', 0x042a}, + {'Y', '=', 0x042b}, + {'%', '"', 0x042c}, + {'J', 'E', 0x042d}, + {'J', 'U', 0x042e}, + {'J', 'A', 0x042f}, + {'a', '=', 0x0430}, + {'b', '=', 0x0431}, + {'v', '=', 0x0432}, + {'g', '=', 0x0433}, + {'d', '=', 0x0434}, + {'e', '=', 0x0435}, + {'z', '%', 0x0436}, + {'z', '=', 0x0437}, + {'i', '=', 0x0438}, + {'j', '=', 0x0439}, + {'k', '=', 0x043a}, + {'l', '=', 0x043b}, + {'m', '=', 0x043c}, + {'n', '=', 0x043d}, + {'o', '=', 0x043e}, + {'p', '=', 0x043f}, + {'r', '=', 0x0440}, + {'s', '=', 0x0441}, + {'t', '=', 0x0442}, + {'u', '=', 0x0443}, + {'f', '=', 0x0444}, + {'h', '=', 0x0445}, + {'c', '=', 0x0446}, + {'c', '%', 0x0447}, + {'s', '%', 0x0448}, + {'s', 'c', 0x0449}, + {'=', '\'', 0x044a}, + {'y', '=', 0x044b}, + {'%', '\'', 0x044c}, + {'j', 'e', 0x044d}, + {'j', 'u', 0x044e}, + {'j', 'a', 0x044f}, + {'i', 'o', 0x0451}, + {'d', '%', 0x0452}, + {'g', '%', 0x0453}, + {'i', 'e', 0x0454}, + {'d', 's', 0x0455}, + {'i', 'i', 0x0456}, + {'y', 'i', 0x0457}, + {'j', '%', 0x0458}, + {'l', 'j', 0x0459}, + {'n', 'j', 0x045a}, + {'t', 's', 0x045b}, + {'k', 'j', 0x045c}, + {'v', '%', 0x045e}, + {'d', 'z', 0x045f}, + {'Y', '3', 0x0462}, + {'y', '3', 0x0463}, + {'O', '3', 0x046a}, + {'o', '3', 0x046b}, + {'F', '3', 0x0472}, + {'f', '3', 0x0473}, + {'V', '3', 0x0474}, + {'v', '3', 0x0475}, + {'C', '3', 0x0480}, + {'c', '3', 0x0481}, + {'G', '3', 0x0490}, + {'g', '3', 0x0491}, +# define DG_START_HEBREW 0x05d0 + {'A', '+', 0x05d0}, + {'B', '+', 0x05d1}, + {'G', '+', 0x05d2}, + {'D', '+', 0x05d3}, + {'H', '+', 0x05d4}, + {'W', '+', 0x05d5}, + {'Z', '+', 0x05d6}, + {'X', '+', 0x05d7}, + {'T', 'j', 0x05d8}, + {'J', '+', 0x05d9}, + {'K', '%', 0x05da}, + {'K', '+', 0x05db}, + {'L', '+', 0x05dc}, + {'M', '%', 0x05dd}, + {'M', '+', 0x05de}, + {'N', '%', 0x05df}, + {'N', '+', 0x05e0}, + {'S', '+', 0x05e1}, + {'E', '+', 0x05e2}, + {'P', '%', 0x05e3}, + {'P', '+', 0x05e4}, + {'Z', 'j', 0x05e5}, + {'Z', 'J', 0x05e6}, + {'Q', '+', 0x05e7}, + {'R', '+', 0x05e8}, + {'S', 'h', 0x05e9}, + {'T', '+', 0x05ea}, +# define DG_START_ARABIC 0x060c + {',', '+', 0x060c}, + {';', '+', 0x061b}, + {'?', '+', 0x061f}, + {'H', '\'', 0x0621}, + {'a', 'M', 0x0622}, + {'a', 'H', 0x0623}, + {'w', 'H', 0x0624}, + {'a', 'h', 0x0625}, + {'y', 'H', 0x0626}, + {'a', '+', 0x0627}, + {'b', '+', 0x0628}, + {'t', 'm', 0x0629}, + {'t', '+', 0x062a}, + {'t', 'k', 0x062b}, + {'g', '+', 0x062c}, + {'h', 'k', 0x062d}, + {'x', '+', 0x062e}, + {'d', '+', 0x062f}, + {'d', 'k', 0x0630}, + {'r', '+', 0x0631}, + {'z', '+', 0x0632}, + {'s', '+', 0x0633}, + {'s', 'n', 0x0634}, + {'c', '+', 0x0635}, + {'d', 'd', 0x0636}, + {'t', 'j', 0x0637}, + {'z', 'H', 0x0638}, + {'e', '+', 0x0639}, + {'i', '+', 0x063a}, + {'+', '+', 0x0640}, + {'f', '+', 0x0641}, + {'q', '+', 0x0642}, + {'k', '+', 0x0643}, + {'l', '+', 0x0644}, + {'m', '+', 0x0645}, + {'n', '+', 0x0646}, + {'h', '+', 0x0647}, + {'w', '+', 0x0648}, + {'j', '+', 0x0649}, + {'y', '+', 0x064a}, + {':', '+', 0x064b}, + {'"', '+', 0x064c}, + {'=', '+', 0x064d}, + {'/', '+', 0x064e}, + {'\'', '+', 0x064f}, + {'1', '+', 0x0650}, + {'3', '+', 0x0651}, + {'0', '+', 0x0652}, + {'a', 'S', 0x0670}, + {'p', '+', 0x067e}, + {'v', '+', 0x06a4}, + {'g', 'f', 0x06af}, + {'0', 'a', 0x06f0}, + {'1', 'a', 0x06f1}, + {'2', 'a', 0x06f2}, + {'3', 'a', 0x06f3}, + {'4', 'a', 0x06f4}, + {'5', 'a', 0x06f5}, + {'6', 'a', 0x06f6}, + {'7', 'a', 0x06f7}, + {'8', 'a', 0x06f8}, + {'9', 'a', 0x06f9}, +# define DG_START_LATIN_EXTENDED 0x1e02 + {'B', '.', 0x1e02}, + {'b', '.', 0x1e03}, + {'B', '_', 0x1e06}, + {'b', '_', 0x1e07}, + {'D', '.', 0x1e0a}, + {'d', '.', 0x1e0b}, + {'D', '_', 0x1e0e}, + {'d', '_', 0x1e0f}, + {'D', ',', 0x1e10}, + {'d', ',', 0x1e11}, + {'F', '.', 0x1e1e}, + {'f', '.', 0x1e1f}, + {'G', '-', 0x1e20}, + {'g', '-', 0x1e21}, + {'H', '.', 0x1e22}, + {'h', '.', 0x1e23}, + {'H', ':', 0x1e26}, + {'h', ':', 0x1e27}, + {'H', ',', 0x1e28}, + {'h', ',', 0x1e29}, + {'K', '\'', 0x1e30}, + {'k', '\'', 0x1e31}, + {'K', '_', 0x1e34}, + {'k', '_', 0x1e35}, + {'L', '_', 0x1e3a}, + {'l', '_', 0x1e3b}, + {'M', '\'', 0x1e3e}, + {'m', '\'', 0x1e3f}, + {'M', '.', 0x1e40}, + {'m', '.', 0x1e41}, + {'N', '.', 0x1e44}, + {'n', '.', 0x1e45}, + {'N', '_', 0x1e48}, + {'n', '_', 0x1e49}, + {'P', '\'', 0x1e54}, + {'p', '\'', 0x1e55}, + {'P', '.', 0x1e56}, + {'p', '.', 0x1e57}, + {'R', '.', 0x1e58}, + {'r', '.', 0x1e59}, + {'R', '_', 0x1e5e}, + {'r', '_', 0x1e5f}, + {'S', '.', 0x1e60}, + {'s', '.', 0x1e61}, + {'T', '.', 0x1e6a}, + {'t', '.', 0x1e6b}, + {'T', '_', 0x1e6e}, + {'t', '_', 0x1e6f}, + {'V', '?', 0x1e7c}, + {'v', '?', 0x1e7d}, + {'W', '!', 0x1e80}, + {'W', '`', 0x1e80}, // extra alternative, easier to remember + {'w', '!', 0x1e81}, + {'w', '`', 0x1e81}, // extra alternative, easier to remember + {'W', '\'', 0x1e82}, + {'w', '\'', 0x1e83}, + {'W', ':', 0x1e84}, + {'w', ':', 0x1e85}, + {'W', '.', 0x1e86}, + {'w', '.', 0x1e87}, + {'X', '.', 0x1e8a}, + {'x', '.', 0x1e8b}, + {'X', ':', 0x1e8c}, + {'x', ':', 0x1e8d}, + {'Y', '.', 0x1e8e}, + {'y', '.', 0x1e8f}, + {'Z', '>', 0x1e90}, + {'z', '>', 0x1e91}, + {'Z', '_', 0x1e94}, + {'z', '_', 0x1e95}, + {'h', '_', 0x1e96}, + {'t', ':', 0x1e97}, + {'w', '0', 0x1e98}, + {'y', '0', 0x1e99}, + {'A', '2', 0x1ea2}, + {'a', '2', 0x1ea3}, + {'E', '2', 0x1eba}, + {'e', '2', 0x1ebb}, + {'E', '?', 0x1ebc}, + {'e', '?', 0x1ebd}, + {'I', '2', 0x1ec8}, + {'i', '2', 0x1ec9}, + {'O', '2', 0x1ece}, + {'o', '2', 0x1ecf}, + {'U', '2', 0x1ee6}, + {'u', '2', 0x1ee7}, + {'Y', '!', 0x1ef2}, + {'Y', '`', 0x1ef2}, // extra alternative, easier to remember + {'y', '!', 0x1ef3}, + {'y', '`', 0x1ef3}, // extra alternative, easier to remember + {'Y', '2', 0x1ef6}, + {'y', '2', 0x1ef7}, + {'Y', '?', 0x1ef8}, + {'y', '?', 0x1ef9}, +# define DG_START_GREEK_EXTENDED 0x1f00 + {';', '\'', 0x1f00}, + {',', '\'', 0x1f01}, + {';', '!', 0x1f02}, + {',', '!', 0x1f03}, + {'?', ';', 0x1f04}, + {'?', ',', 0x1f05}, + {'!', ':', 0x1f06}, + {'?', ':', 0x1f07}, +# define DG_START_PUNCTUATION 0x2002 + {'1', 'N', 0x2002}, + {'1', 'M', 0x2003}, + {'3', 'M', 0x2004}, + {'4', 'M', 0x2005}, + {'6', 'M', 0x2006}, + {'1', 'T', 0x2009}, + {'1', 'H', 0x200a}, + {'-', '1', 0x2010}, + {'-', 'N', 0x2013}, + {'-', 'M', 0x2014}, + {'-', '3', 0x2015}, + {'!', '2', 0x2016}, + {'=', '2', 0x2017}, + {'\'', '6', 0x2018}, + {'\'', '9', 0x2019}, + {'.', '9', 0x201a}, + {'9', '\'', 0x201b}, + {'"', '6', 0x201c}, + {'"', '9', 0x201d}, + {':', '9', 0x201e}, + {'9', '"', 0x201f}, + {'/', '-', 0x2020}, + {'/', '=', 0x2021}, + {'o', 'o', 0x2022}, + {'.', '.', 0x2025}, + {',', '.', 0x2026}, + {'%', '0', 0x2030}, + {'1', '\'', 0x2032}, + {'2', '\'', 0x2033}, + {'3', '\'', 0x2034}, + {'4', '\'', 0x2057}, + {'1', '"', 0x2035}, + {'2', '"', 0x2036}, + {'3', '"', 0x2037}, + {'C', 'a', 0x2038}, + {'<', '1', 0x2039}, + {'>', '1', 0x203a}, + {':', 'X', 0x203b}, + {'\'', '-', 0x203e}, + {'/', 'f', 0x2044}, +# define DG_START_SUB_SUPER 0x2070 + {'0', 'S', 0x2070}, + {'4', 'S', 0x2074}, + {'5', 'S', 0x2075}, + {'6', 'S', 0x2076}, + {'7', 'S', 0x2077}, + {'8', 'S', 0x2078}, + {'9', 'S', 0x2079}, + {'+', 'S', 0x207a}, + {'-', 'S', 0x207b}, + {'=', 'S', 0x207c}, + {'(', 'S', 0x207d}, + {')', 'S', 0x207e}, + {'n', 'S', 0x207f}, + {'0', 's', 0x2080}, + {'1', 's', 0x2081}, + {'2', 's', 0x2082}, + {'3', 's', 0x2083}, + {'4', 's', 0x2084}, + {'5', 's', 0x2085}, + {'6', 's', 0x2086}, + {'7', 's', 0x2087}, + {'8', 's', 0x2088}, + {'9', 's', 0x2089}, + {'+', 's', 0x208a}, + {'-', 's', 0x208b}, + {'=', 's', 0x208c}, + {'(', 's', 0x208d}, + {')', 's', 0x208e}, +# define DG_START_CURRENCY 0x20a4 + {'L', 'i', 0x20a4}, + {'P', 't', 0x20a7}, + {'W', '=', 0x20a9}, + {'=', 'e', 0x20ac}, // euro + {'E', 'u', 0x20ac}, // euro + {'=', 'R', 0x20bd}, // rouble + {'=', 'P', 0x20bd}, // rouble +# define DG_START_OTHER1 0x2103 + {'o', 'C', 0x2103}, + {'c', 'o', 0x2105}, + {'o', 'F', 0x2109}, + {'N', '0', 0x2116}, + {'P', 'O', 0x2117}, + {'R', 'x', 0x211e}, + {'S', 'M', 0x2120}, + {'T', 'M', 0x2122}, + {'O', 'm', 0x2126}, + {'A', 'O', 0x212b}, + {'1', '3', 0x2153}, + {'2', '3', 0x2154}, + {'1', '5', 0x2155}, + {'2', '5', 0x2156}, + {'3', '5', 0x2157}, + {'4', '5', 0x2158}, + {'1', '6', 0x2159}, + {'5', '6', 0x215a}, + {'1', '8', 0x215b}, + {'3', '8', 0x215c}, + {'5', '8', 0x215d}, + {'7', '8', 0x215e}, +# define DG_START_ROMAN 0x2160 + {'1', 'R', 0x2160}, + {'2', 'R', 0x2161}, + {'3', 'R', 0x2162}, + {'4', 'R', 0x2163}, + {'5', 'R', 0x2164}, + {'6', 'R', 0x2165}, + {'7', 'R', 0x2166}, + {'8', 'R', 0x2167}, + {'9', 'R', 0x2168}, + {'a', 'R', 0x2169}, + {'b', 'R', 0x216a}, + {'c', 'R', 0x216b}, + {'1', 'r', 0x2170}, + {'2', 'r', 0x2171}, + {'3', 'r', 0x2172}, + {'4', 'r', 0x2173}, + {'5', 'r', 0x2174}, + {'6', 'r', 0x2175}, + {'7', 'r', 0x2176}, + {'8', 'r', 0x2177}, + {'9', 'r', 0x2178}, + {'a', 'r', 0x2179}, + {'b', 'r', 0x217a}, + {'c', 'r', 0x217b}, +# define DG_START_ARROWS 0x2190 + {'<', '-', 0x2190}, + {'-', '!', 0x2191}, + {'-', '>', 0x2192}, + {'-', 'v', 0x2193}, + {'<', '>', 0x2194}, + {'U', 'D', 0x2195}, + {'<', '=', 0x21d0}, + {'=', '>', 0x21d2}, + {'=', '=', 0x21d4}, +# define DG_START_MATH 0x2200 + {'F', 'A', 0x2200}, + {'d', 'P', 0x2202}, + {'T', 'E', 0x2203}, + {'/', '0', 0x2205}, + {'D', 'E', 0x2206}, + {'N', 'B', 0x2207}, + {'(', '-', 0x2208}, + {'-', ')', 0x220b}, + {'*', 'P', 0x220f}, + {'+', 'Z', 0x2211}, + {'-', '2', 0x2212}, + {'-', '+', 0x2213}, + {'*', '-', 0x2217}, + {'O', 'b', 0x2218}, + {'S', 'b', 0x2219}, + {'R', 'T', 0x221a}, + {'0', '(', 0x221d}, + {'0', '0', 0x221e}, + {'-', 'L', 0x221f}, + {'-', 'V', 0x2220}, + {'P', 'P', 0x2225}, + {'A', 'N', 0x2227}, + {'O', 'R', 0x2228}, + {'(', 'U', 0x2229}, + {')', 'U', 0x222a}, + {'I', 'n', 0x222b}, + {'D', 'I', 0x222c}, + {'I', 'o', 0x222e}, + {'.', ':', 0x2234}, + {':', '.', 0x2235}, + {':', 'R', 0x2236}, + {':', ':', 0x2237}, + {'?', '1', 0x223c}, + {'C', 'G', 0x223e}, + {'?', '-', 0x2243}, + {'?', '=', 0x2245}, + {'?', '2', 0x2248}, + {'=', '?', 0x224c}, + {'H', 'I', 0x2253}, + {'!', '=', 0x2260}, + {'=', '3', 0x2261}, + {'=', '<', 0x2264}, + {'>', '=', 0x2265}, + {'<', '*', 0x226a}, + {'*', '>', 0x226b}, + {'!', '<', 0x226e}, + {'!', '>', 0x226f}, + {'(', 'C', 0x2282}, + {')', 'C', 0x2283}, + {'(', '_', 0x2286}, + {')', '_', 0x2287}, + {'0', '.', 0x2299}, + {'0', '2', 0x229a}, + {'-', 'T', 0x22a5}, + {'.', 'P', 0x22c5}, + {':', '3', 0x22ee}, + {'.', '3', 0x22ef}, +# define DG_START_TECHNICAL 0x2302 + {'E', 'h', 0x2302}, + {'<', '7', 0x2308}, + {'>', '7', 0x2309}, + {'7', '<', 0x230a}, + {'7', '>', 0x230b}, + {'N', 'I', 0x2310}, + {'(', 'A', 0x2312}, + {'T', 'R', 0x2315}, + {'I', 'u', 0x2320}, + {'I', 'l', 0x2321}, + {'<', '/', 0x2329}, + {'/', '>', 0x232a}, +# define DG_START_OTHER2 0x2423 + {'V', 's', 0x2423}, + {'1', 'h', 0x2440}, + {'3', 'h', 0x2441}, + {'2', 'h', 0x2442}, + {'4', 'h', 0x2443}, + {'1', 'j', 0x2446}, + {'2', 'j', 0x2447}, + {'3', 'j', 0x2448}, + {'4', 'j', 0x2449}, + {'1', '.', 0x2488}, + {'2', '.', 0x2489}, + {'3', '.', 0x248a}, + {'4', '.', 0x248b}, + {'5', '.', 0x248c}, + {'6', '.', 0x248d}, + {'7', '.', 0x248e}, + {'8', '.', 0x248f}, + {'9', '.', 0x2490}, +# define DG_START_DRAWING 0x2500 + {'h', 'h', 0x2500}, + {'H', 'H', 0x2501}, + {'v', 'v', 0x2502}, + {'V', 'V', 0x2503}, + {'3', '-', 0x2504}, + {'3', '_', 0x2505}, + {'3', '!', 0x2506}, + {'3', '/', 0x2507}, + {'4', '-', 0x2508}, + {'4', '_', 0x2509}, + {'4', '!', 0x250a}, + {'4', '/', 0x250b}, + {'d', 'r', 0x250c}, + {'d', 'R', 0x250d}, + {'D', 'r', 0x250e}, + {'D', 'R', 0x250f}, + {'d', 'l', 0x2510}, + {'d', 'L', 0x2511}, + {'D', 'l', 0x2512}, + {'L', 'D', 0x2513}, + {'u', 'r', 0x2514}, + {'u', 'R', 0x2515}, + {'U', 'r', 0x2516}, + {'U', 'R', 0x2517}, + {'u', 'l', 0x2518}, + {'u', 'L', 0x2519}, + {'U', 'l', 0x251a}, + {'U', 'L', 0x251b}, + {'v', 'r', 0x251c}, + {'v', 'R', 0x251d}, + {'V', 'r', 0x2520}, + {'V', 'R', 0x2523}, + {'v', 'l', 0x2524}, + {'v', 'L', 0x2525}, + {'V', 'l', 0x2528}, + {'V', 'L', 0x252b}, + {'d', 'h', 0x252c}, + {'d', 'H', 0x252f}, + {'D', 'h', 0x2530}, + {'D', 'H', 0x2533}, + {'u', 'h', 0x2534}, + {'u', 'H', 0x2537}, + {'U', 'h', 0x2538}, + {'U', 'H', 0x253b}, + {'v', 'h', 0x253c}, + {'v', 'H', 0x253f}, + {'V', 'h', 0x2542}, + {'V', 'H', 0x254b}, + {'F', 'D', 0x2571}, + {'B', 'D', 0x2572}, +# define DG_START_BLOCK 0x2580 + {'T', 'B', 0x2580}, + {'L', 'B', 0x2584}, + {'F', 'B', 0x2588}, + {'l', 'B', 0x258c}, + {'R', 'B', 0x2590}, + {'.', 'S', 0x2591}, + {':', 'S', 0x2592}, + {'?', 'S', 0x2593}, +# define DG_START_SHAPES 0x25a0 + {'f', 'S', 0x25a0}, + {'O', 'S', 0x25a1}, + {'R', 'O', 0x25a2}, + {'R', 'r', 0x25a3}, + {'R', 'F', 0x25a4}, + {'R', 'Y', 0x25a5}, + {'R', 'H', 0x25a6}, + {'R', 'Z', 0x25a7}, + {'R', 'K', 0x25a8}, + {'R', 'X', 0x25a9}, + {'s', 'B', 0x25aa}, + {'S', 'R', 0x25ac}, + {'O', 'r', 0x25ad}, + {'U', 'T', 0x25b2}, + {'u', 'T', 0x25b3}, + {'P', 'R', 0x25b6}, + {'T', 'r', 0x25b7}, + {'D', 't', 0x25bc}, + {'d', 'T', 0x25bd}, + {'P', 'L', 0x25c0}, + {'T', 'l', 0x25c1}, + {'D', 'b', 0x25c6}, + {'D', 'w', 0x25c7}, + {'L', 'Z', 0x25ca}, + {'0', 'm', 0x25cb}, + {'0', 'o', 0x25ce}, + {'0', 'M', 0x25cf}, + {'0', 'L', 0x25d0}, + {'0', 'R', 0x25d1}, + {'S', 'n', 0x25d8}, + {'I', 'c', 0x25d9}, + {'F', 'd', 0x25e2}, + {'B', 'd', 0x25e3}, +# define DG_START_SYMBOLS 0x2605 + {'*', '2', 0x2605}, + {'*', '1', 0x2606}, + {'<', 'H', 0x261c}, + {'>', 'H', 0x261e}, + {'0', 'u', 0x263a}, + {'0', 'U', 0x263b}, + {'S', 'U', 0x263c}, + {'F', 'm', 0x2640}, + {'M', 'l', 0x2642}, + {'c', 'S', 0x2660}, + {'c', 'H', 0x2661}, + {'c', 'D', 0x2662}, + {'c', 'C', 0x2663}, + {'M', 'd', 0x2669}, + {'M', '8', 0x266a}, + {'M', '2', 0x266b}, + {'M', 'b', 0x266d}, + {'M', 'x', 0x266e}, + {'M', 'X', 0x266f}, +# define DG_START_DINGBATS 0x2713 + {'O', 'K', 0x2713}, + {'X', 'X', 0x2717}, + {'-', 'X', 0x2720}, +# define DG_START_CJK_SYMBOLS 0x3000 + {'I', 'S', 0x3000}, + {',', '_', 0x3001}, + {'.', '_', 0x3002}, + {'+', '"', 0x3003}, + {'+', '_', 0x3004}, + {'*', '_', 0x3005}, + {';', '_', 0x3006}, + {'0', '_', 0x3007}, + {'<', '+', 0x300a}, + {'>', '+', 0x300b}, + {'<', '\'', 0x300c}, + {'>', '\'', 0x300d}, + {'<', '"', 0x300e}, + {'>', '"', 0x300f}, + {'(', '"', 0x3010}, + {')', '"', 0x3011}, + {'=', 'T', 0x3012}, + {'=', '_', 0x3013}, + {'(', '\'', 0x3014}, + {')', '\'', 0x3015}, + {'(', 'I', 0x3016}, + {')', 'I', 0x3017}, + {'-', '?', 0x301c}, +# define DG_START_HIRAGANA 0x3041 + {'A', '5', 0x3041}, + {'a', '5', 0x3042}, + {'I', '5', 0x3043}, + {'i', '5', 0x3044}, + {'U', '5', 0x3045}, + {'u', '5', 0x3046}, + {'E', '5', 0x3047}, + {'e', '5', 0x3048}, + {'O', '5', 0x3049}, + {'o', '5', 0x304a}, + {'k', 'a', 0x304b}, + {'g', 'a', 0x304c}, + {'k', 'i', 0x304d}, + {'g', 'i', 0x304e}, + {'k', 'u', 0x304f}, + {'g', 'u', 0x3050}, + {'k', 'e', 0x3051}, + {'g', 'e', 0x3052}, + {'k', 'o', 0x3053}, + {'g', 'o', 0x3054}, + {'s', 'a', 0x3055}, + {'z', 'a', 0x3056}, + {'s', 'i', 0x3057}, + {'z', 'i', 0x3058}, + {'s', 'u', 0x3059}, + {'z', 'u', 0x305a}, + {'s', 'e', 0x305b}, + {'z', 'e', 0x305c}, + {'s', 'o', 0x305d}, + {'z', 'o', 0x305e}, + {'t', 'a', 0x305f}, + {'d', 'a', 0x3060}, + {'t', 'i', 0x3061}, + {'d', 'i', 0x3062}, + {'t', 'U', 0x3063}, + {'t', 'u', 0x3064}, + {'d', 'u', 0x3065}, + {'t', 'e', 0x3066}, + {'d', 'e', 0x3067}, + {'t', 'o', 0x3068}, + {'d', 'o', 0x3069}, + {'n', 'a', 0x306a}, + {'n', 'i', 0x306b}, + {'n', 'u', 0x306c}, + {'n', 'e', 0x306d}, + {'n', 'o', 0x306e}, + {'h', 'a', 0x306f}, + {'b', 'a', 0x3070}, + {'p', 'a', 0x3071}, + {'h', 'i', 0x3072}, + {'b', 'i', 0x3073}, + {'p', 'i', 0x3074}, + {'h', 'u', 0x3075}, + {'b', 'u', 0x3076}, + {'p', 'u', 0x3077}, + {'h', 'e', 0x3078}, + {'b', 'e', 0x3079}, + {'p', 'e', 0x307a}, + {'h', 'o', 0x307b}, + {'b', 'o', 0x307c}, + {'p', 'o', 0x307d}, + {'m', 'a', 0x307e}, + {'m', 'i', 0x307f}, + {'m', 'u', 0x3080}, + {'m', 'e', 0x3081}, + {'m', 'o', 0x3082}, + {'y', 'A', 0x3083}, + {'y', 'a', 0x3084}, + {'y', 'U', 0x3085}, + {'y', 'u', 0x3086}, + {'y', 'O', 0x3087}, + {'y', 'o', 0x3088}, + {'r', 'a', 0x3089}, + {'r', 'i', 0x308a}, + {'r', 'u', 0x308b}, + {'r', 'e', 0x308c}, + {'r', 'o', 0x308d}, + {'w', 'A', 0x308e}, + {'w', 'a', 0x308f}, + {'w', 'i', 0x3090}, + {'w', 'e', 0x3091}, + {'w', 'o', 0x3092}, + {'n', '5', 0x3093}, + {'v', 'u', 0x3094}, + {'"', '5', 0x309b}, + {'0', '5', 0x309c}, + {'*', '5', 0x309d}, + {'+', '5', 0x309e}, +# define DG_START_KATAKANA 0x30a1 + {'a', '6', 0x30a1}, + {'A', '6', 0x30a2}, + {'i', '6', 0x30a3}, + {'I', '6', 0x30a4}, + {'u', '6', 0x30a5}, + {'U', '6', 0x30a6}, + {'e', '6', 0x30a7}, + {'E', '6', 0x30a8}, + {'o', '6', 0x30a9}, + {'O', '6', 0x30aa}, + {'K', 'a', 0x30ab}, + {'G', 'a', 0x30ac}, + {'K', 'i', 0x30ad}, + {'G', 'i', 0x30ae}, + {'K', 'u', 0x30af}, + {'G', 'u', 0x30b0}, + {'K', 'e', 0x30b1}, + {'G', 'e', 0x30b2}, + {'K', 'o', 0x30b3}, + {'G', 'o', 0x30b4}, + {'S', 'a', 0x30b5}, + {'Z', 'a', 0x30b6}, + {'S', 'i', 0x30b7}, + {'Z', 'i', 0x30b8}, + {'S', 'u', 0x30b9}, + {'Z', 'u', 0x30ba}, + {'S', 'e', 0x30bb}, + {'Z', 'e', 0x30bc}, + {'S', 'o', 0x30bd}, + {'Z', 'o', 0x30be}, + {'T', 'a', 0x30bf}, + {'D', 'a', 0x30c0}, + {'T', 'i', 0x30c1}, + {'D', 'i', 0x30c2}, + {'T', 'U', 0x30c3}, + {'T', 'u', 0x30c4}, + {'D', 'u', 0x30c5}, + {'T', 'e', 0x30c6}, + {'D', 'e', 0x30c7}, + {'T', 'o', 0x30c8}, + {'D', 'o', 0x30c9}, + {'N', 'a', 0x30ca}, + {'N', 'i', 0x30cb}, + {'N', 'u', 0x30cc}, + {'N', 'e', 0x30cd}, + {'N', 'o', 0x30ce}, + {'H', 'a', 0x30cf}, + {'B', 'a', 0x30d0}, + {'P', 'a', 0x30d1}, + {'H', 'i', 0x30d2}, + {'B', 'i', 0x30d3}, + {'P', 'i', 0x30d4}, + {'H', 'u', 0x30d5}, + {'B', 'u', 0x30d6}, + {'P', 'u', 0x30d7}, + {'H', 'e', 0x30d8}, + {'B', 'e', 0x30d9}, + {'P', 'e', 0x30da}, + {'H', 'o', 0x30db}, + {'B', 'o', 0x30dc}, + {'P', 'o', 0x30dd}, + {'M', 'a', 0x30de}, + {'M', 'i', 0x30df}, + {'M', 'u', 0x30e0}, + {'M', 'e', 0x30e1}, + {'M', 'o', 0x30e2}, + {'Y', 'A', 0x30e3}, + {'Y', 'a', 0x30e4}, + {'Y', 'U', 0x30e5}, + {'Y', 'u', 0x30e6}, + {'Y', 'O', 0x30e7}, + {'Y', 'o', 0x30e8}, + {'R', 'a', 0x30e9}, + {'R', 'i', 0x30ea}, + {'R', 'u', 0x30eb}, + {'R', 'e', 0x30ec}, + {'R', 'o', 0x30ed}, + {'W', 'A', 0x30ee}, + {'W', 'a', 0x30ef}, + {'W', 'i', 0x30f0}, + {'W', 'e', 0x30f1}, + {'W', 'o', 0x30f2}, + {'N', '6', 0x30f3}, + {'V', 'u', 0x30f4}, + {'K', 'A', 0x30f5}, + {'K', 'E', 0x30f6}, + {'V', 'a', 0x30f7}, + {'V', 'i', 0x30f8}, + {'V', 'e', 0x30f9}, + {'V', 'o', 0x30fa}, + {'.', '6', 0x30fb}, + {'-', '6', 0x30fc}, + {'*', '6', 0x30fd}, + {'+', '6', 0x30fe}, +# define DG_START_BOPOMOFO 0x3105 + {'b', '4', 0x3105}, + {'p', '4', 0x3106}, + {'m', '4', 0x3107}, + {'f', '4', 0x3108}, + {'d', '4', 0x3109}, + {'t', '4', 0x310a}, + {'n', '4', 0x310b}, + {'l', '4', 0x310c}, + {'g', '4', 0x310d}, + {'k', '4', 0x310e}, + {'h', '4', 0x310f}, + {'j', '4', 0x3110}, + {'q', '4', 0x3111}, + {'x', '4', 0x3112}, + {'z', 'h', 0x3113}, + {'c', 'h', 0x3114}, + {'s', 'h', 0x3115}, + {'r', '4', 0x3116}, + {'z', '4', 0x3117}, + {'c', '4', 0x3118}, + {'s', '4', 0x3119}, + {'a', '4', 0x311a}, + {'o', '4', 0x311b}, + {'e', '4', 0x311c}, + {'a', 'i', 0x311e}, + {'e', 'i', 0x311f}, + {'a', 'u', 0x3120}, + {'o', 'u', 0x3121}, + {'a', 'n', 0x3122}, + {'e', 'n', 0x3123}, + {'a', 'N', 0x3124}, + {'e', 'N', 0x3125}, + {'e', 'r', 0x3126}, + {'i', '4', 0x3127}, + {'u', '4', 0x3128}, + {'i', 'u', 0x3129}, + {'v', '4', 0x312a}, + {'n', 'G', 0x312b}, + {'g', 'n', 0x312c}, +# define DG_START_OTHER3 0x3220 + {'1', 'c', 0x3220}, + {'2', 'c', 0x3221}, + {'3', 'c', 0x3222}, + {'4', 'c', 0x3223}, + {'5', 'c', 0x3224}, + {'6', 'c', 0x3225}, + {'7', 'c', 0x3226}, + {'8', 'c', 0x3227}, + {'9', 'c', 0x3228}, + // code points 0xe000 - 0xefff excluded, they have no assigned + // characters, only used in proposals. + {'f', 'f', 0xfb00}, + {'f', 'i', 0xfb01}, + {'f', 'l', 0xfb02}, + {'f', 't', 0xfb05}, + {'s', 't', 0xfb06}, + + {NUL, NUL, NUL} // end marker +}; + +/* + * handle digraphs after typing a character + */ + int +do_digraph(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 = digraph_get(backspaced, c, FALSE); + backspaced = -1; + if ((c == K_BS || c == Ctrl_H) && lastchar >= 0) + backspaced = lastchar; + } + lastchar = c; + return c; +} + +/* + * Find a digraph for "val". If found return the string to display it. + * If not found return NULL. + */ + char_u * +get_digraph_for_char(int val_arg) +{ + int val = val_arg; + int i; + int use_defaults; + digr_T *dp; + static char_u r[3]; + +#if defined(USE_UNICODE_DIGRAPHS) + if (!enc_utf8) + { + char_u buf[6], *to; + vimconv_T vc; + + // convert the character from 'encoding' to Unicode + i = mb_char2bytes(val, buf); + vc.vc_type = CONV_NONE; + if (convert_setup(&vc, p_enc, (char_u *)"utf-8") == OK) + { + vc.vc_fail = TRUE; + to = string_convert(&vc, buf, &i); + if (to != NULL) + { + val = utf_ptr2char(to); + vim_free(to); + } + (void)convert_setup(&vc, NULL, NULL); + } + } +#endif + + for (use_defaults = 0; use_defaults <= 1; use_defaults++) + { + if (use_defaults == 0) + dp = (digr_T *)user_digraphs.ga_data; + else + dp = digraphdefault; + for (i = 0; use_defaults ? dp->char1 != NUL + : i < user_digraphs.ga_len; ++i) + { + if (dp->result == val) + { + r[0] = dp->char1; + r[1] = dp->char2; + r[2] = NUL; + return r; + } + ++dp; + } + } + return NULL; +} + +/* + * Get a digraph. Used after typing CTRL-K on the command line or in normal + * mode. + * Returns composed character, or NUL when ESC was used. + */ + int +get_digraph( + int cmdline) // TRUE when called from the cmdline +{ + int c, cc; + + ++no_mapping; + ++allow_keys; + c = plain_vgetc(); + --no_mapping; + --allow_keys; + + if (c == ESC) // ESC cancels CTRL-K + return NUL; + + if (IS_SPECIAL(c)) // insert special key code + return c; + if (cmdline) + { + if (char2cells(c) == 1 +#if defined(FEAT_CRYPT) || defined(FEAT_EVAL) + && cmdline_star == 0 +#endif + ) + putcmdline(c, TRUE); + } + else + add_to_showcmd(c); + ++no_mapping; + ++allow_keys; + cc = plain_vgetc(); + --no_mapping; + --allow_keys; + if (cc != ESC) // ESC cancels CTRL-K + return digraph_get(c, cc, TRUE); + return NUL; +} + +/* + * Lookup the pair "char1", "char2" in the digraph tables. + * If no match, return "char2". + * If "meta_char" is TRUE and "char1" is a space, return "char2" | 0x80. + */ + static int +getexactdigraph(int char1, int char2, int meta_char) +{ + int i; + int retval = 0; + digr_T *dp; + + if (IS_SPECIAL(char1) || IS_SPECIAL(char2)) + return char2; + + /* + * Search user digraphs first. + */ + dp = (digr_T *)user_digraphs.ga_data; + for (i = 0; i < user_digraphs.ga_len; ++i) + { + if ((int)dp->char1 == char1 && (int)dp->char2 == char2) + { + retval = dp->result; + break; + } + ++dp; + } + + /* + * Search default digraphs. + */ + if (retval == 0) + { + dp = digraphdefault; + while (dp->char1 != 0) + { + if ((int)dp->char1 == char1 && (int)dp->char2 == char2) + { + retval = dp->result; + break; + } + ++dp; + } + } +#ifdef USE_UNICODE_DIGRAPHS + if (retval != 0 && !enc_utf8) + { + char_u buf[6], *to; + vimconv_T vc; + + /* + * Convert the Unicode digraph to 'encoding'. + */ + i = utf_char2bytes(retval, buf); + retval = 0; + vc.vc_type = CONV_NONE; + if (convert_setup(&vc, (char_u *)"utf-8", p_enc) == OK) + { + vc.vc_fail = TRUE; + to = string_convert(&vc, buf, &i); + if (to != NULL) + { + retval = (*mb_ptr2char)(to); + vim_free(to); + } + (void)convert_setup(&vc, NULL, NULL); + } + } +#endif + + // Ignore multi-byte characters when not in multi-byte mode. + if (!has_mbyte && retval > 0xff) + retval = 0; + + if (retval == 0) // digraph deleted or not found + { + if (char1 == ' ' && meta_char) // --> meta-char + return (char2 | 0x80); + return char2; + } + return retval; +} + +/* + * Get digraph. + * Allow for both char1-char2 and char2-char1 + */ + int +digraph_get(int char1, int char2, int meta_char) +{ + int retval; + + if (((retval = getexactdigraph(char1, char2, meta_char)) == char2) + && (char1 != char2) + && ((retval = getexactdigraph(char2, char1, meta_char)) == char1)) + return char2; + return retval; +} + +/* + * Add a digraph to the digraph table. + */ + static void +registerdigraph(int char1, int char2, int n) +{ + int i; + digr_T *dp; + + // If the digraph already exists, replace "result". + dp = (digr_T *)user_digraphs.ga_data; + for (i = 0; i < user_digraphs.ga_len; ++i) + { + if ((int)dp->char1 == char1 && (int)dp->char2 == char2) + { + dp->result = n; + return; + } + ++dp; + } + + // Add a new digraph to the table. + if (ga_grow(&user_digraphs, 1) == FAIL) + return; + + dp = (digr_T *)user_digraphs.ga_data + user_digraphs.ga_len; + dp->char1 = char1; + dp->char2 = char2; + dp->result = n; + ++user_digraphs.ga_len; +} + +/* + * Check the characters are valid for a digraph. + * If they are valid, returns TRUE; otherwise, give an error message and + * returns FALSE. + */ + static int +check_digraph_chars_valid(int char1, int char2) +{ + if (char2 == 0) + { + char_u msg[MB_MAXBYTES + 1]; + + msg[mb_char2bytes(char1, msg)] = NUL; + + semsg(_(e_digraph_must_be_just_two_characters_str), msg); + return FALSE; + } + if (char1 == ESC || char2 == ESC) + { + emsg(_(e_escape_not_allowed_in_digraph)); + return FALSE; + } + return TRUE; +} + + + +/* + * Add the digraphs in the argument to the digraph table. + * format: {c1}{c2} char {c1}{c2} char ... + */ + void +putdigraph(char_u *str) +{ + int char1, char2, n; + + while (*str != NUL) + { + str = skipwhite(str); + if (*str == NUL) + return; + char1 = *str++; + char2 = *str++; + + if (!check_digraph_chars_valid(char1, char2)) + return; + + str = skipwhite(str); + if (!VIM_ISDIGIT(*str)) + { + emsg(_(e_number_expected)); + return; + } + n = getdigits(&str); + + registerdigraph(char1, char2, n); + } +} + +#if defined(USE_UNICODE_DIGRAPHS) + static void +digraph_header(char *msg) +{ + if (msg_col > 0) + msg_putchar('\n'); + msg_outtrans_attr((char_u *)msg, HL_ATTR(HLF_CM)); + msg_putchar('\n'); +} +#endif + + void +listdigraphs(int use_headers) +{ + int i; + digr_T *dp; + result_T previous = 0; + + msg_putchar('\n'); + + dp = digraphdefault; + while (dp->char1 != NUL && !got_int) + { +#if defined(USE_UNICODE_DIGRAPHS) + digr_T tmp; + + // May need to convert the result to 'encoding'. + tmp.char1 = dp->char1; + tmp.char2 = dp->char2; + tmp.result = getexactdigraph(tmp.char1, tmp.char2, FALSE); + if (tmp.result != 0 && tmp.result != tmp.char2 + && (has_mbyte || tmp.result <= 255)) + printdigraph(&tmp, use_headers ? &previous : NULL); +#else + + if (getexactdigraph(dp->char1, dp->char2, FALSE) == dp->result + && (has_mbyte || dp->result <= 255)) + printdigraph(dp, use_headers ? &previous : NULL); +#endif + ++dp; + ui_breakcheck(); + } + + dp = (digr_T *)user_digraphs.ga_data; + for (i = 0; i < user_digraphs.ga_len && !got_int; ++i) + { +#if defined(USE_UNICODE_DIGRAPHS) + if (previous >= 0 && use_headers) + digraph_header(_("Custom")); + previous = -1; +#endif + printdigraph(dp, NULL); + ui_breakcheck(); + ++dp; + } + + // clear screen, because some digraphs may be wrong, in which case we + // messed up ScreenLines + set_must_redraw(UPD_CLEAR); +} + + static void +digraph_getlist_appendpair(digr_T *dp, list_T *l) +{ + char_u buf[30]; + char_u *p; + list_T *l2; + listitem_T *li, *li2; + + + li = listitem_alloc(); + if (li == NULL) + return; + list_append(l, li); + li->li_tv.v_type = VAR_LIST; + li->li_tv.v_lock = 0; + + l2 = list_alloc(); + li->li_tv.vval.v_list = l2; + if (l2 == NULL) + return; + ++l2->lv_refcount; + + li2 = listitem_alloc(); + if (li2 == NULL) + return; + list_append(l2, li2); + li2->li_tv.v_type = VAR_STRING; + li2->li_tv.v_lock = 0; + + buf[0] = dp->char1; + buf[1] = dp->char2; + buf[2] = NUL; + li2->li_tv.vval.v_string = vim_strsave(&buf[0]); + + li2 = listitem_alloc(); + if (li2 == NULL) + return; + list_append(l2, li2); + li2->li_tv.v_type = VAR_STRING; + li2->li_tv.v_lock = 0; + + p = buf; + if (has_mbyte) + p += (*mb_char2bytes)(dp->result, p); + else + *p++ = (char_u)dp->result; + *p = NUL; + + li2->li_tv.vval.v_string = vim_strsave(buf); +} + + static void +digraph_getlist_common(int list_all, typval_T *rettv) +{ + int i; + digr_T *dp; + + if (rettv_list_alloc(rettv) == FAIL) + return; + + if (list_all) + { + dp = digraphdefault; + while (dp->char1 != NUL && !got_int) + { +#ifdef USE_UNICODE_DIGRAPHS + digr_T tmp; + + tmp.char1 = dp->char1; + tmp.char2 = dp->char2; + tmp.result = getexactdigraph(tmp.char1, tmp.char2, FALSE); + if (tmp.result != 0 && tmp.result != tmp.char2 + && (has_mbyte || tmp.result <= 255)) + digraph_getlist_appendpair(&tmp, rettv->vval.v_list); +#else + if (getexactdigraph(dp->char1, dp->char2, FALSE) == dp->result + && (has_mbyte || dp->result <= 255)) + digraph_getlist_appendpair(dp, rettv->vval.v_list); +#endif + ++dp; + } + } + + dp = (digr_T *)user_digraphs.ga_data; + for (i = 0; i < user_digraphs.ga_len && !got_int; ++i) + { + digraph_getlist_appendpair(dp, rettv->vval.v_list); + ++dp; + } +} + +static struct dg_header_entry { + int dg_start; + char *dg_header; +} header_table[] = { + {DG_START_LATIN, N_("Latin supplement")}, + {DG_START_GREEK, N_("Greek and Coptic")}, + {DG_START_CYRILLIC, N_("Cyrillic")}, + {DG_START_HEBREW, N_("Hebrew")}, + {DG_START_ARABIC, N_("Arabic")}, + {DG_START_LATIN_EXTENDED, N_("Latin extended")}, + {DG_START_GREEK_EXTENDED, N_("Greek extended")}, + {DG_START_PUNCTUATION, N_("Punctuation")}, + {DG_START_SUB_SUPER, N_("Super- and subscripts")}, + {DG_START_CURRENCY, N_("Currency")}, + {DG_START_OTHER1, N_("Other")}, + {DG_START_ROMAN, N_("Roman numbers")}, + {DG_START_ARROWS, N_("Arrows")}, + {DG_START_MATH, N_("Mathematical operators")}, + {DG_START_TECHNICAL, N_("Technical")}, + {DG_START_OTHER2, N_("Other")}, + {DG_START_DRAWING, N_("Box drawing")}, + {DG_START_BLOCK, N_("Block elements")}, + {DG_START_SHAPES, N_("Geometric shapes")}, + {DG_START_SYMBOLS, N_("Symbols")}, + {DG_START_DINGBATS, N_("Dingbats")}, + {DG_START_CJK_SYMBOLS, N_("CJK symbols and punctuation")}, + {DG_START_HIRAGANA, N_("Hiragana")}, + {DG_START_KATAKANA, N_("Katakana")}, + {DG_START_BOPOMOFO, N_("Bopomofo")}, + {DG_START_OTHER3, N_("Other")}, + {0xfffffff, NULL}, +}; + + static void +printdigraph(digr_T *dp, result_T *previous) +{ + char_u buf[30]; + char_u *p; + + int list_width; + + if ((dy_flags & DY_UHEX) || has_mbyte) + list_width = 13; + else + list_width = 11; + + if (dp->result == 0) + return; + +#if defined(USE_UNICODE_DIGRAPHS) + if (previous != NULL) + { + int i; + + for (i = 0; header_table[i].dg_header != NULL; ++i) + if (*previous < header_table[i].dg_start + && dp->result >= header_table[i].dg_start + && dp->result < header_table[i + 1].dg_start) + { + digraph_header(_(header_table[i].dg_header)); + break; + } + *previous = dp->result; + } +#endif + if (msg_col > Columns - list_width) + msg_putchar('\n'); + if (msg_col) + while (msg_col % list_width != 0) + msg_putchar(' '); + + p = buf; + *p++ = dp->char1; + *p++ = dp->char2; + *p++ = ' '; + *p = NUL; + msg_outtrans(buf); + p = buf; + if (has_mbyte) + { + // add a space to draw a composing char on + if (enc_utf8 && utf_iscomposing(dp->result)) + *p++ = ' '; + p += (*mb_char2bytes)(dp->result, p); + } + else + *p++ = (char_u)dp->result; + *p = NUL; + msg_outtrans_attr(buf, HL_ATTR(HLF_8)); + p = buf; + if (char2cells(dp->result) == 1) + *p++ = ' '; + vim_snprintf((char *)p, sizeof(buf) - (p - buf), " %3d", dp->result); + msg_outtrans(buf); +} + +# ifdef FEAT_EVAL +/* + * Get the two digraph characters from a typval. + * Return OK or FAIL. + */ + static int +get_digraph_chars(typval_T *arg, int *char1, int *char2) +{ + char_u buf_chars[NUMBUFLEN]; + char_u *chars = tv_get_string_buf_chk(arg, buf_chars); + char_u *p = chars; + + if (p != NULL) + { + if (*p != NUL) + { + *char1 = mb_cptr2char_adv(&p); + if (*p != NUL) + { + *char2 = mb_cptr2char_adv(&p); + if (*p == NUL) + { + if (check_digraph_chars_valid(*char1, *char2)) + return OK; + return FAIL; + } + } + } + } + semsg(_(e_digraph_must_be_just_two_characters_str), chars); + return FAIL; +} + + static int +digraph_set_common(typval_T *argchars, typval_T *argdigraph) +{ + int char1, char2; + char_u *digraph; + char_u *p; + char_u buf_digraph[NUMBUFLEN]; + varnumber_T n; + + if (get_digraph_chars(argchars, &char1, &char2) == FAIL) + return FALSE; + + digraph = tv_get_string_buf_chk(argdigraph, buf_digraph); + if (digraph == NULL) + return FALSE; + p = digraph; + n = mb_cptr2char_adv(&p); + if (*p != NUL) + { + semsg(_(e_digraph_argument_must_be_one_character_str), digraph); + return FALSE; + } + + registerdigraph(char1, char2, (int)n); + return TRUE; +} +# endif + +#endif // FEAT_DIGRAPHS + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * "digraph_get()" function + */ + void +f_digraph_get(typval_T *argvars, typval_T *rettv) +{ +# ifdef FEAT_DIGRAPHS + int code; + char_u buf[NUMBUFLEN]; + char_u *digraphs; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; // Return empty string for failure + + if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL) + return; + + digraphs = tv_get_string_chk(&argvars[0]); + + if (digraphs == NULL) + return; + else if (STRLEN(digraphs) != 2) + { + semsg(_(e_digraph_must_be_just_two_characters_str), digraphs); + return; + } + code = digraph_get(digraphs[0], digraphs[1], FALSE); + + if (has_mbyte) + buf[(*mb_char2bytes)(code, buf)] = NUL; + else + { + buf[0] = code; + buf[1] = NUL; + } + + rettv->vval.v_string = vim_strsave(buf); +# else + emsg(_(e_no_digraphs_version)); +# endif +} + +/* + * "digraph_getlist()" function + */ + void +f_digraph_getlist(typval_T *argvars, typval_T *rettv) +{ +# ifdef FEAT_DIGRAPHS + int flag_list_all; + + if (in_vim9script() && check_for_opt_bool_arg(argvars, 0) == FAIL) + return; + + if (argvars[0].v_type == VAR_UNKNOWN) + flag_list_all = FALSE; + else + { + int error = FALSE; + varnumber_T flag = tv_get_number_chk(&argvars[0], &error); + + if (error) + return; + flag_list_all = flag ? TRUE : FALSE; + } + + digraph_getlist_common(flag_list_all, rettv); +# else + emsg(_(e_no_digraphs_version)); +# endif +} + +/* + * "digraph_set()" function + */ + void +f_digraph_set(typval_T *argvars, typval_T *rettv) +{ +# ifdef FEAT_DIGRAPHS + rettv->v_type = VAR_BOOL; + rettv->vval.v_number = VVAL_FALSE; + + if (in_vim9script() + && (check_for_string_arg(argvars, 0) == FAIL + || check_for_string_arg(argvars, 1) == FAIL)) + return; + + if (!digraph_set_common(&argvars[0], &argvars[1])) + return; + + rettv->vval.v_number = VVAL_TRUE; +# else + emsg(_(e_no_digraphs_version)); +# endif +} + +/* + * "digraph_setlist()" function + */ + void +f_digraph_setlist(typval_T * argvars, typval_T *rettv) +{ +# ifdef FEAT_DIGRAPHS + list_T *pl, *l; + listitem_T *pli; + + rettv->v_type = VAR_BOOL; + rettv->vval.v_number = VVAL_FALSE; + + if (argvars[0].v_type != VAR_LIST) + { + emsg(_(e_digraph_setlist_argument_must_be_list_of_lists_with_two_items)); + return; + } + + pl = argvars[0].vval.v_list; + if (pl == NULL) + { + // Empty list always results in success. + rettv->vval.v_number = VVAL_TRUE; + return; + } + + FOR_ALL_LIST_ITEMS(pl, pli) + { + if (pli->li_tv.v_type != VAR_LIST) + { + emsg(_(e_digraph_setlist_argument_must_be_list_of_lists_with_two_items)); + return; + } + + l = pli->li_tv.vval.v_list; + if (l == NULL || l->lv_len != 2) + { + emsg(_(e_digraph_setlist_argument_must_be_list_of_lists_with_two_items)); + return; + } + + if (!digraph_set_common(&l->lv_first->li_tv, + &l->lv_first->li_next->li_tv)) + return; + } + rettv->vval.v_number = VVAL_TRUE; +# else + emsg(_(e_no_digraphs_version)); +# endif +} + +#endif // FEAT_EVAL + + +#if defined(FEAT_KEYMAP) || defined(PROTO) + +// structure used for b_kmap_ga.ga_data +typedef struct +{ + char_u *from; + char_u *to; +} kmap_T; + +#define KMAP_MAXLEN 20 // maximum length of "from" or "to" + +static void keymap_unload(void); + +/* + * Set up key mapping tables for the 'keymap' option. + * Returns NULL if OK, an error message for failure. This only needs to be + * used when setting the option, not later when the value has already been + * checked. + */ + char * +keymap_init(void) +{ + curbuf->b_kmap_state &= ~KEYMAP_INIT; + + if (*curbuf->b_p_keymap == NUL) + { + // Stop any active keymap and clear the table. Also remove + // b:keymap_name, as no keymap is active now. + keymap_unload(); + do_cmdline_cmd((char_u *)"unlet! b:keymap_name"); + } + else + { + char_u *buf; + size_t buflen; + + // Source the keymap file. It will contain a ":loadkeymap" command + // which will call ex_loadkeymap() below. + buflen = STRLEN(curbuf->b_p_keymap) + STRLEN(p_enc) + 14; + buf = alloc(buflen); + if (buf == NULL) + return e_out_of_memory; + + // try finding "keymap/'keymap'_'encoding'.vim" in 'runtimepath' + vim_snprintf((char *)buf, buflen, "keymap/%s_%s.vim", + curbuf->b_p_keymap, p_enc); + if (source_runtime(buf, 0) == FAIL) + { + // try finding "keymap/'keymap'.vim" in 'runtimepath' + vim_snprintf((char *)buf, buflen, "keymap/%s.vim", + curbuf->b_p_keymap); + if (source_runtime(buf, 0) == FAIL) + { + vim_free(buf); + return N_(e_keymap_file_not_found); + } + } + vim_free(buf); + } + + return NULL; +} + +/* + * ":loadkeymap" command: load the following lines as the keymap. + */ + void +ex_loadkeymap(exarg_T *eap) +{ + char_u *line; + char_u *p; + char_u *s; + kmap_T *kp; +#define KMAP_LLEN 200 // max length of "to" and "from" together + char_u buf[KMAP_LLEN + 11]; + int i; + char_u *save_cpo = p_cpo; + + if (!sourcing_a_script(eap)) + { + emsg(_(e_using_loadkeymap_not_in_sourced_file)); + return; + } + + /* + * Stop any active keymap and clear the table. + */ + keymap_unload(); + + curbuf->b_kmap_state = 0; + ga_init2(&curbuf->b_kmap_ga, sizeof(kmap_T), 20); + + // Set 'cpoptions' to "C" to avoid line continuation. + p_cpo = (char_u *)"C"; + + /* + * Get each line of the sourced file, break at the end. + */ + for (;;) + { + line = eap->ea_getline(0, eap->cookie, 0, TRUE); + if (line == NULL) + break; + + p = skipwhite(line); + if (*p != '"' && *p != NUL && ga_grow(&curbuf->b_kmap_ga, 1) == OK) + { + kp = (kmap_T *)curbuf->b_kmap_ga.ga_data + curbuf->b_kmap_ga.ga_len; + s = skiptowhite(p); + kp->from = vim_strnsave(p, s - p); + p = skipwhite(s); + s = skiptowhite(p); + kp->to = vim_strnsave(p, s - p); + + if (kp->from == NULL || kp->to == NULL + || STRLEN(kp->from) + STRLEN(kp->to) >= KMAP_LLEN + || *kp->from == NUL || *kp->to == NUL) + { + if (kp->to != NULL && *kp->to == NUL) + emsg(_(e_empty_keymap_entry)); + vim_free(kp->from); + vim_free(kp->to); + } + else + ++curbuf->b_kmap_ga.ga_len; + } + vim_free(line); + } + + /* + * setup ":lnoremap" to map the keys + */ + for (i = 0; i < curbuf->b_kmap_ga.ga_len; ++i) + { + vim_snprintf((char *)buf, sizeof(buf), " %s %s", + ((kmap_T *)curbuf->b_kmap_ga.ga_data)[i].from, + ((kmap_T *)curbuf->b_kmap_ga.ga_data)[i].to); + (void)do_map(MAPTYPE_NOREMAP, buf, MODE_LANGMAP, FALSE); + } + + p_cpo = save_cpo; + + curbuf->b_kmap_state |= KEYMAP_LOADED; + status_redraw_curbuf(); +} + +/* + * Stop using 'keymap'. + */ + static void +keymap_unload(void) +{ + char_u buf[KMAP_MAXLEN + 10]; + int i; + char_u *save_cpo = p_cpo; + kmap_T *kp; + + if (!(curbuf->b_kmap_state & KEYMAP_LOADED)) + return; + + // Set 'cpoptions' to "C" to avoid line continuation. + p_cpo = (char_u *)"C"; + + // clear the ":lmap"s + kp = (kmap_T *)curbuf->b_kmap_ga.ga_data; + for (i = 0; i < curbuf->b_kmap_ga.ga_len; ++i) + { + vim_snprintf((char *)buf, sizeof(buf), " %s", kp[i].from); + (void)do_map(MAPTYPE_UNMAP, buf, MODE_LANGMAP, FALSE); + } + keymap_clear(&curbuf->b_kmap_ga); + + p_cpo = save_cpo; + + ga_clear(&curbuf->b_kmap_ga); + curbuf->b_kmap_state &= ~KEYMAP_LOADED; + status_redraw_curbuf(); +} + + void +keymap_clear(garray_T *kmap) +{ + int i; + kmap_T *kp = (kmap_T *)kmap->ga_data; + + for (i = 0; i < kmap->ga_len; ++i) + { + vim_free(kp[i].from); + vim_free(kp[i].to); + } +} +#endif // FEAT_KEYMAP diff --git a/userland/apps/vim/src/drawline.c b/userland/apps/vim/src/drawline.c new file mode 100644 index 00000000..7bbe86de --- /dev/null +++ b/userland/apps/vim/src/drawline.c @@ -0,0 +1,4256 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * drawline.c: Functions for drawing window lines on the screen. + * This is the middle level, drawscreen.c is the higher level and screen.c the + * lower level. + */ + +#include "vim.h" + +#ifdef FEAT_SYN_HL +/* + * Advance **color_cols and return TRUE when there are columns to draw. + */ + static int +advance_color_col(int vcol, int **color_cols) +{ + while (**color_cols >= 0 && vcol > **color_cols) + ++*color_cols; + return (**color_cols >= 0); +} +#endif + +#ifdef FEAT_SYN_HL +/* + * Used when 'cursorlineopt' contains "screenline": compute the margins between + * which the highlighting is used. + */ + static void +margin_columns_win(win_T *wp, int *left_col, int *right_col) +{ + // cache previous calculations depending on w_virtcol + static int saved_w_virtcol; + static win_T *prev_wp; + static int prev_left_col; + static int prev_right_col; + static int prev_col_off; + + int cur_col_off = win_col_off(wp); + int width1; + int width2; + + if (saved_w_virtcol == wp->w_virtcol + && prev_wp == wp && prev_col_off == cur_col_off) + { + *right_col = prev_right_col; + *left_col = prev_left_col; + return; + } + + width1 = wp->w_width - cur_col_off; + width2 = width1 + win_col_off2(wp); + + *left_col = 0; + *right_col = width1; + + if (wp->w_virtcol >= (colnr_T)width1) + *right_col = width1 + ((wp->w_virtcol - width1) / width2 + 1) * width2; + if (wp->w_virtcol >= (colnr_T)width1 && width2 > 0) + *left_col = (wp->w_virtcol - width1) / width2 * width2 + width1; + + // cache values + prev_left_col = *left_col; + prev_right_col = *right_col; + prev_wp = wp; + saved_w_virtcol = wp->w_virtcol; + prev_col_off = cur_col_off; +} +#endif + +#if defined(FEAT_SIGNS) || defined(FEAT_QUICKFIX) \ + || defined(FEAT_SYN_HL) || defined(FEAT_DIFF) +// using an attribute for the whole line +# define LINE_ATTR +#endif + +// structure with variables passed between win_line() and other functions +typedef struct { + int draw_state; // what to draw next + + linenr_T lnum; // line number to be drawn + + int startrow; // first row in the window to be drawn + int row; // row in the window, excl w_winrow + int screen_row; // row on the screen, incl w_winrow + + long vcol; // virtual column, before wrapping + int col; // visual column on screen, after wrapping +#ifdef FEAT_CONCEAL + int boguscols; // nonexistent columns added to "col" to force + // wrapping + int vcol_off_co; // offset for concealed characters +#endif + int vcol_off_tp; // offset for virtual text +#ifdef FEAT_SYN_HL + int draw_color_col; // highlight colorcolumn + int *color_cols; // pointer to according columns array +#endif + int eol_hl_off; // 1 if highlighted char after EOL + + unsigned off; // offset in ScreenLines/ScreenAttrs + + int win_attr; // background for the whole window, except + // margins and "~" lines. + int wcr_attr; // attributes from 'wincolor' +#ifdef FEAT_SYN_HL + int cul_attr; // set when 'cursorline' active +#endif +#ifdef LINE_ATTR + int line_attr; // for the whole line, includes 'cursorline' +#endif + + int screen_line_flags; // flags for screen_line() + + int fromcol; // start of inverting + int tocol; // end of inverting + +#ifdef FEAT_LINEBREAK + long vcol_sbr; // virtual column after showbreak + int need_showbreak; // overlong line, skipping first x chars + int dont_use_showbreak; // do not use 'showbreak' +#endif +#ifdef FEAT_PROP_POPUP + int text_prop_above_count; +#endif + + // TRUE when 'cursorlineopt' has "screenline" and cursor is in this line + int cul_screenline; + + int char_attr; // attributes for the next character + + int n_extra; // number of extra bytes + char_u *p_extra; // string of extra chars, plus NUL, only used + // when c_extra and c_final are NUL + char_u *p_extra_free; // p_extra buffer that needs to be freed + int extra_attr; // attributes for p_extra, should be combined + // with win_attr if needed + int n_attr_skip; // chars to skip before using extra_attr + int c_extra; // extra chars, all the same + int c_final; // final char, mandatory if set + int extra_for_textprop; // n_extra set for textprop + int start_extra_for_textprop; // extra_for_textprop was just set + + // saved "extra" items for when draw_state becomes WL_LINE (again) + int saved_n_extra; + char_u *saved_p_extra; + char_u *saved_p_extra_free; + int saved_extra_attr; + int saved_n_attr_skip; + int saved_extra_for_textprop; + int saved_c_extra; + int saved_c_final; + int saved_char_attr; + + char_u extra[NUMBUFLEN + MB_MAXBYTES]; + // "%ld " and 'fdc' must fit in here, as well + // any text sign + +#ifdef FEAT_DIFF + hlf_T diff_hlf; // type of diff highlighting +#endif + int filler_lines; // nr of filler lines to be drawn + int filler_todo; // nr of filler lines still to do + 1 +#ifdef FEAT_SIGNS + sign_attrs_T sattr; +#endif +#ifdef FEAT_LINEBREAK + // do consider wrapping in linebreak mode only after encountering + // a non whitespace char + int need_lbr; +#endif +} winlinevars_T; + +// draw_state values for items that are drawn in sequence: +#define WL_START 0 // nothing done yet, must be zero +#define WL_CMDLINE (WL_START + 1) // cmdline window column +#ifdef FEAT_FOLDING +# define WL_FOLD (WL_CMDLINE + 1) // 'foldcolumn' +#else +# define WL_FOLD WL_CMDLINE +#endif +#ifdef FEAT_SIGNS +# define WL_SIGN (WL_FOLD + 1) // column for signs +#else +# define WL_SIGN WL_FOLD // column for signs +#endif +#define WL_NR (WL_SIGN + 1) // line number +#ifdef FEAT_LINEBREAK +# define WL_BRI (WL_NR + 1) // 'breakindent' +#else +# define WL_BRI WL_NR +#endif +#if defined(FEAT_LINEBREAK) || defined(FEAT_DIFF) +# define WL_SBR (WL_BRI + 1) // 'showbreak' or 'diff' +#else +# define WL_SBR WL_BRI +#endif +#define WL_LINE (WL_SBR + 1) // text in the line + +#if defined(FEAT_SIGNS) || defined(FEAT_FOLDING) +/* + * Return TRUE if CursorLineSign highlight is to be used. + */ + static int +use_cursor_line_highlight(win_T *wp, linenr_T lnum) +{ + return wp->w_p_cul + && lnum == wp->w_cursor.lnum + && (wp->w_p_culopt_flags & CULOPT_NBR); +} +#endif + + +#ifdef FEAT_FOLDING +/* + * Setup for drawing the 'foldcolumn', if there is one. + */ + static void +handle_foldcolumn(win_T *wp, winlinevars_T *wlv) +{ + int fdc = compute_foldcolumn(wp, 0); + + if (fdc <= 0) + return; + + // Allocate a buffer, "wlv->extra[]" may already be in use. + vim_free(wlv->p_extra_free); + wlv->p_extra_free = alloc(MAX_MCO * fdc + 1); + if (wlv->p_extra_free == NULL) + return; + + wlv->n_extra = (int)fill_foldcolumn(wlv->p_extra_free, + wp, FALSE, wlv->lnum); + wlv->p_extra_free[wlv->n_extra] = NUL; + wlv->p_extra = wlv->p_extra_free; + wlv->c_extra = NUL; + wlv->c_final = NUL; + if (use_cursor_line_highlight(wp, wlv->lnum)) + wlv->char_attr = hl_combine_attr(wlv->wcr_attr, HL_ATTR(HLF_CLF)); + else + wlv->char_attr = hl_combine_attr(wlv->wcr_attr, HL_ATTR(HLF_FC)); +} +#endif + +#ifdef FEAT_SIGNS +/* + * Get information needed to display the sign in line "wlv->lnum" in window + * "wp". + * If "nrcol" is TRUE, the sign is going to be displayed in the number column. + * Otherwise the sign is going to be displayed in the sign column. + */ + static void +get_sign_display_info( + int nrcol, + win_T *wp, + winlinevars_T *wlv) +{ + int text_sign; +# ifdef FEAT_SIGN_ICONS + int icon_sign; +# endif + + // Draw two cells with the sign value or blank. + wlv->c_extra = ' '; + wlv->c_final = NUL; + if (nrcol) + wlv->n_extra = number_width(wp) + 1; + else + { + if (use_cursor_line_highlight(wp, wlv->lnum)) + wlv->char_attr = hl_combine_attr(wlv->wcr_attr, HL_ATTR(HLF_CLS)); + else + wlv->char_attr = hl_combine_attr(wlv->wcr_attr, HL_ATTR(HLF_SC)); + wlv->n_extra = 2; + } + + if (wlv->row == wlv->startrow +#ifdef FEAT_DIFF + + wlv->filler_lines && wlv->filler_todo <= 0 +#endif + ) + { + text_sign = (wlv->sattr.sat_text != NULL) ? wlv->sattr.sat_typenr : 0; +# ifdef FEAT_SIGN_ICONS + icon_sign = (wlv->sattr.sat_icon != NULL) ? wlv->sattr.sat_typenr : 0; + if (gui.in_use && icon_sign != 0) + { + // Use the image in this position. + if (nrcol) + { + wlv->c_extra = NUL; + sprintf((char *)wlv->extra, "%-*c ", + number_width(wp), SIGN_BYTE); + wlv->p_extra = wlv->extra; + wlv->n_extra = (int)STRLEN(wlv->p_extra); + } + else + wlv->c_extra = SIGN_BYTE; +# ifdef FEAT_NETBEANS_INTG + if (netbeans_active() && (buf_signcount(wp->w_buffer, wlv->lnum) + > 1)) + { + if (nrcol) + { + wlv->c_extra = NUL; + sprintf((char *)wlv->extra, "%-*c ", number_width(wp), + MULTISIGN_BYTE); + wlv->p_extra = wlv->extra; + wlv->n_extra = (int)STRLEN(wlv->p_extra); + } + else + wlv->c_extra = MULTISIGN_BYTE; + } +# endif + wlv->c_final = NUL; + wlv->char_attr = icon_sign; + } + else +# endif + if (text_sign != 0) + { + wlv->p_extra = wlv->sattr.sat_text; + if (wlv->p_extra != NULL) + { + if (nrcol) + { + int width = number_width(wp) - 2; + int n; + + for (n = 0; n < width; n++) + wlv->extra[n] = ' '; + vim_snprintf((char *)wlv->extra + n, + sizeof(wlv->extra) - n, "%s ", wlv->p_extra); + wlv->p_extra = wlv->extra; + } + wlv->c_extra = NUL; + wlv->c_final = NUL; + wlv->n_extra = (int)STRLEN(wlv->p_extra); + } + + if (use_cursor_line_highlight(wp, wlv->lnum) + && wlv->sattr.sat_culhl > 0) + wlv->char_attr = wlv->sattr.sat_culhl; + else + wlv->char_attr = wlv->sattr.sat_texthl; + } + } +} +#endif + +/* + * Display the absolute or relative line number. After the first row fill with + * blanks when the 'n' flag isn't in 'cpo'. + */ + static void +handle_lnum_col( + win_T *wp, + winlinevars_T *wlv, + int sign_present UNUSED, + int num_attr UNUSED) +{ + int has_cpo_n = vim_strchr(p_cpo, CPO_NUMCOL) != NULL; + int lnum_row = wlv->startrow + wlv->filler_lines +#ifdef FEAT_PROP_POPUP + + wlv->text_prop_above_count +#endif + ; + + if ((wp->w_p_nu || wp->w_p_rnu) + && (wlv->row <= lnum_row || !has_cpo_n) + // there is no line number in a wrapped line when "n" is in + // 'cpoptions', but 'breakindent' assumes it anyway. + && !((has_cpo_n +#ifdef FEAT_LINEBREAK + && !wp->w_p_bri +#endif + ) && wp->w_skipcol > 0 && wlv->lnum == wp->w_topline)) + { +#ifdef FEAT_SIGNS + // If 'signcolumn' is set to 'number' and a sign is present + // in 'lnum', then display the sign instead of the line + // number. + if ((*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u') && sign_present) + get_sign_display_info(TRUE, wp, wlv); + else +#endif + { + // Draw the line number (empty space after wrapping). + // When there are text properties above the line put the line number + // below them. + if (wlv->row == lnum_row + && (wp->w_skipcol == 0 || wlv->row > 0 + || (wp->w_p_nu && wp->w_p_rnu))) + { + long num; + char *fmt = "%*ld "; + + if (wp->w_p_nu && !wp->w_p_rnu) + // 'number' + 'norelativenumber' + num = (long)wlv->lnum; + else + { + // 'relativenumber', don't use negative numbers + num = labs((long)get_cursor_rel_lnum(wp, wlv->lnum)); + if (num == 0 && wp->w_p_nu && wp->w_p_rnu) + { + // 'number' + 'relativenumber' + num = wlv->lnum; + fmt = "%-*ld "; + } + } + + sprintf((char *)wlv->extra, fmt, number_width(wp), num); + if (wp->w_skipcol > 0 && wlv->startrow == 0) + for (wlv->p_extra = wlv->extra; *wlv->p_extra == ' '; + ++wlv->p_extra) + *wlv->p_extra = '-'; +#ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) // reverse line numbers + { + char_u *p1, *p2; + int t; + + // like rl_mirror(), but keep the space at the end + p2 = skipwhite(wlv->extra); + p2 = skiptowhite(p2) - 1; + for (p1 = skipwhite(wlv->extra); p1 < p2; ++p1, --p2) + { + t = *p1; + *p1 = *p2; + *p2 = t; + } + } +#endif + wlv->p_extra = wlv->extra; + wlv->c_extra = NUL; + wlv->c_final = NUL; + } + else + { + wlv->c_extra = ' '; + wlv->c_final = NUL; + } + wlv->n_extra = number_width(wp) + 1; + wlv->char_attr = hl_combine_attr(wlv->wcr_attr, HL_ATTR(HLF_N)); +#ifdef FEAT_SYN_HL + // When 'cursorline' is set highlight the line number of + // the current line differently. + // When 'cursorlineopt' does not have "line" only + // highlight the line number itself. + // TODO: Can we use CursorLine instead of CursorLineNr + // when CursorLineNr isn't set? + if (wp->w_p_cul + && wlv->lnum == wp->w_cursor.lnum + && (wp->w_p_culopt_flags & CULOPT_NBR) + && (wlv->row == wlv->startrow + wlv->filler_lines + || (wlv->row > wlv->startrow + wlv->filler_lines + && (wp->w_p_culopt_flags & CULOPT_LINE)))) + wlv->char_attr = hl_combine_attr(wlv->wcr_attr, HL_ATTR(HLF_CLN)); +#endif + if (wp->w_p_rnu && wlv->lnum < wp->w_cursor.lnum + && HL_ATTR(HLF_LNA) != 0) + // Use LineNrAbove + wlv->char_attr = hl_combine_attr(wlv->wcr_attr, HL_ATTR(HLF_LNA)); + if (wp->w_p_rnu && wlv->lnum > wp->w_cursor.lnum + && HL_ATTR(HLF_LNB) != 0) + // Use LineNrBelow + wlv->char_attr = hl_combine_attr(wlv->wcr_attr, HL_ATTR(HLF_LNB)); + } +#ifdef FEAT_SIGNS + if (num_attr) + wlv->char_attr = num_attr; +#endif + } +} + +#ifdef FEAT_LINEBREAK + static void +handle_breakindent(win_T *wp, winlinevars_T *wlv) +{ + if (wp->w_briopt_sbr && wlv->draw_state == WL_BRI - 1 + && *get_showbreak_value(wp) != NUL) + // draw indent after showbreak value + wlv->draw_state = WL_BRI; + else if (wp->w_briopt_sbr && wlv->draw_state == WL_SBR) + // After the showbreak, draw the breakindent + wlv->draw_state = WL_BRI - 1; + + // draw 'breakindent': indent wrapped text accordingly + if (wlv->draw_state == WL_BRI - 1) + { + wlv->draw_state = WL_BRI; + // if wlv->need_showbreak is set, breakindent also applies + if (wp->w_p_bri && (wlv->row > wlv->startrow +# ifdef FEAT_DIFF + + wlv->filler_lines +# endif + || wlv->need_showbreak) +# ifdef FEAT_PROP_POPUP + && !wlv->dont_use_showbreak +# endif + ) + { + wlv->char_attr = 0; +# ifdef FEAT_DIFF + if (wlv->diff_hlf != (hlf_T)0) + wlv->char_attr = HL_ATTR(wlv->diff_hlf); +# endif + wlv->p_extra = NULL; + wlv->c_extra = ' '; + wlv->c_final = NUL; + wlv->n_extra = get_breakindent_win(wp, + ml_get_buf(wp->w_buffer, wlv->lnum, FALSE)); + if (wlv->row == wlv->startrow) + { + wlv->n_extra -= win_col_off2(wp); + if (wlv->n_extra < 0) + wlv->n_extra = 0; + } + + // Correct start of highlighted area for 'breakindent', + if (wlv->fromcol >= wlv->vcol + && wlv->fromcol < wlv->vcol + wlv->n_extra) + wlv->fromcol = wlv->vcol + wlv->n_extra; + + // Correct end of highlighted area for 'breakindent', + // required when 'linebreak' is also set. + if (wlv->tocol == wlv->vcol) + wlv->tocol += wlv->n_extra; + } + + if (wp->w_skipcol > 0 && wlv->startrow == 0 && wp->w_p_wrap + && wp->w_briopt_sbr) + wlv->need_showbreak = FALSE; + } +} +#endif + +#if defined(FEAT_LINEBREAK) || defined(FEAT_DIFF) + static void +handle_showbreak_and_filler(win_T *wp, winlinevars_T *wlv) +{ +# ifdef FEAT_DIFF + if (wlv->filler_todo > 0) + { + // Draw "deleted" diff line(s). + if (char2cells(wp->w_fill_chars.diff) > 1) + { + wlv->c_extra = '-'; + wlv->c_final = NUL; + } + else + { + wlv->c_extra = wp->w_fill_chars.diff; + wlv->c_final = NUL; + } +# ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + wlv->n_extra = wlv->col + 1; + else +# endif + wlv->n_extra = wp->w_width - wlv->col; + wlv->char_attr = HL_ATTR(HLF_DED); + } +# endif + +# ifdef FEAT_LINEBREAK + char_u *sbr = get_showbreak_value(wp); + if (*sbr != NUL && wlv->need_showbreak) + { + // Draw 'showbreak' at the start of each broken line. + wlv->p_extra = sbr; + wlv->c_extra = NUL; + wlv->c_final = NUL; + wlv->n_extra = (int)STRLEN(sbr); + wlv->vcol_sbr = wlv->vcol + MB_CHARLEN(sbr); + + // Correct start of highlighted area for 'showbreak'. + if (wlv->fromcol >= wlv->vcol && wlv->fromcol < wlv->vcol_sbr) + wlv->fromcol = wlv->vcol_sbr; + + // Correct end of highlighted area for 'showbreak', + // required when 'linebreak' is also set. + if (wlv->tocol == wlv->vcol) + wlv->tocol += wlv->n_extra; + // combine 'showbreak' with 'wincolor' + wlv->char_attr = hl_combine_attr(wlv->win_attr, HL_ATTR(HLF_AT)); +# ifdef FEAT_SYN_HL + // combine 'showbreak' with 'cursorline' + if (wlv->cul_attr != 0) + wlv->char_attr = hl_combine_attr(wlv->char_attr, wlv->cul_attr); +# endif + } + + if (wp->w_skipcol == 0 || wlv->startrow > 0 || !wp->w_p_wrap + || !wp->w_briopt_sbr) + wlv->need_showbreak = FALSE; +# endif +} +#endif + +#if defined(FEAT_PROP_POPUP) || defined(PROTO) +/* + * Return the cell size of virtual text after truncation. + */ + static int +textprop_size_after_trunc( + win_T *wp, + int flags, // TP_FLAG_ALIGN_* + int added, + int padding, + char_u *text, + int *n_used_ptr) +{ + int space = (flags & (TP_FLAG_ALIGN_BELOW | TP_FLAG_ALIGN_ABOVE)) + ? wp->w_width - win_col_off(wp) : added; + int len = (int)STRLEN(text); + int strsize = 0; + int n_used; + + // if the remaining size is to small and 'wrap' is set we wrap anyway and + // use the next line + if (space < PROP_TEXT_MIN_CELLS && wp->w_p_wrap) + space += wp->w_width; + if (flags & (TP_FLAG_ALIGN_BELOW | TP_FLAG_ALIGN_ABOVE)) + space -= padding; + for (n_used = 0; n_used < len; n_used += (*mb_ptr2len)(text + n_used)) + { + int clen = ptr2cells(text + n_used); + + if (strsize + clen > space) + break; + strsize += clen; + } + *n_used_ptr = n_used; + return strsize; +} + +/* + * Take care of padding, right-align and truncation of virtual text after a + * line. + * if "n_attr" is not NULL then "n_extra" and "p_extra" are adjusted for any + * padding, right-align and truncation. Otherwise only the size is computed. + * When "n_attr" is NULL returns the number of screen cells used. + * Otherwise returns TRUE when drawing continues on the next line. + */ + int +text_prop_position( + win_T *wp, + textprop_T *tp, + int vcol, // current text column + int scr_col, // current screen column + int *n_extra, // nr of bytes for virtual text + char_u **p_extra, // virtual text + int *n_attr, // attribute cells, NULL if not used + int *n_attr_skip, // cells to skip attr, NULL if not used + int do_skip) // skip_cells is not zero +{ + int right = (tp->tp_flags & TP_FLAG_ALIGN_RIGHT); + int above = (tp->tp_flags & TP_FLAG_ALIGN_ABOVE); + int below = (tp->tp_flags & TP_FLAG_ALIGN_BELOW); + int wrap = tp->tp_col < MAXCOL || (tp->tp_flags & TP_FLAG_WRAP); + int padding = tp->tp_col == MAXCOL && tp->tp_len > 1 + ? tp->tp_len - 1 : 0; + int col_with_padding = scr_col + (below ? 0 : padding); + int room = wp->w_width - col_with_padding; + int before = room; // spaces before the text + int after = 0; // spaces after the text + int n_used = *n_extra; + char_u *l = NULL; + int strsize = vim_strsize(*p_extra); + int cells = wrap ? strsize : textprop_size_after_trunc(wp, + tp->tp_flags, before, padding, *p_extra, &n_used); + + if (wrap || right || above || below || padding > 0 || n_used < *n_extra) + { + int col_off = win_col_off(wp) - win_col_off2(wp); + + if (above) + { + before = 0; + after = wp->w_width - cells - win_col_off(wp) - padding; + if (after < 0) + { + // text "above" has too much padding to fit + padding += after; + after = 0; + } + } + else + { + // Right-align: fill with before + if (right) + before -= cells; + + // Below-align: empty line add one character + if (below && vcol == 0 && col_with_padding == col_off + && wp->w_width - col_off == before) + col_with_padding += 1; + + if (before < 0 + || !(right || below) + || (below ? (col_with_padding <= col_off || !wp->w_p_wrap) + : (n_used < *n_extra))) + { + if (right && (wrap + || (room < PROP_TEXT_MIN_CELLS && wp->w_p_wrap))) + { + // right-align on next line instead of wrapping if possible + before = wp->w_width - col_off - strsize + room; + if (before < 0) + before = 0; + else + n_used = *n_extra; + } + else if (below && before > vcol && do_skip) + before -= vcol; + else + before = 0; + } + } + + // With 'nowrap' add one to show the "extends" character if needed (it + // doesn't show if the text just fits). + if (!wp->w_p_wrap + && n_used < *n_extra + && wp->w_lcs_chars.ext != NUL + && wp->w_p_list) + ++n_used; + + // add 1 for NUL, 2 for when '…' is used + if (n_attr != NULL) + l = alloc(n_used + before + after + (padding > 0 ? padding : 0) + 3); + if (n_attr == NULL || l != NULL) + { + int off = 0; + + if (n_attr != NULL) + { + vim_memset(l, ' ', before); + off += before; + if (padding > 0) + { + vim_memset(l + off, ' ', padding); + off += padding; + } + vim_strncpy(l + off, *p_extra, n_used); + off += n_used; + } + else + { + off = before + after + padding + n_used; + cells += before + after + padding; + } + if (n_attr != NULL) + { + if (n_used < *n_extra && wp->w_p_wrap) + { + char_u *lp = l + off - 1; + + if (has_mbyte) + { + char_u buf[MB_MAXBYTES + 1]; + char_u *cp = buf; + + // change the last character to '…', converted to the + // current 'encoding' + STRCPY(buf, "…"); + if (!enc_utf8) + { + vimconv_T vc; + + vc.vc_type = CONV_NONE; + convert_setup(&vc, (char_u *)"utf-8", p_enc); + if (vc.vc_type != CONV_NONE) + { + cp = string_convert(&vc, buf, NULL); + if (cp == NULL) + { + // when conversion fails use '>' + cp = buf; + STRCPY(buf, ">"); + } + convert_setup(&vc, NULL, NULL); + } + } + + lp -= (*mb_ptr2cells)(cp) - 1; + lp -= (*mb_head_off)(l, lp); + STRCPY(lp, cp); + n_used = lp - l + 3 - before - padding; + if (cp != buf) + vim_free(cp); + } + else + // change last character to '>' + *lp = '>'; + } + else if (after > 0) + { + vim_memset(l + off, ' ', after); + l[off + after] = NUL; + } + + *p_extra = l; + *n_extra = n_used + before + after + padding; + *n_attr = mb_charlen(*p_extra); + if (above) + *n_attr -= padding + after; + + // n_attr_skip will not be decremented before draw_state is + // WL_LINE + *n_attr_skip = before + (padding > 0 ? padding : 0); + } + } + } + + if (n_attr == NULL) + return cells; + return (below && col_with_padding > win_col_off(wp) && !wp->w_p_wrap); +} +#endif + +/* + * Call screen_line() using values from "wlv". + * Also takes care of putting "<<<" on the first line for 'smoothscroll' + * when 'showbreak' is not set. + */ + static void +wlv_screen_line(win_T *wp, winlinevars_T *wlv, int negative_width) +{ + if (wlv->row == 0 && wp->w_skipcol > 0 +#if defined(FEAT_LINEBREAK) + // do not overwrite the 'showbreak' text with "<<<" + && *get_showbreak_value(wp) == NUL +#endif + // do not overwrite the 'listchars' "precedes" text with "<<<" + && !(wp->w_p_list && wp->w_lcs_chars.prec != 0)) + { + int off = (int)(current_ScreenLine - ScreenLines); + int max_off = off + screen_Columns; + int skip = 0; + + if (wp->w_p_nu && wp->w_p_rnu) + // Do not overwrite the line number, change "123 text" to + // "123<<w_width && VIM_ISDIGIT(ScreenLines[off])) + { + ++off; + ++skip; + } + + for (int i = 0; i < 3 && i + skip < wp->w_width; ++i) + { + if ((*mb_off2cells)(off, max_off) > 1) + // When the first half of a double-width character is + // overwritten, change the second half to a space. + ScreenLines[off + 1] = ' '; + ScreenLines[off] = '<'; + if (enc_utf8) + ScreenLinesUC[off] = 0; + ScreenAttrs[off] = HL_ATTR(HLF_AT); + ++off; + } + } + + screen_line(wp, wlv->screen_row, wp->w_wincol, wlv->col, + negative_width ? -wp->w_width : wp->w_width, + wlv->screen_line_flags); +} + +/* + * Called when finished with the line: draw the screen line and handle any + * highlighting until the right of the window. + */ + static void +draw_screen_line(win_T *wp, winlinevars_T *wlv) +{ +#ifdef FEAT_SYN_HL + long v; + + // Highlight 'cursorcolumn' & 'colorcolumn' past end of the line. + if (wp->w_p_wrap) + v = wlv->startrow == 0 ? wp->w_skipcol : 0; + else + v = wp->w_leftcol; + + // check if line ends before left margin + if (wlv->vcol < v + wlv->col - win_col_off(wp)) + wlv->vcol = v + wlv->col - win_col_off(wp); +# ifdef FEAT_CONCEAL + // Get rid of the boguscols now, we want to draw until the right + // edge for 'cursorcolumn'. + wlv->col -= wlv->boguscols; + wlv->boguscols = 0; +# define VCOL_HLC (wlv->vcol - wlv->vcol_off_co - wlv->vcol_off_tp) +# else +# define VCOL_HLC (wlv->vcol - wlv->vcol_off_tp) +# endif + + if (wlv->draw_color_col) + wlv->draw_color_col = advance_color_col(VCOL_HLC, &wlv->color_cols); + + if (((wp->w_p_cuc + && (int)wp->w_virtcol >= VCOL_HLC - wlv->eol_hl_off + && (int)wp->w_virtcol < + (long)wp->w_width * (wlv->row - wlv->startrow + 1) + v + && wlv->lnum != wp->w_cursor.lnum) + || wlv->draw_color_col +# ifdef LINE_ATTR + || wlv->line_attr != 0 +# endif + || wlv->win_attr != 0) +# ifdef FEAT_RIGHTLEFT + && !wp->w_p_rl +# endif + ) + { + int rightmost_vcol = 0; + int i; + + if (wp->w_p_cuc) + rightmost_vcol = wp->w_virtcol; + if (wlv->draw_color_col) + // determine rightmost colorcolumn to possibly draw + for (i = 0; wlv->color_cols[i] >= 0; ++i) + if (rightmost_vcol < wlv->color_cols[i]) + rightmost_vcol = wlv->color_cols[i]; + + while (wlv->col < wp->w_width) + { + ScreenLines[wlv->off] = ' '; + if (enc_utf8) + ScreenLinesUC[wlv->off] = 0; + ScreenCols[wlv->off] = MAXCOL; + ++wlv->col; + if (wlv->draw_color_col) + wlv->draw_color_col = advance_color_col( + VCOL_HLC, &wlv->color_cols); + + int attr = wlv->win_attr; + if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol) + attr = HL_ATTR(HLF_CUC); + else if (wlv->draw_color_col && VCOL_HLC == *wlv->color_cols) + attr = HL_ATTR(HLF_MC); +# ifdef LINE_ATTR + else if (wlv->line_attr != 0) + attr = wlv->line_attr; +# endif + ScreenAttrs[wlv->off++] = attr; + + if (VCOL_HLC >= rightmost_vcol +# ifdef LINE_ATTR + && wlv->line_attr == 0 +# endif + && wlv->win_attr == 0) + break; + + ++wlv->vcol; + } + } +#endif + + wlv_screen_line(wp, wlv, FALSE); + ++wlv->row; + ++wlv->screen_row; +} +#undef VCOL_HLC + +/* + * Start a screen line at column zero. + * When "save_extra" is TRUE save and reset n_extra, p_extra, etc. + */ + static void +win_line_start(win_T *wp UNUSED, winlinevars_T *wlv, int save_extra) +{ + wlv->col = 0; + wlv->off = (unsigned)(current_ScreenLine - ScreenLines); +#ifdef FEAT_LINEBREAK + wlv->need_lbr = FALSE; +#endif + +#ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + { + // Rightleft window: process the text in the normal direction, but put + // it in current_ScreenLine[] from right to left. Start at the + // rightmost column of the window. + wlv->col = wp->w_width - 1; + wlv->off += wlv->col; + wlv->screen_line_flags |= SLF_RIGHTLEFT; + } +#endif + if (save_extra) + { + // reset the drawing state for the start of a wrapped line + wlv->draw_state = WL_START; + wlv->saved_n_extra = wlv->n_extra; + wlv->saved_p_extra = wlv->p_extra; + vim_free(wlv->saved_p_extra_free); + wlv->saved_p_extra_free = wlv->p_extra_free; + wlv->p_extra_free = NULL; + wlv->saved_extra_attr = wlv->extra_attr; + wlv->saved_n_attr_skip = wlv->n_attr_skip; + wlv->saved_extra_for_textprop = wlv->extra_for_textprop; + wlv->saved_c_extra = wlv->c_extra; + wlv->saved_c_final = wlv->c_final; +#ifdef FEAT_LINEBREAK + wlv->need_lbr = TRUE; +#endif +#ifdef FEAT_SYN_HL + if (!(wlv->cul_screenline +# ifdef FEAT_DIFF + && wlv->diff_hlf == (hlf_T)0 +# endif + )) + wlv->saved_char_attr = wlv->char_attr; + else +#endif + wlv->saved_char_attr = 0; + + // these are not used until restored in win_line_continue() + wlv->n_extra = 0; + wlv->n_attr_skip = 0; + } +} + +/* + * Called when wlv->draw_state is set to WL_LINE. + */ + static void +win_line_continue(winlinevars_T *wlv) +{ + if (wlv->saved_n_extra > 0) + { + // Continue item from end of wrapped line. + wlv->n_extra = wlv->saved_n_extra; + wlv->saved_n_extra = 0; + wlv->c_extra = wlv->saved_c_extra; + wlv->c_final = wlv->saved_c_final; + wlv->p_extra = wlv->saved_p_extra; + vim_free(wlv->p_extra_free); + wlv->p_extra_free = wlv->saved_p_extra_free; + wlv->saved_p_extra_free = NULL; + wlv->extra_attr = wlv->saved_extra_attr; + wlv->n_attr_skip = wlv->saved_n_attr_skip; + wlv->extra_for_textprop = wlv->saved_extra_for_textprop; + wlv->char_attr = wlv->saved_char_attr; + } + else + wlv->char_attr = wlv->win_attr; +} + +#ifdef FEAT_SYN_HL + static void +apply_cursorline_highlight( + winlinevars_T *wlv, + int sign_present UNUSED) +{ + wlv->cul_attr = HL_ATTR(HLF_CUL); +# ifdef FEAT_SIGNS + // Combine the 'cursorline' and sign highlighting, depending on + // the sign priority. + if (sign_present && wlv->sattr.sat_linehl > 0) + { + if (wlv->sattr.sat_priority >= 100) + wlv->line_attr = hl_combine_attr(wlv->cul_attr, wlv->line_attr); + else + wlv->line_attr = hl_combine_attr(wlv->line_attr, wlv->cul_attr); + } + else +# endif +# if defined(FEAT_QUICKFIX) + // let the line attribute overrule 'cursorline', otherwise + // it disappears when both have background set; + // 'cursorline' can use underline or bold to make it show + wlv->line_attr = hl_combine_attr(wlv->cul_attr, wlv->line_attr); +# else + wlv->line_attr = wlv->cul_attr; +# endif +} +#endif + +/* + * Display line "lnum" of window "wp" on the screen. + * Start at row "startrow", stop when "endrow" is reached. + * When "number_only" is TRUE only update the number column. + * "spv" is used to store information for spell checking, kept between + * sequential calls for the same window. + * wp->w_virtcol needs to be valid. + * + * Return the number of last row the line occupies. + */ + int +win_line( + win_T *wp, + linenr_T lnum, + int startrow, + int endrow, + int number_only, + spellvars_T *spv UNUSED) +{ + winlinevars_T wlv; // variables passed between functions + + int c = 0; // init for GCC + long vcol_prev = -1; // "wlv.vcol" of previous character + char_u *line; // current line + char_u *ptr; // current position in "line" + +#ifdef FEAT_PROP_POPUP + char_u *p_extra_free2 = NULL; // another p_extra to be freed +#endif +#if defined(FEAT_LINEBREAK) && defined(FEAT_PROP_POPUP) + int in_linebreak = FALSE; // n_extra set for showing linebreak +#endif + static char_u *at_end_str = (char_u *)""; // used for p_extra when + // displaying eol at end-of-line + int lcs_eol_one = wp->w_lcs_chars.eol; // eol until it's been used + int lcs_prec_todo = wp->w_lcs_chars.prec; + // prec until it's been used + + int n_attr = 0; // chars with special attr + int saved_attr2 = 0; // char_attr saved for n_attr + int n_attr3 = 0; // chars with overruling special attr + int saved_attr3 = 0; // char_attr saved for n_attr3 + + int skip_cells = 0; // nr of cells to skip for w_leftcol or + // w_skipcol or concealing + int skipped_cells = 0; // nr of skipped cells for virtual text + // to be added to wlv.vcol later + int fromcol_prev = -2; // start of inverting after cursor + int noinvcur = FALSE; // don't invert the cursor + int lnum_in_visual_area = FALSE; + pos_T pos; + long v; + + int attr_pri = FALSE; // char_attr has priority + int area_highlighting = FALSE; // Visual or incsearch highlighting + // in this line + int vi_attr = 0; // attributes for Visual and incsearch + // highlighting + int area_attr = 0; // attributes desired by highlighting + int search_attr = 0; // attributes desired by 'hlsearch' +#ifdef FEAT_SYN_HL + int vcol_save_attr = 0; // saved attr for 'cursorcolumn' + int syntax_attr = 0; // attributes desired by syntax + int prev_syntax_col = -1; // column of prev_syntax_attr + int prev_syntax_attr = 0; // syntax_attr at prev_syntax_col + int has_syntax = FALSE; // this buffer has syntax highl. + int save_did_emsg; +#endif +#ifdef FEAT_PROP_POPUP + int did_line = FALSE; // set to TRUE when line text done + int text_prop_count; + int last_textprop_text_idx = -1; + int text_prop_next = 0; // next text property to use + textprop_T *text_props = NULL; + int *text_prop_idxs = NULL; + int text_props_active = 0; + proptype_T *text_prop_type = NULL; + int text_prop_attr = 0; + int text_prop_attr_comb = 0; // text_prop_attr combined with + // syntax_attr + int text_prop_id = 0; // active property ID + int text_prop_flags = 0; + int text_prop_above = FALSE; // first doing virtual text above + int text_prop_follows = FALSE; // another text prop to display + int saved_search_attr = 0; // search_attr to be used when n_extra + // goes to zero + int saved_area_attr = 0; // idem for area_attr + int reset_extra_attr = FALSE; +#endif +#ifdef FEAT_SPELL + int can_spell = FALSE; +# define SPWORDLEN 150 + char_u nextline[SPWORDLEN * 2];// text with start of the next line + int nextlinecol = 0; // column where nextline[] starts + int nextline_idx = 0; // index in nextline[] where next line + // starts + int spell_attr = 0; // attributes desired by spelling + int word_end = 0; // last byte with same spell_attr + int cur_checked_col = 0; // checked column for current line +#endif + int extra_check = 0; // has extra highlighting + int multi_attr = 0; // attributes desired by multibyte + int mb_l = 1; // multi-byte byte length + int mb_c = 0; // decoded multi-byte character + int mb_utf8 = FALSE; // screen char is UTF-8 char + int u8cc[MAX_MCO]; // composing UTF-8 chars +#ifdef FEAT_DIFF + int change_start = MAXCOL; // first col of changed area + int change_end = -1; // last col of changed area +#endif + colnr_T trailcol = MAXCOL; // start of trailing spaces + colnr_T leadcol = 0; // start of leading spaces + int in_multispace = FALSE; // in multiple consecutive spaces + int multispace_pos = 0; // position in lcs-multispace string +#ifdef LINE_ATTR + int line_attr_save = 0; +#endif + int sign_present = FALSE; + int num_attr = 0; // attribute for the number column +#ifdef FEAT_ARABIC + int prev_c = 0; // previous Arabic character + int prev_c1 = 0; // first composing char for prev_c +#endif +#if defined(LINE_ATTR) + int did_line_attr = 0; +#endif +#ifdef FEAT_TERMINAL + int get_term_attr = FALSE; +#endif + +#if defined(FEAT_SYN_HL) || defined(FEAT_DIFF) + // margin columns for the screen line, needed for when 'cursorlineopt' + // contains "screenline" + int left_curline_col = 0; + int right_curline_col = 0; +#endif + +#if defined(FEAT_XIM) && defined(FEAT_GUI_GTK) + int feedback_col = 0; + int feedback_old_attr = -1; +#endif + +#if defined(FEAT_CONCEAL) || defined(FEAT_SEARCH_EXTRA) + int match_conc = 0; // cchar for match functions +#endif +#if defined(FEAT_CONCEAL) || defined(FEAT_SEARCH_EXTRA) || defined(FEAT_LINEBREAK) + int on_last_col = FALSE; +#endif +#ifdef FEAT_CONCEAL + int syntax_flags = 0; + int syntax_seqnr = 0; + int prev_syntax_id = 0; + int conceal_attr = HL_ATTR(HLF_CONCEAL); + int is_concealing = FALSE; + int did_wcol = FALSE; + int old_boguscols = 0; +# define VCOL_HLC (wlv.vcol - wlv.vcol_off_co - wlv.vcol_off_tp) +# define FIX_FOR_BOGUSCOLS \ + { \ + wlv.n_extra += wlv.vcol_off_co; \ + wlv.vcol -= wlv.vcol_off_co; \ + wlv.vcol_off_co = 0; \ + wlv.col -= wlv.boguscols; \ + old_boguscols = wlv.boguscols; \ + wlv.boguscols = 0; \ + } +#else +# define VCOL_HLC (wlv.vcol - wlv.vcol_off_tp) +#endif + + if (startrow > endrow) // past the end already! + return startrow; + + CLEAR_FIELD(wlv); + + wlv.lnum = lnum; + wlv.startrow = startrow; + wlv.row = startrow; + wlv.screen_row = wlv.row + W_WINROW(wp); + wlv.fromcol = -10; + wlv.tocol = MAXCOL; +#ifdef FEAT_LINEBREAK + wlv.vcol_sbr = -1; +#endif + + if (!number_only) + { + // To speed up the loop below, set extra_check when there is linebreak, + // trailing white space and/or syntax processing to be done. +#ifdef FEAT_LINEBREAK + extra_check = wp->w_p_lbr; +#endif +#ifdef FEAT_SYN_HL + if (syntax_present(wp) && !wp->w_s->b_syn_error +# ifdef SYN_TIME_LIMIT + && !wp->w_s->b_syn_slow +# endif + ) + { + // Prepare for syntax highlighting in this line. When there is an + // error, stop syntax highlighting. + save_did_emsg = did_emsg; + did_emsg = FALSE; + syntax_start(wp, lnum); + if (did_emsg) + wp->w_s->b_syn_error = TRUE; + else + { + did_emsg = save_did_emsg; +#ifdef SYN_TIME_LIMIT + if (!wp->w_s->b_syn_slow) +#endif + { + has_syntax = TRUE; + extra_check = TRUE; + } + } + } + + // Check for columns to display for 'colorcolumn'. + wlv.color_cols = wp->w_p_cc_cols; + if (wlv.color_cols != NULL) + wlv.draw_color_col = advance_color_col(VCOL_HLC, &wlv.color_cols); +#endif + +#ifdef FEAT_TERMINAL + if (term_show_buffer(wp->w_buffer)) + { + extra_check = TRUE; + get_term_attr = TRUE; + wlv.win_attr = term_get_attr(wp, lnum, -1); + } +#endif + + // handle Visual active in this window + if (VIsual_active && wp->w_buffer == curwin->w_buffer) + { + pos_T *top, *bot; + + if (LTOREQ_POS(curwin->w_cursor, VIsual)) + { + // Visual is after curwin->w_cursor + top = &curwin->w_cursor; + bot = &VIsual; + } + else + { + // Visual is before curwin->w_cursor + top = &VIsual; + bot = &curwin->w_cursor; + } + lnum_in_visual_area = (lnum >= top->lnum && lnum <= bot->lnum); + if (VIsual_mode == Ctrl_V) + { + // block mode + if (lnum_in_visual_area) + { + wlv.fromcol = wp->w_old_cursor_fcol; + wlv.tocol = wp->w_old_cursor_lcol; + } + } + else + { + // non-block mode + if (lnum > top->lnum && lnum <= bot->lnum) + wlv.fromcol = 0; + else if (lnum == top->lnum) + { + if (VIsual_mode == 'V') // linewise + wlv.fromcol = 0; + else + { + getvvcol(wp, top, (colnr_T *)&wlv.fromcol, NULL, NULL); + if (gchar_pos(top) == NUL) + wlv.tocol = wlv.fromcol + 1; + } + } + if (VIsual_mode != 'V' && lnum == bot->lnum) + { + if (*p_sel == 'e' && bot->col == 0 && bot->coladd == 0) + { + wlv.fromcol = -10; + wlv.tocol = MAXCOL; + } + else if (bot->col == MAXCOL) + wlv.tocol = MAXCOL; + else + { + pos = *bot; + if (*p_sel == 'e') + getvvcol(wp, &pos, (colnr_T *)&wlv.tocol, + NULL, NULL); + else + { + getvvcol(wp, &pos, NULL, NULL, + (colnr_T *)&wlv.tocol); + ++wlv.tocol; + } + } + } + } + + // Check if the character under the cursor should not be inverted + if (!highlight_match && lnum == curwin->w_cursor.lnum + && wp == curwin +#ifdef FEAT_GUI + && !gui.in_use +#endif + ) + noinvcur = TRUE; + + // if inverting in this line set area_highlighting + if (wlv.fromcol >= 0) + { + area_highlighting = TRUE; + vi_attr = HL_ATTR(HLF_V); +#if defined(FEAT_CLIPBOARD) && defined(FEAT_X11) + if ((clip_star.available && !clip_star.owned + && clip_isautosel_star()) + || (clip_plus.available && !clip_plus.owned + && clip_isautosel_plus())) + vi_attr = HL_ATTR(HLF_VNC); +#endif + } + } + + // handle 'incsearch' and ":s///c" highlighting + else if (highlight_match + && wp == curwin + && lnum >= curwin->w_cursor.lnum + && lnum <= curwin->w_cursor.lnum + search_match_lines) + { + if (lnum == curwin->w_cursor.lnum) + getvcol(curwin, &(curwin->w_cursor), + (colnr_T *)&wlv.fromcol, NULL, NULL); + else + wlv.fromcol = 0; + if (lnum == curwin->w_cursor.lnum + search_match_lines) + { + pos.lnum = lnum; + pos.col = search_match_endcol; + getvcol(curwin, &pos, (colnr_T *)&wlv.tocol, NULL, NULL); + } + else + wlv.tocol = MAXCOL; + // do at least one character; happens when past end of line + if (wlv.fromcol == wlv.tocol && search_match_endcol) + wlv.tocol = wlv.fromcol + 1; + area_highlighting = TRUE; + vi_attr = HL_ATTR(HLF_I); + } + } + +#ifdef FEAT_DIFF + wlv.filler_lines = diff_check(wp, lnum); + if (wlv.filler_lines < 0) + { + if (wlv.filler_lines == -1) + { + if (diff_find_change(wp, lnum, &change_start, &change_end)) + wlv.diff_hlf = HLF_ADD; // added line + else if (change_start == 0) + wlv.diff_hlf = HLF_TXD; // changed text + else + wlv.diff_hlf = HLF_CHD; // changed line + } + else + wlv.diff_hlf = HLF_ADD; // added line + wlv.filler_lines = 0; + area_highlighting = TRUE; + } + if (lnum == wp->w_topline) + wlv.filler_lines = wp->w_topfill; + wlv.filler_todo = wlv.filler_lines; +#endif + +#ifdef FEAT_SIGNS + sign_present = buf_get_signattrs(wp, lnum, &wlv.sattr); + if (sign_present) + num_attr = wlv.sattr.sat_numhl; +#endif + +#ifdef LINE_ATTR +# ifdef FEAT_SIGNS + // If this line has a sign with line highlighting set wlv.line_attr. + if (sign_present) + wlv.line_attr = wlv.sattr.sat_linehl; +# endif +# if defined(FEAT_QUICKFIX) + // Highlight the current line in the quickfix window. + if (bt_quickfix(wp->w_buffer) && qf_current_entry(wp) == lnum) + wlv.line_attr = HL_ATTR(HLF_QFL); +# endif + if (wlv.line_attr != 0) + area_highlighting = TRUE; +#endif + +#ifdef FEAT_SPELL + if (spv->spv_has_spell && !number_only) + { + // Prepare for spell checking. + extra_check = TRUE; + + // When a word wrapped from the previous line the start of the + // current line is valid. + if (lnum == spv->spv_checked_lnum) + cur_checked_col = spv->spv_checked_col; + // Previous line was not spell checked, check for capital. This happens + // for the first line in an updated region or after a closed fold. + if (spv->spv_capcol_lnum == 0 && check_need_cap(wp, lnum, 0)) + spv->spv_cap_col = 0; + else if (lnum != spv->spv_capcol_lnum) + spv->spv_cap_col = -1; + spv->spv_checked_lnum = 0; + + // Get the start of the next line, so that words that wrap to the + // next line are found too: "etal.". + // Trick: skip a few chars for C/shell/Vim comments + nextline[SPWORDLEN] = NUL; + if (lnum < wp->w_buffer->b_ml.ml_line_count) + { + line = ml_get_buf(wp->w_buffer, lnum + 1, FALSE); + spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN); + } + line = ml_get_buf(wp->w_buffer, lnum, FALSE); + + // If current line is empty, check first word in next line for capital. + ptr = skipwhite(line); + if (*ptr == NUL) + { + spv->spv_cap_col = 0; + spv->spv_capcol_lnum = lnum + 1; + } + // For checking first word with a capital skip white space. + else if (spv->spv_cap_col == 0) + spv->spv_cap_col = ptr - line; + + // Copy the end of the current line into nextline[]. + if (nextline[SPWORDLEN] == NUL) + { + // No next line or it is empty. + nextlinecol = MAXCOL; + nextline_idx = 0; + } + else + { + v = (long)STRLEN(line); + if (v < SPWORDLEN) + { + // Short line, use it completely and append the start of the + // next line. + nextlinecol = 0; + mch_memmove(nextline, line, (size_t)v); + STRMOVE(nextline + v, nextline + SPWORDLEN); + nextline_idx = v + 1; + } + else + { + // Long line, use only the last SPWORDLEN bytes. + nextlinecol = v - SPWORDLEN; + mch_memmove(nextline, line + nextlinecol, SPWORDLEN); + nextline_idx = SPWORDLEN + 1; + } + } + } +#endif + + line = ml_get_buf(wp->w_buffer, lnum, FALSE); + ptr = line; + + if (wp->w_p_list) + { + if (wp->w_lcs_chars.space + || wp->w_lcs_chars.multispace != NULL + || wp->w_lcs_chars.leadmultispace != NULL + || wp->w_lcs_chars.trail + || wp->w_lcs_chars.lead + || wp->w_lcs_chars.nbsp) + extra_check = TRUE; + + // find start of trailing whitespace + if (wp->w_lcs_chars.trail) + { + trailcol = (colnr_T)STRLEN(ptr); + while (trailcol > (colnr_T)0 && VIM_ISWHITE(ptr[trailcol - 1])) + --trailcol; + trailcol += (colnr_T)(ptr - line); + } + // find end of leading whitespace + if (wp->w_lcs_chars.lead || wp->w_lcs_chars.leadmultispace != NULL) + { + leadcol = 0; + while (VIM_ISWHITE(ptr[leadcol])) + ++leadcol; + if (ptr[leadcol] == NUL) + // in a line full of spaces all of them are treated as trailing + leadcol = (colnr_T)0; + else + // keep track of the first column not filled with spaces + leadcol += (colnr_T)(ptr - line) + 1; + } + } + + wlv.wcr_attr = get_wcr_attr(wp); + if (wlv.wcr_attr != 0) + { + wlv.win_attr = wlv.wcr_attr; + area_highlighting = TRUE; + } + + // When w_skipcol is non-zero and there is virtual text above the actual + // text, then this much of the virtual text is skipped. + int skipcol_in_text_prop_above = 0; + +#ifdef FEAT_PROP_POPUP + if (WIN_IS_POPUP(wp)) + wlv.screen_line_flags |= SLF_POPUP; + + char_u *prop_start; + text_prop_count = get_text_props(wp->w_buffer, lnum, &prop_start, FALSE); + if (text_prop_count > 0) + { + // Make a copy of the properties, so that they are properly + // aligned. + text_props = ALLOC_MULT(textprop_T, text_prop_count); + if (text_props != NULL) + mch_memmove(text_props, prop_start, + text_prop_count * sizeof(textprop_T)); + + // Allocate an array for the indexes. + text_prop_idxs = ALLOC_MULT(int, text_prop_count); + if (text_prop_idxs == NULL) + VIM_CLEAR(text_props); + + if (text_props != NULL) + { + area_highlighting = TRUE; + extra_check = TRUE; + + /* Find the last text property that inserts text. */ + for (int i = 0; i < text_prop_count; ++i) + if (text_props[i].tp_id < 0) + last_textprop_text_idx = i; + + // When skipping virtual text the props need to be sorted. The + // order is reversed! + if (lnum == wp->w_topline && wp->w_skipcol > 0) + { + for (int i = 0; i < text_prop_count; ++i) + text_prop_idxs[i] = i; + sort_text_props(wp->w_buffer, text_props, + text_prop_idxs, text_prop_count); + } + + // Text props "above" move the line number down to where the text + // is. Only count the ones that are visible, not those that are + // skipped because of w_skipcol. + int text_width = wp->w_width - win_col_off(wp); + for (int i = text_prop_count - 1; i >= 0; --i) + if (text_props[i].tp_flags & TP_FLAG_ALIGN_ABOVE) + { + if (lnum == wp->w_topline + && wp->w_skipcol - skipcol_in_text_prop_above + >= text_width) + { + // This virtual text above is skipped, remove it from + // the array. + skipcol_in_text_prop_above += text_width; + for (int j = i + 1; j < text_prop_count; ++j) + text_props[j - 1] = text_props[j]; + ++i; + --text_prop_count; + } + else + ++wlv.text_prop_above_count; + } + } + } + + if (number_only) + { + // skip over rows only used for virtual text above + wlv.row += wlv.text_prop_above_count; + if (wlv.row > endrow) + return wlv.row; + wlv.screen_row += wlv.text_prop_above_count; + } +#endif + +#if defined(FEAT_LINEBREAK) || defined(FEAT_PROP_POPUP) + colnr_T vcol_first_char = 0; + if (wp->w_p_lbr && !number_only) + { + chartabsize_T cts; + init_chartabsize_arg(&cts, wp, lnum, 0, line, line); + (void)win_lbr_chartabsize(&cts, NULL); + vcol_first_char = cts.cts_first_char; + clear_chartabsize_arg(&cts); + } +#endif + + // 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the + // first character to be displayed. + if (wp->w_p_wrap) + v = startrow == 0 ? wp->w_skipcol - skipcol_in_text_prop_above : 0; + else + v = wp->w_leftcol; + if (v > 0 && !number_only) + { + char_u *prev_ptr = ptr; + chartabsize_T cts; + int charsize = 0; + int head = 0; + + init_chartabsize_arg(&cts, wp, lnum, wlv.vcol, line, ptr); + cts.cts_max_head_vcol = v; + while (cts.cts_vcol < v && *cts.cts_ptr != NUL) + { + head = 0; + charsize = win_lbr_chartabsize(&cts, &head); + cts.cts_vcol += charsize; + prev_ptr = cts.cts_ptr; + MB_PTR_ADV(cts.cts_ptr); + if (wp->w_p_list) + { + in_multispace = *prev_ptr == ' ' && (*cts.cts_ptr == ' ' + || (prev_ptr > line && prev_ptr[-1] == ' ')); + if (!in_multispace) + multispace_pos = 0; + else if (cts.cts_ptr >= line + leadcol + && wp->w_lcs_chars.multispace != NULL) + { + ++multispace_pos; + if (wp->w_lcs_chars.multispace[multispace_pos] == NUL) + multispace_pos = 0; + } + else if (cts.cts_ptr < line + leadcol + && wp->w_lcs_chars.leadmultispace != NULL) + { + ++multispace_pos; + if (wp->w_lcs_chars.leadmultispace[multispace_pos] == NUL) + multispace_pos = 0; + } + } + } + wlv.vcol = cts.cts_vcol; + ptr = cts.cts_ptr; + clear_chartabsize_arg(&cts); + + // When: + // - 'cuc' is set, or + // - 'colorcolumn' is set, or + // - 'virtualedit' is set, or + // - the visual mode is active, + // the end of the line may be before the start of the displayed part. + if (wlv.vcol < v && ( +#ifdef FEAT_SYN_HL + wp->w_p_cuc || wlv.draw_color_col || +#endif + virtual_active() || + (VIsual_active && wp->w_buffer == curwin->w_buffer))) + wlv.vcol = v; + + // Handle a character that's not completely on the screen: Put ptr at + // that character but skip the first few screen characters. + if (wlv.vcol > v) + { + wlv.vcol -= charsize; + ptr = prev_ptr; + } + if (v > wlv.vcol) + skip_cells = v - wlv.vcol - head; + + // Adjust for when the inverted text is before the screen, + // and when the start of the inverted text is before the screen. + if (wlv.tocol <= wlv.vcol) + wlv.fromcol = 0; + else if (wlv.fromcol >= 0 && wlv.fromcol < wlv.vcol) + wlv.fromcol = wlv.vcol; + +#ifdef FEAT_LINEBREAK + // When w_skipcol is non-zero, first line needs 'showbreak' + if (wp->w_p_wrap) + wlv.need_showbreak = TRUE; +#endif +#ifdef FEAT_SPELL + // When spell checking a word we need to figure out the start of the + // word and if it's badly spelled or not. + if (spv->spv_has_spell) + { + int len; + colnr_T linecol = (colnr_T)(ptr - line); + hlf_T spell_hlf = HLF_COUNT; + + pos = wp->w_cursor; + wp->w_cursor.lnum = lnum; + wp->w_cursor.col = linecol; + len = spell_move_to(wp, FORWARD, TRUE, TRUE, &spell_hlf); + + // spell_move_to() may call ml_get() and make "line" invalid + line = ml_get_buf(wp->w_buffer, lnum, FALSE); + ptr = line + linecol; + + if (len == 0 || (int)wp->w_cursor.col > ptr - line) + { + // no bad word found at line start, don't check until end of a + // word + spell_hlf = HLF_COUNT; + word_end = (int)(spell_to_word_end(ptr, wp) - line + 1); + } + else + { + // bad word found, use attributes until end of word + word_end = wp->w_cursor.col + len + 1; + + // Turn index into actual attributes. + if (spell_hlf != HLF_COUNT) + spell_attr = highlight_attr[spell_hlf]; + } + wp->w_cursor = pos; + +# ifdef FEAT_SYN_HL + // Need to restart syntax highlighting for this line. + if (has_syntax) + syntax_start(wp, lnum); +# endif + } +#endif + } + + // Correct highlighting for cursor that can't be disabled. + // Avoids having to check this for each character. + if (wlv.fromcol >= 0) + { + if (noinvcur) + { + if ((colnr_T)wlv.fromcol == wp->w_virtcol) + { + // highlighting starts at cursor, let it start just after the + // cursor + fromcol_prev = wlv.fromcol; + wlv.fromcol = -1; + } + else if ((colnr_T)wlv.fromcol < wp->w_virtcol) + // restart highlighting after the cursor + fromcol_prev = wp->w_virtcol; + } + if (wlv.fromcol >= wlv.tocol) + wlv.fromcol = -1; + } + +#ifdef FEAT_SEARCH_EXTRA + if (!number_only) + { + v = (long)(ptr - line); + area_highlighting |= prepare_search_hl_line(wp, lnum, (colnr_T)v, + &line, &screen_search_hl, + &search_attr); + ptr = line + v; // "line" may have been updated + } +#endif + +#ifdef FEAT_SYN_HL + // Cursor line highlighting for 'cursorline' in the current window. + if (wp->w_p_cul && lnum == wp->w_cursor.lnum) + { + // Do not show the cursor line in the text when Visual mode is active, + // because it's not clear what is selected then. + if (!(wp == curwin && VIsual_active) + && wp->w_p_culopt_flags != CULOPT_NBR) + { + wlv.cul_screenline = (wp->w_p_wrap + && (wp->w_p_culopt_flags & CULOPT_SCRLINE)); + + // Only apply CursorLine highlight here when "screenline" is not + // present in 'cursorlineopt'. Otherwise it's done later. + if (!wlv.cul_screenline) + apply_cursorline_highlight(&wlv, sign_present); + else + { + line_attr_save = wlv.line_attr; + margin_columns_win(wp, &left_curline_col, &right_curline_col); + } + area_highlighting = TRUE; + } + } +#endif + + win_line_start(wp, &wlv, FALSE); + + // Repeat for the whole displayed line. + for (;;) + { +#if defined(FEAT_CONCEAL) || defined(FEAT_SEARCH_EXTRA) + int has_match_conc = 0; // match wants to conceal +#endif +#ifdef FEAT_CONCEAL + int did_decrement_ptr = FALSE; +#endif + + // Skip this quickly when working on the text. + if (wlv.draw_state != WL_LINE) + { +#ifdef FEAT_SYN_HL + if (wlv.cul_screenline) + { + wlv.cul_attr = 0; + wlv.line_attr = line_attr_save; + } +#endif + if (wlv.draw_state == WL_CMDLINE - 1 && wlv.n_extra == 0) + { + wlv.draw_state = WL_CMDLINE; + if (cmdwin_type != 0 && wp == curwin) + { + // Draw the cmdline character. + wlv.n_extra = 1; + wlv.c_extra = cmdwin_type; + wlv.c_final = NUL; + wlv.char_attr = + hl_combine_attr(wlv.wcr_attr, HL_ATTR(HLF_AT)); + } + } +#ifdef FEAT_FOLDING + if (wlv.draw_state == WL_FOLD - 1 && wlv.n_extra == 0) + { + wlv.draw_state = WL_FOLD; + handle_foldcolumn(wp, &wlv); + } +#endif +#ifdef FEAT_SIGNS + if (wlv.draw_state == WL_SIGN - 1 && wlv.n_extra == 0) + { + // Show the sign column when desired or when using Netbeans. + wlv.draw_state = WL_SIGN; + if (signcolumn_on(wp)) + get_sign_display_info(FALSE, wp, &wlv); + } +#endif + if (wlv.draw_state == WL_NR - 1 && wlv.n_extra == 0) + { + // Show the line number, if desired. + wlv.draw_state = WL_NR; + handle_lnum_col(wp, &wlv, sign_present, num_attr); + } +#ifdef FEAT_LINEBREAK + // Check if 'breakindent' applies and show it. + // May change wlv.draw_state to WL_BRI or WL_BRI - 1. + if (wlv.n_extra == 0) + handle_breakindent(wp, &wlv); +#endif +#if defined(FEAT_LINEBREAK) || defined(FEAT_DIFF) + if (wlv.draw_state == WL_SBR - 1 && wlv.n_extra == 0) + { + wlv.draw_state = WL_SBR; + handle_showbreak_and_filler(wp, &wlv); + } +#endif + if (wlv.draw_state == WL_LINE - 1 && wlv.n_extra == 0) + { + wlv.draw_state = WL_LINE; + win_line_continue(&wlv); // use wlv.saved_ values + } + } + +#ifdef FEAT_SYN_HL + if (wlv.cul_screenline && wlv.draw_state == WL_LINE + && wlv.vcol >= left_curline_col + && wlv.vcol < right_curline_col) + { + apply_cursorline_highlight(&wlv, sign_present); + } +#endif + + // When still displaying '$' of change command, stop at cursor. + // When only displaying the (relative) line number and that's done, + // stop here. + if (((dollar_vcol >= 0 && wp == curwin + && lnum == wp->w_cursor.lnum + && wlv.vcol >= (long)wp->w_virtcol) + || (number_only && wlv.draw_state > WL_NR)) +#ifdef FEAT_DIFF + && wlv.filler_todo <= 0 +#endif + ) + { + wlv_screen_line(wp, &wlv, TRUE); + // Pretend we have finished updating the window. Except when + // 'cursorcolumn' is set. +#ifdef FEAT_SYN_HL + if (wp->w_p_cuc) + wlv.row = wp->w_cline_row + wp->w_cline_height; + else +#endif + wlv.row = wp->w_height; + break; + } + + if (wlv.draw_state == WL_LINE && (area_highlighting || extra_check)) + { +#ifdef FEAT_PROP_POPUP + if (text_props != NULL) + { + int pi; + int bcol = (int)(ptr - line); + + if (wlv.n_extra > 0 +# ifdef FEAT_LINEBREAK + && !in_linebreak +# endif + ) + --bcol; // still working on the previous char, e.g. Tab + + // Check if any active property ends. + for (pi = 0; pi < text_props_active; ++pi) + { + int tpi = text_prop_idxs[pi]; + textprop_T *tp = &text_props[tpi]; + + // An inline property ends when after the start column plus + // length. An "above" property ends when used and n_extra + // is zero. + if ((tp->tp_col != MAXCOL + && bcol >= tp->tp_col - 1 + tp->tp_len)) + { + if (pi + 1 < text_props_active) + mch_memmove(text_prop_idxs + pi, + text_prop_idxs + pi + 1, + sizeof(int) + * (text_props_active - (pi + 1))); + --text_props_active; + --pi; +# ifdef FEAT_LINEBREAK + // not exactly right but should work in most cases + if (in_linebreak && syntax_attr == text_prop_attr_comb) + syntax_attr = 0; +# endif + } + } + +# ifdef FEAT_LINEBREAK + if (wlv.n_extra > 0 && in_linebreak) + // not on the next char yet, don't start another prop + --bcol; +# endif + int display_text_first = FALSE; + + // Add any text property that starts in this column. + // With 'nowrap' and not in the first screen line only "below" + // text prop can show. + while (text_prop_next < text_prop_count + && (text_props[text_prop_next].tp_col == MAXCOL + ? ((*ptr == NUL + && (wp->w_p_wrap + || wlv.row == startrow + || (text_props[text_prop_next].tp_flags + & TP_FLAG_ALIGN_BELOW))) + || (bcol == 0 + && (text_props[text_prop_next].tp_flags + & TP_FLAG_ALIGN_ABOVE))) + : bcol >= text_props[text_prop_next].tp_col - 1)) + { + if (text_props[text_prop_next].tp_col == MAXCOL + || bcol <= text_props[text_prop_next].tp_col - 1 + + text_props[text_prop_next].tp_len) + text_prop_idxs[text_props_active++] = text_prop_next; + ++text_prop_next; + } + + if (wlv.n_extra == 0 || + (!wlv.extra_for_textprop + && !(text_prop_type != NULL && + text_prop_flags & PT_FLAG_OVERRIDE) + )) + { + text_prop_attr = 0; + text_prop_attr_comb = 0; + text_prop_flags = 0; + text_prop_type = NULL; + text_prop_id = 0; + reset_extra_attr = FALSE; + } + if (text_props_active > 0 && wlv.n_extra == 0 + && !display_text_first) + { + int used_tpi = -1; + int used_attr = 0; + int other_tpi = -1; + + text_prop_above = FALSE; + text_prop_follows = FALSE; + + // Sort the properties on priority and/or starting last. + // Then combine the attributes, highest priority last. + sort_text_props(wp->w_buffer, text_props, + text_prop_idxs, text_props_active); + + for (pi = 0; pi < text_props_active; ++pi) + { + int tpi = text_prop_idxs[pi]; + textprop_T *tp = &text_props[tpi]; + proptype_T *pt = text_prop_type_by_id( + wp->w_buffer, tp->tp_type); + + // Only use a text property that can be displayed. + // Skip "after" properties when wrap is off and at the + // end of the window. + if (pt != NULL + && (pt->pt_hl_id > 0 || tp->tp_id < 0) + && tp->tp_id != -MAXCOL + && !(tp->tp_id < 0 + && !wp->w_p_wrap + && (tp->tp_flags & (TP_FLAG_ALIGN_RIGHT + | TP_FLAG_ALIGN_ABOVE + | TP_FLAG_ALIGN_BELOW)) == 0 + && wlv.col >= wp->w_width)) + { + if (tp->tp_col == MAXCOL + && *ptr == NUL + && ((wp->w_p_list && lcs_eol_one > 0 + && (tp->tp_flags + & TP_FLAG_ALIGN_ABOVE) == 0) + || (ptr == line + && !did_line + && (tp->tp_flags + & TP_FLAG_ALIGN_BELOW)))) + { + // skip this prop, first display the '$' after + // the line or display an empty line + text_prop_follows = TRUE; + if (used_tpi < 0) + display_text_first = TRUE; + continue; + } + + if (pt->pt_hl_id > 0) + used_attr = syn_id2attr(pt->pt_hl_id); + text_prop_type = pt; + text_prop_attr = + hl_combine_attr(text_prop_attr, used_attr); + if (used_tpi >= 0 && text_props[used_tpi].tp_id < 0) + other_tpi = used_tpi; + text_prop_flags = pt->pt_flags; + text_prop_id = tp->tp_id; + used_tpi = tpi; + display_text_first = FALSE; + } + } + if (text_prop_id < 0 && used_tpi >= 0 + && -text_prop_id + <= wp->w_buffer->b_textprop_text.ga_len) + { + textprop_T *tp = &text_props[used_tpi]; + char_u *p = ((char_u **)wp->w_buffer + ->b_textprop_text.ga_data)[ + -text_prop_id - 1]; + int above = (tp->tp_flags + & TP_FLAG_ALIGN_ABOVE); + int bail_out = FALSE; + + // reset the ID in the copy to avoid it being used + // again + tp->tp_id = -MAXCOL; + + if (p != NULL) + { + int right = (tp->tp_flags + & TP_FLAG_ALIGN_RIGHT); + int below = (tp->tp_flags + & TP_FLAG_ALIGN_BELOW); + int wrap = tp->tp_col < MAXCOL + || (tp->tp_flags & TP_FLAG_WRAP); + int padding = tp->tp_col == MAXCOL + && tp->tp_len > 1 + ? tp->tp_len - 1 : 0; + + // Insert virtual text before the current + // character, or add after the end of the line. + wlv.p_extra = p; + wlv.c_extra = NUL; + wlv.c_final = NUL; + wlv.n_extra = (int)STRLEN(p); + wlv.extra_for_textprop = TRUE; + wlv.start_extra_for_textprop = TRUE; + wlv.extra_attr = hl_combine_attr(wlv.win_attr, + used_attr); + n_attr = mb_charlen(p); + text_prop_attr = 0; + text_prop_attr_comb = 0; + if (*ptr == NUL) + // don't combine char attr after EOL + text_prop_flags &= ~PT_FLAG_COMBINE; +# ifdef FEAT_LINEBREAK + if (above || below || right || !wrap) + { + // no 'showbreak' before "below" text property + // or after "above" or "right" text property + wlv.need_showbreak = FALSE; + wlv.dont_use_showbreak = TRUE; + } +# endif + if ((right || above || below || !wrap + || padding > 0) && wp->w_width > 2) + { + char_u *prev_p_extra = wlv.p_extra; + int start_line; + + // Take care of padding, right-align and + // truncation. + // Shared with win_lbr_chartabsize(), must do + // exactly the same. + start_line = text_prop_position(wp, tp, + wlv.vcol, +# ifdef FEAT_RIGHTLEFT + wp->w_p_rl + ? wp->w_width - wlv.col - 1 + : +# endif + wlv.col, + &wlv.n_extra, &wlv.p_extra, + &n_attr, &wlv.n_attr_skip, + skip_cells > 0); + if (wlv.p_extra != prev_p_extra) + { + // wlv.p_extra was allocated + vim_free(p_extra_free2); + p_extra_free2 = wlv.p_extra; + } + + if (above) + wlv.vcol_off_tp = wlv.n_extra; + + if (lcs_eol_one < 0 + && wp->w_p_wrap + && wlv.col + + wlv.n_extra - 2 > wp->w_width) + // don't bail out at end of line + text_prop_follows = TRUE; + + // When 'wrap' is off then for "below" we need + // to start a new line explicitly. + if (start_line) + { + draw_screen_line(wp, &wlv); + + // When line got too long for screen break + // here. + if (wlv.row == endrow) + { + ++wlv.row; + break; + } + win_line_start(wp, &wlv, TRUE); + bail_out = TRUE; + } + } + } + + // If the text didn't reach until the first window + // column we need to skip cells. + if (skip_cells > 0) + { + if (wlv.n_extra > skip_cells) + { + wlv.n_extra -= skip_cells; + wlv.p_extra += skip_cells; + wlv.n_attr_skip -= skip_cells; + if (wlv.n_attr_skip < 0) + wlv.n_attr_skip = 0; + skipped_cells += skip_cells; + skip_cells = 0; + } + else + { + // the whole text is left of the window, drop + // it and advance to the next one + skip_cells -= wlv.n_extra; + skipped_cells += wlv.n_extra; + wlv.n_extra = 0; + wlv.n_attr_skip = 0; + bail_out = TRUE; + } + } + + // If another text prop follows the condition below at + // the last window column must know. + // If this is an "above" text prop and 'nowrap' the we + // must wrap anyway. + text_prop_above = above; + text_prop_follows |= other_tpi != -1 + && (wp->w_p_wrap + || (text_props[other_tpi].tp_flags + & (TP_FLAG_ALIGN_BELOW | TP_FLAG_ALIGN_RIGHT))); + + if (bail_out) + // starting a new line for "below" + continue; + } + } + else if (text_prop_next < text_prop_count + && text_props[text_prop_next].tp_col == MAXCOL + && ((*ptr != NUL && ptr[mb_ptr2len(ptr)] == NUL) + || (!wp->w_p_wrap + && wlv.col == wp->w_width - 1 + && (text_props[text_prop_next].tp_flags + & TP_FLAG_ALIGN_BELOW)))) + // When at last-but-one character and a text property + // follows after it, we may need to flush the line after + // displaying that character. + // Or when not wrapping and at the rightmost column. + text_prop_follows = TRUE; + } + + if (wlv.start_extra_for_textprop) + { + wlv.start_extra_for_textprop = FALSE; + // restore search_attr and area_attr when n_extra + // is down to zero + saved_search_attr = search_attr; + saved_area_attr = area_attr; + search_attr = 0; + area_attr = 0; + } +#endif + + int *area_attr_p = +#ifdef FEAT_PROP_POPUP + wlv.extra_for_textprop ? &saved_area_attr : +#endif + &area_attr; + + // handle Visual or match highlighting in this line + if (wlv.vcol == wlv.fromcol + || (has_mbyte && wlv.vcol + 1 == wlv.fromcol + && ((wlv.n_extra == 0 && (*mb_ptr2cells)(ptr) > 1) + || (wlv.n_extra > 0 && wlv.p_extra != NULL + && (*mb_ptr2cells)(wlv.p_extra) > 1))) + || ((int)vcol_prev == fromcol_prev + && vcol_prev < wlv.vcol // not at margin + && wlv.vcol < wlv.tocol)) + *area_attr_p = vi_attr; // start highlighting + else if (*area_attr_p != 0 + && (wlv.vcol == wlv.tocol + || (noinvcur && (colnr_T)wlv.vcol == wp->w_virtcol))) + *area_attr_p = 0; // stop highlighting + +#ifdef FEAT_SEARCH_EXTRA + if (wlv.n_extra == 0) + { + // Check for start/end of 'hlsearch' and other matches. + // After end, check for start/end of next match. + // When another match, have to check for start again. + v = (long)(ptr - line); + search_attr = update_search_hl(wp, lnum, (colnr_T)v, &line, + &screen_search_hl, &has_match_conc, + &match_conc, did_line_attr, lcs_eol_one, + &on_last_col); + ptr = line + v; // "line" may have been changed + + // Do not allow a conceal over EOL otherwise EOL will be missed + // and bad things happen. + if (*ptr == NUL) + has_match_conc = 0; + } +#endif + +#ifdef FEAT_DIFF + if (wlv.diff_hlf != (hlf_T)0) + { + // When there is extra text (e.g. virtual text) it gets the + // diff highlighting for the line, but not for changed text. + if (wlv.diff_hlf == HLF_CHD && ptr - line >= change_start + && wlv.n_extra == 0) + wlv.diff_hlf = HLF_TXD; // changed text + if (wlv.diff_hlf == HLF_TXD + && ((ptr - line > change_end && wlv.n_extra == 0) + || (wlv.n_extra > 0 && wlv.extra_for_textprop))) + wlv.diff_hlf = HLF_CHD; // changed line + wlv.line_attr = HL_ATTR(wlv.diff_hlf); + if (wp->w_p_cul && lnum == wp->w_cursor.lnum + && wp->w_p_culopt_flags != CULOPT_NBR + && (!wlv.cul_screenline || (wlv.vcol >= left_curline_col + && wlv.vcol <= right_curline_col))) + wlv.line_attr = hl_combine_attr( + wlv.line_attr, HL_ATTR(HLF_CUL)); + } +#endif + +#ifdef FEAT_SYN_HL + if (extra_check && wlv.n_extra == 0) + { + syntax_attr = 0; +# ifdef FEAT_TERMINAL + if (get_term_attr) + syntax_attr = term_get_attr(wp, lnum, wlv.vcol); +# endif + // Get syntax attribute. + if (has_syntax) + { + // Get the syntax attribute for the character. If there + // is an error, disable syntax highlighting. + save_did_emsg = did_emsg; + did_emsg = FALSE; + + v = (long)(ptr - line); + if (v == prev_syntax_col) + // at same column again + syntax_attr = prev_syntax_attr; + else + { +# ifdef FEAT_SPELL + can_spell = TRUE; +# endif + syntax_attr = get_syntax_attr((colnr_T)v, +# ifdef FEAT_SPELL + spv->spv_has_spell ? &can_spell : +# endif + NULL, FALSE); + prev_syntax_col = v; + prev_syntax_attr = syntax_attr; + } + + if (did_emsg) + { + wp->w_s->b_syn_error = TRUE; + has_syntax = FALSE; + syntax_attr = 0; + } + else + did_emsg = save_did_emsg; +# ifdef SYN_TIME_LIMIT + if (wp->w_s->b_syn_slow) + has_syntax = FALSE; +# endif + + // Need to get the line again, a multi-line regexp may + // have made it invalid. + line = ml_get_buf(wp->w_buffer, lnum, FALSE); + ptr = line + v; +# ifdef FEAT_CONCEAL + // no concealing past the end of the line, it interferes + // with line highlighting + if (*ptr == NUL) + syntax_flags = 0; + else + syntax_flags = get_syntax_info(&syntax_seqnr); +# endif + } + } +# ifdef FEAT_PROP_POPUP + // Combine text property highlight into syntax highlight. + if (text_prop_type != NULL) + { + if (text_prop_flags & PT_FLAG_COMBINE) + syntax_attr = hl_combine_attr(syntax_attr, text_prop_attr); + else + syntax_attr = text_prop_attr; + text_prop_attr_comb = syntax_attr; + } +# endif +#endif + + // Decide which of the highlight attributes to use. + attr_pri = TRUE; +#ifdef LINE_ATTR + if (area_attr != 0) + { + wlv.char_attr = hl_combine_attr(wlv.line_attr, area_attr); + if (!highlight_match) + // let search highlight show in Visual area if possible + wlv.char_attr = hl_combine_attr(search_attr, wlv.char_attr); +# ifdef FEAT_SYN_HL + wlv.char_attr = hl_combine_attr(syntax_attr, wlv.char_attr); +# endif + } + else if (search_attr != 0) + { + wlv.char_attr = hl_combine_attr(wlv.line_attr, search_attr); +# ifdef FEAT_SYN_HL + wlv.char_attr = hl_combine_attr(syntax_attr, wlv.char_attr); +# endif + } + else if (wlv.line_attr != 0 + && ((wlv.fromcol == -10 && wlv.tocol == MAXCOL) + || wlv.vcol < wlv.fromcol + || vcol_prev < fromcol_prev + || wlv.vcol >= wlv.tocol)) + { + // Use wlv.line_attr when not in the Visual or 'incsearch' area + // (area_attr may be 0 when "noinvcur" is set). +# ifdef FEAT_SYN_HL + wlv.char_attr = hl_combine_attr(syntax_attr, wlv.line_attr); +# else + wlv.char_attr = wlv.line_attr; +# endif + attr_pri = FALSE; + } +#else + if (area_attr != 0) + wlv.char_attr = area_attr; + else if (search_attr != 0) + wlv.char_attr = search_attr; +#endif + else + { + attr_pri = FALSE; +#ifdef FEAT_SYN_HL + wlv.char_attr = syntax_attr; +#else + wlv.char_attr = 0; +#endif + } +#ifdef FEAT_PROP_POPUP + // override with text property highlight when "override" is TRUE + if (text_prop_type != NULL && (text_prop_flags & PT_FLAG_OVERRIDE)) + wlv.char_attr = hl_combine_attr(wlv.char_attr, text_prop_attr); +#endif + } + + // combine attribute with 'wincolor' + if (wlv.win_attr != 0) + { + if (wlv.char_attr == 0) + wlv.char_attr = wlv.win_attr; + else + wlv.char_attr = hl_combine_attr(wlv.win_attr, wlv.char_attr); + } + + // Get the next character to put on the screen. + + // The "p_extra" points to the extra stuff that is inserted to + // represent special characters (non-printable stuff) and other + // things. When all characters are the same, c_extra is used. + // If wlv.c_final is set, it will compulsorily be used at the end. + // "p_extra" must end in a NUL to avoid mb_ptr2len() reads past + // "p_extra[n_extra]". + // For the '$' of the 'list' option, n_extra == 1, p_extra == "". + if (wlv.n_extra > 0) + { + if (wlv.c_extra != NUL || (wlv.n_extra == 1 && wlv.c_final != NUL)) + { + c = (wlv.n_extra == 1 && wlv.c_final != NUL) + ? wlv.c_final : wlv.c_extra; + mb_c = c; // doesn't handle non-utf-8 multi-byte! + if (enc_utf8 && utf_char2len(c) > 1) + { + mb_utf8 = TRUE; + u8cc[0] = 0; + c = 0xc0; + } + else + mb_utf8 = FALSE; + } + else + { + c = *wlv.p_extra; + if (has_mbyte) + { + mb_c = c; + if (enc_utf8) + { + // If the UTF-8 character is more than one byte: + // Decode it into "mb_c". + mb_l = utfc_ptr2len(wlv.p_extra); + mb_utf8 = FALSE; + if (mb_l > wlv.n_extra) + mb_l = 1; + else if (mb_l > 1) + { + mb_c = utfc_ptr2char(wlv.p_extra, u8cc); + mb_utf8 = TRUE; + c = 0xc0; + } + } + else + { + // if this is a DBCS character, put it in "mb_c" + mb_l = MB_BYTE2LEN(c); + if (mb_l >= wlv.n_extra) + mb_l = 1; + else if (mb_l > 1) + mb_c = (c << 8) + wlv.p_extra[1]; + } + if (mb_l == 0) // at the NUL at end-of-line + mb_l = 1; + + // If a double-width char doesn't fit display a '>' in the + // last column. + if (( +# ifdef FEAT_RIGHTLEFT + wp->w_p_rl ? (wlv.col <= 0) : +# endif + (wlv.col >= wp->w_width - 1)) + && (*mb_char2cells)(mb_c) == 2) + { + c = '>'; + mb_c = c; + mb_l = 1; + mb_utf8 = FALSE; + multi_attr = HL_ATTR(HLF_AT); +#ifdef FEAT_SYN_HL + if (wlv.cul_attr) + multi_attr = hl_combine_attr( + multi_attr, wlv.cul_attr); +#endif + multi_attr = hl_combine_attr(wlv.win_attr, multi_attr); + + // put the pointer back to output the double-width + // character at the start of the next line. + ++wlv.n_extra; + --wlv.p_extra; + } + else + { + wlv.n_extra -= mb_l - 1; + wlv.p_extra += mb_l - 1; + } + } + ++wlv.p_extra; + } + --wlv.n_extra; +#if defined(FEAT_PROP_POPUP) + if (wlv.n_extra <= 0) + { + // Only restore search_attr and area_attr after "n_extra" in + // the next screen line is also done. + if (wlv.saved_n_extra <= 0) + { + if (search_attr == 0) + search_attr = saved_search_attr; + if (area_attr == 0 && *ptr != NUL) + area_attr = saved_area_attr; + + if (wlv.extra_for_textprop) + // wlv.extra_attr should be used at this position but + // not any further. + reset_extra_attr = TRUE; + } + + wlv.extra_for_textprop = FALSE; + in_linebreak = FALSE; + } +#endif + } + else + { +#ifdef FEAT_LINEBREAK + int c0; +#endif + char_u *prev_ptr = ptr; + + // Get a character from the line itself. + c = *ptr; +#ifdef FEAT_LINEBREAK + c0 = *ptr; +#endif + if (c == NUL) + { +#ifdef FEAT_PROP_POPUP + // text is finished, may display a "below" virtual text + did_line = TRUE; +#endif + // no more cells to skip + skip_cells = 0; + } + + if (has_mbyte) + { + mb_c = c; + if (enc_utf8) + { + // If the UTF-8 character is more than one byte: Decode it + // into "mb_c". + mb_l = utfc_ptr2len(ptr); + mb_utf8 = FALSE; + if (mb_l > 1) + { + mb_c = utfc_ptr2char(ptr, u8cc); + // Overlong encoded ASCII or ASCII with composing char + // is displayed normally, except a NUL. + if (mb_c < 0x80) + { + c = mb_c; +#ifdef FEAT_LINEBREAK + c0 = mb_c; +#endif + } + mb_utf8 = TRUE; + + // At start of the line we can have a composing char. + // Draw it as a space with a composing char. + if (utf_iscomposing(mb_c)) + { + int i; + + for (i = Screen_mco - 1; i > 0; --i) + u8cc[i] = u8cc[i - 1]; + u8cc[0] = mb_c; + mb_c = ' '; + } + } + + if ((mb_l == 1 && c >= 0x80) + || (mb_l >= 1 && mb_c == 0) + || (mb_l > 1 && (!vim_isprintc(mb_c)))) + { + // Illegal UTF-8 byte: display as . + // Non-BMP character : display as ? or fullwidth ?. + transchar_hex(wlv.extra, mb_c); +# ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) // reverse + rl_mirror(wlv.extra); +# endif + wlv.p_extra = wlv.extra; + c = *wlv.p_extra; + mb_c = mb_ptr2char_adv(&wlv.p_extra); + mb_utf8 = (c >= 0x80); + wlv.n_extra = (int)STRLEN(wlv.p_extra); + wlv.c_extra = NUL; + wlv.c_final = NUL; + if (area_attr == 0 && search_attr == 0) + { + n_attr = wlv.n_extra + 1; + wlv.extra_attr = hl_combine_attr( + wlv.win_attr, HL_ATTR(HLF_8)); + saved_attr2 = wlv.char_attr; // save current attr + } + } + else if (mb_l == 0) // at the NUL at end-of-line + mb_l = 1; +#ifdef FEAT_ARABIC + else if (p_arshape && !p_tbidi && ARABIC_CHAR(mb_c)) + { + // Do Arabic shaping. + int pc, pc1, nc; + int pcc[MAX_MCO]; + + // The idea of what is the previous and next + // character depends on 'rightleft'. + if (wp->w_p_rl) + { + pc = prev_c; + pc1 = prev_c1; + nc = utf_ptr2char(ptr + mb_l); + prev_c1 = u8cc[0]; + } + else + { + pc = utfc_ptr2char(ptr + mb_l, pcc); + nc = prev_c; + pc1 = pcc[0]; + } + prev_c = mb_c; + + mb_c = arabic_shape(mb_c, &c, &u8cc[0], pc, pc1, nc); + } + else + prev_c = mb_c; +#endif + } + else // enc_dbcs + { + mb_l = MB_BYTE2LEN(c); + if (mb_l == 0) // at the NUL at end-of-line + mb_l = 1; + else if (mb_l > 1) + { + // We assume a second byte below 32 is illegal. + // Hopefully this is OK for all double-byte encodings! + if (ptr[1] >= 32) + mb_c = (c << 8) + ptr[1]; + else + { + if (ptr[1] == NUL) + { + // head byte at end of line + mb_l = 1; + transchar_nonprint(wp->w_buffer, wlv.extra, c); + } + else + { + // illegal tail byte + mb_l = 2; + STRCPY(wlv.extra, "XX"); + } + wlv.p_extra = wlv.extra; + wlv.n_extra = (int)STRLEN(wlv.extra) - 1; + wlv.c_extra = NUL; + wlv.c_final = NUL; + c = *wlv.p_extra++; + if (area_attr == 0 && search_attr == 0) + { + n_attr = wlv.n_extra + 1; + wlv.extra_attr = hl_combine_attr( + wlv.win_attr, HL_ATTR(HLF_8)); + // save current attr + saved_attr2 = wlv.char_attr; + } + mb_c = c; + } + } + } + // If a double-width char doesn't fit display a '>' in the + // last column; the character is displayed at the start of the + // next line. + if (( +# ifdef FEAT_RIGHTLEFT + wp->w_p_rl ? (wlv.col <= 0) : +# endif + (wlv.col >= wp->w_width - 1)) + && (*mb_char2cells)(mb_c) == 2) + { + c = '>'; + mb_c = c; + mb_utf8 = FALSE; + mb_l = 1; + multi_attr = hl_combine_attr(wlv.win_attr, HL_ATTR(HLF_AT)); + // Put pointer back so that the character will be + // displayed at the start of the next line. + --ptr; +#ifdef FEAT_CONCEAL + did_decrement_ptr = TRUE; +#endif + } + else if (*ptr != NUL) + ptr += mb_l - 1; + + // If a double-width char doesn't fit at the left side display + // a '<' in the first column. Don't do this for unprintable + // characters. + if (skip_cells > 0 && mb_l > 1 && wlv.n_extra == 0) + { + wlv.n_extra = 1; + wlv.c_extra = MB_FILLER_CHAR; + wlv.c_final = NUL; + c = ' '; + if (area_attr == 0 && search_attr == 0) + { + n_attr = wlv.n_extra + 1; + wlv.extra_attr = hl_combine_attr( + wlv.win_attr, HL_ATTR(HLF_AT)); + saved_attr2 = wlv.char_attr; // save current attr + } + mb_c = c; + mb_utf8 = FALSE; + mb_l = 1; + } + + } + ++ptr; + + if (extra_check) + { +#ifdef FEAT_SPELL + // Check spelling (unless at the end of the line). + // Only do this when there is no syntax highlighting, the + // @Spell cluster is not used or the current syntax item + // contains the @Spell cluster. + v = (long)(ptr - line); + if (spv->spv_has_spell && v >= word_end && v > cur_checked_col) + { + spell_attr = 0; + // do not calculate cap_col at the end of the line or when + // only white space is following + if (c != 0 && (*skipwhite(prev_ptr) != NUL) && ( +# ifdef FEAT_SYN_HL + !has_syntax || +# endif + can_spell)) + { + char_u *p; + int len; + hlf_T spell_hlf = HLF_COUNT; + + if (has_mbyte) + v -= mb_l - 1; + + // Use nextline[] if possible, it has the start of the + // next line concatenated. + if ((prev_ptr - line) - nextlinecol >= 0) + p = nextline + (prev_ptr - line) - nextlinecol; + else + p = prev_ptr; + spv->spv_cap_col -= (int)(prev_ptr - line); + len = spell_check(wp, p, &spell_hlf, &spv->spv_cap_col, + spv->spv_unchanged); + word_end = v + len; + + // In Insert mode only highlight a word that + // doesn't touch the cursor. + if (spell_hlf != HLF_COUNT + && (State & MODE_INSERT) + && wp->w_cursor.lnum == lnum + && wp->w_cursor.col >= + (colnr_T)(prev_ptr - line) + && wp->w_cursor.col < (colnr_T)word_end) + { + spell_hlf = HLF_COUNT; + spell_redraw_lnum = lnum; + } + + if (spell_hlf == HLF_COUNT && p != prev_ptr + && (p - nextline) + len > nextline_idx) + { + // Remember that the good word continues at the + // start of the next line. + spv->spv_checked_lnum = lnum + 1; + spv->spv_checked_col = (p - nextline) + len + - nextline_idx; + } + + // Turn index into actual attributes. + if (spell_hlf != HLF_COUNT) + spell_attr = highlight_attr[spell_hlf]; + + if (spv->spv_cap_col > 0) + { + if (p != prev_ptr + && (p - nextline) + spv->spv_cap_col + >= nextline_idx) + { + // Remember that the word in the next line + // must start with a capital. + spv->spv_capcol_lnum = lnum + 1; + spv->spv_cap_col = ((p - nextline) + + spv->spv_cap_col - nextline_idx); + } + else + // Compute the actual column. + spv->spv_cap_col += (prev_ptr - line); + } + } + } + if (spell_attr != 0) + { + if (!attr_pri) + wlv.char_attr = hl_combine_attr(wlv.char_attr, + spell_attr); + else + wlv.char_attr = hl_combine_attr(spell_attr, + wlv.char_attr); + } +#endif +#ifdef FEAT_LINEBREAK + // we don't want linebreak to apply for lines that start with + // leading spaces, followed by long letters (since it would add + // a break at the beginning of a line and this might be unexpected) + // + // So only allow to linebreak, once we have found chars not in + // 'breakat' in the line. + if ( wp->w_p_lbr && !wlv.need_lbr && c != NUL && + !VIM_ISBREAK((int)*ptr)) + wlv.need_lbr = TRUE; +#endif +#ifdef FEAT_LINEBREAK + // Found last space before word: check for line break. + if (wp->w_p_lbr && c0 == c && wlv.need_lbr + && VIM_ISBREAK(c) && !VIM_ISBREAK((int)*ptr)) + { + int mb_off = has_mbyte ? (*mb_head_off)(line, ptr - 1) + : 0; + char_u *p = ptr - (mb_off + 1); + chartabsize_T cts; + + init_chartabsize_arg(&cts, wp, lnum, wlv.vcol +# ifdef FEAT_PROP_POPUP + - vcol_first_char, +# endif + line, p); +# ifdef FEAT_PROP_POPUP + // do not want virtual text counted here + cts.cts_has_prop_with_text = FALSE; +# endif + wlv.n_extra = win_lbr_chartabsize(&cts, NULL) - 1; + clear_chartabsize_arg(&cts); + + if (on_last_col && c != TAB) + // Do not continue search/match highlighting over the + // line break, but for TABs the highlighting should + // include the complete width of the character + search_attr = 0; + + if (c == TAB && wlv.n_extra + wlv.col > wp->w_width) +# ifdef FEAT_VARTABS + wlv.n_extra = tabstop_padding(wlv.vcol, + wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array) - 1; +# else + wlv.n_extra = (int)wp->w_buffer->b_p_ts + - wlv.vcol % (int)wp->w_buffer->b_p_ts - 1; +# endif + + wlv.c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' '; + wlv.c_final = NUL; +# ifdef FEAT_PROP_POPUP + if (wlv.n_extra > 0 && c != TAB) + in_linebreak = TRUE; +# endif + if (VIM_ISWHITE(c)) + { +# ifdef FEAT_CONCEAL + if (c == TAB) + // See "Tab alignment" below. + FIX_FOR_BOGUSCOLS; +# endif + if (!wp->w_p_list) + c = ' '; + } + } +#endif + if (wp->w_p_list) + { + in_multispace = c == ' ' && (*ptr == ' ' + || (prev_ptr > line && prev_ptr[-1] == ' ')); + if (!in_multispace) + multispace_pos = 0; + } + + // 'list': Change char 160 to 'nbsp' and space to 'space' + // setting in 'listchars'. But not when the character is + // followed by a composing character (use mb_l to check that). + if (wp->w_p_list + && ((((c == 160 && mb_l == 1) + || (mb_utf8 + && ((mb_c == 160 && mb_l == 2) + || (mb_c == 0x202f && mb_l == 3)))) + && wp->w_lcs_chars.nbsp) + || (c == ' ' + && mb_l == 1 + && (wp->w_lcs_chars.space + || (in_multispace + && wp->w_lcs_chars.multispace != NULL)) + && ptr - line >= leadcol + && ptr - line <= trailcol))) + { + if (in_multispace && wp->w_lcs_chars.multispace != NULL) + { + c = wp->w_lcs_chars.multispace[multispace_pos++]; + if (wp->w_lcs_chars.multispace[multispace_pos] == NUL) + multispace_pos = 0; + } + else + c = (c == ' ') ? wp->w_lcs_chars.space + : wp->w_lcs_chars.nbsp; + if (area_attr == 0 && search_attr == 0) + { + n_attr = 1; + wlv.extra_attr = hl_combine_attr(wlv.win_attr, + HL_ATTR(HLF_8)); + saved_attr2 = wlv.char_attr; // save current attr + } + mb_c = c; + if (enc_utf8 && utf_char2len(c) > 1) + { + mb_utf8 = TRUE; + u8cc[0] = 0; + c = 0xc0; + } + else + mb_utf8 = FALSE; + } + + if (c == ' ' && ((trailcol != MAXCOL && ptr > line + trailcol) + || (leadcol != 0 && ptr < line + leadcol))) + { + if (leadcol != 0 && in_multispace && ptr < line + leadcol + && wp->w_lcs_chars.leadmultispace != NULL) + { + c = wp->w_lcs_chars.leadmultispace[multispace_pos++]; + if (wp->w_lcs_chars.leadmultispace[multispace_pos] + == NUL) + multispace_pos = 0; + } + + else if (ptr > line + trailcol && wp->w_lcs_chars.trail) + c = wp->w_lcs_chars.trail; + + else if (ptr < line + leadcol && wp->w_lcs_chars.lead) + c = wp->w_lcs_chars.lead; + + else if (leadcol != 0 && wp->w_lcs_chars.space) + c = wp->w_lcs_chars.space; + + + if (!attr_pri) + { + n_attr = 1; + wlv.extra_attr = hl_combine_attr(wlv.win_attr, + HL_ATTR(HLF_8)); + saved_attr2 = wlv.char_attr; // save current attr + } + mb_c = c; + if (enc_utf8 && utf_char2len(c) > 1) + { + mb_utf8 = TRUE; + u8cc[0] = 0; + c = 0xc0; + } + else + mb_utf8 = FALSE; + } + } + + // Handling of non-printable characters. + if (!vim_isprintc(c)) + { + // when getting a character from the file, we may have to + // turn it into something else on the way to putting it + // into "ScreenLines". + if (c == TAB && (!wp->w_p_list || wp->w_lcs_chars.tab1)) + { + int tab_len = 0; + long vcol_adjusted = wlv.vcol; // removed showbreak len +#ifdef FEAT_LINEBREAK + char_u *sbr = get_showbreak_value(wp); + + // only adjust the tab_len, when at the first column + // after the showbreak value was drawn + if (*sbr != NUL && wlv.vcol == wlv.vcol_sbr && wp->w_p_wrap) + vcol_adjusted = wlv.vcol - MB_CHARLEN(sbr); +#endif + // tab amount depends on current column +#ifdef FEAT_VARTABS + tab_len = tabstop_padding(vcol_adjusted, + wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array) - 1; +#else + tab_len = (int)wp->w_buffer->b_p_ts + - vcol_adjusted % (int)wp->w_buffer->b_p_ts - 1; +#endif + +#ifdef FEAT_LINEBREAK + if (!wp->w_p_lbr || !wp->w_p_list) +#endif + { + // tab amount depends on current column + wlv.n_extra = tab_len; + } +#ifdef FEAT_LINEBREAK + else + { + char_u *p; + int len; + int i; + int saved_nextra = wlv.n_extra; + +# ifdef FEAT_CONCEAL + if (wlv.vcol_off_co > 0) + // there are characters to conceal + tab_len += wlv.vcol_off_co; + + // boguscols before FIX_FOR_BOGUSCOLS macro from above + if (wp->w_p_list && wp->w_lcs_chars.tab1 + && old_boguscols > 0 + && wlv.n_extra > tab_len) + tab_len += wlv.n_extra - tab_len; +# endif + if (tab_len > 0) + { + // If wlv.n_extra > 0, it gives the number of chars + // to use for a tab, else we need to calculate the + // width for a tab. + int tab2_len = mb_char2len(wp->w_lcs_chars.tab2); + len = tab_len * tab2_len; + if (wp->w_lcs_chars.tab3) + len += mb_char2len(wp->w_lcs_chars.tab3) + - tab2_len; + if (wlv.n_extra > 0) + len += wlv.n_extra - tab_len; + c = wp->w_lcs_chars.tab1; + p = alloc(len + 1); + if (p == NULL) + wlv.n_extra = 0; + else + { + vim_memset(p, ' ', len); + p[len] = NUL; + vim_free(wlv.p_extra_free); + wlv.p_extra_free = p; + for (i = 0; i < tab_len; i++) + { + int lcs = wp->w_lcs_chars.tab2; + + if (*p == NUL) + { + tab_len = i; + break; + } + + // if tab3 is given, use it for the last + // char + if (wp->w_lcs_chars.tab3 + && i == tab_len - 1) + lcs = wp->w_lcs_chars.tab3; + p += mb_char2bytes(lcs, p); + wlv.n_extra += mb_char2len(lcs) + - (saved_nextra > 0 ? 1 : 0); + } + wlv.p_extra = wlv.p_extra_free; +# ifdef FEAT_CONCEAL + // n_extra will be increased by + // FIX_FOX_BOGUSCOLS macro below, so need to + // adjust for that here + if (wlv.vcol_off_co > 0) + wlv.n_extra -= wlv.vcol_off_co; +# endif + } + } + } +#endif +#ifdef FEAT_CONCEAL + { + int vc_saved = wlv.vcol_off_co; + + // Tab alignment should be identical regardless of + // 'conceallevel' value. So tab compensates of all + // previous concealed characters, and thus resets + // vcol_off_co and boguscols accumulated so far in the + // line. Note that the tab can be longer than + // 'tabstop' when there are concealed characters. + FIX_FOR_BOGUSCOLS; + + // Make sure, the highlighting for the tab char will be + // correctly set further below (effectively reverts the + // FIX_FOR_BOGSUCOLS macro). + if (wlv.n_extra == tab_len + vc_saved && wp->w_p_list + && wp->w_lcs_chars.tab1) + tab_len += vc_saved; + } +#endif + mb_utf8 = FALSE; // don't draw as UTF-8 + if (wp->w_p_list) + { + c = (wlv.n_extra == 0 && wp->w_lcs_chars.tab3) + ? wp->w_lcs_chars.tab3 + : wp->w_lcs_chars.tab1; +#ifdef FEAT_LINEBREAK + if (wp->w_p_lbr && wlv.p_extra != NULL + && *wlv.p_extra != NUL) + wlv.c_extra = NUL; // using p_extra from above + else +#endif + wlv.c_extra = wp->w_lcs_chars.tab2; + wlv.c_final = wp->w_lcs_chars.tab3; + n_attr = tab_len + 1; + wlv.extra_attr = hl_combine_attr(wlv.win_attr, + HL_ATTR(HLF_8)); + saved_attr2 = wlv.char_attr; // save current attr + mb_c = c; + if (enc_utf8 && utf_char2len(c) > 1) + { + mb_utf8 = TRUE; + u8cc[0] = 0; + c = 0xc0; + } + } + else + { + wlv.c_final = NUL; + wlv.c_extra = ' '; + c = ' '; + } + } + else if (c == NUL + && wlv.n_extra == 0 + && (wp->w_p_list + || ((wlv.fromcol >= 0 || fromcol_prev >= 0) + && wlv.tocol > wlv.vcol + && VIsual_mode != Ctrl_V + && ( +# ifdef FEAT_RIGHTLEFT + wp->w_p_rl ? (wlv.col >= 0) : +# endif + (wlv.col < wp->w_width)) + && !(noinvcur + && lnum == wp->w_cursor.lnum + && (colnr_T)wlv.vcol == wp->w_virtcol))) + && lcs_eol_one > 0) + { + // Display a '$' after the line or highlight an extra + // character if the line break is included. +#if defined(FEAT_DIFF) || defined(LINE_ATTR) + // For a diff line the highlighting continues after the + // "$". + if ( +# ifdef FEAT_DIFF + wlv.diff_hlf == (hlf_T)0 +# ifdef LINE_ATTR + && +# endif +# endif +# ifdef LINE_ATTR + wlv.line_attr == 0 +# endif + ) +#endif + { + // In virtualedit, visual selections may extend + // beyond end of line. + if (!(area_highlighting && virtual_active() + && wlv.tocol != MAXCOL + && wlv.vcol < wlv.tocol)) + wlv.p_extra = at_end_str; + wlv.n_extra = 0; + } + if (wp->w_p_list && wp->w_lcs_chars.eol > 0) + c = wp->w_lcs_chars.eol; + else + c = ' '; + lcs_eol_one = -1; + --ptr; // put it back at the NUL + if (!attr_pri) + { + wlv.extra_attr = hl_combine_attr(wlv.win_attr, + HL_ATTR(HLF_AT)); + n_attr = 1; + } + mb_c = c; + if (enc_utf8 && utf_char2len(c) > 1) + { + mb_utf8 = TRUE; + u8cc[0] = 0; + c = 0xc0; + } + else + mb_utf8 = FALSE; // don't draw as UTF-8 + } + else if (c != NUL) + { + wlv.p_extra = transchar_buf(wp->w_buffer, c); + if (wlv.n_extra == 0) + wlv.n_extra = byte2cells(c) - 1; +#ifdef FEAT_RIGHTLEFT + if ((dy_flags & DY_UHEX) && wp->w_p_rl) + rl_mirror(wlv.p_extra); // reverse "<12>" +#endif + wlv.c_extra = NUL; + wlv.c_final = NUL; +#ifdef FEAT_LINEBREAK + if (wp->w_p_lbr) + { + char_u *p; + + c = *wlv.p_extra; + p = alloc(wlv.n_extra + 1); + vim_memset(p, ' ', wlv.n_extra); + STRNCPY(p, wlv.p_extra + 1, STRLEN(wlv.p_extra) - 1); + p[wlv.n_extra] = NUL; + vim_free(wlv.p_extra_free); + wlv.p_extra_free = wlv.p_extra = p; + } + else +#endif + { + wlv.n_extra = byte2cells(c) - 1; + c = *wlv.p_extra++; + } + if (!attr_pri) + { + n_attr = wlv.n_extra + 1; + wlv.extra_attr = hl_combine_attr(wlv.win_attr, + HL_ATTR(HLF_8)); +#ifdef FEAT_PROP_POPUP + if (text_prop_type != NULL && + text_prop_flags & PT_FLAG_OVERRIDE) + wlv.extra_attr = hl_combine_attr(text_prop_attr, wlv.extra_attr); +#endif + + saved_attr2 = wlv.char_attr; // save current attr + } + mb_utf8 = FALSE; // don't draw as UTF-8 + } + else if (VIsual_active + && (VIsual_mode == Ctrl_V + || VIsual_mode == 'v') + && virtual_active() + && wlv.tocol != MAXCOL + && wlv.vcol < wlv.tocol + && ( +#ifdef FEAT_RIGHTLEFT + wp->w_p_rl ? (wlv.col >= 0) : +#endif + (wlv.col < wp->w_width))) + { + c = ' '; + --ptr; // put it back at the NUL + } +#if defined(LINE_ATTR) + else if (( +# ifdef FEAT_DIFF + wlv.diff_hlf != (hlf_T)0 || +# endif +# ifdef FEAT_TERMINAL + wlv.win_attr != 0 || +# endif + wlv.line_attr != 0 + ) && ( +# ifdef FEAT_RIGHTLEFT + wp->w_p_rl ? (wlv.col >= 0) : +# endif + (wlv.col +# ifdef FEAT_CONCEAL + - wlv.boguscols +# endif + < wp->w_width))) + { + // Highlight until the right side of the window + c = ' '; + --ptr; // put it back at the NUL + + // Remember we do the char for line highlighting. + ++did_line_attr; + + // don't do search HL for the rest of the line + if (wlv.line_attr != 0 && wlv.char_attr == search_attr + && (did_line_attr > 1 + || (wp->w_p_list && + wp->w_lcs_chars.eol > 0))) + wlv.char_attr = wlv.line_attr; +#ifdef FEAT_SIGNS + // At end of line: if Sign is present with line highlight, reset char_attr + // but not when cursorline is active + if (sign_present && wlv.sattr.sat_linehl > 0 && wlv.draw_state == WL_LINE + && !(wp->w_p_cul && lnum == wp->w_cursor.lnum)) + wlv.char_attr = wlv.sattr.sat_linehl; +#endif +# ifdef FEAT_DIFF + if (wlv.diff_hlf == HLF_TXD) + { + wlv.diff_hlf = HLF_CHD; + if (vi_attr == 0 || wlv.char_attr != vi_attr) + { + wlv.char_attr = HL_ATTR(wlv.diff_hlf); + if (wp->w_p_cul && lnum == wp->w_cursor.lnum + && wp->w_p_culopt_flags != CULOPT_NBR + && (!wlv.cul_screenline + || (wlv.vcol >= left_curline_col + && wlv.vcol <= right_curline_col))) + wlv.char_attr = hl_combine_attr( + wlv.char_attr, HL_ATTR(HLF_CUL)); + } + } +# endif +# ifdef FEAT_TERMINAL + if (wlv.win_attr != 0) + { + wlv.char_attr = wlv.win_attr; + if (wp->w_p_cul && lnum == wp->w_cursor.lnum + && wp->w_p_culopt_flags != CULOPT_NBR) + { + if (!wlv.cul_screenline + || (wlv.vcol >= left_curline_col + && wlv.vcol <= right_curline_col)) + wlv.char_attr = hl_combine_attr( + wlv.char_attr, HL_ATTR(HLF_CUL)); + } + else if (wlv.line_attr) + wlv.char_attr = hl_combine_attr( + wlv.char_attr, wlv.line_attr); + } +# endif + } +#endif + } + +#ifdef FEAT_CONCEAL + if ( wp->w_p_cole > 0 + && (wp != curwin || lnum != wp->w_cursor.lnum + || conceal_cursor_line(wp)) + && ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0) + && !(lnum_in_visual_area + && vim_strchr(wp->w_p_cocu, 'v') == NULL)) + { + wlv.char_attr = conceal_attr; + if (((prev_syntax_id != syntax_seqnr + && (syntax_flags & HL_CONCEAL) != 0) + || has_match_conc > 1) + && (syn_get_sub_char() != NUL + || (has_match_conc && match_conc) + || wp->w_p_cole == 1) + && wp->w_p_cole != 3) + { + // First time at this concealed item: display one + // character. + if (has_match_conc && match_conc) + c = match_conc; + else if (syn_get_sub_char() != NUL) + c = syn_get_sub_char(); + else if (wp->w_lcs_chars.conceal != NUL) + c = wp->w_lcs_chars.conceal; + else + c = ' '; + + prev_syntax_id = syntax_seqnr; + + if (wlv.n_extra > 0) + wlv.vcol_off_co += wlv.n_extra; + wlv.vcol += wlv.n_extra; + if (wp->w_p_wrap && wlv.n_extra > 0) + { +# ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + { + wlv.col -= wlv.n_extra; + wlv.boguscols -= wlv.n_extra; + } + else +# endif + { + wlv.boguscols += wlv.n_extra; + wlv.col += wlv.n_extra; + } + } + wlv.n_extra = 0; + n_attr = 0; + } + else if (skip_cells == 0) + { + is_concealing = TRUE; + skip_cells = 1; + } + mb_c = c; + if (enc_utf8 && utf_char2len(c) > 1) + { + mb_utf8 = TRUE; + u8cc[0] = 0; + c = 0xc0; + } + else + mb_utf8 = FALSE; // don't draw as UTF-8 + } + else + { + prev_syntax_id = 0; + is_concealing = FALSE; + } + + if (skip_cells > 0 && did_decrement_ptr) + // not showing the '>', put pointer back to avoid getting stuck + ++ptr; + +#endif // FEAT_CONCEAL + } + +#ifdef FEAT_CONCEAL + // In the cursor line and we may be concealing characters: correct + // the cursor column when we reach its position. + if (!did_wcol && wlv.draw_state == WL_LINE + && wp == curwin && lnum == wp->w_cursor.lnum + && conceal_cursor_line(wp) + && (int)wp->w_virtcol <= wlv.vcol + skip_cells) + { +# ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + wp->w_wcol = wp->w_width - wlv.col + wlv.boguscols - 1; + else +# endif + wp->w_wcol = wlv.col - wlv.boguscols; + wp->w_wrow = wlv.row; + did_wcol = TRUE; + curwin->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL; +# ifdef FEAT_PROP_POPUP + curwin->w_flags &= ~(WFLAG_WCOL_OFF_ADDED | WFLAG_WROW_OFF_ADDED); +# endif + } +#endif + + // Use "wlv.extra_attr", but don't override visual selection + // highlighting, unless text property overrides. + // Don't use "wlv.extra_attr" until wlv.n_attr_skip is zero. + if (wlv.n_attr_skip == 0 && n_attr > 0 + && wlv.draw_state == WL_LINE + && (!attr_pri +#ifdef FEAT_PROP_POPUP + || (text_prop_flags & PT_FLAG_OVERRIDE) +#endif + )) + { +#ifdef LINE_ATTR + if (wlv.line_attr) + wlv.char_attr = hl_combine_attr(wlv.line_attr, wlv.extra_attr); + else +#endif + wlv.char_attr = wlv.extra_attr; +#ifdef FEAT_PROP_POPUP + if (reset_extra_attr) + { + reset_extra_attr = FALSE; + wlv.extra_attr = 0; + } +#endif + } + +#if defined(FEAT_XIM) && defined(FEAT_GUI_GTK) + // XIM don't send preedit_start and preedit_end, but they send + // preedit_changed and commit. Thus Vim can't set "im_is_active", use + // im_is_preediting() here. + if (p_imst == IM_ON_THE_SPOT + && xic != NULL + && lnum == wp->w_cursor.lnum + && (State & MODE_INSERT) + && !p_imdisable + && im_is_preediting() + && wlv.draw_state == WL_LINE) + { + colnr_T tcol; + + if (preedit_end_col == MAXCOL) + getvcol(curwin, &(wp->w_cursor), &tcol, NULL, NULL); + else + tcol = preedit_end_col; + if ((long)preedit_start_col <= wlv.vcol && wlv.vcol < (long)tcol) + { + if (feedback_old_attr < 0) + { + feedback_col = 0; + feedback_old_attr = wlv.char_attr; + } + wlv.char_attr = im_get_feedback_attr(feedback_col); + if (wlv.char_attr < 0) + wlv.char_attr = feedback_old_attr; + feedback_col++; + } + else if (feedback_old_attr >= 0) + { + wlv.char_attr = feedback_old_attr; + feedback_old_attr = -1; + feedback_col = 0; + } + } +#endif + // Handle the case where we are in column 0 but not on the first + // character of the line and the user wants us to show us a + // special character (via 'listchars' option "precedes:". + if (lcs_prec_todo != NUL + && wp->w_p_list + && (wp->w_p_wrap ? (wp->w_skipcol > 0 && wlv.row == 0) + : wp->w_leftcol > 0) +#ifdef FEAT_DIFF + && wlv.filler_todo <= 0 +#endif + && wlv.draw_state > WL_NR + && c != NUL) + { + c = wp->w_lcs_chars.prec; + lcs_prec_todo = NUL; + if (has_mbyte && (*mb_char2cells)(mb_c) > 1) + { + // Double-width character being overwritten by the "precedes" + // character, need to fill up half the character. + wlv.c_extra = MB_FILLER_CHAR; + wlv.c_final = NUL; + wlv.n_extra = 1; + n_attr = 2; + wlv.extra_attr = + hl_combine_attr(wlv.win_attr, HL_ATTR(HLF_AT)); + } + mb_c = c; + if (enc_utf8 && utf_char2len(c) > 1) + { + mb_utf8 = TRUE; + u8cc[0] = 0; + c = 0xc0; + } + else + mb_utf8 = FALSE; // don't draw as UTF-8 + if (!attr_pri) + { + saved_attr3 = wlv.char_attr; // save current attr + wlv.char_attr = hl_combine_attr(wlv.win_attr, HL_ATTR(HLF_AT)); + n_attr3 = 1; + } + } + + // At end of the text line or just after the last character. + if ((c == NUL +#if defined(LINE_ATTR) + || did_line_attr == 1 +#endif + ) && wlv.eol_hl_off == 0) + { +#ifdef FEAT_SEARCH_EXTRA + // flag to indicate whether prevcol equals startcol of search_hl or + // one of the matches + int prevcol_hl_flag = get_prevcol_hl_flag(wp, &screen_search_hl, + (long)(ptr - line) - (c == NUL)); +#endif + // 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!) Not + // needed when a '$' was displayed for 'list'. + if (wp->w_lcs_chars.eol == lcs_eol_one + && ((area_attr != 0 && wlv.vcol == wlv.fromcol + && (VIsual_mode != Ctrl_V + || lnum == VIsual.lnum + || lnum == curwin->w_cursor.lnum) + && c == NUL) +#ifdef FEAT_SEARCH_EXTRA + // highlight 'hlsearch' match at end of line + || (prevcol_hl_flag +# ifdef FEAT_SYN_HL + && !(wp->w_p_cul && lnum == wp->w_cursor.lnum + && !(wp == curwin && VIsual_active)) +# endif +# ifdef FEAT_DIFF + && wlv.diff_hlf == (hlf_T)0 +# endif +# if defined(LINE_ATTR) + && did_line_attr <= 1 +# endif + ) +#endif + )) + { + int n = 0; + +#ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + { + if (wlv.col < 0) + n = 1; + } + else +#endif + { + if (wlv.col >= wp->w_width) + n = -1; + } + if (n != 0) + { + // At the window boundary, highlight the last character + // instead (better than nothing). + wlv.off += n; + wlv.col += n; + } + else + { + // Add a blank character to highlight. + ScreenLines[wlv.off] = ' '; + if (enc_utf8) + ScreenLinesUC[wlv.off] = 0; + } +#ifdef FEAT_SEARCH_EXTRA + if (area_attr == 0) + { + // Use attributes from match with highest priority among + // 'search_hl' and the match list. + get_search_match_hl(wp, &screen_search_hl, + (long)(ptr - line), &wlv.char_attr); + } +#endif + ScreenAttrs[wlv.off] = wlv.char_attr; + ScreenCols[wlv.off] = MAXCOL; +#ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + { + --wlv.col; + --wlv.off; + } + else +#endif + { + ++wlv.col; + ++wlv.off; + } + ++wlv.vcol; + wlv.eol_hl_off = 1; + } + } + + // At end of the text line. + if (c == NUL) + { +#ifdef FEAT_PROP_POPUP + if (text_prop_follows) + { + // Put the pointer back to the NUL. + --ptr; + c = ' '; + } + else +#endif + { + draw_screen_line(wp, &wlv); + + // Update w_cline_height and w_cline_folded if the cursor line + // was updated (saves a call to plines() later). + if (wp == curwin && lnum == curwin->w_cursor.lnum) + { + curwin->w_cline_row = startrow; + curwin->w_cline_height = wlv.row - startrow; +#ifdef FEAT_FOLDING + curwin->w_cline_folded = FALSE; +#endif + curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW); + } + break; + } + } + + // Show "extends" character from 'listchars' if beyond the line end and + // 'list' is set. + if (wp->w_lcs_chars.ext != NUL + && wlv.draw_state == WL_LINE + && wp->w_p_list + && !wp->w_p_wrap +#ifdef FEAT_DIFF + && wlv.filler_todo <= 0 +#endif + && ( +#ifdef FEAT_RIGHTLEFT + wp->w_p_rl ? wlv.col == 0 : +#endif + wlv.col == wp->w_width - 1) + && (*ptr != NUL + || lcs_eol_one > 0 + || (wlv.n_extra > 0 && (wlv.c_extra != NUL + || *wlv.p_extra != NUL)) +#ifdef FEAT_PROP_POPUP + || text_prop_next <= last_textprop_text_idx +#endif + )) + { + c = wp->w_lcs_chars.ext; + wlv.char_attr = hl_combine_attr(wlv.win_attr, HL_ATTR(HLF_AT)); + mb_c = c; + if (enc_utf8 && utf_char2len(c) > 1) + { + mb_utf8 = TRUE; + u8cc[0] = 0; + c = 0xc0; + } + else + mb_utf8 = FALSE; + } + +#ifdef FEAT_SYN_HL + // advance to the next 'colorcolumn' + if (wlv.draw_color_col) + wlv.draw_color_col = advance_color_col(VCOL_HLC, &wlv.color_cols); + + // Highlight the cursor column if 'cursorcolumn' is set. But don't + // highlight the cursor position itself. + // Also highlight the 'colorcolumn' if it is different than + // 'cursorcolumn' + // Also highlight the 'colorcolumn' if 'breakindent' and/or 'showbreak' + // options are set + vcol_save_attr = -1; + if (((wlv.draw_state == WL_LINE + || wlv.draw_state == WL_BRI + || wlv.draw_state == WL_SBR) + && !lnum_in_visual_area + && search_attr == 0 + && area_attr == 0) +# ifdef FEAT_DIFF + && wlv.filler_todo <= 0 +# endif + ) + { + if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol + && lnum != wp->w_cursor.lnum) + { + vcol_save_attr = wlv.char_attr; + wlv.char_attr = hl_combine_attr(wlv.char_attr, + HL_ATTR(HLF_CUC)); + } + else if (wlv.draw_color_col && VCOL_HLC == *wlv.color_cols) + { + vcol_save_attr = wlv.char_attr; + wlv.char_attr = hl_combine_attr(wlv.char_attr, HL_ATTR(HLF_MC)); + } + } +#endif + + if (wlv.draw_state == WL_LINE) + vcol_prev = wlv.vcol; + + // Store character to be displayed. + // Skip characters that are left of the screen for 'nowrap'. + if (wlv.draw_state < WL_LINE || skip_cells <= 0) + { + // Store the character. +#if defined(FEAT_RIGHTLEFT) + if (has_mbyte && wp->w_p_rl && (*mb_char2cells)(mb_c) > 1) + { + // A double-wide character is: put first half in left cell. + --wlv.off; + --wlv.col; + } +#endif + ScreenLines[wlv.off] = c; + if (enc_dbcs == DBCS_JPNU) + { + if ((mb_c & 0xff00) == 0x8e00) + ScreenLines[wlv.off] = 0x8e; + ScreenLines2[wlv.off] = mb_c & 0xff; + } + else if (enc_utf8) + { + if (mb_utf8) + { + int i; + + ScreenLinesUC[wlv.off] = mb_c; + if ((c & 0xff) == 0) + ScreenLines[wlv.off] = 0x80; // avoid storing zero + for (i = 0; i < Screen_mco; ++i) + { + ScreenLinesC[i][wlv.off] = u8cc[i]; + if (u8cc[i] == 0) + break; + } + } + else + ScreenLinesUC[wlv.off] = 0; + } + if (multi_attr) + { + ScreenAttrs[wlv.off] = multi_attr; + multi_attr = 0; + } + else + ScreenAttrs[wlv.off] = wlv.char_attr; + + if (wlv.draw_state > WL_NR +#ifdef FEAT_DIFF + && wlv.filler_todo <= 0 +#endif + ) + ScreenCols[wlv.off] = wlv.vcol; + else + ScreenCols[wlv.off] = -1; + + if (has_mbyte && (*mb_char2cells)(mb_c) > 1) + { + // Need to fill two screen columns. + ++wlv.off; + ++wlv.col; + if (enc_utf8) + // UTF-8: Put a 0 in the second screen char. + ScreenLines[wlv.off] = 0; + else + // DBCS: Put second byte in the second screen char. + ScreenLines[wlv.off] = mb_c & 0xff; + + if (wlv.draw_state > WL_NR +#ifdef FEAT_DIFF + && wlv.filler_todo <= 0 +#endif + ) + ScreenCols[wlv.off] = ++wlv.vcol; + else + ScreenCols[wlv.off] = -1; + + // When "wlv.tocol" is halfway a character, set it to the end + // of the character, otherwise highlighting won't stop. + if (wlv.tocol == wlv.vcol) + ++wlv.tocol; + +#ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + { + // now it's time to backup one cell + --wlv.off; + --wlv.col; + } +#endif + } +#ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + { + --wlv.off; + --wlv.col; + } + else +#endif + { + ++wlv.off; + ++wlv.col; + } + } +#ifdef FEAT_CONCEAL + else if (wp->w_p_cole > 0 && is_concealing) + { + --skip_cells; + ++wlv.vcol_off_co; + if (wlv.n_extra > 0) + wlv.vcol_off_co += wlv.n_extra; + if (wp->w_p_wrap) + { + // Special voodoo required if 'wrap' is on. + // + // Advance the column indicator to force the line + // drawing to wrap early. This will make the line + // take up the same screen space when parts are concealed, + // so that cursor line computations aren't messed up. + // + // To avoid the fictitious advance of 'wlv.col' causing + // trailing junk to be written out of the screen line + // we are building, 'boguscols' keeps track of the number + // of bad columns we have advanced. + if (wlv.n_extra > 0) + { + wlv.vcol += wlv.n_extra; +# ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + { + wlv.col -= wlv.n_extra; + wlv.boguscols -= wlv.n_extra; + } + else +# endif + { + wlv.col += wlv.n_extra; + wlv.boguscols += wlv.n_extra; + } + wlv.n_extra = 0; + n_attr = 0; + } + + + if (has_mbyte && (*mb_char2cells)(mb_c) > 1) + { + // Need to fill two screen columns. +# ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + { + --wlv.boguscols; + --wlv.col; + } + else +# endif + { + ++wlv.boguscols; + ++wlv.col; + } + } + +# ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + { + --wlv.boguscols; + --wlv.col; + } + else +# endif + { + ++wlv.boguscols; + ++wlv.col; + } + } + else + { + if (wlv.n_extra > 0) + { + wlv.vcol += wlv.n_extra; + wlv.n_extra = 0; + n_attr = 0; + } + } + + } +#endif // FEAT_CONCEAL + else + --skip_cells; + + if (wlv.draw_state > WL_NR && skipped_cells > 0) + { + wlv.vcol += skipped_cells; + skipped_cells = 0; + } + + // Only advance the "wlv.vcol" when after the 'number' or + // 'relativenumber' column. + if (wlv.draw_state > WL_NR +#ifdef FEAT_DIFF + && wlv.filler_todo <= 0 +#endif + ) + ++wlv.vcol; + +#ifdef FEAT_SYN_HL + if (vcol_save_attr >= 0) + wlv.char_attr = vcol_save_attr; +#endif + + // restore attributes after "precedes" in 'listchars' + if (wlv.draw_state > WL_NR && n_attr3 > 0 && --n_attr3 == 0) + wlv.char_attr = saved_attr3; + + // restore attributes after last 'listchars' or 'number' char + if (n_attr > 0 && wlv.draw_state == WL_LINE + && wlv.n_attr_skip == 0 && --n_attr == 0) + wlv.char_attr = saved_attr2; + if (wlv.n_attr_skip > 0) + --wlv.n_attr_skip; + + // At end of screen line and there is more to come: Display the line + // so far. If there is no more to display it is caught above. + if (( +#ifdef FEAT_RIGHTLEFT + wp->w_p_rl ? (wlv.col < 0) : +#endif + (wlv.col >= wp->w_width)) + && (wlv.draw_state != WL_LINE + || *ptr != NUL +#ifdef FEAT_DIFF + || wlv.filler_todo > 0 +#endif +#ifdef FEAT_PROP_POPUP + || text_prop_above || text_prop_follows + || text_prop_next <= last_textprop_text_idx +#endif + || (wp->w_p_list && wp->w_lcs_chars.eol != NUL + && wlv.p_extra != at_end_str) + || (wlv.n_extra != 0 && (wlv.c_extra != NUL + || *wlv.p_extra != NUL))) + ) + { +#ifdef FEAT_CONCEAL + wlv.col -= wlv.boguscols; + wlv_screen_line(wp, &wlv, FALSE); + wlv.col += wlv.boguscols; + wlv.boguscols = 0; + wlv.vcol_off_co = 0; +#else + wlv_screen_line(wp, &wlv, FALSE); +#endif + ++wlv.row; + ++wlv.screen_row; + + // When not wrapping and finished diff lines, or when displayed + // '$' and highlighting until last column, break here. + if (((!wp->w_p_wrap +#ifdef FEAT_DIFF + && wlv.filler_todo <= 0 +#endif +#ifdef FEAT_PROP_POPUP + && !text_prop_above +#endif + ) || lcs_eol_one == -1) +#ifdef FEAT_PROP_POPUP + && !text_prop_follows +#endif + ) + break; +#ifdef FEAT_PROP_POPUP + if (!wp->w_p_wrap && text_prop_follows && !text_prop_above) + { + // do not output more of the line, only the "below" prop + ptr += STRLEN(ptr); +# ifdef FEAT_LINEBREAK + wlv.dont_use_showbreak = TRUE; +# endif + } +#endif + + // When the window is too narrow draw all "@" lines. + if (wlv.draw_state != WL_LINE +#ifdef FEAT_DIFF + && wlv.filler_todo <= 0 +#endif + ) + { + win_draw_end(wp, '@', ' ', TRUE, wlv.row, wp->w_height, HLF_AT); + draw_vsep_win(wp, wlv.row); + wlv.row = endrow; + } + + // When line got too long for screen break here. + if (wlv.row == endrow) + { + ++wlv.row; + break; + } + + if (screen_cur_row == wlv.screen_row - 1 +#ifdef FEAT_DIFF + && wlv.filler_todo <= 0 +#endif +#ifdef FEAT_PROP_POPUP + && !text_prop_above && !text_prop_follows +#endif + && wp->w_width == Columns) + { + // Remember that the line wraps, used for modeless copy. + LineWraps[wlv.screen_row - 1] = TRUE; + + // Special trick to make copy/paste of wrapped lines work with + // xterm/screen: write an extra character beyond the end of + // the line. This will work with all terminal types + // (regardless of the xn,am settings). + // Only do this on a fast tty. + // Only do this if the cursor is on the current line + // (something has been written in it). + // Don't do this for the GUI. + // Don't do this for double-width characters. + // Don't do this for a window not at the right screen border. + if (p_tf +#ifdef FEAT_GUI + && !gui.in_use +#endif + && !(has_mbyte + && ((*mb_off2cells)(LineOffset[wlv.screen_row], + LineOffset[wlv.screen_row] + screen_Columns) + == 2 + || (*mb_off2cells)( + LineOffset[wlv.screen_row - 1] + + (int)Columns - 2, + LineOffset[wlv.screen_row] + + screen_Columns) == 2))) + { + // First make sure we are at the end of the screen line, + // then output the same character again to let the + // terminal know about the wrap. If the terminal doesn't + // auto-wrap, we overwrite the character. + if (screen_cur_col != wp->w_width) + screen_char(LineOffset[wlv.screen_row - 1] + + (unsigned)Columns - 1, + wlv.screen_row - 1, (int)(Columns - 1)); + + // When there is a multi-byte character, just output a + // space to keep it simple. + if (has_mbyte && MB_BYTE2LEN(ScreenLines[LineOffset[ + wlv.screen_row - 1] + (Columns - 1)]) > 1) + out_char(' '); + else + out_char(ScreenLines[LineOffset[wlv.screen_row - 1] + + (Columns - 1)]); + // force a redraw of the first char on the next line + ScreenAttrs[LineOffset[wlv.screen_row]] = (sattr_T)-1; + screen_start(); // don't know where cursor is now + } + } + + win_line_start(wp, &wlv, TRUE); + + lcs_prec_todo = wp->w_lcs_chars.prec; +#ifdef FEAT_LINEBREAK + if (!wlv.dont_use_showbreak +# ifdef FEAT_DIFF + && wlv.filler_todo <= 0 +# endif + ) + wlv.need_showbreak = TRUE; +#endif +#ifdef FEAT_DIFF + --wlv.filler_todo; + // When the filler lines are actually below the last line of the + // file, don't draw the line itself, break here. + if (wlv.filler_todo == 0 && wp->w_botfill) + break; +#endif + } + + } // for every character in the line +#ifdef FEAT_PROP_POPUP + vim_free(text_props); + vim_free(text_prop_idxs); + vim_free(p_extra_free2); +#endif + + vim_free(wlv.p_extra_free); + vim_free(wlv.saved_p_extra_free); + return wlv.row; +} diff --git a/userland/apps/vim/src/drawscreen.c b/userland/apps/vim/src/drawscreen.c new file mode 100644 index 00000000..a2f60a64 --- /dev/null +++ b/userland/apps/vim/src/drawscreen.c @@ -0,0 +1,3376 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * drawscreen.c: Code for updating all the windows on the screen. + * This is the top level, drawline.c is the middle and screen.c the lower + * level. + * + * update_screen() is the function that updates all windows and status lines. + * It is called form the main loop when must_redraw is non-zero. It may be + * called from other places when an immediate screen update is needed. + * + * The part of the buffer that is displayed in a window is set with: + * - w_topline (first buffer line in window) + * - w_topfill (filler lines above the first line) + * - w_leftcol (leftmost window cell in window), + * - w_skipcol (skipped window cells of first line) + * + * Commands that only move the cursor around in a window, do not need to take + * action to update the display. The main loop will check if w_topline is + * valid and update it (scroll the window) when needed. + * + * Commands that scroll a window change w_topline and must call + * check_cursor() to move the cursor into the visible part of the window, and + * call redraw_later(UPD_VALID) to have the window displayed by update_screen() + * later. + * + * Commands that change text in the buffer must call changed_bytes() or + * changed_lines() to mark the area that changed and will require updating + * later. The main loop will call update_screen(), which will update each + * window that shows the changed buffer. This assumes text above the change + * can remain displayed as it is. Text after the change may need updating for + * scrolling, folding and syntax highlighting. + * + * Commands that change how a window is displayed (e.g., setting 'list') or + * invalidate the contents of a window in another way (e.g., change fold + * settings), must call redraw_later(UPD_NOT_VALID) to have the whole window + * redisplayed by update_screen() later. + * + * Commands that change how a buffer is displayed (e.g., setting 'tabstop') + * must call redraw_curbuf_later(UPD_NOT_VALID) to have all the windows for the + * buffer redisplayed by update_screen() later. + * + * Commands that change highlighting and possibly cause a scroll too must call + * redraw_later(UPD_SOME_VALID) to update the whole window but still use + * scrolling to avoid redrawing everything. But the length of displayed lines + * must not change, use UPD_NOT_VALID then. + * + * Commands that move the window position must call redraw_later(UPD_NOT_VALID). + * TODO: should minimize redrawing by scrolling when possible. + * + * Commands that change everything (e.g., resizing the screen) must call + * redraw_all_later(UPD_NOT_VALID) or redraw_all_later(UPD_CLEAR). + * + * Things that are handled indirectly: + * - When messages scroll the screen up, msg_scrolled will be set and + * update_screen() called to redraw. + */ + +#include "vim.h" + +static void win_update(win_T *wp); +#ifdef FEAT_STL_OPT +static void redraw_custom_statusline(win_T *wp); +#endif +#if defined(FEAT_SEARCH_EXTRA) || defined(FEAT_CLIPBOARD) +static int did_update_one_window; +#endif + +/* + * Based on the current value of curwin->w_topline, transfer a screenfull + * of stuff from Filemem to ScreenLines[], and update curwin->w_botline. + * Return OK when the screen was updated, FAIL if it was not done. + */ + int +update_screen(int type_arg) +{ + int type = type_arg; + win_T *wp; + static int did_intro = FALSE; +#ifdef FEAT_GUI + int did_one = FALSE; + int did_undraw = FALSE; + int gui_cursor_col = 0; + int gui_cursor_row = 0; +#endif + int no_update = FALSE; + int save_pum_will_redraw = pum_will_redraw; + + // Don't do anything if the screen structures are (not yet) valid. + if (!screen_valid(TRUE)) + return FAIL; + + if (type == UPD_VALID_NO_UPDATE) + { + no_update = TRUE; + type = 0; + } + +#ifdef FEAT_EVAL + { + buf_T *buf; + + // Before updating the screen, notify any listeners of changed text. + FOR_ALL_BUFFERS(buf) + invoke_listeners(buf); + } +#endif + +#ifdef FEAT_DIFF + // May have postponed updating diffs. + if (need_diff_redraw) + diff_redraw(TRUE); +#endif + + if (must_redraw) + { + if (type < must_redraw) // use maximal type + type = must_redraw; + + // must_redraw is reset here, so that when we run into some weird + // reason to redraw while busy redrawing (e.g., asynchronous + // scrolling), or update_topline() in win_update() will cause a + // scroll, the screen will be redrawn later or in win_update(). + must_redraw = 0; + } + + // May need to update w_lines[]. + if (curwin->w_lines_valid == 0 && type < UPD_NOT_VALID +#ifdef FEAT_TERMINAL + && !term_do_update_window(curwin) +#endif + ) + type = UPD_NOT_VALID; + + // Postpone the redrawing when it's not needed and when being called + // recursively. + if (!redrawing() || updating_screen) + { + redraw_later(type); // remember type for next time + must_redraw = type; + if (type > UPD_INVERTED_ALL) + curwin->w_lines_valid = 0; // don't use w_lines[].wl_size now + return FAIL; + } + updating_screen = TRUE; + +#ifdef FEAT_PROP_POPUP + // Update popup_mask if needed. This may set w_redraw_top and w_redraw_bot + // in some windows. + may_update_popup_mask(type); +#endif + +#ifdef FEAT_SYN_HL + ++display_tick; // let syntax code know we're in a next round of + // display updating +#endif + if (no_update) + ++no_win_do_lines_ins; + + // if the screen was scrolled up when displaying a message, scroll it down + if (msg_scrolled) + { + clear_cmdline = TRUE; + if (type != UPD_CLEAR) + { + if (msg_scrolled > Rows - 5) // redrawing is faster + { + type = UPD_NOT_VALID; + redraw_as_cleared(); + } + else + { + check_for_delay(FALSE); + if (screen_ins_lines(0, 0, msg_scrolled, (int)Rows, 0, NULL) + == FAIL) + { + type = UPD_NOT_VALID; + redraw_as_cleared(); + } + FOR_ALL_WINDOWS(wp) + { + if (wp->w_winrow < msg_scrolled) + { + if (W_WINROW(wp) + wp->w_height > msg_scrolled + && wp->w_redr_type < UPD_REDRAW_TOP + && wp->w_lines_valid > 0 + && wp->w_topline == wp->w_lines[0].wl_lnum) + { + wp->w_upd_rows = msg_scrolled - W_WINROW(wp); + wp->w_redr_type = UPD_REDRAW_TOP; + } + else + { + wp->w_redr_type = UPD_NOT_VALID; + if (W_WINROW(wp) + wp->w_height + + wp->w_status_height <= msg_scrolled) + wp->w_redr_status = TRUE; + } + } + } + if (!no_update) + redraw_cmdline = TRUE; + redraw_tabline = TRUE; + } + } + msg_scrolled = 0; + need_wait_return = FALSE; + } + + // reset cmdline_row now (may have been changed temporarily) + compute_cmdrow(); + + // Check for changed highlighting + if (need_highlight_changed) + highlight_changed(); + + if (type == UPD_CLEAR) // first clear screen + { + screenclear(); // will reset clear_cmdline + type = UPD_NOT_VALID; + // must_redraw may be set indirectly, avoid another redraw later + must_redraw = 0; + } + + if (clear_cmdline) // going to clear cmdline (done below) + check_for_delay(FALSE); + +#ifdef FEAT_LINEBREAK + // Force redraw when width of 'number' or 'relativenumber' column + // changes. + if (curwin->w_redr_type < UPD_NOT_VALID + && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu) + ? number_width(curwin) : 0)) + curwin->w_redr_type = UPD_NOT_VALID; +#endif + + // Only start redrawing if there is really something to do. + if (type == UPD_INVERTED) + update_curswant(); + if (curwin->w_redr_type < type + && !((type == UPD_VALID + && curwin->w_lines[0].wl_valid +#ifdef FEAT_DIFF + && curwin->w_topfill == curwin->w_old_topfill + && curwin->w_botfill == curwin->w_old_botfill +#endif + && curwin->w_topline == curwin->w_lines[0].wl_lnum) + || (type == UPD_INVERTED + && VIsual_active + && curwin->w_old_cursor_lnum == curwin->w_cursor.lnum + && curwin->w_old_visual_mode == VIsual_mode + && (curwin->w_valid & VALID_VIRTCOL) + && curwin->w_old_curswant == curwin->w_curswant) + )) + curwin->w_redr_type = type; + + // Redraw the tab pages line if needed. + if (redraw_tabline || type >= UPD_NOT_VALID) + draw_tabline(); + +#ifdef FEAT_SYN_HL + // Correct stored syntax highlighting info for changes in each displayed + // buffer. Each buffer must only be done once. + FOR_ALL_WINDOWS(wp) + { + if (wp->w_buffer->b_mod_set) + { + win_T *wwp; + + // Check if we already did this buffer. + for (wwp = firstwin; wwp != wp; wwp = wwp->w_next) + if (wwp->w_buffer == wp->w_buffer) + break; + if (wwp == wp && syntax_present(wp)) + syn_stack_apply_changes(wp->w_buffer); + } + } +#endif + + if (pum_redraw_in_same_position()) + // Avoid flicker if the popup menu is going to be redrawn in the same + // position. + pum_will_redraw = TRUE; + + // Go from top to bottom through the windows, redrawing the ones that need + // it. +#if defined(FEAT_SEARCH_EXTRA) || defined(FEAT_CLIPBOARD) + did_update_one_window = FALSE; +#endif +#ifdef FEAT_SEARCH_EXTRA + screen_search_hl.rm.regprog = NULL; +#endif + FOR_ALL_WINDOWS(wp) + { + if (wp->w_redr_type != 0) + { + cursor_off(); +#ifdef FEAT_GUI + if (!did_one) + { + did_one = TRUE; + + // Remove the cursor before starting to do anything, because + // scrolling may make it difficult to redraw the text under + // it. + // Also remove the cursor if it needs to be hidden due to an + // ongoing cursor-less sleep. + if (gui.in_use && (wp == curwin || cursor_is_sleeping())) + { + gui_cursor_col = gui.cursor_col; + gui_cursor_row = gui.cursor_row; + gui_undraw_cursor(); + did_undraw = TRUE; + } + } +#endif + win_update(wp); + } + + // redraw status line after the window to minimize cursor movement + if (wp->w_redr_status) + { + cursor_off(); + win_redr_status(wp, TRUE); // any popup menu will be redrawn below + } + } +#if defined(FEAT_SEARCH_EXTRA) + end_search_hl(); +#endif + + // May need to redraw the popup menu. + pum_will_redraw = save_pum_will_redraw; + pum_may_redraw(); + + // Reset b_mod_set flags. Going through all windows is probably faster + // than going through all buffers (there could be many buffers). + FOR_ALL_WINDOWS(wp) + wp->w_buffer->b_mod_set = FALSE; + +#ifdef FEAT_PROP_POPUP + // Display popup windows on top of the windows and command line. + update_popups(win_update); +#endif + +#ifdef FEAT_TERMINAL + FOR_ALL_WINDOWS(wp) + // If this window contains a terminal, after redrawing all windows, the + // dirty row range can be reset. + term_did_update_window(wp); +#endif + + after_updating_screen(TRUE); + + // Clear or redraw the command line. Done last, because scrolling may + // mess up the command line. + if (clear_cmdline || redraw_cmdline || redraw_mode) + showmode(); + + if (no_update) + --no_win_do_lines_ins; + + // May put up an introductory message when not editing a file + if (!did_intro) + maybe_intro_message(); + did_intro = TRUE; + +#ifdef FEAT_GUI + // Redraw the cursor and update the scrollbars when all screen updating is + // done. + if (gui.in_use) + { + if (did_undraw && !gui_mch_is_blink_off()) + { + mch_disable_flush(); + out_flush(); // required before updating the cursor + mch_enable_flush(); + + // Put the GUI position where the cursor was, gui_update_cursor() + // uses that. + gui.col = gui_cursor_col; + gui.row = gui_cursor_row; + gui.col = mb_fix_col(gui.col, gui.row); + gui_update_cursor(FALSE, FALSE); + gui_may_flush(); + screen_cur_col = gui.col; + screen_cur_row = gui.row; + } + else + out_flush(); + gui_update_scrollbars(FALSE); + } +#endif + return OK; +} + +/* + * Return the row for drawing the statusline and the ruler of window "wp". + */ + int +statusline_row(win_T *wp) +{ +#if defined(FEAT_PROP_POPUP) + // If the window is really zero height the winbar isn't displayed. + if (wp->w_frame->fr_height == wp->w_status_height && !popup_is_popup(wp)) + return wp->w_winrow; +#endif + return W_WINROW(wp) + wp->w_height; +} + +/* + * Redraw the status line of window wp. + * + * If inversion is possible we use it. Else '=' characters are used. + * If "ignore_pum" is TRUE, also redraw statusline when the popup menu is + * displayed. + */ + void +win_redr_status(win_T *wp, int ignore_pum UNUSED) +{ + int row; + char_u *p; + int len; + int fillchar; + int attr; + int this_ru_col; + static int busy = FALSE; + + // It's possible to get here recursively when 'statusline' (indirectly) + // invokes ":redrawstatus". Simply ignore the call then. + if (busy) + return; + busy = TRUE; + + row = statusline_row(wp); + + wp->w_redr_status = FALSE; + if (wp->w_status_height == 0) + { + // no status line, can only be last window + redraw_cmdline = TRUE; + } + else if (!redrawing() + // don't update status line when popup menu is visible and may be + // drawn over it, unless it will be redrawn later + || (!ignore_pum && pum_visible())) + { + // Don't redraw right now, do it later. + wp->w_redr_status = TRUE; + } +#ifdef FEAT_STL_OPT + else if (*p_stl != NUL || *wp->w_p_stl != NUL) + { + // redraw custom status line + redraw_custom_statusline(wp); + } +#endif + else + { + fillchar = fillchar_status(&attr, wp); + + get_trans_bufname(wp->w_buffer); + p = NameBuff; + len = (int)STRLEN(p); + + if ((bt_help(wp->w_buffer) +#ifdef FEAT_QUICKFIX + || wp->w_p_pvw +#endif + || bufIsChanged(wp->w_buffer) + || wp->w_buffer->b_p_ro) + && len < MAXPATHL - 1) + *(p + len++) = ' '; + if (bt_help(wp->w_buffer)) + { + vim_snprintf((char *)p + len, MAXPATHL - len, "%s", _("[Help]")); + len += (int)STRLEN(p + len); + } +#ifdef FEAT_QUICKFIX + if (wp->w_p_pvw) + { + vim_snprintf((char *)p + len, MAXPATHL - len, "%s", _("[Preview]")); + len += (int)STRLEN(p + len); + } +#endif + if (bufIsChanged(wp->w_buffer) && !bt_terminal(wp->w_buffer)) + { + vim_snprintf((char *)p + len, MAXPATHL - len, "%s", "[+]"); + len += (int)STRLEN(p + len); + } + if (wp->w_buffer->b_p_ro) + { + vim_snprintf((char *)p + len, MAXPATHL - len, "%s", _("[RO]")); + len += (int)STRLEN(p + len); + } + + this_ru_col = ru_col - (Columns - wp->w_width); + if (this_ru_col < (wp->w_width + 1) / 2) + this_ru_col = (wp->w_width + 1) / 2; + if (this_ru_col <= 1) + { + p = (char_u *)"<"; // No room for file name! + len = 1; + } + else if (has_mbyte) + { + int clen = 0, i; + + // Count total number of display cells. + clen = mb_string2cells(p, -1); + + // Find first character that will fit. + // Going from start to end is much faster for DBCS. + for (i = 0; p[i] != NUL && clen >= this_ru_col - 1; + i += (*mb_ptr2len)(p + i)) + clen -= (*mb_ptr2cells)(p + i); + len = clen; + if (i > 0) + { + p = p + i - 1; + *p = '<'; + ++len; + } + + } + else if (len > this_ru_col - 1) + { + p += len - (this_ru_col - 1); + *p = '<'; + len = this_ru_col - 1; + } + + screen_puts(p, row, wp->w_wincol, attr); + screen_fill(row, row + 1, len + wp->w_wincol, + this_ru_col + wp->w_wincol, fillchar, fillchar, attr); + + if (get_keymap_str(wp, (char_u *)"<%s>", NameBuff, MAXPATHL) + && (this_ru_col - len) > (int)(STRLEN(NameBuff) + 1)) + screen_puts(NameBuff, row, (int)(this_ru_col - STRLEN(NameBuff) + - 1 + wp->w_wincol), attr); + + win_redr_ruler(wp, TRUE, ignore_pum); + + // Draw the 'showcmd' information if 'showcmdloc' == "statusline". + if (p_sc && *p_sloc == 's') + { + int width = MIN(10, this_ru_col - len - 2); + + if (width > 0) + screen_puts_len(showcmd_buf, width, row, + wp->w_wincol + this_ru_col - width - 1, attr); + } + } + + /* + * May need to draw the character below the vertical separator. + */ + if (wp->w_vsep_width != 0 && wp->w_status_height != 0 && redrawing()) + { + if (stl_connected(wp)) + fillchar = fillchar_status(&attr, wp); + else + fillchar = fillchar_vsep(&attr, wp); + screen_putchar(fillchar, row, W_ENDCOL(wp), attr); + } + busy = FALSE; +} + +#ifdef FEAT_STL_OPT +/* + * Redraw the status line according to 'statusline' and take care of any + * errors encountered. + */ + static void +redraw_custom_statusline(win_T *wp) +{ + static int entered = FALSE; + + // When called recursively return. This can happen when the statusline + // contains an expression that triggers a redraw. + if (entered) + return; + entered = TRUE; + + win_redr_custom(wp, FALSE); + entered = FALSE; +} +#endif + +/* + * Show current status info in ruler and various other places + * If always is FALSE, only show ruler if position has changed. + */ + void +showruler(int always) +{ + if (!always && !redrawing()) + return; + if (pum_visible()) + { + // Don't redraw right now, do it later. + curwin->w_redr_status = TRUE; + return; + } +#if defined(FEAT_STL_OPT) + if ((*p_stl != NUL || *curwin->w_p_stl != NUL) && curwin->w_status_height) + redraw_custom_statusline(curwin); + else +#endif + win_redr_ruler(curwin, always, FALSE); + + if (need_maketitle +#ifdef FEAT_STL_OPT + || (p_icon && (stl_syntax & STL_IN_ICON)) + || (p_title && (stl_syntax & STL_IN_TITLE)) +#endif + ) + maketitle(); + + // Redraw the tab pages line if needed. + if (redraw_tabline) + draw_tabline(); +} + + void +win_redr_ruler(win_T *wp, int always, int ignore_pum) +{ +#define RULER_BUF_LEN 70 + char_u buffer[RULER_BUF_LEN]; + int row; + int fillchar; + int attr; + int empty_line = FALSE; + colnr_T virtcol; + int i; + size_t len; + int o; + int this_ru_col; + int off = 0; + int width; + + // If 'ruler' off don't do anything + if (!p_ru) + return; + + /* + * 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) + return; + + // Don't draw the ruler while doing insert-completion, it might overwrite + // the (long) mode message. + if (wp == lastwin && lastwin->w_status_height == 0) + if (edit_submode != NULL) + return; + // Don't draw the ruler when the popup menu is visible, it may overlap. + // Except when the popup menu will be redrawn anyway. + if (!ignore_pum && pum_visible()) + return; + +#ifdef FEAT_STL_OPT + if (*p_ruf) + { + win_redr_custom(wp, TRUE); + return; + } +#endif + + /* + * Check if not in Insert mode and the line is empty (will show "0-1"). + */ + if ((State & MODE_INSERT) == 0 + && *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, FALSE) == NUL) + empty_line = TRUE; + + /* + * Only draw the ruler when something changed. + */ + validate_virtcol_win(wp); + if ( redraw_cmdline + || always + || wp->w_cursor.lnum != wp->w_ru_cursor.lnum + || wp->w_cursor.col != wp->w_ru_cursor.col + || wp->w_virtcol != wp->w_ru_virtcol + || wp->w_cursor.coladd != wp->w_ru_cursor.coladd + || wp->w_topline != wp->w_ru_topline + || wp->w_buffer->b_ml.ml_line_count != wp->w_ru_line_count +#ifdef FEAT_DIFF + || wp->w_topfill != wp->w_ru_topfill +#endif + || empty_line != wp->w_ru_empty) + { + cursor_off(); + if (wp->w_status_height) + { + row = statusline_row(wp); + fillchar = fillchar_status(&attr, wp); + off = wp->w_wincol; + width = wp->w_width; + } + else + { + row = Rows - 1; + fillchar = ' '; + attr = 0; + width = Columns; + off = 0; + } + + // In list mode virtcol needs to be recomputed + virtcol = wp->w_virtcol; + if (wp->w_p_list && wp->w_lcs_chars.tab1 == NUL) + { + wp->w_p_list = FALSE; + getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); + wp->w_p_list = TRUE; + } + + /* + * Some sprintfs return the length, some return a pointer. + * To avoid portability problems we use strlen() here. + */ + vim_snprintf((char *)buffer, RULER_BUF_LEN, "%ld,", + (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) + ? 0L + : (long)(wp->w_cursor.lnum)); + len = STRLEN(buffer); + col_print(buffer + len, RULER_BUF_LEN - len, + empty_line ? 0 : (int)wp->w_cursor.col + 1, + (int)virtcol + 1); + + /* + * Add a "50%" if there is room for it. + * On the last line, don't print in the last column (scrolls the + * screen up on some terminals). + */ + i = (int)STRLEN(buffer); + get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1); + o = i + vim_strsize(buffer + i + 1); + if (wp->w_status_height == 0) // can't use last char of screen + ++o; + this_ru_col = ru_col - (Columns - width); + if (this_ru_col < 0) + this_ru_col = 0; + // Never use more than half the window/screen width, leave the other + // half for the filename. + if (this_ru_col < (width + 1) / 2) + this_ru_col = (width + 1) / 2; + if (this_ru_col + o < width) + { + // need at least 3 chars left for get_rel_pos() + NUL + while (this_ru_col + o < width && RULER_BUF_LEN > i + 4) + { + if (has_mbyte) + i += (*mb_char2bytes)(fillchar, buffer + i); + else + buffer[i++] = fillchar; + ++o; + } + get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i); + } + // Truncate at window boundary. + if (has_mbyte) + { + o = 0; + for (i = 0; buffer[i] != NUL; i += (*mb_ptr2len)(buffer + i)) + { + o += (*mb_ptr2cells)(buffer + i); + if (this_ru_col + o > width) + { + buffer[i] = NUL; + break; + } + } + } + else if (this_ru_col + (int)STRLEN(buffer) > width) + buffer[width - this_ru_col] = NUL; + + screen_puts(buffer, row, this_ru_col + off, attr); + i = redraw_cmdline; + screen_fill(row, row + 1, + this_ru_col + off + (int)STRLEN(buffer), + (off + width), + fillchar, fillchar, attr); + // don't redraw the cmdline because of showing the ruler + redraw_cmdline = i; + wp->w_ru_cursor = wp->w_cursor; + wp->w_ru_virtcol = wp->w_virtcol; + wp->w_ru_empty = empty_line; + wp->w_ru_topline = wp->w_topline; + wp->w_ru_line_count = wp->w_buffer->b_ml.ml_line_count; +#ifdef FEAT_DIFF + wp->w_ru_topfill = wp->w_topfill; +#endif + } +} + +/* + * To be called when "updating_screen" was set before and now the postponed + * side effects may take place. + */ + void +after_updating_screen(int may_resize_shell UNUSED) +{ + updating_screen = FALSE; +#ifdef FEAT_GUI + if (may_resize_shell) + gui_may_resize_shell(); +#endif +#ifdef FEAT_TERMINAL + term_check_channel_closed_recently(); +#endif + +#ifdef HAVE_DROP_FILE + // If handle_drop() was called while updating_screen was TRUE need to + // handle the drop now. + handle_any_postponed_drop(); +#endif +} + +/* + * Update all windows that are editing the current buffer. + */ + void +update_curbuf(int type) +{ + redraw_curbuf_later(type); + update_screen(type); +} + +#if defined(FEAT_MENU) || defined(FEAT_FOLDING) +/* + * Copy "text" to ScreenLines using "attr". + * Returns the next screen column. + */ + static int +text_to_screenline(win_T *wp, char_u *text, int col) +{ + int off = (int)(current_ScreenLine - ScreenLines); + + if (has_mbyte) + { + int cells; + int u8c, u8cc[MAX_MCO]; + int i; + int idx; + int c_len; + char_u *p; +# ifdef FEAT_ARABIC + int prev_c = 0; // previous Arabic character + int prev_c1 = 0; // first composing char for prev_c +# endif + +# ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + idx = off; + else +# endif + idx = off + col; + + // Store multibyte characters in ScreenLines[] et al. correctly. + for (p = text; *p != NUL; ) + { + cells = (*mb_ptr2cells)(p); + c_len = (*mb_ptr2len)(p); + if (col + cells > wp->w_width +# ifdef FEAT_RIGHTLEFT + - (wp->w_p_rl ? col : 0) +# endif + ) + break; + ScreenLines[idx] = *p; + if (enc_utf8) + { + u8c = utfc_ptr2char(p, u8cc); + if (*p < 0x80 && u8cc[0] == 0) + { + ScreenLinesUC[idx] = 0; +#ifdef FEAT_ARABIC + prev_c = u8c; +#endif + } + else + { +#ifdef FEAT_ARABIC + if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) + { + // Do Arabic shaping. + int pc, pc1, nc; + int pcc[MAX_MCO]; + int firstbyte = *p; + + // The idea of what is the previous and next + // character depends on 'rightleft'. + if (wp->w_p_rl) + { + pc = prev_c; + pc1 = prev_c1; + nc = utf_ptr2char(p + c_len); + prev_c1 = u8cc[0]; + } + else + { + pc = utfc_ptr2char(p + c_len, pcc); + nc = prev_c; + pc1 = pcc[0]; + } + prev_c = u8c; + + u8c = arabic_shape(u8c, &firstbyte, &u8cc[0], + pc, pc1, nc); + ScreenLines[idx] = firstbyte; + } + else + prev_c = u8c; +#endif + // Non-BMP character: display as ? or fullwidth ?. + ScreenLinesUC[idx] = u8c; + for (i = 0; i < Screen_mco; ++i) + { + ScreenLinesC[i][idx] = u8cc[i]; + if (u8cc[i] == 0) + break; + } + } + if (cells > 1) + ScreenLines[idx + 1] = 0; + } + else if (enc_dbcs == DBCS_JPNU && *p == 0x8e) + // double-byte single width character + ScreenLines2[idx] = p[1]; + else if (cells > 1) + // double-width character + ScreenLines[idx + 1] = p[1]; + col += cells; + idx += cells; + p += c_len; + } + } + else + { + int len = (int)STRLEN(text); + + if (len > wp->w_width - col) + len = wp->w_width - col; + if (len > 0) + { +#ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + mch_memmove(current_ScreenLine, text, len); + else +#endif + mch_memmove(current_ScreenLine + col, text, len); + col += len; + } + } + return col; +} +#endif + +#ifdef FEAT_MENU +/* + * Draw the window toolbar. + */ + static void +redraw_win_toolbar(win_T *wp) +{ + vimmenu_T *menu; + int item_idx = 0; + int item_count = 0; + int col = 0; + int next_col; + int off = (int)(current_ScreenLine - ScreenLines); + int fill_attr = syn_name2attr((char_u *)"ToolbarLine"); + int button_attr = syn_name2attr((char_u *)"ToolbarButton"); + + vim_free(wp->w_winbar_items); + FOR_ALL_CHILD_MENUS(wp->w_winbar, menu) + ++item_count; + wp->w_winbar_items = ALLOC_CLEAR_MULT(winbar_item_T, item_count + 1); + + // TODO: use fewer spaces if there is not enough room + for (menu = wp->w_winbar->children; + menu != NULL && col < wp->w_width; menu = menu->next) + { + space_to_screenline(off + col, fill_attr); + if (++col >= wp->w_width) + break; + if (col > 1) + { + space_to_screenline(off + col, fill_attr); + if (++col >= wp->w_width) + break; + } + + wp->w_winbar_items[item_idx].wb_startcol = col; + space_to_screenline(off + col, button_attr); + if (++col >= wp->w_width) + break; + + next_col = text_to_screenline(wp, menu->name, col); + while (col < next_col) + { + ScreenAttrs[off + col] = button_attr; + ++col; + } + wp->w_winbar_items[item_idx].wb_endcol = col; + wp->w_winbar_items[item_idx].wb_menu = menu; + ++item_idx; + + if (col >= wp->w_width) + break; + space_to_screenline(off + col, button_attr); + ++col; + } + while (col < wp->w_width) + { + space_to_screenline(off + col, fill_attr); + ++col; + } + wp->w_winbar_items[item_idx].wb_menu = NULL; // end marker + + screen_line(wp, wp->w_winrow, wp->w_wincol, wp->w_width, wp->w_width, 0); +} +#endif + +#if defined(FEAT_FOLDING) || defined(PROTO) +/* + * Copy "buf[len]" to ScreenLines["off"] and set attributes to "attr". + */ + static void +copy_text_attr( + int off, + char_u *buf, + int len, + int attr) +{ + int i; + + mch_memmove(ScreenLines + off, buf, (size_t)len); + if (enc_utf8) + vim_memset(ScreenLinesUC + off, 0, sizeof(u8char_T) * (size_t)len); + for (i = 0; i < len; ++i) + ScreenAttrs[off + i] = attr; +} + +/* + * Display one folded line. + */ + static void +fold_line( + win_T *wp, + long fold_count, + foldinfo_T *foldinfo, + linenr_T lnum, + int row) +{ + // Max value of 'foldcolumn' is 12 and maximum number of bytes in a + // multi-byte character is MAX_MCO. + char_u buf[MAX_MCO * 12 + 1]; + pos_T *top, *bot; + linenr_T lnume = lnum + fold_count - 1; + int len; + char_u *text; + int fdc; + int col; + int txtcol; + int off = (int)(current_ScreenLine - ScreenLines); + int ri; + + // Build the fold line: + // 1. Add the cmdwin_type for the command-line window + // 2. Add the 'foldcolumn' + // 3. Add the 'number' or 'relativenumber' column + // 4. Compose the text + // 5. Add the text + // 6. set highlighting for the Visual area an other text + col = 0; + + // 1. Add the cmdwin_type for the command-line window + // Ignores 'rightleft', this window is never right-left. + if (cmdwin_type != 0 && wp == curwin) + { + ScreenLines[off] = cmdwin_type; + ScreenAttrs[off] = HL_ATTR(HLF_AT); + if (enc_utf8) + ScreenLinesUC[off] = 0; + ++col; + } + +#ifdef FEAT_RIGHTLEFT +# define RL_MEMSET(p, v, l) \ + do { \ + if (wp->w_p_rl) \ + for (ri = 0; ri < (l); ++ri) \ + ScreenAttrs[off + (wp->w_width - (p) - (l)) + ri] = v; \ + else \ + for (ri = 0; ri < (l); ++ri) \ + ScreenAttrs[off + (p) + ri] = v; \ + } while (0) +#else +# define RL_MEMSET(p, v, l) \ + do { \ + for (ri = 0; ri < l; ++ri) \ + ScreenAttrs[off + (p) + ri] = v; \ + } while (0) +#endif + + // 2. Add the 'foldcolumn' + // Reduce the width when there is not enough space. + fdc = compute_foldcolumn(wp, col); + if (fdc > 0) + { + char_u *p; + int i; + int idx; + + fill_foldcolumn(buf, wp, TRUE, lnum); + p = buf; + for (i = 0; i < fdc; i++) + { + int ch; + + if (has_mbyte) + ch = mb_ptr2char_adv(&p); + else + ch = *p++; +#ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + idx = off + wp->w_width - i - 1 - col; + else +#endif + idx = off + col + i; + if (enc_utf8) + { + if (ch >= 0x80) + { + ScreenLinesUC[idx] = ch; + ScreenLinesC[0][idx] = 0; + ScreenLines[idx] = 0x80; + } + else + { + ScreenLines[idx] = ch; + ScreenLinesUC[idx] = 0; + } + } + else + ScreenLines[idx] = ch; + } + + RL_MEMSET(col, HL_ATTR(HLF_FC), fdc); + col += fdc; + } + + // Set all attributes of the 'number' or 'relativenumber' column and the + // text + RL_MEMSET(col, HL_ATTR(HLF_FL), wp->w_width - col); + +#ifdef FEAT_SIGNS + // If signs are being displayed, add two spaces. + if (signcolumn_on(wp)) + { + len = wp->w_width - col; + if (len > 0) + { + if (len > 2) + len = 2; +# ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + // the line number isn't reversed + copy_text_attr(off + wp->w_width - len - col, + (char_u *)" ", len, HL_ATTR(HLF_FL)); + else +# endif + copy_text_attr(off + col, (char_u *)" ", len, HL_ATTR(HLF_FL)); + col += len; + } + } +#endif + + // 3. Add the 'number' or 'relativenumber' column + if (wp->w_p_nu || wp->w_p_rnu) + { + len = wp->w_width - col; + if (len > 0) + { + int w = number_width(wp); + long num; + char *fmt = "%*ld "; + + if (len > w + 1) + len = w + 1; + + if (wp->w_p_nu && !wp->w_p_rnu) + // 'number' + 'norelativenumber' + num = (long)lnum; + else + { + // 'relativenumber', don't use negative numbers + num = labs((long)get_cursor_rel_lnum(wp, lnum)); + if (num == 0 && wp->w_p_nu && wp->w_p_rnu) + { + // 'number' + 'relativenumber': cursor line shows absolute + // line number + num = lnum; + fmt = "%-*ld "; + } + } + + sprintf((char *)buf, fmt, w, num); +#ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + // the line number isn't reversed + copy_text_attr(off + wp->w_width - len - col, buf, len, + HL_ATTR(HLF_FL)); + else +#endif + copy_text_attr(off + col, buf, len, HL_ATTR(HLF_FL)); + col += len; + } + } + + // 4. Compose the folded-line string with 'foldtext', if set. + text = get_foldtext(wp, lnum, lnume, foldinfo, buf); + + txtcol = col; // remember where text starts + + // 5. move the text to current_ScreenLine. Fill up with "fold" from + // 'fillchars'. + // Right-left text is put in columns 0 - number-col, normal text is put + // in columns number-col - window-width. + col = text_to_screenline(wp, text, col); + + // Fill the rest of the line with the fold filler +#ifdef FEAT_RIGHTLEFT + if (wp->w_p_rl) + col -= txtcol; +#endif + while (col < wp->w_width +#ifdef FEAT_RIGHTLEFT + - (wp->w_p_rl ? txtcol : 0) +#endif + ) + { + int c = wp->w_fill_chars.fold; + + if (enc_utf8) + { + if (c >= 0x80) + { + ScreenLinesUC[off + col] = c; + ScreenLinesC[0][off + col] = 0; + ScreenLines[off + col] = 0x80; // avoid storing zero + } + else + { + ScreenLinesUC[off + col] = 0; + ScreenLines[off + col] = c; + } + col++; + } + else + ScreenLines[off + col++] = c; + } + + if (text != buf) + vim_free(text); + + // 6. set highlighting for the Visual area an other text. + // If all folded lines are in the Visual area, highlight the line. + if (VIsual_active && wp->w_buffer == curwin->w_buffer) + { + if (LTOREQ_POS(curwin->w_cursor, VIsual)) + { + // Visual is after curwin->w_cursor + top = &curwin->w_cursor; + bot = &VIsual; + } + else + { + // Visual is before curwin->w_cursor + top = &VIsual; + bot = &curwin->w_cursor; + } + if (lnum >= top->lnum + && lnume <= bot->lnum + && (VIsual_mode != 'v' + || ((lnum > top->lnum + || (lnum == top->lnum + && top->col == 0)) + && (lnume < bot->lnum + || (lnume == bot->lnum + && (bot->col - (*p_sel == 'e')) + >= (colnr_T)STRLEN(ml_get_buf(wp->w_buffer, lnume, FALSE))))))) + { + if (VIsual_mode == Ctrl_V) + { + // Visual block mode: highlight the chars part of the block + if (wp->w_old_cursor_fcol + txtcol < (colnr_T)wp->w_width) + { + if (wp->w_old_cursor_lcol != MAXCOL + && wp->w_old_cursor_lcol + txtcol + < (colnr_T)wp->w_width) + len = wp->w_old_cursor_lcol; + else + len = wp->w_width - txtcol; + RL_MEMSET(wp->w_old_cursor_fcol + txtcol, HL_ATTR(HLF_V), + len - (int)wp->w_old_cursor_fcol); + } + } + else + { + // Set all attributes of the text + RL_MEMSET(txtcol, HL_ATTR(HLF_V), wp->w_width - txtcol); + } + } + } + +#ifdef FEAT_SYN_HL + // Show colorcolumn in the fold line, but let cursorcolumn override it. + if (wp->w_p_cc_cols) + { + int i = 0; + int j = wp->w_p_cc_cols[i]; + int old_txtcol = txtcol; + + while (j > -1) + { + txtcol += j; + if (wp->w_p_wrap) + txtcol -= wp->w_skipcol; + else + txtcol -= wp->w_leftcol; + if (txtcol >= 0 && txtcol < wp->w_width) + ScreenAttrs[off + txtcol] = hl_combine_attr( + ScreenAttrs[off + txtcol], HL_ATTR(HLF_MC)); + txtcol = old_txtcol; + j = wp->w_p_cc_cols[++i]; + } + } + + // Show 'cursorcolumn' in the fold line. + if (wp->w_p_cuc) + { + txtcol += wp->w_virtcol; + if (wp->w_p_wrap) + txtcol -= wp->w_skipcol; + else + txtcol -= wp->w_leftcol; + if (txtcol >= 0 && txtcol < wp->w_width) + ScreenAttrs[off + txtcol] = hl_combine_attr( + ScreenAttrs[off + txtcol], HL_ATTR(HLF_CUC)); + } +#endif + + screen_line(wp, row + W_WINROW(wp), wp->w_wincol, + wp->w_width, wp->w_width, 0); + + // Update w_cline_height and w_cline_folded if the cursor line was + // updated (saves a call to plines() later). + if (wp == curwin + && lnum <= curwin->w_cursor.lnum + && lnume >= curwin->w_cursor.lnum) + { + curwin->w_cline_row = row; + curwin->w_cline_height = 1; + curwin->w_cline_folded = TRUE; + curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW); + } + +# ifdef FEAT_CONCEAL + // When the line was not folded w_wrow may have been set, recompute it. + if (wp == curwin + && wp->w_cursor.lnum >= lnum + && wp->w_cursor.lnum <= lnume + && conceal_cursor_line(wp)) + curs_columns(TRUE); +# endif +} +#endif + +/* + * Update a single window. + * + * This may cause the windows below it also to be redrawn (when clearing the + * screen or scrolling lines). + * + * How the window is redrawn depends on wp->w_redr_type. Each type also + * implies the one below it. + * UPD_NOT_VALID redraw the whole window + * UPD_SOME_VALID redraw the whole window but do scroll when possible + * UPD_REDRAW_TOP redraw the top w_upd_rows window lines, otherwise like + * UPD_VALID + * UPD_INVERTED redraw the changed part of the Visual area + * UPD_INVERTED_ALL redraw the whole Visual area + * UPD_VALID 1. scroll up/down to adjust for a changed w_topline + * 2. update lines at the top when scrolled down + * 3. redraw changed text: + * - if wp->w_buffer->b_mod_set set, update lines between + * b_mod_top and b_mod_bot. + * - if wp->w_redraw_top non-zero, redraw lines between + * wp->w_redraw_top and wp->w_redr_bot. + * - continue redrawing when syntax status is invalid. + * 4. if scrolled up, update lines at the bottom. + * This results in three areas that may need updating: + * top: from first row to top_end (when scrolled down) + * mid: from mid_start to mid_end (update inversion or changed text) + * bot: from bot_start to last row (when scrolled up) + */ + static void +win_update(win_T *wp) +{ + buf_T *buf = wp->w_buffer; + int type; + int top_end = 0; // Below last row of the top area that needs + // updating. 0 when no top area updating. + int mid_start = 999;// first row of the mid area that needs + // updating. 999 when no mid area updating. + int mid_end = 0; // Below last row of the mid area that needs + // updating. 0 when no mid area updating. + int bot_start = 999;// first row of the bot area that needs + // updating. 999 when no bot area updating + int scrolled_down = FALSE; // TRUE when scrolled down when + // w_topline got smaller a bit +#ifdef FEAT_SEARCH_EXTRA + int top_to_mod = FALSE; // redraw above mod_top +#endif + + int row; // current window row to display + linenr_T lnum; // current buffer lnum to display + int idx; // current index in w_lines[] + int srow; // starting row of the current line + + int eof = FALSE; // if TRUE, we hit the end of the file + int didline = FALSE; // if TRUE, we finished the last line + int i; + long j; + static int recursive = FALSE; // being called recursively + linenr_T old_botline = wp->w_botline; +#ifdef FEAT_CONCEAL + int old_wrow = wp->w_wrow; + int old_wcol = wp->w_wcol; +#endif +#ifdef FEAT_FOLDING + long fold_count; +#endif +#ifdef FEAT_SYN_HL + // remember what happened to the previous line, to know if + // check_visual_highlight() can be used +# define DID_NONE 1 // didn't update a line +# define DID_LINE 2 // updated a normal line +# define DID_FOLD 3 // updated a folded line + int did_update = DID_NONE; + linenr_T syntax_last_parsed = 0; // last parsed text line +#endif + linenr_T mod_top = 0; + linenr_T mod_bot = 0; +#if defined(FEAT_SYN_HL) || defined(FEAT_SEARCH_EXTRA) + int save_got_int; +#endif + +#if defined(FEAT_SEARCH_EXTRA) || defined(FEAT_CLIPBOARD) + // This needs to be done only for the first window when update_screen() is + // called. + if (!did_update_one_window) + { + did_update_one_window = TRUE; +# ifdef FEAT_SEARCH_EXTRA + start_search_hl(); +# endif +# ifdef FEAT_CLIPBOARD + // When Visual area changed, may have to update selection. + if (clip_star.available && clip_isautosel_star()) + clip_update_selection(&clip_star); + if (clip_plus.available && clip_isautosel_plus()) + clip_update_selection(&clip_plus); +# endif + } +#endif + + type = wp->w_redr_type; + + if (type == UPD_NOT_VALID) + { + wp->w_redr_status = TRUE; + wp->w_lines_valid = 0; + } + + // Window frame is zero-height: nothing to draw. + if (wp->w_height + WINBAR_HEIGHT(wp) == 0 + || (wp->w_frame->fr_height == wp->w_status_height +#if defined(FEAT_PROP_POPUP) + && !popup_is_popup(wp) +#endif + )) + { + wp->w_redr_type = 0; + return; + } + + // Window is zero-width: Only need to draw the separator. + if (wp->w_width == 0) + { + // draw the vertical separator right of this window + draw_vsep_win(wp, 0); + wp->w_redr_type = 0; + return; + } + +#ifdef FEAT_TERMINAL + // If this window contains a terminal, redraw works completely differently. + if (term_do_update_window(wp)) + { + term_update_window(wp); +# ifdef FEAT_MENU + // Draw the window toolbar, if there is one. + if (winbar_height(wp) > 0) + redraw_win_toolbar(wp); +# endif + wp->w_redr_type = 0; + return; + } +#endif + +#ifdef FEAT_SEARCH_EXTRA + init_search_hl(wp, &screen_search_hl); +#endif + + // Make sure skipcol is valid, it depends on various options and the window + // width. + if (wp->w_skipcol > 0) + { + int w = 0; + int width1 = wp->w_width - win_col_off(wp); + int width2 = width1 + win_col_off2(wp); + int add = width1; + + while (w < wp->w_skipcol) + { + if (w > 0) + add = width2; + w += add; + } + if (w != wp->w_skipcol) + // always round down, the higher value may not be valid + wp->w_skipcol = w - add; + } + +#ifdef FEAT_LINEBREAK + // Force redraw when width of 'number' or 'relativenumber' column + // changes. + i = (wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) : 0; + if (wp->w_nrwidth != i) + { + type = UPD_NOT_VALID; + wp->w_nrwidth = i; + } + else +#endif + + if (buf->b_mod_set && buf->b_mod_xlines != 0 && wp->w_redraw_top != 0) + { + // When there are both inserted/deleted lines and specific lines to be + // redrawn, w_redraw_top and w_redraw_bot may be invalid, just redraw + // everything (only happens when redrawing is off for while). + type = UPD_NOT_VALID; + } + else + { + // Set mod_top to the first line that needs displaying because of + // changes. Set mod_bot to the first line after the changes. + mod_top = wp->w_redraw_top; + if (wp->w_redraw_bot != 0) + mod_bot = wp->w_redraw_bot + 1; + else + mod_bot = 0; + if (buf->b_mod_set) + { + if (mod_top == 0 || mod_top > buf->b_mod_top) + { + mod_top = buf->b_mod_top; +#ifdef FEAT_SYN_HL + // Need to redraw lines above the change that may be included + // in a pattern match. + if (syntax_present(wp)) + { + mod_top -= buf->b_s.b_syn_sync_linebreaks; + if (mod_top < 1) + mod_top = 1; + } +#endif + } + if (mod_bot == 0 || mod_bot < buf->b_mod_bot) + mod_bot = buf->b_mod_bot; + +#ifdef FEAT_SEARCH_EXTRA + // When 'hlsearch' is on and using a multi-line search pattern, a + // change in one line may make the Search highlighting in a + // previous line invalid. Simple solution: redraw all visible + // lines above the change. + // Same for a match pattern. + if (screen_search_hl.rm.regprog != NULL + && re_multiline(screen_search_hl.rm.regprog)) + top_to_mod = TRUE; + else + { + matchitem_T *cur = wp->w_match_head; + + while (cur != NULL) + { + if (cur->mit_match.regprog != NULL + && re_multiline(cur->mit_match.regprog)) + { + top_to_mod = TRUE; + break; + } + cur = cur->mit_next; + } + } +#endif + } + +#ifdef FEAT_SEARCH_EXTRA + if (search_hl_has_cursor_lnum > 0) + { + // CurSearch was used last time, need to redraw the line with it to + // avoid having two matches highlighted with CurSearch. + if (mod_top == 0 || mod_top > search_hl_has_cursor_lnum) + mod_top = search_hl_has_cursor_lnum; + if (mod_bot == 0 || mod_bot < search_hl_has_cursor_lnum + 1) + mod_bot = search_hl_has_cursor_lnum + 1; + } +#endif + +#ifdef FEAT_FOLDING + if (mod_top != 0 && hasAnyFolding(wp)) + { + linenr_T lnumt, lnumb; + + // A change in a line can cause lines above it to become folded or + // unfolded. Find the top most buffer line that may be affected. + // If the line was previously folded and displayed, get the first + // line of that fold. If the line is folded now, get the first + // folded line. Use the minimum of these two. + + // Find last valid w_lines[] entry above mod_top. Set lnumt to + // the line below it. If there is no valid entry, use w_topline. + // Find the first valid w_lines[] entry below mod_bot. Set lnumb + // to this line. If there is no valid entry, use MAXLNUM. + lnumt = wp->w_topline; + lnumb = MAXLNUM; + for (i = 0; i < wp->w_lines_valid; ++i) + if (wp->w_lines[i].wl_valid) + { + if (wp->w_lines[i].wl_lastlnum < mod_top) + lnumt = wp->w_lines[i].wl_lastlnum + 1; + if (lnumb == MAXLNUM && wp->w_lines[i].wl_lnum >= mod_bot) + { + lnumb = wp->w_lines[i].wl_lnum; + // When there is a fold column it might need updating + // in the next line ("J" just above an open fold). + if (compute_foldcolumn(wp, 0) > 0) + ++lnumb; + } + } + + (void)hasFoldingWin(wp, mod_top, &mod_top, NULL, TRUE, NULL); + if (mod_top > lnumt) + mod_top = lnumt; + + // Now do the same for the bottom line (one above mod_bot). + --mod_bot; + (void)hasFoldingWin(wp, mod_bot, NULL, &mod_bot, TRUE, NULL); + ++mod_bot; + if (mod_bot < lnumb) + mod_bot = lnumb; + } +#endif + + // When a change starts above w_topline and the end is below + // w_topline, start redrawing at w_topline. + // If the end of the change is above w_topline: do like no change was + // made, but redraw the first line to find changes in syntax. + if (mod_top != 0 && mod_top < wp->w_topline) + { + if (mod_bot > wp->w_topline) + mod_top = wp->w_topline; +#ifdef FEAT_SYN_HL + else if (syntax_present(wp)) + top_end = 1; +#endif + } + + // When line numbers are displayed need to redraw all lines below + // inserted/deleted lines. + if (mod_top != 0 && buf->b_mod_xlines != 0 && wp->w_p_nu) + mod_bot = MAXLNUM; + } + wp->w_redraw_top = 0; // reset for next time + wp->w_redraw_bot = 0; +#ifdef FEAT_SEARCH_EXTRA + search_hl_has_cursor_lnum = 0; +#endif + + + // When only displaying the lines at the top, set top_end. Used when + // window has scrolled down for msg_scrolled. + if (type == UPD_REDRAW_TOP) + { + j = 0; + for (i = 0; i < wp->w_lines_valid; ++i) + { + j += wp->w_lines[i].wl_size; + if (j >= wp->w_upd_rows) + { + top_end = j; + break; + } + } + if (top_end == 0) + // not found (cannot happen?): redraw everything + type = UPD_NOT_VALID; + else + // top area defined, the rest is UPD_VALID + type = UPD_VALID; + } + + // Trick: we want to avoid clearing the screen twice. screenclear() will + // set "screen_cleared" to TRUE. The special value MAYBE (which is still + // non-zero and thus not FALSE) will indicate that screenclear() was not + // called. + if (screen_cleared) + screen_cleared = MAYBE; + + // If there are no changes on the screen that require a complete redraw, + // handle three cases: + // 1: we are off the top of the screen by a few lines: scroll down + // 2: wp->w_topline is below wp->w_lines[0].wl_lnum: may scroll up + // 3: wp->w_topline is wp->w_lines[0].wl_lnum: find first entry in + // w_lines[] that needs updating. + if ((type == UPD_VALID || type == UPD_SOME_VALID + || type == UPD_INVERTED || type == UPD_INVERTED_ALL) +#ifdef FEAT_DIFF + && !wp->w_botfill && !wp->w_old_botfill +#endif + ) + { + if (mod_top != 0 + && wp->w_topline == mod_top + && (!wp->w_lines[0].wl_valid + || wp->w_topline == wp->w_lines[0].wl_lnum)) + { + // w_topline is the first changed line and window is not scrolled, + // the scrolling from changed lines will be done further down. + } + else if (wp->w_lines[0].wl_valid + && (wp->w_topline < wp->w_lines[0].wl_lnum +#ifdef FEAT_DIFF + || (wp->w_topline == wp->w_lines[0].wl_lnum + && wp->w_topfill > wp->w_old_topfill) +#endif + )) + { + // New topline is above old topline: May scroll down. +#ifdef FEAT_FOLDING + if (hasAnyFolding(wp)) + { + linenr_T ln; + + // count the number of lines we are off, counting a sequence + // of folded lines as one + j = 0; + for (ln = wp->w_topline; ln < wp->w_lines[0].wl_lnum; ++ln) + { + ++j; + if (j >= wp->w_height - 2) + break; + (void)hasFoldingWin(wp, ln, NULL, &ln, TRUE, NULL); + } + } + else +#endif + j = wp->w_lines[0].wl_lnum - wp->w_topline; + if (j < wp->w_height - 2) // not too far off + { + i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1, + TRUE); +#ifdef FEAT_DIFF + // insert extra lines for previously invisible filler lines + if (wp->w_lines[0].wl_lnum != wp->w_topline) + i += diff_check_fill(wp, wp->w_lines[0].wl_lnum) + - wp->w_old_topfill; +#endif + 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 when the terminal can't do it. + if (i > 0) + check_for_delay(FALSE); + if (win_ins_lines(wp, 0, i, FALSE, wp == firstwin) == OK) + { + if (wp->w_lines_valid != 0) + { + // Need to update rows that are new, stop at the + // first one that scrolled down. + top_end = i; + scrolled_down = TRUE; + + // Move the entries that were scrolled, disable + // the entries for the lines to be redrawn. + if ((wp->w_lines_valid += j) > wp->w_height) + wp->w_lines_valid = wp->w_height; + for (idx = wp->w_lines_valid; idx - j >= 0; idx--) + wp->w_lines[idx] = wp->w_lines[idx - j]; + while (idx >= 0) + wp->w_lines[idx--].wl_valid = FALSE; + } + } + else + mid_start = 0; // redraw all lines + } + else + mid_start = 0; // redraw all lines + } + else + mid_start = 0; // redraw all lines + } + else + { + // New topline is at or below old topline: May scroll up. + // When topline didn't change, find first entry in w_lines[] that + // needs updating. + + // Try to find wp->w_topline in wp->w_lines[].wl_lnum. The check + // for "Rows" is in case "wl_size" is incorrect somehow. + j = -1; + row = 0; + for (i = 0; i < wp->w_lines_valid && i < Rows; i++) + { + if (wp->w_lines[i].wl_valid + && wp->w_lines[i].wl_lnum == wp->w_topline) + { + j = i; + break; + } + row += wp->w_lines[i].wl_size; + } + if (j == -1) + { + // if wp->w_topline is not in wp->w_lines[].wl_lnum redraw all + // lines + mid_start = 0; + } + else + { + // Try to delete the correct number of lines. + // wp->w_topline is at wp->w_lines[i].wl_lnum. +#ifdef FEAT_DIFF + // If the topline didn't change, delete old filler lines, + // otherwise delete filler lines of the new topline... + if (wp->w_lines[0].wl_lnum == wp->w_topline) + row += wp->w_old_topfill; + else + row += diff_check_fill(wp, wp->w_topline); + // ... but don't delete new filler lines. + row -= wp->w_topfill; +#endif + if (row > Rows) // just in case + row = Rows; + if (row > 0) + { + check_for_delay(FALSE); + if (win_del_lines(wp, 0, row, FALSE, wp == firstwin, 0) + == OK) + bot_start = wp->w_height - row; + else + mid_start = 0; // redraw all lines + } + if ((row == 0 || bot_start < 999) && wp->w_lines_valid != 0) + { + // Skip the lines (below the deleted lines) that are still + // valid and don't need redrawing. Copy their info + // upwards, to compensate for the deleted lines. Set + // bot_start to the first row that needs redrawing. + bot_start = 0; + idx = 0; + for (;;) + { + wp->w_lines[idx] = wp->w_lines[j]; + // stop at line that didn't fit, unless it is still + // valid (no lines deleted) + if (row > 0 && bot_start + row + + (int)wp->w_lines[j].wl_size > wp->w_height) + { + wp->w_lines_valid = idx + 1; + break; + } + bot_start += wp->w_lines[idx++].wl_size; + + // stop at the last valid entry in w_lines[].wl_size + if (++j >= wp->w_lines_valid) + { + wp->w_lines_valid = idx; + break; + } + } +#ifdef FEAT_DIFF + // Correct the first entry for filler lines at the top + // when it won't get updated below. + if (wp->w_p_diff && bot_start > 0) + wp->w_lines[0].wl_size = + plines_win_nofill(wp, wp->w_topline, TRUE) + + wp->w_topfill; +#endif + } + } + } + + // When starting redraw in the first line, redraw all lines. + if (mid_start == 0) + mid_end = wp->w_height; + + // When win_del_lines() or win_ins_lines() caused the screen to be + // cleared (only happens for the first window) or when screenclear() + // was called directly above, "must_redraw" will have been set to + // UPD_NOT_VALID, need to reset it here to avoid redrawing twice. + if (screen_cleared == TRUE) + must_redraw = 0; + } + else + { + // Not UPD_VALID or UPD_INVERTED: redraw all lines. + mid_start = 0; + mid_end = wp->w_height; + } + + if (type == UPD_SOME_VALID) + { + // UPD_SOME_VALID: redraw all lines. + mid_start = 0; + mid_end = wp->w_height; + type = UPD_NOT_VALID; + } + + // check if we are updating or removing the inverted part + if ((VIsual_active && buf == curwin->w_buffer) + || (wp->w_old_cursor_lnum != 0 && type != UPD_NOT_VALID)) + { + linenr_T from, to; + + if (VIsual_active) + { + if (VIsual_mode != wp->w_old_visual_mode + || type == UPD_INVERTED_ALL) + { + // If the type of Visual selection changed, redraw the whole + // selection. Also when the ownership of the X selection is + // gained or lost. + if (curwin->w_cursor.lnum < VIsual.lnum) + { + from = curwin->w_cursor.lnum; + to = VIsual.lnum; + } + else + { + from = VIsual.lnum; + to = curwin->w_cursor.lnum; + } + // redraw more when the cursor moved as well + if (wp->w_old_cursor_lnum < from) + from = wp->w_old_cursor_lnum; + if (wp->w_old_cursor_lnum > to) + to = wp->w_old_cursor_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; + } + else + { + // Find the line numbers that need to be updated: The lines + // between the old cursor position and the current cursor + // position. Also check if the Visual position changed. + 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 (from == 0) // Visual mode just started + from = to; + } + + if (VIsual.lnum != wp->w_old_visual_lnum + || VIsual.col != wp->w_old_visual_col) + { + if (wp->w_old_visual_lnum < from + && wp->w_old_visual_lnum != 0) + 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 curwin->w_curswant: + // update all lines. + // First compute the actual start and end column. + if (VIsual_mode == Ctrl_V) + { + colnr_T fromc, toc; +#if defined(FEAT_LINEBREAK) + int save_ve_flags = curwin->w_ve_flags; + + if (curwin->w_p_lbr) + curwin->w_ve_flags = VE_ALL; +#endif + getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc); + ++toc; +#if defined(FEAT_LINEBREAK) + curwin->w_ve_flags = save_ve_flags; +#endif + // Highlight to the end of the line, unless 'virtualedit' has + // "block". + if (curwin->w_curswant == MAXCOL) + { + if (get_ve_flags() & VE_BLOCK) + { + pos_T pos; + int cursor_above = + curwin->w_cursor.lnum < VIsual.lnum; + + // Need to find the longest line. + toc = 0; + pos.coladd = 0; + for (pos.lnum = curwin->w_cursor.lnum; cursor_above + ? pos.lnum <= VIsual.lnum + : pos.lnum >= VIsual.lnum; + pos.lnum += cursor_above ? 1 : -1) + { + colnr_T t; + + pos.col = (int)STRLEN(ml_get_buf(wp->w_buffer, + pos.lnum, FALSE)); + getvvcol(wp, &pos, NULL, NULL, &t); + if (toc < t) + toc = t; + } + ++toc; + } + else + toc = MAXCOL; + } + + if (fromc != wp->w_old_cursor_fcol + || toc != wp->w_old_cursor_lcol) + { + if (from > VIsual.lnum) + from = VIsual.lnum; + if (to < VIsual.lnum) + to = VIsual.lnum; + } + wp->w_old_cursor_fcol = fromc; + wp->w_old_cursor_lcol = toc; + } + } + else + { + // Use the line numbers of the old Visual area. + if (wp->w_old_cursor_lnum < wp->w_old_visual_lnum) + { + from = wp->w_old_cursor_lnum; + to = wp->w_old_visual_lnum; + } + else + { + from = wp->w_old_visual_lnum; + to = wp->w_old_cursor_lnum; + } + } + + // There is no need to update lines above the top of the window. + if (from < wp->w_topline) + from = wp->w_topline; + + // If we know the value of w_botline, use it to restrict the update to + // the lines that are visible in the window. + if (wp->w_valid & VALID_BOTLINE) + { + 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. + // Watch out for scrolling that made entries in w_lines[] invalid. + // E.g., CTRL-U makes the first half of w_lines[] invalid and sets + // top_end; need to redraw from top_end to the "to" line. + // A middle mouse click with a Visual selection may change the text + // above the Visual area and reset wl_valid, do count these for + // mid_end (in srow). + if (mid_start > 0) + { + lnum = wp->w_topline; + idx = 0; + srow = 0; + if (scrolled_down) + mid_start = top_end; + else + mid_start = 0; + while (lnum < from && idx < wp->w_lines_valid) // find start + { + if (wp->w_lines[idx].wl_valid) + mid_start += wp->w_lines[idx].wl_size; + else if (!scrolled_down) + srow += wp->w_lines[idx].wl_size; + ++idx; +# ifdef FEAT_FOLDING + if (idx < wp->w_lines_valid && wp->w_lines[idx].wl_valid) + lnum = wp->w_lines[idx].wl_lnum; + else +# endif + ++lnum; + } + srow += mid_start; + mid_end = wp->w_height; + for ( ; idx < wp->w_lines_valid; ++idx) // find end + { + if (wp->w_lines[idx].wl_valid + && wp->w_lines[idx].wl_lnum >= to + 1) + { + // Only update until first row of this line + mid_end = srow; + break; + } + srow += wp->w_lines[idx].wl_size; + } + } + } + + if (VIsual_active && buf == curwin->w_buffer) + { + wp->w_old_visual_mode = VIsual_mode; + wp->w_old_cursor_lnum = curwin->w_cursor.lnum; + wp->w_old_visual_lnum = VIsual.lnum; + wp->w_old_visual_col = VIsual.col; + wp->w_old_curswant = curwin->w_curswant; + } + else + { + wp->w_old_visual_mode = 0; + wp->w_old_cursor_lnum = 0; + wp->w_old_visual_lnum = 0; + wp->w_old_visual_col = 0; + } + +#if defined(FEAT_SYN_HL) || defined(FEAT_SEARCH_EXTRA) + // reset got_int, otherwise regexp won't work + save_got_int = got_int; + got_int = 0; +#endif +#ifdef SYN_TIME_LIMIT + // Set the time limit to 'redrawtime'. + redrawtime_limit_set = TRUE; + init_regexp_timeout(p_rdt); +#endif +#ifdef FEAT_FOLDING + win_foldinfo.fi_level = 0; +#endif + +#ifdef FEAT_MENU + // Draw the window toolbar, if there is one. + // TODO: only when needed. + if (winbar_height(wp) > 0) + redraw_win_toolbar(wp); +#endif + + lnum = wp->w_topline; // first line shown in window + + spellvars_T spv; +#ifdef FEAT_SPELL + // Initialize spell related variables for the first drawn line. + CLEAR_FIELD(spv); + if (spell_check_window(wp)) + { + spv.spv_has_spell = TRUE; + spv.spv_unchanged = mod_top == 0; + } +#endif + + // Update all the window rows. + idx = 0; // first entry in w_lines[].wl_size + row = 0; + srow = 0; + for (;;) + { + // stop updating when reached the end of the window (check for _past_ + // the end of the window is at the end of the loop) + if (row == wp->w_height) + { + didline = TRUE; + break; + } + + // stop updating when hit the end of the file + if (lnum > buf->b_ml.ml_line_count) + { + eof = TRUE; + break; + } + + // Remember the starting row of the line that is going to be dealt + // with. It is used further down when the line doesn't fit. + srow = row; + + // Update a line when it is in an area that needs updating, when it + // has changes or w_lines[idx] is invalid. + // "bot_start" may be halfway a wrapped line after using + // win_del_lines(), check if the current line includes it. + // When syntax folding is being used, the saved syntax states will + // already have been updated, we can't see where the syntax state is + // the same again, just update until the end of the window. + if (row < top_end + || (row >= mid_start && row < mid_end) +#ifdef FEAT_SEARCH_EXTRA + || top_to_mod +#endif + || idx >= wp->w_lines_valid + || (row + wp->w_lines[idx].wl_size > bot_start) + || (mod_top != 0 + && (lnum == mod_top + || (lnum >= mod_top + && (lnum < mod_bot +#ifdef FEAT_SYN_HL + || did_update == DID_FOLD + || (did_update == DID_LINE + && syntax_present(wp) + && ( +# ifdef FEAT_FOLDING + (foldmethodIsSyntax(wp) + && hasAnyFolding(wp)) || +# endif + syntax_check_changed(lnum))) +#endif +#ifdef FEAT_SEARCH_EXTRA + // match in fixed position might need redraw + // if lines were inserted or deleted + || (wp->w_match_head != NULL + && buf->b_mod_xlines != 0) +#endif + )))) +#ifdef FEAT_SYN_HL + || (wp->w_p_cul && lnum == wp->w_cursor.lnum) + || lnum == wp->w_last_cursorline +#endif + ) + { +#ifdef FEAT_SEARCH_EXTRA + if (lnum == mod_top) + top_to_mod = FALSE; +#endif + + // When at start of changed lines: May scroll following lines + // up or down to minimize redrawing. + // Don't do this when the change continues until the end. + // Don't scroll when dollar_vcol >= 0, keep the "$". + // Don't scroll when redrawing the top, scrolled already above. + if (lnum == mod_top + && mod_bot != MAXLNUM + && !(dollar_vcol >= 0 && mod_bot == mod_top + 1) + && row >= top_end) + { + int old_rows = 0; + int new_rows = 0; + int xtra_rows; + linenr_T l; + + // Count the old number of window rows, using w_lines[], which + // should still contain the sizes for the lines as they are + // currently displayed. + for (i = idx; i < wp->w_lines_valid; ++i) + { + // Only valid lines have a meaningful wl_lnum. Invalid + // lines are part of the changed area. + if (wp->w_lines[i].wl_valid + && wp->w_lines[i].wl_lnum == mod_bot) + break; + old_rows += wp->w_lines[i].wl_size; +#ifdef FEAT_FOLDING + if (wp->w_lines[i].wl_valid + && wp->w_lines[i].wl_lastlnum + 1 == mod_bot) + { + // Must have found the last valid entry above mod_bot. + // Add following invalid entries. + ++i; + while (i < wp->w_lines_valid + && !wp->w_lines[i].wl_valid) + old_rows += wp->w_lines[i++].wl_size; + break; + } +#endif + } + + if (i >= wp->w_lines_valid) + { + // We can't find a valid line below the changed lines, + // need to redraw until the end of the window. + // Inserting/deleting lines has no use. + bot_start = 0; + } + else + { + // Able to count old number of rows: Count new window + // rows, and may insert/delete lines + j = idx; + for (l = lnum; l < mod_bot; ++l) + { +#ifdef FEAT_FOLDING + if (hasFoldingWin(wp, l, NULL, &l, TRUE, NULL)) + ++new_rows; + else +#endif + { +#ifdef FEAT_DIFF + if (l == wp->w_topline) + { + int n = plines_win_nofill(wp, l, FALSE) + + wp->w_topfill; + n -= adjust_plines_for_skipcol(wp); + if (n > wp->w_height) + n = wp->w_height; + new_rows += n; + } + else +#endif + new_rows += plines_win(wp, l, TRUE); + } + ++j; + if (new_rows > wp->w_height - row - 2) + { + // it's getting too much, must redraw the rest + new_rows = 9999; + break; + } + } + xtra_rows = new_rows - old_rows; + if (xtra_rows < 0) + { + // May scroll text up. If there is not enough + // remaining text or scrolling fails, must redraw the + // rest. If scrolling works, must redraw the text + // below the scrolled text. + if (row - xtra_rows >= wp->w_height - 2) + mod_bot = MAXLNUM; + else + { + check_for_delay(FALSE); + if (win_del_lines(wp, row, + -xtra_rows, FALSE, FALSE, 0) == FAIL) + mod_bot = MAXLNUM; + else + bot_start = wp->w_height + xtra_rows; + } + } + else if (xtra_rows > 0) + { + // May scroll text down. If there is not enough + // remaining text of scrolling fails, must redraw the + // rest. + if (row + xtra_rows >= wp->w_height - 2) + mod_bot = MAXLNUM; + else + { + check_for_delay(FALSE); + if (win_ins_lines(wp, row + old_rows, + xtra_rows, FALSE, FALSE) == FAIL) + mod_bot = MAXLNUM; + else if (top_end > row + old_rows) + // Scrolled the part at the top that requires + // updating down. + top_end += xtra_rows; + } + } + + // When not updating the rest, may need to move w_lines[] + // entries. + if (mod_bot != MAXLNUM && i != j) + { + if (j < i) + { + int x = row + new_rows; + + // move entries in w_lines[] upwards + for (;;) + { + // stop at last valid entry in w_lines[] + if (i >= wp->w_lines_valid) + { + wp->w_lines_valid = j; + break; + } + wp->w_lines[j] = wp->w_lines[i]; + // stop at a line that won't fit + if (x + (int)wp->w_lines[j].wl_size + > wp->w_height) + { + wp->w_lines_valid = j + 1; + break; + } + x += wp->w_lines[j++].wl_size; + ++i; + } + if (bot_start > x) + bot_start = x; + } + else // j > i + { + // move entries in w_lines[] downwards + j -= i; + wp->w_lines_valid += j; + if (wp->w_lines_valid > wp->w_height) + wp->w_lines_valid = wp->w_height; + for (i = wp->w_lines_valid; i - j >= idx; --i) + wp->w_lines[i] = wp->w_lines[i - j]; + + // The w_lines[] entries for inserted lines are + // now invalid, but wl_size may be used above. + // Reset to zero. + while (i >= idx) + { + wp->w_lines[i].wl_size = 0; + wp->w_lines[i--].wl_valid = FALSE; + } + } + } + } + } + +#ifdef FEAT_FOLDING + // When lines are folded, display one line for all of them. + // Otherwise, display normally (can be several display lines when + // 'wrap' is on). + fold_count = foldedCount(wp, lnum, &win_foldinfo); + if (fold_count != 0) + { + fold_line(wp, fold_count, &win_foldinfo, lnum, row); + ++row; + --fold_count; + wp->w_lines[idx].wl_folded = TRUE; + wp->w_lines[idx].wl_lastlnum = lnum + fold_count; +# ifdef FEAT_SYN_HL + did_update = DID_FOLD; +# endif +# ifdef FEAT_SPELL + spv.spv_capcol_lnum = 0; +# endif + } + else +#endif + if (idx < wp->w_lines_valid + && wp->w_lines[idx].wl_valid + && wp->w_lines[idx].wl_lnum == lnum + && lnum > wp->w_topline + && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE)) + && !WIN_IS_POPUP(wp) + && srow + wp->w_lines[idx].wl_size > wp->w_height +#ifdef FEAT_DIFF + && diff_check_fill(wp, lnum) == 0 +#endif + ) + { + // This line is not going to fit. Don't draw anything here, + // will draw "@ " lines below. + row = wp->w_height + 1; + } + else + { +#ifdef FEAT_SEARCH_EXTRA + prepare_search_hl(wp, &screen_search_hl, lnum); +#endif +#ifdef FEAT_SYN_HL + // Let the syntax stuff know we skipped a few lines. + if (syntax_last_parsed != 0 && syntax_last_parsed + 1 < lnum + && syntax_present(wp)) + syntax_end_parsing(wp, syntax_last_parsed + 1); +#endif + + // Display one line. + row = win_line(wp, lnum, srow, wp->w_height, FALSE, &spv); + +#ifdef FEAT_FOLDING + wp->w_lines[idx].wl_folded = FALSE; + wp->w_lines[idx].wl_lastlnum = lnum; +#endif +#ifdef FEAT_SYN_HL + did_update = DID_LINE; + syntax_last_parsed = lnum; +#endif + } + + wp->w_lines[idx].wl_lnum = lnum; + wp->w_lines[idx].wl_valid = TRUE; + + // Past end of the window or end of the screen. Note that after + // resizing wp->w_height may be end up too big. That's a problem + // elsewhere, but prevent a crash here. + if (row > wp->w_height || row + wp->w_winrow >= Rows) + { + // we may need the size of that too long line later on + if (dollar_vcol == -1) + wp->w_lines[idx].wl_size = plines_win(wp, lnum, TRUE); + ++idx; + break; + } + if (dollar_vcol == -1) + wp->w_lines[idx].wl_size = row - srow; + ++idx; +#ifdef FEAT_FOLDING + lnum += fold_count + 1; +#else + ++lnum; +#endif + } + else + { + if (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum) + { +#ifdef FEAT_FOLDING + // 'relativenumber' set and the cursor moved vertically: The + // text doesn't need to be drawn, but the number column does. + fold_count = foldedCount(wp, lnum, &win_foldinfo); + if (fold_count != 0) + fold_line(wp, fold_count, &win_foldinfo, lnum, row); + else +#endif + (void)win_line(wp, lnum, srow, wp->w_height, TRUE, &spv); + } + + // This line does not need to be drawn, advance to the next one. + row += wp->w_lines[idx++].wl_size; + if (row > wp->w_height) // past end of screen + break; +#ifdef FEAT_FOLDING + lnum = wp->w_lines[idx - 1].wl_lastlnum + 1; +#else + ++lnum; +#endif +#ifdef FEAT_SYN_HL + did_update = DID_NONE; +#endif +#ifdef FEAT_SPELL + spv.spv_capcol_lnum = 0; +#endif + } + + if (lnum > buf->b_ml.ml_line_count) + { + eof = TRUE; + break; + } + + // Safety check: if any of the wl_size values is wrong we might go over + // the end of w_lines[]. + if (idx >= Rows) + break; + } + + // End of loop over all window lines. + +#ifdef FEAT_SYN_HL + // Now that the window has been redrawn with the old and new cursor line, + // update w_last_cursorline. + wp->w_last_cursorline = wp->w_p_cul ? wp->w_cursor.lnum : 0; +#endif + wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0; + +#ifdef FEAT_VTP + // Rewrite the character at the end of the screen line. + // See the version that was fixed. + if (use_vtp() && get_conpty_fix_type() < 1) + { + int k; + + for (k = 0; k < Rows; ++k) + if (enc_utf8) + if ((*mb_off2cells)(LineOffset[k] + Columns - 2, + LineOffset[k] + screen_Columns) > 1) + screen_draw_rectangle(k, Columns - 2, 1, 2, FALSE); + else + screen_draw_rectangle(k, Columns - 1, 1, 1, FALSE); + else + screen_char(LineOffset[k] + Columns - 1, k, Columns - 1); + } +#endif + + if (idx > wp->w_lines_valid) + wp->w_lines_valid = idx; + +#ifdef FEAT_SYN_HL + // Let the syntax stuff know we stop parsing here. + if (syntax_last_parsed != 0 && syntax_present(wp)) + syntax_end_parsing(wp, syntax_last_parsed + 1); +#endif + + // 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. + wp->w_empty_rows = 0; +#ifdef FEAT_DIFF + wp->w_filler_rows = 0; +#endif + if (!eof && !didline) + { + if (lnum == wp->w_topline) + { + // Single line that does not fit! + // Don't overwrite it, it can be edited. + wp->w_botline = lnum + 1; + } +#ifdef FEAT_DIFF + else if (diff_check_fill(wp, lnum) >= wp->w_height - srow) + { + // Window ends in filler lines. + wp->w_botline = lnum; + wp->w_filler_rows = wp->w_height - srow; + } +#endif +#ifdef FEAT_PROP_POPUP + else if (WIN_IS_POPUP(wp)) + { + // popup line that doesn't fit is left as-is + wp->w_botline = lnum; + } +#endif + else if (dy_flags & DY_TRUNCATE) // 'display' has "truncate" + { + int scr_row = W_WINROW(wp) + wp->w_height - 1; + int symbol = wp->w_fill_chars.lastline; + int charlen; + char_u fillbuf[12]; // 2 characters of 6 bytes + + charlen = mb_char2bytes(symbol, &fillbuf[0]); + mb_char2bytes(symbol, &fillbuf[charlen]); + + // Last line isn't finished: Display "@@@" in the last screen line. + screen_puts_len(fillbuf, + (wp->w_width > 2 ? 2 : wp->w_width) * charlen, + scr_row, wp->w_wincol, HL_ATTR(HLF_AT)); + screen_fill(scr_row, scr_row + 1, + (int)wp->w_wincol + 2, (int)W_ENDCOL(wp), + symbol, ' ', HL_ATTR(HLF_AT)); + set_empty_rows(wp, srow); + wp->w_botline = lnum; + } + else if (dy_flags & DY_LASTLINE) // 'display' has "lastline" + { + int start_col = (int)W_ENDCOL(wp) - 3; + int symbol = wp->w_fill_chars.lastline; + + // Last line isn't finished: Display "@@@" at the end. + screen_fill(W_WINROW(wp) + wp->w_height - 1, + W_WINROW(wp) + wp->w_height, + start_col < wp->w_wincol ? wp->w_wincol : start_col, + (int)W_ENDCOL(wp), + symbol, symbol, HL_ATTR(HLF_AT)); + set_empty_rows(wp, srow); + wp->w_botline = lnum; + } + else + { + win_draw_end(wp, wp->w_fill_chars.lastline, ' ', TRUE, + srow, wp->w_height, HLF_AT); + wp->w_botline = lnum; + } + } + else + { + draw_vsep_win(wp, row); + if (eof) // we hit the end of the file + { + wp->w_botline = buf->b_ml.ml_line_count + 1; +#ifdef FEAT_DIFF + j = diff_check_fill(wp, wp->w_botline); + if (j > 0 && !wp->w_botfill) + { + // Display filler lines at the end of the file. + if (char2cells(wp->w_fill_chars.diff) > 1) + i = '-'; + else + i = wp->w_fill_chars.diff; + if (row + j > wp->w_height) + j = wp->w_height - row; + win_draw_end(wp, i, i, TRUE, row, row + (int)j, HLF_DED); + row += j; + } +#endif + } + else if (dollar_vcol == -1) + wp->w_botline = lnum; + + // Make sure the rest of the screen is blank. + // write the "eob" character from 'fillchars' to rows that aren't part + // of the file. + if (WIN_IS_POPUP(wp)) + win_draw_end(wp, ' ', ' ', FALSE, row, wp->w_height, HLF_AT); + else + win_draw_end(wp, wp->w_fill_chars.eob, ' ', FALSE, + row, wp->w_height, HLF_EOB); + } + +#ifdef SYN_TIME_LIMIT + disable_regexp_timeout(); + redrawtime_limit_set = FALSE; +#endif + + // Reset the type of redrawing required, the window has been updated. + wp->w_redr_type = 0; +#ifdef FEAT_DIFF + wp->w_old_topfill = wp->w_topfill; + wp->w_old_botfill = wp->w_botfill; +#endif + + if (dollar_vcol == -1) + { + // There is a trick with w_botline. If we invalidate it on each + // change that might modify it, this will cause a lot of expensive + // calls to plines() in update_topline() each time. Therefore the + // value of w_botline is often approximated, and this value is used to + // compute the value of w_topline. If the value of w_botline was + // wrong, check that the value of w_topline is correct (cursor is on + // the visible part of the text). If it's not, we need to redraw + // again. Mostly this just means scrolling up a few lines, so it + // doesn't look too bad. Only do this for the current window (where + // changes are relevant). + wp->w_valid |= VALID_BOTLINE; + if (wp == curwin && wp->w_botline != old_botline && !recursive) + { + win_T *wwp; +#if defined(FEAT_CONCEAL) + linenr_T old_topline = wp->w_topline; + int new_wcol = wp->w_wcol; +#endif + recursive = TRUE; + curwin->w_valid &= ~VALID_TOPLINE; + update_topline(); // may invalidate w_botline again + +#if defined(FEAT_CONCEAL) + if (old_wcol != new_wcol && (wp->w_valid & (VALID_WCOL|VALID_WROW)) + != (VALID_WCOL|VALID_WROW)) + { + // A win_line() call applied a fix to screen cursor column to + // accommodate concealment of cursor line, but in this call to + // update_topline() the cursor's row or column got invalidated. + // If they are left invalid, setcursor() will recompute them + // but there won't be any further win_line() call to re-fix the + // column and the cursor will end up misplaced. So we call + // cursor validation now and reapply the fix again (or call + // win_line() to do it for us). + validate_cursor(); + if (wp->w_wcol == old_wcol && wp->w_wrow == old_wrow + && old_topline == wp->w_topline) + wp->w_wcol = new_wcol; + else + redrawWinline(wp, wp->w_cursor.lnum); + } +#endif + // New redraw either due to updated topline or due to wcol fix. + if (wp->w_redr_type != 0) + { + // Don't update for changes in buffer again. + i = curbuf->b_mod_set; + curbuf->b_mod_set = FALSE; + j = curbuf->b_mod_xlines; + curbuf->b_mod_xlines = 0; + win_update(curwin); + curbuf->b_mod_set = i; + curbuf->b_mod_xlines = j; + } + // Other windows might have w_redr_type raised in update_topline(). + must_redraw = 0; + FOR_ALL_WINDOWS(wwp) + if (wwp->w_redr_type > must_redraw) + must_redraw = wwp->w_redr_type; + recursive = FALSE; + } + } + +#if defined(FEAT_SYN_HL) || defined(FEAT_SEARCH_EXTRA) + // restore got_int, unless CTRL-C was hit while redrawing + if (!got_int) + got_int = save_got_int; +#endif +} + +#if defined(FEAT_NETBEANS_INTG) || defined(FEAT_GUI) +/* + * Prepare for updating one or more windows. + * Caller must check for "updating_screen" already set to avoid recursiveness. + */ + static void +update_prepare(void) +{ + cursor_off(); + updating_screen = TRUE; +#ifdef FEAT_GUI + // Remove the cursor before starting to do anything, because scrolling may + // make it difficult to redraw the text under it. + if (gui.in_use) + gui_undraw_cursor(); +#endif +#ifdef FEAT_SEARCH_EXTRA + start_search_hl(); +#endif +#ifdef FEAT_PROP_POPUP + // Update popup_mask if needed. + may_update_popup_mask(must_redraw); +#endif +} + +/* + * Finish updating one or more windows. + */ + static void +update_finish(void) +{ + if (redraw_cmdline || redraw_mode) + showmode(); + +# ifdef FEAT_SEARCH_EXTRA + end_search_hl(); +# endif + + after_updating_screen(TRUE); + +# ifdef FEAT_GUI + // Redraw the cursor and update the scrollbars when all screen updating is + // done. + if (gui.in_use) + { + out_flush_cursor(FALSE, FALSE); + gui_update_scrollbars(FALSE); + } +# endif +} +#endif + +#if defined(FEAT_NETBEANS_INTG) || defined(PROTO) + void +update_debug_sign(buf_T *buf, linenr_T lnum) +{ + win_T *wp; + int doit = FALSE; + +# ifdef FEAT_FOLDING + win_foldinfo.fi_level = 0; +# endif + + // update/delete a specific sign + redraw_buf_line_later(buf, lnum); + + // check if it resulted in the need to redraw a window + FOR_ALL_WINDOWS(wp) + if (wp->w_redr_type != 0) + doit = TRUE; + + // Return when there is nothing to do, screen updating is already + // happening (recursive call), messages on the screen or still starting up. + if (!doit || updating_screen + || State == MODE_ASKMORE || State == MODE_HITRETURN + || msg_scrolled +#ifdef FEAT_GUI + || gui.starting +#endif + || starting) + return; + + // update all windows that need updating + update_prepare(); + + FOR_ALL_WINDOWS(wp) + { + if (wp->w_redr_type != 0) + win_update(wp); + if (wp->w_redr_status) + win_redr_status(wp, FALSE); + } + + update_finish(); +} +#endif + +#if defined(FEAT_GUI) || defined(PROTO) +/* + * Update a single window, its status line and maybe the command line msg. + * Used for the GUI scrollbar. + */ + void +updateWindow(win_T *wp) +{ + // return if already busy updating + if (updating_screen) + return; + + update_prepare(); + +#ifdef FEAT_CLIPBOARD + // When Visual area changed, may have to update selection. + if (clip_star.available && clip_isautosel_star()) + clip_update_selection(&clip_star); + if (clip_plus.available && clip_isautosel_plus()) + clip_update_selection(&clip_plus); +#endif + + win_update(wp); + + // When the screen was cleared redraw the tab pages line. + if (redraw_tabline) + draw_tabline(); + + if (wp->w_redr_status || p_ru +# ifdef FEAT_STL_OPT + || *p_stl != NUL || *wp->w_p_stl != NUL +# endif + ) + win_redr_status(wp, FALSE); + +#ifdef FEAT_PROP_POPUP + // Display popup windows on top of everything. + update_popups(win_update); +#endif + + update_finish(); +} +#endif + +/* + * Redraw as soon as possible. When the command line is not scrolled redraw + * right away and restore what was on the command line. + * Return a code indicating what happened. + */ + int +redraw_asap(int type) +{ + int rows; + int cols = screen_Columns; + int r; + int ret = 0; + schar_T *screenline; // copy from ScreenLines[] + sattr_T *screenattr; // copy from ScreenAttrs[] + int i; + u8char_T *screenlineUC = NULL; // copy from ScreenLinesUC[] + u8char_T *screenlineC[MAX_MCO]; // copy from ScreenLinesC[][] + schar_T *screenline2 = NULL; // copy from ScreenLines2[] + + redraw_later(type); + if (msg_scrolled + || (State != MODE_NORMAL && State != MODE_NORMAL_BUSY) + || exiting) + return ret; + + // Allocate space to save the text displayed in the command line area. + rows = screen_Rows - cmdline_row; + screenline = LALLOC_MULT(schar_T, rows * cols); + screenattr = LALLOC_MULT(sattr_T, rows * cols); + if (screenline == NULL || screenattr == NULL) + ret = 2; + if (enc_utf8) + { + screenlineUC = LALLOC_MULT(u8char_T, rows * cols); + if (screenlineUC == NULL) + ret = 2; + for (i = 0; i < p_mco; ++i) + { + screenlineC[i] = LALLOC_MULT(u8char_T, rows * cols); + if (screenlineC[i] == NULL) + ret = 2; + } + } + if (enc_dbcs == DBCS_JPNU) + { + screenline2 = LALLOC_MULT(schar_T, rows * cols); + if (screenline2 == NULL) + ret = 2; + } + + if (ret != 2) + { + // Save the text displayed in the command line area. + for (r = 0; r < rows; ++r) + { + mch_memmove(screenline + r * cols, + ScreenLines + LineOffset[cmdline_row + r], + (size_t)cols * sizeof(schar_T)); + mch_memmove(screenattr + r * cols, + ScreenAttrs + LineOffset[cmdline_row + r], + (size_t)cols * sizeof(sattr_T)); + if (enc_utf8) + { + mch_memmove(screenlineUC + r * cols, + ScreenLinesUC + LineOffset[cmdline_row + r], + (size_t)cols * sizeof(u8char_T)); + for (i = 0; i < p_mco; ++i) + mch_memmove(screenlineC[i] + r * cols, + ScreenLinesC[i] + LineOffset[cmdline_row + r], + (size_t)cols * sizeof(u8char_T)); + } + if (enc_dbcs == DBCS_JPNU) + mch_memmove(screenline2 + r * cols, + ScreenLines2 + LineOffset[cmdline_row + r], + (size_t)cols * sizeof(schar_T)); + } + + update_screen(0); + ret = 3; + + if (must_redraw == 0) + { + int off = (int)(current_ScreenLine - ScreenLines); + + // Restore the text displayed in the command line area. + for (r = 0; r < rows; ++r) + { + mch_memmove(current_ScreenLine, + screenline + r * cols, + (size_t)cols * sizeof(schar_T)); + mch_memmove(ScreenAttrs + off, + screenattr + r * cols, + (size_t)cols * sizeof(sattr_T)); + if (enc_utf8) + { + mch_memmove(ScreenLinesUC + off, + screenlineUC + r * cols, + (size_t)cols * sizeof(u8char_T)); + for (i = 0; i < p_mco; ++i) + mch_memmove(ScreenLinesC[i] + off, + screenlineC[i] + r * cols, + (size_t)cols * sizeof(u8char_T)); + } + if (enc_dbcs == DBCS_JPNU) + mch_memmove(ScreenLines2 + off, + screenline2 + r * cols, + (size_t)cols * sizeof(schar_T)); + screen_line(curwin, cmdline_row + r, 0, cols, cols, 0); + } + ret = 4; + } + } + + vim_free(screenline); + vim_free(screenattr); + if (enc_utf8) + { + vim_free(screenlineUC); + for (i = 0; i < p_mco; ++i) + vim_free(screenlineC[i]); + } + if (enc_dbcs == DBCS_JPNU) + vim_free(screenline2); + + // Show the intro message when appropriate. + maybe_intro_message(); + + setcursor(); + + return ret; +} + +/* + * Invoked after an asynchronous callback is called. + * If an echo command was used the cursor needs to be put back where + * it belongs. If highlighting was changed a redraw is needed. + * If "call_update_screen" is FALSE don't call update_screen() when at the + * command line. + * If "redraw_message" is TRUE. + */ + void +redraw_after_callback(int call_update_screen, int do_message) +{ + ++redrawing_for_callback; + + if (State == MODE_HITRETURN || State == MODE_ASKMORE + || State == MODE_SETWSIZE || State == MODE_EXTERNCMD + || State == MODE_CONFIRM || exmode_active) + { + if (do_message) + repeat_message(); + } + else if (State & MODE_CMDLINE) + { + if (pum_visible()) + cmdline_pum_display(); + + // Don't redraw when in prompt_for_number(). + if (cmdline_row > 0) + { + // Redrawing only works when the screen didn't scroll. Don't clear + // wildmenu entries. + if (msg_scrolled == 0 + && wild_menu_showing == 0 + && call_update_screen) + update_screen(0); + + // Redraw in the same position, so that the user can continue + // editing the command. + redrawcmdline_ex(FALSE); + } + } + else if (State & (MODE_NORMAL | MODE_INSERT | MODE_TERMINAL)) + { + update_topline(); + validate_cursor(); + + // keep the command line if possible + update_screen(UPD_VALID_NO_UPDATE); + setcursor(); + + if (msg_scrolled == 0) + { + // don't want a hit-enter prompt when something else is displayed + msg_didany = FALSE; + need_wait_return = FALSE; + } + } + cursor_on(); +#ifdef FEAT_GUI + if (gui.in_use && !gui_mch_is_blink_off()) + // Don't update the cursor when it is blinking and off to avoid + // flicker. + out_flush_cursor(FALSE, FALSE); + else +#endif + out_flush(); + + --redrawing_for_callback; +} + +/* + * Redraw the current window later, with update_screen(type). + * Set must_redraw only if not already set to a higher value. + * E.g. if must_redraw is UPD_CLEAR, type UPD_NOT_VALID will do nothing. + */ + void +redraw_later(int type) +{ + redraw_win_later(curwin, type); +} + + void +redraw_win_later( + win_T *wp, + int type) +{ + if (!exiting && !redraw_not_allowed && wp->w_redr_type < type) + { + wp->w_redr_type = type; + if (type >= UPD_NOT_VALID) + wp->w_lines_valid = 0; + if (must_redraw < type) // must_redraw is the maximum of all windows + must_redraw = type; + } +} + +/* + * Force a complete redraw later. Also resets the highlighting. To be used + * after executing a shell command that messes up the screen. + */ + void +redraw_later_clear(void) +{ + redraw_all_later(UPD_CLEAR); + reset_screen_attr(); +} + +/* + * Mark all windows to be redrawn later. Except popup windows. + */ + void +redraw_all_later(int type) +{ + win_T *wp; + + FOR_ALL_WINDOWS(wp) + redraw_win_later(wp, type); + // This may be needed when switching tabs. + set_must_redraw(type); +} + +#if 0 // not actually used yet, it probably should +/* + * Mark all windows, including popup windows, to be redrawn. + */ + void +redraw_all_windows_later(int type) +{ + redraw_all_later(type); +#ifdef FEAT_PROP_POPUP + popup_redraw_all(); // redraw all popup windows +#endif +} +#endif + +/* + * Set "must_redraw" to "type" unless it already has a higher value + * or it is currently not allowed. + */ + void +set_must_redraw(int type) +{ + if (!redraw_not_allowed && must_redraw < type) + must_redraw = type; +} + +/* + * Mark all windows that are editing the current buffer to be updated later. + */ + void +redraw_curbuf_later(int type) +{ + redraw_buf_later(curbuf, type); +} + + void +redraw_buf_later(buf_T *buf, int type) +{ + win_T *wp; + + FOR_ALL_WINDOWS(wp) + { + if (wp->w_buffer == buf) + redraw_win_later(wp, type); + } +#if defined(FEAT_TERMINAL) && defined(FEAT_PROP_POPUP) + // terminal in popup window is not in list of windows + if (curwin->w_buffer == buf) + redraw_win_later(curwin, type); +#endif +} + +#if defined(FEAT_SIGNS) || defined(PROTO) + void +redraw_buf_line_later(buf_T *buf, linenr_T lnum) +{ + win_T *wp; + + FOR_ALL_WINDOWS(wp) + if (wp->w_buffer == buf && lnum >= wp->w_topline + && lnum < wp->w_botline) + redrawWinline(wp, lnum); +} +#endif + +#if defined(FEAT_JOB_CHANNEL) || defined(PROTO) + void +redraw_buf_and_status_later(buf_T *buf, int type) +{ + win_T *wp; + + if (wild_menu_showing != 0) + // Don't redraw while the command line completion is displayed, it + // would disappear. + return; + FOR_ALL_WINDOWS(wp) + { + if (wp->w_buffer == buf) + { + redraw_win_later(wp, type); + wp->w_redr_status = TRUE; + } + } +} +#endif + +/* + * mark all status lines for redraw; used after first :cd + */ + void +status_redraw_all(void) +{ + win_T *wp; + + FOR_ALL_WINDOWS(wp) + if (wp->w_status_height) + { + wp->w_redr_status = TRUE; + redraw_later(UPD_VALID); + } +} + +/* + * mark all status lines of the current buffer for redraw + */ + void +status_redraw_curbuf(void) +{ + win_T *wp; + + FOR_ALL_WINDOWS(wp) + if (wp->w_status_height != 0 && wp->w_buffer == curbuf) + { + wp->w_redr_status = TRUE; + redraw_later(UPD_VALID); + } +} + +/* + * Redraw all status lines that need to be redrawn. + */ + void +redraw_statuslines(void) +{ + win_T *wp; + + FOR_ALL_WINDOWS(wp) + if (wp->w_redr_status) + win_redr_status(wp, FALSE); + if (redraw_tabline) + draw_tabline(); +} + +/* + * Redraw all status lines at the bottom of frame "frp". + */ + void +win_redraw_last_status(frame_T *frp) +{ + if (frp->fr_layout == FR_LEAF) + frp->fr_win->w_redr_status = TRUE; + else if (frp->fr_layout == FR_ROW) + { + FOR_ALL_FRAMES(frp, frp->fr_child) + win_redraw_last_status(frp); + } + else // frp->fr_layout == FR_COL + { + frp = frp->fr_child; + while (frp->fr_next != NULL) + frp = frp->fr_next; + win_redraw_last_status(frp); + } +} + +/* + * Changed something in the current window, at buffer line "lnum", that + * requires that line and possibly other lines to be redrawn. + * Used when entering/leaving Insert mode with the cursor on a folded line. + * Used to remove the "$" from a change command. + * Note that when also inserting/deleting lines w_redraw_top and w_redraw_bot + * may become invalid and the whole window will have to be redrawn. + */ + void +redrawWinline( + win_T *wp, + linenr_T lnum) +{ + if (wp->w_redraw_top == 0 || wp->w_redraw_top > lnum) + wp->w_redraw_top = lnum; + if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) + wp->w_redraw_bot = lnum; + redraw_win_later(wp, UPD_VALID); +} diff --git a/userland/apps/vim/src/edit.c b/userland/apps/vim/src/edit.c new file mode 100644 index 00000000..9435fd6f --- /dev/null +++ b/userland/apps/vim/src/edit.c @@ -0,0 +1,5436 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + * See README.txt for an overview of the Vim source code. + */ + +/* + * edit.c: functions for Insert mode + */ + +#include "vim.h" + +#define BACKSPACE_CHAR 1 +#define BACKSPACE_WORD 2 +#define BACKSPACE_WORD_NOT_SPACE 3 +#define BACKSPACE_LINE 4 + +// Set when doing something for completion that may call edit() recursively, +// which is not allowed. +static int compl_busy = FALSE; + + +static void ins_ctrl_v(void); +static void insert_special(int, int, int); +static void redo_literal(int c); +static void start_arrow_common(pos_T *end_insert_pos, int change); +#ifdef FEAT_SPELL +static void check_spell_redraw(void); +#endif +static void stop_insert(pos_T *end_insert_pos, int esc, int nomove); +static int echeck_abbr(int); +static void mb_replace_pop_ins(int cc); +static void replace_flush(void); +static void replace_do_bs(int limit_col); +static int del_char_after_col(int limit_col); +static void ins_reg(void); +static void ins_ctrl_g(void); +static void ins_ctrl_hat(void); +static int ins_esc(long *count, int cmdchar, int nomove); +#ifdef FEAT_RIGHTLEFT +static void ins_ctrl_(void); +#endif +static int ins_start_select(int c); +static void ins_insert(int replaceState); +static void ins_ctrl_o(void); +static void ins_shift(int c, int lastc); +static void ins_del(void); +static int ins_bs(int c, int mode, int *inserted_space_p); +#if defined(FEAT_GUI_TABLINE) || defined(PROTO) +static void ins_tabline(int c); +#endif +static void ins_left(void); +static void ins_home(int c); +static void ins_end(int c); +static void ins_s_left(void); +static void ins_right(void); +static void ins_s_right(void); +static void ins_up(int startcol); +static void ins_pageup(void); +static void ins_down(int startcol); +static void ins_pagedown(void); +#ifdef FEAT_DND +static void ins_drop(void); +#endif +static int ins_tab(void); +#ifdef FEAT_DIGRAPHS +static int ins_digraph(void); +#endif +static int ins_ctrl_ey(int tc); +#if defined(FEAT_EVAL) +static char_u *do_insert_char_pre(int c); +#endif + +static colnr_T Insstart_textlen; // length of line when insert started +static colnr_T Insstart_blank_vcol; // vcol for first inserted blank +static int update_Insstart_orig = TRUE; // set Insstart_orig to Insstart + +static char_u *last_insert = NULL; // the text of the previous insert, + // K_SPECIAL and CSI are escaped +static int last_insert_skip; // nr of chars in front of previous insert +static int new_insert_skip; // nr of chars in front of current insert +static int did_restart_edit; // "restart_edit" when calling edit() + +static int can_cindent; // may do cindenting on this line + +#ifdef FEAT_RIGHTLEFT +static int revins_on; // reverse insert mode on +static int revins_chars; // how much to skip after edit +static int revins_legal; // was the last char 'legal'? +static int revins_scol; // start column of revins session +#endif + +static int ins_need_undo; // call u_save() before inserting a + // char. Set when edit() is called. + // after that arrow_used is used. + +static int dont_sync_undo = FALSE; // CTRL-G U prevents syncing undo for + // the next left/right cursor key + +/* + * edit(): Start inserting text. + * + * "cmdchar" can be: + * 'i' normal insert command + * 'a' normal append command + * K_PS bracketed paste + * 'R' replace command + * 'r' "r" command: insert one . Note: count can be > 1, for redo, + * but still only one is inserted. The is not used for redo. + * 'g' "gI" command. + * 'V' "gR" command for Virtual Replace mode. + * 'v' "gr" command for single character Virtual Replace mode. + * + * This function is not called recursively. For CTRL-O commands, it returns + * and lets the caller handle the Normal-mode command. + * + * Return TRUE if a CTRL-O command caused the return (insert mode pending). + */ + int +edit( + int cmdchar, + int startln, // if set, insert at start of line + long count) +{ + int c = 0; + char_u *ptr; + int lastc = 0; + int mincol; + static linenr_T o_lnum = 0; + int i; + int did_backspace = TRUE; // previous char was backspace + int line_is_white = FALSE; // line is empty before insert + linenr_T old_topline = 0; // topline before insertion +#ifdef FEAT_DIFF + int old_topfill = -1; +#endif + int inserted_space = FALSE; // just inserted a space + int replaceState = MODE_REPLACE; + int nomove = FALSE; // don't move cursor on return +#ifdef FEAT_JOB_CHANNEL + int cmdchar_todo = cmdchar; +#endif +#ifdef FEAT_CONCEAL + int cursor_line_was_concealed; +#endif + + // Remember whether editing was restarted after CTRL-O. + did_restart_edit = restart_edit; + + // sleep before redrawing, needed for "CTRL-O :" that results in an + // error message + check_for_delay(TRUE); + + // set Insstart_orig to Insstart + update_Insstart_orig = TRUE; + +#ifdef HAVE_SANDBOX + // Don't allow inserting in the sandbox. + if (sandbox != 0) + { + emsg(_(e_not_allowed_in_sandbox)); + return FALSE; + } +#endif + // Don't allow changes in the buffer while editing the cmdline. The + // caller of getcmdline() may get confused. + // Don't allow recursive insert mode when busy with completion. + if (textlock != 0 || ins_compl_active() || compl_busy || pum_visible()) + { + emsg(_(e_not_allowed_to_change_text_or_change_window)); + return FALSE; + } + ins_compl_clear(); // clear stuff for CTRL-X mode + + /* + * Trigger InsertEnter autocommands. Do not do this for "r" or "grx". + */ + if (cmdchar != 'r' && cmdchar != 'v') + { + pos_T save_cursor = curwin->w_cursor; + +#ifdef FEAT_EVAL + if (cmdchar == 'R') + ptr = (char_u *)"r"; + else if (cmdchar == 'V') + ptr = (char_u *)"v"; + else + ptr = (char_u *)"i"; + set_vim_var_string(VV_INSERTMODE, ptr, 1); + set_vim_var_string(VV_CHAR, NULL, -1); // clear v:char +#endif + ins_apply_autocmds(EVENT_INSERTENTER); + + // Check for changed highlighting, e.g. for ModeMsg. + if (need_highlight_changed) + highlight_changed(); + + // Make sure the cursor didn't move. Do call check_cursor_col() in + // case the text was modified. Since Insert mode was not started yet + // a call to check_cursor_col() may move the cursor, especially with + // the "A" command, thus set State to avoid that. Also check that the + // line number is still valid (lines may have been deleted). + // Do not restore if v:char was set to a non-empty string. + if (!EQUAL_POS(curwin->w_cursor, save_cursor) +#ifdef FEAT_EVAL + && *get_vim_var_str(VV_CHAR) == NUL +#endif + && save_cursor.lnum <= curbuf->b_ml.ml_line_count) + { + int save_state = State; + + curwin->w_cursor = save_cursor; + State = MODE_INSERT; + check_cursor_col(); + State = save_state; + } + } + +#ifdef FEAT_CONCEAL + // Check if the cursor line was concealed before changing State. + cursor_line_was_concealed = curwin->w_p_cole > 0 + && conceal_cursor_line(curwin); +#endif + + /* + * 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 + { + Insstart = curwin->w_cursor; + if (startln) + Insstart.col = 0; + } + Insstart_textlen = (colnr_T)linetabsize_str(ml_get_curline()); + Insstart_blank_vcol = MAXCOL; + if (!did_ai) + ai_col = 0; + + if (cmdchar != NUL && restart_edit == 0) + { + ResetRedobuff(); + AppendNumberToRedobuff(count); + if (cmdchar == 'V' || cmdchar == 'v') + { + // "gR" or "gr" command + AppendCharToRedobuff('g'); + AppendCharToRedobuff((cmdchar == 'v') ? 'r' : 'R'); + } + else + { + if (cmdchar == K_PS) + AppendCharToRedobuff('a'); + else + AppendCharToRedobuff(cmdchar); + if (cmdchar == 'g') // "gI" command + AppendCharToRedobuff('I'); + else if (cmdchar == 'r') // "r" command + count = 1; // insert only one + } + } + + if (cmdchar == 'R') + { + State = MODE_REPLACE; + } + else if (cmdchar == 'V' || cmdchar == 'v') + { + State = MODE_VREPLACE; + replaceState = MODE_VREPLACE; + orig_line_count = curbuf->b_ml.ml_line_count; + vr_lines_changed = 1; + } + else + State = MODE_INSERT; + + may_trigger_modechanged(); + stop_insert_mode = FALSE; + +#ifdef FEAT_CONCEAL + // Check if the cursor line needs redrawing after changing State. If + // 'concealcursor' is "n" it needs to be redrawn without concealing. + conceal_check_cursor_line(cursor_line_was_concealed); +#endif + + // Need to position cursor again when on a TAB and when on a char with + // virtual text. + if (gchar_cursor() == TAB +#ifdef FEAT_PROP_POPUP + || curbuf->b_has_textprop +#endif + ) + curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL); + + /* + * Enable langmap or IME, indicated by 'iminsert'. + * Note that IME may enabled/disabled without us noticing here, thus the + * 'iminsert' value may not reflect what is actually used. It is updated + * when hitting . + */ + if (curbuf->b_p_iminsert == B_IMODE_LMAP) + State |= MODE_LANGMAP; +#ifdef HAVE_INPUT_METHOD + im_set_active(curbuf->b_p_iminsert == B_IMODE_IM); +#endif + + setmouse(); + clear_showcmd(); +#ifdef FEAT_RIGHTLEFT + // there is no reverse replace mode + revins_on = (State == MODE_INSERT && p_ri); + if (revins_on) + undisplay_dollar(); + revins_chars = 0; + revins_legal = 0; + revins_scol = -1; +#endif + if (!p_ek) + { + MAY_WANT_TO_LOG_THIS; + + // Disable bracketed paste mode, we won't recognize the escape + // sequences. + out_str(T_BD); + + // Disable modifyOtherKeys, keys with modifiers would cause exiting + // Insert mode. + out_str_t_TE(); + } + + /* + * Handle restarting Insert mode. + * Don't do this for "CTRL-O ." (repeat an insert): In that case we get + * here with something in the stuff buffer. + */ + if (restart_edit != 0 && stuff_empty()) + { + /* + * After a paste we consider text typed to be part of the insert for + * the pasted text. You can backspace over the pasted text too. + */ + if (where_paste_started.lnum) + arrow_used = FALSE; + else + 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|". + */ + validate_virtcol(); + update_curswant(); + if (((ins_at_eol && curwin->w_cursor.lnum == o_lnum) + || curwin->w_curswant > curwin->w_virtcol) + && *(ptr = ml_get_curline() + curwin->w_cursor.col) != NUL) + { + if (ptr[1] == NUL) + ++curwin->w_cursor.col; + else if (has_mbyte) + { + i = (*mb_ptr2len)(ptr); + if (ptr[i] == NUL) + curwin->w_cursor.col += i; + } + } + ins_at_eol = FALSE; + } + else + arrow_used = FALSE; + + // we are in insert mode now, don't need to start it anymore + need_start_insertmode = FALSE; + + // Need to save the line for undo before inserting the first char. + ins_need_undo = TRUE; + + where_paste_started.lnum = 0; + can_cindent = TRUE; +#ifdef FEAT_FOLDING + // The cursor line is not in a closed fold, unless 'insertmode' is set or + // restarting. + if (!p_im && did_restart_edit == 0) + foldOpenCursor(); +#endif + + /* + * If 'showmode' is set, show the current (insert/replace/..) mode. + * A warning message for changing a readonly file is given here, before + * actually changing anything. It's put after the mode, if any. + */ + i = 0; + if (p_smd && msg_silent == 0) + i = showmode(); + + if (!p_im && did_restart_edit == 0) + change_warning(i == 0 ? 0 : i + 1); + +#ifdef CURSOR_SHAPE + ui_cursor_shape(); // may show different cursor shape +#endif +#ifdef FEAT_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(); + if (ptr == NULL) + new_insert_skip = 0; + else + { + new_insert_skip = (int)STRLEN(ptr); + vim_free(ptr); + } + + old_indent = 0; + + /* + * Main loop in Insert mode: repeat until Insert mode is left. + */ + for (;;) + { +#ifdef FEAT_RIGHTLEFT + if (!revins_legal) + revins_scol = -1; // reset on illegal motions + else + revins_legal = 0; +#endif + if (arrow_used) // don't repeat insert when arrow key used + count = 0; + + if (update_Insstart_orig) + Insstart_orig = Insstart; + + if (stop_insert_mode && !ins_compl_active()) + { + // ":stopinsert" used or 'insertmode' reset + count = 0; + goto doESCkey; + } + + // set curwin->w_curswant for next K_DOWN or K_UP + if (!arrow_used) + curwin->w_set_curswant = TRUE; + + // If there is no typeahead may check for timestamps (e.g., for when a + // menu invoked a shell command). + if (stuff_empty()) + { + did_check_timestamps = FALSE; + if (need_check_timestamps) + check_timestamps(FALSE); + } + + /* + * When emsg() was called msg_scroll will have been set. + */ + msg_scroll = FALSE; + +#ifdef FEAT_GUI + // When 'mousefocus' is set a mouse movement may have taken us to + // another window. "need_mouse_correct" may then be set because of an + // autocommand. + if (need_mouse_correct) + gui_mouse_correct(); +#endif + +#ifdef FEAT_FOLDING + // Open fold at the cursor line, according to 'foldopen'. + if (fdo_flags & FDO_INSERT) + foldOpenCursor(); + // Close folds where the cursor isn't, according to 'foldclose' + if (!char_avail()) + foldCheckClose(); +#endif + +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(curbuf)) + { + init_prompt(cmdchar_todo); + cmdchar_todo = NUL; + } +#endif + + /* + * 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. + * Don't do this when the topline changed already, it has + * already been adjusted (by insertchar() calling open_line())). + * Also don't do this when 'smoothscroll' is set, as the window should + * then be scrolled by screen lines. + */ + if (curbuf->b_mod_set + && curwin->w_p_wrap + && !curwin->w_p_sms + && !did_backspace + && curwin->w_topline == old_topline +#ifdef FEAT_DIFF + && curwin->w_topfill == old_topfill +#endif + ) + { + mincol = curwin->w_wcol; + validate_cursor_col(); + + if ( +#ifdef FEAT_VARTABS + curwin->w_wcol < mincol - tabstop_at( + get_nolist_virtcol(), curbuf->b_p_ts, + curbuf->b_p_vts_array) +#else + (int)curwin->w_wcol < mincol - curbuf->b_p_ts +#endif + && curwin->w_wrow == W_WINROW(curwin) + + curwin->w_height - 1 - get_scrolloff_value() + && (curwin->w_cursor.lnum != curwin->w_topline +#ifdef FEAT_DIFF + || curwin->w_topfill > 0 +#endif + )) + { +#ifdef FEAT_DIFF + if (curwin->w_topfill > 0) + --curwin->w_topfill; + else +#endif +#ifdef FEAT_FOLDING + if (hasFolding(curwin->w_topline, NULL, &old_topline)) + set_topline(curwin, old_topline + 1); + else +#endif + set_topline(curwin, curwin->w_topline + 1); + } + } + + // May need to adjust w_topline to show the cursor. + update_topline(); + + did_backspace = FALSE; + + validate_cursor(); // may set must_redraw + + /* + * Redraw the display when no characters are waiting. + * Also shows mode, ruler and positions cursor. + */ + ins_redraw(TRUE); + + if (curwin->w_p_scb) + do_check_scrollbind(TRUE); + + if (curwin->w_p_crb) + do_check_cursorbind(); + update_curswant(); + old_topline = curwin->w_topline; +#ifdef FEAT_DIFF + old_topfill = curwin->w_topfill; +#endif + +#ifdef USE_ON_FLY_SCROLL + dont_scroll = FALSE; // allow scrolling here +#endif + // May request the keyboard protocol state now. + may_send_t_RK(); + + /* + * Get a character for Insert mode. Ignore K_IGNORE and K_NOP. + */ + if (c != K_CURSORHOLD) + lastc = c; // remember the previous char for CTRL-D + + // After using CTRL-G U the next cursor key will not break undo. + if (dont_sync_undo == MAYBE) + dont_sync_undo = TRUE; + else + dont_sync_undo = FALSE; + if (cmdchar == K_PS) + // Got here from normal mode when bracketed paste started. + c = K_PS; + else + do + { + c = safe_vgetc(); + + if (stop_insert_mode +#ifdef FEAT_TERMINAL + || (c == K_IGNORE && term_use_loop()) +#endif + ) + { + // Insert mode ended, possibly from a callback, or a timer + // must have opened a terminal window. + if (c != K_IGNORE && c != K_NOP) + vungetc(c); + count = 0; + nomove = TRUE; + ins_compl_prep(ESC); + goto doESCkey; + } + } while (c == K_IGNORE || c == K_NOP); + + // Don't want K_CURSORHOLD for the second key, e.g., after CTRL-V. + did_cursorhold = TRUE; + +#ifdef FEAT_RIGHTLEFT + if (p_hkmap && KeyTyped) + c = hkmap(c); // Hebrew mode mapping +#endif + + // If the window was made so small that nothing shows, make it at least + // one line and one column when typing. + if (KeyTyped && !KeyStuffed) + win_ensure_size(); + + /* + * Special handling of keys while the popup menu is visible or wanted + * and the cursor is still in the completed word. Only when there is + * a match, skip this when no matches were found. + */ + if (ins_compl_active() + && pum_wanted() + && curwin->w_cursor.col >= ins_compl_col() + && ins_compl_has_shown_match()) + { + // BS: Delete one character from "compl_leader". + if ((c == K_BS || c == Ctrl_H) + && curwin->w_cursor.col > ins_compl_col() + && (c = ins_compl_bs()) == NUL) + continue; + + // When no match was selected or it was edited. + if (!ins_compl_used_match()) + { + // CTRL-L: Add one character from the current match to + // "compl_leader". Except when at the original match and + // there is nothing to add, CTRL-L works like CTRL-P then. + if (c == Ctrl_L + && (!ctrl_x_mode_line_or_eval() + || ins_compl_long_shown_match())) + { + ins_compl_addfrommatch(); + continue; + } + + // A non-white character that fits in with the current + // completion: Add to "compl_leader". + if (ins_compl_accept_char(c)) + { +#if defined(FEAT_EVAL) + // Trigger InsertCharPre. + char_u *str = do_insert_char_pre(c); + char_u *p; + + if (str != NULL) + { + for (p = str; *p != NUL; MB_PTR_ADV(p)) + ins_compl_addleader(PTR2CHAR(p)); + vim_free(str); + } + else +#endif + ins_compl_addleader(c); + continue; + } + + // Pressing CTRL-Y selects the current match. When + // ins_compl_enter_selects() is set the Enter key does the + // same. + if ((c == Ctrl_Y || (ins_compl_enter_selects() + && (c == CAR || c == K_KENTER || c == NL))) + && stop_arrow() == OK) + { + ins_compl_delete(); + ins_compl_insert(FALSE); + } + } + } + + // Prepare for or stop CTRL-X mode. This doesn't do completion, but + // it does fix up the text when finishing completion. + ins_compl_init_get_longest(); + if (ins_compl_prep(c)) + continue; + + // CTRL-\ CTRL-N goes to Normal mode, + // CTRL-\ CTRL-G goes to mode selected with 'insertmode', + // CTRL-\ CTRL-O is like CTRL-O but without moving the cursor. + if (c == Ctrl_BSL) + { + // may need to redraw when no more chars available now + ins_redraw(FALSE); + ++no_mapping; + ++allow_keys; + c = plain_vgetc(); + --no_mapping; + --allow_keys; + if (c != Ctrl_N && c != Ctrl_G && c != Ctrl_O) + { + // it's something else + vungetc(c); + c = Ctrl_BSL; + } + else if (c == Ctrl_G && p_im) + continue; + else + { + if (c == Ctrl_O) + { + ins_ctrl_o(); + ins_at_eol = FALSE; // cursor keeps its column + nomove = TRUE; + } + count = 0; + goto doESCkey; + } + } + +#ifdef FEAT_DIGRAPHS + c = do_digraph(c); +#endif + + if ((c == Ctrl_V || c == Ctrl_Q) && ctrl_x_mode_cmdline()) + goto docomplete; + if (c == Ctrl_V || c == Ctrl_Q) + { + ins_ctrl_v(); + c = Ctrl_V; // pretend CTRL-V is last typed character + continue; + } + + if (cindent_on() && ctrl_x_mode_none()) + { + // A key name preceded by a bang means this key is not to be + // inserted. Skip ahead to the re-indenting below. + // A key name preceded by a star means that indenting has to be + // done before inserting the key. + line_is_white = inindent(0); + if (in_cinkeys(c, '!', line_is_white)) + goto force_cindent; + if (can_cindent && in_cinkeys(c, '*', line_is_white) + && stop_arrow() == OK) + do_c_expr_indent(); + } + +#ifdef FEAT_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_C_LEFT: c = K_C_RIGHT; break; + case K_RIGHT: c = K_LEFT; break; + case K_S_RIGHT: c = K_S_LEFT; break; + case K_C_RIGHT: c = K_C_LEFT; break; + } +#endif + + /* + * If 'keymodel' contains "startsel", may start selection. If it + * does, a CTRL-O and c will be stuffed, we need to get these + * characters. + */ + if (ins_start_select(c)) + continue; + + /* + * The big switch to handle a character in insert mode. + */ + switch (c) + { + case ESC: // End input mode + if (echeck_abbr(ESC + ABBR_OFF)) + break; + // FALLTHROUGH + + case Ctrl_C: // End input mode + if (c == Ctrl_C && cmdwin_type != 0) + { + // Close the cmdline window. + cmdwin_result = K_IGNORE; + got_int = FALSE; // don't stop executing autocommands et al. + nomove = TRUE; + goto doESCkey; + } +#ifdef FEAT_JOB_CHANNEL + if (c == Ctrl_C && bt_prompt(curbuf)) + { + if (invoke_prompt_interrupt()) + { + if (!bt_prompt(curbuf)) + // buffer changed to a non-prompt buffer, get out of + // Insert mode + goto doESCkey; + break; + } + } +#endif + +#ifdef UNIX +do_intr: +#endif + // when 'insertmode' set, and not halfway a mapping, don't leave + // Insert mode + if (goto_im()) + { + if (got_int) + { + (void)vgetc(); // flush all buffers + got_int = FALSE; + } + else + vim_beep(BO_IM); + break; + } +doESCkey: + /* + * This is the ONLY return from edit()! + */ + // Always update o_lnum, so that a "CTRL-O ." that adds a line + // still puts the cursor back after the inserted text. + if (ins_at_eol && gchar_cursor() == NUL) + o_lnum = curwin->w_cursor.lnum; + + if (ins_esc(&count, cmdchar, nomove)) + { + // When CTRL-C was typed got_int will be set, with the result + // that the autocommands won't be executed. When mapped got_int + // is not set, but let's keep the behavior the same. + if (cmdchar != 'r' && cmdchar != 'v' && c != Ctrl_C) + ins_apply_autocmds(EVENT_INSERTLEAVE); + did_cursorhold = FALSE; + curbuf->b_last_changedtick = CHANGEDTICK(curbuf); + return (c == Ctrl_O); + } + continue; + + case Ctrl_Z: // suspend when 'insertmode' set + if (!p_im) + goto normalchar; // insert CTRL-Z as normal char + do_cmdline_cmd((char_u *)"stop"); +#ifdef CURSOR_SHAPE + ui_cursor_shape(); // may need to update cursor shape +#endif + continue; + + case Ctrl_O: // execute one command +#ifdef FEAT_COMPL_FUNC + if (ctrl_x_mode_omni()) + goto docomplete; +#endif + if (echeck_abbr(Ctrl_O + ABBR_OFF)) + break; + ins_ctrl_o(); + + // don't move the cursor left when 'virtualedit' has "onemore". + if (get_ve_flags() & VE_ONEMORE) + { + ins_at_eol = FALSE; + nomove = TRUE; + } + count = 0; + goto doESCkey; + + case K_INS: // toggle insert/replace mode + case K_KINS: + ins_insert(replaceState); + break; + + case K_SELECT: // end of Select mode mapping - ignore + break; + + case K_HELP: // Help key works like + case K_F1: + case K_XF1: + stuffcharReadbuff(K_HELP); + if (p_im) + need_start_insertmode = TRUE; + goto doESCkey; + +#ifdef FEAT_NETBEANS_INTG + case K_F21: // NetBeans command + ++no_mapping; // don't map the next key hits + i = plain_vgetc(); + --no_mapping; + netbeans_keycommand(i); + break; +#endif + + case K_ZERO: // Insert the previously inserted text. + case NUL: + case Ctrl_A: + // For ^@ the trailing ESC will end the insert, unless there is an + // error. + if (stuff_inserted(NUL, 1L, (c == Ctrl_A)) == FAIL + && c != Ctrl_A && !p_im) + goto doESCkey; // quit insert mode + inserted_space = FALSE; + break; + + case Ctrl_R: // insert the contents of a register + ins_reg(); + auto_format(FALSE, TRUE); + inserted_space = FALSE; + break; + + case Ctrl_G: // commands starting with CTRL-G + ins_ctrl_g(); + break; + + case Ctrl_HAT: // switch input mode and/or langmap + ins_ctrl_hat(); + break; + +#ifdef FEAT_RIGHTLEFT + case Ctrl__: // switch between languages + if (!p_ari) + goto normalchar; + ins_ctrl_(); + break; +#endif + + case Ctrl_D: // Make indent one shiftwidth smaller. +#if defined(FEAT_FIND_ID) + if (ctrl_x_mode_path_defines()) + goto docomplete; +#endif + // FALLTHROUGH + + case Ctrl_T: // Make indent one shiftwidth greater. + if (c == Ctrl_T && ctrl_x_mode_thesaurus()) + { + if (has_compl_option(FALSE)) + goto docomplete; + break; + } + + ins_shift(c, lastc); + auto_format(FALSE, TRUE); + inserted_space = FALSE; + break; + + case K_DEL: // delete character under the cursor + case K_KDEL: + ins_del(); + auto_format(FALSE, TRUE); + break; + + case K_BS: // delete character before the cursor + case K_S_BS: + case Ctrl_H: + did_backspace = ins_bs(c, BACKSPACE_CHAR, &inserted_space); + auto_format(FALSE, TRUE); + break; + + case Ctrl_W: // delete word before the cursor +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(curbuf) && (mod_mask & MOD_MASK_SHIFT) == 0) + { + // In a prompt window CTRL-W is used for window commands. + // Use Shift-CTRL-W to delete a word. + stuffcharReadbuff(Ctrl_W); + restart_edit = 'A'; + nomove = TRUE; + count = 0; + goto doESCkey; + } +#endif + did_backspace = ins_bs(c, BACKSPACE_WORD, &inserted_space); + auto_format(FALSE, TRUE); + break; + + case Ctrl_U: // delete all inserted text in current line +# ifdef FEAT_COMPL_FUNC + // CTRL-X CTRL-U completes with 'completefunc'. + if (ctrl_x_mode_function()) + goto docomplete; +# endif + did_backspace = ins_bs(c, BACKSPACE_LINE, &inserted_space); + auto_format(FALSE, TRUE); + inserted_space = FALSE; + break; + + case K_LEFTMOUSE: // mouse keys + case K_LEFTMOUSE_NM: + case K_LEFTDRAG: + case K_LEFTRELEASE: + case K_LEFTRELEASE_NM: + case K_MOUSEMOVE: + case K_MIDDLEMOUSE: + case K_MIDDLEDRAG: + case K_MIDDLERELEASE: + case K_RIGHTMOUSE: + case K_RIGHTDRAG: + case K_RIGHTRELEASE: + case K_X1MOUSE: + case K_X1DRAG: + case K_X1RELEASE: + case K_X2MOUSE: + case K_X2DRAG: + case K_X2RELEASE: + ins_mouse(c); + break; + + case K_MOUSEDOWN: // Default action for scroll wheel up: scroll up + ins_mousescroll(MSCR_DOWN); + break; + + case K_MOUSEUP: // Default action for scroll wheel down: scroll down + ins_mousescroll(MSCR_UP); + break; + + case K_MOUSELEFT: // Scroll wheel left + ins_mousescroll(MSCR_LEFT); + break; + + case K_MOUSERIGHT: // Scroll wheel right + ins_mousescroll(MSCR_RIGHT); + break; + + case K_PS: + bracketed_paste(PASTE_INSERT, FALSE, NULL); + if (cmdchar == K_PS) + // invoked from normal mode, bail out + goto doESCkey; + break; + case K_PE: + // Got K_PE without K_PS, ignore. + break; + +#ifdef FEAT_GUI_TABLINE + case K_TABLINE: + case K_TABMENU: + ins_tabline(c); + break; +#endif + + case K_IGNORE: // Something mapped to nothing + break; + + case K_COMMAND: // command + case K_SCRIPT_COMMAND: // command + { + do_cmdkey_command(c, 0); + +#ifdef FEAT_TERMINAL + if (term_use_loop()) + // Started a terminal that gets the input, exit Insert mode. + goto doESCkey; +#endif + if (curbuf->b_u_synced) + // The command caused undo to be synced. Need to save the + // line for undo before inserting the next char. + ins_need_undo = TRUE; + } + break; + + case K_CURSORHOLD: // Didn't type something for a while. + ins_apply_autocmds(EVENT_CURSORHOLDI); + did_cursorhold = TRUE; + // If CTRL-G U was used apply it to the next typed key. + if (dont_sync_undo == TRUE) + dont_sync_undo = MAYBE; + break; + +#ifdef FEAT_GUI_MSWIN + // On MS-Windows ignore , we get it when closing the window + // was cancelled. + case K_F4: + if (mod_mask != MOD_MASK_ALT) + goto normalchar; + break; +#endif + +#ifdef FEAT_GUI + case K_VER_SCROLLBAR: + ins_scroll(); + break; + + case K_HOR_SCROLLBAR: + ins_horscroll(); + break; +#endif + + case K_HOME: // + case K_KHOME: + case K_S_HOME: + case K_C_HOME: + ins_home(c); + break; + + case K_END: // + case K_KEND: + case K_S_END: + case K_C_END: + ins_end(c); + break; + + case K_LEFT: // + if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) + ins_s_left(); + else + ins_left(); + break; + + case K_S_LEFT: // + case K_C_LEFT: + ins_s_left(); + break; + + case K_RIGHT: // + if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) + ins_s_right(); + else + ins_right(); + break; + + case K_S_RIGHT: // + case K_C_RIGHT: + ins_s_right(); + break; + + case K_UP: // + if (pum_visible()) + goto docomplete; + if (mod_mask & MOD_MASK_SHIFT) + ins_pageup(); + else + ins_up(FALSE); + break; + + case K_S_UP: // + case K_PAGEUP: + case K_KPAGEUP: + if (pum_visible()) + goto docomplete; + ins_pageup(); + break; + + case K_DOWN: // + if (pum_visible()) + goto docomplete; + if (mod_mask & MOD_MASK_SHIFT) + ins_pagedown(); + else + ins_down(FALSE); + break; + + case K_S_DOWN: // + case K_PAGEDOWN: + case K_KPAGEDOWN: + if (pum_visible()) + goto docomplete; + ins_pagedown(); + break; + +#ifdef FEAT_DND + case K_DROP: // drag-n-drop event + ins_drop(); + break; +#endif + + case K_S_TAB: // When not mapped, use like a normal TAB + c = TAB; + // FALLTHROUGH + + case TAB: // TAB or Complete patterns along path +#if defined(FEAT_FIND_ID) + if (ctrl_x_mode_path_patterns()) + goto docomplete; +#endif + inserted_space = FALSE; + if (ins_tab()) + goto normalchar; // insert TAB as a normal char + auto_format(FALSE, TRUE); + break; + + case K_KENTER: // + c = CAR; + // FALLTHROUGH + case CAR: + case NL: +#if defined(FEAT_QUICKFIX) + // In a quickfix window a jumps to the error under the + // cursor. + if (bt_quickfix(curbuf) && c == CAR) + { + if (curwin->w_llist_ref == NULL) // quickfix window + do_cmdline_cmd((char_u *)".cc"); + else // location list window + do_cmdline_cmd((char_u *)".ll"); + break; + } +#endif + if (cmdwin_type != 0) + { + // Execute the command in the cmdline window. + cmdwin_result = CAR; + goto doESCkey; + } +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(curbuf)) + { + invoke_prompt_callback(); + if (!bt_prompt(curbuf)) + // buffer changed to a non-prompt buffer, get out of + // Insert mode + goto doESCkey; + break; + } +#endif + if (ins_eol(c) == FAIL && !p_im) + goto doESCkey; // out of memory + auto_format(FALSE, FALSE); + inserted_space = FALSE; + break; + + case Ctrl_K: // digraph or keyword completion + if (ctrl_x_mode_dictionary()) + { + if (has_compl_option(TRUE)) + goto docomplete; + break; + } +#ifdef FEAT_DIGRAPHS + c = ins_digraph(); + if (c == NUL) + break; +#endif + goto normalchar; + + case Ctrl_X: // Enter CTRL-X mode + ins_ctrl_x(); + break; + + case Ctrl_RSB: // Tag name completion after ^X + if (!ctrl_x_mode_tags()) + goto normalchar; + goto docomplete; + + case Ctrl_F: // File name completion after ^X + if (!ctrl_x_mode_files()) + goto normalchar; + goto docomplete; + + case 's': // Spelling completion after ^X + case Ctrl_S: + if (!ctrl_x_mode_spell()) + goto normalchar; + goto docomplete; + + case Ctrl_L: // Whole line completion after ^X + if (!ctrl_x_mode_whole_line()) + { + // CTRL-L with 'insertmode' set: Leave Insert mode + if (p_im) + { + if (echeck_abbr(Ctrl_L + ABBR_OFF)) + break; + goto doESCkey; + } + goto normalchar; + } + // FALLTHROUGH + + case Ctrl_P: // Do previous/next pattern completion + case Ctrl_N: + // if 'complete' is empty then plain ^P is no longer special, + // but it is under other ^X modes + if (*curbuf->b_p_cpt == NUL + && (ctrl_x_mode_normal() || ctrl_x_mode_whole_line()) + && !compl_status_local()) + goto normalchar; + +docomplete: + compl_busy = TRUE; +#ifdef FEAT_FOLDING + disable_fold_update++; // don't redraw folds here +#endif + if (ins_complete(c, TRUE) == FAIL) + compl_status_clear(); +#ifdef FEAT_FOLDING + disable_fold_update--; +#endif + compl_busy = FALSE; + can_si = may_do_si(); // allow smartindenting + break; + + case Ctrl_Y: // copy from previous line or scroll down + case Ctrl_E: // copy from next line or scroll up + c = ins_ctrl_ey(c); + break; + + default: +#ifdef UNIX + if (c == intr_char) // special interrupt char + goto do_intr; +#endif + +normalchar: + /* + * Insert a normal character. + */ +#if defined(FEAT_EVAL) + if (!p_paste) + { + // Trigger InsertCharPre. + char_u *str = do_insert_char_pre(c); + char_u *p; + + if (str != NULL) + { + if (*str != NUL && stop_arrow() != FAIL) + { + // Insert the new value of v:char literally. + for (p = str; *p != NUL; MB_PTR_ADV(p)) + { + c = PTR2CHAR(p); + if (c == CAR || c == K_KENTER || c == NL) + ins_eol(c); + else + ins_char(c); + } + AppendToRedobuffLit(str, -1); + } + vim_free(str); + c = NUL; + } + + // If the new value is already inserted or an empty string + // then don't insert any character. + if (c == NUL) + break; + } +#endif + // Try to perform smart-indenting. + ins_try_si(c); + + if (c == ' ') + { + inserted_space = TRUE; + if (inindent(0)) + can_cindent = FALSE; + if (Insstart_blank_vcol == MAXCOL + && curwin->w_cursor.lnum == Insstart.lnum) + Insstart_blank_vcol = get_nolist_virtcol(); + } + + // Insert a normal character and check for abbreviations on a + // special character. Let CTRL-] expand abbreviations without + // inserting it. + if (vim_iswordc(c) || (!echeck_abbr( + // Add ABBR_OFF for characters above 0x100, this is + // what check_abbr() expects. + (has_mbyte && c >= 0x100) ? (c + ABBR_OFF) : c) + && c != Ctrl_RSB)) + { + insert_special(c, FALSE, FALSE); +#ifdef FEAT_RIGHTLEFT + revins_legal++; + revins_chars++; +#endif + } + + auto_format(FALSE, TRUE); + +#ifdef FEAT_FOLDING + // When inserting a character the cursor line must never be in a + // closed fold. + foldOpenCursor(); +#endif + break; + } // end of switch (c) + + // If typed something may trigger CursorHoldI again. + if (c != K_CURSORHOLD +#ifdef FEAT_COMPL_FUNC + // but not in CTRL-X mode, a script can't restore the state + && ctrl_x_mode_normal() +#endif + ) + did_cursorhold = FALSE; + + // If the cursor was moved we didn't just insert a space + if (arrow_used) + inserted_space = FALSE; + + if (can_cindent && cindent_on() && ctrl_x_mode_normal()) + { +force_cindent: + /* + * Indent now if a key was typed that is in 'cinkeys'. + */ + if (in_cinkeys(c, ' ', line_is_white)) + { + if (stop_arrow() == OK) + // re-indent the current line + do_c_expr_indent(); + } + } + + } // for (;;) + // NOTREACHED +} + + int +ins_need_undo_get(void) +{ + return ins_need_undo; +} + +/* + * Redraw for Insert mode. + * This is postponed until getting the next character to make '$' in the 'cpo' + * option work correctly. + * Only redraw when there are no characters available. This speeds up + * inserting sequences of characters (e.g., for CTRL-R). + */ + void +ins_redraw(int ready) // not busy with something +{ +#ifdef FEAT_CONCEAL + linenr_T conceal_old_cursor_line = 0; + linenr_T conceal_new_cursor_line = 0; + int conceal_update_lines = FALSE; +#endif + + if (char_avail()) + return; + + // Trigger CursorMoved if the cursor moved. Not when the popup menu is + // visible, the command might delete it. + if (ready && (has_cursormovedI() +# ifdef FEAT_PROP_POPUP + || popup_visible +# endif +# if defined(FEAT_CONCEAL) + || curwin->w_p_cole > 0 +# endif + ) + && !EQUAL_POS(last_cursormoved, curwin->w_cursor) + && !pum_visible()) + { +# ifdef FEAT_SYN_HL + // Need to update the screen first, to make sure syntax + // highlighting is correct after making a change (e.g., inserting + // a "(". The autocommand may also require a redraw, so it's done + // again below, unfortunately. + if (syntax_present(curwin) && must_redraw) + update_screen(0); +# endif + if (has_cursormovedI()) + { + // Make sure curswant is correct, an autocommand may call + // getcurpos(). + update_curswant(); + ins_apply_autocmds(EVENT_CURSORMOVEDI); + } +#ifdef FEAT_PROP_POPUP + if (popup_visible) + popup_check_cursor_pos(); +#endif +# ifdef FEAT_CONCEAL + if (curwin->w_p_cole > 0) + { + conceal_old_cursor_line = last_cursormoved.lnum; + conceal_new_cursor_line = curwin->w_cursor.lnum; + conceal_update_lines = TRUE; + } +# endif + last_cursormoved = curwin->w_cursor; + } + + // Trigger TextChangedI if b_changedtick_i differs. + if (ready && has_textchangedI() + && curbuf->b_last_changedtick_i != CHANGEDTICK(curbuf) + && !pum_visible()) + { + aco_save_T aco; + varnumber_T tick = CHANGEDTICK(curbuf); + + // Save and restore curwin and curbuf, in case the autocmd changes + // them. + aucmd_prepbuf(&aco, curbuf); + apply_autocmds(EVENT_TEXTCHANGEDI, NULL, NULL, FALSE, curbuf); + aucmd_restbuf(&aco); + curbuf->b_last_changedtick_i = CHANGEDTICK(curbuf); + if (tick != CHANGEDTICK(curbuf)) // see ins_apply_autocmds() + u_save(curwin->w_cursor.lnum, + (linenr_T)(curwin->w_cursor.lnum + 1)); + } + + // Trigger TextChangedP if b_changedtick_pum differs. When the popupmenu + // closes TextChangedI will need to trigger for backwards compatibility, + // thus use different b_last_changedtick* variables. + if (ready && has_textchangedP() + && curbuf->b_last_changedtick_pum != CHANGEDTICK(curbuf) + && pum_visible()) + { + aco_save_T aco; + varnumber_T tick = CHANGEDTICK(curbuf); + + // Save and restore curwin and curbuf, in case the autocmd changes + // them. + aucmd_prepbuf(&aco, curbuf); + apply_autocmds(EVENT_TEXTCHANGEDP, NULL, NULL, FALSE, curbuf); + aucmd_restbuf(&aco); + curbuf->b_last_changedtick_pum = CHANGEDTICK(curbuf); + if (tick != CHANGEDTICK(curbuf)) // see ins_apply_autocmds() + u_save(curwin->w_cursor.lnum, + (linenr_T)(curwin->w_cursor.lnum + 1)); + } + + if (ready) + may_trigger_win_scrolled_resized(); + + // Trigger SafeState if nothing is pending. + may_trigger_safestate(ready + && !ins_compl_active() + && !pum_visible()); + +#if defined(FEAT_CONCEAL) + if ((conceal_update_lines + && (conceal_old_cursor_line != conceal_new_cursor_line + || conceal_cursor_line(curwin))) + || need_cursor_line_redraw) + { + if (conceal_old_cursor_line != conceal_new_cursor_line) + redrawWinline(curwin, conceal_old_cursor_line); + redrawWinline(curwin, conceal_new_cursor_line == 0 + ? curwin->w_cursor.lnum : conceal_new_cursor_line); + curwin->w_valid &= ~VALID_CROW; + need_cursor_line_redraw = FALSE; + } +#endif + if (must_redraw) + update_screen(0); + else if (clear_cmdline || redraw_cmdline) + showmode(); // clear cmdline and show mode + showruler(FALSE); + setcursor(); + emsg_on_display = FALSE; // may remove error message now +} + +/* + * Handle a CTRL-V or CTRL-Q typed in Insert mode. + */ + static void +ins_ctrl_v(void) +{ + int c; + int did_putchar = FALSE; + + // may need to redraw when no more chars available now + ins_redraw(FALSE); + + if (redrawing() && !char_avail()) + { + edit_putchar('^', TRUE); + did_putchar = TRUE; + } + AppendToRedobuff((char_u *)CTRL_V_STR); // CTRL-V + + add_to_showcmd_c(Ctrl_V); + + // Do not change any modifyOtherKeys ESC sequence to a normal key for + // CTRL-SHIFT-V. + c = get_literal(mod_mask & MOD_MASK_SHIFT); + if (did_putchar) + // when the line fits in 'columns' the '^' is at the start of the next + // line and will not removed by the redraw + edit_unputchar(); + clear_showcmd(); + + insert_special(c, FALSE, TRUE); +#ifdef FEAT_RIGHTLEFT + revins_chars++; + revins_legal++; +#endif +} + +/* + * After getting an ESC or CSI for a literal key: If the typeahead buffer + * contains a modifyOtherKeys sequence then decode it and return the result. + * Otherwise return "c". + * Note that this doesn't wait for characters, they must be in the typeahead + * buffer already. + */ + static int +decodeModifyOtherKeys(int c) +{ + char_u *p = typebuf.tb_buf + typebuf.tb_off; + int idx; + int form = 0; + int argidx = 0; + int arg[2] = {0, 0}; + + // Recognize: + // form 0: {lead}{key};{modifier}u + // form 1: {lead}27;{modifier};{key}~ + if (typebuf.tb_len >= 4 && (c == CSI || (c == ESC && *p == '['))) + { + idx = (*p == '['); + if (p[idx] == '2' && p[idx + 1] == '7' && p[idx + 2] == ';') + { + form = 1; + idx += 3; + } + while (idx < typebuf.tb_len && argidx < 2) + { + if (p[idx] == ';') + ++argidx; + else if (VIM_ISDIGIT(p[idx])) + arg[argidx] = arg[argidx] * 10 + (p[idx] - '0'); + else + break; + ++idx; + } + if (idx < typebuf.tb_len + && p[idx] == (form == 1 ? '~' : 'u') + && argidx == 1) + { + // Match, consume the code. + typebuf.tb_off += idx + 1; + typebuf.tb_len -= idx + 1; +#if defined(FEAT_CLIENTSERVER) || defined(FEAT_EVAL) + if (typebuf.tb_len == 0) + typebuf_was_filled = FALSE; +#endif + + mod_mask = decode_modifiers(arg[!form]); + c = merge_modifyOtherKeys(arg[form], &mod_mask); + } + } + + return c; +} + +/* + * Put a character directly onto the screen. It's not stored in a buffer. + * Used while handling CTRL-K, CTRL-V, etc. in Insert mode. + */ +static int pc_status; +#define PC_STATUS_UNSET 0 // pc_bytes was not set +#define PC_STATUS_RIGHT 1 // right half of double-wide char +#define PC_STATUS_LEFT 2 // left half of double-wide char +#define PC_STATUS_SET 3 // pc_bytes was filled +static char_u pc_bytes[MB_MAXBYTES + 1]; // saved bytes +static int pc_attr; +static int pc_row; +static int pc_col; + + void +edit_putchar(int c, int highlight) +{ + int attr; + + if (ScreenLines == NULL) + return; + + update_topline(); // just in case w_topline isn't valid + validate_cursor(); + if (highlight) + attr = HL_ATTR(HLF_8); + else + attr = 0; + pc_row = W_WINROW(curwin) + curwin->w_wrow; + pc_col = curwin->w_wincol; + pc_status = PC_STATUS_UNSET; +#ifdef FEAT_RIGHTLEFT + if (curwin->w_p_rl) + { + pc_col += curwin->w_width - 1 - curwin->w_wcol; + if (has_mbyte) + { + int fix_col = mb_fix_col(pc_col, pc_row); + + if (fix_col != pc_col) + { + screen_putchar(' ', pc_row, fix_col, attr); + --curwin->w_wcol; + pc_status = PC_STATUS_RIGHT; + } + } + } + else +#endif + { + pc_col += curwin->w_wcol; + if (mb_lefthalve(pc_row, pc_col)) + pc_status = PC_STATUS_LEFT; + } + + // save the character to be able to put it back + if (pc_status == PC_STATUS_UNSET) + { + screen_getbytes(pc_row, pc_col, pc_bytes, &pc_attr); + pc_status = PC_STATUS_SET; + } + screen_putchar(c, pc_row, pc_col, attr); +} + +#if defined(FEAT_JOB_CHANNEL) || defined(PROTO) +/* + * Set the insert start position for when using a prompt buffer. + */ + void +set_insstart(linenr_T lnum, int col) +{ + Insstart.lnum = lnum; + Insstart.col = col; + Insstart_orig = Insstart; + Insstart_textlen = Insstart.col; + Insstart_blank_vcol = MAXCOL; + arrow_used = FALSE; +} +#endif + +/* + * Undo the previous edit_putchar(). + */ + void +edit_unputchar(void) +{ + if (pc_status != PC_STATUS_UNSET && pc_row >= msg_scrolled) + { + if (pc_status == PC_STATUS_RIGHT) + ++curwin->w_wcol; + if (pc_status == PC_STATUS_RIGHT || pc_status == PC_STATUS_LEFT) + redrawWinline(curwin, curwin->w_cursor.lnum); + else + screen_puts(pc_bytes, pc_row - msg_scrolled, pc_col, pc_attr); + } +} + +/* + * Called when "$" is in 'cpoptions': display a '$' at the end of the changed + * text. Only works when cursor is in the line that changes. + */ + void +display_dollar(colnr_T col_arg) +{ + colnr_T col = col_arg < 0 ? 0 : col_arg; + colnr_T save_col; + + if (!redrawing()) + return; + + cursor_off(); + save_col = curwin->w_cursor.col; + curwin->w_cursor.col = col; + if (has_mbyte) + { + char_u *p; + + // If on the last byte of a multi-byte move to the first byte. + p = ml_get_curline(); + curwin->w_cursor.col -= (*mb_head_off)(p, p + col); + } + curs_columns(FALSE); // recompute w_wrow and w_wcol + if (curwin->w_wcol < curwin->w_width) + { + edit_putchar('$', FALSE); + 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(void) +{ + if (dollar_vcol < 0) + return; + + dollar_vcol = -1; + redrawWinline(curwin, curwin->w_cursor.lnum); +} + +/* + * Truncate the space at the end of a line. This is to be used only in an + * insert mode. It handles fixing the replace stack for MODE_REPLACE and + * MODE_VREPLACE modes. + */ + void +truncate_spaces(char_u *line) +{ + int i; + + // find start of trailing white space + for (i = (int)STRLEN(line) - 1; i >= 0 && VIM_ISWHITE(line[i]); i--) + { + if (State & REPLACE_FLAG) + replace_join(0); // remove a NUL from the replace stack + } + line[i + 1] = NUL; +} + +/* + * Backspace the cursor until the given column. Handles MODE_REPLACE and + * MODE_VREPLACE modes correctly. May also be used when not in insert mode at + * all. Will attempt not to go before "col" even when there is a composing + * character. + */ + void +backspace_until_column(int col) +{ + while ((int)curwin->w_cursor.col > col) + { + curwin->w_cursor.col--; + if (State & REPLACE_FLAG) + replace_do_bs(col); + else if (!del_char_after_col(col)) + break; + } +} + +/* + * Like del_char(), but make sure not to go before column "limit_col". + * Only matters when there are composing characters. + * Return TRUE when something was deleted. + */ + static int +del_char_after_col(int limit_col UNUSED) +{ + if (enc_utf8 && limit_col >= 0) + { + colnr_T ecol = curwin->w_cursor.col + 1; + + // Make sure the cursor is at the start of a character, but + // skip forward again when going too far back because of a + // composing character. + mb_adjust_cursor(); + while (curwin->w_cursor.col < (colnr_T)limit_col) + { + int l = utf_ptr2len(ml_get_cursor()); + + if (l == 0) // end of line + break; + curwin->w_cursor.col += l; + } + if (*ml_get_cursor() == NUL || curwin->w_cursor.col == ecol) + return FALSE; + del_bytes((long)((int)ecol - curwin->w_cursor.col), FALSE, TRUE); + } + else + (void)del_char(FALSE); + return TRUE; +} + +/* + * 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(). + * For Unicode a character > 255 may be returned. + * If "noReduceKeys" is TRUE do not change any modifyOtherKeys ESC sequence + * into a normal key, return ESC. + */ + int +get_literal(int noReduceKeys) +{ + int cc; + int nc; + int i; + int hex = FALSE; + int octal = FALSE; + int unicode = 0; + + if (got_int) + return Ctrl_C; + +#ifdef FEAT_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; + if (noReduceKeys) + ++no_reduce_keys; + } +#endif +#ifdef USE_ON_FLY_SCROLL + dont_scroll = TRUE; // disallow scrolling here +#endif + ++no_mapping; // don't map the next key hits + cc = 0; + i = 0; + for (;;) + { + nc = plain_vgetc(); + if ((nc == ESC || nc == CSI) && !noReduceKeys) + nc = decodeModifyOtherKeys(nc); + + if ((mod_mask & ~MOD_MASK_SHIFT) != 0) + // A character with non-Shift modifiers should not be a valid + // character for i_CTRL-V_digit. + break; + + if ((State & MODE_CMDLINE) == 0 && MB_BYTE2LEN_CHECK(nc) == 1) + add_to_showcmd(nc); + if (nc == 'x' || nc == 'X') + hex = TRUE; + else if (nc == 'o' || nc == 'O') + octal = TRUE; + else if (nc == 'u' || nc == 'U') + unicode = nc; + else + { + if (hex || unicode != 0) + { + if (!vim_isxdigit(nc)) + break; + cc = cc * 16 + hex2nr(nc); + } + else if (octal) + { + if (nc < '0' || nc > '7') + break; + cc = cc * 8 + nc - '0'; + } + else + { + if (!VIM_ISDIGIT(nc)) + break; + cc = cc * 10 + nc - '0'; + } + + ++i; + } + + if (cc > 255 && unicode == 0) + cc = 255; // limit range to 0-255 + nc = 0; + + if (hex) // hex: up to two chars + { + if (i >= 2) + break; + } + else if (unicode) // Unicode: up to four or eight chars + { + if ((unicode == 'u' && i >= 4) || (unicode == 'U' && i >= 8)) + break; + } + else if (i >= 3) // decimal or octal: up to three chars + break; + } + 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'; + if (enc_dbcs && (cc & 0xff) == 0) + cc = '?'; // don't accept an illegal DBCS char, the NUL in the + // second byte will cause trouble! + + --no_mapping; +#ifdef FEAT_GUI + if (gui.in_use) + { + --allow_keys; + if (noReduceKeys) + --no_reduce_keys; + } +#endif + if (nc) + { + vungetc(nc); + // A character typed with i_CTRL-V_digit cannot have modifiers. + mod_mask = 0; + } + 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( + int c, + int allow_modmask, + int ctrlv) // c was typed after CTRL-V +{ + 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. + */ +#ifdef MACOS_X + // Command-key never produces a normal key + if (mod_mask & MOD_MASK_CMD) + allow_modmask = TRUE; +#endif + if (IS_SPECIAL(c) || (mod_mask && allow_modmask)) + { + p = get_special_key_name(c, mod_mask); + len = (int)STRLEN(p); + c = p[len - 1]; + if (len > 2) + { + if (stop_arrow() == FAIL) + return; + p[len - 1] = NUL; + ins_str(p); + AppendToRedobuffLit(p, -1); + ctrlv = FALSE; + } + } + if (stop_arrow() == OK) + insertchar(c, ctrlv ? INSCHAR_CTRLV : 0, -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. + * '0' and '^' are special, because they can be followed by CTRL-D. + */ +#define ISSPECIAL(c) ((c) < ' ' || (c) >= DEL || (c) == '0' || (c) == '^') + +/* + * "flags": INSCHAR_FORMAT - force formatting + * INSCHAR_CTRLV - char typed just after CTRL-V + * INSCHAR_NO_FEX - don't use 'formatexpr' + * + * NOTE: passes the flags value straight through to internal_format() which, + * beside INSCHAR_FORMAT (above), is also looking for these: + * INSCHAR_DO_COM - format comments + * INSCHAR_COM_LIST - format comments with num list or 2nd line indent + */ + void +insertchar( + int c, // character to insert or NUL + int flags, // INSCHAR_FORMAT, etc. + int second_indent) // indent for second line if >= 0 +{ + int textwidth; + char_u *p; + int fo_ins_blank; + int force_format = flags & INSCHAR_FORMAT; + + textwidth = comp_textwidth(force_format); + 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. + * - Always do this when 'formatoptions' has the 'a' flag and the line + * ends in white space. + * - Otherwise: + * - Don't do this if inserting a blank + * - Don't do this if an existing character is being replaced, unless + * we're in MODE_VREPLACE state. + * - 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 (textwidth > 0 + && (force_format + || (!VIM_ISWHITE(c) + && !((State & REPLACE_FLAG) + && !(State & VREPLACE_FLAG) + && *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 + )))))) + { + // Format with 'formatexpr' when it's set. Use internal formatting + // when 'formatexpr' isn't set or it returns non-zero. +#if defined(FEAT_EVAL) + int do_internal = TRUE; + colnr_T virtcol = get_nolist_virtcol() + + char2cells(c != NUL ? c : gchar_cursor()); + + if (*curbuf->b_p_fex != NUL && (flags & INSCHAR_NO_FEX) == 0 + && (force_format || virtcol > (colnr_T)textwidth)) + { + do_internal = (fex_format(curwin->w_cursor.lnum, 1L, c) != 0); + // It may be required to save for undo again, e.g. when setline() + // was called. + ins_need_undo = TRUE; + } + if (do_internal) +#endif + internal_format(textwidth, second_indent, flags, c == NUL, c); + } + + if (c == NUL) // only formatting was wanted + return; + + // Check whether this character should end a comment. + if (did_ai && c == end_comment_pending) + { + char_u *line; + char_u lead_end[COM_MAX_LEN]; // end-comment string + int middle_len, end_len; + int i; + + /* + * Need to remove existing (middle) comment leader and insert end + * comment leader. First, check what comment leader we can find. + */ + i = get_leader_len(line = ml_get_curline(), &p, FALSE, TRUE); + if (i > 0 && vim_strchr(p, COM_MIDDLE) != NULL) // Just checking + { + // Skip middle-comment string + while (*p && p[-1] != ':') // find end of middle flags + ++p; + middle_len = copy_option_part(&p, lead_end, COM_MAX_LEN, ","); + // Don't count trailing white space for middle_len + while (middle_len > 0 && VIM_ISWHITE(lead_end[middle_len - 1])) + --middle_len; + + // Find the end-comment string + while (*p && p[-1] != ':') // find end of end flags + ++p; + end_len = copy_option_part(&p, lead_end, COM_MAX_LEN, ","); + + // Skip white space before the cursor + i = curwin->w_cursor.col; + while (--i >= 0 && VIM_ISWHITE(line[i])) + ; + i++; + + // Skip to before the middle leader + i -= middle_len; + + // Check some expected things before we go on + if (i >= 0 && lead_end[end_len - 1] == end_comment_pending) + { + // Backspace over all the stuff we want to replace + backspace_until_column(i); + + // Insert the end-comment string, except for the last + // character, which will get inserted as normal later. + ins_bytes_len(lead_end, end_len - 1); + } + } + } + end_comment_pending = NUL; + + 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. + * Don't do this when 'cindent' or 'indentexpr' is set, because we might + * need to re-indent at a ':', or any other character (but not what + * 'paste' is set).. + * Don't do this when there an InsertCharPre autocommand is defined, + * because we need to fire the event for every character. + * Do the check for InsertCharPre before the call to vpeekc() because the + * InsertCharPre autocommand could change the input buffer. + */ +#ifdef USE_ON_FLY_SCROLL + dont_scroll = FALSE; // allow scrolling here +#endif + + if ( !ISSPECIAL(c) + && (!has_mbyte || (*mb_char2len)(c) == 1) + && !has_insertcharpre() + && vpeekc() != NUL + && !(State & REPLACE_FLAG) + && !cindent_on() +#ifdef FEAT_RIGHTLEFT + && !p_ri +#endif + ) + { +#define INPUT_BUFLEN 100 + char_u buf[INPUT_BUFLEN + 1]; + int i; + colnr_T virtcol = 0; + + buf[0] = c; + i = 1; + if (textwidth > 0) + virtcol = get_nolist_virtcol(); + /* + * Stop the string when: + * - no more chars available + * - finding a special character (command key) + * - buffer is full + * - running into the 'textwidth' boundary + * - need to check for abbreviation: A non-word char after a word-char + */ + while ( (c = vpeekc()) != NUL + && !ISSPECIAL(c) + && (!has_mbyte || MB_BYTE2LEN_CHECK(c) == 1) + && i < INPUT_BUFLEN + && (textwidth == 0 + || (virtcol += byte2cells(buf[i - 1])) < (colnr_T)textwidth) + && !(!no_abbr && !vim_iswordc(c) && vim_iswordc(buf[i - 1]))) + { +#ifdef FEAT_RIGHTLEFT + c = vgetc(); + if (p_hkmap && KeyTyped) + c = hkmap(c); // Hebrew mode mapping + buf[i++] = c; +#else + buf[i++] = vgetc(); +#endif + } + +#ifdef FEAT_DIGRAPHS + do_digraph(-1); // clear digraphs + do_digraph(buf[i-1]); // may be the start of a digraph +#endif + buf[i] = NUL; + ins_str(buf); + if (flags & INSCHAR_CTRLV) + { + redo_literal(*buf); + i = 1; + } + else + i = 0; + if (buf[i] != NUL) + AppendToRedobuffLit(buf + i, -1); + } + else + { + int cc; + + if (has_mbyte && (cc = (*mb_char2len)(c)) > 1) + { + char_u buf[MB_MAXBYTES + 1]; + + (*mb_char2bytes)(c, buf); + buf[cc] = NUL; + ins_char_bytes(buf, cc); + AppendCharToRedobuff(c); + } + else + { + ins_char(c); + if (flags & INSCHAR_CTRLV) + redo_literal(c); + else + AppendCharToRedobuff(c); + } + } +} + +/* + * Put a character in the redo buffer, for when just after a CTRL-V. + */ + static void +redo_literal(int c) +{ + char_u buf[10]; + + // Only digits need special treatment. Translate them into a string of + // three digits. + if (VIM_ISDIGIT(c)) + { + vim_snprintf((char *)buf, sizeof(buf), "%03d", c); + AppendToRedobuff(buf); + } + else + AppendCharToRedobuff(c); +} + +/* + * start_arrow() is called when an arrow key is used in insert mode. + * For undo/redo it resembles hitting the key. + */ + void +start_arrow( + pos_T *end_insert_pos) // can be NULL +{ + start_arrow_common(end_insert_pos, TRUE); +} + +/* + * Like start_arrow() but with end_change argument. + * Will prepare for redo of CTRL-G U if "end_change" is FALSE. + */ + static void +start_arrow_with_change( + pos_T *end_insert_pos, // can be NULL + int end_change) // end undoable change +{ + start_arrow_common(end_insert_pos, end_change); + if (!end_change) + { + AppendCharToRedobuff(Ctrl_G); + AppendCharToRedobuff('U'); + } +} + + static void +start_arrow_common( + pos_T *end_insert_pos, // can be NULL + int end_change) // end undoable change +{ + if (!arrow_used && end_change) // something has been inserted + { + AppendToRedobuff(ESC_STR); + stop_insert(end_insert_pos, FALSE, FALSE); + arrow_used = TRUE; // this means we stopped the current insert + } +#ifdef FEAT_SPELL + check_spell_redraw(); +#endif +} + +#ifdef FEAT_SPELL +/* + * If we skipped highlighting word at cursor, do it now. + * It may be skipped again, thus reset spell_redraw_lnum first. + */ + static void +check_spell_redraw(void) +{ + if (spell_redraw_lnum != 0) + { + linenr_T lnum = spell_redraw_lnum; + + spell_redraw_lnum = 0; + redrawWinline(curwin, lnum); + } +} + +#endif + +/* + * stop_arrow() is called before a change is made in insert mode. + * If an arrow key has been used, start a new insertion. + * Returns FAIL if undo is impossible, shouldn't insert then. + */ + int +stop_arrow(void) +{ + if (arrow_used) + { + Insstart = curwin->w_cursor; // new insertion starts here + if (Insstart.col > Insstart_orig.col && !ins_need_undo) + // Don't update the original insert position when moved to the + // right, except when nothing was inserted yet. + update_Insstart_orig = FALSE; + Insstart_textlen = (colnr_T)linetabsize_str(ml_get_curline()); + + if (u_save_cursor() == OK) + { + arrow_used = FALSE; + ins_need_undo = FALSE; + } + + ai_col = 0; + if (State & VREPLACE_FLAG) + { + orig_line_count = curbuf->b_ml.ml_line_count; + vr_lines_changed = 1; + } + ResetRedobuff(); + AppendToRedobuff((char_u *)"1i"); // pretend we start an insertion + new_insert_skip = 2; + } + else if (ins_need_undo) + { + if (u_save_cursor() == OK) + ins_need_undo = FALSE; + } + +#ifdef FEAT_FOLDING + // Always open fold at the cursor line when inserting something. + foldOpenCursor(); +#endif + + return (arrow_used || ins_need_undo ? FAIL : OK); +} + +/* + * Do a few things to stop inserting. + * "end_insert_pos" is where insert ended. It is NULL when we already jumped + * to another window/buffer. + */ + static void +stop_insert( + pos_T *end_insert_pos, + int esc, // called by ins_esc() + int nomove) // , don't move cursor +{ + int cc; + char_u *ptr; + + stop_redo_ins(); + replace_flush(); // abandon replace stack + + /* + * Save the inserted text for later redo with ^@ and CTRL-A. + * Don't do it when "restart_edit" was set and nothing was inserted, + * otherwise CTRL-O w and then will clear "last_insert". + */ + ptr = get_inserted(); + int added = ptr == NULL ? 0 : (int)STRLEN(ptr) - new_insert_skip; + if (did_restart_edit == 0 || added > 0) + { + vim_free(last_insert); + last_insert = ptr; + last_insert_skip = added < 0 ? 0 : new_insert_skip; + } + else + vim_free(ptr); + + if (!arrow_used && end_insert_pos != NULL) + { + // Auto-format now. It may seem strange to do this when stopping an + // insertion (or moving the cursor), but it's required when appending + // a line and having it end in a space. But only do it when something + // was actually inserted, otherwise undo won't work. + if (!ins_need_undo && has_format_option(FO_AUTO)) + { + pos_T tpos = curwin->w_cursor; + + // When the cursor is at the end of the line after a space the + // formatting will move it to the following word. Avoid that by + // moving the cursor onto the space. + cc = 'x'; + if (curwin->w_cursor.col > 0 && gchar_cursor() == NUL) + { + dec_cursor(); + cc = gchar_cursor(); + if (!VIM_ISWHITE(cc)) + curwin->w_cursor = tpos; + } + + auto_format(TRUE, FALSE); + + if (VIM_ISWHITE(cc)) + { + if (gchar_cursor() != NUL) + inc_cursor(); + // If the cursor is still at the same character, also keep + // the "coladd". + if (gchar_cursor() == NUL + && curwin->w_cursor.lnum == tpos.lnum + && curwin->w_cursor.col == tpos.col) + curwin->w_cursor.coladd = tpos.coladd; + } + } + + // If a space was inserted for auto-formatting, remove it now. + check_auto_format(TRUE); + + // If we just did an auto-indent, remove the white space from the end + // of the line, and put the cursor back. + // Do this when ESC was used or moving the cursor up/down. + // Check for the old position still being valid, just in case the text + // got changed unexpectedly. + if (!nomove && did_ai && (esc || (vim_strchr(p_cpo, CPO_INDENT) == NULL + && curwin->w_cursor.lnum != end_insert_pos->lnum)) + && end_insert_pos->lnum <= curbuf->b_ml.ml_line_count) + { + pos_T tpos = curwin->w_cursor; + + curwin->w_cursor = *end_insert_pos; + check_cursor_col(); // make sure it is not past the line + for (;;) + { + if (gchar_cursor() == NUL && curwin->w_cursor.col > 0) + --curwin->w_cursor.col; + cc = gchar_cursor(); + if (!VIM_ISWHITE(cc)) + break; + if (del_char(TRUE) == FAIL) + break; // should not happen + } + if (curwin->w_cursor.lnum != tpos.lnum) + curwin->w_cursor = tpos; + else + { + // reset tpos, could have been invalidated in the loop above + tpos = curwin->w_cursor; + tpos.col++; + if (cc != NUL && gchar_pos(&tpos) == NUL) + ++curwin->w_cursor.col; // put cursor back on the NUL + } + + // may have started Visual mode, adjust the position for + // deleted characters. + if (VIsual_active) + check_visual_pos(); + } + } + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + + // Set '[ and '] to the inserted text. When end_insert_pos is NULL we are + // now in a different buffer. + if (end_insert_pos != NULL) + { + curbuf->b_op_start = Insstart; + curbuf->b_op_start_orig = Insstart_orig; + 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(int c) +{ + char_u *s; + + vim_free(last_insert); + last_insert = alloc(MB_MAXBYTES * 3 + 5); + if (last_insert == NULL) + return; + + s = last_insert; + // Use the CTRL-V only when entering a special char + if (c < ' ' || c == DEL) + *s++ = Ctrl_V; + s = add_char2buf(c, s); + *s++ = ESC; + *s++ = NUL; + last_insert_skip = 0; +} + +#if defined(EXITFREE) || defined(PROTO) + void +free_last_insert(void) +{ + VIM_CLEAR(last_insert); +} +#endif + +/* + * Add character "c" to buffer "s". Escape the special meaning of K_SPECIAL + * and CSI. Handle multi-byte characters. + * Returns a pointer to after the added bytes. + */ + char_u * +add_char2buf(int c, char_u *s) +{ + char_u temp[MB_MAXBYTES + 1]; + int i; + int len; + + len = (*mb_char2bytes)(c, temp); + for (i = 0; i < len; ++i) + { + c = temp[i]; + // Need to escape K_SPECIAL and CSI like in the typeahead buffer. + if (c == K_SPECIAL) + { + *s++ = K_SPECIAL; + *s++ = KS_SPECIAL; + *s++ = KE_FILLER; + } +#ifdef FEAT_GUI + else if (c == CSI) + { + *s++ = CSI; + *s++ = KS_EXTRA; + *s++ = (int)KE_CSI; + } +#endif + else + *s++ = c; + } + return s; +} + +/* + * move cursor to start of line + * if flags & BL_WHITE move to first non-white + * if flags & BL_SOL move to first non-white if startofline is set, + * otherwise keep "curswant" column + * if flags & BL_FIX don't leave the cursor on a NUL. + */ + void +beginline(int flags) +{ + if ((flags & BL_SOL) && !p_sol) + coladvance(curwin->w_curswant); + else + { + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + + if (flags & (BL_WHITE | BL_SOL)) + { + char_u *ptr; + + for (ptr = ml_get_curline(); VIM_ISWHITE(*ptr) + && !((flags & BL_FIX) && ptr[1] == NUL); ++ptr) + ++curwin->w_cursor.col; + } + curwin->w_set_curswant = TRUE; + } + adjust_skipcol(); +} + +/* + * oneright oneleft cursor_down cursor_up + * + * Move one char {right,left,down,up}. + * Doesn't move onto the NUL past the end of the line, unless it is allowed. + * Return OK when successful, FAIL when we hit a line of file boundary. + */ + + int +oneright(void) +{ + char_u *ptr; + int l; + + if (virtual_active()) + { + pos_T prevpos = curwin->w_cursor; + + // Adjust for multi-wide char (excluding TAB) + ptr = ml_get_cursor(); + coladvance(getviscol() + ((*ptr != TAB + && vim_isprintc((*mb_ptr2char)(ptr))) + ? ptr2cells(ptr) : 1)); + curwin->w_set_curswant = TRUE; + // Return OK if the cursor moved, FAIL otherwise (at window edge). + return (prevpos.col != curwin->w_cursor.col + || prevpos.coladd != curwin->w_cursor.coladd) ? OK : FAIL; + } + + ptr = ml_get_cursor(); + if (*ptr == NUL) + return FAIL; // already at the very end + + if (has_mbyte) + l = (*mb_ptr2len)(ptr); + else + l = 1; + + // move "l" bytes right, but don't end up on the NUL, unless 'virtualedit' + // contains "onemore". + if (ptr[l] == NUL && (get_ve_flags() & VE_ONEMORE) == 0) + return FAIL; + curwin->w_cursor.col += l; + + curwin->w_set_curswant = TRUE; + adjust_skipcol(); + return OK; +} + + int +oneleft(void) +{ + if (virtual_active()) + { +#ifdef FEAT_LINEBREAK + int width; +#endif + int v = getviscol(); + + if (v == 0) + return FAIL; + +#ifdef FEAT_LINEBREAK + // We might get stuck on 'showbreak', skip over it. + width = 1; + for (;;) + { + coladvance(v - width); + // getviscol() is slow, skip it when 'showbreak' is empty, + // 'breakindent' is not set and there are no multi-byte + // characters + if ((*get_showbreak_value(curwin) == NUL && !curwin->w_p_bri + && !has_mbyte) || getviscol() < v) + break; + ++width; + } +#else + coladvance(v - 1); +#endif + + if (curwin->w_cursor.coladd == 1) + { + char_u *ptr; + + // Adjust for multi-wide char (not a TAB) + ptr = ml_get_cursor(); + if (*ptr != TAB && vim_isprintc((*mb_ptr2char)(ptr)) + && ptr2cells(ptr) > 1) + curwin->w_cursor.coladd = 0; + } + + curwin->w_set_curswant = TRUE; + adjust_skipcol(); + return OK; + } + + if (curwin->w_cursor.col == 0) + return FAIL; + + curwin->w_set_curswant = TRUE; + --curwin->w_cursor.col; + + // if the character on the left of the current cursor is a multi-byte + // character, move to its first byte + if (has_mbyte) + mb_adjust_cursor(); + adjust_skipcol(); + return OK; +} + +/* + * Move the cursor up "n" lines in window "wp". + * Takes care of closed folds. + */ + void +cursor_up_inner(win_T *wp, long n) +{ + linenr_T lnum = wp->w_cursor.lnum; + + if (n >= lnum) + lnum = 1; + else +#ifdef FEAT_FOLDING + if (hasAnyFolding(wp)) + { + /* + * Count each sequence of folded lines as one logical line. + */ + // go to the start of the current fold + (void)hasFoldingWin(wp, lnum, &lnum, NULL, TRUE, NULL); + + while (n--) + { + // move up one line + --lnum; + if (lnum <= 1) + break; + // If we entered a fold, move to the beginning, unless in + // Insert mode or when 'foldopen' contains "all": it will open + // in a moment. + if (n > 0 || !((State & MODE_INSERT) || (fdo_flags & FDO_ALL))) + (void)hasFoldingWin(wp, lnum, &lnum, NULL, TRUE, NULL); + } + if (lnum < 1) + lnum = 1; + } + else +#endif + lnum -= n; + + wp->w_cursor.lnum = lnum; +} + + int +cursor_up( + long n, + int upd_topline) // When TRUE: update topline +{ + // This fails if the cursor is already in the first line or the count is + // larger than the line number and '-' is in 'cpoptions' + linenr_T lnum = curwin->w_cursor.lnum; + if (n > 0 && (lnum <= 1 + || (n >= lnum && vim_strchr(p_cpo, CPO_MINUS) != NULL))) + return FAIL; + cursor_up_inner(curwin, n); + + // try to advance to the column we want to be at + coladvance(curwin->w_curswant); + + if (upd_topline) + update_topline(); // make sure curwin->w_topline is valid + + return OK; +} + +/* + * Move the cursor down "n" lines in window "wp". + * Takes care of closed folds. + */ + void +cursor_down_inner(win_T *wp, long n) +{ + linenr_T lnum = wp->w_cursor.lnum; + linenr_T line_count = wp->w_buffer->b_ml.ml_line_count; + + if (lnum + n >= line_count) + lnum = line_count; + else +#ifdef FEAT_FOLDING + if (hasAnyFolding(wp)) + { + linenr_T last; + + // count each sequence of folded lines as one logical line + while (n--) + { + // Move to last line of fold, will fail if it's the end-of-file. + if (hasFoldingWin(wp, lnum, NULL, &last, TRUE, NULL)) + lnum = last + 1; + else + ++lnum; + if (lnum >= line_count) + break; + } + if (lnum > line_count) + lnum = line_count; + } + else +#endif + lnum += n; + + wp->w_cursor.lnum = lnum; +} + +/* + * Cursor down a number of logical lines. + */ + int +cursor_down( + long n, + int upd_topline) // When TRUE: update topline +{ + linenr_T lnum = curwin->w_cursor.lnum; + linenr_T line_count = curwin->w_buffer->b_ml.ml_line_count; + // This fails if the cursor is already in the last line or would move + // beyond the last line and '-' is in 'cpoptions' + if (n > 0 + && (lnum >= line_count + || (lnum + n > line_count + && vim_strchr(p_cpo, CPO_MINUS) != NULL))) + return FAIL; + cursor_down_inner(curwin, n); + + // try to advance to the column we want to be at + coladvance(curwin->w_curswant); + + if (upd_topline) + update_topline(); // make sure curwin->w_topline is valid + + return OK; +} + +/* + * 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( + int c, // Command character to be inserted + long count, // Repeat this many times + int no_esc) // Don't add an ESC at the end +{ + char_u *esc_ptr; + char_u *ptr; + char_u *last_ptr; + char_u last = NUL; + + ptr = get_last_insert(); + if (ptr == NULL) + { + emsg(_(e_no_inserted_text_yet)); + return FAIL; + } + + // may want to stuff the command character, to start Insert mode + if (c != NUL) + stuffcharReadbuff(c); + if ((esc_ptr = vim_strrchr(ptr, ESC)) != NULL) + *esc_ptr = NUL; // remove the ESC + + // when the last char is either "0" or "^" it will be quoted if no ESC + // comes after it OR if it will inserted more than once and "ptr" + // starts with ^D. -- Acevedo + last_ptr = (esc_ptr ? esc_ptr : ptr + STRLEN(ptr)) - 1; + if (last_ptr >= ptr && (*last_ptr == '0' || *last_ptr == '^') + && (no_esc || (*ptr == Ctrl_D && count > 1))) + { + last = *last_ptr; + *last_ptr = NUL; + } + + do + { + stuffReadbuff(ptr); + // a trailing "0" is inserted as "048", "^" as "^" + if (last) + stuffReadbuff( + (char_u *)(last == '0' ? "\026\060\064\070" : "\026^")); + } + while (--count > 0); + + if (last) + *last_ptr = last; + + if (esc_ptr != NULL) + *esc_ptr = ESC; // put the ESC back + + // may want to stuff a trailing ESC, to get out of Insert mode + if (!no_esc) + stuffcharReadbuff(ESC); + + return OK; +} + + char_u * +get_last_insert(void) +{ + if (last_insert == NULL) + return NULL; + return last_insert + last_insert_skip; +} + +/* + * Get last inserted string, and remove trailing . + * Returns pointer to allocated memory (must be freed) or NULL. + */ + char_u * +get_last_insert_save(void) +{ + char_u *s; + int len; + + if (last_insert == NULL) + return NULL; + s = vim_strsave(last_insert + last_insert_skip); + if (s == NULL) + return NULL; + + len = (int)STRLEN(s); + if (len > 0 && s[len - 1] == ESC) // remove trailing ESC + s[len - 1] = NUL; + return s; +} + +/* + * 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.tb_buf[], followed by "c". + */ + static int +echeck_abbr(int c) +{ + // Don't check for abbreviation in paste mode, when disabled and just + // after moving around with cursor keys. + if (p_paste || no_abbr || arrow_used) + 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 characters are remembered for each + * new character. This is used to re-insert the old text when backspacing. + * + * There is a NUL headed list of characters for each character that is + * currently in the file after the insertion point. When BS is used, one NUL + * headed list is put back for the deleted character. + * + * For a newline, there are two NUL headed lists. One contains the characters + * that the NL replaced. The extra one stores the characters after the cursor + * that were deleted (always white space). + * + * 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. + */ + +static char_u *replace_stack = NULL; +static long replace_stack_nr = 0; // next entry in replace stack +static long replace_stack_len = 0; // max. number of entries + + void +replace_push( + 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 = ALLOC_MULT(char_u, replace_stack_len); + if (p == NULL) // out of memory + { + replace_stack_len -= 50; + return; + } + if (replace_stack != NULL) + { + mch_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) + mch_memmove(p + 1, p, (size_t)(replace_offset * sizeof(char_u))); + *p = c; + ++replace_stack_nr; +} + +/* + * Push a character onto the replace stack. Handles a multi-byte character in + * reverse byte order, so that the first byte is popped off first. + * Return the number of bytes done (includes composing characters). + */ + int +replace_push_mb(char_u *p) +{ + int l = (*mb_ptr2len)(p); + int j; + + for (j = l - 1; j >= 0; --j) + replace_push(p[j]); + return l; +} + +/* + * Pop one item from the replace stack. + * return -1 if stack empty + * return replaced character or NUL otherwise + */ + static int +replace_pop(void) +{ + if (replace_stack_nr == 0) + return -1; + return (int)replace_stack[--replace_stack_nr]; +} + +/* + * Join the top two items on the replace stack. This removes to "off"'th NUL + * encountered. + */ + void +replace_join( + int off) // offset for which NUL to remove +{ + int i; + + for (i = replace_stack_nr; --i >= 0; ) + if (replace_stack[i] == NUL && off-- <= 0) + { + --replace_stack_nr; + mch_memmove(replace_stack + i, replace_stack + i + 1, + (size_t)(replace_stack_nr - i)); + return; + } +} + +/* + * Pop bytes from the replace stack until a NUL is found, and insert them + * before the cursor. Can only be used in MODE_REPLACE or MODE_VREPLACE state. + */ + static void +replace_pop_ins(void) +{ + int cc; + int oldState = State; + + State = MODE_NORMAL; // don't want MODE_REPLACE here + while ((cc = replace_pop()) > 0) + { + mb_replace_pop_ins(cc); + dec_cursor(); + } + State = oldState; +} + +/* + * Insert bytes popped from the replace stack. "cc" is the first byte. If it + * indicates a multi-byte char, pop the other bytes too. + */ + static void +mb_replace_pop_ins(int cc) +{ + int n; + char_u buf[MB_MAXBYTES + 1]; + int i; + int c; + + if (has_mbyte && (n = MB_BYTE2LEN(cc)) > 1) + { + buf[0] = cc; + for (i = 1; i < n; ++i) + buf[i] = replace_pop(); + ins_bytes_len(buf, n); + } + else + ins_char(cc); + + if (enc_utf8) + // Handle composing chars. + for (;;) + { + c = replace_pop(); + if (c == -1) // stack empty + break; + if ((n = MB_BYTE2LEN(c)) == 1) + { + // Not a multi-byte char, put it back. + replace_push(c); + break; + } + + buf[0] = c; + for (i = 1; i < n; ++i) + buf[i] = replace_pop(); + if (utf_iscomposing(utf_ptr2char(buf))) + ins_bytes_len(buf, n); + else + { + // Not a composing char, put it back. + for (i = n - 1; i >= 0; --i) + replace_push(buf[i]); + break; + } + + } +} + +/* + * make the replace stack empty + * (called when exiting replace mode) + */ + static void +replace_flush(void) +{ + VIM_CLEAR(replace_stack); + replace_stack_len = 0; + replace_stack_nr = 0; +} + +/* + * Handle doing a BS for one character. + * cc < 0: replace stack empty, just move cursor + * cc == 0: character was inserted, delete it + * cc > 0: character was replaced, put cc (first byte of original char) back + * and check for more characters to be put back + * When "limit_col" is >= 0, don't delete before this column. Matters when + * using composing characters, use del_char_after_col() instead of del_char(). + */ + static void +replace_do_bs(int limit_col) +{ + int cc; + int orig_len = 0; + int ins_len; + int orig_vcols = 0; + colnr_T start_vcol; + char_u *p; + int i; + int vcol; + + cc = replace_pop(); + if (cc > 0) + { +#ifdef FEAT_PROP_POPUP + size_t len_before = 0; // init to shut up GCC + + if (curbuf->b_has_textprop) + { + // Do not adjust text properties for individual delete and insert + // operations, do it afterwards on the resulting text. + len_before = STRLEN(ml_get_curline()); + ++text_prop_frozen; + } +#endif + if (State & VREPLACE_FLAG) + { + // Get the number of screen cells used by the character we are + // going to delete. + getvcol(curwin, &curwin->w_cursor, NULL, &start_vcol, NULL); + orig_vcols = chartabsize(ml_get_cursor(), start_vcol); + } + if (has_mbyte) + { + (void)del_char_after_col(limit_col); + if (State & VREPLACE_FLAG) + orig_len = (int)STRLEN(ml_get_cursor()); + replace_push(cc); + } + else + { + pchar_cursor(cc); + if (State & VREPLACE_FLAG) + orig_len = (int)STRLEN(ml_get_cursor()) - 1; + } + replace_pop_ins(); + + if (State & VREPLACE_FLAG) + { + // Get the number of screen cells used by the inserted characters + p = ml_get_cursor(); + ins_len = (int)STRLEN(p) - orig_len; + vcol = start_vcol; + for (i = 0; i < ins_len; ++i) + { + vcol += chartabsize(p + i, vcol); + i += (*mb_ptr2len)(p) - 1; + } + vcol -= start_vcol; + + // Delete spaces that were inserted after the cursor to keep the + // text aligned. + curwin->w_cursor.col += ins_len; + while (vcol > orig_vcols && gchar_cursor() == ' ') + { + del_char(FALSE); + ++orig_vcols; + } + curwin->w_cursor.col -= ins_len; + } + + // mark the buffer as changed and prepare for displaying + changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); + +#ifdef FEAT_PROP_POPUP + if (curbuf->b_has_textprop) + { + size_t len_now = STRLEN(ml_get_curline()); + + --text_prop_frozen; + adjust_prop_columns(curwin->w_cursor.lnum, curwin->w_cursor.col, + (int)(len_now - len_before), 0); + } +#endif + } + else if (cc == 0) + (void)del_char_after_col(limit_col); +} + +#if defined(FEAT_RIGHTLEFT) || defined(PROTO) +/* + * Map Hebrew keyboard when in hkmap mode. + */ + int +hkmap(int c) +{ + if (p_hkmapp) // phonetic mapping, by Ilya Dogolazky + { + enum {hALEF=0, BET, GIMEL, DALET, HEI, VAV, ZAIN, HET, TET, IUD, + KAFsofit, hKAF, LAMED, MEMsofit, MEM, NUNsofit, NUN, SAMEH, AIN, + PEIsofit, PEI, ZADIsofit, ZADI, KOF, RESH, hSHIN, TAV}; + static char_u map[26] = + {(char_u)hALEF/*a*/, (char_u)BET /*b*/, (char_u)hKAF /*c*/, + (char_u)DALET/*d*/, (char_u)-1 /*e*/, (char_u)PEIsofit/*f*/, + (char_u)GIMEL/*g*/, (char_u)HEI /*h*/, (char_u)IUD /*i*/, + (char_u)HET /*j*/, (char_u)KOF /*k*/, (char_u)LAMED /*l*/, + (char_u)MEM /*m*/, (char_u)NUN /*n*/, (char_u)SAMEH /*o*/, + (char_u)PEI /*p*/, (char_u)-1 /*q*/, (char_u)RESH /*r*/, + (char_u)ZAIN /*s*/, (char_u)TAV /*t*/, (char_u)TET /*u*/, + (char_u)VAV /*v*/, (char_u)hSHIN/*w*/, (char_u)-1 /*x*/, + (char_u)AIN /*y*/, (char_u)ZADI /*z*/}; + + if (c == 'N' || c == 'M' || c == 'P' || c == 'C' || c == 'Z') + return (int)(map[CharOrd(c)] - 1 + p_aleph); + // '-1'='sofit' + else if (c == 'x') + return 'X'; + else if (c == 'q') + return '\''; // {geresh}={'} + else if (c == 246) + return ' '; // \"o --> ' ' for a german keyboard + else if (c == 228) + return ' '; // \"a --> ' ' -- / -- + else if (c == 252) + return ' '; // \"u --> ' ' -- / -- + // NOTE: islower() does not do the right thing for us on Linux so we + // do this the same was as 5.7 and previous, so it works correctly on + // all systems. Specifically, the e.g. Delete and Arrow keys are + // munged and won't work if e.g. searching for Hebrew text. + else if (c >= 'a' && c <= 'z') + return (int)(map[CharOrdLow(c)] + p_aleph); + else + return c; + } + else + { + 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[CharOrdLow(c)]; + break; + } + } + + return (int)(CharOrdLow(c) + p_aleph); + } +} +#endif + + static void +ins_reg(void) +{ + int need_redraw = FALSE; + int regname; + int literally = 0; + int vis_active = VIsual_active; + + /* + * If we are going to wait for a character, show a '"'. + */ + pc_status = PC_STATUS_UNSET; + if (redrawing() && !char_avail()) + { + // may need to redraw when no more chars available now + ins_redraw(FALSE); + + edit_putchar('"', TRUE); + add_to_showcmd_c(Ctrl_R); + } + +#ifdef USE_ON_FLY_SCROLL + dont_scroll = TRUE; // disallow scrolling here +#endif + + /* + * Don't map the register name. This also prevents the mode message to be + * deleted when ESC is hit. + */ + ++no_mapping; + ++allow_keys; + regname = plain_vgetc(); + LANGMAP_ADJUST(regname, TRUE); + if (regname == Ctrl_R || regname == Ctrl_O || regname == Ctrl_P) + { + // Get a third key for literal register insertion + literally = regname; + add_to_showcmd_c(literally); + regname = plain_vgetc(); + LANGMAP_ADJUST(regname, TRUE); + } + --no_mapping; + --allow_keys; + +#ifdef FEAT_EVAL + // Don't call u_sync() while typing the expression or giving an error + // message for it. Only call it explicitly. + ++no_u_sync; + if (regname == '=') + { + pos_T curpos = curwin->w_cursor; +# ifdef HAVE_INPUT_METHOD + int im_on = im_get_status(); +# endif + // Sync undo when evaluating the expression calls setline() or + // append(), so that it can be undone separately. + u_sync_once = 2; + + regname = get_expr_register(); + + // Cursor may be moved back a column. + curwin->w_cursor = curpos; + check_cursor(); +# ifdef HAVE_INPUT_METHOD + // Restore the Input Method. + if (im_on) + im_set_active(TRUE); +# endif + } + if (regname == NUL || !valid_yank_reg(regname, FALSE)) + { + vim_beep(BO_REG); + need_redraw = TRUE; // remove the '"' + } + else + { +#endif + if (literally == Ctrl_O || literally == Ctrl_P) + { + // Append the command to the redo buffer. + AppendCharToRedobuff(Ctrl_R); + AppendCharToRedobuff(literally); + AppendCharToRedobuff(regname); + + do_put(regname, NULL, BACKWARD, 1L, + (literally == Ctrl_P ? PUT_FIXINDENT : 0) | PUT_CURSEND); + } + else if (insert_reg(regname, literally) == FAIL) + { + vim_beep(BO_REG); + need_redraw = TRUE; // remove the '"' + } + else if (stop_insert_mode) + // When the '=' register was used and a function was invoked that + // did ":stopinsert" then stuff_empty() returns FALSE but we won't + // insert anything, need to remove the '"' + need_redraw = TRUE; + +#ifdef FEAT_EVAL + } + --no_u_sync; + if (u_sync_once == 1) + ins_need_undo = TRUE; + u_sync_once = 0; +#endif + clear_showcmd(); + + // If the inserted register is empty, we need to remove the '"' + if (need_redraw || stuff_empty()) + edit_unputchar(); + + // Disallow starting Visual mode here, would get a weird mode. + if (!vis_active && VIsual_active) + end_visual_mode(); +} + +/* + * CTRL-G commands in Insert mode. + */ + static void +ins_ctrl_g(void) +{ + int c; + + // Right after CTRL-X the cursor will be after the ruler. + setcursor(); + + /* + * Don't map the second key. This also prevents the mode message to be + * deleted when ESC is hit. + */ + ++no_mapping; + ++allow_keys; + c = plain_vgetc(); + --no_mapping; + --allow_keys; + switch (c) + { + // CTRL-G k and CTRL-G : cursor up to Insstart.col + case K_UP: + case Ctrl_K: + case 'k': ins_up(TRUE); + break; + + // CTRL-G j and CTRL-G : cursor down to Insstart.col + case K_DOWN: + case Ctrl_J: + case 'j': ins_down(TRUE); + break; + + // CTRL-G u: start new undoable edit + case 'u': u_sync(TRUE); + ins_need_undo = TRUE; + + // Need to reset Insstart, esp. because a BS that joins + // a line to the previous one must save for undo. + update_Insstart_orig = FALSE; + Insstart = curwin->w_cursor; + break; + + // CTRL-G U: do not break undo with the next char + case 'U': + // Allow one left/right cursor movement with the next char, + // without breaking undo. + dont_sync_undo = MAYBE; + break; + + case ESC: + // Esc after CTRL-G cancels it. + break; + + // Unknown CTRL-G command, reserved for future expansion. + default: vim_beep(BO_CTRLG); + } +} + +/* + * CTRL-^ in Insert mode. + */ + static void +ins_ctrl_hat(void) +{ + if (map_to_exists_mode((char_u *)"", MODE_LANGMAP, FALSE)) + { + // ":lmap" mappings exists, Toggle use of ":lmap" mappings. + if (State & MODE_LANGMAP) + { + curbuf->b_p_iminsert = B_IMODE_NONE; + State &= ~MODE_LANGMAP; + } + else + { + curbuf->b_p_iminsert = B_IMODE_LMAP; + State |= MODE_LANGMAP; +#ifdef HAVE_INPUT_METHOD + im_set_active(FALSE); +#endif + } + } +#ifdef HAVE_INPUT_METHOD + else + { + // There are no ":lmap" mappings, toggle IM + if (im_get_status()) + { + curbuf->b_p_iminsert = B_IMODE_NONE; + im_set_active(FALSE); + } + else + { + curbuf->b_p_iminsert = B_IMODE_IM; + State &= ~MODE_LANGMAP; + im_set_active(TRUE); + } + } +#endif + set_iminsert_global(); + showmode(); +#ifdef FEAT_GUI + // may show different cursor shape or color + if (gui.in_use) + gui_update_cursor(TRUE, FALSE); +#endif +#if defined(FEAT_KEYMAP) + // Show/unshow value of 'keymap' in status lines. + status_redraw_curbuf(); +#endif +} + +/* + * Handle ESC in insert mode. + * Returns TRUE when leaving insert mode, FALSE when going to repeat the + * insert. + */ + static int +ins_esc( + long *count, + int cmdchar, + int nomove) // don't move cursor +{ + int temp; + static int disabled_redraw = FALSE; +#ifdef FEAT_CONCEAL + // Remember if the cursor line was concealed before changing State. + int cursor_line_was_concealed = curwin->w_p_cole > 0 + && conceal_cursor_line(curwin); +#endif + +#ifdef FEAT_SPELL + check_spell_redraw(); +#endif + + temp = curwin->w_cursor.col; + if (disabled_redraw) + { + if (RedrawingDisabled > 0) + --RedrawingDisabled; + disabled_redraw = FALSE; + } + if (!arrow_used) + { + /* + * Don't append the ESC for "r" and "grx". + * When 'insertmode' is set only CTRL-L stops Insert mode. Needed for + * when "count" is non-zero. + */ + if (cmdchar != 'r' && cmdchar != 'v') + AppendToRedobuff(p_im ? (char_u *)"\014" : ESC_STR); + + /* + * Repeating insert may take a long time. Check for + * interrupt now and then. + */ + if (*count > 0) + { + line_breakcheck(); + if (got_int) + *count = 0; + } + + if (--*count > 0) // repeat what was typed + { + // Vi repeats the insert without replacing characters. + if (vim_strchr(p_cpo, CPO_REPLCNT) != NULL) + State &= ~REPLACE_FLAG; + + (void)start_redo_ins(); + if (cmdchar == 'r' || cmdchar == 'v') + stuffRedoReadbuff(ESC_STR); // no ESC in redo buffer + ++RedrawingDisabled; + disabled_redraw = TRUE; + return FALSE; // repeat the insert + } + stop_insert(&curwin->w_cursor, TRUE, nomove); + undisplay_dollar(); + } + + if (cmdchar != 'r' && cmdchar != 'v') + ins_apply_autocmds(EVENT_INSERTLEAVEPRE); + + // When an autoindent was removed, curswant stays after the + // indent + if (restart_edit == NUL && (colnr_T)temp == curwin->w_cursor.col) + curwin->w_set_curswant = TRUE; + + // Remember the last Insert position in the '^ mark. + if ((cmdmod.cmod_flags & CMOD_KEEPJUMPS) == 0) + curbuf->b_last_insert = curwin->w_cursor; + + /* + * The cursor should end up on the last inserted character. + * Don't do it for CTRL-O, unless past the end of the line. + */ + if (!nomove + && (curwin->w_cursor.col != 0 + || curwin->w_cursor.coladd > 0) + && (restart_edit == NUL + || (gchar_cursor() == NUL && !VIsual_active)) +#ifdef FEAT_RIGHTLEFT + && !revins_on +#endif + ) + { + if (curwin->w_cursor.coladd > 0 || get_ve_flags() == VE_ALL) + { + oneleft(); + if (restart_edit != NUL) + ++curwin->w_cursor.coladd; + } + else + { + --curwin->w_cursor.col; + curwin->w_valid &= ~(VALID_WCOL|VALID_VIRTCOL); + // Correct cursor for multi-byte character. + if (has_mbyte) + mb_adjust_cursor(); + } + } + +#ifdef HAVE_INPUT_METHOD + // Disable IM to allow typing English directly for Normal mode commands. + // When ":lmap" is enabled don't change 'iminsert' (IM can be enabled as + // well). + if (!(State & MODE_LANGMAP)) + im_save_status(&curbuf->b_p_iminsert); + im_set_active(FALSE); +#endif + + State = MODE_NORMAL; + may_trigger_modechanged(); + // need to position cursor again when on a TAB and when on a char with + // virtual text. + if (gchar_cursor() == TAB +#ifdef FEAT_PROP_POPUP + || curbuf->b_has_textprop +#endif + ) + curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL); + + setmouse(); +#ifdef CURSOR_SHAPE + ui_cursor_shape(); // may show different cursor shape +#endif + if (!p_ek) + { + MAY_WANT_TO_LOG_THIS; + + // Re-enable bracketed paste mode. + out_str_t_BE(); + + // Re-enable modifyOtherKeys. + out_str_t_TI(); + } +#ifdef FEAT_CONCEAL + // Check if the cursor line needs redrawing after changing State. If + // 'concealcursor' is "i" it needs to be redrawn without concealing. + conceal_check_cursor_line(cursor_line_was_concealed); +#endif + + // When recording or for CTRL-O, need to display the new mode. + // Otherwise remove the mode message. + if (reg_recording != 0 || restart_edit != NUL) + showmode(); + else if (p_smd && (got_int || !skip_showmode())) + msg(""); + + return TRUE; // exit Insert mode +} + +#ifdef FEAT_RIGHTLEFT +/* + * Toggle language: hkmap and revins_on. + * Move to end of reverse inserted text. + */ + static void +ins_ctrl_(void) +{ + if (revins_on && revins_chars && revins_scol >= 0) + { + while (gchar_cursor() != NUL && revins_chars--) + ++curwin->w_cursor.col; + } + p_ri = !p_ri; + revins_on = (State == MODE_INSERT && p_ri); + if (revins_on) + { + revins_scol = curwin->w_cursor.col; + revins_legal++; + revins_chars = 0; + undisplay_dollar(); + } + else + revins_scol = -1; + p_hkmap = curwin->w_p_rl ^ p_ri; // be consistent! + showmode(); +} +#endif + +/* + * If 'keymodel' contains "startsel", may start selection. + * Returns TRUE when a CTRL-O and other keys stuffed. + */ + static int +ins_start_select(int c) +{ + if (!km_startsel) + return FALSE; + switch (c) + { + case K_KHOME: + case K_KEND: + case K_PAGEUP: + case K_KPAGEUP: + case K_PAGEDOWN: + case K_KPAGEDOWN: +# ifdef MACOS_X + case K_LEFT: + case K_RIGHT: + case K_UP: + case K_DOWN: + case K_END: + case K_HOME: +# endif + if (!(mod_mask & MOD_MASK_SHIFT)) + break; + // FALLTHROUGH + case K_S_LEFT: + case K_S_RIGHT: + case K_S_UP: + case K_S_DOWN: + case K_S_END: + case K_S_HOME: + // Start selection right away, the cursor can move with CTRL-O when + // beyond the end of the line. + start_selection(); + + // Execute the key in (insert) Select mode. + stuffcharReadbuff(Ctrl_O); + if (mod_mask) + { + char_u buf[4]; + + buf[0] = K_SPECIAL; + buf[1] = KS_MODIFIER; + buf[2] = mod_mask; + buf[3] = NUL; + stuffReadbuff(buf); + } + stuffcharReadbuff(c); + return TRUE; + } + return FALSE; +} + +/* + * key in Insert mode: toggle insert/replace mode. + */ + static void +ins_insert(int replaceState) +{ +#ifdef FEAT_EVAL + set_vim_var_string(VV_INSERTMODE, + (char_u *)((State & REPLACE_FLAG) ? "i" + : replaceState == MODE_VREPLACE ? "v" : "r"), 1); +#endif + ins_apply_autocmds(EVENT_INSERTCHANGE); + if (State & REPLACE_FLAG) + State = MODE_INSERT | (State & MODE_LANGMAP); + else + State = replaceState | (State & MODE_LANGMAP); + may_trigger_modechanged(); + AppendCharToRedobuff(K_INS); + showmode(); +#ifdef CURSOR_SHAPE + ui_cursor_shape(); // may show different cursor shape +#endif +} + +/* + * Pressed CTRL-O in Insert mode. + */ + static void +ins_ctrl_o(void) +{ + restart_VIsual_select = 0; + if (State & VREPLACE_FLAG) + restart_edit = 'V'; + else if (State & REPLACE_FLAG) + restart_edit = 'R'; + else + restart_edit = 'I'; + if (virtual_active()) + ins_at_eol = FALSE; // cursor always keeps its column + else + ins_at_eol = (gchar_cursor() == NUL); +} + +/* + * 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 'shiftwidth', this is compatible + * with vi. But vi only supports ^T and ^D after an + * autoindent, we support it everywhere. + */ + static void +ins_shift(int c, int lastc) +{ + if (stop_arrow() == FAIL) + return; + AppendCharToRedobuff(c); + + /* + * 0^D and ^^D: remove all indent. + */ + if (c == Ctrl_D && (lastc == '0' || lastc == '^') + && curwin->w_cursor.col > 0) + { + --curwin->w_cursor.col; + (void)del_char(FALSE); // delete the '^' or '0' + // In Replace mode, restore the characters that '^' or '0' replaced. + if (State & REPLACE_FLAG) + replace_pop_ins(); + if (lastc == '^') + old_indent = get_indent(); // remember curr. indent + change_indent(INDENT_SET, 0, TRUE, 0, TRUE); + } + else + change_indent(c == Ctrl_D ? INDENT_DEC : INDENT_INC, 0, TRUE, 0, TRUE); + + if (did_ai && *skipwhite(ml_get_curline()) != NUL) + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + can_cindent = FALSE; // no cindenting after ^D or ^T +} + + static void +ins_del(void) +{ + int temp; + + if (stop_arrow() == FAIL) + return; + if (gchar_cursor() == NUL) // delete newline + { + temp = curwin->w_cursor.col; + if (!can_bs(BS_EOL) // only if "eol" included + || do_join(2, FALSE, TRUE, FALSE, FALSE) == FAIL) + vim_beep(BO_BS); + else + { + curwin->w_cursor.col = temp; + // Adjust orig_line_count in case more lines have been deleted than + // have been added. That makes sure, that open_line() later + // can access all buffer lines correctly + if (State & VREPLACE_FLAG && + orig_line_count > curbuf->b_ml.ml_line_count) + orig_line_count = curbuf->b_ml.ml_line_count; + } + } + else if (del_char(FALSE) == FAIL) // delete char under cursor + vim_beep(BO_BS); + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + AppendCharToRedobuff(K_DEL); +} + +/* + * Delete one character for ins_bs(). + */ + static void +ins_bs_one(colnr_T *vcolp) +{ + dec_cursor(); + getvcol(curwin, &curwin->w_cursor, vcolp, NULL, NULL); + if (State & REPLACE_FLAG) + { + // Don't delete characters before the insert point when in + // Replace mode + if (curwin->w_cursor.lnum != Insstart.lnum + || curwin->w_cursor.col >= Insstart.col) + replace_do_bs(-1); + } + else + (void)del_char(FALSE); +} + +/* + * Handle Backspace, delete-word and delete-line in Insert mode. + * Return TRUE when backspace was actually used. + */ + static int +ins_bs( + int c, + int mode, + int *inserted_space_p) +{ + linenr_T lnum; + int cc; + int temp = 0; // init for GCC + colnr_T save_col; + colnr_T mincol; + int did_backspace = FALSE; + int in_indent; + int oldState; + int cpc[MAX_MCO]; // composing characters + int call_fix_indent = FALSE; + + /* + * 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 FEAT_RIGHTLEFT + !revins_on && +#endif + ((curwin->w_cursor.lnum == 1 && curwin->w_cursor.col == 0) + || (!can_bs(BS_START) + && ((arrow_used +#ifdef FEAT_JOB_CHANNEL + && !bt_prompt(curbuf) +#endif + ) || (curwin->w_cursor.lnum == Insstart_orig.lnum + && curwin->w_cursor.col <= Insstart_orig.col))) + || (!can_bs(BS_INDENT) && !arrow_used && ai_col > 0 + && curwin->w_cursor.col <= ai_col) + || (!can_bs(BS_EOL) && curwin->w_cursor.col == 0)))) + { + vim_beep(BO_BS); + return FALSE; + } + + if (stop_arrow() == FAIL) + return FALSE; + in_indent = inindent(0); + if (in_indent) + can_cindent = FALSE; + end_comment_pending = NUL; // After BS, don't auto-end comment +#ifdef FEAT_RIGHTLEFT + if (revins_on) // put cursor after last inserted char + inc_cursor(); +#endif + + // Virtualedit: + // BACKSPACE_CHAR eats a virtual space + // BACKSPACE_WORD eats all coladd + // BACKSPACE_LINE eats all coladd and keeps going + if (curwin->w_cursor.coladd > 0) + { + if (mode == BACKSPACE_CHAR) + { + --curwin->w_cursor.coladd; + return TRUE; + } + if (mode == BACKSPACE_WORD) + { + curwin->w_cursor.coladd = 0; + return TRUE; + } + curwin->w_cursor.coladd = 0; + } + + /* + * Delete newline! + */ + if (curwin->w_cursor.col == 0) + { + lnum = Insstart.lnum; + if (curwin->w_cursor.lnum == lnum +#ifdef FEAT_RIGHTLEFT + || revins_on +#endif + ) + { + if (u_save((linenr_T)(curwin->w_cursor.lnum - 2), + (linenr_T)(curwin->w_cursor.lnum + 1)) == FAIL) + return FALSE; + --Insstart.lnum; + Insstart.col = (colnr_T)STRLEN(ml_get(Insstart.lnum)); + } + /* + * In replace mode: + * cc < 0: NL was inserted, delete it + * cc >= 0: NL was replaced, put original characters back + */ + cc = -1; + if (State & REPLACE_FLAG) + cc = replace_pop(); // returns -1 if NL was inserted + /* + * In replace mode, in the line we started replacing, we only move the + * cursor. + */ + if ((State & REPLACE_FLAG) && curwin->w_cursor.lnum <= lnum) + { + dec_cursor(); + } + else + { + if (!(State & VREPLACE_FLAG) + || curwin->w_cursor.lnum > orig_line_count) + { + temp = gchar_cursor(); // remember current char + --curwin->w_cursor.lnum; + + // When "aw" is in 'formatoptions' we must delete the space at + // the end of the line, otherwise the line will be broken + // again when auto-formatting. + if (has_format_option(FO_AUTO) + && has_format_option(FO_WHITE_PAR)) + { + char_u *ptr = ml_get_buf(curbuf, curwin->w_cursor.lnum, + TRUE); + int len; + + len = (int)STRLEN(ptr); + if (len > 0 && ptr[len - 1] == ' ') + ptr[len - 1] = NUL; + } + + (void)do_join(2, FALSE, FALSE, FALSE, FALSE); + if (temp == NUL && gchar_cursor() != NUL) + inc_cursor(); + } + else + dec_cursor(); + + /* + * In MODE_REPLACE mode we have to put back the text that was + * replaced by the NL. On the replace stack is first a + * NUL-terminated sequence of characters that were deleted and then + * the characters that NL replaced. + */ + if (State & REPLACE_FLAG) + { + /* + * Do the next ins_char() in MODE_NORMAL state, to + * prevent ins_char() from replacing characters and + * avoiding showmatch(). + */ + oldState = State; + State = MODE_NORMAL; + /* + * restore characters (blanks) deleted after cursor + */ + while (cc > 0) + { + save_col = curwin->w_cursor.col; + mb_replace_pop_ins(cc); + curwin->w_cursor.col = save_col; + cc = replace_pop(); + } + // restore the characters that NL replaced + replace_pop_ins(); + State = oldState; + } + } + did_ai = FALSE; + } + else + { + /* + * Delete character(s) before the cursor. + */ +#ifdef FEAT_RIGHTLEFT + if (revins_on) // put cursor on last inserted char + dec_cursor(); +#endif + mincol = 0; + // keep indent + if (mode == BACKSPACE_LINE + && (curbuf->b_p_ai || cindent_on()) +#ifdef FEAT_RIGHTLEFT + && !revins_on +#endif + ) + { + save_col = curwin->w_cursor.col; + beginline(BL_WHITE); + if (curwin->w_cursor.col < save_col) + { + mincol = curwin->w_cursor.col; + // should now fix the indent to match with the previous line + call_fix_indent = TRUE; + } + curwin->w_cursor.col = save_col; + } + + /* + * Handle deleting one 'shiftwidth' or 'softtabstop'. + */ + if ( mode == BACKSPACE_CHAR + && ((p_sta && in_indent) + || ((get_sts_value() != 0 +#ifdef FEAT_VARTABS + || tabstop_count(curbuf->b_p_vsts_array) +#endif + ) + && curwin->w_cursor.col > 0 + && (*(ml_get_cursor() - 1) == TAB + || (*(ml_get_cursor() - 1) == ' ' + && (!*inserted_space_p + || arrow_used)))))) + { + int ts; + colnr_T vcol; + colnr_T want_vcol; + colnr_T start_vcol; + + *inserted_space_p = FALSE; + // Compute the virtual column where we want to be. Since + // 'showbreak' may get in the way, need to get the last column of + // the previous character. + getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL); + start_vcol = vcol; + dec_cursor(); + getvcol(curwin, &curwin->w_cursor, NULL, NULL, &want_vcol); + inc_cursor(); +#ifdef FEAT_VARTABS + if (p_sta && in_indent) + { + ts = (int)get_sw_value(curbuf); + want_vcol = (want_vcol / ts) * ts; + } + else + want_vcol = tabstop_start(want_vcol, get_sts_value(), + curbuf->b_p_vsts_array); +#else + if (p_sta && in_indent) + ts = (int)get_sw_value(curbuf); + else + ts = (int)get_sts_value(); + want_vcol = (want_vcol / ts) * ts; +#endif + + // delete characters until we are at or before want_vcol + while (vcol > want_vcol && curwin->w_cursor.col > 0 + && (cc = *(ml_get_cursor() - 1), VIM_ISWHITE(cc))) + ins_bs_one(&vcol); + + // insert extra spaces until we are at want_vcol + while (vcol < want_vcol) + { + // Remember the first char we inserted + if (curwin->w_cursor.lnum == Insstart_orig.lnum + && curwin->w_cursor.col < Insstart_orig.col) + Insstart_orig.col = curwin->w_cursor.col; + + if (State & VREPLACE_FLAG) + ins_char(' '); + else + { + ins_str((char_u *)" "); + if ((State & REPLACE_FLAG)) + replace_push(NUL); + } + getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL); + } + + // If we are now back where we started delete one character. Can + // happen when using 'sts' and 'linebreak'. + if (vcol >= start_vcol) + ins_bs_one(&vcol); + } + + /* + * Delete up to starting point, start of line or previous word. + */ + else + { + int cclass = 0, prev_cclass = 0; + + if (has_mbyte) + cclass = mb_get_class(ml_get_cursor()); + do + { +#ifdef FEAT_RIGHTLEFT + if (!revins_on) // put cursor on char to be deleted +#endif + dec_cursor(); + + cc = gchar_cursor(); + // look multi-byte character class + if (has_mbyte) + { + prev_cclass = cclass; + cclass = mb_get_class(ml_get_cursor()); + } + + // start of word? + if (mode == BACKSPACE_WORD && !vim_isspace(cc)) + { + mode = BACKSPACE_WORD_NOT_SPACE; + temp = vim_iswordc(cc); + } + // end of word? + else if (mode == BACKSPACE_WORD_NOT_SPACE + && ((vim_isspace(cc) || vim_iswordc(cc) != temp) + || prev_cclass != cclass)) + { +#ifdef FEAT_RIGHTLEFT + if (!revins_on) +#endif + inc_cursor(); +#ifdef FEAT_RIGHTLEFT + else if (State & REPLACE_FLAG) + dec_cursor(); +#endif + break; + } + if (State & REPLACE_FLAG) + replace_do_bs(-1); + else + { + if (enc_utf8 && p_deco) + (void)utfc_ptr2char(ml_get_cursor(), cpc); + (void)del_char(FALSE); + /* + * If there are combining characters and 'delcombine' is set + * move the cursor back. Don't back up before the base + * character. + */ + if (enc_utf8 && p_deco && cpc[0] != NUL) + inc_cursor(); +#ifdef FEAT_RIGHTLEFT + if (revins_chars) + { + revins_chars--; + revins_legal++; + } + if (revins_on && gchar_cursor() == NUL) + break; +#endif + } + // Just a single backspace?: + if (mode == BACKSPACE_CHAR) + break; + } while ( +#ifdef FEAT_RIGHTLEFT + revins_on || +#endif + (curwin->w_cursor.col > mincol + && (can_bs(BS_NOSTOP) + || (curwin->w_cursor.lnum != Insstart_orig.lnum + || curwin->w_cursor.col != Insstart_orig.col) + ))); + } + did_backspace = TRUE; + } + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + if (curwin->w_cursor.col <= 1) + did_ai = FALSE; + + if (call_fix_indent) + fix_indent(); + + /* + * 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); + + // If deleted before the insertion point, adjust it + if (curwin->w_cursor.lnum == Insstart_orig.lnum + && curwin->w_cursor.col < Insstart_orig.col) + Insstart_orig.col = curwin->w_cursor.col; + + // vi behaviour: the cursor moves backward but the character that + // was there remains visible + // Vim behaviour: the cursor moves backward and the character that + // was there is erased from the screen. + // We can emulate the vi behaviour by pretending there is a dollar + // displayed even when there isn't. + // --pkv Sun Jan 19 01:56:40 EST 2003 + if (vim_strchr(p_cpo, CPO_BACKSPACE) != NULL && dollar_vcol == -1) + dollar_vcol = curwin->w_virtcol; + +#ifdef FEAT_FOLDING + // When deleting a char the cursor line must never be in a closed fold. + // E.g., when 'foldmethod' is indent and deleting the first non-white + // char before a Tab. + if (did_backspace) + foldOpenCursor(); +#endif + + return did_backspace; +} + +/* + * Handle receiving P_PS: start paste mode. Inserts the following text up to + * P_PE literally. + * When "drop" is TRUE then consume the text and drop it. + */ + int +bracketed_paste(paste_mode_T mode, int drop, garray_T *gap) +{ + int c; + char_u buf[NUMBUFLEN + MB_MAXBYTES]; + int idx = 0; + char_u *end = find_termcode((char_u *)"PE"); + int ret_char = -1; + int save_allow_keys = allow_keys; + int save_paste = p_paste; + + // If the end code is too long we can't detect it, read everything. + if (end != NULL && STRLEN(end) >= NUMBUFLEN) + end = NULL; + ++no_mapping; + allow_keys = 0; + if (!p_paste) + // Also have the side effects of setting 'paste' to make it work much + // faster. + set_option_value_give_err((char_u *)"paste", TRUE, NULL, 0); + + for (;;) + { + // When the end is not defined read everything there is. + if (end == NULL && vpeekc() == NUL) + break; + do + c = vgetc(); + while (c == K_IGNORE || c == K_VER_SCROLLBAR || c == K_HOR_SCROLLBAR); + + if (c == NUL || got_int || (ex_normal_busy > 0 && c == Ctrl_C)) + // When CTRL-C was encountered the typeahead will be flushed and we + // won't get the end sequence. Except when using ":normal". + break; + + if (has_mbyte) + idx += (*mb_char2bytes)(c, buf + idx); + else + buf[idx++] = c; + buf[idx] = NUL; + if (end != NULL && STRNCMP(buf, end, idx) == 0) + { + if (end[idx] == NUL) + break; // Found the end of paste code. + continue; + } + if (!drop) + { + switch (mode) + { + case PASTE_CMDLINE: + put_on_cmdline(buf, idx, TRUE); + break; + + case PASTE_EX: + // add one for the NUL that is going to be appended + if (gap != NULL && ga_grow(gap, idx + 1) == OK) + { + mch_memmove((char *)gap->ga_data + gap->ga_len, + buf, (size_t)idx); + gap->ga_len += idx; + } + break; + + case PASTE_INSERT: + if (stop_arrow() == OK) + { + c = buf[0]; + if (idx == 1 && (c == CAR || c == K_KENTER || c == NL)) + ins_eol(c); + else + { + ins_char_bytes(buf, idx); + AppendToRedobuffLit(buf, idx); + } + } + break; + + case PASTE_ONE_CHAR: + if (ret_char == -1) + { + if (has_mbyte) + ret_char = (*mb_ptr2char)(buf); + else + ret_char = buf[0]; + } + break; + } + } + idx = 0; + } + + --no_mapping; + allow_keys = save_allow_keys; + if (!save_paste) + set_option_value_give_err((char_u *)"paste", FALSE, NULL, 0); + + return ret_char; +} + +#if defined(FEAT_GUI_TABLINE) || defined(PROTO) + static void +ins_tabline(int c) +{ + // We will be leaving the current window, unless closing another tab. + if (c != K_TABMENU || current_tabmenu != TABLINE_MENU_CLOSE + || (current_tab != 0 && current_tab != tabpage_index(curtab))) + { + undisplay_dollar(); + start_arrow(&curwin->w_cursor); + can_cindent = TRUE; + } + + if (c == K_TABLINE) + goto_tabpage(current_tab); + else + { + handle_tabmenu(); + redraw_statuslines(); // will redraw the tabline when needed + } +} +#endif + +#if defined(FEAT_GUI) || defined(PROTO) + void +ins_scroll(void) +{ + pos_T tpos; + + undisplay_dollar(); + tpos = curwin->w_cursor; + if (gui_do_scroll()) + { + start_arrow(&tpos); + can_cindent = TRUE; + } +} + + void +ins_horscroll(void) +{ + pos_T tpos; + + undisplay_dollar(); + tpos = curwin->w_cursor; + if (do_mousescroll_horiz(scrollbar_value)) + { + start_arrow(&tpos); + can_cindent = TRUE; + } +} +#endif + + static void +ins_left(void) +{ + pos_T tpos; + int end_change = dont_sync_undo == FALSE; // end undoable change + +#ifdef FEAT_FOLDING + if ((fdo_flags & FDO_HOR) && KeyTyped) + foldOpenCursor(); +#endif + undisplay_dollar(); + tpos = curwin->w_cursor; + if (oneleft() == OK) + { +#if defined(FEAT_XIM) && defined(FEAT_GUI_GTK) + // Only call start_arrow() when not busy with preediting, it will + // break undo. K_LEFT is inserted in im_correct_cursor(). + if (p_imst == IM_OVER_THE_SPOT || !im_is_preediting()) +#endif + { + start_arrow_with_change(&tpos, end_change); + if (!end_change) + AppendCharToRedobuff(K_LEFT); + } +#ifdef FEAT_RIGHTLEFT + // If exit reversed string, position is fixed + if (revins_scol != -1 && (int)curwin->w_cursor.col >= revins_scol) + revins_legal++; + revins_chars++; +#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) + { + // always break undo when moving upwards/downwards, else undo may break + start_arrow(&tpos); + --(curwin->w_cursor.lnum); + coladvance((colnr_T)MAXCOL); + curwin->w_set_curswant = TRUE; // so we stay at the end + } + else + vim_beep(BO_CRSR); + dont_sync_undo = FALSE; +} + + static void +ins_home(int c) +{ + pos_T tpos; + +#ifdef FEAT_FOLDING + if ((fdo_flags & FDO_HOR) && KeyTyped) + foldOpenCursor(); +#endif + undisplay_dollar(); + tpos = curwin->w_cursor; + if (c == K_C_HOME) + curwin->w_cursor.lnum = 1; + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + curwin->w_curswant = 0; + start_arrow(&tpos); +} + + static void +ins_end(int c) +{ + pos_T tpos; + +#ifdef FEAT_FOLDING + if ((fdo_flags & FDO_HOR) && KeyTyped) + foldOpenCursor(); +#endif + undisplay_dollar(); + tpos = curwin->w_cursor; + if (c == K_C_END) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + coladvance((colnr_T)MAXCOL); + curwin->w_curswant = MAXCOL; + + start_arrow(&tpos); +} + + static void +ins_s_left(void) +{ + int end_change = dont_sync_undo == FALSE; // end undoable change +#ifdef FEAT_FOLDING + if ((fdo_flags & FDO_HOR) && KeyTyped) + foldOpenCursor(); +#endif + undisplay_dollar(); + if (curwin->w_cursor.lnum > 1 || curwin->w_cursor.col > 0) + { + start_arrow_with_change(&curwin->w_cursor, end_change); + if (!end_change) + AppendCharToRedobuff(K_S_LEFT); + (void)bck_word(1L, FALSE, FALSE); + curwin->w_set_curswant = TRUE; + } + else + vim_beep(BO_CRSR); + dont_sync_undo = FALSE; +} + + static void +ins_right(void) +{ + int end_change = dont_sync_undo == FALSE; // end undoable change + +#ifdef FEAT_FOLDING + if ((fdo_flags & FDO_HOR) && KeyTyped) + foldOpenCursor(); +#endif + undisplay_dollar(); + if (gchar_cursor() != NUL || virtual_active()) + { + start_arrow_with_change(&curwin->w_cursor, end_change); + if (!end_change) + AppendCharToRedobuff(K_RIGHT); + curwin->w_set_curswant = TRUE; + if (virtual_active()) + oneright(); + else + { + if (has_mbyte) + curwin->w_cursor.col += (*mb_ptr2len)(ml_get_cursor()); + else + ++curwin->w_cursor.col; + } + +#ifdef FEAT_RIGHTLEFT + revins_legal++; + if (revins_chars) + revins_chars--; +#endif + } + // if 'whichwrap' set for cursor in insert mode, may move the + // cursor to the 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 + vim_beep(BO_CRSR); + dont_sync_undo = FALSE; +} + + static void +ins_s_right(void) +{ + int end_change = dont_sync_undo == FALSE; // end undoable change +#ifdef FEAT_FOLDING + if ((fdo_flags & FDO_HOR) && KeyTyped) + foldOpenCursor(); +#endif + undisplay_dollar(); + if (curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count + || gchar_cursor() != NUL) + { + start_arrow_with_change(&curwin->w_cursor, end_change); + if (!end_change) + AppendCharToRedobuff(K_S_RIGHT); + (void)fwd_word(1L, FALSE, 0); + curwin->w_set_curswant = TRUE; + } + else + vim_beep(BO_CRSR); + dont_sync_undo = FALSE; +} + + static void +ins_up( + int startcol) // when TRUE move to Insstart.col +{ + pos_T tpos; + linenr_T old_topline = curwin->w_topline; +#ifdef FEAT_DIFF + int old_topfill = curwin->w_topfill; +#endif + + undisplay_dollar(); + tpos = curwin->w_cursor; + if (cursor_up(1L, TRUE) == OK) + { + if (startcol) + coladvance(getvcol_nolist(&Insstart)); + if (old_topline != curwin->w_topline +#ifdef FEAT_DIFF + || old_topfill != curwin->w_topfill +#endif + ) + redraw_later(UPD_VALID); + start_arrow(&tpos); + can_cindent = TRUE; + } + else + vim_beep(BO_CRSR); +} + + static void +ins_pageup(void) +{ + pos_T tpos; + + undisplay_dollar(); + + if (mod_mask & MOD_MASK_CTRL) + { + // : tab page back + if (first_tabpage->tp_next != NULL) + { + start_arrow(&curwin->w_cursor); + goto_tabpage(-1); + } + return; + } + + tpos = curwin->w_cursor; + if (onepage(BACKWARD, 1L) == OK) + { + start_arrow(&tpos); + can_cindent = TRUE; + } + else + vim_beep(BO_CRSR); +} + + static void +ins_down( + int startcol) // when TRUE move to Insstart.col +{ + pos_T tpos; + linenr_T old_topline = curwin->w_topline; +#ifdef FEAT_DIFF + int old_topfill = curwin->w_topfill; +#endif + + undisplay_dollar(); + tpos = curwin->w_cursor; + if (cursor_down(1L, TRUE) == OK) + { + if (startcol) + coladvance(getvcol_nolist(&Insstart)); + if (old_topline != curwin->w_topline +#ifdef FEAT_DIFF + || old_topfill != curwin->w_topfill +#endif + ) + redraw_later(UPD_VALID); + start_arrow(&tpos); + can_cindent = TRUE; + } + else + vim_beep(BO_CRSR); +} + + static void +ins_pagedown(void) +{ + pos_T tpos; + + undisplay_dollar(); + + if (mod_mask & MOD_MASK_CTRL) + { + // : tab page forward + if (first_tabpage->tp_next != NULL) + { + start_arrow(&curwin->w_cursor); + goto_tabpage(0); + } + return; + } + + tpos = curwin->w_cursor; + if (onepage(FORWARD, 1L) == OK) + { + start_arrow(&tpos); + can_cindent = TRUE; + } + else + vim_beep(BO_CRSR); +} + +#ifdef FEAT_DND + static void +ins_drop(void) +{ + do_put('~', NULL, BACKWARD, 1L, PUT_CURSEND); +} +#endif + +/* + * Handle TAB in Insert or Replace mode. + * Return TRUE when the TAB needs to be inserted like a normal character. + */ + static int +ins_tab(void) +{ + int ind; + int i; + int temp; + + if (Insstart_blank_vcol == MAXCOL && curwin->w_cursor.lnum == Insstart.lnum) + Insstart_blank_vcol = get_nolist_virtcol(); + if (echeck_abbr(TAB + ABBR_OFF)) + return FALSE; + + ind = inindent(0); + if (ind) + can_cindent = FALSE; + + /* + * When nothing special, insert TAB like a normal character. + */ + if (!curbuf->b_p_et +#ifdef FEAT_VARTABS + && !(p_sta && ind + // These five lines mean 'tabstop' != 'shiftwidth' + && ((tabstop_count(curbuf->b_p_vts_array) > 1) + || (tabstop_count(curbuf->b_p_vts_array) == 1 + && tabstop_first(curbuf->b_p_vts_array) + != get_sw_value(curbuf)) + || (tabstop_count(curbuf->b_p_vts_array) == 0 + && curbuf->b_p_ts != get_sw_value(curbuf)))) + && tabstop_count(curbuf->b_p_vsts_array) == 0 +#else + && !(p_sta && ind && curbuf->b_p_ts != get_sw_value(curbuf)) +#endif + && get_sts_value() == 0) + return TRUE; + + if (stop_arrow() == FAIL) + return TRUE; + + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + AppendToRedobuff((char_u *)"\t"); + +#ifdef FEAT_VARTABS + if (p_sta && ind) // insert tab in indent, use 'shiftwidth' + { + temp = (int)get_sw_value(curbuf); + temp -= get_nolist_virtcol() % temp; + } + else if (tabstop_count(curbuf->b_p_vsts_array) > 0 || curbuf->b_p_sts != 0) + // use 'softtabstop' when set + temp = tabstop_padding(get_nolist_virtcol(), get_sts_value(), + curbuf->b_p_vsts_array); + else // otherwise use 'tabstop' + temp = tabstop_padding(get_nolist_virtcol(), curbuf->b_p_ts, + curbuf->b_p_vts_array); +#else + if (p_sta && ind) // insert tab in indent, use 'shiftwidth' + temp = (int)get_sw_value(curbuf); + else if (curbuf->b_p_sts != 0) // use 'softtabstop' when set + temp = (int)get_sts_value(); + else // otherwise use 'tabstop' + temp = (int)curbuf->b_p_ts; + temp -= get_nolist_virtcol() % temp; +#endif + + /* + * 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. For MODE_VREPLACE state, we use ins_char() for all characters. + */ + ins_char(' '); + while (--temp > 0) + { + if (State & VREPLACE_FLAG) + ins_char(' '); + else + { + ins_str((char_u *)" "); + if (State & REPLACE_FLAG) // no char replaced + replace_push(NUL); + } + } + + /* + * When 'expandtab' not set: Replace spaces by TABs where possible. + */ +#ifdef FEAT_VARTABS + if (!curbuf->b_p_et && (tabstop_count(curbuf->b_p_vsts_array) > 0 + || get_sts_value() > 0 + || (p_sta && ind))) +#else + if (!curbuf->b_p_et && (get_sts_value() || (p_sta && ind))) +#endif + { + char_u *ptr; + char_u *saved_line = NULL; // init for GCC + pos_T pos; + pos_T fpos; + pos_T *cursor; + colnr_T want_vcol, vcol; + int change_col = -1; + int save_list = curwin->w_p_list; + char_u *tab = (char_u *)"\t"; + chartabsize_T cts; + + /* + * Get the current line. For MODE_VREPLACE state, don't make real + * changes yet, just work on a copy of the line. + */ + if (State & VREPLACE_FLAG) + { + pos = curwin->w_cursor; + cursor = &pos; + saved_line = vim_strsave(ml_get_curline()); + if (saved_line == NULL) + return FALSE; + ptr = saved_line + pos.col; + } + else + { + ptr = ml_get_cursor(); + cursor = &curwin->w_cursor; + } + + // When 'L' is not in 'cpoptions' a tab always takes up 'ts' spaces. + if (vim_strchr(p_cpo, CPO_LISTWM) == NULL) + curwin->w_p_list = FALSE; + + // Find first white before the cursor + fpos = curwin->w_cursor; + while (fpos.col > 0 && VIM_ISWHITE(ptr[-1])) + { + --fpos.col; + --ptr; + } + + // In Replace mode, don't change characters before the insert point. + if ((State & REPLACE_FLAG) + && fpos.lnum == Insstart.lnum + && fpos.col < Insstart.col) + { + ptr += Insstart.col - fpos.col; + fpos.col = Insstart.col; + } + + // compute virtual column numbers of first white and cursor + getvcol(curwin, &fpos, &vcol, NULL, NULL); + getvcol(curwin, cursor, &want_vcol, NULL, NULL); + + init_chartabsize_arg(&cts, curwin, 0, vcol, tab, tab); + + // Use as many TABs as possible. Beware of 'breakindent', 'showbreak' + // and 'linebreak' adding extra virtual columns. + while (VIM_ISWHITE(*ptr)) + { + i = lbr_chartabsize(&cts); + if (cts.cts_vcol + i > want_vcol) + break; + if (*ptr != TAB) + { + *ptr = TAB; + if (change_col < 0) + { + change_col = fpos.col; // Column of first change + // May have to adjust Insstart + if (fpos.lnum == Insstart.lnum && fpos.col < Insstart.col) + Insstart.col = fpos.col; + } + } + ++fpos.col; + ++ptr; + cts.cts_vcol += i; + } + vcol = cts.cts_vcol; + clear_chartabsize_arg(&cts); + + if (change_col >= 0) + { + int repl_off = 0; + + // Skip over the spaces we need. + init_chartabsize_arg(&cts, curwin, 0, vcol, ptr, ptr); + while (cts.cts_vcol < want_vcol && *cts.cts_ptr == ' ') + { + cts.cts_vcol += lbr_chartabsize(&cts); + ++cts.cts_ptr; + ++repl_off; + } + ptr = cts.cts_ptr; + vcol = cts.cts_vcol; + clear_chartabsize_arg(&cts); + + if (vcol > want_vcol) + { + // Must have a char with 'showbreak' just before it. + --ptr; + --repl_off; + } + fpos.col += repl_off; + + // Delete following spaces. + i = cursor->col - fpos.col; + if (i > 0) + { +#ifdef FEAT_PROP_POPUP + if (!(State & VREPLACE_FLAG)) + { + char_u *newp; + int col; + + newp = alloc(curbuf->b_ml.ml_line_len - i); + if (newp == NULL) + return FALSE; + + col = ptr - curbuf->b_ml.ml_line_ptr; + if (col > 0) + mch_memmove(newp, ptr - col, col); + mch_memmove(newp + col, ptr + i, + curbuf->b_ml.ml_line_len - col - i); + + if (curbuf->b_ml.ml_flags & (ML_LINE_DIRTY | ML_ALLOCATED)) + vim_free(curbuf->b_ml.ml_line_ptr); + curbuf->b_ml.ml_line_ptr = newp; + curbuf->b_ml.ml_line_len -= i; + curbuf->b_ml.ml_flags = + (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY; + } + else +#endif + STRMOVE(ptr, ptr + i); + // correct replace stack. + if ((State & REPLACE_FLAG) && !(State & VREPLACE_FLAG)) + for (temp = i; --temp >= 0; ) + replace_join(repl_off); + } +#ifdef FEAT_NETBEANS_INTG + if (netbeans_active()) + { + netbeans_removed(curbuf, fpos.lnum, cursor->col, (long)(i + 1)); + netbeans_inserted(curbuf, fpos.lnum, cursor->col, + (char_u *)"\t", 1); + } +#endif + cursor->col -= i; + + /* + * In MODE_VREPLACE state, we haven't changed anything yet. Do it + * now by backspacing over the changed spacing and then inserting + * the new spacing. + */ + if (State & VREPLACE_FLAG) + { + // Backspace from real cursor to change_col + backspace_until_column(change_col); + + // Insert each char in saved_line from changed_col to + // ptr-cursor + ins_bytes_len(saved_line + change_col, + cursor->col - change_col); + } + } + + if (State & VREPLACE_FLAG) + vim_free(saved_line); + curwin->w_p_list = save_list; + } + + return FALSE; +} + +/* + * Handle CR or NL in insert mode. + * Return FAIL when out of memory or can't undo. + */ + int +ins_eol(int c) +{ + int i; + + if (echeck_abbr(c + ABBR_OFF)) + return OK; + if (stop_arrow() == FAIL) + return FAIL; + undisplay_dollar(); + + /* + * Strange Vi behaviour: In Replace mode, typing a NL will not delete the + * character under the cursor. Only push a NUL on the replace stack, + * nothing to put back when the NL is deleted. + */ + if ((State & REPLACE_FLAG) && !(State & VREPLACE_FLAG)) + replace_push(NUL); + + /* + * In MODE_VREPLACE state, a NL replaces the rest of the line, and starts + * replacing the next line, so we push all of the characters left on the + * line onto the replace stack. This is not done here though, it is done + * in open_line(). + */ + + // Put cursor on NUL if on the last char and coladd is 1 (happens after + // CTRL-O). + if (virtual_active() && curwin->w_cursor.coladd > 0) + coladvance(getviscol()); + +#ifdef FEAT_RIGHTLEFT + // NL in reverse insert will always start in the end of + // current line. + if (revins_on) + curwin->w_cursor.col += (colnr_T)STRLEN(ml_get_cursor()); +#endif + + AppendToRedobuff(NL_STR); + i = open_line(FORWARD, + has_format_option(FO_RET_COMS) ? OPENLINE_DO_COM : 0, old_indent, + NULL); + old_indent = 0; + can_cindent = TRUE; +#ifdef FEAT_FOLDING + // When inserting a line the cursor line must never be in a closed fold. + foldOpenCursor(); +#endif + + return i; +} + +#ifdef FEAT_DIGRAPHS +/* + * Handle digraph in insert mode. + * Returns character still to be inserted, or NUL when nothing remaining to be + * done. + */ + static int +ins_digraph(void) +{ + int c; + int cc; + int did_putchar = FALSE; + + pc_status = PC_STATUS_UNSET; + if (redrawing() && !char_avail()) + { + // may need to redraw when no more chars available now + ins_redraw(FALSE); + + edit_putchar('?', TRUE); + did_putchar = TRUE; + add_to_showcmd_c(Ctrl_K); + } + +#ifdef USE_ON_FLY_SCROLL + dont_scroll = TRUE; // disallow scrolling here +#endif + + // don't map the digraph chars. This also prevents the + // mode message to be deleted when ESC is hit + ++no_mapping; + ++allow_keys; + c = plain_vgetc(); + --no_mapping; + --allow_keys; + if (did_putchar) + // when the line fits in 'columns' the '?' is at the start of the next + // line and will not be removed by the redraw + edit_unputchar(); + + if (IS_SPECIAL(c) || mod_mask) // special key + { + clear_showcmd(); + insert_special(c, TRUE, FALSE); + return NUL; + } + if (c != ESC) + { + did_putchar = FALSE; + if (redrawing() && !char_avail()) + { + // may need to redraw when no more chars available now + ins_redraw(FALSE); + + if (char2cells(c) == 1) + { + ins_redraw(FALSE); + edit_putchar(c, TRUE); + did_putchar = TRUE; + } + add_to_showcmd_c(c); + } + ++no_mapping; + ++allow_keys; + cc = plain_vgetc(); + --no_mapping; + --allow_keys; + if (did_putchar) + // when the line fits in 'columns' the '?' is at the start of the + // next line and will not be removed by a redraw + edit_unputchar(); + if (cc != ESC) + { + AppendToRedobuff((char_u *)CTRL_V_STR); + c = digraph_get(c, cc, TRUE); + clear_showcmd(); + return c; + } + } + clear_showcmd(); + return NUL; +} +#endif + +/* + * Handle CTRL-E and CTRL-Y in Insert mode: copy char from other line. + * Returns the char to be inserted, or NUL if none found. + */ + int +ins_copychar(linenr_T lnum) +{ + int c; + char_u *ptr, *prev_ptr; + char_u *line; + chartabsize_T cts; + + if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) + { + vim_beep(BO_COPY); + return NUL; + } + + // try to advance to the cursor column + validate_virtcol(); + line = ml_get(lnum); + prev_ptr = line; + init_chartabsize_arg(&cts, curwin, lnum, 0, line, line); + while (cts.cts_vcol < curwin->w_virtcol && *cts.cts_ptr != NUL) + { + prev_ptr = cts.cts_ptr; + cts.cts_vcol += lbr_chartabsize_adv(&cts); + } + if (cts.cts_vcol > curwin->w_virtcol) + ptr = prev_ptr; + else + ptr = cts.cts_ptr; + clear_chartabsize_arg(&cts); + + c = (*mb_ptr2char)(ptr); + if (c == NUL) + vim_beep(BO_COPY); + return c; +} + +/* + * CTRL-Y or CTRL-E typed in Insert mode. + */ + static int +ins_ctrl_ey(int tc) +{ + int c = tc; + + if (ctrl_x_mode_scroll()) + { + if (c == Ctrl_Y) + scrolldown_clamp(); + else + scrollup_clamp(); + redraw_later(UPD_VALID); + } + else + { + c = ins_copychar(curwin->w_cursor.lnum + (c == Ctrl_Y ? -1 : 1)); + if (c != NUL) + { + long tw_save; + + // The character must be taken literally, insert like it + // was typed after a CTRL-V, and pretend 'textwidth' + // wasn't set. Digits, 'o' and 'x' are special after a + // CTRL-V, don't use it for these. + if (c < 256 && !isalnum(c)) + AppendToRedobuff((char_u *)CTRL_V_STR); // CTRL-V + tw_save = curbuf->b_p_tw; + curbuf->b_p_tw = -1; + insert_special(c, TRUE, FALSE); + curbuf->b_p_tw = tw_save; +#ifdef FEAT_RIGHTLEFT + revins_chars++; + revins_legal++; +#endif + c = Ctrl_V; // pretend CTRL-V is last character + auto_format(FALSE, TRUE); + } + } + return c; +} + +/* + * Get the value that w_virtcol would have when 'list' is off. + * Unless 'cpo' contains the 'L' flag. + */ + colnr_T +get_nolist_virtcol(void) +{ + // check validity of cursor in current buffer + if (curwin->w_buffer == NULL + || curwin->w_buffer->b_ml.ml_mfp == NULL + || curwin->w_cursor.lnum > curwin->w_buffer->b_ml.ml_line_count) + return 0; + if (curwin->w_p_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) + return getvcol_nolist(&curwin->w_cursor); + validate_virtcol(); + return curwin->w_virtcol; +} + +#if defined(FEAT_EVAL) +/* + * Handle the InsertCharPre autocommand. + * "c" is the character that was typed. + * Return a pointer to allocated memory with the replacement string. + * Return NULL to continue inserting "c". + */ + static char_u * +do_insert_char_pre(int c) +{ + char_u *res; + char_u buf[MB_MAXBYTES + 1]; + int save_State = State; + + // Return quickly when there is nothing to do. + if (!has_insertcharpre()) + return NULL; + + if (has_mbyte) + buf[(*mb_char2bytes)(c, buf)] = NUL; + else + { + buf[0] = c; + buf[1] = NUL; + } + + // Lock the text to avoid weird things from happening. + ++textlock; + set_vim_var_string(VV_CHAR, buf, -1); // set v:char + + res = NULL; + if (ins_apply_autocmds(EVENT_INSERTCHARPRE)) + { + // Get the value of v:char. It may be empty or more than one + // character. Only use it when changed, otherwise continue with the + // original character to avoid breaking autoindent. + if (STRCMP(buf, get_vim_var_str(VV_CHAR)) != 0) + res = vim_strsave(get_vim_var_str(VV_CHAR)); + } + + set_vim_var_string(VV_CHAR, NULL, -1); // clear v:char + --textlock; + + // Restore the State, it may have been changed. + State = save_State; + + return res; +} +#endif + + int +get_can_cindent(void) +{ + return can_cindent; +} + + void +set_can_cindent(int val) +{ + can_cindent = val; +} + +/* + * Trigger "event" and take care of fixing undo. + */ + int +ins_apply_autocmds(event_T event) +{ + varnumber_T tick = CHANGEDTICK(curbuf); + int r; + + r = apply_autocmds(event, NULL, NULL, FALSE, curbuf); + + // If u_savesub() was called then we are not prepared to start + // a new line. Call u_save() with no contents to fix that. + // Except when leaving Insert mode. + if (event != EVENT_INSERTLEAVE && tick != CHANGEDTICK(curbuf)) + u_save(curwin->w_cursor.lnum, (linenr_T)(curwin->w_cursor.lnum + 1)); + + return r; +} diff --git a/userland/apps/vim/src/errors.h b/userland/apps/vim/src/errors.h new file mode 100644 index 00000000..828e8de2 --- /dev/null +++ b/userland/apps/vim/src/errors.h @@ -0,0 +1,3602 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * 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. + */ + +// Use PLURAL_MSG() for messages that are passed to ngettext(), so that the +// second one uses msgid_plural. +#ifdef DO_INIT +# define PLURAL_MSG(var1, msg1, var2, msg2) \ + char var1[] = msg1; \ + char var2[] = msg2; +#else +# define PLURAL_MSG(var1, msg1, var2, msg2) \ + extern char var1[]; \ + extern char var2[]; +#endif + +/* + * Definition of error messages, sorted on error number. + */ + +EXTERN char e_interrupted[] + INIT(= N_("Interrupted")); + +EXTERN char e_backslash_should_be_followed_by[] + INIT(= N_("E10: \\ should be followed by /, ? or &")); +EXTERN char e_invalid_in_cmdline_window[] + INIT(= N_("E11: Invalid in command-line window; :q closes the window")); +EXTERN char e_command_not_allowed_from_vimrc_in_current_dir_or_tag_search[] + INIT(= N_("E12: Command not allowed from exrc/vimrc in current dir or tag search")); +EXTERN char e_file_exists[] + INIT(= N_("E13: File exists (add ! to override)")); +// E14 unused +#ifdef FEAT_EVAL +EXTERN char e_invalid_expression_str[] + INIT(= N_("E15: Invalid expression: \"%s\"")); +#endif +EXTERN char e_invalid_range[] + INIT(= N_("E16: Invalid range")); +#if defined(UNIX) || defined(FEAT_SYN_HL) \ + || defined(FEAT_SPELL) || defined(FEAT_EVAL) +EXTERN char e_str_is_directory[] + INIT(= N_("E17: \"%s\" is a directory")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_unexpected_characters_in_let[] + INIT(= N_("E18: Unexpected characters in :let")); +EXTERN char e_unexpected_characters_in_assignment[] + INIT(= N_("E18: Unexpected characters in assignment")); +#endif +EXTERN char e_mark_has_invalid_line_number[] + INIT(= N_("E19: Mark has invalid line number")); +EXTERN char e_mark_not_set[] + INIT(= N_("E20: Mark not set")); +EXTERN char e_cannot_make_changes_modifiable_is_off[] + INIT(= N_("E21: Cannot make changes, 'modifiable' is off")); +EXTERN char e_scripts_nested_too_deep[] + INIT(= N_("E22: Scripts nested too deep")); +EXTERN char e_no_alternate_file[] + INIT(= N_("E23: No alternate file")); +EXTERN char e_no_such_abbreviation[] + INIT(= N_("E24: No such abbreviation")); +#if !defined(FEAT_GUI) || defined(VIMDLL) +EXTERN char e_gui_cannot_be_used_not_enabled_at_compile_time[] + INIT(= N_("E25: GUI cannot be used: Not enabled at compile time")); +#endif +#ifndef FEAT_RIGHTLEFT +EXTERN char e_hebrew_cannot_be_used_not_enabled_at_compile_time[] + INIT(= N_("E26: Hebrew cannot be used: Not enabled at compile time\n")); +#endif +EXTERN char e_farsi_support_has_been_removed[] + INIT(= N_("E27: Farsi support has been removed\n")); +#if defined(FEAT_SEARCH_EXTRA) || defined(FEAT_SYN_HL) +EXTERN char e_no_such_highlight_group_name_str[] + INIT(= N_("E28: No such highlight group name: %s")); +#endif +EXTERN char e_no_inserted_text_yet[] + INIT(= N_("E29: No inserted text yet")); +EXTERN char e_no_previous_command_line[] + INIT(= N_("E30: No previous command line")); +EXTERN char e_no_such_mapping[] + INIT(= N_("E31: No such mapping")); +EXTERN char e_no_file_name[] + INIT(= N_("E32: No file name")); +EXTERN char e_no_previous_substitute_regular_expression[] + INIT(= N_("E33: No previous substitute regular expression")); +EXTERN char e_no_previous_command[] + INIT(= N_("E34: No previous command")); +EXTERN char e_no_previous_regular_expression[] + INIT(= N_("E35: No previous regular expression")); +EXTERN char e_not_enough_room[] + INIT(= N_("E36: Not enough room")); +EXTERN char e_no_write_since_last_change[] + INIT(= N_("E37: No write since last change")); +EXTERN char e_no_write_since_last_change_add_bang_to_override[] + INIT(= N_("E37: No write since last change (add ! to override)")); +EXTERN char e_null_argument[] + INIT(= "E38: Null argument"); +#if defined(FEAT_DIGRAPHS) || defined(FEAT_TIMERS) || defined(FEAT_EVAL) +EXTERN char e_number_expected[] + INIT(= N_("E39: Number expected")); +#endif +#ifdef FEAT_QUICKFIX +EXTERN char e_cant_open_errorfile_str[] + INIT(= N_("E40: Can't open errorfile %s")); +#endif +EXTERN char e_out_of_memory[] + INIT(= N_("E41: Out of memory!")); +#ifdef FEAT_QUICKFIX +EXTERN char e_no_errors[] + INIT(= N_("E42: No Errors")); +#endif +EXTERN char e_damaged_match_string[] + INIT(= "E43: Damaged match string"); +EXTERN char e_corrupted_regexp_program[] + INIT(= "E44: Corrupted regexp program"); +EXTERN char e_readonly_option_is_set_add_bang_to_override[] + INIT(= N_("E45: 'readonly' option is set (add ! to override)")); +#ifdef FEAT_EVAL +EXTERN char e_cannot_change_readonly_variable[] + INIT(= N_("E46: Cannot change read-only variable")); +EXTERN char e_cannot_change_readonly_variable_str[] + INIT(= N_("E46: Cannot change read-only variable \"%s\"")); +#endif +#ifdef FEAT_QUICKFIX +EXTERN char e_error_while_reading_errorfile[] + INIT(= N_("E47: Error while reading errorfile")); +#endif +#ifdef HAVE_SANDBOX +EXTERN char e_not_allowed_in_sandbox[] + INIT(= N_("E48: Not allowed in sandbox")); +#endif +EXTERN char e_invalid_scroll_size[] + INIT(= N_("E49: Invalid scroll size")); +#ifdef FEAT_SYN_HL +EXTERN char e_too_many_z[] + INIT(= N_("E50: Too many \\z(")); +#endif +EXTERN char e_too_many_str_open[] + INIT(= N_("E51: Too many %s(")); +#ifdef FEAT_SYN_HL +EXTERN char e_unmatched_z[] + INIT(= N_("E52: Unmatched \\z(")); +#endif +EXTERN char e_unmatched_str_percent_open[] + INIT(= N_("E53: Unmatched %s%%(")); +EXTERN char e_unmatched_str_open[] + INIT(= N_("E54: Unmatched %s(")); +EXTERN char e_unmatched_str_close[] + INIT(= N_("E55: Unmatched %s)")); +// E56 unused +// E57 unused +// E58 unused +EXTERN char e_invalid_character_after_str_at[] + INIT(= N_("E59: Invalid character after %s@")); +EXTERN char e_too_many_complex_str_curly[] + INIT(= N_("E60: Too many complex %s{...}s")); +EXTERN char e_nested_str[] + INIT(= N_("E61: Nested %s*")); +EXTERN char e_nested_str_chr[] + INIT(= N_("E62: Nested %s%c")); +EXTERN char e_invalid_use_of_underscore[] + INIT(= N_("E63: Invalid use of \\_")); +EXTERN char e_str_chr_follows_nothing[] + INIT(= N_("E64: %s%c follows nothing")); +EXTERN char e_illegal_back_reference[] + INIT(= N_("E65: Illegal back reference")); +#ifdef FEAT_SYN_HL +EXTERN char e_z_not_allowed_here[] + INIT(= N_("E66: \\z( not allowed here")); +EXTERN char e_z1_z9_not_allowed_here[] + INIT(= N_("E67: \\z1 - \\z9 not allowed here")); +#endif +EXTERN char e_invalid_character_after_bsl_z[] + INIT(= N_("E68: Invalid character after \\z")); +EXTERN char e_missing_sb_after_str[] + INIT(= N_("E69: Missing ] after %s%%[")); +EXTERN char e_empty_str_brackets[] + INIT(= N_("E70: Empty %s%%[]")); +EXTERN char e_invalid_character_after_str[] + INIT(= N_("E71: Invalid character after %s%%")); +EXTERN char e_close_error_on_swap_file[] + INIT(= N_("E72: Close error on swap file")); +EXTERN char e_tag_stack_empty[] + INIT(= N_("E73: Tag stack empty")); +EXTERN char e_command_too_complex[] + INIT(= N_("E74: Command too complex")); +EXTERN char e_name_too_long[] + INIT(= N_("E75: Name too long")); +EXTERN char e_too_many_brackets[] + INIT(= N_("E76: Too many [")); +EXTERN char e_too_many_file_names[] + INIT(= N_("E77: Too many file names")); +EXTERN char e_unknown_mark[] + INIT(= N_("E78: Unknown mark")); +EXTERN char e_cannot_expand_wildcards[] + INIT(= N_("E79: Cannot expand wildcards")); +EXTERN char e_error_while_writing[] + INIT(= N_("E80: Error while writing")); +#ifdef FEAT_EVAL +EXTERN char e_using_sid_not_in_script_context[] + INIT(= N_("E81: Using not in a script context")); +#endif +EXTERN char e_cannot_allocate_any_buffer_exiting[] + INIT(= N_("E82: Cannot allocate any buffer, exiting...")); +EXTERN char e_cannot_allocate_buffer_using_other_one[] + INIT(= N_("E83: Cannot allocate buffer, using other one...")); +EXTERN char e_no_modified_buffer_found[] + INIT(= N_("E84: No modified buffer found")); +EXTERN char e_there_is_no_listed_buffer[] + INIT(= N_("E85: There is no listed buffer")); +EXTERN char e_buffer_nr_does_not_exist[] + INIT(= N_("E86: Buffer %ld does not exist")); +EXTERN char e_cannot_go_beyond_last_buffer[] + INIT(= N_("E87: Cannot go beyond last buffer")); +EXTERN char e_cannot_go_before_first_buffer[] + INIT(= N_("E88: Cannot go before first buffer")); +EXTERN char e_no_write_since_last_change_for_buffer_nr_add_bang_to_override[] + INIT(= N_("E89: No write since last change for buffer %d (add ! to override)")); +EXTERN char e_cannot_unload_last_buffer[] + INIT(= N_("E90: Cannot unload last buffer")); +EXTERN char e_shell_option_is_empty[] + INIT(= N_("E91: 'shell' option is empty")); +EXTERN char e_buffer_nr_not_found[] + INIT(= N_("E92: Buffer %d not found")); +EXTERN char e_more_than_one_match_for_str[] + INIT(= N_("E93: More than one match for %s")); +EXTERN char e_no_matching_buffer_for_str[] + INIT(= N_("E94: No matching buffer for %s")); +EXTERN char e_buffer_with_this_name_already_exists[] + INIT(= N_("E95: Buffer with this name already exists")); +#if defined(FEAT_DIFF) +EXTERN char e_cannot_diff_more_than_nr_buffers[] + INIT(= N_("E96: Cannot diff more than %d buffers")); +EXTERN char e_cannot_create_diffs[] + INIT(= N_("E97: Cannot create diffs")); +EXTERN char e_cannot_read_diff_output[] + INIT(= N_("E98: Cannot read diff output")); +EXTERN char e_current_buffer_is_not_in_diff_mode[] + INIT(= N_("E99: Current buffer is not in diff mode")); +EXTERN char e_no_other_buffer_in_diff_mode[] + INIT(= N_("E100: No other buffer in diff mode")); +EXTERN char e_more_than_two_buffers_in_diff_mode_dont_know_which_one_to_use[] + INIT(= N_("E101: More than two buffers in diff mode, don't know which one to use")); +EXTERN char e_cant_find_buffer_str[] + INIT(= N_("E102: Can't find buffer \"%s\"")); +EXTERN char e_buffer_str_is_not_in_diff_mode[] + INIT(= N_("E103: Buffer \"%s\" is not in diff mode")); +#endif +#ifdef FEAT_DIGRAPHS +EXTERN char e_escape_not_allowed_in_digraph[] + INIT(= N_("E104: Escape not allowed in digraph")); +#endif +#ifdef FEAT_KEYMAP +EXTERN char e_using_loadkeymap_not_in_sourced_file[] + INIT(= N_("E105: Using :loadkeymap not in a sourced file")); +#endif +// E106 unused +#ifdef FEAT_EVAL +EXTERN char e_missing_parenthesis_str[] + INIT(= N_("E107: Missing parentheses: %s")); +EXTERN char e_no_such_variable_str[] + INIT(= N_("E108: No such variable: \"%s\"")); +EXTERN char e_missing_colon_after_questionmark[] + INIT(= N_("E109: Missing ':' after '?'")); +EXTERN char e_missing_closing_paren[] + INIT(= N_("E110: Missing ')'")); +EXTERN char e_missing_closing_square_brace[] + INIT(= N_("E111: Missing ']'")); +EXTERN char e_option_name_missing_str[] + INIT(= N_("E112: Option name missing: %s")); +EXTERN char e_unknown_option_str[] + INIT(= N_("E113: Unknown option: %s")); +EXTERN char e_missing_double_quote_str[] + INIT(= N_("E114: Missing double quote: %s")); +EXTERN char e_missing_single_quote_str[] + INIT(= N_("E115: Missing single quote: %s")); +EXTERN char e_invalid_arguments_for_function_str[] + INIT(= N_("E116: Invalid arguments for function %s")); +EXTERN char e_unknown_function_str[] + INIT(= N_("E117: Unknown function: %s")); +EXTERN char e_too_many_arguments_for_function_str[] + INIT(= N_("E118: Too many arguments for function: %s")); +EXTERN char e_not_enough_arguments_for_function_str[] + INIT(= N_("E119: Not enough arguments for function: %s")); +EXTERN char e_using_sid_not_in_script_context_str[] + INIT(= N_("E120: Using not in a script context: %s")); +EXTERN char e_undefined_variable_str[] + INIT(= N_("E121: Undefined variable: %s")); +EXTERN char e_undefined_variable_char_str[] + INIT(= N_("E121: Undefined variable: %c:%s")); +EXTERN char e_function_str_already_exists_add_bang_to_replace[] + INIT(= N_("E122: Function %s already exists, add ! to replace it")); +EXTERN char e_undefined_function_str[] + INIT(= N_("E123: Undefined function: %s")); +EXTERN char e_missing_paren_str[] + INIT(= N_("E124: Missing '(': %s")); +EXTERN char e_illegal_argument_str[] + INIT(= N_("E125: Illegal argument: %s")); +EXTERN char e_missing_endfunction[] + INIT(= N_("E126: Missing :endfunction")); +EXTERN char e_cannot_redefine_function_str_it_is_in_use[] + INIT(= N_("E127: Cannot redefine function %s: It is in use")); +EXTERN char e_function_name_must_start_with_capital_or_s_str[] + INIT(= N_("E128: Function name must start with a capital or \"s:\": %s")); +EXTERN char e_function_name_required[] + INIT(= N_("E129: Function name required")); +// E130 unused +EXTERN char e_cannot_delete_function_str_it_is_in_use[] + INIT(= N_("E131: Cannot delete function %s: It is in use")); +EXTERN char e_function_call_depth_is_higher_than_macfuncdepth[] + INIT(= N_("E132: Function call depth is higher than 'maxfuncdepth'")); +EXTERN char e_return_not_inside_function[] + INIT(= N_("E133: :return not inside a function")); +#endif +EXTERN char e_cannot_move_range_of_lines_into_itself[] + INIT(= N_("E134: Cannot move a range of lines into itself")); +EXTERN char e_filter_autocommands_must_not_change_current_buffer[] + INIT(= N_("E135: *Filter* Autocommands must not change current buffer")); +#if defined(FEAT_VIMINFO) +EXTERN char e_viminfo_too_many_errors_skipping_rest_of_file[] + INIT(= N_("E136: viminfo: Too many errors, skipping rest of file")); +EXTERN char e_viminfo_file_is_not_writable_str[] + INIT(= N_("E137: Viminfo file is not writable: %s")); +EXTERN char e_cant_write_viminfo_file_str[] + INIT(= N_("E138: Can't write viminfo file %s!")); +#endif +EXTERN char e_file_is_loaded_in_another_buffer[] + INIT(= N_("E139: File is loaded in another buffer")); +EXTERN char e_use_bang_to_write_partial_buffer[] + INIT(= N_("E140: Use ! to write partial buffer")); +EXTERN char e_no_file_name_for_buffer_nr[] + INIT(= N_("E141: No file name for buffer %ld")); +EXTERN char e_file_not_written_writing_is_disabled_by_write_option[] + INIT(= N_("E142: File not written: Writing is disabled by 'write' option")); +EXTERN char e_autocommands_unexpectedly_deleted_new_buffer_str[] + INIT(= N_("E143: Autocommands unexpectedly deleted new buffer %s")); +EXTERN char e_non_numeric_argument_to_z[] + INIT(= N_("E144: Non-numeric argument to :z")); +EXTERN char e_shell_commands_and_some_functionality_not_allowed_in_rvim[] + INIT(= N_("E145: Shell commands and some functionality not allowed in rvim")); +EXTERN char e_regular_expressions_cant_be_delimited_by_letters[] + INIT(= N_("E146: Regular expressions can't be delimited by letters")); +EXTERN char e_cannot_do_global_recursive_with_range[] + INIT(= N_("E147: Cannot do :global recursive with a range")); +EXTERN char e_regular_expression_missing_from_global[] + INIT(= N_("E148: Regular expression missing from :global")); +EXTERN char e_sorry_no_help_for_str[] + INIT(= N_("E149: Sorry, no help for %s")); +EXTERN char e_not_a_directory_str[] + INIT(= N_("E150: Not a directory: %s")); +EXTERN char e_no_match_str_1[] + INIT(= N_("E151: No match: %s")); +EXTERN char e_cannot_open_str_for_writing_1[] + INIT(= N_("E152: Cannot open %s for writing")); +EXTERN char e_unable_to_open_str_for_reading[] + INIT(= N_("E153: Unable to open %s for reading")); +EXTERN char e_duplicate_tag_str_in_file_str_str[] + INIT(= N_("E154: Duplicate tag \"%s\" in file %s/%s")); +#ifdef FEAT_SIGNS +EXTERN char e_unknown_sign_str[] + INIT(= N_("E155: Unknown sign: %s")); +EXTERN char e_missing_sign_name[] + INIT(= N_("E156: Missing sign name")); +EXTERN char e_invalid_sign_id_nr[] + INIT(= N_("E157: Invalid sign ID: %d")); +#endif +#if defined(FEAT_SIGNS) || defined(FEAT_EVAL) +EXTERN char e_invalid_buffer_name_str[] + INIT(= N_("E158: Invalid buffer name: %s")); +#endif +#ifdef FEAT_SIGNS +EXTERN char e_missing_sign_number[] + INIT(= N_("E159: Missing sign number")); +EXTERN char e_unknown_sign_command_str[] + INIT(= N_("E160: Unknown sign command: %s")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_breakpoint_not_found_str[] + INIT(= N_("E161: Breakpoint not found: %s")); +#endif +EXTERN char e_no_write_since_last_change_for_buffer_str[] + INIT(= N_("E162: No write since last change for buffer \"%s\"")); +EXTERN char e_there_is_only_one_file_to_edit[] + INIT(= N_("E163: There is only one file to edit")); +EXTERN char e_cannot_go_before_first_file[] + INIT(= N_("E164: Cannot go before first file")); +EXTERN char e_cannot_go_beyond_last_file[] + INIT(= N_("E165: Cannot go beyond last file")); +EXTERN char e_cant_open_linked_file_for_writing[] + INIT(= N_("E166: Can't open linked file for writing")); +EXTERN char e_scriptencoding_used_outside_of_sourced_file[] + INIT(= N_("E167: :scriptencoding used outside of a sourced file")); +#ifdef FEAT_EVAL +EXTERN char e_finish_used_outside_of_sourced_file[] + INIT(= N_("E168: :finish used outside of a sourced file")); +#endif +EXTERN char e_command_too_recursive[] + INIT(= N_("E169: Command too recursive")); +#ifdef FEAT_EVAL +EXTERN char e_missing_endwhile[] + INIT(= N_("E170: Missing :endwhile")); +EXTERN char e_missing_endfor[] + INIT(= N_("E170: Missing :endfor")); +EXTERN char e_missing_endif[] + INIT(= N_("E171: Missing :endif")); +EXTERN char e_missing_marker[] + INIT(= N_("E172: Missing marker")); +#endif + +PLURAL_MSG(e_nr_more_file_to_edit, "E173: %d more file to edit", + e_nr_more_files_to_edit, "E173: %d more files to edit") + +EXTERN char e_command_already_exists_add_bang_to_replace_it_str[] + INIT(= N_("E174: Command already exists: add ! to replace it: %s")); +EXTERN char e_no_attribute_specified[] + INIT(= N_("E175: No attribute specified")); +EXTERN char e_invalid_number_of_arguments[] + INIT(= N_("E176: Invalid number of arguments")); +EXTERN char e_count_cannot_be_specified_twice[] + INIT(= N_("E177: Count cannot be specified twice")); +EXTERN char e_invalid_default_value_for_count[] + INIT(= N_("E178: Invalid default value for count")); +EXTERN char e_argument_required_for_str[] + INIT(= N_("E179: Argument required for %s")); +EXTERN char e_invalid_complete_value_str[] + INIT(= N_("E180: Invalid complete value: %s")); +EXTERN char e_invalid_address_type_value_str[] + INIT(= N_("E180: Invalid address type value: %s")); +EXTERN char e_invalid_attribute_str[] + INIT(= N_("E181: Invalid attribute: %s")); +EXTERN char e_invalid_command_name[] + INIT(= N_("E182: Invalid command name")); +EXTERN char e_user_defined_commands_must_start_with_an_uppercase_letter[] + INIT(= N_("E183: User defined commands must start with an uppercase letter")); +EXTERN char e_no_such_user_defined_command_str[] + INIT(= N_("E184: No such user-defined command: %s")); +EXTERN char e_cannot_find_color_scheme_str[] + INIT(= N_("E185: Cannot find color scheme '%s'")); +EXTERN char e_no_previous_directory[] + INIT(= N_("E186: No previous directory")); +EXTERN char e_directory_unknown[] + INIT(= N_("E187: Directory unknown")); +EXTERN char e_obtaining_window_position_not_implemented_for_this_platform[] + INIT(= N_("E188: Obtaining window position not implemented for this platform")); +EXTERN char e_str_exists_add_bang_to_override[] + INIT(= N_("E189: \"%s\" exists (add ! to override)")); +EXTERN char e_cannot_open_str_for_writing_2[] + INIT(= N_("E190: Cannot open \"%s\" for writing")); +EXTERN char e_argument_must_be_letter_or_forward_backward_quote[] + INIT(= N_("E191: Argument must be a letter or forward/backward quote")); +EXTERN char e_recursive_use_of_normal_too_deep[] + INIT(= N_("E192: Recursive use of :normal too deep")); +#ifdef FEAT_EVAL +EXTERN char e_str_not_inside_function[] + INIT(= N_("E193: %s not inside a function")); +#endif +EXTERN char e_no_alternate_file_name_to_substitute_for_hash[] + INIT(= N_("E194: No alternate file name to substitute for '#'")); +#ifdef FEAT_VIMINFO +EXTERN char e_cannot_open_viminfo_file_for_reading[] + INIT(= N_("E195: Cannot open viminfo file for reading")); +#endif +#ifndef FEAT_DIGRAPHS +EXTERN char e_no_digraphs_version[] + INIT(= N_("E196: No digraphs in this version")); +#endif +EXTERN char e_cannot_set_language_to_str[] + INIT(= N_("E197: Cannot set language to \"%s\"")); +// E198 unused +EXTERN char e_active_window_or_buffer_deleted[] + INIT(= N_("E199: Active window or buffer deleted")); +EXTERN char e_readpre_autocommands_made_file_unreadable[] + INIT(= N_("E200: *ReadPre autocommands made the file unreadable")); +EXTERN char e_readpre_autocommands_must_not_change_current_buffer[] + INIT(= N_("E201: *ReadPre autocommands must not change current buffer")); +#ifdef FEAT_EVAL +EXTERN char e_conversion_mad_file_unreadable[] + INIT(= N_("E202: Conversion made file unreadable!")); +#endif +EXTERN char e_autocommands_deleted_or_unloaded_buffer_to_be_written[] + INIT(= N_("E203: Autocommands deleted or unloaded buffer to be written")); +EXTERN char e_autocommands_changed_number_of_lines_in_unexpected_way[] + INIT(= N_("E204: Autocommand changed number of lines in unexpected way")); +EXTERN char e_patchmode_cant_save_original_file[] + INIT(= N_("E205: Patchmode: can't save original file")); +EXTERN char e_patchmode_cant_touch_empty_original_file[] + INIT(= N_("E206: Patchmode: can't touch empty original file")); +EXTERN char e_cant_delete_backup_file[] + INIT(= N_("E207: Can't delete backup file")); +EXTERN char e_error_writing_to_str[] + INIT(= N_("E208: Error writing to \"%s\"")); +EXTERN char e_error_closing_str[] + INIT(= N_("E209: Error closing \"%s\"")); +EXTERN char e_error_reading_str[] + INIT(= N_("E210: Error reading \"%s\"")); +EXTERN char e_file_str_no_longer_available[] + INIT(= N_("E211: File \"%s\" no longer available")); +EXTERN char e_cant_open_file_for_writing[] + INIT(= N_("E212: Can't open file for writing")); +EXTERN char e_cannot_convert_add_bang_to_write_without_conversion[] + INIT(= N_("E213: Cannot convert (add ! to write without conversion)")); +#ifdef FEAT_EVAL +EXTERN char e_cant_find_temp_file_for_writing[] + INIT(= N_("E214: Can't find temp file for writing")); +#endif +EXTERN char e_illegal_character_after_star_str[] + INIT(= N_("E215: Illegal character after *: %s")); +EXTERN char e_no_such_event_str[] + INIT(= N_("E216: No such event: %s")); +EXTERN char e_no_such_group_or_event_str[] + INIT(= N_("E216: No such group or event: %s")); +EXTERN char e_cant_execute_autocommands_for_all_events[] + INIT(= N_("E217: Can't execute autocommands for ALL events")); +EXTERN char e_autocommand_nesting_too_deep[] + INIT(= N_("E218: Autocommand nesting too deep")); +EXTERN char e_missing_open_curly[] + INIT(= N_("E219: Missing {.")); +EXTERN char e_missing_close_curly[] + INIT(= N_("E220: Missing }.")); +#ifdef FEAT_EVAL +EXTERN char e_marker_cannot_start_with_lower_case_letter[] + INIT(= N_("E221: Marker cannot start with lower case letter")); +#endif +EXTERN char e_add_to_internal_buffer_that_was_already_read_from[] + INIT(= "E222: Add to internal buffer that was already read from"); +EXTERN char e_recursive_mapping[] + INIT(= N_("E223: Recursive mapping")); +EXTERN char e_global_abbreviation_already_exists_for_str[] + INIT(= N_("E224: Global abbreviation already exists for %s")); +EXTERN char e_global_mapping_already_exists_for_str[] + INIT(= N_("E225: Global mapping already exists for %s")); +EXTERN char e_abbreviation_already_exists_for_str[] + INIT(= N_("E226: Abbreviation already exists for %s")); +EXTERN char e_mapping_already_exists_for_str[] + INIT(= N_("E227: Mapping already exists for %s")); +EXTERN char e_makemap_illegal_mode[] + INIT(= "E228: makemap: Illegal mode"); +#ifdef FEAT_GUI +EXTERN char e_cannot_start_the_GUI[] + INIT(= N_("E229: Cannot start the GUI")); +EXTERN char e_cannot_read_from_str[] + INIT(= N_("E230: Cannot read from \"%s\"")); +EXTERN char e_guifontwide_invalid[] + INIT(= N_("E231: 'guifontwide' invalid")); +#ifdef FEAT_BEVAL_GUI +EXTERN char e_cannot_create_ballooneval_with_both_message_and_callback[] + INIT(= "E232: Cannot create BalloonEval with both message and callback"); +#endif +# if defined(FEAT_GUI_GTK) || defined(FEAT_GUI_X11) +EXTERN char e_cannot_open_display[] + INIT(= N_("E233: Cannot open display")); +# endif +# if defined(FEAT_XFONTSET) +EXTERN char e_unknown_fontset_str[] + INIT(= N_("E234: Unknown fontset: %s")); +# endif +# if defined(FEAT_GUI_X11) || defined(FEAT_GUI_GTK) \ + || defined(FEAT_GUI_PHOTON) || defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_HAIKU) +EXTERN char e_unknown_font_str[] + INIT(= N_("E235: Unknown font: %s")); +# endif +# if defined(FEAT_GUI_X11) && !defined(FEAT_GUI_GTK) +EXTERN char e_font_str_is_not_fixed_width[] + INIT(= N_("E236: Font \"%s\" is not fixed-width")); +# endif +#endif +#ifdef MSWIN +EXTERN char e_printer_selection_failed[] + INIT(= N_("E237: Printer selection failed")); +EXTERN char e_print_error_str[] + INIT(= N_("E238: Print error: %s")); +#endif +#ifdef FEAT_SIGNS +EXTERN char e_invalid_sign_text_str[] + INIT(= N_("E239: Invalid sign text: %s")); +#endif +#if defined(FEAT_CLIENTSERVER) && defined(FEAT_X11) +EXTERN char e_no_connection_to_x_server[] + INIT(= N_("E240: No connection to the X server")); +#endif +#ifdef FEAT_CLIENTSERVER +EXTERN char e_unable_to_send_to_str[] + INIT(= N_("E241: Unable to send to %s")); +#endif +EXTERN char e_cant_split_window_while_closing_another[] + INIT(= N_("E242: Can't split a window while closing another")); +#if defined(FEAT_GUI_MSWIN) && !defined(FEAT_OLE) +EXTERN char e_argument_not_supported_str_use_ole_version[] + INIT(= N_("E243: Argument not supported: \"-%s\"; Use the OLE version.")); +#endif +#ifdef MSWIN +EXTERN char e_illegal_str_name_str_in_font_name_str[] + INIT(= N_("E244: Illegal %s name \"%s\" in font name \"%s\"")); +EXTERN char e_illegal_char_nr_in_font_name_str[] + INIT(= N_("E245: Illegal char '%c' in font name \"%s\"")); +#endif +EXTERN char e_filechangedshell_autocommand_deleted_buffer[] + INIT(= N_("E246: FileChangedShell autocommand deleted buffer")); +#ifdef FEAT_CLIENTSERVER +EXTERN char e_no_registered_server_named_str[] + INIT(= N_("E247: No registered server named \"%s\"")); +EXTERN char e_failed_to_send_command_to_destination_program[] + INIT(= N_("E248: Failed to send command to the destination program")); +#endif +EXTERN char e_window_layout_changed_unexpectedly[] + INIT(= N_("E249: Window layout changed unexpectedly")); +#ifdef FEAT_XFONTSET +EXTERN char e_fonts_for_the_following_charsets_are_missing_in_fontset[] + INIT(= N_("E250: Fonts for the following charsets are missing in fontset %s:")); +#endif +#ifdef FEAT_CLIENTSERVER +EXTERN char e_vim_instance_registry_property_is_badly_formed_deleted[] + INIT(= N_("E251: VIM instance registry property is badly formed. Deleted!")); +#endif +#ifdef FEAT_GUI_X11 +EXTERN char e_fontsent_name_str_font_str_is_not_fixed_width[] + INIT(= N_("E252: Fontset name: %s - Font '%s' is not fixed-width")); +EXTERN char e_fontset_name_str[] + INIT(= N_("E253: Fontset name: %s")); +#endif +#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) +EXTERN char e_cannot_allocate_color_str[] + INIT(= N_("E254: Cannot allocate color %s")); +#endif +#if defined(FEAT_SIGN_ICONS) && !defined(FEAT_GUI_GTK) +EXTERN char e_couldnt_read_in_sign_data[] + INIT(= N_("E255: Couldn't read in sign data")); +#endif +// E256 unused +#ifdef FEAT_CSCOPE +EXTERN char e_cstag_tag_not_founc[] + INIT(= N_("E257: cstag: Tag not found")); +#endif +#ifdef FEAT_CLIENTSERVER +EXTERN char e_unable_to_send_to_client[] + INIT(= N_("E258: Unable to send to client")); +#endif +#ifdef FEAT_CSCOPE +EXTERN char e_no_matches_found_for_cscope_query_str_of_str[] + INIT(= N_("E259: No matches found for cscope query %s of %s")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_missing_name_after_method[] + INIT(= N_("E260: Missing name after ->")); +#endif +#ifdef FEAT_CSCOPE +EXTERN char e_cscope_connection_str_not_founc[] + INIT(= N_("E261: Cscope connection %s not found")); +EXTERN char e_error_reading_cscope_connection_nr[] + INIT(= N_("E262: Error reading cscope connection %d")); +#endif +#if defined(DYNAMIC_PYTHON) || defined(DYNAMIC_PYTHON3) +EXTERN char e_sorry_this_command_is_disabled_python_library_could_not_be_found[] + INIT(= N_("E263: Sorry, this command is disabled, the Python library could not be loaded.")); +#endif +#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) +EXTERN char e_python_error_initialising_io_object[] + INIT(= N_("E264: Python: Error initialising I/O objects")); +#endif +#ifdef FEAT_RUBY +EXTERN char e_dollar_must_be_an_instance_of_string[] + INIT(= N_("E265: $_ must be an instance of String")); +#endif +#ifdef DYNAMIC_RUBY +EXTERN char e_sorry_this_command_is_disabled_the_ruby_library_could_not_be_loaded[] + INIT(= N_("E266: Sorry, this command is disabled, the Ruby library could not be loaded.")); +#endif +#ifdef FEAT_RUBY +EXTERN char e_unexpected_return[] + INIT(= N_("E267: Unexpected return")); +EXTERN char e_unexpected_next[] + INIT(= N_("E268: Unexpected next")); +EXTERN char e_unexpected_break[] + INIT(= N_("E269: Unexpected break")); +EXTERN char e_unexpected_redo[] + INIT(= N_("E270: Unexpected redo")); +EXTERN char e_retry_outside_of_rescue_clause[] + INIT(= N_("E271: Retry outside of rescue clause")); +EXTERN char e_unhandled_exception[] + INIT(= N_("E272: Unhandled exception")); +EXTERN char e_unknown_longjmp_status_nr[] + INIT(= N_("E273: Unknown longjmp status %d")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_no_white_space_allowed_before_parenthesis[] + INIT(= N_("E274: No white space allowed before parenthesis")); +#endif +#ifdef FEAT_PROP_POPUP +EXTERN char e_cannot_add_text_property_to_unloaded_buffer[] + INIT(= N_("E275: Cannot add text property to unloaded buffer")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_cannot_use_function_as_method_str[] + INIT(= N_("E276: Cannot use function as a method: %s")); +#endif +#ifdef FEAT_CLIENTSERVER +EXTERN char e_unable_to_read_server_reply[] + INIT(= N_("E277: Unable to read a server reply")); +#endif +// E278 unused +#if defined(FEAT_TERMINAL) && !defined(UNIX) && !defined(MSWIN) +EXTERN char e_sorry_plusplusshell_not_supported_on_this_system[] + INIT(= N_("E279: Sorry, ++shell is not supported on this system")); +#endif +#ifdef FEAT_TCL +EXTERN char e_tcl_fatal_error_reflist_corrupt_please_report_this[] + INIT(= "E280: TCL FATAL ERROR: reflist corrupt!? Please report this to vim-dev@vim.org"); +#endif +// E281 unused +EXTERN char e_cannot_read_from_str_2[] + INIT(= N_("E282: Cannot read from \"%s\"")); +EXTERN char e_no_marks_matching_str[] + INIT(= N_("E283: No marks matching \"%s\"")); +#ifdef FEAT_XIM +# ifndef FEAT_GUI_GTK +EXTERN char e_cannot_set_ic_values[] + INIT(= N_("E284: Cannot set IC values")); +# endif +# ifdef FEAT_GUI_X11 +EXTERN char e_failed_to_create_input_context[] + INIT(= N_("E285: Failed to create input context")); +EXTERN char e_failed_to_open_input_method[] + INIT(= N_("E286: Failed to open input method")); +EXTERN char e_warning_could_not_set_destroy_callback_to_im[] + INIT(= N_("E287: Warning: Could not set destroy callback to IM")); +EXTERN char e_input_method_doesnt_support_any_style[] + INIT(= N_("E288: Input method doesn't support any style")); +EXTERN char e_input_method_doesnt_support_my_preedit_type[] + INIT(= N_("E289: Input method doesn't support my preedit type")); +# endif +#endif +#ifdef FEAT_SEARCH_EXTRA +EXTERN char e_list_or_number_required[] + INIT(= N_("E290: List or number required")); +#endif +// E291 unused +EXTERN char e_invalid_count_for_del_bytes_nr[] + INIT(= "E292: Invalid count for del_bytes(): %ld"); +EXTERN char e_block_was_not_locked[] + INIT(= "E293: Block was not locked"); +EXTERN char e_seek_error_in_swap_file_read[] + INIT(= N_("E294: Seek error in swap file read")); +EXTERN char e_read_error_in_swap_file[] + INIT(= N_("E295: Read error in swap file")); +EXTERN char e_seek_error_in_swap_file_write[] + INIT(= N_("E296: Seek error in swap file write")); +EXTERN char e_write_error_in_swap_file[] + INIT(= N_("E297: Write error in swap file")); +EXTERN char e_didnt_get_block_nr_zero[] + INIT(= "E298: Didn't get block nr 0?"); +EXTERN char e_didnt_get_block_nr_one[] + INIT(= "E298: Didn't get block nr 1?"); +EXTERN char e_didnt_get_block_nr_two[] + INIT(= "E298: Didn't get block nr 2?"); +#ifdef FEAT_PERL +EXTERN char e_perl_evaluation_forbidden_in_sandbox_without_safe_module[] + INIT(= N_("E299: Perl evaluation forbidden in sandbox without the Safe module")); +#endif +EXTERN char e_swap_file_already_exists_symlink_attack[] + INIT(= N_("E300: Swap file already exists (symlink attack?)")); +EXTERN char e_oops_lost_the_swap_file[] + INIT(= N_("E301: Oops, lost the swap file!!!")); +EXTERN char e_could_not_rename_swap_file[] + INIT(= N_("E302: Could not rename swap file")); +EXTERN char e_unable_to_open_swap_file_for_str_recovery_impossible[] + INIT(= N_("E303: Unable to open swap file for \"%s\", recovery impossible")); +EXTERN char e_ml_upd_block0_didnt_get_block_zero[] + INIT(= "E304: ml_upd_block0(): Didn't get block 0??"); +EXTERN char e_no_swap_file_found_for_str[] + INIT(= N_("E305: No swap file found for %s")); +EXTERN char e_cannot_open_str[] + INIT(= N_("E306: Cannot open %s")); +EXTERN char e_str_does_not_look_like_vim_swap_file[] + INIT(= N_("E307: %s does not look like a Vim swap file")); +EXTERN char e_warning_original_file_may_have_been_changed[] + INIT(= N_("E308: Warning: Original file may have been changed")); +EXTERN char e_unable_to_read_block_one_from_str[] + INIT(= N_("E309: Unable to read block 1 from %s")); +EXTERN char e_block_one_id_wrong_str_not_swp_file[] + INIT(= N_("E310: Block 1 ID wrong (%s not a .swp file?)")); +EXTERN char e_recovery_interrupted[] + INIT(= N_("E311: Recovery Interrupted")); +EXTERN char e_errors_detected_while_recovering_look_for_lines_starting_with_questions[] + INIT(= N_("E312: Errors detected while recovering; look for lines starting with ???")); +EXTERN char e_cannot_preserve_there_is_no_swap_file[] + INIT(= N_("E313: Cannot preserve, there is no swap file")); +EXTERN char e_preserve_failed[] + INIT(= N_("E314: Preserve failed")); +EXTERN char e_ml_get_invalid_lnum_nr[] + INIT(= "E315: ml_get: Invalid lnum: %ld"); +EXTERN char e_ml_get_cannot_find_line_nr_in_buffer_nr_str[] + INIT(= "E316: ml_get: Cannot find line %ld in buffer %d %s"); +EXTERN char e_pointer_block_id_wrong[] + INIT(= "E317: Pointer block id wrong"); +EXTERN char e_pointer_block_id_wrong_two[] + INIT(= "E317: Pointer block id wrong 2"); +EXTERN char e_pointer_block_id_wrong_three[] + INIT(= "E317: Pointer block id wrong 3"); +EXTERN char e_pointer_block_id_wrong_four[] + INIT(= "E317: Pointer block id wrong 4"); +EXTERN char e_updated_too_many_blocks[] + INIT(= "E318: Updated too many blocks?"); +EXTERN char e_sorry_command_is_not_available_in_this_version[] + INIT(= N_("E319: Sorry, the command is not available in this version")); +EXTERN char e_cannot_find_line_nr[] + INIT(= "E320: Cannot find line %ld"); +EXTERN char e_could_not_reload_str[] + INIT(= N_("E321: Could not reload \"%s\"")); +EXTERN char e_line_number_out_of_range_nr_past_the_end[] + INIT(= "E322: Line number out of range: %ld past the end"); +EXTERN char e_line_count_wrong_in_block_nr[] + INIT(= "E323: Line count wrong in block %ld"); +#ifdef FEAT_POSTSCRIPT +EXTERN char e_cant_open_postscript_output_file[] + INIT(= N_("E324: Can't open PostScript output file")); +#endif +EXTERN char e_attention[] + INIT(= N_("E325: ATTENTION")); +EXTERN char e_too_many_swap_files_found[] + INIT(= N_("E326: Too many swap files found")); +#ifdef FEAT_MENU +EXTERN char_u e_part_of_menu_item_path_is_not_sub_menu[] + INIT(= N_("E327: Part of menu-item path is not sub-menu")); +EXTERN char e_menu_only_exists_in_another_mode[] + INIT(= N_("E328: Menu only exists in another mode")); +EXTERN char_u e_no_menu_str[] + INIT(= N_("E329: No menu \"%s\"")); +EXTERN char e_menu_path_must_not_lead_to_sub_menu[] + INIT(= N_("E330: Menu path must not lead to a sub-menu")); +EXTERN char e_must_not_add_menu_items_directly_to_menu_bar[] + INIT(= N_("E331: Must not add menu items directly to menu bar")); +EXTERN char e_separator_cannot_be_part_of_menu_path[] + INIT(= N_("E332: Separator cannot be part of a menu path")); +EXTERN char e_menu_path_must_lead_to_menu_item[] + INIT(= N_("E333: Menu path must lead to a menu item")); +EXTERN char e_menu_not_found_str[] + INIT(= N_("E334: Menu not found: %s")); +EXTERN char e_menu_not_defined_for_str_mode[] + INIT(= N_("E335: Menu not defined for %s mode")); +EXTERN char e_menu_path_must_lead_to_sub_menu[] + INIT(= N_("E336: Menu path must lead to a sub-menu")); +EXTERN char e_menu_not_found_check_menu_names[] + INIT(= N_("E337: Menu not found - check menu names")); +#endif +#ifdef FEAT_BROWSE +EXTERN char e_sorry_no_file_browser_in_console_mode[] + INIT(= N_("E338: Sorry, no file browser in console mode")); +#endif +EXTERN char e_pattern_too_long[] + INIT(= N_("E339: Pattern too long")); +EXTERN char e_internal_error_please_report_a_bug[] + INIT(= N_("E340: Internal error; if you can reproduce please report a bug")); +EXTERN char e_internal_error_lalloc_zero[] + INIT(= "E341: Internal error: lalloc(0, )"); +EXTERN char e_out_of_memory_allocating_nr_bytes[] + INIT(= N_("E342: Out of memory! (allocating %lu bytes)")); +EXTERN char e_invalid_path_number_must_be_at_end_of_path_or_be_followed_by_str[] + INIT(= N_("E343: Invalid path: '**[number]' must be at the end of the path or be followed by '%s'.")); +EXTERN char e_cant_find_directory_str_in_cdpath[] + INIT(= N_("E344: Can't find directory \"%s\" in cdpath")); +EXTERN char e_cant_find_file_str_in_path[] + INIT(= N_("E345: Can't find file \"%s\" in path")); +EXTERN char e_no_more_directory_str_found_in_cdpath[] + INIT(= N_("E346: No more directory \"%s\" found in cdpath")); +EXTERN char e_no_more_file_str_found_in_path[] + INIT(= N_("E347: No more file \"%s\" found in path")); +EXTERN char e_no_string_under_cursor[] + INIT(= N_("E348: No string under cursor")); +EXTERN char e_no_identifier_under_cursor[] + INIT(= N_("E349: No identifier under cursor")); +#ifdef FEAT_FOLDING +EXTERN char e_cannot_create_fold_with_current_foldmethod[] + INIT(= N_("E350: Cannot create fold with current 'foldmethod'")); +EXTERN char e_cannot_delete_fold_with_current_foldmethod[] + INIT(= N_("E351: Cannot delete fold with current 'foldmethod'")); +EXTERN char e_cannot_erase_folds_with_current_foldmethod[] + INIT(= N_("E352: Cannot erase folds with current 'foldmethod'")); +#endif +EXTERN char e_nothing_in_register_str[] + INIT(= N_("E353: Nothing in register %s")); +EXTERN char e_invalid_register_name_str[] + INIT(= N_("E354: Invalid register name: '%s'")); +EXTERN char e_unknown_option_str_2[] + INIT(= N_("E355: Unknown option: %s")); +EXTERN char e_get_varp_error[] + INIT(= "E356: get_varp ERROR"); +#ifdef FEAT_LANGMAP +EXTERN char e_langmap_matching_character_missing_for_str[] + INIT(= N_("E357: 'langmap': Matching character missing for %s")); +EXTERN char e_langmap_extra_characters_after_semicolon_str[] + INIT(= N_("E358: 'langmap': Extra characters after semicolon: %s")); +#endif +#if defined(AMIGA) || defined(MACOS_X) || defined(MSWIN) \ + || defined(UNIX) || defined(VMS) +EXTERN char e_screen_mode_setting_not_supported[] + INIT(= N_("E359: Screen mode setting not supported")); +#endif +#ifdef AMIGA +EXTERN char e_cannot_execute_shell_with_f_option[] + INIT(= N_("E360: Cannot execute shell with -f option")); +#endif +// E361 unused +#if defined(FEAT_EVAL) +EXTERN char e_using_boolean_value_as_float[] + INIT(= N_("E362: Using a boolean value as a Float")); +#endif +EXTERN char e_pattern_uses_more_memory_than_maxmempattern[] + INIT(= N_("E363: Pattern uses more memory than 'maxmempattern'")); +#ifdef FEAT_LIBCALL +EXTERN char e_library_call_failed_for_str[] + INIT(= N_("E364: Library call failed for \"%s()\"")); +#endif +#ifdef FEAT_POSTSCRIPT +EXTERN char e_failed_to_print_postscript_file[] + INIT(= N_("E365: Failed to print PostScript file")); +#endif +#ifdef FEAT_PROP_POPUP +EXTERN char e_not_allowed_to_enter_popup_window[] + INIT(= N_("E366: Not allowed to enter a popup window")); +#endif +EXTERN char e_no_such_group_str[] + INIT(= N_("E367: No such group: \"%s\"")); +#ifdef FEAT_LIBCALL +EXTERN char e_got_sig_str_in_libcall[] + INIT(= N_("E368: Got SIG%s in libcall()")); +#endif +EXTERN char e_invalid_item_in_str_brackets[] + INIT(= N_("E369: Invalid item in %s%%[]")); +#ifdef USING_LOAD_LIBRARY +EXTERN char e_could_not_load_library_str_str[] + INIT(= N_("E370: Could not load library %s: %s")); +#endif +#ifdef FEAT_GUI_MSWIN +EXTERN char e_command_not_found[] + INIT(= N_("E371: Command not found")); +#endif +#ifdef FEAT_QUICKFIX +EXTERN char e_too_many_chr_in_format_string[] + INIT(= N_("E372: Too many %%%c in format string")); +EXTERN char e_unexpected_chr_in_format_str[] + INIT(= N_("E373: Unexpected %%%c in format string")); +EXTERN char e_missing_rsb_in_format_string[] + INIT(= N_("E374: Missing ] in format string")); +EXTERN char e_unsupported_chr_in_format_string[] + INIT(= N_("E375: Unsupported %%%c in format string")); +EXTERN char e_invalid_chr_in_format_string_prefix[] + INIT(= N_("E376: Invalid %%%c in format string prefix")); +EXTERN char e_invalid_chr_in_format_string[] + INIT(= N_("E377: Invalid %%%c in format string")); +EXTERN char e_errorformat_contains_no_pattern[] + INIT(= N_("E378: 'errorformat' contains no pattern")); +EXTERN char e_missing_or_empty_directory_name[] + INIT(= N_("E379: Missing or empty directory name")); +EXTERN char e_at_bottom_of_quickfix_stack[] + INIT(= N_("E380: At bottom of quickfix stack")); +EXTERN char e_at_top_of_quickfix_stack[] + INIT(= N_("E381: At top of quickfix stack")); +#endif +EXTERN char e_cannot_write_buftype_option_is_set[] + INIT(= N_("E382: Cannot write, 'buftype' option is set")); +EXTERN char e_invalid_search_string_str[] + INIT(= N_("E383: Invalid search string: %s")); +EXTERN char e_search_hit_top_without_match_for_str[] + INIT(= N_("E384: Search hit TOP without match for: %s")); +EXTERN char e_search_hit_bottom_without_match_for_str[] + INIT(= N_("E385: Search hit BOTTOM without match for: %s")); +EXTERN char e_expected_question_or_slash_after_semicolon[] + INIT(= N_("E386: Expected '?' or '/' after ';'")); +#ifdef FEAT_FIND_ID +EXTERN char e_match_is_on_current_line[] + INIT(= N_("E387: Match is on current line")); +EXTERN char e_couldnt_find_definition[] + INIT(= N_("E388: Couldn't find definition")); +EXTERN char e_couldnt_find_pattern[] + INIT(= N_("E389: Couldn't find pattern")); +#endif +#ifdef FEAT_SYN_HL +EXTERN char e_illegal_argument_str_2[] + INIT(= N_("E390: Illegal argument: %s")); +EXTERN char e_no_such_syntax_cluster_str_1[] + INIT(= N_("E391: No such syntax cluster: %s")); +EXTERN char e_no_such_syntax_cluster_str_2[] + INIT(= N_("E392: No such syntax cluster: %s")); +EXTERN char e_groupthere_not_accepted_here[] + INIT(= N_("E393: group[t]here not accepted here")); +EXTERN char e_didnt_find_region_item_for_str[] + INIT(= N_("E394: Didn't find region item for %s")); +EXTERN char e_contains_argument_not_accepted_here[] + INIT(= N_("E395: Contains argument not accepted here")); +// E396 unused +EXTERN char e_filename_required[] + INIT(= N_("E397: Filename required")); +EXTERN char e_missing_equal_str[] + INIT(= N_("E398: Missing '=': %s")); +EXTERN char e_not_enough_arguments_syntax_region_str[] + INIT(= N_("E399: Not enough arguments: syntax region %s")); +EXTERN char e_no_cluster_specified[] + INIT(= N_("E400: No cluster specified")); +EXTERN char e_pattern_delimiter_not_found_str[] + INIT(= N_("E401: Pattern delimiter not found: %s")); +EXTERN char e_garbage_after_pattern_str[] + INIT(= N_("E402: Garbage after pattern: %s")); +EXTERN char e_syntax_sync_line_continuations_pattern_specified_twice[] + INIT(= N_("E403: syntax sync: Line continuations pattern specified twice")); +EXTERN char e_illegal_arguments_str[] + INIT(= N_("E404: Illegal arguments: %s")); +EXTERN char e_missing_equal_sign_str[] + INIT(= N_("E405: Missing equal sign: %s")); +EXTERN char e_empty_argument_str[] + INIT(= N_("E406: Empty argument: %s")); +EXTERN char e_str_not_allowed_here[] + INIT(= N_("E407: %s not allowed here")); +EXTERN char e_str_must_be_first_in_contains_list[] + INIT(= N_("E408: %s must be first in contains list")); +EXTERN char e_unknown_group_name_str[] + INIT(= N_("E409: Unknown group name: %s")); +EXTERN char e_invalid_syntax_subcommand_str[] + INIT(= N_("E410: Invalid :syntax subcommand: %s")); +#endif +EXTERN char e_highlight_group_name_not_found_str[] + INIT(= N_("E411: Highlight group not found: %s")); +EXTERN char e_not_enough_arguments_highlight_link_str[] + INIT(= N_("E412: Not enough arguments: \":highlight link %s\"")); +EXTERN char e_too_many_arguments_highlight_link_str[] + INIT(= N_("E413: Too many arguments: \":highlight link %s\"")); +EXTERN char e_group_has_settings_highlight_link_ignored[] + INIT(= N_("E414: Group has settings, highlight link ignored")); +EXTERN char e_unexpected_equal_sign_str[] + INIT(= N_("E415: Unexpected equal sign: %s")); +EXTERN char e_missing_equal_sign_str_2[] + INIT(= N_("E416: Missing equal sign: %s")); +EXTERN char e_missing_argument_str[] + INIT(= N_("E417: Missing argument: %s")); +EXTERN char e_illegal_value_str[] + INIT(= N_("E418: Illegal value: %s")); +EXTERN char e_im_a_teapot[] + INIT(= N_("E418: I'm a teapot")); +EXTERN char e_fg_color_unknown[] + INIT(= N_("E419: FG color unknown")); +EXTERN char e_bg_color_unknown[] + INIT(= N_("E420: BG color unknown")); +EXTERN char e_color_name_or_number_not_recognized_str[] + INIT(= N_("E421: Color name or number not recognized: %s")); +EXTERN char e_terminal_code_too_long_str[] + INIT(= N_("E422: Terminal code too long: %s")); +EXTERN char e_illegal_argument_str_3[] + INIT(= N_("E423: Illegal argument: %s")); +EXTERN char e_too_many_different_highlighting_attributes_in_use[] + INIT(= N_("E424: Too many different highlighting attributes in use")); +EXTERN char e_cannot_go_before_first_matching_tag[] + INIT(= N_("E425: Cannot go before first matching tag")); +EXTERN char e_tag_not_found_str[] + INIT(= N_("E426: Tag not found: %s")); +EXTERN char e_there_is_only_one_matching_tag[] + INIT(= N_("E427: There is only one matching tag")); +EXTERN char e_cannot_go_beyond_last_matching_tag[] + INIT(= N_("E428: Cannot go beyond last matching tag")); +EXTERN char e_file_str_does_not_exist[] + INIT(= N_("E429: File \"%s\" does not exist")); +#ifdef FEAT_EMACS_TAGS +EXTERN char e_tag_file_path_truncated_for_str[] + INIT(= N_("E430: Tag file path truncated for %s\n")); +#endif +EXTERN char e_format_error_in_tags_file_str[] + INIT(= N_("E431: Format error in tags file \"%s\"")); +EXTERN char e_tags_file_not_sorted_str[] + INIT(= N_("E432: Tags file not sorted: %s")); +EXTERN char e_no_tags_file[] + INIT(= N_("E433: No tags file")); +EXTERN char e_cannot_find_tag_pattern[] + INIT(= N_("E434: Can't find tag pattern")); +EXTERN char e_couldnt_find_tag_just_guessing[] + INIT(= N_("E435: Couldn't find tag, just guessing!")); +EXTERN char e_no_str_entry_in_termcap[] + INIT(= N_("E436: No \"%s\" entry in termcap")); +EXTERN char e_terminal_capability_cm_required[] + INIT(= N_("E437: Terminal capability \"cm\" required")); +EXTERN char e_u_undo_line_numbers_wrong[] + INIT(= "E438: u_undo: Line numbers wrong"); +EXTERN char e_undo_list_corrupt[] + INIT(= "E439: Undo list corrupt"); +EXTERN char e_undo_line_missing[] + INIT(= "E440: Undo line missing"); +#ifdef FEAT_QUICKFIX +EXTERN char e_there_is_no_preview_window[] + INIT(= N_("E441: There is no preview window")); +#endif +EXTERN char e_cant_split_topleft_and_botright_at_the_same_time[] + INIT(= N_("E442: Can't split topleft and botright at the same time")); +EXTERN char e_cannot_rotate_when_another_window_is_split[] + INIT(= N_("E443: Cannot rotate when another window is split")); +EXTERN char e_cannot_close_last_window[] + INIT(= N_("E444: Cannot close last window")); +EXTERN char e_other_window_contains_changes[] + INIT(= N_("E445: Other window contains changes")); +EXTERN char e_no_file_name_under_cursor[] + INIT(= N_("E446: No file name under cursor")); +EXTERN char e_cant_find_file_str_in_path_2[] + INIT(= N_("E447: Can't find file \"%s\" in path")); +#ifdef USING_LOAD_LIBRARY +EXTERN char e_could_not_load_library_function_str[] + INIT(= N_("E448: Could not load library function %s")); +#endif +#ifdef FEAT_CLIENTSERVER +EXTERN char e_invalid_expression_received[] + INIT(= N_("E449: Invalid expression received")); +#endif +#ifdef FEAT_PROP_POPUP +EXTERN char e_buffer_number_text_or_list_required[] + INIT(= N_("E450: Buffer number, text or a list required")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_expected_right_curly_str[] + INIT(= N_("E451: Expected }: %s")); +EXTERN char e_double_semicolon_in_list_of_variables[] + INIT(= N_("E452: Double ; in list of variables")); +#endif +EXTERN char e_ul_color_unknown[] + INIT(= N_("E453: UL color unknown")); +#ifdef FEAT_EVAL +EXTERN char e_function_list_was_modified[] + INIT(= N_("E454: Function list was modified")); +#endif +#ifdef FEAT_POSTSCRIPT +EXTERN char e_error_writing_to_postscript_output_file[] + INIT(= N_("E455: Error writing to PostScript output file")); +EXTERN char e_cant_open_file_str_2[] + INIT(= N_("E456: Can't open file \"%s\"")); +EXTERN char e_cant_find_postscript_resource_file_str_ps[] + INIT(= N_("E456: Can't find PostScript resource file \"%s.ps\"")); +EXTERN char e_cant_read_postscript_resource_file_str[] + INIT(= N_("E457: Can't read PostScript resource file \"%s\"")); +#endif +#ifdef FEAT_GUI_X11 +EXTERN char e_cannot_allocate_colormap_entry_some_colors_may_be_incorrect[] + INIT(= N_("E458: Cannot allocate colormap entry, some colors may be incorrect")); +#endif +#if defined(UNIX) || defined(FEAT_SESSION) +EXTERN char e_cannot_go_back_to_previous_directory[] + INIT(= N_("E459: Cannot go back to previous directory")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_entries_missing_in_mapset_dict_argument[] + INIT(= N_("E460: Entries missing in mapset() dict argument")); +EXTERN char e_illegal_variable_name_str[] + INIT(= N_("E461: Illegal variable name: %s")); +#endif +EXTERN char e_could_not_prepare_for_reloading_str[] + INIT(= N_("E462: Could not prepare for reloading \"%s\"")); +#ifdef FEAT_NETBEANS_INTG +EXTERN char e_region_is_guarded_cannot_modify[] + INIT(= N_("E463: Region is guarded, cannot modify")); +#endif +EXTERN char e_ambiguous_use_of_user_defined_command[] + INIT(= N_("E464: Ambiguous use of user-defined command")); +#ifdef FEAT_EVAL +EXTERN char e_ambiguous_use_of_user_defined_command_str[] + INIT(= N_("E464: Ambiguous use of user-defined command: %s")); +#endif +EXTERN char e_winsize_requires_two_number_arguments[] + INIT(= N_("E465: :winsize requires two number arguments")); +EXTERN char e_winpos_requires_two_number_arguments[] + INIT(= N_("E466: :winpos requires two number arguments")); +#ifdef FEAT_EVAL +EXTERN char e_custom_completion_requires_function_argument[] + INIT(= N_("E467: Custom completion requires a function argument")); +#endif +EXTERN char e_completion_argument_only_allowed_for_custom_completion[] + INIT(= N_("E468: Completion argument only allowed for custom completion")); +#ifdef FEAT_CSCOPE +EXTERN char e_invalid_cscopequickfix_flag_chr_for_chr[] + INIT(= N_("E469: Invalid cscopequickfix flag %c for %c")); +#endif +EXTERN char e_command_aborted[] + INIT(= N_("E470: Command aborted")); +EXTERN char e_argument_required[] + INIT(= N_("E471: Argument required")); +EXTERN char e_command_failed[] + INIT(= N_("E472: Command failed")); +EXTERN char e_internal_error_in_regexp[] + INIT(= "E473: Internal error in regexp"); +EXTERN char e_invalid_argument[] + INIT(= N_("E474: Invalid argument")); +EXTERN char e_invalid_argument_str[] + INIT(= N_("E475: Invalid argument: %s")); +EXTERN char e_invalid_value_for_argument_str[] + INIT(= N_("E475: Invalid value for argument %s")); +#if defined(FEAT_JOB_CHANNEL) || defined(FEAT_PROP_POPUP) || defined(FEAT_EVAL) +EXTERN char e_invalid_value_for_argument_str_str[] + INIT(= N_("E475: Invalid value for argument %s: %s")); +#endif +EXTERN char e_invalid_command[] + INIT(= N_("E476: Invalid command")); +EXTERN char e_invalid_command_str[] + INIT(= N_("E476: Invalid command: %s")); +#ifdef FEAT_EVAL +EXTERN char e_invalid_command_str_expected_str[] + INIT(= N_("E476: Invalid command: %s, expected %s")); +#endif +EXTERN char e_no_bang_allowed[] + INIT(= N_("E477: No ! allowed")); +EXTERN char e_dont_panic[] + INIT(= N_("E478: Don't panic!")); +EXTERN char e_no_match[] + INIT(= N_("E479: No match")); +EXTERN char e_no_match_str_2[] + INIT(= N_("E480: No match: %s")); +EXTERN char e_no_range_allowed[] + INIT(= N_("E481: No range allowed")); +EXTERN char e_cant_create_file_str[] + INIT(= N_("E482: Can't create file %s")); +EXTERN char e_cant_get_temp_file_name[] + INIT(= N_("E483: Can't get temp file name")); +EXTERN char e_cant_open_file_str[] + INIT(= N_("E484: Can't open file %s")); +EXTERN char e_cant_read_file_str[] + INIT(= N_("E485: Can't read file %s")); +EXTERN char e_pattern_not_found[] + INIT(= N_("E486: Pattern not found")); +EXTERN char e_pattern_not_found_str[] + INIT(= N_("E486: Pattern not found: %s")); +EXTERN char e_argument_must_be_positive[] + INIT(= N_("E487: Argument must be positive")); +#ifdef FEAT_PROP_POPUP +EXTERN char e_argument_must_be_positive_str[] + INIT(= N_("E487: Argument must be positive: %s")); +#endif +EXTERN char e_trailing_characters[] + INIT(= N_("E488: Trailing characters")); +EXTERN char e_trailing_characters_str[] + INIT(= N_("E488: Trailing characters: %s")); +EXTERN char e_no_call_stack_to_substitute_for_stack[] + INIT(= N_("E489: No call stack to substitute for \"\"")); +#ifdef FEAT_FOLDING +EXTERN char e_no_fold_found[] + INIT(= N_("E490: No fold found")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_json_decode_error_at_str[] + INIT(= N_("E491: JSON decode error at '%s'")); +#endif +EXTERN char e_not_an_editor_command[] + INIT(= N_("E492: Not an editor command")); +EXTERN char e_backwards_range_given[] + INIT(= N_("E493: Backwards range given")); +EXTERN char e_use_w_or_w_gt_gt[] + INIT(= N_("E494: Use w or w>>")); +EXTERN char e_no_autocommand_file_name_to_substitute_for_afile[] + INIT(= N_("E495: No autocommand file name to substitute for \"\"")); +EXTERN char e_no_autocommand_buffer_number_to_substitute_for_abuf[] + INIT(= N_("E496: No autocommand buffer number to substitute for \"\"")); +EXTERN char e_no_autocommand_match_name_to_substitute_for_amatch[] + INIT(= N_("E497: No autocommand match name to substitute for \"\"")); +EXTERN char e_no_source_file_name_to_substitute_for_sfile[] + INIT(= N_("E498: No :source file name to substitute for \"\"")); +EXTERN char e_empty_file_name_for_percent_or_hash_only_works_with_ph[] + // xgettext:no-c-format + INIT(= N_("E499: Empty file name for '%' or '#', only works with \":p:h\"")); +EXTERN char e_evaluates_to_an_empty_string[] + INIT(= N_("E500: Evaluates to an empty string")); +EXTERN char e_at_end_of_file[] + INIT(= N_("E501: At end-of-file")); + // E502 +EXTERN char e_is_a_directory[] + INIT(= N_("is a directory")); + // E503 +EXTERN char e_is_not_file_or_writable_device[] + INIT(= N_("is not a file or writable device")); +EXTERN char e_str_is_not_file_or_writable_device[] + INIT(= N_("E503: \"%s\" is not a file or writable device")); +EXTERN char e_coffee_currently_not_available[] + INIT(= N_("E503: Coffee is currently not available")); + // E504 +EXTERN char e_is_read_only_cannot_override_W_in_cpoptions[] + INIT(= N_("is read-only (cannot override: \"W\" in 'cpoptions')")); + // E505 +EXTERN char e_is_read_only_add_bang_to_override[] + INIT(= N_("is read-only (add ! to override)")); +EXTERN char e_str_is_read_only_add_bang_to_override[] + INIT(= N_("E505: \"%s\" is read-only (add ! to override)")); +EXTERN char e_cant_write_to_backup_file_add_bang_to_override[] + INIT(= N_("E506: Can't write to backup file (add ! to override)")); +EXTERN char e_close_error_for_backup_file_add_bang_to_write_anyway[] + INIT(= N_("E507: Close error for backup file (add ! to write anyway)")); +EXTERN char e_cant_read_file_for_backup_add_bang_to_write_anyway[] + INIT(= N_("E508: Can't read file for backup (add ! to write anyway)")); +EXTERN char e_cannot_create_backup_file_add_bang_to_write_anyway[] + INIT(= N_("E509: Cannot create backup file (add ! to override)")); +EXTERN char e_cant_make_backup_file_add_bang_to_write_anyway[] + INIT(= N_("E510: Can't make backup file (add ! to write anyway)")); +#ifdef FEAT_NETBEANS_INTG +EXTERN char e_netbeans_already_connected[] + INIT(= N_("E511: NetBeans already connected")); +#endif +EXTERN char e_close_failed[] + INIT(= N_("E512: Close failed")); +EXTERN char e_write_error_conversion_failed_make_fenc_empty_to_override[] + INIT(= N_("E513: Write error, conversion failed (make 'fenc' empty to override)")); +EXTERN char e_write_error_conversion_failed_in_line_nr_make_fenc_empty_to_override[] + INIT(= N_("E513: Write error, conversion failed in line %ld (make 'fenc' empty to override)")); +EXTERN char e_write_error_file_system_full[] + INIT(= N_("E514: Write error (file system full?)")); +EXTERN char e_no_buffers_were_unloaded[] + INIT(= N_("E515: No buffers were unloaded")); +EXTERN char e_no_buffers_were_deleted[] + INIT(= N_("E516: No buffers were deleted")); +EXTERN char e_no_buffers_were_wiped_out[] + INIT(= N_("E517: No buffers were wiped out")); +EXTERN char e_unknown_option[] + INIT(= N_("E518: Unknown option")); +EXTERN char e_option_not_supported[] + INIT(= N_("E519: Option not supported")); +EXTERN char e_not_allowed_in_modeline[] + INIT(= N_("E520: Not allowed in a modeline")); +EXTERN char e_number_required_after_equal[] + INIT(= N_("E521: Number required after =")); +EXTERN char e_number_required_after_str_equal_str[] + INIT(= N_("E521: Number required: &%s = '%s'")); +EXTERN char e_not_found_in_termcap[] + INIT(= N_("E522: Not found in termcap")); +EXTERN char e_not_allowed_here[] + INIT(= N_("E523: Not allowed here")); +EXTERN char e_missing_colon[] + INIT(= N_("E524: Missing colon")); +EXTERN char e_zero_length_string[] + INIT(= N_("E525: Zero length string")); +#ifdef FEAT_VIMINFO +EXTERN char e_missing_number_after_angle_str_angle[] + INIT(= N_("E526: Missing number after <%s>")); +EXTERN char e_missing_comma[] + INIT(= N_("E527: Missing comma")); +EXTERN char e_must_specify_a_value[] + INIT(= N_("E528: Must specify a ' value")); +#endif +EXTERN char e_cannot_set_term_to_empty_string[] + INIT(= N_("E529: Cannot set 'term' to empty string")); +#ifdef FEAT_GUI +EXTERN char e_cannot_change_term_in_GUI[] + INIT(= N_("E530: Cannot change 'term' in the GUI")); +EXTERN char e_use_gui_to_start_GUI[] + INIT(= N_("E531: Use \":gui\" to start the GUI")); +#endif +#ifdef FEAT_NETBEANS_INTG +EXTERN char e_highlighting_color_name_too_long_in_defineAnnoType[] + INIT(= N_("E532: Highlighting color name too long in defineAnnoType")); +#endif +#ifdef FEAT_GUI +EXTERN char e_cant_select_wide_font[] + INIT(= N_("E533: Can't select wide font")); +EXTERN char e_invalid_wide_font[] + INIT(= N_("E534: Invalid wide font")); +#endif +EXTERN char e_illegal_character_after_chr[] + INIT(= N_("E535: Illegal character after <%c>")); +#ifdef FEAT_FOLDING +EXTERN char e_comma_required[] + INIT(= N_("E536: Comma required")); +EXTERN char e_commentstring_must_be_empty_or_contain_str[] + INIT(= N_("E537: 'commentstring' must be empty or contain %s")); +#endif +EXTERN char e_pattern_found_in_every_line_str[] + INIT(= N_("E538: Pattern found in every line: %s")); +EXTERN char e_illegal_character_str[] + INIT(= N_("E539: Illegal character <%s>")); +#ifdef FEAT_STL_OPT +EXTERN char e_unclosed_expression_sequence[] + INIT(= N_("E540: Unclosed expression sequence")); +// E541 unused +EXTERN char e_unbalanced_groups[] + INIT(= N_("E542: Unbalanced groups")); +#endif +#ifdef MSWIN +EXTERN char e_not_valid_codepage[] + INIT(= N_("E543: Not a valid codepage")); +#endif +#ifdef FEAT_KEYMAP +EXTERN char e_keymap_file_not_found[] + INIT(= N_("E544: Keymap file not found")); +#endif +#ifdef CURSOR_SHAPE +EXTERN char e_missing_colon_2[] + INIT(= N_("E545: Missing colon")); +EXTERN char e_illegal_mode[] + INIT(= N_("E546: Illegal mode")); +#endif +#ifdef FEAT_MOUSESHAPE +EXTERN char e_illegal_mouseshape[] + INIT(= N_("E547: Illegal mouseshape")); +#endif +#ifdef CURSOR_SHAPE +EXTERN char e_digit_expected[] + INIT(= N_("E548: Digit expected")); +EXTERN char e_illegal_percentage[] + INIT(= N_("E549: Illegal percentage")); +#endif +#ifdef FEAT_PRINTER +EXTERN char e_missing_colon_3[] + INIT(= N_("E550: Missing colon")); +EXTERN char e_illegal_component[] + INIT(= N_("E551: Illegal component")); +EXTERN char e_digit_expected_2[] + INIT(= N_("E552: Digit expected")); +#endif +#ifdef FEAT_QUICKFIX +EXTERN char e_no_more_items[] + INIT(= N_("E553: No more items")); +#endif +EXTERN char e_syntax_error_in_str_curlies[] + INIT(= N_("E554: Syntax error in %s{...}")); +EXTERN char e_at_bottom_of_tag_stack[] + INIT(= N_("E555: At bottom of tag stack")); +EXTERN char e_at_top_of_tag_stack[] + INIT(= N_("E556: At top of tag stack")); +EXTERN char e_cannot_open_termcap_file[] + INIT(= N_("E557: Cannot open termcap file")); +EXTERN char e_terminal_entry_not_found_in_terminfo[] + INIT(= N_("E558: Terminal entry not found in terminfo")); +#if defined(HAVE_TGETENT) && !defined(TERMINFO) +EXTERN char e_terminal_entry_not_found_in_termcap[] + INIT(= N_("E559: Terminal entry not found in termcap")); +#endif +#ifdef FEAT_CSCOPE +EXTERN char e_usage_cscope_str[] + INIT(= N_("E560: Usage: cs[cope] %s")); +EXTERN char e_unknown_cscope_search_type[] + INIT(= N_("E561: Unknown cscope search type")); +EXTERN char e_usage_cstag_ident[] + INIT(= N_("E562: Usage: cstag ")); +EXTERN char e_stat_str_error_nr[] + INIT(= N_("E563: stat(%s) error: %d")); +EXTERN char e_str_is_not_directory_or_valid_cscope_database[] + INIT(= N_("E564: %s is not a directory or a valid cscope database")); +#endif +EXTERN char e_not_allowed_to_change_text_or_change_window[] + INIT(= N_("E565: Not allowed to change text or change window")); +#ifdef FEAT_CSCOPE +EXTERN char e_could_not_create_cscope_pipes[] + INIT(= N_("E566: Could not create cscope pipes")); +EXTERN char e_no_cscope_connections[] + INIT(= N_("E567: No cscope connections")); +EXTERN char e_duplicate_cscope_database_not_added[] + INIT(= N_("E568: Duplicate cscope database not added")); +// E569 unused +EXTERN char e_fatal_error_in_cs_manage_matches[] + INIT(= "E570: Fatal error in cs_manage_matches"); +#endif +#ifdef DYNAMIC_TCL +EXTERN char e_sorry_this_command_is_disabled_tcl_library_could_not_be_loaded[] + INIT(= N_("E571: Sorry, this command is disabled: the Tcl library could not be loaded.")); +#endif +#ifdef FEAT_TCL +EXTERN char e_exit_code_nr[] + INIT(= N_("E572: Exit code %d")); +#endif +#ifdef FEAT_CLIENTSERVER +EXTERN char e_invalid_server_id_used_str[] + INIT(= N_("E573: Invalid server id used: %s")); +#endif +#ifdef FEAT_VIMINFO +EXTERN char e_unknown_register_type_nr[] + INIT(= N_("E574: Unknown register type %d")); + // E575 +EXTERN char e_illegal_starting_char[] + INIT(= N_("Illegal starting char")); + // E576 +EXTERN char e_nonr_missing_gt[] + INIT(= N_("Missing '>'")); + // E577 +EXTERN char e_illegal_register_name[] + INIT(= N_("Illegal register name")); +#endif +// E578 unused +#ifdef FEAT_EVAL +EXTERN char e_if_nesting_too_deep[] + INIT(= N_("E579: :if nesting too deep")); +EXTERN char e_block_nesting_too_deep[] + INIT(= N_("E579: Block nesting too deep")); +EXTERN char e_endif_without_if[] + INIT(= N_("E580: :endif without :if")); +EXTERN char e_else_without_if[] + INIT(= N_("E581: :else without :if")); +EXTERN char e_elseif_without_if[] + INIT(= N_("E582: :elseif without :if")); +EXTERN char e_multiple_else[] + INIT(= N_("E583: Multiple :else")); +EXTERN char e_elseif_after_else[] + INIT(= N_("E584: :elseif after :else")); +EXTERN char e_while_for_nesting_too_deep[] + INIT(= N_("E585: :while/:for nesting too deep")); +EXTERN char e_continue_without_while_or_for[] + INIT(= N_("E586: :continue without :while or :for")); +EXTERN char e_break_without_while_or_for[] + INIT(= N_("E587: :break without :while or :for")); +EXTERN char e_endwhile_without_while[] + INIT(= N_("E588: :endwhile without :while")); +EXTERN char e_endfor_without_for[] + INIT(= N_("E588: :endfor without :for")); +#endif +EXTERN char e_backupext_and_patchmode_are_equal[] + INIT(= N_("E589: 'backupext' and 'patchmode' are equal")); +#ifdef FEAT_QUICKFIX +EXTERN char e_preview_window_already_exists[] + INIT(= N_("E590: A preview window already exists")); +#endif +EXTERN char e_winheight_cannot_be_smaller_than_winminheight[] + INIT(= N_("E591: 'winheight' cannot be smaller than 'winminheight'")); +EXTERN char e_winwidth_cannot_be_smaller_than_winminwidth[] + INIT(= N_("E592: 'winwidth' cannot be smaller than 'winminwidth'")); +EXTERN char e_need_at_least_nr_lines[] + INIT(= N_("E593: Need at least %d lines")); +EXTERN char e_need_at_least_nr_columns[] + INIT(= N_("E594: Need at least %d columns")); +#ifdef FEAT_LINEBREAK +EXTERN char e_showbreak_contains_unprintable_or_wide_character[] + INIT(= N_("E595: 'showbreak' contains unprintable or wide character")); +#endif +#ifdef FEAT_GUI +EXTERN char e_invalid_fonts[] + INIT(= N_("E596: Invalid font(s)")); +# ifdef FEAT_XFONTSET +EXTERN char e_cant_select_fontset[] + INIT(= N_("E597: Can't select fontset")); +EXTERN char e_invalid_fontset[] + INIT(= N_("E598: Invalid fontset")); +# endif +#endif +#if defined(FEAT_XIM) && defined(FEAT_GUI_GTK) +EXTERN char e_value_of_imactivatekey_is_invalid[] + INIT(= N_("E599: Value of 'imactivatekey' is invalid")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_missing_endtry[] + INIT(= N_("E600: Missing :endtry")); +EXTERN char e_try_nesting_too_deep[] + INIT(= N_("E601: :try nesting too deep")); +EXTERN char e_endtry_without_try[] + INIT(= N_("E602: :endtry without :try")); +EXTERN char e_catch_without_try[] + INIT(= N_("E603: :catch without :try")); +EXTERN char e_catch_after_finally[] + INIT(= N_("E604: :catch after :finally")); +EXTERN char e_exception_not_caught_str[] + INIT(= N_("E605: Exception not caught: %s")); +EXTERN char e_finally_without_try[] + INIT(= N_("E606: :finally without :try")); +EXTERN char e_multiple_finally[] + INIT(= N_("E607: Multiple :finally")); +EXTERN char e_cannot_throw_exceptions_with_vim_prefix[] + INIT(= N_("E608: Cannot :throw exceptions with 'Vim' prefix")); +#endif +#ifdef FEAT_CSCOPE +EXTERN char e_cscope_error_str[] + INIT(= N_("E609: Cscope error: %s")); +#endif +EXTERN char e_no_argument_to_delete[] + INIT(= N_("E610: No argument to delete")); +#ifdef FEAT_EVAL +EXTERN char e_using_special_as_number[] + INIT(= N_("E611: Using a Special as a Number")); +#endif +#ifdef FEAT_SIGNS +EXTERN char e_too_many_signs_defined[] + INIT(= N_("E612: Too many signs defined")); +#endif +#if defined(MSWIN) && defined(FEAT_PRINTER) +EXTERN char e_unknown_printer_font_str[] + INIT(= N_("E613: Unknown printer font: %s")); +#endif +EXTERN char e_class_required[] + INIT(= N_("E614: Class required")); +// E615 unused +EXTERN char e_object_required_for_argument_nr[] + INIT(= N_("E616: Object required for argument %d")); +#ifdef FEAT_GUI_GTK +EXTERN char e_cannot_be_changed_in_gtk_GUI[] + INIT(= N_("E617: Cannot be changed in the GTK GUI")); +#endif +#ifdef FEAT_POSTSCRIPT +EXTERN char e_file_str_is_not_postscript_resource_file[] + INIT(= N_("E618: File \"%s\" is not a PostScript resource file")); +EXTERN char e_file_str_is_not_supported_postscript_resource_file[] + INIT(= N_("E619: File \"%s\" is not a supported PostScript resource file")); +EXTERN char e_unable_to_convert_to_print_encoding_str[] + INIT(= N_("E620: Unable to convert to print encoding \"%s\"")); +EXTERN char e_str_resource_file_has_wrong_version[] + INIT(= N_("E621: \"%s\" resource file has wrong version")); +#endif +#ifdef FEAT_CSCOPE +EXTERN char e_could_not_fork_for_cscope[] + INIT(= N_("E622: Could not fork for cscope")); +# ifndef UNIX +EXTERN char e_could_not_spawn_cscope_process[] + INIT(= N_("E623: Could not spawn cscope process")); +# endif +#endif +#if defined(FEAT_PRINTER) && defined(FEAT_POSTSCRIPT) +EXTERN char e_cant_open_file_str_3[] + INIT(= N_("E624: Can't open file \"%s\"")); +#endif +#if defined(FEAT_CSCOPE) && !defined(UNIX) +EXTERN char e_cannot_open_cscope_database_str[] + INIT(= N_("E625: Cannot open cscope database: %s")); +EXTERN char e_cannot_get_cscope_database_information[] + INIT(= N_("E626: Cannot get cscope database information")); +#endif +#ifdef FEAT_NETBEANS_INTG +EXTERN char e_missing_colon_str[] + INIT(= N_("E627: Missing colon: %s")); +EXTERN char e_missing_bang_or_slash_in_str[] + INIT(= N_("E628: Missing ! or / in: %s")); +#ifdef NBDEBUG +EXTERN char e_bad_return_from_nb_do_cmd[] + INIT(= "E629: Bad return from nb_do_cmd"); +#endif +#endif +#ifdef FEAT_JOB_CHANNEL +EXTERN char e_str_write_while_not_connected[] + INIT(= N_("E630: %s(): Write while not connected")); +EXTERN char e_str_write_failed[] + INIT(= N_("E631: %s(): Write failed")); +#endif +#ifdef FEAT_NETBEANS_INTG +EXTERN char e_invalid_buffer_identifier_in_getlength[] + INIT(= N_("E632: Invalid buffer identifier in getLength")); +EXTERN char e_invalid_buffer_identifier_in_gettext[] + INIT(= N_("E633: Invalid buffer identifier in getText")); +EXTERN char e_invalid_buffer_identifier_in_remove[] + INIT(= N_("E634: Invalid buffer identifier in remove")); +EXTERN char e_invalid_buffer_identifier_in_insert[] + INIT(= N_("E635: Invalid buffer identifier in insert")); +EXTERN char e_invalid_buffer_identifier_in_create[] + INIT(= N_("E636: Invalid buffer identifier in create")); +EXTERN char e_invalid_buffer_identifier_in_startdocumentlisten[] + INIT(= N_("E637: Invalid buffer identifier in startDocumentListen")); +EXTERN char e_invalid_buffer_identifier_in_stopdocumentlisten[] + INIT(= N_("E638: Invalid buffer identifier in stopDocumentListen")); +EXTERN char e_invalid_buffer_identifier_in_settitle[] + INIT(= N_("E639: Invalid buffer identifier in setTitle")); +EXTERN char e_invalid_buffer_identifier_in_initdone[] + INIT(= N_("E640: Invalid buffer identifier in initDone")); +EXTERN char e_invalid_buffer_identifier_in_setbuffernumber[] + INIT(= N_("E641: Invalid buffer identifier in setBufferNumber")); +EXTERN char e_file_str_not_found_in_setbuffernumber[] + INIT(= N_("E642: File %s not found in setBufferNumber")); +EXTERN char e_invalid_buffer_identifier_in_setfullname[] + INIT(= N_("E643: Invalid buffer identifier in setFullName")); +EXTERN char e_invalid_buffer_identifier_in_editfile[] + INIT(= N_("E644: Invalid buffer identifier in editFile")); +EXTERN char e_invalid_buffer_identifier_in_setvisible[] + INIT(= N_("E645: Invalid buffer identifier in setVisible")); +EXTERN char e_invalid_buffer_identifier_in_setmodified[] + INIT(= N_("E646: Invalid buffer identifier in setModified")); +EXTERN char e_invalid_buffer_identifier_in_setdot[] + INIT(= N_("E647: Invalid buffer identifier in setDot")); +EXTERN char e_invalid_buffer_identifier_in_close[] + INIT(= N_("E648: Invalid buffer identifier in close")); +// E649 unused +EXTERN char e_invalid_buffer_identifier_in_defineannotype[] + INIT(= N_("E650: Invalid buffer identifier in defineAnnoType")); +EXTERN char e_invalid_buffer_identifier_in_addanno[] + INIT(= N_("E651: Invalid buffer identifier in addAnno")); +EXTERN char e_invalid_buffer_identifier_in_getanno[] + INIT(= N_("E652: Invalid buffer identifier in getAnno")); +#endif +// E653 unused +EXTERN char e_missing_delimiter_after_search_pattern_str[] + INIT(= N_("E654: Missing delimiter after search pattern: %s")); +#ifdef FEAT_EVAL +EXTERN char e_too_many_symbolic_links_cycle[] + INIT(= N_("E655: Too many symbolic links (cycle?)")); +#endif +#ifdef FEAT_NETBEANS_INTG + // E656 +EXTERN char e_netbeans_disallows_writes_of_unmodified_buffers[] + INIT(= N_("NetBeans disallows writes of unmodified buffers")); + // E657 +EXTERN char e_partial_writes_disallowed_for_netbeans_buffers[] + INIT(= N_("Partial writes disallowed for NetBeans buffers")); +EXTERN char e_netbeans_connection_lost_for_buffer_nr[] + INIT(= N_("E658: NetBeans connection lost for buffer %d")); +#endif +#ifdef FEAT_PYTHON +EXTERN char e_cannot_invoke_python_recursively[] + INIT(= N_("E659: Cannot invoke Python recursively")); +#endif +#ifdef FEAT_NETBEANS_INTG +EXTERN char e_cannot_open_netbeans_connection_info_file[] + INIT(= "E660: Cannot open NetBeans connection info file"); +#endif +#ifdef FEAT_MULTI_LANG +EXTERN char e_sorry_no_str_help_for_str[] + INIT(= N_("E661: Sorry, no '%s' help for %s")); +#endif +EXTERN char e_at_start_of_changelist[] + INIT(= N_("E662: At start of changelist")); +EXTERN char e_at_end_of_changelist[] + INIT(= N_("E663: At end of changelist")); +EXTERN char e_changelist_is_empty[] + INIT(= N_("E664: Changelist is empty")); +#ifdef FEAT_GUI +EXTERN char e_cannot_start_gui_no_valid_font_found[] + INIT(= N_("E665: Cannot start GUI, no valid font found")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_compiler_not_supported_str[] + INIT(= N_("E666: Compiler not supported: %s")); +#endif +#ifdef HAVE_FSYNC +EXTERN char e_fsync_failed[] + INIT(= N_("E667: Fsync failed")); +#endif +#ifdef FEAT_NETBEANS_INTG +EXTERN char e_wrong_access_mode_for_netbeans_connection_info_file_str[] + INIT(= N_("E668: Wrong access mode for NetBeans connection info file: \"%s\"")); +#endif +EXTERN char e_unprintable_character_in_group_name[] + INIT(= N_("E669: Unprintable character in group name")); +EXTERN char e_mix_of_help_file_encodings_within_language_str[] + INIT(= N_("E670: Mix of help file encodings within a language: %s")); +#ifdef FEAT_GUI_MSWIN +EXTERN char e_cannot_find_window_title_str[] + INIT(= N_("E671: Cannot find window title \"%s\"")); +EXTERN char e_unable_to_open_window_inside_mdi_application[] + INIT(= N_("E672: Unable to open window inside MDI application")); +#endif +#if defined(FEAT_PRINTER) && defined(FEAT_POSTSCRIPT) +EXTERN char e_incompatible_multi_byte_encoding_and_character_set[] + INIT(= N_("E673: Incompatible multi-byte encoding and character set")); +EXTERN char e_printmbcharset_cannot_be_empty_with_multi_byte_encoding[] + INIT(= N_("E674: printmbcharset cannot be empty with multi-byte encoding.")); +EXTERN char e_no_default_font_specified_for_multi_byte_printing[] + INIT(= N_("E675: No default font specified for multi-byte printing.")); +#endif +EXTERN char e_no_matching_autocommands_for_buftype_str_buffer[] + INIT(= N_("E676: No matching autocommands for buftype=%s buffer")); +#ifdef FEAT_SYN_HL +EXTERN char e_error_writing_temp_file[] + INIT(= N_("E677: Error writing temp file")); +#endif +EXTERN char e_invalid_character_after_str_2[] + INIT(= N_("E678: Invalid character after %s%%[dxouU]")); +#ifdef FEAT_SYN_HL +EXTERN char e_recursive_loop_loading_syncolor_vim[] + INIT(= N_("E679: Recursive loop loading syncolor.vim")); +#endif +EXTERN char e_buffer_nr_invalid_buffer_number[] + INIT(= N_("E680: : invalid buffer number")); +#ifdef FEAT_QUICKFIX +EXTERN char e_buffer_is_not_loaded[] + INIT(= N_("E681: Buffer is not loaded")); +EXTERN char e_invalid_search_pattern_or_delimiter[] + INIT(= N_("E682: Invalid search pattern or delimiter")); +EXTERN char e_file_name_missing_or_invalid_pattern[] + INIT(= N_("E683: File name missing or invalid pattern")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_list_index_out_of_range_nr[] + INIT(= N_("E684: List index out of range: %ld")); +#endif +EXTERN char e_internal_error_str[] + INIT(= N_("E685: Internal error: %s")); +#ifdef FEAT_EVAL +EXTERN char e_argument_of_str_must_be_list[] + INIT(= N_("E686: Argument of %s must be a List")); +EXTERN char e_less_targets_than_list_items[] + INIT(= N_("E687: Less targets than List items")); +EXTERN char e_more_targets_than_list_items[] + INIT(= N_("E688: More targets than List items")); +EXTERN char e_index_not_allowed_after_str_str[] + INIT(= N_("E689: Index not allowed after a %s: %s")); +EXTERN char e_missing_in_after_for[] + INIT(= N_("E690: Missing \"in\" after :for")); +EXTERN char e_can_only_compare_list_with_list[] + INIT(= N_("E691: Can only compare List with List")); +EXTERN char e_invalid_operation_for_list[] + INIT(= N_("E692: Invalid operation for List")); +EXTERN char e_class_or_typealias_required_for_argument_nr[] + INIT(= N_("E693: Class or class typealias required for argument %d")); +EXTERN char e_invalid_operation_for_funcrefs[] + INIT(= N_("E694: Invalid operation for Funcrefs")); +EXTERN char e_cannot_index_a_funcref[] + INIT(= N_("E695: Cannot index a Funcref")); +EXTERN char e_missing_comma_in_list_str[] + INIT(= N_("E696: Missing comma in List: %s")); +EXTERN char e_missing_end_of_list_rsb_str[] + INIT(= N_("E697: Missing end of List ']': %s")); +EXTERN char e_variable_nested_too_deep_for_making_copy[] + INIT(= N_("E698: Variable nested too deep for making a copy")); +EXTERN char e_too_many_arguments[] + INIT(= N_("E699: Too many arguments")); +EXTERN char e_unknown_function_str_2[] + INIT(= N_("E700: Unknown function: %s")); +EXTERN char e_invalid_type_for_len[] + INIT(= N_("E701: Invalid type for len()")); +EXTERN char e_sort_compare_function_failed[] + INIT(= N_("E702: Sort compare function failed")); +EXTERN char e_using_funcref_as_number[] + INIT(= N_("E703: Using a Funcref as a Number")); +EXTERN char e_funcref_variable_name_must_start_with_capital_str[] + INIT(= N_("E704: Funcref variable name must start with a capital: %s")); +EXTERN char e_variable_name_conflicts_with_existing_function_str[] + INIT(= N_("E705: Variable name conflicts with existing function: %s")); +EXTERN char e_argument_of_str_must_be_list_string_or_dictionary[] + INIT(= N_("E706: Argument of %s must be a List, String or Dictionary")); +EXTERN char e_function_name_conflicts_with_variable_str[] + INIT(= N_("E707: Function name conflicts with variable: %s")); +EXTERN char e_slice_must_come_last[] + INIT(= N_("E708: [:] must come last")); +EXTERN char e_slice_requires_list_or_blob_value[] + INIT(= N_("E709: [:] requires a List or Blob value")); +EXTERN char e_list_value_has_more_items_than_targets[] + INIT(= N_("E710: List value has more items than targets")); +EXTERN char e_list_value_does_not_have_enough_items[] + INIT(= N_("E711: List value does not have enough items")); +EXTERN char e_argument_of_str_must_be_list_or_dictionary[] + INIT(= N_("E712: Argument of %s must be a List or Dictionary")); +EXTERN char e_cannot_use_empty_key_for_dictionary[] + INIT(= N_("E713: Cannot use empty key for Dictionary")); +EXTERN char e_list_required[] + INIT(= N_("E714: List required")); +EXTERN char e_dictionary_required[] + INIT(= N_("E715: Dictionary required")); +EXTERN char e_key_not_present_in_dictionary_str[] + INIT(= N_("E716: Key not present in Dictionary: \"%s\"")); +EXTERN char e_dictionary_entry_already_exists[] + INIT(= N_("E717: Dictionary entry already exists")); +EXTERN char e_funcref_required[] + INIT(= N_("E718: Funcref required")); +EXTERN char e_cannot_slice_dictionary[] + INIT(= N_("E719: Cannot slice a Dictionary")); +EXTERN char e_missing_colon_in_dictionary_str[] + INIT(= N_("E720: Missing colon in Dictionary: %s")); +EXTERN char e_duplicate_key_in_dictionary_str[] + INIT(= N_("E721: Duplicate key in Dictionary: \"%s\"")); +EXTERN char e_missing_comma_in_dictionary_str[] + INIT(= N_("E722: Missing comma in Dictionary: %s")); +EXTERN char e_missing_dict_end_str[] + INIT(= N_("E723: Missing end of Dictionary '}': %s")); +EXTERN char e_variable_nested_too_deep_for_displaying[] + INIT(= N_("E724: Variable nested too deep for displaying")); +EXTERN char e_calling_dict_function_without_dictionary_str[] + INIT(= N_("E725: Calling dict function without Dictionary: %s")); +EXTERN char e_stride_is_zero[] + INIT(= N_("E726: Stride is zero")); +EXTERN char e_start_past_end[] + INIT(= N_("E727: Start past end")); +EXTERN char e_using_dictionary_as_number[] + INIT(= N_("E728: Using a Dictionary as a Number")); +EXTERN char e_using_funcref_as_string[] + INIT(= N_("E729: Using a Funcref as a String")); +EXTERN char e_using_list_as_string[] + INIT(= N_("E730: Using a List as a String")); +EXTERN char e_using_dictionary_as_string[] + INIT(= N_("E731: Using a Dictionary as a String")); +EXTERN char e_using_endfor_with_while[] + INIT(= N_("E732: Using :endfor with :while")); +EXTERN char e_using_endwhile_with_for[] + INIT(= N_("E733: Using :endwhile with :for")); +EXTERN char e_wrong_variable_type_for_str_equal[] + INIT(= N_("E734: Wrong variable type for %s=")); +EXTERN char e_can_only_compare_dictionary_with_dictionary[] + INIT(= N_("E735: Can only compare Dictionary with Dictionary")); +EXTERN char e_invalid_operation_for_dictionary[] + INIT(= N_("E736: Invalid operation for Dictionary")); +EXTERN char e_key_already_exists_str[] + INIT(= N_("E737: Key already exists: %s")); +EXTERN char e_cant_list_variables_for_str[] + INIT(= N_("E738: Can't list variables for %s")); +EXTERN char e_cannot_create_directory_str[] + INIT(= N_("E739: Cannot create directory: %s")); +EXTERN char e_too_many_arguments_for_function_str_2[] + INIT(= N_("E740: Too many arguments for function %s")); +EXTERN char e_value_is_locked[] + INIT(= N_("E741: Value is locked")); +EXTERN char e_value_is_locked_str[] + INIT(= N_("E741: Value is locked: %s")); +EXTERN char e_cannot_change_value[] + INIT(= N_("E742: Cannot change value")); +EXTERN char e_cannot_change_value_of_str[] + INIT(= N_("E742: Cannot change value of %s")); +EXTERN char e_variable_nested_too_deep_for_unlock[] + INIT(= N_("E743: Variable nested too deep for (un)lock")); +#endif +#ifdef FEAT_NETBEANS_INTG +EXTERN char e_netbeans_does_not_allow_changes_in_read_only_files[] + INIT(= N_("E744: NetBeans does not allow changes in read-only files")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_using_list_as_number[] + INIT(= N_("E745: Using a List as a Number")); +EXTERN char e_function_name_does_not_match_script_file_name_str[] + INIT(= N_("E746: Function name does not match script file name: %s")); +#endif +EXTERN char e_cannot_change_directory_buffer_is_modified_add_bang_to_override[] + INIT(= N_("E747: Cannot change directory, buffer is modified (add ! to override)")); +EXTERN char e_no_previously_used_register[] + INIT(= N_("E748: No previously used register")); +EXTERN char e_empty_buffer[] + INIT(= N_("E749: Empty buffer")); +#ifdef FEAT_PROFILE +EXTERN char e_first_use_profile_start_fname[] + INIT(= N_("E750: First use \":profile start {fname}\"")); +#endif +#ifdef FEAT_SPELL +EXTERN char e_output_file_name_must_not_have_region_name[] + INIT(= N_("E751: Output file name must not have region name")); +EXTERN char e_no_previous_spell_replacement[] + INIT(= N_("E752: No previous spell replacement")); +EXTERN char e_not_found_str[] + INIT(= N_("E753: Not found: %s")); +EXTERN char e_only_up_to_nr_regions_supported[] + INIT(= N_("E754: Only up to %d regions supported")); +EXTERN char e_invalid_region_in_str[] + INIT(= N_("E755: Invalid region in %s")); +EXTERN char e_spell_checking_is_not_possible[] + INIT(= N_("E756: Spell checking is not possible")); +EXTERN char e_this_does_not_look_like_spell_file[] + INIT(= N_("E757: This does not look like a spell file")); +EXTERN char e_truncated_spell_file[] + INIT(= N_("E758: Truncated spell file")); +EXTERN char e_format_error_in_spell_file[] + INIT(= N_("E759: Format error in spell file")); +EXTERN char e_no_word_count_in_str[] + INIT(= N_("E760: No word count in %s")); +EXTERN char e_format_error_in_affix_file_fol_low_or_upp[] + INIT(= N_("E761: Format error in affix file FOL, LOW or UPP")); +EXTERN char e_character_in_fol_low_or_upp_is_out_of_range[] + INIT(= N_("E762: Character in FOL, LOW or UPP is out of range")); +EXTERN char e_word_characters_differ_between_spell_files[] + INIT(= N_("E763: Word characters differ between spell files")); +#endif +#if defined(FEAT_SYN_HL) || defined(FEAT_COMPL_FUNC) || defined(FEAT_SPELL) +EXTERN char e_option_str_is_not_set[] + INIT(= N_("E764: Option '%s' is not set")); +#endif +#ifdef FEAT_SPELL +EXTERN char e_spellfile_does_not_have_nr_entries[] + INIT(= N_("E765: 'spellfile' does not have %d entries")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_insufficient_arguments_for_printf[] + INIT(= N_("E766: Insufficient arguments for printf()")); +#endif +EXTERN char e_too_many_arguments_to_printf[] + INIT(= N_("E767: Too many arguments for printf()")); +EXTERN char e_swap_file_exists_str_silent_overrides[] + INIT(= N_("E768: Swap file exists: %s (:silent! overrides)")); +EXTERN char e_missing_rsb_after_str_lsb[] + INIT(= N_("E769: Missing ] after %s[")); +#ifdef FEAT_SPELL +EXTERN char e_unsupported_section_in_spell_file[] + INIT(= N_("E770: Unsupported section in spell file")); +EXTERN char e_old_spell_file_needs_to_be_updated[] + INIT(= N_("E771: Old spell file, needs to be updated")); +EXTERN char e_spell_file_is_for_newer_version_of_vim[] + INIT(= N_("E772: Spell file is for newer version of Vim")); +#endif +EXTERN char e_symlink_loop_for_str[] + INIT(= N_("E773: Symlink loop for \"%s\"")); +#ifdef FEAT_EVAL +EXTERN char e_operatorfunc_is_empty[] + INIT(= N_("E774: 'operatorfunc' is empty")); +#else +EXTERN char e_eval_feature_not_available[] + INIT(= N_("E775: Eval feature not available")); +#endif +#ifdef FEAT_QUICKFIX +EXTERN char e_no_location_list[] + INIT(= N_("E776: No location list")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_string_or_list_expected[] + INIT(= N_("E777: String or List expected")); +#endif +#ifdef FEAT_SPELL +EXTERN char e_this_does_not_look_like_sug_file_str[] + INIT(= N_("E778: This does not look like a .sug file: %s")); +EXTERN char e_old_sug_file_needs_to_be_updated_str[] + INIT(= N_("E779: Old .sug file, needs to be updated: %s")); +EXTERN char e_sug_file_is_for_newer_version_of_vim_str[] + INIT(= N_("E780: .sug file is for newer version of Vim: %s")); +EXTERN char e_sug_file_doesnt_match_spl_file_str[] + INIT(= N_("E781: .sug file doesn't match .spl file: %s")); +EXTERN char e_error_while_reading_sug_file_str[] + INIT(= N_("E782: Error while reading .sug file: %s")); +EXTERN char e_duplicate_char_in_map_entry[] + INIT(= N_("E783: Duplicate char in MAP entry")); +#endif +EXTERN char e_cannot_close_last_tab_page[] + INIT(= N_("E784: Cannot close last tab page")); +#ifdef FEAT_EVAL +# ifdef FEAT_COMPL_FUNC +EXTERN char e_complete_can_only_be_used_in_insert_mode[] + INIT(= N_("E785: complete() can only be used in Insert mode")); +# endif +EXTERN char e_range_not_allowed[] + INIT(= N_("E786: Range not allowed")); +#endif +#ifdef FEAT_DIFF +EXTERN char e_buffer_changed_unexpectedly[] + INIT(= N_("E787: Buffer changed unexpectedly")); +#endif +EXTERN char e_not_allowed_to_edit_another_buffer_now[] + INIT(= N_("E788: Not allowed to edit another buffer now")); +#ifdef FEAT_SYN_HL +EXTERN char e_error_missing_rsb_str[] + INIT(= N_("E789: Missing ']': %s")); +#endif +EXTERN char e_undojoin_is_not_allowed_after_undo[] + INIT(= N_("E790: undojoin is not allowed after undo")); +#ifdef FEAT_KEYMAP +EXTERN char e_empty_keymap_entry[] + INIT(= N_("E791: Empty keymap entry")); +#endif +#ifdef FEAT_MENU +EXTERN char e_empty_menu_name[] + INIT(= N_("E792: Empty menu name")); +#endif +#ifdef FEAT_DIFF +EXTERN char e_no_other_buffer_in_diff_mode_is_modifiable[] + INIT(= N_("E793: No other buffer in diff mode is modifiable")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_cannot_set_variable_in_sandbox[] + INIT(= N_("E794: Cannot set variable in the sandbox")); +EXTERN char e_cannot_set_variable_in_sandbox_str[] + INIT(= N_("E794: Cannot set variable in the sandbox: \"%s\"")); +EXTERN char e_cannot_delete_variable[] + INIT(= N_("E795: Cannot delete variable")); +EXTERN char e_cannot_delete_variable_str[] + INIT(= N_("E795: Cannot delete variable %s")); +#endif +#ifdef MSWIN + // E796 +EXTERN char e_writing_to_device_disabled_with_opendevice_option[] + INIT(= N_("writing to device disabled with 'opendevice' option")); +#endif +#ifdef FEAT_SPELL +EXTERN char e_spellfilemising_autocommand_deleted_buffer[] + INIT(= N_("E797: SpellFileMissing autocommand deleted buffer")); +#endif +#ifdef FEAT_SEARCH_EXTRA +EXTERN char e_id_is_reserved_for_match_nr[] + INIT(= N_("E798: ID is reserved for \":match\": %d")); +EXTERN char e_invalid_id_nr_must_be_greater_than_or_equal_to_one_1[] + INIT(= N_("E799: Invalid ID: %d (must be greater than or equal to 1)")); +#endif +#ifndef FEAT_ARABIC +EXTERN char e_arabic_cannot_be_used_not_enabled_at_compile_time[] + INIT(= N_("E800: Arabic cannot be used: Not enabled at compile time\n")); +#endif +#ifdef FEAT_SEARCH_EXTRA +EXTERN char e_id_already_taken_nr[] + INIT(= N_("E801: ID already taken: %d")); +EXTERN char e_invalid_id_nr_must_be_greater_than_or_equal_to_one_2[] + INIT(= N_("E802: Invalid ID: %d (must be greater than or equal to 1)")); +EXTERN char e_id_not_found_nr[] + INIT(= N_("E803: ID not found: %d")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_cannot_use_percent_with_float[] + // xgettext:no-c-format + INIT(= N_("E804: Cannot use '%' with Float")); +EXTERN char e_using_float_as_number[] + INIT(= N_("E805: Using a Float as a Number")); +EXTERN char e_using_float_as_string[] + INIT(= N_("E806: Using a Float as a String")); +EXTERN char e_expected_float_argument_for_printf[] + INIT(= N_("E807: Expected Float argument for printf()")); +EXTERN char e_number_or_float_required[] + INIT(= N_("E808: Number or Float required")); +#endif +#ifndef FEAT_EVAL +EXTERN char e_hashsmall_is_not_available_without_the_eval_feature[] + INIT(= N_("E809: #< is not available without the +eval feature")); +#endif +#ifdef FEAT_DIFF +EXTERN char e_cannot_read_or_write_temp_files[] + INIT(= N_("E810: Cannot read or write temp files")); +#endif +EXTERN char e_not_allowed_to_change_buffer_information_now[] + INIT(= N_("E811: Not allowed to change buffer information now")); +EXTERN char e_autocommands_changed_buffer_or_buffer_name[] + INIT(= N_("E812: Autocommands changed buffer or buffer name")); +EXTERN char e_cannot_close_autocmd_or_popup_window[] + INIT(= N_("E813: Cannot close autocmd or popup window")); +EXTERN char e_cannot_close_window_only_autocmd_window_would_remain[] + INIT(= N_("E814: Cannot close window, only autocmd window would remain")); +#ifdef FEAT_MZSCHEME +EXTERN char e_sorry_this_command_is_disabled_the_mzscheme_libraries_could_not_be_loaded[] + INIT(= N_("E815: Sorry, this command is disabled, the MzScheme libraries could not be loaded.")); +#endif +#ifdef FEAT_DIFF +EXTERN char e_cannot_read_patch_output[] + INIT(= N_("E816: Cannot read patch output")); +#endif +#ifdef FEAT_CRYPT +EXTERN char e_blowfish_big_little_endian_use_wrong[] + INIT(= N_("E817: Blowfish big/little endian use wrong")); +EXTERN char e_sha256_test_failed[] + INIT(= N_("E818: sha256 test failed")); +EXTERN char e_blowfish_test_failed[] + INIT(= N_("E819: Blowfish test failed")); +EXTERN char e_sizeof_uint32_isnot_four[] + INIT(= N_("E820: sizeof(uint32_t) != 4")); +EXTERN char e_file_is_encrypted_with_unknown_method[] + INIT(= N_("E821: File is encrypted with unknown method")); +#endif +#ifdef FEAT_PERSISTENT_UNDO +EXTERN char e_cannot_open_undo_file_for_reading_str[] + INIT(= N_("E822: Cannot open undo file for reading: %s")); +EXTERN char e_not_an_undo_file_str[] + INIT(= N_("E823: Not an undo file: %s")); +EXTERN char e_incompatible_undo_file_str[] + INIT(= N_("E824: Incompatible undo file: %s")); +EXTERN char e_corrupted_undo_file_str_str[] + INIT(= N_("E825: Corrupted undo file (%s): %s")); +# ifdef FEAT_CRYPT +EXTERN char e_undo_file_decryption_failed[] + INIT(= N_("E826: Undo file decryption failed: %s")); +# else +EXTERN char e_undo_file_is_encrypted_str[] + INIT(= N_("E827: Undo file is encrypted: %s")); +# endif +EXTERN char e_cannot_open_undo_file_for_writing_str[] + INIT(= N_("E828: Cannot open undo file for writing: %s")); +EXTERN char e_write_error_in_undo_file_str[] + INIT(= N_("E829: Write error in undo file: %s")); +#endif +EXTERN char e_undo_number_nr_not_found[] + INIT(= N_("E830: Undo number %ld not found")); +#ifdef FEAT_CRYPT +EXTERN char e_bf_key_init_called_with_empty_password[] + INIT(= "E831: bf_key_init() called with empty password"); +# ifdef FEAT_PERSISTENT_UNDO +EXTERN char e_non_encrypted_file_has_encrypted_undo_file_str[] + INIT(= N_("E832: Non-encrypted file has encrypted undo file: %s")); +# endif +#else +EXTERN char e_str_is_encrypted_and_this_version_of_vim_does_not_support_encryption[] + INIT(= N_("E833: %s is encrypted and this version of Vim does not support encryption")); +#endif +EXTERN char e_conflicts_with_value_of_listchars[] + INIT(= N_("E834: Conflicts with value of 'listchars'")); +EXTERN char e_conflicts_with_value_of_fillchars[] + INIT(= N_("E835: Conflicts with value of 'fillchars'")); +#ifdef DYNAMIC_PYTHON +EXTERN char e_this_vim_cannot_execute_python_after_using_py3[] + INIT(= N_("E836: This Vim cannot execute :python after using :py3")); +EXTERN char e_this_vim_cannot_execute_py3_after_using_python[] + INIT(= N_("E837: This Vim cannot execute :py3 after using :python")); +#endif +#if defined(FEAT_NETBEANS_INTG) && defined(FEAT_GUI) +EXTERN char e_netbeans_is_not_supported_with_this_GUI[] + INIT(= N_("E838: NetBeans is not supported with this GUI")); +#endif +// E839 unused +# ifdef FEAT_COMPL_FUNC +EXTERN char e_complete_function_deleted_text[] + INIT(= N_("E840: Completion function deleted text")); +# endif +EXTERN char e_reserved_name_cannot_be_used_for_user_defined_command[] + INIT(= N_("E841: Reserved name, cannot be used for user defined command")); +EXTERN char e_no_line_number_to_use_for_slnum[] + INIT(= N_("E842: No line number to use for \"\"")); +#ifdef FEAT_CRYPT +EXTERN char e_error_while_updating_swap_file_crypt[] + INIT(= N_("E843: Error while updating swap file crypt")); +#endif +#ifdef FEAT_CONCEAL +EXTERN char e_invalid_cchar_value[] + INIT(= N_("E844: Invalid cchar value")); +#endif +#ifdef FEAT_SPELL +EXTERN char e_insufficient_memory_word_list_will_be_incomplete[] + INIT(= N_("E845: Insufficient memory, word list will be incomplete")); +#endif +EXTERN char e_key_code_not_set[] + INIT(= N_("E846: Key code not set")); +#ifdef FEAT_SYN_HL +EXTERN char e_too_many_syntax_includes[] + INIT(= N_("E847: Too many syntax includes")); +EXTERN char e_too_many_syntax_clusters[] + INIT(= N_("E848: Too many syntax clusters")); +#endif +EXTERN char e_too_many_highlight_and_syntax_groups[] + INIT(= N_("E849: Too many highlight and syntax groups")); +#ifndef FEAT_CLIPBOARD +EXTERN char e_invalid_register_name[] + INIT(= N_("E850: Invalid register name")); +#endif +#ifdef FEAT_GUI +EXTERN char e_failed_to_create_new_process_for_GUI[] + INIT(= N_("E851: Failed to create a new process for the GUI")); +EXTERN char e_the_child_process_failed_to_start_GUI[] + INIT(= N_("E852: The child process failed to start the GUI")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_duplicate_argument_name_str[] + INIT(= N_("E853: Duplicate argument name: %s")); +#endif +EXTERN char e_path_too_long_for_completion[] + INIT(= N_("E854: Path too long for completion")); +EXTERN char e_autocommands_caused_command_to_abort[] + INIT(= N_("E855: Autocommands caused command to abort")); +#ifdef FEAT_EVAL +EXTERN char e_assert_fails_second_arg[] + INIT(= N_("E856: \"assert_fails()\" second argument must be a string or a list with one or two strings")); +EXTERN char e_dictionary_key_str_required[] + INIT(= N_("E857: Dictionary key \"%s\" required")); +#endif +#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) +EXTERN char e_eval_did_not_return_valid_python_object[] + INIT(= N_("E858: Eval did not return a valid python object")); +EXTERN char e_failed_to_convert_returned_python_object_to_vim_value[] + INIT(= N_("E859: Failed to convert returned python object to a Vim value")); +#endif +#ifdef FEAT_PROP_POPUP +EXTERN char e_need_id_and_type_or_types_with_both[] + INIT(= N_("E860: Need 'id' and 'type' or 'types' with 'both'")); +# ifdef FEAT_TERMINAL +EXTERN char e_cannot_open_second_popup_with_terminal[] + INIT(= N_("E861: Cannot open a second popup with a terminal")); +# endif +#endif +#ifdef FEAT_EVAL +EXTERN char e_cannot_use_g_here[] + INIT(= N_("E862: Cannot use g: here")); +#endif +#if defined(FEAT_PROP_POPUP) && defined(FEAT_TERMINAL) +EXTERN char e_not_allowed_for_terminal_in_popup_window[] + INIT(= N_("E863: Not allowed for a terminal in a popup window")); +#endif +EXTERN char e_percent_hash_can_only_be_followed_by_zero_one_two_automatic_engine_will_be_used[] + // xgettext:no-c-format + INIT(= N_("E864: \\%#= can only be followed by 0, 1, or 2. The automatic engine will be used")); +EXTERN char e_nfa_regexp_end_encountered_prematurely[] + INIT(= N_("E865: (NFA) Regexp end encountered prematurely")); +EXTERN char e_nfa_regexp_misplaced_chr[] + INIT(= N_("E866: (NFA regexp) Misplaced %c")); +EXTERN char e_nfa_regexp_unknown_operator_z_chr[] + INIT(= N_("E867: (NFA regexp) Unknown operator '\\z%c'")); +EXTERN char e_nfa_regexp_unknown_operator_percent_chr[] + INIT(= N_("E867: (NFA regexp) Unknown operator '\\%%%c'")); +EXTERN char e_error_building_nfa_with_equivalence_class[] + INIT(= N_("E868: Error building NFA with equivalence class!")); +EXTERN char e_nfa_regexp_unknown_operator_at_chr[] + INIT(= N_("E869: (NFA regexp) Unknown operator '\\@%c'")); +EXTERN char e_nfa_regexp_error_reading_repetition_limits[] + INIT(= N_("E870: (NFA regexp) Error reading repetition limits")); +EXTERN char e_nfa_regexp_cant_have_multi_follow_multi[] + INIT(= N_("E871: (NFA regexp) Can't have a multi follow a multi")); +EXTERN char e_nfa_regexp_too_many_parens[] + INIT(= N_("E872: (NFA regexp) Too many '('")); +EXTERN char e_nfa_regexp_proper_termination_error[] + INIT(= N_("E873: (NFA regexp) proper termination error")); +EXTERN char e_nfa_regexp_could_not_pop_stack[] + INIT(= N_("E874: (NFA regexp) Could not pop the stack!")); +EXTERN char e_nfa_regexp_while_converting_from_postfix_to_nfa_too_many_stats_left_on_stack[] + INIT(= N_("E875: (NFA regexp) (While converting from postfix to NFA), too many states left on stack")); +EXTERN char e_nfa_regexp_not_enough_space_to_store_whole_nfa[] + INIT(= N_("E876: (NFA regexp) Not enough space to store the whole NFA")); +EXTERN char e_nfa_regexp_invalid_character_class_nr[] + INIT(= "E877: (NFA regexp) Invalid character class: %d"); +EXTERN char e_nfa_regexp_could_not_allocate_memory_for_branch_traversal[] + INIT(= N_("E878: (NFA regexp) Could not allocate memory for branch traversal!")); +#ifdef FEAT_SYN_HL +EXTERN char e_nfa_regexp_too_many_z[] + INIT(= N_("E879: (NFA regexp) Too many \\z(")); +#endif +#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) +EXTERN char e_cant_handle_systemexit_of_python_exception_in_vim[] + INIT(= N_("E880: Can't handle SystemExit of python exception in vim")); +#endif +EXTERN char e_line_count_changed_unexpectedly[] + INIT(= N_("E881: Line count changed unexpectedly")); +#ifdef FEAT_EVAL +EXTERN char e_uniq_compare_function_failed[] + INIT(= N_("E882: Uniq compare function failed")); +EXTERN char e_search_pattern_and_expression_register_may_not_contain_two_or_more_lines[] + INIT(= N_("E883: Search pattern and expression register may not contain two or more lines")); +EXTERN char e_function_name_cannot_contain_colon_str[] + INIT(= N_("E884: Function name cannot contain a colon: %s")); +#endif +#ifdef FEAT_SIGNS +EXTERN char e_not_possible_to_change_sign_str[] + INIT(= N_("E885: Not possible to change sign %s")); +#endif +#ifdef FEAT_VIMINFO +EXTERN char e_cant_rename_viminfo_file_to_str[] + INIT(= N_("E886: Can't rename viminfo file to %s!")); +#endif +#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) +EXTERN char e_sorry_this_command_is_disabled_python_side_module_could_not_be_loaded[] + INIT(= N_("E887: Sorry, this command is disabled, the Python's site module could not be loaded.")); +#endif +EXTERN char e_nfa_regexp_cannot_repeat_str[] + INIT(= N_("E888: (NFA regexp) cannot repeat %s")); +#ifdef FEAT_PROP_POPUP +EXTERN char e_number_required[] + INIT(= N_("E889: Number required")); +#endif +#ifdef FEAT_SYN_HL +EXTERN char e_trailing_char_after_rsb_str_str[] + INIT(= N_("E890: Trailing char after ']': %s]%s")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_using_funcref_as_float[] + INIT(= N_("E891: Using a Funcref as a Float")); +EXTERN char e_using_string_as_float[] + INIT(= N_("E892: Using a String as a Float")); +EXTERN char e_using_list_as_float[] + INIT(= N_("E893: Using a List as a Float")); +EXTERN char e_using_dictionary_as_float[] + INIT(= N_("E894: Using a Dictionary as a Float")); +#endif +#ifdef FEAT_MZSCHEME +EXTERN char e_sorry_this_command_is_disabled_the_mzscheme_racket_base_module_could_not_be_loaded[] + INIT(= N_("E895: Sorry, this command is disabled, the MzScheme's racket/base module could not be loaded.")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_argument_of_str_must_be_list_dictionary_or_blob[] + INIT(= N_("E896: Argument of %s must be a List, Dictionary or Blob")); +EXTERN char e_list_or_blob_required[] + INIT(= N_("E897: List or Blob required")); +#endif +#ifdef FEAT_JOB_CHANNEL +EXTERN char e_socket_in_channel_connect[] + INIT(= N_("E898: socket() in channel_connect()")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_argument_of_str_must_be_list_or_blob[] + INIT(= N_("E899: Argument of %s must be a List or Blob")); +EXTERN char e_maxdepth_must_be_non_negative_number[] + INIT(= N_("E900: maxdepth must be non-negative number")); +#endif +#ifdef FEAT_JOB_CHANNEL +EXTERN char e_getaddrinfo_in_channel_open_str[] + INIT(= N_("E901: getaddrinfo() in channel_open(): %s")); +# ifndef FEAT_IPV6 +EXTERN char e_gethostbyname_in_channel_open[] + INIT(= N_("E901: gethostbyname() in channel_open()")); +# endif +EXTERN char e_cannot_connect_to_port[] + INIT(= N_("E902: Cannot connect to port")); +EXTERN char e_received_command_with_non_string_argument[] + INIT(= N_("E903: Received command with non-string argument")); +EXTERN char e_last_argument_for_expr_call_must_be_number[] + INIT(= N_("E904: Last argument for expr/call must be a number")); +EXTERN char e_third_argument_for_call_must_be_list[] + INIT(= N_("E904: Third argument for call must be a list")); +EXTERN char e_received_unknown_command_str[] + INIT(= N_("E905: Received unknown command: %s")); +EXTERN char e_not_an_open_channel[] + INIT(= N_("E906: Not an open channel")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_using_special_value_as_float[] + INIT(= N_("E907: Using a special value as a Float")); +EXTERN char e_using_invalid_value_as_string_str[] + INIT(= N_("E908: Using an invalid value as a String: %s")); +EXTERN char e_cannot_index_special_variable[] + INIT(= N_("E909: Cannot index a special variable")); +#endif +#ifdef FEAT_JOB_CHANNEL +EXTERN char e_using_job_as_number[] + INIT(= N_("E910: Using a Job as a Number")); +EXTERN char e_using_job_as_float[] + INIT(= N_("E911: Using a Job as a Float")); +EXTERN char e_cannot_use_evalexpr_sendexpr_with_raw_or_nl_channel[] + INIT(= N_("E912: Cannot use ch_evalexpr()/ch_sendexpr() with a raw or nl channel")); +EXTERN char e_using_channel_as_number[] + INIT(= N_("E913: Using a Channel as a Number")); +EXTERN char e_using_channel_as_float[] + INIT(= N_("E914: Using a Channel as a Float")); +EXTERN char e_in_io_buffer_requires_in_buf_or_in_name_to_be_set[] + INIT(= N_("E915: in_io buffer requires in_buf or in_name to be set")); +EXTERN char e_not_valid_job[] + INIT(= N_("E916: Not a valid job")); +EXTERN char e_cannot_use_callback_with_str[] + INIT(= N_("E917: Cannot use a callback with %s()")); +EXTERN char e_buffer_must_be_loaded_str[] + INIT(= N_("E918: Buffer must be loaded: %s")); +#endif +EXTERN char e_directory_not_found_in_str_str[] + INIT(= N_("E919: Directory not found in '%s': \"%s\"")); +#ifdef FEAT_JOB_CHANNEL +EXTERN char e_io_file_requires_name_to_be_set[] + INIT(= N_("E920: _io file requires _name to be set")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_invalid_callback_argument[] + INIT(= N_("E921: Invalid callback argument")); +// E922 unused +EXTERN char e_second_argument_of_function_must_be_list_or_dict[] + INIT(= N_("E923: Second argument of function() must be a list or a dict")); +#endif +#ifdef FEAT_QUICKFIX +EXTERN char e_current_window_was_closed[] + INIT(= N_("E924: Current window was closed")); +EXTERN char e_current_quickfix_list_was_changed[] + INIT(= N_("E925: Current quickfix list was changed")); +EXTERN char e_current_location_list_was_changed[] + INIT(= N_("E926: Current location list was changed")); +#endif +#ifdef FEAT_EVAL +# ifdef FEAT_QUICKFIX +EXTERN char e_invalid_action_str_1[] + INIT(= N_("E927: Invalid action: '%s'")); +# endif +EXTERN char e_string_required[] + INIT(= N_("E928: String required")); +#endif +#ifdef FEAT_VIMINFO +EXTERN char e_too_many_viminfo_temp_files_like_str[] + INIT(= N_("E929: Too many viminfo temp files, like %s!")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_cannot_use_redir_inside_execute[] + INIT(= N_("E930: Cannot use :redir inside execute()")); +#endif +EXTERN char e_buffer_cannot_be_registered[] + INIT(= N_("E931: Buffer cannot be registered")); +#ifdef FEAT_EVAL +EXTERN char e_closure_function_should_not_be_at_top_level_str[] + INIT(= N_("E932: Closure function should not be at top level: %s")); +EXTERN char e_function_was_deleted_str[] + INIT(= N_("E933: Function was deleted: %s")); +#endif +#ifdef FEAT_SIGNS +EXTERN char e_cannot_jump_to_buffer_that_does_not_have_name[] + INIT(= N_("E934: Cannot jump to a buffer that does not have a name")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_invalid_submatch_number_nr[] + INIT(= N_("E935: Invalid submatch number: %d")); +#endif +EXTERN char e_cannot_delete_current_group[] + INIT(= N_("E936: Cannot delete the current group")); +EXTERN char e_attempt_to_delete_buffer_that_is_in_use_str[] + INIT(= N_("E937: Attempt to delete a buffer that is in use: %s")); +#ifdef FEAT_EVAL +EXTERN char e_duplicate_key_in_json_str[] + INIT(= N_("E938: Duplicate key in JSON: \"%s\"")); +#endif +EXTERN char e_positive_count_required[] + INIT(= N_("E939: Positive count required")); +#ifdef FEAT_EVAL +EXTERN char e_cannot_lock_or_unlock_variable_str[] + INIT(= N_("E940: Cannot lock or unlock variable %s")); +# ifdef FEAT_CLIENTSERVER +EXTERN char e_already_started_server[] + INIT(= N_("E941: Already started a server")); +# else +EXTERN char e_clientserver_feature_not_available[] + INIT(= N_("E942: +clientserver feature not available")); +# endif +#endif +EXTERN char e_command_table_needs_to_be_updated_run_make_cmdidxs[] + INIT(= "E943: Command table needs to be updated, run 'make cmdidxs'"); +EXTERN char e_reverse_range_in_character_class[] + INIT(= N_("E944: Reverse range in character class")); +EXTERN char e_range_too_large_in_character_class[] + INIT(= N_("E945: Range too large in character class")); +#ifdef FEAT_TERMINAL +EXTERN char e_cannot_make_terminal_with_running_job_modifiable[] + INIT(= N_("E946: Cannot make a terminal with running job modifiable")); +EXTERN char e_job_still_running_in_buffer_str[] + INIT(= N_("E947: Job still running in buffer \"%s\"")); +EXTERN char e_job_still_running[] + INIT(= N_("E948: Job still running")); +EXTERN char e_job_still_running_add_bang_to_end_the_job[] + INIT(= N_("E948: Job still running (add ! to end the job)")); +#endif +EXTERN char e_file_changed_while_writing[] + INIT(= N_("E949: File changed while writing")); +EXTERN char e_cannot_convert_between_str_and_str[] + INIT(= N_("E950: Cannot convert between %s and %s")); +EXTERN char e_percent_value_too_large[] + // xgettext:no-c-format + INIT(= N_("E951: \\% value too large")); +#if defined(FEAT_EVAL) && defined(FEAT_QUICKFIX) +EXTERN char e_autocommand_caused_recursive_behavior[] + INIT(= N_("E952: Autocommand caused recursive behavior")); +#endif +#ifdef FEAT_TERMINAL +EXTERN char e_file_exists_str[] + INIT(= N_("E953: File exists: %s")); +#endif +#if defined(FEAT_TERMGUICOLORS) && defined(FEAT_VTP) +EXTERN char e_24_bit_colors_are_not_supported_on_this_environment[] + INIT(= N_("E954: 24-bit colors are not supported on this environment")); +#endif +#ifdef FEAT_TERMINAL +EXTERN char e_not_terminal_buffer[] + INIT(= N_("E955: Not a terminal buffer")); +#endif +EXTERN char e_cannot_use_pattern_recursively[] + INIT(= N_("E956: Cannot use pattern recursively")); +#ifdef FEAT_EVAL +EXTERN char e_invalid_window_number[] + INIT(= N_("E957: Invalid window number")); +#endif +#ifdef FEAT_TERMINAL +EXTERN char e_job_already_finished[] + INIT(= N_("E958: Job already finished")); +#endif +#ifdef FEAT_DIFF +EXTERN char e_invalid_diff_format[] + INIT(= N_("E959: Invalid diff format.")); +EXTERN char e_problem_creating_internal_diff[] + INIT(= N_("E960: Problem creating the internal diff")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_no_line_number_to_use_for_sflnum[] + INIT(= N_("E961: No line number to use for \"\"")); +EXTERN char e_invalid_action_str_2[] + INIT(= N_("E962: Invalid action: '%s'")); +EXTERN char e_setting_v_str_to_value_with_wrong_type[] + INIT(= N_("E963: Setting v:%s to value with wrong type")); +#endif +#ifdef FEAT_PROP_POPUP +EXTERN char_u e_invalid_column_number_nr[] + INIT(= N_("E964: Invalid column number: %ld")); +EXTERN char e_missing_property_type_name[] + INIT(= N_("E965: Missing property type name")); +#endif +#ifdef FEAT_EVAL +EXTERN char_u e_invalid_line_number_nr[] + INIT(= N_("E966: Invalid line number: %ld")); +#endif +#ifdef FEAT_PROP_POPUP +EXTERN char e_text_property_info_corrupted[] + INIT(= "E967: Text property info corrupted"); +EXTERN char e_need_at_least_one_of_id_or_type[] + INIT(= N_("E968: Need at least one of 'id' or 'type'")); +EXTERN char e_property_type_str_already_defined[] + INIT(= N_("E969: Property type %s already defined")); +EXTERN char e_unknown_highlight_group_name_str[] + INIT(= N_("E970: Unknown highlight group name: '%s'")); +EXTERN char e_property_type_str_does_not_exist[] + INIT(= N_("E971: Property type %s does not exist")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_blob_value_does_not_have_right_number_of_bytes[] + INIT(= N_("E972: Blob value does not have the right number of bytes")); +EXTERN char e_blob_literal_should_have_an_even_number_of_hex_characters[] + INIT(= N_("E973: Blob literal should have an even number of hex characters")); +EXTERN char e_using_blob_as_number[] + INIT(= N_("E974: Using a Blob as a Number")); +EXTERN char e_using_blob_as_float[] + INIT(= N_("E975: Using a Blob as a Float")); +EXTERN char e_using_blob_as_string[] + INIT(= N_("E976: Using a Blob as a String")); +EXTERN char e_can_only_compare_blob_with_blob[] + INIT(= N_("E977: Can only compare Blob with Blob")); +EXTERN char e_invalid_operation_for_blob[] + INIT(= N_("E978: Invalid operation for Blob")); +EXTERN char e_blob_index_out_of_range_nr[] + INIT(= N_("E979: Blob index out of range: %ld")); +# ifndef USE_INPUT_BUF +EXTERN char e_lowlevel_input_not_supported[] + INIT(= N_("E980: Lowlevel input not supported")); +# endif +#endif +EXTERN char e_command_not_allowed_in_rvim[] + INIT(= N_("E981: Command not allowed in rvim")); +#if defined(FEAT_TERMINAL) && defined(MSWIN) +EXTERN char e_conpty_is_not_available[] + INIT(= N_("E982: ConPTY is not available")); +#endif +EXTERN char e_duplicate_argument_str[] + INIT(= N_("E983: Duplicate argument: %s")); +EXTERN char e_scriptversion_used_outside_of_sourced_file[] + INIT(= N_("E984: :scriptversion used outside of a sourced file")); +#ifdef FEAT_EVAL +EXTERN char e_dot_equal_not_supported_with_script_version_two[] + INIT(= N_("E985: .= is not supported with script version >= 2")); +EXTERN char e_cannot_modify_tag_stack_within_tagfunc[] + INIT(= N_("E986: Cannot modify the tag stack within tagfunc")); +EXTERN char e_invalid_return_value_from_tagfunc[] + INIT(= N_("E987: Invalid return value from tagfunc")); +#endif +#ifdef GUI_MAY_SPAWN +EXTERN char e_gui_cannot_be_used_cannot_execute_gvim_exe[] + INIT(= N_("E988: GUI cannot be used. Cannot execute gvim.exe.")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_non_default_argument_follows_default_argument[] + INIT(= N_("E989: Non-default argument follows default argument")); +EXTERN char e_missing_end_marker_str[] + INIT(= N_("E990: Missing end marker '%s'")); +EXTERN char e_cannot_use_heredoc_here[] + INIT(= N_("E991: Cannot use =<< here")); +#endif +EXTERN char e_not_allowed_in_modeline_when_modelineexpr_is_off[] + INIT(= N_("E992: Not allowed in a modeline when 'modelineexpr' is off")); +#ifdef FEAT_EVAL +EXTERN char e_window_nr_is_not_popup_window[] + INIT(= N_("E993: Window %d is not a popup window")); +EXTERN char e_not_allowed_in_popup_window[] + INIT(= N_("E994: Not allowed in a popup window")); +EXTERN char e_cannot_modify_existing_variable[] + INIT(= N_("E995: Cannot modify existing variable")); +EXTERN char e_cannot_lock_range[] + INIT(= N_("E996: Cannot lock a range")); +EXTERN char e_cannot_lock_option[] + INIT(= N_("E996: Cannot lock an option")); +EXTERN char e_cannot_lock_list_or_dict[] + INIT(= N_("E996: Cannot lock a list or dict")); +EXTERN char e_cannot_lock_environment_variable[] + INIT(= N_("E996: Cannot lock an environment variable")); +EXTERN char e_cannot_lock_register[] + INIT(= N_("E996: Cannot lock a register")); +#endif +#ifdef FEAT_PROP_POPUP +EXTERN char e_tabpage_not_found_nr[] + INIT(= N_("E997: Tabpage not found: %d")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_reduce_of_an_empty_str_with_no_initial_value[] + INIT(= N_("E998: Reduce of an empty %s with no initial value")); +#endif +EXTERN char e_scriptversion_not_supported_nr[] + INIT(= N_("E999: scriptversion not supported: %d")); +// E1000 unused +#ifdef FEAT_EVAL +EXTERN char e_variable_not_found_str[] + INIT(= N_("E1001: Variable not found: %s")); +EXTERN char e_syntax_error_at_str[] + INIT(= N_("E1002: Syntax error at %s")); +EXTERN char e_missing_return_value[] + INIT(= N_("E1003: Missing return value")); +EXTERN char e_white_space_required_before_and_after_str_at_str[] + INIT(= N_("E1004: White space required before and after '%s' at \"%s\"")); +EXTERN char e_too_many_argument_types[] + INIT(= N_("E1005: Too many argument types")); +EXTERN char e_str_is_used_as_argument[] + INIT(= N_("E1006: %s is used as an argument")); +EXTERN char e_mandatory_argument_after_optional_argument[] + INIT(= N_("E1007: Mandatory argument after optional argument")); +EXTERN char e_missing_type_after_str[] + INIT(= N_("E1008: Missing after %s")); +EXTERN char e_missing_gt_after_type_str[] + INIT(= N_("E1009: Missing > after type: %s")); +EXTERN char e_type_not_recognized_str[] + INIT(= N_("E1010: Type not recognized: %s")); +EXTERN char e_name_too_long_str[] + INIT(= N_("E1011: Name too long: %s")); +EXTERN char e_type_mismatch_expected_str_but_got_str[] + INIT(= N_("E1012: Type mismatch; expected %s but got %s")); +EXTERN char e_type_mismatch_expected_str_but_got_str_in_str[] + INIT(= N_("E1012: Type mismatch; expected %s but got %s in %s")); +EXTERN char e_argument_nr_type_mismatch_expected_str_but_got_str[] + INIT(= N_("E1013: Argument %d: type mismatch, expected %s but got %s")); +EXTERN char e_argument_nr_type_mismatch_expected_str_but_got_str_in_str[] + INIT(= N_("E1013: Argument %d: type mismatch, expected %s but got %s in %s")); +EXTERN char e_invalid_key_str[] + INIT(= N_("E1014: Invalid key: %s")); +EXTERN char e_name_expected_str[] + INIT(= N_("E1015: Name expected: %s")); +EXTERN char e_cannot_declare_a_scope_variable_str[] + INIT(= N_("E1016: Cannot declare a %s variable: %s")); +EXTERN char e_cannot_declare_an_environment_variable_str[] + INIT(= N_("E1016: Cannot declare an environment variable: %s")); +EXTERN char e_variable_already_declared_str[] + INIT(= N_("E1017: Variable already declared: %s")); +EXTERN char e_cannot_assign_to_constant_str[] + INIT(= N_("E1018: Cannot assign to a constant: %s")); +EXTERN char e_can_only_concatenate_to_string[] + INIT(= N_("E1019: Can only concatenate to string")); +EXTERN char e_cannot_use_operator_on_new_variable_str[] + INIT(= N_("E1020: Cannot use an operator on a new variable: %s")); +EXTERN char e_const_requires_a_value[] + INIT(= N_("E1021: Const requires a value")); +EXTERN char e_type_or_initialization_required[] + INIT(= N_("E1022: Type or initialization required")); +EXTERN char e_using_number_as_bool_nr[] + INIT(= N_("E1023: Using a Number as a Bool: %lld")); +EXTERN char e_using_number_as_string[] + INIT(= N_("E1024: Using a Number as a String")); +EXTERN char e_using_rcurly_outside_if_block_scope[] + INIT(= N_("E1025: Using } outside of a block scope")); +#endif +EXTERN char e_missing_rcurly[] + INIT(= N_("E1026: Missing }")); +#ifdef FEAT_EVAL +EXTERN char e_missing_return_statement[] + INIT(= N_("E1027: Missing return statement")); +EXTERN char e_compiling_def_function_failed[] + INIT(= N_("E1028: Compiling :def function failed")); +EXTERN char e_expected_str_but_got_str[] + INIT(= N_("E1029: Expected %s but got %s")); +EXTERN char e_using_string_as_number_str[] + INIT(= N_("E1030: Using a String as a Number: \"%s\"")); +EXTERN char e_cannot_use_void_value[] + INIT(= N_("E1031: Cannot use void value")); +EXTERN char e_missing_catch_or_finally[] + INIT(= N_("E1032: Missing :catch or :finally")); +EXTERN char e_catch_unreachable_after_catch_all[] + INIT(= N_("E1033: Catch unreachable after catch-all")); +EXTERN char e_cannot_use_reserved_name_str[] + INIT(= N_("E1034: Cannot use reserved name %s")); +EXTERN char e_percent_requires_number_arguments[] + // xgettext:no-c-format + INIT(= N_("E1035: % requires number arguments")); +EXTERN char e_char_requires_number_or_float_arguments[] + INIT(= N_("E1036: %c requires number or float arguments")); +EXTERN char e_cannot_use_str_with_str[] + INIT(= N_("E1037: Cannot use \"%s\" with %s")); +EXTERN char e_vim9script_can_only_be_used_in_script[] + INIT(= N_("E1038: \"vim9script\" can only be used in a script")); +EXTERN char e_vim9script_must_be_first_command_in_script[] + INIT(= N_("E1039: \"vim9script\" must be the first command in a script")); +#endif +EXTERN char e_cannot_use_scriptversion_after_vim9script[] + INIT(= N_("E1040: Cannot use :scriptversion after :vim9script")); +#ifdef FEAT_EVAL +EXTERN char e_redefining_script_item_str[] + INIT(= N_("E1041: Redefining script item: \"%s\"")); +EXTERN char e_export_can_only_be_used_in_vim9script[] + INIT(= N_("E1042: Export can only be used in vim9script")); +EXTERN char e_invalid_command_after_export[] + INIT(= N_("E1043: Invalid command after :export")); +EXTERN char e_export_with_invalid_argument[] + INIT(= N_("E1044: Export with invalid argument")); +// E1045 not used +// E1046 not used +EXTERN char e_syntax_error_in_import_str[] + INIT(= N_("E1047: Syntax error in import: %s")); +EXTERN char e_item_not_found_in_script_str[] + INIT(= N_("E1048: Item not found in script: %s")); +EXTERN char e_item_not_exported_in_script_str[] + INIT(= N_("E1049: Item not exported in script: %s")); +#endif +EXTERN char e_colon_required_before_range_str[] + INIT(= N_("E1050: Colon required before a range: %s")); +#ifdef FEAT_EVAL +EXTERN char e_wrong_argument_type_for_plus[] + INIT(= N_("E1051: Wrong argument type for +")); +EXTERN char e_cannot_declare_an_option_str[] + INIT(= N_("E1052: Cannot declare an option: %s")); +EXTERN char e_could_not_import_str[] + INIT(= N_("E1053: Could not import \"%s\"")); +EXTERN char e_variable_already_declared_in_script_str[] + INIT(= N_("E1054: Variable already declared in the script: %s")); +EXTERN char e_missing_name_after_dots[] + INIT(= N_("E1055: Missing name after ...")); +EXTERN char e_expected_type_str[] + INIT(= N_("E1056: Expected a type: %s")); +EXTERN char e_missing_enddef[] + INIT(= N_("E1057: Missing :enddef")); +EXTERN char e_function_nesting_too_deep[] + INIT(= N_("E1058: Function nesting too deep")); +EXTERN char e_no_white_space_allowed_before_colon_str[] + INIT(= N_("E1059: No white space allowed before colon: %s")); +EXTERN char e_expected_dot_after_name_str[] + INIT(= N_("E1060: Expected dot after name: %s")); +EXTERN char e_cannot_find_function_str[] + INIT(= N_("E1061: Cannot find function %s")); +EXTERN char e_cannot_index_number[] + INIT(= N_("E1062: Cannot index a Number")); +EXTERN char e_type_mismatch_for_v_variable[] + INIT(= N_("E1063: Type mismatch for v: variable")); +#endif +EXTERN char e_yank_register_changed_while_using_it[] + INIT(= N_("E1064: Yank register changed while using it")); +EXTERN char e_command_cannot_be_shortened_str[] + INIT(= N_("E1065: Command cannot be shortened: %s")); +#ifdef FEAT_EVAL +EXTERN char e_cannot_declare_a_register_str[] + INIT(= N_("E1066: Cannot declare a register: %s")); +EXTERN char e_separator_mismatch_str[] + INIT(= N_("E1067: Separator mismatch: %s")); +EXTERN char e_no_white_space_allowed_before_str_str[] + INIT(= N_("E1068: No white space allowed before '%s': %s")); +EXTERN char e_white_space_required_after_str_str[] + INIT(= N_("E1069: White space required after '%s': %s")); +EXTERN char e_invalid_string_for_import_str[] + INIT(= N_("E1071: Invalid string for :import: %s")); +EXTERN char e_cannot_compare_str_with_str[] + INIT(= N_("E1072: Cannot compare %s with %s")); +EXTERN char e_name_already_defined_str[] + INIT(= N_("E1073: Name already defined: %s")); +EXTERN char e_no_white_space_allowed_after_dot[] + INIT(= N_("E1074: No white space allowed after dot")); +EXTERN char e_namespace_not_supported_str[] + INIT(= N_("E1075: Namespace not supported: %s")); +// E1076 unused (was deleted) +EXTERN char e_missing_argument_type_for_str[] + INIT(= N_("E1077: Missing argument type for %s")); +#endif +EXTERN char e_invalid_command_nested_did_you_mean_plusplus_nested[] + INIT(= N_("E1078: Invalid command \"nested\", did you mean \"++nested\"?")); +#ifdef FEAT_EVAL +EXTERN char e_cannot_declare_variable_on_command_line[] + INIT(= N_("E1079: Cannot declare a variable on the command line")); +EXTERN char e_invalid_assignment[] + INIT(= N_("E1080: Invalid assignment")); +EXTERN char e_cannot_unlet_str[] + INIT(= N_("E1081: Cannot unlet %s")); +#endif +EXTERN char e_command_modifier_without_command[] + INIT(= N_("E1082: Command modifier without command")); +#ifdef FEAT_EVAL +EXTERN char e_missing_backtick[] + INIT(= N_("E1083: Missing backtick")); +EXTERN char e_cannot_delete_vim9_script_function_str[] + INIT(= N_("E1084: Cannot delete Vim9 script function %s")); +EXTERN char e_not_callable_type_str[] + INIT(= N_("E1085: Not a callable type: %s")); +// E1086 unused +EXTERN char e_cannot_use_index_when_declaring_variable[] + INIT(= N_("E1087: Cannot use an index when declaring a variable")); +EXTERN char e_script_cannot_import_itself[] + INIT(= N_("E1088: Script cannot import itself")); +EXTERN char e_unknown_variable_str[] + INIT(= N_("E1089: Unknown variable: %s")); +EXTERN char e_cannot_assign_to_argument_str[] + INIT(= N_("E1090: Cannot assign to argument %s")); +EXTERN char e_function_is_not_compiled_str[] + INIT(= N_("E1091: Function is not compiled: %s")); +EXTERN char e_cannot_nest_redir[] + INIT(= N_("E1092: Cannot nest :redir")); +EXTERN char e_expected_nr_items_but_got_nr[] + INIT(= N_("E1093: Expected %d items but got %d")); +EXTERN char e_import_can_only_be_used_in_script[] + INIT(= N_("E1094: Import can only be used in a script")); +EXTERN char e_unreachable_code_after_str[] + INIT(= N_("E1095: Unreachable code after :%s")); +EXTERN char e_returning_value_in_function_without_return_type[] + INIT(= N_("E1096: Returning a value in a function without a return type")); +EXTERN char e_line_incomplete[] + INIT(= N_("E1097: Line incomplete")); +EXTERN char e_string_list_or_blob_required[] + INIT(= N_("E1098: String, List or Blob required")); +EXTERN char e_unknown_error_while_executing_str[] + INIT(= N_("E1099: Unknown error while executing %s")); +EXTERN char e_command_not_supported_in_vim9_script_missing_var_str[] + INIT(= N_("E1100: Command not supported in Vim9 script (missing :var?): %s")); +EXTERN char e_cannot_declare_script_variable_in_function_str[] + INIT(= N_("E1101: Cannot declare a script variable in a function: %s")); +EXTERN char e_lambda_function_not_found_str[] + INIT(= N_("E1102: Lambda function not found: %s")); +EXTERN char e_dictionary_not_set[] + INIT(= N_("E1103: Dictionary not set")); +EXTERN char e_missing_gt[] + INIT(= N_("E1104: Missing >")); +EXTERN char e_cannot_convert_str_to_string[] + INIT(= N_("E1105: Cannot convert %s to string")); + +PLURAL_MSG(e_one_argument_too_many, "E1106: One argument too many", + e_nr_arguments_too_many, "E1106: %d arguments too many") + +EXTERN char e_string_list_dict_or_blob_required[] + INIT(= N_("E1107: String, List, Dict or Blob required")); +// E1108 unused +EXTERN char e_list_item_nr_is_not_list[] + INIT(= N_("E1109: List item %d is not a List")); +EXTERN char e_list_item_nr_does_not_contain_3_numbers[] + INIT(= N_("E1110: List item %d does not contain 3 numbers")); +EXTERN char e_list_item_nr_range_invalid[] + INIT(= N_("E1111: List item %d range invalid")); +EXTERN char e_list_item_nr_cell_width_invalid[] + INIT(= N_("E1112: List item %d cell width invalid")); +EXTERN char e_overlapping_ranges_for_nr[] + INIT(= N_("E1113: Overlapping ranges for 0x%lx")); +EXTERN char e_only_values_of_0x80_and_higher_supported[] + INIT(= N_("E1114: Only values of 0x80 and higher supported")); +EXTERN char e_assert_fails_fourth_argument[] + INIT(= N_("E1115: \"assert_fails()\" fourth argument must be a number")); +EXTERN char e_assert_fails_fifth_argument[] + INIT(= N_("E1116: \"assert_fails()\" fifth argument must be a string")); +EXTERN char e_cannot_use_bang_with_nested_def[] + INIT(= N_("E1117: Cannot use ! with nested :def")); +EXTERN char e_cannot_change_locked_list[] + INIT(= N_("E1118: Cannot change locked list")); +EXTERN char e_cannot_change_locked_list_item[] + INIT(= N_("E1119: Cannot change locked list item")); +EXTERN char e_cannot_change_dict[] + INIT(= N_("E1120: Cannot change dict")); +EXTERN char e_cannot_change_dict_item[] + INIT(= N_("E1121: Cannot change dict item")); +EXTERN char e_variable_is_locked_str[] + INIT(= N_("E1122: Variable is locked: %s")); +EXTERN char e_missing_comma_before_argument_str[] + INIT(= N_("E1123: Missing comma before argument: %s")); +EXTERN char e_str_cannot_be_used_in_legacy_vim_script[] + INIT(= N_("E1124: \"%s\" cannot be used in legacy Vim script")); +EXTERN char e_final_requires_a_value[] + INIT(= N_("E1125: Final requires a value")); +EXTERN char e_cannot_use_let_in_vim9_script[] + INIT(= N_("E1126: Cannot use :let in Vim9 script")); +EXTERN char e_missing_name_after_dot[] + INIT(= N_("E1127: Missing name after dot")); +EXTERN char e_endblock_without_block[] + INIT(= N_("E1128: } without {")); +EXTERN char e_throw_with_empty_string[] + INIT(= N_("E1129: Throw with empty string")); +EXTERN char e_cannot_add_to_null_list[] + INIT(= N_("E1130: Cannot add to null list")); +EXTERN char e_cannot_add_to_null_blob[] + INIT(= N_("E1131: Cannot add to null blob")); +EXTERN char e_missing_function_argument[] + INIT(= N_("E1132: Missing function argument")); +EXTERN char e_cannot_extend_null_dict[] + INIT(= N_("E1133: Cannot extend a null dict")); +EXTERN char e_cannot_extend_null_list[] + INIT(= N_("E1134: Cannot extend a null list")); +EXTERN char e_using_string_as_bool_str[] + INIT(= N_("E1135: Using a String as a Bool: \"%s\"")); +#endif +EXTERN char e_cmd_mapping_must_end_with_cr_before_second_cmd[] + INIT(= N_("E1136: mapping must end with before second ")); +// E1137 unused +#ifdef FEAT_EVAL +EXTERN char e_using_bool_as_number[] + INIT(= N_("E1138: Using a Bool as a Number")); +EXTERN char e_missing_matching_bracket_after_dict_key[] + INIT(= N_("E1139: Missing matching bracket after dict key")); +EXTERN char e_for_argument_must_be_sequence_of_lists[] + INIT(= N_("E1140: :for argument must be a sequence of lists")); +EXTERN char e_indexable_type_required[] + INIT(= N_("E1141: Indexable type required")); +EXTERN char e_calling_test_garbagecollect_now_while_v_testing_is_not_set[] + INIT(= N_("E1142: Calling test_garbagecollect_now() while v:testing is not set")); +EXTERN char e_empty_expression_str[] + INIT(= N_("E1143: Empty expression: \"%s\"")); +EXTERN char e_command_str_not_followed_by_white_space_str[] + INIT(= N_("E1144: Command \"%s\" is not followed by white space: %s")); +EXTERN char e_missing_heredoc_end_marker_str[] + INIT(= N_("E1145: Missing heredoc end marker: %s")); +EXTERN char e_command_not_recognized_str[] + INIT(= N_("E1146: Command not recognized: %s")); +EXTERN char e_list_not_set[] + INIT(= N_("E1147: List not set")); +EXTERN char e_cannot_index_str[] + INIT(= N_("E1148: Cannot index a %s")); +EXTERN char e_script_variable_invalid_after_reload_in_function_str[] + INIT(= N_("E1149: Script variable is invalid after reload in function %s")); +EXTERN char e_script_variable_type_changed[] + INIT(= N_("E1150: Script variable type changed")); +EXTERN char e_mismatched_endfunction[] + INIT(= N_("E1151: Mismatched endfunction")); +EXTERN char e_mismatched_enddef[] + INIT(= N_("E1152: Mismatched enddef")); +EXTERN char e_invalid_operation_for_str[] + INIT(= N_("E1153: Invalid operation for %s")); +EXTERN char e_divide_by_zero[] + INIT(= N_("E1154: Divide by zero")); +#endif +EXTERN char e_cannot_define_autocommands_for_all_events[] + INIT(= N_("E1155: Cannot define autocommands for ALL events")); +EXTERN char e_cannot_change_arglist_recursively[] + INIT(= N_("E1156: Cannot change the argument list recursively")); +#ifdef FEAT_EVAL +EXTERN char e_missing_return_type[] + INIT(= N_("E1157: Missing return type")); +EXTERN char e_cannot_use_flatten_in_vim9_script[] + INIT(= N_("E1158: Cannot use flatten() in Vim9 script, use flattennew()")); +#endif +EXTERN char e_cannot_split_window_when_closing_buffer[] + INIT(= N_("E1159: Cannot split a window when closing the buffer")); +#ifdef FEAT_EVAL +EXTERN char e_cannot_use_default_for_variable_arguments[] + INIT(= N_("E1160: Cannot use a default for variable arguments")); +EXTERN char e_cannot_json_encode_str[] + INIT(= N_("E1161: Cannot json encode a %s")); +EXTERN char e_register_name_must_be_one_char_str[] + INIT(= N_("E1162: Register name must be one character: %s")); +EXTERN char e_variable_nr_type_mismatch_expected_str_but_got_str[] + INIT(= N_("E1163: Variable %d: type mismatch, expected %s but got %s")); +EXTERN char e_variable_nr_type_mismatch_expected_str_but_got_str_in_str[] + INIT(= N_("E1163: Variable %d: type mismatch, expected %s but got %s in %s")); +#endif +EXTERN char e_vim9cmd_must_be_followed_by_command[] + INIT(= N_("E1164: vim9cmd must be followed by a command")); +#ifdef FEAT_EVAL +EXTERN char e_cannot_use_range_with_assignment_str[] + INIT(= N_("E1165: Cannot use a range with an assignment: %s")); +EXTERN char e_cannot_use_range_with_dictionary[] + INIT(= N_("E1166: Cannot use a range with a dictionary")); +EXTERN char e_argument_name_shadows_existing_variable_str[] + INIT(= N_("E1167: Argument name shadows existing variable: %s")); +EXTERN char e_argument_already_declared_in_script_str[] + INIT(= N_("E1168: Argument already declared in the script: %s")); +EXTERN char e_expression_too_recursive_str[] + INIT(= N_("E1169: Expression too recursive: %s")); +EXTERN char e_cannot_use_hash_curly_to_start_comment[] + INIT(= N_("E1170: Cannot use #{ to start a comment")); +EXTERN char e_missing_end_block[] + INIT(= N_("E1171: Missing } after inline function")); +EXTERN char e_cannot_use_default_values_in_lambda[] + INIT(= N_("E1172: Cannot use default values in a lambda")); +EXTERN char e_text_found_after_str_str[] + INIT(= N_("E1173: Text found after %s: %s")); +EXTERN char e_string_required_for_argument_nr[] + INIT(= N_("E1174: String required for argument %d")); +EXTERN char e_non_empty_string_required_for_argument_nr[] + INIT(= N_("E1175: Non-empty string required for argument %d")); +EXTERN char e_misplaced_command_modifier[] + INIT(= N_("E1176: Misplaced command modifier")); +EXTERN char e_for_loop_on_str_not_supported[] + INIT(= N_("E1177: For loop on %s not supported")); +EXTERN char e_cannot_lock_unlock_local_variable[] + INIT(= N_("E1178: Cannot lock or unlock a local variable")); +#endif +#ifdef FEAT_TERMINAL +EXTERN char e_failed_to_extract_pwd_from_str_check_your_shell_config[] + INIT(= N_("E1179: Failed to extract PWD from %s, check your shell's config related to OSC 7")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_variable_arguments_type_must_be_list_str[] + INIT(= N_("E1180: Variable arguments type must be a list: %s")); +EXTERN char e_cannot_use_underscore_here[] + INIT(= N_("E1181: Cannot use an underscore here")); +EXTERN char e_cannot_define_dict_func_in_vim9_script_str[] + INIT(= N_("E1182: Cannot define a dict function in Vim9 script: %s")); +EXTERN char e_cannot_use_range_with_assignment_operator_str[] + INIT(= N_("E1183: Cannot use a range with an assignment operator: %s")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_blob_not_set[] + INIT(= N_("E1184: Blob not set")); +EXTERN char e_missing_redir_end[] + INIT(= N_("E1185: Missing :redir END")); +EXTERN char e_expression_does_not_result_in_value_str[] + INIT(= N_("E1186: Expression does not result in a value: %s")); +#endif +EXTERN char e_failed_to_source_defaults[] + INIT(= N_("E1187: Failed to source defaults.vim")); +#if defined(FEAT_TERMINAL) +EXTERN char e_cannot_open_terminal_from_command_line_window[] + INIT(= N_("E1188: Cannot open a terminal from the command line window")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_cannot_use_legacy_with_command_str[] + INIT(= N_("E1189: Cannot use :legacy with this command: %s")); + +PLURAL_MSG(e_one_argument_too_few, "E1190: One argument too few", + e_nr_arguments_too_few, "E1190: %d arguments too few") + +EXTERN char e_call_to_function_that_failed_to_compile_str[] + INIT(= N_("E1191: Call to function that failed to compile: %s")); +EXTERN char e_empty_function_name[] + INIT(= N_("E1192: Empty function name")); +#endif +// libsodium +#ifdef FEAT_CRYPT +# ifndef FEAT_SODIUM +EXTERN char e_libsodium_not_built_in[] + INIT(= N_("E1193: cryptmethod xchacha20 not built into this Vim")); +# else +# if 0 +EXTERN char e_libsodium_cannot_encrypt_header[] + INIT(= N_("E1194: Cannot encrypt header, not enough space")); +EXTERN char e_libsodium_cannot_encrypt_buffer[] + INIT(= N_("E1195: Cannot encrypt buffer, not enough space")); +EXTERN char e_libsodium_cannot_decrypt_header[] + INIT(= N_("E1196: Cannot decrypt header, not enough space")); +# endif +EXTERN char e_libsodium_cannot_allocate_buffer[] + INIT(= N_("E1197: Cannot allocate_buffer for encryption")); +EXTERN char e_libsodium_decryption_failed_header_incomplete[] + INIT(= N_("E1198: Decryption failed: Header incomplete!")); +# if 0 +EXTERN char e_libsodium_cannot_decrypt_buffer[] + INIT(= N_("E1199: Cannot decrypt buffer, not enough space")); +# endif +EXTERN char e_libsodium_decryption_failed[] + INIT(= N_("E1200: Decryption failed!")); +EXTERN char e_libsodium_decryption_failed_premature[] + INIT(= N_("E1201: Decryption failed: pre-mature end of file!")); +# endif +#endif +#ifdef FEAT_EVAL +EXTERN char e_no_white_space_allowed_after_str_str[] + INIT(= N_("E1202: No white space allowed after '%s': %s")); +EXTERN char e_dot_not_allowed_after_str_str[] + INIT(= N_("E1203: Dot not allowed after a %s: %s")); +#endif +EXTERN char e_regexp_number_after_dot_pos_search_chr[] + INIT(= N_("E1204: No Number allowed after .: '\\%%%c'")); +EXTERN char e_no_white_space_allowed_between_option_and[] + INIT(= N_("E1205: No white space allowed between option and")); +#ifdef FEAT_EVAL +EXTERN char e_dict_required_for_argument_nr[] + INIT(= N_("E1206: Dictionary required for argument %d")); +EXTERN char e_expression_without_effect_str[] + INIT(= N_("E1207: Expression without an effect: %s")); +#endif +EXTERN char e_complete_used_without_allowing_arguments[] + INIT(= N_("E1208: -complete used without allowing arguments")); +#ifdef FEAT_EVAL +EXTERN char e_invalid_value_for_line_number_str[] + INIT(= N_("E1209: Invalid value for a line number: \"%s\"")); +EXTERN char e_number_required_for_argument_nr[] + INIT(= N_("E1210: Number required for argument %d")); +EXTERN char e_list_required_for_argument_nr[] + INIT(= N_("E1211: List required for argument %d")); +EXTERN char e_bool_required_for_argument_nr[] + INIT(= N_("E1212: Bool required for argument %d")); +EXTERN char e_redefining_imported_item_str[] + INIT(= N_("E1213: Redefining imported item \"%s\"")); +#endif +#if defined(FEAT_DIGRAPHS) +EXTERN char e_digraph_must_be_just_two_characters_str[] + INIT(= N_("E1214: Digraph must be just two characters: %s")); +EXTERN char e_digraph_argument_must_be_one_character_str[] + INIT(= N_("E1215: Digraph must be one character: %s")); +EXTERN char e_digraph_setlist_argument_must_be_list_of_lists_with_two_items[] + INIT(= N_("E1216: digraph_setlist() argument must be a list of lists with two items")); +#endif +#ifdef FEAT_EVAL +# ifdef FEAT_JOB_CHANNEL +EXTERN char e_chan_or_job_required_for_argument_nr[] + INIT(= N_("E1217: Channel or Job required for argument %d")); +EXTERN char e_job_required_for_argument_nr[] + INIT(= N_("E1218: Job required for argument %d")); +# endif +EXTERN char e_float_or_number_required_for_argument_nr[] + INIT(= N_("E1219: Float or Number required for argument %d")); +EXTERN char e_string_or_number_required_for_argument_nr[] + INIT(= N_("E1220: String or Number required for argument %d")); +# ifdef FEAT_JOB_CHANNEL +EXTERN char e_string_or_blob_required_for_argument_nr[] + INIT(= N_("E1221: String or Blob required for argument %d")); +# endif +EXTERN char e_string_or_list_required_for_argument_nr[] + INIT(= N_("E1222: String or List required for argument %d")); +EXTERN char e_string_or_dict_required_for_argument_nr[] + INIT(= N_("E1223: String or Dictionary required for argument %d")); +EXTERN char e_string_number_or_list_required_for_argument_nr[] + INIT(= N_("E1224: String, Number or List required for argument %d")); +EXTERN char e_string_list_or_dict_required_for_argument_nr[] + INIT(= N_("E1225: String, List or Dictionary required for argument %d")); +EXTERN char e_list_or_blob_required_for_argument_nr[] + INIT(= N_("E1226: List or Blob required for argument %d")); +EXTERN char e_list_or_dict_required_for_argument_nr[] + INIT(= N_("E1227: List or Dictionary required for argument %d")); +EXTERN char e_list_dict_or_blob_required_for_argument_nr[] + INIT(= N_("E1228: List, Dictionary or Blob required for argument %d")); +EXTERN char e_expected_dictionary_for_using_key_str_but_got_str[] + INIT(= N_("E1229: Expected dictionary for using key \"%s\", but got %s")); +#endif +#ifdef FEAT_SODIUM +EXTERN char e_encryption_sodium_mlock_failed[] + INIT(= N_("E1230: Encryption: sodium_mlock() failed")); +#endif +EXTERN char e_cannot_use_bar_to_separate_commands_here_str[] + INIT(= N_("E1231: Cannot use a bar to separate commands here: %s")); +#ifdef FEAT_EVAL +EXTERN char e_argument_of_exists_compiled_must_be_literal_string[] + INIT(= N_("E1232: Argument of exists_compiled() must be a literal string")); +EXTERN char e_exists_compiled_can_only_be_used_in_def_function[] + INIT(= N_("E1233: exists_compiled() can only be used in a :def function")); +#endif +EXTERN char e_legacy_must_be_followed_by_command[] + INIT(= N_("E1234: legacy must be followed by a command")); +#ifdef FEAT_EVAL +// E1235 unused +EXTERN char e_cannot_use_str_itself_it_is_imported[] + INIT(= N_("E1236: Cannot use %s itself, it is imported")); +#endif +EXTERN char e_no_such_user_defined_command_in_current_buffer_str[] + INIT(= N_("E1237: No such user-defined command in current buffer: %s")); +#ifdef FEAT_EVAL +EXTERN char e_blob_required_for_argument_nr[] + INIT(= N_("E1238: Blob required for argument %d")); +EXTERN char e_invalid_value_for_blob_nr[] + INIT(= N_("E1239: Invalid value for blob: %d")); +#endif +EXTERN char e_resulting_text_too_long[] + INIT(= N_("E1240: Resulting text too long")); +#ifdef FEAT_EVAL +EXTERN char e_separator_not_supported_str[] + INIT(= N_("E1241: Separator not supported: %s")); +EXTERN char e_no_white_space_allowed_before_separator_str[] + INIT(= N_("E1242: No white space allowed before separator: %s")); +#endif +#ifdef FEAT_GUI_GTK +EXTERN char e_ascii_code_not_in_range[] + INIT(= N_("E1243: ASCII code not in 32-127 range")); +#endif +#ifdef FEAT_EVAL +# if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) +EXTERN char e_bad_color_string_str[] + INIT(= N_("E1244: Bad color string: %s")); +# endif +EXTERN char e_cannot_expand_sfile_in_vim9_function[] + INIT(= N_("E1245: Cannot expand in a Vim9 function")); +EXTERN char e_cannot_find_variable_to_unlock_str[] + INIT(= N_("E1246: Cannot find variable to (un)lock: %s")); +#endif +EXTERN char e_line_number_out_of_range[] + INIT(= N_("E1247: Line number out of range")); +#ifdef FEAT_EVAL +EXTERN char e_closure_called_from_invalid_context[] + INIT(= N_("E1248: Closure called from invalid context")); +#endif +EXTERN char e_highlight_group_name_too_long[] + INIT(= N_("E1249: Highlight group name too long")); +#ifdef FEAT_EVAL +EXTERN char e_argument_of_str_must_be_list_string_dictionary_or_blob[] + INIT(= N_("E1250: Argument of %s must be a List, String, Dictionary or Blob")); +EXTERN char e_list_dict_blob_or_string_required_for_argument_nr[] + INIT(= N_("E1251: List, Dictionary, Blob or String required for argument %d")); +EXTERN char e_string_list_or_blob_required_for_argument_nr[] + INIT(= N_("E1252: String, List or Blob required for argument %d")); +// E1253 unused +EXTERN char e_cannot_use_script_variable_in_for_loop[] + INIT(= N_("E1254: Cannot use script variable in for loop")); +#endif +EXTERN char e_cmd_mapping_must_end_with_cr[] + INIT(= N_("E1255: mapping must end with ")); +#ifdef FEAT_EVAL +EXTERN char e_string_or_function_required_for_argument_nr[] + INIT(= N_("E1256: String or function required for argument %d")); +EXTERN char e_imported_script_must_use_as_or_end_in_dot_vim_str[] + INIT(= N_("E1257: Imported script must use \"as\" or end in .vim: %s")); +EXTERN char e_no_dot_after_imported_name_str[] + INIT(= N_("E1258: No '.' after imported name: %s")); +EXTERN char e_missing_name_after_imported_name_str[] + INIT(= N_("E1259: Missing name after imported name: %s")); +EXTERN char e_cannot_unlet_imported_item_str[] + INIT(= N_("E1260: Cannot unlet an imported item: %s")); +EXTERN char e_cannot_import_dot_vim_without_using_as[] + INIT(= N_("E1261: Cannot import .vim without using \"as\"")); +EXTERN char e_cannot_import_same_script_twice_str[] + INIT(= N_("E1262: Cannot import the same script twice: %s")); +EXTERN char e_cannot_use_name_with_hash_in_vim9_script_use_export_instead[] + INIT(= N_("E1263: Cannot use name with # in Vim9 script, use export instead")); +EXTERN char e_autoload_import_cannot_use_absolute_or_relative_path[] + INIT(= N_("E1264: Autoload import cannot use absolute or relative path: %s")); +EXTERN char e_cannot_use_partial_here[] + INIT(= N_("E1265: Cannot use a partial here")); +#endif +#if defined(FEAT_PYTHON3) && defined(MSWIN) +EXTERN char e_critical_error_in_python3_initialization_check_your_installation[] + INIT(= N_("E1266: Critical error in python3 initialization, check your python3 installation")); +#endif +#ifdef FEAT_EVAL +EXTERN char e_function_name_must_start_with_capital_str[] + INIT(= N_("E1267: Function name must start with a capital: %s")); +EXTERN char e_cannot_use_s_colon_in_vim9_script_str[] + INIT(= N_("E1268: Cannot use s: in Vim9 script: %s")); +EXTERN char e_cannot_create_vim9_script_variable_in_function_str[] + INIT(= N_("E1269: Cannot create a Vim9 script variable in a function: %s")); +#endif +EXTERN char e_cannot_use_s_backslash_in_vim9_script[] + INIT(= N_("E1270: Cannot use :s\\/sub/ in Vim9 script")); +#ifdef FEAT_EVAL +EXTERN char e_compiling_closure_without_context_str[] + INIT(= N_("E1271: Compiling closure without context: %s")); +EXTERN char e_using_type_not_in_script_context_str[] + INIT(= N_("E1272: Using type not in a script context: %s")); +#endif +EXTERN char e_nfa_regexp_missing_value_in_chr[] + INIT(= N_("E1273: (NFA regexp) missing value in '\\%%%c'")); +EXTERN char e_no_script_file_name_to_substitute_for_script[] + INIT(= N_("E1274: No script file name to substitute for \"