# <20240518.1921.55>


# These should use the "quick" option, I think...
proc CmdToright {} {
  global glob
  NewPwd right $glob(left,pwd)
  UpdateWindow_ right 1
}

proc CmdToleft {} {
  global glob
  NewPwd left $glob(right,pwd)
  UpdateWindow_ left 1
}

proc CmdSwapWindows {} {
  global glob
  set tmpright $glob(left,pwd)
  set glob(forceupdate) 1
  CmdToleft
  NewPwd right $tmpright
  UpdateWindow right
  set glob(forceupdate) 0
}

proc BatchReceiveVFS { inst } {
  global glob
  if {[IsVFS $glob($inst,pwd)]} {
    PopInfo [_ "You can only issue a receive request to a non-VFS directory"]
    return
  }
  set olddir $glob([Opposite $inst],pwd)
  foreach itemblock $glob(batchlist) {
    set item [lindex $itemblock 0]
    if {![IsVFS $item]} {
      PopWarn [_ "Can't parse %s as VFS file" $item]
    } else {
      NewPwd [Opposite $inst] $VFStok/
      set r [Try { VFSgetFile $item  \
		       "$glob($inst,pwd)/[file tail $item]" \
		       [lindex $itemblock 1] 0 } "" 1]
    }
  }
  set glob(batchlist) {}
  set glob(forceupdate) 1
  NewPwd [Opposite $inst] $olddir
  UpdateWindow both
  set glob(forceupdate) 0
}

proc SandDsame {} {
  smart_dialog .apop[incr ::uni] . [_ "Oops That doesn't work"] \
	[list [_ "Please set different source and destination directories!"]]\
	0 1 [_ "OK"]
}

proc typeMatch {ttype tfile ftype ffile} {
  set fType [expr {[string index $ftype end] == "d" ? "d" : "f"}]
  if {$fType == $ttype} {
    return 1
  }
  if {$ttype == "f"} {
    set rt "directory:"
    set tt "     file:"
  } else {
    set rt "     file:"
    set tt "directory:"
  }
  # Lets not allow replacing files with dirs or dirs with files
  PopWarn [_ "If target exist it must be the same (file or directory) as current.\
               \n%s %s\
               \n%s %s" $rt $ffile $tt $tfile]
  return 0
}
 

# So here are a few observations:
# 1) fr always does recursive copies, set up the 'cp' command to do this
# 2) some copy code will not copy a dirs contents unless we have a trailing /
# 3) other copy code does not care.
# 4) if the destination ends with a dir the data (files and dirs) go there.
# 5) if the destination ends with an undefined dir, (i.e. on the destination) one is created
# 6) the "file tail" command will eliminate a trailing / and return
#    the resulting tail
# 7) if the destination dir exists, it is used as is, i.e. existing files that
#    are not in the source are preserved.
# 8) For files, the new file replaces the old. If it is desired that this only
#    happen if the file differs, see rsync copy.
# 9) The MSW xcopy wanting to assume "/*" is defeated by appending tail to the
#    destination.
# 10) By definition when a user says copy/move he is refering to the opposite
#     pane. Both 'file copy' and cp, given a source dir and a destination
#     dir do different things depending on that dirs existance cp: (create if not  
#     and make a mirror image, or, if it exists, copy to within). 'file copy'
#     on the other hand refuses to do the copy. This is NOT what we want and thus
#     we have written 'frFileCopy' using 'file copy' and 'glob' to get the
#     desired outcome.
# 11) In addition neither can be forced to delete the collision dir.
#
# So,
# A) always end dir copy sources with /
# B) to preserver the trash/recycle bin delete files using delete code,
#    not cp code.
# C) always append source "tail" to the destination. This may be altered by
#    move/copy as code (which is required for local copies depending on
#    existance of that dir on local dir to dir copies.
# D) VFS put/get may also need work in this regard.

# CopyCore
# fromList  a list of files to copy each entry-> {file type size}
# topwd     the directory to copy them to
# inst      the pane (left or right)
# as        1 if we are to ask for new name(s)
# resume    1 if possible restart, else 0
# option    "cp" for copy "hardlink" for hard linking instead

proc CopyCore {fromList topwd inst {as 0} resume option} {
  global config glob
  # This is intended to be the core copy code. If "as" is true
  # it asks for a destination. It does all the permission tests
  # if overlaying files and permission has been requested.
  # The fromlist should be a list of lists (a sublist for each file)
  # The first entry should be the file name, the second its type &
  # The third entry its size (used with VFS)
  # Other code should assemble the fromlist.
  # todir is a dir
  # inst should be the 'from' panel as we may need to modify the 'to' panel

  # first take care of the "as"
  set copyList {}
  foreach ent $fromList {
    lassign $ent file type size
    if {$type == "fl"} {
      # size unknown on VFS links
      set size -1
    }
    set ::ssdTmp [URL norm $topwd/[file tail $file]]
    set typeD [expr {[string index $type end] == "d"}]
    if {$as} {
      set query [expr {$typeD ? [_ "directory"] : [_ "file"]}]
      
      set r [smart_dialog .copy[incr ::uni] "." [_  "Copy As..."] \
		 [list [_  "Please enter new name for destination %s" $query]] \
		 0 4 \
		 [list \
		      [list {} [list -textvariable ::ssdTmp -width 70]]\
		      [_ OK] [_ "Skip This File"] [_ "Cancel all copies"]]\
		 [buildDialogConfig]
	    ]
      # 0,1 use result 2 skip this file -1,3 cancel the whole thing.
      if {$r == -1 || $r == 3} {return}
      if {$r == 2} {continue}
      # Make sure it is not a copy into itself..
    }
    if {$file == $::ssdTmp} {
      SandDsame
      return 0
    }
    # Or a recursive self copy...
    if {$typeD && [string first $file/ $::ssdTmp] == 0} {
      PopError [_ "Can't %s recursively into same directory (%s -> %s)"\
		    $option $file $::ssdTmp]
      return 0
    }
    set tType [fileType $::ssdTmp]
    # tType may have any of three values d=>directory f=>file 0=>does not exist
    if {$tType != 0 && ![typeMatch $tType $::ssdTmp $type $file]} {
      return
    }
    # pass the tType here as it indicates existance
    lappend copyList [list $file $typeD $size $::ssdTmp $tType]
  }
  # frputs copyList fromList
  # now we need to check permissions...
  # do we need to??
  # We don't delete on copy of dirs...
  # set dirA $config(ask,dir_delete)
  set fileA $config(ask,file_delete)
  # Build a list of asks
  set testThese {}
  set doThese {}
  set dirCount 0
  set fileCount 0
  foreach ent $copyList {
    lassign $ent file typeD size dest dType
    #set this [string index $type end]
    frputs file typeD size dest dType
    if {!$typeD && $fileA && $dType != 0} {
      frputs ent
      lappend testThese $ent
      incr fileCount
    } else {
      lappend doThese $ent
    }
  }
  # At this point we have two lists:
  # doThese, the files to copy unconditionally and
  # testThese, the files for which we need permission to copy
  
  # This keeps track of global yes/no from the ok to delete query
  # 3 global yes
  # 4 global no
  # Other vaues -> unknown

  # deleteQuery returns as follows:
  # -1 s/he Xed out the window (taken to mean cancel all copies)
  # 0 yes to this file/dir
  # 1 no  to this file/dir
  # 2 same a -1, cancel all copies..
  # 3 yes to all files or dirs (depending on if a file or dir question)
  # 4 no  to all files or dirs Cancels copy of of files or dirs in this list
  
  set globalYNfile 0
  set globalYNdir 0
  set ty "file"
  set query [_ "file"]
  
  foreach ent $testThese {
    # we only test files, not dirs...
    lassign $ent file typeD size dest dType
    # frputs file type size dest globalYNfile globalYNdir
    switch -exact [set globalYN$ty] {
      4 {continue}
      0 -
      1 {set globalYN$ty [deleteQuery $query $dest  $fileCount [_ "Copy"]]}
    }
    switch -exact [set globalYN$ty] {
      1 -
      4 {continue}
      2 {return 0}
    }
    # Here we have the ok, lets do it.
    lappend doThese $ent
  }
  # Here is where we can go async. All we do is to pass the list
  # and option to an action function, either just a call (not async)
  # or via "after" for async.

  # if {$glob(async)} {
  #   after 1 [list copyAction $doThese $resume $option]
  #   return 1
  # } else {
  #   return [copyAction $doThese $resume $option]
  # }
  Try {copyAction $doThese $resume $option} "" 1 $glob(async)
}

proc copyAction {doThese resume option} {
  global glob config
  
    
  # All tests are done. All that is left is to do the copy
  # We divide this up in ways that make it easy...
  # further from is either a file or a dir
  # There are these copies:
  # 0 & 1 from & to local file system (even is file, odd dir)
  # 2 & 3 from local to VFS
  # 4 & 5 from VFS to local
  # 6 & 7 from VFS to VFS (either within the same VFS or not)
  # all that sounds like a switch is in order
  
  # set olddir $glob($inst,pwd)
  foreach ent $doThese {
    lassign $ent ffile typeD size dest tType
    if {[CheckAbort [_ "Copy"]]} {
      UpdateIf $dest $dest/x
      return 0
    }
    set sw [expr {($typeD) +\
		      2 * [IsVFS $dest] + \
		      4 * [IsVFS $ffile] }]
    if {$option == "hardlink"} {
      if {$sw > 1 && $sw < 6 ||\
	      $sw > 5 && [string first $VFStok $dest] != 0} {
	PopWarn [_ "Hard link not possible across file\
                     systems\n%s to\n%s" $ffile $dest]
	return 0
      } elseif {$sw < 2} {
	# here we run down links (i.e. we always dereference on copy)
	set ffile [file dir [file norm $ffile/x]]
      }
    }
    if {$typeD} {
      set ffile $ffile/
    }
    if {$tType == "f"} {
      # We are either async or not, don't let it go another level...
      deleteFile $dest $tType
    }
    frputs sw ffile dest
    switch -exact $sw {
      0 {
	# This is the file copy or hardlink
	# configure the command...
	set cpcmd $config(cmd,$option)
	#set to $topwd
	if {$cpcmd == {}} {
	  if {$option == "cp"} {
	    set cpcmd "frFileCopy"
	  } else {
	    set cpcmd "file link -hard %2@s"
	    set to $tof
	  }
	} else {
	  set cpcmd [list exec {*}[subst {*}$::stOps $cpcmd]]
	  # if {$glob(async)} {
	  #   append cpcmd " %b &"
	  # }
	}
	# frputs "pre frECF:  " ffile topwd
	frECF [list {*}$cpcmd]\
	    [list $ffile $dest]
	# [list -b $glob(async)]
	
      }
      1 {
	# This is local to local dir copy
	Log [_ "%s (d) %s >> %s" $option $ffile $dest]
	
	set cpcmd $config(cmd,$option)
	if {$option == "cp"} {
	  lassign $config(cmd,dircp) cpcmd dirFlag
	  
	  # Most (all?) of the copy commands in windows blightly assume
	  # that you really ment to put "\*" on the end of source directory
	  # If the user would really like to back that assumption out
	  # we allow him to code an option in the config(cmd,dircp)
	  # (second entry in the list) to tell us what s/he really
	  # wants. To work this wonder we just add the final dir in
	  # the source to the destination...
	  # Uh. that is the default with the new copy code so
	  # negate the test...
	  if {![string match {*-\**} $dirFlag]} {
	    # oops! that must be part of the command
	    set cpcmd [list {*}$cpcmd {*}$dirFlag]
	    # To restore the 'normal' windows type behavior..
	    # only if the tail is the same, else it is a copy 'as'
	    if {[file tail $ffile] == [file tail $dest]} {
	      set dest [file norm $dest/..]
	    }
	  }
	  if {$cpcmd == {}} {
	    if {$option == "cp"} {
	      set cpcmd "frFileCopy"
	    } else {		
	      set cpcmd  "recHardLink %b -L"
	    }
	  } else {
	    set cpcmd [list exec {*}[subst {*}$::stOps $cpcmd]]
	    # if {$glob(async)} {
	    #   append cpcmd " %b &"
	    # }
	  }
	}
	frputs ffile dest
	frECF [list {*}$cpcmd]\
	    [list $ffile $dest]
	# [list -b $glob(async)]
      }
      2 {
	# This is local to VFS file copy
	Try {VFSputFile $dest $ffile $size } "" 1
      }
      3 {
	# This is local to VFS dir copy
	Try { CopyToVFSRecursive $ffile $dest} "" 1
      }
      4 {
	# This VFS to local file copy
	frECF [list VFSgetFile] \
	    [list $ffile\
		 $dest \
		 $size \
		 $resume ]\
	    [list -default { "%n"}]
	#	 -back $glob(async)]
      }
      5 {
	# This is VFS to local dir copy
	Try {CopyFromVFSRecursive "$ffile" "$dest" $resume } "" 1
	# NewPwd $inst $olddir
      }
      7 -
      6 {
	# This is VFS to VFS file copy
	if {$option != "cp"} {
	  CantDoThat
	}
	# If the two VFS's are the same, try the internal copy
	set VFSsourceTok $VFStok
	IsVFS $dest
	set eh {}
	if {$VFSsourceTok == $VFStok && \
		[catch {VFScopy $VFStok $ffile $dest} eh] == 0} {
	  continue
	  # frputs eh
	  if {![string match {*does not support*} $eh] && $eh != {}} {
	    PopError $eh
	    continue
	  }
	}
	# so that failed, try this
	if {$sw == 6} {
	  # Its a file...
	  frputs COPY ffile dest
	  if {$tType == "fl"} {set size -1}
	  Try {VFSputFile $dest [set tmp [MoveToTmp $ffile\
					      type $tType size $size]] $size} "" 1
	  #file delete -force $tmp
	  frputs tmp
	} else {
	  # A dir copy
	  set tail [file tail $dest]
	  if {![Try {CopyFromVFSRecursive $ffile [makeTmp]/$tail $resume} "" 1]} {
	    Try {CopyToVFSRecursive [set tmp [makeTmp]/$tail/] $dest} "" 1
	    file delete -force $tmp
	  }
	}
      }
    }
  }
  UpdateIf $dest $dest/x
  return 1
}

