# HG changeset patch # User wenzelm # Date 1397465756 -7200 # Node ID 282f3b06fa86b84bbddc5ef42915a5c47191e7b6 # Parent 1f9d706968c2d415814a2df7643cfb5313b222b3 support for persistent user dictionaries; diff -r 1f9d706968c2 -r 282f3b06fa86 src/Tools/jEdit/src/spell_checker.scala --- a/src/Tools/jEdit/src/spell_checker.scala Mon Apr 14 09:28:42 2014 +0200 +++ b/src/Tools/jEdit/src/spell_checker.scala Mon Apr 14 10:55:56 2014 +0200 @@ -15,6 +15,7 @@ import scala.collection.mutable import scala.swing.ComboBox import scala.annotation.tailrec +import scala.collection.immutable.{SortedSet, SortedMap} object Spell_Checker @@ -55,19 +56,57 @@ } - /* dictionary consisting of word list */ + /* dictionary -- include/exclude word list */ + + private val USER_DICTIONARIES = Path.explode("$ISABELLE_HOME_USER/dictionaries") class Dictionary private [Spell_Checker](path: Path) { val lang = path.split_ext._1.base.implode override def toString: String = lang - def load_words: List[String] = - path.split_ext._2 match { - case "gz" => split_lines(File.read_gzip(path)) - case "" => split_lines(File.read(path)) - case ext => error("Bad file extension for dictionary " + path) + private val user_path = USER_DICTIONARIES + Path.basic(lang) + + def load(): (List[String], List[String], List[String]) = + { + val main = split_lines(File.read_gzip(path)) + + val user_entries = + if (user_path.is_file) + for { + raw_line <- split_lines(File.read(user_path)) + line = raw_line.trim + if line != "" && !line.startsWith("#") + entry = + Library.try_unprefix("-", line) match { + case Some(s) => (s, false) + case None => (line, true) + } + } yield entry + else Nil + val user = SortedMap.empty[String, Boolean] ++ user_entries + val include = user.toList.filter(_._2).map(_._1) + val exclude = user.toList.filterNot(_._2).map(_._1) + + (main, include, exclude) + } + + def save(include: List[String], exclude: List[String]) + { + if (!include.isEmpty || !exclude.isEmpty || user_path.is_file) { + val header = """# Spell-checker dictionary +# +# * each line contains at most one word +# * extra blanks are ignored +# * lines starting with "#" are ignored +# * lines starting with "-" indicate excluded words +# * later entries take precedence +# +""" + Isabelle_System.mkdirs(USER_DICTIONARIES) + File.write(user_path, header + cat_lines(include ::: exclude.map("-" + _))) } + } } @@ -117,7 +156,11 @@ { override def toString: String = dictionary.toString - private val dict = + private var dict = new Object + private var include_set = SortedSet.empty[String] + private var exclude_set = SortedSet.empty[String] + + private def make_dict() { val factory_class = Class.forName("com.inet.jortho.DictionaryFactory") val factory_cons = factory_class.getConstructor() @@ -126,18 +169,46 @@ val add = factory_class.getDeclaredMethod("add", classOf[String]) add.setAccessible(true) - dictionary.load_words.foreach(add.invoke(factory, _)) + + val (main, include, exclude) = dictionary.load() + include_set = SortedSet.empty[String] ++ include + exclude_set = SortedSet.empty[String] ++ exclude + + for { + word <- main.iterator ++ include.iterator + if !exclude_set.contains(word) + } add.invoke(factory, word) val create = factory_class.getDeclaredMethod("create") create.setAccessible(true) - create.invoke(factory) + dict = create.invoke(factory) + } + make_dict() + + def save() + { + dictionary.save(include_set.toList, exclude_set.toList) } - def add(word: String) + def include(word: String) { + include_set += word + exclude_set -= word + val m = dict.getClass.getDeclaredMethod("add", classOf[String]) m.setAccessible(true) m.invoke(dict, word) + + save() + } + + def exclude(word: String) + { + include_set -= word + exclude_set += word + + save() + make_dict() } def contains(word: String): Boolean =