[collab/gitlab/github] prefer plugin project scope over orphan scope

GitOrigin-RevId: cfb4559bd6b8f27a872a65a4fc33ebedcc143eaa
This commit is contained in:
Ivan Semenov
2024-06-10 15:06:51 +02:00
committed by intellij-monorepo-bot
parent 06de58f442
commit 5ce86532d3
10 changed files with 148 additions and 50 deletions

View File

@@ -491,6 +491,10 @@ a:com.intellij.collaboration.auth.ServerAccount
- <init>(com.intellij.openapi.project.Project,java.awt.Component,com.intellij.collaboration.auth.ui.login.LoginModel,java.lang.String,kotlinx.coroutines.flow.Flow,kotlin.jvm.functions.Function1):V
- b:<init>(com.intellij.openapi.project.Project,java.awt.Component,com.intellij.collaboration.auth.ui.login.LoginModel,java.lang.String,kotlinx.coroutines.flow.Flow,kotlin.jvm.functions.Function1,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- <init>(com.intellij.openapi.project.Project,java.awt.Component,com.intellij.collaboration.auth.ui.login.LoginModel,kotlin.jvm.functions.Function1):V
- <init>(com.intellij.openapi.project.Project,kotlinx.coroutines.CoroutineScope,java.awt.Component,com.intellij.collaboration.auth.ui.login.LoginModel,java.lang.String,kotlin.jvm.functions.Function1):V
- <init>(com.intellij.openapi.project.Project,kotlinx.coroutines.CoroutineScope,java.awt.Component,com.intellij.collaboration.auth.ui.login.LoginModel,java.lang.String,kotlinx.coroutines.flow.Flow,kotlin.jvm.functions.Function1):V
- b:<init>(com.intellij.openapi.project.Project,kotlinx.coroutines.CoroutineScope,java.awt.Component,com.intellij.collaboration.auth.ui.login.LoginModel,java.lang.String,kotlinx.coroutines.flow.Flow,kotlin.jvm.functions.Function1,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- <init>(com.intellij.openapi.project.Project,kotlinx.coroutines.CoroutineScope,java.awt.Component,com.intellij.collaboration.auth.ui.login.LoginModel,kotlin.jvm.functions.Function1):V
*f:com.intellij.collaboration.auth.ui.login.TokenLoginInputPanelFactory
- *sf:Companion:com.intellij.collaboration.auth.ui.login.TokenLoginInputPanelFactory$Companion
- <init>(com.intellij.collaboration.auth.ui.login.TokenLoginPanelModel):V

View File

@@ -0,0 +1,30 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.collaboration.async
import com.intellij.openapi.Disposable
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.platform.util.coroutines.childScope
import kotlinx.coroutines.CoroutineScope
import org.jetbrains.annotations.ApiStatus
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
/**
* A base class for a plugin service which can supply a coroutine scope to places where structured concurrency is not possible
* Can be subclassed and registered as a light service
*/
@ApiStatus.Internal
open class PluginScopeProviderBase(private val parentCs: CoroutineScope) {
fun createDisposedScope(name: String, disposable: Disposable, context: CoroutineContext = EmptyCoroutineContext): CoroutineScope {
return parentCs.childScope(name, context).apply {
cancelledWith(disposable)
}
}
fun <D : DialogWrapper> constructDialog(name: String, constructor: CoroutineScope.() -> D): D {
val cs = parentCs.childScope(name)
return cs.constructor().also {
cs.cancelledWith(it.disposable)
}
}
}

View File

@@ -1,31 +1,41 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.collaboration.auth.ui.login
import com.intellij.collaboration.async.DisposingMainScope
import com.intellij.collaboration.messages.CollaborationToolsBundle
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.asContextElement
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogPanel
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.util.NlsContexts
import kotlinx.coroutines.CoroutineScope
import com.intellij.platform.util.coroutines.childScope
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import java.awt.Component
import javax.swing.JComponent
class TokenLoginDialog @JvmOverloads constructor(
project: Project?, parent: Component?,
project: Project?, parentCs: CoroutineScope, parent: Component?,
private val model: LoginModel,
@NlsContexts.DialogTitle title: String = CollaborationToolsBundle.message("login.dialog.title"),
title: @NlsContexts.DialogTitle String = CollaborationToolsBundle.message("login.dialog.title"),
private val userCustomExitSignal: Flow<Unit>? = null,
private val centerPanelSupplier: CoroutineScope.() -> DialogPanel
) : DialogWrapper(project, parent, false, IdeModalityType.IDE) {
private val uiScope = DisposingMainScope(disposable) + ModalityState.stateForComponent(rootPane).asContextElement()
@Deprecated("A proper coroutine scope should be provided")
@OptIn(DelicateCoroutinesApi::class)
@JvmOverloads
constructor(
project: Project?, parent: Component?,
model: LoginModel,
title: @NlsContexts.DialogTitle String = CollaborationToolsBundle.message("login.dialog.title"),
userCustomExitSignal: Flow<Unit>? = null,
centerPanelSupplier: CoroutineScope.() -> DialogPanel
) : this(project, GlobalScope, parent, model, title, userCustomExitSignal, centerPanelSupplier)
private val uiScope = parentCs.childScope(javaClass.name, Dispatchers.EDT + ModalityState.stateForComponent(rootPane).asContextElement())
init {
setOKButtonText(CollaborationToolsBundle.message("login.button"))

View File

@@ -1,6 +1,7 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.github.authentication
import com.intellij.collaboration.async.cancelledWith
import com.intellij.collaboration.messages.CollaborationToolsBundle
import com.intellij.ide.DataManager
import com.intellij.ide.passwordSafe.PasswordSafe
@@ -16,13 +17,13 @@ import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.text.HtmlBuilder
import com.intellij.openapi.util.text.HtmlChunk
import com.intellij.platform.util.coroutines.childScope
import com.intellij.ui.CollectionComboBoxModel
import com.intellij.ui.components.DropDownLink
import com.intellij.util.AuthData
import com.intellij.util.concurrency.annotations.RequiresEdt
import git4idea.DialogManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
import org.jetbrains.plugins.github.api.GithubServerPath
import org.jetbrains.plugins.github.authentication.accounts.GHAccountManager
import org.jetbrains.plugins.github.authentication.accounts.GithubAccount
@@ -30,6 +31,7 @@ import org.jetbrains.plugins.github.authentication.accounts.GithubProjectDefault
import org.jetbrains.plugins.github.authentication.ui.GHLoginDialog
import org.jetbrains.plugins.github.authentication.ui.GHLoginModel
import org.jetbrains.plugins.github.i18n.GithubBundle
import org.jetbrains.plugins.github.ui.util.GHPluginProjectScopeProvider
import java.awt.Component
import javax.swing.JButton
import javax.swing.JComponent
@@ -81,20 +83,22 @@ object GHAccountsUtil {
val group = DefaultActionGroup()
group.add(
DumbAwareAction.create(GithubBundle.message("action.Github.Accounts.AddGHAccount.text")) {
GHLoginDialog.OAuth(model, project, parentComponent).apply {
setServer(GithubServerPath.DEFAULT_HOST, false)
showAndGet()
}
scopedDialog(project) {
GHLoginDialog.OAuth(model, project, this, parentComponent).apply {
setServer(GithubServerPath.DEFAULT_HOST, false)
}
}.showAndGet()
})
group.add(
DumbAwareAction.create(GithubBundle.message("action.Github.Accounts.AddGHAccountWithToken.text")) {
GHLoginDialog.Token(model, project, parentComponent).apply {
title = GithubBundle.message("dialog.title.add.github.account")
setLoginButtonText(GithubBundle.message("accounts.add.button"))
setServer(GithubServerPath.DEFAULT_HOST, false)
showAndGet()
}
scopedDialog(project) {
GHLoginDialog.Token(model, project, this, parentComponent).apply {
title = GithubBundle.message("dialog.title.add.github.account")
setLoginButtonText(GithubBundle.message("accounts.add.button"))
setServer(GithubServerPath.DEFAULT_HOST, false)
}
}.showAndGet()
}
)
@@ -102,12 +106,13 @@ object GHAccountsUtil {
group.add(
DumbAwareAction.create(GithubBundle.message("action.Github.Accounts.AddGHEAccount.text")) {
GHLoginDialog.Token(model, project, parentComponent).apply {
title = GithubBundle.message("dialog.title.add.github.account")
setServer("", true)
setLoginButtonText(GithubBundle.message("accounts.add.button"))
showAndGet()
}
scopedDialog(project) {
GHLoginDialog.Token(model, project, this, parentComponent).apply {
title = GithubBundle.message("dialog.title.add.github.account")
setServer("", true)
setLoginButtonText(GithubBundle.message("accounts.add.button"))
}
}.showAndGet()
}
)
return group
@@ -205,15 +210,34 @@ private fun GHLoginRequest.configure(dialog: GHLoginDialog) {
login?.let { dialog.setLogin(it, isLoginEditable) }
}
@OptIn(DelicateCoroutinesApi::class)
private fun <D : GHLoginDialog> scopedDialog(project: Project?, dialogConstructor: CoroutineScope.() -> D): D {
if (project != null) {
return project.service<GHPluginProjectScopeProvider>().constructDialog("GitHub login dialog", dialogConstructor)
}
else {
val cs = GlobalScope.childScope(GHLoginDialog::class.java.name)
val dialog = cs.dialogConstructor()
cs.cancelledWith(dialog.disposable)
return dialog
}
}
private fun GHLoginRequest.loginWithToken(model: GHLoginModel, project: Project?, parentComponent: Component?) {
val dialog = GHLoginDialog.Token(model, project, parentComponent)
configure(dialog)
val dialog = scopedDialog(project) {
GHLoginDialog.Token(model, project, this, parentComponent).also {
configure(it)
}
}
DialogManager.show(dialog)
}
private fun GHLoginRequest.loginWithOAuth(model: GHLoginModel, project: Project?, parentComponent: Component?) {
val dialog = GHLoginDialog.OAuth(model, project, parentComponent)
configure(dialog)
val dialog = scopedDialog(project) {
GHLoginDialog.OAuth(model, project, this, parentComponent).also {
configure(it)
}
}
DialogManager.show(dialog)
}

View File

@@ -1,7 +1,7 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.github.authentication.ui
import com.intellij.collaboration.async.DisposingMainScope
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.asContextElement
import com.intellij.openapi.project.Project
@@ -9,13 +9,14 @@ import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.ui.DialogWrapper.IS_VISUAL_PADDING_COMPENSATED_ON_COMPONENT_LEVEL_KEY
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.util.NlsContexts
import com.intellij.platform.util.coroutines.childScope
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.update.UiNotifyConnector
import git4idea.i18n.GitBundle
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import org.jetbrains.plugins.github.api.GithubApiRequestExecutor
import org.jetbrains.plugins.github.api.GithubServerPath
import org.jetbrains.plugins.github.i18n.GithubBundle
@@ -29,10 +30,11 @@ internal fun JComponent.setPaddingCompensated(): JComponent =
internal sealed class GHLoginDialog(
private val model: GHLoginModel,
project: Project?,
parentCs: CoroutineScope,
parent: Component?
) : DialogWrapper(project, parent, false, IdeModalityType.IDE) {
private val cs = DisposingMainScope(disposable) + ModalityState.stateForComponent(window).asContextElement()
private val cs = parentCs.childScope(javaClass.name, Dispatchers.EDT + ModalityState.stateForComponent(window).asContextElement())
protected val loginPanel = GithubLoginPanel(cs, GithubApiRequestExecutor.Factory.getInstance()) { login, server ->
model.isAccountUnique(server, login)
@@ -69,8 +71,8 @@ internal sealed class GHLoginDialog(
}
class Token(model: GHLoginModel, project: Project?, parent: Component?) :
GHLoginDialog(model, project, parent) {
class Token(model: GHLoginModel, project: Project?, parentCs: CoroutineScope, parent: Component?) :
GHLoginDialog(model, project, parentCs, parent) {
init {
title = GithubBundle.message("login.to.github")
@@ -85,8 +87,8 @@ internal sealed class GHLoginDialog(
override fun createCenterPanel(): JComponent = loginPanel.setPaddingCompensated()
}
class OAuth(model: GHLoginModel, project: Project?, parent: Component?) :
GHLoginDialog(model, project, parent) {
class OAuth(model: GHLoginModel, project: Project?, parentCs: CoroutineScope, parent: Component?) :
GHLoginDialog(model, project, parentCs, parent) {
init {
title = GithubBundle.message("login.to.github")

View File

@@ -1,9 +1,9 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.github.ui
import com.intellij.collaboration.async.DisposingMainScope
import com.intellij.collaboration.auth.ui.AccountsPanelFactory
import com.intellij.collaboration.auth.ui.AccountsPanelFactory.Companion.addWarningForMemoryOnlyPasswordSafeAndGet
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.asContextElement
import com.intellij.openapi.components.service
@@ -11,13 +11,14 @@ import com.intellij.openapi.options.BoundConfigurable
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogPanel
import com.intellij.ui.dsl.builder.*
import kotlinx.coroutines.plus
import kotlinx.coroutines.Dispatchers
import org.jetbrains.plugins.github.authentication.accounts.GHAccountManager
import org.jetbrains.plugins.github.authentication.accounts.GithubProjectDefaultAccountHolder
import org.jetbrains.plugins.github.authentication.ui.GHAccountsDetailsProvider
import org.jetbrains.plugins.github.authentication.ui.GHAccountsListModel
import org.jetbrains.plugins.github.authentication.ui.GHAccountsPanelActionsController
import org.jetbrains.plugins.github.i18n.GithubBundle.message
import org.jetbrains.plugins.github.ui.util.GHPluginProjectScopeProvider
import org.jetbrains.plugins.github.util.GithubSettings
import org.jetbrains.plugins.github.util.GithubUtil
@@ -25,11 +26,13 @@ internal class GithubSettingsConfigurable internal constructor(
private val project: Project
) : BoundConfigurable(GithubUtil.SERVICE_DISPLAY_NAME, "settings.github") {
override fun createPanel(): DialogPanel {
val scopeProvider = project.service<GHPluginProjectScopeProvider>()
val defaultAccountHolder = project.service<GithubProjectDefaultAccountHolder>()
val accountManager = service<GHAccountManager>()
val ghSettings = GithubSettings.getInstance()
val scope = DisposingMainScope(disposable!!) + ModalityState.any().asContextElement()
val scope = scopeProvider.createDisposedScope(javaClass.name, disposable!!,
Dispatchers.EDT + ModalityState.any().asContextElement())
val accountsModel = GHAccountsListModel()
val detailsProvider = GHAccountsDetailsProvider(scope, accountManager, accountsModel)

View File

@@ -0,0 +1,9 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.github.ui.util
import com.intellij.collaboration.async.PluginScopeProviderBase
import com.intellij.openapi.components.Service
import kotlinx.coroutines.CoroutineScope
@Service(Service.Level.PROJECT)
internal class GHPluginProjectScopeProvider(parentCs: CoroutineScope) : PluginScopeProviderBase(parentCs)

View File

@@ -1,10 +1,10 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gitlab
import com.intellij.collaboration.async.DisposingMainScope
import com.intellij.collaboration.auth.ui.AccountsPanelFactory
import com.intellij.collaboration.auth.ui.AccountsPanelFactory.Companion.addWarningForMemoryOnlyPasswordSafeAndGet
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.asContextElement
import com.intellij.openapi.components.*
@@ -15,7 +15,7 @@ import com.intellij.ui.dsl.builder.Align
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.bindSelected
import com.intellij.ui.dsl.builder.panel
import kotlinx.coroutines.plus
import kotlinx.coroutines.Dispatchers
import kotlinx.serialization.Serializable
import org.jetbrains.plugins.gitlab.api.GitLabApiManager
import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccountManager
@@ -23,16 +23,19 @@ import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabProjectDefault
import org.jetbrains.plugins.gitlab.authentication.ui.GitLabAccountsDetailsProvider
import org.jetbrains.plugins.gitlab.authentication.ui.GitLabAccountsListModel
import org.jetbrains.plugins.gitlab.authentication.ui.GitLabAccountsPanelActionsController
import org.jetbrains.plugins.gitlab.ui.util.GitLabPluginProjectScopeProvider
import org.jetbrains.plugins.gitlab.util.GitLabBundle.message
import org.jetbrains.plugins.gitlab.util.GitLabUtil
internal class GitLabSettingsConfigurable(private val project: Project)
: BoundConfigurable(GitLabUtil.SERVICE_DISPLAY_NAME, "settings.gitlab") {
override fun createPanel(): DialogPanel {
val scopeProvider = project.service<GitLabPluginProjectScopeProvider>()
val accountManager = service<GitLabAccountManager>()
val defaultAccountHolder = project.service<GitLabProjectDefaultAccountHolder>()
val scope = DisposingMainScope(disposable!!) + ModalityState.any().asContextElement()
val scope = scopeProvider.createDisposedScope(javaClass.name, disposable!!,
Dispatchers.EDT + ModalityState.any().asContextElement())
val accountsModel = GitLabAccountsListModel()
val detailsProvider = GitLabAccountsDetailsProvider(scope, accountsModel) { account ->
accountsModel.newCredentials.getOrElse(account) {

View File

@@ -18,6 +18,7 @@ import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabAccount
import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabProjectDefaultAccountHolder
import org.jetbrains.plugins.gitlab.authentication.ui.GitLabChooseAccountDialog
import org.jetbrains.plugins.gitlab.authentication.ui.GitLabTokenLoginPanelModel
import org.jetbrains.plugins.gitlab.ui.util.GitLabPluginProjectScopeProvider
import org.jetbrains.plugins.gitlab.util.GitLabBundle
import java.awt.Component
import javax.swing.JComponent
@@ -95,14 +96,17 @@ object GitLabLoginUtil {
title: @NlsContexts.DialogTitle String,
serverFieldDisabled: Boolean
): Int {
val dialog = TokenLoginDialog(project, parentComponent, model, title, model.tryGitAuthorizationSignal) {
val cs = this
TokenLoginInputPanelFactory(model).createIn(
cs,
serverFieldDisabled,
tokenNote = CollaborationToolsBundle.message("clone.dialog.insufficient.scopes", GitLabSecurityUtil.MASTER_SCOPES),
errorPresenter = GitLabLoginErrorStatusPresenter(cs, model)
)
val scopeProvider = project.service<GitLabPluginProjectScopeProvider>()
val dialog = scopeProvider.constructDialog("GitLab token login dialog") {
TokenLoginDialog(project, this, parentComponent, model, title, model.tryGitAuthorizationSignal) {
val cs = this
TokenLoginInputPanelFactory(model).createIn(
cs,
serverFieldDisabled,
tokenNote = CollaborationToolsBundle.message("clone.dialog.insufficient.scopes", GitLabSecurityUtil.MASTER_SCOPES),
errorPresenter = GitLabLoginErrorStatusPresenter(cs, model)
)
}
}
dialog.showAndGet()

View File

@@ -0,0 +1,9 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gitlab.ui.util
import com.intellij.collaboration.async.PluginScopeProviderBase
import com.intellij.openapi.components.Service
import kotlinx.coroutines.CoroutineScope
@Service(Service.Level.PROJECT)
internal class GitLabPluginProjectScopeProvider(parentCs: CoroutineScope) : PluginScopeProviderBase(parentCs)