# # This function tests for existance of the file. This is simple for
# # local files, but a bit complicated for VFS files. We use 'inst' to
# # select the opposite in case we need to set up a new list.
# #
# proc fileExists {file inst} {
#   global glob
#   # do the simple stuff first...
#   if {![IsVFS $file]} {
#     return [file exists $file]
#   }
#   # Lets assume the most difficult...
#   # find the dir it is listed in..
#   set dir [URL norm $file/..]
#   set pan [Opposite $inst]
#   set savedPwd $glob($pan,pwd)
#   # short cut possible?
#   if {$dir != $savedPwd} {
#     NewPwd $pan $dir
#     UpdateWindow $pan
#   }
#   set ret [lsearch -exact -index 1  $glob($pan,filelist) [file tail $VFSpath]]
#   if {$glob($pan,pwd) != $savedPwd} {
#     NewPwd $pan $savedPwd
#     UpdateWindow $pan
#   }
#   return [expr {$ret == -1 ? 0 : 1}]
# }
# old args for CmdCopy {resume 0} {cmd cp} {as 0}
proc CmdCopy {args} {
  array set opsAry [list cmd cp resume 0 as 0 async 0]
  array set opsAry $args
  if {$opsAry(async)} {
    set glob(async) "-a"
  }
  global glob
  CmdCopy_ $glob(listbox,$glob(selected)).file glob($glob(selected),filelist) \
      $glob($glob(selected),pwd) $glob(selected)\
      $opsAry(as) $opsAry(resume) $opsAry(cmd)
  # frputs 
  ForceUpdate both
}
proc CmdHardlnk {{resume 0}} {
  CmdCopy resume $resume cmd hardlink
}

proc CmdHardlnkAs {} {
  CmdCopy cmd hardlink as 1
}

proc CmdCopyAs {} {
  CmdCopy cmd cp as 1
}

proc CmdCopy_ { listb_name filelist_var frompwd  inst as resume option} {
  global config glob
  upvar $filelist_var filelist
  set selList [$listb_name curselection]
  set fileList {}
  foreach sel $selList {
    lassign [lindex $filelist $sel] s file type size
    set ffile  [DNtoDirTail [URL norm $frompwd/$file]]
    lappend fileList [list $ffile $type $size]
  }
  CopyCore $fileList $glob([Opposite $inst],pwd) $inst $as $resume $option
}


proc SoftLink { src dst } {
  global config
  if {$config(create_relative_links)} {
    set srcdir [file dirname $src]
    set dstdir [file dirname $dst]
    set srcfile [file tail $src]
    set dstfile [file tail $dst]
    set dstlist [split $dstdir /]
    set srclist [split $srcdir /]
    set dstlen [llength $dstlist]
    set srclen [llength $srclist]
    # Count how many directories are the same in the source and destination paths
    set index 0
    while {([lindex $srclist $index] == [lindex $dstlist $index]) && \
	       ($index < $srclen) && ($index < $dstlen)} {
      incr index
    }
    # Build relative link
    set link {}
    for {set dstindex $index} {$dstindex < $dstlen} {incr dstindex} {
      append link ../
    }
    for {set srcindex $index} {$srcindex < $srclen} {incr srcindex} {
      append link [lindex $srclist $srcindex]/
    }
    set from $link$srcfile
    cd $dstdir
  } else {
    set from $src
  }
  # Log [_ "%s : file -s %s" \
  # 	   "$src $dst $srcdir $srcfile $dstdir $dstfile" "$dst $from"]
  # for MSW the tcl file command seems to put garbage in the link...
  # We create junctions as we know how to delete them...
  # Tcl bug c8d4f01a54d7ecf819f4881fb02467ad14fc7b0c
  set cmd "file link -symbolic"
  if {$::MSW && [set nc [auto_execok mklink]] !={} } {
    set cmd "exec $nc /j "
  }
  frECF $cmd [list $dst $from]
  # if {[Try {{*}$cmd  $dst $from} "" 0] == 1 } {
  #   Try {file link  $dst $from} "" 1
  # }
}

proc CmdSoftLink {} {
  global glob
  if { $glob(left,pwd) == $glob(right,pwd) } {
    SandDsame
    return {}
  }    
  # Well, with VFS we actually can, but we need them both to be
  # on the same remote 
  if {([IsVFS $glob(left,pwd)] && [set lVFStok $VFStok] != {}) ||\
	  [IsVFS $glob(right,pwd)]} {
    if {$lVFStok == $VFStok} {
    # missing code here...
    # It is OK, it is handled below..
    } else {
      frputs "softlink " l r rVFStok lVFStok  glob(left,pwd) glob(right,pwd)
      CantDoThat
      return
    }
  }

  CmdSoftLink_ $glob(listbox,left).file glob(left,filelist) $glob(left,pwd) $glob(right,pwd)
  CmdSoftLink_ $glob(listbox,right).file glob(right,filelist) $glob(right,pwd) $glob(left,pwd)
  UpdateWindow both
}

proc CmdSoftLink_ { listb_name filelist_var frompwd topwd } {
  # For speed, (hopefully) we'll call by reference...
  global config glob
  upvar $filelist_var filelist

  foreach sel [$listb_name curselection] {
    if {[CheckAbort [_ "SoftLink"]]} {return}
    set elem [lindex $filelist $sel]
    set file [lindex $elem 1]
    set realName [DNtoDirTail [URL norm $frompwd/$file]]
    switch [lindex $elem 2] {
      n   -
      d   -
      l   -
      wl  -
      wld -
      ld  { 
        Log [_ "Softlinking %s to %s" "$realName" $topwd]
        SoftLink $realName $topwd/$file
      }
      fn -
      fd -
      fl -
      fld {
	Log [_ "Softlinking %s to %s" "$realName" $topwd]
	IsVFS $topwd/$file
	set opt [expr {$config(create_relative_links) ? "r" : "a"}]
	Try {VFSlink $realName $VFSpath $opt} "" 1
	set glob(forceupdate) 1
      }
      default {
	CantDoThat
	return
      }
    }
  }
}

proc CmdSoftLinkAs {} {
  global glob
  CmdSoftLinkAs_ $glob(listbox,left).file glob(left,filelist) $glob(left,pwd) $glob(right,pwd)
  CmdSoftLinkAs_ $glob(listbox,right).file glob(right,filelist) $glob(right,pwd) $glob(left,pwd)
  UpdateWindow both
}

proc CmdSoftLinkAs_ { listb_name filelist_var frompwd topwd } {
  # For speed, (hopefully) we'll call by reference...
  global config glob
  upvar $filelist_var filelist

  foreach sel [$listb_name curselection] {
    if {[CheckAbort [_ "SoftLinkAs"]]} {return}
    set elem [lindex $filelist $sel]
    set file [lindex $elem 1]
    set realName [DNtoDirTail [URL norm $frompwd/$file]]
    set destfile [simple_smart_dialog "." [_ "Soft-Link As..."] \
		      [_ "Please enter new name for destination link"] \
		      $topwd/$file ]
    if {([IsVFS $frompwd] && [set fVFStok $VFStok] !={}) || [IsVFS $destfile]} {
      # Well, with SVFS we actually can, but we need them both to be
      # on the same remote & SVFS (but I said that)
      if {$fVFStok == $VFStok } {
      } else {
	CantDoThat
	return
      }
    }
    if {$destfile != "" } {
      Log [_ "Softlinking %s to %s" "$realName" $destfile]
      switch [lindex $elem 2] {
	n   -
	d   -
	l   -
	wl  -
	wld -
	ld  {
          SoftLink $realName $destfile
        }
	fn -
	fd -
	fl -
	fld {
	  Log [_ "Softlinking %s to %s" "$realName" $destfile]
	  # This sets up VFSadd to be VFSadd
	  IsVFS $destfile
	  set opt [expr {$config(create_relative_links) ? "r" : "a"}]
	  Try {VFSlink $realName \
		   "$VFSpath" \
		   "$opt"} "" 1
	  set glob(forceupdate) 1
	}
	default {CantDoThat}
      }
    }
  }
}

# This handles all deletes. For locals we use the configured delete
# We do the async option with override on the third parm


proc deleteFile {args} {
  global config glob
  # if we are passed a list, the args processing will have listed it again
  # so, if the length is 1, that sublist is what we want
  if {[llength $args] == 1} {
    set args [lindex $args 0]
  }
  set errs {}
  foreach {newfile tType} $args {
    frputs  tType newfile args
    # tType should be f for files d for dirs
    switch -exact -- $tType[IsVFS $newfile] {
      f0 -
      d0 {
	if {![file writable $newfile]} {
	  append errs "\n\"$newfile\" permission error."
	  continue
	}
	if {[catch {expr {[file exists $newfile] || \
			      [file type $newfile] == "link"}} r] == 0 && $r} {
	  if {$config(cmd,rm) != {}} {
	    # assume a recycle thing
	    # We allow internal and external commands
	    if {[info commands [lindex $config(cmd,rm) 0]] == {}} {
	      set cmd [list "exec" {*}$config(cmd,rm)]
	    } else {
	      set cmd $config(cmd,rm)
	    }
	  } else {
	    set cmd {file delete -force --}
	  } 
	} else {
	  # we seem to have been called with a non-existant file...???
	  append errs "\n\"$newfile\" appears not to exist."
	  continue
	}
	Log [_ "Using \"%s\" to delete %s" $cmd $newfile ]
	if {[Try {{*}$cmd $newfile} -q]} {
	  if {$::MSW} {
	    if {[catch {::twapi::recycle_file $newfile -showerror 1} er] != 0} {
	      lappend errs "\n\"$newfile\" $er"
	    }
	    continue
	  } else {
	    append errs "\n\"$newfile\" failed, open or in use?"
	  }
	}
      }
      f1 {
	Try { VFSdelete $newfile} "-sPossibly open file:"
      }
      d1 {
	Try { DeleteVFSRecursive $newfile} "-sPossibly open file:" 
      }
      00  -
      01  {return}
      default {
	return -code error "deleteFile called with $tType not in {0 d f}"
      }
    }
    if {$errs != {}} {
      PopError "File delete errors:$errs"
    }
  }
}

# Here we implement a cache of filelists indexed by dir
#
proc getFileListFor {dir} {
  upvar #0 glob(left,pwd)  lpwd
  upvar #0 glob(right,pwd) rpwd
  global glob
  global fileListCache fileListExpir

  if {$dir == $lpwd} {
    return $glob(left,filelist)
  } elseif {$dir == $rpwd} {
    return $glob(right,filelist)
  } elseif {[info exists fileListCache($dir)] &&\
		$fileListCache($dir,time) > [clock seconds] + 10} {
    return $fileListCache($dir)
  } else {
    catch {after cancel $fileListExpir}
    set fileListCache($dir,time) [clock seconds]
    set fileListExpir [after 10000 {array unset ::fileListCache}]
    set r [catch {adHockGetDirList $dir} list]
    if {$r == 0} {
      return [set fileListCache($dir) $list]
    }
    return {}
  }
}
#
# This routine attempts to determine if the given file exists
# and if so, its type. Returns are:
# "d" exists and is directory
# "f" exists and is file
# "0" does not exist

proc fileType {file} {
  global glob
  upvar #0 glob(left,pwd)  lpwd
  upvar #0 glob(right,pwd) rpwd
   
  if {![IsVFS $file]} {
    set dir [file dir $file]
    if {[file isdir  $file]} {return "d"}
    if {[file isfile $file]} {return "f"}
    return 0
  }
  
  set dir [URL dir $file]
  set sObj [dirToDNexact $file]
  set sObj [expr {$sObj == {} ? [file tail $file] : $sObj}]
    
  set ent [lsearch -exact -inline -index 1 [getFileListFor $dir] $sObj]
  if {$ent == {}} {return 0}
  return [expr {[string index [lindex $ent 2] end] == "d" ? "d" : "f"}]
}

# Move and rename are, at heart, the same thing. Especially if we
# bring moveas into the mix. Here are the three cases:
#
# 1.) move   just a simple move, target must differ from source
#            (seed will be Move)
# 2.) moveas the same as rename except for the seed in the prompt
# 3.) rename just a different seed in the prompt
#

