merged
authorwenzelm
Mon, 19 Jun 2017 21:33:29 +0200
changeset 66122 ea7c2a245b84
parent 66113 571b698659c0 (current diff)
parent 66121 070f2be51330 (diff)
child 66123 6e4904863d2a
child 66137 d2923067b376
merged
src/Tools/jEdit/src/bibtex_jedit.scala
--- a/src/Pure/PIDE/document.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Pure/PIDE/document.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -498,6 +498,8 @@
     def is_theory: Boolean = node_name.is_theory
     override def toString: String = node_name.toString
 
+    def try_get_text(range: Text.Range): Option[String]
+
     def node_required: Boolean
     def get_blob: Option[Blob]
 
--- a/src/Pure/PIDE/rendering.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Pure/PIDE/rendering.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -209,6 +209,17 @@
 {
   override def toString: String = "Rendering(" + snapshot.toString + ")"
 
+  def model: Document.Model
+
+
+  /* caret */
+
+  def before_caret_range(caret: Text.Offset): Text.Range =
+  {
+    val former_caret = snapshot.revert(caret)
+    snapshot.convert(Text.Range(former_caret - 1, former_caret))
+  }
+
 
   /* completion */
 
@@ -231,13 +242,12 @@
     history: Completion.History,
     unicode: Boolean,
     completed_range: Option[Text.Range],
-    caret_range: Text.Range,
-    try_get_text: Text.Range => Option[String]): (Boolean, Option[Completion.Result]) =
+    caret_range: Text.Range): (Boolean, Option[Completion.Result]) =
   {
     semantic_completion(completed_range, caret_range) match {
       case Some(Text.Info(_, Completion.No_Completion)) => (true, None)
       case Some(Text.Info(range, names: Completion.Names)) =>
-        try_get_text(range) match {
+        model.try_get_text(range) match {
           case Some(original) => (false, names.complete(range, history, unicode, original))
           case None => (false, None)
         }
--- a/src/Pure/PIDE/text.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Pure/PIDE/text.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -73,6 +73,10 @@
       else Some(Range(this.start min that.start, this.stop max that.stop))
 
     def substring(text: String): String = text.substring(start, stop)
+
+    def try_substring(text: String): Option[String] =
+      try { Some(substring(text)) }
+      catch { case _: IndexOutOfBoundsException => None }
   }
 
 
--- a/src/Pure/Tools/bibtex.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Pure/Tools/bibtex.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -422,4 +422,31 @@
     }
     (chunks.toList, ctxt)
   }
+
+
+  /* completion */
+
+  def completion(history: Completion.History, rendering: Rendering, caret: Text.Offset,
+    complete: String => List[String]): Option[Completion.Result] =
+  {
+    for {
+      Text.Info(r, name) <- rendering.citation(rendering.before_caret_range(caret))
+      name1 <- Completion.clean_name(name)
+
+      original <- rendering.model.try_get_text(r)
+      original1 <- Completion.clean_name(Library.perhaps_unquote(original))
+
+      entries = complete(name1).filter(_ != original1)
+      if entries.nonEmpty
+
+      items =
+        entries.sorted.map({
+          case entry =>
+            val full_name = Long_Name.qualify(Markup.CITATION, entry)
+            val description = List(entry, "(BibTeX entry)")
+            val replacement = quote(entry)
+          Completion.Item(r, original, full_name, description, replacement, 0, false)
+        }).sorted(history.ordering).take(rendering.options.int("completion_limit"))
+    } yield Completion.Result(r, original, false, items)
+  }
 }
--- a/src/Pure/Tools/spell_checker.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Pure/Tools/spell_checker.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -52,6 +52,15 @@
     result.toList
   }
 
+  def current_word(rendering: Rendering, range: Text.Range): Option[Text.Info[String]] =
+  {
+    for {
+      spell_range <- rendering.spell_checker_point(range)
+      text <- rendering.model.try_get_text(spell_range)
+      info <- marked_words(spell_range.start, text, info => info.range.overlaps(range)).headOption
+    } yield info
+  }
+
 
   /* dictionaries */
 
@@ -210,7 +219,7 @@
     Spell_Checker.marked_words(base, text, info => !check(info.info))
 
 
-  /* suggestions for unknown words */
+  /* completion: suggestions for unknown words */
 
   private def suggestions(word: String): Option[List[String]] =
   {
@@ -240,6 +249,19 @@
     }
 
   def complete_enabled(word: String): Boolean = complete(word).nonEmpty
+
+  def completion(rendering: Rendering, caret: Text.Offset): Option[Completion.Result] =
+  {
+    val caret_range = rendering.before_caret_range(caret)
+    for {
+      word <- Spell_Checker.current_word(rendering, caret_range)
+      words = complete(word.info)
+      if words.nonEmpty
+      descr = "(from dictionary " + quote(dictionary.toString) + ")"
+      items =
+        words.map(w => Completion.Item(word.range, word.info, "", List(w, descr), w, 0, false))
+    } yield Completion.Result(word.range, word.info, false, items)
+  }
 }
 
 
