#include #include #include #include #include #include #include #include #if defined (__GLIBC__) # ifndef _BSD_SOURCE # define _BSD_SOURCE 1 # endif # ifndef _DEFAULT_SOURCE # define _DEFAULT_SOURCE 1 # endif # include #endif #include #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/vim.h" #include "nvim/ascii.h" #include "nvim/shada.h" #include "nvim/message.h" #include "nvim/globals.h" #include "nvim/memory.h" #include "nvim/mark.h" #include "nvim/ops.h" #include "nvim/garray.h" #include "nvim/option.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/globals.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/misc2.h" #include "nvim/ex_getln.h" #include "nvim/search.h" #include "nvim/eval.h" #include "nvim/eval_defs.h" #include "nvim/version.h" #include "nvim/path.h" #include "nvim/fileio.h" #include "nvim/strings.h" #include "nvim/lib/khash.h" #include "nvim/lib/kvec.h" // Note: when using bufset hash pointers are intentionally casted to uintptr_t // and not to khint32_t or khint64_t: this way compiler must give a warning // (-Wconversion) when types change. #ifdef ARCH_32 KHASH_SET_INIT_INT(bufset) #elif defined(ARCH_64) KHASH_SET_INIT_INT64(bufset) #else # error Not a 64- or 32-bit architecture #endif KHASH_MAP_INIT_STR(fnamebufs, buf_T *) KHASH_SET_INIT_STR(strset) #define copy_option_part(src, dest, ...) \ ((char *) copy_option_part((char_u **) src, (char_u *) dest, __VA_ARGS__)) #define find_shada_parameter(...) \ ((const char *) find_shada_parameter(__VA_ARGS__)) #define emsg2(a, b) emsg2((char_u *) a, (char_u *) b) #define emsg3(a, b, c) emsg3((char_u *) a, (char_u *) b, (char_u *) c) #define emsgu(a, ...) emsgu((char_u *) a, __VA_ARGS__) #define home_replace_save(a, b) \ ((char *)home_replace_save(a, (char_u *)b)) #define vim_rename(a, b) \ (vim_rename((char_u *)a, (char_u *)b)) #define has_non_ascii(a) (has_non_ascii((char_u *)a)) #define string_convert(a, b, c) \ ((char *)string_convert((vimconv_T *)a, (char_u *)b, c)) #define path_shorten_fname_if_possible(b) \ ((char *)path_shorten_fname_if_possible((char_u *)b)) #define buflist_new(ffname, sfname, ...) \ (buflist_new((char_u *)ffname, (char_u *)sfname, __VA_ARGS__)) #define convert_setup(vcp, from, to) \ (convert_setup(vcp, (char_u *)from, (char_u *)to)) #define os_getperm(f) \ (os_getperm((char_u *) f)) #define os_isdir(f) (os_isdir((char_u *) f)) #define path_tail_with_sep(f) ((char *) path_tail_with_sep((char_u *)f)) // From http://www.boost.org/doc/libs/1_43_0/boost/detail/endian.hpp + some // additional checks done after examining `{compiler} -dM -E - < /dev/null` // output. #if defined (__GLIBC__) # if (__BYTE_ORDER == __BIG_ENDIAN) # define SHADA_BIG_ENDIAN # endif #elif defined(_BIG_ENDIAN) || defined(_LITTLE_ENDIAN) # if defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) # define SHADA_BIG_ENDIAN # endif // clang-specific #elif defined(__BIG_ENDIAN__) || defined(__LITTLE_ENDIAN__) # if defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) # define SHADA_BIG_ENDIAN # endif // pcc-, gcc- and clang-specific #elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) # if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ # define SHADA_BIG_ENDIAN # endif #elif defined(__sparc) || defined(__sparc__) \ || defined(_POWER) || defined(__powerpc__) \ || defined(__ppc__) || defined(__hpux) || defined(__hppa) \ || defined(_MIPSEB) || defined(_POWER) \ || defined(__s390__) # define SHADA_BIG_ENDIAN #elif defined(__i386__) || defined(__alpha__) \ || defined(__ia64) || defined(__ia64__) \ || defined(_M_IX86) || defined(_M_IA64) \ || defined(_M_ALPHA) || defined(__amd64) \ || defined(__amd64__) || defined(_M_AMD64) \ || defined(__x86_64) || defined(__x86_64__) \ || defined(_M_X64) || defined(__bfin__) // Define nothing #endif /// Possible ShaDa entry types /// /// @warning Enum values are part of the API and must not be altered. /// /// All values that are not in enum are ignored. typedef enum { kSDItemUnknown = -1, ///< Unknown item. kSDItemMissing = 0, ///< Missing value. Should never appear in a file. kSDItemHeader = 1, ///< Header. Present for debugging purposes. kSDItemSearchPattern = 2, ///< Last search pattern (*not* history item). ///< Comes from user searches (e.g. when typing ///< "/pat") or :substitute command calls. kSDItemSubString = 3, ///< Last substitute replacement string. kSDItemHistoryEntry = 4, ///< History item. kSDItemRegister = 5, ///< Register. kSDItemVariable = 6, ///< Global variable. kSDItemGlobalMark = 7, ///< Global mark definition. kSDItemJump = 8, ///< Item from jump list. kSDItemBufferList = 9, ///< Buffer list. kSDItemLocalMark = 10, ///< Buffer-local mark. kSDItemChange = 11, ///< Item from buffer change list. #define SHADA_LAST_ENTRY ((uint64_t) kSDItemChange) } ShadaEntryType; /// Flags for shada_read_next_item enum SRNIFlags { kSDReadHeader = (1 << kSDItemHeader), ///< Determines whether header should ///< be read (it is usually ignored). kSDReadUndisableableData = ( (1 << kSDItemSearchPattern) | (1 << kSDItemSubString) | (1 << kSDItemGlobalMark) | (1 << kSDItemJump) ), ///< Data reading which cannot be disabled by &shada or other options ///< except for disabling reading ShaDa as a whole. kSDReadRegisters = (1 << kSDItemRegister), ///< Determines whether registers ///< should be read (may only be ///< disabled when writing, but ///< not when reading). kSDReadHistory = (1 << kSDItemHistoryEntry), ///< Determines whether history ///< should be read (can only be ///< disabled by &history). kSDReadVariables = (1 << kSDItemVariable), ///< Determines whether variables ///< should be read (disabled by ///< removing ! from &shada). kSDReadBufferList = (1 << kSDItemBufferList), ///< Determines whether buffer ///< list should be read ///< (disabled by removing ///< % entry from &shada). kSDReadUnknown = (1 << (SHADA_LAST_ENTRY + 1)), ///< Determines whether ///< unknown items should be ///< read (usually disabled). kSDReadLocalMarks = (1 << kSDItemLocalMark), ///< Determines whether local ///< marks should be read. Can ///< only be disabled by ///< disabling &shada or putting ///< '0 there. Is also used for ///< v:oldfiles. kSDReadChanges = (1 << kSDItemChange), ///< Determines whether change list ///< should be read. Can only be ///< disabled by disabling &shada or ///< putting '0 there. }; // Note: SRNIFlags enum name was created only to make it possible to reference // it. This name is not actually used anywhere outside of the documentation. /// Structure defining a single ShaDa file entry typedef struct { ShadaEntryType type; Timestamp timestamp; union { Dictionary header; struct shada_filemark { char name; pos_T mark; char *fname; Dictionary *additional_data; } filemark; struct search_pattern { bool magic; bool smartcase; bool has_line_offset; bool place_cursor_at_end; int64_t offset; bool is_last_used; bool is_substitute_pattern; bool highlighted; char *pat; Dictionary *additional_data; } search_pattern; struct history_item { uint8_t histtype; char *string; char sep; bool canfree; Array *additional_elements; } history_item; struct reg { char name; uint8_t type; char **contents; size_t contents_size; size_t width; Dictionary *additional_data; } reg; struct global_var { char *name; Object value; Array *additional_elements; } global_var; struct { uint64_t type; char *contents; size_t size; } unknown_item; struct sub_string { char *sub; Array *additional_elements; } sub_string; struct buffer_list { size_t size; struct buffer_list_buffer { pos_T pos; char *fname; Dictionary *additional_data; } *buffers; } buffer_list; } data; } ShadaEntry; struct hm_llist_entry; /// One entry in sized linked list typedef struct hm_llist_entry { ShadaEntry data; ///< Entry data. struct hm_llist_entry *next; ///< Pointer to next entry or NULL. struct hm_llist_entry *prev; ///< Pointer to previous entry or NULL. } HMLListEntry; /// Sized linked list structure for history merger typedef struct { HMLListEntry *entries; ///< Pointer to the start of the allocated array of ///< entries. HMLListEntry *first; ///< First entry in the list (is not necessary start ///< of the array) or NULL. HMLListEntry *last; ///< Last entry in the list or NULL. HMLListEntry **free_entries; ///< Free array entries. HMLListEntry *last_free_element; ///< Last free array element. size_t size; ///< Number of allocated entries. size_t free_entries_size; ///< Number of non-NULL entries in free_entries. size_t num_entries; ///< Number of entries already used. } HMLList; typedef struct { HMLList hmll; bool do_merge; bool reading; const void *iter; ShadaEntry last_hist_entry; uint8_t history_type; } HistoryMergerState; struct sd_read_def; /// Function used to read ShaDa files typedef ptrdiff_t (*ShaDaFileReader)(struct sd_read_def *const sd_reader, void *const dest, const size_t size) REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT; /// Structure containing necessary pointers for reading ShaDa files typedef struct sd_read_def { ShaDaFileReader read; ///< Reader function. void *cookie; ///< Reader function last argument. bool eof; ///< True if reader reached end of file. char *error; ///< Error message in case of error. uintmax_t fpos; ///< Current position (amount of bytes read since ///< reader structure initialization). May overflow. vimconv_T sd_conv; ///< Structure used for converting encodings of some ///< items. } ShaDaReadDef; struct sd_write_def; /// Function used to write ShaDa files typedef ptrdiff_t (*ShaDaFileWriter)(struct sd_write_def *const sd_writer, const void *const src, const size_t size) REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT; /// Structure containing necessary pointers for writing ShaDa files typedef struct sd_write_def { ShaDaFileWriter write; ///< Writer function. void *cookie; ///< Writer function last argument. char *error; ///< Error message in case of error. vimconv_T sd_conv; ///< Structure used for converting encodings of some ///< items. } ShaDaWriteDef; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "shada.c.generated.h" #endif /// Initialize new linked list /// /// @param[out] hmll List to initialize. /// @param[in] size Maximum size of the list. static inline void hmll_init(HMLList *const hmll, const size_t size) FUNC_ATTR_NONNULL_ALL { *hmll = (HMLList) { .entries = xcalloc(size, sizeof(hmll->entries[0])), .first = NULL, .last = NULL, .free_entries = NULL, .size = size, .free_entries_size = 0, .num_entries = 0, }; hmll->last_free_element = hmll->entries; } /// Iterate over HMLList in forward direction /// /// @param hmll Pointer to the list. /// @param cur_entry Name of the variable to iterate over. /// /// @return `for` cycle header (use `HMLL_FORALL(hmll, cur_entry) {body}`). #define HMLL_FORALL(hmll, cur_entry) \ for (HMLListEntry *cur_entry = (hmll)->first; cur_entry != NULL; \ cur_entry = cur_entry->next) /// Remove entry from the linked list /// /// @param hmll List to remove from. /// @param hmll_entry Entry to remove. static inline void hmll_remove(HMLList *const hmll, HMLListEntry *const hmll_entry) FUNC_ATTR_NONNULL_ALL { if (hmll->free_entries == NULL) { if (hmll_entry == hmll->last_free_element) { hmll->last_free_element--; } else { hmll->free_entries = xcalloc(hmll->size, sizeof(hmll->free_entries[0])); hmll->free_entries[hmll->free_entries_size++] = hmll_entry; } } else { hmll->free_entries[hmll->free_entries_size++] = hmll_entry; } if (hmll_entry->next == NULL) { hmll->last = hmll_entry->prev; } else { hmll_entry->next->prev = hmll_entry->prev; } if (hmll_entry->prev == NULL) { hmll->first = hmll_entry->next; } else { hmll_entry->prev->next = hmll_entry->next; } hmll->num_entries--; shada_free_shada_entry(&hmll_entry->data); } /// Insert entry to the linked list /// /// @param[out] hmll List to insert to. /// @param[in] hmll_entry Entry to insert after or NULL if it is needed to /// insert at the first entry. /// @param[in] data Data to insert. static inline void hmll_insert(HMLList *const hmll, HMLListEntry *hmll_entry, const ShadaEntry data) FUNC_ATTR_NONNULL_ARG(1) { if (hmll->num_entries == hmll->size) { if (hmll_entry == hmll->first) { hmll_entry = NULL; } hmll_remove(hmll, hmll->first); } HMLListEntry *target_entry; if (hmll->free_entries == NULL) { assert((size_t) (hmll->last_free_element - hmll->entries) == hmll->num_entries); target_entry = hmll->last_free_element++; } else { target_entry = hmll->free_entries[--hmll->free_entries_size]; } target_entry->data = data; hmll->num_entries++; target_entry->prev = hmll_entry; if (hmll_entry == NULL) { target_entry->next = hmll->first; hmll->first = target_entry; } else { target_entry->next = hmll_entry->next; hmll_entry->next = target_entry; } if (target_entry->next == NULL) { hmll->last = target_entry; } else { target_entry->next->prev = target_entry; } } /// Iterate over HMLList in backward direction /// /// @param hmll Pointer to the list. /// @param cur_entry Name of the variable to iterate over, must be already /// defined. /// /// @return `for` cycle header (use `HMLL_FORALL(hmll, cur_entry) {body}`). #define HMLL_ITER_BACK(hmll, cur_entry) \ for (cur_entry = (hmll)->last; cur_entry != NULL; \ cur_entry = cur_entry->prev) /// Free linked list /// /// @param[in] hmll List to free. static inline void hmll_dealloc(HMLList *const hmll) FUNC_ATTR_NONNULL_ALL { xfree(hmll->entries); xfree(hmll->free_entries); } /// Free linked list and all entries /// /// @param[in] hmll List to free. static inline void hmll_free(HMLList *const hmll) FUNC_ATTR_NONNULL_ALL { HMLL_FORALL(hmll, cur_entry) { shada_free_shada_entry(&cur_entry->data); } hmll_dealloc(hmll); } /// Wrapper for reading from file descriptors /// /// @return true if read was successfull, false otherwise. static ptrdiff_t read_file(ShaDaReadDef *const sd_reader, void *const dest, const size_t size) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { size_t read_bytes = 0; bool did_try_to_free = false; while (read_bytes != size) { const ptrdiff_t cur_read_bytes = read((int)(intptr_t) sd_reader->cookie, ((char *) dest) + read_bytes, size - read_bytes); if (cur_read_bytes > 0) { read_bytes += (size_t) cur_read_bytes; sd_reader->fpos += (uintmax_t) cur_read_bytes; assert(read_bytes <= size); } if (errno) { if (errno == EINTR || errno == EAGAIN) { errno = 0; continue; } else if (errno == ENOMEM && !did_try_to_free) { try_to_free_memory(); did_try_to_free = true; errno = 0; continue; } else { sd_reader->error = strerror(errno); errno = 0; return -1; } } if (cur_read_bytes == 0) { sd_reader->eof = true; break; } } return (ptrdiff_t) read_bytes; } /// Read one character static int read_char(ShaDaReadDef *const sd_reader) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { uint8_t ret; ptrdiff_t read_bytes = sd_reader->read(sd_reader, &ret, 1); if (read_bytes != 1) { return EOF; } return (int) ret; } /// Wrapper for writing to file descriptors /// /// @return true if read was successfull, false otherwise. static ptrdiff_t write_file(ShaDaWriteDef *const sd_writer, const void *const dest, const size_t size) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { size_t written_bytes = 0; while (written_bytes != size) { const ptrdiff_t cur_written_bytes = write((int)(intptr_t) sd_writer->cookie, (char *) dest + written_bytes, size - written_bytes); if (cur_written_bytes > 0) { written_bytes += (size_t) cur_written_bytes; } if (errno) { if (errno == EINTR || errno == EAGAIN) { errno = 0; continue; } else { sd_writer->error = strerror(errno); errno = 0; return -1; } } if (cur_written_bytes == 0) { sd_writer->error = "Zero bytes written."; return -1; } } return (ptrdiff_t) written_bytes; } /// Wrapper for opening file descriptors /// /// All arguments are passed to os_open(). /// /// @return file descriptor or -1 on failure. static int open_file(const char *const fname, const int flags, const int mode) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { bool did_try_to_free = false; int fd; open_file_start: fd = os_open(fname, flags, mode); if (fd < 0) { if (-fd == ENOENT) { return fd; } if (-fd == ENOMEM && !did_try_to_free) { try_to_free_memory(); did_try_to_free = true; goto open_file_start; } if (-fd == EINTR) { goto open_file_start; } if (-fd != EEXIST) { emsg3("System error while opening ShaDa file %s: %s", fname, os_strerror(fd)); } return fd; } return fd; } /// Open ShaDa file for reading /// /// @param[in] fname File name to open. /// @param[out] sd_reader Location where reader structure will be saved. /// /// @return OK in case of success, FAIL otherwise. static int open_shada_file_for_reading(const char *const fname, ShaDaReadDef *sd_reader) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { const intptr_t fd = (intptr_t) open_file(fname, O_RDONLY, 0); if (fd < 0) { return FAIL; } *sd_reader = (ShaDaReadDef) { .read = &read_file, .error = NULL, .eof = false, .fpos = 0, .cookie = (void *) fd, }; convert_setup(&sd_reader->sd_conv, "utf-8", p_enc); return OK; } /// Wrapper for closing file descriptors static void close_file(int fd) { close_file_start: if (close(fd) == -1) { if (errno == EINTR) { errno = 0; goto close_file_start; } else { emsg2("System error while closing ShaDa file: %s", strerror(errno)); errno = 0; } } } /// Check whether buffer is in the given set /// /// @param[in] set Set to check within. /// @param[in] buf Buffer to find. /// /// @return true or false. static inline bool in_bufset(const khash_t(bufset) *const set, const buf_T *buf) FUNC_ATTR_PURE { return kh_get(bufset, set, (uintptr_t) buf) != kh_end(set); } /// Check whether string is in the given set /// /// @param[in] set Set to check within. /// @param[in] buf Buffer to find. /// /// @return true or false. static inline bool in_strset(const khash_t(strset) *const set, char *str) FUNC_ATTR_PURE { return kh_get(strset, set, str) != kh_end(set); } /// Check whether buffer is on removable media /// /// Uses pre-populated set with buffers on removable media named removable_bufs. /// /// @param[in] buf Buffer to check. /// /// @return true or false. #define SHADA_REMOVABLE(buf) in_bufset(removable_bufs, buf) /// Msgpack callback for writing to ShaDaWriteDef* static int msgpack_sd_writer_write(void *data, const char *buf, size_t len) { ShaDaWriteDef *const sd_writer = (ShaDaWriteDef *) data; ptrdiff_t written_bytes = sd_writer->write(sd_writer, buf, len); if (written_bytes == -1) { emsg2("System error while writing ShaDa file: %s", sd_writer->error); return -1; } return 0; } /// Check whether writing to shada file was disabled with -i NONE /// /// @return true if it was disabled, false otherwise. static bool shada_disabled(void) FUNC_ATTR_PURE { return used_shada_file != NULL && STRCMP(used_shada_file, "NONE") == 0; } /// Read ShaDa file /// /// @param[in] file File to read or NULL to use default name. /// @param[in] flags Flags, see kShaDa enum values in shada.h. /// /// @return FAIL if reading failed for some reason and OK otherwise. int shada_read_file(const char *const file, const int flags) FUNC_ATTR_WARN_UNUSED_RESULT { if (shada_disabled()) { return FAIL; } char *const fname = shada_filename(file); ShaDaReadDef sd_reader; const int of_ret = open_shada_file_for_reading(fname, &sd_reader); if (p_verbose > 0) { verbose_enter(); smsg(_("Reading ShaDa file \"%s\"%s%s%s"), fname, (flags & kShaDaWantInfo) ? _(" info") : "", (flags & kShaDaWantMarks) ? _(" marks") : "", (flags & kShaDaGetOldfiles) ? _(" oldfiles") : "", of_ret != OK ? _(" FAILED") : ""); verbose_leave(); } xfree(fname); if (of_ret != OK) { return of_ret; } shada_read(&sd_reader, flags); close_file((int)(intptr_t) sd_reader.cookie); return OK; } /// Wrapper for hist_iter() function which produces ShadaEntry values /// /// @warning Zeroes original items in process. static const void *shada_hist_iter(const void *const iter, const uint8_t history_type, const bool zero, ShadaEntry *const hist) FUNC_ATTR_NONNULL_ARG(4) FUNC_ATTR_WARN_UNUSED_RESULT { histentry_T hist_he; const void *const ret = hist_iter(iter, history_type, zero, &hist_he); if (hist_he.hisstr == NULL) { *hist = (ShadaEntry) { .type = kSDItemMissing }; } else { *hist = (ShadaEntry) { .type = kSDItemHistoryEntry, .timestamp = hist_he.timestamp, .data = { .history_item = { .histtype = history_type, .string = (char *) hist_he.hisstr, .sep = (char) (history_type == HIST_SEARCH ? (char) hist_he.hisstr[STRLEN(hist_he.hisstr) + 1] : 0), .additional_elements = hist_he.additional_elements, .canfree = zero, } } }; if (!zero) { hist->data.history_item.string = xstrdup(hist->data.history_item.string); if (hist->data.history_item.additional_elements != NULL) { Object new_array = copy_object( ARRAY_OBJ(*hist->data.history_item.additional_elements)); hist->data.history_item.additional_elements = xmalloc( sizeof(*hist->data.history_item.additional_elements)); memcpy(hist->data.history_item.additional_elements, &new_array.data.array, sizeof(*hist->data.history_item.additional_elements)); } } } return ret; } /// Insert history entry /// /// Inserts history entry at the end of the ring buffer (may insert earlier /// according to the timestamp). If entry was already in the ring buffer /// existing entry will be removed unless it has greater timestamp. /// /// Before the new entry entries from the current NeoVim history will be /// inserted unless `do_iter` argument is false. /// /// @param[in,out] hms_p Ring buffer and associated structures. /// @param[in] entry Inserted entry. /// @param[in] do_iter Determines whether NeoVim own history should be /// used. static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry, const bool no_iter) { HMLList *const hmll = &hms_p->hmll; HMLL_FORALL(hmll, cur_entry) { if (STRCMP(cur_entry->data.data.history_item.string, entry.data.history_item.string) == 0) { if (entry.timestamp > cur_entry->data.timestamp) { hmll_remove(hmll, cur_entry); } else { return; } } } if (!no_iter) { if (hms_p->iter == NULL) { if (hms_p->last_hist_entry.type != kSDItemMissing && hms_p->last_hist_entry.timestamp < entry.timestamp) { hms_insert(hms_p, hms_p->last_hist_entry, false); hms_p->last_hist_entry.type = kSDItemMissing; } } else { while (hms_p->iter != NULL && hms_p->last_hist_entry.type != kSDItemMissing && hms_p->last_hist_entry.timestamp < entry.timestamp) { hms_insert(hms_p, hms_p->last_hist_entry, false); hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type, hms_p->reading, &(hms_p->last_hist_entry)); } } } HMLListEntry *insert_after; HMLL_ITER_BACK(hmll, insert_after) { if (insert_after->data.timestamp <= entry.timestamp) { break; } } hmll_insert(hmll, insert_after, entry); } /// Initialize the history merger /// /// @param[out] hms_p Structure to be initialized. /// @param[in] history_type History type (one of HIST_\* values). /// @param[in] num_elements Number of elements in the result. /// @param[in] do_merge Prepare structure for merging elements. /// @param[in] reading If true, then merger is reading history for use /// in NeoVim. static inline void hms_init(HistoryMergerState *const hms_p, const uint8_t history_type, const size_t num_elements, const bool do_merge, const bool reading) FUNC_ATTR_NONNULL_ALL { hmll_init(&hms_p->hmll, num_elements); hms_p->do_merge = do_merge; hms_p->reading = reading; hms_p->iter = shada_hist_iter(NULL, history_type, hms_p->reading, &hms_p->last_hist_entry); hms_p->history_type = history_type; } /// Merge in all remaining NeoVim own history entries /// /// @param[in,out] hms_p Merger structure into which history should be /// inserted. static inline void hms_insert_whole_neovim_history( HistoryMergerState *const hms_p) FUNC_ATTR_NONNULL_ALL { if (hms_p->last_hist_entry.type != kSDItemMissing) { hms_insert(hms_p, hms_p->last_hist_entry, false); } while (hms_p->iter != NULL && hms_p->last_hist_entry.type != kSDItemMissing) { hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type, hms_p->reading, &(hms_p->last_hist_entry)); hms_insert(hms_p, hms_p->last_hist_entry, false); } } /// Convert merger structure to NeoVim internal structure for history /// /// @param[in] hms_p Converted merger structure. /// @param[out] hist_array Array with the results. /// @param[out] new_hisidx New last history entry index. /// @param[out] new_hisnum Amount of history items in merger structure. static inline void hms_to_he_array(const HistoryMergerState *const hms_p, histentry_T *const hist_array, int *const new_hisidx, int *const new_hisnum) FUNC_ATTR_NONNULL_ALL { histentry_T *hist = hist_array; HMLL_FORALL(&hms_p->hmll, cur_entry) { hist->timestamp = cur_entry->data.timestamp; hist->hisnum = (int) (hist - hist_array) + 1; hist->hisstr = (char_u *) cur_entry->data.data.history_item.string; hist->additional_elements = cur_entry->data.data.history_item.additional_elements; hist++; } *new_hisnum = (int) (hist - hist_array); *new_hisidx = *new_hisnum - 1; } /// Free history merger structure /// /// @param[in] hms_p Structure to be freed. static inline void hms_dealloc(HistoryMergerState *const hms_p) FUNC_ATTR_NONNULL_ALL { if (hms_p->reading) { // Free only the linked list if reading because all of the allocated memory // was either already freed or saved in internal NeoVim history. hmll_dealloc(&hms_p->hmll); } else { // Free everything because when writing data is only used once to write it. hmll_free(&hms_p->hmll); } } /// Iterate over all history entries in history merger, in order /// /// @param[in] hms_p Merger structure to iterate over. /// @param[out] cur_entry Name of the iterator variable. /// /// @return for cycle header. Use `HMS_ITER(hms_p, cur_entry) {body}`. #define HMS_ITER(hms_p, cur_entry) \ HMLL_FORALL(&((hms_p)->hmll), cur_entry) /// Find buffer for given buffer name (cached) /// /// @param[in,out] fname_bufs Cache containing fname to buffer mapping. /// @param[in] fname File name to find. /// /// @return Pointer to the buffer or NULL. static buf_T *find_buffer(khash_t(fnamebufs) *const fname_bufs, const char *const fname) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { int kh_ret; khint_t k = kh_put(fnamebufs, fname_bufs, fname, &kh_ret); if (!kh_ret) { return kh_val(fname_bufs, k); } kh_key(fname_bufs, k) = xstrdup(fname); FOR_ALL_BUFFERS(buf) { if (buf->b_ffname != NULL) { if (fnamecmp(fname, buf->b_ffname) == 0) { kh_val(fname_bufs, k) = buf; return buf; } } } kh_val(fname_bufs, k) = NULL; return NULL; } /// Compare two marks static inline bool marks_equal(const pos_T a, const pos_T b) { return (a.lnum == b.lnum) && (a.col == b.col); } /// Read data from ShaDa file /// /// @param[in] sd_reader Structure containing file reader definition. /// @param[in] flags What to read. static void shada_read(ShaDaReadDef *const sd_reader, const int flags) FUNC_ATTR_NONNULL_ALL { unsigned srni_flags = 0; const bool force = flags & kShaDaForceit; const bool get_old_files = flags & (kShaDaGetOldfiles | kShaDaForceit); const bool want_marks = flags & kShaDaWantMarks; if (flags & kShaDaWantInfo) { srni_flags |= kSDReadUndisableableData | kSDReadRegisters; if (p_hi) { srni_flags |= kSDReadHistory; } if (find_shada_parameter('!') != NULL) { srni_flags |= kSDReadVariables; } if (find_shada_parameter('%') != NULL && ARGCOUNT == 0) { srni_flags |= kSDReadBufferList; } } if (want_marks) { if (get_shada_parameter('\'') > 0) { srni_flags |= kSDReadLocalMarks | kSDReadChanges; } } if (get_old_files) { srni_flags |= kSDReadLocalMarks; } if (srni_flags == 0) { // Nothing to do. return; } HistoryMergerState hms[HIST_COUNT]; if (srni_flags & kSDReadHistory) { for (uint8_t i = 0; i < HIST_COUNT; i++) { hms_init(&hms[i], i, (size_t) p_hi, true, true); } } ShadaEntry cur_entry; khash_t(bufset) *cl_bufs = NULL; if (srni_flags & kSDReadChanges) { cl_bufs = kh_init(bufset); } khash_t(fnamebufs) *fname_bufs = NULL; if (srni_flags & (kSDReadUndisableableData | kSDReadChanges | kSDReadLocalMarks)) { fname_bufs = kh_init(fnamebufs); } khash_t(strset) *oldfiles_set = NULL; list_T *oldfiles_list = NULL; if (get_old_files) { oldfiles_set = kh_init(strset); oldfiles_list = list_alloc(); set_vim_var_list(VV_OLDFILES, oldfiles_list); } while (shada_read_next_item(sd_reader, &cur_entry, srni_flags) == NOTDONE) { switch (cur_entry.type) { case kSDItemMissing: { assert(false); } case kSDItemUnknown: { break; } case kSDItemHeader: { shada_free_shada_entry(&cur_entry); break; } case kSDItemSearchPattern: { if (!force) { SearchPattern pat; (cur_entry.data.search_pattern.is_substitute_pattern ? &get_substitute_pattern : &get_search_pattern)(&pat); if (pat.pat != NULL && pat.timestamp >= cur_entry.timestamp) { shada_free_shada_entry(&cur_entry); break; } } (cur_entry.data.search_pattern.is_substitute_pattern ? &set_substitute_pattern : &set_search_pattern)((SearchPattern) { .magic = cur_entry.data.search_pattern.magic, .no_scs = !cur_entry.data.search_pattern.smartcase, .off = { .line = cur_entry.data.search_pattern.has_line_offset, .end = cur_entry.data.search_pattern.place_cursor_at_end, .off = cur_entry.data.search_pattern.offset, }, .pat = (char_u *) cur_entry.data.search_pattern.pat, .additional_data = cur_entry.data.search_pattern.additional_data, .timestamp = cur_entry.timestamp, }); if (cur_entry.data.search_pattern.is_last_used) { set_last_used_pattern( cur_entry.data.search_pattern.is_substitute_pattern); } if (!cur_entry.data.search_pattern.is_substitute_pattern) { SET_NO_HLSEARCH(!cur_entry.data.search_pattern.highlighted); } // Do not free shada entry: its allocated memory was saved above. break; } case kSDItemSubString: { if (!force) { SubReplacementString sub; sub_get_replacement(&sub); if (sub.sub != NULL && sub.timestamp >= cur_entry.timestamp) { shada_free_shada_entry(&cur_entry); break; } } sub_set_replacement((SubReplacementString) { .sub = cur_entry.data.sub_string.sub, .timestamp = cur_entry.timestamp, .additional_elements = cur_entry.data.sub_string.additional_elements, }); // Do not free shada entry: its allocated memory was saved above. break; } case kSDItemHistoryEntry: { if (cur_entry.data.history_item.histtype >= HIST_COUNT) { shada_free_shada_entry(&cur_entry); break; } hms_insert(hms + cur_entry.data.history_item.histtype, cur_entry, true); // Do not free shada entry: its allocated memory was saved above. break; } case kSDItemRegister: { if (cur_entry.data.reg.type != MCHAR && cur_entry.data.reg.type != MLINE && cur_entry.data.reg.type != MBLOCK) { shada_free_shada_entry(&cur_entry); break; } register_set(cur_entry.data.reg.name, (yankreg_T) { .y_array = (char_u **) cur_entry.data.reg.contents, .y_size = (linenr_T) cur_entry.data.reg.contents_size, .y_type = cur_entry.data.reg.type, .y_width = (colnr_T) cur_entry.data.reg.width, .timestamp = cur_entry.timestamp, .additional_data = cur_entry.data.reg.additional_data, }); // Do not free shada entry: its allocated memory was saved above. break; } case kSDItemVariable: { typval_T vartv; Error err; if (!object_to_vim(cur_entry.data.global_var.value, &vartv, &err)) { if (err.set) { emsg3("Error while reading ShaDa file: " "failed to read value for variable %s: %s", cur_entry.data.global_var.name, err.msg); } break; } var_set_global(cur_entry.data.global_var.name, vartv); shada_free_shada_entry(&cur_entry); break; } case kSDItemJump: case kSDItemGlobalMark: { buf_T *buf = find_buffer(fname_bufs, cur_entry.data.filemark.fname); if (buf != NULL) { xfree(cur_entry.data.filemark.fname); cur_entry.data.filemark.fname = NULL; } xfmark_T fm = (xfmark_T) { .fname = (char_u *) (buf == NULL ? cur_entry.data.filemark.fname : NULL), .fmark = { .mark = cur_entry.data.filemark.mark, .fnum = (buf == NULL ? 0 : buf->b_fnum), .timestamp = cur_entry.timestamp, .additional_data = cur_entry.data.filemark.additional_data, }, }; if (cur_entry.type == kSDItemGlobalMark) { mark_set_global(cur_entry.data.filemark.name, fm, !force); } else { if (force) { if (curwin->w_jumplistlen == JUMPLISTSIZE) { // Jump list items are ignored in this case. free_xfmark(fm); } else { memmove(&curwin->w_jumplist[1], &curwin->w_jumplist[0], sizeof(curwin->w_jumplist[0]) * (size_t) curwin->w_jumplistlen); curwin->w_jumplistidx++; curwin->w_jumplistlen++; curwin->w_jumplist[0] = fm; } } else { const int jl_len = curwin->w_jumplistlen; int i; for (i = 0; i < jl_len; i++) { const xfmark_T jl_fm = curwin->w_jumplist[i]; if (jl_fm.fmark.timestamp >= cur_entry.timestamp) { if (marks_equal(fm.fmark.mark, jl_fm.fmark.mark) && (buf == NULL ? (jl_fm.fname != NULL && STRCMP(fm.fname, jl_fm.fname) == 0) : fm.fmark.fnum == jl_fm.fmark.fnum)) { i = -1; } break; } } if (i != -1) { if (i < jl_len) { if (jl_len == JUMPLISTSIZE) { free_xfmark(curwin->w_jumplist[0]); memmove(&curwin->w_jumplist[0], &curwin->w_jumplist[1], sizeof(curwin->w_jumplist[0]) * (size_t) i); } else { memmove(&curwin->w_jumplist[i + 1], &curwin->w_jumplist[i], sizeof(curwin->w_jumplist[0]) * (size_t) (jl_len - i)); } } else if (i == jl_len) { if (jl_len == JUMPLISTSIZE) { i = -1; } else if (jl_len > 0) { memmove(&curwin->w_jumplist[1], &curwin->w_jumplist[0], sizeof(curwin->w_jumplist[0]) * (size_t) jl_len); } } } if (i != -1) { curwin->w_jumplist[i] = fm; if (jl_len < JUMPLISTSIZE) { curwin->w_jumplistlen++; } if (curwin->w_jumplistidx > i && curwin->w_jumplistidx + 1 < curwin->w_jumplistlen) { curwin->w_jumplistidx++; } } else { shada_free_shada_entry(&cur_entry); } } } // Do not free shada entry: its allocated memory was saved above. break; } case kSDItemBufferList: { for (size_t i = 0; i < cur_entry.data.buffer_list.size; i++) { char *const sfname = path_shorten_fname_if_possible( cur_entry.data.buffer_list.buffers[i].fname); buf_T *const buf = buflist_new( cur_entry.data.buffer_list.buffers[i].fname, sfname, 0, BLN_LISTED); if (buf != NULL) { RESET_FMARK(&buf->b_last_cursor, cur_entry.data.buffer_list.buffers[i].pos, 0); buflist_setfpos(buf, curwin, buf->b_last_cursor.mark.lnum, buf->b_last_cursor.mark.col, false); } } shada_free_shada_entry(&cur_entry); break; } case kSDItemChange: case kSDItemLocalMark: { if (oldfiles_set != NULL && !in_strset(oldfiles_set, cur_entry.data.filemark.fname)) { char *fname = cur_entry.data.filemark.fname; if (want_marks) { // Do not bother with allocating memory for the string if already // allocated string from cur_entry can be used. It cannot be used if // want_marks is set because this way it may be used for a mark. fname = xstrdup(fname); } int kh_ret; (void) kh_put(strset, oldfiles_set, fname, &kh_ret); list_append_allocated_string(oldfiles_list, fname); if (!want_marks) { // Avoid free because this string was already used. cur_entry.data.filemark.fname = NULL; } } if (!want_marks) { shada_free_shada_entry(&cur_entry); break; } buf_T *buf = find_buffer(fname_bufs, cur_entry.data.filemark.fname); if (buf == NULL) { shada_free_shada_entry(&cur_entry); break; } const fmark_T fm = (fmark_T) { .mark = cur_entry.data.filemark.mark, .fnum = 0, .timestamp = cur_entry.timestamp, .additional_data = cur_entry.data.filemark.additional_data, }; if (cur_entry.type == kSDItemLocalMark) { mark_set_local(cur_entry.data.filemark.name, buf, fm, !force); } else { int kh_ret; (void) kh_put(bufset, cl_bufs, (uintptr_t) buf, &kh_ret); if (force) { if (buf->b_changelistlen == JUMPLISTSIZE) { free_fmark(buf->b_changelist[0]); memmove(buf->b_changelist, buf->b_changelist + 1, sizeof(buf->b_changelist[0]) * (JUMPLISTSIZE - 1)); } else { buf->b_changelistlen++; } buf->b_changelist[buf->b_changelistlen - 1] = fm; } else { const int cl_len = buf->b_changelistlen; int i; for (i = cl_len; i > 0; i--) { const fmark_T cl_fm = buf->b_changelist[i - 1]; if (cl_fm.timestamp <= cur_entry.timestamp) { if (marks_equal(fm.mark, cl_fm.mark)) { i = -1; } break; } } if (i > 0) { if (cl_len == JUMPLISTSIZE) { free_fmark(buf->b_changelist[0]); memmove(&buf->b_changelist[0], &buf->b_changelist[1], sizeof(buf->b_changelist[0]) * (size_t) i); } else { memmove(&buf->b_changelist[i + 1], &buf->b_changelist[i], sizeof(buf->b_changelist[0]) * (size_t) (cl_len - i)); } } else if (i == 0) { if (cl_len == JUMPLISTSIZE) { i = -1; } else if (cl_len > 0) { memmove(&buf->b_changelist[1], &buf->b_changelist[0], sizeof(buf->b_changelist[0]) * (size_t) cl_len); } } if (i != -1) { buf->b_changelist[i] = fm; if (cl_len < JUMPLISTSIZE) { buf->b_changelistlen++; } } else { shada_free_shada_entry(&cur_entry); cur_entry.data.filemark.fname = NULL; } } } // Do not free shada entry: except for fname, its allocated memory (i.e. // additional_data attribute contents if non-NULL) was saved above. xfree(cur_entry.data.filemark.fname); break; } } } // Warning: shada_hist_iter returns ShadaEntry elements which use strings from // original history list. This means that once such entry is removed // from the history NeoVim array will no longer be valid. To reduce // amount of memory allocations ShaDa file reader allocates enough // memory for the history string itself and separator character which // may be assigned right away. if (srni_flags & kSDReadHistory) { for (uint8_t i = 0; i < HIST_COUNT; i++) { hms_insert_whole_neovim_history(&hms[i]); clr_history(i); int *new_hisidx; int *new_hisnum; histentry_T *hist = hist_get_array(i, &new_hisidx, &new_hisnum); if (hist != NULL) { hms_to_he_array(&hms[i], hist, new_hisidx, new_hisnum); } hms_dealloc(&hms[i]); } } if (cl_bufs != NULL) { FOR_ALL_TAB_WINDOWS(tp, wp) { (void) tp; if (in_bufset(cl_bufs, wp->w_buffer)) { wp->w_changelistidx = wp->w_buffer->b_changelistlen; } } kh_destroy(bufset, cl_bufs); } if (fname_bufs != NULL) { const char *key; kh_foreach_key(fname_bufs, key, { xfree((void *) key); }) kh_destroy(fnamebufs, fname_bufs); } if (oldfiles_set != NULL) { kh_destroy(strset, oldfiles_set); } } /// Get the ShaDa file name to use /// /// If "file" is given and not empty, use it (has already been expanded by /// cmdline functions). Otherwise use "-i file_name", value from 'shada' or the /// default, and expand environment variables. /// /// @param[in] file Forced file name or NULL. /// /// @return An allocated string containing shada file name. static char *shada_filename(const char *file) FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT { if (file == NULL || *file == NUL) { if (used_shada_file != NULL) { file = used_shada_file; } else if ((file = find_shada_parameter('n')) == NULL || *file == NUL) { #ifdef SHADA_FILE2 // don't use $HOME when not defined (turned into "c:/"!). if (os_getenv((char_u *)"HOME") == NULL) { // don't use $VIM when not available. expand_env((char_u *)"$VIM", NameBuff, MAXPATHL); if (STRCMP("$VIM", NameBuff) != 0) { // $VIM was expanded file = SHADA_FILE2; } else { file = SHADA_FILE; } } else { #endif file = SHADA_FILE; #ifdef SHADA_FILE2 } #endif // XXX It used to be one level lower, so that whatever is in // `used_shada_file` was expanded. I intentionally moved it here // because various expansions must have already be done by the shell. // If shell is not performing them then they should be done in main.c // where arguments are parsed, *not here*. expand_env((char_u *)file, &(NameBuff[0]), MAXPATHL); file = (const char *) &(NameBuff[0]); } } return xstrdup(file); } #define PACK_STATIC_STR(s) \ do { \ msgpack_pack_str(spacker, sizeof(s) - 1); \ msgpack_pack_str_body(spacker, s, sizeof(s) - 1); \ } while (0) /// Write single ShaDa entry /// /// @param[in] packer Packer used to write entry. /// @param[in] entry Entry written. /// @param[in] max_kbyte Maximum size of an item in KiB. Zero means no /// restrictions. static void shada_pack_entry(msgpack_packer *const packer, const ShadaEntry entry, const size_t max_kbyte) FUNC_ATTR_NONNULL_ALL { if (entry.type == kSDItemMissing) { return; } msgpack_sbuffer sbuf; msgpack_sbuffer_init(&sbuf); msgpack_packer *spacker = msgpack_packer_new(&sbuf, &msgpack_sbuffer_write); switch (entry.type) { case kSDItemMissing: { assert(false); } case kSDItemUnknown: { msgpack_pack_uint64(packer, (uint64_t) entry.data.unknown_item.size); packer->callback(packer->data, entry.data.unknown_item.contents, (unsigned) entry.data.unknown_item.size); break; } case kSDItemHistoryEntry: { const bool is_hist_search = entry.data.history_item.histtype == HIST_SEARCH; const size_t arr_size = 2 + (size_t) is_hist_search + ( entry.data.history_item.additional_elements == NULL ? 0 : entry.data.history_item.additional_elements->size); msgpack_pack_array(spacker, arr_size); msgpack_pack_uint8(spacker, entry.data.history_item.histtype); msgpack_rpc_from_string(cstr_as_string(entry.data.history_item.string), spacker); if (is_hist_search) { msgpack_pack_uint8(spacker, (uint8_t) entry.data.history_item.sep); } for (size_t i = 0; i < arr_size - 2 - (size_t) is_hist_search; i++) { msgpack_rpc_from_object( entry.data.history_item.additional_elements->items[i], spacker); } break; } case kSDItemVariable: { const size_t arr_size = 2 + ( entry.data.global_var.additional_elements == NULL ? 0 : entry.data.global_var.additional_elements->size); msgpack_pack_array(spacker, arr_size); msgpack_rpc_from_string(cstr_as_string(entry.data.global_var.name), spacker); msgpack_rpc_from_object(entry.data.global_var.value, spacker); for (size_t i = 0; i < arr_size - 2; i++) { msgpack_rpc_from_object( entry.data.global_var.additional_elements->items[i], spacker); } break; } case kSDItemSubString: { const size_t arr_size = 1 + ( entry.data.sub_string.additional_elements == NULL ? 0 : entry.data.sub_string.additional_elements->size); msgpack_pack_array(spacker, arr_size); msgpack_rpc_from_string(cstr_as_string(entry.data.sub_string.sub), spacker); for (size_t i = 0; i < arr_size - 1; i++) { msgpack_rpc_from_object( entry.data.sub_string.additional_elements->items[i], spacker); } break; } case kSDItemSearchPattern: { const size_t map_size = (size_t) ( 1 // Search pattern is always present // Following items default to true: + (size_t) !entry.data.search_pattern.magic + (size_t) !entry.data.search_pattern.is_last_used // Following items default to false: + (size_t) entry.data.search_pattern.smartcase + (size_t) entry.data.search_pattern.has_line_offset + (size_t) entry.data.search_pattern.place_cursor_at_end + (size_t) entry.data.search_pattern.is_substitute_pattern + (size_t) entry.data.search_pattern.highlighted // offset defaults to zero: + (size_t) (entry.data.search_pattern.offset != 0) // finally, additional data: + (size_t) (entry.data.search_pattern.additional_data ? entry.data.search_pattern.additional_data->size : 0) ); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR("pat"); msgpack_rpc_from_string(cstr_as_string(entry.data.search_pattern.pat), spacker); #define PACK_BOOL(name, attr, nondef_value) \ do { \ if (entry.data.search_pattern.attr == nondef_value) { \ PACK_STATIC_STR(name); \ msgpack_pack_##nondef_value(spacker); \ } \ } while (0) PACK_BOOL("magic", magic, false); PACK_BOOL("islast", is_last_used, false); PACK_BOOL("smartcase", smartcase, true); PACK_BOOL("lineoff", has_line_offset, true); PACK_BOOL("curatend", place_cursor_at_end, true); PACK_BOOL("sub", is_substitute_pattern, true); PACK_BOOL("hlsearch", highlighted, true); if (entry.data.search_pattern.offset) { PACK_STATIC_STR("off"); msgpack_pack_int64(spacker, entry.data.search_pattern.offset); } #undef PACK_BOOL if (entry.data.search_pattern.additional_data != NULL) { for (size_t i = 0; i < entry.data.search_pattern.additional_data->size; i++) { msgpack_rpc_from_string( entry.data.search_pattern.additional_data->items[i].key, spacker); msgpack_rpc_from_object( entry.data.search_pattern.additional_data->items[i].value, spacker); } } break; } case kSDItemChange: case kSDItemGlobalMark: case kSDItemLocalMark: case kSDItemJump: { const size_t map_size = (size_t) ( 1 // File name // Line: defaults to 1 + (size_t) (entry.data.filemark.mark.lnum != 1) // Column: defaults to zero: + (size_t) (entry.data.filemark.mark.col != 0) // Mark name: defaults to '"' + (size_t) (entry.type != kSDItemJump && entry.type != kSDItemChange && entry.data.filemark.name != '"') // Additional entries, if any: + (size_t) (entry.data.filemark.additional_data == NULL ? 0 : entry.data.filemark.additional_data->size) ); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR("file"); msgpack_rpc_from_string(cstr_as_string(entry.data.filemark.fname), spacker); if (entry.data.filemark.mark.lnum != 1) { PACK_STATIC_STR("line"); msgpack_pack_long(spacker, entry.data.filemark.mark.lnum); } if (entry.data.filemark.mark.col != 0) { PACK_STATIC_STR("col"); msgpack_pack_long(spacker, entry.data.filemark.mark.col); } if (entry.data.filemark.name != '"' && entry.type != kSDItemJump && entry.type != kSDItemChange) { PACK_STATIC_STR("name"); msgpack_pack_uint8(spacker, (uint8_t) entry.data.filemark.name); } if (entry.data.filemark.additional_data != NULL) { for (size_t i = 0; i < entry.data.filemark.additional_data->size; i++) { msgpack_rpc_from_string( entry.data.filemark.additional_data->items[i].key, spacker); msgpack_rpc_from_object( entry.data.filemark.additional_data->items[i].value, spacker); } } break; } case kSDItemRegister: { const size_t map_size = (size_t) ( 2 // Register contents and name // Register type: defaults to MCHAR + (size_t) (entry.data.reg.type != MCHAR) // Register width: defaults to zero + (size_t) (entry.data.reg.width != 0) // Additional entries, if any: + (size_t) (entry.data.reg.additional_data == NULL ? 0 : entry.data.reg.additional_data->size) ); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR("contents"); msgpack_pack_array(spacker, entry.data.reg.contents_size); for (size_t i = 0; i < entry.data.reg.contents_size; i++) { msgpack_rpc_from_string(cstr_as_string(entry.data.reg.contents[i]), spacker); } PACK_STATIC_STR("name"); msgpack_pack_char(spacker, entry.data.reg.name); if (entry.data.reg.type != MCHAR) { PACK_STATIC_STR("type"); msgpack_pack_uint8(spacker, entry.data.reg.type); } if (entry.data.reg.width != 0) { PACK_STATIC_STR("width"); msgpack_pack_uint64(spacker, (uint64_t) entry.data.reg.width); } if (entry.data.reg.additional_data != NULL) { for (size_t i = 0; i < entry.data.reg.additional_data->size; i++) { msgpack_rpc_from_string(entry.data.reg.additional_data->items[i].key, spacker); msgpack_rpc_from_object( entry.data.reg.additional_data->items[i].value, spacker); } } break; } case kSDItemBufferList: { msgpack_pack_array(spacker, entry.data.buffer_list.size); for (size_t i = 0; i < entry.data.buffer_list.size; i++) { const size_t map_size = (size_t) ( 1 // Buffer name // Line number: defaults to 1 + (size_t) (entry.data.buffer_list.buffers[i].pos.lnum != 1) // Column number: defaults to 0 + (size_t) (entry.data.buffer_list.buffers[i].pos.col != 0) // Additional entries, if any: + (size_t) ( entry.data.buffer_list.buffers[i].additional_data == NULL ? 0 : entry.data.buffer_list.buffers[i].additional_data->size) ); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR("file"); msgpack_rpc_from_string( cstr_as_string(entry.data.buffer_list.buffers[i].fname), spacker); if (entry.data.buffer_list.buffers[i].pos.lnum != 1) { PACK_STATIC_STR("line"); msgpack_pack_uint64( spacker, (uint64_t) entry.data.buffer_list.buffers[i].pos.lnum); } if (entry.data.buffer_list.buffers[i].pos.col != 0) { PACK_STATIC_STR("col"); msgpack_pack_uint64( spacker, (uint64_t) entry.data.buffer_list.buffers[i].pos.col); } if (entry.data.buffer_list.buffers[i].additional_data != NULL) { for (size_t j = 0; j < entry.data.buffer_list.buffers[i].additional_data->size; j++) { msgpack_rpc_from_string( entry.data.buffer_list.buffers[i].additional_data->items[j].key, spacker); msgpack_rpc_from_object( entry.data.buffer_list.buffers[i].additional_data->items[j].value, spacker); } } } break; } case kSDItemHeader: { msgpack_rpc_from_dictionary(entry.data.header, spacker); break; } } if (!max_kbyte || sbuf.size <= max_kbyte * 1024) { if (entry.type == kSDItemUnknown) { msgpack_pack_uint64(packer, (uint64_t) entry.data.unknown_item.type); } else { msgpack_pack_uint64(packer, (uint64_t) entry.type); } msgpack_pack_uint64(packer, (uint64_t) entry.timestamp); if (sbuf.size > 0) { msgpack_pack_uint64(packer, (uint64_t) sbuf.size); packer->callback(packer->data, sbuf.data, (unsigned) sbuf.size); } } msgpack_packer_free(spacker); msgpack_sbuffer_destroy(&sbuf); } /// Write ShaDa file /// /// @param[in] sd_writer Structure containing file writer definition. /// @param[in] sd_reader Structure containing file reader definition. If it is /// not NULL then contents of this file will be merged /// with current NeoVim runtime. static void shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef *const sd_reader) FUNC_ATTR_NONNULL_ARG(1) { // TODO(ZyX-I): Write only one search pattern, substitute search pattern and // substitute replacement string, which has the greatest timestamp. Make sure // that this also applies if ShaDa file contains more then one replacement // string. khash_t(bufset) *const removable_bufs = kh_init(bufset); int max_kbyte_i = get_shada_parameter('s'); if (max_kbyte_i < 0) { max_kbyte_i = 10; } if (max_kbyte_i == 0) { return; } const size_t max_kbyte = (size_t) max_kbyte_i; msgpack_packer *packer = msgpack_packer_new(sd_writer, &msgpack_sd_writer_write); FOR_ALL_BUFFERS(buf) { if (buf->b_ffname != NULL && shada_removable((char *) buf->b_ffname)) { int kh_ret; (void) kh_put(bufset, removable_bufs, (uintptr_t) buf, &kh_ret); } } // TODO(ZyX-I): Iterate over sd_reader, keeping “replaced” values in a set. // First write values that do not require merging // 1. Header shada_pack_entry(packer, (ShadaEntry) { .type = kSDItemHeader, .timestamp = os_time(), .data = { .header = { .size = 4, .capacity = 4, .items = ((KeyValuePair []) { { STATIC_CSTR_AS_STRING("version"), STRING_OBJ(cstr_as_string(longVersion)) }, { STATIC_CSTR_AS_STRING("max_kbyte"), INTEGER_OBJ((Integer) max_kbyte) }, { STATIC_CSTR_AS_STRING("pid"), INTEGER_OBJ((Integer) os_get_pid()) }, { STATIC_CSTR_AS_STRING("encoding"), STRING_OBJ(cstr_as_string((char *) p_enc)) }, }), } } }, 0); // 2. Buffer list if (find_shada_parameter('%') != NULL) { size_t buf_count = 0; FOR_ALL_BUFFERS(buf) { if (buf->b_ffname != NULL && !SHADA_REMOVABLE(buf)) { buf_count++; } } ShadaEntry buflist_entry = (ShadaEntry) { .type = kSDItemBufferList, .timestamp = os_time(), .data = { .buffer_list = { .size = buf_count, .buffers = xmalloc(buf_count * sizeof(*buflist_entry.data.buffer_list.buffers)), }, }, }; size_t i = 0; FOR_ALL_BUFFERS(buf) { if (buf->b_ffname == NULL || SHADA_REMOVABLE(buf)) { continue; } buflist_entry.data.buffer_list.buffers[i] = (struct buffer_list_buffer) { .pos = buf->b_last_cursor.mark, .fname = (char *) buf->b_ffname, .additional_data = buf->additional_data, }; i++; } shada_pack_entry(packer, buflist_entry, 0); xfree(buflist_entry.data.buffer_list.buffers); } // 3. Jump list const void *jump_iter = NULL; do { xfmark_T fm; cleanup_jumplist(); jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm); const buf_T *const buf = (fm.fmark.fnum == 0 ? NULL : buflist_findnr(fm.fmark.fnum)); if (buf != NULL ? SHADA_REMOVABLE(buf) : fm.fmark.fnum != 0) { continue; } const char *const fname = (char *) (fm.fmark.fnum == 0 ? (fm.fname == NULL ? NULL : fm.fname) : buf->b_ffname); shada_pack_entry(packer, (ShadaEntry) { .type = kSDItemJump, .timestamp = fm.fmark.timestamp, .data = { .filemark = { .name = NUL, .mark = fm.fmark.mark, .fname = (char *) fname, .additional_data = fm.fmark.additional_data, } } }, max_kbyte); } while (jump_iter != NULL); // FIXME No merging currently #define RUN_WITH_CONVERTED_STRING(cstr, code) \ do { \ bool did_convert = false; \ if (sd_writer->sd_conv.vc_type != CONV_NONE && has_non_ascii((cstr))) { \ char *const converted_string = string_convert(&sd_writer->sd_conv, \ (cstr), NULL); \ if (converted_string != NULL) { \ (cstr) = converted_string; \ did_convert = true; \ } \ } \ code \ if (did_convert) { \ xfree((cstr)); \ } \ } while (0) // 4. History HistoryMergerState hms[HIST_COUNT]; for (uint8_t i = 0; i < HIST_COUNT; i++) { long num_saved = get_shada_parameter(hist_type2char(i)); if (num_saved == -1) { num_saved = p_hi; } if (num_saved > 0) { hms_init(&hms[i], i, (size_t) num_saved, false, false); hms_insert_whole_neovim_history(&hms[i]); HMS_ITER(&hms[i], cur_entry) { RUN_WITH_CONVERTED_STRING(cur_entry->data.data.history_item.string, { shada_pack_entry(packer, cur_entry->data, max_kbyte); }); } hms_dealloc(&hms[i]); } } // 5. Search patterns { SearchPattern pat; get_search_pattern(&pat); ShadaEntry sp_entry = (ShadaEntry) { .type = kSDItemSearchPattern, .timestamp = pat.timestamp, .data = { .search_pattern = { .magic = pat.magic, .smartcase = !pat.no_scs, .has_line_offset = pat.off.line, .place_cursor_at_end = pat.off.end, .offset = pat.off.off, .is_last_used = search_was_last_used(), .is_substitute_pattern = false, .highlighted = (!no_hlsearch && find_shada_parameter('h') != NULL), .pat = (char *) pat.pat, .additional_data = pat.additional_data, } } }; RUN_WITH_CONVERTED_STRING(sp_entry.data.search_pattern.pat, { shada_pack_entry(packer, sp_entry, max_kbyte); }); } { SearchPattern pat; get_substitute_pattern(&pat); ShadaEntry sp_entry = (ShadaEntry) { .type = kSDItemSearchPattern, .timestamp = pat.timestamp, .data = { .search_pattern = { .magic = pat.magic, .smartcase = !pat.no_scs, .has_line_offset = false, .place_cursor_at_end = false, .offset = 0, .is_last_used = !search_was_last_used(), .is_substitute_pattern = true, .highlighted = false, .pat = (char *) pat.pat, .additional_data = pat.additional_data, } } }; RUN_WITH_CONVERTED_STRING(sp_entry.data.search_pattern.pat, { shada_pack_entry(packer, sp_entry, max_kbyte); }); } // 6. Substitute string { SubReplacementString sub; sub_get_replacement(&sub); wms->replacement = (PossiblyFreedShadaEntry) { .can_free_entry = false, .data = { .type = kSDItemSubString, .timestamp = sub.timestamp, .data = { .sub_string = { .sub = (char *) sub.sub, .additional_elements = sub.additional_elements, } } } }; RUN_WITH_CONVERTED_STRING(sub_entry.data.sub_string.sub, { shada_pack_entry(packer, sub_entry, max_kbyte); }); } // 7. Global marks if (get_shada_parameter('f') != 0) { ShadaEntry *const global_marks = list_global_marks(removable_bufs); for (ShadaEntry *mark = global_marks; mark->type != kSDItemMissing; mark++) { shada_pack_entry(packer, *mark, max_kbyte); } xfree(global_marks); } // 8. Buffer marks and buffer change list FOR_ALL_BUFFERS(buf) { if (buf->b_ffname == NULL || SHADA_REMOVABLE(buf)) { continue; } ShadaEntry *const buffer_marks = list_buffer_marks(buf); for (ShadaEntry *mark = buffer_marks; mark->type != kSDItemMissing; mark++) { shada_pack_entry(packer, *mark, max_kbyte); } xfree(buffer_marks); for (int i = 0; i < buf->b_changelistlen; i++) { const fmark_T fm = buf->b_changelist[i]; shada_pack_entry(packer, (ShadaEntry) { .type = kSDItemChange, .timestamp = fm.timestamp, .data = { .filemark = { .mark = fm.mark, .fname = (char *) buf->b_ffname, .additional_data = fm.additional_data, } } }, max_kbyte); } } // FIXME: Copy previous marks, up to num_marked_files // size_t num_marked_files = get_shada_parameter('\''); // 9. Registers int max_num_lines_i = get_shada_parameter('<'); if (max_num_lines_i < 0) { max_num_lines_i = get_shada_parameter('"'); } if (max_num_lines_i != 0) { const size_t max_num_lines = (max_num_lines_i < 0 ? 0 : (size_t) max_num_lines_i); ShadaEntry *const registers = list_registers(max_num_lines); for (ShadaEntry *reg = registers; reg->type != kSDItemMissing; reg++) { bool did_convert = false; if (sd_writer->sd_conv.vc_type != CONV_NONE) { did_convert = true; reg->data.reg.contents = xmemdup(reg->data.reg.contents, (reg->data.reg.contents_size * sizeof(reg->data.reg.contents))); for (size_t i = 0; i < reg->data.reg.contents_size; i++) { reg->data.reg.contents[i] = get_converted_string( &sd_writer->sd_conv, reg->data.reg.contents[i], strlen(reg->data.reg.contents[i])); } } shada_pack_entry(packer, *reg, max_kbyte); if (did_convert) { for (size_t i = 0; i < reg->data.reg.contents_size; i++) { xfree(reg->data.reg.contents[i]); } xfree(reg->data.reg.contents); } } xfree(registers); } // 10. Variables if (find_shada_parameter('!') != NULL) { const void *var_iter = NULL; const Timestamp cur_timestamp = os_time(); do { typval_T vartv; const char *name; var_iter = var_shada_iter(var_iter, &name, &vartv); if (var_iter == NULL && vartv.v_type == VAR_UNKNOWN) { break; } Object obj = vim_to_object(&vartv); if (sd_writer->sd_conv.vc_type != CONV_NONE) { convert_object(&sd_writer->sd_conv, &obj); } shada_pack_entry(packer, (ShadaEntry) { .type = kSDItemVariable, .timestamp = cur_timestamp, .data = { .global_var = { .name = (char *) name, .value = obj, .additional_elements = NULL, } } }, max_kbyte); api_free_object(obj); clear_tv(&vartv); } while (var_iter != NULL); } #undef RUN_WITH_CONVERTED_STRING kh_destroy(bufset, removable_bufs); msgpack_packer_free(packer); } #undef PACK_STATIC_STR /// Write ShaDa file to a given location /// /// @param[in] fname File to write to. If it is NULL or empty then default /// location is used. /// @param[in] nomerge If true then old file is ignored. /// /// @return OK if writing was successfull, FAIL otherwise. int shada_write_file(const char *const file, bool nomerge) { char *const fname = shada_filename(file); char *tempname = NULL; ShaDaWriteDef sd_writer = (ShaDaWriteDef) { .write = &write_file, .error = NULL, }; ShaDaReadDef sd_reader; intptr_t fd; if (!nomerge) { // TODO(ZyX-I): Fail on read error. if (open_shada_file_for_reading(fname, &sd_reader) != OK) { nomerge = true; goto shada_write_file_nomerge; } #ifdef UNIX // For Unix we check the owner of the file. It's not very nice to // overwrite a user’s viminfo file after a "su root", with a // viminfo file that the user can't read. FileInfo old_info; if (os_fileinfo((char *)fname, &old_info) && getuid() != ROOT_UID && !(old_info.stat.st_uid == getuid() ? (old_info.stat.st_mode & 0200) : (old_info.stat.st_gid == getgid() ? (old_info.stat.st_mode & 0020) : (old_info.stat.st_mode & 0002)))) { EMSG2(_("E137: ShaDa file is not writable: %s"), fname); close_file((int)(intptr_t) sd_reader.cookie); xfree(fname); return FAIL; } #endif tempname = modname(fname, ".tmp.a", false); if (tempname == NULL) { nomerge = true; goto shada_write_file_nomerge; } // Save permissions from the original file, with modifications: int perm = (int) os_getperm(fname); perm = (perm >= 0) ? ((perm & 0777) | 0600) : 0600; // ^3 ^1 ^2 ^2,3 // 1: Strip SUID bit if any. // 2: Make sure that user can always read and write the result. // 3: If somebody happened to delete the file after it was opened for // reading use u=rw permissions. shada_write_file_open: fd = (intptr_t) open_file(tempname, O_CREAT|O_WRONLY|O_NOFOLLOW|O_EXCL, perm); if (fd < 0) { if (-fd == EEXIST #ifdef ELOOP || -fd == ELOOP #endif ) { // File already exists, try another name char *const wp = tempname + strlen(tempname) - 1; if (*wp == 'z') { // Tried names from .tmp.a to .tmp.z, all failed. Something must be // wrong then. EMSG2(_("E138: All %s.tmp.X files exist, cannot write ShaDa file!"), fname); xfree(fname); xfree(tempname); return FAIL; } else { (*wp)++; goto shada_write_file_open; } } } } if (nomerge) { shada_write_file_nomerge: {} char *const tail = path_tail_with_sep(fname); if (tail != fname) { const char tail_save = *tail; *tail = NUL; if (!os_isdir(fname)) { int ret; char *failed_dir; if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir)) != 0) { EMSG3("Failed to create directory %s for writing ShaDa file: %s", failed_dir, os_strerror(ret)); xfree(fname); xfree(failed_dir); return FAIL; } } *tail = tail_save; } fd = (intptr_t) open_file(fname, O_CREAT|O_WRONLY|O_TRUNC, 0600); } if (p_verbose > 0) { verbose_enter(); smsg(_("Writing ShaDa file \"%s\""), fname); verbose_leave(); } if (fd < 0) { xfree(fname); xfree(tempname); return FAIL; } sd_writer.cookie = (void *) fd; convert_setup(&sd_writer.sd_conv, p_enc, "utf-8"); shada_write(&sd_writer, NULL); close_file((int)(intptr_t) sd_writer.cookie); if (!nomerge) { close_file((int)(intptr_t) sd_reader.cookie); if (vim_rename(tempname, fname) == -1) { EMSG3(_("E886: Can't rename ShaDa file from %s to %s!"), tempname, fname); } else { os_remove(tempname); } xfree(tempname); } xfree(fname); return OK; } /// Read marks information from ShaDa file /// /// @return OK in case of success, FAIL otherwise. int shada_read_marks(void) { return shada_read_file(NULL, kShaDaWantMarks); } /// Read all information from ShaDa file /// /// @param[in] fname File to write to. If it is NULL or empty then default /// /// @return OK in case of success, FAIL otherwise. int shada_read_everything(const char *const fname, const bool forceit) { return shada_read_file(fname, kShaDaWantInfo|kShaDaWantMarks|kShaDaGetOldfiles |(forceit?kShaDaForceit:0)); } static void shada_free_shada_entry(ShadaEntry *const entry) { if (entry == NULL) { return; } switch (entry->type) { case kSDItemMissing: { break; } case kSDItemUnknown: { xfree(entry->data.unknown_item.contents); break; } case kSDItemHeader: { api_free_dictionary(entry->data.header); break; } case kSDItemChange: case kSDItemJump: case kSDItemGlobalMark: case kSDItemLocalMark: { if (entry->data.filemark.additional_data != NULL) { api_free_dictionary(*entry->data.filemark.additional_data); xfree(entry->data.filemark.additional_data); } xfree(entry->data.filemark.fname); break; } case kSDItemSearchPattern: { if (entry->data.search_pattern.additional_data != NULL) { api_free_dictionary(*entry->data.search_pattern.additional_data); xfree(entry->data.search_pattern.additional_data); } xfree(entry->data.search_pattern.pat); break; } case kSDItemRegister: { if (entry->data.reg.additional_data != NULL) { api_free_dictionary(*entry->data.reg.additional_data); xfree(entry->data.reg.additional_data); } for (size_t i = 0; i < entry->data.reg.contents_size; i++) { xfree(entry->data.reg.contents[i]); } xfree(entry->data.reg.contents); break; } case kSDItemHistoryEntry: { if (entry->data.history_item.canfree) { if (entry->data.history_item.additional_elements != NULL) { api_free_array(*entry->data.history_item.additional_elements); xfree(entry->data.history_item.additional_elements); } xfree(entry->data.history_item.string); } break; } case kSDItemVariable: { if (entry->data.global_var.additional_elements != NULL) { api_free_array(*entry->data.global_var.additional_elements); xfree(entry->data.global_var.additional_elements); } xfree(entry->data.global_var.name); api_free_object(entry->data.global_var.value); break; } case kSDItemSubString: { if (entry->data.sub_string.additional_elements != NULL) { api_free_array(*entry->data.sub_string.additional_elements); xfree(entry->data.sub_string.additional_elements); } xfree(entry->data.sub_string.sub); break; } case kSDItemBufferList: { for (size_t i = 0; i < entry->data.buffer_list.size; i++) { xfree(entry->data.buffer_list.buffers[i].fname); if (entry->data.buffer_list.buffers[i].additional_data != NULL) { api_free_dictionary( *entry->data.buffer_list.buffers[i].additional_data); xfree(entry->data.buffer_list.buffers[i].additional_data); } } xfree(entry->data.buffer_list.buffers); break; } } } #ifndef __GLIBC__ static inline uint64_t be64toh(uint64_t big_endian_64_bits) { #ifdef SHADA_BIG_ENDIAN return big_endian_64_bits; #else uint8_t *buf = &big_endian_64_bits; uint64_t ret = 0; for (size_t i = 8; i; i--) { ret |= ((uint64_t) buf[i - 1]) << ((8 - i) * 8); } return ret; #endif } #endif /// Read given number of bytes into given buffer, display error if needed /// /// @param[in] sd_reader Structure containing file reader definition. /// @param[out] buffer Where to save the results. May be NULL. /// @param[in] length How many bytes should be read. /// /// @return FAIL if reading was not successfull, OK otherwise. static int fread_len(ShaDaReadDef *const sd_reader, char *const buffer, const size_t length) FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT { ptrdiff_t read_bytes = 0; if (buffer == NULL) { do { ptrdiff_t new_read_bytes = sd_reader->read( sd_reader, IObuff, (size_t) (length - (size_t) read_bytes > IOSIZE ? IOSIZE : length - (size_t) read_bytes)); if (new_read_bytes == -1) { break; } read_bytes += new_read_bytes; } while ((size_t) read_bytes < length && !sd_reader->eof); } else { read_bytes = sd_reader->read(sd_reader, buffer, length); } if (sd_reader->error != NULL) { emsg2("System error while reading ShaDa file: %s", sd_reader->error); return FAIL; } else if (sd_reader->eof) { emsgu("Error while reading ShaDa file: " "last entry specified that it occupies %" PRIu64 " bytes, " "but file ended earlier", (uint64_t) length); return FAIL; } assert(read_bytes >= 0 && (size_t) read_bytes == length); return OK; } /// Read next unsigned integer from file /// /// Errors out if the result is not an unsigned integer. /// /// Unlike msgpack own function this one works with `FILE *` and reads *exactly* /// as much bytes as needed, making it possible to avoid both maintaining own /// buffer and calling `fseek`. /// /// One byte from file stream is always consumed, even if it is not correct. /// /// @param[in] sd_reader Structure containing file reader definition. /// @param[out] result Location where result is saved. /// /// @return OK if read was successfull, FAIL if it was not. static int msgpack_read_uint64(ShaDaReadDef *const sd_reader, const int first_char, uint64_t *const result) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { const uintmax_t fpos = sd_reader->fpos - 1; if (first_char == EOF) { if (sd_reader->error) { emsg2("System error while reading integer from ShaDa file: %s", sd_reader->error); } else if (sd_reader->eof) { emsgu("Error while reading ShaDa file: " "expected positive integer at position %" PRIu64 ", but got nothing", (uint64_t) fpos); } return FAIL; } if (~first_char & 0x80) { // Positive fixnum *result = (uint64_t) ((uint8_t) first_char); } else { size_t length = 0; switch (first_char) { case 0xCC: { // uint8 length = 1; break; } case 0xCD: { // uint16 length = 2; break; } case 0xCE: { // uint32 length = 4; break; } case 0xCF: { // uint64 length = 8; break; } default: { emsgu("Error while reading ShaDa file: " "expected positive integer at position %" PRIu64, (uint64_t) fpos); return FAIL; } } uint8_t buf[sizeof(uint64_t)] = {0, 0, 0, 0, 0, 0, 0, 0}; if (fread_len(sd_reader, (char *) &(buf[sizeof(uint64_t)-length]), length) != OK) { return FAIL; } *result = be64toh(*((uint64_t *) &(buf[0]))); } return OK; } /// Convert all strings in one Object instance /// /// @param[in] sd_conv Conversion definition. /// @param[in,out] obj Object to convert. static void convert_object(const vimconv_T *const sd_conv, Object *const obj) FUNC_ATTR_NONNULL_ALL { kvec_t(Object *) toconv; kv_init(toconv); kv_push(Object *, toconv, obj); while (kv_size(toconv)) { Object *cur_obj = kv_pop(toconv); #define CONVERT_STRING(str) \ do { \ if (!has_non_ascii((str).data)) { \ break; \ } \ size_t len = (str).size; \ char *const converted_string = string_convert(sd_conv, (str).data, \ &len); \ if (converted_string != NULL) { \ xfree((str).data); \ (str).data = converted_string; \ (str).size = len; \ } \ } while (0) switch (cur_obj->type) { case kObjectTypeNil: case kObjectTypeInteger: case kObjectTypeBoolean: case kObjectTypeFloat: { break; } case kObjectTypeString: { CONVERT_STRING(cur_obj->data.string); break; } case kObjectTypeArray: { for (size_t i = 0; i < cur_obj->data.array.size; i++) { Object *element = &cur_obj->data.array.items[i]; if (element->type == kObjectTypeDictionary || element->type == kObjectTypeArray) { kv_push(Object *, toconv, element); } else if (element->type == kObjectTypeString) { CONVERT_STRING(element->data.string); } } break; } case kObjectTypeDictionary: { for (size_t i = 0; i < cur_obj->data.dictionary.size; i++) { CONVERT_STRING(cur_obj->data.dictionary.items[i].key); Object *value = &cur_obj->data.dictionary.items[i].value; if (value->type == kObjectTypeDictionary || value->type == kObjectTypeArray) { kv_push(Object *, toconv, value); } else if (value->type == kObjectTypeString) { CONVERT_STRING(value->data.string); } } break; } default: { assert(false); } } #undef CONVERT_STRING } kv_destroy(toconv); } /// Convert or copy and return a string /// /// @param[in] sd_conv Conversion definition. /// @param[in] str String to convert. /// @param[in] len String length. /// /// @return [allocated] converted string or copy of the original string. static inline char *get_converted_string(const vimconv_T *const sd_conv, const char *const str, const size_t len) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT { if (!has_non_ascii_len(str, len)) { return xmemdupz(str, len); } size_t new_len = len; char *const new_str = string_convert(sd_conv, str, &new_len); if (new_str == NULL) { return xmemdupz(str, len); } return new_str; } /// Iterate over shada file contents /// /// @param[in] sd_reader Structure containing file reader definition. /// @param[out] entry Address where next entry contents will be saved. /// @param[in] flags Flags, determining whether and which items should be /// skipped (see SRNIFlags enum). /// /// @return NOTDONE if entry was read correctly, FAIL if there were errors and /// OK at EOF. static int shada_read_next_item(ShaDaReadDef *const sd_reader, ShadaEntry *const entry, const unsigned flags) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { shada_read_next_item_start: entry->type = kSDItemMissing; if (sd_reader->eof) { return OK; } // First: manually unpack type, timestamp and length. // This is needed to avoid both seeking and having to maintain a buffer. uint64_t type_u64 = (uint64_t) kSDItemMissing; uint64_t timestamp_u64; uint64_t length_u64; const uintmax_t initial_fpos = sd_reader->fpos; const int first_char = read_char(sd_reader); if (first_char == EOF && sd_reader->eof) { return OK; } if (msgpack_read_uint64(sd_reader, first_char, &type_u64) != OK || (msgpack_read_uint64(sd_reader, read_char(sd_reader), ×tamp_u64) != OK) || (msgpack_read_uint64(sd_reader, read_char(sd_reader), &length_u64) != OK)) { return FAIL; } const size_t length = (size_t) length_u64; entry->timestamp = (Timestamp) timestamp_u64; if ((type_u64 > SHADA_LAST_ENTRY ? !(flags & kSDReadUnknown) : !((unsigned) (1 << type_u64) & flags))) { if (fread_len(sd_reader, NULL, length) != OK) { return FAIL; } goto shada_read_next_item_start; } if (type_u64 > SHADA_LAST_ENTRY) { entry->type = kSDItemUnknown; entry->data.unknown_item.size = length; entry->data.unknown_item.type = type_u64; entry->data.unknown_item.contents = xmalloc(length); return fread_len(sd_reader, entry->data.unknown_item.contents, length); } msgpack_unpacker *const unpacker = msgpack_unpacker_new(length); if (unpacker == NULL || !msgpack_unpacker_reserve_buffer(unpacker, length)) { EMSG(e_outofmem); goto shada_read_next_item_error; } if (fread_len(sd_reader, msgpack_unpacker_buffer(unpacker), length) != OK) { msgpack_unpacker_free(unpacker); return FAIL; } msgpack_unpacker_buffer_consumed(unpacker, length); msgpack_unpacked unpacked; msgpack_unpacked_init(&unpacked); const msgpack_unpack_return result = msgpack_unpacker_next(unpacker, &unpacked); if (result != MSGPACK_UNPACK_SUCCESS) { if (result == MSGPACK_UNPACK_NOMEM_ERROR) { EMSG(e_outofmem); goto shada_read_next_item_error; } if (result == MSGPACK_UNPACK_PARSE_ERROR) { EMSG("Failed to parse ShaDa file"); goto shada_read_next_item_error; } } #define CHECK_KEY(key, expected) \ (key.via.str.size == sizeof(expected) - 1 \ && STRNCMP(key.via.str.ptr, expected, sizeof(expected) - 1) == 0) #define ID(s) s #define BINDUP(b) xmemdupz(b.ptr, b.size) #define TOINT(s) ((int) (s)) #define TOLONG(s) ((long) (s)) #define TOCHAR(s) ((char) (s)) #define TOU8(s) ((uint8_t) (s)) #define TOSIZE(s) ((size_t) (s)) #define CHECKED_ENTRY(condition, error_desc, entry_name, obj, tgt, attr, \ proc) \ do { \ if (!(condition)) { \ emsgu("Error while reading ShaDa file: " \ entry_name " entry at position %" PRIu64 " " \ error_desc, \ (uint64_t) initial_fpos); \ ga_clear(&ad_ga); \ goto shada_read_next_item_error; \ } \ tgt = proc(obj.via.attr); \ } while (0) #define CHECK_KEY_IS_STR(entry_name) \ do { \ if (unpacked.data.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR) { \ emsgu("Error while reading ShaDa file: " \ entry_name " entry at position %" PRIu64 " " \ "has key which is not a string", \ (uint64_t) initial_fpos); \ emsgu("It is %" PRIu64 " instead", \ (uint64_t) unpacked.data.via.map.ptr[i].key.type); \ ga_clear(&ad_ga); \ goto shada_read_next_item_error; \ } \ } while (0) #define CHECKED_KEY(entry_name, name, error_desc, tgt, condition, attr, proc) \ if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, name)) { \ CHECKED_ENTRY( \ condition, "has " name " key value " error_desc, \ entry_name, unpacked.data.via.map.ptr[i].val, \ tgt, attr, proc); \ } #define TYPED_KEY(entry_name, name, type_name, tgt, objtype, attr, proc) \ CHECKED_KEY( \ entry_name, name, " which is not " type_name, tgt, \ unpacked.data.via.map.ptr[i].val.type == MSGPACK_OBJECT_##objtype, \ attr, proc) #define BOOLEAN_KEY(entry_name, name, tgt) \ TYPED_KEY(entry_name, name, "a boolean", tgt, BOOLEAN, boolean, ID) #define STRING_KEY(entry_name, name, tgt) \ TYPED_KEY(entry_name, name, "a binary", tgt, BIN, bin, BINDUP) #define CONVERTED_STRING_KEY(entry_name, name, tgt) \ TYPED_KEY(entry_name, name, "a binary", tgt, BIN, bin, BIN_CONVERTED) #define INT_KEY(entry_name, name, tgt, proc) \ CHECKED_KEY( \ entry_name, name, " which is not an integer", tgt, \ (unpacked.data.via.map.ptr[i].val.type \ == MSGPACK_OBJECT_POSITIVE_INTEGER \ || unpacked.data.via.map.ptr[i].val.type \ == MSGPACK_OBJECT_NEGATIVE_INTEGER), \ i64, proc) #define INTEGER_KEY(entry_name, name, tgt) \ INT_KEY(entry_name, name, tgt, TOINT) #define LONG_KEY(entry_name, name, tgt) \ INT_KEY(entry_name, name, tgt, TOLONG) #define ADDITIONAL_KEY \ { \ ga_grow(&ad_ga, 1); \ memcpy(((char *)ad_ga.ga_data) + ((size_t) ad_ga.ga_len \ * sizeof(*unpacked.data.via.map.ptr)),\ unpacked.data.via.map.ptr + i, \ sizeof(*unpacked.data.via.map.ptr)); \ ad_ga.ga_len++; \ } #define CONVERTED(str, len) \ (sd_reader->sd_conv.vc_type != CONV_NONE \ ? get_converted_string(&sd_reader->sd_conv, (str), (len)) \ : xmemdupz((str), (len))) #define BIN_CONVERTED(b) CONVERTED(b.ptr, b.size) switch ((ShadaEntryType) type_u64) { case kSDItemHeader: { if (!msgpack_rpc_to_dictionary(&(unpacked.data), &(entry->data.header))) { emsgu("Error while reading ShaDa file: " "header entry at position %" PRIu64 " is not a dictionary", (uint64_t) initial_fpos); goto shada_read_next_item_error; } break; } case kSDItemSearchPattern: { if (unpacked.data.type != MSGPACK_OBJECT_MAP) { emsgu("Error while reading ShaDa file: " "search pattern entry at position %" PRIu64 " " "is not a dictionary", (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.search_pattern = (struct search_pattern) { .magic = true, .smartcase = false, .has_line_offset = false, .place_cursor_at_end = false, .offset = 0, .is_last_used = true, .is_substitute_pattern = false, .highlighted = false, .pat = NULL, .additional_data = NULL, }; garray_T ad_ga; ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1); for (size_t i = 0; i < unpacked.data.via.map.size; i++) { CHECK_KEY_IS_STR("search pattern"); BOOLEAN_KEY("search pattern", "magic", entry->data.search_pattern.magic) else BOOLEAN_KEY("search pattern", "smartcase", entry->data.search_pattern.smartcase) else BOOLEAN_KEY("search pattern", "lineoff", entry->data.search_pattern.has_line_offset) else BOOLEAN_KEY("search pattern", "curatend", entry->data.search_pattern.place_cursor_at_end) else BOOLEAN_KEY("search pattern", "islast", entry->data.search_pattern.is_last_used) else BOOLEAN_KEY("search pattern", "sub", entry->data.search_pattern.is_substitute_pattern) else BOOLEAN_KEY("search pattern", "hlsearch", entry->data.search_pattern.highlighted) else INTEGER_KEY("search pattern", "off", entry->data.search_pattern.offset) else CONVERTED_STRING_KEY("search pattern", "pat", entry->data.search_pattern.pat) else ADDITIONAL_KEY } if (entry->data.search_pattern.pat == NULL) { emsgu("Error while reading ShaDa file: " "search pattern entry at position %" PRIu64 " " "has no pattern", (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } if (ad_ga.ga_len) { msgpack_object obj = { .type = MSGPACK_OBJECT_MAP, .via = { .map = { .size = (uint32_t) ad_ga.ga_len, .ptr = ad_ga.ga_data, } } }; entry->data.search_pattern.additional_data = xmalloc(sizeof(Dictionary)); if (!msgpack_rpc_to_dictionary( &obj, entry->data.search_pattern.additional_data)) { emsgu("Error while reading ShaDa file: " "search pattern entry at position %" PRIu64 " " "cannot be converted to a Dictionary", (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } } ga_clear(&ad_ga); break; } case kSDItemChange: case kSDItemJump: case kSDItemGlobalMark: case kSDItemLocalMark: { if (unpacked.data.type != MSGPACK_OBJECT_MAP) { emsgu("Error while reading ShaDa file: " "mark entry at position %" PRIu64 " " "is not a dictionary", (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.filemark = (struct shada_filemark) { .name = '"', .mark = (pos_T) {1, 0, 0}, .fname = NULL, .additional_data = NULL, }; garray_T ad_ga; ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1); for (size_t i = 0; i < unpacked.data.via.map.size; i++) { CHECK_KEY_IS_STR("mark"); CHECKED_KEY( "mark", "name", " which is not an unsigned integer", entry->data.filemark.name, (type_u64 != kSDItemJump && type_u64 != kSDItemChange && unpacked.data.via.map.ptr[i].val.type == MSGPACK_OBJECT_POSITIVE_INTEGER), u64, TOCHAR) else LONG_KEY("mark", "line", entry->data.filemark.mark.lnum) else INTEGER_KEY("mark", "col", entry->data.filemark.mark.col) else STRING_KEY("mark", "file", entry->data.filemark.fname) else ADDITIONAL_KEY } if (entry->data.filemark.mark.lnum == 0) { emsgu("Error while reading ShaDa file: " "mark entry at position %" PRIu64 " " "is missing line number", (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } if (ad_ga.ga_len) { msgpack_object obj = { .type = MSGPACK_OBJECT_MAP, .via = { .map = { .size = (uint32_t) ad_ga.ga_len, .ptr = ad_ga.ga_data, } } }; entry->data.filemark.additional_data = xmalloc(sizeof(Dictionary)); if (!msgpack_rpc_to_dictionary( &obj, entry->data.filemark.additional_data)) { emsgu("Error while reading ShaDa file: " "mark entry at position %" PRIu64 " " "cannot be converted to a Dictionary", (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } } ga_clear(&ad_ga); break; } case kSDItemRegister: { if (unpacked.data.type != MSGPACK_OBJECT_MAP) { emsgu("Error while reading ShaDa file: " "register entry at position %" PRIu64 " " "is not a dictionary", (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.reg = (struct reg) { .name = NUL, .type = MCHAR, .contents = NULL, .contents_size = 0, .width = 0, .additional_data = NULL, }; garray_T ad_ga; ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1); for (size_t i = 0; i < unpacked.data.via.map.size; i++) { CHECK_KEY_IS_STR("register"); TYPED_KEY("register", "type", "an unsigned integer", entry->data.reg.type, POSITIVE_INTEGER, u64, TOU8) else TYPED_KEY("register", "name", "an unsigned integer", entry->data.reg.name, POSITIVE_INTEGER, u64, TOCHAR) else TYPED_KEY("register", "width", "an unsigned integer", entry->data.reg.width, POSITIVE_INTEGER, u64, TOSIZE) else if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, "contents")) { if (unpacked.data.via.map.ptr[i].val.type != MSGPACK_OBJECT_ARRAY) { emsgu("Error while reading ShaDa file: " "register entry at position %" PRIu64 " " "has contents key with non-array value", (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } if (unpacked.data.via.map.ptr[i].val.via.array.size == 0) { emsgu("Error while reading ShaDa file: " "register entry at position %" PRIu64 " " "has contents key with empty array", (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } const msgpack_object_array arr = unpacked.data.via.map.ptr[i].val.via.array; for (size_t i = 0; i < arr.size; i++) { if (arr.ptr[i].type != MSGPACK_OBJECT_BIN) { emsgu("Error while reading ShaDa file: " "register entry at position %" PRIu64 " " "has contents array with non-string value", (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } } entry->data.reg.contents_size = arr.size; entry->data.reg.contents = xmalloc(arr.size * sizeof(char *)); for (size_t i = 0; i < arr.size; i++) { entry->data.reg.contents[i] = BIN_CONVERTED(arr.ptr[i].via.bin); } } else ADDITIONAL_KEY } if (entry->data.reg.contents == NULL) { emsgu("Error while reading ShaDa file: " "register entry at position %" PRIu64 " " "has missing contents array", (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } if (ad_ga.ga_len) { msgpack_object obj = { .type = MSGPACK_OBJECT_MAP, .via = { .map = { .size = (uint32_t) ad_ga.ga_len, .ptr = ad_ga.ga_data, } } }; entry->data.reg.additional_data = xmalloc(sizeof(Dictionary)); if (!msgpack_rpc_to_dictionary( &obj, entry->data.reg.additional_data)) { emsgu("Error while reading ShaDa file: " "register entry at position %" PRIu64 " " "cannot be converted to a Dictionary", (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } } ga_clear(&ad_ga); break; } case kSDItemHistoryEntry: { if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { emsgu("Error while reading ShaDa file: " "history entry at position %" PRIu64 " " "is not an array", (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.history_item = (struct history_item) { .histtype = 0, .string = NULL, .sep = 0, .additional_elements = NULL, }; if (unpacked.data.via.array.size < 2) { emsgu("Error while reading ShaDa file: " "history entry at position %" PRIu64 " " "does not have enough elements", (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_POSITIVE_INTEGER) { emsgu("Error while reading ShaDa file: " "history entry at position %" PRIu64 " " "has wrong history type type", (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[1].type != MSGPACK_OBJECT_BIN) { emsgu("Error while reading ShaDa file: " "history entry at position %" PRIu64 " " "has wrong history string type", (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (memchr(unpacked.data.via.array.ptr[1].via.bin.ptr, 0, unpacked.data.via.array.ptr[1].via.bin.size) != NULL) { emsgu("Error while reading ShaDa file: " "history entry at position %" PRIu64 " " "contains string with zero byte inside", (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.history_item.histtype = (uint8_t) unpacked.data.via.array.ptr[0].via.u64; const bool is_hist_search = entry->data.history_item.histtype == HIST_SEARCH; if (is_hist_search) { if (unpacked.data.via.array.size < 3) { emsgu("Error while reading ShaDa file: " "search history entry at position %" PRIu64 " " "does not have separator character", (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[2].type != MSGPACK_OBJECT_POSITIVE_INTEGER) { emsgu("Error while reading ShaDa file: " "search history entry at position %" PRIu64 " " "has wrong history separator type", (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.history_item.sep = (char) unpacked.data.via.array.ptr[2].via.u64; } size_t strsize; if (sd_reader->sd_conv.vc_type == CONV_NONE || !has_non_ascii_len(unpacked.data.via.array.ptr[1].via.bin.ptr, unpacked.data.via.array.ptr[1].via.bin.size)) { shada_read_next_item_hist_no_conv: strsize = ( unpacked.data.via.array.ptr[1].via.bin.size + 1 // Zero byte + 1 // Separator character ); entry->data.history_item.string = xmalloc(strsize); memcpy(entry->data.history_item.string, unpacked.data.via.array.ptr[1].via.bin.ptr, unpacked.data.via.array.ptr[1].via.bin.size); } else { size_t len = unpacked.data.via.array.ptr[1].via.bin.size; char *const converted = string_convert( &sd_reader->sd_conv, unpacked.data.via.array.ptr[1].via.bin.ptr, &len); if (converted != NULL) { strsize = len + 2; entry->data.history_item.string = xrealloc(converted, strsize); } else { goto shada_read_next_item_hist_no_conv; } } entry->data.history_item.string[strsize - 2] = 0; entry->data.history_item.string[strsize - 1] = entry->data.history_item.sep; if (unpacked.data.via.array.size > (size_t) (2 + is_hist_search)) { msgpack_object obj = { .type = MSGPACK_OBJECT_ARRAY, .via = { .array = { .size = (unpacked.data.via.array.size - (uint32_t) (2 + is_hist_search)), .ptr = unpacked.data.via.array.ptr + (2 + is_hist_search), } } }; entry->data.history_item.additional_elements = xmalloc(sizeof(Array)); if (!msgpack_rpc_to_array( &obj, entry->data.history_item.additional_elements)) { emsgu("Error while reading ShaDa file: " "history entry at position %" PRIu64 " " "cannot be converted to an Array", (uint64_t) initial_fpos); goto shada_read_next_item_error; } } break; } case kSDItemVariable: { if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { emsgu("Error while reading ShaDa file: " "variable entry at position %" PRIu64 " " "is not an array", (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.global_var = (struct global_var) { .name = NULL, .value = { .type = kObjectTypeNil, }, .additional_elements = NULL }; if (unpacked.data.via.array.size < 2) { emsgu("Error while reading ShaDa file: " "variable entry at position %" PRIu64 " " "does not have enough elements", (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) { emsgu("Error while reading ShaDa file: " "variable entry at position %" PRIu64 " " "has wrong variable name type", (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[1].type == MSGPACK_OBJECT_NIL || unpacked.data.via.array.ptr[1].type == MSGPACK_OBJECT_EXT) { emsgu("Error while reading ShaDa file: " "variable entry at position %" PRIu64 " " "has wrong variable value type", (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.global_var.name = xmemdupz(unpacked.data.via.array.ptr[0].via.bin.ptr, unpacked.data.via.array.ptr[0].via.bin.size); if (!msgpack_rpc_to_object(&(unpacked.data.via.array.ptr[1]), &(entry->data.global_var.value))) { emsgu("Error while reading ShaDa file: " "variable entry at position %" PRIu64 " " "has value that cannot be converted to the object", (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (sd_reader->sd_conv.vc_type != CONV_NONE) { convert_object(&sd_reader->sd_conv, &entry->data.global_var.value); } if (unpacked.data.via.array.size > 2) { msgpack_object obj = { .type = MSGPACK_OBJECT_ARRAY, .via = { .array = { .size = unpacked.data.via.array.size - 2, .ptr = unpacked.data.via.array.ptr + 2, } } }; entry->data.global_var.additional_elements = xmalloc(sizeof(Array)); if (!msgpack_rpc_to_array( &obj, entry->data.global_var.additional_elements)) { emsgu("Error while reading ShaDa file: " "variable entry at position %" PRIu64 " " "cannot be converted to an Array", (uint64_t) initial_fpos); goto shada_read_next_item_error; } } break; } case kSDItemSubString: { if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { emsgu("Error while reading ShaDa file: " "sub string entry at position %" PRIu64 " " "is not an array", (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.sub_string = (struct sub_string) { .sub = NULL, .additional_elements = NULL }; if (unpacked.data.via.array.size < 1) { emsgu("Error while reading ShaDa file: " "sub string entry at position %" PRIu64 " " "does not have enough elements", (uint64_t) initial_fpos); goto shada_read_next_item_error; } if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) { emsgu("Error while reading ShaDa file: " "sub string entry at position %" PRIu64 " " "has wrong sub string type", (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.sub_string.sub = BIN_CONVERTED(unpacked.data.via.array.ptr[0].via.bin); if (unpacked.data.via.array.size > 1) { msgpack_object obj = { .type = MSGPACK_OBJECT_ARRAY, .via = { .array = { .size = unpacked.data.via.array.size - 1, .ptr = unpacked.data.via.array.ptr + 1, } } }; entry->data.sub_string.additional_elements = xmalloc(sizeof(Array)); if (!msgpack_rpc_to_array( &obj, entry->data.sub_string.additional_elements)) { emsgu("Error while reading ShaDa file: " "sub string entry at position %" PRIu64 " " "cannot be converted to an Array", (uint64_t) initial_fpos); goto shada_read_next_item_error; } } break; } case kSDItemBufferList: { if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) { emsgu("Error while reading ShaDa file: " "buffer list entry at position %" PRIu64 " " "is not an array", (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.buffer_list = (struct buffer_list) { .size = 0, .buffers = NULL, }; if (unpacked.data.via.array.size == 0) { break; } entry->data.buffer_list.buffers = xcalloc(unpacked.data.via.array.size, sizeof(*entry->data.buffer_list.buffers)); for (size_t i = 0; i < unpacked.data.via.array.size; i++) { entry->data.buffer_list.size++; msgpack_unpacked unpacked_2 = (msgpack_unpacked) { .data = unpacked.data.via.array.ptr[i], }; { msgpack_unpacked unpacked = unpacked_2; if (unpacked.data.type != MSGPACK_OBJECT_MAP) { emsgu("Error while reading ShaDa file: " "buffer list at position %" PRIu64 " " "contains entry that is not a dictionary", (uint64_t) initial_fpos); goto shada_read_next_item_error; } entry->data.buffer_list.buffers[i].pos.lnum = 1; garray_T ad_ga; ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1); { const size_t j = i; { for (size_t i = 0; i < unpacked.data.via.map.size; i++) { CHECK_KEY_IS_STR("buffer list entry"); LONG_KEY("buffer list entry", "line", entry->data.buffer_list.buffers[j].pos.lnum) else INTEGER_KEY("buffer list entry", "col", entry->data.buffer_list.buffers[j].pos.col) else STRING_KEY("buffer list entry", "file", entry->data.buffer_list.buffers[j].fname) else ADDITIONAL_KEY } } } if (entry->data.buffer_list.buffers[i].fname == NULL) { emsgu("Error while reading ShaDa file: " "buffer list at position %" PRIu64 " " "contains entry that does not have a file name", (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } if (ad_ga.ga_len) { msgpack_object obj = { .type = MSGPACK_OBJECT_MAP, .via = { .map = { .size = (uint32_t) ad_ga.ga_len, .ptr = ad_ga.ga_data, } } }; entry->data.buffer_list.buffers[i].additional_data = xmalloc(sizeof(Dictionary)); if (!msgpack_rpc_to_dictionary( &obj, entry->data.buffer_list.buffers[i].additional_data)) { emsgu("Error while reading ShaDa file: " "buffer list at position %" PRIu64 " " "contains entry that cannot be converted to a Dictionary", (uint64_t) initial_fpos); ga_clear(&ad_ga); goto shada_read_next_item_error; } } ga_clear(&ad_ga); } } break; } case kSDItemMissing: { emsgu("Error while reading ShaDa file: " "there is an item at position %" PRIu64 " " "that must not be there: Missing items are " "for internal uses only", (uint64_t) initial_fpos); goto shada_read_next_item_error; } case kSDItemUnknown: { assert(false); } } entry->type = (ShadaEntryType) type_u64; goto shada_read_next_item_end; #undef BIN_CONVERTED #undef CONVERTED #undef CHECK_KEY #undef BOOLEAN_KEY #undef CONVERTED_STRING_KEY #undef STRING_KEY #undef ADDITIONAL_KEY #undef ID #undef BINDUP #undef TOCHAR #undef TOINT #undef TOLONG #undef TYPED_KEY #undef INT_KEY #undef INTEGER_KEY #undef LONG_KEY #undef TOU8 #undef TOSIZE shada_read_next_item_error: msgpack_unpacked_destroy(&unpacked); msgpack_unpacker_free(unpacker); entry->type = (ShadaEntryType) type_u64; shada_free_shada_entry(entry); entry->type = kSDItemMissing; return FAIL; shada_read_next_item_end: msgpack_unpacked_destroy(&unpacked); msgpack_unpacker_free(unpacker); return NOTDONE; } /// Return a list with all global marks set in current NeoVim instance /// /// List is not sorted. /// /// @warning Listed marks must be used before any buffer- or mark-editing /// function is run. /// /// @param[in] removable_bufs Set of buffers on removable media. /// /// @return Array of ShadaEntry values, last one has type kSDItemMissing. /// /// @warning Resulting ShadaEntry values must not be freed. Returned /// array must be freed with `xfree()`. static ShadaEntry *list_global_marks( const khash_t(bufset) *const removable_bufs) { const void *iter = NULL; const size_t nummarks = mark_global_amount(); ShadaEntry *const ret = xmalloc(sizeof(ShadaEntry) * (nummarks + 1)); ShadaEntry *cur = ret; if (nummarks) { do { cur->type = kSDItemGlobalMark; xfmark_T cur_fm; iter = mark_global_iter(iter, &(cur->data.filemark.name), &cur_fm); cur->data.filemark.mark = cur_fm.fmark.mark; cur->data.filemark.additional_data = cur_fm.fmark.additional_data; cur->timestamp = cur_fm.fmark.timestamp; if (cur_fm.fmark.fnum == 0) { if (cur_fm.fname == NULL) { continue; } cur->data.filemark.fname = (char *) cur_fm.fname; } else { const buf_T *const buf = buflist_findnr(cur_fm.fmark.fnum); if (buf == NULL || buf->b_ffname == NULL || SHADA_REMOVABLE(buf)) { continue; } else { cur->data.filemark.fname = (char *) buf->b_ffname; } } cur++; } while(iter != NULL); } cur->type = kSDItemMissing; return ret; } /// Return a list with all buffer marks set in some buffer /// /// List is not sorted. /// /// @warning Listed marks must be used before any buffer- or mark-editing /// function is run. /// /// @param[in] buf Buffer for which marks are listed. /// /// @return Array of ShadaEntry values, last one has type kSDItemMissing. /// /// @warning Resulting ShadaEntry values must not be freed. Returned /// array must be freed with `xfree()`. static ShadaEntry *list_buffer_marks(const buf_T *const buf) { const char *const fname = (char *) buf->b_ffname; const void *iter = NULL; const size_t nummarks = mark_buffer_amount(buf); ShadaEntry *const ret = xmalloc(sizeof(ShadaEntry) * (nummarks + 1)); ShadaEntry *cur = ret; if (nummarks) { do { cur->type = kSDItemLocalMark; fmark_T cur_fm; iter = mark_buffer_iter(iter, buf, &(cur->data.filemark.name), &cur_fm); cur->data.filemark.mark = cur_fm.mark; cur->data.filemark.fname = (char *) fname; cur->data.filemark.additional_data = cur_fm.additional_data; cur->timestamp = cur_fm.timestamp; if (cur->data.filemark.mark.lnum != 0) { cur++; } } while(iter != NULL); } cur->type = kSDItemMissing; return ret; } /// Check whether "name" is on removable media (according to 'shada') /// /// @param[in] name Checked name. /// /// @return True if it is, false otherwise. bool shada_removable(const char *name) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { char *p; char part[51]; int retval = FALSE; size_t n; char *new_name = home_replace_save(NULL, name); for (p = (char *) p_shada; *p; ) { (void) copy_option_part(&p, part, 51, ", "); if (part[0] == 'r') { n = STRLEN(part + 1); if (STRNICMP(part + 1, new_name, n) == 0) { retval = TRUE; break; } } } xfree(new_name); return retval; } /// Return a list with all registers and their contents /// /// List is not sorted. /// /// @warning Listed registers must be used before any register-editing function /// is run. /// /// @param[in] max_num_lines Maximum number of lines in the register. If it is /// zero then all registers are listed. /// /// @return Array of ShadaEntry values, last one has type kSDItemMissing. /// /// @warning Resulting ShadaEntry values must not be freed. Returned /// array must be freed with `xfree()`. static ShadaEntry *list_registers(const size_t max_num_lines) { const void *iter = NULL; const size_t numregs = op_register_amount(); ShadaEntry *const ret = xmalloc(sizeof(ShadaEntry) * (numregs + 1)); ShadaEntry *cur = ret; if (numregs) { do { cur->type = kSDItemRegister; yankreg_T cur_reg; iter = op_register_iter(iter, &(cur->data.reg.name), &cur_reg); if (max_num_lines && (size_t) cur_reg.y_size > max_num_lines) { continue; } cur->data.reg.contents = (char **) cur_reg.y_array; cur->data.reg.type = (uint8_t) cur_reg.y_type; cur->data.reg.contents_size = (size_t) cur_reg.y_size; if (cur_reg.y_type == MBLOCK) { cur->data.reg.width = (size_t) cur_reg.y_width; } else { cur->data.reg.width = 0; } cur->data.reg.additional_data = cur_reg.additional_data; cur->timestamp = cur_reg.timestamp; cur++; } while(iter != NULL); } cur->type = kSDItemMissing; return ret; } #if 0 static int viminfo_errcnt; static int no_viminfo(void) { /* "vim -i NONE" does not read or write a viminfo file */ return use_viminfo != NULL && STRCMP(use_viminfo, "NONE") == 0; } /* * Report an error for reading a viminfo file. * Count the number of errors. When there are more than 10, return TRUE. */ int viminfo_error(char *errnum, char *message, char_u *line) { vim_snprintf((char *)IObuff, IOSIZE, _("%sviminfo: %s in line: "), errnum, message); STRNCAT(IObuff, line, IOSIZE - STRLEN(IObuff) - 1); if (IObuff[STRLEN(IObuff) - 1] == '\n') IObuff[STRLEN(IObuff) - 1] = NUL; emsg(IObuff); if (++viminfo_errcnt >= 10) { EMSG(_("E136: viminfo: Too many errors, skipping rest of file")); return TRUE; } return FALSE; } /* * read_viminfo() -- Read the viminfo file. Registers etc. which are already * set are not over-written unless "flags" includes VIF_FORCEIT. -- webb */ int read_viminfo ( char_u *file, /* file name or NULL to use default name */ int flags /* VIF_WANT_INFO et al. */ ) { FILE *fp; char_u *fname; if (no_viminfo()) return FAIL; fname = viminfo_filename(file); /* get file name in allocated buffer */ fp = mch_fopen((char *)fname, READBIN); if (p_verbose > 0) { verbose_enter(); smsg(_("Reading viminfo file \"%s\"%s%s%s"), fname, (flags & VIF_WANT_INFO) ? _(" info") : "", (flags & VIF_WANT_MARKS) ? _(" marks") : "", (flags & VIF_GET_OLDFILES) ? _(" oldfiles") : "", fp == NULL ? _(" FAILED") : ""); verbose_leave(); } xfree(fname); if (fp == NULL) return FAIL; viminfo_errcnt = 0; do_viminfo(fp, NULL, flags); fclose(fp); return OK; } /* * Write the viminfo file. The old one is read in first so that effectively a * merge of current info and old info is done. This allows multiple vims to * run simultaneously, without losing any marks etc. * If "forceit" is TRUE, then the old file is not read in, and only internal * info is written to the file. */ void write_viminfo(char_u *file, int forceit) { char_u *fname; FILE *fp_in = NULL; /* input viminfo file, if any */ FILE *fp_out = NULL; /* output viminfo file */ char_u *tempname = NULL; /* name of temp viminfo file */ char_u *wp; #if defined(UNIX) mode_t umask_save; #endif if (no_viminfo()) return; fname = viminfo_filename(file); /* may set to default if NULL */ fp_in = mch_fopen((char *)fname, READBIN); if (fp_in == NULL) { /* if it does exist, but we can't read it, don't try writing */ if (os_file_exists(fname)) goto end; #if defined(UNIX) /* * For Unix we create the .viminfo non-accessible for others, * because it may contain text from non-accessible documents. */ umask_save = umask(077); #endif fp_out = mch_fopen((char *)fname, WRITEBIN); #if defined(UNIX) (void)umask(umask_save); #endif } else { /* * There is an existing viminfo file. Create a temporary file to * write the new viminfo into, in the same directory as the * existing viminfo file, which will be renamed later. */ #ifdef UNIX /* * For Unix we check the owner of the file. It's not very nice to * overwrite a user's viminfo file after a "su root", with a * viminfo file that the user can't read. */ FileInfo old_info; // FileInfo of existing viminfo file if (os_fileinfo((char *)fname, &old_info) && getuid() != ROOT_UID && !(old_info.stat.st_uid == getuid() ? (old_info.stat.st_mode & 0200) : (old_info.stat.st_gid == getgid() ? (old_info.stat.st_mode & 0020) : (old_info.stat.st_mode & 0002)))) { int tt = msg_didany; /* avoid a wait_return for this message, it's annoying */ EMSG2(_("E137: Viminfo file is not writable: %s"), fname); msg_didany = tt; fclose(fp_in); goto end; } #endif // Make tempname tempname = (char_u *)modname((char *)fname, ".tmp", FALSE); if (tempname != NULL) { /* * Check if tempfile already exists. Never overwrite an * existing file! */ if (os_file_exists(tempname)) { /* * Try another name. Change one character, just before * the extension. */ wp = tempname + STRLEN(tempname) - 5; if (wp < path_tail(tempname)) /* empty file name? */ wp = path_tail(tempname); for (*wp = 'z'; os_file_exists(tempname); --*wp) { /* * They all exist? Must be something wrong! Don't * write the viminfo file then. */ if (*wp == 'a') { xfree(tempname); tempname = NULL; break; } } } } if (tempname != NULL) { int fd; /* Use os_open() to be able to use O_NOFOLLOW and set file * protection: * Unix: same as original file, but strip s-bit. Reset umask to * avoid it getting in the way. * Others: r&w for user only. */ # ifdef UNIX umask_save = umask(0); fd = os_open((char *)tempname, O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, (int)((old_info.stat.st_mode & 0777) | 0600)); (void)umask(umask_save); # else fd = os_open((char *)tempname, O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600); # endif if (fd < 0) fp_out = NULL; else fp_out = fdopen(fd, WRITEBIN); /* * If we can't create in the same directory, try creating a * "normal" temp file. */ if (fp_out == NULL) { xfree(tempname); if ((tempname = vim_tempname()) != NULL) fp_out = mch_fopen((char *)tempname, WRITEBIN); } #ifdef UNIX /* * Make sure the owner can read/write it. This only works for * root. */ if (fp_out != NULL) { os_fchown(fileno(fp_out), old_info.stat.st_uid, old_info.stat.st_gid); } #endif } } /* * Check if the new viminfo file can be written to. */ if (fp_out == NULL) { EMSG2(_("E138: Can't write viminfo file %s!"), (fp_in == NULL || tempname == NULL) ? fname : tempname); if (fp_in != NULL) fclose(fp_in); goto end; } if (p_verbose > 0) { verbose_enter(); smsg(_("Writing viminfo file \"%s\""), fname); verbose_leave(); } viminfo_errcnt = 0; do_viminfo(fp_in, fp_out, forceit ? 0 : (VIF_WANT_INFO | VIF_WANT_MARKS)); fclose(fp_out); /* errors are ignored !? */ if (fp_in != NULL) { fclose(fp_in); /* In case of an error keep the original viminfo file. Otherwise * rename the newly written file. Give an error if that fails. */ if (viminfo_errcnt == 0 && vim_rename(tempname, fname) == -1) { viminfo_errcnt++; EMSG2(_("E886: Can't rename viminfo file to %s!"), fname); } if (viminfo_errcnt > 0) { os_remove((char *)tempname); } } end: xfree(fname); xfree(tempname); } /* * Get the viminfo file name to use. * If "file" is given and not empty, use it (has already been expanded by * cmdline functions). * Otherwise use "-i file_name", value from 'viminfo' or the default, and * expand environment variables. * Returns an allocated string. */ static char_u *viminfo_filename(char_u *file) { if (file == NULL || *file == NUL) { if (use_viminfo != NULL) file = use_viminfo; else if ((file = find_viminfo_parameter('n')) == NULL || *file == NUL) { #ifdef VIMINFO_FILE2 // don't use $HOME when not defined (turned into "c:/"!). if (!os_env_exists("HOME")) { // don't use $VIM when not available. expand_env((char_u *)"$VIM", NameBuff, MAXPATHL); if (STRCMP("$VIM", NameBuff) != 0) /* $VIM was expanded */ file = (char_u *)VIMINFO_FILE2; else file = (char_u *)VIMINFO_FILE; } else #endif file = (char_u *)VIMINFO_FILE; } expand_env(file, NameBuff, MAXPATHL); file = NameBuff; } return vim_strsave(file); } /* * do_viminfo() -- Should only be called from read_viminfo() & write_viminfo(). */ static void do_viminfo(FILE *fp_in, FILE *fp_out, int flags) { int count = 0; int eof = FALSE; vir_T vir; int merge = FALSE; vir.vir_line = xmalloc(LSIZE); vir.vir_fd = fp_in; vir.vir_conv.vc_type = CONV_NONE; if (fp_in != NULL) { if (flags & VIF_WANT_INFO) { eof = read_viminfo_up_to_marks(&vir, flags & VIF_FORCEIT, fp_out != NULL); merge = TRUE; } else if (flags != 0) /* Skip info, find start of marks */ while (!(eof = viminfo_readline(&vir)) && vir.vir_line[0] != '>') ; } if (fp_out != NULL) { /* Write the info: */ fprintf(fp_out, _("# This viminfo file was generated by Nvim %s.\n"), mediumVersion); fputs(_("# You may edit it if you're careful!\n\n"), fp_out); fputs(_("# Value of 'encoding' when this file was written\n"), fp_out); fprintf(fp_out, "*encoding=%s\n\n", p_enc); write_viminfo_search_pattern(fp_out); write_viminfo_sub_string(fp_out); write_viminfo_history(fp_out, merge); write_viminfo_registers(fp_out); write_viminfo_varlist(fp_out); write_viminfo_filemarks(fp_out); write_viminfo_bufferlist(fp_out); count = write_viminfo_marks(fp_out); } if (fp_in != NULL && (flags & (VIF_WANT_MARKS | VIF_GET_OLDFILES | VIF_FORCEIT))) copy_viminfo_marks(&vir, fp_out, count, eof, flags); xfree(vir.vir_line); if (vir.vir_conv.vc_type != CONV_NONE) convert_setup(&vir.vir_conv, NULL, NULL); } /* * read_viminfo_up_to_marks() -- Only called from do_viminfo(). Reads in the * first part of the viminfo file which contains everything but the marks that * are local to a file. Returns TRUE when end-of-file is reached. -- webb */ static int read_viminfo_up_to_marks(vir_T *virp, int forceit, int writing) { int eof; prepare_viminfo_history(forceit ? 9999 : 0, writing); eof = viminfo_readline(virp); while (!eof && virp->vir_line[0] != '>') { switch (virp->vir_line[0]) { /* Characters reserved for future expansion, ignored now */ case '+': /* "+40 /path/dir file", for running vim without args */ case '|': /* to be defined */ case '^': /* to be defined */ case '<': /* long line - ignored */ /* A comment or empty line. */ case NUL: case '\r': case '\n': case '#': eof = viminfo_readline(virp); break; case '*': /* "*encoding=value" */ eof = viminfo_encoding(virp); break; case '!': /* global variable */ eof = read_viminfo_varlist(virp, writing); break; case '%': /* entry for buffer list */ eof = read_viminfo_bufferlist(virp, writing); break; case '"': eof = read_viminfo_register(virp, forceit); break; case '/': /* Search string */ case '&': /* Substitute search string */ case '~': /* Last search string, followed by '/' or '&' */ eof = read_viminfo_search_pattern(virp, forceit); break; case '$': eof = read_viminfo_sub_string(virp, forceit); break; case ':': case '?': case '=': case '@': eof = read_viminfo_history(virp, writing); break; case '-': case '\'': eof = read_viminfo_filemark(virp, forceit); break; default: if (viminfo_error("E575: ", _("Illegal starting char"), virp->vir_line)) eof = TRUE; else eof = viminfo_readline(virp); break; } } /* Finish reading history items. */ if (!writing) finish_viminfo_history(); /* Change file names to buffer numbers for fmarks. */ FOR_ALL_BUFFERS(buf) { fmarks_check_names(buf); } return eof; } /* * Compare the 'encoding' value in the viminfo file with the current value of * 'encoding'. If different and the 'c' flag is in 'viminfo', setup for * conversion of text with iconv() in viminfo_readstring(). */ static int viminfo_encoding(vir_T *virp) { char_u *p; int i; if (get_viminfo_parameter('c') != 0) { p = vim_strchr(virp->vir_line, '='); if (p != NULL) { /* remove trailing newline */ ++p; for (i = 0; vim_isprintc(p[i]); ++i) ; p[i] = NUL; convert_setup(&virp->vir_conv, p, p_enc); } } return viminfo_readline(virp); } /* * Read a line from the viminfo file. * Returns TRUE for end-of-file; */ int viminfo_readline(vir_T *virp) { return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd); } /* * check string read from viminfo file * remove '\n' at the end of the line * - replace CTRL-V CTRL-V with CTRL-V * - replace CTRL-V 'n' with '\n' * * Check for a long line as written by viminfo_writestring(). * * Return the string in allocated memory. */ char_u * viminfo_readstring ( vir_T *virp, int off, /* offset for virp->vir_line */ int convert /* convert the string */ ) FUNC_ATTR_NONNULL_RET { char_u *retval; char_u *s, *d; if (virp->vir_line[off] == Ctrl_V && ascii_isdigit(virp->vir_line[off + 1])) { ssize_t len = atol((char *)virp->vir_line + off + 1); retval = xmalloc(len); // TODO(philix): change type of vim_fgets() size argument to size_t (void)vim_fgets(retval, (int)len, virp->vir_fd); s = retval + 1; /* Skip the leading '<' */ } else { retval = vim_strsave(virp->vir_line + off); s = retval; } /* Change CTRL-V CTRL-V to CTRL-V and CTRL-V n to \n in-place. */ d = retval; while (*s != NUL && *s != '\n') { if (s[0] == Ctrl_V && s[1] != NUL) { if (s[1] == 'n') *d++ = '\n'; else *d++ = Ctrl_V; s += 2; } else *d++ = *s++; } *d = NUL; if (convert && virp->vir_conv.vc_type != CONV_NONE && *retval != NUL) { d = string_convert(&virp->vir_conv, retval, NULL); if (d != NULL) { xfree(retval); retval = d; } } return retval; } /* * write string to viminfo file * - replace CTRL-V with CTRL-V CTRL-V * - replace '\n' with CTRL-V 'n' * - add a '\n' at the end * * For a long line: * - write " CTRL-V \n " in first line * - write " < \n " in second line */ void viminfo_writestring(FILE *fd, char_u *p) { int c; char_u *s; int len = 0; for (s = p; *s != NUL; ++s) { if (*s == Ctrl_V || *s == '\n') ++len; ++len; } /* If the string will be too long, write its length and put it in the next * line. Take into account that some room is needed for what comes before * the string (e.g., variable name). Add something to the length for the * '<', NL and trailing NUL. */ if (len > LSIZE / 2) fprintf(fd, "\026%d\n<", len + 3); while ((c = *p++) != NUL) { if (c == Ctrl_V || c == '\n') { putc(Ctrl_V, fd); if (c == '\n') c = 'n'; } putc(c, fd); } putc('\n', fd); } /* * Write all the named marks for all buffers. * Return the number of buffers for which marks have been written. */ int write_viminfo_marks(FILE *fp_out) { /* * Set b_last_cursor for the all buffers that have a window. */ FOR_ALL_TAB_WINDOWS(tp, win) { set_last_cursor(win); } fputs(_("\n# History of marks within files (newest to oldest):\n"), fp_out); int count = 0; FOR_ALL_BUFFERS(buf) { /* * Only write something if buffer has been loaded and at least one * mark is set. */ if (buf->b_marks_read) { bool is_mark_set = true; if (buf->b_last_cursor.lnum == 0) { is_mark_set = false; for (int i = 0; i < NMARKS; i++) { if (buf->b_namedm[i].lnum != 0) { is_mark_set = true; break; } } } if (is_mark_set && buf->b_ffname != NULL && buf->b_ffname[0] != NUL && !removable(buf->b_ffname)) { home_replace(NULL, buf->b_ffname, IObuff, IOSIZE, TRUE); fprintf(fp_out, "\n> "); viminfo_writestring(fp_out, IObuff); write_one_mark(fp_out, '"', &buf->b_last_cursor); write_one_mark(fp_out, '^', &buf->b_last_insert); write_one_mark(fp_out, '.', &buf->b_last_change); /* changelist positions are stored oldest first */ for (int i = 0; i < buf->b_changelistlen; ++i) { write_one_mark(fp_out, '+', &buf->b_changelist[i]); } for (int i = 0; i < NMARKS; i++) { write_one_mark(fp_out, 'a' + i, &buf->b_namedm[i]); } count++; } } } return count; } static void write_one_mark(FILE *fp_out, int c, pos_T *pos) { if (pos->lnum != 0) fprintf(fp_out, "\t%c\t%" PRId64 "\t%d\n", c, (int64_t)pos->lnum, (int)pos->col); } /* * Handle marks in the viminfo file: * fp_out != NULL: copy marks for buffers not in buffer list * fp_out == NULL && (flags & VIF_WANT_MARKS): read marks for curbuf only * fp_out == NULL && (flags & VIF_GET_OLDFILES | VIF_FORCEIT): fill v:oldfiles */ void copy_viminfo_marks(vir_T *virp, FILE *fp_out, int count, int eof, int flags) { char_u *line = virp->vir_line; buf_T *buf; int num_marked_files; int load_marks; int copy_marks_out; char_u *str; int i; char_u *p; char_u *name_buf; pos_T pos; list_T *list = NULL; name_buf = xmalloc(LSIZE); *name_buf = NUL; if (fp_out == NULL && (flags & (VIF_GET_OLDFILES | VIF_FORCEIT))) { list = list_alloc(); set_vim_var_list(VV_OLDFILES, list); } num_marked_files = get_viminfo_parameter('\''); while (!eof && (count < num_marked_files || fp_out == NULL)) { if (line[0] != '>') { if (line[0] != '\n' && line[0] != '\r' && line[0] != '#') { if (viminfo_error("E576: ", _("Missing '>'"), line)) break; /* too many errors, return now */ } eof = vim_fgets(line, LSIZE, virp->vir_fd); continue; /* Skip this dud line */ } /* * Handle long line and translate escaped characters. * Find file name, set str to start. * Ignore leading and trailing white space. */ str = skipwhite(line + 1); str = viminfo_readstring(virp, (int)(str - virp->vir_line), FALSE); p = str + STRLEN(str); while (p != str && (*p == NUL || ascii_isspace(*p))) p--; if (*p) p++; *p = NUL; if (list != NULL) list_append_string(list, str, -1); /* * If fp_out == NULL, load marks for current buffer. * If fp_out != NULL, copy marks for buffers not in buflist. */ load_marks = copy_marks_out = FALSE; if (fp_out == NULL) { if ((flags & VIF_WANT_MARKS) && curbuf->b_ffname != NULL) { if (*name_buf == NUL) /* only need to do this once */ home_replace(NULL, curbuf->b_ffname, name_buf, LSIZE, TRUE); if (fnamecmp(str, name_buf) == 0) load_marks = TRUE; } } else { /* fp_out != NULL */ /* This is slow if there are many buffers!! */ buf = NULL; FOR_ALL_BUFFERS(bp) { if (bp->b_ffname != NULL) { home_replace(NULL, bp->b_ffname, name_buf, LSIZE, TRUE); if (fnamecmp(str, name_buf) == 0) { buf = bp; break; } } } /* * copy marks if the buffer has not been loaded */ if (buf == NULL || !buf->b_marks_read) { copy_marks_out = TRUE; fputs("\n> ", fp_out); viminfo_writestring(fp_out, str); count++; } } free(str); pos.coladd = 0; while (!(eof = viminfo_readline(virp)) && line[0] == TAB) { if (load_marks) { if (line[1] != NUL) { int64_t lnum_64; unsigned int u; sscanf((char *)line + 2, "%" SCNd64 "%u", &lnum_64, &u); // safely downcast to linenr_T (long); remove when linenr_T refactored assert(lnum_64 <= LONG_MAX); pos.lnum = (linenr_T)lnum_64; assert(u <= INT_MAX); pos.col = (colnr_T)u; switch (line[1]) { case '"': curbuf->b_last_cursor = pos; break; case '^': curbuf->b_last_insert = pos; break; case '.': curbuf->b_last_change = pos; break; case '+': /* changelist positions are stored oldest * first */ if (curbuf->b_changelistlen == JUMPLISTSIZE) /* list is full, remove oldest entry */ memmove(curbuf->b_changelist, curbuf->b_changelist + 1, sizeof(pos_T) * (JUMPLISTSIZE - 1)); else ++curbuf->b_changelistlen; curbuf->b_changelist[ curbuf->b_changelistlen - 1] = pos; break; default: if ((i = line[1] - 'a') >= 0 && i < NMARKS) curbuf->b_namedm[i] = pos; } } } else if (copy_marks_out) fputs((char *)line, fp_out); } if (load_marks) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer == curbuf) wp->w_changelistidx = curbuf->b_changelistlen; } break; } } free(name_buf); } int read_viminfo_filemark(vir_T *virp, int force) { char_u *str; xfmark_T *fm; int i; /* We only get here if line[0] == '\'' or '-'. * Illegal mark names are ignored (for future expansion). */ str = virp->vir_line + 1; if ( *str <= 127 && ((*virp->vir_line == '\'' && (ascii_isdigit(*str) || isupper(*str))) || (*virp->vir_line == '-' && *str == '\''))) { if (*str == '\'') { /* If the jumplist isn't full insert fmark as oldest entry */ if (curwin->w_jumplistlen == JUMPLISTSIZE) fm = NULL; else { for (i = curwin->w_jumplistlen; i > 0; --i) curwin->w_jumplist[i] = curwin->w_jumplist[i - 1]; ++curwin->w_jumplistidx; ++curwin->w_jumplistlen; fm = &curwin->w_jumplist[0]; fm->fmark.mark.lnum = 0; fm->fname = NULL; } } else if (ascii_isdigit(*str)) fm = &namedfm[*str - '0' + NMARKS]; else { // is uppercase assert(*str >= 'A' && *str <= 'Z'); fm = &namedfm[*str - 'A']; } if (fm != NULL && (fm->fmark.mark.lnum == 0 || force)) { str = skipwhite(str + 1); fm->fmark.mark.lnum = getdigits_long(&str); str = skipwhite(str); fm->fmark.mark.col = getdigits_int(&str); fm->fmark.mark.coladd = 0; fm->fmark.fnum = 0; str = skipwhite(str); free(fm->fname); fm->fname = viminfo_readstring(virp, (int)(str - virp->vir_line), FALSE); } } return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd); } void write_viminfo_filemarks(FILE *fp) { int i; char_u *name; buf_T *buf; xfmark_T *fm; if (get_viminfo_parameter('f') == 0) return; fputs(_("\n# File marks:\n"), fp); /* * Find a mark that is the same file and position as the cursor. * That one, or else the last one is deleted. * Move '0 to '1, '1 to '2, etc. until the matching one or '9 * Set '0 mark to current cursor position. */ if (curbuf->b_ffname != NULL && !removable(curbuf->b_ffname)) { name = buflist_nr2name(curbuf->b_fnum, TRUE, FALSE); for (i = NMARKS; i < NMARKS + EXTRA_MARKS - 1; ++i) if (namedfm[i].fmark.mark.lnum == curwin->w_cursor.lnum && (namedfm[i].fname == NULL ? namedfm[i].fmark.fnum == curbuf->b_fnum : (name != NULL && STRCMP(name, namedfm[i].fname) == 0))) break; free(name); free(namedfm[i].fname); for (; i > NMARKS; --i) namedfm[i] = namedfm[i - 1]; namedfm[NMARKS].fmark.mark = curwin->w_cursor; namedfm[NMARKS].fmark.fnum = curbuf->b_fnum; namedfm[NMARKS].fname = NULL; } /* Write the filemarks '0 - '9 and 'A - 'Z */ for (i = 0; i < NMARKS + EXTRA_MARKS; i++) write_one_filemark(fp, &namedfm[i], '\'', i < NMARKS ? i + 'A' : i - NMARKS + '0'); /* Write the jumplist with -' */ fputs(_("\n# Jumplist (newest first):\n"), fp); setpcmark(); /* add current cursor position */ cleanup_jumplist(); for (fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1]; fm >= &curwin->w_jumplist[0]; --fm) { if (fm->fmark.fnum == 0 || ((buf = buflist_findnr(fm->fmark.fnum)) != NULL && !removable(buf->b_ffname))) write_one_filemark(fp, fm, '-', '\''); } } int read_viminfo_search_pattern(vir_T *virp, int force) { char_u *lp; int idx = -1; int magic = FALSE; int no_scs = FALSE; int off_line = FALSE; int off_end = 0; long off = 0; int setlast = FALSE; static int hlsearch_on = FALSE; char_u *val; /* * Old line types: * "/pat", "&pat": search/subst. pat * "~/pat", "~&pat": last used search/subst. pat * New line types: * "~h", "~H": hlsearch highlighting off/on * "~pat" * : 'm' off, 'M' on * : 's' off, 'S' on * : 'L' line offset, 'l' char offset * : 'E' from end, 'e' from start * : decimal, offset * : '~' last used pattern * : '/' search pat, '&' subst. pat */ lp = virp->vir_line; if (lp[0] == '~' && (lp[1] == 'm' || lp[1] == 'M')) { /* new line type */ if (lp[1] == 'M') /* magic on */ magic = TRUE; if (lp[2] == 's') no_scs = TRUE; if (lp[3] == 'L') off_line = TRUE; if (lp[4] == 'E') off_end = SEARCH_END; lp += 5; off = getdigits_long(&lp); } if (lp[0] == '~') { /* use this pattern for last-used pattern */ setlast = TRUE; lp++; } if (lp[0] == '/') idx = RE_SEARCH; else if (lp[0] == '&') idx = RE_SUBST; else if (lp[0] == 'h') /* ~h: 'hlsearch' highlighting off */ hlsearch_on = FALSE; else if (lp[0] == 'H') /* ~H: 'hlsearch' highlighting on */ hlsearch_on = TRUE; if (idx >= 0) { if (force || spats[idx].pat == NULL) { val = viminfo_readstring(virp, (int)(lp - virp->vir_line + 1), TRUE); set_last_search_pat(val, idx, magic, setlast); xfree(val); spats[idx].no_scs = no_scs; spats[idx].off.line = off_line; spats[idx].off.end = off_end; spats[idx].off.off = off; if (setlast) { SET_NO_HLSEARCH(!hlsearch_on); } } } return viminfo_readline(virp); } void write_viminfo_search_pattern(FILE *fp) { if (get_viminfo_parameter('/') != 0) { fprintf(fp, "\n# hlsearch on (H) or off (h):\n~%c", (no_hlsearch || find_viminfo_parameter('h') != NULL) ? 'h' : 'H'); wvsp_one(fp, RE_SEARCH, "", '/'); wvsp_one(fp, RE_SUBST, _("Substitute "), '&'); } } static void wvsp_one ( FILE *fp, /* file to write to */ int idx, /* spats[] index */ char *s, /* search pat */ int sc /* dir char */ ) { if (spats[idx].pat != NULL) { fprintf(fp, _("\n# Last %sSearch Pattern:\n~"), s); /* off.dir is not stored, it's reset to forward */ fprintf(fp, "%c%c%c%c%" PRId64 "%s%c", spats[idx].magic ? 'M' : 'm', /* magic */ spats[idx].no_scs ? 's' : 'S', /* smartcase */ spats[idx].off.line ? 'L' : 'l', /* line offset */ spats[idx].off.end ? 'E' : 'e', /* offset from end */ (int64_t)spats[idx].off.off, /* offset */ last_idx == idx ? "~" : "", /* last used pat */ sc); viminfo_writestring(fp, spats[idx].pat); } } /* * Prepare for reading the history from the viminfo file. * This allocates history arrays to store the read history lines. */ void prepare_viminfo_history(int asklen, int writing) { int i; int num; init_history(); viminfo_add_at_front = (asklen != 0 && !writing); if (asklen > hislen) asklen = hislen; for (int type = 0; type < HIST_COUNT; ++type) { /* Count the number of empty spaces in the history list. Entries read * from viminfo previously are also considered empty. If there are * more spaces available than we request, then fill them up. */ for (i = 0, num = 0; i < hislen; i++) if (history[type][i].hisstr == NULL || history[type][i].viminfo) num++; int len = asklen; if (num > len) len = num; if (len <= 0) viminfo_history[type] = NULL; else viminfo_history[type] = xmalloc(len * sizeof(char_u *)); if (viminfo_history[type] == NULL) len = 0; viminfo_hislen[type] = len; viminfo_hisidx[type] = 0; } } /* * Accept a line from the viminfo, store it in the history array when it's * new. */ int read_viminfo_history(vir_T *virp, int writing) { int type; char_u *val; type = hist_char2type(virp->vir_line[0]); if (viminfo_hisidx[type] < viminfo_hislen[type]) { val = viminfo_readstring(virp, 1, TRUE); if (val != NULL && *val != NUL) { int sep = (*val == ' ' ? NUL : *val); if (!in_history(type, val + (type == HIST_SEARCH), viminfo_add_at_front, sep, writing)) { /* Need to re-allocate to append the separator byte. */ size_t len = STRLEN(val); char_u *p = xmalloc(len + 2); if (type == HIST_SEARCH) { /* Search entry: Move the separator from the first * column to after the NUL. */ memmove(p, val + 1, len); p[len] = sep; } else { /* Not a search entry: No separator in the viminfo * file, add a NUL separator. */ memmove(p, val, len + 1); p[len + 1] = NUL; } viminfo_history[type][viminfo_hisidx[type]++] = p; } } xfree(val); } return viminfo_readline(virp); } /* * Finish reading history lines from viminfo. Not used when writing viminfo. */ void finish_viminfo_history(void) { int idx; int i; int type; for (type = 0; type < HIST_COUNT; ++type) { if (history[type] == NULL) continue; idx = hisidx[type] + viminfo_hisidx[type]; if (idx >= hislen) idx -= hislen; else if (idx < 0) idx = hislen - 1; if (viminfo_add_at_front) hisidx[type] = idx; else { if (hisidx[type] == -1) hisidx[type] = hislen - 1; do { if (history[type][idx].hisstr != NULL || history[type][idx].viminfo) break; if (++idx == hislen) idx = 0; } while (idx != hisidx[type]); if (idx != hisidx[type] && --idx < 0) idx = hislen - 1; } for (i = 0; i < viminfo_hisidx[type]; i++) { xfree(history[type][idx].hisstr); history[type][idx].hisstr = viminfo_history[type][i]; history[type][idx].viminfo = TRUE; if (--idx < 0) idx = hislen - 1; } idx += 1; idx %= hislen; for (i = 0; i < viminfo_hisidx[type]; i++) { history[type][idx++].hisnum = ++hisnum[type]; idx %= hislen; } xfree(viminfo_history[type]); viminfo_history[type] = NULL; viminfo_hisidx[type] = 0; } } /* * Write history to viminfo file in "fp". * When "merge" is TRUE merge history lines with a previously read viminfo * file, data is in viminfo_history[]. * When "merge" is FALSE just write all history lines. Used for ":wviminfo!". */ void write_viminfo_history(FILE *fp, int merge) { int i; int type; int num_saved; char_u *p; int c; int round; init_history(); if (hislen == 0) return; for (type = 0; type < HIST_COUNT; ++type) { num_saved = get_viminfo_parameter(hist_type2char(type, FALSE)); if (num_saved == 0) continue; if (num_saved < 0) /* Use default */ num_saved = hislen; fprintf(fp, _("\n# %s History (newest to oldest):\n"), type == HIST_CMD ? _("Command Line") : type == HIST_SEARCH ? _("Search String") : type == HIST_EXPR ? _("Expression") : _("Input Line")); if (num_saved > hislen) num_saved = hislen; /* * Merge typed and viminfo history: * round 1: history of typed commands. * round 2: history from recently read viminfo. */ for (round = 1; round <= 2; ++round) { if (round == 1) /* start at newest entry, somewhere in the list */ i = hisidx[type]; else if (viminfo_hisidx[type] > 0) /* start at newest entry, first in the list */ i = 0; else /* empty list */ i = -1; if (i >= 0) while (num_saved > 0 && !(round == 2 && i >= viminfo_hisidx[type])) { p = round == 1 ? history[type][i].hisstr : viminfo_history[type] == NULL ? NULL : viminfo_history[type][i]; if (p != NULL && (round == 2 || !merge || !history[type][i].viminfo)) { --num_saved; fputc(hist_type2char(type, TRUE), fp); /* For the search history: put the separator in the * second column; use a space if there isn't one. */ if (type == HIST_SEARCH) { c = p[STRLEN(p) + 1]; putc(c == NUL ? ' ' : c, fp); } viminfo_writestring(fp, p); } if (round == 1) { /* Decrement index, loop around and stop when back at * the start. */ if (--i < 0) i = hislen - 1; if (i == hisidx[type]) break; } else { /* Increment index. Stop at the end in the while. */ ++i; } } } for (i = 0; i < viminfo_hisidx[type]; ++i) if (viminfo_history[type] != NULL) xfree(viminfo_history[type][i]); xfree(viminfo_history[type]); viminfo_history[type] = NULL; viminfo_hisidx[type] = 0; } } int read_viminfo_register(vir_T *virp, int force) { int eof; int do_it = TRUE; int size; int limit; int set_prev = FALSE; char_u *str; char_u **array = NULL; /* We only get here (hopefully) if line[0] == '"' */ str = virp->vir_line + 1; /* If the line starts with "" this is the y_previous register. */ if (*str == '"') { set_prev = TRUE; str++; } if (!ASCII_ISALNUM(*str) && *str != '-') { if (viminfo_error("E577: ", _("Illegal register name"), virp->vir_line)) return TRUE; /* too many errors, pretend end-of-file */ do_it = FALSE; } yankreg_T *reg = get_yank_register(*str++, YREG_PUT); if (!force && reg->y_array != NULL) do_it = FALSE; if (*str == '@') { /* "x@: register x used for @@ */ if (force || execreg_lastc == NUL) execreg_lastc = str[-1]; } size = 0; limit = 100; /* Optimized for registers containing <= 100 lines */ if (do_it) { if (set_prev) { y_previous = reg; } free_register(reg); array = xmalloc(limit * sizeof(char_u *)); str = skipwhite(skiptowhite(str)); if (STRNCMP(str, "CHAR", 4) == 0) { reg->y_type = MCHAR; } else if (STRNCMP(str, "BLOCK", 5) == 0) { reg->y_type = MBLOCK; } else { reg->y_type = MLINE; } /* get the block width; if it's missing we get a zero, which is OK */ str = skipwhite(skiptowhite(str)); reg->y_width = getdigits_int(&str); } while (!(eof = viminfo_readline(virp)) && (virp->vir_line[0] == TAB || virp->vir_line[0] == '<')) { if (do_it) { if (size >= limit) { limit *= 2; array = xrealloc(array, limit * sizeof(char_u *)); } array[size++] = viminfo_readstring(virp, 1, TRUE); } } if (do_it) { if (size == 0) { xfree(array); } else if (size < limit) { reg->y_array = xrealloc(array, size * sizeof(char_u *)); } else { reg->y_array = array; } reg->y_size = size; } return eof; } void write_viminfo_registers(FILE *fp) { int i, j; char_u *type; char_u c; int num_lines; int max_num_lines; int max_kbyte; long len; fputs(_("\n# Registers:\n"), fp); /* Get '<' value, use old '"' value if '<' is not found. */ max_num_lines = get_viminfo_parameter('<'); if (max_num_lines < 0) max_num_lines = get_viminfo_parameter('"'); if (max_num_lines == 0) return; max_kbyte = get_viminfo_parameter('s'); if (max_kbyte == 0) return; // don't include clipboard registers '*'/'+' for (i = 0; i < NUM_SAVED_REGISTERS; i++) { if (y_regs[i].y_array == NULL) continue; /* Skip empty registers. */ num_lines = y_regs[i].y_size; if (num_lines == 0 || (num_lines == 1 && y_regs[i].y_type == MCHAR && *y_regs[i].y_array[0] == NUL)) continue; if (max_kbyte > 0) { /* Skip register if there is more text than the maximum size. */ len = 0; for (j = 0; j < num_lines; j++) len += (long)STRLEN(y_regs[i].y_array[j]) + 1L; if (len > (long)max_kbyte * 1024L) continue; } switch (y_regs[i].y_type) { case MLINE: type = (char_u *)"LINE"; break; case MCHAR: type = (char_u *)"CHAR"; break; case MBLOCK: type = (char_u *)"BLOCK"; break; default: sprintf((char *)IObuff, _("E574: Unknown register type %d"), y_regs[i].y_type); emsg(IObuff); type = (char_u *)"LINE"; break; } if (y_previous == &y_regs[i]) fprintf(fp, "\""); c = get_register_name(i); fprintf(fp, "\"%c", c); if (c == execreg_lastc) fprintf(fp, "@"); fprintf(fp, "\t%s\t%d\n", type, (int)y_regs[i].y_width ); /* If max_num_lines < 0, then we save ALL the lines in the register */ if (max_num_lines > 0 && num_lines > max_num_lines) num_lines = max_num_lines; for (j = 0; j < num_lines; j++) { putc('\t', fp); viminfo_writestring(fp, y_regs[i].y_array[j]); } } } /* * Restore global vars that start with a capital from the viminfo file */ int read_viminfo_varlist(vir_T *virp, int writing) { char_u *tab; int type = VAR_NUMBER; typval_T tv; if (!writing && (find_viminfo_parameter('!') != NULL)) { tab = vim_strchr(virp->vir_line + 1, '\t'); if (tab != NULL) { *tab++ = NUL; /* isolate the variable name */ switch (*tab) { case 'S': type = VAR_STRING; break; case 'F': type = VAR_FLOAT; break; case 'D': type = VAR_DICT; break; case 'L': type = VAR_LIST; break; } tab = vim_strchr(tab, '\t'); if (tab != NULL) { tv.v_type = type; if (type == VAR_STRING || type == VAR_DICT || type == VAR_LIST) tv.vval.v_string = viminfo_readstring(virp, (int)(tab - virp->vir_line + 1), TRUE); else if (type == VAR_FLOAT) (void)string2float(tab + 1, &tv.vval.v_float); else tv.vval.v_number = atol((char *)tab + 1); if (type == VAR_DICT || type == VAR_LIST) { typval_T *etv = eval_expr(tv.vval.v_string, NULL); if (etv == NULL) /* Failed to parse back the dict or list, use it as a * string. */ tv.v_type = VAR_STRING; else { free(tv.vval.v_string); tv = *etv; free(etv); } } set_var(virp->vir_line + 1, &tv, FALSE); if (tv.v_type == VAR_STRING) free(tv.vval.v_string); else if (tv.v_type == VAR_DICT || tv.v_type == VAR_LIST) clear_tv(&tv); } } } return viminfo_readline(virp); } /* * Write global vars that start with a capital to the viminfo file */ void write_viminfo_varlist(FILE *fp) { hashitem_T *hi; dictitem_T *this_var; int todo; char *s; char_u *p; char_u *tofree; char_u numbuf[NUMBUFLEN]; if (find_viminfo_parameter('!') == NULL) return; fputs(_("\n# global variables:\n"), fp); todo = (int)globvarht.ht_used; for (hi = globvarht.ht_array; todo > 0; ++hi) { if (!HASHITEM_EMPTY(hi)) { --todo; this_var = HI2DI(hi); if (var_flavour(this_var->di_key) == VAR_FLAVOUR_VIMINFO) { switch (this_var->di_tv.v_type) { case VAR_STRING: s = "STR"; break; case VAR_NUMBER: s = "NUM"; break; case VAR_FLOAT: s = "FLO"; break; case VAR_DICT: s = "DIC"; break; case VAR_LIST: s = "LIS"; break; default: continue; } fprintf(fp, "!%s\t%s\t", this_var->di_key, s); p = echo_string(&this_var->di_tv, &tofree, numbuf, 0); if (p != NULL) viminfo_writestring(fp, p); free(tofree); } } } } int read_viminfo_bufferlist(vir_T *virp, int writing) { char_u *tab; linenr_T lnum; colnr_T col; buf_T *buf; char_u *sfname; char_u *xline; /* Handle long line and escaped characters. */ xline = viminfo_readstring(virp, 1, FALSE); /* don't read in if there are files on the command-line or if writing: */ if (xline != NULL && !writing && ARGCOUNT == 0 && find_viminfo_parameter('%') != NULL) { /* Format is: Tab Tab . * Watch out for a Tab in the file name, work from the end. */ lnum = 0; col = 0; tab = vim_strrchr(xline, '\t'); if (tab != NULL) { *tab++ = '\0'; col = (colnr_T)atoi((char *)tab); tab = vim_strrchr(xline, '\t'); if (tab != NULL) { *tab++ = '\0'; lnum = atol((char *)tab); } } /* Expand "~/" in the file name at "line + 1" to a full path. * Then try shortening it by comparing with the current directory */ expand_env(xline, NameBuff, MAXPATHL); sfname = path_shorten_fname_if_possible(NameBuff); buf = buflist_new(NameBuff, sfname, (linenr_T)0, BLN_LISTED); if (buf != NULL) { /* just in case... */ buf->b_last_cursor.lnum = lnum; buf->b_last_cursor.col = col; buflist_setfpos(buf, curwin, lnum, col, FALSE); } } xfree(xline); return viminfo_readline(virp); } void write_viminfo_bufferlist(FILE *fp) { char_u *line; int max_buffers; if (find_viminfo_parameter('%') == NULL) return; /* Without a number -1 is returned: do all buffers. */ max_buffers = get_viminfo_parameter('%'); /* Allocate room for the file name, lnum and col. */ #define LINE_BUF_LEN (MAXPATHL + 40) line = xmalloc(LINE_BUF_LEN); FOR_ALL_TAB_WINDOWS(tp, win) { set_last_cursor(win); } fputs(_("\n# Buffer list:\n"), fp); FOR_ALL_BUFFERS(buf) { if (buf->b_fname == NULL || !buf->b_p_bl || bt_quickfix(buf) || removable(buf->b_ffname)) continue; if (max_buffers-- == 0) break; putc('%', fp); home_replace(NULL, buf->b_ffname, line, MAXPATHL, TRUE); vim_snprintf_add((char *)line, LINE_BUF_LEN, "\t%" PRId64 "\t%d", (int64_t)buf->b_last_cursor.lnum, buf->b_last_cursor.col); viminfo_writestring(fp, line); } xfree(line); } int read_viminfo_sub_string(vir_T *virp, int force) { if (force) xfree(old_sub); if (force || old_sub == NULL) old_sub = viminfo_readstring(virp, 1, TRUE); return viminfo_readline(virp); } void write_viminfo_sub_string(FILE *fp) { if (get_viminfo_parameter('/') != 0 && old_sub != NULL) { fputs(_("\n# Last Substitute String:\n$"), fp); viminfo_writestring(fp, old_sub); } } /* * Structure used for reading from the viminfo file. */ typedef struct { char_u *vir_line; /* text of the current line */ FILE *vir_fd; /* file descriptor */ vimconv_T vir_conv; /* encoding conversion */ } vir_T; #endif