proc CmdMove {{seed {Move}}} {
  global config glob
  upvar #0 glob(selected)             inst
  upvar #0 glob(listbox,$inst)        listb_name
  upvar #0 glob($inst,filelist)       filelist 
  upvar #0 glob($inst,pwd)            frompwd
  upvar #0 glob([Opposite $inst],pwd) topwd

  set selList [$listb_name.file curselection]
  # This keeps track of global yes/no from the ok to delete query
  # Move does not delete dirs, but it may delete files...
  # 2 global yes
  # 3 global no
  # Other vaues -> unknown
  set globalYNfile 0
  set globalYNdir 0

  # A note on async. We need to grab all the selected file names
  # before we return. For multi-step operations, we need to
  # serialize the steps...
  set moveFiles {}
  set UpD {}
  foreach sel  $selList {
    if {[CheckAbort [_ "Move"]]} {return}
    set elem [lindex $filelist $sel]
    lassign $elem duh file type size
    set realName   [DNtoDirTail [URL norm $frompwd/$file]]
    set toRealName [DNtoDirTail [URL norm $topwd/$file]]

    set tof $topwd/$file
    if {$seed != "Move"} {
      # a rename or move to a new name (moveas)
      
      set newname [simple_smart_dialog "." \
		       [_ "%s" $seed] \
		       [_ "Please enter new name."] \
		       [expr {$seed == "Rename" ? $realName : $toRealName}]]
      if {$newname == ""} {continue}
    } else {
      set newname $toRealName
    }
    # The rules, what is and what is not ok.
    # Rename or Move to the same name is NOT ok.
    #     this may happen even if its a simple move.
    # Then there is the MSW alias issue (i.e. same except for case).
    # We want to allow this to effectively change the case of a name.
    if {$newname == $realName} {
      SandDsame
      return
    }
    set tType [fileType $newname]
    frputs tType newname
    if {$tType == "f" &&\
	    (!$::MSW || ![string equal -nocase $newname $realName])} {
      # We have an implied delete
      # set up the permission code...
      set dT {}
      set ty "file"
      set ford [expr {$config(ask,file_delete) ? [_ "file"] : {}}]
      # frputs tType type
      if {![typeMatch $tType $newname $type $realName]} {
	return
      }
    }
    # We don't check ownership on VFS files. (We don't usually know
    # who the owner is in this case.) Of course CheckWhoOwns doesn't
    # know how to approach VFS (yet??)
    
    if {([IsVFS $realName] || [CheckWhoOwns $realName $seed]) && \
	    ($tType == "d" || \
		 [IsVFS $newname] ||\
		 [CheckWhoOwns $newname overwrite])} {
      
      # This keeps track of global yes/no from the ok to delete query
      # 2 global yes
      # 3 global no
      # Other vaues -> unknown
      # deleteQuery returns 0 -> Yes, 1 -> No, 2-> abort,
      #                     3 ->Yes to all, 4 ->No to all
      # Only ask about existent files
      # Since we are doing this one at a time and we don't know if there
      # is more than one existant file, the length of selList is a conserative
      # proxy for the number of files (i.e. always more than or =)
      if {$tType == "f"} {
	# We have an implied delete
	# set up the permission code...
	set dT {}
	set ty "file"
	set ford [expr {$config(ask,file_delete) ? [_ "file"] : {}}]
	switch -exact [set globalYN$ty] {
	  4 {continue}
	  0 -
	  1 {set globalYN$ty [deleteQuery $ford $newname [llength $selList] [_ $seed]]}
	}
	switch -exact [set globalYN$ty] {
	  1 -
	  4 {continue}
	  2 {return 0}
	}
      }

    } else {
      continue
    }
      
    
    # Ok, things are looking serious. We have a few issues left
    # 1) If we are doing a moveas and it is a directory, well
    #    there is no command to do that. For the local case
    #    rename does the trick.
    #    For all others we need to do a copyas followed by a
    #    delete.
 

    # Here is where we start doing things.... first the locals
    lappend UpD $realName $realName/x $newname $newname/x
    if {![IsVFS $newname] && ![IsVFS $realName]} {
      
      # If we have an implied delete, make it explicit
      # and use the configured rm command
      if {$tType == "f"} {
	if {$::MSW && [string equal -nocase $newname $realName]} {
	  # Its just a case change. file rename does not really
	  # know how to do this, so...
	  set tname [::fileutil::tempfile [file dirname $newname]/]
	  file rename -force -- $realName $tname
	  set realName $tname
	} else {
	  deleteFile $newname $tType
	}
      }
      Log [_ "Moving %s to %s" $realName $newname]
      # Note in passing, "file rename" moves the file if
      # required (man page says a different directory, hope
      # it means a different volume)
      Try { file rename -force -- $realName $newname }
      continue
    }
    # Its remote (on one or both sides)
    # collect what we need so we can do the whole thing as one big async
    #
    lappend moveFiles [list $realName $newname $type $tType $size]
  }
  # At this time we have all the info we need in the moveFiles list
  # lets do this either async or not as requested.
  if {$moveFiles != {}} {
    Try {moveDoIt $moveFiles} $glob(async)
  }
    UpdateIf {*}$UpD
}

proc moveDoIt {moveFiles } {
  foreach ent $moveFiles {
    frputs ent moveFiles
    if {[CheckAbort [_ "Move"]]} {break}
    lassign $ent realName newname type tType size
    lappend UpL $realName $realName/x $newname $newname/x
    
    if {[set rf [IsVFS $realName]]} {
      set VFStokf $VFStok
    }
    if {[IsVFS $newname] &&\
	    $rf &&\
	    $VFStokf == $VFStok && \
	    [VFSsupports $newname "rename"]} {
      #  both VFS... and the same and rename supported...
      frputs realName VFSpath newname
      Try { VFSrename $realName $VFSpath\
		[expr {[string index $type end] == "d"}]} "" 1
      continue
    }
    # Ok rename is out, we need to do a move and a delete.
    # one or both VFS but if both, not the same (or rename not supported)
    # six cases:
    # VFS to VFS file        VFStoVFSf
    # VFS to VFS directory   VFStoVFSd
    # local to VFS file      loctoVFSf
    # local to VFS directory loctoVFSd
    # VFS to local file      VFStolocf
    # VFS to local directory VFStolocd
    #
    # Also, if dest exists we need to deleteit

    if {$tType == "f"} {
      deleteFile $newname $tType
    }
    set resume 0
    set sType [expr {[string index $type end] == "d" ? "d" : "f"}]
    set whichCase [expr {$rf ? "VFS" : "loc"}]to[expr {[IsVFS $newname] ? \
							   "VFS" : "loc"}]$sType
    set tmpLoc {}
    Log [_ "Move %s to %s" $realName $newname]

    switch -exact $whichCase {
    
      VFStoVFSf {
	# VFS to VFS, copy to local then to dest.
	# This VFS to local file copy
	if {[set tmpLoc [MoveToTmp $realName type $type size $size]] == 0} {
	  incr er
	} elseif {[Try {VFSputFile $newname $tmpLoc $size } "" 1] != 0} {
	  incr er
	}
      }
      VFStoVFSd {
	set tmpLoc [makeTmp]/[file tail $realName]
	if {[Try {CopyFromVFSRecursive "$realName" "$tmpLoc" $resume }] != 0} {
	  Try {file delete -force -- $tmpLoc} "" 1
	} elseif {[Try {CopyToVFSRecursive $tmpLoc $newname}] != 0} {
	  incr er
	}
      }
      loctoVFSf {
	if {[Try {VFSputFile $newname $realName $size }] != 0} {
	  incr er
	}
      }
      loctoVFSd {
	if {[Try {CopyToVFSRecursive $realName $newname}] != 0} {
	  incr er
	}
      }
      VFStolocf {
	# This VFS to local file copy
	if {$type == "fl"} {set size -1}
	if {[Try {frECF [list VFSgetFile] \
		      [list $realName\
			   $newname \
			   $size \
			   $resume]\
		    } "" 1] != 0} {
	  incr er
	}
      }
      VFStolocd	{
	# This is VFS to local dir copy
	if {[Try {CopyFromVFSRecursive "$realName" "$newname" $resume}] != 0} {
	  incr er
	}
      }
      default {return -code error "Impossible file move $whichCase"}
    }
    # only do the delete if no er
    if {![info exists er]} {
      Log [_ "Deleting %s" $realName]
      if {[IsVFS $realName]} {
	if {$sType == "d"} {
	  Try {DeleteVFSRecursive $realName}
	} else {
	  Try {VFSdelete $realName} "" 1
	}
      } else {
	Try {file delete -force -- $realName}
      }
    }
    if {$tmpLoc != {}} {
      Try {file delete -force -- $tmpLoc}
    }
  }
  UpdateIf {*}$UpL
}	


proc CmdMoveAs {} {
  CmdMove 1
}



proc CmdDelete {} {
  global glob
  CmdDelete_ $glob(listbox,left).file glob(left,filelist) $glob(left,pwd) $glob(right,pwd) left
  CmdDelete_ $glob(listbox,right).file glob(right,filelist) $glob(right,pwd) $glob(left,pwd) right
  UpdateWindow both
  set glob(forceupdate) 0
}
# deleteQuery asks if it is ok to delete something. It puts up a window
# with the message: "OK to delete 'ford' 'elem'" and button answers:
# 'Yes' 0, 'No' 1, Abort 'op' 2, Yes to All 3, No to All 4.
# The last two buttons will be displayed only if 'num' is greater than 1
# 'ford' was set up to read directory or file. X deletion will return 2.

proc deleteQuery {ford elem num op} {
  if {$ford == ""} {return 0}
  set ret [smart_dialog .apop[incr ::uni] . [_ "Sure?"] \
	      [list [_ "OK to delete %s ?" \
			 "$ford $elem"]]\
	      0 [expr {$num > 1 ? 5 : 3}] \
	      [list [_ "Yes"] [_ "No"] [_ "Abort %s" $op] \
		   [_ "Yes to All"] [_ "No to All"]]]
  if {$ret == -1} {return 2}
  return $ret
}

proc CmdDelete_ { listb_name filelist_var frompwd topwd inst } {
  # For speed, (hopefully) we'll call by reference...
  global config glob
  upvar $filelist_var filelist

  set selList [$listb_name curselection]
  # This keeps track of global yes/no from the ok to delete query
  # 3 global yes
  # 4 global no
  # Other vaues -> unknown (0 yes, 1 no)
  # we ask seperatly for files and dirs.
  set dglbYN 0
  set glbYN 0
  # We want to allow async, but need to pull all the info from the
  # dirlist before we do, thus we build a work list and then do
  # the work with Try, possibly asynchronously
  set delList {}
  foreach sel $selList {
    if {[CheckAbort [_ "Delete"]]} return
    set elem [lindex $filelist $sel]
    set type [lindex $elem 2]
    set ford [switch $type {
      d -     
      fd {if {$config(ask,dir_delete)} {expr {{directory tree}}}} 
      default {if {$config(ask,file_delete)} {expr {{file}}}}
    }]
    set dT [expr {$ford != "file" ? {d} :{}}]

    set ff [DNtoDirTail [URL norm $frompwd/[lindex $elem 1]]]
    switch -exact [set ${dT}glbYN] {
      4  {continue}
      0 -
      1 {set ${dT}glbYN [deleteQuery $ford $ff [llength $selList] [_ "Delete"]]}
    }
    switch -exact [set ${dT}glbYN] {
      1 -
      4 {continue}
      2 {return}
    }
    set tType [expr {[string index $type end] == "d" ? "d" : "f"}]
    switch $type {
      l   -
      wl  -
      ld  -
      wld -
       n   -
      d { 
	if {[CheckWhoOwns $ff delete]} {
	  lappend delList $ff $tType
	}
      }
      fwl -
      fwld -
      fn  -
      fld -
      fd  -
      fl  {
	Log [_ "Deleting %s" $ff]
	lappend delList  $ff $tType
      }
      default CantDoThat
    }
  }
  if {$delList == {}} {return}
  Try {
    deleteFile $delList {;}
    UpdateIf $ff
  } "-sPossibly open file:" $::glob(async)
}
# This command is nearly the same as View  It uses the windows
# file 'assoc' and 'ftype' functions to figure out how
# to 'open' a file.  (We use 'open' as windows means it.)


proc CmdOpen {} {
#  puts "CmdWinOpen"
  global glob
  CmdView_ $glob(listbox,left).file glob(left,filelist) \
      $glob(left,pwd) $glob(right,pwd) left open
  CmdView_ $glob(listbox,right).file glob(right,filelist) \
      $glob(right,pwd) $glob(left,pwd) right open
}

# This command allows the help file to be compressed, .gz now, .zip someday.

proc ViewHelp {seed} {
  frputs "ViewHelp  " seed
  if {[file exists $seed]} {
    ViewText $seed
  } elseif {[file exists $seed.gz]} {
    ViewText $seed.gz
  } else {
    PopError "Can't find $seed"
  }
}

proc CmdView {} {
#  puts "CmdView"
  global glob
  CmdView_ $glob(listbox,left).file glob(left,filelist) \
      $glob(left,pwd) $glob(right,pwd) left
  CmdView_ $glob(listbox,right).file glob(right,filelist) \
      $glob(right,pwd) $glob(left,pwd) right
}

