src/Pure/PIDE/editor.scala
author wenzelm
Thu, 07 Aug 2025 22:42:21 +0200
changeset 82968 b2b88d5b01b6
parent 82949 728762181377
permissions -rw-r--r--
update to jdk-21.0.8; enforce rebuild of Isabelle/ML and Isabelle/Scala;

/*  Title:      Pure/PIDE/editor.scala
    Author:     Makarius

General editor operations.
*/

package isabelle


object Editor {
  /* output messages */

  object Output {
    val none: Output = Output(defined = false)
    val init: Output = Output()
  }

  sealed case class Output(
    results: Command.Results = Command.Results.empty,
    messages: List[XML.Elem] = Nil,
    defined: Boolean = true
  )
}

abstract class Editor[Context] {
  /* PIDE session and document model */

  def session: Session
  def flush(): Unit
  def invoke(): Unit

  def get_models(): Iterable[Document.Model]


  /* document editor */

  protected val document_editor: Synchronized[Document_Editor.State] =
    Synchronized(Document_Editor.State())

  protected def document_state(): Document_Editor.State = document_editor.value

  protected def document_state_changed(): Unit = {}
  private def document_state_change(f: Document_Editor.State => Document_Editor.State): Unit = {
    val changed =
      document_editor.change_result { st =>
        val st1 = f(st)
        val changed =
          st.active_document_theories != st1.active_document_theories ||
          st.selection != st1.selection
        (changed, st1)
      }
    if (changed) document_state_changed()
  }

  def document_session(): Document_Editor.Session =
    document_state().session(session)

  def document_required(): List[Document.Node.Name] = {
    val st = document_state()
    if (st.is_active) {
      for {
        a <- st.all_document_theories
        b = session.resources.migrate_name(a)
        if st.selection(b.theory)
      } yield b
    }
    else Nil
  }

  def document_node_required(name: Document.Node.Name): Boolean = {
    val st = document_state()
    st.is_active &&
    st.selection.contains(name.theory) &&
    st.all_document_theories.exists(a => a.theory == name.theory)
  }

  def document_theories(): List[Document.Node.Name] =
    document_state().active_document_theories.map(session.resources.migrate_name)

  def document_selection(): Set[String] = document_state().selection

  def document_setup(background: Option[Sessions.Background]): Unit =
    document_state_change(_.copy(session_background = background))

  def document_select(
    theories: Iterable[String],
    set: Boolean = false,
    toggle: Boolean = false
  ): Unit = document_state_change(_.select(theories, set = set, toggle = toggle))

  def document_select_all(set: Boolean = false): Unit =
    document_state_change(st =>
      st.select(st.active_document_theories.map(_.theory), set = set))

  def document_init(id: AnyRef): Unit = document_state_change(_.register_view(id))
  def document_exit(id: AnyRef): Unit = document_state_change(_.unregister_view(id))


  /* current situation */

  def current_node(context: Context): Option[Document.Node.Name]
  def current_node_snapshot(context: Context): Option[Document.Snapshot]
  def node_snapshot(name: Document.Node.Name): Document.Snapshot
  def current_command(context: Context, snapshot: Document.Snapshot): Option[Command]


  /* output messages */

  def output_state(): Boolean

  def output(
    snapshot: Document.Snapshot,
    caret_offset: Text.Offset,
    restriction: Option[Set[Command]] = None
  ): Editor.Output = {
    if (snapshot.is_outdated) Editor.Output.none
    else {
      val thy_command_range = snapshot.loaded_theory_command(caret_offset)
      val thy_command = thy_command_range.map(_._1)

      def filter(msg: XML.Elem): Boolean =
        (for {
          (command, command_range) <- thy_command_range
          msg_offset <- Position.Offset.unapply(msg.markup.properties)
        } yield command_range.contains(command.chunk.decode(msg_offset))) getOrElse true

      thy_command orElse snapshot.current_command(snapshot.node_name, caret_offset) match {
        case None => Editor.Output.init
        case Some(command) =>
          if (thy_command.isDefined || restriction.isEmpty || restriction.get.contains(command)) {
            val results = snapshot.command_results(command)
            val messages = {
              val (states, other) = {
                List.from(
                  for ((_, msg) <- results.iterator if !Protocol.is_result(msg) && filter(msg))
                    yield msg).partition(Protocol.is_state)
              }
              val (urgent, regular) = other.partition(Protocol.is_urgent)
              urgent ::: (if (output_state()) states else Nil) ::: regular
            }
            Editor.Output(results = results, messages = messages)
          }
          else Editor.Output.none
      }
    }
  }


  /* overlays */

  def node_overlays(name: Document.Node.Name): Document.Node.Overlays
  def insert_overlay(command: Command, fn: String, args: List[String]): Unit
  def remove_overlay(command: Command, fn: String, args: List[String]): Unit


  /* hyperlinks */

  abstract class Hyperlink {
    def external: Boolean = false
    def follow(context: Context): Unit
  }

  def hyperlink_command(
    focus: Boolean, snapshot: Document.Snapshot, id: Document_ID.Generic, offset: Symbol.Offset = 0)
      : Option[Hyperlink]


  /* dispatcher thread */

  def assert_dispatcher[A](body: => A): A
  def require_dispatcher[A](body: => A): A
  def send_dispatcher(body: => Unit): Unit
  def send_wait_dispatcher(body: => Unit): Unit
}