#******************************************************************************
#***
#*** This file is part of XTeXShell; see file xtexsh for details
#*** Version 0.91 (21.2.94)
#***
#******************************************************************************

#******************************************************************************
#*** Execute Programs in a TCL window *****************************************
#******************************************************************************

proc ExecProg {command} {

#***
#*** Execute command. Command may include the symbol :fname, which
#*** will be replaced by the filname of the file loaded in the editor
#*** or, if specified, by the main filename. :fnamewoext will be replaced
#*** by filename without extension.
#*** If fname == "", use mainname or editfname without extension
#*** Before executing, change to directory of fname
#***

       global editfname editwin
       global mainflag mainname
       global readchild readchierr writechild
       global childpid
       global boldl_font
       global chisaveflag chireloadflag
       global critsect

#*** Is there another programm running ?

       if {![info exist critsect]} {set critsect 0}

       if {[childstat] || $critsect} {
               DisplayInfo "Sorry:\nThere is another program running\n\nKill other program first" "$boldl_font"
               return
       }
       set critsect 1

#*** Determine fname as explained above

       set fname ""
       if {[info exists editfname]} {
               set fname $editfname
       }
       if {$mainflag} {set fname $mainname}

#*** Replace macros :save :reload :fname :fnamewoext

       set chisaveflag false
       set chireloadflag false
       set filenameneeded 0

       while {1} {
               set pos [string first ":" "$command"]
               if {$pos==-1} {break}

               set sstr [expr {$pos ? [crange "$command" 0 "$pos-1" ] : ""}]
               set estr [crange "$command" "$pos+1" end]
               set mstr [string tolower [ctoken estr " .,:"]]

               if {[cequal "$mstr" "fnamewoext"]} {
                       set mstr [strip_extension $fname]
                       set filenameneeded 1
               }
               if {[cequal "$mstr" "fname"]} {
                       set mstr $fname
                       set filenameneeded 1
               }
               if {[cequal "$mstr" "save"]} {
                       set mstr ""
                       set chisaveflag true
               }
               if {[cequal "$mstr" "reload"]} {
                       set mstr ""
                       set chireloadflag true
               }
               set command [format "%s%s%s" "$sstr" "$mstr" "$estr"]
       }

#*** Does the command need a file name? If yes, check if we have a valid filename

       if {$filenameneeded && [cequal "" $fname]} {
               if {[info exists editwin] && [winfo exists $editwin]} {
                       DisplayInfo "Please save your file before executing this command.\n I don't know the name of your file yet" $boldl_font
               } else {
                       DisplayInfo "No File has been specified so far.\nCannot execute command!\nPlease load a file in the editor or specify a main file!" $boldl_font
               }
               set critsect 0
               return
       }

#*** Create window if it doesn't exist

       global  exec_win    execwin_name    execwin_geo

       set     exec_win    $execwin_name
       set     c           $execwin_name.c

       if {[cequal "" [CreateTopWin execwin ""]]} {
               wm withdraw  $exec_win
               wm deiconify $exec_win
       } else {
               wm title    $exec_win "$command"
               wm iconname $exec_win "$command"

               frame       $exec_win.but        -relief raised -borderwidth 1 -width 80c -height 10c
               pack        $exec_win.but        -side bottom -fill x

               button      $exec_win.but.kill   -text "Kill Program"  -command {childkill}
               button      $exec_win.but.done   -text "Done"  -command {childkill; destroy $exec_win}
               button      $exec_win.but.clear  -text "Clear" -command {$exec_win.c delete 0.0 end}
               pack        $exec_win.but.kill   $exec_win.but.clear  -side left -anchor s -fill x -padx 3m -pady 1m -ipadx 0.6m
               pack        $exec_win.but.done   -side right -anchor s -fill x -padx 3m -pady 1m -ipadx 0.6m

               scrollbar   $exec_win.ys         -command "$c yview" -relief sunken
               pack        $exec_win.ys         -side right -fill y

               text        $c                   -relief raised -bd 2 -yscrollcommand "$exec_win.ys set"
               pack        $c                   -side left -fill both -expand yes

               $c tag configure error           -foreground red
               $c tag configure message         -foreground blue

               bind        $c <Any-Return>      { %W insert insert "\n"
                                                  puts $writechild ""
                                                  %W yview -pickplace insert
                                                }
               bind        $c <Any-KeyPress>    { if {[childstat]} {
                                                       %W insert insert %A
                                                       puts -nonewline $writechild "%A"
                                                       %W yview -pickplace insert
                                                  }
                                                }
               bind        $c <1>               { focus %W}
               bind        $c <2>               {%W scan mark -%y}
               bind        $c <B2-Motion>       {%W scan dragto -%y}
       }


#*** Save text ?

       global editor
       if {$chisaveflag} {
               if {[cequal "$editor" "xtex"] && [Qmodflag]} {SaveFile ""}
       }

#*** Compose Command, program-name should go to prog, parameters to command

       eval  set command \"$command\"
       set   progname [lvarpop command 0]

       set   childpath [string range $fname 0 [string last "/" $fname]]

#*** Create pipes to enable communication with child

       pipe  readchild   writepar;                     #*** Child --> Parent
       fcntl $readchild  NONBLOCK 1
       fcntl $readchild  NOBUF    1

       pipe  readchierr  writeparerr;                  #*** Child stderr --> Parent
       fcntl $readchierr NONBLOCK 1
       fcntl $readchierr NOBUF    1

       pipe  readpar     writechild;                   #*** Parent --> Child
       fcntl $writechild NOBUF    1

#*** Now fork and start program

       InsertWithTags $c "Executing: $progname $command\n\n" message
       focus  $c
       update

       if {[set childpid [fork]] == 0} {
               dup $readpar stdin;                     #*** Child process, set IO to pipes
               close $readpar
               dup $writepar stdout
               close $writepar
               dup $writeparerr stderr
               close $writeparerr

               pushd $childpath;                       #*** Change to data file directory
               execl $progname $command

               puts stderr "*** Sorry: Programm could not be started"
               kill [id process]
               exit 255
       }

#*** Disable buffering and install driver to update window 5x per second

       set critsect 0
       after  250 {childIO $childpid}
}

