Mercurial > vim
comparison src/undo.c @ 2214:f8222d1f9a73 vim73
Included patch for persistent undo. Lots of changes and added test.
author | Bram Moolenaar <bram@vim.org> |
---|---|
date | Sun, 23 May 2010 23:34:36 +0200 |
parents | 8c6a66e2b3cc |
children | cccb71c2c5c1 |
comparison
equal
deleted
inserted
replaced
2213:0e0e99d1092e | 2214:f8222d1f9a73 |
---|---|
97 static void u_add_time __ARGS((char_u *buf, size_t buflen, time_t tt)); | 97 static void u_add_time __ARGS((char_u *buf, size_t buflen, time_t tt)); |
98 static void u_freeheader __ARGS((buf_T *buf, u_header_T *uhp, u_header_T **uhpp)); | 98 static void u_freeheader __ARGS((buf_T *buf, u_header_T *uhp, u_header_T **uhpp)); |
99 static void u_freebranch __ARGS((buf_T *buf, u_header_T *uhp, u_header_T **uhpp)); | 99 static void u_freebranch __ARGS((buf_T *buf, u_header_T *uhp, u_header_T **uhpp)); |
100 static void u_freeentries __ARGS((buf_T *buf, u_header_T *uhp, u_header_T **uhpp)); | 100 static void u_freeentries __ARGS((buf_T *buf, u_header_T *uhp, u_header_T **uhpp)); |
101 static void u_freeentry __ARGS((u_entry_T *, long)); | 101 static void u_freeentry __ARGS((u_entry_T *, long)); |
102 #ifdef FEAT_PERSISTENT_UNDO | |
103 static void unserialize_pos __ARGS((pos_T *pos, FILE *fp)); | |
104 static void unserialize_visualinfo __ARGS((visualinfo_T *info, FILE *fp)); | |
105 static char_u *u_get_undo_file_name __ARGS((char_u *, int reading)); | |
106 static int serialize_uep __ARGS((u_entry_T *uep, FILE *fp)); | |
107 static void serialize_pos __ARGS((pos_T pos, FILE *fp)); | |
108 static void serialize_visualinfo __ARGS((visualinfo_T info, FILE *fp)); | |
109 #endif | |
102 | 110 |
103 #ifdef U_USE_MALLOC | 111 #ifdef U_USE_MALLOC |
104 # define U_FREE_LINE(ptr) vim_free(ptr) | 112 # define U_FREE_LINE(ptr) vim_free(ptr) |
105 # define U_ALLOC_LINE(size) lalloc((long_u)((size) + 1), FALSE) | 113 # define U_ALLOC_LINE(size) lalloc((long_u)((size) + 1), FALSE) |
106 #else | 114 #else |
116 /* | 124 /* |
117 * When 'u' flag included in 'cpoptions', we behave like vi. Need to remember | 125 * When 'u' flag included in 'cpoptions', we behave like vi. Need to remember |
118 * the action that "u" should do. | 126 * the action that "u" should do. |
119 */ | 127 */ |
120 static int undo_undoes = FALSE; | 128 static int undo_undoes = FALSE; |
129 | |
130 static int lastmark = 0; | |
121 | 131 |
122 #ifdef U_DEBUG | 132 #ifdef U_DEBUG |
123 /* | 133 /* |
124 * Check the undo structures for being valid. Print a warning when something | 134 * Check the undo structures for being valid. Print a warning when something |
125 * looks wrong. | 135 * looks wrong. |
649 return OK; | 659 return OK; |
650 } | 660 } |
651 do_outofmem_msg((long_u)0); | 661 do_outofmem_msg((long_u)0); |
652 return FAIL; | 662 return FAIL; |
653 } | 663 } |
664 | |
665 #ifdef FEAT_PERSISTENT_UNDO | |
666 | |
667 # define UF_START_MAGIC 0xfeac /* magic at start of undofile */ | |
668 # define UF_HEADER_MAGIC 0x5fd0 /* magic at start of header */ | |
669 # define UF_END_MAGIC 0xe7aa /* magic after last header */ | |
670 # define UF_VERSION 1 /* 2-byte undofile version number */ | |
671 | |
672 /* | |
673 * Compute the hash for the current buffer text into hash[UNDO_HASH_SIZE]. | |
674 */ | |
675 void | |
676 u_compute_hash(hash) | |
677 char_u *hash; | |
678 { | |
679 context_sha256_T ctx; | |
680 linenr_T lnum; | |
681 char_u *p; | |
682 | |
683 sha256_start(&ctx); | |
684 for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; ++lnum) | |
685 { | |
686 p = ml_get(lnum); | |
687 sha256_update(&ctx, p, STRLEN(p) + 1); | |
688 } | |
689 sha256_finish(&ctx, hash); | |
690 } | |
691 | |
692 /* | |
693 * Unserialize the pos_T at the current position in fp. | |
694 */ | |
695 static void | |
696 unserialize_pos(pos, fp) | |
697 pos_T *pos; | |
698 FILE *fp; | |
699 { | |
700 pos->lnum = get4c(fp); | |
701 pos->col = get4c(fp); | |
702 #ifdef FEAT_VIRTUALEDIT | |
703 pos->coladd = get4c(fp); | |
704 #else | |
705 (void)get4c(fp); | |
706 #endif | |
707 } | |
708 | |
709 /* | |
710 * Unserialize the visualinfo_T at the current position in fp. | |
711 */ | |
712 static void | |
713 unserialize_visualinfo(info, fp) | |
714 visualinfo_T *info; | |
715 FILE *fp; | |
716 { | |
717 unserialize_pos(&info->vi_start, fp); | |
718 unserialize_pos(&info->vi_end, fp); | |
719 info->vi_mode = get4c(fp); | |
720 info->vi_curswant = get4c(fp); | |
721 } | |
722 | |
723 /* | |
724 * Return an allocated string of the full path of the target undofile. | |
725 * When "reading" is TRUE find the file to read, go over all directories in | |
726 * 'undodir'. | |
727 * When "reading" is FALSE use the first name where the directory exists. | |
728 */ | |
729 static char_u * | |
730 u_get_undo_file_name(buf_ffname, reading) | |
731 char_u *buf_ffname; | |
732 int reading; | |
733 { | |
734 char_u *dirp; | |
735 char_u dir_name[IOSIZE + 1]; | |
736 char_u *munged_name = NULL; | |
737 char_u *undo_file_name = NULL; | |
738 int dir_len; | |
739 char_u *p; | |
740 struct stat st; | |
741 char_u *ffname = buf_ffname; | |
742 #ifdef HAVE_READLINK | |
743 char_u fname_buf[MAXPATHL]; | |
744 #endif | |
745 | |
746 if (ffname == NULL) | |
747 return NULL; | |
748 | |
749 #ifdef HAVE_READLINK | |
750 /* Expand symlink in the file name, so that we put the undo file with the | |
751 * actual file instead of with the symlink. */ | |
752 if (resolve_symlink(ffname, fname_buf) == OK) | |
753 ffname = fname_buf; | |
754 #endif | |
755 | |
756 /* Loop over 'undodir'. When reading find the first file that exists. | |
757 * When not reading use the first directory that exists or ".". */ | |
758 dirp = p_udir; | |
759 while (*dirp != NUL) | |
760 { | |
761 dir_len = copy_option_part(&dirp, dir_name, IOSIZE, ","); | |
762 if (dir_len == 1 && dir_name[0] == '.') | |
763 { | |
764 /* Use same directory as the ffname, | |
765 * "dir/name" -> "dir/.name.un~" */ | |
766 undo_file_name = vim_strnsave(ffname, STRLEN(ffname) + 5); | |
767 if (undo_file_name == NULL) | |
768 break; | |
769 p = gettail(undo_file_name); | |
770 mch_memmove(p + 1, p, STRLEN(p) + 1); | |
771 *p = '.'; | |
772 STRCAT(p, ".un~"); | |
773 } | |
774 else | |
775 { | |
776 dir_name[dir_len] = NUL; | |
777 if (mch_isdir(dir_name)) | |
778 { | |
779 if (munged_name == NULL) | |
780 { | |
781 munged_name = vim_strsave(ffname); | |
782 if (munged_name == NULL) | |
783 return NULL; | |
784 for (p = munged_name; *p != NUL; mb_ptr_adv(p)) | |
785 if (vim_ispathsep(*p)) | |
786 *p = '%'; | |
787 } | |
788 undo_file_name = concat_fnames(dir_name, munged_name, TRUE); | |
789 } | |
790 } | |
791 | |
792 /* When reading check if the file exists. */ | |
793 if (undo_file_name != NULL && (!reading | |
794 || mch_stat((char *)undo_file_name, &st) >= 0)) | |
795 break; | |
796 vim_free(undo_file_name); | |
797 undo_file_name = NULL; | |
798 } | |
799 | |
800 vim_free(munged_name); | |
801 return undo_file_name; | |
802 } | |
803 | |
804 /* | |
805 * Load the undo tree from an undo file. | |
806 * If "name" is not NULL use it as the undo file name. This also means being | |
807 * a bit more verbose. | |
808 * Otherwise use curbuf->b_ffname to generate the undo file name. | |
809 * "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text. | |
810 */ | |
811 void | |
812 u_read_undo(name, hash) | |
813 char_u *name; | |
814 char_u *hash; | |
815 { | |
816 char_u *file_name; | |
817 FILE *fp; | |
818 long magic, version, str_len; | |
819 char_u *line_ptr = NULL; | |
820 linenr_T line_lnum; | |
821 colnr_T line_colnr; | |
822 linenr_T line_count; | |
823 int uep_len; | |
824 int line_len; | |
825 int num_head; | |
826 long old_header_seq, new_header_seq, cur_header_seq; | |
827 long seq_last, seq_cur; | |
828 short old_idx = -1, new_idx = -1, cur_idx = -1; | |
829 long num_read_uhps = 0; | |
830 time_t seq_time; | |
831 int i, j; | |
832 int c; | |
833 short found_first_uep = 0; | |
834 char_u **array; | |
835 char_u *line; | |
836 u_entry_T *uep, *last_uep, *nuep; | |
837 u_header_T *uhp; | |
838 u_header_T **uhp_table = NULL; | |
839 char_u read_hash[UNDO_HASH_SIZE]; | |
840 | |
841 if (name == NULL) | |
842 { | |
843 file_name = u_get_undo_file_name(curbuf->b_ffname, TRUE); | |
844 if (file_name == NULL) | |
845 return; | |
846 } | |
847 else | |
848 file_name = name; | |
849 | |
850 if (p_verbose > 0) | |
851 smsg((char_u *)_("Reading undo file: %s"), file_name); | |
852 fp = mch_fopen((char *)file_name, "r"); | |
853 if (fp == NULL) | |
854 { | |
855 if (name != NULL || p_verbose > 0) | |
856 EMSG2(_("E822: Cannot open undo file for reading: %s"), file_name); | |
857 goto error; | |
858 } | |
859 | |
860 /* Begin overall file information */ | |
861 magic = get2c(fp); | |
862 if (magic != UF_START_MAGIC) | |
863 { | |
864 EMSG2(_("E823: Corrupted undo file: %s"), file_name); | |
865 goto error; | |
866 } | |
867 version = get2c(fp); | |
868 if (version != UF_VERSION) | |
869 { | |
870 EMSG2(_("E824: Incompatible undo file: %s"), file_name); | |
871 goto error; | |
872 } | |
873 | |
874 fread(read_hash, UNDO_HASH_SIZE, 1, fp); | |
875 line_count = (linenr_T)get4c(fp); | |
876 if (memcmp(hash, read_hash, UNDO_HASH_SIZE) != 0 | |
877 || line_count != curbuf->b_ml.ml_line_count) | |
878 { | |
879 if (p_verbose > 0 || name != NULL) | |
880 { | |
881 verbose_enter(); | |
882 give_warning((char_u *)_("Undo file contents changed"), TRUE); | |
883 verbose_leave(); | |
884 } | |
885 goto error; | |
886 } | |
887 | |
888 /* Begin undo data for U */ | |
889 str_len = get4c(fp); | |
890 if (str_len < 0) | |
891 goto error; | |
892 else if (str_len > 0) | |
893 { | |
894 if ((line_ptr = U_ALLOC_LINE(str_len)) == NULL) | |
895 goto error; | |
896 for (i = 0; i < str_len; i++) | |
897 line_ptr[i] = (char_u)getc(fp); | |
898 line_ptr[i] = NUL; | |
899 } | |
900 line_lnum = (linenr_T)get4c(fp); | |
901 line_colnr = (colnr_T)get4c(fp); | |
902 | |
903 /* Begin general undo data */ | |
904 old_header_seq = get4c(fp); | |
905 new_header_seq = get4c(fp); | |
906 cur_header_seq = get4c(fp); | |
907 num_head = get4c(fp); | |
908 seq_last = get4c(fp); | |
909 seq_cur = get4c(fp); | |
910 seq_time = get4c(fp); | |
911 | |
912 /* uhp_table will store the freshly created undo headers we allocate | |
913 * until we insert them into curbuf. The table remains sorted by the | |
914 * sequence numbers of the headers. */ | |
915 uhp_table = (u_header_T **)U_ALLOC_LINE(num_head * sizeof(u_header_T *)); | |
916 if (uhp_table == NULL) | |
917 goto error; | |
918 vim_memset(uhp_table, 0, num_head * sizeof(u_header_T *)); | |
919 | |
920 c = get2c(fp); | |
921 while (c == UF_HEADER_MAGIC) | |
922 { | |
923 found_first_uep = 0; | |
924 uhp = (u_header_T *)U_ALLOC_LINE((unsigned)sizeof(u_header_T)); | |
925 if (uhp == NULL) | |
926 goto error; | |
927 vim_memset(uhp, 0, sizeof(u_header_T)); | |
928 /* We're not actually trying to store pointers here. We're just storing | |
929 * IDs so we can swizzle them into pointers later - hence the type | |
930 * cast. */ | |
931 uhp->uh_next = (u_header_T *)(long)get4c(fp); | |
932 uhp->uh_prev = (u_header_T *)(long)get4c(fp); | |
933 uhp->uh_alt_next = (u_header_T *)(long)get4c(fp); | |
934 uhp->uh_alt_prev = (u_header_T *)(long)get4c(fp); | |
935 uhp->uh_seq = get4c(fp); | |
936 if (uhp->uh_seq <= 0) | |
937 { | |
938 EMSG2(_("E825: Undo file corruption: invalid uh_seq.: %s"), | |
939 file_name); | |
940 U_FREE_LINE(uhp); | |
941 goto error; | |
942 } | |
943 uhp->uh_walk = 0; | |
944 unserialize_pos(&uhp->uh_cursor, fp); | |
945 #ifdef FEAT_VIRTUALEDIT | |
946 uhp->uh_cursor_vcol = get4c(fp); | |
947 #else | |
948 (void)get4c(fp); | |
949 #endif | |
950 uhp->uh_flags = get2c(fp); | |
951 for (i = 0; i < NMARKS; ++i) | |
952 unserialize_pos(&uhp->uh_namedm[i], fp); | |
953 #ifdef FEAT_VISUAL | |
954 unserialize_visualinfo(&uhp->uh_visual, fp); | |
955 #else | |
956 { | |
957 visualinfo_T info; | |
958 unserialize_visualinfo(&info, fp); | |
959 } | |
960 #endif | |
961 uhp->uh_time = get4c(fp); | |
962 | |
963 /* Unserialize uep list. The first 4 bytes is the length of the | |
964 * entire uep in bytes minus the length of the strings within. | |
965 * -1 is a sentinel value meaning no more ueps.*/ | |
966 last_uep = NULL; | |
967 while ((uep_len = get4c(fp)) != -1) | |
968 { | |
969 uep = (u_entry_T *)U_ALLOC_LINE((unsigned)sizeof(u_entry_T)); | |
970 vim_memset(uep, 0, sizeof(u_entry_T)); | |
971 if (uep == NULL) | |
972 goto error; | |
973 uep->ue_top = get4c(fp); | |
974 uep->ue_bot = get4c(fp); | |
975 uep->ue_lcount = get4c(fp); | |
976 uep->ue_size = get4c(fp); | |
977 uep->ue_next = NULL; | |
978 array = (char_u **)U_ALLOC_LINE( | |
979 (unsigned)(sizeof(char_u *) * uep->ue_size)); | |
980 for (i = 0; i < uep->ue_size; i++) | |
981 { | |
982 line_len = get4c(fp); | |
983 /* U_ALLOC_LINE provides an extra byte for the NUL terminator.*/ | |
984 line = (char_u *)U_ALLOC_LINE( | |
985 (unsigned) (sizeof(char_u) * line_len)); | |
986 if (line == NULL) | |
987 goto error; | |
988 for (j = 0; j < line_len; j++) | |
989 { | |
990 line[j] = getc(fp); | |
991 } | |
992 line[j] = '\0'; | |
993 array[i] = line; | |
994 } | |
995 uep->ue_array = array; | |
996 if (found_first_uep == 0) | |
997 { | |
998 uhp->uh_entry = uep; | |
999 found_first_uep = 1; | |
1000 } | |
1001 else | |
1002 { | |
1003 last_uep->ue_next = uep; | |
1004 } | |
1005 last_uep = uep; | |
1006 } | |
1007 | |
1008 /* Insertion sort the uhp into the table by its uh_seq. This is | |
1009 * required because, while the number of uhps is limited to | |
1010 * num_heads, and the uh_seq order is monotonic with respect to | |
1011 * creation time, the starting uh_seq can be > 0 if any undolevel | |
1012 * culling was done at undofile write time, and there can be uh_seq | |
1013 * gaps in the uhps. | |
1014 */ | |
1015 for (i = num_read_uhps - 1; i >= -1; i--) | |
1016 { | |
1017 /* if i == -1, we've hit the leftmost side of the table, so insert | |
1018 * at uhp_table[0]. */ | |
1019 if (i == -1 || uhp->uh_seq > uhp_table[i]->uh_seq) | |
1020 { | |
1021 /* If we've had to move from the rightmost side of the table, | |
1022 * we have to shift everything to the right by one spot. */ | |
1023 if (i < num_read_uhps - 1) | |
1024 { | |
1025 memmove(uhp_table + i + 2, uhp_table + i + 1, | |
1026 (num_read_uhps - i) * sizeof(u_header_T *)); | |
1027 } | |
1028 uhp_table[i + 1] = uhp; | |
1029 break; | |
1030 } | |
1031 else if (uhp->uh_seq == uhp_table[i]->uh_seq) | |
1032 { | |
1033 EMSG2(_("E826 Undo file corruption: duplicate uh_seq: %s"), | |
1034 file_name); | |
1035 goto error; | |
1036 } | |
1037 } | |
1038 num_read_uhps++; | |
1039 c = get2c(fp); | |
1040 } | |
1041 | |
1042 if (c != UF_END_MAGIC) | |
1043 { | |
1044 EMSG2(_("E827: Undo file corruption; no end marker: %s"), file_name); | |
1045 goto error; | |
1046 } | |
1047 | |
1048 /* We've organized all of the uhps into a table sorted by uh_seq. Now we | |
1049 * iterate through the table and swizzle each sequence number we've | |
1050 * stored in uh_foo into a pointer corresponding to the header with that | |
1051 * sequence number. Then free curbuf's old undo structure, give curbuf | |
1052 * the updated {old,new,cur}head pointers, and then free the table. */ | |
1053 for (i = 0; i < num_head; i++) | |
1054 { | |
1055 uhp = uhp_table[i]; | |
1056 if (uhp == NULL) | |
1057 continue; | |
1058 for (j = 0; j < num_head; j++) | |
1059 { | |
1060 if (uhp_table[j] == NULL) | |
1061 continue; | |
1062 if (uhp_table[j]->uh_seq == (long)uhp->uh_next) | |
1063 uhp->uh_next = uhp_table[j]; | |
1064 if (uhp_table[j]->uh_seq == (long)uhp->uh_prev) | |
1065 uhp->uh_prev = uhp_table[j]; | |
1066 if (uhp_table[j]->uh_seq == (long)uhp->uh_alt_next) | |
1067 uhp->uh_alt_next = uhp_table[j]; | |
1068 if (uhp_table[j]->uh_seq == (long)uhp->uh_alt_prev) | |
1069 uhp->uh_alt_prev = uhp_table[j]; | |
1070 } | |
1071 if (old_header_seq > 0 && old_idx < 0 && uhp->uh_seq == old_header_seq) | |
1072 old_idx = i; | |
1073 if (new_header_seq > 0 && new_idx < 0 && uhp->uh_seq == new_header_seq) | |
1074 new_idx = i; | |
1075 if (cur_header_seq > 0 && cur_idx < 0 && uhp->uh_seq == cur_header_seq) | |
1076 cur_idx = i; | |
1077 } | |
1078 u_blockfree(curbuf); | |
1079 curbuf->b_u_oldhead = old_idx < 0 ? 0 : uhp_table[old_idx]; | |
1080 curbuf->b_u_newhead = new_idx < 0 ? 0 : uhp_table[new_idx]; | |
1081 curbuf->b_u_curhead = cur_idx < 0 ? 0 : uhp_table[cur_idx]; | |
1082 curbuf->b_u_line_ptr = line_ptr; | |
1083 curbuf->b_u_line_lnum = line_lnum; | |
1084 curbuf->b_u_line_colnr = line_colnr; | |
1085 curbuf->b_u_numhead = num_head; | |
1086 curbuf->b_u_seq_last = seq_last; | |
1087 curbuf->b_u_seq_cur = seq_cur; | |
1088 curbuf->b_u_seq_time = seq_time; | |
1089 U_FREE_LINE(uhp_table); | |
1090 #ifdef U_DEBUG | |
1091 u_check(TRUE); | |
1092 #endif | |
1093 if (name != NULL) | |
1094 smsg((char_u *)_("Finished reading undo file %s"), file_name); | |
1095 goto theend; | |
1096 | |
1097 error: | |
1098 if (line_ptr != NULL) | |
1099 U_FREE_LINE(line_ptr); | |
1100 if (uhp_table != NULL) | |
1101 { | |
1102 for (i = 0; i < num_head; i++) | |
1103 { | |
1104 if (uhp_table[i] != NULL) | |
1105 { | |
1106 uep = uhp_table[i]->uh_entry; | |
1107 while (uep != NULL) | |
1108 { | |
1109 nuep = uep->ue_next; | |
1110 u_freeentry(uep, uep->ue_size); | |
1111 uep = nuep; | |
1112 } | |
1113 U_FREE_LINE(uhp_table[i]); | |
1114 } | |
1115 } | |
1116 U_FREE_LINE(uhp_table); | |
1117 } | |
1118 | |
1119 theend: | |
1120 if (fp != NULL) | |
1121 fclose(fp); | |
1122 if (file_name != name) | |
1123 vim_free(file_name); | |
1124 return; | |
1125 } | |
1126 | |
1127 /* | |
1128 * Serialize "uep" to "fp". | |
1129 */ | |
1130 static int | |
1131 serialize_uep(uep, fp) | |
1132 u_entry_T *uep; | |
1133 FILE *fp; | |
1134 { | |
1135 int i; | |
1136 int uep_len; | |
1137 int *entry_lens; | |
1138 | |
1139 if (uep->ue_size > 0) | |
1140 entry_lens = (int *)alloc(uep->ue_size * sizeof(int)); | |
1141 | |
1142 /* Define uep_len to be the size of the entire uep minus the size of its | |
1143 * component strings, in bytes. The sizes of the component strings | |
1144 * are written before each individual string. | |
1145 * We have 4 entries each of 4 bytes, plus ue_size * 4 bytes | |
1146 * of string size information. */ | |
1147 | |
1148 uep_len = uep->ue_size * 4; | |
1149 /* Collect sizing information for later serialization. */ | |
1150 for (i = 0; i < uep->ue_size; i++) | |
1151 { | |
1152 entry_lens[i] = (int)STRLEN(uep->ue_array[i]); | |
1153 uep_len += entry_lens[i]; | |
1154 } | |
1155 put_bytes(fp, (long_u)uep_len, 4); | |
1156 put_bytes(fp, (long_u)uep->ue_top, 4); | |
1157 put_bytes(fp, (long_u)uep->ue_bot, 4); | |
1158 put_bytes(fp, (long_u)uep->ue_lcount, 4); | |
1159 put_bytes(fp, (long_u)uep->ue_size, 4); | |
1160 for (i = 0; i < uep->ue_size; i++) | |
1161 { | |
1162 if (put_bytes(fp, (long_u)entry_lens[i], 4) == FAIL) | |
1163 return FAIL; | |
1164 fprintf(fp, "%s", uep->ue_array[i]); | |
1165 } | |
1166 if (uep->ue_size > 0) | |
1167 vim_free(entry_lens); | |
1168 return OK; | |
1169 } | |
1170 | |
1171 /* | |
1172 * Serialize "pos" to "fp". | |
1173 */ | |
1174 static void | |
1175 serialize_pos(pos, fp) | |
1176 pos_T pos; | |
1177 FILE *fp; | |
1178 { | |
1179 put_bytes(fp, (long_u)pos.lnum, 4); | |
1180 put_bytes(fp, (long_u)pos.col, 4); | |
1181 #ifdef FEAT_VIRTUALEDIT | |
1182 put_bytes(fp, (long_u)pos.coladd, 4); | |
1183 #else | |
1184 put_bytes(fp, (long_u)0, 4); | |
1185 #endif | |
1186 } | |
1187 | |
1188 /* | |
1189 * Serialize "info" to "fp". | |
1190 */ | |
1191 static void | |
1192 serialize_visualinfo(info, fp) | |
1193 visualinfo_T info; | |
1194 FILE *fp; | |
1195 { | |
1196 serialize_pos(info.vi_start, fp); | |
1197 serialize_pos(info.vi_end, fp); | |
1198 put_bytes(fp, (long_u)info.vi_mode, 4); | |
1199 put_bytes(fp, (long_u)info.vi_curswant, 4); | |
1200 } | |
1201 | |
1202 static char_u e_not_open[] = N_("E828: Cannot open undo file for writing: %s"); | |
1203 | |
1204 /* | |
1205 * Write the undo tree in an undo file. | |
1206 * When "name" is not NULL, use it as the name of the undo file. | |
1207 * Otherwise use buf->b_ffname to generate the undo file name. | |
1208 * "buf" must never be null, buf->b_ffname is used to obtain the original file | |
1209 * permissions. | |
1210 * "forceit" is TRUE for ":wundo!", FALSE otherwise. | |
1211 * "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text. | |
1212 */ | |
1213 void | |
1214 u_write_undo(name, forceit, buf, hash) | |
1215 char_u *name; | |
1216 int forceit; | |
1217 buf_T *buf; | |
1218 char_u *hash; | |
1219 { | |
1220 u_header_T *uhp; | |
1221 u_entry_T *uep; | |
1222 char_u *file_name; | |
1223 int str_len, i, uep_len, mark; | |
1224 int fd; | |
1225 FILE *fp = NULL; | |
1226 int perm; | |
1227 int write_ok = FALSE; | |
1228 #ifdef UNIX | |
1229 struct stat st_old; | |
1230 struct stat st_new; | |
1231 #endif | |
1232 | |
1233 if (name == NULL) | |
1234 { | |
1235 file_name = u_get_undo_file_name(buf->b_ffname, FALSE); | |
1236 if (file_name == NULL) | |
1237 return; | |
1238 } | |
1239 else | |
1240 file_name = name; | |
1241 | |
1242 #ifdef UNIX | |
1243 if (mch_stat((char *)buf->b_ffname, &st_old) >= 0) | |
1244 perm = st_old.st_mode; | |
1245 else | |
1246 perm = 0600; | |
1247 #else | |
1248 perm = mch_getperm(buf->b_ffname); | |
1249 if (perm < 0) | |
1250 perm = 0600; | |
1251 #endif | |
1252 /* set file protection same as original file, but strip s-bit */ | |
1253 perm = perm & 0777; | |
1254 | |
1255 /* If the undo file exists, verify that it actually is an undo file, and | |
1256 * delete it. */ | |
1257 if (mch_getperm(file_name) >= 0) | |
1258 { | |
1259 if (name == NULL || !forceit) | |
1260 { | |
1261 /* Check we can read it and it's an undo file. */ | |
1262 fd = mch_open((char *)file_name, O_RDONLY|O_EXTRA, 0); | |
1263 if (fd < 0) | |
1264 { | |
1265 if (name != NULL || p_verbose > 0) | |
1266 smsg((char_u *)_("Will not overwrite with undo file, cannot read: %s"), | |
1267 file_name); | |
1268 goto theend; | |
1269 } | |
1270 else | |
1271 { | |
1272 char_u buf[2]; | |
1273 | |
1274 vim_read(fd, buf, 2); | |
1275 close(fd); | |
1276 if ((buf[0] << 8) + buf[1] != UF_START_MAGIC) | |
1277 { | |
1278 if (name != NULL || p_verbose > 0) | |
1279 smsg((char_u *)_("Will not overwrite, this is not an undo file: %s"), | |
1280 file_name); | |
1281 goto theend; | |
1282 } | |
1283 } | |
1284 } | |
1285 mch_remove(file_name); | |
1286 } | |
1287 | |
1288 fd = mch_open((char *)file_name, | |
1289 O_CREAT|O_EXTRA|O_WRONLY|O_EXCL|O_NOFOLLOW, perm); | |
1290 (void)mch_setperm(file_name, perm); | |
1291 if (fd < 0) | |
1292 { | |
1293 EMSG2(_(e_not_open), file_name); | |
1294 goto theend; | |
1295 } | |
1296 if (p_verbose > 0) | |
1297 smsg((char_u *)_("Writing undo file: %s"), file_name); | |
1298 | |
1299 #ifdef UNIX | |
1300 /* | |
1301 * Try to set the group of the undo file same as the original file. If | |
1302 * this fails, set the protection bits for the group same as the | |
1303 * protection bits for others. | |
1304 */ | |
1305 if (mch_stat((char *)file_name, &st_new) >= 0 | |
1306 && st_new.st_gid != st_old.st_gid | |
1307 # ifdef HAVE_FCHOWN /* sequent-ptx lacks fchown() */ | |
1308 && fchown(fd, (uid_t)-1, st_old.st_gid) != 0 | |
1309 # endif | |
1310 ) | |
1311 mch_setperm(file_name, (perm & 0707) | ((perm & 07) << 3)); | |
1312 # ifdef HAVE_SELINUX | |
1313 mch_copy_sec(buf->b_ffname, file_name); | |
1314 # endif | |
1315 #endif | |
1316 | |
1317 fp = fdopen(fd, "w"); | |
1318 if (fp == NULL) | |
1319 { | |
1320 EMSG2(_(e_not_open), file_name); | |
1321 close(fd); | |
1322 mch_remove(file_name); | |
1323 goto theend; | |
1324 } | |
1325 | |
1326 /* Start writing, first overall file information */ | |
1327 put_bytes(fp, (long_u)UF_START_MAGIC, 2); | |
1328 put_bytes(fp, (long_u)UF_VERSION, 2); | |
1329 | |
1330 /* Write a hash of the buffer text, so that we can verify it is still the | |
1331 * same when reading the buffer text. */ | |
1332 if (fwrite(hash, (size_t)UNDO_HASH_SIZE, (size_t)1, fp) != 1) | |
1333 goto write_error; | |
1334 put_bytes(fp, (long_u)buf->b_ml.ml_line_count, 4); | |
1335 | |
1336 /* Begin undo data for U */ | |
1337 str_len = buf->b_u_line_ptr != NULL ? STRLEN(buf->b_u_line_ptr) : 0; | |
1338 put_bytes(fp, (long_u)str_len, 4); | |
1339 if (str_len > 0 && fwrite(buf->b_u_line_ptr, (size_t)str_len, | |
1340 (size_t)1, fp) != 1) | |
1341 goto write_error; | |
1342 | |
1343 put_bytes(fp, (long_u)buf->b_u_line_lnum, 4); | |
1344 put_bytes(fp, (long_u)buf->b_u_line_colnr, 4); | |
1345 | |
1346 /* Begin general undo data */ | |
1347 uhp = buf->b_u_oldhead; | |
1348 put_bytes(fp, (long_u)(uhp != NULL ? uhp->uh_seq : 0), 4); | |
1349 | |
1350 uhp = buf->b_u_newhead; | |
1351 put_bytes(fp, (long_u)(uhp != NULL ? uhp->uh_seq : 0), 4); | |
1352 | |
1353 uhp = buf->b_u_curhead; | |
1354 put_bytes(fp, (long_u)(uhp != NULL ? uhp->uh_seq : 0), 4); | |
1355 | |
1356 put_bytes(fp, (long_u)buf->b_u_numhead, 4); | |
1357 put_bytes(fp, (long_u)buf->b_u_seq_last, 4); | |
1358 put_bytes(fp, (long_u)buf->b_u_seq_cur, 4); | |
1359 put_bytes(fp, (long_u)buf->b_u_seq_time, 4); | |
1360 | |
1361 /* Iteratively serialize UHPs and their UEPs from the top down. */ | |
1362 mark = ++lastmark; | |
1363 uhp = buf->b_u_oldhead; | |
1364 while (uhp != NULL) | |
1365 { | |
1366 /* Serialize current UHP if we haven't seen it */ | |
1367 if (uhp->uh_walk != mark) | |
1368 { | |
1369 if (put_bytes(fp, (long_u)UF_HEADER_MAGIC, 2) == FAIL) | |
1370 goto write_error; | |
1371 | |
1372 put_bytes(fp, (long_u)((uhp->uh_next != NULL) | |
1373 ? uhp->uh_next->uh_seq : 0), 4); | |
1374 put_bytes(fp, (long_u)((uhp->uh_prev != NULL) | |
1375 ? uhp->uh_prev->uh_seq : 0), 4); | |
1376 put_bytes(fp, (long_u)((uhp->uh_alt_next != NULL) | |
1377 ? uhp->uh_alt_next->uh_seq : 0), 4); | |
1378 put_bytes(fp, (long_u)((uhp->uh_alt_prev != NULL) | |
1379 ? uhp->uh_alt_prev->uh_seq : 0), 4); | |
1380 put_bytes(fp, uhp->uh_seq, 4); | |
1381 serialize_pos(uhp->uh_cursor, fp); | |
1382 #ifdef FEAT_VIRTUALEDIT | |
1383 put_bytes(fp, (long_u)uhp->uh_cursor_vcol, 4); | |
1384 #else | |
1385 put_bytes(fp, (long_u)0, 4); | |
1386 #endif | |
1387 put_bytes(fp, (long_u)uhp->uh_flags, 2); | |
1388 /* Assume NMARKS will stay the same. */ | |
1389 for (i = 0; i < NMARKS; ++i) | |
1390 { | |
1391 serialize_pos(uhp->uh_namedm[i], fp); | |
1392 } | |
1393 #ifdef FEAT_VISUAL | |
1394 serialize_visualinfo(uhp->uh_visual, fp); | |
1395 #endif | |
1396 put_bytes(fp, (long_u)uhp->uh_time, 4); | |
1397 | |
1398 uep = uhp->uh_entry; | |
1399 while (uep != NULL) | |
1400 { | |
1401 if (serialize_uep(uep, fp) == FAIL) | |
1402 goto write_error; | |
1403 uep = uep->ue_next; | |
1404 } | |
1405 /* Sentinel value: no more ueps */ | |
1406 uep_len = -1; | |
1407 put_bytes(fp, (long_u)uep_len, 4); | |
1408 uhp->uh_walk = mark; | |
1409 } | |
1410 | |
1411 /* Now walk through the tree - algorithm from undo_time */ | |
1412 if (uhp->uh_prev != NULL && uhp->uh_prev->uh_walk != mark) | |
1413 uhp = uhp->uh_prev; | |
1414 else if (uhp->uh_alt_next != NULL && uhp->uh_alt_next->uh_walk != mark) | |
1415 uhp = uhp->uh_alt_next; | |
1416 else if (uhp->uh_next != NULL && uhp->uh_alt_prev == NULL | |
1417 && uhp->uh_next->uh_walk != mark) | |
1418 uhp = uhp->uh_next; | |
1419 else if (uhp->uh_alt_prev != NULL) | |
1420 uhp = uhp->uh_alt_prev; | |
1421 else | |
1422 uhp = uhp->uh_next; | |
1423 } | |
1424 | |
1425 if (put_bytes(fp, (long_u)UF_END_MAGIC, 2) == OK) | |
1426 write_ok = TRUE; | |
1427 | |
1428 write_error: | |
1429 fclose(fp); | |
1430 if (!write_ok) | |
1431 EMSG2(_("E829: write error in undo file: %s"), file_name); | |
1432 | |
1433 #if defined(MACOS_CLASSIC) || defined(WIN3264) | |
1434 (void)mch_copy_file_attribute(buf->b_ffname, file_name); | |
1435 #endif | |
1436 #ifdef HAVE_ACL | |
1437 { | |
1438 vim_acl_T acl; | |
1439 | |
1440 /* For systems that support ACL: get the ACL from the original file. */ | |
1441 acl = mch_get_acl(buf->b_ffname); | |
1442 mch_set_acl(file_name, acl); | |
1443 } | |
1444 #endif | |
1445 | |
1446 theend: | |
1447 if (file_name != name) | |
1448 vim_free(file_name); | |
1449 } | |
1450 | |
1451 #endif /* FEAT_PERSISTENT_UNDO */ | |
1452 | |
654 | 1453 |
655 /* | 1454 /* |
656 * If 'cpoptions' contains 'u': Undo the previous undo or redo (vi compatible). | 1455 * If 'cpoptions' contains 'u': Undo the previous undo or redo (vi compatible). |
657 * If 'cpoptions' does not contain 'u': Always undo. | 1456 * If 'cpoptions' does not contain 'u': Always undo. |
658 */ | 1457 */ |
754 curbuf->b_u_curhead = curbuf->b_u_curhead->uh_prev; | 1553 curbuf->b_u_curhead = curbuf->b_u_curhead->uh_prev; |
755 } | 1554 } |
756 } | 1555 } |
757 u_undo_end(undo_undoes, FALSE); | 1556 u_undo_end(undo_undoes, FALSE); |
758 } | 1557 } |
759 | |
760 static int lastmark = 0; | |
761 | 1558 |
762 /* | 1559 /* |
763 * Undo or redo over the timeline. | 1560 * Undo or redo over the timeline. |
764 * When "step" is negative go back in time, otherwise goes forward in time. | 1561 * When "step" is negative go back in time, otherwise goes forward in time. |
765 * When "sec" is FALSE make "step" steps, when "sec" is TRUE use "step" as | 1562 * When "sec" is FALSE make "step" steps, when "sec" is TRUE use "step" as |
925 if (uhp != NULL) /* found it */ | 1722 if (uhp != NULL) /* found it */ |
926 break; | 1723 break; |
927 | 1724 |
928 if (absolute) | 1725 if (absolute) |
929 { | 1726 { |
930 EMSGN(_("Undo number %ld not found"), step); | 1727 EMSGN(_("E830: Undo number %ld not found"), step); |
931 return; | 1728 return; |
932 } | 1729 } |
933 | 1730 |
934 if (closest == closest_start) | 1731 if (closest == closest_start) |
935 { | 1732 { |