/* Title: Pure/PIDE/query_operation.scala
Author: Makarius
One-shot query operations via asynchronous print functions and temporary
document overlays.
*/
package isabelle
object Query_Operation
{
object Status extends Enumeration
{
val WAITING = Value("waiting")
val RUNNING = Value("running")
val FINISHED = Value("finished")
}
object State
{
val empty: State = State()
def make(command: Command, query: List[String]): State =
State(instance = Document_ID.make().toString,
location = Some(command),
query = query,
status = Status.WAITING)
}
sealed case class State(
instance: String = Document_ID.none.toString,
location: Option[Command] = None,
query: List[String] = Nil,
update_pending: Boolean = false,
output: List[XML.Tree] = Nil,
status: Status.Value = Status.FINISHED,
exec_id: Document_ID.Exec = Document_ID.none)
}
class Query_Operation[Editor_Context](
editor: Editor[Editor_Context],
editor_context: Editor_Context,
operation_name: String,
consume_status: Query_Operation.Status.Value => Unit,
consume_output: (Document.Snapshot, Command.Results, XML.Body) => Unit)
{
private val print_function = operation_name + "_query"
/* implicit state -- owned by editor thread */
private val current_state = Synchronized(Query_Operation.State.empty)
def get_location: Option[Command] = current_state.value.location
private def remove_overlay()
{
val state = current_state.value
for (command <- state.location) {
editor.remove_overlay(command, print_function, state.instance :: state.query)
}
}
/* content update */
private def content_update()
{
editor.require_dispatcher {}
/* snapshot */
val state0 = current_state.value
val (snapshot, command_results, results, removed) =
state0.location match {
case Some(cmd) =>
val snapshot = editor.node_snapshot(cmd.node_name)
val command_results = snapshot.command_results(cmd)
val results =
(for {
(_, elem @ XML.Elem(Markup(Markup.RESULT, props), _)) <- command_results.iterator
if props.contains((Markup.INSTANCE, state0.instance))
} yield elem).toList
val removed = !snapshot.version.nodes(cmd.node_name).commands.contains(cmd)
(snapshot, command_results, results, removed)
case None =>
(Document.Snapshot.init, Command.Results.empty, Nil, true)
}
/* resolve sendback: static command id */
def resolve_sendback(body: XML.Body): XML.Body =
{
state0.location match {
case None => body
case Some(command) =>
def resolve(body: XML.Body): XML.Body =
body map {
case XML.Wrapped_Elem(m, b1, b2) => XML.Wrapped_Elem(m, resolve(b1), resolve(b2))
case XML.Elem(Markup(Markup.SENDBACK, props), b) =>
val props1 =
props.map({
case (Markup.ID, Value.Long(id)) if id == state0.exec_id =>
(Markup.ID, Value.Long(command.id))
case p => p
})
XML.Elem(Markup(Markup.SENDBACK, props1), resolve(b))
case XML.Elem(m, b) => XML.Elem(m, resolve(b))
case t => t
}
resolve(body)
}
}
/* output */
val new_output =
for {
XML.Elem(_, List(XML.Elem(markup, body))) <- results
if Markup.messages.contains(markup.name)
body1 = resolve_sendback(body)
} yield XML.Elem(Markup(Markup.message(markup.name), markup.properties), body1)
/* status */
def get_status(name: String, status: Query_Operation.Status.Value)
: Option[Query_Operation.Status.Value] =
results.collectFirst({ case XML.Elem(_, List(elem: XML.Elem)) if elem.name == name => status })
val new_status =
if (removed) Query_Operation.Status.FINISHED
else
get_status(Markup.FINISHED, Query_Operation.Status.FINISHED) orElse
get_status(Markup.RUNNING, Query_Operation.Status.RUNNING) getOrElse
Query_Operation.Status.WAITING
/* state update */
if (new_status == Query_Operation.Status.RUNNING)
results.collectFirst(
{
case XML.Elem(Markup(_, Position.Id(id)), List(elem: XML.Elem))
if elem.name == Markup.RUNNING => id
}).foreach(id => current_state.change(_.copy(exec_id = id)))
if (state0.output != new_output || state0.status != new_status) {
if (snapshot.is_outdated)
current_state.change(_.copy(update_pending = true))
else {
current_state.change(_.copy(update_pending = false))
if (state0.output != new_output && !removed) {
current_state.change(_.copy(output = new_output))
consume_output(snapshot, command_results, new_output)
}
if (state0.status != new_status) {
current_state.change(_.copy(status = new_status))
consume_status(new_status)
if (new_status == Query_Operation.Status.FINISHED)
remove_overlay()
}
}
}
}
/* query operations */
def cancel_query(): Unit =
editor.require_dispatcher { editor.session.cancel_exec(current_state.value.exec_id) }
def apply_query(query: List[String])
{
editor.require_dispatcher {}
editor.current_node_snapshot(editor_context) match {
case Some(snapshot) =>
remove_overlay()
current_state.change(_ => Query_Operation.State.empty)
consume_output(Document.Snapshot.init, Command.Results.empty, Nil)
editor.current_command(editor_context, snapshot) match {
case Some(command) =>
val state = Query_Operation.State.make(command, query)
current_state.change(_ => state)
editor.insert_overlay(command, print_function, state.instance :: query)
case None =>
}
consume_status(current_state.value.status)
editor.flush()
case None =>
}
}
def locate_query()
{
editor.require_dispatcher {}
val state = current_state.value
for {
command <- state.location
snapshot = editor.node_snapshot(command.node_name)
link <- editor.hyperlink_command(true, snapshot, command.id)
} link.follow(editor_context)
}
/* main */
private val main =
Session.Consumer[Session.Commands_Changed](getClass.getName) {
case changed =>
val state = current_state.value
state.location match {
case Some(command)
if state.update_pending ||
(state.status != Query_Operation.Status.FINISHED &&
changed.commands.contains(command)) =>
editor.send_dispatcher { content_update() }
case _ =>
}
}
def activate() {
editor.session.commands_changed += main
}
def deactivate() {
editor.session.commands_changed -= main
remove_overlay()
current_state.change(_ => Query_Operation.State.empty)
consume_output(Document.Snapshot.init, Command.Results.empty, Nil)
consume_status(Query_Operation.Status.FINISHED)
}
}