Eskil

Changes On Branch trunk
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch trunk Excluding Merge-Ins

This is equivalent to a diff from 1cdf7d5e95 to f028c6509e

2024-03-07
23:02
Window browser in debug menu Leaf check-in: f028c6509e user: peter tags: trunk
22:06
Ignore short unknowns on command line check-in: a9aaeae7c7 user: peter tags: trunk
2011-05-09
00:08
Minor correction to clear syntax warning. check-in: ddfc1ceec8 user: peter.spjuth@gmail.com tags: trunk
2011-05-08
22:49
Documented tablelist transition Closed-Leaf check-in: 1cdf7d5e95 user: peter.spjuth@gmail.com tags: table-list
2011-05-07
00:37
Handle links in directory diff. Changed buttons to use images in directory diff. check-in: eb61cb3ca6 user: peter.spjuth@gmail.com tags: table-list

Changes to Changes.





































































































































































































































































































































































































































































1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26




































































































































































































































































































































































































































































2011-04-30
 Improved three-way merge.
 Highlight conflicts and navigate directly between conflicts.
 Include status for each merge chunk to see the descision made.
 Added Goto menu in merge window to get fewer toolbar buttons.
 Autodetect line endings in ancestor file to select merge output.

2011-04-28
 Code cleanup to get clean Nagelfar run

2011-04-28
 Added three-way merge. Cmd line options -a and -fine.

2011-04-24
 Added basic GUI for plugin selection.

2011-04-22
 Merging did not work properly if alignement was used. [Bug 9925]
 
2011-04-11
 Support files and revisions with -review in Fossil.
 Support revisions with -review in Git.
 New DiffUtil has a fallback to pure Tcl LCS.

2011-04-05
 Added -pluginlist option.
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



|

|











|
|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
Released 2.8.5

2023-04-23
 Added -printLineSpace. Corrected -printHeaderSize.

2021-09-23
 Allow revision control detect with -browse.

2021-03-29
 Support -context/-w/-b for -review.

2021-03-01
 Bumped revision to 2.8.5

2021-02-28
 Ballonhelp within frame.

2020-12-10
 Working towards runtime plugins.

2020-10-30
 Correct parent in dialogs.

2020-09-28
 Prune alone files dir diff.

2020-09-02
 Fixed bug where copy context menu used the wrong key.

2020-04-14
 Added gunzip plugin.

2020-01-15
 Do not traverse a unique side in dir diff.
 Support -norun for dir diff.
 Prune empty directories in dir diff.
 Added -includefile/dir command line, for dir diff.

2020-01-07
 Handle multiple screens better for balloon help 

2019-11-10
 More combinations allowed in edit mode. Including copy block of selected text.

2019-11-10
 Code cleanup to adhere a bit to some consistent standard.

2019-08-28
 Allow multiple pairs of files on the command line to open multiple windows.

2019-07-05
 Ctrl-E to enable Edit Mode.
 Do not ask about overwriting in Edit Mode. Hidden preference to turn it on.
 Added -subst command line, to acces PreProcess Subst function.

2019-06-12
 Better SVN commit, when added directories are included.

2019-03-12
 Auto-open prefix group dialog.

Released 2.8.4

2019-02-06
 Bumped revision to 2.8.4

2019-02-04
 Include a file selector when committing multiple files.
 Allow directory with -review.

2018-10-03
 Handle deleted files in GIT vcsvfs

2018-09-23
 Upgraded tablelist to 6.3

2018-06-20
 Added save-reload option in edit mode.
 Upgraded tablelist to 6.2

Released 2.8.3

2018-06-13
 Bumped revision to 2.8.3
 Better visibility that commit happened.

2018-06-05
 Working on shortcuts for preprocess dialog.

2018-05-14
 Added changeset tool to fourway,

2018-05-13
 Adjustments to fourway UI.

Released 2.8.2

2018-05-13
 Bumped revision to 2.8.2
 Fixed bug in Fossil handling. Rev -1 did not work.

2018-05-12
 Added fourway diff.

Released 2.8.1

2018-01-14
 Removed support for old RCS style -rREV command line.
 Process directory diff in a nicer order.

2018-01-11
 Bumped revision to 2.8.1
 Corrected detected of Emacs for Registry. (Broken in 2.7.4)

2017-12-28
 Pause before a large file in dirdiff processing to make it clear where it is
 slowed down.

2017-12-22
 Added -excludedir and -excludefile options for dir diff.

2017-12-16
 Make sure plugins are applied in the right order.
 Make sure Dir Diff can pick one out of many plugins.
 Dir Diff no longer shortcuts for files with same size&mtime.

2017-12-13
 Use same font in commit window as in diff window.
 Upgraded tablelist to 6.0

2017-12-07
 Support -nocase in dirdiff.
 Repaired plugins for dirdiff (broken by multi plugin).

2017-12-07
 Handle GIT revisions better for directory diff.
 
2017-12-05
 Bumped revision to 2.8.0

2017-12-04
 Support multiple plugins from command line.

2017-12-02
 Support multiple plugins.
 Changed default pivot to 10. Include GUI choice for 1.

2017-12-01
 Added GUI for multiple plugins. No function yet.

Released 2.7.4

2017-11-30
 Allow multi select in table diff.

2017-06-19
 Allow one side of directory diff to be protected from editing.
 Allow directories to be created in directory diff.

2017-03-18
 When needing an editor, try VISUAL and EDITOR plus a set of common ones.

2017-02-05
 Bumped revision to 2.7.4
 Handle multiple preprocess definitions that can be saved with preferences.

2017-02-04
 Support more areas for file drop.

2017-01-31
 Made --query work again; lost in option reorganisation.

2017-01-12
 Added -gz flag to compare compressed files.

2016-09-04
 Preserve line endings when saving during Edit Mode.

2016-08-30
 Bumped revision to 2.7.3

2016-08-21
 Auto-detect semicolon separator. Connect GUI separator with plugin.

2016-08-19
 GUI support for table and separator.

2016-08-15
 Bumped revision to 2.7.2

2016-07-29
 Include plugin command line options in command line help.
 Include plugin options in plugin dialog.

2016-07-29
 Corrected right side numbering when parsing patch. [288be8f321]

2016-07-06
 Support negative revisions with GIT. Added log view for GIT.

2016-07-01
 When displaying a patch, detect chunks marked with ##.
 E.g. svn diff lists changed properties like that.

2016-06-10
 Reorganised code for option descriptions.

2016-06-09
 Upgraded to DiffUtilTcl 0.3.9 to get consistent word parse behaviour.

2016-04-13
 Allow plugin to know file names. Use source file with pdftotext in PDF plugin
 since stdin is not reliable there.

2016-04-13
 When displaying a patch, detect a file with no changed chunks.
 E.g. svn diff lists changed binary files like that.

2016-04-08
 Allow GUI to set privilege for plugins.
 Better search for pdftotext in pdf plugin.

2015-11-22
 Bumped revision to 2.7.1
 Allow plugins to define command line options.

2015-11-20
 Rebuilt command line option handling.

2015-11-19
 Added binary plugin.

2015-11-17
 Compress printed PDF.

2015-10-14
 Page break between files when printing in patch mode.

2015-10-12
 Use tablelist 5.14 that includes -colorizecommand, for table diff.

2015-07-02
 Printing followed by redo diff closed Eskil. Fixed this.

2015-06-02
 Made a patched version of tablelist for table diff. Patch stored in src dir.
 Table diff works reasonable now

2015-04-19
 Started working on table diff.
 Added grep plugin.

2015-04-09
 Started working on a edit buttons shown between diffs.

2015-03-18
 Allow plugins to yield if Eskil is run in Tcl 8.6 or newer.
 Added swap plugin to exemplify this.

2015-03-18
 Added command line flag "-pluginallow" to allow a plugin to run in a standard
 interpreter instead of a safe one.
 Added pdf plugin.

2015-03-17
 Added command line flag "-sep" to set a separator for table-like files
 like CSV.

2015-03-16
 Added csv plugin.
 Pass ::argv to plugins.

2015-03-15
 Extended Mercurial support to commit, revert, log and directory diff.

2015-03-09
 Released 2.7

2015-03-06
 Generate release files for Mac, now when DiffUtil supports Mac.

2015-03-01
 Added more key bindings to scroll diff.

2015-02-25
 Allow saving prefs from dirdiff window.
 Put "nice" setting in preferences.

2015-02-24
 Bumped revision to 2.7

2015-02-23
 Version support in directory diff, for Fossil, GIT and Subversion.

2014-12-17
 Changed the included print font to a true type font.
 Allow -printFont to be "Courier" for fallback to PDF builtin.

2014-11-25
 Added command line -printFont to select a font file for PDF.

2014-11-16
 First working plugin in dirdiff.

2014-11-13
 Undid all dirdiff refactoring from August. Bad idea...

2014-11-12
 Bumped revision to 2.6.7

2014-11-12
 Added vcsvfs, to be used for revision aware dirdiff.

2014-11-07
 Fixed silly error from dirdiff refactoring. [da1ad24ee2]

2014-10-27
 Bumped revision to 2.6.6

2014-10-27
 Store default prefs as comment in rc file

2014-08-12
 Started to refactor dirdiff code.
 This prepares for making dirdiff revision aware.

2014-02-01
 Added a font fallback in psballon. [0ff6d72ab9]

2014-01-24
 Bumped revision to 2.6.5

2014-01-24
 Include tclkit.ico and tclkit.inf in kit, to get info on Windows.

2014-01-11
 Detect Subversion 1.7 working copy where .svn is just in the top.

2013-09-28
 Support direct print in patch mode. [6bce349e95]

2013-09-28
 Added hourglass cursor during prune equal in directory diff. [766b7a4695]

2013-09-26
 Fixed error printing patch with only deleted or inserted files. [2d89cee14d]

2013-08-22
 Bumped revision to 2.6.4

2013-08-22
 Include afm font for PDF printing.

2013-08-22
 Do not allow edit in text widget after startup. [51ad7323ff]

2013-02-18
 Add .pdf to print file by default [e093eb8eef]

2012-11-15
 No changes in scroll map when displaying a patch.

2012-09-17
 Avoid getting double .-files in dirdiff on Windows.
 Added "nice" option to control dirdiff speed.

2012-08-30
 Corrected display of ancestor lines in three-way merge.

2012-08-21
 Bumped revision to 2.6.3

2012-08-21
 Added Preferences menu for Pivot value.

2012-08-21
 Added Revert button in Revision mode

2012-07-12
 DiffUtilTcl is now 0.3.7.
 This added -pivot to exclude very common lines, and post processing of
 the excluded lines. This cuts down processing time for certain large files.

2012-07-11
 Detect and display error if commit fails.

2012-06-22
 Added -pivot command line flag to control C diff's -pivot.

2012-06-18   Release 2.6.2

2012-06-12
 DiffUtilTcl is now 0.3.5.

2012-06-12
 Added -nocdiff command line flag for debug.

2012-02-28
 Support negative revisions with Fossil.

2012-02-21
 Support branches in Subversion. [b71c8cf01b]

2012-02-19
 Support regsub preprocessing controlled per side.

2012-02-18
 Improved PDF print dialog.

2012-02-17
 Improved plugin viewer.

2012-02-14
 Include added files when using -review with Fossil.

2012-02-07
 Fixed bug where extra lines showed when displaying only diffs (no context).

Release 2.6.1

2011-11-01
  Fixed bug where copy button in directory diff picked the wrong file.
  Use a custom Toolbutton layout to get a small toolbutton in directory diff.

Release 2.6

2011-10-27
  Rebuilt rev-detection to handle any dir depth.
  Detect .fos as fossil indicator.

2011-10-15
  Added Show in plugin dialog. Added sort plugin. [FR 3735]

2011-10-15
 Added procedure editor to debug menu.

2011-10-15
 Corrected search of plugins to find them in VFS. [Bug 18395]

2011-10-05
 Respect block parse setting when showing a patch. [Bug 18147]

2011-10-04
 Fall back to Tcl-dialog when accessing a vfs. [Bug 18371]

2011-05-09
 Use mouse dragging to set alignment.

2011-05-09
 Rewritten directory diff to use tablelist.
 Redesigned appearance of directory diff.

2011-04-30
 Improved three-way merge.
 Highlight conflicts and navigate directly between conflicts.
 Include status for each merge chunk to see the decision made.
 Added Goto menu in merge window to get fewer toolbar buttons.
 Auto-detect line endings in ancestor file to select merge output.

2011-04-28
 Code cleanup to get clean Nagelfar run

2011-04-28
 Added three-way merge. Cmd line options -a and -fine.

2011-04-24
 Added basic GUI for plugin selection.

2011-04-22
 Merging did not work properly if alignment was used. [Bug 9925]

2011-04-11
 Support files and revisions with -review in Fossil.
 Support revisions with -review in Git.
 New DiffUtil has a fallback to pure Tcl LCS.

2011-04-05
 Added -pluginlist option.
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2011-03-31
 Added support for Fossil revision control.

2010-11-07
 Added tkdnd support. [FR 5125]

2010-11-07
 Autodetect line endings in conflict file.
 Allow line ending selection in merge save. [FR 5160]
 Added menu bar to merge window.

2010-06-23
 Support -noempty from DiffUtil, to try it out.

2010-04-27







|







488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
2011-03-31
 Added support for Fossil revision control.

2010-11-07
 Added tkdnd support. [FR 5125]

2010-11-07
 Auto-detect line endings in conflict file.
 Allow line ending selection in merge save. [FR 5160]
 Added menu bar to merge window.

2010-06-23
 Support -noempty from DiffUtil, to try it out.

2010-04-27
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
 Better handling of negative Subversion revisions

2008-11-19
 Bug fix in Clip Diff Capture

2008-11-10
 Improved patch parsing.
 Includ Twapi in windows executable

2008-11-06
 Added Capture in Clip Diff on Windows.
 Handle Rev and Plugin at the same time.

2008-09-23
 Added log button for version control.







|







540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
 Better handling of negative Subversion revisions

2008-11-19
 Bug fix in Clip Diff Capture

2008-11-10
 Improved patch parsing.
 Include Twapi in windows executable

2008-11-06
 Added Capture in Clip Diff on Windows.
 Handle Rev and Plugin at the same time.

2008-09-23
 Added log button for version control.
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
 Kits are mounted readonly.

2007-01-29
 Finished dirdiff filters. [FR 3040]

2007-01-28
 Started on dirdiff filters.
 Added dirdiff preferences dialog. 

2007-01-09
 Document --query flag. [FR 3027]
 Smarter save in merge. [FR 2957]

2007-01-07
 Added commit button for CVS. [FR 2780]







|







622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
 Kits are mounted readonly.

2007-01-29
 Finished dirdiff filters. [FR 3040]

2007-01-28
 Started on dirdiff filters.
 Added dirdiff preferences dialog.

2007-01-09
 Document --query flag. [FR 3027]
 Smarter save in merge. [FR 2957]

2007-01-07
 Added commit button for CVS. [FR 2780]
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288

2004-06-30
 Full -r support for ClearCase.

2004-06-24
 Added simple -r support to ClearCase diff.
 Support ignore case in block matching.
 
Release 2.0.4

2004-06-17
 Added ignore case option.

2004-06-16
 Improved alignment handling a bit. Mark alignment row with an underline.







|







726
727
728
729
730
731
732
733
734
735
736
737
738
739
740

2004-06-30
 Full -r support for ClearCase.

2004-06-24
 Added simple -r support to ClearCase diff.
 Support ignore case in block matching.

Release 2.0.4

2004-06-17
 Added ignore case option.

2004-06-16
 Improved alignment handling a bit. Mark alignment row with an underline.
323
324
325
326
327
328
329
330
331
332
333
334
335

Release 2.0.1

2004-02-10
 Added preferences for width and height.
 Added Tools menu to directory diff window.
 Made it simpler to save a conflict in the same file.
 
2004-02-05
 Stopped Tk from interfering with the command line.

2004-01-30
 Release 2.0







|





775
776
777
778
779
780
781
782
783
784
785
786
787

Release 2.0.1

2004-02-10
 Added preferences for width and height.
 Added Tools menu to directory diff window.
 Made it simpler to save a conflict in the same file.

2004-02-05
 Stopped Tk from interfering with the command line.

2004-01-30
 Release 2.0

Deleted Eskil.html.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
  <title>Eskil - A graphical frontend to Diff</title>
      
  <meta http-equiv="content-type"
 content="text/html; charset=ISO-8859-1">
</head>
<body>
<a href="http://developer.berlios.de" title="BerliOS Developer"> <img src="http://developer.berlios.de/bslogo.php?group_id=6563" width="124px" height="32px" border="0" alt="BerliOS Developer Logo"></a>
<p>
<h3>Bug tracker and mailing list can be found at the
<b><a href="https://developer.berlios.de/projects/eskil/">Project page</a>
</b></h3><p> 
<h1>Eskil: A graphical frontend to Diff</h1>
<a href="#EskilDownload">Download</a>
<a href="#EskilFeatures">Features</a>
<a href="#EskilScreenshots">Screenshots</a>
<a href="#EskilChanges">Changes</a>
<p>
<h3>About Eskil</h3>
The first major application I wrote in Tcl/Tk was this tool (somewhere around
1997).  Writing it is what made me learn and enjoy Tcl. (I already liked Tk
but still found Tcl annoying at the time.)<br>
I have used it a lot during the years and lately it has received enough
polish to grant a public release.
<p>
Pronunciation: The E is short, like in "set", the rest is like "skill".
<p>
Any feedback, good or bad, can be sent to
&lt;peter <i>dot</i> spjuth <i>at</i> gmail <i>dot</i> com&gt;
<p>
It is similar but unrelated to <a href="http://wiki.tcl.tk/tkdiff">TkDiff</a>.

<a name="EskilFeatures"></a><h3>Features</h3>

<ul>
<li>Highlights changes within a line.</li>
<li>Matches similar lines within a changed block to better show changed
lines that are adjacent to added/removed lines.</li>
<li>Directory diff.</li>
<li>CVS/RCS/ClearCase/GIT/SVN/BZR/HG/Perforce/Fossil diff. Conflict merge.</li>
<li>Commit changes directly from Eskil.</li>
<li>View patch, from file or clipboard.</li>
<li>Print to PDF.</li>
<li>"Clip diff"</li>
<li>Plugins for preprocessing files.</li> 
<li>Alignment and block diff functions for tricky diffs.</li>
<li>Edit and Save file from diff window.</li>
<li><a href="http://wiki.tcl.tk/starkit">Starkit</a> browsing.</li>
</ul>

<a name="EskilDownload"></a><h3>Download</h3>

Version 2.5:<br>
Is available from the
<a href="https://developer.berlios.de/projects/eskil/">Project page</a>
both as a Starkit and as Starpacks for Windows, Linux and Solaris.
<p>
The license for the application source is GPL but the bundled packages
are under the same license as Tcl.

<p>More information about <a href="http://wiki.tcl.tk/starkit">Starkits</a>
and <a href="http://wiki.tcl.tk/starpack">Starpacks</a>.

<a name="EskilScreenshots"></a><h3>Screenshots</h3>

<img src="eskil/eskil1.png">
<p>
A "zoom" feature for long lines.<p>
<img src="eskil/eskil2.png"><br>

<a name="EskilChanges"></a><h3>Changes</h3>
Changes in v2.5 (2011-04-01):<br>
<ul>
 <li> Requires Tcl 8.5.</li>
 <li> Plugins: Added dump, better documentation.</li>
 <li> Dir diff: Added step down.</li>
 <li> Dir diff: Redesigned to display less.</li>
 <li> Support for Perforce and Fossil.</li>
 <li> Allow zero lines of context.</li>
 <li> Detect and display annotation in patch view.</li>
 <li> Select colors for PDF print. Command line options for PDF.</li>
 <li> Removed support for Postscript output.</li>
 <li> Support File Drop with TkDnd.</li>
 <li> Handle line endings in conflict and merge.</li>
</ul>
Changes in v2.4 (2009-01-08):<br>
<ul>
 <li> Completely redesigned directory diff.</li>
 <li> Added a log viewer in revision mode.</li>
 <li> Added color option for unchanged text.</li>
 <li> Plugins support.</li> 
 <li> Support for Subversion, Mercurial and Bazaar.</li>
 <li> Support commit in Subversion.</li>
 <li> Added -review for displaying all changes in a tree.</li>
 <li> Support command line "-" to read a patch from std input.</li>
</ul>
Changes in v2.3 (2007-12-05):<br>
<ul>
 <li> Added -printpdf command line option.</li>
 <li> Fixed line numbering in PDF with big line numbers.</li>
 <li> Started on GIT support.</li>
 <li> Anything on the command line is checked for a starkit.
      Kits are mounted and treated as directories.</li>
</ul>
Changes in v2.2 (2007-04-05):<br>
<ul>
 <li> Added experimental -nonewline command option.</li>
 <li> Added -close command option.</li>
 <li> Added experimental PDF print.</li>
 <li> Added dirdiff preferences and filters.</li>
 <li> Smarter save in merge. [FR 2957]</li>
 <li> Added commit button for CVS. [FR 2780]</li>
 <li> Bug fixes include: Kits are mounted readonly,
      fixed dir diff window menu,
      improved patch file parsing.</li>
</ul>
<br>
Changes in v2.1 (2006-06-02):<br>
<ul>
  <li>  Added -preprocess command line option.</li>
  <li>  Added -foreach command line option.</li>
  <li>  Added -context command line option.</li>
  <li>  Handle starkits as directories in dir diff.</li>
  <li>  Support relative -r with CVS.</li>
</ul>
<br>
Changes in v2.0.7 (2004-12-14):<br>
<ul>
  <li>  Added regsub preprocessing option.</li>
  <li>  Added -prefix command line option.</li>
  <li>  Improved merge window.</li>
  <li>  Added ignore keyword option to directory diff.</li>
</ul>
<br>
Changes in v2.0.6 (2004-10-19):<br>
<ul>
  <li>  Added Ignore Digit option.</li>
  <li>  Fixed bug in -r for ClearCase.</li>
  <li>  Edit Mode made more robust.</li>
</ul>
<br>
Changes in v2.0.5 (2004-08-20):<br>
<ul>
  <li>  Option -r can now be used with ClearCase diff.</li>
  <li>  Edit Mode allows simple editing in the diff display and saving.</li>
</ul>
<br>
Changes in v2.0.4 (2004-06-17):<br>
<ul>
  <li>  Added ignore case option.</li>
  <li>  Improved alignment function.</li>
</ul>
<br>
Changes in v2.0.3 (2004-05-26):<br>
<ul>
  <li>  Added context options for "Diffs only" mode.</li>
  <li>  Rewrote how "Separate Diff" and "Align" works.  The latter now only happens after a "Redo Diff".</li>
  <li>  Added scroll map and some more context menu options in Directory Diff.</li>
</ul>
<br>
Changes in v2.0.2 (2004-05-03):<br>
<ul>
  <li>  Fixed a bug in ClearCase support.</li>
  <li>  Improved enscipt usage in print command.</li>
  <li>  Added "mark file" in dirdiff context menu.</li>
</ul>
<br>
Changes in v2.0.1 (2004-02-10):<br>
<ul>
  <li>  Added preference for width and height.</li>
  <li>  Added Tools menu to directory diff window.</li>
  <li>  Made it simpler to save a conflict in the same file.</li>
</ul>
<br>
First public release v2.0 (2004-01-30):<br>
<br>

</body>
</html>
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










































































































































































































































































































































































Changes to Makefile.

1
2
3
4

5


6
7
8
9
10
11

12
13
14
15


16
17
18
19
20
21
22
23
24
25

26
27
28
29
30
31
32
33
34

35
36





































37
38
39
40

41
42

43




44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100








101
102















103
104
105
106
107
108
109



110
111
112
113
114
115
116
117
118
119
120
121
122



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#----------------------------------------------------------------------
# Make file for Eskil
#----------------------------------------------------------------------


VERSION = 26



# Path to the TclKits used for creating StarPacks.
TCLKIT = /home/peter/tclkit/v85
TCLKIT_LINUX   = $(TCLKIT)/tclkit-8.5.8
TCLKIT_SOLARIS = $(TCLKIT)/tclkit-solaris-sparc
TCLKIT_WIN     = $(TCLKIT)/tclkit-win32.upx.exe


# Path to the libraries used
STYLE      = /home/peter/src/packages/style
GRIFFIN    = /home/peter/tclkit/griffin.vfs/lib/griffin


TEXTSEARCH = /home/peter/src/textsearch
DIFFUTIL   = /home/peter/src/DiffUtilTcl/lib.vfs/DiffUtil
WCB        = /home/peter/src/packages/wcb3.0
PDF4TCL    = /home/peter/src/pdf4tcl/pkg
SNIT       = /home/peter/tcl/tcllib/modules/snit
STRUCT     = /home/peter/tcl/tcllib/modules/struct
CMDLINE    = /home/peter/tcl/tcllib/modules/cmdline
TABLELIST  = /home/peter/src/packages/tablelist/tablelist
TWAPI      = /home/peter/src/packages/twapi
TKDND      = /home/peter/src/packages/tkdnd/lib/tkdnd1.0


# Tools
NAGELFAR    = nagelfar

all: setup

SRCFILES = src/eskil.tcl src/clip.tcl src/dirdiff.tcl src/help.tcl src/map.tcl \
	   src/print.tcl src/registry.tcl src/rev.tcl \
	   src/compare.tcl src/merge.tcl src/printobj.tcl src/plugin.tcl


#----------------------------------------------------------------





































# Setup symbolic links from the VFS to the real files
#----------------------------------------------------------------

eskil.vfs/src/eskil.tcl:

	@cd eskil.vfs/src ; for i in $(SRCFILES); do ln -fs ../../$$i ; done
eskil.vfs/src/images:

	@cd eskil.vfs/src ; ln -fs ../../src/images




eskil.vfs/examples:
	cd eskil.vfs ; ln -s ../examples
eskil.vfs/doc:
	cd eskil.vfs ; ln -s ../doc
eskil.vfs/plugins:
	cd eskil.vfs ; ln -s ../plugins
eskil.vfs/COPYING:
	cd eskil.vfs ; ln -s ../COPYING
eskil.vfs/lib/wcb:
	cd eskil.vfs/lib ; ln -s $(WCB) wcb
eskil.vfs/lib/style:
#	cd eskil.vfs/lib ; ln -s $(STYLE) style
eskil.vfs/lib/griffin:
	cd eskil.vfs/lib ; ln -s $(GRIFFIN) griffin
eskil.vfs/lib/textsearch:
	cd eskil.vfs/lib ; ln -s $(TEXTSEARCH) textsearch
eskil.vfs/lib/diffutil:
	cd eskil.vfs/lib ; ln -s $(DIFFUTIL) diffutil
eskil.vfs/lib/pdf4tcl:
	cd eskil.vfs/lib ; ln -s $(PDF4TCL) pdf4tcl
eskil.vfs/lib/tkdnd:
	cd eskil.vfs/lib ; ln -s $(TKDND) tkdnd
eskil.vfs/lib/tablelist:
	cd eskil.vfs/lib ; ln -s $(TABLELIST) tablelist
eskil.vfs/lib/snit:
	cd eskil.vfs/lib ; mkdir snit
	cd eskil.vfs/lib/snit ; ln -s $(SNIT)/pkgIndex.tcl
	cd eskil.vfs/lib/snit ; ln -s $(SNIT)/snit.tcl
	cd eskil.vfs/lib/snit ; ln -s $(SNIT)/snit2.tcl
	cd eskil.vfs/lib/snit ; ln -s $(SNIT)/main2.tcl
	cd eskil.vfs/lib/snit ; ln -s $(SNIT)/main1.tcl
	cd eskil.vfs/lib/snit ; ln -s $(SNIT)/validate.tcl
eskil.vfs/lib/struct:
	cd eskil.vfs/lib ; mkdir struct
	cd eskil.vfs/lib/struct ; ln -s $(STRUCT)/pkgIndex.tcl
	cd eskil.vfs/lib/struct ; ln -s $(STRUCT)/list.tcl
eskil.vfs/lib/cmdline:
	cd eskil.vfs/lib ; ln -s $(CMDLINE) cmdline

links: eskil.vfs/src/eskil.tcl \
	eskil.vfs/src/images \

	eskil.vfs/examples\
	eskil.vfs/doc\
	eskil.vfs/plugins\
	eskil.vfs/COPYING\
	eskil.vfs/lib/griffin\
	eskil.vfs/lib/style\
	eskil.vfs/lib/textsearch\
	eskil.vfs/lib/diffutil\
	eskil.vfs/lib/pdf4tcl\
	eskil.vfs/lib/snit\
	eskil.vfs/lib/struct\
	eskil.vfs/lib/cmdline\
	eskil.vfs/lib/tkdnd\
	eskil.vfs/lib/tablelist\
	eskil.vfs/lib/wcb









setup: links
















#----------------------------------------------------------------
# Testing
#----------------------------------------------------------------

spell:
	@cat doc/*.txt | ispell -d british -l | sort -u




NAGELFARFLAGS = -s syntaxdb86.tcl -filter "*Non constant definition*" -quiet

# Create a common "header" file for all source files.
eskil_h.syntax: $(SRCFILES) src/eskil.syntax
	@echo Creating syntax header file...
	@$(NAGELFAR) $(NAGELFARFLAGS) -header eskil_h.syntax $(SRCFILES)

check: eskil_h.syntax
	@echo Checking...
	@for i in $(SRCFILES); do $(NAGELFAR)  $(NAGELFARFLAGS) eskil_h.syntax $$i ; done

test:
	@./tests/all.tcl




#----------------------------------------------------------------
# Coverage
#----------------------------------------------------------------

# Source files for code coverage
COVFILES = src/rev.tcl src/eskil.tcl
IFILES   = $(COVFILES:.tcl=.tcl_i)
LOGFILES = $(COVFILES:.tcl=.tcl_log)
MFILES   = $(COVFILES:.tcl=.tcl_m)

# Instrument source file for code coverage
%.tcl_i: %.tcl
	@$(NAGELFAR) -instrument $<

# Target to prepare for code coverage run. Makes sure log file is clear.
instrument: $(IFILES)
	@rm -f $(LOGFILES)

# Run tests to create log file.
testcover $(LOGFILES): $(IFILES)




>
|
>
>


|
|
<

>

|
<
<
>
>
|
|
|
|
|
<
<
|
|
|
>







|
|
>


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|


>


>

>
>
>
>

|

|

|

|
|
|
<
<
<
<
|
|
|
|
|
|
|
|
|
<
<
|
<
<
<
<
<
<
|
<
<
<
<
|



>




<
<




<
<




>
>
>
>
>
>
>
>
|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







>
>
>
|


|





|


|
>
>
>






|





|
|







1
2
3
4
5
6
7
8
9
10
11
12

13
14
15
16


17
18
19
20
21
22
23


24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99




100
101
102
103
104
105
106
107
108


109






110




111
112
113
114
115
116
117
118
119


120
121
122
123


124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#----------------------------------------------------------------------
# Make file for Eskil
#----------------------------------------------------------------------

# This string is used to generate release file names
VERSION = 285
# This string is used to tag the version shown in Eskil
DOTVERSION = 2.8.5

# Path to the TclKits used for creating StarPacks.
TCLKIT = /home/$(USER)/tclkit/v86
TCLKIT_LINUX   = $(TCLKIT)/tclkit-linux

TCLKIT_WIN     = $(TCLKIT)/tclkit-win32.upx.exe
TCLKIT_MAC     = $(TCLKIT)/tclkit-mac-867

# Paths to the libraries used.


# If you do not have access to all these, you can get them from an Eskil kit
# as explained below.
TEXTSEARCH = /home/$(USER)/src/textsearch
DIFFUTIL   = /home/$(USER)/src/DiffUtilTcl/lib.vfs/DiffUtil
WCB        = /home/$(USER)/src/packages/wcb3.5
PDF4TCL    = /home/$(USER)/src/pdf4tcl/pkg
SNIT       = /home/$(USER)/src/packages/tcllib/modules/snit


TABLELIST  = /home/$(USER)/src/packages/tablelist6.3
TWAPI      = /home/$(USER)/src/packages/twapi
TKDND      = /home/$(USER)/src/packages/tkdnd/lib/tkdnd2.4
EMBEDFONT  = /usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf

# Tools
NAGELFAR    = nagelfar

all: setup

SRCFILES = src/eskil.tcl src/clip.tcl src/dirdiff.tcl src/help.tcl src/map.tcl \
	   src/print.tcl src/registry.tcl src/rev.tcl src/debug.tcl \
	   src/compare.tcl src/merge.tcl src/printobj.tcl src/plugin.tcl \
           src/vcsvfs.tcl src/preprocess.tcl src/startup.tcl src/fourway.tcl

#----------------------------------------------------------------
# Build a dependency tree to other libs needed.
# This is made in a parallell VFS structure, mimicking Eskil's.
# Thus deps.vfs can also be created by downloading an Eskil kit,
# rename it to "deps" and unwrap it with sdx.
#----------------------------------------------------------------

deps.vfs/src/embedfont.ttf:
	@mkdir -p deps.vfs/src
	@cd deps.vfs/src ; ln -fs $(EMBEDFONT) embedfont.ttf
deps.vfs/lib/wcb:
	@mkdir -p deps.vfs/lib
	cd deps.vfs/lib ; ln -fns $(WCB) wcb
deps.vfs/lib/textsearch:
	@mkdir -p deps.vfs/lib
	cd deps.vfs/lib ; ln -fns $(TEXTSEARCH) textsearch
deps.vfs/lib/diffutil:
	@mkdir -p deps.vfs/lib
	cd deps.vfs/lib ; ln -fns $(DIFFUTIL) diffutil
deps.vfs/lib/pdf4tcl:
	@mkdir -p deps.vfs/lib
	cd deps.vfs/lib ; ln -fns $(PDF4TCL) pdf4tcl
deps.vfs/lib/tkdnd:
	@mkdir -p deps.vfs/lib
	cd deps.vfs/lib ; ln -fns $(TKDND) tkdnd
deps.vfs/lib/tablelist:
	@mkdir -p deps.vfs/lib
	cd deps.vfs/lib ; ln -fns $(TABLELIST) tablelist
deps.vfs/lib/snit:
	@mkdir -p deps.vfs/lib/snit
	cd deps.vfs/lib/snit ; ln -fs $(SNIT)/pkgIndex.tcl
	cd deps.vfs/lib/snit ; ln -fs $(SNIT)/snit.tcl
	cd deps.vfs/lib/snit ; ln -fs $(SNIT)/snit2.tcl
	cd deps.vfs/lib/snit ; ln -fs $(SNIT)/main2.tcl
	cd deps.vfs/lib/snit ; ln -fs $(SNIT)/main1.tcl
	cd deps.vfs/lib/snit ; ln -fs $(SNIT)/validate.tcl

#------------------------------------------------------------------
# Setup symbolic links from the VFS to the sources and dependencies
#------------------------------------------------------------------

eskil.vfs/src/eskil.tcl:
	@mkdir -p eskil.vfs/src
	@cd eskil.vfs/src ; for i in $(SRCFILES); do ln -fs ../../$$i ; done
eskil.vfs/src/images:
	@mkdir -p eskil.vfs/src
	@cd eskil.vfs/src ; ln -fs ../../src/images
eskil.vfs/src/embedfont.ttf: deps.vfs/src/embedfont.ttf
	@mkdir -p eskil.vfs/src
	@cd eskil.vfs/src ; ln -fs ../../deps.vfs/src/embedfont.ttf
	@cd src ; ln -fs ../deps.vfs/src/embedfont.ttf
eskil.vfs/examples:
	cd eskil.vfs ; ln -fs ../examples
eskil.vfs/doc:
	cd eskil.vfs ; ln -fs ../doc
eskil.vfs/plugins:
	cd eskil.vfs ; ln -fs ../plugins
eskil.vfs/COPYING:
	cd eskil.vfs ; ln -fs ../COPYING
eskil.vfs/lib/wcb: deps.vfs/lib/wcb
	cd eskil.vfs/lib ; ln -fs ../../deps.vfs/lib/wcb




eskil.vfs/lib/textsearch: deps.vfs/lib/textsearch
	cd eskil.vfs/lib ; ln -fs ../../deps.vfs/lib/textsearch
eskil.vfs/lib/diffutil: deps.vfs/lib/diffutil
	cd eskil.vfs/lib ; ln -fs ../../deps.vfs/lib/diffutil
eskil.vfs/lib/pdf4tcl: deps.vfs/lib/pdf4tcl
	cd eskil.vfs/lib ; ln -fs ../../deps.vfs/lib/pdf4tcl
eskil.vfs/lib/tkdnd: deps.vfs/lib/tkdnd
	cd eskil.vfs/lib ; ln -fs ../../deps.vfs/lib/tkdnd
eskil.vfs/lib/tablelist: deps.vfs/lib/tablelist


	cd eskil.vfs/lib ; ln -fs ../../deps.vfs/lib/tablelist






eskil.vfs/lib/snit: deps.vfs/lib/snit




	cd eskil.vfs/lib ; ln -fs ../../deps.vfs/lib/snit

links: eskil.vfs/src/eskil.tcl \
	eskil.vfs/src/images \
	eskil.vfs/src/embedfont.ttf \
	eskil.vfs/examples\
	eskil.vfs/doc\
	eskil.vfs/plugins\
	eskil.vfs/COPYING\


	eskil.vfs/lib/textsearch\
	eskil.vfs/lib/diffutil\
	eskil.vfs/lib/pdf4tcl\
	eskil.vfs/lib/snit\


	eskil.vfs/lib/tkdnd\
	eskil.vfs/lib/tablelist\
	eskil.vfs/lib/wcb

# Use this with -B to just update the source links when a new source file
# is present
newsrc: eskil.vfs/src/eskil.tcl

src/TAGS: $(SRCFILES)
	etags -o src/TAGS --regex="/proc[ \t]+\([^ \t]+\)/\1/" $(SRCFILES) \
	eskil.vfs/lib/*/*.tcl

setup: links src/TAGS

# Use this to rebuild the docs when command line changes or
# new wiki files are added.
docs:
	echo "<title>Usage</title>" > htdocs/usage.wiki
	echo "" >> htdocs/usage.wiki
	echo "<h1>Command Line Usage</h1>" >> htdocs/usage.wiki
	echo "" >> htdocs/usage.wiki
	echo "<verbatim>" >> htdocs/usage.wiki
	$(TCLKIT_LINUX) eskil.vfs/main.tcl -help | grep -v "  Version " >> htdocs/usage.wiki
	echo "</verbatim>" >> htdocs/usage.wiki
	echo "<title>Documentation</title>" > htdocs/toc.wiki
	echo "" >> htdocs/toc.wiki
	grep title htdocs/*.wiki | grep -v Documentation | \
	sed -e 's/htdocs/[./' -e 's/:<title>/|/' -e 's,</title>,],' | \
	awk '{print $0; print ""};' >> htdocs/toc.wiki
#----------------------------------------------------------------
# Testing
#----------------------------------------------------------------

spell:
	@cat doc/*.txt | ispell -d british -l | sort -u

CHKFILES = $(SRCFILES) $(wildcard plugins/*.tcl) \
	eskil.vfs/lib/psballoon/psballoon.tcl \
	eskil.vfs/lib/pstools/pstools.tcl
NAGELFARFLAGS = -s syntaxdb.tcl -pkgpicky -filter "*Non constant definition*" -quiet -plugin nfplugin.tcl

# Create a common "header" file for all source files.
eskil_h.syntax: $(SRCFILES) src/eskil.syntax nfplugin.tcl
	@echo Creating syntax header file...
	@$(NAGELFAR) $(NAGELFARFLAGS) -header eskil_h.syntax $(SRCFILES)

check: eskil_h.syntax
	@echo Checking...
	@for i in $(CHKFILES); do $(NAGELFAR)  $(NAGELFARFLAGS) eskil_h.syntax $$i ; done

test:
	@./tests/all.tcl $(TESTFLAGS)

run:
	$(TCLKIT_LINUX) eskil.vfs/main.tcl -debug

#----------------------------------------------------------------
# Coverage
#----------------------------------------------------------------

# Source files for code coverage
COVFILES = eskil.vfs/main.tcl eskil.vfs/src/rev.tcl eskil.vfs/src/eskil.tcl eskil.vfs/src/merge.tcl eskil.vfs/src/startup.tcl
IFILES   = $(COVFILES:.tcl=.tcl_i)
LOGFILES = $(COVFILES:.tcl=.tcl_log)
MFILES   = $(COVFILES:.tcl=.tcl_m)

# Instrument source file for code coverage
%.tcl_i: %.tcl eskil_h.syntax
	@$(NAGELFAR) -instrument eskil_h.syntax $<

# Target to prepare for code coverage run. Makes sure log file is clear.
instrument: $(IFILES)
	@rm -f $(LOGFILES)

# Run tests to create log file.
testcover $(LOGFILES): $(IFILES)
155
156
157
158
159
160
161



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181








clean:
	@rm -f $(LOGFILES) $(IFILES) $(MFILES)

#----------------------------------------------------------------
# Packaging/Releasing
#----------------------------------------------------------------




wrap:
	sdx wrap eskil.kit

wrapexe:
	@\rm -f eskil.linux eskil.exe eskil.solaris
	sdx wrap eskil.linux   -runtime $(TCLKIT_LINUX)
	sdx wrap eskil.solaris -runtime $(TCLKIT_SOLARIS)
	cd eskil.vfs/lib ; ln -s $(TWAPI) twapi
	sdx wrap eskil.exe     -runtime $(TCLKIT_WIN)
	rm eskil.vfs/lib/twapi

release: setup wrap wrapexe
	@cp eskil.kit eskil`date +%Y%m%d`.kit
	@cp eskil.kit eskil$(VERSION).kit
	@gzip eskil.linux
	@mv eskil.linux.gz eskil$(VERSION).linux.gz
	@gzip eskil.solaris
	@mv eskil.solaris.gz eskil$(VERSION).solaris.gz
	@zip eskil$(VERSION).win.zip eskil.exe
	@zip eskil`date +%Y%m%d`.win.zip eskil.exe















>
>
>
|


|
|

|









|
|


>
>
>
>
>
>
>
>
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
clean:
	@rm -f $(LOGFILES) $(IFILES) $(MFILES)

#----------------------------------------------------------------
# Packaging/Releasing
#----------------------------------------------------------------

tagversion:
	echo "Version $(DOTVERSION) `date --iso-8601`" > eskil.vfs/version.txt

wrap: tagversion
	sdx wrap eskil.kit

wrapexe: tagversion
	@\rm -f eskil.linux eskil.exe
	sdx wrap eskil.linux   -runtime $(TCLKIT_LINUX)
	sdx wrap eskil.mac     -runtime $(TCLKIT_MAC)
	cd eskil.vfs/lib ; ln -s $(TWAPI) twapi
	sdx wrap eskil.exe     -runtime $(TCLKIT_WIN)
	rm eskil.vfs/lib/twapi

release: setup wrap wrapexe
	@cp eskil.kit eskil`date +%Y%m%d`.kit
	@cp eskil.kit eskil$(VERSION).kit
	@gzip eskil.linux
	@mv eskil.linux.gz eskil$(VERSION).linux.gz
	@gzip eskil.mac
	@mv eskil.mac.gz eskil$(VERSION).mac.gz
	@zip eskil$(VERSION).win.zip eskil.exe
	@zip eskil`date +%Y%m%d`.win.zip eskil.exe

tofossil:
	fossil unversioned add eskil$(VERSION).kit      --as htdocs/download/eskil$(VERSION).kit
	fossil unversioned add eskil$(VERSION).linux.gz --as htdocs/download/eskil$(VERSION).linux.gz
	fossil unversioned add eskil$(VERSION).mac.gz   --as htdocs/download/eskil$(VERSION).mac.gz
	fossil unversioned add eskil$(VERSION).win.zip  --as htdocs/download/eskil$(VERSION).win.zip
	fossil unversioned list
	@echo 'Remember: fossil unversioned sync'

Changes to TODO.

33
34
35
36
37
38
39







Dirdiff:
Funktion: preprocess filter på namnen så man kan jämföra bibliotek
med ändrade namn.


Print: utföra printkommando. Via dialog och -print? Klara t.ex. lp -n 2
Kanske generera pdf och pipea till lp?














>
>
>
>
>
>
>
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Dirdiff:
Funktion: preprocess filter på namnen så man kan jämföra bibliotek
med ändrade namn.


Print: utföra printkommando. Via dialog och -print? Klara t.ex. lp -n 2
Kanske generera pdf och pipea till lp?


Rev:
SVN: Vad göra med Log när -r är en branch?
Kan den fixas oom två -r är på samma branch?

Fossil: Kan man fixa Log om man använder -r -1?

Changes to bindiff.tcl.

Added bumprev.txt.































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
These files need to be changed when bumping revisions:

Makefile (VERSION and DOTVERSION)
eskil.vfs/tclkit.inf (fileversion/productversion)

Also, mark it in:
Changes

------------
File Release

Make sure revision is bumped and everything is committed.
Clean run of make check / make test
make release
make tofossil

Update htdocs/download.html with info from above.
Update htdocs/changes.wiki
Update Changes

Commit, and do 'fossil unversioned sync'

---------------
New Source File

These changes are needed when adding a new source files:

Add source to e.g. src/apa.tcl  (plus add to fossil)
In src/startup.tcl, update InitReSource
In Makefile, update SRCFILES
Do make -B newsrc

Changes to doc/cmdline.txt.

18
19
20
21
22
23
24


25
26
27
28
29
30
31
  -clip       : Start in clip diff mode. Ignores other args.
  -patch      : View patch file.
  -           : Read patch file from standard input, to allow pipes.
  -review     : View revision control tree as a patch.
  -context <n>: Show only differences, with <n> lines of context.
  -foreach    : Open one diff window per file listed.
  -close      : Close windows with no changes.



  -noparse    : Eskil can perform analysis of changed blocks to
  -line       : improve display. See online help for details.
  -smallblock : The default. Do block analysis on small blocks.
  -block      : Full block analysis. This can be slow if there
                are large change blocks.








>
>







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  -clip       : Start in clip diff mode. Ignores other args.
  -patch      : View patch file.
  -           : Read patch file from standard input, to allow pipes.
  -review     : View revision control tree as a patch.
  -context <n>: Show only differences, with <n> lines of context.
  -foreach    : Open one diff window per file listed.
  -close      : Close windows with no changes.
  -sep <c>    : See char <c> as separator between columns in files.
  -gz         : Uncompress files.

  -noparse    : Eskil can perform analysis of changed blocks to
  -line       : improve display. See online help for details.
  -smallblock : The default. Do block analysis on small blocks.
  -block      : Full block analysis. This can be slow if there
                are large change blocks.

50
51
52
53
54
55
56
57
58
59
60
61
62
63


64
65
66
67
68
69
70
71

72
73
74
75
76
77

  -a <file>   : Give anscestor file for three way merge.
  -conflict   : Treat file as a merge conflict file and enter merge
                mode.
  -o <file>   : Specify merge result output file.
  -fine       : Use fine grained chunks. Useful for merging.

  -browse     : Automatically bring up file dialog after starting.
  -server     : Set up Eskil to be controllable from the outside.

  -print <file>          : Generate PDF and exit.
  -printCharsPerLine <n> : Adapt font size for this line length and wrap. (80)
  -printPaper <paper>    : Select paper size (a4)
  -printHeaderSize <n>   : Font size for page header (10)


  -printColorChange <RGB> : Color for change   (1.0 0.7 0.7)
  -printColorOld <RGB>    : Color for old text (0.7 1.0 0.7)
  -printColorNew <RGB     : Color for new text (0.8 0.8 1.0)

  -plugin <name>       : Preprocess files using plugin.
  -plugininfo <info>   : Pass info to plugin (plugin specific)
  -pluginlist          : List known plugins
  -plugindump <plugin> : Dump plugin source to stdout


  -limit <lines> : Do not process more than <lines> lines.

To list all options matching a prefix, run 'eskil --query prefix'.
In tcsh use this line to get option completion:
complete eskil 'C/-/`eskil --query -`/'







|






>
>


|





>






52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

  -a <file>   : Give anscestor file for three way merge.
  -conflict   : Treat file as a merge conflict file and enter merge
                mode.
  -o <file>   : Specify merge result output file.
  -fine       : Use fine grained chunks. Useful for merging.

  -browse     : Bring up file dialog for missing files after starting
  -server     : Set up Eskil to be controllable from the outside.

  -print <file>          : Generate PDF and exit.
  -printCharsPerLine <n> : Adapt font size for this line length and wrap. (80)
  -printPaper <paper>    : Select paper size (a4)
  -printHeaderSize <n>   : Font size for page header (10)
  -printFont <fontfile>  : Select font to use in PDF, afm or ttf. If <fontfile>
                           is given as "Courier", PDF built in font is used.
  -printColorChange <RGB> : Color for change   (1.0 0.7 0.7)
  -printColorOld <RGB>    : Color for old text (0.7 1.0 0.7)
  -printColorNew <RGB>    : Color for new text (0.8 0.8 1.0)

  -plugin <name>       : Preprocess files using plugin.
  -plugininfo <info>   : Pass info to plugin (plugin specific)
  -pluginlist          : List known plugins
  -plugindump <plugin> : Dump plugin source to stdout
  -pluginallow         : Allow full access for a plugin.

  -limit <lines> : Do not process more than <lines> lines.

To list all options matching a prefix, run 'eskil --query prefix'.
In tcsh use this line to get option completion:
complete eskil 'C/-/`eskil --query -`/'

Changes to doc/editmode.txt.





1


2


3






To be written...





Hint: Right click over line numbers when in Edit Mode.


>
>
>
>
|
>
>

>
>
|
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
The files on display may be edited if you turn on Edit Mode.
This is done with the Tools->Edit Mode menu.
Onle real files may be edited. If you are comparing versions fetched from a
Revision Control system, it cannot be edited.

If an edited side has empty areas, i.e. lines that are not part of the file
and only there to line up with the other side, those will be gray.

Edit mode will not allow you to enter or remove newlines freely. Only by
copying blocks from other side lines may change.

By right clicking over a change's line numbers you get options to copy
lines and blocks between the two sides, as well as the options to save a file.

Changes to doc/eskil.txt.

31
32
33
34
35
36
37



38
39
40
41
42
43
44
         \t           version do not parse big blocks to avoid long runs.
         \t  The Char and Word options selects if the line parsing should
         \t  highlight full words only, or check single characters.
         \t  Mark last  : Last change of a line is underlined
  Colours\t: Choose highlight colours.
  Context\t: You can select that only differing lines shall be displayed,
         \t  and how many surrounding lines are shown.



  Toolbar\t: Show/hide toolbar
  Save default\t: Save current option settings in ~/.eskilrc

<b>Search Menu</b>
  Find      \t: Search dialog
  Find next \t: Repeat search
  Find prev \t: Repeat search backwards







>
>
>







31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
         \t           version do not parse big blocks to avoid long runs.
         \t  The Char and Word options selects if the line parsing should
         \t  highlight full words only, or check single characters.
         \t  Mark last  : Last change of a line is underlined
  Colours\t: Choose highlight colours.
  Context\t: You can select that only differing lines shall be displayed,
         \t  and how many surrounding lines are shown.
  Pivot  \t: If many lines in a file are equal, runtime may go up. By initially
         \t  disregarding such lines this can be kept at a more reasonable
         \t  level. The pivot sets how many lines must be equal to be ignored.
  Toolbar\t: Show/hide toolbar
  Save default\t: Save current option settings in ~/.eskilrc

<b>Search Menu</b>
  Find      \t: Search dialog
  Find next \t: Repeat search
  Find prev \t: Repeat search backwards

Changes to doc/plugins.txt.

1
2
3









4
5
6
7
8

9



10



11
12
13
14
15
16

17
18
19
20


21
22
23
24
25
26
27
28
29
30
31
32
33
Eskil provides a plugin system where a plugin can preprocess data
before being compared and displayed.










The command line options for plugins are:
  -plugin plugin     : Use plugin
  -plugininfo info   : Pass info to plugin (plugin specific)
  -plugindump plugin : Dump plugin source to stdout
  -pluginlist        : List known plugins





A plugin is a Tcl script that must follow a specific format.



Dump one of the included plugins to see what it looks like.
The plugin is executed in a safe interpreter and thus cannot do any
damage.

A plugin is set up with these global variables filled in:
::WhoAmI  : The name of the plugin

::Info    : The contents of -plugininfo parameter
::Pref    : A copy if Eskil's internal preferences array.

Example plugins are included in the kit.



A plugin may give a result that has a line-by-line correspondence to
the original, in which case the preprocessed data is used for comparing
while the original is used for displaying.  The main plugin procedure
returns 0 to signify this case.

If the plugin procedure returns 1, the processed data is used also for
displaying.

When searching for a plugin "x", files "x" and "x.tcl" will
match. The search path is current directory, "plugins" directory,
the directory where Eskil is installed, "plugins" directory where
Eskil is installed, and also the internal "plugins" wrapped into Eskil.
|


>
>
>
>
>
>
>
>
>





>

>
>
>
|
>
>
>
|

|


|
>
|
|
|
<
>
>




|




|
|
|
<
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

37
38
39
40
41
42
43
44
45
46
47
48
49
50

Eskil provides a plugin system where plugins can preprocess data
before being compared and displayed.

A plugin is a Tcl script that must follow a specific format.
Example plugins are included in the kit.
Dump one of the included plugins to see what it looks like.

When searching for a plugin "x", files "x" and "x.tcl" will
match. The search path is current directory, "plugins" directory,
the directory where Eskil is installed, "plugins" directory where
Eskil is installed, and also the internal "plugins" wrapped into Eskil.

The command line options for plugins are:
  -plugin plugin     : Use plugin
  -plugininfo info   : Pass info to plugin (plugin specific)
  -plugindump plugin : Dump plugin source to stdout
  -pluginlist        : List known plugins
  -pluginallow       : Allow full access privilege for a plugin.

A plugin may further define command line options that it accepts.
A way to see the plugin's options is to do:
eskil -plugin <plg> -help

Multiple -plugin may be given and they will be applied in the given
order. Any -plugininfo and -pluginallow belongs to the last -plugin
before them.

The plugin is executed in a safe interpreter and thus cannot do any
damage. You can turn this safety off with -pluginallow.

A plugin is set up with these global variables filled in:
::WhoAmI     : The name of the plugin
::WhoAmIFull : The full path to the plugin source
::Info       : The contents of -plugininfo parameter
::Pref       : A copy if Eskil's internal preferences array.
::File(left) : The name of the left file processed

::File(right): The name of the right file processed
::argv       : A copy of the command line from Eskil's invocation

A plugin may give a result that has a line-by-line correspondence to
the original, in which case the preprocessed data is used for comparing
while the original is used for displaying.  The main plugin procedure
should return 0 to signify this case.

If the plugin procedure returns 1, the processed data is used also for
displaying.


Directory diff only supports one plugin. The first plugin with FileCompare
defined will be used.

Changes to doc/revision.txt.

13
14
15
16
17
18
19






20
21
22

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57





58
59
60
61






62
63
64











65
66
67
68
69
70
71
<pre>eskil -r rev file.txt</pre>
  Compare file.txt with the specified version.
<pre>eskil -r rev1 -r rev2 file.txt</pre>
  Compare the two revisions. This does not involve the local copy of file.txt.

The -r options are also available in the GUI in the "Rev 1" and "Rev 2" fields.







<ul>Commit support</ul>

When comparing a file with the latest checked in version, some of the systems have support for committing directly from Eskil.  If supported, the Commit button will be enabled.


<ul>Priority between systems</ul>

If multiple systems are used within a directory Git/Hg/Bzr will be detected before CVS/SVN.  Command line options -cvs and -svn can be used to put preference on one of those systems.

<ul>Pipe a patch</ul>

Eskil can read a patch from standard input, thus allowing display from any patch generating command. Examples:
<pre>hg diff | eskil -</pre>
<pre>git diff -p --diff-filter=M master | eskil -</pre>

<ul>View all changes</ul>

If the command line option -review is used. Eskil will generate a patch
for the current tree and display it as in patch mode.
E.g. in a Mercurial directory, these show the same thing:
<pre>eskil -preview</pre>
<pre>hg diff | eskil -</pre>

If file names are given after -review, only the listed files are included. If supported,
the Commit button will be enabled allowing the viewed differences to be committed.

<ul>RCS/CVS</ul>

For RCS and CVS the arguments to -r are standard version numbers just like to their -r options.  RCS style -r\u003crev\u003e is allowed.
If a revision is an integer, it is added to the last number in the current version, thus giving relative versions.  E.g. -1 gives the second to last version.

<ul>Subversion</ul>

For Subversion the arguments to -r are standard version numbers just like its -r option.  If a revision is a negative integer, the log is searched backwards for earlier versions. E.g. -1 gives the second to last version.

<ul>Git</ul>

For Git -r <rev> is passed to show, as in "git show <rev>:<file>".






<ul>Fossil</ul>

For Fossil -r <rev> is passed to finfo, as in "fossil finfo -p <file> -r <rev>".







<ul>Mercurial</ul>

For Mercurial -r works as in "hg cat -r".












<ul>Bazaar</ul>

For Bazaar -r works as in "bzr cat -r".

<ul>ClearCase</ul>








>
>
>
>
>
>



>













|


|







|










>
>
>
>
>




>
>
>
>
>
>



>
>
>
>
>
>
>
>
>
>
>







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<pre>eskil -r rev file.txt</pre>
  Compare file.txt with the specified version.
<pre>eskil -r rev1 -r rev2 file.txt</pre>
  Compare the two revisions. This does not involve the local copy of file.txt.

The -r options are also available in the GUI in the "Rev 1" and "Rev 2" fields.

<ul>Directory Diff</ul>

Eskil can also browse and compare revisions for some systems directly in the
directory diff. It works just like for files, but give a directory on the
command line. Currently Git, Fossil and Subversion are supported.

<ul>Commit support</ul>

When comparing a file with the latest checked in version, some of the systems have support for committing directly from Eskil.  If supported, the Commit button will be enabled.
It is also possible to revert the local changes using the Revert button.

<ul>Priority between systems</ul>

If multiple systems are used within a directory Git/Hg/Bzr will be detected before CVS/SVN.  Command line options -cvs and -svn can be used to put preference on one of those systems.

<ul>Pipe a patch</ul>

Eskil can read a patch from standard input, thus allowing display from any patch generating command. Examples:
<pre>hg diff | eskil -</pre>
<pre>git diff -p --diff-filter=M master | eskil -</pre>

<ul>View all changes</ul>

If the command line option -review is used, Eskil will generate a patch
for the current tree and display it as in patch mode.
E.g. in a Mercurial directory, these show the same thing:
<pre>eskil -review</pre>
<pre>hg diff | eskil -</pre>

If file names are given after -review, only the listed files are included. If supported,
the Commit button will be enabled allowing the viewed differences to be committed.

<ul>RCS/CVS</ul>

For RCS and CVS the arguments to -r are standard version numbers just like to their -r options.
If a revision is an integer, it is added to the last number in the current version, thus giving relative versions.  E.g. -1 gives the second to last version.

<ul>Subversion</ul>

For Subversion the arguments to -r are standard version numbers just like its -r option.  If a revision is a negative integer, the log is searched backwards for earlier versions. E.g. -1 gives the second to last version.

<ul>Git</ul>

For Git -r <rev> is passed to show, as in "git show <rev>:<file>".

<pre>git config --global merge.tool eskil</pre>
<pre>git config --global mergetool.eskil.cmd 'eskil -fine -a $BASE -o $MERGED $REMOTE $LOCAL'</pre>
<pre>git config --global diff.tool eskil</pre>
<pre>git config --global difftool.eskil.cmd 'eskil $LOCAL $REMOTE'</pre>

<ul>Fossil</ul>

For Fossil -r <rev> is passed to finfo, as in "fossil finfo -p <file> -r <rev>".

Additionaly, if a revision is a negative integer, the log is searched backwards
for earlier versions. E.g. -1 gives the second to last version. The search
follows the current branch from the current version.

<pre>fossil settings gmerge-command 'eskil -fine -a "%baseline" "%merge" "%original" -o "%output"' -global</pre>

<ul>Mercurial</ul>

For Mercurial -r works as in "hg cat -r".
However, Eskil interprets zero or negative numbers as going back from the tip, i.e. -1 is one step back, corresponding to -2 in Mercurial.

Mercurial is supported in the Directory Diff, but needs the hglist extension to
display correct file sizes and dates. If not they are faked using the file's
sha1 and thus unique per file and gives a correct result in comparison.

To use Eskil for conflict resolution these config settings can be used.

[merge-tools]
eskil.args = -fine -a $base $other $local -o $output
eskil.priority = 1

<ul>Bazaar</ul>

For Bazaar -r works as in "bzr cat -r".

<ul>ClearCase</ul>

Changes to doc/tutorial.txt.

41
42
43
44
45
46
47
48
49
50
51
52
53
54


55
56
57

58
59
60
61
62
63
64
65
66
67
68
69
70

<b>Three way merge</b>
<bullet>
\u2022\tDouble click on merge.txt to bring up the diff.
\u2022\tSelect menu File->Open Ancestor File.
\u2022\tSelect file mergeanc.txt
</bullet>
The merge window will appear with most changes merged.  Conflicts are marked with gray, and a row of asterisks in the status bar.  Conflicts are resolved to the right initially.  Navigate between conflicts using shift-up/down keys. Select side with left/right keys.

<b>Regular expression preprocessing</b>

Double click on enum.c to bring up the diff.
[write something here to explain the problem and the goal]
[add reference to re_syntax and regsub manuals]


<bullet>
\u2022\tSelect menu Options->Preprocess.
\u2022\tPress "Add" to add a new preprocessing set.

\u2022\tEnter the regular expression "^.*?\\m(Apa\\w+).*$" in the field.
\u2022\tEnter "\\1" in the substitution field.
\u2022\tEnter a word starting with "Apa" in one of the example fields and see that the result is just that word.
\u2022\tPress "Ok" and select menu File->Redo Diff.
</bullet>
A shortcut for the above is to use "-prefix Apa" on the command line.

<b>Changed filename in directory diff</b>
<bullet>
\u2022\tRight click on "namechange1" in Directory Diff's left window.
\u2022\tSelect "Mark File" in the menu.
\u2022\tRight click on "namechange2" in Directory Diff's right window.
\u2022\tSelect "Compare with..." in the menu.







|



|
<

>
>


|
>
|




|







41
42
43
44
45
46
47
48
49
50
51
52

53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

<b>Three way merge</b>
<bullet>
\u2022\tDouble click on merge.txt to bring up the diff.
\u2022\tSelect menu File->Open Ancestor File.
\u2022\tSelect file mergeanc.txt
</bullet>
The merge window will appear with most changes merged.  Conflicts are marked with gray, and a row of asterisks in the status bar.  Conflicts are resolved to the right initially.  Navigate between conflicts using ctrl-up/down keys. Select side with left/right keys. Hover over the status bar to see ancestor info.

<b>Regular expression preprocessing</b>

Sometimes there are things in files being compared that you want to highlight or disregard. This preprocessing step applies search/replace regular expressions on the files being compared before lines are matched. The result is only used for determining equality. The original is always used for display and if lines differ after preprocessing, all changes are shown for that line.

[add reference to re_syntax and regsub manuals]

Double click on enum.c to bring up the diff.
<bullet>
\u2022\tSelect menu Options->Preprocess.
\u2022\tIf there is no clear set, press "Add" to add a new preprocessing set.
\u2022\tPress "Edit" to edit that preprocessing set.
\u2022\tEnter the regular expression "^.*?\\m(Apa\\w+).*$" in the Regexp field.
\u2022\tEnter "\\1" in the substitution field.
\u2022\tEnter a word starting with "Apa" in one of the example fields and see that the result is just that word.
\u2022\tPress "Ok" and select menu File->Redo Diff.
</bullet>
A shortcut for the above is to use "-prefix Apa" on the command line, or to use the "Add Prefix" button and enter "Apa" as prefix.

<b>Changed filename in directory diff</b>
<bullet>
\u2022\tRight click on "namechange1" in Directory Diff's left window.
\u2022\tSelect "Mark File" in the menu.
\u2022\tRight click on "namechange2" in Directory Diff's right window.
\u2022\tSelect "Compare with..." in the menu.

Deleted eskil.vfs/COPYING.

1
../COPYING
<


Deleted eskil.vfs/doc.

1
../doc
<


Deleted eskil.vfs/examples.

1
../examples
<


Changes to eskil.vfs/lib/psballoon/pkgIndex.tcl.

1
2
3
4
5
6
7
8
9
10
11
# Tcl package index file, version 1.1
# This file is generated by the "pkg_mkIndex" command
# and sourced either when an application starts up or
# by a "package unknown" script.  It invokes the
# "package ifneeded" command to set up package-related
# information so that packages will be loaded automatically
# in response to "package require" commands.  When this
# script is sourced, the variable $dir must contain the
# full path name of this file's directory.

package ifneeded psballoon 1.0 [list source [file join $dir psballoon.tcl]]










|
1
2
3
4
5
6
7
8
9
10
11
# Tcl package index file, version 1.1
# This file is generated by the "pkg_mkIndex" command
# and sourced either when an application starts up or
# by a "package unknown" script.  It invokes the
# "package ifneeded" command to set up package-related
# information so that packages will be loaded automatically
# in response to "package require" commands.  When this
# script is sourced, the variable $dir must contain the
# full path name of this file's directory.

package ifneeded psballoon 1.2 [list source [file join $dir psballoon.tcl]]

Changes to eskil.vfs/lib/psballoon/psballoon.tcl.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

20


21
22
23
24
25
26

















































27
28

29


































30
31
32
33
34










35
36
37


38
39
40
41
42
43
44
45
46





47
48
49
50







51
52


53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69






































70
71




72









73



74
75












76
77
78
79
80

81
82
83
84
85

86
87
88
89
90






91


92

93












94


95



96


97
98

99
100

101


102


103

104

105
106
107
108



109
110
111
112


113
#----------------------------------------------------------------------
#
#  psballoon.tcl,
#   Procedures to create help message balloons or display balloons for
#   listboxes and labels that can't display all of their contents.
#
#  Copyright (c) 2003, Peter Spjuth  (peter.spjuth@space.se)
#
#  Permission is granted to use this code under the same terms as
#  for the Tcl core code.
#
#----------------------------------------------------------------------
# $Revision: 1.1 $
#----------------------------------------------------------------------

package provide psballoon 1.0

namespace eval psballoon {
    variable balloon




    set balloon(pending) 0
    set balloon(created) 0
    set balloon(id) ""
    namespace export addBalloon
}


















































proc psballoon::addBalloon {w {msg ""}} {
    variable balloon




































    set c [winfo class $w]
    if {$msg == "" && $c != "Listbox" && $c != "Label"} {
        error "Missing message to balloon for $w ($c)"
    }
    set balloon(msg,$w) $msg










    bind $w <Enter> {
        set ::psballoon::balloon(pending) 1
        set ::psballoon::balloon(created) 0


        set ::psballoon::balloon(id) [after 500 {psballoon::createBalloon %W %x %y}]
    }
    bind $w <Button> {
        psballoon::killBalloon
    }
    bind $w <Leave> {
        psballoon::killBalloon
    }
    bind $w <Motion> {





        if {$::psballoon::balloon(pending) == 1} {
            after cancel $::psballoon::balloon(id)
        }
        if {$::psballoon::balloon(created) == 1} {







            psballoon::killBalloon
        }


        set ::psballoon::balloon(id) [after 500 {psballoon::createBalloon %W %x %y}]
        set ::psballoon::balloon(pending) 1
    }
}

proc psballoon::killBalloon {} {
    variable balloon
    if {$balloon(pending) == 1} {
        after cancel $balloon(id)
    }
    if {[winfo exists .balloon] == 1} {
        destroy .balloon
    }
    set balloon(created) 0
    set balloon(pending) 0
}







































proc psballoon::createBalloon {w mx my} {
    variable balloon




    if {$balloon(created) == 0} {









        set font [$w cget -font]



        set ww [winfo width $w]
        set ih [winfo height $w]












        set ix 0
        set iy 0
        set create 1
        set msg $balloon(msg,$w)
        if {$msg == ""} {

            switch [winfo class $w] {
                Listbox {
                    set i [$w index @$mx,$my]
                    set msg [$w get $i]
                    foreach {ix iy iw ih} [$w bbox $i] {break}

                }
                Label {
                    set msg [$w cget -text]
		    set iw [font measure $font $msg]
                }






            }


            #Don't create a balloon if the text is fully visible.

            set create [expr {$iw > $ww - 8}]












        } else {


	    set iw [font measure $font $msg]



	}


	if {$create} {
            set x [expr {[winfo rootx $w] + $ix}]

            set y [expr {[winfo rooty $w] + $iy + $ih + 2}]
            if {$x + $iw + 8 > [winfo screenwidth $w]} {

                set x [expr {[winfo screenwidth $w] - $iw - 8}]


            }


            toplevel .balloon -bg black

            wm overrideredirect .balloon 1

            label .balloon.l \
                    -text $msg -relief flat -font $font -justify left \
                    -bg #ffffaa -fg black -padx 2 -pady 0 -anchor w
            pack .balloon.l -side left -padx 1 -pady 1



            wm geometry .balloon +${x}+${y}
            set balloon(created) 1
        }
    }


}






|








|



>

>
>






>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|

>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|

|

|
>
>
>
>
>
>
>
>
>
>
|


>
>


|


|


|
>
>
>
>
>
|
|
|
|
>
>
>
>
>
>
>
|
|
>
>
|
|
<







|
|





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|

>
>
>
>
|
>
>
>
>
>
>
>
>
>
|
>
>
>
|
|
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
>
|
|
|
|
|
>
|
|
|
|
|
>
>
>
>
>
>
|
>
>
|
>
|
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
|
>
>
>
|
>
>
|
|
>
|
|
>
|
>
>
|
>
>
|
>
|
>
|
|
|
|
>
>
>
|
<
|
<
>
>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167

168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328

329

330
331
332
#----------------------------------------------------------------------
#
#  psballoon.tcl,
#   Procedures to create help message balloons or display balloons for
#   listboxes and labels that can't display all of their contents.
#
#  Copyright (c) 2003, Peter Spjuth  (peter.spjuth@gmail.com)
#
#  Permission is granted to use this code under the same terms as
#  for the Tcl core code.
#
#----------------------------------------------------------------------
# $Revision: 1.1 $
#----------------------------------------------------------------------

package provide psballoon 1.2

namespace eval psballoon {
    variable balloon
    variable config

    set config(-useframe) 0
    set balloon(W) ""
    set balloon(pending) 0
    set balloon(created) 0
    set balloon(id) ""
    namespace export addBalloon
}

# -useframe <bool>
proc psballoon::configure {args} {
    variable config
    foreach {arg val} $args {
        set config($arg) $val
    }
}

# Do some simple formatting, to be able to have cleaner text in source
proc psballoon::Fmt {msg} {
    # Remove any newlines.
    set msg [regsub -all "\n" $msg " "]
    # Remove multiple whitespace
    set msg [regsub -all {\s+} $msg " "]
    set msg [string trim $msg]
    # Any explicitly requested newlines?
    set msg [regsub -all {\\n\s*} $msg "\n"]
    # Further line breaks by length?
    set lines {}
    foreach line [split $msg \n] {
        while {[string length $line] > 80} {
            # There should be no path through this loop that does not
            # shorten $line
            set ix [string last " " $line 80]
            if {$ix < 0} {
                set ix [string first " " $line]
                if {$ix < 0} {
                    # Just cut at 80
                    set ix 80
                }
            }

            if {$ix == 0} {
                set line [string trim $line]
            } else {
                lappend lines [string range $line 0 $ix-1]
                set line [string range $line $ix+1 end]
            }
        }
        lappend lines $line
    }
    set msg [join $lines \n]
    return $msg
}

# addBalloon widget ?widgets...? ?-fmt? ?msg?
# If message is not given, it is extracted from widget. This is used to show
# e.g. labels where text might not be fully visible.
# Message may contain callbacks in [] for dynamic text.
proc psballoon::addBalloon {W args} {
    variable balloon
    variable config

    set frame $config(-useframe)

    # Last argument is message
    set msg [lindex $args end]

    set Wlist [list $W]
    foreach arg [lrange $args 0 end-1] {
        switch $arg {
            -fmt {
                # Request for formatting
                if {$msg ne ""} {
                    set msg [Fmt $msg]
                }
            }
            -frame {
                set frame 1
            }
            -top {
                set frame 0
            }
            default {
                lappend Wlist $arg
            }
        }
    }

    foreach W $Wlist {
        AddBalloon2 $W $msg $frame
    }
}

proc psballoon::AddBalloon2 {W msg frame} {
    variable balloon

    set c [winfo class $W]
    if {$msg == "" && $c != "Listbox" && $c != "Label"} {
        error "Missing message to balloon for $W ($c)"
    }
    set balloon(msg,$W) $msg
    set balloon(frame,$W) $frame

    if {$msg eq "_"} {
        bind $W <Enter> ""
        bind $W <Button> ""
        bind $W <Leave> ""
        bind $W <Motion> ""
        return
    }

    bind $W <Enter> {
        set ::psballoon::balloon(pending) 1
        set ::psballoon::balloon(created) 0
        set ::psballoon::balloon(lastX) %X
        set ::psballoon::balloon(lastY) %Y
        set ::psballoon::balloon(id) [after 500 {psballoon::createBalloon %W %x %y}]
    }
    bind $W <Button> {
        psballoon::killBalloon
    }
    bind $W <Leave> {
        psballoon::killBalloon
    }
    bind $W <Motion> {
        psballoon::motionBalloon %W %X %Y %x %y
    }
}

proc psballoon::motionBalloon {W X Y x y} {
    if {$::psballoon::balloon(pending) == 1} {
        after cancel $::psballoon::balloon(id)
    }
    if {$::psballoon::balloon(created) == 1} {
        if {$::psballoon::balloon(lastX) == $X && \
                    $::psballoon::balloon(lastY) == $Y} {
            # Sometimes when the balloon is created, a motion event with
            # the same coordinates arrive. Ignore that to avoid killing the
            # new balloon.
            return
        }
        psballoon::killBalloon
    }
    set ::psballoon::balloon(lastX) $X
    set ::psballoon::balloon(lastY) $Y
    set ::psballoon::balloon(id) [after 500 "psballoon::createBalloon $W $x $y"]
    set ::psballoon::balloon(pending) 1

}

proc psballoon::killBalloon {} {
    variable balloon
    if {$balloon(pending) == 1} {
        after cancel $balloon(id)
    }
    if {[winfo exists $balloon(W)]} {
        destroy $balloon(W)
    }
    set balloon(created) 0
    set balloon(pending) 0
}

# Measure display width needed for a text with line breaks
proc psballoon::Measure {font txt} {
    set len 0
    foreach line [split $txt \n] {
        set lw [font measure $font $line]
        if {$lw > $len} {
            set len $lw
        }
    }
    return $len
}

# Returns a list of minX maxX for each screen.
# maxX are exclusive and normally equal to the next minX
proc psballoon::FigureOutScreenWidths {W} {
    set screens {}
    # Range of X over multiple windows
    set minX [winfo vrootx $W]
    set maxX [expr {$minX + [winfo vrootwidth $W]}]
    set sW [winfo screenwidth $W]

    # Guess: If minX is negative, there is a screen from minX to 0
    if {$minX < 0} {
	lappend screens $minX 0
    }
    # Guess: Main screen is in the middle if three

    # Main screen is 0 to screenWidth
    lappend screens 0 $sW

    # Guess: If maxX is larger than screen width (main screen), there
    # is one more screen to the right
    if {$maxX > $sW} {
	lappend screens $sW $maxX
    }
    return $screens
}

proc psballoon::createBalloon {W mouseX mouseY} {
    variable balloon
    variable config
    if { ! [winfo exists $W]} {
        return
    }
    if {$balloon(created)} {
        return
    }

    # Figure out widget's font
    if {[catch {set font [$W cget -font]}]} {
        set font [ttk::style lookup [winfo class $W] -font]
    }
    # Fallback to something reasonable if font fails.
    if {$font eq ""} {
        set font TkDefaultFont
    }

    # Widget Geometry
    set wWidth [winfo width $W]
    set wHeight [winfo height $W]
    if {[winfo class $W] in {TLabelframe Labelframe TNotebook}} {
        # Put it below the label, not the entire widget.
        # 1.5 font heights is a reasonable guess
        set fontHeight [font metrics $font -linespace]
        set wHeight [expr {$fontHeight * 3 /2 }]
        # Below cursor at least
        if {$wHeight <= $mouseY} {
            set wHeight [expr {$mouseY + 5}]
        }
    }

    # Item Geometry within Widget (if any)
    set itemX 0
    set itemY 0
    set create 1
    set msg $balloon(msg,$W)
    if {$msg == ""} {
        # Extract text from widget
        switch [winfo class $W] {
            Listbox {
                set i [$W index @$mouseX,$mouseY]
                set msg [$W get $i]
                foreach {itemX itemY itemWidth wHeight} [$W bbox $i] {break}
                set bWidth $itemWidth
            }
            Label {
                set msg [$W cget -text]
                set bWidth [Measure $font $msg]
            }
        }
        # Don't create a balloon if the text is fully visible.
        set create [expr {$bWidth > $wWidth - 8}]
    } else {
        if {[string index $msg 0] eq "\["} {
            set msg [subst -novariables -nobackslashes $msg]
        }
        set bWidth [Measure $font $msg]
    }

    if {!$create} return

    # Preferred position of the balloon
    set rootX [expr {[winfo rootx $W] + $itemX}]
    set rootY [expr {[winfo rooty $W] + $itemY + $wHeight + 2}]

    set useframe $balloon(frame,$W)

    if {$useframe} {
        set top [winfo toplevel $W]
        set posX [expr {$rootX - [winfo rootx $top]}]
        set posY [expr {$rootY - [winfo rooty $top]}]
        set minX 6
        set maxX [expr {[winfo width $top] - 6}]
    } else {
        set posX $rootX
        set posY $rootY

        # Limits of current screen.
        foreach {minX maxX} [FigureOutScreenWidths $W] {
            if {$minX <= $rootX && $rootX < $maxX} break
        }
    }
    # Move it to the left as needed to fit on screen
    if {$posX + $bWidth + 8 > $maxX} {
        set posX [expr {$maxX - $bWidth - 8}]
    }

    if {$useframe} {
        if {$top eq "."} {
            set B .balloon
        } else {
            set B $top.balloon
        }
        frame $B -borderwidth 1 -relief solid
    } else {
        set B .balloon
        toplevel $B -bg black
        wm overrideredirect $B 1
    }
    label $B.l \
            -text $msg -relief flat -font $font -justify left \
            -bg #ffffaa -fg black -padx 2 -pady 0 -anchor "w"
    pack $B.l -side left -padx 1 -pady 1
    if {$useframe} {
        place $B -x $posX -y $posY -anchor nw
    } else {
        wm geometry $B +${posX}+${posY}

    }

    set balloon(W) $B
    set balloon(created) 1
}

Changes to eskil.vfs/lib/pstools/pstools.tcl.

35
36
37
38
39
40
41

42
43
44
45
46
47
48
    interp alias {} _ipexists loadinterp info exists
    interp alias {} _ipset    loadinterp set
    interp alias {} _iparray  loadinterp array

    interp invokehidden loadinterp source $file

    foreach arg $args {

        upvar 1 $arg TheVar
        if {[_iparray exists $arg]} {
            foreach {key val} [_iparray get $arg] {
                if {[info exists TheVar($key)]} {
                    set TheVar($key) $val
                }
            }







>







35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
    interp alias {} _ipexists loadinterp info exists
    interp alias {} _ipset    loadinterp set
    interp alias {} _iparray  loadinterp array

    interp invokehidden loadinterp source $file

    foreach arg $args {
        ##nagelfar vartype arg varName
        upvar 1 $arg TheVar
        if {[_iparray exists $arg]} {
            foreach {key val} [_iparray get $arg] {
                if {[info exists TheVar($key)]} {
                    set TheVar($key) $val
                }
            }
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
        $w configure -yscrollcommand [list pstools::CommonYScroll_YScroll $sby]
    }
    set yscroll($sby) $args
}

# A simple window for displaying e.g. help.
# Returns the frame where things can be put.
proc pstools::helpWin {w title} {
    destroy $w

    toplevel $w -padx 2 -pady 2
    wm title $w $title
    bind $w <Key-Return> [list destroy $w]
    bind $w <Key-Escape> [list destroy $w]
    frame $w.f
    button $w.b -text "Close" -command [list destroy $w] -width 10 \
            -default active
    pack $w.b -side bottom -pady 2
    pack $w.f -side top -expand y -fill both -padx 2 -pady 2
    focus $w
    return $w.f
}

# Figure out a place to store temporary files.
proc pstools::locateTmp {globVar} {
    upvar "#0" $globVar var

    set candidates {}







|
|

|
|
|
|
|
|

|
|
|
|







80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
        $w configure -yscrollcommand [list pstools::CommonYScroll_YScroll $sby]
    }
    set yscroll($sby) $args
}

# A simple window for displaying e.g. help.
# Returns the frame where things can be put.
proc pstools::helpWin {W title} {
    destroy $W

    toplevel $W -padx 2 -pady 2
    wm title $W $title
    bind $W <Key-Return> [list destroy $W]
    bind $W <Key-Escape> [list destroy $W]
    frame $W.f
    button $W.b -text "Close" -command [list destroy $W] -width 10 \
            -default active
    pack $W.b -side bottom -pady 2
    pack $W.f -side top -expand y -fill both -padx 2 -pady 2
    focus $W
    return $W.f
}

# Figure out a place to store temporary files.
proc pstools::locateTmp {globVar} {
    upvar "#0" $globVar var

    set candidates {}
125
126
127
128
129
130
131
132
133






134

135
136
137


138
139
140
141
142
143
144
145
146

147

148












# This is called when an editor is needed to display a file.
# It sets up the variable with the path, unless the var
# already exists.
proc pstools::locateEditor {globVar} {
    upvar "#0" $globVar var

    if {[info exists var]} return
    
    # What is a good value on Mac?






    if {$::tcl_platform(platform) == "unix"} {

        set var emacs
    } else {
        set var wordpad


        foreach dir [lsort -decreasing -dictionary \
                             [glob -nocomplain c:/apps/emacs*]] {
            set em [file join $dir bin runemacs.exe]
            set em [file normalize $em]
            if {[file exists $em]} {
                set var $em
                break
            }
        }

    }

}



















|
|
>
>
>
>
>
>
|
>
|
<
<
>
>
|
<



|



>

>
|
>
>
>
>
>
>
>
>
>
>
>
>
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143


144
145
146

147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# This is called when an editor is needed to display a file.
# It sets up the variable with the path, unless the var
# already exists.
proc pstools::locateEditor {globVar} {
    upvar "#0" $globVar var

    if {[info exists var]} return

    set candidates {}
    if {[info exists ::env(VISUAL)]} {
        lappend candidates $::env(VISUAL)
    }
    if {[info exists ::env(EDITOR)]} {
        lappend candidates $::env(EDITOR)
    }
    if {$::tcl_platform(platform) == "windows"} {
        # Try to locate some common installation points for Emacs
        set dirs [glob -nocomplain c:/apps/emacs*]


        lappend dirs {*}[glob -nocomplain "C:/Program Files/emacs*"]
        lappend dirs {*}[glob -nocomplain "C:/Program Files/emacs*/*"]
        foreach dir [lsort -decreasing -dictionary $dirs] {

            set em [file join $dir bin runemacs.exe]
            set em [file normalize $em]
            if {[file exists $em]} {
                lappend candidates $em
                break
            }
        }
        lappend candidates runemacs wordpad
    }
    # What is a good value on Mac?

    # Add some more for fallback
    lappend candidates emacs gvim gedit kate

    foreach cand $candidates {
        if {[auto_execok $cand] ne ""} {
            set var [list $cand]
            return
        }
    }
    # If we fall through here we are kind of lost...
    set var "could_not_find_editor"
}

Changes to eskil.vfs/main.tcl.

1
2
3
package require starkit
starkit::startup
source $::starkit::topdir/src/eskil.tcl


|
1
2
3
package require starkit
starkit::startup
source $::starkit::topdir/src/startup.tcl

Deleted eskil.vfs/plugins.

1
../plugins
<


Deleted eskil.vfs/src/clip.tcl.

1
../../src/clip.tcl
<


Deleted eskil.vfs/src/compare.tcl.

1
../../src/compare.tcl
<


Deleted eskil.vfs/src/dirdiff.tcl.

1
../../src/dirdiff.tcl
<


Deleted eskil.vfs/src/eskil.tcl.

1
../../src/eskil.tcl
<


Deleted eskil.vfs/src/help.tcl.

1
../../src/help.tcl
<


Deleted eskil.vfs/src/map.tcl.

1
../../src/map.tcl
<


Deleted eskil.vfs/src/merge.tcl.

1
../../src/merge.tcl
<


Deleted eskil.vfs/src/plugin.tcl.

1
../../src/plugin.tcl
<


Deleted eskil.vfs/src/print.tcl.

1
../../src/print.tcl
<


Deleted eskil.vfs/src/printobj.tcl.

1
../../src/printobj.tcl
<


Deleted eskil.vfs/src/registry.tcl.

1
../../src/registry.tcl
<


Deleted eskil.vfs/src/rev.tcl.

1
../../src/rev.tcl
<


Added eskil.vfs/tclkit.ico.

cannot compute difference between binary files

Added eskil.vfs/tclkit.inf.













>
>
>
>
>
>
1
2
3
4
5
6
CompanyName "Peter Spjuth"
LegalCopyright "Copyright (c) 1998-2021 Peter Spjuth et al."
FileDescription "File difference viewer"
ProductName "Eskil"
ProductVersion "2.8.5"
FileVersion "2.8.5"

Added examples/dir1/csv1.txt.

































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
head1,head2,head3,head4,head5,head6,head7,head8,head9,head10
MCPM,JFPC,FJ9S9,J1J,CC3,72<C,;P>HJ,RN?I,O48,BH<<
RKBO,@P:,GGG0,=8FNP,P6K,I@4,44H,48RG,1ODB:,SN:O?
CM7,M75,PML7@,I1:,EMRL,KHK,J3IQ,N4:CJ,2H:F=,L;9I
a,b,c,d,e,f,g,h,i,j
9R6,MQPJA,BL1,0NCR,6BEH0,DC=,D@F,I9J19,L8M99,DQHEE
GGK,OB?PM,I9S;K,060,AHO=8,JC46O,J>=,11?,QNEP,I1Q
:9E<C,P1S,R=PG?,SI=,<P0L5,6<C5<,=P?5E,E5HE?,@L=,N=P=1
0>A,3LPO,>27,BHA6,A3<,ODLH,01?L5,HL<D,SPR>J,KF>S=
601,QK<26,1H>,PAQ,H2P,C@=,J?59<,;MG,E>8KF,BD6I
M>SR,KA3L,PJG,HMF=G,<79I,B3;4<,1>D,JOPM,K:R7,HK<
CK9NB,G;10O,<M6=,>K2;,S6>7,O12KG,@RM,QN>,=<1<,D?CJ
PM2;K,JPQR?,<;7C3,H5SO,6M9,OL;@D,?4H>I,Q3F,E8ODJ,2P;<Q
7O94,?LFG,R=4M,<RLO7,0MHF,3K:?,ENG,8@@HG,?>2O,C7QN=
F036,>FOG0,EI<91,2H5P1,AAPP?,N58HI,RRRN;,J?A6B,;:;C,19KL
SG?H2,:<7,NLS@0,=7GA,5IJ,MLQO,4N0>=,9SRJ,7<?F:,N?9NF
;0I,J8;1:,O>?PM,>P9E5,1O9,=5?,JCGB,A940,D;8MB,B2L
E8Q2F,D12L,JL7ME,P0QPN,6@B,FEHI,QAF69,PIK,4KO,:C?
G8:,J19EL,?2L,6EOBA,D:B,6N>,2G2,F6<,@B8,@BNC?
ERG0J,:7R,DCD,QBBNQ,OBB1,G8@,9QNE,D3S,306,HCM?S
4MJ,;C4,:72BJ,=M;5,64@,:73:3,3SO,?SRJO,59<L9,@N63
63N,1RG,HS9,7AR=,2G8P,SGS,5IH1H,7QD0,=15Q,NC5
FBH;;,H@2S?,21N?,;NMCG,D4S<,<>R,73HHF,4=2,?J?7,C0;
KPG64,QI442,3OCQ,50C2K,M>9C,>9<,CNA1J,>9Q,@RJ4S,899
3;RS,H3;@5,<:C,1OAL,J=85K,PI6,OIA,FQ4S,H?53P,;SBD4
?==,<0A;,:3;Q,3FH,<3N,=D0,ASSH,>E1M,E44I,5S:
RRHBE,0A1M:,DKLIA,NSN,KA:@,BG3,=193,6G;6E,H92,<J4B
:72,6DH6O,OBI,5GOB,0R39:,<<C,6H:,<297,2E;MO,0MCR
A6LN,RD2S,AD20,F7:9,7PJ4@,O@4:9,O92,60I,1AE<,LBN
<NOC,;;9,DAI,C;@<<,?1P1,P?LEM,2GC,::IC,S=DO,?<GIS
;MS,K5HM,8HEP,<0=N,5HJC,15P@O,MSORD,9;NF,R;>@,MS?BR
KPFLI,A>LCB,Q:?C,<?H,8<JH,<@LM8,A06GB,?=R:>,72P,EME

Added examples/dir1/csv2.txt.

































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
head1	head2	head3	head4	head5	head6	head7	head8	head9	head10
MCPM	JFPC	FJ9S9	J1J	CC3	72<C	;P>HJ	RN?I	O48	BH<<
RKBO	@P:	GGG0	=8FNP	P6K	I@4	44H	48RG	1ODB:	SN:O?
CM7	M75	PML7@	I1:	EMRL	KHK	J3IQ	N4:CJ	2H:F=	L;9I
a	b	c	d	e	f	g	h	i	j
9R6	MQPJA	BL1	0NCR	6BEH0	DC=	D@F	I9J19	L8M99	DQHEE
GGK	OB?PM	I9S;K	060	AHO=8	JC46O	J>=	11?	QNEP	I1Q
:9E<C	P1S	R=PG?	SI=	<P0L5	6<C5<	=P?5E	E5HE?	@L=	N=P=1
0>A	3LPO	>27	BHA6	A3<	ODLH	01?L5	HL<D	SPR>J	KF>S=
601	QK<26	1H>	PAQ	H2P	C@=	J?59<	;MG	E>8KF	BD6I
M>SR	KA3L	PJG	HMF=G	<79I	B3;4<	1>D	JOPM	K:R7	HK<
CK9NB	G;10O	<M6=	>K2;	S6>7	O12KG	@RM	QN>	=<1<	D?CJ
PM2;K	JPQR?	<;7C3	H5SO	6M9	OL;@D	?4H>I	Q3F	E8ODJ	2P;<Q
7O94	?LFG	R=4M	<RLO7	0MHF	3K:?	ENG	8@@HG	?>2O	C7QN=
F036	>FOG0	EI<91	2H5P1	AAPP?	N58HI	RRRN;	J?A6B	;:;C	19KL
SG?H2	:<7	NLS@0	=7GA	5IJ	MLQO	4N0>=	9SRJ	7<?F:	N?9NF
;0I	J8;1:	O>?PM	>P9E5	1O9	=5?	JCGB	A940	D;8MB	B2L
E8Q2F	D12L	JL7ME	P0QPN	6@B	FEHI	QAF69	PIK	4KO	:C?
G8:	J19EL	?2L	6EOBA	D:B	6N>	2G2	F6<	@B8	@BNC?
ERG0J	:7R	DCD	QBBNQ	OBB1	G8@	9QNE	D3S	306	HCM?S
4MJ	;C4	:72BJ	=M;5	64@	:73:3	3SO	?SRJO	59<L9	@N63
63N	1RG	HS9	7AR=	2G8P	SGS	5IH1H	7QD0	=15Q	NC5
FBH;;	H@2S?	21N?	;NMCG	D4S<	<>R	73HHF	4=2	?J?7	C0;
KPG64	QI442	3OCQ	50C2K	M>9C	>9<	CNA1J	>9Q	@RJ4S	899
3;RS	H3;@5	<:C	1OAL	J=85K	PI6	OIA	FQ4S	H?53P	;SBD4
?==	<0A;	:3;Q	3FH	<3N	=D0	ASSH	>E1M	E44I	5S:
RRHBE	0A1M:	DKLIA	NSN	KA:@	BG3	=193	6G;6E	H92	<J4B
:72	6DH6O	OBI	5GOB	0R39:	<<C	6H:	<297	2E;MO	0MCR
A6LN	RD2S	AD20	F7:9	7PJ4@	O@4:9	O92	60I	1AE<	LBN
<NOC	;;9	DAI	C;@<<	?1P1	P?LEM	2GC	::IC	S=DO	?<GIS
;MS	K5HM	8HEP	<0=N	5HJC	15P@O	MSORD	9;NF	R;>@	MS?BR
KPFLI	A>LCB	Q:?C	<?H	8<JH	<@LM8	A06GB	?=R:>	72P	EME

Changes to examples/dir1/keyword.

1
2
 A file with a keyword in it.
$Revision$
|
|
1
2
A file with a keyword in it.
$Revision:abc$

Changes to examples/dir1/longline.txt.

1
2
3
abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäö
hopp
abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdef
|

|
1
2
3
abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäö
hopp
abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdef

Added examples/dir1/misc.txt.





















































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Misc examples of diffs.
A line with some inline changes.
1
11
111
One line against many.
2
22
222
Apa 1
Bepa 1
Cepa 1
3
33
333
The following is a real-life tricky case that currently do not show up well.
  WrImmediate16 TME_TmEnCfg0 0563
  WrImmediate16 TME_TmEnCfg1 0212
  WrImmediate16 TME_TmIdCfg 0200
  WrImmediate32 TME_Bat0 21323130
  WrImmediate32 TME_Bat1 21323130
  WrImmediate32 TME_Bat2 21323130
  WrImmediate32 TME_Bat3 21323130
  WrImmediate32 TME_VcCfgA 00400FCA
  WrImmediate32 TME_VcCfgB 004007C1
  WrImmediate32 TME_VcCfgC 004007C1
4
44
444
Apa 1
Bepa 1
Cepa 1
5
55
555
Apa 1
Bepa 1
Cepa 1
Depa 1
6
66
666

Added examples/dir2/csv1.txt.

































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
head1,head2,head3,head4,head5,head6,head7,head8,head9,head10
MCPM,JFPC,FJ9S9,J1J,CC3,72<C,;P>HJ,RN?I,O48,BH<<
RKBO,@P:,GGG0,=8FNP,P6K,O@GJ>,44H,48RG,1ODB:,SN:O?
CM7,M75,PML7@,I1:,EMRL,KHK,J3IQ,N4:CJ,2H:F=,L;9I
9R6,MQPJA,BL1,0NCR,6BEH0,DC=,D@F,I9J19,L8M99,DQHEE
GGK,OB?PM,I9S;K,060,AHO=8,JC46O,J>=,11?,QNEP,I1Q
:9E<C,P1S,R=PG?,SI=,<P0L5,6<C5<,G012D,E5HE?,@L=,=?=
0>A,3LPO,>27,BHA6,A3<,ODLH,01?L5,PN:R,SPR>J,KF>S=
601,QK<26,1H>,PAQ,H2P,C@=,J?59<,;MG,E>8KF,BD6I
M>SR,KA3L,PJG,HMF=G,<79I,B3;4<,1>D,JOPM,=AR,HK<
CK9NB,G;10O,:4JG9,>K2;,S6>7,O12KG,@RM,QN>,=<1<,D?CJ
PM2;K,JPQR?,7DI,H5SO,6SJA,OL;@D,?4H>I,Q3F,E8ODJ,2P;<Q
7O94,?LFG,R=4M,<RLO7,0MHF,3K:?,ENG,8@@HG,EM>,C7QN=
F036,>FOG0,EI<91,2H5P1,AAPP?,N58HI,RRRN;,J?A6B,;:;C,19KL
0PPF@,:<7,NLS@0,=7GA,5IJ,MLQO,4N0>=,9SRJ,7<?F:,N?9NF
;0I,J8;1:,O>?PM,>P9E5,72K5,=5?,JCGB,03I<,D;8MB,B2L
E8Q2F,D12L,JL7ME,PH01C,:A21,FEHI,QAF69,PIK,4KO,SFPI
G8:,F51,?2L,6EOBA,D:B,6N>,2G2,F6<,Q=7K,@BNC?
ERG0J,:7R,DCD,QBBNQ,OBB1,G8@,9QNE,=4M:,306,HCM?S
4MJ,;C4,:72BJ,=M;5,64@,?1A0D,3SO,A2C7,59<L9,?5B<
63N,1RG,HS9,7AR=,2G8P,SGS,5IH1H,7QD0,=15Q,NC5
FBH;;,H@2S?,21N?,;NMCG,D4S<,<>R,73HHF,4=2,?J?7,C0;
KPG64,QI442,3OCQ,50C2K,M>9C,>9<,CNA1J,>9Q,@RJ4S,899
3;RS,H3;@5,<73,1OAL,J=85K,PI6,OIA,FQ4S,H?53P,;SBD4
?==,<0A;,:3;Q,3FH,<3N,=D0,ASSH,:>M,E44I,5S:
RRHBE,0A1M:,DKLIA,=DM,KA:@,66S6,=193,6G;6E,H92,<J4B
:72,6DH6O,OBI,5GOB,0R39:,<<C,LFQ<,<297,2E;MO,<S9AI
a,b,c,d,e,f,g,h,i,j
A6LN,RD2S,AD20,F7:9,7PJ4@,O@4:9,O92,60I,1AE<,LBN
<NOC,;;9,DAI,C;@<<,?1P1,P?LEM,2GC,::IC,S=DO,?<GIS
;MS,K5HM,8HEP,<0=N,5HJC,15P@O,MSORD,9;NF,R;>@,MS?BR
KPFLI,A>LCB,Q:?C,D49J?,KKCI,<@LM8,A06GB,?=R:>,72P,EME

Added examples/dir2/csv2.txt.

































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
head1	head2	head3	head4	head5	head6	head7	head8	head9	head10
MCPM	JFPC	FJ9S9	J1J	CC3	72<C	;P>HJ	RN?I	O48	BH<<
RKBO	@P:	GGG0	=8FNP	P6K	O@GJ>	44H	48RG	1ODB:	SN:O?
CM7	M75	PML7@	I1:	EMRL	KHK	J3IQ	N4:CJ	2H:F=	L;9I
9R6	MQPJA	BL1	0NCR	6BEH0	DC=	D@F	I9J19	L8M99	DQHEE
GGK	OB?PM	I9S;K	060	AHO=8	JC46O	J>=	11?	QNEP	I1Q
:9E<C	P1S	R=PG?	SI=	<P0L5	6<C5<	G012D	E5HE?	@L=	=?=
0>A	3LPO	>27	BHA6	A3<	ODLH	01?L5	PN:R	SPR>J	KF>S=
601	QK<26	1H>	PAQ	H2P	C@=	J?59<	;MG	E>8KF	BD6I
M>SR	KA3L	PJG	HMF=G	<79I	B3;4<	1>D	JOPM	=AR	HK<
CK9NB	G;10O	:4JG9	>K2;	S6>7	O12KG	@RM	QN>	=<1<	D?CJ
PM2;K	JPQR?	7DI	H5SO	6SJA	OL;@D	?4H>I	Q3F	E8ODJ	2P;<Q
7O94	?LFG	R=4M	<RLO7	0MHF	3K:?	ENG	8@@HG	EM>	C7QN=
F036	>FOG0	EI<91	2H5P1	AAPP?	N58HI	RRRN;	J?A6B	;:;C	19KL
0PPF@	:<7	NLS@0	=7GA	5IJ	MLQO	4N0>=	9SRJ	7<?F:	N?9NF
;0I	J8;1:	O>?PM	>P9E5	72K5	=5?	JCGB	03I<	D;8MB	B2L
E8Q2F	D12L	JL7ME	PH01C	:A21	FEHI	QAF69	PIK	4KO	SFPI
G8:	F51	?2L	6EOBA	D:B	6N>	2G2	F6<	Q=7K	@BNC?
ERG0J	:7R	DCD	QBBNQ	OBB1	G8@	9QNE	=4M:	306	HCM?S
4MJ	;C4	:72BJ	=M;5	64@	?1A0D	3SO	A2C7	59<L9	?5B<
63N	1RG	HS9	7AR=	2G8P	SGS	5IH1H	7QD0	=15Q	NC5
FBH;;	H@2S?	21N?	;NMCG	D4S<	<>R	73HHF	4=2	?J?7	C0;
KPG64	QI442	3OCQ	50C2K	M>9C	>9<	CNA1J	>9Q	@RJ4S	899
3;RS	H3;@5	<73	1OAL	J=85K	PI6	OIA	FQ4S	H?53P	;SBD4
?==	<0A;	:3;Q	3FH	<3N	=D0	ASSH	:>M	E44I	5S:
RRHBE	0A1M:	DKLIA	=DM	KA:@	66S6	=193	6G;6E	H92	<J4B
:72	6DH6O	OBI	5GOB	0R39:	<<C	LFQ<	<297	2E;MO	<S9AI
a	b	c	d	e	f	g	h	i	j
A6LN	RD2S	AD20	F7:9	7PJ4@	O@4:9	O92	60I	1AE<	LBN
<NOC	;;9	DAI	C;@<<	?1P1	P?LEM	2GC	::IC	S=DO	?<GIS
;MS	K5HM	8HEP	<0=N	5HJC	15P@O	MSORD	9;NF	R;>@	MS?BR
KPFLI	A>LCB	Q:?C	D49J?	KKCI	<@LM8	A06GB	?=R:>	72P	EME

Changes to examples/dir2/keyword.

1
2
A file with a keyword in it.
$Revision$

|
1
2
A file with a keyword in it.
$Revision:b$

Changes to examples/dir2/longline.txt.

1
2
3
4
5
abcdefghijklmnop
abcdefxhijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrs1uvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklm2opqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäö
hej
hopp
abcdefghi2klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyza4cdef

|


|
1
2
3
4
5
abcdefghijklmnop
abcdefxhijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrs1uvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklm2opqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäö
hej
hopp
abcdefghi2klmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzåäöabcdefghijklmnopqrstuvwxyza4cdef

Added examples/dir2/misc.txt.











































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Misc examples of diffs.
A line 1 with sume line changes.
1
11
111
Surrounding line1
One line against many others.
Surrounding line2
Surrounding line3
2
22
222
Bepa 2
Gurka
Cepa 2
3
33
333
The following is a real-life tricky case that currently do not show up well.
  WrImmediate16 TME_TmIdCfg 0204
  WrImmediate16 TME_VcCfgA 0FC7
  WrImmediate32 TME_VcCfgB 00400FC1
  WrImmediate32 TME_VcCfgC 00400FC1
4
44
444
Cepa 2
Gurka
5
55
555
Bepa 2
Apa 2
Cepa 2
6
66
666

Added htdocs/changes.wiki.























































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
<title>Changes</title>

Upcoming changes (not yet released):

  *  TBW

Changes in v2.8.5 (2023-04-27):

  *  Added -printLineSpace.
  *  Support -context/-w/-b for -review.
  *  Added gunzip plugin.
  *  Added -includedir and -includefile options for directory diff.
  *  Allow multiple pairs of files on the command line to open multiple windows.
  *  Added -subst command line, to access PreProcess Subst function.
  *  Better SVN commit, handling added directories.
  *  Ctrl-E to enable Edit Mode.
  *  Allow copying selected lines in Edit Mode.
  *  Allow deleting any line in Edit Mode.

Changes in v2.8.4 (2019-02-06):

  *  Commit dialog includes a selection of files, for partial commit.
  *  Better support for multiple files and directories with -review.
  *  Added save-reload option in edit mode.
  *  Bug-fix with deleted files in GIT directory diff.

Changes in v2.8.3 (2018-06-13):

  *  More features in Four-way diff.
  *  Better visibility that commit happened.
  *  Shortcuts in preprocess dialog for common patterns.

Changes in v2.8.2 (2018-05-13):

  *  Added Four-way diff, for comparing changes.
  *  Bug-fix in revision handling for Fossil.

Changes in v2.8.1 (2018-01-14):

  *  Repaired plugins for directory diff (broken by multi plugin in 2.8.0).
  *  Added -excludedir and -excludefile options for directory diff.
  *  Handle GIT revisions better for directory diff.
  *  Support -nocase in directory diff.
  *  Directory diff no longer shortcuts for files with same size&mtime.
  *  Removed support for old RCS style -rREV command line.
  *  Corrected detected of Emacs for Registry. (Broken in 2.7.4)

Changes in v2.8.0 (2017-12-05):

  *  Handle multiple plugins.
  *  Upgraded DiffUtil to 0.4.0 which is significantly faster for large files.
  *  Default pivot is now 10. Added GUI choice for pivot 1.

Changes in v2.7.4 (2017-11-30):

  *  Handle multiple preprocess definitions that can be saved with preferences.
  *  Preserve line endings when saving during Edit Mode.
  *  Added -gz flag to compare compressed files.
  *  Allow multi select in table diff.
  *  Allow one side of directory diff to be protected from editing.
  *  Allow directories to be created in directory diff.
  *  When needing an editor, try VISUAL and EDITOR plus a set of common ones.

Changes in v2.7.3 (2016-08-30):

  *  Requires Tcl 8.6
  *  GUI support for table and separator.

Changes in v2.7.2 (2016-08-15):

  *  Corrected right side numbering when parsing patch.
  *  Word parse now consistently uses non-space as word char.
  *  New [./table.wiki | table] view, activated by -table, when comparing tables.
  *  Mercurial support for Directory Diff, Commit, Revert and Log.
  *  GIT support for negative revisions and log view.
  *  Printed PDF is now compressed.
  *  Printed PDF from patch view adds page break between files.
  *  Plugins can define command line options they accept.
  *  Plugins can read ::argv to know the given command line.
  *  New plugin for binary files
  *  New plugin for CSV files
  *  Added option -sep, to set a separator that makes input be interpreted
     in a table like manner.
  *  New plugin for PDF files
  *  Added option -pluginallow to run plugins in a standard interpreter instead
     of a safe one. Thus a plugin could use e.g. exec.
  *  Allow plugins to yield if Eskil is run in Tcl 8.6 or newer.
     Added swap plugin to exemplify this.

Changes in v2.7 (2015-03-09):

  *  Directory Diff support for GIT, Fossil and Subversion.
     Directly browse and compare two revisions.
  *  Plugins in Directory Diff.
  *  Added option -printFont to select font for PDF generation.
     Default font changed to a True Type font.
     Use "-printFont Courier" to fall back on PDF built-in.
  *  Mac supported

Changes in v2.6.7 (2014-11-13):

  *  Fixed Directory Diff that stopped working in 2.6.6

Changes in v2.6.6 (2014-10-27):

  *  Avoid font error with balloon help.
  *  Store default preferences as comment in rc file.

Changes in v2.6.5 (2014-01-24):

  *  Fixed error printing patch with only deleted or inserted files.
  *  Support direct print in patch mode.
  *  Detect Subversion 1.7 working copy

Changes in v2.6.4 (2013-08-22):

  *  Include afm font for consistent PDF printing.
  *  Add .pdf to print file by default
  *  Fixed bug that marked extra changes in scroll map when displaying a patch.
  *  Avoid getting double .-files in dirdiff on Windows.
  *  Corrected display of ancestor lines in three-way merge.

Changes in v2.6.3 (2012-08-21):

  *  Added Revert button in Revision mode 
  *  Added -pivot flag to control diff algorithm. This cuts down processing time for certain large files.

Changes in v2.6.2 (2012-06-18):

  *  Fixed bug where extra lines showed when displaying only diffs (no context).
  *  Include added files when using -review with Fossil.
  *  Improved plugin viewer and PDF print dialog.
  *  Support regsub preprocessing controlled per side.
  *  Support branches in Subversion.
  *  Support negative revisions with Fossil.
  *  Added -nocdiff command line flag for debug.
  *  Fixed a bug where alignment was not properly shown in output.
  *  Fixed out-of-stack crash.

Changes in v2.6.1 (2011-11-01):

  *  Eskil [http://eskil.tcl.tk|re-hosted] and changed to use [http://www.fossil-scm.org|Fossil].
  *  Fixed directory diff appearance on Windows.
  *  Fixed bug where copy button in directory diff picked the wrong file.
  *  Fixed bug where plugins were not found in VFS.

Changes in v2.6 (2011-10-30):

  *  Support commit in Git and Fossil.
  *  Support commit, list of files and revisions with -review.
  *  Added Paste Patch command.
  *  New -pluginlist option. New GUI for plugin selection.
  *  Added three-way merge.
  *  Auto-detect line endings in ancestor file to select merge output.
  *  Fully rewritten directory diff with new design.
  *  Set alignment with drag & drop.

Changes in v2.5 (2011-04-01):

  *  Requires Tcl 8.5.
  *  Plugins: Added dump, better documentation.
  *  Dir diff: Added step down.
  *  Dir diff: Redesigned to display less.
  *  Support for Perforce and Fossil.
  *  Allow zero lines of context.
  *  Detect and display annotation in patch view.
  *  Select colors for PDF print. Command line options for PDF.
  *  Removed support for Postscript output.
  *  Support File Drop with TkDnd.
  *  Handle line endings in conflict and merge.

Changes in v2.4 (2009-01-08):

  *  Completely redesigned directory diff.
  *  Added a log viewer in revision mode.
  *  Added color option for unchanged text.
  *  Plugins support. 
  *  Support for Subversion, Mercurial and Bazaar.
  *  Support commit in Subversion.
  *  Added -review for displaying all changes in a tree.
  *  Support command line "-" to read a patch from std input.

Changes in v2.3 (2007-12-05):

  *  Added -printpdf command line option.
  *  Fixed line numbering in PDF with big line numbers.
  *  Started on GIT support.
  *  Anything on the command line is checked for a starkit.
     Kits are mounted and treated as directories.

Changes in v2.2 (2007-04-05):

  *  Added experimental -nonewline command option.
  *  Added -close command option.
  *  Added experimental PDF print.
  *  Added dirdiff preferences and filters.
  *  Smarter save in merge. FR 2957
  *  Added commit button for CVS. FR 2780
  *  Bug fixes include: Kits are mounted read-only, fixed dir diff window menu,
     improved patch file parsing.

Changes in v2.1 (2006-06-02):

  *  Added -preprocess command line option.
  *  Added -foreach command line option.
  *  Added -context command line option.
  *  Handle starkits as directories in dir diff.
  *  Support relative -r with CVS.

Changes in v2.0.7 (2004-12-14):

  *  Added regsub preprocessing option.
  *  Added -prefix command line option.
  *  Improved merge window.
  *  Added ignore keyword option to directory diff.

Changes in v2.0.6 (2004-10-19):

  *  Added Ignore Digit option.
  *  Fixed bug in -r for ClearCase.
  *  Edit Mode made more robust.

Changes in v2.0.5 (2004-08-20):

  *  Option -r can now be used with ClearCase diff.
  *  Edit Mode allows simple editing in the diff display and saving.

Changes in v2.0.4 (2004-06-17):

  *  Added ignore case option.
  *  Improved alignment function.

Changes in v2.0.3 (2004-05-26):

  *  Added context options for "Diffs only" mode.
  *  Rewrote how "Separate Diff" and "Align" works.  The latter now only happens after a "Redo Diff".
  *  Added scroll map and some more context menu options in Directory Diff.

Changes in v2.0.2 (2004-05-03):

  *  Fixed a bug in ClearCase support.
  *  Improved enscript usage in print command.
  *  Added "mark file" in dirdiff context menu.

Changes in v2.0.1 (2004-02-10):

  *  Added preference for width and height.
  *  Added Tools menu to directory diff window.
  *  Made it simpler to save a conflict in the same file.

First public release v2.0 (2004-01-30):

Added htdocs/download.html.







































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<div class='fossil-doc' data-title='Download'>

Downloads are available both as a <a href="http://wiki.tcl.tk/starkit">Starkit</a>
and as <a href="http://wiki.tcl.tk/starpack">Starpacks</a> for some platforms.
If you need a Starpack for some other platform, you can
<a href="starpack.wiki">generate it yourself</a>.
<p>
Eskil's application source is licensed under GPL, but the bundled packages
included in the starkit are under the same license as Tcl.

<h2>Version 2.8.5 (2023-04-27)</h2><ul>
<li><a href="../../../uv/htdocs/download/eskil285.kit">eskil285.kit</a>
&nbsp;&nbsp;&nbsp;<small>(1387506 bytes)</small> (<a href="http://wiki.tcl.tk/starkit">Starkit</a>)
<li><a href="../../../uv/htdocs/download/eskil285.linux.gz">eskil285.linux.gz</a>
&nbsp;&nbsp;&nbsp;<small>(4623432 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Linux)
<li><a href="../../../uv/htdocs/download/eskil285.win.zip">eskil285.win.zip</a>
&nbsp;&nbsp;&nbsp;<small>(4485745 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Windows)
<li><a href="../../../uv/htdocs/download/eskil285.mac.gz">eskil285.mac.gz</a>
&nbsp;&nbsp;&nbsp;<small>(3320736 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Mac)
</ul>

<h2>Version 2.8.4 (2019-02-06)</h2><ul>
<li><a href="../../../uv/htdocs/download/eskil284.kit">eskil284.kit</a>
&nbsp;&nbsp;&nbsp;<small>(1378090 bytes)</small> (<a href="http://wiki.tcl.tk/starkit">Starkit</a>)
<li><a href="../../../uv/htdocs/download/eskil284.linux.gz">eskil284.linux.gz</a>
&nbsp;&nbsp;&nbsp;<small>(4613039 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Linux)
<li><a href="../../../uv/htdocs/download/eskil284.win.zip">eskil284.win.zip</a>
&nbsp;&nbsp;&nbsp;<small>(3082883 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Windows)
<li><a href="../../../uv/htdocs/download/eskil284.mac.gz">eskil284.mac.gz</a>
&nbsp;&nbsp;&nbsp;<small>(3310490 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Mac)
</ul>

<h2>Version 2.8.3</h2><ul>
<li><a href="../../../uv/htdocs/download/eskil283.kit">eskil283.kit</a>
&nbsp;&nbsp;&nbsp;<small>(1448552 bytes)</small> (<a href="http://wiki.tcl.tk/starkit">Starkit</a>)
<li><a href="../../../uv/htdocs/download/eskil283.linux.gz">eskil283.linux.gz</a>
&nbsp;&nbsp;&nbsp;<small>(4684368 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Linux)
<li><a href="../../../uv/htdocs/download/eskil283.win.zip">eskil283.win.zip</a>
&nbsp;&nbsp;&nbsp;<small>(4588178 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Windows)
<li><a href="../../../uv/htdocs/download/eskil283.mac.gz">eskil283.mac.gz</a>
&nbsp;&nbsp;&nbsp;<small>(3375574 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Mac)
</ul>

<h2>Version 2.8.2</h2><ul>
<li><a href="../../../uv/htdocs/download/eskil282.kit">eskil282.kit</a>
&nbsp;&nbsp;&nbsp;<small>(1364710 bytes)</small> (<a href="http://wiki.tcl.tk/starkit">Starkit</a>)
<li><a href="../../../uv/htdocs/download/eskil282.linux.gz">eskil282.linux.gz</a>
&nbsp;&nbsp;&nbsp;<small>(4600691 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Linux)
<li><a href="../../../uv/htdocs/download/eskil282.win.zip">eskil282.win.zip</a>
&nbsp;&nbsp;&nbsp;<small>(4504351 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Windows)
<li><a href="../../../uv/htdocs/download/eskil282.mac.gz">eskil282.mac.gz</a>
&nbsp;&nbsp;&nbsp;<small>(3291778 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Mac)
</ul>

<h2>Version 2.8.1</h2><ul>
<li><a href="../../../uv/htdocs/download/eskil281.kit">eskil281.kit</a>
&nbsp;&nbsp;&nbsp;<small>(1361190 bytes)</small> (<a href="http://wiki.tcl.tk/starkit">Starkit</a>)
<li><a href="../../../uv/htdocs/download/eskil281.linux.gz">eskil281.linux.gz</a>
&nbsp;&nbsp;&nbsp;<small>(4596746 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Linux)
<li><a href="../../../uv/htdocs/download/eskil281.win.zip">eskil281.win.zip</a>
&nbsp;&nbsp;&nbsp;<small>(4500468 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Windows)
<li><a href="../../../uv/htdocs/download/eskil281.mac.gz">eskil281.mac.gz</a>
&nbsp;&nbsp;&nbsp;<small>(3287876 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Mac)
</ul>

<h2>Version 2.8.0</h2><ul>
<li><a href="../../../uv/htdocs/download/eskil280.kit">eskil280.kit</a>
&nbsp;&nbsp;&nbsp;<small>(1343683 bytes)</small> (<a href="http://wiki.tcl.tk/starkit">Starkit</a>)
<li><a href="../../../uv/htdocs/download/eskil280.linux.gz">eskil280.linux.gz</a>
&nbsp;&nbsp;&nbsp;<small>(4579524 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Linux)
<li><a href="../../../uv/htdocs/download/eskil280.win.zip">eskil280.win.zip</a>
&nbsp;&nbsp;&nbsp;<small>(4483374 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Windows)
<li><a href="../../../uv/htdocs/download/eskil280.mac.gz">eskil280.mac.gz</a>
&nbsp;&nbsp;&nbsp;<small>(3270590 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Mac)
</ul>

<h2>Version 2.7.4</h2><ul>
<li><a href="../../../uv/htdocs/download/eskil274.kit">eskil274.kit</a>
&nbsp;&nbsp;&nbsp;<small>(1277570 bytes)</small> (<a href="http://wiki.tcl.tk/starkit">Starkit</a>)
<li><a href="../../../uv/htdocs/download/eskil274.linux.gz">eskil274.linux.gz</a>
&nbsp;&nbsp;&nbsp;<small>(4516336 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Linux)
<li><a href="../../../uv/htdocs/download/eskil274.win.zip">eskil274.win.zip</a>
&nbsp;&nbsp;&nbsp;<small>(4419956 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Windows)
<li><a href="../../../uv/htdocs/download/eskil274.mac.gz">eskil274.mac.gz</a>
&nbsp;&nbsp;&nbsp;<small>(3207473 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Mac)
</ul>

<h2>Version 2.7.3</h2><ul>
<li><a href="../../../uv/htdocs/download/eskil273.kit">eskil273.kit</a>
&nbsp;&nbsp;&nbsp;<small>(1271818 bytes)</small> (<a href="http://wiki.tcl.tk/starkit">Starkit</a>)
<li><a href="../../../uv/htdocs/download/eskil273.linux.gz">eskil273.linux.gz</a>
&nbsp;&nbsp;&nbsp;<small>(4510733 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Linux)
<li><a href="../../../uv/htdocs/download/eskil273.win.zip">eskil273.win.zip</a>
&nbsp;&nbsp;&nbsp;<small>(4414313 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Windows)
<li><a href="../../../uv/htdocs/download/eskil273.mac.gz">eskil273.mac.gz</a>
&nbsp;&nbsp;&nbsp;<small>(3201758 bytes)</small> (<a href="http://wiki.tcl.tk/starpack">Starpack</a> for Mac)
</ul>

</div>

Added htdocs/editmode.wiki.











































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<title>Edit Mode</title>
<h1>Edit Mode</h1>

The files on display may be edited if you turn on Edit Mode.
This is done with the Tools->Edit Mode menu.
Only real files may be edited. If you are comparing versions fetched from a
[./revision.wiki|Revision Control] system, it cannot be edited.

If an edited side has empty areas, i.e. lines that are not part of the file
and only there to line up with the other side, those will be gray.

Edit mode will not allow you to enter or remove newlines freely. Only by
copying blocks from other side lines may change.

By right clicking over a change's line numbers you get options to copy
lines and blocks between the two sides, as well as the options to save a file.

See also [./merge.wiki|Merge].



Added htdocs/eskil1.png.

cannot compute difference between binary files

Added htdocs/eskil2.png.

cannot compute difference between binary files

Added htdocs/eskil3.png.

cannot compute difference between binary files

Added htdocs/fossil.wiki.



































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<title>Fossil Support</title>
<h1>Fossil Support</h1>

<h2>Introduction</h2>

Eskil can compare versions in many revision control systems including
[http://www.fossil-scm.org | Fossil].

If you specify only one file on the command line to Eskil, it will
automatically detect if the file is under revision control and enter revision
control mode.

By default the local file is compared against the latest checked in version.
This is for the common case when you just want to know what you have changed
before checking in.

You can use the -r option to select which versions to compare.
The -r option works as in fossil finfo. If a revision is zero or a negative
integer,
the log is searched backwards for earlier versions. E.g. -1 gives the second
to last version. The search follows the current branch from the current version.


Examples:

Compare file.txt with the latest checked in version:
<pre>eskil file.txt</pre>

Compare file.txt with the specified version:
<pre>eskil -r rev file.txt</pre>

Compare the two revisions. This does not involve the local copy of file.txt.
<pre>eskil -r rev1 -r rev2 file.txt</pre>

The -r options are also available in the GUI in the "Rev 1" and "Rev 2" fields.

<h2>Directory Diff</h2>

Eskil can also browse and compare Fossil revisions in the directory diff.
It works just like for files, but give a directory on the command line.

<h2>Commit support</h2>

When comparing a file with the latest checked in version, Eskil can commit
directly to Fossil.

<h2>View all changes</h2>

If the command line option -review is used, Eskil will generate a patch
for the current tree and display it as in patch mode.

<verbatim>eskil -review [files] </verbatim>

If file names are given after -review, only the listed files are included.
The Commit button will be enabled allowing the viewed differences to be
committed directly from Eskil.

<h2>Conflict merging</h2>
Eskil can be used as the conflict resolution tool for Fossil by configuring
the gmerge-command setting like this:

<pre>fossil settings gmerge-command 'eskil -fine -a "%baseline" "%merge" "%original" -o "%output"' -global</pre>



Added htdocs/index.html.



























































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<div class='fossil-doc' data-title='A graphical view of file and directory differences'>

<div class="submenu">
<a class="label" href="changes.wiki">Changes</a>
</div>

<h3>About Eskil</h3>
Eskil is a graphical tool to view the differences between files and directories. It supports version management and patch files and has various preprocessing and alignment options to improve the display of tricky diffs.
<p>
Pronunciation: The E is short, like in "set", the rest is like "skill".
<p>
Any feedback, good or bad, can be sent to
&lt;peter <i>dot</i> spjuth <i>at</i> gmail <i>dot</i> com&gt; or added as a <a href='../../../ticket'>Ticket</a>.
<p>
It is similar but unrelated to <a href="http://wiki.tcl.tk/tkdiff">TkDiff</a>.

<a name="EskilFeatures"></a><h3>Features</h3>

<ul>
<li>Highlights changes within a line.</li>
<li>Matches similar lines within a changed block to better show changed
lines that are adjacent to added/removed lines.</li>
<li>Recursive directory diff.</li>
<li><a href="fossil.wiki">Fossil</a>/<a href="revision.wiki">CVS/RCS/ClearCase/GIT/SVN/BZR/HG/Perforce</a> diff.</li>
<li>Conflict <a href="merge.wiki">merge</a> and three-way merge.</li>
<li>Commit changes directly from Eskil.</li>
<li>View patch, from file or clipboard.</li>
<li><a href="print.wiki">Print</a> to PDF.</li>
<li>"Clip diff"</li>
<li><a href="plugins.wiki">Plugins</a> for preprocessing files.</li> 
<li>Alignment and block diff functions for tricky diffs.</li>
<li><a href="editmode.wiki">Edit</a> and Save file from diff window.</li>
<li><a href="starkit.wiki">Starkit</a> compare and browsing.</li>
</ul>

<a name="EskilScreenshots"></a><h3>Screenshots</h3>

<img src="eskil1.png">
<p>
A "zoom" feature for long lines.<p>
<img src="eskil2.png"><br>
<p>Directory Diff.<p>
<img src="eskil3.png"><br>

</div>

Added htdocs/merge.wiki.





































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<title>Merge</title>
<h1>Merge</h1>

The files on display may be merged and saved via the Tools->Merge menu.
The Merge window will also appear if you open an Ancestor File (for three-way
merge) or a Conflict File, via the File menu or command line.

In the Merge window you can navigate between differences with up/down keys
and select between left and right side with left/right keys. See the Goto
menu for other navigation keys.

With the toolbar buttons and menus you can also choose to keep both sides.
There is All Left/Right commands in the menu to select for all differences.

The text can be freely edited. Use escape key to get focus out of the text
window to allow navigating as described above.

The status bar show basic info about the selected difference. A conflict
is marked with ***. Hover over the status line to get more info.

When saving, if no output file has been previously been selected, you get the
choice to overwrite either side or browse for another file.

The following command line parameters are merge related:

  -a <file> : Ancestor file for three-way merge.

  -o <file> : Output file for merge result.

  -fine     : Use fine grained chunks. Left/right choice is made per line instead of per chunk.

  -conflict : Treat input file as a file with merge conflict markers.

See also [./editmode.wiki|Edit Mode], and [./revision.wiki|Revision Control Support].

Added htdocs/plugins.wiki.















































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<title>Plugins</title>

<h1>Introduction</h1>

Eskil provides a plugin system where a plugin can preprocess data
before being compared and displayed.

A plugin is a Tcl script that must follow a specific format.
Example plugins are included in the kit.
Dump one of the included plugins to see what it looks like.

When searching for a plugin "x", files "x" and "x.tcl" will
match. The search path is current directory, "plugins" directory,
the directory where Eskil is installed, "plugins" directory where
Eskil is installed, and also the internal "plugins" wrapped into Eskil.

<h1>Usage</h1>

The command line options for plugins are:
  *  -plugin plugin     : Use plugin
  *  -plugininfo info   : Pass info to plugin (plugin specific)
  *  -plugindump plugin : Dump plugin source to stdout
  *  -pluginlist        : List known plugins
  *  -pluginallow       : Allow full access privilege for a plugin.

A plugin may further define command line options that it accepts.
A way to see the plugin's options is to do:
<pre>eskil -plugin &lt;plg&gt; -help</pre>

Multiple -plugin may be given and they will be applied in the given
order. Any -plugininfo and -pluginallow belongs to the last -plugin
before them.

<h1>General Format</h1>

A plugin is a Tcl script file that must start with the verbatim sequence
"##Eskil Plugin :". A plugin is sourced and used in its own safe
interpreter and thus have free access to its own global space. Hookup
points are defined by declaring specifically named procedures as specified
below, and apart from those, a plugin can define and do whatever within
the limits of a safe interpreter.

In addition to the standard safe interpreter environment, a plugin has
access to stdout as well. By using the command line option -pluginallow,
the plugin is run in a standard interpreter and may e.g. do exec to utilize
external tools.

A plugin is set up with these global variables filled in:
  *  ::WhoAmI     : The name of the plugin
  *  ::WhoAmIFull : The full path to the plugin source
  *  ::Info       : The contents of -plugininfo parameter
  *  ::Pref       : A copy if Eskil's internal preferences array.
  *  ::File(left) : The name of the left file processed
  *  ::File(right): The name of the right file processed
  *  ::argv       : A copy of the command line from Eskil's invocation

<h2>Additional options</h2>
A plugin can declare command line options that should be accepted by Eskil.
They will be passed on to the plugin through the ::argv list.
If the initial "##Eskil" line is followed by comments formatted as below,
it adds options. Any empty line will end parsing for such lines.

A line like "## Option -<option>" declares an option that takes a value and
a line like "## Flag -<option>" declares an option without value. The rest of
the line after the option name is functionally ignored and can be used for
comments. It is included in command line help, so the rest should preferably
be formatted as " : Explanation" if used.

<h1>File plugin</h1>

To process the files being compared, the following procedure should be
defined in the plugin file:

<pre>proc PreProcess {side chi cho} {...}</pre>

The arguments given to PreProcess are:

  *  side : left or right, indicating which file is being handled
  *  chi : An input channel for reading the original file
  *  cho : An output channel for writing the processed file

A plugin may give a result that has a line-by-line correspondence to
the original, in which case the preprocessed data is used for comparing
while the original is used for displaying.  The PreProcess procedure
should return 0 to signify this case.

If the PreProcess procedure returns 1, the processed data is used also for
displaying.

If Eskil is run with Tcl 8.6 or newer, PreProcess is run as a coroutine
and may yield. The left and right side will then be called alternately
until they return. This allows a plugin to take both sides into account
for decisions if needed.

<h1>Directory plugin</h1>

To be used for file comparison in a directory diff, the following procedure
should be defined in the plugin file:

<pre>proc FileCompare {ch1 ch2 info1 info2} {...}</pre>

The arguments given to FileCompare are:

  *  ch1:  An input channel for reading the first file.
  *  ch2:  An input channel for reading the second file.
  *  info1: A dictionary with info about the first file.
  *  info2: A dictionary with info about the second file.

Info dictionaries contain at least elements "name" and "size".

The FileCompare procedure can give the following return values:

  *  0 : Files are not equal
  *  1 : Files are equal
  *  2 : Files are equal as far as the channels have been read.
         Let the normal comparison finish the job.

Directory diff only supports one plugin. The first plugin with FileCompare
defined will be used.

Added htdocs/print.wiki.











































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<title>Print</title>
<h1>Print to PDF</h1>

A PDF of the differences can be generated from the File->Print Pdf menu.

The PDF will be generated from the current screens, so make sure any
settings like context and ignore are done and you have regenerated the diff.

The following choices are available (command line flag in parens):

  *  Header Size. Font size to use for page header. (-printHeaderSize, default 10)
  *  Paper size. Landscape  will always be used. (-printPaper, default a4)
  *  Chars per line. See below. (-printCharsPerLine, default 80)
  *  RGB background values for diff display. Text is always black.  (-printColor*)

From the command line you can also give -printFont <ttffile/afmfile>.
See [./usage.wiki|Usage] for all command line flags.

The font size is automatically scaled to fit the given characters per line.
The GUI will give you choices that fit all or most lines in that line length.
Longer lines will be wrapped.

Added htdocs/revision.wiki.







































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
<title>Revision Control Support</title>

<h1>Introduction</h1>

Eskil can compare versions in many revision control systems.
Currently RCS, CVS, Git, Fossil, Mercurial, Bazaar, Subversion, Perforce and ClearCase are supported (some features are not implemented for all systems).

If you specify only one file on the command line to Eskil, it will
automatically detect if the file is under revision control and enter revision
control mode.

By default the local file is compared against the latest checked in version.
This is for the common case when you just want to know what you have changed
before checking in.

You can use the -r option to select which versions to compare.
The valid values for the -r option depends on the tools used. See below for
details on each one.

Examples:

Compare file.txt with the latest checked in version:

<pre>eskil file.txt</pre>

Compare file.txt with the specified version:

<pre>eskil -r rev file.txt</pre>

Compare the two revisions. This does not involve the local copy of file.txt.

<pre>eskil -r rev1 -r rev2 file.txt</pre>

The -r options are also available in the GUI in the "Rev 1" and "Rev 2" fields.

<h1>Directory Diff</h1>

Eskil can also browse and compare revisions for some systems directly in the
directory diff. It works just like for files, but give a directory on the
command line. Currently Git, Fossil and Subversion are supported.

<h1>Commit support</h1>

When comparing a file with the latest checked in version, some of the systems
have support for committing directly from Eskil.
If supported, the Commit button will be enabled.
It is also possible to revert the local changes using the Revert button.

<h1>Priority between systems</h1>

If multiple systems are used within a directory Git/Hg/Bzr will be detected before CVS/SVN.  Command line options -cvs and -svn can be used to put preference on one of those systems.

<h1>Pipe a patch</h1>

Eskil can read a patch from standard input, thus allowing display from any
patch generating command. Examples:

<pre>hg diff | eskil -</pre>
<pre>git diff -p --diff-filter=M master | eskil -</pre>

<h1>View all changes</h1>

If the command line option -review is used, Eskil will generate a patch
for the current tree and display it as in patch mode.

<verbatim>eskil -review [files] </verbatim>

E.g. in a Mercurial directory, these show the same thing:

<pre>eskil -review</pre>
<pre>hg diff | eskil -</pre>

If file names are given after -review, only the listed files are included.
If supported, the Commit button will be enabled allowing the viewed differences
to be committed directly from Eskil.

<h1>Conflict merging</h1>

Eskil can be used as a conflict resolution tool. See examples below for settings. See also [./merge.wiki|Merge].

<h1>Tools Details</h1>

<h2>RCS/CVS</h2>

For RCS and CVS the arguments to -r are standard version numbers just like to their -r options.
If a revision is an integer, it is added to the last number in the current version, thus giving relative versions.  E.g. -1 gives the second to last version.

<h2>Subversion</h2>

For Subversion the arguments to -r are standard version numbers just like its -r option.
If a revision is zero or a negative integer, the log is searched backwards for earlier versions.
E.g. -1 gives the second to last version.

<h2>Git</h2>

For Git -r <rev> is passed to show, as in "git show <rev>:<file>".
If a revision is zero or a negative integer, the log is searched backwards for earlier versions.

To use Eskil for conflict resolution these settings can be used.

<pre>git config --global merge.tool eskil</pre>
<pre>git config --global mergetool.eskil.cmd 'eskil -fine -a $BASE -o $MERGED $REMOTE $LOCAL'</pre>
<pre>git config --global diff.tool eskil</pre>
<pre>git config --global difftool.eskil.cmd 'eskil $LOCAL $REMOTE'</pre>

<h2>Fossil</h2>

See [./fossil.wiki|Fossil].

<h2>Mercurial</h2>

For Mercurial -r mostly works as in "hg cat -r".
However, Eskil interprets zero or negative numbers as going back from the tip, i.e. -1 is one step back, corresponding to -2 in Mercurial.

Mercurial is supported in the Directory Diff, but needs the hglist extension to
display correct file sizes and dates. If not they are faked using the file's
sha1 and thus unique per file and gives a correct result in comparison.

To use Eskil for conflict resolution these config settings can be used.

<verbatim>
[merge-tools]
eskil.args = -fine -a $base $other $local -o $output
eskil.priority = 1
</verbatim>

<h2>Bazaar</h2>

For Bazaar -r works as in "bzr cat -r".

<h2>ClearCase</h2>

ClearCase has more complex version "numbers".
ClearCase stream names are built like file paths and in -r you can access the streams
similar to how you find files.
Your current stream is the "current directory".
A negative version number is offset from latest.

  <tt>-r 5                    </tt>: Version 5 in current stream.<br>
  <tt>-r .                    </tt>: Latest version in current stream.<br>
  <tt>-r -1                   </tt>: Second to last version in current stream.<br>
  <tt>-r /full/path/stream/4  </tt>: The identified version.<br>
  <tt>-r /full/path/stream    </tt>: Latest version in that stream.<br>
  <tt>-r ../5                 </tt>: Version in parent stream.<br>
  <tt>-r ..                   </tt>: Latest in parent stream.<br>
  <tt>-r stream/5             </tt>: Version in stream, anywhere in tree.<br>
  <tt>-r stream               </tt>: Latest in stream, anywhere in tree.

Added htdocs/starkit.wiki.

















>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
<title>Starkit compare</title>

Eskil is [http://wiki.tcl.tk/starkit | Starkit] aware and will allow you
to compare starkits/starpacks.

If you compare one against itself, it becomes a way to browse a starkit.

The kit needs to be either mentioned on the command line or called *.kit for it to work.

Added htdocs/starpack.wiki.









































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<title>Starpack generation</title>
<h1>Starpack generation</h1>

Eskil is normally distributed as a [http://wiki.tcl.tk/starkit | Starkit] which
needs a [http://wiki.tcl.tk/tclkit | Tclkit] to run. If you have ActiveTcl
installed, tclsh can also run a Starkit.

To generate a stand-alone executable,
a [http://wiki.tcl.tk/starpack | Starpack], of Eskil you need the following:
  *  A [http://wiki.tcl.tk/tclkit | Tclkit] for your platform. (tclkit)
  *  The [http://wiki.tcl.tk/sdx | Sdx] utility. (sdx)
  *  Eskil's Starkit. (eskil.kit)

<verbatim>
./tclkit sdx unwrap eskil.kit
cp tclkit tclkit2
./tclkit sdx wrap eskil -runtime tclkit2
</verbatim>

Note that this could be generated on any platform, not just the target. Then tclkit should be for the current platform and tclkit2 should be for the target platform.

Added htdocs/table.wiki.













































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<title>Table diff</title>
<h1>Table diff</h1>

Eskil can compare tables in comma/tab separated text files and display
them like a table.

<verbatim>
eskil -table apa1.csv apa2.csv
</verbatim>

Eskil will try to auto-detect the separator but you can also give it
using -sep. Example for tab separation:

<verbatim>
eskil -table -sep '\t' apa1.csv apa2.csv
</verbatim>

Eskil has a built in plugin, csv, than can preprocess table files. This example clears the "Short" and "Long" columns before comparison:

<verbatim>
eskil -table apa1.csv apa2.csv -block -sep '\t' -plugin csv -csvignore "Short Long"
</verbatim>

Added htdocs/toc.wiki.

















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<title>Documentation</title>

[./changes.wiki|Changes]

[./editmode.wiki|Edit Mode]

[./fossil.wiki|Fossil Support]

[./merge.wiki|Merge]

[./plugins.wiki|Plugins]

[./print.wiki|Print]

[./revision.wiki|Revision Control Support]

[./starkit.wiki|Starkit compare]

[./starpack.wiki|Starpack generation]

[./table.wiki|Table diff]

[./usage.wiki|Usage]

Added htdocs/usage.wiki.

















































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<title>Usage</title>

<h1>Command Line Usage</h1>

<verbatim>
Usage: eskil [options] [files...]
  [options]  See below.
  [files...] Files to be compared

  If no files are given, the program is started anyway and you can select
  files from within. If only one file is given, the program looks for version
  control of the file, and if found, runs in version control mode.
  If directories are given, Eskil starts in directory diff.

  To list all options matching a prefix, run 'eskil --query prefix'.
  In tcsh use this line to get option completion:
  complete eskil 'C/-/`eskil --query -`/'

  Options:
-            : Read patch file from standard input, to allow pipes
-a <file>    : Give ancestor <file> for three way merge
-b           : Ignore space changes (default)
-block       : Full block analysis. This can be slow if there are large change
               blocks
-browse      : Bring up file dialog for missing files after starting
-char        : Character based change view (default)
-clip        : Start in clip diff mode. Ignores other args
-close       : Close any window with no changes
-conflict    : Treat file as a merge conflict file and enter merge mode
-context <n> : Show only differences, with <n> lines of context
-cvs         : Detect CVS first, if multiple version systems are used
-debug       : Start in debug mode
-dir         : Start in directory diff mode. Ignores other args
-excludedir <v> : Exclude from directory diff
-excludefile <v> : Exclude from directory diff
-fine        : Use fine grained chunks. Useful for merging
-foreach     : Open one diff window per file listed
-fourway     : Start in fourway diff mode. Ignores other args
-gz          : Uncompress input files with gunzip
-i           : Ignore case changes
-includedir <v> : Include in directory diff
-includefile <v> : Include in directory diff
-limit <lines> : Do not process more than <lines> lines
-line        : Line based block analysis
-maxwidth <v> : Limit column width in table mode
-nocase      : Ignore case changes
-nocdiff     : Disable C version of DiffUtil. For debug
-nodiff      : Do not run diff after startup
-nodigit     : Ignore digit changes
-noempty     : Ignore empty lines initially for matching
-noignore    : Don't ignore any whitespace
-nokeyword   : In directory diff, ignore $ Keywords: $
-nonewline   : Try to ignore newline changes
-nonewline+  : Try to ignore newline changes, and don't display
-noparse     : No block analysis
-o <file>    : Specify merge result output <file>
-patch       : View patch file
-pivot <v>   : Pivot setting for diff algorithm (10)
-plugin <v>  : Preprocess files using plugin
-pluginallow : Allow full access privilege for plugin
-plugindump <v> : Dump plugin source to stdout
-plugininfo <v> : Pass info to plugin (plugin specific)
-pluginlist  : List known plugins
-prefix <str> : Care mainly about words starting with <str>
-preprocess <pair> : The <pair> is a list of RE+Subst applied to each line
                     before compare
-preprocessleft <pair> : Use <pair> only on left side
-preprocessright <pair> : Use <pair> only on right side
-print <v>   : Generate PDF and exit
-printCharsPerLine <v> : Adapt font size for this line length and wrap (80)
-printColorChange <v> : Color for change (1.0 0.7 0.7)
-printColorNew <v> : Color for new text (0.8 0.8 1.0)
-printColorOld <v> : Color for old text (0.7 1.0 0.7)
-printFont <fontfile> : Select font to use in PDF, afm or ttf. If <fontfile> is
                        given as "Courier", PDF built in font is used
-printHeaderSize <v> : Font size for page header (10)
-printPaper <v> : Select paper size (a4)
-r <v>       : Version info for version control mode
-review      : View revision control tree as a patch
-sep <c>     : See char <c> as separator between columns in files
-server      : Set up Eskil to be controllable from the outside
-smallblock  : Do block analysis on small blocks (default)
-subst <pair> : The <pair> is a list of Left+Right, used for subst preprocessing
-svn         : Detect SVN first, if multiple version systems are used
-table       : Run in table mode
-w           : Ignore all spaces
-word        : Word based change view
</verbatim>

Added img/dragon_16x16x32.png.

cannot compute difference between binary files

Added img/dragon_24x24x32.png.

cannot compute difference between binary files

Added img/dragon_256x256x32.png.

cannot compute difference between binary files

Added img/dragon_32x32x32.png.

cannot compute difference between binary files

Added img/dragon_48x48x32.png.

cannot compute difference between binary files

Added img/run.sh.































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash

# Icon sizes in tclkit at present:
# 64x64x32 48x48x32 32x32x32 24x24x32 16x16x32
# 48x48x24 32x32x24 24x24x24 16x16x24
# 48x48x8  32x32x8  24x24x8  16x16x8 

# Create 64 from 48
pngtopam -alphapam dragon_48x48x32.png | pamscale -width 64 | pamrgbatopng > apa.png

# The 256 file is a "vista" file, and could be stored raw in the ico file.
# However, the tclkit has it stored as the others
icotool -c -o tclkit.ico apa.png dragon_48x48x32.png dragon_32x32x32.png dragon_24x24x32.png dragon_16x16x32.png dragon_256x256x32.png

cp tclkit.ico ../eskil.vfs/

Added mergetest-fossil.sh.









































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/sh
# Build up a merge conflict in fossil
fossil init apa.fossil
mkdir apa_fossil
cd apa_fossil
fossil open ../apa.fossil
fossil settings gmerge-command 'eskil -fine -a "%baseline" "%merge" "%original" -o "%output"'
cp ../tests/ancestor.txt a.txt
fossil add a.txt
fossil commit -m a
fossil branch new b trunk
fossil update b
cp ../tests/right.txt a.txt
fossil commit -m r
fossil update trunk
cp ../tests/left.txt  a.txt
fossil commit -m l
fossil update b
#fossil merge trunk
#fossil commit -m "Merge from trunk"

Added mergetest-git.sh.





































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/sh
# Build up a merge conflict in git
mkdir apa_git
cd apa_git
git init
cp ../tests/ancestor.txt a.txt
git add a.txt
git commit -m a
git checkout -b b
cp ../tests/right.txt a.txt
git commit -am r
git checkout master
cp ../tests/left.txt  a.txt
git commit -am l
git checkout b
#git merge master
#git mergetool
#git commit -am m

Added nfplugin.tcl.















































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
##Nagelfar Plugin : Check of Eskil's sources

proc statementWords {words info} {
    set caller [dict get $info caller]
    set callee [lindex $words 0]
    set res {}

    # Rule: Space around initial "!" in expr.
    # Reason: I find it more readable
    if {$callee eq "if"} {
        set e [lindex $words 1]
        if {[regexp {\{(\s*)!(\s*)[\[$]} $e -> pre post]} {
            if {$pre ne " " || $post ne " "} {
                lappend res warning
                lappend res "Not (!) should be surrounded by one space"
            }
        }
    }
    # Rule: Do not allow single letter variables as arguments.
    # Reason: A lot of old unreadable code had them.
    # Exception: Upper-case "W","x" and "y".
    if {$callee eq "proc"} {
        set argList [lindex $words 2]
        foreach arg [lindex $argList 0] {
            set arg [lindex $arg 0]

            set lcArg [string tolower $arg]
            if {[string length $arg] == 1 && $lcArg eq $arg} {
                if {$arg ni {x y}} {
                    lappend res warning
                    lappend res "Single letter argument '$arg' is not allowed '$argList'"
                }
            }
        }
    }

    
    return $res
}

Changes to plugins/backslash.tcl.

1
2



3
4
5
6
7
8
9
10
11
12
13
14
15
16
##Eskil Plugin : Compare with backslash-newline removed




# Example file for a plugin.
# A plugin must start exactly like this one.
# The text after : is the summary you can get at the command line

# This plugin replaces any backslash-newline with space, thus
# ignoring restructured lines.

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    set trim 0
    while {[gets $chi line] >= 0} {

|
>
>
>

|


<
<
<







1
2
3
4
5
6
7
8
9



10
11
12
13
14
15
16
##Eskil Plugin : Compare with backslash-newline removed
#
# This plugin replaces any backslash-newline with space, thus
# ignoring restructured lines.

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line




# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    set trim 0
    while {[gets $chi line] >= 0} {

Added plugins/binary.tcl.

























































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
##Eskil Plugin : Compare binary files, in hex
## Option -binsep : A set of chars to be used as "newline"
#
# This plugin converts files to hex to be able to compare binary files.
# A set of chars can be defined to be used as "newline". Default "0 10 13".
# Example usage:
# eskil -plugin binary -binsep "0 10 13 32" f1 f2

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line

# A plugin may declare command line options that should be allowed through
# to ::argv

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    set delimitL [list 0 10 13]
    if {[llength $::Info] > 0} {
        set delimitL $::Info
    }
    set i [lsearch -exact $::argv -binsep]
    if {$i >= 0} {
        incr i
        set delimitL [lindex $::argv $i]
    }
    if {[catch {llength $delimitL}]} {
        puts $cho "Binary plugin needs parameter to be a list"
        return 1
    }

    # Build an RE that matches the given chars
    set REm "\["
    set REi "\[^"
    foreach code $delimitL {
        set c [format %c $code]
        if {[string is wordchar $c]} {
            append REm $c
            append REi $c
        } else {
            # Just in case it is a special char for RE
            append REm \\ $c
            append REi \\ $c
        }
    }
    append REm "\]"
    append REi "\]"

    set RE $REi*$REm*

    fconfigure $chi -translation binary
    # Assume small enough for memory.
    # A file too large to read would be virtually impossible to display anyway.
    set data [read $chi]
    foreach line [regexp -all -inline $RE $data] {
        puts $cho [strToHex $line]
    }

    # Signal that the file after processing should be used both
    # for comparison and for displaying.
    return 1
}

# Note: With 8.6 there is "binary encode hex" that might be faster

# Build a string to hex mapper for speed
set ::hexCharMap {}
for {set i 0} {$i < 256} {incr i} {
    lappend ::hexCharMap [format %c $i] [format "%02X " $i]
}
proc strToHex {str} {
    string map $::hexCharMap $str
}

Added plugins/csv.tcl.























































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
##Eskil Plugin : Compare comma separated value (CSV) files
## Option -csvignore : A list of columns to ignore
## Option -csvkey    : A list of columns to sort on before comparison
## Flag   -csvheader : First line is a header line defining names of columns
#
# This plugin compares CSV files with some preprocessing available
# Example usage:
# eskil -plugin csv -csvignore "head3 head5" -csvkey head2 -sep , \
#       examples/dir*/csv1.txt

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line

# A plugin may declare command line options that should be allowed through
# to ::argv

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    # Look for parameters in command line
    set opts(-sep) ","
    set opts(-csvignore) ""
    set opts(-csvkey) ""
    set opts(-csvheader) 0
    foreach opt {-sep -csvignore -csvkey} {
        set i [lsearch -exact $::argv $opt]
        if {$i >= 0} {
            incr i
            set opts($opt) [lindex $::argv $i]
        }
    }
    set i [lsearch -exact $::argv "-csvheader"]
    if {$i >= 0} {
        set opts(-csvheader) 1
    }

    # Also allow options via info
    foreach {opt val} $::Info {
        set opts($opt) $val
    }
    # Allow backslash for easy access to \t
    set opts(-sep) [subst -nocommands -novariables $opts(-sep)]

    # If any column is given by name, assume the file starts with
    # a header line of column names
    foreach col [concat $opts(-csvignore) $opts(-csvkey)] {
        if { ! [string is integer $col]} {
            set opts(-csvheader) 1
        }
    }
    if {$opts(-csvheader)} {
        set nameLine [gets $chi]
        # Keep it first in file
        puts $cho $nameLine
        set nameList [split $nameLine $opts(-sep)]
    }

    set icol {}
    foreach col $opts(-csvignore) {
        if {[string is integer $col]} {
            lappend icol $col
        } else {
            set i [lsearch $nameList $col]
            if {$i < 0} {
                return -code error "CSV Plugin Error: No such heading '$col'"
            }
            lappend icol $i
        }
    }
    set icol [lsort -integer $icol]

    set kcol {}
    foreach col $opts(-csvkey) {
        if {[string is integer $col]} {
            lappend kcol $col
        } else {
            set i [lsearch $nameList $col]
            if {$i < 0} {
                return -code error "CSV Plugin Error: No such heading '$col'"
            }
            lappend kcol $i
        }
    }

    set olines {}
    while {[gets $chi line] >= 0} {
        set items [split $line $opts(-sep)]
        foreach i $icol {
            lset items $i ""
        }
        lappend olines $items
    }
    # Sort on keys
    foreach i [lreverse $kcol] {
        set olines [lsort -index $i $olines]
    }
    foreach items $olines {
        puts $cho [join $items $opts(-sep)]
    }

    # Signal that the file after processing should be used both
    # for comparison and for displaying.
    return 1
}

Added plugins/grep.tcl.























































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
##Eskil Plugin : Compare after filtering lines
## Option -grepre : Regexp to filter on
#
# This plugin only compares lines that match a regexp pattern.
# Example usage:
# eskil -plugin grep -grepre "<t>" f1 f2

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line

# A plugin may declare command line options that should be allowed through
# to ::argv

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    if {[catch {llength $::Info}]} {
        puts $cho "Grep plugin needs -plugininfo parameter to be a list"
        return 1
    }
    # Look for parameters in info string
    set opts(-re) "."
    foreach {opt val} $::Info {
        set opts($opt) $val
    }
    # And on command line
    set i [lsearch -exact $::argv -grepre]
    if {$i >= 0} {
        incr i
        set opts(-re) [lindex $::argv $i]
    }
    while {[gets $chi line] >= 0} {
        if {[regexp -- $opts(-re) $line]} {
            puts $cho $line
        }
    }
    # Signal that the file after processing should be used both
    # for comparison and for displaying.
    return 1
}

Added plugins/gz.tcl.









































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
##Eskil Plugin : Compare gzip file
#
# This plugin unpacks input gzip file before comparing.

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    zlib push gunzip $chi
    chan copy $chi $cho

    # Signal that the file after processing should be used both
    # for comparison and for displaying.
    return 1
}

Added plugins/keyword.tcl.



















































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
##Eskil Plugin : Ignore $Keywords$
#
# This plugin ignores keywords like $Revision$, both in file diff
# and in directory diff

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    while {1} {
        # Read data in large chunks for speed
        set data [read $chi 100000]
        if {$data eq ""} break
        # Replace keywords with nothing
        # Use line mode to be sure not to affect newlines
        regsub -all -line {\$\w+:[^\$]*\$} $data {} data
        puts -nonewline $cho $data
    }
    # Signal that the file after processing should be used only for
    # comparison, not for displaying.
    # The processed file must match the original line-wise.
    return 0
}

# To be used in directory diff, a plugin must define this procedure.
# ch1:  An input channel for reading the first file.
# ch2:  An input channel for reading the second file.
# info1: A dictionary with info about the first file.
# info2: A dictionary with info about the second file.
# Info dictionaries contain at least elements "name" and "size".
proc FileCompare {ch1 ch2 info1 info2} {
    set bufsz 65536
    # Assume that all keywords are in the first block
    set f1 [read $ch1 $bufsz]
    set f2 [read $ch2 $bufsz]
    regsub -all {\$\w+:[^\$]*\$} $f1 {} f1
    regsub -all {\$\w+:[^\$]*\$} $f2 {} f2
    # Compensate for any change in length
    if {[string length $f1] < [string length $f2]} {
        append f1 [read $ch1 [expr {[string length $f2] - [string length $f1]}]]
    }
    if {[string length $f2] < [string length $f1]} {
        append f2 [read $ch2 [expr {[string length $f1] - [string length $f2]}]]
    }
    if { ! [string equal $f1 $f2]} {
        # Returning 0 signals "not equal"
        return 0
    }
    # Return 1 means "equal"
    # Return 2 means "equal this far", and lets normal compare take over
    return 2
}

Changes to plugins/nocase.tcl.

1
2



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26























##Eskil Plugin : Case insensitive matching




# Example file for a plugin.
# A plugin must start exactly like this one.
# The text after : is the summary you can get at the command line

# This plugin implements case insensitive matching, corresponding to the
# -nocase flag.

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    while {1} {
        # Read data in large chunks for speed
        set data [read $chi 100000]
        if {$data eq ""} break
        # Use lower case for comparison, thus getting case insensitive
        puts -nonewline $cho [string tolower $data]
    }
    # Signal that the file after processing should be used only for
    # comparison, not for displaying.
    # The processed file must match the original line-wise.
    return 0
}
























|
>
>
>

|


<
<
<

















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
##Eskil Plugin : Case insensitive matching
#
# This plugin implements case insensitive matching, similar to the
# -nocase flag.

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line




# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    while {1} {
        # Read data in large chunks for speed
        set data [read $chi 100000]
        if {$data eq ""} break
        # Use lower case for comparison, thus getting case insensitive
        puts -nonewline $cho [string tolower $data]
    }
    # Signal that the file after processing should be used only for
    # comparison, not for displaying.
    # The processed file must match the original line-wise.
    return 0
}

# To be used in directory diff, a plugin must define this procedure.
# ch1:  An input channel for reading the first file.
# ch2:  An input channel for reading the second file.
# info1: A dictionary with info about the first file.
# info2: A dictionary with info about the second file.
# Info dictionaries contain at least elements "name" and "size".
proc FileCompare {ch1 ch2 info1 info2} {
    set bufsz 65536
    while 1 {
        set f1 [read $ch1 $bufsz]
        set f2 [read $ch2 $bufsz]
        if {$f1 eq "" && $f2 eq ""} break
        if { ! [string equal -nocase $f1 $f2]} {
            # Returning 0 signals "not equal"
            return 0
        }
    }

    # Return 1 means "equal"
    # Return 2 means "equal this far", and lets normal compare take over
    return 1
}

Added plugins/pdf.tcl.





































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
##Eskil Plugin : Compare text from PDF files. (needs pdftotext 4+)
## Option -marginl : Left margin to pass to pdftotext
## Option -marginr : Right margin to pass to pdftotext
## Option -margint : Top margin to pass to pdftotext
## Option -marginb : Bottom margin to pass to pdftotext
## Option -pdftotext : Extra options to pass to pdftotext
#
# This plugin runs input through the external tool pdftotext.
# Thus it must be run together with the -pluginallow flag.

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    if {[info commands exec] eq ""} {
        puts $cho "PDF plugin must be run with privilege to be able\
                   to execute pdftotext"
        return 1
    }
    set opts(-marginl) ""
    set opts(-marginr) ""
    set opts(-margint) ""
    set opts(-marginb) ""
    set opts(-pdftotext) ""
    foreach opt {-marginl -marginr -margint -marginb -pdftotext} {
        set i [lsearch -exact $::argv $opt]
        if {$i >= 0} {
            incr i
            set opts($opt) [lindex $::argv $i]
        }
    }
     set cands [auto_execok pdftotext]
    lappend cands [file join $::WhoAmIFull pdftotext]
    lappend cands [file join $::WhoAmIFull .. pdftotext]
    lappend cands [file join $::WhoAmIFull .. .. pdftotext]
    set found 0
    foreach cand $cands {
        if {[file exists $cand]} {
            set found 1
            break
        }
        if {[file exists $cand.exe]} {
            set cand $cand.exe
            set found 1
            break
        }
    }

    if { ! $found} {
        puts $cho "PDF plugin needs external tool 'pdftotext' to run"
        return 1
    }
    if {[catch {llength $::Info}]} {
        puts $cho "PDF plugin needs -plugininfo parameter to be a list"
        return 1
    }
    if {[catch {llength $opts(-pdftotext)}]} {
        puts $cho "PDF plugin needs -pdftotext parameter to be a list"
        return 1
    }
    # Pass options from -plugininfo as well.
    set options [concat $::Info $opts(-pdftotext)]
    foreach opt {-marginl -marginr -margint -marginb} {
        if {$opts($opt) ne ""} {
            lappend options $opt $opts($opt)
        }
    }
    # Use source file with pdftotext since stdin is not reliable on e.g Windows
    if {[catch {exec $cand {*}$options $::File($side) - >&@ $cho}]} {
        puts $cho "**************************************"
        puts $cho "PDF plugin got an error from pdftotext"
    }

    # Signal that the file after processing should be used both
    # for comparison and for displaying.
    return 1
}

Added plugins/sort.tcl.



















































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
##Eskil Plugin : Compare files after sorting lines
## Flag -sortwords : Sort words within each line first.
## Flag -nospace : Ignore space
#
# This plugin compares files after sorting the lines in each side

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line

# A plugin may declare command line options that should be allowed through
# to ::argv

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    # Look for parameters in command line
    set opts(-sortwords) 0
    set opts(-nospace) 0
    set i [lsearch -exact $::argv "-sortwords"]
    if {$i >= 0} {
        set opts(-sortwords) 1
    }
    set i [lsearch -exact $::argv "-nospace"]
    if {$i >= 0} {
        set opts(-nospace) 1
    }

    set data [read $chi]
    set endingNewLine 0
    if {[string index $data end] eq "\n"} {
        set data [string range $data 0 end-1]
        set endingNewLine 1
    }
    set lines [split $data \n]

    if {$opts(-sortwords)} {
        set newlines {}
        foreach line $lines {
            # Extract words
            set words [regexp -all -inline {\w+} $line]
            set words [lsort -dictionary $words]
            lappend newlines [join $words]
        }
        set lines $newlines
    }

    if {$opts(-nospace)} {
        set sortlines {}
        foreach line $lines {
            set nospace [regsub -all {\s+} $line ""]
            lappend sortlines [list $nospace $line]
        }
        set sortlines [lsort -dictionary {*}$::Info  -index 0 $sortlines]
        set lines {}
        foreach line $sortlines {
            lappend lines [lindex $line 1]
        }
    } else {
        # Allow sort parameters in info
        set lines [lsort -dictionary {*}$::Info $lines]
    }

    puts -nonewline $cho [join $lines \n]
    if {$endingNewLine} {
        puts $cho ""
    }
    # Signal that the file after processing should be used both
    # for comparison and for displaying.
    return 1
}

Added plugins/swap.tcl.



































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
##Eskil Plugin : Swap sides of contents
#
# This plugin swaps data between files. A fairly useless thing.
# This is to test and exemplify how to use yield in a plugin.

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    if {[info commands yield] eq ""} {
        puts $cho "Swap plugin must be run with Tcl 8.6 or newer"
        return 1
    }
    # Read all data from both sides
    set ::data($side) [read $chi]
    yield

    # Output data from other side
    if {$side eq "left"} {
        puts $cho $::data(right)
    } else {
        puts $cho $::data(left)
    }

    # Signal that the file after processing should be used both
    # for comparison and for displaying.
    return 1
}

Changes to plugins/words.tcl.

1
2


3
4
5
6
7
8
9
10
11
12
13
14
15
##Eskil Plugin : Compare set of words



# Example file for a plugin.
# A plugin must start exactly like this one.
# The text after : is the summary you can get at the command line

# This plugin compares the set of words in files.

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    while {[gets $chi line] >= 0} {
        foreach word [regexp -all -inline {\w+} $line] {

|
>
>

|


<
<







1
2
3
4
5
6
7
8


9
10
11
12
13
14
15
##Eskil Plugin : Compare set of words
#
# This plugin compares the set of words in files.

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line



# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    while {[gets $chi line] >= 0} {
        foreach word [regexp -all -inline {\w+} $line] {

Changes to src/clip.tcl.

29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
}

proc DoClipDiff {} {
    set f1 [tmpFile]
    set f2 [tmpFile]

    set ch [open $f1 w]
    set data1 [$::diff(wClip1) get 1.0 end]
    set data1 [ClipClean $data1]
    puts $ch $data1
    close $ch

    set ch [open $f2 w]
    set data2 [$::diff(wClip2) get 1.0 end]
    set data2 [ClipClean $data2]
    puts $ch $data2
    close $ch

    #set line1 [split $data1 \n]
    #set len1  [llength $line1]
    #set line2 [split $data2 \n]







|





|







29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
}

proc DoClipDiff {} {
    set f1 [tmpFile]
    set f2 [tmpFile]

    set ch [open $f1 w]
    set data1 [$::eskil(wClip1) get 1.0 end]
    set data1 [ClipClean $data1]
    puts $ch $data1
    close $ch

    set ch [open $f2 w]
    set data2 [$::eskil(wClip2) get 1.0 end]
    set data2 [ClipClean $data2]
    puts $ch $data2
    close $ch

    #set line1 [split $data1 \n]
    #set len1  [llength $line1]
    #set line2 [split $data2 \n]
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
        $::widgets($top,wLine1) configure -height 1
        $t2 configure -height $lines2
        $::widgets($top,wLine2) configure -height 1
    }
}

proc ArmCatch {} {
    if {$::diff(armcatch)} {
        bind .clipdiff <FocusOut> {
            if {[string equal %W .clipdiff]} {
                after 50 CatchFromWin
            }
        }
    } else {
        bind .clipdiff <FocusOut> {}
    }
}

proc CatchFromWin {} {
    set ::diff(armcatch) 0
    ArmCatch
    set win [twapi::get_foreground_window]
    if {$win eq ""} {
        #puts "No fg window"
        return
    }
    #puts "Locating windows"







|











|







62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
        $::widgets($top,wLine1) configure -height 1
        $t2 configure -height $lines2
        $::widgets($top,wLine2) configure -height 1
    }
}

proc ArmCatch {} {
    if {$::eskil(armcatch)} {
        bind .clipdiff <FocusOut> {
            if {[string equal %W .clipdiff]} {
                after 50 CatchFromWin
            }
        }
    } else {
        bind .clipdiff <FocusOut> {}
    }
}

proc CatchFromWin {} {
    set ::eskil(armcatch) 0
    ArmCatch
    set win [twapi::get_foreground_window]
    if {$win eq ""} {
        #puts "No fg window"
        return
    }
    #puts "Locating windows"
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
        twapi::send_keys ^(ac)
        after 50 "set ::CatchFromWinWait 1" ; vwait ::CatchFromWinWait
        lassign [twapi::get_window_coordinates $win] x1 y1 x2 y2
        if {[catch {clipboard get} text]} continue
        if {$text eq ""} continue
        lappend capturedData [list $x1 $text]
    }
    $::diff(wClip1) delete 1.0 end
    $::diff(wClip2) delete 1.0 end
    if {[llength $capturedData] == 0} return
    # Set it up left-to-right
    set capturedData [lsort -index 0 -integer $capturedData]
    if {[llength $capturedData] >= 1} {
        set text [lindex $capturedData 0 1]
        $::diff(wClip1) insert end $text
    }
    if {[llength $capturedData] >= 2} {
        set text [lindex $capturedData 1 1]
        $::diff(wClip2) insert end $text
        after idle DoClipDiff
    }
}

proc makeClipDiffWin {} {
    set top .clipdiff
    if {[winfo exists $top] && [winfo toplevel $top] eq $top} {







|
|





|



|







108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
        twapi::send_keys ^(ac)
        after 50 "set ::CatchFromWinWait 1" ; vwait ::CatchFromWinWait
        lassign [twapi::get_window_coordinates $win] x1 y1 x2 y2
        if {[catch {clipboard get} text]} continue
        if {$text eq ""} continue
        lappend capturedData [list $x1 $text]
    }
    $::eskil(wClip1) delete 1.0 end
    $::eskil(wClip2) delete 1.0 end
    if {[llength $capturedData] == 0} return
    # Set it up left-to-right
    set capturedData [lsort -index 0 -integer $capturedData]
    if {[llength $capturedData] >= 1} {
        set text [lindex $capturedData 0 1]
        $::eskil(wClip1) insert end $text
    }
    if {[llength $capturedData] >= 2} {
        set text [lindex $capturedData 1 1]
        $::eskil(wClip2) insert end $text
        after idle DoClipDiff
    }
}

proc makeClipDiffWin {} {
    set top .clipdiff
    if {[winfo exists $top] && [winfo toplevel $top] eq $top} {
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
    wm title $top "Clip Diff"
    wm protocol $top WM_DELETE_WINDOW "cleanupAndExit $top"
    set t1 [Scroll both \
            text $top.t1 -width 60 -height 35 -font myfont]
    set t2 [Scroll both \
            text $top.t2 -width 60 -height 35 -font myfont]

    set ::diff(wClip1) $t1
    set ::diff(wClip2) $t2

    bind $t1 <Control-o> [list focus $t2]
    bind $t2 <Control-o> [list focus $t1]

    ttk::frame $top.f
    menubutton $top.f.mf -menu $top.f.mf.m -text "File" -underline 0
    menu $top.f.mf.m







|
|







142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
    wm title $top "Clip Diff"
    wm protocol $top WM_DELETE_WINDOW "cleanupAndExit $top"
    set t1 [Scroll both \
            text $top.t1 -width 60 -height 35 -font myfont]
    set t2 [Scroll both \
            text $top.t2 -width 60 -height 35 -font myfont]

    set ::eskil(wClip1) $t1
    set ::eskil(wClip2) $t2

    bind $t1 <Control-o> [list focus $t2]
    bind $t2 <Control-o> [list focus $t1]

    ttk::frame $top.f
    menubutton $top.f.mf -menu $top.f.mf.m -text "File" -underline 0
    menu $top.f.mf.m
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
    #}
    grid $top.f.mf $top.f.b2 $top.f.b4 x $top.f.b x $top.f.b3 $top.f.b5 x \
            -padx 4 -pady 2 -sticky "w"
    grid $top.f.mf -sticky nw -pady 0 -padx 0
    grid columnconfigure $top.f {0 3 5 8} -weight 1
    grid columnconfigure $top.f 8 -minsize [winfo reqwidth $top.f.mf]

    if {![catch {package require twapi}]} {
        ttk::checkbutton $top.f.b6 -text "Capture" -command ArmCatch \
                -underline 0 -variable ::diff(armcatch)
        bind $top <Alt-c> [list $top.f.b6 invoke]
        #raise $top.f.b6
        place $top.f.b6 -anchor e -relx 1.0 -rely 0.5
    }

    grid $top.f    -       -sticky we
    grid $top.t1   $top.t2 -sticky news
    grid $top.t2 -padx {2 0}
    grid rowconfigure    $top 1     -weight 1
    grid columnconfigure $top {0 1} -weight 1
    return $top
}







|

|












179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
    #}
    grid $top.f.mf $top.f.b2 $top.f.b4 x $top.f.b x $top.f.b3 $top.f.b5 x \
            -padx 4 -pady 2 -sticky "w"
    grid $top.f.mf -sticky nw -pady 0 -padx 0
    grid columnconfigure $top.f {0 3 5 8} -weight 1
    grid columnconfigure $top.f 8 -minsize [winfo reqwidth $top.f.mf]

    if { ! [catch {package require twapi}]} {
        ttk::checkbutton $top.f.b6 -text "Capture" -command ArmCatch \
                -underline 0 -variable ::eskil(armcatch)
        bind $top <Alt-c> [list $top.f.b6 invoke]
        #raise $top.f.b6
        place $top.f.b6 -anchor e -relx 1.0 -rely 0.5
    }

    grid $top.f    -       -sticky we
    grid $top.t1   $top.t2 -sticky news
    grid $top.t2 -padx {2 0}
    grid rowconfigure    $top 1     -weight 1
    grid columnconfigure $top {0 1} -weight 1
    return $top
}

Changes to src/compare.tcl.

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------

proc maxAbs {a b} {
    return [expr {abs($a) > abs($b) ? $a : $b}]
}

# Compare two lines and rate how much they resemble each other.
# This has never worked well. Some day I'll sit down, think this through,
# and come up with a better algorithm.
proc CompareLines {line1 line2} {
    set opts $::Pref(ignore)







|
|







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------

proc maxAbs {v1 v2} {
    return [expr {abs($v1) > abs($v2) ? $v1 : $v2}]
}

# Compare two lines and rate how much they resemble each other.
# This has never worked well. Some day I'll sit down, think this through,
# and come up with a better algorithm.
proc CompareLines {line1 line2} {
    set opts $::Pref(ignore)

Added src/debug.tcl.

























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
# debug.tcl
#
#    Helpers for debugging.
#
#

namespace eval ::_Debug {

}

#-----------------------------------------------------------------------------
# Misc useful stuff
#-----------------------------------------------------------------------------

proc ::_Debug::dumpMyMemUsage {str} {
    try {
        set xx [exec ps --pid [pid] --format vsize]
        set mem 0
        regexp {\d+} $xx mem
        puts "$str : memory usage $mem"
    } on error {} {
        puts "$str : memory usage unknown, call to ps failed"
    }
}

#-----------------------------------------------------------------------------
# Tracing
#-----------------------------------------------------------------------------

proc ::_Debug::TRenter {cmd op} {
    set fr [info frame -2]
    set line X
    if {[dict exists $fr line]} {
        set line [dict get $fr line]
    }
    puts "Line $line Enter: '$cmd'"
}
proc ::_Debug::TRenterstep {cmd op} {
    set fr [info frame -2]
    set line X
    if {[dict exists $fr line]} {
        set line [dict get $fr line]
    }
    puts "Line $line  Enterstep: '$cmd'"
}
proc ::_Debug::TRleave {cmd code res op} {
    puts "Leave: '$res'"
}
proc ::_Debug::TRleavestep {cmd code res op} {
    puts "Leavestep: '$res'"
}
proc ::_Debug::TR {cmd {step 0}} {
    TRoff $cmd
    trace add execution $cmd enter ::_Debug::TRenter
    trace add execution $cmd leave ::_Debug::TRleave
    if {$step} {
        trace add execution $cmd enterstep ::_Debug::TRenterstep
        trace add execution $cmd leavestep ::_Debug::TRleavestep
    }
}
proc ::_Debug::TRoff {cmd} {
    trace remove execution $cmd enter ::_Debug::TRenter
    trace remove execution $cmd leave ::_Debug::TRleave
    trace remove execution $cmd enterstep ::_Debug::TRenterstep
    trace remove execution $cmd leavestep ::_Debug::TRleavestep
}

#-----------------------------------------------------------------------------
# GUI
#-----------------------------------------------------------------------------

proc debugMenu {mW} {
    $mW add cascade -label "Debug" -menu $mW.debug -underline 0
    menu $mW.debug

    if {$::tcl_platform(platform) eq "windows"} {
        $mW.debug add checkbutton -label "Console" -variable ::consoleState \
                -onvalue show -offvalue hide -command {console $::consoleState} \
                -underline 0
        $mW.debug add separator
    }

    $mW.debug add command -label "Edit" -command ::_Debug::ProcEditor \
            -underline 0
    $mW.debug add command -label "Windows" -command ::_Debug::WindowBrowser \
            -underline 0
    #after 500 ::_Debug::DumpStuff
    #after 500 ::_Debug::ProcEditor
    return $mW.debug
}

#-----------------------------------------------------------------------------
# Window structure browser
#-----------------------------------------------------------------------------
proc ::_Debug::WindowBrowser {} {
    set top .windowbrowser
    destroy $top
    ttk::toplevel $top -padx 3 -pady 3
    wm title $top "Window Browser"

    ttk::frame $top.ftree
    set tree $top.ftree.tree
    ttk::treeview $tree -height 20 -selectmode browse -show "tree" \
            -yscrollcommand "$top.ftree.sby set"
    ttk::scrollbar $top.ftree.sby -orient vertical -command "$tree yview"
    $tree column "#0" -minwidth 50 -width 200

    pack $top.ftree.sby -side right -fill y -pady 3 -padx {0 3}
    pack $tree -fill both -expand 1 -pady 3 -padx {3 0}

    text $top.t -width 80 -wrap word

    set ::_Debug::WindowBrowser(treeW) $tree
    set ::_Debug::WindowBrowser(textW) $top.t
    bind $tree <<TreeviewSelect>> ::_Debug::WindowBrowserSelected

    grid $top.ftree $top.t -sticky news
    grid rowconfigure    $top 0 -weight 1
    grid columnconfigure $top 0 -weight 1
    grid columnconfigure $top 1 -weight 2

    set ::_Debug::WindowBrowser(deselect) ""
    PopWindowBrowser $tree
}

# An item was selected. Show info
proc ::_Debug::WindowBrowserSelected {} {
    $::_Debug::WindowBrowser(textW) delete 1.0 end
    if {$::_Debug::WindowBrowser(deselect) ne ""} {
        #puts "DESEL: $::_Debug::WindowBrowser(deselect)"
        {*}$::_Debug::WindowBrowser(deselect)
        set ::_Debug::WindowBrowser(deselect) ""
    }
    set tree $::_Debug::WindowBrowser(treeW)
    set items [$tree selection]
    if {[llength $items] < 1} return
    set item [lindex $items 0]
    set values [$tree item $item -values]
    set d [lindex $values 0]
    set txt [dict get $d out]
    $::_Debug::WindowBrowser(textW) insert end $txt

    set interp [dict get $d interp]
    set i [list interp eval $interp]
    set w [dict get $d w]

    # A few experiments to highlight selection.
    try {
        # Overlaid frame seems to work best
        set tl [{*}$i winfo toplevel $w]
        set wx [expr [{*}$i winfo rootx $w] - [{*}$i winfo rootx $tl]]
        set wy [expr [{*}$i winfo rooty $w] - [{*}$i winfo rooty $tl]]
        set ww [{*}$i winfo width $w]
        set wh [{*}$i winfo height $w]
        set cleancmd ""
        if {$tl eq "."} {
            set tl ""
        }
        for {set t 1} {$t <= 4} {incr t} {
            set whl($t) $tl._debug_hl_$t
            destroy $whl($t)
            append cleancmd [list destroy $whl($t)]\;
            frame $whl($t) -background red
        }
        place $whl(1) -x $wx -y $wy -width $ww -height 3
        place $whl(2) -x $wx -y $wy -width 3   -height $wh
        place $whl(3) -x [+ $wx $ww] -y $wy -width 3 -height $wh
        place $whl(4) -x $wx -y [+ $wy $wh] -width $ww   -height 3
        set ::_Debug::WindowBrowser(deselect) \
                [list eval $cleancmd]
        return
    } on error {err info} {
        #puts "In $interp"
        #puts "$err"
        #puts "$info"
    }

    try {
        # Reconfiguring class. Does not work with disabled buttons e.g.
        set class [{*}$i winfo class $w]
        set oldstyle [{*}$i $w cget -style]
        if {$oldstyle eq ""} {
            set basestyle $class
        } else {
            set basestyle $oldstyle
        }
        set style HighLightRed.$basestyle
        {*}$i ttk::style configure $style -background red -fieldbackground red
        {*}$i $w configure -style $style
        set ::_Debug::WindowBrowser(deselect) \
                [list {*}$i [list $w configure -style $oldstyle]]
        #puts "CLASS $class STYLE $style"
        #puts [{*}$i ttk::style configure $basestyle]
        #puts [{*}$i ttk::style configure $style]
        return
    } on error {err info} {
        #puts "In $interp"
        #puts "$err"
        #puts "$info"
    }
    try {
        # Tk style background change. Only works with Tk.
        set bg [{*}$i $w cget -background]
        {*}$i $w configure -background red
        set ::_Debug::WindowBrowser(deselect) \
                [list {*}$i [list $w configure -background $bg]]
        return
    } on error {err info} {
        #puts "In $interp"
        #puts "$err"
        #puts "$info"
    }
    #puts "MOO $w"
}

# Populate
proc ::_Debug::PopWindowBrowser {tree} {
    $tree delete [$tree children {}]
    set todo [list . {}]
    # Outer loop for subinterps TBD
    while {[llength $todo] > 0} {
        set containers {}
        while {[llength $todo] > 0} {
            # POP
            set w [lindex $todo 0]
            set interp [lindex $todo 1]
            set i [list interp eval $interp]
            set todo [lrange $todo 2 end]
            set long $interp$w
            if {$w in {.windowbrowser}} continue

            foreach child [lsort -dictionary [{*}$i winfo children $w]] {
                lappend todo $child $interp
            }
            set id($long) "N$long"
            if {[info exists parents($long)]} {
                # Parent passed from other interp
                set parentId $id($parents($long))
            } else {
                set parent [{*}$i winfo parent $w]
                if {$parent eq ""} {
                    set parentId ""
                } else {
                    set parentId $id($interp$parent)
                }
            }

            set class [{*}$i winfo class $w]
            set out "$w  ($class)\n"
            set delayed ""
            foreach param [{*}$i $w configure] {
                lassign $param flag _ _ def value
                if {$value ne $def} {
                    append out "[list $flag $value] "
                } else {
                    append delayed "[list $flag $value] "
                }
                if {$flag eq "-container" && $value == 1} {
                    lappend containers $w $interp
                }
            }
            if {$delayed ne ""} {
                append out \n $delayed
            }
            try {
                set ix [{*}$i grid info $w]
                if {$ix ne ""} {
                    append out "\n\ngrid\n$ix"
                }
            } on error {} {}
            try {
                set ix [{*}$i pack info $w]
                if {$ix ne ""} {
                    append out "\n\npack\n$ix"
                }
            } on error {} {}

            set name $w
            regexp {\.[^.]+$} $w name

            set open 1
            if {[string match "*#*" $w]} {
                set open 0
            }

            set d {}
            dict set d w $w
            dict set d interp $interp
            dict set d id $id($long)
            dict set d out $out

            $tree insert $parentId end -id $id($long) -open $open \
                    -text $name -values [list $d]
        }
        # TODO: Handle -container and subinterp? How?
        foreach {w interp} $containers {
            set wid [winfo id $w]
            foreach sub [interp slaves $interp] {
                try {
                    set subId [interp eval $sub . cget -use]
                    if {$subId == $wid} {
                        #puts "Found interp $sub for $w"
                        set parents($sub.) $interp$w
                        lappend todo . $sub
                    }
                } on error {} {}

            }
        }
        #break
    }
}

#-----------------------------------------------------------------------------
# Procedure/method editor
#-----------------------------------------------------------------------------

# An item was selected. Show it and make it editable.
proc ::_Debug::ProcEditorSelected {} {
    variable allcmds

    set ::_Debug::ProcEditor(current) ""
    set ::_Debug::ProcEditor(parent) ""
    set ::_Debug::ProcEditor(proc) ""
    set ::_Debug::ProcEditor(args) ""
    $::_Debug::ProcEditor(bodyW) delete 1.0 end

    set tree $::_Debug::ProcEditor(treeW)
    set items [$tree selection]
    if {[llength $items] < 1} return
    set item [lindex $items 0]
    set d $allcmds($item)
    set type [dict get $d type]
    set parent [dict get $d parent]
    set name [dict get $d name]

    set ::_Debug::ProcEditor(current) $item
    set ::_Debug::ProcEditor(parent) $parent
    set ::_Debug::ProcEditor(proc) $name
    set ::_Debug::ProcEditor(args) ""
    $::_Debug::ProcEditor(bodyW) delete 1.0 end

    set traceState normal
    if {$type eq "proc"} {
        set arglist {}
        foreach i [info args $item] {
            if {[info default $item $i value]} {
                lappend arglist [list $i $value]
            } else {
                lappend arglist [list $i]
            }
        }
        set body [info body $item]
        set ::_Debug::ProcEditor(args) $arglist
        $::_Debug::ProcEditor(bodyW) insert end $body
    } elseif {$type eq "method"} {
        lassign [info class definition $parent $name] arglist body
        set traceState disabled
        set ::_Debug::ProcEditor(args) $arglist
        $::_Debug::ProcEditor(bodyW) insert end $body
    } else {
        set traceState disabled
    }
    foreach w $::_Debug::ProcEditor(traceWs) {
        $w configure -state $traceState
    }

}

# Redefine currently edited proc/method
proc ::_Debug::ProcEditorRedefine {} {
    variable allcmds
    set body [$::_Debug::ProcEditor(bodyW) get 1.0 end]
    set body [string trimright $body]

    set item $::_Debug::ProcEditor(current)
    set d $allcmds($item)
    set type [dict get $d type]
    set parent [dict get $d parent]
    set name [dict get $d name]

    if {$type eq "proc"} {
        set todo [list proc $item \
                          $::_Debug::ProcEditor(args) $body]
        set ::_Debug::redefines($item) $todo
        uplevel \#0 $todo
    } elseif {$type eq "method"} {
        set todo [list oo::define $parent method $name \
                          $::_Debug::ProcEditor(args) $body]
        set ::_Debug::redefines($parent..$name) $todo
        uplevel \#0 $todo
    }
}

proc ::_Debug::ProcEditorCopy {} {
    clipboard clear
    foreach item [array names ::_Debug::redefines] {
        clipboard append $::_Debug::redefines($item)\n
    }
}

# Tracing of commands
proc ::_Debug::ProcEditorTrace {level} {
    variable allcmds
    set item $::_Debug::ProcEditor(current)
    set d $allcmds($item)
    set type [dict get $d type]
    set parent [dict get $d parent]
    set name [dict get $d name]
    if {$type ni "proc method"} return

    # TODO: methods
    if {$type eq "proc"} {
        if {$level == 1} {
            TR $item
        } elseif {$level == 2} {
            TR $item 1
        } else {
            TRoff $item
        }
    }
}

# Disassemble of current
proc ::_Debug::ProcEditorDisas {} {
    variable allcmds
    set item $::_Debug::ProcEditor(current)
    set d $allcmds($item)
    set type [dict get $d type]
    set parent [dict get $d parent]
    set name [dict get $d name]
    if {$type ni "proc method"} return

    if {$type eq "proc"} {
        set da [tcl::unsupported::disassemble proc $item]
    } else {
        set da [tcl::unsupported::disassemble method $parent $name]
    }

    set top .proceditor.disas
    destroy $top
    ttk::toplevel $top
    wm title $top "Proc Editor Disassemble"

    text $top.t -yscrollcommand "$top.sby set"
    ttk::scrollbar $top.sby -orient vertical -command "$top.t yview"

    grid $top.t $top.sby -padx 3 -pady 3 -sticky news

    grid columnconfigure $top 0 -weight 1
    grid rowconfigure    $top 0 -weight 1

    $top.t insert end $da
}

# Treeview filtering. React on keystroke
proc ::_Debug::ProcEditorFilter {aVal kVal} {
    set f $::_Debug::ProcEditor(filter)
    set fx $::_Debug::ProcEditor(filterx)
    # Do not react unless changed.
    if {$f eq $fx} {
        return
    }
    set tree $::_Debug::ProcEditor(treeW)

    # Recreate the tree.
    # This is easier since the treeview does not have an item hide attribute.
    set pat *$f*
    TreePopulate $tree $pat
    set ::_Debug::ProcEditor(filterx) $f
}

# Make sure the hierarchy for a leaf exist, creating if needed.
proc ::_Debug::TreeCreatePath {tree path} {
    if {[$tree exists $path]} return
    set d $::_Debug::allcmds($path)
    set parent [dict get $d parent]
    if {$path ni {"" ::}} {
        TreeCreatePath $tree $parent
    }
    set text [dict get $d name]
    if {$parent eq "::"} {
        set parent ""
    }

    $tree insert $parent end -id $path -text $text -open 1 \
            -values [list $parent]
}

# Populate the treeview with all known procs and methods
proc ::_Debug::TreePopulate {tree {filter *}} {
    $tree delete [$tree children {}]
    foreach cmd [lsort -dictionary [array names ::_Debug::allcmds]] {
        set d $::_Debug::allcmds($cmd)
        set type [dict get $d type]
        if {$type ni "proc method"} continue
        if { ! [string match -nocase $filter [dict get $d name]]} continue

        set path [dict get $d parent]
        if {$path ne ""} {
            TreeCreatePath $tree $path
        }
        $tree insert $path end -id $cmd \
                -text [dict get $d name] -values [list $path]
    }
}

# Main Proc Editor window
proc ::_Debug::ProcEditor {} {
    ::_Debug::CollectInfo

    set top .proceditor
    destroy $top
    ttk::toplevel $top -padx 3 -pady 3
    wm title $top "Proc Editor"

    ttk::frame $top.ftree
    set ::_Debug::ProcEditor(filter) ""
    set ::_Debug::ProcEditor(filterx) ""
    ttk::entry $top.ftree.ef -textvariable ::_Debug::ProcEditor(filter)
    addBalloon $top.ftree.ef "Filter"
    bind $top.ftree.ef <KeyRelease> {::_Debug::ProcEditorFilter %A %K}
    set tree $top.ftree.tree
    set ::_Debug::ProcEditor(treeW) $tree
    ttk::treeview $tree -height 20 -selectmode browse -show "tree" \
            -yscrollcommand "$top.ftree.sby set"
    ttk::scrollbar $top.ftree.sby -orient vertical -command "$tree yview"
    $tree tag configure highlight -background pink
    $tree column "#0" -minwidth 50 -width 200
    pack $top.ftree.ef -side "top" -fill x -padx 3 -pady 3
    pack $top.ftree.sby -side right -fill y -pady 3 -padx {0 3}
    pack $tree -fill both -expand 1 -pady 3 -padx {3 0}
    TreePopulate $tree
    bind $tree <<TreeviewSelect>> ::_Debug::ProcEditorSelected

    ttk::label $top.l1a -text "Parent" -anchor w
    ttk::label $top.l1b -textvariable ::_Debug::ProcEditor(parent) -anchor w
    ttk::label $top.l2a -text "Proc/Method" -anchor w
    ttk::label $top.l2b -textvariable ::_Debug::ProcEditor(proc) -anchor w
    ttk::label $top.l3a -text "Args" -anchor w
    ttk::label $top.l3b -textvariable ::_Debug::ProcEditor(args) -anchor w
    ttk::button $top.bc -text "Copy" -command ::_Debug::ProcEditorCopy
    addBalloon $top.bc "Put all redefines on clipboard"

    set ::_Debug::ProcEditor(bodyW) [text $top.t -yscrollcommand "$top.sby set" \
                                             -width 90]
    ttk::scrollbar $top.sby -orient vertical -command "$top.t yview"

    ttk::frame  $top.fb
    ttk::button $top.b1 -text "Redefine" -command ::_Debug::ProcEditorRedefine
    addBalloon $top.b1 "Redefine for this session"
    ttk::button $top.b2 -text "Disas"    -command ::_Debug::ProcEditorDisas
    addBalloon $top.b2 "Show byte code"
    ttk::button $top.b3 -text "Trace"    -command "::_Debug::ProcEditorTrace 1"
    addBalloon $top.b3 "Enable execution trace"
    ttk::button $top.b4 -text "Tr Step"  -command "::_Debug::ProcEditorTrace 2"
    addBalloon $top.b4 "Enable detailed execution trace"
    ttk::button $top.b5 -text "Tr Off"   -command "::_Debug::ProcEditorTrace 0"
    addBalloon $top.b5 "Disable execution trace"
    set ::_Debug::ProcEditor(traceWs) [list $top.b3 $top.b4 $top.b5]
    grid $top.b1 $top.b2 $top.b3 $top.b4 $top.b5 -in $top.fb
    grid columnconfigure $top.fb all -weight 1 -uniform a

    grid $top.ftree $top.l1a $top.l1b - $top.bc - -padx 3 -pady 3 -sticky news
    grid ^          $top.l2a $top.l2b - -       - -padx 3 -pady 3 -sticky we
    grid ^          $top.l3a $top.l3b - -       - -padx 3 -pady 3 -sticky we
    grid ^          $top.t  -         - -  $top.sby -padx 3 -pady 3 -sticky news
    grid ^          $top.fb -         - -  -        -padx 3 -pady 3 -sticky we

    grid columnconfigure $top 2 -weight 1
    grid rowconfigure    $top $top.t -weight 1
}

#-----------------------------------------------------------------------------
# Procedure/method information collection
#-----------------------------------------------------------------------------
#
# There is nuances to namespace handling that needs awareness.
#
# "parent" operates on just existing namespaces and cannot be used on
# procedures. It returns a normalized name, with a slight gotcha that
# top namespace is "::", thus ending in colons. Thus this cannot be used
# directly for joining without care.
# % namespace parent ::eskil::rev
# ::eskil
# % namespace parent eskil::rev
# ::eskil
# % namespace parent ::eskil
# ::
# % namespace parent ::
#
# "qualifier" pairs with "tail"
# It just parses the string and does not need to make sense.
# Thus this can be used on qualified procedure names.
# % namespace qualifier ::eskil::rev
# ::eskil
# % namespace qualifier eskil::rev
# eskil
# % namespace qualifier ::eskil
#
# Ditto with "tail"
# % namespace tail ::eskil::rev
# rev
# % namespace tail ::eskil
# eskil
# % namespace tail ::
#
# "children", like "parent", operates on real namespace and normalizes.
# % namespace children ::eskil
# ::eskil::rev
# % namespace children ::eskil::
# ::eskil::rev
# % namespace children ""
# ::eskil ::zlib ::pkg ::oo ::tcl
#
# Conclusion:
# If a namespace is always kept with "::" at the end things are mostly easy.
# "parent" and "children" will work, as well as joining with $parent$tail.
# This cannot be used with "qualifiers", so extra care is needed there.
# The helpers below handles this.

# Parent namespace. Always ends with ::
proc ::_Debug::Qualifiers {ns} {
    set ns [string trimright $ns ":"]
    set q [namespace qualifiers $ns]
    if { ! [string match *:: $q]} {
        append q ::
    }
    return $q
}
# Parent namespace. Always ends with ::
proc ::_Debug::Parent {ns} {
    set p [namespace parent $ns]
    if { ! [string match *:: $p]} {
        append p ::
    }
    return $p
}

# allcmds structure:
# fullId for different things:
#   proc: Its qualified namespace path. Name = leaf
#   namespace: Its qualified namespace path ending in ::. Name = leaf::
#   class: Its qualified namespace path. Name = leaf
#   method: A list of class id + method. Name = method
# allcmds(fullId) = dict:
#   type = proc/namespace/class/method/import
#   parent = fullId of parent/class
#   name = leaf name
#   origin = for import

# Collect all info about procedures/method/whatever.
# This is work in progress...
proc ::_Debug::CollectInfo {} {
    variable allcmds
    array set allcmds {}

    # Only do this once
    if {[array size allcmds] > 0} return

    # Find all commands in all namespaces
    set todoNs [list ::]
    while {[llength $todoNs] != 0} {
        set nsId [lindex $todoNs 0]
        set todoNs [lrange $todoNs 1 end]

        if {$nsId eq "::_Debug::"} continue

        set tail [namespace tail [string trimright $nsId ":"]]
        dict set allcmds($nsId) type   namespace
        dict set allcmds($nsId) parent [Parent $nsId]
        dict set allcmds($nsId) name   ${tail}::

        foreach child [namespace children $nsId] {
            lappend todoNs ${child}::
        }
        array unset thisround
        array set thisround {}
        # First collect commands, since we want to override with detail later
        foreach cmd [info commands $nsId*] {
            dict set allcmds($cmd) type "cmd"
            dict set allcmds($cmd) parent [Qualifiers $cmd]
            dict set allcmds($cmd) name [namespace tail $cmd]
            set thisround($cmd) 1
        }
        # Which ones are procs?
        foreach cmd [info procs $nsId*] {
            dict set allcmds($cmd) type "proc"
            dict set allcmds($cmd) parent [Qualifiers $cmd]
            dict set allcmds($cmd) name [namespace tail $cmd]
            set thisround($cmd) 0
        }
        # Which ones are imports?
        if { ! [catch {namespace eval $nsId {namespace import}} imports]} {
            foreach cmd $imports  {
                dict set allcmds($nsId$cmd) type "import"
                dict set allcmds($nsId$cmd) origin \
                        [namespace origin $nsId$cmd]
                set thisround($nsId$cmd) 0
            }
        }

        # Look through and command that is not something identified
        foreach cmd [array names thisround] {
            if { ! $thisround($cmd)} continue

            # Is it an ensemble?
            if {[namespace ensemble exists $cmd]} {
                #puts "ENSEMBLE $cmd"
                dict set allcmds($cmd) type ensemble
                foreach {key val} [namespace ensemble configure $cmd] {
                    #lappend allcmds($cmd) $key $val
                    if {$key eq "-map"} {
                        #puts "$cmd $val"
                        dict lappend allcmds($cmd) maps {*}$val
                    }
                    # Recognise a snit class
                    if {$key eq "-unknown" && [string match ::snit::* $val]} {
                        #puts "SNIT? $cmd"
                        #lset allcmds($cmd) 0 snit
                    }
                }
                set thisround($cmd) 0
                continue
            }
        }
        # Namespace ensembles?
    }

    # Go through tcloo classes
    set todoObj [list ::oo::object]
    while {[llength $todoObj] != 0} {
        set obj [lindex $todoObj 0]
        set todoObj [lrange $todoObj 1 end]

        dict set allcmds($obj) type class
        dict set allcmds($obj) parent [Qualifiers $obj]
        dict set allcmds($obj) name   [namespace tail $obj]

        foreach child [info class subclasses $obj] {
            lappend todoObj $child
        }
        foreach m [info class methods $obj -private] {
            set id [list $obj $m]
            dict set allcmds($id) type method
            dict set allcmds($id) parent $obj
            dict set allcmds($id) name $m
        }
    }
}

# Debug of debug
proc ::_Debug::DumpStuff {} {
    try {
        ::_Debug::CollectInfo
    } on error {res i} {
        puts $res
        puts $i
        after 1000
    }

    # Proc
    parray ::_Debug::allcmds *updateColors*
    parray ::_Debug::allcmds *cleanupAndExit
    # Cmd
    parray ::_Debug::allcmds *ttk::paned
    parray ::_Debug::allcmds *llength
    # OO class
    parray ::_Debug::allcmds *Account*
    # Snit class
    parray ::_Debug::allcmds *eskilprint*
    #
    parray ::_Debug::allcmds *indexEntry*
    exit
}

#-----------------------------------------------------------------------------
# Test just to include an OO object in the code
#-----------------------------------------------------------------------------
catch {Account destroy}
oo::class create Account {
    constructor {{ownerName undisclosed}} {
        my variable total overdrawLimit owner
        set total 0
        set overdrawLimit 10
        set owner $ownerName
    }
    method deposit amount {
        my variable total
        set total [expr {$total + $amount}]
    }
    method withdraw amount {
        my variable total overdrawLimit
        if {($amount - $total) > $overdrawLimit} {
            error "Can't overdraw - total: $total, limit: $overdrawLimit"
        }
        set total [expr {$total - $amount}]
    }
    method transfer {amount targetAccount} {
        my variable total
        my withdraw $amount
        $targetAccount deposit $amount
        set total
    }
    method dump {} {
        #HoHA
    }
    destructor {
        my variable total
        if {$total} {puts "remaining $total will be given to charity"}
    }
}

Changes to src/dirdiff.tcl.

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86


87

88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117























118
119
120
121
122
123
124
125
126
127
128
129
130





131
132
133
134

135
136
137





138
139
140
141







142
143
144

145
146
147
148
149
150
151
152
153


154

155





156



157
158
159
160
161
162
163
164
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------

package require tablelist_tile

# Compare file names
proc FStrCmp {s1 s2} {
    # Equality is based on platform's standard
    # Order is dictionary order

    # Exact equal is equal regardless of platform.
    if {$s1 eq $s2} {
        return 0
    }
    # Accept case insensitive equality on windows
    if {$::tcl_platform(platform) eq "windows"} {
        if {[string equal -nocase $s1 $s2]} {
            return 0
        }
    }
    # FIXA: What's the case on Mac?
    
    if {[lindex [lsort -dictionary [list $s1 $s2]] 0] eq $s1} {
        return -1
    }
    return 1
}

# Sort file names
proc Fsort {l} {
    lsort -dictionary $l
}

# Compare two files or dirs
# Return true if equal
proc CompareFiles {file1 file2} {
    global Pref
    if {[catch {file lstat $file1 stat1}]} {
        return 0
    }
    if {[catch {file lstat $file2 stat2}]} {
        return 0
    }

    # Same type?
    set isdir1 [FileIsDirectory $file1]
    set isdir2 [FileIsDirectory $file2]
    if {$isdir1 != $isdir2} {
	return 0
    }
    # Handle links
    if {$stat1(type) eq "link" && $stat2(type) eq "link"} {
        set l1 [file link $file1]
        set l2 [file link $file2]
        # Equal links are considered equal, otherwise check contents
        if {$l1 eq $l2} {
            return 1
        }
        file stat $file1 stat1
        file stat $file2 stat2
    }
    # If contents is not checked, same size is enough to be equal
    if {$stat1(size) == $stat2(size) && $Pref(dir,comparelevel) == 0} {
        return 1
    }


    set ignorekey $Pref(dir,ignorekey)

    # Different size is enough when doing binary compare
    if {$stat1(size) != $stat2(size) && $Pref(dir,comparelevel) == 2 \
        && !$ignorekey} {
        return 0
    }
    # Same size and time is always considered equal
    if {$stat1(size) == $stat2(size) && $stat1(mtime) == $stat2(mtime)} {
	return 1
    }
    # Don't check further if contents should not be checked
    if {$Pref(dir,comparelevel) == 0} {
        return 0
    }
    # Don't check further if any is a directory
    if {$isdir1 || $isdir2} {
        # Consider dirs equal until we implement something recursive
	return 1
    }

    switch $Pref(dir,comparelevel) {
        2 -
        1 { # Check contents internally
            set bufsz 65536
            set eq 1
            set ch1 [open $file1 r]
            set ch2 [open $file2 r]
            if {$Pref(dir,comparelevel) == 2} {
                fconfigure $ch1 -translation binary
                fconfigure $ch2 -translation binary
            }























            if {$ignorekey} {
                # Assume that all keywords are in the first block
                set f1 [read $ch1 $bufsz]
                set f2 [read $ch2 $bufsz]
                regsub -all {\$\w+:[^\$]*\$} $f1 {} f1
                regsub -all {\$\w+:[^\$]*\$} $f2 {} f2
                # Compensate for any change in length
                if {[string length $f1] < [string length $f2]} {
                    append f1 [read $ch1 [expr {[string length $f2] - [string length $f1]}]]
                }
                if {[string length $f2] < [string length $f1]} {
                    append f2 [read $ch2 [expr {[string length $f1] - [string length $f2]}]]
                }





                if {![string equal $f1 $f2]} {
                    set eq 0
                }
            }

            while {$eq && ![eof $ch1] && ![eof $ch2]} {
                set f1 [read $ch1 $bufsz]
                set f2 [read $ch2 $bufsz]





                if {![string equal $f1 $f2]} {
                    set eq 0
                }
            }







            if {![eof $ch1] || ![eof $ch2]} {
                set eq 0
            }

            close $ch1
            close $ch2
        }
    }
    return $eq
}

# Returns the contents of a directory as a sorted list of file tails.
proc DirContents {dir} {


    global Pref

    set files [glob -tails -directory $dir -nocomplain * {.[a-zA-Z]*}]









    if {$Pref(dir,onlyrev)} {
        # FIXA: move to rev and make general for other systems
        set entries [file join $dir CVS Entries]
        if {[file exists $entries]} {
            set ch [open $entries r]
            set data [read $ch]
            close $ch
            foreach line [split $data \n] {







<
<
















|







|
|





<











|













|


>
>
|
>

|
|


<
<
<
<

|





|


|



|


|



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>













>
>
>
>
>
|
|
|
|
>
|


>
>
>
>
>
|
|
|
|
>
>
>
>
>
>
>
|


>
|
|


|


|

>
>
|
>
|
>
>
>
>
>
|
>
>
>
|







18
19
20
21
22
23
24


25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92




93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------



# Compare file names
proc FStrCmp {s1 s2} {
    # Equality is based on platform's standard
    # Order is dictionary order

    # Exact equal is equal regardless of platform.
    if {$s1 eq $s2} {
        return 0
    }
    # Accept case insensitive equality on windows
    if {$::tcl_platform(platform) eq "windows"} {
        if {[string equal -nocase $s1 $s2]} {
            return 0
        }
    }
    # FIXA: What's the case on Mac?

    if {[lindex [lsort -dictionary [list $s1 $s2]] 0] eq $s1} {
        return -1
    }
    return 1
}

# Sort file names
proc Fsort {lst} {
    lsort -dictionary $lst
}

# Compare two files or dirs
# Return true if equal
proc CompareFiles {file1 file2} {

    if {[catch {file lstat $file1 stat1}]} {
        return 0
    }
    if {[catch {file lstat $file2 stat2}]} {
        return 0
    }

    # Same type?
    set isdir1 [FileIsDirectory $file1]
    set isdir2 [FileIsDirectory $file2]
    if {$isdir1 != $isdir2} {
        return 0
    }
    # Handle links
    if {$stat1(type) eq "link" && $stat2(type) eq "link"} {
        set l1 [file link $file1]
        set l2 [file link $file2]
        # Equal links are considered equal, otherwise check contents
        if {$l1 eq $l2} {
            return 1
        }
        file stat $file1 stat1
        file stat $file2 stat2
    }
    # If contents is not checked, same size is enough to be equal
    if {$stat1(size) == $stat2(size) && $::Pref(dir,comparelevel) == 0} {
        return 1
    }
    set anyPlugin $::eskil(.dirdiff,dirPlugin)

    set ignorekey $::Pref(dir,ignorekey)
    set nocase    $::Pref(nocase)
    # Different size is enough when doing binary compare
    if {$stat1(size) != $stat2(size) && $::Pref(dir,comparelevel) == 2 \
        && !$ignorekey && !$anyPlugin} {
        return 0
    }




    # Don't check further if contents should not be checked
    if {$::Pref(dir,comparelevel) == 0} {
        return 0
    }
    # Don't check further if any is a directory
    if {$isdir1 || $isdir2} {
        # Consider dirs equal until we implement something recursive
        return 1
    }

    switch $::Pref(dir,comparelevel) {
        2 -
        1 { # Check contents internally
            set bufsz 65536
            set eq 2 ;# 2 = equal this far, 1 = equal, 0 = not equal
            set ch1 [open $file1 r]
            set ch2 [open $file2 r]
            if {$::Pref(dir,comparelevel) == 2} {
                fconfigure $ch1 -translation binary
                fconfigure $ch2 -translation binary
            }
            # Allow a plugin to do its thing
            if {$anyPlugin} {
                #puts "PLUGIN!"
                $::eskil(.dirdiff,plugin,$anyPlugin) eval \
                        [list array set ::Pref [array get ::Pref]]
                $::eskil(.dirdiff,plugin,$anyPlugin) eval \
                        [list set ::argv $::eskil(argv)]
                interp share {} $ch1 $::eskil(.dirdiff,plugin,$anyPlugin)
                interp share {} $ch2 $::eskil(.dirdiff,plugin,$anyPlugin)
                set info1 [dict create name $file1 size $stat1(size)]
                set info2 [dict create name $file2 size $stat2(size)]
                set eq [$::eskil(.dirdiff,plugin,$anyPlugin) eval \
                                [list FileCompare $ch1 $ch2 $info1 $info2]]
                set allow [dict get $::eskil(.dirdiff,pluginpinfo,$anyPlugin) allow]
                if {$allow} {
                    $::eskil(.dirdiff,plugin,$anyPlugin) eval close $ch1
                    $::eskil(.dirdiff,plugin,$anyPlugin) eval close $ch2
                } else {
                    $::eskil(.dirdiff,plugin,$anyPlugin) invokehidden close $ch1
                    $::eskil(.dirdiff,plugin,$anyPlugin) invokehidden close $ch2
                }
            }

            if {$ignorekey} {
                # Assume that all keywords are in the first block
                set f1 [read $ch1 $bufsz]
                set f2 [read $ch2 $bufsz]
                regsub -all {\$\w+:[^\$]*\$} $f1 {} f1
                regsub -all {\$\w+:[^\$]*\$} $f2 {} f2
                # Compensate for any change in length
                if {[string length $f1] < [string length $f2]} {
                    append f1 [read $ch1 [expr {[string length $f2] - [string length $f1]}]]
                }
                if {[string length $f2] < [string length $f1]} {
                    append f2 [read $ch2 [expr {[string length $f1] - [string length $f2]}]]
                }
                if {$nocase} {
                    if { ! [string equal -nocase $f1 $f2]} {
                        set eq 0
                    }
                } else {
                    if { ! [string equal $f1 $f2]} {
                        set eq 0
                    }
                }
            }
            while {$eq == 2 && ![eof $ch1] && ![eof $ch2]} {
                set f1 [read $ch1 $bufsz]
                set f2 [read $ch2 $bufsz]
                if {$nocase} {
                    if { ! [string equal -nocase $f1 $f2]} {
                        set eq 0
                    }
                } else {
                    if { ! [string equal $f1 $f2]} {
                        set eq 0
                    }
                }
                # It has been observered that sometimes channels fail to
                # signal eof. Maybe when they come from a pipe?
                # Protect by noticing empty strings.
                if {[string equal $f1 ""] || [string equal $f2 ""]} {
                    break
                }
            }
            if {$eq == 2 && (![eof $ch1] || ![eof $ch2])} {
                set eq 0
            }
            # Errors during close are not interesting
            catch {close $ch1}
            catch {close $ch2}
        }
    }
    return [expr {$eq != 0}]
}

# Returns the contents of a directory as a sorted list of full file paths.
proc DirContents {dir} {
    if {$::tcl_platform(platform) eq "windows"} {
        # .-files are not treated specially on windows. * is enough to get all
        set files [glob -directory $dir -nocomplain *]
    } else {
        set files [glob -directory $dir -nocomplain *]
        # Handle .-files and make sure no duplicates are generated
        set files2 [glob -directory $dir -nocomplain {.[a-zA-Z]*}]
        foreach file $files2 {
            if {$file ni $files} {
                lappend files $file
            }
        }
    }

    if {$::Pref(dir,onlyrev)} {
        # FIXA: move to rev and make general for other systems
        set entries [file join $dir CVS Entries]
        if {[file exists $entries]} {
            set ch [open $entries r]
            set data [read $ch]
            close $ch
            foreach line [split $data \n] {
173
174
175
176
177
178
179
180

181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233

234
235
236
237
238
239
240
241
242
243
244
245
246
            }
            set files $files2
        }
    }

    set files2 {}
    foreach file $files {
        set full [file join $dir $file]

        # Apply filters
        if {[FileIsDirectory $full]} {
            if {[llength $Pref(dir,incdirs)] == 0} {
                set allowed 1
            } else {
                set allowed 0
                foreach pat $Pref(dir,incdirs) {
                    if {[string match $pat $file]} {
                        set allowed 1
                        break
                    }
                }
            }
            if {$allowed} {
                foreach pat $Pref(dir,exdirs) {
                    if {[string match $pat $file]} {
                        set allowed 0
                        break
                    }
                }
            }
            if {!$allowed} continue
        } else {
            if {[llength $Pref(dir,incfiles)] == 0} {
                set allowed 1
            } else {
                set allowed 0
                foreach pat $Pref(dir,incfiles) {
                    if {[string match $pat $file]} {
                        set allowed 1
                        break
                    }
                }
            }
            if {$allowed} {
                foreach pat $Pref(dir,exfiles) {
                    if {[string match $pat $file]} {
                        set allowed 0
                        break
                    }
                }
            }
            if {!$allowed} continue
        }
        lappend files2 $file
    }

    return [Fsort $files2]
}

# Bring up an editor to display a file.
proc EditFile {file} {
    locateEditor ::util(editor)

    exec $::util(editor) $file &
}

# Pick a directory for compare
proc BrowseDir {dirVar entryW} {
    global Pref
    upvar "#0" $dirVar dir

    set newdir $dir
    while {$newdir != "." && ![FileIsDirectory $newdir]} {
        set newdir [file dirname $newdir]
    }
    set newdir [tk_chooseDirectory -initialdir $newdir -title "Select Directory"]







|
>


|



|
|






|
|





|

|



|
|






|
|





|

|








>
|




<







222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289

290
291
292
293
294
295
296
            }
            set files $files2
        }
    }

    set files2 {}
    foreach file $files {
        set full $file
        set tail [file tail $file]
        # Apply filters
        if {[FileIsDirectory $full]} {
            if {[llength $::Pref(dir,incdirs)] == 0} {
                set allowed 1
            } else {
                set allowed 0
                foreach pat $::Pref(dir,incdirs) {
                    if {[string match $pat $tail]} {
                        set allowed 1
                        break
                    }
                }
            }
            if {$allowed} {
                foreach pat $::Pref(dir,exdirs) {
                    if {[string match $pat $tail]} {
                        set allowed 0
                        break
                    }
                }
            }
            if { ! $allowed} continue
        } else {
            if {[llength $::Pref(dir,incfiles)] == 0} {
                set allowed 1
            } else {
                set allowed 0
                foreach pat $::Pref(dir,incfiles) {
                    if {[string match $pat $tail]} {
                        set allowed 1
                        break
                    }
                }
            }
            if {$allowed} {
                foreach pat $::Pref(dir,exfiles) {
                    if {[string match $pat $tail]} {
                        set allowed 0
                        break
                    }
                }
            }
            if { ! $allowed} continue
        }
        lappend files2 $full
    }

    return [Fsort $files2]
}

# Bring up an editor to display a file.
proc EditFile {file} {
    locateEditor ::util(editor)
    # util(editor) may contain options, and is treated as a pre-command
    exec {*}$::util(editor) $file &
}

# Pick a directory for compare
proc BrowseDir {dirVar entryW} {

    upvar "#0" $dirVar dir

    set newdir $dir
    while {$newdir != "." && ![FileIsDirectory $newdir]} {
        set newdir [file dirname $newdir]
    }
    set newdir [tk_chooseDirectory -initialdir $newdir -title "Select Directory"]
255
256
257
258
259
260
261




262
263


264
265


266
267

268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312

313
314
315
316
317
318
319
    component tree
    component hsb
    component vsb

    option -leftdirvariable  -default "" -configuremethod SetDirOption
    option -rightdirvariable -default "" -configuremethod SetDirOption
    option -statusvar -default ""





    variable AfterId ""


    variable PauseBgProcessing 0
    variable ScheduledRestart 0


    variable IdleQueue {}
    variable IdleQueueArr

    variable leftMark ""
    variable rightMark ""
    variable leftDir ""
    variable rightDir ""
    variable img

    constructor {args} {
        variable color
        install tree using tablelist::tablelist $win.tree -height 20 \
                -movablecolumns no -setgrid no -showseparators yes \
                -expandcommand [mymethod expandCmd] \
                -collapsecommand [mymethod collapseCmd] \
                -fullseparators yes -selectmode none \
                -columns {0 "Structure" 0 Size 0 Date 0 Copy 0 Size 0 Date}
        install vsb using scrollbar $win.vsb -orient vertical \
                -command "$tree yview"
        install hsb using scrollbar $win.hsb -orient horizontal \
                -command "$tree xview"

        # Use demo images from Tablelist
        set dir $::eskil(thisDir)/../lib/tablelist/demos
        set img(clsd) [image create photo -file [file join $dir clsdFolder.gif]]
        set img(open) [image create photo -file [file join $dir openFolder.gif]]
        set img(file) [image create photo -file [file join $dir file.gif]]
        # Local images
        set dir $::eskil(thisDir)/images
        set img(link) [image create photo -file [file join $dir link.gif]]
        set img(left) [image create photo -file [file join $dir arrow_left.gif]]
        set img(right) [image create photo -file [file join $dir arrow_right.gif]]

        set AfterId ""
        set IdleQueue {}

        $tree configure -yscrollcommand "$vsb set" -xscrollcommand "$hsb set"

        $tree columnconfigure 0 -name structure
        $tree columnconfigure 1 -name leftsize -align right
        $tree columnconfigure 2 -name leftdate
        $tree columnconfigure 3 -name command
        $tree columnconfigure 4 -name rightsize -align right
        $tree columnconfigure 5 -name rightdate

        destroy [$tree separatorpath 1] [$tree separatorpath 4]

        set color(unknown) grey

        set color(empty) grey
        set color(equal) {}
        set color(new) green
        set color(old) blue
        set color(change) red

        #-expandcommand expandCmd







>
>
>
>


>
>


>
>


>




|









|

|


<
<
<
<
<
<
<
<
<
<
<















>







305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345











346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
    component tree
    component hsb
    component vsb

    option -leftdirvariable  -default "" -configuremethod SetDirOption
    option -rightdirvariable -default "" -configuremethod SetDirOption
    option -statusvar -default ""
    option -changelist -default ""
    option -norun -default 0
    # TODO: better name for experimental parameter
    option -bepa -default 0

    variable AfterId ""
    variable DebugCh ""
    variable DebugTime {}
    variable PauseBgProcessing 0
    variable ScheduledRestart 0
    variable AfterTime 1
    variable WorkTime 200
    variable IdleQueue {}
    variable IdleQueueArr
    variable NodeStatus
    variable leftMark ""
    variable rightMark ""
    variable leftDir ""
    variable rightDir ""
    variable protect {left 0 right 0}

    constructor {args} {
        variable color
        install tree using tablelist::tablelist $win.tree -height 20 \
                -movablecolumns no -setgrid no -showseparators yes \
                -expandcommand [mymethod expandCmd] \
                -collapsecommand [mymethod collapseCmd] \
                -fullseparators yes -selectmode none \
                -columns {0 "Structure" 0 Size 0 Date 0 Copy 0 Size 0 Date}
        install vsb using ttk::scrollbar $win.vsb -orient vertical \
                -command "$tree yview"
        install hsb using ttk::scrollbar $win.hsb -orient horizontal \
                -command "$tree xview"












        set AfterId ""
        set IdleQueue {}

        $tree configure -yscrollcommand "$vsb set" -xscrollcommand "$hsb set"

        $tree columnconfigure 0 -name structure
        $tree columnconfigure 1 -name leftsize -align right
        $tree columnconfigure 2 -name leftdate
        $tree columnconfigure 3 -name command
        $tree columnconfigure 4 -name rightsize -align right
        $tree columnconfigure 5 -name rightdate

        destroy [$tree separatorpath 1] [$tree separatorpath 4]

        set color(unknown) grey
        set color(unknown2) grey
        set color(empty) grey
        set color(equal) {}
        set color(new) green
        set color(old) blue
        set color(change) red

        #-expandcommand expandCmd
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378







379
380
381
382
383
384










385
386
387
388

389

















390
391
392
393
394
395
396
397
398
399
400
401

402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
















423
424
425

426
427
428
429
430
431
432
433
434
435
436
437
438















































439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478


















479
480
481






























482
483
484
485
486
487
488
489






490
491



492
493
494
495
496
497
498
499
500
501

502
503
504
505
506
507
508
509
    }

    method SetDirOption {option value} {
        set options($option) $value

        if {$options(-leftdirvariable) eq ""} return
        upvar \#0 $options(-leftdirvariable) left
        if {![info exists left]} return
        if {![file isdirectory $left]} return

        if {$options(-rightdirvariable) eq ""} return
        upvar \#0 $options(-rightdirvariable) right
        if {![info exists right]} return
        if {![file isdirectory $right]} return

        set leftDir $left
        set rightDir $right
        if {!$ScheduledRestart} {
            set ScheduledRestart 1
            after idle [mymethod ReStart]
        }
    }
    method newTopDir {newLeft newRight} {
        if {$newLeft ne "" && [file isdirectory $newLeft]} {
            upvar \#0 $options(-leftdirvariable) left
            set left $newLeft
            set leftDir $left
        }
        if {$newRight ne "" && [file isdirectory $newRight]} {
            upvar \#0 $options(-rightdirvariable) right
            set right $newRight
            set rightDir $right
        }
        if {!$ScheduledRestart} {
            set ScheduledRestart 1
            after idle [mymethod ReStart]
        }
    }        








    method ReStart {} {
        # Delete all idle processing
        if {$AfterId ne ""} {
            after cancel $AfterId
        }










        set AfterId ""
        set IdleQueue {}
        set ScheduledRestart 0
        array unset IdleQueueArr

        

















        # Fill in clean root data
        $tree delete 0 end
        set topIndex [$tree insertchild root end {}]
        set d1 [file tail $leftDir]
        set d2 [file tail $rightDir]
        if {$d1 eq $d2} {
            $tree cellconfigure $topIndex,structure -text $d1
        } else {
            $tree cellconfigure $topIndex,structure -text "$d1 vs $d2"
        }
        $tree cellconfigure $topIndex,structure -image $img(open)
        $tree rowattrib $topIndex type directory

        $self SetNodeStatus $topIndex empty
        $tree rowattrib $topIndex leftfull $leftDir             
        $tree rowattrib $topIndex rightfull $rightDir            

        $self UpdateDirNode $topIndex
    }

    method expandCmd {tbl row} {
        if {[$tree childcount $row] != 0} {
            $tree cellconfigure $row,0 -image $img(open)
        }
    }

    method collapseCmd {tbl row} {
        $tree cellconfigure $row,0 -image $img(clsd)
    }

    # Format a time stamp for display
    proc FormatDate {date} {
        clock format $date -format "%Y-%m-%d %H:%M:%S"
    }

















    # Remove all equal nodes from tree
    method PruneEqual {} {

        set todo [$tree childkeys root]
        while {[llength $todo] > 0} {
            set todoNow $todo
            set todo {}
            foreach node $todoNow {
                set status [$tree rowattrib $node status]
                if {$status eq "equal"} {
                    $tree delete $node
                } else {
                    lappend todo {*}[$tree childkeys $node]
                }
            }
        }















































    }

    # Open or close all directories in the tree view
    method OpenAll {{state 1}} {
        if {$state} {
            $tree expandall
        } else {
            $tree collapseall
        }
    }

    # Copy a file from one directory to the other
    method CopyFile {node from} {
        global dirdiff Pref

        set lf [$tree rowattrib $node leftfull]
        set rf [$tree rowattrib $node rightfull]
        set parent [$tree parent $node]
        set lp [$tree rowattrib $parent leftfull]
        set rp [$tree rowattrib $parent rightfull]

        if {$from eq "left"} {
            set src $lf
            if {$rf ne ""} {
                set dst $rf
            } elseif {$rp ne ""} {
                set dst [file join $rp [file tail $src]]
            } else {
                return
            }
        } elseif {$from eq "right"} {
            set src $rf
            if {$lf ne ""} {
                set dst $lf
            } elseif {$lp ne ""} {
                set dst [file join $lp [file tail $src]]
            } else {
                return
            }
        } else {


















            error "Bad from argument to CopyFile: $from"
        }































        if {[file exists $dst]} {
            if {[tk_messageBox -icon question -title "Overwrite file?" -message \
                    "Copy\n$src\noverwriting\n$dst ?" -type yesno] eq "yes"} {
                file copy -force $src $dst
                # FIXA: update file info in tree too
                $self SetNodeStatus $node equal
            }
        } else {






            if {[tk_messageBox -icon question -title "Copy file?" -message \
                    "Copy\n$src\nto\n$dst ?" -type yesno] eq "yes"} {



                file copy $src $dst
                # FIXA: update file info in tree too
                $self SetNodeStatus $node equal
            }
        }
    }

    # React on double-click
    method DoubleClick {W x y} {
        foreach {W x y} [tablelist::convEventFields $W $x $y] break

        set node [$tree index @$x,$y]

        set lf [$tree rowattrib $node leftfull]
        set rf [$tree rowattrib $node rightfull]
        set type [$tree rowattrib $node type]

        # On a file that exists on both sides, start a file diff
        if {$type eq "file" && $lf ne "" && $rf ne ""} {







|
|



|
|



|















|



|
>
>
>
>
>
>
>






>
>
>
>
>
>
>
>
>
>




>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>










|

>

|
|






|




|






>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



>





|







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>











|
<
<
|






|

<
<
|




|

<
<
|





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


|





>
>
>
>
>
>

|
>
>
>










>
|







390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599


600
601
602
603
604
605
606
607
608


609
610
611
612
613
614
615


616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
    }

    method SetDirOption {option value} {
        set options($option) $value

        if {$options(-leftdirvariable) eq ""} return
        upvar \#0 $options(-leftdirvariable) left
        if { ! [info exists left]} return
        if { ! [file isdirectory $left]} return

        if {$options(-rightdirvariable) eq ""} return
        upvar \#0 $options(-rightdirvariable) right
        if { ! [info exists right]} return
        if { ! [file isdirectory $right]} return

        set leftDir $left
        set rightDir $right
        if { ! $ScheduledRestart} {
            set ScheduledRestart 1
            after idle [mymethod ReStart]
        }
    }
    method newTopDir {newLeft newRight} {
        if {$newLeft ne "" && [file isdirectory $newLeft]} {
            upvar \#0 $options(-leftdirvariable) left
            set left $newLeft
            set leftDir $left
        }
        if {$newRight ne "" && [file isdirectory $newRight]} {
            upvar \#0 $options(-rightdirvariable) right
            set right $newRight
            set rightDir $right
        }
        if { ! $ScheduledRestart} {
            set ScheduledRestart 1
            after idle [mymethod ReStart]
        }
    }

    method nice {ms} {
        # Sanity check
        if {$ms < 1} { set ms 1 }
        if {$ms > 1000} {set ms 1000 }
        set AfterTime $ms
    }

    method ReStart {} {
        # Delete all idle processing
        if {$AfterId ne ""} {
            after cancel $AfterId
        }
        if {$DebugCh ne ""} {
            close $DebugCh
            set DebugCh ""
            set DebugTime {}
        }

        # Uncomment to activate debug logging
        #set DebugCh [open ~/dirdiff.log a]
        #$self DlogTablelist
        $self Dlog RESTART
        set AfterId ""
        set IdleQueue {}
        set ScheduledRestart 0
        array unset IdleQueueArr
        set protect {left 0 right 0}

        if {$options(-norun)} {
            set options(-norun) 0
            return
        }

        # Directory Diff only supports one plugin.
        # Find if any configured plugin supports dir diff and choose it.
        set ::eskil(.dirdiff,dirPlugin) 0
        foreach item [lsort -dictionary [array names ::eskil .dirdiff,pluginname,*]] {
            set n [lindex [split $item ","] end]
            if {$::eskil(.dirdiff,plugin,$n) ne "" && \
                        [dict get $::eskil(.dirdiff,pluginpinfo,$n) dir]} {
                set ::eskil(.dirdiff,dirPlugin) $n
                break
            }
        }

        # Fill in clean root data
        $tree delete 0 end
        set topIndex [$tree insertchild root end {}]
        set d1 [file tail $leftDir]
        set d2 [file tail $rightDir]
        if {$d1 eq $d2} {
            $tree cellconfigure $topIndex,structure -text $d1
        } else {
            $tree cellconfigure $topIndex,structure -text "$d1 vs $d2"
        }
        $tree cellconfigure $topIndex,structure -image $::img(open)
        $tree rowattrib $topIndex type directory
        set NodeStatus($topIndex) ""
        $self SetNodeStatus $topIndex empty
        $tree rowattrib $topIndex leftfull $leftDir
        $tree rowattrib $topIndex rightfull $rightDir

        $self UpdateDirNode $topIndex
    }

    method expandCmd {tbl row} {
        if {[$tree childcount $row] != 0} {
            $tree cellconfigure $row,0 -image $::img(open)
        }
    }

    method collapseCmd {tbl row} {
        $tree cellconfigure $row,0 -image $::img(clsd)
    }

    # Format a time stamp for display
    proc FormatDate {date} {
        clock format $date -format "%Y-%m-%d %H:%M:%S"
    }

    method busyCursor {} {
        variable oldcursor
        if { ! [info exists oldcursor]} {
            set oldcursor(hull) [$hull cget -cursor]
            set oldcursor(tree) [$tree cget -cursor]
        }
        $hull configure -cursor watch
        $tree configure -cursor watch
    }

    method normalCursor {} {
        variable oldcursor
        $hull configure -cursor $oldcursor(hull)
        $tree configure -cursor $oldcursor(tree)
    }

    # Remove all equal nodes from tree
    method PruneEqual {} {
        $self busyCursor
        set todo [$tree childkeys root]
        while {[llength $todo] > 0} {
            set todoNow $todo
            set todo {}
            foreach node $todoNow {
                set status $NodeStatus($node)
                if {$status eq "equal"} {
                    $tree delete $node
                } else {
                    lappend todo {*}[$tree childkeys $node]
                }
            }
        }
        $self normalCursor
    }
    # Remove all empty dir nodes from tree
    method PruneEmpty {} {
        $self busyCursor
        set redo 1
        while {$redo} {
            set redo 0
            set todo [$tree childkeys root]
            while {[llength $todo] > 0} {
                set todoNow $todo
                set todo {}
                foreach node $todoNow {
                    set status $NodeStatus($node)
                    set children [$tree childkeys $node]
                    if {[llength $children] == 0} {
                        set type [$tree rowattrib $node type]
                        if {$type eq "directory"} {
                            $tree delete $node
                            set redo 1
                        }
                    } else {
                        lappend todo {*}$children
                    }
                }
            }
        }
        $self normalCursor
    }

    # Remove all nodes that are just on one side
    method PruneAlone {} {
       $self busyCursor
        set todo [$tree childkeys root]
        while {[llength $todo] > 0} {
            set todoNow $todo
            set todo {}
            foreach node $todoNow {
                set status $NodeStatus($node)
                if {$status in {new old}} {
                    $tree delete $node
                } else {
                    lappend todo {*}[$tree childkeys $node]
                }
            }
        }
        $self normalCursor
    }

    # Open or close all directories in the tree view
    method OpenAll {{state 1}} {
        if {$state} {
            $tree expandall
        } else {
            $tree collapseall
        }
    }

    # Create a directory missing on one side


    method CreateDir {node to} {
        set lf [$tree rowattrib $node leftfull]
        set rf [$tree rowattrib $node rightfull]
        set parent [$tree parent $node]
        set lp [$tree rowattrib $parent leftfull]
        set rp [$tree rowattrib $parent rightfull]

        if {$to eq "right"} {
            set src $lf


            if {$rp ne ""} {
                set dst [file join $rp [file tail $src]]
            } else {
                return
            }
        } elseif {$to eq "left"} {
            set src $rf


            if {$lp ne ""} {
                set dst [file join $lp [file tail $src]]
            } else {
                return
            }
        } else {
            error "Bad from argument to CreateDir: $to"
        }
        if {[tk_messageBox -icon question -title "Create dir?" -message \
                    "Create\n$dst ?" -type yesno] eq "yes"} {
            file mkdir $dst
            # FIXA: update file info in tree too
            #$self SetNodeStatus $node equal
        }
    }

    # Copy a file from one directory to the other
    method CopyFile {node from} {
        ##nagelfar vartype tree _obj,tablelist
        if {$from eq "left"} {
            set to right
        } elseif {$from eq "right"} {
            set to left
        } else {
            error "Bad from argument to CopyFile: $from"
        }


        set fromf  [$tree rowattrib $node ${from}full]
        set tof    [$tree rowattrib $node ${to}full]
        set parent [$tree parent $node]
        set fromp  [$tree rowattrib $parent ${from}full]
        set top    [$tree rowattrib $parent ${to}full]

        set src $fromf
        if {$tof ne ""} {
            set dst $tof
        } else {
            # Go up until we find a common parent
            set dst [file tail $src]
            set Count 0 ;# Safety check while debugging
            while {$Count < 1000} {
                if {[incr Count] > 999} {
                    error "Internal error in CopyFile $from"
                }
                if {$top ne ""} {
                    set dst [file join $top $dst]
                    break
                }
                # Continue up to a commmon parent
                set dst [file join [file tail $fromp] $dst]
                set parent [$tree parent $parent]
                set fromp [$tree rowattrib $parent ${from}full]
                set top   [$tree rowattrib $parent ${to}full]
            }
        }

        if {[file exists $dst]} {
            if {[tk_messageBox -icon question -title "Overwrite file?" -message \
                    "Copy\n$src\n\noverwriting\n$dst ?" -type yesno] eq "yes"} {
                file copy -force $src $dst
                # FIXA: update file info in tree too
                $self SetNodeStatus $node equal
            }
        } else {
            set msg "Copy\n$src\nto\n$dst ?"
            set dstdir [file dirname $dst]
            if { ! [file isdirectory $dstdir]} {
                append msg "\nCreating Directory\n$dstdir ?"
            }

            if {[tk_messageBox -icon question -title "Copy file?" -message \
                         $msg -type yesno] eq "yes"} {
                if { ! [file isdirectory $dstdir]} {
                    file mkdir $dstdir
                }
                file copy $src $dst
                # FIXA: update file info in tree too
                $self SetNodeStatus $node equal
            }
        }
    }

    # React on double-click
    method DoubleClick {W x y} {
        foreach {W x y} [tablelist::convEventFields $W $x $y] break
        set index [$tree index @$x,$y]
        set node [$tree getfullkeys $index]

        set lf [$tree rowattrib $node leftfull]
        set rf [$tree rowattrib $node rightfull]
        set type [$tree rowattrib $node type]

        # On a file that exists on both sides, start a file diff
        if {$type eq "file" && $lf ne "" && $rf ne ""} {
533
534
535
536
537
538
539

540
541
542
543
544
545
546
547
548
549
550
551
552
553
554


555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573





574
575
576
577
578





579
580
581
582

583
584

585
586
587
588
589
590
591
592
593

594
595

596
597
598
599
600
601
602
603
604
605







606
607
608
609












610
611


612






613
614
615
616
617
618































619


620
621
622

623
624
625
626
627
628

629
630










631
632
633
634
635
636
637
638
639
640

641
642
643

644
645
646
647
648
649
650
651
652
653
654
655



656

657
658




659
660
661
662
663
664
665

666
667
668
669
670
671
672
673

674
675

676
677
678
679
680
681
682

683

684
685
686
687
688
689
690
691
692
693
694
695
696




697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724











725
726
727
728
729
730











731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751

752
753
754

755
756
757
758

759
760
761

762








763
764
765
766
767
768
769
770
771
772



773

774
775

776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810


811
812
813
814
815
816
817
818
819
820
821
822
823



824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848



849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
        }
    }

    # Bring up a context menu on a file.
    method ContextMenu {W x y X Y} {
        foreach {W x y} [tablelist::convEventFields $W $x $y] break


        set node [$tree index @$x,$y]
        set col [$tree columnindex @$x,$y]
        set colname [$tree columncget $col -name]

        set lf [$tree rowattrib $node leftfull]
        set rf [$tree rowattrib $node rightfull]
        set type [$tree rowattrib $node type]
        set oneside [expr {($lf ne "") ^ ($rf ne "")}]

        set m $win.popup
        destroy $m
        menu $m
        
        if {$colname eq "structure"} {
            $m add command -label "Prune equal" -command [mymethod PruneEqual]


            $m add command -label "Expand all" -command [mymethod OpenAll]
            $m add command -label "Collapse all" -command [mymethod OpenAll 0]
        }

        if {$type eq "file" && $lf ne "" && $rf ne ""} {
            # Files, both exist
            $m add command -label "Compare Files" -command [list \
                    newDiff $lf $rf]
        }
        if {$type eq "directory"} {
            if {$lf ne "" && $rf ne ""} {
                # Directory, both exist
                $m add command -label "Go down" -command [mymethod \
                        newTopDir $lf $rf]
            }
            if {$lf ne ""} {
                # Directory, left exist
                $m add command -label "Go down left" -command [mymethod \
                        newTopDir $lf ""]





            }
            if {$rf ne ""} {
                # Directory, right exist
                $m add command -label "Go down right" -command [mymethod \
                        newTopDir "" $rf]





            }
        }
        if {$type eq "file"} {
            if {([string match left* $colname] || $oneside) && $lf ne ""} {

                $m add command -label "Copy File to Right" \
                        -command [mymethod CopyFile $node left]

                $m add command -label "Edit Left File" \
                        -command [list EditFile $lf]
                $m add command -label "Mark Left File" \
                        -command [list set [myvar leftMark] $lf]
                if {$rightMark != ""} {
                    $m add command -label "Compare Left with $rightMark" \
                            -command [list newDiff $lf $rightMark]
                }
            } elseif {([string match right* $colname] || $oneside) && $rf ne ""} {

                $m add command -label "Copy File to Left" \
                        -command [mymethod CopyFile $node right]

                $m add command -label "Edit Right File" \
                        -command [list EditFile $rf]
                $m add command -label "Mark Right File" \
                        -command [list set [myvar rightMark] $rf]
                if {$leftMark != ""} {
                    $m add command -label "Compare Right with $leftMark" \
                            -command [list newDiff $leftMark $rf]
                }
            }
        }








        tk_popup $m $X $Y
    }













    method AddNodeToIdle {node} {
        if {[info exists IdleQueueArr($node)]} { return }


        lappend IdleQueue $node






        set IdleQueueArr($node) 1

        if {$AfterId eq ""} {
            set AfterId [after 1 [mymethod UpdateIdle]]
        }
    }































    method UpdateIdle {} {


        set AfterId "X"

        if {$PauseBgProcessing} {

            set AfterId [after 200 [mymethod UpdateIdle]]
            return
        }

        set pre [clock clicks -milliseconds]
        set errors {}

        while {[llength $IdleQueue] > 0} {
            set node [lindex $IdleQueue 0]










            set IdleQueue [lrange $IdleQueue 1 end]
            unset IdleQueueArr($node)

            if {[$tree rowattrib $node type] ne "directory"} {
                set sts [catch {$self UpdateFileNode $node} err]
            } else {
                set sts [catch {$self UpdateDirNode $node} err]
            }
            if {$sts} {
                lappend errors $err

            }
            # Work for at least 20 ms to keep things efficient
            set post [clock clicks -milliseconds]

            if {($post - $pre) > 20} break
        }
        #if {($post - $pre) > 1000} {
            #puts "[expr $post - $pre] ms for [$tree set $node leftfull]"
        #}

        # Update the status variable to track progress
        if {$options(-statusvar) ne ""} {
            upvar \#0 $options(-statusvar) statusvar
        }

        if {[llength $errors] > 0} {



            set answer [tk_messageBox -icon error -type yesno -message \

                    "Error during directory processing:\n[join $errors \n]\nContinue?"]
            if {$answer eq "no"} {




                set statusvar ""
                set AfterId ""
                return
            }
        }

        if {[llength $IdleQueue] > 0} {

            set leftfull [$tree rowattrib $node leftfull]
            set rightfull [$tree rowattrib $node rightfull]
            if {$leftfull ne ""} {
                set statusvar $leftfull
            } else {
                set statusvar $rightfull
            }


            set AfterId [after 1 [mymethod UpdateIdle]]
        } else {

            set statusvar ""
            set AfterId ""
        }
    }

    method SetNodeStatus {node status} {
        variable color

        $tree rowattrib $node status $status

        $tree rowconfigure $node -foreground $color($status) \
                -selectforeground $color($status)
        #puts "Set [$tree item $node -text] to $status"

        # Loop through children to update parent
        set parent [$tree parentkey $node]
        if {$parent eq "" || $parent eq "root"} { return }

        # If this is only present on one side, there is no need to update
        set lf [$tree rowattrib $parent leftfull]
        set rf [$tree rowattrib $parent rightfull]
        if {$lf eq "" || $rf eq ""} { return }





        set pstatus equal
        foreach child [$tree childkeys $parent] {
            set status [$tree rowattrib $child status]
            switch $status {
                unknown {
                    set pstatus unknown
                }
                new - old - change {
                    set pstatus change
                    break
                }
            }
        }
        $self SetNodeStatus $parent $pstatus
    }

    method UpdateDirNode {node} {
        if {[$tree rowattrib $node type] ne "directory"} {
            return
        }
        if {[$tree rowattrib $node status] ne "empty"} {
            #puts "Dir [$tree set $node leftfull] already done"
            return
        }
        $tree delete [$tree childkeys $node]

        set leftfull [$tree rowattrib $node leftfull]
        set rightfull [$tree rowattrib $node rightfull]











        $self CompareDirs $leftfull $rightfull $node
    }

    method UpdateFileNode {node} {
        set leftfull [$tree rowattrib $node leftfull]
        set rightfull [$tree rowattrib $node rightfull]











        set equal [CompareFiles $leftfull $rightfull]
        if {$equal} {
            $self SetNodeStatus $node equal
        } else {
            $self SetNodeStatus $node change
        }
    }

    # List files under a directory node
    # Returns status for the new node
    method ListFiles {df1 df2 node} {
        if {$df1 ne ""} {
            set type [file type $df1]
            set name [file tail $df1]
        } else {
            set type [file type $df2]
            set name [file tail $df2]
        }
        if {[catch {file stat $df1 stat1}]} {
            set size1 ""
            set time1 ""

        } else {
            set size1 $stat1(size)
            set time1 [FormatDate $stat1(mtime)]

        }
        if {[catch {file stat $df2 stat2}]} {
            set size2 ""
            set time2 ""

        } else {
            set size2 $stat2(size)
            set time2 [FormatDate $stat2(mtime)]

        }








        if {$type eq "directory"} {
            set values [list $name \
                    "" "" \
                    "" \
                    "" ""]
        } else {
            set values [list $name \
                    $size1 $time1 \
                    "" \
                    $size2 $time2]



        }

        set id [$tree insertchild $node end $values]
        $tree rowattrib $id type $type

        $tree rowattrib $id status unknown
        $tree rowattrib $id leftfull $df1
        $tree rowattrib $id rightfull $df2
        if {$type ne "directory"} {
            if {$type eq "link"} {
                $tree cellconfigure $id,structure -image $img(link)
            } else {
                $tree cellconfigure $id,structure -image $img(file)
                $tree cellconfigure $id,command -window [mymethod addCmdCol]
            }
        }

        if {$type eq "directory"} {
            ## Make it so that this node is openable
            $tree collapse $id
            #$tree insertchild $id end dummy ;# a dummy
            $tree cellconfigure $id,structure -text $name/
            $self SetNodeStatus $id empty
            $self AddNodeToIdle $id
            $tree cellconfigure $id,structure -image $img(clsd)
        } elseif {$size1 == $size2 && \
                $time1 == $time2} {
            $self SetNodeStatus $id equal
        } elseif {$size1 == ""} {
            $self SetNodeStatus $id new
        } elseif {$size2 == ""} {
            $self SetNodeStatus $id old
        } else {
            $self SetNodeStatus $id unknown
            $self AddNodeToIdle $id
        }
        return [$tree rowattrib $id status]
    }

    method addCmdCol {tbl row col w} {


        set status [$tree rowattrib $row status]
        set type   [$tree rowattrib $row type]
        set lf [$tree rowattrib $row leftfull]
        set rf [$tree rowattrib $row rightfull]
        set bg [$tbl cget -background]
        ttk::style configure Apa.TFrame -background $bg
        ttk::style configure Apa.Toolbutton -background $bg
        ttk::frame $w -style Apa.TFrame
        ttk::button $w.bl -image $img(left) -style Apa.Toolbutton \
                -command [mymethod CopyFile $row right]
        ttk::button $w.br -image $img(right) -style Apa.Toolbutton \
                -command [mymethod CopyFile $row left]
        pack $w.bl $w.br -side left -fill y



        if {$lf eq ""} {
            $w.br configure -state disabled
        }
        if {$rf eq ""} {
            $w.bl configure -state disabled
        }
    }

    # Compare two directories.
    method CompareDirs {dir1 dir2 node} {
        global Pref
        if {$dir1 eq ""} {
            set files1 {}
        } else {
            set files1 [DirContents $dir1]
        }
        if {$dir2 eq ""} {
            set files2 {}
        } else {
            set files2 [DirContents $dir2]
        }

        set len1 [llength $files1]
        set len2 [llength $files2]




        set p1 0
        set p2 0
        set status_change 0
        set status_unknown 0
        while 1 {
            if {$p1 < $len1 && $p2 < $len2} {
                set f1 [lindex $files1 $p1]
                set df1 [file join $dir1 $f1]
                set f2 [lindex $files2 $p2]
                set df2 [file join $dir2 $f2]
                set apa [FStrCmp $f1 $f2]
                if {$apa == 0} {
                    # Equal names, separate them if not the same type
                    set apa [expr {- [FileIsDirectory $df1] \
                                   + [FileIsDirectory $df2]}]
                }








>
|











|


>
>

















|
|
>
>
>
>
>



|
|
>
>
>
>
>




>
|
|
>









>
|
|
>










>
>
>
>
>
>
>




>
>
>
>
>
>
>
>
>
>
>
>
|

>
>
|
>
>
>
>
>
>



|


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>
>



>






>

|
>
>
>
>
>
>
>
>
>
>
|








|
>

|

>
|











>
>
>
|
>
|
|
>
>
>
>







>
|
|

|

|


>
|

>







>
|
>













>
>
>
>


|

|















|







>
>
>
>
>
>
>
>
>
>
>






>
>
>
>
>
>
>
>
>
>
>


|








<
<
<
<
<
<
<
|


>



>

|


>



>

>
>
>
>
>
>
>
>

|








>
>
>
|
>

|
>
|
|
|


|

|







<
<

|
<
<
<
<








|



>
>
|





|

|
|
|
|

>
>
>










<














>
>
>






|
|
|
|







734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072







1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131


1132
1133




1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173

1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
        }
    }

    # Bring up a context menu on a file.
    method ContextMenu {W x y X Y} {
        foreach {W x y} [tablelist::convEventFields $W $x $y] break

        set index [$tree index @$x,$y]
        set node [$tree getfullkeys $index]
        set col [$tree columnindex @$x,$y]
        set colname [$tree columncget $col -name]

        set lf [$tree rowattrib $node leftfull]
        set rf [$tree rowattrib $node rightfull]
        set type [$tree rowattrib $node type]
        set oneside [expr {($lf ne "") ^ ($rf ne "")}]

        set m $win.popup
        destroy $m
        menu $m

        if {$colname eq "structure"} {
            $m add command -label "Prune equal" -command [mymethod PruneEqual]
            $m add command -label "Prune empty" -command [mymethod PruneEmpty]
            $m add command -label "Prune alone" -command [mymethod PruneAlone]
            $m add command -label "Expand all" -command [mymethod OpenAll]
            $m add command -label "Collapse all" -command [mymethod OpenAll 0]
        }

        if {$type eq "file" && $lf ne "" && $rf ne ""} {
            # Files, both exist
            $m add command -label "Compare Files" -command [list \
                    newDiff $lf $rf]
        }
        if {$type eq "directory"} {
            if {$lf ne "" && $rf ne ""} {
                # Directory, both exist
                $m add command -label "Go down" -command [mymethod \
                        newTopDir $lf $rf]
            }
            if {$lf ne ""} {
                # Directory, left exist
                $m add command -label "Go down left" -command \
                        [mymethod newTopDir $lf ""]
                if {$rf eq ""} {
                    # Only left exist
                    $m add command -label "Create Dir right" -command \
                            [mymethod CreateDir $node right]
                }
            }
            if {$rf ne ""} {
                # Directory, right exist
                $m add command -label "Go down right" -command \
                        [mymethod newTopDir "" $rf]
                if {$lf eq ""} {
                    # Only right exist
                    $m add command -label "Create Dir left" -command \
                            [mymethod CreateDir $node left]
                }
            }
        }
        if {$type eq "file"} {
            if {([string match left* $colname] || $oneside) && $lf ne ""} {
                if { ! [dict get $protect right]} {
                    $m add command -label "Copy File to Right" \
                            -command [mymethod CopyFile $node left]
                }
                $m add command -label "Edit Left File" \
                        -command [list EditFile $lf]
                $m add command -label "Mark Left File" \
                        -command [list set [myvar leftMark] $lf]
                if {$rightMark != ""} {
                    $m add command -label "Compare Left with $rightMark" \
                            -command [list newDiff $lf $rightMark]
                }
            } elseif {([string match right* $colname] || $oneside) && $rf ne ""} {
                if { ! [dict get $protect left]} {
                    $m add command -label "Copy File to Left" \
                            -command [mymethod CopyFile $node right]
                }
                $m add command -label "Edit Right File" \
                        -command [list EditFile $rf]
                $m add command -label "Mark Right File" \
                        -command [list set [myvar rightMark] $rf]
                if {$leftMark != ""} {
                    $m add command -label "Compare Right with $leftMark" \
                            -command [list newDiff $leftMark $rf]
                }
            }
        }
        if {[string match left* $colname] && ![dict get $protect left]} {
            $m add command -label "Protect Left Side" \
                    -command [mymethod ProtectSide left]
        } elseif {[string match right* $colname] && ![dict get $protect right]} {
            $m add command -label "Protect Right Side" \
                    -command [mymethod ProtectSide right]
        }

        tk_popup $m $X $Y
    }

    # Mark one side as protected and disable all copy buttons
    method ProtectSide {side} {
        variable widgets
        dict set protect $side 1
        foreach w [dict get $widgets $side] {
            if {[winfo exists $w]} {
                $w configure -state disabled
            }
        }
        dict set widgets $side {}
    }

    method AddNodeToIdle {node {first 0}} {
        if {[info exists IdleQueueArr($node)]} { return }
        if {$first} {
            # Items are popped from the end, so last is first
            lappend IdleQueue $node
        } else {
            # Avoid compiled linsert by having index in a variable
            set c0 0
            set IdleQueue [linsert $IdleQueue[set IdleQueue {}] $c0 $node]
        }

        set IdleQueueArr($node) 1

        if {$AfterId eq ""} {
            set AfterId [after $AfterTime [mymethod UpdateIdle]]
        }
    }
    # Debug logging
    method Dlog {args} {
        if {$DebugCh ne ""} {
            set msg [join $args]
            set now [clock clicks -milliseconds]
            set suffix ""
            if {[dict exists $DebugTime $msg]} {
                set delta [expr {$now - [dict get $DebugTime $msg]}]
                set suffix " (+$delta)"
            }
            dict set DebugTime $msg $now
            puts $DebugCh "$now $msg$suffix"
            flush $DebugCh
        }
    }
    method DlogTablelist {} {
        puts DlogTablelist
        foreach cmd [info commands ::tablelist::*] {
            set tail [namespace tail $cmd]
            #if {[string match *SubCmd $tail]} continue
            if {$tail in {
                synchronize
                tablelistWidgetCmd cleanupWindow getTablelistPath
                handleMotion handleMotionDelayed
                rowIndex isInteger keyToRow colIndex
            }} continue
            
            trace add execution $cmd enter [mymethod Dlog]
            puts "Traced $cmd"
        }
    }
    method UpdateIdle {} {
        ##nagelfar vartype tree _obj,tablelist
        $self Dlog UpdateIdle
        set AfterId "X"

        if {$PauseBgProcessing} {
            $self Dlog Pause
            set AfterId [after 200 [mymethod UpdateIdle]]
            return
        }

        set pre [clock clicks -milliseconds]
        set errors {}
        set count 0
        while {[llength $IdleQueue] > 0} {
            set node [lindex $IdleQueue end]
            # Always make a pause before a large file
            if {[$tree rowattrib $node type] ne "directory"} {
                if {[$tree rowattrib $node largefile]} {
                    if {$count > 0} {
                        $self Dlog "New Lap for large file"
                        break
                    }
                }
            }
            incr count
            set IdleQueue [lrange $IdleQueue[set IdleQueue {}] 0 end-1]
            unset IdleQueueArr($node)

            if {[$tree rowattrib $node type] ne "directory"} {
                set sts [catch {$self UpdateFileNode $node} err]
            } else {
                set sts [catch {$self UpdateDirNode $node} err]
            }
            if {$sts} {
                lappend errors $node $err
                break
            }
            # Work for at least 200 ms to keep things efficient
            set post [clock clicks -milliseconds]
            #puts "$pre $post [expr {$post - $pre}]"
            if {($post - $pre) > $WorkTime} break
        }
        #if {($post - $pre) > 1000} {
            #puts "[expr $post - $pre] ms for [$tree set $node leftfull]"
        #}

        # Update the status variable to track progress
        if {$options(-statusvar) ne ""} {
            upvar \#0 $options(-statusvar) statusvar
        }

        if {[llength $errors] > 0} {
            lassign $errors node err
            set leftfull [$tree rowattrib $node leftfull]
            set rightfull [$tree rowattrib $node rightfull]
            set answer [tk_messageBox -icon error -type abortretryignore \
                    -message \
                    "Error comparing\n$leftfull\nvs\n$rightfull:\n$err"]
            if {$answer eq "retry"} {
                $self AddNodeToIdle $node
            } elseif {$answer eq "ignore"} {
                # Do nothing, just continue
            } else {
                set statusvar ""
                set AfterId ""
                return
            }
        }

        if {[llength $IdleQueue] > 0} {
            set node [lindex $IdleQueue end]
            set leftfull [$tree rowattrib $node "leftfull"]
            set rightfull [$tree rowattrib $node "rightfull"]
            if {$leftfull ne ""} {
                set statusvar "$leftfull  ($count)"
            } else {
                set statusvar "$rightfull  ($count)"
            }

            $self Dlog Reschedule
            set AfterId [after $AfterTime [mymethod UpdateIdle]]
        } else {
            $self Dlog DONE
            set statusvar ""
            set AfterId ""
        }
    }

    method SetNodeStatus {node status} {
        variable color
        set old $NodeStatus($node)
        if {$old eq $status} return
        set NodeStatus($node) $status
        $tree rowconfigure $node -foreground $color($status) \
                -selectforeground $color($status)
        #puts "Set [$tree item $node -text] to $status"

        # Loop through children to update parent
        set parent [$tree parentkey $node]
        if {$parent eq "" || $parent eq "root"} { return }

        # If this is only present on one side, there is no need to update
        set lf [$tree rowattrib $parent leftfull]
        set rf [$tree rowattrib $parent rightfull]
        if {$lf eq "" || $rf eq ""} { return }

        # If parent is being filled, do not update yet
        if {$NodeStatus($parent) eq "unknown2"} {
            return
        }
        set pstatus equal
        foreach child [$tree childkeys $parent] {
            set status $NodeStatus($child)
            switch $status {
                unknown - unknown2 {
                    set pstatus unknown
                }
                new - old - change {
                    set pstatus change
                    break
                }
            }
        }
        $self SetNodeStatus $parent $pstatus
    }

    method UpdateDirNode {node} {
        if {[$tree rowattrib $node type] ne "directory"} {
            return
        }
        if {$NodeStatus($node) ne "empty"} {
            #puts "Dir [$tree set $node leftfull] already done"
            return
        }
        $tree delete [$tree childkeys $node]

        set leftfull [$tree rowattrib $node leftfull]
        set rightfull [$tree rowattrib $node rightfull]
        #$self Dlog "UpdateDirNode $leftfull"
	if {$options(-bepa)} {
	    if {$leftfull eq ""} {
		$self SetNodeStatus $node new
		return
	    }
	    if {$rightfull eq ""} {
		$self SetNodeStatus $node old
		return
	    }
	}
        $self CompareDirs $leftfull $rightfull $node
    }

    method UpdateFileNode {node} {
        set leftfull [$tree rowattrib $node leftfull]
        set rightfull [$tree rowattrib $node rightfull]
        #$self Dlog "UpdateFileNode $leftfull"
        # If a -changelist is given, some higher level optimisation has already
        # figured out what has changed, so the processing time can be cut down.
        if {[llength $options(-changelist)]} {
            if {$rightfull ni $options(-changelist)} {
                #puts "$rightfull equal since not in change list"
                $self SetNodeStatus $node equal
                return
            }
            #puts "$rightfull checked since in change list"
        }
        set equal [CompareFiles $leftfull $rightfull]
        if {$equal} {
            $self SetNodeStatus $node "equal"
        } else {
            $self SetNodeStatus $node change
        }
    }

    # List files under a directory node
    # Returns status for the new node
    method ListFiles {df1 df2 node} {







        if {[catch {file lstat $df1 stat1}]} {
            set size1 ""
            set time1 ""
            set type1 ""
        } else {
            set size1 $stat1(size)
            set time1 [FormatDate $stat1(mtime)]
            set type1 $stat1(type)
        }
        if {[catch {file lstat $df2 stat2}]} {
            set size2 ""
            set time2 ""
            set type2 ""
        } else {
            set size2 $stat2(size)
            set time2 [FormatDate $stat2(mtime)]
            set type2 $stat2(type)
        }
        if {$df1 ne ""} {
            set type $type1
            set name [file tail $df1]
        } else {
            set type $type2
            set name [file tail $df2]
        }
        set largeFile 0
        if {$type eq "directory"} {
            set values [list $name/ \
                    "" "" \
                    "" \
                    "" ""]
        } else {
            set values [list $name \
                    $size1 $time1 \
                    "" \
                    $size2 $time2]
            # TODO: Configurable large file value?
            if {$size1 > 1000000 && $size2 > 1000000} {
                set largeFile 1
            }
        }
        set id [$tree insertchild $node end $values]
        $tree rowattrib $id "type" $type
        set NodeStatus($id) unknown
        $tree rowattrib $id leftfull $df1
        $tree rowattrib $id rightfull $df2
        $tree rowattrib $id largefile $largeFile
        if {$type ne "directory"} {
            if {$type eq "link"} {
                $tree cellconfigure $id,structure -image $::img(link)
            } else {
                $tree cellconfigure $id,structure -image $::img(file)
                $tree cellconfigure $id,command -window [mymethod addCmdCol]
            }
        }

        if {$type eq "directory"} {
            ## Make it so that this node is openable
            $tree collapse $id


            $self SetNodeStatus $id empty
            $self AddNodeToIdle $id 1




        } elseif {$size1 == ""} {
            $self SetNodeStatus $id new
        } elseif {$size2 == ""} {
            $self SetNodeStatus $id old
        } else {
            $self SetNodeStatus $id unknown
            $self AddNodeToIdle $id
        }
        return $NodeStatus($id)
    }

    method addCmdCol {tbl row col w} {
        variable widgets
        set key [$tree getfullkeys $row]
        set status $NodeStatus($key)
        set type   [$tree rowattrib $row type]
        set lf [$tree rowattrib $row leftfull]
        set rf [$tree rowattrib $row rightfull]
        set bg [$tbl cget -background]
        ttk::style configure Apa.TFrame -background $bg
        ttk::style configure Apa.My.Toolbutton -background $bg
        ttk::frame $w -style Apa.TFrame
        ttk::button $w.bl -image $::img(left) -style Apa.My.Toolbutton \
                -command [mymethod CopyFile $key right]
        ttk::button $w.br -image $::img(right) -style Apa.My.Toolbutton \
                -command [mymethod CopyFile $key left]
        pack $w.bl $w.br -side left -fill y
        # Store widgets names
        dict lappend widgets left $w.bl
        dict lappend widgets right $w.br
        if {$lf eq ""} {
            $w.br configure -state disabled
        }
        if {$rf eq ""} {
            $w.bl configure -state disabled
        }
    }

    # Compare two directories.
    method CompareDirs {dir1 dir2 node} {

        if {$dir1 eq ""} {
            set files1 {}
        } else {
            set files1 [DirContents $dir1]
        }
        if {$dir2 eq ""} {
            set files2 {}
        } else {
            set files2 [DirContents $dir2]
        }

        set len1 [llength $files1]
        set len2 [llength $files2]

        # Unknown2 is used to mark a directory filling up
        $self SetNodeStatus $node unknown2

        set p1 0
        set p2 0
        set status_change 0
        set status_unknown 0
        while 1 {
            if {$p1 < $len1 && $p2 < $len2} {
                set df1 [lindex $files1 $p1]
                set f1 [file tail $df1]
                set df2 [lindex $files2 $p2]
                set f2 [file tail $df2]
                set apa [FStrCmp $f1 $f2]
                if {$apa == 0} {
                    # Equal names, separate them if not the same type
                    set apa [expr {- [FileIsDirectory $df1] \
                                   + [FileIsDirectory $df2]}]
                }

880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
                    1 {
                        $self ListFiles "" $df2 $node
                        incr p2
                        set status_change 1
                    }
                }
            } elseif {$p1 < $len1 && $p2 >= $len2} {
                set f1 [lindex $files1 $p1]
                $self ListFiles [file join $dir1 $f1] "" $node
                incr p1
                set status_change 1
            } elseif {$p1 >= $len1 && $p2 < $len2} {
                set f2 [lindex $files2 $p2]
                $self ListFiles "" [file join $dir2 $f2] $node
                incr p2
                set status_change 1
            } else {
                break
            }
        }
        if {$dir1 eq ""} {







|
|



|
|







1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
                    1 {
                        $self ListFiles "" $df2 $node
                        incr p2
                        set status_change 1
                    }
                }
            } elseif {$p1 < $len1 && $p2 >= $len2} {
                set df1 [lindex $files1 $p1]
                $self ListFiles $df1 "" $node
                incr p1
                set status_change 1
            } elseif {$p1 >= $len1 && $p2 < $len2} {
                set df2 [lindex $files2 $p2]
                $self ListFiles "" $df2 $node
                incr p2
                set status_change 1
            } else {
                break
            }
        }
        if {$dir1 eq ""} {
913
914
915
916
917
918
919


920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937





938
939
940
941
942
943
944
}

snit::widget DirDiff {
    hulltype toplevel
    widgetclass Toplevel
    component tree
    variable statusVar



    constructor {args} {
        eskilRegisterToplevel $win
        wm title $win "Eskil Dir"
        wm protocol $win WM_DELETE_WINDOW [list cleanupAndExit $win]

        set dir $::eskil(thisDir)/images
        set img(open) [image create photo -file [file join $dir folderopen1.gif]]
        set img(up) [image create photo -file [file join $dir arrow_up.gif]]
        set h [image height $img(up)]
        set w [image width $img(up)]
        set img(upup) [image create photo -height $h -width [expr {2 * $w}]]
        $img(upup) copy $img(up) -to 0 0 [expr {2 * $w - 1}] [expr {$h - 1}]

        install tree using DirCompareTree $win.dc \
                -leftdirvariable ::dirdiff(leftDir) \
                -rightdirvariable ::dirdiff(rightDir) \
                -statusvar [myvar statusVar]






        ttk::frame $win.fe1
        ttk::frame $win.fe2

        menu $win.m
        $hull configure -menu $win.m








>
>






<
<
<
<
<
<
<
<




>
>
>
>
>







1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269








1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
}

snit::widget DirDiff {
    hulltype toplevel
    widgetclass Toplevel
    component tree
    variable statusVar
    delegate option -norun to tree
    delegate option -bepa  to tree

    constructor {args} {
        eskilRegisterToplevel $win
        wm title $win "Eskil Dir"
        wm protocol $win WM_DELETE_WINDOW [list cleanupAndExit $win]









        install tree using DirCompareTree $win.dc \
                -leftdirvariable ::dirdiff(leftDir) \
                -rightdirvariable ::dirdiff(rightDir) \
                -statusvar [myvar statusVar]
	$self configurelist $args

        if {[info exists ::dirdiff(localChanges)]} {
            $tree configure -changelist $::dirdiff(localChanges)
        }

        ttk::frame $win.fe1
        ttk::frame $win.fe2

        menu $win.m
        $hull configure -menu $win.m

956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
















972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007

1008
1009
1010
1011
1012
1013
1014

1015
1016
1017

1018
1019
1020

1021
1022
1023

1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046

1047
1048
1049





1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061

        $win.m add cascade -menu $win.m.mo -label "Preferences" -underline 0
        menu $win.m.mo
        $win.m.mo add command -label "Prefs..." -command makeDirDiffPrefWin
        $win.m.mo add cascade -label "Check" -menu $win.m.mo.mc

        menu $win.m.mo.mc
        $win.m.mo.mc add radiobutton -variable Pref(dir,comparelevel) -value 0 \
                -label "Do not check contents"
        $win.m.mo.mc add radiobutton -variable Pref(dir,comparelevel) -value 1 \
                -label "Normal compare"
        $win.m.mo.mc add radiobutton -variable Pref(dir,comparelevel) -value 2 \
                -label "Exact compare"
        $win.m.mo.mc add checkbutton -variable Pref(dir,ignorekey) \
                -label "Ignore \$Keyword:\$"
        
















        $win.m add cascade -label "Tools" -underline 0 -menu $win.m.mt
        menu $win.m.mt
        $win.m.mt add command -label "New Diff Window" -underline 0 \
                -command makeDiffWin
        $win.m.mt add command -label "Clip Diff" -underline 0 \
                -command makeClipDiffWin
        if {$::tcl_platform(platform) eq "windows"} {
            if {![catch {package require registry}]} {
                $win.m.mt add separator
                $win.m.mt add command -label "Setup Registry" -underline 6 \
                        -command makeRegistryWin
            }
        }
        
        $win.m add cascade -label "Help" -underline 0 -menu $win.m.help
        menu $win.m.help
        $win.m.help add command -label "Tutorial" -command makeTutorialWin \
                -underline 0
        $win.m.help add command -label "About" -command makeAboutWin -underline 0
        
        if {$::eskil(debug)} {
            $win.m add cascade -label "Debug" -menu $win.m.md -underline 0
            menu $win.m.md
            if {$::tcl_platform(platform) eq "windows"} {
                $win.m.md add checkbutton -label "Console" -variable consolestate \
                        -onvalue show -offvalue hide -command {console $consolestate}
                $win.m.md add separator
            }
            $win.m.md add command -label "Reread Source" -underline 0 \
                    -command {EskilRereadSource}
            $win.m.md add separator
            $win.m.md add command -label "Redraw Window" -command {makeDirDiffWin 1}
        }
        
        ttk::button $win.bu -image $img(upup) -command [mymethod UpDir] \
                -underline 0

        bind $win <Alt-u> "$win.bu invoke"
        
        #catch {font delete myfont}
        #font create myfont -family $Pref(fontfamily) -size $Pref(fontsize)

        ttk::entryX $win.e1 -textvariable dirdiff(leftDir) -width 30
        ttk::button $win.bu1 -image $img(up) -command [mymethod UpDir 1]

        ttk::button $win.bb1 -image $img(open) \
                -command "[list BrowseDir dirdiff(leftDir) $win.e1]
                          [mymethod DoDirCompare]"

        after 50 [list after idle [list $win.e1 xview end]]
        ttk::entryX $win.e2 -textvariable dirdiff(rightDir) -width 30
        ttk::button $win.bu2 -image $img(up) -command [mymethod UpDir 2]

        ttk::button $win.bb2 -image $img(open) \
                -command "[list BrowseDir dirdiff(rightDir) $win.e2]
                          [mymethod DoDirCompare]"

        after 50 [list after idle [list $win.e2 xview end]]
        bind $win.e1 <Return> [mymethod DoDirCompare]
        bind $win.e2 <Return> [mymethod DoDirCompare]

        ttk::label $win.sl -anchor w -textvariable [myvar statusVar]
        
        pack $win.bb1 $win.bu1 -in $win.fe1 -side left -pady 1 -ipadx 10
        pack $win.bu1 -padx 6
        pack $win.e1 -in $win.fe1 -side left -fill x -expand 1
        pack $win.bb2 $win.bu2 -in $win.fe2 -side right -pady 1 -ipadx 10
        pack $win.bu2 -padx 6
        pack $win.e2 -in $win.fe2 -side left -fill x -expand 1
        
        grid $win.fe1  $win.bu $win.fe2  -sticky we
        grid $tree     -       -         -sticky news
        grid $win.sl   -       -         -sticky we
        grid $win.bu -padx 6 -ipadx 15

        grid rowconfigure    $win  1    -weight 1
        grid columnconfigure $win {0 2} -weight 1
    }

    method DoDirCompare {} {

        $tree configure -leftdirvariable ::dirdiff(leftDir) \
                -rightdirvariable ::dirdiff(rightDir)
    }






    # Go up one level in directory hierarchy.
    # 0 = both
    method UpDir {{n 0}} {
        global dirdiff Pref
        switch $n {
            0 {
                set dirdiff(leftDir) [file dirname $dirdiff(leftDir)]
                set dirdiff(rightDir) [file dirname $dirdiff(rightDir)]
                $win.e1 xview end
                $win.e2 xview end
            }







|

|

|

|

|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







|





|





|











|

|
|

>

|

|


|
>
|
|

>


|
>
|
|

>





|






|










>



>
>
>
>
>




|







1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429

        $win.m add cascade -menu $win.m.mo -label "Preferences" -underline 0
        menu $win.m.mo
        $win.m.mo add command -label "Prefs..." -command makeDirDiffPrefWin
        $win.m.mo add cascade -label "Check" -menu $win.m.mo.mc

        menu $win.m.mo.mc
        $win.m.mo.mc add radiobutton -variable ::Pref(dir,comparelevel) -value 0 \
                -label "Do not check contents"
        $win.m.mo.mc add radiobutton -variable ::Pref(dir,comparelevel) -value 1 \
                -label "Normal compare"
        $win.m.mo.mc add radiobutton -variable ::Pref(dir,comparelevel) -value 2 \
                -label "Exact compare"
        $win.m.mo.mc add checkbutton -variable ::Pref(dir,ignorekey) \
                -label "Ignore \$Keyword:\$"

        $win.m.mo add command -label "Plugins..." -underline 1 \
                -command [list editPrefPlugins $win 1]
        $win.m.mo add cascade -label "Nice" -menu $win.m.mo.mn
        menu $win.m.mo.mn
        $win.m.mo.mn add radiobutton -variable ::Pref(dir,nice) -value 1 \
                -command [mymethod DoNice] -label 1
        $win.m.mo.mn add radiobutton -variable ::Pref(dir,nice) -value 50 \
                -command [mymethod DoNice] -label 50
        $win.m.mo.mn add radiobutton -variable ::Pref(dir,nice) -value 100 \
                -command [mymethod DoNice] -label 100
        $win.m.mo.mn add radiobutton -variable ::Pref(dir,nice) -value 1000 \
                -command [mymethod DoNice] -label 1000
        $win.m.mo add separator
        $win.m.mo add command -label "Save default" \
                -command [list saveOptions $win]

        $win.m add cascade -label "Tools" -underline 0 -menu $win.m.mt
        menu $win.m.mt
        $win.m.mt add command -label "New Diff Window" -underline 0 \
                -command makeDiffWin
        $win.m.mt add command -label "Clip Diff" -underline 0 \
                -command makeClipDiffWin
        if {$::tcl_platform(platform) eq "windows"} {
            if { ! [catch {package require registry}]} {
                $win.m.mt add separator
                $win.m.mt add command -label "Setup Registry" -underline 6 \
                        -command makeRegistryWin
            }
        }

        $win.m add cascade -label "Help" -underline 0 -menu $win.m.help
        menu $win.m.help
        $win.m.help add command -label "Tutorial" -command makeTutorialWin \
                -underline 0
        $win.m.help add command -label "About" -command makeAboutWin -underline 0

        if {$::eskil(debug)} {
            $win.m add cascade -label "Debug" -menu $win.m.md -underline 0
            menu $win.m.md
            if {$::tcl_platform(platform) eq "windows"} {
                $win.m.md add checkbutton -label "Console" -variable consolestate \
                        -onvalue show -offvalue hide -command {console $consolestate}
                $win.m.md add separator
            }
            $win.m.md add command -label "Reread Source" -underline 0 \
                    -command {EskilRereadSource}
            $win.m.md add separator
            $win.m.md add command -label "Redraw Window" -command {makeDirDiffWin}
        }

        ttk::button $win.bu -image $::img(upup) -command [mymethod UpDir] \
                -underline 0
        addBalloon $win.bu "Up in both."
        bind $win <Alt-u> "$win.bu invoke"

        #catch {font delete myfont}
        #font create myfont -family $::Pref(fontfamily) -size $::Pref(fontsize)

        ttk::entryX $win.e1 -textvariable dirdiff(leftDir) -width 30
        ttk::button $win.bu1 -image $::img(up) -command [mymethod UpDir 1]
        addBalloon $win.bu1 "Up in left."
        ttk::button $win.bb1 -image $::img(browse) \
                -command "[list BrowseDir "dirdiff(leftDir)" $win.e1]
                          [mymethod DoDirCompare]"
        addBalloon $win.bb1 "Browse left."
        after 50 [list after idle [list $win.e1 xview end]]
        ttk::entryX $win.e2 -textvariable dirdiff(rightDir) -width 30
        ttk::button $win.bu2 -image $::img(up) -command [mymethod UpDir 2]
        addBalloon $win.bu2 "Up in right."
        ttk::button $win.bb2 -image $::img(browse) \
                -command "[list BrowseDir "dirdiff(rightDir)" $win.e2]
                          [mymethod DoDirCompare]"
        addBalloon $win.bb2 "Browse right."
        after 50 [list after idle [list $win.e2 xview end]]
        bind $win.e1 <Return> [mymethod DoDirCompare]
        bind $win.e2 <Return> [mymethod DoDirCompare]

        ttk::label $win.sl -anchor w -textvariable [myvar statusVar]

        pack $win.bb1 $win.bu1 -in $win.fe1 -side left -pady 1 -ipadx 10
        pack $win.bu1 -padx 6
        pack $win.e1 -in $win.fe1 -side left -fill x -expand 1
        pack $win.bb2 $win.bu2 -in $win.fe2 -side right -pady 1 -ipadx 10
        pack $win.bu2 -padx 6
        pack $win.e2 -in $win.fe2 -side left -fill x -expand 1

        grid $win.fe1  $win.bu $win.fe2  -sticky we
        grid $tree     -       -         -sticky news
        grid $win.sl   -       -         -sticky we
        grid $win.bu -padx 6 -ipadx 15

        grid rowconfigure    $win  1    -weight 1
        grid columnconfigure $win {0 2} -weight 1
    }

    method DoDirCompare {} {
        # Reconfiguring the dirdiff widget triggers a rerun
        $tree configure -leftdirvariable ::dirdiff(leftDir) \
                -rightdirvariable ::dirdiff(rightDir)
    }

    method DoNice {} {
        ##nagelfar vartype tree _obj,tablelist
        $tree nice $::Pref(dir,nice)
    }

    # Go up one level in directory hierarchy.
    # 0 = both
    method UpDir {{n 0}} {
        global dirdiff
        switch $n {
            0 {
                set dirdiff(leftDir) [file dirname $dirdiff(leftDir)]
                set dirdiff(rightDir) [file dirname $dirdiff(rightDir)]
                $win.e1 xview end
                $win.e2 xview end
            }
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
            set ::TmpPref($item) $::Pref($item)
        }
    }

    wm title $top "Eskil Directory Preferences"

    set check [ttk::labelframe $top.check -text "Check" -padding 3]
    ttk::radiobutton $check.rb1 -variable TmpPref(dir,comparelevel) -value 0 \
            -text "Do not check contents"
    ttk::radiobutton $check.rb2 -variable TmpPref(dir,comparelevel) -value 1 \
            -text "Normal compare"
    ttk::radiobutton $check.rb3 -variable TmpPref(dir,comparelevel) -value 2 \
            -text "Exact compare"
    grid $check.rb1 -sticky w -padx 3 -pady 3
    grid $check.rb2 -sticky w -padx 3 -pady 3
    grid $check.rb3 -sticky w -padx 3 -pady 3
    grid columnconfigure $check {0 1 2} -uniform a -weight 1

    set opts [ttk::labelframe $top.opts -text "Options" -padding 3]
    ttk::checkbutton $opts.cb1 -variable TmpPref(dir,ignorekey) \
            -text "Ignore \$Keyword:\$"
    pack {*}[winfo children $opts] -side top -anchor w 

    set filter [ttk::labelframe $top.filter -text "Filter" -padding 3]
    ttk::label $filter.l1 -text "Include Files" -anchor w
    ttk::entryX $filter.e1 -width 20 -textvariable TmpPref(dir,incfiles)
    ttk::label $filter.l2 -text "Exclude Files" -anchor w
    ttk::entryX $filter.e2 -width 20 -textvariable TmpPref(dir,exfiles)
    ttk::label $filter.l3 -text "Include Dirs" -anchor w
    ttk::entryX $filter.e3 -width 20 -textvariable TmpPref(dir,incdirs)
    ttk::label $filter.l4 -text "Exclude Dirs" -anchor w
    ttk::entryX $filter.e4 -width 20 -textvariable TmpPref(dir,exdirs)
    ttk::checkbutton $filter.cb1 -text "Only revision controlled" \
            -variable TmpPref(dir,onlyrev)
    grid $filter.l1 $filter.e1 -sticky we -padx 3 -pady 3
    grid $filter.l2 $filter.e2 -sticky we -padx 3 -pady 3
    grid $filter.l3 $filter.e3 -sticky we -padx 3 -pady 3
    grid $filter.l4 $filter.e4 -sticky we -padx 3 -pady 3
    grid $filter.cb1 - -sticky w -padx 3 -pady 3
    grid columnconfigure $filter 1 -weight 1








|

|

|







|

|



|

|

|

|

|







1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
            set ::TmpPref($item) $::Pref($item)
        }
    }

    wm title $top "Eskil Directory Preferences"

    set check [ttk::labelframe $top.check -text "Check" -padding 3]
    ttk::radiobutton $check.rb1 -variable ::TmpPref(dir,comparelevel) -value 0 \
            -text "Do not check contents"
    ttk::radiobutton $check.rb2 -variable ::TmpPref(dir,comparelevel) -value 1 \
            -text "Normal compare"
    ttk::radiobutton $check.rb3 -variable ::TmpPref(dir,comparelevel) -value 2 \
            -text "Exact compare"
    grid $check.rb1 -sticky w -padx 3 -pady 3
    grid $check.rb2 -sticky w -padx 3 -pady 3
    grid $check.rb3 -sticky w -padx 3 -pady 3
    grid columnconfigure $check {0 1 2} -uniform a -weight 1

    set opts [ttk::labelframe $top.opts -text "Options" -padding 3]
    ttk::checkbutton $opts.cb1 -variable ::TmpPref(dir,ignorekey) \
            -text "Ignore \$Keyword:\$"
    pack {*}[winfo children $opts] -side "top" -anchor w

    set filter [ttk::labelframe $top.filter -text "Filter" -padding 3]
    ttk::label $filter.l1 -text "Include Files" -anchor w
    ttk::entryX $filter.e1 -width 20 -textvariable ::TmpPref(dir,incfiles)
    ttk::label $filter.l2 -text "Exclude Files" -anchor w
    ttk::entryX $filter.e2 -width 20 -textvariable ::TmpPref(dir,exfiles)
    ttk::label $filter.l3 -text "Include Dirs" -anchor w
    ttk::entryX $filter.e3 -width 20 -textvariable ::TmpPref(dir,incdirs)
    ttk::label $filter.l4 -text "Exclude Dirs" -anchor w
    ttk::entryX $filter.e4 -width 20 -textvariable ::TmpPref(dir,exdirs)
    ttk::checkbutton $filter.cb1 -text "Only revision controlled" \
            -variable ::TmpPref(dir,onlyrev)
    grid $filter.l1 $filter.e1 -sticky we -padx 3 -pady 3
    grid $filter.l2 $filter.e2 -sticky we -padx 3 -pady 3
    grid $filter.l3 $filter.e3 -sticky we -padx 3 -pady 3
    grid $filter.l4 $filter.e4 -sticky we -padx 3 -pady 3
    grid $filter.cb1 - -sticky w -padx 3 -pady 3
    grid columnconfigure $filter 1 -weight 1

1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206

































































1207

1208
1209
1210
    wm title $top "Eskil Dir Preprocess"

    ttk::entryX $top.e1 -textvariable ::dirdiff(pattern) -width 15
    ttk::entryX $top.e2 -textvariable ::dirdiff(replace) -width 15

    ttk::label $top.l1 -text "Pattern" -anchor w
    ttk::label $top.l2 -text "Subst"   -anchor w
    
    grid $top.l1 $top.e1 -sticky we
    grid $top.l2 $top.e2 -sticky we
    grid columnconfigure $top 1 -weight 1
    grid rowconfigure    $top 2 -weight 1
    
}

proc makeDirDiffWin {{redraw 0}} {
    if {![info exists ::dirdiff(leftDir)]} {
        set ::dirdiff(leftDir) ""
    }
    if {![info exists ::dirdiff(rightDir)]} {
        set ::dirdiff(rightDir) ""
    }

































































    destroy .dirdiff

    DirDiff .dirdiff
    return .dirdiff
}







|




|


|
|


|


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>
|


1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
    wm title $top "Eskil Dir Preprocess"

    ttk::entryX $top.e1 -textvariable ::dirdiff(pattern) -width 15
    ttk::entryX $top.e2 -textvariable ::dirdiff(replace) -width 15

    ttk::label $top.l1 -text "Pattern" -anchor w
    ttk::label $top.l2 -text "Subst"   -anchor w

    grid $top.l1 $top.e1 -sticky we
    grid $top.l2 $top.e2 -sticky we
    grid columnconfigure $top 1 -weight 1
    grid rowconfigure    $top 2 -weight 1

}

proc makeDirDiffWin {{noautodiff 0}} {
    if { ! [info exists ::dirdiff(leftDir)]} {
        set ::dirdiff(leftDir) ""
    }
    if { ! [info exists ::dirdiff(rightDir)]} {
        set ::dirdiff(rightDir) ""
    }
    # TODO, multi plugin for dirdiff?
    set ::eskil(.dirdiff,plugin,1) ""
    foreach {item val} $::eskil(defaultopts) {
        set ::eskil(.dirdiff,$item) $val
    }

    # Support -r for directory diff
    set revs {}
    array set opts $::eskil(defaultopts)
    if {[info exists opts(doptrev1)] && $opts(doptrev1) ne ""} {
        lappend revs $opts(doptrev1)
    }
    if {[info exists opts(doptrev2)] && $opts(doptrev2) ne ""} {
        lappend revs $opts(doptrev2)
    }

    # TODO: Trigger this on DirDiff, so a rerun can do it, and maybe have rev
    # GUI fields
    if {$::dirdiff(leftDir) eq $::dirdiff(rightDir) &&
        $::dirdiff(leftDir) ne "" && ![string match *.kit $::dirdiff(leftDir)]} {
        set fullname $::dirdiff(leftDir)
        set type [detectRevSystem $fullname]
        # Is this a revision system with dirdiff support?
        if {[info commands eskil::rev::${type}::mount] ne ""} {
            # No -r given; fall back on current.
            if {[llength $revs] == 0} {
                # Optimisation attempt for checkout vs latest, see if there
                # is a command to detect local changes
                if {[info commands eskil::rev::${type}::localChanges] ne ""} {
                    set ::dirdiff(localChanges) \
                            [eskil::rev::${type}::localChanges $fullname]
                }
                # Any vcs with dirdiff support should know that _ means current
                set revs _
            }
            set revs [eskil::rev::${type}::ParseRevs $fullname $revs]
            set rev1 [lindex $revs 0]
            set rev2 [lindex $revs 1]

            # A little "splash-screen" to show progress
            destroy .dirdiffX
            toplevel .dirdiffX
            wm title .dirdiffX "Eskil Dir Diff"
            label .dirdiffX.l1 -text "Collecting $type info for rev $rev1..."
            label .dirdiffX.l2 -text ""
            pack .dirdiffX.l1 .dirdiffX.l2 -side top -fill x
            update

            if {[catch {eskil::rev::${type}::mount $fullname $rev1} d1]} {
                destroy .dirdiffX
                tk_messageBox -icon error -message $d1 -type ok
                # Can ony reach this from command line, so safe to exit
                exit
            }
            set ::dirdiff(leftDir) $d1
            if {$rev2 ne ""} {
                .dirdiffX.l2 configure -text "and rev $rev2..."
                update
                set d2 [eskil::rev::${type}::mount $fullname $rev2]
                set ::dirdiff(rightDir) $d2
            }
            destroy .dirdiffX
        }
    }

    destroy .dirdiff
    # TODO: better name for experimental parameter, propagate to cmd line
    DirDiff .dirdiff -norun $noautodiff -bepa 1
    return .dirdiff
}

Changes to src/eskil.syntax.

1
2




3
4
5


6


7
8
9
10
11



12
13
14
15
16
17
18
19







20











21
22






23
24
25
26
27
28



29













30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

90
91
92
93
94
95
96
97
98

99
100
101
102
103
104
105
106
107

108
109
110
111

112
113
114
115











##nagelfar syntax textSearch::searchMenu x
##nagelfar syntax textSearch::enableSearch x x*




##nagelfar syntax DiffUtil::LocateDiffExe x
###nagelfar syntax DiffUtil::diffStrings o* x x
##nagelfar syntax dde s x


##nagelfar syntax dnd s x*


##nagelfar syntax safeLoad x n
##nagelfar syntax helpWin x x
##nagelfar syntax commonYScroll x x*
##nagelfar syntax locateEditor n
##nagelfar syntax locateTmp n



##nagelfar syntax wcb::cancel 0
##nagelfar syntax wcb::callback 4
##nagelfar syntax vfs::mk4::Mount r 2
##nagelfar syntax vfs::unmount 1
###nagelfar syntax ttk::entryX x p*
##nagelfar syntax ::tk::GetSelection x x
##nagelfar syntax ::tk::ScrollButton2Down x x x
##nagelfar syntax console x







##nagelfar syntax registry x x x











##nagelfar syntax pdf4tcl::getPaperSize x
##nagelfar syntax pdf4tcl::getPaperSizeList






##nagelfar syntax twapi::get_foreground_window
##nagelfar syntax twapi::get_window_coordinates x
##nagelfar syntax twapi::get_window_at_location x x
##nagelfar syntax twapi::set_focus x
##nagelfar syntax twapi::send_keys x
##nagelfar syntax twapi::get_window_coordinates x



##nagelfar syntax tablelist::convEventFields x x x














# Operators
##nagelfar syntax + x*
##nagelfar syntax - x x*
##nagelfar syntax * x*
##nagelfar syntax / x x*

# This is the generic definitions needed for Snit.

##nagelfar syntax _stdclass_snit s x*
##nagelfar subcmd _stdclass_snit destroy configurelist configure
##nagelfar syntax _stdclass_snit\ destroy 0
##nagelfar syntax _stdclass_snit\ configurelist x
##nagelfar syntax _stdclass_snit\ configure x*

##nagelfar syntax snit::type do=_stdclass_snit cn
##nagelfar syntax snit::type::method dm
##nagelfar syntax snit::type::constructor cv
##nagelfar syntax snit::type::destructor cl
##nagelfar syntax snit::type::option x p*
##nagelfar syntax snit::type::component x
##nagelfar syntax snit::type::delegate x*
##nagelfar syntax snit::type::install s x*

##nagelfar syntax snit::widgetadaptor do=_stdclass_snit cn
##nagelfar syntax snit::widgetadaptor::method dm
##nagelfar syntax snit::widgetadaptor::constructor cv
##nagelfar syntax snit::widgetadaptor::destructor cl
##nagelfar syntax snit::widgetadaptor::delegate x*
##nagelfar syntax snit::widgetadaptor::installhull x*
##nagelfar syntax snit::widgetadaptor::from l x*
##nagelfar syntax snit::widgetadaptor::component x
##nagelfar syntax snit::widgetadaptor::install s x*
##nagelfar syntax snit::widgetadaptor::option x p*

##nagelfar syntax snit::widget do=_stdclass_snit cn
##nagelfar syntax snit::widget::method dm
##nagelfar syntax snit::widget::constructor cv
##nagelfar syntax snit::widget::destructor cl
##nagelfar syntax snit::widget::delegate x*
##nagelfar syntax snit::widget::installhull x*
##nagelfar syntax snit::widget::from l x*
##nagelfar syntax snit::widget::hulltype x
##nagelfar syntax snit::widget::widgetclass x
##nagelfar syntax snit::widget::myvar l
##nagelfar syntax snit::widget::mymethod x x*
##nagelfar return snit::widget::myvar varName
##nagelfar syntax snit::widget::component x
##nagelfar syntax snit::widget::install s x*
##nagelfar syntax snit::widget::option x p*

# This is the annotation needed for this object definition

##nagelfar syntax eskilprint dc=_obj,eskilprint p*
##nagelfar option eskilprint -file -cpl -cpln -headsize -headleft -headright -headnpages -margin -paper
##nagelfar return eskilprint _obj,eskilprint
##nagelfar subcmd+ _obj,eskilprint text newLine

##nagelfar implicitvar snit::type::eskilprint self\ _obj,eskilprint width height pdf hoy fontsize linesize nlines ox1 ox2 oy page options


# This is the annotation needed for this object definition

##nagelfar syntax DirDiff dc=_obj,DirDiff p*
###nagelfar option DirDiff
##nagelfar return DirDiff _obj,DirDiff
##nagelfar subcmd+ _obj,DirDiff text newLine

##nagelfar implicitvar snit::widget::DirDiff self\ _obj,DirDiff statusVar hull win self tree


# This is the annotation needed for this object definition

##nagelfar syntax DirCompareTree dc=_obj,DirCompareTree p*
##nagelfar option DirCompareTree -leftdirvariable -rightdirvariable -statusvar
##nagelfar return DirCompareTree _obj,DirCompareTree
##nagelfar subcmd+ _obj,DirCompareTree text newLine

##nagelfar implicitvar snit::widget::DirCompareTree self\ _obj,DirCompareTree hull win self tree hsb vsb options AfterId PauseBgProcessing IdleQueue IdleQueueArr leftMark rightMark leftDir rightDir ScheduledRestart img


# This is the annotation needed for this object definition

##nagelfar syntax ttk::entryX dc=_obj,ttk::entryX p*
##nagelfar option ttk::entryX -width -textvariable -style

##nagelfar return ttk::entryX _obj,ttk::entryX
##nagelfar subcmd+ _obj,ttk::entryX text newLine

##nagelfar implicitvar snit::widgetadaptor::ttk::entryX self\ _obj,ttk::entryX hull win self  options












|
>
>
>
>

|
|
>
>
|
>
>





>
>
>


<
|
|



>
>
>
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>


>
>
>
>
>
>






>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>







<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<



|



|

>







|

>







|

>


|

>
|
|

|
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

87










































88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
##nagelfar syntax textSearch::searchMenu x
##nagelfar syntax textSearch::enableSearch x p*
##nagelfar option textSearch::enableSearch -label
##nagelfar option textSearch::enableSearch\ -label n
##nagelfar package known textSearch

##nagelfar syntax DiffUtil::LocateDiffExe x
##nagelfar syntax DiffUtil::diffStrings o* x x
##nagelfar syntax DiffUtil::diffFiles o* x x
##nagelfar package known DiffUtil

##nagelfar syntax dde s x
##nagelfar package known dde

##nagelfar syntax safeLoad x n
##nagelfar syntax helpWin x x
##nagelfar syntax commonYScroll x x*
##nagelfar syntax locateEditor n
##nagelfar syntax locateTmp n
##nagelfar package known pstools
##nagelfar package known psballoon

##nagelfar syntax wcb::cancel 0
##nagelfar syntax wcb::callback 4

##nagelfar package known wcb

##nagelfar syntax ::tk::GetSelection x x
##nagelfar syntax ::tk::ScrollButton2Down x x x
##nagelfar syntax console x
##nagelfar syntax ::tk::dialog::file:: x*

##nagelfar syntax fileLabel x p*
##nagelfar option fileLabel -textvariable
##nagelfar option fileLabel\ -textvariable n
##nagelfar syntax createPluginInterp x x x n

##nagelfar syntax registry x x x
##nagelfar package known registry

##nagelfar syntax vfs::filesystem s x*
##nagelfar subcmd vfs::filesystem mount posixerror
##nagelfar syntax vfs::filesystem\ mount x x
##nagelfar syntax vfs::filesystem\ posixerror x
##nagelfar syntax vfs::matchDirectories x
##nagelfar syntax vfs::matchFiles x
##nagelfar syntax vfs::accessMode x
##nagelfar package known vfs

##nagelfar syntax pdf4tcl::getPaperSize x
##nagelfar syntax pdf4tcl::getPaperSizeList
##nagelfar syntax pdf4tcl::loadBaseType1Font 3
##nagelfar syntax pdf4tcl::loadBaseTrueTypeFont r 2 3
##nagelfar syntax pdf4tcl::createFont 3
##nagelfar syntax _obj,pdf4tcl s x*
##nagelfar package known pdf4tcl

##nagelfar syntax twapi::get_foreground_window
##nagelfar syntax twapi::get_window_coordinates x
##nagelfar syntax twapi::get_window_at_location x x
##nagelfar syntax twapi::set_focus x
##nagelfar syntax twapi::send_keys x
##nagelfar syntax twapi::get_window_coordinates x
##nagelfar package known twapi

##nagelfar syntax tablelist::tablelist x p*
##nagelfar syntax tablelist::convEventFields x x x
##nagelfar syntax tablelist::synchronize x
##nagelfar syntax tablelist::updateKeyToRowMap x
##nagelfar syntax tablelist::displayItems x
##nagelfar syntax tablelist::cellIndex x x x
##nagelfar syntax tablelist::findTabs x x x x n n
##nagelfar syntax mwutil::wrongNumArgs x
##nagelfar syntax _obj,tablelist s x*
##nagelfar subcmd _obj,tablelist parent rowattrib nice
##nagelfar package known tablelist_tile

##nagelfar syntax MySpinBox x p*
##nagelfar option MySpinBox -textvariable -from -to -increment -width -format
##nagelfar option MySpinBox\ -textvariable n

# Operators
##nagelfar syntax + x*
##nagelfar syntax - x x*
##nagelfar syntax * x*
##nagelfar syntax / x x*


##########################################################










































# This is the annotation needed for this object definition

##nagelfar syntax eskilprint dc=_obj,eskilprint p*
##nagelfar option eskilprint -file -cpl -cpln -headsize -headleft -headright -headnpages -margin -paper -lnsp
##nagelfar return eskilprint _obj,eskilprint
##nagelfar subcmd+ _obj,eskilprint text newLine

##nagelfar implicitvarns snit::type::eskilprint self\ _obj,eskilprint width height pdf hoy fontsize linesize nlines ox1 ox2 oy page options

##########################################################
# This is the annotation needed for this object definition

##nagelfar syntax DirDiff dc=_obj,DirDiff p*
###nagelfar option DirDiff
##nagelfar return DirDiff _obj,DirDiff
##nagelfar subcmd+ _obj,DirDiff text newLine

##nagelfar implicitvarns snit::widget::DirDiff self\ _obj,DirDiff statusVar hull win self tree nice

##########################################################
# This is the annotation needed for this object definition

##nagelfar syntax DirCompareTree dc=_obj,DirCompareTree p*
##nagelfar option DirCompareTree -leftdirvariable -rightdirvariable -statusvar
##nagelfar return DirCompareTree _obj,DirCompareTree
##nagelfar subcmd+ _obj,DirCompareTree text newLine

##nagelfar implicitvarns snit::widget::DirCompareTree self\ _obj,DirCompareTree hull win self tree hsb vsb options AfterId PauseBgProcessing IdleQueue IdleQueueArr leftMark rightMark leftDir rightDir protect ScheduledRestart img AfterTime DebugCh DebugTime NodeStatus WorkTime

##########################################################
# This is the annotation needed for this object definition

##nagelfar syntax ttk::entryX dc=_obj,entryX p*
##nagelfar option ttk::entryX -width -textvariable -style
##nagelfar option ttk::entryX\ -textvariable n
##nagelfar return ttk::entryX _obj,entryX
##nagelfar subcmd+ _obj,entryX text newLine

##nagelfar implicitvarns snit::widgetadaptor::ttk::entryX self\ _obj,entryX hull win self  options

##########################################################
# This is the annotation needed for this object definition

##nagelfar syntax FourWay dc=_obj,FourWay p*
###nagelfar option FourWay
##nagelfar return FourWay _obj,FourWay
###nagelfar subcmd+ _obj,FourWay text newLine

##nagelfar implicitvarns snit::widget::FourWay self\ _obj,FourWay fields files filesGui revs revsGui origfiles origrevs revtype doingLine1 doingLine2 win hull

Changes to src/eskil.tcl.

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh
#---------------------------------------------------------- -*- tcl -*-
#
#  Eskil, a Graphical frontend to diff
#
#  Copyright (c) 1998-2011, Peter Spjuth  (peter.spjuth@gmail.com)
#
#  Usage
#             Do 'eskil' for interactive mode
#             Do 'eskil --help' for command line usage
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
<




|








1
2
3
4
5
6
7
8
9
10
11
12

#---------------------------------------------------------- -*- tcl -*-
#
#  Eskil, a Graphical frontend to diff
#
#  Copyright (c) 1998-2015, Peter Spjuth  (peter.spjuth@gmail.com)
#
#  Usage
#             Do 'eskil' for interactive mode
#             Do 'eskil --help' for command line usage
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282























283
284




285
286
287
288
289
290
291
292
293
294
295
296




297
298
299
300
301
302

















































































































































303
304
305
306
307
308
309





310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326




327

328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
#
#  You should have received a copy of the GNU General Public License
#  along with this program; see the file COPYING.  If not, write to
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------
# the next line restarts using tclsh \
exec tclsh "$0" "$@"

package require Tcl 8.5

# Stop Tk from meddling with the command line by copying it first.
set ::eskil(argv) $::argv
set ::eskil(argc) $::argc
set ::argv {}
set ::argc 0

set ::eskil(debug) 0
set ::eskil(diffver) "Version 2.5+ 2011-05-06"
set ::eskil(thisScript) [file join [pwd] [info script]]

namespace import tcl::mathop::+
namespace import tcl::mathop::-
namespace import tcl::mathop::*
namespace import tcl::mathop::/

# Do initalisations for needed packages and globals.
# This is not run until needed to speed up command line error reporting.
proc Init {} {
    package require Tk 8.4
    catch {package require textSearch}
    package require wcb
    package require snit

    if {[catch {package require psballoon}]} {
        # Add a dummy if it does not exist.
        proc addBalloon {args} {}
    } else {
        namespace import -force psballoon::addBalloon
    }

    set ::eskil(thisDir) [file dirname $::eskil(thisScript)]

    # Follow any link
    set tmplink $::eskil(thisScript)
    while {[file type $tmplink] eq "link"} {
        set tmplink [file readlink $tmplink]
        set tmplink [file normalize [file join $::eskil(thisDir) $tmplink]]
        set ::eskil(thisDir) [file dirname $tmplink]
    }

    # Get all other source files
    source $::eskil(thisDir)/clip.tcl
    source $::eskil(thisDir)/compare.tcl
    source $::eskil(thisDir)/map.tcl
    source $::eskil(thisDir)/merge.tcl
    source $::eskil(thisDir)/registry.tcl
    source $::eskil(thisDir)/dirdiff.tcl
    source $::eskil(thisDir)/help.tcl
    source $::eskil(thisDir)/plugin.tcl
    source $::eskil(thisDir)/printobj.tcl
    source $::eskil(thisDir)/print.tcl
    source $::eskil(thisDir)/rev.tcl

    set ::util(diffexe) diff

    # Diff functionality is in the DiffUtil package.
    package require DiffUtil
    # Help DiffUtil to find a diff executable, if needed
    catch {DiffUtil::LocateDiffExe $::eskil(thisScript)}

    # Figure out a place to store temporary files.
    locateTmp ::diff(tmpdir)

    if {$::tcl_platform(platform) eq "windows"} {
        # Locate CVS if it is in c:/bin
        if {[auto_execok cvs] eq "" && [file exists "c:/bin/cvs.exe"]} {
            set ::env(PATH) "$::env(PATH);c:\\bin"
            auto_reset
        }
    }
    defaultGuiOptions
    if {0 && [bind all <Alt-KeyPress>] eq ""} {
        bind all <Alt-KeyPress> [bind Menubutton <Alt-KeyPress>]
        #after 500 "tk_messageBox -message Miffo"
    }
    wm withdraw .

    if {[catch {package require Ttk}]} {
        if {[catch {package require tile}]} {
            if {[info exists ::eskil_testsuite]} {
                return
            } else {
                puts "Themed Tk not found"
                exit
            }
        }
    }
    # Reportedly, the ttk scrollbar looks bad on Aqua
    if {[tk windowingsystem] ne "aqua"} {
        interp alias {} scrollbar {} ttk::scrollbar
    }
    # Provide a ttk-friendly toplevel, fixing background and menubar
    if {[info commands ttk::toplevel] eq ""} {
        proc ttk::toplevel {w args} {
            tk::toplevel $w {*}$args
            place [ttk::frame $w.tilebg] -x 0 -y 0 -relwidth 1 -relheight 1
            # Menubar looks out of place on linux. This adjusts the background
            # Which is enough to make it reasonable.
            set bg [ttk::style configure . -background]
            option add *Menubutton.background $bg
            option add *Menu.background $bg
            return $w
        }
    }

    ::snit::widgetadaptor ttk::entryX {
        delegate method * to hull
        delegate option * to hull

        constructor {args} {
            installhull using ttk::entry
            $self configurelist $args
            # Make sure textvariable is initialised
            set varName [from args -textvariable ""]
            if {$varName ne ""} {
                upvar \#0 $varName var
                if {![info exists var]} {
                    set var ""
                }
            }
        }
        # Circumvent a bug in ttk::entry that "xview end" does not work.
        method xview {args} {
            if {[llength $args] == 1} {
                set ix [lindex $args 0]
                $hull xview [$hull index $ix]
            } else {
                $hull xview {*}$args
            }
        }
    }

    interp alias {} toplevel {} ttk::toplevel
}

# Debug function to be able to reread the source even when wrapped in a kit.
proc EskilRereadSource {} {
    set this $::eskil(thisScript)

    # FIXA: Better detection of starkit?
    # Maybe look at ::starkit::topdir ?

    #if {[info exists ::starkit::topdir]} {
    #    puts "Topdir: $::starkit::topdir"
    #}

    # Are we in a Starkit?
    if {[regexp {^(.*eskil)((?:\.[^/]+)?)(/src/.*)$} $this -> \
            pre ext post]} {
        if {$ext ne ".vfs"} {
            # If the unpacked vfs directory is available, read from that
            # instead.
            set src $pre.vfs$post
            if {[file readable $src]} {
                set this $src
            }
        }
    }
    puts "Resourcing $this"
    uplevel \#0 [list source $this]
}

# This function is called when a toplevel is closed.
# If it is the last remaining toplevel, the application quits.
# If top = "all" it means quit.
# If eskil is embedded, this should be used to close an eskil toplevel.
proc cleanupAndExit {top} {
    # A security thing to make sure we can exit.
    set cont 0
    if {[catch {
        if {$top != "all"} {
            set i [lsearch $::diff(diffWindows) $top]
            if {$i >= 0} {
                set ::diff(diffWindows) [lreplace $::diff(diffWindows) $i $i]
            }
            set i [lsearch $::widgets(toolbars) $top.f]
            if {$i >= 0} {
                set ::widgets(toolbars) [lreplace $::widgets(toolbars) $i $i]
            }

            destroy $top
            array unset ::diff $top,*

            # Any windows remaining?
            if {[llength $::diff(diffWindows)] > 0} {
                set cont 1
            }
        }
    } errMsg]} {
        tk_messageBox -icon error -title "Eskil Error" -message \
                "An error occured in the close process.\n$errMsg\n\
                (This is a bug)\nTerminating application." -type ok
    }
    if {$cont} return

    clearTmp
    exit
}

# If embedding, tell eskil about any other toplevel, then
# cleanupAndExit can be used to get rid of it.
proc eskilRegisterToplevel {top} {
    lappend ::diff(diffWindows) $top
}

# Format a line number
proc myFormL {lineNo} {
    if {![string is integer -strict $lineNo]} {return "$lineNo\n"}
      return [format "%3d: \n" $lineNo]
}

# Get a name for a temporary file
# A tail can be given to make the file more recognisable.
proc tmpFile {{tail {}}} {
    if {[info exists ::tmpcnt]} {
        incr ::tmpcnt
    } else {
        set ::tmpcnt 0
    }
    set name "tmpd[pid]a$::tmpcnt"
    if {$tail ne ""} {
        append name " [file tail $tail]"
    }
    set name [file join $::diff(tmpdir) $name]
    lappend ::tmpfiles $name
    return $name
}

# Delete temporary files
proc clearTmp {args} {
    if {![info exists ::tmpfiles]} {
        set ::tmpfiles {}
        return
    }
    if {[llength $args] > 0} {
        foreach f $args {
            set i [lsearch -exact $::tmpfiles $f]
            if {$i >= 0} {
                catch {file delete $f}
                set ::tmpfiles [lreplace $::tmpfiles $i $i]
            }
        }
    } else {
        foreach f $::tmpfiles {
            catch {file delete $f}
        }
        set ::tmpfiles {}
    }
}
























# Insert lineno and text
proc insertLine {top n line text {tag {equal}} {linetag {}}} {




    $::widgets($top,wDiff$n) insert end "$text\n" $tag
    if {$linetag ne ""} {
        append tag " $linetag"
    }
    if {$tag != "equal"} {
        set tag "hl$::HighLightCount $tag"
    }
    $::widgets($top,wLine$n) insert end [myFormL $line] $tag
}

# Insert an empty line on one side of the diff.
proc emptyLine {top n {highlight 1}} {




    if {$highlight} {
        $::widgets($top,wLine$n) insert end "\n" hl$::HighLightCount
    } else {
        $::widgets($top,wLine$n) insert end "*****\n"
    }
    $::widgets($top,wDiff$n) insert end "\n" padding

















































































































































}

# Insert one line in each text widget.
# Mark them as changed, and optionally parse them.
proc insertMatchingLines {top line1 line2} {
    global doingLine1 doingLine2 Pref






    # FIXA: fully implement filter
    if {$::diff(filter) != ""} {
        if {[regexp $::diff(filter) $line1]} {
            insertLine $top 1 $doingLine1 $line1
            insertLine $top 2 $doingLine2 $line2
            incr doingLine1
            incr doingLine2
            set ::diff(filterflag) 1
            return
        }
        set ::diff(filterflag) 0
    }

    if {$Pref(parse) != 0} {
        set opts $Pref(ignore)
        if {$Pref(nocase)} {lappend opts -nocase}
        if {$Pref(lineparsewords)} {lappend opts -words}




        set res [DiffUtil::diffStrings {*}$opts $line1 $line2]

        set dotag 0
        set n [expr {[llength $res] / 2}]
        $::widgets($top,wLine1) insert end [myFormL $doingLine1] \
                "hl$::HighLightCount change"
        $::widgets($top,wLine2) insert end [myFormL $doingLine2] \
                "hl$::HighLightCount change"
        set new1 "new1"
        set new2 "new2"
        set change "change"
        foreach {i1 i2} $res {
            incr n -1
            if {$dotag} {
                if {$n == 1 && $Pref(marklast)} {
                    lappend new1 last
                    lappend new2 last
                    lappend change last
                }
                if {$i1 eq ""} {
                    $::widgets($top,wDiff2) insert end $i2 $new2
                } elseif {$i2 eq ""} {







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










|

|







|


|

















|




|















|






|













|





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

|
>
>
>
>
|
|
|


|

|



|
>
>
>
>

|

|

|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





|

>
>
>
>
>

|
|




|


|


|
|
|
|
>
>
>
>
|
>












|







20
21
22
23
24
25
26






































































































































































27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
#
#  You should have received a copy of the GNU General Public License
#  along with this program; see the file COPYING.  If not, write to
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------







































































































































































# This function is called when a toplevel is closed.
# If it is the last remaining toplevel, the application quits.
# If top = "all" it means quit.
# If eskil is embedded, this should be used to close an eskil toplevel.
proc cleanupAndExit {top} {
    # A security thing to make sure we can exit.
    set cont 0
    if {[catch {
        if {$top != "all"} {
            set i [lsearch $::eskil(diffWindows) $top]
            if {$i >= 0} {
                set ::eskil(diffWindows) [lreplace $::eskil(diffWindows) $i $i]
            }
            set i [lsearch $::widgets(toolbars) $top.f]
            if {$i >= 0} {
                set ::widgets(toolbars) [lreplace $::widgets(toolbars) $i $i]
            }

            destroy $top
            array unset ::eskil $top,*

            # Any windows remaining?
            if {[llength $::eskil(diffWindows)] > 0} {
                set cont 1
            }
        }
    } errMsg]} {
        tk_messageBox -icon error -title "Eskil Error" -message \
                "An error occured in the close process.\n$errMsg\n\
                (This is a bug)\nTerminating application." -type ok
    }
    if {$cont} return

    clearTmp
    exit
}

# If embedding, tell eskil about any other toplevel, then
# cleanupAndExit can be used to get rid of it.
proc eskilRegisterToplevel {top} {
    lappend ::eskil(diffWindows) $top
}

# Format a line number
proc myFormL {lineNo} {
    if { ! [string is integer -strict $lineNo]} {return "$lineNo\n"}
      return [format "%3d: \n" $lineNo]
}

# Get a name for a temporary file
# A tail can be given to make the file more recognisable.
proc tmpFile {{tail {}}} {
    if {[info exists ::tmpcnt]} {
        incr ::tmpcnt
    } else {
        set ::tmpcnt 0
    }
    set name "tmpd[pid]a$::tmpcnt"
    if {$tail ne ""} {
        append name " [file tail $tail]"
    }
    set name [file join $::eskil(tmpdir) $name]
    lappend ::tmpfiles $name
    return $name
}

# Delete temporary files
proc clearTmp {args} {
    if { ! [info exists ::tmpfiles]} {
        set ::tmpfiles {}
        return
    }
    if {[llength $args] > 0} {
        foreach f $args {
            set i [lsearch -exact $::tmpfiles $f]
            if {$i >= 0} {
                catch {file delete $f}
                set ::tmpfiles [lreplace $::tmpfiles $i $i]
            }
        }
    } else {
        foreach f $::tmpfiles {
            catch {file delete -force $f}
        }
        set ::tmpfiles {}
    }
}

# insertLine, when in table mode
proc insertLineTable {top side line text {tag equal}} {
    set RE $::eskil($top,separator)
    set words [split $text $RE]
    set id [$::widgets($top,wTable) insert end $words]
    if {$tag ne "equal"} {
        set col 0
        foreach word $words {
            if {$side == 1} {
                # TBD TABLE, r is faked here for now
                dict set ::eskil($top,tablechanges) $id,$col w1 $word
                dict set ::eskil($top,tablechanges) $id,$col w2 ""
                dict set ::eskil($top,tablechanges) $id,$col r  "0 0 1 1"
            } else {
                dict set ::eskil($top,tablechanges) $id,$col w1 ""
                dict set ::eskil($top,tablechanges) $id,$col w2 $word
                dict set ::eskil($top,tablechanges) $id,$col r  "0 0 1 1"
            }
            incr col
        }
    }
}

# Insert lineno and text
proc insertLine {top side line text {tag {equal}} {linetag {}}} {
    if {$::eskil($top,view) eq "table"} {
        insertLineTable $top $side $line $text $tag
        return
    }
    $::widgets($top,wDiff$side) insert end "$text\n" $tag
    if {$linetag eq ""} {
        set linetag $tag
    }
    if {$tag != "equal"} {
        set linetag "hl$::HighLightCount $linetag"
    }
    $::widgets($top,wLine$side) insert end [myFormL $line] $linetag
}

# Insert an empty line on one side of the diff.
proc emptyLine {top side {highlight 1}} {
    if {$::eskil($top,view) eq "table"} {
        # This should be ignored for table
        return
    }
    if {$highlight} {
        $::widgets($top,wLine$side) insert end "\n" hl$::HighLightCount
    } else {
        $::widgets($top,wLine$side) insert end "*****\n"
    }
    $::widgets($top,wDiff$side) insert end "\n" padding
}

# Helper to take care of -sep case
# This can be used when diffing e.g. a CSV file.
# Each column will be handled separately, so differences will never be shown
# crossing a separator
proc diffWithSeparator {RE line1 line2 opts} {
    set ixs1 [regexp -all -inline -indices -- $RE $line1]
    set ixs2 [regexp -all -inline -indices -- $RE $line2]
    # Fake a separator after end of line, makes the loop below simpler
    lappend ixs1 [list [string length $line1] [string length $line1]]
    lappend ixs2 [list [string length $line2] [string length $line2]]
    # Res is at all times starting and ending with an equal pair
    # i.e. same format as the result from DiffStrings
    set res [list {} {}]
    set s1 0
    set s2 0
    foreach ix1 $ixs1 ix2 $ixs2 {
        # Handle if one index list is shorter
        if {$ix1 eq ""} {
            set str1 ""
            set sep1 ""
        } else {
            lassign $ix1 e1 ns1
            incr e1 -1
            set str1 [string range $line1 $s1 $e1]
            set sep1 [string range $line1 {*}$ix1]
        }
        if {$ix2 eq ""} {
            set str2 ""
            set sep2 ""
        } else {
            lassign $ix2 e2 ns2
            incr e2 -1
            set str2 [string range $line2 $s2 $e2]
            set sep2 [string range $line2 {*}$ix2]
        }
        if {$str1 eq $str2} {
            # Merge this equality with end of res
            set resEq1 [lindex $res end-1]
            set resEq2 [lindex $res end]
            lset res end-1 $resEq1$str1$sep1
            lset res end   $resEq2$str2$sep2
        } else {
            set r [DiffUtil::diffStrings {*}$opts $str1 $str2]
            # Starting equal pair
            set rEq1a [lindex $r 0]
            set rEq2a [lindex $r 1]
            # Ending equal pair
            set rEq1b [lindex $r end-1]
            set rEq2b [lindex $r end]
            # Mid part
            set r [lrange $r 2 end-2]
            # Merge starting equalities with end of res
            set resEq1 [lindex $res end-1]
            set resEq2 [lindex $res end]
            lset res end-1 $resEq1$rEq1a
            lset res end   $resEq2$rEq2a
            # Merge equality at end with separator
            lappend res {*}$r $rEq1b$sep1 $rEq2b$sep2
        }
        set s1 [expr {$ns1 + 1}]
        set s2 [expr {$ns2 + 1}]
    }
    #puts "RES   '$res'"
    return $res
}

# This is called from the table view whenever a cell is drawn.
# Add color as needed.
proc tblModeColorCallback {win W key row col tabIdx1 tabIdx2 inStripe selected} {
    set cellX $key,$col
    set top [winfo toplevel $win]
    if { ! [dict exists $::eskil($top,tablechanges) $cellX]} {
        # No changes, nothing to do here
        return
    }
    set cinfo [dict get $::eskil($top,tablechanges) $cellX]
    set w1 [dict get $cinfo w1]
    set w2 [dict get $cinfo w2]

    #puts "COLOR UPDATE W $win K $key R $row C $col TB1 $tabIdx1 TB2 $tabIdx2"
    #puts "   [string length $xxx] '$xxx'"
    #puts "   CHANGEME"

    # Currently the displayed string is just $w1$w2
    # The table might have cut of display of a cell so make sure to stay
    # within the boundaries.
    set txIdx1 [$W index $tabIdx1+1c]
    set l1 [string length $w1]
    set mid "$txIdx1 + $l1 char"
    if {[$W compare $mid >= $tabIdx2]} {
        set mid $tabIdx2
    }
    $W tag add new1 $txIdx1 $mid
    $W tag add new2 $mid    $tabIdx2

    # Get the displayed string
    set xxx [$W get $txIdx1 $tabIdx2]
    if {$xxx ne "$w1$w2"} {
        # Make sure dots are coloured
        $W tag add change "$tabIdx2 - 3c" $tabIdx2
    }
}

# insertMatchingLines, when in table mode
proc insertMatchingLinesTable {top line1 line2} {
    global doingLine1 doingLine2

    set opts $::Pref(ignore)
    if {$::Pref(nocase)} {lappend opts -nocase}
    if {$::Pref(lineparsewords)} {lappend opts -words}
    set RE $::eskil($top,separator)
    set words1 [split $line1 $RE]
    set words2 [split $line2 $RE]
    # Lap 1, make row data
    set rs {}
    set row {}
    foreach w1 $words1 w2 $words2 {
        set r [DiffUtil::diffStrings {*}$opts $w1 $w2]
        # Store for next lap
        lappend rs $r
        if {[llength $r] <= 2} {
            # Equal
            lappend row $w1
        } else {
            # TBD TABLE, simple display for now
            lappend row $w1$w2
        }
    }
    set id [$::widgets($top,wTable) insert end $row]

    # Lap 2, collect cell changes once we have the row id
    set col -1
    foreach w1 $words1 w2 $words2 r $rs {
        incr col
        # Equal? Skip
        if {[llength $r] <= 2} continue
        dict set ::eskil($top,tablechanges) $id,$col "w1" $w1
        dict set ::eskil($top,tablechanges) $id,$col "w2" $w2
        dict set ::eskil($top,tablechanges) $id,$col "r"  $r
    }

    incr doingLine1
    incr doingLine2
}

# Insert one line in each text widget.
# Mark them as changed, and optionally parse them.
proc insertMatchingLines {top line1 line2} {
    global doingLine1 doingLine2

    if {$::eskil($top,view) eq "table"} {
        insertMatchingLinesTable $top $line1 $line2
        return
    }

    # FIXA: fully implement filter
    if {$::eskil(filter) != ""} {
        if {[regexp $::eskil(filter) $line1]} {
            insertLine $top 1 $doingLine1 $line1
            insertLine $top 2 $doingLine2 $line2
            incr doingLine1
            incr doingLine2
            set ::eskil(filterflag) 1
            return
        }
        set ::eskil(filterflag) 0
    }

    if {$::Pref(parse) != 0} {
        set opts $::Pref(ignore)
        if {$::Pref(nocase)} {lappend opts -nocase}
        if {$::Pref(lineparsewords)} {lappend opts -words}
        if {$::eskil($top,separator) ne ""} {
            set res [diffWithSeparator $::eskil($top,separator) $line1 $line2 \
                             $opts]
        } else {
            set res [DiffUtil::diffStrings {*}$opts $line1 $line2]
        }
        set dotag 0
        set n [expr {[llength $res] / 2}]
        $::widgets($top,wLine1) insert end [myFormL $doingLine1] \
                "hl$::HighLightCount change"
        $::widgets($top,wLine2) insert end [myFormL $doingLine2] \
                "hl$::HighLightCount change"
        set new1 "new1"
        set new2 "new2"
        set change "change"
        foreach {i1 i2} $res {
            incr n -1
            if {$dotag} {
                if {$n == 1 && $::Pref(marklast)} {
                    lappend new1 last
                    lappend new2 last
                    lappend change last
                }
                if {$i1 eq ""} {
                    $::widgets($top,wDiff2) insert end $i2 $new2
                } elseif {$i2 eq ""} {
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
            set block1nostar [string map {* {}} $block1nospace]
            set block2nostar [string map {* {}} $block2nospace]
            if {$block1nostar eq $block2nostar} {
                set equal 1
            }
        }
    }
    if {!$equal} {
        return 0
    }

    if {$visible} {
        set tag change
    } else {
        set tag {}







|







416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
            set block1nostar [string map {* {}} $block1nospace]
            set block2nostar [string map {* {}} $block2nospace]
            if {$block1nostar eq $block2nostar} {
                set equal 1
            }
        }
    }
    if { ! $equal} {
        return 0
    }

    if {$visible} {
        set tag change
    } else {
        set tag {}
446
447
448
449
450
451
452
453
454










































455
456
457
458
459
460











461
462
463
464
465
466
467
468
469
470
471
472
473
        $::widgets($top,wLine1) insert end "\n" hl$::HighLightCount
        $::widgets($top,wLine2) insert end "\n" hl$::HighLightCount
        return [expr {($n1 > $n2 ? $n1 : $n2) + 1}]
    } else {
        return [expr {-($n1 > $n2 ? $n1 : $n2)}]
    }
}

# Insert two blocks of lines in the compare windows.










































proc insertMatchingBlocks {top block1 block2 line1 line2 details} {
    global doingLine1 doingLine2

    # A large block may take time.  Give a small warning.
    set n1 [llength $block1]
    set n2 [llength $block2]











    if {$n1 * $n2 > 1000} {
        set ::widgets($top,eqLabel) "!"
        #puts "Eskil warning: Analyzing a large block. ($size1 $size2)"
        update idletasks
    }
    
    # Detect if only newlines has changed within the block, e.g.
    # when rearranging newlines.
    if {$::eskil(ignorenewline)} {
        set res [ParseBlocksAcrossNewline $top $block1 $block2]
        if {$res != 0} {
            # FIXA: move this to ParseBlocksAcrossNewline ?
            if {$res > 0 && $details} {









>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



<


>
>
>
>
>
>
>
>
>
>
>
|

<


|







465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518

519
520
521
522
523
524
525
526
527
528
529
530
531
532
533

534
535
536
537
538
539
540
541
542
543
        $::widgets($top,wLine1) insert end "\n" hl$::HighLightCount
        $::widgets($top,wLine2) insert end "\n" hl$::HighLightCount
        return [expr {($n1 > $n2 ? $n1 : $n2) + 1}]
    } else {
        return [expr {-($n1 > $n2 ? $n1 : $n2)}]
    }
}

# Insert two blocks of lines in the compare windows.
# No extra parsing at all.
proc insertMatchingBlocksNoParse {top block1 block2 line1 line2 details} {
    global doingLine1 doingLine2

    set n1 [llength $block1]
    set n2 [llength $block2]
    # Is this a change block, a delete block or an insert block?
    if {$n1 == 0} {set tag2 new2} else {set tag2 change}
    if {$n2 == 0} {set tag1 new1} else {set tag1 change}

    if {$n1 == $n2} {
        # This should only happen for equal sized blocks that were deemed
        # too large for block parsing.
        foreach line1 $block1 line2 $block2 {
            insertMatchingLines $top $line1 $line2
        }
    } else {
        foreach line $block1 {
            insertLine $top 1 $doingLine1 $line $tag1
            incr doingLine1
        }
        foreach line $block2 {
            insertLine $top 2 $doingLine2 $line $tag2
            incr doingLine2
        }
    }
    if {$n1 <= $n2} {
        for {set t $n1} {$t < $n2} {incr t} {
            emptyLine $top 1
        }
        addChange $top $n2 $tag2 $line1 $n1 $line2 $n2
        nextHighlight $top
    } elseif {$n2 < $n1} {
        for {set t $n2} {$t < $n1} {incr t} {
            emptyLine $top 2
        }
        addChange $top $n1 $tag1 $line1 $n1 $line2 $n2
        nextHighlight $top
    }
}

# Insert two blocks of lines in the compare windows.
proc insertMatchingBlocks {top block1 block2 line1 line2 details} {
    global doingLine1 doingLine2


    set n1 [llength $block1]
    set n2 [llength $block2]

    set large [expr {$n1 * $n2 > 5000}]

    if {$n1 == 0 || $n2 == 0 || $::Pref(parse) < 2 || \
            ($large && $::Pref(parse) < 3)} {
        # No extra parsing at all.
        insertMatchingBlocksNoParse $top $block1 $block2 $line1 $line2 $details
        return
    }

    # A large block may take time.  Give a small warning.
    if {$n1 * $n2 > 2000} {
        set ::widgets($top,eqLabel) "!"

        update idletasks
    }

    # Detect if only newlines has changed within the block, e.g.
    # when rearranging newlines.
    if {$::eskil(ignorenewline)} {
        set res [ParseBlocksAcrossNewline $top $block1 $block2]
        if {$res != 0} {
            # FIXA: move this to ParseBlocksAcrossNewline ?
            if {$res > 0 && $details} {
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545

546
547

548

549
550
551
552
553
554
555
556
557
558
559



560
561
562
563

564
565
566
567
568
569
570
571
572



573
574
575
576

577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621


622
623
624
625
626
627
628
629
630

631
632
633
634
635
636
637
638


639
640
641
642
643
644
645
646
647
648
649
650


651
652
653
654
655
656
657
658
659
660
661
662
663
664

665
666



667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769





770
771

772
773
774
775
776
777
778
779
780
781
782
783
784





785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800

801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847



848


849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894

895
896
897
898
899
900
901

    set apa [compareBlocks $block1 $block2]
    # Fine grained changes means that each line is considered its own
    # chunk. This is used for merging better to avoid the same decision
    # for an entire block.
    set finegrain [expr {$::Pref(finegrainchunks) && $details}]

    if {$finegrain && $::diff($top,ancestorFile) ne ""} {
        # Avoid fine grain depending on relation to ancestor
        set leftChange 0
        set leftChangeOrAdd 0
        for {set t $line1} {$t < $line1 + $n1} {incr t} {
            if {[info exists ::diff($top,ancestorLeft,$t)]} {
                set leftChangeOrAdd 1
                if {$::diff($top,ancestorLeft,$t) eq "c"} {
                    set leftChange 1
                    break
                }
            }
        }
        set rightChange 0
        set rightChangeOrAdd 0
        for {set t $line2} {$t < $line2 + $n2} {incr t} {
            if {[info exists ::diff($top,ancestorRight,$t)]} {
                set rightChangeOrAdd 1
                if {$::diff($top,ancestorRight,$t) eq "c"} {
                    set rightChange 1
                    break
                }
            }
        }
        # Avoid fine grain if either side has no changes against ancestor
        if {!$leftChangeOrAdd || !$rightChangeOrAdd} {
            set finegrain 0
        }
        # Avoid fine grain if both sides have at most additions
        if {!$leftChange && !$rightChange} {
            set finegrain 0
        }
    }

    set t1 0
    set t2 0
    foreach c $apa {
        if {$c eq "c"} {
            set textline1 [lindex $block1 $t1]
            set textline2 [lindex $block2 $t2]
            insertMatchingLines $top $textline1 $textline2
            if {$finegrain} {
                addChange $top 1 change [expr {$line1 + $t1}] 1 \
                        [expr {$line2 + $t2}] 1
                nextHighlight $top
            }
            incr t1
            incr t2
        } elseif {$c eq "C"} {
	    # This is two lines that the block matching considered
	    # too different to use line parsing on them.
	    # Marked the whole line as deleted/inserted
            set textline1 [lindex $block1 $t1]
            set textline2 [lindex $block2 $t2]
            $::widgets($top,wLine1) insert end [myFormL $doingLine1] \
                    "hl$::HighLightCount change"
            $::widgets($top,wDiff1) insert end "$textline1\n" new1

            $::widgets($top,wLine2) insert end [myFormL $doingLine2] \
                    "hl$::HighLightCount change"

            $::widgets($top,wDiff2) insert end "$textline2\n" new2

            if {$finegrain} {
                addChange $top 1 change [expr {$line1 + $t1}] 1 \
                        [expr {$line2 + $t2}] 1
                nextHighlight $top
            }
            incr doingLine1
            incr doingLine2
            incr t1
            incr t2
        } elseif {$c eq "d"} {
            set bepa [lindex $block1 $t1]



            $::widgets($top,wLine1) insert end [myFormL $doingLine1] \
                    "hl$::HighLightCount change"
            $::widgets($top,wDiff1) insert end "$bepa\n" new1
            emptyLine $top 2

            incr doingLine1
            if {$finegrain} {
                addChange $top 1 new1 [expr {$line1 + $t1}] 1 \
                        [expr {$line2 + $t2}] 0
                nextHighlight $top
            }
            incr t1
        } elseif {$c eq "a"} {
            set bepa [lindex $block2 $t2]



            $::widgets($top,wLine2) insert end [myFormL $doingLine2] \
                    "hl$::HighLightCount change"
            $::widgets($top,wDiff2) insert end "$bepa\n" new2
            emptyLine $top 1

            incr doingLine2
            if {$finegrain} {
                addChange $top 1 new2 [expr {$line1 + $t1}] 0 \
                        [expr {$line2 + $t2}] 1
                nextHighlight $top
            }
            incr t2
        }
    }
    if {!$finegrain} {
        if {$details} {
            addChange $top [llength $apa] change $line1 $n1 $line2 $n2
            nextHighlight $top
        } else {
            addMapLines $top [llength $apa]
        }
    }
}

# Process one of the change/add/delete blocks reported by diff.
#  ch1 is a file channel for the left file
#  ch2 is a file channel for the right file
#  n1/n2 is the number of lines involved
#  line1/line2 says on what lines this block starts
# If n1/n2 are both 0, it means that this is the last lines to be displayed.
#  In that case line1/line2, if non-zero says the last line to display.
proc doText {top ch1 ch2 n1 n2 line1 line2} {
    global doingLine1 doingLine2 Pref

    if {$n1 == 0 && $n2 == 0} {
        # All blocks have been processed. Continue until end of file.
        # If "show all" is not on, just display a couple of context lines.
        set limit -1
        if {$Pref(context) >= 0} {
            set limit $Pref(context)
        }
	# Consider any total limit on displayed lines.
        if {$::diff($top,limitlines)} {
            set limit [expr {$::diff($top,limitlines) - $::diff($top,mapMax)}]
            if {$limit < 0} {
                set limit 0
            }
        }
        if {$limit >= 0} {disallowEdit $top}



        set t 0
        while {[gets $ch2 apa] != -1} {
            if {$line2 > 0 && $doingLine2 > $line2} break
            insertLine $top 2 $doingLine2 $apa
            incr doingLine2
            addMapLines $top 1
            incr t
            if {$limit >= 0 && $t >= $limit} break
        }

        set t 0
        while {[gets $ch1 apa] != -1} {
            if {$line1 > 0 && $doingLine1 > $line1} break
            insertLine $top 1 $doingLine1 $apa
            incr doingLine1
            incr t
            if {$limit >= 0 && $t >= $limit} break
        }


        return
    }

    # Is this a change block, a delete block or a insert block?
    if {$n1 == 0} {set tag2 new2} else {set tag2 change}
    if {$n2 == 0} {set tag1 new1} else {set tag1 change}

    # Display all equal lines before next diff
    # If only diff is on, only skip a section if the blank
    # line replaces at least 3 lines.
    set limit -1
    if {$Pref(context) >= 0 && \


            ($line1 - $doingLine1 > (2 * $Pref(context) + 2))} {
        set limit $Pref(context)
    }
    if {$doingLine1 == 1} {
        set allowStartFill 0
    } else {
        set allowStartFill 1
    }
    set t 0
    while {$doingLine1 < $line1} {
        gets $ch1 apa
        gets $ch2 bepa
        if {$limit < 0 || ($t < $limit && $allowStartFill) || \
                ($line1 - $doingLine1) <= $limit} {

            insertLine $top 1 $doingLine1 $apa
            insertLine $top 2 $doingLine2 $bepa



            addMapLines $top 1
        } elseif {$t == $limit && $allowStartFill} {
            # If zero context is shown, skip the filler to keep display tight.
            if {$limit > 0} {
                emptyLine $top 1 0
                emptyLine $top 2 0
                addMapLines $top 1
            }
        }
        incr doingLine1
        incr doingLine2
        incr t
        if {$::diff($top,limitlines) && \
                ($::diff($top,mapMax) > $::diff($top,limitlines))} {
            return
        }
    }
    # This should not happen unless something is wrong...
    if {$doingLine2 != $line2} {
        disallowEdit $top
        $::widgets($top,wDiff1) insert end \
                "**Bad alignment here!! $doingLine2 $line2**\n"
        $::widgets($top,wDiff2) insert end \
                "**Bad alignment here!! $doingLine2 $line2**\n"
        $::widgets($top,wLine1) insert end "\n"
        $::widgets($top,wLine2) insert end "\n"
    }

    # Process the block

    if {$n1 == $n2 && ($n1 == 1 || $Pref(parse) < 2)} {
        # Never do block parsing for one line blocks.
        # If block parsing is turned off, only do line parsing for
        # blocks of equal size.
        for {set t 0} {$t < $n1} {incr t} {
            gets $ch1 textline1
            gets $ch2 textline2
            insertMatchingLines $top $textline1 $textline2
        }
        if {$::diff(filter) != "" &&  $::diff(filterflag)} {
            addMapLines $top $n1
        } else {
            addChange $top $n1 change $line1 $n1 $line2 $n2
            nextHighlight $top
        }
    } else {
        if {$n1 != 0 && $n2 != 0 && $Pref(parse) >= 2 && \
                ($n1 * $n2 < 1000 || $Pref(parse) == 3)} {
            # Full block parsing
            set block1 {}
            for {set t 0} {$t < $n1} {incr t} {
                gets $ch1 apa
                lappend block1 $apa
            }
            set block2 {}
            for {set t 0} {$t < $n2} {incr t} {
                gets $ch2 apa
                lappend block2 $apa
            }
            insertMatchingBlocks $top $block1 $block2 $line1 $line2 1
        } else {
            # No extra parsing at all.
            for {set t 0} {$t < $n1} {incr t} {
                gets $ch1 apa
                insertLine $top 1 $doingLine1 $apa $tag1
                incr doingLine1
            }
            for {set t 0} {$t < $n2} {incr t} {
                gets $ch2 apa
                insertLine $top 2 $doingLine2 $apa $tag2
                incr doingLine2
            }
            if {$n1 <= $n2} {
                for {set t $n1} {$t < $n2} {incr t} {
                    emptyLine $top 1
                }
                addChange $top $n2 $tag2 $line1 $n1 $line2 $n2
                nextHighlight $top
            } elseif {$n2 < $n1} {
                for {set t $n2} {$t < $n1} {incr t} {
                    emptyLine $top 2
                }
                addChange $top $n1 $tag1 $line1 $n1 $line2 $n2
                nextHighlight $top
            }
        }
    }
    # Empty return value
    return
}

proc enableRedo {top} {
    $top.m.mf entryconfigure "Redo Diff" -state normal
    $top.m.mt entryconfigure "Merge"     -state normal
}

proc disableRedo {top} {
    $top.m.mf entryconfigure "Redo Diff" -state disabled
    $top.m.mt entryconfigure "Merge"     -state disabled
}

proc busyCursor {top} {
    global oldcursor oldcursor2





    if {![info exists oldcursor]} {
        set oldcursor [$top cget -cursor]

        set oldcursor2 [$::widgets($top,wDiff1) cget -cursor]
    }
    $top config -cursor watch
    foreach item {wLine1 wDiff1 wLine2 wDiff2} {
        if {[info exists ::widgets($top,$item)]} {
            set w $::widgets($top,$item)
            $w config -cursor watch
        }
    }
}

proc normalCursor {top} {
    global oldcursor oldcursor2





    $top config -cursor $oldcursor
    foreach item {wLine1 wDiff1 wLine2 wDiff2} {
        if {[info exists ::widgets($top,$item)]} {
            set w $::widgets($top,$item)
            $w config -cursor $oldcursor2
        }
    }
}

#####################################
# Special cases.  Conflict/patch
#####################################

proc startConflictDiff {top file} {
    set ::diff($top,mode) "conflict"
    set ::diff($top,modetype) ""

    set ::diff($top,conflictFile) $file
    set ::diff($top,rightDir) [file dirname $file]
    set ::diff($top,rightOK) 1
    set ::diff($top,rightLabel) $file
    set ::diff($top,leftLabel) $file
    set ::diff($top,leftOK) 0

    # Turn off ignore
    set ::Pref(ignore) " "
    set ::Pref(nocase) 0
    set ::Pref(noempty) 0

    # Try to autodetect line endings in file
    set ch [open $file rb]
    set data [read $ch 10000]
    close $ch
    if {[string first \r\n $data] >= 0} {
        set ::diff($top,mergetranslation) crlf
    } else {
        set ::diff($top,mergetranslation) lf
    }
}

# Read a conflict file and extract the two versions.
proc prepareConflict {top} {
    global Pref

    disallowEdit $top
    set ::diff($top,leftFile) [tmpFile]
    set ::diff($top,rightFile) [tmpFile]

    set ch1 [open $::diff($top,leftFile) w]
    set ch2 [open $::diff($top,rightFile) w]
    set ch [open $::diff($top,conflictFile) r]

    set ::diff($top,conflictDiff) {}
    set leftLine 1
    set rightLine 1
    set state both
    set rightName ""
    set leftName ""
    while {[gets $ch line] != -1} {
        if {[string match <<<<<<* $line]} {
            set state right
            regexp {<*\s*(.*)} $line -> rightName
            set start2 $rightLine
        } elseif {[string match ======* $line] && $state eq "right"} {



            set state left


            set end2 [expr {$rightLine - 1}]
            set start1 $leftLine
        } elseif {[string match >>>>>>* $line] && $state eq "left"} {
            set state both
            regexp {>*\s*(.*)} $line -> leftName
            set end1 [expr {$leftLine - 1}]
            lappend ::diff($top,conflictDiff) [list \
                    $start1 [expr {$end1 - $start1 + 1}] \
                    $start2 [expr {$end2 - $start2 + 1}]]
        } elseif {$state eq "both"} {
            puts $ch1 $line
            puts $ch2 $line
            incr leftLine
            incr rightLine
        } elseif {$state eq "left"} {
            puts $ch1 $line
            incr leftLine
        } else {
            puts $ch2 $line
            incr rightLine
        }
    }
    close $ch
    close $ch1
    close $ch2

    if {$leftName eq "" && $rightName eq ""} {
        set leftName "No Conflict: [file tail $::diff($top,conflictFile)]"
        set rightName $leftName
    }
    set ::diff($top,leftLabel) $leftName
    set ::diff($top,rightLabel) $rightName
    update idletasks
}

# Clean up after a conflict diff.
proc cleanupConflict {top} {
    global Pref

    clearTmp $::diff($top,rightFile) $::diff($top,leftFile)
    set ::diff($top,rightFile) $::diff($top,conflictFile)
    set ::diff($top,leftFile) $::diff($top,conflictFile)
}

# Display one chunk from a patch file
proc displayOnePatch {top leftLines rightLines leftLine rightLine} {

    emptyLine $top 1
    emptyLine $top 2

    set leftlen [llength $leftLines]
    set rightlen [llength $rightLines]

    set leftc 0







|




|

|








|

|






|



|



















|
|
|


|
|
|
>
|
|
>
|
>





<
<




>
>
>
|
|
|
|
>









>
>
>
|
|
|
|
>









|

















|





|
|

|
|
|






>
>
|
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
>
>



|



|
|


|
>
>
|
|












>
|
|
>
>
>












|
|
















|








|






<
<
|
|
|
|
|
|
|
|
|
|
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

















>
>
>
>
>
|

>
|


|

|
|






>
>
>
>
>

|

|
|









|
|
>
|
|
|
|
|
|







<
<
<
<
|
<
<
<




<
<

|
|

|
|
|

|










|
>
>
>

>
>

|




|










|









|


|
|





<
<
|
|
|




>







552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626


627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802


803
804
805
806
807
808
809
810
811
812
813
814


























815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887




888



889
890
891
892


893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955


956
957
958
959
960
961
962
963
964
965
966
967
968
969
970

    set apa [compareBlocks $block1 $block2]
    # Fine grained changes means that each line is considered its own
    # chunk. This is used for merging better to avoid the same decision
    # for an entire block.
    set finegrain [expr {$::Pref(finegrainchunks) && $details}]

    if {$finegrain && $::eskil($top,ancestorFile) ne ""} {
        # Avoid fine grain depending on relation to ancestor
        set leftChange 0
        set leftChangeOrAdd 0
        for {set t $line1} {$t < $line1 + $n1} {incr t} {
            if {[info exists ::eskil($top,ancestorLeft,$t)]} {
                set leftChangeOrAdd 1
                if {$::eskil($top,ancestorLeft,$t) eq "c"} {
                    set leftChange 1
                    break
                }
            }
        }
        set rightChange 0
        set rightChangeOrAdd 0
        for {set t $line2} {$t < $line2 + $n2} {incr t} {
            if {[info exists ::eskil($top,ancestorRight,$t)]} {
                set rightChangeOrAdd 1
                if {$::eskil($top,ancestorRight,$t) eq "c"} {
                    set rightChange 1
                    break
                }
            }
        }
        # Avoid fine grain if either side has no changes against ancestor
        if { ! $leftChangeOrAdd || !$rightChangeOrAdd} {
            set finegrain 0
        }
        # Avoid fine grain if both sides have at most additions
        if { ! $leftChange && !$rightChange} {
            set finegrain 0
        }
    }

    set t1 0
    set t2 0
    foreach c $apa {
        if {$c eq "c"} {
            set textline1 [lindex $block1 $t1]
            set textline2 [lindex $block2 $t2]
            insertMatchingLines $top $textline1 $textline2
            if {$finegrain} {
                addChange $top 1 change [expr {$line1 + $t1}] 1 \
                        [expr {$line2 + $t2}] 1
                nextHighlight $top
            }
            incr t1
            incr t2
        } elseif {$c eq "C"} {
            # This is two lines that the block matching considered
            # too different to use line parsing on them.
            # Marked the whole line as deleted/inserted
            set textline1 [lindex $block1 $t1]
            set textline2 [lindex $block2 $t2]
            if {$::eskil($top,view) eq "table"} {
                # Fall back to proc that handles table
                insertMatchingLinesTable $top $textline1 $textline2
            } else {
                insertLine $top 1 $doingLine1 $textline1 new1 change
                insertLine $top 2 $doingLine2 $textline2 new2 change
                incr doingLine1
                incr doingLine2
            }
            if {$finegrain} {
                addChange $top 1 change [expr {$line1 + $t1}] 1 \
                        [expr {$line2 + $t2}] 1
                nextHighlight $top
            }


            incr t1
            incr t2
        } elseif {$c eq "d"} {
            set bepa [lindex $block1 $t1]
            if {$::eskil($top,view) eq "table"} {
                insertLineTable $top 1 $doingLine1 $bepa new1
            } else {
                $::widgets($top,wLine1) insert end [myFormL $doingLine1] \
                        "hl$::HighLightCount change"
                $::widgets($top,wDiff1) insert end "$bepa\n" new1
                emptyLine $top 2
            }
            incr doingLine1
            if {$finegrain} {
                addChange $top 1 new1 [expr {$line1 + $t1}] 1 \
                        [expr {$line2 + $t2}] 0
                nextHighlight $top
            }
            incr t1
        } elseif {$c eq "a"} {
            set bepa [lindex $block2 $t2]
            if {$::eskil($top,view) eq "table"} {
                insertLineTable $top 2 $doingLine2 $bepa new2
            } else {
                $::widgets($top,wLine2) insert end [myFormL $doingLine2] \
                        "hl$::HighLightCount change"
                $::widgets($top,wDiff2) insert end "$bepa\n" new2
                emptyLine $top 1
            }
            incr doingLine2
            if {$finegrain} {
                addChange $top 1 new2 [expr {$line1 + $t1}] 0 \
                        [expr {$line2 + $t2}] 1
                nextHighlight $top
            }
            incr t2
        }
    }
    if { ! $finegrain} {
        if {$details} {
            addChange $top [llength $apa] change $line1 $n1 $line2 $n2
            nextHighlight $top
        } else {
            addMapLines $top [llength $apa]
        }
    }
}

# Process one of the change/add/delete blocks reported by diff.
#  ch1 is a file channel for the left file
#  ch2 is a file channel for the right file
#  n1/n2 is the number of lines involved
#  line1/line2 says on what lines this block starts
# If n1/n2 are both 0, it means that this is the last lines to be displayed.
#  In that case line1/line2, if non-zero says the last line to display.
proc doText {top ch1 ch2 n1 n2 line1 line2} {
    global doingLine1 doingLine2

    if {$n1 == 0 && $n2 == 0} {
        # All blocks have been processed. Continue until end of file.
        # If "show all" is not on, just display a couple of context lines.
        set limit -1
        if {$::Pref(context) >= 0} {
            set limit $::Pref(context)
        }
        # Consider any total limit on displayed lines.
        if {$::eskil($top,limitlines)} {
            set limit [expr {$::eskil($top,limitlines) - $::eskil($top,mapMax)}]
            if {$limit < 0} {
                set limit 0
            }
        }
        if {$limit >= 0} {disallowEdit $top}

        # Unless we are in "only diffs", display remaining lines to the limit
        if {$limit != 0} {
            set t 0
            while {[gets $ch2 apa] != -1} {
                if {$line2 > 0 && $doingLine2 > $line2} break
                insertLine $top 2 $doingLine2 $apa
                incr doingLine2
                addMapLines $top 1
                incr t
                if {$limit >= 0 && $t >= $limit} break
            }
            if {$::eskil($top,view) ne "table"} {
                set t 0
                while {[gets $ch1 apa] != -1} {
                    if {$line1 > 0 && $doingLine1 > $line1} break
                    insertLine $top 1 $doingLine1 $apa
                    incr doingLine1
                    incr t
                    if {$limit >= 0 && $t >= $limit} break
                }
            }
        }
        return
    }

    # Is this a change block, a delete block or an insert block?
    if {$n1 == 0} {set tag2 new2} else {set tag2 change}
    if {$n2 == 0} {set tag1 new1} else {set tag1 change}

    # Display all equal lines before next diff, or skip if context is set.
    # If context is on, only skip a section if the blank
    # line replaces at least 3 lines.
    set limit -1
    if {$::Pref(context) == 0} {
        set limit 0
    } elseif {$::Pref(context) > 0 && \
            ($line1 - $doingLine1 > (2 * $::Pref(context) + 2))} {
        set limit $::Pref(context)
    }
    if {$doingLine1 == 1} {
        set allowStartFill 0
    } else {
        set allowStartFill 1
    }
    set t 0
    while {$doingLine1 < $line1} {
        gets $ch1 apa
        gets $ch2 bepa
        if {$limit < 0 || ($t < $limit && $allowStartFill) || \
                ($line1 - $doingLine1) <= $limit} {
            if {$::eskil($top,view) ne "table"} {
                insertLine $top 1 $doingLine1 $apa
                insertLine $top 2 $doingLine2 $bepa
            } else {
                insertLineTable $top 1 $doingLine1 $apa
            }
            addMapLines $top 1
        } elseif {$t == $limit && $allowStartFill} {
            # If zero context is shown, skip the filler to keep display tight.
            if {$limit > 0} {
                emptyLine $top 1 0
                emptyLine $top 2 0
                addMapLines $top 1
            }
        }
        incr doingLine1
        incr doingLine2
        incr t
        if {$::eskil($top,limitlines) && \
                ($::eskil($top,mapMax) > $::eskil($top,limitlines))} {
            return
        }
    }
    # This should not happen unless something is wrong...
    if {$doingLine2 != $line2} {
        disallowEdit $top
        $::widgets($top,wDiff1) insert end \
                "**Bad alignment here!! $doingLine2 $line2**\n"
        $::widgets($top,wDiff2) insert end \
                "**Bad alignment here!! $doingLine2 $line2**\n"
        $::widgets($top,wLine1) insert end "\n"
        $::widgets($top,wLine2) insert end "\n"
    }

    # Process the block

    if {$n1 == $n2 && ($n1 == 1 || $::Pref(parse) < 2)} {
        # Never do block parsing for one line blocks.
        # If block parsing is turned off, only do line parsing for
        # blocks of equal size.
        for {set t 0} {$t < $n1} {incr t} {
            gets $ch1 textline1
            gets $ch2 textline2
            insertMatchingLines $top $textline1 $textline2
        }
        if {$::eskil(filter) != "" &&  $::eskil(filterflag)} {
            addMapLines $top $n1
        } else {
            addChange $top $n1 change $line1 $n1 $line2 $n2
            nextHighlight $top
        }
    } else {


        # Collect blocks
        set block1 {}
        for {set t 0} {$t < $n1} {incr t} {
            gets $ch1 apa
            lappend block1 $apa
        }
        set block2 {}
        for {set t 0} {$t < $n2} {incr t} {
            gets $ch2 apa
            lappend block2 $apa
        }
        insertMatchingBlocks $top $block1 $block2 $line1 $line2 1


























    }
    # Empty return value
    return
}

proc enableRedo {top} {
    $top.m.mf entryconfigure "Redo Diff" -state normal
    $top.m.mt entryconfigure "Merge"     -state normal
}

proc disableRedo {top} {
    $top.m.mf entryconfigure "Redo Diff" -state disabled
    $top.m.mt entryconfigure "Merge"     -state disabled
}

proc busyCursor {top} {
    global oldcursor oldcursor2
    if {$::eskil($top,view) eq "table"} {
        set items wTable
    } else {
        set items {wLine1 wDiff1 wLine2 wDiff2}
    }
    if { ! [info exists oldcursor]} {
        set oldcursor [$top cget -cursor]
        set i1 [lindex $items 0]
        set oldcursor2 [$::widgets($top,$i1) cget -cursor]
    }
    $top config -cursor watch
    foreach item $items {
        if {[info exists ::widgets($top,$item)]} {
            set W $::widgets($top,$item)
            $W config -cursor watch
        }
    }
}

proc normalCursor {top} {
    global oldcursor oldcursor2
    if {$::eskil($top,view) eq "table"} {
        set items wTable
    } else {
        set items {wLine1 wDiff1 wLine2 wDiff2}
    }
    $top config -cursor $oldcursor
    foreach item $items {
        if {[info exists ::widgets($top,$item)]} {
            set W $::widgets($top,$item)
            $W config -cursor $oldcursor2
        }
    }
}

#####################################
# Special cases.  Conflict/patch
#####################################

proc startConflictDiff {top file} {
    set ::eskil($top,mode) "conflict"
    set ::eskil($top,modetype) ""
    set ::eskil($top,view) ""
    set ::eskil($top,conflictFile) $file
    set ::eskil($top,rightDir) [file dirname $file]
    set ::eskil($top,rightOK) 1
    set ::eskil($top,rightLabel) $file
    set ::eskil($top,leftLabel) $file
    set ::eskil($top,leftOK) 0

    # Turn off ignore
    set ::Pref(ignore) " "
    set ::Pref(nocase) 0
    set ::Pref(noempty) 0

    # Try to autodetect line endings in file




    detectLineEnd $top $file mergetranslation lf



}

# Read a conflict file and extract the two versions.
proc prepareConflict {top} {


    disallowEdit $top
    set ::eskil($top,leftFile) [tmpFile]
    set ::eskil($top,rightFile) [tmpFile]

    set ch1 [open $::eskil($top,leftFile) w]
    set ch2 [open $::eskil($top,rightFile) w]
    set ch [open $::eskil($top,conflictFile) r]

    set ::eskil($top,conflictDiff) {}
    set leftLine 1
    set rightLine 1
    set state both
    set rightName ""
    set leftName ""
    while {[gets $ch line] != -1} {
        if {[string match <<<<<<* $line]} {
            set state right
            regexp {<*\s*(.*)} $line -> rightName
            set start2 $rightLine
        } elseif {[string match ======* $line] && $state in "right ancestor"} {
            if {$state eq "right"} {
                set end2 [expr {$rightLine - 1}]
            }
            set state left
            set start1 $leftLine
        } elseif {[string match ||||||* $line] && $state eq "right"} {
            set end2 [expr {$rightLine - 1}]
            set state ancestor
        } elseif {[string match >>>>>>* $line] && $state eq "left"} {
            set state both
            regexp {>*\s*(.*)} $line -> leftName
            set end1 [expr {$leftLine - 1}]
            lappend ::eskil($top,conflictDiff) [list \
                    $start1 [expr {$end1 - $start1 + 1}] \
                    $start2 [expr {$end2 - $start2 + 1}]]
        } elseif {$state eq "both"} {
            puts $ch1 $line
            puts $ch2 $line
            incr leftLine
            incr rightLine
        } elseif {$state eq "left"} {
            puts $ch1 $line
            incr leftLine
        } elseif {$state eq "right"} {
            puts $ch2 $line
            incr rightLine
        }
    }
    close $ch
    close $ch1
    close $ch2

    if {$leftName eq "" && $rightName eq ""} {
        set leftName "No Conflict: [file tail $::eskil($top,conflictFile)]"
        set rightName $leftName
    }
    set ::eskil($top,leftLabel) $leftName
    set ::eskil($top,rightLabel) $rightName
    update idletasks
}

# Clean up after a conflict diff.
proc cleanupConflict {top} {


    clearTmp $::eskil($top,rightFile) $::eskil($top,leftFile)
    set ::eskil($top,rightFile) $::eskil($top,conflictFile)
    set ::eskil($top,leftFile) $::eskil($top,conflictFile)
}

# Display one chunk from a patch file
proc displayOnePatch {top leftLines rightLines leftLine rightLine} {
    mapNoChange $top 1
    emptyLine $top 1
    emptyLine $top 2

    set leftlen [llength $leftLines]
    set rightlen [llength $rightLines]

    set leftc 0
989
990
991
992
993
994
995

996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027

1028
1029




1030










1031
1032
1033
1034
1035
1036


















1037
1038
1039
1040
1041
1042
1043
    if {[llength $lblock] > 0 || [llength $rblock] > 0} {
        set ::doingLine1 $lblockl
        set ::doingLine2 $rblockl
        insertMatchingBlocks $top $lblock $rblock $lblockl $rblockl 0
        set lblock {}
        set rblock {}
    }

}

# Read a patch file and display it
proc displayPatch {top} {
    global Pref

    set ::diff($top,leftLabel) "Patch $::diff($top,patchFile): old"
    set ::diff($top,rightLabel) "Patch $::diff($top,patchFile): new"
    update idletasks

    if {$::diff($top,patchFile) eq ""} {
        if {$::diff($top,patchData) eq ""} {
            set data [getFullPatch $top]
        } else {
            set data $::diff($top,patchData)
        }
    } elseif {$::diff($top,patchFile) eq "-"} {
        set data [read stdin]
    } else {
        set ch [open $::diff($top,patchFile) r]
        set data [read $ch]
        close $ch
    }

    set style ""
    set divider "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-"

    set leftLine 1
    set rightLine 1
    set leftLines {}
    set rightLines {}
    set state none

    foreach line [split $data \n] {
        # Detect a new file




        if {[string match ======* $line] || [string match "diff *" $line]} {










            if {$state != "none"} {
                displayOnePatch $top $leftLines $rightLines $leftLine $rightLine
            }
            set leftLines {}
            set rightLines {}
            set state none


















            continue
        }
        # Detect the first line in a -c style diff
        if {[regexp {^\*\*\* } $line]} {
            if {$state eq "right"} {
                displayOnePatch $top $leftLines $rightLines $leftLine $rightLine
                set leftLines {}







>




<
|
|
|


|
|


|

|


|












>

|
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>






>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069

1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
    if {[llength $lblock] > 0 || [llength $rblock] > 0} {
        set ::doingLine1 $lblockl
        set ::doingLine2 $rblockl
        insertMatchingBlocks $top $lblock $rblock $lblockl $rblockl 0
        set lblock {}
        set rblock {}
    }
    mapNoChange $top 0
}

# Read a patch file and display it
proc displayPatch {top} {

    set ::eskil($top,leftLabel) "Patch $::eskil($top,patchFile): old"
    set ::eskil($top,rightLabel) "Patch $::eskil($top,patchFile): new"
    set ::eskil($top,patchFilelist) {}
    update idletasks

    if {$::eskil($top,patchFile) eq ""} {
        if {$::eskil($top,patchData) eq ""} {
            set data [getFullPatch $top]
        } else {
            set data $::eskil($top,patchData)
        }
    } elseif {$::eskil($top,patchFile) eq "-"} {
        set data [read stdin]
    } else {
        set ch [open $::eskil($top,patchFile) r]
        set data [read $ch]
        close $ch
    }

    set style ""
    set divider "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-"

    set leftLine 1
    set rightLine 1
    set leftLines {}
    set rightLines {}
    set state none
    set fname ""
    foreach line [split $data \n] {
        # Detect a new file or file name
        # "diff *" handles at least GIT and HG output
        # "Index:" and "=====*" handles at least FOSSIL and SVN output
        set newFile 0
        set newName ""
        if {[string match ======* $line]} {
            set newFile 1
        } elseif {[string match "diff *" $line]} {
            set newFile 1
            # Extract the last non-space. Works reasonably well.
            regexp {\S+\s*$} $line newName
        } elseif {[string match "Index: *" $line]} {
            set newName [string range $line 7 end]
        }

        if {$newFile} {
            if {$state != "none"} {
                displayOnePatch $top $leftLines $rightLines $leftLine $rightLine
            }
            set leftLines {}
            set rightLines {}
            set state none
        }
        if {$newName ne ""} {
            # If fname is set, a file that had no contents has passed.
            # It could be a binary file or some other that the diffing source
            # could not handle.
            # Display the name to see that it is involved.
            if {$fname ne ""} {
                foreach side {1 2} {
                    emptyLine $top $side
                    insertLine $top $side "" $divider patch
                    insertLine $top $side "" $fname   patch
                    insertLine $top $side "" $divider patch
                }
                addChange $top 4 change 0 0 0 0
            }
            set fname $newName
        }
        if {$newFile || $newName ne ""} {
            continue
        }
        # Detect the first line in a -c style diff
        if {[regexp {^\*\*\* } $line]} {
            if {$state eq "right"} {
                displayOnePatch $top $leftLines $rightLines $leftLine $rightLine
                set leftLines {}
1063
1064
1065
1066
1067
1068
1069

1070
1071
1072
1073
1074
1075
1076
1077

1078
1079
1080
1081
1082
1083
1084



1085
1086
1087
1088
1089
1090
1091
1092
                set state newfile
                set style u
                set leftRE {^---\s+(.*)$}
                set rightRE {^\+\+\+\s+(.*)$}
            }
        }
        if {$state eq "newfile" && [regexp $leftRE $line -> sub]} {

            emptyLine $top 1
            insertLine $top 1 "" $divider patch
            insertLine $top 1 "" $sub     patch
            insertLine $top 1 "" $divider patch
            addChange $top 4 change 0 0 0 0
            continue
        }
        if {$state eq "newfile" && [regexp $rightRE $line -> sub]} {

            emptyLine $top 2
            insertLine $top 2 "" $divider patch
            insertLine $top 2 "" $sub     patch
            insertLine $top 2 "" $divider patch
            continue
        }
        # A new section in a -u style diff



        if {[regexp {^@@\s+-(\d+),\d+\s+\+(\d+),} $line -> sub1 sub2]} {
            if {$state eq "both"} {
                displayOnePatch $top $leftLines $rightLines \
                        $leftLine $rightLine
            }
            # Look for c function annotation in -u style
            if {[regexp {^@@.*@@(.*)$} $line -> cfun]} {
                set cfun [string trim $cfun]







>








>







>
>
>
|







1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
                set state newfile
                set style u
                set leftRE {^---\s+(.*)$}
                set rightRE {^\+\+\+\s+(.*)$}
            }
        }
        if {$state eq "newfile" && [regexp $leftRE $line -> sub]} {
            set fname ""
            emptyLine $top 1
            insertLine $top 1 "" $divider patch
            insertLine $top 1 "" $sub     patch
            insertLine $top 1 "" $divider patch
            addChange $top 4 change 0 0 0 0
            continue
        }
        if {$state eq "newfile" && [regexp $rightRE $line -> sub]} {
            set fname ""
            emptyLine $top 2
            insertLine $top 2 "" $divider patch
            insertLine $top 2 "" $sub     patch
            insertLine $top 2 "" $divider patch
            continue
        }
        # A new section in a -u style diff
        # Normally the chunk starts with @@
        # From some tools the chunk starts with ##
        if {[regexp {^(?:@@|\#\#)\s+-(\d+)(?:,\d+)?\s+\+(\d+)} $line ->\
                     sub1 sub2]} {
            if {$state eq "both"} {
                displayOnePatch $top $leftLines $rightLines \
                        $leftLine $rightLine
            }
            # Look for c function annotation in -u style
            if {[regexp {^@@.*@@(.*)$} $line -> cfun]} {
                set cfun [string trim $cfun]
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176









1177
1178
1179
1180
1181














1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192









1193





























1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212







1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272







1273
1274
1275
1276

1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291

1292
1293
1294
1295
1296
1297

1298



1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310


1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321

1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349



1350

1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374





1375
1376


































1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416

1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430

1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458


1459
1460
1461
1462
1463
1464

1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481

1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538


1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568














1569
1570
1571
1572
1573
1574
1575



1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593



1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
            if {[regexp {^---\s*(\d*)} $line -> sub]} {
                if {$sub != ""} {
                    set rightLine $sub
                }
                set state right
                continue
            }
            if {![regexp {^[\s!+-]} $line]} continue
            lappend leftLines [list $leftLine \
                    [string trim [string range $line 0 1]] \
                    [string range $line 2 end]]
            incr leftLine
            continue
        }
        # We are in the right part of a -c style diff
        if {$state eq "right"} {
            if {![regexp {^[\s!+-]} $line]} continue
            lappend rightLines [list $rightLine \
                    [string trim [string range $line 0 1]] \
                    [string range $line 2 end]]
            incr rightLine
            continue
        }
        # We are in a -u style diff
        if {$state eq "both"} {
            if {![regexp {^[\s+-]} $line]} continue
            set sig [string trim [string index $line 0]]
            set str [string range $line 1 end]
            if {$sig eq ""} {
                lappend leftLines [list $leftLine "" $str]
                lappend rightLines [list $leftLine "" $str]
                incr leftLine
                incr rightLine
            } elseif {$sig eq "-"} {
                lappend leftLines [list $leftLine "-" $str]
                incr leftLine
            } else {
                lappend rightLines [list $leftLine "+" $str]
                incr rightLine
            }
            continue
        }
    }
    if {$state != "none"} {
        displayOnePatch $top $leftLines $rightLines $leftLine $rightLine
    }









}

#####################################
# Main diff
#####################################















# Prepare for a diff by creating needed temporary files
proc prepareFiles {top} {
    set ::diff($top,cleanup) {}
    if {$::diff($top,mode) eq "rev"} {
        prepareRev $top
        lappend ::diff($top,cleanup) "rev"
    } elseif {$::diff($top,mode) eq "conflict"} {
        prepareConflict $top
        lappend ::diff($top,cleanup) "conflict"
    }









    if {$::diff($top,plugin) ne ""} {





























        preparePlugin $top
        set ::diff($top,cleanup) "plugin $::diff($top,cleanup)"
    }
}

# Clean up after a diff
proc cleanupFiles {top} {
    foreach keyword $::diff($top,cleanup) {
        switch $keyword {
            "rev"      {cleanupRev      $top}
            "conflict" {cleanupConflict $top}
            "plugin"   {cleanupPlugin   $top}
        }
    }
    set ::diff($top,cleanup) {}
}

# Redo Diff command
proc redoDiff {top} {







    # Note what rows are being displayed
    set w $::widgets($top,wDiff1)

    set width  [winfo width $w]
    set height [winfo height $w]

    set first [$w index @0,0]
    set last  [$w index @[- $width 4],[- $height 4]]

    set first [lindex [split $first .] 0]
    set last  [lindex [split $last  .] 0]

    # Narrow it 5 lines since seeText will try to view 5 lines extra
    incr first 5
    incr last -5
    if {$last < $first} {
        set last $first
    }

    doDiff $top

    # Restore view
    foreach item {wLine1 wDiff1 wLine2 wDiff2} {
        set w $::widgets($top,$item)
        seeText $w $first.0 $last.0
    }
}

# Make an appropriate tail for a window title, depending on mode and files.
proc TitleTail {top} {
    set tail1 [file tail $::diff($top,rightLabel)]
    set tail2 [file tail $::diff($top,leftLabel)]
    if {$::diff($top,mode) ne "" || $tail1 eq $tail2} {
        if {$::diff($top,mode) eq "rev"} {
            set tail1 [file tail $::diff($top,RevFile)]
        } elseif {$::diff($top,mode) eq "conflict"} {
            set tail1 [file tail $::diff($top,conflictFile)]
        }
        return $tail1
    } else {
        return "$tail2 vs $tail1"
    }
}

# Main diff function.
proc doDiff {top} {
    global Pref
    global doingLine1 doingLine2

    if {$::diff($top,mode) eq "" && ($::diff($top,leftOK) == 0 || $::diff($top,rightOK) == 0)} {
        disableRedo $top
        return
    } else {
        enableRedo $top
    }

    busyCursor $top
    resetEdit $top

    # Clear up everything before starting processing







    foreach item {wLine1 wDiff1 wLine2 wDiff2} {
        set w $::widgets($top,$item)
        $w configure -state normal
        $w delete 1.0 end

    }
    clearMap $top
    set ::HighLightCount 0
    highLightChange $top -1
    # Display a star during diff execution, to know when the internal
    # processing starts, and when the label is "valid".
    set ::widgets($top,eqLabel) "*"

    wm title $top "Eskil:"
    update idletasks

    if {$::diff($top,mode) eq "patch"} {
        disallowEdit $top
        displayPatch $top
        drawMap $top -1

        foreach item {wLine1 wLine2} {
            set w $::widgets($top,$item)
            $w configure -state disabled
        }
        update idletasks
        wm title $top "Eskil: [file tail $::diff($top,patchFile)]"

        $::widgets($top,wLine2) see 1.0



        normalCursor $top
        return
    } else {
        prepareFiles $top
    }

    wm title $top "Eskil: [TitleTail $top]"

    # Run diff and parse the result.
    set opts $Pref(ignore)
    if {$Pref(nocase)} {lappend opts -nocase}
    if {$Pref(noempty)} {lappend opts -noempty}


    if {[info exists ::diff($top,aligns)] && \
            [llength $::diff($top,aligns)] > 0} {
        lappend opts -align $::diff($top,aligns)
    }
    set range {}
    if {[info exists ::diff($top,range)] && \
            [llength $::diff($top,range)] == 4} {
        set range $::diff($top,range)
        lappend opts -range $range
    }
    if {[llength $Pref(regsub)] > 0} {

        lappend opts -regsub $Pref(regsub)
    }
    # Apply nodigit after preprocess
    if {$Pref(nodigit)} {lappend opts -nodigit}

    # If a special file for diffing is present, use it.
    if {[info exists ::diff($top,leftFileDiff)]} {
        set dFile1 $::diff($top,leftFileDiff)
    } else {
        set dFile1 $::diff($top,leftFile)
    }
    if {[info exists ::diff($top,rightFileDiff)]} {
        set dFile2 $::diff($top,rightFileDiff)
    } else {
        set dFile2 $::diff($top,rightFile)
    }

    set differr [catch {DiffUtil::diffFiles {*}$opts \
            $dFile1 $dFile2} diffres]

    # In conflict mode we can use the diff information collected when
    # parsing the conflict file. This makes sure the blocks in the conflict
    # file become change-blocks during merge.
    if {$::diff($top,mode) eq "conflict" && $::diff($top,modetype) eq "Pure"} {
        set diffres $::diff($top,conflictDiff)
    }

    if {$differr != 0} {



        $::widgets($top,wDiff1) insert end $diffres

        normalCursor $top
        return
    }
    if {[llength $diffres] == 0} {
        set ::widgets($top,eqLabel) "="
        # Automatically close if equal
        if {$::eskil(autoclose)} {
            after idle cleanupAndExit $top
            return
        }
    } else {
        set ::widgets($top,eqLabel) " "
    }
    # Update the equal label immediately for better feedback
    update idletasks

    if {$::diff($top,ancestorFile) ne ""} {
        collectAncestorInfo $top $dFile1 $dFile2 $opts
    }

    set firstview 1

    set ch1 [open $::diff($top,leftFile)]
    set ch2 [open $::diff($top,rightFile)]





    set doingLine1 1
    set doingLine2 1



































    # If there is a range, skip lines up to the range
    if {[llength $range] != 0} {
        disallowEdit $top
        lassign $range start1 end1 start2 end2
        while {$doingLine1 < $start1 && [gets $ch1 line] >= 0} {
            incr doingLine1
        }
        while {$doingLine2 < $start2 && [gets $ch2 line] >= 0} {
            incr doingLine2
        }
    }

    set t 0
    foreach i $diffres {
        lassign $i line1 n1 line2 n2
        doText $top $ch1 $ch2 $n1 $n2 $line1 $line2
        if {$::diff($top,limitlines) && \
                ($::diff($top,mapMax) > $::diff($top,limitlines))} {
            break
        }

        # Get one update when the screen has been filled.
        # Show the first diff.
        if {$firstview && $::diff($top,mapMax) > 100} {
            set firstview 0
            showDiff $top 0
            update idletasks
        }
    }

    # If there is a range, just display the range
    if {[llength $range] != 0} {
        lassign $range start1 end1 start2 end2
    } else {
        set end1 0
        set end2 0
    }
    doText $top $ch1 $ch2 0 0 $end1 $end2


    # Make sure all text widgets have the same number of lines.
    # The common y scroll doesn't work well if not.
    set max 0.0
    foreach item {wLine1 wDiff1 wLine2 wDiff2} {
        set w $::widgets($top,$item)
        if {[$w index end] > $max} {
            set max [$w index end]
        }
    }
    foreach item {wLine1 wDiff1 wLine2 wDiff2} {
        set w $::widgets($top,$item)
        set d [expr {int($max) - int([$w index end])}]
        for {set t 0} {$t < $d} {incr t} {
            $w insert end \n padding

        }
    }

    close $ch1
    close $ch2

    # We can turn off editing in the text windows after everything
    # is displayed.
    noEdit $top

    # Mark aligned lines
    if {[info exists ::diff($top,aligns)] && \
            [llength $::diff($top,aligns)] > 0} {
        foreach {align1 align2} $::diff($top,aligns) {
            set i [$::widgets($top,wLine1) search -regexp "\\m$align1\\M" 1.0]
            if {$i != ""} {
                $::widgets($top,wLine1) tag add align \
                        "$i linestart" "$i lineend"
            }
            set i [$::widgets($top,wLine2) search -regexp "\\m$align2\\M" 1.0]
            if {$i != ""} {
                $::widgets($top,wLine2) tag add align \
                        "$i linestart" "$i lineend"
            }
        }
    }

    drawMap $top -1


    foreach item {wLine1 wLine2} {
        set w $::widgets($top,$item)
        $w configure -state disabled
    }
    update idletasks
    $::widgets($top,wLine2) see 1.0

    normalCursor $top
    showDiff $top 0
    if {$::widgets($top,eqLabel) eq "!"} {
        set ::widgets($top,eqLabel) " "
    }

    cleanupFiles $top
    if {$::diff($top,mode) eq "conflict"} {
        if {$::widgets($top,eqLabel) != "="} {
            makeMergeWin $top
        }
    } elseif {$::diff($top,ancestorFile) ne ""} {
        if {$::widgets($top,eqLabel) != "="} {
            makeMergeWin $top
        }
    }
    if {$::diff($top,printFile) ne ""} {

        after idle "doPrint $top 1 ; cleanupAndExit all"
    }
}

# This is the entrypoint to do a diff via DDE or Send
proc remoteDiff {file1 file2} {
    newDiff $file1 $file2
}

#####################################
# Highlight and navigation stuff
#####################################

# Scroll windows to next/previous diff
proc findDiff {top delta} {
    showDiff $top [expr {$::diff($top,currHighLight) + $delta}]
}

# Scroll a text window to view a certain range, and possibly some
# lines before and after.
proc seeText {w si ei} {
    $w see $ei
    $w see $si
    $w see $si-5lines
    $w see $ei+5lines
    if {[llength [$w bbox $si]] == 0} {
        $w yview $si-5lines
    }
    if {[llength [$w bbox $ei]] == 0} {
        $w yview $si
    }
}

# Highlight a diff
proc highLightChange {top n} {
    if {[info exists ::diff($top,currHighLight)] && \
            $::diff($top,currHighLight) >= 0} {
        $::widgets($top,wLine1) tag configure hl$::diff($top,currHighLight) \
                -background {}
        $::widgets($top,wLine2) tag configure hl$::diff($top,currHighLight) \
                -background {}
    }
    set ::diff($top,currHighLight) $n
    if {$::diff($top,currHighLight) < 0} {
        set ::diff($top,currHighLight) -1
    } elseif {$::diff($top,currHighLight) >= [llength $::diff($top,changes)]} {
        set ::diff($top,currHighLight) [llength $::diff($top,changes)]
    } else {
        $::widgets($top,wLine1) tag configure hl$::diff($top,currHighLight) \
                -background yellow
        $::widgets($top,wLine2) tag configure hl$::diff($top,currHighLight) \
                -background yellow
    }
}

# Highlight a diff and scroll windows to it.
proc showDiff {top num} {


    highLightChange $top $num

    set change [lindex $::diff($top,changes) $::diff($top,currHighLight)]
    set line1 [lindex $change 0]

    if {$::diff($top,currHighLight) < 0} {
        set line1 1.0
        set line2 1.0
    } elseif {$line1 eq ""} {
        set line1 end
        set line2 end
    } else {
        set line2 [expr {$line1 + [lindex $change 1]}]
        incr line1
        set line1 $line1.0
        set line2 $line2.0
    }

    foreach item {wLine1 wDiff1 wLine2 wDiff2} {
        set w $::widgets($top,$item)
        seeText $w $line1 $line2
    }
}

#####################################
# Editing
#####################################

# FIXA: Use snit to adapt text widget instead of using wcb
# include seeText in such a snidget.















# Clear Editing state
proc resetEdit {top} {
    set ::diff($top,leftEdit) 0
    set ::diff($top,rightEdit) 0
    $top.m.mt entryconfigure "Edit Mode" -state normal




    resetEditW $::widgets($top,wDiff1)
    resetEditW $::widgets($top,wDiff2)
}

# Clear Editing state for a Text widget
proc resetEditW {w} {
    $w tag configure padding -background {}
    $w edit reset
    $w configure -undo 0

    set ::diff($w,allowChange) all

    wcb::callback $w before insert {}
    wcb::callback $w before delete {}
}

# Do not allow any editing
proc noEdit {top} {



    noEditW $::widgets($top,wDiff1)
    noEditW $::widgets($top,wDiff2)
}

# Do not allow any editing in a Text widget
proc noEditW {w} {
    set ::diff($w,allowChange) none

    wcb::callback $w before insert [list TextInterceptInsert $w]
    wcb::callback $w before delete [list TextInterceptDelete $w]
}

proc TextInterceptInsert {w ow index str args} {
    if {$::diff($w,allowChange) eq "none"} {
        wcb::cancel
        return
    }
    if {$::diff($w,allowChange) eq "all"} return

    #wcb::cancel - Cancel a widget command
    #wcb::replace - Replace arguments of a widget command with new ones

    # Disallow all new lines
    if {[string first "\n" $str] >= 0} {
        wcb::cancel
        return
    }
    foreach {tag str2} $args {
        if {[string first "\n" $str2] >= 0} {
            wcb::cancel
            return
        }
    }
}

proc TextInterceptDelete {w ow from {to {}}} {
    if {$::diff($w,allowChange) eq "none"} {
        wcb::cancel
        return
    }
    if {$::diff($w,allowChange) eq "all"} return

    if {$to eq ""} {
        set to $from+1char
    }
    set text [$ow get $from $to]
    # Disallow all new lines
    if {[string first "\n" $text] >= 0} {
        wcb::cancel
        return
    }
}

# Turn on editing for a Text widget
proc turnOnEdit {w} {
    $w tag configure padding -background \#f0f0f0
    $w configure -undo 1

    set ::diff($w,allowChange) line
}

# Turn on editing on sides where it has not been disallowed
proc allowEdit {top} {
    $top.m.mt entryconfigure "Edit Mode" -state disable
    if {$::diff($top,leftEdit) == 0} {
        set ::diff($top,leftEdit) 1
        turnOnEdit $::widgets($top,wDiff1)
    }
    if {$::diff($top,rightEdit) == 0} {
        set ::diff($top,rightEdit) 1
        turnOnEdit $::widgets($top,wDiff2)
    }
}

# Turn off editing on sides that do not correspond to a file
proc disallowEdit {top {side 0}} {
    if {$side == 0 || $side == 1} {
        set ::diff($top,leftEdit) -1
    }
    if {$side == 0 || $side == 2} {
        set ::diff($top,rightEdit) -1
    }
    if {$::diff($top,leftEdit) == -1 && $::diff($top,rightEdit) == -1} {
        $top.m.mt entryconfigure "Edit Mode" -state disabled
    }
}

# Ask if editing is allowed on a side
proc mayEdit {top side} {
    if {$side == 1} {
        return [expr {$::diff($top,leftEdit) == 1}]
    } else {
        return [expr {$::diff($top,rightEdit) == 1}]
    }
}

# Start an undo block in a bunch of text widgets
proc startUndoBlock {args} {
    foreach w $args {
        $w configure -autoseparators 0
        # Open up editing for copy functions
        set ::diff($w,allowChange) all
    }
}

# End an undo block in a bunch of text widgets
proc endUndoBlock {args} {
    foreach w $args {
        $w configure -autoseparators 1
        $w edit separator
        set ::diff($w,allowChange) line
    }
}

# Copy a block
proc copyBlock {top from first last} {
    set to [expr {3 - $from}]

    set wfrom $::widgets($top,wDiff$from)
    set wto   $::widgets($top,wDiff$to)

    set tags ""
    set dump [$wfrom dump -all $first.0 $last.end+1c]








|








|








|




|






|








>
>
>
>
>
>
>
>
>





>
>
>
>
>
>
>
>
>
>
>
>
>
>



|
|

|
|

|

>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|





|






|




>
>
>
>
>
>
>

|

|
|

|
|















|
|





|
|
|
|
|
|
|









<


|










>
>
>
>
>
>
>
|
|
|
|
>











|



>

|
|


|
>

>
>
>









|
|
|
>
>
|
|
|


|
|
|


<
>
|


|


|
|

|

|
|

|








|
|



>
>
>
|
>
















|





|
|
>
>
>
>
>


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

















|
|





|















>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>










|
|
|
|














>
>
|
|
|
|
|
|
>







|



|




|
>















|




|
|
|
|
|
|
|

|
|




|
|
|
|

|


|
|
|
|
|

|

|





|
>
>
|

|


|













|
|









>
>
>
>
>
>
>
>
>
>
>
>
>
>



|
|


>
>
>





|
|
|
|

|

|
|




>
>
>





|
|

|
|


|
|



|

















|
|



|




|








|
|
|

|





|
|


|
|







|


|

|







|

|





|
|

|





|
|
|
|





|







1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433

1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509

1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
            if {[regexp {^---\s*(\d*)} $line -> sub]} {
                if {$sub != ""} {
                    set rightLine $sub
                }
                set state right
                continue
            }
            if { ! [regexp {^[\s!+-]} $line]} continue
            lappend leftLines [list $leftLine \
                    [string trim [string range $line 0 1]] \
                    [string range $line 2 end]]
            incr leftLine
            continue
        }
        # We are in the right part of a -c style diff
        if {$state eq "right"} {
            if { ! [regexp {^[\s!+-]} $line]} continue
            lappend rightLines [list $rightLine \
                    [string trim [string range $line 0 1]] \
                    [string range $line 2 end]]
            incr rightLine
            continue
        }
        # We are in a -u style diff
        if {$state eq "both"} {
            if { ! [regexp {^[\s+-]} $line]} continue
            set sig [string trim [string index $line 0]]
            set str [string range $line 1 end]
            if {$sig eq ""} {
                lappend leftLines [list $leftLine "" $str]
                lappend rightLines [list $rightLine "" $str]
                incr leftLine
                incr rightLine
            } elseif {$sig eq "-"} {
                lappend leftLines [list $leftLine "-" $str]
                incr leftLine
            } else {
                lappend rightLines [list $rightLine "+" $str]
                incr rightLine
            }
            continue
        }
    }
    if {$state != "none"} {
        displayOnePatch $top $leftLines $rightLines $leftLine $rightLine
    }
    if {$fname ne ""} {
        foreach side {1 2} {
            emptyLine $top $side
            insertLine $top $side "" $divider patch
            insertLine $top $side "" $fname   patch
            insertLine $top $side "" $divider patch
        }
        addChange $top 4 change 0 0 0 0
    }
}

#####################################
# Main diff
#####################################

proc highlightTabs {top} {
    foreach item {wDiff1 wDiff2} {
        set W $::widgets($top,$item)
        ##nagelfar vartype W _obj,text
        set count {}
        set x [$W search -regexp -all -count count {\t+} 1.0]
        foreach si $x l $count {
            $W tag add tab $si "$si + $l chars"
        }
        $W tag configure tab -background bisque
        $W tag raise tab
    }
}

# Prepare for a diff by creating needed temporary files
proc prepareFiles {top} {
    set ::eskil($top,cleanup) {}
    if {$::eskil($top,mode) eq "rev"} {
        prepareRev $top
        lappend ::eskil($top,cleanup) "rev"
    } elseif {$::eskil($top,mode) eq "conflict"} {
        prepareConflict $top
        lappend ::eskil($top,cleanup) "conflict"
    }
    # Try to autodetect line endings in files
    detectLineEnd $top $::eskil($top,rightFile) righttranslation
    detectLineEnd $top $::eskil($top,leftFile)  lefttranslation
    # Prepare Separator
    set ::eskil($top,separator) \
            [subst -nocommands -novariables $::eskil($top,separatorview)]
    # Autodetect separator before any plugin processing
    if {$::eskil($top,view) eq "table" && $::eskil($top,separator) eq ""} {
        set ch1 [open $::eskil($top,leftFile)]
        if {$::eskil($top,gz)} {
            zlib push gunzip $ch1
        }
        gets $ch1 line1
        close $ch1
        # Any tab, comma or semicolon?
        if {[regsub -all "\t" $line1 "\t" _] >= 2} {
            set ::eskil($top,separator) "\t"
            set ::eskil($top,separatorview) "\\t"
        } elseif {[regsub -all "," $line1 "," _] >= 2} {
            set ::eskil($top,separator) ","
            set ::eskil($top,separatorview) ","
            lappend ::eskil(argv) -sep ","
        } elseif {[regsub -all ";" $line1 ";" _] >= 2} {
            set ::eskil($top,separator) ";"
            set ::eskil($top,separatorview) ";"
            lappend ::eskil(argv) -sep ";"
        }
    }
    # Make it look like it came from command line
    # It could come from the GUI or auto-detect, put it in the command line
    # to make it visible for plugins.
    set i [lsearch -exact $::eskil(argv) "-sep"]
    if {$i >= 0} {
        incr i
        lset ::eskil(argv) $i $::eskil($top,separatorview)
    } else {
        lappend ::eskil(argv) -sep $::eskil($top,separatorview)
    }
    # Prepare plugin, if any
    if {[preparePlugin $top]} {
        set ::eskil($top,cleanup) "plugin $::eskil($top,cleanup)"
    }
}

# Clean up after a diff
proc cleanupFiles {top} {
    foreach keyword $::eskil($top,cleanup) {
        switch $keyword {
            "rev"      {cleanupRev      $top}
            "conflict" {cleanupConflict $top}
            "plugin"   {cleanupPlugin   $top}
        }
    }
    set ::eskil($top,cleanup) {}
}

# Redo Diff command
proc redoDiff {top} {
    if {$::eskil($top,view) eq "table"} {
        # TBD TABLE
        doDiff $top
        # Restore view
        return
    }

    # Note what rows are being displayed
    set W $::widgets($top,wDiff1)

    set width  [winfo width $W]
    set height [winfo height $W]

    set first [$W index @0,0]
    set last  [$W index @[- $width 4],[- $height 4]]

    set first [lindex [split $first .] 0]
    set last  [lindex [split $last  .] 0]

    # Narrow it 5 lines since seeText will try to view 5 lines extra
    incr first 5
    incr last -5
    if {$last < $first} {
        set last $first
    }

    doDiff $top

    # Restore view
    foreach item {wLine1 wDiff1 wLine2 wDiff2} {
        set W $::widgets($top,$item)
        seeText $W $first.0 $last.0
    }
}

# Make an appropriate tail for a window title, depending on mode and files.
proc TitleTail {top} {
    set tail1 [file tail $::eskil($top,rightLabel)]
    set tail2 [file tail $::eskil($top,leftLabel)]
    if {$::eskil($top,mode) ne "" || $tail1 eq $tail2} {
        if {$::eskil($top,mode) eq "rev"} {
            set tail1 [file tail $::eskil($top,RevFile)]
        } elseif {$::eskil($top,mode) eq "conflict"} {
            set tail1 [file tail $::eskil($top,conflictFile)]
        }
        return $tail1
    } else {
        return "$tail2 vs $tail1"
    }
}

# Main diff function.
proc doDiff {top} {

    global doingLine1 doingLine2

    if {$::eskil($top,mode) eq "" && ($::eskil($top,leftOK) == 0 || $::eskil($top,rightOK) == 0)} {
        disableRedo $top
        return
    } else {
        enableRedo $top
    }

    busyCursor $top
    resetEdit $top

    # Clear up everything before starting processing
    if {$::eskil($top,view) eq "table"} {
        set W $::widgets($top,wTable)
        # TBD TABLE
        $W configure -state normal
        $W delete 0 end
        set ::eskil($top,tablechanges) {}
    } else {
        foreach item {wLine1 wDiff1 wLine2 wDiff2 wTb} {
            set W $::widgets($top,$item)
            $W configure -state normal
            $W delete 1.0 end
        }
    }
    clearMap $top
    set ::HighLightCount 0
    highLightChange $top -1
    # Display a star during diff execution, to know when the internal
    # processing starts, and when the label is "valid".
    set ::widgets($top,eqLabel) "*"

    wm title $top "Eskil:"
    update idletasks

    if {$::eskil($top,mode) eq "patch"} {
        disallowEdit $top
        displayPatch $top
        drawMap $top -1
        #drawEditButtons $top
        foreach item {wLine1 wLine2} {
            set W $::widgets($top,$item)
            $W configure -state disabled
        }
        update idletasks
        wm title $top "Eskil: [file tail $::eskil($top,patchFile)]"
        # TBD TABLE
        $::widgets($top,wLine2) see 1.0
        if {$::eskil($top,printFileCmd) && $::eskil($top,printFile) ne ""} {
            after idle "doPrint $top 1 ; cleanupAndExit all"
        }
        normalCursor $top
        return
    } else {
        prepareFiles $top
    }

    wm title $top "Eskil: [TitleTail $top]"

    # Run diff and parse the result.
    set opts $::Pref(ignore)
    if {$::Pref(nocase)} {lappend opts -nocase}
    if {$::Pref(noempty)} {lappend opts -noempty}
    if {$::Pref(pivot) > 0} {lappend opts -pivot $::Pref(pivot)}
    if {$::eskil($top,gz)} {lappend opts -gz}
    if {[info exists ::eskil($top,aligns)] && \
            [llength $::eskil($top,aligns)] > 0} {
        lappend opts -align $::eskil($top,aligns)
    }
    set range {}
    if {[info exists ::eskil($top,range)] && \
            [llength $::eskil($top,range)] == 4} {
        set range $::eskil($top,range)
        lappend opts -range $range
    }

    foreach {RE sub side} [getActivePreprocess $top] {
        lappend opts -regsub$side [list $RE $sub]
    }
    # Apply nodigit after preprocess
    if {$::Pref(nodigit)} {lappend opts -nodigit}

    # If a special file for diffing is present, use it.
    if {[info exists ::eskil($top,leftFileDiff)]} {
        set dFile1 $::eskil($top,leftFileDiff)
    } else {
        set dFile1 $::eskil($top,leftFile)
    }
    if {[info exists ::eskil($top,rightFileDiff)]} {
        set dFile2 $::eskil($top,rightFileDiff)
    } else {
        set dFile2 $::eskil($top,rightFile)
    }

    set differr [catch {DiffUtil::diffFiles {*}$opts \
            $dFile1 $dFile2} diffres]

    # In conflict mode we can use the diff information collected when
    # parsing the conflict file. This makes sure the blocks in the conflict
    # file become change-blocks during merge.
    if {$::eskil($top,mode) eq "conflict" && $::eskil($top,modetype) eq "Pure"} {
        set diffres $::eskil($top,conflictDiff)
    }

    if {$differr != 0} {
        if {$::eskil($top,view) eq "table"} {
            # TBD TABLE
        } else {
            $::widgets($top,wDiff1) insert end $diffres
        }
        normalCursor $top
        return
    }
    if {[llength $diffres] == 0} {
        set ::widgets($top,eqLabel) "="
        # Automatically close if equal
        if {$::eskil(autoclose)} {
            after idle cleanupAndExit $top
            return
        }
    } else {
        set ::widgets($top,eqLabel) " "
    }
    # Update the equal label immediately for better feedback
    update idletasks

    if {$::eskil($top,ancestorFile) ne ""} {
        collectAncestorInfo $top $dFile1 $dFile2 $opts
    }

    set firstview 1

    set ch1 [open $::eskil($top,leftFile)]
    set ch2 [open $::eskil($top,rightFile)]
    if {$::eskil($top,gz)} {
        disallowEdit $top
        zlib push gunzip $ch1
        zlib push gunzip $ch2
    }
    set doingLine1 1
    set doingLine2 1

    if {$::eskil($top,view) eq "table"} {
        # Look for table header line
        set i [lindex $diffres 0]
        lassign $i line1 n1 line2 n2
        if {$line1 == 1 || $line2 == 1} {
            # Hide header line of widget TBD TABLE
            #$::widgets($top,wTable) configure
            # Set up columns??
            $::widgets($top,wTable) configure \
                    -columns "0 Table 0 without 0 header 0 not 0 implemented 0 yet"
        } else {
            # First lines are equal, treat them as header
            # Consume table header line
            gets $ch1 line1
            incr doingLine1
            gets $ch2 line
            incr doingLine2
            set headings [split $line1 $::eskil($top,separator)]
            set columns {}
            foreach heading $headings {
                lappend columns 0 $heading
            }
            $::widgets($top,wTable) configure -columns $columns
            if {$::eskil($top,maxwidth) > 0} {
                set col -1
                foreach {_ _} $columns {
                    incr col
                    $::widgets($top,wTable) columnconfigure $col \
                            -maxwidth $::eskil($top,maxwidth)
                }
            }
        }
    }

    # If there is a range, skip lines up to the range
    if {[llength $range] != 0} {
        disallowEdit $top
        lassign $range start1 end1 start2 end2
        while {$doingLine1 < $start1 && [gets $ch1 line] >= 0} {
            incr doingLine1
        }
        while {$doingLine2 < $start2 && [gets $ch2 line] >= 0} {
            incr doingLine2
        }
    }

    set t 0
    foreach i $diffres {
        lassign $i line1 n1 line2 n2
        doText $top $ch1 $ch2 $n1 $n2 $line1 $line2
        if {$::eskil($top,limitlines) && \
                ($::eskil($top,mapMax) > $::eskil($top,limitlines))} {
            break
        }

        # Get one update when the screen has been filled.
        # Show the first diff.
        if {$firstview && $::eskil($top,mapMax) > 100} {
            set firstview 0
            showDiff $top 0
            update idletasks
        }
    }

    # If there is a range, just display the range
    if {[llength $range] != 0} {
        lassign $range start1 end1 start2 end2
    } else {
        set end1 0
        set end2 0
    }
    doText $top $ch1 $ch2 0 0 $end1 $end2

    if {$::eskil($top,view) ne "table"} {
        # Make sure all text widgets have the same number of lines.
        # The common y scroll doesn't work well if not.
        set max 0.0
        foreach item {wLine1 wDiff1 wLine2 wDiff2} {
            set W $::widgets($top,$item)
            if {[$W index end] > $max} {
                set max [$W index end]
            }
        }
        foreach item {wLine1 wDiff1 wLine2 wDiff2} {
            set W $::widgets($top,$item)
            set d [expr {int($max) - int([$W index end])}]
            for {set t 0} {$t < $d} {incr t} {
                $W insert end \n padding
            }
        }
    }

    close $ch1
    close $ch2

    # We can turn off editing in the text windows after everything
    # is displayed.
    noEdit $top

    # Mark aligned lines TBD TABLE
    if {[info exists ::eskil($top,aligns)] && \
            [llength $::eskil($top,aligns)] > 0} {
        foreach {align1 align2} $::eskil($top,aligns) {
            set i [$::widgets($top,wLine1) search -regexp "\\m$align1\\M" 1.0]
            if {$i != ""} {
                $::widgets($top,wLine1) tag add align \
                        "$i linestart" "$i lineend"
            }
            set i [$::widgets($top,wLine2) search -regexp "\\m$align2\\M" 1.0]
            if {$i != ""} {
                $::widgets($top,wLine2) tag add align \
                        "$i linestart" "$i lineend"
            }
        }
    }

    drawMap $top -1
    #drawEditButtons $top
    if {$::eskil($top,view) ne "table"} {
        foreach item {wLine1 wLine2 wTb} {
            set W $::widgets($top,$item)
            $W configure -state disabled
        }
        update idletasks
        $::widgets($top,wLine2) see 1.0
    }
    normalCursor $top
    showDiff $top 0
    if {$::widgets($top,eqLabel) eq "!"} {
        set ::widgets($top,eqLabel) " "
    }

    cleanupFiles $top
    if {$::eskil($top,mode) eq "conflict"} {
        if {$::widgets($top,eqLabel) != "="} {
            makeMergeWin $top
        }
    } elseif {$::eskil($top,ancestorFile) ne ""} {
        if {$::widgets($top,eqLabel) != "="} {
            makeMergeWin $top
        }
    }
    if {$::eskil($top,printFileCmd) && $::eskil($top,printFile) ne ""} {
        # TBD TABLE
        after idle "doPrint $top 1 ; cleanupAndExit all"
    }
}

# This is the entrypoint to do a diff via DDE or Send
proc remoteDiff {file1 file2} {
    newDiff $file1 $file2
}

#####################################
# Highlight and navigation stuff
#####################################

# Scroll windows to next/previous diff
proc findDiff {top delta} {
    showDiff $top [expr {$::eskil($top,currHighLight) + $delta}]
}

# Scroll a text window to view a certain range, and possibly some
# lines before and after.
proc seeText {W si ei} {
    $W see $ei
    $W see $si
    $W see $si-5lines
    $W see $ei+5lines
    if {[llength [$W bbox $si]] == 0} {
        $W yview $si-5lines
    }
    if {[llength [$W bbox $ei]] == 0} {
        $W yview $si
    }
}

# Highlight a diff
proc highLightChange {top changeIndex} {
    if {[info exists ::eskil($top,currHighLight)] && \
            $::eskil($top,currHighLight) >= 0} {
        $::widgets($top,wLine1) tag configure hl$::eskil($top,currHighLight) \
                -background {}
        $::widgets($top,wLine2) tag configure hl$::eskil($top,currHighLight) \
                -background {}
    }
    set ::eskil($top,currHighLight) $changeIndex
    if {$::eskil($top,currHighLight) < 0} {
        set ::eskil($top,currHighLight) -1
    } elseif {$::eskil($top,currHighLight) >= [llength $::eskil($top,changes)]} {
        set ::eskil($top,currHighLight) [llength $::eskil($top,changes)]
    } else {
        $::widgets($top,wLine1) tag configure hl$::eskil($top,currHighLight) \
                -background yellow
        $::widgets($top,wLine2) tag configure hl$::eskil($top,currHighLight) \
                -background yellow
    }
}

# Highlight a diff and scroll windows to it.
proc showDiff {top changeIndex} {
    # TBD TABLE
    if {$::eskil($top,view) eq "table"} return
    highLightChange $top $changeIndex

    set change [lindex $::eskil($top,changes) $::eskil($top,currHighLight)]
    set line1 [lindex $change 0]

    if {$::eskil($top,currHighLight) < 0} {
        set line1 1.0
        set line2 1.0
    } elseif {$line1 eq ""} {
        set line1 end
        set line2 end
    } else {
        set line2 [expr {$line1 + [lindex $change 1]}]
        incr line1
        set line1 $line1.0
        set line2 $line2.0
    }

    foreach item {wLine1 wDiff1 wLine2 wDiff2} {
        set W $::widgets($top,$item)
        seeText $W $line1 $line2
    }
}

#####################################
# Editing
#####################################

# FIXA: Use snit to adapt text widget instead of using wcb
# include seeText in such a snidget.

# Try to autodetect line endings in file
proc detectLineEnd {top file field {def {}}} {
    set ch [open $file rb]
    set data [read $ch 1000]
    close $ch
    if {[string first \r\n $data] >= 0} {
        set ::eskil($top,$field) crlf
    } elseif {[string first \n $data] >= 0} {
        set ::eskil($top,$field) lf
    } else {
        set ::eskil($top,$field) $def
    }
}

# Clear Editing state
proc resetEdit {top} {
    set ::eskil($top,leftEdit) 0
    set ::eskil($top,rightEdit) 0
    $top.m.mt entryconfigure "Edit Mode" -state normal

    if {$::eskil($top,view) eq "table"} {
        return
    }
    resetEditW $::widgets($top,wDiff1)
    resetEditW $::widgets($top,wDiff2)
}

# Clear Editing state for a Text widget
proc resetEditW {W} {
    $W tag configure padding -background {}
    $W edit reset
    $W configure -undo 0

    set ::eskil($W,allowChange) all

    wcb::callback $W before insert {}
    wcb::callback $W before delete {}
}

# Do not allow any editing
proc noEdit {top} {
    if {$::eskil($top,view) eq "table"} {
        return
    }
    noEditW $::widgets($top,wDiff1)
    noEditW $::widgets($top,wDiff2)
}

# Do not allow any editing in a Text widget
proc noEditW {W} {
    set ::eskil($W,allowChange) none

    wcb::callback $W before insert [list TextInterceptInsert $W]
    wcb::callback $W before delete [list TextInterceptDelete $W]
}

proc TextInterceptInsert {W oW index str args} {
    if {$::eskil($W,allowChange) eq "none"} {
        wcb::cancel
        return
    }
    if {$::eskil($W,allowChange) eq "all"} return

    #wcb::cancel - Cancel a widget command
    #wcb::replace - Replace arguments of a widget command with new ones

    # Disallow all new lines
    if {[string first "\n" $str] >= 0} {
        wcb::cancel
        return
    }
    foreach {tag str2} $args {
        if {[string first "\n" $str2] >= 0} {
            wcb::cancel
            return
        }
    }
}

proc TextInterceptDelete {W oW from {to {}}} {
    if {$::eskil($W,allowChange) eq "none"} {
        wcb::cancel
        return
    }
    if {$::eskil($W,allowChange) eq "all"} return

    if {$to eq ""} {
        set to $from+1char
    }
    set text [$oW get $from $to]
    # Disallow all new lines
    if {[string first "\n" $text] >= 0} {
        wcb::cancel
        return
    }
}

# Turn on editing for a Text widget
proc turnOnEdit {W} {
    $W tag configure padding -background \#f0f0f0
    $W configure -undo 1

    set ::eskil($W,allowChange) line
}

# Turn on editing on sides where it has not been disallowed
proc allowEdit {top} {
    $top.m.mt entryconfigure "Edit Mode" -state disable
    if {$::eskil($top,leftEdit) == 0} {
        set ::eskil($top,leftEdit) 1
        turnOnEdit $::widgets($top,wDiff1)
    }
    if {$::eskil($top,rightEdit) == 0} {
        set ::eskil($top,rightEdit) 1
        turnOnEdit $::widgets($top,wDiff2)
    }
}

# Turn off editing on sides that do not correspond to a file
proc disallowEdit {top {side 0}} {
    if {$side == 0 || $side == 1} {
        set ::eskil($top,leftEdit) -1
    }
    if {$side == 0 || $side == 2} {
        set ::eskil($top,rightEdit) -1
    }
    if {$::eskil($top,leftEdit) == -1 && $::eskil($top,rightEdit) == -1} {
        $top.m.mt entryconfigure "Edit Mode" -state disabled
    }
}

# Ask if editing is allowed on a side
proc mayEdit {top side} {
    if {$side == 1} {
        return [expr {$::eskil($top,leftEdit) == 1}]
    } else {
        return [expr {$::eskil($top,rightEdit) == 1}]
    }
}

# Start an undo block in a bunch of text widgets
proc startUndoBlock {args} {
    foreach W $args {
        $W configure -autoseparators 0
        # Open up editing for copy functions
        set ::eskil($W,allowChange) all
    }
}

# End an undo block in a bunch of text widgets
proc endUndoBlock {args} {
    foreach W $args {
        $W configure -autoseparators 1
        $W edit separator
        set ::eskil($W,allowChange) line
    }
}

# Copy a block
proc copyBlock {top from first last} {
    set to [expr {$from == 1 ? 2 : 1}]

    set wfrom $::widgets($top,wDiff$from)
    set wto   $::widgets($top,wDiff$to)

    set tags ""
    set dump [$wfrom dump -all $first.0 $last.end+1c]

1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801

1802
1803
1804





1805
1806








1807

















1808

1809
1810
1811
1812
1813
1814
1815
1816

1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857

1858
1859
1860
1861
1862
1863
1864
1865

1866
1867
1868

1869
1870
1871
1872
1873


1874
1875
1876
1877

1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890



1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912








1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931


1932
1933
1934
1935
1936
1937
1938







1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061

2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078

2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106











2107
2108
2109
2110
2111
2112
2113


2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184






























































2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221







2222
2223
2224
2225
2226
2227
2228
2229

2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245

2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256

2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
















































































































2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342

2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397




2398
2399


2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
        }
    }
    endUndoBlock $wfrom $wto
}

# Copy a row between text widgets
proc copyRow {top from row} {
    set to [expr {3 - $from}]

    set wfrom $::widgets($top,wDiff$from)
    set wto   $::widgets($top,wDiff$to)

    set text [$wfrom get $row.0 $row.end+1c]

    startUndoBlock $wfrom $wto

    $wto delete $row.0 $row.end+1c
    $wto insert $row.0 $text ""
    # Rewrite the source row to remove any tags
    $wfrom delete $row.0 $row.end+1c
    $wfrom insert $row.0 $text ""

    endUndoBlock $wfrom $wto
}

# Delete a row filling it with padding
proc deleteBlock {top side from {to {}}} {
    set w $::widgets($top,wDiff$side)

    if {$to eq ""} {set to $from}
    startUndoBlock $w
    $w delete $from.0 $to.end+1c
    $w insert $from.0 [string repeat \n [expr {$to - $from + 1}]] padding
    endUndoBlock $w
}

# Get the lines involved in the display
proc getLinesFromRange {w range} {
    set from [lindex $range 0]
    set to   [lindex $range 1]
    lassign [split $from "."] fromr fromi
    lassign [split $to   "."] tor   toi
    if {$toi == 0} {incr tor -1}

    # Get the corresponding lines in the file
    set t [$w get $fromr.0 $tor.end]
    set lines [lsort -integer [regexp -all -inline {\d+} $t]]
    set froml [lindex $lines 0]
    set tol [lindex $lines end]
    return [list $fromr $tor $froml $tol]
}

# Called by popup menus over row numbers to add commands for editing.
# Returns 1 if nothing was added.
proc editMenu {m top n hl x y} {

    if {![mayEdit $top $n]} {return 1}

    # Only copy when in a change block
    if {$hl ne ""} {
        set o [expr {3 - $n}]
        set editOther [mayEdit $top $o]


        set w $::widgets($top,wLine$n)
        set wo $::widgets($top,wLine$o)






        # Get the row that was clicked
        set index [$w index @$x,$y]








        set row [lindex [split $index "."] 0]



















        set line  [regexp -inline {\d+} [$w  get $row.0 $row.end]]
        set lineo [regexp -inline {\d+} [$wo get $row.0 $row.end]]

        # Row copy
        if {$lineo ne ""} {
            $m add command -label "Copy Row from other side" \
                    -command [list copyRow $top $o $row]
        } else {

            $m add command -label "Delete Row" \
                    -command [list deleteBlock $top $n $row]
        }
        if {$line ne "" && $editOther} {
            $m add command -label "Copy Row to other side" \
                    -command [list copyRow $top $n $row]
        }

        # Get ranges for the change block
        set range  [$w tag ranges hl$hl]
        set rangeo [$wo tag ranges hl$hl]

        # Get the lines involved in the block
        lassign [getLinesFromRange $w  $range ] from  to  froml  tol
        lassign [getLinesFromRange $wo $rangeo] fromo too fromlo tolo

        # More than one line in the block?
        set thisSize 0
        set otherSize 0
        if {$froml ne "" && $tol ne ""} {
            set thisSize [expr {$tol - $froml + 1}]
        }
        if {$fromlo ne "" && $tolo ne ""} {
            set otherSize [expr {$tolo - $fromlo + 1}]
        }
        if {$thisSize > 1 || $otherSize > 1} {
            if {$otherSize > 0} {
                $m add command -label "Copy Block from other side" \
                        -command [list copyBlock $top $o $fromo $too]
            } else {
                $m add command -label "Delete Block" \
                        -command [list deleteBlock $top $n $from $to]
            }
            if {$editOther && $thisSize > 0} {
                $m add command -label "Copy Block to other side" \
                        -command [list copyBlock $top $n $from $to]
            }
        }
    }

    $m add command -label "Save File" -command [list saveFile $top $n]


    return 0
}

proc saveFile {top side} {
    if {$side == 1} {
        if {!$::diff($top,leftEdit)} return
        set fileName $::diff($top,leftFile)

    } else {
        if {!$::diff($top,rightEdit)} return
        set fileName $::diff($top,rightFile)

    }

    set w $::widgets($top,wDiff$side)

    # Confirm dialog


    set apa [tk_messageBox -parent $top -icon question \
            -title "Overwrite file" -type yesnocancel -message \
            "Overwriting file [file tail $fileName]\nDo you want to\
            create a backup copy ?"]

    if {$apa eq "yes"} {
        set backup [file rootname $fileName].bak
        if {[catch {file copy -force $fileName $backup} result]} {
            tk_messageBox -parent $top -icon error \
                    -title "File error" -type ok -message \
                    "Error creating backup file $backup:\n$result"
            return
        }
    } elseif {$apa ne "no"} {
        return
    }

    set ch [open $fileName "w"]



    set save 1
    foreach {key value index} [$w dump -all 1.0 end-1c] {
        switch -- $key {
            text {
                if {$save} {
                    puts -nonewline $ch $value
                }
            }
            tagon {
                if {$value eq "padding"} {
                    set save 0
                }
            }
            tagoff {
                if {$value eq "padding"} {
                    set save 1
                }
            }
        }
    }
    close $ch
}









#####################################
# File dialog stuff
#####################################

# Check if a filename is a directory and handle starkits
proc FileIsDirectory {file {kitcheck 0}} {
    # Skip directories
    if {[file isdirectory $file]} {return 1}

    # This detects .kit but how to detect starpacks?
    if {[file extension $file] eq ".kit" || $kitcheck} {
        if {![catch {package require vfs::mk4}]} {
            if {![catch {vfs::mk4::Mount $file $file -readonly}]} {
                # Check for contents to ensure it is a kit
                if {[llength [glob -nocomplain $file/*]] == 0} {
                    vfs::unmount $file
                }
            }


        }
    }
    return [file isdirectory $file]
}

# A wrapper for tk_getOpenFile
proc myOpenFile {args} {







    # When in tutorial mode, make sure the Tcl file dialog is used
    # to be able to access the files in a starkit.
    if {[info exists ::diff(tutorial)] && $::diff(tutorial)} {
        # Only do this if tk_getOpenFile is not a proc.
        if {[info procs tk_getOpenFile] eq ""} {
            # If there is any problem, call the real one
            if {![catch {set res [::tk::dialog::file:: open {*}$args]}]} {
                return $res
            }
        }
    }
    return [tk_getOpenFile {*}$args]
}

proc doOpenLeft {top {forget 0}} {
    if {!$forget && [info exists ::diff($top,leftDir)]} {
        set initDir $::diff($top,leftDir)
    } elseif {[info exists ::diff($top,rightDir)]} {
        set initDir $::diff($top,rightDir)
    } else {
        set initDir [pwd]
    }

    set apa [myOpenFile -title "Select left file" -initialdir $initDir \
            -parent $top]
    if {$apa != ""} {
        set ::diff($top,leftDir) [file dirname $apa]
        set ::diff($top,leftFile) $apa
        set ::diff($top,leftLabel) $apa
        set ::diff($top,leftOK) 1
        return 1
    }
    return 0
}

proc doOpenRight {top {forget 0}} {
    if {!$forget && [info exists ::diff($top,rightDir)]} {
        set initDir $::diff($top,rightDir)
    } elseif {[info exists ::diff($top,leftDir)]} {
        set initDir $::diff($top,leftDir)
    } else {
        set initDir [pwd]
    }

    set apa [myOpenFile -title "Select right file" -initialdir $initDir \
            -parent $top]
    if {$apa != ""} {
        set ::diff($top,rightDir) [file dirname $apa]
        set ::diff($top,rightFile) $apa
        set ::diff($top,rightLabel) $apa
        set ::diff($top,rightOK) 1
        return 1
    }
    return 0
}

proc doOpenAncestor {top} {
    if {$::diff($top,ancestorFile) ne ""} {
        set initDir [file dirname $::diff($top,ancestorFile)]
    } elseif {[info exists ::diff($top,leftDir)]} {
        set initDir $::diff($top,leftDir)
    } elseif {[info exists ::diff($top,rightDir)]} {
        set initDir $::diff($top,rightDir)
    } else {
        set initDir [pwd]
    }
    set apa [myOpenFile -title "Select ancestor file" -initialdir $initDir \
            -parent $top]
    if {$apa != ""} {
        set ::diff($top,ancestorFile) $apa
        return 1
    }
    return 0
}

proc openLeft {top} {
    if {[doOpenLeft $top]} {
        set ::diff($top,mode) ""
        set ::diff($top,mergeFile) ""
        doDiff $top
    }
}

proc openRight {top} {
    if {[doOpenRight $top]} {
        set ::diff($top,mode) ""
        set ::diff($top,mergeFile) ""
        doDiff $top
    }
}

proc openAncestor {top} {
    if {[doOpenAncestor $top]} {
        # Redo diff with ancestor
        doDiff $top
    }
}

proc openConflict {top} {
    global Pref
    if {[doOpenRight $top]} {
        startConflictDiff $top $::diff($top,rightFile)
        set ::diff($top,mergeFile) ""
        doDiff $top
    }
}

proc openPatch {top} {
    global Pref
    if {[doOpenLeft $top]} {
        set ::diff($top,mode) "patch"
        set Pref(ignore) " "
        set Pref(nocase) 0
        set Pref(noempty) 0 
        set ::diff($top,patchFile) $::diff($top,leftFile)
        set ::diff($top,patchData) ""
        doDiff $top
    }
}

# Get data from clipboard and display as a patch.
proc doPastePatch {top} {
    if {[catch {::tk::GetSelection $top CLIPBOARD} sel]} {

        tk_messageBox -icon error -title "Eskil Error" -parent $top \
                -message "Could not retreive clipboard" -type ok
        return
    }
    set ::diff($top,mode) "patch"
    set ::Pref(ignore) " "
    set ::Pref(nocase) 0
    set ::Pref(noempty) 0 
    set ::diff($top,patchFile) ""
    set ::diff($top,patchData) $sel
    doDiff $top
}

proc openRev {top} {
    if {[doOpenRight $top]} {
        set rev [detectRevSystem $::diff($top,rightFile)]
        if {$rev eq ""} {

            tk_messageBox -icon error -title "Eskil Error" -message \
                    "Could not figure out which revison control system\
                    \"$::diff($top,rightFile)\" is under." -type ok
            return
        }
        startRevMode $top $rev $::diff($top,rightFile)
        set ::diff($top,mergeFile) ""
        doDiff $top
    }
}

proc openBoth {top forget} {
    if {[doOpenLeft $top]} {
        if {[doOpenRight $top $forget]} {
            set ::diff($top,mode) ""
            set ::diff($top,mergeFile) ""
            doDiff $top
        }
    }
}

# File drop using TkDnd
proc fileDrop {top side files} {
    # FIXA: Maybe single drop during rev mode should stay in rev mode?
    # Dropping two files mean set both
    if {[llength $files] >= 2} {
        set leftFile [lindex $files 0]
        set rightFile [lindex $files 1]











    } elseif {$side eq "left"} {
        set leftFile [lindex $files 0]
        set rightFile ""
    } else {
        set leftFile ""
        set rightFile [lindex $files 0]
    }


    if {$leftFile ne ""} {
        set ::diff($top,leftDir) [file dirname $leftFile]
        set ::diff($top,leftFile) $leftFile
        set ::diff($top,leftLabel) $leftFile
        set ::diff($top,leftOK) 1
        set ::diff($top,mode) ""
        set ::diff($top,mergeFile) ""
    }
    if {$rightFile ne ""} {
        set ::diff($top,rightDir) [file dirname $rightFile]
        set ::diff($top,rightFile) $rightFile
        set ::diff($top,rightLabel) $rightFile
        set ::diff($top,rightOK) 1
        set ::diff($top,mode) ""
        set ::diff($top,mergeFile) ""
    }
    if {$::diff($top,leftOK) && $::diff($top,rightOK)} {
        doDiff $top
    }
}

#####################################
# GUI stuff
#####################################

# A little helper to make a scrolled window
# It returns the name of the scrolled window
proc Scroll {dir class w args} {
    switch -- $dir {
        both {
            set scrollx 1
            set scrolly 1
        }
        x {
            set scrollx 1
            set scrolly 0
        }
        y {
            set scrollx 0
            set scrolly 1
        }
        default {
            return -code error "Bad scrolldirection \"$dir\""
        }
    }

    ttk::frame $w
    $class $w.s {*}$args

    # Move border properties to frame
    set bw [$w.s cget -borderwidth]
    set relief [$w.s cget -relief]
    $w configure -relief $relief -borderwidth $bw
    $w.s configure -borderwidth 0

    grid $w.s -sticky news

    if {$scrollx} {
        $w.s configure -xscrollcommand [list $w.sbx set]
        scrollbar $w.sbx -orient horizontal -command [list $w.s xview]
        grid $w.sbx -row 1 -sticky we
    }
    if {$scrolly} {
        $w.s configure -yscrollcommand [list $w.sby set]
        scrollbar $w.sby -orient vertical -command [list $w.s yview]
        grid $w.sby -row 0 -column 1 -sticky ns
    }
    grid columnconfigure $w 0 -weight 1
    grid rowconfigure    $w 0 -weight 1

    return $w.s






























































}

################
# Align function
################

proc enableAlign {top} {
    eval $::widgets($top,enableAlignCmd)
}

proc disableAlign {top} {
    eval $::widgets($top,disableAlignCmd)
}

# Remove one or all alignment pairs
proc clearAlign {top {leftline {}}} {
    if {$leftline == ""} {
        set ::diff($top,aligns) {}
    } else {
        set i 0
        while 1 {
            set i [lsearch -integer -start $i $::diff($top,aligns) $leftline]
            if {$i < 0} break
            if {($i % 2) == 0} {
                set ::diff($top,aligns) [lreplace $::diff($top,aligns) \
                        $i [+ $i 1]]
                break
            }
            incr i
        }
    }

    if {[llength $::diff($top,aligns)] == 0} {
        disableAlign $top
    }
}








# Mark a line as aligned.
proc markAlign {top n line text} {
    set ::diff($top,align$n) $line
    set ::diff($top,aligntext$n) $text

    if {[info exists ::diff($top,align1)] && [info exists ::diff($top,align2)]} {
        set level 2
        if {![string equal $::diff($top,aligntext1) $::diff($top,aligntext2)]} {

            set apa [tk_messageBox -icon question -title "Align" -type yesno \
                    -message "Those lines are not equal.\nReally align them?"]
            if {$apa != "yes"} {
                return
            }
            set level 3
        }

        lappend ::diff($top,aligns) $::diff($top,align1) $::diff($top,align2)
        enableAlign $top

        unset ::diff($top,align1)
        unset ::diff($top,align2)
        unset ::diff($top,aligntext1)
        unset ::diff($top,aligntext2)
    }

}

# Called by popup menus over row numbers to add command for alignment.
# Returns 1 if nothing was added.
proc alignMenu {m top n x y} {
    # Get the row that was clicked
    set w $::widgets($top,wLine$n)
    set index [$w index @$x,$y]
    set row [lindex [split $index "."] 0]

    set data [$w get $row.0 $row.end]

    if {![regexp {\d+} $data line]} {
        return 1
    }
    set text [$::widgets($top,wDiff$n) get $row.0 $row.end]

    set other [expr {$n == 1 ? 2 : 1}]
    set cmd [list markAlign $top $n $line $text]
    if {![info exists ::diff($top,align$other)]} {
        set label "Mark line for alignment"
    } else {
        set label "Align with line $::diff($top,align$other) on other side"
    }

    if {[info exists ::diff($top,aligns)]} {
        foreach {align1 align2} $::diff($top,aligns) {
            if {$n == 1 && $line == $align1} {
                set label "Remove alignment with line $align2"
                set cmd [list clearAlign $top $align1]
            } elseif {$n == 2 && $line == $align2} {
                set label "Remove alignment with line $align1"
                set cmd [list clearAlign $top $align1]
            }
        }
    }

    $m add command -label $label -command $cmd

    return 0
}

















































































































###################
# Diff highlighting
###################

proc hlSelect {top hl} {
    highLightChange $top $hl
}

proc hlSeparate {top n hl} {
    set ::diff($top,separate$n) $hl
    set wd $::widgets($top,wDiff$n)
    set wl $::widgets($top,wLine$n)

    if {$hl eq ""} {
        set range [$wd tag ranges sel]
    } else {
        set range [$wl tag ranges hl$::diff($top,separate$n)]
    }
    set text [$wd get {*}$range]
    set ::diff($top,separatetext$n) $text

    # Get the lines involved in the display
    set from [lindex $range 0]
    set to   [lindex $range 1]
    lassign [split $from "."] froml fromi
    lassign [split $to   "."] tol   toi
    if {$toi == 0} {incr tol -1}
    # Get the corresponding lines in the file
    set t [$wl get $froml.0 $tol.end]
    set lines [lsort -integer [regexp -all -inline {\d+} $t]]
    set froml [lindex $lines 0]
    set tol [lindex $lines end]
    set ::diff($top,separatelines$n) [list $froml $tol]

    if {[info exists ::diff($top,separate1)] && \
            [info exists ::diff($top,separate2)]} {
        if {1} {
            cloneDiff $top [concat $::diff($top,separatelines1) \
                    $::diff($top,separatelines2)]
        } else {
            set f1 [tmpFile]
            set f2 [tmpFile]
            set ch [open $f1 w]
            puts $ch $::diff($top,separatetext1)
            close $ch
            set ch [open $f2 w]
            puts $ch $::diff($top,separatetext2)
            close $ch

            newDiff $f1 $f2
        }
        unset ::diff($top,separate1)
        unset ::diff($top,separate2)
    }
}


proc hlPopup {top n hl X Y x y} {
    if {[info exists ::diff($top,nopopup)] && $::diff($top,nopopup)} return
    destroy .lpm
    menu .lpm

    if {![editMenu .lpm $top $n $hl $x $y]} {
        .lpm add separator
    }

    if {$hl != ""} {
        .lpm add command -label "Select" \
                -command [list hlSelect $top $hl]
    }

    set other [expr {$n == 1 ? 2 : 1}]
    if {![info exists ::diff($top,separate$other)]} {
        set label "Mark for Separate Diff"
    } else {
        set label "Separate Diff"
    }

    .lpm add command -label $label -command [list hlSeparate $top $n $hl]
    alignMenu .lpm $top $n $x $y

    set ::diff($top,nopopup) 1
    tk_popup .lpm $X $Y
    after idle [list after 1 [list set ::diff($top,nopopup) 0]]

    return
}

# This is called when right clicking over the line numbers which are not
# marked for changes
proc rowPopup {w X Y x y} {
    set top [winfo toplevel $w]
    if {[info exists ::diff($top,nopopup)] && $::diff($top,nopopup)} return
    destroy .lpm
    menu .lpm

    regexp {(\d+)\D*$} $w -> n
    set tmp1 [editMenu  .lpm $top $n "" $x $y]
    if {!$tmp1} {.lpm add separator}
    set tmp2 [alignMenu .lpm $top $n $x $y]
    if {$tmp1 && $tmp2} {
        # Nothing in the menu
        return
    }
    if {!$tmp1 && $tmp2} {.lpm delete last}

    set ::diff($top,nopopup) 1
    tk_popup .lpm $X $Y
    after idle [list after 1 [list set ::diff($top,nopopup) 0]]
}

proc nextHighlight {top} {




    set tag hl$::HighLightCount
    foreach n {1 2} {


        $::widgets($top,wLine$n) tag bind $tag <ButtonPress-3> \
                "hlPopup $top $n $::HighLightCount %X %Y %x %y ; break"
        $::widgets($top,wLine$n) tag bind $tag <ButtonPress-1> \
                "hlSelect $top $::HighLightCount"
    }
    incr ::HighLightCount
}

#########
# Zooming
#########

proc zoomRow {w X Y x y} {
    global Pref
    set top [winfo toplevel $w]
    # Get the row that was clicked
    set index [$w index @$x,$y]
    set row [lindex [split $index "."] 0]

    # Check if it is selected
    if {[lsearch [$w tag names $index] sel] >= 0} {
        regexp {(\d+)\D*$} $w -> n
        hlPopup $top $n "" $X $Y $x $y
        return
    }

    # Extract the data
    set data(1) [$::widgets($top,wDiff1) dump -tag -text $row.0 $row.end]
    set data(2) [$::widgets($top,wDiff2) dump -tag -text $row.0 $row.end]
    if {[llength $data(1)] == 0 && [llength $data(2)] == 0} return

    set font [$::widgets($top,wDiff1) cget -font]
    set wx $X
    set wy [expr {$Y + 4}]

    destroy $top.balloon
    toplevel $top.balloon -background black
    wm withdraw $top.balloon
    wm overrideredirect $top.balloon 1

    set wid 0
    foreach x {1 2} {
        text $top.balloon.t$x -relief flat -font $font -background \#ffffcc \
            -foreground black -padx 2 -pady 0 -height 1 -wrap word
        $top.balloon.t$x tag configure new1 -foreground $Pref(colornew1) \
                -background $Pref(bgnew1)
        $top.balloon.t$x tag configure change -foreground $Pref(colorchange) \
                -background $Pref(bgchange)
        $top.balloon.t$x tag configure new2 -foreground $Pref(colornew2) \
                -background $Pref(bgnew2)
        $top.balloon.t$x tag configure equal -foreground $Pref(colorequal) \
                -background $Pref(bgequal)
        pack $top.balloon.t$x -side "top" -padx 1 -pady 1 -fill both -expand 1

        set tags {}
        foreach {key value index} $data($x) {
            if {$key eq "tagon"} {
                lappend tags $value
                set tags [lsort -unique $tags]







|



















|


|
|
|
|



|







|








|

|

<
<
|
|

>
|
|

>
>
>
>
>

|
>
>
>
>
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
|
|

|
|
|
|
<
>
|
|
<
|
|
|
|

<
<
<
|

|
|












|
|

|
|


|
|




|
>






|
|
>

|
|
>


|


>
>
|
|
|
|
>













>
>
>

|




















>
>
>
>
>
>
>
>












|
|





>
>


|




>
>
>
>
>
>
>
|

|



|








|
|
|
|







|
|
|
|






|
|
|
|







|
|
|
|






|
|
|
|
|
|






|







|
|






|
|












<

|
|





<

|
|
|
|
|
|







>
|
|


|


|
|
|





|

>
|

|


|
|







|
|












>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
|
|
>
>

|
|
|
|
|
|


|
|
|
|
|
|

|








|

|


















|
|


|
|
|
|

|


|
|
|


|
|
|

|
|

|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

















|



|


|







|




>
>
>
>
>
>
>

|
|
|

|
<
|
>
|
|

|

<


|


|
|
<
<

>




|

|
|


|
>
|


|

|
|
|


|


|
|
|


|






|



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





|
|


|
|
|
|

|


|


|












|

|
|
<
|
|
<
<
<
<
|
<
<
|
<
|
<
|
<
<
|
<
|
>
|
|



|



|

|


|
|





|
|

|

|






|
|
|



|
|
|
|




|

|

|



>
>
>
>

|
>
>
|
|
|









|
<
|

|



|
|
|

















<


|
|
|
|
|
|
|
|
|







1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056


2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105

2106
2107
2108

2109
2110
2111
2112
2113



2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349

2350
2351
2352
2353
2354
2355
2356
2357

2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621

2622
2623
2624
2625
2626
2627
2628

2629
2630
2631
2632
2633
2634
2635


2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827

2828
2829




2830


2831

2832

2833


2834

2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912

2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938

2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
        }
    }
    endUndoBlock $wfrom $wto
}

# Copy a row between text widgets
proc copyRow {top from row} {
    set to [expr {$from == 1 ? 2 : 1}]

    set wfrom $::widgets($top,wDiff$from)
    set wto   $::widgets($top,wDiff$to)

    set text [$wfrom get $row.0 $row.end+1c]

    startUndoBlock $wfrom $wto

    $wto delete $row.0 $row.end+1c
    $wto insert $row.0 $text ""
    # Rewrite the source row to remove any tags
    $wfrom delete $row.0 $row.end+1c
    $wfrom insert $row.0 $text ""

    endUndoBlock $wfrom $wto
}

# Delete a row filling it with padding
proc deleteBlock {top side from {to {}}} {
    set W $::widgets($top,wDiff$side)

    if {$to eq ""} {set to $from}
    startUndoBlock $W
    $W delete $from.0 $to.end+1c
    $W insert $from.0 [string repeat \n [expr {$to - $from + 1}]] padding
    endUndoBlock $W
}

# Get the lines involved in the display
proc getLinesFromRange {W range} {
    set from [lindex $range 0]
    set to   [lindex $range 1]
    lassign [split $from "."] fromr fromi
    lassign [split $to   "."] tor   toi
    if {$toi == 0} {incr tor -1}

    # Get the corresponding lines in the file
    set t [$W get $fromr.0 $tor.end]
    set lines [lsort -integer [regexp -all -inline {\d+} $t]]
    set froml [lindex $lines 0]
    set tol [lindex $lines end]
    return [list $fromr $tor $froml $tol]
}

# Called by popup menus over row numbers to add commands for editing.
# Returns 1 if nothing was added.
proc editMenu {mW top side changeIndex x y} {

    if { ! [mayEdit $top $side]} {return 1}



    set other [expr {$side == 1 ? 2 : 1}]
    set editOther [mayEdit $top $other]

    set dW $::widgets($top,wDiff$side)
    set lW $::widgets($top,wLine$side)
    set oW $::widgets($top,wLine$other)

    set changed 1

    if {$changeIndex eq "_"} {
        # The popup is on unchanged line numbers
        set changed 0
        # Get the row that was clicked
        set index [$lW index @$x,$y]
        set row [lindex [split $index "."] 0]
        # Range is that row
        set range  [list $row.0 $row.end]
        set rangeo [list $row.0 $row.end]
    } elseif {$changeIndex eq ""} {
        # The popup is on selected text.
        # Get the row that was clicked
        set index [$dW index @$x,$y]
        set row [lindex [split $index "."] 0]
        # Figure out the rows involved in the selection.
        set range [$dW tag ranges sel]
        set from [lindex $range 0]
        set to   [lindex $range 1]
        lassign [split $from "."] froml fromi
        lassign [split $to   "."] tol   toi
        if {$toi == 0} {incr tol -1}
        set range  [list $froml.0 $tol.end]
        set rangeo [list $froml.0 $tol.end]
    } else {
        # The popup is on a change block in line numbers
        # Get the row that was clicked
        set index [$lW index @$x,$y]
        set row [lindex [split $index "."] 0]
        # Get ranges for the change block
        set range  [$lW tag ranges hl$changeIndex]
        set rangeo [$oW tag ranges hl$changeIndex]
    }

    set line  [regexp -inline {\d+} [$lW get $row.0 $row.end]]
    set lineo [regexp -inline {\d+} [$oW get $row.0 $row.end]]

    # Row copy
    if {$lineo ne ""} {
        $mW add command -label "Copy Row from other side" \
                -command [list copyRow $top $other $row]

    }
    $mW add command -label "Delete Row" \
            -command [list deleteBlock $top $side $row]

    if {$line ne "" && $editOther && $changed} {
        $mW add command -label "Copy Row to other side" \
                -command [list copyRow $top $side $row]
    }




    if {$changed} {
        # Get the lines involved in the block
        lassign [getLinesFromRange $lW $range ] from  to  froml  tol
        lassign [getLinesFromRange $oW $rangeo] fromo too fromlo tolo

        # More than one line in the block?
        set thisSize 0
        set otherSize 0
        if {$froml ne "" && $tol ne ""} {
            set thisSize [expr {$tol - $froml + 1}]
        }
        if {$fromlo ne "" && $tolo ne ""} {
            set otherSize [expr {$tolo - $fromlo + 1}]
        }
        if {$thisSize > 1 || $otherSize > 1} {
            if {$otherSize > 0} {
                $mW add command -label "Copy Block from other side" \
                        -command [list copyBlock $top $other $fromo $too]
            } else {
                $mW add command -label "Delete Block" \
                        -command [list deleteBlock $top $side $from $to]
            }
            if {$editOther && $thisSize > 0} {
                $mW add command -label "Copy Block to other side" \
                        -command [list copyBlock $top $side $from $to]
            }
        }
    }

    $mW add command -label "Save File" -command [list saveFile $top $side]
    $mW add command -label "Save File, Reload" -command [list saveFileR $top $side]

    return 0
}

proc saveFile {top side} {
    if {$side == 1} {
        if { ! $::eskil($top,leftEdit)} return
        set fileName $::eskil($top,leftFile)
        set trans $::eskil($top,lefttranslation)
    } else {
        if { ! $::eskil($top,rightEdit)} return
        set fileName $::eskil($top,rightFile)
        set trans $::eskil($top,righttranslation)
    }

    set W $::widgets($top,wDiff$side)

    # Confirm dialog
    set apa no
    if {$::Pref(askOverwrite)} {
        set apa [tk_messageBox -parent $top -icon question \
                -title "Overwrite file" -type yesnocancel -message \
                "Overwriting file [file tail $fileName]\nDo you want to\
                create a backup copy ?"]
    }
    if {$apa eq "yes"} {
        set backup [file rootname $fileName].bak
        if {[catch {file copy -force $fileName $backup} result]} {
            tk_messageBox -parent $top -icon error \
                    -title "File error" -type ok -message \
                    "Error creating backup file $backup:\n$result"
            return
        }
    } elseif {$apa ne "no"} {
        return
    }

    set ch [open $fileName "w"]
    if {$trans ne ""} {
        fconfigure $ch -translation $trans
    }
    set save 1
    foreach {key value index} [$W dump -all 1.0 end-1c] {
        switch -- $key {
            text {
                if {$save} {
                    puts -nonewline $ch $value
                }
            }
            tagon {
                if {$value eq "padding"} {
                    set save 0
                }
            }
            tagoff {
                if {$value eq "padding"} {
                    set save 1
                }
            }
        }
    }
    close $ch
}

# Save file and reload
proc saveFileR {top side} {
    saveFile $top $side
    # Redo
    redoDiff $top
    allowEdit $top
}

#####################################
# File dialog stuff
#####################################

# Check if a filename is a directory and handle starkits
proc FileIsDirectory {file {kitcheck 0}} {
    # Skip directories
    if {[file isdirectory $file]} {return 1}

    # This detects .kit but how to detect starpacks?
    if {[file extension $file] eq ".kit" || $kitcheck} {
        if { ! [catch {package require vfs::mk4}]} {
            if { ! [catch {vfs::mk4::Mount $file $file -readonly}]} {
                # Check for contents to ensure it is a kit
                if {[llength [glob -nocomplain $file/*]] == 0} {
                    vfs::unmount $file
                }
            }
            # Now it is possible that the isdirectory status has changed
            return [file isdirectory $file]
        }
    }
    return 0
}

# A wrapper for tk_getOpenFile
proc myOpenFile {args} {
    array set opts $args
    set isVfs 0
    if {[info exists opts(-initialdir)]} {
        if {[string match tclvfs* [file system $opts(-initialdir)]]} {
            set isVfs 1
        }
    }
    # When in a vfs, make sure the Tcl file dialog is used
    # to be able to access the files in a starkit.
    if {$isVfs} {
        # Only do this if tk_getOpenFile is not a proc.
        if {[info procs tk_getOpenFile] eq ""} {
            # If there is any problem, call the real one
            if { ! [catch {set res [::tk::dialog::file:: open {*}$args]}]} {
                return $res
            }
        }
    }
    return [tk_getOpenFile {*}$args]
}

proc doOpenLeft {top {forget 0}} {
    if { ! $forget && [info exists ::eskil($top,leftDir)]} {
        set initDir $::eskil($top,leftDir)
    } elseif {[info exists ::eskil($top,rightDir)]} {
        set initDir $::eskil($top,rightDir)
    } else {
        set initDir [pwd]
    }

    set apa [myOpenFile -title "Select left file" -initialdir $initDir \
            -parent $top]
    if {$apa != ""} {
        set ::eskil($top,leftDir) [file dirname $apa]
        set ::eskil($top,leftFile) $apa
        set ::eskil($top,leftLabel) $apa
        set ::eskil($top,leftOK) 1
        return 1
    }
    return 0
}

proc doOpenRight {top {forget 0}} {
    if { ! $forget && [info exists ::eskil($top,rightDir)]} {
        set initDir $::eskil($top,rightDir)
    } elseif {[info exists ::eskil($top,leftDir)]} {
        set initDir $::eskil($top,leftDir)
    } else {
        set initDir [pwd]
    }

    set apa [myOpenFile -title "Select right file" -initialdir $initDir \
            -parent $top]
    if {$apa != ""} {
        set ::eskil($top,rightDir) [file dirname $apa]
        set ::eskil($top,rightFile) $apa
        set ::eskil($top,rightLabel) $apa
        set ::eskil($top,rightOK) 1
        return 1
    }
    return 0
}

proc doOpenAncestor {top} {
    if {$::eskil($top,ancestorFile) ne ""} {
        set initDir [file dirname $::eskil($top,ancestorFile)]
    } elseif {[info exists ::eskil($top,leftDir)]} {
        set initDir $::eskil($top,leftDir)
    } elseif {[info exists ::eskil($top,rightDir)]} {
        set initDir $::eskil($top,rightDir)
    } else {
        set initDir [pwd]
    }
    set apa [myOpenFile -title "Select ancestor file" -initialdir $initDir \
            -parent $top]
    if {$apa != ""} {
        set ::eskil($top,ancestorFile) $apa
        return 1
    }
    return 0
}

proc openLeft {top} {
    if {[doOpenLeft $top]} {
        set ::eskil($top,mode) ""
        set ::eskil($top,mergeFile) ""
        doDiff $top
    }
}

proc openRight {top} {
    if {[doOpenRight $top]} {
        set ::eskil($top,mode) ""
        set ::eskil($top,mergeFile) ""
        doDiff $top
    }
}

proc openAncestor {top} {
    if {[doOpenAncestor $top]} {
        # Redo diff with ancestor
        doDiff $top
    }
}

proc openConflict {top} {

    if {[doOpenRight $top]} {
        startConflictDiff $top $::eskil($top,rightFile)
        set ::eskil($top,mergeFile) ""
        doDiff $top
    }
}

proc openPatch {top} {

    if {[doOpenLeft $top]} {
        set ::eskil($top,mode) "patch"
        set ::Pref(ignore) " "
        set ::Pref(nocase) 0
        set ::Pref(noempty) 0
        set ::eskil($top,patchFile) $::eskil($top,leftFile)
        set ::eskil($top,patchData) ""
        doDiff $top
    }
}

# Get data from clipboard and display as a patch.
proc doPastePatch {top} {
    if {[catch {::tk::GetSelection $top CLIPBOARD} sel]} {
        tk_messageBox -parent $top -icon error \
                -title "Eskil Error" -type ok \
                -message "Could not retreive clipboard"
        return
    }
    set ::eskil($top,mode) "patch"
    set ::Pref(ignore) " "
    set ::Pref(nocase) 0
    set ::Pref(noempty) 0
    set ::eskil($top,patchFile) ""
    set ::eskil($top,patchData) $sel
    doDiff $top
}

proc openRev {top} {
    if {[doOpenRight $top]} {
        set rev [detectRevSystem $::eskil($top,rightFile)]
        if {$rev eq ""} {
            tk_messageBox -parent $top -icon error \
                    -title "Eskil Error" -type ok -message \
                    "Could not figure out which revison control system\
                    \"$::eskil($top,rightFile)\" is under."
            return
        }
        startRevMode $top $rev $::eskil($top,rightFile)
        set ::eskil($top,mergeFile) ""
        doDiff $top
    }
}

proc openBoth {top forget} {
    if {[doOpenLeft $top]} {
        if {[doOpenRight $top $forget]} {
            set ::eskil($top,mode) ""
            set ::eskil($top,mergeFile) ""
            doDiff $top
        }
    }
}

# File drop using TkDnd
proc fileDrop {top side files} {
    # FIXA: Maybe single drop during rev mode should stay in rev mode?
    # Dropping two files mean set both
    if {[llength $files] >= 2} {
        set leftFile [lindex $files 0]
        set rightFile [lindex $files 1]
    } else {
        if {$side eq "any"} {
            # Dropped outside the text widgets. Try to be clever.
            if { ! [info exists ::eskil($top,lastDrop)]} {
                set side left
            } elseif {$::eskil($top,lastDrop) eq "left"} {
                set side right
            } else {
                set side left
            }
        }
        if {$side eq "left"} {
            set leftFile [lindex $files 0]
            set rightFile ""
        } else {
            set leftFile ""
            set rightFile [lindex $files 0]
        }
        set ::eskil($top,lastDrop) $side
    }
    if {$leftFile ne ""} {
        set ::eskil($top,leftDir) [file dirname $leftFile]
        set ::eskil($top,leftFile) $leftFile
        set ::eskil($top,leftLabel) $leftFile
        set ::eskil($top,leftOK) 1
        set ::eskil($top,mode) ""
        set ::eskil($top,mergeFile) ""
    }
    if {$rightFile ne ""} {
        set ::eskil($top,rightDir) [file dirname $rightFile]
        set ::eskil($top,rightFile) $rightFile
        set ::eskil($top,rightLabel) $rightFile
        set ::eskil($top,rightOK) 1
        set ::eskil($top,mode) ""
        set ::eskil($top,mergeFile) ""
    }
    if {$::eskil($top,leftOK) && $::eskil($top,rightOK)} {
        doDiff $top
    }
}

#####################################
# GUI stuff
#####################################

# A little helper to make a window with scrollbars
# It returns the name of the scrolled window
proc Scroll {dir class W args} {
    switch -- $dir {
        both {
            set scrollx 1
            set scrolly 1
        }
        x {
            set scrollx 1
            set scrolly 0
        }
        y {
            set scrollx 0
            set scrolly 1
        }
        default {
            return -code error "Bad scrolldirection \"$dir\""
        }
    }

    ttk::frame $W
    $class $W.s {*}$args

    # Move border properties to frame
    set bw [$W.s cget -borderwidth]
    set relief [$W.s cget -relief]
    $W configure -relief $relief -borderwidth $bw
    $W.s configure -borderwidth 0

    grid $W.s -sticky news

    if {$scrollx} {
        $W.s configure -xscrollcommand [list $W.sbx set]
        ttk::scrollbar $W.sbx -orient horizontal -command [list $W.s xview]
        grid $W.sbx -row 1 -sticky we
    }
    if {$scrolly} {
        $W.s configure -yscrollcommand [list $W.sby set]
        ttk::scrollbar $W.sby -orient vertical -command [list $W.s yview]
        grid $W.sby -row 0 -column 1 -sticky ns
    }
    grid columnconfigure $W 0 -weight 1
    grid rowconfigure    $W 0 -weight 1

    return $W.s
}

# Rearrange a dynamic grid to a specified number of columns
proc DynGridRearrange {W cols} {
    # Go down columns first. Thus we must know how many rows there will be.
    set children [grid slaves $W._dyn]
    set rows [expr {([llength $children] + $cols - 1) / $cols}]
    set row 0
    set col 0
    foreach child $children {
        grid $child -row $row -column $col
        grid columnconfigure $W._dyn $col -uniform a
        incr row
        if {$row >= $rows} {
            incr col
            set row 0
        }
    }
    # Clear other columns from uniform in case we shrunk
    if {$row != 0} {
        incr col
    }
    for {} {$col < 15} {incr col} {
        grid columnconfigure $W._dyn $col -uniform ""
    }
    # Recalculate
    update idletasks
    # Propagate Height
    set height [winfo reqheight $W._dyn]
    $W configure -width 100 -height $height
}

# Update dynamic grid on configure event
proc DynGridRedo {W} {
    set maxW 0
    set children [grid slaves $W._dyn]
    foreach child $children {
        set maxW [expr {max($maxW,[winfo reqwidth $child])}]
    }
    set fW [winfo width $W]
    set cols [expr {max(1,$fW / $maxW)}]
    # Rerrange if needed
    lassign [grid size $W._dyn] mCols mRows
    if {$mCols != $cols} {
        DynGridRearrange $W $cols
    }
}

# Ask for widget to have its children managed by dynGrid.
proc dynGridManage {W} {
    # Limit its inital requirements
    pack propagate $W 0
    $W configure -width 100 -height 10
    set children [winfo children $W]
    # Add an inner frame
    ttk::frame $W._dyn
    lower $W._dyn
    pack $W._dyn -fill both -expand 1
    # Get all children managed
    grid {*}$children -in $W._dyn -padx 3 -pady 3 -sticky w
    # React
    bind $W <Configure> "DynGridRedo $W"
}

################
# Align function
################

proc enableAlign {top} {
    eval $::widgets($top,enableAlignCmd)
}

proc disableAlign {top} {
    eval $::widgets($top,disableAlignCmd)
}

# Remove one or all alignment pairs
proc clearAlign {top {leftline {}}} {
    if {$leftline == ""} {
        set ::eskil($top,aligns) {}
    } else {
        set i 0
        while 1 {
            set i [lsearch -integer -start $i $::eskil($top,aligns) $leftline]
            if {$i < 0} break
            if {($i % 2) == 0} {
                set ::eskil($top,aligns) [lreplace $::eskil($top,aligns) \
                        $i [+ $i 1]]
                break
            }
            incr i
        }
    }

    if {[llength $::eskil($top,aligns)] == 0} {
        disableAlign $top
    }
}

proc NoMarkAlign {top} {
    unset -nocomplain ::eskil($top,align1)
    unset -nocomplain ::eskil($top,align2)
    unset -nocomplain ::eskil($top,aligntext1)
    unset -nocomplain ::eskil($top,aligntext2)
}

# Mark a line as aligned.
proc markAlign {top side line text} {
    set ::eskil($top,align$side) $line
    set ::eskil($top,aligntext$side) $text

    if {[info exists ::eskil($top,align1)] && [info exists ::eskil($top,align2)]} {

        if { ! [string equal $::eskil($top,aligntext1) $::eskil($top,aligntext2)]} {
            set apa [tk_messageBox -parent $top -icon question \
                    -title "Align" -type yesno -message \
                    "Those lines are not equal.\nReally align them?"]
            if {$apa != "yes"} {
                return 0
            }

        }

        lappend ::eskil($top,aligns) $::eskil($top,align1) $::eskil($top,align2)
        enableAlign $top

        NoMarkAlign $top
        return 1


    }
    return 0
}

# Called by popup menus over row numbers to add command for alignment.
# Returns 1 if nothing was added.
proc alignMenu {mW top side x y} {
    # Get the row that was clicked
    set W $::widgets($top,wLine$side)
    set index [$W index @$x,$y]
    set row [lindex [split $index "."] 0]

    set data [$W get $row.0 $row.end]
    # Must be a line number
    if { ! [regexp {\d+} $data line]} {
        return 1
    }
    set text [$::widgets($top,wDiff$side) get $row.0 $row.end]

    set other [expr {$side == 1 ? 2 : 1}]
    set cmd [list markAlign $top $side $line $text]
    if { ! [info exists ::eskil($top,align$other)]} {
        set label "Mark line for alignment"
    } else {
        set label "Align with line $::eskil($top,align$other) on other side"
    }

    if {[info exists ::eskil($top,aligns)]} {
        foreach {align1 align2} $::eskil($top,aligns) {
            if {$side == 1 && $line == $align1} {
                set label "Remove alignment with line $align2"
                set cmd [list clearAlign $top $align1]
            } elseif {$side == 2 && $line == $align2} {
                set label "Remove alignment with line $align1"
                set cmd [list clearAlign $top $align1]
            }
        }
    }

    $mW add command -label $label -command $cmd

    return 0
}

# Set up bindings to allow setting alignment using drag
proc SetupAlignDrag {top left right} {
    bind $left <ButtonPress-1> [list startAlignDrag $top 1 %x %y %X %Y]\;break
    bind $left <B1-Motion> [list motionAlignDrag $top 1 0 %x %y %X %Y]\;break
    bind $left <Shift-B1-Motion> [list motionAlignDrag $top 1 1 %x %y %X %Y]\;break
    bind $left <ButtonRelease-1> [list endAlignDrag $top 1 %x %y %X %Y]\;break
    bind $left <B1-Leave> break
    bind $right <ButtonPress-1> [list startAlignDrag $top 2 %x %y %X %Y]\;break
    bind $right <B1-Motion> [list motionAlignDrag $top 2 0 %x %y %X %Y]\;break
    bind $right <Shift-B1-Motion> [list motionAlignDrag $top 2 1 %x %y %X %Y]\;break
    bind $right <ButtonRelease-1> [list endAlignDrag $top 2 %x %y %X %Y]\;break
    bind $right <B1-Leave> break
}

# Button has been pressed over line window
proc startAlignDrag {top side x y X Y} {
    # Get the row that was clicked
    set W $::widgets($top,wLine$side)
    set index [$W index @$x,$y]
    set row [lindex [split $index "."] 0]

    set data [$W get $row.0 $row.end]
    set ::eskil($top,alignDrag,state) none
    # Must be a line number
    if { ! [regexp {\d+} $data line]} {
        return 1
    }
    # Set up information about start of drag
    set text [$::widgets($top,wDiff$side) get $row.0 $row.end]
    set other [expr {$side == 1 ? 2 : 1}]
    set ::eskil($top,alignDrag,X) $X
    set ::eskil($top,alignDrag,Y) $Y
    set ::eskil($top,alignDrag,from) $side
    set ::eskil($top,alignDrag,line$side) $line
    set ::eskil($top,alignDrag,text$side) $text
    set ::eskil($top,alignDrag,line$other) "?"
    set ::eskil($top,alignDrag,state) press
}

# Mouse moves with button down
proc motionAlignDrag {top side shift x y X Y} {
    if {$::eskil($top,alignDrag,state) eq "press"} {
        # Have we moved enough to call it dragging?
        set dX [expr {abs($X - $::eskil($top,alignDrag,X))}]
        set dY [expr {abs($Y - $::eskil($top,alignDrag,Y))}]
        if {$dX + $dY > 3} {
            # Start a drag action
            set W $top.alignDrag
            destroy $W
            toplevel $W
            wm overrideredirect $W 1
            label $W.l -borderwidth 1 -relief solid -justify left
            pack $W.l
            set ::eskil($top,alignDrag,W) $W
            set ::eskil($top,alignDrag,state) "drag"
        }
    }
    if {$::eskil($top,alignDrag,state) eq "drag"} {
        set W $::eskil($top,alignDrag,W)
        # Move drag label with cursor
        wm geometry $W +[expr {$X + 1}]+[expr {$Y + 1}]

        set n $::eskil($top,alignDrag,from)
        set other [expr {$side == 1 ? 2 : 1}]
        set w2 $::widgets($top,wLine$other)
        # Are we over the other line window?
        if {[winfo containing $X $Y] eq $w2} {
            set x [expr {$X - [winfo rootx $w2]}]
            set y [expr {$Y - [winfo rooty $w2]}]
            set index [$w2 index @$x,$y]
            set row [lindex [split $index "."] 0]
            set data [$w2 get $row.0 $row.end]
            if { ! [regexp {\d+} $data line]} {
                set ::eskil($top,alignDrag,line$other) "?"
            } else {
                set ::eskil($top,alignDrag,line$other) $line
                set text [$::widgets($top,wDiff$other) get $row.0 $row.end]
                set ::eskil($top,alignDrag,text$other) $text
            }
        } else {
            set ::eskil($top,alignDrag,line$other) "?"
        }
        set txt "Align Left $::eskil($top,alignDrag,line1)"
        append txt "\nwith Right $::eskil($top,alignDrag,line2)"
        set ::eskil($top,alignDrag,shift) $shift
        if {$shift} {
            append txt "\nAnd Redo Diff"
        }
        $W.l configure -text $txt
    }
}

# Button has been released
proc endAlignDrag {top side x y X Y} {
    if {$::eskil($top,alignDrag,state) eq "drag"} {
        destroy $::eskil($top,alignDrag,W)
        # Are both line numbers valid? I.e. is this a full align operation?
        if {$::eskil($top,alignDrag,line1) ne "?" && \
                $::eskil($top,alignDrag,line2) ne "?"} {
            NoMarkAlign $top
            markAlign $top 1 $::eskil($top,alignDrag,line1) \
                    $::eskil($top,alignDrag,text1)
            set marked [markAlign $top 2 $::eskil($top,alignDrag,line2) \
                    $::eskil($top,alignDrag,text2)]
            if {$::eskil($top,alignDrag,shift) && $marked} {
                redoDiff $top
            }
        }
    }
    set ::eskil($top,alignDrag,state) none
}

###################
# Diff highlighting
###################

proc hlSelect {top changeIndex} {
    highLightChange $top $changeIndex
}

proc hlSeparate {top side changeIndex} {
    set ::eskil($top,separate$side) $changeIndex
    set wd $::widgets($top,wDiff$side)
    set wl $::widgets($top,wLine$side)

    if {$changeIndex eq ""} {
        set range [$wd tag ranges sel]
    } else {
        set range [$wl tag ranges hl$::eskil($top,separate$side)]
    }
    set text [$wd get {*}$range]
    set ::eskil($top,separatetext$side) $text

    # Get the lines involved in the display
    set from [lindex $range 0]
    set to   [lindex $range 1]
    lassign [split $from "."] froml fromi
    lassign [split $to   "."] tol   toi
    if {$toi == 0} {incr tol -1}
    # Get the corresponding lines in the file
    set t [$wl get $froml.0 $tol.end]
    set lines [lsort -integer [regexp -all -inline {\d+} $t]]
    set froml [lindex $lines 0]
    set tol [lindex $lines end]
    set ::eskil($top,separatelines$side) [list $froml $tol]

    if {[info exists ::eskil($top,separate1)] && \
            [info exists ::eskil($top,separate2)]} {

        cloneDiff $top [concat $::eskil($top,separatelines1) \
                               $::eskil($top,separatelines2)]




        unset ::eskil($top,separate1)


        unset ::eskil($top,separate2)

    }

}




# No changeIndex means that the popup is over selected text rather than
# line numbers.
proc hlPopup {top side changeIndex X Y x y} {
    if {[info exists ::eskil($top,nopopup)] && $::eskil($top,nopopup)} return
    destroy .lpm
    menu .lpm

    if { ! [editMenu .lpm $top $side $changeIndex $x $y]} {
        .lpm add separator
    }

    if {$changeIndex != ""} {
        .lpm add command -label "Select" \
                -command [list hlSelect $top $changeIndex]
    }

    set other [expr {$side == 1 ? 2 : 1}]
    if { ! [info exists ::eskil($top,separate$other)]} {
        set label "Mark for Separate Diff"
    } else {
        set label "Separate Diff"
    }

    .lpm add command -label $label -command [list hlSeparate $top $side $changeIndex]
    alignMenu .lpm $top $side $x $y

    set ::eskil($top,nopopup) 1
    tk_popup .lpm $X $Y
    after idle [list after 1 [list set "::eskil($top,nopopup)" 0]]

    return
}

# This is called when right clicking over the line numbers which are not
# marked for changes
proc rowPopup {W X Y x y} {
    set top [winfo toplevel $W]
    if {[info exists ::eskil($top,nopopup)] && $::eskil($top,nopopup)} return
    destroy .lpm
    menu .lpm

    regexp {(\d+)\D*$} $W -> side
    set tmp1 [editMenu  .lpm $top $side "_" $x $y]
    if { ! $tmp1} {.lpm add separator}
    set tmp2 [alignMenu .lpm $top $side $x $y]
    if {$tmp1 && $tmp2} {
        # Nothing in the menu
        return
    }
    if { ! $tmp1 && $tmp2} {.lpm delete last}

    set ::eskil($top,nopopup) 1
    tk_popup .lpm $X $Y
    after idle [list after 1 [list set "::eskil($top,nopopup)" 0]]
}

proc nextHighlight {top} {
    # TBD TABLE, stop for now?
    if {$::eskil($top,view) eq "table"} {
        return
    }
    set tag hl$::HighLightCount
    foreach side {1 2} {
        set W $::widgets($top,wLine$side)
        ##nagelfar vartype W _obj,text
        $W tag bind $tag <ButtonPress-3> \
                "hlPopup $top $side $::HighLightCount %X %Y %x %y ; break"
        $W tag bind $tag <ButtonPress-1> \
                "hlSelect $top $::HighLightCount"
    }
    incr ::HighLightCount
}

#########
# Zooming
#########

proc zoomRow {W X Y x y} {

    set top [winfo toplevel $W]
    # Get the row that was clicked
    set index [$W index @$x,$y]
    set row [lindex [split $index "."] 0]

    # Check if it is selected
    if {[lsearch [$W tag names $index] sel] >= 0} {
        regexp {(\d+)\D*$} $W -> side
        hlPopup $top $side "" $X $Y $x $y
        return
    }

    # Extract the data
    set data(1) [$::widgets($top,wDiff1) dump -tag -text $row.0 $row.end]
    set data(2) [$::widgets($top,wDiff2) dump -tag -text $row.0 $row.end]
    if {[llength $data(1)] == 0 && [llength $data(2)] == 0} return

    set font [$::widgets($top,wDiff1) cget -font]
    set wx $X
    set wy [expr {$Y + 4}]

    destroy $top.balloon
    toplevel $top.balloon -background black
    wm withdraw $top.balloon
    wm overrideredirect $top.balloon 1


    foreach x {1 2} {
        text $top.balloon.t$x -relief flat -font $font -background \#ffffcc \
            -foreground black -padx 2 -pady 0 -height 1
        $top.balloon.t$x tag configure new1 -foreground $::Pref(colornew1) \
                -background $::Pref(bgnew1)
        $top.balloon.t$x tag configure change -foreground $::Pref(colorchange) \
                -background $::Pref(bgchange)
        $top.balloon.t$x tag configure new2 -foreground $::Pref(colornew2) \
                -background $::Pref(bgnew2)
        $top.balloon.t$x tag configure equal -foreground $::Pref(colorequal) \
                -background $::Pref(bgequal)
        pack $top.balloon.t$x -side "top" -padx 1 -pady 1 -fill both -expand 1

        set tags {}
        foreach {key value index} $data($x) {
            if {$key eq "tagon"} {
                lappend tags $value
                set tags [lsort -unique $tags]
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493

2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512






























































2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525

2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563




2564



2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590


2591
2592
2593


2594
2595


2596





2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612

2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647

2648
2649





























2650
2651

2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670




2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
        $top.balloon.t$x configure -width [string length $text]
    }

    # Let geometry requests propagate
    update idletasks

    # Is the balloon within the diff window?
    set wid [winfo reqwidth $top.balloon]
    if {$wid + $wx > [winfo rootx $top] + [winfo width $top]} {
        # No.
        # Center on diff window
        set wx [expr {([winfo width $top] - $wid) / 2 + [winfo rootx $top]}]
        if {$wx < 0} {set wx 0}
        # Is the balloon not within the screen?
        if {$wx + $wid > [winfo screenwidth $top]} {
            # Center in screen
            set wx [expr {([winfo screenwidth $top] - $wid) / 2}]
            if {$wx < 0} {set wx 0}
        }
    }

    # Does the balloon fit within the screen?
    if {$wid > [winfo screenwidth $top]} {
        # How many rows does it take?

        set rows [expr {ceil(double($wid) / [winfo screenwidth $top])}]
        # Add rows and fill screen width
        $top.balloon.t1 configure -height $rows
        $top.balloon.t2 configure -height $rows
        # Let geometry requests propagate
        update idletasks
        wm geometry $top.balloon \
                [winfo screenwidth $top]x[winfo reqheight $top.balloon]
        set wx 0
    }
    wm geometry $top.balloon +$wx+$wy
    wm deiconify $top.balloon
}

proc unzoomRow {w} {
    set top [winfo toplevel $w]
    destroy $top.balloon
}































































# Reconfigure font
proc chFont {} {
    global Pref

    font configure myfont -size $Pref(fontsize) -family $Pref(fontfamily)
}

# Change color settings
proc applyColor {} {
    global dirdiff Pref

    foreach top $::diff(diffWindows) {
        if {$top eq ".clipdiff"} continue

        if {$top != ".dirdiff"} {
            foreach item {wLine1 wDiff1 wLine2 wDiff2} {
                if {![info exists ::widgets($top,$item)]} continue
                set w $::widgets($top,$item)

                $w tag configure equal -foreground $Pref(colorequal) \
                        -background $Pref(bgequal)
                $w tag configure new1 -foreground $Pref(colornew1) \
                        -background $Pref(bgnew1)
                $w tag configure change -foreground $Pref(colorchange) \
                        -background $Pref(bgchange)
                $w tag configure new2 -foreground $Pref(colornew2) \
                        -background $Pref(bgnew2)
            }
            continue
        }

#        $dirdiff(wLeft) tag configure new1 -foreground $Pref(colornew1) \
#                -background $Pref(bgnew1)
#        $dirdiff(wLeft) tag configure change -foreground $Pref(colorchange) \
#                -background $Pref(bgchange)
#        $dirdiff(wLeft) tag configure changed -foreground $Pref(colorchange)
#        $dirdiff(wLeft) tag configure invalid -background #a9a9a9
#        $dirdiff(wRight) tag configure new2 -foreground $Pref(colornew2) \
#                -background $Pref(bgnew2)
#        $dirdiff(wRight) tag configure change -foreground $Pref(colorchange) \
#                -background $Pref(bgchange)
#        $dirdiff(wRight) tag configure changed -foreground $Pref(colorchange)
#        $dirdiff(wRight) tag configure invalid -background #a9a9a9

    }
}

# Scroll text windows
proc scrollText {top n what} {
    # Do not scroll if focus is in a text window.
    # This is for scroll bindings in the toplevel.
    if {[winfo class [focus]] != "Text"} {




        $::widgets($top,wDiff1) yview scroll $n $what



    }
}

# Emulate a label that:
# 1 : Displays the right part of the text if there isn't enough room
# 2 : Justfify text to the left if there is enough room.
# 3 : Does not try to allocate space according to its contents
proc fileLabel {w args} {
    ttk::entryX $w -style TLabel
    $w configure {*}$args

    $w configure -takefocus 0 -state readonly ;#-readonlybackground $bg

    set i [lsearch $args -textvariable]
    if {$i >= 0} {
	set var [lindex $args [+ $i 1]]
	uplevel \#0 "trace variable $var w \
		{after idle {$w xview end} ;#}"
    }
}

# Fill in default data for a diff window
proc initDiffData {top} {
    set ::diff($top,leftOK) 0
    set ::diff($top,rightOK) 0
    set ::diff($top,mode) ""


    set ::diff($top,printFile) ""
    set ::diff($top,mergeFile) ""
    set ::diff($top,ancestorFile) ""


    set ::diff($top,conflictFile) ""
    set ::diff($top,limitlines) 0


    set ::diff($top,plugin) ""





}

# Create a new diff window and diff two files
proc newDiff {file1 file2 {range {}}} {
    set top [makeDiffWin]
    update

    set ::diff($top,leftDir) [file dirname $file1]
    set ::diff($top,leftFile) $file1
    set ::diff($top,leftLabel) $file1
    set ::diff($top,leftOK) 1
    set ::diff($top,rightDir) [file dirname $file2]
    set ::diff($top,rightFile) $file2
    set ::diff($top,rightLabel) $file2
    set ::diff($top,rightOK) 1
    set ::diff($top,mode) ""

    set ::diff($top,range) $range
    wm deiconify $top
    raise $top
    update
    doDiff $top
    return $top
}


# Create a new diff window equal to another, except for possibly a range
proc cloneDiff {other {range {}}} {
    set top [makeDiffWin]
    update

    foreach item [array names ::diff $other,*] {
        regsub {^[^,]*,} $item {} item
        set ::diff($top,$item) $::diff($other,$item)
    }
    if {[llength $range] != 0} {
        set ::diff($top,range) $range
    }
    wm deiconify $top
    raise $top
    update
    doDiff $top
}

# A thing to easily get to debug mode
proc backDoor {a} {
    append ::eskil(backdoor) $a
    set ::eskil(backdoor) [string range $::eskil(backdoor) end-9 end]
    if {$::eskil(backdoor) eq "PeterDebug"} {
        set ::eskil(debug) 1
        catch {console show}
        set ::eskil(backdoor) ""

    }
}






























# Build the main window

proc makeDiffWin {{top {}}} {
    global Pref tcl_platform

    if {$top != "" && [winfo exists $top] && [winfo toplevel $top] eq $top} {
        # Reuse the old window
        destroy {*}[winfo children $top]
    } else {
        # Locate a free toplevel name
        if {[info exists ::diff(topDiffCnt)]} {
            set t $::diff(topDiffCnt)
        } else {
            set t 0
        }
        while {[winfo exists .diff$t]} {
            incr t
        }
        set top .diff$t
        toplevel $top
        eskilRegisterToplevel $top




    }

    wm title $top "Eskil:"
    wm protocol $top WM_DELETE_WINDOW [list cleanupAndExit $top]

    ttk::frame $top.f
    grid $top.f -row 0 -columnspan 4 -sticky nws
    lappend ::widgets(toolbars) $top.f
    if {!$::Pref(toolbar)} {
        grid remove $top.f
    }

    menu $top.m
    $top configure -menu $top.m

    $top.m add cascade -label "File" -underline 0 -menu $top.m.mf







|
|


|


|

|





|

>
|













|
|



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


<
<
|




|

|

>


|
|

|
|
|
|
|
|
|
|



<
<
<
<
<
<
<
<
<
<
<
<
<
<




|


|
>
>
>
>
|
>
>
>







|
|
|

|



|
|
|





|
|
|
>
>
|
|
|
>
>
|
|
>
>
|
>
>
>
>
>







|
|
|
|
|
|
|
|
|
>
|










|


|

|


|








|
|

|



>


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


>
|
|

<
<
<
<
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>






|

|







2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075


3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101














3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250




3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
        $top.balloon.t$x configure -width [string length $text]
    }

    # Let geometry requests propagate
    update idletasks

    # Is the balloon within the diff window?
    set rWidth [winfo reqwidth $top.balloon]
    if {$rWidth + $wx > [winfo rootx $top] + [winfo width $top]} {
        # No.
        # Center on diff window
        set wx [expr {([winfo width $top] - $rWidth) / 2 + [winfo rootx $top]}]
        if {$wx < 0} {set wx 0}
        # Is the balloon not within the screen?
        if {$wx + $rWidth > [winfo screenwidth $top]} {
            # Center in screen
            set wx [expr {([winfo screenwidth $top] - $rWidth) / 2}]
            if {$wx < 0} {set wx 0}
        }
    }

    # Does the balloon fit within the screen?
    if {$rWidth > [winfo screenwidth $top]} {
        # How many rows does it take?
        # Adjust ScreenWidth a bit to accomodate for padding.
        set rows [expr {ceil(double($rWidth) / ([winfo screenwidth $top]-10))}]
        # Add rows and fill screen width
        $top.balloon.t1 configure -height $rows
        $top.balloon.t2 configure -height $rows
        # Let geometry requests propagate
        update idletasks
        wm geometry $top.balloon \
                [winfo screenwidth $top]x[winfo reqheight $top.balloon]
        set wx 0
    }
    wm geometry $top.balloon +$wx+$wy
    wm deiconify $top.balloon
}

proc unzoomRow {W} {
    set top [winfo toplevel $W]
    destroy $top.balloon
}

# Helper for fillWindowX
proc FillWindowX {W widthName newXName} {
    upvar 1 $widthName width $newXName newX
    set x [winfo rootx $W]
    set widths [::psballoon::FigureOutScreenWidths $W]
    set nScreen [expr {[llength $widths] / 2}]
    if {$nScreen <= 1} {
	set width [winfo screenwidth $W]
        set newX 0
	return
    }
    if {$nScreen == 2} {
        set minX [lindex $widths 0]
        set maxX [lindex $widths end]
        set width [expr {$maxX - $minX}]
	set newX $minX
	return
    }
    set widthList {}
    set i -1
    foreach {minX maxX} $widths {
        incr i
        lappend widthList [expr {$maxX - $minX}]
        if {$minX <= $x && $x < $maxX} {
            set screenI $i
        }
    }
    if {$screenI == 0} {
        set minX [lindex $widths 0]
        set maxX [lindex $widths 3]
        set width [expr {$maxX - $minX}]
        set newX $minX
        return
    }
    if {$screenI >= $nScreen-1} {
        set minX [lindex $widths end-3]
        set maxX [lindex $widths end]
        set width [expr {$maxX - $minX}]
        set newX $minX
        return
    }
    set widthL [expr {[lindex $widthList $screenI] + [lindex $widthList $screenI-1]}]
    set widthR [expr {[lindex $widthList $screenI] + [lindex $widthList $screenI+1]}]
    if {$widthL >= $widthR} {
        incr screenI -1
    }
    set minX [lindex $widths [* $screenI 2]]
    set maxX [lindex $widths [expr {$screenI * 2 + 3}]]
    set width [expr {$maxX - $minX}]
    set newX $minX
}

# Maximize window in X direction, trying to fill two screens
proc fillWindowX {W} {
    FillWindowX $W width newX
    set newY [winfo rooty $W]
    set height [winfo height $W]
    puts "$W [wm geometry $W]"
    puts "$W X $newX Y $newY W $width H $height"
    wm geometry $W ${width}x$height+$newX+$newY
}

# Reconfigure font
proc chFont {} {


    font configure myfont -size $::Pref(fontsize) -family $::Pref(fontfamily)
}

# Change color settings
proc applyColor {} {
    global dirdiff

    foreach top $::eskil(diffWindows) {
        if {$top eq ".clipdiff"} continue
        if {[string match .fourway* $top]} continue
        if {$top != ".dirdiff"} {
            foreach item {wLine1 wDiff1 wLine2 wDiff2} {
                if { ! [info exists ::widgets($top,$item)]} continue
                set W $::widgets($top,$item)

                $W tag configure equal -foreground $::Pref(colorequal) \
                        -background $::Pref(bgequal)
                $W tag configure new1 -foreground $::Pref(colornew1) \
                        -background $::Pref(bgnew1)
                $W tag configure change -foreground $::Pref(colorchange) \
                        -background $::Pref(bgchange)
                $W tag configure new2 -foreground $::Pref(colornew2) \
                        -background $::Pref(bgnew2)
            }
            continue
        }














    }
}

# Scroll text windows
proc scrollText {top args} {
    # Do not scroll if focus is in a text window.
    # This is for scroll bindings in the toplevel.
    set class [winfo class [focus]]
    if {$class in "Text TEntry"} {
        return
    }

    $::widgets($top,wDiff1) {*}$args
    if {[string index [lindex $args 0] 0] eq "x"} {
        # x commands go to both since that is not synched
        $::widgets($top,wDiff2) {*}$args
    }
}

# Emulate a label that:
# 1 : Displays the right part of the text if there isn't enough room
# 2 : Justfify text to the left if there is enough room.
# 3 : Does not try to allocate space according to its contents
proc fileLabel {W args} {
    ttk::entryX $W -style TLabel
    $W configure {*}$args

    $W configure -takefocus 0 -state readonly ;#-readonlybackground $bg

    set i [lsearch $args -textvariable]
    if {$i >= 0} {
        set var [lindex $args [+ $i 1]]
        uplevel \#0 "trace variable $var w \
                {after idle {$W xview end} ;#}"
    }
}

# Fill in default data for a diff window
proc initDiffData {top} {
    set ::eskil($top,leftOK) 0
    set ::eskil($top,rightOK) 0
    set ::eskil($top,mode) ""
    set ::eskil($top,view) ""
    set ::eskil($top,printFileCmd) 0
    set ::eskil($top,printFile) ""
    set ::eskil($top,mergeFile) ""
    set ::eskil($top,ancestorFile) ""
    set ::eskil($top,separator) ""
    set ::eskil($top,separatorview) ""
    set ::eskil($top,conflictFile) ""
    set ::eskil($top,limitlines) 0
    set ::eskil($top,gz) 0
    set ::eskil($top,maxwidth) 0
    set ::eskil($top,plugin,1) ""

    # Copy the collected options from command line
    foreach {item val} $::eskil(defaultopts) {
        set ::eskil($top,$item) $val
    }
}

# Create a new diff window and diff two files
proc newDiff {file1 file2 {range {}}} {
    set top [makeDiffWin]
    update

    set ::eskil($top,leftDir) [file dirname $file1]
    set ::eskil($top,leftFile) $file1
    set ::eskil($top,leftLabel) $file1
    set ::eskil($top,leftOK) 1
    set ::eskil($top,rightDir) [file dirname $file2]
    set ::eskil($top,rightFile) $file2
    set ::eskil($top,rightLabel) $file2
    set ::eskil($top,rightOK) 1
    set ::eskil($top,mode) ""
    set ::eskil($top,view) ""
    set ::eskil($top,range) $range
    wm deiconify $top
    raise $top
    update
    doDiff $top
    return $top
}


# Create a new diff window equal to another, except for possibly a range
proc cloneDiff {other {range {}}} {
    set top [makeDiffWin $other]
    update

    foreach item [array names ::eskil $other,*] {
        regsub {^[^,]*,} $item {} item
        set ::eskil($top,$item) $::eskil($other,$item)
    }
    if {[llength $range] != 0} {
        set ::eskil($top,range) $range
    }
    wm deiconify $top
    raise $top
    update
    doDiff $top
}

# A thing to easily get to debug mode
proc backDoor {top aVal} {
    append ::eskil(backdoor) $aVal
    set ::eskil(backdoor) [string range $::eskil(backdoor) end-9 end]
    if {$::eskil(backdoor) eq "EskilDebug"} {
        set ::eskil(debug) 1
        catch {console show}
        set ::eskil(backdoor) ""
        AddDebugMenu $top
    }
}

# Runtime disable of C version of DiffUtil
proc DisableDiffUtilC {} {
    uplevel \#0 [list source $::eskil(thisDir)/../lib/diffutil/tcl/diffutil.tcl]
}

# Add a debug menu to a toplevel window
proc AddDebugMenu {top} {
    set dMenu [debugMenu $top.m]
    $dMenu add checkbutton -label "Wrap" -variable wrapstate \
        -onvalue char -offvalue none -command \
        "$top.ft1.tt configure -wrap \$wrapstate ;\
                $top.ft2.tt configure -wrap \$wrapstate"
    $dMenu add command -label "Date Filter" \
        -command {set ::eskil(filter) {^Date}}
    $dMenu add separator
    $dMenu add command -label "Reread Source" -underline 0 \
        -command {EskilRereadSource}
        $dMenu add separator
    $dMenu add command -label "Normal Cursor" \
        -command [list normalCursor $top]
    $dMenu add command -label "Fill X" \
        -command [list fillWindowX $top]
    $dMenu add separator
    # Runtime disable of C version of DiffUtil
    $dMenu add command -label "Tcl DiffUtil" -command DisableDiffUtilC
    $dMenu add command -label "Evalstats" -command {evalstats}
    $dMenu add command -label "_stats" -command {parray _stats}
}

# Build the main window
# "other" is related window. Currently unused
proc makeDiffWin {{other {}} args} {
    global tcl_platform





    # Locate a free toplevel name
    if {[info exists ::eskil(topDiffCnt)]} {
        set t $::eskil(topDiffCnt)
    } else {
        set t 0
    }
    while {[winfo exists .diff$t]} {
        incr t
    }
    set top .diff$t
    toplevel $top
    eskilRegisterToplevel $top
    initDiffData $top

    if {"-table" in $args} {
        set ::eskil($top,view) "table"
    }

    wm title $top "Eskil:"
    wm protocol $top WM_DELETE_WINDOW [list cleanupAndExit $top]

    ttk::frame $top.f
    grid $top.f -row 0 -columnspan 5 -sticky nws
    lappend ::widgets(toolbars) $top.f
    if { ! $::Pref(toolbar)} {
        grid remove $top.f
    }

    menu $top.m
    $top configure -menu $top.m

    $top.m add cascade -label "File" -underline 0 -menu $top.m.mf
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731

2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797












2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814




2815
2816
2817
2818

2819
2820
2821
2822


2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848

2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863



2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894

2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929

2930


2931
2932

2933

2934
2935

2936




2937





2938
















2939

2940



2941









2942
2943
2944


2945
2946
2947
2948
2949
2950
2951








2952















2953


2954





2955







2956


























2957






2958
2959
2960
2961
2962

2963
2964
2965



2966

2967

2968

2969











2970

2971





2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
            -command {cleanupAndExit all}

    $top.m add cascade -label "Options" -underline 0 -menu $top.m.mo
    menu $top.m.mo
    $top.m.mo add cascade -label "Font" -underline 0 -menu $top.m.mo.f
    $top.m.mo add cascade -label "Ignore" -underline 0 -menu $top.m.mo.i
    $top.m.mo add command -label "Preprocess..." -underline 0 \
            -command [list EditPrefRegsub $top]
    $top.m.mo add command -label "Plugins..." -underline 1 \
            -command [list EditPrefPlugins $top]
    $top.m.mo add cascade -label "Parse" -underline 1 -menu $top.m.mo.p
    $top.m.mo add command -label "Colours..." -underline 0 -command makePrefWin
    $top.m.mo add cascade -label "Context" -underline 1 -menu $top.m.mo.c

    $top.m.mo add separator
    $top.m.mo add checkbutton -label "Toolbar" -variable ::Pref(toolbar)
    $top.m.mo add separator
    $top.m.mo add command -label "Save default" \
            -command [list saveOptions $top]

    menu $top.m.mo.f
    $top.m.mo.f add command -label "Select..." -command makeFontWin \
            -underline 0
    $top.m.mo.f add radiobutton -label 6 -variable Pref(fontsize) -value 6 \
            -command chFont
    $top.m.mo.f add radiobutton -label 7 -variable Pref(fontsize) -value 7 \
            -command chFont
    $top.m.mo.f add radiobutton -label 8 -variable Pref(fontsize) -value 8 \
            -command chFont
    $top.m.mo.f add radiobutton -label 9 -variable Pref(fontsize) -value 9 \
            -command chFont
    $top.m.mo.f add radiobutton -label 10 -variable Pref(fontsize) -value 10 \
            -command chFont

    menu $top.m.mo.i
    $top.m.mo.i add radiobutton -label "No spaces" \
            -variable Pref(ignore) -value " "
    $top.m.mo.i add radiobutton -label "Space changes (-b)" \
            -variable Pref(ignore) -value "-b"
    $top.m.mo.i add radiobutton -label "All spaces (-w)" \
            -variable Pref(ignore) -value "-w"
    $top.m.mo.i add separator
    $top.m.mo.i add checkbutton -label "Case (-i)" \
            -variable Pref(nocase)
    $top.m.mo.i add checkbutton -label "Empty" \
            -variable Pref(noempty)
    $top.m.mo.i add checkbutton -label "Digits" \
            -variable Pref(nodigit)

    menu $top.m.mo.p
    $top.m.mo.p add radiobutton -label "Nothing" -variable Pref(parse) -value 0
    $top.m.mo.p add radiobutton -label "Lines" -variable Pref(parse) -value 1
    $top.m.mo.p add radiobutton -label "Blocks (small)" -variable Pref(parse) \
            -value 2
    $top.m.mo.p add radiobutton -label "Blocks" -variable Pref(parse) -value 3
    $top.m.mo.p add separator
    $top.m.mo.p add radiobutton -label "Characters" \
            -variable Pref(lineparsewords) -value "0"
    $top.m.mo.p add radiobutton -label "Words" \
            -variable Pref(lineparsewords) -value "1"
    $top.m.mo.p add separator
    $top.m.mo.p add checkbutton -label "Fine chunks" -variable Pref(finegrainchunks)
    $top.m.mo.p add separator
    $top.m.mo.p add checkbutton -label "Mark last" -variable Pref(marklast)

    menu $top.m.mo.c
    $top.m.mo.c add radiobutton -label "Show all lines" \
            -variable ::Pref(context) -value -1
    $top.m.mo.c add radiobutton -label "Show only diffs" \
            -variable ::Pref(context) -value 0
    $top.m.mo.c add separator
    $top.m.mo.c add radiobutton -label "Context 2 lines" \
            -variable ::Pref(context) -value 2
    $top.m.mo.c add radiobutton -label "Context 5 lines" \
            -variable ::Pref(context) -value 5
    $top.m.mo.c add radiobutton -label "Context 10 lines" \
            -variable ::Pref(context) -value 10
    $top.m.mo.c add radiobutton -label "Context 20 lines" \
            -variable ::Pref(context) -value 20













    $top.m add cascade -label "Search" -underline 0 -menu $top.m.ms
    menu $top.m.ms
    if {[info procs textSearch::searchMenu] != ""} {
        textSearch::searchMenu $top.m.ms
    } else {
        $top.m.ms add command -label "Text search not available" \
                -state disabled
    }

    $top.m add cascade -label "Tools" -underline 0 -menu $top.m.mt
    menu $top.m.mt
    $top.m.mt add command -label "New Diff Window" -underline 0 \
            -command makeDiffWin
    $top.m.mt add command -label "Directory Diff" -underline 0 \
            -command makeDirDiffWin
    $top.m.mt add command -label "Clip Diff" -underline 0 \
            -command makeClipDiffWin




    $top.m.mt add command -label "Merge" -underline 0 \
            -command [list makeMergeWin $top] -state disabled
    $top.m.mt add command -label "Edit Mode" -underline 0 \
            -command [list allowEdit $top] -state disabled

    $top.m.mt add command -label "Paste Patch" -underline 0 \
            -command [list doPastePatch $top]
    $top.m.mt add command -label "Clear Align" \
            -command [list clearAlign $top] -state disabled


    set ::widgets($top,enableAlignCmd) [list \
            $top.m.mt entryconfigure "Clear Align" -state normal]
    set ::widgets($top,disableAlignCmd) [list \
            $top.m.mt entryconfigure "Clear Align" -state disabled]

    if {$::tcl_platform(platform) eq "windows"} {
        if {![catch {package require registry}]} {
            $top.m.mt add separator
            $top.m.mt add command -label "Setup Registry" -underline 6 \
                    -command makeRegistryWin
        }
    }

    $top.m add cascade -label "Help" -underline 0 -menu $top.m.help
    menu $top.m.help
    $top.m.help add command -label "General" -command makeHelpWin -underline 0
    $top.m.help add command -label "Tutorial" -command makeTutorialWin \
            -underline 0
    foreach label {{Revision Control} {Edit Mode} {Plugins}} \
            file {revision.txt editmode.txt plugins.txt} {
        $top.m.help add command -label $label \
                -command [list makeDocWin $file] -underline 0
    }
    $top.m.help add separator
    $top.m.help add command -label "About" -command makeAboutWin -underline 0


    ttk::label $top.lr1 -text "Rev 1"
    addBalloon $top.lr1 "Revision number for version diff."
    ttk::entryX $top.er1 -width 12 -textvariable diff($top,doptrev1)
    set ::widgets($top,rev1) $top.er1
    bind $top.er1 <Key-Return> [list redoDiff $top]

    ttk::label $top.lr2 -text "Rev 2"
    addBalloon $top.lr2 "Revision number for version diff."
    ttk::entryX $top.er2 -width 12 -textvariable diff($top,doptrev2)
    set ::widgets($top,rev2) $top.er2
    bind $top.er2 <Key-Return> [list redoDiff $top]

    ttk::button $top.bcm -text Commit -command [list revCommit $top] \
            -state disabled -underline 0
    set ::widgets($top,commit) $top.bcm



    ttk::button $top.blg -text Log -command [list revLog $top] \
        -state disabled -underline 0
    set ::widgets($top,log) $top.blg
    ttk::button $top.bfp -text "Prev Diff" \
            -command [list findDiff $top -1] \
            -underline 0
    ttk::button $top.bfn -text "Next Diff" \
            -command [list findDiff $top 1] \
            -underline 0
    bind $top <Alt-n> [list findDiff $top 1]
    bind $top <Alt-p> [list findDiff $top -1]
    bind $top <Alt-c> [list revCommit $top]
    bind $top <Alt-l> [list revLog $top]

    catch {font delete myfont}
    font create myfont -family $Pref(fontfamily) -size $Pref(fontsize)

    fileLabel $top.l1 -textvariable diff($top,leftLabel)
    fileLabel $top.l2 -textvariable diff($top,rightLabel)

    ttk::frame $top.ft1 -borderwidth 2 -relief sunken
    text $top.ft1.tl -height $Pref(lines) -width 5 -wrap none \
            -font myfont -borderwidth 0 -padx 0 -highlightthickness 0 \
            -takefocus 0
    text $top.ft1.tt -height $Pref(lines) -width $Pref(linewidth) -wrap none \
            -xscrollcommand [list $top.sbx1 set] \
            -font myfont -borderwidth 0 -padx 1 \
            -highlightthickness 0
    $top.ft1.tt configure -tabstyle wordprocessor
    tk::frame $top.ft1.f -width 2 -height 2 -background lightgray
    pack $top.ft1.tl -side left -fill y

    pack $top.ft1.f -side left -fill y
    pack $top.ft1.tt -side right -fill both -expand 1
    scrollbar $top.sby -orient vertical
    scrollbar $top.sbx1 -orient horizontal -command [list $top.ft1.tt xview]
    set ::widgets($top,wLine1) $top.ft1.tl
    set ::widgets($top,wDiff1) $top.ft1.tt

    ttk::frame $top.ft2 -borderwidth 2 -relief sunken
    text $top.ft2.tl -height $Pref(lines) -width 5 -wrap none \
            -font myfont -borderwidth 0 -padx 0 -highlightthickness 0 \
            -takefocus 0
    text $top.ft2.tt -height $Pref(lines) -width $Pref(linewidth) -wrap none \
            -xscrollcommand [list $top.sbx2 set] \
            -font myfont -borderwidth 0 -padx 1 \
            -highlightthickness 0
    $top.ft2.tt configure -tabstyle wordprocessor
    tk::frame $top.ft2.f -width 2 -height 2 -background lightgray
    pack $top.ft2.tl -side left -fill y
    pack $top.ft2.f -side left -fill y
    pack $top.ft2.tt -side right -fill both -expand 1
    scrollbar $top.sbx2 -orient horizontal -command [list $top.ft2.tt xview]
    set ::widgets($top,wLine2) $top.ft2.tl
    set ::widgets($top,wDiff2) $top.ft2.tt
    commonYScroll $top.sby $top.ft1.tl $top.ft1.tt $top.ft2.tl $top.ft2.tt

    # Set up a tag for incremental search bindings
    if {[info procs textSearch::enableSearch] != ""} {
        textSearch::enableSearch $top.ft1.tt -label ::widgets($top,isearchLabel)
        textSearch::enableSearch $top.ft2.tt -label ::widgets($top,isearchLabel)
    }

    # Set up file dropping in text windows if TkDnd is available
    if {![catch {package require tkdnd}]} {
        dnd bindtarget $top.ft1.tt text/uri-list <Drop> "fileDrop $top left %D"
        dnd bindtarget $top.ft2.tt text/uri-list <Drop> "fileDrop $top right %D"

    }



    ttk::label $top.le -textvariable ::widgets($top,eqLabel) -width 1

    addBalloon $top.le "* means external diff is running.\n= means files do\

            not differ.\n! means a large block is being processed.\nBlank\
            means files differ."

    # FIXA: verify that this label is ok after Tile migration




    ttk::label $top.ls -width 1 \





            -textvariable ::widgets($top,isearchLabel)
















    addBalloon $top.ls "Incremental search indicator"

    set map [createMap $top]













    applyColor
    foreach w [list $top.ft1.tt $top.ft2.tt] {
        # The last change in a row is underlined


        $w tag configure last -underline 1
        # Each file in a patch view starts with a block of this type
        $w tag configure patch -background gray
        # Make sure selection is visible
        $w tag raise sel
        bind $w <ButtonPress-3> "zoomRow %W %X %Y %x %y"
        bind $w <ButtonRelease-3> "unzoomRow %W"








    }















    foreach w [list $top.ft1.tl $top.ft2.tl] {


        $w tag configure align -underline 1





        bind $w <ButtonPress-3> "rowPopup %W %X %Y %x %y"







    }

































    grid $top.l1   $top.le -        $top.l2   -row 1 -sticky news
    grid $top.ft1  $map    $top.sby $top.ft2  -row 2 -sticky news
    grid $top.sbx1 $top.ls -        $top.sbx2 -row 3 -sticky news
    grid columnconfigure $top {0 3} -weight 1
    grid rowconfigure $top 2 -weight 1

    grid $map -pady [expr {[winfo reqwidth $top.sby] - 2}]
    grid $top.ls -sticky ""




    bind $top <Key-Up>    [list scrollText $top -1 u]

    bind $top <Key-Down>  [list scrollText $top  1 u]

    bind $top <Key-Prior> [list scrollText $top -1 pa]

    bind $top <Key-Next>  [list scrollText $top  1 pa]











    bind $top <Key-Escape> [list focus $top]

    if {$::eskil(debug) == 0} {





        bind $top <Key> "backDoor %A"
    }

    pack $top.bfn -in $top.f -side right -padx {3 6}
    pack $top.bfp $top.bcm $top.blg \
            $top.er2 $top.lr2 $top.er1 $top.lr1 \
            -in $top.f -side right -padx 3
    pack $top.bfn $top.bfp $top.bcm -ipadx 15

    if {$::eskil(debug) == 1} {
        $top.m add cascade -label "Debug" -menu $top.m.md -underline 0
        menu $top.m.md
        if {$tcl_platform(platform) eq "windows"} {
            $top.m.md add checkbutton -label "Console" -variable consolestate \
                    -onvalue show -offvalue hide \
                    -command {console $consolestate}
            $top.m.md add separator
        }
        $top.m.md add checkbutton -label "Wrap" -variable wrapstate \
                -onvalue char -offvalue none -command \
                "$top.ft1.tt configure -wrap \$wrapstate ;\
                $top.ft2.tt configure -wrap \$wrapstate"
        $top.m.md add command -label "Date Filter" \
                -command {set ::diff(filter) {^Date}}
        $top.m.md add separator
        $top.m.md add command -label "Reread Source" -underline 0 \
                -command {EskilRereadSource}
        $top.m.md add separator
        $top.m.md add command -label "Redraw Window" \
                -command [list makeDiffWin $top]
        $top.m.md add separator
        $top.m.md add command -label "Normal Cursor" \
                -command [list normalCursor $top]
        $top.m.md add separator
        $top.m.md add command -label "Evalstats" -command {evalstats}
        $top.m.md add command -label "_stats" -command {parray _stats}
        $top.m.md add command -label "Nuisance" -command [list makeNuisance \
                $top "It looks like you are trying out the debug menu."]
    }

    initDiffData $top
    return $top
}

proc ValidateNewColors {} {
    global TmpPref
    foreach item {colorchange bgchange colornew1 bgnew1
        colornew2 bgnew2 colorequal bgequal} {
        if {![info exists TmpPref($item)]} continue
        set col $TmpPref($item)
        if {$col eq ""} continue
        if {[catch {winfo rgb . $col}]} {
            # FIXA: Error message
            # Just restore for now
            set TmpPref($item) $::Pref($item)
        }
    }
}

# Set new preferences.
proc applyPref {} {
    global Pref TmpPref

    ValidateNewColors
    array set Pref [array get TmpPref]
    applyColor
}

# Update test color fields.
proc testColor {} {
    global TmpPref


    ValidateNewColors
    .pr.fc.t1 tag configure change -foreground $TmpPref(colorchange) \
            -background $TmpPref(bgchange)
    .pr.fc.t2 tag configure new1 -foreground $TmpPref(colornew1) \
            -background $TmpPref(bgnew1)
    .pr.fc.t3 tag configure new2 -foreground $TmpPref(colornew2) \
            -background $TmpPref(bgnew2)
    .pr.fc.t4 tag configure equal -foreground $TmpPref(colorequal) \
            -background $TmpPref(bgequal)
}

# Color dialog.
proc selColor {name} {
    global TmpPref

    set old $TmpPref($name)
    if {$old eq ""} {
        set t [tk_chooseColor -parent .pr]
    } else {
        set t [tk_chooseColor -parent .pr -initialcolor $old]
    }
    if {$t != ""} {
        set TmpPref($name) $t
    }
}

# Create a window for changing preferences.
# Currently only colors are changed in this dialog.
proc makePrefWin {} {
    global Pref TmpPref

    array set TmpPref [array get Pref]

    destroy .pr

    toplevel .pr
    wm title .pr "Eskil Preferences"

    ttk::frame .pr.fc -borderwidth 1 -relief solid
    ttk::label .pr.fc.l1 -text "Colours" -anchor w
    ttk::label .pr.fc.l2 -text "Text" -anchor w
    ttk::label .pr.fc.l3 -text "Background" -anchor w

    ttk::entryX .pr.fc.e1 -textvariable "TmpPref(colorchange)" -width 10
    ttk::entryX .pr.fc.e2 -textvariable "TmpPref(colornew1)" -width 10
    ttk::entryX .pr.fc.e3 -textvariable "TmpPref(colornew2)" -width 10
    ttk::entryX .pr.fc.e4 -textvariable "TmpPref(colorequal)" -width 10

    ttk::button .pr.fc.b1 -text "Sel" -command "selColor colorchange"
    ttk::button .pr.fc.b2 -text "Sel" -command "selColor colornew1"
    ttk::button .pr.fc.b3 -text "Sel" -command "selColor colornew2"
    ttk::button .pr.fc.b4 -text "Sel" -command "selColor colorequal"

    ttk::entryX .pr.fc.e5 -textvariable "TmpPref(bgchange)" -width 10
    ttk::entryX .pr.fc.e6 -textvariable "TmpPref(bgnew1)" -width 10
    ttk::entryX .pr.fc.e7 -textvariable "TmpPref(bgnew2)" -width 10
    ttk::entryX .pr.fc.e8 -textvariable "TmpPref(bgequal)" -width 10

    ttk::button .pr.fc.b5 -text "Sel" -command "selColor bgchange"
    ttk::button .pr.fc.b6 -text "Sel" -command "selColor bgnew1"
    ttk::button .pr.fc.b7 -text "Sel" -command "selColor bgnew2"
    ttk::button .pr.fc.b8 -text "Sel" -command "selColor bgequal"

    text .pr.fc.t1 -width 12 -height 1 -font myfont -takefocus 0
    text .pr.fc.t2 -width 12 -height 1 -font myfont -takefocus 0
    text .pr.fc.t3 -width 12 -height 1 -font myfont -takefocus 0
    text .pr.fc.t4 -width 12 -height 1 -font myfont -takefocus 0
    .pr.fc.t1 tag configure change -foreground $TmpPref(colorchange) \
            -background $TmpPref(bgchange)
    .pr.fc.t2 tag configure new1 -foreground $TmpPref(colornew1) \
            -background $TmpPref(bgnew1)
    .pr.fc.t3 tag configure new2 -foreground $TmpPref(colornew2) \
            -background $TmpPref(bgnew2)
    .pr.fc.t4 tag configure equal -foreground $TmpPref(colorequal) \
            -background $TmpPref(bgequal)
    .pr.fc.t1 insert end "Changed text" change
    .pr.fc.t2 insert end "Deleted text" new1
    .pr.fc.t3 insert end "Added text" new2
    .pr.fc.t4 insert end "Equal text" equal

    .pr.fc.t1 configure -state disabled
    .pr.fc.t2 configure -state disabled







|

|



>









|

|

|

|

|




|

|

|


|

|

|


|
|
|

|


|

|

|

|
















>
>
>
>
>
>
>
>
>
>
>
>












|




>
>
>
>


|

>




>
>






|



















>


|





|






>
>
>














<
<
|
|
<
|
|
<
<
|
<
<
<
<
<
<
|
>
|
|
|
<
|
|
|
<
<
<
<
<
<
<
<
<
<
|
<
|
<
<
<
<
|
<
<
<
<
|
|
<
|
<
<
>
|
>
>


>
|
>
|
|
>
|
>
>
>
>
|
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
|
>
>
>
|
>
>
>
>
>
>
>
>
>
|
|
<
>
>
|
<
|
<
<
<
<
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
|
>
>
>
>
>
|
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
|
|
|
|
|
>
|
|

>
>
>
|
>
|
>
|
>
|
>
>
>
>
>
>
>
>
>
>
>

>

>
>
>
>
>
|


<
<
<
<
<
<

<
|
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
<




<


|
|




|






<
<

|





<
<
<

|
|
|
|
|
|
|
|




<
<
|






|






<
<
|











|
|
|
|






|
|
|
|










|
|
|
|
|
|
|
|







3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497


3498
3499

3500
3501


3502






3503
3504
3505
3506
3507

3508
3509
3510










3511

3512




3513




3514
3515

3516


3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574

3575
3576
3577

3578




3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694






3695

3696





3697




















3698
3699

3700
3701
3702
3703

3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718


3719
3720
3721
3722
3723
3724
3725



3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738


3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752


3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
            -command {cleanupAndExit all}

    $top.m add cascade -label "Options" -underline 0 -menu $top.m.mo
    menu $top.m.mo
    $top.m.mo add cascade -label "Font" -underline 0 -menu $top.m.mo.f
    $top.m.mo add cascade -label "Ignore" -underline 0 -menu $top.m.mo.i
    $top.m.mo add command -label "Preprocess..." -underline 0 \
            -command [list EditPrefPreprocess $top]
    $top.m.mo add command -label "Plugins..." -underline 1 \
            -command [list editPrefPlugins $top]
    $top.m.mo add cascade -label "Parse" -underline 1 -menu $top.m.mo.p
    $top.m.mo add command -label "Colours..." -underline 0 -command makePrefWin
    $top.m.mo add cascade -label "Context" -underline 1 -menu $top.m.mo.c
    $top.m.mo add cascade -label "Pivot" -underline 2 -menu $top.m.mo.piv
    $top.m.mo add separator
    $top.m.mo add checkbutton -label "Toolbar" -variable ::Pref(toolbar)
    $top.m.mo add separator
    $top.m.mo add command -label "Save default" \
            -command [list saveOptions $top]

    menu $top.m.mo.f
    $top.m.mo.f add command -label "Select..." -command makeFontWin \
            -underline 0
    $top.m.mo.f add radiobutton -label 6 -variable ::Pref(fontsize) -value 6 \
            -command chFont
    $top.m.mo.f add radiobutton -label 7 -variable ::Pref(fontsize) -value 7 \
            -command chFont
    $top.m.mo.f add radiobutton -label 8 -variable ::Pref(fontsize) -value 8 \
            -command chFont
    $top.m.mo.f add radiobutton -label 9 -variable ::Pref(fontsize) -value 9 \
            -command chFont
    $top.m.mo.f add radiobutton -label 10 -variable ::Pref(fontsize) -value 10 \
            -command chFont

    menu $top.m.mo.i
    $top.m.mo.i add radiobutton -label "No spaces" \
            -variable ::Pref(ignore) -value " "
    $top.m.mo.i add radiobutton -label "Space changes (-b)" \
            -variable ::Pref(ignore) -value "-b"
    $top.m.mo.i add radiobutton -label "All spaces (-w)" \
            -variable ::Pref(ignore) -value "-w"
    $top.m.mo.i add separator
    $top.m.mo.i add checkbutton -label "Case (-i)" \
            -variable ::Pref(nocase)
    $top.m.mo.i add checkbutton -label "Empty" \
            -variable ::Pref(noempty)
    $top.m.mo.i add checkbutton -label "Digits" \
            -variable ::Pref(nodigit)

    menu $top.m.mo.p
    $top.m.mo.p add radiobutton -label "Nothing" -variable ::Pref(parse) -value 0
    $top.m.mo.p add radiobutton -label "Lines" -variable ::Pref(parse) -value 1
    $top.m.mo.p add radiobutton -label "Blocks (small)" -variable ::Pref(parse) \
            -value 2
    $top.m.mo.p add radiobutton -label "Blocks" -variable ::Pref(parse) -value 3
    $top.m.mo.p add separator
    $top.m.mo.p add radiobutton -label "Characters" \
            -variable ::Pref(lineparsewords) -value "0"
    $top.m.mo.p add radiobutton -label "Words" \
            -variable ::Pref(lineparsewords) -value "1"
    $top.m.mo.p add separator
    $top.m.mo.p add checkbutton -label "Fine chunks" -variable ::Pref(finegrainchunks)
    $top.m.mo.p add separator
    $top.m.mo.p add checkbutton -label "Mark last" -variable ::Pref(marklast)

    menu $top.m.mo.c
    $top.m.mo.c add radiobutton -label "Show all lines" \
            -variable ::Pref(context) -value -1
    $top.m.mo.c add radiobutton -label "Show only diffs" \
            -variable ::Pref(context) -value 0
    $top.m.mo.c add separator
    $top.m.mo.c add radiobutton -label "Context 2 lines" \
            -variable ::Pref(context) -value 2
    $top.m.mo.c add radiobutton -label "Context 5 lines" \
            -variable ::Pref(context) -value 5
    $top.m.mo.c add radiobutton -label "Context 10 lines" \
            -variable ::Pref(context) -value 10
    $top.m.mo.c add radiobutton -label "Context 20 lines" \
            -variable ::Pref(context) -value 20

    menu $top.m.mo.piv
    $top.m.mo.piv add radiobutton -label "1" \
            -variable ::Pref(pivot) -value 1
    $top.m.mo.piv add radiobutton -label "10" \
            -variable ::Pref(pivot) -value 10
    $top.m.mo.piv add radiobutton -label "100" \
            -variable ::Pref(pivot) -value 100
    $top.m.mo.piv add radiobutton -label "1000" \
            -variable ::Pref(pivot) -value 1000
    $top.m.mo.piv add radiobutton -label "Max" \
            -variable ::Pref(pivot) -value 2000000000

    $top.m add cascade -label "Search" -underline 0 -menu $top.m.ms
    menu $top.m.ms
    if {[info procs textSearch::searchMenu] != ""} {
        textSearch::searchMenu $top.m.ms
    } else {
        $top.m.ms add command -label "Text search not available" \
                -state disabled
    }

    $top.m add cascade -label "Tools" -underline 0 -menu $top.m.mt
    menu $top.m.mt
    $top.m.mt add command -label "New Diff Window" -underline 0 \
            -command [list makeDiffWin $top]
    $top.m.mt add command -label "Directory Diff" -underline 0 \
            -command makeDirDiffWin
    $top.m.mt add command -label "Clip Diff" -underline 0 \
            -command makeClipDiffWin
    $top.m.mt add command -label "Fourway Diff" -underline 0 \
            -command makeFourWayWin
    $top.m.mt add command -label "Table Diff" -underline 0 \
            -command [list makeDiffWin $top -table]
    $top.m.mt add command -label "Merge" -underline 0 \
            -command [list makeMergeWin $top] -state disabled
    $top.m.mt add command -label "Edit Mode" -underline 0 -accelerator Ctrl-E \
            -command [list allowEdit $top] -state disabled
    bind $top <Control-Key-E> [list allowEdit $top]
    $top.m.mt add command -label "Paste Patch" -underline 0 \
            -command [list doPastePatch $top]
    $top.m.mt add command -label "Clear Align" \
            -command [list clearAlign $top] -state disabled
    $top.m.mt add command -label "Highlight tabs" \
            -command [list highlightTabs $top]
    set ::widgets($top,enableAlignCmd) [list \
            $top.m.mt entryconfigure "Clear Align" -state normal]
    set ::widgets($top,disableAlignCmd) [list \
            $top.m.mt entryconfigure "Clear Align" -state disabled]

    if {$::tcl_platform(platform) eq "windows"} {
        if { ! [catch {package require registry}]} {
            $top.m.mt add separator
            $top.m.mt add command -label "Setup Registry" -underline 6 \
                    -command makeRegistryWin
        }
    }

    $top.m add cascade -label "Help" -underline 0 -menu $top.m.help
    menu $top.m.help
    $top.m.help add command -label "General" -command makeHelpWin -underline 0
    $top.m.help add command -label "Tutorial" -command makeTutorialWin \
            -underline 0
    foreach label {{Revision Control} {Edit Mode} {Plugins}} \
            file {revision.txt editmode.txt plugins.txt} {
        $top.m.help add command -label $label \
                -command [list makeDocWin $file] -underline 0
    }
    $top.m.help add separator
    $top.m.help add command -label "About" -command makeAboutWin -underline 0

    # Toolbar
    ttk::label $top.lr1 -text "Rev 1"
    addBalloon $top.lr1 "Revision number for version diff."
    ttk::entryX $top.er1 -width 12 -textvariable ::eskil($top,doptrev1)
    set ::widgets($top,rev1) $top.er1
    bind $top.er1 <Key-Return> [list redoDiff $top]

    ttk::label $top.lr2 -text "Rev 2"
    addBalloon $top.lr2 "Revision number for version diff."
    ttk::entryX $top.er2 -width 12 -textvariable ::eskil($top,doptrev2)
    set ::widgets($top,rev2) $top.er2
    bind $top.er2 <Key-Return> [list redoDiff $top]

    ttk::button $top.bcm -text Commit -command [list revCommit $top] \
            -state disabled -underline 0
    set ::widgets($top,commit) $top.bcm
    ttk::button $top.brv -text Revert -command [list revRevert $top] \
            -state disabled
    set ::widgets($top,revert) $top.brv
    ttk::button $top.blg -text Log -command [list revLog $top] \
        -state disabled -underline 0
    set ::widgets($top,log) $top.blg
    ttk::button $top.bfp -text "Prev Diff" \
            -command [list findDiff $top -1] \
            -underline 0
    ttk::button $top.bfn -text "Next Diff" \
            -command [list findDiff $top 1] \
            -underline 0
    bind $top <Alt-n> [list findDiff $top 1]
    bind $top <Alt-p> [list findDiff $top -1]
    bind $top <Alt-c> [list revCommit $top]
    bind $top <Alt-l> [list revLog $top]



    pack $top.bfn -in $top.f -side right -padx {3 6}
    pack $top.bfp $top.bcm $top.brv $top.blg \

            $top.er2 $top.lr2 $top.er1 $top.lr1 \
            -in $top.f -side right -padx 3


    # Adjust






    pack $top.bfn $top.bfp $top.bcm -ipadx 15
    # Add a separator entry in toolbar if table mode is on
    if {$::eskil($top,view) eq "table"} {
        ttk::label $top.lsep -text "Sep"
        addBalloon $top.lsep "Separator for interpreting file as table"

        ttk::entryX $top.esep -width 2 -textvariable ::eskil($top,separatorview)
        set ::widgets($top,sep) $top.esep
        bind $top.esep <Key-Return> [list redoDiff $top]










        pack $top.esep $top.lsep \

                -in $top.f -side right -padx 3




    }





    # File and progress indicators

    catch {font delete myfont}


    font create myfont -family $::Pref(fontfamily) -size $::Pref(fontsize)

    fileLabel $top.l1 -textvariable ::eskil($top,leftLabel)
    fileLabel $top.l2 -textvariable ::eskil($top,rightLabel)

    ttk::label $top.le -textvariable ::widgets($top,eqLabel) -width 1
    addBalloon $top.le -fmt {
        * means external diff is running.\n
        = means files do not differ.\n
        ! means a large block is being processed.\n
        Blank means files differ.
    }

    # Main window
    if {$::eskil($top,view) eq "table"} {
        # Single frame for contents
        ttk::frame $top.ft -borderwidth 2 -relief sunken
        grid $top.l1   $top.le $top.l2  -row 1 -sticky news
        grid $top.ft   -       -        -row 2 -sticky news
        grid columnconfigure $top "0 2" -weight 1
        grid rowconfigure $top $top.ft  -weight 1
        # TBD TABLE
        tablelist::tablelist $top.ft.tab -height 25 -width 100 \
                -font myfont -labelfont myfont \
                -movablecolumns no -setgrid no -showseparators no \
                -fullseparators yes -selectmode extended \
                -colorizecommand tblModeColorCallback
        ttk::scrollbar $top.ft.vsb -orient vertical \
                -command "$top.ft.tab yview"
        ttk::scrollbar $top.ft.hsb -orient horizontal \
                -command "$top.ft.tab xview"
        $top.ft.tab configure -yscrollcommand "$top.ft.vsb set" \
                -xscrollcommand "$top.ft.hsb set"
        set body [$top.ft.tab bodypath]
        $body tag configure new1 -foreground $::Pref(colornew1) \
                -background $::Pref(bgnew1)
        $body tag configure new2 -foreground $::Pref(colornew2) \
                -background $::Pref(bgnew2)
        $body tag configure change -foreground $::Pref(colorchange) \
                -background $::Pref(bgchange)

        set bg [ttk::style configure . -background]
        set map [createMap $top $bg]

        grid $top.ft.tab $top.ft.vsb $map -sticky news
        grid $top.ft.hsb x           x    -sticky news
        grid columnconfigure $top.ft 0 -weight 1
        grid rowconfigure    $top.ft 0 -weight 1
        grid $map -pady [expr {[winfo reqwidth $top.ft.vsb] - 2}]
        set ::widgets($top,wTable) $top.ft.tab
    } else {
        ttk::frame $top.ft1 -borderwidth 2 -relief sunken
        text $top.ft1.tl -height $::Pref(lines) -width 5 -wrap none \
                -font myfont -borderwidth 0 -padx 0 -highlightthickness 0 \
                -takefocus 0
        text $top.ft1.tt -height $::Pref(lines) -width $::Pref(linewidth) \
                -wrap none \
                -xscrollcommand [list $top.sbx1 set] \

                -font myfont -borderwidth 0 -padx 1 \
                -highlightthickness 0
        $top.ft1.tt configure -tabstyle wordprocessor

        tk::frame $top.ft1.f -width 2 -height 2 -background lightgray




        pack $top.ft1.tl -side left -fill y
        pack $top.ft1.f -side left -fill y
        pack $top.ft1.tt -side right -fill both -expand 1
        ttk::scrollbar $top.sby -orient vertical
        ttk::scrollbar $top.sbx1 -orient horizontal \
                -command [list $top.ft1.tt xview]
        set ::widgets($top,wLine1) $top.ft1.tl
        set ::widgets($top,wDiff1) $top.ft1.tt

        ttk::frame $top.ft2 -borderwidth 2 -relief sunken
        text $top.ft2.tl -height $::Pref(lines) -width 5 -wrap none \
                -font myfont -borderwidth 0 -padx 0 -highlightthickness 0 \
                -takefocus 0
        text $top.ft2.tt -height $::Pref(lines) -width $::Pref(linewidth) \
                -wrap none \
                -xscrollcommand [list $top.sbx2 set] \
                -font myfont -borderwidth 0 -padx 1 \
                -highlightthickness 0
        $top.ft2.tt configure -tabstyle wordprocessor
        tk::frame $top.ft2.f -width 2 -height 2 -background lightgray
        pack $top.ft2.tl -side left -fill y
        pack $top.ft2.f -side left -fill y
        pack $top.ft2.tt -side right -fill both -expand 1
        ttk::scrollbar $top.sbx2 -orient horizontal \
                -command [list $top.ft2.tt xview]
        set ::widgets($top,wLine2) $top.ft2.tl
        set ::widgets($top,wDiff2) $top.ft2.tt

        # Set up a tag for incremental search bindings
        if {[info procs textSearch::enableSearch] != ""} {
            textSearch::enableSearch $top.ft1.tt -label ::widgets($top,isearchLabel)
            textSearch::enableSearch $top.ft2.tt -label ::widgets($top,isearchLabel)
        }

        # Set up file dropping in text windows if TkDnd is available
        if { ! [catch {package require tkdnd}]} {
            dnd bindtarget $top text/uri-list <Drop> "fileDrop $top any %D"
            dnd bindtarget $top.ft1.tl text/uri-list <Drop> "fileDrop $top left %D"
            dnd bindtarget $top.ft1.tt text/uri-list <Drop> "fileDrop $top left %D"
            dnd bindtarget $top.ft2.tl text/uri-list <Drop> "fileDrop $top right %D"
            dnd bindtarget $top.ft2.tt text/uri-list <Drop> "fileDrop $top right %D"
        }

        # FIXA: verify that this label is ok after Tile migration
        ttk::label $top.ls -width 1 \
                -textvariable ::widgets($top,isearchLabel)
        addBalloon $top.ls "Incremental search indicator"
        set bg [ttk::style configure . -background]
        set map [createMap $top $bg]

        # Edit buttons widget
        set ::widgets($top,wTb) $top.tb
        text $top.tb -width 4 -wrap none -background $bg \
                -font myfont -borderwidth 0 -padx 0 -highlightthickness 0 \
                -takefocus 0
        commonYScroll $top.sby $top.ft1.tl $top.ft1.tt $top.ft2.tl $top.ft2.tt \
                ;#$top.tb

        applyColor
        foreach W [list $top.ft1.tt $top.ft2.tt] {
            # The last change in a row is underlined
            $W tag configure last -underline 1
            # Each file in a patch view starts with a block of this type
            $W tag configure patch -background gray
            # Make sure selection is visible
            $W tag raise sel
            bind $W <ButtonPress-3> "zoomRow %W %X %Y %x %y"
            bind $W <ButtonRelease-3> "unzoomRow %W"
        }
        foreach W [list $top.ft1.tl $top.ft2.tl] {
            $W tag configure align -underline 1
            bind $W <ButtonPress-3> "rowPopup %W %X %Y %x %y"
        }
        SetupAlignDrag $top $top.ft1.tl $top.ft2.tl

        grid $top.l1   $top.le -    -        $top.l2   -row 1 -sticky news
        grid $top.ft1  $top.tb $map $top.sby $top.ft2  -row 2 -sticky news
        grid $top.sbx1 $top.ls -    -        $top.sbx2 -row 3 -sticky news
        grid columnconfigure $top "$top.ft1 $top.ft2" -weight 1
        grid rowconfigure $top $top.ft1 -weight 1
        grid $top.tb -pady 2
        grid $map -pady [expr {[winfo reqwidth $top.sby] - 2}]
        grid $top.ls -sticky ""

        grid remove $top.tb ;# Hide until done

        # Allow scrolling from keys at toplevel
        bind $top <Key-Up>    [list scrollText $top yview scroll -1 u]
        bind $top <Key-k>     [list scrollText $top yview scroll -1 u]
        bind $top <Key-Down>  [list scrollText $top yview scroll  1 u]
        bind $top <Key-j>     [list scrollText $top yview scroll  1 u]
        bind $top <Key-Prior> [list scrollText $top yview scroll -1 pa]
        bind $top <Key-b>     [list scrollText $top yview scroll -1 pa]
        bind $top <Key-Next>  [list scrollText $top yview scroll  1 pa]
        bind $top <Key-space> [list scrollText $top yview scroll  1 pa]
        bind $top <Key-Left>  [list scrollText $top xview scroll -5 u]
        bind $top <Key-h>     [list scrollText $top xview scroll -5 u]
        bind $top <Key-Right> [list scrollText $top xview scroll  5 u]
        bind $top <Key-l>     [list scrollText $top xview scroll  5 u]
        bind $top <Key-Home>  [list scrollText $top yview moveto 0]
        bind $top <Key-g>     [list scrollText $top yview moveto 0]
        bind $top <Key-End>   [list scrollText $top yview moveto 1]
    }

    # Go out to toplevel with escape, whereever you are
    bind $top <Key-Escape> [list focus $top]

    if {$::eskil(debug) == 0} {
        set val [bindtags $top]
        lappend val backDoor$top
        bindtags $top $val
        # Keep this binding on a separate tag, so that other key
        # bindings on the top does not steal the keys
        bind backDoor$top <Key> "backDoor $top %A"
    }







    if {$::eskil(debug) == 1} {

        AddDebugMenu $top





    }





















    resetEdit $top

    return $top
}

proc ValidateNewColors {} {

    foreach item {colorchange bgchange colornew1 bgnew1
        colornew2 bgnew2 colorequal bgequal} {
        if { ! [info exists ::TmpPref($item)]} continue
        set col $::TmpPref($item)
        if {$col eq ""} continue
        if {[catch {winfo rgb . $col}]} {
            # FIXA: Error message
            # Just restore for now
            set ::TmpPref($item) $::Pref($item)
        }
    }
}

# Set new preferences.
proc applyPref {} {


    ValidateNewColors
    array set ::Pref [array get ::TmpPref]
    applyColor
}

# Update test color fields.
proc testColor {} {



    ValidateNewColors
    .pr.fc.t1 tag configure change -foreground $::TmpPref(colorchange) \
            -background $::TmpPref(bgchange)
    .pr.fc.t2 tag configure new1 -foreground $::TmpPref(colornew1) \
            -background $::TmpPref(bgnew1)
    .pr.fc.t3 tag configure new2 -foreground $::TmpPref(colornew2) \
            -background $::TmpPref(bgnew2)
    .pr.fc.t4 tag configure equal -foreground $::TmpPref(colorequal) \
            -background $::TmpPref(bgequal)
}

# Color dialog.
proc selColor {name} {


    set old $::TmpPref($name)
    if {$old eq ""} {
        set t [tk_chooseColor -parent .pr]
    } else {
        set t [tk_chooseColor -parent .pr -initialcolor $old]
    }
    if {$t != ""} {
        set ::TmpPref($name) $t
    }
}

# Create a window for changing preferences.
# Currently only colors are changed in this dialog.
proc makePrefWin {} {


    array set ::TmpPref [array get ::Pref]

    destroy .pr

    toplevel .pr
    wm title .pr "Eskil Preferences"

    ttk::frame .pr.fc -borderwidth 1 -relief solid
    ttk::label .pr.fc.l1 -text "Colours" -anchor w
    ttk::label .pr.fc.l2 -text "Text" -anchor w
    ttk::label .pr.fc.l3 -text "Background" -anchor w

    ttk::entryX .pr.fc.e1 -textvariable "::TmpPref(colorchange)" -width 10
    ttk::entryX .pr.fc.e2 -textvariable "::TmpPref(colornew1)" -width 10
    ttk::entryX .pr.fc.e3 -textvariable "::TmpPref(colornew2)" -width 10
    ttk::entryX .pr.fc.e4 -textvariable "::TmpPref(colorequal)" -width 10

    ttk::button .pr.fc.b1 -text "Sel" -command "selColor colorchange"
    ttk::button .pr.fc.b2 -text "Sel" -command "selColor colornew1"
    ttk::button .pr.fc.b3 -text "Sel" -command "selColor colornew2"
    ttk::button .pr.fc.b4 -text "Sel" -command "selColor colorequal"

    ttk::entryX .pr.fc.e5 -textvariable "::TmpPref(bgchange)" -width 10
    ttk::entryX .pr.fc.e6 -textvariable "::TmpPref(bgnew1)" -width 10
    ttk::entryX .pr.fc.e7 -textvariable "::TmpPref(bgnew2)" -width 10
    ttk::entryX .pr.fc.e8 -textvariable "::TmpPref(bgequal)" -width 10

    ttk::button .pr.fc.b5 -text "Sel" -command "selColor bgchange"
    ttk::button .pr.fc.b6 -text "Sel" -command "selColor bgnew1"
    ttk::button .pr.fc.b7 -text "Sel" -command "selColor bgnew2"
    ttk::button .pr.fc.b8 -text "Sel" -command "selColor bgequal"

    text .pr.fc.t1 -width 12 -height 1 -font myfont -takefocus 0
    text .pr.fc.t2 -width 12 -height 1 -font myfont -takefocus 0
    text .pr.fc.t3 -width 12 -height 1 -font myfont -takefocus 0
    text .pr.fc.t4 -width 12 -height 1 -font myfont -takefocus 0
    .pr.fc.t1 tag configure change -foreground $::TmpPref(colorchange) \
            -background $::TmpPref(bgchange)
    .pr.fc.t2 tag configure new1 -foreground $::TmpPref(colornew1) \
            -background $::TmpPref(bgnew1)
    .pr.fc.t3 tag configure new2 -foreground $::TmpPref(colornew2) \
            -background $::TmpPref(bgnew2)
    .pr.fc.t4 tag configure equal -foreground $::TmpPref(colorequal) \
            -background $::TmpPref(bgequal)
    .pr.fc.t1 insert end "Changed text" change
    .pr.fc.t2 insert end "Deleted text" new1
    .pr.fc.t3 insert end "Added text" new2
    .pr.fc.t4 insert end "Equal text" equal

    .pr.fc.t1 configure -state disabled
    .pr.fc.t2 configure -state disabled
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
    pack .pr.fc -side top -fill x
    pack .pr.b1 .pr.b2 .pr.b3 -side left -expand 1 -fill x -anchor s \
            -padx 2 -pady 2
}

# Change font preference
proc applyFont {lb} {
    global Pref TmpPref

    set Pref(fontsize) $TmpPref(fontsize)

    set i [lindex [$lb curselection] 0]
    set Pref(fontfamily) [$lb get $i]

    chFont
}

# Update example font
proc exampleFont {lb} {
    global TmpPref
    set i [lindex [$lb curselection] 0]
    if {$i eq ""} return
    set TmpPref(fontfamily) [$lb get $i]

    font configure tmpfont -family $TmpPref(fontfamily)
    if {[string is integer -strict $TmpPref(fontsize)]} {
        font configure tmpfont -size $TmpPref(fontsize)
    }
}

proc UpdateFontBox {lb} {
    $lb delete 0 end
    foreach {f fixed} $::FontCache {
        if {$fixed || !$::diff(fixedfont)} {
            $lb insert end $f
            if {[string equal -nocase $f $::Pref(fontfamily)]} {
                $lb selection set end
                $lb see end
            }
        }
    }
}

# Font dialog
proc makeFontWin {} {
    global Pref TmpPref FontCache

    destroy .fo
    toplevel .fo -padx 3 -pady 3
    wm title .fo "Select Font"

    ttk::label .fo.ltmp -text "Searching for fonts..."
    pack .fo.ltmp -padx {10 50} -pady {10 50}
    update

    catch {font delete tmpfont}
    font create tmpfont

    array set TmpPref [array get Pref]
    ttk::labelframe .fo.lf -text "Family" -padding 3
    set lb [Scroll y listbox .fo.lf.lb -width 15 -height 10 \
            -exportselection no -selectmode single]
    bind $lb <<ListboxSelect>> [list exampleFont $lb]
    pack .fo.lf.lb -fill both -expand 1

    ttk::labelframe .fo.ls -text "Size" -padding 3
    spinbox .fo.ls.sp -from 1 -to 30 -increment 1 -width 3 -state readonly \
            -textvariable TmpPref(fontsize) -command [list exampleFont $lb]
    pack .fo.ls.sp -fill both -expand 1

    ttk::label .fo.le -text "Example\n0Ooi1Il" -anchor w -font tmpfont \
            -width 1 -justify left
    if {![info exists ::diff(fixedfont)]} {set ::diff(fixedfont) 1}
    ttk::checkbutton .fo.cb -text "Fixed" -variable ::diff(fixedfont) \
            -command [list UpdateFontBox $lb]
    ttk::button .fo.bo -text "Ok"    -command "applyFont $lb ; destroy .fo"
    ttk::button .fo.ba -text "Apply" -command "applyFont $lb"
    ttk::button .fo.bc -text "Close" -command "destroy .fo"

    if {![info exists FontCache]} {
        set fam [lsort -dictionary [font families]]
        font create testfont
        foreach f $fam {
            if {![string equal $f ""]} {
                font configure testfont -family $f
                lappend FontCache $f [font metrics testfont -fixed]
            }
        }
        font delete testfont
    }
    UpdateFontBox $lb







<
<
|


|






<


|

|
|
|






|











|












|








|




|
|





|



|







3822
3823
3824
3825
3826
3827
3828


3829
3830
3831
3832
3833
3834
3835
3836
3837
3838

3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
    pack .pr.fc -side top -fill x
    pack .pr.b1 .pr.b2 .pr.b3 -side left -expand 1 -fill x -anchor s \
            -padx 2 -pady 2
}

# Change font preference
proc applyFont {lb} {


    set ::Pref(fontsize) $::TmpPref(fontsize)

    set i [lindex [$lb curselection] 0]
    set ::Pref(fontfamily) [$lb get $i]

    chFont
}

# Update example font
proc exampleFont {lb} {

    set i [lindex [$lb curselection] 0]
    if {$i eq ""} return
    set ::TmpPref(fontfamily) [$lb get $i]

    font configure tmpfont -family $::TmpPref(fontfamily)
    if {[string is integer -strict $::TmpPref(fontsize)]} {
        font configure tmpfont -size $::TmpPref(fontsize)
    }
}

proc UpdateFontBox {lb} {
    $lb delete 0 end
    foreach {f fixed} $::FontCache {
        if {$fixed || !$::eskil(fixedfont)} {
            $lb insert end $f
            if {[string equal -nocase $f $::Pref(fontfamily)]} {
                $lb selection set end
                $lb see end
            }
        }
    }
}

# Font dialog
proc makeFontWin {} {
    global FontCache

    destroy .fo
    toplevel .fo -padx 3 -pady 3
    wm title .fo "Select Font"

    ttk::label .fo.ltmp -text "Searching for fonts..."
    pack .fo.ltmp -padx {10 50} -pady {10 50}
    update

    catch {font delete tmpfont}
    font create tmpfont

    array set ::TmpPref [array get ::Pref]
    ttk::labelframe .fo.lf -text "Family" -padding 3
    set lb [Scroll y listbox .fo.lf.lb -width 15 -height 10 \
            -exportselection no -selectmode single]
    bind $lb <<ListboxSelect>> [list exampleFont $lb]
    pack .fo.lf.lb -fill both -expand 1

    ttk::labelframe .fo.ls -text "Size" -padding 3
    spinbox .fo.ls.sp -from 1 -to 30 -increment 1 -width 3 -state readonly \
            -textvariable ::TmpPref(fontsize) -command [list exampleFont $lb]
    pack .fo.ls.sp -fill both -expand 1

    ttk::label .fo.le -text "Example\n0Ooi1Il" -anchor w -font tmpfont \
            -width 1 -justify left
    if { ! [info exists ::eskil(fixedfont)]} {set ::eskil(fixedfont) 1}
    ttk::checkbutton .fo.cb -text "Fixed" -variable ::eskil(fixedfont) \
            -command [list UpdateFontBox $lb]
    ttk::button .fo.bo -text "Ok"    -command "applyFont $lb ; destroy .fo"
    ttk::button .fo.ba -text "Apply" -command "applyFont $lb"
    ttk::button .fo.bc -text "Close" -command "destroy .fo"

    if { ! [info exists FontCache]} {
        set fam [lsort -dictionary [font families]]
        font create testfont
        foreach f $fam {
            if { ! [string equal $f ""]} {
                font configure testfont -family $f
                lappend FontCache $f [font metrics testfont -fixed]
            }
        }
        font delete testfont
    }
    UpdateFontBox $lb
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
    grid .fo.le -      -sticky nwe  -padx 3 -pady 3
    grid .fo.lf -sticky news -rowspan 5
    grid columnconfigure .fo 0 -weight 1
    grid rowconfigure .fo 1 -weight 1

    exampleFont $lb
}

###########################
# Editor for ::Pref(regsub)
###########################

proc EditPrefRegsubOk {top w} {
    set exa $::diff($top,prefregexa)

    set result {}
    for {set t 1} {[info exists ::diff($top,prefregexp$t)]} {incr t} {
        set RE $::diff($top,prefregexp$t)
        set Sub $::diff($top,prefregsub$t)
        if {$RE eq ""} continue

        if {[catch {regsub -all -- $RE $exa $Sub _} err]} {
            return
        }
        lappend result $RE $Sub
    }

    set ::Pref(regsub) $result
    destroy $w

    array unset ::diff $top,prefregexp*
    array unset ::diff $top,prefregsub*
}

proc EditPrefRegsubUpdate {top args} {
    set exa $::diff($top,prefregexa)
    set exa2 $::diff($top,prefregexa2)
    set ok $::widgets($top,prefRegsubOk)

    for {set t 1} {[info exists ::diff($top,prefregexp$t)]} {incr t} {
        set RE $::diff($top,prefregexp$t)
        set Sub $::diff($top,prefregsub$t)

        if {$RE eq ""} continue

        if {[catch {regsub -all -- $RE $exa $Sub result} err]} {
            set ::diff($top,prefregresult) "$t ERROR: $err"
            $ok configure -state disabled
            return
        } else {
            set exa $result
        }
        if {[catch {regsub -all -- $RE $exa2 $Sub result} err]} {
            set ::diff($top,prefregresult2) "$t ERROR: $err"
            $ok configure -state disabled
            return
        } else {
            set exa2 $result
        }
    }
    set ::diff($top,prefregresult2) $exa2
    set ::diff($top,prefregresult) $exa
    $ok configure -state normal
}

proc AddPrefRegsub {top parent} {
    for {set t 1} {[winfo exists $parent.fr$t]} {incr t} {
        #Empty
    }
    set w [ttk::frame $parent.fr$t -borderwidth 2 -relief groove -padding 3]
    ttk::label $w.l1 -text "Regexp:" -anchor "w"
    ttk::entryX $w.e1 -textvariable ::diff($top,prefregexp$t) -width 60
    ttk::label $w.l2 -text "Subst:" -anchor "w"
    ttk::entryX $w.e2 -textvariable ::diff($top,prefregsub$t)

    grid $w.l1 $w.e1 -sticky we -padx 3 -pady 3
    grid $w.l2 $w.e2 -sticky we -padx 3 -pady 3
    grid columnconfigure $w 1 -weight 1

    pack $w -side "top" -fill x -padx 3 -pady 3

    trace add variable ::diff($top,prefregexp$t) write \
            [list EditPrefRegsubUpdate $top]
    trace add variable ::diff($top,prefregsub$t) write \
            [list EditPrefRegsubUpdate $top]
}

# Editor for ::Pref(regsub)
proc EditPrefRegsub {top} {
    set w $top.prefregsub

    if {[winfo exists $w] && [winfo toplevel $w] eq $w} {
        wm deiconify $w
        raise $w
        focus $w
    } else {
        toplevel $w -padx 3 -pady 3
        wm title $w "Preferences: Preprocess"
    }

    ttk::button $w.b -text "Add" -command [list AddPrefRegsub $top $w]

    # Result example part
    if {![info exists ::diff($top,prefregexa)]} {
        set ::diff($top,prefregexa) \
                "An example TextString FOR_REGSUB /* Comment */"
        set ::diff($top,prefregexa2) \
                "An example TextString FOR_REGSUB /* Comment */"
    }
    ttk::labelframe $w.res -text "Preprocessing result" -padding 3
    ttk::label $w.res.l3 -text "Example 1:" -anchor "w"
    ttk::entryX $w.res.e3 -textvariable ::diff($top,prefregexa) -width 60
    ttk::label $w.res.l4 -text "Result 1:" -anchor "w"
    ttk::label $w.res.e4 -textvariable ::diff($top,prefregresult) \
            -anchor "w" -width 10
    ttk::label $w.res.l5 -text "Example 2:" -anchor "w"
    ttk::entryX $w.res.e5 -textvariable ::diff($top,prefregexa2)
    ttk::label $w.res.l6 -text "Result 2:" -anchor "w"
    ttk::label $w.res.e6 -textvariable ::diff($top,prefregresult2) \
            -anchor "w" -width 10

    grid $w.res.l3 $w.res.e3 -sticky we -padx 3 -pady 3
    grid $w.res.l4 $w.res.e4 -sticky we -padx 3 -pady 3
    grid $w.res.l5 $w.res.e5 -sticky we -padx 3 -pady 3
    grid $w.res.l6 $w.res.e6 -sticky we -padx 3 -pady 3
    grid columnconfigure $w.res 1 -weight 1

    # Buttons
    ttk::frame $w.fb -padding 3
    ttk::button $w.fb.b1 -text "Ok"     -command [list EditPrefRegsubOk $top $w]
    ttk::button $w.fb.b2 -text "Cancel" -command [list destroy $w]
    set ::widgets($top,prefRegsubOk) $w.fb.b1

    grid $w.fb.b1 x $w.fb.b2 -sticky we
    grid columnconfigure $w.fb {0 2} -uniform a
    grid columnconfigure $w.fb 1 -weight 1

    # Top layout
    pack $w.b -side "top" -anchor "w" -padx 3 -pady 3 -ipadx 15
    pack $w.fb $w.res -side bottom -fill x -padx 3 -pady 3

    # Fill in existing or an empty line
    if {[llength $::Pref(regsub)] == 0} {
        AddPrefRegsub $top $w
    } else {
        set t 1
        foreach {RE Sub} $::Pref(regsub) {
            set ::diff($top,prefregexp$t) $RE
            set ::diff($top,prefregsub$t) $Sub
            AddPrefRegsub $top $w
            incr t
        }
    }

    trace add variable ::diff($top,prefregexa) write \
            [list EditPrefRegsubUpdate $top]
    trace add variable ::diff($top,prefregexa2) write \
            [list EditPrefRegsubUpdate $top]
    EditPrefRegsubUpdate $top
}

proc defaultGuiOptions {} {
    catch {package require griffin}

    option add *Menu.tearOff 0
    option add *Button.padX 5
    if {[tk windowingsystem] eq "x11"} {
        option add *Menu.activeBorderWidth 1
        option add *Menu.borderWidth 1

        option add *Listbox.exportSelection 0
        option add *Listbox.borderWidth 1
        #option add *Listbox.highlightThickness 1
        option add *Font "Helvetica -12"
        option add *Scrollbar.highlightThickness 0
        option add *Scrollbar.takeFocus 0
    }

    if {$::tcl_platform(platform) eq "windows"} {
        option add *Panedwindow.sashRelief flat
        option add *Panedwindow.sashWidth 4
        option add *Panedwindow.sashPad 0
        #option add *Menubutton.activeBackground SystemHighlight
        #option add *Menubutton.activeForeground SystemHighlightText
        option add *Menubutton.padY 1
    }

    # Use Tahoma 8 as default on Windows, which is the system default
    # on Win2K and WinXP.
    if { [tk windowingsystem] == "win32" } {
        set ASfont "Tahoma 8"
        option add *Button.font             $ASfont widgetDefault
        option add *Checkbutton.font        $ASfont widgetDefault
        option add *Label.font              $ASfont widgetDefault
        option add *Listbox.font            $ASfont widgetDefault
        option add *Menu.font               $ASfont widgetDefault
        option add *Menubutton.font         $ASfont widgetDefault
        option add *Message.font            $ASfont widgetDefault
        option add *Radiobutton.font        $ASfont widgetDefault
        option add *Spinbox.font            $ASfont widgetDefault
    }
}

#####################################
# Startup stuff
#####################################

proc printUsage {} {
    puts {Usage: eskil [options] [file1] [file2]
  [options]              See below.
  [file1],[file2]        Files to be compared
                         If no files are given, the program is
                         started anyway and you can select files
                         from within.
                         If only one file is given, the program
                         looks for version control of the file, and
                         if found, runs in version control mode.
  Options:

  -nodiff     : Normally, if there are enough information on the
                command line to run diff, Eskil will do so unless
                this option is specified.
  -dir        : Start in directory diff mode. Ignores other args.
  -clip       : Start in clip diff mode. Ignores other args.
  -patch      : View patch file.
  -           : Read patch file from standard input, to allow pipes.
  -review     : View revision control tree as a patch.
  -context <n>: Show only differences, with <n> lines of context.
  -foreach    : Open one diff window per file listed.
  -close      : Close windows with no changes.

  -noparse    : Eskil can perform analysis of changed blocks to
  -line       : improve display. See online help for details.
  -smallblock : The default. Do block analysis on small blocks.
  -block      : Full block analysis. This can be slow if there
                are large change blocks.

  -char       : The analysis of changes can be done on either
  -word       : character or word basis. -char is the default.

  -noignore   : Don't ignore any whitespace.
  -b          : Ignore space changes. Default.
  -w          : Ignore all spaces.
  -nocase     : Ignore case changes.
  -nodigit    : Ignore digit changes.
  -nokeyword  : In directory diff, ignore $ Keywords: $
  -nonewline  : Try to ignore newline changes.
  -nonewline+ : Try to ignore newline changes, and don't display.

  -prefix <str> : Care mainly about words starting with "str".
  -preprocess <pair> : TBW

  -r <ver>    : Version info for version control mode.
  -cvs        : Detect CVS first, if multiple version systems are used.
  -svn        : Detect SVN first, if multiple version systems are used.

  -a <file>   : Give anscestor file for three way merge.
  -conflict   : Treat file as a merge conflict file and enter merge
                mode.
  -o <file>   : Specify merge result output file.
  -fine       : Use fine grained chunks. Useful for merging.

  -browse     : Automatically bring up file dialog after starting.
  -server     : Set up Eskil to be controllable from the outside.

  -print <file>          : Generate PDF and exit.
  -printCharsPerLine <n> : Adapt font size for this line length and wrap. (80)
  -printPaper <paper>    : Select paper size (a4)
  -printHeaderSize <n>   : Font size for page header (10)
  -printColorChange <RGB> : Color for change   (1.0 0.7 0.7)
  -printColorOld <RGB>    : Color for old text (0.7 1.0 0.7)
  -printColorNew <RGB     : Color for new text (0.8 0.8 1.0)

  -plugin <name>       : Preprocess files using plugin.
  -plugininfo <info>   : Pass info to plugin (plugin specific)
  -pluginlist          : List known plugins
  -plugindump <plugin> : Dump plugin source to stdout

  -limit <lines> : Do not process more than <lines> lines.

To list all options matching a prefix, run 'eskil --query prefix'.
In tcsh use this line to get option completion:
complete eskil 'C/-/`eskil --query -`/'}
}

# Helper to validate command line option for color
proc ValidatePdfColor {arg opt} {
    set fail 0
    if {![string is list $arg] || [llength $arg] != 3} {
        set fail 1
    } else {
        foreach val $arg {
            if {![string is double -strict $val] || $val < 0.0 || $val > 1.0} {
                set fail 1
            }
        }
    }
    if {$fail} {
        puts "Argument $opt must be a list of RBG values from 0.0 to 1.0"
        exit
    }
}

# Go through all command line arguments and start the appropriate
# diff window.
# Returns the created toplevel.
# This can be used as an entry point if embedding eskil.
# In that case fill in ::eskil(argv) and ::eskil(argc) before calling.
proc parseCommandLine {} {
    global dirdiff Pref

    set ::eskil(autoclose) 0
    set ::eskil(ignorenewline) 0

    if {$::eskil(argc) == 0} {
        Init
        return [makeDiffWin]
    }

    set allOpts {
        -w --help -help -b -noignore -i -nocase -nodigit -nokeyword -prefix
        -noparse -line -smallblock -block -char -word -limit -nodiff -dir
        -clip -patch -browse -conflict -print
        -printHeaderSize -printCharsPerLine -printPaper
        -printColorChange -printColorOld -printColorNew
        -server -o -a -fine -r -context -cvs -svn -review
        -foreach -preprocess -close -nonewline -plugin -plugininfo
        -plugindump -pluginlist
    }

    # If the first option is "--query", use it to ask about options.
    if {$::eskil(argc) == 2 && [lindex $::eskil(argv) 0] == "--query"} {
        set arg [lindex $::eskil(argv) 1]
        if {[lsearch -exact $allOpts $arg] < 0} {
            set match [lsearch -glob -all -inline $allOpts $arg*]
        } else {
            set match [list $arg]
        }
        puts [lsort -dictionary $match]
        exit
    }

    set noautodiff 0
    set autobrowse 0
    set dodir 0
    set doclip 0
    set files ""
    set nextArg ""
    set revNo 1
    set dopatch 0
    set doreview 0
    set foreach 0
    set preferedRev "GIT"
    set plugin ""
    set plugininfo ""
    set plugindump ""
    set pluginlist 0

    foreach arg $::eskil(argv) {
        if {$nextArg != ""} {
            if {$nextArg eq "mergeFile"} {
                set opts(mergeFile) [file join [pwd] $arg]
            } elseif {$nextArg eq "ancestorFile"} {
                set opts(ancestorFile) [file join [pwd] $arg]
            } elseif {$nextArg eq "printFile"} {
                set opts(printFile) [file join [pwd] $arg]
            } elseif {$nextArg eq "printHeaderSize"} {
                if {![string is double -strict $arg] || $arg <= 0} {
                    puts "Argument -printHeaderSize must be a positive number"
                    exit
                }
                set Pref(printHeaderSize) $arg
            } elseif {$nextArg eq "printCharsPerLine"} {
                if {![string is integer -strict $arg] || $arg <= 0} {
                    puts "Argument -printCharsPerLine must be a positive number"
                    exit
                }
                set Pref(printCharsPerLine) $arg
            } elseif {$nextArg eq "printPaper"} {
                package require pdf4tcl
                if {[llength [pdf4tcl::getPaperSize $arg]] != 2} {
                    puts "Argument -printPaper must be a valid paper size"
                    puts "Valid paper sizes:"
                    puts [join [lsort -dictionary [pdf4tcl::getPaperSizeList]] \n]
                    exit
                }
                set Pref(printPaper) $arg
            } elseif {$nextArg eq "printColorChange"} {
                ValidatePdfColor $arg -printColorChange
                set Pref(printColorChange) $arg
            } elseif {$nextArg eq "printColorOld"} {
                ValidatePdfColor $arg -printColorOld
                set Pref(printColorNew1) $arg
            } elseif {$nextArg eq "printColorNew"} {
                ValidatePdfColor $arg -printColorNew
                set Pref(printColorNew2) $arg
            } elseif {$nextArg eq "revision"} {
                set opts(doptrev$revNo) $arg
                incr revNo
            } elseif {$nextArg eq "limitlines"} {
                set opts(limitlines) $arg
            } elseif {$nextArg eq "context"} {
                set Pref(context) $arg
            } elseif {$nextArg eq "prefix"} {
                set RE [string map [list % $arg] {^.*?\m(%\w+).*$}]
                if {$Pref(nocase)} {
                    set RE "(?i)$RE"
                }
                lappend ::Pref(regsub) $RE {\1}
            } elseif {$nextArg eq "plugin"} {
                set plugin $arg
            } elseif {$nextArg eq "plugininfo"} {
                set plugininfo $arg
            } elseif {$nextArg eq "plugindump"} {
                set plugindump $arg
            } elseif {$nextArg eq "preprocess"} {
                if {[catch {llength $arg} len]} {

                } elseif {[llength $arg] % 2 == 1} {

                } else {
                    # FIXA: better validity check
                    foreach {RE sub} $arg {
                        lappend ::Pref(regsub) $RE $sub
                    }
                }
            }
            set nextArg ""
            continue
        }
        # Take care of the special case of RCS style -r<rev>
        if {$arg ne "-review" && [string range $arg 0 1] eq "-r" && \
                [string length $arg] > 2} {
            set opts(doptrev$revNo) [string range $arg 2 end]
            incr revNo
            continue
        }
        # Try to see if it is an unique abbreviation of an option.
        # If not, let it fall through to the file check.
        if {[lsearch -exact $allOpts $arg] < 0} {
            set match [lsearch -glob -all -inline $allOpts $arg*]
            if {[llength $match] == 1} {
                set arg [lindex $match 0]
            }
        }

        if {$arg eq "-w"} {
            set Pref(ignore) "-w"
        } elseif {$arg eq "--help" || $arg eq "-help"} {
            printUsage
            exit
        } elseif {$arg eq "-b"} {
            set Pref(ignore) "-b"
        } elseif {$arg eq "-noignore"} {
            set Pref(ignore) " "
        } elseif {$arg eq "-i"} {
            set Pref(nocase) 1
        } elseif {$arg eq "-nocase"} {
            set Pref(nocase) 1
        } elseif {$arg eq "-noempty"} {
            set Pref(noempty) 1
        } elseif {$arg eq "-nodigit"} {
            set Pref(nodigit) 1
        } elseif {$arg eq "-nokeyword"} {
            set Pref(dir,ignorekey) 1
        } elseif {$arg eq "-prefix"} {
            set nextArg prefix
        } elseif {$arg eq "-preprocess"} {
            set nextArg preprocess
        } elseif {$arg eq "-plugin"} {
            set nextArg "plugin"
        } elseif {$arg eq "-plugininfo"} {
            set nextArg "plugininfo"
        } elseif {$arg eq "-plugindump"} {
            set nextArg "plugindump"
        } elseif {$arg eq "-pluginlist"} {
            set pluginlist 1
        } elseif {$arg eq "-context"} {
            set nextArg context
        } elseif {$arg eq "-noparse"} {
            set Pref(parse) 0
        } elseif {$arg eq "-line"} {
            set Pref(parse) 1
        } elseif {$arg eq "-smallblock"} {
            set Pref(parse) 2
        } elseif {$arg eq "-block"} {
            set Pref(parse) 3
        } elseif {$arg eq "-char"} {
            set Pref(lineparsewords) 0
        } elseif {$arg eq "-word"} {
            set Pref(lineparsewords) 1
        } elseif {$arg eq "-2nd"} { # Deprecated
            #set Pref(extralineparse) 1
        } elseif {$arg eq "-no2nd"} { # Deprecated
            #set Pref(extralineparse) 0
        } elseif {$arg eq "-limit"} {
            set nextArg limitlines
        } elseif {$arg eq "-nodiff"} {
            set noautodiff 1
        } elseif {$arg eq "-dir"} {
            set dodir 1
        } elseif {$arg eq "-clip"} {
            set doclip 1
        } elseif {$arg eq "-patch"} {
            set dopatch 1
        } elseif {$arg eq "-review"} {
            set doreview 1
        } elseif {$arg eq "-browse"} {
            set autobrowse 1
        } elseif {$arg eq "-foreach"} {
            set foreach 1
        } elseif {$arg eq "-nonewline"} {
            set ::eskil(ignorenewline) 1
        } elseif {$arg eq "-nonewline+"} {
            set ::eskil(ignorenewline) 2
        } elseif {$arg eq "-close"} {
            set ::eskil(autoclose) 1
        } elseif {$arg eq "-conflict"} {
            set opts(mode) "conflict"
            # Conflict implies foreach
            set foreach 1
        } elseif {$arg eq "-print" || $arg eq "-printpdf"} {
            set nextArg printFile
        } elseif {$arg in {-printHeaderSize -printCharsPerLine -printPaper \
                -printColorChange -printColorOld -printColorNew}} {
            set nextArg [string range $arg 1 end]
        } elseif {$arg eq "-server"} {
            if {$::tcl_platform(platform) eq "windows"} {
                catch {
                    package require dde
                    dde servername Eskil
                }
            } else {
                package require Tk
                tk appname Eskil
            }
        } elseif {$arg eq "-o"} {
            set nextArg mergeFile
        } elseif {$arg eq "-a"} {
            set nextArg ancestorFile
            # Default is no ignore on three-way merge
            set Pref(ignore) " "
        } elseif {$arg eq "-fine"} {
            set Pref(finegrainchunks) 1
        } elseif {$arg eq "-r"} {
            set nextArg revision
        } elseif {$arg eq "-debug"} {
            set ::eskil(debug) 1
        } elseif {$arg eq "-svn"} {
            set preferedRev "SVN"
        } elseif {$arg eq "-cvs"} {
            set preferedRev "CVS"
        } elseif {$arg eq "-"} {
            # Allow "-" for stdin patch processing
            lappend files "-"
        } else {
            set apa [file normalize [file join [pwd] $arg]]
            if {![file exists $apa]} {
                puts "Bad argument: $arg"
                exit
            } else {
                lappend files $apa
            }
        }
    }

    Init

    if {$pluginlist} {
        printPlugins
        exit
    }
    if {$plugindump ne ""} {
        printPlugin $plugindump
        exit
    }
    if {$plugin ne ""} {
        set pinterp [createPluginInterp $plugin $plugininfo]
        if {$pinterp eq ""} {
            puts "Bad plugin: $plugin"
            printPlugins
            exit
        }
        set opts(plugin) $pinterp
        set opts(pluginname) $plugin
        set opts(plugininfo) $plugininfo
    }

    # Do we start in clip diff mode?
    if {$doclip} {
        return [makeClipDiffWin]
    }

    # Figure out if we start in a diff or dirdiff window.
    set len [llength $files]

    if {$len == 0 && $dodir} {
        set dirdiff(leftDir) [pwd]
        set dirdiff(rightDir) [pwd]
        return [makeDirDiffWin]
    }
    if {$len == 1} {
        set fullname [lindex $files 0]
        if {[FileIsDirectory $fullname 1]} {
            set dirdiff(leftDir) $fullname
            set dirdiff(rightDir) $dirdiff(leftDir)
            return [makeDirDiffWin]
        }
    } elseif {$len >= 2} {
        set fullname1 [lindex $files 0]
        set fullname2 [lindex $files 1]
        if {[FileIsDirectory $fullname1 1] && [FileIsDirectory $fullname2 1]} {
            set dirdiff(leftDir) $fullname1
            set dirdiff(rightDir) $fullname2
            return [makeDirDiffWin]
        }
    }

    # Ok, we have a normal diff
    set top [makeDiffWin]
    update
    # Copy the previously collected options
    foreach {item val} [array get opts] {
        set ::diff($top,$item) $val
    }

    # It is preferable to see the end if the rev string is too long
    $::widgets($top,rev1) xview end
    $::widgets($top,rev2) xview end

    if {$doreview} {
        set rev [detectRevSystem "" $preferedRev]
        set ::diff($top,modetype) $rev
        set ::diff($top,mode) "patch"
        set ::diff($top,patchFile) ""
        set ::diff($top,patchData) ""
        set ::diff($top,reviewFiles) $files
        set ::Pref(toolbar) 1
        after idle [list doDiff $top]
        return $top
    }
    if {$len == 1 || $foreach} {
        set ReturnAfterLoop 0
        set first 1
        foreach file $files {
            if {$first} {
                set first 0
            } else {
                # Create new window for other files
                set top [makeDiffWin]
                update
                # Copy the previously collected options
                foreach {item val} [array get opts] {
                    set ::diff($top,$item) $val
                }
                # It is preferable to see the end if the rev string is too long
                $::widgets($top,rev1) xview end
                $::widgets($top,rev2) xview end
            }
            set fullname $file
            set fulldir [file dirname $fullname]
            if {$::diff($top,mode) eq "conflict"} {
                startConflictDiff $top $fullname
                after idle [list doDiff $top]
                set ReturnAfterLoop 1
                continue
            }
            if {!$autobrowse && !$dopatch} {
                # Check for revision control
                set rev [detectRevSystem $fullname $preferedRev]
                if {$rev ne ""} {
                    startRevMode $top $rev $fullname
                    if {$noautodiff} {
                        enableRedo $top
                    } else {
                        after idle [list doDiff $top]
                    }
                    set ReturnAfterLoop 1
                    continue
                }
            }
            # No revision control. Is it a patch file?
            set ::diff($top,leftDir) $fulldir
            set ::diff($top,leftFile) $fullname
            set ::diff($top,leftLabel) $fullname
            set ::diff($top,leftOK) 1
            if {$dopatch                                 || \
                    [regexp {\.(diff|patch)$} $fullname] || \
                    $fullname eq "-"} {
                set ::diff($top,mode) "patch"
                set ::diff($top,patchFile) $fullname
                set ::diff($top,patchData) ""
                set autobrowse 0
                if {$noautodiff} {
                    enableRedo $top
                } else {
                    after idle [list doDiff $top]
                }
                set ReturnAfterLoop 1
                continue
            }
        }
        if {$ReturnAfterLoop} {return $top}
    } elseif {$len >= 2} {
        set fullname [file join [pwd] [lindex $files 0]]
        set fulldir [file dirname $fullname]
        set ::diff($top,leftDir) $fulldir
        set ::diff($top,leftFile) $fullname
        set ::diff($top,leftLabel) $fullname
        set ::diff($top,leftOK) 1
        set fullname [file join [pwd] [lindex $files 1]]
        set fulldir [file dirname $fullname]
        set ::diff($top,rightDir) $fulldir
        set ::diff($top,rightFile) $fullname
        set ::diff($top,rightLabel) $fullname
        set ::diff($top,rightOK) 1
        if {$noautodiff} {
            enableRedo $top
        } else {
            after idle [list doDiff $top]
        }
    }
    if {$autobrowse && (!$::diff($top,leftOK) || !$::diff($top,rightOK))} {
        if {!$::diff($top,leftOK) && !$::diff($top,rightOK)} {
            openBoth $top 0
        } elseif {!$::diff($top,leftOK)} {
            openLeft $top
        } elseif {!$::diff($top,rightOK)} {
            openRight $top
        }
        # If we cancel the second file and detect CVS, ask about it.
        if {$::diff($top,leftOK) && !$::diff($top,rightOK) && \
                [llength [glob -nocomplain [file join $fulldir CVS]]]} {

            if {[tk_messageBox -title Diff -icon question \
                    -message "Do CVS diff?" -type yesno] eq "yes"} {
                set fullname $::diff($top,leftFile)
                set ::diff($top,leftOK) 0
                startRevMode $top "CVS" $fullname
                after idle [list doDiff $top]
            }
        }
    }
    return $top
}

# Save options to file ~/.eskilrc
proc saveOptions {top} {
    global Pref

    # Check if the window size has changed
    set w $::widgets($top,wDiff1)
    if {[winfo reqwidth $w] != [winfo width $w] || \
            [winfo reqheight $w] != [winfo height $w]} {
        set dx [expr {[winfo width $w] - [winfo reqwidth $w]}]
        set dy [expr {[winfo height $w] - [winfo reqheight $w]}]
        set cx [font measure myfont 0]
        set cy [font metrics myfont -linespace]
        set neww [expr {[$w cget -width]  + $dx / $cx}]
        set newh [expr {[$w cget -height] + $dy / $cy}]
        if {$neww != $Pref(linewidth) || $newh != $Pref(lines)} {
            set apa [tk_messageBox -title "Save Preferences" -icon question \
                    -type yesno -message "Should I save the current window\
                    size with the preferences?\nCurrent: $neww x $newh  Old:\
                    $Pref(linewidth) x $Pref(lines)"]
            if {$apa == "yes"} {
                set Pref(linewidth) $neww
                set Pref(lines) $newh
            }
        }
    }

    set rcfile "~/.eskilrc"
    if {[catch {set ch [open $rcfile "w"]} err]} {
        tk_messageBox -icon error -title "File error" -message \
                "Error when trying to save preferences:\n$err"
        return
    }

    foreach i [array names Pref] {
        # Skip unchanged options.
        if {[info exists ::DefaultPref($i)]} {
            if {$::DefaultPref($i) eq $Pref($i)} {
                continue
            }
        }
        puts $ch [list set Pref($i) $Pref($i)]
    }
    close $ch

    tk_messageBox -icon info -title "Saved" -message \
            "Preferences saved to:\n[file nativename $rcfile]"
}

proc getOptions {} {
    global Pref

    set Pref(fontsize) 8
    # Maybe change to TkFixedFont in 8.5 ?
    set Pref(fontfamily) Courier
    set Pref(ignore) "-b"
    set Pref(nocase) 0
    set Pref(noempty) 0
    set Pref(nodigit) 0
    set Pref(parse) 2
    set Pref(lineparsewords) 0
    set Pref(colorequal) ""
    set Pref(colorchange) red
    set Pref(colornew1) darkgreen
    set Pref(colornew2) blue
    set Pref(bgequal) ""
    set Pref(bgchange) \#ffe0e0
    set Pref(bgnew1) \#a0ffa0
    set Pref(bgnew2) \#e0e0ff
    set Pref(context) -1
    set Pref(finegrainchunks) 0
    set Pref(marklast) 1
    set Pref(linewidth) 80
    set Pref(lines) 60
    set Pref(editor) ""
    set Pref(regsub) {}
    set Pref(toolbar) 0
    set Pref(wideMap) 0 ;# Not settable in GUI yet

    # Print options
    set Pref(printHeaderSize) 10
    set Pref(printCharsPerLine) 80
    set Pref(printPaper) a4
    set Pref(printColorChange) "1.0 0.7 0.7"
    set Pref(printColorNew1)   "0.7 1.0 0.7"
    set Pref(printColorNew2)   "0.8 0.8 1.0"

    # Directory diff options
    set Pref(dir,comparelevel) 1
    set Pref(dir,ignorekey) 0
    set Pref(dir,incfiles) ""
    set Pref(dir,exfiles) "*.o"
    set Pref(dir,incdirs) ""
    set Pref(dir,exdirs) "RCS CVS .git .svn .hg"
    set Pref(dir,onlyrev) 0

    # Store default preferences, to filter saved preferences
    array set ::DefaultPref [array get Pref]

    # Backward compatibilty option
    set Pref(onlydiffs) -1

    set ::diff(filter) ""

    if {![info exists ::eskil_testsuite] && [file exists "~/.eskilrc"]} {
        safeLoad "~/.eskilrc" Pref
    }

    if {$Pref(editor) ne ""} {
        set ::util(editor) $Pref(editor)
    }

    # If the user's file has this old option, translate it to the new
    if {$Pref(onlydiffs) == 0} {
        set Pref(context) -1
    }
    unset Pref(onlydiffs)

    # Set up reactions to some Pref settings
    if {![info exists ::widgets(toolbars)]} {
        set ::widgets(toolbars) {}
    }
    trace add variable ::Pref(toolbar) write TraceToolbar
}

proc TraceToolbar {args} {
    # FIXA: Handle destroyed windows ?
    foreach __ $::widgets(toolbars) {
        if {$::Pref(toolbar)} {
            grid configure $__
        } else {
            grid remove $__
        }
    }
}

# Global code is only run the first time to be able to reread source
if {![info exists ::eskil(gurkmeja)]} {
    set ::eskil(gurkmeja) 1

    package require pstools
    namespace import -force pstools::*
    getOptions
    if {![info exists ::eskil_testsuite]} {
        parseCommandLine
    }
}







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
3918
3919
3920
3921
3922
3923
3924





















































































































































































































































































































































































































































































































































































































































































































































































































































































































    grid .fo.le -      -sticky nwe  -padx 3 -pady 3
    grid .fo.lf -sticky news -rowspan 5
    grid columnconfigure .fo 0 -weight 1
    grid rowconfigure .fo 1 -weight 1

    exampleFont $lb
}





















































































































































































































































































































































































































































































































































































































































































































































































































































































































Added src/fourway.tcl.





































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
#----------------------------------------------------------------------
#  Eskil, Fourway diff section
#
#  Copyright (c) 2018, Peter Spjuth  (peter.spjuth@gmail.com)
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; see the file COPYING.  If not, write to
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------

# Top level dialog, for doing fourway diff
snit::widget FourWay {
    hulltype toplevel
    widgetclass Toplevel
    # Static
    variable fields
    # Gui
    variable filesGui
    variable revsGui
    # Working copy of Gui
    variable files
    variable revs
    # Working variables
    variable origfiles
    variable origrevs
    variable revtype
    variable doingLine1
    variable doingLine2

    constructor {args} {
        eskilRegisterToplevel $win

        wm title $win "Four Way Diff"
        wm protocol $win WM_DELETE_WINDOW "cleanupAndExit $win"
        $hull configure -padx 3 -pady 3

        menu $win.m
        $hull configure -menu $win.m
        $win.m add cascade -menu $win.m.mf -label "File" -underline 0
        menu $win.m.mf
        $win.m.mf add command -label "Close" -underline 0 \
                -command [list cleanupAndExit $win]
        $win.m.mf add separator
        $win.m.mf add command -label "Quit" -underline 0 \
                -command [list cleanupAndExit all]

        $win.m add cascade -menu $win.m.mt -label "Tools" -underline 0
        menu $win.m.mt
        $win.m.mt add command -label "Changeset" -underline 0 \
                -command [mymethod changeset]

        if {$::eskil(debug) == 1} {
            AddDebugMenu $win
        }
 
        # Four files, with optional revision
        set fields {base1 change1 base2 change2}
    
        ttk::label $win.l1 -text "Base 1"
        ttk::label $win.l2 -text "Changed 1"
        ttk::label $win.l3 -text "Base 2"
        ttk::label $win.l4 -text "Changed 2"
        set txt1 {
            First diff is made from Base 1 to Changed 1.\n
            If a file is empty and have a revision, the other file name is used.
        }
        addBalloon $win.l1 -fmt $txt1
        addBalloon $win.l2 -fmt $txt1
        set txt2 [string map {1 2 First Second} $txt1]
        addBalloon $win.l3 -fmt $txt2
        addBalloon $win.l4 -fmt $txt2

        ttk::label $win.el -text "File path"
        ttk::label $win.rl -text "Rev"
        addBalloon $win.rl -fmt {
            If you want to use a revisioned controlled file
            instead of the one on disk, add a revision here.
            E.g. 0 can be used for latest commited revision.
        }

        set n 0
        foreach field $fields {
            incr n
            ttk::entryX $win.e$n -width 60 \
                    -textvariable [myvar filesGui($field)]
            ttk::button $win.b$n -text "Browse" \
                    -command [mymethod browseFile $field]
            ttk::entryX $win.r$n -width 8 \
                    -textvariable [myvar revsGui($field)]
        }

        ttk::button $win.bd -text "Diff" -command [mymethod doFourWayDiff] \
                -underline 0 -width 8
        bind $win <Alt-d> [list $win.bd invoke]

        grid x       $win.el x       $win.rl -sticky w  -padx 3 -pady 3
        grid $win.l1 $win.e1 $win.b1 $win.r1 -sticky we -padx 3 -pady 3
        grid $win.l2 $win.e2 $win.b2 $win.r2 -sticky we -padx 3 -pady 3
        grid $win.l3 $win.e3 $win.b3 $win.r3 -sticky we -padx 3 -pady {10 3}
        grid $win.l4 $win.e4 $win.b4 $win.r4 -sticky we -padx 3 -pady 3
        grid $win.bd -       -                  -padx 3 -pady {10 3}

        grid columnconfigure $win $win.el -weight 1
        
        # Set up file dropping in entry windows if TkDnd is available
        if { ! [catch {package require tkdnd}]} {
            dnd bindtarget $win    text/uri-list <Drop> "[mymethod fileDrop any    ] %D"
            dnd bindtarget $win.e1 text/uri-list <Drop> "[mymethod fileDrop base1  ] %D"
            dnd bindtarget $win.e2 text/uri-list <Drop> "[mymethod fileDrop change1] %D"
            dnd bindtarget $win.e3 text/uri-list <Drop> "[mymethod fileDrop base2  ] %D"
            dnd bindtarget $win.e4 text/uri-list <Drop> "[mymethod fileDrop change2] %D"
        }
    }

    # File drop using TkDnd
    method fileDrop {field filesDropped} {
        if {$field eq "any"} {
            # Dropped outside the entry widgets. Try to be clever.
            set todo {}
            # Drop in empty fields first
            foreach field $fields {
                if {$filesGui($field) eq ""} {
                    lappend todo $field
                }
            }
            # Fill fields otherwise
            if {[llength $todo] == 0} {
                set todo $fields
            }
        } else {
            set todo [list $field]
        }
        foreach fn $filesDropped field $todo {
            # Loop until any list ends
            if {$fn eq "" || $field eq ""} break
            # Sanity check
            if {[file exists $fn]} {
                set filesGui($field) $fn
            }
        }
    }

    # Browse for file
    method browseFile {field} {
        set initDir [pwd]
        if {$filesGui($field) ne ""} {
            set initDir [file dirname $filesGui($field)]
        } else {
            # Pick default dir from other files
            foreach other [lreverse $fields] {
                if {$other eq $field} continue
                puts $other
                if {$filesGui($other) ne ""} {
                    set initDir [file dirname $filesGui($other)]
                    puts $initDir
                    break
                }
            }
        }
        set apa [myOpenFile -title "Select file" -initialdir $initDir \
                         -parent $win]
        if {$apa != ""} {
            set filesGui($field) $apa
        }
    }

    # Fill in working copies of variables
    method PrepareFw {} {
        $self PrepareFw1
        $self PrepareFw2
    }
    method PrepareFw1 {} {
        # Copy to work vars to be able to replace with defaults and parsed
        foreach field $fields {
            set files($field) $filesGui($field)
            set revs($field) [string trim $revsGui($field)]
        }
        # Fill in defaults, if only one file is given
        foreach {from to} $fields {
            if {$files($to) eq ""} {
                set files($to) $filesGui($from)
            }
            if {$files($from) eq ""} {
                set files($from) $filesGui($to)
            }
        }
    }
    method PrepareFw2 {} {
        # Remember originals for display, they might be replaced below
        foreach field $fields {
            set origfiles($field) $files($field)
            set origrevs($field)  $revs($field)
        }
        # Figure out any revisions
        foreach field $fields {
            set revtype($field) ""
            # TODO: Move this to helper function in rev.tcl ?
            if {$revs($field) ne ""} {
                set revtype($field) [detectRevSystem $files($field)]
                if {$revtype($field) eq ""} {
                    tk_messageBox -icon error -title "Eskil Error" \
                            -parent $win -message \
                            "Could not detect version system for file $files($field)"
                    return -level 2
                    # TBD continue
                    set revs($field) ""
                    continue
                }
                set revList [list $revs($field)]
                set revList [eskil::rev::$revtype($field)::ParseRevs \
                                     $files($field) $revList]
                if {[llength $revList] == 0} {
                    tk_messageBox -icon error -title "Eskil Error" \
                            -parent $win -message \
                            "Could not parse revision for file $files($field)"
                    return -level 2
                    # TBD continue
                    set revs($field) ""
                } else {
                    set revs($field) [lindex $revList 0]
                }
            }
        }
    }

    method doFourWayDiff {{skipPrepare 0}} {
        if { ! $skipPrepare} {
            $self PrepareFw
        }
        # Extract revisions
        foreach field $fields {
            if {$revs($field) ne ""} {
                # Replace with checkout copy
                set files($field) [tmpFile]
                eskil::rev::$revtype($field)::get $origfiles($field) \
                        $files($field) $revs($field)
            }
        }

        # Do compare of files, to generate patches
        foreach side {1 2} {
            set header ""
            foreach str {From To} field "base$side change$side" {
                set line "$str $origfiles($field)"
                if {$revs($field) ne ""} {
                    append line "  Revision $revs($field)"
                    if {$origrevs($field) ne $revs($field)} {
                        append line " ($origrevs($field))"
                    }
                }
                append header $line\n
            }

            set outfile($side) [tmpFile]
            $self GenPatch $header $files(base$side) $files(change$side) \
                    $outfile($side)
        }

        # Now run a diff window with the patch files
        set top [newDiff $outfile(1) $outfile(2)]
    }

    # Get the full change in other files corresponding to the ones listed
    method changeset {} {
        $self PrepareFw
        #catch {console show}
        foreach side {1 2} {
            set dir [file dirname $origfiles(base$side)]
            set revL {}
            set type  ""
            if {$revs(base$side) ne ""} {
                lappend revL $revs(base$side)
                set type $revtype(base$side)
            }
            if {$revs(change$side) ne ""} {
                lappend revL $revs(change$side)
                set type $revtype(change$side)
            }
            if {$type eq ""} {
                # TBD error?
                set changes($side) {}
            } else {
                #puts "Getting change list in $dir for $revL"
                set changes($side) [eskil::rev::${type}::getChangedFiles \
                                            $dir $revL]
                set changes($side) [lsort -dictionary $changes($side)]
                #puts [join $changes($side) \n]
            }
        }
        # Look for matching files in the two sets.
        set matching(1) {}
        set matching(2) {}
        # Gather tail data
        foreach side {1 2} {
            foreach f $changes($side) {
                set tail [file tail $f]
                lappend file($side,$tail) $f
                lappend file($side,nc,[string tolower $tail]) $f
            }
        }
        # 1. Unique case-insensitive match in tails
        foreach f1 $changes(1) {
            set tail [string tolower [file tail $f1]]
            if {[llength $file(1,nc,$tail)] == 1} {
                if {[info exists file(2,nc,$tail)]} {
                    if {[llength $file(2,nc,$tail)] == 1} {
                        set f2 [lindex $file(2,nc,$tail) 0]
                        lappend matching(1) $f1
                        lappend matching(2) $f2
                        set done($f1) 1
                        set done($f2) 1
                    }
                }
            }
        }
        # 2. Unique case-sensitive match in tails
        foreach f1 $changes(1) {
            if {[info exists done($f1)]} continue
            set tail [file tail $f1]
            if {[llength $file(1,$tail)] == 1} {
                if {[info exists file(2,$tail)]} {
                    if {[llength $file(2,$tail)] == 1} {
                        set f2 [lindex $file(2,$tail) 0]
                        if {[info exists done($f2)]} continue
                        lappend matching(1) $f1
                        lappend matching(2) $f2
                        set done($f1) 1
                        set done($f2) 1
                    }
                }
            }
        }
        # Rest in order
        foreach side {1 2} {
            set rest($side) {}
            foreach f $changes($side) {
                if {[info exists done($f)]} continue
                lappend rest($side) $f
            }
            lappend matching($side) {*}$rest($side)
        }

        set [myvar csList1] $matching(1)
        set [myvar csList2] $matching(2)

        #destroy $win.csf
        if { ! [winfo exists $win.csf]} {
            ttk::labelframe $win.csf -text "Change Set" -padding 3
            grid $win.csf -columnspan 4 -sticky news -padx 3 -pady 3
            grid rowconfigure $win $win.csf -weight 1

            listbox $win.csf.lb1 -height 20 -listvariable [myvar csList1] \
                    -exportselection 0
            bind $win.csf.lb1 <<ListboxSelect>> [mymethod csNewSelect]
            listbox $win.csf.lb2 -height 20 -listvariable [myvar csList2] \
                    -exportselection 0
            ttk::button $win.csf.bd -text "Diff" -width 8 \
                    -command [mymethod doChangesetDiff]
            grid $win.csf.lb1 $win.csf.lb2 -sticky news -padx 3 -pady 3
            grid $win.csf.bd  -            -padx 3 -pady 3
            grid rowconfigure $win.csf 0 -weight 1
            grid columnconfigure $win.csf all -weight 1 -uniform a
        }
    }
    method csNewSelect {} {
        set s1 [$win.csf.lb1 curselection]
        if {[llength $s1] != 1} return
        $win.csf.lb2 selection clear 0 end
        $win.csf.lb2 selection set $s1

    }

    method doChangesetDiff {} {
        variable csList1
        variable csList2
        set s1 [$win.csf.lb1 curselection]
        set s2 [$win.csf.lb2 curselection]
        if {[llength $s1] != 1} return
        if {[llength $s2] != 1} return
        set f(1) [lindex $csList1 $s1]
        set f(2) [lindex $csList2 $s2]
        puts "$f(1) vs $f(2)"
        
        $self PrepareFw1
        foreach side {1 2} {
            set files(base$side) $f($side)
            set files(change$side) $f($side)
        }
        $self PrepareFw2
        $self doFourWayDiff 1
    }

    method GenPatch {header file1 file2 outfile} {
        # Handle at least base options
        set opts $::Pref(ignore)
        if {$::Pref(nocase)} {lappend opts -nocase}
        if {$::Pref(noempty)} {lappend opts -noempty}
        if {$::Pref(pivot) > 0} {lappend opts -pivot $::Pref(pivot)}

        set differr [catch {DiffUtil::diffFiles {*}$opts \
                    $file1 $file2} diffres]
        set ch [open $outfile w]
        if {$differr != 0} {
            # TODO error
            puts $ch $diffres
            close $ch
            return
        }
        puts $ch [string trim $header]
        puts $ch [string repeat "-" 78]

        set doingLine1 1
        set doingLine2 1
        set ch1 [open $file1]
        set ch2 [open $file2]
        foreach i $diffres {
            lassign $i line1 n1 line2 n2
            $self DoText $ch $ch1 $ch2 $n1 $n2 $line1 $line2
        }
        $self DoText $ch $ch1 $ch2 0 0 0 0
        close $ch1
        close $ch2
        close $ch
    }

    # See dotext in eskil.tcl for more info since this is similar
    method DoText {ch ch1 ch2 n1 n2 line1 line2} {
        if {$n1 == 0 && $n2 == 0} {
            # All blocks have been processed. Continue until end of file.
            # TBD context
            return
        }
        set limit 3
        if {($line1 - $doingLine1 < (2 * $limit + 2))} {
            set limit -1
        }

        # Fill in context before change block

        if {$doingLine1 == 1} {
            set allowStartFill 0
        } else {
            set allowStartFill 1
        }
        set t 0
        while {$doingLine1 < $line1} {
            gets $ch1 apa
            gets $ch2 bepa
            if {$limit < 0 || ($t < $limit && $allowStartFill) || \
                        ($line1 - $doingLine1) <= $limit} {
                # Both sides are supposed to be equal, use one of them
                puts $ch "  $apa"
            } elseif {$t == $limit && $allowStartFill} {
                # TBD empty instead?
                puts $ch [string repeat "-" 78]
            }
            incr doingLine1
            incr doingLine2
            incr t
        }
 
        # Output diff
        for {set t 0} {$t < $n1} {incr t} {
            gets $ch1 apa
            puts $ch "- $apa"
            incr doingLine1
        }
        for {set t 0} {$t < $n2} {incr t} {
            gets $ch2 apa
            puts $ch "+ $apa"
            incr doingLine2
        }
    }
}

proc makeFourWayWin {} {
    set t 1
    set top .fourway$t
    while {[winfo exists $top]} {
        incr t
        set top .fourway$t
    }
    FourWay $top
}

Changes to src/help.tcl.

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90

91







92



















93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------

# Silly experiment...
proc makeNuisance {top {str {Hi there!}}} {
    if {[lsearch [image names] nuisance] < 0} {
        set file [file join $::eskil(thisDir) .. Nuisance.gif]
        if {![file exists $file]} return
        image create photo nuisance -file $file
    }

    destroy $top.nui
    toplevel $top.nui
    wm transient $top.nui $top
    wm geometry $top.nui +400+400
    wm title $top.nui ""
    ttk::label $top.nui.l -image nuisance
    pack $top.nui.l
    wm protocol $top.nui WM_DELETE_WINDOW [list destroy $top.nui2 $top.nui]
    update

    destroy $top.nui2
    toplevel $top.nui2 -background yellow
    wm transient $top.nui2 $top.nui
    wm overrideredirect $top.nui2 1
    wm title $top.nui2 ""
    ttk::label $top.nui2.l -text "$str\nDo you want help?" -justify left \
            -background yellow
    button $top.nui2.b -text "No, get out of my face!" \
            -command [list destroy $top.nui2 $top.nui] -background yellow
    pack $top.nui2.l $top.nui2.b -side "top" -fill x
    wm geometry $top.nui2 +[expr {405 + [winfo width $top.nui]}]+400
}

# A simple window for displaying e.g. help.
# Returns the frame where things can be put.
proc helpWin {w title} {
    destroy $w

    toplevel $w -padx 2 -pady 2
    wm title $w $title
    bind $w <Key-Return> [list destroy $w]
    bind $w <Key-Escape> [list destroy $w]
    ttk::frame $w.f
    ttk::button $w.b -text "Close" -command [list destroy $w] -width 10 \
            -default active
    pack $w.b -side bottom -pady 2
    pack $w.f -side top -expand y -fill both -padx 2 -pady 2
    focus $w
    return $w.f
}

proc makeAboutWin {} {
    set w [helpWin .ab "About Eskil"]

    set bg [ttk::style configure . -background]
    text $w.t -width 45 -height 11 -wrap none -relief flat \
            -background $bg
    pack $w.t -side top -expand y -fill both

    $w.t insert end "A graphical frontend to diff\n\n"
    $w.t insert end "$::eskil(diffver)\n\n"
    $w.t insert end "Made by Peter Spjuth\n"
    $w.t insert end "E-Mail: peter.spjuth@gmail.com\n"
    $w.t insert end "\nURL: http://eskil.berlios.de\n"
    $w.t insert end "\nTcl version: [info patchlevel]\n"

    set du $::DiffUtil::version
    append du " ($::DiffUtil::implementation)"









    $w.t insert end "DiffUtil version: $du\n"



















    $w.t insert end "\nCredits:\n"
    $w.t insert end "Ideas for scrollbar map and merge function\n"
    $w.t insert end "taken from TkDiff"

    set last [lindex [split [$w.t index end] "."] 0]
    $w.t configure -height $last
    $w.t configure -state disabled
}

# Insert a text file into a text widget.
# Any XML-style tags in the file are used as tags in the text window.
proc insertTaggedText {w file} {
    set ch [open $file r]
    set data [read $ch]
    close $ch

    set tags {}
    while {$data != ""} {
        if {[regexp {^([^<]*)<(/?)([^>]+)>(.*)$} $data -> pre sl tag post]} {
            $w insert end [subst -nocommands -novariables $pre] $tags
            set i [lsearch $tags $tag]
            if {$sl != ""} {
                # Remove tag
                if {$i >= 0} {
                    set tags [lreplace $tags $i $i]
                }
            } else {
                # Add tag
                lappend tags $tag
            }
            set data $post
        } else {
            $w insert end [subst -nocommands -novariables $data] $tags
            set data ""
        }
    }
}

proc makeHelpWin {} {
    global Pref

    set doc [file join $::eskil(thisDir) .. doc/eskil.txt]
    if {![file exists $doc]} return

    set w [helpWin .he "Eskil Help"]
    set t [Scroll y text $w.t -width 85 -height 35]
    pack $w.t -side top -expand 1 -fill both

    configureDocWin $t

    # Set up tags for change marks
    $t tag configure new1 -foreground $Pref(colornew1) \
            -background $Pref(bgnew1)
    $t tag configure new2 -foreground $Pref(colornew2) \
            -background $Pref(bgnew2)
    $t tag configure change -foreground $Pref(colorchange) \
            -background $Pref(bgchange)
    $t tag configure ul -underline 1

    set width [font measure [$t cget -font] [string repeat x 20]]
    $t configure -tabs [list $width [expr {$width * 3/2}] [expr {$width * 2}]]

    set width [font measure docFontP [string repeat x 36]]
    $t tag configure example -tabs [list $width] -wrap none







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


|
|

|
|
|
|
|
|

|
|
|
|



|


|

|

|
|
|
|
|
|



>

>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|

|
|
|




|







|












|






<
<

|

|
|
|




|
|
|
|
|
|







18
19
20
21
22
23
24































25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127


128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------
































# A simple window for displaying e.g. help.
# Returns the frame where things can be put.
proc helpWin {W title} {
    destroy $W

    toplevel $W -padx 2 -pady 2
    wm title $W $title
    bind $W <Key-Return> [list destroy $W]
    bind $W <Key-Escape> [list destroy $W]
    ttk::frame $W.f
    ttk::button $W.b -text "Close" -command [list destroy $W] -width 10 \
            -default active
    pack $W.b -side bottom -pady 2
    pack $W.f -side top -expand y -fill both -padx 2 -pady 2
    focus $W
    return $W.f
}

proc makeAboutWin {} {
    set W [helpWin .ab "About Eskil"]

    set bg [ttk::style configure . -background]
    text $W.t -width 45 -height 11 -wrap none -relief flat \
            -background $bg
    pack $W.t -side top -expand y -fill both

    $W.t insert end "A graphical frontend to diff\n\n"
    $W.t insert end "$::eskil(diffver)\n\n"
    $W.t insert end "Made by Peter Spjuth\n"
    $W.t insert end "E-Mail: peter.spjuth@gmail.com\n"
    $W.t insert end "\nURL: http://eskil.tcl.tk\n"
    $W.t insert end "\nTcl version: [info patchlevel]\n"

    set du $::DiffUtil::version
    append du " ($::DiffUtil::implementation)"
    $W.t insert end "DiffUtil version: $du\n"

    # Provide debug info to help when DiffUtil does not load.
    if {[info exists ::DiffUtil::DebugLibFile]} {
        set lf $::DiffUtil::DebugLibFile
        set exist [file exists $lf]
        set lf [file join {*}[lrange [file split $lf] end-1 end]]
        if {$exist} {
            $W.t insert end "  DiffUtil debug: Could not load\n"
            $W.t insert end "    $lf\n"
        } else {
            $W.t insert end "  DiffUtil debug: Could not find\n"
            $W.t insert end "    $lf\n"
        }
    }

    if {[catch {package require pdf4tcl} pdf4tclVer]} { set pdf4tclVer None }
    $W.t insert end "Pdf4Tcl version: $pdf4tclVer\n"
    if {[catch {package require snit} snitVer]} { set snitVer None }
    $W.t insert end "Snit version: $snitVer\n"
    if {[catch {package require vfs} vfsVer]} { set vfsVer None }
    $W.t insert end "Vfs version: $vfsVer\n"
    if {[catch {package require wcb} wcbVer]} { set wcbVer None }
    $W.t insert end "Wcb version: $wcbVer\n"
    if {[catch {package require tablelist_tile} tblVer]} { set tblVer None }
    $W.t insert end "Tablelist version: $tblVer\n"
    if {[catch {package require tkdnd} tkdndVer]} { set tkdndVer None }
    $W.t insert end "TkDnd version: $tkdndVer\n"

    $W.t insert end "\nCredits:\n"
    $W.t insert end "Ideas for scrollbar map and merge function\n"
    $W.t insert end "taken from TkDiff"

    set last [lindex [split [$W.t index end] "."] 0]
    $W.t configure -height $last
    $W.t configure -state disabled
}

# Insert a text file into a text widget.
# Any XML-style tags in the file are used as tags in the text window.
proc insertTaggedText {W file} {
    set ch [open $file r]
    set data [read $ch]
    close $ch

    set tags {}
    while {$data != ""} {
        if {[regexp {^([^<]*)<(/?)([^>]+)>(.*)$} $data -> pre sl tag post]} {
            $W insert end [subst -nocommands -novariables $pre] $tags
            set i [lsearch $tags $tag]
            if {$sl != ""} {
                # Remove tag
                if {$i >= 0} {
                    set tags [lreplace $tags $i $i]
                }
            } else {
                # Add tag
                lappend tags $tag
            }
            set data $post
        } else {
            $W insert end [subst -nocommands -novariables $data] $tags
            set data ""
        }
    }
}

proc makeHelpWin {} {


    set doc [file join $::eskil(thisDir) .. doc/eskil.txt]
    if { ! [file exists $doc]} return

    set W [helpWin .he "Eskil Help"]
    set t [Scroll y text $W.t -width 85 -height 35]
    pack $W.t -side top -expand 1 -fill both

    configureDocWin $t

    # Set up tags for change marks
    $t tag configure new1 -foreground $::Pref(colornew1) \
            -background $::Pref(bgnew1)
    $t tag configure new2 -foreground $::Pref(colornew2) \
            -background $::Pref(bgnew2)
    $t tag configure change -foreground $::Pref(colorchange) \
            -background $::Pref(bgchange)
    $t tag configure ul -underline 1

    set width [font measure [$t cget -font] [string repeat x 20]]
    $t configure -tabs [list $width [expr {$width * 3/2}] [expr {$width * 2}]]

    set width [font measure docFontP [string repeat x 36]]
    $t tag configure example -tabs [list $width] -wrap none
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
    for {} {$t > -20} {incr t -1} {
        font configure docFontP -size $t
        if {[font metrics docFontP -linespace] >= $h} break
    }
}

# Configure a text window as Doc viewer
proc configureDocWin {w} {
    createDocFonts
    $w configure -font docFont -wrap word
    $w tag configure ul -underline 1
    $w tag configure b -font docFontB
    $w tag configure bullet -tabs "1c" -lmargin2 "1c"
    $w tag configure pre -font docFontP

    set top [winfo toplevel $w]
    foreach event {<Key-Prior> <Key-Next>} {
        bind $top $event [string map [list "%W" $w] [bind Text $event]]
    }
}

proc makeDocWin {fileName} {
    set w [helpWin .doc "Eskil Help"]
    set t [Scroll y text $w.t -width 80 -height 25]
    pack $w.t -side top -expand 1 -fill both

    configureDocWin $t

    if {![file exists $::eskil(thisDir)/../doc/$fileName]} {
        $t insert end "ERROR: Could not find doc file "
        $t insert end \"$fileName\"
        return
    }
    insertTaggedText $t $::eskil(thisDir)/../doc/$fileName

    #focus $t
    $t configure -state disabled
}

proc makeTutorialWin {} {
    global Pref

    set doc [file join $::eskil(thisDir) .. doc/tutorial.txt]
    if {![file exists $doc]} return

    if {[catch {cd [file join $::eskil(thisDir) .. examples]}]} {
        tk_messageBox -icon error -title "Eskil Error" -message \
                "Could not locate examples directory." \
                -type ok
        return
    }
    #set ::diff(tutorial) 1

    # Start up a dirdiff in the examples directory
    set ::dirdiff(leftDir) [file join [pwd] dir1]
    set ::dirdiff(rightDir) [file join [pwd] dir2]
    makeDirDiffWin

    set w [helpWin .ht "Eskil Tutorial"]

    text $w.t -width 82 -height 35 -yscrollcommand "$w.sb set"
    scrollbar $w.sb -orient vert -command "$w.t yview"
    pack $w.sb -side right -fill y
    pack $w.t -side left -expand 1 -fill both

    configureDocWin $w.t

    # Move border properties to frame
    set bw [$w.t cget -borderwidth]
    set relief [$w.t cget -relief]
    $w configure -relief $relief -borderwidth $bw
    $w.t configure -borderwidth 0

    insertTaggedText $w.t $doc
    $w.t configure -state disabled
}







|

|
|
|
|
|

|

|




|
|
|



|











<
<

|







<






|

|
|
|
|

|


|
|
|
|

|
|

164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203


204
205
206
207
208
209
210
211
212

213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
    for {} {$t > -20} {incr t -1} {
        font configure docFontP -size $t
        if {[font metrics docFontP -linespace] >= $h} break
    }
}

# Configure a text window as Doc viewer
proc configureDocWin {W} {
    createDocFonts
    $W configure -font docFont -wrap word
    $W tag configure ul -underline 1
    $W tag configure b -font docFontB
    $W tag configure bullet -tabs "1c" -lmargin2 "1c"
    $W tag configure pre -font docFontP

    set top [winfo toplevel $W]
    foreach event {<Key-Prior> <Key-Next>} {
        bind $top $event [string map [list "%W" $W] [bind Text $event]]
    }
}

proc makeDocWin {fileName} {
    set W [helpWin .doc "Eskil Help"]
    set t [Scroll y text $W.t -width 80 -height 25]
    pack $W.t -side top -expand 1 -fill both

    configureDocWin $t

    if { ! [file exists $::eskil(thisDir)/../doc/$fileName]} {
        $t insert end "ERROR: Could not find doc file "
        $t insert end \"$fileName\"
        return
    }
    insertTaggedText $t $::eskil(thisDir)/../doc/$fileName

    #focus $t
    $t configure -state disabled
}

proc makeTutorialWin {} {


    set doc [file join $::eskil(thisDir) .. doc/tutorial.txt]
    if { ! [file exists $doc]} return

    if {[catch {cd [file join $::eskil(thisDir) .. examples]}]} {
        tk_messageBox -icon error -title "Eskil Error" -message \
                "Could not locate examples directory." \
                -type ok
        return
    }


    # Start up a dirdiff in the examples directory
    set ::dirdiff(leftDir) [file join [pwd] dir1]
    set ::dirdiff(rightDir) [file join [pwd] dir2]
    makeDirDiffWin

    set W [helpWin .ht "Eskil Tutorial"]

    text $W.t -width 82 -height 35 -yscrollcommand "$W.sb set"
    ttk::scrollbar $W.sb -orient vert -command "$W.t yview"
    pack $W.sb -side right -fill y
    pack $W.t -side left -expand 1 -fill both

    configureDocWin $W.t

    # Move border properties to frame
    set bw [$W.t cget -borderwidth]
    set relief [$W.t cget -relief]
    $W configure -relief $relief -borderwidth $bw
    $W.t configure -borderwidth 0

    insertTaggedText $W.t $doc
    $W.t configure -state disabled
}

Changes to src/map.tcl.

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

49
50
51





52
53
54
55
56
57
58
59
60
61
62
63

64

65






66























67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------

proc createMap {top} {
    global Pref

    set w $top.c_map
    if {$Pref(wideMap)} {
        set width 20
    } else {
        set width 6
    }
    canvas $w -width $width -borderwidth 0 -selectborderwidth 0 \
            -highlightthickness 0 -height 10
    set map [image create photo map$top]

    $w create image 0 0 -anchor nw -image $map
    bind $w <Destroy>   [list image delete $map]
    bind $w <Configure> [list drawMap $top %h]
    bind $w <Button-2>  [list ThumbMap $top %y]

    return $w
}

proc clearMap {top} {
    set ::diff($top,changes) {}
    set ::diff($top,mapMax) 0

    drawMap $top -1
}






proc addChange {top n tag line1 n1 line2 n2} {
    if {$tag ne ""} {
        lappend ::diff($top,changes) [list $::diff($top,mapMax) $n \
                $tag $line1 $n1 $line2 $n2]
    }
    incr ::diff($top,mapMax) $n
}

proc addMapLines {top n} {
    incr ::diff($top,mapMax) $n
}


proc drawMap {top newh} {

    global Pref






























    set oldh [map$top cget -height]
    if {$oldh == $newh} return

    map$top blank
    if {![info exists ::diff($top,changes)] || \
	    [llength $::diff($top,changes)] == 0} return

    set w [winfo width $top.c_map]
    set h [winfo height $top.c_map]
    set x2 [expr {$w - ($Pref(wideMap) ? 5 : 1)}]
    if {$x2 < 0} { set x2 0 }
    map$top configure -width $w -height $h
    incr h -1
    set y0 0
    foreach change $::diff($top,changes) {
	lassign $change start length type
	set y1 [expr {$start * $h / $::diff($top,mapMax) + 1}]
	if {!$y0} { set y0 $y1 } ;# Record first occurance
	if {$y1 < 1} {set y1 1}
	if {$y1 > $h} {set y1 $h}
	set y2 [expr {($start + $length) * $h / $::diff($top,mapMax) + 1}]
	if {$y2 < 1} {set y2 1}
	if {$y2 <= $y1} {set y2 [expr {$y1 + 1}]}
	if {$y2 > $h} {set y2 $h}
	incr y2
	map$top put $Pref(color$type) -to 1 $y1 $x2 $y2
    }
    if {$Pref(wideMap)} {
        map$top put black -to $x2 $y0 $w $y2
    }
}

# Allow button 2 on map to jump to a position
proc ThumbMap  {top y} {
    incr y 15







|
<
<

|





|











|
|
>



>
>
>
>
>
|
|
|


|


|
|


>
|
>
|
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




|
|



|




|

|
|


|




|

|







18
19
20
21
22
23
24
25


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------

proc createMap {top bg} {


    set w $top.c_map
    if {$::Pref(wideMap)} {
        set width 20
    } else {
        set width 6
    }
    canvas $w -width $width -borderwidth 0 -selectborderwidth 0 \
            -highlightthickness 0 -height 10 -background $bg
    set map [image create photo map$top]

    $w create image 0 0 -anchor nw -image $map
    bind $w <Destroy>   [list image delete $map]
    bind $w <Configure> [list drawMap $top %h]
    bind $w <Button-2>  [list ThumbMap $top %y]

    return $w
}

proc clearMap {top} {
    set ::eskil($top,changes) {}
    set ::eskil($top,mapMax) 0
    set ::eskil($top,mapNoChange) 0
    drawMap $top -1
}

# Temporarily ignore changes added by addChange.
proc mapNoChange {top value} {
    set ::eskil($top,mapNoChange) $value
}

proc addChange {top nLines tag line1 n1 line2 n2} {
    if {$tag ne "" && $::eskil($top,mapNoChange) == 0} {
        lappend ::eskil($top,changes) [list $::eskil($top,mapMax) $nLines \
                $tag $line1 $n1 $line2 $n2]
    }
    incr ::eskil($top,mapMax) $nLines
}

proc addMapLines {top nLines} {
    incr ::eskil($top,mapMax) $nLines
}

# Use the assembled information for the map to draw edit buttons
proc drawEditButtons {top} {
    $::widgets($top,wTb) delete 1.0 end
    set l 0
    foreach change $::eskil($top,changes) {
	lassign $change start length type line1 n1 line2 n2
        set pre [expr {($length - 1) / 2}]
        while {$l < ($start + $pre)} {
            incr l
            $::widgets($top,wTb) insert end \n
        }
        incr l
        $::widgets($top,wTb) image create end -image $::img(left) \
                -pady -2 -padx 1 -name li$l
        $::widgets($top,wTb) image create end -image $::img(right) \
                -pady -2 -name ri$l
        $::widgets($top,wTb) tag add lt$l li$l
        $::widgets($top,wTb) tag add rt$l ri$l
        # Make visible for now
        $::widgets($top,wTb) tag configure lt$l -background pink
        $::widgets($top,wTb) tag configure rt$l -background lightgreen
        $::widgets($top,wTb) insert end \n
        while {$l < ($start+ $length)} {
            incr l
            $::widgets($top,wTb) insert end \n
        }
    }
    while {$l < $::eskil($top,mapMax)} {
        incr l
        $::widgets($top,wTb) insert end \n
    }
}

proc drawMap {top newh} {
    set oldh [map$top cget -height]
    if {$oldh == $newh} return

    map$top blank
    if { ! [info exists ::eskil($top,changes)] || \
	    [llength $::eskil($top,changes)] == 0} return

    set w [winfo width $top.c_map]
    set h [winfo height $top.c_map]
    set x2 [expr {$w - ($::Pref(wideMap) ? 5 : 1)}]
    if {$x2 < 0} { set x2 0 }
    map$top configure -width $w -height $h
    incr h -1
    set y0 0
    foreach change $::eskil($top,changes) {
	lassign $change start length type
	set y1 [expr {$start * $h / $::eskil($top,mapMax) + 1}]
	if { ! $y0} { set y0 $y1 } ;# Record first occurance
	if {$y1 < 1} {set y1 1}
	if {$y1 > $h} {set y1 $h}
	set y2 [expr {($start + $length) * $h / $::eskil($top,mapMax) + 1}]
	if {$y2 < 1} {set y2 1}
	if {$y2 <= $y1} {set y2 [expr {$y1 + 1}]}
	if {$y2 > $h} {set y2 $h}
	incr y2
	map$top put $::Pref(color$type) -to 1 $y1 $x2 $y2
    }
    if {$::Pref(wideMap)} {
        map$top put black -to $x2 $y0 $w $y2
    }
}

# Allow button 2 on map to jump to a position
proc ThumbMap  {top y} {
    incr y 15

Changes to src/merge.tcl.

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105

106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------

# Get all data from the files to merge
proc collectMergeData {top} {
    global diff

    set diff($top,leftMergeData) {}
    set diff($top,rightMergeData) {}
    set diff($top,mergeSelection,AnyConflict) 0

    if {![info exists ::diff($top,changes)]} {
        set ::diff($top,changes) {}
    }

    prepareFiles $top

    set ch1 [open $::diff($top,leftFile) r]
    set ch2 [open $::diff($top,rightFile) r]
    set doingLine1 1
    set doingLine2 1
    set changeNo 0
    foreach change $::diff($top,changes) {
        lassign $change start length type line1 n1 line2 n2
        set data1 {}
        set data2 {}
        while {$doingLine1 < $line1} {
            gets $ch1 apa
            append data1 $apa\n
            incr doingLine1
        }
        while {$doingLine2 < $line2} {
            gets $ch2 apa
            append data2 $apa\n
            incr doingLine2
        }
        lappend diff($top,leftMergeData) $data1
        lappend diff($top,rightMergeData) $data2

        set data1 {}
        set data2 {}
        for {set t 0} {$t < $n1} {incr t} {
            gets $ch1 apa
            append data1 $apa\n
            incr doingLine1
        }
        for {set t 0} {$t < $n2} {incr t} {
            gets $ch2 apa
            append data2 $apa\n
            incr doingLine2
        }
        lappend diff($top,leftMergeData) $data1
        lappend diff($top,rightMergeData) $data2
        set diff($top,mergeSelection,$changeNo) \
                [WhichSide $top $line1 $n1 $line2 $n2 conflict comment]
        set diff($top,mergeSelection,Conflict,$changeNo) $conflict
        set diff($top,mergeSelection,Comment,$changeNo) $comment



        if {$conflict} {
            set diff($top,mergeSelection,AnyConflict) 1
        }
        incr changeNo
    }
    set data1 {}
    set data2 {}
    while {[gets $ch1 apa] != -1} {
        append data1 $apa\n
        incr doingLine1
    }
    while {[gets $ch2 apa] != -1} {
        append data2 $apa\n
        incr doingLine2
    }
    lappend diff($top,leftMergeData) $data1
    lappend diff($top,rightMergeData) $data2

    close $ch1
    close $ch2

    cleanupFiles $top
}

# Fill up the merge window with the initial version of merged files.
proc fillMergeWindow {top} {
    global diff

    set w $top.merge.t

    $w delete 1.0 end
    set marks {}
    set t 0
    set firstConflict -1
    foreach {commLeft diffLeft} $diff($top,leftMergeData) \
            {commRight diffRight} $diff($top,rightMergeData) {
        $w insert end $commRight
        if {![info exists diff($top,mergeSelection,$t)]} continue
        $w mark set merges$t insert
        $w mark gravity merges$t left
        switch $diff($top,mergeSelection,$t) {
            1 { $w insert end $diffLeft merge$t }
            2 { $w insert end $diffRight merge$t }
            12 { $w insert end $diffLeft merge$t 
                $w insert end $diffRight merge$t }
            21 { $w insert end $diffRight merge$t
                $w insert end $diffLeft merge$t  }
        }
        if {$diff($top,mergeSelection,Conflict,$t)} {
            $w tag configure merge$t -background grey
            if {$firstConflict == -1} {
                set firstConflict $t
            }
        }
        lappend marks mergee$t [$w index insert]
        incr t







|

|
|
|

|
|




|
|



|













|
|













|
|
|
|
|
|
>
>
>

|













|
|









|


>




|
|

|


|







|







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------

# Get all data from the files to merge
proc collectMergeData {top} {
    global eskil

    set eskil($top,leftMergeData) {}
    set eskil($top,rightMergeData) {}
    set eskil($top,mergeSelection,AnyConflict) 0

    if { ! [info exists eskil($top,changes)]} {
        set eskil($top,changes) {}
    }

    prepareFiles $top

    set ch1 [open $eskil($top,leftFile) r]
    set ch2 [open $eskil($top,rightFile) r]
    set doingLine1 1
    set doingLine2 1
    set changeNo 0
    foreach change $eskil($top,changes) {
        lassign $change start length type line1 n1 line2 n2
        set data1 {}
        set data2 {}
        while {$doingLine1 < $line1} {
            gets $ch1 apa
            append data1 $apa\n
            incr doingLine1
        }
        while {$doingLine2 < $line2} {
            gets $ch2 apa
            append data2 $apa\n
            incr doingLine2
        }
        lappend eskil($top,leftMergeData) $data1
        lappend eskil($top,rightMergeData) $data2

        set data1 {}
        set data2 {}
        for {set t 0} {$t < $n1} {incr t} {
            gets $ch1 apa
            append data1 $apa\n
            incr doingLine1
        }
        for {set t 0} {$t < $n2} {incr t} {
            gets $ch2 apa
            append data2 $apa\n
            incr doingLine2
        }
        lappend eskil($top,leftMergeData) $data1
        lappend eskil($top,rightMergeData) $data2
        set eskil($top,mergeSelection,$changeNo) \
                [WhichSide $top $line1 $n1 $line2 $n2 conflict comment ancLines]
        set eskil($top,mergeSelection,Conflict,$changeNo) $conflict
        set eskil($top,mergeSelection,Comment,$changeNo) $comment
        set ancLines [lsort -dictionary -unique $ancLines]
        set eskil($top,mergeSelection,AncLines,$changeNo) \
                "Lines from ancestor file:\n[join $ancLines \n]"
        if {$conflict} {
            set eskil($top,mergeSelection,AnyConflict) 1
        }
        incr changeNo
    }
    set data1 {}
    set data2 {}
    while {[gets $ch1 apa] != -1} {
        append data1 $apa\n
        incr doingLine1
    }
    while {[gets $ch2 apa] != -1} {
        append data2 $apa\n
        incr doingLine2
    }
    lappend eskil($top,leftMergeData) $data1
    lappend eskil($top,rightMergeData) $data2

    close $ch1
    close $ch2

    cleanupFiles $top
}

# Fill up the merge window with the initial version of merged files.
proc fillMergeWindow {top} {
    global eskil

    set w $top.merge.t
    ##nagelfar vartype w _obj,text
    $w delete 1.0 end
    set marks {}
    set t 0
    set firstConflict -1
    foreach {commLeft diffLeft} $eskil($top,leftMergeData) \
            {commRight diffRight} $eskil($top,rightMergeData) {
        $w insert end $commRight
        if { ! [info exists eskil($top,mergeSelection,$t)]} continue
        $w mark set merges$t insert
        $w mark gravity merges$t left
        switch $eskil($top,mergeSelection,$t) {
            1 { $w insert end $diffLeft merge$t }
            2 { $w insert end $diffRight merge$t }
            12 { $w insert end $diffLeft merge$t 
                $w insert end $diffRight merge$t }
            21 { $w insert end $diffRight merge$t
                $w insert end $diffLeft merge$t  }
        }
        if {$eskil($top,mergeSelection,Conflict,$t)} {
            $w tag configure merge$t -background grey
            if {$firstConflict == -1} {
                set firstConflict $t
            }
        }
        lappend marks mergee$t [$w index insert]
        incr t
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157


158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199


200
201
202
203
204

205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401

402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442


443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466

467
468
469
470
471
472
473
    $w mark set merges[expr {$t + 1}] end

    set showFirst 0
    if {$firstConflict != -1} {
        set showFirst $firstConflict
    }

    set diff($top,curMerge) $showFirst
    set diff($top,curMergeSel) $diff($top,mergeSelection,$showFirst)
    $w tag configure merge$showFirst -foreground red
    showDiff $top $showFirst
    update
    # If there is any diff, show the first
    if {$t > 0} {
        seeText $w merges$showFirst mergee$showFirst
        # Show status for first chunk
        set diff($top,mergeStatus) \
                $diff($top,mergeSelection,Comment,$showFirst)


    }
}

# Move to and highlight another diff.
proc nextMerge {top delta} {
    global diff

    set w $top.merge.t
    $w tag configure merge$diff($top,curMerge) -foreground ""

    set last [expr {[llength $diff($top,leftMergeData)] / 2 - 1}]

    if {$delta == -1000} {
        # Search backward for conflict
        for {set t [expr {$diff($top,curMerge) - 1}]} {$t >= 0} {incr t -1} {
            if {$diff($top,mergeSelection,Conflict,$t)} {
                set delta [expr {$t - $diff($top,curMerge)}]
                break
            }
        }
    } elseif {$delta == 1000} {
        # Search forward for conflict
        for {set t [expr {$diff($top,curMerge) + 1}]} {$t <= $last} {incr t} {
            if {$diff($top,mergeSelection,Conflict,$t)} {
                set delta [expr {$t - $diff($top,curMerge)}]
                break
            }
        }
    }

    set diff($top,curMerge) [expr {$diff($top,curMerge) + $delta}]
    if {$diff($top,curMerge) < 0} {set diff($top,curMerge) 0}
    if {$diff($top,curMerge) > $last} {
        set diff($top,curMerge) $last
    }
    set diff($top,curMergeSel) $diff($top,mergeSelection,$diff($top,curMerge))
    $w tag configure merge$diff($top,curMerge) -foreground red
    showDiff $top $diff($top,curMerge)
    seeText $w merges$diff($top,curMerge) mergee$diff($top,curMerge)

    set diff($top,mergeStatus) \
            $diff($top,mergeSelection,Comment,$diff($top,curMerge))


}

# Select a merge setting for all diffs.
proc selectMergeAll {top new} {
    global diff

    set end [expr {[llength $diff($top,leftMergeData)] / 2}]
    for {set t 0} {$t < $end} {incr t} {
        selectMerge2 $top $t $new
    }
    set diff($top,curMergeSel) $new
    set w $top.merge.t
    seeText $w merges$diff($top,curMerge) mergee$diff($top,curMerge)
}

# Change merge setting fo current diff.
proc selectMerge {top} {
    global diff

    set w $top.merge.t
    selectMerge2 $top $diff($top,curMerge) $diff($top,curMergeSel)
    seeText $w merges$diff($top,curMerge) mergee$diff($top,curMerge)
}

# Change merge setting for a diff.
proc selectMerge2 {top no new} {
    global diff

    set w $top.merge.t
    # Delete current string
    $w delete merges$no mergee$no

    set diff($top,mergeSelection,$no) $new

    set i [expr {$no * 2 + 1}]
    set diffLeft [lindex $diff($top,leftMergeData) $i]
    set diffRight [lindex $diff($top,rightMergeData) $i]

    # Temporarily switch surrounding marks
    # Two steps are enough since there can't be consecutive empty areas
    # The one before and/or the one after the one being switch might
    # be empty.
    $w mark gravity mergee[expr {$no - 2}] left
    $w mark gravity mergee[expr {$no - 1}] left
    $w mark gravity merges[expr {$no + 1}] right
    $w mark gravity merges[expr {$no + 2}] right

    if {$diff($top,mergeSelection,$no) == 12} {
        $w insert merges$no $diffLeft$diffRight merge$no
    } elseif {$diff($top,mergeSelection,$no) == 21} {
        $w insert merges$no $diffRight$diffLeft merge$no
    } elseif {$diff($top,mergeSelection,$no) == 1} {
        $w insert merges$no $diffLeft merge$no
    } elseif {$diff($top,mergeSelection,$no) == 2} {
        $w insert merges$no $diffRight merge$no
    }
    # Switch back surrounding marks
    $w mark gravity mergee[expr {$no - 2}] right
    $w mark gravity mergee[expr {$no - 1}] right
    $w mark gravity merges[expr {$no + 1}] left
    $w mark gravity merges[expr {$no + 2}] left
}

# Save the merge result.
proc saveMerge {top} {
    set w $top.merge.t

    if {$::diff($top,mergeFile) eq "" && $::diff($top,mode) eq "conflict"} {
        set apa [tk_messageBox -parent $top.merge -icon question \
                -title "Save merge file" -type yesno -message \
                "Do you want to overwrite the original conflict file?"]
        if {$apa == "yes"} {
            set ::diff($top,mergeFile) $::diff($top,conflictFile)
        }
    }
    if {$::diff($top,mergeFile) eq ""} {
        # Ask user which file
        set buttons {}
        set text "Overwrite file or Browse?"
        if {[file exists $::diff($top,leftFile)] && \
                $::diff($top,leftFile) eq $::diff($top,leftLabel)} {
            lappend buttons Left
            append text "\nLeft: $::diff($top,leftFile)"
        }
        if {[file exists $::diff($top,rightFile)] && \
                $::diff($top,rightFile) eq $::diff($top,rightLabel)} {
            lappend buttons Right
            append text "\nRight: $::diff($top,rightFile)"
        }
        lappend buttons Browse Cancel
        if {[llength $buttons] > 2} {
            set apa [tk_dialog .savemerge "Save merge file" \
                    $text \
                    questhead -1 {*}$buttons]
            if {$apa < 0} return
            set apa [lindex $buttons $apa]
            if {$apa eq "Left"} {
                set ::diff($top,mergeFile) $::diff($top,leftFile)
            } elseif {$apa eq "Right"} {
                set ::diff($top,mergeFile) $::diff($top,rightFile)
            } elseif {$apa eq "Cancel"} {
                return
            }
        }
        if {$::diff($top,mergeFile) eq ""} {
            # Browse
            if {[info exists ::diff($top,rightDir)]} {
                set initDir $::diff($top,rightDir)
            } elseif {[info exists ::diff($top,leftDir)]} {
                set initDir $::diff($top,leftDir)
            } else {
                set initDir [pwd]
            }

            set apa [tk_getSaveFile -title "Save merge file" -initialdir $initDir \
                    -parent $top.merge]
            if {$apa eq ""} return
            set ::diff($top,mergeFile) $apa
        }
    }

    set ch [open $::diff($top,mergeFile) "w"]
    fconfigure $ch -translation $::diff($top,mergetranslation)
    puts -nonewline $ch [$w get 1.0 end-1char]
    close $ch

    # Detect if this is a GIT merge, and possibly add it to the index
    # after save (i.e. git add file)
    if {[detectRevSystem $::diff($top,mergeFile)] eq "GIT"} {
        set apa [tk_messageBox -parent $top.merge -icon info -type yesno \
                -title "Diff" \
                -message "Saved merge to file $::diff($top,mergeFile).\nAdd\
                it to GIT index?"]
        if {$apa eq "yes"} {
            eskil::rev::GIT::add $::diff($top,mergeFile)
        }
    } else {
        tk_messageBox -parent $top.merge -icon info -type ok -title "Diff" \
                -message "Saved merge to file $::diff($top,mergeFile)."
    }
}

# Close merge window and clean up.
proc closeMerge {top} {
    global diff

    destroy $top.merge
    set diff($top,leftMergeData) {}
    set diff($top,rightMergeData) {}
    array unset diff $top,mergeSelection,*
}

# Create a window to display merge result.
proc makeMergeWin {top} {
    collectMergeData $top
    if {![info exists ::diff($top,mergetranslation)]} {
        if {$::tcl_platform(platform) eq "windows"} {
            set ::diff($top,mergetranslation) crlf
        } else {
            set ::diff($top,mergetranslation) lf
        }
    }

    set w $top.merge
    if {![winfo exists $w]} {
        toplevel $w
    } else {
        destroy {*}[winfo children $w]
    }
    set anyC $::diff($top,mergeSelection,AnyConflict)

    wm title $w "Merge result: [TitleTail $top]"

    menu $w.m
    $w configure -menu $w.m
    $w.m add cascade -label "File" -underline 0 -menu $w.m.mf
    menu $w.m.mf
    $w.m.mf add command -label "Save" -underline 0 -command "saveMerge $top"
    $w.m.mf add separator
    $w.m.mf add command -label "Close" -underline 0 -command "closeMerge $top"

    $w.m add cascade -label "Select" -underline 0 -menu $w.m.ms
    menu $w.m.ms
    $w.m.ms add radiobutton -label "Left+Right"         -value 12 \
            -variable diff($top,curMergeSel) -command "selectMerge $top"
    $w.m.ms add radiobutton -label "Left" -underline 0  -value 1  \
            -variable diff($top,curMergeSel) -command "selectMerge $top"
    $w.m.ms add radiobutton -label "Right" -underline 0 -value 2  \
            -variable diff($top,curMergeSel) -command "selectMerge $top"
    $w.m.ms add radiobutton -label "Right+Left"         -value 21 \
            -variable diff($top,curMergeSel) -command "selectMerge $top"
    $w.m.ms add separator
    $w.m.ms add command -label "All Left"  -command "selectMergeAll $top 1"
    $w.m.ms add command -label "All Right" -command "selectMergeAll $top 2"

    $w.m add cascade -label "Goto" -underline 0 -menu $w.m.mg
    menu $w.m.mg
    $w.m.mg add command -accelerator "Up" -label "Previous" -command "nextMerge $top -1"
    $w.m.mg add command -accelerator "Down" -label "Next" -command "nextMerge $top 1"
    if {$anyC} {
        $w.m.mg add command -accelerator "Shift-Up" -label "Previous Conflict" -command "nextMerge $top -1000"
        $w.m.mg add command -accelerator "Shift-Down" -label "Next Conflict" -command "nextMerge $top 1000"
    } else {

        $w.m.mg add command -accelerator "Shift-Up" -label "Previous 10" -command "nextMerge $top -10"
        $w.m.mg add command -accelerator "Shift-Down" -label "Next 10" -command "nextMerge $top 10"
    }
    

    $w.m add cascade -label "Config" -underline 0 -menu $w.m.mc
    menu $w.m.mc
    $w.m.mc add radiobutton -label "Line end LF"   -value lf   -variable diff($top,mergetranslation)
    $w.m.mc add radiobutton -label "Line end CRLF" -value crlf -variable diff($top,mergetranslation)
    if {$::diff($top,mode) eq "conflict"} {
        $w.m.mc add separator
        $w.m.mc add checkbutton -label "Pure" -variable diff($top,modetype) \
                -onvalue "Pure" -offvalue "" -command {doDiff}
    }

    ttk::frame $w.f

    ttk::radiobutton $w.f.rb1 -text "LR" -value 12 \
            -variable diff($top,curMergeSel) \
            -command "selectMerge $top"
    ttk::radiobutton $w.f.rb2 -text "L"  -value 1 \
            -variable diff($top,curMergeSel) \
            -command "selectMerge $top"
    ttk::radiobutton $w.f.rb3 -text "R"  -value 2 \
            -variable diff($top,curMergeSel) \
            -command "selectMerge $top"
    ttk::radiobutton $w.f.rb4 -text "RL" -value 21 \
            -variable diff($top,curMergeSel) \
            -command "selectMerge $top"
    bind $w <Key-Left>  "focus $w; set diff($top,curMergeSel) 1; selectMerge $top"
    bind $w <Key-Right> "focus $w; set diff($top,curMergeSel) 2; selectMerge $top"

    ttk::button $w.f.bl -text "Prev C" -command "nextMerge $top -1000"
    ttk::button $w.f.br -text "Next C" -command "nextMerge $top 1000"
    
    ttk::button $w.f.b1 -text "Prev" -command "nextMerge $top -1"
    ttk::button $w.f.b2 -text "Next" -command "nextMerge $top 1"
    bind $w <Key-Down> "focus $w ; nextMerge $top 1"
    bind $w <Key-Up>   "focus $w ; nextMerge $top -1"
    bind $w <Shift-Key-Down> "focus $w ; nextMerge $top 10"
    bind $w <Shift-Key-Up>   "focus $w ; nextMerge $top -10"



    ttk::button $w.f.bs -text "Save" -command "saveMerge $top"
    ttk::button $w.f.bq -text "Close" -command "closeMerge $top"
    wm protocol $w WM_DELETE_WINDOW "closeMerge $top"

    grid $w.f.rb1 $w.f.rb2 $w.f.rb3 $w.f.rb4 x $w.f.b1 $w.f.b2 x \
            $w.f.bl $w.f.br x $w.f.bs $w.f.bq -sticky we -padx 1
    if {!$anyC} {
        grid forget $w.f.bl $w.f.br
    }
    grid columnconfigure $w.f {4 7 10} -minsize 10
    grid columnconfigure $w.f 10 -weight 1
    grid columnconfigure $w.f {0 1 2 3} -uniform a
    grid columnconfigure $w.f {5 6 8 9 11 12} -uniform b
    #grid columnconfigure $w.f {11 13 14} -uniform c

    text $w.t -width 80 -height 20 -xscrollcommand "$w.sbx set" \
            -yscrollcommand "$w.sby set" -font myfont
    scrollbar $w.sbx -orient horizontal -command "$w.t xview"
    scrollbar $w.sby -orient vertical   -command "$w.t yview"

    bind $w.t <Key-Escape> [list focus $w]

    ttk::label $w.ls -textvariable ::diff($top,mergeStatus)


    # Prevent toplevel bindings on keys to fire while in the text widget.
    bindtags $w.t [list Text $w.t $w all]
    bind $w.t <Key-Left>  "break"
    bind $w.t <Key-Right> "break"
    bind $w.t <Key-Down>  "break"
    bind $w.t <Key-Up>    "break"







|
|







|
|
>
>





|


|

|



|
|
|





|
|
|





|
|
|
|

|
|
|
|

|
|
>
>




|
>
|



|

|




|


|
|




|





|


|
|










|

|

|

|













|




|


|



|
|

|

|
|

|









|

|




|

|
|
|
|







|



|
|





|


|


|



|





|


|
|
|





|

|

|




|




|














|

|

|

|









|
|
<
>
|
|
<
<



|
|
|

|






|


|


|


|

|
|










>
>







|









|
|
|



|
>







144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409

410
411
412


413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
    $w mark set merges[expr {$t + 1}] end

    set showFirst 0
    if {$firstConflict != -1} {
        set showFirst $firstConflict
    }

    set eskil($top,curMerge) $showFirst
    set eskil($top,curMergeSel) $eskil($top,mergeSelection,$showFirst)
    $w tag configure merge$showFirst -foreground red
    showDiff $top $showFirst
    update
    # If there is any diff, show the first
    if {$t > 0} {
        seeText $w merges$showFirst mergee$showFirst
        # Show status for first chunk
        set eskil($top,mergeStatus) \
                $eskil($top,mergeSelection,Comment,$showFirst)
        set eskil($top,mergeAncLines) \
                $eskil($top,mergeSelection,AncLines,$showFirst)
    }
}

# Move to and highlight another diff.
proc nextMerge {top delta} {
    global eskil

    set w $top.merge.t
    $w tag configure merge$eskil($top,curMerge) -foreground ""

    set last [expr {[llength $eskil($top,leftMergeData)] / 2 - 1}]

    if {$delta == -1000} {
        # Search backward for conflict
        for {set t [expr {$eskil($top,curMerge) - 1}]} {$t >= 0} {incr t -1} {
            if {$eskil($top,mergeSelection,Conflict,$t)} {
                set delta [expr {$t - $eskil($top,curMerge)}]
                break
            }
        }
    } elseif {$delta == 1000} {
        # Search forward for conflict
        for {set t [expr {$eskil($top,curMerge) + 1}]} {$t <= $last} {incr t} {
            if {$eskil($top,mergeSelection,Conflict,$t)} {
                set delta [expr {$t - $eskil($top,curMerge)}]
                break
            }
        }
    }

    set eskil($top,curMerge) [expr {$eskil($top,curMerge) + $delta}]
    if {$eskil($top,curMerge) < 0} {set eskil($top,curMerge) 0}
    if {$eskil($top,curMerge) > $last} {
        set eskil($top,curMerge) $last
    }
    set eskil($top,curMergeSel) $eskil($top,mergeSelection,$eskil($top,curMerge))
    $w tag configure merge$eskil($top,curMerge) -foreground red
    showDiff $top $eskil($top,curMerge)
    seeText $w merges$eskil($top,curMerge) mergee$eskil($top,curMerge)

    set eskil($top,mergeStatus) \
            $eskil($top,mergeSelection,Comment,$eskil($top,curMerge))
    set eskil($top,mergeAncLines) \
            $eskil($top,mergeSelection,AncLines,$eskil($top,curMerge))
}

# Select a merge setting for all diffs.
proc selectMergeAll {top new} {
    global eskil

    set end [expr {[llength $eskil($top,leftMergeData)] / 2}]
    for {set t 0} {$t < $end} {incr t} {
        selectMerge2 $top $t $new
    }
    set eskil($top,curMergeSel) $new
    set w $top.merge.t
    seeText $w merges$eskil($top,curMerge) mergee$eskil($top,curMerge)
}

# Change merge setting fo current diff.
proc selectMerge {top} {
    global eskil

    set w $top.merge.t
    selectMerge2 $top $eskil($top,curMerge) $eskil($top,curMergeSel)
    seeText $w merges$eskil($top,curMerge) mergee$eskil($top,curMerge)
}

# Change merge setting for a diff.
proc selectMerge2 {top no new} {
    global eskil

    set w $top.merge.t
    # Delete current string
    $w delete merges$no mergee$no

    set eskil($top,mergeSelection,$no) $new

    set i [expr {$no * 2 + 1}]
    set diffLeft [lindex $eskil($top,leftMergeData) $i]
    set diffRight [lindex $eskil($top,rightMergeData) $i]

    # Temporarily switch surrounding marks
    # Two steps are enough since there can't be consecutive empty areas
    # The one before and/or the one after the one being switch might
    # be empty.
    $w mark gravity mergee[expr {$no - 2}] left
    $w mark gravity mergee[expr {$no - 1}] left
    $w mark gravity merges[expr {$no + 1}] right
    $w mark gravity merges[expr {$no + 2}] right

    if {$eskil($top,mergeSelection,$no) == 12} {
        $w insert merges$no $diffLeft$diffRight merge$no
    } elseif {$eskil($top,mergeSelection,$no) == 21} {
        $w insert merges$no $diffRight$diffLeft merge$no
    } elseif {$eskil($top,mergeSelection,$no) == 1} {
        $w insert merges$no $diffLeft merge$no
    } elseif {$eskil($top,mergeSelection,$no) == 2} {
        $w insert merges$no $diffRight merge$no
    }
    # Switch back surrounding marks
    $w mark gravity mergee[expr {$no - 2}] right
    $w mark gravity mergee[expr {$no - 1}] right
    $w mark gravity merges[expr {$no + 1}] left
    $w mark gravity merges[expr {$no + 2}] left
}

# Save the merge result.
proc saveMerge {top} {
    set w $top.merge.t

    if {$::eskil($top,mergeFile) eq "" && $::eskil($top,mode) eq "conflict"} {
        set apa [tk_messageBox -parent $top.merge -icon question \
                -title "Save merge file" -type yesno -message \
                "Do you want to overwrite the original conflict file?"]
        if {$apa == "yes"} {
            set ::eskil($top,mergeFile) $::eskil($top,conflictFile)
        }
    }
    if {$::eskil($top,mergeFile) eq ""} {
        # Ask user which file
        set buttons {}
        set text "Overwrite file or Browse?"
        if {[file exists $::eskil($top,leftFile)] && \
                $::eskil($top,leftFile) eq $::eskil($top,leftLabel)} {
            lappend buttons Left
            append text "\nLeft: $::eskil($top,leftFile)"
        }
        if {[file exists $::eskil($top,rightFile)] && \
                $::eskil($top,rightFile) eq $::eskil($top,rightLabel)} {
            lappend buttons Right
            append text "\nRight: $::eskil($top,rightFile)"
        }
        lappend buttons Browse Cancel
        if {[llength $buttons] > 2} {
            set apa [tk_dialog .savemerge "Save merge file" \
                    $text \
                    questhead -1 {*}$buttons]
            if {$apa < 0} return
            set apa [lindex $buttons $apa]
            if {$apa eq "Left"} {
                set ::eskil($top,mergeFile) $::eskil($top,leftFile)
            } elseif {$apa eq "Right"} {
                set ::eskil($top,mergeFile) $::eskil($top,rightFile)
            } elseif {$apa eq "Cancel"} {
                return
            }
        }
        if {$::eskil($top,mergeFile) eq ""} {
            # Browse
            if {[info exists ::eskil($top,rightDir)]} {
                set initDir $::eskil($top,rightDir)
            } elseif {[info exists ::eskil($top,leftDir)]} {
                set initDir $::eskil($top,leftDir)
            } else {
                set initDir [pwd]
            }

            set apa [tk_getSaveFile -title "Save merge file" -initialdir $initDir \
                    -parent $top.merge]
            if {$apa eq ""} return
            set ::eskil($top,mergeFile) $apa
        }
    }

    set ch [open $::eskil($top,mergeFile) "w"]
    fconfigure $ch -translation $::eskil($top,mergetranslation)
    puts -nonewline $ch [$w get 1.0 end-1char]
    close $ch

    # Detect if this is a GIT merge, and possibly add it to the index
    # after save (i.e. git add file)
    if {[detectRevSystem $::eskil($top,mergeFile)] eq "GIT"} {
        set apa [tk_messageBox -parent $top.merge -icon info -type yesno \
                -title "Diff" \
                -message "Saved merge to file $::eskil($top,mergeFile).\nAdd\
                it to GIT index?"]
        if {$apa eq "yes"} {
            eskil::rev::GIT::add $::eskil($top,mergeFile)
        }
    } else {
        tk_messageBox -parent $top.merge -icon info -type ok -title "Diff" \
                -message "Saved merge to file $::eskil($top,mergeFile)."
    }
}

# Close merge window and clean up.
proc closeMerge {top} {
    global eskil

    destroy $top.merge
    set eskil($top,leftMergeData) {}
    set eskil($top,rightMergeData) {}
    array unset eskil $top,mergeSelection,*
}

# Create a window to display merge result.
proc makeMergeWin {top} {
    collectMergeData $top
    if { ! [info exists ::eskil($top,mergetranslation)]} {
        if {$::tcl_platform(platform) eq "windows"} {
            set ::eskil($top,mergetranslation) crlf
        } else {
            set ::eskil($top,mergetranslation) lf
        }
    }

    set w $top.merge
    if { ! [winfo exists $w]} {
        toplevel $w
    } else {
        destroy {*}[winfo children $w]
    }
    set anyC $::eskil($top,mergeSelection,AnyConflict)

    wm title $w "Merge result: [TitleTail $top]"

    menu $w.m
    $w configure -menu $w.m
    $w.m add cascade -label "File" -underline 0 -menu $w.m.mf
    menu $w.m.mf
    $w.m.mf add command -label "Save" -underline 0 -command "saveMerge $top"
    $w.m.mf add separator
    $w.m.mf add command -label "Close" -underline 0 -command "closeMerge $top"

    $w.m add cascade -label "Select" -underline 0 -menu $w.m.ms
    menu $w.m.ms
    $w.m.ms add radiobutton -label "Left+Right"         -value 12 \
            -variable ::eskil($top,curMergeSel) -command "selectMerge $top"
    $w.m.ms add radiobutton -label "Left" -underline 0  -value 1  \
            -variable ::eskil($top,curMergeSel) -command "selectMerge $top"
    $w.m.ms add radiobutton -label "Right" -underline 0 -value 2  \
            -variable ::eskil($top,curMergeSel) -command "selectMerge $top"
    $w.m.ms add radiobutton -label "Right+Left"         -value 21 \
            -variable ::eskil($top,curMergeSel) -command "selectMerge $top"
    $w.m.ms add separator
    $w.m.ms add command -label "All Left"  -command "selectMergeAll $top 1"
    $w.m.ms add command -label "All Right" -command "selectMergeAll $top 2"

    $w.m add cascade -label "Goto" -underline 0 -menu $w.m.mg
    menu $w.m.mg
    $w.m.mg add command -accelerator "Up" -label "Previous" -command "nextMerge $top -1"
    $w.m.mg add command -accelerator "Down" -label "Next" -command "nextMerge $top 1"
    if {$anyC} {
        $w.m.mg add command -accelerator "Ctrl-Up" -label "Previous Conflict" -command "nextMerge $top -1000"
        $w.m.mg add command -accelerator "Ctrl-Down" -label "Next Conflict" -command "nextMerge $top 1000"

    }
    $w.m.mg add command -accelerator "Shift-Up" -label "Previous 10" -command "nextMerge $top -10"
    $w.m.mg add command -accelerator "Shift-Down" -label "Next 10" -command "nextMerge $top 10"



    $w.m add cascade -label "Config" -underline 0 -menu $w.m.mc
    menu $w.m.mc
    $w.m.mc add radiobutton -label "Line end LF"   -value lf   -variable ::eskil($top,mergetranslation)
    $w.m.mc add radiobutton -label "Line end CRLF" -value crlf -variable ::eskil($top,mergetranslation)
    if {$::eskil($top,mode) eq "conflict"} {
        $w.m.mc add separator
        $w.m.mc add checkbutton -label "Pure" -variable ::eskil($top,modetype) \
                -onvalue "Pure" -offvalue "" -command {doDiff}
    }

    ttk::frame $w.f

    ttk::radiobutton $w.f.rb1 -text "LR" -value 12 \
            -variable ::eskil($top,curMergeSel) \
            -command "selectMerge $top"
    ttk::radiobutton $w.f.rb2 -text "L"  -value 1 \
            -variable ::eskil($top,curMergeSel) \
            -command "selectMerge $top"
    ttk::radiobutton $w.f.rb3 -text "R"  -value 2 \
            -variable ::eskil($top,curMergeSel) \
            -command "selectMerge $top"
    ttk::radiobutton $w.f.rb4 -text "RL" -value 21 \
            -variable ::eskil($top,curMergeSel) \
            -command "selectMerge $top"
    bind $w <Key-Left>  "focus $w; set ::eskil($top,curMergeSel) 1; selectMerge $top"
    bind $w <Key-Right> "focus $w; set ::eskil($top,curMergeSel) 2; selectMerge $top"

    ttk::button $w.f.bl -text "Prev C" -command "nextMerge $top -1000"
    ttk::button $w.f.br -text "Next C" -command "nextMerge $top 1000"
    
    ttk::button $w.f.b1 -text "Prev" -command "nextMerge $top -1"
    ttk::button $w.f.b2 -text "Next" -command "nextMerge $top 1"
    bind $w <Key-Down> "focus $w ; nextMerge $top 1"
    bind $w <Key-Up>   "focus $w ; nextMerge $top -1"
    bind $w <Shift-Key-Down> "focus $w ; nextMerge $top 10"
    bind $w <Shift-Key-Up>   "focus $w ; nextMerge $top -10"
    bind $w <Control-Key-Down> "focus $w ; nextMerge $top 1000"
    bind $w <Control-Key-Up>   "focus $w ; nextMerge $top -1000"

    ttk::button $w.f.bs -text "Save" -command "saveMerge $top"
    ttk::button $w.f.bq -text "Close" -command "closeMerge $top"
    wm protocol $w WM_DELETE_WINDOW "closeMerge $top"

    grid $w.f.rb1 $w.f.rb2 $w.f.rb3 $w.f.rb4 x $w.f.b1 $w.f.b2 x \
            $w.f.bl $w.f.br x $w.f.bs $w.f.bq -sticky we -padx 1
    if { ! $anyC} {
        grid forget $w.f.bl $w.f.br
    }
    grid columnconfigure $w.f {4 7 10} -minsize 10
    grid columnconfigure $w.f 10 -weight 1
    grid columnconfigure $w.f {0 1 2 3} -uniform a
    grid columnconfigure $w.f {5 6 8 9 11 12} -uniform b
    #grid columnconfigure $w.f {11 13 14} -uniform c

    text $w.t -width 80 -height 20 -xscrollcommand "$w.sbx set" \
            -yscrollcommand "$w.sby set" -font myfont -tabstyle wordprocessor
    ttk::scrollbar $w.sbx -orient horizontal -command "$w.t xview"
    ttk::scrollbar $w.sby -orient vertical   -command "$w.t yview"

    bind $w.t <Key-Escape> [list focus $w]

    ttk::label $w.ls -textvariable ::eskil($top,mergeStatus)
    addBalloon $w.ls \[[list set "::eskil($top,mergeAncLines)"]\]

    # Prevent toplevel bindings on keys to fire while in the text widget.
    bindtags $w.t [list Text $w.t $w all]
    bind $w.t <Key-Left>  "break"
    bind $w.t <Key-Right> "break"
    bind $w.t <Key-Down>  "break"
    bind $w.t <Key-Up>    "break"
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510








511
512
513
514
515
516
517
518
519
520
521
522
523




524
525

526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541




542
543

544
545
546
547
548
549
550
551
552
553
554
555

556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572


573
574

575

576
577







578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596


597
598

599

600
601







602
603
604
605
606
607
608
609
610
611
612
613
614



615
616
617
618
619
620
621
622
623
624
625
626
627
628



629
630
631
632
633
634
635
636
637
638
639
640
641

642
643
644
645
646
647
648
649
650
651
652
    grid rowconfigure $w 1 -weight 1

    fillMergeWindow $top
}

# Compare each file against an ancestor file for three-way merge
proc collectAncestorInfo {top dFile1 dFile2 opts} {
    if {![info exists ::diff($top,mergetranslation)]} {
        # Try to autodetect line endings in ancestor file
        set ch [open $::diff($top,ancestorFile) rb]
        set data [read $ch 10000]
        close $ch
        if {[string first \r\n $data] >= 0} {
            set ::diff($top,mergetranslation) crlf
        } else {
            set ::diff($top,mergetranslation) lf
        }
    }
    array unset ::diff $top,ancestorLeft,*
    array unset ::diff $top,ancestorRight,*
    set differrA1 [catch {DiffUtil::diffFiles {*}$opts \
            $::diff($top,ancestorFile) $dFile1} diffresA1]
    set differrA2 [catch {DiffUtil::diffFiles {*}$opts \
            $::diff($top,ancestorFile) $dFile2} diffresA2]
    if {$differrA1 != 0 || $differrA2 != 0} {
        puts $diffresA1
        puts $diffresA2
        return
    }








    foreach i $diffresA1 {
        lassign $i line1 n1 line2 n2
        if {$n1 == 0} {
            # Added lines
            for {set t $line2} {$t < $line2 + $n2} {incr t} {
                set ::diff($top,ancestorLeft,$t) a
            }
        } elseif {$n2 == 0} {
            # Deleted lines
            # Mark the following line
            set ::diff($top,ancestorLeft,d$line2) d
        } else {
            # Changed lines




            for {set t $line2} {$t < $line2 + $n2} {incr t} {
                set ::diff($top,ancestorLeft,$t) c

            }
        }
    }            
    foreach i $diffresA2 {
        lassign $i line1 n1 line2 n2
        if {$n1 == 0} {
            # Added lines
            for {set t $line2} {$t < $line2 + $n2} {incr t} {
                set ::diff($top,ancestorRight,$t) a
            }
        } elseif {$n2 == 0} {
            # Deleted lines
            # Mark the following line
            set ::diff($top,ancestorRight,d$line2) d
        } else {
            # Changed lines




            for {set t $line2} {$t < $line2 + $n2} {incr t} {
                set ::diff($top,ancestorRight,$t) c

            }
        }
    }
    #parray ::diff $top,ancestor*
}

# Use ancestor info to select which side to use in a merge chunk
##nagelfar syntax WhichSide x x x x x n n
proc WhichSide {top line1 n1 line2 n2 conflictName commentName} {
    upvar 1 $conflictName conflict $commentName comment
    set conflict 0
    set comment ""

    if {$::diff($top,ancestorFile) eq ""} {
        # No ancestor info, just select right side
        return 2
    }
    if {$n1 == 0} {
        # Only to the right
        set delLeft [info exists ::diff($top,ancestorLeft,d$line1)]
        # Inserted to right : Keep right side
        if {!$delLeft} {
            set comment "Right: Add"
            return 2
        }

        for {set t $line2} {$t < $line2 + $n2} {incr t} {
            if {[info exists ::diff($top,ancestorRight,$t)]} {
                set right($::diff($top,ancestorRight,$t)) 1
            }


        }
        # Deleted to left   : Keep left side

        if {[array size right] == 0} {

            set comment "Left: Delete"
            return 1







        }
        # Deleted to left and changed to the right : ?? (right for now)
        # FIXA
        set comment "*** Left: Delete, Right: Change"
        set conflict 1
        return 2
    } elseif {$n2 == 0} {
        # Only to the left, this can be:
        set delRight [info exists ::diff($top,ancestorRight,d$line2)]
        # Inserted to left : Keep left side
        if {!$delRight} {
            set comment "Left: Add"
            return 1
        }

        for {set t $line1} {$t < $line1 + $n1} {incr t} {
            if {[info exists ::diff($top,ancestorLeft,$t)]} {
                set left($::diff($top,ancestorLeft,$t)) 1
            }


        }
        # Deleted to right : Keep right side

        if {[array size left] == 0} {

            set comment "Right: Delete"
            return 2







        }
        # Deleted to right and changed to the left : ?? (right for now)
        # FIXA
        set comment "*** Left: Change, Right: Delete"
        set conflict 1
        return 2
    } else {
        # Changed on both sides

        # Collect left side info
        for {set t $line1} {$t < $line1 + $n1} {incr t} {
            if {[info exists ::diff($top,ancestorLeft,$t)]} {
                set left($::diff($top,ancestorLeft,$t)) 1



            }
        }

        # No changes against ancestor on left side means it is just
        # changed to the right : Keep right
        if {[array size left] == 0} {
            set comment "Right: Change"
            return 2
        }

        # Collect right side info
        for {set t $line2} {$t < $line2 + $n2} {incr t} {
            if {[info exists ::diff($top,ancestorRight,$t)]} {
                set right($::diff($top,ancestorRight,$t)) 1



            }
        }

        # No changes against ancestor on right side means it is just
        # changed to the left : Keep left
        if {[array size right] == 0} {
            set comment "Left: Change"
            return 1
        }

        if {[info exists left(a)] && ![info exists left(c)] && \
                [info exists right(a)] && ![info exists right(c)]} {
            # Pure add on both sides, keep both

            set comment "*** Left: Add, Right: Add"
            set conflict 1
            return 12
        }
        # Changed in both, right for now
        # FIXA
        set comment "*** Left: Change, Right: Change"
        set conflict 1
        return 2
    }
}







|

|
<
<
<
<
<
<
|
<
|
|

|

|





>
>
>
>
>
>
>
>





|




|


>
>
>
>

|
>








|




|


>
>
>
>

|
>







|
|
|


>
|




|
<
<
<
<
<
<
|

|
|

>
>
|
<
>

>


>
>
>
>
>
>
>







|
<
<
<
<
<
<
|

|
|

>
>
|
<
>

>


>
>
>
>
>
>
>











|
|
>
>
>












|
|
>
>
>












|
>











492
493
494
495
496
497
498
499
500
501






502

503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583






584
585
586
587
588
589
590
591

592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611






612
613
614
615
616
617
618
619

620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
    grid rowconfigure $w 1 -weight 1

    fillMergeWindow $top
}

# Compare each file against an ancestor file for three-way merge
proc collectAncestorInfo {top dFile1 dFile2 opts} {
    if { ! [info exists ::eskil($top,mergetranslation)]} {
        # Try to autodetect line endings in ancestor file
        detectLineEnd $top $::eskil($top,ancestorFile) mergetranslation lf






    }

    array unset ::eskil $top,ancestorLeft,*
    array unset ::eskil $top,ancestorRight,*
    set differrA1 [catch {DiffUtil::diffFiles {*}$opts \
            $::eskil($top,ancestorFile) $dFile1} diffresA1]
    set differrA2 [catch {DiffUtil::diffFiles {*}$opts \
            $::eskil($top,ancestorFile) $dFile2} diffresA2]
    if {$differrA1 != 0 || $differrA2 != 0} {
        puts $diffresA1
        puts $diffresA2
        return
    }

    # We store ancestor data to provide it as popup info.
    # This is a bit ugly but it at least allows access to ancestor contents
    # at all, even if a nicer presentation could probably be made.
    set ch [open $::eskil($top,ancestorFile)]
    set ancestorLines [split [read $ch] \n]
    close $ch

    foreach i $diffresA1 {
        lassign $i line1 n1 line2 n2
        if {$n1 == 0} {
            # Added lines
            for {set t $line2} {$t < $line2 + $n2} {incr t} {
                set ::eskil($top,ancestorLeft,$t) a
            }
        } elseif {$n2 == 0} {
            # Deleted lines
            # Mark the following line
            set ::eskil($top,ancestorLeft,d$line2) d
        } else {
            # Changed lines
            set ancLines {}
            for {set t $line1} {$t < $line1 + $n1} {incr t} {
                lappend ancLines "$t: [lindex $ancestorLines [- $t 1]]"
            }
            for {set t $line2} {$t < $line2 + $n2} {incr t} {
                set ::eskil($top,ancestorLeft,$t) c
                set ::eskil($top,ancestorLeft,$t,lines) $ancLines
            }
        }
    }            
    foreach i $diffresA2 {
        lassign $i line1 n1 line2 n2
        if {$n1 == 0} {
            # Added lines
            for {set t $line2} {$t < $line2 + $n2} {incr t} {
                set ::eskil($top,ancestorRight,$t) a
            }
        } elseif {$n2 == 0} {
            # Deleted lines
            # Mark the following line
            set ::eskil($top,ancestorRight,d$line2) d
        } else {
            # Changed lines
            set ancLines {}
            for {set t $line1} {$t < $line1 + $n1} {incr t} {
                lappend ancLines "$t: [lindex $ancestorLines [- $t 1]]"
            }
            for {set t $line2} {$t < $line2 + $n2} {incr t} {
                set ::eskil($top,ancestorRight,$t) c
                set ::eskil($top,ancestorRight,$t,lines) $ancLines
            }
        }
    }
    #parray ::diff $top,ancestor*
}

# Use ancestor info to select which side to use in a merge chunk
##nagelfar syntax WhichSide x x x x x n n n
proc WhichSide {top line1 n1 line2 n2 conflictName commentName ancLinesName} {
    upvar 1 $conflictName conflict $commentName comment $ancLinesName ancLines
    set conflict 0
    set comment ""
    set ancLines {}
    if {$::eskil($top,ancestorFile) eq ""} {
        # No ancestor info, just select right side
        return 2
    }
    if {$n1 == 0} {
        # This chunk has lines only to the right






        # Look for changes on the right side
        for {set t $line2} {$t < $line2 + $n2} {incr t} {
            if {[info exists ::eskil($top,ancestorRight,$t)]} {
                set right($::eskil($top,ancestorRight,$t)) 1
            }
            if {[info exists ::eskil($top,ancestorRight,$t,lines)]} {
                lappend ancLines {*}$::eskil($top,ancestorRight,$t,lines)
            }

        }
        if {[array size right] == 0} {
            # No changes to the right, so deleted to the left : Keep left side
            set comment "Left: Delete"
            return 1
        }
        # Is it deleted on the left side?
        set delLeft [info exists ::eskil($top,ancestorLeft,d$line1)]
        if { ! $delLeft} {
            # It is inserted to the right : Keep right side
            set comment "Right: Add"
            return 2
        }
        # Deleted to left and changed to the right : ?? (right for now)
        # FIXA
        set comment "*** Left: Delete, Right: Change"
        set conflict 1
        return 2
    } elseif {$n2 == 0} {
        # This chunk has lines only to the left






        # Look for changes on the left side
        for {set t $line1} {$t < $line1 + $n1} {incr t} {
            if {[info exists ::eskil($top,ancestorLeft,$t)]} {
                set left($::eskil($top,ancestorLeft,$t)) 1
            }
            if {[info exists ::eskil($top,ancestorLeft,$t,lines)]} {
                lappend ancLines {*}$::eskil($top,ancestorLeft,$t,lines)
            }

        }
        if {[array size left] == 0} {
            # No changes to the left, so deleted to the right : Keep right side
            set comment "Right: Delete"
            return 2
        }
        # Is it deleted on the right side?
        set delRight [info exists ::eskil($top,ancestorRight,d$line2)]
        if { ! $delRight} {
            # It is inserted to the left : Keep left side
            set comment "Left: Add"
            return 1
        }
        # Deleted to right and changed to the left : ?? (right for now)
        # FIXA
        set comment "*** Left: Change, Right: Delete"
        set conflict 1
        return 2
    } else {
        # Changed on both sides

        # Collect left side info
        for {set t $line1} {$t < $line1 + $n1} {incr t} {
            if {[info exists ::eskil($top,ancestorLeft,$t)]} {
                set left($::eskil($top,ancestorLeft,$t)) 1
            }
            if {[info exists ::eskil($top,ancestorLeft,$t,lines)]} {
                lappend ancLines {*}$::eskil($top,ancestorLeft,$t,lines)
            }
        }

        # No changes against ancestor on left side means it is just
        # changed to the right : Keep right
        if {[array size left] == 0} {
            set comment "Right: Change"
            return 2
        }

        # Collect right side info
        for {set t $line2} {$t < $line2 + $n2} {incr t} {
            if {[info exists ::eskil($top,ancestorRight,$t)]} {
                set right($::eskil($top,ancestorRight,$t)) 1
            }
            if {[info exists ::eskil($top,ancestorRight,$t,lines)]} {
                lappend ancLines {*}$::eskil($top,ancestorRight,$t,lines)
            }
        }

        # No changes against ancestor on right side means it is just
        # changed to the left : Keep left
        if {[array size right] == 0} {
            set comment "Left: Change"
            return 1
        }

        if {[info exists left(a)] && ![info exists left(c)] && \
                [info exists right(a)] && ![info exists right(c)]} {
            # Pure add on both sides, keep both, but mark it as a conflict
            # to alert user
            set comment "*** Left: Add, Right: Add"
            set conflict 1
            return 12
        }
        # Changed in both, right for now
        # FIXA
        set comment "*** Left: Change, Right: Change"
        set conflict 1
        return 2
    }
}

Changes to src/plugin.tcl.

1
2
3
4
5
6
7
8
9
10
11
#----------------------------------------------------------------------
#  Eskil, Plugin handling
#
#  Copyright (c) 2008, Peter Spjuth  (peter.spjuth@gmail.com)
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,



|







1
2
3
4
5
6
7
8
9
10
11
#----------------------------------------------------------------------
#  Eskil, Plugin handling
#
#  Copyright (c) 2008-2016, Peter Spjuth  (peter.spjuth@gmail.com)
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
26
27
28
29
30
31
32
33





34

35

36





37



38

39
40
41
42
43
44
45
46
47
48

49
50
51
52
53








54







55














56
57
58






59

60


61
62
63
64
65
66




67

68
69
70
71

72
73
74
75

76
77










78
79
80
81
82
83

84
85
86
87

88


89


90

91
92
93
94

95




96

97
98
99
100
101
102
103
104
105
106
107
108
109
110






111
112
113













114




115
116
117
118
119
120
121
122










123
124

125
126
127
128


129




130
131














132




133
134
135
136
137


138
139
140
141
142
143
144
145
146




147
148


149
150
151


152

153
154


155





















156
157
158

159


160
161
162
163
164
165

166


167
168
169


170

171

172
173

174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
























































































196
197
198
199
200
201
202
203
204
205
206


































207









208







































209

210
211
212































213






































214
215




216
217


218

219





220
221




222









223
224
225



226
227




228
229


230









231

232







233







234
235

236



237

238



239
240




241



242
243
244




245





246
    set dirs [list . ./plugins]
    lappend dirs [file join $::eskil(thisDir) .. ..]
    lappend dirs [file join $::eskil(thisDir) .. .. plugins]
    lappend dirs [file join $::eskil(thisDir) .. plugins]
    return $dirs
}

# Locate plugin source





proc LocatePlugin {plugin} {

    set src ""

    set dirs [PluginSearchPath]









    foreach dir $dirs {

        set files {}
        lappend files [file join $dir $plugin]
        lappend files [file join $dir $plugin.tcl]
        foreach file $files {
            if {![file exists   $file]} continue
            if {![file isfile   $file]} continue
            if {![file readable $file]} continue
            set ch [open $file r]
            set data [read $ch 20]
            close $ch

            if {[string match "##Eskil Plugin*" $data]} {
                set src $file
                break
            }
        }








        if {$src ne ""} break







    }














    return $src
}







proc createPluginInterp {plugin info} {

    set src [LocatePlugin $plugin]



    if {$src eq ""} {
        return ""
    }

    # Create interpreter




    set pi [interp create -safe]


    # Load source
    $pi invokehidden -global source $src
    $pi eval [list set ::WhoAmI [file rootname [file tail $src]]]

    $pi eval [list set ::Info $info]
    interp share {} stdout $pi

    # Expose needed commands

    interp expose $pi fconfigure ;# ??
    interp hide $pi close











    return $pi
}

proc printPlugin {plugin} {
    set src [LocatePlugin $plugin]

    if {$src eq ""} {
        printPlugins
        return
    }

    set ch [open $src]


    puts -nonewline [read $ch]


    close $ch

}

proc listPlugins {} {
    set dirs [PluginSearchPath]






    foreach dir $dirs {

        set files [glob -nocomplain [file join $dir *.tcl]]
        foreach file $files {
            set file [file normalize $file]
            if {[info exists done($file)]} continue
            if {![file exists $file]} continue
            if {![file isfile $file]} continue
            if {![file readable $file]} continue

            set done($file) 1
            set ch [open $file r]
            set data [read $ch 200]
            close $ch
            if {[regexp {^\#\#Eskil Plugin :(.*?)(\n|$)} $data -> descr]} {
                set result([file rootname [file tail $file]]) $descr






            }
        }
    }













    return [array get result]




}

proc printPlugins {} {
    set plugins [listPlugins]
    if {[llength $plugins] == 0} {
        puts "No plugins found."
        return
    }










    puts "Available plugins:"
    foreach {plugin descr} $plugins {

        puts "Plugin \"$plugin\" : $descr"
    }
}



proc preparePlugin {top} {




    #FIXA: plugin miffo
    disallowEdit $top














    $::diff($top,plugin) eval [list array set ::Pref [array get ::Pref]]




    set out1 [tmpFile]
    set out2 [tmpFile]

    set chi [open $::diff($top,leftFile) r]
    set cho [open $out1 w]


    interp share {} $chi $::diff($top,plugin)
    interp share {} $cho $::diff($top,plugin)
    set usenew1 [$::diff($top,plugin) eval [list PreProcess left $chi $cho]]
    $::diff($top,plugin) invokehidden close $chi
    $::diff($top,plugin) invokehidden close $cho
    close $chi
    close $cho

    set chi [open $::diff($top,rightFile) r]




    set cho [open $out2 w]
    interp share {} $chi $::diff($top,plugin)


    interp share {} $cho $::diff($top,plugin)
    set usenew2 [$::diff($top,plugin) eval [list PreProcess right $chi $cho]]
    $::diff($top,plugin) invokehidden close $chi


    $::diff($top,plugin) invokehidden close $cho

    close $chi
    close $cho
























    if {$usenew1} {
        # The file after processing should be used both
        # for comparison and for displaying.

        set ::diff($top,leftFileBak) $::diff($top,leftFile)


        set ::diff($top,leftFile) $out1
    } else {
        set ::diff($top,leftFileDiff) $out1
        #set ::diff($top,leftLabel) "$::diff($top,RevFile) $tag"
    }
    if {$usenew2} {

        set ::diff($top,rightFileBak) $::diff($top,rightFile)


        set ::diff($top,rightFile) $out2
    } else {
        set ::diff($top,rightFileDiff) $out2


        #set ::diff($top,rightLabel) $::diff($top,RevFile)

    }

}


proc cleanupPlugin {top} {
    if {[info exists ::diff($top,leftFileBak)]} {
        set ::diff($top,leftFile) $::diff($top,leftFileBak)
    }
    if {[info exists ::diff($top,rightFileBak)]} {
        set ::diff($top,rightFile) $::diff($top,rightFileBak)
    }
    unset -nocomplain \
            ::diff($top,leftFileBak) ::diff($top,rightFileBak) \
            ::diff($top,leftFileDiff) ::diff($top,rightFileDiff)
}

# GUI for plugin selection
proc EditPrefPlugins {top} {
    set w $top.prefplugin

    # Create window
    destroy $w
    toplevel $w -padx 3 -pady 3
    ttk::frame $w._bg
    place $w._bg -x 0 -y 0 -relwidth 1.0 -relheight 1.0 -border outside
    wm title $w "Preferences: Plugins"

























































































    set plugins [listPlugins]
    if {[llength $plugins] == 0} {
        grid [ttk::label $w.l -text "No plugins found."] - -padx 3 -pady 3
    }
    if {![info exists ::diff($top,pluginname)]} {
        set ::diff($top,pluginname) ""
    }
    if {![info exists ::diff($top,plugininfo)]} {
        set ::diff($top,plugininfo) ""
    }


































    set ::diff($top,edit,pluginname) $::diff($top,pluginname) 









    set ::diff($top,edit,plugininfo) $::diff($top,plugininfo)







































    set t 0

    foreach {plugin descr} $plugins {
        ttk::radiobutton $w.rb$t -variable ::diff($top,edit,pluginname) -value $plugin -text $plugin
        ttk::label $w.l$t -text $descr -anchor "w"































        grid $w.rb$t $w.l$t -sticky we -padx 3 -pady 3






































        incr t
    }




    ttk::radiobutton $w.rb$t -variable ::diff($top,edit,pluginname) -value "" -text "No Plugin"
    grid $w.rb$t -sticky we -padx 3 -pady 3




    ttk::label $w.li -text "Info" -anchor "w"





    ttk::entry $w.ei -textvariable ::diff($top,edit,plugininfo)
    grid $w.li $w.ei -sticky we -padx 3 -pady 3














    ttk::frame $w.fb -padding 3
    ttk::button $w.fb.b1 -text "Ok"     -command [list EditPrefPluginsOk $top $w]
    ttk::button $w.fb.b2 -text "Cancel" -command [list destroy $w]



    set ::widgets($top,prefPluginsOk) $w.fb.b1





    grid $w.fb.b1 x $w.fb.b2 -sticky we
    grid columnconfigure $w.fb {0 2} -uniform a


    grid columnconfigure $w.fb 1 -weight 1











    grid $w.fb - -sticky we







    grid columnconfigure $w 1 -weight 1







}


proc EditPrefPluginsOk {top w} {



    destroy $w

    set ::diff($top,pluginname) $::diff($top,edit,pluginname) 



    set ::diff($top,plugininfo) $::diff($top,edit,plugininfo)
    if {$::diff($top,pluginname) ne ""} {




        set pinterp [createPluginInterp $::diff($top,pluginname) $::diff($top,plugininfo)]



    } else {
        set pinterp ""
    }




    set ::diff($top,plugin) $pinterp





}







|
>
>
>
>
>

>
|
>
|
>
>
>
>
>
|
>
>
>
|
>
|
|
|
|
|
|
|
|
|
|
>
|
|
|
|
|
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
|


>
>
>
>
>
>
|
>
|
>
>

|



|
>
>
>
>
|
>
|
|
|
|
>




>
|
|
>
>
>
>
>
>
>
>
>
>




|
|
>
|



>
|
>
>
|
>
>
|
>




>

>
>
>
>

>




|
|
|



|
<
|
|
>
>
>
>
>
>



>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>








>
>
>
>
>
>
>
>
>
>

|
>
|



>
>

>
>
>
>
|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
|
|

|
|
>
>
|
|
<
|
|
<
<

|
>
>
>
>
|
|
>
>
|
|
|
>
>
|
>
|
<
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
>
|
>
>
|
|
|
<
|
|
>
|
>
>
|
|
|
>
>
|
>

>


>

|
|

|
|


|
|



|
|


|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



|

|
|

|
|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>
|
<
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


>
>
>
>
|
<
>
>
|
>
|
>
>
>
>
>
|
<
>
>
>
>
|
>
>
>
>
>
>
>
>
>
|
<
<
>
>
>
|
|
>
>
>
>
|
<
>
>
|
>
>
>
>
>
>
>
>
>
|
>
|
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
|
|
>
|
>
>
>
|
>
|
>
>
>
|
|
>
>
>
>
|
>
>
>

|

>
>
>
>
|
>
>
>
>
>

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192

193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283

284
285


286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303

304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337

338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563

564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641

642
643
644
645
646
647
648
649
650
651
652

653
654
655
656
657
658
659
660
661
662
663
664
665
666
667


668
669
670
671
672
673
674
675
676
677

678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
    set dirs [list . ./plugins]
    lappend dirs [file join $::eskil(thisDir) .. ..]
    lappend dirs [file join $::eskil(thisDir) .. .. plugins]
    lappend dirs [file join $::eskil(thisDir) .. plugins]
    return $dirs
}

# Locate plugin source and extract some info
# Data structure in this return dict:
# name: Plugin name.
# file: Source file name. "_" for a runtime plugin.
# data: Source code.
# opts: Options accepted by plugin.
proc LocatePlugin {plugin} {
    set res [dict create name "" file "" opts "" data ""]
    set fSrc ""
    set code ""

    # Search runtime plugins first
    foreach name [dict keys $::eskil(plugins)] {
        if {$name eq $plugin} {
            set fSrc "_"
            set code [dict get $::eskil(plugins) $name data]
        }
    }

    if {$fSrc eq ""} {
        foreach dir [PluginSearchPath] {
            set dir [file normalize $dir]
            set files {}
            lappend files [file join $dir $plugin]
            lappend files [file join $dir $plugin.tcl]
            foreach file $files {
                if { ! [file exists   $file]} continue
                if { ! [file isfile   $file]} continue
                if { ! [file readable $file]} continue
                set ch [open $file r]
                set code [read $ch 20]
                close $ch
                # Magic pattern to identify a plugin
                if {[string match "##Eskil Plugin*" $code]} {
                    set fSrc $file
                    break
                }
            }
            if {$fSrc ne ""} break
        }
    }

    if {$fSrc eq "_"} {
        dict set res "name" $plugin
        dict set res "file" $fSrc
        dict set res "data" $code
    } elseif {$fSrc ne ""} {
        dict set res "name" $plugin
        dict set res "file" $fSrc
        # Plugin source is reloaded each time to facilitate debug/rerun.
        set ch [open $fSrc r]
        set code [read $ch]
        close $ch
        dict set res "data" $code
    }

    # Look for declarations of command line options
    foreach line [split $code \n] {
        # Only look until empty line
        if {[string trim $line] eq ""} break
        if {[regexp {^\#\# Option\s+(\S+)(.*)} $line -> name rest]} {
            # structure is name flag doc
            dict lappend res opts $name 0 [string trim $rest " :"]
        }
        if {[regexp {^\#\# Flag\s+(\S+)(.*)} $line -> name rest]} {
            dict lappend res opts $name 1 [string trim $rest " :"]
        }
    }

    return $res
}

# Return value: Handle to interpreter
#
# pinfo dict structure:
#  file : File plugin
#  dir  : Directory plugin
#  allow: Raised privileges
proc createPluginInterp {plugin info allow pinfoName} {
    upvar 1 $pinfoName pinfo
    set res [LocatePlugin $plugin]
    set code [dict get $res data]
    set fSrc [dict get $res file]

    if {$code eq ""} {
        return ""
    }

    # Create interpreter and load source
    if {$allow} {
        set pi [interp create]
        $pi eval $code
    } else {
        set pi [interp create -safe]
        $pi eval $code
    }

    # Setup info
    $pi eval [list set ::WhoAmI [file rootname [file tail $fSrc]]]
    $pi eval [list set ::WhoAmIFull [file normalize $fSrc]]
    $pi eval [list set ::Info $info]
    interp share {} stdout $pi

    # Expose needed commands
    if { ! $allow} {
        interp expose $pi fconfigure ;# needed??
        interp hide $pi close
    }

    set pinfo {file 0 dir 0}
    dict set pinfo "allow" $allow
    if {[$pi eval info proc PreProcess] ne ""} {
        dict set pinfo file 1
    }
    if {[$pi eval info proc FileCompare] ne ""} {
        dict set pinfo dir 1
    }

    return $pi
}

proc printPlugin {plugin {short 0}} {
    set res [LocatePlugin $plugin]
    set fSrc [dict get $res file]
    if {$fSrc eq ""} {
        printPlugins
        return
    }
    foreach line [split [dict get $res data] \n] {
        set lineT [string trim $line]
        if {$short} {
            if { ! [string match "#*" $lineT]} {
                break
            }
        }
        puts $line
    }
}

proc listPlugins {} {
    set dirs [PluginSearchPath]
    set result {}

    foreach name [dict keys $::eskil(plugins)] {
        dict set result $name [dict get $::eskil(plugins) $name]
    }

    foreach dir $dirs {
        set dir [file normalize $dir]
        set files [glob -nocomplain [file join $dir *.tcl]]
        foreach file $files {
            set file [file normalize $file]
            if {[info exists done($file)]} continue
            if { ! [file exists $file]} continue
            if { ! [file isfile $file]} continue
            if { ! [file readable $file]} continue

            set done($file) 1
            set ch [open $file r]
            set code [read $ch 200]

            if {[regexp {^\#\#Eskil Plugin :(.*?)(\n|$)} $code -> descr]} {
                set root [file rootname [file tail $file]]
                dict set result $root "descr" $descr
                dict set result $root "file" 0
                dict set result $root "dir"  0
                # Load it all for inspection
                append code [read $ch]
                dict set result $root "data" $code
            }
        }
    }
    foreach root [dict keys $result] {
        set code [dict get $result $root data]
        if {[regexp {^\#\#Eskil Plugin :(.*?)(\n|$)} $code -> descr]} {
            dict set result $root "descr" $descr
        }
        if {[string first "proc PreProcess " $code] >= 0} {
            dict set result $root "file" 1
        }
        if {[string first "proc FileCompare " $code] >= 0} {
            dict set result $root "dir" 1
        }
    }

    set resultSort {}
    foreach elem [lsort -dictionary [dict keys $result]] {
        dict set resultSort $elem [dict get $result $elem]
    }
    return $resultSort
}

proc printPlugins {} {
    set plugins [listPlugins]
    if {[llength $plugins] == 0} {
        puts "No plugins found."
        return
    }
    # Longest name?
    set w 0
    foreach {plugin info} $plugins {
        if {[string length $plugin] > $w} {
            set w [string length $plugin]
        }
    }
    # Room for quote marks in output
    incr w 2

    puts "Available plugins:"
    foreach {plugin info} $plugins {
        set descr [dict get $info descr]
        puts "Plugin [format %-*s $w \"$plugin\"] : $descr"
    }
}

# Handle plugins for a diff session that uses plugins.
# Returns true if something has been done that needs cleanup.
proc preparePlugin {top} {
    if {$::eskil($top,plugin,1) eq "" || \
                ![dict get $::eskil($top,pluginpinfo,1) file]} {
        return 0
    }

    disallowEdit $top
    set in1 $::eskil($top,leftFile)
    set in2 $::eskil($top,rightFile)

    foreach item [lsort -dictionary [array names ::eskil $top,pluginname,*]] {
        set pI [lindex [split $item ","] end]

        set allow [dict get $::eskil($top,pluginpinfo,$pI) allow]
        # Pass ::argv to plugin
        set pArgv $::eskil(argv)
        if {[info exists ::eskil($top,pluginargv,$pI)]} {
            lappend pArgv {*}$::eskil($top,pluginargv,$pI)
        }
        $::eskil($top,plugin,$pI) eval [list set ::argv $pArgv]
        # Pass ::Pref to plugin
        $::eskil($top,plugin,$pI) eval [list array set ::Pref [array get ::Pref]]
        # Pass File info to plugin
        $::eskil($top,plugin,$pI) eval [list set ::File(left)  $::eskil($top,leftFile)]
        $::eskil($top,plugin,$pI) eval [list set ::File(right) $::eskil($top,rightFile)]

        set out1 [tmpFile]
        set out2 [tmpFile]

        set chi [open $in1 r]
        set cho [open $out1 w]
        set chi2 [open $in2 r]
        set cho2 [open $out2 w]
        interp share {} $chi $::eskil($top,plugin,$pI)
        interp share {} $cho $::eskil($top,plugin,$pI)

        interp share {} $chi2 $::eskil($top,plugin,$pI)
        interp share {} $cho2 $::eskil($top,plugin,$pI)



        set cmd1 [list PreProcess left $chi $cho]
        set cmd2 [list PreProcess right $chi2 $cho2]
        if {[info commands yield] ne ""} {
            # When in 8.6, this is done in coroutines allowing each call
            # to yield and to alternate between them until done
            set c1 __plugin_cr1$top
            set c2 __plugin_cr2$top
            set cmd1 [linsert $cmd1 0 coroutine $c1]
            set cmd2 [linsert $cmd2 0 coroutine $c2]
            set usenew1 [$::eskil($top,plugin,$pI) eval $cmd1]
            set usenew2 [$::eskil($top,plugin,$pI) eval $cmd2]
            interp alias {} pnw $::eskil($top,plugin,$pI) namespace which
            while {[pnw $c1] ne {} || [pnw $c2] ne {}} {
                if {[pnw $c1] ne {}} {
                    set usenew1 [$::eskil($top,plugin,$pI) eval $c1]
                }
                if {[pnw $c2] ne {}} {

                    set usenew2 [$::eskil($top,plugin,$pI) eval $c2]
                }
            }
        } else {
            set usenew1 [$::eskil($top,plugin,$pI) eval $cmd1]
            set usenew2 [$::eskil($top,plugin,$pI) eval $cmd2]
        }

        if {$allow} {
            $::eskil($top,plugin,$pI) eval close $chi
            $::eskil($top,plugin,$pI) eval close $cho
            $::eskil($top,plugin,$pI) eval close $chi2
            $::eskil($top,plugin,$pI) eval close $cho2
        } else {
            $::eskil($top,plugin,$pI) invokehidden close $chi
            $::eskil($top,plugin,$pI) invokehidden close $cho
            $::eskil($top,plugin,$pI) invokehidden close $chi2
            $::eskil($top,plugin,$pI) invokehidden close $cho2
        }
        close $chi
        close $cho
        close $chi2
        close $cho2

        if {$usenew1} {
            # The file after processing should be used both
            # for comparison and for displaying.
            if { ! [info exists ::eskil($top,leftFileBak)]} {
                set ::eskil($top,leftFileBak) $::eskil($top,leftFile)
            }
            unset -nocomplain ::eskil($top,leftFileDiff)
            set ::eskil($top,leftFile) $out1
        } else {
            set ::eskil($top,leftFileDiff) $out1

        }
        if {$usenew2} {
            if { ! [info exists ::eskil($top,rightFileBak)]} {
                set ::eskil($top,rightFileBak) $::eskil($top,rightFile)
            }
            unset -nocomplain ::eskil($top,rightFileDiff)
            set ::eskil($top,rightFile) $out2
        } else {
            set ::eskil($top,rightFileDiff) $out2
        }
        # For next plugin, if any
        set in1 $out1
        set in2 $out2
    }
    return 1
}

# After diff is done, this is called if preparePlugin returned true.
proc cleanupPlugin {top} {
    if {[info exists ::eskil($top,leftFileBak)]} {
        set ::eskil($top,leftFile) $::eskil($top,leftFileBak)
    }
    if {[info exists ::eskil($top,rightFileBak)]} {
        set ::eskil($top,rightFile) $::eskil($top,rightFileBak)
    }
    unset -nocomplain \
            ::eskil($top,leftFileBak) ::eskil($top,rightFileBak) \
            ::eskil($top,leftFileDiff) ::eskil($top,rightFileDiff)
}

# GUI for plugin selection
proc editPrefPlugins {top {dirdiff 0}} {
    set wt $top.prefplugin

    # Create window
    destroy $wt
    toplevel $wt -padx 3 -pady 3
    ttk::frame $wt._bg
    place $wt._bg -x 0 -y 0 -relwidth 1.0 -relheight 1.0 -border outside
    wm title $wt "Preferences: Plugins"

    ttk::notebook $wt.tab
    ttk::frame $wt.tab.plus
    $wt.tab add $wt.tab.plus -text "+"
    set n [llength [array names ::eskil $top,pluginname,*]]
    if {$n < 1} { set n 1 }
    for {set t 0} {$t < $n} {incr t} {
        EditPrefPluginsAddTab $top $dirdiff
    }
    $wt.tab select 0
    bind $wt.tab <<NotebookTabChanged>> \
            [list EditPrefPluginsChangeTab $top $dirdiff]
    bind $wt.tab <ButtonPress-3> \
            [list EditPrefPluginsRightClick $top $dirdiff %x %y %X %Y]

    ttk::frame $wt.fb -padding 3
    ttk::button $wt.fb.b1 -text "Ok"   \
            -command [list EditPrefPluginsOk $top $wt 0]
    ttk::button $wt.fb.b2 -text "Apply" \
            -command [list EditPrefPluginsOk $top $wt 1]
    ttk::button $wt.fb.b3 -text "Cancel" -command [list destroy $wt]
    set ::widgets($top,prefPluginsOk) $wt.fb.b1

    grid $wt.fb.b1 x $wt.fb.b2 x $wt.fb.b3 -sticky we
    grid columnconfigure $wt.fb {0 2 4} -uniform a
    grid columnconfigure $wt.fb {1 3} -weight 1

    grid $wt.tab -sticky news -padx 3 -pady 3
    grid $wt.fb  -sticky we   -padx 3 -pady 3
    grid columnconfigure $wt 0 -weight 1
    grid row             $wt 0 -weight 1

}

# Detect a plugin tab change to add tab when "+" is selected.
proc EditPrefPluginsChangeTab {top dirdiff} {
    set wt $top.prefplugin.tab
    set n [$wt index end]
    set t [$wt index [$wt select]]

    if {$t + 1 == $n} {
        # Plus selected
        EditPrefPluginsAddTab $top $dirdiff
        $wt select $t
    }
}

# Context menu
proc EditPrefPluginsRightClick {top dirdiff x y X Y} {
    set wt $top.prefplugin.tab
    set elem [$wt identify element $x $y]
    set t [$wt identify tab $x $y]
    if {$elem eq "" || ![string is integer -strict $t]} return

    set m [winfo toplevel $wt].pm
    destroy $m
    menu $m
    set n [$wt index end]

    $m add command -label "Add left" \
            -command [list EditPrefPluginsAddTab $top $dirdiff $t]
    if {$t > 0 && $t < ($n - 1)} {
        $m add command -label "Move left" \
                -command [list EditPrefPluginsMoveLeft $top $t]
    }
    tk_popup $m $X $Y
}

# Move a tab to the left
proc EditPrefPluginsMoveLeft {top pos} {
    set wt $top.prefplugin.tab
    set win [lindex [$wt tabs] $pos]
    incr pos -1
    $wt insert $pos $win
}

# Add a tab to plugin prefernces
proc EditPrefPluginsAddTab {top dirdiff {pos {}}} {
    set wt $top.prefplugin.tab
    set pI [$wt index end]
    if {$pos eq "" || $pos >= ($pI - 1)} {
        # Since the "+" tab is last, the index is n for any new one
        set pos [expr {$pI - 1}]
    }
    ttk::frame $wt.f,$pI
    $wt insert $pos $wt.f,$pI -text "Plugin"

    set wt $wt.f,$pI

    set plugins [listPlugins]
    if {[llength $plugins] == 0} {
        grid [ttk::label $wt.l -text "No plugins found."] - -padx 3 -pady 3
    }
    if { ! [info exists ::eskil($top,pluginname,$pI)]} {
        set ::eskil($top,pluginname,$pI) ""
    }
    if { ! [info exists ::eskil($top,plugininfo,$pI)]} {
        set ::eskil($top,plugininfo,$pI) ""
    }
    if { ! [info exists ::eskil($top,pluginallow,$pI)]} {
        set ::eskil($top,pluginallow,$pI) 0
    }
    set ::eskil($top,edit,pluginname,$pI) $::eskil($top,pluginname,$pI)
    set ::eskil($top,edit,plugininfo,$pI) $::eskil($top,plugininfo,$pI)
    set ::eskil($top,edit,pluginallow,$pI) $::eskil($top,pluginallow,$pI)

    ttk::labelframe $wt.lfs -text "Select"
    grid columnconfigure $wt.lfs 1 -weight 1

    set t 0
    foreach {plugin info} $plugins {
        set descr [dict get $info descr]
        if {$dirdiff && ![dict get $info dir]} continue
        ttk::radiobutton $wt.rb$t -variable ::eskil($top,edit,pluginname,$pI) \
                -value $plugin -text $plugin -command "SelectPlugin $top $pI $plugin"
        ttk::label $wt.l$t -text $descr -anchor w
        grid $wt.rb$t $wt.l$t -  - -in $wt.lfs -sticky we -padx 3 -pady 3
        incr t
    }
    ttk::radiobutton $wt.rb$t -variable ::eskil($top,edit,pluginname,$pI) \
            -value "" -text "No Plugin" -command "SelectPlugin $top $pI $plugin"
    ttk::button $wt.bs -text "Show" -state disable \
            -command "ShowPlugin $wt \$::eskil($top,edit,pluginname,$pI)"
    addBalloon $wt.bs "Show plugin source code."
    ttk::button $wt.bc -text "Clone" -state disable \
            -command "ClonePlugin $wt \$::eskil($top,edit,pluginname,$pI)"
    addBalloon $wt.bc "Clone to a runtime plugin."
    ttk::button $wt.be -text "Edit" -state disable \
            -command "EditPlugin $wt \$::eskil($top,edit,pluginname,$pI)"
    set ::eskil($top,edit,showW,$pI) $wt.bs
    set ::eskil($top,edit,cloneW,$pI) $wt.bc
    set ::eskil($top,edit,editW,$pI) $wt.be
    addBalloon $wt.be "Edit a runtime plugin."
    SelectPlugin $top $pI $::eskil($top,edit,pluginname,$pI)

    grid $wt.rb$t $wt.be $wt.bc $wt.bs -in $wt.lfs -sticky we -padx 3 -pady 3
    grid $wt.bs $wt.bc $wt.be -sticky e

    ttk::labelframe $wt.lfgc -text "Generic Configuration"
    grid columnconfigure $wt.lfgc 1 -weight 1

    ttk::label $wt.li -text "Info" -anchor w
    addBalloon $wt.li "Info passed to plugin. Plugin specific."
    ttk::entry $wt.ei -textvariable ::eskil($top,edit,plugininfo,$pI)
    grid $wt.li $wt.ei -in $wt.lfgc -sticky we -padx 3 -pady 3

    ttk::checkbutton $wt.cb -text "Privilege" \
            -variable ::eskil($top,edit,pluginallow,$pI)
    addBalloon $wt.cb "Run plugin with raised privileges"
    grid $wt.cb -  -in $wt.lfgc -sticky w -padx 3 -pady 3

    ttk::labelframe $wt.lfsc -text "Specific Configuration"
    set ::widgets($top,prefPluginsSpec,$pI) $wt.lfsc
    trace add variable ::eskil($top,edit,pluginname,$pI) write \
            [list UpdateSpecificPluginConf $top $pI]
    UpdateSpecificPluginConf $top $pI

    grid $wt.lfs  -sticky we -padx 3 -pady 3
    grid $wt.lfgc -sticky we -padx 3 -pady 3
    grid $wt.lfsc -sticky we -padx 3 -pady 3
    grid columnconfigure $wt 0 -weight 1
}

# When a new plugin is selected, update the list of specific options.
# "args" is needed to swallow the extra variable trace args.
proc UpdateSpecificPluginConf {top pI args} {
    set w $::widgets($top,prefPluginsSpec,$pI)
    # If the dialog is closed w might not exist
    if { ! [winfo exists $w]} return
    eval destroy [winfo children $w]

    set arg $::eskil($top,edit,pluginname,$pI)
    set pOpts {}
    if {$arg ne ""} {
        set res [LocatePlugin $arg]
        set pOpts [dict get $res opts]
    }
    # Look for defaults on the command line
    set pArgv $::eskil(argv)
    if {[info exists ::eskil($top,pluginargv,$pI)]} {
        lappend pArgv {*}$::eskil($top,pluginargv,$pI)
    }
    # Look for declarations of command line options
    set t 0
    set ::eskil($top,edit,opts,$pI) $pOpts
    foreach {name flag doc} $pOpts {

        ttk::label $w.l$t -text $name
        addBalloon $w.l$t -fmt $doc
        grid $w.l$t -sticky "w" -padx 3 -pady 3
        if {$flag} {
            # Initialise if given.
            if {[lsearch -exact $pArgv $name] >= 0} {
                set ::eskil($top,edit,$name,$pI) 1
                # Move responsibility from global argv
                set ix [lsearch -exact $::eskil(argv) $name]
                if {$ix >= 0} {
                    set ::eskil(argv) [lreplace $::eskil(argv) $ix $ix]
                    lappend ::eskil($top,pluginargv,$pI) $name
                }
            }
            ttk::checkbutton $w.s$t -text "On" \
                    -variable ::eskil($top,edit,$name,$pI)
            grid $w.s$t -row $t -column 1 -sticky "w" -padx 3 -pady 3
        } else {
            # Initialise if given.
            set ix [lsearch -exact $pArgv $name]
            if {$ix >= 0} {
                set ::eskil($top,edit,$name,$pI) [lindex $pArgv $ix+1]
                # Move responsibility from global argv
                set ix [lsearch -exact $::eskil(argv) $name]
                if {$ix >= 0} {
                    lappend ::eskil($top,pluginargv,$pI) $name \
                            [lindex $::eskil(argv) $ix+1]
                    set ::eskil(argv) [lreplace $::eskil(argv) $ix $ix+1]
                }
            }
            ttk::entry $w.s$t \
                    -textvariable ::eskil($top,edit,$name,$pI)
            grid $w.s$t -row $t -column 1 -sticky we -padx 3 -pady 3
        }
        incr t
    }
    grid columnconfigure $w 1 -weight 1
    if {$t == 0} {
        ttk::label $w.l -text "No specific configuration"
        grid $w.l -sticky "w" -padx 3 -pady 3
        return
    }
}

# Ok or Apply pressend in Plugin Preference
proc EditPrefPluginsOk {top wt apply} {
    # Compress plugin info in tab order
    set allN {}
    foreach win [$wt.tab tabs] {
        set pI [lindex [split $win ","] end]
        if { ! [string is integer -strict $pI]} continue
        # Find all used.
        if {$::eskil($top,edit,pluginname,$pI) ne ""} {
            lappend allN $pI
        }
    }
    if {[llength $allN] == 0} {
        lappend allN 1
    }

    # Keep the dialog if we are only applying
    if { ! $apply} {
        destroy $wt
    }

    # Transfer them to consecutive numbers
    set t 1
    foreach pI $allN {
        set ::eskil($top,pluginname,$t)  $::eskil($top,edit,pluginname,$pI)
        set ::eskil($top,plugininfo,$t)  $::eskil($top,edit,plugininfo,$pI)
        set ::eskil($top,pluginallow,$t) $::eskil($top,edit,pluginallow,$pI)
        incr t
    }
    # Remove any old
    foreach item [array names ::eskil $top,pluginname,*] {
        set pI [lindex [split $item ","] end]
        if {$pI >= $t} {
            unset ::eskil($top,pluginname,$pI)

            set ::eskil($top,plugininfo,$pI) ""
            set ::eskil($top,pluginallow,$pI) 0
        }
    }

    # Handle all plugins
    foreach item [array names ::eskil $top,pluginname,*] {
        set pI [lindex [split $item ","] end]
        if {$::eskil($top,pluginname,$pI) ne ""} {
            set pinterp [createPluginInterp $::eskil($top,pluginname,$pI) \
                                 $::eskil($top,plugininfo,$pI) \

                                 $::eskil($top,pluginallow,$pI) pinfo]
        } else {
            set pinterp ""
            set pinfo ""
        }
        set ::eskil($top,plugin,$pI) $pinterp
        set ::eskil($top,pluginpinfo,$pI) $pinfo
        set ::eskil($top,pluginargv,$pI) {}
        foreach {name flag doc} $::eskil($top,edit,opts,$pI) {
            if {$flag} {
                if {[info exists ::eskil($top,edit,$name,$pI)] && \
                            $::eskil($top,edit,$name,$pI)} {
                    lappend ::eskil($top,pluginargv,$pI) $name
                }
            } else {


                if {[info exists ::eskil($top,edit,$name,$pI)] && \
                            $::eskil($top,edit,$name,$pI) ne ""} {
                    lappend ::eskil($top,pluginargv,$pI) $name \
                            $::eskil($top,edit,$name,$pI)
                }
            }
        }
    }
}


# Put Tcl code in a text widget, with some syntax highlighting
proc TextViewTcl {tW data} {
    $tW tag configure comment -foreground "#b22222"
    foreach line [split $data \n] {
        if {[regexp {^\s*#} $line]} {
            $tW insert end $line\n comment
        } elseif {[regexp {^(.*;\s*)(#.*)$} $line -> pre post]} {
            $tW insert end $pre
            $tW insert end $post\n comment
        } else {
            $tW insert end $line\n
        }
    }
}

proc SelectPlugin {top pI plugin} {
    $::eskil($top,edit,showW,$pI)  configure -state disable
    $::eskil($top,edit,cloneW,$pI) configure -state disable
    $::eskil($top,edit,editW,$pI)  configure -state disable
    if {$plugin eq ""} {
        return
    }

    $::eskil($top,edit,showW,$pI)  configure -state normal
    # TODO: Enable when this works.
    #$::eskil($top,edit,cloneW,$pI) configure -state normal
    foreach name [dict keys $::eskil(plugins)] {
        if {$name eq $plugin} {
            # TODO: Enable when this works.
            #$::eskil($top,edit,editW,$pI)  configure -state normal
        }
    }
}

proc EditPlugin {parent plugin} {
    # TODO
}

proc ClonePlugin {parent plugin} {
    set res [LocatePlugin $plugin]
    dict set res name clone_$plugin
    dict set ::eskil(plugins) clone_$plugin $res
}

# Show plugin source
proc ShowPlugin {parent plugin} {
    set res [LocatePlugin $plugin]
    set data [dict get $res data]
    if {$data eq ""} return

    set wt $parent.plugin
    if {[winfo exists $wt]} {
        wm deiconify $wt
    } else {
        toplevel $wt -padx 3 -pady 3
    }
    destroy {*}[winfo children $wt]
    ttk::frame $wt._bg
    place $wt._bg -x 0 -y 0 -relwidth 1.0 -relheight 1.0 -border outside
    wm title $wt "Plugin: $plugin"

    set t [Scroll both text $wt.t -width 80 -height 30 -font myfont -wrap none]
    pack $wt.t -fill both -expand 1
    bind $t <Control-a> "[list $t tag add sel 1.0 end];break"

    TextViewTcl $t $data
}

Added src/preprocess.tcl.

















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
#---------------------------------------------------------- -*- tcl -*-
#  Eskil, Preprocess dialog
#
#  Copyright (c) 2004-2017, Peter Spjuth  (peter.spjuth@gmail.com)
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; see the file COPYING.  If not, write to
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------
#
# The format of the ::Pref(preprocessn) is:
# Flat list of stride 2, (name data)
# Data dict elements: preprocess active save
# Preprocess element is a flat list of stride 3 (RE sub side)
# If side is a special word, it is a special shortcut format.
#
# There used to be a ::Pref(preprocess) with a different format, the new
# was named a bit different for compatibilty with saved preferences.

# Return active preprocess items as a flat list with stride 3
proc getActivePreprocess {top} {
    set res {}
    set count 0
    foreach {name data} $::Pref(preprocessn) {
        if {[dict get $data active]} {
            foreach {RE sub side} [dict get $data preprocess] {
                if {$side eq "Subst"} {
                    # Translate to Regexps
                    # Unique substitution
                    set pattern __$count[clock clicks -microseconds]__
                    incr count
                    lappend res $RE  $pattern left
                    lappend res $sub $pattern right
                } elseif {$side eq "Prefix"} {
                    # Translate to Regexp
                    set RE2 [string map [list % $RE] {^.*?\m(%\w+).*$}]
                    lappend res $RE2 {\1} ""
                } else {
                    # Generic
                    lappend res $RE $sub $side
                }
            }
        }
    }
    return $res
}

# Entry for adding preprocess from command line
proc addPreprocess {name RE sub side} {
    set data {}
    dict set data preprocess [list $RE $sub $side]
    dict set data active 1
    dict set data save 0
    lappend ::Pref(preprocessn) $name $data
}

# Get the value used when saving preferences
proc getPreprocessSave {} {
    set res {}
    foreach {name data} $::Pref(preprocessn) {
        if {[dict get $data save]} {
            # Always save with active 0 for now.
            # A user can edit the save file to have it by default.
            dict set data active 0
            lappend res $name $data
        }
    }
    return $res
}

# This is called when Ok or Apply is pressed.
# Update preference from dialog contents.
proc EditPrefRegsubOk {top W item {keep 0}} {
    set exa $::eskil($top,prefregexa)

    set result {}
    for {set t 1} {[info exists ::eskil($top,prefregexp$t)]} {incr t} {
        set RE $::eskil($top,prefregexp$t)
        set Sub $::eskil($top,prefregsub$t)
        set l $::eskil($top,prefregleft$t)
        set r $::eskil($top,prefregright$t)
        if {$RE eq ""} continue
        switch $::eskil($top,prefregtype$t) {
            Subst {
                lappend result $RE $Sub Subst
            }
            Prefix {
                lappend result $RE "" Prefix
            }
            default {
                set side ""
                if {$l && !$r} { set side left }
                if { ! $l && $r} { set side right }
                if { ! $l && !$r} { continue }

                if {[catch {regsub -all -- $RE $exa $Sub _} err]} {
                    return
                }
                lappend result $RE $Sub $side
            }
        }
    }

    set ::TmpPref(preprocess,re,$item) $result
    set ::TmpPref(preprocess,active,$item) 1

    if {$keep} {
        # Apply was pressed, also apply main dialog
        # TODO: Get widgets right. Right now it does not matter
        EditPrefPrepOk . $top 1
        return
    }
    destroy $W

    array unset ::eskil $top,prefregexp*
    array unset ::eskil $top,prefregsub*
    array unset ::eskil $top,prefregleft*
    array unset ::eskil $top,prefregright*
    array unset ::eskil $top,prefregtype*
}

# Update the example in the preprocess dialog
proc EditPrefRegsubUpdate {top args} {
    set exal $::eskil($top,prefregexa)
    set exar $::eskil($top,prefregexa)
    set exal2 $::eskil($top,prefregexa2)
    set exar2 $::eskil($top,prefregexa2)
    set ok $::widgets($top,prefRegsubOk)
    set app $::widgets($top,prefRegsubApply)

    set pp {}
    for {set t 1} {[info exists ::eskil($top,prefregexp$t)]} {incr t} {
        set RE $::eskil($top,prefregexp$t)
        set Sub $::eskil($top,prefregsub$t)
        set l $::eskil($top,prefregleft$t)
        set r $::eskil($top,prefregright$t)

        if {$RE eq ""} continue

        switch $::eskil($top,prefregtype$t) {
            Subst {
                set pattern __$t[clock clicks -microseconds]__
                lappend pp $RE  $pattern 1 0
                lappend pp $Sub $pattern 0 1
            }
            Prefix {
                set RE2 [string map [list % $RE] {^.*?\m(%\w+).*$}]
                lappend pp $RE2 {\1} 1 1
            }
            default {
                lappend pp $RE $Sub $l $r
            }
        }
    }
    foreach {RE Sub l r} $pp {
        if {$l} {
            if {[catch {regsub -all -- $RE $exal $Sub result} err]} {
                set ::eskil($top,prefregresultl) "$t ERROR: $err"
                $ok configure -state disabled
                $app configure -state disabled
                return
            } else {
                set exal $result
            }
            if {[catch {regsub -all -- $RE $exal2 $Sub result} err]} {
                set ::eskil($top,prefregresultl2) "$t ERROR: $err"
                $ok configure -state disabled
                $app configure -state disabled
                return
            } else {
                set exal2 $result
            }
        }
        if {$r} {
            if {[catch {regsub -all -- $RE $exar $Sub result} err]} {
                set ::eskil($top,prefregresultr) "$t ERROR: $err"
                $ok configure -state disabled
                $app configure -state disabled
                return
            } else {
                set exar $result
            }
            if {[catch {regsub -all -- $RE $exar2 $Sub result} err]} {
                set ::eskil($top,prefregresultr2) "$t ERROR: $err"
                $ok configure -state disabled
                $app configure -state disabled
                return
            } else {
                set exar2 $result
            }
        }
    }
    set ::eskil($top,prefregresultl2) $exal2
    set ::eskil($top,prefregresultr2) $exar2
    set ::eskil($top,prefregresultl) $exal
    set ::eskil($top,prefregresultr) $exar
    $ok configure -state normal
    $app configure -state normal
}

# Add a new entry in the preprocess dialog
proc AddPrefRegsub {top parent {type {}}} {
    # Figure out next number to use
    for {set t 1} {[winfo exists $parent.fr$t]} {incr t} {
        #Empty
    }
    # Default values
    if { ! [info exists ::eskil($top,prefregexp$t)]} {
        set ::eskil($top,prefregtype$t) Generic
        set ::eskil($top,prefregexp$t) ""
        set ::eskil($top,prefregexp$t) ""
        set ::eskil($top,prefregsub$t) ""
        set ::eskil($top,prefregleft$t) 1
        set ::eskil($top,prefregright$t) 1
    }
    # Override type if given
    if {$type ne ""} {
        set ::eskil($top,prefregtype$t) $type
    }

    set W [ttk::frame $parent.fr$t -borderwidth 2 -relief groove -padding 3]
    pack $W -side "top" -fill x -padx 3 -pady 3

    switch $::eskil($top,prefregtype$t) {
        Subst {
            ttk::label $W.l1 -text "Left:" -anchor w
            ttk::entryX $W.e1 -textvariable ::eskil($top,prefregexp$t) -width 20
            ttk::label $W.l2 -text "Right:" -anchor w
            ttk::entryX $W.e2 -textvariable ::eskil($top,prefregsub$t)
            grid $W.l1 $W.e1 $W.l2 $W.e2 -sticky we -padx 3 -pady 3
            grid columnconfigure $W {0 2} -uniform a
            grid columnconfigure $W {1 3} -weight 1 -uniform b

            addBalloon $W.l1 -fmt {
                Each pattern is applied to its side and substituted
                for a common unique string.
            }
        }
        Prefix {
            ttk::label $W.l1 -text "Prefix:" -anchor w
            ttk::entryX $W.e1 -textvariable ::eskil($top,prefregexp$t) -width 20
            grid $W.l1 $W.e1 -sticky we -padx 3 -pady 3
            grid columnconfigure $W 1 -weight 1
            addBalloon $W.l1 -fmt {
                Only one word that start with prefix is valid for line
                comparison.
            }
        }
        default {
            ttk::label $W.l1 -text "Regexp:" -anchor w
            ttk::entryX $W.e1 -textvariable ::eskil($top,prefregexp$t) -width 60
            ttk::label $W.l2 -text "Subst:" -anchor w
            ttk::entryX $W.e2 -textvariable ::eskil($top,prefregsub$t)
            ttk::checkbutton $W.cb1 -text "Left"  -variable ::eskil($top,prefregleft$t)
            ttk::checkbutton $W.cb2 -text "Right" -variable ::eskil($top,prefregright$t)
            addBalloon $W.cb1 "Apply to left file"
            addBalloon $W.cb2 "Apply to right file"

            grid $W.l1 $W.e1 $W.cb1 -sticky we -padx 3 -pady 3
            grid $W.l2 $W.e2 $W.cb2 -sticky we -padx 3 -pady 3
            grid columnconfigure $W 1 -weight 1
        }
    }

    trace add variable ::eskil($top,prefregexp$t) write \
            [list EditPrefRegsubUpdate $top]
    trace add variable ::eskil($top,prefregsub$t) write \
            [list EditPrefRegsubUpdate $top]
    trace add variable ::eskil($top,prefregleft$t) write \
            [list EditPrefRegsubUpdate $top]
    trace add variable ::eskil($top,prefregright$t) write \
            [list EditPrefRegsubUpdate $top]
}

# Editor for one item in ::Pref(preprocessn)
proc EditPrefRegsub {top item} {
    set W $top.prefregsub

    ToplevelForce $W "Preferences: Preprocess group"

    # Buttons
    ttk::frame $W.fb1 -padding 3
    ttk::button $W.fb1.b1 -text "Add" -command [list AddPrefRegsub $top $W Generic]
    addBalloon $W.fb1.b1 "Add generic pattern"
    ttk::button $W.fb1.b2 -text "Add Subst" -command [list AddPrefRegsub $top $W Subst]
    addBalloon $W.fb1.b2 "Add using substitution shortcut"
    ttk::button $W.fb1.b3 -text "Add Prefix" -command [list AddPrefRegsub $top $W Prefix]
    addBalloon $W.fb1.b3 "Add using prefix shortcut"
    grid $W.fb1.b1 $W.fb1.b2 $W.fb1.b3 -sticky we -ipadx 5 -padx 3 -pady 3
    grid columnconfigure $W.fb1 all -uniform a
    grid anchor $W.fb1 w

    # Result example part
    if { ! [info exists ::eskil($top,prefregexa)]} {
        set ::eskil($top,prefregexa) \
                "An example TextString FOR_REGSUB /* Comment */"
        set ::eskil($top,prefregexa2) \
                "An example TextString FOR_REGSUB /* Comment */"
    }
    ttk::labelframe $W.res -text "Preprocessing result" -padding 3
    ttk::label $W.res.l3 -text "Example 1:" -anchor w
    ttk::entryX $W.res.e3 -textvariable ::eskil($top,prefregexa) -width 60
    ttk::label $W.res.l4l -text "Result 1 L:" -anchor w
    ttk::label $W.res.l4r -text "Result 1 R:" -anchor w
    ttk::label $W.res.e4l -textvariable ::eskil($top,prefregresultl) \
            -anchor w -width 10
    ttk::label $W.res.e4r -textvariable ::eskil($top,prefregresultr) \
            -anchor w -width 10
    ttk::label $W.res.l5 -text "Example 2:" -anchor w
    ttk::entryX $W.res.e5 -textvariable ::eskil($top,prefregexa2)
    ttk::label $W.res.l6l -text "Result 2 L:" -anchor w
    ttk::label $W.res.l6r -text "Result 2 R:" -anchor w
    ttk::label $W.res.e6l -textvariable ::eskil($top,prefregresultl2) \
            -anchor w -width 10
    ttk::label $W.res.e6r -textvariable ::eskil($top,prefregresultr2) \
            -anchor w -width 10

    grid $W.res.l3  $W.res.e3  -sticky we -padx 3 -pady 3
    grid $W.res.l4l $W.res.e4l -sticky we -padx 3 -pady 3
    grid $W.res.l4r $W.res.e4r -sticky we -padx 3 -pady 3
    grid $W.res.l5  $W.res.e5  -sticky we -padx 3 -pady 3
    grid $W.res.l6l $W.res.e6l -sticky we -padx 3 -pady 3
    grid $W.res.l6r $W.res.e6r -sticky we -padx 3 -pady 3
    grid columnconfigure $W.res 1 -weight 1

    # Buttons
    ttk::frame $W.fb -padding 3
    ttk::button $W.fb.b1 -text "Ok"     -command [list EditPrefRegsubOk $top $W $item]
    ttk::button $W.fb.b2 -text "Apply"  -command [list EditPrefRegsubOk $top $W $item 1]
    ttk::button $W.fb.b3 -text "Cancel" -command [list destroy $W]
    set ::widgets($top,prefRegsubOk) $W.fb.b1
    set ::widgets($top,prefRegsubApply) $W.fb.b2

    grid $W.fb.b1 x $W.fb.b2 x $W.fb.b3 -sticky we
    grid columnconfigure $W.fb {0 2 4} -uniform a
    grid columnconfigure $W.fb {1 3} -weight 1

    # Top layout
    pack $W.fb1 -side "top" -fill x -padx 3 -pady 3
    pack $W.fb $W.res -side bottom -fill x -padx 3 -pady 3

    # Fill in existing or an empty line
    set preprocess $::TmpPref(preprocess,re,$item)

    if {[llength $preprocess] == 0} {
        AddPrefRegsub $top $W Generic
    } else {
        set t 1
        foreach {RE Sub side} $preprocess {
            set ::eskil($top,prefregexp$t) $RE
            set ::eskil($top,prefregsub$t) $Sub
            set ::eskil($top,prefregleft$t) 0
            set ::eskil($top,prefregright$t) 0
            set ::eskil($top,prefregtype$t) Generic
            if {$side in {Subst Prefix}} {
                set ::eskil($top,prefregtype$t) $side
            } else {
                if {$side eq "" || $side eq "left"} {
                    set ::eskil($top,prefregleft$t) 1
                }
                if {$side eq "" || $side eq "right"} {
                    set ::eskil($top,prefregright$t) 1
                }
            }
            AddPrefRegsub $top $W
            incr t
        }
    }

    trace add variable ::eskil($top,prefregexa) write \
            [list EditPrefRegsubUpdate $top]
    trace add variable ::eskil($top,prefregexa2) write \
            [list EditPrefRegsubUpdate $top]
    EditPrefRegsubUpdate $top
}


# This is called when Ok or Apply is pressed.
proc EditPrefPrepOk {top W {keep 0}} {
    # Update preference from dialog contents.
    set new {}
    for {set r 1} {$r <= $::TmpPref(preprocess,n)} {incr r} {
        set name $::TmpPref(preprocess,name,$r)
        set act $::TmpPref(preprocess,active,$r)
        set save $::TmpPref(preprocess,save,$r)
        set re $::TmpPref(preprocess,re,$r)
        lappend new $name
        lappend new [dict create active $act "save" $save preprocess $re]
    }
    set ::Pref(preprocessn) $new
    
    if {$keep} return
    destroy $W
}

# Create a toplevel, even if it exists
proc ToplevelForce {W title} {
    destroy $W
    ttk::toplevel $W -padx 3 -pady 3
    wm title $W $title
}

# Move an item one step up
proc EditPrefPreUp {rI} {
    #puts EditPrefPreUp$rI
    # Sanity check
    if {$rI <= 1 || $rI > $::TmpPref(preprocess,n)} {
        return
    }
    set pI [expr {$rI - 1}]
    foreach item {name active save re} {
        set tmp $::TmpPref(preprocess,$item,$rI)
        set ::TmpPref(preprocess,$item,$rI) $::TmpPref(preprocess,$item,$pI)
        set ::TmpPref(preprocess,$item,$pI) $tmp
    }
}

proc EditPrefPreprocessAddItem {W autoEdit} {
    set r $::TmpPref(preprocess,n)
    incr r
    if { ! [info exists ::TmpPref(preprocess,name,$r)]} {
        set ::TmpPref(preprocess,name,$r) ""
        set ::TmpPref(preprocess,active,$r) 0
        set ::TmpPref(preprocess,save,$r) 0
        set ::TmpPref(preprocess,re,$r) ""
    }
    ttk::entry $W.fp.ne$r -textvariable ::TmpPref(preprocess,name,$r)
    addBalloon $W.fp.ne$r "Name of preprocess group (optional)"
    ttk::checkbutton $W.fp.cba$r -text "Active" \
            -variable ::TmpPref(preprocess,active,$r)
    addBalloon $W.fp.cba$r "Activate group for this session"
    ttk::checkbutton $W.fp.cbs$r -text "Save" \
            -variable ::TmpPref(preprocess,save,$r)
    addBalloon $W.fp.cbs$r "Save group when preferences are saved"
    ttk::button $W.fp.be$r -text "Edit" \
            -command [list EditPrefRegsub $W $r]
    addBalloon $W.fp.be$r "Edit the associated list of regexps"
    if {$autoEdit} {
        after idle [list after 50 [list $W.fp.be$r invoke]]
    }
    ttk::button $W.fp.bu$r -image $::img(up) \
            -command [list EditPrefPreUp $r]
    addBalloon $W.fp.bu$r "Move group up in list"
    grid $W.fp.ne$r $W.fp.cba$r $W.fp.cbs$r $W.fp.be$r $W.fp.bu$r -sticky we \
            -padx 3 -pady 3
    # Make buttons symmetric
    grid  $W.fp.be$r $W.fp.bu$r -sticky news

    set ::TmpPref(preprocess,n) $r
}

proc EditPrefPreprocess {top} {
    set W $top.prefpreprocess

    # Make a working copy more suitable for GUI connection
    set r 0
    foreach {name data} $::Pref(preprocessn) {
        incr r
        set ::TmpPref(preprocess,name,$r) $name
        set ::TmpPref(preprocess,active,$r) [dict get $data active]
        set ::TmpPref(preprocess,save,$r) [dict get $data save]
        set ::TmpPref(preprocess,re,$r) [dict get $data preprocess]
    }
    # Create one if there is none, to simplify GUI usage
    set autoEdit 0
    if {$r == 0} {
        set autoEdit 1
        incr r
    }
    set ::TmpPref(preprocess,n) 0
    set nItems $r

    ToplevelForce $W "Preferences: Preprocess"

    # Frame for List of preprocessing
    ttk::frame $W.fp -padding 3
    grid columnconfigure $W.fp 0 -weight 1
    
    for {set r 1} {$r <= $nItems} {incr r} {
        EditPrefPreprocessAddItem $W $autoEdit
    }

    # Frame for action buttons
    ttk::frame $W.fa -padding 3
    ttk::button $W.fa.b1 -text "Add" \
            -command [list EditPrefPreprocessAddItem $W 1]
    addBalloon $W.fa.b1 "Add a preprocess group"

    grid $W.fa.b1 -sticky we
    grid columnconfigure $W.fa {0 2 4} -uniform a
    grid columnconfigure $W.fa {1 3} -weight 1

    
    # Frame for dialog Buttons
    ttk::frame $W.fb -padding 3
    ttk::button $W.fb.b1 -text "Ok"     -command [list EditPrefPrepOk $top $W]
    ttk::button $W.fb.b2 -text "Apply"  -command [list EditPrefPrepOk $top $W 1]
    ttk::button $W.fb.b3 -text "Cancel" -command [list destroy $W]

    grid $W.fb.b1 x $W.fb.b2 x $W.fb.b3 -sticky we
    grid columnconfigure $W.fb {0 2 4} -uniform a
    grid columnconfigure $W.fb {1 3} -weight 1

    # Top layout
    pack $W.fb -side bottom -fill x
    pack $W.fa -side bottom -fill x
    pack $W.fp -side "top" -fill both -expand 1
}

Changes to src/print.tcl.

38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
        set res [format "%*s" $maxlen $res]
    }
    return $res
}

# Process the line numbers from the line number widget into a list
# of "linestarters"
proc ProcessLineno {w maxlen} {
    set tdump [$w dump -tag -text 1.0 end]
    set tag ""
    set line ""
    set lines {}
    foreach {key value index} $tdump {
        if {$key eq "tagon"} {
            if {$value eq "change" || [string match "new*" $value]} {
                set tag $value







|
|







38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
        set res [format "%*s" $maxlen $res]
    }
    return $res
}

# Process the line numbers from the line number widget into a list
# of "linestarters"
proc ProcessLineno {W maxlen} {
    set tdump [$W dump -tag -text 1.0 end]
    set tag ""
    set line ""
    set lines {}
    foreach {key value index} $tdump {
        if {$key eq "tagon"} {
            if {$value eq "change" || [string match "new*" $value]} {
                set tag $value
92
93
94
95
96
97
98
99
100




101
102
103
104
105
106
107
108
        set n [expr {(- $i - $index - 1) % 8 + 1}]
        set text [string replace $text $i $i [format %*s $n ""]]
    }
    return $text
}

# Find the lastnumber in a text widget
proc FindLastNumber {w} {
    set index [$w search -backwards -regexp {\d} end]




    set line [$w get "$index linestart" "$index lineend"]
    #puts "X '$line' '$index'"
    regexp {\d+} $line number
    return $number
}

# Main print function
proc PrintDiffs {top {quiet 0}} {







|
|
>
>
>
>
|







92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
        set n [expr {(- $i - $index - 1) % 8 + 1}]
        set text [string replace $text $i $i [format %*s $n ""]]
    }
    return $text
}

# Find the lastnumber in a text widget
proc FindLastNumber {W} {
    set index [$W search -backwards -regexp {\d} end]
    if {$index eq ""} {
        # There where no numbers there, treat it like 0
        return 0
    }
    set line [$W get "$index linestart" "$index lineend"]
    #puts "X '$line' '$index'"
    regexp {\d+} $line number
    return $number
}

# Main print function
proc PrintDiffs {top {quiet 0}} {
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260


261
262
263
264



























265
266
267
268
269
270
271
        } elseif {$w2 > $w1} {
            for {set t $w1} {$t < $w2} {incr t} {
                lappend wraplines1 {}
            }
        }
    }

    PdfPrint $top $wraplength $maxlen $wraplines1 $wraplines2

    # Finished

    normalCursor $top
}

proc PdfPrint {top cpl cpln wraplines1 wraplines2} {

    if {$::diff($top,printFile) != ""} {
        set pdfFile $::diff($top,printFile)
    } else {
        set pdfFile ~/eskil.pdf
    }

    if {![regexp {^(.*)( \(.*?\))$} $::diff($top,leftLabel) -> lfile lrest]} {
        set lfile $::diff($top,leftLabel)
        set lrest ""
    }
    set lfile [file tail $lfile]$lrest
    if {![regexp {^(.*)( \(.*?\))$} $::diff($top,rightLabel) -> rfile rrest]} {
        set rfile $::diff($top,rightLabel)
        set rrest ""
    }
    set rfile [file tail $rfile]$rrest

    set pdf [eskilprint %AUTO% -file $pdfFile -cpl $cpl -cpln $cpln \
                     -headleft $lfile -headright $rfile -headsize 10]


    set linesPerPage [$pdf getNLines]
    $pdf setTag change $::Pref(printColorChange)
    $pdf setTag new1   $::Pref(printColorNew1)
    $pdf setTag new2   $::Pref(printColorNew2)




























    set len1 [llength $wraplines1]
    set len2 [llength $wraplines2]

    set max [expr {$len1 > $len2 ? $len1 : $len2}]
    set npages [expr {($max + $linesPerPage - 1) / $linesPerPage}]
    $pdf configure -headnpages $npages







|






|

|
|




|
|



|
|





|
>
>




>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
        } elseif {$w2 > $w1} {
            for {set t $w1} {$t < $w2} {incr t} {
                lappend wraplines1 {}
            }
        }
    }

    PdfPrint $top $wraplength $maxlen $wraplines1 $wraplines2 $quiet

    # Finished

    normalCursor $top
}

proc PdfPrint {top cpl cpln wraplines1 wraplines2 {quiet 0}} {

    if {$::eskil($top,printFile) != ""} {
        set pdfFile $::eskil($top,printFile)
    } else {
        set pdfFile ~/eskil.pdf
    }

    if { ! [regexp {^(.*)( \(.*?\))$} $::eskil($top,leftLabel) -> lfile lrest]} {
        set lfile $::eskil($top,leftLabel)
        set lrest ""
    }
    set lfile [file tail $lfile]$lrest
    if { ! [regexp {^(.*)( \(.*?\))$} $::eskil($top,rightLabel) -> rfile rrest]} {
        set rfile $::eskil($top,rightLabel)
        set rrest ""
    }
    set rfile [file tail $rfile]$rrest

    set pdf [eskilprint %AUTO% -file $pdfFile -cpl $cpl -cpln $cpln \
                     -headleft $lfile -headright $rfile \
                     -lnsp $::Pref(printLineSpace) \
                     -headsize $::Pref(printHeaderSize)]
    set linesPerPage [$pdf getNLines]
    $pdf setTag change $::Pref(printColorChange)
    $pdf setTag new1   $::Pref(printColorNew1)
    $pdf setTag new2   $::Pref(printColorNew2)

    # Preprocess for page breaks in patch mode
    if {$::eskil($top,mode) eq "patch"} {
        set i 0
        set newWlines1 {}
        set newWlines2 {}
        foreach wline1 $wraplines1 wline2 $wraplines2 {
            if {[string match "-+-+-+-+-+-+-+-+-*" [lindex $wline1 0]]} {
                # This is a patch chunk header
                if {$i > 3} {
                    for {} {$i < $linesPerPage} {incr i} {
                        lappend newWlines1 {}
                        lappend newWlines2 {}
                    }
                    set i 0
                }
            }
            incr i
            if {$i >= $linesPerPage} {
                set i 0
            }
            lappend newWlines1 $wline1
            lappend newWlines2 $wline2
        }
        set wraplines1 $newWlines1
        set wraplines2 $newWlines2
    }

    set len1 [llength $wraplines1]
    set len2 [llength $wraplines2]

    set max [expr {$len1 > $len2 ? $len1 : $len2}]
    set npages [expr {($max + $linesPerPage - 1) / $linesPerPage}]
    $pdf configure -headnpages $npages
283
284
285
286
287
288
289





290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306






























































307
308
309
310
311
312
313
314
315





316
317






























318
319
320
321
322
323
324
325
326
327

328
329
330
331
332

333
334

335
336
337
338

339
340
341
342
343
344
345
346
347
348
349
350
351
352

353
354
355
356
357
358
359
360
361





362
363
364
365
366
367
368
369
370
371
372
373
374
375


376
377
378
379
380
381
382
383


384
385
386
387
388
389
390
391

392
393
394
395
396
397
398
399

400

401
402
403



404
405
406
407
408
409
410


411
412
413
414
415
416
417
418
419
420
421
422

423
424
425
426
427
428
429
430

431
432
        $pdf setHalf right
        for {set i 0} {$i < $linesPerPage && $i2 < $len2} {incr i ; incr i2} {
            $pdf drawTextLine [lindex $wraplines2 $i2]
            $pdf newLine
        }
    }
    $pdf endPrint





}

# Count the length of a line during a text dump
proc AccumulateMax {key value index} {
    set index [lindex [split $index "."] 1]
    set len [expr {[string length $value] + $index - 1}]
    if {$len > $::diff(currentCharsPerLine)} {
        set ::diff(currentCharsPerLine) $len
    }
}

# Count the longest line length in the current display
proc CountCharsPerLine {top} {
    set ::diff(currentCharsPerLine) 0
    $::widgets($top,wDiff1) dump -text -command AccumulateMax 1.0 end
    $::widgets($top,wDiff2) dump -text -command AccumulateMax 1.0 end
    return $::diff(currentCharsPerLine)






























































}

proc BrowsePrintFileName {top entry} {
    set prev $::diff($top,printFile)
    set dir [file dirname $prev]

    set apa [tk_getSaveFile -initialdir $dir -initialfile [file tail $prev] \
                     -parent [winfo toplevel $entry] -title "PDF file"]
    if {$apa ne ""} {





        set ::diff($top,printFile) $apa
        $entry xview end






























    }
}

# Create a print dialog for PDF.
proc doPrint {top {quiet 0}} {
    if {$quiet} {
        PrintDiffs $top 1
        return
    }


    destroy .pr
    toplevel .pr -padx 3 -pady 3
    wm title .pr "Print diffs to PDF"

    # Layout


    ttk::label .pr.hsl -anchor w -text "Header Size"

    tk::spinbox .pr.hss -textvariable ::Pref(printHeaderSize) \
        -from 5 -to 16 -width 3

    ttk::label .pr.cll -anchor w -text "Chars per line"

    ttk::entryX .pr.cle -textvariable ::Pref(printCharsPerLine) -width 4
    ttk::frame .pr.clf
    set values [list 80]
    set cpl [CountCharsPerLine $top]
    if {$cpl != 0} {
        lappend values $cpl
    }
    if {[string is digit -strict $::Pref(printCharsPerLine)]} {
        lappend values $::Pref(printCharsPerLine)
    }
    set values [lsort -unique -integer $values]
    foreach value $values {
        ttk::radiobutton .pr.clf.$value -variable ::Pref(printCharsPerLine) \
            -value $value -text $value

        pack .pr.clf.$value -side left -padx 3 -pady 3
    }

    # Select paper size
    set paperlist [lsort -dictionary [pdf4tcl::getPaperSizeList]]
    ttk::label .pr.psl -anchor w -text "Paper Size"
    ttk::combobox .pr.psc -values $paperlist -textvariable ::Pref(printPaper) \
            -width 6 -state readonly
    





    # Color
    foreach {::TmpPref(chr) ::TmpPref(chg) ::TmpPref(chb)} \
            $::Pref(printColorChange) break
    foreach {::TmpPref(n1r) ::TmpPref(n1g) ::TmpPref(n1b)} \
            $::Pref(printColorNew1) break
    foreach {::TmpPref(n2r) ::TmpPref(n2g) ::TmpPref(n2b)} \
            $::Pref(printColorNew2) break
    trace add variable ::TmpPref write {
        set ::Pref(printColorChange) [list $::TmpPref(chr) $::TmpPref(chg) $::TmpPref(chb)]
        set ::Pref(printColorNew1)   [list $::TmpPref(n1r) $::TmpPref(n1g) $::TmpPref(n1b)]
        set ::Pref(printColorNew2)   [list $::TmpPref(n2r) $::TmpPref(n2g) $::TmpPref(n2b)]
    list}

    ttk::labelframe .pr.cf -text "Color" -padding 3



    ttk::label .pr.cf.l1 -text "Change"
    tk::spinbox .pr.cf.s1r -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 4 -textvariable ::TmpPref(chr)
    tk::spinbox .pr.cf.s1g -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 4 -textvariable ::TmpPref(chg)
    tk::spinbox .pr.cf.s1b -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 4 -textvariable ::TmpPref(chb)



    ttk::label .pr.cf.l2 -text "Old"
    tk::spinbox .pr.cf.s2r -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 4 -textvariable ::TmpPref(n1r)
    tk::spinbox .pr.cf.s2g -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 4 -textvariable ::TmpPref(n1g)
    tk::spinbox .pr.cf.s2b -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 4 -textvariable ::TmpPref(n1b)


    ttk::label .pr.cf.l3 -text "New"
    tk::spinbox .pr.cf.s3r -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 4 -textvariable ::TmpPref(n2r)
    tk::spinbox .pr.cf.s3g -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 4 -textvariable ::TmpPref(n2g)
    tk::spinbox .pr.cf.s3b -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 4 -textvariable ::TmpPref(n2b)



    grid .pr.cf.l1 .pr.cf.s1r .pr.cf.s1g .pr.cf.s1b -sticky w -padx 3 -pady 3
    grid .pr.cf.l2 .pr.cf.s2r .pr.cf.s2g .pr.cf.s2b -sticky w -padx 3 -pady 3
    grid .pr.cf.l3 .pr.cf.s3r .pr.cf.s3g .pr.cf.s3b -sticky w -padx 3 -pady 3




    # File

    ttk::label .pr.fnl -anchor w -text "File name"
    ttk::entryX .pr.fne -textvariable ::diff($top,printFile) -width 30
    ttk::button .pr.fnb -text Browse \
            -command [list BrowsePrintFileName $top .pr.fne]



    if {$::diff($top,printFile) eq ""} {
        set ::diff($top,printFile) "~/eskil.pdf"
    }

    ttk::frame .pr.fb
    ttk::button .pr.b1 -text "Print to File" \
            -command "destroy .pr; update; PrintDiffs $top"
    ttk::button .pr.b2 -text "Cancel" -command {destroy .pr}
    pack .pr.b1 -in .pr.fb -side left  -padx 3 -pady 3 -ipadx 5
    pack .pr.b2 -in .pr.fb -side right -padx 3 -pady 3 -ipadx 5


    grid .pr.hsl .pr.hss         -sticky we -padx 3 -pady 3
    grid .pr.psl .pr.psc         -sticky we -padx 3 -pady 3
    grid .pr.cll .pr.cle .pr.clf -sticky we -padx 3 -pady 3
    grid .pr.cf  -       - -     -sticky w  -padx 3 -pady 3
    grid .pr.fnl .pr.fne - .pr.fnb -sticky we -padx 3 -pady 3
    grid .pr.fb  -       - -       -sticky we -padx 3 -pady 3

    grid columnconfigure .pr 2 -weight 1

}








>
>
>
>
>



|


|
|





|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



|




|
>
>
>
>
>
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>










>
|
|
|

|
>

|
>
|
|

|
>
|
|
<
<
<
<
|
<
|
<
<
|
|
|
>
|




|
|

|
>
>
>
>
>







|
|
<
<
<

|
>
>

|
|
|
|
|
|
|
>
>

|
|
|
|
|
|
|
>

|
|
|
|
|
|
|
>

>
|
|
|
>
>
>


|
<
|
|
|
>
>

|
|


|
|
|
|
|
|

>
|
|
|
<
|
<

|
>

<
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479




480

481


482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508



509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551

552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572

573

574
575
576
577

        $pdf setHalf right
        for {set i 0} {$i < $linesPerPage && $i2 < $len2} {incr i ; incr i2} {
            $pdf drawTextLine [lindex $wraplines2 $i2]
            $pdf newLine
        }
    }
    $pdf endPrint

    if { ! $quiet} {
        tk_messageBox -title "Eskil Print" -parent $top \
                -message "Printed $npages pages to $pdfFile" -type ok
    }
}

# Count the length of a line during a text dump
proc AccumulateMax {top key value index} {
    set index [lindex [split $index "."] 1]
    set len [expr {[string length $value] + $index - 1}]
    if {$len > 0} {
        lappend ::eskil($top,currentCharsPerLine) $len
    }
}

# Count the longest line length in the current display
proc CountCharsPerLine {top} {
    set ::eskil($top,currentCharsPerLine) {}
    $::widgets($top,wDiff1) dump -text -command [list AccumulateMax $top] 1.0 end
    $::widgets($top,wDiff2) dump -text -command [list AccumulateMax $top] 1.0 end
    set ::eskil($top,currentCharsPerLine) \
            [lsort -integer $::eskil($top,currentCharsPerLine)]
    return [lindex $::eskil($top,currentCharsPerLine) end]
}

# In a sorted list of integers, figure out where val fits
# In 8.6 this could use lsearch -bisect
proc FindPercentile {lst val} {
    set len [llength $lst]
    # No elements, so all are covered in a way
    if {$len == 0} { return 100 }
    # Above range, so 100%
    if {[lindex $lst end] <= $val} { return 100 }
    # Under range, so 0%
    if {[lindex $lst 0] > $val} { return 0 }
    # Single element should not slip through...
    if {$len <= 1} { return 0 }

    set i [lsearch -integer -all $lst $val]
    set i [lindex $i end]
    if {$i >= 0} {
        return [expr {100 * $i / ($len - 1)}]
    }

    # To keep search down, just look at multiples of 1%
    set prev 0
    for {set t 0} {$t <= 100} {incr t} {
        set i [expr {$t * ($len - 1) / 100}]
        if {$val < [lindex $lst $i]} {
            return $prev
        }
        set prev $t
    }
    return 99
}

# Figure out reasonable selections for line length.
# 80 chars, and longest line used are always included.
proc CharsPerLineOptions {top} {
    set values [list 80]
    set cpl [CountCharsPerLine $top]
    if {$cpl ne "" && $cpl != 0} {
        lappend values $cpl
    }
    # Include previous selection
    if {[string is digit -strict $::Pref(printCharsPerLine)]} {
        lappend values $::Pref(printCharsPerLine)
    }
    # Include 90% if reasonable
    set len [llength $::eskil($top,currentCharsPerLine)]
    set cpl [lindex $::eskil($top,currentCharsPerLine) [expr {9*$len/10}]]
    if {$cpl ne "" && $cpl != 0} {
        lappend values $cpl
    }

    set values [lsort -unique -integer $values]
    set result {}
    foreach value $values {
        set p [FindPercentile $::eskil($top,currentCharsPerLine) $value]
        lappend result $value "$value ($p %)" \
                "$p % of the lines are within this line length"
    }
    return $result
}

proc BrowsePrintFileName {top entry} {
    set prev $::eskil($top,printFile)
    set dir [file dirname $prev]

    set apa [tk_getSaveFile -initialdir $dir -initialfile [file tail $prev] \
                     -parent [winfo toplevel $entry] -title "PDF file"]
    if {$apa eq ""} return
    # Auto-add .pdf
    if {[file extension $apa] eq ""} {
        append apa .pdf
    }

    set ::eskil($top,printFile) $apa
    $entry xview end
}

# Fix to give spinbox nicer appearance
proc MySpinBox {W args} {
    # Handle if ttk::spinbox is not there since it was introduced later
    if {[info commands ttk::spinbox] eq ""} {
        set cmd [list tk::spinbox $W]
    } else {
        set cmd [list ttk::spinbox $W]
        lappend cmd -command [list $W selection clear] -state readonly
    }
    lappend cmd {*}$args
    {*}$cmd
}

proc PrintTracePrefs {W args} {
    set ::Pref(printColorChange) \
            [list $::TmpPref(chr) $::TmpPref(chg) $::TmpPref(chb)]
    set ::Pref(printColorNew1) \
            [list $::TmpPref(n1r) $::TmpPref(n1g) $::TmpPref(n1b)]
    set ::Pref(printColorNew2) \
            [list $::TmpPref(n2r) $::TmpPref(n2g) $::TmpPref(n2b)]

    if  { ! [winfo exists $W.cf.l1e]} return
    foreach num {1 2 3} p {ch n1 n2} {
        set r [expr {int(255*$::TmpPref(${p}r))}]
        set g [expr {int(255*$::TmpPref(${p}g))}]
        set b [expr {int(255*$::TmpPref(${p}b))}]
        set col [format \#%02X%02X%02X $r $g $b]
        $W.cf.l${num}e configure -background $col
    }
}

# Create a print dialog for PDF.
proc doPrint {top {quiet 0}} {
    if {$quiet} {
        PrintDiffs $top 1
        return
    }

    set W $top.pr
    destroy $W
    ttk::toplevel $W -padx 3 -pady 3
    wm title $W "Print diffs to PDF"

    # Layout settings
    ttk::labelframe $W.lfs -text "Settings" -padding 3

    ttk::label $W.lfs.hsl -anchor w -text "Header Size"
    addBalloon $W.lfs.hsl "Font size for page header"
    MySpinBox $W.lfs.hss -textvariable ::Pref(printHeaderSize) \
            -from 5 -to 16 -width 3 -format %.0f

    ttk::label $W.lfs.cll -anchor w -text "Chars per line"
    addBalloon $W.lfs.cll "Font size is scaled to fit this"
    ttk::entryX $W.lfs.cle -textvariable ::Pref(printCharsPerLine) -width 4
    ttk::frame $W.lfs.clf






    set values [CharsPerLineOptions $top]


    foreach {value label balloon} $values {
        ttk::radiobutton $W.lfs.clf.$value -variable ::Pref(printCharsPerLine) \
            -value $value -text $label
        addBalloon $W.lfs.clf.$value $balloon
        pack $W.lfs.clf.$value -side left -padx 3 -pady 3
    }

    # Select paper size
    set paperlist [lsort -dictionary [pdf4tcl::getPaperSizeList]]
    ttk::label $W.lfs.psl -anchor w -text "Paper Size"
    ttk::combobox $W.lfs.psc -values $paperlist -textvariable ::Pref(printPaper) \
            -width 6 -state readonly

    grid $W.lfs.hsl $W.lfs.hss               -sticky we -padx 3 -pady 3
    grid $W.lfs.psl $W.lfs.psc               -sticky we -padx 3 -pady 3
    grid $W.lfs.cll $W.lfs.cle $W.lfs.clf -  -sticky we -padx 3 -pady 3
    grid columnconfigure $W.lfs 1 -weight 1

    # Color
    foreach {::TmpPref(chr) ::TmpPref(chg) ::TmpPref(chb)} \
            $::Pref(printColorChange) break
    foreach {::TmpPref(n1r) ::TmpPref(n1g) ::TmpPref(n1b)} \
            $::Pref(printColorNew1) break
    foreach {::TmpPref(n2r) ::TmpPref(n2g) ::TmpPref(n2b)} \
            $::Pref(printColorNew2) break

    ttk::labelframe $W.cf -text "Background Color" -padding 3




    ttk::label $W.cf.hr -text "Red"
    ttk::label $W.cf.hg -text "Green"
    ttk::label $W.cf.hb -text "Blue"

    ttk::label $W.cf.l1 -text "Change"
    MySpinBox $W.cf.s1r -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 5 -textvariable ::TmpPref(chr)
    MySpinBox $W.cf.s1g -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 5 -textvariable ::TmpPref(chg)
    MySpinBox $W.cf.s1b -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 5 -textvariable ::TmpPref(chb)
    ttk::label $W.cf.l1e -text "Example"
    addBalloon $W.cf.l1e "Screen approximation of print color"

    ttk::label $W.cf.l2 -text "Old"
    MySpinBox $W.cf.s2r -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 5 -textvariable ::TmpPref(n1r)
    MySpinBox $W.cf.s2g -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 5 -textvariable ::TmpPref(n1g)
    MySpinBox $W.cf.s2b -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 5 -textvariable ::TmpPref(n1b)
    ttk::label $W.cf.l2e -text "Example"

    ttk::label $W.cf.l3 -text "New"
    MySpinBox $W.cf.s3r -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 5 -textvariable ::TmpPref(n2r)
    MySpinBox $W.cf.s3g -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 5 -textvariable ::TmpPref(n2g)
    MySpinBox $W.cf.s3b -from 0.0 -to 1.0 -increment 0.1 -format %.1f \
            -width 5 -textvariable ::TmpPref(n2b)
    ttk::label $W.cf.l3e -text "Example"

    grid x        $W.cf.hr  $W.cf.hg  $W.cf.hb  -pady 1
    grid $W.cf.l1 $W.cf.s1r $W.cf.s1g $W.cf.s1b $W.cf.l1e -sticky w -padx 3 -pady 3
    grid $W.cf.l2 $W.cf.s2r $W.cf.s2g $W.cf.s2b $W.cf.l2e -sticky w -padx 3 -pady 3
    grid $W.cf.l3 $W.cf.s3r $W.cf.s3g $W.cf.s3b $W.cf.l3e -sticky w -padx 3 -pady 3

    trace add variable ::TmpPref write [list PrintTracePrefs $W]
    PrintTracePrefs $W

    # File
    ttk::labelframe $W.lff -text "Output File" -padding 3

    ttk::entryX $W.lff.fne -textvariable ::eskil($top,printFile) -width 30
    ttk::button $W.lff.fnb -text "Browse" \
            -command [list BrowsePrintFileName $top $W.lff.fne]
    grid $W.lff.fne $W.lff.fnb -sticky we -padx 3 -pady 3
    grid columnconfigure $W.lff 0 -weight 1

    if {$::eskil($top,printFile) eq ""} {
        set ::eskil($top,printFile) "~/eskil.pdf"
    }

    ttk::frame $W.fb
    ttk::button $W.b1 -text "Print to File" \
            -command "destroy $W; update; PrintDiffs $top"
    ttk::button $W.b2 -text "Cancel" -command "destroy $W"
    pack $W.b1 -in $W.fb -side left  -padx {0 3} -pady 3 -ipadx 5
    pack $W.b2 -in $W.fb -side right -padx {3 0} -pady 3 -ipadx 5

    # Top Layout
    grid $W.lfs   -sticky we -padx 3 -pady 3
    grid $W.cf    -sticky we -padx 3 -pady 3
    grid $W.lff   -sticky we -padx 3 -pady 3

    grid $W.fb    -sticky swe -padx 3 -pady 3


    grid columnconfigure $W 0 -weight 1
    grid rowconfigure $W $W.fb -weight 1
}

Changes to src/printobj.tcl.

30
31
32
33
34
35
36

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82


83
84
85
86
87
88
89
    delegate method * to pdf

    delegate option -margin to pdf
    delegate option -paper  to pdf

    option -cpl        -default 80
    option -cpln       -default 5

    option -headsize   -default 8
    option -headleft   -default "Header Text Left"
    option -headright  -default "Header Text Right"
    option -headnpages -default 10
    option -file       -default exp.pdf

    variable width
    variable height
    variable hoy
    variable fontsize
    variable linesize
    variable nlines
    variable ox1
    variable ox2
    variable oy
    variable page

    constructor {args} {
        set tmp(-file) $options(-file)
        catch {array set tmp $args}
        install pdf using pdf4tcl::pdf4tcl %AUTO% \
                -landscape 1 -paper a4 -margin 15mm -file $tmp(-file)
        $self configurelist $args
        $self StartPrint
    }
    destructor {
        catch {$pdf destroy}
    }

    method StartPrint {} {
        # Page size
        lassign [$pdf getDrawableArea] width height

        # Header metrics
        $pdf setFont $options(-headsize) Courier
        set headoffset [expr {$options(-headsize) + [$pdf getFontMetric bboxy]}]
        set hoy $headoffset

        # Figure out font size from number of chars per line
        set charwidthHead [$pdf getCharWidth "0"]
        set charwidth [expr {$width / 2.0 / ($options(-cpl) + $options(-cpln) + 1)}]
        set fontsize [expr {$options(-headsize) * $charwidth / $charwidthHead}]
        $pdf setFont $fontsize

        # Text metrics
        set linesize  $fontsize


        set offset    [expr {$fontsize + [$pdf getFontMetric bboxy]}]
        set charwidth [$pdf getCharWidth "0"]
        set nlinesf [expr {($height - $options(-headsize)) / $linesize}]
        # Number of lines per page
        set nlines  [expr {int($nlinesf - 1.0)}]
        #set nlines 66
        # Offsets to starting points in both subpages.







>




















|













|










|
>
>







30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
    delegate method * to pdf

    delegate option -margin to pdf
    delegate option -paper  to pdf

    option -cpl        -default 80
    option -cpln       -default 5
    option -lnsp       -default 1.0
    option -headsize   -default 8
    option -headleft   -default "Header Text Left"
    option -headright  -default "Header Text Right"
    option -headnpages -default 10
    option -file       -default exp.pdf

    variable width
    variable height
    variable hoy
    variable fontsize
    variable linesize
    variable nlines
    variable ox1
    variable ox2
    variable oy
    variable page

    constructor {args} {
        set tmp(-file) $options(-file)
        catch {array set tmp $args}
        install pdf using pdf4tcl::new %AUTO% -compress 1 \
                -landscape 1 -paper a4 -margin 15mm -file $tmp(-file)
        $self configurelist $args
        $self StartPrint
    }
    destructor {
        catch {$pdf destroy}
    }

    method StartPrint {} {
        # Page size
        lassign [$pdf getDrawableArea] width height

        # Header metrics
        $pdf setFont $options(-headsize) $::eskil(printFont)
        set headoffset [expr {$options(-headsize) + [$pdf getFontMetric bboxy]}]
        set hoy $headoffset

        # Figure out font size from number of chars per line
        set charwidthHead [$pdf getCharWidth "0"]
        set charwidth [expr {$width / 2.0 / ($options(-cpl) + $options(-cpln) + 1)}]
        set fontsize [expr {$options(-headsize) * $charwidth / $charwidthHead}]
        $pdf setFont $fontsize

        # Text metrics
        set linesize  [expr {[$pdf getFontMetric "height"] * $options(-lnsp)}]
        set spacing [expr {$linesize / $fontsize}]
        $pdf setLineSpacing $spacing
        set offset    [expr {$fontsize + [$pdf getFontMetric bboxy]}]
        set charwidth [$pdf getCharWidth "0"]
        set nlinesf [expr {($height - $options(-headsize)) / $linesize}]
        # Number of lines per page
        set nlines  [expr {int($nlinesf - 1.0)}]
        #set nlines 66
        # Offsets to starting points in both subpages.
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
        $pdf rectangle 0 $options(-headsize) \
                $width [- $height $options(-headsize)]
        # Center line
        $pdf line [/ $width 2.0] $options(-headsize) \
                [/ $width 2.0] $height

        # Header
        $pdf setFont $options(-headsize) Courier
        $pdf text $options(-headleft) -x 0 -y $hoy 
        $pdf text "Page $page of $options(-headnpages)" \
                -x [expr {$width / 2.0}] -y $hoy -align center
        $pdf text $options(-headright) -x $width -y $hoy -align right

        # Normal font
        $pdf setFont $fontsize Courier
    }

    method setHalf {half} {
        if {$half eq "left"} {
            $pdf setTextPosition $ox1 $oy
        } else {
            $pdf setTextPosition $ox2 $oy







|






|







115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
        $pdf rectangle 0 $options(-headsize) \
                $width [- $height $options(-headsize)]
        # Center line
        $pdf line [/ $width 2.0] $options(-headsize) \
                [/ $width 2.0] $height

        # Header
        $pdf setFont $options(-headsize) $::eskil(printFont)
        $pdf text $options(-headleft) -x 0 -y $hoy 
        $pdf text "Page $page of $options(-headnpages)" \
                -x [expr {$width / 2.0}] -y $hoy -align center
        $pdf text $options(-headright) -x $width -y $hoy -align right

        # Normal font
        $pdf setFont $fontsize $::eskil(printFont)
    }

    method setHalf {half} {
        if {$half eq "left"} {
            $pdf setTextPosition $ox1 $oy
        } else {
            $pdf setTextPosition $ox2 $oy

Changes to src/registry.tcl.

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------

proc MakeRegistryFrame {w label key newvalue} {
    set old {}
    catch {set old [registry get $key {}]}

    set l [ttk::labelframe $w -text $label -padding 4]

    ttk::label $l.key1 -text "Key:"
    ttk::label $l.key2 -text $key
    ttk::label $l.old1 -text "Old value:"
    ttk::label $l.old2 -text $old
    ttk::label $l.new1 -text "New value:"
    ttk::label $l.new2 -text $newvalue







|



|







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------

proc MakeRegistryFrame {W label key newvalue} {
    set old {}
    catch {set old [registry get $key {}]}

    set l [ttk::labelframe $W -text $label -padding 4]

    ttk::label $l.key1 -text "Key:"
    ttk::label $l.key2 -text $key
    ttk::label $l.old1 -text "Old value:"
    ttk::label $l.old2 -text $old
    ttk::label $l.new1 -text "New value:"
    ttk::label $l.new2 -text $newvalue
116
117
118
119
120
121
122
123
124
125




126
127
128
129
130
131
132
    MakeRegistryFrame $top.c "Diff Conflict" $keyc $new

    set new "$valbase \"%1\""
    MakeRegistryFrame $top.dd "Directory Diff" $keydd $new
    pack $top.d $top.c $top.dd -side "top" -fill x -padx 4 -pady 4

    locateEditor ::util(editor)
    if {[string match "*runemacs.exe" $::util(editor)]} {
        # Set up emacs
        set newkey "\"[file nativename $::util(editor)]\" \"%1\""




        MakeRegistryFrame $top.e "Emacs" $keye $newkey
        pack $top.e -side "top" -fill x -padx 4 -pady 4
    }

    ttk::button $top.close -text "Close" -width 10 \
            -command [list destroy $top] -default active
    pack $top.close -side bottom -pady 4







|

|
>
>
>
>







116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
    MakeRegistryFrame $top.c "Diff Conflict" $keyc $new

    set new "$valbase \"%1\""
    MakeRegistryFrame $top.dd "Directory Diff" $keydd $new
    pack $top.d $top.c $top.dd -side "top" -fill x -padx 4 -pady 4

    locateEditor ::util(editor)
    if {[string match "*runemacs.exe" [lindex $::util(editor) 0]]} {
        # Set up emacs
        set newkey "\"[file nativename [lindex $::util(editor) 0]]\""
        foreach eArg [lrange $::util(editor) 1 end] {
            append newkey " \"$eArg\""
        }
        append newkey " \"%1\""
        MakeRegistryFrame $top.e "Emacs" $keye $newkey
        pack $top.e -side "top" -fill x -padx 4 -pady 4
    }

    ttk::button $top.close -text "Close" -width 10 \
            -command [list destroy $top] -default active
    pack $top.close -side bottom -pady 4

Changes to src/rev.tcl.

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55










56
57
58
59
60
61
62






63
64
65
66
67





68
69
70
71
72
73
74
75
76
77
78
79
80


81
82
83
84
85
86
87
88
89
90
91
92

93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# If file is empty, check directory for control.
#
# Returns true if controlled or false if not.

# eskil::rev::XXX::ParseRevs {filename revs}
#
# Figure out revision from a list given by user
# 
# Returns a list of revisions to display.
#
# Filename may be empty, the rev corresponds to the working tree

# eskil::rev::XXX::get {filename outfile rev}
#
# Get a revision of a file and place it in outfile.
# rev is in any format understood by this system, and
# should be retrieved from ParseRevs

# eskil::rev::XXX::getPatch {revs {files {}}}
#
# Get a patch of the file tree, between the revisions given.
# revs is in any format understood by this system, and
# should be retrieved from ParseRevs
# An optional list of files that should be included can be given.











# eskil::rev::XXX::commitFile {top args}
#
# If implemented, enables the commit feature when comparing edited
# file(s) agains latest check in.
# If no files are given, all edited files are committed.







# eskil::rev::XXX::viewLog {top filename revs}
#
# If implemented, enables the log feature when comparing revisions.
# View log between displayed versions






namespace eval eskil::rev::CVS {}
namespace eval eskil::rev::RCS {}
namespace eval eskil::rev::CT {}
namespace eval eskil::rev::GIT {}
namespace eval eskil::rev::FOSSIL {}
namespace eval eskil::rev::SVN {}
namespace eval eskil::rev::HG {}
namespace eval eskil::rev::BZR {}
namespace eval eskil::rev::P4 {}

proc eskil::rev::CVS::detect {file} {
    if {$file eq ""} {
        set dir [pwd]


    } else {
        set dir [file dirname $file]
    }
    if {[file isdirectory [file join $dir CVS]]} {
        if {[auto_execok cvs] ne ""} {
            return 1
        }
    }
    return 0
}

proc eskil::rev::SVN::detect {file} {

    if {$file eq ""} {
        set dir [pwd]
    } else {
        set dir [file dirname $file]
    }
    if {[file isdirectory [file join $dir .svn]]} {
        if {[auto_execok svn] ne ""} {
            return 1
        }
    }
    return 0
}

proc eskil::rev::HG::detect {file} {
    if {$file eq ""} {
        set dir [pwd]
    } else {
        set dir [file dirname $file]
    }
    # HG, detect two steps down. Could be improved. FIXA
    if {[file isdirectory [file join $dir .hg]] ||
        [file isdirectory [file join $dir .. .hg]] ||
        [file isdirectory [file join $dir .. .. .hg]]} {
        if {[auto_execok hg] ne ""} {
            return 1
        }
    }
    return 0
}

proc eskil::rev::BZR::detect {file} {
    if {$file eq ""} {
        set dir [pwd]
    } else {
        set dir [file dirname $file]
    }
    # HG, detect two steps down. Could be improved. FIXA
    if {[file isdirectory [file join $dir .bzr]] ||
        [file isdirectory [file join $dir .. .bzr]] ||
        [file isdirectory [file join $dir .. .. .bzr]]} {
        if {[auto_execok bzr] ne ""} {
            return 1
        }
    }
    return 0
}








|










|




|
>
>
>
>
>
>
>
>
>
>







>
>
>
>
>
>





>
>
>
>
>













>
>












>
|
<
<
<
<
<








|
<
<
<
<
<
<
<
<








|
<
<
<
<
<
<
<
<







32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117





118
119
120
121
122
123
124
125
126








127
128
129
130
131
132
133
134
135








136
137
138
139
140
141
142
# If file is empty, check directory for control.
#
# Returns true if controlled or false if not.

# eskil::rev::XXX::ParseRevs {filename revs}
#
# Figure out revision from a list given by user
#
# Returns a list of revisions to display.
#
# Filename may be empty, the rev corresponds to the working tree

# eskil::rev::XXX::get {filename outfile rev}
#
# Get a revision of a file and place it in outfile.
# rev is in any format understood by this system, and
# should be retrieved from ParseRevs

# eskil::rev::XXX::getPatch {revs files {fileListName {}}}
#
# Get a patch of the file tree, between the revisions given.
# revs is in any format understood by this system, and
# should be retrieved from ParseRevs
# If files is non-empty it is a list of files that should be included.
# If fileListName is given, it is a variable name where to place the
# list of files affected by the patch. The list should be cleaned
# through lsort -dictionary -unique.
# NOTE that current directory must be correct before calling.

# eskil::rev::XXX::getChangedFiles {dir revs}
#
# Get a list of files changed between the revisions given.
# revs is in any format understood by this system, and
# should be retrieved from ParseRevs

# eskil::rev::XXX::commitFile {top args}
#
# If implemented, enables the commit feature when comparing edited
# file(s) agains latest check in.
# If no files are given, all edited files are committed.

# eskil::rev::XXX::revertFile {top args}
#
# If implemented, enables the revert feature when comparing edited
# file(s) agains latest check in.
# If no files are given, all edited files are reverted.

# eskil::rev::XXX::viewLog {top filename revs}
#
# If implemented, enables the log feature when comparing revisions.
# View log between displayed versions

# eskil::rev::XXX::mount {dir rev}
#
# If implemented, directory diff can view revisions for this system.
# Mounts a directory revision as a VFS, and returns the mount point

namespace eval eskil::rev::CVS {}
namespace eval eskil::rev::RCS {}
namespace eval eskil::rev::CT {}
namespace eval eskil::rev::GIT {}
namespace eval eskil::rev::FOSSIL {}
namespace eval eskil::rev::SVN {}
namespace eval eskil::rev::HG {}
namespace eval eskil::rev::BZR {}
namespace eval eskil::rev::P4 {}

proc eskil::rev::CVS::detect {file} {
    if {$file eq ""} {
        set dir [pwd]
    } elseif {[file isdirectory $file]} {
        set dir $file
    } else {
        set dir [file dirname $file]
    }
    if {[file isdirectory [file join $dir CVS]]} {
        if {[auto_execok cvs] ne ""} {
            return 1
        }
    }
    return 0
}

proc eskil::rev::SVN::detect {file} {
    # From SVN 1.7, there is only a .svn at the top of the checkout
    if {[SearchUpwardsFromFile $file .svn]} {





        if {[auto_execok svn] ne ""} {
            return 1
        }
    }
    return 0
}

proc eskil::rev::HG::detect {file} {
    if {[SearchUpwardsFromFile $file .hg]} {








        if {[auto_execok hg] ne ""} {
            return 1
        }
    }
    return 0
}

proc eskil::rev::BZR::detect {file} {
    if {[SearchUpwardsFromFile $file .bzr]} {








        if {[auto_execok bzr] ne ""} {
            return 1
        }
    }
    return 0
}

152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209



























































210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258





259
260
261
262
263
264
265
266
267
268
269
270
271




272
273
274
275


276
277
278
279
280
281
282
283
284
285

































































286
287
288
289
290








291
292
293
294
295
296
297
298













299
300




















301
302
303
304
305
306
307
        set dir [pwd]
    } else {
        set dir [file dirname $file]
    }
    if {[auto_execok cleartool] != ""} {
        set old [pwd]
        cd $dir
        if {![catch {exec cleartool pwv -s} view] && $view != "** NONE **"} {
            cd $old
            return 1
        }
        cd $old
    }
    return 0
}

proc eskil::rev::GIT::detect {file} {
    if {$file eq ""} {
        set dir [pwd]
    } else {
        set dir [file dirname $file]
    }
    # Git, detect two steps down. Could be improved. FIXA
    if {[file isdirectory [file join $dir .git]] ||
        [file isdirectory [file join $dir .. .git]] ||
        [file isdirectory [file join $dir .. .. .git]]} {
        if {[auto_execok git] ne ""} {
            return 1
        }
    }
    return 0
}

proc eskil::rev::FOSSIL::detect {file} {
    if {$file eq ""} {
        set dir [pwd]
    } else {
        set dir [file dirname $file]
    }
    # Fossil, detect three steps down. Could be improved. FIXA
    if {[file exists [file join $dir _FOSSIL_]] ||
        [file exists [file join $dir .. _FOSSIL_]] ||
        [file exists [file join $dir .. .. _FOSSIL_]] ||
        [file exists [file join $dir .. .. .. _FOSSIL_]]} {
        if {[auto_execok fossil] ne ""} {
            return 1
        }
    }
    return 0
}

proc eskil::rev::P4::detect {file} {
    if {[auto_execok icmp4] != ""} {
        if {[catch {exec csh -c "icmp4 have $file"} p4have]} { return 0 }
	if {[lindex $p4have 1] eq "-"} { return 1 }
    }
    return 0
}




























































# Get a CVS revision
proc eskil::rev::CVS::get {filename outfile rev} {
    set old ""
    set dir [file dirname $filename]
    if {$dir != "."} {
        set old [pwd]
        set outfile [file join [pwd] $outfile]
        cd $dir
        set filename [file tail $filename]
    }

    set cmd [list exec cvs -z3 update -p]
    if {$rev != ""} {
        lappend cmd -r $rev
    }
    lappend cmd [file nativename $filename] > $outfile
    if {[catch {eval $cmd} res]} {
        if {![string match "*Checking out*" $res]} {
            tk_messageBox -icon error -title "CVS error" -message $res
        }
    }

    if {$old != ""} {
        cd $old
    }
}

# Get a CVS patch
proc eskil::rev::CVS::getPatch {revs {files {}}} {
    if {$::Pref(context) > 0} {
        set context $::Pref(context)
    } else {
        set context 5
    }
    # TODO: support files
    set cmd [list exec cvs diff -U $context]
    foreach rev $revs {
        lappend cmd -r $rev
    }

    if {[catch {eval $cmd} res]} {
        if {![string match "*=========*" $res]} {
            tk_messageBox -icon error -title "CVS error" -message $res
            return ""
        }
    }
    return $res
}






# Get a SVN revision
proc eskil::rev::SVN::get {filename outfile rev} {
    set old ""
    set dir [file dirname $filename]
    if {$dir != "."} {
        set old [pwd]
        set outfile [file join [pwd] $outfile]
        cd $dir
        set filename [file tail $filename]
    }

    set cmd [list exec svn cat]




    if {$rev != ""} {
        lappend cmd -r $rev
    }
    lappend cmd [file nativename $filename] > $outfile


    if {[catch {eval $cmd} res]} {
        if {![string match "*Checking out*" $res]} {
            tk_messageBox -icon error -title "SVN error" -message $res
        }
    }

    if {$old != ""} {
        cd $old
    }
}


































































# Get a SVN patch
proc eskil::rev::SVN::getPatch {revs {files {}}} {
    set cmd [list exec svn diff]
    foreach rev $revs {








        lappend cmd -r $rev
    }
    lappend cmd {*}$files

    if {[catch {eval $cmd} res]} {
        tk_messageBox -icon error -title "SVN error" -message $res
        return ""
    }













    return $res
}





















# Get a HG revision
proc eskil::rev::HG::get {filename outfile rev} {
    set old ""
    set dir [file dirname $filename]
    if {$dir != "."} {
        set old [pwd]







|









|
<
<
<
<
<
<
<
<








|
<
<
<
<
<
<
<
<
<














>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


















|










|












|






>
>
>
>
>













>
>
>
>
|
|
|
|
>
>

|








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


|

|
>
>
>
>
>
>
>
>
|







>
>
>
>
>
>
>
>
>
>
>
>
>


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172








173
174
175
176
177
178
179
180
181









182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
        set dir [pwd]
    } else {
        set dir [file dirname $file]
    }
    if {[auto_execok cleartool] != ""} {
        set old [pwd]
        cd $dir
        if { ! [catch {exec cleartool pwv -s} view] && $view != "** NONE **"} {
            cd $old
            return 1
        }
        cd $old
    }
    return 0
}

proc eskil::rev::GIT::detect {file} {
    if {[SearchUpwardsFromFile $file .git]} {








        if {[auto_execok git] ne ""} {
            return 1
        }
    }
    return 0
}

proc eskil::rev::FOSSIL::detect {file} {
    if {[SearchUpwardsFromFile $file _FOSSIL_ .fslckout .fos]} {









        if {[auto_execok fossil] ne ""} {
            return 1
        }
    }
    return 0
}

proc eskil::rev::P4::detect {file} {
    if {[auto_execok icmp4] != ""} {
        if {[catch {exec csh -c "icmp4 have $file"} p4have]} { return 0 }
	if {[lindex $p4have 1] eq "-"} { return 1 }
    }
    return 0
}

# Find the repo top dir given a file/dir reference.
# Return the tail relative to top dir.
# cands is a list of candidates for top marker
proc GetTopDirCand {ref cands dirName tailName} {
    upvar 1 $dirName dir $tailName tail
    if {[file isdirectory $ref]} {
        set dir $ref
        set tail ""
    } else {
        set dir [file dirname $ref]
        set tail [file tail $ref]
    }
    # Locate the top directory
    while {[file readable $dir] && [file isdirectory $dir]} {
        set found 0
        foreach candidate $cands {
            if {[file exists [file join $dir $candidate]]} {
                set found 1
                break
            }
        }
        if {$found} break

        set parent [file dirname $dir]
        # Make sure to stop if we reach a dead end
        if {$parent eq $dir} break
        set tail [file join [file tail $dir] $tail]
        set dir $parent
    }
}

# Find the repo top dir given a file/dir reference.
# Return the tail relative to top dir.
proc eskil::rev::SVN::GetTopDir {ref dirName tailName} {
    upvar 1 $dirName dir $tailName tail
    GetTopDirCand $ref .svn dir tail
}

# Find the repo top dir given a file/dir reference.
# Return the tail relative to top dir.
proc eskil::rev::GIT::GetTopDir {ref dirName tailName} {
    upvar 1 $dirName dir $tailName tail
    GetTopDirCand $ref .git dir tail
}

# Find the repo top dir given a file/dir reference.
# Return the tail relative to top dir.
proc eskil::rev::HG::GetTopDir {ref dirName tailName} {
    upvar 1 $dirName dir $tailName tail
    GetTopDirCand $ref .hg dir tail
}

# Find the repo top dir given a file/dir reference.
# Return the tail relative to top dir.
proc eskil::rev::FOSSIL::GetTopDir {ref dirName tailName} {
    upvar 1 $dirName dir $tailName tail
    GetTopDirCand $ref ".fos .fslckout _FOSSIL_" dir tail
}

# Get a CVS revision
proc eskil::rev::CVS::get {filename outfile rev} {
    set old ""
    set dir [file dirname $filename]
    if {$dir != "."} {
        set old [pwd]
        set outfile [file join [pwd] $outfile]
        cd $dir
        set filename [file tail $filename]
    }

    set cmd [list exec cvs -z3 update -p]
    if {$rev != ""} {
        lappend cmd -r $rev
    }
    lappend cmd [file nativename $filename] > $outfile
    if {[catch {eval $cmd} res]} {
        if { ! [string match "*Checking out*" $res]} {
            tk_messageBox -icon error -title "CVS error" -message $res
        }
    }

    if {$old != ""} {
        cd $old
    }
}

# Get a CVS patch
proc eskil::rev::CVS::getPatch {revs files {fileListName {}}} {
    if {$::Pref(context) > 0} {
        set context $::Pref(context)
    } else {
        set context 5
    }
    # TODO: support files
    set cmd [list exec cvs diff -U $context]
    foreach rev $revs {
        lappend cmd -r $rev
    }

    if {[catch {eval $cmd} res]} {
        if { ! [string match "*=========*" $res]} {
            tk_messageBox -icon error -title "CVS error" -message $res
            return ""
        }
    }
    return $res
}

proc eskil::rev::CVS::getChangedFiles {dir revs} {
    # Not supported yet
    return ""
}

# Get a SVN revision
proc eskil::rev::SVN::get {filename outfile rev} {
    set old ""
    set dir [file dirname $filename]
    if {$dir != "."} {
        set old [pwd]
        set outfile [file join [pwd] $outfile]
        cd $dir
        set filename [file tail $filename]
    }

    set cmd [list exec svn cat]
    if {[string match "*://*" $rev]} {
        # Full URL
        lappend cmd $rev
    } else {
        if {$rev != ""} {
            lappend cmd -r $rev
        }
        lappend cmd [file nativename $filename]
    }
    lappend cmd > $outfile
    if {[catch {eval $cmd} res]} {
        if { ! [string match "*Checking out*" $res]} {
            tk_messageBox -icon error -title "SVN error" -message $res
        }
    }

    if {$old != ""} {
        cd $old
    }
}

# List local changes in a checkout
# This is used to optimise dirdiff in the case of current vs local.
# For SVN a lot of server calls can thus be avoided.
proc eskil::rev::SVN::localChanges {dir} {
    set old [pwd]
    cd $dir
    set info [exec svn status --ignore-externals -q]
    cd $old
    set changes {}
    foreach line [split $info \n] {
        set line [string trim $line]
        if {[regexp {\S+$} $line file]} {
            lappend changes [file join $dir $file]
        }
    }
    return $changes
}
proc eskil::rev::FOSSIL::localChanges {dir} {
    set old [pwd]
    cd $dir
    set info [exec fossil changes]
    cd $old
    set changes {}
    foreach line [split $info \n] {
        set line [string trim $line]
        if {[regexp {^\S+\s+(\S+)$} $line -> file]} {
            lappend changes [file join $dir $file]
        }
    }
    return $changes
}
proc eskil::rev::GIT::localChanges {dir} {
    set old [pwd]
    cd $dir
    set info [exec git status -s --porcelain]
    cd $old
    set changes {}
    foreach line [split $info \n] {
        set line [string trim $line]
        if {[regexp {^(\S+)\s+(\S+)$} $line -> pre file]} {
            lappend changes [file join $dir $file]
        }
    }
    return $changes
}

# Common helper for SVN revisions
proc eskil::rev::SVN::RevsToCmd {revs} {
    set cmd {}
    set revs2 {}
    foreach rev $revs {
        # TODO: What happens in strange combinations ?
        if {[string match "*://*" $rev]} {
            # Full URL
            lappend cmd $rev
        } else {
            lappend revs2 $rev
        }
    }
    if {[llength $revs2] > 0} {
        lappend cmd -r [join $revs2 :]
    }
    return $cmd
}

# Get a SVN patch
proc eskil::rev::SVN::getPatch {revs files {fileListName {}}} {
    set cmd [list exec svn diff]
    lappend cmd {*}[RevsToCmd $revs]
    set ext {}
    if {$::Pref(context) >= 0} {
        lappend ext --context $::Pref(context)
    }
    if {$::Pref(ignore) in "-w -b"} {
        lappend ext $::Pref(ignore)
    }
    if {[llength $ext] > 0} {
        lappend cmd -x $ext
    }
    lappend cmd {*}$files

    if {[catch {eval $cmd} res]} {
        tk_messageBox -icon error -title "SVN error" -message $res
        return ""
    }
    if {$fileListName ne ""} {
        upvar 1 $fileListName fileList
        set fileList {}
        # SVN will have lines like this to show files:
        #Index: dir1/f11
        foreach line [lsearch -all -inline -regexp [split $res \n] {^Index: }] {
            if {[regexp {Index: (.*)} $line -> fn]} {
                lappend fileList $fn
            }
        }
        set fileList [lsort -dictionary -unique $fileList]
    }

    return $res
}

proc eskil::rev::SVN::getChangedFiles {dir revs} {
    # Must call SVN in top dir to get full changeset
    GetTopDir $dir top tail
    set cmd [list execDir $top svn diff --summarize]
    lappend cmd {*}[RevsToCmd $revs]

    if {[catch {eval $cmd} res]} {
        tk_messageBox -icon error -title "SVN error" -message $res
        return ""
    }
    # Result is one file per line, with an info word before
    set files {}
    foreach line [split $res \n] {
        if {[regexp {^\S+\s+(.*)} $line -> f]} {
            lappend files [file join $top $f]
        }
    }
    return $files
}

# Get a HG revision
proc eskil::rev::HG::get {filename outfile rev} {
    set old ""
    set dir [file dirname $filename]
    if {$dir != "."} {
        set old [pwd]
323
324
325
326
327
328
329
330





331






















332
333
334
335
336
337
338
339
340









341
342
343
344
345
346
347
348

    if {$old != ""} {
        cd $old
    }
}

# Get a HG patch
proc eskil::rev::HG::getPatch {revs {files {}}} {





    # TODO: support files






















    set cmd [list exec hg diff]
    foreach rev $revs {
        lappend cmd -r $rev
    }

    if {[catch {eval $cmd} res]} {
        tk_messageBox -icon error -title "HG error" -message $res
        return ""
    }









    return $res
}

# Get a BZR revision
proc eskil::rev::BZR::get {filename outfile rev} {
    set old ""
    set dir [file dirname $filename]
    if {$dir != "."} {







|
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|








>
>
>
>
>
>
>
>
>
|







485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546

    if {$old != ""} {
        cd $old
    }
}

# Get a HG patch
proc eskil::rev::HG::getPatch {revs files {fileListName {}}} {
    set cmd [list exec hg diff]
    foreach rev $revs {
        lappend cmd -r $rev
    }
    lappend cmd "--" {*}$files

    if {[catch {eval $cmd} res]} {
        tk_messageBox -icon error -title "HG error" -message $res
        return ""
    }
    if {$fileListName ne ""} {
        upvar 1 $fileListName fileList
        set fileList {}
        # HG will have lines like this to show files:
        #diff -r 533b1d848a1c dir1/f12
        #diff -r 0dba7b280f8f -r 2e84355cc419 f1
        foreach line [lsearch -all -inline -regexp [split $res \n] {^diff -}] {
            if {[regexp {diff (?:-r \w+\s+)*(.*)$} $line -> fn]} {
                lappend fileList $fn
            }
        }
        set fileList [lsort -dictionary -unique $fileList]
    }

    return $res
}

proc eskil::rev::HG::getChangedFiles {dir revs} {
    set cmd [list execDir $dir hg diff --stat]
    foreach rev $revs {
        lappend cmd -r $rev
    }

    if {[catch {eval $cmd} res]} {
        tk_messageBox -icon error -title "HG error" -message $res
        return ""
    }
    # Result is one file per line, with an info word before
    GetTopDir $dir top tail
    set files {}
    foreach line [split $res \n] {
        if {[regexp {(.+)\|} $line -> f]} {
            set f [string trim $f]
            lappend files [file join $top $f]
        }
    }
    return $files
}

# Get a BZR revision
proc eskil::rev::BZR::get {filename outfile rev} {
    set old ""
    set dir [file dirname $filename]
    if {$dir != "."} {
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388





389
390
391
392
393
394
395
396
397





398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441






442
443
444
445
446
447
448
449
450
451
452
453
454
455













456
457
458
459
460
461




462




463
464

465

466
467
468

469
470


471




472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494


495




496
497
498
499







500
501





502











503
504
505
506






507
508
509
510
511
512
513
514
515
516
517
518
519
520
521





522
523
524
525
526
527
528

    if {$old != ""} {
        cd $old
    }
}

# Get a BZR patch
proc eskil::rev::BZR::getPatch {revs {files {}}} {
    # TODO: support files
    set cmd [list exec bzr diff]
    if {[llength $revs] == 2} {
        lappend cmd -r [lindex $revs 0]..[lindex $revs 1]
    } elseif {[llength $revs] == 1} {
        lappend cmd -r [lindex $revs 0]
    }

    if {[catch {eval $cmd} res]} {
        if {![string match "*===*" $res]} {
            tk_messageBox -icon error -title "BZR error" -message $res
            return ""
        }
    }
    return $res
}






# Get an RCS revision
proc eskil::rev::RCS::get {filename outfile {rev {}}} {
    catch {exec co -p$rev [file nativename $filename] \
            > $outfile}
}

# Get a RCS patch
proc eskil::rev::RCS::getPatch {revs {files {}}} {





    # Not supported yet.
    return ""
}

# Get a GIT revision
# No support for revisions yet
proc eskil::rev::GIT::get {filename outfile rev} {
    set old [pwd]
    set dir [file dirname $filename]
    set tail [file tail $filename]
    # Locate the top directory
    while {![file isdirectory $dir/.git]} {
        set thisdir [file tail $dir]
        set dir [file dirname $dir]
        set tail [file join $thisdir $tail]
    }
    if {$rev eq ""} {
        set rev HEAD
    }
    cd $dir
    catch {exec git show $rev:$tail > $outfile}
    cd $old
    # example: git show HEAD^^^:apa
}

# Add file to GIT index
proc eskil::rev::GIT::add {filename} {
    set old [pwd]
    set dir [file dirname $filename]
    set tail [file tail $filename]
    # Locate the top directory
    while {![file isdirectory $dir/.git]} {
        set thisdir [file tail $dir]
        set dir [file dirname $dir]
        set tail [file join $thisdir $tail]
    }
    cd $dir
    catch {exec git add $tail}
    cd $old
}

# Get a GIT patch
proc eskil::rev::GIT::getPatch {revs {files {}}} {
    set cmd [list exec git diff -p]






    if {[llength $revs] == 0} {
        # Always default to HEAD to see changes regardless of index
        lappend cmd HEAD
    } else {
        foreach rev $revs {
            lappend cmd $rev
        }
    }
    lappend cmd "--" {*}$files

    if {[catch {eval $cmd} res]} {
        tk_messageBox -icon error -title "GIT error" -message $res
        return ""
    }













    return $res
}

# Get a FOSSIL revision
# No support for revisions yet
proc eskil::rev::FOSSIL::get {filename outfile rev} {




    set old [pwd]




    set dir [file dirname $filename]
    set tail [file tail $filename]

    # Locate the top directory

    while {![file exists $dir/_FOSSIL_]} {
        set thisdir [file tail $dir]
        set dir [file dirname $dir]

        set tail [file join $thisdir $tail]
    }


    cd $dir




    if {$rev eq "HEAD" || $rev eq ""} {
        catch {exec fossil finfo -p $tail > $outfile}
    } else {
        catch {exec fossil finfo -p $tail -r $rev > $outfile}
    }
    cd $old
}

# Get a FOSSIL patch
proc eskil::rev::FOSSIL::getPatch {revs {files {}}} {
    set cmd [list exec fossil diff]

    if {[llength $revs] >= 1} {
        lappend cmd --from [lindex $revs 0]
    }
    if {[llength $revs] >= 2} {
        lappend cmd --to [lindex $revs 1]
    }
    if {[llength $files] > 0} {
        # Fossil diff only handles one file at a time.
        set res ""
        foreach file $files {
            set fcmd $cmd


            lappend fcmd $file




            if {[catch {eval $cmd} fres]} {
                tk_messageBox -icon error -title "FOSSIL error" -message $fres
                return ""
            }







            append res $fres
        }





    } else {











        if {[catch {eval $cmd} res]} {
            tk_messageBox -icon error -title "FOSSIL error" -message $res
            return ""
        }






    }
    return $res
}

# Get a ClearCase revision
proc eskil::rev::CT::get {filename outfile rev} {
    set filerev [file nativename $filename@@$rev]
    if {[catch {exec cleartool get -to $outfile $filerev} msg]} {
        tk_messageBox -icon error -title "Cleartool error" -message $msg
        return
    }
}

# Get a CT patch
proc eskil::rev::CT::getPatch {revs {files {}}} {





    # Not supported yet
    return ""
}

# Get a P4 revision
proc eskil::rev::P4::get {filename outfile rev} {
    set dir [file dirname $filename]







|









|






>
>
>
>
>








|
>
>
>
>
>







<
<
|
<
<
<
<
<
<



<
|
<





<
<
|
<
<
<
<
<
<
<
|
<



|

>
>
>
>
>
>














>
>
>
>
>
>
>
>
>
>
>
>
>



|
<
|
>
>
>
>
|
>
>
>
>
|
|
>
|
>
|
|
|
>
|

>
>
|
>
>
>
>

|

|

<



|








|
|
|
|
|
>
>
|
>
>
>
>
|
|
|
|
>
>
>
>
>
>
>
|
|
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
>
>
>
>
>
>

|












|
>
>
>
>
>







563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612


613






614
615
616

617

618
619
620
621
622


623







624

625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666

667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699

700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790

    if {$old != ""} {
        cd $old
    }
}

# Get a BZR patch
proc eskil::rev::BZR::getPatch {revs files {fileListName {}}} {
    # TODO: support files
    set cmd [list exec bzr diff]
    if {[llength $revs] == 2} {
        lappend cmd -r [lindex $revs 0]..[lindex $revs 1]
    } elseif {[llength $revs] == 1} {
        lappend cmd -r [lindex $revs 0]
    }

    if {[catch {eval $cmd} res]} {
        if { ! [string match "*===*" $res]} {
            tk_messageBox -icon error -title "BZR error" -message $res
            return ""
        }
    }
    return $res
}

proc eskil::rev::BZR::getChangedFiles {dir revs} {
    # Not supported yet
    return ""
}

# Get an RCS revision
proc eskil::rev::RCS::get {filename outfile {rev {}}} {
    catch {exec co -p$rev [file nativename $filename] \
            > $outfile}
}

# Get a RCS patch
proc eskil::rev::RCS::getPatch {revs files {fileListName {}}} {
    # Not supported yet.
    return ""
}

proc eskil::rev::RCS::getChangedFiles {dir revs} {
    # Not supported yet.
    return ""
}

# Get a GIT revision
# No support for revisions yet
proc eskil::rev::GIT::get {filename outfile rev} {


    GetTopDir $filename dir tail






    if {$rev eq ""} {
        set rev HEAD
    }

    catch {execDir $dir git show $rev:$tail > $outfile}

    # example: git show HEAD^^^:apa
}

# Add file to GIT index
proc eskil::rev::GIT::add {filename} {


    GetTopDir $filename dir tail







    catch {execDir $dir git add $tail}

}

# Get a GIT patch
proc eskil::rev::GIT::getPatch {revs files {fileListName {}}} {
    set cmd [list exec git diff -p]
    if {$::Pref(context) >= 0} {
        lappend cmd -U$::Pref(context)
    }
    if {$::Pref(ignore) in "-w -b"} {
        lappend cmd $::Pref(ignore)
    }
    if {[llength $revs] == 0} {
        # Always default to HEAD to see changes regardless of index
        lappend cmd HEAD
    } else {
        foreach rev $revs {
            lappend cmd $rev
        }
    }
    lappend cmd "--" {*}$files

    if {[catch {eval $cmd} res]} {
        tk_messageBox -icon error -title "GIT error" -message $res
        return ""
    }
    if {$fileListName ne ""} {
        upvar 1 $fileListName fileList
        set fileList {}
        # GIT will have lines like this to show files:
        #diff --git a/dir1/f12 b/dir1/f12
        foreach line [lsearch -all -inline -regexp [split $res \n] {^diff -}] {
            if {[regexp { a/(.*) b/} $line -> fn]} {
                lappend fileList $fn
            }
        }
        set fileList [lsort -dictionary -unique $fileList]
    }

    return $res
}

# Get a GIT change set

proc eskil::rev::GIT::getChangedFiles {dir revs} {
    set cmd [list execDir $dir git diff --name-only]
    if {[llength $revs] == 0} {
        # Always default to HEAD to see changes regardless of index
        lappend cmd HEAD
    } else {
        foreach rev $revs {
            lappend cmd $rev
        }
    }

    if {[catch {eval $cmd} res]} {
        tk_messageBox -icon error -title "GIT error" -message $res
        return ""
    }
    # Result is one file per line, relative to repo
    GetTopDir $dir top tail
    set files {}
    foreach line [split $res \n] {
        lappend files [file join $top $line]
    }
    return $files
}

# Get a FOSSIL revision
# No support for revisions yet
proc eskil::rev::FOSSIL::get {filename outfile rev} {
    GetTopDir $filename dir tail
    if {$rev eq "HEAD" || $rev eq ""} {
        catch {execDir $dir fossil finfo -p $tail > $outfile}
    } else {
        catch {execDir $dir fossil finfo -p $tail -r $rev > $outfile}
    }

}

# Get a FOSSIL patch
proc eskil::rev::FOSSIL::getPatch {revs files {fileListName {}}} {
    set cmd [list exec fossil diff]

    if {[llength $revs] >= 1} {
        lappend cmd --from [lindex $revs 0]
    }
    if {[llength $revs] >= 2} {
        lappend cmd --to [lindex $revs 1]
    }
    # Include added files contents
    lappend cmd -N

    if {$::Pref(context) >= 0} {
        lappend cmd --context $::Pref(context)
    }
    if {$::Pref(ignore) in "-w -b"} {
        lappend cmd -w
    }

    lappend cmd {*}$files

    if {[catch {eval $cmd} res]} {
        tk_messageBox -icon error -title "FOSSIL error" -message $res
        return ""
    }
    if {$fileListName ne ""} {
        upvar 1 $fileListName fileList
        set fileList {}
        # FOSSIL will have lines like this to show files:
        #Index: dir1/f11
        foreach line [lsearch -all -inline -regexp [split $res \n] {^Index: }] {
            if {[regexp {Index: (.*)} $line -> fn]} {
                lappend fileList $fn
            }
        }
        set fileList [lsort -dictionary -unique $fileList]
    }
    return $res
}

proc eskil::rev::FOSSIL::getChangedFiles {dir revs} {
    set cmd [list execDir $dir fossil diff]

    if {[llength $revs] >= 1} {
        lappend cmd --from [lindex $revs 0]
    }
    if {[llength $revs] >= 2} {
        lappend cmd --to [lindex $revs 1]
    }

    lappend cmd --brief
    if {[catch {eval $cmd} res]} {
        tk_messageBox -icon error -title "FOSSIL error" -message $res
        return ""
    }
    # Result is one file per line, with an info word before
    GetTopDir $dir top tail
    set files {}
    foreach line [split $res \n] {
        regexp {\S+\s+(.*)} $line -> f
        lappend files [file join $top $f]
    }
    return $files
}

# Get a ClearCase revision
proc eskil::rev::CT::get {filename outfile rev} {
    set filerev [file nativename $filename@@$rev]
    if {[catch {exec cleartool get -to $outfile $filerev} msg]} {
        tk_messageBox -icon error -title "Cleartool error" -message $msg
        return
    }
}

# Get a CT patch
proc eskil::rev::CT::getPatch {revs files {fileListName {}}} {
    # Not supported yet
    return ""
}

proc eskil::rev::CT::getChangedFiles {dir revs} {
    # Not supported yet
    return ""
}

# Get a P4 revision
proc eskil::rev::P4::get {filename outfile rev} {
    set dir [file dirname $filename]
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578

579
580
581
582
583
584
585
586



587
588
589
590
591
592
593
594



595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611



















































































































612
613
614
615
616
617
618
619
620
621
622

623
624
625


626



627

628

629
630


631
632
633
634
635
636
637
638




639



640

641

642
643


644
645
646
647
648
649
650
651


652








653
654
655
656
657
658
659
    }

    set cmd [list exec cvs -n status [file nativename $filename]]
    if {[catch {eval $cmd} res]} {
        # What to do here?
        set rev "1.1"
    } else {
        if {![regexp {Working revision:\s+(\d\S*)} $res -> rev]} {
            set rev "1.1"
        }
    }

    if {$old != ""} {
        cd $old
    }
    return $rev
}

# Return current revision of a SVN file
proc eskil::rev::SVN::GetCurrent {filename} {
    set old ""
    if {$filename eq ""} {
        set cmd [list exec svn info]
    } else {
        set dir [file dirname $filename]
        if {$dir != "."} {
            set old [pwd]
            cd $dir
            set filename [file tail $filename]
        }

        set cmd [list exec svn info [file nativename $filename]]
    }
    if {[catch {eval $cmd} res]} {
        # What to do here?
        set rev "1"

    } else {
        if {![regexp {Last Changed Rev:\s+(\d+)} $res -> rev]} {
            set rev "1"
        }
    }

    if {$old != ""} {
        cd $old



    }
    return $rev
}

# Return revision list of a SVN file
proc eskil::rev::SVN::GetRevList {filename} {
    if {$filename eq ""} {
        set cmd [list exec svn log -q -l 50]



    } else {
        set cmd [list exec svn log -q -l 50 [file nativename $filename]]
    }
    if {[catch {eval $cmd} res]} {
        # What to do here?
        set revs [list 1]
    } else {
        set lines [lsearch -all -inline -regexp [split $res \n] {^\s*r\d}]
        set revs {}
        foreach line $lines {
            if {[regexp {r(\d+)} $line -> rev]} {
                lappend revs $rev
            }
        }
    }
    return $revs
}




















































































































# Figure out RCS revision from arguments
proc eskil::rev::RCS::ParseRevs {filename revs} {
    if {$filename eq ""} {
        # RCS does not support tree versions
        return {}
    }
    return $revs
}

# Figure out GIT revision from arguments

proc eskil::rev::GIT::ParseRevs {filename revs} {
    set result ""
    foreach rev $revs {


        switch -glob -- $rev {



            HEAD - master - * { # Let anything through for now

                lappend result $rev

            }
        }


    }
    return $result
}

# Figure out FOSSIL revision from arguments
proc eskil::rev::FOSSIL::ParseRevs {filename revs} {
    set result ""
    foreach rev $revs {




        switch -glob -- $rev {



            HEAD - master - * { # Let anything through for now FIXA

                lappend result $rev

            }
        }


    }
    return $result
}

# Figure out HG revision from arguments
proc eskil::rev::HG::ParseRevs {filename revs} {
    set result ""
    foreach rev $revs {


        # No parsing yet...








        lappend result $rev
    }
    return $result
}

# Figure out BZR revision from arguments
proc eskil::rev::BZR::ParseRevs {filename revs} {







|











|
















>

|






>
>
>








>
>
>

















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>











>



>
>
|
>
>
>
|
>
|
>


>
>








>
>
>
>
|
>
>
>
|
>
|
>


>
>








>
>
|
>
>
>
>
>
>
>
>







805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
    }

    set cmd [list exec cvs -n status [file nativename $filename]]
    if {[catch {eval $cmd} res]} {
        # What to do here?
        set rev "1.1"
    } else {
        if { ! [regexp {Working revision:\s+(\d\S*)} $res -> rev]} {
            set rev "1.1"
        }
    }

    if {$old != ""} {
        cd $old
    }
    return $rev
}

# Return current revision of a SVN file
proc eskil::rev::SVN::GetCurrent {filename {fullInfo 0}} {
    set old ""
    if {$filename eq ""} {
        set cmd [list exec svn info]
    } else {
        set dir [file dirname $filename]
        if {$dir != "."} {
            set old [pwd]
            cd $dir
            set filename [file tail $filename]
        }

        set cmd [list exec svn info [file nativename $filename]]
    }
    if {[catch {eval $cmd} res]} {
        # What to do here?
        set rev "1"
        set res ""
    } else {
        if { ! [regexp {Last Changed Rev:\s+(\d+)} $res -> rev]} {
            set rev "1"
        }
    }

    if {$old != ""} {
        cd $old
    }
    if {$fullInfo} {
        return $res
    }
    return $rev
}

# Return revision list of a SVN file
proc eskil::rev::SVN::GetRevList {filename} {
    if {$filename eq ""} {
        set cmd [list exec svn log -q -l 50]
    } elseif {[string match "*://*" $filename]} {
        # Full URL
        set cmd [list exec svn log -q -l 50 $filename]
    } else {
        set cmd [list exec svn log -q -l 50 [file nativename $filename]]
    }
    if {[catch {eval $cmd} res]} {
        # What to do here?
        set revs [list 1]
    } else {
        set lines [lsearch -all -inline -regexp [split $res \n] {^\s*r\d}]
        set revs {}
        foreach line $lines {
            if {[regexp {r(\d+)} $line -> rev]} {
                lappend revs $rev
            }
        }
    }
    return $revs
}

# Return revision list of a HG file
proc eskil::rev::HG::GetRevList {filename} {
    if {$filename eq ""} {
        set cmd [list exec hg log -q -l 50]
    } else {
        set cmd [list exec hg log -q -l 50 [file nativename $filename]]
    }
    if {[catch {eval $cmd} res]} {
        # What to do here?
        set revs [list 1]
    } else {
        set revs {}
        foreach line [split $res \n] {
            if {[regexp {^(\d+):} $line -> rev]} {
                lappend revs $rev
            }
        }
    }
    return $revs
}

# Return revision list of a GIT file
proc eskil::rev::GIT::GetRevList {filename} {
    set old ""
    set cmd [list exec git log --first-parent --oneline -n 50]
    if {$filename eq ""} {
        # Nothing
    } elseif {[file isdirectory $filename]} {
        set old [pwd]
        cd $filename
    } else {
        set old [pwd]
        cd [file dirname $filename]
        lappend cmd [file nativename [file tail $filename]]
    }
    if {[catch {eval $cmd} res]} {
        # What to do here?
        puts "ERROR for '$filename' $res"
        set revs [list HEAD]
    } else {
        set lines [split $res \n]
        set revs {}
        foreach line $lines {
            if {[regexp {^(\w+)} $line -> rev]} {
                lappend revs $rev
            }
        }
    }
    if {$old ne ""} {
        cd $old
    }
    return $revs
}

# Return revision list of a FOSSIL file
proc eskil::rev::FOSSIL::GetRevList {filename} {
    # Keep on current branch
    set x [execDir $filename fossil branch list]
    if { ! [regexp -line {^\* (.*)$} $x -> branch]} {
        set branch ""
    }

    # First, traverse timeline to get a set of ancestor checkins on the
    # current branch
    set x [execDir $filename fossil timeline ancestors current -t ci -n 5000]
    set ancestors {}
    set lines ""
    set currentArtefact ""
    foreach line [split $x \n] {
        # Recognise the first line of each checkin
        if {[regexp {^\d\d:\d\d:\d\d \[(\w+)\]} $line -> newArtefact]} {
            # Check the accumulated lines before this for tags
            if {[regexp {tags:\s+([^\)]+)} $lines -> tags]} {
                if {$branch eq ""} {
                    set branch [lindex $tags 0]
                }
                if {$branch in $tags} {
                    dict set ancestors $currentArtefact 1
                }
            }
            set currentArtefact $newArtefact
            set lines [string trim $line]
        } else {
            set line [string trim $line]
            if {[string index $lines end] eq "-"} {
                append lines $line
            } else {
                append lines \n$line
            }
        }
    }
    #puts "Assuming branch '$branch'"
    #puts "Found [dict size $ancestors] ancestors in timeline"

    if {[file isdirectory $filename]} {
        # Just use the ancestors as is. TBD to filter this for a sub directory
        return [dict keys $ancestors]
    }

    # Now get all commits on the file. If finfo had a tag filter,
    # this would be much easier.
    set x [execDir $filename fossil finfo -l -b $filename]
    set fAncestors {}
    foreach line [split $x \n] {
        if {[regexp {^(\w+)} $line -> artefact]} {
            if {[dict exists $ancestors $artefact]} {
                lappend fAncestors $artefact
            }
        }
    }
    #puts "Found [llength $fAncestors] ancestors for file"
    #puts [join $fAncestors \n]
    return $fAncestors
}

# Figure out RCS revision from arguments
proc eskil::rev::RCS::ParseRevs {filename revs} {
    if {$filename eq ""} {
        # RCS does not support tree versions
        return {}
    }
    return $revs
}

# Figure out GIT revision from arguments
# The resulting rev should work with 'git show <rev>:filename'
proc eskil::rev::GIT::ParseRevs {filename revs} {
    set result ""
    foreach rev $revs {
        # Special cases that shortcuts to GIT special names
        if {$rev eq "_" || $rev eq "0"} {set rev HEAD}

        if {[string is integer -strict $rev] && $rev < 0} {
            # A negative integer rev is a relative rev
            set revList [eskil::rev::GIT::GetRevList $filename]

            set rev [lindex $revList [- $rev]]
            if {$rev eq ""} {
                set rev [lindex $revs end]
            }
        }
        # Let anything else through
        lappend result $rev
    }
    return $result
}

# Figure out FOSSIL revision from arguments
proc eskil::rev::FOSSIL::ParseRevs {filename revs} {
    set result ""
    foreach rev $revs {
        # Special cases that shortcuts to Fossil special names
        if {$rev eq "_" || $rev eq "0"} {set rev current}
        # Previous does not work for files
        #if {$rev eq "-1"} {set rev previous}

        if {[string is integer -strict $rev] && $rev < 0} {
            # A negative integer rev is a relative rev
            set revList [eskil::rev::FOSSIL::GetRevList $filename]

            set rev [lindex $revList [- $rev]]
            if {$rev eq ""} {
                set rev [lindex $revList end]
            }
        }
        # Let anything else through
        lappend result $rev
    }
    return $result
}

# Figure out HG revision from arguments
proc eskil::rev::HG::ParseRevs {filename revs} {
    set result ""
    foreach rev $revs {
        # Shortcut to HG special names
        if {$rev eq "_" || $rev eq "0"} {set rev tip}

        if {[string is integer -strict $rev] && $rev < 0} {
            # A negative integer rev is a relative rev
            set revList [eskil::rev::HG::GetRevList $filename]
            set rev [lindex $revList [- $rev]]
            if {$rev eq ""} {
                set rev [lindex $revList end]
            }
        }
        lappend result $rev
    }
    return $result
}

# Figure out BZR revision from arguments
proc eskil::rev::BZR::ParseRevs {filename revs} {
681
682
683
684
685
686
687
688












































689
690
691
692
693


694
695
696
697
698
699

700
















701
702
703
704
705



706

707
708
709
710
711



712


713

714
715
716
717
718
719
720
            if {$tail < 1} {set tail 1}
            set rev $head$tail
        }
        lappend result $rev
    }
    return $result
}













































# Figure out SVN revision from arguments
proc eskil::rev::SVN::ParseRevs {filename revs} {
    set result {}
    foreach rev $revs {
        # A negative integer rev is a relative rev


        if {[string is integer -strict $rev] && $rev < 0} {
            # Save a roundtrip to the server in the case where we
            # can start from current
            if {$rev == -1} {
                set curr [eskil::rev::SVN::GetCurrent $filename]
                set rev [expr {$curr + $rev}]

            } else {
















                # Get a list from the log
                if {$filename eq ""} {
                    set filename "."
                }
                set cmd [list svn log -q [file nativename $filename]]



                set revs [eskil::rev::SVN::GetRevList $filename]

                set rev [lindex $revs [- $rev]]
                if {$rev eq ""} {
                    set rev [lindex $revs end]
                }
            }



        }


        lappend result $rev

    }
    return $result
}

# Figure out ClearCase revision from arguments
proc eskil::rev::CT::ParseRevs {filename revs} {
    if {$filename eq ""} {








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




<
>
>
|
<
<
|
<
|
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
<
>
>
>

>
|
|
|
|
|
>
>
>
|
>
>
|
>







1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151

1152
1153
1154


1155

1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178

1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
            if {$tail < 1} {set tail 1}
            set rev $head$tail
        }
        lappend result $rev
    }
    return $result
}

# Look for alternative version in a branch
# Return value, if any, is a full URL to the file
proc eskil::rev::SVN::LookForBranch {filename rev} {
    set info [eskil::rev::SVN::GetCurrent $filename 1]
    if { ! [regexp -line {URL:\s+(.+)} $info -> URL]} {
        return
    }
    if { ! [regexp -line {Repository Root:\s+(.+)} $info -> Root]} {
        return
    }
    set tail [string range $URL [string length $Root] end]
    if { ! [string match "/*" $tail]} {
        return
    }
    set tail [string range $tail 1 end]
    set parts [file split $tail]
    set alt {}
    switch [lindex $parts 0] {
        trunk {
            lappend alt [file join [lreplace $parts 0 0 branches $rev]]
            lappend alt [file join [lreplace $parts 0 0 tags $rev]]
            if {$rev eq "trunk"} {
                lappend alt [file join [lreplace $parts 0 0 trunk]]
            }
        }
        branches - tags {
            if {$rev eq "trunk"} {
                lappend alt [file join [lreplace $parts 0 1 trunk]]
            }
            lappend alt [file join [lreplace $parts 0 1 branches $rev]]
            lappend alt [file join [lreplace $parts 0 1 tags $rev]]
        }
    }
    foreach tailAlt $alt {
        set urlAlt $Root/[join $tailAlt /]
        if {[catch {exec svn "info" $urlAlt} res]} {
            continue
        }
        # Is it enough that svn info worked to check success? Seems so
        return $urlAlt
    }
    return
}

# Figure out SVN revision from arguments
proc eskil::rev::SVN::ParseRevs {filename revs} {
    set result {}
    foreach rev $revs {

        set Url ""
        # Non-numeric could be a branch or tag. Look for it.
        if { ! [string is integer -strict $rev]} {


            if {[regexp {^([^@]+)@(.+)$} $rev -> pre post]} {

                set rev $pre
                set atRev $post
            } else {
                set atRev ""
            }
            set Url [eskil::rev::SVN::LookForBranch $filename $rev]
            if {$Url ne ""} {
                set rev $atRev
            }
        }
        if {$rev eq "_" || $rev eq "0"} {
            # Common names for current
            # Use BASE since SVN then knows to use the local copy and avoid
            # server calls.
            set rev BASE
            #set rev [eskil::rev::SVN::GetCurrent $filename]
        } elseif {[string is integer -strict $rev] && $rev <= 0} {
            # Zero means current
            # A negative integer rev is a relative rev
            # Get a list from the log
            if {$filename eq ""} {
                set filename "."
            }

            if {$Url ne ""} {
                set revs [eskil::rev::SVN::GetRevList $Url]
            } else {
                set revs [eskil::rev::SVN::GetRevList $filename]
            }
            set rev [lindex $revs [- $rev]]
            if {$rev eq ""} {
                set rev [lindex $revs end]
            }
        }
        if {$Url ne ""} {
            if {$rev ne ""} {
                append Url @$rev
            }
            lappend result $Url
        } else {
            lappend result $rev
        }
    }
    return $result
}

# Figure out ClearCase revision from arguments
proc eskil::rev::CT::ParseRevs {filename revs} {
    if {$filename eq ""} {
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
            set offset $tail
	    if {$offset == -1} { # Predecessor
                return [exec cleartool describe -fmt %PSn $filename]
            }
            set rev [file dirname $rev]
        }
        # If the argument is of the form "name/rev", look for a fitting one
        if {![string is integer $rev] && [regexp {^[^/.]+(/\d+)?$} $rev]} {
            if {[catch {exec cleartool lshistory -short $filename} allrevs]} {#
                tk_messageBox -icon error -title "Cleartool error" \
                        -message $allrevs
                return
            }
            set allrevs [split $allrevs \n]

            set i [lsearch -glob $allrevs "*$rev" ]
            if {$i >= 0} {
                set rev [lindex [split [lindex $allrevs $i] "@"] end]
            }
        }
        set rev [file normalize [file join $stream $rev]]
        # If we don't have a version number, try to find the latest
        if {![string is integer [file tail $rev]]} {
            if {![info exists allrevs]} {
                if {[catch {exec cleartool lshistory -short $filename} allrevs]} {#
                    tk_messageBox -icon error -title "Cleartool error" \
                            -message $allrevs
                    return
                }
                set allrevs [split $allrevs \n]
            }







|














|
|







1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
            set offset $tail
	    if {$offset == -1} { # Predecessor
                return [exec cleartool describe -fmt %PSn $filename]
            }
            set rev [file dirname $rev]
        }
        # If the argument is of the form "name/rev", look for a fitting one
        if { ! [string is integer $rev] && [regexp {^[^/.]+(/\d+)?$} $rev]} {
            if {[catch {exec cleartool lshistory -short $filename} allrevs]} {#
                tk_messageBox -icon error -title "Cleartool error" \
                        -message $allrevs
                return
            }
            set allrevs [split $allrevs \n]

            set i [lsearch -glob $allrevs "*$rev" ]
            if {$i >= 0} {
                set rev [lindex [split [lindex $allrevs $i] "@"] end]
            }
        }
        set rev [file normalize [file join $stream $rev]]
        # If we don't have a version number, try to find the latest
        if { ! [string is integer [file tail $rev]]} {
            if { ! [info exists allrevs]} {
                if {[catch {exec cleartool lshistory -short $filename} allrevs]} {#
                    tk_messageBox -icon error -title "Cleartool error" \
                            -message $allrevs
                    return
                }
                set allrevs [split $allrevs \n]
            }
804
805
806
807
808
809
810
811


812
813
814
815
816
817
818
819
820
821
822
823
824
825
826



827

828
















829








830



831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848








849
850
851
852
853
854
855
856
857
858
859
860


























861
862
863














































864
















































865
866
867
868
869
870
871
proc eskil::rev::CVS::commitFile {top args} {
    if {[llength $args] == 0} {
        set target all
    } elseif {[llength $args] == 1} {
        set target [file tail [lindex $args 0]]
    } else {
        set target "[file tail [lindex $args 0]] ..."
    }        


    set logmsg [LogDialog $top $target]
    if {$logmsg ne ""} {
        catch {exec cvs -q commit -m $logmsg {*}$args}
    }
}

# Check in SVN controlled file
proc eskil::rev::SVN::commitFile {top args} {
    if {[llength $args] == 0} {
        set target all
    } elseif {[llength $args] == 1} {
        set target [file tail [lindex $args 0]]
    } else {
        set target "[file tail [lindex $args 0]] ..."
    }        



    set logmsg [LogDialog $top $target]

    if {$logmsg ne ""} {
















        catch {exec svn -q commit -m $logmsg {*}$args}








    }



}

# Check in GIT controlled file
proc eskil::rev::GIT::commitFile {top args} {
    if {[llength $args] == 0} {
        set target all
    } elseif {[llength $args] == 1} {
        set target [file tail [lindex $args 0]]
    } else {
        set target "[file tail [lindex $args 0]] ..."
    }        
    set logmsg [LogDialog $top $target]
    if {$logmsg eq ""} return

    if {[llength $args] == 0} {
        catch {exec git commit -a -m $logmsg}
    } else {
        catch {exec git commit -m $logmsg {*}$args}








    }
}

# Check in Fossil controlled file
proc eskil::rev::FOSSIL::commitFile {top args} {
    if {[llength $args] == 0} {
        set target all
    } elseif {[llength $args] == 1} {
        set target [file tail [lindex $args 0]]
    } else {
        set target "[file tail [lindex $args 0]] ..."
    }        


























    set logmsg [LogDialog $top $target]
    if {$logmsg eq ""} return















































    catch {exec fossil commit -m $logmsg {*}$args}
















































}

# View log between displayed versions
proc eskil::rev::CVS::viewLog {top filename revs} {
    set cmd [list exec cvs -q log -N]
    if {[llength $revs] > 1} {
        lappend cmd -r[join $revs ":"]







|
>
>
|
<
<
<










|
>
>
>
|
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>

>
>
>










|
<
<
<

|

|
>
>
>
>
>
>
>
>











|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297



1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354



1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
proc eskil::rev::CVS::commitFile {top args} {
    if {[llength $args] == 0} {
        set target all
    } elseif {[llength $args] == 1} {
        set target [file tail [lindex $args 0]]
    } else {
        set target "[file tail [lindex $args 0]] ..."
    }
    set precmd [list cvs -q commit -m]
    set postcmd $args
    CommitDialog $top $target CVS "" $precmd $postcmd



}

# Check in SVN controlled file
proc eskil::rev::SVN::commitFile {top args} {
    if {[llength $args] == 0} {
        set target all
    } elseif {[llength $args] == 1} {
        set target [file tail [lindex $args 0]]
    } else {
        set target "[file tail [lindex $args 0]] ..."
    }
    # Any explict dirs mentioned should not recurse.
    set precmd [list svn -q commit --depth=empty -m]
    set postcmd $args
    CommitDialog $top $target SVN "" $precmd $postcmd
}

# Does anything else needs to be committed with this file?
# Typically that would be added directories in SVN.
proc eskil::rev::SVN::commitFileDependency {filename} {
    set dir [file dirname $filename]
    set result {}
    while {$dir ni {. /}} {
        set s [exec svn status --depth=empty $dir]
        if {[string match "A*" $s]} {
            lappend result $dir
        } else {
            break
        }
        set dir [file dirname $dir]
    }
    return $result
}

# Check in HG controlled file
proc eskil::rev::HG::commitFile {top args} {
    if {[llength $args] == 0} {
        set target all
    } elseif {[llength $args] == 1} {
        set target [file tail [lindex $args 0]]
    } else {
        set target "[file tail [lindex $args 0]] ..."
    }
    set precmd [list hg -q commit -m]
    set postcmd $args
    CommitDialog $top $target HG "" $precmd $postcmd
}

# Check in GIT controlled file
proc eskil::rev::GIT::commitFile {top args} {
    if {[llength $args] == 0} {
        set target all
    } elseif {[llength $args] == 1} {
        set target [file tail [lindex $args 0]]
    } else {
        set target "[file tail [lindex $args 0]] ..."
    }



    if {[llength $args] == 0} {
        set precmd [list git commit -a -m]
    } else {
        set precmd [list git commit -m]
    }
    GetTopDir [pwd] topdir _
    set postcmd $args
    set gitmsg [CommitDialog $top $target GIT $topdir $precmd $postcmd 1]
    if {[string match "*detached HEAD*" $gitmsg]} {
        # Make sure to make a detached HEAD commit visible.
        tk_messageBox -icon info -title "GIT commit message" -message $gitmsg \
                -parent $top
    }
}

# Check in Fossil controlled file
proc eskil::rev::FOSSIL::commitFile {top args} {
    if {[llength $args] == 0} {
        set target all
    } elseif {[llength $args] == 1} {
        set target [file tail [lindex $args 0]]
    } else {
        set target "[file tail [lindex $args 0]] ..."
    }
    set precmd [list fossil commit -no-prompt -m]
    set postcmd $args
    GetTopDir [pwd] topdir _
    # Files to commit might be relative to topdir, take care of that.
    # This can happen with -review in a subdir.
    set usetopdir ""
    foreach f $args {
        if { ! [file exists $f]} {
            if {[file exists [file join $topdir $f]]} {
                set usetopdir $topdir
            }
        }
    }
    CommitDialog $top $target Fossil $usetopdir $precmd $postcmd 1
}


# Revert SVN controlled file
proc eskil::rev::SVN::revertFile {top args} {
    if {[llength $args] == 0} {
        set target all
    } elseif {[llength $args] == 1} {
        set target [file tail [lindex $args 0]]
    } else {
        set target "[file tail [lindex $args 0]] ..."
    }
    set ok [RevertDialog $top $target]
    if {$ok ne "ok"} return

    if {[llength $args] == 0} {
        set args "-R ."
    }
    set sts [catch {exec svn revert -q {*}$args} svnmsg]
    set svnmsg [string trim $svnmsg]
    if {$svnmsg ne ""} {
        tk_messageBox -icon error -title "SVN revert error" -message $svnmsg \
                -parent $top
    }
}

# Revert HG controlled file
proc eskil::rev::HG::revertFile {top args} {
    if {[llength $args] == 0} {
        set target all
    } elseif {[llength $args] == 1} {
        set target [file tail [lindex $args 0]]
    } else {
        set target "[file tail [lindex $args 0]] ..."
    }
    set ok [RevertDialog $top $target]
    if {$ok ne "ok"} return

    if {[llength $args] == 0} {
        set args "--all"
    }
    set sts [catch {exec hg revert -q -C {*}$args} svnmsg]
    set svnmsg [string trim $svnmsg]
    if {$svnmsg ne ""} {
        tk_messageBox -icon error -title "HG revert error" -message $svnmsg \
                -parent $top
    }
}

# Revert Fossil controlled file
proc eskil::rev::FOSSIL::revertFile {top args} {
    if {[llength $args] == 0} {
        set target all
    } elseif {[llength $args] == 1} {
        set target [file tail [lindex $args 0]]
    } else {
        set target "[file tail [lindex $args 0]] ..."
    }
    set ok [RevertDialog $top $target]
    if {$ok ne "ok"} return

    set sts [catch {exec fossil revert {*}$args} errmsg]
    if {$sts} {
        tk_messageBox -icon error -title "Fossil revert error" \
                -message $errmsg -parent $top
    }
}

# Revert Git controlled file
proc eskil::rev::GIT::revertFile {top args} {
    if {[llength $args] == 0} {
        set target all
    } elseif {[llength $args] == 1} {
        set target [file tail [lindex $args 0]]
    } else {
        set target "[file tail [lindex $args 0]] ..."
    }
    set ok [RevertDialog $top $target]
    if {$ok ne "ok"} return

    if {[llength $args] == 0} {
        set sts [catch {exec git checkout .} gitmsg]
    } else {
        set sts [catch {exec git checkout {*}$args} gitmsg]
    }
    set gitmsg [string trim $gitmsg]
    if {$sts} {
        tk_messageBox -icon error -title "GIT revert error" -message $gitmsg \
                -parent $top
    }
}

# Mount a directory revision as a VFS, and return the mount point
proc eskil::rev::FOSSIL::mount {dir rev} {
    return [vcsvfs::fossil::mount $dir $rev]
}

# Mount a directory revision as a VFS, and return the mount point
proc eskil::rev::SVN::mount {dir rev} {
    return [vcsvfs::svn::mount $dir $rev]
}

# Mount a directory revision as a VFS, and return the mount point
proc eskil::rev::HG::mount {dir rev} {
    return [vcsvfs::hg::mount $dir $rev]
}

# Mount a directory revision as a VFS, and return the mount point
proc eskil::rev::GIT::mount {dir rev} {
    return [vcsvfs::git::mount $dir $rev]
}

# View log between displayed versions
proc eskil::rev::CVS::viewLog {top filename revs} {
    set cmd [list exec cvs -q log -N]
    if {[llength $revs] > 1} {
        lappend cmd -r[join $revs ":"]
889
890
891
892
893
894
895










































896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962

963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979

980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002



1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041





1042

1043
1044
1045
1046
1047











1048
1049
1050
1051
1052
1053
1054
1055
1056

1057
1058
1059
1060

1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085


1086
1087


1088


1089
1090
1091
1092
1093







































1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106






1107
1108
1109
1110
1111
1112
1113
1114




1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133


















1134
1135
1136
1137
1138



1139
1140
1141




1142



1143























1144







1145
1146













1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
    }
    lappend cmd $filename
    if {[catch {eval $cmd} result]} {
        #return
    }
    ViewLog $top $filename $result
}











































proc eskil::rev::CT::current {filename} {
    # Figure out stream and current version
    if {[catch {exec cleartool ls $filename} info]} {
        tk_messageBox -icon error -title "Cleartool error" -message $info
        return
    }
    set currV {}
    if {![regexp {@@(\S+)\s+from (\S+)\s+Rule} $info -> dummy currV]} {
        regexp {@@(\S+)} $info -> currV
    }
    set stream [file dirname $currV]
    set latest [file tail $currV]
    return [list $stream $latest]
}

##############################################################################
# Exported procedures
##############################################################################

# Figure out what revision control system a file is under
# Returns name of rev system if detected, or "" if none.
proc detectRevSystem {file {preference GIT}} {
    variable eskil::rev::cache

    if {$file ne ""} {
        if {![file exists $file]} { return "" }

        if {[info exists cache($file)]} {
            return $cache($file)
        }
    }
    
    set searchlist [list $preference GIT FOSSIL HG BZR P4]
    foreach ns [namespace children eskil::rev] {
        lappend searchlist [namespace tail $ns]
    }
    foreach rev $searchlist {
        set result [eskil::rev::${rev}::detect $file]
        if {$result} {
            set cache($file) $rev
            return $rev
        }
    }
    return
}

# Initialise revision control mode
# The file name should be an absolute normalized path.
proc startRevMode {top rev file} {
    set ::diff($top,mode) "rev"
    set ::diff($top,modetype) $rev
    set ::diff($top,rightDir) [file dirname $file]
    set ::diff($top,RevFile) $file
    set ::diff($top,rightLabel) $file
    set ::diff($top,rightFile) $file
    set ::diff($top,rightOK) 1
    set ::diff($top,leftLabel) $rev
    set ::diff($top,leftOK) 0
    set ::Pref(toolbar) 1
}

# Prepare for revision diff. Checkout copies of the versions needed.
proc prepareRev {top} {
    global Pref

    $::widgets($top,commit) configure -state disabled

    $::widgets($top,log)    configure -state disabled

    set type $::diff($top,modetype)

    set revs {}

    # Search for revision options
    if {$::diff($top,doptrev1) != ""} {
        lappend revs $::diff($top,doptrev1)
    }
    if {$::diff($top,doptrev2) != ""} {
        lappend revs $::diff($top,doptrev2)
    }

    set revs [eskil::rev::${type}::ParseRevs $::diff($top,RevFile) $revs]
    set revlabels {}
    foreach rev $revs {

        lappend revlabels [GetLastTwoPath $rev]
    }
    set ::diff($top,RevRevs) $revs

    if {[llength $revs] < 2} {
        # Compare local file with specified version.
        disallowEdit $top 1
        if {[llength $revs] == 0} {
            set r ""
            set tag "($type)"
        } else {
            set r [lindex $revs 0]
            set tag "($type [lindex $revlabels 0])"
        }
        set ::diff($top,leftFile) [tmpFile]
        set ::diff($top,leftLabel) "$::diff($top,RevFile) $tag"
        set ::diff($top,rightLabel) $::diff($top,RevFile)
        set ::diff($top,rightFile) $::diff($top,RevFile)

        eskil::rev::${type}::get $::diff($top,RevFile) $::diff($top,leftFile) $r
        if {[llength $revs] == 0} {
            if {[info commands eskil::rev::${type}::commitFile] ne ""} {
                $::widgets($top,commit) configure -state normal



            }
        }
    } else {
        # Compare the two specified versions.
        disallowEdit $top
        set r1 [lindex $revs 0]
        set r2 [lindex $revs 1]
        set ::diff($top,leftFile)  [tmpFile]
        set ::diff($top,rightFile) [tmpFile]

        set ::diff($top,leftLabel) \
                "$::diff($top,RevFile) ($type [lindex $revlabels 0])"
        set ::diff($top,rightLabel) \
                "$::diff($top,RevFile) ($type [lindex $revlabels 1])"
        eskil::rev::${type}::get $::diff($top,RevFile) $::diff($top,leftFile) $r1
        eskil::rev::${type}::get $::diff($top,RevFile) $::diff($top,rightFile) $r2
    }
    if {[llength $revs] > 0} {
        if {[info commands eskil::rev::${type}::viewLog] ne ""} {
            $::widgets($top,log) configure -state normal
        }
    }
    # Make sure labels are updated before processing starts
    update idletasks
}

# Clean up after a revision diff.
proc cleanupRev {top} {
    global Pref

    clearTmp $::diff($top,rightFile) $::diff($top,leftFile)
    set ::diff($top,rightFile) $::diff($top,RevFile)
    set ::diff($top,leftFile) $::diff($top,RevFile)
}

proc revCommit {top} {
    if {[$::widgets($top,commit) cget -state] eq "disabled"} return
    set type $::diff($top,modetype)
    if {$::diff($top,mode) eq "patch"} {





        set files $::diff($top,reviewFiles)

    } else {
        set files [list $::diff($top,RevFile)]
    }
    eskil::rev::${type}::commitFile $top {*}$files
}












proc revLog {top} {
    if {[$::widgets($top,log) cget -state] eq "disabled"} return
    set type $::diff($top,modetype)
    eskil::rev::${type}::viewLog $top $::diff($top,RevFile) \
            $::diff($top,RevRevs)
}

# Get a complete tree patch from this system.

proc getFullPatch {top} {
    global Pref

    $::widgets($top,commit) configure -state disabled

    $::widgets($top,log)    configure -state disabled

    set type $::diff($top,modetype)
    set files $::diff($top,reviewFiles)

    set revs {}

    # Search for revision options
    if {$::diff($top,doptrev1) != ""} {
        lappend revs $::diff($top,doptrev1)
    }
    if {$::diff($top,doptrev2) != ""} {
        lappend revs $::diff($top,doptrev2)
    }

    set revs [eskil::rev::${type}::ParseRevs "" $revs]
    set revlabels {}
    foreach rev $revs {
        lappend revlabels [GetLastTwoPath $rev]
    }

    if {[llength $revs] == 0} {
        if {[info commands eskil::rev::${type}::commitFile] ne ""} {
            $::widgets($top,commit) configure -state normal
        }


    }



    return [eskil::rev::${type}::getPatch $revs $files]


}

##############################################################################
# Utilities
##############################################################################








































# Get the last two elements in a file path
proc GetLastTwoPath {path} {
    set last [file tail $path]
    set penultimate [file tail [file dirname $path]]
    if {$penultimate eq "."} {
        return $last
    } else {
        return [file join $penultimate $last]
    }
}

# Dialog for log message






proc LogDialog {top target {clean 0}} {
    set w $top.logmsg
    destroy  $w
    toplevel $w -padx 3 -pady 3
    wm title $w "Commit log message for $target"

    set ::diff($top,logdialogok) 0





    text $w.t -width 70 -height 10
    if {!$clean && [info exists ::diff(logdialog)]} {
        $w.t insert end $::diff(logdialog)
        $w.t tag add sel 1.0 end-1c
        $w.t mark set insert 1.0
    }

    ttk::button $w.ok -width 10 -text "Commit" -underline 1 \
            -command "set ::diff($top,logdialogok) 1 ; \
                      set ::diff(logdialog) \[$w.t get 1.0 end\] ; \
                      destroy $w"
    ttk::button $w.ca -width 10 -text "Cancel" -command "destroy $w" \
            -underline 0
    bind $w <Alt-o> [list $w.ok invoke]\;break
    bind $w <Alt-c> [list destroy $w]\;break
    bind $w <Key-Escape> [list destroy $w]\;break

    grid $w.t  - -sticky news -padx 3 -pady 3
    grid $w.ok $w.ca -padx 3 -pady 3


















    tkwait visibility $w
    focus -force $w.t
    tkwait window $w

    if {$::diff($top,logdialogok)} {



        set res [string trim $::diff(logdialog)]
        set ::diff(logdialog) $res
        if {$res eq ""} {




            set res "No Log"



        }























    } else {







        set res ""
    }













    return $res
}

# Dialog for log view
proc ViewLog {top filename message} {
    set w $top.logview
    destroy  $w
    toplevel $w -padx 3 -pady 3
    wm title $w "Log for [file tail $filename]"

    text $w.t -width 80 -height 15 -yscrollcommand "$w.sby set" -wrap none
    scrollbar $w.sby -orient vertical -command "$w.t yview"
    $w.t insert end $message

    ttk::button $w.ok -width 10 -text "Dismiss" -command "destroy $w" \
            -underline 0
    bind $w <Alt-d> [list destroy $w]\;break
    bind $w <Key-Escape> [list destroy $w]\;break

    grid $w.t  $w.sby -sticky news -padx 3 -pady 3
    grid $w.ok -      -padx 3 -pady 3
    grid columnconfigure $w 0 -weight 1
    grid rowconfigure    $w 0 -weight 1
}







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>








|

















|





|

















|
|
|
|
|
|
|
|
|





<
<

>


|




|
|

|
|


|


>


|











|
|
|
|

|



>
>
>







|
|

|
|
|
|
|
|












<
<
|
|
|




|
|
>
>
>
>
>
|
>

|



>
>
>
>
>
>
>
>
>
>
>



|
|
|



>

<
<

>


|
|




|
|

|
|












>
>
|
|
>
>
|
>
>





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>












|
>
>
>
>
>
>
|





|

>
>
>
>
|
|
|





|
|
|








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


|

|
>
>
>
|
|
|
>
>
>
>
|
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>
>
>
>
>
>
>
|

>
>
>
>
>
>
>
>
>
>
>
>
>
|










|












1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639


1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713


1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756


1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
    }
    lappend cmd $filename
    if {[catch {eval $cmd} result]} {
        #return
    }
    ViewLog $top $filename $result
}

# View log between displayed versions
proc eskil::rev::GIT::viewLog {top filename revs} {
    set cmd [list exec git log]
    if {[llength $revs] > 1} {
        lappend cmd [join $revs ".."]
    } else {
        lappend cmd [lindex $revs 0]..
    }
    lappend cmd $filename
    if {[catch {eval $cmd} result]} {
        #return
    }
    ViewLog $top $filename $result
}

# View log between displayed versions
proc eskil::rev::HG::viewLog {top filename revs} {
    set cmd [list exec hg log]
    foreach rev $revs {
        lappend cmd -r $rev
    }
    lappend cmd $filename
    if {[catch {eval $cmd} result]} {
        #return
    }
    ViewLog $top $filename $result
}

proc eskil::rev::FOSSIL::viewLog {top filename revs} {
    set cmd [list exec fossil timeline]
    foreach rev $revs {
        lappend cmd after $rev
        # Only support for one at this point.
        break
    }
    lappend cmd --limit 0 --path $filename
    if {[catch {eval $cmd} result]} {
        #return
    }
    ViewLog $top $filename $result
}

proc eskil::rev::CT::current {filename} {
    # Figure out stream and current version
    if {[catch {exec cleartool ls $filename} info]} {
        tk_messageBox -icon error -title "Cleartool error" -message $info
        return
    }
    set currV {}
    if { ! [regexp {@@(\S+)\s+from (\S+)\s+Rule} $info -> dummy currV]} {
        regexp {@@(\S+)} $info -> currV
    }
    set stream [file dirname $currV]
    set latest [file tail $currV]
    return [list $stream $latest]
}

##############################################################################
# Exported procedures
##############################################################################

# Figure out what revision control system a file is under
# Returns name of rev system if detected, or "" if none.
proc detectRevSystem {file {preference GIT}} {
    variable eskil::rev::cache

    if {$file ne ""} {
        if { ! [file exists $file]} { return "" }

        if {[info exists cache($file)]} {
            return $cache($file)
        }
    }

    set searchlist [list $preference GIT FOSSIL HG BZR P4]
    foreach ns [namespace children eskil::rev] {
        lappend searchlist [namespace tail $ns]
    }
    foreach rev $searchlist {
        set result [eskil::rev::${rev}::detect $file]
        if {$result} {
            set cache($file) $rev
            return $rev
        }
    }
    return
}

# Initialise revision control mode
# The file name should be an absolute normalized path.
proc startRevMode {top rev file} {
    set ::eskil($top,mode) "rev"
    set ::eskil($top,modetype) $rev
    set ::eskil($top,rightDir) [file dirname $file]
    set ::eskil($top,RevFile) $file
    set ::eskil($top,rightLabel) $file
    set ::eskil($top,rightFile) $file
    set ::eskil($top,rightOK) 1
    set ::eskil($top,leftLabel) $rev
    set ::eskil($top,leftOK) 0
    set ::Pref(toolbar) 1
}

# Prepare for revision diff. Checkout copies of the versions needed.
proc prepareRev {top} {


    $::widgets($top,commit) configure -state disabled
    $::widgets($top,revert) configure -state disabled
    $::widgets($top,log)    configure -state disabled

    set type $::eskil($top,modetype)

    set revs {}

    # Search for revision options
    if {$::eskil($top,doptrev1) != ""} {
        lappend revs $::eskil($top,doptrev1)
    }
    if {$::eskil($top,doptrev2) != ""} {
        lappend revs $::eskil($top,doptrev2)
    }

    set revs [eskil::rev::${type}::ParseRevs $::eskil($top,RevFile) $revs]
    set revlabels {}
    foreach rev $revs {
        # TODO: In SVN rev could be a full URL, display it nicer
        lappend revlabels [GetLastTwoPath $rev]
    }
    set ::eskil($top,RevRevs) $revs

    if {[llength $revs] < 2} {
        # Compare local file with specified version.
        disallowEdit $top 1
        if {[llength $revs] == 0} {
            set r ""
            set tag "($type)"
        } else {
            set r [lindex $revs 0]
            set tag "($type [lindex $revlabels 0])"
        }
        set ::eskil($top,leftFile) [tmpFile]
        set ::eskil($top,leftLabel) "$::eskil($top,RevFile) $tag"
        set ::eskil($top,rightLabel) $::eskil($top,RevFile)
        set ::eskil($top,rightFile) $::eskil($top,RevFile)

        eskil::rev::${type}::get $::eskil($top,RevFile) $::eskil($top,leftFile) $r
        if {[llength $revs] == 0} {
            if {[info commands eskil::rev::${type}::commitFile] ne ""} {
                $::widgets($top,commit) configure -state normal
            }
            if {[info commands eskil::rev::${type}::revertFile] ne ""} {
                $::widgets($top,revert) configure -state normal
            }
        }
    } else {
        # Compare the two specified versions.
        disallowEdit $top
        set r1 [lindex $revs 0]
        set r2 [lindex $revs 1]
        set ::eskil($top,leftFile)  [tmpFile]
        set ::eskil($top,rightFile) [tmpFile]

        set ::eskil($top,leftLabel) \
                "$::eskil($top,RevFile) ($type [lindex $revlabels 0])"
        set ::eskil($top,rightLabel) \
                "$::eskil($top,RevFile) ($type [lindex $revlabels 1])"
        eskil::rev::${type}::get $::eskil($top,RevFile) $::eskil($top,leftFile) $r1
        eskil::rev::${type}::get $::eskil($top,RevFile) $::eskil($top,rightFile) $r2
    }
    if {[llength $revs] > 0} {
        if {[info commands eskil::rev::${type}::viewLog] ne ""} {
            $::widgets($top,log) configure -state normal
        }
    }
    # Make sure labels are updated before processing starts
    update idletasks
}

# Clean up after a revision diff.
proc cleanupRev {top} {


    clearTmp $::eskil($top,rightFile) $::eskil($top,leftFile)
    set ::eskil($top,rightFile) $::eskil($top,RevFile)
    set ::eskil($top,leftFile) $::eskil($top,RevFile)
}

proc revCommit {top} {
    if {[$::widgets($top,commit) cget -state] eq "disabled"} return
    set type $::eskil($top,modetype)
    if {$::eskil($top,mode) eq "patch"} {
        if {[llength $::eskil($top,patchFilelist)] != 0} {
            # Use the list extracted from patch
            set files $::eskil($top,patchFilelist)
        } else {
            # Use the list given by user
            set files $::eskil($top,reviewFiles)
        }
    } else {
        set files [list $::eskil($top,RevFile)]
    }
    eskil::rev::${type}::commitFile $top {*}$files
}

proc revRevert {top} {
    if {[$::widgets($top,revert) cget -state] eq "disabled"} return
    set type $::eskil($top,modetype)
    if {$::eskil($top,mode) eq "patch"} {
        set files $::eskil($top,reviewFiles)
    } else {
        set files [list $::eskil($top,RevFile)]
    }
    eskil::rev::${type}::revertFile $top {*}$files
}

proc revLog {top} {
    if {[$::widgets($top,log) cget -state] eq "disabled"} return
    set type $::eskil($top,modetype)
    eskil::rev::${type}::viewLog $top $::eskil($top,RevFile) \
            $::eskil($top,RevRevs)
}

# Get a complete tree patch from this system.
# Note that current directory must be correct before calling.
proc getFullPatch {top} {


    $::widgets($top,commit) configure -state disabled
    $::widgets($top,revert) configure -state disabled
    $::widgets($top,log)    configure -state disabled

    set type $::eskil($top,modetype)
    set files $::eskil($top,reviewFiles)

    set revs {}

    # Search for revision options
    if {$::eskil($top,doptrev1) != ""} {
        lappend revs $::eskil($top,doptrev1)
    }
    if {$::eskil($top,doptrev2) != ""} {
        lappend revs $::eskil($top,doptrev2)
    }

    set revs [eskil::rev::${type}::ParseRevs "" $revs]
    set revlabels {}
    foreach rev $revs {
        lappend revlabels [GetLastTwoPath $rev]
    }

    if {[llength $revs] == 0} {
        if {[info commands eskil::rev::${type}::commitFile] ne ""} {
            $::widgets($top,commit) configure -state normal
        }
        if {[info commands eskil::rev::${type}::revertFile] ne ""} {
            $::widgets($top,revert) configure -state normal
        }
    }

    set fileList {}
    set patch [eskil::rev::${type}::getPatch $revs $files fileList]
    set ::eskil($top,patchFilelist) $fileList
    return $patch
}

##############################################################################
# Utilities
##############################################################################

# Execute a command within a specific dir as pwd
proc execDir {dir args} {
    set old [pwd]
    if {[file isdirectory $dir]} {
        cd $dir
    } else {
        # A file may be given as reference
        cd [file dirname $dir]
    }
    try {
        exec {*}$args
    } finally {
        cd $old
    }
}

# Search upwards the directory structure for a file
proc SearchUpwardsFromFile {file args} {
    if {$file eq ""} {
        set dir [pwd]
    } elseif {[file isdirectory $file]} {
        set dir $file
    } else {
        set dir [file dirname $file]
    }
    while {[file readable $dir] && [file isdirectory $dir]} {
        foreach candidate $args {
            if {[file exists [file join $dir $candidate]]} {
                return 1
            }
        }
        set parent [file dirname $dir]
        # Make sure to stop if we reach a dead end
        if {$parent eq $dir} break
        set dir $parent
    }
    return 0
}

# Get the last two elements in a file path
proc GetLastTwoPath {path} {
    set last [file tail $path]
    set penultimate [file tail [file dirname $path]]
    if {$penultimate eq "."} {
        return $last
    } else {
        return [file join $penultimate $last]
    }
}

# Dialog for commit, getting log message
# target: String shown in dialog
# system: Rev System
# topdir: Directory to execute commit in, if given.
# precmd: Command part before message
# postcmd: Command part after message. Assumed to be files.
# useSts: Use status from exec rather than message to recognise error.
proc CommitDialog {top target system topdir precmd postcmd {useSts 0}} {
    set w $top.logmsg
    destroy  $w
    toplevel $w -padx 3 -pady 3
    wm title $w "Commit log message for $target"

    set ::eskil($top,logdialogok) 0

    # Dummy frame used for detecting closed window
    ttk::frame $w.dummy -width 10 -height 10
    place $w.dummy -x 0 -y 0

    text $w.t -width 70 -height 10 -font myfont
    if {[info exists ::eskil(logdialog)]} {
        $w.t insert end $::eskil(logdialog)
        $w.t tag add sel 1.0 end-1c
        $w.t mark set insert 1.0
    }

    ttk::button $w.ok -width 10 -text "Commit" -underline 1 \
            -command "set ::eskil($top,logdialogok) 1 ; \
                      set ::eskil(logdialog) \[$w.t get 1.0 end\] ; \
                      destroy $w.dummy"
    ttk::button $w.ca -width 10 -text "Cancel" -command "destroy $w" \
            -underline 0
    bind $w <Alt-o> [list $w.ok invoke]\;break
    bind $w <Alt-c> [list destroy $w]\;break
    bind $w <Key-Escape> [list destroy $w]\;break

    grid $w.t  - -sticky news -padx 3 -pady 3
    grid $w.ok $w.ca -padx 3 -pady 3
    grid columnconfigure $w $w.t -weight 1 -uniform a
    grid rowconfigure    $w $w.t -weight 1

    if {[llength $postcmd] > 1} {
        # TODO: Scrolled frame maybe? Is dynamic grid enough?
        ttk::frame $w.f -padding 1
        grid $w.f - -sticky news -padx 3 -pady 3

        set t 0
        foreach fileName $postcmd {
            set ::eskil($top,commit,fileselect$t) 1
            ttk::checkbutton $w.f.cb$t -text $fileName \
                    -variable ::eskil($top,commit,fileselect$t)
            incr t
        }
        dynGridManage $w.f
    }

    tkwait visibility $w
    focus -force $w.t
    tkwait window $w.dummy

    if { ! $::eskil($top,logdialogok)} {
        return
    }

    set res [string trim $::eskil(logdialog)]
    set ::eskil(logdialog) $res

    set todo $postcmd
    if {[llength $postcmd] > 1} {
        # Look through checkbuttons
        set todo {}
        set t 0
        foreach fileName $postcmd {
            if {$::eskil($top,commit,fileselect$t)} {
                lappend todo $fileName
            }
            incr t
        }
        # None left means ignore.
        if {[llength $todo] == 0} {
            return
        }
    }

    if {[info commands eskil::rev::${system}::commitFileDependency] ne ""} {
        foreach filename $todo {
            lappend todo {*}[eskil::rev::${system}::commitFileDependency $filename]
        }
    }

    # Splash screen for visual feedback
    set now [clock clicks -milliseconds]
    ttk::label $w.splash -text "Committing" -anchor center -font myfont
    place $w.splash -x 0 -y 0 -relwidth 1.0 -relheight 1.0
    update
    # Commit
    set cmd [list {*}$precmd $res {*}$todo]
    if {$topdir ne ""} {
        set sts [catch {execDir $topdir {*}$cmd} msg]
    } else {
        set sts [catch {exec {*}$cmd} msg]
    }
    set msg [string trim $msg]
    if {($useSts && $sts) || (!$useSts && $msg ne "")} {
        destroy $w
        tk_messageBox -icon error -title "$system commit error" -message $msg \
                -parent $top
        return
    }
    # Keep it up for a decent length, regardless of commit delay
    while {abs([clock clicks -milliseconds] - $now) < 500} {
        after 100
    }
    destroy $w
    return $msg
}

# Dialog for revert acknowledge
proc RevertDialog {top target} {
    set msg "Discard local changes for $target ?"
    set result [tk_messageBox -type okcancel -icon question -parent $top \
                        -title "Revert" -message $msg]
    return $result
}

# Dialog for log view
proc ViewLog {top filename message} {
    set w $top.logview
    destroy  $w
    toplevel $w -padx 3 -pady 3
    wm title $w "Log for [file tail $filename]"

    text $w.t -width 80 -height 15 -yscrollcommand "$w.sby set" -wrap none
    ttk::scrollbar $w.sby -orient vertical -command "$w.t yview"
    $w.t insert end $message

    ttk::button $w.ok -width 10 -text "Dismiss" -command "destroy $w" \
            -underline 0
    bind $w <Alt-d> [list destroy $w]\;break
    bind $w <Key-Escape> [list destroy $w]\;break

    grid $w.t  $w.sby -sticky news -padx 3 -pady 3
    grid $w.ok -      -padx 3 -pady 3
    grid columnconfigure $w 0 -weight 1
    grid rowconfigure    $w 0 -weight 1
}

Added src/startup.tcl.























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
#!/bin/sh
#---------------------------------------------------------- -*- tcl -*-
#
#  Eskil, a Graphical frontend to diff
#
#  Copyright (c) 1998-2015, Peter Spjuth  (peter.spjuth@gmail.com)
#
#  Usage
#             Do 'eskil' for interactive mode
#             Do 'eskil --help' for command line usage
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; see the file COPYING.  If not, write to
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------
# the next line restarts using tclsh \
exec tclsh "$0" "$@"

package require Tcl 8.6

# Stop Tk from meddling with the command line by copying it first.
set ::eskil(argv) $::argv
set ::eskil(argc) $::argc
set ::argv {}
set ::argc 0

set ::eskil(debug) 0
# Version string is loaded from version.txt
set ::eskil(diffver) "Version Unknown"
set ::eskil(thisScript) [file join [pwd] [info script]]
set ::eskil(thisDir) [file dirname $::eskil(thisScript)]

namespace import tcl::mathop::+
namespace import tcl::mathop::-
namespace import tcl::mathop::*
namespace import tcl::mathop::/

# Do initalisations for needed packages and globals.
# This is not run until needed to speed up command line error reporting.
proc Init {} {
    if {[info exists ::eskil(initHasRun)]} {
        return
    }
    set ::eskil(initHasRun) 1
    package require Tk 8.6
    catch {package require textSearch}
    package require wcb
    package require snit
    package require tablelist_tile

    if {[catch {package require psballoon}]} {
        # Add a dummy if it does not exist.
        proc addBalloon {args} {}
    } else {
        namespace import -force psballoon::addBalloon
    }

    # Follow any link
    set tmplink $::eskil(thisScript)
    while {[file type $tmplink] eq "link"} {
        set tmplink [file readlink $tmplink]
        set tmplink [file normalize [file join $::eskil(thisDir) $tmplink]]
        set ::eskil(thisDir) [file dirname $tmplink]
    }

    if {[file exists $::eskil(thisDir)/../version.txt]} {
        set ch [open $::eskil(thisDir)/../version.txt]
        set ::eskil(diffver) [string trim [read $ch 100]]
        close $ch
    }

    # Get all other source files
    InitReSource

    # Diff functionality is in the DiffUtil package.
    package require DiffUtil 0.4
    # Help DiffUtil to find a diff executable, if needed
    catch {DiffUtil::LocateDiffExe $::eskil(thisScript)}

    # Create font for PDF
    if {$::Pref(printFont) eq ""} {
        set fontfile $::eskil(thisDir)/embedfont.ttf
    } else {
        set fontfile $::Pref(printFont)
    }
    # Allow fallback to PDF-builtin Courier
    if {$fontfile eq "Courier"} {
        set ::eskil(printFont) Courier
    } else {
        set ext [file extension $fontfile]
        if {$ext eq ".afm"} {
            pdf4tcl::loadBaseType1Font EskilBase $fontfile \
                    [file rootname $fontfile].pfb
        } else {
            pdf4tcl::loadBaseTrueTypeFont EskilBase $fontfile 1
        }

        pdf4tcl::createFont EskilBase EskilFont cp1252
        set ::eskil(printFont) EskilFont
    }

    # Figure out a place to store temporary files.
    locateTmp ::eskil(tmpdir)

    if {$::tcl_platform(platform) eq "windows"} {
        # Locate CVS if it is in c:/bin
        if {[auto_execok cvs] eq "" && [file exists "c:/bin/cvs.exe"]} {
            set ::env(PATH) "$::env(PATH);c:\\bin"
            auto_reset
        }
    }
    defaultGuiOptions
    if {0 && [bind all <Alt-KeyPress>] eq ""} {
        bind all <Alt-KeyPress> [bind Menubutton <Alt-KeyPress>]
        #after 500 "tk_messageBox -message Miffo"
    }
    wm withdraw .

    if {[catch {package require Ttk}]} {
        if {[catch {package require tile}]} {
            if {[info exists ::eskil_testsuite]} {
                return
            } else {
                puts "Themed Tk not found"
                exit
            }
        }
    }
    # Provide a ttk-friendly toplevel, fixing background and menubar
    if {[info commands ttk::toplevel] eq ""} {
        proc ttk::toplevel {W args} {
            tk::toplevel $W {*}$args
            place [ttk::frame $W.tilebg] -border outside \
                    -x 0 -y 0 -relwidth 1 -relheight 1
            return $W
        }
    }

    ::snit::widgetadaptor ttk::entryX {
        delegate method * to hull
        delegate option * to hull

        constructor {args} {
            installhull using ttk::entry
            $self configurelist $args
            # Make sure textvariable is initialised
            set varName [from args -textvariable ""]
            if {$varName ne ""} {
                upvar \#0 $varName var
                if { ! [info exists var]} {
                    set var ""
                }
            }
        }
        # Circumvent a bug in ttk::entry that "xview end" does not work.
        # Fixed 2013-06-05, bug 3613750. 8.5.16 and 8.6.2.
        method xview {args} {
            if {[llength $args] == 1} {
                set ix [lindex $args 0]
                $hull xview [$hull index $ix]
            } else {
                $hull xview {*}$args
            }
        }
    }

    interp alias {} toplevel {} ttk::toplevel

    # Use demo images from Tablelist
    set dir $::eskil(thisDir)/../lib/tablelist/demos
    if {[catch {
        set ::img(clsd) [image create photo -file [file join $dir clsdFolder.gif]]
        set ::img(open) [image create photo -file [file join $dir openFolder.gif]]
        set ::img(file) [image create photo -file [file join $dir file.gif]]
    }]} then {
        set ::img(clsd) ""
        set ::img(open) ""
        set ::img(file) ""
    }
    # Local images
    set dir $::eskil(thisDir)/images
    set ::img(link) [image create photo -file [file join $dir link.gif]]
    set ::img(left) [image create photo -file [file join $dir arrow_left.gif]]
    set ::img(right) [image create photo -file [file join $dir arrow_right.gif]]
    set ::img(browse) [image create photo -file [file join $dir folderopen1.gif]]
    set ::img(up) [image create photo -file [file join $dir arrow_up.gif]]
    # Create a double up arrow
    set ih [image height $::img(up)]
    set iw [image width $::img(up)]
    set ::img(upup) [image create photo -height $ih -width [expr {2 * $iw}]]
    $::img(upup) copy $::img(up) -to 0 0 [expr {2 * $iw - 1}] [expr {$ih - 1}]

    EskilThemeInit
}

# Load sources needed early, during command line handling
proc InitSourceEarly {{srcdir {}}} {
    if {$srcdir eq ""} {
        set srcdir $::eskil(thisDir)
    }
    source $srcdir/preprocess.tcl
}

proc InitReSource {{srcdir {}}} {
    if {$srcdir eq ""} {
        set srcdir $::eskil(thisDir)
    }

    InitSourceEarly $srcdir
    # Get all other source files
    source $srcdir/eskil.tcl
    source $srcdir/clip.tcl
    source $srcdir/compare.tcl
    source $srcdir/map.tcl
    source $srcdir/merge.tcl
    source $srcdir/registry.tcl
    source $srcdir/dirdiff.tcl
    source $srcdir/fourway.tcl
    source $srcdir/help.tcl
    source $srcdir/plugin.tcl
    source $srcdir/printobj.tcl
    source $srcdir/print.tcl
    source $srcdir/rev.tcl
    source $srcdir/debug.tcl

    # Only load vcsvfs if vfs is present
    if { ! [catch {package require vfs}]} {
        source $srcdir/vcsvfs.tcl
    }
}

# Debug function to be able to reread the source even when wrapped in a kit.
proc EskilRereadSource {} {
    set this $::eskil(thisScript)

    # FIXA: Better detection of starkit?
    # Maybe look at ::starkit::topdir ?

    #if {[info exists ::starkit::topdir]} {
    #    puts "Topdir: $::starkit::topdir"
    #}

    # Are we in a Starkit?
    if {[regexp {^(.*eskil)((?:\.[^/]+)?)(/src/.*)$} $this -> \
            pre ext post]} {
        if {$ext ne ".vfs"} {
            # If the unpacked vfs directory is available, read from that
            # instead.
            set src $pre.vfs$post
            if {[file readable $src]} {
                set this $src
            }
        }
    }
    puts "Resourcing $this"
    uplevel \#0 [list source $this]
    # Get all other source files
    InitReSource [file dirname $this]
}

# Initialize Ttk style settings
proc EskilThemeInit {} {
    # Import the 'default' theme border element.
    catch { ttk::style element create plain.border from default }
    catch { ttk::style element create plain.padding from default }
    catch { ttk::style element create plain.label from default }

    # Create a new style using the imported element.
    ttk::style layout My.Toolbutton {
        My.Toolbutton.plain.border -sticky nswe -children {
            My.Toolbutton.padding -sticky nswe -children {
                My.Toolbutton.label -sticky nswe
            }
        }
    }
    # Configure  our new style.
    ttk::style configure My.Toolbutton {*}[ttk::style configure Toolbutton] \
        -padding {1 1}
    ttk::style map My.Toolbutton {*}[ttk::style map Toolbutton] \
        -relief {disabled flat selected sunken pressed sunken active raised}
    # Re-do if the user changes theme.
    if {[lsearch -exact [bind . <<ThemeChanged>>] EskilThemeInit] == -1} {
        bind . <<ThemeChanged>> +EskilThemeInit
    }
}

proc defaultGuiOptions {} {
    # Turn off tearoff on all systems
    option add *Menu.tearOff 0
    if {[tk windowingsystem]=="x11"} {
        # Menubar looks out of place on linux. This adjusts the background
        # Which is enough to make it reasonable.
        set bg [ttk::style configure . -background]
        set sbg [ttk::style configure . -selectbackground]
        option add *Menubutton.background $bg
        option add *Menu.background $bg
        option add *Menu.activeBackground $sbg

        option add *Listbox.background $bg
        option add *Listbox.selectBackground $sbg
        option add *Text.background white
        option add *Text.selectBackground $sbg

        #option add *Scrollbar.takeFocus 0
        #option add *highlightThickness 0
    }
}

#####################################
# Startup stuff
#####################################

proc printUsage {} {
    set usageStr {Usage: eskil [options] [files...]
  [options]  See below.
  [files...] Files to be compared
  %v%
  If no files are given, the program is started anyway and you can select
  files from within. If only one file is given, the program looks for version
  control of the file, and if found, runs in version control mode.
  If directories are given, Eskil starts in directory diff.

  To list all options matching a prefix, run 'eskil --query prefix'.
  In tcsh use this line to get option completion:
  complete eskil 'C/-/`eskil --query -`/'

  Options:}

    set versionStr ""
    if {[file exists $::eskil(thisDir)/../version.txt]} {
        set ch [open $::eskil(thisDir)/../version.txt]
        set versionStr [string trim [read $ch 100]]
        close $ch
        set versionStr "$versionStr\n"
    }

    set usageStr [string map [list "%v%" $versionStr] $usageStr]
    puts $usageStr

    # Dump option info
    foreach name [lsort -dictionary [dict keys $::eskil(opts,info)]] {
        set outName $name
        if { ! [dict exists $::eskil(opts,info) $name flag]} {
            puts "Internal Error: BOHOHOHO $name"
            break
        }
        if { ! [dict get $::eskil(opts,info) $name flag]} {
            set valueName v
            # Detect a reference in short description
            set short [dict get $::eskil(opts,info) $name shortdescr]
            if {[regexp {<(.*?)>} $short -> var] } {
                set valueName $var
            }
            append outName " <$valueName>"
        }
        # Line up shorter ones
        if {[string length $outName] < 12} {
            set outName [format %-12s $outName]
        }
        set outName "$outName : "
        set indent [string length $outName]
        set len [expr {80 - $indent}]
        set d [dict get $::eskil(opts,info) $name shortdescr]
        if {$d eq "_"} continue
        while {$d ne ""} {
            if {[string length $d] <= $len} {
                set chunk $d
                set d ""
            } else {
                set ci $len
                while {[string index $d $ci] ne " " && $ci > 40} {
                    incr ci -1
                }
                set chunk [string range $d 0 $ci-1]
                set d [string trim [string range $d $ci end]]
            }
            puts "$outName$chunk"
            set outName [format %*s $indent ""]
        }
    }
    # Dump any plugin that has options defined
    foreach {plugin _} $::eskil(opts,src) {
        puts ""
        printPlugin $plugin 1
    }
}

#####################################
# Option/flag handling helpers
#####################################
# Validators
proc optValidatePdfColor {opt arg} {
    set fail 0
    if { ! [string is list $arg] || [llength $arg] != 3} {
        set fail 1
    } else {
        foreach val $arg {
            if { ! [string is double -strict $val] || $val < 0.0 || $val > 1.0} {
                set fail 1
            }
        }
    }
    if {$fail} {
        puts "Argument $opt must be a list of RBG values from 0.0 to 1.0"
        exit
    }
}
proc optValidatePositive {opt arg} {
    if { ! [string is double -strict $arg] || $arg <= 0} {
        puts "Argument $opt must be a positive number"
        exit
    }
}
proc optValidateNatural {opt arg} {
    if { ! [string is integer -strict $arg] || $arg < 0} {
        puts "Argument $opt must be a natural number"
        exit
    }
}
proc optValidatePaper {opt arg} {
    package require pdf4tcl
    if {[llength [pdf4tcl::getPaperSize $arg]] != 2} {
        puts "Argument $opt must be a valid paper size"
        puts "Valid paper sizes:"
        puts [join [lsort -dictionary [pdf4tcl::getPaperSizeList]] \n]
        exit
    }
}
proc optValidatePlugin {opt arg} {
    # We must start up completely to check a plugin
    Init
    set res [LocatePlugin $arg]
    set src [dict get $res file]
    if {$src eq ""} {
        puts "Bad plugin: $arg"
        printPlugins
        exit
    }
    # Look for declarations of command line options
    foreach {name flag doc} [dict get $res opts] {
        if {$flag} {
            addFlags $name
        } else {
            addOpt $name
        }
        docFlag $name "Plugin $arg : $doc"
        addSource $name $arg
    }
    # Special:
    # If a -plugin is given and plugininfo and pluginallow is not
    # balanced, extend them.
    set n [llength [dict get $::eskil(opts) -plugin]]
    # Validator is called after this -plugin was added.
    incr n -1
    while {[llength [dict get $::eskil(opts) -plugininfo]] < $n} {
        dict lappend ::eskil(opts) -plugininfo ""
    }
    while {[llength [dict get $::eskil(opts) -pluginallow]] < $n} {
        dict lappend ::eskil(opts) -pluginallow 0
    }
}

# Option database setup
proc initOpts {} {
    set ::eskil(opts) {}
    set ::eskil(opts,info) {}
    set ::eskil(opts,src) {}
    set ::eskil(defoptinfo) {
        flag 0
        given 0
        multi 0
        type ""
        validator ""
        filter ""
        sideeffect ""
        shortdescr ""
        longdescr ""
        source ""
    }
}
# Add a command line flag that do not take a value
proc addFlags {args} {
    foreach name $args {
        dict set ::eskil(opts) $name 0
        dict set ::eskil(opts,info) $name $::eskil(defoptinfo)
        dict set ::eskil(opts,info) $name flag  1
    }
}
# Add a command line flag that do not take a value, but can be given multiple
proc addMultFlags {args} {
    foreach name $args {
        dict set ::eskil(opts) $name {}
        dict set ::eskil(opts,info) $name $::eskil(defoptinfo)
        dict set ::eskil(opts,info) $name flag  1
        dict set ::eskil(opts,info) $name multi 1
    }
}
# Document a flag or option
proc docFlag {name short {long {}}} {
    dict set ::eskil(opts,info) $name shortdescr $short
    dict set ::eskil(opts,info) $name longdescr $long
}

# Flag that affects Pref
proc addPrefFlag {name elem {value 1}} {
    dict set ::eskil(opts) $name 0
    dict set ::eskil(opts,info) $name $::eskil(defoptinfo)
    dict set ::eskil(opts,info) $name flag  1
    dict set ::eskil(opts,info) $name type Pref
    dict set ::eskil(opts,info) $name "elem" $elem
    dict set ::eskil(opts,info) $name "value" $value
}
# Flag that affects local opts
proc addOptsFlag {name elem {value 1}} {
    dict set ::eskil(opts) $name 0
    dict set ::eskil(opts,info) $name $::eskil(defoptinfo)
    dict set ::eskil(opts,info) $name flag  1
    dict set ::eskil(opts,info) $name type Opts
    dict set ::eskil(opts,info) $name "elem" $elem
    dict set ::eskil(opts,info) $name "value" $value
}
# Add a command line option that takes a value
proc addOpt {name {def ""}} {
    dict set ::eskil(opts) $name $def
    dict set ::eskil(opts,info) $name $::eskil(defoptinfo)
}
# Add a command line option that takes a value and stores in Pref
proc addPrefOpt {name elem {validator ""}} {
    dict set ::eskil(opts) $name ""
    dict set ::eskil(opts,info) $name $::eskil(defoptinfo)
    dict set ::eskil(opts,info) $name type Pref
    dict set ::eskil(opts,info) $name "elem" $elem
    dict set ::eskil(opts,info) $name "validator" $validator
}
# Add a command line option that takes multiple values and stores in Pref
proc addPrefMultOpt {name elem {validator ""}} {
    dict set ::eskil(opts) $name ""
    dict set ::eskil(opts,info) $name $::eskil(defoptinfo)
    dict set ::eskil(opts,info) $name type Pref
    dict set ::eskil(opts,info) $name "elem" $elem
    dict set ::eskil(opts,info) $name "validator" $validator
    dict set ::eskil(opts,info) $name multi 1
}
# Add a vaildator command to an Opt
proc addValidator {name cmd} {
    dict set ::eskil(opts,info) $name validator $cmd
}
# Add a filter command prefix to an Opt
proc addFilter {name cmd} {
    dict set ::eskil(opts,info) $name filter $cmd
}
# Add a source reference to an Opt
proc addSource {name src} {
    # Remember them if needed for -help
    dict set ::eskil(opts,src) $src 1
    # This points to the plugin the Opt belongs to.
    dict set ::eskil(opts,info) $name source $src
}
# Add a sideeffect to an Opt
##nagelfar syntax addSideEffect x c
proc addSideEffect {name script} {
    dict set ::eskil(opts,info) $name sideeffect $script
}
# Add a command line option that takes a value and stores in local opts
proc addOptsOpt {name elem {validator ""}} {
    dict set ::eskil(opts) $name ""
    dict set ::eskil(opts,info) $name $::eskil(defoptinfo)
    dict set ::eskil(opts,info) $name type Opts
    dict set ::eskil(opts,info) $name "elem" $elem
    dict set ::eskil(opts,info) $name "validator" $validator
}
# Add a command line option that takes multiple values
proc addMultOpt {name} {
    dict set ::eskil(opts) $name {}
    dict set ::eskil(opts,info) $name $::eskil(defoptinfo)
    dict set ::eskil(opts,info) $name multi 1
}
# List all known options
proc allOpts {{pat *}} {
    return [dict keys $::eskil(opts) $pat]
}
proc optIsFlag {arg} {
    return [dict get $::eskil(opts,info) $arg flag]
}
proc optIsGiven {arg valName} {
    upvar 1 $valName val
    set val [dict get $::eskil(opts) $arg]
    return [dict get $::eskil(opts,info) $arg given]
}
proc optSet {arg val} {
    if {[dict get $::eskil(opts,info) $arg multi]} {
        dict lappend ::eskil(opts) $arg $val
    } else {
        dict set ::eskil(opts) $arg $val
    }
    # If it is a flag, the value can come from the settings
    if {[dict exists $::eskil(opts,info) $arg value]} {
        set val [dict get $::eskil(opts,info) $arg value]
    }
    # Any validator?
    set cmd [dict get $::eskil(opts,info) $arg validator]
    if {$cmd ne ""} {
        # The validator will exit if it fails
        $cmd $arg $val
    }
    # Any filter?
    set cmd [dict get $::eskil(opts,info) $arg filter]
    if {$cmd ne ""} {
        set val [{*}$cmd $val]
    }
    # Any side effect?
    set cmd [dict get $::eskil(opts,info) $arg sideeffect]
    if {$cmd ne ""} {
        uplevel 1 $cmd
    }

    set type [dict get $::eskil(opts,info) $arg type]
    switch $type {
        Pref {
            if {[dict get $::eskil(opts,info) $arg multi]} {
                lappend ::Pref([dict get $::eskil(opts,info) $arg elem]) $val
            } else {
                set ::Pref([dict get $::eskil(opts,info) $arg elem]) $val
            }
        }
        Opts {
            # Does not support multi yet
            upvar 1 opts _xx
            set _xx([dict get $::eskil(opts,info) $arg elem]) $val
        }
    }
    dict set ::eskil(opts,info) $arg given 1
}
proc optGet {arg} {
    return [dict get $::eskil(opts) $arg]
}

# Helper to add a file argument to list of files
proc cmdLineAddFile {filesName arg} {
    upvar 1 $filesName files
    set apa [file normalize [file join [pwd] $arg]]
    if { ! [file exists $apa]} {
        if {[string length $arg] <= 2 && [string match *M* $arg]} {
            puts "Ignoring argument: $arg"
        } else {
            puts "Bad argument: $arg"
            exit
        }
    } else {
        lappend files $apa
    }
}

# Go through all command line arguments and start the appropriate
# diff window.
# Returns the created toplevel.
# This can be used as an entry point if embedding eskil.
# In that case fill in ::eskil(argv) and ::eskil(argc) before calling.
proc parseCommandLine {} {
    global dirdiff

    set ::eskil(autoclose) 0
    set ::eskil(ignorenewline) 0
    set ::eskil(defaultopts) {}

    if {$::eskil(argc) == 0} {
        Init
        return [makeDiffWin]
    }

    # Set up all options info
    initOpts
    addFlags --help -help

    addPrefFlag -w          ignore -w
    addPrefFlag -b          ignore -b
    addPrefFlag -noignore   ignore " "
    docFlag     -w          "Ignore all spaces"
    docFlag     -b          "Ignore space changes (default)"
    docFlag     -noignore   "Don't ignore any whitespace"
    addPrefFlag -noparse    parse 0
    addPrefFlag -line       parse 1
    addPrefFlag -smallblock parse 2
    addPrefFlag -block      parse 3
    docFlag -noparse    "No block analysis"
    docFlag -line       "Line based block analysis"
    docFlag -smallblock "Do block analysis on small blocks (default)"
    docFlag -block      "Full block analysis. This can be slow if there are large change blocks"

    addPrefFlag -char       lineparsewords 0
    addPrefFlag -word       lineparsewords 1
    docFlag     -char       "Character based change view (default)"
    docFlag     -word       "Word based change view"
    addPrefFlag -i          nocase
    addPrefFlag -nocase     nocase
    docFlag     -i          "Ignore case changes"
    docFlag     -nocase     "Ignore case changes"
    addPrefFlag -nodigit    nodigit
    docFlag     -nodigit    "Ignore digit changes"
    addPrefFlag -nokeyword  dir,ignorekey
    docFlag     -nokeyword  "In directory diff, ignore \$ Keywords: \$"
    addPrefFlag -noempty    noempty
    docFlag     -noempty    "Ignore empty lines initially for matching"
    addPrefFlag -fine       finegrainchunks
    docFlag     -fine       "Use fine grained chunks. Useful for merging"
    addOptsFlag -table      view  table
    docFlag     -table      "Run in table mode"
    addOptsFlag -conflict mode conflict
    docFlag -conflict "Treat file as a merge conflict file and enter merge mode"
    # Conflict implies foreach
    addSideEffect -conflict {
        optSet -foreach 1
    }
    addFlags -dir -clip -fourway -patch -review -
    docFlag -dir    "Start in directory diff mode. Ignores other args"
    docFlag -clip   "Start in clip diff mode. Ignores other args"
    docFlag -fourway "Start in fourway diff mode. Ignores other args"
    docFlag -patch  "View patch file"
    docFlag -       "Read patch file from standard input, to allow pipes"
    docFlag -review "View revision control tree as a patch"
    addSideEffect -review {
        optSet -noignore 1
    }
    addFlags -browse -nodiff
    docFlag -browse "Bring up file dialog for missing files after starting"
    docFlag -nodiff "Do not run diff after startup"
    addFlags -server -cvs -svn -debug
    docFlag -server "Set up Eskil to be controllable from the outside"
    docFlag -cvs    "Detect CVS first, if multiple version systems are used"
    docFlag -svn    "Detect SVN first, if multiple version systems are used"
    docFlag -debug  "Start in debug mode"
    addFlags -foreach -close
    docFlag -foreach  "Open one diff window per file listed"
    docFlag -close   "Close any window with no changes"
    addFlags -nonewline -nonewline+ -nocdiff
    docFlag -nonewline  "Try to ignore newline changes"
    docFlag -nonewline+ "Try to ignore newline changes, and don't display"
    docFlag -nocdiff    "Disable C version of DiffUtil. For debug"
    addFlags -pluginlist
    addMultFlags -pluginallow
    docFlag -pluginlist  "List known plugins"
    docFlag -pluginallow "Allow full access privilege for plugin"
    # Options that take values
    addMultOpt -plugin
    docFlag  -plugin "Preprocess files using plugin"
    addValidator -plugin optValidatePlugin
    addMultOpt -plugininfo
    docFlag  -plugininfo "Pass info to plugin (plugin specific)"
    addOpt   -plugindump
    docFlag  -plugindump "Dump plugin source to stdout"
    # These options affect Pref
    addPrefOpt -pivot             pivot             optValidatePositive
    docFlag    -pivot "Pivot setting for diff algorithm (10)"
    addPrefOpt -context           context           optValidateNatural
    docFlag    -context "Show only differences, with <n> lines of context"
    addPrefOpt -printHeaderSize   printHeaderSize   optValidatePositive
    addPrefOpt -printLineSpace    printLineSpace    optValidatePositive
    addPrefOpt -printCharsPerLine printCharsPerLine optValidatePositive
    addPrefOpt -printPaper        printPaper        optValidatePaper
    addPrefOpt -printColorChange  printColorChange  optValidatePdfColor
    addPrefOpt -printColorOld     printColorOld     optValidatePdfColor
    addPrefOpt -printColorNew     printColorNew     optValidatePdfColor
    addPrefOpt -printFont         printFont
    docFlag -printHeaderSize   "Font size for page header (10)"
    docFlag -printLineSpace    "Line spacing (1.0)"
    docFlag -printCharsPerLine "Adapt font size for this line length and wrap (80)"
    docFlag -printPaper       "Select paper size (a4)"
    docFlag -printColorChange "Color for change (1.0 0.7 0.7)"
    docFlag -printColorOld    "Color for old text (0.7 1.0 0.7)"
    docFlag -printColorNew    "Color for new text (0.8 0.8 1.0)"
    docFlag -printFont "Select font to use in PDF, afm or ttf. If <fontfile> is given as \"Courier\", PDF built in font is used"
    addPrefMultOpt -excludedir dir,exdirs
    docFlag    -excludedir "Exclude from directory diff"
    addPrefMultOpt -excludefile dir,exfiles
    docFlag    -excludefile "Exclude from directory diff"
    addPrefMultOpt -includedir dir,incdirs
    docFlag    -includedir "Include in directory diff"
    addPrefMultOpt -includefile dir,incfiles
    docFlag    -includefile "Include in directory diff"
    # These affect Pref but via special processing later
    addMultOpt -prefix
    docFlag    -prefix "Care mainly about words starting with <str>"
    addMultOpt -subst
    docFlag    -subst  "The <pair> is a list of Left+Right, used for subst preprocessing"
    addMultOpt -preprocess
    addMultOpt -preprocessleft
    addMultOpt -preprocessright
    docFlag    -preprocess  "The <pair> is a list of RE+Subst applied to each line before compare"
    docFlag    -preprocessleft  "Use <pair> only on left side"
    docFlag    -preprocessright "Use <pair> only on right side"
    # These affect opts
    addOptsOpt -limit    limitlines
    docFlag    -limit "Do not process more than <lines> lines"
    addOptsFlag -gz   gz
    docFlag    -gz    "Uncompress input files with gunzip"
    addOptsOpt -maxwidth maxwidth
    docFlag    -maxwidth "Limit column width in table mode"
    addOptsOpt -o mergeFile
    docFlag    -o "Specify merge result output <file>"
    addFilter  -o [list file join [pwd]]
    addOptsOpt -a ancestorFile
    docFlag    -a "Give ancestor <file> for three way merge"
    addFilter  -a [list file join [pwd]]
    # Default is no ignore on three-way merge
    addSideEffect -a { set ::Pref(ignore) " " }
    addOptsOpt -sep separatorview
    docFlag -sep "See char <c> as separator between columns in files"
    addOptsOpt -print    printFile
    docFlag -print           "Generate PDF and exit"
    addOptsOpt -printpdf printFile ;# Old option
    docFlag -printpdf        "_"
    addSideEffect -print    { set opts(printFileCmd) 1 }
    addSideEffect -printpdf { set opts(printFileCmd) 1 }
    addMultOpt -r
    docFlag -r "Version info for version control mode"

    # If the first option is "--query", use it to ask about options.
    if {$::eskil(argc) == 2 && [lindex $::eskil(argv) 0] == "--query"} {
        set arg [lindex $::eskil(argv) 1]
        set allOpts [allOpts]
        # Remove "-" from allOpts
        set i [lsearch -exact $allOpts "-"]
        set allOpts [lreplace $allOpts $i $i]
        if {[lsearch -exact $allOpts $arg] < 0} {
            set match [lsearch -glob -all -inline $allOpts $arg*]
        } else {
            set match [list $arg]
        }
        puts [lsort -dictionary $match]
        exit
    }

    # Local opts array that some flags puts their info in.
    array set opts {}

    # Go through and fill in options
    set files {}
    for {set i 0} {$i < [llength $::eskil(argv)]} {incr i} {
        set arg [lindex $::eskil(argv) $i]
        # Non-dash means not an option
        if {[string index $arg 0] ne "-"} {
            cmdLineAddFile files $arg
            continue
        }
        if {$arg eq "-"} {
            # Allow "-" for stdin patch processing
            lappend files "-"
            continue
        }
        # Handle unknowns
        if { ! [dict exists $::eskil(opts) $arg]} {
            # Try to see if it is an unique abbreviation of an option.
            set match [allOpts $arg*]
            if {[llength $match] == 1} {
                set arg [lindex $match 0]
            } else {
                # If not, try to put it among files
                cmdLineAddFile files $arg
                continue
            }
        }
        # Flags
        if {[optIsFlag $arg]} {
            set val 1
        } else {
            # Options with values
            incr i
            set val [lindex $::eskil(argv) $i]
        }
        optSet $arg $val
    }

    # Any help flag given just prints and exits
    if {[optIsGiven -help arg] || [optIsGiven --help arg]} {
        printUsage
        exit
    }

    # All options have been parsed, extract them to where they need to go

    # Straight to locals
    set pluginL     [optGet -plugin]
    set plugininfoL [optGet -plugininfo]
    set plugindump  [optGet -plugindump]
    set pluginlist  [optGet -pluginlist]
    set pluginallowL [optGet -pluginallow]
    set noautodiff  [optGet -nodiff]
    set nocdiff     [optGet -nocdiff]
    set dodir       [optGet -dir]
    set doclip      [optGet -clip]
    set dofourway   [optGet -fourway]
    set dopatch     [optGet -patch]
    set doreview    [optGet -review]
    set autobrowse  [optGet -browse]
    set foreachOpt  [optGet -foreach]
    set preferedRev "GIT"
    if {[optGet -svn]} {
        set preferedRev "SVN"
    } elseif {[optGet -cvs]} {
        set preferedRev "CVS"
    }

    # These directly correspond to ::eskil settings
    set apa {
        -nonewline  ignorenewline 1
        -nonewline+ ignorenewline 2
        -close      autoclose 1
        -debug      debug 1
    }
    foreach {opt elem val} $apa {
        if {[optIsGiven $opt arg]} {
            set ::eskil($elem) $val
        }
    }

    # Options that need individual checking/processing
    if {[optIsGiven -prefix arg]} {
        foreach apa $arg {
            set RE [string map [list % $apa] {^.*?\m(%\w+).*$}]
            if {$::Pref(nocase)} {
                set RE "(?i)$RE"
            }
            addPreprocess prefix $RE {\1} ""
        }
    }
    if {[optIsGiven -subst arg]} {
        # FIXA: better validity check
        foreach apa $arg {
            foreach {left right} $apa {
                if {$::Pref(nocase)} {
                    set left "(?i)$left"
                    set right "(?i)$right"
                }
                addPreprocess subst $left $right Subst
            }
        }
    }
    if {[optIsGiven -preprocess arg]} {
        # FIXA: better validity check
        foreach apa $arg {
            foreach {RE sub} $apa {
                addPreprocess cmdline $RE $sub ""
            }
        }
    }
    if {[optIsGiven -preprocessleft arg]} {
        # FIXA: better validity check
        foreach apa $arg {
            foreach {RE sub} $apa {
                addPreprocess cmdline $RE $sub "left"
            }
        }
    }
    if {[optIsGiven -preprocessright arg]} {
        # FIXA: better validity check
        foreach apa $arg {
            foreach {RE sub} $apa {
                addPreprocess cmdline $RE $sub "right"
            }
        }
    }
    # Handle list of revisions
    if {[optIsGiven -r arg]} {
        set revNo 1
        foreach rev $arg {
            set opts(doptrev$revNo) $rev
            incr revNo
        }
    }
    if {[optGet -server]} {
        if {$::tcl_platform(platform) eq "windows"} {
            catch {
                package require dde
                dde servername Eskil
            }
        } else {
            package require Tk
            tk appname Eskil
        }
    }

    # Option handling done. Lets get started.

    Init

    if {$nocdiff} {
        DisableDiffUtilC
    }

    if {$pluginlist} {
        printPlugins
        exit
    }
    if {$plugindump ne ""} {
        printPlugin $plugindump
        exit
    }
    set t 0
    foreach plugin $pluginL {
        set plugininfo [lindex $plugininfoL $t]
        set pluginallow [lindex $pluginallowL $t]
        # If pluginallow list is too short
        if {$pluginallow eq ""} { set pluginallow 0 }
        incr t
        set pinterp [createPluginInterp $plugin $plugininfo $pluginallow pinfo]
        if {$pinterp eq ""} {
            # This should not happen since the validator should handle it
            puts "Bad plugin: $plugin"
            printPlugins
            exit
        }
        set opts(plugin,$t) $pinterp
        set opts(pluginname,$t) $plugin
        set opts(pluginallow,$t) $pluginallow
        set opts(plugininfo,$t) $plugininfo
        set opts(pluginpinfo,$t) $pinfo
    }

    # Store the command line given opts
    set ::eskil(defaultopts) [array get opts]

    # Do we start in clip diff mode?
    if {$doclip} {
        return [makeClipDiffWin]
    }

    # Do we start in fourway diff mode?
    if {$dofourway} {
        return [makeFourWayWin]
    }

    # Figure out if we start in a diff or dirdiff window.
    set len [llength $files]

    if {$len == 0 && $dodir} {
        set dirdiff(leftDir) ""
        set dirdiff(rightDir) ""
        return [makeDirDiffWin $noautodiff]
    }
    if { ! $doreview && $len == 1} {
        set fullname [lindex $files 0]
        if {[FileIsDirectory $fullname 1]} {
            set dirdiff(leftDir) $fullname
            set dirdiff(rightDir) $dirdiff(leftDir)
            return [makeDirDiffWin $noautodiff]
        }
    } elseif { ! $doreview && $len >= 2} {
        set fullname1 [lindex $files 0]
        set fullname2 [lindex $files 1]
        if {[FileIsDirectory $fullname1 1] && [FileIsDirectory $fullname2 1]} {
            set dirdiff(leftDir) $fullname1
            set dirdiff(rightDir) $fullname2
            return [makeDirDiffWin $noautodiff]
        }
    }

    # Ok, we have a normal diff
    set top [makeDiffWin]
    update

    # It is preferable to see the end if the rev string is too long
    $::widgets($top,rev1) xview end
    $::widgets($top,rev2) xview end

    if {$doreview} {
        set rev [detectRevSystem "" $preferedRev]
        set ::eskil($top,modetype) $rev
        set ::eskil($top,mode) "patch"
        set ::eskil($top,patchFile) ""
        set ::eskil($top,patchData) ""
        set ::eskil($top,reviewFiles) $files
        set ::Pref(toolbar) 1
        after idle [list doDiff $top]
        return $top
    }
    if {$len == 1 || $foreachOpt} {
        set ReturnAfterLoop 0
        set first 1
        foreach file $files {
            if {$first} {
                set first 0
            } else {
                # Create new window for other files
                set top [makeDiffWin $top]
                update
                # It is preferable to see the end if the rev string is too long
                $::widgets($top,rev1) xview end
                $::widgets($top,rev2) xview end
            }
            set fullname $file
            set fulldir [file dirname $fullname]
            if {$::eskil($top,mode) eq "conflict"} {
                startConflictDiff $top $fullname
                after idle [list doDiff $top]
                set ReturnAfterLoop 1
                continue
            }
            if { ! $dopatch} {
                # Check for revision control
                set rev [detectRevSystem $fullname $preferedRev]
                if {$rev ne ""} {
                    startRevMode $top $rev $fullname
                    if {$noautodiff} {
                        enableRedo $top
                    } else {
                        after idle [list doDiff $top]
                    }
                    set ReturnAfterLoop 1
                    continue
                }
            }
            # No revision control. Is it a patch file?
            set ::eskil($top,leftDir) $fulldir
            set ::eskil($top,leftFile) $fullname
            set ::eskil($top,leftLabel) $fullname
            set ::eskil($top,leftOK) 1
            if {$dopatch                                 || \
                    [regexp {\.(diff|patch)$} $fullname] || \
                    $fullname eq "-"} {
                set ::eskil($top,mode) "patch"
                set ::eskil($top,patchFile) $fullname
                set ::eskil($top,patchData) ""
                set autobrowse 0
                if {$noautodiff} {
                    enableRedo $top
                } else {
                    after idle [list doDiff $top]
                }
                set ReturnAfterLoop 1
                continue
            }
        }
        if {$ReturnAfterLoop} {return $top}
    } elseif {$len >= 2} {
        if {$len % 2 != 0} {
            puts "I see $len files. It must an even number."
            exit
        }
        set first 1
        foreach {file1 file2} $files {
            if {$first} {
                set first 0
            } else {
                # Create new window for other files
                set top [makeDiffWin $top]
                update
            }

            set fullname [file join [pwd] $file1]
            set fulldir [file dirname $fullname]
            set ::eskil($top,leftDir) $fulldir
            set ::eskil($top,leftFile) $fullname
            set ::eskil($top,leftLabel) $fullname
            set ::eskil($top,leftOK) 1
            set fullname [file join [pwd] $file2]
            set fulldir [file dirname $fullname]
            set ::eskil($top,rightDir) $fulldir
            set ::eskil($top,rightFile) $fullname
            set ::eskil($top,rightLabel) $fullname
            set ::eskil($top,rightOK) 1
            if {$noautodiff} {
                enableRedo $top
            } else {
                after idle [list doDiff $top]
            }
        }
    }
    if {$autobrowse && (!$::eskil($top,leftOK) || !$::eskil($top,rightOK))} {
        if { ! $::eskil($top,leftOK) && !$::eskil($top,rightOK)} {
            openBoth $top 0
        } elseif { ! $::eskil($top,leftOK)} {
            openLeft $top
        } elseif { ! $::eskil($top,rightOK)} {
            openRight $top
        }
        # If we cancel the second file and detect CVS, ask about it.
        # TBD: Extend this to all VCS:s?
        if {$::eskil($top,leftOK) && !$::eskil($top,rightOK) && \
                [llength [glob -nocomplain [file join $fulldir CVS]]]} {

            if {[tk_messageBox -title Diff -icon question \
                    -message "Do CVS diff?" -type yesno] eq "yes"} {
                set fullname $::eskil($top,leftFile)
                set ::eskil($top,leftOK) 0
                startRevMode $top "CVS" $fullname
                after idle [list doDiff $top]
            }
        }
    }
    return $top
}

# Save options to file ~/.eskilrc
proc saveOptions {top} {
    # Is this a diff win or some other win?
    if {[info exists ::widgets($top,wDiff1)]} {
        # Check if the window size has changed
        set w $::widgets($top,wDiff1)
        if {[winfo reqwidth $w] != [winfo width $w] || \
                    [winfo reqheight $w] != [winfo height $w]} {
            set dx [expr {[winfo width $w] - [winfo reqwidth $w]}]
            set dy [expr {[winfo height $w] - [winfo reqheight $w]}]
            set cx [font measure myfont 0]
            set cy [font metrics myfont -linespace]
            set neww [expr {[$w cget -width]  + $dx / $cx}]
            set newh [expr {[$w cget -height] + $dy / $cy}]
            if {$neww != $::Pref(linewidth) || $newh != $::Pref(lines)} {
                set msg "Should I save the current window\
                    size with the preferences?\nCurrent: $neww x $newh  Old:\
                    $::Pref(linewidth) x $::Pref(lines)"
                set apa [tk_messageBox -title "Save Preferences" \
                                 -icon question -type yesno -message $msg]
                if {$apa == "yes"} {
                    set ::Pref(linewidth) $neww
                    set ::Pref(lines) $newh
                }
            }
        }
    }

    set rcfile "~/.eskilrc"
    if {[catch {set ch [open $rcfile "w"]} err]} {
        tk_messageBox -icon error -title "File error" -message \
                "Error when trying to save preferences:\n$err"
        return
    }

    foreach i [array names ::Pref] {
        set value $::Pref($i)
        # Special handling for preprocess
        if {$i eq "preprocessn"} {
            set value [getPreprocessSave]
        }
        # Skip unchanged options.
        if {[info exists ::DefaultPref($i)]} {
            if {$::DefaultPref($i) eq $value} {
                continue
            }
            puts $ch "# $i default : $::DefaultPref($i)"
        }
        puts $ch [list set "::Pref($i)" $value]
    }
    close $ch

    tk_messageBox -icon info -title "Saved" -message \
            "Preferences saved to:\n[file nativename $rcfile]"
}

proc getOptions {} {
    if {$::tcl_platform(os) eq "Darwin"} {
        set ::DefaultPref(fontsize) 10
    } else {
        set ::DefaultPref(fontsize) 8
    }
    # Maybe base default font on TkFixedFont ?
    set ::DefaultPref(fontfamily) Courier
    set ::DefaultPref(ignore) "-b"
    set ::DefaultPref(nocase) 0
    set ::DefaultPref(noempty) 0
    set ::DefaultPref(pivot) 10
    set ::DefaultPref(nodigit) 0
    set ::DefaultPref(parse) 2
    set ::DefaultPref(lineparsewords) 0
    set ::DefaultPref(colorequal) ""
    set ::DefaultPref(colorchange) red
    set ::DefaultPref(colornew1) darkgreen
    set ::DefaultPref(colornew2) blue
    set ::DefaultPref(bgequal) ""
    set ::DefaultPref(bgchange) \#ffe0e0
    set ::DefaultPref(bgnew1) \#a0ffa0
    set ::DefaultPref(bgnew2) \#e0e0ff
    set ::DefaultPref(context) -1
    set ::DefaultPref(finegrainchunks) 0
    set ::DefaultPref(marklast) 1
    set ::DefaultPref(linewidth) 80
    set ::DefaultPref(lines) 60
    set ::DefaultPref(editor) "" ;# Not settable in GUI yet
    set ::DefaultPref(preprocessn) {}
    set ::DefaultPref(toolbar) 0
    set ::DefaultPref(wideMap) 0 ;# Not settable in GUI yet
    set ::DefaultPref(askOverwrite) 0 ;# Not settable in GUI yet

    # Print options
    set ::DefaultPref(printHeaderSize) 10
    set ::DefaultPref(printLineSpace) 1.0
    set ::DefaultPref(printCharsPerLine) 80
    set ::DefaultPref(printPaper) a4
    set ::DefaultPref(printColorChange) "1.0 0.7 0.7"
    set ::DefaultPref(printColorNew1)   "0.7 1.0 0.7"
    set ::DefaultPref(printColorNew2)   "0.8 0.8 1.0"
    set ::DefaultPref(printFont) "" ;# Not settable in GUI yet (-printFont)

    # Directory diff options
    set ::DefaultPref(dir,comparelevel) 1
    set ::DefaultPref(dir,ignorekey) 0
    set ::DefaultPref(dir,incfiles) ""
    set ::DefaultPref(dir,exfiles) "*.o"
    set ::DefaultPref(dir,incdirs) ""
    set ::DefaultPref(dir,exdirs) "RCS CVS .git .svn .hg"
    set ::DefaultPref(dir,onlyrev) 0
    set ::DefaultPref(dir,nice) 1

    # Start with default preferences, before loading setup file
    array set ::Pref [array get ::DefaultPref]

    # Handle old option
    set ::Pref(preprocess) {}

    # TODO: implement filter option fully
    set ::eskil(filter) ""

    if { ! [info exists ::eskil_testsuite] && [file exists "~/.eskilrc"]} {
        safeLoad "~/.eskilrc" ::Pref
    }

    if {$::Pref(editor) ne ""} {
        set ::util(editor) $::Pref(editor)
    }

    # If the user's file has this old option, translate it to the new
    if {$::Pref(preprocess) ne ""} {
        lappend ::Pref(preprocessn) "old"
        lappend ::Pref(preprocessn) \
                [dict create preprocess $::Pref(preprocess) \
                         active 1 save 1]
    }
    array unset ::Pref preprocess

    # Set up reactions to some Pref settings
    if { ! [info exists ::widgets(toolbars)]} {
        set ::widgets(toolbars) {}
    }
    trace add variable ::Pref(toolbar) write TraceToolbar
}

proc TraceToolbar {args} {
    # FIXA: Handle destroyed windows ?
    foreach __ $::widgets(toolbars) {
        if {$::Pref(toolbar)} {
            grid configure $__
        } else {
            grid remove $__
        }
    }
}

# Global code is only run the first time to be able to reread source
if { ! [info exists ::eskil(gurkmeja)]} {
    set ::eskil(gurkmeja) 1
    set ::eskil(plugins) {}

    package require pstools
    namespace import -force pstools::*
    getOptions
    if { ! [info exists ::eskil_testsuite]} {
        InitSourceEarly
        parseCommandLine
    }
}

Added src/vcsvfs.tcl.































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
#----------------------------------------------------------------------
#  Virtual File System for Version Control Systems
#
#  Copyright (c) 2014-2015, Peter Spjuth
#
#  License for vcsvfs package: Same as for Tcl
#----------------------------------------------------------------------

package require vfs
package provide vcsvfs 0.2

namespace eval vcsvfs {
    variable DataRefChan
    variable mpoints {}
    namespace eval fossil {}
    namespace eval svn {}
    namespace eval git {}
    namespace eval hg {}
}

# Create a command which when eval'd recreates known file systems
proc vcsvfs::serialize {} {
    variable ::vcsvfs::mpoints
    return [list vcsvfs::deserialize $mpoints]
}

# Pick up the command created by serialize
proc vcsvfs::deserialize {data} {
    variable ::vcsvfs::mpoints
    dict for {key value} $data {
        dict set mpoints $key $value
        # Handle if this is done again, do not mount it twice
        if {[string match *vcsvfs* [file system $key]]} {
            continue
        }
        vfs::filesystem mount $key [list vcsvfs::Vfs]
    }
}

# Create a Virtual File System showing a revision of a fossil checkout
#
# dir: Directory in a fossil checkout
# rev: Revision to mount
#
# Returns: path to the generated VFS
proc vcsvfs::fossil::mount {dir rev} {
    variable ::vcsvfs::mpoints
    set dir [file normalize $dir]

    # Fossil command must be run within the dir, so temporarily change pwd
    set oldpwd [pwd]
    cd $dir

    # The mount point will always be at the fossil root, even if
    # a sub directory was given.
    # Locate fossil root for the given directory.
    set info [exec fossil info]
    regexp -line {local-root:\s*(\S.*)} $info -> root
    set root [file normalize $root]
    cd $root

    # Getting files via manifest artifact
    # This is a quick and robust way to get the file tree and each file's sha
    # Other info is trickier and is handled below
    if {[catch {exec fossil artifact $rev} artifact]} {
        return -code error "No such fossil revision: $rev"
    }
    set commitTime 0
    set cTime now
    set finfo {}
    set todo [split $artifact \n]
    while {[llength $todo] > 0} {
        set line [lindex $todo 0]
        set todo [lrange $todo 1 end]
        # Expected format in a line:
        # B baseline
        # F tests/left.txt c1572b3809a1ba6ab2de9307c96b1cfeefdcf0ba
        # D 2015-02-23T23:30:07.509
        if {[regexp {^B (.*)} $line -> bUuid]} {
            # Pick up a baseline manifest and parse it first
            set artifact [exec fossil "artifact" $bUuid]
            set todo [concat [split $artifact \n] $todo]
            continue
        }
        if {[regexp {^D (.*)} $line -> cTime]} {
            # Remove decimals and middle T
            regsub {\.\d+} $cTime "" cTime
            regsub {T} $cTime " " cTime
            set commitTime [clock scan $cTime -gmt 1]
        }
        if {[regexp {^F (\S+) (\S+)} $line -> fName fSha]} {
            # File names can have spaces, coded with \s
            set fName [string map {\\s " "} $fName]
            dict set finfo $fName sha $fSha
            dict set finfo $fName mtimestr $cTime ;# Anything
            dict set finfo $fName type file
            dict set finfo $fName isfile 1
            dict set finfo $fName isdir 0
            # Setting size is delayed until needed since the needed
            # calls are relatively expensive.

            # Mark all known directory paths and build up file tree info
            set parentStr ""
            foreach dirPath [file split $fName] {
                dict set finfo $parentStr child $dirPath 1
                dict set finfo $parentStr isfile 0
                dict set finfo $parentStr isdir 1
                dict set finfo $parentStr type directory
                set parentStr [file join $parentStr $dirPath]
            }
        }
    }
    # Try to use "fossil ls -r, available in newer versions"
    set doneCollecting 0
    if { ! [catch {exec fossil ls -r $rev -v} lsdata]} {
        set lsdata [string trim $lsdata \n]
        foreach line [split $lsdata \n] {
            # Expected format in a line:
            # 2012-08-21 20:38:19  4563  tests/rev.test
            regexp {(\S+ \S+)\s+(\d+)\s+(.+)} $line -> fDate fSize fName
            dict set finfo $fName mtimestr $fDate
            dict set finfo $fName size $fSize
        }
        set doneCollecting 1
    }

    # Getting files via http fileage to aquire file times
    # Since dates are parsed from the age string they are rather imprecise
    # Use a while around it to be able to break free easily (faking goto)
    while { ! $doneCollecting} {
        set html [exec fossil http << "GET /fileage?name=$rev"]
        if { ! [regexp {Files in.*} $html html]} {
            # Not the expected format of response, skip
            break
        }
        if { ! [regexp {\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}} $html cTime2]} {
            # Not the expected format of response, skip
            break
        }
        # This is currently unused since we do not trust the formatted time in
        # the web page. The time stamp from the artifact is used later.
        set commitTime2 [clock scan $cTime2 -gmt 1]
        #puts "CT $commitTime  CT2 $commitTime2"
        # Rows in the HTML table
        foreach row [regexp -all -inline {<tr>.*?</tr>} $html] {
            # Columns in the HTML table
            set cols [regexp -all -inline {<td>(.*?)</td>} $row]
            set col1 [string trim [lindex $cols 1]]
            set col2 [string trim [lindex $cols 3]]
            # First column is age, in readable format
            # e.g. "current" "36.4 minutes" "97.0 days" "1.06 years"
            if {$col1 eq ""} continue
            if {$col1 eq "current"} {
                set fTime $commitTime
                set err 0
            } else {
                set value [lindex $col1 0]
                set unit [lindex $col1 1]
                set err 0
                switch -glob $unit {
                    second* {
                        set value [expr {int($value)}]
                        set unit second
                        set err 0
                    }
                    minute* {
                        # In general, try to underestimate the value. The web
                        # page rounds to one decimal.
                        set value [expr {int(($value-0.05)*60)}]
                        set unit second
                        set err 6
                    }
                    hour* {
                        set value [expr {int(($value-0.05)*60*60)}]
                        set unit second
                        set err 360
                    }
                    day* {
                        set value [expr {int(($value-0.05)*60*60*24)}]
                        set unit second
                        set err 8640
                    }
                    year* {
                        # Year has two decimals
                        set value [expr {int(($value-0.005)*60*60*24*365)}]
                        set unit second
                        set err [expr {6*60*24*365}]
                    }
                    default {
                        puts "Unhandled unit: $unit in '$col1'"
                        set value [expr {int($value)}]
                    }
                }
                set fTime [expr {$commitTime - $value}]
            }
            #puts "AGE $col1 -> $fTime"

            # Second column is file names, separated by <br>
            # Remove links
            regsub -all {<a .*?>} $col2 "" col2
            regsub -all {</a>} $col2 "" col2
            regsub -all {\n} $col2 "" col2
            regsub -all {<br>} $col2 "\n" col2
            set col2 [string trim $col2]
            foreach fName [split $col2 \n] {
                # Check that it matches something filled in from the artifact
                if {[dict exists $finfo $fName]} {
                    dict set finfo $fName mtime $fTime
                    # Store error estimate for debug help
                    dict set finfo $fName errX $err
                }
            }
        }
        # Kill surrounding while loop
        break
    }

    # As another step, get current file stamps from fossil ls.
    # Since ls show current checkout they might not be valid for the rev
    # being looked at. However if they are still present and older than the
    # ones from fileage they are likely correct.
    # Also, fileage and ls uses different criteria for which commit defines
    # the age (across merges), so things basically will be a best effort guess.
    if { ! $doneCollecting} {
        set allfiles [exec fossil ls --age .]
        foreach line [split $allfiles \n] {
            # Expected format in a line:
            # 2012-08-21 20:38:19  tests/rev.test
            regexp {(\S+ \S+)\s+(.+)} $line -> fDate fName
            set mTime [clock scan $fDate -gmt 1]
            if {[dict exists $finfo $fName mtime]} {
                set x [dict get $finfo $fName mtime]
                set e [dict get $finfo $fName errX]
                if {$mTime < $x} {
                    dict set finfo $fName mtime $mTime
                } elseif {abs($mTime - $x) < 3600} {
                    #puts "$fName   age $x   ls $mTime  diff [expr {$mTime - $x}]  err $e"
                }
            }
        }
    }

    cd $oldpwd

    # Generate a mount point.
    set tail [string range $dir [string length $root] end]
    set mountpoint "${root} ($rev)"

    dict set mpoints $mountpoint "finfo" $finfo
    dict set mpoints $mountpoint "origroot" $root
    dict set mpoints $mountpoint "rev" $rev
    dict set mpoints $mountpoint "vcstype" fossil
    vfs::filesystem mount $mountpoint [list vcsvfs::Vfs]

    set result $mountpoint$tail
    #puts $result
    #puts [dict size $finfo]
    #puts [dict get $finfo [lindex $finfo 0]]
    return $result
}

proc vcsvfs::fossil::unmount {dir} {
    variable ::vcsvfs::mpoints
    # TBD: Find the mountpoint
    #dict unset mpoints $mountpoint
    #vfs::filesystem unmount $mountpoint
}

# Create a Virtual File System showing a revision of an SVN checkout
#
# dir: Directory in an SVN checkout
# rev: Revision to mount
#
# Returns: path to the generated VFS
proc vcsvfs::svn::mount {dir rev} {
    variable ::vcsvfs::mpoints
    set dir [file normalize $dir]

    # Command must be run within the dir, so temporarily change pwd
    set oldpwd [pwd]
    cd $dir

    # The mount point will normally be at the wc root, even if
    # a sub directory was given.
    # Locate root for the given directory.
    set info [exec svn info]
    if { ! [regexp -line {Working Copy Root Path:\s*(\S.*)} $info -> root]} {
        # Fallback to given dir
        set root .
    }
    # TBD: Always root at given dir, for speed
    set root .
    set root [file normalize $root]
    cd $root

    # Getting files via ls
    set allfiles [exec svn ls -R -r $rev]
    foreach line [split $allfiles \n] {
        # Each line is one file/dir name
        set fName $line
        if {[string index $fName end] eq "/"} {
            # This is a directory, strip the /
            set fName [string range $fName 0 end-1]
            dict set finfo $fName isfile 0
            dict set finfo $fName isdir 1
            dict set finfo $fName type directory
        } else {
            # This is a file
            dict set finfo $fName isfile 1
            dict set finfo $fName isdir 0
            dict set finfo $fName type file
        }
        # Mark all known directory paths and build up file tree info
        set parentStr ""
        foreach dirPath [file split $fName] {
            dict set finfo $parentStr child $dirPath 1
            dict set finfo $parentStr isfile 0
            dict set finfo $parentStr isdir 1
            dict set finfo $parentStr type directory
            set parentStr [file join $parentStr $dirPath]
        }
    }

    set xml [exec svn ls -R -r $rev --xml]
    # TBD real xml parser
    foreach line [split $xml \n] {
        if {[regexp {<name>(.*)</name>} $line -> fName]} {
            continue
        }
        if {[regexp {<date>(.*)</date>} $line -> fDate]} {
            dict set finfo $fName mtimestr $fDate
            continue
        }
        if {[regexp {<size>(.*)</size>} $line -> fSize]} {
            dict set finfo $fName size $fSize
            continue
        }
    }

    cd $oldpwd

    # Generate a mount point.
    set tail [string range $dir [string length $root] end]
    set mountpoint "${root} ($rev)"

    dict set mpoints $mountpoint "finfo" $finfo
    dict set mpoints $mountpoint "origroot" $root
    dict set mpoints $mountpoint "rev" $rev
    dict set mpoints $mountpoint "vcstype" svn
    vfs::filesystem mount $mountpoint [list vcsvfs::Vfs]

    set result $mountpoint$tail
    #puts $result
    #puts [dict size $finfo]
    #puts [dict get $finfo [lindex $finfo 0]]
    return $result
}

proc vcsvfs::svn::unmount {dir} {
    variable ::vcsvfs::mpoints
    # TBD: Find the mountpoint
    #dict unset mpoints $mountpoint
    #vfs::filesystem unmount $mountpoint
}

# Create a Virtual File System showing a revision of a HG checkout
#
# dir: Directory in an HG checkout
# rev: Revision to mount
#
# Returns: path to the generated VFS
proc vcsvfs::hg::mount {dir rev} {
    variable ::vcsvfs::mpoints
    set dir [file normalize $dir]

    # Command must be run within the dir, so temporarily change pwd
    set oldpwd [pwd]
    cd $dir

    # The mount point will normally be at the wc root, even if
    # a sub directory was given.
    # Locate root for the given directory.
    set root [exec hg root]
    # TBD: Always root at given dir, for speed
    #set root .
    #set root [file normalize $root]
    #cd $root

    # Getting files via manifest
    set allfiles [exec hg manifest --debug -r $rev]
    # Expected line format:
    # sha1sum perms *? name
    foreach line [split $allfiles \n] {
        # Each line is one file name
        regexp {^(\S+)\s+\S+\s+\*?\s*(\S.*)$} $line -> sha fName
        dict set finfo $fName isfile 1
        dict set finfo $fName isdir 0
        dict set finfo $fName "sha" $sha
        dict set finfo $fName type file
        # Fake mtime and size from sha, to make same look same
        dict set finfo $fName mtime [scan [string range $sha 0 6] %x]
        dict set finfo $fName size  [scan [string range $sha 7 9] %x]
        # Mark all known directory paths and build up file tree info
        set parentStr ""
        foreach dirPath [file split $fName] {
            dict set finfo $parentStr child $dirPath 1
            dict set finfo $parentStr isfile 0
            dict set finfo $parentStr isdir 1
            dict set finfo $parentStr type directory
            set parentStr [file join $parentStr $dirPath]
        }
    }
    # TBD: Any way to get file sizes and mtimes from HG?

    # Try using the hglist extension
    set cmd [list hg ls --template "{size} {date} {name}\n" -a \
                     --recursive -r $rev]
    if { ! [catch {exec {*}$cmd} allfiles]} {
        # Expected line format:
        # size date name
        foreach line [split $allfiles \n] {
            if {[regexp {^(\d+)\s+(\d+)\S*\s+(\S.*)$} $line -> size mtime fName]} {
                # Check that it matches something filled in from the manifest
                if {[dict exists $finfo $fName]} {
                    dict set finfo $fName "mtime" $mtime
                    dict set finfo $fName "size"  $size
                }
            }
        }
    }

    cd $oldpwd

    # Generate a mount point.
    set tail [string range $dir [string length $root] end]
    set mountpoint "${root} ($rev)"

    dict set mpoints $mountpoint "finfo" $finfo
    dict set mpoints $mountpoint "origroot" $root
    dict set mpoints $mountpoint "rev" $rev
    dict set mpoints $mountpoint "vcstype" hg
    vfs::filesystem mount $mountpoint [list vcsvfs::Vfs]

    set result $mountpoint$tail
    return $result
}

proc vcsvfs::hg::unmount {dir} {
    variable ::vcsvfs::mpoints
    # TBD: Find the mountpoint
    #dict unset mpoints $mountpoint
    #vfs::filesystem unmount $mountpoint
}

# Create a Virtual File System showing a revision of a GIT checkout
#
# dir: Directory in an GIT checkout
# rev: Revision to mount
#
# Returns: path to the generated VFS
proc vcsvfs::git::mount {dir rev} {
    variable ::vcsvfs::mpoints
    set dir [file normalize $dir]

    # Command must be run within the dir, so temporarily change pwd
    set oldpwd [pwd]
    cd $dir

    # The mount point will be at the given dir
    set root $dir

    # Getting files via ls
    set allfiles [exec git ls-tree -r --long $rev .]
    foreach line [split $allfiles \n] {
        # Each line is:
        # <mode> SP <type> SP <object> SP <object size> TAB <file>
        regexp {(\S)+\s+(\S+)\s+(\S+)\s+(\S+)\t(.*)} $line -> \
                mode type sha size fName
        # TBD: check mode to see a link
        if {$type eq "tree"} {
            dict set finfo $fName isfile 0
            dict set finfo $fName isdir 1
            dict set finfo $fName "type" directory
        } else {
            # This is a file
            dict set finfo $fName isfile 1
            dict set finfo $fName isdir 0
            dict set finfo $fName "type" file
            dict set finfo $fName "sha" $sha
            dict set finfo $fName "size" $size
            # TBD: Delay this call until mtime is needed?
            set mtime [exec git log --pretty=format:%ct -n 1 $rev -- $fName]
            dict set finfo $fName "mtime" $mtime
        }
        # Mark all known directory paths and build up file tree info
        set parentStr ""
        foreach dirPath [file split $fName] {
            dict set finfo $parentStr child $dirPath 1
            dict set finfo $parentStr isfile 0
            dict set finfo $parentStr isdir 1
            dict set finfo $parentStr "type" directory
            set parentStr [file join $parentStr $dirPath]
        }
    }

    cd $oldpwd

    # Generate a mount point.
    set tail [string range $dir [string length $root] end]
    set mountpoint "${root} ($rev)"

    dict set mpoints $mountpoint "finfo" $finfo
    dict set mpoints $mountpoint "origroot" $root
    dict set mpoints $mountpoint "rev" $rev
    dict set mpoints $mountpoint "vcstype" git
    vfs::filesystem mount $mountpoint [list vcsvfs::Vfs]

    set result $mountpoint$tail
    #puts $result
    #puts [dict size $finfo]
    #puts [dict get $finfo [lindex $finfo 0]]
    return $result
}

proc vcsvfs::git::unmount {dir} {
    variable ::vcsvfs::mpoints
    # TBD: Find the mountpoint
    #dict unset mpoints $mountpoint
    #vfs::filesystem unmount $mountpoint
}

# Handler for Reflected Channel
proc vcsvfs::DataRefChan {id cmd chId args} {
    variable DataRefChan
    switch $cmd {
        initialize {
            set mode [lindex $args 0]
            return "initialize finalize watch read"
        }
        finalize {
            unset DataRefChan($id,data)
            return
        }
        watch {
            #set eventSpec [lindex $args 0]
            return
        }
        read {
            set count [lindex $args 0]
            set si $DataRefChan($id,ptr)
            set newPtr [expr {$si + $count}]
            set ei [expr {$newPtr - 1}]
            set data [string range $DataRefChan($id,data) $si $ei]
            set DataRefChan($id,ptr) $newPtr
            return $data
        }
    }
}

# Set up a Reflected Channel which reads the provided data
proc vcsvfs::CreateDataRefChan {data} {
    variable DataRefChan
    set t 0
    while {[info exists DataRefChan($t,data)]} {
        incr t
    }
    set DataRefChan($t,data) $data
    set DataRefChan($t,ptr) 0
    set chId [chan create r [list vcsvfs::DataRefChan $t]]
    return $chId
}

# This is used before closing a pipe from a command.
# It should read all data to avoid errors from the command.
proc vcsvfs::ReadAllBeforeClose {chId} {
    read $chId
}

# Helper for glob matching in directory
proc vcsvfs::MatchInDirectory {finfo relative actual args} {
    set pattern [lindex $args 0]
    set types [lindex $args 1]
    set allowFile 0
    set allowDir 0
    if {[vfs::matchDirectories $types]} {set allowDir 1}
    if {[vfs::matchFiles $types]} {set allowFile 1}

    set result {}
    if {[dict exists $finfo $relative child]} {
        set childD [dict get $finfo $relative child]
    } else {
        # Empty dir
        return {}
    }
    foreach child [dict keys $childD] {
        if { ! [string match $pattern $child]} continue
        set local [file join $relative $child]
        if {[dict get $finfo $local isfile] && !$allowFile} continue
        if {[dict get $finfo $local isdir] && !$allowDir} continue
        lappend result [file join $actual $child]
    }
    return $result
}

# Extract file data from Fossil revision
proc vcsvfs::fossil::openFile {rootD relative mode} {
    set oldpwd [pwd]
    cd [dict get $rootD "origroot"]
    set rev [dict get $rootD rev]
    # Which way of extracting file data is best?
    # fossil finfo -p -r $rev $relative
    # set sha [dict get $finfor sha]
    # fossil artifact $sha
    # fossil cat $relative -r $rev
    # Read through a pipe to get requested mode
    set chId [open [list |fossil cat $relative -r $rev] $mode]
    cd $oldpwd

    return [list $chId [list vcsvfs::ReadAllBeforeClose $chId]]
}

# Extract file data from Subversion revision
proc vcsvfs::svn::openFile {rootD relative mode} {
    set oldpwd [pwd]
    cd [dict get $rootD "origroot"]
    set rev [dict get $rootD rev]
    # Read through a pipe to get requested mode
    set chId [open [list |svn cat -r $rev $relative] $mode]
    cd $oldpwd

    return [list $chId [list vcsvfs::ReadAllBeforeClose $chId]]
}

# Extract file data from HG revision
proc vcsvfs::hg::openFile {rootD relative mode} {
    set oldpwd [pwd]
    cd [dict get $rootD "origroot"]
    set rev [dict get $rootD rev]
    # Read through a pipe to get requested mode
    set chId [open [list |hg cat -r $rev $relative] $mode]
    cd $oldpwd

    return [list $chId [list vcsvfs::ReadAllBeforeClose $chId]]
}

# Some notes about git commands that can be good to have
proc vcsvfs::git::openFile {rootD relative mode} {
    set oldpwd [pwd]
    cd [dict get $rootD "origroot"]
    set sha [dict get $rootD finfo $relative sha]
    #git cat-file
    #git show <rev>^{tree}
    # example: git show HEAD^^^:apa

    # Read through a pipe to get requested mode
    set chId [open [list |git cat-file blob $sha] $mode]
    cd $oldpwd

    return [list $chId [list vcsvfs::ReadAllBeforeClose $chId]]
}

# Fossil may delay filling in size, this takes car of that
proc vcsvfs::fossil::size {finfo} {
    # Use "fossil whatis" on its sha
    # Expected format in a line:
    # size:    629 bytes
    set whatis [exec fossil whatis [dict get $finfo sha]]
    regexp {size:\s+(\d+)} $whatis -> fSize
    return $fSize
}

# Parse a time string from Fossil
proc vcsvfs::fossil::mTime {finfo} {
    set mtimestr [dict get $finfo mtimestr]
    # TBD parse to mtime correct?
    set mtime [clock scan $mtimestr -gmt 1]
    return $mtime
}

# Parse a time string from Subversion
proc vcsvfs::svn::mTime {finfo} {
    set mtimestr [dict get $finfo mtimestr]
    # TBD parse to mtime correct?
    # Remove any decimals from time string
    regsub {\.\d+Z} $mtimestr "" mtimestr
    set mtime [clock scan $mtimestr -gmt 1]
    return $mtime
}

# The handler for the mounted VFS
proc vcsvfs::Vfs {subcmd root relative actual args} {
    variable mpoints
    #puts "\nVfs called:"
    #puts " Su $subcmd"
    #puts " Ro $root"
    #puts " Re $relative"
    #puts " Ac $actual"
    #puts " Ar $args"

    set rootD [dict get $mpoints $root]
    set origroot [dict get $rootD origroot]
    set finfo [dict get $rootD finfo]
    set vcstype [dict get $rootD vcstype]

    if { ! [dict exists $finfo $relative]} {
        # Unknown path
        vfs::filesystem posixerror $::vfs::posix(EACCES)
        return -code error $::vfs::posix(EACCES)
    }
    set finfor [dict get $finfo $relative]
    #puts " $finfor"

    switch $subcmd {
        access {
            set mode [vfs::accessMode [lindex $args 0]]
            # Only read supported
            if {$mode ne "R"} {
                vfs::filesystem posixerror $::vfs::posix(EACCES)
                return -code error $::vfs::posix(EACCES)
            }
            return
        }
        fileattributes {
            #set index [lindex $args 0]
            #set value [lindex $args 1]
            return
        }
        matchindirectory {
            return [vcsvfs::MatchInDirectory $finfo $relative $actual {*}$args]
        }
        open {
            set mode [lindex $args 0]
            if {$mode == {}} {set mode r}
            #set permissions [lindex $args 1]
            if {$mode ne "r" && $mode ne "rb"} {
                # Read only
                vfs::filesystem posixerror $::vfs::posix(EACCES)
                return -code error $::vfs::posix(EACCES)
            }

            return [vcsvfs::${vcstype}::openFile $rootD $relative $mode]
        }
        stat {
            set res [dict create dev 0 ino 0 "mode" 0 nlink 0 uid 0 gid 0 \
                             size 0 atime 0 mtime 0 ctime 0 type file]
            dict set res type [dict get $finfor type]
            if {[dict get $finfor isfile]} {
                # Fill in any postponed info
                if { ! [dict exists $finfor mtime]} {
                    set mtime [vcsvfs::${vcstype}::mTime $finfor]
                    dict set finfor "mtime" $mtime
                    # Cache in main dictionary too
                    dict set mpoints $root "finfo" $relative "mtime" $mtime
                }
                if { ! [dict exists $finfor size]} {
                    set size [vcsvfs::${vcstype}::size $finfor]
                    dict set finfor "size" $size
                    # Cache in main dictionary too
                    dict set mpoints $root "finfo" $relative "size" $size
                }
                dict set res "mtime" [dict get $finfor "mtime"]
                dict set res "size"  [dict get $finfor "size"]
            }
            return $res
        }
        createdirectory - deletefile - removedirectory - utime {
            # Read-only, always error
        }
    }
    vfs::filesystem posixerror $::vfs::posix(EACCES)
    return -code error $::vfs::posix(EACCES)
}

##################################################################
# Test structure
##################################################################
if 0 {
# File traversing stuff from wiki...
proc ftw_1 {{dirs .}} {
    while {[llength $dirs]} {
        set dirs [lassign $dirs name]
        lappend dirs {*}[glob -nocomplain -directory $name -type d *]
        puts $name
    }
}
proc ls-l { dir } {
    # Get the current year, because the date format depends on it.
    set thisYear [clock format [clock seconds] -format %Y]
    # Walk the files in the given directory, accumulating lines
    # in $retval
    set retval {}
    set sep {}
    # In Tcl older than 8.3 use 'glob [file join $dir *]'
    foreach fileName [lsort [glob -dir $dir *]] {
        append retval $sep
        set sep \n
        # Get status of the file
        #file stat $fileName stat
        # use 'file lstat' instead: if the file is a symbolic link we don't want info about its target
        file lstat $fileName stat
        # Put in one character for file type.  Use - for a plain file.
        set type -
        if { [info exists stat(type)]
             && [string compare file $stat(type)] } {
            set type [string index $stat(type) 0]
        }
        append retval $type
        # Decode $stat(mode) into permissions the way that ls does it.
        foreach { mask pairs } {
            00400 { 00400 r }
            00200 { 00200 w }
            04100 { 04100 s 04000 S 00100 x }
            00040 { 00040 r }
            00020 { 00020 w }
            02010 { 02010 s 02000 S 00010 x }
            00004 { 00004 r }
            00002 { 00002 w }
            01001 { 01001 t 01000 T 00001 x }
        } {
            set value [expr $stat(mode) & $mask]
            set bit -
            foreach { x b } $pairs {
                if { $value == $x } {
                    set bit $b
                }
            }
            append retval $bit
        }
        # Put in link count, user ID, and size.  Note that the UID
        # will be numeric.  If you know how to back-translate this
        # from Tcl, please feel free to edit it in!
        # LV writes - use file userid and file groupid to convert the numbers back to names.
        #   I don't know what version of Tcl added those commands...
        append retval [format %4d $stat(nlink)] { }
        array set attribs [file attributes $fileName]
        if {[info exists attribs(-owner)]} {
            append retval [format %-8s $attribs(-owner)]
            append retval [format %-8s $attribs(-group)]
        } else {
            append retval [format %8d $stat(uid)]
            append retval [format %8d $stat(gid)]
        }
        append retval [format %9d $stat(size)]
        # Put in the date.  The current year is formatted differently
        # from prior years.
        set year [clock format $stat(mtime) -format "%Y"]
        if { $year == $thisYear } {
            set modified [clock format $stat(mtime) -format "%h %e %H:%M"]
        } else {
            set modified [clock format $stat(mtime) -format "%h %e  %Y"]
        }
        # glennj: see note below
        append retval { } $modified { }
        # Finally, put in the file name, stripping off the directory.
        append retval [file tail $fileName]
        if {[string compare $stat(type) link] == 0} {
            append retval " -> [file readlink $fileName]"
        }
        if {$type eq "-"} {
            set ch [open $fileName]
            set x [read $ch]
            set x [string range $x 0 4]
            close $ch
            append retval " = '$x'"
        }
        unset stat attribs
    }
    return $retval
}


set d [vcsvfs::fossil::mount ~/src/eskil f96b0fd915]
puts "------------- GLOB:"
puts [join [glob -dir $d *] \n]
puts "------------- FTW:"
ftw_1 [list $d]
puts "------------- LS:"
puts [ls-l $d]
}

Changes to tests/all.tcl.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17




18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/bin/sh
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------
# the next line restarts using tclsh \
exec tclsh8.5 "$0" "$@"

set testScript [file normalize [file join [pwd] [info script]]]
set testDir    [file dirname $testScript]

lappend auto_path eskil.vfs/lib

package require tcltest 2.2
namespace import tcltest::*
tcltest::configure -verbose "body error"
#testConstraint knownbug 1
#tcltest::configure -match print-*





package require Tk
wm withdraw .

set ::eskil_testsuite 1

if {[file exists src/eskil.tcl_i]} {
    puts "Running with code coverage"
    source src/eskil.tcl_i
} else {
    source src/eskil.tcl
}
Init

# Helpers to temporarily stub things out
set ::stubs {}
proc stub {name argv body} {
    if {[info commands _stub_$name] eq ""} {





|








|


>
>
>
>






|

|

|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/bin/sh
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------
# the next line restarts using tclsh \
exec tclsh "$0" "$@"

set testScript [file normalize [file join [pwd] [info script]]]
set testDir    [file dirname $testScript]

lappend auto_path eskil.vfs/lib

package require tcltest 2.2
namespace import tcltest::*
tcltest::configure -verbose "body error" -singleproc 1
#testConstraint knownbug 1
#tcltest::configure -match print-*

if {$argc > 0} {
    eval tcltest::configure $argv
}

package require Tk
wm withdraw .

set ::eskil_testsuite 1

if {[file exists eskil.vfs/src/startup.tcl_i]} {
    puts "Running with code coverage"
    source eskil.vfs/src/startup.tcl_i
} else {
    source eskil.vfs/src/startup.tcl
}
Init

# Helpers to temporarily stub things out
set ::stubs {}
proc stub {name argv body} {
    if {[info commands _stub_$name] eq ""} {
43
44
45
46
47
48
49


50
51
52
53
54
55
56

57
58
59
    foreach name $::stubs {
        rename $name {}
        rename _stub_$name $name
    }
    set ::stubs {}
}




puts "Running Tests"

foreach test [glob -nocomplain $testDir/*.test] {
    source $test
    clearstub
}

tcltest::cleanupTests 1

exit







>
>
|
<

|
|
<
|
>



47
48
49
50
51
52
53
54
55
56

57
58
59

60
61
62
63
64
    foreach name $::stubs {
        rename $name {}
        rename _stub_$name $name
    }
    set ::stubs {}
}

proc ExecEskil {args} {
    return [exec ./eskil.kit {*}$args]
}


tcltest::testsDirectory $testDir
tcltest::runAllTests


cleanupTestFile
tcltest::cleanupTests 1

exit

Changes to tests/blocks.test.

1
2
3
4
5
6
7
8
9
10
# Tests for comparing blocks.                               -*- tcl -*-
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------

test blocks-1.1 {
    Change-block parsing
} -body {
    set b1 [list "Apa 1" "Bepa 1" "Cepa 1"]
    set b2 [list "Apa 2" "Bepa 2" "Cepa 2"]
<
|
|








1
2
3
4
5
6
7
8
9

#------------------------------------------------------------*- tcl -*-
# Tests for comparing blocks.
#----------------------------------------------------------------------

test blocks-1.1 {
    Change-block parsing
} -body {
    set b1 [list "Apa 1" "Bepa 1" "Cepa 1"]
    set b2 [list "Apa 2" "Bepa 2" "Cepa 2"]

Added tests/cmdline.test.























>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
#------------------------------------------------------------*- tcl -*-
# Tests for command line.
#----------------------------------------------------------------------

test cmdline-1.1 {cmd line query} -body {
    set res [ExecEskil --query -]
} -match glob -result {*-browse *-help *-table *}

test cmdline-1.2 {cmd line query} -body {
    set res [ExecEskil --query -l]
} -result {-limit -line}

Changes to tests/dir.test.

1
2
3

4
5
6
7


8
9
10
11
12
13
14
# Tests for comparing directories.                          -*- tcl -*-
#----------------------------------------------------------------------
# $Revision$

#----------------------------------------------------------------------

set ::Pref(dir,ignorekey) 0
set ::Pref(dir,comparelevel) 1



proc testCompareFiles {text1 text2 {sametime 0}} {
    set ch [open _f1_ w]
    puts -nonewline $ch $text1
    close $ch

    set ch [open _f2_ w]
<
|
<
>




>
>








1

2
3
4
5
6
7
8
9
10
11
12
13
14
15

#------------------------------------------------------------*- tcl -*-

# Tests for comparing directories.
#----------------------------------------------------------------------

set ::Pref(dir,ignorekey) 0
set ::Pref(dir,comparelevel) 1
set ::eskil(.dirdiff,plugin) ""
set ::eskil(.dirdiff,dirPlugin) 0

proc testCompareFiles {text1 text2 {sametime 0}} {
    set ch [open _f1_ w]
    puts -nonewline $ch $text1
    close $ch

    set ch [open _f2_ w]
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
} -body {
    set ::Pref(dir,comparelevel) 0
    set res [testCompareFiles "abab" "baba"]
    # Different size
    append res [testCompareFiles "abab" "babax"]
    set ::Pref(dir,comparelevel) 1
    append res [testCompareFiles "abab" "baba"]
    # Same time
    append res [testCompareFiles "abab" "baba" 1]
} -result {11000011}

test dir-5.1 {
    CompareFiles, directories
} -body {
    touch _f1_
    file mkdir _d1_
    file mkdir _d2_







|

|







114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
} -body {
    set ::Pref(dir,comparelevel) 0
    set res [testCompareFiles "abab" "baba"]
    # Different size
    append res [testCompareFiles "abab" "babax"]
    set ::Pref(dir,comparelevel) 1
    append res [testCompareFiles "abab" "baba"]
    # Same time is not enough anymore
    append res [testCompareFiles "abab" "baba" 1]
} -result {11000000}

test dir-5.1 {
    CompareFiles, directories
} -body {
    touch _f1_
    file mkdir _d1_
    file mkdir _d2_

Changes to tests/gui.test.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27







28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#------------------------------------------------------------*- tcl -*-
# Tests for GUI
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------

lappend ::auto_path /home/peter/src/TkTest
package require TkTest
wm withdraw .

proc XauthSecure {} {
    global tcl_platform

    if {[string compare unix $tcl_platform(platform)]} {
	# This makes no sense outside of Unix
	return
    }
    set hosts [exec xhost]
    # the first line is info only
    foreach host [lrange [split $hosts \n] 1 end] {
	exec xhost -$host
    }
    exec xhost -
}
XauthSecure

proc RestartClient {args} {







    set ::clientfile ./eskil.kit
    #if {[file exists ${::clientfile}_i]} {
    #    set ::clientfile ${::clientfile}_i
    #}

    if {![catch {send -async Eskil exit}]} {
        update
        after 500
    }

    set slavepid [exec $::clientfile -server {*}$args &]

    after 1000
    while {[catch {tktest::init Eskil}]} {
        after 500
    }
    tktest::cmd wm geometry . +10+10




<
<

|




















>
>
>
>
>
>
>
|









|







1
2
3


4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#------------------------------------------------------------*- tcl -*-
# Tests for GUI
#----------------------------------------------------------------------



lappend ::auto_path /home/$::env(USER)/src/TkTest
package require TkTest
wm withdraw .

proc XauthSecure {} {
    global tcl_platform

    if {[string compare unix $tcl_platform(platform)]} {
	# This makes no sense outside of Unix
	return
    }
    set hosts [exec xhost]
    # the first line is info only
    foreach host [lrange [split $hosts \n] 1 end] {
	exec xhost -$host
    }
    exec xhost -
}
XauthSecure

proc RestartClient {args} {
    if {[file exists eskil.vfs/main.tcl_i]} {
        puts "Starting gui instrumented"
        set src eskil.vfs/main.tcl_i
    } else {
        set src eskil.vfs/main.tcl
    }
    set cmd [list tclkit $src]
    #set ::clientfile ./eskil.kit
    #if {[file exists ${::clientfile}_i]} {
    #    set ::clientfile ${::clientfile}_i
    #}

    if {![catch {send -async Eskil exit}]} {
        update
        after 500
    }

    set slavepid [exec {*}$cmd -server {*}$args &]

    after 1000
    while {[catch {tktest::init Eskil}]} {
        after 500
    }
    tktest::cmd wm geometry . +10+10

94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
}

test gui-1.1 {Run 3-way merge} -setup {
    set f1 [tcltest::makeFile {} _test1]
} -body {
    RestartClient -fine -a tests/ancestor.txt tests/left.txt tests/right.txt -o $f1
    tktest::waitFocus press Save
    tktest::waitFocus press No
    tktest::press Close
    catch {exec diff $f1 tests/merge.txt}
} -cleanup {
    tcltest::removeFile {} _test1
} -result {0}

catch {tktest::menu File Quit}
catch {send -async Eskil exit}
update







|









99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
}

test gui-1.1 {Run 3-way merge} -setup {
    set f1 [tcltest::makeFile {} _test1]
} -body {
    RestartClient -fine -a tests/ancestor.txt tests/left.txt tests/right.txt -o $f1
    tktest::waitFocus press Save
    tktest::waitFocus press Ok
    tktest::press Close
    catch {exec diff $f1 tests/merge.txt}
} -cleanup {
    tcltest::removeFile {} _test1
} -result {0}

catch {tktest::menu File Quit}
catch {send -async Eskil exit}
update

Changes to tests/patch.test.

1
2
3

4
5
6
7
8
9
10
11
12
13







14





15
16
17
18
19
20
21
22

23



24
25
26
27


















28
29
30
31
32
33
34
# Tests for patch file parsingunctions                      -*- tcl -*-
#----------------------------------------------------------------------
# $Revision$

#----------------------------------------------------------------------

# Overload exec during these tests
set ::diff(gurka,patchFile) ""
set ::diff(gurka,patchData) ""
stub update args {}
stub getFullPatch {top} {
    return $::testpatch
}
stub displayOnePatch {top leftLines rightLines leftLine rightLine} {







}





stub emptyLine {top n {highlight 1}} {
    incr ::_patchfiles
}
stub insertLine {top n line text {tag {equal}} {linetag {}}} {
}
stub addChange {top n tag line1 n1 line2 n2} {}

proc _PatchInit {} {

    set ::_patchfiles 0




}
test patch-1.1 {
    Xxx


















} -body {
    _PatchInit
    set ::testpatch {
Index: vhdl/tb/tb_system.bhv
===================================================================
--- vhdl/tb/tb_system.bhv	(revision 320)
+++ vhdl/tb/tb_system.bhv	(working copy)
<
|
<
>



|
|





>
>
>
>
>
>
>
|
>
>
>
>
>

|






>
|
>
>
>
|
|


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>








1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

#------------------------------------------------------------*- tcl -*-

# Tests for patch file parsing functions
#----------------------------------------------------------------------

# Overload exec during these tests
set ::eskil(gurka,patchFile) ""
set ::eskil(gurka,patchData) ""
stub update args {}
stub getFullPatch {top} {
    return $::testpatch
}
stub displayOnePatch {top leftLines rightLines leftLine rightLine} {
    # Line per patch
    lappend ::_patchfiles(pll) $leftLine
    lappend ::_patchfiles(prl) $rightLine
    # All lines
    foreach l $leftLines {
        lassign $l lline lmode lstr
        lappend ::_patchfiles(ll) $lline
    }
    foreach l $rightLines {
        lassign $l lline lmode lstr
        lappend ::_patchfiles(rl) $lline
    }
}
stub emptyLine {top n {highlight 1}} {
    incr ::_patchfiles(e)
}
stub insertLine {top n line text {tag {equal}} {linetag {}}} {
}
stub addChange {top n tag line1 n1 line2 n2} {}

proc _PatchInit {} {
    set ::_patchfiles(e) 0
    set ::_patchfiles(pll) {}
    set ::_patchfiles(prl) {}
    set ::_patchfiles(ll) {}
    set ::_patchfiles(rl) {}
}

test patch-1.1 {
    Xxx
} -body {
    _PatchInit
    set ::testpatch [string trim {
--- foo.txt	2016-07-10 21:53:36.671932638 -0700
+++ bar.txt	2016-07-10 21:53:54.739860205 -0700
@@ -1 +1,2 @@
+0
 1
@@ -5 +8,9 @@
+0
 1
    }]
    displayPatch gurka
    concat $_patchfiles(ll) $_patchfiles(rl)
} -result {1 5 1 2 8 9}

test patch-1.2 {
    Xxx
} -body {
    _PatchInit
    set ::testpatch {
Index: vhdl/tb/tb_system.bhv
===================================================================
--- vhdl/tb/tb_system.bhv	(revision 320)
+++ vhdl/tb/tb_system.bhv	(working copy)
129
130
131
132
133
134
135
136
137




































+                      variable TcDFHeader     : out TcDFH_T);
   
   procedure TcAddrCalc(PresentAddr : in integer;
                        AccWidth    : in DynamicSize_T;

    }
    displayPatch gurka
    set ::_patchfiles
} -result {6}











































|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
+                      variable TcDFHeader     : out TcDFH_T);
   
   procedure TcAddrCalc(PresentAddr : in integer;
                        AccWidth    : in DynamicSize_T;

    }
    displayPatch gurka
    set ::_patchfiles(e)
} -result {6}

test patch-2.1 {
    Format with #
} -body {
    _PatchInit
    set ::testpatch [string trim {
--- foo.txt	2016-07-10 21:53:36.671932638 -0700
+++ bar.txt	2016-07-10 21:53:54.739860205 -0700
## -1 +1,2 ##
+0
 1
## -5 +8,9 ##
+0
 1
    }]
    displayPatch gurka
    concat $_patchfiles(ll) $_patchfiles(rl)
} -result {1 5 1 2 8 9}

test patch-2.2 {
    FOrmat with #
} -body {
    _PatchInit
    set ::testpatch [string trim {
--- foo.txt	2016-07-10 21:53:36.671932638 -0700
+++ bar.txt	2016-07-10 21:53:54.739860205 -0700
## -1 +1 ##
+0
 1
## -5 +8 ##
+0
 1
    }]
    displayPatch gurka
    concat $_patchfiles(ll) $_patchfiles(rl)
} -result {1 5 1 2 8 9}

Changes to tests/print.test.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23




24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44




45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# Tests for printing.                                       -*- tcl -*-
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------

test print-1.1 {Tab expansion} {FixTextBlock "a\fp\fa" 1.0} {apa}
test print-1.2 {Tab expansion} {FixTextBlock "\tapa"   1.0} {        apa}
test print-1.3 {Tab expansion} {FixTextBlock "\tapa"   1.1} {       apa}
test print-1.4 {Tab expansion} {FixTextBlock "\tapa"   1.7} { apa}
test print-1.5 {Tab expansion} {FixTextBlock "\tapa"   1.8} {        apa}

test print-1.6 {Tab expansion} {FixTextBlock "apa\tapa"   1.0} {apa     apa}
test print-1.7 {Tab expansion} {FixTextBlock "a\fpa\tapa" 1.1} {apa    apa}
test print-1.8 {Tab expansion} {FixTextBlock "apa\tapa"   1.4} {apa apa}
test print-1.9 {Tab expansion} {FixTextBlock "apa\tapa"   1.5} {apa        apa}

test print-2.1 {Pdf, line numbers} -setup {
    # Create big files with differences at five-digit line numbers
    set f1 [tcltest::makeFile {} _test1]
    set f2 [tcltest::makeFile {} _test2]
    set f3 [tcltest::makeFile {} _test3]
    set ch1 [open $f1 w]
    set ch2 [open $f2 w]




    set data [string repeat xx\n 12345]
    puts $ch1 "Diffline0.1"
    puts $ch2 "Diffline0.2"
    puts $ch1 $data
    puts $ch2 $data
    puts $ch1 "Diffline1.1"
    puts $ch2 "Diffline1.2"
    puts $ch1 $data
    puts $ch2 $data
    puts $ch1 "Diffline2.1"
    puts $ch2 "Diffline2.2"
    puts $ch1 $data
    puts $ch2 $data
    close $ch1
    close $ch2
} -body {
    set res [exec ./eskil.kit -context 5 -printpdf $f3 $f1 $f2]
    puts $res
    set ch [open $f3 r]
    set data [read $ch]
    close $ch




    # Check that line numbers take up 7 chars
    string match "*(    3: )*(24690: )*" $data
} -cleanup {
    tcltest::removeFile {} _test1
    tcltest::removeFile {} _test2
    tcltest::removeFile {} _test3
} -result {1}

test print-3.1 {Pdf, cmd line} -body {
    set res [exec ./eskil.kit -printHeaderSize x]
} -result {Argument -printHeaderSize must be a positive number}

test print-3.2 {Pdf, cmd line} -body {
    set res [exec ./eskil.kit -printCharsPerLine -5]
} -result {Argument -printCharsPerLine must be a positive number}

test print-3.3 {Pdf, cmd line} -body {
    set res [exec ./eskil.kit -printPaper qx]
} -match glob -result {Argument -printPaper must be a valid paper size*}

test print-3.4 {Pdf, cmd line} -body {
    set res [exec ./eskil.kit -printColorChange x]
} -result {Argument -printColorChange must be a list of RBG values from 0.0 to 1.0}

test print-3.5 {Pdf, cmd line} -body {
    set res [exec ./eskil.kit -printColorOld "0 1 2"]
} -result {Argument -printColorOld must be a list of RBG values from 0.0 to 1.0}

test print-3.6 {Pdf, cmd line} -body {
    set res [exec ./eskil.kit -printColorNew "0 -1 0.5"]
} -result {Argument -printColorNew must be a list of RBG values from 0.0 to 1.0}
<
|
|




















>
>
>
>
|















|

|


>
>
>
>









|



|



|



|



|



|


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

#------------------------------------------------------------*- tcl -*-
# Tests for printing.
#----------------------------------------------------------------------

test print-1.1 {Tab expansion} {FixTextBlock "a\fp\fa" 1.0} {apa}
test print-1.2 {Tab expansion} {FixTextBlock "\tapa"   1.0} {        apa}
test print-1.3 {Tab expansion} {FixTextBlock "\tapa"   1.1} {       apa}
test print-1.4 {Tab expansion} {FixTextBlock "\tapa"   1.7} { apa}
test print-1.5 {Tab expansion} {FixTextBlock "\tapa"   1.8} {        apa}

test print-1.6 {Tab expansion} {FixTextBlock "apa\tapa"   1.0} {apa     apa}
test print-1.7 {Tab expansion} {FixTextBlock "a\fpa\tapa" 1.1} {apa    apa}
test print-1.8 {Tab expansion} {FixTextBlock "apa\tapa"   1.4} {apa apa}
test print-1.9 {Tab expansion} {FixTextBlock "apa\tapa"   1.5} {apa        apa}

test print-2.1 {Pdf, line numbers} -setup {
    # Create big files with differences at five-digit line numbers
    set f1 [tcltest::makeFile {} _test1]
    set f2 [tcltest::makeFile {} _test2]
    set f3 [tcltest::makeFile {} _test3]
    set ch1 [open $f1 w]
    set ch2 [open $f2 w]
    set dlines {}
    for {set t 1} {$t <= 12345} {incr t} {
        lappend dlines "xx$t"
    }
    set data [join $dlines \n]
    puts $ch1 "Diffline0.1"
    puts $ch2 "Diffline0.2"
    puts $ch1 $data
    puts $ch2 $data
    puts $ch1 "Diffline1.1"
    puts $ch2 "Diffline1.2"
    puts $ch1 $data
    puts $ch2 $data
    puts $ch1 "Diffline2.1"
    puts $ch2 "Diffline2.2"
    puts $ch1 $data
    puts $ch2 $data
    close $ch1
    close $ch2
} -body {
    set res [ExecEskil -context 5 -printpdf $f3 $f1 $f2]
    puts $res
    set ch [open $f3 rb]
    set data [read $ch]
    close $ch
    # Find first stream, which is the page obj
    regexp {stream\n(.*?)endstream} $data -> pageStream
    catch {package require zlib}
    set data [zlib decompress $pageStream]
    # Check that line numbers take up 7 chars
    string match "*(    3: )*(24690: )*" $data
} -cleanup {
    tcltest::removeFile {} _test1
    tcltest::removeFile {} _test2
    tcltest::removeFile {} _test3
} -result {1}

test print-3.1 {Pdf, cmd line} -body {
    set res [ExecEskil -printHeaderSize x]
} -result {Argument -printHeaderSize must be a positive number}

test print-3.2 {Pdf, cmd line} -body {
    set res [ExecEskil -printCharsPerLine -5]
} -result {Argument -printCharsPerLine must be a positive number}

test print-3.3 {Pdf, cmd line} -body {
    set res [ExecEskil -printPaper qx]
} -match glob -result {Argument -printPaper must be a valid paper size*}

test print-3.4 {Pdf, cmd line} -body {
    set res [ExecEskil -printColorChange x]
} -result {Argument -printColorChange must be a list of RBG values from 0.0 to 1.0}

test print-3.5 {Pdf, cmd line} -body {
    set res [ExecEskil -printColorOld "0 1 2"]
} -result {Argument -printColorOld must be a list of RBG values from 0.0 to 1.0}

test print-3.6 {Pdf, cmd line} -body {
    set res [ExecEskil -printColorNew "0 -1 0.5"]
} -result {Argument -printColorNew must be a list of RBG values from 0.0 to 1.0}

Changes to tests/procs.test.

1
2
3

4
5
6
7
8
9
10
# Tests for comparing misc procedures.                      -*- tcl -*-
#----------------------------------------------------------------------
# $Revision$

#----------------------------------------------------------------------

test procs-1.1 {
    Linit
} -body {
    # Make sure argument ordering is same as lindex
    lindex [Linit x 3 4 5] 2 3 4
<
|
<
>








1

2
3
4
5
6
7
8
9

#------------------------------------------------------------*- tcl -*-

# Tests for comparing misc procedures.
#----------------------------------------------------------------------

test procs-1.1 {
    Linit
} -body {
    # Make sure argument ordering is same as lindex
    lindex [Linit x 3 4 5] 2 3 4

Changes to tests/rev.test.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Tests for revision control functions                      -*- tcl -*-
#----------------------------------------------------------------------
# $Revision$
#----------------------------------------------------------------------

# Overload exec during these tests
stub exec {args} {
    set cmd [lindex $args 0]
    switch -- $cmd {
        cleartool {
            # cleartool lshistory -short $filename
            # cleartool pwv -s
            # cleartool get -to $outfile $filerev
            # cleartool ls $::diff($top,RevFile)
            if {[lindex $args 1] eq "lshistory"} {
                return [join [list x@/Apa/Bepa/12 x@/Apa/Cepa/2 x@/Apa/22 x@/Apa] \n]
            }
            if {[lindex $args 1] eq "pwv"} {
                return $::ct_pwv
            }
            if {[lindex $args 1] eq "ls"} {
<
|
|










|








1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

#------------------------------------------------------------*- tcl -*-
# Tests for revision control functions
#----------------------------------------------------------------------

# Overload exec during these tests
stub exec {args} {
    set cmd [lindex $args 0]
    switch -- $cmd {
        cleartool {
            # cleartool lshistory -short $filename
            # cleartool pwv -s
            # cleartool get -to $outfile $filerev
            # cleartool ls $::eskil($top,RevFile)
            if {[lindex $args 1] eq "lshistory"} {
                return [join [list x@/Apa/Bepa/12 x@/Apa/Cepa/2 x@/Apa/22 x@/Apa] \n]
            }
            if {[lindex $args 1] eq "pwv"} {
                return $::ct_pwv
            }
            if {[lindex $args 1] eq "ls"} {
54
55
56
57
58
59
60
61
62
63
64
65
66
67

68
69
70
71
72
73
74
                        ------------------------------------------
                    }
                }
            }
            return
        }
        default {
            eval _stub_exec $args
        }
    }
}

# Do not detect git
set ::auto_execs(git) ""


test rev-1.1 {
    ClearCase revisions
} -body {
    set ::ct_ls @@/Bepa/5
    eskil::rev::CT::ParseRevs filename 2
} -result {/Bepa/2}







|




|

>







53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
                        ------------------------------------------
                    }
                }
            }
            return
        }
        default {
            #eval _stub_exec $args
        }
    }
}

# Do not detect git or fossil
set ::auto_execs(git) ""
set ::auto_execs(fossil) ""

test rev-1.1 {
    ClearCase revisions
} -body {
    set ::ct_ls @@/Bepa/5
    eskil::rev::CT::ParseRevs filename 2
} -result {/Bepa/2}
290
291
292
293
294
295
296

297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312



















































































































































































































    if {!$apa} {
        file mkdir _FOSSIL_
    }
    set res [detectRevSystem $f]
    if {!$apa} {
        file delete _FOSSIL_
    }

    set res
} -cleanup {
    tcltest::removeFile {} _rev2_12
} -result {FOSSIL}

test rev-3.1 {
    Subversion revisions
} -body {
    eskil::rev::SVN::ParseRevs filename -1
} -result {157}

test rev-3.2 {
    Subversion revisions
} -body {
    eskil::rev::SVN::ParseRevs filename -2
} -result {115}


























































































































































































































>









|






>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
    if {!$apa} {
        file mkdir _FOSSIL_
    }
    set res [detectRevSystem $f]
    if {!$apa} {
        file delete _FOSSIL_
    }
    set ::auto_execs(fossil) ""
    set res
} -cleanup {
    tcltest::removeFile {} _rev2_12
} -result {FOSSIL}

test rev-3.1 {
    Subversion revisions
} -body {
    eskil::rev::SVN::ParseRevs filename -1
} -result {117}

test rev-3.2 {
    Subversion revisions
} -body {
    eskil::rev::SVN::ParseRevs filename -2
} -result {115}

test rev-3.3 {
    Subversion revisions
} -body {
    eskil::rev::SVN::ParseRevs filename 0
} -result {BASE}


# Tests below tries to use the real tools and temporary repos.
# They are dependent on running on a system with tools installed.
clearstub

proc Fill1 {} {
    cd $::work
    file mkdir dir1
    file mkdir dir2
    exec touch f1
    exec touch f2
    exec touch dir1/f11
    exec touch dir1/f12
    exec touch dir2/f21
    exec touch dir2/f22
}
proc Fill2 {} {
    cd $::work
    exec echo Hej > f1
    exec echo Hopp > f2
}
proc Fill3 {} {
    cd $::work
    exec echo Nisse > dir1/f11
    exec echo Hult  > dir2/f21
    exec echo Lingonben >> f1
}
proc Fill4 {} {
    cd $::work
    exec echo Musse > dir1/f11
    exec echo Pigg  > dir1/f12
    exec echo Mimmi >> dir2/f21
    exec echo Pluto >> f2
}

proc CreateRepo {type} {
    set repoDir [tmpFile]
    file mkdir $repoDir
    switch $type {
        FOSSIL {
            set repo $repoDir/apa.fossil
            set ::work $repoDir/wk
            exec fossil new $repo
            file mkdir $::work
            cd $::work
            exec fossil open $repo
            set cmt "fossil commit -m"
        }
        GIT {
            set ::work $repoDir
            file mkdir $::work
            cd $::work
            exec git init
            set cmt "git commit -am"
        }
        SVN {
            exec svnadmin create $repoDir
            set ::work [tmpFile]
            exec svn checkout file://$repoDir $::work
            cd $::work
            set cmt "svn commit -m"
        }
        HG {
            set ::work $repoDir
            file mkdir $::work
            cd $::work
            exec hg init
            set cmt "hg commit -u Eskil -m"
        }
        default { error MOO }
    }
    Fill1
    switch $type {
        FOSSIL { exec fossil addremove }
        GIT    { exec git add *}
        SVN    { exec svn add {*}[glob *]}
        HG     { exec hg  add {*}[glob *]}
    }
    exec {*}$cmt "First"
    Fill2
    exec {*}$cmt "Second"
    Fill3
    exec {*}$cmt "Third"
    # Local changes
    Fill4

    # Any cleanup?
    switch $type {
        SVN { exec svn update }
        FOSSIL {exec fossil status --sha1sum}
    }
}

foreach type {FOSSIL GIT SVN HG} {
    test rev-4.$type.1 {
        Setup fake repo
    } -body {
        CreateRepo $type
        # Dump info for debug of setup
        if 0 {
            puts "------------------- $type --------------------"
            puts "--- $::work"
            switch $type {
                FOSSIL { puts [exec fossil timeline -v] }
                GIT    { puts [exec git log --name-only] }
                SVN    { puts [exec svn log --verbose] }
                HG     { puts [exec hg log --verbose] }
            }
        }
        list
    }

    test rev-4.$type.2 {
        GetTopDir
    } -body {
        cd ~
        eskil::rev::${type}::GetTopDir $::work/dir1/f11 dir tail
        list [expr {$dir eq $::work}] $tail
    } -result {1 dir1/f11}

    test rev-4.$type.3 {
        get
    } -body {
        cd ~
        set out [tmpFile]
        eskil::rev::${type}::get $::work/dir1/f11 $out ""
        exec cat $out
    } -result {Nisse}

    test rev-4.$type.4 {
        getChangedFiles
    } -body {
        cd ~
        set f $::work/dir1/f11
        set revs [eskil::rev::${type}::ParseRevs $f "-1 0"]
        lsort -dictionary [eskil::rev::${type}::getChangedFiles $f $revs]
    } -match regexp -result {^/\S+/dir1/f11 /\S+/dir2/f21 /\S+/f1 /\S+/f2$}

    test rev-4.$type.5 {
        getChangedFiles
    } -body {
        cd ~
        set f $::work/f1
        set revs [eskil::rev::${type}::ParseRevs $f "-1 0"]
        lsort -dictionary [eskil::rev::${type}::getChangedFiles $f $revs]
    } -match regexp -result {^/\S+/dir1/f11 /\S+/dir2/f21 /\S+/f1$}

    test rev-4.$type.16 {
        getPatch, list of files
    } -body {
        # getPatch needs to be in the checkout
        cd $::work
        set files [list $::work/dir1 $::work/f1 $::work/f2]
        if {$type in "SVN"} {
            # SVN gives full paths when given full paths
            set expect [list $::work/dir1/f11 $::work/dir1/f12 $::work/f2]
        } else {
            set expect [list dir1/f11 dir1/f12 f2]
        }
        set expect [lsort -dictionary $expect]
        set patch [eskil::rev::${type}::getPatch {} $files outFileList]
        if {$outFileList ne $expect} {
            return $outFileList ;#$patch
        } else {
            return 1
        }
    } -result 1

    test rev-4.$type.7 {
        getPatch, list of files
    } -body {
        # getPatch needs to be in the checkout
        cd $::work
        set files [list dir1 f1 f2]
        set expect [list dir1/f11 dir1/f12 f2]
        set expect [lsort -dictionary $expect]
        set patch [eskil::rev::${type}::getPatch {} $files outFileList]
        if {$outFileList ne $expect} {
            return $outFileList ;#$patch
        } else {
            return 1
        }
    } -result 1

    test rev-4.$type.8 {
        getPatch, list of files
    } -body {
        # getPatch needs to be in the checkout
        cd $::work
        set f f1
        set revs [eskil::rev::${type}::ParseRevs $f "-1 0"]
        set files [list dir1 f1 f2]
        set expect [list dir1/f11 f1]
        set expect [lsort -dictionary $expect]
        set patch [eskil::rev::${type}::getPatch $revs $files outFileList]
        if {$outFileList ne $expect} {
            return $outFileList ;#$patch
        } else {
            return 1
        }
    } -result 1
    
    clearTmp
}