report token range after inner parse error -- often provides important clues about misunderstanding concerning lexical phase;
tuned color;
1 /* Title: Tools/jEdit/src/jedit/document_view.scala
2 Author: Fabian Immler, TU Munich
5 Document view connected to jEdit text area.
13 import scala.actors.Actor._
15 import java.awt.event.{MouseAdapter, MouseMotionAdapter, MouseEvent, FocusAdapter, FocusEvent}
16 import java.awt.{BorderLayout, Graphics, Dimension, Color, Graphics2D}
17 import javax.swing.{JPanel, ToolTipManager}
18 import javax.swing.event.{CaretListener, CaretEvent}
20 import org.gjt.sp.jedit.OperatingSystem
21 import org.gjt.sp.jedit.gui.RolloverButton
22 import org.gjt.sp.jedit.textarea.{JEditTextArea, TextArea, TextAreaExtension, TextAreaPainter}
23 import org.gjt.sp.jedit.syntax.SyntaxStyle
28 /* physical rendering */
30 def status_color(snapshot: Document.Snapshot, command: Command): Color =
32 val state = snapshot.state(command)
33 if (snapshot.is_outdated) new Color(240, 240, 240)
35 Isar_Document.command_status(state.status) match {
36 case Isar_Document.Forked(i) if i > 0 => new Color(255, 228, 225)
37 case Isar_Document.Finished => new Color(234, 248, 255)
38 case Isar_Document.Failed => new Color(255, 193, 193)
39 case Isar_Document.Unprocessed => new Color(255, 228, 225)
44 val message_markup: PartialFunction[Text.Info[Any], Color] =
46 case Text.Info(_, XML.Elem(Markup(Markup.WRITELN, _), _)) => new Color(192, 192, 192)
47 case Text.Info(_, XML.Elem(Markup(Markup.WARNING, _), _)) => new Color(255, 165, 0)
48 case Text.Info(_, XML.Elem(Markup(Markup.ERROR, _), _)) => new Color(255, 106, 106)
51 val box_markup: PartialFunction[Text.Info[Any], Color] =
53 case Text.Info(_, XML.Elem(Markup(Markup.TOKEN_RANGE, _), _)) => new Color(192, 192, 192)
57 /* document view of text area */
59 private val key = new Object
61 def init(model: Document_Model, text_area: TextArea): Document_View =
63 Swing_Thread.require()
64 val doc_view = new Document_View(model, text_area)
65 text_area.putClientProperty(key, doc_view)
70 def apply(text_area: TextArea): Option[Document_View] =
72 Swing_Thread.require()
73 text_area.getClientProperty(key) match {
74 case doc_view: Document_View => Some(doc_view)
79 def exit(text_area: TextArea)
81 Swing_Thread.require()
82 apply(text_area) match {
83 case None => error("No document view for text area: " + text_area)
84 case Some(doc_view) =>
86 text_area.putClientProperty(key, null)
92 class Document_View(val model: Document_Model, text_area: TextArea)
94 private val session = model.session
97 /* extended token styles */
99 private var styles: Array[SyntaxStyle] = null // owned by Swing thread
103 Swing_Thread.require()
104 styles = Document_Model.Token_Markup.extend_styles(text_area.getPainter.getStyles)
110 Swing_Thread.require()
111 text_area.getPainter.setStyles(styles)
115 /* visible line ranges */
117 // simplify slightly odd result of TextArea.getScreenLineEndOffset etc.
118 // NB: jEdit already normalizes \r\n and \r to \n
119 def proper_line_range(start: Text.Offset, end: Text.Offset): Text.Range =
121 val stop = if (start < end) end - 1 else end min model.buffer.getLength
122 Text.Range(start, stop)
125 def screen_lines_range(): Text.Range =
127 val start = text_area.getScreenLineStartOffset(0)
128 val raw_end = text_area.getScreenLineEndOffset(text_area.getVisibleLines - 1 max 0)
129 proper_line_range(start, if (raw_end >= 0) raw_end else model.buffer.getLength)
132 def invalidate_line_range(range: Text.Range)
134 text_area.invalidateLineRange(
135 model.buffer.getLineOfOffset(range.start),
136 model.buffer.getLineOfOffset(range.stop))
140 /* commands_changed_actor */
142 private val commands_changed_actor = actor {
145 case Session.Commands_Changed(changed) =>
146 val buffer = model.buffer
147 Isabelle.swing_buffer_lock(buffer) {
148 val snapshot = model.snapshot()
150 if (changed.exists(snapshot.node.commands.contains))
153 val visible_range = screen_lines_range()
154 val visible_cmds = snapshot.node.command_range(snapshot.revert(visible_range)).map(_._1)
155 if (visible_cmds.exists(changed)) {
157 line <- 0 until text_area.getVisibleLines
158 val start = text_area.getScreenLineStartOffset(line) if start >= 0
159 val end = text_area.getScreenLineEndOffset(line) if end >= 0
160 val range = proper_line_range(start, end)
161 val line_cmds = snapshot.node.command_range(snapshot.revert(range)).map(_._1)
162 if line_cmds.exists(changed)
163 } text_area.invalidateScreenLineRange(line, line)
165 // FIXME danger of deadlock!?
166 // FIXME potentially slow!?
167 model.buffer.propertiesChanged()
171 case bad => System.err.println("command_change_actor: ignoring bad message " + bad)
177 /* subexpression highlighting */
179 private def subexp_range(snapshot: Document.Snapshot, x: Int, y: Int): Option[Text.Range] =
181 val subexp_markup: PartialFunction[Text.Info[Any], Option[Text.Range]] =
183 case Text.Info(range, XML.Elem(Markup(Markup.ML_TYPING, _), _)) =>
184 Some(snapshot.convert(range))
186 val offset = text_area.xyToOffset(x, y)
187 val markup = snapshot.select_markup(Text.Range(offset, offset + 1))(subexp_markup)(None)
188 if (markup.hasNext) markup.next.info else None
191 private var highlight_range: Option[Text.Range] = None
193 private val focus_listener = new FocusAdapter {
194 override def focusLost(e: FocusEvent) { highlight_range = None }
197 private val mouse_motion_listener = new MouseMotionAdapter {
198 override def mouseMoved(e: MouseEvent) {
199 val control = if (OperatingSystem.isMacOS()) e.isMetaDown else e.isControlDown
200 if (!model.buffer.isLoaded) highlight_range = None
202 Isabelle.swing_buffer_lock(model.buffer) {
203 highlight_range.map(invalidate_line_range(_))
205 if (control) subexp_range(model.snapshot(), e.getX(), e.getY()) else None
206 highlight_range.map(invalidate_line_range(_))
212 /* text_area_extension */
214 private val text_area_extension = new TextAreaExtension
216 override def paintScreenLineRange(gfx: Graphics2D,
217 first_line: Int, last_line: Int, physical_lines: Array[Int],
218 start: Array[Int], end: Array[Int], y: Int, line_height: Int)
220 Isabelle.swing_buffer_lock(model.buffer) {
221 val snapshot = model.snapshot()
222 val saved_color = gfx.getColor
223 val ascent = text_area.getPainter.getFontMetrics.getAscent
226 for (i <- 0 until physical_lines.length) {
227 if (physical_lines(i) != -1) {
228 val line_range = proper_line_range(start(i), end(i))
231 val cmds = snapshot.node.command_range(snapshot.revert(line_range))
233 (command, command_start) <- cmds if !command.is_ignored
234 val range = line_range.restrict(snapshot.convert(command.range + command_start))
235 r <- Isabelle.gfx_range(text_area, range)
237 gfx.setColor(Document_View.status_color(snapshot, command))
238 gfx.fillRect(r.x, y + i * line_height, r.length, line_height)
241 // subexpression highlighting -- potentially from other snapshot
242 if (highlight_range.isDefined) {
243 if (line_range.overlaps(highlight_range.get)) {
244 Isabelle.gfx_range(text_area, line_range.restrict(highlight_range.get)) match {
247 gfx.setColor(Color.black)
248 gfx.drawRect(r.x, y + i * line_height, r.length, line_height - 1)
255 Text.Info(range, color) <-
256 snapshot.select_markup(line_range)(Document_View.box_markup)(null)
258 r <- Isabelle.gfx_range(text_area, range)
261 gfx.drawRect(r.x + 1, y + i * line_height + 1, r.length - 2, line_height - 3)
264 // squiggly underline
266 Text.Info(range, color) <-
267 snapshot.select_markup(line_range)(Document_View.message_markup)(null)
269 r <- Isabelle.gfx_range(text_area, range)
272 val x0 = (r.x / 2) * 2
273 val y0 = r.y + ascent + 1
274 for (x1 <- Range(x0, x0 + r.length, 2)) {
275 val y1 = if (x1 % 4 < 2) y0 else y0 + 1
276 gfx.drawLine(x1, y1, x1 + 1, y1)
282 finally { gfx.setColor(saved_color) }
286 override def getToolTipText(x: Int, y: Int): String =
288 Isabelle.swing_buffer_lock(model.buffer) {
289 val snapshot = model.snapshot()
290 val offset = text_area.xyToOffset(x, y)
292 snapshot.select_markup(Text.Range(offset, offset + 1)) {
293 case Text.Info(range, XML.Elem(Markup(Markup.ML_TYPING, _), body)) =>
294 Isabelle.tooltip(Pretty.string_of(List(Pretty.block(body)), margin = 40))
296 if (markup.hasNext) markup.next.info else null
304 def selected_command(): Option[Command] =
306 Swing_Thread.require()
307 model.snapshot().node.proper_command_at(text_area.getCaretPosition)
310 private val caret_listener = new CaretListener {
311 private val delay = Swing_Thread.delay_last(session.input_delay) {
312 session.perspective.event(Session.Perspective)
314 override def caretUpdate(e: CaretEvent) { delay() }
318 /* overview of command status left of scrollbar */
320 private val overview = new JPanel(new BorderLayout)
322 private val WIDTH = 10
323 private val HEIGHT = 2
325 setPreferredSize(new Dimension(WIDTH, 0))
327 setRequestFocusEnabled(false)
329 addMouseListener(new MouseAdapter {
330 override def mousePressed(event: MouseEvent) {
331 val line = y_to_line(event.getY)
332 if (line >= 0 && line < text_area.getLineCount)
333 text_area.setCaretPosition(text_area.getLineStartOffset(line))
337 override def addNotify() {
339 ToolTipManager.sharedInstance.registerComponent(this)
342 override def removeNotify() {
343 ToolTipManager.sharedInstance.unregisterComponent(this)
347 override def getToolTipText(event: MouseEvent): String =
349 val line = y_to_line(event.getY())
350 if (line >= 0 && line < text_area.getLineCount) "<html><b>TODO:</b><br>Tooltip</html>"
354 override def paintComponent(gfx: Graphics)
356 super.paintComponent(gfx)
357 Swing_Thread.assert()
358 val buffer = model.buffer
359 Isabelle.buffer_lock(buffer) {
360 val snapshot = model.snapshot()
361 val saved_color = gfx.getColor // FIXME needed!?
363 for ((command, start) <- snapshot.node.command_starts if !command.is_ignored) {
364 val line1 = buffer.getLineOfOffset(snapshot.convert(start))
365 val line2 = buffer.getLineOfOffset(snapshot.convert(start + command.length)) + 1
366 val y = line_to_y(line1)
367 val height = HEIGHT * (line2 - line1)
368 gfx.setColor(Document_View.status_color(snapshot, command))
369 gfx.fillRect(0, y, getWidth - 1, height)
372 finally { gfx.setColor(saved_color) }
376 private def line_to_y(line: Int): Int =
377 (line * getHeight) / (text_area.getBuffer.getLineCount max text_area.getVisibleLines)
379 private def y_to_line(y: Int): Int =
380 (y * (text_area.getBuffer.getLineCount max text_area.getVisibleLines)) / getHeight
386 private def activate()
388 text_area.getPainter.
389 addExtension(TextAreaPainter.LINE_BACKGROUND_LAYER + 1, text_area_extension)
390 text_area.addFocusListener(focus_listener)
391 text_area.getPainter.addMouseMotionListener(mouse_motion_listener)
392 text_area.addCaretListener(caret_listener)
393 text_area.addLeftOfScrollBar(overview)
394 session.commands_changed += commands_changed_actor
397 private def deactivate()
399 session.commands_changed -= commands_changed_actor
400 text_area.removeFocusListener(focus_listener)
401 text_area.getPainter.removeMouseMotionListener(mouse_motion_listener)
402 text_area.removeCaretListener(caret_listener)
403 text_area.removeLeftOfScrollBar(overview)
404 text_area.getPainter.removeExtension(text_area_extension)