├── LICENSE ├── README.md ├── screenshot.png ├── tedit └── tedit.desktop /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014-current Thanos Zygouris 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ---- 2 | 3 | **tEdit** is a simple tabbed text editor written in core [Tcl/Tk](https://www.tcl.tk). 4 | 5 | ---- 6 | 7 | ### Features 8 | 9 | * Tabs 10 | * On Top 11 | * Word Wrap 12 | * Mark Lines 13 | * Auto Indent 14 | * Block Indent 15 | * Go to Line 16 | * Line Numbers 17 | * Custom Folding 18 | * Block Selection 19 | * Recent File List 20 | * File Browser Panel 21 | * Unlimited Undo/Redo 22 | * Search/Replace with [Regular Expressions](https://www.tcl.tk/man/tcl/TclCmd/re_syntax.htm) support 23 | * Tabs Menu (right mouse button) 24 | * Rearrange Tabs (drag'n'drop) 25 | * In-Text arithmetic calculations 26 | * Tk Themes 27 | * Colors and Fonts 28 | * Soft Tabs (Spaces instead of Tab) 29 | * Customizable Tab Size 30 | * Line Spacing (above/below lines and wraps) 31 | * Base64 Encoder 32 | * Command Line Support 33 | * Runs in GNU/Linux, [Microsoft Windows](#for-microsoft-windows-users) and Unix (MacOS not tested) 34 | 35 | ---- 36 | 37 | ### Screenshot 38 | 39 | ![Screenshot](screenshot.png "Screenshot") 40 | 41 | ---- 42 | 43 | ### Dependencies 44 | 45 | **Tcl** version 8.6 or later. 46 | 47 | **Tk** version 8.6 or later. 48 | 49 | ##### For Microsoft Windows users: 50 | 51 | [ActiveTcl](https://www.activestate.com/activetcl) version 8.6 or later. 52 | 53 | For executables go to [Releases](https://github.com/thanoulis/tedit/releases) section, or download the [latest](https://github.com/thanoulis/tedit/releases/latest/download/tedit.exe). 54 | 55 | To run it, after download, right-click->Properties->Unblock. 56 | 57 | ---- 58 | 59 | ### License 60 | 61 | **tEdit** is licensed under the **MIT License**. 62 | 63 | Read [LICENSE](LICENSE) for details. 64 | 65 | ---- 66 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thanoulis/tedit/b8a383bf5d776d27175bc975cbf38d9db4503901/screenshot.png -------------------------------------------------------------------------------- /tedit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tclsh 2 | 3 | package require Tk 4 | # force utf-8 encoding (freewrap workaround) 5 | if {[encoding system] ne "utf-8"} {encoding system "utf-8"} 6 | 7 | ################################################################################ 8 | # VARIABLES 9 | # 10 | namespace eval tEdit { 11 | array set filename {} 12 | array set folds {} 13 | array set marks {} 14 | array set marknum {} 15 | array set modified {} 16 | 17 | variable version {1.7.11} 18 | variable safe [interp create -safe] 19 | variable filepath {} 20 | variable message {} 21 | variable encoding [encoding system] 22 | variable tabnum 0 23 | variable untitlednum 1 24 | variable savename {} 25 | variable recent [list] 26 | variable filetypes {{{All Files} {*}} 27 | {{Text Files} {*.txt *.TXT}} {{Log Files} {*.log *.LOG}} 28 | {{C Files} {*.c *.C}} {{C++ Files} {*.cpp *.CPP}} {{Header Files} {*.h *.H}} 29 | {{Go Files} {*.go *.GO}} 30 | {{Perl Files} {*.pl *.PL}} {{Perl Modules} {*.pm *.PM}} 31 | {{Python Files} {*.py *.PY}} 32 | {{Ruby Files} {*.rb *.RB}} 33 | {{Rust Files} {*.rs *.RS}} 34 | {{Tcl Files} {*.tcl *.TCL}} 35 | } 36 | variable goto {} 37 | variable hgoto [list] 38 | variable showgoto {false} 39 | variable blockselect [list] 40 | variable blockpaste {false} 41 | variable search {} 42 | variable hsearch [list] 43 | variable regexp {-exact} 44 | variable replace {} 45 | variable hreplace [list] 46 | variable showsearch {false} 47 | variable showreplace {false} 48 | variable showmarks {false} 49 | variable browser {false} 50 | variable linenumbers {true} 51 | variable showmenu {.menu} 52 | variable showstatus {true} 53 | variable showyscroll {true} 54 | variable showxscroll {true} 55 | variable ontop {false} 56 | variable precision 3 57 | variable autoindent {true} 58 | variable wrap {none} 59 | variable blinkcursor 500 60 | variable blockcursor {false} 61 | variable textfont {TkFixedFont} 62 | variable spacing1 0 63 | variable spacing2 0 64 | variable spacing3 0 65 | variable colorscheme {default} 66 | variable theme [ttk::style theme use] 67 | variable tabsize 2 68 | variable softtabs {true} 69 | 70 | namespace eval DnD { 71 | variable timer {} 72 | variable start {} 73 | } 74 | } 75 | # add precision and math functions 76 | # for use with [tEdit::Calculate] 77 | namespace eval tcl::mathfunc { 78 | proc precision {number precision} { 79 | expr {double(round(10 ** $precision * $number)) / 10 ** $precision} 80 | } 81 | # e = exp(1) 82 | proc e {} {return 2.718281828459045} 83 | # π = acos(-1) 84 | proc pi {} {return 3.141592653589793} 85 | # φ = (1+sqrt(5))/2 86 | proc phi {} {return 1.618033988749895} 87 | # trigonometric functions with degrees 88 | proc sind {x} {expr {sin($x * pi() / 180)}} 89 | proc cosd {x} {expr {cos($x * pi() / 180)}} 90 | proc tand {x} {expr {tan($x * pi() / 180)}} 91 | proc asind {x} {expr {asin($x) * 180 / pi()}} 92 | proc acosd {x} {expr {acos($x) * 180 / pi()}} 93 | proc atand {x} {expr {atan($x) * 180 / pi()}} 94 | # teach safe interpreter the new functions 95 | foreach function [info procs [namespace current]::*] { 96 | $tEdit::safe alias $function $function 97 | } 98 | # use ln() instead of log() 99 | $tEdit::safe alias [namespace current]::ln [namespace current]::log 100 | # use log() instead of log10() 101 | $tEdit::safe alias [namespace current]::log [namespace current]::log10 102 | } 103 | 104 | ################################################################################ 105 | # PROCEDURES 106 | # 107 | proc tEdit::Message {msg {timer 5000}} { 108 | after cancel {set tEdit::message ""} 109 | set tEdit::message $msg 110 | after $timer {set tEdit::message ""} 111 | } 112 | 113 | proc tEdit::RecentAdd {filename} { 114 | if {$filename ni $tEdit::recent} { 115 | lappend tEdit::recent $filename 116 | .menu.file entryconfigure "Recent Files" -state normal 117 | .menu.file.recent add command -label $filename -command [list tEdit::OpenFile $filename] 118 | } 119 | } 120 | 121 | proc tEdit::Modified {text} { 122 | set tEdit::modified(text) [expr {[$text edit modified] ? "\u2022" : ""}] 123 | } 124 | 125 | proc tEdit::NewTab {{position ""} {filename ""}} { 126 | set nb .nb 127 | set tabnum $tEdit::tabnum 128 | # set array for tabbed widgets 129 | array set tab [list \ 130 | {tab} "${nb}.tab${tabnum}"\ 131 | {canvas} "${nb}.tab${tabnum}.canvas"\ 132 | {text} "${nb}.tab${tabnum}.text"\ 133 | {yscroll} "${nb}.tab${tabnum}.yscroll"\ 134 | {xscroll} "${nb}.tab${tabnum}.xscroll"\ 135 | {modified} "${nb}.tab${tabnum}.modified"\ 136 | ] 137 | # set arrays specific to this tab 138 | array set tEdit::filename [list $tab(tab) $filename] 139 | array set tEdit::folds [list $tab(text) [list]] 140 | array set tEdit::marks [list $tab(text) [list]] 141 | array set tEdit::marknum [list $tab(text) 0] 142 | array set tEdit::modified [list $tab(text) ""] 143 | # increase tab number for future use 144 | incr tEdit::tabnum 145 | 146 | ttk::frame $tab(tab) 147 | tk::canvas $tab(canvas) -width 10 -highlightthickness 0 -background "#FFFF00" 148 | tk::text $tab(text) -relief sunken -highlightthickness 0 -takefocus 1 \ 149 | -font $tEdit::textfont -undo true -maxundo 0 -autoseparators true \ 150 | -insertunfocussed hollow -wrap $tEdit::wrap \ 151 | -tabstyle wordprocessor -tabs $tEdit::tabsize \ 152 | -blockcursor $tEdit::blockcursor -insertofftime $tEdit::blinkcursor \ 153 | -spacing1 $tEdit::spacing1 -spacing2 $tEdit::spacing2 -spacing3 $tEdit::spacing3 \ 154 | -xscrollcommand [list $tab(xscroll) set] -yscrollcommand [list $tab(yscroll) set] 155 | # text tags management and starting colors 156 | $tab(text) tag configure tag_search -foreground "#FFFF00" -background "#000000" 157 | $tab(text) tag configure tag_search_all -foreground "#000000" -background "#FFFF00" 158 | $tab(text) tag raise tag_search tag_search_all 159 | ttk::scrollbar $tab(yscroll) -orient vertical -command [list $tab(text) yview] 160 | ttk::scrollbar $tab(xscroll) -orient horizontal -command [list $tab(text) xview] 161 | ttk::label $tab(modified) -textvariable tEdit::modified(text) 162 | 163 | grid $tab(canvas) -in $tab(tab) -row 0 -column 0 -sticky ns 164 | grid $tab(text) -in $tab(tab) -row 0 -column 1 -sticky nswe 165 | grid $tab(yscroll) -in $tab(tab) -row 0 -column 2 -sticky ns 166 | grid $tab(xscroll) -in $tab(tab) -row 1 -column 0 -sticky we -columnspan 2 167 | grid $tab(modified) -in $tab(tab) -row 1 -column 2 168 | grid rowconfigure $tab(tab) $tab(text) -weight 1 169 | grid columnconfigure $tab(tab) $tab(text) -weight 1 170 | # remove canvas from view 171 | grid remove $tab(canvas) 172 | 173 | # need to disable this for Tabs Traversal 174 | bind $tab(text) {return 0} 175 | # bindings for block selections 176 | bind Text { 177 | set tEdit::blockselect [list %x %y] 178 | %W mark set insert @%x,%y 179 | %W see insert 180 | break 181 | } 182 | bind $tab(text) { 183 | if {$tEdit::blockselect ne ""} { 184 | tEdit::BlockSelect %W %x %y 185 | %W mark set insert @%x,%y 186 | %W see insert 187 | break 188 | } 189 | } 190 | bind Text { 191 | tEdit::BlockSelect %W %x %y 192 | set tEdit::blockselect [list] 193 | break 194 | } 195 | 196 | if {$filename eq ""} { 197 | set filename "Untitled $tEdit::untitlednum" 198 | incr tEdit::untitlednum 199 | } else { 200 | set filename [file tail $filename] 201 | } 202 | # where to insert new tab 203 | if {$position eq "" || ($position + 1) >= [$nb index end]} { 204 | set position end 205 | } else { 206 | incr position 207 | } 208 | $nb insert $position $tab(tab) -text $filename 209 | tEdit::TabOptions new $tab(tab) 210 | $nb select $tab(tab) 211 | focus $tab(text) 212 | return $tab(text) 213 | } 214 | 215 | proc tEdit::CloseTab {tab {nb ".nb"}} { 216 | if {[llength $tab] > 1} {set tab [$nb select]} 217 | set text "${tab}.text" 218 | if {[tEdit::AskSave $tab $text] != 0} {return 2} 219 | $nb forget $tab 220 | destroy $tab 221 | if {[$nb index end] < 1} {exit} 222 | } 223 | 224 | proc tEdit::OpenFileDialog {} { 225 | set filenames [tk_getOpenFile -title "Open File" -parent . \ 226 | -filetypes $tEdit::filetypes -multiple true] 227 | if {$filenames eq ""} {return 2} 228 | foreach filename $filenames { 229 | tEdit::OpenFile $filename 230 | } 231 | } 232 | 233 | proc tEdit::OpenFile {filename} { 234 | foreach tab [.nb tabs] { 235 | if {$filename eq $tEdit::filename($tab)} { 236 | .nb select $tab 237 | tEdit::Message "[file tail $filename] already open" 238 | return 1 239 | } 240 | } 241 | if {[file type $filename] ne "file"} { 242 | puts stderr "Cannot open: $filename is [file type $filename]" 243 | tk_messageBox -title "Cannot open file!" -icon error -type ok \ 244 | -parent . -message "Cannot open file:" \ 245 | -detail "${filename}\nis a [file type $filename]." 246 | return 1 247 | } 248 | if {[catch {set data [open $filename r]} msg]} { 249 | puts stderr $msg 250 | tEdit::Message [lindex [split $msg ":"] 1] 251 | return 1 252 | } 253 | chan configure $data -translation auto 254 | set text [tEdit::NewTab [.nb index current] $filename] 255 | $text insert end [chan read -nonewline $data] 256 | chan close $data 257 | $text mark set insert 0.0 258 | $text see insert 259 | $text edit modified 0 260 | $text edit reset 261 | tEdit::RecentAdd $filename 262 | } 263 | 264 | proc tEdit::GetPipedData {} { 265 | chan event stdin readable {} 266 | set data "" 267 | while {[chan gets stdin line] >= 0} { 268 | append data $line "\n" 269 | } 270 | set data [string range $data 0 end-1] 271 | if {[string length $data] > 0} { 272 | set text [tEdit::NewTab {} "<>"] 273 | $text insert end $data 274 | } 275 | } 276 | 277 | proc tEdit::StoreFilename {filename} { 278 | set tab [.nb select] 279 | set tEdit::filename($tab) $filename 280 | .nb tab $tab -text [file tail $filename] 281 | } 282 | 283 | proc tEdit::SaveFileDialog {} { 284 | set tabname [.nb tab [.nb select] -text] 285 | set filename [tk_getSaveFile -title "Save As" -parent . \ 286 | -filetypes $tEdit::filetypes -initialfile $tabname] 287 | if {$filename eq ""} {return 2} 288 | if {[file exists $filename]} { 289 | if {[file type $filename] ne "file"} { 290 | puts stderr "Cannot save: $filename is [file type $filename]" 291 | tk_messageBox -title "Cannot save file!" -icon error -type ok \ 292 | -parent . -message "Cannot save [file tail $filename]:" \ 293 | -detail "$filename\nis a [file type $filename]." 294 | return 1 295 | } elseif {[file writable $filename] == 0} { 296 | puts stderr "Cannot save: $filename is read only" 297 | tk_messageBox -title "Cannot save file!" -icon error -type ok \ 298 | -parent . -message "Cannot save [file tail $filename]:" \ 299 | -detail "${filename}\nis read only." 300 | return 1 301 | } 302 | } 303 | set tEdit::savename $filename 304 | return 0 305 | } 306 | 307 | proc tEdit::SaveFile {filename text} { 308 | if {$filename eq ""} { 309 | if {[tEdit::SaveFileDialog] == 0} { 310 | set filename $tEdit::savename 311 | } else { 312 | return 2 313 | } 314 | } 315 | if {[catch {set data [open $filename w]} msg]} { 316 | puts stderr $msg 317 | tEdit::Message [lindex [split $msg ":"] 1] 318 | return 1 319 | } 320 | chan puts -nonewline $data [$text get 0.0 end] 321 | chan flush $data 322 | chan close $data 323 | $text edit modified 0 324 | tEdit::StoreFilename $filename 325 | tEdit::RecentAdd $filename 326 | tEdit::Message "[file tail $filename] saved" 327 | set tEdit::filepath [file nativename $filename] 328 | return 0 329 | } 330 | 331 | proc tEdit::SaveAs {text} { 332 | if {[tEdit::SaveFileDialog] == 0} { 333 | tEdit::SaveFile $tEdit::savename $text 334 | } 335 | } 336 | 337 | proc tEdit::AskSave {tab text} { 338 | if {[$text edit modified]} { 339 | set answer [tk_messageBox -title "Save file?" -icon warning -parent . \ 340 | -type yesnocancel -default yes \ 341 | -message "[.nb tab $tab -text] has changed!" -detail "Save changes?"] 342 | switch -- $answer { 343 | yes { 344 | if {$tEdit::filename($tab) eq ""} { 345 | if {[tEdit::SaveFileDialog] == 0} { 346 | return [tEdit::SaveFile $tEdit::savename $text] 347 | } else { 348 | return 2 349 | } 350 | } else { 351 | return [tEdit::SaveFile $tEdit::filename($tab) $text] 352 | } 353 | } 354 | no {return 0} 355 | cancel {return 2} 356 | } 357 | } 358 | return 0 359 | } 360 | 361 | proc tEdit::CloseAll {{noclose ""}} { 362 | foreach tab [.nb tabs] { 363 | # 'close others' tabs menu command 364 | if {$tab eq $noclose} { 365 | continue 366 | } 367 | .nb select $tab 368 | if {[tEdit::CloseTab $tab] == 2} {return 2} 369 | } 370 | } 371 | 372 | proc tEdit::UndoStack {text} { 373 | foreach {item action} {Undo canundo Redo canredo} { 374 | .menu.edit entryconfigure $item \ 375 | -state [expr {[$text edit $action] ? "normal" : "disabled"}] 376 | } 377 | } 378 | 379 | proc tEdit::UndoRedo {text action} { 380 | if {[catch {$text edit $action} msg]} { 381 | tEdit::Message $msg 382 | } 383 | } 384 | 385 | proc tEdit::SelectAll {text} { 386 | if {[$text get 0.0 "end -1 indices"] eq ""} {return 1} 387 | $text tag add sel 0.0 "end -1 indices" 388 | } 389 | 390 | proc tEdit::PopulateBrowser {node {tree ".tree"}} { 391 | set path [$tree set $node fullpath] 392 | $tree delete [$tree children $node] 393 | if {[catch {glob -nocomplain -directory $path *} msg]} { 394 | tEdit::Message [lindex [split $msg ":"] 1] 395 | return 1 396 | } 397 | foreach f [lsort -dictionary [glob -nocomplain -directory $path *]] { 398 | set type [file type $f] 399 | set id [$tree insert $node end -text [file tail $f] \ 400 | -values [list $f $type]] 401 | if {$type eq "directory"} { 402 | $tree insert $id 0 403 | $tree item $id -text [file tail $f]/ 404 | } 405 | } 406 | } 407 | 408 | proc tEdit::StartBrowser {{tree ".tree"}} { 409 | foreach dir [lsort -dictionary [file volumes]] { 410 | tEdit::PopulateBrowser [$tree insert {} end -text $dir \ 411 | -values [list $dir directory]] $tree 412 | } 413 | } 414 | 415 | proc tEdit::OpenFileBrowser {node {tree ".tree"}} { 416 | if {$node eq ""} {return 2} 417 | if {[$tree set $node type] eq "directory"} { 418 | tEdit::PopulateBrowser $node $tree 419 | } else { 420 | tEdit::OpenFile [$tree set $node fullpath] 421 | } 422 | } 423 | 424 | proc tEdit::ToggleBrowser {show} { 425 | if {$show eq "true"} { 426 | .main insert 0 .browser 427 | } else { 428 | .main forget .browser 429 | } 430 | } 431 | 432 | proc tEdit::Copy {text action} { 433 | if {[set selected [$text tag ranges sel]] eq ""} {return 1} 434 | if {[llength $selected] > 2} { 435 | set prev [$text index sel.first] 436 | foreach {first last} $selected { 437 | set lines [expr {$first - $prev}] 438 | if {$lines >= 2} { 439 | set length [string length [$text get $first $last]] 440 | for {set i 1} {$i < $lines} {incr i} { 441 | append data [string repeat " " $length] "\n" 442 | } 443 | } 444 | append data [$text get $first $last] "\n" 445 | if {$action eq "Cut"} { 446 | $text delete $first $last 447 | } 448 | set prev $first 449 | } 450 | set data [string trimright $data "\n"] 451 | clipboard clear 452 | clipboard append -type STRING -- $data 453 | set tEdit::blockpaste "true" 454 | } else { 455 | if {$action eq "Cut"} { 456 | tk_textCut $text 457 | } else { 458 | tk_textCopy $text 459 | } 460 | set tEdit::blockpaste "false" 461 | } 462 | } 463 | 464 | proc tEdit::Paste {text} { 465 | if {$tEdit::blockpaste eq "true"} { 466 | set type [expr {[tk windowingsystem] eq "x11" ? "UTF8_STRING" : "STRING"}] 467 | set data [split [clipboard get -type $type] "\n"] 468 | set cursor [$text index insert] 469 | set ycursor [lindex [split $cursor "."] end] 470 | set lastline [$text index end] 471 | set i 0 472 | foreach block $data { 473 | set line [expr {$cursor + $i}] 474 | if {$line >= $lastline} { 475 | $text insert end "\n" 476 | $text see "$cursor +${i} lines" 477 | } 478 | set eol [$text index "$line lineend"] 479 | set yeol [lindex [split $eol "."] end] 480 | set missing [expr {$ycursor - $yeol}] 481 | if {$missing > 0} { 482 | $text insert $eol [string repeat " " $missing] 483 | } 484 | $text insert "$cursor +${i} lines" $block 485 | incr i 486 | } 487 | } else { 488 | tk_textPaste $text 489 | $text see insert 490 | } 491 | } 492 | 493 | proc tEdit::BlockSelect {text x2 y2} { 494 | selection clear 495 | set x1 [lindex $tEdit::blockselect 0] 496 | set y1 [lindex $tEdit::blockselect 1] 497 | if {$x1 > $x2} {set x $x2;set endx $x1} else {set x $x1;set endx $x2} 498 | if {$y1 > $y2} {set y $y2;set endy $y1} else {set y $y1;set endy $y2} 499 | while {$y <= $endy} { 500 | $text tag add sel "@$x,$y" "@$endx,$y" 501 | incr y 502 | } 503 | } 504 | 505 | proc tEdit::DeleteSelection {text key} { 506 | if {[set selected [$text tag ranges sel]] eq ""} { 507 | switch -- $key { 508 | Delete {$text delete "insert"} 509 | Backspace {$text delete "insert -1 indices"} 510 | default {return 1} 511 | } 512 | } else { 513 | foreach {first last} $selected { 514 | $text delete $first $last 515 | } 516 | } 517 | } 518 | 519 | proc tEdit::BlockIndent {text action} { 520 | if {$tEdit::softtabs eq "true"} { 521 | set indent [string repeat " " $tEdit::tabsize] 522 | } else { 523 | set indent "\t" 524 | } 525 | # check if a selection exists 526 | if {[set selected [$text tag ranges sel]] eq ""} { 527 | foreach idx {linestart lineend} { 528 | lappend blockindent [$text index "insert $idx"] 529 | } 530 | } else { 531 | set first [expr {int([lindex $selected 0])}] 532 | set last [expr {int([lindex $selected end])}] 533 | for {set line $first} {$line <= $last} {incr line} { 534 | if {[$text get "${line}.0" "${line}.end"] ne ""} { 535 | lappend blockindent "${line}.0" "${line}.end" 536 | } 537 | } 538 | } 539 | switch -- $action { 540 | increase { 541 | foreach {first last} $blockindent { 542 | $text insert $first $indent 543 | } 544 | tEdit::Message "increased indentation" 545 | } 546 | decrease { 547 | set indent [string length $indent] 548 | set whitespace "" 549 | foreach {first last} $blockindent { 550 | $text search -regexp -all -count whitespace -- {^([ \t]*)} $first $last 551 | if {$whitespace >= $indent} { 552 | $text delete [$text index $first] [$text index "$first +${indent} indices"] 553 | } elseif {$whitespace >= 1} { 554 | $text delete [$text index $first] [$text index "$first +${whitespace} indices"] 555 | } else { 556 | set whitespace "" 557 | } 558 | } 559 | if {$whitespace eq ""} { 560 | tEdit::Message "cannot decrease further" 561 | } else { 562 | tEdit::Message "decreased indentation" 563 | } 564 | } 565 | default { 566 | return 1 567 | } 568 | } 569 | } 570 | 571 | proc tEdit::AutoIndent {text} { 572 | if {$tEdit::autoindent eq "true"} { 573 | bind $text { 574 | # find any whitespace at the start of the current line 575 | set first [%W index "insert linestart"] 576 | set last [%W index "insert lineend"] 577 | regexp {^([ \t]*)} [%W get $first $last] -> whitespace 578 | # create a newline, insert whitespace 579 | %W insert insert "\n${whitespace}" 580 | # if necessary, scroll the view so cursor is visible 581 | %W see insert 582 | break 583 | } 584 | } else { 585 | bind $text { 586 | %W insert insert "\n" 587 | %W see insert 588 | break 589 | } 590 | } 591 | } 592 | 593 | proc tEdit::Calculate {text} { 594 | if {[$text tag ranges sel] eq ""} { 595 | set first [$text index "insert linestart"] 596 | set last [$text index "insert lineend"] 597 | } else { 598 | set first [$text index sel.first] 599 | set last [$text index sel.last] 600 | } 601 | set math [$text get $first $last] 602 | try { 603 | $tEdit::safe eval [list expr [string map {/ *1.0/} $math]] 604 | } on error msg { 605 | tEdit::Message [lindex [split $msg "\n"] 0] 606 | } on ok result { 607 | # round to user defined decimal points 608 | set result [expr {precision($result,$tEdit::precision)}] 609 | # avoid printing extra decimal zeros 610 | set result0 [expr {round($result)}] 611 | if {$result0 == $result} { 612 | set result $result0 613 | } 614 | $text insert $last " = $result" 615 | } 616 | } 617 | 618 | proc tEdit::Fold {text} { 619 | if {[set selected [$text tag ranges sel]] eq ""} { 620 | tEdit::Message "select lines to fold" 621 | return 1 622 | } 623 | set first [$text index "sel.first linestart"] 624 | set firsteol [$text index "sel.first lineend"] 625 | set last [$text index "sel.last lineend"] 626 | set dfirst [expr {int($first)}] 627 | set dlast [expr {int($last)}] 628 | if {[set foldsum [expr {($dlast - $dfirst) + 1}]] == 1} { 629 | tEdit::Message "cannot fold a single line" 630 | return 1 631 | } 632 | # check if any older folds exist in current selection 633 | # and unfold them 634 | foreach old $tEdit::folds($text) { 635 | set oldtag [$text tag ranges $old] 636 | set oldfirst [lindex $oldtag 0] 637 | set oldlast [lindex $oldtag end] 638 | if {[$text compare $first <= $oldfirst] && [$text compare $last >= $oldlast]} { 639 | $old invoke 640 | } 641 | } 642 | # get first line to show in fold button text 643 | set buttontext "\u25b6\u25b6 $foldsum lines: [$text get $first $firsteol]" 644 | set fold [string cat $text ".fold" $dfirst "-" $dlast] 645 | tk::button $fold -text $buttontext -relief flat -padx 0 -pady 0 \ 646 | -takefocus 0 -cursor "hand2" -font $tEdit::textfont \ 647 | -background [$text cget -background] \ 648 | -foreground [$text cget -foreground] \ 649 | -command [list tEdit::Unfold $text $fold $first] 650 | $text tag add $fold $first $last 651 | $text tag configure $fold -elide true 652 | $text window create $first -window $fold 653 | $text mark set insert "$last +1 lines" 654 | selection clear 655 | lappend tEdit::folds($text) $fold 656 | .menu.edit entryconfigure "Unfold All" -state normal 657 | } 658 | 659 | proc tEdit::Unfold {text fold {index ""}} { 660 | # store modified flag, to restore it later 661 | set modified [$text edit modified] 662 | switch -- $fold { 663 | all { 664 | foreach fold $tEdit::folds($text) { 665 | $fold invoke 666 | } 667 | } 668 | default { 669 | $text tag configure $fold -elide false 670 | $text tag delete $fold 671 | # destroy fold button (same name with fold tag) 672 | destroy $fold 673 | # delete the extra index which held the button 674 | $text delete $index 675 | set tEdit::folds($text) \ 676 | [lsearch -inline -all -not -exact $tEdit::folds($text) $fold] 677 | } 678 | } 679 | # restore modified flag ('$text delete $index' modifies $text) 680 | $text edit modified $modified 681 | if {[llength $tEdit::folds($text)] == 0} { 682 | .menu.edit entryconfigure "Unfold All" -state disabled 683 | } 684 | } 685 | 686 | proc tEdit::Search {what {direction "nextrange"}} { 687 | if {$what ni $tEdit::hsearch} { 688 | lappend tEdit::hsearch $what 689 | } 690 | if {[tEdit::SearchAll $what] == 1} {return 1} 691 | set text "[.nb select].text" 692 | if {$direction eq "prevrange"} { 693 | set newpos "insert -1 indices" 694 | set loop [list end-1 end] 695 | } else { 696 | set newpos "insert +1 indices" 697 | set loop [list 0 1] 698 | } 699 | set taglist [$text tag ranges tag_search_all] 700 | set lastsearch [$text tag ranges tag_search] 701 | set tagsearch [$text tag $direction tag_search_all insert] 702 | # if same search, move insert by one 703 | if {$tagsearch eq $lastsearch} { 704 | set tagsearch [$text tag $direction tag_search_all $newpos] 705 | } 706 | # if search returns null, search from top/bottom 707 | if {$tagsearch eq ""} { 708 | set tagsearch [lrange $taglist {*}$loop] 709 | } 710 | tEdit::ClearHighlight "tag_search" 711 | $text tag add tag_search {*}$tagsearch 712 | $text mark set insert tag_search.first 713 | $text see tag_search.first 714 | set sum [expr {[llength $taglist] / 2}] 715 | set count [lsearch -exact $taglist [lindex $tagsearch 0]] 716 | set count [expr {($count / 2) + 1}] 717 | set matches [expr {$sum == 1 ? "match" : "matches"}] 718 | tEdit::Message "$count of $sum $matches" 719 | } 720 | 721 | proc tEdit::SearchAll {what} { 722 | if {$what eq ""} { 723 | tEdit::Message "nothing to search for" 724 | return 1 725 | } 726 | set text "[.nb select].text" 727 | # search only in selection if exists 728 | if {[$text tag ranges sel] ne ""} { 729 | set first "sel.first" 730 | set last "sel.last" 731 | } else { 732 | set first "0.0" 733 | set last "end" 734 | } 735 | set length 0 736 | set search_cmd [list $text search $tEdit::regexp -all -count length -- $what $first $last] 737 | if {[catch {set taglist [{*}$search_cmd]} msg]} { 738 | .cbsearch configure -style error.TCombobox 739 | tEdit::Message [lindex [split $msg ":"] 1] 740 | return 1 741 | } elseif {$taglist eq ""} { 742 | .cbsearch configure -style error.TCombobox 743 | tEdit::Message "no matches found" 744 | return 1 745 | } else { 746 | tEdit::ClearHighlight "tag_search_all" 747 | .cbsearch configure -style valid.TCombobox 748 | set count 0 749 | foreach first $taglist { 750 | set last "$first +[lindex $length $count] indices" 751 | $text tag add tag_search_all $first $last 752 | incr count 753 | } 754 | } 755 | } 756 | 757 | proc tEdit::Replace {what with} { 758 | if {$with ni $tEdit::hreplace} { 759 | lappend tEdit::hreplace $with 760 | } 761 | if {$what eq $with} { 762 | tEdit::Message "search and replace are same" 763 | return 1 764 | } 765 | set text "[.nb select].text" 766 | # if tag_search is empty, search for the first time 767 | if {[set taglist [$text tag ranges tag_search]] eq ""} { 768 | if {[tEdit::Search $what] == 1} { 769 | tEdit::Message "nothing to replace" 770 | return 1 771 | } 772 | # recreate $taglist after search 773 | set taglist [$text tag ranges tag_search] 774 | } 775 | $text edit separator 776 | $text replace {*}$taglist $with 777 | tEdit::Search $what 778 | } 779 | 780 | proc tEdit::ReplaceAll {what with} { 781 | if {$with ni $tEdit::hreplace} { 782 | lappend tEdit::hreplace $with 783 | } 784 | if {$what eq $with} { 785 | tEdit::Message "search and replace are same" 786 | return 1 787 | } 788 | if {[tEdit::SearchAll $what] == 1} { 789 | tEdit::Message "nothing to replace" 790 | return 1 791 | } 792 | set text "[.nb select].text" 793 | set taglist [$text tag ranges tag_search_all] 794 | $text edit separator 795 | set count 0 796 | foreach {first last} $taglist { 797 | $text replace $first $last $with 798 | incr count 799 | } 800 | tEdit::Message "replace finished: $count substitutions" 801 | } 802 | 803 | proc tEdit::ClearHighlight {tags} { 804 | set text "[.nb select].text" 805 | foreach tag $tags { 806 | foreach {first last} [$text tag ranges $tag] { 807 | $text tag remove $tag $first $last 808 | } 809 | } 810 | } 811 | 812 | proc tEdit::MarkToggle {} { 813 | set text "[.nb select].text" 814 | set first [$text index "insert linestart"] 815 | set last [$text index "insert lineend"] 816 | set mark [lsearch -inline -all -glob [$text tag names $first] "Mark#*"] 817 | if {$mark eq ""} { 818 | if {[$text get $first $last] eq ""} { 819 | tEdit::Message "cannot mark: empty line" 820 | return 1 821 | } 822 | set mark "Mark#[incr tEdit::marknum($text)]" 823 | $text mark set $mark $first 824 | $text tag configure $mark -underline true 825 | $text tag raise $mark sel 826 | $text tag add $mark $first $last 827 | lappend tEdit::marks($text) $mark 828 | foreach item {"Next Mark" "Clear Marks"} { 829 | .menu.marks entryconfigure $item -state normal 830 | } 831 | } else { 832 | tEdit::MarkDelete $mark 833 | } 834 | } 835 | 836 | proc tEdit::MarkSee {mark} { 837 | set text "[.nb select].text" 838 | if {$mark eq ""} {return 1} 839 | $text mark set insert $mark 840 | $text see $mark 841 | focus $text 842 | } 843 | 844 | proc tEdit::MarkNext {markbox} { 845 | set text "[.nb select].text" 846 | if {[llength $tEdit::marks($text)] == 0} { 847 | tEdit::Message "no marks found" 848 | return 1 849 | } 850 | set mark [$text mark next "insert +1 indices"] 851 | while {$mark ni $tEdit::marks($text)} { 852 | if {$mark eq ""} {set mark 0.0} 853 | set mark [$text mark next $mark] 854 | } 855 | set index [lsearch -exact $tEdit::marks($text) $mark] 856 | $markbox selection clear 0 end 857 | $markbox selection set $index 858 | $markbox activate $index 859 | $text mark set insert $mark 860 | $text see $mark 861 | } 862 | 863 | proc tEdit::MarkDelete {which} { 864 | set text "[.nb select].text" 865 | set markbox .marks 866 | switch -- $which { 867 | all { 868 | foreach mark $tEdit::marks($text) { 869 | $text mark unset $mark 870 | $text tag delete $mark 871 | } 872 | set tEdit::marks($text) [list] 873 | set tEdit::marknum($text) 0 874 | } 875 | active { 876 | set mark [$markbox get active] 877 | $text mark unset $mark 878 | $text tag delete $mark 879 | $markbox delete active 880 | } 881 | default { 882 | $text mark unset $which 883 | $text tag delete $which 884 | $markbox delete [lsearch -exact $tEdit::marks($text) $which] 885 | } 886 | } 887 | if {[llength $tEdit::marks($text)] == 0} { 888 | foreach item {"Next Mark" "Clear Marks"} { 889 | .menu.marks entryconfigure $item -state disabled 890 | } 891 | } 892 | } 893 | 894 | proc tEdit::GotoLine {text line} { 895 | if {$line eq ""} {return 1} 896 | # $line is always digit, from combobox validation 897 | if {$line ni $tEdit::hgoto} { 898 | lappend tEdit::hgoto $line 899 | } 900 | if {$line <= [$text index "end -1 indices"]} { 901 | set mark "${line}.0" 902 | $text mark set insert $mark 903 | $text see $mark 904 | .cbgoto configure -style valid.TCombobox 905 | tEdit::Message "gone to line $line" 906 | } else { 907 | .cbgoto configure -style error.TCombobox 908 | tEdit::Message "line $line does not exist" 909 | } 910 | } 911 | 912 | proc tEdit::LineNumbers {text canvas args} { 913 | set checklist "configure delete insert see yview" 914 | # canvas font don't need italics, underline or overstrike 915 | # family and size are enough 916 | set font [lrange $tEdit::textfont 0 1] 917 | # check only for items in checklist and elide tags 918 | # $args == 0 when opening a file 919 | if {[llength $args] == 0 || [lindex $args 0 1] in $checklist \ 920 | || [lindex $args 0 4] eq "-elide"} { 921 | $canvas delete all 922 | set i [$text index @0,0] 923 | while true { 924 | set dline [$text dlineinfo $i] 925 | if {[llength $dline] == 0} {break} 926 | set height [lindex $dline 3] 927 | set y [expr {[lindex $dline 1] + $tEdit::spacing1}] 928 | set linenum [lindex [split $i "."] 0] 929 | if {$linenum > 999} { 930 | set width [expr {[font measure $tEdit::textfont $linenum] + 5}] 931 | } else { 932 | set width [expr {[font measure $tEdit::textfont "000"] + 5}] 933 | } 934 | $canvas configure -width $width 935 | $canvas create text 2 $y -anchor nw -text $linenum \ 936 | -font $font -fill [$text cget -foreground] 937 | set i [$text index "$i +1 lines linestart"] 938 | } 939 | } 940 | } 941 | 942 | proc tEdit::ToggleLineNumbers {text canvas show} { 943 | if {$show eq "true"} { 944 | bind $text [list tEdit::LineNumbers $text $canvas] 945 | if {[trace info execution $text] eq ""} { 946 | trace add execution $text leave [list tEdit::LineNumbers $text $canvas] 947 | } 948 | grid $canvas 949 | } else { 950 | bind $text {return 0} 951 | trace remove execution $text leave [list tEdit::LineNumbers $text $canvas] 952 | grid remove $canvas 953 | } 954 | } 955 | 956 | proc tEdit::ToggleView {widget show} { 957 | if {$show eq "false"} { 958 | grid remove $widget 959 | switch -- $widget { 960 | .lfreplace {focus .cbsearch} 961 | default {focus [.nb select].text} 962 | } 963 | } else { 964 | grid $widget 965 | switch -- $widget { 966 | .fgoto {focus .cbgoto} 967 | .fsearch {focus .cbsearch} 968 | .lfreplace {focus .cbreplace} 969 | default {focus [.nb select].text} 970 | } 971 | } 972 | } 973 | 974 | proc tEdit::ToggleSoftTabs {text} { 975 | if {$tEdit::softtabs eq "true"} { 976 | set tabstring [string repeat " " $tEdit::tabsize] 977 | } else { 978 | set tabstring "\t" 979 | } 980 | bind $text [list $text insert insert $tabstring] 981 | tEdit::ConfigureTabSize $text 982 | } 983 | 984 | proc tEdit::ConfigureTabSize {text} { 985 | $text configure -tabs "[expr {$tEdit::tabsize * [font measure $tEdit::textfont 0]}] left" 986 | } 987 | 988 | proc tEdit::SetFont {font} { 989 | set tEdit::textfont $font 990 | foreach tab [.nb tabs] { 991 | set text "${tab}.text" 992 | $text configure -font $font 993 | tEdit::ConfigureTabSize $text 994 | if {$tEdit::folds($text) ne ""} { 995 | foreach fold $tEdit::folds($text) { 996 | $fold configure -font $font 997 | } 998 | } 999 | } 1000 | } 1001 | 1002 | proc tEdit::SelectFont {text} { 1003 | tk fontchooser configure -title "Select Font" -parent . \ 1004 | -font [$text cget -font] -command {tEdit::SetFont} 1005 | tk fontchooser show 1006 | } 1007 | 1008 | proc tEdit::ColorScheme {text canvas colorscheme} { 1009 | switch -- $colorscheme { 1010 | amber {array set color {fg "#FFBF00" bg "#000000" hl "#0000FF"}} 1011 | black {array set color {fg "#C0C0C0" bg "#000000" hl "#0000FF"}} 1012 | blue {array set color {fg "#FFFF00" bg "#0000FF" hl "#444444"}} 1013 | brownsugar {array set color {fg "#FF9999" bg "#472400" hl "#000000"}} 1014 | gray {array set color {fg "#D6D6D6" bg "#4E4E4E" hl "#0000FF"}} 1015 | green {array set color {fg "#00FF00" bg "#000000" hl "#0000FF"}} 1016 | salmon {array set color {fg "#222200" bg "#CA607B" hl "#DED494"}} 1017 | sugar {array set color {fg "#000000" bg "#EEEFDF" hl "#BEBFB2"}} 1018 | default {array set color {fg "#000000" bg "#FFFFFF" hl "#FFFF00"}} 1019 | } 1020 | $canvas configure -background $color(hl) 1021 | $text configure -foreground $color(fg) 1022 | $text configure -background $color(bg) 1023 | $text configure -insertbackground $color(fg) 1024 | $text tag configure tag_search -foreground $color(hl) -background $color(fg) 1025 | $text tag configure tag_search_all -foreground $color(fg) -background $color(hl) 1026 | if {$tEdit::folds($text) ne ""} { 1027 | foreach fold $tEdit::folds($text) { 1028 | $fold configure -background $color(bg) 1029 | $fold configure -foreground $color(fg) 1030 | } 1031 | } 1032 | } 1033 | 1034 | proc tEdit::Base64Encode {} { 1035 | set filename [file nativename [tk_getOpenFile -title "Select File to Encode" \ 1036 | -parent . -multiple no]] 1037 | if {$filename eq ""} {return 2} 1038 | if {[file type $filename] ne "file"} { 1039 | puts stderr "Cannot open: $filename is [file type $filename]" 1040 | tk_messageBox -title "Cannot open file!" -icon error -type ok -parent . \ 1041 | -message "Cannot open file:" \ 1042 | -detail "${filename}\nis a [file type $filename]." 1043 | return 1 1044 | } 1045 | if {[catch {set data [open $filename r]} msg]} { 1046 | puts stderr $msg 1047 | tEdit::Message [lindex [split $msg ":"] 1] 1048 | return 1 1049 | } 1050 | chan configure $data -translation binary 1051 | set encoded [binary encode base64 -maxlen 75 [chan read -nonewline $data]] 1052 | chan close $data 1053 | set text [tEdit::NewTab [.nb index current] "${filename}.b64"] 1054 | $text insert end $encoded 1055 | $text mark set insert 0.0 1056 | $text see insert 1057 | $text edit modified 0 1058 | $text edit reset 1059 | tEdit::Message "Encoding finished" 1060 | } 1061 | 1062 | proc tEdit::TabOptions {arg {tab ""}} { 1063 | if {$arg eq "new" && $tab ne ""} { 1064 | set text "${tab}.text" 1065 | set canvas "${tab}.canvas" 1066 | tEdit::ToggleLineNumbers $text $canvas $tEdit::linenumbers 1067 | tEdit::ToggleView "${tab}.yscroll" $tEdit::showyscroll 1068 | tEdit::ToggleView "${tab}.xscroll" $tEdit::showxscroll 1069 | tEdit::AutoIndent $text 1070 | tEdit::ColorScheme $text $canvas $tEdit::colorscheme 1071 | tEdit::ToggleSoftTabs $text 1072 | } else { 1073 | foreach tab [.nb tabs] { 1074 | set text "${tab}.text" 1075 | set canvas "${tab}.canvas" 1076 | switch -- $arg { 1077 | LineNumbers {tEdit::ToggleLineNumbers $text $canvas $tEdit::linenumbers} 1078 | YScroll {tEdit::ToggleView "${tab}.yscroll" $tEdit::showyscroll} 1079 | XScroll {tEdit::ToggleView "${tab}.xscroll" $tEdit::showxscroll} 1080 | Indent {tEdit::AutoIndent $text} 1081 | WordWrap {$text configure -wrap $tEdit::wrap} 1082 | BlinkCursor {$text configure -insertofftime $tEdit::blinkcursor} 1083 | BlockCursor {$text configure -blockcursor $tEdit::blockcursor} 1084 | Spacing1 {$text configure -spacing1 $tEdit::spacing1} 1085 | Spacing2 {$text configure -spacing2 $tEdit::spacing2} 1086 | Spacing3 {$text configure -spacing3 $tEdit::spacing3} 1087 | ColorScheme {tEdit::ColorScheme $text $canvas $tEdit::colorscheme} 1088 | TabKey {tEdit::ToggleSoftTabs $text} 1089 | default {return 1} 1090 | } 1091 | } 1092 | } 1093 | } 1094 | 1095 | proc tEdit::FocusTab {tab} { 1096 | wm title . "tEdit - [.nb tab $tab -text]" 1097 | # remove any leftover message 1098 | set tEdit::message "" 1099 | set tEdit::filepath [file nativename $tEdit::filename($tab)] 1100 | set text "${tab}.text" 1101 | tEdit::UndoStack $text 1102 | tEdit::Modified $text 1103 | # set "Unfold all" menu state 1104 | .menu.edit entryconfigure "Unfold All" \ 1105 | -state [expr {$tEdit::folds($text) eq "" ? "disabled" : "normal"}] 1106 | # set "Next Mark" & "Clear Marks" menu state 1107 | .marks configure -listvariable tEdit::marks($text) 1108 | foreach item {"Next Mark" "Clear Marks"} { 1109 | .menu.marks entryconfigure $item \ 1110 | -state [expr {$tEdit::marks($text) eq "" ? "disabled" : "normal"}] 1111 | } 1112 | } 1113 | 1114 | proc tEdit::TabsMenu {W x y X Y} { 1115 | .menutabs.goto delete 0 end 1116 | .menutabs delete 0 2 1117 | .menutabs insert 0 command -label "New Tab" -underline 4 \ 1118 | -command [list tEdit::NewTab [lindex [$W index @${x},${y}]]] 1119 | .menutabs insert 1 command -label "Close Tab" -underline 0 \ 1120 | -command [list tEdit::CloseTab [lindex [$W tabs] [$W index @${x},${y}]]] 1121 | .menutabs insert 2 command -label "Close Others" -underline 6 \ 1122 | -command [list tEdit::CloseAll [lindex [$W tabs] [$W index @${x},${y}]]] 1123 | foreach tabid [$W tabs] { 1124 | set tabname [$W tab $tabid -text] 1125 | .menutabs.goto add command -label $tabname -command [list $W select $tabid] 1126 | } 1127 | tk_popup .menutabs $X $Y 1128 | } 1129 | 1130 | proc tEdit::NextTab {{nb ".nb"}} { 1131 | set tabid [$nb index current] 1132 | if {[incr tabid] >= [$nb index end]} {set tabid 0} 1133 | $nb select $tabid 1134 | } 1135 | 1136 | proc tEdit::PrevTab {{nb ".nb"}} { 1137 | set tabid [$nb index current] 1138 | if {[incr tabid -1] < 0} {set tabid [expr {[$nb index end] - 1}]} 1139 | $nb select $tabid 1140 | } 1141 | 1142 | proc tEdit::DnD::DragDelay {W x y X Y} { 1143 | set tEdit::DnD::timer [list tEdit::DnD::DragTab $W $x $y $X $Y] 1144 | after 200 $tEdit::DnD::timer 1145 | } 1146 | 1147 | proc tEdit::DnD::DragTab {W x y X Y} { 1148 | set tEdit::DnD::timer "" 1149 | set tEdit::DnD::start [$W index @$x,$y] 1150 | # check for a valid start 1151 | if {[string is integer -strict $tEdit::DnD::start]} { 1152 | # create window to show when drag 1153 | set tabid [lindex [$W tabs] [$W index @$x,$y]] 1154 | set tabtext [$W tab $tabid -text] 1155 | toplevel .dnd 1156 | tk::label .dnd.tabname -relief raised -padx 5 -pady 5 \ 1157 | -background "white" -text $tabtext 1158 | pack .dnd.tabname -in .dnd -expand true -fill both 1159 | wm resizable .dnd 0 0 1160 | wm geometry .dnd "+${X}+${Y}" 1161 | wm overrideredirect .dnd 1 1162 | wm attributes .dnd -topmost true 1163 | bind $W {tEdit::DnD::MoveTab %W %x %y %X %Y} 1164 | $W configure -cursor "hand2" 1165 | } 1166 | } 1167 | 1168 | proc tEdit::DnD::DropTab {W x y} { 1169 | focus [.nb select].text 1170 | if {$tEdit::DnD::timer ne ""} { 1171 | after cancel $tEdit::DnD::timer 1172 | return 0 1173 | } 1174 | $W configure -cursor "left_ptr" 1175 | set from $tEdit::DnD::start 1176 | # check for a valid source 1177 | if {[string is integer -strict $from]} { 1178 | set to [$W index @$x,$y] 1179 | # check for a valid destination 1180 | if {[string is integer -strict $to]} { 1181 | set tab [lindex [$W tabs] $from] 1182 | $W insert $to $tab 1183 | } 1184 | } 1185 | set tEdit::DnD::start "" 1186 | bind $W {return 0} 1187 | destroy .dnd 1188 | } 1189 | 1190 | proc tEdit::DnD::MoveTab {W x y X Y} { 1191 | if {[winfo exists .dnd]} { 1192 | # check for valid path 1193 | set to [$W index @$x,$y] 1194 | if {[string is integer -strict $to]} { 1195 | wm geometry .dnd "+${X}+${Y}" 1196 | } 1197 | } 1198 | } 1199 | 1200 | proc tEdit::DnD::CancelDnD {W} { 1201 | if {[winfo exists .dnd]} { 1202 | $W configure -cursor "left_ptr" 1203 | set tEdit::DnD::start "" 1204 | bind $W {return 0} 1205 | destroy .dnd 1206 | } 1207 | } 1208 | 1209 | proc tEdit::SystemDetails {} { 1210 | tk_messageBox -title "System Information" -icon info -type ok -parent . \ 1211 | -message "Tcl/Tk Version: [info patchlevel]" \ 1212 | -detail "\ 1213 | User Name:\t${::tcl_platform(user)}\n\ 1214 | Hostname:\t[info hostname]\n\ 1215 | OS Family:\t${::tcl_platform(platform)}\n\ 1216 | OS Identifier:\t${::tcl_platform(os)}\n\ 1217 | OS Version:\t${::tcl_platform(osVersion)}\n\ 1218 | Architecture:\t${::tcl_platform(machine)}\n\ 1219 | Window System:\t[tk windowingsystem]\n\ 1220 | Encoding:\t[encoding system]" 1221 | } 1222 | 1223 | proc tEdit::About {version} { 1224 | tk_messageBox -title "About tEdit" -icon info -type ok -parent . \ 1225 | -message "tEdit $version" -detail \ 1226 | {A simple tabbed text editor, 1227 | written in core Tcl/Tk. 1228 | 1229 | MIT License 1230 | 1231 | Copyright © Thanos Zygouris 1232 | } 1233 | } 1234 | 1235 | ################################################################################ 1236 | # MENUS 1237 | # 1238 | proc tEdit::Menus {} { 1239 | option add *tearOff false 1240 | . configure -menu [menu .menu] 1241 | .menu add cascade -label "File" -underline 0 -menu [menu .menu.file] 1242 | .menu.file add command -label "New Tab" -underline 0 \ 1243 | -accelerator "Ctrl+N" -command {tEdit::NewTab [.nb index current]} 1244 | .menu.file add command -label "Close Tab" -underline 0 \ 1245 | -accelerator "Ctrl+F4" -command {tEdit::CloseTab [.nb select]} 1246 | .menu.file add separator 1247 | .menu.file add command -label "Open..." -underline 0 \ 1248 | -accelerator "Ctrl+O" -command {tEdit::OpenFileDialog} 1249 | .menu.file add command -label "Save" -underline 0 \ 1250 | -accelerator "Ctrl+S" \ 1251 | -command {tEdit::SaveFile $tEdit::filename([.nb select]) [.nb select].text} 1252 | .menu.file add command -label "Save As..." -underline 5 \ 1253 | -accelerator "Ctrl+Shift+S" -command {tEdit::SaveAs [.nb select].text} 1254 | .menu.file add separator 1255 | .menu.file add cascade -label "Recent Files" -underline 0 \ 1256 | -state disabled -menu [menu .menu.file.recent] 1257 | .menu.file add separator 1258 | .menu.file add command -label "Base64 Encode..." -underline 0 \ 1259 | -command {tEdit::Base64Encode} 1260 | .menu.file add separator 1261 | .menu.file add command -label "Close All" -underline 1 \ 1262 | -accelerator "Ctrl+Q" -command {tEdit::CloseAll} 1263 | .menu.file add command -label "Exit" -underline 1 \ 1264 | -accelerator "Ctrl+Shift+Q" -command {exit} 1265 | .menu add cascade -label "Edit" -underline 0 -menu [menu .menu.edit] 1266 | .menu.edit add command -label "Increase Indent" -underline 0 \ 1267 | -accelerator "Ctrl+I" -command {tEdit::BlockIndent [.nb select].text "increase"} 1268 | .menu.edit add command -label "Decrease Indent" -underline 0 \ 1269 | -accelerator "Ctrl+U" -command {tEdit::BlockIndent [.nb select].text "decrease"} 1270 | .menu.edit add separator 1271 | .menu.edit add command -label "Undo" -underline 0 \ 1272 | -accelerator "Ctrl+Z" -command {tEdit::UndoRedo [.nb select].text "undo"} 1273 | .menu.edit add command -label "Redo" -underline 0 \ 1274 | -accelerator "Ctrl+Shift+Z" -command {tEdit::UndoRedo [.nb select].text "redo"} 1275 | .menu.edit add separator 1276 | .menu.edit add command -label "Cut" -underline 2 \ 1277 | -accelerator "Ctrl+X" -command {tEdit::Copy [.nb select].text "Cut"} 1278 | .menu.edit add command -label "Copy" -underline 0 \ 1279 | -accelerator "Ctrl+C" -command {tEdit::Copy [.nb select].text "Copy"} 1280 | .menu.edit add command -label "Paste" -underline 0 \ 1281 | -accelerator "Ctrl+V" -command {tEdit::Paste [.nb select].text} 1282 | .menu.edit add separator 1283 | .menu.edit add command -label "Select All" -underline 7 \ 1284 | -accelerator "Ctrl+A" -command {tEdit::SelectAll [.nb select].text} 1285 | .menu.edit add separator 1286 | .menu.edit add command -label "Fold Lines" -underline 0 \ 1287 | -accelerator "Ctrl+J" -command {tEdit::Fold [.nb select].text} 1288 | .menu.edit add command -label "Unfold All" -underline 1 \ 1289 | -accelerator "Ctrl+Shift+J" -command {tEdit::Unfold [.nb select].text "all"} 1290 | .menu.edit add separator 1291 | .menu.edit add command -label "Calculate" -underline 8 \ 1292 | -accelerator "Ctrl+E" -command {tEdit::Calculate [.nb select].text} 1293 | .menu add cascade -label "Search" -underline 0 -menu [menu .menu.search] 1294 | .menu.search add checkbutton -label "Show Search" -underline 0 \ 1295 | -accelerator "Ctrl+F" \ 1296 | -variable tEdit::showsearch -onvalue "true" -offvalue "false" \ 1297 | -command {tEdit::ToggleView .fsearch $tEdit::showsearch} 1298 | .menu.search add command -label "Find Next" -underline 5 \ 1299 | -accelerator "F3" -command {tEdit::Search $tEdit::search "nextrange"} 1300 | .menu.search add command -label "Find Prev" -underline 5 \ 1301 | -accelerator "Shift+F3" -command {tEdit::Search $tEdit::search "prevrange"} 1302 | .menu.search add command -label "Clear Highlight" -underline 0 \ 1303 | -accelerator "Ctrl+L" \ 1304 | -command {tEdit::ClearHighlight "tag_search tag_search_all"} 1305 | .menu add cascade -label "Marks" -underline 0 -menu [menu .menu.marks] 1306 | .menu.marks add checkbutton -label "Show Marks" -underline 0 \ 1307 | -accelerator "Shift+F5" \ 1308 | -variable tEdit::showmarks -onvalue "true" -offvalue "false" \ 1309 | -command {tEdit::ToggleView .fmarks $tEdit::showmarks} 1310 | .menu.marks add command -label "Mark Toggle" -underline 0 \ 1311 | -accelerator "Ctrl+M" -command {tEdit::MarkToggle} 1312 | .menu.marks add command -label "Next Mark" -underline 0 \ 1313 | -accelerator "F5" -state disabled -command {tEdit::MarkNext .marks} 1314 | .menu.marks add command -label "Clear Marks" -underline 0 \ 1315 | -accelerator "Ctrl+Shift+M" -state disabled -command {tEdit::MarkDelete "all"} 1316 | .menu add cascade -label "Show" -underline 3 -menu [menu .menu.show] 1317 | .menu.show add checkbutton -label "File Browser" -underline 0 \ 1318 | -accelerator "F2" \ 1319 | -variable tEdit::browser -onvalue "true" -offvalue "false" \ 1320 | -command {tEdit::ToggleBrowser $tEdit::browser} 1321 | .menu.show add separator 1322 | .menu.show add checkbutton -label "Line Numbers" -underline 5 \ 1323 | -variable tEdit::linenumbers -onvalue "true" -offvalue "false" \ 1324 | -command {tEdit::TabOptions "LineNumbers"} 1325 | .menu.show add separator 1326 | .menu.show add checkbutton -label "Menu Bar" -underline 0 \ 1327 | -variable tEdit::showmenu -onvalue ".menu" -offvalue "" \ 1328 | -command {. configure -menu $tEdit::showmenu} 1329 | .menu.show add checkbutton -label "Status Bar" -underline 7 \ 1330 | -variable tEdit::showstatus -onvalue "true" -offvalue "false" \ 1331 | -command {tEdit::ToggleView .statusbar $tEdit::showstatus} 1332 | .menu.show add checkbutton -label "Vertical Scrollbar" -underline 0 \ 1333 | -variable tEdit::showyscroll -onvalue "true" -offvalue "false" \ 1334 | -command {tEdit::TabOptions "YScroll"} 1335 | .menu.show add checkbutton -label "Horizontal Scrollbar" -underline 0 \ 1336 | -variable tEdit::showxscroll -onvalue "true" -offvalue "false" \ 1337 | -command {tEdit::TabOptions "XScroll"} 1338 | .menu.show add separator 1339 | .menu.show add checkbutton -label "Go to line" -underline 0 \ 1340 | -accelerator "Ctrl+G" \ 1341 | -variable tEdit::showgoto -onvalue "true" -offvalue "false" \ 1342 | -command {tEdit::ToggleView .fgoto $tEdit::showgoto} 1343 | .menu add cascade -label "Options" -underline 0 -menu [menu .menu.options] 1344 | .menu.options add checkbutton -label "On Top" -underline 0 \ 1345 | -variable tEdit::ontop -onvalue "true" -offvalue "false" \ 1346 | -command {wm attributes . -topmost $tEdit::ontop} 1347 | .menu.options add separator 1348 | .menu.options add cascade -label "Precision" -underline 0 \ 1349 | -menu [menu .menu.options.precision] 1350 | for {set i 0} {$i <= 15} {incr i} { 1351 | .menu.options.precision add radiobutton -label $i \ 1352 | -variable tEdit::precision -value $i 1353 | } 1354 | .menu.options add separator 1355 | .menu.options add checkbutton -label "Autoindent" -underline 0 \ 1356 | -variable tEdit::autoindent -onvalue "true" -offvalue "false" \ 1357 | -command {tEdit::TabOptions "Indent"} 1358 | .menu.options add checkbutton -label "Word Wrap" -underline 5 \ 1359 | -variable tEdit::wrap -onvalue "word" -offvalue "none" \ 1360 | -command {tEdit::TabOptions "WordWrap"} 1361 | .menu.options add separator 1362 | .menu.options add checkbutton -label "Blink Cursor" -underline 0 \ 1363 | -variable tEdit::blinkcursor -onvalue "500" -offvalue "0" \ 1364 | -command {tEdit::TabOptions "BlinkCursor"} 1365 | .menu.options add checkbutton -label "Block Cursor" -underline 4 \ 1366 | -variable tEdit::blockcursor -onvalue "true" -offvalue "false" \ 1367 | -command {tEdit::TabOptions "BlockCursor"} 1368 | .menu.options add separator 1369 | .menu.options add command -label "Select Font..." -underline 7 \ 1370 | -command {tEdit::SelectFont "[.nb select].text"} 1371 | .menu.options add cascade -label "Line Spacing" -underline 0 \ 1372 | -menu [menu .menu.options.spacing] 1373 | .menu.options.spacing add cascade -label "Above Lines" -underline 0 \ 1374 | -menu [menu .menu.options.spacing.1] 1375 | for {set i 0} {$i <= 9} {incr i} { 1376 | .menu.options.spacing.1 add radiobutton -label $i -underline 0 \ 1377 | -variable tEdit::spacing1 -value $i \ 1378 | -command {tEdit::TabOptions "Spacing1"} 1379 | } 1380 | .menu.options.spacing add cascade -label "Below Lines" -underline 0 \ 1381 | -menu [menu .menu.options.spacing.3] 1382 | for {set i 0} {$i <= 9} {incr i} { 1383 | .menu.options.spacing.3 add radiobutton -label $i -underline 0 \ 1384 | -variable tEdit::spacing3 -value $i \ 1385 | -command {tEdit::TabOptions "Spacing3"} 1386 | } 1387 | .menu.options.spacing add cascade -label "Between Wraps" -underline 8 \ 1388 | -menu [menu .menu.options.spacing.2] 1389 | for {set i 0} {$i <= 9} {incr i} { 1390 | .menu.options.spacing.2 add radiobutton -label $i -underline 0 \ 1391 | -variable tEdit::spacing2 -value $i \ 1392 | -command {tEdit::TabOptions "Spacing2"} 1393 | } 1394 | .menu.options add separator 1395 | .menu.options add cascade -label "Theme" -underline 3 \ 1396 | -menu [menu .menu.options.themes] 1397 | foreach theme [ttk::style theme names] { 1398 | .menu.options.themes add radiobutton -label $theme \ 1399 | -variable tEdit::theme -value $theme \ 1400 | -command [list ttk::style theme use $theme] 1401 | } 1402 | .menu.options add cascade -label "Colorscheme" -underline 0 \ 1403 | -menu [menu .menu.options.colorscheme] 1404 | foreach colorscheme {amber black blue brownsugar default gray green salmon sugar} { 1405 | .menu.options.colorscheme add radiobutton -label $colorscheme \ 1406 | -variable tEdit::colorscheme -value $colorscheme \ 1407 | -command {tEdit::TabOptions "ColorScheme"} 1408 | } 1409 | .menu.options add separator 1410 | .menu.options add cascade -label "Tab Size" -underline 0 \ 1411 | -menu [menu .menu.options.tabsize] 1412 | foreach i [list 2 4 6 7 8] { 1413 | .menu.options.tabsize add radiobutton -label $i -underline 0 \ 1414 | -variable tEdit::tabsize -value $i \ 1415 | -command {tEdit::TabOptions "TabKey"} 1416 | } 1417 | .menu.options add checkbutton -label "Soft Tabs" -underline 0 \ 1418 | -variable tEdit::softtabs -onvalue "true" -offvalue "false" \ 1419 | -command {tEdit::TabOptions "TabKey"} 1420 | .menu add cascade -label "Help" -underline 0 -menu [menu .menu.help] 1421 | .menu.help add command -label "System..." -underline 0 \ 1422 | -command {tEdit::SystemDetails} 1423 | .menu.help add separator 1424 | .menu.help add command -label "About tEdit..." -underline 0 \ 1425 | -accelerator "F1" -command {tEdit::About $tEdit::version} 1426 | 1427 | # popup menu for managing tabs 1428 | # dummy labels will be replaced after tEdit::TabsMenu proc 1429 | menu .menutabs 1430 | .menutabs add command -label "New Tab - Dummy Label" 1431 | .menutabs add command -label "Close Tab - Dummy Label" 1432 | .menutabs add command -label "Close Others - Dummy Label" 1433 | .menutabs add separator 1434 | .menutabs add cascade -label "Go To" -underline 0 -menu [menu .menutabs.goto] 1435 | .menutabs add separator 1436 | .menutabs add command -label "Close All" -underline 6 \ 1437 | -command {.menu.file invoke "Close All"} 1438 | 1439 | # popup menu for comboboxes 1440 | menu .menucbx 1441 | .menucbx add command -label "Cut" -underline 2 \ 1442 | -accelerator "Ctrl+X" -command {event generate [focus] <>} 1443 | .menucbx add command -label "Copy" -underline 0 \ 1444 | -accelerator "Ctrl+C" -command {event generate [focus] <>} 1445 | .menucbx add command -label "Paste" -underline 0 \ 1446 | -accelerator "Ctrl+V" -command {event generate [focus] <>} 1447 | } 1448 | 1449 | ################################################################################ 1450 | # WIDGETS 1451 | # 1452 | proc tEdit::Widgets {} { 1453 | # set different styles for search/replace comboboxes 1454 | ttk::style configure valid.TCombobox -fieldbackground white 1455 | ttk::style configure error.TCombobox -fieldbackground red 1456 | 1457 | ttk::panedwindow .main -orient horizontal 1458 | ttk::frame .browser 1459 | ttk::treeview .tree -takefocus 0 -selectmode browse \ 1460 | -columns {fullpath type} -displaycolumns "" \ 1461 | -yscrollcommand {.ytree set} -xscrollcommand {.xtree set} 1462 | .tree heading #0 -anchor center -text "File Browser" 1463 | ttk::scrollbar .ytree -orient vertical -command {.tree yview} 1464 | ttk::scrollbar .xtree -orient horizontal -command {.tree xview} 1465 | # grid treeview and scrollbars in the left pane frame 1466 | grid .tree -in .browser -row 0 -column 0 -sticky nswe 1467 | grid .ytree -in .browser -row 0 -column 1 -sticky ns 1468 | grid .xtree -in .browser -row 1 -column 0 -sticky we 1469 | grid rowconfigure .browser .tree -weight 1 1470 | grid columnconfigure .browser .tree -weight 1 1471 | 1472 | ttk::frame .editor 1473 | ttk::notebook .nb -takefocus 0 1474 | ttk::notebook::enableTraversal .nb 1475 | ttk::labelframe .fmarks -borderwidth 1 -relief flat -text "Marks:" 1476 | tk::listbox .marks -takefocus 0 -highlightthickness 0 -width 10 \ 1477 | -xscrollcommand {.xmarks set} -yscrollcommand {.ymarks set} 1478 | ttk::scrollbar .ymarks -orient vertical -command {.marks yview} 1479 | ttk::scrollbar .xmarks -orient horizontal -command {.marks xview} 1480 | # grid listbox and scrollbars in the labelframe 1481 | grid .marks -in .fmarks -row 0 -column 0 -sticky ns 1482 | grid .ymarks -in .fmarks -row 0 -column 1 -sticky ns 1483 | grid .xmarks -in .fmarks -row 1 -column 0 -sticky we 1484 | grid rowconfigure .fmarks .marks -weight 1 1485 | # grid notebook and marks in the right pane frame 1486 | grid .nb -in .editor -row 0 -column 0 -sticky nswe 1487 | grid .fmarks -in .editor -row 0 -column 1 -sticky ns 1488 | grid rowconfigure .editor .nb -weight 1 1489 | grid columnconfigure .editor .nb -weight 1 1490 | # remove marks from view 1491 | grid remove .fmarks 1492 | 1493 | ttk::frame .fgoto -borderwidth 1 -relief flat 1494 | ttk::label .lgoto -relief flat -text "Go to line:" 1495 | ttk::combobox .cbgoto -takefocus 0 -style valid.TCombobox \ 1496 | -textvariable tEdit::goto -values $tEdit::hgoto \ 1497 | -validate key -validatecommand {string is digit %P} \ 1498 | -postcommand {.cbgoto configure -values $tEdit::hgoto} 1499 | tk::button .bgoto -text "Go" -width 3 -padx 0 -pady 0 \ 1500 | -underline 0 -takefocus 0 \ 1501 | -command {tEdit::GotoLine [.nb select].text $tEdit::goto} 1502 | grid .lgoto -in .fgoto -row 0 -column 0 -sticky w 1503 | grid .cbgoto -in .fgoto -row 0 -column 1 -sticky w 1504 | grid .bgoto -in .fgoto -row 0 -column 2 -sticky w 1505 | 1506 | ttk::frame .fsearch -borderwidth 1 -relief sunken 1507 | ttk::labelframe .lfsearch -borderwidth 1 -relief flat -text "Search for:" 1508 | ttk::combobox .cbsearch -takefocus 0 -style valid.TCombobox \ 1509 | -textvariable tEdit::search -values $tEdit::hsearch \ 1510 | -postcommand {.cbsearch configure -values $tEdit::hsearch} 1511 | ttk::button .bfindnext -text "Find Next" -width 9 \ 1512 | -underline 5 -takefocus 0 \ 1513 | -command {.menu.search invoke "Find Next"} 1514 | ttk::button .bfindprev -text "Find Prev" -width 9 \ 1515 | -underline 5 -takefocus 0 \ 1516 | -command {.menu.search invoke "Find Prev"} 1517 | ttk::radiobutton .rbnocase -text "Ignore Case" \ 1518 | -underline 7 -takefocus 0 -variable tEdit::regexp -value "-nocase" 1519 | ttk::radiobutton .rbexact -text "Exact" \ 1520 | -underline 1 -takefocus 0 -variable tEdit::regexp -value "-exact" 1521 | ttk::radiobutton .rbregexp -text "Regular Expression" \ 1522 | -underline 8 -takefocus 0 -variable tEdit::regexp -value "-regexp" 1523 | ttk::checkbutton .ckreplace -text "Replace" \ 1524 | -underline 0 -takefocus 0 \ 1525 | -variable tEdit::showreplace -onvalue "true" -offvalue "false" \ 1526 | -command {tEdit::ToggleView .lfreplace $tEdit::showreplace} 1527 | # grid them in the search labelframe 1528 | grid .cbsearch -in .lfsearch -row 0 -column 0 -sticky we 1529 | grid .bfindnext -in .lfsearch -row 0 -column 1 -sticky w 1530 | grid .bfindprev -in .lfsearch -row 0 -column 2 -sticky w 1531 | grid .rbregexp -in .lfsearch -row 1 -column 0 -sticky w 1532 | grid .rbexact -in .lfsearch -row 1 -column 1 -sticky w 1533 | grid .rbnocase -in .lfsearch -row 1 -column 2 -sticky w 1534 | grid .ckreplace -in .lfsearch -row 1 -column 3 -sticky e 1535 | grid columnconfigure .lfsearch .ckreplace -weight 1 1536 | 1537 | ttk::labelframe .lfreplace -borderwidth 1 -relief flat -text "Replace with:" 1538 | ttk::combobox .cbreplace -takefocus 0 -style valid.TCombobox \ 1539 | -textvariable tEdit::replace -values $tEdit::hreplace \ 1540 | -postcommand {.cbreplace configure -values $tEdit::hreplace} 1541 | ttk::button .breplace -text "Replace" -width 9 \ 1542 | -underline 0 -takefocus 0 \ 1543 | -command {tEdit::Replace $tEdit::search $tEdit::replace} 1544 | ttk::button .breplaceall -text "All" -width 9 \ 1545 | -underline 0 -takefocus 0 \ 1546 | -command {tEdit::ReplaceAll $tEdit::search $tEdit::replace} 1547 | # grid them in the replace labelframe 1548 | grid .cbreplace -in .lfreplace -row 0 -column 0 -sticky we 1549 | grid .breplace -in .lfreplace -row 0 -column 1 -sticky w 1550 | grid .breplaceall -in .lfreplace -row 0 -column 2 -sticky w 1551 | # grid find and replace labelframes in the search frame 1552 | grid .lfsearch -in .fsearch -row 0 -column 0 -sticky we 1553 | grid .lfreplace -in .fsearch -row 1 -column 0 -sticky we 1554 | grid columnconfigure .fsearch .lfsearch -weight 1 1555 | # remove replace frame from view 1556 | grid remove .lfreplace 1557 | 1558 | ttk::frame .statusbar -borderwidth 1 -relief sunken 1559 | ttk::label .filepath -relief flat -anchor w -textvariable tEdit::filepath 1560 | ttk::label .message -relief flat -anchor c -textvariable tEdit::message 1561 | ttk::label .encoding -relief flat -anchor e -textvariable tEdit::encoding 1562 | ttk::sizegrip .sizegrip 1563 | grid .filepath -in .statusbar -row 0 -column 0 -sticky w 1564 | grid .message -in .statusbar -row 0 -column 1 -sticky we 1565 | grid .encoding -in .statusbar -row 0 -column 2 -sticky e 1566 | grid .sizegrip -in .statusbar -row 0 -column 3 -sticky e 1567 | grid columnconfigure .statusbar .message -weight 1 1568 | 1569 | grid .main -in . -row 0 -column 0 -sticky nswe 1570 | grid .fgoto -in . -row 1 -column 0 -sticky we 1571 | grid .fsearch -in . -row 2 -column 0 -sticky we 1572 | grid .statusbar -in . -row 3 -column 0 -sticky we 1573 | grid rowconfigure . .main -weight 1 1574 | grid columnconfigure . .main -weight 1 1575 | # remove search frame from view 1576 | grid remove .fgoto 1577 | grid remove .fsearch 1578 | 1579 | .main add .editor 1580 | } 1581 | 1582 | ################################################################################ 1583 | # BINDINGS 1584 | # 1585 | proc tEdit::Bindings {} { 1586 | # deactivate some predefined bindings from Text widget 1587 | set bindings [list \ 1588 | \ 1589 | \ 1590 | ] 1591 | foreach keysym $bindings { 1592 | bind Text $keysym {return 0} 1593 | } 1594 | # text widget bindings 1595 | bind Text <> {tEdit::Modified %W} 1596 | bind Text <> {tEdit::UndoStack %W} 1597 | bind Text {.menu.edit invoke "Undo"} 1598 | bind Text {.menu.edit invoke "Redo"} 1599 | bind Text {.menu.edit invoke "Increase Indent"} 1600 | bind Text {.menu.edit invoke "Decrease Indent"} 1601 | bind Text {.menu.edit invoke "Cut"} 1602 | bind Text {.menu.edit invoke "Copy"} 1603 | bind Text {.menu.edit invoke "Paste"} 1604 | bind Text {.menu.edit invoke "Select All"} 1605 | bind Text {.menu.edit invoke "Fold Lines"} 1606 | bind Text {.menu.edit invoke "Unfold All"} 1607 | bind Text {.menu.edit invoke "Calculate"} 1608 | bind Text {.menu.marks invoke "Mark Toggle"} 1609 | bind Text {focus %W; tk_popup .menu.edit %X %Y} 1610 | bind Text {focus %W; tk_popup .menu %X %Y} 1611 | bind Text {event generate %W <>} 1612 | bind Text {event generate %W <>} 1613 | bind Text {event generate %W <>} 1614 | bind Text {event generate %W <>} 1615 | # bindings for better block selection/deletion 1616 | bind Text {tEdit::DeleteSelection %W "Delete"} 1617 | bind Text {tEdit::DeleteSelection %W "Backspace"} 1618 | 1619 | # do not keep selection on combobox select 1620 | bind TCombobox <> {%W selection clear} 1621 | 1622 | # global window bindings 1623 | foreach keysym { } { 1624 | bind all $keysym {tEdit::DnD::CancelDnD .tree} 1625 | } 1626 | bind all {.menu.file invoke "New Tab"} 1627 | bind all {.menu.file invoke "Close Tab"} 1628 | bind all {.menu.file invoke "Open..."} 1629 | bind all {.menu.file invoke "Save"} 1630 | bind all {.menu.file invoke "Save As..."} 1631 | bind all {.menu.file invoke "Close All"} 1632 | bind all {.menu.file invoke "Exit"} 1633 | bind all {.menu.search invoke "Show Search"} 1634 | bind all {.menu.search invoke "Find Next"} 1635 | bind all {.menu.search invoke "Find Prev"} 1636 | bind all {.menu.search invoke "Clear Highlight"} 1637 | bind all {.menu.marks invoke "Show Marks"} 1638 | bind all {.menu.marks invoke "Next Mark"} 1639 | bind all {.menu.marks invoke "Clear Marks"} 1640 | bind all {.menu.show invoke "File Browser"} 1641 | bind all {.menu.show invoke "Go to line"} 1642 | bind all {.menu.help invoke "About tEdit..."} 1643 | # toggle menu "Set Font..." state 1644 | bind all <> { 1645 | .menu.options entryconfigure "Select Font..." \ 1646 | -state [expr {[tk fontchooser configure -visible] ? "disabled" : "normal"}] 1647 | } 1648 | # make Return and Keypad-Enter keys behave the same 1649 | bind all {event generate %W } 1650 | 1651 | # treeview widget bindings 1652 | foreach keysym { } { 1653 | bind .tree $keysym {tEdit::OpenFileBrowser [%W focus] %W} 1654 | } 1655 | bind .tree <> {tEdit::PopulateBrowser [%W focus] %W} 1656 | 1657 | # notebook bindings 1658 | # on tab change reload user settings (except font and line numbering) 1659 | bind .nb <> {tEdit::FocusTab [%W select]} 1660 | # popup menu on tab labels 1661 | bind .nb {tEdit::DnD::CancelDnD %W; tEdit::TabsMenu %W %x %y %X %Y} 1662 | # mouse wheel rotate tabs 1663 | bind .nb <4> {tEdit::NextTab} 1664 | bind .nb <5> {tEdit::PrevTab} 1665 | # drag and drop tab replacement 1666 | bind .nb {tEdit::DnD::DragDelay %W %x %y %X %Y} 1667 | bind .nb {tEdit::DnD::DropTab %W %x %y} 1668 | bind .nb {tEdit::DnD::DragDelay %W %x %y %X %Y} 1669 | bind .nb {tEdit::DnD::DropTab %W %x %y} 1670 | 1671 | # mark list bindings 1672 | foreach keysym [list ] { 1673 | bind .marks $keysym {tEdit::MarkSee [%W get active]} 1674 | } 1675 | bind .marks {tEdit::MarkDelete active} 1676 | 1677 | # search & replace combobox bindings 1678 | foreach combobox {.cbsearch .cbreplace} { 1679 | bind $combobox {focus %W; tk_popup .menucbx %X %Y} 1680 | bind $combobox {.menu.search invoke "Show Search"} 1681 | } 1682 | bind .cbreplace {.breplace invoke} 1683 | bind .cbsearch {.bfindnext invoke} 1684 | 1685 | # go to line combobox 1686 | bind .cbgoto {focus %W; tk_popup .menucbx %X %Y} 1687 | bind .cbgoto {.bgoto invoke} 1688 | bind .cbgoto {.menu.show invoke "Go to line"} 1689 | bind .cbgoto <> {.bgoto invoke} 1690 | } 1691 | 1692 | ################################################################################ 1693 | # MAIN PROGRAM 1694 | # 1695 | tEdit::Widgets 1696 | tEdit::Menus 1697 | tEdit::Bindings 1698 | tEdit::StartBrowser 1699 | 1700 | # window manager instructions 1701 | wm title . "tEdit" 1702 | wm minsize . 460 300 1703 | wm protocol . WM_DELETE_WINDOW {.menu.file invoke "Close All"} 1704 | wm attributes . -topmost $tEdit::ontop 1705 | 1706 | ################################################################################ 1707 | # COMMAND LINE 1708 | # 1709 | if {$::argc > 0} { 1710 | foreach arg $::argv { 1711 | if {[file exists $arg]} { 1712 | tEdit::OpenFile $arg 1713 | } else { 1714 | tEdit::NewTab {} $arg 1715 | } 1716 | } 1717 | if {[llength [.nb tabs]] == 0} {exit 1} 1718 | } else { 1719 | tEdit::NewTab 1720 | } 1721 | # get input from OS pipe (stdout) 1722 | chan event stdin readable {tEdit::GetPipedData} 1723 | -------------------------------------------------------------------------------- /tedit.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Name=tEdit 4 | GenericName=Text Editor 5 | Comment=A simple text editor written in core Tcl/Tk 6 | Exec=tedit %F 7 | Icon=accessories-text-editor 8 | Type=Application 9 | Terminal=false 10 | Keywords=Text;Editor; 11 | Categories=Utility;TextEditor; 12 | StartupNotify=false 13 | MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++; 14 | --------------------------------------------------------------------------------