more static handling of rendering options;
authorwenzelm
Fri, 14 Sep 2012 13:52:16 +0200
changeset 49356 6e0c0ffb6ec7
parent 49355 7d1af0a6e797
child 49357 34ac36642a31
more static handling of rendering options;
src/Tools/jEdit/src/document_view.scala
src/Tools/jEdit/src/isabelle_rendering.scala
src/Tools/jEdit/src/jedit_options.scala
src/Tools/jEdit/src/session_dockable.scala
src/Tools/jEdit/src/text_area_painter.scala
src/Tools/jEdit/src/text_overview.scala
--- a/src/Tools/jEdit/src/document_view.scala	Fri Sep 14 12:46:33 2012 +0200
+++ b/src/Tools/jEdit/src/document_view.scala	Fri Sep 14 13:52:16 2012 +0200
@@ -232,25 +232,26 @@
       else
         Isabelle.buffer_lock(model.buffer) {
           val snapshot = model.snapshot()
+          val rendering = Isabelle_Rendering(snapshot, Isabelle.options.value)
 
           if (control) init_popup(snapshot, x, y)
 
           def update_range[A](
-            rendering: (Document.Snapshot, Text.Range) => Option[Text.Info[A]],
+            rendering: Text.Range => Option[Text.Info[A]],
             info: Option[Text.Info[A]]): Option[Text.Info[A]] =
           {
             for (Text.Info(range, _) <- info) invalidate_range(range)
             val new_info =
               if (control) {
                 val offset = text_area.xyToOffset(x, y)
-                rendering(snapshot, Text.Range(offset, offset + 1))
+                rendering(Text.Range(offset, offset + 1))
               }
               else None
             for (Text.Info(range, _) <- info) invalidate_range(range)
             new_info
           }
-          _highlight_range = update_range(Isabelle_Rendering.subexp, _highlight_range)
-          _hyperlink_range = update_range(Isabelle_Rendering.hyperlink, _hyperlink_range)
+          _highlight_range = update_range(rendering.subexp, _highlight_range)
+          _hyperlink_range = update_range(rendering.hyperlink, _hyperlink_range)
         }
     }
   }
@@ -265,12 +266,12 @@
     override def getToolTipText(x: Int, y: Int): String =
     {
       robust_body(null: String) {
-        val snapshot = model.snapshot()
+        val rendering = Isabelle_Rendering(model.snapshot(), Isabelle.options.value)
         val offset = text_area.xyToOffset(x, y)
         val range = Text.Range(offset, offset + 1)
         val tip =
-          if (control) Isabelle_Rendering.tooltip(snapshot, range)
-          else Isabelle_Rendering.tooltip_message(snapshot, range)
+          if (control) rendering.tooltip(range)
+          else rendering.tooltip_message(range)
         tip.map(Isabelle.tooltip(_)) getOrElse null
       }
     }
