explicit Change.Snapshot and Document.Node;
authorwenzelm
Thu, 05 Aug 2010 16:58:18 +0200
changeset 38151 2837c952ca31
parent 38150 67fc24df3721
child 38152 eab0d1c2e46e
explicit Change.Snapshot and Document.Node; misc tuning and clarification; Document_View: explicitly highlight outdated command status;
src/Pure/PIDE/change.scala
src/Pure/PIDE/document.scala
src/Tools/jEdit/src/jedit/document_model.scala
src/Tools/jEdit/src/jedit/document_view.scala
src/Tools/jEdit/src/jedit/isabelle_hyperlinks.scala
src/Tools/jEdit/src/jedit/isabelle_sidekick.scala
src/Tools/jEdit/src/jedit/isabelle_token_marker.scala
src/Tools/jEdit/src/jedit/output_dockable.scala
--- a/src/Pure/PIDE/change.scala	Thu Aug 05 14:35:35 2010 +0200
+++ b/src/Pure/PIDE/change.scala	Thu Aug 05 16:58:18 2010 +0200
@@ -2,7 +2,7 @@
     Author:     Fabian Immler, TU Munich
     Author:     Makarius
 
-Changes of plain text.
+Changes of plain text, resulting in document edits.
 */
 
 package isabelle
@@ -11,14 +11,25 @@
 object Change
 {
   val init = new Change(Document.NO_ID, None, Nil, Future.value(Nil, Document.init))
+
+  abstract class Snapshot
+  {
+    val latest_version: Change
+    val stable_version: Change
+    val document: Document
+    val node: Document.Node
+    def is_outdated: Boolean = stable_version != latest_version
+  }
 }
 
 class Change(
   val id: Document.Version_ID,
   val parent: Option[Change],
-  val edits: List[Document.Node.Text_Edit],
+  val edits: List[Document.Node_Text_Edit],
   val result: Future[(List[Document.Edit[Command]], Document)])
 {
+  /* ancestor versions */
+
   def ancestors: Iterator[Change] = new Iterator[Change]
   {
     private var state: Option[Change] = Some(Change.this)
@@ -30,10 +41,10 @@
       }
   }
 
-  def join_document: Document = result.join._2
-  def is_assigned: Boolean = result.is_finished && join_document.assignment.is_finished
 
-  def edit(session: Session, edits: List[Document.Node.Text_Edit]): Change =
+  /* editing and state assignment */
+
+  def edit(session: Session, edits: List[Document.Node_Text_Edit]): Change =
   {
     val new_id = session.create_id()
     val result: Future[(List[Document.Edit[Command]], Document)] =
@@ -44,4 +55,21 @@
       }
     new Change(new_id, Some(this), edits, result)
   }
+
+  def join_document: Document = result.join._2
+  def is_assigned: Boolean = result.is_finished && join_document.assignment.is_finished
+
+
+  /* snapshot */
+
+  def snapshot(name: String): Change.Snapshot =
+  {
+    val latest = this
+    new Change.Snapshot {
+      val latest_version = latest
+      val stable_version: Change = latest.ancestors.find(_.is_assigned).get
+      val document: Document = stable_version.join_document
+      val node: Document.Node = document.nodes(name)
+    }
+  }
 }
\ No newline at end of file
--- a/src/Pure/PIDE/document.scala	Thu Aug 05 14:35:35 2010 +0200
+++ b/src/Pure/PIDE/document.scala	Thu Aug 05 16:58:18 2010 +0200
@@ -23,15 +23,6 @@
   val NO_ID = ""
 
 
-  /* nodes */
-
-  object Node { type Text_Edit = (String, List[isabelle.Text_Edit]) }  // FIXME None: remove
-
-  type Edit[C] =
-   (String,  // node name
-    Option[List[(Option[C], Option[C])]])  // None: remove, Some: insert/remove commands
-
-
   /* command start positions */
 
   def command_starts(commands: Iterator[Command], offset: Int = 0): Iterator[(Command, Int)] =
@@ -45,21 +36,65 @@
   }
 
 
