[wdb] This package `DoubleClick` extends text widget by generalized behaviour: deals with quotes `""` as well as international quotes such as „Deutsche Gänsefüßchen“, moreover all kinds of nesting parens – `(){}[[]]<>` – where the angles `<…>` are treatened depending of XML situation: either as comments – `` – or – or DOM tag elements – `
…
` – or as special markup. Triple click selects grammatical sentence. Put bindings to a single widget, or to a newly-defined Tag to use with [bindtags]. Iʼd say, quite nützlich. I found it neccessary for editing master files of [One Hand]; and I guess it should be usable for PHP files too (but not tested, any volunteer?). In German we call such thing “Eierlegende Wollmilchsau”. Ask one who can translate it. ====== # # file: DoubleClick-0.3.tm # Usage: # package require DoubleClick # bindDoubleClick .textwindow ?yes|no? # bindDoubleClick ComfortableText ?yes|no? # # option yes is default # if "yes" then triple-click selects grammatical sentence # if "no" then no specific triple-click # if false { Double-click behaves as Emacs: Double-click on quote (") selects balanced counterpart, opening or closing depends on environment. double-click on opening brace \{ selects to balanced closing counterbrace \} and vice versa. Round parens ( and ) dito. Moreover: Double-click on international quote such as “this” selects to balanced counter-quote, back or forward depends on environment. (Caution! Reliable only if no different national quotes mixed.) Double-click on opening CSS comment such as "/*" selects to "*/". Double-click on internetprotocol such as "http://wolf-dieter-busch.de" selects complete URI. Double-click on less-than < selects to counterpart: XML comment such as to closing char ">", " ..." to "?>, "", "", opening tag such as "" to closing counterpart such as "", on closing tag "" vice versa to opening tag "". Double-click on leading "&" of entity such as "&" selects to ";". Tcl-specific: Double-click on leading "$" selects Tcl string. Double-click on widget name such as ".t.menu" selects complete name. } # package require Tcl 8.6.1 package provide DoubleClick 0.3 namespace eval DoubleClick { variable quotes { „ “ ‚ ‘ “ ” ‘ ’ » « › ‹ « » ‹ › } variable click none variable index 1.0 } proc DoubleClick::backslashed {win {index insert}} { # \{ \} set target [$win search -elide -backwards -regexp {[^\\]} $index 1.0] if {$target eq ""} then { return false } set found [$win get $target+1chars $index] set length [string length $found] if {$length % 2 == 1} then { return true } else { return false } } proc DoubleClick::extendSel {win target} { lassign "[$win tag ranges sel] insert" start $win tag add sel $start $target } proc DoubleClick::findCSScomment {win {index insert}} { if {[$win get $index $index+2chars] ne "/*"} then { return false } set target [$win search -elide */ $index end] if {$target eq ""} then { return false } else { extendSel $win $target+2chars return true } } proc DoubleClick::findTagComment {win {index insert}} { # -- comment ... -- if {[$win get $index $index+2chars] ne "--"} then { return false } else { set idx [$win search -elide -- -- $index+2chars end] if {$idx eq ""} then { return false } else { $win mark set insert $index extendSel $win $idx+2chars return true } } } proc DoubleClick::findUrl {win {index insert}} { # "http://wolf-dieter-busch.de/blog/index.htm" set wordStart [$win index "$index wordstart"] set wordEnd [$win index "$index wordend"] set word [$win get $wordStart $wordEnd+3chars] if {[string match *:// $word]} then { set urlEnd [$win search -regexp -elide {[\s<>]} $wordStart end] # # quotes # set q [$win get $wordStart-1chars] if {$q in {' {"}}} then { set idx [$win search -elide $q $wordStart $urlEnd] if {$idx ne ""} then { set urlEnd $idx } else { return false } } $win mark set insert $wordStart if { ([$win get $urlEnd-1char] eq {"} && $q ne {"}) || ([$win get $urlEnd-1char] eq {'} && $q ne {'}) } then { set urlEnd $urlEnd-1char } extendSel $win $urlEnd return true } else { return false } } proc DoubleClick::findEntity {win {index insert}} { if {[$win get $index] eq "&"} then { set endIdx [$win search ";" $index end] if {$endIdx eq ""} then { return false } else { set name [$win get $index+1char $endIdx] if { [regexp {^[a-zA-Z]+$} $name] || [regexp {^#[[:digit:]]+$} $name] || [regexp {^#x[[:xdigit:]]+$} $name] } then { extendSel $win $endIdx+1chars return true } else { return false } } } else { return false } } proc DoubleClick::startOfExpression {win {index insert}} { set startIdx [$win search -elide -backwards \{ $index 1.0 ] if {$startIdx eq ""} then { $win index 1.0 } elseif {[info complete [$win get 1.0 $startIdx]]} then { $win index $startIdx+1char } else { startOfExpression $win $startIdx } } proc DoubleClick::openOrClosedQuote? {win {index insert}} { set start [startOfExpression $win $index] set txt [string map [list \{ " " \} " " ] [$win get $start $index+1char]] if {[$win get $index] == "'"} then { set txt [string map [list \" " " ' \"] $txt] } expr {[info complete $txt] ? "closing" : "opening"} } proc DoubleClick::findOpenQuote {win {index insert}} { set char [$win get $index] set startIdx [startOfExpression $win $index] set quoteIdx [$win search -backward $char $index-1char $startIdx] while {$quoteIdx ne "" && [backslashed $win $quoteIdx]} { set quoteIdx [$win search -backward $char $quoteIdx-1char $startIdx] } $win tag add sel $quoteIdx $index+1char return true } proc DoubleClick::findCloseDquote {win {index insert}} { # " ... " if {[openOrClosedQuote? $win $index] eq "closing"} then { return [findOpenQuote $win $index] } set start $index while true { set target [$win search -elide \u0022 $start+2chars end] if {$target eq ""} then { return false } if {[info complete [$win get $index $target+2chars]]} then { extendSel $win $target+1chars return true } set start $target+1chars } } proc DoubleClick::findCloseSquote {win {index insert}} { # ' ... ' if {[openOrClosedQuote? $win $index] eq "closing"} then { return [findOpenQuote $win $index] } set start $index while true { set target [$win search -elide ' $start+1chars end] if {$target eq ""} then { return false } set txt [$win get $index $target+1chars] set txt\ [string map [list \u0022 " " \{ " " \} " " ' \u0022] $txt] if {[info complete $txt]} then { extendSel $win $target+1chars return true } set start $target+2chars } } proc DoubleClick::findOpenBrace {win {index insert}} { # { ... } set start $index while true { set start [$win search -elide -backward \{ $start 1.0] if {$start eq ""} break set target [closeBraceIdx $win $start] if {[backslashed $win $start]} continue if {[$win compare [closeBraceIdx $win $start] >= $index]} then { $win tag add sel $start $index+1char return true } } return false } proc DoubleClick::closeBraceIdx {win {index insert}} { # { ... } set start $index while true { set target [$win search -elide \u007d $start end] if {$target eq ""} break if {[info complete [$win get $index $target+1chars]]} then { return $target } set start $target+1chars } } proc DoubleClick::findCloseBrace {win {index insert}} { set target [closeBraceIdx $win $index] if {$target eq ""} then { return false } else { extendSel $win $target+1char return true } } proc DoubleClick::findCloseBracket {win {index insert}} { # [ ... ] lappend map \[ \{ \] \} set start $index while true { set target [$win search -elide \u005d $start end] if {$target eq ""} then { return false } set txt [$win get $index $target+1chars] if {[info complete [string map $map $txt]]} then { extendSel $win $target+1chars return true } set start $target+1chars } } proc DoubleClick::closeParenIndex {win {index insert}} { # at opening of ( ... ) lappend map \" " " \{ " " \} " " ( \{ ) \} set start $index while true { set target [$win search -elide ) $start end] if {$target eq ""} break set txt [$win get $index $target+1chars] if {[info complete [string map $map $txt]]} then { return $target } set start $target+1chars } } proc DoubleClick::findCloseParen {win {index insert}} { # at opening of ( ... ) set endIdx [closeParenIndex $win $index] if {$endIdx eq ""} then { return false } $win tag add sel $index $endIdx+1char return true } proc DoubleClick::findOpenParen {win {index insert}} { # ( ... ) at closing set map [list \{ " " \} " " \" " " ( \{ ) \}] set top [startOfExpression $win $index] set start $index while true { set start [$win search -elide -backward \( $start $top] if {$start eq ""} break if {[backslashed $win $start]} continue set target [closeParenIndex $win $start] if {[$win compare $target >= $index]} then { $win tag add sel $start $index+1char return true } } return false } proc DoubleClick::findIntlCloseQuote {win {index insert}} { # „international“ variable quotes set open [$win get $index] set close [dict get $quotes $open] set target [$win search -elide $close $index end] if {$target eq ""} then { return false } extendSel $win $target+1chars return true } proc DoubleClick::findIntlOpenQuote {win {index insert}} { # „international“ variable quotes set map [concat {*}[lmap {a b} $quotes {list $b $a}]] set closeChar [$win get $index] set openQuote [dict get $map $closeChar] set startIdx [$win search -elide -backward $openQuote $index 1.0] if {$startIdx eq ""} then { return false } $win tag add sel $startIdx $index+1char return true } proc DoubleClick::documentIntQuotes win { # which national quotes used in doc? E. g. "de" set idx [$win search -elide -regexp {[»«›‹„‚“‘]} 1.0 end] switch -exact -- [$win get $idx] { „ - ‚ { return de } “ - ‘ { return en } » - › { return fr } « - ‹ { return ch } default { return C } } } proc DoubleClick::findMatchIntlQuote {win {index insert}} { set char [$win get $index] set closingQuotes { “ ‘ ” ’ » › « ‹ } set lang [documentIntQuotes $win] if {$char in { “ ‘ }} then { if {$lang eq "en"} then { set dir forw } else { set dir back } } elseif {$char in { » › }} then { if {$lang eq "fr"} then { set dir forw } else { set dir back } } elseif {$char in { « ‹ }} then { if {$lang eq "ch"} then { set dir forw } else { set dir back } } elseif {$char in { ’ ” }} then { set dir back } else { set dir forw } if {$dir eq "forw"} then { findIntlCloseQuote $win $index } else { findIntlOpenQuote $win $index } } proc DoubleClick::findCloseAngleExcl {win {index insert}} { # set target [$win search -elide > $index end] if {$target eq ""} then { return false } extendSel $win $target+1chars return true } proc DoubleClick::findCloseAngleQuest {win {index insert}} { # set target [$win search -elide ?> $index end] if {$target eq ""} then { return false } extendSel $win $target+2chars return true } proc DoubleClick::findEndOfComment {win {index insert}} { # set target [$win search -elide -- --> $index end] if {$target eq ""} then { return false } extendSel $win $target+3chars return true } proc DoubleClick::findEndOfCdata {win {index insert}} { # set target [$win search -elide {]]>} $index end] if {$target eq ""} then { return false } extendSel $win $target+3chars return true } proc DoubleClick::findNestedCloseAngle {win {index insert}} { # <