src/Tools/jEdit/src/isabelle_rendering.scala
changeset 49356 6e0c0ffb6ec7
parent 49355 7d1af0a6e797
child 49358 0fa351b1bd14
equal deleted inserted replaced
49355:7d1af0a6e797 49356:6e0c0ffb6ec7
    18 import scala.collection.immutable.SortedMap
    18 import scala.collection.immutable.SortedMap
    19 
    19 
    20 
    20 
    21 object Isabelle_Rendering
    21 object Isabelle_Rendering
    22 {
    22 {
       
    23   def apply(snapshot: Document.Snapshot, options: Options): Isabelle_Rendering =
       
    24     new Isabelle_Rendering(snapshot, options)
       
    25 
       
    26 
    23   /* physical rendering */
    27   /* physical rendering */
    24 
       
    25   def color_value(s: String): Color = Color_Value(Isabelle.options.value.string(s))
       
    26 
    28 
    27   private val writeln_pri = 1
    29   private val writeln_pri = 1
    28   private val warning_pri = 2
    30   private val warning_pri = 2
    29   private val legacy_pri = 3
    31   private val legacy_pri = 3
    30   private val error_pri = 4
    32   private val error_pri = 4
    31 
    33 
    32 
       
    33   /* command overview */
       
    34 
       
    35   def overview_color(snapshot: Document.Snapshot, range: Text.Range): Option[Color] =
       
    36   {
       
    37     if (snapshot.is_outdated) None
       
    38     else {
       
    39       val results =
       
    40         snapshot.cumulate_markup[(Protocol.Status, Int)](
       
    41           range, (Protocol.Status.init, 0),
       
    42           Some(Protocol.command_status_markup + Isabelle_Markup.WARNING + Isabelle_Markup.ERROR),
       
    43           {
       
    44             case ((status, pri), Text.Info(_, XML.Elem(markup, _))) =>
       
    45               if (markup.name == Isabelle_Markup.WARNING) (status, pri max warning_pri)
       
    46               else if (markup.name == Isabelle_Markup.ERROR) (status, pri max error_pri)
       
    47               else (Protocol.command_status(status, markup), pri)
       
    48           })
       
    49       if (results.isEmpty) None
       
    50       else {
       
    51         val (status, pri) =
       
    52           ((Protocol.Status.init, 0) /: results) {
       
    53             case ((s1, p1), Text.Info(_, (s2, p2))) => (s1 + s2, p1 max p2) }
       
    54 
       
    55         if (pri == warning_pri) Some(color_value("warning_color"))
       
    56         else if (pri == error_pri) Some(color_value("error_color"))
       
    57         else if (status.is_unprocessed) Some(color_value("unprocessed_color"))
       
    58         else if (status.is_running) Some(color_value("running_color"))
       
    59         else if (status.is_failed) Some(color_value("error_color"))
       
    60         else None
       
    61       }
       
    62     }
       
    63   }
       
    64 
       
    65 
       
    66   /* markup selectors */
       
    67 
       
    68   private val subexp_include =
       
    69     Set(Isabelle_Markup.SORT, Isabelle_Markup.TYP, Isabelle_Markup.TERM, Isabelle_Markup.PROP,
       
    70       Isabelle_Markup.ML_TYPING, Isabelle_Markup.TOKEN_RANGE, Isabelle_Markup.ENTITY,
       
    71       Isabelle_Markup.PATH, Isabelle_Markup.TYPING, Isabelle_Markup.FREE, Isabelle_Markup.SKOLEM,
       
    72       Isabelle_Markup.BOUND, Isabelle_Markup.VAR, Isabelle_Markup.TFREE, Isabelle_Markup.TVAR,
       
    73       Isabelle_Markup.ML_SOURCE, Isabelle_Markup.DOCUMENT_SOURCE)
       
    74 
       
    75   def subexp(snapshot: Document.Snapshot, range: Text.Range): Option[Text.Info[Color]] =
       
    76   {
       
    77     snapshot.select_markup(range, Some(subexp_include),
       
    78         {
       
    79           case Text.Info(info_range, XML.Elem(Markup(name, _), _)) if subexp_include(name) =>
       
    80             Text.Info(snapshot.convert(info_range), color_value("subexp_color"))
       
    81         }) match { case Text.Info(_, info) #:: _ => Some(info) case _ => None }
       
    82   }
       
    83 
       
    84 
       
    85   private val hyperlink_include = Set(Isabelle_Markup.ENTITY, Isabelle_Markup.PATH)
       
    86 
       
    87   def hyperlink(snapshot: Document.Snapshot, range: Text.Range): Option[Text.Info[Hyperlink]] =
       
    88   {
       
    89     snapshot.cumulate_markup[List[Text.Info[Hyperlink]]](range, Nil, Some(hyperlink_include),
       
    90         {
       
    91           case (links, Text.Info(info_range, XML.Elem(Isabelle_Markup.Path(name), _)))
       
    92           if Path.is_ok(name) =>
       
    93             val jedit_file = Isabelle.thy_load.append(snapshot.node_name.dir, Path.explode(name))
       
    94             Text.Info(snapshot.convert(info_range), Hyperlink(jedit_file, 0, 0)) :: links
       
    95 
       
    96           case (links, Text.Info(info_range, XML.Elem(Markup(Isabelle_Markup.ENTITY, props), _)))
       
    97           if (props.find(
       
    98             { case (Markup.KIND, Isabelle_Markup.ML_OPEN) => true
       
    99               case (Markup.KIND, Isabelle_Markup.ML_STRUCT) => true
       
   100               case _ => false }).isEmpty) =>
       
   101 
       
   102             props match {
       
   103               case Position.Line_File(line, name) if Path.is_ok(name) =>
       
   104                 Isabelle_System.source_file(Path.explode(name)) match {
       
   105                   case Some(path) =>
       
   106                     val jedit_file = Isabelle_System.platform_path(path)
       
   107                     Text.Info(snapshot.convert(info_range), Hyperlink(jedit_file, line, 0)) :: links
       
   108                   case None => links
       
   109                 }
       
   110 
       
   111               case Position.Id_Offset(id, offset) if !snapshot.is_outdated =>
       
   112                 snapshot.state.find_command(snapshot.version, id) match {
       
   113                   case Some((node, command)) =>
       
   114                     val sources =
       
   115                       node.commands.iterator.takeWhile(_ != command).map(_.source) ++
       
   116                         Iterator.single(command.source(Text.Range(0, command.decode(offset))))
       
   117                     val (line, column) = ((1, 1) /: sources)(Symbol.advance_line_column)
       
   118                     Text.Info(snapshot.convert(info_range),
       
   119                       Hyperlink(command.node_name.node, line, column)) :: links
       
   120                   case None => links
       
   121                 }
       
   122 
       
   123               case _ => links
       
   124             }
       
   125         }) match { case Text.Info(_, info :: _) #:: _ => Some(info) case _ => None }
       
   126   }
       
   127 
       
   128 
       
   129   private def tooltip_text(msg: XML.Tree): String =
       
   130     Pretty.string_of(List(msg), margin = Isabelle.options.int("jedit_tooltip_margin"))
       
   131 
       
   132   def tooltip_message(snapshot: Document.Snapshot, range: Text.Range): Option[String] =
       
   133   {
       
   134     val msgs =
       
   135       snapshot.cumulate_markup[SortedMap[Long, String]](range, SortedMap.empty,
       
   136         Some(Set(Isabelle_Markup.WRITELN, Isabelle_Markup.WARNING, Isabelle_Markup.ERROR,
       
   137           Isabelle_Markup.BAD)),
       
   138         {
       
   139           case (msgs, Text.Info(_, msg @
       
   140               XML.Elem(Markup(markup, Isabelle_Markup.Serial(serial)), _)))
       
   141           if markup == Isabelle_Markup.WRITELN ||
       
   142               markup == Isabelle_Markup.WARNING ||
       
   143               markup == Isabelle_Markup.ERROR =>
       
   144             msgs + (serial -> tooltip_text(msg))
       
   145           case (msgs, Text.Info(_, msg @ XML.Elem(Markup(Isabelle_Markup.BAD, _), body)))
       
   146           if !body.isEmpty => msgs + (Document.new_id() -> tooltip_text(msg))
       
   147         }).toList.flatMap(_.info)
       
   148     if (msgs.isEmpty) None else Some(cat_lines(msgs.iterator.map(_._2)))
       
   149   }
       
   150 
       
   151 
       
   152   private val tooltips: Map[String, String] =
       
   153     Map(
       
   154       Isabelle_Markup.SORT -> "sort",
       
   155       Isabelle_Markup.TYP -> "type",
       
   156       Isabelle_Markup.TERM -> "term",
       
   157       Isabelle_Markup.PROP -> "proposition",
       
   158       Isabelle_Markup.TOKEN_RANGE -> "inner syntax token",
       
   159       Isabelle_Markup.FREE -> "free variable",
       
   160       Isabelle_Markup.SKOLEM -> "skolem variable",
       
   161       Isabelle_Markup.BOUND -> "bound variable",
       
   162       Isabelle_Markup.VAR -> "schematic variable",
       
   163       Isabelle_Markup.TFREE -> "free type variable",
       
   164       Isabelle_Markup.TVAR -> "schematic type variable",
       
   165       Isabelle_Markup.ML_SOURCE -> "ML source",
       
   166       Isabelle_Markup.DOCUMENT_SOURCE -> "document source")
       
   167 
       
   168   private val tooltip_elements =
       
   169     Set(Isabelle_Markup.ENTITY, Isabelle_Markup.TYPING, Isabelle_Markup.ML_TYPING,
       
   170       Isabelle_Markup.PATH) ++ tooltips.keys
       
   171 
       
   172   private def string_of_typing(kind: String, body: XML.Body): String =
       
   173     Pretty.string_of(List(Pretty.block(XML.Text(kind) :: Pretty.Break(1) :: body)),
       
   174       margin = Isabelle.options.int("jedit_tooltip_margin"))
       
   175 
       
   176   def tooltip(snapshot: Document.Snapshot, range: Text.Range): Option[String] =
       
   177   {
       
   178     def add(prev: Text.Info[List[(Boolean, String)]], r: Text.Range, p: (Boolean, String)) =
       
   179       if (prev.range == r) Text.Info(r, p :: prev.info) else Text.Info(r, List(p))
       
   180 
       
   181     val tips =
       
   182       snapshot.cumulate_markup[Text.Info[(List[(Boolean, String)])]](
       
   183         range, Text.Info(range, Nil), Some(tooltip_elements),
       
   184         {
       
   185           case (prev, Text.Info(r, XML.Elem(Isabelle_Markup.Entity(kind, name), _))) =>
       
   186             val kind1 = space_explode('_', kind).mkString(" ")
       
   187             add(prev, r, (true, kind1 + " " + quote(name)))
       
   188           case (prev, Text.Info(r, XML.Elem(Isabelle_Markup.Path(name), _)))
       
   189           if Path.is_ok(name) =>
       
   190             val jedit_file = Isabelle.thy_load.append(snapshot.node_name.dir, Path.explode(name))
       
   191             add(prev, r, (true, "file " + quote(jedit_file)))
       
   192           case (prev, Text.Info(r, XML.Elem(Markup(Isabelle_Markup.TYPING, _), body))) =>
       
   193             add(prev, r, (true, string_of_typing("::", body)))
       
   194           case (prev, Text.Info(r, XML.Elem(Markup(Isabelle_Markup.ML_TYPING, _), body))) =>
       
   195             add(prev, r, (false, string_of_typing("ML:", body)))
       
   196           case (prev, Text.Info(r, XML.Elem(Markup(name, _), _)))
       
   197           if tooltips.isDefinedAt(name) => add(prev, r, (true, tooltips(name)))
       
   198         }).toList.flatMap(_.info.info)
       
   199 
       
   200     val all_tips =
       
   201       (tips.filter(_._1) ++ tips.filter(!_._1).lastOption.toList).map(_._2)
       
   202     if (all_tips.isEmpty) None else Some(cat_lines(all_tips))
       
   203   }
       
   204 
       
   205 
       
   206   private val gutter_icons = Map(
    34   private val gutter_icons = Map(
   207     warning_pri -> Isabelle.load_icon("16x16/status/dialog-information.png"),
    35     warning_pri -> Isabelle.load_icon("16x16/status/dialog-information.png"),
   208     legacy_pri -> Isabelle.load_icon("16x16/status/dialog-warning.png"),
    36     legacy_pri -> Isabelle.load_icon("16x16/status/dialog-warning.png"),
   209     error_pri -> Isabelle.load_icon("16x16/status/dialog-error.png"))
    37     error_pri -> Isabelle.load_icon("16x16/status/dialog-error.png"))
   210 
       
   211   def gutter_message(snapshot: Document.Snapshot, range: Text.Range): Option[Icon] =
       
   212   {
       
   213     val results =
       
   214       snapshot.cumulate_markup[Int](range, 0,
       
   215         Some(Set(Isabelle_Markup.WARNING, Isabelle_Markup.ERROR)),
       
   216         {
       
   217           case (pri, Text.Info(_, XML.Elem(Markup(Isabelle_Markup.WARNING, _), body))) =>
       
   218             body match {
       
   219               case List(XML.Elem(Markup(Isabelle_Markup.LEGACY, _), _)) => pri max legacy_pri
       
   220               case _ => pri max warning_pri
       
   221             }
       
   222           case (pri, Text.Info(_, XML.Elem(Markup(Isabelle_Markup.ERROR, _), _))) =>
       
   223             pri max error_pri
       
   224         })
       
   225     val pri = (0 /: results) { case (p1, Text.Info(_, p2)) => p1 max p2 }
       
   226     gutter_icons.get(pri)
       
   227   }
       
   228 
       
   229 
       
   230   def squiggly_underline(snapshot: Document.Snapshot, range: Text.Range): Stream[Text.Info[Color]] =
       
   231   {
       
   232     val squiggly_colors = Map(
       
   233       writeln_pri -> color_value("writeln_color"),
       
   234       warning_pri -> color_value("warning_color"),
       
   235       error_pri -> color_value("error_color"))
       
   236 
       
   237     val results =
       
   238       snapshot.cumulate_markup[Int](range, 0,
       
   239         Some(Set(Isabelle_Markup.WRITELN, Isabelle_Markup.WARNING, Isabelle_Markup.ERROR)),
       
   240         {
       
   241           case (pri, Text.Info(_, XML.Elem(Markup(name, _), _))) =>
       
   242             name match {
       
   243               case Isabelle_Markup.WRITELN => pri max writeln_pri
       
   244               case Isabelle_Markup.WARNING => pri max warning_pri
       
   245               case Isabelle_Markup.ERROR => pri max error_pri
       
   246               case _ => pri
       
   247             }
       
   248         })
       
   249     for {
       
   250       Text.Info(r, pri) <- results
       
   251       color <- squiggly_colors.get(pri)
       
   252     } yield Text.Info(r, color)
       
   253   }
       
   254 
       
   255 
       
   256   def background1(snapshot: Document.Snapshot, range: Text.Range): Stream[Text.Info[Color]] =
       
   257   {
       
   258     if (snapshot.is_outdated) Stream(Text.Info(range, color_value("outdated_color")))
       
   259     else
       
   260       for {
       
   261         Text.Info(r, result) <-
       
   262           snapshot.cumulate_markup[(Option[Protocol.Status], Option[Color])](
       
   263             range, (Some(Protocol.Status.init), None),
       
   264             Some(Protocol.command_status_markup + Isabelle_Markup.BAD + Isabelle_Markup.HILITE),
       
   265             {
       
   266               case (((Some(status), color), Text.Info(_, XML.Elem(markup, _))))
       
   267               if (Protocol.command_status_markup(markup.name)) =>
       
   268                 (Some(Protocol.command_status(status, markup)), color)
       
   269               case (_, Text.Info(_, XML.Elem(Markup(Isabelle_Markup.BAD, _), _))) =>
       
   270                 (None, Some(color_value("bad_color")))
       
   271               case (_, Text.Info(_, XML.Elem(Markup(Isabelle_Markup.HILITE, _), _))) =>
       
   272                 (None, Some(color_value("hilite_color")))
       
   273             })
       
   274         color <-
       
   275           (result match {
       
   276             case (Some(status), opt_color) =>
       
   277               if (status.is_unprocessed) Some(color_value("unprocessed1_color"))
       
   278               else if (status.is_running) Some(color_value("running1_color"))
       
   279               else opt_color
       
   280             case (_, opt_color) => opt_color
       
   281           })
       
   282       } yield Text.Info(r, color)
       
   283   }
       
   284 
       
   285 
       
   286   def background2(snapshot: Document.Snapshot, range: Text.Range): Stream[Text.Info[Color]] =
       
   287     snapshot.select_markup(range,
       
   288       Some(Set(Isabelle_Markup.TOKEN_RANGE)),
       
   289       {
       
   290         case Text.Info(_, XML.Elem(Markup(Isabelle_Markup.TOKEN_RANGE, _), _)) =>
       
   291           color_value("light_color")
       
   292       })
       
   293 
       
   294 
       
   295   def foreground(snapshot: Document.Snapshot, range: Text.Range): Stream[Text.Info[Color]] =
       
   296     snapshot.select_markup(range,
       
   297       Some(Set(Isabelle_Markup.STRING, Isabelle_Markup.ALTSTRING, Isabelle_Markup.VERBATIM)),
       
   298       {
       
   299         case Text.Info(_, XML.Elem(Markup(Isabelle_Markup.STRING, _), _)) =>
       
   300           color_value("quoted_color")
       
   301         case Text.Info(_, XML.Elem(Markup(Isabelle_Markup.ALTSTRING, _), _)) =>
       
   302           color_value("quoted_color")
       
   303         case Text.Info(_, XML.Elem(Markup(Isabelle_Markup.VERBATIM, _), _)) =>
       
   304           color_value("quoted_color")
       
   305       })
       
   306 
       
   307 
       
   308   def text_color(snapshot: Document.Snapshot, range: Text.Range, color: Color)
       
   309       : Stream[Text.Info[Color]] =
       
   310   {
       
   311     val text_colors: Map[String, Color] = Map(
       
   312       Isabelle_Markup.STRING -> Color.BLACK,
       
   313       Isabelle_Markup.ALTSTRING -> Color.BLACK,
       
   314       Isabelle_Markup.VERBATIM -> Color.BLACK,
       
   315       Isabelle_Markup.LITERAL -> color_value("keyword1_color"),
       
   316       Isabelle_Markup.DELIMITER -> Color.BLACK,
       
   317       Isabelle_Markup.TFREE -> color_value("tfree_color"),
       
   318       Isabelle_Markup.TVAR -> color_value("tvar_color"),
       
   319       Isabelle_Markup.FREE -> color_value("free_color"),
       
   320       Isabelle_Markup.SKOLEM -> color_value("skolem_color"),
       
   321       Isabelle_Markup.BOUND -> color_value("bound_color"),
       
   322       Isabelle_Markup.VAR -> color_value("var_color"),
       
   323       Isabelle_Markup.INNER_STRING -> color_value("inner_quoted_color"),
       
   324       Isabelle_Markup.INNER_COMMENT -> color_value("inner_comment_color"),
       
   325       Isabelle_Markup.DYNAMIC_FACT -> color_value("dynamic_color"),
       
   326       Isabelle_Markup.ML_KEYWORD -> color_value("keyword1_color"),
       
   327       Isabelle_Markup.ML_DELIMITER -> Color.BLACK,
       
   328       Isabelle_Markup.ML_NUMERAL -> color_value("inner_numeral_color"),
       
   329       Isabelle_Markup.ML_CHAR -> color_value("inner_quoted_color"),
       
   330       Isabelle_Markup.ML_STRING -> color_value("inner_quoted_color"),
       
   331       Isabelle_Markup.ML_COMMENT -> color_value("inner_comment_color"),
       
   332       Isabelle_Markup.ANTIQ -> color_value("antiquotation_color"))
       
   333 
       
   334     val text_color_elements = Set.empty[String] ++ text_colors.keys
       
   335 
       
   336     snapshot.cumulate_markup(range, color, Some(text_color_elements),
       
   337       {
       
   338         case (_, Text.Info(_, XML.Elem(Markup(m, _), _)))
       
   339         if text_colors.isDefinedAt(m) => text_colors(m)
       
   340       })
       
   341   }
       
   342 
    38 
   343 
    39 
   344   /* token markup -- text styles */
    40   /* token markup -- text styles */
   345 
    41 
   346   private val command_style: Map[String, Byte] =
    42   private val command_style: Map[String, Byte] =
   380   def token_markup(syntax: Outer_Syntax, token: Token): Byte =
    76   def token_markup(syntax: Outer_Syntax, token: Token): Byte =
   381     if (token.is_command) command_style(syntax.keyword_kind(token.content).getOrElse(""))
    77     if (token.is_command) command_style(syntax.keyword_kind(token.content).getOrElse(""))
   382     else if (token.is_operator) JEditToken.OPERATOR
    78     else if (token.is_operator) JEditToken.OPERATOR
   383     else token_style(token.kind)
    79     else token_style(token.kind)
   384 }
    80 }
       
    81 
       
    82 
       
    83 class Isabelle_Rendering private(val snapshot: Document.Snapshot, val options: Options)
       
    84 {
       
    85   /* colors */
       
    86 
       
    87   def color_value(s: String): Color = Color_Value(options.string(s))
       
    88 
       
    89   val outdated_color = color_value("outdated_color")
       
    90   val unprocessed_color = color_value("unprocessed_color")
       
    91   val unprocessed1_color = color_value("unprocessed1_color")
       
    92   val running_color = color_value("running_color")
       
    93   val running1_color = color_value("running1_color")
       
    94   val light_color = color_value("light_color")
       
    95   val writeln_color = color_value("writeln_color")
       
    96   val warning_color = color_value("warning_color")
       
    97   val error_color = color_value("error_color")
       
    98   val error1_color = color_value("error1_color")
       
    99   val bad_color = color_value("bad_color")
       
   100   val hilite_color = color_value("hilite_color")
       
   101   val quoted_color = color_value("quoted_color")
       
   102   val subexp_color = color_value("subexp_color")
       
   103   val hyperlink_color = color_value("hyperlink_color")
       
   104   val keyword1_color = color_value("keyword1_color")
       
   105   val keyword2_color = color_value("keyword2_color")
       
   106 
       
   107   val tfree_color = color_value("tfree_color")
       
   108   val tvar_color = color_value("tvar_color")
       
   109   val free_color = color_value("free_color")
       
   110   val skolem_color = color_value("skolem_color")
       
   111   val bound_color = color_value("bound_color")
       
   112   val var_color = color_value("var_color")
       
   113   val inner_numeral_color = color_value("inner_numeral_color")
       
   114   val inner_quoted_color = color_value("inner_quoted_color")
       
   115   val inner_comment_color = color_value("inner_comment_color")
       
   116   val antiquotation_color = color_value("antiquotation_color")
       
   117   val dynamic_color = color_value("dynamic_color")
       
   118 
       
   119 
       
   120   /* command overview */
       
   121 
       
   122   def overview_color(range: Text.Range): Option[Color] =
       
   123   {
       
   124     if (snapshot.is_outdated) None
       
   125     else {
       
   126       val results =
       
   127         snapshot.cumulate_markup[(Protocol.Status, Int)](
       
   128           range, (Protocol.Status.init, 0),
       
   129           Some(Protocol.command_status_markup + Isabelle_Markup.WARNING + Isabelle_Markup.ERROR),
       
   130           {
       
   131             case ((status, pri), Text.Info(_, XML.Elem(markup, _))) =>
       
   132               if (markup.name == Isabelle_Markup.WARNING)
       
   133                 (status, pri max Isabelle_Rendering.warning_pri)
       
   134               else if (markup.name == Isabelle_Markup.ERROR)
       
   135                 (status, pri max Isabelle_Rendering.error_pri)
       
   136               else (Protocol.command_status(status, markup), pri)
       
   137           })
       
   138       if (results.isEmpty) None
       
   139       else {
       
   140         val (status, pri) =
       
   141           ((Protocol.Status.init, 0) /: results) {
       
   142             case ((s1, p1), Text.Info(_, (s2, p2))) => (s1 + s2, p1 max p2) }
       
   143 
       
   144         if (pri == Isabelle_Rendering.warning_pri) Some(warning_color)
       
   145         else if (pri == Isabelle_Rendering.error_pri) Some(error_color)
       
   146         else if (status.is_unprocessed) Some(unprocessed_color)
       
   147         else if (status.is_running) Some(running_color)
       
   148         else if (status.is_failed) Some(error_color)
       
   149         else None
       
   150       }
       
   151     }
       
   152   }
       
   153 
       
   154 
       
   155   /* markup selectors */
       
   156 
       
   157   private val subexp_include =
       
   158     Set(Isabelle_Markup.SORT, Isabelle_Markup.TYP, Isabelle_Markup.TERM, Isabelle_Markup.PROP,
       
   159       Isabelle_Markup.ML_TYPING, Isabelle_Markup.TOKEN_RANGE, Isabelle_Markup.ENTITY,
       
   160       Isabelle_Markup.PATH, Isabelle_Markup.TYPING, Isabelle_Markup.FREE, Isabelle_Markup.SKOLEM,
       
   161       Isabelle_Markup.BOUND, Isabelle_Markup.VAR, Isabelle_Markup.TFREE, Isabelle_Markup.TVAR,
       
   162       Isabelle_Markup.ML_SOURCE, Isabelle_Markup.DOCUMENT_SOURCE)
       
   163 
       
   164   def subexp(range: Text.Range): Option[Text.Info[Color]] =
       
   165   {
       
   166     snapshot.select_markup(range, Some(subexp_include),
       
   167         {
       
   168           case Text.Info(info_range, XML.Elem(Markup(name, _), _)) if subexp_include(name) =>
       
   169             Text.Info(snapshot.convert(info_range), subexp_color)
       
   170         }) match { case Text.Info(_, info) #:: _ => Some(info) case _ => None }
       
   171   }
       
   172 
       
   173 
       
   174   private val hyperlink_include = Set(Isabelle_Markup.ENTITY, Isabelle_Markup.PATH)
       
   175 
       
   176   def hyperlink(range: Text.Range): Option[Text.Info[Hyperlink]] =
       
   177   {
       
   178     snapshot.cumulate_markup[List[Text.Info[Hyperlink]]](range, Nil, Some(hyperlink_include),
       
   179         {
       
   180           case (links, Text.Info(info_range, XML.Elem(Isabelle_Markup.Path(name), _)))
       
   181           if Path.is_ok(name) =>
       
   182             val jedit_file = Isabelle.thy_load.append(snapshot.node_name.dir, Path.explode(name))
       
   183             Text.Info(snapshot.convert(info_range), Hyperlink(jedit_file, 0, 0)) :: links
       
   184 
       
   185           case (links, Text.Info(info_range, XML.Elem(Markup(Isabelle_Markup.ENTITY, props), _)))
       
   186           if (props.find(
       
   187             { case (Markup.KIND, Isabelle_Markup.ML_OPEN) => true
       
   188               case (Markup.KIND, Isabelle_Markup.ML_STRUCT) => true
       
   189               case _ => false }).isEmpty) =>
       
   190 
       
   191             props match {
       
   192               case Position.Line_File(line, name) if Path.is_ok(name) =>
       
   193                 Isabelle_System.source_file(Path.explode(name)) match {
       
   194                   case Some(path) =>
       
   195                     val jedit_file = Isabelle_System.platform_path(path)
       
   196                     Text.Info(snapshot.convert(info_range), Hyperlink(jedit_file, line, 0)) :: links
       
   197                   case None => links
       
   198                 }
       
   199 
       
   200               case Position.Id_Offset(id, offset) if !snapshot.is_outdated =>
       
   201                 snapshot.state.find_command(snapshot.version, id) match {
       
   202                   case Some((node, command)) =>
       
   203                     val sources =
       
   204                       node.commands.iterator.takeWhile(_ != command).map(_.source) ++
       
   205                         Iterator.single(command.source(Text.Range(0, command.decode(offset))))
       
   206                     val (line, column) = ((1, 1) /: sources)(Symbol.advance_line_column)
       
   207                     Text.Info(snapshot.convert(info_range),
       
   208                       Hyperlink(command.node_name.node, line, column)) :: links
       
   209                   case None => links
       
   210                 }
       
   211 
       
   212               case _ => links
       
   213             }
       
   214         }) match { case Text.Info(_, info :: _) #:: _ => Some(info) case _ => None }
       
   215   }
       
   216 
       
   217 
       
   218   private def tooltip_text(msg: XML.Tree): String =
       
   219     Pretty.string_of(List(msg), margin = options.int("jedit_tooltip_margin"))
       
   220 
       
   221   def tooltip_message(range: Text.Range): Option[String] =
       
   222   {
       
   223     val msgs =
       
   224       snapshot.cumulate_markup[SortedMap[Long, String]](range, SortedMap.empty,
       
   225         Some(Set(Isabelle_Markup.WRITELN, Isabelle_Markup.WARNING, Isabelle_Markup.ERROR,
       
   226           Isabelle_Markup.BAD)),
       
   227         {
       
   228           case (msgs, Text.Info(_, msg @
       
   229               XML.Elem(Markup(markup, Isabelle_Markup.Serial(serial)), _)))
       
   230           if markup == Isabelle_Markup.WRITELN ||
       
   231               markup == Isabelle_Markup.WARNING ||
       
   232               markup == Isabelle_Markup.ERROR =>
       
   233             msgs + (serial -> tooltip_text(msg))
       
   234           case (msgs, Text.Info(_, msg @ XML.Elem(Markup(Isabelle_Markup.BAD, _), body)))
       
   235           if !body.isEmpty => msgs + (Document.new_id() -> tooltip_text(msg))
       
   236         }).toList.flatMap(_.info)
       
   237     if (msgs.isEmpty) None else Some(cat_lines(msgs.iterator.map(_._2)))
       
   238   }
       
   239 
       
   240 
       
   241   private val tooltips: Map[String, String] =
       
   242     Map(
       
   243       Isabelle_Markup.SORT -> "sort",
       
   244       Isabelle_Markup.TYP -> "type",
       
   245       Isabelle_Markup.TERM -> "term",
       
   246       Isabelle_Markup.PROP -> "proposition",
       
   247       Isabelle_Markup.TOKEN_RANGE -> "inner syntax token",
       
   248       Isabelle_Markup.FREE -> "free variable",
       
   249       Isabelle_Markup.SKOLEM -> "skolem variable",
       
   250       Isabelle_Markup.BOUND -> "bound variable",
       
   251       Isabelle_Markup.VAR -> "schematic variable",
       
   252       Isabelle_Markup.TFREE -> "free type variable",
       
   253       Isabelle_Markup.TVAR -> "schematic type variable",
       
   254       Isabelle_Markup.ML_SOURCE -> "ML source",
       
   255       Isabelle_Markup.DOCUMENT_SOURCE -> "document source")
       
   256 
       
   257   private val tooltip_elements =
       
   258     Set(Isabelle_Markup.ENTITY, Isabelle_Markup.TYPING, Isabelle_Markup.ML_TYPING,
       
   259       Isabelle_Markup.PATH) ++ tooltips.keys
       
   260 
       
   261   private def string_of_typing(kind: String, body: XML.Body): String =
       
   262     Pretty.string_of(List(Pretty.block(XML.Text(kind) :: Pretty.Break(1) :: body)),
       
   263       margin = options.int("jedit_tooltip_margin"))
       
   264 
       
   265   def tooltip(range: Text.Range): Option[String] =
       
   266   {
       
   267     def add(prev: Text.Info[List[(Boolean, String)]], r: Text.Range, p: (Boolean, String)) =
       
   268       if (prev.range == r) Text.Info(r, p :: prev.info) else Text.Info(r, List(p))
       
   269 
       
   270     val tips =
       
   271       snapshot.cumulate_markup[Text.Info[(List[(Boolean, String)])]](
       
   272         range, Text.Info(range, Nil), Some(tooltip_elements),
       
   273         {
       
   274           case (prev, Text.Info(r, XML.Elem(Isabelle_Markup.Entity(kind, name), _))) =>
       
   275             val kind1 = space_explode('_', kind).mkString(" ")
       
   276             add(prev, r, (true, kind1 + " " + quote(name)))
       
   277           case (prev, Text.Info(r, XML.Elem(Isabelle_Markup.Path(name), _)))
       
   278           if Path.is_ok(name) =>
       
   279             val jedit_file = Isabelle.thy_load.append(snapshot.node_name.dir, Path.explode(name))
       
   280             add(prev, r, (true, "file " + quote(jedit_file)))
       
   281           case (prev, Text.Info(r, XML.Elem(Markup(Isabelle_Markup.TYPING, _), body))) =>
       
   282             add(prev, r, (true, string_of_typing("::", body)))
       
   283           case (prev, Text.Info(r, XML.Elem(Markup(Isabelle_Markup.ML_TYPING, _), body))) =>
       
   284             add(prev, r, (false, string_of_typing("ML:", body)))
       
   285           case (prev, Text.Info(r, XML.Elem(Markup(name, _), _)))
       
   286           if tooltips.isDefinedAt(name) => add(prev, r, (true, tooltips(name)))
       
   287         }).toList.flatMap(_.info.info)
       
   288 
       
   289     val all_tips =
       
   290       (tips.filter(_._1) ++ tips.filter(!_._1).lastOption.toList).map(_._2)
       
   291     if (all_tips.isEmpty) None else Some(cat_lines(all_tips))
       
   292   }
       
   293 
       
   294 
       
   295   def gutter_message(range: Text.Range): Option[Icon] =
       
   296   {
       
   297     val results =
       
   298       snapshot.cumulate_markup[Int](range, 0,
       
   299         Some(Set(Isabelle_Markup.WARNING, Isabelle_Markup.ERROR)),
       
   300         {
       
   301           case (pri, Text.Info(_, XML.Elem(Markup(Isabelle_Markup.WARNING, _), body))) =>
       
   302             body match {
       
   303               case List(XML.Elem(Markup(Isabelle_Markup.LEGACY, _), _)) =>
       
   304                 pri max Isabelle_Rendering.legacy_pri
       
   305               case _ => pri max Isabelle_Rendering.warning_pri
       
   306             }
       
   307           case (pri, Text.Info(_, XML.Elem(Markup(Isabelle_Markup.ERROR, _), _))) =>
       
   308             pri max Isabelle_Rendering.error_pri
       
   309         })
       
   310     val pri = (0 /: results) { case (p1, Text.Info(_, p2)) => p1 max p2 }
       
   311     Isabelle_Rendering.gutter_icons.get(pri)
       
   312   }
       
   313 
       
   314 
       
   315   private val squiggly_colors = Map(
       
   316     Isabelle_Rendering.writeln_pri -> writeln_color,
       
   317     Isabelle_Rendering.warning_pri -> warning_color,
       
   318     Isabelle_Rendering.error_pri -> error_color)
       
   319 
       
   320   def squiggly_underline(range: Text.Range): Stream[Text.Info[Color]] =
       
   321   {
       
   322     val results =
       
   323       snapshot.cumulate_markup[Int](range, 0,
       
   324         Some(Set(Isabelle_Markup.WRITELN, Isabelle_Markup.WARNING, Isabelle_Markup.ERROR)),
       
   325         {
       
   326           case (pri, Text.Info(_, XML.Elem(Markup(name, _), _))) =>
       
   327             name match {
       
   328               case Isabelle_Markup.WRITELN => pri max Isabelle_Rendering.writeln_pri
       
   329               case Isabelle_Markup.WARNING => pri max Isabelle_Rendering.warning_pri
       
   330               case Isabelle_Markup.ERROR => pri max Isabelle_Rendering.error_pri
       
   331               case _ => pri
       
   332             }
       
   333         })
       
   334     for {
       
   335       Text.Info(r, pri) <- results
       
   336       color <- squiggly_colors.get(pri)
       
   337     } yield Text.Info(r, color)
       
   338   }
       
   339 
       
   340 
       
   341   def background1(range: Text.Range): Stream[Text.Info[Color]] =
       
   342   {
       
   343     if (snapshot.is_outdated) Stream(Text.Info(range, outdated_color))
       
   344     else
       
   345       for {
       
   346         Text.Info(r, result) <-
       
   347           snapshot.cumulate_markup[(Option[Protocol.Status], Option[Color])](
       
   348             range, (Some(Protocol.Status.init), None),
       
   349             Some(Protocol.command_status_markup + Isabelle_Markup.BAD + Isabelle_Markup.HILITE),
       
   350             {
       
   351               case (((Some(status), color), Text.Info(_, XML.Elem(markup, _))))
       
   352               if (Protocol.command_status_markup(markup.name)) =>
       
   353                 (Some(Protocol.command_status(status, markup)), color)
       
   354               case (_, Text.Info(_, XML.Elem(Markup(Isabelle_Markup.BAD, _), _))) =>
       
   355                 (None, Some(bad_color))
       
   356               case (_, Text.Info(_, XML.Elem(Markup(Isabelle_Markup.HILITE, _), _))) =>
       
   357                 (None, Some(hilite_color))
       
   358             })
       
   359         color <-
       
   360           (result match {
       
   361             case (Some(status), opt_color) =>
       
   362               if (status.is_unprocessed) Some(unprocessed1_color)
       
   363               else if (status.is_running) Some(running1_color)
       
   364               else opt_color
       
   365             case (_, opt_color) => opt_color
       
   366           })
       
   367       } yield Text.Info(r, color)
       
   368   }
       
   369 
       
   370 
       
   371   def background2(range: Text.Range): Stream[Text.Info[Color]] =
       
   372     snapshot.select_markup(range,
       
   373       Some(Set(Isabelle_Markup.TOKEN_RANGE)),
       
   374       {
       
   375         case Text.Info(_, XML.Elem(Markup(Isabelle_Markup.TOKEN_RANGE, _), _)) => light_color
       
   376       })
       
   377 
       
   378 
       
   379   def foreground(range: Text.Range): Stream[Text.Info[Color]] =
       
   380     snapshot.select_markup(range,
       
   381       Some(Set(Isabelle_Markup.STRING, Isabelle_Markup.ALTSTRING, Isabelle_Markup.VERBATIM)),
       
   382       {
       
   383         case Text.Info(_, XML.Elem(Markup(Isabelle_Markup.STRING, _), _)) => quoted_color
       
   384         case Text.Info(_, XML.Elem(Markup(Isabelle_Markup.ALTSTRING, _), _)) => quoted_color
       
   385         case Text.Info(_, XML.Elem(Markup(Isabelle_Markup.VERBATIM, _), _)) => quoted_color
       
   386       })
       
   387 
       
   388 
       
   389   private val text_colors: Map[String, Color] = Map(
       
   390       Isabelle_Markup.STRING -> Color.BLACK,
       
   391       Isabelle_Markup.ALTSTRING -> Color.BLACK,
       
   392       Isabelle_Markup.VERBATIM -> Color.BLACK,
       
   393       Isabelle_Markup.LITERAL -> keyword1_color,
       
   394       Isabelle_Markup.DELIMITER -> Color.BLACK,
       
   395       Isabelle_Markup.TFREE -> tfree_color,
       
   396       Isabelle_Markup.TVAR -> tvar_color,
       
   397       Isabelle_Markup.FREE -> free_color,
       
   398       Isabelle_Markup.SKOLEM -> skolem_color,
       
   399       Isabelle_Markup.BOUND -> bound_color,
       
   400       Isabelle_Markup.VAR -> var_color,
       
   401       Isabelle_Markup.INNER_STRING -> inner_quoted_color,
       
   402       Isabelle_Markup.INNER_COMMENT -> inner_comment_color,
       
   403       Isabelle_Markup.DYNAMIC_FACT -> dynamic_color,
       
   404       Isabelle_Markup.ML_KEYWORD -> keyword1_color,
       
   405       Isabelle_Markup.ML_DELIMITER -> Color.BLACK,
       
   406       Isabelle_Markup.ML_NUMERAL -> inner_numeral_color,
       
   407       Isabelle_Markup.ML_CHAR -> inner_quoted_color,
       
   408       Isabelle_Markup.ML_STRING -> inner_quoted_color,
       
   409       Isabelle_Markup.ML_COMMENT -> inner_comment_color,
       
   410       Isabelle_Markup.ANTIQ -> antiquotation_color)
       
   411 
       
   412   private val text_color_elements = Set.empty[String] ++ text_colors.keys
       
   413 
       
   414   def text_color(range: Text.Range, color: Color)
       
   415       : Stream[Text.Info[Color]] =
       
   416   {
       
   417     snapshot.cumulate_markup(range, color, Some(text_color_elements),
       
   418       {
       
   419         case (_, Text.Info(_, XML.Elem(Markup(m, _), _)))
       
   420         if text_colors.isDefinedAt(m) => text_colors(m)
       
   421       })
       
   422   }
       
   423 }