Mercurial > vim
view src/viminfo.c @ 17463:16f01738068b
Added tag v8.1.1729 for changeset 9088fafff9b320882ac2b250bdc4560bb2bccdc7
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Sun, 21 Jul 2019 23:15:05 +0200 |
parents | e43f0c0c491c |
children | 3e708b5c0509 |
line wrap: on
line source
/* vi:set ts=8 sts=4 sw=4 noet: * * VIM - Vi IMproved by Bram Moolenaar * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ /* * viminfo.c: viminfo related functions */ #include "vim.h" #include "version.h" #if defined(FEAT_VIMINFO) || defined(PROTO) static int viminfo_errcnt; /* * Get the viminfo file name to use. * If "file" is given and not empty, use it (has already been expanded by * cmdline functions). * Otherwise use "-i file_name", value from 'viminfo' or the default, and * expand environment variables. * Returns an allocated string. NULL when out of memory. */ static char_u * viminfo_filename(char_u *file) { if (file == NULL || *file == NUL) { if (*p_viminfofile != NUL) file = p_viminfofile; else if ((file = find_viminfo_parameter('n')) == NULL || *file == NUL) { #ifdef VIMINFO_FILE2 # ifdef VMS if (mch_getenv((char_u *)"SYS$LOGIN") == NULL) # else # ifdef MSWIN // Use $VIM only if $HOME is the default "C:/". if (STRCMP(vim_getenv((char_u *)"HOME", NULL), "C:/") == 0 && mch_getenv((char_u *)"HOME") == NULL) # else if (mch_getenv((char_u *)"HOME") == NULL) # endif # endif { // don't use $VIM when not available. expand_env((char_u *)"$VIM", NameBuff, MAXPATHL); if (STRCMP("$VIM", NameBuff) != 0) // $VIM was expanded file = (char_u *)VIMINFO_FILE2; else file = (char_u *)VIMINFO_FILE; } else #endif file = (char_u *)VIMINFO_FILE; } expand_env(file, NameBuff, MAXPATHL); file = NameBuff; } return vim_strsave(file); } static int read_viminfo_bufferlist( vir_T *virp, int writing) { char_u *tab; linenr_T lnum; colnr_T col; buf_T *buf; char_u *sfname; char_u *xline; // Handle long line and escaped characters. xline = viminfo_readstring(virp, 1, FALSE); // don't read in if there are files on the command-line or if writing: if (xline != NULL && !writing && ARGCOUNT == 0 && find_viminfo_parameter('%') != NULL) { // Format is: <fname> Tab <lnum> Tab <col>. // Watch out for a Tab in the file name, work from the end. lnum = 0; col = 0; tab = vim_strrchr(xline, '\t'); if (tab != NULL) { *tab++ = '\0'; col = (colnr_T)atoi((char *)tab); tab = vim_strrchr(xline, '\t'); if (tab != NULL) { *tab++ = '\0'; lnum = atol((char *)tab); } } // Expand "~/" in the file name at "line + 1" to a full path. // Then try shortening it by comparing with the current directory expand_env(xline, NameBuff, MAXPATHL); sfname = shorten_fname1(NameBuff); buf = buflist_new(NameBuff, sfname, (linenr_T)0, BLN_LISTED); if (buf != NULL) // just in case... { buf->b_last_cursor.lnum = lnum; buf->b_last_cursor.col = col; buflist_setfpos(buf, curwin, lnum, col, FALSE); } } vim_free(xline); return viminfo_readline(virp); } static void write_viminfo_bufferlist(FILE *fp) { buf_T *buf; win_T *win; tabpage_T *tp; char_u *line; int max_buffers; if (find_viminfo_parameter('%') == NULL) return; // Without a number -1 is returned: do all buffers. max_buffers = get_viminfo_parameter('%'); // Allocate room for the file name, lnum and col. #define LINE_BUF_LEN (MAXPATHL + 40) line = alloc(LINE_BUF_LEN); if (line == NULL) return; FOR_ALL_TAB_WINDOWS(tp, win) set_last_cursor(win); fputs(_("\n# Buffer list:\n"), fp); FOR_ALL_BUFFERS(buf) { if (buf->b_fname == NULL || !buf->b_p_bl #ifdef FEAT_QUICKFIX || bt_quickfix(buf) #endif #ifdef FEAT_TERMINAL || bt_terminal(buf) #endif || removable(buf->b_ffname)) continue; if (max_buffers-- == 0) break; putc('%', fp); home_replace(NULL, buf->b_ffname, line, MAXPATHL, TRUE); vim_snprintf_add((char *)line, LINE_BUF_LEN, "\t%ld\t%d", (long)buf->b_last_cursor.lnum, buf->b_last_cursor.col); viminfo_writestring(fp, line); } vim_free(line); } #if defined(FEAT_CMDHIST) || defined(PROTO) /* * Buffers for history read from a viminfo file. Only valid while reading. */ static histentry_T *viminfo_history[HIST_COUNT] = {NULL, NULL, NULL, NULL, NULL}; static int viminfo_hisidx[HIST_COUNT] = {0, 0, 0, 0, 0}; static int viminfo_hislen[HIST_COUNT] = {0, 0, 0, 0, 0}; static int viminfo_add_at_front = FALSE; /* * Translate a history type number to the associated character. */ static int hist_type2char( int type, int use_question) // use '?' instead of '/' { if (type == HIST_CMD) return ':'; if (type == HIST_SEARCH) { if (use_question) return '?'; else return '/'; } if (type == HIST_EXPR) return '='; return '@'; } /* * Prepare for reading the history from the viminfo file. * This allocates history arrays to store the read history lines. */ static void prepare_viminfo_history(int asklen, int writing) { int i; int num; int type; int len; int hislen = get_hislen(); init_history(); viminfo_add_at_front = (asklen != 0 && !writing); if (asklen > hislen) asklen = hislen; for (type = 0; type < HIST_COUNT; ++type) { histentry_T *histentry = get_histentry(type); // Count the number of empty spaces in the history list. Entries read // from viminfo previously are also considered empty. If there are // more spaces available than we request, then fill them up. for (i = 0, num = 0; i < hislen; i++) if (histentry[i].hisstr == NULL || histentry[i].viminfo) num++; len = asklen; if (num > len) len = num; if (len <= 0) viminfo_history[type] = NULL; else viminfo_history[type] = LALLOC_MULT(histentry_T, len); if (viminfo_history[type] == NULL) len = 0; viminfo_hislen[type] = len; viminfo_hisidx[type] = 0; } } /* * Accept a line from the viminfo, store it in the history array when it's * new. */ static int read_viminfo_history(vir_T *virp, int writing) { int type; long_u len; char_u *val; char_u *p; type = hist_char2type(virp->vir_line[0]); if (viminfo_hisidx[type] < viminfo_hislen[type]) { val = viminfo_readstring(virp, 1, TRUE); if (val != NULL && *val != NUL) { int sep = (*val == ' ' ? NUL : *val); if (!in_history(type, val + (type == HIST_SEARCH), viminfo_add_at_front, sep, writing)) { // Need to re-allocate to append the separator byte. len = STRLEN(val); p = alloc(len + 2); if (p != NULL) { if (type == HIST_SEARCH) { // Search entry: Move the separator from the first // column to after the NUL. mch_memmove(p, val + 1, (size_t)len); p[len] = sep; } else { // Not a search entry: No separator in the viminfo // file, add a NUL separator. mch_memmove(p, val, (size_t)len + 1); p[len + 1] = NUL; } viminfo_history[type][viminfo_hisidx[type]].hisstr = p; viminfo_history[type][viminfo_hisidx[type]].time_set = 0; viminfo_history[type][viminfo_hisidx[type]].viminfo = TRUE; viminfo_history[type][viminfo_hisidx[type]].hisnum = 0; viminfo_hisidx[type]++; } } } vim_free(val); } return viminfo_readline(virp); } /* * Accept a new style history line from the viminfo, store it in the history * array when it's new. */ static void handle_viminfo_history( garray_T *values, int writing) { int type; long_u len; char_u *val; char_u *p; bval_T *vp = (bval_T *)values->ga_data; // Check the format: // |{bartype},{histtype},{timestamp},{separator},"text" if (values->ga_len < 4 || vp[0].bv_type != BVAL_NR || vp[1].bv_type != BVAL_NR || (vp[2].bv_type != BVAL_NR && vp[2].bv_type != BVAL_EMPTY) || vp[3].bv_type != BVAL_STRING) return; type = vp[0].bv_nr; if (type >= HIST_COUNT) return; if (viminfo_hisidx[type] < viminfo_hislen[type]) { val = vp[3].bv_string; if (val != NULL && *val != NUL) { int sep = type == HIST_SEARCH && vp[2].bv_type == BVAL_NR ? vp[2].bv_nr : NUL; int idx; int overwrite = FALSE; if (!in_history(type, val, viminfo_add_at_front, sep, writing)) { // If lines were written by an older Vim we need to avoid // getting duplicates. See if the entry already exists. for (idx = 0; idx < viminfo_hisidx[type]; ++idx) { p = viminfo_history[type][idx].hisstr; if (STRCMP(val, p) == 0 && (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) { overwrite = TRUE; break; } } if (!overwrite) { // Need to re-allocate to append the separator byte. len = vp[3].bv_len; p = alloc(len + 2); } else len = 0; // for picky compilers if (p != NULL) { viminfo_history[type][idx].time_set = vp[1].bv_nr; if (!overwrite) { mch_memmove(p, val, (size_t)len + 1); // Put the separator after the NUL. p[len + 1] = sep; viminfo_history[type][idx].hisstr = p; viminfo_history[type][idx].hisnum = 0; viminfo_history[type][idx].viminfo = TRUE; viminfo_hisidx[type]++; } } } } } } /* * Concatenate history lines from viminfo after the lines typed in this Vim. */ static void concat_history(int type) { int idx; int i; int hislen = get_hislen(); histentry_T *histentry = get_histentry(type); int *hisidx = get_hisidx(type); int *hisnum = get_hisnum(type); idx = *hisidx + viminfo_hisidx[type]; if (idx >= hislen) idx -= hislen; else if (idx < 0) idx = hislen - 1; if (viminfo_add_at_front) *hisidx = idx; else { if (*hisidx == -1) *hisidx = hislen - 1; do { if (histentry[idx].hisstr != NULL || histentry[idx].viminfo) break; if (++idx == hislen) idx = 0; } while (idx != *hisidx); if (idx != *hisidx && --idx < 0) idx = hislen - 1; } for (i = 0; i < viminfo_hisidx[type]; i++) { vim_free(histentry[idx].hisstr); histentry[idx].hisstr = viminfo_history[type][i].hisstr; histentry[idx].viminfo = TRUE; histentry[idx].time_set = viminfo_history[type][i].time_set; if (--idx < 0) idx = hislen - 1; } idx += 1; idx %= hislen; for (i = 0; i < viminfo_hisidx[type]; i++) { histentry[idx++].hisnum = ++*hisnum; idx %= hislen; } } static int sort_hist(const void *s1, const void *s2) { histentry_T *p1 = *(histentry_T **)s1; histentry_T *p2 = *(histentry_T **)s2; if (p1->time_set < p2->time_set) return -1; if (p1->time_set > p2->time_set) return 1; return 0; } /* * Merge history lines from viminfo and lines typed in this Vim based on the * timestamp; */ static void merge_history(int type) { int max_len; histentry_T **tot_hist; histentry_T *new_hist; int i; int len; int hislen = get_hislen(); histentry_T *histentry = get_histentry(type); int *hisidx = get_hisidx(type); int *hisnum = get_hisnum(type); // Make one long list with all entries. max_len = hislen + viminfo_hisidx[type]; tot_hist = ALLOC_MULT(histentry_T *, max_len); new_hist = ALLOC_MULT(histentry_T, hislen ); if (tot_hist == NULL || new_hist == NULL) { vim_free(tot_hist); vim_free(new_hist); return; } for (i = 0; i < viminfo_hisidx[type]; i++) tot_hist[i] = &viminfo_history[type][i]; len = i; for (i = 0; i < hislen; i++) if (histentry[i].hisstr != NULL) tot_hist[len++] = &histentry[i]; // Sort the list on timestamp. qsort((void *)tot_hist, (size_t)len, sizeof(histentry_T *), sort_hist); // Keep the newest ones. for (i = 0; i < hislen; i++) { if (i < len) { new_hist[i] = *tot_hist[i]; tot_hist[i]->hisstr = NULL; if (new_hist[i].hisnum == 0) new_hist[i].hisnum = ++*hisnum; } else clear_hist_entry(&new_hist[i]); } *hisidx = (i < len ? i : len) - 1; // Free what is not kept. for (i = 0; i < viminfo_hisidx[type]; i++) vim_free(viminfo_history[type][i].hisstr); for (i = 0; i < hislen; i++) vim_free(histentry[i].hisstr); vim_free(histentry); set_histentry(type, new_hist); vim_free(tot_hist); } /* * Finish reading history lines from viminfo. Not used when writing viminfo. */ static void finish_viminfo_history(vir_T *virp) { int type; int merge = virp->vir_version >= VIMINFO_VERSION_WITH_HISTORY; for (type = 0; type < HIST_COUNT; ++type) { if (get_histentry(type) == NULL) continue; if (merge) merge_history(type); else concat_history(type); VIM_CLEAR(viminfo_history[type]); viminfo_hisidx[type] = 0; } } /* * Write history to viminfo file in "fp". * When "merge" is TRUE merge history lines with a previously read viminfo * file, data is in viminfo_history[]. * When "merge" is FALSE just write all history lines. Used for ":wviminfo!". */ static void write_viminfo_history(FILE *fp, int merge) { int i; int type; int num_saved; int round; int hislen; init_history(); hislen = get_hislen(); if (hislen == 0) return; for (type = 0; type < HIST_COUNT; ++type) { histentry_T *histentry = get_histentry(type); int *hisidx = get_hisidx(type); num_saved = get_viminfo_parameter(hist_type2char(type, FALSE)); if (num_saved == 0) continue; if (num_saved < 0) // Use default num_saved = hislen; fprintf(fp, _("\n# %s History (newest to oldest):\n"), type == HIST_CMD ? _("Command Line") : type == HIST_SEARCH ? _("Search String") : type == HIST_EXPR ? _("Expression") : type == HIST_INPUT ? _("Input Line") : _("Debug Line")); if (num_saved > hislen) num_saved = hislen; /* * Merge typed and viminfo history: * round 1: history of typed commands. * round 2: history from recently read viminfo. */ for (round = 1; round <= 2; ++round) { if (round == 1) // start at newest entry, somewhere in the list i = *hisidx; else if (viminfo_hisidx[type] > 0) // start at newest entry, first in the list i = 0; else // empty list i = -1; if (i >= 0) while (num_saved > 0 && !(round == 2 && i >= viminfo_hisidx[type])) { char_u *p; time_t timestamp; int c = NUL; if (round == 1) { p = histentry[i].hisstr; timestamp = histentry[i].time_set; } else { p = viminfo_history[type] == NULL ? NULL : viminfo_history[type][i].hisstr; timestamp = viminfo_history[type] == NULL ? 0 : viminfo_history[type][i].time_set; } if (p != NULL && (round == 2 || !merge || !histentry[i].viminfo)) { --num_saved; fputc(hist_type2char(type, TRUE), fp); // For the search history: put the separator in the // second column; use a space if there isn't one. if (type == HIST_SEARCH) { c = p[STRLEN(p) + 1]; putc(c == NUL ? ' ' : c, fp); } viminfo_writestring(fp, p); { char cbuf[NUMBUFLEN]; // New style history with a bar line. Format: // |{bartype},{histtype},{timestamp},{separator},"text" if (c == NUL) cbuf[0] = NUL; else sprintf(cbuf, "%d", c); fprintf(fp, "|%d,%d,%ld,%s,", BARTYPE_HISTORY, type, (long)timestamp, cbuf); barline_writestring(fp, p, LSIZE - 20); putc('\n', fp); } } if (round == 1) { // Decrement index, loop around and stop when back at // the start. if (--i < 0) i = hislen - 1; if (i == *hisidx) break; } else { // Increment index. Stop at the end in the while. ++i; } } } for (i = 0; i < viminfo_hisidx[type]; ++i) if (viminfo_history[type] != NULL) vim_free(viminfo_history[type][i].hisstr); VIM_CLEAR(viminfo_history[type]); viminfo_hisidx[type] = 0; } } #endif // FEAT_VIMINFO static void write_viminfo_barlines(vir_T *virp, FILE *fp_out) { int i; garray_T *gap = &virp->vir_barlines; int seen_useful = FALSE; char *line; if (gap->ga_len > 0) { fputs(_("\n# Bar lines, copied verbatim:\n"), fp_out); // Skip over continuation lines until seeing a useful line. for (i = 0; i < gap->ga_len; ++i) { line = ((char **)(gap->ga_data))[i]; if (seen_useful || line[1] != '<') { fputs(line, fp_out); seen_useful = TRUE; } } } } /* * Parse a viminfo line starting with '|'. * Add each decoded value to "values". * Returns TRUE if the next line is to be read after using the parsed values. */ static int barline_parse(vir_T *virp, char_u *text, garray_T *values) { char_u *p = text; char_u *nextp = NULL; char_u *buf = NULL; bval_T *value; int i; int allocated = FALSE; int eof; char_u *sconv; int converted; while (*p == ',') { ++p; if (ga_grow(values, 1) == FAIL) break; value = (bval_T *)(values->ga_data) + values->ga_len; if (*p == '>') { // Need to read a continuation line. Put strings in allocated // memory, because virp->vir_line is overwritten. if (!allocated) { for (i = 0; i < values->ga_len; ++i) { bval_T *vp = (bval_T *)(values->ga_data) + i; if (vp->bv_type == BVAL_STRING && !vp->bv_allocated) { vp->bv_string = vim_strnsave(vp->bv_string, vp->bv_len); vp->bv_allocated = TRUE; } } allocated = TRUE; } if (vim_isdigit(p[1])) { size_t len; size_t todo; size_t n; // String value was split into lines that are each shorter // than LSIZE: // |{bartype},>{length of "{text}{text2}"} // |<"{text1} // |<{text2}",{value} // Length includes the quotes. ++p; len = getdigits(&p); buf = alloc((int)(len + 1)); if (buf == NULL) return TRUE; p = buf; for (todo = len; todo > 0; todo -= n) { eof = viminfo_readline(virp); if (eof || virp->vir_line[0] != '|' || virp->vir_line[1] != '<') { // File was truncated or garbled. Read another line if // this one starts with '|'. vim_free(buf); return eof || virp->vir_line[0] == '|'; } // Get length of text, excluding |< and NL chars. n = STRLEN(virp->vir_line); while (n > 0 && (virp->vir_line[n - 1] == NL || virp->vir_line[n - 1] == CAR)) --n; n -= 2; if (n > todo) { // more values follow after the string nextp = virp->vir_line + 2 + todo; n = todo; } mch_memmove(p, virp->vir_line + 2, n); p += n; } *p = NUL; p = buf; } else { // Line ending in ">" continues in the next line: // |{bartype},{lots of values},> // |<{value},{value} eof = viminfo_readline(virp); if (eof || virp->vir_line[0] != '|' || virp->vir_line[1] != '<') // File was truncated or garbled. Read another line if // this one starts with '|'. return eof || virp->vir_line[0] == '|'; p = virp->vir_line + 2; } } if (isdigit(*p)) { value->bv_type = BVAL_NR; value->bv_nr = getdigits(&p); ++values->ga_len; } else if (*p == '"') { int len = 0; char_u *s = p; // Unescape special characters in-place. ++p; while (*p != '"') { if (*p == NL || *p == NUL) return TRUE; // syntax error, drop the value if (*p == '\\') { ++p; if (*p == 'n') s[len++] = '\n'; else s[len++] = *p; ++p; } else s[len++] = *p++; } ++p; s[len] = NUL; converted = FALSE; if (virp->vir_conv.vc_type != CONV_NONE && *s != NUL) { sconv = string_convert(&virp->vir_conv, s, NULL); if (sconv != NULL) { if (s == buf) vim_free(s); s = sconv; buf = s; converted = TRUE; } } // Need to copy in allocated memory if the string wasn't allocated // above and we did allocate before, thus vir_line may change. if (s != buf && allocated) s = vim_strsave(s); value->bv_string = s; value->bv_type = BVAL_STRING; value->bv_len = len; value->bv_allocated = allocated || converted; ++values->ga_len; if (nextp != NULL) { // values following a long string p = nextp; nextp = NULL; } } else if (*p == ',') { value->bv_type = BVAL_EMPTY; ++values->ga_len; } else break; } return TRUE; } 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) { 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; } /* * Report an error for reading a viminfo file. * Count the number of errors. When there are more than 10, return TRUE. */ int viminfo_error(char *errnum, char *message, char_u *line) { vim_snprintf((char *)IObuff, IOSIZE, _("%sviminfo: %s in line: "), errnum, message); STRNCAT(IObuff, line, IOSIZE - STRLEN(IObuff) - 1); if (IObuff[STRLEN(IObuff) - 1] == '\n') IObuff[STRLEN(IObuff) - 1] = NUL; emsg((char *)IObuff); if (++viminfo_errcnt >= 10) { emsg(_("E136: viminfo: Too many errors, skipping rest of file")); return TRUE; } return FALSE; } /* * Compare the 'encoding' value in the viminfo file with the current value of * 'encoding'. If different and the 'c' flag is in 'viminfo', setup for * conversion of text with iconv() in viminfo_readstring(). */ static int viminfo_encoding(vir_T *virp) { char_u *p; int i; if (get_viminfo_parameter('c') != 0) { p = vim_strchr(virp->vir_line, '='); if (p != NULL) { // remove trailing newline ++p; for (i = 0; vim_isprintc(p[i]); ++i) ; p[i] = NUL; convert_setup(&virp->vir_conv, p, p_enc); } } return viminfo_readline(virp); } #if defined(FEAT_EVAL) || defined(PROTO) /* * Restore global vars that start with a capital from the viminfo file */ static int read_viminfo_varlist(vir_T *virp, int writing) { char_u *tab; int type = VAR_NUMBER; typval_T tv; funccal_entry_T funccal_entry; if (!writing && (find_viminfo_parameter('!') != NULL)) { tab = vim_strchr(virp->vir_line + 1, '\t'); if (tab != NULL) { *tab++ = '\0'; // isolate the variable name switch (*tab) { case 'S': type = VAR_STRING; break; #ifdef FEAT_FLOAT case 'F': type = VAR_FLOAT; break; #endif case 'D': type = VAR_DICT; break; case 'L': type = VAR_LIST; break; case 'B': type = VAR_BLOB; break; case 'X': type = VAR_SPECIAL; break; } tab = vim_strchr(tab, '\t'); if (tab != NULL) { tv.v_type = type; if (type == VAR_STRING || type == VAR_DICT || type == VAR_LIST || type == VAR_BLOB) tv.vval.v_string = viminfo_readstring(virp, (int)(tab - virp->vir_line + 1), TRUE); #ifdef FEAT_FLOAT else if (type == VAR_FLOAT) (void)string2float(tab + 1, &tv.vval.v_float); #endif else tv.vval.v_number = atol((char *)tab + 1); if (type == VAR_DICT || type == VAR_LIST) { typval_T *etv = eval_expr(tv.vval.v_string, NULL); if (etv == NULL) // Failed to parse back the dict or list, use it as a // string. tv.v_type = VAR_STRING; else { vim_free(tv.vval.v_string); tv = *etv; vim_free(etv); } } else if (type == VAR_BLOB) { blob_T *blob = string2blob(tv.vval.v_string); if (blob == NULL) // Failed to parse back the blob, use it as a string. tv.v_type = VAR_STRING; else { vim_free(tv.vval.v_string); tv.v_type = VAR_BLOB; tv.vval.v_blob = blob; } } // when in a function use global variables save_funccal(&funccal_entry); set_var(virp->vir_line + 1, &tv, FALSE); restore_funccal(); if (tv.v_type == VAR_STRING) vim_free(tv.vval.v_string); else if (tv.v_type == VAR_DICT || tv.v_type == VAR_LIST || tv.v_type == VAR_BLOB) clear_tv(&tv); } } } return viminfo_readline(virp); } /* * Write global vars that start with a capital to the viminfo file */ static void write_viminfo_varlist(FILE *fp) { hashitem_T *hi; dictitem_T *this_var; int todo; char *s = ""; char_u *p; char_u *tofree; char_u numbuf[NUMBUFLEN]; if (find_viminfo_parameter('!') == NULL) return; fputs(_("\n# global variables:\n"), fp); todo = (int)globvarht.ht_used; for (hi = globvarht.ht_array; todo > 0; ++hi) { if (!HASHITEM_EMPTY(hi)) { --todo; this_var = HI2DI(hi); if (var_flavour(this_var->di_key) == VAR_FLAVOUR_VIMINFO) { switch (this_var->di_tv.v_type) { case VAR_STRING: s = "STR"; break; case VAR_NUMBER: s = "NUM"; break; case VAR_FLOAT: s = "FLO"; break; case VAR_DICT: s = "DIC"; break; case VAR_LIST: s = "LIS"; break; case VAR_BLOB: s = "BLO"; break; case VAR_SPECIAL: s = "XPL"; break; case VAR_UNKNOWN: case VAR_FUNC: case VAR_PARTIAL: case VAR_JOB: case VAR_CHANNEL: continue; } fprintf(fp, "!%s\t%s\t", this_var->di_key, s); if (this_var->di_tv.v_type == VAR_SPECIAL) { sprintf((char *)numbuf, "%ld", (long)this_var->di_tv.vval.v_number); p = numbuf; tofree = NULL; } else p = echo_string(&this_var->di_tv, &tofree, numbuf, 0); if (p != NULL) viminfo_writestring(fp, p); vim_free(tofree); } } } } #endif // FEAT_EVAL /* * 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; } 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 /* * 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 (;;) { 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 (;;) { /* * 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) break; // continue if shortname was set } #if defined(UNIX) && defined(HAVE_FCHOWN) if (tempname != NULL && fp_out != NULL) { stat_T tmp_st; /* * 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 (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); } else // can't stat the file, set conservative permissions (void)mch_setperm(tempname, 0600); } #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; } 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) { 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); } end: vim_free(fname); vim_free(tempname); } /* * Read a line from the viminfo file. * Returns TRUE for end-of-file; */ int viminfo_readline(vir_T *virp) { return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd); } /* * 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). */ 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; } /* * 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 <length> \n " in first line * - write " < <string> \n " in second line */ 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. */ 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; } /* * ":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