proc CmdView_ { listb_name filelist_var frompwd topwd inst {extList view}} {
  # For speed, (hopefully) we'll call by reference...
  #  puts "CmdView_"
  global glob config env
  upvar $filelist_var filelist
  set filenamelist {}
  set filenameorg {}
  foreach sel [$listb_name curselection] {
    if {[CheckAbort [_ "View"]]} return
    set fileelem [lindex $filelist $sel]
    set file1 [DNtoDirTail [URL join $frompwd [lindex $fileelem 1]]]
    frputs file1 frompwd
    # for windows lnk files we pass both the translated version and the file
    set type [lindex $fileelem 2]
    switch  $type {
      l   -
      n   -
      fn  -
      fl  { 
	lappend filenamelist $file1
	lappend filenameorg  $file1
      }
      wl  { 
	lappend filenamelist [TranslateLnk [wLinkName $inst $fileelem] \
				      [lindex $glob($inst,df) 1]]
	lappend filenameorg $file1
      }
      wld {
	set newdir [TranslateLnk [wLinkName $inst $fileelem] \
			[lindex $glob($inst,df) 1]]
	if {$newdir != {}} {
	  NewPwd $inst $newdir
	  UpdateWindow $inst
	}
      }
      fd  -
      fld -	
      ld  - 
      d   {
	if {$config(cd_pwd) && $type in {fld ld}} {
	  set file1 [DNtoDirTail [URL join $frompwd [lindex $fileelem 7]]]
	}
	NewPwd $inst $file1
	UpdateWindow $inst
	return
      }
      fwl  -
      fwld {
	# We do these only if we have already figured the link in some way
	if {[set lnk [wLinkName $inst $fileelem]] == {}} {
	  CantDoThat
	  return
	}
	set file [DNtoDirTail [URL join $frompwd $lnk]]
	if {$type == "fwld"} {
	  NewPwd $inst $file
	  UpdateWindow $inst
	  return
	}
	lappend filenamelist $file
	lappend filenameorg  $file1
      }
      default CantDoThat
    }
  }
  if {$filenamelist != {}} {
    ViewAny $filenamelist $extList $filenameorg
  }
}

proc makeTmp {} {
  global glob
  set r 0
  if { ! [file exists $glob(tmpdir)] } {
    set r [Try { file mkdir $glob(tmpdir) } "" 1]
    if {$r} {
      return -code error "Failed to create $glob(tmpdir)."
    }
  }
  return $glob(tmpdir)
}
# Move a VFS file to tmp dir. options: type <file type> size <size> and del <bool>
# If type is "fl" (a link) we force the size to -1
# If size is known, pass it. del 1 will delete any existing file by this name
# in the tmp dir.
proc MoveToTmp {file args} {
  array set opsAry [list type {} size -1 del 0]
  array set opsAry $args
  global glob
  if {$opsAry(type) == "fl"} {set opsAry(size) -1}
  set dest [makeTmp]/[file tail $file]
  if {$opsAry(del)} {
    file delete -force $dest
  } else {
    set inc {}
    while {[file exists $dest$inc]} {set inc [expr {$inc == {} ? "0" : [incr inc]}]}
    set dest $dest$inc
  }
  #set size [lindex $fileelem 3]
  set rr [frECF [list VFSgetFile %b $opsAry(size) 0]\
	      [list $file $dest]]
  lassign $rr r val cm
  frputs rr file dest
  if {$r == 0} {
    set glob(havedoneftp) 1 
    return $dest
  }
  PopError "Command $cm \ncaused error: $val"
  return 0
}



# proc CmdViewAsText {} {
#   global glob
#   CmdViewAsText_ $glob(listbox,left).file glob(left,filelist) \
#       $glob(left,pwd) $glob(right,pwd)
#   CmdViewAsText_ $glob(listbox,right).file glob(right,filelist) \
#       $glob(right,pwd) $glob(left,pwd)
# }listb_name filelist_var frompwd topwd 

proc CmdViewAsText { } {
  global glob
  set inst $glob(selected)
  set listb_name $glob(listbox,$inst).file
  set filelist $glob($inst,filelist)
  set frompwd $glob($inst,pwd)
  # For speed, (hopefully) we'll call by reference...
  # upvar $filelist_var filelist
  # set inst $glob(select_cur_lr)
  foreach sel [$listb_name curselection] {
    lassign [lindex $filelist $sel] dum file type size
    set ffile [DNtoDirTail [URL norm $frompwd/$file]]
    switch $type {
      wl  { ViewText [TranslateLnk [wLinkName $inst [lindex $filelist $sel]] \
		      [lindex $glob($inst,df) 1]]
      }
      l   -
      n   { ViewText $frompwd/$file }
      ld  -
      wld -
      d   { PopInfo [_ "Can't view directory %s in the text viewer" \
			 "$ffile"] }
      fn  -
      fl  { ViewText [MoveToTmp $ffile type $type size $size] $ffile}
      default CantDoThat
    }
  }
}

proc CmdCheckSize {} {
  global glob
  CmdCheckSize_ $glob(listbox,left).file glob(left,filelist)\
      $glob(left,pwd) $glob(right,pwd) left
  CmdCheckSize_ $glob(listbox,right).file glob(right,filelist) \
      $glob(right,pwd) $glob(left,pwd) right
}

proc CmdCheckSize_ { listb_name filelist_var frompwd topwd inst } {
  # For speed, (hopefully) we'll call by reference...
  global glob config env
  upvar $filelist_var filelist
  set filenamelist {}
  foreach sel [$listb_name curselection] {
    if {[CheckAbort [_ "View"]]} return
    set fileelem [lindex $filelist $sel]
    switch [lindex $fileelem 2] {
      d   -
      ld  -
      wld -
      wl  -
      l   -
      n   { lappend filenamelist [DNtoDirTail [URL norm $frompwd/[lindex $fileelem 1]]] }
      default CantDoThat
    }
  }
  if {$filenamelist != {}} {
    set pr [lindex $config(cmd,du) 0]
    frECF [list exec {*}[subst {*}$::stOps $config(cmd,du)]] $filenamelist \
	-w 1 -x 1\
	[list -post \
	     [list postOptions [list ViewString [_ "Output from %s" $pr]] nop ]]
   }
}


proc CmdWhatIs {} {
  global glob
  CmdWhatIs_ $glob(listbox,left).file glob(left,filelist)\
      $glob(left,pwd) $glob(right,pwd)
  CmdWhatIs_ $glob(listbox,right).file glob(right,filelist)\
      $glob(right,pwd) $glob(left,pwd)
}
package require fileutil

proc CmdWhatIs_ { listb_name filelist_var frompwd topwd } {
  # For speed, (hopefully) we'll call by reference...
  upvar $filelist_var filelist
  set files {}
  foreach sel [$listb_name curselection] {
    lassign [lindex $filelist $sel] dum file type
    switch $type {
      l   -
      wl  -
      wld -
      n   -
      ld  -
      d   {lappend files [DNtoDirTail [URL norm $frompwd/$file]]
          }
      default CantDoThat
    }
  }
  if {$files != {}} {
    if {$::MSW} {
      foreach file $files {
	append txt "$file: [::fileutil::fileType $file]\n"
      }
      ViewString [_ "file types"] txt
    } else {
      frECF "exec file %b"  $files\
	  [list -post [list postOptions [list ViewString [_ "Output from file"]] nop ]]
    }
  }
}
	# Try { PopInfo [exec file "$frompwd/$file"] } "" 1 

proc CmdEdit {} {
  global glob
  CmdEdit_ $glob(listbox,left).file glob(left,filelist) \
      $glob(left,pwd) $glob(right,pwd)
  CmdEdit_ $glob(listbox,right).file glob(right,filelist) \
      $glob(right,pwd) $glob(left,pwd)
}

proc CmdEdit_ { listb_name filelist_var frompwd topwd } {
  global env config glob
  # For speed, (hopefully) we'll call by reference...
  upvar $filelist_var filelist
  set filenamelist {}
  set inst $glob(select_cur_lr)

  foreach sel [$listb_name curselection] {
    set elem [lindex $filelist $sel]
    lassign $elem dum file swt
    # set swt [lindex $elem 2]
    switch -glob $swt {
      wl* {
	set ffile [TranslateLnk [wLinkName $inst $elem] \
		       [lindex $glob($inst,df) 1]]
      }
      default {set ffile [DNtoDirTail [URL norm $frompwd/$file]]}
    }
    switch  $swt {
      l   -
      fl  -
      wl  -
      fwl -
      fn  -
      n   { lappend filenamelist  $ffile }
      ld  -
      wld -
      d   { PopInfo [_ "Can't edit directory %s in the text editor" "$ffile"] }
      default CantDoThat
    }
  }
  if {$filenamelist != {}} {
    Log "exec $config(editor) $filenamelist"

    set rr [frECF [list exec {*}[subst {*}$::stOps $config(editor)]]\
		$filenamelist -w 1 -x 1]
  }
}

proc DoUsrButton {which} {
  global config glob
  upvar #0 glob(selected)             inst
  upvar #0 glob(listbox,$inst)        listb_name
  upvar #0 glob($inst,filelist)       filelist 
  upvar #0 glob($inst,pwd)            pwd
  # get the config info
  set command {}
  set name $which
  foreach f {dirs nomultiple viewout prior numparms} {
    set $f 0
  }
  set exec 1
  
  foreach {key value} $config(userButton,$which) {
    # we look for "n" first to grandfather the nomultiple "n" abbreviation
    switch -glob $key {
      n   -
      no* {set nomultiple $value}
      nu* {set numparms   $value}
      l* {set name       $value}
      c* {set command    $value}
      d* {set dirs       $value}
      v* {set viewout    $value}
      p* {set prior      $value}
      e* {set exec       $value}
    }
  }
  # These make sense?
  foreach f {dirs nomultiple viewout prior exec} {
    if {[string is boolean [set $f]]} {continue}
    PopWarn "Value for $f ($value) is not boolean."
    return
  }
  if {$nomultiple && ($prior || $numparms !=0)} {
    PopWarn "It is inconsistent to assert both \"nomultiple\" and \"prior\" or \"numparms\"."
  }
  if {$nomultiple} {
    set numparms "== 1"
  }
  if {$numparms == 0} {
    set numparms "*"
  }
  if {[string is integer $numparms]} {
    set numparms "==$numparms"
  }
  # get the current selected file list...
  set fileList {}
  # if doing prior, collect that first to maintain order
  set opwd $glob([Opposite $inst],pwd)
  if {$prior && [set pinst $glob(select_pry_lr)] != {}} {
    set opwd $glob($pinst,pwd)
    
    foreach sel $glob(select_pry_s) {
      set elem [lindex $glob($pinst,filelist) $sel]
      lassign $elem du file1 type
      if {[string match "wl*" $type]} {
	set file1 [TranslateLnk [wLinkName $pinst $elem] \
		       [lindex $glob($pinst,df) 1]]
      }
      lappend fileList [list [DNtoDirTail [URL norm $opwd/$file1]] $type]
    }
  }

  foreach sel [$listb_name.file curselection] {
    set elem [lindex $filelist $sel]
    lassign $elem duh file1 type
    if {[string match "wl*" $type]} {
      set file1 [TranslateLnk [wLinkName $inst $elem] \
		    [lindex $glob($inst,df) 1]]
    }
    lappend fileList [list [DNtoDirTail [URL norm $pwd/$file1]] $type]
  }
  # Ok we now have a list of all the files.
  # At the moment we will not allow VFS files and
  # while checking that, verify that any  dirs are allowed.
  set file {}
  # test for proper number of parameters * | < n | > n | == n|
  if {$numparms == "*" || [eval expr [llength $fileList] $numparms]} {
  } else {
    PopWarn "[llength $fileList] parms does not pass $numparms test"
    return
  }
  foreach ent $fileList {
    lassign $ent file1 type
    if {!$dirs && [string index $type end] == "d"} {
      PopWarn "The file \"$file1\" is a directory which is not allowed."
      return
    }
    if {[IsVFS $file1]} {
      PopWarn "The file \"$file1\" is a VFS file which is not allowed."
      return
    }
    lappend file $file1
  }
  # Ok, lets do it...
  if {$viewout} {
    set post [list -post \
	      [list postOptions \
		   [list ViewString "$name output: " ]\
		   [list PopInfo "No output" ]]]
  } else {
    set post {}
  }
  set ex [expr {[regexp {^ *twapi::.*} $command] || !$exec ? {} : {exec}}]
  frputs command file
  set r [catch {frECF [list {*}$ex {*}[subst {*}$::stOps $command]] $file {*}$post \
		    [list -error {}] -w 1 -x 1} er]

  if {$r != 0} {
    PopWarn "frECF error: $er"
  }	 
}
#  mid

proc CmdQEdit {} {
  global glob
  CmdQEdit_ $glob(listbox,left).file glob(left,filelist) \
      $glob(left,pwd) $glob(right,pwd)
  CmdQEdit_ $glob(listbox,right).file glob(right,filelist) \
      $glob(right,pwd) $glob(left,pwd)
}

proc CmdQEdit_ { listb_name filelist_var frompwd topwd } {
  # For speed, (hopefully) we'll call by reference...
  global glob
  upvar $filelist_var filelist
  set inst $glob(select_cur_lr)

  foreach sel [$listb_name curselection] {
    if {[CheckAbort [_ "Q-Edit"]]} return
    set elem [lindex $filelist $sel]
    lassign $elem dum file swt
    # set swt [lindex $elem 2]
    switch -glob $swt {
      wl* {
	set ffile [TranslateLnk [wLinkName $inst $elem] \
		       [lindex $glob($inst,df) 1]]
      }
      default {set ffile [DNtoDirTail [URL norm $frompwd/$file]]}
    }
    switch $swt {
      wl  -
      l   -
      n   { 
            set r [Try {EditText "$ffile" ""} \
		       [_ "Error editing %s" $ffile] 1]
            if {$r != 0} { catch { destroy .toplevel_$glob(toplevelidx) } }
          }
      ld  -
      d   { PopInfo [_ "Can't edit directory %s in the text editor" "$ffile"] }
      default CantDoThat
    }
  }
}


proc CmdRename {} {
  CmdMove Rename
  return
}

proc CmdUnArc {} {
  CmdUnArcPack unarc
}
proc CmdUnPack {} {
  CmdUnArcPack unpack
}

