📄 memline.c
字号:
/* vi:set ts=8 sts=4 sw=4:
*
* VIM - Vi IMproved by Bram Moolenaar
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
*/
/* for debugging */
/* #define CHECK(c, s) if (c) EMSG(s) */
#define CHECK(c, s)
/*
* memline.c: Contains the functions for appending, deleting and changing the
* text lines. The memfile functions are used to store the information in blocks
* of memory, backed up by a file. The structure of the information is a tree.
* The root of the tree is a pointer block. The leaves of the tree are data
* blocks. In between may be several layers of pointer blocks, forming branches.
*
* Three types of blocks are used:
* - Block nr 0 contains information for recovery
* - Pointer blocks contain list of pointers to other blocks.
* - Data blocks contain the actual text.
*
* Block nr 0 contains the block0 structure (see below).
*
* Block nr 1 is the first pointer block. It is the root of the tree.
* Other pointer blocks are branches.
*
* If a line is too big to fit in a single page, the block containing that
* line is made big enough to hold the line. It may span several pages.
* Otherwise all blocks are one page.
*
* A data block that was filled when starting to edit a file and was not
* changed since then, can have a negative block number. This means that it
* has not yet been assigned a place in the file. When recovering, the lines
* in this data block can be read from the original file. When the block is
* changed (lines appended/deleted/changed) or when it is flushed it gets a
* positive number. Use mf_trans_del() to get the new number, before calling
* mf_get().
*/
#if defined MSDOS || defined WIN32
# include <io.h>
#endif
#include "vim.h"
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifndef UNIX /* it's in os_unix.h for Unix */
# include <time.h>
#endif
#ifdef SASC
# include <proto/dos.h> /* for Open() and Close() */
#endif
typedef struct block0 ZERO_BL; /* contents of the first block */
typedef struct pointer_block PTR_BL; /* contents of a pointer block */
typedef struct data_block DATA_BL; /* contents of a data block */
typedef struct pointer_entry PTR_EN; /* block/line-count pair */
#define DATA_ID (('d' << 8) + 'a') /* data block id */
#define PTR_ID (('p' << 8) + 't') /* pointer block id */
#define BLOCK0_ID0 'b' /* block 0 id 0 */
#define BLOCK0_ID1 '0' /* block 0 id 1 */
/*
* pointer to a block, used in a pointer block
*/
struct pointer_entry
{
blocknr_t pe_bnum; /* block number */
linenr_t pe_line_count; /* number of lines in this branch */
linenr_t pe_old_lnum; /* lnum for this block (for recovery) */
int pe_page_count; /* number of pages in block pe_bnum */
};
/*
* A pointer block contains a list of branches in the tree.
*/
struct pointer_block
{
short_u pb_id; /* ID for pointer block: PTR_ID */
short_u pb_count; /* number of pointer in this block */
short_u pb_count_max; /* maximum value for pb_count */
PTR_EN pb_pointer[1]; /* list of pointers to blocks (actually longer)
* followed by empty space until end of page */
};
/*
* A data block is a leaf in the tree.
*
* The text of the lines is at the end of the block. The text of the first line
* in the block is put at the end, the text of the second line in front of it,
* etc. Thus the order of the lines is the opposite of the line number.
*/
struct data_block
{
short_u db_id; /* ID for data block: DATA_ID */
unsigned db_free; /* free space available */
unsigned db_txt_start; /* byte where text starts */
unsigned db_txt_end; /* byte just after data block */
linenr_t db_line_count; /* number of lines in this block */
unsigned db_index[1]; /* index for start of line (actually bigger)
* followed by empty space upto db_txt_start
* followed by the text in the lines until
* end of page */
};
/*
* The low bits of db_index hold the actual index. The topmost bit is
* used for the global command to be able to mark a line.
* This method is not clean, but otherwise there would be at least one extra
* byte used for each line.
* The mark has to be in this place to keep it with the correct line when other
* lines are inserted or deleted.
*/
#define DB_MARKED ((unsigned)1 << ((sizeof(unsigned) * 8) - 1))
#define DB_INDEX_MASK (~DB_MARKED)
#define INDEX_SIZE (sizeof(unsigned)) /* size of one db_index entry */
#define HEADER_SIZE (sizeof(DATA_BL) - INDEX_SIZE) /* size of data block header */
#define B0_FNAME_SIZE 900
#define B0_UNAME_SIZE 40
#define B0_HNAME_SIZE 40
/*
* Restrict the numbers to 32 bits, otherwise most compilers will complain.
* This won't detect a 64 bit machine that only swaps a byte in the top 32
* bits, but that is crazy anyway.
*/
#define B0_MAGIC_LONG 0x30313233
#define B0_MAGIC_INT 0x20212223
#define B0_MAGIC_SHORT 0x10111213
#define B0_MAGIC_CHAR 0x55
/*
* Block zero holds all info about the swap file.
*
* NOTE: DEFINITION OF BLOCK 0 SHOULD NOT CHANGE, it makes all existing swap
* files unusable!
*
* If size of block0 changes anyway, adjust minimal block size
* in mf_open()!!
*
* This block is built up of single bytes, to make it portable accros
* different machines. b0_magic_* is used to check the byte order and size of
* variables, because the rest of the swap is not portable.
*/
struct block0
{
char_u b0_id[2]; /* id for block 0: BLOCK0_ID0 and BLOCK0_ID1 */
char_u b0_version[10]; /* Vim version string */
char_u b0_page_size[4];/* number of bytes per page */
char_u b0_mtime[4]; /* last modification time of file */
char_u b0_ino[4]; /* inode of b0_fname */
char_u b0_pid[4]; /* process id of creator (or 0) */
char_u b0_uname[B0_UNAME_SIZE]; /* name of user (uid if no name) */
char_u b0_hname[B0_HNAME_SIZE]; /* host name (if it has a name) */
char_u b0_fname[B0_FNAME_SIZE]; /* name of file being edited */
long b0_magic_long; /* check for byte order of long */
int b0_magic_int; /* check for byte order of int */
short b0_magic_short; /* check for byte order of short */
char_u b0_magic_char; /* check for last char */
};
#define STACK_INCR 5 /* nr of entries added to ml_stack at a time */
/*
* The line number where the first mark may be is remembered.
* If it is 0 there are no marks at all.
* (always used for the current buffer only, no buffer change possible while
* executing a global command).
*/
static linenr_t lowest_marked = 0;
/*
* arguments for ml_find_line()
*/
#define ML_DELETE 0x11 /* delete line */
#define ML_INSERT 0x12 /* insert line */
#define ML_FIND 0x13 /* just find the line */
#define ML_FLUSH 0x02 /* flush locked block */
#define ML_SIMPLE(x) (x & 0x10) /* DEL, INS or FIND */
static void set_b0_fname __ARGS((ZERO_BL *, BUF *buf));
static void swapfile_info __ARGS((char_u *));
static int recov_file_names __ARGS((char_u **, char_u *, int prepend_dot));
static int ml_append_int __ARGS((BUF *, linenr_t, char_u *, colnr_t, int, int));
static int ml_delete_int __ARGS((BUF *, linenr_t, int));
static char_u *findswapname __ARGS((BUF *, char_u **, char_u *));
static void ml_flush_line __ARGS((BUF *));
static BHDR *ml_new_data __ARGS((MEMFILE *, int, int));
static BHDR *ml_new_ptr __ARGS((MEMFILE *));
static BHDR *ml_find_line __ARGS((BUF *, linenr_t, int));
static int ml_add_stack __ARGS((BUF *));
static char_u *makeswapname __ARGS((BUF *, char_u *));
static void ml_lineadd __ARGS((BUF *, int));
static int b0_magic_wrong __ARGS((ZERO_BL *));
#ifdef CHECK_INODE
static int fnamecmp_ino __ARGS((char_u *, char_u *, long));
#endif
static void long_to_char __ARGS((long, char_u *));
static long char_to_long __ARGS((char_u *));
/*
* open a new memline for 'curbuf'
*
* return FAIL for failure, OK otherwise
*/
int
ml_open()
{
MEMFILE *mfp;
BHDR *hp = NULL;
ZERO_BL *b0p;
PTR_BL *pp;
DATA_BL *dp;
/*
* init fields in memline struct
*/
curbuf->b_ml.ml_stack_size = 0; /* no stack yet */
curbuf->b_ml.ml_stack = NULL; /* no stack yet */
curbuf->b_ml.ml_stack_top = 0; /* nothing in the stack */
curbuf->b_ml.ml_locked = NULL; /* no cached block */
curbuf->b_ml.ml_line_lnum = 0; /* no cached line */
/*
* When 'updatecount' is non-zero, flag that a swap file may be opened later.
*/
if (p_uc && curbuf->b_p_swf)
curbuf->b_may_swap = TRUE;
else
curbuf->b_may_swap = FALSE;
/*
* Open the memfile. No swap file is created yet.
*/
mfp = mf_open(NULL, TRUE);
if (mfp == NULL)
goto error;
#if defined(MSDOS) && !defined(DJGPP)
/* for 16 bit MS-DOS create a swapfile now, because we run out of
* memory very quickly */
if (p_uc)
ml_open_file(curbuf);
#endif
curbuf->b_ml.ml_mfp = mfp;
curbuf->b_ml.ml_flags = ML_EMPTY;
curbuf->b_ml.ml_line_count = 1;
/*
* fill block0 struct and write page 0
*/
if ((hp = mf_new(mfp, FALSE, 1)) == NULL)
goto error;
if (hp->bh_bnum != 0)
{
EMSG("didn't get block nr 0?");
goto error;
}
b0p = (ZERO_BL *)(hp->bh_data);
b0p->b0_id[0] = BLOCK0_ID0;
b0p->b0_id[1] = BLOCK0_ID1;
b0p->b0_magic_long = (long)B0_MAGIC_LONG;
b0p->b0_magic_int = (int)B0_MAGIC_INT;
b0p->b0_magic_short = (short)B0_MAGIC_SHORT;
b0p->b0_magic_char = B0_MAGIC_CHAR;
STRNCPY(b0p->b0_version, "VIM ", 4);
STRNCPY(b0p->b0_version + 4, Version, 6);
set_b0_fname(b0p, curbuf);
long_to_char((long)mfp->mf_page_size, b0p->b0_page_size);
(void)mch_get_user_name(b0p->b0_uname, B0_UNAME_SIZE);
b0p->b0_uname[B0_UNAME_SIZE - 1] = NUL;
mch_get_host_name(b0p->b0_hname, B0_HNAME_SIZE);
b0p->b0_hname[B0_HNAME_SIZE - 1] = NUL;
long_to_char(mch_get_pid(), b0p->b0_pid);
/*
* Always sync block number 0 to disk, so we can check the file name in
* the swap file in findswapname(). Don't do this for help files though.
* Only works when there's a swapfile, otherwise it's done when the file
* is created.
*/
mf_put(mfp, hp, TRUE, FALSE);
if (!curbuf->b_help)
(void)mf_sync(mfp, 0);
/*
* fill in root pointer block and write page 1
*/
if ((hp = ml_new_ptr(mfp)) == NULL)
goto error;
if (hp->bh_bnum != 1)
{
EMSG("didn't get block nr 1?");
goto error;
}
pp = (PTR_BL *)(hp->bh_data);
pp->pb_count = 1;
pp->pb_pointer[0].pe_bnum = 2;
pp->pb_pointer[0].pe_page_count = 1;
pp->pb_pointer[0].pe_old_lnum = 1;
pp->pb_pointer[0].pe_line_count = 1; /* line count after insertion */
mf_put(mfp, hp, TRUE, FALSE);
/*
* allocate first data block and create an empty line 1.
*/
if ((hp = ml_new_data(mfp, FALSE, 1)) == NULL)
goto error;
if (hp->bh_bnum != 2)
{
EMSG("didn't get block nr 2?");
goto error;
}
dp = (DATA_BL *)(hp->bh_data);
dp->db_index[0] = --dp->db_txt_start; /* at end of block */
dp->db_free -= 1 + INDEX_SIZE;
dp->db_line_count = 1;
*((char_u *)dp + dp->db_txt_start) = NUL; /* emtpy line */
return OK;
error:
if (mfp != NULL)
{
if (hp)
mf_put(mfp, hp, FALSE, FALSE);
mf_close(mfp, TRUE); /* will also free(mfp->mf_fname) */
}
curbuf->b_ml.ml_mfp = NULL;
return FAIL;
}
/*
* ml_setname() is called when the file name has been changed.
* It may rename the swap file.
*/
void
ml_setname()
{
int success = FALSE;
MEMFILE *mfp;
char_u *fname;
char_u *dirp;
#if defined(MSDOS) || defined(WIN32)
char_u *p;
#endif
mfp = curbuf->b_ml.ml_mfp;
if (mfp->mf_fd < 0) /* there is no swap file yet */
{
/*
* When 'updatecount' is 0 and 'noswapfile' there is no swap file.
* For help files we will make a swap file now.
*/
if (p_uc)
ml_open_file(curbuf); /* create a swap file */
return;
}
/*
* Try all directories in the 'directory' option.
*/
dirp = p_dir;
for (;;)
{
if (*dirp == NUL) /* tried all directories, fail */
break;
fname = findswapname(curbuf, &dirp, mfp->mf_fname); /* alloc's fname */
if (fname == NULL) /* no file name found for this dir */
continue;
#if defined(MSDOS) || defined(WIN32)
/*
* Set full pathname for swap file now, because a ":!cd dir" may
* change directory without us knowing it.
*/
p = FullName_save(fname, FALSE);
vim_free(fname);
fname = p;
if (fname == NULL)
continue;
#endif
/* if the file name is the same we don't have to do anything */
if (fnamecmp(fname, mfp->mf_fname) == 0)
{
vim_free(fname);
success = TRUE;
break;
}
/* need to close the swap file before renaming */
if (mfp->mf_fd >= 0)
{
close(mfp->mf_fd);
mfp->mf_fd = -1;
}
/* try to rename the swap file */
if (vim_rename(mfp->mf_fname, fname) == 0)
{
success = TRUE;
vim_free(mfp->mf_fname);
mfp->mf_fname = fname;
vim_free(mfp->mf_ffname);
#if defined(MSDOS) || defined(WIN32)
mfp->mf_ffname = NULL; /* mf_fname is full pathname already */
#else
mf_set_ffname(mfp);
#endif
break;
}
vim_free(fname); /* this fname didn't work, try another */
}
if (mfp->mf_fd == -1) /* need to (re)open the swap file */
{
mfp->mf_fd = open((char *)mfp->mf_fname, O_RDWR | O_EXTRA
#ifndef macintosh
, 0
#endif
);
if (mfp->mf_fd < 0)
{
/* could not (re)open the swap file, what can we do???? */
EMSG("Oops, lost the swap file!!!");
return;
}
}
if (!success)
EMSG("Could not rename swap file");
}
/*
* Open a file for the memfile for all buffers that are not readonly or have
* been modified.
* Used when 'updatecount' changes from zero to non-zero.
*/
void
ml_open_files()
{
BUF *buf;
for (buf = firstbuf; buf != NULL; buf = buf->b_next)
if (!buf->b_p_ro || buf->b_changed)
ml_open_file(buf);
}
/*
* Open a swap file for an existing memfile, if there is no swap file yet.
* If we are unable to find a file name, mf_fname will be NULL
* and the memfile will be in memory only (no recovery possible).
*/
void
ml_open_file(buf)
BUF *buf;
{
MEMFILE *mfp;
char_u *fname;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -