completion of keywords and symbols based on language context;
authorwenzelm
Thu, 20 Feb 2014 12:53:12 +0100
changeset 56957bf4bbe72f740
parent 56956 e2d71b8b0d95
child 56958 25a7a998852a
completion of keywords and symbols based on language context;
src/Pure/Isar/completion.scala
src/Pure/PIDE/markup.ML
src/Pure/PIDE/markup.scala
src/Tools/jEdit/src/completion_popup.scala
src/Tools/jEdit/src/rendering.scala
     1.1 --- a/src/Pure/Isar/completion.scala	Wed Feb 19 21:38:44 2014 +0100
     1.2 +++ b/src/Pure/Isar/completion.scala	Thu Feb 20 12:53:12 2014 +0100
     1.3 @@ -13,6 +13,19 @@
     1.4  
     1.5  object Completion
     1.6  {
     1.7 +  /* context */
     1.8 +
     1.9 +  object Context
    1.10 +  {
    1.11 +    val default = Context("", true)
    1.12 +  }
    1.13 +
    1.14 +  sealed case class Context(language: String, symbols: Boolean)
    1.15 +  {
    1.16 +    def is_outer: Boolean = language == ""
    1.17 +  }
    1.18 +
    1.19 +
    1.20    /* result */
    1.21  
    1.22    sealed case class Item(
    1.23 @@ -113,20 +126,22 @@
    1.24    }
    1.25  
    1.26  
    1.27 -  /** word completion **/
    1.28 +  /** word parsers **/
    1.29  
    1.30 -  private val word_regex = "[a-zA-Z0-9_']+".r
    1.31 -  private def is_word(s: CharSequence): Boolean = word_regex.pattern.matcher(s).matches
    1.32 -
    1.33 -  private object Parse extends RegexParsers
    1.34 +  private object Word_Parsers extends RegexParsers
    1.35    {
    1.36      override val whiteSpace = "".r
    1.37  
    1.38 -    def reverse_symbol: Parser[String] = """>[A-Za-z0-9_']+\^?<\\""".r
    1.39 -    def reverse_symb: Parser[String] = """[A-Za-z0-9_']{2,}\^?<\\""".r
    1.40 -    def escape: Parser[String] = """[a-zA-Z0-9_']+\\""".r
    1.41 -    def word: Parser[String] = word_regex
    1.42 -    def word3: Parser[String] = """[a-zA-Z0-9_']{3,}""".r
    1.43 +    private def reverse_symbol: Parser[String] = """>[A-Za-z0-9_']+\^?<\\""".r
    1.44 +    private def reverse_symb: Parser[String] = """[A-Za-z0-9_']{2,}\^?<\\""".r
    1.45 +    private def escape: Parser[String] = """[a-zA-Z0-9_']+\\""".r
    1.46 +
    1.47 +    private val word_regex = "[a-zA-Z0-9_']+".r
    1.48 +    private def word: Parser[String] = word_regex
    1.49 +    private def word3: Parser[String] = """[a-zA-Z0-9_']{3,}""".r
    1.50 +
    1.51 +    def is_word(s: CharSequence): Boolean =
    1.52 +      word_regex.pattern.matcher(s).matches
    1.53  
    1.54      def read(explicit: Boolean, in: CharSequence): Option[String] =
    1.55      {
    1.56 @@ -141,6 +156,7 @@
    1.57  }
    1.58  
    1.59  final class Completion private(
    1.60 +  keywords: Set[String] = Set.empty,
    1.61    words_lex: Scan.Lexicon = Scan.Lexicon.empty,
    1.62    words_map: Multi_Map[String, String] = Multi_Map.empty,
    1.63    abbrevs_lex: Scan.Lexicon = Scan.Lexicon.empty,
    1.64 @@ -150,6 +166,7 @@
    1.65  
    1.66    def + (keyword: String, replace: String): Completion =
    1.67      new Completion(
    1.68 +      keywords + keyword,
    1.69        words_lex + keyword,
    1.70        words_map + (keyword -> replace),
    1.71        abbrevs_lex,
    1.72 @@ -160,15 +177,16 @@
    1.73    private def add_symbols(): Completion =
    1.74    {
    1.75      val words =
    1.76 -      (for ((x, _) <- Symbol.names) yield (x, x)).toList :::
    1.77 -      (for ((x, y) <- Symbol.names) yield ("\\" + y, x)).toList :::
    1.78 -      (for ((x, y) <- Symbol.abbrevs.iterator if Completion.is_word(y)) yield (y, x)).toList
    1.79 +      (for ((x, _) <- Symbol.names.toList) yield (x, x)) :::
    1.80 +      (for ((x, y) <- Symbol.names.toList) yield ("\\" + y, x)) :::
    1.81 +      (for ((x, y) <- Symbol.abbrevs.toList if Completion.Word_Parsers.is_word(y)) yield (y, x))
    1.82  
    1.83      val abbrs =
    1.84 -      (for ((x, y) <- Symbol.abbrevs.iterator if !Completion.is_word(y))
    1.85 -        yield (y.reverse.toString, (y, x))).toList
    1.86 +      (for ((x, y) <- Symbol.abbrevs.iterator if !Completion.Word_Parsers.is_word(y))
    1.87 +        yield (y.reverse, (y, x))).toList
    1.88  
    1.89      new Completion(
    1.90 +      keywords,
    1.91        words_lex ++ words.map(_._1),
    1.92        words_map ++ words,
    1.93        abbrevs_lex ++ abbrs.map(_._1),
    1.94 @@ -182,34 +200,45 @@
    1.95      history: Completion.History,
    1.96      decode: Boolean,
    1.97      explicit: Boolean,
    1.98 -    line: CharSequence): Option[Completion.Result] =
    1.99 +    text: CharSequence,
   1.100 +    context: Completion.Context): Option[Completion.Result] =
   1.101    {
   1.102 -    val raw_result =
   1.103 -      Scan.Parsers.parse(Scan.Parsers.literal(abbrevs_lex), new Library.Reverse(line)) match {
   1.104 -        case Scan.Parsers.Success(reverse_a, _) =>
   1.105 -          val abbrevs = abbrevs_map.get_list(reverse_a)
   1.106 -          abbrevs match {
   1.107 -            case Nil => None
   1.108 -            case (a, _) :: _ => Some((a, abbrevs.map(_._2)))
   1.109 -          }
   1.110 -        case _ =>
   1.111 -          Completion.Parse.read(explicit, line) match {
   1.112 -            case Some(word) =>
   1.113 -              words_lex.completions(word).map(words_map.get_list(_)).flatten match {
   1.114 -                case Nil => None
   1.115 -                case cs => Some(word, cs)
   1.116 -              }
   1.117 -            case None => None
   1.118 -          }
   1.119 +    val abbrevs_result =
   1.120 +      if (context.symbols)
   1.121 +        Scan.Parsers.parse(Scan.Parsers.literal(abbrevs_lex), new Library.Reverse(text)) match {
   1.122 +          case Scan.Parsers.Success(reverse_a, _) =>
   1.123 +            val abbrevs = abbrevs_map.get_list(reverse_a)
   1.124 +            abbrevs match {
   1.125 +              case Nil => None
   1.126 +              case (a, _) :: _ => Some((a, abbrevs.map(_._2)))
   1.127 +            }
   1.128 +          case _ => None
   1.129 +        }
   1.130 +      else None
   1.131 +
   1.132 +    val words_result =
   1.133 +      abbrevs_result orElse {
   1.134 +        Completion.Word_Parsers.read(explicit, text) match {
   1.135 +          case Some(word) =>
   1.136 +            val completions =
   1.137 +              for {
   1.138 +                s <- words_lex.completions(word)
   1.139 +                if (if (keywords(s)) context.is_outer else context.symbols)
   1.140 +                r <- words_map.get_list(s)
   1.141 +              } yield r
   1.142 +            if (completions.isEmpty) None
   1.143 +            else Some(word, completions)
   1.144 +          case None => None
   1.145 +        }
   1.146        }
   1.147 -    raw_result match {
   1.148 +
   1.149 +    words_result match {
   1.150        case Some((word, cs)) =>
   1.151 -        val ds =
   1.152 -          (if (decode) cs.map(Symbol.decode(_)) else cs).filter(_ != word)
   1.153 +        val ds = (if (decode) cs.map(Symbol.decode(_)) else cs).filter(_ != word)
   1.154          if (ds.isEmpty) None
   1.155          else {
   1.156            val immediate =
   1.157 -            !Completion.is_word(word) &&
   1.158 +            !Completion.Word_Parsers.is_word(word) &&
   1.159              Character.codePointCount(word, 0, word.length) > 1
   1.160            val items = ds.map(s => Completion.Item(word, s, s, explicit || immediate))
   1.161            Some(Completion.Result(word, cs.length == 1, items.sorted(history.ordering)))
     2.1 --- a/src/Pure/PIDE/markup.ML	Wed Feb 19 21:38:44 2014 +0100
     2.2 +++ b/src/Pure/PIDE/markup.ML	Thu Feb 20 12:53:12 2014 +0100
     2.3 @@ -20,7 +20,8 @@
     2.4    val name: string -> T -> T
     2.5    val kindN: string
     2.6    val instanceN: string
     2.7 -  val languageN: string val language: string -> T
     2.8 +  val symbolsN: string
     2.9 +  val languageN: string val language: string -> bool -> T
    2.10    val language_sort: T
    2.11    val language_type: T
    2.12    val language_term: T
    2.13 @@ -246,17 +247,19 @@
    2.14  
    2.15  (* embedded languages *)
    2.16  
    2.17 -val (languageN, language) = markup_string "language" nameN;
    2.18 +val symbolsN = "symbols";
    2.19 +val languageN = "language";
    2.20 +fun language name symbols = (languageN, [(nameN, name), (symbolsN, print_bool symbols)]);
    2.21  
    2.22 -val language_sort = language "sort";
    2.23 -val language_type = language "type";
    2.24 -val language_term = language "term";
    2.25 -val language_prop = language "prop";
    2.26 +val language_sort = language "sort" true;
    2.27 +val language_type = language "type" true;
    2.28 +val language_term = language "term" true;
    2.29 +val language_prop = language "prop" true;
    2.30  
    2.31 -val language_ML = language "ML";
    2.32 -val language_document = language "document";
    2.33 -val language_text = language "text";
    2.34 -val language_rail = language "rail";
    2.35 +val language_ML = language "ML" false;
    2.36 +val language_document = language "document" false;
    2.37 +val language_text = language "text" true;
    2.38 +val language_rail = language "rail" true;
    2.39  
    2.40  
    2.41  (* formal entities *)
     3.1 --- a/src/Pure/PIDE/markup.scala	Wed Feb 19 21:38:44 2014 +0100
     3.2 +++ b/src/Pure/PIDE/markup.scala	Thu Feb 20 12:53:12 2014 +0100
     3.3 @@ -59,7 +59,7 @@
     3.4        markup match {
     3.5          case Markup(ENTITY, props) =>
     3.6            (props, props) match {
     3.7 -            case (Kind(kind), Name(name)) => Some(kind, name)
     3.8 +            case (Kind(kind), Name(name)) => Some((kind, name))
     3.9              case _ => None
    3.10            }
    3.11          case _ => None
    3.12 @@ -87,8 +87,22 @@
    3.13  
    3.14    /* embedded languages */
    3.15  
    3.16 +  val SYMBOLS = "symbols"
    3.17 +  val Symbols = new Properties.Boolean(SYMBOLS)
    3.18 +
    3.19    val LANGUAGE = "language"
    3.20 -  val Language = new Markup_String(LANGUAGE, NAME)
    3.21 +  object Language
    3.22 +  {
    3.23 +    def unapply(markup: Markup): Option[(String, Boolean)] =
    3.24 +      markup match {
    3.25 +        case Markup(LANGUAGE, props) =>
    3.26 +          (props, props) match {
    3.27 +            case (Name(name), Symbols(symbols)) => Some((name, symbols))
    3.28 +            case _ => None
    3.29 +          }
    3.30 +        case _ => None
    3.31 +      }
    3.32 +  }
    3.33  
    3.34  
    3.35    /* external resources */
     4.1 --- a/src/Tools/jEdit/src/completion_popup.scala	Wed Feb 19 21:38:44 2014 +0100
     4.2 +++ b/src/Tools/jEdit/src/completion_popup.scala	Thu Feb 20 12:53:12 2014 +0100
     4.3 @@ -110,7 +110,12 @@
     4.4  
     4.5              val history = PIDE.completion_history.value
     4.6              val decode = Isabelle_Encoding.is_active(buffer)
     4.7 -            syntax.completion.complete(history, decode, explicit, text) match {
     4.8 +            val context =
     4.9 +              PIDE.document_view(text_area) match {
    4.10 +                case None => Completion.Context.default
    4.11 +                case Some(doc_view) => doc_view.get_rendering().completion_context(caret)
    4.12 +              }
    4.13 +            syntax.completion.complete(history, decode, explicit, text, context) match {
    4.14                case Some(result) =>
    4.15                  if (result.unique && result.items.head.immediate && immediate)
    4.16                    insert(result.items.head)
    4.17 @@ -277,7 +282,8 @@
    4.18            val caret = text_field.getCaret.getDot
    4.19            val text = text_field.getText.substring(0, caret)
    4.20  
    4.21 -          syntax.completion.complete(history, decode = true, explicit = false, text) match {
    4.22 +          syntax.completion.complete(
    4.23 +              history, decode = true, explicit = false, text, Completion.Context.default) match {
    4.24              case Some(result) =>
    4.25                val fm = text_field.getFontMetrics(text_field.getFont)
    4.26                val loc =
     5.1 --- a/src/Tools/jEdit/src/rendering.scala	Wed Feb 19 21:38:44 2014 +0100
     5.2 +++ b/src/Tools/jEdit/src/rendering.scala	Thu Feb 20 12:53:12 2014 +0100
     5.3 @@ -200,6 +200,31 @@
     5.4    val dynamic_color = color_value("dynamic_color")
     5.5  
     5.6  
     5.7 +  /* completion context */
     5.8 +
     5.9 +  private val completion_elements =
    5.10 +    Set(Markup.STRING, Markup.ALTSTRING, Markup.VERBATIM, Markup.CARTOUCHE,
    5.11 +      Markup.COMMENT, Markup.LANGUAGE)
    5.12 +
    5.13 +  def completion_context(caret: Text.Offset): Completion.Context =
    5.14 +    if (caret > 0) {
    5.15 +      val result =
    5.16 +        snapshot.select_markup(Text.Range(caret - 1, caret + 1), Some(completion_elements), _ =>
    5.17 +          {
    5.18 +            case Text.Info(_, XML.Elem(Markup.Language(language, symbols), _)) =>
    5.19 +              Some(Completion.Context(language, symbols))
    5.20 +            case Text.Info(_, XML.Elem(markup, _)) =>
    5.21 +              if (completion_elements(markup.name)) Some(Completion.Context("unknown", true))
    5.22 +              else None
    5.23 +          })
    5.24 +      result match {
    5.25 +        case Text.Info(_, context) :: _ => context
    5.26 +        case Nil => Completion.Context.default
    5.27 +      }
    5.28 +    }
    5.29 +    else Completion.Context.default
    5.30 +
    5.31 +
    5.32    /* command overview */
    5.33  
    5.34    val overview_limit = options.int("jedit_text_overview_limit")
    5.35 @@ -429,8 +454,8 @@
    5.36              Some(add(prev, r, (true, pretty_typing("::", body))))
    5.37            case (prev, Text.Info(r, XML.Elem(Markup(Markup.ML_TYPING, _), body))) =>
    5.38              Some(add(prev, r, (false, pretty_typing("ML:", body))))
    5.39 -          case (prev, Text.Info(r, XML.Elem(Markup.Language(name), _))) =>
    5.40 -            Some(add(prev, r, (true, XML.Text("language: " + name))))
    5.41 +          case (prev, Text.Info(r, XML.Elem(Markup.Language(language, _), _))) =>
    5.42 +            Some(add(prev, r, (true, XML.Text("language: " + language))))
    5.43            case (prev, Text.Info(r, XML.Elem(Markup(name, _), _))) =>
    5.44              if (tooltips.isDefinedAt(name))
    5.45                Some(add(prev, r, (true, XML.Text(tooltips(name)))))