From: Bram Moolenaar Date: Tue, 23 Jul 2019 20:15:25 +0000 (+0200) Subject: patch 8.1.1736: viminfo support is spread out X-Git-Tag: v8.1.1736 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c3328169d5566b97a6a6921067017e4369dd7cd6;p=vim patch 8.1.1736: viminfo support is spread out Problem: Viminfo support is spread out. Solution: Move more viminfo code to viminfo.c. (Yegappan Lakshmanan, closes #4717) Reorder code to make most functions static. --- diff --git a/src/ex_cmds.c b/src/ex_cmds.c index 47d1998e8..9e093ee1b 100644 --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -5085,24 +5085,23 @@ global_exe(char_u *cmd) } #ifdef FEAT_VIMINFO - int -read_viminfo_sub_string(vir_T *virp, int force) +/* + * Get the previous substitute pattern. + */ + char_u * +get_old_sub(void) { - if (force) - vim_free(old_sub); - if (force || old_sub == NULL) - old_sub = viminfo_readstring(virp, 1, TRUE); - return viminfo_readline(virp); + return old_sub; } +/* + * Set the previous substitute pattern. "val" must be allocated. + */ void -write_viminfo_sub_string(FILE *fp) +set_old_sub(char_u *val) { - if (get_viminfo_parameter('/') != 0 && old_sub != NULL) - { - fputs(_("\n# Last Substitute String:\n$"), fp); - viminfo_writestring(fp, old_sub); - } + vim_free(old_sub); + old_sub = val; } #endif // FEAT_VIMINFO diff --git a/src/fileio.c b/src/fileio.c index 03183255e..5e79870ad 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -31,9 +31,6 @@ static char_u *next_fenc(char_u **pp); #ifdef FEAT_EVAL static char_u *readfile_charconvert(char_u *fname, char_u *fenc, int *fdp); #endif -#ifdef FEAT_VIMINFO -static void check_marks_read(void); -#endif #ifdef FEAT_CRYPT static char_u *check_for_cryptkey(char_u *cryptkey, char_u *ptr, long *sizep, off_T *filesizep, int newfile, char_u *fname, int *did_ask); #endif @@ -2855,25 +2852,6 @@ readfile_charconvert( } #endif - -#ifdef FEAT_VIMINFO -/* - * Read marks for the current buffer from the viminfo file, when we support - * buffer marks and the buffer has a name. - */ - static void -check_marks_read(void) -{ - if (!curbuf->b_marks_read && get_viminfo_parameter('\'') > 0 - && curbuf->b_ffname != NULL) - read_viminfo(NULL, VIF_WANT_MARKS); - - /* Always set b_marks_read; needed when 'viminfo' is changed to include - * the ' parameter after opening a buffer. */ - curbuf->b_marks_read = TRUE; -} -#endif - #if defined(FEAT_CRYPT) || defined(PROTO) /* * Check for magic number used for encryption. Applies to the current buffer. diff --git a/src/ops.c b/src/ops.c index a0b91f978..29ade3f19 100644 --- a/src/ops.c +++ b/src/ops.c @@ -23,45 +23,6 @@ * 37 = Selection register '*'. Only if FEAT_CLIPBOARD defined * 38 = Clipboard register '+'. Only if FEAT_CLIPBOARD and FEAT_X11 defined */ -/* - * Symbolic names for some registers. - */ -#define DELETION_REGISTER 36 -#ifdef FEAT_CLIPBOARD -# define STAR_REGISTER 37 -# ifdef FEAT_X11 -# define PLUS_REGISTER 38 -# else -# define PLUS_REGISTER STAR_REGISTER /* there is only one */ -# endif -#endif -#ifdef FEAT_DND -# define TILDE_REGISTER (PLUS_REGISTER + 1) -#endif - -#ifdef FEAT_CLIPBOARD -# ifdef FEAT_DND -# define NUM_REGISTERS (TILDE_REGISTER + 1) -# else -# define NUM_REGISTERS (PLUS_REGISTER + 1) -# endif -#else -# define NUM_REGISTERS 37 -#endif - -/* - * Each yank register has an array of pointers to lines. - */ -typedef struct -{ - char_u **y_array; /* pointer to array of line pointers */ - linenr_T y_size; /* number of lines in y_array */ - char_u y_type; /* MLINE, MCHAR or MBLOCK */ - colnr_T y_width; /* only set if y_type == MBLOCK */ -#ifdef FEAT_VIMINFO - time_t y_time_set; -#endif -} yankreg_T; static yankreg_T y_regs[NUM_REGISTERS]; @@ -160,6 +121,31 @@ static char opchars[][3] = {Ctrl_X, NUL, OPF_CHANGE}, // OP_NR_SUB }; + yankreg_T * +get_y_regs(void) +{ + return y_regs; +} + + yankreg_T * +get_y_current(void) +{ + return y_current; +} + + yankreg_T * +get_y_previous(void) +{ + return y_previous; +} + + void +set_y_previous(yankreg_T *yreg) +{ + y_previous = yreg; +} + + /* * Translate a command name into an operator type. * Must only be called with a valid operator name! @@ -1192,6 +1178,18 @@ stuff_yank(int regname, char_u *p) static int execreg_lastc = NUL; + int +get_execreg_lastc(void) +{ + return execreg_lastc; +} + + void +set_execreg_lastc(int lastc) +{ + execreg_lastc = lastc; +} + /* * Execute a yank register: copy it into the stuff buffer. * @@ -5958,400 +5956,6 @@ theend: return did_change; } -#ifdef FEAT_VIMINFO - -static yankreg_T *y_read_regs = NULL; - -#define REG_PREVIOUS 1 -#define REG_EXEC 2 - -/* - * Prepare for reading viminfo registers when writing viminfo later. - */ - void -prepare_viminfo_registers(void) -{ - y_read_regs = ALLOC_CLEAR_MULT(yankreg_T, NUM_REGISTERS); -} - - void -finish_viminfo_registers(void) -{ - int i; - int j; - - if (y_read_regs != NULL) - { - for (i = 0; i < NUM_REGISTERS; ++i) - if (y_read_regs[i].y_array != NULL) - { - for (j = 0; j < y_read_regs[i].y_size; j++) - vim_free(y_read_regs[i].y_array[j]); - vim_free(y_read_regs[i].y_array); - } - VIM_CLEAR(y_read_regs); - } -} - - int -read_viminfo_register(vir_T *virp, int force) -{ - int eof; - int do_it = TRUE; - int size; - int limit; - int i; - int set_prev = FALSE; - char_u *str; - char_u **array = NULL; - int new_type = MCHAR; /* init to shut up compiler */ - colnr_T new_width = 0; /* init to shut up compiler */ - - /* We only get here (hopefully) if line[0] == '"' */ - str = virp->vir_line + 1; - - /* If the line starts with "" this is the y_previous register. */ - if (*str == '"') - { - set_prev = TRUE; - str++; - } - - if (!ASCII_ISALNUM(*str) && *str != '-') - { - if (viminfo_error("E577: ", _("Illegal register name"), virp->vir_line)) - return TRUE; /* too many errors, pretend end-of-file */ - do_it = FALSE; - } - get_yank_register(*str++, FALSE); - if (!force && y_current->y_array != NULL) - do_it = FALSE; - - if (*str == '@') - { - /* "x@: register x used for @@ */ - if (force || execreg_lastc == NUL) - execreg_lastc = str[-1]; - } - - size = 0; - limit = 100; /* Optimized for registers containing <= 100 lines */ - if (do_it) - { - /* - * Build the new register in array[]. - * y_array is kept as-is until done. - * The "do_it" flag is reset when something is wrong, in which case - * array[] needs to be freed. - */ - if (set_prev) - y_previous = y_current; - array = ALLOC_MULT(char_u *, limit); - str = skipwhite(skiptowhite(str)); - if (STRNCMP(str, "CHAR", 4) == 0) - new_type = MCHAR; - else if (STRNCMP(str, "BLOCK", 5) == 0) - new_type = MBLOCK; - else - new_type = MLINE; - /* get the block width; if it's missing we get a zero, which is OK */ - str = skipwhite(skiptowhite(str)); - new_width = getdigits(&str); - } - - while (!(eof = viminfo_readline(virp)) - && (virp->vir_line[0] == TAB || virp->vir_line[0] == '<')) - { - if (do_it) - { - if (size == limit) - { - char_u **new_array = (char_u **) - alloc(limit * 2 * sizeof(char_u *)); - - if (new_array == NULL) - { - do_it = FALSE; - break; - } - for (i = 0; i < limit; i++) - new_array[i] = array[i]; - vim_free(array); - array = new_array; - limit *= 2; - } - str = viminfo_readstring(virp, 1, TRUE); - if (str != NULL) - array[size++] = str; - else - /* error, don't store the result */ - do_it = FALSE; - } - } - - if (do_it) - { - /* free y_array[] */ - for (i = 0; i < y_current->y_size; i++) - vim_free(y_current->y_array[i]); - vim_free(y_current->y_array); - - y_current->y_type = new_type; - y_current->y_width = new_width; - y_current->y_size = size; - y_current->y_time_set = 0; - if (size == 0) - { - y_current->y_array = NULL; - } - else - { - /* Move the lines from array[] to y_array[]. */ - y_current->y_array = ALLOC_MULT(char_u *, size); - for (i = 0; i < size; i++) - { - if (y_current->y_array == NULL) - vim_free(array[i]); - else - y_current->y_array[i] = array[i]; - } - } - } - else - { - /* Free array[] if it was filled. */ - for (i = 0; i < size; i++) - vim_free(array[i]); - } - vim_free(array); - - return eof; -} - -/* - * Accept a new style register line from the viminfo, store it when it's new. - */ - void -handle_viminfo_register(garray_T *values, int force) -{ - bval_T *vp = (bval_T *)values->ga_data; - int flags; - int name; - int type; - int linecount; - int width; - time_t timestamp; - yankreg_T *y_ptr; - int i; - - /* Check the format: - * |{bartype},{flags},{name},{type}, - * {linecount},{width},{timestamp},"line1","line2" - */ - if (values->ga_len < 6 - || vp[0].bv_type != BVAL_NR - || vp[1].bv_type != BVAL_NR - || vp[2].bv_type != BVAL_NR - || vp[3].bv_type != BVAL_NR - || vp[4].bv_type != BVAL_NR - || vp[5].bv_type != BVAL_NR) - return; - flags = vp[0].bv_nr; - name = vp[1].bv_nr; - if (name < 0 || name >= NUM_REGISTERS) - return; - type = vp[2].bv_nr; - if (type != MCHAR && type != MLINE && type != MBLOCK) - return; - linecount = vp[3].bv_nr; - if (values->ga_len < 6 + linecount) - return; - width = vp[4].bv_nr; - if (width < 0) - return; - - if (y_read_regs != NULL) - /* Reading viminfo for merging and writing. Store the register - * content, don't update the current registers. */ - y_ptr = &y_read_regs[name]; - else - y_ptr = &y_regs[name]; - - /* Do not overwrite unless forced or the timestamp is newer. */ - timestamp = (time_t)vp[5].bv_nr; - if (y_ptr->y_array != NULL && !force - && (timestamp == 0 || y_ptr->y_time_set > timestamp)) - return; - - if (y_ptr->y_array != NULL) - for (i = 0; i < y_ptr->y_size; i++) - vim_free(y_ptr->y_array[i]); - vim_free(y_ptr->y_array); - - if (y_read_regs == NULL) - { - if (flags & REG_PREVIOUS) - y_previous = y_ptr; - if ((flags & REG_EXEC) && (force || execreg_lastc == NUL)) - execreg_lastc = get_register_name(name); - } - y_ptr->y_type = type; - y_ptr->y_width = width; - y_ptr->y_size = linecount; - y_ptr->y_time_set = timestamp; - if (linecount == 0) - { - y_ptr->y_array = NULL; - return; - } - y_ptr->y_array = ALLOC_MULT(char_u *, linecount); - if (y_ptr->y_array == NULL) - { - y_ptr->y_size = 0; // ensure object state is consistent - return; - } - for (i = 0; i < linecount; i++) - { - if (vp[i + 6].bv_allocated) - { - y_ptr->y_array[i] = vp[i + 6].bv_string; - vp[i + 6].bv_string = NULL; - } - else - y_ptr->y_array[i] = vim_strsave(vp[i + 6].bv_string); - } -} - - void -write_viminfo_registers(FILE *fp) -{ - int i, j; - char_u *type; - char_u c; - int num_lines; - int max_num_lines; - int max_kbyte; - long len; - yankreg_T *y_ptr; - - fputs(_("\n# Registers:\n"), fp); - - /* Get '<' value, use old '"' value if '<' is not found. */ - max_num_lines = get_viminfo_parameter('<'); - if (max_num_lines < 0) - max_num_lines = get_viminfo_parameter('"'); - if (max_num_lines == 0) - return; - max_kbyte = get_viminfo_parameter('s'); - if (max_kbyte == 0) - return; - - for (i = 0; i < NUM_REGISTERS; i++) - { -#ifdef FEAT_CLIPBOARD - /* Skip '*'/'+' register, we don't want them back next time */ - if (i == STAR_REGISTER || i == PLUS_REGISTER) - continue; -#endif -#ifdef FEAT_DND - /* Neither do we want the '~' register */ - if (i == TILDE_REGISTER) - continue; -#endif - /* When reading viminfo for merging and writing: Use the register from - * viminfo if it's newer. */ - if (y_read_regs != NULL - && y_read_regs[i].y_array != NULL - && (y_regs[i].y_array == NULL || - y_read_regs[i].y_time_set > y_regs[i].y_time_set)) - y_ptr = &y_read_regs[i]; - else if (y_regs[i].y_array == NULL) - continue; - else - y_ptr = &y_regs[i]; - - /* Skip empty registers. */ - num_lines = y_ptr->y_size; - if (num_lines == 0 - || (num_lines == 1 && y_ptr->y_type == MCHAR - && *y_ptr->y_array[0] == NUL)) - continue; - - if (max_kbyte > 0) - { - /* Skip register if there is more text than the maximum size. */ - len = 0; - for (j = 0; j < num_lines; j++) - len += (long)STRLEN(y_ptr->y_array[j]) + 1L; - if (len > (long)max_kbyte * 1024L) - continue; - } - - switch (y_ptr->y_type) - { - case MLINE: - type = (char_u *)"LINE"; - break; - case MCHAR: - type = (char_u *)"CHAR"; - break; - case MBLOCK: - type = (char_u *)"BLOCK"; - break; - default: - semsg(_("E574: Unknown register type %d"), y_ptr->y_type); - type = (char_u *)"LINE"; - break; - } - if (y_previous == &y_regs[i]) - fprintf(fp, "\""); - c = get_register_name(i); - fprintf(fp, "\"%c", c); - if (c == execreg_lastc) - fprintf(fp, "@"); - fprintf(fp, "\t%s\t%d\n", type, (int)y_ptr->y_width); - - /* If max_num_lines < 0, then we save ALL the lines in the register */ - if (max_num_lines > 0 && num_lines > max_num_lines) - num_lines = max_num_lines; - for (j = 0; j < num_lines; j++) - { - putc('\t', fp); - viminfo_writestring(fp, y_ptr->y_array[j]); - } - - { - int flags = 0; - int remaining; - - /* New style with a bar line. Format: - * |{bartype},{flags},{name},{type}, - * {linecount},{width},{timestamp},"line1","line2" - * flags: REG_PREVIOUS - register is y_previous - * REG_EXEC - used for @@ - */ - if (y_previous == &y_regs[i]) - flags |= REG_PREVIOUS; - if (c == execreg_lastc) - flags |= REG_EXEC; - fprintf(fp, "|%d,%d,%d,%d,%d,%d,%ld", BARTYPE_REGISTER, flags, - i, y_ptr->y_type, num_lines, (int)y_ptr->y_width, - (long)y_ptr->y_time_set); - /* 11 chars for type/flags/name/type, 3 * 20 for numbers */ - remaining = LSIZE - 71; - for (j = 0; j < num_lines; j++) - { - putc(',', fp); - --remaining; - remaining = barline_writestring(fp, y_ptr->y_array[j], - remaining); - } - putc('\n', fp); - } - } -} -#endif /* FEAT_VIMINFO */ - #if defined(FEAT_CLIPBOARD) || defined(PROTO) /* * SELECTION / PRIMARY ('*') diff --git a/src/option.c b/src/option.c index c17e9128a..3959d4711 100644 --- a/src/option.c +++ b/src/option.c @@ -5573,49 +5573,6 @@ set_options_bin( } } -#ifdef FEAT_VIMINFO -/* - * Find the parameter represented by the given character (eg ', :, ", or /), - * and return its associated value in the 'viminfo' string. - * Only works for number parameters, not for 'r' or 'n'. - * If the parameter is not specified in the string or there is no following - * number, return -1. - */ - int -get_viminfo_parameter(int type) -{ - char_u *p; - - p = find_viminfo_parameter(type); - if (p != NULL && VIM_ISDIGIT(*p)) - return atoi((char *)p); - return -1; -} - -/* - * Find the parameter represented by the given character (eg ''', ':', '"', or - * '/') in the 'viminfo' option and return a pointer to the string after it. - * Return NULL if the parameter is not specified in the string. - */ - char_u * -find_viminfo_parameter(int type) -{ - char_u *p; - - for (p = p_viminfo; *p; ++p) - { - if (*p == type) - return p + 1; - if (*p == 'n') /* 'n' is always the last one */ - break; - p = vim_strchr(p, ','); /* skip until next ',' */ - if (p == NULL) /* hit the end without finding parameter */ - break; - } - return NULL; -} -#endif - /* * Expand environment variables for some string options. * These string options cannot be indirect! diff --git a/src/proto/ex_cmds.pro b/src/proto/ex_cmds.pro index 783ed11e9..c31fee375 100644 --- a/src/proto/ex_cmds.pro +++ b/src/proto/ex_cmds.pro @@ -34,8 +34,8 @@ void do_sub(exarg_T *eap); int do_sub_msg(int count_only); void ex_global(exarg_T *eap); void global_exe(char_u *cmd); -int read_viminfo_sub_string(vir_T *virp, int force); -void write_viminfo_sub_string(FILE *fp); +char_u *get_old_sub(void); +void set_old_sub(char_u *val); void free_old_sub(void); int prepare_tagpreview(int undo_sync); void ex_help(exarg_T *eap); diff --git a/src/proto/ops.pro b/src/proto/ops.pro index 0ff27a2c8..ed5877dfb 100644 --- a/src/proto/ops.pro +++ b/src/proto/ops.pro @@ -1,4 +1,8 @@ /* ops.c */ +yankreg_T *get_y_regs(void); +yankreg_T *get_y_current(void); +yankreg_T *get_y_previous(void); +void set_y_previous(yankreg_T *yreg); int get_op_type(int char1, int char2); int op_on_lines(int op); int op_is_change(int op); @@ -19,6 +23,8 @@ void put_register(int name, void *reg); void free_register(void *reg); int yank_register_mline(int regname); int do_record(int c); +int get_execreg_lastc(void); +void set_execreg_lastc(int lastc); int do_execreg(int regname, int colon, int addcr, int silent); int insert_reg(int regname, int literally_arg); int get_spec_reg(int regname, char_u **argp, int *allocated, int errmsg); @@ -47,11 +53,6 @@ int fex_format(linenr_T lnum, long count, int c); void format_lines(linenr_T line_count, int avoid_fex); int paragraph_start(linenr_T lnum); void op_addsub(oparg_T *oap, linenr_T Prenum1, int g_cmd); -void prepare_viminfo_registers(void); -void finish_viminfo_registers(void); -int read_viminfo_register(vir_T *virp, int force); -void handle_viminfo_register(garray_T *values, int force); -void write_viminfo_registers(FILE *fp); void x11_export_final_selection(void); void clip_free_selection(Clipboard_T *cbd); void clip_get_selection(Clipboard_T *cbd); diff --git a/src/proto/option.pro b/src/proto/option.pro index 49b2b6570..86c751658 100644 --- a/src/proto/option.pro +++ b/src/proto/option.pro @@ -12,8 +12,6 @@ void set_title_defaults(void); int do_set(char_u *arg, int opt_flags); int string_to_key(char_u *arg, int multi_byte); void set_options_bin(int oldval, int newval, int opt_flags); -int get_viminfo_parameter(int type); -char_u *find_viminfo_parameter(int type); void check_options(void); void check_buf_options(buf_T *buf); void free_string_option(char_u *p); diff --git a/src/proto/search.pro b/src/proto/search.pro index eb614a18f..143333d46 100644 --- a/src/proto/search.pro +++ b/src/proto/search.pro @@ -46,6 +46,6 @@ int current_quote(oparg_T *oap, long count, int include, int quotechar); int current_search(long count, int forward); int linewhite(linenr_T lnum); void find_pattern_in_path(char_u *ptr, int dir, int len, int whole, int skip_comments, int type, long count, int action, linenr_T start_lnum, linenr_T end_lnum); -int read_viminfo_search_pattern(vir_T *virp, int force); -void write_viminfo_search_pattern(FILE *fp); +struct spat *get_spat(int idx); +int get_spat_last_idx(void); /* vim: set ft=c : */ diff --git a/src/proto/viminfo.pro b/src/proto/viminfo.pro index 0261b3861..a1f03373d 100644 --- a/src/proto/viminfo.pro +++ b/src/proto/viminfo.pro @@ -1,18 +1,7 @@ /* viminfo.c */ -int viminfo_error(char *errnum, char *message, char_u *line); +int get_viminfo_parameter(int type); +void check_marks_read(void); int read_viminfo(char_u *file, int flags); void write_viminfo(char_u *file, int forceit); -int viminfo_readline(vir_T *virp); -char_u *viminfo_readstring(vir_T *virp, int off, int convert); -void viminfo_writestring(FILE *fd, char_u *p); -int barline_writestring(FILE *fd, char_u *s, int remaining_start); void ex_viminfo(exarg_T *eap); -int read_viminfo_filemark(vir_T *virp, int force); -void prepare_viminfo_marks(void); -void finish_viminfo_marks(void); -void handle_viminfo_mark(garray_T *values, int force); -void write_viminfo_filemarks(FILE *fp); -int removable(char_u *name); -void write_viminfo_marks(FILE *fp_out, garray_T *buflist); -void copy_viminfo_marks(vir_T *virp, FILE *fp_out, garray_T *buflist, int eof, int flags); /* vim: set ft=c : */ diff --git a/src/search.c b/src/search.c index ce87a3ef3..626bc3daf 100644 --- a/src/search.c +++ b/src/search.c @@ -23,9 +23,6 @@ static int skip_chars(int, int); static void show_pat_in_path(char_u *, int, int, int, FILE *, linenr_T *, long); #endif -#ifdef FEAT_VIMINFO -static void wvsp_one(FILE *fp, int idx, char *s, int sc); -#endif static void search_stat(int dirc, pos_T *pos, int show_top_bot_msg, char_u *msgbuf, int recompute); /* @@ -51,31 +48,12 @@ static void search_stat(int dirc, pos_T *pos, int show_top_bot_msg, char_u *msgb * Henry Spencer's regular expression library. See regexp.c. */ -/* The offset for a search command is store in a soff struct */ -/* Note: only spats[0].off is really used */ -struct soffset -{ - int dir; /* search direction, '/' or '?' */ - int line; /* search has line offset */ - int end; /* search set cursor at end */ - long off; /* line or char offset */ -}; - -/* A search pattern and its attributes are stored in a spat struct */ -struct spat -{ - char_u *pat; /* the pattern (in allocated memory) or NULL */ - int magic; /* magicness of the pattern */ - int no_scs; /* no smartcase for this pattern */ - struct soffset off; -}; - /* * Two search patterns are remembered: One for the :substitute command and * one for other searches. last_idx points to the one that was used the last * time. */ -static struct spat spats[2] = +static spat_T spats[2] = { {NULL, TRUE, FALSE, {'/', 0, 0, 0L}}, /* last used search pat */ {NULL, TRUE, FALSE, {'/', 0, 0, 0L}} /* last used substitute pat */ @@ -90,7 +68,7 @@ static char_u lastc_bytes[MB_MAXBYTES + 1]; static int lastc_bytelen = 1; /* >1 for multi-byte char */ /* copy of spats[], for keeping the search patterns while executing autocmds */ -static struct spat saved_spats[2]; +static spat_T saved_spats[2]; # ifdef FEAT_SEARCH_EXTRA static int saved_spats_last_idx = 0; static int saved_spats_no_hlsearch = 0; @@ -349,7 +327,7 @@ free_search_patterns(void) #ifdef FEAT_SEARCH_EXTRA // copy of spats[RE_SEARCH], for keeping the search patterns while incremental // searching -static struct spat saved_last_search_spat; +static spat_T saved_last_search_spat; static int did_save_last_search_spat = 0; static int saved_last_idx = 0; static int saved_no_hlsearch = 0; @@ -1210,7 +1188,7 @@ do_search( { pos_T pos; /* position of the last match */ char_u *searchstr; - struct soffset old_off; + soffset_T old_off; int retval; /* Return value */ char_u *p; long c; @@ -5831,124 +5809,15 @@ show_pat_in_path( #endif #ifdef FEAT_VIMINFO - int -read_viminfo_search_pattern(vir_T *virp, int force) + spat_T * +get_spat(int idx) { - char_u *lp; - int idx = -1; - int magic = FALSE; - int no_scs = FALSE; - int off_line = FALSE; - int off_end = 0; - long off = 0; - int setlast = FALSE; -#ifdef FEAT_SEARCH_EXTRA - static int hlsearch_on = FALSE; -#endif - char_u *val; - - /* - * Old line types: - * "/pat", "&pat": search/subst. pat - * "~/pat", "~&pat": last used search/subst. pat - * New line types: - * "~h", "~H": hlsearch highlighting off/on - * "~pat" - * : 'm' off, 'M' on - * : 's' off, 'S' on - * : 'L' line offset, 'l' char offset - * : 'E' from end, 'e' from start - * : decimal, offset - * : '~' last used pattern - * : '/' search pat, '&' subst. pat - */ - lp = virp->vir_line; - if (lp[0] == '~' && (lp[1] == 'm' || lp[1] == 'M')) /* new line type */ - { - if (lp[1] == 'M') /* magic on */ - magic = TRUE; - if (lp[2] == 's') - no_scs = TRUE; - if (lp[3] == 'L') - off_line = TRUE; - if (lp[4] == 'E') - off_end = SEARCH_END; - lp += 5; - off = getdigits(&lp); - } - if (lp[0] == '~') /* use this pattern for last-used pattern */ - { - setlast = TRUE; - lp++; - } - if (lp[0] == '/') - idx = RE_SEARCH; - else if (lp[0] == '&') - idx = RE_SUBST; -#ifdef FEAT_SEARCH_EXTRA - else if (lp[0] == 'h') /* ~h: 'hlsearch' highlighting off */ - hlsearch_on = FALSE; - else if (lp[0] == 'H') /* ~H: 'hlsearch' highlighting on */ - hlsearch_on = TRUE; -#endif - if (idx >= 0) - { - if (force || spats[idx].pat == NULL) - { - val = viminfo_readstring(virp, (int)(lp - virp->vir_line + 1), - TRUE); - if (val != NULL) - { - set_last_search_pat(val, idx, magic, setlast); - vim_free(val); - spats[idx].no_scs = no_scs; - spats[idx].off.line = off_line; - spats[idx].off.end = off_end; - spats[idx].off.off = off; -#ifdef FEAT_SEARCH_EXTRA - if (setlast) - set_no_hlsearch(!hlsearch_on); -#endif - } - } - } - return viminfo_readline(virp); -} - - void -write_viminfo_search_pattern(FILE *fp) -{ - if (get_viminfo_parameter('/') != 0) - { -#ifdef FEAT_SEARCH_EXTRA - fprintf(fp, "\n# hlsearch on (H) or off (h):\n~%c", - (no_hlsearch || find_viminfo_parameter('h') != NULL) ? 'h' : 'H'); -#endif - wvsp_one(fp, RE_SEARCH, "", '/'); - wvsp_one(fp, RE_SUBST, _("Substitute "), '&'); - } + return &spats[idx]; } - static void -wvsp_one( - FILE *fp, /* file to write to */ - int idx, /* spats[] index */ - char *s, /* search pat */ - int sc) /* dir char */ + int +get_spat_last_idx(void) { - if (spats[idx].pat != NULL) - { - fprintf(fp, _("\n# Last %sSearch Pattern:\n~"), s); - /* off.dir is not stored, it's reset to forward */ - fprintf(fp, "%c%c%c%c%ld%s%c", - spats[idx].magic ? 'M' : 'm', /* magic */ - spats[idx].no_scs ? 's' : 'S', /* smartcase */ - spats[idx].off.line ? 'L' : 'l', /* line offset */ - spats[idx].off.end ? 'E' : 'e', /* offset from end */ - spats[idx].off.off, /* offset */ - last_idx == idx ? "~" : "", /* last used pat */ - sc); - viminfo_writestring(fp, spats[idx].pat); - } + return last_idx; } -#endif /* FEAT_VIMINFO */ +#endif diff --git a/src/structs.h b/src/structs.h index 367c70db3..ac233629c 100644 --- a/src/structs.h +++ b/src/structs.h @@ -3751,3 +3751,58 @@ typedef enum { FIND_POPUP, // also find popup windows FAIL_POPUP // return NULL if mouse on popup window } mouse_find_T; + +// Symbolic names for some registers. +#define DELETION_REGISTER 36 +#ifdef FEAT_CLIPBOARD +# define STAR_REGISTER 37 +# ifdef FEAT_X11 +# define PLUS_REGISTER 38 +# else +# define PLUS_REGISTER STAR_REGISTER // there is only one +# endif +#endif +#ifdef FEAT_DND +# define TILDE_REGISTER (PLUS_REGISTER + 1) +#endif + +#ifdef FEAT_CLIPBOARD +# ifdef FEAT_DND +# define NUM_REGISTERS (TILDE_REGISTER + 1) +# else +# define NUM_REGISTERS (PLUS_REGISTER + 1) +# endif +#else +# define NUM_REGISTERS 37 +#endif + +// Each yank register has an array of pointers to lines. +typedef struct +{ + char_u **y_array; // pointer to array of line pointers + linenr_T y_size; // number of lines in y_array + char_u y_type; // MLINE, MCHAR or MBLOCK + colnr_T y_width; // only set if y_type == MBLOCK +#ifdef FEAT_VIMINFO + time_t y_time_set; +#endif +} yankreg_T; + +// The offset for a search command is store in a soff struct +// Note: only spats[0].off is really used +typedef struct soffset +{ + int dir; // search direction, '/' or '?' + int line; // search has line offset + int end; // search set cursor at end + long off; // line or char offset +} soffset_T; + +// A search pattern and its attributes are stored in a spat struct +typedef struct spat +{ + char_u *pat; // the pattern (in allocated memory) or NULL + int magic; // magicness of the pattern + int no_scs; // no smartcase for this pattern + soffset_T off; +} spat_T; diff --git a/src/version.c b/src/version.c index e8f5d7fc3..4e91567a3 100644 --- a/src/version.c +++ b/src/version.c @@ -777,6 +777,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1736, /**/ 1735, /**/ diff --git a/src/viminfo.c b/src/viminfo.c index 7e192cecd..cd3bbb65c 100644 --- a/src/viminfo.c +++ b/src/viminfo.c @@ -18,6 +18,47 @@ static int viminfo_errcnt; +/* + * Find the parameter represented by the given character (eg ''', ':', '"', or + * '/') in the 'viminfo' option and return a pointer to the string after it. + * Return NULL if the parameter is not specified in the string. + */ + static char_u * +find_viminfo_parameter(int type) +{ + char_u *p; + + for (p = p_viminfo; *p; ++p) + { + if (*p == type) + return p + 1; + if (*p == 'n') // 'n' is always the last one + break; + p = vim_strchr(p, ','); // skip until next ',' + if (p == NULL) // hit the end without finding parameter + break; + } + return NULL; +} + +/* + * Find the parameter represented by the given character (eg ', :, ", or /), + * and return its associated value in the 'viminfo' string. + * Only works for number parameters, not for 'r' or 'n'. + * If the parameter is not specified in the string or there is no following + * number, return -1. + */ + int +get_viminfo_parameter(int type) +{ + char_u *p; + + p = find_viminfo_parameter(type); + if (p != NULL && VIM_ISDIGIT(*p)) + return atoi((char *)p); + return -1; +} + /* * Get the viminfo file name to use. * If "file" is given and not empty, use it (has already been expanded by @@ -65,6 +106,191 @@ viminfo_filename(char_u *file) return vim_strsave(file); } +/* + * write string to viminfo file + * - replace CTRL-V with CTRL-V CTRL-V + * - replace '\n' with CTRL-V 'n' + * - add a '\n' at the end + * + * For a long line: + * - write " CTRL-V \n " in first line + * - write " < \n " in second line + */ + static void +viminfo_writestring(FILE *fd, char_u *p) +{ + int c; + char_u *s; + int len = 0; + + for (s = p; *s != NUL; ++s) + { + if (*s == Ctrl_V || *s == '\n') + ++len; + ++len; + } + + // If the string will be too long, write its length and put it in the next + // line. Take into account that some room is needed for what comes before + // the string (e.g., variable name). Add something to the length for the + // '<', NL and trailing NUL. + if (len > LSIZE / 2) + fprintf(fd, IF_EB("\026%d\n<", CTRL_V_STR "%d\n<"), len + 3); + + while ((c = *p++) != NUL) + { + if (c == Ctrl_V || c == '\n') + { + putc(Ctrl_V, fd); + if (c == '\n') + c = 'n'; + } + putc(c, fd); + } + putc('\n', fd); +} + +/* + * Write a string in quotes that barline_parse() can read back. + * Breaks the line in less than LSIZE pieces when needed. + * Returns remaining characters in the line. + */ + static int +barline_writestring(FILE *fd, char_u *s, int remaining_start) +{ + char_u *p; + int remaining = remaining_start; + int len = 2; + + // Count the number of characters produced, including quotes. + for (p = s; *p != NUL; ++p) + { + if (*p == NL) + len += 2; + else if (*p == '"' || *p == '\\') + len += 2; + else + ++len; + } + if (len > remaining - 2) + { + fprintf(fd, ">%d\n|<", len); + remaining = LSIZE - 20; + } + + putc('"', fd); + for (p = s; *p != NUL; ++p) + { + if (*p == NL) + { + putc('\\', fd); + putc('n', fd); + --remaining; + } + else if (*p == '"' || *p == '\\') + { + putc('\\', fd); + putc(*p, fd); + --remaining; + } + else + putc(*p, fd); + --remaining; + + if (remaining < 3) + { + putc('\n', fd); + putc('|', fd); + putc('<', fd); + // Leave enough space for another continuation. + remaining = LSIZE - 20; + } + } + putc('"', fd); + return remaining - 2; +} + +/* + * Check string read from viminfo file. + * Remove '\n' at the end of the line. + * - replace CTRL-V CTRL-V with CTRL-V + * - replace CTRL-V 'n' with '\n' + * + * Check for a long line as written by viminfo_writestring(). + * + * Return the string in allocated memory (NULL when out of memory). + */ + static char_u * +viminfo_readstring( + vir_T *virp, + int off, // offset for virp->vir_line + int convert UNUSED) // convert the string +{ + char_u *retval; + char_u *s, *d; + long len; + + if (virp->vir_line[off] == Ctrl_V && vim_isdigit(virp->vir_line[off + 1])) + { + len = atol((char *)virp->vir_line + off + 1); + retval = lalloc(len, TRUE); + if (retval == NULL) + { + // Line too long? File messed up? Skip next line. + (void)vim_fgets(virp->vir_line, 10, virp->vir_fd); + return NULL; + } + (void)vim_fgets(retval, (int)len, virp->vir_fd); + s = retval + 1; // Skip the leading '<' + } + else + { + retval = vim_strsave(virp->vir_line + off); + if (retval == NULL) + return NULL; + s = retval; + } + + // Change CTRL-V CTRL-V to CTRL-V and CTRL-V n to \n in-place. + d = retval; + while (*s != NUL && *s != '\n') + { + if (s[0] == Ctrl_V && s[1] != NUL) + { + if (s[1] == 'n') + *d++ = '\n'; + else + *d++ = Ctrl_V; + s += 2; + } + else + *d++ = *s++; + } + *d = NUL; + + if (convert && virp->vir_conv.vc_type != CONV_NONE && *retval != NUL) + { + d = string_convert(&virp->vir_conv, retval, NULL); + if (d != NULL) + { + vim_free(retval); + retval = d; + } + } + + return retval; +} + +/* + * Read a line from the viminfo file. + * Returns TRUE for end-of-file; + */ + static int +viminfo_readline(vir_T *virp) +{ + return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd); +} + static int read_viminfo_bufferlist( vir_T *virp, @@ -119,6 +345,38 @@ read_viminfo_bufferlist( return viminfo_readline(virp); } +/* + * Return TRUE if "name" is on removable media (depending on 'viminfo'). + */ + static int +removable(char_u *name) +{ + char_u *p; + char_u part[51]; + int retval = FALSE; + size_t n; + + name = home_replace_save(NULL, name); + if (name != NULL) + { + for (p = p_viminfo; *p; ) + { + copy_option_part(&p, part, 51, ", "); + if (part[0] == 'r') + { + n = STRLEN(part + 1); + if (MB_STRNICMP(part + 1, name, n) == 0) + { + retval = TRUE; + break; + } + } + } + vim_free(name); + } + return retval; +} + static void write_viminfo_bufferlist(FILE *fp) { @@ -655,7 +913,7 @@ write_viminfo_history(FILE *fp, int merge) viminfo_hisidx[type] = 0; } } -#endif // FEAT_VIMINFO +#endif // FEAT_CMDHIST static void write_viminfo_barlines(vir_T *virp, FILE *fp_out) @@ -861,104 +1119,25 @@ barline_parse(vir_T *virp, char_u *text, garray_T *values) return TRUE; } + static void +write_viminfo_version(FILE *fp_out) +{ + fprintf(fp_out, "# Viminfo version\n|%d,%d\n\n", + BARTYPE_VERSION, VIMINFO_VERSION); +} + static int -read_viminfo_barline(vir_T *virp, int got_encoding, int force, int writing) +no_viminfo(void) { - char_u *p = virp->vir_line + 1; - int bartype; - garray_T values; - bval_T *vp; - int i; - int read_next = TRUE; - - /* - * The format is: |{bartype},{value},... - * For a very long string: - * |{bartype},>{length of "{text}{text2}"} - * |<{text1} - * |<{text2},{value} - * For a long line not using a string - * |{bartype},{lots of values},> - * |<{value},{value} - */ - if (*p == '<') - { - // Continuation line of an unrecognized item. - if (writing) - ga_add_string(&virp->vir_barlines, virp->vir_line); - } - else - { - ga_init2(&values, sizeof(bval_T), 20); - bartype = getdigits(&p); - switch (bartype) - { - case BARTYPE_VERSION: - // Only use the version when it comes before the encoding. - // If it comes later it was copied by a Vim version that - // doesn't understand the version. - if (!got_encoding) - { - read_next = barline_parse(virp, p, &values); - vp = (bval_T *)values.ga_data; - if (values.ga_len > 0 && vp->bv_type == BVAL_NR) - virp->vir_version = vp->bv_nr; - } - break; - - case BARTYPE_HISTORY: - read_next = barline_parse(virp, p, &values); - handle_viminfo_history(&values, writing); - break; - - case BARTYPE_REGISTER: - read_next = barline_parse(virp, p, &values); - handle_viminfo_register(&values, force); - break; - - case BARTYPE_MARK: - read_next = barline_parse(virp, p, &values); - handle_viminfo_mark(&values, force); - break; - - default: - // copy unrecognized line (for future use) - if (writing) - ga_add_string(&virp->vir_barlines, virp->vir_line); - } - for (i = 0; i < values.ga_len; ++i) - { - vp = (bval_T *)values.ga_data + i; - if (vp->bv_type == BVAL_STRING && vp->bv_allocated) - vim_free(vp->bv_string); - } - ga_clear(&values); - } - - if (read_next) - return viminfo_readline(virp); - return FALSE; -} - - static void -write_viminfo_version(FILE *fp_out) -{ - fprintf(fp_out, "# Viminfo version\n|%d,%d\n\n", - BARTYPE_VERSION, VIMINFO_VERSION); -} - - static int -no_viminfo(void) -{ - // "vim -i NONE" does not read or write a viminfo file - return STRCMP(p_viminfofile, "NONE") == 0; -} + // "vim -i NONE" does not read or write a viminfo file + return STRCMP(p_viminfofile, "NONE") == 0; +} /* * Report an error for reading a viminfo file. * Count the number of errors. When there are more than 10, return TRUE. */ - int + static int viminfo_error(char *errnum, char *message, char_u *line) { vim_snprintf((char *)IObuff, IOSIZE, _("%sviminfo: %s in line: "), @@ -1158,1551 +1337,1974 @@ write_viminfo_varlist(FILE *fp) } #endif // FEAT_EVAL + static int +read_viminfo_sub_string(vir_T *virp, int force) +{ + if (force || get_old_sub() == NULL) + set_old_sub(viminfo_readstring(virp, 1, TRUE)); + return viminfo_readline(virp); +} + + static void +write_viminfo_sub_string(FILE *fp) +{ + char_u *old_sub = get_old_sub(); + + if (get_viminfo_parameter('/') != 0 && old_sub != NULL) + { + fputs(_("\n# Last Substitute String:\n$"), fp); + viminfo_writestring(fp, old_sub); + } +} + /* - * read_viminfo_up_to_marks() -- Only called from do_viminfo(). Reads in the - * first part of the viminfo file which contains everything but the marks that - * are local to a file. Returns TRUE when end-of-file is reached. -- webb + * Functions relating to reading/writing the search pattern from viminfo */ + static int -read_viminfo_up_to_marks( - vir_T *virp, - int forceit, - int writing) +read_viminfo_search_pattern(vir_T *virp, int force) { - int eof; - buf_T *buf; - int got_encoding = FALSE; - -#ifdef FEAT_CMDHIST - prepare_viminfo_history(forceit ? 9999 : 0, writing); + char_u *lp; + int idx = -1; + int magic = FALSE; + int no_scs = FALSE; + int off_line = FALSE; + int off_end = 0; + long off = 0; + int setlast = FALSE; +#ifdef FEAT_SEARCH_EXTRA + static int hlsearch_on = FALSE; #endif - - eof = viminfo_readline(virp); - while (!eof && virp->vir_line[0] != '>') + char_u *val; + spat_T *spat; + + // Old line types: + // "/pat", "&pat": search/subst. pat + // "~/pat", "~&pat": last used search/subst. pat + // New line types: + // "~h", "~H": hlsearch highlighting off/on + // "~pat" + // : 'm' off, 'M' on + // : 's' off, 'S' on + // : 'L' line offset, 'l' char offset + // : 'E' from end, 'e' from start + // : decimal, offset + // : '~' last used pattern + // : '/' search pat, '&' subst. pat + lp = virp->vir_line; + if (lp[0] == '~' && (lp[1] == 'm' || lp[1] == 'M')) // new line type { - switch (virp->vir_line[0]) - { - // Characters reserved for future expansion, ignored now - case '+': // "+40 /path/dir file", for running vim without args - case '^': // to be defined - case '<': // long line - ignored - // A comment or empty line. - case NUL: - case '\r': - case '\n': - case '#': - eof = viminfo_readline(virp); - break; - case '|': - eof = read_viminfo_barline(virp, got_encoding, - forceit, writing); - break; - case '*': // "*encoding=value" - got_encoding = TRUE; - eof = viminfo_encoding(virp); - break; - case '!': // global variable -#ifdef FEAT_EVAL - eof = read_viminfo_varlist(virp, writing); -#else - eof = viminfo_readline(virp); + if (lp[1] == 'M') // magic on + magic = TRUE; + if (lp[2] == 's') + no_scs = TRUE; + if (lp[3] == 'L') + off_line = TRUE; + if (lp[4] == 'E') + off_end = SEARCH_END; + lp += 5; + off = getdigits(&lp); + } + if (lp[0] == '~') // use this pattern for last-used pattern + { + setlast = TRUE; + lp++; + } + if (lp[0] == '/') + idx = RE_SEARCH; + else if (lp[0] == '&') + idx = RE_SUBST; +#ifdef FEAT_SEARCH_EXTRA + else if (lp[0] == 'h') // ~h: 'hlsearch' highlighting off + hlsearch_on = FALSE; + else if (lp[0] == 'H') // ~H: 'hlsearch' highlighting on + hlsearch_on = TRUE; #endif - break; - case '%': // entry for buffer list - eof = read_viminfo_bufferlist(virp, writing); - break; - case '"': - // When registers are in bar lines skip the old style register - // lines. - if (virp->vir_version < VIMINFO_VERSION_WITH_REGISTERS) - eof = read_viminfo_register(virp, forceit); - else - do { - eof = viminfo_readline(virp); - } while (!eof && (virp->vir_line[0] == TAB - || virp->vir_line[0] == '<')); - break; - case '/': // Search string - case '&': // Substitute search string - case '~': // Last search string, followed by '/' or '&' - eof = read_viminfo_search_pattern(virp, forceit); - break; - case '$': - eof = read_viminfo_sub_string(virp, forceit); - break; - case ':': - case '?': - case '=': - case '@': -#ifdef FEAT_CMDHIST - // When history is in bar lines skip the old style history - // lines. - if (virp->vir_version < VIMINFO_VERSION_WITH_HISTORY) - eof = read_viminfo_history(virp, writing); - else + spat = get_spat(idx); + if (idx >= 0) + { + if (force || spat->pat == NULL) + { + val = viminfo_readstring(virp, (int)(lp - virp->vir_line + 1), + TRUE); + if (val != NULL) + { + set_last_search_pat(val, idx, magic, setlast); + vim_free(val); + spat->no_scs = no_scs; + spat->off.line = off_line; + spat->off.end = off_end; + spat->off.off = off; +#ifdef FEAT_SEARCH_EXTRA + if (setlast) + set_no_hlsearch(!hlsearch_on); #endif - eof = viminfo_readline(virp); - break; - case '-': - case '\'': - // When file marks are in bar lines skip the old style lines. - if (virp->vir_version < VIMINFO_VERSION_WITH_MARKS) - eof = read_viminfo_filemark(virp, forceit); - else - eof = viminfo_readline(virp); - break; - default: - if (viminfo_error("E575: ", _("Illegal starting char"), - virp->vir_line)) - eof = TRUE; - else - eof = viminfo_readline(virp); - break; + } } } + return viminfo_readline(virp); +} -#ifdef FEAT_CMDHIST - // Finish reading history items. - if (!writing) - finish_viminfo_history(virp); + static void +wvsp_one( + FILE *fp, // file to write to + int idx, // spats[] index + char *s, // search pat + int sc) // dir char +{ + spat_T *spat = get_spat(idx); + if (spat->pat != NULL) + { + fprintf(fp, _("\n# Last %sSearch Pattern:\n~"), s); + // off.dir is not stored, it's reset to forward + fprintf(fp, "%c%c%c%c%ld%s%c", + spat->magic ? 'M' : 'm', // magic + spat->no_scs ? 's' : 'S', // smartcase + spat->off.line ? 'L' : 'l', // line offset + spat->off.end ? 'E' : 'e', // offset from end + spat->off.off, // offset + get_spat_last_idx() == idx ? "~" : "", // last used pat + sc); + viminfo_writestring(fp, spat->pat); + } +} + + static void +write_viminfo_search_pattern(FILE *fp) +{ + if (get_viminfo_parameter('/') != 0) + { +#ifdef FEAT_SEARCH_EXTRA + fprintf(fp, "\n# hlsearch on (H) or off (h):\n~%c", + (no_hlsearch || find_viminfo_parameter('h') != NULL) ? 'h' : 'H'); #endif + wvsp_one(fp, RE_SEARCH, "", '/'); + wvsp_one(fp, RE_SUBST, _("Substitute "), '&'); + } +} - // Change file names to buffer numbers for fmarks. - FOR_ALL_BUFFERS(buf) - fmarks_check_names(buf); +/* + * Functions relating to reading/writing registers from viminfo + */ - return eof; -} +static yankreg_T *y_read_regs = NULL; + +#define REG_PREVIOUS 1 +#define REG_EXEC 2 /* - * do_viminfo() -- Should only be called from read_viminfo() & write_viminfo(). + * Prepare for reading viminfo registers when writing viminfo later. */ static void -do_viminfo(FILE *fp_in, FILE *fp_out, int flags) +prepare_viminfo_registers(void) { - int eof = FALSE; - vir_T vir; - int merge = FALSE; - int do_copy_marks = FALSE; - garray_T buflist; + y_read_regs = ALLOC_CLEAR_MULT(yankreg_T, NUM_REGISTERS); +} - if ((vir.vir_line = alloc(LSIZE)) == NULL) - return; - vir.vir_fd = fp_in; - vir.vir_conv.vc_type = CONV_NONE; - ga_init2(&vir.vir_barlines, (int)sizeof(char_u *), 100); - vir.vir_version = -1; + static void +finish_viminfo_registers(void) +{ + int i; + int j; - if (fp_in != NULL) + if (y_read_regs != NULL) { - if (flags & VIF_WANT_INFO) - { - if (fp_out != NULL) + for (i = 0; i < NUM_REGISTERS; ++i) + if (y_read_regs[i].y_array != NULL) { - // Registers and marks are read and kept separate from what - // this Vim is using. They are merged when writing. - prepare_viminfo_registers(); - prepare_viminfo_marks(); + for (j = 0; j < y_read_regs[i].y_size; j++) + vim_free(y_read_regs[i].y_array[j]); + vim_free(y_read_regs[i].y_array); } + VIM_CLEAR(y_read_regs); + } +} - eof = read_viminfo_up_to_marks(&vir, - flags & VIF_FORCEIT, fp_out != NULL); - merge = TRUE; - } - else if (flags != 0) - // Skip info, find start of marks - while (!(eof = viminfo_readline(&vir)) - && vir.vir_line[0] != '>') - ; + static int +read_viminfo_register(vir_T *virp, int force) +{ + int eof; + int do_it = TRUE; + int size; + int limit; + int i; + int set_prev = FALSE; + char_u *str; + char_u **array = NULL; + int new_type = MCHAR; // init to shut up compiler + colnr_T new_width = 0; // init to shut up compiler + yankreg_T *y_current_p; - do_copy_marks = (flags & - (VIF_WANT_MARKS | VIF_GET_OLDFILES | VIF_FORCEIT)); - } + // We only get here (hopefully) if line[0] == '"' + str = virp->vir_line + 1; - if (fp_out != NULL) + // If the line starts with "" this is the y_previous register. + if (*str == '"') { - // Write the info: - fprintf(fp_out, _("# This viminfo file was generated by Vim %s.\n"), - VIM_VERSION_MEDIUM); - fputs(_("# You may edit it if you're careful!\n\n"), fp_out); - write_viminfo_version(fp_out); - fputs(_("# Value of 'encoding' when this file was written\n"), fp_out); - fprintf(fp_out, "*encoding=%s\n\n", p_enc); - write_viminfo_search_pattern(fp_out); - write_viminfo_sub_string(fp_out); -#ifdef FEAT_CMDHIST - write_viminfo_history(fp_out, merge); -#endif - write_viminfo_registers(fp_out); - finish_viminfo_registers(); -#ifdef FEAT_EVAL - write_viminfo_varlist(fp_out); -#endif - write_viminfo_filemarks(fp_out); - finish_viminfo_marks(); - write_viminfo_bufferlist(fp_out); - write_viminfo_barlines(&vir, fp_out); - - if (do_copy_marks) - ga_init2(&buflist, sizeof(buf_T *), 50); - write_viminfo_marks(fp_out, do_copy_marks ? &buflist : NULL); + set_prev = TRUE; + str++; } - if (do_copy_marks) + if (!ASCII_ISALNUM(*str) && *str != '-') { - copy_viminfo_marks(&vir, fp_out, &buflist, eof, flags); - if (fp_out != NULL) - ga_clear(&buflist); + if (viminfo_error("E577: ", _("Illegal register name"), virp->vir_line)) + return TRUE; // too many errors, pretend end-of-file + do_it = FALSE; } + get_yank_register(*str++, FALSE); + y_current_p = get_y_current(); + if (!force && y_current_p->y_array != NULL) + do_it = FALSE; - vim_free(vir.vir_line); - if (vir.vir_conv.vc_type != CONV_NONE) - convert_setup(&vir.vir_conv, NULL, NULL); - ga_clear_strings(&vir.vir_barlines); -} + if (*str == '@') + { + // "x@: register x used for @@ + if (force || get_execreg_lastc() == NUL) + set_execreg_lastc(str[-1]); + } -/* - * read_viminfo() -- Read the viminfo file. Registers etc. which are already - * set are not over-written unless "flags" includes VIF_FORCEIT. -- webb - */ - int -read_viminfo( - char_u *file, // file name or NULL to use default name - int flags) // VIF_WANT_INFO et al. -{ - FILE *fp; - char_u *fname; + size = 0; + limit = 100; // Optimized for registers containing <= 100 lines + if (do_it) + { + // Build the new register in array[]. + // y_array is kept as-is until done. + // The "do_it" flag is reset when something is wrong, in which case + // array[] needs to be freed. + if (set_prev) + set_y_previous(y_current_p); + array = ALLOC_MULT(char_u *, limit); + str = skipwhite(skiptowhite(str)); + if (STRNCMP(str, "CHAR", 4) == 0) + new_type = MCHAR; + else if (STRNCMP(str, "BLOCK", 5) == 0) + new_type = MBLOCK; + else + new_type = MLINE; + // get the block width; if it's missing we get a zero, which is OK + str = skipwhite(skiptowhite(str)); + new_width = getdigits(&str); + } - if (no_viminfo()) - return FAIL; + while (!(eof = viminfo_readline(virp)) + && (virp->vir_line[0] == TAB || virp->vir_line[0] == '<')) + { + if (do_it) + { + if (size == limit) + { + char_u **new_array = (char_u **) + alloc(limit * 2 * sizeof(char_u *)); - fname = viminfo_filename(file); // get file name in allocated buffer - if (fname == NULL) - return FAIL; - fp = mch_fopen((char *)fname, READBIN); + if (new_array == NULL) + { + do_it = FALSE; + break; + } + for (i = 0; i < limit; i++) + new_array[i] = array[i]; + vim_free(array); + array = new_array; + limit *= 2; + } + str = viminfo_readstring(virp, 1, TRUE); + if (str != NULL) + array[size++] = str; + else + // error, don't store the result + do_it = FALSE; + } + } - if (p_verbose > 0) + if (do_it) { - verbose_enter(); - smsg(_("Reading viminfo file \"%s\"%s%s%s"), - fname, - (flags & VIF_WANT_INFO) ? _(" info") : "", - (flags & VIF_WANT_MARKS) ? _(" marks") : "", - (flags & VIF_GET_OLDFILES) ? _(" oldfiles") : "", - fp == NULL ? _(" FAILED") : ""); - verbose_leave(); + // free y_array[] + for (i = 0; i < y_current_p->y_size; i++) + vim_free(y_current_p->y_array[i]); + vim_free(y_current_p->y_array); + + y_current_p->y_type = new_type; + y_current_p->y_width = new_width; + y_current_p->y_size = size; + y_current_p->y_time_set = 0; + if (size == 0) + { + y_current_p->y_array = NULL; + } + else + { + // Move the lines from array[] to y_array[]. + y_current_p->y_array = ALLOC_MULT(char_u *, size); + for (i = 0; i < size; i++) + { + if (y_current_p->y_array == NULL) + vim_free(array[i]); + else + y_current_p->y_array[i] = array[i]; + } + } } + else + { + // Free array[] if it was filled. + for (i = 0; i < size; i++) + vim_free(array[i]); + } + vim_free(array); - vim_free(fname); - if (fp == NULL) - return FAIL; - - viminfo_errcnt = 0; - do_viminfo(fp, NULL, flags); - - fclose(fp); - return OK; + return eof; } /* - * Write the viminfo file. The old one is read in first so that effectively a - * merge of current info and old info is done. This allows multiple vims to - * run simultaneously, without losing any marks etc. - * If "forceit" is TRUE, then the old file is not read in, and only internal - * info is written to the file. + * Accept a new style register line from the viminfo, store it when it's new. */ - void -write_viminfo(char_u *file, int forceit) + static void +handle_viminfo_register(garray_T *values, int force) { - char_u *fname; - FILE *fp_in = NULL; // input viminfo file, if any - FILE *fp_out = NULL; // output viminfo file - char_u *tempname = NULL; // name of temp viminfo file - stat_T st_new; // mch_stat() of potential new file -#if defined(UNIX) || defined(VMS) - mode_t umask_save; -#endif -#ifdef UNIX - int shortname = FALSE; // use 8.3 file name - stat_T st_old; // mch_stat() of existing viminfo file -#endif -#ifdef MSWIN - int hidden = FALSE; -#endif + bval_T *vp = (bval_T *)values->ga_data; + int flags; + int name; + int type; + int linecount; + int width; + time_t timestamp; + yankreg_T *y_ptr; + yankreg_T *y_regs_p = get_y_regs(); + int i; - if (no_viminfo()) + // Check the format: + // |{bartype},{flags},{name},{type}, + // {linecount},{width},{timestamp},"line1","line2" + if (values->ga_len < 6 + || vp[0].bv_type != BVAL_NR + || vp[1].bv_type != BVAL_NR + || vp[2].bv_type != BVAL_NR + || vp[3].bv_type != BVAL_NR + || vp[4].bv_type != BVAL_NR + || vp[5].bv_type != BVAL_NR) return; - - fname = viminfo_filename(file); // may set to default if NULL - if (fname == NULL) + flags = vp[0].bv_nr; + name = vp[1].bv_nr; + if (name < 0 || name >= NUM_REGISTERS) + return; + type = vp[2].bv_nr; + if (type != MCHAR && type != MLINE && type != MBLOCK) + return; + linecount = vp[3].bv_nr; + if (values->ga_len < 6 + linecount) + return; + width = vp[4].bv_nr; + if (width < 0) return; - fp_in = mch_fopen((char *)fname, READBIN); - if (fp_in == NULL) - { - int fd; + if (y_read_regs != NULL) + // Reading viminfo for merging and writing. Store the register + // content, don't update the current registers. + y_ptr = &y_read_regs[name]; + else + y_ptr = &y_regs_p[name]; - // if it does exist, but we can't read it, don't try writing - if (mch_stat((char *)fname, &st_new) == 0) - goto end; + // Do not overwrite unless forced or the timestamp is newer. + timestamp = (time_t)vp[5].bv_nr; + if (y_ptr->y_array != NULL && !force + && (timestamp == 0 || y_ptr->y_time_set > timestamp)) + return; - // Create the new .viminfo non-accessible for others, because it may - // contain text from non-accessible documents. It is up to the user to - // widen access (e.g. to a group). This may also fail if there is a - // race condition, then just give up. - fd = mch_open((char *)fname, - O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600); - if (fd < 0) - goto end; - fp_out = fdopen(fd, WRITEBIN); + if (y_ptr->y_array != NULL) + for (i = 0; i < y_ptr->y_size; i++) + vim_free(y_ptr->y_array[i]); + vim_free(y_ptr->y_array); + + if (y_read_regs == NULL) + { + if (flags & REG_PREVIOUS) + set_y_previous(y_ptr); + if ((flags & REG_EXEC) && (force || get_execreg_lastc() == NUL)) + set_execreg_lastc(get_register_name(name)); } - else + y_ptr->y_type = type; + y_ptr->y_width = width; + y_ptr->y_size = linecount; + y_ptr->y_time_set = timestamp; + if (linecount == 0) { - /* - * There is an existing viminfo file. Create a temporary file to - * write the new viminfo into, in the same directory as the - * existing viminfo file, which will be renamed once all writing is - * successful. - */ -#ifdef UNIX - /* - * For Unix we check the owner of the file. It's not very nice to - * overwrite a user's viminfo file after a "su root", with a - * viminfo file that the user can't read. - */ - st_old.st_dev = (dev_t)0; - st_old.st_ino = 0; - st_old.st_mode = 0600; - if (mch_stat((char *)fname, &st_old) == 0 - && getuid() != ROOT_UID - && !(st_old.st_uid == getuid() - ? (st_old.st_mode & 0200) - : (st_old.st_gid == getgid() - ? (st_old.st_mode & 0020) - : (st_old.st_mode & 0002)))) + y_ptr->y_array = NULL; + return; + } + y_ptr->y_array = ALLOC_MULT(char_u *, linecount); + if (y_ptr->y_array == NULL) + { + y_ptr->y_size = 0; // ensure object state is consistent + return; + } + for (i = 0; i < linecount; i++) + { + if (vp[i + 6].bv_allocated) { - int tt = msg_didany; - - // avoid a wait_return for this message, it's annoying - semsg(_("E137: Viminfo file is not writable: %s"), fname); - msg_didany = tt; - fclose(fp_in); - goto end; + y_ptr->y_array[i] = vp[i + 6].bv_string; + vp[i + 6].bv_string = NULL; } + else + y_ptr->y_array[i] = vim_strsave(vp[i + 6].bv_string); + } +} + + static void +write_viminfo_registers(FILE *fp) +{ + int i, j; + char_u *type; + char_u c; + int num_lines; + int max_num_lines; + int max_kbyte; + long len; + yankreg_T *y_ptr; + yankreg_T *y_regs_p = get_y_regs();; + + fputs(_("\n# Registers:\n"), fp); + + // Get '<' value, use old '"' value if '<' is not found. + max_num_lines = get_viminfo_parameter('<'); + if (max_num_lines < 0) + max_num_lines = get_viminfo_parameter('"'); + if (max_num_lines == 0) + return; + max_kbyte = get_viminfo_parameter('s'); + if (max_kbyte == 0) + return; + + for (i = 0; i < NUM_REGISTERS; i++) + { +#ifdef FEAT_CLIPBOARD + // Skip '*'/'+' register, we don't want them back next time + if (i == STAR_REGISTER || i == PLUS_REGISTER) + continue; #endif -#ifdef MSWIN - // Get the file attributes of the existing viminfo file. - hidden = mch_ishidden(fname); +#ifdef FEAT_DND + // Neither do we want the '~' register + if (i == TILDE_REGISTER) + continue; #endif + // When reading viminfo for merging and writing: Use the register from + // viminfo if it's newer. + if (y_read_regs != NULL + && y_read_regs[i].y_array != NULL + && (y_regs_p[i].y_array == NULL || + y_read_regs[i].y_time_set > y_regs_p[i].y_time_set)) + y_ptr = &y_read_regs[i]; + else if (y_regs_p[i].y_array == NULL) + continue; + else + y_ptr = &y_regs_p[i]; - /* - * Make tempname, find one that does not exist yet. - * Beware of a race condition: If someone logs out and all Vim - * instances exit at the same time a temp file might be created between - * stat() and open(). Use mch_open() with O_EXCL to avoid that. - * May try twice: Once normal and once with shortname set, just in - * case somebody puts his viminfo file in an 8.3 filesystem. - */ - for (;;) + // Skip empty registers. + num_lines = y_ptr->y_size; + if (num_lines == 0 + || (num_lines == 1 && y_ptr->y_type == MCHAR + && *y_ptr->y_array[0] == NUL)) + continue; + + if (max_kbyte > 0) { - int next_char = 'z'; - char_u *wp; + // Skip register if there is more text than the maximum size. + len = 0; + for (j = 0; j < num_lines; j++) + len += (long)STRLEN(y_ptr->y_array[j]) + 1L; + if (len > (long)max_kbyte * 1024L) + continue; + } - tempname = buf_modname( -#ifdef UNIX - shortname, -#else - FALSE, -#endif - fname, -#ifdef VMS - (char_u *)"-tmp", -#else - (char_u *)".tmp", -#endif - FALSE); - if (tempname == NULL) // out of memory + switch (y_ptr->y_type) + { + case MLINE: + type = (char_u *)"LINE"; break; - - /* - * Try a series of names. Change one character, just before - * the extension. This should also work for an 8.3 - * file name, when after adding the extension it still is - * the same file as the original. - */ - wp = tempname + STRLEN(tempname) - 5; - if (wp < gettail(tempname)) // empty file name? - wp = gettail(tempname); - for (;;) - { - /* - * Check if tempfile already exists. Never overwrite an - * existing file! - */ - if (mch_stat((char *)tempname, &st_new) == 0) - { -#ifdef UNIX - /* - * Check if tempfile is same as original file. May happen - * when modname() gave the same file back. E.g. silly - * link, or file name-length reached. Try again with - * shortname set. - */ - if (!shortname && st_new.st_dev == st_old.st_dev - && st_new.st_ino == st_old.st_ino) - { - VIM_CLEAR(tempname); - shortname = TRUE; - break; - } -#endif - } - else - { - // Try creating the file exclusively. This may fail if - // another Vim tries to do it at the same time. -#ifdef VMS - // fdopen() fails for some reason - umask_save = umask(077); - fp_out = mch_fopen((char *)tempname, WRITEBIN); - (void)umask(umask_save); -#else - int fd; - - // Use mch_open() to be able to use O_NOFOLLOW and set file - // protection: - // Unix: same as original file, but strip s-bit. Reset - // umask to avoid it getting in the way. - // Others: r&w for user only. -# ifdef UNIX - umask_save = umask(0); - fd = mch_open((char *)tempname, - O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW, - (int)((st_old.st_mode & 0777) | 0600)); - (void)umask(umask_save); -# else - fd = mch_open((char *)tempname, - O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600); -# endif - if (fd < 0) - { - fp_out = NULL; -# ifdef EEXIST - // Avoid trying lots of names while the problem is lack - // of permission, only retry if the file already - // exists. - if (errno != EEXIST) - break; -# endif - } - else - fp_out = fdopen(fd, WRITEBIN); -#endif // VMS - if (fp_out != NULL) - break; - } - - // Assume file exists, try again with another name. - if (next_char == 'a' - 1) - { - // They all exist? Must be something wrong! Don't write - // the viminfo file then. - semsg(_("E929: Too many viminfo temp files, like %s!"), - tempname); - break; - } - *wp = next_char; - --next_char; - } - - if (tempname != NULL) + case MCHAR: + type = (char_u *)"CHAR"; + break; + case MBLOCK: + type = (char_u *)"BLOCK"; + break; + default: + semsg(_("E574: Unknown register type %d"), y_ptr->y_type); + type = (char_u *)"LINE"; break; - // continue if shortname was set } - -#if defined(UNIX) && defined(HAVE_FCHOWN) - if (tempname != NULL && fp_out != NULL) + if (get_y_previous() == &y_regs_p[i]) + fprintf(fp, "\""); + c = get_register_name(i); + fprintf(fp, "\"%c", c); + if (c == get_execreg_lastc()) + fprintf(fp, "@"); + fprintf(fp, "\t%s\t%d\n", type, (int)y_ptr->y_width); + + // If max_num_lines < 0, then we save ALL the lines in the register + if (max_num_lines > 0 && num_lines > max_num_lines) + num_lines = max_num_lines; + for (j = 0; j < num_lines; j++) { - stat_T tmp_st; + putc('\t', fp); + viminfo_writestring(fp, y_ptr->y_array[j]); + } - /* - * Make sure the original owner can read/write the tempfile and - * otherwise preserve permissions, making sure the group matches. - */ - if (mch_stat((char *)tempname, &tmp_st) >= 0) + { + int flags = 0; + int remaining; + + // New style with a bar line. Format: + // |{bartype},{flags},{name},{type}, + // {linecount},{width},{timestamp},"line1","line2" + // flags: REG_PREVIOUS - register is y_previous + // REG_EXEC - used for @@ + if (get_y_previous() == &y_regs_p[i]) + flags |= REG_PREVIOUS; + if (c == get_execreg_lastc()) + flags |= REG_EXEC; + fprintf(fp, "|%d,%d,%d,%d,%d,%d,%ld", BARTYPE_REGISTER, flags, + i, y_ptr->y_type, num_lines, (int)y_ptr->y_width, + (long)y_ptr->y_time_set); + // 11 chars for type/flags/name/type, 3 * 20 for numbers + remaining = LSIZE - 71; + for (j = 0; j < num_lines; j++) { - if (st_old.st_uid != tmp_st.st_uid) - // Changing the owner might fail, in which case the - // file will now owned by the current user, oh well. - vim_ignored = fchown(fileno(fp_out), st_old.st_uid, -1); - if (st_old.st_gid != tmp_st.st_gid - && fchown(fileno(fp_out), -1, st_old.st_gid) == -1) - // can't set the group to what it should be, remove - // group permissions - (void)mch_setperm(tempname, 0600); + putc(',', fp); + --remaining; + remaining = barline_writestring(fp, y_ptr->y_array[j], + remaining); } - else - // can't stat the file, set conservative permissions - (void)mch_setperm(tempname, 0600); + putc('\n', fp); } -#endif } +} - /* - * Check if the new viminfo file can be written to. - */ - if (fp_out == NULL) - { - semsg(_("E138: Can't write viminfo file %s!"), - (fp_in == NULL || tempname == NULL) ? fname : tempname); - if (fp_in != NULL) - fclose(fp_in); - goto end; - } +/* + * Functions relating to reading/writing marks from viminfo + */ - if (p_verbose > 0) - { - verbose_enter(); - smsg(_("Writing viminfo file \"%s\""), fname); - verbose_leave(); - } +static xfmark_T *vi_namedfm = NULL; +#ifdef FEAT_JUMPLIST +static xfmark_T *vi_jumplist = NULL; +static int vi_jumplist_len = 0; +#endif - viminfo_errcnt = 0; - do_viminfo(fp_in, fp_out, forceit ? 0 : (VIF_WANT_INFO | VIF_WANT_MARKS)); + static void +write_one_mark(FILE *fp_out, int c, pos_T *pos) +{ + if (pos->lnum != 0) + fprintf(fp_out, "\t%c\t%ld\t%d\n", c, (long)pos->lnum, (int)pos->col); +} - if (fclose(fp_out) == EOF) - ++viminfo_errcnt; + static void +write_buffer_marks(buf_T *buf, FILE *fp_out) +{ + int i; + pos_T pos; - if (fp_in != NULL) - { - fclose(fp_in); + home_replace(NULL, buf->b_ffname, IObuff, IOSIZE, TRUE); + fprintf(fp_out, "\n> "); + viminfo_writestring(fp_out, IObuff); - // In case of an error keep the original viminfo file. Otherwise - // rename the newly written file. Give an error if that fails. - if (viminfo_errcnt == 0) - { - if (vim_rename(tempname, fname) == -1) - { - ++viminfo_errcnt; - semsg(_("E886: Can't rename viminfo file to %s!"), fname); - } -# ifdef MSWIN - // If the viminfo file was hidden then also hide the new file. - else if (hidden) - mch_hide(fname); -# endif - } - if (viminfo_errcnt > 0) - mch_remove(tempname); - } + // Write the last used timestamp as the lnum of the non-existing mark '*'. + // Older Vims will ignore it and/or copy it. + pos.lnum = (linenr_T)buf->b_last_used; + pos.col = 0; + write_one_mark(fp_out, '*', &pos); -end: - vim_free(fname); - vim_free(tempname); + write_one_mark(fp_out, '"', &buf->b_last_cursor); + write_one_mark(fp_out, '^', &buf->b_last_insert); + write_one_mark(fp_out, '.', &buf->b_last_change); +#ifdef FEAT_JUMPLIST + // changelist positions are stored oldest first + for (i = 0; i < buf->b_changelistlen; ++i) + { + // skip duplicates + if (i == 0 || !EQUAL_POS(buf->b_changelist[i - 1], + buf->b_changelist[i])) + write_one_mark(fp_out, '+', &buf->b_changelist[i]); + } +#endif + for (i = 0; i < NMARKS; i++) + write_one_mark(fp_out, 'a' + i, &buf->b_namedm[i]); } /* - * Read a line from the viminfo file. - * Returns TRUE for end-of-file; + * Return TRUE if marks for "buf" should not be written. */ - int -viminfo_readline(vir_T *virp) + static int +skip_for_viminfo(buf_T *buf) { - return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd); + return +#ifdef FEAT_TERMINAL + bt_terminal(buf) || +#endif + removable(buf->b_ffname); } /* - * Check string read from viminfo file. - * Remove '\n' at the end of the line. - * - replace CTRL-V CTRL-V with CTRL-V - * - replace CTRL-V 'n' with '\n' - * - * Check for a long line as written by viminfo_writestring(). - * - * Return the string in allocated memory (NULL when out of memory). + * Write all the named marks for all buffers. + * When "buflist" is not NULL fill it with the buffers for which marks are to + * be written. */ - char_u * -viminfo_readstring( - vir_T *virp, - int off, // offset for virp->vir_line - int convert UNUSED) // convert the string + static void +write_viminfo_marks(FILE *fp_out, garray_T *buflist) { - char_u *retval; - char_u *s, *d; - long len; + buf_T *buf; + int is_mark_set; + int i; + win_T *win; + tabpage_T *tp; - if (virp->vir_line[off] == Ctrl_V && vim_isdigit(virp->vir_line[off + 1])) + // Set b_last_cursor for the all buffers that have a window. + FOR_ALL_TAB_WINDOWS(tp, win) + set_last_cursor(win); + + fputs(_("\n# History of marks within files (newest to oldest):\n"), fp_out); + FOR_ALL_BUFFERS(buf) { - len = atol((char *)virp->vir_line + off + 1); - retval = lalloc(len, TRUE); - if (retval == NULL) - { - // Line too long? File messed up? Skip next line. - (void)vim_fgets(virp->vir_line, 10, virp->vir_fd); - return NULL; - } - (void)vim_fgets(retval, (int)len, virp->vir_fd); - s = retval + 1; // Skip the leading '<' - } - else - { - retval = vim_strsave(virp->vir_line + off); - if (retval == NULL) - return NULL; - s = retval; - } - - // Change CTRL-V CTRL-V to CTRL-V and CTRL-V n to \n in-place. - d = retval; - while (*s != NUL && *s != '\n') - { - if (s[0] == Ctrl_V && s[1] != NUL) + // Only write something if buffer has been loaded and at least one + // mark is set. + if (buf->b_marks_read) { - if (s[1] == 'n') - *d++ = '\n'; + if (buf->b_last_cursor.lnum != 0) + is_mark_set = TRUE; else - *d++ = Ctrl_V; - s += 2; - } - else - *d++ = *s++; - } - *d = NUL; - - if (convert && virp->vir_conv.vc_type != CONV_NONE && *retval != NUL) - { - d = string_convert(&virp->vir_conv, retval, NULL); - if (d != NULL) - { - vim_free(retval); - retval = d; + { + is_mark_set = FALSE; + for (i = 0; i < NMARKS; i++) + if (buf->b_namedm[i].lnum != 0) + { + is_mark_set = TRUE; + break; + } + } + if (is_mark_set && buf->b_ffname != NULL + && buf->b_ffname[0] != NUL + && !skip_for_viminfo(buf)) + { + if (buflist == NULL) + write_buffer_marks(buf, fp_out); + else if (ga_grow(buflist, 1) == OK) + ((buf_T **)buflist->ga_data)[buflist->ga_len++] = buf; + } } } - - return retval; } -/* - * write string to viminfo file - * - replace CTRL-V with CTRL-V CTRL-V - * - replace '\n' with CTRL-V 'n' - * - add a '\n' at the end - * - * For a long line: - * - write " CTRL-V \n " in first line - * - write " < \n " in second line - */ - void -viminfo_writestring(FILE *fd, char_u *p) + static void +write_one_filemark( + FILE *fp, + xfmark_T *fm, + int c1, + int c2) { - int c; - char_u *s; - int len = 0; - - for (s = p; *s != NUL; ++s) - { - if (*s == Ctrl_V || *s == '\n') - ++len; - ++len; - } + char_u *name; - // If the string will be too long, write its length and put it in the next - // line. Take into account that some room is needed for what comes before - // the string (e.g., variable name). Add something to the length for the - // '<', NL and trailing NUL. - if (len > LSIZE / 2) - fprintf(fd, IF_EB("\026%d\n<", CTRL_V_STR "%d\n<"), len + 3); + if (fm->fmark.mark.lnum == 0) // not set + return; - while ((c = *p++) != NUL) + if (fm->fmark.fnum != 0) // there is a buffer + name = buflist_nr2name(fm->fmark.fnum, TRUE, FALSE); + else + name = fm->fname; // use name from .viminfo + if (name != NULL && *name != NUL) { - if (c == Ctrl_V || c == '\n') - { - putc(Ctrl_V, fd); - if (c == '\n') - c = 'n'; - } - putc(c, fd); + fprintf(fp, "%c%c %ld %ld ", c1, c2, (long)fm->fmark.mark.lnum, + (long)fm->fmark.mark.col); + viminfo_writestring(fp, name); + + // Barline: |{bartype},{name},{lnum},{col},{timestamp},{filename} + // size up to filename: 8 + 3 * 20 + fprintf(fp, "|%d,%d,%ld,%ld,%ld,", BARTYPE_MARK, c2, + (long)fm->fmark.mark.lnum, (long)fm->fmark.mark.col, + (long)fm->time_set); + barline_writestring(fp, name, LSIZE - 70); + putc('\n', fp); } - putc('\n', fd); + + if (fm->fmark.fnum != 0) + vim_free(name); } -/* - * Write a string in quotes that barline_parse() can read back. - * Breaks the line in less than LSIZE pieces when needed. - * Returns remaining characters in the line. - */ - int -barline_writestring(FILE *fd, char_u *s, int remaining_start) + static void +write_viminfo_filemarks(FILE *fp) { - char_u *p; - int remaining = remaining_start; - int len = 2; + int i; + char_u *name; + buf_T *buf; + xfmark_T *namedfm_p = get_namedfm(); + xfmark_T *fm; + int vi_idx; + int idx; - // Count the number of characters produced, including quotes. - for (p = s; *p != NUL; ++p) + if (get_viminfo_parameter('f') == 0) + return; + + fputs(_("\n# File marks:\n"), fp); + + // Write the filemarks 'A - 'Z + for (i = 0; i < NMARKS; i++) { - if (*p == NL) - len += 2; - else if (*p == '"' || *p == '\\') - len += 2; + if (vi_namedfm != NULL + && (vi_namedfm[i].time_set > namedfm_p[i].time_set + || namedfm_p[i].fmark.mark.lnum == 0)) + fm = &vi_namedfm[i]; else - ++len; + fm = &namedfm_p[i]; + write_one_filemark(fp, fm, '\'', i + 'A'); } - if (len > remaining - 2) + + // Find a mark that is the same file and position as the cursor. + // That one, or else the last one is deleted. + // Move '0 to '1, '1 to '2, etc. until the matching one or '9 + // Set the '0 mark to current cursor position. + if (curbuf->b_ffname != NULL && !skip_for_viminfo(curbuf)) { - fprintf(fd, ">%d\n|<", len); - remaining = LSIZE - 20; + name = buflist_nr2name(curbuf->b_fnum, TRUE, FALSE); + for (i = NMARKS; i < NMARKS + EXTRA_MARKS - 1; ++i) + if (namedfm_p[i].fmark.mark.lnum == curwin->w_cursor.lnum + && (namedfm_p[i].fname == NULL + ? namedfm_p[i].fmark.fnum == curbuf->b_fnum + : (name != NULL + && STRCMP(name, namedfm_p[i].fname) == 0))) + break; + vim_free(name); + + vim_free(namedfm_p[i].fname); + for ( ; i > NMARKS; --i) + namedfm_p[i] = namedfm_p[i - 1]; + namedfm_p[NMARKS].fmark.mark = curwin->w_cursor; + namedfm_p[NMARKS].fmark.fnum = curbuf->b_fnum; + namedfm_p[NMARKS].fname = NULL; + namedfm_p[NMARKS].time_set = vim_time(); } - putc('"', fd); - for (p = s; *p != NUL; ++p) + // Write the filemarks '0 - '9. Newest (highest timestamp) first. + vi_idx = NMARKS; + idx = NMARKS; + for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++) { - if (*p == NL) - { - putc('\\', fd); - putc('n', fd); - --remaining; - } - else if (*p == '"' || *p == '\\') + xfmark_T *vi_fm = vi_namedfm != NULL ? &vi_namedfm[vi_idx] : NULL; + + if (vi_fm != NULL + && vi_fm->fmark.mark.lnum != 0 + && (vi_fm->time_set > namedfm_p[idx].time_set + || namedfm_p[idx].fmark.mark.lnum == 0)) { - putc('\\', fd); - putc(*p, fd); - --remaining; + fm = vi_fm; + ++vi_idx; } else - putc(*p, fd); - --remaining; - - if (remaining < 3) { - putc('\n', fd); - putc('|', fd); - putc('<', fd); - // Leave enough space for another continuation. - remaining = LSIZE - 20; + fm = &namedfm_p[idx++]; + if (vi_fm != NULL + && vi_fm->fmark.mark.lnum == fm->fmark.mark.lnum + && vi_fm->time_set == fm->time_set + && ((vi_fm->fmark.fnum != 0 + && vi_fm->fmark.fnum == fm->fmark.fnum) + || (vi_fm->fname != NULL + && fm->fname != NULL + && STRCMP(vi_fm->fname, fm->fname) == 0))) + ++vi_idx; // skip duplicate } + write_one_filemark(fp, fm, '\'', i - NMARKS + '0'); } - putc('"', fd); - return remaining - 2; -} - -/* - * ":rviminfo" and ":wviminfo". - */ - void -ex_viminfo( - exarg_T *eap) -{ - char_u *save_viminfo; - save_viminfo = p_viminfo; - if (*p_viminfo == NUL) - p_viminfo = (char_u *)"'100"; - if (eap->cmdidx == CMD_rviminfo) - { - if (read_viminfo(eap->arg, VIF_WANT_INFO | VIF_WANT_MARKS - | (eap->forceit ? VIF_FORCEIT : 0)) == FAIL) - emsg(_("E195: Cannot open viminfo file for reading")); - } - else - write_viminfo(eap->arg, eap->forceit); - p_viminfo = save_viminfo; -} - - int -read_viminfo_filemark(vir_T *virp, int force) -{ - char_u *str; - xfmark_T *namedfm_p = get_namedfm(); - xfmark_T *fm; - int i; - - // We only get here if line[0] == '\'' or '-'. - // Illegal mark names are ignored (for future expansion). - str = virp->vir_line + 1; - if ( -#ifndef EBCDIC - *str <= 127 && -#endif - ((*virp->vir_line == '\'' && (VIM_ISDIGIT(*str) || isupper(*str))) - || (*virp->vir_line == '-' && *str == '\''))) +#ifdef FEAT_JUMPLIST + // Write the jumplist with -' + fputs(_("\n# Jumplist (newest first):\n"), fp); + setpcmark(); // add current cursor position + cleanup_jumplist(curwin, FALSE); + vi_idx = 0; + idx = curwin->w_jumplistlen - 1; + for (i = 0; i < JUMPLISTSIZE; ++i) { - if (*str == '\'') + xfmark_T *vi_fm; + + fm = idx >= 0 ? &curwin->w_jumplist[idx] : NULL; + vi_fm = vi_idx < vi_jumplist_len ? &vi_jumplist[vi_idx] : NULL; + if (fm == NULL && vi_fm == NULL) + break; + if (fm == NULL || (vi_fm != NULL && fm->time_set < vi_fm->time_set)) { -#ifdef FEAT_JUMPLIST - // If the jumplist isn't full insert fmark as oldest entry - if (curwin->w_jumplistlen == JUMPLISTSIZE) - fm = NULL; - else - { - for (i = curwin->w_jumplistlen; i > 0; --i) - curwin->w_jumplist[i] = curwin->w_jumplist[i - 1]; - ++curwin->w_jumplistidx; - ++curwin->w_jumplistlen; - fm = &curwin->w_jumplist[0]; - fm->fmark.mark.lnum = 0; - fm->fname = NULL; - } -#else - fm = NULL; -#endif + fm = vi_fm; + ++vi_idx; } - else if (VIM_ISDIGIT(*str)) - fm = &namedfm_p[*str - '0' + NMARKS]; else - fm = &namedfm_p[*str - 'A']; - if (fm != NULL && (fm->fmark.mark.lnum == 0 || force)) - { - str = skipwhite(str + 1); - fm->fmark.mark.lnum = getdigits(&str); - str = skipwhite(str); - fm->fmark.mark.col = getdigits(&str); - fm->fmark.mark.coladd = 0; - fm->fmark.fnum = 0; - str = skipwhite(str); - vim_free(fm->fname); - fm->fname = viminfo_readstring(virp, (int)(str - virp->vir_line), - FALSE); - fm->time_set = 0; - } + --idx; + if (fm->fmark.fnum == 0 + || ((buf = buflist_findnr(fm->fmark.fnum)) != NULL + && !skip_for_viminfo(buf))) + write_one_filemark(fp, fm, '-', '\''); } - return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd); -} - -static xfmark_T *vi_namedfm = NULL; -#ifdef FEAT_JUMPLIST -static xfmark_T *vi_jumplist = NULL; -static int vi_jumplist_len = 0; #endif +} /* - * Prepare for reading viminfo marks when writing viminfo later. + * Compare functions for qsort() below, that compares b_last_used. */ - void -prepare_viminfo_marks(void) + static int +buf_compare(const void *s1, const void *s2) { - vi_namedfm = ALLOC_CLEAR_MULT(xfmark_T, NMARKS + EXTRA_MARKS); -#ifdef FEAT_JUMPLIST - vi_jumplist = ALLOC_CLEAR_MULT(xfmark_T, JUMPLISTSIZE); - vi_jumplist_len = 0; -#endif + buf_T *buf1 = *(buf_T **)s1; + buf_T *buf2 = *(buf_T **)s2; + + if (buf1->b_last_used == buf2->b_last_used) + return 0; + return buf1->b_last_used > buf2->b_last_used ? -1 : 1; } - void -finish_viminfo_marks(void) +/* + * Handle marks in the viminfo file: + * fp_out != NULL: copy marks, in time order with buffers in "buflist". + * fp_out == NULL && (flags & VIF_WANT_MARKS): read marks for curbuf only + * fp_out == NULL && (flags & VIF_GET_OLDFILES | VIF_FORCEIT): fill v:oldfiles + */ + static void +copy_viminfo_marks( + vir_T *virp, + FILE *fp_out, + garray_T *buflist, + int eof, + int flags) { + char_u *line = virp->vir_line; + buf_T *buf; + int num_marked_files; + int load_marks; + int copy_marks_out; + char_u *str; int i; + char_u *p; + char_u *name_buf; + pos_T pos; +#ifdef FEAT_EVAL + list_T *list = NULL; +#endif + int count = 0; + int buflist_used = 0; + buf_T *buflist_buf = NULL; - if (vi_namedfm != NULL) + if ((name_buf = alloc(LSIZE)) == NULL) + return; + *name_buf = NUL; + + if (fp_out != NULL && buflist->ga_len > 0) { - for (i = 0; i < NMARKS + EXTRA_MARKS; ++i) - vim_free(vi_namedfm[i].fname); - VIM_CLEAR(vi_namedfm); + // Sort the list of buffers on b_last_used. + qsort(buflist->ga_data, (size_t)buflist->ga_len, + sizeof(buf_T *), buf_compare); + buflist_buf = ((buf_T **)buflist->ga_data)[0]; } -#ifdef FEAT_JUMPLIST - if (vi_jumplist != NULL) + +#ifdef FEAT_EVAL + if (fp_out == NULL && (flags & (VIF_GET_OLDFILES | VIF_FORCEIT))) { - for (i = 0; i < vi_jumplist_len; ++i) - vim_free(vi_jumplist[i].fname); - VIM_CLEAR(vi_jumplist); + list = list_alloc(); + if (list != NULL) + set_vim_var_list(VV_OLDFILES, list); } #endif -} -/* - * Accept a new style mark line from the viminfo, store it when it's new. - */ - void -handle_viminfo_mark(garray_T *values, int force) -{ - bval_T *vp = (bval_T *)values->ga_data; - int name; - linenr_T lnum; - colnr_T col; - time_t timestamp; - xfmark_T *fm = NULL; + num_marked_files = get_viminfo_parameter('\''); + while (!eof && (count < num_marked_files || fp_out == NULL)) + { + if (line[0] != '>') + { + if (line[0] != '\n' && line[0] != '\r' && line[0] != '#') + { + if (viminfo_error("E576: ", _("Missing '>'"), line)) + break; // too many errors, return now + } + eof = vim_fgets(line, LSIZE, virp->vir_fd); + continue; // Skip this dud line + } - // Check the format: - // |{bartype},{name},{lnum},{col},{timestamp},{filename} - if (values->ga_len < 5 - || vp[0].bv_type != BVAL_NR - || vp[1].bv_type != BVAL_NR - || vp[2].bv_type != BVAL_NR - || vp[3].bv_type != BVAL_NR - || vp[4].bv_type != BVAL_STRING) - return; + // Handle long line and translate escaped characters. + // Find file name, set str to start. + // Ignore leading and trailing white space. + str = skipwhite(line + 1); + str = viminfo_readstring(virp, (int)(str - virp->vir_line), FALSE); + if (str == NULL) + continue; + p = str + STRLEN(str); + while (p != str && (*p == NUL || vim_isspace(*p))) + p--; + if (*p) + p++; + *p = NUL; - name = vp[0].bv_nr; - if (name != '\'' && !VIM_ISDIGIT(name) && !ASCII_ISUPPER(name)) - return; - lnum = vp[1].bv_nr; - col = vp[2].bv_nr; - if (lnum <= 0 || col < 0) - return; - timestamp = (time_t)vp[3].bv_nr; +#ifdef FEAT_EVAL + if (list != NULL) + list_append_string(list, str, -1); +#endif - if (name == '\'') - { -#ifdef FEAT_JUMPLIST - if (vi_jumplist != NULL) + // If fp_out == NULL, load marks for current buffer. + // If fp_out != NULL, copy marks for buffers not in buflist. + load_marks = copy_marks_out = FALSE; + if (fp_out == NULL) { - if (vi_jumplist_len < JUMPLISTSIZE) - fm = &vi_jumplist[vi_jumplist_len++]; + if ((flags & VIF_WANT_MARKS) && curbuf->b_ffname != NULL) + { + if (*name_buf == NUL) // only need to do this once + home_replace(NULL, curbuf->b_ffname, name_buf, LSIZE, TRUE); + if (fnamecmp(str, name_buf) == 0) + load_marks = TRUE; + } } - else + else // fp_out != NULL { - int idx; - int i; + // This is slow if there are many buffers!! + FOR_ALL_BUFFERS(buf) + if (buf->b_ffname != NULL) + { + home_replace(NULL, buf->b_ffname, name_buf, LSIZE, TRUE); + if (fnamecmp(str, name_buf) == 0) + break; + } - // If we have a timestamp insert it in the right place. - if (timestamp != 0) + // Copy marks if the buffer has not been loaded. + if (buf == NULL || !buf->b_marks_read) { - for (idx = curwin->w_jumplistlen - 1; idx >= 0; --idx) - if (curwin->w_jumplist[idx].time_set < timestamp) - { - ++idx; - break; - } - // idx cannot be zero now - if (idx < 0 && curwin->w_jumplistlen < JUMPLISTSIZE) - // insert as the oldest entry - idx = 0; - } - else if (curwin->w_jumplistlen < JUMPLISTSIZE) - // insert as oldest entry - idx = 0; - else - idx = -1; + int did_read_line = FALSE; - if (idx >= 0) - { - if (curwin->w_jumplistlen == JUMPLISTSIZE) - { - // Drop the oldest entry. - --idx; - vim_free(curwin->w_jumplist[0].fname); - for (i = 0; i < idx; ++i) - curwin->w_jumplist[i] = curwin->w_jumplist[i + 1]; - } - else + if (buflist_buf != NULL) { - // Move newer entries forward. - for (i = curwin->w_jumplistlen; i > idx; --i) - curwin->w_jumplist[i] = curwin->w_jumplist[i - 1]; - ++curwin->w_jumplistidx; - ++curwin->w_jumplistlen; + // Read the next line. If it has the "*" mark compare the + // time stamps. Write entries from "buflist" that are + // newer. + if (!(eof = viminfo_readline(virp)) && line[0] == TAB) + { + did_read_line = TRUE; + if (line[1] == '*') + { + long ltime; + + sscanf((char *)line + 2, "%ld ", <ime); + while ((time_T)ltime < buflist_buf->b_last_used) + { + write_buffer_marks(buflist_buf, fp_out); + if (++count >= num_marked_files) + break; + if (++buflist_used == buflist->ga_len) + { + buflist_buf = NULL; + break; + } + buflist_buf = + ((buf_T **)buflist->ga_data)[buflist_used]; + } + } + else + { + // No timestamp, must be written by an older Vim. + // Assume all remaining buffers are older then + // ours. + while (count < num_marked_files + && buflist_used < buflist->ga_len) + { + buflist_buf = ((buf_T **)buflist->ga_data) + [buflist_used++]; + write_buffer_marks(buflist_buf, fp_out); + ++count; + } + buflist_buf = NULL; + } + + if (count >= num_marked_files) + { + vim_free(str); + break; + } + } } - fm = &curwin->w_jumplist[idx]; - fm->fmark.mark.lnum = 0; - fm->fname = NULL; - fm->time_set = 0; + + fputs("\n> ", fp_out); + viminfo_writestring(fp_out, str); + if (did_read_line) + fputs((char *)line, fp_out); + + count++; + copy_marks_out = TRUE; } } -#endif - } - else - { - int idx; - xfmark_T *namedfm_p = get_namedfm(); + vim_free(str); - if (VIM_ISDIGIT(name)) + pos.coladd = 0; + while (!(eof = viminfo_readline(virp)) && line[0] == TAB) { - if (vi_namedfm != NULL) - idx = name - '0' + NMARKS; - else + if (load_marks) { - int i; + if (line[1] != NUL) + { + unsigned u; - // Do not use the name from the viminfo file, insert in time - // order. - for (idx = NMARKS; idx < NMARKS + EXTRA_MARKS; ++idx) - if (namedfm_p[idx].time_set < timestamp) - break; - if (idx == NMARKS + EXTRA_MARKS) - // All existing entries are newer. - return; - i = NMARKS + EXTRA_MARKS - 1; + sscanf((char *)line + 2, "%ld %u", &pos.lnum, &u); + pos.col = u; + switch (line[1]) + { + case '"': curbuf->b_last_cursor = pos; break; + case '^': curbuf->b_last_insert = pos; break; + case '.': curbuf->b_last_change = pos; break; + case '+': +#ifdef FEAT_JUMPLIST + // changelist positions are stored oldest + // first + if (curbuf->b_changelistlen == JUMPLISTSIZE) + // list is full, remove oldest entry + mch_memmove(curbuf->b_changelist, + curbuf->b_changelist + 1, + sizeof(pos_T) * (JUMPLISTSIZE - 1)); + else + ++curbuf->b_changelistlen; + curbuf->b_changelist[ + curbuf->b_changelistlen - 1] = pos; +#endif + break; - vim_free(namedfm_p[i].fname); - for ( ; i > idx; --i) - namedfm_p[i] = namedfm_p[i - 1]; - namedfm_p[idx].fname = NULL; + // Using the line number for the last-used + // timestamp. + case '*': curbuf->b_last_used = pos.lnum; break; + + default: if ((i = line[1] - 'a') >= 0 && i < NMARKS) + curbuf->b_namedm[i] = pos; + } + } } + else if (copy_marks_out) + fputs((char *)line, fp_out); } - else - idx = name - 'A'; - if (vi_namedfm != NULL) - fm = &vi_namedfm[idx]; - else - fm = &namedfm_p[idx]; - } - if (fm != NULL) - { - if (vi_namedfm != NULL || fm->fmark.mark.lnum == 0 - || fm->time_set < timestamp || force) + if (load_marks) { - fm->fmark.mark.lnum = lnum; - fm->fmark.mark.col = col; - fm->fmark.mark.coladd = 0; - fm->fmark.fnum = 0; - vim_free(fm->fname); - if (vp[4].bv_allocated) +#ifdef FEAT_JUMPLIST + win_T *wp; + + FOR_ALL_WINDOWS(wp) { - fm->fname = vp[4].bv_string; - vp[4].bv_string = NULL; + if (wp->w_buffer == curbuf) + wp->w_changelistidx = curbuf->b_changelistlen; } - else - fm->fname = vim_strsave(vp[4].bv_string); - fm->time_set = timestamp; +#endif + break; } } + + if (fp_out != NULL) + // Write any remaining entries from buflist. + while (count < num_marked_files && buflist_used < buflist->ga_len) + { + buflist_buf = ((buf_T **)buflist->ga_data)[buflist_used++]; + write_buffer_marks(buflist_buf, fp_out); + ++count; + } + + vim_free(name_buf); } /* - * Return TRUE if marks for "buf" should not be written. + * Read marks for the current buffer from the viminfo file, when we support + * buffer marks and the buffer has a name. */ - static int -skip_for_viminfo(buf_T *buf) -{ - return -#ifdef FEAT_TERMINAL - bt_terminal(buf) || -#endif - removable(buf->b_ffname); -} - - static void -write_one_filemark( - FILE *fp, - xfmark_T *fm, - int c1, - int c2) + void +check_marks_read(void) { - char_u *name; - - if (fm->fmark.mark.lnum == 0) // not set - return; - - if (fm->fmark.fnum != 0) // there is a buffer - name = buflist_nr2name(fm->fmark.fnum, TRUE, FALSE); - else - name = fm->fname; // use name from .viminfo - if (name != NULL && *name != NUL) - { - fprintf(fp, "%c%c %ld %ld ", c1, c2, (long)fm->fmark.mark.lnum, - (long)fm->fmark.mark.col); - viminfo_writestring(fp, name); - - // Barline: |{bartype},{name},{lnum},{col},{timestamp},{filename} - // size up to filename: 8 + 3 * 20 - fprintf(fp, "|%d,%d,%ld,%ld,%ld,", BARTYPE_MARK, c2, - (long)fm->fmark.mark.lnum, (long)fm->fmark.mark.col, - (long)fm->time_set); - barline_writestring(fp, name, LSIZE - 70); - putc('\n', fp); - } + if (!curbuf->b_marks_read && get_viminfo_parameter('\'') > 0 + && curbuf->b_ffname != NULL) + read_viminfo(NULL, VIF_WANT_MARKS); - if (fm->fmark.fnum != 0) - vim_free(name); + // Always set b_marks_read; needed when 'viminfo' is changed to include + // the ' parameter after opening a buffer. + curbuf->b_marks_read = TRUE; } - void -write_viminfo_filemarks(FILE *fp) + static int +read_viminfo_filemark(vir_T *virp, int force) { - int i; - char_u *name; - buf_T *buf; + char_u *str; xfmark_T *namedfm_p = get_namedfm(); xfmark_T *fm; - int vi_idx; - int idx; - - if (get_viminfo_parameter('f') == 0) - return; - - fputs(_("\n# File marks:\n"), fp); + int i; - // Write the filemarks 'A - 'Z - for (i = 0; i < NMARKS; i++) - { - if (vi_namedfm != NULL - && (vi_namedfm[i].time_set > namedfm_p[i].time_set - || namedfm_p[i].fmark.mark.lnum == 0)) - fm = &vi_namedfm[i]; - else - fm = &namedfm_p[i]; - write_one_filemark(fp, fm, '\'', i + 'A'); - } - - // Find a mark that is the same file and position as the cursor. - // That one, or else the last one is deleted. - // Move '0 to '1, '1 to '2, etc. until the matching one or '9 - // Set the '0 mark to current cursor position. - if (curbuf->b_ffname != NULL && !skip_for_viminfo(curbuf)) - { - name = buflist_nr2name(curbuf->b_fnum, TRUE, FALSE); - for (i = NMARKS; i < NMARKS + EXTRA_MARKS - 1; ++i) - if (namedfm_p[i].fmark.mark.lnum == curwin->w_cursor.lnum - && (namedfm_p[i].fname == NULL - ? namedfm_p[i].fmark.fnum == curbuf->b_fnum - : (name != NULL - && STRCMP(name, namedfm_p[i].fname) == 0))) - break; - vim_free(name); - - vim_free(namedfm_p[i].fname); - for ( ; i > NMARKS; --i) - namedfm_p[i] = namedfm_p[i - 1]; - namedfm_p[NMARKS].fmark.mark = curwin->w_cursor; - namedfm_p[NMARKS].fmark.fnum = curbuf->b_fnum; - namedfm_p[NMARKS].fname = NULL; - namedfm_p[NMARKS].time_set = vim_time(); - } - - // Write the filemarks '0 - '9. Newest (highest timestamp) first. - vi_idx = NMARKS; - idx = NMARKS; - for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++) + // We only get here if line[0] == '\'' or '-'. + // Illegal mark names are ignored (for future expansion). + str = virp->vir_line + 1; + if ( +#ifndef EBCDIC + *str <= 127 && +#endif + ((*virp->vir_line == '\'' && (VIM_ISDIGIT(*str) || isupper(*str))) + || (*virp->vir_line == '-' && *str == '\''))) { - xfmark_T *vi_fm = vi_namedfm != NULL ? &vi_namedfm[vi_idx] : NULL; - - if (vi_fm != NULL - && vi_fm->fmark.mark.lnum != 0 - && (vi_fm->time_set > namedfm_p[idx].time_set - || namedfm_p[idx].fmark.mark.lnum == 0)) + if (*str == '\'') { - fm = vi_fm; - ++vi_idx; +#ifdef FEAT_JUMPLIST + // If the jumplist isn't full insert fmark as oldest entry + if (curwin->w_jumplistlen == JUMPLISTSIZE) + fm = NULL; + else + { + for (i = curwin->w_jumplistlen; i > 0; --i) + curwin->w_jumplist[i] = curwin->w_jumplist[i - 1]; + ++curwin->w_jumplistidx; + ++curwin->w_jumplistlen; + fm = &curwin->w_jumplist[0]; + fm->fmark.mark.lnum = 0; + fm->fname = NULL; + } +#else + fm = NULL; +#endif } + else if (VIM_ISDIGIT(*str)) + fm = &namedfm_p[*str - '0' + NMARKS]; else + fm = &namedfm_p[*str - 'A']; + if (fm != NULL && (fm->fmark.mark.lnum == 0 || force)) { - fm = &namedfm_p[idx++]; - if (vi_fm != NULL - && vi_fm->fmark.mark.lnum == fm->fmark.mark.lnum - && vi_fm->time_set == fm->time_set - && ((vi_fm->fmark.fnum != 0 - && vi_fm->fmark.fnum == fm->fmark.fnum) - || (vi_fm->fname != NULL - && fm->fname != NULL - && STRCMP(vi_fm->fname, fm->fname) == 0))) - ++vi_idx; // skip duplicate - } - write_one_filemark(fp, fm, '\'', i - NMARKS + '0'); - } - -#ifdef FEAT_JUMPLIST - // Write the jumplist with -' - fputs(_("\n# Jumplist (newest first):\n"), fp); - setpcmark(); // add current cursor position - cleanup_jumplist(curwin, FALSE); - vi_idx = 0; - idx = curwin->w_jumplistlen - 1; - for (i = 0; i < JUMPLISTSIZE; ++i) - { - xfmark_T *vi_fm; - - fm = idx >= 0 ? &curwin->w_jumplist[idx] : NULL; - vi_fm = vi_idx < vi_jumplist_len ? &vi_jumplist[vi_idx] : NULL; - if (fm == NULL && vi_fm == NULL) - break; - if (fm == NULL || (vi_fm != NULL && fm->time_set < vi_fm->time_set)) - { - fm = vi_fm; - ++vi_idx; + str = skipwhite(str + 1); + fm->fmark.mark.lnum = getdigits(&str); + str = skipwhite(str); + fm->fmark.mark.col = getdigits(&str); + fm->fmark.mark.coladd = 0; + fm->fmark.fnum = 0; + str = skipwhite(str); + vim_free(fm->fname); + fm->fname = viminfo_readstring(virp, (int)(str - virp->vir_line), + FALSE); + fm->time_set = 0; } - else - --idx; - if (fm->fmark.fnum == 0 - || ((buf = buflist_findnr(fm->fmark.fnum)) != NULL - && !skip_for_viminfo(buf))) - write_one_filemark(fp, fm, '-', '\''); } -#endif + return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd); } /* - * Return TRUE if "name" is on removable media (depending on 'viminfo'). + * Prepare for reading viminfo marks when writing viminfo later. */ - int -removable(char_u *name) -{ - char_u *p; - char_u part[51]; - int retval = FALSE; - size_t n; - - name = home_replace_save(NULL, name); - if (name != NULL) - { - for (p = p_viminfo; *p; ) - { - copy_option_part(&p, part, 51, ", "); - if (part[0] == 'r') - { - n = STRLEN(part + 1); - if (MB_STRNICMP(part + 1, name, n) == 0) - { - retval = TRUE; - break; - } - } - } - vim_free(name); - } - return retval; -} - static void -write_one_mark(FILE *fp_out, int c, pos_T *pos) +prepare_viminfo_marks(void) { - if (pos->lnum != 0) - fprintf(fp_out, "\t%c\t%ld\t%d\n", c, (long)pos->lnum, (int)pos->col); + vi_namedfm = ALLOC_CLEAR_MULT(xfmark_T, NMARKS + EXTRA_MARKS); +#ifdef FEAT_JUMPLIST + vi_jumplist = ALLOC_CLEAR_MULT(xfmark_T, JUMPLISTSIZE); + vi_jumplist_len = 0; +#endif } - static void -write_buffer_marks(buf_T *buf, FILE *fp_out) +finish_viminfo_marks(void) { int i; - pos_T pos; - - home_replace(NULL, buf->b_ffname, IObuff, IOSIZE, TRUE); - fprintf(fp_out, "\n> "); - viminfo_writestring(fp_out, IObuff); - - // Write the last used timestamp as the lnum of the non-existing mark '*'. - // Older Vims will ignore it and/or copy it. - pos.lnum = (linenr_T)buf->b_last_used; - pos.col = 0; - write_one_mark(fp_out, '*', &pos); - write_one_mark(fp_out, '"', &buf->b_last_cursor); - write_one_mark(fp_out, '^', &buf->b_last_insert); - write_one_mark(fp_out, '.', &buf->b_last_change); + if (vi_namedfm != NULL) + { + for (i = 0; i < NMARKS + EXTRA_MARKS; ++i) + vim_free(vi_namedfm[i].fname); + VIM_CLEAR(vi_namedfm); + } #ifdef FEAT_JUMPLIST - // changelist positions are stored oldest first - for (i = 0; i < buf->b_changelistlen; ++i) + if (vi_jumplist != NULL) { - // skip duplicates - if (i == 0 || !EQUAL_POS(buf->b_changelist[i - 1], - buf->b_changelist[i])) - write_one_mark(fp_out, '+', &buf->b_changelist[i]); + for (i = 0; i < vi_jumplist_len; ++i) + vim_free(vi_jumplist[i].fname); + VIM_CLEAR(vi_jumplist); } #endif - for (i = 0; i < NMARKS; i++) - write_one_mark(fp_out, 'a' + i, &buf->b_namedm[i]); } /* - * Write all the named marks for all buffers. - * When "buflist" is not NULL fill it with the buffers for which marks are to - * be written. + * Accept a new style mark line from the viminfo, store it when it's new. */ - void -write_viminfo_marks(FILE *fp_out, garray_T *buflist) + static void +handle_viminfo_mark(garray_T *values, int force) { - buf_T *buf; - int is_mark_set; - int i; - win_T *win; - tabpage_T *tp; + bval_T *vp = (bval_T *)values->ga_data; + int name; + linenr_T lnum; + colnr_T col; + time_t timestamp; + xfmark_T *fm = NULL; - // Set b_last_cursor for the all buffers that have a window. - FOR_ALL_TAB_WINDOWS(tp, win) - set_last_cursor(win); + // Check the format: + // |{bartype},{name},{lnum},{col},{timestamp},{filename} + if (values->ga_len < 5 + || vp[0].bv_type != BVAL_NR + || vp[1].bv_type != BVAL_NR + || vp[2].bv_type != BVAL_NR + || vp[3].bv_type != BVAL_NR + || vp[4].bv_type != BVAL_STRING) + return; - fputs(_("\n# History of marks within files (newest to oldest):\n"), fp_out); - FOR_ALL_BUFFERS(buf) + name = vp[0].bv_nr; + if (name != '\'' && !VIM_ISDIGIT(name) && !ASCII_ISUPPER(name)) + return; + lnum = vp[1].bv_nr; + col = vp[2].bv_nr; + if (lnum <= 0 || col < 0) + return; + timestamp = (time_t)vp[3].bv_nr; + + if (name == '\'') { - // Only write something if buffer has been loaded and at least one - // mark is set. - if (buf->b_marks_read) +#ifdef FEAT_JUMPLIST + if (vi_jumplist != NULL) { - if (buf->b_last_cursor.lnum != 0) - is_mark_set = TRUE; - else - { - is_mark_set = FALSE; - for (i = 0; i < NMARKS; i++) - if (buf->b_namedm[i].lnum != 0) + if (vi_jumplist_len < JUMPLISTSIZE) + fm = &vi_jumplist[vi_jumplist_len++]; + } + else + { + int idx; + int i; + + // If we have a timestamp insert it in the right place. + if (timestamp != 0) + { + for (idx = curwin->w_jumplistlen - 1; idx >= 0; --idx) + if (curwin->w_jumplist[idx].time_set < timestamp) { - is_mark_set = TRUE; + ++idx; break; } + // idx cannot be zero now + if (idx < 0 && curwin->w_jumplistlen < JUMPLISTSIZE) + // insert as the oldest entry + idx = 0; } - if (is_mark_set && buf->b_ffname != NULL - && buf->b_ffname[0] != NUL - && !skip_for_viminfo(buf)) + else if (curwin->w_jumplistlen < JUMPLISTSIZE) + // insert as oldest entry + idx = 0; + else + idx = -1; + + if (idx >= 0) { - if (buflist == NULL) - write_buffer_marks(buf, fp_out); - else if (ga_grow(buflist, 1) == OK) - ((buf_T **)buflist->ga_data)[buflist->ga_len++] = buf; + if (curwin->w_jumplistlen == JUMPLISTSIZE) + { + // Drop the oldest entry. + --idx; + vim_free(curwin->w_jumplist[0].fname); + for (i = 0; i < idx; ++i) + curwin->w_jumplist[i] = curwin->w_jumplist[i + 1]; + } + else + { + // Move newer entries forward. + for (i = curwin->w_jumplistlen; i > idx; --i) + curwin->w_jumplist[i] = curwin->w_jumplist[i - 1]; + ++curwin->w_jumplistidx; + ++curwin->w_jumplistlen; + } + fm = &curwin->w_jumplist[idx]; + fm->fmark.mark.lnum = 0; + fm->fname = NULL; + fm->time_set = 0; } } - } -} - -/* - * Compare functions for qsort() below, that compares b_last_used. - */ - static int -buf_compare(const void *s1, const void *s2) -{ - buf_T *buf1 = *(buf_T **)s1; - buf_T *buf2 = *(buf_T **)s2; - - if (buf1->b_last_used == buf2->b_last_used) - return 0; - return buf1->b_last_used > buf2->b_last_used ? -1 : 1; -} - -/* - * Handle marks in the viminfo file: - * fp_out != NULL: copy marks, in time order with buffers in "buflist". - * fp_out == NULL && (flags & VIF_WANT_MARKS): read marks for curbuf only - * fp_out == NULL && (flags & VIF_GET_OLDFILES | VIF_FORCEIT): fill v:oldfiles - */ - void -copy_viminfo_marks( - vir_T *virp, - FILE *fp_out, - garray_T *buflist, - int eof, - int flags) -{ - char_u *line = virp->vir_line; - buf_T *buf; - int num_marked_files; - int load_marks; - int copy_marks_out; - char_u *str; - int i; - char_u *p; - char_u *name_buf; - pos_T pos; -#ifdef FEAT_EVAL - list_T *list = NULL; #endif - int count = 0; - int buflist_used = 0; - buf_T *buflist_buf = NULL; - - if ((name_buf = alloc(LSIZE)) == NULL) - return; - *name_buf = NUL; - - if (fp_out != NULL && buflist->ga_len > 0) - { - // Sort the list of buffers on b_last_used. - qsort(buflist->ga_data, (size_t)buflist->ga_len, - sizeof(buf_T *), buf_compare); - buflist_buf = ((buf_T **)buflist->ga_data)[0]; } - -#ifdef FEAT_EVAL - if (fp_out == NULL && (flags & (VIF_GET_OLDFILES | VIF_FORCEIT))) + else { - list = list_alloc(); - if (list != NULL) - set_vim_var_list(VV_OLDFILES, list); - } -#endif + int idx; + xfmark_T *namedfm_p = get_namedfm(); - num_marked_files = get_viminfo_parameter('\''); - while (!eof && (count < num_marked_files || fp_out == NULL)) - { - if (line[0] != '>') + if (VIM_ISDIGIT(name)) { - if (line[0] != '\n' && line[0] != '\r' && line[0] != '#') + if (vi_namedfm != NULL) + idx = name - '0' + NMARKS; + else { - if (viminfo_error("E576: ", _("Missing '>'"), line)) - break; // too many errors, return now - } - eof = vim_fgets(line, LSIZE, virp->vir_fd); - continue; // Skip this dud line - } + int i; - // Handle long line and translate escaped characters. - // Find file name, set str to start. - // Ignore leading and trailing white space. - str = skipwhite(line + 1); - str = viminfo_readstring(virp, (int)(str - virp->vir_line), FALSE); - if (str == NULL) - continue; - p = str + STRLEN(str); - while (p != str && (*p == NUL || vim_isspace(*p))) - p--; - if (*p) - p++; - *p = NUL; + // Do not use the name from the viminfo file, insert in time + // order. + for (idx = NMARKS; idx < NMARKS + EXTRA_MARKS; ++idx) + if (namedfm_p[idx].time_set < timestamp) + break; + if (idx == NMARKS + EXTRA_MARKS) + // All existing entries are newer. + return; + i = NMARKS + EXTRA_MARKS - 1; -#ifdef FEAT_EVAL - if (list != NULL) - list_append_string(list, str, -1); -#endif + vim_free(namedfm_p[i].fname); + for ( ; i > idx; --i) + namedfm_p[i] = namedfm_p[i - 1]; + namedfm_p[idx].fname = NULL; + } + } + else + idx = name - 'A'; + if (vi_namedfm != NULL) + fm = &vi_namedfm[idx]; + else + fm = &namedfm_p[idx]; + } - // If fp_out == NULL, load marks for current buffer. - // If fp_out != NULL, copy marks for buffers not in buflist. - load_marks = copy_marks_out = FALSE; - if (fp_out == NULL) + if (fm != NULL) + { + if (vi_namedfm != NULL || fm->fmark.mark.lnum == 0 + || fm->time_set < timestamp || force) { - if ((flags & VIF_WANT_MARKS) && curbuf->b_ffname != NULL) + fm->fmark.mark.lnum = lnum; + fm->fmark.mark.col = col; + fm->fmark.mark.coladd = 0; + fm->fmark.fnum = 0; + vim_free(fm->fname); + if (vp[4].bv_allocated) { - if (*name_buf == NUL) // only need to do this once - home_replace(NULL, curbuf->b_ffname, name_buf, LSIZE, TRUE); - if (fnamecmp(str, name_buf) == 0) - load_marks = TRUE; + fm->fname = vp[4].bv_string; + vp[4].bv_string = NULL; } + else + fm->fname = vim_strsave(vp[4].bv_string); + fm->time_set = timestamp; } - else // fp_out != NULL + } +} + + static int +read_viminfo_barline(vir_T *virp, int got_encoding, int force, int writing) +{ + char_u *p = virp->vir_line + 1; + int bartype; + garray_T values; + bval_T *vp; + int i; + int read_next = TRUE; + + /* + * The format is: |{bartype},{value},... + * For a very long string: + * |{bartype},>{length of "{text}{text2}"} + * |<{text1} + * |<{text2},{value} + * For a long line not using a string + * |{bartype},{lots of values},> + * |<{value},{value} + */ + if (*p == '<') + { + // Continuation line of an unrecognized item. + if (writing) + ga_add_string(&virp->vir_barlines, virp->vir_line); + } + else + { + ga_init2(&values, sizeof(bval_T), 20); + bartype = getdigits(&p); + switch (bartype) { - // This is slow if there are many buffers!! - FOR_ALL_BUFFERS(buf) - if (buf->b_ffname != NULL) + case BARTYPE_VERSION: + // Only use the version when it comes before the encoding. + // If it comes later it was copied by a Vim version that + // doesn't understand the version. + if (!got_encoding) { - home_replace(NULL, buf->b_ffname, name_buf, LSIZE, TRUE); - if (fnamecmp(str, name_buf) == 0) - break; + read_next = barline_parse(virp, p, &values); + vp = (bval_T *)values.ga_data; + if (values.ga_len > 0 && vp->bv_type == BVAL_NR) + virp->vir_version = vp->bv_nr; } + break; - // Copy marks if the buffer has not been loaded. - if (buf == NULL || !buf->b_marks_read) - { - int did_read_line = FALSE; + case BARTYPE_HISTORY: + read_next = barline_parse(virp, p, &values); + handle_viminfo_history(&values, writing); + break; - if (buflist_buf != NULL) - { - // Read the next line. If it has the "*" mark compare the - // time stamps. Write entries from "buflist" that are - // newer. - if (!(eof = viminfo_readline(virp)) && line[0] == TAB) - { - did_read_line = TRUE; - if (line[1] == '*') - { - long ltime; + case BARTYPE_REGISTER: + read_next = barline_parse(virp, p, &values); + handle_viminfo_register(&values, force); + break; - sscanf((char *)line + 2, "%ld ", <ime); - while ((time_T)ltime < buflist_buf->b_last_used) - { - write_buffer_marks(buflist_buf, fp_out); - if (++count >= num_marked_files) - break; - if (++buflist_used == buflist->ga_len) - { - buflist_buf = NULL; - break; - } - buflist_buf = - ((buf_T **)buflist->ga_data)[buflist_used]; - } - } - else - { - // No timestamp, must be written by an older Vim. - // Assume all remaining buffers are older then - // ours. - while (count < num_marked_files - && buflist_used < buflist->ga_len) - { - buflist_buf = ((buf_T **)buflist->ga_data) - [buflist_used++]; - write_buffer_marks(buflist_buf, fp_out); - ++count; - } - buflist_buf = NULL; - } + case BARTYPE_MARK: + read_next = barline_parse(virp, p, &values); + handle_viminfo_mark(&values, force); + break; - if (count >= num_marked_files) - { - vim_free(str); - break; - } - } - } + default: + // copy unrecognized line (for future use) + if (writing) + ga_add_string(&virp->vir_barlines, virp->vir_line); + } + for (i = 0; i < values.ga_len; ++i) + { + vp = (bval_T *)values.ga_data + i; + if (vp->bv_type == BVAL_STRING && vp->bv_allocated) + vim_free(vp->bv_string); + } + ga_clear(&values); + } - fputs("\n> ", fp_out); - viminfo_writestring(fp_out, str); - if (did_read_line) - fputs((char *)line, fp_out); + if (read_next) + return viminfo_readline(virp); + return FALSE; +} - count++; - copy_marks_out = TRUE; +/* + * read_viminfo_up_to_marks() -- Only called from do_viminfo(). Reads in the + * first part of the viminfo file which contains everything but the marks that + * are local to a file. Returns TRUE when end-of-file is reached. -- webb + */ + static int +read_viminfo_up_to_marks( + vir_T *virp, + int forceit, + int writing) +{ + int eof; + buf_T *buf; + int got_encoding = FALSE; + +#ifdef FEAT_CMDHIST + prepare_viminfo_history(forceit ? 9999 : 0, writing); +#endif + + eof = viminfo_readline(virp); + while (!eof && virp->vir_line[0] != '>') + { + switch (virp->vir_line[0]) + { + // Characters reserved for future expansion, ignored now + case '+': // "+40 /path/dir file", for running vim without args + case '^': // to be defined + case '<': // long line - ignored + // A comment or empty line. + case NUL: + case '\r': + case '\n': + case '#': + eof = viminfo_readline(virp); + break; + case '|': + eof = read_viminfo_barline(virp, got_encoding, + forceit, writing); + break; + case '*': // "*encoding=value" + got_encoding = TRUE; + eof = viminfo_encoding(virp); + break; + case '!': // global variable +#ifdef FEAT_EVAL + eof = read_viminfo_varlist(virp, writing); +#else + eof = viminfo_readline(virp); +#endif + break; + case '%': // entry for buffer list + eof = read_viminfo_bufferlist(virp, writing); + break; + case '"': + // When registers are in bar lines skip the old style register + // lines. + if (virp->vir_version < VIMINFO_VERSION_WITH_REGISTERS) + eof = read_viminfo_register(virp, forceit); + else + do { + eof = viminfo_readline(virp); + } while (!eof && (virp->vir_line[0] == TAB + || virp->vir_line[0] == '<')); + break; + case '/': // Search string + case '&': // Substitute search string + case '~': // Last search string, followed by '/' or '&' + eof = read_viminfo_search_pattern(virp, forceit); + break; + case '$': + eof = read_viminfo_sub_string(virp, forceit); + break; + case ':': + case '?': + case '=': + case '@': +#ifdef FEAT_CMDHIST + // When history is in bar lines skip the old style history + // lines. + if (virp->vir_version < VIMINFO_VERSION_WITH_HISTORY) + eof = read_viminfo_history(virp, writing); + else +#endif + eof = viminfo_readline(virp); + break; + case '-': + case '\'': + // When file marks are in bar lines skip the old style lines. + if (virp->vir_version < VIMINFO_VERSION_WITH_MARKS) + eof = read_viminfo_filemark(virp, forceit); + else + eof = viminfo_readline(virp); + break; + default: + if (viminfo_error("E575: ", _("Illegal starting char"), + virp->vir_line)) + eof = TRUE; + else + eof = viminfo_readline(virp); + break; + } + } + +#ifdef FEAT_CMDHIST + // Finish reading history items. + if (!writing) + finish_viminfo_history(virp); +#endif + + // Change file names to buffer numbers for fmarks. + FOR_ALL_BUFFERS(buf) + fmarks_check_names(buf); + + return eof; +} + +/* + * do_viminfo() -- Should only be called from read_viminfo() & write_viminfo(). + */ + static void +do_viminfo(FILE *fp_in, FILE *fp_out, int flags) +{ + int eof = FALSE; + vir_T vir; + int merge = FALSE; + int do_copy_marks = FALSE; + garray_T buflist; + + if ((vir.vir_line = alloc(LSIZE)) == NULL) + return; + vir.vir_fd = fp_in; + vir.vir_conv.vc_type = CONV_NONE; + ga_init2(&vir.vir_barlines, (int)sizeof(char_u *), 100); + vir.vir_version = -1; + + if (fp_in != NULL) + { + if (flags & VIF_WANT_INFO) + { + if (fp_out != NULL) + { + // Registers and marks are read and kept separate from what + // this Vim is using. They are merged when writing. + prepare_viminfo_registers(); + prepare_viminfo_marks(); } + + eof = read_viminfo_up_to_marks(&vir, + flags & VIF_FORCEIT, fp_out != NULL); + merge = TRUE; } - vim_free(str); + else if (flags != 0) + // Skip info, find start of marks + while (!(eof = viminfo_readline(&vir)) + && vir.vir_line[0] != '>') + ; + + do_copy_marks = (flags & + (VIF_WANT_MARKS | VIF_GET_OLDFILES | VIF_FORCEIT)); + } + + if (fp_out != NULL) + { + // Write the info: + fprintf(fp_out, _("# This viminfo file was generated by Vim %s.\n"), + VIM_VERSION_MEDIUM); + fputs(_("# You may edit it if you're careful!\n\n"), fp_out); + write_viminfo_version(fp_out); + fputs(_("# Value of 'encoding' when this file was written\n"), fp_out); + fprintf(fp_out, "*encoding=%s\n\n", p_enc); + write_viminfo_search_pattern(fp_out); + write_viminfo_sub_string(fp_out); +#ifdef FEAT_CMDHIST + write_viminfo_history(fp_out, merge); +#endif + write_viminfo_registers(fp_out); + finish_viminfo_registers(); +#ifdef FEAT_EVAL + write_viminfo_varlist(fp_out); +#endif + write_viminfo_filemarks(fp_out); + finish_viminfo_marks(); + write_viminfo_bufferlist(fp_out); + write_viminfo_barlines(&vir, fp_out); + + if (do_copy_marks) + ga_init2(&buflist, sizeof(buf_T *), 50); + write_viminfo_marks(fp_out, do_copy_marks ? &buflist : NULL); + } + + if (do_copy_marks) + { + copy_viminfo_marks(&vir, fp_out, &buflist, eof, flags); + if (fp_out != NULL) + ga_clear(&buflist); + } + + vim_free(vir.vir_line); + if (vir.vir_conv.vc_type != CONV_NONE) + convert_setup(&vir.vir_conv, NULL, NULL); + ga_clear_strings(&vir.vir_barlines); +} + +/* + * read_viminfo() -- Read the viminfo file. Registers etc. which are already + * set are not over-written unless "flags" includes VIF_FORCEIT. -- webb + */ + int +read_viminfo( + char_u *file, // file name or NULL to use default name + int flags) // VIF_WANT_INFO et al. +{ + FILE *fp; + char_u *fname; + + if (no_viminfo()) + return FAIL; + + fname = viminfo_filename(file); // get file name in allocated buffer + if (fname == NULL) + return FAIL; + fp = mch_fopen((char *)fname, READBIN); + + if (p_verbose > 0) + { + verbose_enter(); + smsg(_("Reading viminfo file \"%s\"%s%s%s"), + fname, + (flags & VIF_WANT_INFO) ? _(" info") : "", + (flags & VIF_WANT_MARKS) ? _(" marks") : "", + (flags & VIF_GET_OLDFILES) ? _(" oldfiles") : "", + fp == NULL ? _(" FAILED") : ""); + verbose_leave(); + } + + vim_free(fname); + if (fp == NULL) + return FAIL; + + viminfo_errcnt = 0; + do_viminfo(fp, NULL, flags); + + fclose(fp); + return OK; +} + +/* + * Write the viminfo file. The old one is read in first so that effectively a + * merge of current info and old info is done. This allows multiple vims to + * run simultaneously, without losing any marks etc. + * If "forceit" is TRUE, then the old file is not read in, and only internal + * info is written to the file. + */ + void +write_viminfo(char_u *file, int forceit) +{ + char_u *fname; + FILE *fp_in = NULL; // input viminfo file, if any + FILE *fp_out = NULL; // output viminfo file + char_u *tempname = NULL; // name of temp viminfo file + stat_T st_new; // mch_stat() of potential new file +#if defined(UNIX) || defined(VMS) + mode_t umask_save; +#endif +#ifdef UNIX + int shortname = FALSE; // use 8.3 file name + stat_T st_old; // mch_stat() of existing viminfo file +#endif +#ifdef MSWIN + int hidden = FALSE; +#endif + + if (no_viminfo()) + return; + + fname = viminfo_filename(file); // may set to default if NULL + if (fname == NULL) + return; + + fp_in = mch_fopen((char *)fname, READBIN); + if (fp_in == NULL) + { + int fd; + + // if it does exist, but we can't read it, don't try writing + if (mch_stat((char *)fname, &st_new) == 0) + goto end; + + // Create the new .viminfo non-accessible for others, because it may + // contain text from non-accessible documents. It is up to the user to + // widen access (e.g. to a group). This may also fail if there is a + // race condition, then just give up. + fd = mch_open((char *)fname, + O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600); + if (fd < 0) + goto end; + fp_out = fdopen(fd, WRITEBIN); + } + else + { + /* + * There is an existing viminfo file. Create a temporary file to + * write the new viminfo into, in the same directory as the + * existing viminfo file, which will be renamed once all writing is + * successful. + */ +#ifdef UNIX + /* + * For Unix we check the owner of the file. It's not very nice to + * overwrite a user's viminfo file after a "su root", with a + * viminfo file that the user can't read. + */ + st_old.st_dev = (dev_t)0; + st_old.st_ino = 0; + st_old.st_mode = 0600; + if (mch_stat((char *)fname, &st_old) == 0 + && getuid() != ROOT_UID + && !(st_old.st_uid == getuid() + ? (st_old.st_mode & 0200) + : (st_old.st_gid == getgid() + ? (st_old.st_mode & 0020) + : (st_old.st_mode & 0002)))) + { + int tt = msg_didany; + + // avoid a wait_return for this message, it's annoying + semsg(_("E137: Viminfo file is not writable: %s"), fname); + msg_didany = tt; + fclose(fp_in); + goto end; + } +#endif +#ifdef MSWIN + // Get the file attributes of the existing viminfo file. + hidden = mch_ishidden(fname); +#endif - pos.coladd = 0; - while (!(eof = viminfo_readline(virp)) && line[0] == TAB) + /* + * Make tempname, find one that does not exist yet. + * Beware of a race condition: If someone logs out and all Vim + * instances exit at the same time a temp file might be created between + * stat() and open(). Use mch_open() with O_EXCL to avoid that. + * May try twice: Once normal and once with shortname set, just in + * case somebody puts his viminfo file in an 8.3 filesystem. + */ + for (;;) { - if (load_marks) + int next_char = 'z'; + char_u *wp; + + tempname = buf_modname( +#ifdef UNIX + shortname, +#else + FALSE, +#endif + fname, +#ifdef VMS + (char_u *)"-tmp", +#else + (char_u *)".tmp", +#endif + FALSE); + if (tempname == NULL) // out of memory + break; + + /* + * Try a series of names. Change one character, just before + * the extension. This should also work for an 8.3 + * file name, when after adding the extension it still is + * the same file as the original. + */ + wp = tempname + STRLEN(tempname) - 5; + if (wp < gettail(tempname)) // empty file name? + wp = gettail(tempname); + for (;;) { - if (line[1] != NUL) + /* + * Check if tempfile already exists. Never overwrite an + * existing file! + */ + if (mch_stat((char *)tempname, &st_new) == 0) { - unsigned u; - - sscanf((char *)line + 2, "%ld %u", &pos.lnum, &u); - pos.col = u; - switch (line[1]) +#ifdef UNIX + /* + * Check if tempfile is same as original file. May happen + * when modname() gave the same file back. E.g. silly + * link, or file name-length reached. Try again with + * shortname set. + */ + if (!shortname && st_new.st_dev == st_old.st_dev + && st_new.st_ino == st_old.st_ino) { - case '"': curbuf->b_last_cursor = pos; break; - case '^': curbuf->b_last_insert = pos; break; - case '.': curbuf->b_last_change = pos; break; - case '+': -#ifdef FEAT_JUMPLIST - // changelist positions are stored oldest - // first - if (curbuf->b_changelistlen == JUMPLISTSIZE) - // list is full, remove oldest entry - mch_memmove(curbuf->b_changelist, - curbuf->b_changelist + 1, - sizeof(pos_T) * (JUMPLISTSIZE - 1)); - else - ++curbuf->b_changelistlen; - curbuf->b_changelist[ - curbuf->b_changelistlen - 1] = pos; + VIM_CLEAR(tempname); + shortname = TRUE; + break; + } #endif - break; - - // Using the line number for the last-used - // timestamp. - case '*': curbuf->b_last_used = pos.lnum; break; + } + else + { + // Try creating the file exclusively. This may fail if + // another Vim tries to do it at the same time. +#ifdef VMS + // fdopen() fails for some reason + umask_save = umask(077); + fp_out = mch_fopen((char *)tempname, WRITEBIN); + (void)umask(umask_save); +#else + int fd; - default: if ((i = line[1] - 'a') >= 0 && i < NMARKS) - curbuf->b_namedm[i] = pos; + // Use mch_open() to be able to use O_NOFOLLOW and set file + // protection: + // Unix: same as original file, but strip s-bit. Reset + // umask to avoid it getting in the way. + // Others: r&w for user only. +# ifdef UNIX + umask_save = umask(0); + fd = mch_open((char *)tempname, + O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW, + (int)((st_old.st_mode & 0777) | 0600)); + (void)umask(umask_save); +# else + fd = mch_open((char *)tempname, + O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600); +# endif + if (fd < 0) + { + fp_out = NULL; +# ifdef EEXIST + // Avoid trying lots of names while the problem is lack + // of permission, only retry if the file already + // exists. + if (errno != EEXIST) + break; +# endif } + else + fp_out = fdopen(fd, WRITEBIN); +#endif // VMS + if (fp_out != NULL) + break; + } + + // Assume file exists, try again with another name. + if (next_char == 'a' - 1) + { + // They all exist? Must be something wrong! Don't write + // the viminfo file then. + semsg(_("E929: Too many viminfo temp files, like %s!"), + tempname); + break; } + *wp = next_char; + --next_char; } - else if (copy_marks_out) - fputs((char *)line, fp_out); + + if (tempname != NULL) + break; + // continue if shortname was set } - if (load_marks) +#if defined(UNIX) && defined(HAVE_FCHOWN) + if (tempname != NULL && fp_out != NULL) { -#ifdef FEAT_JUMPLIST - win_T *wp; + stat_T tmp_st; - FOR_ALL_WINDOWS(wp) + /* + * Make sure the original owner can read/write the tempfile and + * otherwise preserve permissions, making sure the group matches. + */ + if (mch_stat((char *)tempname, &tmp_st) >= 0) { - if (wp->w_buffer == curbuf) - wp->w_changelistidx = curbuf->b_changelistlen; + if (st_old.st_uid != tmp_st.st_uid) + // Changing the owner might fail, in which case the + // file will now owned by the current user, oh well. + vim_ignored = fchown(fileno(fp_out), st_old.st_uid, -1); + if (st_old.st_gid != tmp_st.st_gid + && fchown(fileno(fp_out), -1, st_old.st_gid) == -1) + // can't set the group to what it should be, remove + // group permissions + (void)mch_setperm(tempname, 0600); } -#endif - break; + else + // can't stat the file, set conservative permissions + (void)mch_setperm(tempname, 0600); } +#endif } - if (fp_out != NULL) - // Write any remaining entries from buflist. - while (count < num_marked_files && buflist_used < buflist->ga_len) + /* + * Check if the new viminfo file can be written to. + */ + if (fp_out == NULL) + { + semsg(_("E138: Can't write viminfo file %s!"), + (fp_in == NULL || tempname == NULL) ? fname : tempname); + if (fp_in != NULL) + fclose(fp_in); + goto end; + } + + if (p_verbose > 0) + { + verbose_enter(); + smsg(_("Writing viminfo file \"%s\""), fname); + verbose_leave(); + } + + viminfo_errcnt = 0; + do_viminfo(fp_in, fp_out, forceit ? 0 : (VIF_WANT_INFO | VIF_WANT_MARKS)); + + if (fclose(fp_out) == EOF) + ++viminfo_errcnt; + + if (fp_in != NULL) + { + fclose(fp_in); + + // In case of an error keep the original viminfo file. Otherwise + // rename the newly written file. Give an error if that fails. + if (viminfo_errcnt == 0) { - buflist_buf = ((buf_T **)buflist->ga_data)[buflist_used++]; - write_buffer_marks(buflist_buf, fp_out); - ++count; + if (vim_rename(tempname, fname) == -1) + { + ++viminfo_errcnt; + semsg(_("E886: Can't rename viminfo file to %s!"), fname); + } +# ifdef MSWIN + // If the viminfo file was hidden then also hide the new file. + else if (hidden) + mch_hide(fname); +# endif } + if (viminfo_errcnt > 0) + mch_remove(tempname); + } - vim_free(name_buf); +end: + vim_free(fname); + vim_free(tempname); +} + +/* + * ":rviminfo" and ":wviminfo". + */ + void +ex_viminfo( + exarg_T *eap) +{ + char_u *save_viminfo; + + save_viminfo = p_viminfo; + if (*p_viminfo == NUL) + p_viminfo = (char_u *)"'100"; + if (eap->cmdidx == CMD_rviminfo) + { + if (read_viminfo(eap->arg, VIF_WANT_INFO | VIF_WANT_MARKS + | (eap->forceit ? VIF_FORCEIT : 0)) == FAIL) + emsg(_("E195: Cannot open viminfo file for reading")); + } + else + write_viminfo(eap->arg, eap->forceit); + p_viminfo = save_viminfo; } + #endif // FEAT_VIMINFO