+  /* named document nodes */
+
+  object Node
+  {
+    val empty: Node = new Node(Linear_Set())
+  }
+
+  class Node(val commands: Linear_Set[Command])
+  {
+    /* command ranges */
+
+    def command_starts: Iterator[(Command, Int)] =
+      Document.command_starts(commands.iterator)
+
+    def command_start(cmd: Command): Option[Int] =
+      command_starts.find(_._1 == cmd).map(_._2)
+
+    def command_range(i: Int): Iterator[(Command, Int)] =
+      command_starts dropWhile { case (cmd, start) => start + cmd.length <= i }
+
+    def command_range(i: Int, j: Int): Iterator[(Command, Int)] =
+      command_range(i) takeWhile { case (_, start) => start < j }
+
+    def command_at(i: Int): Option[(Command, Int)] =
+    {
+      val range = command_range(i)
+      if (range.hasNext) Some(range.next) else None
+    }
+
+    def proper_command_at(i: Int): Option[Command] =
+      command_at(i) match {
+        case Some((command, _)) =>
+          commands.reverse_iterator(command).find(cmd => !cmd.is_ignored)
+        case None => None
+      }
+  }
+
+
   /* initial document */
 
   val init: Document =
   {
-    val doc = new Document(NO_ID, Map().withDefaultValue(Linear_Set()), Map())
+    val doc = new Document(NO_ID, Map().withDefaultValue(Node.empty), Map())
     doc.assign_states(Nil)
     doc
   }
 
 
 
-  /** document edits **/
+  /** editing **/
+
+  type Node_Text_Edit = (String, List[Text_Edit])  // FIXME None: remove
+
+  type Edit[C] =
+   (String,  // node name
+    Option[List[(Option[C], Option[C])]])  // None: remove, Some: insert/remove commands
 
   def text_edits(session: Session, old_doc: Document, new_id: Version_ID,
-      edits: List[Node.Text_Edit]): (List[Edit[Command]], Document) =
+      edits: List[Node_Text_Edit]): (List[Edit[Command]], Document) =
   {
     require(old_doc.assignment.is_finished)
 
@@ -145,7 +180,7 @@
       var former_states = old_doc.assignment.join
 
       for ((name, text_edits) <- edits) {
-        val commands0 = nodes(name)
+        val commands0 = nodes(name).commands
         val commands1 = edit_text(text_edits, commands0)
         val commands2 = parse_spans(commands1)
 
@@ -157,7 +192,7 @@
           inserted_commands.map(cmd => (commands2.prev(cmd), Some(cmd)))
 
         doc_edits += (name -> Some(cmd_edits))
-        nodes += (name -> commands2)
+        nodes += (name -> new Node(commands2))
         former_states --= removed_commands
       }
       (doc_edits.toList, new Document(new_id, nodes, former_states))
@@ -168,39 +203,9 @@
 
 class Document(
     val id: Document.Version_ID,
-    val nodes: Map[String, Linear_Set[Command]],
+    val nodes: Map[String, Document.Node],
     former_states: Map[Command, Command])  // FIXME !?
 {
-  /* command ranges */
-
-  def commands(name: String): Linear_Set[Command] = nodes.get(name) getOrElse Linear_Set()
-
-  def command_starts(name: String): Iterator[(Command, Int)] =
-    Document.command_starts(commands(name).iterator)
-
-  def command_start(name: String, cmd: Command): Option[Int] =
-    command_starts(name).find(_._1 == cmd).map(_._2)
-
-  def command_range(name: String, i: Int): Iterator[(Command, Int)] =
-    command_starts(name) dropWhile { case (cmd, start) => start + cmd.length <= i }
-
-  def command_range(name: String, i: Int, j: Int): Iterator[(Command, Int)] =
-    command_range(name, i) takeWhile { case (_, start) => start < j }
-
-  def command_at(name: String, i: Int): Option[(Command, Int)] =
-  {
-    val range = command_range(name, i)
-    if (range.hasNext) Some(range.next) else None
-  }
-
-  def proper_command_at(name: String, i: Int): Option[Command] =
-    command_at(name, i) match {
-      case Some((command, _)) =>
-        commands(name).reverse_iterator(command).find(cmd => !cmd.is_ignored)
-      case None => None
-    }
-
-
   /* command state assignment */
 
   val assignment = Future.promise[Map[Command, Command]]
--- a/src/Tools/jEdit/src/jedit/document_model.scala	Thu Aug 05 14:35:35 2010 +0200
+++ b/src/Tools/jEdit/src/jedit/document_model.scala	Thu Aug 05 16:58:18 2010 +0200
@@ -54,6 +54,7 @@
   }
 }
 
