//
// Copyright (c) 2012, Brian Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 25 Aug 12 Brian Frank Creation
//
using gfx
using fwt
using compiler
using syntax
using concurrent
**
** Console
**
class Console : InsetPane
{
ConsoleCmd? lastCmd
Menu menu := Menu
{
MenuItem
{
it.text = "Clear console"
it.onAction.add |Event e| {clear}
},
MenuItem
{
it.text = "Copy all text"
it.onAction.add |Event e| {copyText}
},
}
new make(Frame frame) : super(3, 5, 0, 5)
{
this.frame = frame
this.list = ItemList(frame, Item[,])
this.content = BgEdgePane{
left = GridPane{
Button
{
image = Image(`fan://icons/x16/refresh.png`)
onAction.add {redo}
},
Button
{
it.image = Image(`fan://icons/x16/close.png`)
it.onAction.add |evt| {Sys.cur.commands.processWindow.invoke(evt)}
},
Button
{
image = Image(`fan://icons/x16/file.png`)
onAction.add {clear}
},
}
center = this.list
}
this.visible = false
list.onMouseDown.add |Event event|
{
if(event.button == 3)
menu.open(event.widget, event.pos)
}
}
Bool isBusy() { proc != null }
Bool isOpen := false { private set }
Void toggle() { if (isOpen) close; else open }
Void open()
{
isOpen = true
visible = true
frame.updateStatus
parent.relayout
}
Void show(Item[] marks)
{
frame.marks = marks
list.items = marks
open
}
Void append(Item[] marks)
{
marks.each {list.addItem(it)}
list.scrollToLine(list.lineCount)
open
}
Void close()
{
isOpen = false
visible = false
frame.updateStatus
parent.relayout
}
Void highlight(Item? item) { list.highlight = item }
Void log(Str line)
{
list.addItem(Item(line))
}
Void clear()
{
frame.marks = [,]
list.items = frame.marks
}
** Copy all the text from the console to the clipboard
Void copyText()
{
text := ""
list.items.each {text += "${it.dis}\n"}
Desktop.clipboard.setText(text)
}
/*** kill and wait for kill to be complete
** Return wether kill succeeded
Bool killAndWait()
{
if(proc == null)
return true
kill
start := DateTime.now
while(proc != null)
{
if(DateTime.now - start > 10sec)
break
Actor.sleep(100ms)
}
if(proc != null)
{
append([Item.makeStr("Oooops ... could not terminate the process !")
.setIcon(Sys.cur.theme.iconErr)])
return false
}
return true
}
Void kill()
{
if (proc == null)
return
this.inKill = true
log("Stopping.")
proc.kill
}*/
Void redo()
{
if(lastCmd != null)
{
exec(lastCmd)
}
}
Void exec(ConsoleCmd cmd)
{
lastCmd = cmd
open
frame.marks = Item[,]
this.inKill = false
this.proc = ConsoleProcess(this, cmd.itemFinder)
this.onDone = cmd.onDone
log("Running: $cmd.args in $cmd.dir.osPath")
proc.spawn(cmd.args, cmd.dir)
}
internal Void procDone(Int result)
{
// ** WARNING: using **Any** fwt/console access methods here can cause lockups
// Even if using Deskop.callAsync
lastResult = result
proc = null
if (onDone != null)
{
try
onDone(this)
catch (Err e){}
}
inKill = false
onDone = null
Sys.log.info("Process completed")
}
Frame frame { private set }
ItemList list { private set}
Int lastResult := 0
private ConsoleProcess? proc
private Bool inKill
private |Console|? onDone
}
const class ConsoleCmd
{
const Str[] args
const File dir
** Will be called back when done
const |Console|? onDone := null
** Create fileItem for given error lines (ie: errors)
const |Str -> Item?|? itemFinder := null
new make(|This|? f) {f(this)}
}
**************************************************************************
** ConsoleProcess
**************************************************************************
internal const class ConsoleProcess
{
const |Str -> Item?|? itemFinder := null
new make(Console console, |Str -> Item?|? itemFinder := null)
{
Actor.locals["console"] = console
actor = Actor(ActorPool()) |msg| { receive(msg) }
this.itemFinder = itemFinder
}
Void spawn(Str[] cmd, File dir)
{
actor.send(Msg("spawn", cmd, dir, Unsafe(console)))
}
Console console()
{
Actor.locals["console"] ?: throw Err("Missing 'console' actor locale")
}
Void kill()
{
proc := (Process)((Unsafe)procRef.val).val
proc.kill
}
Void writeLines(Str[] lines)
{
frame := console.frame
lines.each |line|
{
try
{
item := itemFinder?.call(line) ?: Item(line)
console.list.addItem(item)
if ((item is FileItem))
frame.marks = frame.marks.dup.add(item)
}
catch (Err e)
{
console.list.addItem(Item(line))
e.trace
}
}
console.list.scrollToLine(console.list.lineCount)
}
private Obj? receive(Msg msg)
{
if (msg.id == "spawn") return doSpawn(msg.a, msg.b, msg.c)
Sys.log.info("WARNING: unknown msg: $msg")
throw Err("unknown msg $msg")
}
private Obj? doSpawn(Str[] cmd, File dir, Unsafe c)
{
Int result := -1
try
{
if( Desktop.isWindows && ! cmd.isEmpty && ! cmd[0].endsWith(".exe"))
cmd = [cmd[0] + ".exe"].addAll(cmd[1 .. -1])
proc := Process(cmd, dir)
procRef.val = Unsafe(proc)
proc.out = ConsoleOutStream(this)
cons := c.val as Console
id := Sys.cur.processManager.register(proc)
try
result = proc.run.join
finally
Sys.cur.processManager.unregister(id)
}
catch (Err e)
{
e.trace
cons := c.val as Console
cons.log(e.traceToStr)
return null
}
finally
{
cons := c.val as Console
cons.procDone(result)
}
return null
}
private const Actor actor
private const AtomicRef procRef := AtomicRef(null)
}
**************************************************************************
** ConsoleOutStream
**************************************************************************
internal class ConsoleOutStream : OutStream
{
new make(ConsoleProcess proc) : super(null) { this.proc = proc }
const ConsoleProcess proc
override This write(Int b)
{
append(Buf().write(b).flip.readAllStr)
return this
}
override This writeBuf(Buf b, Int n := b.remaining)
{
append(Buf().writeBuf(b, n).flip.readAllStr)
return this
}
Void append(Str str)
{
proc := this.proc
curStr = curStr + str
lines := curStr.splitLines
if (lines.size <= 1) return
Desktop.callAsync |->| { proc.writeLines(lines[0..-2]) }
curStr = lines.last
}
Str curStr := ""
}