A minimal editor explained

Richard Suchenwirth 2003-08-12 - In contrast to a basic editor which has quite some features, here's an utterly simple one which just allows to load and save files, and of course edit, and cut and paste, and whatever is built-in into the text widget anyway. And it has a bit "online help"... ;-)

This is yet another study in literate programming. For the bare source code (26 lines), see A minimal editor.

It is always a good idea to start a source file with some explanations on the name, purpose, author, and date. I have recently picked up the habit to put this information into a string variable (which in Tcl can easily span multiple lines), so the same info is presented to the reader of the source code, and can be displayed as online help:

 set about "minEd - a minimal editor
 Richard Suchenwirth 2003
 F1: help
 F2: load
 F3: save
 "

The visible part of a Graphical User Interface (GUI) consists of widgets. For this editor, I of course need a text widget, and a vertical scrollbar. With the option "-wrap word" for the text widget, another horizontal scrollbar is not needed - lines longer than the window just wrap at word boundaries.

Tk widgets come on the screen in two steps: first, they are created with an initial configuration; then, handed to a "geometry manager" for display. As widget creation commands return the pathname, they can be nested into the manager command (pack in this case), to keep all settings for a widget in one place. This may lead to over-long lines, though.

Although the scrollbar comes to the right of the text, I create and pack it first. The reason is that when a window is made smaller by the user, the widgets last packed first lose visibility.

These two lines also illustrate the coupling between a scrollbar and the widget it controls:

  • the scrollbar sends a yview message to it when moved
  • the widget sends a set message to the scrollbar when the view changed, for instance from cursor keys

And these two lines already give us an editor for arbitrarily long text, with built-in capabilities of cut, copy, and paste - see the text man page. Only file I/O has to be added by us to make it really usable.

 pack [scrollbar .y -command ".t yview"] -side right -fill y
 pack [text .t -wrap word -yscrollc ".y set"] -side right -fill both -expand 1

The other important part of a GUI are the bindings - what event shall trigger what action. For simplicity, I've limited the bindings here to a few of the function keys on top of typical keyboards:

 bind . <F1> {tk_messageBox -message $about}

Online help is done with a no-frills tk_messageBox with the "about" text defined at start of file. - The other bindings call custom commands, which get a filename argument from Tk's file selector dialogs:

 bind . <F2> {loadText .t [tk_getOpenFile]}
 bind . <F3> {saveText .t [tk_getSaveFile]}

These dialogs can also be configured in a number of ways, but even in this simple form they are quite powerful - allow navigation around the file system, etc. On Windows they call the native file selectors, which have a history of previously opened files, detail view (size/date etc.)

When this editor is called with a filename on the command line, that file is loaded on startup (simple as it is, it can only handle one file at a time):

 if {$argv != ""} {loadText .t [lindex $argv 0]}

The procedures for loading and saving text both start with a sanity check of the filename argument - if it's an empty string, as produced by file selector dialogs when the user cancels, they return immediately. Otherwise, they transfer file content to text widget or vice-versa. loadText adds the "luxury" that the name of the current file is also put into the window title. Then it opens the file, clears the text widget, reads all file contents in one go, and puts them into the text widget.

 proc loadText {w fn} {
    if {$fn==""} return
    wm title . [file tail $fn]
    set fp [open $fn]
    $w delete 1.0 end
    $w insert end [read $fp]
    close $fp
 }

saveText takes care not to save the extra newline that text widgets append at end, by limiting the range to "end - 1 c"(haracter).

 proc saveText {w fn} {
    if {$fn==""} return
    set fp [open $fn w]
    puts -nonewline $fp [$w get 1.0 "end - 1 c"]
    close $fp
 }

DKF: Are you targetting 8.4 or later? If so, add -undo 1 to the options to text and get full undo/redo support. Easy!}