+
 class Document_Model(val session: Session, val buffer: Buffer)
 {
   /* visible line end */
@@ -77,7 +78,7 @@
   @volatile private var history = Change.init // owned by Swing thread
 
   def current_change(): Change = history
-  def recent_document(): Document = current_change().ancestors.find(_.is_assigned).get.join_document
+  def snapshot(): Change.Snapshot = current_change().snapshot(thy_name)
 
 
   /* transforming offsets */
--- a/src/Tools/jEdit/src/jedit/document_view.scala	Thu Aug 05 14:35:35 2010 +0200
+++ b/src/Tools/jEdit/src/jedit/document_view.scala	Thu Aug 05 16:58:18 2010 +0200
@@ -24,10 +24,11 @@
 
 object Document_View
 {
-  def choose_color(document: Document, command: Command): Color =
+  def choose_color(snapshot: Change.Snapshot, command: Command): Color =
   {
-    val state = document.current_state(command)
-    if (state.forks > 0) new Color(255, 228, 225)
+    val state = snapshot.document.current_state(command)
+    if (snapshot.is_outdated) new Color(240, 240, 240)
+    else if (state.forks > 0) new Color(255, 228, 225)
     else if (state.forks < 0) Color.red
     else
       state.status match {
@@ -105,9 +106,9 @@
         case Command_Set(changed) =>
           Swing_Thread.now {
             // FIXME cover doc states as well!!?
-            val document = model.recent_document()
-            if (changed.exists(document.commands(model.thy_name).contains))
-              full_repaint(document, changed)
+            val snapshot = model.snapshot()
+            if (changed.exists(snapshot.node.commands.contains))
+              full_repaint(snapshot, changed)
           }
 
         case bad => System.err.println("command_change_actor: ignoring bad message " + bad)
@@ -115,14 +116,16 @@
     }
   }
 
-  def full_repaint(document: Document, changed: Set[Command])
+  def full_repaint(snapshot: Change.Snapshot, changed: Set[Command])
   {
     Swing_Thread.assert()
 
+    val document = snapshot.document
+    val doc = snapshot.node
     val buffer = model.buffer
     var visible_change = false
 
-    for ((command, start) <- document.command_starts(model.thy_name)) {
+    for ((command, start) <- doc.command_starts) {
       if (changed(command)) {
         val stop = start + command.length
         val line1 = buffer.getLineOfOffset(model.to_current(document, start))
@@ -148,18 +151,19 @@
       start: Array[Int], end: Array[Int], y0: Int, line_height: Int)
     {
       Swing_Thread.now {
-        val document = model.recent_document()
+        val snapshot = model.snapshot()
+        val document = snapshot.document
+        val doc = snapshot.node
         def from_current(pos: Int) = model.from_current(document, pos)
         def to_current(pos: Int) = model.to_current(document, pos)
 
         val command_range: Iterable[(Command, Int)] =
         {
-          val range = document.command_range(model.thy_name, from_current(start(0)))
+          val range = doc.command_range(from_current(start(0)))
           if (range.hasNext) {
             val (cmd0, start0) = range.next
             new Iterable[(Command, Int)] {
-              def iterator =
-                Document.command_starts(document.commands(model.thy_name).iterator(cmd0), start0)
+              def iterator = Document.command_starts(doc.commands.iterator(cmd0), start0)
             }
           }
           else Iterable.empty
@@ -183,7 +187,7 @@
                 val p = text_area.offsetToXY(line_start max to_current(command_start))
                 val q = text_area.offsetToXY(line_end min to_current(command_start + command.length))
                 assert(p.y == q.y)
-                gfx.setColor(Document_View.choose_color(document, command))
+                gfx.setColor(Document_View.choose_color(snapshot, command))
                 gfx.fillRect(p.x, y, q.x - p.x, line_height)
               }
             }
@@ -196,9 +200,11 @@
 
     override def getToolTipText(x: Int, y: Int): String =
     {
-      val document = model.recent_document()
+      val snapshot = model.snapshot()
+      val document = snapshot.document
+      val doc = snapshot.node
       val offset = model.from_current(document, text_area.xyToOffset(x, y))
-      document.command_at(model.thy_name, offset) match {
+      doc.command_at(offset) match {
         case Some((command, command_start)) =>
           document.current_state(command).type_at(offset - command_start) match {
             case Some(text) => Isabelle.tooltip(text)
@@ -213,7 +219,7 @@
   /* caret handling */
 
   def selected_command(): Option[Command] =
-    model.recent_document().proper_command_at(model.thy_name, text_area.getCaretPosition)
+    model.snapshot().node.proper_command_at(text_area.getCaretPosition)
 
   private val caret_listener = new CaretListener {
     private val delay = Swing_Thread.delay_last(session.input_delay) {
@@ -263,16 +269,16 @@
     {
       super.paintComponent(gfx)
       val buffer = model.buffer
-      val document = model.recent_document()
-      def to_current(pos: Int) = model.to_current(document, pos)
+      val snapshot = model.snapshot()
+      def to_current(pos: Int) = model.to_current(snapshot.document, pos)
       val saved_color = gfx.getColor  // FIXME needed!?
       try {
-        for ((command, start) <- document.command_starts(model.thy_name) if !command.is_ignored) {
+        for ((command, start) <- snapshot.node.command_starts if !command.is_ignored) {
           val line1 = buffer.getLineOfOffset(to_current(start))
           val line2 = buffer.getLineOfOffset(to_current(start + command.length)) + 1
           val y = line_to_y(line1)
           val height = HEIGHT * (line2 - line1)
-          gfx.setColor(Document_View.choose_color(document, command))
+          gfx.setColor(Document_View.choose_color(snapshot, command))
           gfx.fillRect(0, y, getWidth - 1, height)
         }
       }
--- a/src/Tools/jEdit/src/jedit/isabelle_hyperlinks.scala	Thu Aug 05 14:35:35 2010 +0200
+++ b/src/Tools/jEdit/src/jedit/isabelle_hyperlinks.scala	Thu Aug 05 16:58:18 2010 +0200
@@ -42,9 +42,11 @@
   {
     Document_Model(buffer) match {
       case Some(model) =>
-        val document = model.recent_document()
+        val snapshot = model.snapshot()
+        val document = snapshot.document
+        val doc = snapshot.node
         val offset = model.from_current(document, original_offset)
-        document.command_at(model.thy_name, offset) match {
+        doc.command_at(offset) match {
           case Some((command, command_start)) =>
             document.current_state(command).ref_at(offset - command_start) match {
               case Some(ref) =>
@@ -57,7 +59,7 @@
                   case Command.RefInfo(_, _, Some(id), Some(offset)) =>
                     Isabelle.session.lookup_entity(id) match {
                       case Some(ref_cmd: Command) =>
-                        document.command_start(model.thy_name, ref_cmd) match {
+                        doc.command_start(ref_cmd) match {
                           case Some(ref_cmd_start) =>
                             new Internal_Hyperlink(begin, end, line,
                               model.to_current(document, ref_cmd_start + offset - 1))
--- a/src/Tools/jEdit/src/jedit/isabelle_sidekick.scala	Thu Aug 05 14:35:35 2010 +0200
+++ b/src/Tools/jEdit/src/jedit/isabelle_sidekick.scala	Thu Aug 05 16:58:18 2010 +0200
@@ -95,9 +95,9 @@
     import Isabelle_Sidekick.int_to_pos
 
     val root = data.root
-    val document = model.recent_document()
+    val doc = model.snapshot().node  // FIXME cover all nodes (!??)
     for {
-      (command, command_start) <- document.command_range(model.thy_name, 0)
+      (command, command_start) <- doc.command_range(0)
       if command.is_command && !stopped
     }
     {
@@ -128,8 +128,10 @@
     import Isabelle_Sidekick.int_to_pos
 
     val root = data.root
-    val document = model.recent_document()
-    for ((command, command_start) <- document.command_range(model.thy_name, 0) if !stopped) {
+    val snapshot = model.snapshot()
+    val document = snapshot.document
+    val doc = snapshot.node  // FIXME cover all nodes (!??)
+    for ((command, command_start) <- doc.command_range(0) if !stopped) {
       root.add(document.current_state(command).markup_root.swing_tree((node: Markup_Node) =>
           {
             val content = command.source(node.start, node.stop).replace('\n', ' ')
--- a/src/Tools/jEdit/src/jedit/isabelle_token_marker.scala	Thu Aug 05 14:35:35 2010 +0200
+++ b/src/Tools/jEdit/src/jedit/isabelle_token_marker.scala	Thu Aug 05 16:58:18 2010 +0200
@@ -151,7 +151,9 @@
     val start = model.buffer.getLineStartOffset(line)
     val stop = start + line_segment.count
 
-    val document = model.recent_document()
+    val snapshot = model.snapshot()
+    val document = snapshot.document
+    val doc = snapshot.node
     def to: Int => Int = model.to_current(document, _)
     def from: Int => Int = model.from_current(document, _)
 
@@ -166,7 +168,7 @@
 
     var next_x = start
     for {
-      (command, command_start) <- document.command_range(model.thy_name, from(start), from(stop))
+      (command, command_start) <- doc.command_range(from(start), from(stop))
       markup <- document.current_state(command).highlight.flatten
       val abs_start = to(command_start + markup.start)
       val abs_stop = to(command_start + markup.stop)
--- a/src/Tools/jEdit/src/jedit/output_dockable.scala	Thu Aug 05 14:35:35 2010 +0200
+++ b/src/Tools/jEdit/src/jedit/output_dockable.scala	Thu Aug 05 16:58:18 2010 +0200
@@ -65,7 +65,7 @@
         case Some(doc_view) =>
           current_command match {
             case Some(cmd) if !restriction.isDefined || restriction.get.contains(cmd) =>
-              val document = doc_view.model.recent_document
+              val document = doc_view.model.snapshot().document
               val filtered_results =
                 document.current_state(cmd).results filter {
                   case XML.Elem(Markup.TRACING, _, _) => show_tracing