changeset 10889:5780bd3a5a7e v8.0.0334

patch 8.0.0334: can't access b:changedtick from a dict reference commit https://github.com/vim/vim/commit/79518e2ace5fce7b9c49060e462a6e935dba0a84 Author: Bram Moolenaar <Bram@vim.org> Date: Fri Feb 17 16:31:35 2017 +0100 patch 8.0.0334: can't access b:changedtick from a dict reference Problem: Can't access b:changedtick from a dict reference. Solution: Make changedtick a member of the b: dict. (inspired by neovim #6112)
author Christian Brabandt <cb@256bit.org>
date Fri, 17 Feb 2017 16:45:05 +0100
parents 46008b393de1
children d2c0687e460b
files src/Makefile src/buffer.c src/edit.c src/eval.c src/evalfunc.c src/ex_docmd.c src/fileio.c src/globals.h src/main.c src/memline.c src/misc1.c src/proto/eval.pro src/structs.h src/syntax.c src/testdir/test91.in src/testdir/test91.ok src/testdir/test_alot.vim src/testdir/test_changedtick.vim src/testdir/test_functions.vim src/version.c
diffstat 20 files changed, 205 insertions(+), 183 deletions(-) [+]
line wrap: on
line diff
--- a/src/Makefile
+++ b/src/Makefile
@@ -2099,6 +2099,7 @@ test_arglist \
 	test_cdo \
 	test_channel \
 	test_charsearch \
+	test_changedtick \
 	test_cmdline \
 	test_command_count \
 	test_crypt \
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -832,6 +832,7 @@ free_buffer(buf_T *buf)
     free_buffer_stuff(buf, TRUE);
 #ifdef FEAT_EVAL
     unref_var_dict(buf->b_vars);
+    buf->b_changedtick = &buf->b_ct_val;
 #endif
 #ifdef FEAT_LUA
     lua_buffer_free(buf);
@@ -873,6 +874,29 @@ free_buffer(buf_T *buf)
 }
 
 /*
+ * Initializes buf->b_changedtick.
+ */
+    static void
+init_changedtick(buf_T *buf)
+{
+#ifdef FEAT_EVAL
+    dictitem_T *di = dictitem_alloc((char_u *)"changedtick");
+
+    if (di != NULL)
+    {
+	di->di_flags |= DI_FLAGS_LOCK | DI_FLAGS_FIX | DI_FLAGS_RO;
+	di->di_tv.v_type = VAR_NUMBER;
+	di->di_tv.v_lock = VAR_FIXED;
+	di->di_tv.vval.v_number = 0;
+	dict_add(buf->b_vars, di);
+	buf->b_changedtick = &di->di_tv.vval.v_number;
+    }
+    else
+#endif
+	buf->b_changedtick = &buf->b_ct_val;
+}
+
+/*
  * Free stuff in the buffer for ":bdel" and when wiping out the buffer.
  */
     static void
@@ -889,8 +913,14 @@ free_buffer_stuff(
 #endif
     }
 #ifdef FEAT_EVAL
-    vars_clear(&buf->b_vars->dv_hashtab); /* free all internal variables */
-    hash_init(&buf->b_vars->dv_hashtab);
+    {
+	varnumber_T tick = *buf->b_changedtick;
+
+	vars_clear(&buf->b_vars->dv_hashtab); /* free all buffer variables */
+	hash_init(&buf->b_vars->dv_hashtab);
+	init_changedtick(buf);
+	*buf->b_changedtick = tick;
+    }
 #endif
 #ifdef FEAT_USR_CMDS
     uc_clear(&buf->b_ucmds);		/* clear local user commands */
@@ -1979,6 +2009,7 @@ buflist_new(
 	}
 	init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE);
 #endif
+	init_changedtick(buf);
     }
 
     if (ffname != NULL)
