Version 21 of Linux Console Text Editor In Pure TCL

Updated 2006-03-17 13:33:38

2004-06-16 SRIV This is a minimal console text editor for Linux written in pure TCL.

Usage: con-editor.tcl filename To save the file, press Ctrl-Q, it will prompt you to Save Y/n . Ctrl-Y will delete the current line.

If you use this from a minicom serial console in Linux, switch minicoms terminal type from VT102 to ANSI. I'm using this on a Gumstix with tclsh, since theres no more space left for any larger editor binary.


LV Perhaps the backspace key would work if you added a \u0008 (^h) to the current \u0007f (^?) case? (JH: Yes, added in)


Edited by JH 2005-05-04 to support any size terminal, clean up the code for improved readability, add real tab handling (tricky) and some other tidbits. Anyone wanting to interpret more escape codes should find it easier to work with.

Screenshot:

http://www.sriv.net/images/con-editor.jpg


 #!/bin/sh
 # The next line is executed by /bin/sh, but not tcl \
     exec tclsh "$0" ${1+"$@"}

 # con-editor.tcl a linux console based editor in pure tcl
 # 2004-06-16 Steve Redler IV
 # 2005-05-04 mods by Hobbs to work in any terminal size, clean up code
 #            and add more key functionality, tab handling
 # 2006-03-17 bugfix for cursor-left & for terms that report 0 cols & rows

 set filename [lindex $argv 0]

 proc edittext {fid} {
     global BUFFER IDX

     set viewRow 1        ; # row idx into view area, 1-based
     set viewCol 1        ; # col idx into view area, 1-based
     set bufRow 0        ; # row idx into full buffer, 0-based
     set bufCol 0        ; # col idx into full buffer, 0-based
     set IDX(ROWLAST) -1        ; # last row most recently displayed in view
     set IDX(COLLAST) -1        ; # last col most recently displayed in view
     set char ""                ; # last char received
     set line [lindex $BUFFER $bufRow] ; # line data of current line

     display $bufRow $bufCol
     home

     while {$char != "\u0011"} {
         set char [read $fid 1]
         if {[eof $fid]} {return done}

         # Control chars start at a == \u0001 and count up.
         switch -exact -- $char {
             \u0011 { # ^q - quit
                 return done
             }
             \u0001 { # ^a - beginning of line
                 set bufCol 0
             }
             \u0004 { # ^d - delete
                 if {$bufCol > [string length $line]} {
                     set bufCol [string length $line]
                 }
                 set line [string replace $line $bufCol $bufCol]
                 set BUFFER [lreplace $BUFFER $bufRow $bufRow $line]
                 set IDX(COLLAST) -1 ; # force redraw
             }
             \u0005 { # ^e - end of line
                 set bufCol [string length $line]
             }
             \u000a { # ^j - insert last yank
                 set currline [string range $line 0 [expr {$bufCol - 1}]]
                 set BUFFER [lreplace $BUFFER $bufRow $bufRow $currline]

                 incr bufRow
                 incr viewRow
                 set BUFFER [linsert $BUFFER $bufRow \
                                 [string range $line $bufCol end]]
                 set IDX(COLLAST) -1 ; # force redraw
                 set line [lindex $BUFFER $bufRow]
                 set bufCol 0
             }
             \u0019 { # ^y - yank line
                 if {$bufRow < [llength $BUFFER]} {
                     set BUFFER [lreplace $BUFFER $bufRow $bufRow]
                     set IDX(COLLAST) -1 ; # force redraw
                 }
             }
             \u0008 -
             \u007f { # ^h && backspace ?
                 if {$bufCol != 0} {
                     if {$bufCol > [string length $line]} {
                         set bufCol [string length $line]
                     }
                     incr bufCol -1
                     set line [string replace $line $bufCol $bufCol]
                     set BUFFER [lreplace $BUFFER $bufRow $bufRow $line]
                     set IDX(COLLAST) -1 ; # force redraw
                 }
             }
             \u001b { # ESC - handle escape sequences
                 set next [read $fid 1]
                 if {$next == "\["} { ; # \[
                     set next [read $fid 1]
                     switch -exact -- $next {
                         A { # Cursor Up (cuu1,up)
                             if {$bufRow > 0} {
                                 incr bufRow -1
                                 incr viewRow -1
                             }
                         }
                         B { # Cursor Down
                             if {$bufRow < [expr {[llength $BUFFER] - 1}]} {
                                 incr bufRow 1
                                 incr viewRow 1
                             }
                         }
                         C { # Cursor Right (cuf1,nd)
                             if {$bufCol < [string length $line]} {
                                 incr bufCol 1
                             }
                         }
                         D { # Cursor Left
                             if {$bufCol > [string length $line]} {
                                 set bufCol [string length $line]
                            }
                             if {$bufCol > 0} { incr bufCol -1 }
                         }
                         H { # Cursor Home
                             set bufCol 0
                             set bufRow 0
                             set viewRow 1
                         }
                         3 { # delete
                             set next [read $fid 1]
                             if {$bufCol > [string length $line]} {
                                 set bufCol [string length $line]
                             }
                             set line [string replace $line $bufCol $bufCol]
                             set BUFFER [lreplace $BUFFER $bufRow $bufRow $line]
                             set IDX(COLLAST) -1 ; # force redraw
                         }
                         5 { # 5 Prev screen
                             if {[read $fid 1] == "~"} {
                                 set size [expr {$IDX(ROWMAX) - 1}]
                                 if {$bufRow < $size} {
                                     set bufRow  0
                                     set viewRow 1
                                 } else {
                                     incr bufRow  -$size
                                     incr viewRow -$size
                                 }
                             }
                         }
                         6 { # 6 Next screen
                             if {[read $fid 1] == "~"} {
                                 set size [expr {$IDX(ROWMAX) - 1}]
                                 incr bufRow  $size
                                 incr viewRow $size
                                 if {$bufRow >= [llength $BUFFER]} {
                                     set viewRow [llength $BUFFER]
                                     set bufRow  [expr {$viewRow - 1}]
                                 }
                             }
                         }
                     }
                     # most of the above cause a BUFFER row change
                     set line [lindex $BUFFER $bufRow]
                 }
             }

             default {
                 set line [string replace $line $bufCol $bufCol \
                               $char[string index $line $bufCol]]
                 set BUFFER [lreplace $BUFFER $bufRow $bufRow $line]
                 incr bufCol [string length $char]
                 if {$bufCol > [string length $line]} {
                     set bufCol [string length $line]
                 }
                 set IDX(COLLAST) -1 ; # force redraw
             }

         }

         # Constrain current view idx
         if {$viewRow <= 1} {set viewRow 1}
         if {$viewRow >= ($IDX(ROWMAX) - 1)} {
             set viewRow [expr {$IDX(ROWMAX) - 1}]
         }
         set viewCol [expr {$bufCol + 1}]
         if {$viewCol >= $IDX(COLMAX)} {set viewCol $IDX(COLMAX)}

         # start and end view area to display
         set startRow [expr {$bufRow + 1 - $viewRow}]
         set startCol [expr {$bufCol + 1 - $viewCol}]

         display $startRow $startCol

         # translate viewCol to proper index (account for tabs)
         if {[string match "*\t*" $line]} {
             # let's just brute force over the line
             set i 0
             foreach c [split [string range $line \
                                   $startCol [expr {$bufCol - 1}]] ""] {
                 if {[string equal "\t" $c]} {
                     set i [expr {$i + (8 - $i%8)}] ; # align to 8c boundary
                 } else {
                     incr i
                 }
             }
             set viewCol [expr {$startCol + 1 + $i}]
         }

         idx [expr {$bufRow + 1}] $viewCol

         puts -nonewline "\u001b\[${viewRow};${viewCol}H" ; # place cursor
         cursor on
         flush stdout
     }
 }

 proc linerange {line start end} {
     # Get # *visual* chars - account for tabs (== 8c) in line range
     set line [string range $line $start $end]
     if {[string match "*\t*" $line]} {
         # let's just brute force over the line
         set i   0
         set end [expr {$end-$start}]
         set res {}
         foreach c [split $line ""] {
             if {[string equal "\t" $c]} {
                 set i [expr {$i + (8 - $i%8)}] ; # align to 8c boundary
             } else {
                 incr i
             }
             append res $c
             if {$i > $end} { break }
         }
         return $res
     }
     return $line
 }

 proc display {startRow startCol} {
     global IDX BUFFER

     cursor off ; home

     if {($IDX(ROWLAST) != $startRow) || ($IDX(COLLAST) != $startCol)} {
         # Add display size to get end points
         set endRow [expr {$startRow + $IDX(ROWMAX) - 1}]
         set endCol [expr {$startCol + $IDX(COLMAX) - 1}]

         for {set i $startRow} {$i < $endRow} {incr i} {
             puts -nonewline "\u001b\[K" ; # erase current line
             puts [linerange [lindex $BUFFER $i] $startCol $endCol]
         }

         set IDX(ROWLAST) $startRow
         set IDX(COLLAST) $startCol
     }
 }

 proc status {msg} {
     global IDX
     set len [expr {$IDX(ROWCOL) - 1}]
     set str [format "%-${len}.${len}s" $msg]
     puts -nonewline "\u001b\[$IDX(ROWMAX);00H$str"
 }

 proc idx {row col} {
     global IDX
     set str [format " L:%-4d C:%-4d" $row $col]
     # the string must not exceed $IDX(ROWCOLLEN) length
     puts -nonewline "\u001b\[$IDX(ROWMAX);$IDX(ROWCOL)H[string range \
                         $str 0 $IDX(ROWCOLLEN)]"
 }

 proc home {}  { puts -nonewline "\u001b\[1;1H"; flush stdout }
 proc clear {} { puts -nonewline "\u001b\[2J"; flush stdout }
 proc cursor {bool} {
     puts -nonewline "\u001b\[?[expr \
                 {$::IDX(ROWMAX)+1}][expr {$bool ? "h" : "j"}]"
     flush stdout
 }

 #start of console editor program
 #puts -nonewline $CLEAR

 proc console_edit {fileName} {
     global BUFFER IDX
     #Script-Edit by Steve Redler IV   [email protected]    5-30-2001

     set IDX(ROWMAX) 24
     set IDX(COLMAX) 80
     if {![catch {exec stty -a} err]
         && [regexp {rows (\d+); columns (\d+)} $err -> rows cols]} {
        if {$rows != "0" && $cols != 0} {
           set IDX(ROWMAX) $rows
           set IDX(COLMAX) $cols
        }
     }
     set IDX(ROWCOLLEN) 15
     set IDX(ROWCOL) [expr {$IDX(COLMAX) - $IDX(ROWCOLLEN)}]

     set infile [open $fileName RDWR]
     set BUFFER [split [read $infile] "\n"]
     close $infile

     clear ; home
     status "\u0007$fileName loaded"
     idx [llength $BUFFER] 1

     fconfigure stdin -buffering none -blocking 1
     fconfigure stdout -translation crlf

     flush stdout
     exec stty raw -echo
     edittext stdin
     status "Save '$fileName'? Y/n"
     flush stdout
     #fconfigure stdin -buffering full -blocking 1
     set line [read stdin 1]
     exec stty -raw echo
     if {$line != "n"} {
         set outfile [open $fileName w ]
         puts "len of buffer [llength $BUFFER]"
         for {set i 0} {$i<[llength $BUFFER]} {incr i} {
             puts $outfile [lindex $BUFFER $i]
         }
         close $outfile
         status " Saved"
     } else {
         status " Aborted"
     }
     after 100

     clear ; home
     exit 0
 }

 if {$filename == ""} {
    puts "\nPlease specify a filename"
    gets stdin filename
    if {$filename == ""} {exit}

 } 

 console_edit $filename

Category Application