@@ -292,13 +293,14 @@
 
         if (gutter.isSelectionAreaEnabled && !gutter.isExpanded && width >= 12 && line_height >= 12) {
           Isabelle.buffer_lock(model.buffer) {
-            val snapshot = model.snapshot()
+            val rendering = Isabelle_Rendering(model.snapshot(), Isabelle.options.value)
+
             for (i <- 0 until physical_lines.length) {
               if (physical_lines(i) != -1) {
                 val line_range = proper_line_range(start(i), end(i))
 
                 // gutter icons
-                Isabelle_Rendering.gutter_message(snapshot, line_range) match {
+                rendering.gutter_message(line_range) match {
                   case Some(icon) =>
                     val x0 = (FOLD_MARKER_SIZE + width - border_width - icon.getIconWidth) max 10
                     val y0 = y + i * line_height + (((line_height - icon.getIconHeight) / 2) max 0)
--- a/src/Tools/jEdit/src/isabelle_rendering.scala	Fri Sep 14 12:46:33 2012 +0200
+++ b/src/Tools/jEdit/src/isabelle_rendering.scala	Fri Sep 14 13:52:16 2012 +0200
@@ -20,326 +20,22 @@
 
 object Isabelle_Rendering
 {
-  /* physical rendering */
+  def apply(snapshot: Document.Snapshot, options: Options): Isabelle_Rendering =
+    new Isabelle_Rendering(snapshot, options)
 
-  def color_value(s: String): Color = Color_Value(Isabelle.options.value.string(s))
+
+  /* physical rendering */
 
   private val writeln_pri = 1
   private val warning_pri = 2
   private val legacy_pri = 3
   private val error_pri = 4
 
-
-  /* command overview */
-
-  def overview_color(snapshot: Document.Snapshot, range: Text.Range): Option[Color] =
-  {
-    if (snapshot.is_outdated) None
-    else {
-      val results =
-        snapshot.cumulate_markup[(Protocol.Status, Int)](
-          range, (Protocol.Status.init, 0),
-          Some(Protocol.command_status_markup + Isabelle_Markup.WARNING + Isabelle_Markup.ERROR),
-          {
-            case ((status, pri), Text.Info(_, XML.Elem(markup, _))) =>
-              if (markup.name == Isabelle_Markup.WARNING) (status, pri max warning_pri)
-              else if (markup.name == Isabelle_Markup.ERROR) (status, pri max error_pri)
-              else (Protocol.command_status(status, markup), pri)
-          })
-      if (results.isEmpty) None
-      else {
-        val (status, pri) =
-          ((Protocol.Status.init, 0) /: results) {
-            case ((s1, p1), Text.Info(_, (s2, p2))) => (s1 + s2, p1 max p2) }
-
-        if (pri == warning_pri) Some(color_value("warning_color"))
-        else if (pri == error_pri) Some(color_value("error_color"))
-        else if (status.is_unprocessed) Some(color_value("unprocessed_color"))
-        else if (status.is_running) Some(color_value("running_color"))
-        else if (status.is_failed) Some(color_value("error_color"))
-        else None
-      }
-    }
-  }
-
-
-  /* markup selectors */
-
-  private val subexp_include =
-    Set(Isabelle_Markup.SORT, Isabelle_Markup.TYP, Isabelle_Markup.TERM, Isabelle_Markup.PROP,
-      Isabelle_Markup.ML_TYPING, Isabelle_Markup.TOKEN_RANGE, Isabelle_Markup.ENTITY,
-      Isabelle_Markup.PATH, Isabelle_Markup.TYPING, Isabelle_Markup.FREE, Isabelle_Markup.SKOLEM,
-      Isabelle_Markup.BOUND, Isabelle_Markup.VAR, Isabelle_Markup.TFREE, Isabelle_Markup.TVAR,
-      Isabelle_Markup.ML_SOURCE, Isabelle_Markup.DOCUMENT_SOURCE)
-
-  def subexp(snapshot: Document.Snapshot, range: Text.Range): Option[Text.Info[Color]] =
-  {
-    snapshot.select_markup(range, Some(subexp_include),
-        {
-          case Text.Info(info_range, XML.Elem(Markup(name, _), _)) if subexp_include(name) =>
-            Text.Info(snapshot.convert(info_range), color_value("subexp_color"))
-        }) match { case Text.Info(_, info) #:: _ => Some(info) case _ => None }
-  }
-
-
-  private val hyperlink_include = Set(Isabelle_Markup.ENTITY, Isabelle_Markup.PATH)
-
-  def hyperlink(snapshot: Document.Snapshot, range: Text.Range): Option[Text.Info[Hyperlink]] =
-  {
-    snapshot.cumulate_markup[List[Text.Info[Hyperlink]]](range, Nil, Some(hyperlink_include),
-        {
-          case (links, Text.Info(info_range, XML.Elem(Isabelle_Markup.Path(name), _)))
-          if Path.is_ok(name) =>
-            val jedit_file = Isabelle.thy_load.append(snapshot.node_name.dir, Path.explode(name))
-            Text.Info(snapshot.convert(info_range), Hyperlink(jedit_file, 0, 0)) :: links
-
-          case (links, Text.Info(info_range, XML.Elem(Markup(Isabelle_Markup.ENTITY, props), _)))
-          if (props.find(
-            { case (Markup.KIND, Isabelle_Markup.ML_OPEN) => true
-              case (Markup.KIND, Isabelle_Markup.ML_STRUCT) => true
-              case _ => false }).isEmpty) =>
-
-            props match {
-              case Position.Line_File(line, name) if Path.is_ok(name) =>
-                Isabelle_System.source_file(Path.explode(name)) match {
-                  case Some(path) =>
-                    val jedit_file = Isabelle_System.platform_path(path)
-                    Text.Info(snapshot.convert(info_range), Hyperlink(jedit_file, line, 0)) :: links
-                  case None => links
-                }
-
-              case Position.Id_Offset(id, offset) if !snapshot.is_outdated =>
-                snapshot.state.find_command(snapshot.version, id) match {
-                  case Some((node, command)) =>
-                    val sources =
-                      node.commands.iterator.takeWhile(_ != command).map(_.source) ++
-                        Iterator.single(command.source(Text.Range(0, command.decode(offset))))
-                    val (line, column) = ((1, 1) /: sources)(Symbol.advance_line_column)
-                    Text.Info(snapshot.convert(info_range),
-                      Hyperlink(command.node_name.node, line, column)) :: links
-                  case None => links
-                }
-
-              case _ => links
-            }
-        }) match { case Text.Info(_, info :: _) #:: _ => Some(info) case _ => None }
-  }
-
-
-  private def tooltip_text(msg: XML.Tree): String =
-    Pretty.string_of(List(msg), margin = Isabelle.options.int("jedit_tooltip_margin"))
-
-  def tooltip_message(snapshot: Document.Snapshot, range: Text.Range): Option[String] =
-  {
-    val msgs =
-      snapshot.cumulate_markup[SortedMap[Long, String]](range, SortedMap.empty,
-        Some(Set(Isabelle_Markup.WRITELN, Isabelle_Markup.WARNING, Isabelle_Markup.ERROR,
-          Isabelle_Markup.BAD)),
-        {
-          case (msgs, Text.Info(_, msg @
-              XML.Elem(Markup(markup, Isabelle_Markup.Serial(serial)), _)))
-          if markup == Isabelle_Markup.WRITELN ||
-              markup == Isabelle_Markup.WARNING ||
-              markup == Isabelle_Markup.ERROR =>
-            msgs + (serial -> tooltip_text(msg))
-          case (msgs, Text.Info(_, msg @ XML.Elem(Markup(Isabelle_Markup.BAD, _), body)))
-          if !body.isEmpty => msgs + (Document.new_id() -> tooltip_text(msg))
-        }).toList.flatMap(_.info)
-    if (msgs.isEmpty) None else Some(cat_lines(msgs.iterator.map(_._2)))
-  }
-
-
-  private val tooltips: Map[String, String] =
-    Map(
-      Isabelle_Markup.SORT -> "sort",
-      Isabelle_Markup.TYP -> "type",
-      Isabelle_Markup.TERM -> "term",
-      Isabelle_Markup.PROP -> "proposition",
-      Isabelle_Markup.TOKEN_RANGE -> "inner syntax token",
-      Isabelle_Markup.FREE -> "free variable",
-      Isabelle_Markup.SKOLEM -> "skolem variable",
-      Isabelle_Markup.BOUND -> "bound variable",
-      Isabelle_Markup.VAR -> "schematic variable",
-      Isabelle_Markup.TFREE -> "free type variable",
-      Isabelle_Markup.TVAR -> "schematic type variable",
-      Isabelle_Markup.ML_SOURCE -> "ML source",
-      Isabelle_Markup.DOCUMENT_SOURCE -> "document source")
-
-  private val tooltip_elements =
-    Set(Isabelle_Markup.ENTITY, Isabelle_Markup.TYPING, Isabelle_Markup.ML_TYPING,
-      Isabelle_Markup.PATH) ++ tooltips.keys
-
-  private def string_of_typing(kind: String, body: XML.Body): String =
-    Pretty.string_of(List(Pretty.block(XML.Text(kind) :: Pretty.Break(1) :: body)),
-      margin = Isabelle.options.int("jedit_tooltip_margin"))
-
-  def tooltip(snapshot: Document.Snapshot, range: Text.Range): Option[String] =
-  {
-    def add(prev: Text.Info[List[(Boolean, String)]], r: Text.Range, p: (Boolean, String)) =
-      if (prev.range == r) Text.Info(r, p :: prev.info) else Text.Info(r, List(p))
-
-    val tips =
-      snapshot.cumulate_markup[Text.Info[(List[(Boolean, String)])]](
-        range, Text.Info(range, Nil), Some(tooltip_elements),
-        {
-          case (prev, Text.Info(r, XML.Elem(Isabelle_Markup.Entity(kind, name), _))) =>
-            val kind1 = space_explode('_', kind).mkString(" ")
-            add(prev, r, (true, kind1 + " " + quote(name)))
-          case (prev, Text.Info(r, XML.Elem(Isabelle_Markup.Path(name), _)))
-          if Path.is_ok(name) =>
-            val jedit_file = Isabelle.thy_load.append(snapshot.node_name.dir, Path.explode(name))
-            add(prev, r, (true, "file " + quote(jedit_file)))
-          case (prev, Text.Info(r, XML.Elem(Markup(Isabelle_Markup.TYPING, _), body))) =>
-            add(prev, r, (true, string_of_typing("::", body)))
-          case (prev, Text.Info(r, XML.Elem(Markup(Isabelle_Markup.ML_TYPING, _), body))) =>
-            add(prev, r, (false, string_of_typing("ML:", body)))
-          case (prev, Text.Info(r, XML.Elem(Markup(name, _), _)))
-          if tooltips.isDefinedAt(name) => add(prev, r, (true, tooltips(name)))
-        }).toList.flatMap(_.info.info)
-
-    val all_tips =
-      (tips.filter(_._1) ++ tips.filter(!_._1).lastOption.toList).map(_._2)
-    if (all_tips.isEmpty) None else Some(cat_lines(all_tips))
-  }
-
-
   private val gutter_icons = Map(
     warning_pri -> Isabelle.load_icon("16x16/status/dialog-information.png"),
     legacy_pri -> Isabelle.load_icon("16x16/status/dialog-warning.png"),
     error_pri -> Isabelle.load_icon("16x16/status/dialog-error.png"))
 
-  def gutter_message(snapshot: Document.Snapshot, range: Text.Range): Option[Icon] =
-  {
-    val results =
-      snapshot.cumulate_markup[Int](range, 0,
-        Some(Set(Isabelle_Markup.WARNING, Isabelle_Markup.ERROR)),
-        {
-          case (pri, Text.Info(_, XML.Elem(Markup(Isabelle_Markup.WARNING, _), body))) =>
-            body match {
-              case List(XML.Elem(Markup(Isabelle_Markup.LEGACY, _), _)) => pri max legacy_pri
-              case _ => pri max warning_pri
-            }
-          case (pri, Text.Info(_, XML.Elem(Markup(Isabelle_Markup.ERROR, _), _))) =>
-            pri max error_pri
-        })
-    val pri = (0 /: results) { case (p1, Text.Info(_, p2)) => p1 max p2 }
-    gutter_icons.get(pri)
-  }
-
-
-  def squiggly_underline(snapshot: Document.Snapshot, range: Text.Range): Stream[Text.Info[Color]] =
-  {
-    val squiggly_colors = Map(
-      writeln_pri -> color_value("writeln_color"),
-      warning_pri -> color_value("warning_color"),
-      error_pri -> color_value("error_color"))
-
-    val results =
-      snapshot.cumulate_markup[Int](range, 0,
-        Some(Set(Isabelle_Markup.WRITELN, Isabelle_Markup.WARNING, Isabelle_Markup.ERROR)),
-        {
-          case (pri, Text.Info(_, XML.Elem(Markup(name, _), _))) =>
-            name match {
-              case Isabelle_Markup.WRITELN => pri max writeln_pri
-              case Isabelle_Markup.WARNING => pri max warning_pri
-              case Isabelle_Markup.ERROR => pri max error_pri
-              case _ => pri
-            }
-        })
-    for {
-      Text.Info(r, pri) <- results
-      color <- squiggly_colors.get(pri)
-    } yield Text.Info(r, color)
-  }
-
-
-  def background1(snapshot: Document.Snapshot, range: Text.Range): Stream[Text.Info[Color]] =
-  {
-    if (snapshot.is_outdated) Stream(Text.Info(range, color_value("outdated_color")))
-    else
-      for {
-        Text.Info(r, result) <-
-          snapshot.cumulate_markup[(Option[Protocol.Status], Option[Color])](
-            range, (Some(Protocol.Status.init), None),
-            Some(Protocol.command_status_markup + Isabelle_Markup.BAD + Isabelle_Markup.HILITE),
-            {
-              case (((Some(status), color), Text.Info(_, XML.Elem(markup, _))))
-              if (Protocol.command_status_markup(markup.name)) =>
-                (Some(Protocol.command_status(status, markup)), color)
-              case (_, Text.Info(_, XML.Elem(Markup(Isabelle_Markup.BAD, _), _))) =>
-                (None, Some(color_value("bad_color")))
-              case (_, Text.Info(_, XML.Elem(Markup(Isabelle_Markup.HILITE, _), _))) =>
-                (None, Some(color_value("hilite_color")))
-            })
-        color <-
-          (result match {
-            case (Some(status), opt_color) =>
-              if (status.is_unprocessed) Some(color_value("unprocessed1_color"))
-              else if (status.is_running) Some(color_value("running1_color"))
-              else opt_color
-            case (_, opt_color) => opt_color
-          })
-      } yield Text.Info(r, color)
-  }
-
-
-  def background2(snapshot: Document.Snapshot, range: Text.Range): Stream[Text.Info[Color]] =
-    snapshot.select_markup(range,
-      Some(Set(Isabelle_Markup.TOKEN_RANGE)),
-      {
-        case Text.Info(_, XML.Elem(Markup(Isabelle_Markup.TOKEN_RANGE, _), _)) =>
-          color_value("light_color")
-      })
-
-
-  def foreground(snapshot: Document.Snapshot, range: Text.Range): Stream[Text.Info[Color]] =
-    snapshot.select_markup(range,
-      Some(Set(Isabelle_Markup.STRING, Isabelle_Markup.ALTSTRING, Isabelle_Markup.VERBATIM)),
-      {
-        case Text.Info(_, XML.Elem(Markup(Isabelle_Markup.STRING, _), _)) =>
-          color_value("quoted_color")
-        case Text.Info(_, XML.Elem(Markup(Isabelle_Markup.ALTSTRING, _), _)) =>
-          color_value("quoted_color")
-        case Text.Info(_, XML.Elem(Markup(Isabelle_Markup.VERBATIM, _), _)) =>
-          color_value("quoted_color")
-      })
-
-
-  def text_color(snapshot: Document.Snapshot, range: Text.Range, color: Color)
-      : Stream[Text.Info[Color]] =
-  {
-    val text_colors: Map[String, Color] = Map(
-      Isabelle_Markup.STRING -> Color.BLACK,
-      Isabelle_Markup.ALTSTRING -> Color.BLACK,
-      Isabelle_Markup.VERBATIM -> Color.BLACK,
-      Isabelle_Markup.LITERAL -> color_value("keyword1_color"),
-      Isabelle_Markup.DELIMITER -> Color.BLACK,
-      Isabelle_Markup.TFREE -> color_value("tfree_color"),
-      Isabelle_Markup.TVAR -> color_value("tvar_color"),
-      Isabelle_Markup.FREE -> color_value("free_color"),
-      Isabelle_Markup.SKOLEM -> color_value("skolem_color"),
-      Isabelle_Markup.BOUND -> color_value("bound_color"),
-      Isabelle_Markup.VAR -> color_value("var_color"),
-      Isabelle_Markup.INNER_STRING -> color_value("inner_quoted_color"),
-      Isabelle_Markup.INNER_COMMENT -> color_value("inner_comment_color"),
-      Isabelle_Markup.DYNAMIC_FACT -> color_value("dynamic_color"),
-      Isabelle_Markup.ML_KEYWORD -> color_value("keyword1_color"),
-      Isabelle_Markup.ML_DELIMITER -> Color.BLACK,
-      Isabelle_Markup.ML_NUMERAL -> color_value("inner_numeral_color"),
-      Isabelle_Markup.ML_CHAR -> color_value("inner_quoted_color"),
-      Isabelle_Markup.ML_STRING -> color_value("inner_quoted_color"),
-      Isabelle_Markup.ML_COMMENT -> color_value("inner_comment_color"),
-      Isabelle_Markup.ANTIQ -> color_value("antiquotation_color"))
-
-    val text_color_elements = Set.empty[String] ++ text_colors.keys
-
-    snapshot.cumulate_markup(range, color, Some(text_color_elements),
-      {
-        case (_, Text.Info(_, XML.Elem(Markup(m, _), _)))
-        if text_colors.isDefinedAt(m) => text_colors(m)
-      })
-  }
-
 
   /* token markup -- text styles */
 
@@ -382,3 +78,346 @@
     else if (token.is_operator) JEditToken.OPERATOR
     else token_style(token.kind)
 }
+
+
+class Isabelle_Rendering private(val snapshot: Document.Snapshot, val options: Options)
+{
+  /* colors */
+
+  def color_value(s: String): Color = Color_Value(options.string(s))
+
+  val outdated_color = color_value("outdated_color")
+  val unprocessed_color = color_value("unprocessed_color")
+  val unprocessed1_color = color_value("unprocessed1_color")
+  val running_color = color_value("running_color")
+  val running1_color = color_value("running1_color")
+  val light_color = color_value("light_color")
+  val writeln_color = color_value("writeln_color")
+  val warning_color = color_value("warning_color")
+  val error_color = color_value("error_color")
+  val error1_color = color_value("error1_color")
+  val bad_color = color_value("bad_color")
+  val hilite_color = color_value("hilite_color")
+  val quoted_color = color_value("quoted_color")
+  val subexp_color = color_value("subexp_color")
+  val hyperlink_color = color_value("hyperlink_color")
+  val keyword1_color = color_value("keyword1_color")
+  val keyword2_color = color_value("keyword2_color")
+
+  val tfree_color = color_value("tfree_color")
+  val tvar_color = color_value("tvar_color")
+  val free_color = color_value("free_color")
+  val skolem_color = color_value("skolem_color")
+  val bound_color = color_value("bound_color")
+  val var_color = color_value("var_color")
+  val inner_numeral_color = color_value("inner_numeral_color")
+  val inner_quoted_color = color_value("inner_quoted_color")
+  val inner_comment_color = color_value("inner_comment_color")
+  val antiquotation_color = color_value("antiquotation_color")
+  val dynamic_color = color_value("dynamic_color")
+
+
+  /* command overview */
+
+  def overview_color(range: Text.Range): Option[Color] =
+  {
+    if (snapshot.is_outdated) None
+    else {
+      val results =
+        snapshot.cumulate_markup[(Protocol.Status, Int)](
+          range, (Protocol.Status.init, 0),
+          Some(Protocol.command_status_markup + Isabelle_Markup.WARNING + Isabelle_Markup.ERROR),
+          {
+            case ((status, pri), Text.Info(_, XML.Elem(markup, _))) =>
+              if (markup.name == Isabelle_Markup.WARNING)
+                (status, pri max Isabelle_Rendering.warning_pri)
+              else if (markup.name == Isabelle_Markup.ERROR)
+                (status, pri max Isabelle_Rendering.error_pri)
+              else (Protocol.command_status(status, markup), pri)
+          })
+      if (results.isEmpty) None
+      else {
+        val (status, pri) =
+          ((Protocol.Status.init, 0) /: results) {
+            case ((s1, p1), Text.Info(_, (s2, p2))) => (s1 + s2, p1 max p2) }
+
+        if (pri == Isabelle_Rendering.warning_pri) Some(warning_color)
+        else if (pri == Isabelle_Rendering.error_pri) Some(error_color)
+        else if (status.is_unprocessed) Some(unprocessed_color)
+        else if (status.is_running) Some(running_color)
+        else if (status.is_failed) Some(error_color)
+        else None
+      }
+    }
+  }
+
+
+  /* markup selectors */
+
+  private val subexp_include =
+    Set(Isabelle_Markup.SORT, Isabelle_Markup.TYP, Isabelle_Markup.TERM, Isabelle_Markup.PROP,
+      Isabelle_Markup.ML_TYPING, Isabelle_Markup.TOKEN_RANGE, Isabelle_Markup.ENTITY,
+      Isabelle_Markup.PATH, Isabelle_Markup.TYPING, Isabelle_Markup.FREE, Isabelle_Markup.SKOLEM,
+      Isabelle_Markup.BOUND, Isabelle_Markup.VAR, Isabelle_Markup.TFREE, Isabelle_Markup.TVAR,
+      Isabelle_Markup.ML_SOURCE, Isabelle_Markup.DOCUMENT_SOURCE)
+
+  def subexp(range: Text.Range): Option[Text.Info[Color]] =
+  {
+    snapshot.select_markup(range, Some(subexp_include),
+        {
+          case Text.Info(info_range, XML.Elem(Markup(name, _), _)) if subexp_include(name) =>
+            Text.Info(snapshot.convert(info_range), subexp_color)
+        }) match { case Text.Info(_, info) #:: _ => Some(info) case _ => None }
+  }
+
+
+  private val hyperlink_include = Set(Isabelle_Markup.ENTITY, Isabelle_Markup.PATH)
+
+  def hyperlink(range: Text.Range): Option[Text.Info[Hyperlink]] =
+  {
+    snapshot.cumulate_markup[List[Text.Info[Hyperlink]]](range, Nil, Some(hyperlink_include),
+        {
+          case (links, Text.Info(info_range, XML.Elem(Isabelle_Markup.Path(name), _)))
+          if Path.is_ok(name) =>
+            val jedit_file = Isabelle.thy_load.append(snapshot.node_name.dir, Path.explode(name))
+            Text.Info(snapshot.convert(info_range), Hyperlink(jedit_file, 0, 0)) :: links
+
+          case (links, Text.Info(info_range, XML.Elem(Markup(Isabelle_Markup.ENTITY, props), _)))
+          if (props.find(
+            { case (Markup.KIND, Isabelle_Markup.ML_OPEN) => true
+              case (Markup.KIND, Isabelle_Markup.ML_STRUCT) => true
+              case _ => false }).isEmpty) =>
+
+            props match {
+              case Position.Line_File(line, name) if Path.is_ok(name) =>
+                Isabelle_System.source_file(Path.explode(name)) match {
+                  case Some(path) =>
+                    val jedit_file = Isabelle_System.platform_path(path)
+                    Text.Info(snapshot.convert(info_range), Hyperlink(jedit_file, line, 0)) :: links
+                  case None => links
+                }
+
+              case Position.Id_Offset(id, offset) if !snapshot.is_outdated =>
+                snapshot.state.find_command(snapshot.version, id) match {
+                  case Some((node, command)) =>
+                    val sources =
+                      node.commands.iterator.takeWhile(_ != command).map(_.source) ++
+                        Iterator.single(command.source(Text.Range(0, command.decode(offset))))
+                    val (line, column) = ((1, 1) /: sources)(Symbol.advance_line_column)
+                    Text.Info(snapshot.convert(info_range),
+                      Hyperlink(command.node_name.node, line, column)) :: links
+                  case None => links
+                }
+
+              case _ => links
+            }
+        }) match { case Text.Info(_, info :: _) #:: _ => Some(info) case _ => None }
+  }
+
+
+  private def tooltip_text(msg: XML.Tree): String =
+    Pretty.string_of(List(msg), margin = options.int("jedit_tooltip_margin"))
+
+  def tooltip_message(range: Text.Range): Option[String] =
+  {
+    val msgs =
+      snapshot.cumulate_markup[SortedMap[Long, String]](range, SortedMap.empty,
+        Some(Set(Isabelle_Markup.WRITELN, Isabelle_Markup.WARNING, Isabelle_Markup.ERROR,
+          Isabelle_Markup.BAD)),
+        {
+          case (msgs, Text.Info(_, msg @
+              XML.Elem(Markup(markup, Isabelle_Markup.Serial(serial)), _)))
+          if markup == Isabelle_Markup.WRITELN ||
+              markup == Isabelle_Markup.WARNING ||
+              markup == Isabelle_Markup.ERROR =>
+            msgs + (serial -> tooltip_text(msg))
+          case (msgs, Text.Info(_, msg @ XML.Elem(Markup(Isabelle_Markup.BAD, _), body)))
+          if !body.isEmpty => msgs + (Document.new_id() -> tooltip_text(msg))
+        }).toList.flatMap(_.info)
+    if (msgs.isEmpty) None else Some(cat_lines(msgs.iterator.map(_._2)))
+  }
+
+
+  private val tooltips: Map[String, String] =
+    Map(
+      Isabelle_Markup.SORT -> "sort",
+      Isabelle_Markup.TYP -> "type",
+      Isabelle_Markup.TERM -> "term",
+      Isabelle_Markup.PROP -> "proposition",
+      Isabelle_Markup.TOKEN_RANGE -> "inner syntax token",
+      Isabelle_Markup.FREE -> "free variable",
+      Isabelle_Markup.SKOLEM -> "skolem variable",
+      Isabelle_Markup.BOUND -> "bound variable",
+      Isabelle_Markup.VAR -> "schematic variable",
+      Isabelle_Markup.TFREE -> "free type variable",
+      Isabelle_Markup.TVAR -> "schematic type variable",
+      Isabelle_Markup.ML_SOURCE -> "ML source",
+      Isabelle_Markup.DOCUMENT_SOURCE -> "document source")
+
+  private val tooltip_elements =
+    Set(Isabelle_Markup.ENTITY, Isabelle_Markup.TYPING, Isabelle_Markup.ML_TYPING,
+      Isabelle_Markup.PATH) ++ tooltips.keys
+
+  private def string_of_typing(kind: String, body: XML.Body): String =
+    Pretty.string_of(List(Pretty.block(XML.Text(kind) :: Pretty.Break(1) :: body)),
+      margin = options.int("jedit_tooltip_margin"))
+
+  def tooltip(range: Text.Range): Option[String] =
+  {
+    def add(prev: Text.Info[List[(Boolean, String)]], r: Text.Range, p: (Boolean, String)) =
+      if (prev.range == r) Text.Info(r, p :: prev.info) else Text.Info(r, List(p))
+
+    val tips =
+      snapshot.cumulate_markup[Text.Info[(List[(Boolean, String)])]](
+        range, Text.Info(range, Nil), Some(tooltip_elements),
+        {
+          case (prev, Text.Info(r, XML.Elem(Isabelle_Markup.Entity(kind, name), _))) =>
+            val kind1 = space_explode('_', kind).mkString(" ")
+            add(prev, r, (true, kind1 + " " + quote(name)))
+          case (prev, Text.Info(r, XML.Elem(Isabelle_Markup.Path(name), _)))
+          if Path.is_ok(name) =>
+            val jedit_file = Isabelle.thy_load.append(snapshot.node_name.dir, Path.explode(name))
+            add(prev, r, (true, "file " + quote(jedit_file)))
+          case (prev, Text.Info(r, XML.Elem(Markup(Isabelle_Markup.TYPING, _), body))) =>
+            add(prev, r, (true, string_of_typing("::", body)))
+          case (prev, Text.Info(r, XML.Elem(Markup(Isabelle_Markup.ML_TYPING, _), body))) =>
+            add(prev, r, (false, string_of_typing("ML:", body)))
+          case (prev, Text.Info(r, XML.Elem(Markup(name, _), _)))
+          if tooltips.isDefinedAt(name) => add(prev, r, (true, tooltips(name)))
+        }).toList.flatMap(_.info.info)
+
+    val all_tips =
+      (tips.filter(_._1) ++ tips.filter(!_._1).lastOption.toList).map(_._2)
+    if (all_tips.isEmpty) None else Some(cat_lines(all_tips))
+  }
+
+
+  def gutter_message(range: Text.Range): Option[Icon] =
+  {
+    val results =
+      snapshot.cumulate_markup[Int](range, 0,
+        Some(Set(Isabelle_Markup.WARNING, Isabelle_Markup.ERROR)),
+        {
+          case (pri, Text.Info(_, XML.Elem(Markup(Isabelle_Markup.WARNING, _), body))) =>
+            body match {
+              case List(XML.Elem(Markup(Isabelle_Markup.LEGACY, _), _)) =>
+                pri max Isabelle_Rendering.legacy_pri
+              case _ => pri max Isabelle_Rendering.warning_pri
+            }
+          case (pri, Text.Info(_, XML.Elem(Markup(Isabelle_Markup.ERROR, _), _))) =>
+            pri max Isabelle_Rendering.error_pri
+        })
+    val pri = (0 /: results) { case (p1, Text.Info(_, p2)) => p1 max p2 }
+    Isabelle_Rendering.gutter_icons.get(pri)
+  }
+
+
+  private val squiggly_colors = Map(
+    Isabelle_Rendering.writeln_pri -> writeln_color,
+    Isabelle_Rendering.warning_pri -> warning_color,
+    Isabelle_Rendering.error_pri -> error_color)
+
+  def squiggly_underline(range: Text.Range): Stream[Text.Info[Color]] =
+  {
+    val results =
+      snapshot.cumulate_markup[Int](range, 0,
+        Some(Set(Isabelle_Markup.WRITELN, Isabelle_Markup.WARNING, Isabelle_Markup.ERROR)),
+        {
+          case (pri, Text.Info(_, XML.Elem(Markup(name, _), _))) =>
+            name match {
+              case Isabelle_Markup.WRITELN => pri max Isabelle_Rendering.writeln_pri
+              case Isabelle_Markup.WARNING => pri max Isabelle_Rendering.warning_pri
+              case Isabelle_Markup.ERROR => pri max Isabelle_Rendering.error_pri
+              case _ => pri
+            }
+        })
+    for {
+      Text.Info(r, pri) <- results
+      color <- squiggly_colors.get(pri)
+    } yield Text.Info(r, color)
+  }
+
+
+  def background1(range: Text.Range): Stream[Text.Info[Color]] =
+  {
+    if (snapshot.is_outdated) Stream(Text.Info(range, outdated_color))
+    else
+      for {
+        Text.Info(r, result) <-
+          snapshot.cumulate_markup[(Option[Protocol.Status], Option[Color])](
+            range, (Some(Protocol.Status.init), None),
+            Some(Protocol.command_status_markup + Isabelle_Markup.BAD + Isabelle_Markup.HILITE),
+            {
+              case (((Some(status), color), Text.Info(_, XML.Elem(markup, _))))
+              if (Protocol.command_status_markup(markup.name)) =>
+                (Some(Protocol.command_status(status, markup)), color)
+              case (_, Text.Info(_, XML.Elem(Markup(Isabelle_Markup.BAD, _), _))) =>
+                (None, Some(bad_color))
+              case (_, Text.Info(_, XML.Elem(Markup(Isabelle_Markup.HILITE, _), _))) =>
+                (None, Some(hilite_color))
+            })
+        color <-
+          (result match {
+            case (Some(status), opt_color) =>
+              if (status.is_unprocessed) Some(unprocessed1_color)
+              else if (status.is_running) Some(running1_color)
+              else opt_color
+            case (_, opt_color) => opt_color
+          })
+      } yield Text.Info(r, color)
+  }
+
+
+  def background2(range: Text.Range): Stream[Text.Info[Color]] =
+    snapshot.select_markup(range,
+      Some(Set(Isabelle_Markup.TOKEN_RANGE)),
+      {
+        case Text.Info(_, XML.Elem(Markup(Isabelle_Markup.TOKEN_RANGE, _), _)) => light_color
+      })
+
+
+  def foreground(range: Text.Range): Stream[Text.Info[Color]] =
+    snapshot.select_markup(range,
+      Some(Set(Isabelle_Markup.STRING, Isabelle_Markup.ALTSTRING, Isabelle_Markup.VERBATIM)),
+      {
+        case Text.Info(_, XML.Elem(Markup(Isabelle_Markup.STRING, _), _)) => quoted_color
+        case Text.Info(_, XML.Elem(Markup(Isabelle_Markup.ALTSTRING, _), _)) => quoted_color
+        case Text.Info(_, XML.Elem(Markup(Isabelle_Markup.VERBATIM, _), _)) => quoted_color
+      })
+
+
+  private val text_colors: Map[String, Color] = Map(
+      Isabelle_Markup.STRING -> Color.BLACK,
+      Isabelle_Markup.ALTSTRING -> Color.BLACK,
+      Isabelle_Markup.VERBATIM -> Color.BLACK,
+      Isabelle_Markup.LITERAL -> keyword1_color,
+      Isabelle_Markup.DELIMITER -> Color.BLACK,
+      Isabelle_Markup.TFREE -> tfree_color,
+      Isabelle_Markup.TVAR -> tvar_color,
+      Isabelle_Markup.FREE -> free_color,
+      Isabelle_Markup.SKOLEM -> skolem_color,
+      Isabelle_Markup.BOUND -> bound_color,
+      Isabelle_Markup.VAR -> var_color,
+      Isabelle_Markup.INNER_STRING -> inner_quoted_color,
+      Isabelle_Markup.INNER_COMMENT -> inner_comment_color,
+      Isabelle_Markup.DYNAMIC_FACT -> dynamic_color,
+      Isabelle_Markup.ML_KEYWORD -> keyword1_color,
+      Isabelle_Markup.ML_DELIMITER -> Color.BLACK,
+      Isabelle_Markup.ML_NUMERAL -> inner_numeral_color,
+      Isabelle_Markup.ML_CHAR -> inner_quoted_color,
+      Isabelle_Markup.ML_STRING -> inner_quoted_color,
+      Isabelle_Markup.ML_COMMENT -> inner_comment_color,
+      Isabelle_Markup.ANTIQ -> antiquotation_color)
+
+  private val text_color_elements = Set.empty[String] ++ text_colors.keys
+
+  def text_color(range: Text.Range, color: Color)
+      : Stream[Text.Info[Color]] =
+  {
+    snapshot.cumulate_markup(range, color, Some(text_color_elements),
+      {
+        case (_, Text.Info(_, XML.Elem(Markup(m, _), _)))
+        if text_colors.isDefinedAt(m) => text_colors(m)
+      })
+  }
+}
--- a/src/Tools/jEdit/src/jedit_options.scala	Fri Sep 14 12:46:33 2012 +0200
+++ b/src/Tools/jEdit/src/jedit_options.scala	Fri Sep 14 13:52:16 2012 +0200
@@ -9,6 +9,7 @@
 
 import isabelle._
 
+import java.awt.Color
 import javax.swing.{InputVerifier, JComponent, UIManager}
 import javax.swing.text.JTextComponent
 
@@ -26,6 +27,8 @@
 
 class JEdit_Options extends Options_Variable
 {
+  def color_value(s: String): Color = Color_Value(string(s))
+
   def make_color_component(opt: Options.Opt): Option_Component =
   {
     Swing_Thread.require()
--- a/src/Tools/jEdit/src/session_dockable.scala	Fri Sep 14 12:46:33 2012 +0200
+++ b/src/Tools/jEdit/src/session_dockable.scala	Fri Sep 14 13:52:16 2012 +0200
@@ -89,10 +89,10 @@
           var end = size.width - insets.right
           for {
             (n, color) <- List(
-              (st.unprocessed, Isabelle_Rendering.color_value("unprocessed1_color")),
-              (st.running, Isabelle_Rendering.color_value("running_color")),
-              (st.warned, Isabelle_Rendering.color_value("warning_color")),
-              (st.failed, Isabelle_Rendering.color_value("error_color"))) }
+              (st.unprocessed, Isabelle.options.color_value("unprocessed1_color")),
+              (st.running, Isabelle.options.color_value("running_color")),
+              (st.warned, Isabelle.options.color_value("warning_color")),
+              (st.failed, Isabelle.options.color_value("error_color"))) }
           {
             gfx.setColor(color)
             val v = (n * w / st.total) max (if (n > 0) 2 else 0)
--- a/src/Tools/jEdit/src/text_area_painter.scala	Fri Sep 14 12:46:33 2012 +0200
+++ b/src/Tools/jEdit/src/text_area_painter.scala	Fri Sep 14 13:52:16 2012 +0200
@@ -75,7 +75,7 @@
 
   /* common painter state */
 
-  @volatile private var painter_snapshot: Document.Snapshot = null
+  @volatile private var painter_rendering: Isabelle_Rendering = null
   @volatile private var painter_clip: Shape = null
 
   private val set_state = new TextAreaExtension
@@ -84,7 +84,7 @@
       first_line: Int, last_line: Int, physical_lines: Array[Int],
       start: Array[Int], end: Array[Int], y: Int, line_height: Int)
     {
-      painter_snapshot = model.snapshot()
+      painter_rendering = Isabelle_Rendering(model.snapshot(), Isabelle.options.value)
       painter_clip = gfx.getClip
     }
   }
@@ -95,14 +95,14 @@
       first_line: Int, last_line: Int, physical_lines: Array[Int],
       start: Array[Int], end: Array[Int], y: Int, line_height: Int)
     {
-      painter_snapshot = null
+      painter_rendering = null
       painter_clip = null
     }
   }
 
-  private def robust_snapshot(body: Document.Snapshot => Unit)
+  private def robust_rendering(body: Isabelle_Rendering => Unit)
   {
-    doc_view.robust_body(()) { body(painter_snapshot) }
+    doc_view.robust_body(()) { body(painter_rendering) }
   }
 
 
@@ -114,7 +114,7 @@
       first_line: Int, last_line: Int, physical_lines: Array[Int],
       start: Array[Int], end: Array[Int], y: Int, line_height: Int)
     {
-      robust_snapshot { snapshot =>
+      robust_rendering { rendering =>
         val ascent = text_area.getPainter.getFontMetrics.getAscent
 
         for (i <- 0 until physical_lines.length) {
@@ -123,7 +123,7 @@
 
             // background color (1)
             for {
-              Text.Info(range, color) <- Isabelle_Rendering.background1(snapshot, line_range)
+              Text.Info(range, color) <- rendering.background1(line_range)
               r <- gfx_range(range)
             } {
               gfx.setColor(color)
@@ -132,7 +132,7 @@
 
             // background color (2)
             for {
-              Text.Info(range, color) <- Isabelle_Rendering.background2(snapshot, line_range)
+              Text.Info(range, color) <- rendering.background2(line_range)
               r <- gfx_range(range)
             } {
               gfx.setColor(color)
@@ -141,7 +141,7 @@
 
             // squiggly underline
             for {
-              Text.Info(range, color) <- Isabelle_Rendering.squiggly_underline(snapshot, line_range)
+              Text.Info(range, color) <- rendering.squiggly_underline(line_range)
               r <- gfx_range(range)
             } {
               gfx.setColor(color)
@@ -161,7 +161,7 @@
 
   /* text */
 
-  private def paint_chunk_list(snapshot: Document.Snapshot,
+  private def paint_chunk_list(rendering: Isabelle_Rendering,
     gfx: Graphics2D, line_start: Text.Offset, head: Chunk, x: Float, y: Float): Float =
   {
     val clip_rect = gfx.getClipBounds
@@ -190,7 +190,7 @@
 
         val markup =
           for {
-            r1 <- Isabelle_Rendering.text_color(snapshot, chunk_range, chunk_color)
+            r1 <- rendering.text_color(chunk_range, chunk_color)
             r2 <- r1.try_restrict(chunk_range)
           } yield r2
 
@@ -246,7 +246,7 @@
       first_line: Int, last_line: Int, physical_lines: Array[Int],
       start: Array[Int], end: Array[Int], y: Int, line_height: Int)
     {
-      robust_snapshot { snapshot =>
+      robust_rendering { rendering =>
         val clip = gfx.getClip
         val x0 = text_area.getHorizontalOffset
         val fm = text_area.getPainter.getFontMetrics
@@ -260,7 +260,7 @@
             if (chunks != null) {
               val line_start = text_area.getBuffer.getLineStartOffset(line)
               gfx.clipRect(x0, y + line_height * i, Integer.MAX_VALUE, line_height)
-              val w = paint_chunk_list(snapshot, gfx, line_start, chunks, x0, y0).toInt
+              val w = paint_chunk_list(rendering, gfx, line_start, chunks, x0, y0).toInt
               gfx.clipRect(x0 + w.toInt, 0, Integer.MAX_VALUE, Integer.MAX_VALUE)
               orig_text_painter.paintValidLine(gfx,
                 screen_line, line, start(i), end(i), y + line_height * i)
@@ -282,14 +282,14 @@
       first_line: Int, last_line: Int, physical_lines: Array[Int],
       start: Array[Int], end: Array[Int], y: Int, line_height: Int)
     {
-      robust_snapshot { snapshot =>
+      robust_rendering { rendering =>
         for (i <- 0 until physical_lines.length) {
           if (physical_lines(i) != -1) {
             val line_range = doc_view.proper_line_range(start(i), end(i))
 
             // foreground color
             for {
-              Text.Info(range, color) <- Isabelle_Rendering.foreground(snapshot, line_range)
+              Text.Info(range, color) <- rendering.foreground(line_range)
               r <- gfx_range(range)
             } {
               gfx.setColor(color)
@@ -312,7 +312,7 @@
               Text.Info(range, _) <- info.try_restrict(line_range)
               r <- gfx_range(range)
             } {
-              gfx.setColor(Isabelle_Rendering.color_value("hyperlink_color"))
+              gfx.setColor(rendering.hyperlink_color)
               gfx.drawRect(r.x, y + i * line_height, r.length - 1, line_height - 1)
             }
           }
@@ -329,7 +329,7 @@
     override def paintValidLine(gfx: Graphics2D,
       screen_line: Int, physical_line: Int, start: Int, end: Int, y: Int)
     {
-      robust_snapshot { _ =>
+      robust_rendering { _ =>
         if (before) gfx.clipRect(0, 0, 0, 0)
         else gfx.setClip(painter_clip)
       }
@@ -346,7 +346,7 @@
     override def paintValidLine(gfx: Graphics2D,
       screen_line: Int, physical_line: Int, start: Int, end: Int, y: Int)
     {
-      robust_snapshot { _ =>
+      robust_rendering { _ =>
         if (text_area.isCaretVisible) {
           val caret = text_area.getCaretPosition
           if (start <= caret && caret == end - 1) {
--- a/src/Tools/jEdit/src/text_overview.scala	Fri Sep 14 12:46:33 2012 +0200
+++ b/src/Tools/jEdit/src/text_overview.scala	Fri Sep 14 13:52:16 2012 +0200
@@ -54,6 +54,7 @@
   private var cached_colors: List[(Color, Int, Int)] = Nil
 
   private var last_snapshot = Document.State.init.snapshot()
+  private var last_options = Isabelle.options.value
   private var last_line_count = 0
   private var last_char_count = 0
   private var last_L = 0
@@ -69,7 +70,7 @@
         val snapshot = doc_view.model.snapshot()
 
         if (snapshot.is_outdated) {
-          gfx.setColor(Isabelle_Rendering.color_value("outdated_color"))
+          gfx.setColor(Isabelle.options.color_value("outdated_color"))
           gfx.asInstanceOf[Graphics2D].fill(gfx.getClipBounds)
         }
         else {
@@ -82,9 +83,14 @@
           val L = lines()
           val H = getHeight()
 
+          val options = Isabelle.options.value
+
           if (!(line_count == last_line_count && char_count == last_char_count &&
-                L == last_L && H == last_H && (snapshot eq_markup last_snapshot)))
+                L == last_L && H == last_H && (snapshot eq_markup last_snapshot) &&
+                (options eq last_options)))
           {
+            val rendering = Isabelle_Rendering(snapshot, options)
+
             @tailrec def loop(l: Int, h: Int, p: Int, q: Int, colors: List[(Color, Int, Int)])
               : List[(Color, Int, Int)] =
             {
@@ -102,7 +108,7 @@
                 val range = Text.Range(start, end)
 
                 val colors1 =
-                  (Isabelle_Rendering.overview_color(snapshot, range), colors) match {
+                  (rendering.overview_color(range), colors) match {
                     case (Some(color), (old_color, old_h, old_h1) :: rest)
                     if color == old_color && old_h1 == h => (color, old_h, h1) :: rest
                     case (Some(color), _) => (color, h, h1) :: colors
@@ -115,6 +121,7 @@
             cached_colors = loop(0, 0, 0, 0, Nil)
 
             last_snapshot = snapshot
+            last_options = options
             last_line_count = line_count
             last_char_count = char_count
             last_L = L