proc CmdUnArcPack {which} {
  global glob
  CmdUnArcPack_ $glob(listbox,left).file glob(left,filelist) \
      $glob(left,pwd) $glob(right,pwd) $which
  CmdUnArcPack_ $glob(listbox,right).file glob(right,filelist)\
      $glob(right,pwd) $glob(left,pwd) $which
  UpdateWindow both
}

proc CmdUnArcPack_ { listb_name filelist_var frompwd topwd which} {
  upvar $filelist_var filelist
  global glob
  set inst $glob(select_cur_lr)
  foreach sel [$listb_name curselection] {
    if {[CheckAbort $which]} return
    set elem [lindex $filelist $sel]
    lassign $elem dum file swt
    # set swt [lindex $elem 2]
    switch -glob $swt {
      wl* {
	set ffile [TranslateLnk [wLinkName $inst $elem] \
		       [lindex $glob($inst,df) 1]]
      }
      default {set ffile [DNtoDirTail [URL norm $frompwd/$file]]}
    }
    switch $swt {
      ld  -
      wld -
      d   { PopError [_ "Can't %s directory %s..." $which $file] }
      l   -
      wl  -
      n   { Log [_ "Which %s to %s" $ffile $topwd]
            UnArcPackAny $ffile $topwd $which
          }
      default CantDoThat
    }
  }
}


proc CmdArc {} {
  global glob
  CmdArc_ $glob(listbox,left).file glob(left,filelist) \
      $glob(left,pwd) $glob(right,pwd)
  CmdArc_ $glob(listbox,right).file glob(right,filelist) \
      $glob(right,pwd) $glob(left,pwd)
  UpdateWindow both
}

proc CmdArc_ { listb_name filelist_var frompwd topwd } {
  global config glob
  upvar $filelist_var filelist
  set inst $glob(select_cur_lr)

  foreach sel [$listb_name curselection] {
    if {[CheckAbort [_ "Archive"]]} return
    set elem [lindex $filelist $sel]
    lassign $elem dum file swt
    # set swt [lindex $elem 2]
    switch -glob $swt {
      wl* {
	set ffile [TranslateLnk [wLinkName $inst $elem] \
		       [lindex $glob($inst,df) 1]]
	
      }
      default {set ffile [DNtoDirTail [URL norm $frompwd/$file]]}
    }
    set tail [file tail $ffile]
    set dir [file dirname $ffile]
    switch $swt {
      wl  -
      l   -
      n   { 
        Log [_ "Packing %s" $ffile]
#        Try { eval exec [format $config(cmd,pack) \
	    #	    {$ffile}] } "" 1  $glob(async)
	set rr [frECF [list exec {*}[subst {*}$::stOps $config(cmd,pack)]] \
			 [list $ffile] \
			 [list -b $glob(async)]]
	lassign $rr r out
	if {$r != 0} {
	  PopError $out
	}
      }
      wld -
      ld  -
      d   { 
        Log [_ "Archiving %s" $ffile]
	cd $dir
        if {$config(cmd,archive) == "tar+gz {%s}"} {
	  frECF [list exec tar cf - %a | gzip > %a] \
	      [list $tail $tail.tar.gz] \
	      [list -back $glob(async)]
	  
          # Try { cd $dir; exec tar cf - $tail | \
	  # 	    gzip > $tail.tar.gz } "" 1 $glob(async)
        } else {
	  frECF [list exec {*}[subst {*}$::stOps $config(cmd,archive)]]\
		   [list $ffile]\
		   [list -back $glob(async)]
	}
      }
      default CantDoThat
    }
  }
}

proc CmdPrint {} {
  global glob
  CmdPrint_ $glob(listbox,left).file glob(left,filelist) \
      $glob(left,pwd) $glob(right,pwd)
  CmdPrint_ $glob(listbox,right).file glob(right,filelist) \
      $glob(right,pwd) $glob(left,pwd)
}

proc CmdPrint_ { listb_name filelist_var frompwd topwd } {
  global config glob
  upvar $filelist_var filelist
  set inst $glob(select_cur_lr)

  foreach sel [$listb_name curselection] {
    if {[CheckAbort [_ "Print"]]} return
    set elem [lindex $filelist $sel]
    lassign $elem dum file swt
    # set swt [lindex $elem 2]
    switch -glob $swt {
      wl* {
	set ffile [TranslateLnk [wLinkName $inst $elem] \
		       [lindex $glob($inst,df) 1]]
      }
      default {set ffile [DNtoDirTail [URL norm $frompwd/$file]]}
    }
    switch $swt {
      wl  -
      l   -
      n   {
	Log [_ "Printing %s" $ffile]
	# If a direct call to twapi:: eliminate the 'exec'
	set ex "exec"
	if {[regexp {^ *twapi::.*} $config(cmd,print)]} {
	  set ex {}
	} 
	frECF [list {*}$ex {*}[subst {*}$::stOps $config(cmd,print)]]\
	    [list $ffile]\
	    [list -back $glob(async)]
      }
      ld  -
      wld -
      d   { PopError [_ "Can't print directories!!"] }
      default CantDoThat
    }
  }
}
 
proc CmdMakeSameDir {} {
  global glob
  if { $glob(left,pwd) == $glob(right,pwd) } {
    SandDsame
    return {}
  }    
  CmdMakeSameDir_ $glob(listbox,left).file glob(left,filelist) $glob(left,pwd) $glob(right,pwd)
  CmdMakeSameDir_ $glob(listbox,right).file glob(right,filelist) $glob(right,pwd) $glob(left,pwd)
  UpdateWindow both
}

proc CmdMakeDir {} {
  global config glob
  set inst {}
   if {[$glob(listbox,left).file curselection] != {}} {
    set inst left
  } elseif {[$glob(listbox,right).file curselection] != {}} {
    set inst right
  }
  if {[info exists glob(whichdir)]} {
    if {$inst == {} ||\
	    [$glob(win,$glob(whichdir)).entry_dir get] != \
	    [dirToDN $glob($glob(whichdir),pwd)]} {
      set inst $glob(whichdir)
    }
  }
  if {$inst == {}} {
    PopWarn [_ "\"MkDir\" pushed with nothing selected and display directory \
                unchanged.\n Please do one of these so filerunner\
                knows where the directory is desired."]
    return
  }
               
  if {[set newdir [$glob(win,$inst).entry_dir get]] == \
	  [dirToDN $glob($inst,pwd)]} {
    set newdir [simple_smart_dialog "." [_ "Directory name?"] \
		    [_ "Please enter the name of the new directory.\
                        Another way of creating directories is to enter\
                        the name the new directory in one\
                        of the directory entries and then pressing the\
                        MkDir button"] $newdir]
    if {$newdir == ""} return
  }
  set newdir [DNtoDir $newdir]
  Log [_ "Creating directory %s" $newdir]
  if {[IsVFS $newdir]} {
    Try { VFSmkdir $newdir } "" 1
    set glob(forceupdate) 1
  } else {
    Try { file mkdir $newdir } "" 1
  }
  UpdateWindow both
  set glob(forceupdate) 0
}

proc CmdSelect {} {
  global glob
  if { [info exists glob(whichdir)] } {
    set inst $glob(whichdir)
    set pat [DNtoDir [$glob(win,$inst).entry_dir get]]
  } else {
    PopInfo [_ "Please enter a selection pattern in one of the\
                directory entries and then press the Select button"]
    return
  }
  setDisplayDir $inst
  # $glob(win,$inst).entry_dir delete 0 end
  # $glob(win,$inst).entry_dir insert end $glob(${inst},pwd)
  set pat [file tail $pat]
  set i 0
  foreach elem $glob($inst,filelist) {
    if {[string match $pat [lindex $elem 1]]} {
      $glob(listbox,$inst).file selection set $i
    }
    incr i
  }
  propagateSelection $glob(listbox,$inst).file
  UpdateStat
}

proc CmdCSelect {} {
  global glob
  set cmd [simple_smart_dialog "." [_ "Contents-select"] \
	       [_ "Make sure you have selected the files you want to\
                   search in, then please edit this command to do a\
                   contents-select."] "grep -i "]
  if { $cmd == "" } return
  CmdCSelect_ $glob(listbox,left).file glob(left,filelist) \
      $glob(left,pwd) $glob(right,pwd) $cmd
  CmdCSelect_ $glob(listbox,right).file glob(right,filelist) \
      $glob(right,pwd) $glob(left,pwd) $cmd
  UpdateStat
}

proc CmdCSelect_ { listb_name filelist_var frompwd topwd cmd } {
  upvar $filelist_var filelist

  foreach sel [$listb_name curselection] {
    set elem [lindex $filelist $sel]
    switch [lindex $elem 2] {
      l   -
      wl  -
      n   { 
	set r [catch {
	  eval exec $cmd {
	    [DNtoDirTail [URL norm $frompwd/[lindex $elem 1]]]
	  }
	} outp]
	if { $r != 0 } {
	  $listb_name selection clear $sel
	}
      }
      default CantDoThat
    }
  }
}

# This searches the run list for 'what'
# and returns either -1 (not found) or a list
# of indexes to the found entry
proc runCmdListSearch {clist  what} {
  set level -1
  foreach en $clist {
    incr level
    if {[llength $en] != 2} {
      if {[lindex $en 0 0] == $what} {
	return $level
      }
    } else {
      set rt [runCmdListSearch [lindex $en 1] $what]
      if {$rt != -1} {
	return [concat $level 1 $rt]
      }
    }
  }
  return -1
}

proc CmdRunCmd {args} {
  frputs "Run command called to run $args "
  global glob config
  set glob(runGlob,run) {}
  set thisone {}
  # Old call sequence had 0/1 followed by name...
  foreach thisone $args {
    if {[string is integer $thisone]} {
      set thisone {}
      continue
    }
    break
  }
  if {$thisone != {} } {
    # This search is complicated by the tree structure of the runlist.
    set indx [runCmdListSearch $config(runlist) $thisone]
    if {$indx == -1} {
      PopError [_ "Run command: could not find \"%s\" in the runlist."\
		    $thisone]
      return
    }
    set glob(runGlob,run) [lindex $config(runlist) {*}$indx]
    after idle [list DoProtCmd CmdRunCmd_]
    return
  }
  # none of that, put up a menu and bail...
  
  # This is an convential menu window on which we force a tearoff.
  # This allows us to have checkboxes that don't unpost the menu.
  # Given that FixTearoff fixes most of the problems with
  # tearoff windows, all we are left with is positioning it
  # somewhere near the mouse pointer.

  # Read Config will eliminate the menu so that we can rebuild
  # it with a possibly new runlist. Otherwise, we keep the same
  # menu for the duration.

  # Unlike other tearoffs we only allow one on the screen at a time.

  set w [expr {[info exists glob(runMenu)] ? $glob(runMenu) : {}}]
  if {![winfo exists $w]} {
    # We need to build a new menu
    set w .menu_[incr glob(toplevelidx)]
    set glob(runMenu) $w
   
    destroy $w
    
    menu $w\
	-tearoff 1 \
	-title "Run menu"\
	-borderwidth 1 \
	-relief solid \
	-tearoffcommand "FixTearoff GrabTearoff"
    
    $w add checkbutton\
	-label [_ "<Edit entry>"] \
	-variable glob(runEdit)
    
    $w add checkbutton \
	-label [_ "<Delete menu on select>"] \
	-variable glob(runDeleteMenu)
 
    $w add separator
    
     buildRunListMenu
  }
  # this covers the case where it was but s/he destroyed it
  set glob(runDeleteMenu) 1
  set glob(runEdit) 0
  if {![info exists glob(runMenuTearoff)] || \
	  ![winfo exists $glob(runMenuTearoff)]} {
    $w invoke 0
  }

  if {[llength $config(runlist)] == 0} {
    set glob(runEdit) 0
    wm with $glob(runMenuTearoff)
    cmdrunEdit $glob(runGlob,run)
    return
  }
  # Now we have a menu which is in a tearoff frame
  # Lets put it near the mouse...
    
  set hm [winfo reqheight $w]
  lassign [winfo pointerxy .] msx msy
  
  # If the mouse is in the lower half of the screen, put the bottom of the
  # window where the mouse is, otherwise, put the top there
  
  set yloc [expr {$msy < [winfo screenheight .] / 2 ? $msy : $msy - $hm}]
  
  update
  # we should have a new tearoff window 
  # Lets get its true name...
  if {[info exists glob(runMenuTearoff)]} {
    set wto $glob(runMenuTearoff)
  } else {
    error "Can not find tearoff window from $w"
  }
  # wm with $w
  wm geo $wto +${msx}+$yloc
  wm deicon $wto

  frputs "Run menu building  " w wm hm ww wh "[wm geo $w] [wm geo $w]  "
  return
}

# This code snipit adds the runlist entries to the menu after first
# deleting what is there...

proc buildRunListMenu {} {
  global config glob
  set w $glob(runMenu)
  $w delete 4 end
  
  buildCasMenu {} \
      [runListMenuBuild $config(runlist)] \
      $w \
      runListButton\
      [list -tearoffcommand FixTearoff ]
  
  $w add command \
      -label [_ "<Add a new program>"] \
      -command  "runListButton 1"
}



# We pass this proc to FixTearoff which will add the two parms, the
# origional window and the name of the new tearoff.  We need the
# ladder to fix a few things...

proc GrabTearoff {menu tearoff} {
  set ::glob(runMenuTearoff) $tearoff
}

# The following is driven by glob(runGlob,run) as to what and how
# to run the program

