more general support for Isabelle/PIDE file formats -- less hardwired Bibtex operations;
authorwenzelm
Wed, 07 Nov 2018 21:42:16 +0100
changeset 69255 800b1ce96fce
parent 69254 9f8d26b8c731
child 69256 c78c95d2a3d1
more general support for Isabelle/PIDE file formats -- less hardwired Bibtex operations;
etc/settings
lib/scripts/getfunctions
src/Pure/PIDE/document.scala
src/Pure/PIDE/document_status.scala
src/Pure/PIDE/headless.scala
src/Pure/PIDE/resources.scala
src/Pure/Thy/bibtex.scala
src/Pure/Thy/file_format.scala
src/Pure/Thy/present.scala
src/Pure/Thy/sessions.scala
src/Pure/Thy/thy_header.scala
src/Pure/build-jars
src/Tools/VSCode/src/document_model.scala
src/Tools/VSCode/src/preview_panel.scala
src/Tools/VSCode/src/vscode_resources.scala
src/Tools/jEdit/src/document_model.scala
src/Tools/jEdit/src/jedit_resources.scala
src/Tools/jEdit/src/plugin.scala
src/Tools/jEdit/src/theories_dockable.scala
--- a/etc/settings	Wed Nov 07 14:06:43 2018 +0100
+++ b/etc/settings	Wed Nov 07 21:42:16 2018 +0100
@@ -20,6 +20,8 @@
 
 classpath "$ISABELLE_HOME/lib/classes/Pure.jar"
 
+isabelle_file_format 'isabelle.Bibtex$File_Format'
+
 #paranoia settings -- avoid intrusion of alien options
 unset "_JAVA_OPTIONS"
 unset "JAVA_TOOL_OPTIONS"
--- a/lib/scripts/getfunctions	Wed Nov 07 14:06:43 2018 +0100
+++ b/lib/scripts/getfunctions	Wed Nov 07 21:42:16 2018 +0100
@@ -109,6 +109,22 @@
 }
 export -f classpath
 
+#file formats
+function isabelle_file_format ()
+{
+  local X=""
+  for X in "$@"
+  do
+    if [ -z "$ISABELLE_CLASSES_FILE_FORMAT" ]; then
+      ISABELLE_CLASSES_FILE_FORMAT="$X"
+    else
+      ISABELLE_CLASSES_FILE_FORMAT="$ISABELLE_CLASSES_FILE_FORMAT:$X"
+    fi
+  done
+  export ISABELLE_CLASSES_FILE_FORMAT
+}
+export -f isabelle_file_format
+
 #administrative build
 if [ -e "$ISABELLE_HOME/Admin/build" ]; then
   function isabelle_admin_build ()
--- a/src/Pure/PIDE/document.scala	Wed Nov 07 14:06:43 2018 +0100
+++ b/src/Pure/PIDE/document.scala	Wed Nov 07 21:42:16 2018 +0100
@@ -119,9 +119,6 @@
 
       def path: Path = Path.explode(File.standard_path(node))
 
-      def is_bibtex: Boolean = Bibtex.is_bibtex(node)
-      def is_bibtex_theory: Boolean = Bibtex.is_bibtex(theory)
-
       def is_theory: Boolean = theory.nonEmpty
 
       def theory_base_name: String = Long_Name.base_name(theory)
@@ -586,8 +583,6 @@
 
     def node_required: Boolean
     def get_blob: Option[Blob]