--- a/src/Tools/VSCode/src/document_model.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Tools/VSCode/src/document_model.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -39,7 +39,6 @@
     def text_length: Text.Offset = doc.text_length
     def text_range: Text.Range = doc.text_range
     def text: String = doc.text
-    def try_get_text(range: Text.Range): Option[String] = doc.try_get_text(range)
 
     lazy val bytes: Bytes = Bytes(text)
     lazy val chunk: Symbol.Text_Chunk = Symbol.Text_Chunk(text)
@@ -64,6 +63,14 @@
   published_diagnostics: List[Text.Info[Command.Results]] = Nil,
   published_decorations: List[Document_Model.Decoration] = Nil) extends Document.Model
 {
+  model =>
+
+
+  /* text */
+
+  def try_get_text(range: Text.Range): Option[String] = content.doc.try_get_text(range)
+
+
   /* external file */
 
   def external(b: Boolean): Document_Model = copy(external_file = b)
@@ -84,7 +91,7 @@
     : (Boolean, Document.Node.Perspective_Text) =
   {
     if (is_theory) {
-      val snapshot = this.snapshot()
+      val snapshot = model.snapshot()
 
       val caret_perspective = resources.options.int("vscode_caret_perspective") max 0
       val caret_range =
@@ -192,7 +199,7 @@
   def snapshot(): Document.Snapshot = session.snapshot(node_name, pending_edits)
 
   def rendering(snapshot: Document.Snapshot): VSCode_Rendering =
-    new VSCode_Rendering(this, snapshot)
+    new VSCode_Rendering(snapshot, model)
   def rendering(): VSCode_Rendering = rendering(snapshot())
 
 
--- a/src/Tools/VSCode/src/vscode_rendering.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Tools/VSCode/src/vscode_rendering.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -65,20 +65,16 @@
     Markup.Elements(Markup.ENTITY, Markup.PATH, Markup.POSITION, Markup.CITATION)
 }
 
-class VSCode_Rendering(val model: Document_Model, snapshot: Document.Snapshot)
-  extends Rendering(snapshot, model.resources.options, model.session)
+class VSCode_Rendering(snapshot: Document.Snapshot, _model: Document_Model)
+  extends Rendering(snapshot, _model.resources.options, _model.session)
 {
   rendering =>
 
+  def model: Document_Model = _model
+
 
   /* completion */
 
-  def before_caret_range(caret: Text.Offset): Text.Range =
-  {
-    val former_caret = snapshot.revert(caret)
-    snapshot.convert(Text.Range(former_caret - 1, former_caret))
-  }
-
   def completion(caret_pos: Line.Position, caret: Text.Offset): List[Protocol.CompletionItem] =
   {
     val doc = model.content.doc
@@ -97,11 +93,15 @@
 
         val (no_completion, semantic_completion) =
           rendering.semantic_completion_result(
-            history, false, syntax_completion.map(_.range), caret_range, doc.try_get_text(_))
+            history, false, syntax_completion.map(_.range), caret_range)
 
         if (no_completion) Nil
         else {
-          Completion.Result.merge(history, semantic_completion, syntax_completion) match {
+          val results =
+            Completion.Result.merge(history,
+              Completion.Result.merge(history, semantic_completion, syntax_completion),
+              spell_checker_completion(caret))
+          results match {
             case None => Nil
             case Some(result) =>
               result.items.map(item =>
@@ -199,12 +199,15 @@
       (for {
         spell_checker <- model.resources.spell_checker.get.iterator
         spell_range <- spell_checker_ranges(model.content.text_range).iterator
-        text <- model.content.try_get_text(spell_range).iterator
+        text <- model.try_get_text(spell_range).iterator
         info <- spell_checker.marked_words(spell_range.start, text).iterator
       } yield info.range).toList
     Document_Model.Decoration.ranges("spell_checker", ranges)
   }
 
+  def spell_checker_completion(caret: Text.Offset): Option[Completion.Result] =
+    model.resources.spell_checker.get.flatMap(_.completion(rendering, caret))
+
 
   /* decorations */
 
--- a/src/Tools/jEdit/lib/Tools/jedit	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Tools/jEdit/lib/Tools/jedit	Mon Jun 19 21:33:29 2017 +0200
@@ -21,7 +21,6 @@
 
 declare -a SOURCES=(
   "src/active.scala"
-  "src/bibtex_jedit.scala"
   "src/completion_popup.scala"
   "src/context_menu.scala"
   "src/debugger_dockable.scala"
@@ -37,6 +36,7 @@
   "src/isabelle_encoding.scala"
   "src/isabelle_options.scala"
   "src/isabelle_sidekick.scala"
+  "src/jedit_bibtex.scala"
   "src/jedit_editor.scala"
   "src/jedit_lib.scala"
   "src/jedit_options.scala"
--- a/src/Tools/jEdit/src/bibtex_jedit.scala	Mon Jun 19 16:42:28 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,234 +0,0 @@
-/*  Title:      Tools/jEdit/src/bibtex_jedit.scala
-    Author:     Makarius
-
-BibTeX support in Isabelle/jEdit.
-*/
-
-package isabelle.jedit
-
-
-import isabelle._
-
-
-import scala.collection.mutable
-
-import java.awt.event.{ActionListener, ActionEvent}
-
-import javax.swing.text.Segment
-import javax.swing.tree.DefaultMutableTreeNode
-import javax.swing.{JMenu, JMenuItem}
-
-import org.gjt.sp.jedit.Buffer
-import org.gjt.sp.jedit.textarea.{JEditTextArea, TextArea}
-import org.gjt.sp.jedit.syntax.{Token => JEditToken, TokenMarker, TokenHandler, ParserRuleSet}
-
-import sidekick.{SideKickParser, SideKickParsedData}
-
-
-object Bibtex_JEdit
-{
-  /** completion **/
-
-  def complete(name: String): List[String] =
-    (for {
-      Text.Info(_, (entry, _)) <- Document_Model.bibtex_entries_iterator()
-      if entry.toLowerCase.containsSlice(name.toLowerCase)
-    } yield entry).toList
-
-  def completion(
-    history: Completion.History,
-    text_area: JEditTextArea,
-    rendering: JEdit_Rendering): Option[Completion.Result] =
-  {
-    for {
-      Text.Info(r, name) <- rendering.citation(JEdit_Lib.before_caret_range(text_area, rendering))
-      name1 <- Completion.clean_name(name)
-
-      original <- JEdit_Lib.try_get_text(text_area.getBuffer, r)
-      original1 <- Completion.clean_name(Library.perhaps_unquote(original))
-
-      entries = complete(name1).filter(_ != original1)
-      if entries.nonEmpty
-
-      items =
-        entries.sorted.map({
-          case entry =>
-            val full_name = Long_Name.qualify(Markup.CITATION, entry)
-            val description = List(entry, "(BibTeX entry)")
-            val replacement = quote(entry)
-          Completion.Item(r, original, full_name, description, replacement, 0, false)
-        }).sorted(history.ordering).take(PIDE.options.int("completion_limit"))
-    } yield Completion.Result(r, original, false, items)
-  }
-
-
-
-  /** context menu **/
-
-  def context_menu(text_area0: JEditTextArea): List[JMenuItem] =
-  {
-    text_area0 match {
-      case text_area: TextArea =>
-        text_area.getBuffer match {
-          case buffer: Buffer
-          if (Bibtex.check_name(JEdit_Lib.buffer_name(buffer)) && buffer.isEditable) =>
-            val menu = new JMenu("BibTeX entries")
-            for (entry <- Bibtex.entries) {
-              val item = new JMenuItem(entry.kind)
-              item.addActionListener(new ActionListener {
-                def actionPerformed(evt: ActionEvent): Unit =
-                  Isabelle.insert_line_padding(text_area, entry.template)
-              })
-              menu.add(item)
-            }
-            List(menu)
-          case _ => Nil
-        }
-      case _ => Nil
-    }
-  }
-
-
-
-  /** token markup **/
-
-  /* token style */
-
-  private def token_style(context: String, token: Bibtex.Token): Byte =
-    token.kind match {
-      case Bibtex.Token.Kind.COMMAND => JEditToken.KEYWORD2
-      case Bibtex.Token.Kind.ENTRY => JEditToken.KEYWORD1
-      case Bibtex.Token.Kind.KEYWORD => JEditToken.OPERATOR
-      case Bibtex.Token.Kind.NAT => JEditToken.LITERAL2
-      case Bibtex.Token.Kind.STRING => JEditToken.LITERAL1
-      case Bibtex.Token.Kind.NAME => JEditToken.LABEL
-      case Bibtex.Token.Kind.IDENT =>
-        if (Bibtex.is_month(token.source)) JEditToken.LITERAL3
-        else
-          Bibtex.get_entry(context) match {
-            case Some(entry) if entry.is_required(token.source) => JEditToken.KEYWORD3
-            case Some(entry) if entry.is_optional(token.source) => JEditToken.KEYWORD4
-            case _ => JEditToken.DIGIT
-          }
-      case Bibtex.Token.Kind.SPACE => JEditToken.NULL
-      case Bibtex.Token.Kind.COMMENT => JEditToken.COMMENT1
-      case Bibtex.Token.Kind.ERROR => JEditToken.INVALID
-    }
-
-
-  /* line context */
-
-  private val context_rules = new ParserRuleSet("bibtex", "MAIN")
-
-  private class Line_Context(val context: Option[Bibtex.Line_Context])
-    extends TokenMarker.LineContext(context_rules, null)
-  {
-    override def hashCode: Int = context.hashCode
-    override def equals(that: Any): Boolean =
-      that match {
-        case other: Line_Context => context == other.context
-        case _ => false
-      }
-  }
-
-
-  /* token marker */
-
-  class Token_Marker extends TokenMarker
-  {
-    override def markTokens(context: TokenMarker.LineContext,
-        handler: TokenHandler, raw_line: Segment): TokenMarker.LineContext =
-    {
-      val line_ctxt =
-        context match {
-          case c: Line_Context => c.context
-          case _ => Some(Bibtex.Ignored)
-        }
-      val line = if (raw_line == null) new Segment else raw_line
-
-      def no_markup =
-      {
-        val styled_token = (JEditToken.NULL, line.subSequence(0, line.count).toString)
-        (List(styled_token), new Line_Context(None))
-      }
-
-      val context1 =
-      {
-        val (styled_tokens, context1) =
-          line_ctxt match {
-            case Some(ctxt) =>
-              try {
-                val (chunks, ctxt1) = Bibtex.parse_line(line, ctxt)
-                val styled_tokens =
-                  for { chunk <- chunks; tok <- chunk.tokens }
-                  yield (token_style(chunk.kind, tok), tok.source)
-                (styled_tokens, new Line_Context(Some(ctxt1)))
-              }
-              catch { case ERROR(msg) => Output.warning(msg); no_markup }
-            case None => no_markup
-          }
-
-        var offset = 0
-        for ((style, token) <- styled_tokens) {
-          val length = token.length
-          val end_offset = offset + length
-
-          if ((offset until end_offset).exists(i => line.charAt(i) == '\t')) {
-            for (i <- offset until end_offset)
-              handler.handleToken(line, style, i, 1, context1)
-          }
-          else handler.handleToken(line, style, offset, length, context1)
-
-          offset += length
-        }
-        handler.handleToken(line, JEditToken.END, line.count, 0, context1)
-        context1
-      }
-      val context2 = context1.intern
-      handler.setLineContext(context2)
-      context2
-    }
-  }
-
-
-
-  /** Sidekick parser **/
-
-  class Sidekick_Parser extends SideKickParser("bibtex")
-  {
-    override def supportsCompletion = false
-
-    private class Asset(label: String, label_html: String, range: Text.Range, source: String)
-      extends Isabelle_Sidekick.Asset(label, range) {
-        override def getShortString: String = label_html
-        override def getLongString: String = source
-      }
-
-    def parse(buffer: Buffer, error_source: errorlist.DefaultErrorSource): SideKickParsedData =
-    {
-      val data = Isabelle_Sidekick.root_data(buffer)
-
-      try {
-        var offset = 0
-        for (chunk <- Bibtex.parse(JEdit_Lib.buffer_text(buffer))) {
-          val kind = chunk.kind
-          val name = chunk.name
-          val source = chunk.source
-          if (kind != "") {
-            val label = kind + (if (name == "") "" else " " + name)
-            val label_html =
-              "<html><b>" + HTML.output(kind) + "</b>" +
-              (if (name == "") "" else " " + HTML.output(name)) + "</html>"
-            val range = Text.Range(offset, offset + source.length)
-            val asset = new Asset(label, label_html, range, source)
-            data.root.add(new DefaultMutableTreeNode(asset))
-          }
-          offset += source.length
-        }
-        data
-      }
-      catch { case ERROR(msg) => Output.warning(msg); null }
-    }
-  }
-}
-
--- a/src/Tools/jEdit/src/completion_popup.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Tools/jEdit/src/completion_popup.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -125,15 +125,16 @@
       active_range match {
         case Some(range) => range.try_restrict(line_range)
         case None =>
-          if (line_range.contains(text_area.getCaretPosition)) {
-            JEdit_Lib.before_caret_range(text_area, rendering).try_restrict(line_range) match {
+          val caret = text_area.getCaretPosition
+          if (line_range.contains(caret)) {
+            rendering.before_caret_range(caret).try_restrict(line_range) match {
               case Some(range) if !range.is_singularity =>
                 val range0 =
                   Completion.Result.merge(Completion.History.empty,
                     syntax_completion(Completion.History.empty, true, Some(rendering)),
                     Completion.Result.merge(Completion.History.empty,
                       path_completion(rendering),
-                      Bibtex_JEdit.completion(Completion.History.empty, text_area, rendering)))
+                      JEdit_Bibtex.completion(Completion.History.empty, rendering, caret)))
                   .map(_.range)
                 rendering.semantic_completion(range0, range) match {
                   case None => range0
@@ -164,7 +165,7 @@
             (for {
               rendering <- opt_rendering
               if PIDE.options.bool("jedit_completion_context")
-              caret_range = JEdit_Lib.before_caret_range(text_area, rendering)
+              caret_range = rendering.before_caret_range(text_area.getCaretPosition)
               context <- rendering.language_context(caret_range)
             } yield context) getOrElse syntax.language_context
 
@@ -226,7 +227,7 @@
         s.startsWith(Symbol.open_decoded) && s.endsWith(Symbol.close_decoded)
 
       for {
-        r1 <- rendering.language_path(JEdit_Lib.before_caret_range(text_area, rendering))
+        r1 <- rendering.language_path(rendering.before_caret_range(text_area.getCaretPosition))
         s1 <- JEdit_Lib.try_get_text(text_area.getBuffer, r1)
         if is_wrapped(s1)
         r2 = Text.Range(r1.start + 1, r1.stop - 1)
@@ -349,6 +350,7 @@
       }
 
       if (buffer.isEditable) {
+        val caret = text_area.getCaretPosition
         val opt_rendering = Document_View.get(text_area).map(_.get_rendering())
         val result0 = syntax_completion(history, explicit, opt_rendering)
         val (no_completion, semantic_completion) =
@@ -356,8 +358,7 @@
           opt_rendering match {
             case Some(rendering) =>
               rendering.semantic_completion_result(history, unicode, result0.map(_.range),
-                JEdit_Lib.before_caret_range(text_area, rendering),
-                JEdit_Lib.try_get_text(buffer, _))
+                rendering.before_caret_range(caret))
             case None => (false, None)
           }
         }
@@ -373,10 +374,10 @@
               case Some(rendering) =>
                 Completion.Result.merge(history, result1,
                   Completion.Result.merge(history,
-                    JEdit_Spell_Checker.completion(text_area, explicit, rendering),
+                    JEdit_Spell_Checker.completion(rendering, explicit, caret),
                     Completion.Result.merge(history,
                       path_completion(rendering),
-                      Bibtex_JEdit.completion(history, text_area, rendering))))
+                      JEdit_Bibtex.completion(history, rendering, caret))))
             }
           }
           result match {
@@ -509,7 +510,7 @@
       val range = item.range
       if (text_field.isEditable) {
         val content = text_field.getText
-        JEdit_Lib.try_get_text(content, range) match {
+        range.try_substring(content) match {
           case Some(text) if text == item.original =>
             text_field.setText(
               content.substring(0, range.start) +
--- a/src/Tools/jEdit/src/context_menu.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Tools/jEdit/src/context_menu.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -35,7 +35,7 @@
         }
         else Nil
 
-      val items2 = Bibtex_JEdit.context_menu(text_area)
+      val items2 = JEdit_Bibtex.context_menu(text_area)
 
       val items = items1 ::: items2
       if (items.isEmpty) null else items.toArray
--- a/src/Tools/jEdit/src/document_model.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Tools/jEdit/src/document_model.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -372,6 +372,10 @@
 
 object File_Model
 {
+  def empty(session: Session): File_Model =
+    File_Model(session, Document.Node.Name.empty, None, Document_Model.File_Content(""),
+      false, Document.Node.no_perspective_text, Nil)
+
   def init(session: Session,
     node_name: Document.Node.Name,
     text: String,
@@ -396,6 +400,12 @@
   last_perspective: Document.Node.Perspective_Text,
   pending_edits: List[Text.Edit]) extends Document_Model
 {
+  /* text */
+
+  def try_get_text(range: Text.Range): Option[String] =
+    range.try_substring(content.text)
+
+
   /* header */
 
   def node_header: Document.Node.Header =
@@ -457,6 +467,12 @@
 case class Buffer_Model(session: Session, node_name: Document.Node.Name, buffer: Buffer)
   extends Document_Model
 {
+  /* text */
+
+  def try_get_text(range: Text.Range): Option[String] =
+    JEdit_Lib.try_get_text(buffer, range)
+
+
   /* header */
 
   def node_header(): Document.Node.Header =
--- a/src/Tools/jEdit/src/document_view.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Tools/jEdit/src/document_view.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -60,7 +60,8 @@
 {
   private val session = model.session
 
-  def get_rendering(): JEdit_Rendering = JEdit_Rendering(model.snapshot(), PIDE.options.value)
+  def get_rendering(): JEdit_Rendering =
+    JEdit_Rendering(model.snapshot(), model, PIDE.options.value)
 
   val rich_text_area =
     new Rich_Text_Area(text_area.getView, text_area, get_rendering _, () => (), () => None,
--- a/src/Tools/jEdit/src/graphview_dockable.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Tools/jEdit/src/graphview_dockable.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -68,7 +68,8 @@
           {
             Pretty_Tooltip.invoke(() =>
               {
-                val rendering = JEdit_Rendering(snapshot, options)
+                val model = File_Model.empty(PIDE.session)
+                val rendering = JEdit_Rendering(snapshot, model, options)
                 val info = Text.Info(Text.Range.offside, body)
                 Pretty_Tooltip(view, parent, new Point(x, y), rendering, Command.Results.empty, info)
               })
--- a/src/Tools/jEdit/src/isabelle.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Tools/jEdit/src/isabelle.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -74,7 +74,7 @@
 
   private val mode_markers: Map[String, TokenMarker] =
     Map(modes.map(mode => (mode, new Token_Markup.Marker(mode, None))): _*) +
-      ("bibtex" -> new Bibtex_JEdit.Token_Marker)
+      ("bibtex" -> new JEdit_Bibtex.Token_Marker)
 
   def mode_token_marker(mode: String): Option[TokenMarker] = mode_markers.get(mode)
 
@@ -419,7 +419,7 @@
       doc_view <- Document_View.get(text_area)
       rendering = doc_view.get_rendering()
       range = JEdit_Lib.caret_range(text_area)
-      Text.Info(_, word) <- JEdit_Spell_Checker.current_word(text_area, rendering, range)
+      Text.Info(_, word) <- Spell_Checker.current_word(rendering, range)
     } {
       spell_checker.update(word, include, permanent)
       JEdit_Lib.jedit_views().foreach(_.repaint())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Tools/jEdit/src/jedit_bibtex.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -0,0 +1,210 @@
+/*  Title:      Tools/jEdit/src/jedit_bibtex.scala
+    Author:     Makarius
+
+BibTeX support in Isabelle/jEdit.
+*/
+
+package isabelle.jedit
+
+
+import isabelle._
+
+
+import scala.collection.mutable
+
+import java.awt.event.{ActionListener, ActionEvent}
+
+import javax.swing.text.Segment
+import javax.swing.tree.DefaultMutableTreeNode
+import javax.swing.{JMenu, JMenuItem}
+
+import org.gjt.sp.jedit.Buffer
+import org.gjt.sp.jedit.textarea.{JEditTextArea, TextArea}
+import org.gjt.sp.jedit.syntax.{Token => JEditToken, TokenMarker, TokenHandler, ParserRuleSet}
+
+import sidekick.{SideKickParser, SideKickParsedData}
+
+
+object JEdit_Bibtex
+{
+  /** completion **/
+
+  def complete(name: String): List[String] =
+    (for {
+      Text.Info(_, (entry, _)) <- Document_Model.bibtex_entries_iterator()
+      if entry.toLowerCase.containsSlice(name.toLowerCase)
+    } yield entry).toList
+
+  def completion(history: Completion.History, rendering: Rendering, caret: Text.Offset)
+    : Option[Completion.Result] = Bibtex.completion(history, rendering, caret, complete _)
+
+
+
+  /** context menu **/
+
+  def context_menu(text_area0: JEditTextArea): List[JMenuItem] =
+  {
+    text_area0 match {
+      case text_area: TextArea =>
+        text_area.getBuffer match {
+          case buffer: Buffer
+          if (Bibtex.check_name(JEdit_Lib.buffer_name(buffer)) && buffer.isEditable) =>
+            val menu = new JMenu("BibTeX entries")
+            for (entry <- Bibtex.entries) {
+              val item = new JMenuItem(entry.kind)
+              item.addActionListener(new ActionListener {
+                def actionPerformed(evt: ActionEvent): Unit =
+                  Isabelle.insert_line_padding(text_area, entry.template)
+              })
+              menu.add(item)
+            }
+            List(menu)
+          case _ => Nil
+        }
+      case _ => Nil
+    }
+  }
+
+
+
+  /** token markup **/
+
+  /* token style */
+
+  private def token_style(context: String, token: Bibtex.Token): Byte =
+    token.kind match {
+      case Bibtex.Token.Kind.COMMAND => JEditToken.KEYWORD2
+      case Bibtex.Token.Kind.ENTRY => JEditToken.KEYWORD1
+      case Bibtex.Token.Kind.KEYWORD => JEditToken.OPERATOR
+      case Bibtex.Token.Kind.NAT => JEditToken.LITERAL2
+      case Bibtex.Token.Kind.STRING => JEditToken.LITERAL1
+      case Bibtex.Token.Kind.NAME => JEditToken.LABEL
+      case Bibtex.Token.Kind.IDENT =>
+        if (Bibtex.is_month(token.source)) JEditToken.LITERAL3
+        else
+          Bibtex.get_entry(context) match {
+            case Some(entry) if entry.is_required(token.source) => JEditToken.KEYWORD3
+            case Some(entry) if entry.is_optional(token.source) => JEditToken.KEYWORD4
+            case _ => JEditToken.DIGIT
+          }
+      case Bibtex.Token.Kind.SPACE => JEditToken.NULL
+      case Bibtex.Token.Kind.COMMENT => JEditToken.COMMENT1
+      case Bibtex.Token.Kind.ERROR => JEditToken.INVALID
+    }
+
+
+  /* line context */
+
+  private val context_rules = new ParserRuleSet("bibtex", "MAIN")
+
+  private class Line_Context(val context: Option[Bibtex.Line_Context])
+    extends TokenMarker.LineContext(context_rules, null)
+  {
+    override def hashCode: Int = context.hashCode
+    override def equals(that: Any): Boolean =
+      that match {
+        case other: Line_Context => context == other.context
+        case _ => false
+      }
+  }
+
+
+  /* token marker */
+
+  class Token_Marker extends TokenMarker
+  {
+    override def markTokens(context: TokenMarker.LineContext,
+        handler: TokenHandler, raw_line: Segment): TokenMarker.LineContext =
+    {
+      val line_ctxt =
+        context match {
+          case c: Line_Context => c.context
+          case _ => Some(Bibtex.Ignored)
+        }
+      val line = if (raw_line == null) new Segment else raw_line
+
+      def no_markup =
+      {
+        val styled_token = (JEditToken.NULL, line.subSequence(0, line.count).toString)
+        (List(styled_token), new Line_Context(None))
+      }
+
+      val context1 =
+      {
+        val (styled_tokens, context1) =
+          line_ctxt match {
+            case Some(ctxt) =>
+              try {
+                val (chunks, ctxt1) = Bibtex.parse_line(line, ctxt)
+                val styled_tokens =
+                  for { chunk <- chunks; tok <- chunk.tokens }
+                  yield (token_style(chunk.kind, tok), tok.source)
+                (styled_tokens, new Line_Context(Some(ctxt1)))
+              }
+              catch { case ERROR(msg) => Output.warning(msg); no_markup }
+            case None => no_markup
+          }
+
+        var offset = 0
+        for ((style, token) <- styled_tokens) {
+          val length = token.length
+          val end_offset = offset + length
+
+          if ((offset until end_offset).exists(i => line.charAt(i) == '\t')) {
+            for (i <- offset until end_offset)
+              handler.handleToken(line, style, i, 1, context1)
+          }
+          else handler.handleToken(line, style, offset, length, context1)
+
+          offset += length
+        }
+        handler.handleToken(line, JEditToken.END, line.count, 0, context1)
+        context1
+      }
+      val context2 = context1.intern
+      handler.setLineContext(context2)
+      context2
+    }
+  }
+
+
+
+  /** Sidekick parser **/
+
+  class Sidekick_Parser extends SideKickParser("bibtex")
+  {
+    override def supportsCompletion = false
+
+    private class Asset(label: String, label_html: String, range: Text.Range, source: String)
+      extends Isabelle_Sidekick.Asset(label, range) {
+        override def getShortString: String = label_html
+        override def getLongString: String = source
+      }
+
+    def parse(buffer: Buffer, error_source: errorlist.DefaultErrorSource): SideKickParsedData =
+    {
+      val data = Isabelle_Sidekick.root_data(buffer)
+
+      try {
+        var offset = 0
+        for (chunk <- Bibtex.parse(JEdit_Lib.buffer_text(buffer))) {
+          val kind = chunk.kind
+          val name = chunk.name
+          val source = chunk.source
+          if (kind != "") {
+            val label = kind + (if (name == "") "" else " " + name)
+            val label_html =
+              "<html><b>" + HTML.output(kind) + "</b>" +
+              (if (name == "") "" else " " + HTML.output(name)) + "</html>"
+            val range = Text.Range(offset, offset + source.length)
+            val asset = new Asset(label, label_html, range, source)
+            data.root.add(new DefaultMutableTreeNode(asset))
+          }
+          offset += source.length
+        }
+        data
+      }
+      catch { case ERROR(msg) => Output.warning(msg); null }
+    }
+  }
+}
--- a/src/Tools/jEdit/src/jedit_lib.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Tools/jEdit/src/jedit_lib.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -175,10 +175,6 @@
     try { Some(buffer.getText(range.start, range.length)) }
     catch { case _: ArrayIndexOutOfBoundsException => None }
 
-  def try_get_text(text: String, range: Text.Range): Option[String] =
-    try { Some(range.substring(text)) }
-    catch { case _: IndexOutOfBoundsException => None }
-
 
   /* point range */
 
@@ -211,13 +207,6 @@
   def caret_range(text_area: TextArea): Text.Range =
     point_range(text_area.getBuffer, text_area.getCaretPosition)
 
-  def before_caret_range(text_area: TextArea, rendering: JEdit_Rendering): Text.Range =
-  {
-    val snapshot = rendering.snapshot
-    val former_caret = snapshot.revert(text_area.getCaretPosition)
-    snapshot.convert(Text.Range(former_caret - 1, former_caret))
-  }
-
   def visible_range(text_area: TextArea): Option[Text.Range] =
   {
     val buffer = text_area.getBuffer
--- a/src/Tools/jEdit/src/jedit_rendering.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Tools/jEdit/src/jedit_rendering.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -21,8 +21,8 @@
 
 object JEdit_Rendering
 {
-  def apply(snapshot: Document.Snapshot, options: Options): JEdit_Rendering =
-    new JEdit_Rendering(snapshot, options)
+  def apply(snapshot: Document.Snapshot, model: Document_Model, options: Options): JEdit_Rendering =
+    new JEdit_Rendering(snapshot, model, options)
 
 
   /* popup window bounds */
@@ -148,9 +148,12 @@
 }
 
 
-class JEdit_Rendering(snapshot: Document.Snapshot, options: Options)
+class JEdit_Rendering(snapshot: Document.Snapshot, _model: Document_Model, options: Options)
   extends Rendering(snapshot, options, PIDE.session)
 {
+  def model: Document_Model = _model
+
+
   /* colors */
 
   def color(s: String): Color =
--- a/src/Tools/jEdit/src/jedit_spell_checker.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Tools/jEdit/src/jedit_spell_checker.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -19,36 +19,16 @@
 
 object JEdit_Spell_Checker
 {
-  /* words within text */
-
-  def current_word(text_area: TextArea, rendering: JEdit_Rendering, range: Text.Range)
-    : Option[Text.Info[String]] =
-  {
-    for {
-      spell_range <- rendering.spell_checker_point(range)
-      text <- JEdit_Lib.try_get_text(text_area.getBuffer, spell_range)
-      info <- Spell_Checker.marked_words(
-        spell_range.start, text, info => info.range.overlaps(range)).headOption
-    } yield info
-  }
-
-
   /* completion */
 
-  def completion(text_area: TextArea, explicit: Boolean, rendering: JEdit_Rendering)
-      : Option[Completion.Result] =
+  def completion(
+    rendering: JEdit_Rendering, explicit: Boolean, caret: Text.Offset): Option[Completion.Result] =
   {
-    for {
-      spell_checker <- PIDE.plugin.spell_checker.get
-      if explicit
-      range = JEdit_Lib.before_caret_range(text_area, rendering)
-      word <- current_word(text_area, rendering, range)
-      words = spell_checker.complete(word.info)
-      if words.nonEmpty
-      descr = "(from dictionary " + quote(spell_checker.toString) + ")"
-      items =
-        words.map(w => Completion.Item(word.range, word.info, "", List(w, descr), w, 0, false))
-    } yield Completion.Result(word.range, word.info, false, items)
+    PIDE.plugin.spell_checker.get match {
+      case Some(spell_checker) if explicit =>
+        spell_checker.completion(rendering, caret)
+      case _ => None
+    }
   }
 
 
@@ -62,7 +42,7 @@
         doc_view <- Document_View.get(text_area)
         rendering = doc_view.get_rendering()
         range = JEdit_Lib.point_range(text_area.getBuffer, offset)
-        Text.Info(_, word) <- current_word(text_area, rendering, range)
+        Text.Info(_, word) <- Spell_Checker.current_word(rendering, range)
       } yield (spell_checker, word)
 
     result match {
--- a/src/Tools/jEdit/src/pretty_text_area.scala	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Tools/jEdit/src/pretty_text_area.scala	Mon Jun 19 21:33:29 2017 +0200
@@ -57,7 +57,8 @@
     formatted_body: XML.Body): (String, JEdit_Rendering) =
   {
     val (text, state) = document_state(base_snapshot, base_results, formatted_body)
-    val rendering = JEdit_Rendering(state.snapshot(), PIDE.options.value)
+    val model = File_Model.empty(PIDE.session)
+    val rendering = JEdit_Rendering(state.snapshot(), model, PIDE.options.value)
     (text, rendering)
   }
 }
--- a/src/Tools/jEdit/src/services.xml	Mon Jun 19 16:42:28 2017 +0100
+++ b/src/Tools/jEdit/src/services.xml	Mon Jun 19 21:33:29 2017 +0200
@@ -39,7 +39,7 @@
     new isabelle.jedit.Isabelle_Sidekick_Root();
   </SERVICE>
   <SERVICE NAME="bibtex" CLASS="sidekick.SideKickParser">
-    new isabelle.jedit.Bibtex_JEdit.Sidekick_Parser();
+    new isabelle.jedit.JEdit_Bibtex.Sidekick_Parser();
   </SERVICE>
   <SERVICE CLASS="console.Shell" NAME="Scala">
     new isabelle.jedit.Scala_Console();