proc CmdRunCmd_ {} {
  global glob config
  # default the pwd to left (changes if selection made in right)
  set frompwd $glob(left,pwd)
  set fl {}
  foreach inst {left right} {
    foreach sel [$glob(listbox,$inst).file curselection] {
      set elem [lindex $glob($inst,filelist) $sel]
      switch [lindex $elem 2] {
	wl  {
	  lappend fl [TranslateLnk [wLinkName $inst $elem] \
			  [lindex $glob($inst,df) 1]]
	}
	d   -
	ld  -
	l   -
	n   { lappend fl [lindex $elem 1] }
	default CantDoThat
      }
      set frompwd $glob($inst,pwd)
    }
  }
  set options [lassign $glob(runGlob,run) namecmd sep] 
  lassign $namecmd name cmd
 
  if {"last+current" in $options && $glob(select_pry_lr) != {} } {
    set secdir $glob($glob(select_pry_lr),pwd)
    if { $secdir == $frompwd } {
      set secdir ""
    }
    foreach sel $glob(select_pry_s) {
      lappend fl \
	  [DNtoDirTail\
	       [URL norm ${secdir}/[lindex $glob($glob(select_pry_lr),filelist) $sel 1]]]
    }
  }
  # set cmd [FixFormatString $cmd]
  # puts "run $cmd sep = $sep"
  if {"fullname" in $options} {
    set fll $fl
    set fl {}
    foreach file $fll {
      # if we already have a full name, join will just pass the file
      lappend fl [file join $frompwd $file]
    }
  }
  # we want to use frECF here.
  # This fixes the run string to accept more than one file at the same loc
  # and, with the change to -default, takes care the the file seperator issue
  # We replace the first spec (one of:%s|%n|%m|%q|%r) with that %b

  # First count the specs, keep location of the last one.
  set start 0
  set count 0
  set here [string length $cmd]
  while {[set loc [string first "%" $cmd $start]] != -1} {
    if {[string index $cmd $loc-1] == {\\}} {
      set start [incr loc]
      continue
    }
    set start [incr loc 2]
    if {[string index $cmd $loc-1] in {s n q r} } {
      incr count
      set here $loc
    }
  }
  
  set ncmd [string range $cmd 0 $here-1]
  set spec %[expr {$count > 0 ? [string index $cmd $here-1] : "s"}]
  if {[set remains [expr [llength $fl] - $count]] > 0} {
    if {$count == 0} {
      append ncmd " $spec"
      incr remains -1
    }
    append ncmd [string repeat "${sep}$spec" $remains]
    append ncmd [string range $cmd $here end]
    set cmd $ncmd
  }
  set async [expr {"async" in $options}]
  if {!$async} {
    set op [list -post \
		[list postOptions \
		     [list ViewString [_ "Output from %s %s"\
					   $name \
					   [string index $cmd end]]]\
		     nop]]
  } else {
    set op "-nop"
  }
  LogSilent [_ "Running command: %s" $cmd]
  cd $frompwd
  frECF [list exec {*}[subst {*}$::stOps $cmd] {*}[expr {$async ? "&" : {}}]]\
      $fl \
      [list -default "${sep}$spec" -back $async]\
      $op
  
  UpdateWindow both
}


proc runListMenuBuild {list} {
  set runMenu {}
  while {[llength $list] > 0} {
    set list [lassign $list this]
    set more [lassign $this name ent]
    if {[string index $name 0] != "-"} {
      # simple entry
      lappend runMenu [list [lindex $name 0]]
    } else {
      # a sub list
      lappend runMenu [list $name [runListMenuBuild $ent]]
    }
  }
  frputs "Raw run menu  " runMenu
  return $runMenu		       
}

# [list {label} {runstring} {{ }} fullname async]
proc cmdrunEdit {what} {
  global glob config
  set opts [lassign $what namecmd glob(runGlob,file)]
  lassign $namecmd glob(runGlob,display) glob(runGlob,run)
  set oldDisplayName $glob(runGlob,display)
  
  set glob(runGlob,full) [expr {"fullname" in $opts}]
  set glob(async) [expr {"async" in $opts}]
  set glob(runGlob,duo) [expr {"last+current" in $opts}]
  # default to add to run list 
  set glob(runGlob,add) [expr {! $glob(runEdit) ? 1 : 0}]
  set glob(runNow) 0
					 
  set sdr [smart_dialog .run[incr ::uni] \
	       . \
	       [_ "Run Dialog"] \
	       [list [_ "Edit the 'command  & ops' line.\
                         \n      Ctrl-A takes you to the start of the line.\
                         \n      Tab does command complete.\
                         \n      Mouse button 3 brings up a browser.\
                         \n      This command is run through the FileRunner\
                                  Exec Call Formater (FRECF)\
                         \n      so all format commands\
                                  that FRECF supports are available.\
                         \n      See Help menu->Tips: 'Configure options for commands II'\
                         \n      A '%s' in this string will be appended with\
                         \n      sufficient copys for the given file list\
                         \n      with the requested file seperator between them\
                         \n\n'file separator' specifies\
			 what string is used to separate files (should there\
                         \n      be more than one). Default is space ({ }).\
                         \n'display name' if supplied will be the name displayed\
                         in the run list\
                         \n'full file names' will pass the full name rather than\
                         the working directory relative name.\
                         \n'Last + current' will pass both the last selection\
                          and the current selection.\
                         \nAn edit operation, unless canceled, will save the\
                         result.\
                         \n\"Add to run list\" will, in this case, also add the\
                         same entry to the end of the list.\
                         \n" "%s, %n, %q or %r" ]] \
	       0 \
	       [expr {$glob(runEdit) ? 11 : 10}] \
	       [list [list [_ "command & opts"] \
			  "-textvariable glob(runGlob,run) -width 80"] \
		    [list [_ "file separator"] \
			 "-textvariable glob(runGlob,file)"]\
		    [list [_ "display name"] \
			 "-textvariable glob(runGlob,display)"] \
		    [list [_ "Async"] "-variable glob(async)"]\
		    [list [_ "full file\nnames"] \
			 "-variable glob(runGlob,full)"]\
		    [list [_ "Last +\ncurrent"] \
			 "-variable glob(runGlob,duo)"]\
		    [list [_ "Add to\nrun list"] \
			 "-variable glob(runGlob,add)"]\
		    [list [_ "Run \nnow?"] \
			 "-variable glob(runNow)"]\
		    [list [_ "OK"]] \
		    [list [_ "Cancel"]]\
		    [list [_ "Delete from\nrun list"]]\
		    ]\
	       [list -height 16 scroll 15 \
		    bind [list .run.0 <Tab> \
			      "Complete .run.0\
                               .run.text ; break"]\
		    bind [list .run.0 <3> \
			      "CompleteWithBrowse .run.0"]\
		   ]]
  if {$sdr == 9 || $sdr == -1 || $glob(runGlob,run) == "" } {
    return {}
  }

  if {$sdr == 10} {
    # This is the delete option...
    set config(runlist) [ldelete $config(runlist) $glob(runIndex)]
    buildRunListMenu
    return
  }

  if {$glob(runGlob,file) == {}} {
    set glob(runGlob,file) " "
  }
  if {$glob(runGlob,display) == {}} {
    set glob(runGlob,display) [lindex $glob(runGlob,run) 0]
  }
  set new {}
  if {$glob(runGlob,full)} {lappend new "fullname"}
  if {$glob(async)}        {lappend new "async"}
  if {$glob(runGlob,duo)}  {lappend new "last+current"}
  set new [list [list $glob(runGlob,display) $glob(runGlob,run)] \
	       $glob(runGlob,file) {*}$new]
  if {$glob(runEdit)} {
    lset config(runlist) {*}$glob(runIndex) $new
  }
  if {$glob(runGlob,add)} {
    # $glob(runMenu) insert [$glob(runMenu) index end] command \
    # 	-label $glob(runGlob,display)\
    # 	-command [list runListButton 2 {} [llength $config(runlist)]]
    lappend config(runlist) $new
  }
  if {$glob(runGlob,add) || $glob(runEdit) >= 0} {
    buildRunListMenu
    SaveConfig
  }
  set glob(runGlob,run) $new
  if {$glob(runNow)} {
    DoProtCmd CmdRunCmd_
  }
  return
}

# The run menu sends us here. The parms are:
#
# val this will be 1 if s/he wants to add to the run list
#                  2 if loc is the index into the runlist
#                    of the program to run
# indx is the display name (we don't use this here)
# val is a list of indexies as per 2 above

# In aaddition we use glob(runEdit) and glob(runDeleteMenu)

proc runListButton {val {indx {0}} {loc {}}} {
  global glob config
  if {$glob(runDeleteMenu) && [winfo exists $glob(runMenuTearoff)]} {
    wm with $glob(runMenuTearoff)
  }
  if {$val < 1} {
    return
  }
  if {$val == 1} {
    set glob(runEdit) 0
    if {[cmdrunEdit {}] == {}} {
      return
    }
  }
  set glob(runGlob,run) [lindex $config(runlist) {*}$loc]
  if {$glob(runEdit) } {
    set glob(runIndex) $loc
    cmdrunEdit $glob(runGlob,run)
    return
  }
  DoProtCmd CmdRunCmd_
  return
}

proc CmdForEach {} {
  global glob
  CmdForEach_ $glob(listbox,left).file glob(left,filelist) \
      $glob(left,pwd) $glob(right,pwd) 
  CmdForEach_ $glob(listbox,right).file glob(right,filelist) \
      $glob(right,pwd) $glob(left,pwd) 
}

proc CmdForEach_ { listb_name filelist_var frompwd topwd } {
  global glob config
  upvar $filelist_var filelist
  set inst $glob(select_cur_lr)

  set fl {}
  foreach sel [$listb_name curselection] {
    set elem [lindex $filelist $sel]
    switch [lindex $elem 2] {
      wl {
	lappend fl [TranslateLnk [wLinkName $inst $elem] \
		       [lindex $glob($inst,df) 1]]
      }
      d   -
      ld  -
      l   -
      n   { lappend fl [lindex $elem 1] }
      default CantDoThat
    }
  }
  if { $fl == "" } return
  if {$glob(async)} {
    PopError [_ "This command does not support asynchronous execution"]
    return
  }
  if {![info exists glob(foreach,cmd)] || $glob(foreach,cmd) == {}} {
    set glob(foreach,cmd) {echo '%s'}
  }
  set glob(foreach,cmd) \
      [simple_smart_dialog "." [_ "Foreach"] \
	   [_ "Enter command to run on each of the selected files.\
               The file will show up in the '%s'. A second '%s'\
               (if coded) will be replaced by the opposite panel's\
               working directory. You can use '%s' for the first\
               '%s' to reorder these two. You can use pipes\
               and other bourne-shell syntax elements since the\
               commands will each run in a separate bourne shell\
               in the working directory of the selected file." %s %s %2@s %s]\
	   $glob(foreach,cmd) ]
  if { $glob(foreach,cmd) == "" } return

  set output {}
  foreach k $fl {
    if {[CheckAbort [_ "ForEach"]]} return
    set pcount [expr {[stringCount {%} $glob(foreach,cmd)] - \
			  (2 * [stringCount {%%} $glob(foreach,cmd)])}]
    set k [list $k]
    if {$pcount > 1} {
      lappend k $topwd
    }
    Log [_ "Running %s ..." [list exec [subst {*}$::stOps $glob(foreach,cmd)] $k]]
    
    set rr [frECF [list exec {*}[subst {*}$::stOps $glob(foreach,cmd)]] $k]

    
    append output [lindex $rr 1]
    append output "\n"
  }
  ViewString [_ "Output from commands"] output
  UpdateWindow both
}

# proc CmdRecurseCommand { inst } {
#   global glob config

#   set dir $glob($inst,pwd)

#   if { [IsVFS $dir] } {
#     CantDoThat
#     return
#   }
#   CmdFind $inst ok
#   return
# }

proc CmdDiff {} {
  global glob
  CmdDiff_ $glob(listbox,left).file glob(left,filelist) \
      $glob(left,pwd) $glob(right,pwd) left

  CmdDiff_ $glob(listbox,right).file glob(right,filelist) \
      $glob(right,pwd) $glob(left,pwd) right
}

proc CmdDiff_ { listb_name filelist_var frompwd topwd inst } {
    global glob
    upvar $filelist_var filelist
    global config

    set null 1
    set file1 ""
    set file2 ""
    foreach sel [$listb_name curselection] {
      set null 0
      set elem [lindex $filelist $sel]
      lassign $elem du ffile swt size
      switch -glob $swt {
	wl* {
	  set ffile [TranslateLnk [wLinkName $inst $elem] \
		       [lindex $glob($inst,df) 1]]
	}
	default {set ffile [DNtoDirTail [URL norm $frompwd/$ffile]]}
      }
      switch $swt {
	ld  -
	fld -
	d   -
	fd  -
	l   -
	fl  -
	fn  -
	n   { 
	  if {$file1 == ""} { 
	    set file1 $ffile
	    if {[IsVFS $file1]} {
	      set file1 [MoveToTmp $file1 type $swt size $size]
	    }
	  } else {
	    if { $file2 != "" } { 
	      PopError [_ "Please select one or two\
                                    files or directories for diffing."]
	      return
	    }
	    set file2 $ffile
	    if {[IsVFS $file2]} {
	      set file2 [MoveToTmp $file2 type $swt size $size del 1]
	    }	    
	    # But which was first?
	    if {$inst == $glob(select_pry_lr) && 
		$sel == $glob(select_pry_s)} { } else {
		  set tmp $file1
		  set file1 $file2
		  set file2 $tmp
		    }
		}
	    }
	default {
	  CantDoThat
	  return
	}
      }
    }

  if {$null} return

  if {$file2 == "" && $glob(select_pry_lr) != {}} {
    set sel $glob(select_pry_s)
    if {[llength $sel] == 1} {
      set elem2 [lindex $glob($glob(select_pry_lr),filelist) $sel]
      lassign $elem2 du file2t type2 size2
      set file2  [DNtoDirTail [URL norm $glob($glob(select_pry_lr),pwd)/$file2t]]
      if {[IsVFS $glob($glob(select_pry_lr),pwd)]} {
	set file2 [MoveToTmp $file2 type $type2 size $size2 del 1]
	# CantDoThat
	# return
      }
      switch -glob [lindex $elem 2] {
	wl* {
	  set file2 [TranslateLnk [wLinkName $glob(select_pry_lr) $elem] \
			 [lindex $glob($inst,df) 1]]
	}
      }
    } else {
	  PopError [_ "Please select one or two\
                         files or directories for diffing."]
	  return
	}
    }     
  if {! ($file2 == "")} {
    set tmp $file1
    set file1 $file2
    set file2 $tmp
  }
  frECF [list exec {*}[subst {*}$::stOps $config(cmd,diff)]] [list $file1 $file2] \
	[list -post \
	     [list postOptions \
		  [list ViewString [_ "Diffing %s and %s %s" \
					$file1 $file2\
					[string index $config(cmd,diff) end]]]\
		  [list PopInfo \
		       [_ "No difference between\n\n%s\n\nand\n\n%s" $file1 $file2]]]]\
	[list -error {} -background $glob(async)]
  
  
}

