diff --git a/clion/src/main/kotlin/org/rust/clion/cargo/CargoBuildConfigurationProvider.kt b/clion/src/main/kotlin/org/rust/clion/cargo/CargoBuildConfigurationProvider.kt index 9f29079f34e..d96d8c32f03 100644 --- a/clion/src/main/kotlin/org/rust/clion/cargo/CargoBuildConfigurationProvider.kt +++ b/clion/src/main/kotlin/org/rust/clion/cargo/CargoBuildConfigurationProvider.kt @@ -5,16 +5,11 @@ package org.rust.clion.cargo -import com.intellij.execution.ExecutorRegistry import com.intellij.execution.RunManager -import com.intellij.execution.executors.DefaultRunExecutor import com.intellij.execution.impl.RunManagerImpl -import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl -import com.intellij.execution.runners.ExecutionEnvironment -import com.intellij.execution.runners.ProgramRunner import com.intellij.openapi.project.Project import com.jetbrains.cidr.cpp.execution.build.CLionBuildConfigurationProvider -import org.rust.cargo.runconfig.CargoCommandRunner +import org.rust.cargo.runconfig.buildtool.CargoBuildManager.createBuildEnvironment import org.rust.cargo.runconfig.buildtool.CargoBuildManager.getBuildConfiguration import org.rust.cargo.runconfig.command.CargoCommandConfiguration @@ -24,10 +19,7 @@ class CargoBuildConfigurationProvider : CLionBuildConfigurationProvider { val configuration = runManager.selectedConfiguration?.configuration as? CargoCommandConfiguration ?: return emptyList() val buildConfiguration = getBuildConfiguration(configuration) ?: return emptyList() - val executor = ExecutorRegistry.getInstance().getExecutorById(DefaultRunExecutor.EXECUTOR_ID) - val runner = ProgramRunner.findRunnerById(CargoCommandRunner.RUNNER_ID) ?: return emptyList() - val settings = RunnerAndConfigurationSettingsImpl(runManager, buildConfiguration) - val environment = ExecutionEnvironment(executor, runner, settings, project) + val environment = createBuildEnvironment(buildConfiguration) ?: return emptyList() return listOf(CLionCargoBuildConfiguration(buildConfiguration, environment)) } } diff --git a/src/191/main/kotlin/org/rust/cargo/runconfig/buildtool/Utils.kt b/src/191/main/kotlin/org/rust/cargo/runconfig/buildtool/UtilsExtra.kt similarity index 69% rename from src/191/main/kotlin/org/rust/cargo/runconfig/buildtool/Utils.kt rename to src/191/main/kotlin/org/rust/cargo/runconfig/buildtool/UtilsExtra.kt index 56a3e081dae..0f2d133e669 100644 --- a/src/191/main/kotlin/org/rust/cargo/runconfig/buildtool/Utils.kt +++ b/src/191/main/kotlin/org/rust/cargo/runconfig/buildtool/UtilsExtra.kt @@ -11,6 +11,9 @@ import com.intellij.build.BuildProgressListener import com.intellij.build.events.BuildEvent import com.intellij.build.output.BuildOutputInstantReaderImpl import com.intellij.build.output.BuildOutputParser +import com.intellij.execution.ExecutionListener +import com.intellij.execution.ExecutionManager +import com.intellij.execution.process.ProcessHandler import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.openapi.progress.EmptyProgressIndicator import com.intellij.openapi.util.Key @@ -29,13 +32,6 @@ fun BuildOutputInstantReaderImpl.closeAndGetFuture(): CompletableFuture = fun BuildProgressListener.onEvent(parentEventId: Any, event: BuildEvent) = onEvent(event) -typealias CargoPatch = (CargoCommandLine) -> CargoCommandLine - -val ExecutionEnvironment.cargoPatches: MutableList - get() = putUserDataIfAbsent(CARGO_PATCHES, mutableListOf()) - -private val CARGO_PATCHES: Key> = Key.create("CARGO.PATCHES") - object EmptyBuildProgressListener : BuildProgressListener { override fun onEvent(event: BuildEvent) = Unit } @@ -49,18 +45,3 @@ class MockBuildProgressListener : BuildProgressListener { _eventHistory.add(event) } } - -class MockProgressIndicator : EmptyProgressIndicator() { - private val _textHistory: MutableList = mutableListOf() - val textHistory: List get() = _textHistory - - override fun setText(text: String?) { - super.setText(text) - _textHistory += text - } - - override fun setText2(text: String?) { - super.setText2(text) - _textHistory += text - } -} diff --git a/src/191/main/kotlin/org/rust/ide/hints/RsPlainTypeHint.kt b/src/191/main/kotlin/org/rust/ide/hints/RsPlainTypeHint.kt new file mode 100644 index 00000000000..b1ba89235a2 --- /dev/null +++ b/src/191/main/kotlin/org/rust/ide/hints/RsPlainTypeHint.kt @@ -0,0 +1,88 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +package org.rust.ide.hints + +import com.intellij.codeInsight.hints.InlayInfo +import com.intellij.codeInsight.hints.Option +import com.intellij.psi.PsiElement +import org.rust.ide.presentation.shortPresentableText +import org.rust.lang.core.psi.* +import org.rust.lang.core.psi.ext.descendantsOfType +import org.rust.lang.core.psi.ext.endOffset +import org.rust.lang.core.psi.ext.patList +import org.rust.lang.core.psi.ext.valueParameters +import org.rust.lang.core.types.declaration +import org.rust.lang.core.types.ty.TyUnknown +import org.rust.lang.core.types.type + +enum class RsPlainTypeHint(desc: String, enabled: Boolean) : RsPlainHint { + LET_BINDING_HINT("Show local variable type hints", true) { + override fun provideHints(elem: PsiElement): List { + val (expr, pats) = when (elem) { + is RsLetDecl -> { + if (elem.typeReference != null) return emptyList() + elem.expr to listOfNotNull(elem.pat) + } + is RsCondition -> elem.expr to elem.patList + is RsMatchExpr -> elem.expr to elem.matchBody?.matchArmList?.flatMap { it.patList } + else -> return emptyList() + } + + var finalPats = pats.orEmpty() + if (smart) { + val declaration = expr?.declaration + if (declaration is RsStructItem || declaration is RsEnumVariant) { + finalPats = finalPats.filter { it !is RsPatIdent } + } + } + return finalPats.flatMap { it.inlayInfo } + } + + override fun isApplicable(elem: PsiElement): Boolean = elem is RsLetDecl || elem is RsCondition || elem is RsMatchExpr + }, + LAMBDA_PARAMETER_HINT("Show lambda parameter type hints", true) { + override fun provideHints(elem: PsiElement): List { + val element = elem as? RsLambdaExpr ?: return emptyList() + return element.valueParameters + .filter { it.typeReference == null } + .mapNotNull { it.pat } + .flatMap { it.inlayInfo } + } + + override fun isApplicable(elem: PsiElement): Boolean = elem is RsLambdaExpr + }, + FOR_PARAMETER_HINT("Show type hints for for loops parameter", true) { + override fun provideHints(elem: PsiElement): List { + val element = elem as? RsForExpr ?: return emptyList() + return element.pat?.inlayInfo ?: emptyList() + } + + override fun isApplicable(elem: PsiElement): Boolean = elem is RsForExpr + }; + + val smart get() = RsPlainHint.SMART_HINTING.get() + override val enabled get() = option.get() + override val option = Option("SHOW_${this.name}", desc, enabled) +} + +private val RsPat.inlayInfo: List + get() = descendantsOfType().asSequence() + .filter { patBinding -> + when { + // "ignored" bindings like `let _a = 123;` + patBinding.referenceName.startsWith("_") -> false + // match foo { + // None -> {} // None is enum variant, so we don't want to provide type hint for it + // _ -> {} + // } + patBinding.reference.resolve() is RsEnumVariant -> false + else -> true + } + } + .map { it to it.type } + .filterNot { (_, type) -> type is TyUnknown } + .map { (patBinding, type) -> InlayInfo(": " + type.shortPresentableText, patBinding.endOffset) } + .toList() diff --git a/src/191/test/kotlin/org/rust/ide/hints/RsPlainInlayTypeHintsProviderTest.kt b/src/191/test/kotlin/org/rust/ide/hints/RsPlainInlayTypeHintsProviderTest.kt new file mode 100644 index 00000000000..6658f50ac8f --- /dev/null +++ b/src/191/test/kotlin/org/rust/ide/hints/RsPlainInlayTypeHintsProviderTest.kt @@ -0,0 +1,260 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +package org.rust.ide.hints + +import com.intellij.openapi.vfs.VirtualFileFilter +import org.rust.ProjectDescriptor +import org.rust.WithStdlibRustProjectDescriptor +import org.rust.fileTreeFromText +import org.rust.lang.core.psi.RsMethodCall + +class RsPlainInlayTypeHintsProviderTest : RsPlainInlayHintsProviderTestBase() { + fun `test let decl`() = checkByText(""" + struct S; + fn main() { + let s/*hint text=": S"*/ = S; + } + """, enabledHints = RsPlainTypeHint.LET_BINDING_HINT, smart = false) + + fun `test let stmt without expression`() = checkByText(""" + struct S; + fn main() { + let s/*hint text=": S"*/; + s = S; + } + """, enabledHints = RsPlainTypeHint.LET_BINDING_HINT, smart = false) + + fun `test no redundant hints`() = checkByText(""" + fn main() { + let _ = 1; + let _a = 1; + let a = UnknownType; + } + """, enabledHints = RsPlainTypeHint.LET_BINDING_HINT, smart = false) + + fun `test smart hint don't show redundant hints`() = checkByText(""" + struct S; + struct TupleStruct(f32); + struct BracedStruct { f: f32 } + enum E { + C, B { f: f32 }, T(f32) + } + + fn main() { + let no_hint = S; + let no_hint = TupleStruct(1.0); + let no_hint = BracedStruct { f: 1.0 }; + let no_hint = E::C; + let no_hint = E::B { f: 1.0 }; + let no_hint = E::T(1.0); + } + """, enabledHints = RsPlainTypeHint.LET_BINDING_HINT) + + fun `test let decl tuple`() = checkByText(""" + struct S; + fn main() { + let (s/*hint text=": S"*/, c/*hint text=": S"*/) = (S, S); + } + """, enabledHints = RsPlainTypeHint.LET_BINDING_HINT) + + fun `test pat field`() = checkByText(""" + struct S; + struct TupleStruct(S); + struct BracedStruct { a: S, b: S } + fn main() { + let TupleStruct(x/*hint text=": S"*/) = TupleStruct(S); + let BracedStruct { a: a/*hint text=": S"*/, b/*hint text=": S"*/ } = BracedStruct { a: S, b: S }; + } + """, enabledHints = RsPlainTypeHint.LET_BINDING_HINT, smart = false) + + fun `test smart should not annotate tuples`() = checkByText(""" + enum Option { + Some(T), + None + } + fn main() { + let s = Option::Some(10); + } + """, enabledHints = RsPlainTypeHint.LET_BINDING_HINT) + + + private val fnTypes = """ + #[lang = "fn_once"] + trait FnOnce { type Output; } + + #[lang = "fn_mut"] + trait FnMut: FnOnce { } + + #[lang = "fn"] + trait Fn: FnMut { } + """ + + fun `test lambda type hint`() = checkByText(""" + $fnTypes + struct S; + fn with_s(f: F) {} + fn main() { + with_s(|s/*hint text=": S"*/| s.bar()) + } + """, enabledHints = RsPlainTypeHint.LAMBDA_PARAMETER_HINT) + + fun `test lambda type not shown if redundant`() = checkByText(""" + $fnTypes + struct S; + fn with_s(f: F) {} + fn main() { + with_s(|s: S| s.bar()) + with_s(|_| ()) + } + """, enabledHints = RsPlainTypeHint.LAMBDA_PARAMETER_HINT) + + fun `test lambda type should show after an defined type correct`() = checkByText(""" + $fnTypes + struct S; + fn foo ()>(action: T) {} + fn main() { + foo(|x/*hint text=": S"*/, y: S, z/*hint text=": (S, S)"*/| {}); + } + """, enabledHints = RsPlainTypeHint.LAMBDA_PARAMETER_HINT) + + fun `test don't render horrendous types in their full glory`() = checkByText(""" + struct S; + + impl S { + fn wrap(self, f: F) -> S { + unimplemented!() + } + } + + fn main() { + let s: S<(), ()> = unimplemented!(); + let foo/*hint text=": S i32, S i32, S<…, …>>>"*/ = s + .wrap(|x: i32| x) + .wrap(|x: i32| x) + .wrap(|x: i32| x) + .wrap(|x: i32| x); + } + """, enabledHints = RsPlainTypeHint.LET_BINDING_HINT) + + @ProjectDescriptor(WithStdlibRustProjectDescriptor::class) + fun `test inlay hint for loops`() = checkByText(""" + struct S; + struct I; + impl Iterator for I { + type Item = S; + fn next(&mut self) -> Option { None } + } + + fn main() { + for s/*hint text=": S"*/ in I { } + } + """, enabledHints = RsPlainTypeHint.FOR_PARAMETER_HINT) + + fun `test don't touch ast`() { + fileTreeFromText(""" + //- main.rs + mod foo; + use foo::Foo; + + fn main() { + Foo.bar(92) + } //^ + //- foo.rs + struct Foo; + impl Foo { fn bar(&self, x: i32) {} } + """).createAndOpenFileWithCaretMarker() + + val handler = RsInlayParameterHintsProvider() + val target = findElementInEditor("^") + checkAstNotLoaded(VirtualFileFilter.ALL) + val inlays = handler.getParameterHints(target) + check(inlays.size == 1) + } + + fun `test hints in if let expr`() = checkByText(""" + enum Option { + Some(T), None + } + fn main() { + let result = Option::Some((1, 2)); + if let Option::Some((x/*hint text=": i32"*/, y/*hint text=": i32"*/)) = result {} + } + """, enabledHints = RsPlainTypeHint.LET_BINDING_HINT) + + fun `test hints in if let expr with multiple patterns`() = checkByText(""" + enum V { + V1(T), V2(T) + } + fn main() { + let result = V::V1((1, 2)); + if let V::V1(x/*hint text=": (i32, i32)"*/) | V::V2(x/*hint text=": (i32, i32)"*/) = result {} + } + """, enabledHints = RsPlainTypeHint.LET_BINDING_HINT) + + fun `test hints in while let expr`() = checkByText(""" + enum Option { + Some(T), None + } + fn main() { + let result = Option::Some((1, 2)); + while let Option::Some((x/*hint text=": i32"*/, y/*hint text=": i32"*/)) = result {} + } + """, enabledHints = RsPlainTypeHint.LET_BINDING_HINT) + + fun `test hints in while let expr with multiple patterns`() = checkByText(""" + enum V { + V1(T), V2(T) + } + fn main() { + let result = V::V1((1, 2)); + while let V::V1(x/*hint text=": (i32, i32)"*/) | V::V2(x/*hint text=": (i32, i32)"*/) = result {} + } + """, enabledHints = RsPlainTypeHint.LET_BINDING_HINT) + + fun `test hints in match expr`() = checkByText(""" + enum Option { + Some(T), None + } + fn main() { + let result = Option::Some((1, 2)); + match result { + Option::Some((x/*hint text=": i32"*/, y/*hint text=": i32"*/)) => (), + _ => () + } + } + """, enabledHints = RsPlainTypeHint.LET_BINDING_HINT) + + fun `test show hints only for new local variables and ignore enum variants`() = checkByText(""" + enum Option { + Some(T), None + } + + use Option::{Some, None}; + + fn main() { + let result = Some(1); + match result { + None => (), + Name/*hint text=": Option"*/ => () + } + } + """, enabledHints = RsPlainTypeHint.LET_BINDING_HINT) + + fun `test show hints for inner pat bindings`() = checkByText(""" + enum Option { + Some(T), None + } + + use Option::{Some, None}; + + fn main() { + match Option::Some((1, 2)) { + Some((x/*hint text=": i32"*/, 5)) => (), + y => () + } + } + """, enabledHints = RsPlainTypeHint.LET_BINDING_HINT) +} diff --git a/src/192/main/kotlin/org/rust/cargo/runconfig/buildtool/Utils.kt b/src/192/main/kotlin/org/rust/cargo/runconfig/buildtool/Utils.kt deleted file mode 100644 index de26e910988..00000000000 --- a/src/192/main/kotlin/org/rust/cargo/runconfig/buildtool/Utils.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Use of this source code is governed by the MIT license that can be - * found in the LICENSE file. - */ - -@file:Suppress("UnstableApiUsage") - -package org.rust.cargo.runconfig.buildtool - -import com.intellij.build.BuildProgressListener -import com.intellij.build.events.BuildEvent -import com.intellij.execution.runners.ExecutionEnvironment -import com.intellij.openapi.progress.EmptyProgressIndicator -import com.intellij.openapi.util.Key -import org.rust.cargo.toolchain.CargoCommandLine - -typealias CargoPatch = (CargoCommandLine) -> CargoCommandLine - -val ExecutionEnvironment.cargoPatches: MutableList - get() = putUserDataIfAbsent(CARGO_PATCHES, mutableListOf()) - -private val CARGO_PATCHES: Key> = Key.create("CARGO.PATCHES") - -object EmptyBuildProgressListener : BuildProgressListener { - override fun onEvent(buildId: Any, event: BuildEvent) = Unit -} - -class MockProgressIndicator : EmptyProgressIndicator() { - private val _textHistory: MutableList = mutableListOf() - val textHistory: List get() = _textHistory - - override fun setText(text: String?) { - super.setText(text) - _textHistory += text - } - - override fun setText2(text: String?) { - super.setText2(text) - _textHistory += text - } -} - -@Suppress("UnstableApiUsage") -class MockBuildProgressListener : BuildProgressListener { - private val _eventHistory: MutableList = mutableListOf() - val eventHistory: List get() = _eventHistory - - override fun onEvent(buildId: Any, event: BuildEvent) { - _eventHistory.add(event) - } -} diff --git a/src/192/main/kotlin/org/rust/cargo/runconfig/buildtool/UtilsExtra.kt b/src/192/main/kotlin/org/rust/cargo/runconfig/buildtool/UtilsExtra.kt new file mode 100644 index 00000000000..6f2c124c17c --- /dev/null +++ b/src/192/main/kotlin/org/rust/cargo/runconfig/buildtool/UtilsExtra.kt @@ -0,0 +1,25 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +@file:Suppress("UnstableApiUsage") + +package org.rust.cargo.runconfig.buildtool + +import com.intellij.build.BuildProgressListener +import com.intellij.build.events.BuildEvent + +object EmptyBuildProgressListener : BuildProgressListener { + override fun onEvent(buildId: Any, event: BuildEvent) = Unit +} + +@Suppress("UnstableApiUsage") +class MockBuildProgressListener : BuildProgressListener { + private val _eventHistory: MutableList = mutableListOf() + val eventHistory: List get() = _eventHistory + + override fun onEvent(buildId: Any, event: BuildEvent) { + _eventHistory.add(event) + } +} diff --git a/src/192/main/kotlin/org/rust/ide/hints/RsInlayTypeHintsProvider.kt b/src/192/main/kotlin/org/rust/ide/hints/RsInlayTypeHintsProvider.kt new file mode 100644 index 00000000000..1c163fbbc71 --- /dev/null +++ b/src/192/main/kotlin/org/rust/ide/hints/RsInlayTypeHintsProvider.kt @@ -0,0 +1,192 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +package org.rust.ide.hints + +import com.intellij.codeInsight.hints.* +import com.intellij.codeInsight.hints.presentation.InlayPresentation +import com.intellij.codeInsight.hints.presentation.InsetPresentation +import com.intellij.codeInsight.hints.presentation.MenuOnClickPresentation +import com.intellij.openapi.components.service +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.ui.components.CheckBox +import com.intellij.ui.layout.panel +import com.intellij.util.ui.JBUI +import org.rust.lang.RsLanguage +import org.rust.lang.core.psi.* +import org.rust.lang.core.psi.ext.* +import org.rust.lang.core.types.declaration +import org.rust.lang.core.types.ty.TyUnknown +import org.rust.lang.core.types.type +import javax.swing.JPanel + +class RsInlayTypeHintsProvider : InlayHintsProvider { + override val key: SettingsKey get() = KEY + + override val name: String get() = "Type hints" + + override val previewText: String + get() = """ + struct Foo { x: T1, y: T2, z: T3 } + + fn main() { + let foo = Foo { x: 1, y: "abc", z: true }; + } + """.trimIndent() + + override fun createConfigurable(settings: Settings): ImmediateConfigurable = object : ImmediateConfigurable { + val showForVariables = "Show for variables" + val showForLambdas = "Show for closures" + val showForIterators = "Show for iterators" + val showObviousTypes = "Show obvious types" + + private val varField = CheckBox(showForVariables) + private val lambdaField = CheckBox(showForLambdas) + private val iteratorField = CheckBox(showForIterators) + private val obviousTypesField = CheckBox(showObviousTypes) + + override fun createComponent(listener: ChangeListener): JPanel { + varField.isSelected = settings.showForVariables + varField.addItemListener { handleChange(listener) } + lambdaField.isSelected = settings.showForLambdas + lambdaField.addItemListener { handleChange(listener) } + iteratorField.isSelected = settings.showForIterators + iteratorField.addItemListener { handleChange(listener) } + obviousTypesField.isSelected = settings.showObviousTypes + obviousTypesField.addItemListener { handleChange(listener) } + + val panel = panel { + row { varField(pushX) } + row { lambdaField(pushX) } + row { iteratorField(pushX) } + row { obviousTypesField(pushX) } + } + panel.border = JBUI.Borders.empty(5) + return panel + } + + private fun handleChange(listener: ChangeListener) { + settings.showForVariables = varField.isSelected + settings.showForLambdas = lambdaField.isSelected + settings.showForIterators = iteratorField.isSelected + settings.showObviousTypes = obviousTypesField.isSelected + listener.settingsChanged() + } + } + + override fun createSettings(): Settings = Settings() + + override fun getCollectorFor(file: PsiFile, editor: Editor, settings: Settings, sink: InlayHintsSink): InlayHintsCollector? = + object : FactoryInlayHintsCollector(editor) { + + val typeHintsFactory = RsTypeHintsPresentationFactory(factory) + + override fun collect(element: PsiElement, editor: Editor, sink: InlayHintsSink): Boolean { + if (file.project.service().isDumb) return true + if (element !is RsElement) return true + + if (settings.showForVariables) { + presentVariable(element) + } + if (settings.showForLambdas) { + presentLambda(element) + } + if (settings.showForIterators) { + presentIterator(element) + } + + return true + } + + private fun presentVariable(element: RsElement) { + when (element) { + is RsLetDecl -> { + if (element.typeReference != null) return + val pat = element.pat ?: return + presentTypeForPat(pat, element.expr) + } + is RsCondition -> { + for (pat in element.patList) { + presentTypeForPat(pat, element.expr) + } + } + is RsMatchExpr -> { + for (pat in element.arms.flatMap { it.patList }) { + presentTypeForPat(pat, element.expr) + } + } + } + } + + private fun presentLambda(element: RsElement) { + if (element !is RsLambdaExpr) return + + for (parameter in element.valueParameterList.valueParameterList) { + if (parameter.typeReference != null) continue + val pat = parameter.pat ?: continue + presentTypeForPat(pat) + } + } + + private fun presentIterator(element: RsElement) { + if (element !is RsForExpr) return + + val pat = element.pat ?: return + presentTypeForPat(pat) + } + + private fun presentTypeForPat(pat: RsPat, expr: RsExpr? = null) { + if (!settings.showObviousTypes && isObvious(pat, expr?.declaration)) return + + for (binding in pat.descendantsOfType()) { + if (binding.referenceName.startsWith("_")) continue + if (binding.reference.resolve()?.isConstantLike == true) continue + if (binding.type is TyUnknown) continue + + presentTypeForBinding(binding) + } + } + + private fun presentTypeForBinding(binding: RsPatBinding) { + val project = binding.project + val presentation = typeHintsFactory.typeHint(binding.type) + val finalPresentation = presentation.withDisableAction(project) + sink.addInlineElement(binding.endOffset, false, finalPresentation) + } + } + + private fun InlayPresentation.withDisableAction(project: Project): InsetPresentation = InsetPresentation( + MenuOnClickPresentation(this, project) { + listOf(InlayProviderDisablingAction(name, RsLanguage, project, key)) + }, left = 1 + ) + + data class Settings( + var showForVariables: Boolean = true, + var showForLambdas: Boolean = true, + var showForIterators: Boolean = true, + var showObviousTypes: Boolean = false + ) + + companion object { + private val KEY: SettingsKey = SettingsKey("rust.type.hints") + } +} + +/** + * Don't show hints in such cases: + * + * `let a = MyEnum::A(42);` + * `let b = MyStruct { x: 42 };` + */ +private fun isObvious(pat: RsPat, declaration: RsElement?): Boolean = + when (declaration) { + is RsStructItem, is RsEnumVariant -> pat is RsPatIdent + else -> false + } diff --git a/src/192/main/kotlin/org/rust/ide/hints/RsPlainTypeHint.kt b/src/192/main/kotlin/org/rust/ide/hints/RsPlainTypeHint.kt new file mode 100644 index 00000000000..ebce9c1f81f --- /dev/null +++ b/src/192/main/kotlin/org/rust/ide/hints/RsPlainTypeHint.kt @@ -0,0 +1,20 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +package org.rust.ide.hints + +import com.intellij.codeInsight.hints.InlayInfo +import com.intellij.codeInsight.hints.Option +import com.intellij.psi.PsiElement + +/** New interactive type hints are used instead */ +enum class RsPlainTypeHint(desc: String, enabled: Boolean) : RsPlainHint { + ; + + override fun isApplicable(elem: PsiElement): Boolean = false + override fun provideHints(elem: PsiElement): List = emptyList() + override val enabled: Boolean = false + override val option: Option = Option("SHOW_${this.name}", desc, enabled) +} diff --git a/src/192/main/kotlin/org/rust/ide/hints/RsTypeHintsPresentationFactory.kt b/src/192/main/kotlin/org/rust/ide/hints/RsTypeHintsPresentationFactory.kt new file mode 100644 index 00000000000..ae56aa8c95c --- /dev/null +++ b/src/192/main/kotlin/org/rust/ide/hints/RsTypeHintsPresentationFactory.kt @@ -0,0 +1,254 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +package org.rust.ide.hints + +import com.intellij.codeInsight.hints.presentation.InlayPresentation +import com.intellij.codeInsight.hints.presentation.PresentationFactory +import org.rust.ide.presentation.shortPresentableText +import org.rust.lang.core.psi.RsTraitItem +import org.rust.lang.core.psi.ext.typeParameters +import org.rust.lang.core.types.BoundElement +import org.rust.lang.core.types.ty.* + +class RsTypeHintsPresentationFactory(private val factory: PresentationFactory) { + + fun typeHint(type: Ty): InlayPresentation = factory.roundWithBackground( + listOf(text(": "), hint(type, 1)).join() + ) + + private fun hint(type: Ty, level: Int): InlayPresentation = when (type) { + is TyTuple -> tupleTypeHint(type, level) + is TyAdt -> adtTypeHint(type, level) + is TyFunction -> functionTypeHint(type, level) + is TyReference -> referenceTypeHint(type, level) + is TyPointer -> pointerTypeHint(type, level) + is TyProjection -> projectionTypeHint(type, level) + is TyTypeParameter -> typeParameterTypeHint(type) + is TyArray -> arrayTypeHint(type, level) + is TySlice -> sliceTypeHint(type, level) + is TyTraitObject -> traitObjectTypeHint(type, level) + is TyAnon -> anonTypeHint(type, level) + else -> text(type.shortPresentableText) + } + + private fun functionTypeHint(type: TyFunction, level: Int): InlayPresentation { + val parameters = type.paramTypes + val returnType = type.retType + + val startWithPlaceholder = checkSize(level, parameters.size + 1) + val fn = if (parameters.isEmpty()) { + text("fn()") + } else { + factory.collapsible( + prefix = text("fn("), + collapsed = text(PLACEHOLDER), + expanded = { parametersHint(parameters, level + 1) }, + suffix = text(")"), + startWithPlaceholder = startWithPlaceholder + ) + } + + if (returnType != TyUnit) { + val ret = factory.collapsible( + prefix = text(" → "), + collapsed = text(PLACEHOLDER), + expanded = { hint(returnType, level + 1) }, + suffix = text(""), + startWithPlaceholder = startWithPlaceholder + ) + return factory.seq(fn, ret) + } + + return fn + } + + private fun tupleTypeHint(type: TyTuple, level: Int): InlayPresentation = + factory.collapsible( + prefix = text("("), + collapsed = text(PLACEHOLDER), + expanded = { parametersHint(type.types, level + 1) }, + suffix = text(")"), + startWithPlaceholder = checkSize(level, type.types.size) + ) + + private fun adtTypeHint(type: TyAdt, level: Int): InlayPresentation { + val aliasedBy = type.aliasedBy + + val adtName = aliasedBy?.element?.name ?: type.item.name + val typeArguments = aliasedBy?.element?.typeParameters?.map { aliasedBy.subst[it] ?: TyUnknown } + ?: type.typeArguments + + val typeDeclaration = type.item + val typeNamePresentation = factory.psiSingleReference(text(adtName)) { typeDeclaration } + + if (typeArguments.isNotEmpty()) { + val collapsible = factory.collapsible( + prefix = text("<"), + collapsed = text(PLACEHOLDER), + expanded = { parametersHint(typeArguments, level + 1) }, + suffix = text(">"), + startWithPlaceholder = checkSize(level, typeArguments.size) + ) + return listOf(typeNamePresentation, collapsible).join() + } + return typeNamePresentation + } + + private fun referenceTypeHint(type: TyReference, level: Int): InlayPresentation = listOf( + text("&" + if (type.mutability.isMut) "mut " else ""), + hint(type.referenced, level) // level is not incremented intentionally + ).join() + + private fun pointerTypeHint(type: TyPointer, level: Int): InlayPresentation = listOf( + text("*" + if (type.mutability.isMut) "mut " else "const "), + hint(type.referenced, level) // level is not incremented intentionally + ).join() + + private fun projectionTypeHint(type: TyProjection, level: Int): InlayPresentation { + val collapsible = factory.collapsible( + prefix = text("<"), + collapsed = text(PLACEHOLDER), + expanded = { + val typePresentation = hint(type.type, level + 1) + val traitPresentation = traitItemTypeHint(type.trait, level + 1, false) + listOf(typePresentation, traitPresentation).join(" as ") + }, + suffix = text(">"), + startWithPlaceholder = checkSize(level, 2) + ) + + val targetDeclaration = type.target.navigationElement + val targetPresentation = if (targetDeclaration != null) { + factory.psiSingleReference(text(type.target.name)) { targetDeclaration } + } else { + text(type.target.name) + } + + return listOf(collapsible, targetPresentation).join("::") + } + + private fun typeParameterTypeHint(type: TyTypeParameter): InlayPresentation { + val parameter = type.parameter + if (parameter is TyTypeParameter.Named) { + return factory.psiSingleReference(text(parameter.name)) { parameter.parameter.navigationElement } + } + return text(parameter.name) + } + + private fun arrayTypeHint(type: TyArray, level: Int): InlayPresentation = + factory.collapsible( + prefix = text("["), + collapsed = text(PLACEHOLDER), + expanded = { + val basePresentation = hint(type.base, level + 1) + val sizePresentation = text(type.size?.toString()) + listOf(basePresentation, sizePresentation).join("; ") + }, + suffix = text("]"), + startWithPlaceholder = checkSize(level, 1) + ) + + private fun sliceTypeHint(type: TySlice, level: Int): InlayPresentation = + factory.collapsible( + prefix = text("["), + collapsed = text(PLACEHOLDER), + expanded = { hint(type.elementType, level + 1) }, + suffix = text("]"), + startWithPlaceholder = checkSize(level, 1) + ) + + private fun traitObjectTypeHint(type: TyTraitObject, level: Int): InlayPresentation = + factory.collapsible( + prefix = text("dyn "), + collapsed = text(PLACEHOLDER), + expanded = { traitItemTypeHint(type.trait, level, true) }, + suffix = text(""), + startWithPlaceholder = checkSize(level, 1) + ) + + private fun anonTypeHint(type: TyAnon, level: Int): InlayPresentation = + factory.collapsible( + prefix = text("impl "), + collapsed = text(PLACEHOLDER), + expanded = { type.traits.map { traitItemTypeHint(it, level + 1, true) }.join("+") }, + suffix = text(""), + startWithPlaceholder = checkSize(level, type.traits.size) + ) + + private fun parametersHint(types: List, level: Int): InlayPresentation = + types.map { hint(it, level) }.join(", ") + + private fun traitItemTypeHint(trait: BoundElement, level: Int, includeAssoc: Boolean): InlayPresentation { + val traitPresentation = factory.psiSingleReference(text(trait.element.name)) { trait.element } + + val typeParametersPresentations = mutableListOf() + for (parameter in trait.element.typeParameters) { + val argument = trait.subst[parameter] ?: continue + val parameterPresentation = hint(argument, level + 1) + typeParametersPresentations.add(parameterPresentation) + } + + val assocTypesPresentations = mutableListOf() + if (includeAssoc) { + for (alias in trait.element.associatedTypesTransitively) { + val name = alias.name ?: continue + val type = trait.assoc[alias] ?: continue + + val declaration = alias.navigationElement + val aliasPresentation = if (declaration != null) { + factory.psiSingleReference(text(name)) { declaration } + } else { + text(name) + } + + val presentation = listOf(aliasPresentation, text("="), hint(type, level + 1)).join() + assocTypesPresentations.add(presentation) + } + } + + val innerPresentations = typeParametersPresentations + assocTypesPresentations + + return if (innerPresentations.isEmpty()) { + traitPresentation + } else { + val expanded = innerPresentations.join(", ") + val traitTypesPresentation = factory.collapsible( + prefix = text("<"), + collapsed = text(PLACEHOLDER), + expanded = { expanded }, + suffix = text(">"), + startWithPlaceholder = checkSize(level, innerPresentations.size) + ) + listOf(traitPresentation, traitTypesPresentation).join() + } + } + + private fun checkSize(level: Int, elementsCount: Int): Boolean = + level + elementsCount > FOLDING_TRESHOLD + + private fun List.join(separator: String = ""): InlayPresentation { + if (separator.isEmpty()) { + return factory.seq(*toTypedArray()) + } + val presentations = mutableListOf() + var first = true + for (presentation in this) { + if (!first) { + presentations.add(text(separator)) + } + presentations.add(presentation) + first = false + } + return factory.seq(*presentations.toTypedArray()) + } + + private fun text(text: String?): InlayPresentation = factory.smallText(text ?: "?") + + companion object { + private const val PLACEHOLDER: String = "…" + private const val FOLDING_TRESHOLD: Int = 3 + } +} diff --git a/src/192/main/resources/META-INF/platform-core.xml b/src/192/main/resources/META-INF/platform-core.xml index 164f46cfcfb..0e2425da89a 100644 --- a/src/192/main/resources/META-INF/platform-core.xml +++ b/src/192/main/resources/META-INF/platform-core.xml @@ -1,2 +1,5 @@ + + + diff --git a/src/192/test/kotlin/org/rust/ide/hints/RsInlayTypeHintsProviderTest.kt b/src/192/test/kotlin/org/rust/ide/hints/RsInlayTypeHintsProviderTest.kt new file mode 100644 index 00000000000..06e4c90ee35 --- /dev/null +++ b/src/192/test/kotlin/org/rust/ide/hints/RsInlayTypeHintsProviderTest.kt @@ -0,0 +1,304 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +package org.rust.ide.hints + +import com.intellij.codeInsight.hints.presentation.PresentationRenderer +import com.intellij.openapi.vfs.VirtualFileFilter +import org.intellij.lang.annotations.Language +import org.rust.ProjectDescriptor +import org.rust.RsTestBase +import org.rust.WithStdlibRustProjectDescriptor +import org.rust.fileTreeFromText +import org.rust.lang.core.psi.RsMethodCall + +class RsInlayTypeHintsProviderTest : RsTestBase() { + fun `test simple`() = checkByText(""" + fn main() { + let s/*hint text="[: i32]"*/ = 42; + } + """) + + fun `test let stmt without expression`() = checkByText(""" + struct S; + fn main() { + let s/*hint text="[: S]"*/; + s = S; + } + """) + + fun `test no redundant hints`() = checkByText(""" + fn main() { + let _ = 1; + let _a = 1; + let a = UnknownType; + } + """) + + fun `test smart hint don't show redundant hints`() = checkByText(""" + struct S; + struct TupleStruct(f32); + struct BracedStruct { f: f32 } + enum E { + C, B { f: f32 }, T(f32) + } + + fn main() { + let no_hint = S; + let no_hint = TupleStruct(1.0); + let no_hint = BracedStruct { f: 1.0 }; + let no_hint = E::C; + let no_hint = E::B { f: 1.0 }; + let no_hint = E::T(1.0); + } + """) + + fun `test let decl tuple`() = checkByText(""" + struct S; + fn main() { + let (s/*hint text="[: S]"*/, c/*hint text="[: S]"*/) = (S, S); + } + """) + + fun `test pat field`() = checkByText(""" + struct S; + struct TupleStruct(S); + struct BracedStruct { a: S, b: S } + fn main() { + let TupleStruct(x/*hint text="[: S]"*/) = TupleStruct(S); + let BracedStruct { a: a/*hint text="[: S]"*/, b/*hint text="[: S]"*/ } = BracedStruct { a: S, b: S }; + } + """) + + + fun `test smart should not annotate tuples`() = checkByText(""" + enum Option { + Some(T), + None + } + fn main() { + let s = Option::Some(10); + } + """) + + private val fnTypes = """ + #[lang = "fn_once"] + trait FnOnce { type Output; } + + #[lang = "fn_mut"] + trait FnMut: FnOnce { } + + #[lang = "fn"] + trait Fn: FnMut { } + """ + + fun `test lambda type hint`() = checkByText(""" + $fnTypes + struct S; + fn with_s(f: F) {} + fn main() { + with_s(|s/*hint text="[: S]"*/| s.bar()) + } + """) + + fun `test lambda type not shown if redundant`() = checkByText(""" + $fnTypes + struct S; + fn with_s(f: F) {} + fn main() { + with_s(|s: S| s.bar()) + with_s(|_| ()) + } + """) + + fun `test lambda type should show after an defined type correct`() = checkByText(""" + $fnTypes + struct S; + fn foo ()>(action: T) {} + fn main() { + foo(|x/*hint text="[: S]"*/, y: S, z/*hint text="[: [( [S , S] )]]"*/| {}); + } + """) + + fun `test don't render horrendous types in their full glory`() = checkByText(""" + struct S; + + impl S { + fn wrap(self, f: F) -> S { + unimplemented!() + } + } + + fn main() { + let s: S<(), ()> = unimplemented!(); + let foo/*hint text="[: [S [< [[[fn( … )] [ → … ]] , [S [< … >]]] >]]]"*/ = s + .wrap(|x: i32| x) + .wrap(|x: i32| x) + .wrap(|x: i32| x) + .wrap(|x: i32| x); + } + """) + + @ProjectDescriptor(WithStdlibRustProjectDescriptor::class) + fun `test inlay hint for loops`() = checkByText(""" + struct S; + struct I; + impl Iterator for I { + type Item = S; + fn next(&mut self) -> Option { None } + } + + fn main() { + for s/*hint text="[: S]"*/ in I { } + } + """) + + fun `test don't touch ast`() { + fileTreeFromText(""" + //- main.rs + mod foo; + use foo::Foo; + + fn main() { + Foo.bar(92) + } //^ + //- foo.rs + struct Foo; + impl Foo { fn bar(&self, x: i32) {} } + """).createAndOpenFileWithCaretMarker() + + val handler = RsInlayParameterHintsProvider() + val target = findElementInEditor("^") + checkAstNotLoaded(VirtualFileFilter.ALL) + val inlays = handler.getParameterHints(target) + check(inlays.size == 1) + } + + fun `test hints in if let expr`() = checkByText(""" + enum Option { + Some(T), None + } + fn main() { + let result = Option::Some((1, 2)); + if let Option::Some((x/*hint text="[: i32]"*/, y/*hint text="[: i32]"*/)) = result {} + } + """) + + fun `test hints in if let expr with multiple patterns`() = checkByText(""" + enum V { + V1(T), V2(T) + } + fn main() { + let result = V::V1((1, 2)); + if let V::V1(x/*hint text="[: [( [i32 , i32] )]]"*/) | V::V2(x/*hint text="[: [( [i32 , i32] )]]"*/) = result {} + } + """) + + fun `test hints in while let expr`() = checkByText(""" + enum Option { + Some(T), None + } + fn main() { + let result = Option::Some((1, 2)); + while let Option::Some((x/*hint text="[: i32]"*/, y/*hint text="[: i32]"*/)) = result {} + } + """) + + fun `test hints in while let expr with multiple patterns`() = checkByText(""" + enum V { + V1(T), V2(T) + } + fn main() { + let result = V::V1((1, 2)); + while let V::V1(x/*hint text="[: [( [i32 , i32] )]]"/>) | V::V2(x { + Some(T), None + } + fn main() { + let result = Option::Some((1, 2)); + match result { + Option::Some((x/*hint text="[: i32]"*/, y/*hint text="[: i32]"*/)) => (), + _ => () + } + } + """) + + fun `test show hints only for new local variables and ignore enum variants`() = checkByText(""" + enum Option { + Some(T), None + } + + use Option::{Some, None}; + + fn main() { + let result = Some(1); + match result { + None => (), + Name/*hint text="[: [Option [< i32 >]]]"*/ => () + } + } + """) + + fun `test show hints for inner pat bindings`() = checkByText(""" + enum Option { + Some(T), None + } + + use Option::{Some, None}; + + fn main() { + match Option::Some((1, 2)) { + Some((x/*hint text="[: i32]"*/, 5)) => (), + y => () + } + } + """) + + @ProjectDescriptor(WithStdlibRustProjectDescriptor::class) + fun `test show hints for slice`() = checkByText(""" + fn main() { + let xs/*hint text="[: [& [[ i32 ]]]]"*/ = &vec![1,2,3][..]; + } + """) + + fun `test dyn trait with assoc type`() = checkByText(""" + trait Trait { type Item; } + fn foo(x: &Trait) { + let a/*hint text="[: [& [dyn [Trait [< [Item = u8] >]] ]]]"*/ = x; + } + """) + + fun `test impl trait with assoc type`() = checkByText(""" + trait Trait { type Item; } + fn bar() -> impl Trait { unimplemented!() } + fn foo() { + let a/*hint text="[: [impl [Trait [< [Item = u8] >]] ]]"*/ = bar(); + } + """) + + fun `test projection type`() = checkByText(""" + trait Trait { type Item; } + fn foo(x: T::Item) { + let a/*hint text="[: [[< [T as Trait] >] :: Item]]"*/ = x; + } + """) + + private fun checkByText(@Language("Rust") code: String) { + InlineFile(code.replace(HINT_COMMENT_PATTERN, "<$1/>")) + + myFixture.testInlays( + { (it.renderer as PresentationRenderer).presentation.toString() }, + { it.renderer is PresentationRenderer } + ) + } + + companion object { + private val HINT_COMMENT_PATTERN = Regex("""/\*(hint.*?)\*/""") + } +} diff --git a/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildAdapter.kt b/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildAdapter.kt index d427162cfab..e34211fe440 100644 --- a/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildAdapter.kt +++ b/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildAdapter.kt @@ -9,12 +9,20 @@ import com.intellij.build.BuildProgressListener import com.intellij.build.DefaultBuildDescriptor import com.intellij.build.events.impl.* import com.intellij.build.output.BuildOutputInstantReaderImpl -import com.intellij.execution.process.AnsiEscapeDecoder -import com.intellij.execution.process.ProcessAdapter -import com.intellij.execution.process.ProcessEvent -import com.intellij.execution.process.ProcessOutputTypes +import com.intellij.execution.ExecutorRegistry +import com.intellij.execution.actions.StopProcessAction +import com.intellij.execution.impl.ExecutionManagerImpl +import com.intellij.execution.process.* +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.execution.runners.ExecutionUtil +import com.intellij.icons.AllIcons +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.project.DumbAwareAction +import com.intellij.openapi.project.DumbService import com.intellij.openapi.util.Key import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.vfs.VfsUtil import org.rust.cargo.CargoConstants import org.rust.cargo.runconfig.RsAnsiEscapeDecoder @@ -40,6 +48,7 @@ class CargoBuildAdapter( private val textBuffer: MutableList = mutableListOf() init { + context.environment.notifyProcessStarted(context.processHandler) val descriptor = DefaultBuildDescriptor( context.buildId, "Run Cargo command", @@ -48,6 +57,8 @@ class CargoBuildAdapter( ) val buildStarted = StartBuildEventImpl(descriptor, "${context.taskName} running...") .withExecutionFilters(*createFilters(context.cargoProject).toTypedArray()) + .withRestartAction(createRerunAction(context.processHandler, context.environment)) + .withRestartAction(createStopAction(context.processHandler)) buildProgressListener.onEvent(context.buildId, buildStarted) } @@ -72,12 +83,18 @@ class CargoBuildAdapter( buildProgressListener.onEvent(context.buildId, buildFinished) context.finished(isSuccess) + context.environment.notifyProcessTerminated(event.processHandler, event.exitCode) + val targetPath = context.workingDirectory.resolve(CargoConstants.ProjectLayout.target) val targetDir = VfsUtil.findFile(targetPath, true) ?: return@whenComplete VfsUtil.markDirtyAndRefresh(true, true, true, targetDir) } } + override fun processWillTerminate(event: ProcessEvent, willBeDestroyed: Boolean) { + context.environment.notifyProcessTerminating(event.processHandler) + } + override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { var rawText = event.text if (SystemInfo.isWindows && rawText.matches(BUILD_PROGRESS_INNER_RE)) { @@ -112,5 +129,37 @@ class CargoBuildAdapter( companion object { private val BUILD_PROGRESS_INNER_RE: Regex = """ \[ *=*>? *] \d+/\d+: [\w\-(.)]+(, [\w\-(.)]+)*""".toRegex() private val BUILD_PROGRESS_FULL_RE: Regex = """ *Building$BUILD_PROGRESS_INNER_RE( *[\r\n])*""".toRegex() + + private fun createStopAction(processHandler: ProcessHandler): StopProcessAction = + StopProcessAction("Stop", "Stop", processHandler) + + private fun createRerunAction(processHandler: ProcessHandler, environment: ExecutionEnvironment): RestartProcessAction = + RestartProcessAction(processHandler, environment) + + private class RestartProcessAction( + private val processHandler: ProcessHandler, + private val environment: ExecutionEnvironment + ) : DumbAwareAction(), AnAction.TransparentUpdate { + private val isEnabled: Boolean + get() { + val project = environment.project + val settings = environment.runnerAndConfigurationSettings + return (!DumbService.isDumb(project) || settings == null || settings.type.isDumbAware) && + !ExecutorRegistry.getInstance().isStarting(environment) && + !processHandler.isProcessTerminating + } + + override fun update(event: AnActionEvent) { + val presentation = event.presentation + presentation.text = "Rerun '${StringUtil.escapeMnemonics(environment.runProfile.name)}'" + presentation.icon = if (processHandler.isProcessTerminated) AllIcons.Actions.Compile else AllIcons.Actions.Restart + presentation.isEnabled = isEnabled + } + + override fun actionPerformed(event: AnActionEvent) { + ExecutionManagerImpl.stopProcess(processHandler) + ExecutionUtil.restart(environment) + } + } } } diff --git a/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildContext.kt b/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildContext.kt index 24b3593beab..f602eb44c06 100644 --- a/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildContext.kt +++ b/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildContext.kt @@ -128,6 +128,8 @@ class CargoBuildContext( warnings = warnings, message = "$taskName canceled" )) + + environment.notifyProcessNotStarted() } companion object { diff --git a/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildManager.kt b/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildManager.kt index 60ae5db0ab0..1b4fe2ec5df 100644 --- a/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildManager.kt +++ b/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildManager.kt @@ -8,7 +8,14 @@ package org.rust.cargo.runconfig.buildtool import com.intellij.build.BuildContentManager import com.intellij.build.BuildViewManager import com.intellij.execution.ExecutionException +import com.intellij.execution.ExecutorRegistry +import com.intellij.execution.RunManager import com.intellij.execution.configuration.EnvironmentVariablesData +import com.intellij.execution.executors.DefaultRunExecutor +import com.intellij.execution.impl.RunManagerImpl +import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.execution.runners.ProgramRunner import com.intellij.notification.NotificationGroup import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.TransactionGuard @@ -32,6 +39,7 @@ import com.intellij.util.text.SemVer import com.intellij.util.ui.UIUtil import org.jetbrains.annotations.TestOnly import org.rust.cargo.project.model.cargoProjects +import org.rust.cargo.runconfig.CargoCommandRunner import org.rust.cargo.runconfig.CargoRunState import org.rust.cargo.runconfig.addFormatJsonOption import org.rust.cargo.runconfig.command.CargoCommandConfiguration @@ -113,6 +121,7 @@ object CargoBuildManager { context: CargoBuildContext, doExecute: CargoBuildContext.() -> Unit ): FutureResult { + context.environment.notifyProcessStartScheduled() val processCreationLock = Any() when { @@ -160,6 +169,7 @@ object CargoBuildManager { ApplicationManager.getApplication().executeOnPooledThread { if (!context.waitAndStart()) return@executeOnPooledThread + context.environment.notifyProcessStarting() if (isUnitTestMode) { context.doExecute() @@ -218,6 +228,16 @@ object CargoBuildManager { return buildConfiguration } + fun createBuildEnvironment(buildConfiguration: CargoCommandConfiguration): ExecutionEnvironment? { + require(isBuildConfiguration(buildConfiguration)) + val project = buildConfiguration.project + val runManager = RunManager.getInstance(project) as? RunManagerImpl ?: return null + val executor = ExecutorRegistry.getInstance().getExecutorById(DefaultRunExecutor.EXECUTOR_ID) ?: return null + val runner = ProgramRunner.findRunnerById(CargoCommandRunner.RUNNER_ID) ?: return null + val settings = RunnerAndConfigurationSettingsImpl(runManager, buildConfiguration) + return ExecutionEnvironment(executor, runner, settings, project) + } + fun showBuildNotification( project: Project, messageType: MessageType, diff --git a/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildTaskProvider.kt b/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildTaskProvider.kt index 7cc97665935..354dacf5c15 100644 --- a/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildTaskProvider.kt +++ b/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildTaskProvider.kt @@ -13,6 +13,7 @@ import com.intellij.icons.AllIcons import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.util.Key import com.intellij.task.ProjectTaskManager +import org.rust.cargo.runconfig.buildtool.CargoBuildManager.createBuildEnvironment import org.rust.cargo.runconfig.buildtool.CargoBuildManager.getBuildConfiguration import org.rust.cargo.runconfig.command.CargoCommandConfiguration import java.util.concurrent.CompletableFuture @@ -30,12 +31,17 @@ class CargoBuildTaskProvider : BeforeRunTaskProvider() ProjectTaskManager.getInstance(configuration.project).build(arrayOf(buildableElement)) { result.complete(it.errors == 0 && !it.isAborted) diff --git a/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildTaskRunner.kt b/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildTaskRunner.kt index d416b198848..ce185cab1e5 100644 --- a/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildTaskRunner.kt +++ b/src/main/kotlin/org/rust/cargo/runconfig/buildtool/CargoBuildTaskRunner.kt @@ -98,9 +98,9 @@ class CargoBuildTaskRunner : ProjectTaskRunner() { return cargoProjects.mapNotNull { cargoProject -> val commandLine = CargoCommandLine.forProject(cargoProject, "build", additionalArguments) val settings = runManager.createCargoCommandRunConfiguration(commandLine) - val executionEnvironment = ExecutionEnvironment(executor, runner, settings, project) + val environment = ExecutionEnvironment(executor, runner, settings, project) val configuration = settings.configuration as? CargoCommandConfiguration ?: return@mapNotNull null - val buildableElement = CargoBuildConfiguration(configuration, executionEnvironment) + val buildableElement = CargoBuildConfiguration(configuration, environment) ProjectModelBuildTaskImpl(buildableElement, false) } } diff --git a/src/main/kotlin/org/rust/cargo/runconfig/buildtool/Utils.kt b/src/main/kotlin/org/rust/cargo/runconfig/buildtool/Utils.kt new file mode 100644 index 00000000000..61e3441cec4 --- /dev/null +++ b/src/main/kotlin/org/rust/cargo/runconfig/buildtool/Utils.kt @@ -0,0 +1,59 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +@file:Suppress("UnstableApiUsage") + +package org.rust.cargo.runconfig.buildtool + +import com.intellij.execution.ExecutionListener +import com.intellij.execution.ExecutionManager +import com.intellij.execution.process.ProcessHandler +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.openapi.progress.EmptyProgressIndicator +import com.intellij.openapi.util.Key +import org.rust.cargo.toolchain.CargoCommandLine + +typealias CargoPatch = (CargoCommandLine) -> CargoCommandLine + +val ExecutionEnvironment.cargoPatches: MutableList + get() = putUserDataIfAbsent(CARGO_PATCHES, mutableListOf()) + +private val CARGO_PATCHES: Key> = Key.create("CARGO.PATCHES") + +private val ExecutionEnvironment.executionListener: ExecutionListener + get() = project.messageBus.syncPublisher(ExecutionManager.EXECUTION_TOPIC) + +fun ExecutionEnvironment.notifyProcessStartScheduled() = + executionListener.processStartScheduled(executor.id, this) + +fun ExecutionEnvironment.notifyProcessStarting() = + executionListener.processStarting(executor.id, this) + +fun ExecutionEnvironment.notifyProcessNotStarted() = + executionListener.processNotStarted(executor.id, this) + +fun ExecutionEnvironment.notifyProcessStarted(handler: ProcessHandler) = + executionListener.processStarted(executor.id, this, handler) + +fun ExecutionEnvironment.notifyProcessTerminating(handler: ProcessHandler) = + executionListener.processTerminating(executor.id, this, handler) + +fun ExecutionEnvironment.notifyProcessTerminated(handler: ProcessHandler, exitCode: Int) = + executionListener.processTerminated(executor.id, this, handler, exitCode) + +class MockProgressIndicator : EmptyProgressIndicator() { + private val _textHistory: MutableList = mutableListOf() + val textHistory: List get() = _textHistory + + override fun setText(text: String?) { + super.setText(text) + _textHistory += text + } + + override fun setText2(text: String?) { + super.setText2(text) + _textHistory += text + } +} diff --git a/src/main/kotlin/org/rust/ide/hints/RsInlayParameterHintsProvider.kt b/src/main/kotlin/org/rust/ide/hints/RsInlayParameterHintsProvider.kt index b0312f88a5c..dd35a9960f1 100644 --- a/src/main/kotlin/org/rust/ide/hints/RsInlayParameterHintsProvider.kt +++ b/src/main/kotlin/org/rust/ide/hints/RsInlayParameterHintsProvider.kt @@ -10,154 +10,17 @@ import com.intellij.codeInsight.hints.InlayInfo import com.intellij.codeInsight.hints.InlayParameterHintsProvider import com.intellij.codeInsight.hints.Option import com.intellij.psi.PsiElement -import org.rust.ide.presentation.shortPresentableText -import org.rust.ide.utils.CallInfo -import org.rust.lang.core.psi.* -import org.rust.lang.core.psi.ext.descendantsOfType -import org.rust.lang.core.psi.ext.endOffset -import org.rust.lang.core.psi.ext.patList -import org.rust.lang.core.psi.ext.startOffset -import org.rust.lang.core.types.declaration -import org.rust.lang.core.types.ty.TyUnknown -import org.rust.lang.core.types.type -import org.rust.stdext.buildList - -private val RsPat.inlayInfo: List - get() = descendantsOfType().asSequence() - .filter { patBinding -> - when { - // "ignored" bindings like `let _a = 123;` - patBinding.referenceName.startsWith("_") -> false - // match foo { - // None -> {} // None is enum variant, so we don't want to provide type hint for it - // _ -> {} - // } - patBinding.reference.resolve() is RsEnumVariant -> false - else -> true - } - } - .map { it to it.type } - .filterNot { (_, type) -> type is TyUnknown } - .map { (patBinding, type) -> InlayInfo(": " + type.shortPresentableText, patBinding.endOffset) } - .toList() - -enum class HintType(desc: String, enabled: Boolean) { - LET_BINDING_HINT("Show local variable type hints", true) { - override fun provideHints(elem: PsiElement): List { - val (expr, pats) = when (elem) { - is RsLetDecl -> { - if (elem.typeReference != null) return emptyList() - elem.expr to listOfNotNull(elem.pat) - } - is RsCondition -> elem.expr to elem.patList - is RsMatchExpr -> elem.expr to elem.matchBody?.matchArmList?.flatMap { it.patList } - else -> return emptyList() - } - - var finalPats = pats.orEmpty() - if (smart) { - val declaration = expr?.declaration - if (declaration is RsStructItem || declaration is RsEnumVariant) { - finalPats = finalPats.filter { it !is RsPatIdent } - } - } - return finalPats.flatMap { it.inlayInfo } - } - - override fun isApplicable(elem: PsiElement): Boolean = elem is RsLetDecl || elem is RsCondition || elem is RsMatchExpr - }, - PARAMETER_HINT("Show argument name hints", true) { - override fun provideHints(elem: PsiElement): List { - val (callInfo, valueArgumentList) = when (elem) { - is RsCallExpr -> (CallInfo.resolve(elem) to elem.valueArgumentList) - is RsMethodCall -> (CallInfo.resolve(elem) to elem.valueArgumentList) - else -> return emptyList() - } - if (callInfo == null) return emptyList() - - val hints = buildList { - if (callInfo.selfParameter != null && elem is RsCallExpr) { - add(callInfo.selfParameter) - } - addAll(callInfo.parameters.map { it.pattern }) - }.zip(valueArgumentList.exprList) - - if (smart) { - if (onlyOneParam(hints, callInfo, elem)) { - if (callInfo.methodName?.startsWith("set_") != false) { - return emptyList() - } - if (callInfo.methodName == callInfo.parameters.getOrNull(0)?.pattern) { - return emptyList() - } - } - if (hints.all { (hint, _) -> hint == "_"}) { - return emptyList() - } - return hints - .filter { (hint, arg) -> !arg.text.endsWith(hint) } - .map { (hint, arg) -> InlayInfo("$hint:", arg.startOffset) } - } - return hints.map { (hint, arg) -> InlayInfo("$hint:", arg.startOffset) } - } - - private fun onlyOneParam(hints: List>, callInfo: CallInfo, elem: PsiElement): Boolean { - if (callInfo.selfParameter != null && elem is RsCallExpr && hints.size == 2) { - return true - } - if (!(callInfo.selfParameter != null && elem is RsCallExpr) && hints.size == 1) { - return true - } - return false - } - - override fun isApplicable(elem: PsiElement): Boolean = - elem is RsCallExpr || elem is RsMethodCall - }, - LAMBDA_PARAMETER_HINT("Show lambda parameter type hints", true) { - override fun provideHints(elem: PsiElement): List { - val element = elem as? RsLambdaExpr ?: return emptyList() - return element.valueParameterList.valueParameterList - .filter { it.typeReference == null } - .mapNotNull { it.pat } - .flatMap { it.inlayInfo } - } - - override fun isApplicable(elem: PsiElement): Boolean = elem is RsLambdaExpr - }, - FOR_PARAMETER_HINT("Show type hints for for loops parameter", true) { - override fun provideHints(elem: PsiElement): List { - val element = elem as? RsForExpr ?: return emptyList() - return element.pat?.inlayInfo ?: emptyList() - } - - override fun isApplicable(elem: PsiElement): Boolean = elem is RsForExpr - }; - - companion object { - val SMART_HINTING = Option("SMART_HINTING", "Show only smart hints", true) - fun resolve(elem: PsiElement): HintType? = - HintType.values().find { it.isApplicable(elem) } - } - - abstract fun isApplicable(elem: PsiElement): Boolean - abstract fun provideHints(elem: PsiElement): List - - val option = Option("SHOW_${this.name}", desc, enabled) - val enabled get() = option.get() - val smart get() = SMART_HINTING.get() -} class RsInlayParameterHintsProvider : InlayParameterHintsProvider { override fun getSupportedOptions(): List