src/Pure/PIDE/rendering.scala
author wenzelm
Wed Mar 08 10:29:40 2017 +0100 (2017-03-08)
changeset 65150 fa299b4e50c3
parent 65149 9dccbebf4511
child 65176 908d8be90533
permissions -rw-r--r--
clarified rendering;
     1 /*  Title:      Pure/PIDE/rendering.scala
     2     Author:     Makarius
     3 
     4 Isabelle-specific implementation of quasi-abstract rendering and
     5 markup interpretation.
     6 */
     7 
     8 package isabelle
     9 
    10 
    11 object Rendering
    12 {
    13   /* color */
    14 
    15   object Color extends Enumeration
    16   {
    17     // background
    18     val unprocessed1 = Value("unprocessed1")
    19     val running1 = Value("running1")
    20     val bad = Value("bad")
    21     val intensify = Value("intensify")
    22     val entity = Value("entity")
    23     val active = Value("active")
    24     val active_result = Value("active_result")
    25     val markdown_item1 = Value("markdown_item1")
    26     val markdown_item2 = Value("markdown_item2")
    27     val markdown_item3 = Value("markdown_item3")
    28     val markdown_item4 = Value("markdown_item4")
    29     val background_colors = values
    30 
    31     // foreground
    32     val quoted = Value("quoted")
    33     val antiquoted = Value("antiquoted")
    34     val foreground_colors = values -- background_colors
    35 
    36     // message underline
    37     val writeln = Value("writeln")
    38     val information = Value("information")
    39     val warning = Value("warning")
    40     val legacy = Value("legacy")
    41     val error = Value("error")
    42     val message_underline_colors = values -- background_colors -- foreground_colors
    43 
    44     // message background
    45     val writeln_message = Value("writeln_message")
    46     val information_message = Value("information_message")
    47     val tracing_message = Value("tracing_message")
    48     val warning_message = Value("warning_message")
    49     val legacy_message = Value("legacy_message")
    50     val error_message = Value("error_message")
    51     val message_background_colors =
    52       values -- background_colors -- foreground_colors -- message_underline_colors
    53 
    54     // text
    55     val main = Value("main")
    56     val keyword1 = Value("keyword1")
    57     val keyword2 = Value("keyword2")
    58     val keyword3 = Value("keyword3")
    59     val quasi_keyword = Value("quasi_keyword")
    60     val improper = Value("improper")
    61     val operator = Value("operator")
    62     val tfree = Value("tfree")
    63     val tvar = Value("tvar")
    64     val free = Value("free")
    65     val skolem = Value("skolem")
    66     val bound = Value("bound")
    67     val var_ = Value("var")
    68     val inner_numeral = Value("inner_numeral")
    69     val inner_quoted = Value("inner_quoted")
    70     val inner_cartouche = Value("inner_cartouche")
    71     val inner_comment = Value("inner_comment")
    72     val dynamic = Value("dynamic")
    73     val class_parameter = Value("class_parameter")
    74     val antiquote = Value("antiquote")
    75     val text_colors =
    76       values -- background_colors -- foreground_colors -- message_underline_colors --
    77       message_background_colors
    78   }
    79 
    80 
    81   /* message priorities */
    82 
    83   val state_pri = 1
    84   val writeln_pri = 2
    85   val information_pri = 3
    86   val tracing_pri = 4
    87   val warning_pri = 5
    88   val legacy_pri = 6
    89   val error_pri = 7
    90 
    91   val message_pri = Map(
    92     Markup.STATE -> state_pri,
    93     Markup.STATE_MESSAGE -> state_pri,
    94     Markup.WRITELN -> writeln_pri,
    95     Markup.WRITELN_MESSAGE -> writeln_pri,
    96     Markup.INFORMATION -> information_pri,
    97     Markup.INFORMATION_MESSAGE -> information_pri,
    98     Markup.TRACING -> tracing_pri,
    99     Markup.TRACING_MESSAGE -> tracing_pri,
   100     Markup.WARNING -> warning_pri,
   101     Markup.WARNING_MESSAGE -> warning_pri,
   102     Markup.LEGACY -> legacy_pri,
   103     Markup.LEGACY_MESSAGE -> legacy_pri,
   104     Markup.ERROR -> error_pri,
   105     Markup.ERROR_MESSAGE -> error_pri
   106   ).withDefaultValue(0)
   107 
   108   val message_underline_color = Map(
   109     writeln_pri -> Color.writeln,
   110     information_pri -> Color.information,
   111     warning_pri -> Color.warning,
   112     legacy_pri -> Color.legacy,
   113     error_pri -> Color.error)
   114 
   115   val message_background_color = Map(
   116     writeln_pri -> Color.writeln_message,
   117     information_pri -> Color.information_message,
   118     tracing_pri -> Color.tracing_message,
   119     warning_pri -> Color.warning_message,
   120     legacy_pri -> Color.legacy_message,
   121     error_pri -> Color.error_message)
   122 
   123 
   124   /* text color */
   125 
   126   val text_color = Map(
   127     Markup.KEYWORD1 -> Color.keyword1,
   128     Markup.KEYWORD2 -> Color.keyword2,
   129     Markup.KEYWORD3 -> Color.keyword3,
   130     Markup.QUASI_KEYWORD -> Color.quasi_keyword,
   131     Markup.IMPROPER -> Color.improper,
   132     Markup.OPERATOR -> Color.operator,
   133     Markup.STRING -> Color.main,
   134     Markup.ALT_STRING -> Color.main,
   135     Markup.VERBATIM -> Color.main,
   136     Markup.CARTOUCHE -> Color.main,
   137     Markup.LITERAL -> Color.keyword1,
   138     Markup.DELIMITER -> Color.main,
   139     Markup.TFREE -> Color.tfree,
   140     Markup.TVAR -> Color.tvar,
   141     Markup.FREE -> Color.free,
   142     Markup.SKOLEM -> Color.skolem,
   143     Markup.BOUND -> Color.bound,
   144     Markup.VAR -> Color.var_,
   145     Markup.INNER_STRING -> Color.inner_quoted,
   146     Markup.INNER_CARTOUCHE -> Color.inner_cartouche,
   147     Markup.INNER_COMMENT -> Color.inner_comment,
   148     Markup.DYNAMIC_FACT -> Color.dynamic,
   149     Markup.CLASS_PARAMETER -> Color.class_parameter,
   150     Markup.ANTIQUOTE -> Color.antiquote,
   151     Markup.ML_KEYWORD1 -> Color.keyword1,
   152     Markup.ML_KEYWORD2 -> Color.keyword2,
   153     Markup.ML_KEYWORD3 -> Color.keyword3,
   154     Markup.ML_DELIMITER -> Color.main,
   155     Markup.ML_NUMERAL -> Color.inner_numeral,
   156     Markup.ML_CHAR -> Color.inner_quoted,
   157     Markup.ML_STRING -> Color.inner_quoted,
   158     Markup.ML_COMMENT -> Color.inner_comment,
   159     Markup.SML_STRING -> Color.inner_quoted,
   160     Markup.SML_COMMENT -> Color.inner_comment)
   161 
   162 
   163   /* tooltips */
   164 
   165   val tooltip_descriptions =
   166     Map(
   167       Markup.CITATION -> "citation",
   168       Markup.TOKEN_RANGE -> "inner syntax token",
   169       Markup.FREE -> "free variable",
   170       Markup.SKOLEM -> "skolem variable",
   171       Markup.BOUND -> "bound variable",
   172       Markup.VAR -> "schematic variable",
   173       Markup.TFREE -> "free type variable",
   174       Markup.TVAR -> "schematic type variable")
   175 
   176 
   177   /* markup elements */
   178 
   179   val active_elements =
   180     Markup.Elements(Markup.DIALOG, Markup.BROWSER, Markup.GRAPHVIEW,
   181       Markup.SENDBACK, Markup.JEDIT_ACTION, Markup.SIMP_TRACE_PANEL)
   182 
   183   val background_elements =
   184     Protocol.proper_status_elements + Markup.WRITELN_MESSAGE +
   185       Markup.STATE_MESSAGE + Markup.INFORMATION_MESSAGE +
   186       Markup.TRACING_MESSAGE + Markup.WARNING_MESSAGE +
   187       Markup.LEGACY_MESSAGE + Markup.ERROR_MESSAGE +
   188       Markup.BAD + Markup.INTENSIFY + Markup.ENTITY +
   189       Markup.Markdown_Item.name ++ active_elements
   190 
   191   val foreground_elements =
   192     Markup.Elements(Markup.STRING, Markup.ALT_STRING, Markup.VERBATIM,
   193       Markup.CARTOUCHE, Markup.ANTIQUOTED)
   194 
   195   val semantic_completion_elements =
   196     Markup.Elements(Markup.COMPLETION, Markup.NO_COMPLETION)
   197 
   198   val tooltip_elements =
   199     Markup.Elements(Markup.LANGUAGE, Markup.EXPRESSION, Markup.TIMING, Markup.ENTITY,
   200       Markup.SORTING, Markup.TYPING, Markup.CLASS_PARAMETER, Markup.ML_TYPING,
   201       Markup.ML_BREAKPOINT, Markup.PATH, Markup.DOC, Markup.URL, Markup.MARKDOWN_PARAGRAPH,
   202       Markup.Markdown_List.name) ++ Markup.Elements(tooltip_descriptions.keySet)
   203 
   204   val tooltip_message_elements =
   205     Markup.Elements(Markup.WRITELN, Markup.INFORMATION, Markup.WARNING, Markup.LEGACY, Markup.ERROR,
   206       Markup.BAD)
   207 
   208   val caret_focus_elements = Markup.Elements(Markup.ENTITY)
   209 
   210   val text_color_elements = Markup.Elements(text_color.keySet)
   211 }
   212 
   213 abstract class Rendering(
   214   val snapshot: Document.Snapshot,
   215   val options: Options,
   216   val resources: Resources)
   217 {
   218   override def toString: String = "Rendering(" + snapshot.toString + ")"
   219 
   220 
   221   /* completion */
   222 
   223   def semantic_completion(completed_range: Option[Text.Range], range: Text.Range)
   224       : Option[Text.Info[Completion.Semantic]] =
   225     if (snapshot.is_outdated) None
   226     else {
   227       snapshot.select(range, Rendering.semantic_completion_elements, _ =>
   228         {
   229           case Completion.Semantic.Info(info) =>
   230             completed_range match {
   231               case Some(range0) if range0.contains(info.range) && range0 != info.range => None
   232               case _ => Some(info)
   233             }
   234           case _ => None
   235         }).headOption.map(_.info)
   236     }
   237 
   238 
   239   /* spell checker */
   240 
   241   private lazy val spell_checker_elements =
   242     Markup.Elements(space_explode(',', options.string("spell_checker_elements")): _*)
   243 
   244   def spell_checker_ranges(range: Text.Range): List[Text.Range] =
   245     snapshot.select(range, spell_checker_elements, _ => _ => Some(())).map(_.range)
   246 
   247   def spell_checker_point(range: Text.Range): Option[Text.Range] =
   248     snapshot.select(range, spell_checker_elements, _ =>
   249       {
   250         case info => Some(snapshot.convert(info.range))
   251       }).headOption.map(_.info)
   252 
   253 
   254   /* text background */
   255 
   256   def background(range: Text.Range, focus: Set[Long]): List[Text.Info[Rendering.Color.Value]] =
   257   {
   258     for {
   259       Text.Info(r, result) <-
   260         snapshot.cumulate[(List[Markup], Option[Rendering.Color.Value])](
   261           range, (List(Markup.Empty), None), Rendering.background_elements,
   262           command_states =>
   263             {
   264               case (((markups, color), Text.Info(_, XML.Elem(markup, _))))
   265               if markups.nonEmpty && Protocol.proper_status_elements(markup.name) =>
   266                 Some((markup :: markups, color))
   267               case (_, Text.Info(_, XML.Elem(Markup(Markup.BAD, _), _))) =>
   268                 Some((Nil, Some(Rendering.Color.bad)))
   269               case (_, Text.Info(_, XML.Elem(Markup(Markup.INTENSIFY, _), _))) =>
   270                 Some((Nil, Some(Rendering.Color.intensify)))
   271               case (_, Text.Info(_, XML.Elem(Markup(Markup.ENTITY, props), _))) =>
   272                 props match {
   273                   case Markup.Entity.Def(i) if focus(i) => Some((Nil, Some(Rendering.Color.entity)))
   274                   case Markup.Entity.Ref(i) if focus(i) => Some((Nil, Some(Rendering.Color.entity)))
   275                   case _ => None
   276                 }
   277               case (_, Text.Info(_, XML.Elem(Markup.Markdown_Item(depth), _))) =>
   278                 val color =
   279                   depth % 4 match {
   280                     case 1 => Rendering.Color.markdown_item1
   281                     case 2 => Rendering.Color.markdown_item2
   282                     case 3 => Rendering.Color.markdown_item3
   283                     case _ => Rendering.Color.markdown_item4
   284                   }
   285                 Some((Nil, Some(color)))
   286               case (acc, Text.Info(_, Protocol.Dialog(_, serial, result))) =>
   287                 command_states.collectFirst(
   288                   { case st if st.results.defined(serial) => st.results.get(serial).get }) match
   289                 {
   290                   case Some(Protocol.Dialog_Result(res)) if res == result =>
   291                     Some((Nil, Some(Rendering.Color.active_result)))
   292                   case _ =>
   293                     Some((Nil, Some(Rendering.Color.active)))
   294                 }
   295               case (_, Text.Info(_, elem)) =>
   296                 if (Rendering.active_elements(elem.name))
   297                   Some((Nil, Some(Rendering.Color.active)))
   298                 else None
   299             })
   300       color <-
   301         (result match {
   302           case (markups, opt_color) if markups.nonEmpty =>
   303             val status = Protocol.Status.make(markups.iterator)
   304             if (status.is_unprocessed) Some(Rendering.Color.unprocessed1)
   305             else if (status.is_running) Some(Rendering.Color.running1)
   306             else opt_color
   307           case (_, opt_color) => opt_color
   308         })
   309     } yield Text.Info(r, color)
   310   }
   311 
   312 
   313   /* text foreground */
   314 
   315   def foreground(range: Text.Range): List[Text.Info[Rendering.Color.Value]] =
   316     snapshot.select(range, Rendering.foreground_elements, _ =>
   317       {
   318         case Text.Info(_, elem) =>
   319           if (elem.name == Markup.ANTIQUOTED) Some(Rendering.Color.antiquoted)
   320           else Some(Rendering.Color.quoted)
   321       })
   322 
   323 
   324   /* caret focus */
   325 
   326   private def entity_focus(range: Text.Range,
   327     check: (Boolean, Long) => Boolean = (is_def: Boolean, i: Long) => is_def): Set[Long] =
   328   {
   329     val results =
   330       snapshot.cumulate[Set[Long]](range, Set.empty, Rendering.caret_focus_elements, _ =>
   331           {
   332             case (serials, Text.Info(_, XML.Elem(Markup(Markup.ENTITY, props), _))) =>
   333               props match {
   334                 case Markup.Entity.Def(i) if check(true, i) => Some(serials + i)
   335                 case Markup.Entity.Ref(i) if check(false, i) => Some(serials + i)
   336                 case _ => None
   337               }
   338             case _ => None
   339           })
   340     (Set.empty[Long] /: results){ case (s1, Text.Info(_, s2)) => s1 ++ s2 }
   341   }
   342 
   343   def caret_focus(caret_range: Text.Range, visible_range: Text.Range): Set[Long] =
   344   {
   345     val focus_defs = entity_focus(caret_range)
   346     if (focus_defs.nonEmpty) focus_defs
   347     else {
   348       val visible_defs = entity_focus(visible_range)
   349       entity_focus(caret_range, (is_def: Boolean, i: Long) => !is_def && visible_defs.contains(i))
   350     }
   351   }
   352 
   353   def caret_focus_ranges(caret_range: Text.Range, visible_range: Text.Range): List[Text.Range] =
   354   {
   355     val focus = caret_focus(caret_range, visible_range)
   356     if (focus.nonEmpty) {
   357       val results =
   358         snapshot.cumulate[Boolean](visible_range, false, Rendering.caret_focus_elements, _ =>
   359           {
   360             case (_, Text.Info(_, XML.Elem(Markup(Markup.ENTITY, props), _))) =>
   361               props match {
   362                 case Markup.Entity.Def(i) if focus(i) => Some(true)
   363                 case Markup.Entity.Ref(i) if focus(i) => Some(true)
   364                 case _ => None
   365               }
   366           })
   367       for (info <- results if info.info) yield info.range
   368     }
   369     else Nil
   370   }
   371 
   372 
   373   /* message underline color */
   374 
   375   def message_underline_color(elements: Markup.Elements, range: Text.Range)
   376     : List[Text.Info[Rendering.Color.Value]] =
   377   {
   378     val results =
   379       snapshot.cumulate[Int](range, 0, elements, _ =>
   380         {
   381           case (pri, Text.Info(_, elem)) => Some(pri max Rendering.message_pri(elem.name))
   382         })
   383     for {
   384       Text.Info(r, pri) <- results
   385       color <- Rendering.message_underline_color.get(pri)
   386     } yield Text.Info(r, color)
   387   }
   388 
   389 
   390   /* tooltips */
   391 
   392   def timing_threshold: Double
   393 
   394   private sealed case class Tooltip_Info(
   395     range: Text.Range,
   396     timing: Timing = Timing.zero,
   397     messages: List[Command.Results.Entry] = Nil,
   398     rev_infos: List[(Boolean, XML.Tree)] = Nil)
   399   {
   400     def + (t: Timing): Tooltip_Info = copy(timing = timing + t)
   401     def + (r0: Text.Range, serial: Long, tree: XML.Tree): Tooltip_Info =
   402     {
   403       val r = snapshot.convert(r0)
   404       if (range == r) copy(messages = (serial -> tree) :: messages)
   405       else copy(range = r, messages = List(serial -> tree))
   406     }
   407     def + (r0: Text.Range, important: Boolean, tree: XML.Tree): Tooltip_Info =
   408     {
   409       val r = snapshot.convert(r0)
   410       if (range == r) copy(rev_infos = (important -> tree) :: rev_infos)
   411       else copy (range = r, rev_infos = List(important -> tree))
   412     }
   413     def infos(important: Boolean): List[XML.Tree] =
   414       rev_infos.filter(p => p._1 == important).reverse.map(_._2)
   415   }
   416 
   417   def tooltips(elements: Markup.Elements, range: Text.Range): Option[Text.Info[List[XML.Tree]]] =
   418   {
   419     val results =
   420       snapshot.cumulate[Tooltip_Info](range, Tooltip_Info(range), elements, _ =>
   421         {
   422           case (info, Text.Info(_, XML.Elem(Markup.Timing(t), _))) => Some(info + t)
   423 
   424           case (info, Text.Info(r0, XML.Elem(Markup(name, props @ Markup.Serial(serial)), body)))
   425           if Rendering.tooltip_message_elements(name) && body.nonEmpty =>
   426             Some(info + (r0, serial, XML.Elem(Markup(Markup.message(name), props), body)))
   427 
   428           case (info, Text.Info(r0, XML.Elem(Markup.Entity(kind, name), _)))
   429           if kind != "" && kind != Markup.ML_DEF =>
   430             val kind1 = Word.implode(Word.explode('_', kind))
   431             val txt1 =
   432               if (name == "") kind1
   433               else if (kind1 == "") quote(name)
   434               else kind1 + " " + quote(name)
   435             val txt2 =
   436               if (kind == Markup.COMMAND && info.timing.elapsed.seconds >= timing_threshold)
   437                 "\n" + info.timing.message
   438               else ""
   439             Some(info + (r0, true, XML.Text(txt1 + txt2)))
   440 
   441           case (info, Text.Info(r0, XML.Elem(Markup.Path(name), _))) =>
   442             val file = resources.append_file(snapshot.node_name.master_dir, name)
   443             val text =
   444               if (name == file) "file " + quote(file)
   445               else "path " + quote(name) + "\nfile " + quote(file)
   446             Some(info + (r0, true, XML.Text(text)))
   447 
   448           case (info, Text.Info(r0, XML.Elem(Markup.Doc(name), _))) =>
   449             val text = "doc " + quote(name)
   450             Some(info + (r0, true, XML.Text(text)))
   451 
   452           case (info, Text.Info(r0, XML.Elem(Markup.Url(name), _))) =>
   453             Some(info + (r0, true, XML.Text("URL " + quote(name))))
   454 
   455           case (info, Text.Info(r0, XML.Elem(Markup(name, _), body)))
   456           if name == Markup.SORTING || name == Markup.TYPING =>
   457             Some(info + (r0, true, Pretty.block(XML.Text("::") :: Pretty.brk(1) :: body)))
   458 
   459           case (info, Text.Info(r0, XML.Elem(Markup(Markup.CLASS_PARAMETER, _), body))) =>
   460             Some(info + (r0, true, Pretty.block(0, body)))
   461 
   462           case (info, Text.Info(r0, XML.Elem(Markup(Markup.ML_TYPING, _), body))) =>
   463             Some(info + (r0, false, Pretty.block(XML.Text("ML:") :: Pretty.brk(1) :: body)))
   464 
   465           case (info, Text.Info(r0, Protocol.ML_Breakpoint(breakpoint))) =>
   466             val text =
   467               if (Debugger.breakpoint_state(breakpoint)) "breakpoint (enabled)"
   468               else "breakpoint (disabled)"
   469             Some(info + (r0, true, XML.Text(text)))
   470 
   471           case (info, Text.Info(r0, XML.Elem(Markup.Language(language, _, _, _), _))) =>
   472             val lang = Word.implode(Word.explode('_', language))
   473             Some(info + (r0, true, XML.Text("language: " + lang)))
   474 
   475           case (info, Text.Info(r0, XML.Elem(Markup.Expression(kind), _))) =>
   476             val descr = if (kind == "") "expression" else "expression: " + kind
   477             Some(info + (r0, true, XML.Text(descr)))
   478 
   479           case (info, Text.Info(r0, XML.Elem(Markup(Markup.MARKDOWN_PARAGRAPH, _), _))) =>
   480             Some(info + (r0, true, XML.Text("Markdown: paragraph")))
   481           case (info, Text.Info(r0, XML.Elem(Markup.Markdown_List(kind), _))) =>
   482             Some(info + (r0, true, XML.Text("Markdown: " + kind)))
   483 
   484           case (info, Text.Info(r0, XML.Elem(Markup(name, _), _))) =>
   485             Rendering.tooltip_descriptions.get(name).map(desc => info + (r0, true, XML.Text(desc)))
   486         }).map(_.info)
   487 
   488     if (results.isEmpty) None
   489     else {
   490       val r = Text.Range(results.head.range.start, results.last.range.stop)
   491       val all_tips =
   492         Command.Results.make(results.flatMap(_.messages)).iterator.map(_._2).toList :::
   493         results.flatMap(res => res.infos(true)) :::
   494         results.flatMap(res => res.infos(false)).lastOption.toList
   495       if (all_tips.isEmpty) None else Some(Text.Info(r, all_tips))
   496     }
   497   }
   498 }