proc CmdCreateEmptyFile { inst } {
  global glob config
  set start_entry $glob($inst,pwd)
  set newfile [simple_smart_dialog "." [_ "Create New File"] \
		   [_ "Please enter the name of the new file."] \
		   $start_entry ]

  if {$newfile != ""} {
    Log [_ "Creating new file %s" $newfile]
    if {[IsVFS $newfile]} {
      # This is a bit complicated in that we really do want to
      # do a "touch" and not just blindly create a new file.
      # Its simple if it does not exist. If it does, well we
      # don't want to bring it here and send it back just to
      # change its modify time...
      # Try a touch command...
      set r [catch {VFScommand $VFStok [list touch $VFSpath]} out]
      if {$r != 0} {
	PopError "VFS command touch failed: $out"
      }
    } else {
      if {$config(cmd,touch) == {}} {
	if {![DoWeHavePackage fileutil touch]} {
	  PopWarn "config(cmd,touch) is 'nil' and tcl 'fileutil' not found.
                  \nWe quit!"
	  return
	}
	Try {::fileutil::touch "$newfile"} "" 1
      } else {
	Try { exec $config(cmd,touch) "$newfile" } "" 1
      }
    }
    ForceUpdate
  }
}

proc DeleteVFSRecursive {dir} {
  set me "DeleteVFSRecursive"
  global glob config
  set r [IsVFS $dir]
  if {[VFSrmdirEmpty $dir]} {
    VFSrmdir $dir
    return
  }
    
  Log [_ "$me: Entering %s" $dir]
  # If we don't save the 'wd' we could end up asking for a pwd in
  # a dir we just removed. This keeps it easy for the VFSpwd code.
  set savedCd [VFSpwd $VFStok]
  set fileList [getFileListFor $dir]
  VFScd $savedCd
  foreach elem  $fileList {
    if {[CheckAbort [_ "Delete"]]} {return}
    set file [lindex $elem 1]
    switch [lindex $elem 2] {
      fn  -
      fld -
      fl  {
        Log [_ "$me Deleting %s" $dir/$file]
        VFSdelete [URL norm $dir/$file]
      }
	fd { 
	    
	  if {$file ni {. ..}} {
	    DeleteVFSRecursive [DNtoDirTail [URL norm $dir/$file]]
	  }
      }
      default {
        error [_ "Unexpected file type in $me"]
      }
    }
  }
  Log [_ "$me Deleting %s" $dir]
  VFSrmdir $dir
}

proc CopyFromVFSRecursive { fromdir todir resume } {
  set me "CopyFromVFSRecursive"
  global glob config
  if {[CheckAbort [_ $me]]} return
  IsVFS $fromdir
  frputs fromdir todir 
  if {[VFSRcopyOk $fromdir] & 1} {
    if {[file tail $fromdir] == [file tail $todir] ||\
	     ![VFSsupports $fromdir nocopyas]} {
      VFSgetFile $fromdir $todir  0
      return
    }
  }
  set dir [file tail $fromdir]
  Log [_ "$me: Creating %s" $todir/$dir]
  file mkdir "$todir"
  Log [_ "$me: Entering %s" $fromdir]
  # NewPwd $inst $fromdir
  # UpdateWindow $inst
  foreach elem [getFileListFor $fromdir] {
    if {[CheckAbort [_ $me]]} return
    lassign $elem duh file type size
    set tof $todir/$file
    switch $type {
      fld -
      fl {
        Log [_ "$me Skipping %s - link" $fromdir/$file]
      }
      fd {
	if {$file ni {. ..}} {
	  CopyFromVFSRecursive "$fromdir/$file" \
	      "$tof" $resume
	}
      }
      fn {
        Log [_ "$me: Copying %s -> %s bytes)" \
             $fromdir/$file \
             "$todir/$dir/$file $size"]
        VFSgetFile "$fromdir/$file" \
	    "$todir/$file" "$size" $resume
      }
      default {
        error [_ "Unexpected file type in $me"]
      }
    }
  }
}

# The wsl file system type "9P" seems to foul up the tcl "file copy"
# causing it to spit out attributes from NTFS files and making companion
# files. By and large, we don't want this so we here have a "file copy"
# replacement.  We use 'CopyToVFSRecursive' if we are working with
# directories.

proc filecopy {args} {
  set force {}
  set args [lassign $args first]
  while {[string index $first 0] == "-"} {
    if {$first == "--"} {break}
    if {![string match "$first*" "-force"]} {
      return code error "filecopy option must be \"-force\" not \"$first\!"
    } else {
      set force "-force"
    }
    set args [lassign $args first]
  }
  set dest [lindex $args end]
  if {[lindex [file system $dest] 1] != "9P"} {
    # only the "9P" file system is a problem and only on destination
    return [file copy {*}$force -- {*}$args]
  }
  if {[file isdir $dest]} {
    foreach source [lrange $args 0 end-1] {
      set newD $dest/[file tail $source]
      if {[file isdir $source]} {
	file mkdir $newD
	filecopy {*}$force -- [glob -nocomplain -dir $source] $newD
      } else {
      
	set destFid [open $newD w]
      }
    }
    
  }
}

proc CopyToVFSRecursive { fromdir todir} {
  set me "CopyToVFSRecursive"
  global glob config
  if {[CheckAbort [_ $me]]} return
  frputs fromdir todir
  IsVFS $todir
  if {[VFSRcopyOk $todir] & 2} {
    if {[file tail $fromdir] == [file tail $todir] ||\
	    ![VFSsupports $todir nocopyas]} {
      # begin exp
      if {0} {
	frputs fromdir todir
	if {[VFSisDir $todir] == {}} {
	  # we know it should be a dir, but it does not exist...
	  VFSmkdir $todir
	}
	VFSputFile [URL dir $todir] $fromdir 0
	return
      }
	
      VFSputFile $todir $fromdir 0
      return
    }
  }
  Log [_ "$me: Creating %s" $todir]
  # VFSmkdir "[URL norm $todir/$dir]"
  if {[fileType $todir] != "d"} {
    VFSmkdir $todir
  }
  Log [_ "$me: Entering %s" $fromdir]
  # NewPwd $inst $fromdir
  # UpdateWindow $inst
  foreach elem [getFileListFor $fromdir] {
    if {[CheckAbort [_ $me]]} return
    lassign $elem duh file type size
    # set file [lindex $elem 1]
    set tof [URL norm $todir/$file]
    switch $type {
      ld -
      l {
        Log [_ "$me Skipping %s - link" $fromdir/$file]
      }
      d {
	if {$file ni {. ..}} {
	  CopyToVFSRecursive "$fromdir/$file"\
	      $tof
	}
      }
      n {
        Log [_ "$me: Copying %s -> %s %s bytes)"\
		 "$fromdir/$file" $tof $size]
        VFSputFile $tof "$fromdir/$file" $size
      }
      default {
        error [_ "Unexpected file type in $me"]
      }
    }
  }
}

# I may be dense, but I have not found a simple way to get 'file copy'
# to recursivly copy directories if the destination directory exists.
# It seems to do fine if it does not.
# Here is an 'file copy' replacement that does the same thing as 'xcopy'
# and 'cp'.

proc frFileCopy {from to} {
  # First the simple...
  set tail [file tail $from]
  if {$tail in {. ..}} {return}
  set nextTo $to/$tail
  if {![file exists $nextTo] || ![file isdir $from]} {
    file copy -force $from $to
    return
  }
  foreach file [glob -nocomplain -directory $from .* *.*] {
   frFileCopy $file $nextTo
  }
  return
}

proc FindDialog {inst pat} {
  # we use a special version of viewstring where we provide the call 
  # to the string search...
  #set result [join [lsort $result] "\n"]
  set result ""
  return [ViewString \
	      [_ "Find %s in %s Click on file to display in panel" \
		   $pat "[pwd]/*" ] \
	      result SearchConfig [list FindConfig $inst]]
}

proc FindConfig {inst w title filename result} {
  global glob
  upvar 2 $result string
  centerWin $w
  textSearch $w.text $title  "+buildViewConfig" \
      {} \
      [list \
	   {Sort on tail} "findSort $w.text t" \
	   {Sort on full name} "findSort $w.text f" \
	   {Save As...} "SaveToFile $w.text [list $glob($inst,pwd)/] 1" \
	   [_ Quit]  "destroy $w"]
  bind $w.text <1> "GotoFind \"$glob($inst,pwd)\" \"\[findGetLine  $w.text %x %y]\" $inst;break"
  #puts "$string"
}

proc findGetLine {w x y} {
  lassign [split [$w index @$x,$y] .] line
  return [$w get $line.0 $line.end]
}
proc findSort {w opt} {
  set files [split [$w get 0.0 end] \n]
  $w delete 0.0 end
  $w mark set insert 0.0
  set sort {}
  foreach file $files {
    if {$file != {} } {
      if {$opt == "t"} {
	lappend sort [file tail $file]$file
      } else {
	lappend sort $file
      }
    }
  }
  #puts "$sort"
  set sindex [lsort -indices $sort]
  foreach idx $sindex {
    $w insert insert "[lindex $files $idx]\n"
  }
  # Lets not ask about saving this file...
  $w edit reset
  $w edit modified 0
}

proc GotoFind { dir file inst } {
  global glob config
  frputs "gfind [pwd]  " inst dir file
  if {[string index $file 0] == " "|| $file == {}} {return 0}
  set file [URL join $dir $file]
  NewPwd $inst [URL dirname $file]
  UpdateWindow $inst
  set name [file tail $file]
  if {$name == {}} {return 1}
  # use search here? need ordinal number
  if {[set j [lsearch -exact -index 1 $glob($inst,filelist) $name]] != -1} {
    $glob(listbox,$inst).file selection clear 0 end
    $glob(listbox,$inst).file selection set $j
    $glob(listbox,$inst).file see $j
    propagateSelection $glob(listbox,$inst).file
    return 1
  }
  # File not found. Could it be hidden?
  if {$config(fileshow,all) == 0} {
    if {!$::MSW} {
      set h [expr {[string index $name 0] == "."}]
    } else {
      set h [file attributes $file -hidden]
    }
    if {$h} {
      PopError [_ "File %s is a \"hidden\" file which are\
               \nnot displayed if \"Show All File\" configuration is false." $file]
      return 1
    }
  }
  PopError [_ "File %s can not be found." $file]
}

# fileutilFindAsyncHandler is an async function to set up a call to
# ::fileutil::find. It will provide a compare function based on the
# cmds and will log results to 'logFun'
#
proc fileutilFindAsyncHandler {w cmds logFun} {
  # first remove the >2 stuff
  if {[lindex $cmds end-1] == ">2"} {set cmds [lreplace $cmds end-1 end]}
  set ::findStop$w 0
  set parms  [lindex $logFun 1 ]
  frputs w cmds logFun parms
  set r [catch {::realfind::realFind [list [pwd]] \
    "$cmds" [linsert $logFun end "::findStop$w" "data"]} what]
  if {$r !=0} {
    #puts "rtn $what"
    findData $parms $w d data " ${what}\n"
    findData $parms $w d data " $::errorInfo"
  } else {
    #puts "rtn 0 $what"
    foreach line $what {
      findData $parms $w d data " $line"
    }
  }
  findData $parms $w d eof
  unset ::findStop$w
  
}

