tedit

MJ's modal editor

MJ - I have just discovered bindtags and what they can do. They allow for some really advanced keyboard handling. An example of this is the base for a Tcl editor below. Keybindings can be associated to certain modes (a la Emacs) This is just a rudimentary basis, but I will expand on this when I have time. It now supports creating slave interpreters and sending the selection to the interpreter.

 package require Tk
  
  namespace eval utils {
    proc lremove {list item} {
      return [lsearch -all -inline -not -exact $list $item]
    }
  }
  
  namespace eval buffer {
    proc get-point {buffer} {
      return [$buffer index insert]
    }
  
    proc get-active {} {
      return .t
    }
  
    proc send-sel-inf-tcl {interp} {
      set buffer [get-active]
      set ::*mini-buffer* "Sent selection to $interp"
      if {[catch {$interp eval [$buffer get {*}[lrange [$buffer tag ranges sel] 0 1 ]]} error]} {
        set ::*mini-buffer* $error
      }
      return -code break
    }
  
    proc eval-print-last-exp {} {
      set current_buffer [get-active]
      set point [get-point $current_buffer]
      set line [get-line-with-point $current_buffer]
      if {[catch {uplevel #0 $line} result]} {
        #display in red
      } else {
        #display in black
      }
      $current_buffer insert "$point lineend" "\n$result\n"
      return -code break
  
    }
    proc get-line-with-point {buffer} {
      set point [get-point $buffer]
      set line [$buffer get "$point linestart" "$point lineend"]
      return $line
    }
  
    # return a list with all bindings on buffer (global and buffer)
    proc list-bindings {buffer} {
      set bindings {} 
      foreach tag [bindtags $buffer] {
        set bindings [concat $bindings [bind $tag]]
      }
      return $bindings
    }
  
    proc create-new {} {
      text .t
      grid .t -sticky ewns
      grid rowconfigure    . 0 -weight 1
      grid columnconfigure . 0 -weight 1
  
      # default bindtags for any new buffer
      bindtags .t "fundamental-mode keymap [bindtags .t]"
      return .t
    }
  
    proc major-mode {buffer mode} {
      bindtags ${buffer} "$mode [lrange [bindtags ${buffer}] 1 end]"
      # call mode-hook here
      wm title . $mode
    }
  }
  
  namespace eval kb {
    # keys that are collected from minibuffer
    set *keys-collected* {}
    set *active-prefix* {}
  
    # Stuff to detect key modifiers (taken from https://wiki.tcl-lang.org)
  
    # array of bit masks to recognize the modifers:
    # - shift - mod5 masks taken from .../tcl/include/X11/X.h
    # - alt mask defined by analysing the status field of Alt-KeyPress
    #   (analysed on MS Windows)
    #
    array set masks [list \
        shift   [list [expr {1 <<  0}] "Shift"] \
        lock    [list [expr {1 <<  1}] "Lock"] \
        alt     [list [expr {1 << 17}] "Alt"] \
        control [list [expr {1 <<  2}] "Control"] \
        mod1    [list [expr {1 <<  3}] "Mod1"] \
        mod2    [list [expr {1 <<  4}] "Mod2"] \
        mod3    [list [expr {1 <<  5}] "Mod3"] \
        mod4    [list [expr {1 <<  6}] "Mod4"] \
        mod5    [list [expr {1 <<  7}] "Mod5"] \
        ];
  
    # MS Windows modifier name map:
    # - Mod1 is identical to "Num"-lock key
    # - Mod3 is identical to "Scroll"-lock key
    #
    set maps [list \
        "Mod1" "Num" \
        "Mod3" "Scroll" \
      ];
  
  proc keyModifiers {state {mapToRealName 1}} {
    variable masks;
    variable maps;
  
    set modifiers [list];
  
    foreach mask [array names masks] {
      lassign $masks($mask) bits label;
  
      if {$state & $bits} {
        lappend modifiers $label;
      }
    }
    # Remove Shift modifier, is already include in character case
    set modifiers [::utils::lremove $modifiers Shift]
    set modifiers [join $modifiers "-"];
  
    if {$mapToRealName == 1} {
      set modifiers [string map $maps $modifiers];
    }
  
    return $modifiers;
  }
  
  proc add-binding {tag key proc} {
    set keys [split $key]
    if {[llength $keys] == 1 } {
      if {[llength [split $keys -]]==1 } {
        # simple keys
        bind $tag ${keys} $proc
      } else {
        # Key with modifiers
        bind $tag <$keys> $proc
      }
    } else {
      # Prefixed key combination
      # create binding for the prefix
      bind $tag "<[lindex $keys 0]>" {event generate .mini <<CollectKeys>> -data [list %W %s %K]}
  
      # create virtual binding event for the whole shebang
      bind $tag "<<$key>>" $proc
    }
  }
  
  # minibuffer will handle prefixed commands 
  proc handle-prefix-binding {buffer state key} {
    # here the system can collect keybindings until a binding matches
    set prefix "[keyModifiers $state]-$key"
    set all_bindings [::buffer::list-bindings $buffer]
  
    # add enable minibuffer bindtag
    focus .mini 
    set ::*mini-buffer* {}
    set ::*mini-buffer* "$prefix "
  }
   }
  
   # scratch mode bindings
   ::kb::add-binding scratch-mode "a" {puts "in scratch mode"}
   ::kb::add-binding scratch-mode "Control-j" {::buffer::eval-print-last-exp }
   ::kb::add-binding scratch-mode "Control-J" {puts "should execute something now without displaying output" ; break }
  
  
   ::kb::add-binding scratch-mode "Control-x Control-b" {puts prefixed}
   ::kb::add-binding scratch-mode "Control-x Control-c" {puts prefixed}
  
   ::kb::add-binding scratch-mode "Control-Alt-t" {
      set ::*mini-buffer* "Inferior Tcl [::app::create-inferior-tcl] created"
      break;
   }
  
   ::kb::add-binding scratch-mode "Control-Alt-j" {
     ::buffer::send-sel-inf-tcl interp0
     break;
   }
  
   ::kb::add-binding scratch-mode "Control-u a" {puts prefixed}
  
   # global keybindings
   ::kb::add-binding keymap "Control-space" {puts [::buffer::get-point %W]}
   ::kb::add-binding keymap "Alt-m" [list ask-user-input %W]
   set buff [::buffer::create-new]
  
   # rudimentary minibuffer. The current state of the minibuffer will be determined by the active bindtags
   # there will be support for collecting key bindings
   # there will be support for collecting user input
   # there will be support for displaying status info
  
   namespace eval mini-buffer {
  
     entry .mini
     grid .mini -sticky ew    
     .mini configure -state disabled
     .mini configure -textvar *mini-buffer*
  
     ::kb::add-binding .mini <<CollectKeys>> {
       bindtags .mini [list collect-keys {*}[bindtags .mini]]
       focus .mini
       set *mini-buffer* %d
       puts %d
     }
  
     ::kb::add-binding collect-keys <KeyPress> {
       if {%k > 63 } {
         set *mini-buffer* [list {*}[set *mini-buffer*]\
           [::kb::keyModifiers %s]-%K]
       }
       puts "%A|%s|%K"
       break 
     }
  
     ::kb::add-binding collect-keys "Control-g" {
       set ::*mini-buffer* Aborted
       # remove collect-keys bindtag
       bindtags .mini [::utils::lremove [bindtags .mini] collect-keys]
       focus .t
     }
   }
  
  namespace eval app {
    proc create-inferior-tcl {} {
      return [interp create]
    }
  }
  
   ::buffer::major-mode $buff scratch-mode

thanoulis's tabbed editor

What tEdit
Where https://github.com/thanoulis/tedit
Description A simple tabbed text editor written in core Tcl/Tk 8.5
License MIT
Updated 2020-04-11