-    def is_bibtex: Boolean = node_name.is_bibtex
-    def is_bibtex_theory: Boolean = node_name.is_bibtex_theory
     def bibtex_entries: List[Text.Info[String]]
 
     def node_edits(
--- a/src/Pure/PIDE/document_status.scala	Wed Nov 07 14:06:43 2018 +0100
+++ b/src/Pure/PIDE/document_status.scala	Wed Nov 07 21:42:16 2018 +0100
@@ -248,7 +248,7 @@
       }
 
     def update(
-      session_base: Sessions.Base,
+      resources: Resources,
       state: Document.State,
       version: Document.Version,
       domain: Option[Set[Document.Node.Name]] = None,
@@ -258,8 +258,8 @@
       val update_iterator =
         for {
           name <- domain.getOrElse(nodes1.domain).iterator
-          if !Sessions.is_hidden(name) &&
-              !session_base.loaded_theory(name) &&
+          if !resources.is_hidden(name) &&
+              !resources.session_base.loaded_theory(name) &&
               !nodes1.is_suppressed(name) &&
               !nodes1(name).is_empty
           st = Document_Status.Node_Status.make(state, version, name)
--- a/src/Pure/PIDE/headless.scala	Wed Nov 07 14:06:43 2018 +0100
+++ b/src/Pure/PIDE/headless.scala	Wed Nov 07 21:42:16 2018 +0100
@@ -263,7 +263,7 @@
                       else changed.nodes.iterator.filter(dep_theories_set).toSet
 
                     val (nodes_status_changed, nodes_status1) =
-                      st.nodes_status.update(resources.session_base, state, version,
+                      st.nodes_status.update(resources, state, version,
                         domain = Some(domain), trim = changed.assignment)
 
                     if (nodes_status_delay >= Time.zero && nodes_status_changed) {
--- a/src/Pure/PIDE/resources.scala	Wed Nov 07 14:06:43 2018 +0100
+++ b/src/Pure/PIDE/resources.scala	Wed Nov 07 21:42:16 2018 +0100
@@ -19,6 +19,23 @@
 {
   resources =>
 
+
+  /* file formats */
+
+  val file_formats: File_Format.Environment = File_Format.environment()
+
+  def make_theory_name(name: Document.Node.Name): Option[Document.Node.Name] =
+    file_formats.get(name).flatMap(_.make_theory_name(resources, name))
+
+  def make_theory_content(thy_name: Document.Node.Name): Option[String] =
+    file_formats.get_theory(thy_name).flatMap(_.make_theory_content(resources, thy_name))
+
+  def make_preview(snapshot: Document.Snapshot): Option[Present.Preview] =
+    file_formats.get(snapshot.node_name).flatMap(_.make_preview(snapshot))
+
+  def is_hidden(name: Document.Node.Name): Boolean =
+    !name.is_theory || name.theory == Sessions.root_name || file_formats.is_theory(name)
+
   def thy_path(path: Path): Path = path.ext("thy")
 
 
--- a/src/Pure/Thy/bibtex.scala	Wed Nov 07 14:06:43 2018 +0100
+++ b/src/Pure/Thy/bibtex.scala	Wed Nov 07 21:42:16 2018 +0100
@@ -16,6 +16,54 @@
 
 object Bibtex
 {
+  /** file format **/
+
+  def is_bibtex(name: String): Boolean = name.endsWith(".bib")
+
+  class File_Format extends isabelle.File_Format
+  {
+    val format_name: String = "bibtex"
+    val node_suffix: String = "bibtex_file"
+
+    def detect(name: String): Boolean = is_bibtex(name)
+
+    override def make_theory_name(
+      resources: Resources, name: Document.Node.Name): Option[Document.Node.Name] =
+    {
+      for { (_, ext_name) <- Thy_Header.split_file_name(name.node) if detect(ext_name) }
+      yield {
+        val thy_node = resources.append(name.node, Path.explode(node_suffix))
+        Document.Node.Name(thy_node, name.master_dir, ext_name)
+      }
+    }
+
+    override def make_theory_content(
+      resources: Resources, thy_name: Document.Node.Name): Option[String] =
+    {
+      for {
+        (prefix, suffix) <- Thy_Header.split_file_name(thy_name.node) if suffix == node_suffix
+        (_, ext_name) <- Thy_Header.split_file_name(prefix) if detect(ext_name)
+      } yield """theory "bib" imports Pure begin bibtex_file """ + quote(ext_name) + """ end"""
+    }
+
+    override def make_preview(snapshot: Document.Snapshot): Option[Present.Preview] =
+    {
+      val name = snapshot.node_name
+      if (detect(name.node)) {
+        val title = "Bibliography " + quote(snapshot.node_name.path.base_name)
+        val content =
+          Isabelle_System.with_tmp_file("bib", "bib") { bib =>
+            File.write(bib, snapshot.node.source)
+            Bibtex.html_output(List(bib), style = "unsort", title = title)
+          }
+        Some(Present.Preview(title, content))
+      }
+      else None
+    }
+  }
+
+
+
   /** bibtex errors **/
 
   def bibtex_errors(dir: Path, root_name: String): List[String] =
@@ -122,37 +170,6 @@
 
   /** document model **/
 
-  /* bibtex files */
-
-  def is_bibtex(name: String): Boolean = name.endsWith(".bib")
-
-  private val node_suffix: String = "bibtex_file"
-
-  def make_theory_name(resources: Resources, name: Document.Node.Name): Option[Document.Node.Name] =
-  {
-    Thy_Header.file_name(name.node) match {
-      case Some(bib_name) if is_bibtex(bib_name) =>
-        val thy_node = resources.append(name.node, Path.explode(node_suffix))
-        Some(Document.Node.Name(thy_node, name.master_dir, bib_name))
-      case _ => None
-    }
-  }
-
-  def make_theory_content(bib_name: String): Option[String] =
-    if (is_bibtex(bib_name)) {
-      Some("""theory "bib" imports Pure begin bibtex_file """ + quote(bib_name) + """ end""")
-    }
-    else None
-
-  def make_theory_content(file: JFile): Option[String] =
-    if (file.getName == node_suffix) {
-      val parent = file.getParentFile
-      if (parent != null && is_bibtex(parent.getName)) make_theory_content(parent.getName)
-      else None
-    }
-    else None
-
-
   /* entries */
 
   def entries(text: String): List[Text.Info[String]] =
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Pure/Thy/file_format.scala	Wed Nov 07 21:42:16 2018 +0100
@@ -0,0 +1,56 @@
+/*  Title:      Pure/Thy/file_format.scala
+    Author:     Makarius
+
+Support for user-defined file formats.
+*/
+
+package isabelle
+
+
+object File_Format
+{
+  def environment(): Environment =
+  {
+    val file_formats =
+      for (name <- space_explode(':', Isabelle_System.getenv_strict("ISABELLE_CLASSES_FILE_FORMAT")))
+      yield {
+        def err(msg: String): Nothing =
+          error("Bad entry " + quote(name) + " in ISABELLE_CLASSES_FILE_FORMAT\n" + msg)
+
+        try { Class.forName(name).asInstanceOf[Class[File_Format]].newInstance() }
+        catch {
+          case _: ClassNotFoundException => err("Class not found")
+          case exn: Throwable => err(Exn.message(exn))
+        }
+      }
+    new Environment(file_formats)
+  }
+
+  final class Environment private [File_Format](file_formats: List[File_Format])
+  {
+    override def toString: String = file_formats.mkString("File_Format.Environment(", ",", ")")
+
+    def get(name: String): Option[File_Format] = file_formats.find(_.detect(name))
+    def get(name: Document.Node.Name): Option[File_Format] = get(name.node)
+    def get_theory(name: Document.Node.Name): Option[File_Format] = get(name.theory)
+    def is_theory(name: Document.Node.Name): Boolean = get_theory(name).isDefined
+  }
+
+  sealed case class Theory_Context(name: Document.Node.Name, content: String)
+}
+
+trait File_Format
+{
+  def format_name: String
+  override def toString = format_name
+
+  def detect(name: String): Boolean
+
+  def make_theory_name(
+    resources: Resources, name: Document.Node.Name): Option[Document.Node.Name] = None
+
+  def make_theory_content(
+    resources: Resources, thy_name: Document.Node.Name): Option[String] = None
+
+  def make_preview(snapshot: Document.Snapshot): Option[Present.Preview] = None
+}
--- a/src/Pure/Thy/present.scala	Wed Nov 07 14:06:43 2018 +0100
+++ b/src/Pure/Thy/present.scala	Wed Nov 07 21:42:16 2018 +0100
@@ -105,7 +105,9 @@
 
   sealed case class Preview(title: String, content: String)
 
-  def preview(snapshot: Document.Snapshot,
+  def preview(
+    resources: Resources,
+    snapshot: Document.Snapshot,
     plain_text: Boolean = false,
     fonts_url: String => String = HTML.fonts_url()): Preview =
   {
@@ -119,26 +121,22 @@
         List(HTML.source(body)), css = "", structural = false)
 
     val name = snapshot.node_name
+
     if (plain_text) {
       val title = "File " + quote(name.path.base_name)
       val content = output_document(title, HTML.text(snapshot.node.source))
       Preview(title, content)
     }
-    else if (name.is_bibtex) {
-      val title = "Bibliography " + quote(name.path.base_name)
-      val content =
-        Isabelle_System.with_tmp_file("bib", "bib") { bib =>
-          File.write(bib, snapshot.node.source)
-          Bibtex.html_output(List(bib), style = "unsort", title = title)
-        }
-      Preview(title, content)
-    }
     else {
-      val title =
-        if (name.is_theory) "Theory " + quote(name.theory_base_name)
-        else "File " + quote(name.path.base_name)
-      val content = output_document(title, pide_document(snapshot))
-      Preview(title, content)
+      resources.make_preview(snapshot) match {
+        case Some(preview) => preview
+        case None =>
+          val title =
+            if (name.is_theory) "Theory " + quote(name.theory_base_name)
+            else "File " + quote(name.path.base_name)
+          val content = output_document(title, pide_document(snapshot))
+          Preview(title, content)
+      }
     }
   }
 
--- a/src/Pure/Thy/sessions.scala	Wed Nov 07 14:06:43 2018 +0100
+++ b/src/Pure/Thy/sessions.scala	Wed Nov 07 21:42:16 2018 +0100
@@ -26,9 +26,6 @@
 
   def is_pure(name: String): Boolean = name == Thy_Header.PURE
 
-  def is_hidden(name: Document.Node.Name): Boolean =
-    !name.is_theory || name.theory == Sessions.root_name || name.is_bibtex_theory
-
 
   def exclude_session(name: String): Boolean = name == "" || name == DRAFT
 
--- a/src/Pure/Thy/thy_header.scala	Wed Nov 07 14:06:43 2018 +0100
+++ b/src/Pure/Thy/thy_header.scala	Wed Nov 07 21:42:16 2018 +0100
@@ -80,14 +80,15 @@
     (Sessions.root_name :: (ml_roots ::: bootstrap_thys).map(_._2)).map(_ -> PURE)
 
   private val Thy_File_Name = new Regex(""".*?([^/\\:]+)\.thy""")
+  private val Split_File_Name = new Regex("""(.*?)[/\\]*([^/\\:]+)""")
   private val File_Name = new Regex(""".*?([^/\\:]+)""")
 
   def is_base_name(s: String): Boolean =
     s != "" && !s.exists("/\\:".contains(_))
 
-  def file_name(s: String): Option[String] =
+  def split_file_name(s: String): Option[(String, String)] =
     s match {
-      case File_Name(s) => Some(s)
+      case Split_File_Name(s1, s2) => Some((s1, s2))
       case _ => None
     }
 
--- a/src/Pure/build-jars	Wed Nov 07 14:06:43 2018 +0100
+++ b/src/Pure/build-jars	Wed Nov 07 21:42:16 2018 +0100
@@ -131,6 +131,7 @@
   Thy/bibtex.scala
   Thy/export.scala
   Thy/export_theory.scala
+  Thy/file_format.scala
   Thy/html.scala
   Thy/latex.scala
   Thy/present.scala
--- a/src/Tools/VSCode/src/document_model.scala	Wed Nov 07 14:06:43 2018 +0100
+++ b/src/Tools/VSCode/src/document_model.scala	Wed Nov 07 21:42:16 2018 +0100
@@ -59,7 +59,7 @@
 
   def init(session: Session, editor: Server.Editor, node_name: Document.Node.Name): Document_Model =
     Document_Model(session, editor, node_name, Content.empty,
-      node_required = node_name.is_bibtex_theory)
+      node_required = session.resources.file_formats.is_theory(node_name))
 }
 
 sealed case class Document_Model(
--- a/src/Tools/VSCode/src/preview_panel.scala	Wed Nov 07 14:06:43 2018 +0100
+++ b/src/Tools/VSCode/src/preview_panel.scala	Wed Nov 07 21:42:16 2018 +0100
@@ -30,7 +30,7 @@
               val snapshot = model.snapshot()
               if (snapshot.is_outdated) m
               else {
-                val preview = Present.preview(snapshot)
+                val preview = Present.preview(resources, snapshot)
                 channel.write(
                   Protocol.Preview_Response(file, column, preview.title, preview.content))
                 m - file
--- a/src/Tools/VSCode/src/vscode_resources.scala	Wed Nov 07 14:06:43 2018 +0100
+++ b/src/Tools/VSCode/src/vscode_resources.scala	Wed Nov 07 21:42:16 2018 +0100
@@ -123,10 +123,8 @@
 
   def read_file_content(file: JFile): Option[String] =
   {
-    Bibtex.make_theory_content(file) orElse {
-      try { Some(Line.normalize(File.read(file))) }
-      catch { case ERROR(_) => None }
-    }
+    try { Some(Line.normalize(File.read(file))) }
+    catch { case ERROR(_) => None }
   }
 
   def get_file_content(file: JFile): Option[String] =
@@ -226,8 +224,8 @@
 
         val thy_files2 =
           (for {
-            (_, model) <- st.models.iterator if model.node_name.is_bibtex
-            thy_name <- Bibtex.make_theory_name(resources, model.node_name)
+            (_, model) <- st.models.iterator
+            thy_name <- resources.make_theory_name(model.node_name)
           } yield thy_name).toList
 
 
--- a/src/Tools/jEdit/src/document_model.scala	Wed Nov 07 14:06:43 2018 +0100
+++ b/src/Tools/jEdit/src/document_model.scala	Wed Nov 07 21:42:16 2018 +0100
@@ -320,7 +320,7 @@
         yield {
           val snapshot = model.await_stable_snapshot()
           val preview =
-            Present.preview(snapshot, fonts_url = HTML.fonts_dir(fonts_root),
+            Present.preview(PIDE.resources, snapshot, fonts_url = HTML.fonts_dir(fonts_root),
               plain_text = query.startsWith(plain_text_prefix))
           HTTP.Response.html(preview.content)
         })
@@ -389,7 +389,7 @@
     file.foreach(PIDE.plugin.file_watcher.register_parent(_))
 
     val content = Document_Model.File_Content(text)
-    val node_required1 = node_required || node_name.is_bibtex_theory
+    val node_required1 = node_required || session.resources.file_formats.is_theory(node_name)
     File_Model(session, node_name, file, content, node_required1, last_perspective, pending_edits)
   }
 }
@@ -427,7 +427,7 @@
     else Some(Document.Blob(content.bytes, content.text, content.chunk, pending_edits.nonEmpty))
 
   def bibtex_entries: List[Text.Info[String]] =
-    if (is_bibtex) content.bibtex_entries else Nil
+    if (Bibtex.is_bibtex(node_name.node)) content.bibtex_entries else Nil
 
 
   /* edits */
@@ -454,7 +454,7 @@
 
   def purge_edits(doc_blobs: Document.Blobs): Option[List[Document.Edit_Text]] =
     if (pending_edits.nonEmpty ||
-        !is_bibtex_theory &&
+        !session.resources.file_formats.is_theory(node_name) &&
           (node_required || !Document.Node.is_no_perspective_text(last_perspective))) None
     else {
       val text_edits = List(Text.Edit.remove(0, content.text))
@@ -551,7 +551,7 @@
 
   def bibtex_entries: List[Text.Info[String]] =
     GUI_Thread.require {
-      if (is_bibtex) {
+      if (Bibtex.is_bibtex(node_name.node)) {
         _bibtex_entries match {
           case Some(entries) => entries
           case None =>
--- a/src/Tools/jEdit/src/jedit_resources.scala	Wed Nov 07 14:06:43 2018 +0100
+++ b/src/Tools/jEdit/src/jedit_resources.scala	Wed Nov 07 21:42:16 2018 +0100
@@ -80,7 +80,7 @@
 
   def read_file_content(node_name: Document.Node.Name): Option[String] =
   {
-    Bibtex.make_theory_content(node_name.theory) orElse {
+    make_theory_content(node_name) orElse {
       val name = node_name.node
       try {
         val text =
--- a/src/Tools/jEdit/src/plugin.scala	Wed Nov 07 14:06:43 2018 +0100
+++ b/src/Tools/jEdit/src/plugin.scala	Wed Nov 07 21:42:16 2018 +0100
@@ -131,8 +131,8 @@
 
           val thy_files2 =
             (for {
-              (name, _) <- models.iterator if name.is_bibtex
-              thy_name <- Bibtex.make_theory_name(resources, name)
+              (name, _) <- models.iterator
+              thy_name <- resources.make_theory_name(name)
             } yield thy_name).toList
 
           val aux_files =
--- a/src/Tools/jEdit/src/theories_dockable.scala	Wed Nov 07 14:06:43 2018 +0100
+++ b/src/Tools/jEdit/src/theories_dockable.scala	Wed Nov 07 21:42:16 2018 +0100
@@ -214,12 +214,11 @@
   {
     GUI_Thread.require {}
 
-    val session_base = PIDE.resources.session_base
     val snapshot = PIDE.session.snapshot()
 
     val (nodes_status_changed, nodes_status1) =
       nodes_status.update(
-        session_base, snapshot.state, snapshot.version, domain = domain, trim = trim)
+        PIDE.resources, snapshot.state, snapshot.version, domain = domain, trim = trim)
 
     nodes_status = nodes_status1
     if (nodes_status_changed) status.listData = nodes_status1.present.map(_._1)