A basic editor

Summary

Arjen Markus (12 august 2003) I wanted a simple, basic editor for young, inexperienced programmers. The thing is: any editor I know of is too general or too limited or has quirks that I do not fancy. So, what is easier than writing an editor yourself? After asking around a bit, I came up with this script.

Note:

  • It is far from complete
  • It contains things that I envisage as being a nice feature but I may be the only one
  • I also use this to criticise Tk -- see the new Tk revitalisation project

Most importantly: it contains some Windows-specific settings, as I wanted it to look as good as I could get it.


Code

 # ypedit.tcl --
 #    Shell procedures for Young Programmers Project:
 #    It defines several utility commands:
 #    ed       - edit a file
 #    vw       - view the contents of a file
 #

 package require Tk

 # ypshell --
 #    Namespace for the commands
 #
 namespace eval ::ypshell {

    namespace export ed vw

    variable editfile
    set editfile(count) 0
 }

 # ed --
 #    Edit a file
 #
 # Arguments:
 #    filename   Name of the file to edit
 #
 # Return value:
 #    None
 #
 proc ::ypshell::ed { filename } {
    ::ypshell::EditFile 1 0 $filename
 }

 # vw --
 #    View a file
 #
 # Arguments:
 #    filename   Name of the file to view
 #
 # Return value:
 #    None
 #
 proc ::ypshell::vw { filename } {
    ::ypshell::EditFile 0 0 $filename
 }

 # EditFile --
 #    Edit or view a file (actual procedure)
 #
 # Arguments:
 #    editable   Select edit mode (1=edit, 0=view)
 #    newopen    Allow new/open menu items (1=yes, 0=no)
 #    filename   Name of the file to edit/view
 #
 # Return value:
 #    None
 #
 proc ::ypshell::EditFile { editable newopen filename } {
    variable editfile

    incr editfile(count)
    set  w .edit$editfile(count)
    toplevel $w
    wm withdraw .
    wm title $w "Edit/view: $filename"

    set  mb $w.menubar
    frame      $mb
    pack       $mb -side top -fill x -pady 0
    menubutton $mb.file   -text File   -menu $mb.file.menu    -underline 0
    menubutton $mb.edit   -text Edit   -menu $mb.edit.menu    -underline 0
    menubutton $mb.search -text Search -menu $mb.search.menu  -underline 0
    menubutton $mb.help   -text Help   -menu $mb.help.menu    -underline 0

    menu       $mb.file.menu   -tearoff false
    menu       $mb.edit.menu   -tearoff false
    menu       $mb.search.menu -tearoff false
    menu       $mb.help.menu   -tearoff false

    pack       $mb.file $mb.edit $mb.search -side left
    pack       $mb.help -side left ;# Platform-dependent!

    #
    # Set up the "File" menu
    #
    if { $newopen } {
       if { $editable } {
          $mb.file.menu add command -label New -underline 0 \
             -command [list ::ypshell::NewFile]
       }
       $mb.file.menu add command -label Open -underline 0 \
          -command [list ::ypshell::OpenFile]
       $mb.file.menu add separator
    }

    if { $editable } {
       $mb.file.menu add command -label Save -underline 0 \
          -command [list ::ypshell::SaveFile $w $filename 0]
       if { $newopen } {
          $mb.file.menu add command -label "Save as ..." -underline 1 \
             -command [list ::ypshell::SaveFile $w $filename 1]
       }
       $mb.file.menu add separator
    }
    $mb.file.menu add command -label Exit -underline 1 \
       -command [list ::ypshell::ExitEdit $w $newopen $editable $filename]

    #
    # Set up the "Edit" menu
    #
    if { $editable } {
       $mb.edit.menu add command -label "Undo" -underline 0 \
          -command [list ::ypshell::UndoChange]
       $mb.edit.menu add command -label "Redo" -underline 0 \
          -command [list ::ypshell::RedoChange]
       $mb.edit.menu add separator
    }
    $mb.edit.menu add command -label "Mark line(s)" -underline 0 \
       -command [list ::ypshell::MarkLines]
    $mb.edit.menu add command -label "Unmark line(s)" -underline 1 \
       -command [list ::ypshell::UnmarkLines]
    $mb.edit.menu add separator
    $mb.edit.menu add command -label "Copy" -underline 0 \
       -command [list ::ypshell::CopyBlock]
    if { $editable } {
       $mb.edit.menu add command -label "Paste" -underline 0 \
          -command [list ::ypshell::PasteBlock]
       $mb.edit.menu add separator
       $mb.edit.menu add command -label "Delete" -underline 0 \
          -command [list ::ypshell::DeleteBlock]
    }

    #
    # Set up the "Search" menu
    #
    $mb.search.menu add command -label "Find" -underline 0 \
       -command [list ::ypshell::FindText $w]
    if { $editable } {
       $mb.search.menu add command -label "Replace" -underline 0 \
       -command [list ::ypshell::ChangeText]
    }

    #
    # Set up the "Help" menu
    #
    $mb.help.menu add command -label "Overview" -underline 1 \
       -command [list ::ypshell::HelpOverview]
    $mb.help.menu add command -label "About" -underline 0 \
       -command [list ::ypshell::AboutEdit]

    #
    # Create a thin separator
    #
    set ts $w.separator
    frame $ts -height 2 -relief sunken -borderwidth 1
    pack  $ts -side top -fill x

    #
    # Create the toolbar
    #
    set fnt       "Courier, 10"
    set fnt       "systemfixed"
    set fixedfont "systemfixed"
    set tb  $w.toolbar
    frame $tb -height 10
    button $tb.goto   -text "Go to:" -command [list ::ypshell::GotoLine $w ""]
    entry  $tb.lineno -textvariable ::ypshell::editfile(lineno,$w) \
       -width 5 -font $fnt
    button $tb.top    -text "Top"    -command [list ::ypshell::GotoLine $w 1]
    button $tb.bottom -text "Bottom" -command [list ::ypshell::GotoLine $w end]
    label  $tb.empty1 -text " "
    label  $tb.empty2 -text " "

    set ::ypshell::editfile(lineno,$w) 1

    pack   $tb.goto $tb.lineno $tb.empty1 $tb.top $tb.bottom $tb.empty2 -side left

    button $tb.find   -text "Find:" -command [list ::ypshell::FindText $w]
    entry  $tb.string -textvariable ::ypshell::editfile(find,$w) \
       -font $fnt

    set ypshell::editfile(find,$w) ""

    pack   $tb.goto $tb.find $tb.string -side left

    pack   $tb -fill x -side top

    #
    # Create the text widget and the scroll bars
    #
    set tf $w.textframe
    set tw $tf.text
    frame $tf
    scrollbar $tf.scrollx -orient horiz -command "$tw xview"
    scrollbar $tf.scrolly               -command "$tw yview"
    text      $tw         -yscrollcommand "$tf.scrolly set" \
                          -xscrollcommand "$tf.scrollx set" \
                          -font $fixedfont

    grid      $tw         $tf.scrolly
    grid      $tf.scrollx x
    grid      $tw         -sticky news
    grid      $tf.scrolly -sticky ns
    grid      $tf.scrollx -sticky ew

    grid columnconfigure $tf 0 -weight 1
    grid rowconfigure    $tf 0 -weight 1

    $tw configure -wrap none

    pack $tf -fill both -side top -expand 1

    set ypshell::editfile(textwidget,$w) $tw

    LoadFile $tw $filename
 }

 # LoadFile --
 #    Load the file into the text widget
 #
 # Arguments:
 #    textw      Text widget to use
 #    filename   Name of the file to edit/view
 #
 # Return value:
 #    None
 #
 proc ::ypshell::LoadFile { textw filename } {
    set infile [ open "$filename" "r" ]

    while { ! [ eof $infile ] } {
       gets $infile line
       $textw insert end $line
       $textw insert end "\n"
    }

    close $infile
 }

 # SaveFile --
 #    Save the file (possibly under a different name)
 #
 # Arguments:
 #    w          Main widget holding the text
 #    filename   Name of the file to edit/view
 #    newname    Ask for a new name or not
 #
 # Return value:
 #    None
 #
 proc ::ypshell::SaveFile { w filename newname } {
    variable editfile

    set tw $editfile(textwidget,$w)

    # Select a new file name -- TODO

    set outfile [ open "$filename" "w" ]

    set lineno 1
    while { [$tw compare $lineno.0 < end] } {
       puts $outfile [$tw get "$lineno.0" "$lineno.0 lineend"]
       incr lineno
    }

    close $outfile
 }

 # GotoLine --
 #    Go to a specified line number
 #
 # Arguments:
 #    w          Main widget containing the text widget
 #    pos        Position
 #
 # Return value:
 #    None
 #
 proc ::ypshell::GotoLine { w pos } {
    variable editfile

    set tw $editfile(textwidget,$w)
    if { $pos == "" } {
       set pos $editfile(lineno,$w)
    }

    if { $pos == "end" } {
       $tw mark set current "end linestart"
    } else {
       $tw mark set current "$pos.0"
    }
    $tw mark set insert [$tw index current]
    $tw see current

    focus $tw
 }

 # FindText --
 #    Find a text string
 #
 # Arguments:
 #    w          Main widget containing the text widget
 #
 # Return value:
 #    None
 #
 proc ::ypshell::FindText { w } {
    variable editfile

    set string $editfile(find,$w)
    set tw     $editfile(textwidget,$w)

    if { $string != "" } {
       set newpos [$editfile(textwidget,$w) search -forwards -exact -- $string "insert + 1 chars"]

       if { $newpos != "" } {
          $tw mark set current $newpos
          $tw mark set insert [$tw index current]
          $tw see current
          focus $tw
       }
    }
 }

 # ExitEdit --
 #    Exit and save the file (if wanted)
 #
 # Arguments:
 #    w          Widget from which it is called
 #    editable   Select edit mode (1=edit, 0=view)
 #    newopen    Allow new/open menu items (1=yes, 0=no)
 #    filename   Name of the file to edit/view
 #
 # Return value:
 #    None
 #
 proc ::ypshell::ExitEdit { w editable newopen filename } {
    if { $editable } {
       if { !$newopen } {
          SaveFile $w $filename 0
       } else {
          # Ask whether to save or not
       }
    }

    destroy $w
 }

 #
 # Simple test code
 #
 namespace import ::ypshell::ed
 namespace import ::ypshell::vw

 ed [file join [file dirname [info script]] ypedit.tcl]
 vw [file join [file dirname [info script]] ypedit.tcl]

Comments

(2003-08-12): minor change -- made the calls at the end relative to the script location. (Later) added wm withdraw . to get rid of the empty toplevel.

AM There is a rather annoying flaw in my way of setting up the menu bar and the menus - this causes most of the grieve (as pointed out by DKF): you better use the menu command, not the frame and menubars that I originally used.

Paul Kienzle (2003-10-12): changed -fill x to -fill both so text box resizes


See also A minimal editor for short code that might be educational reading for kids and notebook editor