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 8c3da5fe5278..388ed4846607 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 @@ -1,6 +1,7 @@ package com.intellij.grazie.grammar import com.intellij.grazie.GrazieBundle +import com.intellij.grazie.detection.toLanguage import com.intellij.grazie.ide.ui.components.utils.html import com.intellij.grazie.jlanguage.Lang import com.intellij.grazie.jlanguage.LangTool @@ -17,7 +18,7 @@ import java.util.* // ltRule used in ReSharper 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)) { +) : Rule(LangTool.globalIdPrefix(lang) + ltRule.id, lang.toLanguage(), ltRule.description, categories(ltRule, lang)) { override fun isEnabledByDefault(domain: TextStyleDomain): Boolean = LangTool.isRuleEnabledByDefault(lang, ltRule.id, domain) 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 c638a10a89c5..887d1da6f4ef 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 @@ -6,46 +6,44 @@ import com.intellij.codeInspection.IntentionAndQuickFixAction import com.intellij.grazie.GrazieConfig import com.intellij.grazie.ide.fus.GrazieFUSCounter import com.intellij.grazie.ide.ui.components.dsl.msg -import com.intellij.grazie.ide.ui.grammar.GrazieConfigurable +import com.intellij.grazie.ide.ui.configurable.StyleConfigurable import com.intellij.grazie.text.Rule +import com.intellij.grazie.utils.TextStyleDomain import com.intellij.icons.AllIcons import com.intellij.openapi.editor.Editor -import com.intellij.openapi.options.ShowSettingsUtil import com.intellij.openapi.project.Project import com.intellij.openapi.util.Iconable import com.intellij.psi.PsiFile import javax.swing.Icon -class GrazieRuleSettingsAction(private val ruleName: String, private val rule: Rule) +class GrazieRuleSettingsAction(private val rule: Rule, private val domain: TextStyleDomain) : IntentionAndQuickFixAction(), Iconable, CustomizableIntentionAction, Comparable { + override fun isShowSubmenu(): Boolean = false override fun getIcon(flags: Int): Icon = AllIcons.Actions.Edit - override fun getName() = msg("grazie.grammar.quickfix.open.rule.text", ruleName) + override fun getName(): String = msg(when (domain) { + TextStyleDomain.Other -> "grazie.grammar.quickfix.open.rule.text" + TextStyleDomain.Commit -> "grazie.grammar.quickfix.open.rule.text.commit" + TextStyleDomain.CodeComment -> "grazie.grammar.quickfix.open.rule.text.comments" + TextStyleDomain.CodeDocumentation -> "grazie.grammar.quickfix.open.rule.text.documentation" + TextStyleDomain.AIPrompt -> "grazie.grammar.quickfix.open.rule.text.ai" + }, rule.presentableName) override fun getFamilyName(): String = msg("grazie.grammar.quickfix.open.rule.family") - override fun startInWriteAction() = false + override fun startInWriteAction(): Boolean = false override fun compareTo(other: IntentionAction): Int { return if (other is GrazieAddExceptionQuickFix) 1 else 0 } override fun applyFix(project: Project, psiFile: PsiFile?, editor: Editor?) { - val state1 = GrazieConfig.get() - - val ok: Boolean - val navigatable = rule.editSettings() - if (navigatable != null && navigatable.canNavigate()) { - navigatable.navigate(true) - ok = true - } else { - ok = ShowSettingsUtil.getInstance().editConfigurable(project, GrazieConfigurable()) - } - - val result = if (!ok) "canceled" else analyzeStateChange(state1, GrazieConfig.get()) + val state = GrazieConfig.get() + val ok = StyleConfigurable.focusSetting(rule.featuredSetting, rule, domain, rule.language, project) + val result = if (!ok) "canceled" else analyzeStateChange(state, GrazieConfig.get()) GrazieFUSCounter.settingsUpdated("rule.settings:$result", rule, project) } 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 3166058a2e18..6b31323cd592 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 @@ -11,14 +11,12 @@ import ai.grazie.rules.toolkit.LanguageToolkit import com.intellij.grazie.GrazieBundle import com.intellij.grazie.GrazieConfig import com.intellij.grazie.detection.toLanguage +import com.intellij.grazie.ide.ui.components.dsl.msg 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.utils.TextStyleDomain -import com.intellij.grazie.utils.getAffectedGlobalRules -import com.intellij.grazie.utils.getOtherDomainStyles -import com.intellij.grazie.utils.getTextDomain +import com.intellij.grazie.utils.* import com.intellij.icons.AllIcons import com.intellij.ide.BrowserUtil import com.intellij.openapi.Disposable @@ -31,7 +29,6 @@ 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 import com.intellij.ui.* import com.intellij.ui.components.JBLabel @@ -55,7 +52,6 @@ import javax.swing.event.DocumentEvent import javax.swing.event.HyperlinkEvent class StyleConfigurable : BoundConfigurable(GrazieBundle.message("grazie.settings.grammar.tabs.rules"), null), Disposable, Configurable.NoScroll { - private var focusedControl: JComponent? = null private val settings: Settings = Settings() private val langComboModel = CollectionComboBoxModel(ArrayList()) private lateinit var langCombo: ComboBox @@ -72,7 +68,7 @@ class StyleConfigurable : BoundConfigurable(GrazieBundle.message("grazie.setting private val treeWrapper by lazy { JBSplitter(false, 0.45f).apply { - firstComponent = createScrollTreeComponent() + firstComponent = createScrollTreeComponent(textStyle, Language.ENGLISH) secondComponent = settings.getTreeSettings(textStyle, Language.ENGLISH).description.component } } @@ -118,7 +114,6 @@ class StyleConfigurable : BoundConfigurable(GrazieBundle.message("grazie.setting 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!!) } @@ -151,8 +146,7 @@ class StyleConfigurable : BoundConfigurable(GrazieBundle.message("grazie.setting langCombo = comboBox(langComboModel, SimpleListCellRenderer.create { label, lang, _ -> label.text = lang.nativeName }) .widthGroup("TopCombo") .whenItemSelectedFromUi { language -> - settings.addTextStyle(textStyle, language, filterComponent) - selectLanguage(textStyle, language) + selectTextStyle(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) @@ -190,11 +184,11 @@ class StyleConfigurable : BoundConfigurable(GrazieBundle.message("grazie.setting override fun createPanel(): DialogPanel = component - override fun isModified(): Boolean = super.isModified || settings.isModified() + override fun isModified(): Boolean = super.isModified || settings.isModified(GrazieConfig.get()) override fun apply() { super.apply() - settings.apply() + settings.apply(GrazieConfig.get()) } override fun reset() { @@ -207,7 +201,12 @@ class StyleConfigurable : BoundConfigurable(GrazieBundle.message("grazie.setting } private fun selectTextStyle(textStyle: TextStyle, language: Language) { + val domain = textStyle.getTextDomain() + if (domain != TextStyleDomain.Other) domainComboBox.selectedItem = domain else styleProfileCombo.selectedItem = textStyle + styleRowVisibleUpdater.invoke(domain == TextStyleDomain.Other) + settings.addTextStyle(textStyle, language, filterComponent) + langCombo.selectedItem = language repaintSettings(textStyle, language) } @@ -226,7 +225,8 @@ class StyleConfigurable : BoundConfigurable(GrazieBundle.message("grazie.setting settingWrapper.maximumSize = Dimension(Int.MAX_VALUE, settingWrapper.preferredSize.height) settingWrapper.repaint() } - treeWrapper.firstComponent = createScrollTreeComponent() + treeWrapper.removeAll() + treeWrapper.firstComponent = createScrollTreeComponent(textStyle, language) treeWrapper.secondComponent = settings.getTreeSettings(textStyle, language).description.component treeWrapper.repaint() } @@ -248,15 +248,11 @@ class StyleConfigurable : BoundConfigurable(GrazieBundle.message("grazie.setting return true } - override fun getDisplayName(): @NlsContexts.ConfigurableName String = "" + override fun getDisplayName(): @NlsContexts.ConfigurableName String = msg("grazie.settings.page.name") - override fun getPreferredFocusedComponent(): JComponent? { - return focusedControl ?: super.getPreferredFocusedComponent() - } - - private fun createScrollTreeComponent(): JScrollPane { + private fun createScrollTreeComponent(textStyle: TextStyle, language: Language): JScrollPane { return ScrollPaneFactory.createScrollPane( - settings.getTreeSettings(textStyle, Language.ENGLISH).tree, + settings.getTreeSettings(textStyle, language).tree, VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED ) @@ -268,37 +264,61 @@ class StyleConfigurable : BoundConfigurable(GrazieBundle.message("grazie.setting 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 } + fun focusSetting(setting: Setting, domain: TextStyleDomain, language: Language, project: Project): Boolean { + return focusSetting(setting, null, domain, language, project) + } @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 - } - } + fun focusSetting(setting: Setting?, rule: com.intellij.grazie.text.Rule?, domain: TextStyleDomain, language: Language, project: Project): Boolean { + require(setting != null || rule != null) { "Setting and Rule can't be null" } + val configurable = StyleConfigurable().apply { + createComponent() + val style = if (domain == TextStyleDomain.Other) GrazieConfig.get().getTextStyle() else domain.textStyle + selectTextStyle(style, language) + + if (setting != null && featuredSettings(language).contains(setting)) focusFeaturedSetting(this, setting, style, language, project) + else if (rule != null) focusTreeSetting(this, rule, style, language, project) + } + return ShowSettingsUtil.getInstance().editConfigurable(project, configurable) + } + } + + private fun focusFeaturedSetting(styleConfigurable: StyleConfigurable, setting: Setting, style: TextStyle, language: Language, project: Project) { + val data = settings.getFeaturedSettings(style, language) + if (data == null) return + styleConfigurable.apply { + val settingComponent = data.component.findParentSettingComponent(setting) + val paramComponent = data.component.findOwnSettingComponent(setting) + if (paramComponent != null && settingComponent != null) { + UiNotifyConnector.doWhenFirstShown(data.component) { + SwingUtilities.invokeLater { + val scrollPane = settingComponent.getParentOfType()!! + settingComponent.scrollRectToVisible(Rectangle(settingComponent.width, scrollPane.height)) + IdeFocusManager.getInstance(project).requestFocus(paramComponent, true) } } + } + } + } - override fun canNavigate() = true - override fun canNavigateToSource() = false + private fun focusTreeSetting(styleConfigurable: StyleConfigurable, rule: com.intellij.grazie.text.Rule, style: TextStyle, language: Language, project: Project) { + val data = settings.getTreeSettings(style, language) + styleConfigurable.apply { + data.tree.focusRule(rule) + UiNotifyConnector.doWhenFirstShown(data.tree) { + SwingUtilities.invokeLater { + val scroll = data.tree.getParentOfType() + val dataTreeScroll = scroll?.getParentOfType() + val componentScroll = dataTreeScroll?.getParentOfType() + if (dataTreeScroll == null || componentScroll == null) return@invokeLater + val destination = SwingUtilities.convertRectangle( + scroll, + Rectangle(0, 0, scroll.width, scroll.height), + dataTreeScroll.viewport.view + ) + componentScroll.scrollRectToVisible(destination) + IdeFocusManager.getInstance(project).requestFocus(data.tree, true) + } } } } @@ -306,19 +326,19 @@ class StyleConfigurable : BoundConfigurable(GrazieBundle.message("grazie.setting data class Settings( val featuredSettings: MutableMap> = HashMap(), - private val treeSettings: MutableMap> = HashMap(), + 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 isModified(state: GrazieConfig.State): Boolean = + featuredSettings.values.any { areModifiedFeaturedSettings(it) } || + treeSettings.values.any { areModifiedTreeSettings(it, state) } fun reset(state: GrazieConfig.State) { featuredSettings.forEach { (domain, featuredSettings) -> - if (isModifiedFeaturedSettings(featuredSettings)) { - featuredSettings.forEach { (language, settings) -> + if (areModifiedFeaturedSettings(featuredSettings)) { + featuredSettings.filter { areModifiedFeaturedSettings(it.value) }.forEach { (language, settings) -> val textStyle = getTextStyle(domain) settings.component.loadState(getSettingsState(language, textStyle), textStyle) settings.resetState = settings.component.state @@ -326,57 +346,63 @@ data class Settings( } } treeSettings.forEach { (_, treesSettings) -> - if (isModifiedTreeSettings(treesSettings)) { - treesSettings.forEach { (_, settings) -> settings.tree.reset(state) } + if (areModifiedTreeSettings(treesSettings, state)) { + treesSettings.filter { areModifiedTreeSettings(it.value, state) }.forEach { (_, settings) -> settings.tree.reset(state) } } } } - fun apply() { + fun apply(originalState: GrazieConfig.State) { treeSettings - .filter { (domainId, treeSettingsMap) -> isModifiedFeaturedSettings(featuredSettings[domainId]!!) || isModifiedTreeSettings(treeSettingsMap) } + .filter { (domainId, treeSettingsMap) -> areModifiedFeaturedSettings(featuredSettings[domainId]!!) || areModifiedTreeSettings(treeSettingsMap, originalState) } .forEach { (domainId, treeSettingsMap) -> val domain = getTextStyle(domainId).getTextDomain() val userEnabledRules = HashSet() val userDisabledRules = HashSet() val parameters = HashMap>() - featuredSettings[domainId]?.forEach { (language, settings) -> - val prefix = Rule.globalIdPrefix(language) - val settingsState = settings.component.state - settings.resetState = settingsState + treeSettingsMap.filter { areModifiedTreeSettings(it.value, originalState) }.forEach { (language, treeSettings) -> + val userEnabledRulesPerLanguage = HashSet() + val userDisabledRulesPerLanguage = HashSet() - for (id in settingsState.enabledRules) { - userEnabledRules.add(prefix + id) - userDisabledRules.remove(prefix + id) + featuredSettings[domainId]?.filter { areModifiedFeaturedSettings(it.value) }?.get(language)?.let { featuredSettings -> + val prefix = Rule.globalIdPrefix(language) + val settingsState = featuredSettings.component.state + featuredSettings.resetState = settingsState + + for (id in settingsState.enabledRules) { + userEnabledRulesPerLanguage.add(prefix + id) + userDisabledRulesPerLanguage.remove(prefix + id) + } + for (id in settingsState.disabledRules) { + userEnabledRulesPerLanguage.remove(prefix + id) + userDisabledRulesPerLanguage.add(prefix + id) + } + parameters[language] = settingsState.paramValues } - for (id in settingsState.disabledRules) { - userEnabledRules.remove(prefix + id) - userDisabledRules.add(prefix + id) + + val updatedState = treeSettings.tree.apply(originalState) + val affectedGlobalRules = getAffectedGlobalRules(language) + updatedState.getUserChangedRules(domain).let { (enabledRules, disabledRules) -> + userEnabledRulesPerLanguage.addAll(enabledRules - affectedGlobalRules) + userDisabledRulesPerLanguage.addAll(disabledRules - affectedGlobalRules) } - parameters[language] = settingsState.paramValues + userEnabledRules.addAll(userEnabledRulesPerLanguage) + userDisabledRules.addAll(userDisabledRulesPerLanguage) } 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() + val settings = treeSettings[textStyle.id] + if (settings != null && language in settings) return + if (featuredSettings[textStyle.id] == null) featuredSettings[textStyle.id] = HashMap() + if (treeSettings[textStyle.id] == null) treeSettings[textStyle.id] = HashMap() addLanguage(textStyle, language, filterComponent) } @@ -451,12 +477,20 @@ data class Settings( treeSettings.clear() } - private fun isModifiedFeaturedSettings(featuredSettings: MutableMap): Boolean { - return featuredSettings.any { it.value.component.state != it.value.resetState } + private fun areModifiedFeaturedSettings(featuredSettings: MutableMap): Boolean { + return featuredSettings.any { areModifiedFeaturedSettings(it.value) } } - private fun isModifiedTreeSettings(treeSettings: MutableMap): Boolean { - return treeSettings.any { it.value.tree.isModified(GrazieConfig.get()) } + private fun areModifiedTreeSettings(treeSettings: MutableMap, state: GrazieConfig.State): Boolean { + return treeSettings.any { areModifiedTreeSettings(it.value, state) } + } + + private fun areModifiedFeaturedSettings(featuredSettings: FeaturedSettings): Boolean { + return featuredSettings.component.state != featuredSettings.resetState + } + + private fun areModifiedTreeSettings(treeSettings: TreeSettings, state: GrazieConfig.State): Boolean { + return treeSettings.tree.isModified(state) } } 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 65a147499281..65f639e097f5 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 @@ -105,6 +105,13 @@ class GrazieTreeComponent( fun getCurrentFilterString(): String? = filter.text + fun focusRule(rule: Rule) { + val ruleNode = (model.root as GrazieRulesTreeNode).findRuleNode(rule.globalId) + if (ruleNode != null) { + TreeUtil.selectNode(this, ruleNode) + } + } + fun resetTreeModel(rules: List) { val root = GrazieRulesTreeNode(domain) val model = model as DefaultTreeModel diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/style/ConfigureSuggestedParameter.java b/plugins/grazie/src/main/kotlin/com/intellij/grazie/style/ConfigureSuggestedParameter.java index f5dc891a2b8c..a122f8e968a9 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/style/ConfigureSuggestedParameter.java +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/style/ConfigureSuggestedParameter.java @@ -1,14 +1,17 @@ package com.intellij.grazie.style; +import ai.grazie.nlp.langs.Language; import ai.grazie.rules.tree.Parameter; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.util.IntentionFamilyName; import com.intellij.grazie.ide.ui.configurable.StyleConfigurable; +import com.intellij.grazie.utils.TextStyleDomain; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; -public record ConfigureSuggestedParameter(Parameter parameter, String text) implements LocalQuickFix { +public record ConfigureSuggestedParameter(Parameter parameter, TextStyleDomain domain, Language language, String text) + implements LocalQuickFix { public ConfigureSuggestedParameter { assert !parameter.id().equals(Parameter.LANGUAGE_VARIANT); @@ -26,7 +29,7 @@ public record ConfigureSuggestedParameter(Parameter parameter, String text) impl @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - StyleConfigurable.focusSetting(parameter, project).navigate(true); + StyleConfigurable.focusSetting(parameter, domain, language, project); //todo FUS? } } diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/CheckerRunner.kt b/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/CheckerRunner.kt index 9414faad4a8e..7a8c4aaefa35 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/CheckerRunner.kt +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/CheckerRunner.kt @@ -20,6 +20,7 @@ import com.intellij.grazie.ide.inspection.grammar.quickfix.GrazieCustomFixWrappe import com.intellij.grazie.ide.inspection.grammar.quickfix.GrazieReplaceTypoQuickFix import com.intellij.grazie.ide.inspection.grammar.quickfix.GrazieRuleSettingsAction import com.intellij.grazie.ide.language.LanguageGrammarChecking +import com.intellij.grazie.utils.getTextDomain import com.intellij.openapi.components.service import com.intellij.openapi.editor.Editor import com.intellij.openapi.progress.ProgressManager @@ -233,7 +234,7 @@ class CheckerRunner(val text: TextContent) { super.applyFix(project, psiFile, editor) } }) - result.add(GrazieRuleSettingsAction(problem.rule.presentableName, problem.rule)) + result.add(GrazieRuleSettingsAction(problem.rule, problem.text.getTextDomain())) return result.toTypedArray() } 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 4ad93bb10c67..c58d44b6864f 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,9 +1,10 @@ package com.intellij.grazie.text; +import ai.grazie.nlp.langs.Language; +import ai.grazie.rules.settings.Setting; 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; @@ -14,15 +15,16 @@ import java.util.Objects; /** A representation of rules from various {@link TextChecker}s. */ public abstract class Rule { private final String globalId; + private final Language language; private final String presentableName; private final List categories; /** * Create a rule in a single category - * @see #Rule(String, String, List) + * @see #Rule(String, Language, String, List) */ - public Rule(String globalId, String presentableName, String category) { - this(globalId, presentableName, List.of(category)); + public Rule(String globalId, Language language, String presentableName, String category) { + this(globalId, language, presentableName, List.of(category)); } /** @@ -34,8 +36,9 @@ public abstract class Rule { * @param categories a non-empty list of the presentable names of the rule's categories. * It's used to group the rules in the settings, starting from the language node. */ - public Rule(String globalId, String presentableName, List categories) { + public Rule(String globalId, Language language, String presentableName, List categories) { this.globalId = globalId; + this.language = language; this.presentableName = presentableName; this.categories = List.copyOf(categories); if (categories.isEmpty()) { @@ -54,6 +57,13 @@ public abstract class Rule { return presentableName; } + /** + * @return the language that the rule is applicable to + */ + public Language getLanguage() { + return language; + } + /** * @return a text/HTML description of what the rule does, to be displayed in settings */ @@ -100,9 +110,9 @@ public abstract class Rule { } /** - * @return an optional navigatable to open from "Rule X settings" quick fix. + * @return an optional featured rule setting to open from "Rule X settings" quick fix. */ - public @Nullable Navigatable editSettings() { + public @Nullable Setting getFeaturedSetting() { return null; } 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 3bdc2237563a..44eeee658065 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 @@ -11,6 +11,7 @@ import ai.grazie.rules.document.Delimiter; import ai.grazie.rules.document.DocumentRule; import ai.grazie.rules.document.DocumentSentence; import ai.grazie.rules.settings.RuleSetting; +import ai.grazie.rules.settings.Setting; import ai.grazie.rules.settings.TextStyle; import ai.grazie.rules.toolkit.LanguageToolkit; import ai.grazie.rules.tree.ActionSuggestion; @@ -35,15 +36,12 @@ 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.grazie.utils.TextUtilsKt; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Attachment; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vcs.ui.CommitMessage; -import com.intellij.pom.Navigatable; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiPlainTextFile; import com.intellij.psi.util.CachedValueProvider; @@ -134,7 +132,7 @@ public final class TreeRuleChecker { List categories = rule.isStyleLike() ? List.of(styleCategories.get(langCode)) : List.of(category); - return new Rule(id, rule.displayName, categories.getFirst()) { + return new Rule(id, rule.language(), rule.displayName, categories.getFirst()) { @Override public List getCategories() { @@ -170,14 +168,9 @@ public final class TreeRuleChecker { return rule.isRuleEnabledByDefault(GrazieConfig.Companion.get().getTextStyle(domain), RuleIdeClient.INSTANCE); } - @SuppressWarnings("SuspiciousMethodCalls")//false negative in Qodana @Override - public Navigatable editSettings() { - RuleSetting setting = new RuleSetting(rule); - LanguageToolkit toolkit = LanguageToolkit.forLanguage(rule.language()); - return StyleConfigurable.featuredSettings(toolkit).contains(setting) - ? StyleConfigurable.focusSetting(setting, null) - : null; + public Setting getFeaturedSetting() { + return new RuleSetting(rule); } @Override @@ -540,6 +533,7 @@ public final class TreeRuleChecker { public static class TreeProblem extends GrazieProblem { public final RuleMatch match; private final List customFixes; + private final TextStyleDomain domain; TreeProblem(Problem problem, RuleMatch match, TextContent text) { this(problem, toGrazieRule(match.rule()), text, match, List.of()); @@ -551,6 +545,7 @@ public final class TreeRuleChecker { super(problem, ideaRule, text); this.match = match; this.customFixes = customFixes; + this.domain = getTextDomain(text); } @Override @@ -567,7 +562,7 @@ public final class TreeRuleChecker { if (parameter.id().equals(Parameter.LANGUAGE_VARIANT)) { return ChangeLanguageVariant.create(match.rule().language(), Objects.requireNonNull(suggestedValue), quickFixText); } - return new ConfigureSuggestedParameter(parameter, quickFixText); + return new ConfigureSuggestedParameter(parameter, domain, match.rule().language(), quickFixText); } if (sug == ActionSuggestion.REPHRASE) { return new RephraseAction(); @@ -612,24 +607,6 @@ public final class TreeRuleChecker { public boolean shouldSuppressInCodeLikeFragments() { return match.rule().shouldSuppressInCodeLikeFragments(); } - - private record MySuggestion(ProblemFix fix, TextContent text) implements TextProblem.Suggestion { - - @Override - public List getChanges() { - return ContainerUtil.map(fix.getChanges(), r -> StringOperation.replace(ijRange(r), r.getText())); - } - - @Override - public String getPresentableText() { - return getQuickFixText(fix); - } - - @Override - public @Nullable String getBatchId() { - return fix.getBatchId(); - } - } } public static class DocProblemFilter extends ProblemFilter { 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 517efef94c63..2d61fddd0c95 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 @@ -4,10 +4,11 @@ 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.settings.Setting 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.grazie.rule.RuleIdeClient import com.intellij.openapi.util.TextRange import com.intellij.util.containers.ContainerUtil.createConcurrentSoftValueMap import ai.grazie.text.TextRange as GrazieTextRange @@ -17,15 +18,24 @@ private val affectedGlobalRules = createConcurrentSoftValueMap { if (language !in ruleEngineLanguages) return emptySet() return affectedGlobalRules.computeIfAbsent(language) { - val toolkit = LanguageToolkit.forLanguage(language) val prefix = Rule.globalIdPrefix(language) - featuredSettings(toolkit) + featuredSettings(language) .filterIsInstance() .map { prefix + it.rule.id } .toHashSet() } } +fun featuredSettings(language: Language): List = + LanguageToolkit.forLanguage(language).getSettings(RuleIdeClient.INSTANCE) + .flatMap { it.settings() } + .flatMap { setting -> + when (setting) { + is RuleSetting -> listOf(setting) + setting.subSettings + else -> listOf(setting) + } + } + fun getLanguageIfAvailable(text: String): Language? { return LangDetector.getLanguage(text)?.takeIf { HighlightingUtil.findInstalledLang(it) != null } } diff --git a/plugins/grazie/src/main/resources/messages/GrazieBundle.properties b/plugins/grazie/src/main/resources/messages/GrazieBundle.properties index cbd51426131b..17aaf724567e 100644 --- a/plugins/grazie/src/main/resources/messages/GrazieBundle.properties +++ b/plugins/grazie/src/main/resources/messages/GrazieBundle.properties @@ -31,6 +31,10 @@ grazie.grammar.inspection.grammar.text=Grammar grazie.grammar.quickfix.open.rule.family=Configure rule grazie.grammar.quickfix.open.rule.text=Configure rule ''{0}''\u2026 +grazie.grammar.quickfix.open.rule.text.commit=Configure rule ''{0}'' in commit messages\u2026 +grazie.grammar.quickfix.open.rule.text.documentation=Configure rule ''{0}'' in documentation\u2026 +grazie.grammar.quickfix.open.rule.text.comments=Configure rule ''{0}'' in comments\u2026 +grazie.grammar.quickfix.open.rule.text.ai=Configure rule ''{0}'' in AI prompts\u2026 grazie.grammar.quickfix.ignore.family=Add exception grazie.grammar.quickfix.ignore.text.no.context=Ignore ''{0}'' diff --git a/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/CheckerRunnerTest.kt b/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/CheckerRunnerTest.kt index 4044367495bf..a1c59ef69c9d 100644 --- a/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/CheckerRunnerTest.kt +++ b/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/CheckerRunnerTest.kt @@ -1,5 +1,6 @@ package com.intellij.grazie.ide.language +import ai.grazie.nlp.langs.Language import com.intellij.grazie.text.* import com.intellij.openapi.application.runReadAction import com.intellij.openapi.extensions.ExtensionPointName @@ -110,7 +111,7 @@ class CheckerRunnerTest: BasePlatformTestCase() { } private fun createFakeProblem(text: TextContent, range: TextRange): TextProblem { - val rule = object: Rule("fake.global.id", "Fake rule", "Fake category") { + val rule = object : Rule("fake.global.id", Language.UNKNOWN, "Fake rule", "Fake category") { override fun getDescription(): String { return "Fake rule description" } diff --git a/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/CommitSupportTest.kt b/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/CommitSupportTest.kt index c767ed1573c5..c7139d77d4f1 100644 --- a/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/CommitSupportTest.kt +++ b/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/CommitSupportTest.kt @@ -25,7 +25,7 @@ class CommitSupportTest : GrazieTestBase() { myFixture.findSingleIntention("Wrong article") myFixture.findSingleIntention("an") myFixture.findSingleIntention("Ignore 'a error'") - myFixture.findSingleIntention("Configure rule 'Use of 'a' vs. 'an''…") + myFixture.findSingleIntention("Configure rule 'Use of 'a' vs. 'an'' in commit messages…") } } diff --git a/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/ReportingTest.kt b/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/ReportingTest.kt index 134cadb72f05..ba6bcce3da2b 100644 --- a/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/ReportingTest.kt +++ b/plugins/grazie/src/test/kotlin/com/intellij/grazie/ide/language/ReportingTest.kt @@ -1,5 +1,6 @@ package com.intellij.grazie.ide.language +import ai.grazie.nlp.langs.Language import com.intellij.codeInsight.CodeInsightBundle import com.intellij.codeInsight.daemon.impl.actions.IntentionActionWithFixAllOption import com.intellij.codeInsight.intention.CustomizableIntentionAction @@ -180,7 +181,7 @@ class ReportingTest : BasePlatformTestCase() { range: TextRange, suggestions: List, customFixes: List): TextProblem { - val rule = object : Rule("something.something", "something", "something") { + val rule = object : Rule("something.something", Language.UNKNOWN, "something", "something") { override fun getDescription() = "something" } return object : TextProblem(rule, text, range) {