# <20200301.1736.20>
# spell check.

# This script is ment to be 'attached' to a text window.  It provides
# spell check capability for that window using one of:
# hunspell
# ispell
# aspell
#
# We assume (most likey incorrectly :) that all three of these have the
# same syntax and usage.  In any case, the code will be written to the
# (in hand) hunspell interface.

# We assume the following functions are available:
# "_"  takes a text string and returns the equivalent in the current
#      language.  We default this to: proc _ {s} {return $s}

#
# 'Attaching:'
# To attach this to a text window, we assume that there is already a 
# menu on <3> and we provide a list of entries for that menu.
#
# So to spell check, click <3> and choose one of the spell check 
# options:
# The list is provided by a call to 'spellCheckText w args' where w is the text
# window name.

# args is a (possibly empty) list of couplets taken from the following:
#
#  -log  script          a script that is called to log "progress" info
#                        This includes "no spelling errors found"
#                                      <info returned from the hunspell at
#                                      inital invocation>
#                        If script is nil {} these messages are suppressed.
#                        If -log is not provided these messages are sent to
#                        stdout (via puts).
# -expect <bool>         If true use "expect" to dialog with spell program.
#                        Expect is required in Windows. This overrides (if 1)
#                        that default. False here does nothing.
# -file  <name>          file name to pass to hunspell to help it suppress 
#                        and/or adapt to the files syntax (see hunspell manual)
# -filter <list>         <list> should be a list of couplets of format:
#                        NAME  script
#                        NAME will appear as a sub menu item under 
#                        "spell check filters" and, when selected will cause
#                        spell check, when doing a "selection" or "all" spell
#                        check,to call "script" passing each line that is to be 
#                        spell checked.  The script should return a list of 
#                        couplets  which are the indexes into the string of
#                        what is to be  checked. So, for example, {0 end} would
#                        check the whole line. {{0 20} {40 end}} would not 
#                        check characters 21 thru 39.  A null return will cause
#                        none of the line to be checked.  It is expected that 
#                        the filter might look for quoted strings or comments.
#                        The script should keep track of multi-line comments
#                        and for this reason is called with the "string" and
#                        the "line number".  "script" may be multiword if 
#                        a single script needs additional parameters
#
proc spellCheckText { w args} {
  if {[info procs "_"] == {}} {
    proc _ {s args} {return [format $s {*}$args]}
  }
  setUpSpellCheck $w
  set NameSpace${w}::log "puts stdout"
  set NameSpace${w}::file {}
  foreach {tag value} $args {
    switch -exact $tag {
      -log  {set NameSpace${w}::log $value}
      -expect {set NameSpace${w}::expect $value}
      -file {set NameSpace${w}::file $value}
      -filter {
	set NameSpace${w}::filter $value
	set NameSpace${w}::activeFilter None
	if {$value != {} && [llength $value] > 1} {
	  set filter [list [_ "Spell check filter.."] \
			  [list NameSpace${w}::spellFilterMenu]]
	}
      }
    }
  }
  if {[set NameSpace${w}::log] == {}} {
    set NameSpace${w}::log [list set NameSpace${w}::lastmess]
  }
    set return [list [_ "Spell check all"] [list NameSpace${w}::spellCheckThis \
						0.0 end all]\
		    [_ "Spell check selected"] \
		    [list NameSpace${w}::spellCheckThis {sel.first wordstart} \
			 {sel.last wordend} sel] \
		    [_ "Spell check word"] [list NameSpace${w}::spellCheckWord ]\
		    [_ "End Spell check"] [list NameSpace${w}::clearSpellTags]]
    if {[info exists filter]} {
      lappend return {*}$filter
    }
    return $return
  }


