diff --git a/plugins/grazie/resources/META-INF/plugin.xml b/plugins/grazie/resources/META-INF/plugin.xml index ca37c6bb3501..21127b0e832e 100644 --- a/plugins/grazie/resources/META-INF/plugin.xml +++ b/plugins/grazie/resources/META-INF/plugin.xml @@ -41,13 +41,6 @@ - - , ModificationT //Since commit cc47dd17 NEW_UI; - override fun next() = values().getOrNull(ordinal + 1) - override fun toString() = ordinal.toString() + override fun next(): Version? = entries.getOrNull(ordinal + 1) + override fun toString(): String = ordinal.toString() companion object { val CURRENT = NEW_UI @@ -76,6 +77,8 @@ class GrazieConfig : PersistentStateComponent, ModificationT @Deprecated("Moved to checkingContext in version 2") @ApiStatus.ScheduledForRemoval @Property val enabledCommitIntegration: Boolean = false, @Property val userDisabledRules: Set = HashSet(), @Property val userEnabledRules: Set = HashSet(), + @Property val domainDisabledRules: Map> = TreeMap(), + @Property val domainEnabledRules: Map> = TreeMap(), //Formerly suppressionContext -- name changed due to compatibility issues @Property val suppressingContext: SuppressingContext = SuppressingContext(), @Property val detectionContext: DetectionContext.State = DetectionContext.State(), @@ -84,6 +87,7 @@ class GrazieConfig : PersistentStateComponent, ModificationT //Ex. Grazie pro properties @Property val styleProfile: String? = TextStyle.Unspecified.id, @Property val parameters: Map> = TreeMap(), + @Property val parametersPerDomain: Map>> = TreeMap(), @Property val useOxfordSpelling: Boolean = false, @Property val autoFix: Boolean = false, ) : VersionedState { @@ -104,7 +108,7 @@ class GrazieConfig : PersistentStateComponent, ModificationT val missedLanguages: Set get() = enabledLanguages.asSequence().filter { isMissingLanguage(it) }.toCollection(CollectionFactory.createSmallMemoryFootprintLinkedSet()) - override fun increment() = copy(version = version.next() ?: error("Attempt to increment latest version $version")) + override fun increment(): State = copy(version = version.next() ?: error("Attempt to increment latest version $version")) fun hasMissedLanguages(): Boolean { return enabledLanguages.any { isMissingLanguage(it) } @@ -114,32 +118,102 @@ class GrazieConfig : PersistentStateComponent, ModificationT return !isAvailableLocally(lang) && lang.jLanguage == null } - fun withAutoFix(autoFix: Boolean): State = copy(autoFix = autoFix) - fun withOxfordSpelling(useOxford: Boolean): State = copy(useOxfordSpelling = useOxford) - fun withParameter(language: Language, parameter: Parameter, value: String?): State { - val newLangParams = TreeMap(parameters[language] ?: emptyMap()) - if (value != null) { - newLangParams[parameter.id()] = value - } - else { - newLangParams.remove(parameter.id()) - } - - val newParams = TreeMap(parameters) - if (newLangParams.isEmpty()) { - newParams.remove(language) - } - else { - newParams[language] = newLangParams - } - return copy(parameters = newParams) + fun isRuleEnabled(ruleId: String, domain: TextStyleDomain): Boolean { + return ruleId in (if (domain == TextStyleDomain.Other) userEnabledRules else domainEnabledRules[domain] ?: emptySet()) } - val textStyle: TextStyle - get() = TextStyle.styles(RuleIdeClient.INSTANCE).find { it.id == styleProfile } ?: TextStyle.Unspecified + fun isRuleDisabled(ruleId: String, domain: TextStyleDomain): Boolean { + return ruleId in (if (domain == TextStyleDomain.Other) userDisabledRules else domainDisabledRules[domain] ?: emptySet()) + } - fun paramValue(language: Language, parameter: Parameter): String? { - return parameters[language]?.get(parameter.id()) + fun withAutoFix(autoFix: Boolean): State = copy(autoFix = autoFix) + fun withOxfordSpelling(useOxford: Boolean): State = copy(useOxfordSpelling = useOxford) + fun withParameter(domain: TextStyleDomain, language: Language, parameter: Parameter, value: String?): State { + if (domain == TextStyleDomain.Other) { + val newLangParams = TreeMap(parameters[language] ?: emptyMap()) + if (value != null) newLangParams[parameter.id()] = value else newLangParams.remove(parameter.id()) + + val newParams = TreeMap(parameters) + if (newLangParams.isEmpty()) newParams.remove(language) else newParams[language] = newLangParams + return copy(parameters = newParams) + } + + val newParamsPerDomain = TreeMap(parametersPerDomain) + val langsInDomain = TreeMap(newParamsPerDomain[domain] ?: emptyMap()) + val newLangParams = TreeMap(langsInDomain[language] ?: emptyMap()) + + if (value != null) newLangParams[parameter.id()] = value else newLangParams.remove(parameter.id()) + + if (newLangParams.isEmpty()) { + langsInDomain.remove(language) + } else { + langsInDomain[language] = newLangParams + } + + if (langsInDomain.isEmpty()) { + newParamsPerDomain.remove(domain) + } else { + newParamsPerDomain[domain] = langsInDomain + } + + return copy(parametersPerDomain = newParamsPerDomain) + } + + fun getUserChangedRules(domain: TextStyleDomain): UserChangedRules { + val userEnabledRules = HashSet() + val userDisabledRules = HashSet() + val isOtherDomain = domain == TextStyleDomain.Other + if (isOtherDomain) { + userEnabledRules.addAll(this.userEnabledRules) + userDisabledRules.addAll(this.userDisabledRules) + } + else { + userEnabledRules.addAll(getDomainEnabledRules(domain)) + userDisabledRules.addAll(getDomainDisabledRules(domain)) + } + return UserChangedRules(userEnabledRules, userDisabledRules) + } + + fun updateUserRules(domain: TextStyleDomain, userEnabledRules: Set, userDisabledRules: Set): State { + val userRules = getUserChangedRules(domain) + if (userEnabledRules == userRules.enabled && userDisabledRules == userRules.disabled) { + return this + } + return if (domain == TextStyleDomain.Other) this.copy(userEnabledRules = userEnabledRules, userDisabledRules = userDisabledRules) + else this.withDomainEnabledRules(domain, userEnabledRules).withDomainDisabledRules(domain, userDisabledRules) + } + + private fun getDomainEnabledRules(domain: TextStyleDomain) = domainEnabledRules[domain] ?: emptySet() + + private fun getDomainDisabledRules(domain: TextStyleDomain) = domainDisabledRules[domain] ?: emptySet() + + fun withDomainEnabledRules(domain: TextStyleDomain, rules: Set): State { + val newRules = TreeMap(domainEnabledRules) + newRules[domain] = rules + return copy(domainEnabledRules = newRules) + } + + fun withDomainDisabledRules(domain: TextStyleDomain, rules: Set): State { + val newRules = TreeMap(domainDisabledRules) + newRules[domain] = rules + return copy(domainDisabledRules = newRules) + } + + fun paramValue(domain: TextStyleDomain, language: Language, parameter: Parameter): String? { + if (domain == TextStyleDomain.Other) { + return parameters[language]?.get(parameter.id()) + } + return parametersPerDomain[domain]?.get(language)?.get(parameter.id()) + } + + @JvmOverloads + fun getTextStyle(domain: TextStyleDomain? = null): TextStyle { + val styleProfileId = getTextStyleId(domain) ?: return TextStyle.Unspecified + return TextStyle.styles(RuleIdeClient.INSTANCE).find { it.id == styleProfileId } ?: TextStyle.Unspecified + } + + private fun getTextStyleId(domain: TextStyleDomain? = null): String? { + return if (domain == null || domain == TextStyleDomain.Other) styleProfile else domain.name } enum class Processing { @@ -155,7 +229,7 @@ class GrazieConfig : PersistentStateComponent, ModificationT @VisibleForTesting fun migrateLTRuleIds(state: State): State { val ltRules: List by lazy { - state.enabledLanguages.filter { it.jLanguage != null }.flatMap { grammarRules(LangTool.createTool(it, state), it) } + state.enabledLanguages.filter { it.jLanguage != null }.flatMap { grammarRules(LangTool.createTool(it, state, TextStyleDomain.Other), it) } } fun convert(ids: Set): Set = @@ -179,7 +253,7 @@ class GrazieConfig : PersistentStateComponent, ModificationT * * Should never be called in GrazieStateLifecycle actions */ - fun get() = service().state + fun get(): State = service().state /** Update Grazie config state */ @Synchronized @@ -194,6 +268,8 @@ class GrazieConfig : PersistentStateComponent, ModificationT } } + data class UserChangedRules(val enabled: Set, val disabled: Set) + class PresentableNameGetter : com.intellij.openapi.components.State.NameGetter() { override fun get() = GrazieBundle.message("grazie.config.name") } @@ -203,7 +279,7 @@ class GrazieConfig : PersistentStateComponent, ModificationT override fun getModificationCount(): Long = myModCount.get() - override fun getState() = myState + override fun getState(): State = myState override fun loadState(state: State) { myModCount.incrementAndGet() @@ -224,7 +300,7 @@ class GrazieConfig : PersistentStateComponent, ModificationT update { state -> val oxford = get().useOxfordSpelling if (oxford) { - state.copy(userDisabledRules = state.userDisabledRules - oxfordSpellingLtRules.toSet()) + state.copy(userDisabledRules = state.userDisabledRules - oxfordSpellingLtRules) } else { state.copy(userDisabledRules = state.userDisabledRules + oxfordSpellingLtRules) diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/detection/Extensions.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/detection/Extensions.kt index 496af75ff3ea..a44021522c67 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/detection/Extensions.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/detection/Extensions.kt @@ -2,16 +2,19 @@ package com.intellij.grazie.detection import ai.grazie.nlp.langs.Language import ai.grazie.nlp.langs.alphabet.Alphabet +import com.intellij.grazie.GrazieConfig import com.intellij.grazie.jlanguage.Lang -fun Lang.toLanguage() = Language.values().find { it.iso == this.iso }!! +fun Lang.toLanguage(): Language = Language.entries.find { it.iso == this.iso }!! /** Note that it will return SOME dialect */ -fun Language.toLang() = Lang.values().find { it.iso == this.iso }!! +fun Language.toLang(): Lang = Lang.entries.find { it.iso == this.iso }!! -fun Language.toLangOrNull(): Lang? { - return Lang.values().find { it.iso == this.iso } -} +fun Language.toAvailableLang(): Lang = GrazieConfig.get().availableLanguages.find { it.iso == this.iso }!! + +fun Language.toAvailableLangOrNull(): Lang? = GrazieConfig.get().availableLanguages.find { it.iso == this.iso } + +fun Language.toLangOrNull(): Lang? = Lang.entries.find { it.iso == this.iso } val Language.hasWhitespaces: Boolean get() = alphabet.group != Alphabet.Group.ASIAN diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/grammar/LanguageToolChecker.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/grammar/LanguageToolChecker.kt index cefc7b7c1e1e..920f81c4b945 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/grammar/LanguageToolChecker.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/grammar/LanguageToolChecker.kt @@ -8,6 +8,8 @@ import com.intellij.grazie.jlanguage.Lang import com.intellij.grazie.jlanguage.LangTool import com.intellij.grazie.text.* import com.intellij.grazie.utils.NaturalTextDetector +import com.intellij.grazie.utils.TextStyleDomain +import com.intellij.grazie.utils.getTextDomain import com.intellij.grazie.utils.trimToNull import com.intellij.openapi.application.runReadAction import com.intellij.openapi.progress.ProcessCanceledException @@ -44,7 +46,7 @@ open class LanguageToolChecker : ExternalTextChecker() { override fun getRules(locale: Locale): Collection { val language = Languages.getLanguageForLocale(locale) val lang = Lang.entries.find { it.jLanguage == language } ?: return emptyList() - return grammarRules(LangTool.getTool(lang), lang) + return grammarRules(LangTool.getTool(lang, TextStyleDomain.Other), lang) } @OptIn(DelicateCoroutinesApi::class) @@ -72,7 +74,7 @@ open class LanguageToolChecker : ExternalTextChecker() { } private fun collectLanguageToolProblems(extracted: TextContent, text: String, lang: Lang): List { - val tool = LangTool.getTool(lang) + val tool = LangTool.getTool(lang, extracted.getTextDomain()) val sentences = tool.sentenceTokenize(text) if (sentences.any { it.length > 1000 }) { return emptyList() diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/grammar/LanguageToolRule.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/grammar/LanguageToolRule.kt index 0e42ebb2e502..8c3da5fe5278 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/grammar/LanguageToolRule.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/grammar/LanguageToolRule.kt @@ -5,6 +5,7 @@ import com.intellij.grazie.ide.ui.components.utils.html import com.intellij.grazie.jlanguage.Lang import com.intellij.grazie.jlanguage.LangTool import com.intellij.grazie.text.Rule +import com.intellij.grazie.utils.TextStyleDomain import kotlinx.html.* import org.languagetool.JLanguageTool import org.languagetool.rules.Categories @@ -18,7 +19,7 @@ class LanguageToolRule( private val lang: Lang, val ltRule: org.languagetool.rules.Rule, private val similarLtRules: List = emptyList(), ) : Rule(LangTool.globalIdPrefix(lang) + ltRule.id, ltRule.description, categories(ltRule, lang)) { - override fun isEnabledByDefault(): Boolean = LangTool.isRuleEnabledByDefault(lang, ltRule.id) + override fun isEnabledByDefault(domain: TextStyleDomain): Boolean = LangTool.isRuleEnabledByDefault(lang, ltRule.id, domain) override fun getUrl(): URL? = similarLtRules.map { it.url }.toSet().singleOrNull() diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/fus/GrazieFUSState.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/fus/GrazieFUSState.kt index 848c93e64348..23b5fdca3bd3 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/fus/GrazieFUSState.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/fus/GrazieFUSState.kt @@ -18,7 +18,7 @@ import com.intellij.internal.statistic.service.fus.collectors.ApplicationUsagesC import com.intellij.internal.statistic.utils.getPluginInfo internal class GrazieFUSState : ApplicationUsagesCollector() { - private val GROUP = EventLogGroup("grazie.state", 9) + private val GROUP = EventLogGroup("grazie.state", 10) private val ENABLE_LANGUAGE = GROUP.registerEvent( "enabled.language", EventFields.Enum("value", LanguageISO::class.java) { it.name.lowercase() } @@ -100,7 +100,7 @@ internal class GrazieFUSState : ApplicationUsagesCollector() { val connectionType = GrazieCloudConnector.EP_NAME.extensionList.firstNotNullOfOrNull { it.connectionType() } ?: Processing.Local metrics.add(PROCESSING.metric(connectionType)) if (state.styleProfile != DEFAULT_STATE.styleProfile) { - metrics.add(WRITING_STYLE.metric(state.textStyle.id.uppercase())) + metrics.add(WRITING_STYLE.metric(state.getTextStyle().id.uppercase())) } if (state.autoFix != DEFAULT_STATE.autoFix) { metrics.add(AUTO_FIX.metric(state.autoFix)) diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/inspection/grammar/quickfix/GrazieRuleSettingsAction.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/inspection/grammar/quickfix/GrazieRuleSettingsAction.kt index d6f0817f813b..c638a10a89c5 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/inspection/grammar/quickfix/GrazieRuleSettingsAction.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/inspection/grammar/quickfix/GrazieRuleSettingsAction.kt @@ -42,9 +42,7 @@ class GrazieRuleSettingsAction(private val ruleName: String, private val rule: R navigatable.navigate(true) ok = true } else { - val configurable = GrazieConfigurable() - configurable.selectRule(rule.globalId) - ok = ShowSettingsUtil.getInstance().editConfigurable(project, configurable) + ok = ShowSettingsUtil.getInstance().editConfigurable(project, GrazieConfigurable()) } val result = if (!ok) "canceled" else analyzeStateChange(state1, GrazieConfig.get()) diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/components/GrazieUIComponent.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/components/GrazieUIComponent.kt index 9cc0e441f371..7a8fb8324348 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/components/GrazieUIComponent.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/components/GrazieUIComponent.kt @@ -15,13 +15,6 @@ internal interface GrazieUIComponent { /** Applies changes from component to passed state of GrazieConfig and returns new version */ fun apply(state: GrazieConfig.State): GrazieConfig.State - /** View-only components, that can not be modified somehow */ - interface ViewOnly : GrazieUIComponent { - override fun isModified(state: GrazieConfig.State) = false - override fun apply(state: GrazieConfig.State) = state - override fun reset(state: GrazieConfig.State) {} - } - /** Components, that change representation, but delegate actual data handing to `impl` */ interface Delegating : GrazieUIComponent { val impl: GrazieUIComponent diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/configurable/StyleConfigurable.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/configurable/StyleConfigurable.kt index e37800f91fbc..3166058a2e18 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/configurable/StyleConfigurable.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/configurable/StyleConfigurable.kt @@ -1,30 +1,35 @@ package com.intellij.grazie.ide.ui.configurable import ai.grazie.nlp.langs.Language +import ai.grazie.nlp.langs.utils.englishName import ai.grazie.nlp.langs.utils.nativeName import ai.grazie.rules.Rule -import ai.grazie.rules.settings.RuleSetting import ai.grazie.rules.settings.Setting import ai.grazie.rules.settings.SettingComponent import ai.grazie.rules.settings.TextStyle import ai.grazie.rules.toolkit.LanguageToolkit import com.intellij.grazie.GrazieBundle import com.intellij.grazie.GrazieConfig -import com.intellij.grazie.ide.ui.grammar.GrazieConfigurable +import com.intellij.grazie.detection.toLanguage +import com.intellij.grazie.ide.ui.configurable.StyleConfigurable.Companion.ruleEngineLanguages +import com.intellij.grazie.ide.ui.grammar.tabs.rules.component.GrazieDescriptionComponent +import com.intellij.grazie.ide.ui.grammar.tabs.rules.component.GrazieTreeComponent import com.intellij.grazie.rule.RuleIdeClient -import com.intellij.grazie.rule.SentenceBatcher +import com.intellij.grazie.utils.TextStyleDomain +import com.intellij.grazie.utils.getAffectedGlobalRules +import com.intellij.grazie.utils.getOtherDomainStyles +import com.intellij.grazie.utils.getTextDomain import com.intellij.icons.AllIcons import com.intellij.ide.BrowserUtil -import com.intellij.ide.DataManager +import com.intellij.openapi.Disposable import com.intellij.openapi.options.BoundConfigurable import com.intellij.openapi.options.Configurable -import com.intellij.openapi.options.ConfigurableWithId import com.intellij.openapi.options.ShowSettingsUtil -import com.intellij.openapi.options.ex.Settings import com.intellij.openapi.project.Project import com.intellij.openapi.ui.ComboBox import com.intellij.openapi.ui.DialogPanel import com.intellij.openapi.ui.getParentOfType +import com.intellij.openapi.util.NlsContexts import com.intellij.openapi.wm.IdeFocusManager import com.intellij.pom.Navigatable import com.intellij.psi.codeStyle.NameUtil @@ -36,89 +41,125 @@ import com.intellij.util.IconUtil import com.intellij.util.ui.JBEmptyBorder import com.intellij.util.ui.JBFont import com.intellij.util.ui.JBUI +import com.intellij.util.ui.JBUI.Borders import com.intellij.util.ui.UIUtil.FontSize import com.intellij.util.ui.update.UiNotifyConnector import java.awt.BorderLayout +import java.awt.Dimension import java.awt.Image import java.awt.Rectangle import java.net.URL -import javax.swing.JComponent -import javax.swing.JEditorPane -import javax.swing.JPanel -import javax.swing.SwingUtilities +import javax.swing.* +import javax.swing.ScrollPaneConstants.* import javax.swing.event.DocumentEvent import javax.swing.event.HyperlinkEvent -class StyleConfigurable: - BoundConfigurable(GrazieBundle.message("grazie.settings.style.configurable.name"), null), - ConfigurableWithId, Configurable.NoScroll { //todo searchable? - +class StyleConfigurable : BoundConfigurable(GrazieBundle.message("grazie.settings.grammar.tabs.rules"), null), Disposable, Configurable.NoScroll { private var focusedControl: JComponent? = null - - private var langData = LinkedHashMap() + private val settings: Settings = Settings() private val langComboModel = CollectionComboBoxModel(ArrayList()) + private lateinit var langCombo: ComboBox private val filterComponent: SearchTextField = SearchTextField(false).also { it.textEditor.emptyText.text = GrazieBundle.message("grazie.settings.style.search.placeholder") it.textEditor.document.addDocumentListener(object : DocumentAdapter() { override fun textChanged(e: DocumentEvent) { - updateFilter() + settings.updateFilter(textStyle, langComboModel.selected!!, filterComponent.text) } }) it.border = JBEmptyBorder(5) } - private val settingWrapper by lazy { - JPanel(BorderLayout()).also { it.add(langData[Language.ENGLISH]!!.component) } + private val treeWrapper by lazy { + JBSplitter(false, 0.45f).apply { + firstComponent = createScrollTreeComponent() + secondComponent = settings.getTreeSettings(textStyle, Language.ENGLISH).description.component + } } + + private val settingWrapper by lazy { + JPanel(BorderLayout()).also { + it.add(settings.getFeaturedSettings(textStyle, Language.ENGLISH)!!.component) + it.maximumSize = Dimension(Int.MAX_VALUE, it.preferredSize.height) + } + } + + private val separator by lazy { + createTitledSeparator(GrazieBundle.message("grazie.settings.style.rules.other"), IntelliJSpacingConfiguration()) + } + + private lateinit var domainComboBox: ComboBox private lateinit var styleProfileCombo: ComboBox - private lateinit var langCombo: ComboBox + private lateinit var styleRowVisibleUpdater: ((Boolean) -> Unit) private val config get() = GrazieConfig.get() - private var styleProfile: TextStyle - get() = config.textStyle - set(value) = GrazieConfig.update { it.copy(styleProfile = value.id) } + private val textStyle + get(): TextStyle { + val domain = domainComboBox.selected!! + if (domain == TextStyleDomain.Other) return styleProfileCombo.selected!! + return TextStyle.styles(RuleIdeClient.INSTANCE).find { it.id == domain.name }!! + } - override fun getId() = ID + private var otherDomainStyle: TextStyle + get() = config.getTextStyle() + set(value) { + GrazieConfig.update { it.copy(styleProfile = value.id) } + } - @Suppress("UnstableApiUsage") - private val component by lazy { - ruleLanguages.forEach { addLanguage(it) } + val component: DialogPanel by lazy { + loadLanguages() + val userTextStyle = GrazieConfig.get().getTextStyle() panel { - row(GrazieBundle.message("grazie.settings.writing.style.text")) { - styleProfileCombo = writingStyleComboBox() - .widthGroup("TopCombo") - .bindItem(::styleProfile.toNullableProperty()) - .whenItemSelectedFromUi { profile -> - for (data in langData.values) { - data.component.loadState(data.component.state, profile) + row { + comment(GrazieBundle.message("grazie.settings.writing.style.hint")) + } + row { + label(GrazieBundle.message("grazie.settings.writing.style.domain")) + domainComboBox = domainComboBox() + .whenItemSelectedFromUi { domainId -> + styleRowVisibleUpdater.invoke(domainId == TextStyleDomain.Other) + val textStyle = if (domainId == TextStyleDomain.Other) GrazieConfig.get().getTextStyle() else domainId.textStyle + selectTextStyle(textStyle, langComboModel.selected!!) + } + .component + domainComboBox.selectedItem = TextStyleDomain.Other + + val styleLabelCell = label(GrazieBundle.message("grazie.settings.writing.style.text")) + val styleComboCell = writingStyleComboBox() + .applyIfEnabled() + .bindItem(::otherDomainStyle.toNullableProperty()) + .whenItemSelectedFromUi { textStyle -> + if (domainComboBox.selected == TextStyleDomain.Other) { + selectTextStyle(textStyle, langComboModel.selected!!) + settings.reset(GrazieConfig.get()) + selectLanguage(textStyle, langComboModel.selected!!) } } - .component + settings.addTextStyle(userTextStyle, Language.ENGLISH, filterComponent) + styleProfileCombo = styleComboCell.component + styleProfileCombo.selectedItem = userTextStyle + styleRowVisibleUpdater = { visible -> + styleLabelCell.visible(visible) + styleComboCell.visible(visible) + styleComboCell.enabled(visible) + } + styleRowVisibleUpdater.invoke(domainComboBox.selectedItem == TextStyleDomain.Other) } - row(GrazieBundle.message("grazie.settings.style.language.chooser.label")) { - langCombo = comboBox( - langComboModel, - SimpleListCellRenderer.create { label, lang, _ -> label.text = lang.nativeName } - ) + row(GrazieBundle.message("grazie.settings.language.chooser.label")) { + langCombo = comboBox(langComboModel, SimpleListCellRenderer.create { label, lang, _ -> label.text = lang.nativeName }) .widthGroup("TopCombo") - .whenItemSelectedFromUi {lang -> languageChanged(lang) } - .component - - selectLanguage(Language.ENGLISH) - trackNewLanguageAddition() - - link(GrazieBundle.message("grazie.settings.style.configure.all.rules.link")) { - val settings = ideSettings() - val configurable = settings?.find(GrazieConfigurable::class.java) - if (configurable != null) { - settings.select(configurable) - } else { - ShowSettingsUtil.getInstance().editConfigurable(settingWrapper, GrazieConfigurable()) + .whenItemSelectedFromUi { language -> + settings.addTextStyle(textStyle, language, filterComponent) + selectLanguage(textStyle, language) + separator.text = GrazieBundle.message(if (language in ruleEngineLanguages) "grazie.settings.style.rules.other" else "grazie.settings.style.rules.all") + if (filterComponent.text.isNotBlank()) settings.updateFilter(textStyle, language, filterComponent.text) + settings.getTreeSettings(textStyle, language).description.listener(language) } - } + .component + selectLanguage(userTextStyle, Language.ENGLISH) + trackNewLanguageAddition() } row { @@ -126,40 +167,233 @@ class StyleConfigurable: } row { - scrollCell(settingWrapper).resizableColumn().align(Align.FILL) + val content = JPanel().apply { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + add(settingWrapper) + add(separator) + add(treeWrapper) + add(Box.createVerticalGlue()) + } + val scroll = ScrollPaneFactory.createScrollPane(content, VERTICAL_SCROLLBAR_ALWAYS, HORIZONTAL_SCROLLBAR_NEVER) + cell(scroll).resizableColumn().align(Align.FILL) resizableRow() } - } + + treeWrapper.minimumSize = JBUI.size(150, 200) + treeWrapper.maximumSize = Dimension(Int.MAX_VALUE, Int.MAX_VALUE) + treeWrapper.preferredSize = JBUI.size(-1, 300) + treeWrapper.setHonorComponentsMinimumSize(true) + + settings.getTreeSettings(textStyle, Language.ENGLISH).description.listener(Language.ENGLISH) + }.also { it.border = Borders.empty() } } override fun createPanel(): DialogPanel = component + override fun isModified(): Boolean = super.isModified || settings.isModified() + + override fun apply() { + super.apply() + settings.apply() + } + + override fun reset() { + super.reset() + settings.reset(GrazieConfig.get()) + } + + override fun dispose() { + disposeUIResources() + } + + private fun selectTextStyle(textStyle: TextStyle, language: Language) { + settings.addTextStyle(textStyle, language, filterComponent) + repaintSettings(textStyle, language) + } + + private fun selectLanguage(textStyle: TextStyle, language: Language) { + settings.addLanguage(textStyle, language, filterComponent) + langCombo.selectedItem = language + repaintSettings(textStyle, language) + } + + private fun repaintSettings(textStyle: TextStyle, language: Language) { + val hasFeaturedVisible = language in ruleEngineLanguages + settingWrapper.isVisible = hasFeaturedVisible + if (hasFeaturedVisible) { + settingWrapper.removeAll() + settingWrapper.add(settings.getFeaturedSettings(textStyle, language)!!.component) + settingWrapper.maximumSize = Dimension(Int.MAX_VALUE, settingWrapper.preferredSize.height) + settingWrapper.repaint() + } + treeWrapper.firstComponent = createScrollTreeComponent() + treeWrapper.secondComponent = settings.getTreeSettings(textStyle, language).description.component + treeWrapper.repaint() + } + private fun trackNewLanguageAddition() { - GrazieConfig.subscribe(disposable!!) { - for (lang in ruleLanguages) { - if (lang !in langData) { - addLanguage(lang) + GrazieConfig.subscribe(this) { + if (loadLanguages()) { + settings.clear() + settings.addTextStyle(textStyle, Language.ENGLISH, filterComponent) + } + } + } + + private fun loadLanguages(): Boolean { + val langs = GrazieConfig.get().availableLanguages.map { it.toLanguage() }.sortedBy { it.englishName } + if (langComboModel.items == langs) return false + langComboModel.removeAll() + langComboModel.add(langs) + return true + } + + override fun getDisplayName(): @NlsContexts.ConfigurableName String = "" + + override fun getPreferredFocusedComponent(): JComponent? { + return focusedControl ?: super.getPreferredFocusedComponent() + } + + private fun createScrollTreeComponent(): JScrollPane { + return ScrollPaneFactory.createScrollPane( + settings.getTreeSettings(textStyle, Language.ENGLISH).tree, + VERTICAL_SCROLLBAR_AS_NEEDED, + HORIZONTAL_SCROLLBAR_AS_NEEDED + ) + } + + companion object { + + @JvmStatic + val ruleEngineLanguages: List = listOf(Language.ENGLISH, Language.GERMAN, Language.RUSSIAN, Language.UKRAINIAN) + + @JvmStatic + fun featuredSettings(toolkit: LanguageToolkit): List = toolkit.getSettings(RuleIdeClient.INSTANCE).flatMap { it.settings } + + @JvmStatic + fun focusSetting(setting: Setting, contextProject: Project): Navigatable { + return object : Navigatable { + override fun navigate(requestFocus: Boolean) { + ShowSettingsUtil.getInstance().showSettingsDialog(contextProject, StyleConfigurable::class.java) { conf -> + conf.createComponent() + val style = getTextStyle(GrazieConfig.get().styleProfile ?: TextStyle.Unspecified.id) + val featuredSettings = conf.settings.featuredSettings[style.id]!! + for ((lang, data) in featuredSettings) { + val settingComponent = data.component.findParentSettingComponent(setting) + val paramComponent = data.component.findOwnSettingComponent(setting) + if (paramComponent != null && settingComponent != null) { + conf.langComboModel.add(lang) + conf.selectLanguage(style, lang) + UiNotifyConnector.doWhenFirstShown(data.component) { + SwingUtilities.invokeLater { + val scrollPane = settingComponent.getParentOfType()!! + settingComponent.scrollRectToVisible(Rectangle(settingComponent.width, scrollPane.height)) + IdeFocusManager.getInstance(contextProject).requestFocus(paramComponent, true) + } + } + break + } + } + } + } + + override fun canNavigate() = true + override fun canNavigateToSource() = false + } + } + } +} + +data class Settings( + val featuredSettings: MutableMap> = HashMap(), + private val treeSettings: MutableMap> = HashMap(), +) { + fun getFeaturedSettings(textStyle: TextStyle, language: Language): FeaturedSettings? = featuredSettings[textStyle.id]?.get(language) + fun getTreeSettings(textStyle: TextStyle, language: Language): TreeSettings = treeSettings[textStyle.id]!![language]!! + + fun isModified(): Boolean = + featuredSettings.values.any { isModifiedFeaturedSettings(it) } || + treeSettings.values.any { isModifiedTreeSettings(it) } + + fun reset(state: GrazieConfig.State) { + featuredSettings.forEach { (domain, featuredSettings) -> + if (isModifiedFeaturedSettings(featuredSettings)) { + featuredSettings.forEach { (language, settings) -> + val textStyle = getTextStyle(domain) + settings.component.loadState(getSettingsState(language, textStyle), textStyle) + settings.resetState = settings.component.state } } } - } - - private fun createLinkLabel(text: String, onClick: Runnable): JComponent { - val link = HyperlinkLabel(text) - link.addHyperlinkListener { e: HyperlinkEvent -> - if (e.eventType == HyperlinkEvent.EventType.ACTIVATED) { - onClick.run() + treeSettings.forEach { (_, treesSettings) -> + if (isModifiedTreeSettings(treesSettings)) { + treesSettings.forEach { (_, settings) -> settings.tree.reset(state) } } } - link.setFontSize(FontSize.SMALL) - link.updateUI() - return link } - private fun addLanguage(lang: Language) { - if (lang !in ruleLanguages || SentenceBatcher.findInstalledLTLanguage(lang) == null) return + fun apply() { + treeSettings + .filter { (domainId, treeSettingsMap) -> isModifiedFeaturedSettings(featuredSettings[domainId]!!) || isModifiedTreeSettings(treeSettingsMap) } + .forEach { (domainId, treeSettingsMap) -> + val domain = getTextStyle(domainId).getTextDomain() + val userEnabledRules = HashSet() + val userDisabledRules = HashSet() + val parameters = HashMap>() - val toolkit = LanguageToolkit.forLanguage(lang) + featuredSettings[domainId]?.forEach { (language, settings) -> + val prefix = Rule.globalIdPrefix(language) + val settingsState = settings.component.state + settings.resetState = settingsState + + for (id in settingsState.enabledRules) { + userEnabledRules.add(prefix + id) + userDisabledRules.remove(prefix + id) + } + for (id in settingsState.disabledRules) { + userEnabledRules.remove(prefix + id) + userDisabledRules.add(prefix + id) + } + parameters[language] = settingsState.paramValues + } + + if (domain == TextStyleDomain.Other) GrazieConfig.update { it.copy(parameters = parameters) } + else GrazieConfig.update { it.copy(parametersPerDomain = mapOf(domain to parameters)) } + + treeSettingsMap.forEach { + val updatedState = it.value.tree.apply(GrazieConfig.get()) + val affectedGlobalRules = getAffectedGlobalRules(it.key) + updatedState.getUserChangedRules(domain).let { (enabledRules, disabledRules) -> + userEnabledRules.addAll(enabledRules - affectedGlobalRules) + userDisabledRules.addAll(disabledRules - affectedGlobalRules) + } + } + GrazieConfig.update { it.updateUserRules(domain, userEnabledRules, userDisabledRules) } + treeSettingsMap.forEach { it.value.tree.reset(GrazieConfig.get()) } + } + } + + fun addTextStyle(textStyle: TextStyle, language: Language, filterComponent: SearchTextField) { + if (textStyle.id in featuredSettings && textStyle.id in treeSettings) return + featuredSettings[textStyle.id] = HashMap() + treeSettings[textStyle.id] = HashMap() + addLanguage(textStyle, language, filterComponent) + } + + fun addLanguage(textStyle: TextStyle, language: Language, filterComponent: SearchTextField) { + val featuredSettingsPerLanguage = featuredSettings[textStyle.id]!! + val treeSettingsPerLanguage = treeSettings[textStyle.id]!! + if (language in featuredSettingsPerLanguage || language in treeSettingsPerLanguage) return + + val domain = textStyle.getTextDomain() + val description = GrazieDescriptionComponent() + val tree = GrazieTreeComponent(description.listener, language, domain, filterComponent) + treeSettingsPerLanguage[language] = TreeSettings(description, tree) + treeSettingsPerLanguage[language]!!.tree.reset(GrazieConfig.get()) + + if (language !in ruleEngineLanguages) return + + val toolkit = LanguageToolkit.forLanguage(language) val spacing = IntelliJSpacingConfiguration() val ui = object : SettingComponent.UI { override fun getExamplePrefix(): String = GrazieBundle.message("grazie.settings.style.configurable.example.prefix") @@ -185,18 +419,11 @@ class StyleConfigurable: override fun createCollapseComponent(examplesOnly: Boolean, doCollapse: Runnable): JComponent { val text = if (examplesOnly) GrazieBundle.message("grazie.settings.style.configurable.collapse.examples.link") - else GrazieBundle.message("grazie.settings.style.configurable.collapse.link") + else GrazieBundle.message("grazie.settings.style.configurable.collapse.link") return createLinkLabel(text, doCollapse) } - override fun createGroupHeader(name: String): JComponent { - val title = JBLabel(name) - val separator = object : TitledSeparator(title.text) { - override fun createLabel(): JBLabel = title - } - separator.border = JBEmptyBorder(spacing.verticalMediumGap, 0, spacing.verticalSmallGap, 0) - return separator - } + override fun createGroupHeader(@NlsContexts.Label name: String): JComponent = createTitledSeparator(name, spacing) override fun customizeSettingSection(setting: Setting, section: JComponent) { section.border = JBEmptyBorder(0, spacing.horizontalIndent, spacing.verticalComponentGap, 0) @@ -207,159 +434,101 @@ class StyleConfigurable: pane.foreground = JBUI.CurrentTheme.ContextHelp.FOREGROUND } } - val comp = SettingComponent(toolkit, RuleIdeClient.INSTANCE, ui) + val component = SettingComponent(toolkit, RuleIdeClient.INSTANCE, ui) - val prefix = Rule.globalIdPrefix(lang) - val affectedRules = affectedSettings(toolkit) - .filterIsInstance() - .map { prefix + it.rule.id } - .toSet() - - langData[lang] = LangData(comp, SettingComponent.SettingState.UNCHANGED, affectedRules) - langComboModel.add(lang) + val settingState = getSettingsState(language, textStyle) + component.loadState(settingState, textStyle) + featuredSettingsPerLanguage[language] = FeaturedSettings(component, settingState) } - private fun updateFilter() { - langData[langComboModel.selected!!]!!.component.filter(filterComponent.text) + fun updateFilter(textStyle: TextStyle, language: Language, option: String) { + featuredSettings[textStyle.id]!![language]!!.component.filter(option) + treeSettings[textStyle.id]!![language]!!.tree.filter(option) } - private fun selectLanguage(lang: Language) { - langCombo.selectedItem = lang - languageChanged(lang) + fun clear() { + featuredSettings.clear() + treeSettings.clear() } - private fun languageChanged(lang: Language) { - updateFilter() - settingWrapper.removeAll() - settingWrapper.add(langData[lang]!!.component) - settingWrapper.repaint() + private fun isModifiedFeaturedSettings(featuredSettings: MutableMap): Boolean { + return featuredSettings.any { it.value.component.state != it.value.resetState } } - override fun apply() { - val prev = GrazieConfig.get() - super.apply() - - val userEnabledRules = HashSet(GrazieConfig.get().userEnabledRules) - val userDisabledRules = HashSet(GrazieConfig.get().userDisabledRules) - val params = HashMap(GrazieConfig.get().parameters) - - for ((lang, data) in langData) { - val prefix = Rule.globalIdPrefix(lang) - val state = data.component.state - data.resetState = state - - val affectedIds = data.affectedGlobalRuleIds - userEnabledRules.removeIf { it.startsWith(prefix) && it in affectedIds } - userDisabledRules.removeIf { it.startsWith(prefix) && it in affectedIds } - - for (id in state.enabledRules) { - userEnabledRules.add(prefix + id) - userDisabledRules.remove(prefix + id) - } - for (id in state.disabledRules) { - userEnabledRules.remove(prefix + id) - userDisabledRules.add(prefix + id) - } - - params[lang] = state.paramValues - } - - GrazieConfig.update { it.copy(parameters = params) } - - val liteConfigurable = ideSettings()?.find(GrazieConfigurable::class.java) - - if (userEnabledRules != GrazieConfig.get().userEnabledRules || - userDisabledRules != GrazieConfig.get().userDisabledRules) { - GrazieConfig.update { it.copy(userEnabledRules = userEnabledRules, userDisabledRules = userDisabledRules) } - liteConfigurable?.reset() - } - - if (GrazieConfig.get().styleProfile != prev.styleProfile) { - liteConfigurable?.ruleEnablednessChanged(GrazieConfig.get()) - } + private fun isModifiedTreeSettings(treeSettings: MutableMap): Boolean { + return treeSettings.any { it.value.tree.isModified(GrazieConfig.get()) } } +} - override fun isModified(): Boolean { - return super.isModified() || - langData.values.any { it.component.state != it.resetState } - } +data class FeaturedSettings( + val component: SettingComponent, + var resetState: SettingComponent.SettingState, +) - override fun reset() { - super.reset() +data class TreeSettings( + val description: GrazieDescriptionComponent, + val tree: GrazieTreeComponent, +) - val currentStyle = styleProfileCombo.selectedItem as TextStyle +private fun getSettingsState(language: Language, textStyle: TextStyle): SettingComponent.SettingState { + val state = GrazieConfig.get() - val userEnabledRules = GrazieConfig.get().userEnabledRules - val userDisabledRules = GrazieConfig.get().userDisabledRules - - for ((lang, data) in langData) { - val prefix = Rule.globalIdPrefix(lang) - val langState = SettingComponent.SettingState( - GrazieConfig.get().parameters[lang] ?: emptyMap(), - userEnabledRules.filter { it.startsWith(prefix) }.map { it.substring(prefix.length) }.toSet(), - userDisabledRules.filter { it.startsWith(prefix) }.map { it.substring(prefix.length) }.toSet(), - ) - data.component.loadState(langState, currentStyle) - data.resetState = data.component.state - } - } - - override fun getPreferredFocusedComponent(): JComponent? { - return focusedControl ?: super.getPreferredFocusedComponent() - } - - private fun ideSettings() = DataManager.getInstance().getDataContext(settingWrapper).getData(Settings.KEY) - - companion object { - internal const val ID = "reference.settings.grazie.pro.style" - - val ruleLanguages = listOf(Language.ENGLISH, Language.GERMAN, Language.RUSSIAN, Language.UKRAINIAN) - - - @JvmStatic - fun affectedSettings(toolkit: LanguageToolkit): List = toolkit.getSettings(RuleIdeClient.INSTANCE).flatMap { it.settings } - - @JvmStatic - fun focusSetting(setting: Setting, contextProject: Project?): Navigatable { - return object : Navigatable { - override fun navigate(requestFocus: Boolean) { - ShowSettingsUtil.getInstance().showSettingsDialog(contextProject, StyleConfigurable::class.java) { conf -> - conf.createComponent() - for ((lang, data) in conf.langData) { - val settingComponent = data.component.findParentSettingComponent(setting) - val paramComponent = data.component.findOwnSettingComponent(setting) - if (paramComponent != null && settingComponent != null) { - conf.focusedControl = paramComponent - conf.selectLanguage(lang) - UiNotifyConnector.doWhenFirstShown(data.component) { - SwingUtilities.invokeLater { - val scrollPane = settingComponent.getParentOfType()!! - settingComponent.scrollRectToVisible(Rectangle(settingComponent.width, scrollPane.height)) - IdeFocusManager.getInstance(contextProject).requestFocus(paramComponent, true) - } - } - break - } - } - } - } - override fun canNavigate() = true - override fun canNavigateToSource() = false - } - } - } - - private data class LangData( - val component: SettingComponent, - var resetState: SettingComponent.SettingState, - var affectedGlobalRuleIds: Set + val prefix = Rule.globalIdPrefix(language) + val domain = textStyle.getTextDomain() + val (userEnabledRules, userDisabledRules) = state.getUserChangedRules(domain) + val parameters = if (domain != TextStyleDomain.Other) state.parametersPerDomain[domain] else state.parameters + return SettingComponent.SettingState( + parameters?.get(language) ?: emptyMap(), + userEnabledRules.filter { it.startsWith(prefix) }.map { it.substring(prefix.length) }.toSet(), + userDisabledRules.filter { it.startsWith(prefix) }.map { it.substring(prefix.length) }.toSet(), ) } -fun Row.writingStyleComboBox() = comboBox( - CollectionComboBoxModel(TextStyle.styles(RuleIdeClient.INSTANCE)), +private fun getTextStyle(textStyleId: String): TextStyle { + return TextStyle.styles(RuleIdeClient.INSTANCE).find { it.id == textStyleId } ?: TextStyle.Unspecified +} + +private fun createTitledSeparator(@NlsContexts.Label name: String, spacing: IntelliJSpacingConfiguration): TitledSeparator { + val title = JBLabel(name) + return createTitledSeparator(title, spacing) +} + +private fun createTitledSeparator(title: JBLabel, spacing: IntelliJSpacingConfiguration): TitledSeparator { + val separator = object : TitledSeparator(title.text) { + override fun createLabel(): JBLabel = title + } + separator.border = JBEmptyBorder(spacing.verticalMediumGap, 0, spacing.verticalSmallGap, 0) + separator.maximumSize = Dimension(Int.MAX_VALUE, separator.preferredSize.height) + return separator +} + +private fun createLinkLabel(@NlsContexts.LinkLabel text: String, onClick: Runnable): JComponent { + val link = HyperlinkLabel(text) + link.addHyperlinkListener { e: HyperlinkEvent -> + if (e.eventType == HyperlinkEvent.EventType.ACTIVATED) { + onClick.run() + } + } + link.setFontSize(FontSize.SMALL) + link.updateUI() + return link +} + +private fun Row.writingStyleComboBox() = comboBox( + CollectionComboBoxModel(getOtherDomainStyles()), SimpleListCellRenderer.create { label, value, _ -> label.text = GrazieBundle.messageOrNull("grazie.settings.style.profile.display.${value.id}") ?: NameUtil.splitNameIntoWords(value.id).joinToString(" ") } ) + +private fun Row.domainComboBox() = comboBox( + CollectionComboBoxModel(TextStyleDomain.entries), + SimpleListCellRenderer.create { label, value, _ -> + label.text = GrazieBundle.messageOrNull("grazie.settings.domain.profile.display.$value") + } +) + +@Suppress("UNCHECKED_CAST") +private val ComboBox.selected: T? + get() = this.selectedItem as? T \ No newline at end of file diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/GrazieConfigurable.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/GrazieConfigurable.kt index c0d2d2d34b59..f95f4b7cce72 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/GrazieConfigurable.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/GrazieConfigurable.kt @@ -11,55 +11,19 @@ import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.options.Configurable.WithEpDependencies import com.intellij.openapi.options.ConfigurableBase import com.intellij.openapi.options.SearchableConfigurable -import com.intellij.util.ui.tree.TreeUtil import javax.swing.JComponent class GrazieConfigurable : ConfigurableBase( "reference.settingsdialog.project.grazie", GraziePlugin.settingsPageName, "reference.settings.ide.settings.grammar"), WithEpDependencies, - SearchableConfigurable -{ + SearchableConfigurable { private val ui: GrazieSettingsPanel by lazy { GrazieSettingsPanel() } override fun getSettings() = service() override fun createUi(): GrazieSettingsPanel = ui - override fun getPreferredFocusedComponent(): JComponent? { - if (ui.component.selectedComponent == ui.rules.component) { - return ui.rules.impl - } - - return super.getPreferredFocusedComponent() - } - - override fun enableSearch(option: String?): Runnable? { - if (option != null) { - return Runnable { - ui.component.selectedComponent = ui.rules.component - ui.rules.impl.filter(option) - } - } - return null - } - - internal fun selectRule(globalId: String) { - ui.component.selectedComponent = ui.rules.component - val tree = ui.rules.impl - val ruleNode = (tree.model.root as GrazieRulesTreeNode).findRuleNode(globalId) - if (ruleNode != null) { - TreeUtil.selectNode(tree, ruleNode) - } - } - - // used in Grazie Pro - @Suppress("unused", "SpellCheckingInspection") - fun ruleEnablednessChanged(state: GrazieConfig.State) { - val tree = ui.rules.impl - (tree.model.root as GrazieRulesTreeNode).resetMark(tree.apply(state)) - } - override fun getDependencies(): Collection> { return setOf(LanguageGrammarChecking.EP_NAME, ExtensionPointName("com.intellij.grazie.textExtractor"), diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/GrazieSettingsPanel.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/GrazieSettingsPanel.kt index 2d06672aa4e3..614d540209b0 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/GrazieSettingsPanel.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/GrazieSettingsPanel.kt @@ -3,34 +3,34 @@ package com.intellij.grazie.ide.ui.grammar import com.intellij.grazie.GrazieConfig import com.intellij.grazie.ide.ui.components.dsl.msg +import com.intellij.grazie.ide.ui.configurable.StyleConfigurable import com.intellij.grazie.ide.ui.grammar.tabs.exceptions.GrazieExceptionsTab -import com.intellij.grazie.ide.ui.grammar.tabs.rules.GrazieRulesTab import com.intellij.grazie.ide.ui.grammar.tabs.scope.GrazieScopeTab import com.intellij.openapi.Disposable import com.intellij.openapi.options.ConfigurableUi +import com.intellij.openapi.util.Disposer import com.intellij.ui.components.JBTabbedPane import com.intellij.util.ui.JBUI import javax.swing.JComponent class GrazieSettingsPanel : ConfigurableUi, Disposable { private val scope = GrazieScopeTab() - internal val rules = GrazieRulesTab() + internal val rules = StyleConfigurable() private val exceptions = GrazieExceptionsTab() - override fun isModified(settings: GrazieConfig): Boolean = rules.isModified(settings.state) || + override fun isModified(settings: GrazieConfig): Boolean = rules.isModified || scope.isModified(settings.state) || exceptions.isModified(settings.state) override fun apply(settings: GrazieConfig) { + rules.apply() GrazieConfig.update { state -> - exceptions.apply(scope.apply(rules.apply(state))) + exceptions.apply(scope.apply(state)) } - - rules.reset(settings.state) } override fun reset(settings: GrazieConfig) { - rules.reset(settings.state) + rules.reset() scope.reset(settings.state) exceptions.reset(settings.state) } @@ -44,6 +44,7 @@ class GrazieSettingsPanel : ConfigurableUi, Disposable { override fun getComponent(): JComponent = component - override fun dispose() = rules.dispose() - + override fun dispose() { + Disposer.dispose(rules) + } } diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/GrazieRulesTab.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/GrazieRulesTab.kt deleted file mode 100644 index b7847577b23a..000000000000 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/GrazieRulesTab.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.intellij.grazie.ide.ui.grammar.tabs.rules - -import com.intellij.grazie.GrazieConfig -import com.intellij.grazie.ide.msg.GrazieInitializerManager -import com.intellij.grazie.ide.msg.GrazieStateLifecycle -import com.intellij.grazie.ide.ui.components.GrazieUIComponent -import com.intellij.grazie.ide.ui.components.dsl.panel -import com.intellij.grazie.ide.ui.grammar.tabs.rules.component.GrazieDescriptionComponent -import com.intellij.grazie.ide.ui.grammar.tabs.rules.component.GrazieTreeComponent -import com.intellij.grazie.ide.ui.grammar.tabs.rules.component.allRules -import com.intellij.openapi.Disposable -import com.intellij.openapi.components.service -import com.intellij.openapi.util.Disposer -import com.intellij.ui.layout.migLayout.* -import com.intellij.util.ui.JBUI -import net.miginfocom.layout.AC -import net.miginfocom.layout.CC -import net.miginfocom.swing.MigLayout - -internal class GrazieRulesTab : GrazieUIComponent.Delegating, Disposable { - private val description = GrazieDescriptionComponent() - - override val impl = GrazieTreeComponent(description.listener) - - override val component = panel(MigLayout(createLayoutConstraints(), AC().grow(), AC().grow())) { - border = JBUI.Borders.empty() - add(impl.component, CC().grow().width("45%").minWidth("250px")) - add(description.component, CC().grow().width("55%")) - - impl.reset(GrazieConfig.get()) - } - - // update the tree on language list change in Natural Languages configurable - private val connection = service().register(object : GrazieStateLifecycle { - override fun update(prevState: GrazieConfig.State, newState: GrazieConfig.State) { - if (prevState.enabledLanguages != newState.enabledLanguages) { - impl.resetTreeModel(allRules(newState)) - } - } - }) - - override fun dispose() { - Disposer.dispose(impl) - Disposer.dispose(connection) - } -} diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/component/GrazieDescriptionComponent.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/component/GrazieDescriptionComponent.kt index 54853aa8c264..76423a64a401 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/component/GrazieDescriptionComponent.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/component/GrazieDescriptionComponent.kt @@ -1,12 +1,13 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.grazie.ide.ui.grammar.tabs.rules.component +import ai.grazie.nlp.langs.Language +import ai.grazie.nlp.langs.utils.nativeName import com.intellij.grazie.ide.ui.components.dsl.msg import com.intellij.grazie.ide.ui.components.dsl.padding import com.intellij.grazie.ide.ui.components.dsl.panel import com.intellij.grazie.ide.ui.components.utils.GrazieLinkLabel import com.intellij.grazie.ide.ui.components.utils.html -import com.intellij.grazie.jlanguage.Lang import com.intellij.grazie.text.Rule import com.intellij.ide.BrowserUtil import com.intellij.openapi.util.NlsSafe @@ -66,7 +67,7 @@ class GrazieDescriptionComponent { @NlsSafe private fun getDescriptionPaneContent(meta: Any): String = when (meta) { - is Lang -> html { unsafe { +msg("grazie.settings.grammar.rule.language.template", meta.nativeName) } } + is Language -> html { unsafe { +msg("grazie.settings.grammar.rule.language.template", meta.nativeName) } } is String -> html { unsafe { +msg("grazie.settings.grammar.rule.category.template", meta) } } is Rule -> meta.description else -> "" diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/component/GrazieTreeComponent.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/component/GrazieTreeComponent.kt index a20a64df4a41..65a147499281 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/component/GrazieTreeComponent.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/component/GrazieTreeComponent.kt @@ -1,6 +1,7 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.grazie.ide.ui.grammar.tabs.rules.component +import ai.grazie.nlp.langs.Language import com.intellij.grazie.GrazieConfig import com.intellij.grazie.ide.ui.components.GrazieUIComponent import com.intellij.grazie.ide.ui.components.dsl.panel @@ -10,6 +11,8 @@ import com.intellij.grazie.ide.ui.grammar.tabs.rules.component.rules.GrazieRules import com.intellij.grazie.jlanguage.Lang import com.intellij.grazie.text.Rule import com.intellij.grazie.text.TextChecker +import com.intellij.grazie.utils.TextStyleDomain +import com.intellij.grazie.utils.getAffectedGlobalRules import com.intellij.ide.CommonActionsManager import com.intellij.ide.DefaultTreeExpander import com.intellij.openapi.Disposable @@ -20,14 +23,18 @@ import com.intellij.util.ui.JBUI import com.intellij.util.ui.tree.TreeUtil import org.jetbrains.annotations.ApiStatus import java.awt.BorderLayout -import javax.swing.ScrollPaneConstants import javax.swing.tree.DefaultTreeModel -internal class GrazieTreeComponent(onSelectionChanged: (meta: Any) -> Unit) : CheckboxTree(GrazieRulesTreeCellRenderer(), GrazieRulesTreeNode()), - Disposable, GrazieUIComponent { +class GrazieTreeComponent( + onSelectionChanged: (meta: Any) -> Unit, + private val language: Language, + private val domain: TextStyleDomain, + private val filter: SearchTextField, +) : CheckboxTree(GrazieRulesTreeCellRenderer(), GrazieRulesTreeNode(domain)), + Disposable, GrazieUIComponent { private val disabledRules = hashSetOf() private val enabledRules = hashSetOf() - private val filterComponent: GrazieRulesTreeFilter = GrazieRulesTreeFilter(this) + private val filterComponent = GrazieRulesTreeFilter(this, language) init { selectionModel.addTreeSelectionListener { event -> @@ -42,7 +49,7 @@ internal class GrazieTreeComponent(onSelectionChanged: (meta: Any) -> Unit) : Ch val id = meta.globalId enabledRules.remove(id) disabledRules.remove(id) - if (node.isChecked != meta.isEnabledByDefault) { + if (node.isChecked != meta.isEnabledByDefault(domain)) { (if (node.isChecked) enabledRules else disabledRules).add(id) } } @@ -69,76 +76,66 @@ internal class GrazieTreeComponent(onSelectionChanged: (meta: Any) -> Unit) : Ch toolbar.setTargetComponent(this@tree) add(toolbar.component, BorderLayout.WEST) } - - add(filterComponent, BorderLayout.CENTER) - } - - panel(constraint = BorderLayout.CENTER) { - add(ScrollPaneFactory.createScrollPane(this@GrazieTreeComponent, - ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, - ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER)) } } } override fun isModified(state: GrazieConfig.State): Boolean { - return state.userEnabledRules != enabledRules || state.userDisabledRules != disabledRules + val (userEnabledRules, userDisabledRules) = state.getUserChangedRules(domain) + return userEnabledRules != enabledRules || userDisabledRules != disabledRules } override fun reset(state: GrazieConfig.State) { - enabledRules.clear(); enabledRules.addAll(state.userEnabledRules) - disabledRules.clear(); disabledRules.addAll(state.userDisabledRules) - filterComponent.filter() - if (isSelectionEmpty) setSelectionRow(0) + val (userEnabledRules, userDisabledRules) = state.getUserChangedRules(domain) + enabledRules.clear(); enabledRules.addAll(userEnabledRules) + disabledRules.clear(); disabledRules.addAll(userDisabledRules) + filterComponent.filter(filter.text) } override fun apply(state: GrazieConfig.State): GrazieConfig.State { - return state.copy( - userEnabledRules = HashSet(enabledRules), - userDisabledRules = HashSet(disabledRules) - ) + return state.updateUserRules(domain, enabledRules, disabledRules) } - override fun dispose() { - filterComponent.dispose() + override fun dispose() {} + + @JvmOverloads + fun filter(filterText: String? = filter.text) { + filterComponent.filter(filterText) } - fun filter(str: String) { - filterComponent.filter = str - filterComponent.filter() - } + fun getCurrentFilterString(): String? = filter.text - fun getCurrentFilterString(): String? = filterComponent.filter - - fun resetTreeModel(rules: Map>) { - val root = GrazieRulesTreeNode() + fun resetTreeModel(rules: List) { + val root = GrazieRulesTreeNode(domain) val model = model as DefaultTreeModel - rules.entries.sortedBy { it.key.nativeName }.forEach { (lang, rules) -> - val langNode = GrazieRulesTreeNode(lang) - model.insertNodeInto(langNode, root, root.childCount) - - fun splitIntoCategories(level: Int, rules: List, parent: GrazieRulesTreeNode) { - rules.groupBy { it.categories.getOrNull(level) }.entries - .sortedWith(Comparator.comparing({ it.key }, nullsLast(Comparator.comparing { it.lowercase() }))) - .forEach { (category, catRules) -> - if (category != null) { - val categoryNode = GrazieRulesTreeNode(category) - model.insertNodeInto(categoryNode, parent, parent.childCount) - splitIntoCategories(level + 1, catRules, categoryNode) - } - else { - catRules.sortedBy { it.presentableName.lowercase() }.forEach { rule -> - model.insertNodeInto(GrazieRulesTreeNode(rule), parent, parent.childCount) - } + fun splitIntoCategories(level: Int, rules: List, parent: GrazieRulesTreeNode) { + rules.groupBy { it.categories.getOrNull(level) }.entries + .sortedWith(Comparator.comparing({ it.key }, nullsLast(Comparator.comparing { it.lowercase() }))) + .forEach { (category, categoryRules) -> + if (category != null) { + val categoryNode = GrazieRulesTreeNode(domain, category) + model.insertNodeInto(categoryNode, parent, parent.childCount) + splitIntoCategories(level + 1, categoryRules, categoryNode) + } + else { + categoryRules.sortedBy { it.presentableName.lowercase() }.forEach { rule -> + model.insertNodeInto(GrazieRulesTreeNode(domain, rule), parent, parent.childCount) } } - } - splitIntoCategories(0, rules, langNode) + } } + val affectedGlobalRules = getAffectedGlobalRules(language) + splitIntoCategories( + 0, + rules.filter { it.globalId !in affectedGlobalRules }, + root + ) + + val state = GrazieConfig.get() model.setRoot(root) - root.resetMark(apply(GrazieConfig.get())) + root.resetMark(apply(state)) model.nodeChanged(root) } } @@ -156,4 +153,17 @@ fun allRules(state: GrazieConfig.State = GrazieConfig.get()): Map { + if (lang !in state.enabledLanguages) return emptyList() + val jLanguage = lang.jLanguage + if (jLanguage != null) { + val rules = TextChecker.allCheckers().flatMap { it.getRules(jLanguage.localeWithCountryAndVariant) } + if (rules.isNotEmpty()) { + return rules + } + } + return emptyList() +} diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/component/rules/GrazieRulesTreeFilter.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/component/rules/GrazieRulesTreeFilter.kt index 4b3abc86fcf0..cf9bbd74f11a 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/component/rules/GrazieRulesTreeFilter.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/component/rules/GrazieRulesTreeFilter.kt @@ -1,26 +1,36 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.grazie.ide.ui.grammar.tabs.rules.component.rules +import ai.grazie.nlp.langs.Language +import com.intellij.grazie.detection.toAvailableLang import com.intellij.grazie.ide.ui.grammar.tabs.rules.component.GrazieTreeComponent import com.intellij.grazie.ide.ui.grammar.tabs.rules.component.allRules import com.intellij.packageDependencies.ui.TreeExpansionMonitor -import com.intellij.ui.FilterComponent import com.intellij.util.ui.tree.TreeUtil import javax.swing.tree.DefaultTreeModel -internal class GrazieRulesTreeFilter(private val tree: GrazieTreeComponent) : FilterComponent("GRAZIE_RULE_FILTER", 10) { +internal class GrazieRulesTreeFilter( + private val tree: GrazieTreeComponent, + private val language: Language, +) { private val expansionMonitor = TreeExpansionMonitor.install(tree) - override fun filter() { + fun filter(filterText: String?) { + val hadSelection = !tree.selectionPaths.isNullOrEmpty() expansionMonitor.freeze() - filter(filter) + filterTree(filterText) (tree.model as DefaultTreeModel).reload() - if (filter.isNullOrBlank()) { + if (filterText.isNullOrBlank()) { TreeUtil.collapseAll(tree, 0) - expansionMonitor.restore() + if (hadSelection) { + expansionMonitor.restore() + } + else { + expansionMonitor.unfreeze() + } } else { TreeUtil.expandAll(tree) @@ -28,21 +38,20 @@ internal class GrazieRulesTreeFilter(private val tree: GrazieTreeComponent) : Fi } } - private fun filter(filterString: String?) { + private fun filterTree(filterString: String?) { + val lang = language.toAvailableLang() if (filterString.isNullOrBlank()) { - tree.resetTreeModel(allRules()) + tree.resetTreeModel(allRules(lang)) return } - tree.resetTreeModel( - allRules().map { (lang, rules) -> - lang to rules.filter { - lang.nativeName.contains(filterString, true) || - it.categories.any { cat -> cat.contains(filterString, true) } || - it.presentableName.contains(filterString, true) || - it.searchableDescription.contains(filterString, true) - } - }.toMap().filterValues { it.isNotEmpty() } - ) + val rules = allRules(lang) + .filter { + lang.nativeName.contains(filterString, true) || + it.categories.any { cat -> cat.contains(filterString, true) } || + it.presentableName.contains(filterString, true) || + it.searchableDescription.contains(filterString, true) + } + tree.resetTreeModel(rules) } } diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/component/rules/GrazieRulesTreeNode.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/component/rules/GrazieRulesTreeNode.kt index f750365e49ff..d3e480dcbf44 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/component/rules/GrazieRulesTreeNode.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/ide/ui/grammar/tabs/rules/component/rules/GrazieRulesTreeNode.kt @@ -4,12 +4,16 @@ package com.intellij.grazie.ide.ui.grammar.tabs.rules.component.rules import com.intellij.grazie.GrazieConfig import com.intellij.grazie.jlanguage.Lang import com.intellij.grazie.text.Rule +import com.intellij.grazie.utils.TextStyleDomain import com.intellij.ui.CheckedTreeNode import com.intellij.ui.JBColor import com.intellij.ui.SimpleTextAttributes @Suppress("EqualsOrHashCode") -internal class GrazieRulesTreeNode(userObject: Any? = null) : CheckedTreeNode(userObject) { +internal class GrazieRulesTreeNode( + private val domain: TextStyleDomain, + userObject: Any? = null, +) : CheckedTreeNode(userObject) { val nodeText: String get() = when (val meta = userObject) { is Rule -> meta.presentableName @@ -26,7 +30,7 @@ internal class GrazieRulesTreeNode(userObject: Any? = null) : CheckedTreeNode(us private fun differsFromDefault(): Boolean { val meta = userObject - if (meta is Rule) return meta.isEnabledByDefault != isChecked + if (meta is Rule) return meta.isEnabledByDefault(domain) != isChecked return children.orEmpty().any { (it as GrazieRulesTreeNode).differsFromDefault() } } @@ -38,7 +42,7 @@ internal class GrazieRulesTreeNode(userObject: Any? = null) : CheckedTreeNode(us fun resetMark(state: GrazieConfig.State): Boolean { val meta = userObject if (meta is Rule) { - isChecked = meta.isEnabledInState(state) + isChecked = meta.isEnabledInState(state, domain) } else { isChecked = false diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/jlanguage/LangTool.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/jlanguage/LangTool.kt index 59f54d005c29..d051bc071bd3 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/jlanguage/LangTool.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/jlanguage/LangTool.kt @@ -8,6 +8,7 @@ import com.intellij.grazie.ide.msg.GrazieStateLifecycle import com.intellij.grazie.jlanguage.broker.GrazieDynamicDataBroker import com.intellij.grazie.jlanguage.filters.UppercaseMatchFilter import com.intellij.grazie.jlanguage.hunspell.LuceneHunspellDictionary +import com.intellij.grazie.utils.TextStyleDomain import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.util.text.StringUtil import com.intellij.util.containers.ContainerUtil @@ -31,8 +32,8 @@ import kotlin.math.max import kotlin.math.min object LangTool : GrazieStateLifecycle { - private val langs: MutableMap = Collections.synchronizedMap(ContainerUtil.createSoftValueMap()) - private val rulesEnabledByDefault = ConcurrentHashMap>() + private val langs: MutableMap> = ConcurrentHashMap() + private val rulesEnabledByDefault = ConcurrentHashMap>>() private val inappropriateExamples = mapOf( "DT_NNS_AGREEMENT" to setOf("small children and their mothers", "eternal rest") ) @@ -49,40 +50,37 @@ object LangTool : GrazieStateLifecycle { internal fun globalIdPrefix(lang: Lang): String = "LanguageTool." + lang.ltRemote!!.iso.name + "." - fun getTool(lang: Lang): JLanguageTool { + fun getTool(lang: Lang, domain: TextStyleDomain): JLanguageTool { // this is equivalent to computeIfAbsent, but allows multiple threads to create tools concurrently, // so that threads can be interrupted (with checkCanceled on their own indicator) instead of waiting on a lock while (true) { - var tool = langs[lang] + val tools = langs.computeIfAbsent(lang) { Collections.synchronizedMap(ContainerUtil.createSoftValueMap()) } + var tool = tools[domain] if (tool != null) return tool val state = GrazieConfig.get() - tool = createTool(lang, state) + tool = createTool(lang, state, domain) synchronized(langs) { if (state === GrazieConfig.get()) { - val alreadyComputed = langs[lang] + val alreadyComputed = tools[domain] if (alreadyComputed != null) return alreadyComputed - langs[lang] = tool + tools[domain] = tool return tool } } } } - - internal fun createTool(lang: Lang, state: GrazieConfig.State): JLanguageTool { + internal fun createTool(lang: Lang, state: GrazieConfig.State, domain: TextStyleDomain): JLanguageTool { val jLanguage = lang.jLanguage require(jLanguage != null) { "Trying to get LangTool for not available language" } return JLanguageTool(jLanguage, null, ResultCache(10_000)).apply { setCheckCancelledCallback { ProgressManager.checkCanceled(); false } addMatchFilter(UppercaseMatchFilter()) - val prefix = globalIdPrefix(lang) - val disabledRules = state.userDisabledRules.mapNotNull { if (it.startsWith(prefix)) it.substring(prefix.length) else null }.toSet() - val enabledRules = state.userEnabledRules.mapNotNull { if (it.startsWith(prefix)) it.substring(prefix.length) else null }.toSet() - + val (enabledRules, disabledRules) = getRules(lang, state, domain) + enabledRules. forEach { id -> enableRule(id) } disabledRules.forEach { id -> disableRule(id) } - enabledRules.forEach { id -> enableRule(id) } fun loadConfigFile(path: String, block: (iso: String, id: String) -> Unit) { GrazieDynamicDataBroker.getFromResourceDirAsStream(path).use { stream -> @@ -147,6 +145,22 @@ object LangTool : GrazieStateLifecycle { return false } + private fun getRules(lang: Lang, state: GrazieConfig.State, domain: TextStyleDomain): Pair, Set> { + val prefix = globalIdPrefix(lang) + val enabledRules = HashSet() + val disabledRules = HashSet() + if (domain == TextStyleDomain.Other) { + enabledRules.addAll( state.userEnabledRules.mapNotNull { if (it.startsWith(prefix)) it.substring(prefix.length) else null }) + disabledRules.addAll(state.userDisabledRules.mapNotNull { if (it.startsWith(prefix)) it.substring(prefix.length) else null }) + } else { + val domainEnabledRules = state.domainEnabledRules[domain] ?: emptySet() + val domainDisabledRules = state.domainDisabledRules[domain] ?: emptySet() + enabledRules.addAll( domainEnabledRules.mapNotNull { if (it.startsWith(prefix)) it.substring(prefix.length) else null }) + disabledRules.addAll(domainDisabledRules.mapNotNull { if (it.startsWith(prefix)) it.substring(prefix.length) else null }) + } + return enabledRules to disabledRules + } + private fun prepareForNoChunkTags(rule: Rule) { @Suppress("TestOnlyProblems") fun relaxChunkConditions(token: PatternToken, positive: Boolean) { @@ -185,7 +199,7 @@ object LangTool : GrazieStateLifecycle { val inappropriateExamples = inappropriateExamples[rule.id] if (inappropriateExamples != null) { return rule.incorrectExamples - .filterNot { example -> inappropriateExamples.any { example.example.contains(it)} } + .filterNot { example -> inappropriateExamples.any { example.example.contains(it) } } } return rule.incorrectExamples } @@ -202,9 +216,10 @@ object LangTool : GrazieStateLifecycle { return suffixLength + prefixLength >= distance } - internal fun isRuleEnabledByDefault(lang: Lang, ruleId: String): Boolean { - val activeIds = rulesEnabledByDefault.computeIfAbsent(lang) { - createTool(lang, GrazieConfig.State()).allActiveRules.map { it.id }.toSet() + internal fun isRuleEnabledByDefault(lang: Lang, ruleId: String, domain: TextStyleDomain): Boolean { + val rules = rulesEnabledByDefault.computeIfAbsent(lang) { ConcurrentHashMap() } + val activeIds = rules.computeIfAbsent(domain) { + createTool(lang, GrazieConfig.State(), domain).allActiveRules.map { it.id }.toSet() } return activeIds.contains(ruleId) } @@ -214,6 +229,8 @@ object LangTool : GrazieStateLifecycle { prevState.availableLanguages == newState.availableLanguages && prevState.userDisabledRules == newState.userDisabledRules && prevState.userEnabledRules == newState.userEnabledRules + && prevState.domainEnabledRules == newState.domainEnabledRules + && prevState.domainDisabledRules == newState.domainDisabledRules ) return langs.clear() diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/rule/RuleIdeClient.java b/plugins/grazie/src/main/kotlin/com/intellij/grazie/rule/RuleIdeClient.java index ec374e94956c..1e78d31f8a1c 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/rule/RuleIdeClient.java +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/rule/RuleIdeClient.java @@ -34,4 +34,9 @@ public class RuleIdeClient implements RuleClient { public boolean hasLocalMode() { return true; } + + @Override + public boolean showIdeStyles() { + return true; + } } diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/spellcheck/GrazieSpellchecker.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/spellcheck/GrazieSpellchecker.kt index e8c284f38f41..22ecc48b67df 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/spellcheck/GrazieSpellchecker.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/spellcheck/GrazieSpellchecker.kt @@ -10,6 +10,7 @@ import com.intellij.grazie.ide.msg.CONFIG_STATE_TOPIC import com.intellij.grazie.ide.msg.GrazieStateLifecycle import com.intellij.grazie.jlanguage.Lang import com.intellij.grazie.jlanguage.LangTool +import com.intellij.grazie.utils.TextStyleDomain import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ex.ApplicationUtil import com.intellij.openapi.components.Service @@ -113,7 +114,7 @@ class GrazieCheckers(coroutineScope: CoroutineScope) : GrazieStateLifecycle { for (lang in GrazieConfig.get().availableLanguages) { if (lang.isEnglish()) continue - val tool = LangTool.getTool(lang) + val tool = LangTool.getTool(lang, TextStyleDomain.Other) tool.allSpellingCheckRules.firstOrNull() ?.let { set.add(SpellerTool(tool, lang, it)) } } diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/ChangeLanguageVariant.java b/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/ChangeLanguageVariant.java index d45f89b32619..0082f9f2e06b 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/ChangeLanguageVariant.java +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/ChangeLanguageVariant.java @@ -58,11 +58,10 @@ record ChangeLanguageVariant(Lang from, Lang to, boolean wasOxford, boolean toOx languages.add(to); return s.copy( - languages, s.getEnabledGrammarStrategies(), s.getDisabledGrammarStrategies(), - s.getEnabledCommitIntegration(), - s.getUserDisabledRules(), s.getUserEnabledRules(), + languages, s.getEnabledGrammarStrategies(), s.getDisabledGrammarStrategies(), s.getEnabledCommitIntegration(), + s.getUserDisabledRules(), s.getUserEnabledRules(), s.getDomainDisabledRules(), s.getDomainEnabledRules(), s.getSuppressingContext(), s.getDetectionContext(), s.getCheckingContext(), s.getVersion(), - s.getStyleProfile(), s.getParameters(), s.getUseOxfordSpelling(), s.getAutoFix() + s.getStyleProfile(), s.getParameters(), s.getParametersPerDomain(), s.getUseOxfordSpelling(), s.getAutoFix() ); }); if (from.isEnglish()) { diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/Rule.java b/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/Rule.java index 8e700e207330..4ad93bb10c67 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/Rule.java +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/Rule.java @@ -1,6 +1,8 @@ package com.intellij.grazie.text; import com.intellij.grazie.GrazieConfig; +import com.intellij.grazie.utils.TextStyleDomain; +import com.intellij.grazie.utils.TextUtilsKt; import com.intellij.pom.Navigatable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -82,11 +84,21 @@ public abstract class Rule { /** * @return whether this rule is enabled by default + * + * @deprecated Use {@link #isEnabledByDefault(TextStyleDomain)} instead. */ + @Deprecated(forRemoval = true) public boolean isEnabledByDefault() { return true; } + /** + * @return whether this rule is enabled by default in the given domain + */ + public boolean isEnabledByDefault(TextStyleDomain domain) { + return true; + } + /** * @return an optional navigatable to open from "Rule X settings" quick fix. */ @@ -94,14 +106,15 @@ public abstract class Rule { return null; } - @SuppressWarnings("unused") - public final boolean isCurrentlyEnabled() { - return isEnabledInState(GrazieConfig.Companion.get()); + public final boolean isCurrentlyEnabled(TextContent content) { + GrazieConfig.State state = GrazieConfig.Companion.get(); + TextStyleDomain domain = TextUtilsKt.getTextDomain(content); + return isEnabledInState(state, domain); } - public final boolean isEnabledInState(GrazieConfig.State state) { - return isEnabledByDefault() ? !state.getUserDisabledRules().contains(globalId) - : state.getUserEnabledRules().contains(globalId); + public final boolean isEnabledInState(GrazieConfig.State state, TextStyleDomain domain) { + return isEnabledByDefault(domain) ? !state.isRuleDisabled(globalId, domain) + : state.isRuleEnabled(globalId, domain); } @Override diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/TreeRuleChecker.java b/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/TreeRuleChecker.java index 69a6ec55e31a..2831833fe262 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/TreeRuleChecker.java +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/TreeRuleChecker.java @@ -33,6 +33,7 @@ import com.intellij.grazie.style.TextLevelFix; import com.intellij.grazie.text.TextContent.TextDomain; import com.intellij.grazie.utils.HighlightingUtil; import com.intellij.grazie.utils.Text; +import com.intellij.grazie.utils.TextStyleDomain; import com.intellij.openapi.diagnostic.Attachment; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; @@ -61,6 +62,7 @@ import java.util.regex.Pattern; import static com.intellij.grazie.text.GrazieProblem.getQuickFixText; import static com.intellij.grazie.text.GrazieProblem.visualizeSpace; +import static com.intellij.grazie.utils.TextUtilsKt.getTextDomain; import static com.intellij.grazie.utils.UtilsKt.ijRange; @SuppressWarnings("NonAsciiCharacters") @@ -108,7 +110,7 @@ public final class TreeRuleChecker { public static final String SMART_APOSTROPHE = "Grazie.RuleEngine.En.Typography.SMART_APOSTROPHE"; public static List getRules(Language language) { - if (!StyleConfigurable.Companion.getRuleLanguages().contains(language) || SentenceBatcher.findInstalledLTLanguage(language) == null) { + if (!StyleConfigurable.getRuleEngineLanguages().contains(language) || SentenceBatcher.findInstalledLTLanguage(language) == null) { return List.of(); } @@ -161,8 +163,8 @@ public final class TreeRuleChecker { } @Override - public boolean isEnabledByDefault() { - return rule.isRuleEnabledByDefault(GrazieConfig.Companion.get().getTextStyle(), RuleIdeClient.INSTANCE); + public boolean isEnabledByDefault(TextStyleDomain domain) { + return rule.isRuleEnabledByDefault(GrazieConfig.Companion.get().getTextStyle(domain), RuleIdeClient.INSTANCE); } @SuppressWarnings("SuspiciousMethodCalls")//false negative in Qodana @@ -170,7 +172,7 @@ public final class TreeRuleChecker { public Navigatable editSettings() { RuleSetting setting = new RuleSetting(rule); LanguageToolkit toolkit = LanguageToolkit.forLanguage(rule.language()); - return StyleConfigurable.affectedSettings(toolkit).contains(setting) + return StyleConfigurable.featuredSettings(toolkit).contains(setting) ? StyleConfigurable.focusSetting(setting, null) : null; } @@ -210,7 +212,7 @@ public final class TreeRuleChecker { try { Cached cached = ref.get(); if (cached == null || !cached.sentences.equals(sentences)) { - List rules = enabledRules(sentences.getFirst().tree); + List rules = enabledRules(sentences.getFirst().tree, text); List matches = matchTrees(trees, rules); ref.set(cached = new Cached(sentences, matches)); } @@ -224,10 +226,10 @@ public final class TreeRuleChecker { } } - private static List enabledRules(Tree sampleTree) { + private static List enabledRules(Tree sampleTree, TextContent content) { Language language = sampleTree.treeSupport().getGrazieLanguage(); LanguageToolkit toolkit = LanguageToolkit.forLanguage(language); - List rules = ContainerUtil.filter(toolkit.publishedRules(), r -> toGrazieRule(r).isCurrentlyEnabled()); + List rules = ContainerUtil.filter(toolkit.publishedRules(), r -> toGrazieRule(r).isCurrentlyEnabled(content)); if (sampleTree.isFlat()) { return ContainerUtil.filter(rules, r -> r.supportsFlatTrees()); } @@ -249,8 +251,9 @@ public final class TreeRuleChecker { var parameters = new HashMap(); var ltLanguage = sentences.getFirst().tree.language(); Language language = sentences.getFirst().tree.treeSupport().getGrazieLanguage(); + TextContent content = sentences.getFirst().extractedText; LanguageToolkit toolkit = LanguageToolkit.forLanguage(language); - toolkit.allParameters(RuleIdeClient.INSTANCE).forEach(p -> parameters.put(p.id(), getParamValue(p, language))); + toolkit.allParameters(RuleIdeClient.INSTANCE).forEach(p -> parameters.put(p.id(), getParamValue(p, language, content))); if (language == Language.ENGLISH || language == Language.GERMAN) { String[] countries = ltLanguage.getCountries(); if (countries.length > 0) { @@ -264,10 +267,15 @@ public final class TreeRuleChecker { return new ParameterValues(parameters); } - private static @Nullable String getParamValue(Parameter param, Language language) { - String value = GrazieConfig.Companion.get().paramValue(language, param); + private static @Nullable String getParamValue(Parameter param, Language language, TextContent content) { + TextStyleDomain domain = getTextDomain(content); + String value = GrazieConfig.Companion.get().paramValue(domain, language, param); if (value == null || !ContainerUtil.exists(param.possibleValues(RuleIdeClient.INSTANCE), v -> value.equals(v.id()))) { - return param.defaultValue(GrazieConfig.Companion.get().getTextStyle(), RuleIdeClient.INSTANCE).id(); + TextStyle textStyle = TextStyle.styles(RuleIdeClient.INSTANCE).stream() + .filter(it -> it.id().equals(domain.name())) + .findFirst() + .orElseGet(() -> GrazieConfig.Companion.get().getTextStyle()); + return param.defaultValue(textStyle, RuleIdeClient.INSTANCE).id(); } return value; } @@ -355,8 +363,8 @@ public final class TreeRuleChecker { Map> rules = new LinkedHashMap<>(); for (SentenceWithContent ds : doc) { Language language = ds.sentence.language; - if (!rules.containsKey(language) && StyleConfigurable.Companion.getRuleLanguages().contains(language)) { - rules.put(language, enabledRules(ds.sentence.treeOrThrow())); + if (!rules.containsKey(language) && StyleConfigurable.getRuleEngineLanguages().contains(language)) { + rules.put(language, enabledRules(ds.sentence.treeOrThrow(), ds.content)); } } diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/utils/GrazieUtils.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/utils/GrazieUtils.kt index 298ccf3749fc..517efef94c63 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/utils/GrazieUtils.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/utils/GrazieUtils.kt @@ -2,10 +2,30 @@ package com.intellij.grazie.utils import ai.grazie.gec.model.problem.ProblemHighlighting import ai.grazie.nlp.langs.Language +import ai.grazie.rules.Rule +import ai.grazie.rules.settings.RuleSetting +import ai.grazie.rules.toolkit.LanguageToolkit import com.intellij.grazie.detection.LangDetector +import com.intellij.grazie.ide.ui.configurable.StyleConfigurable.Companion.featuredSettings +import com.intellij.grazie.ide.ui.configurable.StyleConfigurable.Companion.ruleEngineLanguages import com.intellij.openapi.util.TextRange +import com.intellij.util.containers.ContainerUtil.createConcurrentSoftValueMap import ai.grazie.text.TextRange as GrazieTextRange +private val affectedGlobalRules = createConcurrentSoftValueMap>() + +fun getAffectedGlobalRules(language: Language): Set { + if (language !in ruleEngineLanguages) return emptySet() + return affectedGlobalRules.computeIfAbsent(language) { + val toolkit = LanguageToolkit.forLanguage(language) + val prefix = Rule.globalIdPrefix(language) + featuredSettings(toolkit) + .filterIsInstance() + .map { prefix + it.rule.id } + .toHashSet() + } +} + fun getLanguageIfAvailable(text: String): Language? { return LangDetector.getLanguage(text)?.takeIf { HighlightingUtil.findInstalledLang(it) != null } } diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/utils/TextUtils.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/utils/TextUtils.kt index f8751e03f628..8e220db5d412 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/utils/TextUtils.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/utils/TextUtils.kt @@ -1,7 +1,13 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.grazie.utils +import ai.grazie.rules.RuleClient +import ai.grazie.rules.settings.TextStyle +import com.intellij.grazie.text.TextContent +import com.intellij.grazie.text.TextContent.TextDomain +import com.intellij.grazie.utils.TextStyleDomain.* import com.intellij.openapi.util.TextRange +import com.intellij.openapi.vcs.ui.CommitMessage import java.util.regex.Matcher import java.util.regex.Pattern @@ -79,7 +85,8 @@ object Text { inToken = true textTokens++ } - } else { + } + else { inToken = false if ("(){}[]<>=+-*/%|&!;,.:\\@$#^".contains(c)) { codeChars++ @@ -138,3 +145,40 @@ object Text { return TextRange(start, end) } } + +enum class TextStyleDomain { + Commit, AIPrompt, CodeDocumentation, CodeComment, Other; + + val textStyle: TextStyle + get() = when (this) { + Commit -> TextStyle.Commit + CodeComment -> TextStyle.CodeComment + CodeDocumentation -> TextStyle.CodeDocumentation + AIPrompt -> TextStyle.AIPrompt + else -> throw IllegalArgumentException("TextStyle can't be defined by '$this'") + } +} + +fun TextStyle.getTextDomain(): TextStyleDomain = TextStyleDomain.entries.find { id == it.name } ?: Other + +fun TextContent.getTextDomain(): TextStyleDomain { + val style = when (this.domain) { + TextDomain.COMMENTS -> CodeComment + TextDomain.DOCUMENTATION -> CodeDocumentation + else -> null + } + if (style != null) return style + val file = this.containingFile + if (CommitMessage.isCommitMessage(file)) return Commit + if ("ChatInput" == file.getLanguage().id) return AIPrompt + return Other +} + +fun getOtherDomainStyles(): List { + val client = object : RuleClient { + override fun showIdeStyles(): Boolean { + return false + } + } + return TextStyle.styles(client) +} \ No newline at end of file diff --git a/plugins/grazie/src/main/resources/messages/GrazieBundle.properties b/plugins/grazie/src/main/resources/messages/GrazieBundle.properties index 1b6f1c5bbdc8..cbd51426131b 100644 --- a/plugins/grazie/src/main/resources/messages/GrazieBundle.properties +++ b/plugins/grazie/src/main/resources/messages/GrazieBundle.properties @@ -106,10 +106,10 @@ grazie.settings.grammar.cloud.only.rule=This rule is only available in the Cloud grazie.settings.configurable.name=Processing grazie.settings.auto.apply.fixes.label=Automatically fix simple issues as you type (e.g., convert hyphens to dashes) grazie.settings.use.oxford.spelling.checkbox=Use Oxford Spelling for British English -grazie.settings.style.configurable.name=Writing Style +grazie.settings.writing.style.hint=Define how writing is checked in different contexts and languages. Each domain may have its own style and rules. grazie.settings.writing.style.text=Writing style: -grazie.settings.style.language.chooser.label=Featured rules for: -grazie.settings.style.configure.all.rules.link=Configure all rules +grazie.settings.writing.style.domain=Domain: +grazie.settings.language.chooser.label=Language: grazie.settings.style.configurable.example.prefix=Example: grazie.settings.style.configurable.corrected.prefix=Corrected: grazie.settings.style.configurable.reset.to.default.link=Reset to default @@ -118,6 +118,8 @@ grazie.settings.style.configurable.collapse.link=Collapse grazie.settings.style.configurable.expand.examples.link=Show examples grazie.settings.style.configurable.collapse.examples.link=Hide examples grazie.settings.style.search.placeholder=Find rules that match keywords or phrases +grazie.settings.style.rules.all=All rules +grazie.settings.style.rules.other=Other rules # suppress inspection "UnusedProperty" grazie.settings.style.profile.display.Informal=Casual (messengers, forums) # suppress inspection "UnusedProperty" @@ -128,6 +130,16 @@ grazie.settings.style.profile.display.Public=Public (blog posts, documentation) grazie.settings.style.profile.display.Formal=Formal (official communication) # suppress inspection "UnusedProperty" grazie.settings.style.profile.display.Academic=Academic +# suppress inspection "UnusedProperty" +grazie.settings.domain.profile.display.CodeComment=Code comments +# suppress inspection "UnusedProperty" +grazie.settings.domain.profile.display.Commit=Commit messages +# suppress inspection "UnusedProperty" +grazie.settings.domain.profile.display.CodeDocumentation=In-code documentation +# suppress inspection "UnusedProperty" +grazie.settings.domain.profile.display.AIPrompt=AI Chat +# suppress inspection "UnusedProperty" +grazie.settings.domain.profile.display.Other=Other # Dictionary Variables grazie.spellcheck.dictionary.name=Extended Dictionary diff --git a/plugins/grazie/src/test/kotlin/com/intellij/grazie/GrazieTestBase.kt b/plugins/grazie/src/test/kotlin/com/intellij/grazie/GrazieTestBase.kt index 363d7c916e54..bcd552b7ce6a 100644 --- a/plugins/grazie/src/test/kotlin/com/intellij/grazie/GrazieTestBase.kt +++ b/plugins/grazie/src/test/kotlin/com/intellij/grazie/GrazieTestBase.kt @@ -2,6 +2,7 @@ package com.intellij.grazie import ai.grazie.nlp.langs.LanguageISO +import ai.grazie.rules.settings.TextStyle import com.intellij.codeInspection.LocalInspectionTool import com.intellij.grazie.grammar.LanguageToolChecker import com.intellij.grazie.ide.inspection.grammar.GrazieInspection @@ -14,6 +15,8 @@ import com.intellij.grazie.text.TextChecker import com.intellij.grazie.text.TextContent import com.intellij.grazie.text.TextExtractor import com.intellij.grazie.text.TextProblem +import com.intellij.grazie.utils.TextStyleDomain +import com.intellij.grazie.utils.TextStyleDomain.* import com.intellij.grazie.utils.filterFor import com.intellij.lang.Language import com.intellij.openapi.Disposable @@ -129,7 +132,12 @@ abstract class GrazieTestBase : BasePlatformTestCase() { } } - protected fun enableProofreadingFor(languages: Set) { + @JvmOverloads + protected fun enableProofreadingFor( + languages: Set, + domainEnabledRules: MutableMap> = HashMap(), + domainDisabledRules: MutableMap> = HashMap(), + ) { // Load langs manually to prevent potential deadlock val enabledLanguages = languages + GrazieConfig.get().enabledLanguages loadLangs(enabledLanguages, project) @@ -141,10 +149,17 @@ abstract class GrazieTestBase : BasePlatformTestCase() { isCheckInDocumentationEnabled = true, enabledLanguages = additionalEnabledContextLanguages.map { it.id }.toSet(), ) + val domains = setOf(Commit, AIPrompt, CodeDocumentation, CodeComment) + if (domainEnabledRules.isEmpty()) { + domains.forEach { domainEnabledRules[it] = enabledRules + additionalEnabledRules } + } state.copy( enabledLanguages = enabledLanguages, userEnabledRules = enabledRules + additionalEnabledRules, - checkingContext = checkingContext + checkingContext = checkingContext, + styleProfile = TextStyle.Unspecified.id, + domainEnabledRules = domainEnabledRules, + domainDisabledRules = domainDisabledRules, ) } diff --git a/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/JavaSupportTest.kt b/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/JavaSupportTest.kt index ab17c674ba84..1d9a14726c01 100644 --- a/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/JavaSupportTest.kt +++ b/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/JavaSupportTest.kt @@ -1,8 +1,10 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.grazie.ide.language +import com.intellij.grazie.GrazieConfig import com.intellij.grazie.GrazieTestBase import com.intellij.grazie.jlanguage.Lang +import com.intellij.grazie.utils.TextStyleDomain import com.intellij.openapi.components.service import com.intellij.openapi.util.Disposer import com.intellij.spellchecker.ProjectDictionaryLayer @@ -19,7 +21,6 @@ import java.util.function.Consumer class JavaSupportTest : GrazieTestBase() { - override val additionalEnabledRules: Set = setOf("LanguageTool.EN.UPPERCASE_SENTENCE_START", "LanguageTool.EN.FILE_EXTENSIONS_CASE") override val enableGrazieChecker: Boolean = true override fun getProjectDescriptor(): LightProjectDescriptor { @@ -166,6 +167,9 @@ class JavaSupportTest : GrazieTestBase() { } fun `test no highlighting after fixing an error within the same range`() { + GrazieConfig.update { + it.withDomainEnabledRules(TextStyleDomain.CodeDocumentation, setOf("LanguageTool.EN.FILE_EXTENSIONS_CASE")) + } runHighlightTestForFile("ide/language/java/PDF.java") myFixture.launchAction(myFixture.findSingleIntention("PDF")) myFixture.checkHighlighting() diff --git a/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/MarkdownSupportTest.kt b/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/MarkdownSupportTest.kt index 35372e08caa8..3e13894dadc1 100644 --- a/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/MarkdownSupportTest.kt +++ b/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/MarkdownSupportTest.kt @@ -7,7 +7,6 @@ import com.intellij.grazie.jlanguage.Lang class MarkdownSupportTest : GrazieTestBase() { override val additionalEnabledRules: Set = setOf( - "LanguageTool.EN.UPPERCASE_SENTENCE_START", "LanguageTool.EN.COMMA_COMPOUND_SENTENCE", "LanguageTool.EN.EN_QUOTES" ) diff --git a/plugins/grazie/src/test/kotlin/com/intellij/grazie/text/TextStyleDomainTest.kt b/plugins/grazie/src/test/kotlin/com/intellij/grazie/text/TextStyleDomainTest.kt new file mode 100644 index 000000000000..4537a0c3c7b1 --- /dev/null +++ b/plugins/grazie/src/test/kotlin/com/intellij/grazie/text/TextStyleDomainTest.kt @@ -0,0 +1,31 @@ +package com.intellij.grazie.text + +import com.intellij.grazie.GrazieConfig +import com.intellij.grazie.GrazieTestBase +import com.intellij.grazie.utils.TextStyleDomain + +class TextStyleDomainTest: GrazieTestBase() { + + fun `test honor domain settings`() { + GrazieConfig.update { + it.copy(styleProfile = "Academic") + .withDomainDisabledRules(TextStyleDomain.CodeComment, setOf("LanguageTool.EN.EN_A_VS_AN")) + .withDomainDisabledRules(TextStyleDomain.CodeDocumentation, setOf("LanguageTool.EN.EN_A_VS_AN")) + } + myFixture.configureByText("C.java", """ + /** + * It is an cat of human + */ + class C { + // It is an friend of human + void foo(int x) {} + } + """.trimIndent()) + myFixture.checkHighlighting() + + myFixture.configureByText(".md", """ + It is an dog of human + """.trimIndent()) + myFixture.checkHighlighting() + } +} \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/grazie/PythonGrazieSupportTest.kt b/python/testSrc/com/jetbrains/python/grazie/PythonGrazieSupportTest.kt index 0920b8ecb119..7ef5e590290a 100644 --- a/python/testSrc/com/jetbrains/python/grazie/PythonGrazieSupportTest.kt +++ b/python/testSrc/com/jetbrains/python/grazie/PythonGrazieSupportTest.kt @@ -1,8 +1,10 @@ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.python.grazie +import com.intellij.grazie.GrazieConfig import com.intellij.grazie.GrazieTestBase import com.intellij.grazie.jlanguage.Lang +import com.intellij.grazie.utils.TextStyleDomain class PythonGrazieSupportTest : GrazieTestBase() { override fun getBasePath() = "python/testData/grazie/" @@ -27,6 +29,7 @@ class PythonGrazieSupportTest : GrazieTestBase() { // PY-53047 fun `test docstring tags are excluded`() { + GrazieConfig.update { it.withDomainEnabledRules(TextStyleDomain.CodeDocumentation, enabledRules) } runHighlightTestForFile("DocstringTagsAreExcluded.py") } }