Skip to content

Commit 43e22fc

Browse files
author
builduser
committed
Merged branch idea251.release into idea251.x
2 parents 7de9c9c + 457f53c commit 43e22fc

File tree

7 files changed

+413
-11
lines changed

7 files changed

+413
-11
lines changed

scala/compiler-integration/src/org/jetbrains/plugins/scala/compiler/actions/internal/ScalaCollectShortTroubleshootingInfoAction.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import java.awt.datatransfer.StringSelection
1919
import java.text.SimpleDateFormat
2020

2121
/**
22-
* See also [[org.jetbrains.plugins.scala.internal.ScalaGeneralTroubleInfoCollector]]
22+
* @see [[org.jetbrains.plugins.scala.internal.ScalaGeneralTroubleInfoCollector]]
23+
* @see [[org.jetbrains.plugins.scala.internal.ScalaPluginAboutPopupDescriptionProvider]]
2324
*/
2425
class ScalaCollectShortTroubleshootingInfoAction extends AnAction(
2526
CompilerIntegrationBundle.message("scala.collect.troubleshooting.information.short.action.text"),
Binary file not shown.

scala/scala-impl/resources/META-INF/scala-plugin-common.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2021,6 +2021,7 @@
20212021
<iconMapper mappingFile="ScalaIconMappings.json" />
20222022

20232023
<generalTroubleInfoCollector implementation="org.jetbrains.plugins.scala.internal.ScalaGeneralTroubleInfoCollector"/>
2024+
<aboutPopupDescriptionProvider implementation="org.jetbrains.plugins.scala.internal.ScalaPluginAboutPopupDescriptionProvider"/>
20242025

20252026
<psi.treeChangeListener implementation="org.jetbrains.plugins.scala.lang.psi.impl.ScalaPsiChangeListener" />
20262027

scala/scala-impl/src/org/jetbrains/plugins/scala/internal/ScalaGeneralTroubleInfoCollector.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import scala.util.control.NonFatal
1818
* SBT version: 1.9.3
1919
* }}}
2020
*
21-
* See also [[org.jetbrains.plugins.scala.compiler.actions.internal.ScalaCollectShortTroubleshootingInfoAction]]
21+
* @see [[org.jetbrains.plugins.scala.compiler.actions.internal.ScalaCollectShortTroubleshootingInfoAction]]
22+
* @see [[org.jetbrains.plugins.scala.internal.ScalaPluginAboutPopupDescriptionProvider]]
2223
*/
2324
final class ScalaGeneralTroubleInfoCollector extends GeneralTroubleInfoCollector {
2425

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
package org.jetbrains.plugins.scala.internal
2+
3+
import com.intellij.ide.AboutPopupDescriptionProvider
4+
import com.intellij.ide.impl.ProjectUtil
5+
import com.intellij.openapi.application.ApplicationManager
6+
import com.intellij.openapi.project.{Project, ProjectManager}
7+
import com.intellij.psi.codeStyle.CodeStyleSettings
8+
import org.jetbrains.annotations.ApiStatus.Internal
9+
import org.jetbrains.plugins.scala.internal.ScalaPluginAboutPopupDescriptionProvider._
10+
import org.jetbrains.plugins.scala.lang.formatting.settings.ScalaCodeStyleSettings
11+
import org.jetbrains.plugins.scala.project.ProjectExt
12+
import org.jetbrains.plugins.scala.project.settings.ScalaCompilerConfiguration
13+
import org.jetbrains.plugins.scala.settings.{ScalaApplicationSettings, ScalaCompileServerSettings, ScalaProjectSettings}
14+
import org.jetbrains.sbt.project.settings.SbtProjectSettings
15+
16+
/**
17+
* Adds additional details to display in the `Help | About` dialog (and add to "Copy and Close" action)
18+
*
19+
* The current implementation adds some Application and Project level settings when they are different from the default values.
20+
*
21+
* @note It doesn't handle all the settings exhaustively.
22+
* Ideally, there should be a unified mechanism in IntelliJ to collect all non-default settings.
23+
* However, it's missing at the moment (see IJPL-163081)
24+
* @note there is also a separate action [[org.jetbrains.plugins.scala.internal.ScalaGeneralTroubleInfoCollector]]
25+
* but it's rarely used compared to "About".
26+
*/
27+
@Internal
28+
class ScalaPluginAboutPopupDescriptionProvider extends AboutPopupDescriptionProvider {
29+
30+
override def getDescription: String = null
31+
32+
/**
33+
* Details that are added to the content of the "Copy and Close" action in the "About" dialog.
34+
*
35+
* Example output: {{{
36+
* Scala plugin:
37+
* === group name 1 ===
38+
* a.b.c=true
39+
* d.e.g=false
40+
* === group name 2 ===
41+
* x.y.z=true
42+
* }}}
43+
*/
44+
override def getExtendedDescription: String = {
45+
val ideDetails = collectApplicationLevelDetails()
46+
47+
val activeProject = if (ApplicationManager.getApplication.isUnitTestMode)
48+
ProjectManager.getInstance().getOpenProjects.find(p => !p.isDisposed)
49+
else
50+
Option(ProjectUtil.getActiveProject)
51+
val activeProjectDetails = activeProject.toSeq.flatMap(collectProjectLevelDetails)
52+
53+
val allDetails = ideDetails ++ activeProjectDetails
54+
val hasNonDefaultSettings = allDetails.exists(_.nonDefaultSettings.nonEmpty)
55+
if (hasNonDefaultSettings) {
56+
val detailsText = buildGroupsText(allDetails)
57+
s"""Scala plugin:
58+
|${detailsText.indented(1)}""".stripMargin
59+
}
60+
else null
61+
}
62+
63+
private def collectApplicationLevelDetails(): Seq[NonDefaultSettingsGroup] = {
64+
val compileServerSettings = ScalaCompileServerSettings.getInstance
65+
val compileServerSettingsMappings = buildSettingsMappings(compileServerSettings, DefaultSettings.ScalaCompileServerSettings)(
66+
SettingLabels.CompileServerEnabled -> (_.COMPILE_SERVER_ENABLED),
67+
SettingLabels.UseProjectHomeAsWorkingDir -> (_.USE_PROJECT_HOME_AS_WORKING_DIR),
68+
)
69+
70+
val scalaApplicationSettings = ScalaApplicationSettings.getInstance
71+
val smartKeysSettingsMappings = buildSettingsMappings(scalaApplicationSettings, DefaultSettings.SmartKeysSettings)(
72+
SettingLabels.IndentPastedLinesAtCaret -> (_.INDENT_PASTED_LINES_AT_CARET),
73+
SettingLabels.InsertMultilineQuotes -> (_.INSERT_MULTILINE_QUOTES),
74+
SettingLabels.UpgradeToInterpolated -> (_.UPGRADE_TO_INTERPOLATED),
75+
SettingLabels.WrapSingleExpressionBody -> (_.WRAP_SINGLE_EXPRESSION_BODY),
76+
SettingLabels.DeleteClosingBrace -> (_.DELETE_CLOSING_BRACE),
77+
SettingLabels.HandleBlockBracesInsertionAutomatically -> (_.HANDLE_BLOCK_BRACES_INSERTION_AUTOMATICALLY),
78+
SettingLabels.HandleBlockBracesRemovalAutomatically -> (_.HANDLE_BLOCK_BRACES_REMOVAL_AUTOMATICALLY),
79+
)
80+
81+
val compileServerSettingsGroup = collectNonDefaultSettingsGroup(SettingGroupNames.CompileServerSettings, compileServerSettingsMappings)
82+
val smartKeysSettingsGroup = collectNonDefaultSettingsGroup(SettingGroupNames.SmartKeysSettings, smartKeysSettingsMappings)
83+
Seq(
84+
compileServerSettingsGroup,
85+
smartKeysSettingsGroup
86+
)
87+
}
88+
89+
private def collectProjectLevelDetails(project: Project): Seq[NonDefaultSettingsGroup] = {
90+
// Do not add Scala specific information if there is no Scala SDK in the project
91+
if (!project.hasScala)
92+
return Nil
93+
94+
val sbtProjectSettings = SbtProjectSettings.forProject(project)
95+
val sbtSettingsMappings: Seq[SettingsMapping[_, _]] = sbtProjectSettings.toSeq.flatMap { settings =>
96+
buildSettingsMappings(settings, DefaultSettings.SbtProjectSettings)(
97+
SettingLabels.ResolveClassifiers -> (_.resolveClassifiers),
98+
SettingLabels.ResolveSbtClassifiers -> (_.resolveSbtClassifiers),
99+
100+
SettingLabels.SeparateProdAndTestSources -> (_.separateProdAndTestSources),
101+
SettingLabels.UseSeparateCompilerOutputPaths -> (_.useSeparateCompilerOutputPaths),
102+
SettingLabels.OpenCrossCompiledScala3AsScala2 -> (_.preferScala2),
103+
104+
SettingLabels.UseSbtShellForImport -> (_.useSbtShellForImport),
105+
SettingLabels.UseSbtShellForBuild -> (_.useSbtShellForBuild),
106+
SettingLabels.EnableDebugSbtShell -> (_.enableDebugSbtShell),
107+
)
108+
}
109+
110+
val scalaProjectSettings = ScalaProjectSettings.getInstance(project)
111+
val scalaProjectSettingsMappings: Seq[SettingsMapping[_, _]] =
112+
buildSettingsMappings(scalaProjectSettings, DefaultSettings.ScalaProjectSettings)(
113+
SettingLabels.CompilerHighlightingScala2 -> (_.isCompilerHighlightingScala2),
114+
SettingLabels.CompilerHighlightingScala3 -> (_.isCompilerHighlightingScala3),
115+
SettingLabels.CompilerHighlightingUseCompilerRanges -> (_.isUseCompilerRanges),
116+
SettingLabels.CompilerHighlightingUseCompilerTypes -> (_.isUseCompilerTypes),
117+
SettingLabels.TypeAwareHighlighting -> (_.isTypeAwareHighlightingEnabled),
118+
SettingLabels.IncrementalHighlighting -> (_.isIncrementalHighlighting),
119+
)
120+
121+
val scalaCompilerSettingsMappings: Seq[SettingsMapping[_, _]] =
122+
buildSettingsMappings(ScalaCompilerConfiguration.instanceIn(project), DefaultSettings.ScalaCompilerSettings)(
123+
SettingLabels.IncrementalityType -> (_.incrementalityType),
124+
)
125+
126+
val scalaCodeStyleSettings = ScalaCodeStyleSettings.getInstance(project)
127+
val formatterSettingsMappings = collectFormatterSettings(scalaCodeStyleSettings)
128+
129+
val scalaSettingsGroup = collectNonDefaultSettingsGroup(SettingGroupNames.ScalaSettingsForActiveProject, scalaProjectSettingsMappings)
130+
val sbtSettingsGroup = collectNonDefaultSettingsGroup(SettingGroupNames.SbtSettingsForActiveProject, sbtSettingsMappings)
131+
val scalaCompilerSettingsGroup = collectNonDefaultSettingsGroup(SettingGroupNames.ScalaCompilerSettings, scalaCompilerSettingsMappings)
132+
val formatterSettingsGroup = collectNonDefaultSettingsGroup(SettingGroupNames.FormatterSettings, formatterSettingsMappings)
133+
134+
Seq(
135+
scalaSettingsGroup,
136+
sbtSettingsGroup,
137+
scalaCompilerSettingsGroup,
138+
formatterSettingsGroup,
139+
)
140+
}
141+
142+
private def buildGroupsText(groups: Seq[NonDefaultSettingsGroup]): String = {
143+
val groupsTexts = groups.filter(_.nonDefaultSettings.nonEmpty).map(buildGroupText)
144+
groupsTexts.mkString("\n")
145+
}
146+
147+
private def buildGroupText(group: NonDefaultSettingsGroup): String = {
148+
val groupInnerText = group.nonDefaultSettings.map(buildSettingValueTxt).mkString("\n")
149+
s"""=== ${group.groupName} ===
150+
|${groupInnerText.indented(1)}""".stripMargin
151+
}
152+
153+
private def buildSettingValueTxt(settingValue: SettingValue): String =
154+
s"${settingValue.label}=${settingValue.value}"
155+
156+
private implicit class StringOps(private val text: String) {
157+
def indented(level: Int): String = {
158+
val indent = " " * level
159+
text.linesIterator.map(indent + _).mkString("\n")
160+
}
161+
}
162+
}
163+
164+
object ScalaPluginAboutPopupDescriptionProvider {
165+
166+
/**
167+
* This class represents a setting that is potentially displayed in the "About" extended description.
168+
* It represents the label used to display the setting, the setting value, it's default value
169+
* and also how to display the value in the description
170+
*
171+
* @param label the label that will be represented the setting in the extended description
172+
* @param settings instance that represents current settings
173+
* @param defaultSettings instance that represents the default settings
174+
* @param accessor function that returns value of the setting from the setting instance
175+
* @param renderer function that returns string representation of the setting value that is shown in the extended description.<br>
176+
* NOTE: It uses `toString` method by default. This should be enough for simple cases
177+
* (primitive types, enums) but would be not OK if we add settings with other types
178+
* @tparam T type of the settings
179+
*/
180+
private case class SettingsMapping[T, V](
181+
label: String,
182+
settings: T,
183+
defaultSettings: T,
184+
accessor: T => V,
185+
renderer: V => String = (v: V) => v.toString
186+
)
187+
188+
private case class NonDefaultSettingsGroup(groupName: String, nonDefaultSettings: Seq[SettingValue])
189+
/**
190+
* @param label setting label as it is displayed in the help details
191+
* @param value current setting value string representation
192+
*/
193+
private case class SettingValue(label: String, value: String)
194+
195+
private def collectFormatterSettings(scalaCodeStyleSettings: ScalaCodeStyleSettings): Seq[SettingsMapping[_, _]] = {
196+
// Create a custom mapping for formatter type, use the string representation instead of Int (ScalaCodeStyleSettings.FORMATTER)
197+
def renderFormatterType(formatterType: Int): String = formatterType match {
198+
case ScalaCodeStyleSettings.SCALAFMT_FORMATTER => "scalafmt"
199+
case ScalaCodeStyleSettings.INTELLIJ_FORMATTER => "intellij"
200+
case _ => s"<unsupported formatter type: $formatterType>" // this branch is unexpected in reality
201+
}
202+
203+
val formatterSettingsMappings = Seq(SettingsMapping[ScalaCodeStyleSettings, Int](
204+
SettingLabels.Formatter, scalaCodeStyleSettings, DefaultSettings.ScalaCodeStyleSettings, _.FORMATTER, renderer = renderFormatterType
205+
))
206+
207+
// If ScalaFmt is enabled, check for non-default scalafmt-specific settings
208+
val scalafmtSpecificSettingsMappings = if (scalaCodeStyleSettings.USE_SCALAFMT_FORMATTER())
209+
buildSettingsMappings(scalaCodeStyleSettings, DefaultSettings.ScalaCodeStyleSettings)(
210+
SettingLabels.ScalafmtShowInvalidCodeWarnings -> (_.SCALAFMT_SHOW_INVALID_CODE_WARNINGS),
211+
SettingLabels.ScalafmtUseIntellijFormatterForRangeFormat -> (_.SCALAFMT_USE_INTELLIJ_FORMATTER_FOR_RANGE_FORMAT),
212+
SettingLabels.ScalafmtReformatOnFilesSave -> (_.SCALAFMT_REFORMAT_ON_FILES_SAVE),
213+
SettingLabels.ScalafmtFallbackToDefaultSettings -> (_.SCALAFMT_FALLBACK_TO_DEFAULT_SETTINGS),
214+
)
215+
else Nil
216+
formatterSettingsMappings ++ scalafmtSpecificSettingsMappings
217+
}
218+
219+
private def buildSettingsMappings[T](settings: T, defaultSettings: T)(items: (String, T => Any)*): Seq[SettingsMapping[T, Any]] = {
220+
items.map { case (label, accessor) =>
221+
SettingsMapping(label, settings, defaultSettings, accessor)
222+
}
223+
}
224+
225+
private def collectNonDefaultSettingsGroup(groupName: String, mappings: Seq[SettingsMapping[_, _]]): NonDefaultSettingsGroup = {
226+
val nonDefaultLabels = collectNonDefaultSettingsLabels(mappings)
227+
NonDefaultSettingsGroup(groupName, nonDefaultLabels)
228+
}
229+
230+
/**
231+
* @return list of items in format `setting.label=non-default-value`
232+
* for all settings that are different from the default value
233+
*/
234+
private def collectNonDefaultSettingsLabels(mappings: Seq[SettingsMapping[_, _]]): Seq[SettingValue] =
235+
mappings.flatMap { case SettingsMapping(label, settings, defaultSettings, accessor, renderer) =>
236+
val currentValue = accessor(settings)
237+
val defaultValue = accessor(defaultSettings)
238+
if (currentValue != defaultValue) {
239+
val currentValueStr = renderer(currentValue)
240+
Some(SettingValue(label, currentValueStr))
241+
}
242+
else
243+
None
244+
}
245+
246+
/**
247+
* Contains labels of the settings in the way how they are presented in the "About" extended description.
248+
*
249+
* Note: In theory, we could reuse some other existing representation of the strings. For example from FUS
250+
* (using field names, access via reflection) or directly from the UI (reading i18 bundle for English version).
251+
* But I did not want to reuse any existing string literals intentionally for several reasons:
252+
* 1. I use dots to be "visually-unified" with registry values in the "About" details
253+
* 2. Simplicity of first implementation: not relying on other modules with message bundles (e.t. sbt-api or compiler)
254+
*/
255+
private object SettingLabels {
256+
//compiler-server settings (Settings | Build, Execution, Deployment | Compiler | Scala Compiler | Scala Compile Server)
257+
val CompileServerEnabled = "compile.server.enabled"
258+
val UseProjectHomeAsWorkingDir = "use.project.home.as.working.dir"
259+
260+
//sbt project settings (Settings | Build, Execution, Deployment | Build Tools | sbt)
261+
val OpenCrossCompiledScala3AsScala2 = "open.cross.compiled.scala3.as.scala2"
262+
val SeparateProdAndTestSources = "separate.prod.and.test.sources"
263+
val UseSeparateCompilerOutputPaths = "use.separate.compiler.output.paths"
264+
val ResolveClassifiers = "resolve.classifiers"
265+
val ResolveSbtClassifiers = "resolve.sbt.classifiers"
266+
val UseSbtShellForImport = "use.sbt.shell.for.import"
267+
val UseSbtShellForBuild = "use.sbt.shell.for.build"
268+
val EnableDebugSbtShell = "enable.debug.sbt.shell"
269+
270+
//scala project settings (Settings | Languages & Frameworks | Scala | Editor)
271+
val TypeAwareHighlighting = "type.aware.highlighting.enabled"
272+
val CompilerHighlightingScala2 = "compiler.highlighting.scala2.enabled"
273+
val CompilerHighlightingScala3 = "compiler.highlighting.scala3.enabled"
274+
val CompilerHighlightingUseCompilerRanges = "compiler.highlighting.use.compiler.ranges"
275+
val CompilerHighlightingUseCompilerTypes = "compiler.highlighting.use.compiler.types"
276+
val IncrementalHighlighting = "incremental.highlighting.enabled"
277+
278+
//scala compiler settings (Settings | Build, Execution, Deployment | Compiler | Scala Compiler)
279+
val IncrementalityType = "incrementality.type"
280+
281+
//formatter settings (Settings | Editor | Code Style | Scala)
282+
val Formatter = "formatter"
283+
val ScalafmtShowInvalidCodeWarnings = "scalafmt.show.invalid.code.warnings"
284+
val ScalafmtUseIntellijFormatterForRangeFormat = "scalafmt.use.intellij.formatter.for.range.format"
285+
val ScalafmtReformatOnFilesSave = "scalafmt.reformat.on.files.save"
286+
val ScalafmtFallbackToDefaultSettings = "scalafmt.fallback.to.default.settings"
287+
288+
//smart keys settings (Settings | Editor | General | Smart Keys | Scala)
289+
val IndentPastedLinesAtCaret = "indent.pasted.lines.at.caret"
290+
val InsertMultilineQuotes = "insert.multiline.quotes"
291+
val UpgradeToInterpolated = "upgrade.to.interpolated"
292+
val WrapSingleExpressionBody = "wrap.single.expression.body"
293+
val DeleteClosingBrace = "delete.closing.brace"
294+
val HandleBlockBracesInsertionAutomatically = "handle.block.braces.insertion.automatically"
295+
val HandleBlockBracesRemovalAutomatically = "handle.block.braces.removal.automatically"
296+
}
297+
298+
private object SettingGroupNames {
299+
val CompileServerSettings = "compile server settings"
300+
val SmartKeysSettings = "smart keys settings"
301+
val ScalaCompilerSettings = "compiler settings for active project"
302+
val SbtSettingsForActiveProject = "sbt settings for active project"
303+
val ScalaSettingsForActiveProject = "scala settings for active project"
304+
val FormatterSettings = "formatter settings for active project"
305+
}
306+
307+
//noinspection TypeAnnotation
308+
private object DefaultSettings {
309+
val ScalaCompileServerSettings = new org.jetbrains.plugins.scala.settings.ScalaCompileServerSettings
310+
val ScalaCompilerSettings = new org.jetbrains.plugins.scala.project.settings.ScalaCompilerConfiguration(null)
311+
val SbtProjectSettings = new org.jetbrains.sbt.project.settings.SbtProjectSettings
312+
val ScalaProjectSettings = new org.jetbrains.plugins.scala.settings.ScalaProjectSettings(null)
313+
val ScalaCodeStyleSettings = CodeStyleSettings.getDefaults.getCustomSettings(classOf[ScalaCodeStyleSettings])
314+
val SmartKeysSettings = new org.jetbrains.plugins.scala.settings.ScalaApplicationSettings
315+
}
316+
}

0 commit comments

Comments
 (0)