proc setUpSpellCheck { w } {
  # we put the key data in a namespace so we can do several windows
  # with out messing up one with another.
  if {[namespace exists NameSpace${w}] } {
    # treat existing space as stale, shut it down
    spellShutAlldown NameSpace${w}
  }

  namespace eval NameSpace${w} {
    variable w {}
    variable closing 0
    variable fid {}
    variable inStack {}
    variable result {}
    variable userDicModified 0
    variable lastPosition {}
    variable dw 0
    variable expect 0
    variable dwp {} ; # dialog window prefix

    proc spellFilterMenu {} {
      variable w
      variable filter
      variable activeFilter
      set mw $w.spellfilter
      destroy $mw
      menu $mw -tearoff false
      foreach filt $filter {
	lassign $filt name opt
	$mw add radiobutton -label $name -value $opt \
	    -variable NameSpace${w}::activeFilter
      }
      tk_popup $mw [winfo pointerx $w] [winfo pointery $w]
    }
    
    proc spellSetColors {} {
      variable w
      # inherit the colors...
      variable highlightcolor       [$w cget -highlightcolor]
      variable selectforeground     [$w cget -selectforeground] 
      variable selectbackground     [$w cget -selectbackground] 
      variable highlightbackground  [$w cget -highlightbackground]
      variable background           [$w cget -background]
      variable foreground           [$w cget -foreground] 
    }

    proc spellShutdown {} {
      variable w
      variable closing
      variable fid
      variable userDicModified
      variable expect
      variable spawn_id
      # This routine is called when the window we are doing a spell
      # check on is closed.  We want to tell the spell routine to
      # save the users dictionary additions and then to quit.
      set closing 1
      stopSpellFlash $w {}
      while {[winfo exists $w.spell]} {
	# if we are here and the window exists some one must have pulled
	# the plug, i.e. the window "w" is being destroyed. Wait for this
	# window to quit first...
	update
      }
      clearSpellTags
      if {$userDicModified} {
	spellLog [_ "Updating user dictionary"]
	if {$expect} {
	  exp_send "#\r"
	  exp_close
	  set fid 0
	} else {
	  chan puts $fid "#"
	}
	chan flush $fid
	pipeoAbort $fid
      }
    }

    proc clearSpellTags {args} {
      variable result
      variable aborting
      variable w
      set n 0
      foreach tag $result {
	set tagn [lindex $tag 0]$n
	if {$args == {} || $args == $tagn} {
	  catch "$w tag delete $tagn" out
	  if {$args != {}} {
	    set result [lreplace $result $n $n [list [lindex $tag 0] -1]]
	    return
	  }
	}
	incr n
      }
      # This is the global shutdown, take down the query and flash windows
      set aborting [winfo exists $w.spell]
      if {$aborting} {
	set scr [bind $w.spell <Destroy>]
	frputs "spell check kill window  " out aborting w scr
	destroy $w.spell;frputs {doit }
	#vwait [namespace current]::aborting
      }
      set result {}
    }

    proc spellCheckWord {} {
      variable w
      # We find the word by either the insert cursor or the selected area.
      # First check to see if there is a highlighted area...

      set offset -0
  
      set out [$w get -- {insert wordstart} {insert wordend}]
      if {$out == " " || $out == "\n"} {
	# Position is either at the end of a word or line
	# Try backing up one...
	set offset -1
      }
      spellCheckThis [list insert $offset c wordstart]\
	  [list insert $offset c wordend] wd
    }

    proc spellLog {m} {
      # not sure why we have a problem here, but this is stdout, just ignor
      catch {puts "SpellLog: >$m<"}
      variable log
      eval "$log \{$m\}"
    }

    proc spellTestForSqt {wordin} {
      # test to see if first and last chars are "'" and
      # strip and retest if so.
      # returns stripped word if it still fails spellcheck
      # else returns {}
      set word $wordin
      while {[string range $word 0 0] == "'" && \
		 [string range $word end end]  == "'" } {
	set word [string range $word 1 end-1]
      }
      #puts "'''$word $wordin"
      if {$word == $wordin} {
	#puts "returning $wordin"
	return $wordin
      }
      #puts "round"
      set r [spellCheckThisOne $word 0.0]
      #puts "round2 $word >$r<"
      return {*}$r
 
    }

    proc spellCheckThisOne { string index} {
      variable fid
      variable w
      variable expect
      variable inStack
      set in $string
      # We MUST break this into lines as that is the only way
      # to prevent the first character from taking us into
      # never-never land.  This way we can use '^' as the 
      # first character to avoid such untimely trips.
      lassign [split $index "."] line char
      set result {}
      set callCount 0
      set indata {}
      if {$expect} {
	set indata [spellCheckExpect "^$string"]
      } else {
	if {$inStack != {}} {
	  frputs "Extra data from spell pgm:  " inStack
	  set inStack {}
	}
	chan puts $fid "^$string"
	chan flush $fid
	set count 1
	while {$count != 0} {
	  set count [chanSub data]
	  #puts "count $count data $data"
	  if { $count  != 0 } {
	    lappend indata $data
	    #puts "$data"
	  }
	}
      }
      frputs "inline  " indata
      foreach data $indata {
	#puts "3 $data $result"
	switch [string range $data 0 0] {
	  & {
	    #puts "doing &"
	    set sub {}
	    lassign [split $data ":"] one two
	    lassign $one foo word dun offset
	    # the '-1' is needed because of the "^" we added above
	    set offset [expr {$offset == {} ? 0 : $offset}]
	    incr offset -1
	    foreach itm [split $two ","] {
	      lappend sub [string trim $itm]
	    }
	    set word [spellTestForSqt $word]
	    if {$word == {} } {continue}
	    set off [expr {$offset + $char}]
	    if {[llength $word] == 1} {
	      #puts "is1 $word"
	      lappend result [list $word $line.$off $sub]
	    } else {
	      #puts "is~1 $word"
	      lappend result [lreplace $word 1 1 $line.[incr off]]
	    }
	    frputs "result-1  " word line off
	    #puts "1 $word $result"
	  }
	  default {
	    # This should be the "#" case.  We just have an offset
	    # however, the test for "'" may return more...
	    lassign $data flag word offset
	    set offset [expr {$offset == {} ? 0 : $offset}]
	    incr offset -1
	    set off [expr {$offset + $char}]
	    set word [spellTestForSqt $word]
	    if {$word == {} } {continue}
	    if {[llength $word] == 1} {
	      lappend result [list $word $line.$off]
	    } else {
	      lappend result [lreplace $word 1 1 $line.[incr off]] 
	    }
	    frputs "result-2  " word line off
	  }
	}
      }
      #frputs "result  " result
      return $result
    }

    proc spellCheckThis { index1 index2 which} {
      variable fid
      variable highlight
      variable w
      variable aborting
      variable highlightcolor 
      variable selectforeground 
      variable selectbackground 
      variable highlightbackground
      variable background 
      variable foreground 
      variable result
      variable activeFilter

      if {$fid == {}} {
	if { ! [spellCheckOpen]} {return}
      }
      clearSpellTags
      if {$aborting} {
	# we have a reentry problem in that the last entry put up
	# a window which has yet to clear.  Here we set a timer
	# to restart us and then return in the hope that on 
	# return the prior call will have completed...
	after idle "[namespace current]::spellCheckThis \
                           [list $index1] [list $index2] $which"
	frputs "spell check waiting... "
	return
      }
      
      # this prevents errors when nothing is selected...
      set r [catch "$w index [list $index2]" index2]
      if {$r != 0} {
	frputs "index2 err  :" index2 ::errorInfo
	return
      }
      set firstIndex [$w index $index1]
      lassign [split $index2 "."] endLine endChar
      frputs "SpellCheckThis  " firstIndex index2
      set callCount 0
      while {[$w compare $firstIndex < $index2]} {
	lassign [split $firstIndex "."] line char
	set Schar [expr {$line == $endLine  ? $endChar : "end"} ]
	set r [catch "$w get -- [list $firstIndex] [list $line.$Schar]" out]
	frputs "Getting text  " r out firstIndex line Schar endLine
	if {$activeFilter != "None" && $which != "wd"} {
	  foreach ent [{*}$activeFilter $out $line] {
	    lassign $ent first last
	    lappend result {*}[spellCheckThisOne \
				   [string range $out $first $last]\
				   $line.[expr {$char + $first}]]
	  }
	} else {
	  lappend result {*}[spellCheckThisOne $out $firstIndex]
	  frputs "spllCheckThisOne  " out firstIndex result
	}  
	set firstIndex [incr line].0
	if {$callCount > 10} {
	  set callCount -1
	  spellLog [_ "Checked %s lines" [incr Lines 10]]
	}
	incr callCount
      }
      if {$which == "wd" && $result != {}} {
	set obj [lindex $result 0 0]
	set tagn [lindex $result 0 0]0
	$w tag delete $tagn
	$w tag config $tagn -foreground $highlightcolor\
	    -background $highlightbackground
	$w tag bind $tagn <1> "[namespace current]::spellFix\
                               $tagn  0"
	$w replace $index1 $index2 $obj $tagn
	spellFix [lindex $result 0 0]0 0
	return
      }
      #  find each word and set it up...
      set n 0
      set len 0
      #puts "result $result"
      foreach objn $result {
	lassign $objn obj loc
	#puts "$objn $obj $loc"
	set tagn ${obj}$n
	set len [string length $obj]
	$w tag delete $tagn
	$w tag config $tagn -foreground $highlightcolor\
	    -background $highlightbackground
	$w tag bind $tagn <1> "[namespace current]::spellFix  $tagn $n"
	$w replace $loc "$loc + $len chars" $obj $tagn
	#set start [$w index "$loc + $len chars"]
	incr n
      }
      if {$result == {}} {
	spellLog [_ "No spelling error(s) found"]
	return
      }
      spellFix [lindex $result 0 0]0 0
      
    }

    # Called by smart_dialog 'textfun' option.
    # 'w' is the smart_dialog text window.
    proc SpellPutOptions {opts w} {
      variable highlightcolor
      variable highlightbackground
      variable selectforeground
      variable selectbackground
      variable background
      variable foreground

      $w insert insert "[_ "Fixing word %s" [lindex $opts 0]]\n"
      set n 0
      foreach possible [lindex $opts 2] {
	$w insert insert "$possible\n" $n
	$w tag bind $n <1> \
	    "[namespace current]::spellUseThis $w \"$possible\""
	$w tag bind \
	    $n <Enter> "$w tag config $n \
                              -background $selectbackground\
                              -foreground $selectforeground"
	$w tag bind \
	    $n <Leave> "$w tag config $n \
                             -background $background\
                             -foreground $foreground"
	incr n
      }
    }

    # this function is bound to <1> for each suggested choice in the
    # smart_dialog window. 'w' is the smart_dialog text window
    proc spellUseThis {w this} {
      # called by the user clicking on a word in the smart_dialog box
      variable word
      set word $this
      set ::smart_dialogBut$w 0
    }
    
    proc spellFlash {w tag flash oncolor offcolor onfgclr offfgclr} {
      variable timer
      catch {after cancel $timer}
      if {![winfo exists $w]} {return}
      $w tag config $tag -background $oncolor -foreground $onfgclr
      set timer [after $flash "[namespace current]::spellFlash \
                        $w $tag $flash $offcolor $oncolor\
                        $offfgclr $onfgclr"]
    }
    proc stopSpellFlash {w tag} {
      variable timer
      variable highlightcolor
      variable highlightbackground
      after cancel $timer
      if {![winfo exists $w] || $tag == {}} {
	return
      }
      $w tag config $tag -background $highlightbackground \
	  -foreground $highlightcolor
    }

    proc spellFix {tag idx} {
      variable w
      variable closing
      variable aborting
      variable addto
      variable fixall
      variable word
      variable auto
      variable fid
      variable background
      variable foreground
      variable highlightbackground
      variable highlightcolor
      variable result
      variable userDicModified
      variable expect
      variable spawn_id
      variable dwp

      set addto 0
      set fixall 1
      set auto 1
      set lasteffort {}
      
      while {1} {
	set args [lindex $result $idx]
	#puts "in spellFix with w= $w and $tag< and >$idx< $args"
	set addto 0
	$w see $tag.first
	spellFlash $w $tag 500 $background $highlightbackground \
	                       $foreground $highlightcolor
	set word [lindex $args 0]
	if {$lasteffort != {}} {
	  set word $lasteffort
	}
	set r [smart_dialog  ${dwp}spell [winfo parent $w]\
		   [_ "Spell correct"] {} 0 6 \
		   [list \
			[list [_ {You Spell}] \
			     [list -textvariable [namespace current]::word]]\
			[list [_ {Fix All Occurances}]\
			     [list -variable [namespace current]::fixall]] \
			[list [_ {Add to dict}]\
			     [list -variable [namespace current]::addto]] \
			[list [_ {Auto Advance}] \
			     [list -variable [namespace current]::auto]] \
			[list [_ {Accept: %s} $word] {-anchor w}]\
			[list [_ {Cancel}]]] \
		   [list textfun \
			[list [namespace current]::SpellPutOptions $args]]\
		   [list stackbuttons {}] \
		   [list arrange {3 2 1}]\
		   [list position [namespace current]::spellPositionWindow]]
	# 0 means use "word'
	# 1,2,3 (should never happen)
	# 4 This word is ok here (use orgional) 
	# 5 Cancel spell check (NOT end spellcheck, just quit this query)
	# puts "switch $r"
	set aborting 0
	frputs "Abort spellcheck  " r
	if {$closing || $r == -1} {
	  stopSpellFlash $w $tag
	  return
	}
	switch $r {
	  1 -
	  2 -
	  3 {
	    puts "Unexpected return from smart_dialog"
	    return
	  }
	  4 {
	    set word [lindex $args 0]
	    if {$lasteffort != {}} {
	      set word $lasteffort
	      $w replace $tag.first $tag.last $word 
	    }
	  }
	  5 {
	    stopSpellFlash $w $tag
	    return
	  }
	  0 {
	    # replace the word. and then fall into the dictionary tests
	    # Now we check if the user typed a new word.  If so, we need 
	    # to check if the new word is good.
	    if {$word ni [lindex $args 2]} {
	      # s/he typed something...
	      set r [spellCheckThisOne $word 0.0]
	      if {$r != {}} {
		set lasteffort $word
		# puts "no-no $r"
		# and it is not a recognized word
		# we have a new list of options.  Change the options for
		# the orgional word to the new ones and try again.
		set newresult {}
		set flagword [lindex $result $idx 0]
		# puts "looking for $flagword"
		foreach ent $result {
		  if {$flagword == [lindex $ent 0]} {
		    lappend newresult [lreplace $ent 2 2 [lindex $r 0 2]]
		  } else {
		    lappend newresult $ent
		  }
		}
		set result $newresult
		# puts "continue"
		continue
	      }
	      # The typed word is good...
	    }
	    # puts "replace the word"
	    $w replace $tag.first $tag.last $word 
	  }
	}
	set lasteffort {}
	# If we are here the user has ok'ed a word.  If it is the same as before
	# we either tell hunspell that the word is ok for now or to add it to 
	# the users dictionary
	clearSpellTags $tag
	if {$word == [lindex $args 0]} {
	  if {$addto} {
	    set action  "*"
	    set mes [_ "user"]
	    set userDicModified 1
	  } else {
	    set action "@"
	    set mes [_ "document"]
	  }
	  spellLog [_ "Adding %s to %s dictionary" $word $mes]
	  if {$expect} {
	    spellCheckExpect "$action$word" 2
	  } else {
	    chan puts $fid "$action$word\n" ; # do we want/need the linefeed?
	    # chan gets $fid foo
	    #puts "hunspell responds >$foo<"
	  }
	}
	if {$fixall} {
	  while {[set next [spellFindNext $tag 1]] != {}} {
	    lassign $next tagt n
	    $w replace $tagt.first $tagt.last $word 
	    clearSpellTags $tagt
	  }
	}
	if {!$auto} {return}
	set next [spellFindNext $tag 0]
	if {$next == {}} {return}
	lassign $next tag idx
      }
    }

    # 'w' here is the smart_dialog window name
    # 'master' (what we passed to smart_dialog) is the text window we
    #          are spell checking.
    proc spellPositionWindow {w master} {
      variable lastPosition
      variable dw 
      # If we put this on the top window we will get called
      # for each child window so we put it on the child
      bind $w.text <Destroy> "+[namespace current]::spellDestroy $w"

      # we want to position the window as it was last time.
      # if we don't have a position we stumble around in the dark...
      # we need to know the width of the display, but only one
      # in the case of multi head screens.
      # This code ASSUMES that displays are either arranged side by side
      # OR stacked.  It fails if they are both ... (like 4 displays)
      # and for this code we only care about width...

      if {$dw == 0} {
	set scw [winfo screenwidth $master]
	set sch [winfo screenheight $master].0
	set aspect [expr {${scw} / $sch}]
	set nd 1
	update
	while {$aspect / $nd > 2.} {incr nd}
	set dw [expr $scw / $nd] ; # display width
	# div should be the number of displays wide...
	# now figure the bounds of the display that has the master
      }
      # for what ever reason, winfo geo does not give correct x/y values
      # but winfo rootx/y does...
      foreach {var val} {mw width mh height mx rootx my rooty} {
	set $var [winfo $val $master]
      }
      # the master's display is the one under mx+(mw/2)
      set mwCent [expr {$mx + $mw /2}]
      waitForIdle
      set ww [winfo reqwidth $w]
      frputs "New   " ww
      if {$lastPosition != {}} {
	# lastPosition is where the user left the window last time
	# We try to maintain the relative position.  This may be
	# to one of three references, 1, left side to the master right
	# 2 right side to master left or 3, center to center
	# We use 1) if the right side is to the right of master center
	# 2) if the left side is left of master center, and 3) if
	# neither 1) or 2).
	lassign [split $lastPosition "+x"] lw lh lx ly
	# (l for last)
	if {$lx > $mwCent} {
	  # case 1
	  set newx $lx
	  frputs "Case 1  " newx mwCent lw lx
	} elseif {$lx + $lw < $mwCent} {
	  # case 2
	  set newx [expr {$lx + $lw - $ww}]
	  frputs "Case 2  " newx mwCent lw lx
	} else {
	  # case 3
	  set newx [expr {$lx + $lw / 2 - $ww /2}]
	  frputs "Case 3  " newx mwCent lw lx
	}
      } else {
	# assume there may be negative values up to (display width) * (nd -1)
	set minx [expr {-($dw  * ($nd -1))}]
	set left $minx
	while {$mwCent > $left} {incr left $dw}
	set right $left
	incr left -$dw
	# we want to put the window on the side of the master with the most room
	if {2 * $mwCent > ($left * 2) + $dw} {
	  # put on left
	  set newx [expr {$mx - $ww - 8}]
	  # if not within window, allow some overlap
	  if {$newx < $left} {set newx $left}
	  frputs "On left  " newx mwCent dw ms ww left
	} else {
	  # put on right
	  set newx [expr {$mx + $mw + 8}]
	  if {$newx + $ww > $right} {set newx [expr {$right - $ww}]}
	  frputs "On right  " newx mwCent dw mx mw right
	}
      }
      wm geo $w +$newx+$my
      frputs "Applied  " w newx my
    }

    proc spellDestroy {w} {
      variable lastPosition
      # should come here when the smart_dialog is done.
      set lastPosition [wm geo $w]
      frputs lastPosition w
    }

    proc spellFindNext {thistag wd} {
      # wd false if next entry
      #    true if next occurrence of same word
      variable w
      variable result
      set n 0
      foreach ent $result {
	set word [lindex $ent 0]
	if {$thistag == "$word$n"} {
	  break
	}
	incr n
      }
      set n [expr {$wd ? 0 : $n + 1}]
      foreach ent [lrange $result $n end] {
	set exist [expr {[lindex $ent 1] != -1}]
	if {$exist} {
	  if {!$wd || $word == [lindex $ent 0]} {
	    return [list [lindex $ent 0]$n $n]
	  }
	}	
	incr n
      }
      return {}      
    }

    # spellCheckExpect handles 3 cases
    # 1 send "out" expect 0 or more non-null lines followed by a null line
    # 0 send "out" expect exactly one non-null line
    # 2 send "out" expect nothing back
    # In all cases handle the output echo.
    proc spellCheckExpect {out {end 1}} {
      variable spawn_id
      
      if {$out != {} } {
	exp_send "$out\r"
	frputs "spell sending:  " out
      }
      if {$end == "2"} {return}
      set input {}
      set tmp {}
      while {1} {
	expect \
	    -re "^\r?\n" {
	      set r 0
	      frputs "spell found:  " expect_out(1,string) r
	      set expect_out(1,string) ""
	    }\
	    -re "(\[^\r\n]+)\r?\n" {
	      if {$expect_out(1,string) == $out} {
		frputs "output echoed  " out
		if {$end == "2"} {return}
		exp_continue
	      }
	      set r 1
	      frputs "spell found:  " expect_out(1,string) r
	    } \
	    timeout {set r 2} \
	    eof {set r 3}
	
	switch $r {
	  0 {
	    if {$tmp != {}} {
	      lappend input $tmp
	    }
	    return $input
	  }
	  1 {
	    if {[string index $expect_out(1,string) 0] in {* + & #} } {
	      if {$tmp != {}} {
		lappend input $tmp
		set tmp {}
	      }
	    }
	    append tmp $expect_out(1,string)
	    if {$end == "0" } {
	      if {$tmp != {}} {		
		lappend input $tmp
	      }
	      return $input}
	  }
	  2 -
	  3 {
	    frputs "Timeout or eof  " out end expect_out(1,string)
	    spellLog "Timeout or eof on spell program"
	    if {$tmp != {}} {		
	      lappend input $tmp
	    }
	    return $input
	  }
	}     
      }
    }

    # These two routines process the data coming from the spell program
    # The first is called on lines avaiable and stores the lins in inStack
    # The second waits for things to show up and then returns the lines from
    # inStack
    proc spellResult {lfid why {arg {}}} {
      variable inStack
      variable fid
      if {$why == "data" } {
	lappend inStack $arg
      }
      if {$why == "eof" } {
	set fid {}
      }
      return
      # we may care about other stuff, but...
    }

    proc chanSub {{arg {}}} {
      variable inStack
      # To prevent loosing control, we will time this out
      set timer [after 30000 {error "Spell program is not responding"}]
      while {[llength $inStack] == 0} {vwait [namespace current]::inStack}
      after cancel $timer
      set inStack [lassign $inStack rtv]
      frputs "chanSub finds:  " rtv
      set rtv [string range $rtv 0 end-1]
      if {$arg != {} } {
	uplevel "set $arg \"$rtv\""
	return [string length $rtv]
      }
      return $rtv
    }

    # Open the pipe...
    # returns 1 on success, else 0
    proc spellCheckOpen { } {
      variable fid
      variable file
      variable w
      variable expect
      variable spawn_id
      variable dwp
      
      if {$::tcl_platform(platform) == "windows" || $expect} {
	set expect 1
	if {[catch "package require Expect"] != 0} {
	  smart_dialog ${dwp}oops $w  [_ "Info"] \
	      [list "Required package Expect not found.\nSpell\
                   check not available."] 0 1 [_ "OK"]
	  return 0
	}
      }
      foreach pgm { ispell hunspell  aspell} {
	set pgmName [auto_execok $pgm]
	if {$pgmName != {}} {break}
      }
      if {$pgmName == {}} {
	smart_dialog ${dwp}oops $w [_ "No spellcheck program"] \
	    [list [_ "No spell check program found. \
	     \nChecked for hunspell, ispell and aspell"]] \
	    0 1 "Ok"
	return 0
      }
      
      if {$expect} {
	exp_log_user 0
	if {[set r [catch "eval \"spawn -noecho $pgmName -a\""]]} {continue}
	set back [spellCheckExpect {} 0]
	if {$back != {} } {
	  set fid 1
	  return 1
	}
      } else {
	set cmd "$pgmName -a"
	lassign [pipeoExec $cmd r+ [namespace current]::spellResult] r fid	
	if {$r != 0 } {
	  error "Failed to open $pgmName to check spelling: $fid"
	}
      }
      # Ok, we have the pipe open. 
      if {$expect} {
	spellLog $back
	spellCheckExpect "! " 2
      } else {
	spellLog "[chanSub]"
	chan puts $fid "! "
      }
      if {$file != {}} {
	if {$expect} {
	  spellCheckExpect "~$file"
	} else {
	  chan puts $fid "~$file"
	  while {[chanSub] != {} } {}
	}
	set type [file extension $file]
	if {$type == ".tex"} {
	  set ext "tex"
	  # man files have extensions of .[0-9]* or .n or .man
	} elseif {[regexp {((\.[0-9]*)|(\.n)|(\.man))$} $type]} {
	  set ext "nroff"
	} else {
	  set ext ""
	}
	if {$ext != ""} {
	  if {$expect} {
	    spellCheckExpect "+$ext\r" 2
	  } else {
	    chan puts $fid "+$ext"
	    #chan gets $fid foo
	  }
	}
      }
      #      chan gets $fid foo
      return 1
    }
  }
  set NameSpace${w}::w $w
  set NameSpace${w}::fid {}
  set NameSpace${w}::dwp .[regsub -all {\.} $w {}]
  NameSpace${w}::spellSetColors
  bind $w <Destroy> "+spellShutAlldown NameSpace${w}"
}

proc spellShutAlldown {space} {
  # here on window destroy
  catch "${space}::spellShutdown"
  catch "namespace delete $space"
}

