src/Pure/PIDE/rendering.scala
author wenzelm
Mon Mar 13 21:37:35 2017 +0100 (2017-03-13 ago)
changeset 65214 a2ec0db555c7
parent 65213 51c0f094dc02
child 65222 fb8253564483
permissions -rw-r--r--
clarified modules;
     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   /* output messages */
    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   def output_messages(results: Command.Results): List[XML.Tree] =
   124   {
   125     val (states, other) =
   126       results.iterator.map(_._2).filterNot(Protocol.is_result(_)).toList
   127         .partition(Protocol.is_state(_))
   128     states ::: other
   129   }
   130 
   131 
   132   /* text color */
   133 
   134   val text_color = Map(
   135     Markup.KEYWORD1 -> Color.keyword1,
   136     Markup.KEYWORD2 -> Color.keyword2,
   137     Markup.KEYWORD3 -> Color.keyword3,
   138     Markup.QUASI_KEYWORD -> Color.quasi_keyword,
   139     Markup.IMPROPER -> Color.improper,
   140     Markup.OPERATOR -> Color.operator,
   141     Markup.STRING -> Color.main,
   142     Markup.ALT_STRING -> Color.main,
   143     Markup.VERBATIM -> Color.main,
   144     Markup.CARTOUCHE -> Color.main,
   145     Markup.LITERAL -> Color.keyword1,
   146     Markup.DELIMITER -> Color.main,
   147     Markup.TFREE -> Color.tfree,
   148     Markup.TVAR -> Color.tvar,
   149     Markup.FREE -> Color.free,
   150     Markup.SKOLEM -> Color.skolem,
   151     Markup.BOUND -> Color.bound,
   152     Markup.VAR -> Color.var_,
   153     Markup.INNER_STRING -> Color.inner_quoted,
   154     Markup.INNER_CARTOUCHE -> Color.inner_cartouche,
   155     Markup.INNER_COMMENT -> Color.inner_comment,
   156     Markup.DYNAMIC_FACT -> Color.dynamic,
   157     Markup.CLASS_PARAMETER -> Color.class_parameter,
   158     Markup.ANTIQUOTE -> Color.antiquote,
   159     Markup.ML_KEYWORD1 -> Color.keyword1,
   160     Markup.ML_KEYWORD2 -> Color.keyword2,
   161     Markup.ML_KEYWORD3 -> Color.keyword3,
   162     Markup.ML_DELIMITER -> Color.main,
   163     Markup.ML_NUMERAL -> Color.inner_numeral,
   164     Markup.ML_CHAR -> Color.inner_quoted,
   165     Markup.ML_STRING -> Color.inner_quoted,
   166     Markup.ML_COMMENT -> Color.inner_comment,
   167     Markup.SML_STRING -> Color.inner_quoted,
   168     Markup.SML_COMMENT -> Color.inner_comment)
   169 
   170 
   171   /* tooltips */
   172 
   173   val tooltip_descriptions =
   174     Map(
   175       Markup.CITATION -> "citation",
   176       Markup.TOKEN_RANGE -> "inner syntax token",
   177       Markup.FREE -> "free variable",
   178       Markup.SKOLEM -> "skolem variable",
   179       Markup.BOUND -> "bound variable",
   180       Markup.VAR -> "schematic variable",
   181       Markup.TFREE -> "free type variable",
   182       Markup.TVAR -> "schematic type variable")
   183 
   184 
   185   /* markup elements */
   186 
   187   val active_elements =
   188     Markup.Elements(Markup.DIALOG, Markup.BROWSER, Markup.GRAPHVIEW,
   189       Markup.SENDBACK, Markup.JEDIT_ACTION, Markup.SIMP_TRACE_PANEL)
   190 
   191   val background_elements =
   192     Protocol.proper_status_elements + Markup.WRITELN_MESSAGE +
   193       Markup.STATE_MESSAGE + Markup.INFORMATION_MESSAGE +
   194       Markup.TRACING_MESSAGE + Markup.WARNING_MESSAGE +
   195       Markup.LEGACY_MESSAGE + Markup.ERROR_MESSAGE +
   196       Markup.BAD + Markup.INTENSIFY + Markup.ENTITY +
   197       Markup.Markdown_Item.name ++ active_elements
   198 
   199   val foreground_elements =
   200     Markup.Elements(Markup.STRING, Markup.ALT_STRING, Markup.VERBATIM,
   201       Markup.CARTOUCHE, Markup.ANTIQUOTED)
   202 
   203   val semantic_completion_elements =
   204     Markup.Elements(Markup.COMPLETION, Markup.NO_COMPLETION)
   205 
   206   val tooltip_elements =
   207     Markup.Elements(Markup.LANGUAGE, Markup.EXPRESSION, Markup.TIMING, Markup.ENTITY,
   208       Markup.SORTING, Markup.TYPING, Markup.CLASS_PARAMETER, Markup.ML_TYPING,
   209       Markup.ML_BREAKPOINT, Markup.PATH, Markup.DOC, Markup.URL, Markup.MARKDOWN_PARAGRAPH,
   210       Markup.Markdown_List.name) ++ Markup.Elements(tooltip_descriptions.keySet)
   211 
   212   val tooltip_message_elements =
   213     Markup.Elements(Markup.WRITELN, Markup.INFORMATION, Markup.WARNING, Markup.LEGACY, Markup.ERROR,
   214       Markup.BAD)
   215 
   216   val caret_focus_elements = Markup.Elements(Markup.ENTITY)
   217 
   218   val text_color_elements = Markup.Elements(text_color.keySet)
   219 }
   220 
   221 abstract class Rendering(
   222   val snapshot: Document.Snapshot,
   223   val options: Options,
   224   val session: Session)
   225 {
   226   override def toString: String = "Rendering(" + snapshot.toString + ")"
   227 
   228 
   229   /* completion */
   230 
   231   def semantic_completion(completed_range: Option[Text.Range], range: Text.Range)
   232       : Option[Text.Info[Completion.Semantic]] =
   233     if (snapshot.is_outdated) None
   234     else {
   235       snapshot.select(range, Rendering.semantic_completion_elements, _ =>
   236         {
   237           case Completion.Semantic.Info(info) =>
   238             completed_range match {
   239               case Some(range0) if range0.contains(info.range) && range0 != info.range => None
   240               case _ => Some(info)
   241             }
   242           case _ => None
   243         }).headOption.map(_.info)
   244     }
   245 
   246 
   247   /* spell checker */
   248 
   249   private lazy val spell_checker_elements =
   250     Markup.Elements(space_explode(',', options.string("spell_checker_elements")): _*)
   251 
   252   def spell_checker_ranges(range: Text.Range): List[Text.Range] =
   253     snapshot.select(range, spell_checker_elements, _ => _ => Some(())).map(_.range)
   254 
   255   def spell_checker_point(range: Text.Range): Option[Text.Range] =
   256     snapshot.select(range, spell_checker_elements, _ =>
   257       {
   258         case info => Some(snapshot.convert(info.range))
   259       }).headOption.map(_.info)
   260 
   261 
   262   /* text background */
   263 
   264   def background(elements: Markup.Elements, range: Text.Range, focus: Set[Long])
   265     : List[Text.Info[Rendering.Color.Value]] =
   266   {
   267     for {
   268       Text.Info(r, result) <-
   269         snapshot.cumulate[(List[Markup], Option[Rendering.Color.Value])](
   270           range, (List(Markup.Empty), None), Rendering.background_elements,
   271           command_states =>
   272             {
   273               case (((markups, color), Text.Info(_, XML.Elem(markup, _))))
   274               if markups.nonEmpty && Protocol.proper_status_elements(markup.name) =>
   275                 Some((markup :: markups, color))
   276               case (_, Text.Info(_, XML.Elem(Markup(Markup.BAD, _), _))) =>
   277                 Some((Nil, Some(Rendering.Color.bad)))
   278               case (_, Text.Info(_, XML.Elem(Markup(Markup.INTENSIFY, _), _))) =>
   279                 Some((Nil, Some(Rendering.Color.intensify)))
   280               case (_, Text.Info(_, XML.Elem(Markup(Markup.ENTITY, props), _))) =>
   281                 props match {
   282                   case Markup.Entity.Def(i) if focus(i) => Some((Nil, Some(Rendering.Color.entity)))
   283                   case Markup.Entity.Ref(i) if focus(i) => Some((Nil, Some(Rendering.Color.entity)))
   284                   case _ => None
   285                 }
   286               case (_, Text.Info(_, XML.Elem(Markup.Markdown_Item(depth), _))) =>
   287                 val color =
   288                   depth % 4 match {
   289                     case 1 => Rendering.Color.markdown_item1
   290                     case 2 => Rendering.Color.markdown_item2
   291                     case 3 => Rendering.Color.markdown_item3
   292                     case _ => Rendering.Color.markdown_item4
   293                   }
   294                 Some((Nil, Some(color)))
   295               case (acc, Text.Info(_, Protocol.Dialog(_, serial, result))) =>
   296                 command_states.collectFirst(
   297                   { case st if st.results.defined(serial) => st.results.get(serial).get }) match
   298                 {
   299                   case Some(Protocol.Dialog_Result(res)) if res == result =>
   300                     Some((Nil, Some(Rendering.Color.active_result)))
   301                   case _ =>
   302                     Some((Nil, Some(Rendering.Color.active)))
   303                 }
   304               case (_, Text.Info(_, elem)) =>
   305                 if (Rendering.active_elements(elem.name))
   306                   Some((Nil, Some(Rendering.Color.active)))
   307                 else None
   308             })
   309       color <-
   310         (result match {
   311           case (markups, opt_color) if markups.nonEmpty =>
   312             val status = Protocol.Status.make(markups.iterator)
   313             if (status.is_unprocessed) Some(Rendering.Color.unprocessed1)
   314             else if (status.is_running) Some(Rendering.Color.running1)
   315             else opt_color
   316           case (_, opt_color) => opt_color
   317         })
   318     } yield Text.Info(r, color)
   319   }
   320 
   321 
   322   /* text foreground */
   323 
   324   def foreground(range: Text.Range): List[Text.Info[Rendering.Color.Value]] =
   325     snapshot.select(range, Rendering.foreground_elements, _ =>
   326       {
   327         case Text.Info(_, elem) =>
   328           if (elem.name == Markup.ANTIQUOTED) Some(Rendering.Color.antiquoted)
   329           else Some(Rendering.Color.quoted)
   330       })
   331 
   332 
   333   /* caret focus */
   334 
   335   private def entity_focus(range: Text.Range,
   336     check: (Boolean, Long) => Boolean = (is_def: Boolean, i: Long) => is_def): Set[Long] =
   337   {
   338     val results =
   339       snapshot.cumulate[Set[Long]](range, Set.empty, Rendering.caret_focus_elements, _ =>
   340           {
   341             case (serials, Text.Info(_, XML.Elem(Markup(Markup.ENTITY, props), _))) =>
   342               props match {
   343                 case Markup.Entity.Def(i) if check(true, i) => Some(serials + i)
   344                 case Markup.Entity.Ref(i) if check(false, i) => Some(serials + i)
   345                 case _ => None
   346               }
   347             case _ => None
   348           })
   349     (Set.empty[Long] /: results){ case (s1, Text.Info(_, s2)) => s1 ++ s2 }
   350   }
   351 
   352   def caret_focus(caret_range: Text.Range, visible_range: Text.Range): Set[Long] =
   353   {
   354     val focus_defs = entity_focus(caret_range)
   355     if (focus_defs.nonEmpty) focus_defs
   356     else {
   357       val visible_defs = entity_focus(visible_range)
   358       entity_focus(caret_range, (is_def: Boolean, i: Long) => !is_def && visible_defs.contains(i))
   359     }
   360   }
   361 
   362   def caret_focus_ranges(caret_range: Text.Range, visible_range: Text.Range): List[Text.Range] =
   363   {
   364     val focus = caret_focus(caret_range, visible_range)
   365     if (focus.nonEmpty) {
   366       val results =
   367         snapshot.cumulate[Boolean](visible_range, false, Rendering.caret_focus_elements, _ =>
   368           {
   369             case (_, Text.Info(_, XML.Elem(Markup(Markup.ENTITY, props), _))) =>
   370               props match {
   371                 case Markup.Entity.Def(i) if focus(i) => Some(true)
   372                 case Markup.Entity.Ref(i) if focus(i) => Some(true)
   373                 case _ => None
   374               }
   375           })
   376       for (info <- results if info.info) yield info.range
   377     }
   378     else Nil
   379   }
   380 
   381 
   382   /* message underline color */
   383 
   384   def message_underline_color(elements: Markup.Elements, range: Text.Range)
   385     : List[Text.Info[Rendering.Color.Value]] =
   386   {
   387     val results =
   388       snapshot.cumulate[Int](range, 0, elements, _ =>
   389         {
   390           case (pri, Text.Info(_, elem)) => Some(pri max Rendering.message_pri(elem.name))
   391         })
   392     for {
   393       Text.Info(r, pri) <- results
   394       color <- Rendering.message_underline_color.get(pri)
   395     } yield Text.Info(r, color)
   396   }
   397 
   398 
   399   /* tooltips */
   400 
   401   def timing_threshold: Double
   402 
   403   private sealed case class Tooltip_Info(
   404     range: Text.Range,
   405     timing: Timing = Timing.zero,
   406     messages: List[Command.Results.Entry] = Nil,
   407     rev_infos: List[(Boolean, XML.Tree)] = Nil)
   408   {
   409     def + (t: Timing): Tooltip_Info = copy(timing = timing + t)
   410     def + (r0: Text.Range, serial: Long, tree: XML.Tree): Tooltip_Info =
   411     {
   412       val r = snapshot.convert(r0)
   413       if (range == r) copy(messages = (serial -> tree) :: messages)
   414       else copy(range = r, messages = List(serial -> tree))
   415     }
   416     def + (r0: Text.Range, important: Boolean, tree: XML.Tree): Tooltip_Info =
   417     {
   418       val r = snapshot.convert(r0)
   419       if (range == r) copy(rev_infos = (important -> tree) :: rev_infos)
   420       else copy (range = r, rev_infos = List(important -> tree))
   421     }
   422     def infos(important: Boolean): List[XML.Tree] =
   423       rev_infos.filter(p => p._1 == important).reverse.map(_._2)
   424   }
   425 
   426   def tooltips(elements: Markup.Elements, range: Text.Range): Option[Text.Info[List[XML.Tree]]] =
   427   {
   428     val results =
   429       snapshot.cumulate[Tooltip_Info](range, Tooltip_Info(range), elements, _ =>
   430         {
   431           case (info, Text.Info(_, XML.Elem(Markup.Timing(t), _))) => Some(info + t)
   432 
   433           case (info, Text.Info(r0, XML.Elem(Markup(name, props @ Markup.Serial(serial)), body)))
   434           if Rendering.tooltip_message_elements(name) && body.nonEmpty =>
   435             Some(info + (r0, serial, XML.Elem(Markup(Markup.message(name), props), body)))
   436 
   437           case (info, Text.Info(r0, XML.Elem(Markup.Entity(kind, name), _)))
   438           if kind != "" && kind != Markup.ML_DEF =>
   439             val kind1 = Word.implode(Word.explode('_', kind))
   440             val txt1 =
   441               if (name == "") kind1
   442               else if (kind1 == "") quote(name)
   443               else kind1 + " " + quote(name)
   444             val txt2 =
   445               if (kind == Markup.COMMAND && info.timing.elapsed.seconds >= timing_threshold)
   446                 "\n" + info.timing.message
   447               else ""
   448             Some(info + (r0, true, XML.Text(txt1 + txt2)))
   449 
   450           case (info, Text.Info(r0, XML.Elem(Markup.Path(name), _))) =>
   451             val file = session.resources.append_file(snapshot.node_name.master_dir, name)
   452             val text =
   453               if (name == file) "file " + quote(file)
   454               else "path " + quote(name) + "\nfile " + quote(file)
   455             Some(info + (r0, true, XML.Text(text)))
   456 
   457           case (info, Text.Info(r0, XML.Elem(Markup.Doc(name), _))) =>
   458             val text = "doc " + quote(name)
   459             Some(info + (r0, true, XML.Text(text)))
   460 
   461           case (info, Text.Info(r0, XML.Elem(Markup.Url(name), _))) =>
   462             Some(info + (r0, true, XML.Text("URL " + quote(name))))
   463 
   464           case (info, Text.Info(r0, XML.Elem(Markup(name, _), body)))
   465           if name == Markup.SORTING || name == Markup.TYPING =>
   466             Some(info + (r0, true, Pretty.block(XML.Text("::") :: Pretty.brk(1) :: body)))
   467 
   468           case (info, Text.Info(r0, XML.Elem(Markup(Markup.CLASS_PARAMETER, _), body))) =>
   469             Some(info + (r0, true, Pretty.block(0, body)))
   470 
   471           case (info, Text.Info(r0, XML.Elem(Markup(Markup.ML_TYPING, _), body))) =>
   472             Some(info + (r0, false, Pretty.block(XML.Text("ML:") :: Pretty.brk(1) :: body)))
   473 
   474           case (info, Text.Info(r0, Protocol.ML_Breakpoint(breakpoint))) =>
   475             Debugger.get(session) match {
   476               case None => None
   477               case Some(debugger) =>
   478                 val text =
   479                   if (debugger.breakpoint_state(breakpoint)) "breakpoint (enabled)"
   480                   else "breakpoint (disabled)"
   481                 Some(info + (r0, true, XML.Text(text)))
   482             }
   483           case (info, Text.Info(r0, XML.Elem(Markup.Language(language, _, _, _), _))) =>
   484             val lang = Word.implode(Word.explode('_', language))
   485             Some(info + (r0, true, XML.Text("language: " + lang)))
   486 
   487           case (info, Text.Info(r0, XML.Elem(Markup.Expression(kind), _))) =>
   488             val descr = if (kind == "") "expression" else "expression: " + kind
   489             Some(info + (r0, true, XML.Text(descr)))
   490 
   491           case (info, Text.Info(r0, XML.Elem(Markup(Markup.MARKDOWN_PARAGRAPH, _), _))) =>
   492             Some(info + (r0, true, XML.Text("Markdown: paragraph")))
   493           case (info, Text.Info(r0, XML.Elem(Markup.Markdown_List(kind), _))) =>
   494             Some(info + (r0, true, XML.Text("Markdown: " + kind)))
   495 
   496           case (info, Text.Info(r0, XML.Elem(Markup(name, _), _))) =>
   497             Rendering.tooltip_descriptions.get(name).map(desc => info + (r0, true, XML.Text(desc)))
   498         }).map(_.info)
   499 
   500     if (results.isEmpty) None
   501     else {
   502       val r = Text.Range(results.head.range.start, results.last.range.stop)
   503       val all_tips =
   504         Command.Results.make(results.flatMap(_.messages)).iterator.map(_._2).toList :::
   505         results.flatMap(res => res.infos(true)) :::
   506         results.flatMap(res => res.infos(false)).lastOption.toList
   507       if (all_tips.isEmpty) None else Some(Text.Info(r, all_tips))
   508     }
   509   }
   510 }