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 545940abe0
2024-10-02
| ||
19:05 | debugMenu can append Leaf check-in: 545940abe0 user: peter tags: trunk | |
18:50 | Moved debug to module check-in: 3d33ff0139 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 | 2011-04-30 Improved three-way merge. Highlight conflicts and navigate directly between conflicts. | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | 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 | 2011-03-31 Added support for Fossil revision control. 2010-11-07 Added tkdnd support. [FR 5125] 2010-11-07 | | | 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 | Better handling of negative Subversion revisions 2008-11-19 Bug fix in Clip Diff Capture 2008-11-10 Improved patch parsing. | | | 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 | Kits are mounted readonly. 2007-01-29 Finished dirdiff filters. [FR 3040] 2007-01-28 Started on dirdiff filters. | | | 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 | 2004-06-30 Full -r support for ClearCase. 2004-06-24 Added simple -r support to ClearCase diff. Support ignore case in block matching. | | | 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 | 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. | | | 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.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to Makefile.
1 2 3 4 | #---------------------------------------------------------------------- # Make file for 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 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 | #---------------------------------------------------------------------- # 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.8 PDF4TCL = /home/$(USER)/src/pdf4tcl/pkg SNIT = /home/$(USER)/src/packages/tcllib/modules/snit TABLELIST = /home/$(USER)/src/packages/tablelist6.22 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/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 # Check modules against local copies tmcheck: @ls -ltr `find . -name '*.tm'` `find /home/peter/mystuff -name '*.tm'` # 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/psmenu-1.1.tm \ eskil.vfs/lib/pstools-1.0.tm \ eskil.vfs/lib/psdebug-1.0.tm \ eskil.vfs/lib/psballoon-1.3.tm 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 | clean: @rm -f $(LOGFILES) $(IFILES) $(MFILES) #---------------------------------------------------------------- # Packaging/Releasing #---------------------------------------------------------------- | > > > | | | | | | > > > > > > > > | 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 | 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 | -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. | | > > | > | 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 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 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 | <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> | > > > > > > > | | | > > > > > > > > > > > > > > > > > > > > > > | 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 | <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> | | | < > > | > | | | 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.
|
| < |
Deleted eskil.vfs/doc.
|
| < |
Deleted eskil.vfs/examples.
|
| < |
Added eskil.vfs/lib/psballoon-1.3.tm.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | #---------------------------------------------------------*-tcl-*------ # # 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-2024, Peter Spjuth (peter.spjuth@gmail.com) # # Permission is granted to use this code under the same terms as # for the Tcl core code. # #---------------------------------------------------------------------- # This is used as a Tcl Module. Use it like this: # ::tcl::tm::path add <path-to-dir-with-module> # package require psballoon # namespace import psballoon::* # # addBalloon .l .b "My help text" # addBalloon .f -fmt { # Write help more freely.\n # New lines need to be explicit like above. # } #---------------------------------------------------------------------- package provide psballoon 1.3 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"] # Any remaining substs like tabs? set msg [subst -nocommands -novariables $msg] # 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 } |
Deleted eskil.vfs/lib/psballoon/pkgIndex.tcl.
|
| < < < < < < < < < < < |
Deleted eskil.vfs/lib/psballoon/psballoon.tcl.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added eskil.vfs/lib/psdebug-1.0.tm.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | #---------------------------------------------------------*-tcl-*------ # # psdebug.tcl, # Helpers for debugging. # # Copyright (c) 2024, Peter Spjuth (peter.spjuth@gmail.com) # # Permission is granted to use this code under the same terms as # for the Tcl core code. # #---------------------------------------------------------------------- # This is used as a Tcl Module. Use it like this: # ::tcl::tm::path add <path-to-dir-with-module> # package require psdebug # namespace import ::_PsDebug::* #---------------------------------------------------------------------- package provide psdebug 1.0 namespace eval ::_PsDebug { variable allcmds namespace export debugMenu } #----------------------------------------------------------------------------- # Misc useful stuff #----------------------------------------------------------------------------- proc ::_PsDebug::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 ::_PsDebug::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 ::_PsDebug::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 ::_PsDebug::TRleave {cmd code res op} { puts "Leave: '$res'" } proc ::_PsDebug::TRleavestep {cmd code res op} { puts "Leavestep: '$res'" } proc ::_PsDebug::TR {cmd {step 0}} { TRoff $cmd trace add execution $cmd enter ::_PsDebug::TRenter trace add execution $cmd leave ::_PsDebug::TRleave if {$step} { trace add execution $cmd enterstep ::_PsDebug::TRenterstep trace add execution $cmd leavestep ::_PsDebug::TRleavestep } } proc ::_PsDebug::TRoff {cmd} { trace remove execution $cmd enter ::_PsDebug::TRenter trace remove execution $cmd leave ::_PsDebug::TRleave trace remove execution $cmd enterstep ::_PsDebug::TRenterstep trace remove execution $cmd leavestep ::_PsDebug::TRleavestep } #----------------------------------------------------------------------------- # GUI #----------------------------------------------------------------------------- proc ::_PsDebug::debugMenu {mW args} { if {"-append" in $args} { set dW $mW } else { set dW $mW.debug $mW add cascade -label "Debug" -menu $dW -underline 0 menu $dW } if {$::tcl_platform(platform) eq "windows"} { $dW add checkbutton -label "Console" -variable ::consoleState \ -onvalue show -offvalue hide -command {console $::consoleState} \ -underline 0 $dW add separator } $dW add command -label "Edit" -command ::_PsDebug::ProcEditor \ -underline 0 $dW add command -label "Windows" -command ::_PsDebug::WindowBrowser \ -underline 0 return $dW } #----------------------------------------------------------------------------- # Window structure browser #----------------------------------------------------------------------------- proc ::_PsDebug::WindowBrowser {} { set top .windowbrowser destroy $top tk::toplevel $top -padx 3 -pady 3 place [ttk::frame $top.tilebg] -border outside \ -x 0 -y 0 -relwidth 1 -relheight 1 wm title $top "Window Browser" wm protocol $top WM_DELETE_WINDOW [list ::_PsDebug::WindowBrowserClosed $top] ttk::panedwindow $top.pw -orient horizontal pack $top.pw -fill both -expand 1 # Widget Tree 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} # Info Text text $top.t -width 80 -wrap word set ::_PsDebug::WindowBrowser(treeW) $tree set ::_PsDebug::WindowBrowser(textW) $top.t bind $tree <<TreeviewSelect>> ::_PsDebug::WindowBrowserSelected $top.pw add $top.ftree -weight 1 $top.pw add $top.t -weight 2 set ::_PsDebug::WindowBrowser(deselect) "" PopulateWindowBrowser $tree } proc ::_PsDebug::WindowBrowserClosed {top} { destroy $top if {$::_PsDebug::WindowBrowser(deselect) ne ""} { {*}$::_PsDebug::WindowBrowser(deselect) set ::_PsDebug::WindowBrowser(deselect) "" } } # An item was selected. Show info proc ::_PsDebug::WindowBrowserSelected {} { $::_PsDebug::WindowBrowser(textW) delete 1.0 end if {$::_PsDebug::WindowBrowser(deselect) ne ""} { #puts "DESEL: $::_PsDebug::WindowBrowser(deselect)" {*}$::_PsDebug::WindowBrowser(deselect) set ::_PsDebug::WindowBrowser(deselect) "" } set tree $::_PsDebug::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] $::_PsDebug::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 ::_PsDebug::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 ::_PsDebug::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 ::_PsDebug::WindowBrowser(deselect) \ [list {*}$i [list $w configure -background $bg]] return } on error {err info} { #puts "In $interp" #puts "$err" #puts "$info" } #puts "MOO $w" } # Format configure data from a widget for display proc ::_PsDebug::FormatConfigure {configData} { set first "" set last "" foreach param $configData { lassign $param flag _ _ def value if {$value ne $def} { # List changed values first append first "[list $flag $value] " } else { append last "[list $flag $value] " } } set first [string trim $first] set last [string trim $last] if {$first ne ""} { set first "Changed Parameters:\n$first\n" } if {$last ne ""} { append first "Default Parameters:\n" $last } return [string trim $first] } # Populate proc ::_PsDebug::PopulateWindowBrowser {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] # Info to be displayed set out "$w ($class)\n" set configData [{*}$i $w configure] append out [FormatConfigure $configData] foreach param $configData { lassign $param flag _ _ def value if {$flag eq "-container" && $value == 1} { lappend containers $w $interp } } # Add grid info, if any try { set ix [{*}$i grid info $w] if {$ix ne ""} { append out "\n\ngrid\n$ix" } } on error {} {} # Add pack info, if any try { set ix [{*}$i pack info $w] if {$ix ne ""} { append out "\n\npack\n$ix" } } on error {} {} # Add menu info, if menu try { set last [{*}$i $w index end] for {set ix 0} {$ix <= $last} {incr ix} { set configData [{*}$i $w entryconfigure $ix] append out \n\n [FormatConfigure $configData] } } trap {TCL LOOKUP INDEX} {} { # Non-menu widgets will normally error out on not having the # "index" subcommand, which ends up here. Ignore. } on error {msg erri} { # Give some hint on other errors #puts "MOOO $msg\n$erri" } 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 ::_PsDebug::ProcEditorSelected {} { variable allcmds set ::_PsDebug::ProcEditor(current) "" set ::_PsDebug::ProcEditor(parent) "" set ::_PsDebug::ProcEditor(proc) "" set ::_PsDebug::ProcEditor(args) "" $::_PsDebug::ProcEditor(bodyW) delete 1.0 end set tree $::_PsDebug::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 ::_PsDebug::ProcEditor(current) $item set ::_PsDebug::ProcEditor(parent) $parent set ::_PsDebug::ProcEditor(proc) $name set ::_PsDebug::ProcEditor(args) "" $::_PsDebug::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 ::_PsDebug::ProcEditor(args) $arglist $::_PsDebug::ProcEditor(bodyW) insert end $body } elseif {$type eq "method"} { lassign [info class definition $parent $name] arglist body set traceState disabled set ::_PsDebug::ProcEditor(args) $arglist $::_PsDebug::ProcEditor(bodyW) insert end $body } else { set traceState disabled } foreach w $::_PsDebug::ProcEditor(traceWs) { $w configure -state $traceState } } # Redefine currently edited proc/method proc ::_PsDebug::ProcEditorRedefine {} { variable allcmds set body [$::_PsDebug::ProcEditor(bodyW) get 1.0 end] set body [string trimright $body] set item $::_PsDebug::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 \ $::_PsDebug::ProcEditor(args) $body] set ::_PsDebug::redefines($item) $todo uplevel \#0 $todo } elseif {$type eq "method"} { set todo [list oo::define $parent method $name \ $::_PsDebug::ProcEditor(args) $body] set ::_PsDebug::redefines($parent..$name) $todo uplevel \#0 $todo } } proc ::_PsDebug::ProcEditorCopy {} { clipboard clear foreach item [array names ::_PsDebug::redefines] { clipboard append $::_PsDebug::redefines($item)\n } } # Tracing of commands proc ::_PsDebug::ProcEditorTrace {level} { variable allcmds set item $::_PsDebug::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 ::_PsDebug::ProcEditorDisas {} { variable allcmds set item $::_PsDebug::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 tk::toplevel $top -padx 3 -pady 3 place [ttk::frame $top.tilebg] -border outside \ -x 0 -y 0 -relwidth 1 -relheight 1 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 ::_PsDebug::ProcEditorFilter {aVal kVal} { set f $::_PsDebug::ProcEditor(filter) set fx $::_PsDebug::ProcEditor(filterx) # Do not react unless changed. if {$f eq $fx} { return } set tree $::_PsDebug::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 ::_PsDebug::ProcEditor(filterx) $f } # Make sure the hierarchy for a leaf exist, creating if needed. proc ::_PsDebug::TreeCreatePath {tree path} { if {[$tree exists $path]} return set d $::_PsDebug::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 ::_PsDebug::TreePopulate {tree {filter *}} { $tree delete [$tree children {}] foreach cmd [lsort -dictionary [array names ::_PsDebug::allcmds]] { set d $::_PsDebug::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 ::_PsDebug::ProcEditor {} { ::_PsDebug::CollectInfo set top .proceditor destroy $top tk::toplevel $top -padx 3 -pady 3 place [ttk::frame $top.tilebg] -border outside \ -x 0 -y 0 -relwidth 1 -relheight 1 wm title $top "Proc Editor" ttk::frame $top.ftree set ::_PsDebug::ProcEditor(filter) "" set ::_PsDebug::ProcEditor(filterx) "" ttk::entry $top.ftree.ef -textvariable ::_PsDebug::ProcEditor(filter) addBalloon $top.ftree.ef "Filter" bind $top.ftree.ef <KeyRelease> {::_PsDebug::ProcEditorFilter %A %K} set tree $top.ftree.tree set ::_PsDebug::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>> ::_PsDebug::ProcEditorSelected ttk::label $top.l1a -text "Parent" -anchor w ttk::label $top.l1b -textvariable ::_PsDebug::ProcEditor(parent) -anchor w ttk::label $top.l2a -text "Proc/Method" -anchor w ttk::label $top.l2b -textvariable ::_PsDebug::ProcEditor(proc) -anchor w ttk::label $top.l3a -text "Args" -anchor w ttk::label $top.l3b -textvariable ::_PsDebug::ProcEditor(args) -anchor w ttk::button $top.bc -text "Copy" -command ::_PsDebug::ProcEditorCopy addBalloon $top.bc "Put all redefines on clipboard" set ::_PsDebug::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 ::_PsDebug::ProcEditorRedefine addBalloon $top.b1 "Redefine for this session" ttk::button $top.b2 -text "Disas" -command ::_PsDebug::ProcEditorDisas addBalloon $top.b2 "Show byte code" ttk::button $top.b3 -text "Trace" -command "::_PsDebug::ProcEditorTrace 1" addBalloon $top.b3 "Enable execution trace" ttk::button $top.b4 -text "Tr Step" -command "::_PsDebug::ProcEditorTrace 2" addBalloon $top.b4 "Enable detailed execution trace" ttk::button $top.b5 -text "Tr Off" -command "::_PsDebug::ProcEditorTrace 0" addBalloon $top.b5 "Disable execution trace" set ::_PsDebug::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 ::_PsDebug::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 ::_PsDebug::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 ::_PsDebug::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 "::_PsDebug::"} 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 ::_PsDebug::DumpStuff {} { try { ::_PsDebug::CollectInfo } on error {res i} { puts $res puts $i after 1000 } # Proc parray ::_PsDebug::allcmds *updateColors* parray ::_PsDebug::allcmds *cleanupAndExit # Cmd parray ::_PsDebug::allcmds *ttk::paned parray ::_PsDebug::allcmds *llength # OO class parray ::_PsDebug::allcmds *Account* # Snit class parray ::_PsDebug::allcmds *eskilprint* # parray ::_PsDebug::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"} } } |
Added eskil.vfs/lib/psmenu-1.1.tm.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | #---------------------------------------------------------*-tcl-*------ # # psmenu.tcl # Framework for building Tk menu structure # # Copyright (c) 2024, Peter Spjuth (peter.spjuth@gmail.com) # # Permission is granted to use this code under the same terms as # for the Tcl core code. # #---------------------------------------------------------------------- # This is used as a Tcl Module. Use it like this: # ::tcl::tm::path add <path-to-dir-with-module> # package require psmenu # psmenu::psmenu . { # definitions... # } #---------------------------------------------------------------------- package provide psmenu 1.1 namespace eval psmenu { namespace export psmenu } set psmenu::example { # All definition blocks are lists. # Thus \-newline are not strictly necessary. # For compactness, options are kept short # -ul for -underline # "&File"-syntax supported for label # -var for -variable # Variable is initialised if not existing # If used with cascade, store menu widget in var # -cfgvar Store entryconfigure command in var # -cmd for -command # command part is put through "subst" # -acc for -accelerator # Will bind <Key-acc> to command unless -accelerator # -def for default value of -var # checkbuttons default to default 0 # radiobuttons default to first value seen # if/else commands can be included package require psmenu psmenu::psmenu . { # Comments are ok # If an argument looks like a "body" this is a cascade "&File" { "&Copy Solver URL" -cmd copySolverUrl # Separator is --* -------------------- "&Quit" -cmd exit } "&Config" { "&Rules" -var ::mooa { "Knight" -var ::ruleset(knight) -cmd changeRuleSet "King" -var ::ruleset(king) -cmd changeRuleSet "No Consecutive" -var ::ruleset(consecutive) -cmd changeRuleSet "Grid Size" -var mooo { # TBD allow loops? # Define a bunch of radiobuttons at once. # Without -value it is same as label string _Radio -var ::ruleset(gridsize) -cmd newGame -def 9 { "8" -value 8 "9" -value 9 "10" } # Individual example "6" -value 6 -var ::ruleset(gridsize) -cmd newGame } } # checkbutton has a -var and possibly -cmd, -onvalue, -offvalue "Conflict Check" -var ::uiData(conflict) -cmd autoConflict } } # More calls with more cascades work psmenu::psmenu . { "&Debug" { "Reread &Source" -acc F1 -cmd _rs } } } ##nagelfar syntax psmenu::PopEntry v ##nagelfar syntax tk::AmpMenuArgs x* # Main call for psmenu. Some optional arguments are for internal use. # Canbe called with an existing menu, but then -top must be given. proc psmenu::psmenu {top args} { set def [lindex $args end] set args [lrange $args 0 end-1] set opts(-top) "" set opts(-level) "" set opts(-level) "" set opts(-recursive) 0 array set opts $args # Is given arg a toplevel or menu? if {$top eq "."} { set m .m } else { if {[winfo class $top] ne "Menu"} { set m $top.m } else { # A menu was given, assume -top set m $top set top $opts(-top) } } if {$opts(-recursive)} { # Locate a free window name for the menu, for internal call while {[winfo exists $m]} { if {[regexp {^(.*?)(\d+)$} $m -> prefix Index]} { incr Index } else { set prefix $m set Index 0 } set m $prefix$Index } } # It might exist for a second user call if { ! [winfo exists $m]} { # Create menu $m -tearoff 0 } if {$opts(-level) eq ""} { # Store initial level to handle scope when recursing cascades set opts(-level) [uplevel 1 info level] } if {$opts(-top) eq ""} { set opts(-top) $top $top configure -menu $m } # Comments in definition block set def [regsub -all -line {^\s*#.*$} $def ""] set state "" while {[llength $def] > 0} { #puts "Def length [llength $def]"; update set entry [PopEntry def] set label [lindex $entry 0] if {$label eq "_Radio"} { set options [lrange $entry 1 end-1] set body [lindex $entry end] set radioDef {} for {set t 0} {$t < [llength $body]} {incr t} { set label [lindex $body $t] if {[lindex $body $t+1] eq "-value"} { incr t 2 set value [lindex $body $t] } else { set value $label } lappend radioDef $label -value $value {*}$options } #puts "RADIO '$radioDef'" # Prepend set def [list {*}$radioDef {*}$def] # TBD FIXA continue } # Conditionals if {$label eq "if"} { # TBD support elseif set ifExpr [lindex $entry 1] set body [lindex $entry 2] set elseBody [lindex $entry 4] set cond [uplevel \#$opts(-level) [list expr $ifExpr]] #puts "if expression '$ifExpr' = $cond" if {$cond} { # Prepend set def [list {*}$body {*}$def] } elseif {$elseBody ne ""} { set def [list {*}$elseBody {*}$def] } continue } # Recognise Cascade by even args "Name ?opts? Def" # An item will be "Name ?opts?", i.e odd if {[llength $entry] % 2 == 0} { # Cascade set options [lrange $entry 1 end-1] set body [lindex $entry end] # Recurse cascade defintion set cascade [psmenu $m {*}[array get opts] -recursive 1 $body] # Since -menu is last, processing below can assume that. lappend options -menu $cascade } else { set options [lrange $entry 1 end] } #puts "Label '$label'" #puts "Options '$options'" # Figure out type if {[string match "-*" $label]} { set type separator set label "" } elseif {[dict exists $options -menu]} { set type "cascade" } elseif {[dict exists $options -value]} { set type radiobutton } elseif {[dict exists $options -var]} { set type checkbutton } else { set type command } # Process options set newOptions {} if {$label ne ""} { lappend newOptions -label $label } set doBind "" set command "" set value "" set variable "" set cfgvar "" set default 0 foreach {opt val} $options { set val [uplevel \#$opts(-level) [list subst $val]] switch -- $opt { -ul - -underline { lappend newOptions -underline $val } -var - -variable { if {$type eq "cascade"} { set variable $val } else { set variable $val lappend newOptions -variable $val } } -cfgvar { set cfgvar $val } -cmd - -command { set command $val lappend newOptions -command $val } -acc { lappend newOptions -accelerator $val set doBind $val } -accelerator { lappend newOptions -accelerator $val } -value { lappend newOptions -value $val set default $val } -offvalue { lappend newOptions -offvalue $val set default $val } -onvalue { lappend newOptions -onvalue $val } -menu { lappend newOptions -menu $val if {$variable ne ""} { uplevel \#$opts(-level) [list set $variable $val] } } -def { set default $val } default { # Just let through lappend newOptions $opt $val } } } if {$variable ne ""} { ##nagelfar ignore Non constant level upvar \#$opts(-level) $variable __vv if { ! [info exists __vv]} { set __vv $default } } # TK helper to handle & in label ::tk::AmpMenuArgs $m add $type {*}$newOptions if {$cfgvar ne ""} { set ix [$m index end] set tmp [list $m entryconfigure $ix] uplevel \#$opts(-level) [list set $cfgvar $tmp] } if {$doBind ne ""} { if {[regexp {^(.*)-(.*)$} $doBind -> pre post]} { if {$pre eq "Ctrl"} { set pre "Control" } set doBind $pre-Key-$post } else { set doBind Key-$doBind } #puts "Binding '$doBind' '$command'" bind $opts(-top) <$doBind> $command } } return $m } # Extract one entry from definiton proc psmenu::PopEntry {defName} { upvar 1 $defName def set result {} if {[lindex $def 0] eq "if"} { # TBD support elseif if {[lindex $def 0] eq "else"} { set result [lrange $def 0 4] set def [lrange $def 5 end] } else { set result [lrange $def 0 2] set def [lrange $def 3 end] } return $result } set state "label" set n -1 foreach arg $def { incr n switch $state { "label" { lappend result $arg set state "option" } "option" { if {[string match "--*" $arg]} { incr n -1 break } elseif {[string match "-*" $arg]} { lappend result $arg set state "value" } elseif {[regexp {^\s+.*\s+$} $arg] || $arg eq ""} { # recognise body somehow lappend result $arg break } else { # Must be next label incr n -1 break } } "value" { lappend result $arg set state "option" } } } incr n set def [lrange $def $n end] return $result } if 0 { set mooo "Not set" console show package require Tk update wm geometry . 400x400 update eval ${psmenu::example} puts "mooo $mooo" puts "mooa $mooa" } |
Added eskil.vfs/lib/pstools-1.0.tm.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | #---------------------------------------------------------*-tcl-*------ # # pstools.tcl, # a package providing misc facilites # # 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. # #---------------------------------------------------------------------- # This is used as a Tcl Module. Use it like this: # ::tcl::tm::path add <path-to-dir-with-module> # package require pstools # namespace import pstools::* #---------------------------------------------------------------------- package provide pstools 1.0 #package require Tcl 8.4 namespace eval pstools { namespace export safeLoad commonYScroll locateTmp locateEditor if {[info commands ::ttk::*] ne ""} { catch {namespace path ::ttk} } } ##nagelfar syntax _ipexists l ##nagelfar syntax _ipset v ##nagelfar syntax _iparray s v # Load a preference file # Args lists the variables allowed to be set by the file # If -existing is given, only existing variables and elements may be set ##nagelfar syntax pstools::safeLoad x o* v* proc pstools::safeLoad {file args} { interp create -safe loadinterp interp alias {} _ipexists loadinterp info exists interp alias {} _ipset loadinterp set interp alias {} _iparray loadinterp array interp invokehidden loadinterp source $file set existing 0 foreach arg $args { if {$arg eq "-existing"} { set existing 1 continue } ##nagelfar vartype arg varName upvar 1 $arg TheVar if {[_iparray exists $arg]} { foreach {key val} [_iparray get $arg] { if { ! $existing || [info exists TheVar($key)]} { set TheVar($key) $val } } } elseif {[_ipexists $arg]} { if { ! $existing || [info exists TheVar]} { set TheVar [_ipset $arg] } } } interp delete loadinterp } # Procedures for common y-scroll proc pstools::CommonYScroll_YView {sby args} { variable yscroll foreach w $yscroll($sby) { eval [list $w yview] $args } } proc pstools::CommonYScroll_YScroll {sby args} { eval [list $sby set] $args CommonYScroll_YView $sby moveto [lindex $args 0] } # Set up a common yscrollbar for a few scrollable widgets proc pstools::commonYScroll {sby args} { variable yscroll $sby configure -command [list pstools::CommonYScroll_YView $sby] foreach w $args { $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 {} if {[info exists ::env(TEMP)]} { lappend candidates $::env(TEMP) } if {[info exists ::env(TMP)]} { lappend candidates $::env(TMP) } lappend candidates /tmp . ~ foreach cand $candidates { set cand [file normalize $cand] if {[file isdirectory $cand] && [file writable $cand]} { set var $cand return } } # Panic? set var . } # 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" } |
Deleted eskil.vfs/lib/pstools/pkgIndex.tcl.
|
| < < < < < < < < < < < |
Deleted eskil.vfs/lib/pstools/pstools.tcl.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to eskil.vfs/main.tcl.
1 2 | package require starkit starkit::startup | | | 1 2 3 | package require starkit starkit::startup source $::starkit::topdir/src/startup.tcl |
Deleted eskil.vfs/plugins.
|
| < |
Deleted eskil.vfs/src/clip.tcl.
|
| < |
Deleted eskil.vfs/src/compare.tcl.
|
| < |
Deleted eskil.vfs/src/dirdiff.tcl.
|
| < |
Deleted eskil.vfs/src/eskil.tcl.
|
| < |
Deleted eskil.vfs/src/help.tcl.
|
| < |
Deleted eskil.vfs/src/map.tcl.
|
| < |
Deleted eskil.vfs/src/merge.tcl.
|
| < |
Deleted eskil.vfs/src/plugin.tcl.
|
| < |
Deleted eskil.vfs/src/print.tcl.
|
| < |
Deleted eskil.vfs/src/printobj.tcl.
|
| < |
Deleted eskil.vfs/src/registry.tcl.
|
| < |
Deleted eskil.vfs/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/conflict.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 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 | 0 1 Added left x 2 3 4 Added right y 5 6 7 Changed same 8a 9 10 Changed different <<<<<<< HEAD 12b ======= 12a >>>>>>> main 13 14 Deleted left 17 18 Deleted right 20 21 Left: Add+change Right: change <<<<<<< HEAD 22b 23 24 Left: change Right add+change yy 25b 26 27 Left: Delete Right: change 28a 29 30 Left: change Right: delete ======= xx 22a 23 24 Left: change Right add+change 25a 26 27 Left: Delete Right: change 29 30 Left: change Right: delete 31a >>>>>>> main 32 33 Added same xxx 34 35 36 Deleted same 38 39 Changed adjacent <<<<<<< HEAD 40 41a 42 Left: Add+change Right: change same ======= 40a 41 42 Left: Add+change Right: change same xxxx >>>>>>> main 43a 44 45 Left: change same Right: add+change <<<<<<< HEAD yyyy ======= >>>>>>> main 46a 47 48 Left: change Right: deleted block <<<<<<< HEAD 52 Added different yyyyy ======= 49 50a 51 52 Added different xxxxx >>>>>>> main 53 54 55 Left mixed change 57x 59 60 Right mixed change 61x 63x 64 65 Both added multiple <<<<<<< HEAD yy1 yy2 ======= xx1 xx2 >>>>>>> main 66 67 |
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: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 |
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 | A file with a keyword in it. | | | 1 2 | A file with a keyword in it. $Revision:b$ |
Changes to examples/dir2/longline.txt.
1 | abcdefghijklmnop | | | | 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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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> <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 <peter <i>dot</i> spjuth <i>at</i> gmail <i>dot</i> com> 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 <plg> -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 main cp ../tests/left.txt a.txt git commit -am l git checkout b #git merge main #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 | ##Eskil Plugin : Compare with backslash-newline removed | | > > > | < < < | 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 | ##Eskil Plugin : Case insensitive matching | | > > > | < < < > > > > > > > > > > > > > > > > > > > > > > > | 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 | ##Eskil Plugin : Compare set of words | | > > | < < | 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 | } proc DoClipDiff {} { set f1 [tmpFile] set f2 [tmpFile] set ch [open $f1 w] | | | | 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 | $::widgets($top,wLine1) configure -height 1 $t2 configure -height $lines2 $::widgets($top,wLine2) configure -height 1 } } proc ArmCatch {} { | | | | 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 | 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] } | | | | | | 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 | 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] | | | > | 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | 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] # Have the file menu in top frame to save space ttk::frame $top.f menubutton $top.f.mf -menu $top.f.mf.m -text "File" -underline 0 menu $top.f.mf.m $top.f.mf.m add command -label "Close" -underline 0 \ -command [list cleanupAndExit $top] $top.f.mf.m add separator $top.f.mf.m add command -label "Quit" -underline 0 \ |
︙ | ︙ | |||
179 180 181 182 183 184 185 | #} 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] | | | | 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | #} 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 | # the Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # #---------------------------------------------------------------------- # $Revision$ #---------------------------------------------------------------------- | | | | 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) |
︙ | ︙ |
Changes to src/dirdiff.tcl.
︙ | ︙ | |||
18 19 20 21 22 23 24 | # the Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # #---------------------------------------------------------------------- # $Revision$ #---------------------------------------------------------------------- | < < | | | < | | > > | > | | < < < < | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | > | > > > > > | | | | > > > > > > > | > | | | | > > | > | > > > > > | > > > | | 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 | } set files $files2 } } set files2 {} foreach file $files { | | > | | | | | | | | | | | | | > | < | 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 | 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 "" | > > > > > > > > > | | | < < < < < < < < < < < > | 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 | } method SetDirOption {option value} { set options($option) $value if {$options(-leftdirvariable) eq ""} return upvar \#0 $options(-leftdirvariable) left | | | | | | | | > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > | > | | | | > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < < | | < < | | < < | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > | > > > > | | 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 | } } # 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 | > | | > > | | > > > > > | | > > > > > > | | > > | | > > > > > > > > > > > > > > > > > > > > | > > | > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > | | > | > | > > > | > | | > > > > > | | | | > | > > | > > > > > | | | > > > > > > > > > > > > > > > > > > > > > > | < < < < < < < | > > | > > > > > > > > > > | > > > | > | > | | | | | < < | < < < < | > > | | | | | | > > > < > > > | | | | | 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 | 1 { $self ListFiles "" $df2 $node incr p2 set status_change 1 } } } elseif {$p1 < $len1 && $p2 >= $len2} { | | | | | | 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 | } 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] | > > < < < < < < < < > > > > > | | | < < < | < < > | < < > | | | < | | > > > > | > > | < < < | | > > > | | < | | < | < < | | | < < > | > > < | < < | < | | | | < | | | < > | < | < > | | > > > > > | < < | | | > | | > | > | | > | | > > > > > > | | 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 | } 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 # Need to do this manually with snit $hull configure -menu $win.m psmenu::psmenu $win -top $win { "&File" { "C&ompare" -cmd "[mymethod DoDirCompare]" -acc "Alt-c" --- "&Close" -cmd "cleanupAndExit $win" --- "&Quit" -cmd "cleanupAndExit all" } "&Preferences" { "Prefs..." -cmd makeDirDiffPrefWin "Check" { _Radio -var ::Pref(dir,comparelevel) { "Do not check contents" -value 0 "Normal compare" -value 1 "Exact compare" -value 2 } "Ignore \$Keyword:\$" -var ::Pref(dir,ignorekey) } "P&lugins..." -cmd "editPrefPlugins $win 1" "Nice" { _Radio -var ::Pref(dir,nice) -cmd "[mymethod DoNice]" { 1 50 100 1000 } } --- "Save default" -cmd "saveOptions $win" } "&Tools" { "&New Diff Window" -cmd makeDiffWin "&Clip Diff" -cmd makeClipDiffWin if {$::tcl_platform(platform) eq "windows"} { if { ! [catch {package require registry}]} { --- "Setup &Registry" -cmd makeRegistryWin } } } "&Help" { "&Tutorial" -cmd makeTutorialWin "&About" -cmd makeAboutWin } if {$::eskil(debug)} { "&Debug" { if {$::tcl_platform(platform) eq "windows"} { "Console" -var consolestate \ -onvalue show -offvalue hide -cmd "console \\$consolestate" --- } "&Reread Source" -cmd {EskilRereadSource} --- "Redraw Window" -cmd {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 | set ::TmpPref($item) $::Pref($item) } } wm title $top "Eskil Directory Preferences" set check [ttk::labelframe $top.check -text "Check" -padding 3] | | | | | | | | | | | | 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 | 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 | 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 | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | 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 | 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 | ##nagelfar syntax textSearch::searchMenu x | | > > > > | | > > | > > > > > < | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | > | > | > | > | | | > > > > > > > > > > > | 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 | #---------------------------------------------------------- -*- 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 | # # 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. # #---------------------------------------------------------------------- | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > | > > > > | | | | | | > > > > | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < | < < < | < < | | < | < | | | | > > > > | > | | 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 | # # 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 } 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 | set block1nostar [string map {* {}} $block1nospace] set block2nostar [string map {* {}} $block2nospace] if {$block1nostar eq $block2nostar} { set equal 1 } } } | | | 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 | 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 | $::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 | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < > > > > > > > > > > > | < | | 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 | $::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 | 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}] | | | | | | | | | | | | | | > | | > | > < < > > > | | | | > > > > | | | | > | | | | | | | > > | | | | | | | | | > | | | | | | | | > > | | | | > > | | > | | > > > | | | < < < | | < < < | | | | | | | | | | | | < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | > > > > > | > | | | | > > > > > | | | | | > | | | | | | < < < < | < < < < < | | | | | | | > > > > > | | | | | | < < | | | > | 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 | 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 } 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} { {*}$::widgets($top,configureRedoDiffCmd) -state normal {*}$::widgets($top,configureMergeCmd) -state normal } proc disableRedo {top} { {*}$::widgets($top,configureRedoDiffCmd) -state disabled {*}$::widgets($top,configureMergeCmd) -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 | 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} { | > < | | | | | | | | > | > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | 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 | 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 | > > > > > | | 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 | 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 | if {[regexp {^---\s*(\d*)} $line -> sub]} { if {$sub != ""} { set rightLine $sub } set state right continue } | | | | | | > > > > > > > > > > > > > > > > > > > > > > > | | | | | > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | > > > > > > > | | | | | | | | | | | | | | < | > > > > > > > | | | | > | > | | | > > > > | | | > > | | | | | | < > | | | | | | | | | | > > > | > | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | > | | | | | | | | | | | | | | > | | | | > > | | | | | | > | | | > | | | | | | | | | | | | | | | | | | | | | | | > > | | | | | > > > > > > > > > > > > > > | | > | > > | | | | | | | | > > > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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 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 | 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 {*}$::widgets($top,configureEditModeCmd) -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} { {*}$::widgets($top,configureEditModeCmd) -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} { {*}$::widgets($top,configureEditModeCmd) -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 | } } endUndoBlock $wfrom $wto } # Copy a row between text widgets proc copyRow {top from row} { | | | | | | | | | | | < < | | > | | > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > | > | | | | | | < > | | < | | | | < < < | | | | | | | | | | > | | > | | > | > > | | | | > > > > | > > > > > > > > | | > > | > > > > > > > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | < | | < | | | | | | > | | | | | | | > | | | | | | > > > > > > > > > > > | | | | | | | > > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | > > > > > > > | | | | < | > | | | < | | | < < > | | | | > | | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | | | < | | < < < < | < < | < | < | < < | < | > | | | | | | | | | | | | | | | | | | | | | > > > > | > > | | | | < | | | | | < | | | | | | | | | | 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 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 | } } 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} { {*}$::widgets($top,configureAlignCmd) -state normal } proc disableAlign {top} { {*}$::widgets($top,configureAlignCmd) -state disabled } # 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 | $top.balloon.t$x configure -width [string length $text] } # Let geometry requests propagate update idletasks # Is the balloon within the diff window? | | | | | | | > | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < < | | | > | | | | | | | | | | < < < < < < < < < < < < < < | | > > > > | > > > | | | | | | | | | | > > | | | > > | | > > | > > > > > | | | | | | | | | > | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | < < < < | | | | | | | | | | | | > > > > | | | < < | < > | < | < < < > | | < | < | < < > | < | < | < | > > > > > > > > > | < < | > > > | < < | | | > > > > | < < < < | < < | | > > | | < > | | | | > > | < | < > | < | | | | | < | < | < < > | < | < | < < | < > > > | > | > | > | > | > > | < | | | | | | | | | | | | > | < < < > > | | | | | | | | < < < | < < > | | < < < < < < < < < < < < < < < < < < < < < | < < < < < < < < < < < < < < < < < < < < < | | > > > < < | | < | | < < | < < < < < < | > | | | < | | | < < < < < < < < < < | < | < < < < | < < < < | | < | < < > | > > > | > | | > | > > > > | > > > > > | > > > > > > > > > > > > > > > > | > | > > > | > > > > > > > > > | | < > > | < | < < < < > > > > > > > > | > > > > > > > > > > > > > > > | > > | > > > > > | > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > | | | | | > | | > > > | > | > | > | > > > > > > > > > > > > > > > > > | < < < < < < < | < < < < < | < < < < < < < < < < < < < < < < < < < < | | < < | | | < < | < < < | | | | | | | | < < | | < < | | | | | | | | | | | | | | | | | | 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 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 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 | $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 \ "$::widgets($top,wDiff1) configure -wrap \$wrapstate ;\ $::widgets($top,wDiff2) configure -wrap \$wrapstate" psmenu::psmenu $dMenu -top $top { --- "&Reread Source" -cmd EskilRereadSource --- "Normal Cursor" -cmd "normalCursor $top" "Fill X" -cmd "fillWindowX $top" --- # Runtime disable of C version of DiffUtil "Tcl DiffUtil" -cmd DisableDiffUtilC "Evalstats" -cmd {evalstats} "_stats" -cmd {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 } set redoState [expr {$::eskil(debug) == 1 ? "normal" : "disabled"}] psmenu::psmenu $top { "&File" { "Redo &Diff" -cmd "redoDiff $top" -state $redoState \ -cfgvar ::widgets($top,configureRedoDiffCmd) --- "&Open Both..." -cmd "openBoth $top 0" "Open Both (forget)..." -cmd "openBoth $top 1" "Open Left File..." -cmd "openLeft $top" "Open Right File..." -cmd "openRight $top" --- "Open Ancestor File..." -cmd "openAncestor $top" "Open Conflict File..." -cmd "openConflict $top" "Open Patch File..." -cmd "openPatch $top" "&Revision Diff..." -cmd "openRev $top" --- "&Print Pdf..." -cmd "doPrint $top" --- "&Close" -cmd "list cleanupAndExit $top" --- "&Quit" -cmd "cleanupAndExit all" } "&Options" { "&Font" { "&Select..." -command makeFontWin _Radio -var ::Pref(fontsize) -command chFont { 6 7 8 9 10 } } "&Ignore" { "No spaces" -var ::Pref(ignore) -value " " "Space changes (-b)" -var ::Pref(ignore) -value "-b" "All spaces (-w)" -var ::Pref(ignore) -value "-w" --- "Case (-i)" -var ::Pref(nocase) "Empty" -var ::Pref(noempty) "Digits" -var ::Pref(nodigit) } "&Preprocess..." -cmd "EditPrefPreprocess $top" "P&lugins..." -cmd "editPrefPlugins $top" "P&arse" { "Nothing" -var ::Pref(parse) -value 0 "Lines" -var ::Pref(parse) -value 1 "Blocks (small)" -var ::Pref(parse) -value 2 "Blocks" -var ::Pref(parse) -value 3 --- "Characters" -var ::Pref(lineparsewords) -value "0" "Words" -var ::Pref(lineparsewords) -value "1" --- "Fine chunks" -var ::Pref(finegrainchunks) --- "Mark last" -var ::Pref(marklast) } "&Colours..." -cmd makePrefWin "C&ontext" { "Show all lines" -var ::Pref(context) -value -1 "Show only diffs" -var ::Pref(context) -value 0 --- "Context 2 lines" -var ::Pref(context) -value 2 "Context 5 lines" -var ::Pref(context) -value 5 "Context 10 lines" -var ::Pref(context) -value 10 "Context 20 lines" -var ::Pref(context) -value 20 } "Pi&vot" { "1" -var ::Pref(pivot) -value 1 "10" -var ::Pref(pivot) -value 10 "100" -var ::Pref(pivot) -value 100 "1000" -var ::Pref(pivot) -value 1000 "Max" -var ::Pref(pivot) -value 2000000000 } --- "Toolbar" -var ::Pref(toolbar) --- "Save default" -cmd "saveOptions $top" } "&Search" -var searchMenu { # Added below } "&Tools" { "&New Diff Window" -cmd "makeDiffWin $top" "&Directory Diff" -cmd makeDirDiffWin "&Clip Diff" -cmd makeClipDiffWin "&Fourway Diff" -cmd makeFourWayWin "&Table Diff" -cmd "makeDiffWin $top -table" "&Merge" -cmd "makeMergeWin $top" -state disabled \ -cfgvar ::widgets($top,configureMergeCmd) "&Edit Mode" -cmd "allowEdit $top" -acc Ctrl-E -state disabled \ -cfgvar ::widgets($top,configureEditModeCmd) "&Paste Patch" -cmd "doPastePatch $top" "Clear Align" -cmd "clearAlign $top" -state disabled \ -cfgvar ::widgets($top,configureAlignCmd) "Highlight tabs" -cmd "highlightTabs $top" if {$::tcl_platform(platform) eq "windows"} { if { ! [catch {package require registry}]} { --- "Setup &Registry" -cmd makeRegistryWin } } } "&Help" { "&General" -cmd makeHelpWin "&Tutorial" -cmd makeTutorialWin "&Revision Control" -cmd "makeDocWin revision.txt" "&Edit Mode" -cmd "makeDocWin editmode.txt" "&Plugins" -cmd "makeDocWin plugins.txt" --- "&About" -cmd makeAboutWin } } if {[info procs textSearch::searchMenu] != ""} { textSearch::searchMenu $searchMenu } else { $searchMenu add command -label "Text search not available" -state disabled } # 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 | 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} { | < < | | < | | | | | | | | | | | | | 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 | 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 | 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 } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 3830 3831 3832 3833 3834 3835 3836 | 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 | # the Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # #---------------------------------------------------------------------- # $Revision$ #---------------------------------------------------------------------- | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | | | | | | | | | | | | | | | | | | > > > > > > > > | > > > > > > > > > > > > > > > > > > > | | | | | | | | | < < | | | | | | | | | | | 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 | 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 | | | | | | | | | | | | | < < | < | | | | | | | | | | | | | 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 | # the Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # #---------------------------------------------------------------------- # $Revision$ #---------------------------------------------------------------------- | | < < | | | | > > > > > > | | | | | | > | > | > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | 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 | # 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} { | | | | | | | | | | | | | | | | | | > > > | | | | > | | | | | | 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 | $w mark set merges[expr {$t + 1}] end set showFirst 0 if {$firstConflict != -1} { set showFirst $firstConflict } | | | | | > > | | | | | | | | | | | | | | | | | | | > > | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | < | < | < > | | | < < | | < | < | < < > > > | | | | < | | | | | < < < | > > | | | | > | | | < > | | > > | | | | | | > > | | | | | > | 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 | $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]" psmenu::psmenu $w { "&File" { "&Save" -cmd "saveMerge $top" ---- "&Close" -cmd "closeMerge $top" } "&Select" { _Radio -var ::eskil($top,curMergeSel) -cmd "selectMerge $top" { "Left+Right" -value 12 "&Left" -value 1 "&Right" -value 2 "Right+Left" -value 21 } --- "All Left" -cmd "selectMergeAll $top 1" "All Right" -cmd "selectMergeAll $top 2" } "&Goto" { "Previous" -accelerator "Up" -cmd "nextMerge $top -1" "Next" -accelerator "Down" -cmd "nextMerge $top 1" if {$anyC} { "Previous Conflict" -accelerator "Ctrl-Up" -cmd "nextMerge $top -1000" "Next Conflict" -accelerator "Ctrl-Down" -cmd "nextMerge $top 1000" } "Previous 10" -accelerator "Shift-Up" -cmd "nextMerge $top -10" "Next 10" -accelerator "Shift-Down" -cmd "nextMerge $top 10" } } # Test how to add more cascade in more calls psmenu::psmenu $w { "&Config" { "Line end LF" -value lf -var ::eskil($top,mergetranslation) "Line end CRLF" -value crlf -var ::eskil($top,mergetranslation) if {$::eskil($top,mode) eq "conflict"} { ---- "Pure" -var ::eskil($top,modetype) \ -onvalue "Pure" -offvalue "" -cmd "doDiff $top" } } } 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 | 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} { | | | < < < < < < | < | | | | > > > > > > > > | | > > > > | > | | > > > > | > | | | > | | < < < < < < | | | > > | < > > > > > > > > > | < < < < < < | | | > > | < > > > > > > > > > | | > > > | | > > > | > | 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 | 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 | #---------------------------------------------------------------------- # Eskil, Plugin handling # | | | 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 | 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 } | | > > > > > > | > | > > > > > | > > > | > | | | | | | | | | | > | | | | | > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > | > > > > > > | > | > > | | > > > > | > | | | | > > | | > > > > > > > > > > | | > | > | > > | > > | > > > > > > > | | | | < | | > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > | > | > > > > > > | > > > > > > > > > > > > > > | > > > > | | | | > > | | < | | < < | > > > > | | > > | | | > > | > | < > > | > > > > > > > > > > > > > > > > > > > > > | | | > | > > | | | < | | > | > > | | | > > | > > > | | | | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < > > | > | > > > > > | < > > > > | > > > > > > > > > | < < > > > | | > > > > | < > > | > > > > > > > > > | > | > > > > > > > | > > > > > > > | | > | > > > | > | > > > | | > > > > | > > > | > > > > | > > > > > | 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 | set res [format "%*s" $maxlen $res] } return $res } # Process the line numbers from the line number widget into a list # of "linestarters" | | | | 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 | 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 | | | > > > > | | 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 | } elseif {$w2 > $w1} { for {set t $w1} {$t < $w2} {incr t} { lappend wraplines1 {} } } } | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | $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 | > > > > > | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > > > > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | > | > | | | > | | < < < < | < | < < | | | > | | | | > > > > > | | < < < | > > | | | | | | | > > | | | | | | | > | | | | | | | > > | | | > > > | < | | | > > | | | | | | | | > | | | < | < | > < | 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 | 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} | > | | | > > | 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 | $pdf rectangle 0 $options(-headsize) \ $width [- $height $options(-headsize)] # Center line $pdf line [/ $width 2.0] $options(-headsize) \ [/ $width 2.0] $height # Header | | | | 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 | # the Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. # #---------------------------------------------------------------------- # $Revision$ #---------------------------------------------------------------------- | | | | 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 | 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) | | | > > > > | 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 | # 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 | | | | > > > > > > > > > > > > > > > > > > > > > > > > | < < < < < | < < < < < < < < | < < < < < < < < | 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 | set dir [pwd] } else { set dir [file dirname $file] } if {[auto_execok cleartool] != ""} { set old [pwd] cd $dir | | | < < < < < < < < | < < < < < < < < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | > > > > > > > > > | | | | > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | if {$old != ""} { cd $old } } # Get a HG patch | | > > > > > | > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > | | 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 | if {$old != ""} { cd $old } } # Get a BZR patch | | | > > > > > | > > > > > < < | < < < < < < < | < < < | < < < < < < < | < | > > > > > > > > > > > > > > > > > > > | < | > > > > | > > > > | | > | > | | | > | > > | > > > > | | < | | | | | | > > | > > > > | | | | > > > > > > > | | > > > > > | > > > > > > > > > > > | | | | > > > > > > | | > > > > > | 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 | } set cmd [list exec cvs -n status [file nativename $filename]] if {[catch {eval $cmd} res]} { # What to do here? set rev "1.1" } else { | | | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > | > | > > > > > > > | > > > | > | > > > > > | > > > > > > > > | 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 | 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 { | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < > > | < < | < | > > > > > > > > > > > > > > > > > | | | | < > > > > | | | | | > > > | > > | > | 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 | 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 | | | | | 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 | 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]] ..." | | > > | < < < | > > > | > | > > > > > > > > > > > > > > > > | > > > > > > > > > > > | < < < | | > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | } 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 {} | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | | | < < > | | | | | | > | | | | | | > > > | | | | | | | | < < | | | | | > > > > > | > | > > > > > > > > > > > | | | > < < > | | | | | | > > | | > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > | | > > > > | | | | | | > > > > > > > > > > > > > > > > > > | | > > > | | | > > > > | > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > | | | 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 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 | #!/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 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 package require psmenu package require psdebug namespace import ::_PsDebug::* if {[catch {package require psballoon}]} { # Add a dummy if it does not exist. proc addBalloon {args} {} } else { namespace import -force psballoon::addBalloon } 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 # 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) {} # Version string is loaded from version.txt later set ::eskil(diffver) "Version Unknown" set ::eskil(thisScript) [file join [pwd] [info script]] 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] } set libDir [file join $::eskil(thisDir) .. lib] if { ! [file isdirectory $libDir]} { # Try the local file from devel set libDir [file join $::eskil(thisDir) .. eskil.vfs lib] } ::tcl::tm::path add $libDir 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 | #!/bin/sh #---------------------------------------------------------------------- # $Revision$ #---------------------------------------------------------------------- # the next line restarts using tclsh \ | | | > > > > | | | | 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 | foreach name $::stubs { rename $name {} rename _stub_$name $name } set ::stubs {} } | > > | < | | < | > | 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 | #------------------------------------------------------------*- 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 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 | } -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"] | | | | 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 | #------------------------------------------------------------*- tcl -*- # Tests for GUI #---------------------------------------------------------------------- | < < | > > > > > > > | | | 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 | } 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 | | | 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 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 | + variable TcDFHeader : out TcDFH_T); procedure TcAddrCalc(PresentAddr : in integer; AccWidth : in DynamicSize_T; } displayPatch gurka | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 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 | #------------------------------------------------------------*- 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 | #------------------------------------------------------------*- 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 | ------------------------------------------ } } } return } default { | | | > | 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 | 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 | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 } |