mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[grazie] IJPL-201886 Add per-domain writing style settings to Grammar and Style | Rules
Merge-request: IJ-MR-173802 Merged-by: Ilia Permiashkin <ilia.permiashkin@jetbrains.com> GitOrigin-RevId: beea0ca1c4e93543b3126384c96b42d068516ac8
This commit is contained in:
committed by
intellij-monorepo-bot
parent
cd5ef01926
commit
32bc57f455
@@ -41,13 +41,6 @@
|
||||
</content>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<applicationConfigurable
|
||||
parentId="proofread"
|
||||
instance="com.intellij.grazie.ide.ui.configurable.StyleConfigurable"
|
||||
key="grazie.settings.style.configurable.name"
|
||||
bundle="messages.GrazieBundle"
|
||||
id="reference.settings.grazie"/>
|
||||
|
||||
<projectConfigurable parentId="proofread" instance="com.intellij.grazie.spellcheck.settings.SpellCheckerSettingsManager"
|
||||
id="reference.settings.ide.settings.spelling"
|
||||
key="spelling"
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.intellij.grazie.jlanguage.LangTool
|
||||
import com.intellij.grazie.remote.GrazieRemote.isAvailableLocally
|
||||
import com.intellij.grazie.rule.RuleIdeClient
|
||||
import com.intellij.grazie.text.Rule
|
||||
import com.intellij.grazie.utils.TextStyleDomain
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.components.*
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
@@ -51,8 +52,8 @@ class GrazieConfig : PersistentStateComponent<GrazieConfig.State>, 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<GrazieConfig.State>, ModificationT
|
||||
@Deprecated("Moved to checkingContext in version 2") @ApiStatus.ScheduledForRemoval @Property val enabledCommitIntegration: Boolean = false,
|
||||
@Property val userDisabledRules: Set<String> = HashSet(),
|
||||
@Property val userEnabledRules: Set<String> = HashSet(),
|
||||
@Property val domainDisabledRules: Map<TextStyleDomain, Set<String>> = TreeMap(),
|
||||
@Property val domainEnabledRules: Map<TextStyleDomain, Set<String>> = 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<GrazieConfig.State>, ModificationT
|
||||
//Ex. Grazie pro properties
|
||||
@Property val styleProfile: String? = TextStyle.Unspecified.id,
|
||||
@Property val parameters: Map<Language, Map<String, String>> = TreeMap(),
|
||||
@Property val parametersPerDomain: Map<TextStyleDomain, Map<Language, Map<String, String>>> = TreeMap(),
|
||||
@Property val useOxfordSpelling: Boolean = false,
|
||||
@Property val autoFix: Boolean = false,
|
||||
) : VersionedState<Version, State> {
|
||||
@@ -104,7 +108,7 @@ class GrazieConfig : PersistentStateComponent<GrazieConfig.State>, ModificationT
|
||||
val missedLanguages: Set<Lang>
|
||||
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<GrazieConfig.State>, 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<String>()
|
||||
val userDisabledRules = HashSet<String>()
|
||||
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<String>, userDisabledRules: Set<String>): 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<String>): State {
|
||||
val newRules = TreeMap(domainEnabledRules)
|
||||
newRules[domain] = rules
|
||||
return copy(domainEnabledRules = newRules)
|
||||
}
|
||||
|
||||
fun withDomainDisabledRules(domain: TextStyleDomain, rules: Set<String>): 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<GrazieConfig.State>, ModificationT
|
||||
@VisibleForTesting
|
||||
fun migrateLTRuleIds(state: State): State {
|
||||
val ltRules: List<Rule> 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<String>): Set<String> =
|
||||
@@ -179,7 +253,7 @@ class GrazieConfig : PersistentStateComponent<GrazieConfig.State>, ModificationT
|
||||
*
|
||||
* Should never be called in GrazieStateLifecycle actions
|
||||
*/
|
||||
fun get() = service<GrazieConfig>().state
|
||||
fun get(): State = service<GrazieConfig>().state
|
||||
|
||||
/** Update Grazie config state */
|
||||
@Synchronized
|
||||
@@ -194,6 +268,8 @@ class GrazieConfig : PersistentStateComponent<GrazieConfig.State>, ModificationT
|
||||
}
|
||||
}
|
||||
|
||||
data class UserChangedRules(val enabled: Set<String>, val disabled: Set<String>)
|
||||
|
||||
class PresentableNameGetter : com.intellij.openapi.components.State.NameGetter() {
|
||||
override fun get() = GrazieBundle.message("grazie.config.name")
|
||||
}
|
||||
@@ -203,7 +279,7 @@ class GrazieConfig : PersistentStateComponent<GrazieConfig.State>, 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<GrazieConfig.State>, 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Rule> {
|
||||
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<Problem> {
|
||||
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()
|
||||
|
||||
@@ -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<org.languagetool.rules.Rule> = 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()
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
+1
-3
@@ -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())
|
||||
|
||||
-7
@@ -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
|
||||
|
||||
+385
-216
@@ -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<Language, LangData>()
|
||||
private val settings: Settings = Settings()
|
||||
private val langComboModel = CollectionComboBoxModel(ArrayList<Language>())
|
||||
private lateinit var langCombo: ComboBox<Language>
|
||||
|
||||
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<TextStyleDomain>
|
||||
private lateinit var styleProfileCombo: ComboBox<TextStyle>
|
||||
private lateinit var langCombo: ComboBox<Language>
|
||||
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<BoundConfigurable>.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<Language> = listOf(Language.ENGLISH, Language.GERMAN, Language.RUSSIAN, Language.UKRAINIAN)
|
||||
|
||||
@JvmStatic
|
||||
fun featuredSettings(toolkit: LanguageToolkit): List<Setting> = 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<JBScrollPane>()!!
|
||||
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<String, MutableMap<Language, FeaturedSettings>> = HashMap(),
|
||||
private val treeSettings: MutableMap<String, MutableMap<Language, TreeSettings>> = 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<String>()
|
||||
val userDisabledRules = HashSet<String>()
|
||||
val parameters = HashMap<Language, Map<String, String>>()
|
||||
|
||||
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<RuleSetting>()
|
||||
.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<Language, FeaturedSettings>): 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<Language, TreeSettings>): Boolean {
|
||||
return treeSettings.any { it.value.tree.isModified(GrazieConfig.get()) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun isModified(): Boolean {
|
||||
return super<BoundConfigurable>.isModified() ||
|
||||
langData.values.any { it.component.state != it.resetState }
|
||||
}
|
||||
data class FeaturedSettings(
|
||||
val component: SettingComponent,
|
||||
var resetState: SettingComponent.SettingState,
|
||||
)
|
||||
|
||||
override fun reset() {
|
||||
super<BoundConfigurable>.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<BoundConfigurable>.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<Setting> = 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<JBScrollPane>()!!
|
||||
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<String>
|
||||
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 <T> ComboBox<T>.selected: T?
|
||||
get() = this.selectedItem as? T
|
||||
+1
-37
@@ -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<GrazieSettingsPanel, GrazieConfig>(
|
||||
"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<GrazieConfig>()
|
||||
|
||||
override fun createUi(): GrazieSettingsPanel = ui
|
||||
|
||||
override fun getPreferredFocusedComponent(): JComponent? {
|
||||
if (ui.component.selectedComponent == ui.rules.component) {
|
||||
return ui.rules.impl
|
||||
}
|
||||
|
||||
return super<ConfigurableBase>.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<BaseExtensionPointName<*>> {
|
||||
return setOf(LanguageGrammarChecking.EP_NAME,
|
||||
ExtensionPointName("com.intellij.grazie.textExtractor"),
|
||||
|
||||
+10
-9
@@ -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<GrazieConfig>, 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<GrazieConfig>, Disposable {
|
||||
|
||||
override fun getComponent(): JComponent = component
|
||||
|
||||
override fun dispose() = rules.dispose()
|
||||
|
||||
override fun dispose() {
|
||||
Disposer.dispose(rules)
|
||||
}
|
||||
}
|
||||
|
||||
-46
@@ -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<GrazieInitializerManager>().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)
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -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 -> ""
|
||||
|
||||
+63
-53
@@ -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<String>()
|
||||
private val enabledRules = hashSetOf<String>()
|
||||
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<Lang, List<Rule>>) {
|
||||
val root = GrazieRulesTreeNode()
|
||||
fun resetTreeModel(rules: List<Rule>) {
|
||||
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<Rule>, 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<Rule>, 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<Lang, List<Rul
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
fun allRules(lang: Lang, state: GrazieConfig.State = GrazieConfig.get()): List<Rule> {
|
||||
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()
|
||||
}
|
||||
|
||||
+27
-18
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+7
-3
@@ -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
|
||||
|
||||
@@ -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<Lang, JLanguageTool> = Collections.synchronizedMap(ContainerUtil.createSoftValueMap())
|
||||
private val rulesEnabledByDefault = ConcurrentHashMap<Lang, Set<String>>()
|
||||
private val langs: MutableMap<Lang, MutableMap<TextStyleDomain, JLanguageTool>> = ConcurrentHashMap()
|
||||
private val rulesEnabledByDefault = ConcurrentHashMap<Lang, MutableMap<TextStyleDomain, Set<String>>>()
|
||||
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<String>, Set<String>> {
|
||||
val prefix = globalIdPrefix(lang)
|
||||
val enabledRules = HashSet<String>()
|
||||
val disabledRules = HashSet<String>()
|
||||
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()
|
||||
|
||||
@@ -34,4 +34,9 @@ public class RuleIdeClient implements RuleClient {
|
||||
public boolean hasLocalMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showIdeStyles() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)) }
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Rule> 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<ai.grazie.rules.Rule> rules = enabledRules(sentences.getFirst().tree);
|
||||
List<ai.grazie.rules.Rule> rules = enabledRules(sentences.getFirst().tree, text);
|
||||
List<MatchingResult> matches = matchTrees(trees, rules);
|
||||
ref.set(cached = new Cached(sentences, matches));
|
||||
}
|
||||
@@ -224,10 +226,10 @@ public final class TreeRuleChecker {
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ai.grazie.rules.Rule> enabledRules(Tree sampleTree) {
|
||||
private static List<ai.grazie.rules.Rule> enabledRules(Tree sampleTree, TextContent content) {
|
||||
Language language = sampleTree.treeSupport().getGrazieLanguage();
|
||||
LanguageToolkit toolkit = LanguageToolkit.forLanguage(language);
|
||||
List<ai.grazie.rules.Rule> rules = ContainerUtil.filter(toolkit.publishedRules(), r -> toGrazieRule(r).isCurrentlyEnabled());
|
||||
List<ai.grazie.rules.Rule> 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<String, String>();
|
||||
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<Language, List<ai.grazie.rules.Rule>> 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Language, Set<String>>()
|
||||
|
||||
fun getAffectedGlobalRules(language: Language): Set<String> {
|
||||
if (language !in ruleEngineLanguages) return emptySet()
|
||||
return affectedGlobalRules.computeIfAbsent(language) {
|
||||
val toolkit = LanguageToolkit.forLanguage(language)
|
||||
val prefix = Rule.globalIdPrefix(language)
|
||||
featuredSettings(toolkit)
|
||||
.filterIsInstance<RuleSetting>()
|
||||
.map { prefix + it.rule.id }
|
||||
.toHashSet()
|
||||
}
|
||||
}
|
||||
|
||||
fun getLanguageIfAvailable(text: String): Language? {
|
||||
return LangDetector.getLanguage(text)?.takeIf { HighlightingUtil.findInstalledLang(it) != null }
|
||||
}
|
||||
|
||||
@@ -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<TextStyle> {
|
||||
val client = object : RuleClient {
|
||||
override fun showIdeStyles(): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return TextStyle.styles(client)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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<Lang>) {
|
||||
@JvmOverloads
|
||||
protected fun enableProofreadingFor(
|
||||
languages: Set<Lang>,
|
||||
domainEnabledRules: MutableMap<TextStyleDomain, Set<String>> = HashMap(),
|
||||
domainDisabledRules: MutableMap<TextStyleDomain, Set<String>> = 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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String> = 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()
|
||||
|
||||
@@ -7,7 +7,6 @@ import com.intellij.grazie.jlanguage.Lang
|
||||
|
||||
class MarkdownSupportTest : GrazieTestBase() {
|
||||
override val additionalEnabledRules: Set<String> = setOf(
|
||||
"LanguageTool.EN.UPPERCASE_SENTENCE_START",
|
||||
"LanguageTool.EN.COMMA_COMPOUND_SENTENCE",
|
||||
"LanguageTool.EN.EN_QUOTES"
|
||||
)
|
||||
|
||||
@@ -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 <GRAMMAR_ERROR descr="EN_A_VS_AN">an</GRAMMAR_ERROR> dog of human
|
||||
""".trimIndent())
|
||||
myFixture.checkHighlighting()
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user