src/Tools/VSCode/src/state_panel.scala
author Thomas Lindae <thomas.lindae@in.tum.de>
Wed, 01 May 2024 12:34:53 +0200
changeset 81025 d4eb94b46e83
parent 81023 8602af6f4bd0
child 81028 84f6f17274d0
permissions -rw-r--r--
lsp: added State and Dynamic Output html_output and margin handling;

/*  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: Language_Server): Unit = {
    val instance = new State_Panel(server)
    instances.change(_ + (instance.id -> instance))
    instance.init()
    instance.init_response()
  }

  def exit(id: Counter.ID): Unit = {
    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: Language_Server) {
  /* output */

  val id: Counter.ID = State_Panel.make_id()
  private val margin: Double = 80

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

  private def init_response(): Unit =
    server.channel.write(LSP.State_Init_Response(id))


  /* query operation */

  private val output_active = Synchronized(true)

  private val print_state =
    new Query_Operation(server.editor, (), "print_state", _ => (),
      (_, _, body) =>
        if (output_active.value && body.nonEmpty) {
          if (server.resources.html_output) {
            val node_context =
              new Browser_Info.Node_Context {
                override def make_ref(props: Properties.T, body: XML.Body): Option[XML.Elem] =
                  for {
                    thy_file <- Position.Def_File.unapply(props)
                    def_line <- Position.Def_Line.unapply(props)
                    source <- server.resources.source_file(thy_file)
                    uri = File.uri(Path.explode(source).absolute_file)
                  } yield HTML.link(uri.toString + "#" + def_line, body)
              }
            val elements = Browser_Info.extra_elements.copy(entity = Markup.Elements.full)
            val separate = Pretty.separate(body)
            val formatted = Pretty.formatted(separate, margin = margin)
            val html = node_context.make_html(elements, formatted)
            output(HTML.source(html).toString)
          } else {
            output(server.resources.output_pretty(body, margin))
          }
        })

  def locate(): Unit = print_state.locate_query()

  def update(): Unit = {
    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): Unit = {
    val enabled =
      auto_update_enabled.guarded_access(a =>
        set match {
          case None => Some((a, a))
          case Some(b) => Some((b, b))
        })
    if (enabled) update()
  }



  /* 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(): Unit = {
    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(): Unit = {
    output_active.change(_ => false)
    server.session.commands_changed -= main
    server.session.caret_focus -= main
    server.editor.send_wait_dispatcher { print_state.deactivate() }
  }
}