- moving `CommandLineProcessor` extensions to more appropriate places
- dropping long-obsolete `ApplicationStarter#getCommandName`
- deprecating cumbersome `ApplicationStarter#getCommandNameFromExtension` in favor of constants
- typos
- formatting

GitOrigin-RevId: 2668c9f3474bd78fe97d9c614a2cf3faebbe9eee
This commit is contained in:
Roman Shevchenko
2025-09-26 14:55:59 +00:00
committed by intellij-monorepo-bot
parent 1ed6de18ba
commit 73fa5bfe55
17 changed files with 159 additions and 247 deletions
@@ -34,6 +34,7 @@ import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
private class DiffApplication : ApplicationStarterBase(/* ...possibleArgumentsCount = */ 0, 2, 3) {
override val commandName: String get() = "diff"
override val usageMessage: String
get() {
val scriptName = ApplicationNamesInfo.getInstance().scriptName
@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.diff.applications
import com.intellij.diff.DiffDialogHints
@@ -27,6 +27,7 @@ import kotlinx.coroutines.withContext
import java.util.concurrent.atomic.AtomicReference
internal class MergeApplication : ApplicationStarterBase(3, 4) {
override val commandName: String get() = "merge"
override val usageMessage: String
get() {
val scriptName = ApplicationNamesInfo.getInstance().scriptName
@@ -91,4 +92,4 @@ internal class MergeApplication : ApplicationStarterBase(3, 4) {
}
}
}
}
}
@@ -1,6 +1,4 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("DeprecatedCallableAddReplaceWith", "ReplaceGetOrSet")
package com.intellij.openapi.extensions
import com.intellij.openapi.Disposable
@@ -69,9 +67,7 @@ class ExtensionPointName<T : Any>(name: @NonNls String) : BaseExtensionPointName
*
* @return first extension matching [predicate], or `null` if there is no such extension.
*/
fun findFirstSafe(predicate: Predicate<in T>): T? {
return findFirstSafe(predicate = predicate, sequence = getRootPoint().asSequence())
}
fun findFirstSafe(predicate: Predicate<in T>): T? = findFirstSafe(predicate, getRootPoint().asSequence())
/**
* Iterates over registered extensions and calls [processor] for each of them.
@@ -82,9 +78,7 @@ class ExtensionPointName<T : Any>(name: @NonNls String) : BaseExtensionPointName
*
* @return first not-null value returned by [processor], or `null` if processor didn't return any non-null value.
*/
fun <R : Any> computeSafeIfAny(processor: Function<T, out R?>): R? {
return computeSafeIfAny(processor = processor::apply, sequence = getRootPoint().asSequence())
}
fun <R : Any> computeSafeIfAny(processor: Function<T, out R?>): R? = computeSafeIfAny(processor::apply, getRootPoint().asSequence())
val extensionsIfPointIsRegistered: List<T>
get() = getExtensionsIfPointIsRegistered(null)
@@ -98,10 +92,9 @@ class ExtensionPointName<T : Any>(name: @NonNls String) : BaseExtensionPointName
@Deprecated("Use {@code getExtensionList().stream()}", level = DeprecationLevel.ERROR)
fun extensions(): Stream<T> = getRootPoint().asSequence().asStream()
fun hasAnyExtensions(): Boolean {
@Suppress("DEPRECATION")
return (Extensions.getRootArea().getExtensionPointIfRegistered<T>(name) ?: return false).size() != 0
}
@Suppress("DEPRECATION", "RemoveUnnecessaryParentheses")
fun hasAnyExtensions(): Boolean =
(Extensions.getRootArea().getExtensionPointIfRegistered<T>(name)?.size() ?: 0) != 0
/**
* Use [extensionList] for application-level extensions and [ProjectExtensionPointName.getExtensions] for project-level extension instead
@@ -123,17 +116,14 @@ class ExtensionPointName<T : Any>(name: @NonNls String) : BaseExtensionPointName
val point: ExtensionPoint<T>
get() = getRootPoint()
fun <V : T> findExtension(instanceOf: Class<V>): V? {
return getRootPoint().findExtension(aClass = instanceOf, isRequired = false, strictMatch = ThreeState.UNSURE)
}
fun <V : T> findExtension(instanceOf: Class<V>): V? =
getRootPoint().findExtension(instanceOf, isRequired = false, strictMatch = ThreeState.UNSURE)
fun <V : T> findExtensionOrFail(exactClass: Class<V>): V {
return getRootPoint().findExtension(aClass = exactClass, isRequired = true, strictMatch = ThreeState.UNSURE)!!
}
fun <V : T> findExtensionOrFail(instanceOf: Class<V>): V =
getRootPoint().findExtension(instanceOf, isRequired = true, strictMatch = ThreeState.UNSURE)!!
fun <V : T> findFirstAssignableExtension(instanceOf: Class<V>): V? {
return getRootPoint().findExtension(aClass = instanceOf, isRequired = true, strictMatch = ThreeState.NO)
}
fun <V : T> findFirstAssignableExtension(instanceOf: Class<V>): V? =
getRootPoint().findExtension(instanceOf, isRequired = true, strictMatch = ThreeState.NO)
/**
* Do not use it if there is any extension point listener, because in this case behavior is not predictable -
@@ -152,9 +142,7 @@ class ExtensionPointName<T : Any>(name: @NonNls String) : BaseExtensionPointName
fun getIterable(): Iterable<T?> = getRootPoint().asSequence().asIterable()
@Internal
fun lazySequence(): Sequence<T> {
return getRootPoint().asSequence()
}
fun lazySequence(): Sequence<T> = getRootPoint().asSequence()
@Internal
fun processWithPluginDescriptor(consumer: (T, PluginDescriptor) -> Unit) {
@@ -163,25 +151,21 @@ class ExtensionPointName<T : Any>(name: @NonNls String) : BaseExtensionPointName
@Deprecated("Pass CoroutineScope to addExtensionPointListener")
fun addExtensionPointListener(listener: ExtensionPointListener<T>, parentDisposable: Disposable?) {
getRootPoint().addExtensionPointListener(listener = listener,
invokeForLoadedExtensions = false,
parentDisposable = parentDisposable)
getRootPoint().addExtensionPointListener(listener, invokeForLoadedExtensions = false, parentDisposable)
}
@Internal
fun addExtensionPointListener(coroutineScope: CoroutineScope, listener: ExtensionPointListener<T>) {
getRootPoint().addExtensionPointListener(listener = listener,
invokeForLoadedExtensions = false,
coroutineScope = coroutineScope)
getRootPoint().addExtensionPointListener(coroutineScope, invokeForLoadedExtensions = false, listener)
}
@Deprecated("Pass CoroutineScope to addExtensionPointListener")
fun addExtensionPointListener(listener: ExtensionPointListener<T>) {
getRootPoint().addExtensionPointListener(listener = listener, invokeForLoadedExtensions = false, parentDisposable = null)
getRootPoint().addExtensionPointListener(listener, invokeForLoadedExtensions = false, parentDisposable = null)
}
fun addExtensionPointListener(areaInstance: AreaInstance, listener: ExtensionPointListener<T>) {
getPointImpl(areaInstance).addExtensionPointListener(listener = listener, invokeForLoadedExtensions = false, parentDisposable = null)
getPointImpl(areaInstance).addExtensionPointListener(listener, invokeForLoadedExtensions = false, parentDisposable = null)
}
@ApiStatus.ScheduledForRemoval
@@ -193,11 +177,11 @@ class ExtensionPointName<T : Any>(name: @NonNls String) : BaseExtensionPointName
@ApiStatus.ScheduledForRemoval
@Deprecated("Pass CoroutineScope to addChangeListener")
fun addChangeListener(listener: Runnable, parentDisposable: Disposable?) {
getRootPoint().addChangeListener(listener = listener, parentDisposable = parentDisposable)
getRootPoint().addChangeListener(listener, parentDisposable)
}
fun addChangeListener(coroutineScope: CoroutineScope, listener: Runnable) {
getRootPoint().addChangeListener(listener = listener, coroutineScope = coroutineScope)
getRootPoint().addChangeListener(coroutineScope, listener)
}
/**
@@ -210,9 +194,8 @@ class ExtensionPointName<T : Any>(name: @NonNls String) : BaseExtensionPointName
*/
@Internal
@ApiStatus.Experimental
fun <K : Any> getByGroupingKey(key: K, cacheId: Class<*>, keyMapper: Function<T, K?>): List<T> {
return getByGroupingKey(point = getRootPoint(), cacheId = cacheId, key = key, keyMapper = keyMapper)
}
fun <K : Any> getByGroupingKey(key: K, cacheId: Class<*>, keyMapper: Function<T, K?>): List<T> =
getByGroupingKey(getRootPoint(), cacheId, key, keyMapper)
/**
* Build cache by arbitrary key using the provided key to value mapper. Return value by key.
@@ -221,9 +204,8 @@ class ExtensionPointName<T : Any>(name: @NonNls String) : BaseExtensionPointName
*/
@Internal
@ApiStatus.Experimental
fun <K : Any> getByKey(key: K, cacheId: Class<*>, keyMapper: Function<T, K?>): T? {
return getByKey(point = getRootPoint(), key = key, cacheId = cacheId, keyMapper = keyMapper)
}
fun <K : Any> getByKey(key: K, cacheId: Class<*>, keyMapper: Function<T, K?>): T? =
getByKey(getRootPoint(), key, cacheId, keyMapper)
/**
* Build cache by arbitrary key using the provided key to value mapper. Return value by key.
@@ -232,33 +214,24 @@ class ExtensionPointName<T : Any>(name: @NonNls String) : BaseExtensionPointName
*/
@Internal
@ApiStatus.Experimental
fun <K : Any, V : Any> getByKey(
key: K,
cacheId: Class<*>,
keyMapper: Function<T, K?>,
valueMapper: Function<T, V?>,
): V? {
return getByKey(point = getRootPoint(), key = key, cacheId = cacheId, keyMapper = keyMapper, valueMapper = valueMapper)
}
fun <K : Any, V : Any> getByKey(key: K, cacheId: Class<*>, keyMapper: Function<T, K?>, valueMapper: Function<T, V?>): V? =
getByKey(getRootPoint(), key, cacheId, keyMapper, valueMapper)
@Internal
@ApiStatus.Experimental
fun <K : Any, V : Any> computeIfAbsent(key: K, cacheId: Class<*>, valueMapper: Function<K, V>): V {
return computeIfAbsent(point = getRootPoint(), key = key, cacheId = cacheId, valueProducer = valueMapper)
}
fun <K : Any, V : Any> computeIfAbsent(key: K, cacheId: Class<*>, valueMapper: Function<K, V>): V =
computeIfAbsent(getRootPoint(), key, cacheId, valueMapper)
/**
* Cache some value per extension point.
*/
fun <V : Any> computeIfAbsent(cacheId: Class<*>, valueMapper: Supplier<V>): V {
return computeIfAbsent(point = getRootPoint(), cacheId = cacheId, valueProducer = valueMapper)
}
fun <V : Any> computeIfAbsent(cacheId: Class<*>, valueMapper: Supplier<V>): V =
computeIfAbsent(getRootPoint(), cacheId, valueMapper)
@Internal
fun filterableLazySequence(): Sequence<LazyExtension<T>> {
val point = getRootPoint()
val adapters = point.sortedAdapters
return LazyExtensionSequence(point = point, adapters = adapters)
return LazyExtensionSequence(point, point.sortedAdapters)
}
@Internal
@@ -316,14 +289,10 @@ private class LazyExtensionSequence<T : Any>(
private val point: ExtensionPointImpl<T>,
private val adapters: List<ExtensionComponentAdapter>,
) : Sequence<LazyExtension<T>> {
override fun iterator(): Iterator<LazyExtension<T>> {
return object : Iterator<LazyExtension<T>> {
private var currentIndex = 0
override fun hasNext(): Boolean = currentIndex < adapters.size
override fun next(): LazyExtension<T> = LazyExtensionImpl(adapter = adapters.get(currentIndex++), point = point)
}
override fun iterator(): Iterator<LazyExtension<T>> = object : Iterator<LazyExtension<T>> {
private var currentIndex = 0
override fun hasNext(): Boolean = currentIndex < adapters.size
override fun next(): LazyExtension<T> = LazyExtensionImpl(adapter = adapters[currentIndex++], point = point)
}
}
@@ -337,12 +306,11 @@ private class LazyExtensionImpl<T : Any>(
override val order: LoadingOrder
get() = adapter.order
override fun getCustomAttribute(name: String): String? {
return if (adapter is AdapterWithCustomAttributes) adapter.customAttributes.get(name) else null
}
override fun getCustomAttribute(name: String): String? =
if (adapter is AdapterWithCustomAttributes) adapter.customAttributes[name] else null
override val instance: T?
get() = createOrError(adapter = adapter, point = point)
get() = createOrError(adapter, point)
override val implementationClassName: String
get() = adapter.assignableToClassName
@@ -376,4 +344,4 @@ private fun <T : Any> createOrError(adapter: ExtensionComponentAdapter, point: E
logger<ExtensionPointName<T>>().error(point.componentManager.createError(e, adapter.pluginDescriptor.pluginId))
return null
}
}
}
-1
View File
@@ -460,7 +460,6 @@ com.intellij.openapi.application.ApplicationStarter
- sf:NON_MODAL:I
- sf:NOT_IN_EDT:I
- canProcessExternalCommandLine():Z
- getCommandName():java.lang.String
- getRequiredModality():I
- isHeadless():Z
- main(java.util.List):V
@@ -1,20 +1,19 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.application
import com.intellij.ide.CliResult
import com.intellij.openapi.extensions.ExtensionPointName
import org.intellij.lang.annotations.MagicConstant
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.annotations.ApiStatus
@Internal
@ApiStatus.Internal
abstract class ModernApplicationStarter : ApplicationStarter {
final override val requiredModality: Int
get() = ApplicationStarter.NOT_IN_EDT
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated(message = "use start", level = DeprecationLevel.ERROR)
final override fun main(args: List<String>) {
throw UnsupportedOperationException("Use start(args)")
}
final override fun main(args: List<String>): Unit = throw UnsupportedOperationException("Use start(args)")
abstract suspend fun start(args: List<String>)
}
@@ -31,6 +30,12 @@ interface ApplicationStarter {
const val NON_MODAL: Int = 1
const val ANY_MODALITY: Int = 2
const val NOT_IN_EDT: Int = 3
private val EP_NAME = ExtensionPointName<ApplicationStarter>("com.intellij.appStarter")
@ApiStatus.Internal
@JvmStatic
fun findStarter(key: String): ApplicationStarter? = EP_NAME.findByIdOrFromInstance(key, idGetter = { "no-${key}" })
}
/**
@@ -45,14 +50,6 @@ interface ApplicationStarter {
val requiredModality: Int
get() = NON_MODAL
/**
* Command-line switch to start with this runner.
* For example, return `"inspect"` if you would like to start an app with `"idea.exe inspect ..."` command.
*/
@Deprecated("Specify it as `id` for extension definition in a plugin descriptor")
val commandName: String?
get() = null
/**
* Called before application initialization.
*
@@ -61,7 +58,6 @@ interface ApplicationStarter {
fun premain(args: List<String>) {}
/**
*
* Called when application has been initialized. Invoked in event dispatch thread.
*
* An application starter should take care of terminating JVM when appropriate by calling [System.exit].
@@ -83,7 +79,6 @@ interface ApplicationStarter {
fun canProcessExternalCommandLine(): Boolean = false
/** @see [canProcessExternalCommandLine] */
suspend fun processExternalCommandLine(args: List<String>, currentDirectory: String?): CliResult {
suspend fun processExternalCommandLine(args: List<String>, currentDirectory: String?): CliResult =
throw UnsupportedOperationException("Class ${javaClass.name} must implement `processExternalCommandLineAsync()`")
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.script
import com.intellij.ide.CliResult
@@ -18,10 +18,8 @@ import java.io.OutputStreamWriter
import java.nio.charset.Charset
import java.nio.file.Path
/**
* @author gregsh
*/
private class IdeScriptStarter : ApplicationStarterBase() {
override val commandName: String get() = "ideScript"
override val usageMessage: String
get() {
val scriptName = ApplicationNamesInfo.getInstance().scriptName
@@ -485,7 +485,7 @@ private suspend fun createAppStarter(args: List<String>, asyncScope: CoroutineSc
}
else -> {
span("app custom starter creation") {
val starter = findStarter(commandName) ?: createDefaultAppStarter()
val starter = ApplicationStarter.findStarter(commandName) ?: createDefaultAppStarter()
if (AppMode.isHeadless() && !starter.isHeadless) {
val message = BootstrapBundle.message(
"bootstrap.error.message.headless",
@@ -1,8 +1,7 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.ide.bootstrap
import com.intellij.diagnostic.PluginException
import com.intellij.ide.isIdeStartupWizardEnabled
import com.intellij.internal.statistic.eventLog.EventLogGroup
import com.intellij.internal.statistic.eventLog.events.EventFields
import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector
@@ -96,7 +95,7 @@ internal suspend fun runStartupWizard(isInitialStart: Job, app: Application) {
}
}
if (isIdeStartupWizardEnabled) {
if (ConfigImportHelper.isStartupWizardEnabled()) {
LOG.info("Passing execution control to $wizard.")
wizard.run()
firstWizardExecuted = true
@@ -12,11 +12,9 @@ import com.intellij.ide.lightEdit.LightEditFeatureUsagesUtil.OpenPlace
import com.intellij.ide.lightEdit.LightEditService
import com.intellij.ide.lightEdit.LightEditUtil
import com.intellij.ide.util.PsiNavigationSupport
import com.intellij.idea.AppMode
import com.intellij.notification.Notification
import com.intellij.notification.NotificationType
import com.intellij.openapi.application.*
import com.intellij.openapi.application.ex.ApplicationManagerEx
import com.intellij.openapi.components.ComponentManagerEx
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.extensions.ExtensionPointName
@@ -43,7 +41,7 @@ import com.intellij.util.io.URLUtil
import io.netty.handler.codec.http.QueryStringDecoder
import kotlinx.coroutines.*
import kotlinx.coroutines.future.asDeferred
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.VisibleForTesting
import java.awt.Frame
import java.awt.Window
@@ -52,27 +50,22 @@ import java.nio.file.Path
import java.text.ParseException
import java.util.concurrent.CancellationException
import kotlin.Boolean
import kotlin.Deprecated
import kotlin.DeprecationLevel
import kotlin.Int
import kotlin.Result
import kotlin.String
import kotlin.Suppress
import kotlin.Throwable
import kotlin.check
import kotlin.collections.any
import kotlin.collections.count
import kotlin.error
import kotlin.let
import kotlin.require
import kotlin.requireNotNull
import kotlin.use
@get:Internal
val isIdeStartupWizardEnabled: Boolean
get() {
return (!ApplicationManagerEx.isInIntegrationTest() ||
System.getProperty("show.wizard.in.test", "false").toBoolean()) &&
!AppMode.isRemoteDevHost() &&
System.getProperty("intellij.startup.wizard", "true").toBoolean()
}
object CommandLineProcessor {
private val LOG = logger<CommandLineProcessor>()
private const val OPTION_WAIT = "--wait"
@@ -80,11 +73,11 @@ object CommandLineProcessor {
@JvmField
val OK_FUTURE: Deferred<CliResult> = CompletableDeferred(value = CliResult.OK)
@Internal
@ApiStatus.Internal
const val SCHEME_INTERNAL: String = "!!!internal!!!"
@VisibleForTesting
@Internal
@ApiStatus.Internal
suspend fun doOpenFileOrProject(file: Path, shouldWait: Boolean): CommandLineProcessorResult {
if (!LightEditUtil.isForceOpenInLightEditMode()) {
val options = OpenProjectTask {
@@ -96,13 +89,11 @@ object CommandLineProcessor {
try {
val project = ProjectUtil.openOrImportAsync(file, options)
if (project != null) {
return CommandLineProcessorResult(
project = project,
future = if (shouldWait) CommandLineWaitingManager.getInstance().addHookForProject(project).asDeferred() else OK_FUTURE,
)
val future = if (shouldWait) CommandLineWaitingManager.getInstance().addHookForProject(project).asDeferred() else OK_FUTURE
return CommandLineProcessorResult(project, future)
}
}
catch (_: ProcessCanceledException) {
catch (@Suppress("IncorrectCancellationExceptionHandling") _: ProcessCanceledException) {
return createError(IdeBundle.message("dialog.message.open.cancelled"))
}
}
@@ -157,6 +148,7 @@ object CommandLineProcessor {
else {
PsiNavigationSupport.getInstance().createNavigatable(project, file, -1)
}
@Suppress("UsagesOfObsoleteApi")
(project as ComponentManagerEx).getCoroutineScope().launch(Dispatchers.EDT) {
navigatable.navigate(true)
}
@@ -175,7 +167,7 @@ object CommandLineProcessor {
return if (project != null && !LightEdit.owns(project)) project else projects.first()
}
@Internal
@ApiStatus.Internal
suspend fun processProtocolCommand(rawUri: @NlsSafe String): CliResult {
LOG.info("external URI request:\n$rawUri")
check(!ApplicationManager.getApplication().isHeadlessEnvironment) { "cannot process URI requests in headless state" }
@@ -183,7 +175,7 @@ object CommandLineProcessor {
val uri = if (internal) rawUri.substring(SCHEME_INTERNAL.length) else rawUri
val separatorStart = uri.indexOf(URLUtil.SCHEME_SEPARATOR)
require(separatorStart >= 0) { uri }
val scheme = uri.substring(0, separatorStart)
val scheme = uri.take(separatorStart)
val query = uri.substring(separatorStart + URLUtil.SCHEME_SEPARATOR.length)
val cliResult = try {
@@ -238,9 +230,7 @@ object CommandLineProcessor {
val logMessage = StringBuilder()
logMessage.append("External command line:").append('\n')
logMessage.append("Dir: ").append(currentDirectory).append('\n')
for (arg in args) {
logMessage.append(arg).append('\n')
}
for (arg in args) logMessage.append(arg).append('\n')
logMessage.append("-----")
LOG.info(logMessage.toString())
if (args.isEmpty()) {
@@ -252,13 +242,13 @@ object CommandLineProcessor {
}
}
}
return CommandLineProcessorResult(project = null, future = OK_FUTURE)
return CommandLineProcessorResult(project = null, OK_FUTURE)
}
processApplicationStarters(args, currentDirectory)?.let {
processApplicationStarters(args, currentDirectory)?.let { result ->
FUSProjectHotStartUpMeasurer.reportStarterUsed()
// app focus is up to app starter
return CommandLineProcessorResult(project = null, result = it)
return CommandLineProcessorResult(project = null, result)
}
val result = processOpenFile(args, currentDirectory)
@@ -284,17 +274,14 @@ object CommandLineProcessor {
return result
}
// find a frame to activate
@Internal
fun findVisibleFrame(): Window? {
// we assume that the most recently created frame is the most relevant one
return Frame.getFrames().asList().asReversed().firstOrNull { it.isVisible }
}
// find a frame to activate (assuming that the most recently created frame is the most relevant one)
@ApiStatus.Internal
fun findVisibleFrame(): Window? = Frame.getFrames().asList().asReversed().firstOrNull { it.isVisible }
private suspend fun processApplicationStarters(args: List<String>, currentDirectory: String?): CliResult? {
val command = args.first()
val starter = findStarter(command) ?: return null
val starter = ApplicationStarter.findStarter(command) ?: return null
if (!starter.canProcessExternalCommandLine()) {
return CliResult(1, IdeBundle.message("dialog.message.only.one.instance.can.be.run.at.time",
@@ -353,9 +340,11 @@ object CommandLineProcessor {
FUSProjectHotStartUpMeasurer.noProjectFound()
}
else if (commands.size > 1) {
val numberOfProjects = commands.count { command -> command is OpenProjectResult }
val hasLightEditProject = commands.any { command -> (command is OpenProjectResult && command.lightEditMode) ||
(command is NoProjectResult && !command.shouldWait && command.lightEditMode) }
val numberOfProjects = commands.count { it is OpenProjectResult }
val hasLightEditProject = commands.any {
it is OpenProjectResult && it.lightEditMode ||
it is NoProjectResult && !it.shouldWait && it.lightEditMode
}
FUSProjectHotStartUpMeasurer.openingMultipleProjects(false, numberOfProjects, hasLightEditProject)
}
else {
@@ -376,12 +365,7 @@ object CommandLineProcessor {
result = when (command) {
is OpenProjectResult -> {
FUSProjectHotStartUpMeasurer.withProjectContextElement(command.file) {
openFileOrProject(file = command.file,
line = command.line,
column = command.column,
tempProject = command.tempProject,
shouldWait = command.shouldWait,
lightEditMode = command.lightEditMode)
openFileOrProject(command.file, command.line, command.column, command.tempProject, command.shouldWait, command.lightEditMode)
}
}
is NoProjectResult -> {
@@ -406,10 +390,7 @@ object CommandLineProcessor {
return result ?: error("Parsing result shouldn't be null at this point; args are not empty")
}
private fun parseArgs(
args: List<String>,
currentDirectory: String?,
): Result<List<ParsingResult>> {
private fun parseArgs(args: List<String>, currentDirectory: String?): Result<List<ParsingResult>> {
val openProjectResults = mutableListOf<OpenProjectResult>()
var line = -1
var column = -1
@@ -458,31 +439,17 @@ object CommandLineProcessor {
}
val file = parseFilePath(arg, currentDirectory) ?: return Result.failure(ParseException(arg, i))
openProjectResults += OpenProjectResult(
file = file,
line = line,
column = column,
tempProject = tempProject,
shouldWait = shouldWait,
lightEditMode = lightEditMode
)
if (shouldWait) {
break
}
openProjectResults += OpenProjectResult(file, line, column, tempProject, shouldWait, lightEditMode)
if (shouldWait) break
column = -1
line = column
tempProject = false
i++
}
return Result.success(
openProjectResults.ifEmpty {
listOf(NoProjectResult(
shouldWait = shouldWait,
lightEditMode = lightEditMode
))
}
)
return Result.success(openProjectResults.ifEmpty { listOf(NoProjectResult(shouldWait, lightEditMode)) })
}
private fun parseFilePath(path: String, currentDirectory: String?): Path? {
@@ -500,41 +467,25 @@ object CommandLineProcessor {
}
}
private suspend fun openFileOrProject(file: Path,
line: Int,
column: Int,
tempProject: Boolean,
shouldWait: Boolean,
lightEditMode: Boolean): CommandLineProcessorResult {
return LightEditUtil.computeWithCommandLineOptions(shouldWait, lightEditMode).use {
val asFile = line != -1 || tempProject
if (asFile) {
doOpenFile(file, line, column, tempProject, shouldWait)
}
else {
doOpenFileOrProject(file, shouldWait)
}
}
private suspend fun openFileOrProject(
file: Path,
line: Int,
column: Int,
tempProject: Boolean,
shouldWait: Boolean,
lightEditMode: Boolean,
): CommandLineProcessorResult = LightEditUtil.computeWithCommandLineOptions(shouldWait, lightEditMode).use {
val asFile = line != -1 || tempProject
if (asFile) doOpenFile(file, line, column, tempProject, shouldWait)
else doOpenFileOrProject(file, shouldWait)
}
}
private const val APP_STARTER_EP_NAME = "com.intellij.appStarter"
/**
* Returns name of the command for this [ApplicationStarter] specified in plugin.xml file.
* It should be used instead of deprecated [ApplicationStarter.commandName].
*/
@get:Internal
@Deprecated("Replace with a hard-coded name", level = DeprecationLevel.ERROR)
@Suppress("unused")
@get:ApiStatus.Internal
val ApplicationStarter.commandNameFromExtension: String?
get() {
return ExtensionPointName<ApplicationStarter>(APP_STARTER_EP_NAME)
.filterableLazySequence()
.find { it.implementationClassName == javaClass.name }
?.id
}
@Suppress("DEPRECATION")
@Internal
fun findStarter(key: String): ApplicationStarter? {
return ExtensionPointName<ApplicationStarter>(APP_STARTER_EP_NAME).findByIdOrFromInstance(key) { it.commandName }
}
get() = ExtensionPointName<ApplicationStarter>("com.intellij.appStarter")
.filterableLazySequence()
.find { it.implementationClassName == javaClass.name }
?.id
@@ -1,9 +1,8 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.plugins;
import com.intellij.externalDependencies.DependencyOnPlugin;
import com.intellij.externalDependencies.ExternalDependenciesManager;
import com.intellij.ide.CommandLineProcessorKt;
import com.intellij.ide.impl.OpenProjectTaskKt;
import com.intellij.ide.impl.ProjectUtil;
import com.intellij.openapi.application.ApplicationStarter;
@@ -24,6 +23,7 @@ import java.util.*;
@SuppressWarnings("UseOfSystemOutOrSystemErr")
public class HeadlessPluginsInstaller implements ApplicationStarter {
private static final Logger LOG = Logger.getInstance(HeadlessPluginsInstaller.class);
private static final String COMMAND_NAME = "installPlugins";
@Override
public int getRequiredModality() {
@@ -79,8 +79,7 @@ public class HeadlessPluginsInstaller implements ApplicationStarter {
}
}
private void printUsageHint() {
var commandName = CommandLineProcessorKt.getCommandNameFromExtension(this);
private static void printUsageHint() {
System.out.printf(
"""
Usage: %s pluginId* repository* (--for-project=<project-path>)* [--give-consent-to-use-third-party-plugins]
@@ -89,7 +88,7 @@ public class HeadlessPluginsInstaller implements ApplicationStarter {
If `--for-project` is specified, also installs the required plugins for a project located at <project-path>.
If `--give-consent-to-use-third-party-plugins` is specified, installed third-party plugins will be approved automatically.
Without this option, if a third-party plugin is installed, a user will be asked to approve it when the IDE starts.%n""",
commandName);
COMMAND_NAME);
}
private static void collectProjectRequiredPlugins(Collection<PluginId> collector, List<Path> projectPaths) {
@@ -3,10 +3,10 @@ package com.intellij.openapi.application
import com.intellij.configurationStore.saveSettings
import com.intellij.ide.CliResult
import com.intellij.ide.commandNameFromExtension
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.vfs.VirtualFile
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
@@ -17,6 +17,7 @@ import kotlin.system.exitProcess
@Internal
abstract class ApplicationStarterBase protected constructor(private vararg val argsCount: Int) : ModernApplicationStarter() {
abstract val commandName: @NlsSafe String
abstract val usageMessage: @NlsContexts.DialogMessage String?
override val isHeadless: Boolean
@@ -35,7 +36,6 @@ abstract class ApplicationStarterBase protected constructor(private vararg val a
override fun canProcessExternalCommandLine(): Boolean = true
override suspend fun processExternalCommandLine(args: List<String>, currentDirectory: String?): CliResult {
val commandName = commandNameFromExtension
if (!checkArguments(args)) {
val title = ApplicationBundle.message("app.command.exec.error.title", commandName)
withContext(Dispatchers.EDT) {
@@ -59,8 +59,7 @@ abstract class ApplicationStarterBase protected constructor(private vararg val a
}
}
protected open fun checkArguments(args: List<String>): Boolean =
Arrays.binarySearch(argsCount, args.size - 1) >= 0 && commandNameFromExtension == args[0]
protected open fun checkArguments(args: List<String>): Boolean = Arrays.binarySearch(argsCount, args.size - 1) >= 0
protected abstract suspend fun executeCommand(args: List<String>, currentDirectory: String?): CliResult
@@ -14,6 +14,7 @@ import com.intellij.ide.startup.StartupActionScriptManager;
import com.intellij.ide.startup.StartupActionScriptManager.ActionCommand;
import com.intellij.ide.ui.laf.LookAndFeelThemeAdapterKt;
import com.intellij.idea.AppMode;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.application.impl.ApplicationInfoImpl;
import com.intellij.openapi.application.migrations.NotebooksMigration242;
import com.intellij.openapi.application.migrations.SpaceMigration252;
@@ -76,7 +77,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.intellij.ide.CommandLineProcessorKt.isIdeStartupWizardEnabled;
import static com.intellij.ide.SpecialConfigFiles.*;
import static com.intellij.ide.plugins.BundledPluginsState.BUNDLED_PLUGINS_FILENAME;
import static com.intellij.openapi.application.ImportOldConfigsUsagesCollector.InitialImportScenario.*;
@@ -218,7 +218,7 @@ public final class ConfigImportHelper {
log.error("Couldn't backup current config or delete current config directory", e);
}
}
else if (isIdeStartupWizardEnabled()) {
else if (isStartupWizardEnabled()) {
if (!guessedOldConfigDirs.isEmpty() && !shouldAskForConfig()) {
var bestConfigGuess = guessedOldConfigDirs.getFirstItem();
if (!isConfigOld(bestConfigGuess.second)) {
@@ -230,31 +230,28 @@ public final class ConfigImportHelper {
}
}
}
else {
var askForConfig = shouldAskForConfig();
if (askForConfig) {
else if (shouldAskForConfig()) {
oldConfigDirAndOldIdePath = showDialogAndGetOldConfigPath(guessedOldConfigDirs.getPaths());
importScenarioStatistics = SHOW_DIALOG_REQUESTED_BY_PROPERTY;
}
else if (guessedOldConfigDirs.isEmpty()) {
if (!veryFirstStartOnThisComputer) {
oldConfigDirAndOldIdePath = showDialogAndGetOldConfigPath(guessedOldConfigDirs.getPaths());
importScenarioStatistics = SHOW_DIALOG_REQUESTED_BY_PROPERTY;
importScenarioStatistics = SHOW_DIALOG_NO_CONFIGS_FOUND;
}
else if (guessedOldConfigDirs.isEmpty()) {
if (!veryFirstStartOnThisComputer) {
oldConfigDirAndOldIdePath = showDialogAndGetOldConfigPath(guessedOldConfigDirs.getPaths());
importScenarioStatistics = SHOW_DIALOG_NO_CONFIGS_FOUND;
}
}
else {
var bestConfigGuess = guessedOldConfigDirs.getFirstItem();
if (isConfigOld(bestConfigGuess.second)) {
log.info("The best config guess [" + bestConfigGuess.first + "] is too old, it won't be used for importing.");
oldConfigDirAndOldIdePath = showDialogAndGetOldConfigPath(guessedOldConfigDirs.getPaths());
importScenarioStatistics = SHOW_DIALOG_CONFIGS_ARE_TOO_OLD;
}
else {
var bestConfigGuess = guessedOldConfigDirs.getFirstItem();
if (isConfigOld(bestConfigGuess.second)) {
log.info("The best config guess [" + bestConfigGuess.first + "] is too old, it won't be used for importing.");
oldConfigDirAndOldIdePath = showDialogAndGetOldConfigPath(guessedOldConfigDirs.getPaths());
importScenarioStatistics = SHOW_DIALOG_CONFIGS_ARE_TOO_OLD;
}
else {
oldConfigDirAndOldIdePath = findConfigDirectoryByPath(bestConfigGuess.first);
if (oldConfigDirAndOldIdePath == null) {
logRejectedConfigDirectory(log, "Previous config directory", bestConfigGuess.first);
importScenarioStatistics = CONFIG_DIRECTORY_NOT_FOUND;
}
oldConfigDirAndOldIdePath = findConfigDirectoryByPath(bestConfigGuess.first);
if (oldConfigDirAndOldIdePath == null) {
logRejectedConfigDirectory(log, "Previous config directory", bestConfigGuess.first);
importScenarioStatistics = CONFIG_DIRECTORY_NOT_FOUND;
}
}
}
@@ -480,6 +477,13 @@ public final class ConfigImportHelper {
}
}
public static boolean isStartupWizardEnabled() {
return
(!ApplicationManagerEx.isInIntegrationTest() || Boolean.getBoolean("show.wizard.in.test")) &&
!AppMode.isRemoteDevHost() &&
Boolean.parseBoolean(System.getProperty("intellij.startup.wizard", "true"));
}
private static boolean shouldAskForConfig() {
if (!canAskForConfig()) {
return false;
@@ -1,6 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("ConstPropertyName")
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.application
import com.intellij.ide.CliResult
@@ -13,6 +11,7 @@ private const val ourRestartParameter = "--restart"
@ApiStatus.Internal
class ExitStarter private constructor() : ApplicationStarterBase(0, 1, 2) {
override val commandName: String get() = "exit"
override val usageMessage: String
get() = IdeBundle.message("wrong.number.of.arguments.usage.ide.executable.exit")
@@ -33,4 +32,4 @@ class ExitStarter private constructor() : ApplicationStarterBase(0, 1, 2) {
application.invokeLater({ application.exit(true, true, restart) }, ModalityState.nonModal())
}
}
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.application
import com.intellij.configurationStore.saveSettings
@@ -9,6 +9,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
private class SaveStarter() : ApplicationStarterBase(0) {
override val commandName: String get() = "save"
override val usageMessage: String
get() = IdeBundle.message("wrong.number.of.arguments.usage.ide.executable.save")
@@ -21,4 +22,4 @@ private class SaveStarter() : ApplicationStarterBase(0) {
saveSettings(ApplicationManager.getApplication())
return CliResult.OK
}
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ui.win
import com.intellij.ide.CliResult
@@ -9,6 +9,7 @@ import com.intellij.ui.AppIcon
import java.nio.file.Path
internal class RecentProjectApplication : ApplicationStarterBase(1) {
override val commandName: String get() = "reopen"
override val usageMessage: String
get() = "This command is used for internal purpose only." //NON-NLS
@@ -21,4 +22,4 @@ internal class RecentProjectApplication : ApplicationStarterBase(1) {
}
return CliResult.OK
}
}
}
@@ -25,7 +25,6 @@ import com.intellij.cce.util.ExceptionsUtil.stackTraceToString
import com.intellij.cce.workspace.Config
import com.intellij.cce.workspace.ConfigFactory
import com.intellij.cce.workspace.EvaluationWorkspace
import com.intellij.ide.commandNameFromExtension
import com.intellij.openapi.application.ApplicationStarter
import com.intellij.openapi.application.ex.ApplicationEx.FORCE_EXIT
import com.intellij.openapi.application.ex.ApplicationManagerEx
@@ -41,8 +40,7 @@ internal class CompletionEvaluationStarter : ApplicationStarter {
get() = ApplicationStarter.NOT_IN_EDT
override fun main(args: List<String>) {
fun run() = MainEvaluationCommand()
fun run() = MainEvaluationCommand("ml-evaluate")
.subcommands(
FullCommand(),
GenerateActionsCommand(),
@@ -59,7 +57,6 @@ internal class CompletionEvaluationStarter : ApplicationStarter {
}
.main(args.toList().subList(1, args.size))
val startTimestamp = System.currentTimeMillis()
try {
run()
@@ -102,7 +99,7 @@ internal class CompletionEvaluationStarter : ApplicationStarter {
}
}
inner class MainEvaluationCommand : EvaluationCommand(commandNameFromExtension!!, "Evaluate code completion quality in headless mode") {
class MainEvaluationCommand(name: String) : EvaluationCommand(name, "Evaluate code completion quality in headless mode") {
override fun run() = Unit
}
@@ -248,7 +245,7 @@ internal class CompletionEvaluationStarter : ApplicationStarter {
try {
sessionsStorage.getSessionFiles()
}
catch (e: Throwable) {
catch (_: Throwable) {
logger.warn("Failed to get session files from workspace ${this.path()}. Probably some evaluation builds failed")
emptyList()
}
@@ -288,4 +285,4 @@ private fun exit(exitCode: Int) {
private fun fatalError(msg: String) {
System.err.println("Evaluation failed: $msg")
exit(1)
}
}
@@ -1,12 +1,12 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.startup.importSettings
import com.intellij.ide.isIdeStartupWizardEnabled
import com.intellij.ide.startup.importSettings.chooser.ui.OnboardingController
import com.intellij.ide.startup.importSettings.data.SettingsService
import com.intellij.ide.startup.importSettings.data.StartupWizardService
import com.intellij.ide.startup.importSettings.statistics.ImportSettingsEventsCollector
import com.intellij.openapi.application.ApplicationNamesInfo
import com.intellij.openapi.application.ConfigImportHelper
import com.intellij.openapi.diagnostic.logger
import com.intellij.platform.ide.bootstrap.IdeStartupWizard
import com.intellij.util.concurrency.ThreadingAssertions
@@ -15,7 +15,7 @@ import kotlinx.coroutines.coroutineScope
private class IdeStartupWizardImpl : IdeStartupWizard {
override suspend fun run() {
if (!isIdeStartupWizardEnabled) return
if (!ConfigImportHelper.isStartupWizardEnabled()) return
logger.info("Initial startup wizard is enabled. Will start the wizard.")
ThreadingAssertions.assertEventDispatchThread()