mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
viminfo: First version of ShaDa file dumping
What works: 1. ShaDa file dumping: header, registers, jump list, history, search patterns, substitute strings, variables. 2. ShaDa file reading: registers, global marks, variables. Most was not tested. TODO: 1. Merging. 2. Reading history, local marks, jump and buffer lists. 3. Documentation update. 4. Converting some data from &encoding. 5. Safer variant of dumping viminfo (dump to temporary file then rename). 6. Removing old viminfo code (currently masked with `#if 0` in a ShaDa file for reference).
This commit is contained in:
parent
0fdaab995e
commit
244dbe3a77
@ -9,7 +9,7 @@ def DirectoryOfThisScript():
|
||||
|
||||
def GetDatabase():
|
||||
compilation_database_folder = os.path.join(DirectoryOfThisScript(),
|
||||
'..', 'build')
|
||||
'..', '..', 'build')
|
||||
if os.path.exists(compilation_database_folder):
|
||||
return ycm_core.CompilationDatabase(compilation_database_folder)
|
||||
return None
|
||||
|
@ -6770,10 +6770,14 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
e.g., for Unix: "r/tmp". Case is ignored. Maximum length of
|
||||
each 'r' argument is 50 characters.
|
||||
*viminfo-s*
|
||||
s Maximum size of an item in Kbyte. If zero then registers are
|
||||
not saved. Currently only applies to registers. The default
|
||||
"s10" will exclude registers with more than 10 Kbyte of text.
|
||||
Also see the '<' item above: line count limit.
|
||||
s Maximum size of an item contents in KiB. If zero then nothing
|
||||
is saved. Unlike Vim this applies to all items, except for
|
||||
the buffer list and header. Full item size is off by three
|
||||
unsigned integers: with `s10` maximum item size may be 1 byte
|
||||
(type: 7-bit integer) + 9 bytes (timestamp: up to 64-bit
|
||||
integer) + 3 bytes (item size: up to 16-bit integer because
|
||||
2^8 < 10240 < 2^16) + 10240 bytes (requested maximum item
|
||||
contents size) = 10253 bytes.
|
||||
|
||||
Example: >
|
||||
:set viminfo='50,<1000,s100,:0,n~/vim/viminfo
|
||||
@ -6782,7 +6786,8 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
edited.
|
||||
<1000 Contents of registers (up to 1000 lines each) will be
|
||||
remembered.
|
||||
s100 Registers with more than 100 Kbyte text are skipped.
|
||||
s100 Items with contents occupying more then 100 KiB are
|
||||
skipped.
|
||||
:0 Command-line history will not be saved.
|
||||
n~/vim/viminfo The name of the file to use is "~/vim/viminfo".
|
||||
no / Since '/' is not specified, the default will be used,
|
||||
|
73
scripts/shadacat.py
Executable file
73
scripts/shadacat.py
Executable file
@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3.4
|
||||
|
||||
import sys
|
||||
import codecs
|
||||
|
||||
from enum import Enum
|
||||
from datetime import datetime
|
||||
from functools import reduce
|
||||
|
||||
import msgpack
|
||||
|
||||
|
||||
class EntryTypes(Enum):
|
||||
Unknown = -1
|
||||
Missing = 0
|
||||
Header = 1
|
||||
SearchPattern = 2
|
||||
SubString = 3
|
||||
HistoryEntry = 4
|
||||
Register = 5
|
||||
Variable = 6
|
||||
GlobalMark = 7
|
||||
Jump = 8
|
||||
BufferList = 9
|
||||
LocalMark = 10
|
||||
|
||||
|
||||
def strtrans_errors(e):
|
||||
if not isinstance(e, UnicodeDecodeError):
|
||||
raise NotImplementedError('don’t know how to handle {0} error'.format(
|
||||
e.__class__.__name__))
|
||||
return '<{0:x}>'.format(reduce((lambda a, b: a*0x100+b),
|
||||
list(e.object[e.start:e.end]))), e.end
|
||||
|
||||
|
||||
codecs.register_error('strtrans', strtrans_errors)
|
||||
|
||||
|
||||
def idfunc(o):
|
||||
return o
|
||||
|
||||
|
||||
class CharInt(int):
|
||||
def __repr__(self):
|
||||
return super(CharInt, self).__repr__() + ' (\'%s\')' % chr(self)
|
||||
|
||||
|
||||
ctable = {
|
||||
bytes: lambda s: s.decode('utf-8', 'strtrans'),
|
||||
dict: lambda d: dict((mnormalize(k), mnormalize(v)) for k, v in d.items()),
|
||||
list: lambda l: list(mnormalize(i) for i in l),
|
||||
int: lambda n: CharInt(n) if 0x20 <= n <= 0x7E else n,
|
||||
}
|
||||
|
||||
|
||||
def mnormalize(o):
|
||||
return ctable.get(type(o), idfunc)(o)
|
||||
|
||||
|
||||
with open(sys.argv[1], 'rb') as fp:
|
||||
unpacker = msgpack.Unpacker(file_like=fp)
|
||||
while True:
|
||||
try:
|
||||
typ = EntryTypes(unpacker.unpack())
|
||||
except msgpack.OutOfData:
|
||||
break
|
||||
else:
|
||||
timestamp = unpacker.unpack()
|
||||
time = datetime.fromtimestamp(timestamp)
|
||||
length = unpacker.unpack()
|
||||
entry = unpacker.unpack()
|
||||
print('{0:13} {1} {2:5} {3!r}'.format(
|
||||
typ.name, time.isoformat(), length, mnormalize(entry)))
|
@ -69,6 +69,8 @@
|
||||
#define ADD(array, item) \
|
||||
kv_push(Object, array, item)
|
||||
|
||||
#define STATIC_CSTR_AS_STRING(s) ((String) {.data = s, .size = sizeof(s) - 1})
|
||||
|
||||
// Helpers used by the generated msgpack-rpc api wrappers
|
||||
#define api_init_boolean
|
||||
#define api_init_integer
|
||||
|
@ -84,7 +84,7 @@ return {
|
||||
'User', -- user defined autocommand
|
||||
'VimEnter', -- after starting Vim
|
||||
'VimLeave', -- before exiting Vim
|
||||
'VimLeavePre', -- before exiting Vim and writing .viminfo
|
||||
'VimLeavePre', -- before exiting Vim and writing ShaDa file
|
||||
'VimResized', -- after Vim window was resized
|
||||
'WinEnter', -- after entering a window
|
||||
'WinLeave', -- before leaving a window
|
||||
|
@ -73,6 +73,7 @@
|
||||
#include "nvim/undo.h"
|
||||
#include "nvim/version.h"
|
||||
#include "nvim/window.h"
|
||||
#include "nvim/shada.h"
|
||||
#include "nvim/os/os.h"
|
||||
#include "nvim/os/time.h"
|
||||
#include "nvim/os/input.h"
|
||||
@ -555,6 +556,12 @@ static void free_buffer(buf_T *buf)
|
||||
free_buffer_stuff(buf, TRUE);
|
||||
unref_var_dict(buf->b_vars);
|
||||
aubuflocal_remove(buf);
|
||||
free_fmark(buf->b_last_cursor);
|
||||
free_fmark(buf->b_last_insert);
|
||||
free_fmark(buf->b_last_change);
|
||||
for (size_t i = 0; i < NMARKS; i++) {
|
||||
free_fmark(buf->b_namedm[i]);
|
||||
}
|
||||
if (autocmd_busy) {
|
||||
// Do not free the buffer structure while autocommands are executing,
|
||||
// it's still needed. Free it when autocmd_busy is reset.
|
||||
@ -4164,93 +4171,6 @@ chk_modeline (
|
||||
return retval;
|
||||
}
|
||||
|
||||
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 = path_shorten_fname_if_possible(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);
|
||||
}
|
||||
}
|
||||
xfree(xline);
|
||||
|
||||
return viminfo_readline(virp);
|
||||
}
|
||||
|
||||
void write_viminfo_bufferlist(FILE *fp)
|
||||
{
|
||||
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 = xmalloc(LINE_BUF_LEN);
|
||||
|
||||
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
|
||||
|| bt_quickfix(buf)
|
||||
|| 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%" PRId64 "\t%d",
|
||||
(int64_t)buf->b_last_cursor.lnum,
|
||||
buf->b_last_cursor.col);
|
||||
viminfo_writestring(fp, line);
|
||||
}
|
||||
xfree(line);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Return special buffer name.
|
||||
* Returns NULL when the buffer has a normal file name.
|
||||
|
@ -327,15 +327,6 @@ typedef struct {
|
||||
bool vc_fail; /* fail for invalid char, don't use '?' */
|
||||
} vimconv_T;
|
||||
|
||||
/*
|
||||
* Structure used for reading from the viminfo file.
|
||||
*/
|
||||
typedef struct {
|
||||
char_u *vir_line; /* text of the current line */
|
||||
FILE *vir_fd; /* file descriptor */
|
||||
vimconv_T vir_conv; /* encoding conversion */
|
||||
} vir_T;
|
||||
|
||||
#define CONV_NONE 0
|
||||
#define CONV_TO_UTF8 1
|
||||
#define CONV_9_TO_UTF8 2
|
||||
@ -515,16 +506,16 @@ struct file_buffer {
|
||||
uint64_t b_orig_size; /* size of original file in bytes */
|
||||
int b_orig_mode; /* mode of original file */
|
||||
|
||||
pos_T b_namedm[NMARKS]; /* current named marks (mark.c) */
|
||||
fmark_T b_namedm[NMARKS]; /* current named marks (mark.c) */
|
||||
|
||||
/* These variables are set when VIsual_active becomes FALSE */
|
||||
visualinfo_T b_visual;
|
||||
int b_visual_mode_eval; /* b_visual.vi_mode for visualmode() */
|
||||
|
||||
pos_T b_last_cursor; /* cursor position when last unloading this
|
||||
fmark_T b_last_cursor; /* cursor position when last unloading this
|
||||
buffer */
|
||||
pos_T b_last_insert; /* where Insert mode was left */
|
||||
pos_T b_last_change; /* position of last change: '. mark */
|
||||
fmark_T b_last_insert; /* where Insert mode was left */
|
||||
fmark_T b_last_change; /* position of last change: '. mark */
|
||||
|
||||
/*
|
||||
* the changelist contains old change positions
|
||||
@ -553,7 +544,7 @@ struct file_buffer {
|
||||
pos_T b_op_start_orig; // used for Insstart_orig
|
||||
pos_T b_op_end;
|
||||
|
||||
bool b_marks_read; /* Have we read viminfo marks yet? */
|
||||
bool b_marks_read; /* Have we read ShaDa marks yet? */
|
||||
|
||||
/*
|
||||
* The following only used in undo.c.
|
||||
|
@ -60,6 +60,7 @@
|
||||
#include "nvim/undo.h"
|
||||
#include "nvim/window.h"
|
||||
#include "nvim/event/loop.h"
|
||||
#include "nvim/mark.h"
|
||||
#include "nvim/os/input.h"
|
||||
#include "nvim/os/time.h"
|
||||
|
||||
@ -6991,8 +6992,9 @@ ins_esc (
|
||||
curwin->w_set_curswant = TRUE;
|
||||
|
||||
/* Remember the last Insert position in the '^ mark. */
|
||||
if (!cmdmod.keepjumps)
|
||||
curbuf->b_last_insert = curwin->w_cursor;
|
||||
if (!cmdmod.keepjumps) {
|
||||
RESET_FMARK(&curbuf->b_last_insert, curwin->w_cursor, curbuf->b_fnum);
|
||||
}
|
||||
|
||||
/*
|
||||
* The cursor should end up on the last inserted character.
|
||||
|
141
src/nvim/eval.c
141
src/nvim/eval.c
@ -354,7 +354,7 @@ typedef struct {
|
||||
typedef enum {
|
||||
VAR_FLAVOUR_DEFAULT, /* doesn't start with uppercase */
|
||||
VAR_FLAVOUR_SESSION, /* starts with uppercase, some lower */
|
||||
VAR_FLAVOUR_VIMINFO /* all uppercase */
|
||||
VAR_FLAVOUR_SHADA /* all uppercase */
|
||||
} var_flavour_T;
|
||||
|
||||
/* values for vv_flags: */
|
||||
@ -10482,6 +10482,7 @@ static void f_has(typval_T *argvars, typval_T *rettv)
|
||||
"scrollbind",
|
||||
"showcmd",
|
||||
"cmdline_info",
|
||||
"shada",
|
||||
"signs",
|
||||
"smartindent",
|
||||
"startuptime",
|
||||
@ -10498,7 +10499,6 @@ static void f_has(typval_T *argvars, typval_T *rettv)
|
||||
"title",
|
||||
"user-commands", /* was accidentally included in 5.4 */
|
||||
"user_commands",
|
||||
"viminfo",
|
||||
"vertsplit",
|
||||
"virtualedit",
|
||||
"visual",
|
||||
@ -20712,107 +20712,62 @@ static var_flavour_T var_flavour(char_u *varname)
|
||||
while (*(++p))
|
||||
if (ASCII_ISLOWER(*p))
|
||||
return VAR_FLAVOUR_SESSION;
|
||||
return VAR_FLAVOUR_VIMINFO;
|
||||
return VAR_FLAVOUR_SHADA;
|
||||
} else
|
||||
return VAR_FLAVOUR_DEFAULT;
|
||||
}
|
||||
|
||||
/*
|
||||
* Restore global vars that start with a capital from the viminfo file
|
||||
*/
|
||||
int read_viminfo_varlist(vir_T *virp, int writing)
|
||||
/// Iterate over global variables
|
||||
///
|
||||
/// @warning No modifications to global variable dictionary must be performed
|
||||
/// while iteration is in progress.
|
||||
///
|
||||
/// @param[in] iter Iterator. Pass NULL to start iteration.
|
||||
/// @param[out] name Variable name.
|
||||
/// @param[out] rettv Variable value.
|
||||
///
|
||||
/// @return Pointer that needs to be passed to next `var_shada_iter` invocation
|
||||
/// or NULL to indicate that iteration is over.
|
||||
const void *var_shada_iter(const void *const iter, const char **const name,
|
||||
typval_T *rettv)
|
||||
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(2, 3)
|
||||
{
|
||||
char_u *tab;
|
||||
int type = VAR_NUMBER;
|
||||
typval_T tv;
|
||||
|
||||
if (!writing && (find_viminfo_parameter('!') != NULL)) {
|
||||
tab = vim_strchr(virp->vir_line + 1, '\t');
|
||||
if (tab != NULL) {
|
||||
*tab++ = NUL; /* isolate the variable name */
|
||||
switch (*tab) {
|
||||
case 'S': type = VAR_STRING; break;
|
||||
case 'F': type = VAR_FLOAT; break;
|
||||
case 'D': type = VAR_DICT; break;
|
||||
case 'L': type = VAR_LIST; break;
|
||||
}
|
||||
|
||||
tab = vim_strchr(tab, '\t');
|
||||
if (tab != NULL) {
|
||||
tv.v_type = type;
|
||||
if (type == VAR_STRING || type == VAR_DICT || type == VAR_LIST)
|
||||
tv.vval.v_string = viminfo_readstring(virp,
|
||||
(int)(tab - virp->vir_line + 1), TRUE);
|
||||
else if (type == VAR_FLOAT)
|
||||
(void)string2float(tab + 1, &tv.vval.v_float);
|
||||
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 {
|
||||
xfree(tv.vval.v_string);
|
||||
tv = *etv;
|
||||
xfree(etv);
|
||||
}
|
||||
}
|
||||
|
||||
set_var(virp->vir_line + 1, &tv, FALSE);
|
||||
|
||||
if (tv.v_type == VAR_STRING)
|
||||
xfree(tv.vval.v_string);
|
||||
else if (tv.v_type == VAR_DICT || tv.v_type == VAR_LIST)
|
||||
clear_tv(&tv);
|
||||
}
|
||||
const hashitem_T *hi;
|
||||
const hashitem_T *hifirst = globvarht.ht_array;
|
||||
const size_t hinum = (size_t) globvarht.ht_mask + 1;
|
||||
*name = NULL;
|
||||
if (iter == NULL) {
|
||||
hi = globvarht.ht_array;
|
||||
while ((HASHITEM_EMPTY(hi)
|
||||
|| var_flavour(HI2DI(hi)->di_key) != VAR_FLAVOUR_SHADA)
|
||||
&& (size_t) (hi - hifirst) < hinum) {
|
||||
hi++;
|
||||
}
|
||||
if (HASHITEM_EMPTY(hi)
|
||||
|| var_flavour(HI2DI(hi)->di_key) != VAR_FLAVOUR_SHADA) {
|
||||
*rettv = (typval_T) {.v_type = VAR_UNKNOWN};
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
hi = (const hashitem_T *) iter;
|
||||
}
|
||||
*name = (char *) HI2DI(hi)->di_key;
|
||||
copy_tv(&(HI2DI(hi)->di_tv), rettv);
|
||||
while ((size_t) (++hi - hifirst) < hinum) {
|
||||
if (!HASHITEM_EMPTY(hi)
|
||||
&& var_flavour(HI2DI(hi)->di_key) == VAR_FLAVOUR_SHADA) {
|
||||
return hi;
|
||||
}
|
||||
}
|
||||
|
||||
return viminfo_readline(virp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write global vars that start with a capital to the viminfo file
|
||||
*/
|
||||
void write_viminfo_varlist(FILE *fp)
|
||||
void var_set_global(const char *const name, typval_T vartv)
|
||||
{
|
||||
hashitem_T *hi;
|
||||
dictitem_T *this_var;
|
||||
int todo;
|
||||
char *s;
|
||||
char_u *p;
|
||||
|
||||
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;
|
||||
default: continue;
|
||||
}
|
||||
fprintf(fp, "!%s\t%s\t", this_var->di_key, s);
|
||||
p = (char_u *) echo_string(&this_var->di_tv, NULL);
|
||||
if (p != NULL) {
|
||||
viminfo_writestring(fp, p);
|
||||
}
|
||||
xfree(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
funccall_T *const saved_current_funccal = current_funccal;
|
||||
current_funccal = NULL;
|
||||
set_var((char_u *) name, &vartv, false);
|
||||
current_funccal = saved_current_funccal;
|
||||
}
|
||||
|
||||
int store_session_globals(FILE *fd)
|
||||
|
@ -67,6 +67,9 @@
|
||||
#include "nvim/os/os.h"
|
||||
#include "nvim/os/shell.h"
|
||||
#include "nvim/os/input.h"
|
||||
#include "nvim/os/time.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
|
||||
/*
|
||||
* Struct to hold the sign properties.
|
||||
@ -1391,550 +1394,6 @@ void append_redir(char_u *buf, int buflen, char_u *opt, char_u *fname)
|
||||
(char *)opt, (char *)fname);
|
||||
}
|
||||
|
||||
|
||||
static int viminfo_errcnt;
|
||||
|
||||
static int no_viminfo(void)
|
||||
{
|
||||
/* "vim -i NONE" does not read or write a viminfo file */
|
||||
return use_viminfo != NULL && STRCMP(use_viminfo, "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(IObuff);
|
||||
if (++viminfo_errcnt >= 10) {
|
||||
EMSG(_("E136: viminfo: Too many errors, skipping rest of file"));
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
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();
|
||||
}
|
||||
|
||||
xfree(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 */
|
||||
char_u *wp;
|
||||
#if defined(UNIX)
|
||||
mode_t umask_save;
|
||||
#endif
|
||||
|
||||
if (no_viminfo())
|
||||
return;
|
||||
|
||||
fname = viminfo_filename(file); /* may set to default if NULL */
|
||||
|
||||
fp_in = mch_fopen((char *)fname, READBIN);
|
||||
if (fp_in == NULL) {
|
||||
/* if it does exist, but we can't read it, don't try writing */
|
||||
if (os_file_exists(fname))
|
||||
goto end;
|
||||
#if defined(UNIX)
|
||||
/*
|
||||
* For Unix we create the .viminfo non-accessible for others,
|
||||
* because it may contain text from non-accessible documents.
|
||||
*/
|
||||
umask_save = umask(077);
|
||||
#endif
|
||||
fp_out = mch_fopen((char *)fname, WRITEBIN);
|
||||
#if defined(UNIX)
|
||||
(void)umask(umask_save);
|
||||
#endif
|
||||
} 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 later.
|
||||
*/
|
||||
#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.
|
||||
*/
|
||||
|
||||
FileInfo old_info; // FileInfo of existing viminfo file
|
||||
if (os_fileinfo((char *)fname, &old_info)
|
||||
&& getuid() != ROOT_UID
|
||||
&& !(old_info.stat.st_uid == getuid()
|
||||
? (old_info.stat.st_mode & 0200)
|
||||
: (old_info.stat.st_gid == getgid()
|
||||
? (old_info.stat.st_mode & 0020)
|
||||
: (old_info.stat.st_mode & 0002)))) {
|
||||
int tt = msg_didany;
|
||||
|
||||
/* avoid a wait_return for this message, it's annoying */
|
||||
EMSG2(_("E137: Viminfo file is not writable: %s"), fname);
|
||||
msg_didany = tt;
|
||||
fclose(fp_in);
|
||||
goto end;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Make tempname
|
||||
tempname = (char_u *)modname((char *)fname, ".tmp", FALSE);
|
||||
if (tempname != NULL) {
|
||||
/*
|
||||
* Check if tempfile already exists. Never overwrite an
|
||||
* existing file!
|
||||
*/
|
||||
if (os_file_exists(tempname)) {
|
||||
/*
|
||||
* Try another name. Change one character, just before
|
||||
* the extension.
|
||||
*/
|
||||
wp = tempname + STRLEN(tempname) - 5;
|
||||
if (wp < path_tail(tempname)) /* empty file name? */
|
||||
wp = path_tail(tempname);
|
||||
for (*wp = 'z'; os_file_exists(tempname); --*wp) {
|
||||
/*
|
||||
* They all exist? Must be something wrong! Don't
|
||||
* write the viminfo file then.
|
||||
*/
|
||||
if (*wp == 'a') {
|
||||
xfree(tempname);
|
||||
tempname = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tempname != NULL) {
|
||||
int fd;
|
||||
|
||||
/* Use os_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 = os_open((char *)tempname,
|
||||
O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW,
|
||||
(int)((old_info.stat.st_mode & 0777) | 0600));
|
||||
(void)umask(umask_save);
|
||||
# else
|
||||
fd = os_open((char *)tempname,
|
||||
O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600);
|
||||
# endif
|
||||
if (fd < 0)
|
||||
fp_out = NULL;
|
||||
else
|
||||
fp_out = fdopen(fd, WRITEBIN);
|
||||
|
||||
/*
|
||||
* If we can't create in the same directory, try creating a
|
||||
* "normal" temp file.
|
||||
*/
|
||||
if (fp_out == NULL) {
|
||||
xfree(tempname);
|
||||
if ((tempname = vim_tempname()) != NULL)
|
||||
fp_out = mch_fopen((char *)tempname, WRITEBIN);
|
||||
}
|
||||
|
||||
#ifdef UNIX
|
||||
/*
|
||||
* Make sure the owner can read/write it. This only works for
|
||||
* root.
|
||||
*/
|
||||
if (fp_out != NULL) {
|
||||
os_fchown(fileno(fp_out), old_info.stat.st_uid, old_info.stat.st_gid);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the new viminfo file can be written to.
|
||||
*/
|
||||
if (fp_out == NULL) {
|
||||
EMSG2(_("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));
|
||||
|
||||
fclose(fp_out); /* errors are ignored !? */
|
||||
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 && vim_rename(tempname, fname) == -1) {
|
||||
viminfo_errcnt++;
|
||||
EMSG2(_("E886: Can't rename viminfo file to %s!"), fname);
|
||||
}
|
||||
if (viminfo_errcnt > 0) {
|
||||
os_remove((char *)tempname);
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
xfree(fname);
|
||||
xfree(tempname);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
static char_u *viminfo_filename(char_u *file)
|
||||
{
|
||||
if (file == NULL || *file == NUL) {
|
||||
if (use_viminfo != NULL)
|
||||
file = use_viminfo;
|
||||
else if ((file = find_viminfo_parameter('n')) == NULL || *file == NUL) {
|
||||
#ifdef VIMINFO_FILE2
|
||||
// don't use $HOME when not defined (turned into "c:/"!).
|
||||
if (!os_env_exists("HOME")) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* do_viminfo() -- Should only be called from read_viminfo() & write_viminfo().
|
||||
*/
|
||||
static void do_viminfo(FILE *fp_in, FILE *fp_out, int flags)
|
||||
{
|
||||
int count = 0;
|
||||
int eof = FALSE;
|
||||
vir_T vir;
|
||||
int merge = FALSE;
|
||||
|
||||
vir.vir_line = xmalloc(LSIZE);
|
||||
vir.vir_fd = fp_in;
|
||||
vir.vir_conv.vc_type = CONV_NONE;
|
||||
|
||||
if (fp_in != NULL) {
|
||||
if (flags & VIF_WANT_INFO) {
|
||||
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] != '>')
|
||||
;
|
||||
}
|
||||
if (fp_out != NULL) {
|
||||
/* Write the info: */
|
||||
fprintf(fp_out, _("# This viminfo file was generated by Nvim %s.\n"),
|
||||
mediumVersion);
|
||||
fputs(_("# You may edit it if you're careful!\n\n"), 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);
|
||||
write_viminfo_history(fp_out, merge);
|
||||
write_viminfo_registers(fp_out);
|
||||
write_viminfo_varlist(fp_out);
|
||||
write_viminfo_filemarks(fp_out);
|
||||
write_viminfo_bufferlist(fp_out);
|
||||
count = write_viminfo_marks(fp_out);
|
||||
}
|
||||
if (fp_in != NULL
|
||||
&& (flags & (VIF_WANT_MARKS | VIF_GET_OLDFILES | VIF_FORCEIT)))
|
||||
copy_viminfo_marks(&vir, fp_out, count, eof, flags);
|
||||
|
||||
xfree(vir.vir_line);
|
||||
if (vir.vir_conv.vc_type != CONV_NONE)
|
||||
convert_setup(&vir.vir_conv, NULL, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
prepare_viminfo_history(forceit ? 9999 : 0, writing);
|
||||
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 '^': /* 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 '*': /* "*encoding=value" */
|
||||
eof = viminfo_encoding(virp);
|
||||
break;
|
||||
case '!': /* global variable */
|
||||
eof = read_viminfo_varlist(virp, writing);
|
||||
break;
|
||||
case '%': /* entry for buffer list */
|
||||
eof = read_viminfo_bufferlist(virp, writing);
|
||||
break;
|
||||
case '"':
|
||||
eof = read_viminfo_register(virp, forceit);
|
||||
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 '@':
|
||||
eof = read_viminfo_history(virp, writing);
|
||||
break;
|
||||
case '-':
|
||||
case '\'':
|
||||
eof = read_viminfo_filemark(virp, forceit);
|
||||
break;
|
||||
default:
|
||||
if (viminfo_error("E575: ", _("Illegal starting char"),
|
||||
virp->vir_line))
|
||||
eof = TRUE;
|
||||
else
|
||||
eof = viminfo_readline(virp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Finish reading history items. */
|
||||
if (!writing)
|
||||
finish_viminfo_history();
|
||||
|
||||
/* Change file names to buffer numbers for fmarks. */
|
||||
FOR_ALL_BUFFERS(buf) {
|
||||
fmarks_check_names(buf);
|
||||
}
|
||||
|
||||
return eof;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
char_u *
|
||||
viminfo_readstring (
|
||||
vir_T *virp,
|
||||
int off, /* offset for virp->vir_line */
|
||||
int convert /* convert the string */
|
||||
)
|
||||
FUNC_ATTR_NONNULL_RET
|
||||
{
|
||||
char_u *retval;
|
||||
char_u *s, *d;
|
||||
|
||||
if (virp->vir_line[off] == Ctrl_V && ascii_isdigit(virp->vir_line[off + 1])) {
|
||||
ssize_t len = atol((char *)virp->vir_line + off + 1);
|
||||
retval = xmalloc(len);
|
||||
// TODO(philix): change type of vim_fgets() size argument to size_t
|
||||
(void)vim_fgets(retval, (int)len, virp->vir_fd);
|
||||
s = retval + 1; /* Skip the leading '<' */
|
||||
} else {
|
||||
retval = vim_strsave(virp->vir_line + off);
|
||||
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) {
|
||||
xfree(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, "\026%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);
|
||||
}
|
||||
|
||||
void print_line_no_prefix(linenr_T lnum, int use_number, int list)
|
||||
{
|
||||
char_u numbuf[30];
|
||||
@ -3364,8 +2823,36 @@ int check_secure(void)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static char_u *old_sub = NULL; /* previous substitute pattern */
|
||||
static int global_need_beginline; /* call beginline() after ":g" */
|
||||
/// Previous substitute replacement string
|
||||
static SubReplacementString old_sub = {NULL, 0, NULL};
|
||||
|
||||
static int global_need_beginline; // call beginline() after ":g"
|
||||
|
||||
/// Get old substitute replacement string
|
||||
///
|
||||
/// @param[out] ret_sub Location where old string will be saved.
|
||||
void sub_get_replacement(SubReplacementString *const ret_sub)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
*ret_sub = old_sub;
|
||||
}
|
||||
|
||||
/// Set substitute string and timestamp
|
||||
///
|
||||
/// @warning `sub` must be in allocated memory. It is not copied.
|
||||
///
|
||||
/// @param[in] sub New replacement string.
|
||||
void sub_set_replacement(SubReplacementString sub)
|
||||
{
|
||||
xfree(old_sub.sub);
|
||||
if (sub.additional_elements != old_sub.additional_elements) {
|
||||
if (old_sub.additional_elements != NULL) {
|
||||
api_free_array(*old_sub.additional_elements);
|
||||
xfree(old_sub.additional_elements);
|
||||
}
|
||||
}
|
||||
old_sub = sub;
|
||||
}
|
||||
|
||||
/* do_sub()
|
||||
*
|
||||
@ -3473,16 +2960,19 @@ void do_sub(exarg_T *eap)
|
||||
}
|
||||
|
||||
if (!eap->skip) {
|
||||
xfree(old_sub);
|
||||
old_sub = vim_strsave(sub);
|
||||
sub_set_replacement((SubReplacementString) {
|
||||
.sub = xstrdup((char *) sub),
|
||||
.timestamp = os_time(),
|
||||
.additional_elements = NULL,
|
||||
});
|
||||
}
|
||||
} else if (!eap->skip) { /* use previous pattern and substitution */
|
||||
if (old_sub == NULL) { /* there is no previous command */
|
||||
if (old_sub.sub == NULL) { /* there is no previous command */
|
||||
EMSG(_(e_nopresub));
|
||||
return;
|
||||
}
|
||||
pat = NULL; /* search_regcomp() will use previous pattern */
|
||||
sub = old_sub;
|
||||
sub = (char_u *) old_sub.sub;
|
||||
|
||||
/* Vi compatibility quirk: repeating with ":s" keeps the cursor in the
|
||||
* last column after using "$". */
|
||||
@ -4501,27 +3991,10 @@ void global_exe(char_u *cmd)
|
||||
msgmore(curbuf->b_ml.ml_line_count - old_lcount);
|
||||
}
|
||||
|
||||
int read_viminfo_sub_string(vir_T *virp, int force)
|
||||
{
|
||||
if (force)
|
||||
xfree(old_sub);
|
||||
if (force || old_sub == NULL)
|
||||
old_sub = viminfo_readstring(virp, 1, TRUE);
|
||||
return viminfo_readline(virp);
|
||||
}
|
||||
|
||||
void write_viminfo_sub_string(FILE *fp)
|
||||
{
|
||||
if (get_viminfo_parameter('/') != 0 && old_sub != NULL) {
|
||||
fputs(_("\n# Last Substitute String:\n$"), fp);
|
||||
viminfo_writestring(fp, old_sub);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(EXITFREE)
|
||||
void free_old_sub(void)
|
||||
{
|
||||
xfree(old_sub);
|
||||
sub_set_replacement((SubReplacementString) {NULL, 0, NULL});
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -3,6 +3,9 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "nvim/os/time.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
|
||||
/* flags for do_ecmd() */
|
||||
#define ECMD_HIDE 0x01 /* don't free the current buffer */
|
||||
#define ECMD_SET_HELP 0x02 /* set b_help flag of (new) buffer before
|
||||
@ -16,11 +19,12 @@
|
||||
#define ECMD_LAST (linenr_T)-1 /* use last position in all files */
|
||||
#define ECMD_ONE (linenr_T)1 /* use first line */
|
||||
|
||||
/* flags for read_viminfo() and children */
|
||||
#define VIF_WANT_INFO 1 /* load non-mark info */
|
||||
#define VIF_WANT_MARKS 2 /* load file marks */
|
||||
#define VIF_FORCEIT 4 /* overwrite info already read */
|
||||
#define VIF_GET_OLDFILES 8 /* load v:oldfiles */
|
||||
/// Previous :substitute replacement string definition
|
||||
typedef struct {
|
||||
char *sub; ///< Previous replacement string.
|
||||
Timestamp timestamp; ///< Time when it was last set.
|
||||
Array *additional_elements; ///< Additional data left from ShaDa file.
|
||||
} SubReplacementString;
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "ex_cmds.h.generated.h"
|
||||
|
@ -75,6 +75,7 @@
|
||||
#include "nvim/mouse.h"
|
||||
#include "nvim/event/rstream.h"
|
||||
#include "nvim/event/wstream.h"
|
||||
#include "nvim/shada.h"
|
||||
|
||||
static int quitmore = 0;
|
||||
static int ex_pressedreturn = FALSE;
|
||||
@ -9149,11 +9150,11 @@ static void ex_viminfo(exarg_T *eap)
|
||||
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);
|
||||
if (shada_read_everything((char *) eap->arg, eap->forceit) == FAIL)
|
||||
EMSG(_("E195: Cannot open ShaDa file for reading"));
|
||||
} else {
|
||||
shada_write_file((char *) eap->arg, eap->forceit);
|
||||
}
|
||||
p_viminfo = save_viminfo;
|
||||
}
|
||||
|
||||
|
@ -65,6 +65,9 @@
|
||||
#include "nvim/os/input.h"
|
||||
#include "nvim/os/os.h"
|
||||
#include "nvim/event/loop.h"
|
||||
#include "nvim/os/time.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
|
||||
/*
|
||||
* Variables shared between getcmdline(), redrawcmdline() and others.
|
||||
@ -100,12 +103,6 @@ static int cmd_showtail; /* Only show path tail in lists ? */
|
||||
|
||||
static int new_cmdpos; /* position set by set_cmdline_pos() */
|
||||
|
||||
typedef struct hist_entry {
|
||||
int hisnum; /* identifying number */
|
||||
int viminfo; /* when TRUE hisstr comes from viminfo */
|
||||
char_u *hisstr; /* actual entry, separator char after the NUL */
|
||||
} histentry_T;
|
||||
|
||||
/*
|
||||
* Type used by call_user_expand_func
|
||||
*/
|
||||
@ -4230,12 +4227,10 @@ void init_history(void)
|
||||
|
||||
// delete entries that don't fit in newlen, if any
|
||||
for (int i = 0; i < i1; i++) {
|
||||
xfree(history[type][i].hisstr);
|
||||
history[type][i].hisstr = NULL;
|
||||
hist_free_entry(history[type] + i);
|
||||
}
|
||||
for (int i = i1 + l1; i < i2; i++) {
|
||||
xfree(history[type][i].hisstr);
|
||||
history[type][i].hisstr = NULL;
|
||||
hist_free_entry(history[type] + i);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4253,11 +4248,21 @@ void init_history(void)
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_hist_entry(histentry_T *hisptr)
|
||||
static inline void hist_free_entry(histentry_T *hisptr)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
hisptr->hisnum = 0;
|
||||
hisptr->viminfo = FALSE;
|
||||
hisptr->hisstr = NULL;
|
||||
xfree(hisptr->hisstr);
|
||||
if (hisptr->additional_elements != NULL) {
|
||||
api_free_array(*hisptr->additional_elements);
|
||||
xfree(hisptr->additional_elements);
|
||||
}
|
||||
clear_hist_entry(hisptr);
|
||||
}
|
||||
|
||||
static inline void clear_hist_entry(histentry_T *hisptr)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
memset(hisptr, 0, sizeof(*hisptr));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -4310,6 +4315,8 @@ in_history (
|
||||
history[type][i].hisnum = ++hisnum[type];
|
||||
history[type][i].viminfo = FALSE;
|
||||
history[type][i].hisstr = str;
|
||||
history[type][i].timestamp = os_time();
|
||||
history[type][i].additional_elements = NULL;
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
@ -4372,8 +4379,7 @@ add_to_history (
|
||||
if (maptick == last_maptick) {
|
||||
/* Current line is from the same mapping, remove it */
|
||||
hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]];
|
||||
xfree(hisptr->hisstr);
|
||||
clear_hist_entry(hisptr);
|
||||
hist_free_entry(hisptr);
|
||||
--hisnum[histype];
|
||||
if (--hisidx[HIST_SEARCH] < 0)
|
||||
hisidx[HIST_SEARCH] = hislen - 1;
|
||||
@ -4384,11 +4390,13 @@ add_to_history (
|
||||
if (++hisidx[histype] == hislen)
|
||||
hisidx[histype] = 0;
|
||||
hisptr = &history[histype][hisidx[histype]];
|
||||
xfree(hisptr->hisstr);
|
||||
hist_free_entry(hisptr);
|
||||
|
||||
/* Store the separator after the NUL of the string. */
|
||||
len = (int)STRLEN(new_entry);
|
||||
hisptr->hisstr = vim_strnsave(new_entry, len + 2);
|
||||
hisptr->timestamp = os_time();
|
||||
hisptr->additional_elements = NULL;
|
||||
hisptr->hisstr[len + 1] = sep;
|
||||
|
||||
hisptr->hisnum = ++hisnum[histype];
|
||||
@ -4545,23 +4553,21 @@ char_u *get_history_entry(int histype, int idx)
|
||||
return (char_u *)"";
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear all entries of a history.
|
||||
* "histype" may be one of the HIST_ values.
|
||||
*/
|
||||
int clr_history(int histype)
|
||||
/// Clear all entries in a history
|
||||
///
|
||||
/// @param[in] histype One of the HIST_ values.
|
||||
///
|
||||
/// @return OK if there was something to clean and histype was one of HIST_
|
||||
/// values, FAIL otherwise.
|
||||
int clr_history(const int histype)
|
||||
{
|
||||
int i;
|
||||
histentry_T *hisptr;
|
||||
|
||||
if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) {
|
||||
hisptr = history[histype];
|
||||
for (i = hislen; i--; ) {
|
||||
xfree(hisptr->hisstr);
|
||||
clear_hist_entry(hisptr);
|
||||
histentry_T *hisptr = history[histype];
|
||||
for (int i = hislen; i--; hisptr++) {
|
||||
hist_free_entry(hisptr);
|
||||
}
|
||||
hisidx[histype] = -1; /* mark history as cleared */
|
||||
hisnum[histype] = 0; /* reset identifier counter */
|
||||
hisidx[histype] = -1; // mark history as cleared
|
||||
hisnum[histype] = 0; // reset identifier counter
|
||||
return OK;
|
||||
}
|
||||
return FAIL;
|
||||
@ -4596,8 +4602,7 @@ int del_history_entry(int histype, char_u *str)
|
||||
break;
|
||||
if (vim_regexec(®match, hisptr->hisstr, (colnr_T)0)) {
|
||||
found = TRUE;
|
||||
xfree(hisptr->hisstr);
|
||||
clear_hist_entry(hisptr);
|
||||
hist_free_entry(hisptr);
|
||||
} else {
|
||||
if (i != last) {
|
||||
history[histype][last] = *hisptr;
|
||||
@ -4628,7 +4633,7 @@ int del_history_idx(int histype, int idx)
|
||||
if (i < 0)
|
||||
return FALSE;
|
||||
idx = hisidx[histype];
|
||||
xfree(history[histype][i].hisstr);
|
||||
hist_free_entry(&history[histype][i]);
|
||||
|
||||
/* When deleting the last added search string in a mapping, reset
|
||||
* last_maptick, so that the last added search string isn't deleted again.
|
||||
@ -4641,9 +4646,10 @@ int del_history_idx(int histype, int idx)
|
||||
history[histype][i] = history[histype][j];
|
||||
i = j;
|
||||
}
|
||||
clear_hist_entry(&history[histype][i]);
|
||||
if (--i < 0)
|
||||
clear_hist_entry(&history[histype][idx]);
|
||||
if (--i < 0) {
|
||||
i += hislen;
|
||||
}
|
||||
hisidx[histype] = i;
|
||||
return TRUE;
|
||||
}
|
||||
@ -4762,250 +4768,6 @@ void ex_history(exarg_T *eap)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Buffers for history read from a viminfo file. Only valid while reading.
|
||||
*/
|
||||
static char_u **viminfo_history[HIST_COUNT] = {NULL, NULL, NULL, NULL};
|
||||
static int viminfo_hisidx[HIST_COUNT] = {0, 0, 0, 0};
|
||||
static int viminfo_hislen[HIST_COUNT] = {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.
|
||||
*/
|
||||
void prepare_viminfo_history(int asklen, int writing)
|
||||
{
|
||||
int i;
|
||||
int num;
|
||||
|
||||
init_history();
|
||||
viminfo_add_at_front = (asklen != 0 && !writing);
|
||||
if (asklen > hislen)
|
||||
asklen = hislen;
|
||||
|
||||
for (int type = 0; type < HIST_COUNT; ++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 (history[type][i].hisstr == NULL || history[type][i].viminfo)
|
||||
num++;
|
||||
int len = asklen;
|
||||
if (num > len)
|
||||
len = num;
|
||||
if (len <= 0)
|
||||
viminfo_history[type] = NULL;
|
||||
else
|
||||
viminfo_history[type] = xmalloc(len * sizeof(char_u *));
|
||||
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.
|
||||
*/
|
||||
int read_viminfo_history(vir_T *virp, int writing)
|
||||
{
|
||||
int type;
|
||||
char_u *val;
|
||||
|
||||
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. */
|
||||
size_t len = STRLEN(val);
|
||||
char_u *p = xmalloc(len + 2);
|
||||
if (type == HIST_SEARCH) {
|
||||
/* Search entry: Move the separator from the first
|
||||
* column to after the NUL. */
|
||||
memmove(p, val + 1, len);
|
||||
p[len] = sep;
|
||||
} else {
|
||||
/* Not a search entry: No separator in the viminfo
|
||||
* file, add a NUL separator. */
|
||||
memmove(p, val, len + 1);
|
||||
p[len + 1] = NUL;
|
||||
}
|
||||
viminfo_history[type][viminfo_hisidx[type]++] = p;
|
||||
}
|
||||
}
|
||||
xfree(val);
|
||||
}
|
||||
return viminfo_readline(virp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Finish reading history lines from viminfo. Not used when writing viminfo.
|
||||
*/
|
||||
void finish_viminfo_history(void)
|
||||
{
|
||||
int idx;
|
||||
int i;
|
||||
int type;
|
||||
|
||||
for (type = 0; type < HIST_COUNT; ++type) {
|
||||
if (history[type] == NULL)
|
||||
continue;
|
||||
idx = hisidx[type] + viminfo_hisidx[type];
|
||||
if (idx >= hislen)
|
||||
idx -= hislen;
|
||||
else if (idx < 0)
|
||||
idx = hislen - 1;
|
||||
if (viminfo_add_at_front)
|
||||
hisidx[type] = idx;
|
||||
else {
|
||||
if (hisidx[type] == -1)
|
||||
hisidx[type] = hislen - 1;
|
||||
do {
|
||||
if (history[type][idx].hisstr != NULL
|
||||
|| history[type][idx].viminfo)
|
||||
break;
|
||||
if (++idx == hislen)
|
||||
idx = 0;
|
||||
} while (idx != hisidx[type]);
|
||||
if (idx != hisidx[type] && --idx < 0)
|
||||
idx = hislen - 1;
|
||||
}
|
||||
for (i = 0; i < viminfo_hisidx[type]; i++) {
|
||||
xfree(history[type][idx].hisstr);
|
||||
history[type][idx].hisstr = viminfo_history[type][i];
|
||||
history[type][idx].viminfo = TRUE;
|
||||
if (--idx < 0)
|
||||
idx = hislen - 1;
|
||||
}
|
||||
idx += 1;
|
||||
idx %= hislen;
|
||||
for (i = 0; i < viminfo_hisidx[type]; i++) {
|
||||
history[type][idx++].hisnum = ++hisnum[type];
|
||||
idx %= hislen;
|
||||
}
|
||||
xfree(viminfo_history[type]);
|
||||
viminfo_history[type] = NULL;
|
||||
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!".
|
||||
*/
|
||||
void write_viminfo_history(FILE *fp, int merge)
|
||||
{
|
||||
int i;
|
||||
int type;
|
||||
int num_saved;
|
||||
char_u *p;
|
||||
int c;
|
||||
int round;
|
||||
|
||||
init_history();
|
||||
if (hislen == 0)
|
||||
return;
|
||||
for (type = 0; type < HIST_COUNT; ++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") :
|
||||
_("Input 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[type];
|
||||
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])) {
|
||||
p = round == 1 ? history[type][i].hisstr
|
||||
: viminfo_history[type] == NULL ? NULL
|
||||
: viminfo_history[type][i];
|
||||
if (p != NULL && (round == 2
|
||||
|| !merge
|
||||
|| !history[type][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);
|
||||
}
|
||||
if (round == 1) {
|
||||
/* Decrement index, loop around and stop when back at
|
||||
* the start. */
|
||||
if (--i < 0)
|
||||
i = hislen - 1;
|
||||
if (i == hisidx[type])
|
||||
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)
|
||||
xfree(viminfo_history[type][i]);
|
||||
xfree(viminfo_history[type]);
|
||||
viminfo_history[type] = NULL;
|
||||
viminfo_hisidx[type] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a character at the current cursor+offset position.
|
||||
* It is directly written into the command buffer block.
|
||||
@ -5294,3 +5056,68 @@ char_u *script_get(exarg_T *eap, char_u *cmd)
|
||||
|
||||
return (char_u *)ga.ga_data;
|
||||
}
|
||||
|
||||
/// Iterate over history items
|
||||
///
|
||||
/// @warning No history-editing functions must be run while iteration is in
|
||||
/// progress.
|
||||
///
|
||||
/// @param[in] iter Pointer to the last history entry.
|
||||
/// @param[in] history_type Type of the history (HIST_*). Ignored if iter
|
||||
/// parameter is not NULL.
|
||||
/// @param[in] zero If true then zero (but not free) returned items.
|
||||
///
|
||||
/// @warning When using this parameter user is
|
||||
/// responsible for calling clr_history()
|
||||
/// itself after iteration is over. If
|
||||
/// clr_history() is not called behaviour is
|
||||
/// undefined. No functions that work with
|
||||
/// history must be called during iteration
|
||||
/// in this case.
|
||||
/// @param[out] hist Next history entry.
|
||||
///
|
||||
/// @return Pointer used in next iteration or NULL to indicate that iteration
|
||||
/// was finished.
|
||||
const void *hist_iter(const void *const iter, const size_t history_type,
|
||||
const bool zero, histentry_T *const hist)
|
||||
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(4)
|
||||
{
|
||||
*hist = (histentry_T) {
|
||||
.hisstr = NULL
|
||||
};
|
||||
if (hisidx[history_type] == -1) {
|
||||
return NULL;
|
||||
}
|
||||
histentry_T *const hstart = &(history[history_type][0]);
|
||||
histentry_T *const hlast = (
|
||||
&(history[history_type][hisidx[history_type]]));
|
||||
const histentry_T *const hend = &(history[history_type][hislen - 1]);
|
||||
histentry_T *hiter;
|
||||
if (iter == NULL) {
|
||||
histentry_T *hfirst = hlast;
|
||||
do {
|
||||
hfirst++;
|
||||
if (hfirst > hend) {
|
||||
hfirst = hstart;
|
||||
}
|
||||
if (hfirst->hisstr != NULL) {
|
||||
break;
|
||||
}
|
||||
} while (hfirst != hlast);
|
||||
hiter = hfirst;
|
||||
} else {
|
||||
hiter = (histentry_T *) iter;
|
||||
}
|
||||
if (hiter == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
*hist = *hiter;
|
||||
if (zero) {
|
||||
memset(hiter, 0, sizeof(*hiter));
|
||||
}
|
||||
if (hiter == hlast) {
|
||||
return NULL;
|
||||
}
|
||||
hiter++;
|
||||
return (const void *) ((hiter > hend) ? hstart : hiter);
|
||||
}
|
||||
|
@ -35,6 +35,15 @@
|
||||
|
||||
typedef char_u *(*CompleteListItemGetter)(expand_T *, int);
|
||||
|
||||
/// History entry definition
|
||||
typedef struct hist_entry {
|
||||
int hisnum; ///< Entry identifier number.
|
||||
bool viminfo; ///< If true, indicates that entry comes from viminfo.
|
||||
char_u *hisstr; ///< Actual entry, separator char after the NUL.
|
||||
Timestamp timestamp; ///< Time when entry was added.
|
||||
Array *additional_elements; ///< Additional entries from ShaDa file.
|
||||
} histentry_T;
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "ex_getln.h.generated.h"
|
||||
#endif
|
||||
|
@ -57,6 +57,7 @@
|
||||
#include "nvim/types.h"
|
||||
#include "nvim/undo.h"
|
||||
#include "nvim/window.h"
|
||||
#include "nvim/shada.h"
|
||||
#include "nvim/os/os.h"
|
||||
#include "nvim/os/time.h"
|
||||
#include "nvim/os/input.h"
|
||||
@ -2166,14 +2167,15 @@ readfile_charconvert (
|
||||
|
||||
|
||||
/*
|
||||
* Read marks for the current buffer from the viminfo file, when we support
|
||||
* Read marks for the current buffer from the ShaDa 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);
|
||||
&& curbuf->b_ffname != NULL) {
|
||||
shada_read_marks();
|
||||
}
|
||||
|
||||
/* Always set b_marks_read; needed when 'viminfo' is changed to include
|
||||
* the ' parameter after opening a buffer. */
|
||||
|
@ -892,7 +892,7 @@ EXTERN int skip_redraw INIT(= FALSE); /* skip redraw once */
|
||||
EXTERN int do_redraw INIT(= FALSE); /* extra redraw once */
|
||||
|
||||
EXTERN int need_highlight_changed INIT(= TRUE);
|
||||
EXTERN char_u *use_viminfo INIT(= NULL); /* name of viminfo file to use */
|
||||
EXTERN char *used_shada_file INIT(= NULL); /* name of viminfo file to use */
|
||||
|
||||
#define NSCRIPT 15
|
||||
EXTERN FILE *scriptin[NSCRIPT]; /* streams to read script from */
|
||||
|
@ -49,6 +49,7 @@
|
||||
#include "nvim/ops.h"
|
||||
#include "nvim/option.h"
|
||||
#include "nvim/os_unix.h"
|
||||
#include "nvim/os/os_defs.h"
|
||||
#include "nvim/path.h"
|
||||
#include "nvim/profile.h"
|
||||
#include "nvim/quickfix.h"
|
||||
@ -58,6 +59,7 @@
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/version.h"
|
||||
#include "nvim/window.h"
|
||||
#include "nvim/shada.h"
|
||||
#include "nvim/os/input.h"
|
||||
#include "nvim/os/os.h"
|
||||
#include "nvim/os/time.h"
|
||||
@ -377,12 +379,12 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
/*
|
||||
* Read in registers, history etc, but not marks, from the viminfo file.
|
||||
* Read in registers, history etc, but not marks, from the ShaDa file.
|
||||
* This is where v:oldfiles gets filled.
|
||||
*/
|
||||
if (*p_viminfo != NUL) {
|
||||
read_viminfo(NULL, VIF_WANT_INFO | VIF_GET_OLDFILES);
|
||||
TIME_MSG("reading viminfo");
|
||||
(void) shada_read_file(NULL, kShaDaWantInfo|kShaDaGetOldfiles);
|
||||
TIME_MSG("reading ShaDa");
|
||||
}
|
||||
/* It's better to make v:oldfiles an empty list than NULL. */
|
||||
if (get_vim_var_list(VV_OLDFILES) == NULL)
|
||||
@ -803,9 +805,10 @@ void getout(int exitval)
|
||||
apply_autocmds(EVENT_VIMLEAVEPRE, NULL, NULL, FALSE, curbuf);
|
||||
}
|
||||
|
||||
if (p_viminfo && *p_viminfo != NUL)
|
||||
/* Write out the registers, history, marks etc, to the viminfo file */
|
||||
write_viminfo(NULL, FALSE);
|
||||
if (p_viminfo && *p_viminfo != NUL) {
|
||||
// Write out the registers, history, marks etc, to the viminfo file
|
||||
shada_write_file(NULL, false);
|
||||
}
|
||||
|
||||
if (get_vim_var_nr(VV_DYING) <= 1)
|
||||
apply_autocmds(EVENT_VIMLEAVE, NULL, NULL, FALSE, curbuf);
|
||||
@ -1164,7 +1167,7 @@ static void command_line_scan(mparm_T *parmp)
|
||||
}
|
||||
/*FALLTHROUGH*/
|
||||
case 'S': /* "-S {file}" execute Vim script */
|
||||
case 'i': /* "-i {viminfo}" use for viminfo */
|
||||
case 'i': /* "-i {shada}" use for ShaDa file */
|
||||
case 'u': /* "-u {vimrc}" vim inits file */
|
||||
case 'U': /* "-U {gvimrc}" gvim inits file */
|
||||
case 'W': /* "-W {scriptout}" overwrite */
|
||||
@ -1235,8 +1238,8 @@ static void command_line_scan(mparm_T *parmp)
|
||||
parmp->use_ef = (char_u *)argv[0];
|
||||
break;
|
||||
|
||||
case 'i': /* "-i {viminfo}" use for viminfo */
|
||||
use_viminfo = (char_u *)argv[0];
|
||||
case 'i': /* "-i {shada}" use for shada */
|
||||
used_shada_file = argv[0];
|
||||
break;
|
||||
|
||||
case 's': /* "-s {scriptin}" read from script file */
|
||||
@ -2039,7 +2042,7 @@ static void usage(void)
|
||||
mch_msg(_(" -r, -L List swap files and exit\n"));
|
||||
mch_msg(_(" -r <file> Recover crashed session\n"));
|
||||
mch_msg(_(" -u <nvimrc> Use <nvimrc> instead of the default\n"));
|
||||
mch_msg(_(" -i <nviminfo> Use <nviminfo> instead of the default\n"));
|
||||
mch_msg(_(" -i <shada> Use <shada> instead of the default " SHADA_FILE "\n"));
|
||||
mch_msg(_(" --noplugin Don't load plugin scripts\n"));
|
||||
mch_msg(_(" -o[N] Open N windows (default: one for each file)\n"));
|
||||
mch_msg(_(" -O[N] Like -o but split vertically\n"));
|
||||
|
762
src/nvim/mark.c
762
src/nvim/mark.c
@ -39,7 +39,10 @@
|
||||
#include "nvim/strings.h"
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/os/os.h"
|
||||
#include "nvim/os/time.h"
|
||||
#include "nvim/os/input.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
|
||||
/*
|
||||
* This file contains routines to maintain and manipulate marks.
|
||||
@ -48,12 +51,20 @@
|
||||
/*
|
||||
* If a named file mark's lnum is non-zero, it is valid.
|
||||
* If a named file mark's fnum is non-zero, it is for an existing buffer,
|
||||
* otherwise it is from .viminfo and namedfm[n].fname is the file name.
|
||||
* otherwise it is from .shada and namedfm[n].fname is the file name.
|
||||
* There are marks 'A - 'Z (set by user) and '0 to '9 (set when writing
|
||||
* viminfo).
|
||||
* shada).
|
||||
*/
|
||||
#define EXTRA_MARKS 10 /* marks 0-9 */
|
||||
static xfmark_T namedfm[NMARKS + EXTRA_MARKS]; /* marks with file nr */
|
||||
|
||||
/// Global marks (marks with file number or name)
|
||||
static xfmark_T namedfm[NGLOBALMARKS];
|
||||
|
||||
#define SET_XFMARK(xfmarkp_, mark_, fnum_, fname_) \
|
||||
do { \
|
||||
xfmark_T *const xfmarkp__ = xfmarkp_; \
|
||||
xfmarkp__->fname = fname_; \
|
||||
RESET_FMARK(&(xfmarkp__->fmark), mark_, fnum_); \
|
||||
} while (0)
|
||||
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
@ -68,6 +79,23 @@ int setmark(int c)
|
||||
return setmark_pos(c, &curwin->w_cursor, curbuf->b_fnum);
|
||||
}
|
||||
|
||||
/// Free fmark_T item
|
||||
void free_fmark(fmark_T fm)
|
||||
{
|
||||
if (fm.additional_data != NULL) {
|
||||
api_free_dictionary(*fm.additional_data);
|
||||
free(fm.additional_data);
|
||||
}
|
||||
}
|
||||
|
||||
/// Free xfmark_T item
|
||||
static inline void free_xfmark(xfmark_T fm)
|
||||
{
|
||||
xfree(fm.fname);
|
||||
fm.fname = NULL;
|
||||
free_fmark(fm.fmark);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set named mark "c" to position "pos".
|
||||
* When "c" is upper case use file "fnum".
|
||||
@ -92,7 +120,7 @@ int setmark_pos(int c, pos_T *pos, int fnum)
|
||||
}
|
||||
|
||||
if (c == '"') {
|
||||
curbuf->b_last_cursor = *pos;
|
||||
RESET_FMARK(&curbuf->b_last_cursor, *pos, curbuf->b_fnum);
|
||||
return OK;
|
||||
}
|
||||
|
||||
@ -123,16 +151,14 @@ int setmark_pos(int c, pos_T *pos, int fnum)
|
||||
return FAIL;
|
||||
if (islower(c)) {
|
||||
i = c - 'a';
|
||||
curbuf->b_namedm[i] = *pos;
|
||||
RESET_FMARK(curbuf->b_namedm + i, *pos, curbuf->b_fnum);
|
||||
return OK;
|
||||
}
|
||||
if (isupper(c)) {
|
||||
assert(c >= 'A' && c <= 'Z');
|
||||
i = c - 'A';
|
||||
namedfm[i].fmark.mark = *pos;
|
||||
namedfm[i].fmark.fnum = fnum;
|
||||
xfree(namedfm[i].fname);
|
||||
namedfm[i].fname = NULL;
|
||||
free_xfmark(namedfm[i]);
|
||||
SET_XFMARK(namedfm + i, *pos, fnum, NULL);
|
||||
return OK;
|
||||
}
|
||||
return FAIL;
|
||||
@ -157,16 +183,14 @@ void setpcmark(void)
|
||||
/* If jumplist is full: remove oldest entry */
|
||||
if (++curwin->w_jumplistlen > JUMPLISTSIZE) {
|
||||
curwin->w_jumplistlen = JUMPLISTSIZE;
|
||||
xfree(curwin->w_jumplist[0].fname);
|
||||
free_xfmark(curwin->w_jumplist[0]);
|
||||
for (i = 1; i < JUMPLISTSIZE; ++i)
|
||||
curwin->w_jumplist[i - 1] = curwin->w_jumplist[i];
|
||||
}
|
||||
curwin->w_jumplistidx = curwin->w_jumplistlen;
|
||||
fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1];
|
||||
|
||||
fm->fmark.mark = curwin->w_pcmark;
|
||||
fm->fmark.fnum = curbuf->b_fnum;
|
||||
fm->fname = NULL;
|
||||
SET_XFMARK(fm, curwin->w_pcmark, curbuf->b_fnum, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -302,11 +326,11 @@ pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum)
|
||||
pos_copy = curwin->w_pcmark; /* need to make a copy because */
|
||||
posp = &pos_copy; /* w_pcmark may be changed soon */
|
||||
} else if (c == '"') /* to pos when leaving buffer */
|
||||
posp = &(buf->b_last_cursor);
|
||||
posp = &(buf->b_last_cursor.mark);
|
||||
else if (c == '^') /* to where Insert mode stopped */
|
||||
posp = &(buf->b_last_insert);
|
||||
posp = &(buf->b_last_insert.mark);
|
||||
else if (c == '.') /* to where last change was made */
|
||||
posp = &(buf->b_last_change);
|
||||
posp = &(buf->b_last_change.mark);
|
||||
else if (c == '[') /* to start of previous operator */
|
||||
posp = &(buf->b_op_start);
|
||||
else if (c == ']') /* to end of previous operator */
|
||||
@ -357,7 +381,7 @@ pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum)
|
||||
pos_copy.coladd = 0;
|
||||
}
|
||||
} else if (ASCII_ISLOWER(c)) { /* normal named mark */
|
||||
posp = &(buf->b_namedm[c - 'a']);
|
||||
posp = &(buf->b_namedm[c - 'a'].mark);
|
||||
} else if (ASCII_ISUPPER(c) || ascii_isdigit(c)) { /* named file mark */
|
||||
if (ascii_isdigit(c))
|
||||
c = c - '0' + NMARKS;
|
||||
@ -365,8 +389,9 @@ pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum)
|
||||
c -= 'A';
|
||||
posp = &(namedfm[c].fmark.mark);
|
||||
|
||||
if (namedfm[c].fmark.fnum == 0)
|
||||
if (namedfm[c].fmark.fnum == 0) {
|
||||
fname2fnum(&namedfm[c]);
|
||||
}
|
||||
|
||||
if (fnum != NULL)
|
||||
*fnum = namedfm[c].fmark.fnum;
|
||||
@ -420,15 +445,15 @@ getnextmark (
|
||||
pos.col = MAXCOL;
|
||||
|
||||
for (i = 0; i < NMARKS; i++) {
|
||||
if (curbuf->b_namedm[i].lnum > 0) {
|
||||
if (curbuf->b_namedm[i].mark.lnum > 0) {
|
||||
if (dir == FORWARD) {
|
||||
if ((result == NULL || lt(curbuf->b_namedm[i], *result))
|
||||
&& lt(pos, curbuf->b_namedm[i]))
|
||||
result = &curbuf->b_namedm[i];
|
||||
if ((result == NULL || lt(curbuf->b_namedm[i].mark, *result))
|
||||
&& lt(pos, curbuf->b_namedm[i].mark))
|
||||
result = &curbuf->b_namedm[i].mark;
|
||||
} else {
|
||||
if ((result == NULL || lt(*result, curbuf->b_namedm[i]))
|
||||
&& lt(curbuf->b_namedm[i], pos))
|
||||
result = &curbuf->b_namedm[i];
|
||||
if ((result == NULL || lt(*result, curbuf->b_namedm[i].mark))
|
||||
&& lt(curbuf->b_namedm[i].mark, pos))
|
||||
result = &curbuf->b_namedm[i].mark;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -438,12 +463,12 @@ getnextmark (
|
||||
|
||||
/*
|
||||
* For an xtended filemark: set the fnum from the fname.
|
||||
* This is used for marks obtained from the .viminfo file. It's postponed
|
||||
* This is used for marks obtained from the .shada file. It's postponed
|
||||
* until the mark is used to avoid a long startup delay.
|
||||
*/
|
||||
static void fname2fnum(xfmark_T *fm)
|
||||
{
|
||||
char_u *p;
|
||||
char_u *p;
|
||||
|
||||
if (fm->fname != NULL) {
|
||||
/*
|
||||
@ -475,19 +500,17 @@ static void fname2fnum(xfmark_T *fm)
|
||||
/*
|
||||
* Check all file marks for a name that matches the file name in buf.
|
||||
* May replace the name with an fnum.
|
||||
* Used for marks that come from the .viminfo file.
|
||||
* Used for marks that come from the .shada file.
|
||||
*/
|
||||
void fmarks_check_names(buf_T *buf)
|
||||
{
|
||||
char_u *name;
|
||||
char_u *name = buf->b_ffname;
|
||||
int i;
|
||||
|
||||
if (buf->b_ffname == NULL)
|
||||
return;
|
||||
|
||||
name = home_replace_save(buf, buf->b_ffname);
|
||||
|
||||
for (i = 0; i < NMARKS + EXTRA_MARKS; ++i)
|
||||
for (i = 0; i < NGLOBALMARKS; ++i)
|
||||
fmarks_check_one(&namedfm[i], name, buf);
|
||||
|
||||
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
|
||||
@ -495,8 +518,6 @@ void fmarks_check_names(buf_T *buf)
|
||||
fmarks_check_one(&wp->w_jumplist[i], name, buf);
|
||||
}
|
||||
}
|
||||
|
||||
xfree(name);
|
||||
}
|
||||
|
||||
static void fmarks_check_one(xfmark_T *fm, char_u *name, buf_T *buf)
|
||||
@ -541,23 +562,20 @@ int check_mark(pos_T *pos)
|
||||
*/
|
||||
void clrallmarks(buf_T *buf)
|
||||
{
|
||||
static int i = -1;
|
||||
static bool initialized = false;
|
||||
|
||||
if (i == -1) /* first call ever: initialize */
|
||||
for (i = 0; i < NMARKS + 1; i++) {
|
||||
namedfm[i].fmark.mark.lnum = 0;
|
||||
namedfm[i].fname = NULL;
|
||||
}
|
||||
if (!initialized) {
|
||||
// first call ever: initialize
|
||||
memset(&(namedfm[0]), 0, sizeof(namedfm));
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
for (i = 0; i < NMARKS; i++)
|
||||
buf->b_namedm[i].lnum = 0;
|
||||
memset(&(buf->b_namedm[0]), 0, sizeof(buf->b_namedm));
|
||||
buf->b_op_start.lnum = 0; /* start/end op mark cleared */
|
||||
buf->b_op_end.lnum = 0;
|
||||
buf->b_last_cursor.lnum = 1; /* '" mark cleared */
|
||||
buf->b_last_cursor.col = 0;
|
||||
buf->b_last_cursor.coladd = 0;
|
||||
buf->b_last_insert.lnum = 0; /* '^ mark cleared */
|
||||
buf->b_last_change.lnum = 0; /* '. mark cleared */
|
||||
RESET_FMARK(&buf->b_last_cursor, ((pos_T) {1, 0, 0}), 0); // '" mark
|
||||
CLEAR_FMARK(&buf->b_last_insert); // '^ mark
|
||||
CLEAR_FMARK(&buf->b_last_change); // '. mark
|
||||
buf->b_changelistlen = 0;
|
||||
}
|
||||
|
||||
@ -612,8 +630,8 @@ void do_marks(exarg_T *eap)
|
||||
|
||||
show_one_mark('\'', arg, &curwin->w_pcmark, NULL, TRUE);
|
||||
for (i = 0; i < NMARKS; ++i)
|
||||
show_one_mark(i + 'a', arg, &curbuf->b_namedm[i], NULL, TRUE);
|
||||
for (i = 0; i < NMARKS + EXTRA_MARKS; ++i) {
|
||||
show_one_mark(i + 'a', arg, &curbuf->b_namedm[i].mark, NULL, TRUE);
|
||||
for (i = 0; i < NGLOBALMARKS; ++i) {
|
||||
if (namedfm[i].fmark.fnum != 0)
|
||||
name = fm_getname(&namedfm[i].fmark, 15);
|
||||
else
|
||||
@ -626,11 +644,11 @@ void do_marks(exarg_T *eap)
|
||||
xfree(name);
|
||||
}
|
||||
}
|
||||
show_one_mark('"', arg, &curbuf->b_last_cursor, NULL, TRUE);
|
||||
show_one_mark('"', arg, &curbuf->b_last_cursor.mark, NULL, TRUE);
|
||||
show_one_mark('[', arg, &curbuf->b_op_start, NULL, TRUE);
|
||||
show_one_mark(']', arg, &curbuf->b_op_end, NULL, TRUE);
|
||||
show_one_mark('^', arg, &curbuf->b_last_insert, NULL, TRUE);
|
||||
show_one_mark('.', arg, &curbuf->b_last_change, NULL, TRUE);
|
||||
show_one_mark('^', arg, &curbuf->b_last_insert.mark, NULL, TRUE);
|
||||
show_one_mark('.', arg, &curbuf->b_last_change.mark, NULL, TRUE);
|
||||
show_one_mark('<', arg, &curbuf->b_visual.vi_start, NULL, TRUE);
|
||||
show_one_mark('>', arg, &curbuf->b_visual.vi_end, NULL, TRUE);
|
||||
show_one_mark(-1, arg, NULL, NULL, FALSE);
|
||||
@ -728,7 +746,7 @@ void ex_delmarks(exarg_T *eap)
|
||||
|
||||
for (i = from; i <= to; ++i) {
|
||||
if (lower)
|
||||
curbuf->b_namedm[i - 'a'].lnum = 0;
|
||||
curbuf->b_namedm[i - 'a'].mark.lnum = 0;
|
||||
else {
|
||||
if (digit)
|
||||
n = i - '0' + NMARKS;
|
||||
@ -741,9 +759,9 @@ void ex_delmarks(exarg_T *eap)
|
||||
}
|
||||
} else
|
||||
switch (*p) {
|
||||
case '"': curbuf->b_last_cursor.lnum = 0; break;
|
||||
case '^': curbuf->b_last_insert.lnum = 0; break;
|
||||
case '.': curbuf->b_last_change.lnum = 0; break;
|
||||
case '"': CLEAR_FMARK(&curbuf->b_last_cursor); break;
|
||||
case '^': CLEAR_FMARK(&curbuf->b_last_insert); break;
|
||||
case '.': CLEAR_FMARK(&curbuf->b_last_change); break;
|
||||
case '[': curbuf->b_op_start.lnum = 0; break;
|
||||
case ']': curbuf->b_op_end.lnum = 0; break;
|
||||
case '<': curbuf->b_visual.vi_start.lnum = 0; break;
|
||||
@ -886,24 +904,24 @@ void mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_after)
|
||||
if (!cmdmod.lockmarks) {
|
||||
/* named marks, lower case and upper case */
|
||||
for (i = 0; i < NMARKS; i++) {
|
||||
one_adjust(&(curbuf->b_namedm[i].lnum));
|
||||
one_adjust(&(curbuf->b_namedm[i].mark.lnum));
|
||||
if (namedfm[i].fmark.fnum == fnum)
|
||||
one_adjust_nodel(&(namedfm[i].fmark.mark.lnum));
|
||||
}
|
||||
for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++) {
|
||||
for (i = NMARKS; i < NGLOBALMARKS; i++) {
|
||||
if (namedfm[i].fmark.fnum == fnum)
|
||||
one_adjust_nodel(&(namedfm[i].fmark.mark.lnum));
|
||||
}
|
||||
|
||||
/* last Insert position */
|
||||
one_adjust(&(curbuf->b_last_insert.lnum));
|
||||
one_adjust(&(curbuf->b_last_insert.mark.lnum));
|
||||
|
||||
/* last change position */
|
||||
one_adjust(&(curbuf->b_last_change.lnum));
|
||||
one_adjust(&(curbuf->b_last_change.mark.lnum));
|
||||
|
||||
/* last cursor position, if it was set */
|
||||
if (!equalpos(curbuf->b_last_cursor, initpos))
|
||||
one_adjust(&(curbuf->b_last_cursor.lnum));
|
||||
if (!equalpos(curbuf->b_last_cursor.mark, initpos))
|
||||
one_adjust(&(curbuf->b_last_cursor.mark.lnum));
|
||||
|
||||
|
||||
/* list of change positions */
|
||||
@ -1038,20 +1056,20 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, long lnum_amount, long col_a
|
||||
|
||||
/* named marks, lower case and upper case */
|
||||
for (i = 0; i < NMARKS; i++) {
|
||||
col_adjust(&(curbuf->b_namedm[i]));
|
||||
col_adjust(&(curbuf->b_namedm[i].mark));
|
||||
if (namedfm[i].fmark.fnum == fnum)
|
||||
col_adjust(&(namedfm[i].fmark.mark));
|
||||
}
|
||||
for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++) {
|
||||
for (i = NMARKS; i < NGLOBALMARKS; i++) {
|
||||
if (namedfm[i].fmark.fnum == fnum)
|
||||
col_adjust(&(namedfm[i].fmark.mark));
|
||||
}
|
||||
|
||||
/* last Insert position */
|
||||
col_adjust(&(curbuf->b_last_insert));
|
||||
col_adjust(&(curbuf->b_last_insert.mark));
|
||||
|
||||
/* last change position */
|
||||
col_adjust(&(curbuf->b_last_change));
|
||||
col_adjust(&(curbuf->b_last_change.mark));
|
||||
|
||||
/* list of change positions */
|
||||
for (i = 0; i < curbuf->b_changelistlen; ++i)
|
||||
@ -1150,6 +1168,246 @@ void copy_jumplist(win_T *from, win_T *to)
|
||||
to->w_jumplistidx = from->w_jumplistidx;
|
||||
}
|
||||
|
||||
/// Iterate over jumplist items
|
||||
///
|
||||
/// @warning No jumplist-editing functions must be run while iteration is in
|
||||
/// progress.
|
||||
///
|
||||
/// @param[in] iter Iterator. Pass NULL to start iteration.
|
||||
/// @param[in] win Window for which jump list is processed.
|
||||
/// @param[out] fm Item definition.
|
||||
///
|
||||
/// @return Pointer that needs to be passed to next `mark_jumplist_iter` call or
|
||||
/// NULL if iteration is over.
|
||||
const void *mark_jumplist_iter(const void *const iter, const win_T *const win,
|
||||
xfmark_T *const fm)
|
||||
FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
if (iter == NULL && win->w_jumplistlen == 0) {
|
||||
*fm = (xfmark_T) {{{0, 0, 0}, 0, 0, NULL}, NULL};
|
||||
return NULL;
|
||||
}
|
||||
const xfmark_T *const iter_mark =
|
||||
(iter == NULL
|
||||
? &(win->w_jumplist[win->w_jumplistlen - 1])
|
||||
: (const xfmark_T *const) iter);
|
||||
*fm = *iter_mark;
|
||||
if (iter_mark == &(win->w_jumplist[0])) {
|
||||
return NULL;
|
||||
} else {
|
||||
return iter_mark - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over global marks
|
||||
///
|
||||
/// @warning No mark-editing functions must be run while iteration is in
|
||||
/// progress.
|
||||
///
|
||||
/// @param[in] iter Iterator. Pass NULL to start iteration.
|
||||
/// @param[out] name Mark name.
|
||||
/// @param[out] fm Mark definition.
|
||||
///
|
||||
/// @return Pointer that needs to be passed to next `mark_global_iter` call or
|
||||
/// NULL if iteration is over.
|
||||
const void *mark_global_iter(const void *const iter, char *const name,
|
||||
xfmark_T *const fm)
|
||||
FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
const xfmark_T *iter_mark = (iter == NULL
|
||||
? &(namedfm[0])
|
||||
: (const xfmark_T *const) iter);
|
||||
while (!iter_mark->fmark.mark.lnum
|
||||
&& (size_t) (iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm)) {
|
||||
iter_mark++;
|
||||
}
|
||||
if (!iter_mark->fmark.mark.lnum) {
|
||||
*fm = (xfmark_T) {.fmark = {.mark = {.lnum = 0}}};
|
||||
}
|
||||
size_t iter_off = (size_t) (iter_mark - &(namedfm[0]));
|
||||
*name = (char) (iter_off < NMARKS
|
||||
? 'A' + (char) iter_off
|
||||
: '0' + (char) (iter_off - NMARKS));
|
||||
*fm = *iter_mark;
|
||||
while ((size_t) (++iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm)) {
|
||||
if (iter_mark->fmark.mark.lnum) {
|
||||
return (const void *) iter_mark;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/// Get next mark and its name
|
||||
///
|
||||
/// @param[in] buf Buffer for which next mark is taken.
|
||||
/// @param[in,out] mark_name Pointer to the current mark name. Next mark name
|
||||
/// will be saved at this address as well.
|
||||
///
|
||||
/// Current mark name must either be NUL, '"', '^',
|
||||
/// '.' or 'a' .. 'z'. If it is neither of these
|
||||
/// behaviour is undefined.
|
||||
///
|
||||
/// @return Pointer to the next mark or NULL.
|
||||
static inline const fmark_T *next_buffer_mark(const buf_T *const buf,
|
||||
char *const mark_name)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
switch (*mark_name) {
|
||||
case NUL: {
|
||||
*mark_name = '"';
|
||||
return &(buf->b_last_cursor);
|
||||
}
|
||||
case '"': {
|
||||
*mark_name = '^';
|
||||
return &(buf->b_last_insert);
|
||||
}
|
||||
case '^': {
|
||||
*mark_name = '.';
|
||||
return &(buf->b_last_change);
|
||||
}
|
||||
case '.': {
|
||||
*mark_name = 'a';
|
||||
return &(buf->b_namedm[0]);
|
||||
}
|
||||
case 'z': {
|
||||
return NULL;
|
||||
}
|
||||
default: {
|
||||
(*mark_name)++;
|
||||
return &(buf->b_namedm[*mark_name - 'a']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over buffer marks
|
||||
///
|
||||
/// @warning No mark-editing functions must be run while iteration is in
|
||||
/// progress.
|
||||
///
|
||||
/// @param[in] iter Iterator. Pass NULL to start iteration.
|
||||
/// @param[in] buf Buffer.
|
||||
/// @param[out] name Mark name.
|
||||
/// @param[out] fm Mark definition.
|
||||
///
|
||||
/// @return Pointer that needs to be passed to next `mark_buffer_iter` call or
|
||||
/// NULL if iteration is over.
|
||||
const void *mark_buffer_iter(const void *const iter, const buf_T *const buf,
|
||||
char *const name, fmark_T *const fm)
|
||||
FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
char mark_name = (char) (iter == NULL
|
||||
? NUL
|
||||
: (iter == &(buf->b_last_cursor)
|
||||
? '"'
|
||||
: (iter == &(buf->b_last_insert)
|
||||
? '^'
|
||||
: (iter == &(buf->b_last_change)
|
||||
? '.'
|
||||
: 'a' + (char) ((const fmark_T *)iter
|
||||
- &(buf->b_namedm[0]))))));
|
||||
const fmark_T *iter_mark = next_buffer_mark(buf, &mark_name);
|
||||
while (iter_mark != NULL && iter_mark->mark.lnum == 0) {
|
||||
iter_mark = next_buffer_mark(buf, &mark_name);
|
||||
}
|
||||
if (iter_mark == NULL) {
|
||||
*fm = (fmark_T) {.mark = {.lnum = 0}};
|
||||
return NULL;
|
||||
}
|
||||
size_t iter_off = (size_t) (iter_mark - &(buf->b_namedm[0]));
|
||||
if (mark_name) {
|
||||
*name = mark_name;
|
||||
} else {
|
||||
*name = (char) ('a' + (char) iter_off);
|
||||
}
|
||||
*fm = *iter_mark;
|
||||
return (const void *) iter_mark;
|
||||
}
|
||||
|
||||
/// Get a number of valid marks
|
||||
size_t mark_global_amount(void)
|
||||
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
size_t ret = 0;
|
||||
for (size_t i = 0; i < NGLOBALMARKS; i++) {
|
||||
if (namedfm[i].fmark.mark.lnum != 0) {
|
||||
ret++;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Get a number of valid marks
|
||||
size_t mark_buffer_amount(const buf_T *const buf)
|
||||
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
size_t ret = (size_t) ((buf->b_last_cursor.mark.lnum != 0)
|
||||
+ (buf->b_last_insert.mark.lnum != 0)
|
||||
+ (buf->b_last_change.mark.lnum != 0));
|
||||
for (size_t i = 0; i < NMARKS; i++) {
|
||||
if (buf->b_namedm[i].mark.lnum != 0) {
|
||||
ret++;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Set global mark
|
||||
///
|
||||
/// @param[in] name Mark name.
|
||||
/// @param[in] fm Mark to be set.
|
||||
/// @param[in] update If true then only set global mark if it was created
|
||||
/// later then existing one.
|
||||
void mark_set_global(const char name, const xfmark_T fm, const bool update)
|
||||
{
|
||||
xfmark_T *fm_tgt = NULL;
|
||||
if (ASCII_ISUPPER(name)) {
|
||||
fm_tgt = &(namedfm[name - 'A']);
|
||||
} else if (ascii_isdigit(name)) {
|
||||
fm_tgt = &(namedfm[NMARKS + (name - '0')]);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (update && fm.fmark.timestamp < fm_tgt->fmark.timestamp) {
|
||||
return;
|
||||
}
|
||||
if (fm_tgt->fmark.mark.lnum != 0) {
|
||||
free_xfmark(*fm_tgt);
|
||||
}
|
||||
*fm_tgt = fm;
|
||||
}
|
||||
|
||||
/// Set local mark
|
||||
///
|
||||
/// @param[in] name Mark name.
|
||||
/// @param[in] buf Pointer to the buffer to set mark in.
|
||||
/// @param[in] fm Mark to be set.
|
||||
/// @param[in] update If true then only set global mark if it was created
|
||||
/// later then existing one.
|
||||
void mark_set_local(const char name, buf_T *const buf,
|
||||
const fmark_T fm, const bool update)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
fmark_T *fm_tgt = NULL;
|
||||
if (ASCII_ISLOWER(name)) {
|
||||
fm_tgt = &(buf->b_namedm[name - 'a']);
|
||||
} else if (name == '"') {
|
||||
fm_tgt = &(buf->b_last_cursor);
|
||||
} else if (name == '^') {
|
||||
fm_tgt = &(buf->b_last_insert);
|
||||
} else if (name == '.') {
|
||||
fm_tgt = &(buf->b_last_change);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (update && fm.timestamp < fm_tgt->timestamp) {
|
||||
return;
|
||||
}
|
||||
if (fm_tgt->mark.lnum != 0) {
|
||||
free_fmark(*fm_tgt);
|
||||
}
|
||||
*fm_tgt = fm;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free items in the jumplist of window "wp".
|
||||
*/
|
||||
@ -1157,14 +1415,16 @@ void free_jumplist(win_T *wp)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < wp->w_jumplistlen; ++i)
|
||||
xfree(wp->w_jumplist[i].fname);
|
||||
for (i = 0; i < wp->w_jumplistlen; ++i) {
|
||||
free_xfmark(wp->w_jumplist[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void set_last_cursor(win_T *win)
|
||||
{
|
||||
if (win->w_buffer != NULL)
|
||||
win->w_buffer->b_last_cursor = win->w_cursor;
|
||||
if (win->w_buffer != NULL) {
|
||||
RESET_FMARK(&win->w_buffer->b_last_cursor, win->w_cursor, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(EXITFREE)
|
||||
@ -1172,360 +1432,10 @@ void free_all_marks(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NMARKS + EXTRA_MARKS; i++)
|
||||
if (namedfm[i].fmark.mark.lnum != 0)
|
||||
xfree(namedfm[i].fname);
|
||||
for (i = 0; i < NGLOBALMARKS; i++) {
|
||||
if (namedfm[i].fmark.mark.lnum != 0) {
|
||||
free_xfmark(namedfm[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int read_viminfo_filemark(vir_T *virp, int force)
|
||||
{
|
||||
char_u *str;
|
||||
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 (
|
||||
*str <= 127 &&
|
||||
((*virp->vir_line == '\'' && (ascii_isdigit(*str) || isupper(*str)))
|
||||
|| (*virp->vir_line == '-' && *str == '\''))) {
|
||||
if (*str == '\'') {
|
||||
/* 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 if (ascii_isdigit(*str))
|
||||
fm = &namedfm[*str - '0' + NMARKS];
|
||||
else { // is uppercase
|
||||
assert(*str >= 'A' && *str <= 'Z');
|
||||
fm = &namedfm[*str - 'A'];
|
||||
}
|
||||
if (fm != NULL && (fm->fmark.mark.lnum == 0 || force)) {
|
||||
str = skipwhite(str + 1);
|
||||
fm->fmark.mark.lnum = getdigits_long(&str);
|
||||
str = skipwhite(str);
|
||||
fm->fmark.mark.col = getdigits_int(&str);
|
||||
fm->fmark.mark.coladd = 0;
|
||||
fm->fmark.fnum = 0;
|
||||
str = skipwhite(str);
|
||||
xfree(fm->fname);
|
||||
fm->fname = viminfo_readstring(virp, (int)(str - virp->vir_line),
|
||||
FALSE);
|
||||
}
|
||||
}
|
||||
return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd);
|
||||
}
|
||||
|
||||
void write_viminfo_filemarks(FILE *fp)
|
||||
{
|
||||
int i;
|
||||
char_u *name;
|
||||
buf_T *buf;
|
||||
xfmark_T *fm;
|
||||
|
||||
if (get_viminfo_parameter('f') == 0)
|
||||
return;
|
||||
|
||||
fputs(_("\n# File marks:\n"), fp);
|
||||
|
||||
/*
|
||||
* Find a mark that is the same file and position as the cursor.
|
||||
* That one, or else the last one is deleted.
|
||||
* Move '0 to '1, '1 to '2, etc. until the matching one or '9
|
||||
* Set '0 mark to current cursor position.
|
||||
*/
|
||||
if (curbuf->b_ffname != NULL && !removable(curbuf->b_ffname)) {
|
||||
name = buflist_nr2name(curbuf->b_fnum, TRUE, FALSE);
|
||||
for (i = NMARKS; i < NMARKS + EXTRA_MARKS - 1; ++i)
|
||||
if (namedfm[i].fmark.mark.lnum == curwin->w_cursor.lnum
|
||||
&& (namedfm[i].fname == NULL
|
||||
? namedfm[i].fmark.fnum == curbuf->b_fnum
|
||||
: (name != NULL
|
||||
&& STRCMP(name, namedfm[i].fname) == 0)))
|
||||
break;
|
||||
xfree(name);
|
||||
|
||||
xfree(namedfm[i].fname);
|
||||
for (; i > NMARKS; --i)
|
||||
namedfm[i] = namedfm[i - 1];
|
||||
namedfm[NMARKS].fmark.mark = curwin->w_cursor;
|
||||
namedfm[NMARKS].fmark.fnum = curbuf->b_fnum;
|
||||
namedfm[NMARKS].fname = NULL;
|
||||
}
|
||||
|
||||
/* Write the filemarks '0 - '9 and 'A - 'Z */
|
||||
for (i = 0; i < NMARKS + EXTRA_MARKS; i++)
|
||||
write_one_filemark(fp, &namedfm[i], '\'',
|
||||
i < NMARKS ? i + 'A' : i - NMARKS + '0');
|
||||
|
||||
/* Write the jumplist with -' */
|
||||
fputs(_("\n# Jumplist (newest first):\n"), fp);
|
||||
setpcmark(); /* add current cursor position */
|
||||
cleanup_jumplist();
|
||||
for (fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1];
|
||||
fm >= &curwin->w_jumplist[0]; --fm) {
|
||||
if (fm->fmark.fnum == 0
|
||||
|| ((buf = buflist_findnr(fm->fmark.fnum)) != NULL
|
||||
&& !removable(buf->b_ffname)))
|
||||
write_one_filemark(fp, fm, '-', '\'');
|
||||
}
|
||||
}
|
||||
|
||||
static void write_one_filemark(FILE *fp, xfmark_T *fm, int c1, int c2)
|
||||
{
|
||||
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 %" PRId64 " %" PRId64 " ",
|
||||
c1, c2, (int64_t)fm->fmark.mark.lnum, (int64_t)fm->fmark.mark.col);
|
||||
viminfo_writestring(fp, name);
|
||||
}
|
||||
|
||||
if (fm->fmark.fnum != 0)
|
||||
xfree(name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return TRUE if "name" is on removable media (depending on 'viminfo').
|
||||
*/
|
||||
int removable(char_u *name)
|
||||
{
|
||||
char_u *p;
|
||||
char_u part[51];
|
||||
int retval = FALSE;
|
||||
size_t n;
|
||||
|
||||
name = home_replace_save(NULL, name);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
xfree(name);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Write all the named marks for all buffers.
|
||||
* Return the number of buffers for which marks have been written.
|
||||
*/
|
||||
int write_viminfo_marks(FILE *fp_out)
|
||||
{
|
||||
/*
|
||||
* 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);
|
||||
int count = 0;
|
||||
FOR_ALL_BUFFERS(buf) {
|
||||
/*
|
||||
* Only write something if buffer has been loaded and at least one
|
||||
* mark is set.
|
||||
*/
|
||||
if (buf->b_marks_read) {
|
||||
bool is_mark_set = true;
|
||||
if (buf->b_last_cursor.lnum == 0) {
|
||||
is_mark_set = false;
|
||||
for (int 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 && !removable(buf->b_ffname)) {
|
||||
home_replace(NULL, buf->b_ffname, IObuff, IOSIZE, TRUE);
|
||||
fprintf(fp_out, "\n> ");
|
||||
viminfo_writestring(fp_out, IObuff);
|
||||
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);
|
||||
/* changelist positions are stored oldest first */
|
||||
for (int i = 0; i < buf->b_changelistlen; ++i) {
|
||||
write_one_mark(fp_out, '+', &buf->b_changelist[i]);
|
||||
}
|
||||
for (int i = 0; i < NMARKS; i++) {
|
||||
write_one_mark(fp_out, 'a' + i, &buf->b_namedm[i]);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void write_one_mark(FILE *fp_out, int c, pos_T *pos)
|
||||
{
|
||||
if (pos->lnum != 0)
|
||||
fprintf(fp_out, "\t%c\t%" PRId64 "\t%d\n", c,
|
||||
(int64_t)pos->lnum, (int)pos->col);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle marks in the viminfo file:
|
||||
* fp_out != NULL: copy marks for buffers not in buffer list
|
||||
* 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, int count, 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;
|
||||
list_T *list = NULL;
|
||||
|
||||
name_buf = xmalloc(LSIZE);
|
||||
*name_buf = NUL;
|
||||
|
||||
if (fp_out == NULL && (flags & (VIF_GET_OLDFILES | VIF_FORCEIT))) {
|
||||
list = list_alloc();
|
||||
set_vim_var_list(VV_OLDFILES, list);
|
||||
}
|
||||
|
||||
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 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
p = str + STRLEN(str);
|
||||
while (p != str && (*p == NUL || ascii_isspace(*p)))
|
||||
p--;
|
||||
if (*p)
|
||||
p++;
|
||||
*p = NUL;
|
||||
|
||||
if (list != NULL)
|
||||
list_append_string(list, str, -1);
|
||||
|
||||
/*
|
||||
* 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 ((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 { /* fp_out != NULL */
|
||||
/* This is slow if there are many buffers!! */
|
||||
buf = NULL;
|
||||
FOR_ALL_BUFFERS(bp) {
|
||||
if (bp->b_ffname != NULL) {
|
||||
home_replace(NULL, bp->b_ffname, name_buf, LSIZE, TRUE);
|
||||
if (fnamecmp(str, name_buf) == 0) {
|
||||
buf = bp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* copy marks if the buffer has not been loaded
|
||||
*/
|
||||
if (buf == NULL || !buf->b_marks_read) {
|
||||
copy_marks_out = TRUE;
|
||||
fputs("\n> ", fp_out);
|
||||
viminfo_writestring(fp_out, str);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
xfree(str);
|
||||
|
||||
pos.coladd = 0;
|
||||
while (!(eof = viminfo_readline(virp)) && line[0] == TAB) {
|
||||
if (load_marks) {
|
||||
if (line[1] != NUL) {
|
||||
int64_t lnum_64;
|
||||
unsigned int u;
|
||||
sscanf((char *)line + 2, "%" SCNd64 "%u", &lnum_64, &u);
|
||||
// safely downcast to linenr_T (long); remove when linenr_T refactored
|
||||
assert(lnum_64 <= LONG_MAX);
|
||||
pos.lnum = (linenr_T)lnum_64;
|
||||
assert(u <= INT_MAX);
|
||||
pos.col = (colnr_T)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 '+':
|
||||
/* changelist positions are stored oldest
|
||||
* first */
|
||||
if (curbuf->b_changelistlen == JUMPLISTSIZE)
|
||||
/* list is full, remove oldest entry */
|
||||
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;
|
||||
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);
|
||||
}
|
||||
if (load_marks) {
|
||||
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
|
||||
if (wp->w_buffer == curbuf)
|
||||
wp->w_changelistidx = curbuf->b_changelistlen;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
xfree(name_buf);
|
||||
}
|
||||
|
@ -4,6 +4,22 @@
|
||||
#include "nvim/buffer_defs.h"
|
||||
#include "nvim/mark_defs.h"
|
||||
#include "nvim/pos.h"
|
||||
#include "nvim/os/time.h"
|
||||
|
||||
/// Free and set fmark using given value
|
||||
#define RESET_FMARK(fmarkp_, mark_, fnum_) \
|
||||
do { \
|
||||
fmark_T *const fmarkp__ = fmarkp_; \
|
||||
free_fmark(*fmarkp__); \
|
||||
fmarkp__->mark = mark_; \
|
||||
fmarkp__->fnum = fnum_; \
|
||||
fmarkp__->timestamp = os_time(); \
|
||||
fmarkp__->additional_data = NULL; \
|
||||
} while (0)
|
||||
|
||||
/// Clear given fmark
|
||||
#define CLEAR_FMARK(fmarkp_) \
|
||||
RESET_FMARK(fmarkp_, ((pos_T) {0, 0, 0}), 0)
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "mark.h.generated.h"
|
||||
|
@ -2,25 +2,41 @@
|
||||
#define NVIM_MARK_DEFS_H
|
||||
|
||||
#include "nvim/pos.h"
|
||||
#include "nvim/os/time.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
|
||||
/*
|
||||
* marks: positions in a file
|
||||
* (a normal mark is a lnum/col pair, the same as a file position)
|
||||
*/
|
||||
|
||||
#define NMARKS ('z' - 'a' + 1) /* max. # of named marks */
|
||||
#define JUMPLISTSIZE 100 /* max. # of marks in jump list */
|
||||
#define TAGSTACKSIZE 20 /* max. # of tags in tag stack */
|
||||
/// Number of possible numbered global marks
|
||||
#define EXTRA_MARKS ('9' - '0' + 1)
|
||||
|
||||
/// Maximum possible number of letter marks
|
||||
#define NMARKS ('z' - 'a' + 1)
|
||||
|
||||
/// Total possible number of global marks
|
||||
#define NGLOBALMARKS (NMARKS + EXTRA_MARKS)
|
||||
|
||||
/// Maximum number of marks in jump list
|
||||
#define JUMPLISTSIZE 100
|
||||
|
||||
/// Maximum number of tags in tag stack
|
||||
#define TAGSTACKSIZE 20
|
||||
|
||||
/// Structure defining single local mark
|
||||
typedef struct filemark {
|
||||
pos_T mark; /* cursor position */
|
||||
int fnum; /* file number */
|
||||
pos_T mark; ///< Cursor position.
|
||||
int fnum; ///< File number.
|
||||
Timestamp timestamp; ///< Time when this mark was last set.
|
||||
Dictionary *additional_data; ///< Additional data from ShaDa file.
|
||||
} fmark_T;
|
||||
|
||||
/* Xtended file mark: also has a file name */
|
||||
/// Structure defining extended mark (mark with file name attached)
|
||||
typedef struct xfilemark {
|
||||
fmark_T fmark;
|
||||
char_u *fname; /* file name, used when fnum == 0 */
|
||||
fmark_T fmark; ///< Actual mark.
|
||||
char_u *fname; ///< File name, used when fnum == 0.
|
||||
} xfmark_T;
|
||||
|
||||
#endif // NVIM_MARK_DEFS_H
|
||||
|
@ -66,7 +66,7 @@
|
||||
* (4) The encoding of the file is specified with 'fileencoding'. Conversion
|
||||
* is to be done when it's different from 'encoding'.
|
||||
*
|
||||
* The viminfo file is a special case: Only text is converted, not file names.
|
||||
* The ShaDa file is a special case: Only text is converted, not file names.
|
||||
* Vim scripts may contain an ":encoding" command. This has an effect for
|
||||
* some commands, like ":menutrans"
|
||||
*/
|
||||
|
@ -2042,8 +2042,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra
|
||||
|
||||
/* set the '. mark */
|
||||
if (!cmdmod.keepjumps) {
|
||||
curbuf->b_last_change.lnum = lnum;
|
||||
curbuf->b_last_change.col = col;
|
||||
RESET_FMARK(&curbuf->b_last_change, ((pos_T) {lnum, col, 0}), 0);
|
||||
|
||||
/* Create a new entry if a new undo-able change was started or we
|
||||
* don't have an entry yet. */
|
||||
@ -2095,7 +2094,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra
|
||||
}
|
||||
}
|
||||
curbuf->b_changelist[curbuf->b_changelistlen - 1] =
|
||||
curbuf->b_last_change;
|
||||
curbuf->b_last_change.mark;
|
||||
/* The current window is always after the last change, so that "g,"
|
||||
* takes you back to it. */
|
||||
curwin->w_changelistidx = curbuf->b_changelistlen;
|
||||
|
@ -6327,8 +6327,8 @@ static void nv_g_cmd(cmdarg_T *cap)
|
||||
* "gi": start Insert at the last position.
|
||||
*/
|
||||
case 'i':
|
||||
if (curbuf->b_last_insert.lnum != 0) {
|
||||
curwin->w_cursor = curbuf->b_last_insert;
|
||||
if (curbuf->b_last_insert.mark.lnum != 0) {
|
||||
curwin->w_cursor = curbuf->b_last_insert.mark;
|
||||
check_cursor_lnum();
|
||||
i = (int)STRLEN(get_cursor_line_ptr());
|
||||
if (curwin->w_cursor.col > (colnr_T)i) {
|
||||
|
315
src/nvim/ops.c
315
src/nvim/ops.c
@ -50,6 +50,9 @@
|
||||
#include "nvim/undo.h"
|
||||
#include "nvim/window.h"
|
||||
#include "nvim/os/input.h"
|
||||
#include "nvim/os/time.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
|
||||
/*
|
||||
* Registers:
|
||||
@ -62,14 +65,14 @@
|
||||
*/
|
||||
#define DELETION_REGISTER 36
|
||||
#define NUM_SAVED_REGISTERS 37
|
||||
// The following registers should not be saved in viminfo:
|
||||
// The following registers should not be saved in ShaDa file:
|
||||
#define STAR_REGISTER 37
|
||||
#define PLUS_REGISTER 38
|
||||
#define NUM_REGISTERS 39
|
||||
|
||||
static yankreg_T y_regs[NUM_REGISTERS];
|
||||
|
||||
static yankreg_T *y_previous = NULL; /* ptr to last written yankreg */
|
||||
static yankreg_T *y_previous = NULL; /* ptr to last written yankreg */
|
||||
|
||||
static bool clipboard_didwarn_unnamed = false;
|
||||
|
||||
@ -746,6 +749,31 @@ typedef enum {
|
||||
YREG_PUT,
|
||||
} yreg_mode_t;
|
||||
|
||||
/// Convert register name into register index
|
||||
///
|
||||
/// @param[in] regname Register name.
|
||||
///
|
||||
/// @return Index in y_regs array or -1 if register name was not recognized.
|
||||
static inline int reg_index(const int regname)
|
||||
FUNC_ATTR_CONST
|
||||
{
|
||||
if (ascii_isdigit(regname)) {
|
||||
return regname - '0';
|
||||
} else if (ASCII_ISLOWER(regname)) {
|
||||
return CharOrdLow(regname) + 10;
|
||||
} else if (ASCII_ISUPPER(regname)) {
|
||||
return CharOrdUp(regname) + 10;
|
||||
} else if (regname == '-') {
|
||||
return DELETION_REGISTER;
|
||||
} else if (regname == '*') {
|
||||
return STAR_REGISTER;
|
||||
} else if (regname == '+') {
|
||||
return PLUS_REGISTER;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Return yankreg_T to use, according to the value of `regname`.
|
||||
/// Cannot handle the '_' (black hole) register.
|
||||
/// Must only be called with a valid register name!
|
||||
@ -778,19 +806,11 @@ yankreg_T *get_yank_register(int regname, int mode)
|
||||
return y_previous;
|
||||
}
|
||||
|
||||
int i = 0; // when not 0-9, a-z, A-Z or '-'/'+'/'*': use register 0
|
||||
if (ascii_isdigit(regname))
|
||||
i = regname - '0';
|
||||
else if (ASCII_ISLOWER(regname))
|
||||
i = CharOrdLow(regname) + 10;
|
||||
else if (ASCII_ISUPPER(regname)) {
|
||||
i = CharOrdUp(regname) + 10;
|
||||
} else if (regname == '-')
|
||||
i = DELETION_REGISTER;
|
||||
else if (regname == '*')
|
||||
i = STAR_REGISTER;
|
||||
else if (regname == '+')
|
||||
i = PLUS_REGISTER;
|
||||
int i = reg_index(regname);
|
||||
// when not 0-9, a-z, A-Z or '-'/'+'/'*': use register 0
|
||||
if (i == -1) {
|
||||
i = 0;
|
||||
}
|
||||
reg = &y_regs[i];
|
||||
|
||||
if (mode == YREG_YANK) {
|
||||
@ -890,6 +910,20 @@ int do_record(int c)
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void set_yreg_additional_data(yankreg_T *reg,
|
||||
Dictionary *additional_data)
|
||||
FUNC_ATTR_NONNULL_ARG(1)
|
||||
{
|
||||
if (reg->additional_data == additional_data) {
|
||||
return;
|
||||
}
|
||||
if (reg->additional_data != NULL) {
|
||||
api_free_dictionary(*reg->additional_data);
|
||||
free(reg->additional_data);
|
||||
}
|
||||
reg->additional_data = additional_data;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stuff string "p" into yank register "regname" as a single line (append if
|
||||
* uppercase). "p" must have been alloced.
|
||||
@ -919,11 +953,13 @@ static int stuff_yank(int regname, char_u *p)
|
||||
*pp = lp;
|
||||
} else {
|
||||
free_register(reg);
|
||||
set_yreg_additional_data(reg, NULL);
|
||||
reg->y_array = (char_u **)xmalloc(sizeof(char_u *));
|
||||
reg->y_array[0] = p;
|
||||
reg->y_size = 1;
|
||||
reg->y_type = MCHAR; /* used to be MLINE, why? */
|
||||
}
|
||||
reg->timestamp = os_time();
|
||||
return OK;
|
||||
}
|
||||
|
||||
@ -2266,10 +2302,7 @@ int op_change(oparg_T *oap)
|
||||
*/
|
||||
void init_yank(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_REGISTERS; i++)
|
||||
y_regs[i].y_array = NULL;
|
||||
memset(&(y_regs[0]), 0, sizeof(y_regs));
|
||||
}
|
||||
|
||||
#if defined(EXITFREE)
|
||||
@ -2291,6 +2324,7 @@ void clear_registers(void)
|
||||
void free_register(yankreg_T *reg)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
set_yreg_additional_data(reg, NULL);
|
||||
if (reg->y_array != NULL) {
|
||||
long i;
|
||||
|
||||
@ -2369,6 +2403,8 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
|
||||
reg->y_type = yanktype; /* set the yank register type */
|
||||
reg->y_width = 0;
|
||||
reg->y_array = xcalloc(yanklines, sizeof(char_u *));
|
||||
set_yreg_additional_data(reg, NULL);
|
||||
reg->timestamp = os_time();
|
||||
|
||||
y_idx = 0;
|
||||
lnum = oap->start.lnum;
|
||||
@ -4433,171 +4469,6 @@ int do_addsub(int command, linenr_T Prenum1)
|
||||
return OK;
|
||||
}
|
||||
|
||||
int read_viminfo_register(vir_T *virp, int force)
|
||||
{
|
||||
int eof;
|
||||
int do_it = TRUE;
|
||||
int size;
|
||||
int limit;
|
||||
int set_prev = FALSE;
|
||||
char_u *str;
|
||||
char_u **array = NULL;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
yankreg_T *reg = get_yank_register(*str++, YREG_PUT);
|
||||
if (!force && reg->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) {
|
||||
if (set_prev) {
|
||||
y_previous = reg;
|
||||
}
|
||||
|
||||
free_register(reg);
|
||||
array = xmalloc(limit * sizeof(char_u *));
|
||||
|
||||
str = skipwhite(skiptowhite(str));
|
||||
if (STRNCMP(str, "CHAR", 4) == 0) {
|
||||
reg->y_type = MCHAR;
|
||||
} else if (STRNCMP(str, "BLOCK", 5) == 0) {
|
||||
reg->y_type = MBLOCK;
|
||||
} else {
|
||||
reg->y_type = MLINE;
|
||||
}
|
||||
/* get the block width; if it's missing we get a zero, which is OK */
|
||||
str = skipwhite(skiptowhite(str));
|
||||
reg->y_width = getdigits_int(&str);
|
||||
}
|
||||
|
||||
while (!(eof = viminfo_readline(virp))
|
||||
&& (virp->vir_line[0] == TAB || virp->vir_line[0] == '<')) {
|
||||
if (do_it) {
|
||||
if (size >= limit) {
|
||||
limit *= 2;
|
||||
array = xrealloc(array, limit * sizeof(char_u *));
|
||||
}
|
||||
array[size++] = viminfo_readstring(virp, 1, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
if (do_it) {
|
||||
if (size == 0) {
|
||||
xfree(array);
|
||||
} else if (size < limit) {
|
||||
reg->y_array = xrealloc(array, size * sizeof(char_u *));
|
||||
} else {
|
||||
reg->y_array = array;
|
||||
}
|
||||
reg->y_size = size;
|
||||
}
|
||||
return eof;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
// don't include clipboard registers '*'/'+'
|
||||
for (i = 0; i < NUM_SAVED_REGISTERS; i++) {
|
||||
if (y_regs[i].y_array == NULL)
|
||||
continue;
|
||||
|
||||
/* Skip empty registers. */
|
||||
num_lines = y_regs[i].y_size;
|
||||
if (num_lines == 0
|
||||
|| (num_lines == 1 && y_regs[i].y_type == MCHAR
|
||||
&& *y_regs[i].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_regs[i].y_array[j]) + 1L;
|
||||
if (len > (long)max_kbyte * 1024L)
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (y_regs[i].y_type) {
|
||||
case MLINE:
|
||||
type = (char_u *)"LINE";
|
||||
break;
|
||||
case MCHAR:
|
||||
type = (char_u *)"CHAR";
|
||||
break;
|
||||
case MBLOCK:
|
||||
type = (char_u *)"BLOCK";
|
||||
break;
|
||||
default:
|
||||
sprintf((char *)IObuff, _("E574: Unknown register type %d"),
|
||||
y_regs[i].y_type);
|
||||
emsg(IObuff);
|
||||
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_regs[i].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_regs[i].y_array[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Return the type of a register.
|
||||
* Used for getregtype()
|
||||
@ -4739,7 +4610,6 @@ void *get_reg_contents(int regname, int flags)
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
static yankreg_T *init_write_reg(int name, yankreg_T **old_y_previous, bool must_append)
|
||||
{
|
||||
if (!valid_yank_reg(name, true)) { // check for valid reg name
|
||||
@ -4973,6 +4843,8 @@ static void str_to_reg(yankreg_T *y_ptr, int yank_type, const char_u *str,
|
||||
}
|
||||
y_ptr->y_type = type;
|
||||
y_ptr->y_size = lnum;
|
||||
set_yreg_additional_data(y_ptr, NULL);
|
||||
y_ptr->timestamp = os_time();
|
||||
if (type == MBLOCK) {
|
||||
y_ptr->y_width = (blocklen == -1 ? (colnr_T) maxlen - 1 : blocklen);
|
||||
} else {
|
||||
@ -5363,6 +5235,10 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet)
|
||||
|
||||
reg->y_array = xcalloc(lines->lv_len, sizeof(uint8_t *));
|
||||
reg->y_size = lines->lv_len;
|
||||
reg->additional_data = NULL;
|
||||
reg->timestamp = 0;
|
||||
// Timestamp is not saved for clipboard registers because clipboard registers
|
||||
// are not saved in the viminfo.
|
||||
|
||||
int i = 0;
|
||||
for (listitem_T *li = lines->lv_first; li != NULL; li = li->li_next) {
|
||||
@ -5411,6 +5287,8 @@ err:
|
||||
}
|
||||
reg->y_array = NULL;
|
||||
reg->y_size = 0;
|
||||
reg->additional_data = NULL;
|
||||
reg->timestamp = 0;
|
||||
if (errmsg) {
|
||||
EMSG("clipboard: provider returned invalid data");
|
||||
}
|
||||
@ -5478,3 +5356,70 @@ void end_global_changes(void)
|
||||
clipboard_needs_update = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether register is empty
|
||||
static inline bool reg_empty(const yankreg_T *const reg)
|
||||
FUNC_ATTR_CONST
|
||||
{
|
||||
return (reg->y_array == NULL
|
||||
|| reg->y_size == 0
|
||||
|| (reg->y_size == 1
|
||||
&& reg->y_type == MCHAR
|
||||
&& *(reg->y_array[0]) == NUL));
|
||||
}
|
||||
|
||||
/// Iterate over registerrs
|
||||
///
|
||||
/// @param[in] iter Iterator. Pass NULL to start iteration.
|
||||
/// @param[out] name Register name.
|
||||
/// @param[out] reg Register contents.
|
||||
///
|
||||
/// @return Pointer that needs to be passed to next `op_register_iter` call or
|
||||
/// NULL if iteration is over.
|
||||
const void *op_register_iter(const void *const iter, char *const name,
|
||||
yankreg_T *const reg)
|
||||
FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
const yankreg_T *iter_reg = (iter == NULL
|
||||
? &(y_regs[0])
|
||||
: (const yankreg_T *const) iter);
|
||||
while (reg_empty(iter_reg) && iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS) {
|
||||
iter_reg++;
|
||||
}
|
||||
if (reg_empty(iter_reg)) {
|
||||
*reg = (yankreg_T) {.y_array = NULL};
|
||||
return NULL;
|
||||
}
|
||||
size_t iter_off = iter_reg - &(y_regs[0]);
|
||||
*name = (char) get_register_name(iter_off);
|
||||
*reg = *iter_reg;
|
||||
while (++iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS) {
|
||||
if (!reg_empty(iter_reg)) {
|
||||
return (void *) iter_reg;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/// Get a number of non-empty registers
|
||||
size_t op_register_amount(void)
|
||||
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
size_t ret = 0;
|
||||
for (size_t i = 0; i < NUM_SAVED_REGISTERS; i++) {
|
||||
if (!reg_empty(y_regs + i)) {
|
||||
ret++;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Set register to a given value
|
||||
void register_set(const char name, const yankreg_T reg)
|
||||
{
|
||||
int i = reg_index(name);
|
||||
if (i == -1) {
|
||||
return;
|
||||
}
|
||||
y_regs[i] = reg;
|
||||
}
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "nvim/types.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/os/time.h"
|
||||
|
||||
typedef int (*Indenter)(void);
|
||||
|
||||
@ -47,14 +49,6 @@ typedef int (*Indenter)(void);
|
||||
#define OP_FORMAT2 26 /* "gw" format operator, keeps cursor pos */
|
||||
#define OP_FUNCTION 27 /* "g@" call 'operatorfunc' */
|
||||
|
||||
/// Contents of a yank (read-write) register
|
||||
typedef struct yankreg {
|
||||
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
|
||||
} yankreg_T;
|
||||
|
||||
/// Flags for get_reg_contents().
|
||||
enum GRegFlags {
|
||||
kGRegNoExpr = 1, ///< Do not allow expression register.
|
||||
@ -62,6 +56,16 @@ enum GRegFlags {
|
||||
kGRegList = 4 ///< Return list.
|
||||
};
|
||||
|
||||
/// Definition of one register
|
||||
typedef struct yankreg {
|
||||
char_u **y_array; ///< Pointer to an array of line pointers.
|
||||
linenr_T y_size; ///< Number of lines in y_array.
|
||||
char_u y_type; ///< Register type: MLINE, MCHAR or MBLOCK.
|
||||
colnr_T y_width; ///< Register width (only valid for y_type == MBLOCK).
|
||||
Timestamp timestamp; ///< Time when register was last modified.
|
||||
Dictionary *additional_data; ///< Additional data from ShaDa file.
|
||||
} yankreg_T;
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "ops.h.generated.h"
|
||||
#endif
|
||||
|
@ -103,3 +103,12 @@ struct tm *os_get_localtime(struct tm *result) FUNC_ATTR_NONNULL_ALL
|
||||
time_t rawtime = time(NULL);
|
||||
return os_localtime_r(&rawtime, result);
|
||||
}
|
||||
|
||||
/// Obtains the current UNIX timestamp
|
||||
///
|
||||
/// @return Seconds since epoch.
|
||||
Timestamp os_time(void)
|
||||
FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
return (Timestamp) time(NULL);
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
|
||||
typedef time_t Timestamp;
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "os/time.h.generated.h"
|
||||
#endif
|
||||
|
@ -43,8 +43,8 @@
|
||||
#ifndef VIMRC_FILE
|
||||
# define VIMRC_FILE ".nvimrc"
|
||||
#endif
|
||||
#ifndef VIMINFO_FILE
|
||||
# define VIMINFO_FILE "~/.nviminfo"
|
||||
#ifndef SHADA_FILE
|
||||
# define SHADA_FILE "~/.nvim/shada/main.shada"
|
||||
#endif
|
||||
|
||||
// Default for 'backupdir'.
|
||||
|
@ -9,7 +9,7 @@
|
||||
// Defines needed to fix the build on Windows:
|
||||
// - USR_EXRC_FILE
|
||||
// - USR_VIMRC_FILE
|
||||
// - VIMINFO_FILE
|
||||
// - SHADA_FILE
|
||||
// - DFLT_DIR
|
||||
// - DFLT_BDIR
|
||||
// - DFLT_VDIR
|
||||
|
@ -51,6 +51,7 @@
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/window.h"
|
||||
#include "nvim/os/time.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
@ -79,23 +80,6 @@
|
||||
* 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
|
||||
@ -103,8 +87,10 @@ struct spat {
|
||||
*/
|
||||
static struct spat spats[2] =
|
||||
{
|
||||
{NULL, TRUE, FALSE, {'/', 0, 0, 0L}}, /* last used search pat */
|
||||
{NULL, TRUE, FALSE, {'/', 0, 0, 0L}} /* last used substitute pat */
|
||||
// Last used search pattern
|
||||
[0] = {NULL, true, false, 0, {'/', false, false, 0L}, NULL},
|
||||
// Last used substitute pattern
|
||||
[1] = {NULL, true, false, 0, {'/', false, false, 0L}, NULL}
|
||||
};
|
||||
|
||||
static int last_idx = 0; /* index in spats[] for RE_LAST */
|
||||
@ -256,10 +242,12 @@ char_u *reverse_text(char_u *s) FUNC_ATTR_NONNULL_RET
|
||||
void save_re_pat(int idx, char_u *pat, int magic)
|
||||
{
|
||||
if (spats[idx].pat != pat) {
|
||||
xfree(spats[idx].pat);
|
||||
free_spat(&spats[idx]);
|
||||
spats[idx].pat = vim_strsave(pat);
|
||||
spats[idx].magic = magic;
|
||||
spats[idx].no_scs = no_smartcase;
|
||||
spats[idx].timestamp = os_time();
|
||||
spats[idx].additional_data = NULL;
|
||||
last_idx = idx;
|
||||
/* If 'hlsearch' set and search pat changed: need redraw. */
|
||||
if (p_hls)
|
||||
@ -291,21 +279,30 @@ void save_search_patterns(void)
|
||||
void restore_search_patterns(void)
|
||||
{
|
||||
if (--save_level == 0) {
|
||||
xfree(spats[0].pat);
|
||||
free_spat(&spats[0]);
|
||||
spats[0] = saved_spats[0];
|
||||
set_vv_searchforward();
|
||||
xfree(spats[1].pat);
|
||||
free_spat(&spats[1]);
|
||||
spats[1] = saved_spats[1];
|
||||
last_idx = saved_last_idx;
|
||||
SET_NO_HLSEARCH(saved_no_hlsearch);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void free_spat(struct spat *const spat)
|
||||
{
|
||||
xfree(spat->pat);
|
||||
if (spat->additional_data != NULL) {
|
||||
api_free_dictionary(*spat->additional_data);
|
||||
xfree(spat->additional_data);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(EXITFREE)
|
||||
void free_search_patterns(void)
|
||||
{
|
||||
xfree(spats[0].pat);
|
||||
xfree(spats[1].pat);
|
||||
free_spat(&spats[0]);
|
||||
free_spat(&spats[1]);
|
||||
|
||||
if (mr_pattern_alloced) {
|
||||
xfree(mr_pattern);
|
||||
@ -414,17 +411,19 @@ void reset_search_dir(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the last search pattern. For ":let @/ =" and viminfo.
|
||||
* Set the last search pattern. For ":let @/ =" and ShaDa file.
|
||||
* Also set the saved search pattern, so that this works in an autocommand.
|
||||
*/
|
||||
void set_last_search_pat(const char_u *s, int idx, int magic, int setlast)
|
||||
{
|
||||
xfree(spats[idx].pat);
|
||||
free_spat(&spats[idx]);
|
||||
/* An empty string means that nothing should be matched. */
|
||||
if (*s == NUL)
|
||||
spats[idx].pat = NULL;
|
||||
else
|
||||
spats[idx].pat = (char_u *) xstrdup((char *) s);
|
||||
spats[idx].timestamp = os_time();
|
||||
spats[idx].additional_data = NULL;
|
||||
spats[idx].magic = magic;
|
||||
spats[idx].no_scs = FALSE;
|
||||
spats[idx].off.dir = '/';
|
||||
@ -435,7 +434,7 @@ void set_last_search_pat(const char_u *s, int idx, int magic, int setlast)
|
||||
if (setlast)
|
||||
last_idx = idx;
|
||||
if (save_level) {
|
||||
xfree(saved_spats[idx].pat);
|
||||
free_spat(&saved_spats[idx]);
|
||||
saved_spats[idx] = spats[0];
|
||||
if (spats[idx].pat == NULL)
|
||||
saved_spats[idx].pat = NULL;
|
||||
@ -1053,7 +1052,7 @@ int do_search(
|
||||
else if ((options & SEARCH_OPT) &&
|
||||
(*p == 'e' || *p == 's' || *p == 'b')) {
|
||||
if (*p == 'e') /* end */
|
||||
spats[0].off.end = SEARCH_END;
|
||||
spats[0].off.end = true;
|
||||
++p;
|
||||
}
|
||||
if (ascii_isdigit(*p) || *p == '+' || *p == '-') { /* got an offset */
|
||||
@ -1166,12 +1165,13 @@ int do_search(
|
||||
lrFswap(searchstr,0);
|
||||
|
||||
c = searchit(curwin, curbuf, &pos, dirc == '/' ? FORWARD : BACKWARD,
|
||||
searchstr, count, spats[0].off.end + (options &
|
||||
(SEARCH_KEEP + SEARCH_PEEK +
|
||||
SEARCH_HIS
|
||||
+ SEARCH_MSG + SEARCH_START
|
||||
+ ((pat != NULL && *pat ==
|
||||
';') ? 0 : SEARCH_NOOF))),
|
||||
searchstr, count, (spats[0].off.end * SEARCH_END
|
||||
+ (options &
|
||||
(SEARCH_KEEP + SEARCH_PEEK +
|
||||
SEARCH_HIS
|
||||
+ SEARCH_MSG + SEARCH_START
|
||||
+ ((pat != NULL && *pat ==
|
||||
';') ? 0 : SEARCH_NOOF)))),
|
||||
RE_LAST, (linenr_T)0, tm);
|
||||
|
||||
if (dircp != NULL)
|
||||
@ -4605,105 +4605,45 @@ static void show_pat_in_path(char_u *line, int type, int did_show, int action, F
|
||||
}
|
||||
}
|
||||
|
||||
int read_viminfo_search_pattern(vir_T *virp, int force)
|
||||
/// Get last search pattern
|
||||
void get_search_pattern(SearchPattern *const pat)
|
||||
{
|
||||
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;
|
||||
static int hlsearch_on = FALSE;
|
||||
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
|
||||
* "~<magic><smartcase><line><end><off><last><which>pat"
|
||||
* <magic>: 'm' off, 'M' on
|
||||
* <smartcase>: 's' off, 'S' on
|
||||
* <line>: 'L' line offset, 'l' char offset
|
||||
* <end>: 'E' from end, 'e' from start
|
||||
* <off>: decimal, offset
|
||||
* <last>: '~' last used pattern
|
||||
* <which>: '/' 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_long(&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;
|
||||
else if (lp[0] == 'h') /* ~h: 'hlsearch' highlighting off */
|
||||
hlsearch_on = FALSE;
|
||||
else if (lp[0] == 'H') /* ~H: 'hlsearch' highlighting on */
|
||||
hlsearch_on = TRUE;
|
||||
if (idx >= 0) {
|
||||
if (force || spats[idx].pat == NULL) {
|
||||
val = viminfo_readstring(virp, (int)(lp - virp->vir_line + 1), TRUE);
|
||||
set_last_search_pat(val, idx, magic, setlast);
|
||||
xfree(val);
|
||||
spats[idx].no_scs = no_scs;
|
||||
spats[idx].off.line = off_line;
|
||||
spats[idx].off.end = off_end;
|
||||
spats[idx].off.off = off;
|
||||
if (setlast) {
|
||||
SET_NO_HLSEARCH(!hlsearch_on);
|
||||
}
|
||||
}
|
||||
}
|
||||
return viminfo_readline(virp);
|
||||
memcpy(pat, &(spats[0]), sizeof(spats[0]));
|
||||
}
|
||||
|
||||
void write_viminfo_search_pattern(FILE *fp)
|
||||
/// Get last substitute pattern
|
||||
void get_substitute_pattern(SearchPattern *const pat)
|
||||
{
|
||||
if (get_viminfo_parameter('/') != 0) {
|
||||
fprintf(fp, "\n# hlsearch on (H) or off (h):\n~%c",
|
||||
(no_hlsearch || find_viminfo_parameter('h') != NULL) ? 'h' : 'H');
|
||||
wvsp_one(fp, RE_SEARCH, "", '/');
|
||||
wvsp_one(fp, RE_SUBST, _("Substitute "), '&');
|
||||
}
|
||||
memcpy(pat, &(spats[1]), sizeof(spats[1]));
|
||||
memset(&(pat->off), 0, sizeof(pat->off));
|
||||
}
|
||||
|
||||
static void
|
||||
wvsp_one (
|
||||
FILE *fp, /* file to write to */
|
||||
int idx, /* spats[] index */
|
||||
char *s, /* search pat */
|
||||
int sc /* dir char */
|
||||
)
|
||||
/// Set last search pattern
|
||||
void set_search_pattern(const SearchPattern pat)
|
||||
{
|
||||
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%" PRId64 "%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 */
|
||||
(int64_t)spats[idx].off.off, /* offset */
|
||||
last_idx == idx ? "~" : "", /* last used pat */
|
||||
sc);
|
||||
viminfo_writestring(fp, spats[idx].pat);
|
||||
}
|
||||
free_spat(&spats[0]);
|
||||
memcpy(&(spats[0]), &pat, sizeof(spats[0]));
|
||||
}
|
||||
|
||||
/// Set last substitute pattern
|
||||
void set_substitute_pattern(const SearchPattern pat)
|
||||
{
|
||||
free_spat(&spats[1]);
|
||||
memcpy(&(spats[1]), &pat, sizeof(spats[1]));
|
||||
memset(&(spats[1].off), 0, sizeof(spats[1].off));
|
||||
}
|
||||
|
||||
/// Set last used search pattern
|
||||
///
|
||||
/// @param[in] is_substitute_pattern If true set substitute pattern as last
|
||||
/// used. Otherwise sets search pattern.
|
||||
void set_last_used_pattern(const bool is_substitute_pattern)
|
||||
{
|
||||
last_idx = (is_substitute_pattern ? 1 : 0);
|
||||
}
|
||||
|
||||
/// Returns true if search pattern was the last used one
|
||||
bool search_was_last_used(void)
|
||||
{
|
||||
return last_idx == 0;
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
#ifndef NVIM_SEARCH_H
|
||||
#define NVIM_SEARCH_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Values for the find_pattern_in_path() function args 'type' and 'action': */
|
||||
#define FIND_ANY 1
|
||||
#define FIND_DEFINE 2
|
||||
@ -39,6 +42,27 @@
|
||||
#define RE_BOTH 2 /* save pat in both patterns */
|
||||
#define RE_LAST 2 /* use last used pattern if "pat" is NULL */
|
||||
|
||||
/// Structure containing offset definition for the last search pattern
|
||||
///
|
||||
/// @note Only offset for the last search pattern is used, not for the last
|
||||
/// substitute pattern.
|
||||
typedef struct soffset {
|
||||
char dir; ///< Search direction: forward ('/') or backward ('?')
|
||||
bool line; ///< True if search has line offset.
|
||||
bool end; ///< True if search sets cursor at the end.
|
||||
int64_t off; ///< Actual offset value.
|
||||
} SearchOffset;
|
||||
|
||||
/// Structure containing last search pattern and its attributes.
|
||||
typedef struct spat {
|
||||
char_u *pat; ///< The pattern (in allocated memory) or NULL.
|
||||
bool magic; ///< Magicness of the pattern.
|
||||
bool no_scs; ///< No smartcase for this pattern.
|
||||
Timestamp timestamp; ///< Time of the last change.
|
||||
SearchOffset off; ///< Pattern offset.
|
||||
Dictionary *additional_data; ///< Additional data from ShaDa file.
|
||||
} SearchPattern;
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "search.h.generated.h"
|
||||
#endif
|
||||
|
3454
src/nvim/shada.c
Normal file
3454
src/nvim/shada.c
Normal file
File diff suppressed because it is too large
Load Diff
18
src/nvim/shada.h
Normal file
18
src/nvim/shada.h
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef NVIM_SHADA_H
|
||||
#define NVIM_SHADA_H
|
||||
|
||||
typedef long ShadaPosition;
|
||||
|
||||
/// Flags for shada_read_file and children
|
||||
enum {
|
||||
kShaDaWantInfo = 1, ///< Load non-mark information
|
||||
kShaDaWantMarks = 2, ///< Load file marks
|
||||
kShaDaForceit = 4, ///< Overwrite info already read
|
||||
kShaDaGetOldfiles = 8, ///< Load v:oldfiles.
|
||||
};
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "shada.h.generated.h"
|
||||
#endif
|
||||
|
||||
#endif // NVIM_SHADA_H
|
@ -130,7 +130,7 @@ static char_u *tagmatchname = NULL; /* name of last used tag */
|
||||
* Tag for preview window is remembered separately, to avoid messing up the
|
||||
* normal tagstack.
|
||||
*/
|
||||
static taggy_T ptag_entry = {NULL, {INIT_POS_T(0, 0, 0), 0}, 0, 0};
|
||||
static taggy_T ptag_entry = {NULL, {INIT_POS_T(0, 0, 0), 0, 0, NULL}, 0, 0};
|
||||
|
||||
/*
|
||||
* Jump to tag; handling of tag commands and tag stack
|
||||
|
@ -111,6 +111,7 @@
|
||||
#include "nvim/types.h"
|
||||
#include "nvim/os/os.h"
|
||||
#include "nvim/os/time.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "undo.c.generated.h"
|
||||
@ -325,6 +326,17 @@ static long get_undolevel(void)
|
||||
return curbuf->b_p_ul;
|
||||
}
|
||||
|
||||
static inline void zero_fmark_additional_data(fmark_T *fmarks)
|
||||
{
|
||||
for (size_t i = 0; i < NMARKS; i++) {
|
||||
if (fmarks[i].additional_data != NULL) {
|
||||
api_free_dictionary(*fmarks[i].additional_data);
|
||||
free(fmarks[i].additional_data);
|
||||
fmarks[i].additional_data = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Common code for various ways to save text before a change.
|
||||
* "top" is the line above the first changed line.
|
||||
@ -467,7 +479,9 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload)
|
||||
((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0);
|
||||
|
||||
/* save named marks and Visual marks for undo */
|
||||
memmove(uhp->uh_namedm, curbuf->b_namedm, sizeof(pos_T) * NMARKS);
|
||||
zero_fmark_additional_data(curbuf->b_namedm);
|
||||
memmove(uhp->uh_namedm, curbuf->b_namedm,
|
||||
sizeof(curbuf->b_namedm[0]) * NMARKS);
|
||||
uhp->uh_visual = curbuf->b_visual;
|
||||
|
||||
curbuf->b_u_newhead = uhp;
|
||||
@ -785,7 +799,7 @@ static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp)
|
||||
undo_write_bytes(bi, (uintmax_t)uhp->uh_flags, 2);
|
||||
// Assume NMARKS will stay the same.
|
||||
for (size_t i = 0; i < (size_t)NMARKS; i++) {
|
||||
serialize_pos(bi, uhp->uh_namedm[i]);
|
||||
serialize_pos(bi, uhp->uh_namedm[i].mark);
|
||||
}
|
||||
serialize_visualinfo(bi, &uhp->uh_visual);
|
||||
uint8_t time_buf[8];
|
||||
@ -832,7 +846,9 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi, char_u *file_name)
|
||||
uhp->uh_cursor_vcol = undo_read_4c(bi);
|
||||
uhp->uh_flags = undo_read_2c(bi);
|
||||
for (size_t i = 0; i < (size_t)NMARKS; i++) {
|
||||
unserialize_pos(bi, &uhp->uh_namedm[i]);
|
||||
unserialize_pos(bi, &uhp->uh_namedm[i].mark);
|
||||
uhp->uh_namedm[i].timestamp = 0;
|
||||
uhp->uh_namedm[i].fnum = 0;
|
||||
}
|
||||
unserialize_visualinfo(bi, &uhp->uh_visual);
|
||||
uhp->uh_time = undo_read_time(bi);
|
||||
@ -2009,7 +2025,7 @@ static void u_undoredo(int undo)
|
||||
u_entry_T *newlist = NULL;
|
||||
int old_flags;
|
||||
int new_flags;
|
||||
pos_T namedm[NMARKS];
|
||||
fmark_T namedm[NMARKS];
|
||||
visualinfo_T visualinfo;
|
||||
int empty_buffer; /* buffer became empty */
|
||||
u_header_T *curhead = curbuf->b_u_curhead;
|
||||
@ -2029,7 +2045,8 @@ static void u_undoredo(int undo)
|
||||
/*
|
||||
* save marks before undo/redo
|
||||
*/
|
||||
memmove(namedm, curbuf->b_namedm, sizeof(pos_T) * NMARKS);
|
||||
zero_fmark_additional_data(curbuf->b_namedm);
|
||||
memmove(namedm, curbuf->b_namedm, sizeof(curbuf->b_namedm[0]) * NMARKS);
|
||||
visualinfo = curbuf->b_visual;
|
||||
curbuf->b_op_start.lnum = curbuf->b_ml.ml_line_count;
|
||||
curbuf->b_op_start.col = 0;
|
||||
@ -2158,7 +2175,8 @@ static void u_undoredo(int undo)
|
||||
* restore marks from before undo/redo
|
||||
*/
|
||||
for (i = 0; i < NMARKS; ++i)
|
||||
if (curhead->uh_namedm[i].lnum != 0) {
|
||||
if (curhead->uh_namedm[i].mark.lnum != 0) {
|
||||
free_fmark(curbuf->b_namedm[i]);
|
||||
curbuf->b_namedm[i] = curhead->uh_namedm[i];
|
||||
curhead->uh_namedm[i] = namedm[i];
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "nvim/pos.h"
|
||||
#include "nvim/buffer_defs.h"
|
||||
#include "nvim/mark_defs.h"
|
||||
|
||||
/* Structure to store info about the Visual area. */
|
||||
typedef struct {
|
||||
@ -54,7 +55,7 @@ struct u_header {
|
||||
pos_T uh_cursor; /* cursor position before saving */
|
||||
long uh_cursor_vcol;
|
||||
int uh_flags; /* see below */
|
||||
pos_T uh_namedm[NMARKS]; /* marks before undo/after redo */
|
||||
fmark_T uh_namedm[NMARKS]; /* marks before undo/after redo */
|
||||
visualinfo_T uh_visual; /* Visual areas before undo/after redo */
|
||||
time_t uh_time; /* timestamp when the change was made */
|
||||
long uh_save_nr; /* set when the file was saved after the
|
||||
|
@ -1925,7 +1925,7 @@ int win_close(win_T *win, int free_buf)
|
||||
&& (last_window() || curtab != prev_curtab
|
||||
|| close_last_window_tabpage(win, free_buf, prev_curtab))) {
|
||||
/* Autocommands have close all windows, quit now. Restore
|
||||
* curwin->w_buffer, otherwise writing viminfo may fail. */
|
||||
* curwin->w_buffer, otherwise writing ShaDa file may fail. */
|
||||
if (curwin->w_buffer == NULL)
|
||||
curwin->w_buffer = curbuf;
|
||||
getout(0);
|
||||
|
40
test/functional/ex_getln/history_spec.lua
Normal file
40
test/functional/ex_getln/history_spec.lua
Normal file
@ -0,0 +1,40 @@
|
||||
local helpers = require('test.functional.helpers')
|
||||
local clear, nvim, call, eq =
|
||||
helpers.clear, helpers.nvim, helpers.call, helpers.eq
|
||||
|
||||
describe('history support code', function()
|
||||
before_each(clear)
|
||||
|
||||
local histadd = function(...) return call('histadd', ...) end
|
||||
local histget = function(...) return call('histget', ...) end
|
||||
local histdel = function(...) return call('histdel', ...) end
|
||||
|
||||
it('correctly clears start of the history', function()
|
||||
-- Regression test: check absense of the memory leak when clearing start of
|
||||
-- the history using ex_getln.c/clr_history().
|
||||
eq(1, histadd(':', 'foo'))
|
||||
eq(1, histdel(':'))
|
||||
eq('', histget(':', -1))
|
||||
end)
|
||||
|
||||
it('correctly clears end of the history', function()
|
||||
-- Regression test: check absense of the memory leak when clearing end of
|
||||
-- the history using ex_getln.c/clr_history().
|
||||
nvim('set_option', 'history', 1)
|
||||
eq(1, histadd(':', 'foo'))
|
||||
eq(1, histdel(':'))
|
||||
eq('', histget(':', -1))
|
||||
end)
|
||||
|
||||
it('correctly removes item from history', function()
|
||||
-- Regression test: check that ex_getln.c/del_history_idx() correctly clears
|
||||
-- history index after removing history entry. If it does not then deleting
|
||||
-- history will result in a double free.
|
||||
eq(1, histadd(':', 'foo'))
|
||||
eq(1, histadd(':', 'bar'))
|
||||
eq(1, histadd(':', 'baz'))
|
||||
eq(1, histdel(':', -2))
|
||||
eq(1, histdel(':'))
|
||||
eq('', histget(':', -1))
|
||||
end)
|
||||
end)
|
Loading…
Reference in New Issue
Block a user