src/Tools/jEdit/src/proofdocument/theory_view.scala
changeset 34759 bfea7839d9e1
parent 34742 7b8847852aae
child 34760 dc7f5e0d9d27
equal deleted inserted replaced
34758:710e3a9a4c95 34759:bfea7839d9e1
       
     1 /*
       
     2  * jEdit text area as document text source
       
     3  *
       
     4  * @author Fabian Immler, TU Munich
       
     5  * @author Johannes Hölzl, TU Munich
       
     6  * @author Makarius
       
     7  */
       
     8 
       
     9 package isabelle.jedit
       
    10 
       
    11 import scala.actors.Actor, Actor._
       
    12 import scala.collection.mutable
       
    13 
       
    14 import isabelle.proofdocument.{ProofDocument, Change, Edit, Insert, Remove}
       
    15 import isabelle.prover.{Prover, Command}
       
    16 
       
    17 import java.awt.{Color, Graphics2D}
       
    18 import javax.swing.event.{CaretListener, CaretEvent}
       
    19 
       
    20 import org.gjt.sp.jedit.buffer.{BufferListener, JEditBuffer}
       
    21 import org.gjt.sp.jedit.textarea.{JEditTextArea, TextAreaExtension, TextAreaPainter}
       
    22 import org.gjt.sp.jedit.syntax.{ModeProvider, SyntaxStyle}
       
    23 
       
    24 
       
    25 object TheoryView
       
    26 {
       
    27   def choose_color(command: Command, doc: ProofDocument): Color =
       
    28   {
       
    29     command.status(doc) match {
       
    30       case Command.Status.UNPROCESSED => new Color(255, 228, 225)
       
    31       case Command.Status.FINISHED => new Color(234, 248, 255)
       
    32       case Command.Status.FAILED => new Color(255, 106, 106)
       
    33       case _ => Color.red
       
    34     }
       
    35   }
       
    36 }
       
    37 
       
    38 
       
    39 class TheoryView(text_area: JEditTextArea)
       
    40 {
       
    41   private val buffer = text_area.getBuffer
       
    42 
       
    43 
       
    44   /* prover setup */
       
    45 
       
    46   val prover: Prover = new Prover(Isabelle.system, Isabelle.default_logic())
       
    47 
       
    48   prover.command_change += ((command: Command) =>
       
    49     if (current_document().commands.contains(command))
       
    50       Swing_Thread.later {
       
    51         // FIXME proper handling of buffer vs. text areas
       
    52         // repaint if buffer is active
       
    53         if (text_area.getBuffer == buffer) {
       
    54           update_syntax(command)
       
    55           invalidate_line(command)
       
    56           overview.repaint()
       
    57         }
       
    58       })
       
    59 
       
    60 
       
    61   /* changes vs. edits */
       
    62 
       
    63   private val change_0 = new Change(prover.document_0.id, None, Nil)
       
    64   private var _changes = List(change_0)   // owned by Swing/AWT thread
       
    65   def changes = _changes
       
    66   private var current_change = change_0
       
    67 
       
    68   private val edits = new mutable.ListBuffer[Edit]   // owned by Swing thread
       
    69 
       
    70   private val edits_delay = Swing_Thread.delay_last(300) {
       
    71     if (!edits.isEmpty) {
       
    72       val change = new Change(Isabelle.system.id(), Some(current_change), edits.toList)
       
    73       _changes ::= change
       
    74       prover.input(change)
       
    75       current_change = change
       
    76       edits.clear
       
    77     }
       
    78   }
       
    79 
       
    80 
       
    81   /* buffer_listener */
       
    82 
       
    83   private val buffer_listener = new BufferListener {
       
    84     override def preContentInserted(buffer: JEditBuffer,
       
    85       start_line: Int, offset: Int, num_lines: Int, length: Int)
       
    86     {
       
    87       edits += Insert(offset, buffer.getText(offset, length))
       
    88       edits_delay()
       
    89     }
       
    90 
       
    91     override def preContentRemoved(buffer: JEditBuffer,
       
    92       start_line: Int, start: Int, num_lines: Int, removed_length: Int)
       
    93     {
       
    94       edits += Remove(start, buffer.getText(start, removed_length))
       
    95       edits_delay()
       
    96     }
       
    97 
       
    98     override def contentInserted(buffer: JEditBuffer,
       
    99       start_line: Int, offset: Int, num_lines: Int, length: Int) { }
       
   100 
       
   101     override def contentRemoved(buffer: JEditBuffer,
       
   102       start_line: Int, offset: Int, num_lines: Int, length: Int) { }
       
   103 
       
   104     override def bufferLoaded(buffer: JEditBuffer) { }
       
   105     override def foldHandlerChanged(buffer: JEditBuffer) { }
       
   106     override def foldLevelChanged(buffer: JEditBuffer, start_line: Int, end_line: Int) { }
       
   107     override def transactionComplete(buffer: JEditBuffer) { }
       
   108   }
       
   109 
       
   110 
       
   111   /* text_area_extension */
       
   112 
       
   113   private val text_area_extension = new TextAreaExtension {
       
   114     override def paintValidLine(gfx: Graphics2D,
       
   115       screen_line: Int, physical_line: Int, start: Int, end: Int, y: Int)
       
   116     {
       
   117       val document = current_document()
       
   118       def from_current(pos: Int) = TheoryView.this.from_current(document, pos)
       
   119       def to_current(pos: Int) = TheoryView.this.to_current(document, pos)
       
   120       val saved_color = gfx.getColor
       
   121 
       
   122       val metrics = text_area.getPainter.getFontMetrics
       
   123 
       
   124       // encolor phase
       
   125       var cmd = document.command_at(from_current(start))
       
   126       while (cmd.isDefined && cmd.get.start(document) < end) {
       
   127         val e = cmd.get
       
   128         val begin = start max to_current(e.start(document))
       
   129         val finish = (end - 1) min to_current(e.stop(document))
       
   130         encolor(gfx, y, metrics.getHeight, begin, finish,
       
   131           TheoryView.choose_color(e, document), true)
       
   132         cmd = document.commands.next(e)
       
   133       }
       
   134 
       
   135       gfx.setColor(saved_color)
       
   136     }
       
   137 
       
   138     override def getToolTipText(x: Int, y: Int): String =
       
   139     {
       
   140       val document = current_document()
       
   141       val offset = from_current(document, text_area.xyToOffset(x, y))
       
   142       document.command_at(offset) match {
       
   143         case Some(cmd) =>
       
   144           document.token_start(cmd.tokens.first)
       
   145           cmd.type_at(document, offset - cmd.start(document)).getOrElse(null)
       
   146         case None => null
       
   147       }
       
   148     }
       
   149   }
       
   150 
       
   151 
       
   152   /* activation */
       
   153 
       
   154   private val overview = new Document_Overview(prover, text_area, to_current)
       
   155 
       
   156   private val selected_state_controller = new CaretListener {
       
   157     override def caretUpdate(e: CaretEvent) {
       
   158       val doc = current_document()
       
   159       doc.command_at(e.getDot) match {
       
   160         case Some(cmd)
       
   161           if (doc.token_start(cmd.tokens.first) <= e.getDot &&
       
   162             Isabelle.plugin.selected_state != cmd) =>
       
   163           Isabelle.plugin.selected_state = cmd
       
   164         case _ =>
       
   165       }
       
   166     }
       
   167   }
       
   168 
       
   169   def activate() {
       
   170     text_area.addCaretListener(selected_state_controller)
       
   171     text_area.addLeftOfScrollBar(overview)
       
   172     text_area.getPainter.
       
   173       addExtension(TextAreaPainter.LINE_BACKGROUND_LAYER + 1, text_area_extension)
       
   174     buffer.setTokenMarker(new DynamicTokenMarker(buffer, prover))
       
   175     buffer.addBufferListener(buffer_listener)
       
   176 
       
   177     val dockable =
       
   178       text_area.getView.getDockableWindowManager.getDockable("isabelle-output")
       
   179     if (dockable != null) {
       
   180       val output_dockable = dockable.asInstanceOf[OutputDockable]
       
   181       val output_text_view = prover.output_text_view
       
   182       output_dockable.set_text(output_text_view)
       
   183     }
       
   184 
       
   185     buffer.propertiesChanged()
       
   186   }
       
   187 
       
   188   def deactivate() {
       
   189     buffer.setTokenMarker(buffer.getMode.getTokenMarker)
       
   190     buffer.removeBufferListener(buffer_listener)
       
   191     text_area.getPainter.removeExtension(text_area_extension)
       
   192     text_area.removeLeftOfScrollBar(overview)
       
   193     text_area.removeCaretListener(selected_state_controller)
       
   194   }
       
   195 
       
   196 
       
   197   /* history of changes */
       
   198 
       
   199   private def doc_or_pred(c: Change): ProofDocument =
       
   200     prover.document(c.id).getOrElse(doc_or_pred(c.parent.get))
       
   201 
       
   202   def current_document() = doc_or_pred(current_change)
       
   203 
       
   204 
       
   205   /* update to desired version */
       
   206 
       
   207   def set_version(goal: Change) {
       
   208     // changes in buffer must be ignored
       
   209     buffer.removeBufferListener(buffer_listener)
       
   210 
       
   211     def apply(change: Change): Unit = change.edits.foreach {
       
   212       case Insert(start, text) => buffer.insert(start, text)
       
   213       case Remove(start, text) => buffer.remove(start, text.length)
       
   214     }
       
   215 
       
   216     def unapply(change: Change): Unit = change.edits.reverse.foreach {
       
   217       case Insert(start, text) => buffer.remove(start, text.length)
       
   218       case Remove(start, text) => buffer.insert(start, text)
       
   219     }
       
   220 
       
   221     // undo/redo changes
       
   222     val ancs_current = current_change.ancestors
       
   223     val ancs_goal = goal.ancestors
       
   224     val paired = ancs_current.reverse zip ancs_goal.reverse
       
   225     def last_common[A](xs: List[(A, A)]): Option[A] = {
       
   226       xs match {
       
   227         case (x, y) :: xs =>
       
   228           if (x == y)
       
   229             xs match {
       
   230               case (a, b) :: ys =>
       
   231                 if (a == b) last_common(xs)
       
   232                 else Some(x)
       
   233               case _ => Some(x)
       
   234             }
       
   235           else None
       
   236         case _ => None
       
   237       }
       
   238     }
       
   239     val common_anc = last_common(paired).get
       
   240 
       
   241     ancs_current.takeWhile(_ != common_anc) map unapply
       
   242     ancs_goal.takeWhile(_ != common_anc).reverse map apply
       
   243 
       
   244     current_change = goal
       
   245     // invoke repaint
       
   246     buffer.propertiesChanged()
       
   247     invalidate_all()
       
   248     overview.repaint()
       
   249 
       
   250     // track changes in buffer
       
   251     buffer.addBufferListener(buffer_listener)
       
   252   }
       
   253 
       
   254 
       
   255   /* transforming offsets */
       
   256 
       
   257   private def changes_from(doc: ProofDocument): List[Edit] =
       
   258     List.flatten(current_change.ancestors(_.id == doc.id).reverse.map(_.edits)) :::
       
   259       edits.toList
       
   260 
       
   261   def from_current(doc: ProofDocument, offset: Int): Int =
       
   262     (offset /: changes_from(doc).reverse) ((i, change) => change before i)
       
   263 
       
   264   def to_current(doc: ProofDocument, offset: Int): Int =
       
   265     (offset /: changes_from(doc)) ((i, change) => change after i)
       
   266 
       
   267 
       
   268   private def lines_of_command(cmd: Command) =
       
   269   {
       
   270     val document = current_document()
       
   271     (buffer.getLineOfOffset(to_current(document, cmd.start(document))),
       
   272      buffer.getLineOfOffset(to_current(document, cmd.stop(document))))
       
   273   }
       
   274 
       
   275 
       
   276   /* (re)painting */
       
   277 
       
   278   private val update_delay = Swing_Thread.delay_first(500) { buffer.propertiesChanged() }
       
   279 
       
   280   private def update_syntax(cmd: Command) {
       
   281     val (line1, line2) = lines_of_command(cmd)
       
   282     if (line2 >= text_area.getFirstLine &&
       
   283       line1 <= text_area.getFirstLine + text_area.getVisibleLines)
       
   284         update_delay()
       
   285   }
       
   286 
       
   287   private def invalidate_line(cmd: Command) =
       
   288   {
       
   289     val (start, stop) = lines_of_command(cmd)
       
   290     text_area.invalidateLineRange(start, stop)
       
   291 
       
   292     if (Isabelle.plugin.selected_state == cmd)
       
   293       Isabelle.plugin.selected_state = cmd  // update State view
       
   294   }
       
   295 
       
   296   private def invalidate_all() =
       
   297     text_area.invalidateLineRange(text_area.getFirstPhysicalLine,
       
   298       text_area.getLastPhysicalLine)
       
   299 
       
   300   private def encolor(gfx: Graphics2D,
       
   301     y: Int, height: Int, begin: Int, finish: Int, color: Color, fill: Boolean)
       
   302   {
       
   303     val start = text_area.offsetToXY(begin)
       
   304     val stop =
       
   305       if (finish < buffer.getLength) text_area.offsetToXY(finish)
       
   306       else {
       
   307         val p = text_area.offsetToXY(finish - 1)
       
   308         val metrics = text_area.getPainter.getFontMetrics
       
   309         p.x = p.x + (metrics.charWidth(' ') max metrics.getMaxAdvance)
       
   310         p
       
   311       }
       
   312 
       
   313     if (start != null && stop != null) {
       
   314       gfx.setColor(color)
       
   315       if (fill) gfx.fillRect(start.x, y, stop.x - start.x, height)
       
   316       else gfx.drawRect(start.x, y, stop.x - start.x, height)
       
   317     }
       
   318   }
       
   319 
       
   320 
       
   321   /* init */
       
   322 
       
   323   prover.start()
       
   324 
       
   325   edits += Insert(0, buffer.getText(0, buffer.getLength))
       
   326   edits_delay()
       
   327 }