src/Tools/VSCode/src/state_panel.scala
author wenzelm
Thu, 29 Jun 2017 11:58:00 +0200
changeset 66213 9380ec9babfb
parent 66212 f41396c15bb1
child 66397 f55d2e2c2ca0
permissions -rw-r--r--
proper dynamic controls, notably for auto_update_enabled;

/*  Title:      Tools/VSCode/src/state_panel.scala
    Author:     Makarius

Show proof state.
*/

package isabelle.vscode


import isabelle._


object State_Panel
{
  private val make_id = Counter.make()
  private val instances = Synchronized(Map.empty[Counter.ID, State_Panel])

  def init(server: Server)
  {
    val instance = new State_Panel(server)
    instances.change(_ + (instance.id -> instance))
    instance.init()
  }

  def exit(id: Counter.ID)
  {
    instances.change(map =>
      map.get(id) match {
        case None => map
        case Some(instance) => instance.exit(); map - id
      })
  }

  def locate(id: Counter.ID): Unit =
    instances.value.get(id).foreach(state =>
      state.server.editor.send_dispatcher(state.locate()))

  def update(id: Counter.ID): Unit =
    instances.value.get(id).foreach(state =>
      state.server.editor.send_dispatcher(state.update()))

  def auto_update(id: Counter.ID, enabled: Boolean): Unit =
    instances.value.get(id).foreach(state =>
      state.server.editor.send_dispatcher(state.auto_update(Some(enabled))))
}


class State_Panel private(val server: Server)
{
  /* output */

  val id: Counter.ID = State_Panel.make_id()

  private def output(content: String): Unit =
    server.channel.write(Protocol.State_Output(id, content))


  /* query operation */

  private val print_state =
    new Query_Operation(server.editor, (), "print_state", _ => (),
      (snapshot, results, body) =>
        {
          val text = server.resources.output_pretty_message(Pretty.separate(body))
          val content =
            HTML.output_document(
              List(HTML.style(HTML.fonts_css()),
                HTML.style_file(HTML.isabelle_css),
                HTML.script(controls_script)),
              List(controls, HTML.source(text)),
              css = "")
          output(content)
        })

  def locate() { print_state.locate_query() }

  def update()
  {
    server.editor.current_node_snapshot(()) match {
      case Some(snapshot) =>
        (server.editor.current_command((), snapshot), print_state.get_location) match {
          case (Some(command1), Some(command2)) if command1.id == command2.id =>
          case _ => print_state.apply_query(Nil)
        }
      case None =>
    }
  }


  /* auto update */

  private val auto_update_enabled = Synchronized(true)

  def auto_update(set: Option[Boolean] = None)
  {
    val enabled =
      auto_update_enabled.guarded_access(a =>
        set match {
          case None => Some((a, a))
          case Some(b) => Some((b, b))
        })
    if (enabled) update()
  }


  /* controls */

  private def controls_script =
    VSCode_JavaScript.invoke_command +
"""
function invoke_auto_update(enabled)
{ invoke_command("_isabelle.state-auto-update", [""" + id + """, enabled]) }

function invoke_update() { invoke_command("_isabelle.state-update", [""" + id + """]) }

function invoke_locate() { invoke_command("_isabelle.state-locate", [""" + id + """]) }
"""

  private def auto_update_button: XML.Elem =
    HTML.GUI.checkbox(HTML.text("Auto update"),
      name = "auto_update",
      tooltip = "Indicate automatic update following cursor movement",
      selected = auto_update_enabled.value,
      script = "invoke_auto_update(this.checked)")

  private def update_button: XML.Elem =
    HTML.GUI.button(List(HTML.bold(HTML.text("Update"))),
      name = "update",
      tooltip = "Update display according to the command at cursor position",
      script = "invoke_update()")

  private def locate_button: XML.Elem =
    HTML.GUI.button(HTML.text("Locate"),
      name = "locate",
      tooltip = "Locate printed command within source text",
      script = "invoke_locate()")

  private def controls: XML.Elem =
    HTML.Wrap_Panel(List(auto_update_button, update_button, locate_button))


  /* main */

  private val main =
    Session.Consumer[Any](getClass.getName) {
      case changed: Session.Commands_Changed =>
        if (changed.assignment) auto_update()

      case Session.Caret_Focus =>
        auto_update()
    }

  def init()
  {
    server.session.commands_changed += main
    server.session.caret_focus += main
    server.editor.send_wait_dispatcher { print_state.activate() }
    server.editor.send_dispatcher { auto_update() }
  }

  def exit()
  {
    server.editor.send_wait_dispatcher { print_state.deactivate() }
    server.session.commands_changed -= main
    server.session.caret_focus -= main
  }
}