src/Pure/PIDE/rendering.scala
author wenzelm
Mon Mar 06 11:38:06 2017 +0100 (2017-03-06)
changeset 65124 759c64c39a6f
parent 65121 12c6774a8f65
child 65126 45ccb8ee3d08
permissions -rw-r--r--
more generic colors;
     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 = values
    30 
    31     // foreground
    32     val quoted = Value("quoted")
    33     val antiquoted = Value("antiquoted")
    34     val foreground = values -- background
    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 = values -- background -- foreground
    43 
    44     // message background
    45 
    46     val writeln_message = Value("writeln_message")
    47     val information_message = Value("information_message")
    48     val tracing_message = Value("tracing_message")
    49     val warning_message = Value("warning_message")
    50     val legacy_message = Value("legacy_message")
    51     val error_message = Value("error_message")
    52     val message_background = values -- background -- foreground -- message_underline
    53   }
    54 
    55 
    56   /* message priorities */
    57 
    58   val state_pri = 1
    59   val writeln_pri = 2
    60   val information_pri = 3
    61   val tracing_pri = 4
    62   val warning_pri = 5
    63   val legacy_pri = 6
    64   val error_pri = 7
    65 
    66   val message_pri = Map(
    67     Markup.STATE -> state_pri,
    68     Markup.STATE_MESSAGE -> state_pri,
    69     Markup.WRITELN -> writeln_pri,
    70     Markup.WRITELN_MESSAGE -> writeln_pri,
    71     Markup.INFORMATION -> information_pri,
    72     Markup.INFORMATION_MESSAGE -> information_pri,
    73     Markup.TRACING -> tracing_pri,
    74     Markup.TRACING_MESSAGE -> tracing_pri,
    75     Markup.WARNING -> warning_pri,
    76     Markup.WARNING_MESSAGE -> warning_pri,
    77     Markup.LEGACY -> legacy_pri,
    78     Markup.LEGACY_MESSAGE -> legacy_pri,
    79     Markup.ERROR -> error_pri,
    80     Markup.ERROR_MESSAGE -> error_pri)
    81 
    82   val message_underline_color = Map(
    83     writeln_pri -> Color.writeln,
    84     information_pri -> Color.information,
    85     warning_pri -> Color.warning,
    86     legacy_pri -> Color.legacy,
    87     error_pri -> Color.error)
    88 
    89   val message_background_color = Map(
    90     writeln_pri -> Color.writeln_message,
    91     information_pri -> Color.information_message,
    92     tracing_pri -> Color.tracing_message,
    93     warning_pri -> Color.warning_message,
    94     legacy_pri -> Color.legacy_message,
    95     error_pri -> Color.error_message)
    96 
    97 
    98   /* markup elements */
    99 
   100   val active_elements =
   101     Markup.Elements(Markup.DIALOG, Markup.BROWSER, Markup.GRAPHVIEW,
   102       Markup.SENDBACK, Markup.JEDIT_ACTION, Markup.SIMP_TRACE_PANEL)
   103 
   104   private val background_elements =
   105     Protocol.proper_status_elements + Markup.WRITELN_MESSAGE +
   106       Markup.STATE_MESSAGE + Markup.INFORMATION_MESSAGE +
   107       Markup.TRACING_MESSAGE + Markup.WARNING_MESSAGE +
   108       Markup.LEGACY_MESSAGE + Markup.ERROR_MESSAGE +
   109       Markup.BAD + Markup.INTENSIFY + Markup.ENTITY +
   110       Markup.Markdown_Item.name ++ active_elements
   111 
   112   private val foreground_elements =
   113     Markup.Elements(Markup.STRING, Markup.ALT_STRING, Markup.VERBATIM,
   114       Markup.CARTOUCHE, Markup.ANTIQUOTED)
   115 
   116   private val semantic_completion_elements =
   117     Markup.Elements(Markup.COMPLETION, Markup.NO_COMPLETION)
   118 
   119   private val tooltip_descriptions =
   120     Map(
   121       Markup.CITATION -> "citation",
   122       Markup.TOKEN_RANGE -> "inner syntax token",
   123       Markup.FREE -> "free variable",
   124       Markup.SKOLEM -> "skolem variable",
   125       Markup.BOUND -> "bound variable",
   126       Markup.VAR -> "schematic variable",
   127       Markup.TFREE -> "free type variable",
   128       Markup.TVAR -> "schematic type variable")
   129 
   130   private val tooltip_elements =
   131     Markup.Elements(Markup.LANGUAGE, Markup.EXPRESSION, Markup.TIMING, Markup.ENTITY,
   132       Markup.SORTING, Markup.TYPING, Markup.CLASS_PARAMETER, Markup.ML_TYPING,
   133       Markup.ML_BREAKPOINT, Markup.PATH, Markup.DOC, Markup.URL, Markup.MARKDOWN_PARAGRAPH,
   134       Markup.Markdown_List.name) ++ Markup.Elements(tooltip_descriptions.keySet)
   135 
   136   private def pretty_typing(kind: String, body: XML.Body): XML.Tree =
   137     Pretty.block(XML.Text(kind) :: Pretty.brk(1) :: body)
   138 
   139   val caret_focus_elements = Markup.Elements(Markup.ENTITY)
   140 }
   141 
   142 abstract class Rendering(
   143   val snapshot: Document.Snapshot,
   144   val options: Options,
   145   val resources: Resources)
   146 {
   147   override def toString: String = "Rendering(" + snapshot.toString + ")"
   148 
   149 
   150   /* completion */
   151 
   152   def semantic_completion(completed_range: Option[Text.Range], range: Text.Range)
   153       : Option[Text.Info[Completion.Semantic]] =
   154     if (snapshot.is_outdated) None
   155     else {
   156       snapshot.select(range, Rendering.semantic_completion_elements, _ =>
   157         {
   158           case Completion.Semantic.Info(info) =>
   159             completed_range match {
   160               case Some(range0) if range0.contains(info.range) && range0 != info.range => None
   161               case _ => Some(info)
   162             }
   163           case _ => None
   164         }).headOption.map(_.info)
   165     }
   166 
   167 
   168   /* tooltips */
   169 
   170   def timing_threshold: Double
   171 
   172   def tooltips(range: Text.Range): Option[Text.Info[List[XML.Tree]]] =
   173   {
   174     def add(prev: Text.Info[(Timing, Vector[(Boolean, XML.Tree)])],
   175       r0: Text.Range, p: (Boolean, XML.Tree)): Text.Info[(Timing, Vector[(Boolean, XML.Tree)])] =
   176     {
   177       val r = snapshot.convert(r0)
   178       val (t, info) = prev.info
   179       if (prev.range == r)
   180         Text.Info(r, (t, info :+ p))
   181       else Text.Info(r, (t, Vector(p)))
   182     }
   183 
   184     val results =
   185       snapshot.cumulate[Text.Info[(Timing, Vector[(Boolean, XML.Tree)])]](
   186         range, Text.Info(range, (Timing.zero, Vector.empty)), Rendering.tooltip_elements, _ =>
   187         {
   188           case (Text.Info(r, (t1, info)), Text.Info(_, XML.Elem(Markup.Timing(t2), _))) =>
   189             Some(Text.Info(r, (t1 + t2, info)))
   190 
   191           case (prev, Text.Info(r, XML.Elem(Markup.Entity(kind, name), _)))
   192           if kind != "" && kind != Markup.ML_DEF =>
   193             val kind1 = Word.implode(Word.explode('_', kind))
   194             val txt1 =
   195               if (name == "") kind1
   196               else if (kind1 == "") quote(name)
   197               else kind1 + " " + quote(name)
   198             val t = prev.info._1
   199             val txt2 =
   200               if (kind == Markup.COMMAND && t.elapsed.seconds >= timing_threshold)
   201                 "\n" + t.message
   202               else ""
   203             Some(add(prev, r, (true, XML.Text(txt1 + txt2))))
   204 
   205           case (prev, Text.Info(r, XML.Elem(Markup.Path(name), _))) =>
   206             val file = resources.append_file(snapshot.node_name.master_dir, name)
   207             val text =
   208               if (name == file) "file " + quote(file)
   209               else "path " + quote(name) + "\nfile " + quote(file)
   210             Some(add(prev, r, (true, XML.Text(text))))
   211 
   212           case (prev, Text.Info(r, XML.Elem(Markup.Doc(name), _))) =>
   213             val text = "doc " + quote(name)
   214             Some(add(prev, r, (true, XML.Text(text))))
   215 
   216           case (prev, Text.Info(r, XML.Elem(Markup.Url(name), _))) =>
   217             Some(add(prev, r, (true, XML.Text("URL " + quote(name)))))
   218 
   219           case (prev, Text.Info(r, XML.Elem(Markup(name, _), body)))
   220           if name == Markup.SORTING || name == Markup.TYPING =>
   221             Some(add(prev, r, (true, Rendering.pretty_typing("::", body))))
   222 
   223           case (prev, Text.Info(r, XML.Elem(Markup(Markup.CLASS_PARAMETER, _), body))) =>
   224             Some(add(prev, r, (true, Pretty.block(0, body))))
   225 
   226           case (prev, Text.Info(r, XML.Elem(Markup(Markup.ML_TYPING, _), body))) =>
   227             Some(add(prev, r, (false, Rendering.pretty_typing("ML:", body))))
   228 
   229           case (prev, Text.Info(r, Protocol.ML_Breakpoint(breakpoint))) =>
   230             val text =
   231               if (Debugger.breakpoint_state(breakpoint)) "breakpoint (enabled)"
   232               else "breakpoint (disabled)"
   233             Some(add(prev, r, (true, XML.Text(text))))
   234 
   235           case (prev, Text.Info(r, XML.Elem(Markup.Language(language, _, _, _), _))) =>
   236             val lang = Word.implode(Word.explode('_', language))
   237             Some(add(prev, r, (true, XML.Text("language: " + lang))))
   238 
   239           case (prev, Text.Info(r, XML.Elem(Markup.Expression(kind), _))) =>
   240             val descr = if (kind == "") "expression" else "expression: " + kind
   241             Some(add(prev, r, (true, XML.Text(descr))))
   242 
   243           case (prev, Text.Info(r, XML.Elem(Markup(Markup.MARKDOWN_PARAGRAPH, _), _))) =>
   244             Some(add(prev, r, (true, XML.Text("Markdown: paragraph"))))
   245           case (prev, Text.Info(r, XML.Elem(Markup.Markdown_List(kind), _))) =>
   246             Some(add(prev, r, (true, XML.Text("Markdown: " + kind))))
   247 
   248           case (prev, Text.Info(r, XML.Elem(Markup(name, _), _))) =>
   249             Rendering.tooltip_descriptions.get(name).
   250               map(descr => add(prev, r, (true, XML.Text(descr))))
   251         }).map(_.info)
   252 
   253     results.map(_.info).flatMap(res => res._2.toList) match {
   254       case Nil => None
   255       case tips =>
   256         val r = Text.Range(results.head.range.start, results.last.range.stop)
   257         val all_tips = (tips.filter(_._1) ++ tips.filter(!_._1).lastOption.toList).map(_._2)
   258         Some(Text.Info(r, all_tips))
   259     }
   260   }
   261 
   262 
   263   /* text background */
   264 
   265   def background(range: Text.Range, focus: Set[Long]): 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 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(
   385     elements: Markup.Elements, range: Text.Range): 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 }