auto-indent

if 0 {Richard Suchenwirth 2006-02-12 - Here's ten lines of code that I really like. If you intend to indent your source code (like good coders should), on most editors you'd have to sprinkle in spaces or tabs for that effect. Emacs at least takes care of bracing levels and auto-indents accordingly. I'm not reinventing Emacs here (which would take more than 10 loc anyway), but the following command, bound to <Return> in a text widget, does

  • determine the whitespace prefix of the current line
  • insert the prefix on the next line
  • if the current line ended in "{", indents one layer more
  • and creates the matching "}" line after the insert point.

I have scheduled events with after so that they happen after the <Return> has been inserted. This code goes into e: a tiny editor plugin for eTcl which is part of Sepp, but obviously does not depend on it. }

 proc e'indent {w {extra "    "}} {
    set lineno [expr {int([$w index insert])}]
    set line [$w get $lineno.0 $lineno.end]
    regexp {^(\s*)} $line -> prefix
    after 1 [list $w insert insert $prefix]
    if {[string index $line end] eq "\{"} {
        after 2 [list $w insert insert $extra]
        after 3 [list $w insert insert+1c $prefix\}\n]
    }
 }

#-- Testing demo:

 pack [text .t]
 .t insert end \n ;#-- see below
 bind .t <Return> {e'indent %W}
 focus -force .t

 bind . <Escape> {exec wish $argv0 &; exit}
 bind . <F1> {console show}

if 0 {Known problem: if the insert cursor is at the very end of the text, "insert+1c" delivers the same position as "insert", leading to wrong indentation on open-brace. I haven't found a good solution for that. But it rarely happens when editing existing text, and in an empty text widget, just type one newline and <Up> to avoid this problem.

2006-03-20 HE I would use this version;-)

 proc e'indent {w {extra "    "}} {
        set lineno [expr {int([$w index insert])}]
        set line [$w get $lineno.0 $lineno.end]
        regexp {^(\s*)} $line -> prefix
        tk::TextInsert $w "\n$prefix"
        if {[string index $line end] eq "\{"} {
                tk::TextInsert $w "$extra"
        }
 }

Or considering the close braces:

 proc e'indent {w {extra "    "}} {
        set lineno [expr {int([$w index insert])}]
        set line [$w get $lineno.0 $lineno.end]
        regexp {^(\s*)} $line -> prefix
        if {[string index $line end] eq "\{"} {
                tk::TextInsert $w "\n$prefix$extra"
        } elseif {[string index $line end] eq "\}"} {
                if {[regexp {^\s+\}} $line]} {
                        $w delete insert-[expr [string length $extra]+1]c insert-1c
                        tk::TextInsert $w "\n[string range $prefix 0 end-[string length $extra]]"
                } else {
                        tk::TextInsert $w "\n$prefix"
                }
        } else {
                tk::TextInsert $w "\n$prefix"
        }
 }

The corresponding binding is:

 bind .t <Return> {e'indent %W;break}

We don't need the row '.t insert end \n' anymore.


See also Maintaining indentation in a text widget when go to a new line


}