1 | // This is an open source non-commercial project. Dear PVS-Studio, please check |
2 | // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com |
3 | |
4 | // for debugging |
5 | // #define CHECK(c, s) do { if (c) EMSG(s); } while (0) |
6 | #define CHECK(c, s) do { } while (0) |
7 | |
8 | /* |
9 | * memline.c: Contains the functions for appending, deleting and changing the |
10 | * text lines. The memfile functions are used to store the information in |
11 | * blocks of memory, backed up by a file. The structure of the information is |
12 | * a tree. The root of the tree is a pointer block. The leaves of the tree |
13 | * are data blocks. In between may be several layers of pointer blocks, |
14 | * forming branches. |
15 | * |
16 | * Three types of blocks are used: |
17 | * - Block nr 0 contains information for recovery |
18 | * - Pointer blocks contain list of pointers to other blocks. |
19 | * - Data blocks contain the actual text. |
20 | * |
21 | * Block nr 0 contains the block0 structure (see below). |
22 | * |
23 | * Block nr 1 is the first pointer block. It is the root of the tree. |
24 | * Other pointer blocks are branches. |
25 | * |
26 | * If a line is too big to fit in a single page, the block containing that |
27 | * line is made big enough to hold the line. It may span several pages. |
28 | * Otherwise all blocks are one page. |
29 | * |
30 | * A data block that was filled when starting to edit a file and was not |
31 | * changed since then, can have a negative block number. This means that it |
32 | * has not yet been assigned a place in the file. When recovering, the lines |
33 | * in this data block can be read from the original file. When the block is |
34 | * changed (lines appended/deleted/changed) or when it is flushed it gets a |
35 | * positive number. Use mf_trans_del() to get the new number, before calling |
36 | * mf_get(). |
37 | */ |
38 | |
39 | #include <assert.h> |
40 | #include <errno.h> |
41 | #include <inttypes.h> |
42 | #include <string.h> |
43 | #include <stdbool.h> |
44 | #include <fcntl.h> |
45 | |
46 | #include "nvim/ascii.h" |
47 | #include "nvim/vim.h" |
48 | #include "nvim/memline.h" |
49 | #include "nvim/buffer.h" |
50 | #include "nvim/change.h" |
51 | #include "nvim/cursor.h" |
52 | #include "nvim/eval.h" |
53 | #include "nvim/getchar.h" |
54 | #include "nvim/fileio.h" |
55 | #include "nvim/func_attr.h" |
56 | #include "nvim/main.h" |
57 | #include "nvim/mark.h" |
58 | #include "nvim/mbyte.h" |
59 | #include "nvim/memfile.h" |
60 | #include "nvim/memory.h" |
61 | #include "nvim/message.h" |
62 | #include "nvim/misc1.h" |
63 | #include "nvim/option.h" |
64 | #include "nvim/os_unix.h" |
65 | #include "nvim/path.h" |
66 | #include "nvim/screen.h" |
67 | #include "nvim/sha256.h" |
68 | #include "nvim/spell.h" |
69 | #include "nvim/strings.h" |
70 | #include "nvim/ui.h" |
71 | #include "nvim/version.h" |
72 | #include "nvim/undo.h" |
73 | #include "nvim/window.h" |
74 | #include "nvim/os/os.h" |
75 | #include "nvim/os/process.h" |
76 | #include "nvim/os/input.h" |
77 | |
78 | #ifndef UNIX /* it's in os/unix_defs.h for Unix */ |
79 | # include <time.h> |
80 | #endif |
81 | |
82 | typedef struct block0 ZERO_BL; /* contents of the first block */ |
83 | typedef struct pointer_block PTR_BL; /* contents of a pointer block */ |
84 | typedef struct data_block DATA_BL; /* contents of a data block */ |
85 | typedef struct pointer_entry PTR_EN; /* block/line-count pair */ |
86 | |
87 | #define DATA_ID (('d' << 8) + 'a') /* data block id */ |
88 | #define PTR_ID (('p' << 8) + 't') /* pointer block id */ |
89 | #define BLOCK0_ID0 'b' /* block 0 id 0 */ |
90 | #define BLOCK0_ID1 '0' /* block 0 id 1 */ |
91 | |
92 | /* |
93 | * pointer to a block, used in a pointer block |
94 | */ |
95 | struct pointer_entry { |
96 | blocknr_T pe_bnum; /* block number */ |
97 | linenr_T pe_line_count; /* number of lines in this branch */ |
98 | linenr_T pe_old_lnum; /* lnum for this block (for recovery) */ |
99 | int pe_page_count; /* number of pages in block pe_bnum */ |
100 | }; |
101 | |
102 | /* |
103 | * A pointer block contains a list of branches in the tree. |
104 | */ |
105 | struct pointer_block { |
106 | uint16_t pb_id; /* ID for pointer block: PTR_ID */ |
107 | uint16_t pb_count; /* number of pointers in this block */ |
108 | uint16_t pb_count_max; /* maximum value for pb_count */ |
109 | PTR_EN pb_pointer[1]; /* list of pointers to blocks (actually longer) |
110 | * followed by empty space until end of page */ |
111 | }; |
112 | |
113 | /* |
114 | * A data block is a leaf in the tree. |
115 | * |
116 | * The text of the lines is at the end of the block. The text of the first line |
117 | * in the block is put at the end, the text of the second line in front of it, |
118 | * etc. Thus the order of the lines is the opposite of the line number. |
119 | */ |
120 | struct data_block { |
121 | uint16_t db_id; /* ID for data block: DATA_ID */ |
122 | unsigned db_free; /* free space available */ |
123 | unsigned db_txt_start; /* byte where text starts */ |
124 | unsigned db_txt_end; /* byte just after data block */ |
125 | linenr_T db_line_count; /* number of lines in this block */ |
126 | unsigned db_index[1]; /* index for start of line (actually bigger) |
127 | * followed by empty space upto db_txt_start |
128 | * followed by the text in the lines until |
129 | * end of page */ |
130 | }; |
131 | |
132 | /* |
133 | * The low bits of db_index hold the actual index. The topmost bit is |
134 | * used for the global command to be able to mark a line. |
135 | * This method is not clean, but otherwise there would be at least one extra |
136 | * byte used for each line. |
137 | * The mark has to be in this place to keep it with the correct line when other |
138 | * lines are inserted or deleted. |
139 | */ |
140 | #define DB_MARKED ((unsigned)1 << ((sizeof(unsigned) * 8) - 1)) |
141 | #define DB_INDEX_MASK (~DB_MARKED) |
142 | |
143 | #define INDEX_SIZE (sizeof(unsigned)) /* size of one db_index entry */ |
144 | #define (sizeof(DATA_BL) - INDEX_SIZE) /* size of data block header */ |
145 | |
146 | #define B0_FNAME_SIZE_ORG 900 /* what it was in older versions */ |
147 | #define B0_FNAME_SIZE_NOCRYPT 898 /* 2 bytes used for other things */ |
148 | #define B0_FNAME_SIZE_CRYPT 890 /* 10 bytes used for other things */ |
149 | #define B0_UNAME_SIZE 40 |
150 | #define B0_HNAME_SIZE 40 |
151 | /* |
152 | * Restrict the numbers to 32 bits, otherwise most compilers will complain. |
153 | * This won't detect a 64 bit machine that only swaps a byte in the top 32 |
154 | * bits, but that is crazy anyway. |
155 | */ |
156 | #define B0_MAGIC_LONG 0x30313233L |
157 | #define B0_MAGIC_INT 0x20212223L |
158 | #define B0_MAGIC_SHORT 0x10111213L |
159 | #define B0_MAGIC_CHAR 0x55 |
160 | |
161 | /* |
162 | * Block zero holds all info about the swap file. |
163 | * |
164 | * NOTE: DEFINITION OF BLOCK 0 SHOULD NOT CHANGE! It would make all existing |
165 | * swap files unusable! |
166 | * |
167 | * If size of block0 changes anyway, adjust MIN_SWAP_PAGE_SIZE in vim.h!! |
168 | * |
169 | * This block is built up of single bytes, to make it portable across |
170 | * different machines. b0_magic_* is used to check the byte order and size of |
171 | * variables, because the rest of the swap file is not portable. |
172 | */ |
173 | struct block0 { |
174 | char_u b0_id[2]; ///< ID for block 0: BLOCK0_ID0 and BLOCK0_ID1. |
175 | char_u b0_version[10]; /* Vim version string */ |
176 | char_u b0_page_size[4]; /* number of bytes per page */ |
177 | char_u b0_mtime[4]; /* last modification time of file */ |
178 | char_u b0_ino[4]; /* inode of b0_fname */ |
179 | char_u b0_pid[4]; /* process id of creator (or 0) */ |
180 | char_u b0_uname[B0_UNAME_SIZE]; /* name of user (uid if no name) */ |
181 | char_u b0_hname[B0_HNAME_SIZE]; /* host name (if it has a name) */ |
182 | char_u b0_fname[B0_FNAME_SIZE_ORG]; /* name of file being edited */ |
183 | long b0_magic_long; /* check for byte order of long */ |
184 | int b0_magic_int; /* check for byte order of int */ |
185 | short b0_magic_short; /* check for byte order of short */ |
186 | char_u b0_magic_char; /* check for last char */ |
187 | }; |
188 | |
189 | /* |
190 | * Note: b0_dirty and b0_flags are put at the end of the file name. For very |
191 | * long file names in older versions of Vim they are invalid. |
192 | * The 'fileencoding' comes before b0_flags, with a NUL in front. But only |
193 | * when there is room, for very long file names it's omitted. |
194 | */ |
195 | #define B0_DIRTY 0x55 |
196 | #define b0_dirty b0_fname[B0_FNAME_SIZE_ORG - 1] |
197 | |
198 | /* |
199 | * The b0_flags field is new in Vim 7.0. |
200 | */ |
201 | #define b0_flags b0_fname[B0_FNAME_SIZE_ORG - 2] |
202 | |
203 | /* The lowest two bits contain the fileformat. Zero means it's not set |
204 | * (compatible with Vim 6.x), otherwise it's EOL_UNIX + 1, EOL_DOS + 1 or |
205 | * EOL_MAC + 1. */ |
206 | #define B0_FF_MASK 3 |
207 | |
208 | /* Swap file is in directory of edited file. Used to find the file from |
209 | * different mount points. */ |
210 | #define B0_SAME_DIR 4 |
211 | |
212 | /* The 'fileencoding' is at the end of b0_fname[], with a NUL in front of it. |
213 | * When empty there is only the NUL. */ |
214 | #define B0_HAS_FENC 8 |
215 | |
216 | #define STACK_INCR 5 /* nr of entries added to ml_stack at a time */ |
217 | |
218 | /* |
219 | * The line number where the first mark may be is remembered. |
220 | * If it is 0 there are no marks at all. |
221 | * (always used for the current buffer only, no buffer change possible while |
222 | * executing a global command). |
223 | */ |
224 | static linenr_T lowest_marked = 0; |
225 | |
226 | /* |
227 | * arguments for ml_find_line() |
228 | */ |
229 | #define ML_DELETE 0x11 /* delete line */ |
230 | #define ML_INSERT 0x12 /* insert line */ |
231 | #define ML_FIND 0x13 /* just find the line */ |
232 | #define ML_FLUSH 0x02 /* flush locked block */ |
233 | #define ML_SIMPLE(x) (x & 0x10) /* DEL, INS or FIND */ |
234 | |
235 | /* argument for ml_upd_block0() */ |
236 | typedef enum { |
237 | UB_FNAME = 0 /* update timestamp and filename */ |
238 | , UB_SAME_DIR /* update the B0_SAME_DIR flag */ |
239 | } upd_block0_T; |
240 | |
241 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
242 | # include "memline.c.generated.h" |
243 | #endif |
244 | |
245 | /* |
246 | * Open a new memline for "buf". |
247 | * |
248 | * Return FAIL for failure, OK otherwise. |
249 | */ |
250 | int ml_open(buf_T *buf) |
251 | { |
252 | bhdr_T *hp = NULL; |
253 | ZERO_BL *b0p; |
254 | PTR_BL *pp; |
255 | DATA_BL *dp; |
256 | |
257 | /* |
258 | * init fields in memline struct |
259 | */ |
260 | buf->b_ml.ml_stack_size = 0; /* no stack yet */ |
261 | buf->b_ml.ml_stack = NULL; /* no stack yet */ |
262 | buf->b_ml.ml_stack_top = 0; /* nothing in the stack */ |
263 | buf->b_ml.ml_locked = NULL; /* no cached block */ |
264 | buf->b_ml.ml_line_lnum = 0; /* no cached line */ |
265 | buf->b_ml.ml_chunksize = NULL; |
266 | |
267 | if (cmdmod.noswapfile) { |
268 | buf->b_p_swf = false; |
269 | } |
270 | |
271 | /* |
272 | * When 'updatecount' is non-zero swap file may be opened later. |
273 | */ |
274 | if (!buf->terminal && p_uc && buf->b_p_swf) { |
275 | buf->b_may_swap = true; |
276 | } else { |
277 | buf->b_may_swap = false; |
278 | } |
279 | |
280 | // Open the memfile. No swap file is created yet. |
281 | memfile_T *mfp = mf_open(NULL, 0); |
282 | if (mfp == NULL) { |
283 | goto error; |
284 | } |
285 | |
286 | buf->b_ml.ml_mfp = mfp; |
287 | buf->b_ml.ml_flags = ML_EMPTY; |
288 | buf->b_ml.ml_line_count = 1; |
289 | curwin->w_nrwidth_line_count = 0; |
290 | |
291 | |
292 | /* |
293 | * fill block0 struct and write page 0 |
294 | */ |
295 | hp = mf_new(mfp, false, 1); |
296 | if (hp->bh_bnum != 0) { |
297 | IEMSG(_("E298: Didn't get block nr 0?" )); |
298 | goto error; |
299 | } |
300 | b0p = hp->bh_data; |
301 | |
302 | b0p->b0_id[0] = BLOCK0_ID0; |
303 | b0p->b0_id[1] = BLOCK0_ID1; |
304 | b0p->b0_magic_long = (long)B0_MAGIC_LONG; |
305 | b0p->b0_magic_int = (int)B0_MAGIC_INT; |
306 | b0p->b0_magic_short = (short)B0_MAGIC_SHORT; |
307 | b0p->b0_magic_char = B0_MAGIC_CHAR; |
308 | xstrlcpy(xstpcpy((char *) b0p->b0_version, "VIM " ), Version, 6); |
309 | long_to_char((long)mfp->mf_page_size, b0p->b0_page_size); |
310 | |
311 | if (!buf->b_spell) { |
312 | b0p->b0_dirty = buf->b_changed ? B0_DIRTY : 0; |
313 | b0p->b0_flags = get_fileformat(buf) + 1; |
314 | set_b0_fname(b0p, buf); |
315 | (void)os_get_user_name((char *)b0p->b0_uname, B0_UNAME_SIZE); |
316 | b0p->b0_uname[B0_UNAME_SIZE - 1] = NUL; |
317 | os_get_hostname((char *)b0p->b0_hname, B0_HNAME_SIZE); |
318 | b0p->b0_hname[B0_HNAME_SIZE - 1] = NUL; |
319 | long_to_char(os_get_pid(), b0p->b0_pid); |
320 | } |
321 | |
322 | /* |
323 | * Always sync block number 0 to disk, so we can check the file name in |
324 | * the swap file in findswapname(). Don't do this for a help files or |
325 | * a spell buffer though. |
326 | * Only works when there's a swapfile, otherwise it's done when the file |
327 | * is created. |
328 | */ |
329 | mf_put(mfp, hp, true, false); |
330 | if (!buf->b_help && !B_SPELL(buf)) |
331 | (void)mf_sync(mfp, 0); |
332 | |
333 | /* |
334 | * Fill in root pointer block and write page 1. |
335 | */ |
336 | if ((hp = ml_new_ptr(mfp)) == NULL) |
337 | goto error; |
338 | if (hp->bh_bnum != 1) { |
339 | IEMSG(_("E298: Didn't get block nr 1?" )); |
340 | goto error; |
341 | } |
342 | pp = hp->bh_data; |
343 | pp->pb_count = 1; |
344 | pp->pb_pointer[0].pe_bnum = 2; |
345 | pp->pb_pointer[0].pe_page_count = 1; |
346 | pp->pb_pointer[0].pe_old_lnum = 1; |
347 | pp->pb_pointer[0].pe_line_count = 1; /* line count after insertion */ |
348 | mf_put(mfp, hp, true, false); |
349 | |
350 | /* |
351 | * Allocate first data block and create an empty line 1. |
352 | */ |
353 | hp = ml_new_data(mfp, false, 1); |
354 | if (hp->bh_bnum != 2) { |
355 | IEMSG(_("E298: Didn't get block nr 2?" )); |
356 | goto error; |
357 | } |
358 | |
359 | dp = hp->bh_data; |
360 | dp->db_index[0] = --dp->db_txt_start; /* at end of block */ |
361 | dp->db_free -= 1 + INDEX_SIZE; |
362 | dp->db_line_count = 1; |
363 | *((char_u *)dp + dp->db_txt_start) = NUL; /* empty line */ |
364 | |
365 | return OK; |
366 | |
367 | error: |
368 | if (mfp != NULL) { |
369 | if (hp) { |
370 | mf_put(mfp, hp, false, false); |
371 | } |
372 | mf_close(mfp, true); // will also xfree(mfp->mf_fname) |
373 | } |
374 | buf->b_ml.ml_mfp = NULL; |
375 | return FAIL; |
376 | } |
377 | |
378 | /* |
379 | * ml_setname() is called when the file name of "buf" has been changed. |
380 | * It may rename the swap file. |
381 | */ |
382 | void ml_setname(buf_T *buf) |
383 | { |
384 | int success = FALSE; |
385 | memfile_T *mfp; |
386 | char_u *fname; |
387 | char_u *dirp; |
388 | |
389 | mfp = buf->b_ml.ml_mfp; |
390 | if (mfp->mf_fd < 0) { /* there is no swap file yet */ |
391 | /* |
392 | * When 'updatecount' is 0 and 'noswapfile' there is no swap file. |
393 | * For help files we will make a swap file now. |
394 | */ |
395 | if (p_uc != 0 && !cmdmod.noswapfile) { |
396 | ml_open_file(buf); /* create a swap file */ |
397 | } |
398 | return; |
399 | } |
400 | |
401 | /* |
402 | * Try all directories in the 'directory' option. |
403 | */ |
404 | dirp = p_dir; |
405 | bool found_existing_dir = false; |
406 | for (;; ) { |
407 | if (*dirp == NUL) /* tried all directories, fail */ |
408 | break; |
409 | fname = (char_u *)findswapname(buf, (char **)&dirp, (char *)mfp->mf_fname, |
410 | &found_existing_dir); |
411 | /* alloc's fname */ |
412 | if (dirp == NULL) /* out of memory */ |
413 | break; |
414 | if (fname == NULL) /* no file name found for this dir */ |
415 | continue; |
416 | |
417 | /* if the file name is the same we don't have to do anything */ |
418 | if (fnamecmp(fname, mfp->mf_fname) == 0) { |
419 | xfree(fname); |
420 | success = TRUE; |
421 | break; |
422 | } |
423 | /* need to close the swap file before renaming */ |
424 | if (mfp->mf_fd >= 0) { |
425 | close(mfp->mf_fd); |
426 | mfp->mf_fd = -1; |
427 | } |
428 | |
429 | /* try to rename the swap file */ |
430 | if (vim_rename(mfp->mf_fname, fname) == 0) { |
431 | success = TRUE; |
432 | mf_free_fnames(mfp); |
433 | mf_set_fnames(mfp, fname); |
434 | ml_upd_block0(buf, UB_SAME_DIR); |
435 | break; |
436 | } |
437 | xfree(fname); /* this fname didn't work, try another */ |
438 | } |
439 | |
440 | if (mfp->mf_fd == -1) { /* need to (re)open the swap file */ |
441 | mfp->mf_fd = os_open((char *)mfp->mf_fname, O_RDWR, 0); |
442 | if (mfp->mf_fd < 0) { |
443 | /* could not (re)open the swap file, what can we do???? */ |
444 | EMSG(_("E301: Oops, lost the swap file!!!" )); |
445 | return; |
446 | } |
447 | (void)os_set_cloexec(mfp->mf_fd); |
448 | } |
449 | if (!success) |
450 | EMSG(_("E302: Could not rename swap file" )); |
451 | } |
452 | |
453 | /* |
454 | * Open a file for the memfile for all buffers that are not readonly or have |
455 | * been modified. |
456 | * Used when 'updatecount' changes from zero to non-zero. |
457 | */ |
458 | void ml_open_files(void) |
459 | { |
460 | FOR_ALL_BUFFERS(buf) { |
461 | if (!buf->b_p_ro || buf->b_changed) { |
462 | ml_open_file(buf); |
463 | } |
464 | } |
465 | } |
466 | |
467 | /* |
468 | * Open a swap file for an existing memfile, if there is no swap file yet. |
469 | * If we are unable to find a file name, mf_fname will be NULL |
470 | * and the memfile will be in memory only (no recovery possible). |
471 | */ |
472 | void ml_open_file(buf_T *buf) |
473 | { |
474 | memfile_T *mfp; |
475 | char_u *fname; |
476 | char_u *dirp; |
477 | |
478 | mfp = buf->b_ml.ml_mfp; |
479 | if (mfp == NULL || mfp->mf_fd >= 0 || !buf->b_p_swf || cmdmod.noswapfile |
480 | || buf->terminal) { |
481 | return; /* nothing to do */ |
482 | } |
483 | |
484 | /* For a spell buffer use a temp file name. */ |
485 | if (buf->b_spell) { |
486 | fname = vim_tempname(); |
487 | if (fname != NULL) |
488 | (void)mf_open_file(mfp, fname); /* consumes fname! */ |
489 | buf->b_may_swap = false; |
490 | return; |
491 | } |
492 | |
493 | /* |
494 | * Try all directories in 'directory' option. |
495 | */ |
496 | dirp = p_dir; |
497 | bool found_existing_dir = false; |
498 | for (;; ) { |
499 | if (*dirp == NUL) |
500 | break; |
501 | // There is a small chance that between choosing the swap file name |
502 | // and creating it, another Vim creates the file. In that case the |
503 | // creation will fail and we will use another directory. |
504 | fname = (char_u *)findswapname(buf, (char **)&dirp, NULL, |
505 | &found_existing_dir); |
506 | if (dirp == NULL) |
507 | break; /* out of memory */ |
508 | if (fname == NULL) |
509 | continue; |
510 | if (mf_open_file(mfp, fname) == OK) { /* consumes fname! */ |
511 | ml_upd_block0(buf, UB_SAME_DIR); |
512 | |
513 | /* Flush block zero, so others can read it */ |
514 | if (mf_sync(mfp, MFS_ZERO) == OK) { |
515 | /* Mark all blocks that should be in the swapfile as dirty. |
516 | * Needed for when the 'swapfile' option was reset, so that |
517 | * the swap file was deleted, and then on again. */ |
518 | mf_set_dirty(mfp); |
519 | break; |
520 | } |
521 | /* Writing block 0 failed: close the file and try another dir */ |
522 | mf_close_file(buf, false); |
523 | } |
524 | } |
525 | |
526 | if (mfp->mf_fname == NULL) { /* Failed! */ |
527 | need_wait_return = TRUE; /* call wait_return later */ |
528 | ++no_wait_return; |
529 | (void)EMSG2(_( |
530 | "E303: Unable to open swap file for \"%s\", recovery impossible" ), |
531 | buf_spname(buf) != NULL ? buf_spname(buf) : buf->b_fname); |
532 | --no_wait_return; |
533 | } |
534 | |
535 | /* don't try to open a swap file again */ |
536 | buf->b_may_swap = false; |
537 | } |
538 | |
539 | /// If still need to create a swap file, and starting to edit a not-readonly |
540 | /// file, or reading into an existing buffer, create a swap file now. |
541 | /// |
542 | /// @param newfile reading file into new buffer |
543 | void check_need_swap(int newfile) |
544 | { |
545 | int old_msg_silent = msg_silent; // might be reset by an E325 message |
546 | msg_silent = 0; // If swap dialog prompts for input, user needs to see it! |
547 | |
548 | if (curbuf->b_may_swap && (!curbuf->b_p_ro || !newfile)) { |
549 | ml_open_file(curbuf); |
550 | } |
551 | |
552 | msg_silent = old_msg_silent; |
553 | } |
554 | |
555 | /* |
556 | * Close memline for buffer 'buf'. |
557 | * If 'del_file' is TRUE, delete the swap file |
558 | */ |
559 | void ml_close(buf_T *buf, int del_file) |
560 | { |
561 | if (buf->b_ml.ml_mfp == NULL) /* not open */ |
562 | return; |
563 | mf_close(buf->b_ml.ml_mfp, del_file); /* close the .swp file */ |
564 | if (buf->b_ml.ml_line_lnum != 0 && (buf->b_ml.ml_flags & ML_LINE_DIRTY)) |
565 | xfree(buf->b_ml.ml_line_ptr); |
566 | xfree(buf->b_ml.ml_stack); |
567 | XFREE_CLEAR(buf->b_ml.ml_chunksize); |
568 | buf->b_ml.ml_mfp = NULL; |
569 | |
570 | /* Reset the "recovered" flag, give the ATTENTION prompt the next time |
571 | * this buffer is loaded. */ |
572 | buf->b_flags &= ~BF_RECOVERED; |
573 | } |
574 | |
575 | /* |
576 | * Close all existing memlines and memfiles. |
577 | * Only used when exiting. |
578 | * When 'del_file' is TRUE, delete the memfiles. |
579 | * But don't delete files that were ":preserve"d when we are POSIX compatible. |
580 | */ |
581 | void ml_close_all(int del_file) |
582 | { |
583 | FOR_ALL_BUFFERS(buf) { |
584 | ml_close(buf, del_file && ((buf->b_flags & BF_PRESERVED) == 0)); |
585 | } |
586 | spell_delete_wordlist(); /* delete the internal wordlist */ |
587 | vim_deltempdir(); /* delete created temp directory */ |
588 | } |
589 | |
590 | /* |
591 | * Close all memfiles for not modified buffers. |
592 | * Only use just before exiting! |
593 | */ |
594 | void ml_close_notmod(void) |
595 | { |
596 | FOR_ALL_BUFFERS(buf) { |
597 | if (!bufIsChanged(buf)) { |
598 | ml_close(buf, TRUE); /* close all not-modified buffers */ |
599 | } |
600 | } |
601 | } |
602 | |
603 | /* |
604 | * Update the timestamp in the .swp file. |
605 | * Used when the file has been written. |
606 | */ |
607 | void ml_timestamp(buf_T *buf) |
608 | { |
609 | ml_upd_block0(buf, UB_FNAME); |
610 | } |
611 | |
612 | /// Checks whether the IDs in b0 are valid. |
613 | static bool ml_check_b0_id(ZERO_BL *b0p) |
614 | FUNC_ATTR_NONNULL_ALL |
615 | { |
616 | return b0p->b0_id[0] == BLOCK0_ID0 && b0p->b0_id[1] == BLOCK0_ID1; |
617 | } |
618 | |
619 | /// Checks whether all strings in b0 are valid (i.e. nul-terminated). |
620 | static bool ml_check_b0_strings(ZERO_BL *b0p) |
621 | FUNC_ATTR_NONNULL_ALL |
622 | { |
623 | return (memchr(b0p->b0_version, NUL, 10) |
624 | && memchr(b0p->b0_uname, NUL, B0_UNAME_SIZE) |
625 | && memchr(b0p->b0_hname, NUL, B0_HNAME_SIZE) |
626 | && memchr(b0p->b0_fname, NUL, B0_FNAME_SIZE_CRYPT)); // -V512 |
627 | } |
628 | |
629 | /* |
630 | * Update the timestamp or the B0_SAME_DIR flag of the .swp file. |
631 | */ |
632 | static void ml_upd_block0(buf_T *buf, upd_block0_T what) |
633 | { |
634 | memfile_T *mfp; |
635 | bhdr_T *hp; |
636 | ZERO_BL *b0p; |
637 | |
638 | mfp = buf->b_ml.ml_mfp; |
639 | if (mfp == NULL || (hp = mf_get(mfp, 0, 1)) == NULL) |
640 | return; |
641 | b0p = hp->bh_data; |
642 | if (ml_check_b0_id(b0p) == FAIL) { |
643 | IEMSG(_("E304: ml_upd_block0(): Didn't get block 0??" )); |
644 | } else { |
645 | if (what == UB_FNAME) { |
646 | set_b0_fname(b0p, buf); |
647 | } else { // what == UB_SAME_DIR |
648 | set_b0_dir_flag(b0p, buf); |
649 | } |
650 | } |
651 | mf_put(mfp, hp, true, false); |
652 | } |
653 | |
654 | /* |
655 | * Write file name and timestamp into block 0 of a swap file. |
656 | * Also set buf->b_mtime. |
657 | * Don't use NameBuff[]!!! |
658 | */ |
659 | static void set_b0_fname(ZERO_BL *b0p, buf_T *buf) |
660 | { |
661 | if (buf->b_ffname == NULL) |
662 | b0p->b0_fname[0] = NUL; |
663 | else { |
664 | char uname[B0_UNAME_SIZE]; |
665 | |
666 | /* |
667 | * For a file under the home directory of the current user, we try to |
668 | * replace the home directory path with "~user". This helps when |
669 | * editing the same file on different machines over a network. |
670 | * First replace home dir path with "~/" with home_replace(). |
671 | * Then insert the user name to get "~user/". |
672 | */ |
673 | home_replace(NULL, buf->b_ffname, b0p->b0_fname, |
674 | B0_FNAME_SIZE_CRYPT, TRUE); |
675 | if (b0p->b0_fname[0] == '~') { |
676 | /* If there is no user name or it is too long, don't use "~/" */ |
677 | int retval = os_get_user_name(uname, B0_UNAME_SIZE); |
678 | size_t ulen = STRLEN(uname); |
679 | size_t flen = STRLEN(b0p->b0_fname); |
680 | if (retval == FAIL || ulen + flen > B0_FNAME_SIZE_CRYPT - 1) { |
681 | STRLCPY(b0p->b0_fname, buf->b_ffname, B0_FNAME_SIZE_CRYPT); |
682 | } else { |
683 | memmove(b0p->b0_fname + ulen + 1, b0p->b0_fname + 1, flen); |
684 | memmove(b0p->b0_fname + 1, uname, ulen); |
685 | } |
686 | } |
687 | FileInfo file_info; |
688 | if (os_fileinfo((char *)buf->b_ffname, &file_info)) { |
689 | long_to_char(file_info.stat.st_mtim.tv_sec, b0p->b0_mtime); |
690 | long_to_char((long)os_fileinfo_inode(&file_info), b0p->b0_ino); |
691 | buf_store_file_info(buf, &file_info); |
692 | buf->b_mtime_read = buf->b_mtime; |
693 | } else { |
694 | long_to_char(0L, b0p->b0_mtime); |
695 | long_to_char(0L, b0p->b0_ino); |
696 | buf->b_mtime = 0; |
697 | buf->b_mtime_read = 0; |
698 | buf->b_orig_size = 0; |
699 | buf->b_orig_mode = 0; |
700 | } |
701 | } |
702 | |
703 | /* Also add the 'fileencoding' if there is room. */ |
704 | add_b0_fenc(b0p, curbuf); |
705 | } |
706 | |
707 | /* |
708 | * Update the B0_SAME_DIR flag of the swap file. It's set if the file and the |
709 | * swapfile for "buf" are in the same directory. |
710 | * This is fail safe: if we are not sure the directories are equal the flag is |
711 | * not set. |
712 | */ |
713 | static void set_b0_dir_flag(ZERO_BL *b0p, buf_T *buf) |
714 | { |
715 | if (same_directory(buf->b_ml.ml_mfp->mf_fname, buf->b_ffname)) |
716 | b0p->b0_flags |= B0_SAME_DIR; |
717 | else |
718 | b0p->b0_flags &= ~B0_SAME_DIR; |
719 | } |
720 | |
721 | /* |
722 | * When there is room, add the 'fileencoding' to block zero. |
723 | */ |
724 | static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf) |
725 | { |
726 | int n; |
727 | int size = B0_FNAME_SIZE_NOCRYPT; |
728 | |
729 | n = (int)STRLEN(buf->b_p_fenc); |
730 | if ((int)STRLEN(b0p->b0_fname) + n + 1 > size) |
731 | b0p->b0_flags &= ~B0_HAS_FENC; |
732 | else { |
733 | memmove((char *)b0p->b0_fname + size - n, |
734 | (char *)buf->b_p_fenc, (size_t)n); |
735 | *(b0p->b0_fname + size - n - 1) = NUL; |
736 | b0p->b0_flags |= B0_HAS_FENC; |
737 | } |
738 | } |
739 | |
740 | |
741 | /* |
742 | * Try to recover curbuf from the .swp file. |
743 | */ |
744 | void ml_recover(void) |
745 | { |
746 | buf_T *buf = NULL; |
747 | memfile_T *mfp = NULL; |
748 | char_u *fname; |
749 | char_u *fname_used = NULL; |
750 | bhdr_T *hp = NULL; |
751 | ZERO_BL *b0p; |
752 | int b0_ff; |
753 | char_u *b0_fenc = NULL; |
754 | PTR_BL *pp; |
755 | DATA_BL *dp; |
756 | infoptr_T *ip; |
757 | blocknr_T bnum; |
758 | int page_count; |
759 | int len; |
760 | int directly; |
761 | linenr_T lnum; |
762 | char_u *p; |
763 | int i; |
764 | long error; |
765 | int cannot_open; |
766 | linenr_T line_count; |
767 | bool has_error; |
768 | int idx; |
769 | int top; |
770 | int txt_start; |
771 | off_T size; |
772 | int called_from_main; |
773 | int serious_error = TRUE; |
774 | long mtime; |
775 | int attr; |
776 | int orig_file_status = NOTDONE; |
777 | |
778 | recoverymode = TRUE; |
779 | called_from_main = (curbuf->b_ml.ml_mfp == NULL); |
780 | attr = HL_ATTR(HLF_E); |
781 | |
782 | // If the file name ends in ".s[a-w][a-z]" we assume this is the swap file. |
783 | // Otherwise a search is done to find the swap file(s). |
784 | fname = curbuf->b_fname; |
785 | if (fname == NULL) /* When there is no file name */ |
786 | fname = (char_u *)"" ; |
787 | len = (int)STRLEN(fname); |
788 | if (len >= 4 |
789 | && STRNICMP(fname + len - 4, ".s" , 2) == 0 |
790 | && vim_strchr((char_u *)"abcdefghijklmnopqrstuvw" , |
791 | TOLOWER_ASC(fname[len - 2])) != NULL |
792 | && ASCII_ISALPHA(fname[len - 1])) { |
793 | directly = TRUE; |
794 | fname_used = vim_strsave(fname); /* make a copy for mf_open() */ |
795 | } else { |
796 | directly = FALSE; |
797 | |
798 | /* count the number of matching swap files */ |
799 | len = recover_names(fname, FALSE, 0, NULL); |
800 | if (len == 0) { /* no swap files found */ |
801 | EMSG2(_("E305: No swap file found for %s" ), fname); |
802 | goto theend; |
803 | } |
804 | if (len == 1) /* one swap file found, use it */ |
805 | i = 1; |
806 | else { /* several swap files found, choose */ |
807 | /* list the names of the swap files */ |
808 | (void)recover_names(fname, TRUE, 0, NULL); |
809 | msg_putchar('\n'); |
810 | MSG_PUTS(_("Enter number of swap file to use (0 to quit): " )); |
811 | i = get_number(FALSE, NULL); |
812 | if (i < 1 || i > len) |
813 | goto theend; |
814 | } |
815 | /* get the swap file name that will be used */ |
816 | (void)recover_names(fname, FALSE, i, &fname_used); |
817 | } |
818 | if (fname_used == NULL) |
819 | goto theend; // user chose invalid number. |
820 | |
821 | /* When called from main() still need to initialize storage structure */ |
822 | if (called_from_main && ml_open(curbuf) == FAIL) |
823 | getout(1); |
824 | |
825 | /* |
826 | * Allocate a buffer structure for the swap file that is used for recovery. |
827 | * Only the memline in it is really used. |
828 | */ |
829 | buf = xmalloc(sizeof(buf_T)); |
830 | |
831 | /* |
832 | * init fields in memline struct |
833 | */ |
834 | buf->b_ml.ml_stack_size = 0; /* no stack yet */ |
835 | buf->b_ml.ml_stack = NULL; /* no stack yet */ |
836 | buf->b_ml.ml_stack_top = 0; /* nothing in the stack */ |
837 | buf->b_ml.ml_line_lnum = 0; /* no cached line */ |
838 | buf->b_ml.ml_locked = NULL; /* no locked block */ |
839 | buf->b_ml.ml_flags = 0; |
840 | |
841 | /* |
842 | * open the memfile from the old swap file |
843 | */ |
844 | p = vim_strsave(fname_used); /* save "fname_used" for the message: |
845 | mf_open() will consume "fname_used"! */ |
846 | mfp = mf_open(fname_used, O_RDONLY); |
847 | fname_used = p; |
848 | if (mfp == NULL || mfp->mf_fd < 0) { |
849 | EMSG2(_("E306: Cannot open %s" ), fname_used); |
850 | goto theend; |
851 | } |
852 | buf->b_ml.ml_mfp = mfp; |
853 | |
854 | /* |
855 | * The page size set in mf_open() might be different from the page size |
856 | * used in the swap file, we must get it from block 0. But to read block |
857 | * 0 we need a page size. Use the minimal size for block 0 here, it will |
858 | * be set to the real value below. |
859 | */ |
860 | mfp->mf_page_size = MIN_SWAP_PAGE_SIZE; |
861 | |
862 | /* |
863 | * try to read block 0 |
864 | */ |
865 | if ((hp = mf_get(mfp, 0, 1)) == NULL) { |
866 | msg_start(); |
867 | MSG_PUTS_ATTR(_("Unable to read block 0 from " ), attr | MSG_HIST); |
868 | msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST); |
869 | MSG_PUTS_ATTR(_( |
870 | "\nMaybe no changes were made or Vim did not update the swap file." ), |
871 | attr | MSG_HIST); |
872 | msg_end(); |
873 | goto theend; |
874 | } |
875 | b0p = hp->bh_data; |
876 | if (STRNCMP(b0p->b0_version, "VIM 3.0" , 7) == 0) { |
877 | msg_start(); |
878 | msg_outtrans_attr(mfp->mf_fname, MSG_HIST); |
879 | MSG_PUTS_ATTR(_(" cannot be used with this version of Vim.\n" ), |
880 | MSG_HIST); |
881 | MSG_PUTS_ATTR(_("Use Vim version 3.0.\n" ), MSG_HIST); |
882 | msg_end(); |
883 | goto theend; |
884 | } |
885 | if (ml_check_b0_id(b0p) == FAIL) { |
886 | EMSG2(_("E307: %s does not look like a Vim swap file" ), mfp->mf_fname); |
887 | goto theend; |
888 | } |
889 | if (b0_magic_wrong(b0p)) { |
890 | msg_start(); |
891 | msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST); |
892 | MSG_PUTS_ATTR(_(" cannot be used on this computer.\n" ), |
893 | attr | MSG_HIST); |
894 | MSG_PUTS_ATTR(_("The file was created on " ), attr | MSG_HIST); |
895 | /* avoid going past the end of a corrupted hostname */ |
896 | b0p->b0_fname[0] = NUL; |
897 | MSG_PUTS_ATTR(b0p->b0_hname, attr | MSG_HIST); |
898 | MSG_PUTS_ATTR(_(",\nor the file has been damaged." ), attr | MSG_HIST); |
899 | msg_end(); |
900 | goto theend; |
901 | } |
902 | |
903 | /* |
904 | * If we guessed the wrong page size, we have to recalculate the |
905 | * highest block number in the file. |
906 | */ |
907 | if (mfp->mf_page_size != (unsigned)char_to_long(b0p->b0_page_size)) { |
908 | unsigned previous_page_size = mfp->mf_page_size; |
909 | |
910 | mf_new_page_size(mfp, (unsigned)char_to_long(b0p->b0_page_size)); |
911 | if (mfp->mf_page_size < previous_page_size) { |
912 | msg_start(); |
913 | msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST); |
914 | MSG_PUTS_ATTR(_( |
915 | " has been damaged (page size is smaller than minimum value).\n" ), |
916 | attr | MSG_HIST); |
917 | msg_end(); |
918 | goto theend; |
919 | } |
920 | if ((size = vim_lseek(mfp->mf_fd, (off_T)0L, SEEK_END)) <= 0) { |
921 | mfp->mf_blocknr_max = 0; // no file or empty file |
922 | } else { |
923 | mfp->mf_blocknr_max = size / mfp->mf_page_size; |
924 | } |
925 | mfp->mf_infile_count = mfp->mf_blocknr_max; |
926 | |
927 | /* need to reallocate the memory used to store the data */ |
928 | p = xmalloc(mfp->mf_page_size); |
929 | memmove(p, hp->bh_data, previous_page_size); |
930 | xfree(hp->bh_data); |
931 | hp->bh_data = p; |
932 | b0p = hp->bh_data; |
933 | } |
934 | |
935 | /* |
936 | * If .swp file name given directly, use name from swap file for buffer. |
937 | */ |
938 | if (directly) { |
939 | expand_env(b0p->b0_fname, NameBuff, MAXPATHL); |
940 | if (setfname(curbuf, NameBuff, NULL, TRUE) == FAIL) |
941 | goto theend; |
942 | } |
943 | |
944 | home_replace(NULL, mfp->mf_fname, NameBuff, MAXPATHL, TRUE); |
945 | smsg(_("Using swap file \"%s\"" ), NameBuff); |
946 | |
947 | if (buf_spname(curbuf) != NULL) |
948 | STRLCPY(NameBuff, buf_spname(curbuf), MAXPATHL); |
949 | else |
950 | home_replace(NULL, curbuf->b_ffname, NameBuff, MAXPATHL, TRUE); |
951 | smsg(_("Original file \"%s\"" ), NameBuff); |
952 | msg_putchar('\n'); |
953 | |
954 | /* |
955 | * check date of swap file and original file |
956 | */ |
957 | FileInfo org_file_info; |
958 | FileInfo swp_file_info; |
959 | mtime = char_to_long(b0p->b0_mtime); |
960 | if (curbuf->b_ffname != NULL |
961 | && os_fileinfo((char *)curbuf->b_ffname, &org_file_info) |
962 | && ((os_fileinfo((char *)mfp->mf_fname, &swp_file_info) |
963 | && org_file_info.stat.st_mtim.tv_sec |
964 | > swp_file_info.stat.st_mtim.tv_sec) |
965 | || org_file_info.stat.st_mtim.tv_sec != mtime)) { |
966 | EMSG(_("E308: Warning: Original file may have been changed" )); |
967 | } |
968 | ui_flush(); |
969 | |
970 | /* Get the 'fileformat' and 'fileencoding' from block zero. */ |
971 | b0_ff = (b0p->b0_flags & B0_FF_MASK); |
972 | if (b0p->b0_flags & B0_HAS_FENC) { |
973 | int fnsize = B0_FNAME_SIZE_NOCRYPT; |
974 | |
975 | for (p = b0p->b0_fname + fnsize; p > b0p->b0_fname && p[-1] != NUL; --p) |
976 | ; |
977 | b0_fenc = vim_strnsave(p, (int)(b0p->b0_fname + fnsize - p)); |
978 | } |
979 | |
980 | mf_put(mfp, hp, false, false); /* release block 0 */ |
981 | hp = NULL; |
982 | |
983 | /* |
984 | * Now that we are sure that the file is going to be recovered, clear the |
985 | * contents of the current buffer. |
986 | */ |
987 | while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) { |
988 | ml_delete((linenr_T)1, false); |
989 | } |
990 | |
991 | /* |
992 | * Try reading the original file to obtain the values of 'fileformat', |
993 | * 'fileencoding', etc. Ignore errors. The text itself is not used. |
994 | */ |
995 | if (curbuf->b_ffname != NULL) |
996 | orig_file_status = readfile(curbuf->b_ffname, NULL, (linenr_T)0, |
997 | (linenr_T)0, (linenr_T)MAXLNUM, NULL, READ_NEW); |
998 | |
999 | /* Use the 'fileformat' and 'fileencoding' as stored in the swap file. */ |
1000 | if (b0_ff != 0) |
1001 | set_fileformat(b0_ff - 1, OPT_LOCAL); |
1002 | if (b0_fenc != NULL) { |
1003 | set_option_value("fenc" , 0L, (char *)b0_fenc, OPT_LOCAL); |
1004 | xfree(b0_fenc); |
1005 | } |
1006 | unchanged(curbuf, true, true); |
1007 | |
1008 | bnum = 1; /* start with block 1 */ |
1009 | page_count = 1; /* which is 1 page */ |
1010 | lnum = 0; /* append after line 0 in curbuf */ |
1011 | line_count = 0; |
1012 | idx = 0; /* start with first index in block 1 */ |
1013 | error = 0; |
1014 | buf->b_ml.ml_stack_top = 0; |
1015 | buf->b_ml.ml_stack = NULL; |
1016 | buf->b_ml.ml_stack_size = 0; /* no stack yet */ |
1017 | |
1018 | if (curbuf->b_ffname == NULL) |
1019 | cannot_open = TRUE; |
1020 | else |
1021 | cannot_open = FALSE; |
1022 | |
1023 | serious_error = FALSE; |
1024 | for (; !got_int; line_breakcheck()) { |
1025 | if (hp != NULL) |
1026 | mf_put(mfp, hp, false, false); /* release previous block */ |
1027 | |
1028 | /* |
1029 | * get block |
1030 | */ |
1031 | if ((hp = mf_get(mfp, bnum, page_count)) == NULL) { |
1032 | if (bnum == 1) { |
1033 | EMSG2(_("E309: Unable to read block 1 from %s" ), mfp->mf_fname); |
1034 | goto theend; |
1035 | } |
1036 | ++error; |
1037 | ml_append(lnum++, (char_u *)_("???MANY LINES MISSING" ), |
1038 | (colnr_T)0, true); |
1039 | } else { // there is a block |
1040 | pp = hp->bh_data; |
1041 | if (pp->pb_id == PTR_ID) { /* it is a pointer block */ |
1042 | /* check line count when using pointer block first time */ |
1043 | if (idx == 0 && line_count != 0) { |
1044 | for (i = 0; i < (int)pp->pb_count; ++i) |
1045 | line_count -= pp->pb_pointer[i].pe_line_count; |
1046 | if (line_count != 0) { |
1047 | ++error; |
1048 | ml_append(lnum++, (char_u *)_("???LINE COUNT WRONG" ), |
1049 | (colnr_T)0, true); |
1050 | } |
1051 | } |
1052 | |
1053 | if (pp->pb_count == 0) { |
1054 | ml_append(lnum++, (char_u *)_("???EMPTY BLOCK" ), |
1055 | (colnr_T)0, true); |
1056 | error++; |
1057 | } else if (idx < (int)pp->pb_count) { // go a block deeper |
1058 | if (pp->pb_pointer[idx].pe_bnum < 0) { |
1059 | /* |
1060 | * Data block with negative block number. |
1061 | * Try to read lines from the original file. |
1062 | * This is slow, but it works. |
1063 | */ |
1064 | if (!cannot_open) { |
1065 | line_count = pp->pb_pointer[idx].pe_line_count; |
1066 | if (readfile(curbuf->b_ffname, NULL, lnum, |
1067 | pp->pb_pointer[idx].pe_old_lnum - 1, line_count, |
1068 | NULL, 0) != OK) { |
1069 | cannot_open = true; |
1070 | } else { |
1071 | lnum += line_count; |
1072 | } |
1073 | } |
1074 | if (cannot_open) { |
1075 | ++error; |
1076 | ml_append(lnum++, (char_u *)_("???LINES MISSING" ), |
1077 | (colnr_T)0, true); |
1078 | } |
1079 | ++idx; /* get same block again for next index */ |
1080 | continue; |
1081 | } |
1082 | |
1083 | /* |
1084 | * going one block deeper in the tree |
1085 | */ |
1086 | top = ml_add_stack(buf); // new entry in stack |
1087 | ip = &(buf->b_ml.ml_stack[top]); |
1088 | ip->ip_bnum = bnum; |
1089 | ip->ip_index = idx; |
1090 | |
1091 | bnum = pp->pb_pointer[idx].pe_bnum; |
1092 | line_count = pp->pb_pointer[idx].pe_line_count; |
1093 | page_count = pp->pb_pointer[idx].pe_page_count; |
1094 | idx = 0; |
1095 | continue; |
1096 | } |
1097 | } else { /* not a pointer block */ |
1098 | dp = hp->bh_data; |
1099 | if (dp->db_id != DATA_ID) { /* block id wrong */ |
1100 | if (bnum == 1) { |
1101 | EMSG2(_("E310: Block 1 ID wrong (%s not a .swp file?)" ), |
1102 | mfp->mf_fname); |
1103 | goto theend; |
1104 | } |
1105 | ++error; |
1106 | ml_append(lnum++, (char_u *)_("???BLOCK MISSING" ), |
1107 | (colnr_T)0, true); |
1108 | } else { |
1109 | // it is a data block |
1110 | // Append all the lines in this block |
1111 | has_error = false; |
1112 | // check length of block |
1113 | // if wrong, use length in pointer block |
1114 | if (page_count * mfp->mf_page_size != dp->db_txt_end) { |
1115 | ml_append( |
1116 | lnum++, |
1117 | (char_u *)_("??? from here until ???END lines" |
1118 | " may be messed up" ), |
1119 | (colnr_T)0, true); |
1120 | error++; |
1121 | has_error = true; |
1122 | dp->db_txt_end = page_count * mfp->mf_page_size; |
1123 | } |
1124 | |
1125 | /* make sure there is a NUL at the end of the block */ |
1126 | *((char_u *)dp + dp->db_txt_end - 1) = NUL; |
1127 | |
1128 | /* |
1129 | * check number of lines in block |
1130 | * if wrong, use count in data block |
1131 | */ |
1132 | if (line_count != dp->db_line_count) { |
1133 | ml_append( |
1134 | lnum++, |
1135 | (char_u *)_("??? from here until ???END lines" |
1136 | " may have been inserted/deleted" ), |
1137 | (colnr_T)0, true); |
1138 | error++; |
1139 | has_error = true; |
1140 | } |
1141 | |
1142 | for (i = 0; i < dp->db_line_count; ++i) { |
1143 | txt_start = (dp->db_index[i] & DB_INDEX_MASK); |
1144 | if (txt_start <= (int)HEADER_SIZE |
1145 | || txt_start >= (int)dp->db_txt_end) { |
1146 | p = (char_u *)"???" ; |
1147 | ++error; |
1148 | } else |
1149 | p = (char_u *)dp + txt_start; |
1150 | ml_append(lnum++, p, (colnr_T)0, true); |
1151 | } |
1152 | if (has_error) { |
1153 | ml_append(lnum++, (char_u *)_("???END" ), (colnr_T)0, true); |
1154 | } |
1155 | } |
1156 | } |
1157 | } |
1158 | |
1159 | if (buf->b_ml.ml_stack_top == 0) /* finished */ |
1160 | break; |
1161 | |
1162 | /* |
1163 | * go one block up in the tree |
1164 | */ |
1165 | ip = &(buf->b_ml.ml_stack[--(buf->b_ml.ml_stack_top)]); |
1166 | bnum = ip->ip_bnum; |
1167 | idx = ip->ip_index + 1; /* go to next index */ |
1168 | page_count = 1; |
1169 | } |
1170 | |
1171 | /* |
1172 | * Compare the buffer contents with the original file. When they differ |
1173 | * set the 'modified' flag. |
1174 | * Lines 1 - lnum are the new contents. |
1175 | * Lines lnum + 1 to ml_line_count are the original contents. |
1176 | * Line ml_line_count + 1 in the dummy empty line. |
1177 | */ |
1178 | if (orig_file_status != OK || curbuf->b_ml.ml_line_count != lnum * 2 + 1) { |
1179 | /* Recovering an empty file results in two lines and the first line is |
1180 | * empty. Don't set the modified flag then. */ |
1181 | if (!(curbuf->b_ml.ml_line_count == 2 && *ml_get(1) == NUL)) { |
1182 | changed_internal(); |
1183 | buf_inc_changedtick(curbuf); |
1184 | } |
1185 | } else { |
1186 | for (idx = 1; idx <= lnum; ++idx) { |
1187 | /* Need to copy one line, fetching the other one may flush it. */ |
1188 | p = vim_strsave(ml_get(idx)); |
1189 | i = STRCMP(p, ml_get(idx + lnum)); |
1190 | xfree(p); |
1191 | if (i != 0) { |
1192 | changed_internal(); |
1193 | buf_inc_changedtick(curbuf); |
1194 | break; |
1195 | } |
1196 | } |
1197 | } |
1198 | |
1199 | /* |
1200 | * Delete the lines from the original file and the dummy line from the |
1201 | * empty buffer. These will now be after the last line in the buffer. |
1202 | */ |
1203 | while (curbuf->b_ml.ml_line_count > lnum |
1204 | && !(curbuf->b_ml.ml_flags & ML_EMPTY)) |
1205 | ml_delete(curbuf->b_ml.ml_line_count, false); |
1206 | curbuf->b_flags |= BF_RECOVERED; |
1207 | |
1208 | recoverymode = FALSE; |
1209 | if (got_int) |
1210 | EMSG(_("E311: Recovery Interrupted" )); |
1211 | else if (error) { |
1212 | ++no_wait_return; |
1213 | MSG(">>>>>>>>>>>>>" ); |
1214 | EMSG(_( |
1215 | "E312: Errors detected while recovering; look for lines starting with ???" )); |
1216 | --no_wait_return; |
1217 | MSG(_("See \":help E312\" for more information." )); |
1218 | MSG(">>>>>>>>>>>>>" ); |
1219 | } else { |
1220 | if (curbuf->b_changed) { |
1221 | MSG(_("Recovery completed. You should check if everything is OK." )); |
1222 | MSG_PUTS(_( |
1223 | "\n(You might want to write out this file under another name\n" )); |
1224 | MSG_PUTS(_("and run diff with the original file to check for changes)" )); |
1225 | } else |
1226 | MSG(_("Recovery completed. Buffer contents equals file contents." )); |
1227 | MSG_PUTS(_("\nYou may want to delete the .swp file now.\n\n" )); |
1228 | cmdline_row = msg_row; |
1229 | } |
1230 | redraw_curbuf_later(NOT_VALID); |
1231 | |
1232 | theend: |
1233 | xfree(fname_used); |
1234 | recoverymode = FALSE; |
1235 | if (mfp != NULL) { |
1236 | if (hp != NULL) |
1237 | mf_put(mfp, hp, false, false); |
1238 | mf_close(mfp, false); /* will also xfree(mfp->mf_fname) */ |
1239 | } |
1240 | if (buf != NULL) { //may be NULL if swap file not found. |
1241 | xfree(buf->b_ml.ml_stack); |
1242 | xfree(buf); |
1243 | } |
1244 | if (serious_error && called_from_main) |
1245 | ml_close(curbuf, TRUE); |
1246 | else { |
1247 | apply_autocmds(EVENT_BUFREADPOST, NULL, curbuf->b_fname, FALSE, curbuf); |
1248 | apply_autocmds(EVENT_BUFWINENTER, NULL, curbuf->b_fname, FALSE, curbuf); |
1249 | } |
1250 | return; |
1251 | } |
1252 | |
1253 | /* |
1254 | * Find the names of swap files in current directory and the directory given |
1255 | * with the 'directory' option. |
1256 | * |
1257 | * Used to: |
1258 | * - list the swap files for "vim -r" |
1259 | * - count the number of swap files when recovering |
1260 | * - list the swap files when recovering |
1261 | * - find the name of the n'th swap file when recovering |
1262 | */ |
1263 | int |
1264 | recover_names ( |
1265 | char_u *fname, /* base for swap file name */ |
1266 | int list, /* when TRUE, list the swap file names */ |
1267 | int nr, /* when non-zero, return nr'th swap file name */ |
1268 | char_u **fname_out /* result when "nr" > 0 */ |
1269 | ) |
1270 | { |
1271 | int num_names; |
1272 | char_u *(names[6]); |
1273 | char_u *tail; |
1274 | char_u *p; |
1275 | int num_files; |
1276 | int file_count = 0; |
1277 | char_u **files; |
1278 | char_u *dirp; |
1279 | char_u *dir_name; |
1280 | char_u *fname_res = NULL; |
1281 | #ifdef HAVE_READLINK |
1282 | char_u fname_buf[MAXPATHL]; |
1283 | #endif |
1284 | |
1285 | if (fname != NULL) { |
1286 | #ifdef HAVE_READLINK |
1287 | /* Expand symlink in the file name, because the swap file is created |
1288 | * with the actual file instead of with the symlink. */ |
1289 | if (resolve_symlink(fname, fname_buf) == OK) |
1290 | fname_res = fname_buf; |
1291 | else |
1292 | #endif |
1293 | fname_res = fname; |
1294 | } |
1295 | |
1296 | if (list) { |
1297 | /* use msg() to start the scrolling properly */ |
1298 | msg((char_u *)_("Swap files found:" )); |
1299 | msg_putchar('\n'); |
1300 | } |
1301 | |
1302 | // Do the loop for every directory in 'directory'. |
1303 | // First allocate some memory to put the directory name in. |
1304 | dir_name = xmalloc(STRLEN(p_dir) + 1); |
1305 | dirp = p_dir; |
1306 | while (*dirp) { |
1307 | // Isolate a directory name from *dirp and put it in dir_name (we know |
1308 | // it is large enough, so use 31000 for length). |
1309 | // Advance dirp to next directory name. |
1310 | (void)copy_option_part(&dirp, dir_name, 31000, "," ); |
1311 | |
1312 | if (dir_name[0] == '.' && dir_name[1] == NUL) { /* check current dir */ |
1313 | if (fname == NULL) { |
1314 | names[0] = vim_strsave((char_u *)"*.sw?" ); |
1315 | /* For Unix names starting with a dot are special. MS-Windows |
1316 | * supports this too, on some file systems. */ |
1317 | names[1] = vim_strsave((char_u *)".*.sw?" ); |
1318 | names[2] = vim_strsave((char_u *)".sw?" ); |
1319 | num_names = 3; |
1320 | } else |
1321 | num_names = recov_file_names(names, fname_res, TRUE); |
1322 | } else { /* check directory dir_name */ |
1323 | if (fname == NULL) { |
1324 | names[0] = (char_u *)concat_fnames((char *)dir_name, "*.sw?" , TRUE); |
1325 | /* For Unix names starting with a dot are special. MS-Windows |
1326 | * supports this too, on some file systems. */ |
1327 | names[1] = (char_u *)concat_fnames((char *)dir_name, ".*.sw?" , TRUE); |
1328 | names[2] = (char_u *)concat_fnames((char *)dir_name, ".sw?" , TRUE); |
1329 | num_names = 3; |
1330 | } else { |
1331 | int len = (int)STRLEN(dir_name); |
1332 | p = dir_name + len; |
1333 | if (after_pathsep((char *)dir_name, (char *)p) |
1334 | && len > 1 |
1335 | && p[-1] == p[-2]) { |
1336 | // Ends with '//', Use Full path for swap name |
1337 | tail = (char_u *)make_percent_swname((char *)dir_name, |
1338 | (char *)fname_res); |
1339 | } else { |
1340 | tail = path_tail(fname_res); |
1341 | tail = (char_u *)concat_fnames((char *)dir_name, (char *)tail, TRUE); |
1342 | } |
1343 | num_names = recov_file_names(names, tail, FALSE); |
1344 | xfree(tail); |
1345 | } |
1346 | } |
1347 | |
1348 | if (num_names == 0) |
1349 | num_files = 0; |
1350 | else if (expand_wildcards(num_names, names, &num_files, &files, |
1351 | EW_KEEPALL|EW_FILE|EW_SILENT) == FAIL) |
1352 | num_files = 0; |
1353 | |
1354 | /* |
1355 | * When no swap file found, wildcard expansion might have failed (e.g. |
1356 | * not able to execute the shell). |
1357 | * Try finding a swap file by simply adding ".swp" to the file name. |
1358 | */ |
1359 | if (*dirp == NUL && file_count + num_files == 0 && fname != NULL) { |
1360 | char_u *swapname = (char_u *)modname((char *)fname_res, ".swp" , TRUE); |
1361 | if (swapname != NULL) { |
1362 | if (os_path_exists(swapname)) { |
1363 | files = xmalloc(sizeof(char_u *)); |
1364 | files[0] = swapname; |
1365 | swapname = NULL; |
1366 | num_files = 1; |
1367 | } |
1368 | xfree(swapname); |
1369 | } |
1370 | } |
1371 | |
1372 | /* |
1373 | * remove swapfile name of the current buffer, it must be ignored |
1374 | */ |
1375 | if (curbuf->b_ml.ml_mfp != NULL |
1376 | && (p = curbuf->b_ml.ml_mfp->mf_fname) != NULL) { |
1377 | for (int i = 0; i < num_files; i++) { |
1378 | if (path_full_compare(p, files[i], true) & kEqualFiles) { |
1379 | // Remove the name from files[i]. Move further entries |
1380 | // down. When the array becomes empty free it here, since |
1381 | // FreeWild() won't be called below. |
1382 | xfree(files[i]); |
1383 | if (--num_files == 0) |
1384 | xfree(files); |
1385 | else |
1386 | for (; i < num_files; ++i) |
1387 | files[i] = files[i + 1]; |
1388 | } |
1389 | } |
1390 | } |
1391 | if (nr > 0) { |
1392 | file_count += num_files; |
1393 | if (nr <= file_count) { |
1394 | *fname_out = vim_strsave( |
1395 | files[nr - 1 + num_files - file_count]); |
1396 | dirp = (char_u *)"" ; /* stop searching */ |
1397 | } |
1398 | } else if (list) { |
1399 | if (dir_name[0] == '.' && dir_name[1] == NUL) { |
1400 | if (fname == NULL) |
1401 | MSG_PUTS(_(" In current directory:\n" )); |
1402 | else |
1403 | MSG_PUTS(_(" Using specified name:\n" )); |
1404 | } else { |
1405 | MSG_PUTS(_(" In directory " )); |
1406 | msg_home_replace(dir_name); |
1407 | MSG_PUTS(":\n" ); |
1408 | } |
1409 | |
1410 | if (num_files) { |
1411 | for (int i = 0; i < num_files; ++i) { |
1412 | /* print the swap file name */ |
1413 | msg_outnum((long)++file_count); |
1414 | msg_puts(". " ); |
1415 | msg_puts((const char *)path_tail(files[i])); |
1416 | msg_putchar('\n'); |
1417 | (void)swapfile_info(files[i]); |
1418 | } |
1419 | } else |
1420 | MSG_PUTS(_(" -- none --\n" )); |
1421 | ui_flush(); |
1422 | } else |
1423 | file_count += num_files; |
1424 | |
1425 | for (int i = 0; i < num_names; ++i) |
1426 | xfree(names[i]); |
1427 | if (num_files > 0) |
1428 | FreeWild(num_files, files); |
1429 | } |
1430 | xfree(dir_name); |
1431 | return file_count; |
1432 | } |
1433 | |
1434 | /* |
1435 | * Append the full path to name with path separators made into percent |
1436 | * signs, to dir. An unnamed buffer is handled as "" (<currentdir>/"") |
1437 | */ |
1438 | static char *make_percent_swname(const char *dir, char *name) |
1439 | FUNC_ATTR_NONNULL_ARG(1) |
1440 | { |
1441 | char *d = NULL; |
1442 | char *f = fix_fname(name != NULL ? name : "" ); |
1443 | if (f != NULL) { |
1444 | char *s = xstrdup(f); |
1445 | for (d = s; *d != NUL; MB_PTR_ADV(d)) { |
1446 | if (vim_ispathsep(*d)) { |
1447 | *d = '%'; |
1448 | } |
1449 | } |
1450 | d = concat_fnames(dir, s, TRUE); |
1451 | xfree(s); |
1452 | xfree(f); |
1453 | } |
1454 | return d; |
1455 | } |
1456 | |
1457 | static bool process_still_running; |
1458 | |
1459 | /// Return information found in swapfile "fname" in dictionary "d". |
1460 | /// This is used by the swapinfo() function. |
1461 | void get_b0_dict(const char *fname, dict_T *d) |
1462 | { |
1463 | int fd; |
1464 | struct block0 b0; |
1465 | |
1466 | if ((fd = os_open(fname, O_RDONLY, 0)) >= 0) { |
1467 | if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) { |
1468 | if (ml_check_b0_id(&b0) == FAIL) { |
1469 | tv_dict_add_str(d, S_LEN("error" ), "Not a swap file" ); |
1470 | } else if (b0_magic_wrong(&b0)) { |
1471 | tv_dict_add_str(d, S_LEN("error" ), "Magic number mismatch" ); |
1472 | } else { |
1473 | // We have swap information. |
1474 | tv_dict_add_str_len(d, S_LEN("version" ), (char *)b0.b0_version, 10); |
1475 | tv_dict_add_str_len(d, S_LEN("user" ), (char *)b0.b0_uname, |
1476 | B0_UNAME_SIZE); |
1477 | tv_dict_add_str_len(d, S_LEN("host" ), (char *)b0.b0_hname, |
1478 | B0_HNAME_SIZE); |
1479 | tv_dict_add_str_len(d, S_LEN("fname" ), (char *)b0.b0_fname, |
1480 | B0_FNAME_SIZE_ORG); |
1481 | |
1482 | tv_dict_add_nr(d, S_LEN("pid" ), char_to_long(b0.b0_pid)); |
1483 | tv_dict_add_nr(d, S_LEN("mtime" ), char_to_long(b0.b0_mtime)); |
1484 | tv_dict_add_nr(d, S_LEN("dirty" ), b0.b0_dirty ? 1 : 0); |
1485 | tv_dict_add_nr(d, S_LEN("inode" ), char_to_long(b0.b0_ino)); |
1486 | } |
1487 | } else { |
1488 | tv_dict_add_str(d, S_LEN("error" ), "Cannot read file" ); |
1489 | } |
1490 | close(fd); |
1491 | } else { |
1492 | tv_dict_add_str(d, S_LEN("error" ), "Cannot open file" ); |
1493 | } |
1494 | } |
1495 | |
1496 | /// Give information about an existing swap file. |
1497 | /// Returns timestamp (0 when unknown). |
1498 | static time_t swapfile_info(char_u *fname) |
1499 | { |
1500 | assert(fname != NULL); |
1501 | int fd; |
1502 | struct block0 b0; |
1503 | time_t x = (time_t)0; |
1504 | char *p; |
1505 | #ifdef UNIX |
1506 | char uname[B0_UNAME_SIZE]; |
1507 | #endif |
1508 | |
1509 | /* print the swap file date */ |
1510 | FileInfo file_info; |
1511 | if (os_fileinfo((char *)fname, &file_info)) { |
1512 | #ifdef UNIX |
1513 | /* print name of owner of the file */ |
1514 | if (os_get_uname(file_info.stat.st_uid, uname, B0_UNAME_SIZE) == OK) { |
1515 | MSG_PUTS(_(" owned by: " )); |
1516 | msg_outtrans((char_u *)uname); |
1517 | MSG_PUTS(_(" dated: " )); |
1518 | } else |
1519 | #endif |
1520 | MSG_PUTS(_(" dated: " )); |
1521 | x = file_info.stat.st_mtim.tv_sec; |
1522 | p = ctime(&x); // includes '\n' |
1523 | if (p == NULL) |
1524 | MSG_PUTS("(invalid)\n" ); |
1525 | else |
1526 | MSG_PUTS(p); |
1527 | } |
1528 | |
1529 | /* |
1530 | * print the original file name |
1531 | */ |
1532 | fd = os_open((char *)fname, O_RDONLY, 0); |
1533 | if (fd >= 0) { |
1534 | if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) { |
1535 | if (STRNCMP(b0.b0_version, "VIM 3.0" , 7) == 0) { |
1536 | MSG_PUTS(_(" [from Vim version 3.0]" )); |
1537 | } else if (ml_check_b0_id(&b0) == FAIL) { |
1538 | MSG_PUTS(_(" [does not look like a Vim swap file]" )); |
1539 | } else if (!ml_check_b0_strings(&b0)) { |
1540 | MSG_PUTS(_(" [garbled strings (not nul terminated)]" )); |
1541 | } else { |
1542 | MSG_PUTS(_(" file name: " )); |
1543 | if (b0.b0_fname[0] == NUL) |
1544 | MSG_PUTS(_("[No Name]" )); |
1545 | else |
1546 | msg_outtrans(b0.b0_fname); |
1547 | |
1548 | MSG_PUTS(_("\n modified: " )); |
1549 | MSG_PUTS(b0.b0_dirty ? _("YES" ) : _("no" )); |
1550 | |
1551 | if (*(b0.b0_uname) != NUL) { |
1552 | MSG_PUTS(_("\n user name: " )); |
1553 | msg_outtrans(b0.b0_uname); |
1554 | } |
1555 | |
1556 | if (*(b0.b0_hname) != NUL) { |
1557 | if (*(b0.b0_uname) != NUL) |
1558 | MSG_PUTS(_(" host name: " )); |
1559 | else |
1560 | MSG_PUTS(_("\n host name: " )); |
1561 | msg_outtrans(b0.b0_hname); |
1562 | } |
1563 | |
1564 | if (char_to_long(b0.b0_pid) != 0L) { |
1565 | MSG_PUTS(_("\n process ID: " )); |
1566 | msg_outnum(char_to_long(b0.b0_pid)); |
1567 | if (os_proc_running((int)char_to_long(b0.b0_pid))) { |
1568 | MSG_PUTS(_(" (STILL RUNNING)" )); |
1569 | process_still_running = true; |
1570 | } |
1571 | } |
1572 | |
1573 | if (b0_magic_wrong(&b0)) { |
1574 | MSG_PUTS(_("\n [not usable on this computer]" )); |
1575 | } |
1576 | } |
1577 | } else |
1578 | MSG_PUTS(_(" [cannot be read]" )); |
1579 | close(fd); |
1580 | } else |
1581 | MSG_PUTS(_(" [cannot be opened]" )); |
1582 | msg_putchar('\n'); |
1583 | |
1584 | return x; |
1585 | } |
1586 | |
1587 | /// Returns TRUE if the swap file looks OK and there are no changes, thus it |
1588 | /// can be safely deleted. |
1589 | static time_t swapfile_unchanged(char *fname) |
1590 | { |
1591 | struct block0 b0; |
1592 | int ret = true; |
1593 | |
1594 | // Swap file must exist. |
1595 | if (!os_path_exists((char_u *)fname)) { |
1596 | return false; |
1597 | } |
1598 | |
1599 | // must be able to read the first block |
1600 | int fd = os_open(fname, O_RDONLY, 0); |
1601 | if (fd < 0) { |
1602 | return false; |
1603 | } |
1604 | if (read_eintr(fd, &b0, sizeof(b0)) != sizeof(b0)) { |
1605 | close(fd); |
1606 | return false; |
1607 | } |
1608 | |
1609 | // the ID and magic number must be correct |
1610 | if (ml_check_b0_id(&b0) == FAIL|| b0_magic_wrong(&b0)) { |
1611 | ret = false; |
1612 | } |
1613 | |
1614 | // must be unchanged |
1615 | if (b0.b0_dirty) { |
1616 | ret = false; |
1617 | } |
1618 | |
1619 | // process must be known and not running. |
1620 | long pid = char_to_long(b0.b0_pid); |
1621 | if (pid == 0L || os_proc_running((int)pid)) { |
1622 | ret = false; |
1623 | } |
1624 | |
1625 | // TODO(bram): Should we check if the swap file was created on the current |
1626 | // system? And the current user? |
1627 | |
1628 | close(fd); |
1629 | return ret; |
1630 | } |
1631 | |
1632 | static int recov_file_names(char_u **names, char_u *path, int prepend_dot) |
1633 | FUNC_ATTR_NONNULL_ALL |
1634 | { |
1635 | int num_names = 0; |
1636 | |
1637 | // May also add the file name with a dot prepended, for swap file in same |
1638 | // dir as original file. |
1639 | if (prepend_dot) { |
1640 | names[num_names] = (char_u *)modname((char *)path, ".sw?" , TRUE); |
1641 | if (names[num_names] == NULL) |
1642 | return num_names; |
1643 | ++num_names; |
1644 | } |
1645 | |
1646 | // Form the normal swap file name pattern by appending ".sw?". |
1647 | names[num_names] = (char_u *)concat_fnames((char *)path, ".sw?" , FALSE); |
1648 | if (num_names >= 1) { /* check if we have the same name twice */ |
1649 | char_u *p = names[num_names - 1]; |
1650 | int i = (int)STRLEN(names[num_names - 1]) - (int)STRLEN(names[num_names]); |
1651 | if (i > 0) |
1652 | p += i; /* file name has been expanded to full path */ |
1653 | |
1654 | if (STRCMP(p, names[num_names]) != 0) |
1655 | ++num_names; |
1656 | else |
1657 | xfree(names[num_names]); |
1658 | } else |
1659 | ++num_names; |
1660 | |
1661 | return num_names; |
1662 | } |
1663 | |
1664 | /* |
1665 | * sync all memlines |
1666 | * |
1667 | * If 'check_file' is TRUE, check if original file exists and was not changed. |
1668 | * If 'check_char' is TRUE, stop syncing when character becomes available, but |
1669 | * always sync at least one block. |
1670 | */ |
1671 | void ml_sync_all(int check_file, int check_char, bool do_fsync) |
1672 | { |
1673 | FOR_ALL_BUFFERS(buf) { |
1674 | if (buf->b_ml.ml_mfp == NULL || buf->b_ml.ml_mfp->mf_fname == NULL) |
1675 | continue; /* no file */ |
1676 | |
1677 | ml_flush_line(buf); /* flush buffered line */ |
1678 | /* flush locked block */ |
1679 | (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); |
1680 | if (bufIsChanged(buf) && check_file && mf_need_trans(buf->b_ml.ml_mfp) |
1681 | && buf->b_ffname != NULL) { |
1682 | /* |
1683 | * If the original file does not exist anymore or has been changed |
1684 | * call ml_preserve() to get rid of all negative numbered blocks. |
1685 | */ |
1686 | FileInfo file_info; |
1687 | if (!os_fileinfo((char *)buf->b_ffname, &file_info) |
1688 | || file_info.stat.st_mtim.tv_sec != buf->b_mtime_read |
1689 | || os_fileinfo_size(&file_info) != buf->b_orig_size) { |
1690 | ml_preserve(buf, false, do_fsync); |
1691 | did_check_timestamps = false; |
1692 | need_check_timestamps = true; // give message later |
1693 | } |
1694 | } |
1695 | if (buf->b_ml.ml_mfp->mf_dirty) { |
1696 | (void)mf_sync(buf->b_ml.ml_mfp, (check_char ? MFS_STOP : 0) |
1697 | | (do_fsync && bufIsChanged(buf) ? MFS_FLUSH : 0)); |
1698 | if (check_char && os_char_avail()) { // character available now |
1699 | break; |
1700 | } |
1701 | } |
1702 | } |
1703 | } |
1704 | |
1705 | /* |
1706 | * sync one buffer, including negative blocks |
1707 | * |
1708 | * after this all the blocks are in the swap file |
1709 | * |
1710 | * Used for the :preserve command and when the original file has been |
1711 | * changed or deleted. |
1712 | * |
1713 | * when message is TRUE the success of preserving is reported |
1714 | */ |
1715 | void ml_preserve(buf_T *buf, int message, bool do_fsync) |
1716 | { |
1717 | bhdr_T *hp; |
1718 | linenr_T lnum; |
1719 | memfile_T *mfp = buf->b_ml.ml_mfp; |
1720 | int status; |
1721 | int got_int_save = got_int; |
1722 | |
1723 | if (mfp == NULL || mfp->mf_fname == NULL) { |
1724 | if (message) |
1725 | EMSG(_("E313: Cannot preserve, there is no swap file" )); |
1726 | return; |
1727 | } |
1728 | |
1729 | /* We only want to stop when interrupted here, not when interrupted |
1730 | * before. */ |
1731 | got_int = FALSE; |
1732 | |
1733 | ml_flush_line(buf); // flush buffered line |
1734 | (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush locked block |
1735 | status = mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0)); |
1736 | |
1737 | /* stack is invalid after mf_sync(.., MFS_ALL) */ |
1738 | buf->b_ml.ml_stack_top = 0; |
1739 | |
1740 | /* |
1741 | * Some of the data blocks may have been changed from negative to |
1742 | * positive block number. In that case the pointer blocks need to be |
1743 | * updated. |
1744 | * |
1745 | * We don't know in which pointer block the references are, so we visit |
1746 | * all data blocks until there are no more translations to be done (or |
1747 | * we hit the end of the file, which can only happen in case a write fails, |
1748 | * e.g. when file system if full). |
1749 | * ml_find_line() does the work by translating the negative block numbers |
1750 | * when getting the first line of each data block. |
1751 | */ |
1752 | if (mf_need_trans(mfp) && !got_int) { |
1753 | lnum = 1; |
1754 | while (mf_need_trans(mfp) && lnum <= buf->b_ml.ml_line_count) { |
1755 | hp = ml_find_line(buf, lnum, ML_FIND); |
1756 | if (hp == NULL) { |
1757 | status = FAIL; |
1758 | goto theend; |
1759 | } |
1760 | CHECK(buf->b_ml.ml_locked_low != lnum, "low != lnum" ); |
1761 | lnum = buf->b_ml.ml_locked_high + 1; |
1762 | } |
1763 | (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush locked block |
1764 | // sync the updated pointer blocks |
1765 | if (mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0)) == FAIL) { |
1766 | status = FAIL; |
1767 | } |
1768 | buf->b_ml.ml_stack_top = 0; // stack is invalid now |
1769 | } |
1770 | theend: |
1771 | got_int |= got_int_save; |
1772 | |
1773 | if (message) { |
1774 | if (status == OK) |
1775 | MSG(_("File preserved" )); |
1776 | else |
1777 | EMSG(_("E314: Preserve failed" )); |
1778 | } |
1779 | } |
1780 | |
1781 | /* |
1782 | * NOTE: The pointer returned by the ml_get_*() functions only remains valid |
1783 | * until the next call! |
1784 | * line1 = ml_get(1); |
1785 | * line2 = ml_get(2); // line1 is now invalid! |
1786 | * Make a copy of the line if necessary. |
1787 | */ |
1788 | /* |
1789 | * Return a pointer to a (read-only copy of a) line. |
1790 | * |
1791 | * On failure an error message is given and IObuff is returned (to avoid |
1792 | * having to check for error everywhere). |
1793 | */ |
1794 | char_u *ml_get(linenr_T lnum) |
1795 | { |
1796 | return ml_get_buf(curbuf, lnum, FALSE); |
1797 | } |
1798 | |
1799 | /* |
1800 | * Return pointer to position "pos". |
1801 | */ |
1802 | char_u *ml_get_pos(pos_T *pos) |
1803 | { |
1804 | return ml_get_buf(curbuf, pos->lnum, FALSE) + pos->col; |
1805 | } |
1806 | |
1807 | /* |
1808 | * Return a pointer to a line in a specific buffer |
1809 | * |
1810 | * "will_change": if TRUE mark the buffer dirty (chars in the line will be |
1811 | * changed) |
1812 | */ |
1813 | char_u * |
1814 | ml_get_buf ( |
1815 | buf_T *buf, |
1816 | linenr_T lnum, |
1817 | bool will_change // line will be changed |
1818 | ) |
1819 | { |
1820 | bhdr_T *hp; |
1821 | DATA_BL *dp; |
1822 | char_u *ptr; |
1823 | static int recursive = 0; |
1824 | |
1825 | if (lnum > buf->b_ml.ml_line_count) { /* invalid line number */ |
1826 | if (recursive == 0) { |
1827 | // Avoid giving this message for a recursive call, may happen when |
1828 | // the GUI redraws part of the text. |
1829 | recursive++; |
1830 | IEMSGN(_("E315: ml_get: invalid lnum: %" PRId64), lnum); |
1831 | recursive--; |
1832 | } |
1833 | errorret: |
1834 | STRCPY(IObuff, "???" ); |
1835 | return IObuff; |
1836 | } |
1837 | if (lnum <= 0) /* pretend line 0 is line 1 */ |
1838 | lnum = 1; |
1839 | |
1840 | if (buf->b_ml.ml_mfp == NULL) /* there are no lines */ |
1841 | return (char_u *)"" ; |
1842 | |
1843 | /* |
1844 | * See if it is the same line as requested last time. |
1845 | * Otherwise may need to flush last used line. |
1846 | * Don't use the last used line when 'swapfile' is reset, need to load all |
1847 | * blocks. |
1848 | */ |
1849 | if (buf->b_ml.ml_line_lnum != lnum) { |
1850 | ml_flush_line(buf); |
1851 | |
1852 | /* |
1853 | * Find the data block containing the line. |
1854 | * This also fills the stack with the blocks from the root to the data |
1855 | * block and releases any locked block. |
1856 | */ |
1857 | if ((hp = ml_find_line(buf, lnum, ML_FIND)) == NULL) { |
1858 | if (recursive == 0) { |
1859 | // Avoid giving this message for a recursive call, may happen |
1860 | // when the GUI redraws part of the text. |
1861 | recursive++; |
1862 | IEMSGN(_("E316: ml_get: cannot find line %" PRId64), lnum); |
1863 | recursive--; |
1864 | } |
1865 | goto errorret; |
1866 | } |
1867 | |
1868 | dp = hp->bh_data; |
1869 | |
1870 | ptr = (char_u *)dp + |
1871 | ((dp->db_index[lnum - buf->b_ml.ml_locked_low]) & DB_INDEX_MASK); |
1872 | buf->b_ml.ml_line_ptr = ptr; |
1873 | buf->b_ml.ml_line_lnum = lnum; |
1874 | buf->b_ml.ml_flags &= ~ML_LINE_DIRTY; |
1875 | } |
1876 | if (will_change) |
1877 | buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS); |
1878 | |
1879 | return buf->b_ml.ml_line_ptr; |
1880 | } |
1881 | |
1882 | /* |
1883 | * Check if a line that was just obtained by a call to ml_get |
1884 | * is in allocated memory. |
1885 | */ |
1886 | int ml_line_alloced(void) |
1887 | { |
1888 | return curbuf->b_ml.ml_flags & ML_LINE_DIRTY; |
1889 | } |
1890 | |
1891 | /* |
1892 | * Append a line after lnum (may be 0 to insert a line in front of the file). |
1893 | * "line" does not need to be allocated, but can't be another line in a |
1894 | * buffer, unlocking may make it invalid. |
1895 | * |
1896 | * newfile: TRUE when starting to edit a new file, meaning that pe_old_lnum |
1897 | * will be set for recovery |
1898 | * Check: The caller of this function should probably also call |
1899 | * appended_lines(). |
1900 | * |
1901 | * return FAIL for failure, OK otherwise |
1902 | */ |
1903 | int ml_append( |
1904 | linenr_T lnum, // append after this line (can be 0) |
1905 | char_u *line, // text of the new line |
1906 | colnr_T len, // length of new line, including NUL, or 0 |
1907 | bool newfile // flag, see above |
1908 | ) |
1909 | { |
1910 | /* When starting up, we might still need to create the memfile */ |
1911 | if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL) |
1912 | return FAIL; |
1913 | |
1914 | if (curbuf->b_ml.ml_line_lnum != 0) |
1915 | ml_flush_line(curbuf); |
1916 | return ml_append_int(curbuf, lnum, line, len, newfile, FALSE); |
1917 | } |
1918 | |
1919 | /* |
1920 | * Like ml_append() but for an arbitrary buffer. The buffer must already have |
1921 | * a memline. |
1922 | */ |
1923 | int ml_append_buf( |
1924 | buf_T *buf, |
1925 | linenr_T lnum, // append after this line (can be 0) |
1926 | char_u *line, // text of the new line |
1927 | colnr_T len, // length of new line, including NUL, or 0 |
1928 | bool newfile // flag, see above |
1929 | ) |
1930 | { |
1931 | if (buf->b_ml.ml_mfp == NULL) |
1932 | return FAIL; |
1933 | |
1934 | if (buf->b_ml.ml_line_lnum != 0) |
1935 | ml_flush_line(buf); |
1936 | return ml_append_int(buf, lnum, line, len, newfile, FALSE); |
1937 | } |
1938 | |
1939 | static int ml_append_int( |
1940 | buf_T *buf, |
1941 | linenr_T lnum, // append after this line (can be 0) |
1942 | char_u *line, // text of the new line |
1943 | colnr_T len, // length of line, including NUL, or 0 |
1944 | bool newfile, // flag, see above |
1945 | int mark // mark the new line |
1946 | ) |
1947 | { |
1948 | int i; |
1949 | int line_count; /* number of indexes in current block */ |
1950 | int offset; |
1951 | int from, to; |
1952 | int space_needed; /* space needed for new line */ |
1953 | int page_size; |
1954 | int page_count; |
1955 | int db_idx; /* index for lnum in data block */ |
1956 | bhdr_T *hp; |
1957 | memfile_T *mfp; |
1958 | DATA_BL *dp; |
1959 | PTR_BL *pp; |
1960 | infoptr_T *ip; |
1961 | |
1962 | /* lnum out of range */ |
1963 | if (lnum > buf->b_ml.ml_line_count || buf->b_ml.ml_mfp == NULL) |
1964 | return FAIL; |
1965 | |
1966 | if (lowest_marked && lowest_marked > lnum) |
1967 | lowest_marked = lnum + 1; |
1968 | |
1969 | if (len == 0) |
1970 | len = (colnr_T)STRLEN(line) + 1; /* space needed for the text */ |
1971 | space_needed = len + INDEX_SIZE; /* space needed for text + index */ |
1972 | |
1973 | mfp = buf->b_ml.ml_mfp; |
1974 | page_size = mfp->mf_page_size; |
1975 | |
1976 | /* |
1977 | * find the data block containing the previous line |
1978 | * This also fills the stack with the blocks from the root to the data block |
1979 | * This also releases any locked block. |
1980 | */ |
1981 | if ((hp = ml_find_line(buf, lnum == 0 ? (linenr_T)1 : lnum, |
1982 | ML_INSERT)) == NULL) |
1983 | return FAIL; |
1984 | |
1985 | buf->b_ml.ml_flags &= ~ML_EMPTY; |
1986 | |
1987 | if (lnum == 0) /* got line one instead, correct db_idx */ |
1988 | db_idx = -1; /* careful, it is negative! */ |
1989 | else |
1990 | db_idx = lnum - buf->b_ml.ml_locked_low; |
1991 | /* get line count before the insertion */ |
1992 | line_count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low; |
1993 | |
1994 | dp = hp->bh_data; |
1995 | |
1996 | /* |
1997 | * If |
1998 | * - there is not enough room in the current block |
1999 | * - appending to the last line in the block |
2000 | * - not appending to the last line in the file |
2001 | * insert in front of the next block. |
2002 | */ |
2003 | if ((int)dp->db_free < space_needed && db_idx == line_count - 1 |
2004 | && lnum < buf->b_ml.ml_line_count) { |
2005 | /* |
2006 | * Now that the line is not going to be inserted in the block that we |
2007 | * expected, the line count has to be adjusted in the pointer blocks |
2008 | * by using ml_locked_lineadd. |
2009 | */ |
2010 | --(buf->b_ml.ml_locked_lineadd); |
2011 | --(buf->b_ml.ml_locked_high); |
2012 | if ((hp = ml_find_line(buf, lnum + 1, ML_INSERT)) == NULL) |
2013 | return FAIL; |
2014 | |
2015 | db_idx = -1; /* careful, it is negative! */ |
2016 | /* get line count before the insertion */ |
2017 | line_count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low; |
2018 | CHECK(buf->b_ml.ml_locked_low != lnum + 1, "locked_low != lnum + 1" ); |
2019 | |
2020 | dp = hp->bh_data; |
2021 | } |
2022 | |
2023 | ++buf->b_ml.ml_line_count; |
2024 | |
2025 | if ((int)dp->db_free >= space_needed) { /* enough room in data block */ |
2026 | /* |
2027 | * Insert new line in existing data block, or in data block allocated above. |
2028 | */ |
2029 | dp->db_txt_start -= len; |
2030 | dp->db_free -= space_needed; |
2031 | ++(dp->db_line_count); |
2032 | |
2033 | /* |
2034 | * move the text of the lines that follow to the front |
2035 | * adjust the indexes of the lines that follow |
2036 | */ |
2037 | if (line_count > db_idx + 1) { /* if there are following lines */ |
2038 | /* |
2039 | * Offset is the start of the previous line. |
2040 | * This will become the character just after the new line. |
2041 | */ |
2042 | if (db_idx < 0) |
2043 | offset = dp->db_txt_end; |
2044 | else |
2045 | offset = ((dp->db_index[db_idx]) & DB_INDEX_MASK); |
2046 | memmove((char *)dp + dp->db_txt_start, |
2047 | (char *)dp + dp->db_txt_start + len, |
2048 | (size_t)(offset - (dp->db_txt_start + len))); |
2049 | for (i = line_count - 1; i > db_idx; --i) |
2050 | dp->db_index[i + 1] = dp->db_index[i] - len; |
2051 | dp->db_index[db_idx + 1] = offset - len; |
2052 | } else /* add line at the end */ |
2053 | dp->db_index[db_idx + 1] = dp->db_txt_start; |
2054 | |
2055 | /* |
2056 | * copy the text into the block |
2057 | */ |
2058 | memmove((char *)dp + dp->db_index[db_idx + 1], line, (size_t)len); |
2059 | if (mark) |
2060 | dp->db_index[db_idx + 1] |= DB_MARKED; |
2061 | |
2062 | /* |
2063 | * Mark the block dirty. |
2064 | */ |
2065 | buf->b_ml.ml_flags |= ML_LOCKED_DIRTY; |
2066 | if (!newfile) |
2067 | buf->b_ml.ml_flags |= ML_LOCKED_POS; |
2068 | } else { /* not enough space in data block */ |
2069 | /* |
2070 | * If there is not enough room we have to create a new data block and copy some |
2071 | * lines into it. |
2072 | * Then we have to insert an entry in the pointer block. |
2073 | * If this pointer block also is full, we go up another block, and so on, up |
2074 | * to the root if necessary. |
2075 | * The line counts in the pointer blocks have already been adjusted by |
2076 | * ml_find_line(). |
2077 | */ |
2078 | long line_count_left, line_count_right; |
2079 | int page_count_left, page_count_right; |
2080 | bhdr_T *hp_left; |
2081 | bhdr_T *hp_right; |
2082 | bhdr_T *hp_new; |
2083 | int lines_moved; |
2084 | int data_moved = 0; /* init to shut up gcc */ |
2085 | int total_moved = 0; /* init to shut up gcc */ |
2086 | DATA_BL *dp_right, *dp_left; |
2087 | int stack_idx; |
2088 | int in_left; |
2089 | int lineadd; |
2090 | blocknr_T bnum_left, bnum_right; |
2091 | linenr_T lnum_left, lnum_right; |
2092 | int pb_idx; |
2093 | PTR_BL *pp_new; |
2094 | |
2095 | /* |
2096 | * We are going to allocate a new data block. Depending on the |
2097 | * situation it will be put to the left or right of the existing |
2098 | * block. If possible we put the new line in the left block and move |
2099 | * the lines after it to the right block. Otherwise the new line is |
2100 | * also put in the right block. This method is more efficient when |
2101 | * inserting a lot of lines at one place. |
2102 | */ |
2103 | if (db_idx < 0) { /* left block is new, right block is existing */ |
2104 | lines_moved = 0; |
2105 | in_left = TRUE; |
2106 | /* space_needed does not change */ |
2107 | } else { /* left block is existing, right block is new */ |
2108 | lines_moved = line_count - db_idx - 1; |
2109 | if (lines_moved == 0) |
2110 | in_left = FALSE; /* put new line in right block */ |
2111 | /* space_needed does not change */ |
2112 | else { |
2113 | data_moved = ((dp->db_index[db_idx]) & DB_INDEX_MASK) - |
2114 | dp->db_txt_start; |
2115 | total_moved = data_moved + lines_moved * INDEX_SIZE; |
2116 | if ((int)dp->db_free + total_moved >= space_needed) { |
2117 | in_left = TRUE; /* put new line in left block */ |
2118 | space_needed = total_moved; |
2119 | } else { |
2120 | in_left = FALSE; /* put new line in right block */ |
2121 | space_needed += total_moved; |
2122 | } |
2123 | } |
2124 | } |
2125 | |
2126 | page_count = ((space_needed + HEADER_SIZE) + page_size - 1) / page_size; |
2127 | hp_new = ml_new_data(mfp, newfile, page_count); |
2128 | if (db_idx < 0) { /* left block is new */ |
2129 | hp_left = hp_new; |
2130 | hp_right = hp; |
2131 | line_count_left = 0; |
2132 | line_count_right = line_count; |
2133 | } else { /* right block is new */ |
2134 | hp_left = hp; |
2135 | hp_right = hp_new; |
2136 | line_count_left = line_count; |
2137 | line_count_right = 0; |
2138 | } |
2139 | dp_right = hp_right->bh_data; |
2140 | dp_left = hp_left->bh_data; |
2141 | bnum_left = hp_left->bh_bnum; |
2142 | bnum_right = hp_right->bh_bnum; |
2143 | page_count_left = hp_left->bh_page_count; |
2144 | page_count_right = hp_right->bh_page_count; |
2145 | |
2146 | /* |
2147 | * May move the new line into the right/new block. |
2148 | */ |
2149 | if (!in_left) { |
2150 | dp_right->db_txt_start -= len; |
2151 | dp_right->db_free -= len + INDEX_SIZE; |
2152 | dp_right->db_index[0] = dp_right->db_txt_start; |
2153 | if (mark) |
2154 | dp_right->db_index[0] |= DB_MARKED; |
2155 | |
2156 | memmove((char *)dp_right + dp_right->db_txt_start, |
2157 | line, (size_t)len); |
2158 | ++line_count_right; |
2159 | } |
2160 | /* |
2161 | * may move lines from the left/old block to the right/new one. |
2162 | */ |
2163 | if (lines_moved) { |
2164 | /* |
2165 | */ |
2166 | dp_right->db_txt_start -= data_moved; |
2167 | dp_right->db_free -= total_moved; |
2168 | memmove((char *)dp_right + dp_right->db_txt_start, |
2169 | (char *)dp_left + dp_left->db_txt_start, |
2170 | (size_t)data_moved); |
2171 | offset = dp_right->db_txt_start - dp_left->db_txt_start; |
2172 | dp_left->db_txt_start += data_moved; |
2173 | dp_left->db_free += total_moved; |
2174 | |
2175 | /* |
2176 | * update indexes in the new block |
2177 | */ |
2178 | for (to = line_count_right, from = db_idx + 1; |
2179 | from < line_count_left; ++from, ++to) |
2180 | dp_right->db_index[to] = dp->db_index[from] + offset; |
2181 | line_count_right += lines_moved; |
2182 | line_count_left -= lines_moved; |
2183 | } |
2184 | |
2185 | /* |
2186 | * May move the new line into the left (old or new) block. |
2187 | */ |
2188 | if (in_left) { |
2189 | dp_left->db_txt_start -= len; |
2190 | dp_left->db_free -= len + INDEX_SIZE; |
2191 | dp_left->db_index[line_count_left] = dp_left->db_txt_start; |
2192 | if (mark) |
2193 | dp_left->db_index[line_count_left] |= DB_MARKED; |
2194 | memmove((char *)dp_left + dp_left->db_txt_start, |
2195 | line, (size_t)len); |
2196 | ++line_count_left; |
2197 | } |
2198 | |
2199 | if (db_idx < 0) { /* left block is new */ |
2200 | lnum_left = lnum + 1; |
2201 | lnum_right = 0; |
2202 | } else { /* right block is new */ |
2203 | lnum_left = 0; |
2204 | if (in_left) |
2205 | lnum_right = lnum + 2; |
2206 | else |
2207 | lnum_right = lnum + 1; |
2208 | } |
2209 | dp_left->db_line_count = line_count_left; |
2210 | dp_right->db_line_count = line_count_right; |
2211 | |
2212 | /* |
2213 | * release the two data blocks |
2214 | * The new one (hp_new) already has a correct blocknumber. |
2215 | * The old one (hp, in ml_locked) gets a positive blocknumber if |
2216 | * we changed it and we are not editing a new file. |
2217 | */ |
2218 | if (lines_moved || in_left) |
2219 | buf->b_ml.ml_flags |= ML_LOCKED_DIRTY; |
2220 | if (!newfile && db_idx >= 0 && in_left) |
2221 | buf->b_ml.ml_flags |= ML_LOCKED_POS; |
2222 | mf_put(mfp, hp_new, true, false); |
2223 | |
2224 | /* |
2225 | * flush the old data block |
2226 | * set ml_locked_lineadd to 0, because the updating of the |
2227 | * pointer blocks is done below |
2228 | */ |
2229 | lineadd = buf->b_ml.ml_locked_lineadd; |
2230 | buf->b_ml.ml_locked_lineadd = 0; |
2231 | ml_find_line(buf, (linenr_T)0, ML_FLUSH); /* flush data block */ |
2232 | |
2233 | /* |
2234 | * update pointer blocks for the new data block |
2235 | */ |
2236 | for (stack_idx = buf->b_ml.ml_stack_top - 1; stack_idx >= 0; |
2237 | --stack_idx) { |
2238 | ip = &(buf->b_ml.ml_stack[stack_idx]); |
2239 | pb_idx = ip->ip_index; |
2240 | if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) |
2241 | return FAIL; |
2242 | pp = hp->bh_data; /* must be pointer block */ |
2243 | if (pp->pb_id != PTR_ID) { |
2244 | IEMSG(_("E317: pointer block id wrong 3" )); |
2245 | mf_put(mfp, hp, false, false); |
2246 | return FAIL; |
2247 | } |
2248 | /* |
2249 | * TODO: If the pointer block is full and we are adding at the end |
2250 | * try to insert in front of the next block |
2251 | */ |
2252 | /* block not full, add one entry */ |
2253 | if (pp->pb_count < pp->pb_count_max) { |
2254 | if (pb_idx + 1 < (int)pp->pb_count) |
2255 | memmove(&pp->pb_pointer[pb_idx + 2], |
2256 | &pp->pb_pointer[pb_idx + 1], |
2257 | (size_t)(pp->pb_count - pb_idx - 1) * sizeof(PTR_EN)); |
2258 | ++pp->pb_count; |
2259 | pp->pb_pointer[pb_idx].pe_line_count = line_count_left; |
2260 | pp->pb_pointer[pb_idx].pe_bnum = bnum_left; |
2261 | pp->pb_pointer[pb_idx].pe_page_count = page_count_left; |
2262 | pp->pb_pointer[pb_idx + 1].pe_line_count = line_count_right; |
2263 | pp->pb_pointer[pb_idx + 1].pe_bnum = bnum_right; |
2264 | pp->pb_pointer[pb_idx + 1].pe_page_count = page_count_right; |
2265 | |
2266 | if (lnum_left != 0) |
2267 | pp->pb_pointer[pb_idx].pe_old_lnum = lnum_left; |
2268 | if (lnum_right != 0) |
2269 | pp->pb_pointer[pb_idx + 1].pe_old_lnum = lnum_right; |
2270 | |
2271 | mf_put(mfp, hp, true, false); |
2272 | buf->b_ml.ml_stack_top = stack_idx + 1; /* truncate stack */ |
2273 | |
2274 | if (lineadd) { |
2275 | --(buf->b_ml.ml_stack_top); |
2276 | /* fix line count for rest of blocks in the stack */ |
2277 | ml_lineadd(buf, lineadd); |
2278 | /* fix stack itself */ |
2279 | buf->b_ml.ml_stack[buf->b_ml.ml_stack_top].ip_high += |
2280 | lineadd; |
2281 | ++(buf->b_ml.ml_stack_top); |
2282 | } |
2283 | |
2284 | /* |
2285 | * We are finished, break the loop here. |
2286 | */ |
2287 | break; |
2288 | } else { /* pointer block full */ |
2289 | /* |
2290 | * split the pointer block |
2291 | * allocate a new pointer block |
2292 | * move some of the pointer into the new block |
2293 | * prepare for updating the parent block |
2294 | */ |
2295 | for (;; ) { /* do this twice when splitting block 1 */ |
2296 | hp_new = ml_new_ptr(mfp); |
2297 | if (hp_new == NULL) /* TODO: try to fix tree */ |
2298 | return FAIL; |
2299 | pp_new = hp_new->bh_data; |
2300 | |
2301 | if (hp->bh_bnum != 1) |
2302 | break; |
2303 | |
2304 | /* |
2305 | * if block 1 becomes full the tree is given an extra level |
2306 | * The pointers from block 1 are moved into the new block. |
2307 | * block 1 is updated to point to the new block |
2308 | * then continue to split the new block |
2309 | */ |
2310 | memmove(pp_new, pp, (size_t)page_size); |
2311 | pp->pb_count = 1; |
2312 | pp->pb_pointer[0].pe_bnum = hp_new->bh_bnum; |
2313 | pp->pb_pointer[0].pe_line_count = buf->b_ml.ml_line_count; |
2314 | pp->pb_pointer[0].pe_old_lnum = 1; |
2315 | pp->pb_pointer[0].pe_page_count = 1; |
2316 | mf_put(mfp, hp, true, false); /* release block 1 */ |
2317 | hp = hp_new; /* new block is to be split */ |
2318 | pp = pp_new; |
2319 | CHECK(stack_idx != 0, _("stack_idx should be 0" )); |
2320 | ip->ip_index = 0; |
2321 | ++stack_idx; /* do block 1 again later */ |
2322 | } |
2323 | /* |
2324 | * move the pointers after the current one to the new block |
2325 | * If there are none, the new entry will be in the new block. |
2326 | */ |
2327 | total_moved = pp->pb_count - pb_idx - 1; |
2328 | if (total_moved) { |
2329 | memmove(&pp_new->pb_pointer[0], |
2330 | &pp->pb_pointer[pb_idx + 1], |
2331 | (size_t)(total_moved) * sizeof(PTR_EN)); |
2332 | pp_new->pb_count = total_moved; |
2333 | pp->pb_count -= total_moved - 1; |
2334 | pp->pb_pointer[pb_idx + 1].pe_bnum = bnum_right; |
2335 | pp->pb_pointer[pb_idx + 1].pe_line_count = line_count_right; |
2336 | pp->pb_pointer[pb_idx + 1].pe_page_count = page_count_right; |
2337 | if (lnum_right) |
2338 | pp->pb_pointer[pb_idx + 1].pe_old_lnum = lnum_right; |
2339 | } else { |
2340 | pp_new->pb_count = 1; |
2341 | pp_new->pb_pointer[0].pe_bnum = bnum_right; |
2342 | pp_new->pb_pointer[0].pe_line_count = line_count_right; |
2343 | pp_new->pb_pointer[0].pe_page_count = page_count_right; |
2344 | pp_new->pb_pointer[0].pe_old_lnum = lnum_right; |
2345 | } |
2346 | pp->pb_pointer[pb_idx].pe_bnum = bnum_left; |
2347 | pp->pb_pointer[pb_idx].pe_line_count = line_count_left; |
2348 | pp->pb_pointer[pb_idx].pe_page_count = page_count_left; |
2349 | if (lnum_left) |
2350 | pp->pb_pointer[pb_idx].pe_old_lnum = lnum_left; |
2351 | lnum_left = 0; |
2352 | lnum_right = 0; |
2353 | |
2354 | /* |
2355 | * recompute line counts |
2356 | */ |
2357 | line_count_right = 0; |
2358 | for (i = 0; i < (int)pp_new->pb_count; ++i) |
2359 | line_count_right += pp_new->pb_pointer[i].pe_line_count; |
2360 | line_count_left = 0; |
2361 | for (i = 0; i < (int)pp->pb_count; ++i) |
2362 | line_count_left += pp->pb_pointer[i].pe_line_count; |
2363 | |
2364 | bnum_left = hp->bh_bnum; |
2365 | bnum_right = hp_new->bh_bnum; |
2366 | page_count_left = 1; |
2367 | page_count_right = 1; |
2368 | mf_put(mfp, hp, true, false); |
2369 | mf_put(mfp, hp_new, true, false); |
2370 | } |
2371 | } |
2372 | |
2373 | /* |
2374 | * Safety check: fallen out of for loop? |
2375 | */ |
2376 | if (stack_idx < 0) { |
2377 | IEMSG(_("E318: Updated too many blocks?" )); |
2378 | buf->b_ml.ml_stack_top = 0; // invalidate stack |
2379 | } |
2380 | } |
2381 | |
2382 | /* The line was inserted below 'lnum' */ |
2383 | ml_updatechunk(buf, lnum + 1, (long)len, ML_CHNK_ADDLINE); |
2384 | return OK; |
2385 | } |
2386 | |
2387 | void ml_add_deleted_len(char_u *ptr, ssize_t len) |
2388 | { |
2389 | if (inhibit_delete_count) { |
2390 | return; |
2391 | } |
2392 | if (len == -1) { |
2393 | len = STRLEN(ptr); |
2394 | } |
2395 | curbuf->deleted_bytes += len+1; |
2396 | if (curbuf->update_need_codepoints) { |
2397 | mb_utflen(ptr, len, &curbuf->deleted_codepoints, |
2398 | &curbuf->deleted_codeunits); |
2399 | curbuf->deleted_codepoints++; // NL char |
2400 | curbuf->deleted_codeunits++; |
2401 | } |
2402 | } |
2403 | |
2404 | /* |
2405 | * Replace line lnum, with buffering, in current buffer. |
2406 | * |
2407 | * If "copy" is TRUE, make a copy of the line, otherwise the line has been |
2408 | * copied to allocated memory already. |
2409 | * |
2410 | * Check: The caller of this function should probably also call |
2411 | * changed_lines(), unless update_screen(NOT_VALID) is used. |
2412 | * |
2413 | * return FAIL for failure, OK otherwise |
2414 | */ |
2415 | int ml_replace(linenr_T lnum, char_u *line, bool copy) |
2416 | { |
2417 | if (line == NULL) /* just checking... */ |
2418 | return FAIL; |
2419 | |
2420 | /* When starting up, we might still need to create the memfile */ |
2421 | if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL) |
2422 | return FAIL; |
2423 | |
2424 | bool readlen = true; |
2425 | |
2426 | if (copy) { |
2427 | line = vim_strsave(line); |
2428 | } |
2429 | if (curbuf->b_ml.ml_line_lnum != lnum) { // other line buffered |
2430 | ml_flush_line(curbuf); // flush it |
2431 | } else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) { // same line allocated |
2432 | ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, -1); |
2433 | readlen = false; // already added the length |
2434 | |
2435 | xfree(curbuf->b_ml.ml_line_ptr); // free it |
2436 | } |
2437 | |
2438 | if (readlen && kv_size(curbuf->update_callbacks)) { |
2439 | ml_add_deleted_len(ml_get_buf(curbuf, lnum, false), -1); |
2440 | } |
2441 | |
2442 | curbuf->b_ml.ml_line_ptr = line; |
2443 | curbuf->b_ml.ml_line_lnum = lnum; |
2444 | curbuf->b_ml.ml_flags = (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY; |
2445 | |
2446 | return OK; |
2447 | } |
2448 | |
2449 | /// Delete line `lnum` in the current buffer. |
2450 | /// |
2451 | /// @note The caller of this function should probably also call |
2452 | /// deleted_lines() after this. |
2453 | /// |
2454 | /// @param message Show "--No lines in buffer--" message. |
2455 | /// @return FAIL for failure, OK otherwise |
2456 | int ml_delete(linenr_T lnum, bool message) |
2457 | { |
2458 | ml_flush_line(curbuf); |
2459 | return ml_delete_int(curbuf, lnum, message); |
2460 | } |
2461 | |
2462 | static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) |
2463 | { |
2464 | bhdr_T *hp; |
2465 | memfile_T *mfp; |
2466 | DATA_BL *dp; |
2467 | PTR_BL *pp; |
2468 | infoptr_T *ip; |
2469 | int count; /* number of entries in block */ |
2470 | int idx; |
2471 | int stack_idx; |
2472 | int text_start; |
2473 | int line_start; |
2474 | long line_size; |
2475 | int i; |
2476 | |
2477 | if (lnum < 1 || lnum > buf->b_ml.ml_line_count) |
2478 | return FAIL; |
2479 | |
2480 | if (lowest_marked && lowest_marked > lnum) |
2481 | lowest_marked--; |
2482 | |
2483 | /* |
2484 | * If the file becomes empty the last line is replaced by an empty line. |
2485 | */ |
2486 | if (buf->b_ml.ml_line_count == 1) { /* file becomes empty */ |
2487 | if (message |
2488 | ) |
2489 | set_keep_msg((char_u *)_(no_lines_msg), 0); |
2490 | |
2491 | i = ml_replace((linenr_T)1, (char_u *)"" , true); |
2492 | buf->b_ml.ml_flags |= ML_EMPTY; |
2493 | |
2494 | return i; |
2495 | } |
2496 | |
2497 | /* |
2498 | * find the data block containing the line |
2499 | * This also fills the stack with the blocks from the root to the data block |
2500 | * This also releases any locked block. |
2501 | */ |
2502 | mfp = buf->b_ml.ml_mfp; |
2503 | if (mfp == NULL) |
2504 | return FAIL; |
2505 | |
2506 | if ((hp = ml_find_line(buf, lnum, ML_DELETE)) == NULL) |
2507 | return FAIL; |
2508 | |
2509 | dp = hp->bh_data; |
2510 | /* compute line count before the delete */ |
2511 | count = (long)(buf->b_ml.ml_locked_high) |
2512 | - (long)(buf->b_ml.ml_locked_low) + 2; |
2513 | idx = lnum - buf->b_ml.ml_locked_low; |
2514 | |
2515 | --buf->b_ml.ml_line_count; |
2516 | |
2517 | line_start = ((dp->db_index[idx]) & DB_INDEX_MASK); |
2518 | if (idx == 0) /* first line in block, text at the end */ |
2519 | line_size = dp->db_txt_end - line_start; |
2520 | else |
2521 | line_size = ((dp->db_index[idx - 1]) & DB_INDEX_MASK) - line_start; |
2522 | |
2523 | // Line should always have an NL char internally (represented as NUL), |
2524 | // even if 'noeol' is set. |
2525 | assert(line_size >= 1); |
2526 | ml_add_deleted_len((char_u *)dp + line_start, line_size-1); |
2527 | |
2528 | /* |
2529 | * special case: If there is only one line in the data block it becomes empty. |
2530 | * Then we have to remove the entry, pointing to this data block, from the |
2531 | * pointer block. If this pointer block also becomes empty, we go up another |
2532 | * block, and so on, up to the root if necessary. |
2533 | * The line counts in the pointer blocks have already been adjusted by |
2534 | * ml_find_line(). |
2535 | */ |
2536 | if (count == 1) { |
2537 | mf_free(mfp, hp); /* free the data block */ |
2538 | buf->b_ml.ml_locked = NULL; |
2539 | |
2540 | for (stack_idx = buf->b_ml.ml_stack_top - 1; stack_idx >= 0; |
2541 | --stack_idx) { |
2542 | buf->b_ml.ml_stack_top = 0; /* stack is invalid when failing */ |
2543 | ip = &(buf->b_ml.ml_stack[stack_idx]); |
2544 | idx = ip->ip_index; |
2545 | if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) |
2546 | return FAIL; |
2547 | pp = hp->bh_data; /* must be pointer block */ |
2548 | if (pp->pb_id != PTR_ID) { |
2549 | IEMSG(_("E317: pointer block id wrong 4" )); |
2550 | mf_put(mfp, hp, false, false); |
2551 | return FAIL; |
2552 | } |
2553 | count = --(pp->pb_count); |
2554 | if (count == 0) /* the pointer block becomes empty! */ |
2555 | mf_free(mfp, hp); |
2556 | else { |
2557 | if (count != idx) /* move entries after the deleted one */ |
2558 | memmove(&pp->pb_pointer[idx], &pp->pb_pointer[idx + 1], |
2559 | (size_t)(count - idx) * sizeof(PTR_EN)); |
2560 | mf_put(mfp, hp, true, false); |
2561 | |
2562 | buf->b_ml.ml_stack_top = stack_idx; /* truncate stack */ |
2563 | /* fix line count for rest of blocks in the stack */ |
2564 | if (buf->b_ml.ml_locked_lineadd != 0) { |
2565 | ml_lineadd(buf, buf->b_ml.ml_locked_lineadd); |
2566 | buf->b_ml.ml_stack[buf->b_ml.ml_stack_top].ip_high += |
2567 | buf->b_ml.ml_locked_lineadd; |
2568 | } |
2569 | ++(buf->b_ml.ml_stack_top); |
2570 | |
2571 | break; |
2572 | } |
2573 | } |
2574 | CHECK(stack_idx < 0, _("deleted block 1?" )); |
2575 | } else { |
2576 | /* |
2577 | * delete the text by moving the next lines forwards |
2578 | */ |
2579 | text_start = dp->db_txt_start; |
2580 | memmove((char *)dp + text_start + line_size, |
2581 | (char *)dp + text_start, (size_t)(line_start - text_start)); |
2582 | |
2583 | /* |
2584 | * delete the index by moving the next indexes backwards |
2585 | * Adjust the indexes for the text movement. |
2586 | */ |
2587 | for (i = idx; i < count - 1; ++i) |
2588 | dp->db_index[i] = dp->db_index[i + 1] + line_size; |
2589 | |
2590 | dp->db_free += line_size + INDEX_SIZE; |
2591 | dp->db_txt_start += line_size; |
2592 | --(dp->db_line_count); |
2593 | |
2594 | /* |
2595 | * mark the block dirty and make sure it is in the file (for recovery) |
2596 | */ |
2597 | buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS); |
2598 | } |
2599 | |
2600 | ml_updatechunk(buf, lnum, line_size, ML_CHNK_DELLINE); |
2601 | return OK; |
2602 | } |
2603 | |
2604 | /* |
2605 | * set the B_MARKED flag for line 'lnum' |
2606 | */ |
2607 | void ml_setmarked(linenr_T lnum) |
2608 | { |
2609 | bhdr_T *hp; |
2610 | DATA_BL *dp; |
2611 | /* invalid line number */ |
2612 | if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count |
2613 | || curbuf->b_ml.ml_mfp == NULL) |
2614 | return; /* give error message? */ |
2615 | |
2616 | if (lowest_marked == 0 || lowest_marked > lnum) |
2617 | lowest_marked = lnum; |
2618 | |
2619 | /* |
2620 | * find the data block containing the line |
2621 | * This also fills the stack with the blocks from the root to the data block |
2622 | * This also releases any locked block. |
2623 | */ |
2624 | if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) |
2625 | return; /* give error message? */ |
2626 | |
2627 | dp = hp->bh_data; |
2628 | dp->db_index[lnum - curbuf->b_ml.ml_locked_low] |= DB_MARKED; |
2629 | curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY; |
2630 | } |
2631 | |
2632 | /* |
2633 | * find the first line with its B_MARKED flag set |
2634 | */ |
2635 | linenr_T ml_firstmarked(void) |
2636 | { |
2637 | bhdr_T *hp; |
2638 | DATA_BL *dp; |
2639 | linenr_T lnum; |
2640 | int i; |
2641 | |
2642 | if (curbuf->b_ml.ml_mfp == NULL) |
2643 | return (linenr_T) 0; |
2644 | |
2645 | /* |
2646 | * The search starts with lowest_marked line. This is the last line where |
2647 | * a mark was found, adjusted by inserting/deleting lines. |
2648 | */ |
2649 | for (lnum = lowest_marked; lnum <= curbuf->b_ml.ml_line_count; ) { |
2650 | /* |
2651 | * Find the data block containing the line. |
2652 | * This also fills the stack with the blocks from the root to the data |
2653 | * block This also releases any locked block. |
2654 | */ |
2655 | if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) |
2656 | return (linenr_T)0; /* give error message? */ |
2657 | |
2658 | dp = hp->bh_data; |
2659 | |
2660 | for (i = lnum - curbuf->b_ml.ml_locked_low; |
2661 | lnum <= curbuf->b_ml.ml_locked_high; ++i, ++lnum) |
2662 | if ((dp->db_index[i]) & DB_MARKED) { |
2663 | (dp->db_index[i]) &= DB_INDEX_MASK; |
2664 | curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY; |
2665 | lowest_marked = lnum + 1; |
2666 | return lnum; |
2667 | } |
2668 | } |
2669 | |
2670 | return (linenr_T) 0; |
2671 | } |
2672 | |
2673 | /* |
2674 | * clear all DB_MARKED flags |
2675 | */ |
2676 | void ml_clearmarked(void) |
2677 | { |
2678 | bhdr_T *hp; |
2679 | DATA_BL *dp; |
2680 | linenr_T lnum; |
2681 | int i; |
2682 | |
2683 | if (curbuf->b_ml.ml_mfp == NULL) /* nothing to do */ |
2684 | return; |
2685 | |
2686 | /* |
2687 | * The search starts with line lowest_marked. |
2688 | */ |
2689 | for (lnum = lowest_marked; lnum <= curbuf->b_ml.ml_line_count; ) { |
2690 | /* |
2691 | * Find the data block containing the line. |
2692 | * This also fills the stack with the blocks from the root to the data |
2693 | * block and releases any locked block. |
2694 | */ |
2695 | if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) |
2696 | return; /* give error message? */ |
2697 | |
2698 | dp = hp->bh_data; |
2699 | |
2700 | for (i = lnum - curbuf->b_ml.ml_locked_low; |
2701 | lnum <= curbuf->b_ml.ml_locked_high; ++i, ++lnum) |
2702 | if ((dp->db_index[i]) & DB_MARKED) { |
2703 | (dp->db_index[i]) &= DB_INDEX_MASK; |
2704 | curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY; |
2705 | } |
2706 | } |
2707 | |
2708 | lowest_marked = 0; |
2709 | return; |
2710 | } |
2711 | |
2712 | size_t ml_flush_deleted_bytes(buf_T *buf, size_t *codepoints, size_t *codeunits) |
2713 | { |
2714 | size_t ret = buf->deleted_bytes; |
2715 | *codepoints = buf->deleted_codepoints; |
2716 | *codeunits = buf->deleted_codeunits; |
2717 | buf->deleted_bytes = 0; |
2718 | buf->deleted_codepoints = 0; |
2719 | buf->deleted_codeunits = 0; |
2720 | return ret; |
2721 | } |
2722 | |
2723 | /* |
2724 | * flush ml_line if necessary |
2725 | */ |
2726 | static void ml_flush_line(buf_T *buf) |
2727 | { |
2728 | bhdr_T *hp; |
2729 | DATA_BL *dp; |
2730 | linenr_T lnum; |
2731 | char_u *new_line; |
2732 | char_u *old_line; |
2733 | colnr_T new_len; |
2734 | int old_len; |
2735 | int ; |
2736 | int idx; |
2737 | int start; |
2738 | int count; |
2739 | int i; |
2740 | static int entered = FALSE; |
2741 | |
2742 | if (buf->b_ml.ml_line_lnum == 0 || buf->b_ml.ml_mfp == NULL) |
2743 | return; /* nothing to do */ |
2744 | |
2745 | if (buf->b_ml.ml_flags & ML_LINE_DIRTY) { |
2746 | /* This code doesn't work recursively. */ |
2747 | if (entered) |
2748 | return; |
2749 | entered = TRUE; |
2750 | |
2751 | buf->flush_count++; |
2752 | |
2753 | lnum = buf->b_ml.ml_line_lnum; |
2754 | new_line = buf->b_ml.ml_line_ptr; |
2755 | |
2756 | hp = ml_find_line(buf, lnum, ML_FIND); |
2757 | if (hp == NULL) { |
2758 | IEMSGN(_("E320: Cannot find line %" PRId64), lnum); |
2759 | } else { |
2760 | dp = hp->bh_data; |
2761 | idx = lnum - buf->b_ml.ml_locked_low; |
2762 | start = ((dp->db_index[idx]) & DB_INDEX_MASK); |
2763 | old_line = (char_u *)dp + start; |
2764 | if (idx == 0) /* line is last in block */ |
2765 | old_len = dp->db_txt_end - start; |
2766 | else /* text of previous line follows */ |
2767 | old_len = (dp->db_index[idx - 1] & DB_INDEX_MASK) - start; |
2768 | new_len = (colnr_T)STRLEN(new_line) + 1; |
2769 | extra = new_len - old_len; /* negative if lines gets smaller */ |
2770 | |
2771 | /* |
2772 | * if new line fits in data block, replace directly |
2773 | */ |
2774 | if ((int)dp->db_free >= extra) { |
2775 | /* if the length changes and there are following lines */ |
2776 | count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 1; |
2777 | if (extra != 0 && idx < count - 1) { |
2778 | /* move text of following lines */ |
2779 | memmove((char *)dp + dp->db_txt_start - extra, |
2780 | (char *)dp + dp->db_txt_start, |
2781 | (size_t)(start - dp->db_txt_start)); |
2782 | |
2783 | /* adjust pointers of this and following lines */ |
2784 | for (i = idx + 1; i < count; ++i) |
2785 | dp->db_index[i] -= extra; |
2786 | } |
2787 | dp->db_index[idx] -= extra; |
2788 | |
2789 | /* adjust free space */ |
2790 | dp->db_free -= extra; |
2791 | dp->db_txt_start -= extra; |
2792 | |
2793 | /* copy new line into the data block */ |
2794 | memmove(old_line - extra, new_line, (size_t)new_len); |
2795 | buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS); |
2796 | /* The else case is already covered by the insert and delete */ |
2797 | ml_updatechunk(buf, lnum, (long)extra, ML_CHNK_UPDLINE); |
2798 | } else { |
2799 | // Cannot do it in one data block: Delete and append. |
2800 | // Append first, because ml_delete_int() cannot delete the |
2801 | // last line in a buffer, which causes trouble for a buffer |
2802 | // that has only one line. |
2803 | // Don't forget to copy the mark! |
2804 | // How about handling errors??? |
2805 | (void)ml_append_int(buf, lnum, new_line, new_len, false, |
2806 | (dp->db_index[idx] & DB_MARKED)); |
2807 | (void)ml_delete_int(buf, lnum, false); |
2808 | } |
2809 | } |
2810 | xfree(new_line); |
2811 | |
2812 | entered = FALSE; |
2813 | } |
2814 | |
2815 | buf->b_ml.ml_line_lnum = 0; |
2816 | } |
2817 | |
2818 | /* |
2819 | * create a new, empty, data block |
2820 | */ |
2821 | static bhdr_T *ml_new_data(memfile_T *mfp, bool negative, int page_count) |
2822 | { |
2823 | assert(page_count >= 0); |
2824 | bhdr_T *hp = mf_new(mfp, negative, (unsigned)page_count); |
2825 | DATA_BL *dp = hp->bh_data; |
2826 | dp->db_id = DATA_ID; |
2827 | dp->db_txt_start = dp->db_txt_end = page_count * mfp->mf_page_size; |
2828 | dp->db_free = dp->db_txt_start - HEADER_SIZE; |
2829 | dp->db_line_count = 0; |
2830 | |
2831 | return hp; |
2832 | } |
2833 | |
2834 | /* |
2835 | * create a new, empty, pointer block |
2836 | */ |
2837 | static bhdr_T *ml_new_ptr(memfile_T *mfp) |
2838 | { |
2839 | bhdr_T *hp = mf_new(mfp, false, 1); |
2840 | PTR_BL *pp = hp->bh_data; |
2841 | pp->pb_id = PTR_ID; |
2842 | pp->pb_count = 0; |
2843 | pp->pb_count_max = (mfp->mf_page_size - sizeof(PTR_BL)) / sizeof(PTR_EN) + 1; |
2844 | |
2845 | return hp; |
2846 | } |
2847 | |
2848 | /* |
2849 | * lookup line 'lnum' in a memline |
2850 | * |
2851 | * action: if ML_DELETE or ML_INSERT the line count is updated while searching |
2852 | * if ML_FLUSH only flush a locked block |
2853 | * if ML_FIND just find the line |
2854 | * |
2855 | * If the block was found it is locked and put in ml_locked. |
2856 | * The stack is updated to lead to the locked block. The ip_high field in |
2857 | * the stack is updated to reflect the last line in the block AFTER the |
2858 | * insert or delete, also if the pointer block has not been updated yet. But |
2859 | * if ml_locked != NULL ml_locked_lineadd must be added to ip_high. |
2860 | * |
2861 | * return: NULL for failure, pointer to block header otherwise |
2862 | */ |
2863 | static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) |
2864 | { |
2865 | DATA_BL *dp; |
2866 | PTR_BL *pp; |
2867 | infoptr_T *ip; |
2868 | bhdr_T *hp; |
2869 | memfile_T *mfp; |
2870 | linenr_T t; |
2871 | blocknr_T bnum, bnum2; |
2872 | int dirty; |
2873 | linenr_T low, high; |
2874 | int top; |
2875 | int page_count; |
2876 | int idx; |
2877 | |
2878 | mfp = buf->b_ml.ml_mfp; |
2879 | |
2880 | /* |
2881 | * If there is a locked block check if the wanted line is in it. |
2882 | * If not, flush and release the locked block. |
2883 | * Don't do this for ML_INSERT_SAME, because the stack need to be updated. |
2884 | * Don't do this for ML_FLUSH, because we want to flush the locked block. |
2885 | * Don't do this when 'swapfile' is reset, we want to load all the blocks. |
2886 | */ |
2887 | if (buf->b_ml.ml_locked) { |
2888 | if (ML_SIMPLE(action) |
2889 | && buf->b_ml.ml_locked_low <= lnum |
2890 | && buf->b_ml.ml_locked_high >= lnum) { |
2891 | // remember to update pointer blocks and stack later |
2892 | if (action == ML_INSERT) { |
2893 | ++(buf->b_ml.ml_locked_lineadd); |
2894 | ++(buf->b_ml.ml_locked_high); |
2895 | } else if (action == ML_DELETE) { |
2896 | --(buf->b_ml.ml_locked_lineadd); |
2897 | --(buf->b_ml.ml_locked_high); |
2898 | } |
2899 | return buf->b_ml.ml_locked; |
2900 | } |
2901 | |
2902 | mf_put(mfp, buf->b_ml.ml_locked, buf->b_ml.ml_flags & ML_LOCKED_DIRTY, |
2903 | buf->b_ml.ml_flags & ML_LOCKED_POS); |
2904 | buf->b_ml.ml_locked = NULL; |
2905 | |
2906 | /* |
2907 | * If lines have been added or deleted in the locked block, need to |
2908 | * update the line count in pointer blocks. |
2909 | */ |
2910 | if (buf->b_ml.ml_locked_lineadd != 0) |
2911 | ml_lineadd(buf, buf->b_ml.ml_locked_lineadd); |
2912 | } |
2913 | |
2914 | if (action == ML_FLUSH) /* nothing else to do */ |
2915 | return NULL; |
2916 | |
2917 | bnum = 1; /* start at the root of the tree */ |
2918 | page_count = 1; |
2919 | low = 1; |
2920 | high = buf->b_ml.ml_line_count; |
2921 | |
2922 | if (action == ML_FIND) { /* first try stack entries */ |
2923 | for (top = buf->b_ml.ml_stack_top - 1; top >= 0; --top) { |
2924 | ip = &(buf->b_ml.ml_stack[top]); |
2925 | if (ip->ip_low <= lnum && ip->ip_high >= lnum) { |
2926 | bnum = ip->ip_bnum; |
2927 | low = ip->ip_low; |
2928 | high = ip->ip_high; |
2929 | buf->b_ml.ml_stack_top = top; /* truncate stack at prev entry */ |
2930 | break; |
2931 | } |
2932 | } |
2933 | if (top < 0) |
2934 | buf->b_ml.ml_stack_top = 0; /* not found, start at the root */ |
2935 | } else /* ML_DELETE or ML_INSERT */ |
2936 | buf->b_ml.ml_stack_top = 0; /* start at the root */ |
2937 | |
2938 | /* |
2939 | * search downwards in the tree until a data block is found |
2940 | */ |
2941 | for (;; ) { |
2942 | if ((hp = mf_get(mfp, bnum, page_count)) == NULL) |
2943 | goto error_noblock; |
2944 | |
2945 | /* |
2946 | * update high for insert/delete |
2947 | */ |
2948 | if (action == ML_INSERT) |
2949 | ++high; |
2950 | else if (action == ML_DELETE) |
2951 | --high; |
2952 | |
2953 | dp = hp->bh_data; |
2954 | if (dp->db_id == DATA_ID) { /* data block */ |
2955 | buf->b_ml.ml_locked = hp; |
2956 | buf->b_ml.ml_locked_low = low; |
2957 | buf->b_ml.ml_locked_high = high; |
2958 | buf->b_ml.ml_locked_lineadd = 0; |
2959 | buf->b_ml.ml_flags &= ~(ML_LOCKED_DIRTY | ML_LOCKED_POS); |
2960 | return hp; |
2961 | } |
2962 | |
2963 | pp = (PTR_BL *)(dp); /* must be pointer block */ |
2964 | if (pp->pb_id != PTR_ID) { |
2965 | IEMSG(_("E317: pointer block id wrong" )); |
2966 | goto error_block; |
2967 | } |
2968 | |
2969 | top = ml_add_stack(buf); // add new entry to stack |
2970 | ip = &(buf->b_ml.ml_stack[top]); |
2971 | ip->ip_bnum = bnum; |
2972 | ip->ip_low = low; |
2973 | ip->ip_high = high; |
2974 | ip->ip_index = -1; /* index not known yet */ |
2975 | |
2976 | dirty = FALSE; |
2977 | for (idx = 0; idx < (int)pp->pb_count; ++idx) { |
2978 | t = pp->pb_pointer[idx].pe_line_count; |
2979 | CHECK(t == 0, _("pe_line_count is zero" )); |
2980 | if ((low += t) > lnum) { |
2981 | ip->ip_index = idx; |
2982 | bnum = pp->pb_pointer[idx].pe_bnum; |
2983 | page_count = pp->pb_pointer[idx].pe_page_count; |
2984 | high = low - 1; |
2985 | low -= t; |
2986 | |
2987 | /* |
2988 | * a negative block number may have been changed |
2989 | */ |
2990 | if (bnum < 0) { |
2991 | bnum2 = mf_trans_del(mfp, bnum); |
2992 | if (bnum != bnum2) { |
2993 | bnum = bnum2; |
2994 | pp->pb_pointer[idx].pe_bnum = bnum; |
2995 | dirty = TRUE; |
2996 | } |
2997 | } |
2998 | |
2999 | break; |
3000 | } |
3001 | } |
3002 | if (idx >= (int)pp->pb_count) { // past the end: something wrong! |
3003 | if (lnum > buf->b_ml.ml_line_count) { |
3004 | IEMSGN(_("E322: line number out of range: %" PRId64 " past the end" ), |
3005 | lnum - buf->b_ml.ml_line_count); |
3006 | |
3007 | } else { |
3008 | IEMSGN(_("E323: line count wrong in block %" PRId64), bnum); |
3009 | } |
3010 | goto error_block; |
3011 | } |
3012 | if (action == ML_DELETE) { |
3013 | pp->pb_pointer[idx].pe_line_count--; |
3014 | dirty = TRUE; |
3015 | } else if (action == ML_INSERT) { |
3016 | pp->pb_pointer[idx].pe_line_count++; |
3017 | dirty = TRUE; |
3018 | } |
3019 | mf_put(mfp, hp, dirty, false); |
3020 | } |
3021 | |
3022 | error_block: |
3023 | mf_put(mfp, hp, false, false); |
3024 | error_noblock: |
3025 | /* |
3026 | * If action is ML_DELETE or ML_INSERT we have to correct the tree for |
3027 | * the incremented/decremented line counts, because there won't be a line |
3028 | * inserted/deleted after all. |
3029 | */ |
3030 | if (action == ML_DELETE) |
3031 | ml_lineadd(buf, 1); |
3032 | else if (action == ML_INSERT) |
3033 | ml_lineadd(buf, -1); |
3034 | buf->b_ml.ml_stack_top = 0; |
3035 | return NULL; |
3036 | } |
3037 | |
3038 | /* |
3039 | * add an entry to the info pointer stack |
3040 | * |
3041 | * return number of the new entry |
3042 | */ |
3043 | static int ml_add_stack(buf_T *buf) |
3044 | { |
3045 | int top = buf->b_ml.ml_stack_top; |
3046 | |
3047 | /* may have to increase the stack size */ |
3048 | if (top == buf->b_ml.ml_stack_size) { |
3049 | CHECK(top > 0, _("Stack size increases" )); /* more than 5 levels??? */ |
3050 | |
3051 | buf->b_ml.ml_stack_size += STACK_INCR; |
3052 | size_t new_size = sizeof(infoptr_T) * buf->b_ml.ml_stack_size; |
3053 | buf->b_ml.ml_stack = xrealloc(buf->b_ml.ml_stack, new_size); |
3054 | } |
3055 | |
3056 | buf->b_ml.ml_stack_top++; |
3057 | return top; |
3058 | } |
3059 | |
3060 | /* |
3061 | * Update the pointer blocks on the stack for inserted/deleted lines. |
3062 | * The stack itself is also updated. |
3063 | * |
3064 | * When an insert/delete line action fails, the line is not inserted/deleted, |
3065 | * but the pointer blocks have already been updated. That is fixed here by |
3066 | * walking through the stack. |
3067 | * |
3068 | * Count is the number of lines added, negative if lines have been deleted. |
3069 | */ |
3070 | static void ml_lineadd(buf_T *buf, int count) |
3071 | { |
3072 | int idx; |
3073 | infoptr_T *ip; |
3074 | PTR_BL *pp; |
3075 | memfile_T *mfp = buf->b_ml.ml_mfp; |
3076 | bhdr_T *hp; |
3077 | |
3078 | for (idx = buf->b_ml.ml_stack_top - 1; idx >= 0; --idx) { |
3079 | ip = &(buf->b_ml.ml_stack[idx]); |
3080 | if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) |
3081 | break; |
3082 | pp = hp->bh_data; /* must be pointer block */ |
3083 | if (pp->pb_id != PTR_ID) { |
3084 | mf_put(mfp, hp, false, false); |
3085 | IEMSG(_("E317: pointer block id wrong 2" )); |
3086 | break; |
3087 | } |
3088 | pp->pb_pointer[ip->ip_index].pe_line_count += count; |
3089 | ip->ip_high += count; |
3090 | mf_put(mfp, hp, true, false); |
3091 | } |
3092 | } |
3093 | |
3094 | #if defined(HAVE_READLINK) |
3095 | /* |
3096 | * Resolve a symlink in the last component of a file name. |
3097 | * Note that f_resolve() does it for every part of the path, we don't do that |
3098 | * here. |
3099 | * If it worked returns OK and the resolved link in "buf[MAXPATHL]". |
3100 | * Otherwise returns FAIL. |
3101 | */ |
3102 | int resolve_symlink(const char_u *fname, char_u *buf) |
3103 | { |
3104 | char_u tmp[MAXPATHL]; |
3105 | int ret; |
3106 | int depth = 0; |
3107 | |
3108 | if (fname == NULL) |
3109 | return FAIL; |
3110 | |
3111 | /* Put the result so far in tmp[], starting with the original name. */ |
3112 | STRLCPY(tmp, fname, MAXPATHL); |
3113 | |
3114 | for (;; ) { |
3115 | /* Limit symlink depth to 100, catch recursive loops. */ |
3116 | if (++depth == 100) { |
3117 | EMSG2(_("E773: Symlink loop for \"%s\"" ), fname); |
3118 | return FAIL; |
3119 | } |
3120 | |
3121 | ret = readlink((char *)tmp, (char *)buf, MAXPATHL - 1); |
3122 | if (ret <= 0) { |
3123 | if (errno == EINVAL || errno == ENOENT) { |
3124 | /* Found non-symlink or not existing file, stop here. |
3125 | * When at the first level use the unmodified name, skip the |
3126 | * call to vim_FullName(). */ |
3127 | if (depth == 1) |
3128 | return FAIL; |
3129 | |
3130 | /* Use the resolved name in tmp[]. */ |
3131 | break; |
3132 | } |
3133 | |
3134 | /* There must be some error reading links, use original name. */ |
3135 | return FAIL; |
3136 | } |
3137 | buf[ret] = NUL; |
3138 | |
3139 | // Check whether the symlink is relative or absolute. |
3140 | // If it's relative, build a new path based on the directory |
3141 | // portion of the filename (if any) and the path the symlink |
3142 | // points to. |
3143 | if (path_is_absolute(buf)) { |
3144 | STRCPY(tmp, buf); |
3145 | } else { |
3146 | char_u *tail = path_tail(tmp); |
3147 | if (STRLEN(tail) + STRLEN(buf) >= MAXPATHL) { |
3148 | return FAIL; |
3149 | } |
3150 | STRCPY(tail, buf); |
3151 | } |
3152 | } |
3153 | |
3154 | /* |
3155 | * Try to resolve the full name of the file so that the swapfile name will |
3156 | * be consistent even when opening a relative symlink from different |
3157 | * working directories. |
3158 | */ |
3159 | return vim_FullName((char *)tmp, (char *)buf, MAXPATHL, TRUE); |
3160 | } |
3161 | #endif |
3162 | |
3163 | /* |
3164 | * Make swap file name out of the file name and a directory name. |
3165 | * Returns pointer to allocated memory or NULL. |
3166 | */ |
3167 | char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name) |
3168 | { |
3169 | char_u *r, *s; |
3170 | char_u *fname_res = fname; |
3171 | #ifdef HAVE_READLINK |
3172 | char_u fname_buf[MAXPATHL]; |
3173 | #endif |
3174 | int len = (int)STRLEN(dir_name); |
3175 | |
3176 | s = dir_name + len; |
3177 | if (after_pathsep((char *)dir_name, (char *)s) |
3178 | && len > 1 |
3179 | && s[-1] == s[-2]) { // Ends with '//', Use Full path |
3180 | r = NULL; |
3181 | if ((s = (char_u *)make_percent_swname((char *)dir_name, (char *)fname)) != NULL) { |
3182 | r = (char_u *)modname((char *)s, ".swp" , FALSE); |
3183 | xfree(s); |
3184 | } |
3185 | return r; |
3186 | } |
3187 | |
3188 | #ifdef HAVE_READLINK |
3189 | /* Expand symlink in the file name, so that we put the swap file with the |
3190 | * actual file instead of with the symlink. */ |
3191 | if (resolve_symlink(fname, fname_buf) == OK) |
3192 | fname_res = fname_buf; |
3193 | #endif |
3194 | |
3195 | // Prepend a '.' to the swap file name for the current directory. |
3196 | r = (char_u *)modname((char *)fname_res, ".swp" , |
3197 | dir_name[0] == '.' && dir_name[1] == NUL); |
3198 | if (r == NULL) /* out of memory */ |
3199 | return NULL; |
3200 | |
3201 | s = get_file_in_dir(r, dir_name); |
3202 | xfree(r); |
3203 | return s; |
3204 | } |
3205 | |
3206 | /* |
3207 | * Get file name to use for swap file or backup file. |
3208 | * Use the name of the edited file "fname" and an entry in the 'dir' or 'bdir' |
3209 | * option "dname". |
3210 | * - If "dname" is ".", return "fname" (swap file in dir of file). |
3211 | * - If "dname" starts with "./", insert "dname" in "fname" (swap file |
3212 | * relative to dir of file). |
3213 | * - Otherwise, prepend "dname" to the tail of "fname" (swap file in specific |
3214 | * dir). |
3215 | * |
3216 | * The return value is an allocated string and can be NULL. |
3217 | */ |
3218 | char_u * |
3219 | get_file_in_dir ( |
3220 | char_u *fname, |
3221 | char_u *dname /* don't use "dirname", it is a global for Alpha */ |
3222 | ) |
3223 | { |
3224 | char_u *t; |
3225 | char_u *tail; |
3226 | char_u *retval; |
3227 | int save_char; |
3228 | |
3229 | tail = path_tail(fname); |
3230 | |
3231 | if (dname[0] == '.' && dname[1] == NUL) |
3232 | retval = vim_strsave(fname); |
3233 | else if (dname[0] == '.' && vim_ispathsep(dname[1])) { |
3234 | if (tail == fname) /* no path before file name */ |
3235 | retval = (char_u *)concat_fnames((char *)dname + 2, (char *)tail, TRUE); |
3236 | else { |
3237 | save_char = *tail; |
3238 | *tail = NUL; |
3239 | t = (char_u *)concat_fnames((char *)fname, (char *)dname + 2, TRUE); |
3240 | *tail = save_char; |
3241 | retval = (char_u *)concat_fnames((char *)t, (char *)tail, TRUE); |
3242 | xfree(t); |
3243 | } |
3244 | } else { |
3245 | retval = (char_u *)concat_fnames((char *)dname, (char *)tail, TRUE); |
3246 | } |
3247 | |
3248 | return retval; |
3249 | } |
3250 | |
3251 | |
3252 | /* |
3253 | * Print the ATTENTION message: info about an existing swap file. |
3254 | */ |
3255 | static void |
3256 | attention_message ( |
3257 | buf_T *buf, /* buffer being edited */ |
3258 | char_u *fname /* swap file name */ |
3259 | ) |
3260 | { |
3261 | assert(buf->b_fname != NULL); |
3262 | time_t x, sx; |
3263 | char *p; |
3264 | |
3265 | ++no_wait_return; |
3266 | (void)EMSG(_("E325: ATTENTION" )); |
3267 | MSG_PUTS(_("\nFound a swap file by the name \"" )); |
3268 | msg_home_replace(fname); |
3269 | MSG_PUTS("\"\n" ); |
3270 | sx = swapfile_info(fname); |
3271 | MSG_PUTS(_("While opening file \"" )); |
3272 | msg_outtrans(buf->b_fname); |
3273 | MSG_PUTS("\"\n" ); |
3274 | FileInfo file_info; |
3275 | if (!os_fileinfo((char *)buf->b_fname, &file_info)) { |
3276 | MSG_PUTS(_(" CANNOT BE FOUND" )); |
3277 | } else { |
3278 | MSG_PUTS(_(" dated: " )); |
3279 | x = file_info.stat.st_mtim.tv_sec; |
3280 | p = ctime(&x); // includes '\n' |
3281 | if (p == NULL) |
3282 | MSG_PUTS("(invalid)\n" ); |
3283 | else |
3284 | MSG_PUTS(p); |
3285 | if (sx != 0 && x > sx) |
3286 | MSG_PUTS(_(" NEWER than swap file!\n" )); |
3287 | } |
3288 | /* Some of these messages are long to allow translation to |
3289 | * other languages. */ |
3290 | MSG_PUTS(_("\n(1) Another program may be editing the same file. If this is" |
3291 | " the case,\n be careful not to end up with two different" |
3292 | " instances of the same\n file when making changes." |
3293 | " Quit, or continue with caution.\n" )); |
3294 | MSG_PUTS(_("(2) An edit session for this file crashed.\n" )); |
3295 | MSG_PUTS(_(" If this is the case, use \":recover\" or \"vim -r " )); |
3296 | msg_outtrans(buf->b_fname); |
3297 | MSG_PUTS(_("\"\n to recover the changes (see \":help recovery\").\n" )); |
3298 | MSG_PUTS(_(" If you did this already, delete the swap file \"" )); |
3299 | msg_outtrans(fname); |
3300 | MSG_PUTS(_("\"\n to avoid this message.\n" )); |
3301 | cmdline_row = msg_row; |
3302 | --no_wait_return; |
3303 | } |
3304 | |
3305 | |
3306 | /* |
3307 | * Trigger the SwapExists autocommands. |
3308 | * Returns a value for equivalent to do_dialog() (see below): |
3309 | * 0: still need to ask for a choice |
3310 | * 1: open read-only |
3311 | * 2: edit anyway |
3312 | * 3: recover |
3313 | * 4: delete it |
3314 | * 5: quit |
3315 | * 6: abort |
3316 | */ |
3317 | static int do_swapexists(buf_T *buf, char_u *fname) |
3318 | { |
3319 | set_vim_var_string(VV_SWAPNAME, (char *) fname, -1); |
3320 | set_vim_var_string(VV_SWAPCHOICE, NULL, -1); |
3321 | |
3322 | /* Trigger SwapExists autocommands with <afile> set to the file being |
3323 | * edited. Disallow changing directory here. */ |
3324 | ++allbuf_lock; |
3325 | apply_autocmds(EVENT_SWAPEXISTS, buf->b_fname, NULL, FALSE, NULL); |
3326 | --allbuf_lock; |
3327 | |
3328 | set_vim_var_string(VV_SWAPNAME, NULL, -1); |
3329 | |
3330 | switch (*get_vim_var_str(VV_SWAPCHOICE)) { |
3331 | case 'o': return 1; |
3332 | case 'e': return 2; |
3333 | case 'r': return 3; |
3334 | case 'd': return 4; |
3335 | case 'q': return 5; |
3336 | case 'a': return 6; |
3337 | } |
3338 | |
3339 | return 0; |
3340 | } |
3341 | |
3342 | /// Find out what name to use for the swap file for buffer 'buf'. |
3343 | /// |
3344 | /// Several names are tried to find one that does not exist. Last directory in |
3345 | /// option is automatically created. |
3346 | /// |
3347 | /// @note If BASENAMELEN is not correct, you will get error messages for |
3348 | /// not being able to open the swap or undo file. |
3349 | /// @note May trigger SwapExists autocmd, pointers may change! |
3350 | /// |
3351 | /// @param[in] buf Buffer for which swap file names needs to be found. |
3352 | /// @param[in,out] dirp Pointer to a list of directories. When out of memory, |
3353 | /// is set to NULL. Is advanced to the next directory in |
3354 | /// the list otherwise. |
3355 | /// @param[in] old_fname Allowed existing swap file name. Except for this |
3356 | /// case, name of the non-existing file is used. |
3357 | /// @param[in,out] found_existing_dir If points to true, then new directory |
3358 | /// for swap file is not created. At first |
3359 | /// findswapname() call this argument must |
3360 | /// point to false. This parameter may only |
3361 | /// be set to true by this function, it is |
3362 | /// never set to false. |
3363 | /// |
3364 | /// @return [allocated] Name of the swap file. |
3365 | static char *findswapname(buf_T *buf, char **dirp, char *old_fname, |
3366 | bool *found_existing_dir) |
3367 | FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1, 2, 4) |
3368 | { |
3369 | char *fname; |
3370 | size_t n; |
3371 | char *dir_name; |
3372 | char *buf_fname = (char *) buf->b_fname; |
3373 | |
3374 | /* |
3375 | * Isolate a directory name from *dirp and put it in dir_name. |
3376 | * First allocate some memory to put the directory name in. |
3377 | */ |
3378 | const size_t dir_len = strlen(*dirp) + 1; |
3379 | dir_name = xmalloc(dir_len); |
3380 | (void)copy_option_part((char_u **) dirp, (char_u *) dir_name, dir_len, "," ); |
3381 | |
3382 | /* |
3383 | * we try different names until we find one that does not exist yet |
3384 | */ |
3385 | fname = (char *)makeswapname((char_u *)buf_fname, buf->b_ffname, buf, |
3386 | (char_u *)dir_name); |
3387 | |
3388 | for (;; ) { |
3389 | if (fname == NULL) { // must be out of memory |
3390 | break; |
3391 | } |
3392 | if ((n = strlen(fname)) == 0) { // safety check |
3393 | XFREE_CLEAR(fname); |
3394 | break; |
3395 | } |
3396 | // check if the swapfile already exists |
3397 | // Extra security check: When a swap file is a symbolic link, this |
3398 | // is most likely a symlink attack. |
3399 | FileInfo file_info; |
3400 | bool file_or_link_found = os_fileinfo_link(fname, &file_info); |
3401 | if (!file_or_link_found) { |
3402 | break; |
3403 | } |
3404 | |
3405 | // A file name equal to old_fname is OK to use. |
3406 | if (old_fname != NULL && fnamecmp(fname, old_fname) == 0) { |
3407 | break; |
3408 | } |
3409 | |
3410 | // get here when file already exists |
3411 | if (fname[n - 2] == 'w' && fname[n - 1] == 'p') { // first try |
3412 | // If we get here the ".swp" file really exists. |
3413 | // Give an error message, unless recovering, no file name, we are |
3414 | // viewing a help file or when the path of the file is different |
3415 | // (happens when all .swp files are in one directory). |
3416 | if (!recoverymode && buf_fname != NULL |
3417 | && !buf->b_help && !(buf->b_flags & BF_DUMMY)) { |
3418 | int fd; |
3419 | struct block0 b0; |
3420 | int differ = FALSE; |
3421 | |
3422 | // Try to read block 0 from the swap file to get the original |
3423 | // file name (and inode number). |
3424 | fd = os_open(fname, O_RDONLY, 0); |
3425 | if (fd >= 0) { |
3426 | if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) { |
3427 | // If the swapfile has the same directory as the |
3428 | // buffer don't compare the directory names, they can |
3429 | // have a different mountpoint. |
3430 | if (b0.b0_flags & B0_SAME_DIR) { |
3431 | if (fnamecmp(path_tail(buf->b_ffname), |
3432 | path_tail(b0.b0_fname)) != 0 |
3433 | || !same_directory((char_u *)fname, buf->b_ffname)) { |
3434 | // Symlinks may point to the same file even |
3435 | // when the name differs, need to check the |
3436 | // inode too. |
3437 | expand_env(b0.b0_fname, NameBuff, MAXPATHL); |
3438 | if (fnamecmp_ino(buf->b_ffname, NameBuff, |
3439 | char_to_long(b0.b0_ino))) { |
3440 | differ = TRUE; |
3441 | } |
3442 | } |
3443 | } else { |
3444 | // The name in the swap file may be |
3445 | // "~user/path/file". Expand it first. |
3446 | expand_env(b0.b0_fname, NameBuff, MAXPATHL); |
3447 | if (fnamecmp_ino(buf->b_ffname, NameBuff, |
3448 | char_to_long(b0.b0_ino))) { |
3449 | differ = TRUE; |
3450 | } |
3451 | } |
3452 | } |
3453 | close(fd); |
3454 | } |
3455 | |
3456 | // give the ATTENTION message when there is an old swap file |
3457 | // for the current file, and the buffer was not recovered. */ |
3458 | if (differ == false && !(curbuf->b_flags & BF_RECOVERED) |
3459 | && vim_strchr(p_shm, SHM_ATTENTION) == NULL) { |
3460 | int choice = 0; |
3461 | |
3462 | process_still_running = false; |
3463 | // It's safe to delete the swap file if all these are true: |
3464 | // - the edited file exists |
3465 | // - the swap file has no changes and looks OK |
3466 | if (os_path_exists(buf->b_fname) && swapfile_unchanged(fname)) { |
3467 | choice = 4; |
3468 | if (p_verbose > 0) { |
3469 | verb_msg(_("Found a swap file that is not useful, deleting it" )); |
3470 | } |
3471 | } |
3472 | |
3473 | // If there is a SwapExists autocommand and we can handle the |
3474 | // response, trigger it. It may return 0 to ask the user anyway. |
3475 | if (choice == 0 |
3476 | && swap_exists_action != SEA_NONE |
3477 | && has_autocmd(EVENT_SWAPEXISTS, (char_u *)buf_fname, buf)) { |
3478 | choice = do_swapexists(buf, (char_u *)fname); |
3479 | } |
3480 | |
3481 | if (choice == 0) { |
3482 | // Show info about the existing swap file. |
3483 | attention_message(buf, (char_u *)fname); |
3484 | |
3485 | // We don't want a 'q' typed at the more-prompt |
3486 | // interrupt loading a file. |
3487 | got_int = false; |
3488 | |
3489 | // If vimrc has "simalt ~x" we don't want it to |
3490 | // interfere with the prompt here. |
3491 | flush_buffers(FLUSH_TYPEAHEAD); |
3492 | } |
3493 | |
3494 | if (swap_exists_action != SEA_NONE && choice == 0) { |
3495 | const char *const sw_msg_1 = _("Swap file \"" ); |
3496 | const char *const sw_msg_2 = _("\" already exists!" ); |
3497 | |
3498 | const size_t fname_len = strlen(fname); |
3499 | const size_t sw_msg_1_len = strlen(sw_msg_1); |
3500 | const size_t sw_msg_2_len = strlen(sw_msg_2); |
3501 | |
3502 | const size_t name_len = sw_msg_1_len + fname_len + sw_msg_2_len + 5; |
3503 | |
3504 | char *const name = xmalloc(name_len); |
3505 | memcpy(name, sw_msg_1, sw_msg_1_len + 1); |
3506 | home_replace(NULL, (char_u *)fname, (char_u *)&name[sw_msg_1_len], |
3507 | fname_len, true); |
3508 | xstrlcat(name, sw_msg_2, name_len); |
3509 | choice = do_dialog(VIM_WARNING, (char_u *)_("VIM - ATTENTION" ), |
3510 | (char_u *)name, |
3511 | process_still_running |
3512 | ? (char_u *)_( |
3513 | "&Open Read-Only\n&Edit anyway\n&Recover" |
3514 | "\n&Quit\n&Abort" ) : |
3515 | (char_u *)_( |
3516 | "&Open Read-Only\n&Edit anyway\n&Recover" |
3517 | "\n&Delete it\n&Quit\n&Abort" ), |
3518 | 1, NULL, false); |
3519 | |
3520 | if (process_still_running && choice >= 4) { |
3521 | choice++; // Skip missing "Delete it" button. |
3522 | } |
3523 | xfree(name); |
3524 | |
3525 | // pretend screen didn't scroll, need redraw anyway |
3526 | msg_reset_scroll(); |
3527 | } |
3528 | |
3529 | if (choice > 0) { |
3530 | switch (choice) { |
3531 | case 1: |
3532 | buf->b_p_ro = TRUE; |
3533 | break; |
3534 | case 2: |
3535 | break; |
3536 | case 3: |
3537 | swap_exists_action = SEA_RECOVER; |
3538 | break; |
3539 | case 4: |
3540 | os_remove(fname); |
3541 | break; |
3542 | case 5: |
3543 | swap_exists_action = SEA_QUIT; |
3544 | break; |
3545 | case 6: |
3546 | swap_exists_action = SEA_QUIT; |
3547 | got_int = TRUE; |
3548 | break; |
3549 | } |
3550 | |
3551 | // If the file was deleted this fname can be used. |
3552 | if (!os_path_exists((char_u *)fname)) { |
3553 | break; |
3554 | } |
3555 | } else { |
3556 | MSG_PUTS("\n" ); |
3557 | if (msg_silent == 0) |
3558 | /* call wait_return() later */ |
3559 | need_wait_return = TRUE; |
3560 | } |
3561 | |
3562 | } |
3563 | } |
3564 | } |
3565 | |
3566 | /* |
3567 | * Change the ".swp" extension to find another file that can be used. |
3568 | * First decrement the last char: ".swo", ".swn", etc. |
3569 | * If that still isn't enough decrement the last but one char: ".svz" |
3570 | * Can happen when editing many "No Name" buffers. |
3571 | */ |
3572 | if (fname[n - 1] == 'a') { /* ".s?a" */ |
3573 | if (fname[n - 2] == 'a') { /* ".saa": tried enough, give up */ |
3574 | EMSG(_("E326: Too many swap files found" )); |
3575 | XFREE_CLEAR(fname); |
3576 | break; |
3577 | } |
3578 | --fname[n - 2]; /* ".svz", ".suz", etc. */ |
3579 | fname[n - 1] = 'z' + 1; |
3580 | } |
3581 | --fname[n - 1]; /* ".swo", ".swn", etc. */ |
3582 | } |
3583 | |
3584 | if (os_isdir((char_u *) dir_name)) { |
3585 | *found_existing_dir = true; |
3586 | } else if (!*found_existing_dir && **dirp == NUL) { |
3587 | int ret; |
3588 | char *failed_dir; |
3589 | if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir)) != 0) { |
3590 | EMSG3(_("E303: Unable to create directory \"%s\" for swap file, " |
3591 | "recovery impossible: %s" ), |
3592 | failed_dir, os_strerror(ret)); |
3593 | xfree(failed_dir); |
3594 | } |
3595 | } |
3596 | |
3597 | xfree(dir_name); |
3598 | return fname; |
3599 | } |
3600 | |
3601 | static int b0_magic_wrong(ZERO_BL *b0p) |
3602 | { |
3603 | return b0p->b0_magic_long != (long)B0_MAGIC_LONG |
3604 | || b0p->b0_magic_int != (int)B0_MAGIC_INT |
3605 | || b0p->b0_magic_short != (short)B0_MAGIC_SHORT |
3606 | || b0p->b0_magic_char != B0_MAGIC_CHAR; |
3607 | } |
3608 | |
3609 | /* |
3610 | * Compare current file name with file name from swap file. |
3611 | * Try to use inode numbers when possible. |
3612 | * Return non-zero when files are different. |
3613 | * |
3614 | * When comparing file names a few things have to be taken into consideration: |
3615 | * - When working over a network the full path of a file depends on the host. |
3616 | * We check the inode number if possible. It is not 100% reliable though, |
3617 | * because the device number cannot be used over a network. |
3618 | * - When a file does not exist yet (editing a new file) there is no inode |
3619 | * number. |
3620 | * - The file name in a swap file may not be valid on the current host. The |
3621 | * "~user" form is used whenever possible to avoid this. |
3622 | * |
3623 | * This is getting complicated, let's make a table: |
3624 | * |
3625 | * ino_c ino_s fname_c fname_s differ = |
3626 | * |
3627 | * both files exist -> compare inode numbers: |
3628 | * != 0 != 0 X X ino_c != ino_s |
3629 | * |
3630 | * inode number(s) unknown, file names available -> compare file names |
3631 | * == 0 X OK OK fname_c != fname_s |
3632 | * X == 0 OK OK fname_c != fname_s |
3633 | * |
3634 | * current file doesn't exist, file for swap file exist, file name(s) not |
3635 | * available -> probably different |
3636 | * == 0 != 0 FAIL X TRUE |
3637 | * == 0 != 0 X FAIL TRUE |
3638 | * |
3639 | * current file exists, inode for swap unknown, file name(s) not |
3640 | * available -> probably different |
3641 | * != 0 == 0 FAIL X TRUE |
3642 | * != 0 == 0 X FAIL TRUE |
3643 | * |
3644 | * current file doesn't exist, inode for swap unknown, one file name not |
3645 | * available -> probably different |
3646 | * == 0 == 0 FAIL OK TRUE |
3647 | * == 0 == 0 OK FAIL TRUE |
3648 | * |
3649 | * current file doesn't exist, inode for swap unknown, both file names not |
3650 | * available -> compare file names |
3651 | * == 0 == 0 FAIL FAIL fname_c != fname_s |
3652 | * |
3653 | * Only the last 32 bits of the inode will be used. This can't be changed |
3654 | * without making the block 0 incompatible with 32 bit versions. |
3655 | */ |
3656 | |
3657 | static bool fnamecmp_ino( |
3658 | char_u *fname_c, // current file name |
3659 | char_u *fname_s, // file name from swap file |
3660 | long ino_block0 |
3661 | ) |
3662 | { |
3663 | uint64_t ino_c = 0; /* ino of current file */ |
3664 | uint64_t ino_s; /* ino of file from swap file */ |
3665 | char_u buf_c[MAXPATHL]; /* full path of fname_c */ |
3666 | char_u buf_s[MAXPATHL]; /* full path of fname_s */ |
3667 | int retval_c; /* flag: buf_c valid */ |
3668 | int retval_s; /* flag: buf_s valid */ |
3669 | |
3670 | FileInfo file_info; |
3671 | if (os_fileinfo((char *)fname_c, &file_info)) { |
3672 | ino_c = os_fileinfo_inode(&file_info); |
3673 | } |
3674 | |
3675 | /* |
3676 | * First we try to get the inode from the file name, because the inode in |
3677 | * the swap file may be outdated. If that fails (e.g. this path is not |
3678 | * valid on this machine), use the inode from block 0. |
3679 | */ |
3680 | if (os_fileinfo((char *)fname_s, &file_info)) { |
3681 | ino_s = os_fileinfo_inode(&file_info); |
3682 | } else { |
3683 | ino_s = (uint64_t)ino_block0; |
3684 | } |
3685 | |
3686 | if (ino_c && ino_s) |
3687 | return ino_c != ino_s; |
3688 | |
3689 | /* |
3690 | * One of the inode numbers is unknown, try a forced vim_FullName() and |
3691 | * compare the file names. |
3692 | */ |
3693 | retval_c = vim_FullName((char *)fname_c, (char *)buf_c, MAXPATHL, TRUE); |
3694 | retval_s = vim_FullName((char *)fname_s, (char *)buf_s, MAXPATHL, TRUE); |
3695 | if (retval_c == OK && retval_s == OK) |
3696 | return STRCMP(buf_c, buf_s) != 0; |
3697 | |
3698 | /* |
3699 | * Can't compare inodes or file names, guess that the files are different, |
3700 | * unless both appear not to exist at all, then compare with the file name |
3701 | * in the swap file. |
3702 | */ |
3703 | if (ino_s == 0 && ino_c == 0 && retval_c == FAIL && retval_s == FAIL) { |
3704 | return STRCMP(fname_c, fname_s) != 0; |
3705 | } |
3706 | return true; |
3707 | } |
3708 | |
3709 | /* |
3710 | * Move a long integer into a four byte character array. |
3711 | * Used for machine independency in block zero. |
3712 | */ |
3713 | static void long_to_char(long n, char_u *s) |
3714 | { |
3715 | s[0] = (char_u)(n & 0xff); |
3716 | n = (unsigned)n >> 8; |
3717 | s[1] = (char_u)(n & 0xff); |
3718 | n = (unsigned)n >> 8; |
3719 | s[2] = (char_u)(n & 0xff); |
3720 | n = (unsigned)n >> 8; |
3721 | s[3] = (char_u)(n & 0xff); |
3722 | } |
3723 | |
3724 | static long char_to_long(char_u *s) |
3725 | { |
3726 | long retval; |
3727 | |
3728 | retval = s[3]; |
3729 | retval <<= 8; |
3730 | retval |= s[2]; |
3731 | retval <<= 8; |
3732 | retval |= s[1]; |
3733 | retval <<= 8; |
3734 | retval |= s[0]; |
3735 | |
3736 | return retval; |
3737 | } |
3738 | |
3739 | /* |
3740 | * Set the flags in the first block of the swap file: |
3741 | * - file is modified or not: buf->b_changed |
3742 | * - 'fileformat' |
3743 | * - 'fileencoding' |
3744 | */ |
3745 | void ml_setflags(buf_T *buf) |
3746 | { |
3747 | bhdr_T *hp; |
3748 | ZERO_BL *b0p; |
3749 | |
3750 | if (!buf->b_ml.ml_mfp) |
3751 | return; |
3752 | for (hp = buf->b_ml.ml_mfp->mf_used_last; hp != NULL; hp = hp->bh_prev) { |
3753 | if (hp->bh_bnum == 0) { |
3754 | b0p = hp->bh_data; |
3755 | b0p->b0_dirty = buf->b_changed ? B0_DIRTY : 0; |
3756 | b0p->b0_flags = (b0p->b0_flags & ~B0_FF_MASK) |
3757 | | (get_fileformat(buf) + 1); |
3758 | add_b0_fenc(b0p, buf); |
3759 | hp->bh_flags |= BH_DIRTY; |
3760 | mf_sync(buf->b_ml.ml_mfp, MFS_ZERO); |
3761 | break; |
3762 | } |
3763 | } |
3764 | } |
3765 | |
3766 | #define MLCS_MAXL 800 /* max no of lines in chunk */ |
3767 | #define MLCS_MINL 400 /* should be half of MLCS_MAXL */ |
3768 | |
3769 | /* |
3770 | * Keep information for finding byte offset of a line, updtype may be one of: |
3771 | * ML_CHNK_ADDLINE: Add len to parent chunk, possibly splitting it |
3772 | * Careful: ML_CHNK_ADDLINE may cause ml_find_line() to be called. |
3773 | * ML_CHNK_DELLINE: Subtract len from parent chunk, possibly deleting it |
3774 | * ML_CHNK_UPDLINE: Add len to parent chunk, as a signed entity. |
3775 | */ |
3776 | static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype) |
3777 | { |
3778 | static buf_T *ml_upd_lastbuf = NULL; |
3779 | static linenr_T ml_upd_lastline; |
3780 | static linenr_T ml_upd_lastcurline; |
3781 | static int ml_upd_lastcurix; |
3782 | |
3783 | linenr_T curline = ml_upd_lastcurline; |
3784 | int curix = ml_upd_lastcurix; |
3785 | long size; |
3786 | chunksize_T *curchnk; |
3787 | int rest; |
3788 | bhdr_T *hp; |
3789 | DATA_BL *dp; |
3790 | |
3791 | if (buf->b_ml.ml_usedchunks == -1 || len == 0) |
3792 | return; |
3793 | if (buf->b_ml.ml_chunksize == NULL) { |
3794 | buf->b_ml.ml_chunksize = xmalloc(sizeof(chunksize_T) * 100); |
3795 | buf->b_ml.ml_numchunks = 100; |
3796 | buf->b_ml.ml_usedchunks = 1; |
3797 | buf->b_ml.ml_chunksize[0].mlcs_numlines = 1; |
3798 | buf->b_ml.ml_chunksize[0].mlcs_totalsize = 1; |
3799 | } |
3800 | |
3801 | if (updtype == ML_CHNK_UPDLINE && buf->b_ml.ml_line_count == 1) { |
3802 | /* |
3803 | * First line in empty buffer from ml_flush_line() -- reset |
3804 | */ |
3805 | buf->b_ml.ml_usedchunks = 1; |
3806 | buf->b_ml.ml_chunksize[0].mlcs_numlines = 1; |
3807 | buf->b_ml.ml_chunksize[0].mlcs_totalsize = |
3808 | (long)STRLEN(buf->b_ml.ml_line_ptr) + 1; |
3809 | return; |
3810 | } |
3811 | |
3812 | /* |
3813 | * Find chunk that our line belongs to, curline will be at start of the |
3814 | * chunk. |
3815 | */ |
3816 | if (buf != ml_upd_lastbuf || line != ml_upd_lastline + 1 |
3817 | || updtype != ML_CHNK_ADDLINE) { |
3818 | for (curline = 1, curix = 0; |
3819 | curix < buf->b_ml.ml_usedchunks - 1 |
3820 | && line >= curline + |
3821 | buf->b_ml.ml_chunksize[curix].mlcs_numlines; |
3822 | curix++) { |
3823 | curline += buf->b_ml.ml_chunksize[curix].mlcs_numlines; |
3824 | } |
3825 | } else if (curix < buf->b_ml.ml_usedchunks - 1 |
3826 | && line >= curline + buf->b_ml.ml_chunksize[curix].mlcs_numlines) { |
3827 | // Adjust cached curix & curline |
3828 | curline += buf->b_ml.ml_chunksize[curix].mlcs_numlines; |
3829 | curix++; |
3830 | } |
3831 | curchnk = buf->b_ml.ml_chunksize + curix; |
3832 | |
3833 | if (updtype == ML_CHNK_DELLINE) |
3834 | len = -len; |
3835 | curchnk->mlcs_totalsize += len; |
3836 | if (updtype == ML_CHNK_ADDLINE) { |
3837 | curchnk->mlcs_numlines++; |
3838 | |
3839 | /* May resize here so we don't have to do it in both cases below */ |
3840 | if (buf->b_ml.ml_usedchunks + 1 >= buf->b_ml.ml_numchunks) { |
3841 | buf->b_ml.ml_numchunks = buf->b_ml.ml_numchunks * 3 / 2; |
3842 | buf->b_ml.ml_chunksize = (chunksize_T *) |
3843 | xrealloc(buf->b_ml.ml_chunksize, |
3844 | sizeof(chunksize_T) * buf->b_ml.ml_numchunks); |
3845 | } |
3846 | |
3847 | if (buf->b_ml.ml_chunksize[curix].mlcs_numlines >= MLCS_MAXL) { |
3848 | int count; /* number of entries in block */ |
3849 | int idx; |
3850 | int text_end; |
3851 | int linecnt; |
3852 | |
3853 | memmove(buf->b_ml.ml_chunksize + curix + 1, |
3854 | buf->b_ml.ml_chunksize + curix, |
3855 | (buf->b_ml.ml_usedchunks - curix) * |
3856 | sizeof(chunksize_T)); |
3857 | /* Compute length of first half of lines in the split chunk */ |
3858 | size = 0; |
3859 | linecnt = 0; |
3860 | while (curline < buf->b_ml.ml_line_count |
3861 | && linecnt < MLCS_MINL) { |
3862 | if ((hp = ml_find_line(buf, curline, ML_FIND)) == NULL) { |
3863 | buf->b_ml.ml_usedchunks = -1; |
3864 | return; |
3865 | } |
3866 | dp = hp->bh_data; |
3867 | count = (long)(buf->b_ml.ml_locked_high) - |
3868 | (long)(buf->b_ml.ml_locked_low) + 1; |
3869 | idx = curline - buf->b_ml.ml_locked_low; |
3870 | curline = buf->b_ml.ml_locked_high + 1; |
3871 | if (idx == 0) /* first line in block, text at the end */ |
3872 | text_end = dp->db_txt_end; |
3873 | else |
3874 | text_end = ((dp->db_index[idx - 1]) & DB_INDEX_MASK); |
3875 | /* Compute index of last line to use in this MEMLINE */ |
3876 | rest = count - idx; |
3877 | if (linecnt + rest > MLCS_MINL) { |
3878 | idx += MLCS_MINL - linecnt - 1; |
3879 | linecnt = MLCS_MINL; |
3880 | } else { |
3881 | idx = count - 1; |
3882 | linecnt += rest; |
3883 | } |
3884 | size += text_end - ((dp->db_index[idx]) & DB_INDEX_MASK); |
3885 | } |
3886 | buf->b_ml.ml_chunksize[curix].mlcs_numlines = linecnt; |
3887 | buf->b_ml.ml_chunksize[curix + 1].mlcs_numlines -= linecnt; |
3888 | buf->b_ml.ml_chunksize[curix].mlcs_totalsize = size; |
3889 | buf->b_ml.ml_chunksize[curix + 1].mlcs_totalsize -= size; |
3890 | buf->b_ml.ml_usedchunks++; |
3891 | ml_upd_lastbuf = NULL; /* Force recalc of curix & curline */ |
3892 | return; |
3893 | } else if (buf->b_ml.ml_chunksize[curix].mlcs_numlines >= MLCS_MINL |
3894 | && curix == buf->b_ml.ml_usedchunks - 1 |
3895 | && buf->b_ml.ml_line_count - line <= 1) { |
3896 | /* |
3897 | * We are in the last chunk and it is cheap to crate a new one |
3898 | * after this. Do it now to avoid the loop above later on |
3899 | */ |
3900 | curchnk = buf->b_ml.ml_chunksize + curix + 1; |
3901 | buf->b_ml.ml_usedchunks++; |
3902 | if (line == buf->b_ml.ml_line_count) { |
3903 | curchnk->mlcs_numlines = 0; |
3904 | curchnk->mlcs_totalsize = 0; |
3905 | } else { |
3906 | /* |
3907 | * Line is just prior to last, move count for last |
3908 | * This is the common case when loading a new file |
3909 | */ |
3910 | hp = ml_find_line(buf, buf->b_ml.ml_line_count, ML_FIND); |
3911 | if (hp == NULL) { |
3912 | buf->b_ml.ml_usedchunks = -1; |
3913 | return; |
3914 | } |
3915 | dp = hp->bh_data; |
3916 | if (dp->db_line_count == 1) |
3917 | rest = dp->db_txt_end - dp->db_txt_start; |
3918 | else |
3919 | rest = |
3920 | ((dp->db_index[dp->db_line_count - 2]) & DB_INDEX_MASK) |
3921 | - dp->db_txt_start; |
3922 | curchnk->mlcs_totalsize = rest; |
3923 | curchnk->mlcs_numlines = 1; |
3924 | curchnk[-1].mlcs_totalsize -= rest; |
3925 | curchnk[-1].mlcs_numlines -= 1; |
3926 | } |
3927 | } |
3928 | } else if (updtype == ML_CHNK_DELLINE) { |
3929 | curchnk->mlcs_numlines--; |
3930 | ml_upd_lastbuf = NULL; /* Force recalc of curix & curline */ |
3931 | if (curix < (buf->b_ml.ml_usedchunks - 1) |
3932 | && (curchnk->mlcs_numlines + curchnk[1].mlcs_numlines) |
3933 | <= MLCS_MINL) { |
3934 | curix++; |
3935 | curchnk = buf->b_ml.ml_chunksize + curix; |
3936 | } else if (curix == 0 && curchnk->mlcs_numlines <= 0) { |
3937 | buf->b_ml.ml_usedchunks--; |
3938 | memmove(buf->b_ml.ml_chunksize, buf->b_ml.ml_chunksize + 1, |
3939 | buf->b_ml.ml_usedchunks * sizeof(chunksize_T)); |
3940 | return; |
3941 | } else if (curix == 0 || (curchnk->mlcs_numlines > 10 |
3942 | && (curchnk->mlcs_numlines + |
3943 | curchnk[-1].mlcs_numlines) |
3944 | > MLCS_MINL)) { |
3945 | return; |
3946 | } |
3947 | |
3948 | /* Collapse chunks */ |
3949 | curchnk[-1].mlcs_numlines += curchnk->mlcs_numlines; |
3950 | curchnk[-1].mlcs_totalsize += curchnk->mlcs_totalsize; |
3951 | buf->b_ml.ml_usedchunks--; |
3952 | if (curix < buf->b_ml.ml_usedchunks) { |
3953 | memmove(buf->b_ml.ml_chunksize + curix, |
3954 | buf->b_ml.ml_chunksize + curix + 1, |
3955 | (buf->b_ml.ml_usedchunks - curix) * |
3956 | sizeof(chunksize_T)); |
3957 | } |
3958 | return; |
3959 | } |
3960 | ml_upd_lastbuf = buf; |
3961 | ml_upd_lastline = line; |
3962 | ml_upd_lastcurline = curline; |
3963 | ml_upd_lastcurix = curix; |
3964 | } |
3965 | |
3966 | /// Find offset for line or line with offset. |
3967 | /// |
3968 | /// @param buf buffer to use |
3969 | /// @param lnum if > 0, find offset of lnum, store offset in offp |
3970 | /// if == 0, return line with offset *offp |
3971 | /// @param offp Location where offset of line is stored, or to read offset to |
3972 | /// use to find line. In the later case, store remaining offset. |
3973 | /// @param no_ff ignore 'fileformat' option, always use one byte for NL. |
3974 | /// |
3975 | /// @return -1 if information is not available |
3976 | long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff) |
3977 | { |
3978 | linenr_T curline; |
3979 | int curix; |
3980 | long size; |
3981 | bhdr_T *hp; |
3982 | DATA_BL *dp; |
3983 | int count; /* number of entries in block */ |
3984 | int idx; |
3985 | int start_idx; |
3986 | int text_end; |
3987 | long offset; |
3988 | int len; |
3989 | int ffdos = !no_ff && (get_fileformat(buf) == EOL_DOS); |
3990 | int = 0; |
3991 | |
3992 | /* take care of cached line first */ |
3993 | ml_flush_line(curbuf); |
3994 | |
3995 | if (buf->b_ml.ml_usedchunks == -1 |
3996 | || buf->b_ml.ml_chunksize == NULL |
3997 | || lnum < 0) |
3998 | return -1; |
3999 | |
4000 | if (offp == NULL) |
4001 | offset = 0; |
4002 | else |
4003 | offset = *offp; |
4004 | if (lnum == 0 && offset <= 0) |
4005 | return 1; /* Not a "find offset" and offset 0 _must_ be in line 1 */ |
4006 | /* |
4007 | * Find the last chunk before the one containing our line. Last chunk is |
4008 | * special because it will never qualify |
4009 | */ |
4010 | curline = 1; |
4011 | curix = size = 0; |
4012 | while (curix < buf->b_ml.ml_usedchunks - 1 |
4013 | && ((lnum != 0 |
4014 | && lnum >= curline + buf->b_ml.ml_chunksize[curix].mlcs_numlines) |
4015 | || (offset != 0 |
4016 | && offset > size + |
4017 | buf->b_ml.ml_chunksize[curix].mlcs_totalsize |
4018 | + ffdos * buf->b_ml.ml_chunksize[curix].mlcs_numlines))) { |
4019 | curline += buf->b_ml.ml_chunksize[curix].mlcs_numlines; |
4020 | size += buf->b_ml.ml_chunksize[curix].mlcs_totalsize; |
4021 | if (offset && ffdos) |
4022 | size += buf->b_ml.ml_chunksize[curix].mlcs_numlines; |
4023 | curix++; |
4024 | } |
4025 | |
4026 | while ((lnum != 0 && curline < lnum) || (offset != 0 && size < offset)) { |
4027 | if (curline > buf->b_ml.ml_line_count |
4028 | || (hp = ml_find_line(buf, curline, ML_FIND)) == NULL) |
4029 | return -1; |
4030 | dp = hp->bh_data; |
4031 | count = (long)(buf->b_ml.ml_locked_high) - |
4032 | (long)(buf->b_ml.ml_locked_low) + 1; |
4033 | start_idx = idx = curline - buf->b_ml.ml_locked_low; |
4034 | if (idx == 0) /* first line in block, text at the end */ |
4035 | text_end = dp->db_txt_end; |
4036 | else |
4037 | text_end = ((dp->db_index[idx - 1]) & DB_INDEX_MASK); |
4038 | /* Compute index of last line to use in this MEMLINE */ |
4039 | if (lnum != 0) { |
4040 | if (curline + (count - idx) >= lnum) |
4041 | idx += lnum - curline - 1; |
4042 | else |
4043 | idx = count - 1; |
4044 | } else { |
4045 | extra = 0; |
4046 | while (offset >= size |
4047 | + text_end - (int)((dp->db_index[idx]) & DB_INDEX_MASK) |
4048 | + ffdos) { |
4049 | if (ffdos) |
4050 | size++; |
4051 | if (idx == count - 1) { |
4052 | extra = 1; |
4053 | break; |
4054 | } |
4055 | idx++; |
4056 | } |
4057 | } |
4058 | len = text_end - ((dp->db_index[idx]) & DB_INDEX_MASK); |
4059 | size += len; |
4060 | if (offset != 0 && size >= offset) { |
4061 | if (size + ffdos == offset) |
4062 | *offp = 0; |
4063 | else if (idx == start_idx) |
4064 | *offp = offset - size + len; |
4065 | else |
4066 | *offp = offset - size + len |
4067 | - (text_end - ((dp->db_index[idx - 1]) & DB_INDEX_MASK)); |
4068 | curline += idx - start_idx + extra; |
4069 | if (curline > buf->b_ml.ml_line_count) |
4070 | return -1; /* exactly one byte beyond the end */ |
4071 | return curline; |
4072 | } |
4073 | curline = buf->b_ml.ml_locked_high + 1; |
4074 | } |
4075 | |
4076 | if (lnum != 0) { |
4077 | /* Count extra CR characters. */ |
4078 | if (ffdos) |
4079 | size += lnum - 1; |
4080 | |
4081 | /* Don't count the last line break if 'noeol' and ('bin' or |
4082 | * 'nofixeol'). */ |
4083 | if ((!buf->b_p_fixeol || buf->b_p_bin) && !buf->b_p_eol |
4084 | && lnum > buf->b_ml.ml_line_count) { |
4085 | size -= ffdos + 1; |
4086 | } |
4087 | } |
4088 | |
4089 | return size; |
4090 | } |
4091 | |
4092 | /// Goto byte in buffer with offset 'cnt'. |
4093 | void goto_byte(long cnt) |
4094 | { |
4095 | long boff = cnt; |
4096 | linenr_T lnum; |
4097 | |
4098 | ml_flush_line(curbuf); // cached line may be dirty |
4099 | setpcmark(); |
4100 | if (boff) { |
4101 | boff--; |
4102 | } |
4103 | lnum = ml_find_line_or_offset(curbuf, (linenr_T)0, &boff, false); |
4104 | if (lnum < 1) { // past the end |
4105 | curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; |
4106 | curwin->w_curswant = MAXCOL; |
4107 | coladvance((colnr_T)MAXCOL); |
4108 | } else { |
4109 | curwin->w_cursor.lnum = lnum; |
4110 | curwin->w_cursor.col = (colnr_T)boff; |
4111 | curwin->w_cursor.coladd = 0; |
4112 | curwin->w_set_curswant = TRUE; |
4113 | } |
4114 | check_cursor(); |
4115 | |
4116 | // Make sure the cursor is on the first byte of a multi-byte char. |
4117 | if (has_mbyte) { |
4118 | mb_adjust_cursor(); |
4119 | } |
4120 | } |
4121 | |
4122 | /// Increment the line pointer "lp" crossing line boundaries as necessary. |
4123 | /// Return 1 when going to the next line. |
4124 | /// Return 2 when moving forward onto a NUL at the end of the line). |
4125 | /// Return -1 when at the end of file. |
4126 | /// Return 0 otherwise. |
4127 | int inc(pos_T *lp) |
4128 | { |
4129 | // when searching position may be set to end of a line |
4130 | if (lp->col != MAXCOL) { |
4131 | const char_u *const p = ml_get_pos(lp); |
4132 | if (*p != NUL) { // still within line, move to next char (may be NUL) |
4133 | const int l = utfc_ptr2len(p); |
4134 | |
4135 | lp->col += l; |
4136 | return ((p[l] != NUL) ? 0 : 2); |
4137 | } |
4138 | } |
4139 | if (lp->lnum != curbuf->b_ml.ml_line_count) { // there is a next line |
4140 | lp->col = 0; |
4141 | lp->lnum++; |
4142 | lp->coladd = 0; |
4143 | return 1; |
4144 | } |
4145 | return -1; |
4146 | } |
4147 | |
4148 | /// Same as inc(), but skip NUL at the end of non-empty lines. |
4149 | int incl(pos_T *lp) |
4150 | { |
4151 | int r; |
4152 | |
4153 | if ((r = inc(lp)) >= 1 && lp->col) { |
4154 | r = inc(lp); |
4155 | } |
4156 | return r; |
4157 | } |
4158 | |
4159 | int dec(pos_T *lp) |
4160 | { |
4161 | lp->coladd = 0; |
4162 | if (lp->col == MAXCOL) { |
4163 | // past end of line |
4164 | char_u *p = ml_get(lp->lnum); |
4165 | lp->col = (colnr_T)STRLEN(p); |
4166 | lp->col -= utf_head_off(p, p + lp->col); |
4167 | return 0; |
4168 | } |
4169 | |
4170 | if (lp->col > 0) { |
4171 | // still within line |
4172 | lp->col--; |
4173 | char_u *p = ml_get(lp->lnum); |
4174 | lp->col -= utf_head_off(p, p + lp->col); |
4175 | return 0; |
4176 | } |
4177 | if (lp->lnum > 1) { |
4178 | // there is a prior line |
4179 | lp->lnum--; |
4180 | char_u *p = ml_get(lp->lnum); |
4181 | lp->col = (colnr_T)STRLEN(p); |
4182 | lp->col -= utf_head_off(p, p + lp->col); |
4183 | return 1; |
4184 | } |
4185 | |
4186 | // at start of file |
4187 | return -1; |
4188 | } |
4189 | |
4190 | /// Same as dec(), but skip NUL at the end of non-empty lines. |
4191 | int decl(pos_T *lp) |
4192 | { |
4193 | int r; |
4194 | |
4195 | if ((r = dec(lp)) == 1 && lp->col) { |
4196 | r = dec(lp); |
4197 | } |
4198 | return r; |
4199 | } |
4200 | |