proc    childkill {} {

#*** Kill the child process

       global childpid
       if {[childstat]} {
               kill 9 $childpid
       }
}


proc    childstat {} {

#*** Get status of child. (0 = child does not exist)
#*** If child exists: Read stdout and stderr from child and append to the
#*** variables chistdout and chistderr.
#*** Then check status of child with the wait function and write return value
#*** to chistatus. Return 0 if wait returns end of process condition.

       global childpid chireloadflag
       global childpid readchild readchierr writechild
       global exec_win

       if {![info exists childpid]} {set childpid 0; return 0}
       if {!$childpid} {return 0}

#*** Read Standard-Error and Standard-Output from child and write it on exec-window

       set pmode [lindex [select [list $readchierr $readchild] {} {} 0] 0]

       if {[winfo exists $exec_win.c]} {
               if {[string first $readchierr $pmode] >=0 } {
                       InsertWithTags $exec_win.c [read $readchierr] error
                       $exec_win.c yview -pickplace insert
               }
               if {[string first $readchild $pmode] >=0 } {
                       InsertWithTags $exec_win.c [read $readchild] ""
                       $exec_win.c yview -pickplace insert
               }
       }

#*** Check Status of child

       set chistatus [wait -nohang $childpid]

       if {$childpid != [lindex $chistatus 0]} {
               return 1
       } else {

#*** Process has terminated. Tell user

               close $readchild
               close $readchierr
               close $writechild

               if {[cequal "EXIT" [lindex $chistatus 1]]} {
                       set msg [format "\nProgram terminated, ERC: %d" [lindex $chistatus 2]]
               } else {
                       set msg [format "\n\nProgram was killed by signal: %s" [lindex $chistatus 2]]
               }

               if {[winfo exists $exec_win.c]} {
                       InsertWithTags $exec_win.c "$msg\n" message
                       InsertWithTags $exec_win.c "----------------------------------------------------------------------------\n" ""
                       $exec_win.c yview -pickplace insert
               }

               global editor
               if {$chireloadflag} {
                       if {[cequal $editor "xtex"]} { ReLoadFile }
               }

               set childpid 0
       }
}


proc    childIO {reqchipid} {

#*** This process runs in background during execution of a unix command
#*** It calls childstat which updates the exec window and restarts itself
#*** after a delay of 200ms

       global childpid

       if {[childstat]==1 && $reqchipid==$childpid} {
               after 200 childIO $reqchipid
       }
}