diff --git a/changelog.xml b/changelog.xml index f8a819db..c4b8670c 100644 --- a/changelog.xml +++ b/changelog.xml @@ -19,6 +19,15 @@ Add support for the `main` option of the babel package (LaTeX) + + Add support for [`ltex.dictionary`](https://valentjn.github.io/vscode-ltex/docs/settings.html#ltexdictionary) when using a LanguageTool HTTP server + + + Handle disabled rules ourselves to prevent reinitialization of LanguageTool when running the `Disable rule` quick fix + + + Fix LanguageTool reinitialized when running the `Add '...' to dictionary` quick fix + Fix used i18n keys removed diff --git a/pom.xml b/pom.xml index f2fc2b2c..24ab8bbe 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ 4.0.0 org.bsplines ltexls - 13.0.1-alpha.1.develop + 13.1.0-alpha.1.develop ${project.groupId}:${project.artifactId} LTeX Language Server (LTeX LS): LSP language server for LanguageTool with support for LaTeX, Markdown, and others https://github.com/valentjn/ltex-ls diff --git a/src/main/kotlin/org/bsplines/ltexls/languagetool/LanguageToolHttpInterface.kt b/src/main/kotlin/org/bsplines/ltexls/languagetool/LanguageToolHttpInterface.kt index 5ac1d6a1..34ce6b43 100644 --- a/src/main/kotlin/org/bsplines/ltexls/languagetool/LanguageToolHttpInterface.kt +++ b/src/main/kotlin/org/bsplines/ltexls/languagetool/LanguageToolHttpInterface.kt @@ -16,7 +16,6 @@ import org.bsplines.ltexls.tools.I18n import org.bsplines.ltexls.tools.Logging import org.languagetool.markup.AnnotatedText import org.languagetool.markup.TextPart -import org.languagetool.rules.RuleMatch import java.io.IOException import java.io.UnsupportedEncodingException import java.net.MalformedURLException @@ -34,9 +33,8 @@ class LanguageToolHttpInterface( uriString: String, private val languageShortCode: String, private val motherTongueShortCode: String, -) : LanguageToolInterface { - private val enabledRuleIds: MutableList = ArrayList() - private val disabledRuleIds: MutableList = ArrayList() +) : LanguageToolInterface() { + private val enabledRules: MutableList = ArrayList() private val httpClient: HttpClient = HttpClient.newHttpClient() private val uri: URI? @@ -61,7 +59,9 @@ class LanguageToolHttpInterface( return (this.uri != null) } - override fun check(annotatedTextFragment: AnnotatedTextFragment): List { + override fun checkInternal( + annotatedTextFragment: AnnotatedTextFragment, + ): List { if (!isInitialized()) return emptyList() val requestBody: String = createRequestBody(annotatedTextFragment) ?: return emptyList() @@ -92,20 +92,9 @@ class LanguageToolHttpInterface( val result = ArrayList() for (jsonElement: JsonElement in jsonMatches) { - val jsonMatch: JsonObject = jsonElement.asJsonObject - val ruleId: String = jsonMatch.get("rule").asJsonObject.get("id").asString - val sentence: String = jsonMatch.get("sentence").asString - val fromPos: Int = jsonMatch.get("offset").asInt - val toPos: Int = fromPos + jsonMatch.get("length").asInt - val message: String = jsonMatch.get("message").asString - val suggestedReplacements = ArrayList() - - for (replacement: JsonElement in jsonMatch.get("replacements").asJsonArray) { - suggestedReplacements.add(replacement.asJsonObject.get("value").asString) - } - - result.add(LanguageToolRuleMatch.fromLanguageTool(ruleId, sentence, fromPos, toPos, message, - suggestedReplacements, RuleMatch.Type.Hint, annotatedTextFragment)) + result.add( + LanguageToolRuleMatch.fromLanguageTool(jsonElement.asJsonObject, annotatedTextFragment) + ) } return result @@ -127,12 +116,8 @@ class LanguageToolHttpInterface( requestEntries["motherTongue"] = this.motherTongueShortCode } - if (this.enabledRuleIds.isNotEmpty()) { - requestEntries["enabledRules"] = this.enabledRuleIds.joinToString(",") - } - - if (this.disabledRuleIds.isNotEmpty()) { - requestEntries["disabledRules"] = this.disabledRuleIds.joinToString(",") + if (this.enabledRules.isNotEmpty()) { + requestEntries["enabledRules"] = this.enabledRules.joinToString(",") } val builder = StringBuilder() @@ -169,13 +154,7 @@ class LanguageToolHttpInterface( } override fun enableRules(ruleIds: Set) { - this.enabledRuleIds.addAll(ruleIds) - this.disabledRuleIds.removeAll(ruleIds) - } - - override fun disableRules(ruleIds: Set) { - this.enabledRuleIds.removeAll(ruleIds) - this.disabledRuleIds.addAll(ruleIds) + this.enabledRules.addAll(ruleIds) } override fun enableEasterEgg() { diff --git a/src/main/kotlin/org/bsplines/ltexls/languagetool/LanguageToolInterface.kt b/src/main/kotlin/org/bsplines/ltexls/languagetool/LanguageToolInterface.kt index fe792015..c1a1f6b2 100644 --- a/src/main/kotlin/org/bsplines/ltexls/languagetool/LanguageToolInterface.kt +++ b/src/main/kotlin/org/bsplines/ltexls/languagetool/LanguageToolInterface.kt @@ -9,14 +9,45 @@ package org.bsplines.ltexls.languagetool import org.bsplines.ltexls.parsing.AnnotatedTextFragment -interface LanguageToolInterface { - fun isInitialized(): Boolean - fun check(annotatedTextFragment: AnnotatedTextFragment): List - fun activateDefaultFalseFriendRules() - fun activateLanguageModelRules(languageModelRulesDirectory: String) - fun activateNeuralNetworkRules(neuralNetworkRulesDirectory: String) - fun activateWord2VecModelRules(word2vecRulesDirectory: String) - fun enableRules(ruleIds: Set) - fun disableRules(ruleIds: Set) - fun enableEasterEgg() +abstract class LanguageToolInterface { + var dictionary: Set = emptySet() + var disabledRules: Set = emptySet() + + fun check(annotatedTextFragment: AnnotatedTextFragment): List { + val matches = ArrayList() + + for (match: LanguageToolRuleMatch in checkInternal(annotatedTextFragment)) { + if (checkMatchValidity(annotatedTextFragment, match)) matches.add(match) + } + + return matches + } + + protected fun checkMatchValidity( + annotatedTextFragment: AnnotatedTextFragment, + match: LanguageToolRuleMatch, + ): Boolean { + return ( + ( + !match.isUnknownWordRule() + || !this.dictionary.contains( + annotatedTextFragment.getSubstringOfPlainText(match.fromPos, match.toPos) + ) + ) + && !this.disabledRules.contains(match.ruleId) + ) + } + + abstract fun isInitialized(): Boolean + + protected abstract fun checkInternal( + annotatedTextFragment: AnnotatedTextFragment, + ): List + + abstract fun activateDefaultFalseFriendRules() + abstract fun activateLanguageModelRules(languageModelRulesDirectory: String) + abstract fun activateNeuralNetworkRules(neuralNetworkRulesDirectory: String) + abstract fun activateWord2VecModelRules(word2vecRulesDirectory: String) + abstract fun enableRules(ruleIds: Set) + abstract fun enableEasterEgg() } diff --git a/src/main/kotlin/org/bsplines/ltexls/languagetool/LanguageToolJavaInterface.kt b/src/main/kotlin/org/bsplines/ltexls/languagetool/LanguageToolJavaInterface.kt index 81183e16..765c5c5f 100644 --- a/src/main/kotlin/org/bsplines/ltexls/languagetool/LanguageToolJavaInterface.kt +++ b/src/main/kotlin/org/bsplines/ltexls/languagetool/LanguageToolJavaInterface.kt @@ -37,8 +37,7 @@ class LanguageToolJavaInterface( motherTongueShortCode: String, sentenceCacheSize: Long, dictionary: Set, -) : LanguageToolInterface { - private val dictionary: Set = dictionary.toSet() +) : LanguageToolInterface() { private val resultCache = ResultCache(sentenceCacheSize, RESULT_CACHE_EXPIRE_AFTER_MINUTES, TimeUnit.MINUTES) private val languageTool: JLanguageTool? @@ -61,7 +60,9 @@ class LanguageToolJavaInterface( } @Suppress("INACCESSIBLE_TYPE") - override fun check(annotatedTextFragment: AnnotatedTextFragment): List { + override fun checkInternal( + annotatedTextFragment: AnnotatedTextFragment, + ): List { val languageTool: JLanguageTool? = this.languageTool if (languageTool == null) { @@ -123,16 +124,7 @@ class LanguageToolJavaInterface( val result = ArrayList() for (match: RuleMatch in matches) { - val languageToolRuleMatch = LanguageToolRuleMatch.fromLanguageTool( - match, annotatedTextFragment) - - if (languageToolRuleMatch.isUnknownWordRule() - && this.dictionary.contains(annotatedTextFragment.getSubstringOfPlainText( - languageToolRuleMatch.fromPos, languageToolRuleMatch.toPos))) { - continue - } - - result.add(languageToolRuleMatch) + result.add(LanguageToolRuleMatch.fromLanguageTool(match, annotatedTextFragment)) } return result @@ -206,11 +198,6 @@ class LanguageToolJavaInterface( } } - override fun disableRules(ruleIds: Set) { - val languageTool: JLanguageTool = (this.languageTool ?: return) - languageTool.disableRules(ruleIds.toList()) - } - override fun enableEasterEgg() { val languageTool: JLanguageTool = (this.languageTool ?: return) diff --git a/src/main/kotlin/org/bsplines/ltexls/languagetool/LanguageToolRuleMatch.kt b/src/main/kotlin/org/bsplines/ltexls/languagetool/LanguageToolRuleMatch.kt index b95f38da..6047ed5b 100644 --- a/src/main/kotlin/org/bsplines/ltexls/languagetool/LanguageToolRuleMatch.kt +++ b/src/main/kotlin/org/bsplines/ltexls/languagetool/LanguageToolRuleMatch.kt @@ -7,6 +7,8 @@ package org.bsplines.ltexls.languagetool +import com.google.gson.JsonElement +import com.google.gson.JsonObject import org.bsplines.ltexls.parsing.AnnotatedTextFragment import org.bsplines.ltexls.server.LtexTextDocumentItem import org.bsplines.ltexls.tools.Tools @@ -34,8 +36,10 @@ data class LanguageToolRuleMatch( companion object { private val TWO_OR_MORE_SPACES_REGEX = Regex("[ \n]{2,}") - fun fromLanguageTool(match: RuleMatch, annotatedTextFragment: AnnotatedTextFragment): - LanguageToolRuleMatch { + fun fromLanguageTool( + match: RuleMatch, + annotatedTextFragment: AnnotatedTextFragment, + ): LanguageToolRuleMatch { return fromLanguageTool( match.rule?.id, match.sentence?.text, @@ -48,6 +52,29 @@ data class LanguageToolRuleMatch( ) } + fun fromLanguageTool( + jsonMatch: JsonObject, + annotatedTextFragment: AnnotatedTextFragment, + ): LanguageToolRuleMatch { + val fromPos: Int = jsonMatch.get("offset").asInt + val suggestedReplacements = ArrayList() + + for (replacement: JsonElement in jsonMatch.get("replacements").asJsonArray) { + suggestedReplacements.add(replacement.asJsonObject.get("value").asString) + } + + return fromLanguageTool( + jsonMatch.get("rule").asJsonObject.get("id").asString, + jsonMatch.get("sentence").asString, + fromPos, + fromPos + jsonMatch.get("length").asInt, + jsonMatch.get("message").asString, + suggestedReplacements, + RuleMatch.Type.Hint, + annotatedTextFragment, + ) + } + @Suppress("LongParameterList") fun fromLanguageTool( ruleId: String?, diff --git a/src/main/kotlin/org/bsplines/ltexls/server/CodeActionGenerator.kt b/src/main/kotlin/org/bsplines/ltexls/server/CodeActionGenerator.kt index a02fd33c..44a187a0 100644 --- a/src/main/kotlin/org/bsplines/ltexls/server/CodeActionGenerator.kt +++ b/src/main/kotlin/org/bsplines/ltexls/server/CodeActionGenerator.kt @@ -99,8 +99,7 @@ class CodeActionGenerator( document, newWord, acceptSuggestionsMatches))) } - if (addToDictionaryMatches.isNotEmpty() - && this.settingsManager.settings.languageToolHttpServerUri.isEmpty()) { + if (addToDictionaryMatches.isNotEmpty()) { result.add(Either.forRight(getAddWordToDictionaryCodeAction(document, addToDictionaryMatches, annotatedTextFragments))) } diff --git a/src/main/kotlin/org/bsplines/ltexls/settings/Settings.kt b/src/main/kotlin/org/bsplines/ltexls/settings/Settings.kt index 20fa0088..546253b6 100644 --- a/src/main/kotlin/org/bsplines/ltexls/settings/Settings.kt +++ b/src/main/kotlin/org/bsplines/ltexls/settings/Settings.kt @@ -89,14 +89,6 @@ data class Settings( return differences } - if (dictionary != other.dictionary) { - differences.add(SettingsDifference("dictionary", this.dictionary, other.dictionary)) - } - - if (disabledRules != other.disabledRules) { - differences.add(SettingsDifference("disabledRules", this.disabledRules, other.disabledRules)) - } - if (enabledRules != other.enabledRules) { differences.add(SettingsDifference("enabledRules", this.enabledRules, other.enabledRules)) } diff --git a/src/main/kotlin/org/bsplines/ltexls/settings/SettingsManager.kt b/src/main/kotlin/org/bsplines/ltexls/settings/SettingsManager.kt index be5d8f7c..db623af7 100644 --- a/src/main/kotlin/org/bsplines/ltexls/settings/SettingsManager.kt +++ b/src/main/kotlin/org/bsplines/ltexls/settings/SettingsManager.kt @@ -33,7 +33,11 @@ class SettingsManager { value.getDifferencesRelevantForLanguageTool(oldSettings) if (settingsDifferencesRelevantForLanguageTool.isEmpty()) { - this.languageToolInterface = this.languageToolInterfaceMap[newLanguage] + val languageToolInterface: LanguageToolInterface? = + this.languageToolInterfaceMap[newLanguage] + this.languageToolInterface?.dictionary = value.dictionary + this.languageToolInterface?.disabledRules = value.disabledRules + this.languageToolInterface = languageToolInterface } else { if (Logging.logger.isLoggable(Level.FINE)) { logDifferentSettings(newLanguage, settingsDifferencesRelevantForLanguageTool) @@ -100,7 +104,6 @@ class SettingsManager { } languageToolInterface.enableRules(this.settings.enabledRules) - languageToolInterface.disableRules(this.settings.disabledRules) this.languageToolInterface = languageToolInterface } diff --git a/src/test/kotlin/org/bsplines/ltexls/settings/SettingsTest.kt b/src/test/kotlin/org/bsplines/ltexls/settings/SettingsTest.kt index 23515749..4dd54598 100644 --- a/src/test/kotlin/org/bsplines/ltexls/settings/SettingsTest.kt +++ b/src/test/kotlin/org/bsplines/ltexls/settings/SettingsTest.kt @@ -35,13 +35,13 @@ class SettingsTest { settings = settings.copy(_allDictionaries = settings.getModifiedDictionary(setOf("dictionary"))) assertEquals(setOf("dictionary"), settings.dictionary) - settings2 = compareSettings(settings, settings2, true) + settings2 = compareSettings(settings, settings2, false) settings = settings.copy( _allDisabledRules = settings.getModifiedDisabledRules(setOf("disabledRules")) ) assertEquals(setOf("disabledRules"), settings.disabledRules) - settings2 = compareSettings(settings, settings2, true) + settings2 = compareSettings(settings, settings2, false) settings = settings.copy( _allEnabledRules = settings.getModifiedEnabledRules(setOf("enabledRules"))