proc findAquots {f} {regsub -all {^\{|\}$} $f {"}} ; # for emacs "

# We assume that being here implies that the resources exists to do the job,
# i.e. there is a search routine of the given variety on the system (local or VFS)
# if local, we may consult the configure options for a given command.

proc CmdFind { inst args} {
  global glob config
  # Two options (so far) :
  # cmd prog (use prog instead of find (only really know find and locate (now))
  # recur <bool> run a given program over each found file
  
  array set opsAry [list recur 0 cmd find] ;# defaults
  array set opsAry $args
  
  set VFStok "r"
  set vfs [IsVFS [set VFSpath $glob($inst,pwd)]]

  catch "destroy .apop"
  set cmd [set Cmd $opsAry(cmd)]
  set progFind 0       ;# true if using the tcl find, else the external program
  if {$Cmd == "find"} {
    if {$config(cmd,find) == {}} {
      set progFind 1
    } else {
      set cmd $config(cmd,find) 
    }
  }
  if {![info exists glob($Cmd,string)]} {
    set glob($Cmd,string) ""
    set glob($Cmd,options) {}
    set glob($Cmd,expressions) {}
    set glob($Cmd,regexp) 0
    set glob($Cmd,casefold) 0
    set glob(${Cmd}Foreach,all) 0
    set glob(${Cmd}Foreach,cmd) {}
  }
  set findStart [list {} [_ "Please enter substring of filename\
                        to search for in %s/ and below." $glob($inst,pwd)]]
  set recurStart [list [_ "This command will run:\
                 \n\n %s %s <options>\
                 \n\n(with <options> selected below) to [expr {$Cmd == {find} ?\
                 {recurse into the current directory (%s)} : \
                 {locate files in the current directory (%s/) and below}}].
                 \nThe below entered <command> will then be run on all files\
                 from this \"%s\" command (either singly or as a group).\n\
                 \nSee also: manpage for the \"%s\" command.\
                 \n\nUse tab to go to next entry.\
                 Return or the OK button starts execution."\
			    $opsAry(cmd) $glob($inst,pwd)\
			    $glob($inst,pwd) $opsAry(cmd) $opsAry(cmd) ]]
  set namRex  [list [_ "name or regex pattern"] [list -textvariable glob($Cmd,string)]]
  set namGlob [expr {$::MSW ? [list [_ "name or glob * pattern"]\
				   [list -textvariable glob($Cmd,string)]] : $namRex}]
  set findOps [list [_ "extra options (-L,-P,-D,-O)"] \
		   [list -textvariable glob($Cmd,options)]]
  set rexOp   [list [_ "regexp"] [list -variable glob($Cmd,regexp)]]
  set caseOp  [list [_ "casefold"] [list -variable glob($Cmd,casefold)]]
  set findExt [list [_ "extra expressions and tests"]\
		   [list -textvariable glob($Cmd,expressions)]]
  set allOp   [list [_ "all files"] {-variable glob(${Cmd}Foreach,all)}]
  set recrCmd [list [list {} [list -text [_ "\nEnter <command> to run on each file\
               or all files as a group\
               (option <all files>). The file(s) will show up in the '%s'\
               (or at the end of the command if %s is not coded).\ 
               \nThe commands are run directly by the \
	           [expr {$VFStok != {r} ?  {remote shell} : {tcl exec command}}].\
               You can use pipes and other constructs.\
               You can also enter a shell as the first part of the\
               command to pickup shell syntax elements." "%s" "%s"] \
			    -wrap \
			    [expr {60 * [font measure $config(gui,ListBoxFont) {0}]}]\
				  -justify left]]\
		   [list {} {-textvariable glob(${Cmd}Foreach,cmd)}]]

  set okCan [list [_ OK] [_ Cancel]]
 
  switch $opsAry(cmd)$opsAry(recur) {
    find0   {
      set title [_ "Find file(s)"]
      set buts [list $findStart $namRex $findOps $rexOp $caseOp $findExt]
    }
    find1   {
      set title [_ "Find & run command"]
      set buts [list $recurStart $namRex $findOps $rexOp $caseOp $findExt {*}$recrCmd]
    }
    locate0 {
      set title [_ "Locate file(s)"]
      set buts [list $findStart $namRex $rexOp $caseOp ]
    }
    locate1 {
      set title [_ "Locate & run command"]
      set buts [list $recurStart  $namRex {*}$recrCmd $rexOp $caseOp ]
    }
  }
  lappend buts {*}$okCan   
  set butCount [llength $buts]
  set endButts [lassign $buts startBut]
  set r [smart_dialog .dbl_entry_dialog[incr ::uni] "."  $title\
	      $startBut\
	     -1 [incr butCount -1]\
	     $endButts [buildDialogConfig] \
	     [list bind [list .dbl_entry_dialog.1 <Return> \
			     "event generate .dbl_entry_dialog.0 <Tab>"]]]
  frputs r butCount glob($Cmd,expressions) glob($Cmd,string) namRex namGlob rexOp caseOp
  if {$r == -1 || \
	  $r == ($butCount - 1) || \
	  ($glob($Cmd,expressions) == {} && $glob($Cmd,string) == {})} {return}
  #
  set r [catch {[expr {$VFStok == {r} ? "cd" : "VFScd"}] $glob($inst,pwd)} out]
  if {$r} {
    PopError "$out"
    return
  }
 

  set ext [list $glob(${Cmd}Foreach,all) $glob(${Cmd}Foreach,cmd)]
  # if {$glob($Cmd,expressions) == {} && $glob($Cmd,string) == {}} {return}
  set pcall {}
  if {$glob($Cmd,string) != {}} {
    # build the proper pattern call out name, iname regex iregex
    switch -exact $glob($Cmd,regexp)$glob($Cmd,casefold) {
      00 {set pcall "-name";  set options {-e}}
      01 {set pcall "-iname"; set options {-e --ignor-case}}
      10 {set pcall "-regex"; set options {-e --regx}}
      11 {set pcall "-iregex";set options {-e --ignor-case --regx}}
    }
  }

  if {[llength [set str $glob($Cmd,string)]] > 1 && $Cmd == "find"} {
    return -code error "\"find\" only supports one pattern string."
  }
  # if {$VFSpath == "/"} {
  #   set VFSpath {}
  # }
  
  set dirPrefix [expr {$Cmd == "find" ? {} : "$VFSpath/"}]
  foreach stri $str {
    lappend pat $dirPrefix[expr {$glob($Cmd,regexp) ? ".*$stri.*" : "*$stri*"}]
  }
  # Use the frECF to get this right...
  set patQuot [expr {$vfs ? "{'%m'}" : "%m"}]
  if {$Cmd == "find"} {
    # set cmds [slList {*}[subst {*}$::stOps $cmd] \
    # 		  {*}$glob($Cmd,options) [pwd]/ $pcall %n \
    # 		  {*}$glob($Cmd,expressions) -print 2> \
			   # 		  [expr {$::MSW ? "nul" : "/dev/null"}]]
    set cmds [frECF [slList {*}[subst {*}$::stOps $cmd] %m $pcall $patQuot\
			 {*}$glob($Cmd,expressions) -print ] \
		  [list $VFSpath/ {*}$pat] -f 1 -m findAquots]
     # set cmds [slList {*}[subst {*}$::stOps $cmd] \
     # 		   $VFSpath/ {*}$glob($Cmd,options) $pcall $patQuot \
     # 		   {*}$glob($Cmd,expressions) -print]
 } else {
   set cmds [slList {*}[subst {*}$::stOps $cmd] \
		 {*}$options %n]
   set cmds [frECF $cmds [list {*}$pat] -f 1]
  }
  
  # set cmds [frECF $cmds [list {*}$pat] -f 1]
  frputs "parts of find  " cmds glob($Cmd,string) pat
  
  set w [FindDialog $inst "$pat $glob($Cmd,expressions)"]
  
  set logFun [list findlog [list findData [list [list $ext [list $VFSpath]]] $w]]
  if {$progFind} {
    set cmds [slList {*}$glob($Cmd,options) $pcall \
		  $pat {*}$glob($Cmd,expressions)]
    
    set geo [buildStop $w \
		 [_ "Searching, %s for %s. Please wait..." "$VFSpath/*" "$pat" ] \
		 "set ::findStop$w" 1]
    Try {fileutilFindAsyncHandler $w "$cmds" \
	     {findData { [list $ext] [list [pwd]]} $w}} -a
  } else {
    # We have a race here as 'buildStop' needs to do an update which
    # means that calling after we call pipeoExec allows the pipo command
    # to send us stuff before we are ready.  So,
    # Here we set a dummy call back to 'update' which should never be
    # called and then after the pipeo call we update the call back
    # function to what it ought to be (for which we need the fid that
    # pipeo returns.
    set geo [buildStop $w \
		 [_ "Searching, %s for %s. Please wait..." "$VFSpath/*" "$pat" ] \
		 update 1]
    set where [expr {$vfs ? "to $VFStok" : "Local"}]
    Log "Command: \"$cmds\" $where"
    lassign [pipeoExec $cmds $VFStok $logFun] r fid
    frputs r fid
    if {$r != 0} {
      destroy $w
      error [_ "Bad %s call: %s \n %s" $Cmd $fid $cmds]
    }
    frputs "stopReSetCallBack "
    stopReSetCallBack $w "pipeoAbort $fid"
    frputs "STOP set " fid
  }
 
  if {[winfo exists $w]} {wm geo $w $geo}
}

proc findData {parms w fid tag {arg {}}} {
  global config glob
  lassign $parms ext pwd
  frputs "[incr ::xx] " parms ext pwd fid tag arg 
  set loc [$w.text index end-1chars]
  lassign [split $loc .] line char
  if {$tag == "data" } {
    if {$ext == "com"} {
      $w.text insert insert ${arg}
    } else {
      if {[string index $arg end] != "\n" } {
	StopProgress $w $arg
      } else {
	set pwd [string trimright $pwd /]
	if {[string compare -length [string length $pwd] $pwd ${arg}] == 0} {
	  $w.text insert insert  [string range ${arg} [string length $pwd]+1 end]
	} else {
	  $w.text insert insert ${arg}
	}
      }
      # This might be excessive, but, at least on my box, it works well.
      intelWinSize $config(geometry,textviewer) $w.text increase 1 location bound

      $w.text see insert
    }
    return
  }
  # must be end of file. If no data, kill the window and use a popup...
  LogStatusOnly [_ "...done"]
  set loc [$w.text index end-1chars]
  if {[$w.text index insert] == 1.0} {
    destroy $w
    if {$ext == "com"} {
      PopInfo [_ "No output"]
    } else {
      PopInfo [_ "No files found"]
    }
    return
  }
  # At this point we have a list of found files
  # If ext is -1 we just remove the stop button and
  # sort the files
  # Otherwise, ext will be 1 if we are to send all files
  # to the command and 0 if we are to send them one at a time
  # In either case we use pipeo an give a running result
  # But first lets set up a more appropiate window size

  # This location for the following two lines is much more relaxed,
  # but on long searches it scrunches up the window.
  
  # lassign [intelWinSize  "75x5" $config(geometry,textviewer)
  # [$w.text get 0.0 end]] width height
  # wm geo $w [getGeo g${width}x$height $w -win $w.text]
  frputs "Phase 2?  " ext
  lassign $ext all cmd
  if {$all == 0 && $cmd == {}} {
     findSort $w.text f
  } else {
    if {$all == "com"} {
      incr ::[set w]_flag
      return
    }
    # if {[string first {%s} $cmd] == -1} {
    #   append cmd " %s"
    # }
    # get the file, remove any error lines...
    set files {}
    foreach fl [split [$w.text get 0.0 end] \n] {
      if {$fl == {} || [string index $fl 0] == " "} {continue}
      lappend files $fl
    }
    frputs "phase 2  " files
    $w.text delete 0.0 end
    set Log [list findlog [list findData [list [list com [pwd]]] $w]]
    # foreach fl $files {
    # 	lappend fll [FixFileNameO [file native $fl] 3 { }]
    # }
    foreach fl $files {
      if {$all == 1} {
	set fl $files
	# set fl [join $fll " "]
      } else {
	set fl [list $fl]
      }
      # set realCmd  "[ReSpaceString {} [format $cmd [braceToQuote $fl]]]\n"
      set realCmd [frECF [list {*}[subst {*}$::stOps $cmd]] $fl -f 1]
      frputs realCmd
      # set realCmd [subst -noc -nov $realCmd]
      StopProgress $w $realCmd
      frputs realCmd  fl
      lassign [pipeoExec $realCmd r $Log] r fid 
      if {$r != 0} {
	destroy $w
	error [_ "Bad find call: %s \n %s" $fid  $cmd]
      }
      stopReSetCallBack $w "pipeoAbort $fid"
      # wait for completion...
      vwait ::[set w]_flag
      if {$all == 1} {break}
    }
    catch "unset ::[set w]_flag" 
  }
  StopButRemove $w

}

proc findlog {fid tag} {Log "Find log: $tag"}


proc CmdGetHttp { inst } {
  global glob config
  
  if {[IsVFS $glob($inst,pwd)]} {
    PopInfo [_ "You can only download HTTP files to a non-VFS directory"]
    return
  }

  if {![info exists glob(http,lasturl)]} {
    set glob(http,lasturl) {}
  }
  set URL $glob(http,lasturl)

  while { 1 } {
    set URL [simple_smart_dialog "." [_ "Get HTTP File"] \
		 [_ "Please enter HTTP URL to download"] $URL ]
    if {$URL == ""} {
      return
    }
    if {![regexp {^https?://} $URL]} {
      set URL "http://$URL"
    }
    set glob(http,lasturl) $URL

    if {![IsVFS $URL] || $VFSpro ni {http https}} {
      PopError [_ "Could not parse %s as an HTTP(s) URL" $URL]
      continue
    } 
    if {$VFSadd == {}} {
      append URL /
    }
    break
  }

  set filename [file tail $URL]
  if {[string range $URL end end] == "/" && [file ext $filename] == {}} {
    append filename .html
  }
  set filename [simple_smart_dialog "." [_ "Get HTTP File"] \
		    [_ "Please edit filename to save to.\n(URL: %s)" $URL] \
		    $filename]
  if {$filename == ""} {
    return
  }
  cd $glob($inst,pwd)
  HTTP_Get $URL $filename
  UpdateWindow both
}

