summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--generic/tkText.c11
-rw-r--r--generic/tkText.h4
-rw-r--r--generic/tkTextDisp.c77
-rw-r--r--tests/text.test221
4 files changed, 179 insertions, 134 deletions
diff --git a/generic/tkText.c b/generic/tkText.c
index 280eec8..b0aec04 100644
--- a/generic/tkText.c
+++ b/generic/tkText.c
@@ -406,7 +406,6 @@ static Tcl_Obj * TextGetText(const TkText *textPtr,
static void GenerateModifiedEvent(TkText *textPtr);
static void GenerateUndoStackEvent(TkText *textPtr);
static void UpdateDirtyFlag(TkSharedText *sharedPtr);
-static void RunAfterSyncCmd(ClientData clientData);
static void TextPushUndoAction(TkText *textPtr,
Tcl_Obj *undoString, int insert,
const TkTextIndex *index1Ptr,
@@ -1544,7 +1543,7 @@ TextWidgetObjCmd(
textPtr->afterSyncCmd = cmd;
} else {
textPtr->afterSyncCmd = cmd;
- Tcl_DoWhenIdle(RunAfterSyncCmd, (ClientData) textPtr);
+ Tcl_DoWhenIdle(TkTextRunAfterSyncCmd, (ClientData) textPtr);
}
break;
} else if (objc != 2) {
@@ -1556,7 +1555,7 @@ TextWidgetObjCmd(
Tcl_DecrRefCount(textPtr->afterSyncCmd);
}
textPtr->afterSyncCmd = NULL;
- TkTextUpdateLineMetrics(textPtr, 1,
+ TkTextUpdateLineMetrics(textPtr, 0,
TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), -1);
break;
}
@@ -5587,7 +5586,7 @@ UpdateDirtyFlag(
/*
*----------------------------------------------------------------------
*
- * RunAfterSyncCmd --
+ * TkTextRunAfterSyncCmd --
*
* This function is called by the event loop and executes the command
* scheduled by [.text sync -command $cmd].
@@ -5601,8 +5600,8 @@ UpdateDirtyFlag(
*----------------------------------------------------------------------
*/
-static void
-RunAfterSyncCmd(
+void
+TkTextRunAfterSyncCmd(
ClientData clientData) /* Information about text widget. */
{
register TkText *textPtr = clientData;
diff --git a/generic/tkText.h b/generic/tkText.h
index 5e84fd5..8bdb193 100644
--- a/generic/tkText.h
+++ b/generic/tkText.h
@@ -1080,7 +1080,7 @@ MODULE_SCOPE int TkTextGetObjIndex(Tcl_Interp *interp, TkText *textPtr,
MODULE_SCOPE int TkTextSharedGetObjIndex(Tcl_Interp *interp,
TkSharedText *sharedTextPtr, Tcl_Obj *idxPtr,
TkTextIndex *indexPtr);
-MODULE_SCOPE const TkTextIndex *TkTextGetIndexFromObj(Tcl_Interp *interp,
+MODULE_SCOPE const TkTextIndex *TkTextGetIndexFromObj(Tcl_Interp *interp,
TkText *textPtr, Tcl_Obj *objPtr);
MODULE_SCOPE TkTextTabArray *TkTextGetTabs(Tcl_Interp *interp,
TkText *textPtr, Tcl_Obj *stringPtr);
@@ -1169,7 +1169,7 @@ MODULE_SCOPE int TkTextYviewCmd(TkText *textPtr, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
MODULE_SCOPE void TkTextWinFreeClient(Tcl_HashEntry *hPtr,
TkTextEmbWindowClient *client);
-
+MODULE_SCOPE void TkTextRunAfterSyncCmd(ClientData clientData);
#endif /* _TKTEXT */
/*
diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c
index 0ad9ead..e1b4987 100644
--- a/generic/tkTextDisp.c
+++ b/generic/tkTextDisp.c
@@ -491,13 +491,15 @@ static TkTextDispChunk *baseCharChunkPtr = NULL;
* different character might be under the mouse
* cursor now). Need to recompute the current
* character before the next redisplay.
+ * OUT_OF_SYNC 1 means that the last <<WidgetViewSync>> event had
+ * value 0, indicating that the widget is out of sync.
*/
#define DINFO_OUT_OF_DATE 1
#define REDRAW_PENDING 2
#define REDRAW_BORDERS 4
#define REPICK_NEEDED 8
-
+#define OUT_OF_SYNC 16
/*
* Action values for FreeDLines:
*
@@ -675,7 +677,7 @@ TkTextCreateDInfo(
dInfoPtr->scanTotalYScroll = 0;
dInfoPtr->scanMarkY = 0;
dInfoPtr->dLinesInvalidated = 0;
- dInfoPtr->flags = DINFO_OUT_OF_DATE;
+ dInfoPtr->flags = 0;
dInfoPtr->topPixelOffset = 0;
dInfoPtr->newTopPixelOffset = 0;
dInfoPtr->currentMetricUpdateLine = -1;
@@ -3063,10 +3065,13 @@ AsyncUpdateLineMetrics(
* We have looped over all lines, so we're done. We must release our
* refCount on the widget (the timer token was already set to NULL
* above). If there is a registered aftersync command, run that first.
+ * Cancel any pending idle task which would try to run the command
+ * after the afterSyncCmd pointer had been set to NULL.
*/
if (textPtr->afterSyncCmd) {
int code;
+ Tcl_CancelIdleCall(TkTextRunAfterSyncCmd, textPtr);
Tcl_Preserve((ClientData) textPtr->interp);
code = Tcl_EvalObjEx(textPtr->interp, textPtr->afterSyncCmd,
TCL_EVAL_GLOBAL);
@@ -3084,7 +3089,6 @@ AsyncUpdateLineMetrics(
* with its internal data (actually it will be after the next trip
* through the event loop, because the widget redraws at idle-time).
*/
-
GenerateWidgetViewSyncEvent(textPtr, 1);
if (textPtr->refCount-- <= 1) {
@@ -3108,8 +3112,14 @@ AsyncUpdateLineMetrics(
* GenerateWidgetViewSyncEvent --
*
* Send the <<WidgetViewSync>> event related to the text widget
- * line metrics asynchronous update.
- * This is equivalent to:
+ * line metrics asynchronous update. These events should only
+ * be sent when the sync status has changed. So this function
+ * compares the requested state with the state saved in the
+ * TkText structure, and only generates the event if they are
+ * different. This means that it is safe to call this function
+ * at any time when the state is known.
+ *
+ * If an event is sent, the effect is equivalent to:
* event generate $textWidget <<WidgetViewSync>> -data $s
* where $s is the sync status: true (when the widget view is in
* sync with its internal data) or false (when it is not).
@@ -3125,9 +3135,12 @@ AsyncUpdateLineMetrics(
static void
GenerateWidgetViewSyncEvent(
- TkText *textPtr, /* Information about text widget. */
- Bool InSync) /* true if in sync, false otherwise */
+ TkText *textPtr, /* Information about text widget. */
+ Bool InSync) /* true if becoming in sync, false otherwise */
{
+ Bool NewSyncState = (InSync != 0); /* ensure 0 or 1 value */
+ Bool OldSyncState = !(textPtr->dInfoPtr->flags & OUT_OF_SYNC);
+
/*
* OSX 10.14 needs to be told to display the window when the Text Widget
* is in sync. (That is, to run DisplayText inside of the drawRect
@@ -3140,8 +3153,15 @@ GenerateWidgetViewSyncEvent(
FORCE_DISPLAY(textPtr->tkwin);
}
- TkSendVirtualEvent(textPtr->tkwin, "WidgetViewSync",
- Tcl_NewBooleanObj(InSync));
+ if (NewSyncState != OldSyncState) {
+ if (NewSyncState) {
+ textPtr->dInfoPtr->flags &= ~OUT_OF_SYNC;
+ } else {
+ textPtr->dInfoPtr->flags |= OUT_OF_SYNC;
+ }
+ TkSendVirtualEvent(textPtr->tkwin, "WidgetViewSync",
+ Tcl_NewBooleanObj(NewSyncState));
+ }
}
/*
@@ -3184,6 +3204,9 @@ TkTextUpdateLineMetrics(
TkTextLine *linePtr = NULL;
int count = 0;
int totalLines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
+ int fullUpdateRequested = (lineNum == 0 &&
+ endLine == totalLines &&
+ doThisMuch == -1);
if (totalLines == 0) {
/*
@@ -3194,6 +3217,7 @@ TkTextUpdateLineMetrics(
}
while (1) {
+
/*
* Get a suitable line.
*/
@@ -3220,6 +3244,7 @@ TkTextUpdateLineMetrics(
*/
if (textPtr->dInfoPtr->metricEpoch == TCL_AUTO_LENGTH && lineNum == endLine) {
+
/*
* We have looped over all lines, so we're done.
*/
@@ -3243,10 +3268,12 @@ TkTextUpdateLineMetrics(
if (TkBTreeLinePixelEpoch(textPtr, linePtr)
== textPtr->dInfoPtr->lineMetricUpdateEpoch) {
+
/*
* This line is already up to date. That means there's nothing
* to do here.
*/
+
} else if (doThisMuch == -1) {
count += 8 * TkTextUpdateOneLine(textPtr, linePtr, 0,NULL,0);
} else {
@@ -3268,6 +3295,7 @@ TkTextUpdateLineMetrics(
indexPtr = &textPtr->dInfoPtr->metricIndex;
pixelHeight = textPtr->dInfoPtr->metricPixelHeight;
} else {
+
/*
* We must reset the partial line height calculation data
* here, so we don't use it when it is out of date.
@@ -3291,6 +3319,7 @@ TkTextUpdateLineMetrics(
pixelHeight, indexPtr, 1);
if (indexPtr->linePtr == linePtr) {
+
/*
* We didn't complete the logical line, because it
* produced very many display lines, which must be because
@@ -3299,6 +3328,7 @@ TkTextUpdateLineMetrics(
*/
if (pixelHeight == 0) {
+
/*
* These have already been stored, unless we just
* started the new line.
@@ -3320,6 +3350,7 @@ TkTextUpdateLineMetrics(
textPtr->dInfoPtr->metricEpoch = -1;
}
} else {
+
/*
* We must never recalculate the height of the last artificial
* line. It must stay at zero, and if we recalculate it, it will
@@ -3344,13 +3375,21 @@ TkTextUpdateLineMetrics(
}
}
if (doThisMuch == -1) {
+
/*
- * If we were requested to provide a full update, then also update the
- * scrollbar.
+ * If we were requested to update the entire range, then also update
+ * the scrollbar.
*/
GetYView(textPtr->interp, textPtr, 1);
}
+ if (fullUpdateRequested) {
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+
+ dInfoPtr->lastMetricUpdateLine = lineNum;
+ dInfoPtr->currentMetricUpdateLine = lineNum;
+ GenerateWidgetViewSyncEvent(textPtr, 1);
+ }
return lineNum;
}
@@ -3519,8 +3558,12 @@ TextInvalidateLineMetrics(
textPtr->refCount++;
dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
AsyncUpdateLineMetrics, textPtr);
- GenerateWidgetViewSyncEvent(textPtr, 0);
}
+
+ /*
+ * The widget is out of sync: send a <<WidgetViewSync>> event.
+ */
+ GenerateWidgetViewSyncEvent(textPtr, 0);
}
/*
@@ -5262,9 +5305,7 @@ TkTextRelayoutWindow(
inSync = 0;
}
- if (!inSync) {
- GenerateWidgetViewSyncEvent(textPtr, 0);
- }
+ GenerateWidgetViewSyncEvent(textPtr, inSync);
}
}
@@ -6289,11 +6330,7 @@ TkTextPendingsync(
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- return (
- (!(dInfoPtr->flags & REDRAW_PENDING) &&
- (dInfoPtr->metricEpoch == TCL_AUTO_LENGTH) &&
- (dInfoPtr->lastMetricUpdateLine == dInfoPtr->currentMetricUpdateLine)) ?
- 0 : 1);
+ return ((dInfoPtr->flags & OUT_OF_SYNC) != 0);
}
/*
diff --git a/tests/text.test b/tests/text.test
index 621a9eb..c79d818 100644
--- a/tests/text.test
+++ b/tests/text.test
@@ -2948,11 +2948,13 @@ test text-11a.1 {TextWidgetCmd procedure, "pendingsync" option} -setup {
} -cleanup {
destroy .yt
} -result {1 {wrong # args: should be ".yt pendingsync"}}
+
test text-11a.2 {TextWidgetCmd procedure, "pendingsync" option} -setup {
destroy .top.yt .top
} -body {
toplevel .top
pack [text .top.yt]
+ update
set content {}
for {set i 1} {$i < 300} {incr i} {
append content [string repeat "$i " 15] \n
@@ -2990,9 +2992,11 @@ test text-11a.12 {TextWidgetCmd procedure, "sync" option} -setup {
} -body {
toplevel .top
pack [text .top.yt]
+ update
set content {}
+ # Use long lines so the line metrics will need updating.
for {set i 1} {$i < 30} {incr i} {
- append content [string repeat "$i " 15] \n
+ append content [string repeat "$i " 200] \n
}
.top.yt insert 1.0 $content
# wait for end of line metrics calculation to get correct $fraction1
@@ -3065,19 +3069,18 @@ test text-11a.31 {"<<WidgetViewSync>>" event} -setup {
for {set i 1} {$i < 300} {incr i} {
append content [string repeat "$i " 15] \n
}
- .top.yt insert 1.0 $content
+ # Sync the widget and process <<WidgetViewSync>> events before binding.
+ .top.yt sync
update
bind .top.yt <<WidgetViewSync>> { if {%d} {set yud(%W) 1} }
- # wait for end of line metrics calculation to get correct $fraction1
- # as a reference
- if {[.top.yt pendingsync]} {vwait yud(.top.yt)}
+ .top.yt insert 1.0 $content
.top.yt yview moveto 1
set fraction1 [lindex [.top.yt yview] 0]
set res [expr {$fraction1 > 0}]
.top.yt delete 1.0 end
.top.yt insert 1.0 $content
# synchronously wait for completion of line metrics calculation
- # and ensure the test is relevant
+ # and verify that the fractions agree.
set waited 0
if {[.top.yt pendingsync]} {set waited 1 ; vwait yud(.top.yt)}
lappend res $waited
@@ -3091,7 +3094,6 @@ test text-11a.31 {"<<WidgetViewSync>>" event} -setup {
test text-11a.41 {"sync" "pendingsync" and <<WidgetViewSync>>} -setup {
destroy .top.yt .top
} -body {
- set res {}
toplevel .top
pack [text .top.yt]
update
@@ -3099,15 +3101,21 @@ test text-11a.41 {"sync" "pendingsync" and <<WidgetViewSync>>} -setup {
for {set i 1} {$i < 300} {incr i} {
append content [string repeat "$i " 50] \n
}
+ # Sync the widget and process all <<WidgetViewSync>> events before binding.
+ .top.yt sync
+ update
bind .top.yt <<WidgetViewSync>> {lappend res Sync:%d}
+ set res {}
+ # The next line triggers <<WidgetViewSync>> with %d==0 i.e. out of sync.
.top.yt insert 1.0 $content
- vwait res ; # event dealt with by the event loop, with %d==0 i.e. we're out of sync
- # ensure the test is relevant
+ vwait res
+ # Verify that the line metrics are not up-to-date (pendingsync is 1).
lappend res "Pending:[.top.yt pendingsync]"
- # - <<WidgetViewSync>> fires when sync returns if there was pending syncs
- # - there is no more any pending sync after running 'sync'
+ # Update all line metrics by calling the sync command.
.top.yt sync
- vwait res ; # event dealt with by the event loop, with %d==1 i.e. we're in sync again
+ # <<WidgetViewSync>> should fire with %d==1 i.e. back in sync.
+ vwait res
+ # At this time the line metrics should be up-to-date (pendingsync is 0).
lappend res "Pending:[.top.yt pendingsync]"
set res
} -cleanup {
@@ -3122,6 +3130,7 @@ test text-11a.51 {<<WidgetViewSync>> calls TkSendVirtualEvent(),
set res {}
toplevel .top
pack [text .top.t]
+ update
for {set i 1} {$i < 10000} {incr i} {
.top.t insert end "Hello world!\n"
}
@@ -7429,6 +7438,100 @@ test text-32.1 {line heights on creation} -setup {
destroy .t
} -result {1}
+test text-32.2 {peer widget -start, -end and deletion (bug 1630262)} -setup {
+ destroy .t .pt
+ set res {}
+} -body {
+ text .t
+ .t peer create .pt
+ for {set i 1} {$i < 100} {incr i} {
+ .t insert end "Line $i\n"
+ }
+ .t configure -startline 5
+ # none of the following delete shall crash
+ # (all did before fixing bug 1630262)
+ # 1. delete on the same line: line1 == line2 in DeleteIndexRange,
+ # and resetView is true neither for .t not for .pt
+ .pt delete 2.0 2.2
+ # 2. delete just one line: line1 < line2 in DeleteIndexRange,
+ # and resetView is true only for .t, not for .pt
+ .pt delete 2.0 3.0
+ # 3. delete several lines: line1 < line2 in DeleteIndexRange,
+ # and resetView is true only for .t, not for .pt
+ .pt delete 2.0 5.0
+ # 4. delete to the end line: line1 < line2 in DeleteIndexRange,
+ # and resetView is true only for .t, not for .pt
+ .pt delete 2.0 end
+ # this test succeeds provided there is no crash
+ set res 1
+} -cleanup {
+ destroy .pt
+} -result {1}
+
+test text-32.3 {peer widget -start, -end and deletion (bug 1630262)} -setup {
+ destroy .t .pt
+ set res {}
+} -body {
+ text .t
+ .t peer create .pt
+ for {set i 1} {$i < 100} {incr i} {
+ .t insert end "Line $i\n"
+ }
+ .t configure -startline 5
+ .pt configure -startline 3
+ # the following delete shall not crash
+ # (it did before fixing bug 1630262)
+ .pt delete 2.0 3.0
+ # moreover -startline shall be correct
+ # (was wrong before fixing bug 1630262)
+ lappend res [.t cget -start] [.pt cget -start]
+} -cleanup {
+ destroy .pt
+} -result {4 3}
+
+test text-32.4 {peer widget -start, -end and deletion (bug 1630262)} -setup {
+ destroy .t .pt
+ set res {}
+} -body {
+ text .t
+ .t peer create .pt
+ for {set i 1} {$i < 100} {incr i} {
+ .t insert end "Line $i\n"
+ }
+ .t configure -startline 5 -endline 15
+ .pt configure -startline 8 -endline 12
+ # .pt now shows a range entirely inside the range of .pt
+ # from .t, delete lines located after [.pt cget -end]
+ .t delete 9.0 10.0
+ # from .t, delete lines straddling [.pt cget -end]
+ .t delete 6.0 9.0
+ lappend res [.t cget -start] [.t cget -end] [.pt cget -start] [.pt cget -end]
+ .t configure -startline 5 -endline 12
+ .pt configure -startline 8 -endline 12
+ # .pt now shows again a range entirely inside the range of .pt
+ # from .t, delete lines located before [.pt cget -start]
+ .t delete 2.0 3.0
+ # from .t, delete lines straddling [.pt cget -start]
+ .t delete 2.0 5.0
+ lappend res [.t cget -start] [.t cget -end] [.pt cget -start] [.pt cget -end]
+ .t configure -startline 22 -endline 31
+ .pt configure -startline 42 -endline 51
+ # .t now shows a range entirely before the range of .pt
+ # from .t, delete some lines, then do it from .pt
+ .t delete 2.0 3.0
+ .t delete 2.0 5.0
+ .pt delete 2.0 5.0
+ lappend res [.t cget -start] [.t cget -end] [.pt cget -start] [.pt cget -end]
+ .t configure -startline 55 -endline 75
+ .pt configure -startline 60 -endline 70
+ # .pt now shows a range entirely inside the range of .t
+ # from .t, delete a range straddling the entire range of .pt
+ .t delete 3.0 18.0
+ lappend res [.t cget -start] [.t cget -end] [.pt cget -start] [.pt cget -end]
+} -cleanup {
+ destroy .pt .t
+} -result {5 11 8 10 5 8 6 8 22 27 38 44 55 60 57 57}
+
test text-33.1 {TextWidgetCmd procedure, "peer" option} -setup {
text .t
@@ -7561,100 +7664,6 @@ test text-34.1 {peer widget -start, -end and selection} -setup {
destroy .t
} -result {{10.0 20.0} {6.0 16.0} {6.0 11.0} {1.0 6.0} {1.0 2.0} {} {10.0 20.0}}
-test text-32.2 {peer widget -start, -end and deletion (bug 1630262)} -setup {
- destroy .t .pt
- set res {}
-} -body {
- text .t
- .t peer create .pt
- for {set i 1} {$i < 100} {incr i} {
- .t insert end "Line $i\n"
- }
- .t configure -startline 5
- # none of the following delete shall crash
- # (all did before fixing bug 1630262)
- # 1. delete on the same line: line1 == line2 in DeleteIndexRange,
- # and resetView is true neither for .t not for .pt
- .pt delete 2.0 2.2
- # 2. delete just one line: line1 < line2 in DeleteIndexRange,
- # and resetView is true only for .t, not for .pt
- .pt delete 2.0 3.0
- # 3. delete several lines: line1 < line2 in DeleteIndexRange,
- # and resetView is true only for .t, not for .pt
- .pt delete 2.0 5.0
- # 4. delete to the end line: line1 < line2 in DeleteIndexRange,
- # and resetView is true only for .t, not for .pt
- .pt delete 2.0 end
- # this test succeeds provided there is no crash
- set res 1
-} -cleanup {
- destroy .pt
-} -result {1}
-
-test text-32.3 {peer widget -start, -end and deletion (bug 1630262)} -setup {
- destroy .t .pt
- set res {}
-} -body {
- text .t
- .t peer create .pt
- for {set i 1} {$i < 100} {incr i} {
- .t insert end "Line $i\n"
- }
- .t configure -startline 5
- .pt configure -startline 3
- # the following delete shall not crash
- # (it did before fixing bug 1630262)
- .pt delete 2.0 3.0
- # moreover -startline shall be correct
- # (was wrong before fixing bug 1630262)
- lappend res [.t cget -start] [.pt cget -start]
-} -cleanup {
- destroy .pt
-} -result {4 3}
-
-test text-32.4 {peer widget -start, -end and deletion (bug 1630262)} -setup {
- destroy .t .pt
- set res {}
-} -body {
- text .t
- .t peer create .pt
- for {set i 1} {$i < 100} {incr i} {
- .t insert end "Line $i\n"
- }
- .t configure -startline 5 -endline 15
- .pt configure -startline 8 -endline 12
- # .pt now shows a range entirely inside the range of .pt
- # from .t, delete lines located after [.pt cget -end]
- .t delete 9.0 10.0
- # from .t, delete lines straddling [.pt cget -end]
- .t delete 6.0 9.0
- lappend res [.t cget -start] [.t cget -end] [.pt cget -start] [.pt cget -end]
- .t configure -startline 5 -endline 12
- .pt configure -startline 8 -endline 12
- # .pt now shows again a range entirely inside the range of .pt
- # from .t, delete lines located before [.pt cget -start]
- .t delete 2.0 3.0
- # from .t, delete lines straddling [.pt cget -start]
- .t delete 2.0 5.0
- lappend res [.t cget -start] [.t cget -end] [.pt cget -start] [.pt cget -end]
- .t configure -startline 22 -endline 31
- .pt configure -startline 42 -endline 51
- # .t now shows a range entirely before the range of .pt
- # from .t, delete some lines, then do it from .pt
- .t delete 2.0 3.0
- .t delete 2.0 5.0
- .pt delete 2.0 5.0
- lappend res [.t cget -start] [.t cget -end] [.pt cget -start] [.pt cget -end]
- .t configure -startline 55 -endline 75
- .pt configure -startline 60 -endline 70
- # .pt now shows a range entirely inside the range of .t
- # from .t, delete a range straddling the entire range of .pt
- .t delete 3.0 18.0
- lappend res [.t cget -start] [.t cget -end] [.pt cget -start] [.pt cget -end]
-} -cleanup {
- destroy .pt .t
-} -result {5 11 8 10 5 8 6 8 22 27 38 44 55 60 57 57}
-
test text-35.1 {widget dump -command alters tags} -setup {
proc Dumpy {key value index} {
#puts "KK: $key, $value"