--- a/src/edit.c
+++ b/src/edit.c
@@ -1668,7 +1668,7 @@ ins_redraw(
 #ifdef FEAT_AUTOCMD
     /* Trigger TextChangedI if b_changedtick differs. */
     if (ready && has_textchangedI()
-	    && last_changedtick != curbuf->b_changedtick
+	    && last_changedtick != *curbuf->b_changedtick
 # ifdef FEAT_INS_EXPAND
 	    && !pum_visible()
 # endif
@@ -1677,7 +1677,7 @@ ins_redraw(
 	if (last_changedtick_buf == curbuf)
 	    apply_autocmds(EVENT_TEXTCHANGEDI, NULL, NULL, FALSE, curbuf);
 	last_changedtick_buf = curbuf;
-	last_changedtick = curbuf->b_changedtick;
+	last_changedtick = *curbuf->b_changedtick;
     }
 #endif
 
--- a/src/eval.c
+++ b/src/eval.c
@@ -1451,14 +1451,8 @@ list_glob_vars(int *first)
     static void
 list_buf_vars(int *first)
 {
-    char_u	numbuf[NUMBUFLEN];
-
     list_hashtable_vars(&curbuf->b_vars->dv_hashtab, (char_u *)"b:",
 								 TRUE, first);
-
-    sprintf((char *)numbuf, "%ld", (long)curbuf->b_changedtick);
-    list_one_var_a((char_u *)"b:", (char_u *)"changedtick", VAR_NUMBER,
-							       numbuf, first);
 }
 
 /*
@@ -1806,20 +1800,6 @@ ex_let_one(
 }
 
 /*
- * If "arg" is equal to "b:changedtick" give an error and return TRUE.
- */
-    int
-check_changedtick(char_u *arg)
-{
-    if (STRNCMP(arg, "b:changedtick", 13) == 0 && !eval_isnamec(arg[13]))
-    {
-	EMSG2(_(e_readonlyvar), arg);
-	return TRUE;
-    }
-    return FALSE;
-}
-
-/*
  * Get an lval: variable, Dict item or List item that can be assigned a value
  * to: "name", "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]",
  * "name.key", "name.key[expr]" etc.
@@ -2208,32 +2188,29 @@ set_var_lval(
 
     if (lp->ll_tv == NULL)
     {
-	if (!check_changedtick(lp->ll_name))
-	{
-	    cc = *endp;
-	    *endp = NUL;
-	    if (op != NULL && *op != '=')
-	    {
-		typval_T tv;
-
-		/* handle +=, -= and .= */
-		di = NULL;
-		if (get_var_tv(lp->ll_name, (int)STRLEN(lp->ll_name),
-						 &tv, &di, TRUE, FALSE) == OK)
-		{
-		    if ((di == NULL
-			   || (!var_check_ro(di->di_flags, lp->ll_name, FALSE)
-			      && !tv_check_lock(di->di_tv.v_lock, lp->ll_name,
-								      FALSE)))
-			    && tv_op(&tv, rettv, op) == OK)
-			set_var(lp->ll_name, &tv, FALSE);
-		    clear_tv(&tv);
-		}
-	    }
-	    else
-		set_var(lp->ll_name, rettv, copy);
-	    *endp = cc;
-	}
+	cc = *endp;
+	*endp = NUL;
+	if (op != NULL && *op != '=')
+	{
+	    typval_T tv;
+
+	    /* handle +=, -= and .= */
+	    di = NULL;
+	    if (get_var_tv(lp->ll_name, (int)STRLEN(lp->ll_name),
+					     &tv, &di, TRUE, FALSE) == OK)
+	    {
+		if ((di == NULL
+		       || (!var_check_ro(di->di_flags, lp->ll_name, FALSE)
+			  && !tv_check_lock(di->di_tv.v_lock, lp->ll_name,
+								  FALSE)))
+			&& tv_op(&tv, rettv, op) == OK)
+		    set_var(lp->ll_name, &tv, FALSE);
+		clear_tv(&tv);
+	    }
+	}
+	else
+	    set_var(lp->ll_name, rettv, copy);
+	*endp = cc;
     }
     else if (tv_check_lock(lp->ll_newkey == NULL
 		? lp->ll_tv->v_lock
@@ -2776,9 +2753,7 @@ do_unlet_var(
 	*name_end = NUL;
 
 	/* Normal name or expanded name. */
-	if (check_changedtick(lp->ll_name))
-	    ret = FAIL;
-	else if (do_unlet(lp->ll_name, forceit) == FAIL)
+	if (do_unlet(lp->ll_name, forceit) == FAIL)
 	    ret = FAIL;
 	*name_end = cc;
     }
@@ -2904,21 +2879,16 @@ do_lock_var(
 	*name_end = NUL;
 
 	/* Normal name or expanded name. */
-	if (check_changedtick(lp->ll_name))
+	di = find_var(lp->ll_name, NULL, TRUE);
+	if (di == NULL)
 	    ret = FAIL;
 	else
 	{
-	    di = find_var(lp->ll_name, NULL, TRUE);
-	    if (di == NULL)
-		ret = FAIL;
+	    if (lock)
+		di->di_flags |= DI_FLAGS_LOCK;
 	    else
-	    {
-		if (lock)
-		    di->di_flags |= DI_FLAGS_LOCK;
-		else
-		    di->di_flags &= ~DI_FLAGS_LOCK;
-		item_lock(&di->di_tv, deep, lock);
-	    }
+		di->di_flags &= ~DI_FLAGS_LOCK;
+	    item_lock(&di->di_tv, deep, lock);
 	}
 	*name_end = cc;
     }
@@ -3139,11 +3109,6 @@ get_user_var_name(expand_T *xp, int idx)
 	    ++hi;
 	return cat_prefix_varname('b', hi->hi_key);
     }
-    if (bdone == ht->ht_used)
-    {
-	++bdone;
-	return (char_u *)"b:changedtick";
-    }
 
     /* w: variables */
     ht = &curwin->w_vars->dv_hashtab;
@@ -6815,7 +6780,6 @@ get_var_tv(
 {
     int		ret = OK;
     typval_T	*tv = NULL;
-    typval_T	atv;
     dictitem_T	*v;
     int		cc;
 
@@ -6824,27 +6788,14 @@ get_var_tv(
     name[len] = NUL;
 
     /*
-     * Check for "b:changedtick".
-     */
-    if (STRCMP(name, "b:changedtick") == 0)
-    {
-	atv.v_type = VAR_NUMBER;
-	atv.vval.v_number = curbuf->b_changedtick;
-	tv = &atv;
-    }
-
-    /*
      * Check for user-defined variables.
      */
-    else
-    {
-	v = find_var(name, NULL, no_autoload);
-	if (v != NULL)
-	{
-	    tv = &v->di_tv;
-	    if (dip != NULL)
-		*dip = v;
-	}
+    v = find_var(name, NULL, no_autoload);
+    if (v != NULL)
+    {
+	tv = &v->di_tv;
+	if (dip != NULL)
+	    *dip = v;
     }
 
     if (tv == NULL)
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -2539,7 +2539,7 @@ f_diff_hlID(typval_T *argvars UNUSED, ty
 #ifdef FEAT_DIFF
     linenr_T		lnum = get_tv_lnum(argvars);
     static linenr_T	prev_lnum = 0;
-    static int		changedtick = 0;
+    static varnumber_T	changedtick = 0;
     static int		fnum = 0;
     static int		change_start = 0;
     static int		change_end = 0;
@@ -2550,7 +2550,7 @@ f_diff_hlID(typval_T *argvars UNUSED, ty
     if (lnum < 0)	/* ignore type error in {lnum} arg */
 	lnum = 0;
     if (lnum != prev_lnum
-	    || changedtick != curbuf->b_changedtick
+	    || changedtick != *curbuf->b_changedtick
 	    || fnum != curbuf->b_fnum)
     {
 	/* New line, buffer, change: need to get the values. */
@@ -2572,7 +2572,7 @@ f_diff_hlID(typval_T *argvars UNUSED, ty
 	else
 	    hlID = (hlf_T)0;
 	prev_lnum = lnum;
-	changedtick = curbuf->b_changedtick;
+	changedtick = *curbuf->b_changedtick;
 	fnum = curbuf->b_fnum;
     }
 
@@ -3957,7 +3957,7 @@ get_buffer_info(buf_T *buf)
     dict_add_nr_str(dict, "loaded", buf->b_ml.ml_mfp != NULL, NULL);
     dict_add_nr_str(dict, "listed", buf->b_p_bl, NULL);
     dict_add_nr_str(dict, "changed", bufIsChanged(buf), NULL);
-    dict_add_nr_str(dict, "changedtick", buf->b_changedtick, NULL);
+    dict_add_nr_str(dict, "changedtick", *buf->b_changedtick, NULL);
     dict_add_nr_str(dict, "hidden",
 		    buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0,
 		    NULL);
@@ -4190,12 +4190,6 @@ f_getbufvar(typval_T *argvars, typval_T 
 		/* buffer-local-option */
 		done = TRUE;
 	}
-	else if (STRCMP(varname, "changedtick") == 0)
-	{
-	    rettv->v_type = VAR_NUMBER;
-	    rettv->vval.v_number = curbuf->b_changedtick;
-	    done = TRUE;
-	}
 	else
 	{
 	    /* Look up the variable. */
@@ -6576,21 +6570,16 @@ f_islocked(typval_T *argvars, typval_T *
 	{
 	    if (lv.ll_tv == NULL)
 	    {
-		if (check_changedtick(lv.ll_name))
-		    rettv->vval.v_number = 1;	    /* always locked */
-		else
+		di = find_var(lv.ll_name, NULL, TRUE);
+		if (di != NULL)
 		{
-		    di = find_var(lv.ll_name, NULL, TRUE);
-		    if (di != NULL)
-		    {
-			/* Consider a variable locked when:
-			 * 1. the variable itself is locked
-			 * 2. the value of the variable is locked.
-			 * 3. the List or Dict value is locked.
-			 */
-			rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK)
-						  || tv_islocked(&di->di_tv));
-		    }
+		    /* Consider a variable locked when:
+		     * 1. the variable itself is locked
+		     * 2. the value of the variable is locked.
+		     * 3. the List or Dict value is locked.
+		     */
+		    rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK)
+						   || tv_islocked(&di->di_tv));
 		}
 	    }
 	    else if (lv.ll_range)
@@ -11551,8 +11540,8 @@ f_submatch(typval_T *argvars, typval_T *
 	return;
     if (no < 0 || no >= NSUBEXP)
     {
-        EMSGN(_("E935: invalid submatch number: %d"), no);
-        return;
+	EMSGN(_("E935: invalid submatch number: %d"), no);
+	return;
     }
     if (argvars[1].v_type != VAR_UNKNOWN)
 	retList = (int)get_tv_number_chk(&argvars[1], &error);
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -626,7 +626,7 @@ do_exmode(
     int		save_msg_scroll;
     int		prev_msg_row;
     linenr_T	prev_line;
-    int		changedtick;
+    varnumber_T	changedtick;
 
     if (improved)
 	exmode_active = EXMODE_VIM;
@@ -660,7 +660,7 @@ do_exmode(
 	need_wait_return = FALSE;
 	ex_pressedreturn = FALSE;
 	ex_no_reprint = FALSE;
-	changedtick = curbuf->b_changedtick;
+	changedtick = *curbuf->b_changedtick;
 	prev_msg_row = msg_row;
 	prev_line = curwin->w_cursor.lnum;
 	if (improved)
@@ -673,7 +673,7 @@ do_exmode(
 	lines_left = Rows - 1;
 
 	if ((prev_line != curwin->w_cursor.lnum
-		    || changedtick != curbuf->b_changedtick) && !ex_no_reprint)
+		   || changedtick != *curbuf->b_changedtick) && !ex_no_reprint)
 	{
 	    if (curbuf->b_ml.ml_flags & ML_EMPTY)
 		EMSG(_(e_emptybuf));
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -4926,9 +4926,9 @@ restore_backup:
 #ifdef FEAT_AUTOCMD
 	/* buf->b_changedtick is always incremented in unchanged() but that
 	 * should not trigger a TextChanged event. */
-	if (last_changedtick + 1 == buf->b_changedtick
+	if (last_changedtick + 1 == *buf->b_changedtick
 					       && last_changedtick_buf == buf)
-	    last_changedtick = buf->b_changedtick;
+	    last_changedtick = *buf->b_changedtick;
 #endif
 	u_unchanged(buf);
 	u_update_save_nr(buf);
--- a/src/globals.h
+++ b/src/globals.h
@@ -1088,7 +1088,7 @@ EXTERN pos_T	last_cursormoved	      /* f
 			= INIT_POS_T(0, 0, 0)
 # endif
 			;
-EXTERN int	last_changedtick INIT(= 0);   /* for TextChanged event */
+EXTERN varnumber_T last_changedtick INIT(= 0);   /* for TextChanged event */
 EXTERN buf_T	*last_changedtick_buf INIT(= NULL);
 #endif
 
--- a/src/main.c
+++ b/src/main.c
@@ -1164,13 +1164,13 @@ main_loop(
 #ifdef FEAT_AUTOCMD
 	    /* Trigger TextChanged if b_changedtick differs. */
 	    if (!finish_op && has_textchanged()
-		    && last_changedtick != curbuf->b_changedtick)
+		    && last_changedtick != *curbuf->b_changedtick)
 	    {
 		if (last_changedtick_buf == curbuf)
 		    apply_autocmds(EVENT_TEXTCHANGED, NULL, NULL,
 							       FALSE, curbuf);
 		last_changedtick_buf = curbuf;
-		last_changedtick = curbuf->b_changedtick;
+		last_changedtick = *curbuf->b_changedtick;
 	    }
 #endif
 
@@ -1388,11 +1388,11 @@ getout(int exitval)
 		    /* Autocmd must have close the buffer already, skip. */
 		    continue;
 		buf = wp->w_buffer;
-		if (buf->b_changedtick != -1)
+		if (buf->b_ct_val != -1)
 		{
 		    apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname,
 						    buf->b_fname, FALSE, buf);
-		    buf->b_changedtick = -1;  /* note that we did it already */
+		    buf->b_ct_val = -1;  /* note that we did it already */
 		    /* start all over, autocommands may mess up the lists */
 		    next_tp = first_tabpage;
 		    break;
--- a/src/memline.c
+++ b/src/memline.c
@@ -1148,11 +1148,11 @@ ml_recover(void)
     len = (int)STRLEN(fname);
     if (len >= 4 &&
 #if defined(VMS)
-	    STRNICMP(fname + len - 4, "_s" , 2)
+	    STRNICMP(fname + len - 4, "_s", 2)
 #else
-	    STRNICMP(fname + len - 4, ".s" , 2)
+	    STRNICMP(fname + len - 4, ".s", 2)
 #endif
-		== 0
+						== 0
 		&& vim_strchr((char_u *)"UVWuvw", fname[len - 2]) != NULL
 		&& ASCII_ISALPHA(fname[len - 1]))
     {
@@ -1649,7 +1649,7 @@ ml_recover(void)
 	if (!(curbuf->b_ml.ml_line_count == 2 && *ml_get(1) == NUL))
 	{
 	    changed_int();
-	    ++curbuf->b_changedtick;
+	    ++*curbuf->b_changedtick;
 	}
     }
     else
@@ -1663,7 +1663,7 @@ ml_recover(void)
 	    if (i != 0)
 	    {
 		changed_int();
-		++curbuf->b_changedtick;
+		++*curbuf->b_changedtick;
 		break;
 	    }
 	}
--- a/src/misc1.c
+++ b/src/misc1.c
@@ -492,7 +492,7 @@ get_breakindent_win(
     static int	    prev_indent = 0;  /* cached indent value */
     static long	    prev_ts     = 0L; /* cached tabstop value */
     static char_u   *prev_line = NULL; /* cached pointer to line */
-    static int	    prev_tick = 0;   /* changedtick of cached value */
+    static varnumber_T prev_tick = 0;   /* changedtick of cached value */
     int		    bri = 0;
     /* window width minus window margin space, i.e. what rests for text */
     const int	    eff_wwidth = W_WIDTH(wp)
@@ -502,11 +502,11 @@ get_breakindent_win(
 
     /* used cached indent, unless pointer or 'tabstop' changed */
     if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts
-				  || prev_tick != wp->w_buffer->b_changedtick)
+				  || prev_tick != *wp->w_buffer->b_changedtick)
     {
 	prev_line = line;
 	prev_ts = wp->w_buffer->b_p_ts;
-	prev_tick = wp->w_buffer->b_changedtick;
+	prev_tick = *wp->w_buffer->b_changedtick;
 	prev_indent = get_indent_str(line,
 				     (int)wp->w_buffer->b_p_ts, wp->w_p_list);
     }
@@ -2768,7 +2768,7 @@ changed(void)
 	}
 	changed_int();
     }
-    ++curbuf->b_changedtick;
+    ++*curbuf->b_changedtick;
 }
 
 /*
@@ -3195,7 +3195,7 @@ unchanged(
 	need_maketitle = TRUE;	    /* set window title later */
 #endif
     }
-    ++buf->b_changedtick;
+    ++*buf->b_changedtick;
 #ifdef FEAT_NETBEANS_INTG
     netbeans_unmodified(buf);
 #endif
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -25,7 +25,6 @@ void *call_func_retlist(char_u *func, in
 int eval_foldexpr(char_u *arg, int *cp);
 void ex_let(exarg_T *eap);
 void list_hashtable_vars(hashtab_T *ht, char_u *prefix, int empty, int *first);
-int check_changedtick(char_u *arg);
 char_u *get_lval(char_u *name, typval_T *rettv, lval_T *lp, int unlet, int skip, int flags, int fne_flags);
 void clear_lval(lval_T *lp);
 void *eval_for_line(char_u *arg, int *errp, char_u **nextcmdp, int skip);
--- a/src/structs.h
+++ b/src/structs.h
@@ -1916,7 +1916,9 @@ struct file_buffer
 
     int		b_changed;	/* 'modified': Set to TRUE if something in the
 				   file has been changed and not written out. */
-    int		b_changedtick;	/* incremented for each change, also for undo */
+    varnumber_T	*b_changedtick;	/* points into b:changedtick or b_ct_val;
+				   incremented for each change, also for undo */
+    varnumber_T b_ct_val;	/* fallback for b:changedtick */
 
     int		b_saving;	/* Set to TRUE if we are in the middle of
 				   saving the buffer. */
--- a/src/syntax.c
+++ b/src/syntax.c
@@ -503,7 +503,7 @@ syntax_start(win_T *wp, linenr_T lnum)
     linenr_T	parsed_lnum;
     linenr_T	first_stored;
     int		dist;
-    static int	changedtick = 0;	/* remember the last change ID */
+    static varnumber_T changedtick = 0;	/* remember the last change ID */
 
 #ifdef FEAT_CONCEAL
     current_sub_char = NUL;
@@ -516,13 +516,13 @@ syntax_start(win_T *wp, linenr_T lnum)
      */
     if (syn_block != wp->w_s
 	    || syn_buf != wp->w_buffer
-	    || changedtick != syn_buf->b_changedtick)
+	    || changedtick != *syn_buf->b_changedtick)
     {
 	invalidate_current_state();
 	syn_buf = wp->w_buffer;
 	syn_block = wp->w_s;
     }
-    changedtick = syn_buf->b_changedtick;
+    changedtick = *syn_buf->b_changedtick;
     syn_win = wp;
 
     /*
--- a/src/testdir/test91.in
+++ b/src/testdir/test91.in
@@ -1,4 +1,4 @@
-Tests for getbufvar(), getwinvar(), gettabvar() and gettabwinvar().
+Tests for getwinvar(), gettabvar() and gettabwinvar().
 vim: set ft=vim :
 
 STARTTEST
@@ -10,34 +10,7 @@ STARTTEST
 :let t:testvar='abcd'
 :$put =string(gettabvar(1,'testvar'))
 :$put =string(gettabvar(1,'testvar'))
-:" Test for getbufvar()
-:let b:var_num = '1234'
-:let def_num = '5678'
-:$put =string(getbufvar(1, 'var_num'))
-:$put =string(getbufvar(1, 'var_num', def_num))
-:$put =string(getbufvar(1, ''))
-:$put =string(getbufvar(1, '', def_num))
-:unlet b:var_num
-:$put =string(getbufvar(1, 'var_num', def_num))
-:$put =string(getbufvar(1, ''))
-:$put =string(getbufvar(1, '', def_num))
-:$put =string(getbufvar(9, ''))
-:$put =string(getbufvar(9, '', def_num))
-:unlet def_num
-:$put =string(getbufvar(1, '&autoindent'))
-:$put =string(getbufvar(1, '&autoindent', 1))
 :"
-:" Open new window with forced option values
-:set fileformats=unix,dos
-:new ++ff=dos ++bin ++enc=iso-8859-2
-:let otherff = getbufvar(bufnr('%'), '&fileformat')
-:let otherbin = getbufvar(bufnr('%'), '&bin')
-:let otherfenc = getbufvar(bufnr('%'), '&fenc')
-:close
-:$put =otherff
-:$put =string(otherbin)
-:$put =otherfenc
-:unlet otherff otherbin otherfenc
 :" test for getwinvar()
 :let w:var_str = "Dance"
 :let def_str = "Chance"
--- a/src/testdir/test91.ok
+++ b/src/testdir/test91.ok
@@ -1,20 +1,6 @@
 start:
 'abcd'
 'abcd'
-'1234'
-'1234'
-{'var_num': '1234'}
-{'var_num': '1234'}
-'5678'
-{}
-{}
-''
-'5678'
-0
-0
-dos
-1
-iso-8859-2
 'Dance'
 'Dance'
 {'var_str': 'Dance'}
--- a/src/testdir/test_alot.vim
+++ b/src/testdir/test_alot.vim
@@ -3,10 +3,11 @@
 
 source test_assign.vim
 source test_autocmd.vim
+source test_changedtick.vim
 source test_cursor_func.vim
 source test_delete.vim
+source test_ex_undo.vim
 source test_execute_func.vim
-source test_ex_undo.vim
 source test_expand.vim
 source test_expr.vim
 source test_expand_dllpath.vim
new file mode 100644
--- /dev/null
+++ b/src/testdir/test_changedtick.vim
@@ -0,0 +1,45 @@
+" Tests for b:changedtick
+
+func Test_changedtick_increments()
+  new
+  " New buffer has an empty line, tick starts at 2.
+  let expected = 2
+  call assert_equal(expected, b:changedtick)
+  call assert_equal(expected, b:['changedtick'])
+  call setline(1, 'hello')
+  let expected += 1
+  call assert_equal(expected, b:changedtick)
+  call assert_equal(expected, b:['changedtick'])
+  undo
+  " Somehow undo counts as two changes.
+  let expected += 2
+  call assert_equal(expected, b:changedtick)
+  call assert_equal(expected, b:['changedtick'])
+  bwipe!
+endfunc
+
+func Test_changedtick_dict_entry()
+  let d = b:
+  call assert_equal(b:changedtick, d['changedtick'])
+endfunc
+
+func Test_changedtick_bdel()
+  new
+  let bnr = bufnr('%')
+  let v = b:changedtick
+  bdel
+  " Delete counts as a change too.
+  call assert_equal(v + 1, getbufvar(bnr, 'changedtick'))
+endfunc
+
+func Test_changedtick_fixed()
+  call assert_fails('let b:changedtick = 4', 'E46')
+  call assert_fails('let b:["changedtick"] = 4', 'E46')
+
+  call assert_fails('unlet b:changedtick', 'E795')
+  call assert_fails('unlet b:["changedtick"]', 'E46')
+
+  let d = b:
+  call assert_fails('unlet d["changedtick"]', 'E46')
+
+endfunc
--- a/src/testdir/test_functions.vim
+++ b/src/testdir/test_functions.vim
@@ -424,3 +424,45 @@ func! Test_mode()
   bwipe!
   iunmap <F2>
 endfunc
+
+func Test_getbufvar()
+  let bnr = bufnr('%')
+  let b:var_num = '1234'
+  let def_num = '5678'
+  call assert_equal('1234', getbufvar(bnr, 'var_num'))
+  call assert_equal('1234', getbufvar(bnr, 'var_num', def_num))
+
+  let bd = getbufvar(bnr, '')
+  call assert_equal('1234', bd['var_num'])
+  call assert_true(exists("bd['changedtick']"))
+  call assert_equal(2, len(bd))
+
+  let bd2 = getbufvar(bnr, '', def_num)
+  call assert_equal(bd, bd2)
+
+  unlet b:var_num
+  call assert_equal(def_num, getbufvar(bnr, 'var_num', def_num))
+  call assert_equal('', getbufvar(bnr, 'var_num'))
+
+  let bd = getbufvar(bnr, '')
+  call assert_equal(1, len(bd))
+  let bd = getbufvar(bnr, '',def_num)
+  call assert_equal(1, len(bd))
+
+  call assert_equal('', getbufvar(9, ''))
+  call assert_equal(def_num, getbufvar(9, '', def_num))
+  unlet def_num
+
+  call assert_equal(0, getbufvar(1, '&autoindent'))
+  call assert_equal(0, getbufvar(1, '&autoindent', 1))
+
+  " Open new window with forced option values
+  set fileformats=unix,dos
+  new ++ff=dos ++bin ++enc=iso-8859-2
+  call assert_equal('dos', getbufvar(bufnr('%'), '&fileformat'))
+  call assert_equal(1, getbufvar(bufnr('%'), '&bin'))
+  call assert_equal('iso-8859-2', getbufvar(bufnr('%'), '&fenc'))
+  close
+
+  set fileformats&
+endfunc
--- a/src/version.c
+++ b/src/version.c
@@ -765,6 +765,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    334,
+/**/
     333,
 /**/
     332,