src/Tools/VSCode/src/vscode_model.scala
changeset 72761 4519eeefe3b5
parent 71774 491f185fd705
child 72772 a9ef39041114
equal deleted inserted replaced
72760:042180540068 72761:4519eeefe3b5
       
     1 /*  Title:      Tools/VSCode/src/vscode_model.scala
       
     2     Author:     Makarius
       
     3 
       
     4 VSCode document model for line-oriented text.
       
     5 */
       
     6 
       
     7 package isabelle.vscode
       
     8 
       
     9 
       
    10 import isabelle._
       
    11 
       
    12 import java.io.{File => JFile}
       
    13 
       
    14 
       
    15 object VSCode_Model
       
    16 {
       
    17   /* decorations */
       
    18 
       
    19   object Decoration
       
    20   {
       
    21     def empty(typ: String): Decoration = Decoration(typ, Nil)
       
    22 
       
    23     def ranges(typ: String, ranges: List[Text.Range]): Decoration =
       
    24       Decoration(typ, ranges.map(Text.Info(_, List.empty[XML.Body])))
       
    25   }
       
    26   sealed case class Decoration(typ: String, content: List[Text.Info[List[XML.Body]]])
       
    27 
       
    28 
       
    29   /* content */
       
    30 
       
    31   object Content
       
    32   {
       
    33     val empty: Content = Content(Line.Document.empty)
       
    34   }
       
    35 
       
    36   sealed case class Content(doc: Line.Document)
       
    37   {
       
    38     override def toString: String = doc.toString
       
    39     def text_length: Text.Offset = doc.text_length
       
    40     def text_range: Text.Range = doc.text_range
       
    41     def text: String = doc.text
       
    42 
       
    43     lazy val bytes: Bytes = Bytes(text)
       
    44     lazy val chunk: Symbol.Text_Chunk = Symbol.Text_Chunk(text)
       
    45     lazy val bibtex_entries: List[Text.Info[String]] =
       
    46       try { Bibtex.entries(text) }
       
    47       catch { case ERROR(_) => Nil }
       
    48 
       
    49     def recode_symbols: List[LSP.TextEdit] =
       
    50       (for {
       
    51         (line, l) <- doc.lines.iterator.zipWithIndex
       
    52         text1 = Symbol.encode(line.text)
       
    53         if (line.text != text1)
       
    54       } yield {
       
    55         val range = Line.Range(Line.Position(l), Line.Position(l, line.text.length))
       
    56         LSP.TextEdit(range, text1)
       
    57       }).toList
       
    58   }
       
    59 
       
    60   def init(session: Session, editor: Language_Server.Editor, node_name: Document.Node.Name)
       
    61     : VSCode_Model =
       
    62   {
       
    63     VSCode_Model(session, editor, node_name, Content.empty,
       
    64       node_required = File_Format.registry.is_theory(node_name))
       
    65   }
       
    66 }
       
    67 
       
    68 sealed case class VSCode_Model(
       
    69   session: Session,
       
    70   editor: Language_Server.Editor,
       
    71   node_name: Document.Node.Name,
       
    72   content: VSCode_Model.Content,
       
    73   version: Option[Long] = None,
       
    74   external_file: Boolean = false,
       
    75   node_required: Boolean = false,
       
    76   last_perspective: Document.Node.Perspective_Text = Document.Node.no_perspective_text,
       
    77   pending_edits: List[Text.Edit] = Nil,
       
    78   published_diagnostics: List[Text.Info[Command.Results]] = Nil,
       
    79   published_decorations: List[VSCode_Model.Decoration] = Nil) extends Document.Model
       
    80 {
       
    81   model =>
       
    82 
       
    83 
       
    84   /* content */
       
    85 
       
    86   def get_text(range: Text.Range): Option[String] = content.doc.get_text(range)
       
    87 
       
    88   def set_version(new_version: Long): VSCode_Model = copy(version = Some(new_version))
       
    89 
       
    90 
       
    91   /* external file */
       
    92 
       
    93   def external(b: Boolean): VSCode_Model = copy(external_file = b)
       
    94 
       
    95   def node_visible: Boolean = !external_file
       
    96 
       
    97 
       
    98   /* header */
       
    99 
       
   100   def node_header: Document.Node.Header =
       
   101     resources.special_header(node_name) getOrElse
       
   102       resources.check_thy_reader(node_name, Scan.char_reader(content.text))
       
   103 
       
   104 
       
   105   /* perspective */
       
   106 
       
   107   def node_perspective(doc_blobs: Document.Blobs, caret: Option[Line.Position])
       
   108     : (Boolean, Document.Node.Perspective_Text) =
       
   109   {
       
   110     if (is_theory) {
       
   111       val snapshot = model.snapshot()
       
   112 
       
   113       val caret_perspective = resources.options.int("vscode_caret_perspective") max 0
       
   114       val caret_range =
       
   115         if (caret_perspective != 0) {
       
   116           caret match {
       
   117             case Some(pos) =>
       
   118               val doc = content.doc
       
   119               val pos1 = Line.Position((pos.line - caret_perspective) max 0)
       
   120               val pos2 = Line.Position((pos.line + caret_perspective + 1) min doc.lines.length)
       
   121               Text.Range(doc.offset(pos1).get, doc.offset(pos2).get)
       
   122             case None => Text.Range.offside
       
   123           }
       
   124         }
       
   125         else if (node_visible) content.text_range
       
   126         else Text.Range.offside
       
   127 
       
   128       val text_perspective =
       
   129         if (snapshot.commands_loading_ranges(resources.visible_node(_)).nonEmpty)
       
   130           Text.Perspective.full
       
   131         else
       
   132           content.text_range.try_restrict(caret_range) match {
       
   133             case Some(range) => Text.Perspective(List(range))
       
   134             case None => Text.Perspective.empty
       
   135           }
       
   136 
       
   137       val overlays = editor.node_overlays(node_name)
       
   138 
       
   139       (snapshot.node.load_commands_changed(doc_blobs),
       
   140         Document.Node.Perspective(node_required, text_perspective, overlays))
       
   141     }
       
   142     else (false, Document.Node.no_perspective_text)
       
   143   }
       
   144 
       
   145 
       
   146   /* blob */
       
   147 
       
   148   def get_blob: Option[Document.Blob] =
       
   149     if (is_theory) None
       
   150     else Some(Document.Blob(content.bytes, content.text, content.chunk, pending_edits.nonEmpty))
       
   151 
       
   152 
       
   153   /* bibtex entries */
       
   154 
       
   155   def bibtex_entries: List[Text.Info[String]] =
       
   156     model.content.bibtex_entries
       
   157 
       
   158 
       
   159   /* edits */
       
   160 
       
   161   def change_text(text: String, range: Option[Line.Range] = None): Option[VSCode_Model] =
       
   162   {
       
   163     val insert = Line.normalize(text)
       
   164     range match {
       
   165       case None =>
       
   166         Text.Edit.replace(0, content.text, insert) match {
       
   167           case Nil => None
       
   168           case edits =>
       
   169             val content1 = VSCode_Model.Content(Line.Document(insert))
       
   170             Some(copy(content = content1, pending_edits = pending_edits ::: edits))
       
   171         }
       
   172       case Some(remove) =>
       
   173         content.doc.change(remove, insert) match {
       
   174           case None => error("Failed to apply document change: " + remove)
       
   175           case Some((Nil, _)) => None
       
   176           case Some((edits, doc1)) =>
       
   177             val content1 = VSCode_Model.Content(doc1)
       
   178             Some(copy(content = content1, pending_edits = pending_edits ::: edits))
       
   179         }
       
   180     }
       
   181   }
       
   182 
       
   183   def flush_edits(
       
   184       unicode_symbols: Boolean,
       
   185       doc_blobs: Document.Blobs,
       
   186       file: JFile,
       
   187       caret: Option[Line.Position])
       
   188     : Option[((List[LSP.TextDocumentEdit], List[Document.Edit_Text]), VSCode_Model)] =
       
   189   {
       
   190     val workspace_edits =
       
   191       if (unicode_symbols && version.isDefined) {
       
   192         val edits = content.recode_symbols
       
   193         if (edits.nonEmpty) List(LSP.TextDocumentEdit(file, version.get, edits))
       
   194         else Nil
       
   195       }
       
   196       else Nil
       
   197 
       
   198     val (reparse, perspective) = node_perspective(doc_blobs, caret)
       
   199     if (reparse || pending_edits.nonEmpty || last_perspective != perspective ||
       
   200         workspace_edits.nonEmpty)
       
   201     {
       
   202       val prover_edits = node_edits(node_header, pending_edits, perspective)
       
   203       val edits = (workspace_edits, prover_edits)
       
   204       Some((edits, copy(pending_edits = Nil, last_perspective = perspective)))
       
   205     }
       
   206     else None
       
   207   }
       
   208 
       
   209 
       
   210   /* publish annotations */
       
   211 
       
   212   def publish(rendering: VSCode_Rendering):
       
   213     (Option[List[Text.Info[Command.Results]]], Option[List[VSCode_Model.Decoration]], VSCode_Model) =
       
   214   {
       
   215     val diagnostics = rendering.diagnostics
       
   216     val decorations =
       
   217       if (node_visible) rendering.decorations
       
   218       else { for (deco <- published_decorations) yield VSCode_Model.Decoration.empty(deco.typ) }
       
   219 
       
   220     val changed_diagnostics =
       
   221       if (diagnostics == published_diagnostics) None else Some(diagnostics)
       
   222     val changed_decorations =
       
   223       if (decorations == published_decorations) None
       
   224       else if (published_decorations.isEmpty) Some(decorations)
       
   225       else Some(for { (a, b) <- decorations zip published_decorations if a != b } yield a)
       
   226 
       
   227     (changed_diagnostics, changed_decorations,
       
   228       copy(published_diagnostics = diagnostics, published_decorations = decorations))
       
   229   }
       
   230 
       
   231 
       
   232   /* prover session */
       
   233 
       
   234   def resources: VSCode_Resources = session.resources.asInstanceOf[VSCode_Resources]
       
   235 
       
   236   def is_stable: Boolean = pending_edits.isEmpty
       
   237   def snapshot(): Document.Snapshot = session.snapshot(node_name, pending_edits)
       
   238 
       
   239   def rendering(snapshot: Document.Snapshot): VSCode_Rendering =
       
   240     new VSCode_Rendering(snapshot, model)
       
   241   def rendering(): VSCode_Rendering = rendering(snapshot())
       
   242 
       
   243 
       
   244   /* syntax */
       
   245 
       
   246   def syntax(): Outer_Syntax =
       
   247     if (is_theory) session.recent_syntax(node_name) else Outer_Syntax.empty
       
   248 }