From a856cfd7fa9b7b1abf67883e79645eb8da2d661e Mon Sep 17 00:00:00 2001 From: Piotr Kukielka Date: Wed, 16 Oct 2024 16:32:33 +0200 Subject: [PATCH] Switch to webview-based sign-in flow --- .../cody/util/CodyIntegrationTextFixture.kt | 4 +- .../com/sourcegraph/cody/CodyActionGroup.java | 4 +- .../cody/CodyToolWindowFactory.java | 2 - .../com/sourcegraph/cody/api/Promises.java | 19 -- .../cody/chat/ChatUIConstants.java | 6 - .../cody/chat/CodeEditorFactory.java | 8 - .../config/CodyAuthNotificationActivity.java | 30 +- .../GoToPluginSettingsButtonFactory.java | 3 +- .../config/OpenPluginSettingsAction.java | 25 -- .../find/browser/BrowserAndLoadingPanel.java | 6 +- .../find/browser/SourcegraphJBCefBrowser.java | 6 - .../sourcegraph/cody/CodyToolWindowContent.kt | 67 +--- .../com/sourcegraph/cody/agent/CodyAgent.kt | 2 + .../sourcegraph/cody/agent/CodyAgentClient.kt | 39 +++ .../cody/api/SourcegraphApiRequestExecutor.kt | 2 +- .../cody/api/SourcegraphApiRequests.kt | 29 +- .../com/sourcegraph/cody/auth/Account.kt | 34 -- .../sourcegraph/cody/auth/AccountDetails.kt | 7 - .../sourcegraph/cody/auth/AccountManager.kt | 34 -- .../cody/auth/AccountManagerBase.kt | 138 -------- .../sourcegraph/cody/auth/AccountsListener.kt | 10 - .../cody/auth/AccountsRepository.kt | 8 - .../cody/auth/AuthCallbackHandler.kt | 45 --- .../com/sourcegraph/cody/auth/AuthRequest.kt | 10 - .../com/sourcegraph/cody/auth/AuthService.kt | 13 - .../sourcegraph/cody/auth/AuthServiceBase.kt | 44 --- .../com/sourcegraph/cody/auth/CodyAccount.kt | 50 +++ .../sourcegraph/cody/auth/ServerAccount.kt | 11 - .../com/sourcegraph/cody/auth/ServerPath.kt | 7 - .../sourcegraph/cody/auth/SingleValueModel.kt | 32 -- .../cody/auth/SourcegraphAuthService.kt | 64 ---- .../{config => auth}/SourcegraphServerPath.kt | 6 +- .../sourcegraph/cody/auth/SsoAuthMethod.kt | 12 - .../auth/deprecated/DeprecatedCodyAccount.kt | 43 +++ .../DeprecatedCodyAccountManager.kt | 101 ++++++ .../DeprecatedCodyPersistentAccounts.kt} | 15 +- .../com/sourcegraph/cody/auth/package.md | 6 - .../cody/auth/ui/AccountsDetailsProvider.kt | 25 -- .../cody/auth/ui/AccountsListModel.kt | 28 -- .../cody/auth/ui/AccountsListModelBase.kt | 34 -- .../auth/ui/AccountsPanelFactoryExtensions.kt | 199 ----------- .../auth/ui/DeferredIconRepaintScheduler.kt | 121 ------- .../auth/ui/LoadingAccountsDetailsProvider.kt | 80 ----- .../cody/auth/ui/ScaleContextCache.kt | 32 -- .../cody/auth/ui/ScalingAsyncImageIcon.kt | 97 ------ .../ui/SignInWithEnterpriseInstanceAction.kt | 46 --- .../auth/ui/SimpleAccountsListCellRenderer.kt | 191 ----------- .../autocomplete/CodyAutocompleteManager.kt | 15 +- .../sourcegraph/cody/chat/OpenChatAction.kt | 7 +- .../cody/chat/SignInWithSourcegraphPanel.kt | 130 ------- .../cody/chat/actions/BaseCommandAction.kt | 7 + .../cody/chat/actions/NewChatAction.kt | 5 +- .../chat/ui/CodyOnboardingGuidancePanel.kt | 169 ---------- .../cody/config/BaseLoginDialog.kt | 107 ------ .../config/CachingCodyUserAvatarLoader.kt | 84 ----- .../sourcegraph/cody/config/CodyAccount.kt | 29 -- .../cody/config/CodyAccountCodyProEnabled.kt | 3 - .../cody/config/CodyAccountDetails.kt | 13 - .../cody/config/CodyAccountDetailsProvider.kt | 49 --- .../cody/config/CodyAccountListModel.kt | 87 ----- .../cody/config/CodyAccountManager.kt | 16 - .../cody/config/CodyAccountsHost.kt | 19 -- .../cody/config/CodyAuthCredentialsUi.kt | 68 ---- .../cody/config/CodyAuthenticationManager.kt | 318 ------------------ .../cody/config/CodyCredentialsUi.kt | 47 --- .../sourcegraph/cody/config/CodyLoginPanel.kt | 144 -------- .../cody/config/CodyLoginRequest.kt | 39 --- .../cody/config/CodyPersistentAccountsHost.kt | 30 -- .../cody/config/CodyTokenCredentialsUi.kt | 149 -------- .../cody/config/DialogValidationUtils.kt | 32 -- .../cody/config/LogInToSourcegraphAction.kt | 137 -------- .../com/sourcegraph/cody/config/ServerAuth.kt | 24 -- .../config/SourcegraphInstanceLoginDialog.kt | 260 -------------- .../config/migration/AccountsMigration.kt | 18 + .../config/migration/ChatHistoryMigration.kt | 8 +- .../config/migration/SettingsMigration.kt | 48 +-- .../AccountSettingChangeActionNotifier.kt | 17 - .../AccountSettingChangeContext.kt | 18 - .../AccountSettingChangeListener.kt | 43 --- .../cody/config/ui/AccountConfigurable.kt | 145 -------- .../cody/edit/actions/BaseEditCodeAction.kt | 31 +- .../cody/edit/actions/EditCodeAction.kt | 3 +- .../edit/lenses/actions/LensEditAction.kt | 5 +- .../cody/history/HistoryService.kt | 8 +- .../ignore/ActionInIgnoredFileNotification.kt | 4 +- .../EndOfTrialNotificationScheduler.kt | 4 +- .../initialization/PostStartupActivity.kt | 4 - .../cody/initialization/UninstallListener.kt | 6 +- .../cody/listeners/CodyDocumentListener.kt | 10 +- .../listeners/CodySelectionInlayManager.kt | 4 +- .../CodyDisableAutocompleteAction.kt | 7 +- ...odyDisableLanguageForAutocompleteAction.kt | 5 +- .../statusbar/CodyManageAccountsAction.kt | 12 - .../statusbar/CodyStatusBarActionGroup.kt | 1 - .../cody/statusbar/CodyStatusService.kt | 11 +- .../com/sourcegraph/cody/ui/HtmlViewer.kt | 41 --- .../cody/ui/UnderlinedActionLink.kt | 20 -- .../com/sourcegraph/cody/ui/web/WebUIHost.kt | 11 +- .../com/sourcegraph/config/ConfigUtil.kt | 40 +-- src/main/resources/META-INF/plugin.xml | 41 +-- .../cody/config/SettingsMigrationTest.kt | 10 +- .../cody/config/SourcegraphServerPathTest.kt | 1 + 102 files changed, 421 insertions(+), 3938 deletions(-) delete mode 100644 src/main/java/com/sourcegraph/cody/api/Promises.java delete mode 100644 src/main/java/com/sourcegraph/cody/chat/ChatUIConstants.java delete mode 100644 src/main/java/com/sourcegraph/cody/chat/CodeEditorFactory.java delete mode 100644 src/main/java/com/sourcegraph/config/OpenPluginSettingsAction.java delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/Account.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/AccountDetails.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/AccountManager.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/AccountManagerBase.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/AccountsListener.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/AccountsRepository.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/AuthCallbackHandler.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/AuthRequest.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/AuthService.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/AuthServiceBase.kt create mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/CodyAccount.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/ServerAccount.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/ServerPath.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/SingleValueModel.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/SourcegraphAuthService.kt rename src/main/kotlin/com/sourcegraph/cody/{config => auth}/SourcegraphServerPath.kt (95%) delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/SsoAuthMethod.kt create mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/deprecated/DeprecatedCodyAccount.kt create mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/deprecated/DeprecatedCodyAccountManager.kt rename src/main/kotlin/com/sourcegraph/cody/{config/CodyPersistentAccounts.kt => auth/deprecated/DeprecatedCodyPersistentAccounts.kt} (56%) delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/package.md delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/ui/AccountsDetailsProvider.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/ui/AccountsListModel.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/ui/AccountsListModelBase.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/ui/AccountsPanelFactoryExtensions.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/ui/DeferredIconRepaintScheduler.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/ui/LoadingAccountsDetailsProvider.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/ui/ScaleContextCache.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/ui/ScalingAsyncImageIcon.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/ui/SignInWithEnterpriseInstanceAction.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/auth/ui/SimpleAccountsListCellRenderer.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/chat/SignInWithSourcegraphPanel.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/chat/ui/CodyOnboardingGuidancePanel.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/BaseLoginDialog.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/CachingCodyUserAvatarLoader.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/CodyAccount.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/CodyAccountCodyProEnabled.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/CodyAccountDetails.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/CodyAccountDetailsProvider.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/CodyAccountListModel.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/CodyAccountManager.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/CodyAccountsHost.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/CodyAuthCredentialsUi.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/CodyAuthenticationManager.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/CodyCredentialsUi.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/CodyLoginPanel.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/CodyLoginRequest.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/CodyPersistentAccountsHost.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/CodyTokenCredentialsUi.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/DialogValidationUtils.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/LogInToSourcegraphAction.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/ServerAuth.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/SourcegraphInstanceLoginDialog.kt create mode 100644 src/main/kotlin/com/sourcegraph/cody/config/migration/AccountsMigration.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/notification/AccountSettingChangeActionNotifier.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/notification/AccountSettingChangeContext.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/notification/AccountSettingChangeListener.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/config/ui/AccountConfigurable.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/statusbar/CodyManageAccountsAction.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/ui/HtmlViewer.kt delete mode 100644 src/main/kotlin/com/sourcegraph/cody/ui/UnderlinedActionLink.kt diff --git a/src/integrationTest/kotlin/com/sourcegraph/cody/util/CodyIntegrationTextFixture.kt b/src/integrationTest/kotlin/com/sourcegraph/cody/util/CodyIntegrationTextFixture.kt index 8861c5a2ca..dc245c58ba 100644 --- a/src/integrationTest/kotlin/com/sourcegraph/cody/util/CodyIntegrationTextFixture.kt +++ b/src/integrationTest/kotlin/com/sourcegraph/cody/util/CodyIntegrationTextFixture.kt @@ -21,8 +21,8 @@ import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.intellij.testFramework.runInEdtAndWait import com.sourcegraph.cody.agent.CodyAgentService import com.sourcegraph.cody.agent.protocol_generated.ProtocolCodeLens -import com.sourcegraph.cody.config.CodyPersistentAccountsHost -import com.sourcegraph.cody.config.SourcegraphServerPath +import com.sourcegraph.cody.auth.CodyPersistentAccountsHost +import com.sourcegraph.cody.auth.SourcegraphServerPath import com.sourcegraph.cody.edit.lenses.LensListener import com.sourcegraph.cody.edit.lenses.LensesService import com.sourcegraph.cody.edit.lenses.providers.EditAcceptCodeVisionProvider diff --git a/src/main/java/com/sourcegraph/cody/CodyActionGroup.java b/src/main/java/com/sourcegraph/cody/CodyActionGroup.java index 5ff2652371..f5d6f7c2ee 100644 --- a/src/main/java/com/sourcegraph/cody/CodyActionGroup.java +++ b/src/main/java/com/sourcegraph/cody/CodyActionGroup.java @@ -3,6 +3,7 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.sourcegraph.cody.auth.CodyAccount; import com.sourcegraph.config.ConfigUtil; import org.jetbrains.annotations.NotNull; @@ -21,6 +22,7 @@ public boolean isDumbAware() { @Override public void update(@NotNull AnActionEvent e) { super.update(e); - e.getPresentation().setVisible(ConfigUtil.isCodyEnabled()); + e.getPresentation() + .setVisible(ConfigUtil.isCodyEnabled() && CodyAccount.Companion.hasActiveAccount()); } } diff --git a/src/main/java/com/sourcegraph/cody/CodyToolWindowFactory.java b/src/main/java/com/sourcegraph/cody/CodyToolWindowFactory.java index b5f6d4c4ea..1b04393229 100644 --- a/src/main/java/com/sourcegraph/cody/CodyToolWindowFactory.java +++ b/src/main/java/com/sourcegraph/cody/CodyToolWindowFactory.java @@ -10,7 +10,6 @@ import com.intellij.ui.content.ContentFactory; import com.sourcegraph.cody.config.actions.OpenCodySettingsEditorAction; import com.sourcegraph.config.ConfigUtil; -import com.sourcegraph.config.OpenPluginSettingsAction; import org.jetbrains.annotations.NotNull; public class CodyToolWindowFactory implements ToolWindowFactory, DumbAware { @@ -30,7 +29,6 @@ public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindo content.setPreferredFocusableComponent(null); toolWindow.getContentManager().addContent(content); DefaultActionGroup customCodySettings = new DefaultActionGroup(); - customCodySettings.add(new OpenPluginSettingsAction("Cody Settings...")); customCodySettings.add(new OpenCodySettingsEditorAction()); customCodySettings.addSeparator(); diff --git a/src/main/java/com/sourcegraph/cody/api/Promises.java b/src/main/java/com/sourcegraph/cody/api/Promises.java deleted file mode 100644 index c0c14e50b5..0000000000 --- a/src/main/java/com/sourcegraph/cody/api/Promises.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.sourcegraph.cody.api; - -import java.util.ArrayList; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; - -public class Promises { - /** Rough translation of `Promise.all` from JavaScript. */ - public static CompletableFuture> all(List> promises) { - Queue responses = new ConcurrentLinkedQueue<>(); - for (CompletableFuture promise : promises) { - promise.thenAccept(responses::add); - } - return CompletableFuture.allOf(promises.toArray(new CompletableFuture[0])) - .thenApply((ignore) -> new ArrayList<>(responses)); - } -} diff --git a/src/main/java/com/sourcegraph/cody/chat/ChatUIConstants.java b/src/main/java/com/sourcegraph/cody/chat/ChatUIConstants.java deleted file mode 100644 index 6a2d27b8f8..0000000000 --- a/src/main/java/com/sourcegraph/cody/chat/ChatUIConstants.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.sourcegraph.cody.chat; - -public class ChatUIConstants { - public static final int ASSISTANT_MESSAGE_GRADIENT_WIDTH = 2; - public static final int TEXT_MARGIN = 14; -} diff --git a/src/main/java/com/sourcegraph/cody/chat/CodeEditorFactory.java b/src/main/java/com/sourcegraph/cody/chat/CodeEditorFactory.java deleted file mode 100644 index d1bb82b8f3..0000000000 --- a/src/main/java/com/sourcegraph/cody/chat/CodeEditorFactory.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.sourcegraph.cody.chat; - -public class CodeEditorFactory { - - public static final int spaceBetweenButtons = 5; - - public static volatile String lastCopiedText = null; -} diff --git a/src/main/java/com/sourcegraph/config/CodyAuthNotificationActivity.java b/src/main/java/com/sourcegraph/config/CodyAuthNotificationActivity.java index 9cd0ed8e23..b29b9cd99a 100644 --- a/src/main/java/com/sourcegraph/config/CodyAuthNotificationActivity.java +++ b/src/main/java/com/sourcegraph/config/CodyAuthNotificationActivity.java @@ -5,18 +5,14 @@ import com.intellij.notification.Notifications; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowManager; import com.sourcegraph.Icons; import com.sourcegraph.cody.CodyToolWindowFactory; -import com.sourcegraph.cody.config.CodyAccount; -import com.sourcegraph.cody.config.CodyAccountManager; +import com.sourcegraph.cody.auth.CodyAccount; import com.sourcegraph.cody.config.CodyApplicationSettings; -import com.sourcegraph.cody.config.CodyAuthenticationManager; import com.sourcegraph.cody.initialization.Activity; -import com.sourcegraph.cody.statusbar.CodyManageAccountsAction; import com.sourcegraph.common.NotificationGroups; import com.sourcegraph.common.ui.DumbAwareEDTAction; import org.jetbrains.annotations.NotNull; @@ -25,19 +21,8 @@ public class CodyAuthNotificationActivity implements Activity { @Override public void runActivity(@NotNull Project project) { - CodyAccount activeAccount = CodyAuthenticationManager.getInstance().getAccount(); - CodyAccountManager service = - ApplicationManager.getApplication().getService(CodyAccountManager.class); - - if (activeAccount != null) { - String token = service.findCredentials(activeAccount); - if (token == null) { - showMissingTokenNotification(); - } - } - if (!CodyApplicationSettings.getInstance().isGetStartedNotificationDismissed() - && activeAccount == null) { + && !CodyAccount.Companion.hasActiveAccount()) { showOpenCodySidebarNotification(project); } } @@ -79,15 +64,4 @@ public void actionPerformed(@NotNull AnActionEvent anActionEvent) { notification.addAction(neverShowAgainAction); Notifications.Bus.notify(notification); } - - private void showMissingTokenNotification() { - // Display notification - Notification notification = - new Notification( - NotificationGroups.CODY_AUTH, "Missing access token", "", NotificationType.WARNING); - - notification.setIcon(Icons.CodyLogo); - notification.addAction(new CodyManageAccountsAction()); - Notifications.Bus.notify(notification); - } } diff --git a/src/main/java/com/sourcegraph/config/GoToPluginSettingsButtonFactory.java b/src/main/java/com/sourcegraph/config/GoToPluginSettingsButtonFactory.java index f1c2886a1e..f8dc399f78 100644 --- a/src/main/java/com/sourcegraph/config/GoToPluginSettingsButtonFactory.java +++ b/src/main/java/com/sourcegraph/config/GoToPluginSettingsButtonFactory.java @@ -7,6 +7,7 @@ import com.intellij.util.ui.JBDimension; import com.intellij.util.ui.JBUI; import com.sourcegraph.Icons; +import com.sourcegraph.cody.config.actions.OpenCodySettingsEditorAction; import javax.swing.*; import org.jetbrains.annotations.NotNull; @@ -16,7 +17,7 @@ public class GoToPluginSettingsButtonFactory { public static ActionButton createGoToPluginSettingsButton() { JBDimension actionButtonSize = JBUI.size(22, 22); - AnAction action = new OpenPluginSettingsAction(); + AnAction action = new OpenCodySettingsEditorAction(); Presentation presentation = new Presentation("Open Plugin Settings"); ActionButton button = diff --git a/src/main/java/com/sourcegraph/config/OpenPluginSettingsAction.java b/src/main/java/com/sourcegraph/config/OpenPluginSettingsAction.java deleted file mode 100644 index 139dfe6410..0000000000 --- a/src/main/java/com/sourcegraph/config/OpenPluginSettingsAction.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.sourcegraph.config; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.options.ShowSettingsUtil; -import com.intellij.openapi.util.NlsActions; -import com.sourcegraph.cody.config.ui.AccountConfigurable; -import com.sourcegraph.common.ui.DumbAwareEDTAction; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class OpenPluginSettingsAction extends DumbAwareEDTAction { - public OpenPluginSettingsAction() { - super(); - } - - public OpenPluginSettingsAction(@Nullable @NlsActions.ActionText String text) { - super(text); - } - - @Override - public void actionPerformed(@NotNull AnActionEvent event) { - ShowSettingsUtil.getInstance() - .showSettingsDialog(event.getProject(), AccountConfigurable.class); - } -} diff --git a/src/main/java/com/sourcegraph/find/browser/BrowserAndLoadingPanel.java b/src/main/java/com/sourcegraph/find/browser/BrowserAndLoadingPanel.java index b65ce302f9..8b39f732c6 100644 --- a/src/main/java/com/sourcegraph/find/browser/BrowserAndLoadingPanel.java +++ b/src/main/java/com/sourcegraph/find/browser/BrowserAndLoadingPanel.java @@ -8,7 +8,7 @@ import com.intellij.ui.components.JBPanelWithEmptyText; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.StatusText; -import com.sourcegraph.cody.config.ui.AccountConfigurable; +import com.sourcegraph.cody.config.ui.CodyConfigurable; import java.awt.*; import javax.swing.*; import org.apache.commons.lang.WordUtils; @@ -69,9 +69,7 @@ private void refreshUI() { emptyText.appendLine( "Click here to configure your Sourcegraph Cody + Code Search settings.", new SimpleTextAttributes(STYLE_PLAIN, JBUI.CurrentTheme.Link.Foreground.ENABLED), - __ -> - ShowSettingsUtil.getInstance() - .showSettingsDialog(project, AccountConfigurable.class)); + __ -> ShowSettingsUtil.getInstance().showSettingsDialog(project, CodyConfigurable.class)); } else if (errorMessage != null) { String wrappedText = WordUtils.wrap("Error: " + errorMessage, 100); diff --git a/src/main/java/com/sourcegraph/find/browser/SourcegraphJBCefBrowser.java b/src/main/java/com/sourcegraph/find/browser/SourcegraphJBCefBrowser.java index 8c6341b19d..4991b97043 100644 --- a/src/main/java/com/sourcegraph/find/browser/SourcegraphJBCefBrowser.java +++ b/src/main/java/com/sourcegraph/find/browser/SourcegraphJBCefBrowser.java @@ -2,7 +2,6 @@ import com.intellij.openapi.util.Disposer; import com.intellij.ui.jcef.JBCefBrowser; -import com.sourcegraph.cody.config.notification.AccountSettingChangeListener; import com.sourcegraph.cody.config.notification.CodySettingChangeListener; import com.sourcegraph.config.ThemeUtil; import javax.swing.*; @@ -24,11 +23,6 @@ public SourcegraphJBCefBrowser(@NotNull JSToJavaBridgeRequestHandler requestHand Disposer.register(this, jsToJavaBridge); javaToJSBridge = new JavaToJSBridge(this); - requestHandler - .getProject() - .getService(AccountSettingChangeListener.class) - .setJavaToJSBridge(javaToJSBridge); - requestHandler .getProject() .getService(CodySettingChangeListener.class) diff --git a/src/main/kotlin/com/sourcegraph/cody/CodyToolWindowContent.kt b/src/main/kotlin/com/sourcegraph/cody/CodyToolWindowContent.kt index bb25d21f0a..407a71d87a 100644 --- a/src/main/kotlin/com/sourcegraph/cody/CodyToolWindowContent.kt +++ b/src/main/kotlin/com/sourcegraph/cody/CodyToolWindowContent.kt @@ -3,17 +3,12 @@ package com.sourcegraph.cody import com.intellij.openapi.components.Service import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project +import com.intellij.openapi.wm.ToolWindowManager import com.intellij.ui.components.JBLabel import com.intellij.util.concurrency.annotations.RequiresEdt import com.intellij.util.ui.UIUtil -import com.sourcegraph.cody.chat.SignInWithSourcegraphPanel -import com.sourcegraph.cody.chat.ui.CodyOnboardingGuidancePanel -import com.sourcegraph.cody.config.CodyAccount -import com.sourcegraph.cody.config.CodyApplicationSettings -import com.sourcegraph.cody.config.CodyAuthenticationManager import com.sourcegraph.cody.ui.web.CodyToolWindowContentWebviewHost import com.sourcegraph.cody.ui.web.WebUIService -import java.awt.CardLayout import java.awt.GridBagConstraints import java.awt.GridBagLayout import java.awt.GridLayout @@ -22,67 +17,27 @@ import javax.swing.JPanel @Service(Service.Level.PROJECT) class CodyToolWindowContent(project: Project) { - private val cardLayout = CardLayout() - private val cardPanel = JPanel(cardLayout) val allContentPanel: JComponent = JPanel(GridLayout(1, 1)) + private val mainPanel = JPanel(GridBagLayout()) private var webview: CodyToolWindowContentWebviewHost? = null init { - cardPanel.add(SignInWithSourcegraphPanel(project), SIGN_IN_PANEL, SIGN_IN_PANEL_INDEX) - val codyOnboardingGuidancePanel = CodyOnboardingGuidancePanel(project) - codyOnboardingGuidancePanel.addMainButtonActionListener { - CodyApplicationSettings.instance.isOnboardingGuidanceDismissed = true - refreshPanelsVisibility() - } - cardPanel.add(codyOnboardingGuidancePanel, ONBOARDING_PANEL, ONBOARDING_PANEL_INDEX) - - // Because the webview may be created lazily, populate a placeholder control. - val placeholder = JPanel(GridBagLayout()) val spinnerLabel = JBLabel("Starting Cody...", Icons.StatusBar.CompletionInProgress, JBLabel.CENTER) - placeholder.add(spinnerLabel, GridBagConstraints()) - cardPanel.add(placeholder, LOADING_PANEL, LOADING_PANEL_INDEX) - + allContentPanel.add(spinnerLabel, GridBagConstraints()) WebUIService.getInstance(project).views.provideCodyToolWindowContent(this) - refreshPanelsVisibility() } @RequiresEdt fun refreshPanelsVisibility() { - val codyAuthenticationManager = CodyAuthenticationManager.getInstance() - if (codyAuthenticationManager.hasNoActiveAccount() || - codyAuthenticationManager.showInvalidAccessTokenError()) { - cardLayout.show(cardPanel, SIGN_IN_PANEL) - showView(cardPanel) - return - } - val activeAccount = codyAuthenticationManager.account - if (!CodyApplicationSettings.instance.isOnboardingGuidanceDismissed) { - val displayName = activeAccount?.let(CodyAccount::displayName) - cardPanel.getComponent(ONBOARDING_PANEL_INDEX)?.let { - (it as CodyOnboardingGuidancePanel).updateDisplayName(displayName) - } - cardLayout.show(cardPanel, ONBOARDING_PANEL) - showView(cardPanel) - return - } - cardLayout.show(cardPanel, LOADING_PANEL) - showView(webview?.proxy?.component ?: cardPanel) - } - - // Flips the sidebar view to the specified top level component. We do it this way - // because JetBrains Remote does not display webviews inside a component using - // CardLayout. - private fun showView(component: JComponent) { + val component = webview?.proxy?.component ?: mainPanel if (allContentPanel.components.isEmpty() || allContentPanel.getComponent(0) != component) { allContentPanel.removeAll() allContentPanel.add(component) } } - /** Sets the webview component to display, if any. */ - @RequiresEdt internal fun setWebviewComponent(host: CodyToolWindowContentWebviewHost?) { webview = host if (host != null && host.proxy?.component == null) { @@ -96,16 +51,14 @@ class CodyToolWindowContent(project: Project) { } companion object { - const val ONBOARDING_PANEL = "onboardingPanel" - const val SIGN_IN_PANEL = "signInWithSourcegraphPanel" - const val LOADING_PANEL = "loadingPanel" - - const val SIGN_IN_PANEL_INDEX = 0 - const val ONBOARDING_PANEL_INDEX = 1 - const val LOADING_PANEL_INDEX = 2 - var logger = Logger.getInstance(CodyToolWindowContent::class.java) + fun show(project: Project) { + ToolWindowManager.getInstance(project) + .getToolWindow(CodyToolWindowFactory.TOOL_WINDOW_ID) + ?.show() + } + fun executeOnInstanceIfNotDisposed( project: Project, myAction: CodyToolWindowContent.() -> Unit diff --git a/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgent.kt b/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgent.kt index 65f3e23a5d..072804fb71 100644 --- a/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgent.kt +++ b/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgent.kt @@ -119,6 +119,7 @@ private constructor( extensionConfiguration = ConfigUtil.getAgentConfiguration(project), capabilities = ClientCapabilities( + authentication = ClientCapabilities.AuthenticationEnum.Enabled, edit = ClientCapabilities.EditEnum.Enabled, editWorkspace = ClientCapabilities.EditWorkspaceEnum.Enabled, codeLenses = ClientCapabilities.CodeLensesEnum.Enabled, @@ -128,6 +129,7 @@ private constructor( untitledDocuments = ClientCapabilities.UntitledDocumentsEnum.Enabled, codeActions = ClientCapabilities.CodeActionsEnum.Enabled, globalState = ClientCapabilities.GlobalStateEnum.`Server-managed`, + secrets = ClientCapabilities.SecretsEnum.`Client-managed`, webview = ClientCapabilities.WebviewEnum.Native, webviewNativeConfig = WebviewNativeConfig( diff --git a/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentClient.kt b/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentClient.kt index 5852b95abc..061390cbfc 100644 --- a/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentClient.kt +++ b/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentClient.kt @@ -17,14 +17,22 @@ import com.sourcegraph.cody.agent.protocol_generated.DisplayCodeLensParams import com.sourcegraph.cody.agent.protocol_generated.Env_OpenExternalParams import com.sourcegraph.cody.agent.protocol_generated.Null import com.sourcegraph.cody.agent.protocol_generated.SaveDialogOptionsParams +import com.sourcegraph.cody.agent.protocol_generated.Secrets_DeleteParams +import com.sourcegraph.cody.agent.protocol_generated.Secrets_GetParams +import com.sourcegraph.cody.agent.protocol_generated.Secrets_StoreParams import com.sourcegraph.cody.agent.protocol_generated.TextDocumentEditParams import com.sourcegraph.cody.agent.protocol_generated.TextDocument_ShowParams import com.sourcegraph.cody.agent.protocol_generated.UntitledTextDocument +import com.sourcegraph.cody.agent.protocol_generated.Window_DidChangeContextParams import com.sourcegraph.cody.agent.protocol_generated.WorkspaceEditParams +import com.sourcegraph.cody.auth.CodyAccount +import com.sourcegraph.cody.auth.SourcegraphServerPath import com.sourcegraph.cody.edit.EditService import com.sourcegraph.cody.edit.lenses.LensesService import com.sourcegraph.cody.error.CodyConsole import com.sourcegraph.cody.ignore.IgnoreOracle +import com.sourcegraph.cody.statusbar.CodyStatus +import com.sourcegraph.cody.statusbar.CodyStatusService import com.sourcegraph.cody.ui.web.NativeWebviewProvider import com.sourcegraph.common.BrowserOpener import com.sourcegraph.utils.CodyEditorUtil @@ -125,6 +133,24 @@ class CodyAgentClient(private val project: Project, private val webview: NativeW } } + @JsonRequest("secrets/get") + fun secrets_get(params: Secrets_GetParams): CompletableFuture { + return CompletableFuture.completedFuture( + CodyAccount(SourcegraphServerPath(params.key)).getToken()) + } + + @JsonRequest("secrets/store") + fun secrets_store(params: Secrets_StoreParams): CompletableFuture { + CodyAccount(SourcegraphServerPath(params.key)).storeToken(params.value) + return CompletableFuture.completedFuture(null) + } + + @JsonRequest("secrets/delete") + fun secrets_delete(params: Secrets_DeleteParams): CompletableFuture { + CodyAccount(SourcegraphServerPath(params.key)).storeToken(null) + return CompletableFuture.completedFuture(null) + } + // ============= // Notifications // ============= @@ -228,4 +254,17 @@ class CodyAgentClient(private val project: Project, private val webview: NativeW return saveFileFuture } + + @JsonNotification("window/didChangeContext") + fun window_didChangeContext(params: Window_DidChangeContextParams) { + if (params.key == "cody.activated") { + CodyAccount.setActivated(params.value?.toBoolean() ?: false) + CodyStatusService.notifyApplication(project, CodyStatus.CodyNotSignedIn) + } + if (params.key == "cody.serverEndpoint") { + val endpoint = params.value ?: return + CodyAccount.setActiveAccount(CodyAccount(SourcegraphServerPath(endpoint))) + CodyStatusService.resetApplication(project) + } + } } diff --git a/src/main/kotlin/com/sourcegraph/cody/api/SourcegraphApiRequestExecutor.kt b/src/main/kotlin/com/sourcegraph/cody/api/SourcegraphApiRequestExecutor.kt index a17815dd95..5297ff3ae3 100644 --- a/src/main/kotlin/com/sourcegraph/cody/api/SourcegraphApiRequestExecutor.kt +++ b/src/main/kotlin/com/sourcegraph/cody/api/SourcegraphApiRequestExecutor.kt @@ -11,7 +11,7 @@ import com.intellij.util.concurrency.annotations.RequiresBackgroundThread import com.intellij.util.io.HttpRequests import com.intellij.util.io.HttpSecurityUtil import com.intellij.util.io.RequestBuilder -import com.sourcegraph.cody.config.SourcegraphServerPath +import com.sourcegraph.cody.auth.SourcegraphServerPath import java.io.IOException import java.io.InputStream import java.io.InputStreamReader diff --git a/src/main/kotlin/com/sourcegraph/cody/api/SourcegraphApiRequests.kt b/src/main/kotlin/com/sourcegraph/cody/api/SourcegraphApiRequests.kt index cb372247c6..5af28d14c0 100644 --- a/src/main/kotlin/com/sourcegraph/cody/api/SourcegraphApiRequests.kt +++ b/src/main/kotlin/com/sourcegraph/cody/api/SourcegraphApiRequests.kt @@ -1,9 +1,6 @@ package com.sourcegraph.cody.api import com.intellij.openapi.progress.ProgressIndicator -import com.sourcegraph.cody.config.CodyAccountCodyProEnabled -import com.sourcegraph.cody.config.CodyAccountDetails -import java.awt.Image object SourcegraphApiRequests { class CurrentUser( @@ -14,12 +11,6 @@ object SourcegraphApiRequests { getCurrentUser(SourcegraphGQLQueries.getUserDetails, CurrentUserDetailsWrapper::class.java) .currentUser - fun getCodyProEnabled(): CodyAccountCodyProEnabled = - getCurrentUser( - SourcegraphGQLQueries.getUserCodyProEnabled, - CurrentUserCodyProEnabledWrapper::class.java) - .currentUser - private fun getCurrentUser(queryName: String, clazz: Class): T = executor.execute( progressIndicator, @@ -28,16 +19,14 @@ object SourcegraphApiRequests { data class CurrentUserDetailsWrapper(val currentUser: CodyAccountDetails) - data class CurrentUserCodyProEnabledWrapper(val currentUser: CodyAccountCodyProEnabled) - - fun getAvatar(url: String): Image = - executor.execute( - progressIndicator, - object : SourcegraphApiRequest.Get(url) { - override fun extractResult(response: SourcegraphApiResponse): Image { - return response.handleBody { SourcegraphApiContentHelper.loadImage(it) } - } - } - .apply { operationName = "get profile avatar" }) + class CodyAccountDetails( + val id: String, + val username: String, + val displayName: String?, + val avatarURL: String? + ) { + val name: String + get() = displayName ?: username + } } } diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/Account.kt b/src/main/kotlin/com/sourcegraph/cody/auth/Account.kt deleted file mode 100644 index 06c0bd9ca0..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/Account.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.sourcegraph.cody.auth - -import java.util.UUID -import org.jetbrains.annotations.Nls - -/** - * Base class to represent an account for some external system Properties are abstract to allow - * marking them with persistence annotations - * - * Generally supposed to be used as means of distinguishing multiple credentials from PSafe - * - * @property id an internal unique identifier of an account - * @property name short display name for an account to be shown to a user (login/username/email) - */ -abstract class Account { - - abstract val id: String - - @get:Nls abstract val name: String - - final override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is Account) return false - return id == other.id - } - - final override fun hashCode(): Int { - return id.hashCode() - } - - companion object { - fun generateId() = UUID.randomUUID().toString() - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/AccountDetails.kt b/src/main/kotlin/com/sourcegraph/cody/auth/AccountDetails.kt deleted file mode 100644 index 3045630469..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/AccountDetails.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.sourcegraph.cody.auth - -import com.intellij.openapi.util.NlsSafe - -interface AccountDetails { - @get:NlsSafe val name: String -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/AccountManager.kt b/src/main/kotlin/com/sourcegraph/cody/auth/AccountManager.kt deleted file mode 100644 index c4e99a54c7..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/AccountManager.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.sourcegraph.cody.auth - -import com.intellij.openapi.Disposable -import com.intellij.util.concurrency.annotations.RequiresEdt - -/** - * Account management service - * - * @param A - account type - * @param Cred - account credentials - */ -interface AccountManager { - - /** Set of accounts registered within application */ - @get:RequiresEdt val accounts: Set - - /** Add/update account and it's credentials */ - @RequiresEdt fun updateAccount(account: A, credentials: Cred) - - /** - * Add/update/remove multiple accounts and their credentials Credentials are not updated if null - * value is passed Should only be used by a bulk update from settings - */ - @RequiresEdt fun updateAccounts(accountsWithCredentials: Map) - - /** Remove an account and clear stored credentials Does nothing if account is not present */ - @RequiresEdt fun removeAccount(account: A) - - /** Retrieve credentials for account */ - @RequiresEdt fun findCredentials(account: A): Cred? - - /** Add accounts data listener */ - @RequiresEdt fun addListener(disposable: Disposable, listener: AccountsListener) -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/AccountManagerBase.kt b/src/main/kotlin/com/sourcegraph/cody/auth/AccountManagerBase.kt deleted file mode 100644 index 3b4aede7f5..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/AccountManagerBase.kt +++ /dev/null @@ -1,138 +0,0 @@ -package com.sourcegraph.cody.auth - -import com.intellij.credentialStore.CredentialAttributes -import com.intellij.credentialStore.Credentials -import com.intellij.credentialStore.PasswordSafeSettings -import com.intellij.credentialStore.PasswordSafeSettingsListener -import com.intellij.credentialStore.generateServiceName -import com.intellij.ide.passwordSafe.PasswordSafe -import com.intellij.openapi.Disposable -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.diagnostic.thisLogger -import com.intellij.openapi.util.Disposer -import java.util.concurrent.CopyOnWriteArrayList -import org.jetbrains.annotations.VisibleForTesting - -/** - * Base class for account management application service Accounts are stored in [accountsRepository] - * Credentials are serialized and stored in [passwordSafe] - * - * @see [AccountsListener] - */ -abstract class AccountManagerBase(private val serviceName: String) : - AccountManager, Disposable { - - protected open val passwordSafe - get() = PasswordSafe.instance - - private val persistentAccounts - get() = accountsRepository() - - protected abstract fun accountsRepository(): AccountsRepository - - private val listeners = CopyOnWriteArrayList>() - - private val messageBusConnection by lazy { messageBusConnection() } - - @VisibleForTesting - protected open fun messageBusConnection() = - ApplicationManager.getApplication().messageBus.connect(this) - - override val accounts: Set - get() = persistentAccounts.accounts - - init { - messageBusConnection.subscribe( - PasswordSafeSettings.TOPIC, - object : PasswordSafeSettingsListener { - override fun credentialStoreCleared() = - persistentAccounts.accounts.forEach { account -> - listeners.forEach { it.onAccountCredentialsChanged(account) } - } - }) - } - - override fun updateAccounts(accountsWithCredentials: Map) { - val currentSet = persistentAccounts.accounts - val removed = currentSet - accountsWithCredentials.keys - for (account in removed) { - passwordSafe.set(account.credentialAttributes(), null) - } - for ((account, credentials) in accountsWithCredentials) { - if (credentials != null) { - passwordSafe.set(account.credentialAttributes(), account.credentials(credentials)) - if (currentSet.contains(account)) - listeners.forEach { it.onAccountCredentialsChanged(account) } - } - } - val added = accountsWithCredentials.keys - currentSet - if (added.isNotEmpty() || removed.isNotEmpty()) { - persistentAccounts.accounts = accountsWithCredentials.keys - listeners.forEach { it.onAccountListChanged(currentSet, accountsWithCredentials.keys) } - LOG.debug("Account list changed to: ${persistentAccounts.accounts}") - } - } - - override fun updateAccount(account: A, credentials: Cred) { - val currentSet = persistentAccounts.accounts - val newAccount = !currentSet.contains(account) - if (!newAccount) { - // remove and add an account to update auxiliary fields - persistentAccounts.accounts = (currentSet - account) + account - } else { - persistentAccounts.accounts = currentSet + account - LOG.debug("Added new account: $account") - } - passwordSafe.set(account.credentialAttributes(), account.credentials(credentials)) - LOG.debug( - (if (credentials == null) "Cleared" else "Updated") + " credentials for account: $account") - if (!newAccount) { - listeners.forEach { it.onAccountCredentialsChanged(account) } - } else { - listeners.forEach { it.onAccountListChanged(currentSet, persistentAccounts.accounts) } - } - } - - override fun removeAccount(account: A) { - val currentSet = persistentAccounts.accounts - val newSet = currentSet - account - if (newSet.size != currentSet.size) { - persistentAccounts.accounts = newSet - passwordSafe.set(account.credentialAttributes(), null) - LOG.debug("Removed account: $account") - listeners.forEach { it.onAccountListChanged(currentSet, newSet) } - } - } - - override fun findCredentials(account: A): Cred? = - passwordSafe - .get(account.credentialAttributes()) - ?.getPasswordAsString() - ?.let(::deserializeCredentials) - - private fun A.credentialAttributes() = CredentialAttributes(generateServiceName(serviceName, id)) - - private fun A.credentials(credentials: Cred?): Credentials? = - credentials?.let { Credentials(id, serializeCredentials(it)) } - - protected abstract fun serializeCredentials(credentials: Cred): String - - protected abstract fun deserializeCredentials(credentials: String): Cred - - override fun addListener(disposable: Disposable, listener: AccountsListener) { - listeners.add(listener) - Disposer.register(disposable) { listeners.remove(listener) } - } - - @VisibleForTesting - fun addListener(listener: AccountsListener) { - listeners.add(listener) - } - - override fun dispose() {} - - companion object { - private val LOG - get() = thisLogger() - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/AccountsListener.kt b/src/main/kotlin/com/sourcegraph/cody/auth/AccountsListener.kt deleted file mode 100644 index ddd091c5b4..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/AccountsListener.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.sourcegraph.cody.auth - -import java.util.EventListener - -/** @param A - account type */ -interface AccountsListener : EventListener { - fun onAccountListChanged(old: Collection, new: Collection) {} - - fun onAccountCredentialsChanged(account: A) {} -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/AccountsRepository.kt b/src/main/kotlin/com/sourcegraph/cody/auth/AccountsRepository.kt deleted file mode 100644 index 2630781d84..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/AccountsRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.sourcegraph.cody.auth - -/** - * In most cases should be an instance of [com.intellij.openapi.components.PersistentStateComponent] - */ -interface AccountsRepository { - var accounts: Set -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/AuthCallbackHandler.kt b/src/main/kotlin/com/sourcegraph/cody/auth/AuthCallbackHandler.kt deleted file mode 100644 index e557bf6444..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/AuthCallbackHandler.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.sourcegraph.cody.auth - -import io.netty.buffer.Unpooled -import io.netty.channel.ChannelHandlerContext -import io.netty.handler.codec.http.FullHttpRequest -import io.netty.handler.codec.http.HttpResponseStatus -import io.netty.handler.codec.http.HttpUtil -import io.netty.handler.codec.http.QueryStringDecoder -import org.jetbrains.ide.RestService -import org.jetbrains.io.response -import org.jetbrains.io.send - -class AuthCallbackHandler : RestService() { - private val service: AuthService - get() = SourcegraphAuthService.instance - - override fun getServiceName(): String = service.name - - override fun execute( - urlDecoder: QueryStringDecoder, - request: FullHttpRequest, - context: ChannelHandlerContext - ): String? { - val parameters = urlDecoder.parameters() - val accessToken = parameters.getAccessToken() - if (accessToken == null) { - sendStatus(HttpResponseStatus.BAD_REQUEST, HttpUtil.isKeepAlive(request), context.channel()) - } else { - val isAccessTokenAccepted = service.handleServerCallback(urlDecoder.path(), accessToken) - if (isAccessTokenAccepted) { - // Send response - val htmlContent = - " Cody: Authentication successful

Authentication successful

You may close this tab and return to your editor

" - response( - "text/html; charset=UTF-8", - Unpooled.wrappedBuffer(htmlContent.toByteArray(Charsets.UTF_8))) - .send(context.channel(), request) - } - } - - return null - } - - private fun Map>.getAccessToken(): String? = this["token"]?.firstOrNull() -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/AuthRequest.kt b/src/main/kotlin/com/sourcegraph/cody/auth/AuthRequest.kt deleted file mode 100644 index d379bb4d31..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/AuthRequest.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.sourcegraph.cody.auth - -import com.intellij.util.Url - -interface AuthRequest { - val serviceName: String - - /** Url that is usually opened in browser where user can accept authorization */ - val authUrlWithParameters: Url -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/AuthService.kt b/src/main/kotlin/com/sourcegraph/cody/auth/AuthService.kt deleted file mode 100644 index ba0362bd1d..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/AuthService.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.sourcegraph.cody.auth - -import java.util.concurrent.CompletableFuture - -interface AuthService { - val name: String - - /** Starting the authorization flow */ - fun authorize(request: AuthRequest): CompletableFuture - - /** Processing received access token */ - fun handleServerCallback(path: String, accessToken: String): Boolean -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/AuthServiceBase.kt b/src/main/kotlin/com/sourcegraph/cody/auth/AuthServiceBase.kt deleted file mode 100644 index 2357f2e5f3..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/AuthServiceBase.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.sourcegraph.cody.auth - -import com.intellij.ide.BrowserUtil -import java.util.concurrent.CompletableFuture -import java.util.concurrent.atomic.AtomicReference - -/** The basic service that implements general authorization flow methods */ -abstract class AuthServiceBase : AuthService { - private val currentRequest = AtomicReference() - - override fun authorize(request: AuthRequest): CompletableFuture { - if (!currentRequest.compareAndSet( - null, OAuthRequestWithResult(request, CompletableFuture()))) { - return currentRequest.get()!!.result - } - - val result = currentRequest.get()!!.result - result.whenComplete { _, _ -> currentRequest.set(null) } - startAuthorization(request) - - return result - } - - override fun handleServerCallback(path: String, accessToken: String): Boolean { - val request = currentRequest.get() ?: return false - request.processToken(accessToken) - val result = request.result - return result.isDone && !result.isCancelled && !result.isCompletedExceptionally - } - - protected open fun startAuthorization(request: AuthRequest) { - val authUrl = request.authUrlWithParameters.toExternalForm() - BrowserUtil.browse(authUrl) - } - - private fun OAuthRequestWithResult.processToken(accessToken: String) { - result.complete(accessToken) - } - - protected data class OAuthRequestWithResult( - val request: AuthRequest, - val result: CompletableFuture - ) -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/CodyAccount.kt b/src/main/kotlin/com/sourcegraph/cody/auth/CodyAccount.kt new file mode 100644 index 0000000000..81e206cf98 --- /dev/null +++ b/src/main/kotlin/com/sourcegraph/cody/auth/CodyAccount.kt @@ -0,0 +1,50 @@ +package com.sourcegraph.cody.auth + +import com.intellij.credentialStore.CredentialAttributes +import com.intellij.credentialStore.Credentials +import com.intellij.credentialStore.generateServiceName +import com.intellij.ide.passwordSafe.PasswordSafe +import com.sourcegraph.config.ConfigUtil + +data class CodyAccount(val server: SourcegraphServerPath) { + + fun isDotcomAccount(): Boolean = server.url.lowercase().startsWith(ConfigUtil.DOTCOM_URL) + + fun getToken(): String? { + return PasswordSafe.instance.get(credentialAttributes(server.url))?.getPasswordAsString() + } + + fun storeToken(token: String?) { + PasswordSafe.instance.set(credentialAttributes(server.url), Credentials(user = "", token)) + } + + companion object { + private const val ACTIVE_ACCOUNT_MARKER = "active_cody_account" + + @Volatile private var isActivated: Boolean = false + + private fun credentialAttributes(key: String): CredentialAttributes = + CredentialAttributes(generateServiceName("Sourcegraph", key)) + + fun hasActiveAccount(): Boolean { + return isActivated && getActiveAccount() != null + } + + fun setActivated(isActivated: Boolean) { + this.isActivated = isActivated + } + + fun getActiveAccount(): CodyAccount? { + val serverUrl = + PasswordSafe.instance + .get(credentialAttributes(ACTIVE_ACCOUNT_MARKER)) + ?.getPasswordAsString() + return if (serverUrl == null) null else CodyAccount(SourcegraphServerPath(serverUrl)) + } + + fun setActiveAccount(account: CodyAccount?) { + PasswordSafe.instance.set( + credentialAttributes(ACTIVE_ACCOUNT_MARKER), Credentials(user = "", account?.server?.url)) + } + } +} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/ServerAccount.kt b/src/main/kotlin/com/sourcegraph/cody/auth/ServerAccount.kt deleted file mode 100644 index 1eb56dc67b..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/ServerAccount.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.sourcegraph.cody.auth - -/** - * Base class for an account which correspond to a certain server Most systems can have multiple - * servers while some have only the central one - * - * @property server some definition of a server, which can be presented to a user - */ -abstract class ServerAccount : Account() { - abstract val server: ServerPath -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/ServerPath.kt b/src/main/kotlin/com/sourcegraph/cody/auth/ServerPath.kt deleted file mode 100644 index cd1e904c19..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/ServerPath.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.sourcegraph.cody.auth - -import com.intellij.openapi.util.NlsSafe - -interface ServerPath { - @NlsSafe override fun toString(): String -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/SingleValueModel.kt b/src/main/kotlin/com/sourcegraph/cody/auth/SingleValueModel.kt deleted file mode 100644 index 008dbb18d0..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/SingleValueModel.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.sourcegraph.cody.auth - -import com.intellij.collaboration.ui.SimpleEventListener -import com.intellij.util.EventDispatcher -import com.intellij.util.concurrency.annotations.RequiresEdt - -class SingleValueModel(initialValue: T) { - private val changeEventDispatcher = EventDispatcher.create(SimpleEventListener::class.java) - - var value: T = initialValue - set(value) { - field = value - changeEventDispatcher.multicaster.eventOccurred() - } - - @RequiresEdt - fun addAndInvokeListener(listener: (newValue: T) -> Unit) { - addListener(listener) - listener(value) - } - - @RequiresEdt - fun addListener(listener: (newValue: T) -> Unit) { - SimpleEventListener.addListener(changeEventDispatcher) { listener(value) } - } - - fun map(mapper: (T) -> R): SingleValueModel { - val mappedModel = SingleValueModel(value.let(mapper)) - this.addListener { mappedModel.value = value.let(mapper) } - return mappedModel - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/SourcegraphAuthService.kt b/src/main/kotlin/com/sourcegraph/cody/auth/SourcegraphAuthService.kt deleted file mode 100644 index 897ab81c0d..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/SourcegraphAuthService.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.sourcegraph.cody.auth - -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.intellij.util.Url -import com.intellij.util.Urls -import java.util.concurrent.CompletableFuture -import org.jetbrains.ide.BuiltInServerManager - -@Service -internal class SourcegraphAuthService : AuthServiceBase() { - - override val name: String - get() = SERVICE_NAME - - fun authorize(server: String, authMethod: SsoAuthMethod): CompletableFuture { - return authorize(SourcegraphAuthRequest(name, server, authMethod)) - } - - private class SourcegraphAuthRequest( - override val serviceName: String, - val server: String, - val authMethod: SsoAuthMethod - ) : AuthRequest { - private val port: Int - get() = BuiltInServerManager.getInstance().port - - override val authUrlWithParameters: Url = createUrl() - - private fun createUrl() = - when (authMethod) { - SsoAuthMethod.GITHUB -> { - val end = - ".auth/openidconnect/login?prompt_auth=github&pc=sams&redirect=/user/settings/tokens/new/callback?requestFrom=JETBRAINS-$port" - Urls.newFromEncoded(server + end) - } - SsoAuthMethod.GITLAB -> { - val end = - ".auth/openidconnect/login?prompt_auth=gitlab&pc=sams&redirect=/user/settings/tokens/new/callback?requestFrom=JETBRAINS-$port" - Urls.newFromEncoded(server + end) - } - SsoAuthMethod.GOOGLE -> { - val end = - ".auth/openidconnect/login?prompt_auth=google&pc=sams&redirect=/user/settings/tokens/new/callback?requestFrom=JETBRAINS-$port" - Urls.newFromEncoded(server + end) - } - else -> - serviceUrl(server) - .addParameters(mapOf("requestFrom" to "JETBRAINS", "port" to port.toString())) - } - } - - companion object { - private const val SERVICE_NAME = "sourcegraph" - - @JvmStatic - val instance: SourcegraphAuthService - get() = service() - - @JvmStatic - fun serviceUrl(server: String): Url = - Urls.newFromEncoded(server + "user/settings/tokens/new/callback") - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/SourcegraphServerPath.kt b/src/main/kotlin/com/sourcegraph/cody/auth/SourcegraphServerPath.kt similarity index 95% rename from src/main/kotlin/com/sourcegraph/cody/config/SourcegraphServerPath.kt rename to src/main/kotlin/com/sourcegraph/cody/auth/SourcegraphServerPath.kt index 958144ed39..400cb3d5bc 100644 --- a/src/main/kotlin/com/sourcegraph/cody/config/SourcegraphServerPath.kt +++ b/src/main/kotlin/com/sourcegraph/cody/auth/SourcegraphServerPath.kt @@ -1,8 +1,8 @@ -package com.sourcegraph.cody.config +package com.sourcegraph.cody.auth import com.intellij.util.xmlb.annotations.Attribute import com.intellij.util.xmlb.annotations.Tag -import com.sourcegraph.cody.auth.ServerPath +import com.sourcegraph.cody.config.SourcegraphParseException import com.sourcegraph.config.ConfigUtil import java.net.URI import java.util.regex.Pattern @@ -11,7 +11,7 @@ import java.util.regex.Pattern data class SourcegraphServerPath( @Attribute("url") val url: String = "", @Attribute("customRequestHeaders") val customRequestHeaders: String = "" -) : ServerPath { +) { private val GRAPHQL_API_SUFFIX = ".api/graphql" diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/SsoAuthMethod.kt b/src/main/kotlin/com/sourcegraph/cody/auth/SsoAuthMethod.kt deleted file mode 100644 index e62f9ceaa5..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/SsoAuthMethod.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.sourcegraph.cody.auth - -enum class SsoAuthMethod(val value: String) { - GITHUB("Sign in with GitHub"), - GITLAB("Sign in with GitLab"), - GOOGLE("Sign in with Google"), - DEFAULT(""); - - companion object { - fun from(value: String): SsoAuthMethod = values().firstOrNull { it.value == value } ?: DEFAULT - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/deprecated/DeprecatedCodyAccount.kt b/src/main/kotlin/com/sourcegraph/cody/auth/deprecated/DeprecatedCodyAccount.kt new file mode 100644 index 0000000000..16cea291a2 --- /dev/null +++ b/src/main/kotlin/com/sourcegraph/cody/auth/deprecated/DeprecatedCodyAccount.kt @@ -0,0 +1,43 @@ +package com.sourcegraph.cody.auth.deprecated + +import com.intellij.credentialStore.CredentialAttributes +import com.intellij.credentialStore.generateServiceName +import com.intellij.openapi.util.NlsSafe +import com.intellij.util.xmlb.annotations.Attribute +import com.intellij.util.xmlb.annotations.Property +import com.intellij.util.xmlb.annotations.Tag +import com.sourcegraph.cody.auth.SourcegraphServerPath +import com.sourcegraph.config.ConfigUtil +import java.io.File +import java.util.UUID + +@Tag("account") +data class DeprecatedCodyAccount( + @NlsSafe @Attribute("name") var name: String = "", + @Attribute("displayName") var displayName: String? = name, + @Property(style = Property.Style.ATTRIBUTE, surroundWithTag = false) + var server: SourcegraphServerPath = SourcegraphServerPath.from(ConfigUtil.DOTCOM_URL, ""), + @Attribute("id") var id: String = generateId(), +) { + + fun isDotcomAccount(): Boolean = server.url.lowercase().startsWith(ConfigUtil.DOTCOM_URL) + + fun credentialAttributes(): CredentialAttributes = + CredentialAttributes(generateServiceName("Sourcegraph", id)) + + override fun toString(): String = File(server.toString(), name).path + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is DeprecatedCodyAccount) return false + return id == other.id + } + + override fun hashCode(): Int { + return id.hashCode() + } + + companion object { + fun generateId() = UUID.randomUUID().toString() + } +} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/deprecated/DeprecatedCodyAccountManager.kt b/src/main/kotlin/com/sourcegraph/cody/auth/deprecated/DeprecatedCodyAccountManager.kt new file mode 100644 index 0000000000..a12c5df762 --- /dev/null +++ b/src/main/kotlin/com/sourcegraph/cody/auth/deprecated/DeprecatedCodyAccountManager.kt @@ -0,0 +1,101 @@ +package com.sourcegraph.cody.auth.deprecated + +import com.intellij.credentialStore.Credentials +import com.intellij.ide.passwordSafe.PasswordSafe +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import com.intellij.openapi.components.StoragePathMacros +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.ProjectManager +import com.intellij.util.concurrency.annotations.RequiresEdt +import com.sourcegraph.cody.auth.SourcegraphServerPath +import org.jetbrains.annotations.CalledInAny + +class AccountState { + var activeAccountId: String? = null +} + +// That class is keep only for a compatibility purposes, so we can load old per-project account +// settings, and use them as the new default when `CodyAccountsSettings` state is loaded for a very +// first time in the `noStateLoaded` method +@Deprecated("Use only for backward compatibility purposes") +@State( + name = "CodyActiveAccount", + storages = [Storage(StoragePathMacros.WORKSPACE_FILE)], + reportStatistic = false) +@Service(Service.Level.PROJECT) +class DeprecatedCodyActiveAccount(val project: Project) : PersistentStateComponent { + private var accountState: AccountState? = null + + override fun getState(): AccountState? { + return accountState + } + + override fun loadState(state: AccountState) { + accountState = state + } +} + +/** Entry point for interactions with Sourcegraph authentication subsystem */ +@State( + name = "CodyAccountsSettings", + storages = [Storage("cody_accounts_settings.xml")], + reportStatistic = false) +@Service(Service.Level.APP) +class DeprecatedCodyAccountManager : PersistentStateComponent { + + var account: DeprecatedCodyAccount? = null + private set + + @CalledInAny + fun getAccounts(): Set = + service().accounts + + fun hasActiveAccount(): Boolean = account != null + + fun setActiveAccount(newAccount: DeprecatedCodyAccount?) { + account = newAccount + } + + fun isAccountUnique(name: String, server: SourcegraphServerPath) = + getAccounts().none { it.name == name && it.server.url == server.url } + + fun getTokenForAccount(account: DeprecatedCodyAccount): String? = + PasswordSafe.instance.get(account.credentialAttributes())?.getPasswordAsString() + + @RequiresEdt + internal fun addOrUpdateAccountToken(account: DeprecatedCodyAccount, newToken: String) { + service().accounts = (getAccounts() - account) + account + PasswordSafe.instance.set(account.credentialAttributes(), Credentials(account.id, newToken)) + } + + override fun getState(): AccountState { + return AccountState().apply { activeAccountId = account?.id } + } + + override fun loadState(state: AccountState) { + account = state.activeAccountId?.let { id -> getAccounts().find { it.id == id } } + } + + override fun noStateLoaded() { + super.noStateLoaded() + val initialAccountId = + ProjectManager.getInstance().openProjects.firstNotNullOfOrNull { + it.service().state?.activeAccountId + } ?: getAccounts().firstOrNull()?.id + + loadState(AccountState().apply { activeAccountId = initialAccountId }) + } + + companion object { + @JvmStatic + fun getInstance(): DeprecatedCodyAccountManager { + return ApplicationManager.getApplication() + .getService(DeprecatedCodyAccountManager::class.java) + } + } +} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CodyPersistentAccounts.kt b/src/main/kotlin/com/sourcegraph/cody/auth/deprecated/DeprecatedCodyPersistentAccounts.kt similarity index 56% rename from src/main/kotlin/com/sourcegraph/cody/config/CodyPersistentAccounts.kt rename to src/main/kotlin/com/sourcegraph/cody/auth/deprecated/DeprecatedCodyPersistentAccounts.kt index 05adfc2ec6..9d643928e1 100644 --- a/src/main/kotlin/com/sourcegraph/cody/config/CodyPersistentAccounts.kt +++ b/src/main/kotlin/com/sourcegraph/cody/auth/deprecated/DeprecatedCodyPersistentAccounts.kt @@ -1,10 +1,9 @@ -package com.sourcegraph.cody.config +package com.sourcegraph.cody.auth.deprecated import com.intellij.openapi.components.PersistentStateComponent import com.intellij.openapi.components.SettingsCategory import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage -import com.sourcegraph.cody.auth.AccountsRepository @State( name = "CodyAccounts", @@ -14,19 +13,19 @@ import com.sourcegraph.cody.auth.AccountsRepository ], reportStatistic = false, category = SettingsCategory.TOOLS) -class CodyPersistentAccounts : - AccountsRepository, PersistentStateComponent> { - private var state = emptyArray() +class DeprecatedCodyPersistentAccounts : PersistentStateComponent> { - override var accounts: Set + private var state = emptyArray() + + var accounts: Set get() = state.toSet() set(value) { state = value.toTypedArray() } - override fun getState(): Array = state + override fun getState(): Array = state - override fun loadState(state: Array) { + override fun loadState(state: Array) { this.state = state } } diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/package.md b/src/main/kotlin/com/sourcegraph/cody/auth/package.md deleted file mode 100644 index b033c3c54e..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/package.md +++ /dev/null @@ -1,6 +0,0 @@ -# Package com.sourcegraph.cody.auth - -This package contains API to manage user accounts. -Most of the code was copied here from package `com.intellij.collaboration.auth`, because it looks like it is a public API, -but it was changing without any sign of deprecation, and it broke our code. That's why we decided to copy it to our sources. -The code can be updated to the same state as in the original package in the new versions of IntelliJ IDEA diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/ui/AccountsDetailsProvider.kt b/src/main/kotlin/com/sourcegraph/cody/auth/ui/AccountsDetailsProvider.kt deleted file mode 100644 index ba022502b5..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/ui/AccountsDetailsProvider.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.sourcegraph.cody.auth.ui - -import com.intellij.util.concurrency.annotations.RequiresEdt -import com.sourcegraph.cody.auth.Account -import com.sourcegraph.cody.auth.AccountDetails -import com.sourcegraph.cody.auth.SingleValueModel -import java.awt.Image -import org.jetbrains.annotations.Nls - -interface AccountsDetailsProvider { - - @get:RequiresEdt val loadingStateModel: SingleValueModel - - @RequiresEdt fun getDetails(account: A): D? - - @RequiresEdt fun getAvatarImage(account: A): Image? - - @RequiresEdt @Nls fun getErrorText(account: A): String? - - @RequiresEdt fun checkErrorRequiresReLogin(account: A): Boolean - - @RequiresEdt fun reset(account: A) - - @RequiresEdt fun resetAll() -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/ui/AccountsListModel.kt b/src/main/kotlin/com/sourcegraph/cody/auth/ui/AccountsListModel.kt deleted file mode 100644 index 72fb488838..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/ui/AccountsListModel.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.sourcegraph.cody.auth.ui - -import com.intellij.ui.awt.RelativePoint -import com.sourcegraph.cody.auth.Account -import com.sourcegraph.cody.auth.SingleValueModel -import javax.swing.JComponent -import javax.swing.ListModel - -interface AccountsListModel
{ - var accounts: Set - var selectedAccount: A? - val newCredentials: Map - - val accountsListModel: ListModel - val busyStateModel: SingleValueModel - - fun addAccount(parentComponent: JComponent, point: RelativePoint? = null) - - fun editAccount(parentComponent: JComponent, account: A) - - fun clearNewCredentials() - - fun addCredentialsChangeListener(listener: (A) -> Unit) - - interface WithActive : AccountsListModel { - var activeAccount: A? - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/ui/AccountsListModelBase.kt b/src/main/kotlin/com/sourcegraph/cody/auth/ui/AccountsListModelBase.kt deleted file mode 100644 index 13cfa456dd..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/ui/AccountsListModelBase.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.sourcegraph.cody.auth.ui - -import com.intellij.ui.CollectionListModel -import com.sourcegraph.cody.auth.Account -import com.sourcegraph.cody.auth.SingleValueModel -import java.util.concurrent.CopyOnWriteArrayList - -abstract class AccountsListModelBase : AccountsListModel { - override var accounts: Set - get() = accountsListModel.items.toSet() - set(value) { - accountsListModel.removeAll() - accountsListModel.add(value.toList()) - } - - override var selectedAccount: A? = null - override val newCredentials = mutableMapOf() - - override val accountsListModel = CollectionListModel() - override val busyStateModel = SingleValueModel(false) - - private val credentialsChangesListeners = CopyOnWriteArrayList<(A) -> Unit>() - - override fun clearNewCredentials() = newCredentials.clear() - - protected fun notifyCredentialsChanged(account: A) { - credentialsChangesListeners.forEach { it(account) } - accountsListModel.contentsChanged(account) - } - - final override fun addCredentialsChangeListener(listener: (A) -> Unit) { - credentialsChangesListeners.add(listener) - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/ui/AccountsPanelFactoryExtensions.kt b/src/main/kotlin/com/sourcegraph/cody/auth/ui/AccountsPanelFactoryExtensions.kt deleted file mode 100644 index 5c7e824fa8..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/ui/AccountsPanelFactoryExtensions.kt +++ /dev/null @@ -1,199 +0,0 @@ -package com.sourcegraph.cody.auth.ui - -import com.intellij.collaboration.messages.CollaborationToolsBundle -import com.intellij.collaboration.ui.util.JListHoveredRowMaterialiser -import com.intellij.icons.AllIcons -import com.intellij.openapi.Disposable -import com.intellij.openapi.actionSystem.ActionUpdateThread -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.CommonShortcuts -import com.intellij.openapi.keymap.KeymapUtil -import com.intellij.ui.ClientProperty -import com.intellij.ui.LayeredIcon -import com.intellij.ui.SimpleTextAttributes -import com.intellij.ui.ToolbarDecorator -import com.intellij.ui.awt.RelativePoint -import com.intellij.ui.components.JBList -import com.intellij.ui.dsl.builder.Cell -import com.intellij.ui.dsl.builder.Row -import com.intellij.util.ui.EmptyIcon -import com.intellij.util.ui.StatusText -import com.intellij.util.ui.UIUtil -import com.sourcegraph.cody.auth.Account -import com.sourcegraph.cody.auth.AccountManager -import com.sourcegraph.cody.auth.AccountsListener -import com.sourcegraph.cody.config.CodyAccount -import com.sourcegraph.cody.config.CodyAuthenticationManager -import java.awt.event.MouseAdapter -import java.awt.event.MouseEvent -import java.awt.event.MouseListener -import javax.swing.Icon -import javax.swing.JComponent -import javax.swing.JMenuItem -import javax.swing.JPopupMenu -import javax.swing.ListCellRenderer -import javax.swing.ListSelectionModel -import javax.swing.SwingUtilities - -/** - * Custom factory method to create and account panel with possibility to add mouse listener for list - * elements. This is a custom version of a - * [com.intellij.collaboration.auth.ui.AccountsPanelFactory.create] - */ -private fun create( - model: AccountsListModel, - needAddBtnWithDropdown: Boolean, - listElementMouseListener: (JBList, AccountsListModel) -> MouseListener, - listCellRendererFactory: () -> R -): JComponent where R : ListCellRenderer, R : JComponent { - - val accountsListModel = model.accountsListModel - val accountsList = - JBList(accountsListModel).apply { - val decoratorRenderer = listCellRendererFactory() - cellRenderer = decoratorRenderer - JListHoveredRowMaterialiser.install(this, listCellRendererFactory()) - ClientProperty.put(this, UIUtil.NOT_IN_HIERARCHY_COMPONENTS, listOf(decoratorRenderer)) - - selectionMode = ListSelectionModel.SINGLE_SELECTION - addMouseListener(listElementMouseListener(this, model)) - } - model.busyStateModel.addListener { accountsList.setPaintBusy(it) } - accountsList.addListSelectionListener { model.selectedAccount = accountsList.selectedValue } - - accountsList.emptyText.apply { - appendText(CollaborationToolsBundle.message("accounts.none.added")) - appendSecondaryText( - CollaborationToolsBundle.message("accounts.add.link"), - SimpleTextAttributes.LINK_PLAIN_ATTRIBUTES) { - val event = it.source - val relativePoint = if (event is MouseEvent) RelativePoint(event) else null - model.addAccount(accountsList, relativePoint) - } - appendSecondaryText( - " (${KeymapUtil.getFirstKeyboardShortcutText(CommonShortcuts.getNew())})", - StatusText.DEFAULT_ATTRIBUTES, - null) - } - - model.busyStateModel.addListener { accountsList.setPaintBusy(it) } - - val addIcon: Icon = - if (needAddBtnWithDropdown) LayeredIcon.ADD_WITH_DROPDOWN else AllIcons.General.Add - - val toolbar = - ToolbarDecorator.createDecorator(accountsList) - .disableUpDownActions() - .setAddAction { model.addAccount(accountsList, it.preferredPopupPoint) } - .setAddIcon(addIcon) - - if (model is AccountsListModel.WithActive) { - toolbar.addExtraAction( - object : ToolbarDecorator.ElementActionButton("Set as Active", AllIcons.Actions.Checked) { - init { - addCustomUpdater { isEnabled && model.activeAccount != accountsList.selectedValue } - } - - override fun getActionUpdateThread(): ActionUpdateThread { - return ActionUpdateThread.BGT - } - - override fun actionPerformed(e: AnActionEvent) { - val selected = accountsList.selectedValue - if (selected == model.activeAccount) return - if (selected != null) model.activeAccount = selected - } - - override fun isDumbAware(): Boolean { - return true - } - }) - } - - return toolbar.createPanel() -} - -/** - * Accounts panel with context menu actions displayed when right-clicked on menu elements This is a - * custom version of a [com.intellij.collaboration.auth.ui.AccountsPanelFactory.accountsPanel] - */ -fun Row.customAccountsPanel( - accountManager: AccountManager, - authManager: CodyAuthenticationManager, - accountsModel: AccountsListModel.WithActive, - detailsProvider: AccountsDetailsProvider, - disposable: Disposable, - needAddBtnWithDropdown: Boolean, - defaultAvatarIcon: Icon = EmptyIcon.ICON_16, - copyAccount: (CodyAccount) -> CodyAccount = { it }, -): Cell { - - accountsModel.addCredentialsChangeListener(detailsProvider::reset) - detailsProvider.loadingStateModel.addListener { accountsModel.busyStateModel.value = it } - - fun isModified() = - accountsModel.newCredentials.isNotEmpty() || - accountsModel.accounts != accountManager.accounts || - accountsModel.activeAccount != authManager.account - - fun reset() { - val activeAccount = authManager.account - val accountsWithoutActive = - if (activeAccount != null) accountManager.accounts - activeAccount - else accountManager.accounts - if (activeAccount != null) { - val activeAccountCopy = copyAccount(activeAccount) - accountsModel.accounts = (accountsWithoutActive.map(copyAccount) + activeAccountCopy).toSet() - accountsModel.activeAccount = activeAccountCopy - } else { - accountsModel.accounts = accountsWithoutActive.map(copyAccount).toSet() - accountsModel.activeAccount = null - } - - accountsModel.clearNewCredentials() - detailsProvider.resetAll() - } - - fun apply() { - for ((account, token) in accountsModel.newCredentials) { - if (token != null) { - accountManager.updateAccount(account, token) - } - } - val newTokensMap = accountsModel.accounts.associateWith { null } - accountManager.updateAccounts(newTokensMap) - accountsModel.clearNewCredentials() - } - - accountManager.addListener( - disposable, - object : AccountsListener { - override fun onAccountCredentialsChanged(account: CodyAccount) { - if (!isModified()) reset() - } - }) - - val component = - create( - accountsModel, - needAddBtnWithDropdown, - { list, model -> - object : MouseAdapter() { - override fun mouseReleased(e: MouseEvent) { - if (SwingUtilities.isRightMouseButton(e)) { - list.selectedIndex = list.locationToIndex(e.point) - list.setSelectedValue(list.model.getElementAt(list.selectedIndex), true) - - val menu = JPopupMenu() - val editAccount = JMenuItem("Edit Account") - editAccount.addActionListener { model.editAccount(list, list.selectedValue) } - menu.add(editAccount) - menu.show(list, e.point.x, e.point.y) - } - } - } - }) { - SimpleAccountsListCellRenderer(accountsModel, detailsProvider, defaultAvatarIcon) - } - return cell(component).onIsModified(::isModified).onReset(::reset).onApply(::apply) -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/ui/DeferredIconRepaintScheduler.kt b/src/main/kotlin/com/sourcegraph/cody/auth/ui/DeferredIconRepaintScheduler.kt deleted file mode 100644 index 2436271499..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/ui/DeferredIconRepaintScheduler.kt +++ /dev/null @@ -1,121 +0,0 @@ -package com.sourcegraph.cody.auth.ui - -import com.intellij.openapi.application.ApplicationManager -import com.intellij.ui.PaintingParent -import com.intellij.ui.tabs.impl.TabLabel -import com.intellij.util.Alarm -import com.intellij.util.concurrency.annotations.RequiresEdt -import java.awt.Component -import java.awt.Rectangle -import javax.swing.* - -class DeferredIconRepaintScheduler { - private val repaintScheduler = RepaintScheduler() - - @RequiresEdt - fun createRepaintRequest(c: Component, x: Int, y: Int): RepaintRequest { - val target = getTarget(c) - val paintingParent: Component? = - SwingUtilities.getAncestorOfClass(PaintingParent::class.java, c) - val paintingParentRec: Rectangle? = - if (paintingParent == null) null else (paintingParent as PaintingParent).getChildRec(c) - - return RepaintRequest(c, x, y, target, paintingParent, paintingParentRec) - } - - @RequiresEdt - fun scheduleRepaint( - request: RepaintRequest, - iconWidth: Int, - iconHeight: Int, - alwaysSchedule: Boolean - ) { - val actualTarget = request.getActualTarget() ?: return - val component = request.component - if (!alwaysSchedule && component == actualTarget) { - component.repaint(request.x, request.y, iconWidth, iconHeight) - } else { - repaintScheduler.pushDirtyComponent(actualTarget, request.paintingParentRec) - } - } - - private fun getTarget(c: Component?): Component? { - val list = SwingUtilities.getAncestorOfClass(JList::class.java, c) - if (list != null) { - return list - } - - // check table first to process com.intellij.ui.treeStructure.treetable.TreeTable correctly - val table = SwingUtilities.getAncestorOfClass(JTable::class.java, c) - if (table != null) { - return table - } - - val tree = SwingUtilities.getAncestorOfClass(JTree::class.java, c) - if (tree != null) { - return tree - } - - val box = SwingUtilities.getAncestorOfClass(JComboBox::class.java, c) - if (box != null) { - return box - } - - val tabLabel = SwingUtilities.getAncestorOfClass(TabLabel::class.java, c) - if (tabLabel != null) { - return tabLabel - } - - return c - } - - class RepaintRequest( - val component: Component?, - val x: Int, - val y: Int, - val target: Component?, - val paintingParent: Component?, - val paintingParentRec: Rectangle? - ) { - fun getActualTarget(): Component? { - if (target == null) { - return null - } - if (SwingUtilities.getWindowAncestor(target) != null) { - return target - } - - if (paintingParent != null && SwingUtilities.getWindowAncestor(paintingParent) != null) { - return paintingParent - } - return null - } - } - - private class RepaintScheduler { - private val myAlarm = Alarm() - private val myQueue = LinkedHashSet() - - fun pushDirtyComponent(c: Component, rec: Rectangle?) { - ApplicationManager.getApplication() - .assertIsDispatchThread() // assert myQueue accessed from EDT only - myAlarm.cancelAllRequests() - myAlarm.addRequest( - { - for (each in myQueue) { - val r = each.rectangle - if (r == null) { - each.component.repaint() - } else { - each.component.repaint(r.x, r.y, r.width, r.height) - } - } - myQueue.clear() - }, - 50) - myQueue.add(RepaintSchedulerRequest(c, rec)) - } - } - - private data class RepaintSchedulerRequest(val component: Component, val rectangle: Rectangle?) -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/ui/LoadingAccountsDetailsProvider.kt b/src/main/kotlin/com/sourcegraph/cody/auth/ui/LoadingAccountsDetailsProvider.kt deleted file mode 100644 index 1e3219e961..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/ui/LoadingAccountsDetailsProvider.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.sourcegraph.cody.auth.ui - -import com.intellij.collaboration.async.CompletableFutureUtil -import com.intellij.collaboration.util.ProgressIndicatorsProvider -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.util.IconUtil -import com.intellij.util.ui.EmptyIcon -import com.sourcegraph.cody.auth.Account -import com.sourcegraph.cody.auth.AccountDetails -import com.sourcegraph.cody.auth.SingleValueModel -import com.sourcegraph.cody.config.notification.AccountSettingChangeContext.Companion.UNAUTHORIZED_ERROR_MESSAGE -import java.awt.Image -import java.util.concurrent.CompletableFuture -import javax.swing.Icon -import org.jetbrains.annotations.Nls - -abstract class LoadingAccountsDetailsProvider( - private val progressIndicatorsProvider: ProgressIndicatorsProvider -) : AccountsDetailsProvider { - - open val defaultIcon: Icon = IconUtil.resizeSquared(EmptyIcon.ICON_16, 40) - private val detailsMap = mutableMapOf>>() - override val loadingStateModel = SingleValueModel(false) - - private var runningProcesses = 0 - - override fun getDetails(account: A): D? = getOrLoad(account).getNow(null)?.details - - private fun getOrLoad(account: A): CompletableFuture> { - return detailsMap.getOrPut(account) { - val indicator = progressIndicatorsProvider.acquireIndicator() - runningProcesses++ - loadingStateModel.value = true - scheduleLoad(account, indicator) - .whenComplete { _, _ -> - ApplicationManager.getApplication().invokeAndWait { - progressIndicatorsProvider.releaseIndicator(indicator) - runningProcesses-- - if (runningProcesses == 0) loadingStateModel.value = false - } - } - .exceptionally { - val error = CompletableFutureUtil.extractError(it) - if (error.message == UNAUTHORIZED_ERROR_MESSAGE) { - DetailsLoadingResult(null, null, error.localizedMessage, true) - } else { - val errorMessage = - error.localizedMessage.takeWhile { c -> c.isLetterOrDigit() || c.isWhitespace() } - DetailsLoadingResult(null, null, errorMessage, false) - } - } - } - } - - abstract fun scheduleLoad( - account: A, - indicator: ProgressIndicator - ): CompletableFuture> - - override fun getAvatarImage(account: A): Image? = getOrLoad(account).getNow(null)?.avatarImage - - override fun getErrorText(account: A): String? = getOrLoad(account).getNow(null)?.error - - override fun checkErrorRequiresReLogin(account: A) = - getOrLoad(account).getNow(null)?.needReLogin ?: false - - override fun reset(account: A) { - detailsMap.remove(account) - } - - override fun resetAll() = detailsMap.clear() - - data class DetailsLoadingResult( - val details: D?, - val avatarImage: Image?, - @Nls val error: String?, - val needReLogin: Boolean - ) -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/ui/ScaleContextCache.kt b/src/main/kotlin/com/sourcegraph/cody/auth/ui/ScaleContextCache.kt deleted file mode 100644 index a0348f933c..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/ui/ScaleContextCache.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.sourcegraph.cody.auth.ui - -import com.intellij.openapi.util.Pair -import com.intellij.ui.scale.DerivedScaleType -import com.intellij.ui.scale.UserScaleContext -import java.util.concurrent.atomic.AtomicReference -import java.util.function.Function - -open class ScaleContextCache( - private val myDataProvider: Function -) { - - private val myData = AtomicReference?>(null) - - /** - * Returns the data object from the cache if it matches the `ctx`, otherwise provides the new data - * via the provider and caches it. - */ - fun getOrProvide(ctx: S): D? { - var data = myData.get() - val scale = ctx.getScale(DerivedScaleType.PIX_SCALE) - if (data == null || scale.compareTo(data.first) != 0) { - myData.set(Pair.create(scale, myDataProvider.apply(ctx)).also { data = it }) - } - return data!!.second - } - - /** Clears the cache. */ - fun clear() { - myData.set(null) - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/ui/ScalingAsyncImageIcon.kt b/src/main/kotlin/com/sourcegraph/cody/auth/ui/ScalingAsyncImageIcon.kt deleted file mode 100644 index cc2caefb2e..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/ui/ScalingAsyncImageIcon.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.sourcegraph.cody.auth.ui - -import com.intellij.ui.scale.ScaleContext -import com.intellij.util.IconUtil -import com.intellij.util.concurrency.AppExecutorUtil -import com.intellij.util.concurrency.EdtExecutorService -import com.intellij.util.ui.ImageUtil -import java.awt.Component -import java.awt.Graphics -import java.awt.Image -import java.util.concurrent.CompletableFuture -import javax.swing.Icon - -class ScalingAsyncImageIcon( - private val size: Int, - defaultIcon: Icon, - imageLoader: () -> CompletableFuture -) : Icon { - private val baseIcon = IconUtil.resizeSquared(defaultIcon, size) - - // Icon can be located on different monitors (with different ScaleContext), - // so it is better to cache icon for each - private val scaledIconCache = - ScaleContextCache { scaleCtx -> - val imageIcon = - imageLoader() - .thenApplyAsync( - { image -> - image?.let { - val resizedImage = ImageUtil.resize(it, size, scaleCtx) - IconUtil.createImageIcon(resizedImage) - } - }, - resizeExecutor) - - DelegatingIcon(baseIcon, imageIcon) - } - - override fun getIconHeight() = baseIcon.iconHeight - - override fun getIconWidth() = baseIcon.iconWidth - - override fun paintIcon(c: Component, g: Graphics, x: Int, y: Int) { - val orProvide: DelegatingIcon? = scaledIconCache.getOrProvide(ScaleContext.create(c)) - orProvide?.paintIcon(c, g, x, y) - } - - companion object { - private val resizeExecutor = - AppExecutorUtil.createBoundedApplicationPoolExecutor("ImageIcon resize executor", 1) - } -} - -private class DelegatingIcon( - baseIcon: Icon, - private val delegateResult: CompletableFuture -) : Icon { - // We collect repaintRequests for the icon to understand which Components should be repainted when - // icon is loaded - // We can receive paintIcon few times for the same component but with different x, y - // Only the last request should be scheduled - private val repaintRequests = - mutableMapOf() - private var delegate: Icon = baseIcon - - init { - delegateResult.thenApplyAsync( - { icon -> - if (icon != null) { - delegate = icon - for ((_, repaintRequest) in repaintRequests) { - repaintScheduler.scheduleRepaint( - repaintRequest, iconWidth, iconHeight, alwaysSchedule = false) - } - } - repaintRequests.clear() - }, - EdtExecutorService.getInstance()) - } - - override fun getIconHeight() = delegate.iconHeight - - override fun getIconWidth() = delegate.iconWidth - - override fun paintIcon(c: Component?, g: Graphics?, x: Int, y: Int) { - delegate.paintIcon(c, g, x, y) - if (!delegateResult.isDone && c != null) { - repaintRequests[c] = repaintScheduler.createRepaintRequest(c, x, y) - } - } - - companion object { - // Scheduler for all DelegatingIcon instances. It repaints components that contain these icons - // in a batch - private val repaintScheduler = DeferredIconRepaintScheduler() - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/ui/SignInWithEnterpriseInstanceAction.kt b/src/main/kotlin/com/sourcegraph/cody/auth/ui/SignInWithEnterpriseInstanceAction.kt deleted file mode 100644 index 790672b80d..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/ui/SignInWithEnterpriseInstanceAction.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.sourcegraph.cody.auth.ui - -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.PlatformCoreDataKeys -import com.intellij.openapi.wm.ToolWindowManager -import com.sourcegraph.cody.CodyToolWindowFactory -import com.sourcegraph.cody.config.CodyAuthenticationManager -import com.sourcegraph.cody.config.CodyPersistentAccountsHost -import com.sourcegraph.cody.config.SourcegraphInstanceLoginDialog -import com.sourcegraph.common.ui.DumbAwareEDTAction -import com.sourcegraph.config.ConfigUtil - -class SignInWithEnterpriseInstanceAction( - private val defaultServer: String = ConfigUtil.DOTCOM_URL -) : DumbAwareEDTAction("Sign in with Sourcegraph") { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - val accountsHost = CodyPersistentAccountsHost(project) - val authManager = CodyAuthenticationManager.getInstance() - val serverUrl = authManager.account?.server?.url ?: defaultServer - val dialog = - SourcegraphInstanceLoginDialog( - project, e.getData(PlatformCoreDataKeys.CONTEXT_COMPONENT), serverUrl) - if (dialog.showAndGet()) { - accountsHost.addAccount( - dialog.codyAuthData.server, - dialog.codyAuthData.login, - dialog.codyAuthData.account.displayName, - dialog.codyAuthData.token, - dialog.codyAuthData.account.id) - if (ConfigUtil.isCodyEnabled()) { - // Open Cody sidebar - val toolWindowManager = ToolWindowManager.getInstance(project) - val toolWindow = toolWindowManager.getToolWindow(CodyToolWindowFactory.TOOL_WINDOW_ID) - toolWindow?.setAvailable(true, null) - toolWindow?.activate {} - } - } - } - - companion object { - // Make it wide enough to see a reasonable servername and token - const val MIN_DIALOG_WIDTH = 600 - const val MIN_DIALOG_HEIGHT = 200 - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/auth/ui/SimpleAccountsListCellRenderer.kt b/src/main/kotlin/com/sourcegraph/cody/auth/ui/SimpleAccountsListCellRenderer.kt deleted file mode 100644 index abb35c8eb2..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/auth/ui/SimpleAccountsListCellRenderer.kt +++ /dev/null @@ -1,191 +0,0 @@ -package com.sourcegraph.cody.auth.ui - -import com.intellij.collaboration.messages.CollaborationToolsBundle -import com.intellij.icons.AllIcons -import com.intellij.ui.components.labels.LinkLabel -import com.intellij.util.IconUtil -import com.intellij.util.ui.GridBag -import com.intellij.util.ui.JBUI -import com.intellij.util.ui.ListUiUtil -import com.intellij.util.ui.UIUtil -import com.sourcegraph.cody.auth.Account -import com.sourcegraph.cody.auth.AccountDetails -import com.sourcegraph.cody.auth.ServerAccount -import com.sourcegraph.cody.config.CachingCodyUserAvatarLoader -import java.awt.BorderLayout -import java.awt.Component -import java.awt.FlowLayout -import java.awt.Font -import java.awt.GridBagConstraints -import java.awt.GridBagLayout -import java.awt.Image -import java.util.concurrent.CompletableFuture -import javax.swing.Icon -import javax.swing.JComponent -import javax.swing.JLabel -import javax.swing.JList -import javax.swing.JPanel -import javax.swing.ListCellRenderer -import org.jetbrains.annotations.Nls - -class SimpleAccountsListCellRenderer( - private val listModel: AccountsListModel, - private val detailsProvider: AccountsDetailsProvider, - private val defaultAvatarIcon: Icon -) : ListCellRenderer, JPanel() { - - private val avatarIcons = mutableMapOf() - - private val accountName = JLabel() - - private val serverName = JLabel() - private val profilePicture = JLabel() - - private val fullName = JLabel() - - private val loadingError = JLabel() - private val reloginLink = LinkLabel(CollaborationToolsBundle.message("login.link"), null) - - private val rightLabel = JLabel() - - init { - layout = BorderLayout(0, 0) - border = JBUI.Borders.empty(5, 8) - - val accountDetailsPanel = - JPanel().apply { - layout = FlowLayout(FlowLayout.LEFT, 0, 0) - - val namesPanel = - JPanel().apply { - layout = GridBagLayout() - border = JBUI.Borders.empty(0, 6, 4, 6) - - val bag = - GridBag() - .setDefaultInsets(JBUI.insetsRight(UIUtil.DEFAULT_HGAP)) - .setDefaultAnchor(GridBagConstraints.WEST) - .setDefaultFill(GridBagConstraints.VERTICAL) - add(fullName, bag.nextLine().next()) - add(accountName, bag.next()) - add(loadingError, bag.next()) - add(reloginLink, bag.next()) - add(serverName, bag.nextLine().coverLine()) - } - - add(profilePicture) - add(namesPanel) - } - - val rightLabelPanel = - JPanel().apply { - layout = FlowLayout(FlowLayout.RIGHT, 0, 0) - add(rightLabel) - } - - val rightPanelWrapper = - JPanel(GridBagLayout()).apply { - val constraints = - GridBagConstraints().apply { - gridx = 0 - gridy = 0 - anchor = GridBagConstraints.CENTER - } - add(rightLabelPanel, constraints) - } - - add(accountDetailsPanel, BorderLayout.WEST) - add(rightPanelWrapper, BorderLayout.EAST) - } - - override fun getListCellRendererComponent( - list: JList, - account: A, - index: Int, - isSelected: Boolean, - cellHasFocus: Boolean - ): Component { - UIUtil.setBackgroundRecursively( - this, ListUiUtil.WithTallRow.background(list, isSelected, list.hasFocus())) - val primaryTextColor = ListUiUtil.WithTallRow.foreground(isSelected, list.hasFocus()) - val secondaryTextColor = ListUiUtil.WithTallRow.secondaryForeground(isSelected, list.hasFocus()) - - accountName.apply { - text = account.name - setBold(if (getDetails(account)?.name == null) isDefault(account) else false) - foreground = if (getDetails(account)?.name == null) primaryTextColor else secondaryTextColor - } - serverName.apply { - if (account is ServerAccount) { - isVisible = true - text = account.server.toString().lowercase() - } else { - isVisible = false - } - foreground = secondaryTextColor - } - profilePicture.apply { icon = getAvatarIcon(account) } - fullName.apply { - text = getDetails(account)?.name - setBold(isDefault(account)) - isVisible = getDetails(account)?.name != null - foreground = primaryTextColor - } - rightLabel.apply { - if (isDefault(account)) { - icon = AllIcons.Actions.Checked_selected - text = "Active" - iconTextGap = JBUI.scale(4) - } else { - icon = null - text = null - } - } - loadingError.apply { - text = getError(account) - foreground = UIUtil.getErrorForeground() - } - reloginLink.apply { - isVisible = getError(account) != null && needReLogin(account) - setListener({ _, _ -> editAccount(list, account) }, null) - } - return this - } - - private fun getAvatarIcon(account: A): Icon { - val image = getAvatarImage(account) - val iconSize = 40 - if (image == null) return IconUtil.resizeSquared(defaultAvatarIcon, iconSize) - return avatarIcons.getOrPut(account) { - ScalingAsyncImageIcon( - iconSize, - defaultAvatarIcon, - imageLoader = { - CompletableFuture() - .completeAsync( - { getAvatarImage(account) }, CachingCodyUserAvatarLoader.avatarLoadingExecutor) - }) - } - } - - private fun isDefault(account: A): Boolean = - (listModel is AccountsListModel.WithActive) && account == listModel.activeAccount - - private fun editAccount(parentComponent: JComponent, account: A) = - listModel.editAccount(parentComponent, account) - - private fun getDetails(account: A): D? = detailsProvider.getDetails(account) - - private fun getAvatarImage(account: A): Image? = detailsProvider.getAvatarImage(account) - - @Nls private fun getError(account: A): String? = detailsProvider.getErrorText(account) - - private fun needReLogin(account: A): Boolean = detailsProvider.checkErrorRequiresReLogin(account) - - companion object { - private fun JLabel.setBold(isBold: Boolean) { - font = - font.deriveFont(if (isBold) font.style or Font.BOLD else font.style and Font.BOLD.inv()) - } - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/autocomplete/CodyAutocompleteManager.kt b/src/main/kotlin/com/sourcegraph/cody/autocomplete/CodyAutocompleteManager.kt index c4665c02c9..58a47ce13c 100644 --- a/src/main/kotlin/com/sourcegraph/cody/autocomplete/CodyAutocompleteManager.kt +++ b/src/main/kotlin/com/sourcegraph/cody/autocomplete/CodyAutocompleteManager.kt @@ -21,18 +21,19 @@ import com.intellij.openapi.wm.ToolWindowId import com.intellij.openapi.wm.ToolWindowManager import com.intellij.ui.GotItTooltip import com.intellij.util.concurrency.annotations.RequiresEdt +import com.sourcegraph.cody.CodyToolWindowContent import com.sourcegraph.cody.Icons import com.sourcegraph.cody.agent.CodyAgentService import com.sourcegraph.cody.agent.protocol.AutocompleteItem import com.sourcegraph.cody.agent.protocol.AutocompleteResult import com.sourcegraph.cody.agent.protocol.CompletionItemParams +import com.sourcegraph.cody.auth.CodyAccount import com.sourcegraph.cody.autocomplete.Utils.triggerAutocompleteAsync import com.sourcegraph.cody.autocomplete.render.AutocompleteRendererType import com.sourcegraph.cody.autocomplete.render.CodyAutocompleteBlockElementRenderer import com.sourcegraph.cody.autocomplete.render.CodyAutocompleteElementRenderer import com.sourcegraph.cody.autocomplete.render.CodyAutocompleteSingleLineRenderer import com.sourcegraph.cody.autocomplete.render.InlayModelUtil.getAllInlaysForEditor -import com.sourcegraph.cody.config.CodyAuthenticationManager import com.sourcegraph.cody.statusbar.CodyStatusService.Companion.resetApplication import com.sourcegraph.cody.vscode.CancellationToken import com.sourcegraph.cody.vscode.InlineCompletionTriggerKind @@ -149,13 +150,14 @@ class CodyAutocompleteManager { isCommandExcluded(currentCommand)) { return } - - val textDocument: TextDocument = IntelliJTextDocument(editor, project) - - if (isTriggeredExplicitly && CodyAuthenticationManager.getInstance().hasNoActiveAccount()) { - HintManager.getInstance().showErrorHint(editor, "Cody: Sign in to use autocomplete") + if (CodyAccount.hasActiveAccount()) { + if (isTriggeredExplicitly) { + HintManager.getInstance().showErrorHint(editor, "Cody: Sign in to use autocomplete") + CodyToolWindowContent.show(project) + } return } + cancelCurrentJob(project) val cancellationToken = CancellationToken() currentJob.set(cancellationToken) @@ -169,6 +171,7 @@ class CodyAutocompleteManager { return } + val textDocument: TextDocument = IntelliJTextDocument(editor, project) triggerAutocompleteAsync( project, editor, diff --git a/src/main/kotlin/com/sourcegraph/cody/chat/OpenChatAction.kt b/src/main/kotlin/com/sourcegraph/cody/chat/OpenChatAction.kt index ceade9a90a..41b028dfc7 100644 --- a/src/main/kotlin/com/sourcegraph/cody/chat/OpenChatAction.kt +++ b/src/main/kotlin/com/sourcegraph/cody/chat/OpenChatAction.kt @@ -1,17 +1,14 @@ package com.sourcegraph.cody.chat import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.wm.ToolWindowManager -import com.sourcegraph.cody.CodyToolWindowFactory +import com.sourcegraph.cody.CodyToolWindowContent import com.sourcegraph.common.ui.DumbAwareEDTAction class OpenChatAction : DumbAwareEDTAction() { override fun actionPerformed(event: AnActionEvent) { val project = event.project ?: return - ToolWindowManager.getInstance(project) - .getToolWindow(CodyToolWindowFactory.TOOL_WINDOW_ID) - ?.show() + CodyToolWindowContent.show(project) TODO("NYI, focus the chat thru TypeScript") } } diff --git a/src/main/kotlin/com/sourcegraph/cody/chat/SignInWithSourcegraphPanel.kt b/src/main/kotlin/com/sourcegraph/cody/chat/SignInWithSourcegraphPanel.kt deleted file mode 100644 index 82bf10fffc..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/chat/SignInWithSourcegraphPanel.kt +++ /dev/null @@ -1,130 +0,0 @@ -package com.sourcegraph.cody.chat - -import com.intellij.ide.DataManager -import com.intellij.openapi.actionSystem.ActionManager -import com.intellij.openapi.actionSystem.ActionPlaces -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.DataContextWrapper -import com.intellij.openapi.actionSystem.Presentation -import com.intellij.openapi.actionSystem.ex.ActionUtil -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.VerticalFlowLayout -import com.intellij.ui.ColorUtil -import com.intellij.ui.SeparatorComponent -import com.intellij.ui.components.AnActionLink -import com.intellij.util.ui.JBUI -import com.intellij.util.ui.UIUtil -import com.sourcegraph.cody.Icons -import com.sourcegraph.cody.auth.SsoAuthMethod.GITHUB -import com.sourcegraph.cody.auth.SsoAuthMethod.GITLAB -import com.sourcegraph.cody.auth.SsoAuthMethod.GOOGLE -import com.sourcegraph.cody.auth.ui.SignInWithEnterpriseInstanceAction -import com.sourcegraph.cody.chat.ui.UIComponents -import com.sourcegraph.cody.config.CodyAccountsHost -import com.sourcegraph.cody.config.CodyPersistentAccountsHost -import com.sourcegraph.cody.config.LogInToSourcegraphAction -import com.sourcegraph.cody.ui.HtmlViewer.createHtmlViewer -import com.sourcegraph.cody.ui.UnderlinedActionLink -import java.awt.BorderLayout -import java.awt.FlowLayout -import java.awt.GridBagLayout -import java.awt.event.ActionEvent -import javax.swing.BoxLayout -import javax.swing.JButton -import javax.swing.JLabel -import javax.swing.JPanel -import javax.swing.border.Border - -class SignInWithSourcegraphPanel(private val project: Project) : JPanel() { - - init { - val buttons = - listOf( - UIComponents.createMainButton(GITHUB.value, Icons.SignIn.Github), - UIComponents.createMainButton(GITLAB.value, Icons.SignIn.Gitlab), - UIComponents.createMainButton(GOOGLE.value, Icons.SignIn.Google)) - - val jEditorPane = createHtmlViewer(project) - jEditorPane.text = - ("

Welcome to Cody

" + - "

Understand and write code faster with an AI assistant

" + - "") - - val panelWithTheMessage = JPanel() - panelWithTheMessage.setLayout(BoxLayout(panelWithTheMessage, BoxLayout.Y_AXIS)) - jEditorPane.setMargin(JBUI.emptyInsets()) - val paddingInsideThePanel: Border = - JBUI.Borders.empty(ADDITIONAL_PADDING_FOR_HEADER, PADDING, 0, PADDING) - val hiImCodyLabel = JLabel(Icons.HiImCody) - val hiImCodyPanel = JPanel(FlowLayout(FlowLayout.LEFT, 5, 0)) - hiImCodyPanel.add(hiImCodyLabel) - panelWithTheMessage.add(hiImCodyPanel) - panelWithTheMessage.add(jEditorPane) - panelWithTheMessage.setBorder(paddingInsideThePanel) - val separatorPanel = JPanel(BorderLayout()) - separatorPanel.setBorder(JBUI.Borders.empty(PADDING, 0)) - val separatorComponent = - SeparatorComponent( - 3, ColorUtil.brighter(UIUtil.getPanelBackground(), 3), UIUtil.getPanelBackground()) - separatorPanel.add(separatorComponent) - panelWithTheMessage.add(separatorPanel) - - val logInToSourcegraphAction = LogInToSourcegraphAction() - for (button in buttons) { - button.addActionListener(getSignInAction(button, logInToSourcegraphAction)) - val buttonPanel = JPanel(BorderLayout()) - buttonPanel.add(button, BorderLayout.CENTER) - panelWithTheMessage.add(buttonPanel) - } - - setLayout(VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, false)) - setBorder(JBUI.Borders.empty(PADDING)) - this.add(panelWithTheMessage) - this.add(createPanelWithSignInWithAnEnterpriseInstance()) - } - - private fun getSignInAction( - button: JButton, - logInToSourcegraphAction: LogInToSourcegraphAction - ): (e: ActionEvent) -> Unit { - return { - val dataContext = DataManager.getInstance().getDataContext(button) - val dataContextWrapper = DataContextWrapper(dataContext) - val accountsHost: CodyAccountsHost = CodyPersistentAccountsHost(project) - dataContextWrapper.putUserData(CodyAccountsHost.KEY, accountsHost) - val event = - AnActionEvent( - null, - dataContext, - ActionPlaces.POPUP, - Presentation(), - ActionManager.getInstance(), - it.modifiers) - if (ActionUtil.lastUpdateAndCheckDumb(logInToSourcegraphAction, event, false)) { - ActionUtil.performActionDumbAwareWithCallbacks(logInToSourcegraphAction, event) - } - } - } - - private fun createPanelWithSignInWithAnEnterpriseInstance(): JPanel { - val signInWithAnEnterpriseInstance: AnActionLink = - UnderlinedActionLink( - "Sign in with an Enterprise Instance", SignInWithEnterpriseInstanceAction("")) - signInWithAnEnterpriseInstance.setAlignmentX(CENTER_ALIGNMENT) - val panelWithSettingsLink = JPanel(BorderLayout()) - panelWithSettingsLink.setBorder(JBUI.Borders.empty(PADDING, 0)) - val linkPanel = JPanel(GridBagLayout()) - linkPanel.add(signInWithAnEnterpriseInstance) - panelWithSettingsLink.add(linkPanel, BorderLayout.PAGE_START) - return panelWithSettingsLink - } - - companion object { - private const val PADDING = 20 - - // 10 here is the default padding from the styles of the h2 - // and we want to make the whole padding to be 20, that's why - // we need the difference between our PADDING and the default padding of the h2 - private const val ADDITIONAL_PADDING_FOR_HEADER = PADDING - 10 - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/chat/actions/BaseCommandAction.kt b/src/main/kotlin/com/sourcegraph/cody/chat/actions/BaseCommandAction.kt index be7fee74f1..6772476327 100644 --- a/src/main/kotlin/com/sourcegraph/cody/chat/actions/BaseCommandAction.kt +++ b/src/main/kotlin/com/sourcegraph/cody/chat/actions/BaseCommandAction.kt @@ -10,11 +10,13 @@ import com.intellij.util.concurrency.AppExecutorUtil import com.sourcegraph.cody.agent.CodyAgentService import com.sourcegraph.cody.agent.protocol.ProtocolTextDocument import com.sourcegraph.cody.agent.protocol_generated.ExecuteCommandParams +import com.sourcegraph.cody.auth.CodyAccount.Companion.hasActiveAccount import com.sourcegraph.cody.commands.CommandId import com.sourcegraph.cody.ignore.ActionInIgnoredFileNotification import com.sourcegraph.cody.ignore.IgnoreOracle import com.sourcegraph.cody.ignore.IgnorePolicy import com.sourcegraph.common.ui.DumbAwareEDTAction +import com.sourcegraph.config.ConfigUtil.isCodyEnabled import com.sourcegraph.utils.CodyEditorUtil import java.util.concurrent.Callable @@ -26,6 +28,11 @@ abstract class BaseCommandAction : DumbAwareEDTAction() { doAction(event.project ?: return) } + override fun update(e: AnActionEvent) { + super.update(e) + e.presentation.isVisible = isCodyEnabled() && hasActiveAccount() + } + open fun doAction(project: Project) { ApplicationManager.getApplication().assertIsDispatchThread() CodyEditorUtil.getSelectedEditors(project).firstOrNull()?.let { editor -> diff --git a/src/main/kotlin/com/sourcegraph/cody/chat/actions/NewChatAction.kt b/src/main/kotlin/com/sourcegraph/cody/chat/actions/NewChatAction.kt index 6a79c278f7..6c015142b6 100644 --- a/src/main/kotlin/com/sourcegraph/cody/chat/actions/NewChatAction.kt +++ b/src/main/kotlin/com/sourcegraph/cody/chat/actions/NewChatAction.kt @@ -2,7 +2,7 @@ package com.sourcegraph.cody.chat.actions import com.intellij.openapi.actionSystem.AnActionEvent import com.sourcegraph.cody.agent.CodyAgentService -import com.sourcegraph.cody.config.CodyAuthenticationManager +import com.sourcegraph.cody.auth.CodyAccount import com.sourcegraph.common.CodyBundle import com.sourcegraph.common.ui.DumbAwareEDTAction @@ -12,8 +12,7 @@ class NewChatAction : DumbAwareEDTAction() { } override fun update(event: AnActionEvent) { - val hasActiveAccount = CodyAuthenticationManager.getInstance().hasActiveAccount() - event.presentation.isEnabled = hasActiveAccount + event.presentation.isEnabled = CodyAccount.hasActiveAccount() if (!event.presentation.isEnabled) { event.presentation.description = CodyBundle.getString("action.sourcegraph.disabled.description") diff --git a/src/main/kotlin/com/sourcegraph/cody/chat/ui/CodyOnboardingGuidancePanel.kt b/src/main/kotlin/com/sourcegraph/cody/chat/ui/CodyOnboardingGuidancePanel.kt deleted file mode 100644 index 68d86493de..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/chat/ui/CodyOnboardingGuidancePanel.kt +++ /dev/null @@ -1,169 +0,0 @@ -package com.sourcegraph.cody.chat.ui - -import com.intellij.openapi.project.Project -import com.intellij.ui.ColorUtil -import com.intellij.ui.JBColor -import com.intellij.ui.components.JBLabel -import com.intellij.ui.components.JBScrollPane -import com.intellij.util.ui.JBUI -import com.intellij.util.ui.UIUtil -import com.sourcegraph.cody.Icons -import com.sourcegraph.cody.chat.ui.UIComponents.createMainButton -import com.sourcegraph.cody.ui.HtmlViewer.createHtmlViewer -import java.awt.BorderLayout -import java.awt.Dimension -import java.awt.event.ActionListener -import javax.swing.BorderFactory -import javax.swing.BoxLayout -import javax.swing.Icon -import javax.swing.JButton -import javax.swing.JComponent -import javax.swing.JEditorPane -import javax.swing.JPanel -import javax.swing.text.html.HTMLEditorKit - -class CodyOnboardingGuidancePanel(val project: Project) : JPanel() { - - private val createIntroductionMessage: JEditorPane = createIntroductionMessage() - - private val mainButton: JButton = createMainButton("Get started") - - init { - val contentPanel = JPanel() - contentPanel.layout = BoxLayout(contentPanel, BoxLayout.Y_AXIS) - contentPanel.add(createChatWithCodyPanel()) - contentPanel.add(createAutocompletePanel()) - contentPanel.add(createExploreCommandsPanel()) - - val scrollPanel = - JBScrollPane( - contentPanel, - JBScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, - JBScrollPane.HORIZONTAL_SCROLLBAR_NEVER) - scrollPanel.preventStretching() - scrollPanel.setBorder(BorderFactory.createEmptyBorder()) - - val buttonPanel = createGetStartedButton() - border = JBUI.Borders.empty(PADDING) - layout = BoxLayout(this, BoxLayout.Y_AXIS) - add(createIntroductionMessage) - add(scrollPanel) - add(buttonPanel) - updateDisplayName(null) - } - - fun updateDisplayName(displayName: String?) { - val userDisplayName = displayName?.let { truncateDisplayName(it) } - createIntroductionMessage.text = buildString { - append("

${createGreetings(userDisplayName)}

") - append("

Let's start by getting you familiar with all the possibilities Cody provides:

") - append("") - } - } - - private fun createGreetings(userDisplayName: String?): String { - if (!userDisplayName.isNullOrEmpty()) { - return "Hi, $userDisplayName" - } - return "Hi" - } - - private fun createIntroductionMessage(): JEditorPane { - val introductionMessage = createHtmlViewer(project) - val introductionMessageEditorKit = introductionMessage.editorKit as HTMLEditorKit - introductionMessageEditorKit.styleSheet.addRule(paragraphColorStyle) - introductionMessage.setMargin(JBUI.emptyInsets()) - introductionMessage.preventStretching() - return introductionMessage - } - - private fun createGetStartedButton(): JPanel { - val buttonPanel = JPanel(BorderLayout()) - buttonPanel.add(mainButton, BorderLayout.NORTH) - buttonPanel.border = BorderFactory.createEmptyBorder(PADDING, 0, 0, 0) - return buttonPanel - } - - private fun createSectionWithTextAndImage(sectionText: String, sectionImage: Icon?): JPanel { - val sectionPanel = sectionPanel() - val infoSectionEditorPane = createInfoSection() - infoSectionEditorPane.text = sectionText - infoSectionEditorPane.setMargin(JBUI.insets(PADDING)) - sectionPanel.add(infoSectionEditorPane, BorderLayout.NORTH) - val imagePanel = JPanel(BorderLayout()) - imagePanel.border = BorderFactory.createEmptyBorder(0, PADDING, PADDING, PADDING) - imagePanel.add(JBLabel(sectionImage), BorderLayout.SOUTH) - sectionPanel.add(imagePanel) - return sectionPanel - } - - private fun createChatWithCodyPanel() = - createSectionWithTextAndImage( - buildString { - append("

Chat with Cody

") - append( - "

Use this sidebar to engage with Cody. Get answers and suggestions about the code you are working on.

") - append("") - }, - Icons.Onboarding.Chat) - - private fun createAutocompletePanel() = - createSectionWithTextAndImage( - buildString { - append("

Autocompletions

") - append( - "

Start typing code to get autocompletions base on the surrounding context (press Tab to accept them):

") - append("") - }, - Icons.Onboarding.Autocomplete) - - private fun createExploreCommandsPanel() = - createSectionWithTextAndImage( - "

Explore the Commands

" + - "

Use commands to execute useful tasks on your code, like generating unit tests, docstrings and more

" + - "", - Icons.Onboarding.Commands) - - private fun JComponent.preventStretching() { - maximumSize = Dimension(Int.MAX_VALUE, getPreferredSize().height) - } - - private fun sectionPanel(): JPanel { - val panel = JPanel() - panel.layout = BorderLayout() - panel.border = - BorderFactory.createCompoundBorder( - BorderFactory.createEmptyBorder(PADDING, 0, 0, 0), - BorderFactory.createLineBorder(borderColor, 1, true)) - return panel - } - - private fun createInfoSection(): JEditorPane { - val sectionInfo = createHtmlViewer(project) - val sectionInfoHtmlEditorKit = sectionInfo.editorKit as HTMLEditorKit - sectionInfoHtmlEditorKit.styleSheet.addRule(paragraphColorStyle) - sectionInfoHtmlEditorKit.styleSheet.addRule("""h3 { margin-top: 0;}""") - return sectionInfo - } - - private fun truncateDisplayName(displayName: String): String = - if (displayName.length > 32) displayName.take(32) + "..." else displayName - - fun addMainButtonActionListener(actionListener: ActionListener) { - mainButton.addActionListener(actionListener) - } - - companion object { - private const val PADDING = 20 - - private val borderColor = - JBColor( - ColorUtil.darker(UIUtil.getPanelBackground(), 1), - ColorUtil.brighter(UIUtil.getPanelBackground(), 3)) - private val paragraphColor = - JBColor( - ColorUtil.brighter(UIUtil.getLabelForeground(), 2), - ColorUtil.darker(UIUtil.getLabelForeground(), 2)) - private val paragraphColorStyle = """p { color: ${ColorUtil.toHtmlColor(paragraphColor)}; }""" - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/BaseLoginDialog.kt b/src/main/kotlin/com/sourcegraph/cody/config/BaseLoginDialog.kt deleted file mode 100644 index d5a6f33a8f..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/BaseLoginDialog.kt +++ /dev/null @@ -1,107 +0,0 @@ -package com.sourcegraph.cody.config - -import com.intellij.collaboration.async.CompletableFutureUtil -import com.intellij.collaboration.async.CompletableFutureUtil.errorOnEdt -import com.intellij.collaboration.async.CompletableFutureUtil.successOnEdt -import com.intellij.openapi.application.ModalityState -import com.intellij.openapi.progress.EmptyProgressIndicator -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.DialogWrapper -import com.intellij.openapi.ui.ValidationInfo -import com.intellij.openapi.util.Disposer -import com.sourcegraph.cody.api.SourcegraphApiRequestExecutor -import com.sourcegraph.cody.auth.SsoAuthMethod -import java.awt.Component -import javax.swing.Action -import javax.swing.JComponent - -abstract class BaseLoginDialog( - protected val project: Project?, - protected val parent: Component?, - executorFactory: SourcegraphApiRequestExecutor.Factory, - private val authMethod: SsoAuthMethod -) : DialogWrapper(project, parent, false, IdeModalityType.PROJECT) { - - protected val loginPanel = CodyLoginPanel(executorFactory) - - override fun createActions(): Array = arrayOf(cancelAction, okAction) - - var id: String = "" - private set - - var login: String = "" - private set - - var displayName: String? = null - private set - - var token: String = "" - private set - - val server: SourcegraphServerPath - get() = loginPanel.getServer() - - fun setToken(token: String?) = loginPanel.setToken(token) - - fun setLogin(login: String?) = loginPanel.setLogin(login, false) - - fun setServer(path: String) = loginPanel.setServer(path) - - fun setCustomRequestHeaders(customRequestHeaders: String) = - loginPanel.setCustomRequestHeaders(customRequestHeaders) - - fun setLoginButtonText(text: String) = setOKButtonText(text) - - fun setError(exception: Throwable) { - loginPanel.setError(exception) - startTrackingValidation() - } - - override fun getPreferredFocusedComponent(): JComponent? = - loginPanel.getPreferredFocusableComponent() - - override fun doValidateAll(): List = loginPanel.doValidateAll() - - override fun doOKAction() { - val modalityState = ModalityState.stateForComponent(loginPanel) - val emptyProgressIndicator = EmptyProgressIndicator(modalityState) - Disposer.register(disposable) { emptyProgressIndicator.cancel() } - - loginPanel - .acquireDetailsAndToken(emptyProgressIndicator, authMethod) - .successOnEdt(modalityState) { (details, newToken) -> - login = details.username - displayName = details.displayName - token = newToken - id = details.id - - close(OK_EXIT_CODE, true) - } - .errorOnEdt(modalityState) { - if (!CompletableFutureUtil.isCancellation(it)) startTrackingValidation() - } - } - - fun authenticate() { - val modalityState = ModalityState.stateForComponent(loginPanel) - val emptyProgressIndicator = EmptyProgressIndicator(modalityState) - Disposer.register(disposable) { emptyProgressIndicator.cancel() } - - loginPanel.setAuthUI() - okAction.isEnabled = false - - loginPanel - .acquireDetailsAndToken(emptyProgressIndicator, SsoAuthMethod.DEFAULT) - .successOnEdt(modalityState) { (details, newToken) -> - login = details.username - displayName = details.displayName - token = newToken - id = details.id - - close(OK_EXIT_CODE, true) - } - .errorOnEdt(modalityState) { - if (!CompletableFutureUtil.isCancellation(it)) startTrackingValidation() - } - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CachingCodyUserAvatarLoader.kt b/src/main/kotlin/com/sourcegraph/cody/config/CachingCodyUserAvatarLoader.kt deleted file mode 100644 index c04bea6e73..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/CachingCodyUserAvatarLoader.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.sourcegraph.cody.config - -import com.github.benmanes.caffeine.cache.Caffeine -import com.intellij.collaboration.async.CompletableFutureUtil.submitIOTask -import com.intellij.collaboration.util.ProgressIndicatorsProvider -import com.intellij.execution.process.ProcessIOExecutorService -import com.intellij.openapi.Disposable -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.progress.ProcessCanceledException -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.progress.ProgressManager -import com.intellij.openapi.util.Disposer -import com.intellij.openapi.util.LowMemoryWatcher -import com.intellij.util.ImageLoader -import com.intellij.util.concurrency.AppExecutorUtil -import com.sourcegraph.cody.api.SourcegraphApiRequestExecutor -import com.sourcegraph.cody.api.SourcegraphApiRequests -import java.awt.Image -import java.time.Duration -import java.time.temporal.ChronoUnit -import java.util.concurrent.CompletableFuture - -@Service -class CachingCodyUserAvatarLoader : Disposable { - - private val indicatorProvider = ProgressIndicatorsProvider().also { Disposer.register(this, it) } - - private val avatarCache = - Caffeine.newBuilder() - .expireAfterAccess(Duration.of(5, ChronoUnit.MINUTES)) - .build>() - - init { - LowMemoryWatcher.register({ avatarCache.invalidateAll() }, this) - } - - fun requestAvatar( - requestExecutor: SourcegraphApiRequestExecutor, - url: String - ): CompletableFuture = - avatarCache.get(url) { - ProgressManager.getInstance().submitIOTask(indicatorProvider) { - loadAndDownscale(requestExecutor, it, url, STORED_IMAGE_SIZE) - } - } - - private fun loadAndDownscale( - requestExecutor: SourcegraphApiRequestExecutor, - indicator: ProgressIndicator, - url: String, - maximumSize: Int - ): Image? { - return try { - val image = SourcegraphApiRequests.CurrentUser(requestExecutor, indicator).getAvatar(url) - if (image.getWidth(null) <= maximumSize && image.getHeight(null) <= maximumSize) image - else ImageLoader.scaleImage(image, maximumSize) - } catch (e: ProcessCanceledException) { - null - } catch (e: Exception) { - LOG.debug("Error loading image from $url", e) - null - } - } - - override fun dispose() {} - - companion object { - private val LOG = logger() - - @JvmStatic fun getInstance(): CachingCodyUserAvatarLoader = service() - - private const val MAXIMUM_ICON_SIZE = 40 - - // store images at maximum used size with maximum reasonable scale to avoid upscaling (3 for - // system scale, 2 for user scale) - private const val STORED_IMAGE_SIZE = MAXIMUM_ICON_SIZE * 6 - - internal val avatarLoadingExecutor = - AppExecutorUtil.createBoundedApplicationPoolExecutor( - "Avatars loading executor", ProcessIOExecutorService.INSTANCE, 3) - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CodyAccount.kt b/src/main/kotlin/com/sourcegraph/cody/config/CodyAccount.kt deleted file mode 100644 index bfda777229..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/CodyAccount.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.sourcegraph.cody.config - -import com.intellij.openapi.util.NlsSafe -import com.intellij.util.xmlb.annotations.Attribute -import com.intellij.util.xmlb.annotations.Property -import com.intellij.util.xmlb.annotations.Tag -import com.sourcegraph.cody.auth.ServerAccount -import com.sourcegraph.config.ConfigUtil -import java.io.File - -@Tag("account") -data class CodyAccount( - @NlsSafe @Attribute("name") override var name: String = "", - @Attribute("displayName") var displayName: String? = name, - @Property(style = Property.Style.ATTRIBUTE, surroundWithTag = false) - override var server: SourcegraphServerPath = - SourcegraphServerPath.from(ConfigUtil.DOTCOM_URL, ""), - @Attribute("id") override var id: String = generateId(), -) : ServerAccount() { - - fun isDotcomAccount(): Boolean = server.url.lowercase().startsWith(ConfigUtil.DOTCOM_URL) - - fun isEnterpriseAccount(): Boolean = isDotcomAccount().not() - - override fun toString(): String = File(server.toString(), name).path -} - -fun Collection.getFirstAccountOrNull() = - this.firstOrNull { it.isDotcomAccount() } ?: this.firstOrNull() diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountCodyProEnabled.kt b/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountCodyProEnabled.kt deleted file mode 100644 index 2c41c0f9b5..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountCodyProEnabled.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.sourcegraph.cody.config - -class CodyAccountCodyProEnabled(val codyProEnabled: Boolean?) diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountDetails.kt b/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountDetails.kt deleted file mode 100644 index d130331536..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountDetails.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.sourcegraph.cody.config - -import com.sourcegraph.cody.auth.AccountDetails - -class CodyAccountDetails( - val id: String, - val username: String, - val displayName: String?, - val avatarURL: String? -) : AccountDetails { - override val name: String - get() = displayName ?: username -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountDetailsProvider.kt b/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountDetailsProvider.kt deleted file mode 100644 index d5e2510261..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountDetailsProvider.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.sourcegraph.cody.config - -import com.intellij.collaboration.async.CompletableFutureUtil.submitIOTask -import com.intellij.collaboration.async.CompletableFutureUtil.successOnEdt -import com.intellij.collaboration.util.ProgressIndicatorsProvider -import com.intellij.openapi.components.service -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.progress.ProgressManager -import com.sourcegraph.cody.api.SourcegraphApiRequestExecutor -import com.sourcegraph.cody.api.SourcegraphApiRequests -import com.sourcegraph.cody.auth.ui.LoadingAccountsDetailsProvider -import java.util.concurrent.CompletableFuture - -class CodyAccountDetailsProvider( - progressIndicatorsProvider: ProgressIndicatorsProvider, - private val accountManager: CodyAccountManager, - private val accountsModel: CodyAccountListModel -) : LoadingAccountsDetailsProvider(progressIndicatorsProvider) { - - override fun scheduleLoad( - account: CodyAccount, - indicator: ProgressIndicator - ): CompletableFuture> { - - return ProgressManager.getInstance() - .submitIOTask(indicator) { ind -> - val token = - accountsModel.newCredentials.getOrElse(account) { - accountManager.findCredentials(account) - } ?: return@submitIOTask noToken() - val executor = - service().create(account.server, token) - - val accountDetails = SourcegraphApiRequests.CurrentUser(executor, ind).getDetails() - val image = - accountDetails.avatarURL?.let { url -> - CachingCodyUserAvatarLoader.getInstance().requestAvatar(executor, url).join() - } - DetailsLoadingResult(accountDetails, image, null, false) - } - .successOnEdt(indicator.modalityState) { - accountsModel.accountsListModel.contentsChanged(account) - it - } - } - - private fun noToken() = - DetailsLoadingResult(null, null, "Missing access token", true) -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountListModel.kt b/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountListModel.kt deleted file mode 100644 index d2026113f2..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountListModel.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.sourcegraph.cody.config - -import com.intellij.openapi.actionSystem.ActionGroup -import com.intellij.openapi.actionSystem.ActionManager -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.JBPopupMenu -import com.intellij.ui.awt.RelativePoint -import com.sourcegraph.cody.agent.protocol.BillingCategory -import com.sourcegraph.cody.agent.protocol.BillingMetadata -import com.sourcegraph.cody.agent.protocol.BillingProduct -import com.sourcegraph.cody.agent.protocol.TelemetryEventParameters -import com.sourcegraph.cody.auth.ui.AccountsListModel -import com.sourcegraph.cody.auth.ui.AccountsListModelBase -import com.sourcegraph.cody.telemetry.TelemetryV2 -import javax.swing.JComponent - -class CodyAccountListModel(private val project: Project) : - AccountsListModelBase(), - AccountsListModel.WithActive, - CodyAccountsHost { - - private val actionManager = ActionManager.getInstance() - - override var activeAccount: CodyAccount? = null - - override fun addAccount(parentComponent: JComponent, point: RelativePoint?) { - val group = actionManager.getAction("Cody.Accounts.AddAccount") as ActionGroup - val popup = actionManager.createActionPopupMenu("LogInToSourcegraphAction", group) - - val actualPoint = point ?: RelativePoint.getCenterOf(parentComponent) - popup.setTargetComponent(parentComponent) - JBPopupMenu.showAt(actualPoint, popup.component) - } - - override fun editAccount(parentComponent: JComponent, account: CodyAccount) { - val token = newCredentials[account] ?: getOldToken(account) - val authData = - CodyAuthenticationManager.getInstance() - .login( - project, - parentComponent, - CodyLoginRequest( - title = "Edit Sourcegraph Account", - server = account.server, - login = account.name, - token = token, - customRequestHeaders = account.server.customRequestHeaders, - loginButtonText = "Save account", - )) - - if (authData == null) return - - account.name = authData.login - account.server = - SourcegraphServerPath(authData.server.url, authData.server.customRequestHeaders) - newCredentials[account] = authData.token - notifyCredentialsChanged(account) - } - - private fun getOldToken(account: CodyAccount) = - CodyAuthenticationManager.getInstance().getTokenForAccount(account) - - override fun addAccount( - server: SourcegraphServerPath, - login: String, - displayName: String?, - token: String, - id: String - ) { - TelemetryV2.sendTelemetryEvent( - project, - "auth.signin.token", - "clicked", - TelemetryEventParameters( - billingMetadata = BillingMetadata(BillingProduct.CODY, BillingCategory.BILLABLE))) - - val account = CodyAccount(login, displayName, server, id) - if (accountsListModel.isEmpty) { - CodyAuthenticationManager.getInstance().setActiveAccount(account) - } - if (!accountsListModel.toList().contains(account)) { - accountsListModel.add(account) - } - newCredentials[account] = token - notifyCredentialsChanged(account) - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountManager.kt b/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountManager.kt deleted file mode 100644 index 0c2cb1605f..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountManager.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.sourcegraph.cody.config - -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.sourcegraph.cody.auth.AccountManagerBase -import com.sourcegraph.config.ConfigUtil - -@Service -class CodyAccountManager : - AccountManagerBase(ConfigUtil.SERVICE_DISPLAY_NAME) { - override fun accountsRepository() = service() - - override fun deserializeCredentials(credentials: String): String = credentials - - override fun serializeCredentials(credentials: String): String = credentials -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountsHost.kt b/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountsHost.kt deleted file mode 100644 index e93d394e7c..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/CodyAccountsHost.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.sourcegraph.cody.config - -import com.intellij.openapi.actionSystem.DataKey -import com.intellij.openapi.util.Key - -interface CodyAccountsHost { - fun addAccount( - server: SourcegraphServerPath, - login: String, - displayName: String?, - token: String, - id: String - ) - - companion object { - val DATA_KEY: DataKey = DataKey.create("CodyAccountsHots") - val KEY: Key = Key.create("CodyAccountsHots") - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CodyAuthCredentialsUi.kt b/src/main/kotlin/com/sourcegraph/cody/config/CodyAuthCredentialsUi.kt deleted file mode 100644 index 4d43061e3c..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/CodyAuthCredentialsUi.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.sourcegraph.cody.config - -import com.intellij.openapi.progress.ProcessCanceledException -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.progress.util.ProgressIndicatorUtils -import com.intellij.openapi.ui.ValidationInfo -import com.intellij.ui.AnimatedIcon -import com.intellij.ui.components.JBLabel -import com.intellij.ui.dsl.builder.Panel -import com.intellij.util.ui.UIUtil.getInactiveTextColor -import com.sourcegraph.cody.api.SourcegraphApiRequestExecutor -import com.sourcegraph.cody.auth.SourcegraphAuthService -import com.sourcegraph.cody.auth.SsoAuthMethod -import com.sourcegraph.common.CodyBundle -import javax.swing.JComponent - -class CodyAuthCredentialsUi(val factory: SourcegraphApiRequestExecutor.Factory) : - CodyCredentialsUi() { - - override fun getPreferredFocusableComponent(): JComponent? = null - - override fun getValidationInfo(): ValidationInfo? = null - - override fun createExecutor(server: SourcegraphServerPath): SourcegraphApiRequestExecutor = - factory.create(server, "") - - override fun acquireDetailsAndToken( - executor: SourcegraphApiRequestExecutor, - indicator: ProgressIndicator, - authMethod: SsoAuthMethod - ): Pair { - val token = acquireToken(indicator, executor.server.url, authMethod) - // The token has changed, so create a new executor to talk to the same server with the new - // token. - val newExecutor = factory.create(executor.server, token) - val details = CodyTokenCredentialsUi.acquireDetails(newExecutor, indicator, null) - return details to token - } - - override fun handleAcquireError(error: Throwable): ValidationInfo = - CodyTokenCredentialsUi.handleError(error) - - override fun setBusy(busy: Boolean) = Unit - - override fun Panel.centerPanel() { - row { - cell( - JBLabel(CodyBundle.getString("login.dialog.check-browser")).apply { - icon = AnimatedIcon.Default.INSTANCE - foreground = getInactiveTextColor() - }) - } - } - - private fun acquireToken( - indicator: ProgressIndicator, - server: String, - authMethod: SsoAuthMethod - ): String { - val credentialsFuture = SourcegraphAuthService.instance.authorize(server, authMethod) - try { - return ProgressIndicatorUtils.awaitWithCheckCanceled(credentialsFuture, indicator) - } catch (pce: ProcessCanceledException) { - credentialsFuture.completeExceptionally(pce) - throw pce - } - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CodyAuthenticationManager.kt b/src/main/kotlin/com/sourcegraph/cody/config/CodyAuthenticationManager.kt deleted file mode 100644 index bd75be4b83..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/CodyAuthenticationManager.kt +++ /dev/null @@ -1,318 +0,0 @@ -package com.sourcegraph.cody.config - -import com.intellij.collaboration.async.CompletableFutureUtil.submitIOTask -import com.intellij.openapi.Disposable -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.ModalityState -import com.intellij.openapi.components.PersistentStateComponent -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.State -import com.intellij.openapi.components.Storage -import com.intellij.openapi.components.StoragePathMacros -import com.intellij.openapi.components.service -import com.intellij.openapi.progress.EmptyProgressIndicator -import com.intellij.openapi.progress.ProgressManager -import com.intellij.openapi.project.Project -import com.intellij.openapi.project.ProjectManager -import com.intellij.openapi.util.Disposer -import com.intellij.openapi.wm.WindowManager -import com.intellij.util.AuthData -import com.intellij.util.concurrency.annotations.RequiresEdt -import com.sourcegraph.cody.agent.CodyAgentService -import com.sourcegraph.cody.api.SourcegraphApiRequestExecutor -import com.sourcegraph.cody.api.SourcegraphApiRequests -import com.sourcegraph.cody.config.notification.AccountSettingChangeActionNotifier -import com.sourcegraph.cody.config.notification.AccountSettingChangeContext -import com.sourcegraph.cody.config.notification.AccountSettingChangeContext.Companion.UNAUTHORIZED_ERROR_MESSAGE -import com.sourcegraph.config.ConfigUtil -import java.awt.Component -import java.awt.event.WindowAdapter -import java.awt.event.WindowEvent -import java.util.concurrent.CompletableFuture -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit -import org.jetbrains.annotations.CalledInAny - -internal class CodyAuthData(val account: CodyAccount, login: String, token: String) : - AuthData(login, token) { - val server: SourcegraphServerPath - get() = account.server - - val token: String - get() = password!! -} - -enum class AccountTier(val value: Int) { - DOTCOM_FREE(0), - DOTCOM_PRO(1), - ENTERPRISE(2) -} - -data class AuthenticationState( - val tier: CompletableFuture, - val isTokenInvalid: CompletableFuture -) - -// That class is keep only for a compatibility purposes, so we can load old per-project account -// settings, and use them as the new default when `CodyAccountsSettings` state is loaded for a very -// first time in the `noStateLoaded` method -@Deprecated("Use only for backward compatibility purposes") -@State( - name = "CodyActiveAccount", - storages = [Storage(StoragePathMacros.WORKSPACE_FILE)], - reportStatistic = false) -@Service(Service.Level.PROJECT) -class DeprecatedCodyActiveAccount(val project: Project) : - PersistentStateComponent { - private var accountState: CodyAuthenticationManager.AccountState? = null - - override fun getState(): CodyAuthenticationManager.AccountState? { - return accountState - } - - override fun loadState(state: CodyAuthenticationManager.AccountState) { - accountState = state - } -} - -/** Entry point for interactions with Sourcegraph authentication subsystem */ -@State( - name = "CodyAccountsSettings", - storages = [Storage("cody_accounts_settings.xml")], - reportStatistic = false) -@Service(Service.Level.APP) -class CodyAuthenticationManager : - PersistentStateComponent, Disposable { - - var account: CodyAccount? = null - private set - - private val scheduler = Executors.newScheduledThreadPool(1) - - private fun publisher(project: Project) = - project.messageBus.syncPublisher(AccountSettingChangeActionNotifier.TOPIC) - - @Volatile private var tier: CompletableFuture? = null - - @Volatile private var isTokenInvalid: CompletableFuture? = null - - init { - scheduler.scheduleAtFixedRate( - /* command = */ { getAuthenticationState() }, - /* initialDelay = */ 2, - /* period = */ 2, - /* unit = */ TimeUnit.HOURS) - } - - private val accountManager: CodyAccountManager - get() = service() - - fun addAuthChangeListener(project: Project) { - val frame = WindowManager.getInstance().getFrame(project) - val listener = - object : WindowAdapter() { - override fun windowActivated(e: WindowEvent?) { - super.windowActivated(e) - ApplicationManager.getApplication().executeOnPooledThread { getAuthenticationState() } - } - } - frame?.addWindowListener(listener) - Disposer.register(this) { frame?.removeWindowListener(listener) } - } - - @CalledInAny fun getAccounts(): Set = accountManager.accounts - - @CalledInAny - private fun getAuthenticationState(): AuthenticationState { - val previousIsTokenInvalid = isTokenInvalid?.getNow(null) - val previousTier = tier?.getNow(null) - val isTokenInvalidFuture = CompletableFuture() - val tierFuture = CompletableFuture() - val authenticationState = AuthenticationState(tierFuture, isTokenInvalidFuture) - val theAccount = account ?: return authenticationState - val token = theAccount.let(::getTokenForAccount) - - if (isTokenInvalid == null) isTokenInvalid = isTokenInvalidFuture - if (tier == null) tier = tierFuture - - tierFuture.thenApply { currentAccountTier -> - if (previousTier != currentAccountTier) { - tier = tierFuture - ProjectManager.getInstance().openProjects.forEach { project -> - publisher(project).afterAction(AccountSettingChangeContext(accountTierChanged = true)) - } - } - } - - isTokenInvalidFuture.thenApply { isInvalid -> - if (previousIsTokenInvalid != isInvalid) { - isTokenInvalid = isTokenInvalidFuture - ProjectManager.getInstance().openProjects.forEach { project -> - publisher(project).afterAction(AccountSettingChangeContext(isTokenInvalidChanged = true)) - } - } - } - - if (token != null) { - val executor = SourcegraphApiRequestExecutor.Factory.instance.create(theAccount.server, token) - val progressIndicator = EmptyProgressIndicator(ModalityState.nonModal()) - val submitIOTask = - service().submitIOTask(progressIndicator) { - if (theAccount.isEnterpriseAccount()) { - // We ignore the result, but we need to make sure the request is executed - // successfully. Otherwise, the token will be invalidated and the user will be - // prompted to re-authenticate. - SourcegraphApiRequests.CurrentUser(executor, progressIndicator).getDetails() - tierFuture.complete(AccountTier.ENTERPRISE) - } else { - // We need a separate request to check if the user is on Cody Pro. - val codyProEnabled = - SourcegraphApiRequests.CurrentUser(executor, progressIndicator) - .getCodyProEnabled() - val currentAccountType = - if (codyProEnabled.codyProEnabled == true) { - AccountTier.DOTCOM_PRO - } else { - AccountTier.DOTCOM_FREE - } - tierFuture.complete(currentAccountType) - } - } - - submitIOTask.exceptionally { error -> - isTokenInvalidFuture.complete(error.cause?.message == UNAUTHORIZED_ERROR_MESSAGE) - null - } - } else { - isTokenInvalidFuture.complete(true) - } - - return authenticationState - } - - /** - * User account type can change because users can renew or cancel their subscriptions at any time. - * Components which state depends on this property should be checking state of - * `getActiveAccountTier` in non-blocking way or listen for `AccountSettingChangeContext` events - * to update their state accordingly later. - */ - @CalledInAny - fun getActiveAccountTier(): CompletableFuture { - return tier ?: getAuthenticationState().tier - } - - @CalledInAny - fun getIsTokenInvalid(): CompletableFuture { - return isTokenInvalid ?: getAuthenticationState().isTokenInvalid - } - - @CalledInAny - internal fun getTokenForAccount(account: CodyAccount): String? = - accountManager.findCredentials(account) - - internal fun isAccountUnique(name: String, server: SourcegraphServerPath) = - accountManager.accounts.none { it.name == name && it.server.url == server.url } - - @RequiresEdt - internal fun login( - project: Project, - parentComponent: Component?, - request: CodyLoginRequest - ): CodyAuthData? = request.loginWithToken(project, parentComponent) - - @RequiresEdt - internal fun updateAccountToken(newAccount: CodyAccount, newToken: String) { - val oldToken = getTokenForAccount(newAccount) - accountManager.updateAccount(newAccount, newToken) - if (oldToken != newToken && newAccount == account) { - - ProjectManager.getInstance().openProjects.forEach { project -> - CodyAgentService.withAgentRestartIfNeeded(project) { agent -> - if (!project.isDisposed) { - agent.server.extensionConfiguration_didChange(ConfigUtil.getAgentConfiguration(project)) - publisher(project).afterAction(AccountSettingChangeContext(accessTokenChanged = true)) - } - } - } - } - } - - fun setActiveAccount(newAccount: CodyAccount?) { - val previousAccount = account - val previousUrl = previousAccount?.server?.url - val previousTier = previousAccount?.isDotcomAccount() - - account = newAccount - tier = null - isTokenInvalid = null - - val serverUrlChanged = previousUrl != newAccount?.server?.url - val tierChanged = previousTier != newAccount?.isDotcomAccount() - val accountChanged = previousAccount != newAccount - - ProjectManager.getInstance().openProjects.forEach { project -> - CodyAgentService.withAgentRestartIfNeeded(project) { agent -> - if (!project.isDisposed) { - agent.server.extensionConfiguration_didChange(ConfigUtil.getAgentConfiguration(project)) - if (serverUrlChanged || tierChanged || accountChanged) { - publisher(project) - .afterAction( - AccountSettingChangeContext( - serverUrlChanged = serverUrlChanged, - accountTierChanged = tierChanged, - accessTokenChanged = accountChanged)) - } - } - } - } - } - - fun hasActiveAccount() = !hasNoActiveAccount() - - fun hasNoActiveAccount() = account == null - - fun showInvalidAccessTokenError() = getIsTokenInvalid().getNow(null) == true - - fun removeAll() { - accountManager.accounts.forEach { accountManager.removeAccount(it) } - } - - override fun dispose() { - scheduler.shutdown() - } - - override fun getState(): AccountState { - return AccountState().apply { activeAccountId = account?.id } - } - - override fun loadState(state: AccountState) { - val initialAccount = - state.activeAccountId?.let { id -> accountManager.accounts.find { it.id == id } } - - if (initialAccount != null) { - setActiveAccount(initialAccount) - } - } - - override fun noStateLoaded() { - super.noStateLoaded() - val initialAccountId = - ProjectManager.getInstance().openProjects.firstNotNullOfOrNull { - it.service().state?.activeAccountId - } ?: getAccounts().firstOrNull()?.id - - loadState(AccountState().apply { activeAccountId = initialAccountId }) - } - - companion object { - - @JvmStatic - fun getInstance(): CodyAuthenticationManager { - return ApplicationManager.getApplication().getService(CodyAuthenticationManager::class.java) - } - } - - class AccountState { - var activeAccountId: String? = null - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CodyCredentialsUi.kt b/src/main/kotlin/com/sourcegraph/cody/config/CodyCredentialsUi.kt deleted file mode 100644 index 57456fc77c..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/CodyCredentialsUi.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.sourcegraph.cody.config - -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.ui.ValidationInfo -import com.intellij.ui.dsl.builder.Panel -import com.intellij.ui.dsl.builder.panel -import com.intellij.util.ui.JBEmptyBorder -import com.intellij.util.ui.UIUtil -import com.sourcegraph.cody.api.SourcegraphApiRequestExecutor -import com.sourcegraph.cody.auth.SsoAuthMethod -import javax.swing.JComponent -import javax.swing.JPanel - -abstract class CodyCredentialsUi { - abstract fun getPreferredFocusableComponent(): JComponent? - - abstract fun getValidationInfo(): ValidationInfo? - - abstract fun createExecutor(server: SourcegraphServerPath): SourcegraphApiRequestExecutor - - abstract fun acquireDetailsAndToken( - executor: SourcegraphApiRequestExecutor, - indicator: ProgressIndicator, - authMethod: SsoAuthMethod - ): Pair - - abstract fun handleAcquireError(error: Throwable): ValidationInfo - - abstract fun setBusy(busy: Boolean) - - var footer: Panel.() -> Unit = {} - - fun getPanel(): JPanel = - panel { - centerPanel() - footer() - } - .apply { - // Border is required to have more space - otherwise there could be issues with focus - // ring. - // `getRegularPanelInsets()` is used to simplify border calculation for dialogs where - // this panel is used. - border = JBEmptyBorder(UIUtil.getRegularPanelInsets()) - } - - protected abstract fun Panel.centerPanel() -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CodyLoginPanel.kt b/src/main/kotlin/com/sourcegraph/cody/config/CodyLoginPanel.kt deleted file mode 100644 index d563c947bc..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/CodyLoginPanel.kt +++ /dev/null @@ -1,144 +0,0 @@ -package com.sourcegraph.cody.config - -import com.intellij.collaboration.async.CompletableFutureUtil.completionOnEdt -import com.intellij.collaboration.async.CompletableFutureUtil.errorOnEdt -import com.intellij.collaboration.async.CompletableFutureUtil.submitIOTask -import com.intellij.openapi.components.service -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.progress.ProgressManager -import com.intellij.openapi.ui.ValidationInfo -import com.intellij.ui.AnimatedIcon -import com.intellij.ui.components.fields.ExtendableTextComponent -import com.intellij.ui.components.fields.ExtendableTextField -import com.intellij.ui.components.panels.Wrapper -import com.intellij.ui.dsl.builder.Panel -import com.sourcegraph.cody.api.SourcegraphApiRequestExecutor -import com.sourcegraph.cody.auth.SsoAuthMethod -import java.util.concurrent.CompletableFuture -import javax.swing.JComponent -import javax.swing.JTextField - -internal typealias UniqueLoginPredicate = (login: String, server: SourcegraphServerPath) -> Boolean - -class ServerTextField : ExtendableTextField(SourcegraphServerPath.DEFAULT_HOST, 0) - -class CodyLoginPanel( - executorFactory: SourcegraphApiRequestExecutor.Factory, -) : Wrapper() { - - private val serverTextField = ServerTextField() - private var tokenAcquisitionError: ValidationInfo? = null - - private lateinit var currentUi: CodyCredentialsUi - private var tokenUi = CodyTokenCredentialsUi(serverTextField, executorFactory) - - private var authUi = CodyAuthCredentialsUi(executorFactory) - - private val progressIcon = AnimatedIcon.Default.INSTANCE - private val progressExtension = ExtendableTextComponent.Extension { progressIcon } - - var footer: Panel.() -> Unit - get() = tokenUi.footer - set(value) { - tokenUi.footer = value - applyUi(currentUi) - } - - init { - applyUi(tokenUi) - } - - private fun applyUi(ui: CodyCredentialsUi) { - currentUi = ui - setContent(currentUi.getPanel()) - currentUi.getPreferredFocusableComponent()?.requestFocus() - tokenAcquisitionError = null - } - - fun getPreferredFocusableComponent(): JComponent? = - serverTextField.takeIf { it.isEditable && it.text.isBlank() } - ?: currentUi.getPreferredFocusableComponent() - - fun doValidateAll(): List { - val uiError = - validateCustomRequestHeaders(tokenUi.customRequestHeadersField) - ?: currentUi.getValidationInfo() - - return listOfNotNull(uiError, tokenAcquisitionError) - } - - fun getServerPathValidationInfo() = tokenUi.getServerPathValidationInfo() - - private fun validateCustomRequestHeaders(field: JTextField): ValidationInfo? { - if (field.text.isEmpty()) { - return null - } - val pairs: Array = - field.text.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - if (pairs.size % 2 != 0) { - return ValidationInfo("Must be a comma-separated list of string pairs", field) - } - var i = 0 - while (i < pairs.size) { - val headerName = pairs[i].trim { it <= ' ' } - if (!headerName.matches("[\\w-]+".toRegex())) { - return ValidationInfo("Invalid HTTP header name: $headerName", field) - } - i += 2 - } - return null - } - - private fun setBusy(busy: Boolean) { - serverTextField.apply { - if (busy) addExtension(progressExtension) else removeExtension(progressExtension) - } - serverTextField.isEnabled = !busy - - currentUi.setBusy(busy) - } - - fun acquireDetailsAndToken( - progressIndicator: ProgressIndicator, - authMethod: SsoAuthMethod - ): CompletableFuture> { - setBusy(true) - tokenAcquisitionError = null - - val server = getServer() - val executor = currentUi.createExecutor(server) - - return service() - .submitIOTask(progressIndicator) { - currentUi.acquireDetailsAndToken(executor, it, authMethod) - } - .completionOnEdt(progressIndicator.modalityState) { setBusy(false) } - .errorOnEdt(progressIndicator.modalityState) { setError(it) } - } - - fun getServer(): SourcegraphServerPath = - SourcegraphServerPath.from( - serverTextField.text.trim().lowercase(), tokenUi.customRequestHeadersField.text.trim()) - - fun setServer(path: String) { - serverTextField.text = path.lowercase() - } - - fun setCustomRequestHeaders(customRequestHeaders: String) { - tokenUi.customRequestHeadersField.text = customRequestHeaders - } - - fun setLogin(login: String?, editable: Boolean) { - tokenUi.setFixedLogin(if (editable) null else login) - } - - fun setToken(token: String?) = tokenUi.setToken(token.orEmpty()) - - fun setError(exception: Throwable?) { - tokenAcquisitionError = exception?.let { currentUi.handleAcquireError(it) } - } - - fun setTokenUi() = applyUi(tokenUi) - - fun setAuthUI() = applyUi(authUi) -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CodyLoginRequest.kt b/src/main/kotlin/com/sourcegraph/cody/config/CodyLoginRequest.kt deleted file mode 100644 index cdb8520acd..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/CodyLoginRequest.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.sourcegraph.cody.config - -import com.intellij.openapi.project.Project -import com.sourcegraph.cody.auth.SsoAuthMethod -import git4idea.DialogManager -import java.awt.Component - -internal class CodyLoginRequest( - val title: String? = null, - val server: SourcegraphServerPath? = null, - val login: String? = null, - val token: String? = null, - val customRequestHeaders: String? = null, - val loginButtonText: String? = null -) - -internal fun CodyLoginRequest.loginWithToken( - project: Project, - parentComponent: Component? -): CodyAuthData? { - val dialog = SourcegraphTokenLoginDialog(project, parentComponent, SsoAuthMethod.DEFAULT) - configure(dialog) - - return dialog.getAuthData() -} - -private fun CodyLoginRequest.configure(dialog: BaseLoginDialog) { - server?.let { dialog.setServer(it.toString()) } - login?.let { dialog.setLogin(it) } - token?.let { dialog.setToken(it) } - customRequestHeaders?.let { dialog.setCustomRequestHeaders(it) } - title?.let { dialog.title = it } - loginButtonText?.let { dialog.setLoginButtonText(it) } -} - -private fun BaseLoginDialog.getAuthData(): CodyAuthData? { - DialogManager.show(this) - return if (isOK) CodyAuthData(CodyAccount(login, displayName, server), login, token) else null -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CodyPersistentAccountsHost.kt b/src/main/kotlin/com/sourcegraph/cody/config/CodyPersistentAccountsHost.kt deleted file mode 100644 index bb72972843..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/CodyPersistentAccountsHost.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.sourcegraph.cody.config - -import com.intellij.openapi.project.Project -import com.sourcegraph.cody.agent.protocol.BillingCategory -import com.sourcegraph.cody.agent.protocol.BillingMetadata -import com.sourcegraph.cody.agent.protocol.BillingProduct -import com.sourcegraph.cody.agent.protocol.TelemetryEventParameters -import com.sourcegraph.cody.telemetry.TelemetryV2 - -class CodyPersistentAccountsHost(private val project: Project) : CodyAccountsHost { - override fun addAccount( - server: SourcegraphServerPath, - login: String, - displayName: String?, - token: String, - id: String - ) { - TelemetryV2.sendTelemetryEvent( - project, - "auth.signin.token", - "clicked", - TelemetryEventParameters( - billingMetadata = BillingMetadata(BillingProduct.CODY, BillingCategory.BILLABLE))) - - val codyAccount = CodyAccount(login, displayName, server, id) - val authManager = CodyAuthenticationManager.getInstance() - authManager.updateAccountToken(codyAccount, token) - authManager.setActiveAccount(codyAccount) - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CodyTokenCredentialsUi.kt b/src/main/kotlin/com/sourcegraph/cody/config/CodyTokenCredentialsUi.kt deleted file mode 100644 index 1e60fdfd30..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/CodyTokenCredentialsUi.kt +++ /dev/null @@ -1,149 +0,0 @@ -package com.sourcegraph.cody.config - -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.ui.ValidationInfo -import com.intellij.openapi.ui.setEmptyState -import com.intellij.ui.components.JBTextField -import com.intellij.ui.components.fields.ExtendableTextField -import com.intellij.ui.dsl.builder.AlignX -import com.intellij.ui.dsl.builder.MAX_LINE_LENGTH_NO_WRAP -import com.intellij.ui.dsl.builder.Panel -import com.sourcegraph.cody.api.SourcegraphApiRequestExecutor -import com.sourcegraph.cody.api.SourcegraphApiRequests -import com.sourcegraph.cody.api.SourcegraphAuthenticationException -import com.sourcegraph.cody.auth.SsoAuthMethod -import com.sourcegraph.cody.config.DialogValidationUtils.custom -import com.sourcegraph.cody.config.DialogValidationUtils.notBlank -import com.sourcegraph.common.AuthorizationUtil -import com.sourcegraph.common.CodyBundle -import com.sourcegraph.common.CodyBundle.fmt -import java.net.UnknownHostException -import javax.swing.JComponent -import javax.swing.JTextField - -internal class CodyTokenCredentialsUi( - private val serverTextField: ExtendableTextField, - val factory: SourcegraphApiRequestExecutor.Factory -) : CodyCredentialsUi() { - - lateinit var customRequestHeadersField: ExtendableTextField - private val tokenTextField = JBTextField() - private var fixedLogin: String? = null - - fun setToken(token: String) { - tokenTextField.text = token - } - - override fun Panel.centerPanel() { - row(CodyBundle.getString("login.dialog.instance-url.label")) { - cell(serverTextField).align(AlignX.FILL) - } - row(CodyBundle.getString("login.dialog.token.label")) { - cell(tokenTextField).align(AlignX.FILL) - } - group(CodyBundle.getString("login.dialog.optional.group"), indent = false) { - row(CodyBundle.getString("login.dialog.custom-headers.label")) { - customRequestHeadersField = ExtendableTextField("", 0) - cell(customRequestHeadersField) - .align(AlignX.FILL) - .comment( - CodyBundle.getString("login.dialog.custom-headers.comment").trimMargin(), - MAX_LINE_LENGTH_NO_WRAP) - .applyToComponent { - this.setEmptyState(CodyBundle.getString("login.dialog.custom-headers.empty")) - } - } - } - } - - override fun getPreferredFocusableComponent(): JComponent = tokenTextField - - override fun getValidationInfo() = - getServerPathValidationInfo() - ?: notBlank(tokenTextField, CodyBundle.getString("login.dialog.validation.empty-token")) - ?: custom(tokenTextField, CodyBundle.getString("login.dialog.validation.invalid-token")) { - AuthorizationUtil.isValidAccessToken(tokenTextField.text) - } - - fun getServerPathValidationInfo(): ValidationInfo? { - return notBlank( - serverTextField, CodyBundle.getString("login.dialog.validation.empty-instance-url")) - ?: validateServerPath(serverTextField) - } - - private fun validateServerPath(field: JTextField): ValidationInfo? = - if (!isServerPathValid(field.text)) { - ValidationInfo(CodyBundle.getString("login.dialog.validation.invalid-instance-url"), field) - } else { - null - } - - private fun isServerPathValid(text: String): Boolean { - return runCatching { SourcegraphServerPath.from(text, "") }.getOrNull() != null - } - - override fun createExecutor(server: SourcegraphServerPath): SourcegraphApiRequestExecutor { - return factory.create(server, tokenTextField.text) - } - - override fun acquireDetailsAndToken( - executor: SourcegraphApiRequestExecutor, - indicator: ProgressIndicator, - authMethod: SsoAuthMethod - ): Pair { - val details = acquireDetails(executor, indicator, fixedLogin) - return details to tokenTextField.text - } - - override fun handleAcquireError(error: Throwable): ValidationInfo = - when (error) { - is SourcegraphParseException -> - ValidationInfo( - error.message - ?: CodyBundle.getString("login.dialog.validation.invalid-instance-url"), - serverTextField) - else -> handleError(error) - } - - override fun setBusy(busy: Boolean) { - tokenTextField.isEnabled = !busy - } - - fun setFixedLogin(fixedLogin: String?) { - this.fixedLogin = fixedLogin - } - - companion object { - - fun acquireDetails( - executor: SourcegraphApiRequestExecutor, - indicator: ProgressIndicator, - fixedLogin: String? - ): CodyAccountDetails { - val accountDetails = SourcegraphApiRequests.CurrentUser(executor, indicator).getDetails() - - val login = accountDetails.username - if (fixedLogin != null && fixedLogin != login) - throw SourcegraphAuthenticationException("Token should match username \"$fixedLogin\".") - - return accountDetails - } - - fun handleError(error: Throwable): ValidationInfo = - when (error) { - is UnknownHostException -> - ValidationInfo(CodyBundle.getString("login.dialog.error.server-unreachable")) - .withOKEnabled() - is SourcegraphAuthenticationException -> - ValidationInfo( - CodyBundle.getString("login.dialog.error.incorrect-credentials") - .fmt(error.message.orEmpty())) - .withOKEnabled() - else -> - ValidationInfo( - CodyBundle.getString("login.dialog.error.invalid-authentication") - .fmt(error.message.orEmpty())) - .withOKEnabled() - } - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/DialogValidationUtils.kt b/src/main/kotlin/com/sourcegraph/cody/config/DialogValidationUtils.kt deleted file mode 100644 index 9943fc5944..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/DialogValidationUtils.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.sourcegraph.cody.config - -import com.intellij.openapi.ui.ValidationInfo -import com.intellij.openapi.util.NlsContexts -import javax.swing.JComponent -import javax.swing.JTextField - -object DialogValidationUtils { - /** Returns [ValidationInfo] with [message] if [textField] is blank */ - fun notBlank(textField: JTextField, @NlsContexts.DialogMessage message: String): ValidationInfo? { - return custom(textField, message) { !textField.text.isNullOrBlank() } - } - - /** Returns [ValidationInfo] with [message] if [isValid] returns false */ - fun custom( - component: JComponent, - @NlsContexts.DialogMessage message: String, - isValid: () -> Boolean - ): ValidationInfo? { - return if (!isValid()) ValidationInfo(message, component) else null - } - - /** - * Chains the [validators] so that if one of them returns non-null [ValidationInfo] the rest of - * them are not checked - */ - fun chain(vararg validators: Validator): Validator = { - validators.asSequence().mapNotNull { it() }.firstOrNull() - } -} - -typealias Validator = () -> ValidationInfo? diff --git a/src/main/kotlin/com/sourcegraph/cody/config/LogInToSourcegraphAction.kt b/src/main/kotlin/com/sourcegraph/cody/config/LogInToSourcegraphAction.kt deleted file mode 100644 index 2cee68fffb..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/LogInToSourcegraphAction.kt +++ /dev/null @@ -1,137 +0,0 @@ -package com.sourcegraph.cody.config - -import com.intellij.ide.DataManager -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.PlatformCoreDataKeys -import com.intellij.openapi.project.Project -import com.intellij.util.ui.JBUI -import com.sourcegraph.cody.agent.protocol.BillingCategory -import com.sourcegraph.cody.agent.protocol.BillingMetadata -import com.sourcegraph.cody.agent.protocol.BillingProduct -import com.sourcegraph.cody.agent.protocol.TelemetryEventParameters -import com.sourcegraph.cody.api.SourcegraphApiRequestExecutor -import com.sourcegraph.cody.auth.SsoAuthMethod -import com.sourcegraph.cody.telemetry.TelemetryV2 -import com.sourcegraph.common.ui.DumbAwareEDTAction -import java.awt.Component -import javax.swing.Action -import javax.swing.JButton -import javax.swing.JComponent - -class LogInToSourcegraphAction : BaseAddAccountWithTokenAction() { - override val defaultServer: String - get() = SourcegraphServerPath.DEFAULT_HOST - - override fun actionPerformed(e: AnActionEvent) { - e.project?.let { - TelemetryV2.sendTelemetryEvent( - it, - "auth.login", - "clicked", - TelemetryEventParameters( - billingMetadata = BillingMetadata(BillingProduct.CODY, BillingCategory.BILLABLE))) - } - - val accountsHost = getCodyAccountsHost(e) ?: return - val authMethod: SsoAuthMethod = - try { - val text = (e.getData(PlatformCoreDataKeys.CONTEXT_COMPONENT) as JButton).text - SsoAuthMethod.from(text) - } catch (e: ClassCastException) { - SsoAuthMethod.DEFAULT - } - val dialog = - CodyAuthLoginDialog( - e.project, e.getData(PlatformCoreDataKeys.CONTEXT_COMPONENT), authMethod) - dialog.setServer(defaultServer) - if (dialog.showAndGet()) { - accountsHost.addAccount( - dialog.server, dialog.login, dialog.displayName, dialog.token, dialog.id) - } - } -} - -class AddCodyEnterpriseAccountAction : BaseAddAccountWithTokenAction() { - override val defaultServer: String - get() = "" - - override fun actionPerformed(e: AnActionEvent) { - e.project?.let { - TelemetryV2.sendTelemetryEvent( - it, - "auth.login", - "clicked", - TelemetryEventParameters( - billingMetadata = BillingMetadata(BillingProduct.CODY, BillingCategory.BILLABLE))) - } - - val accountsHost = getCodyAccountsHost(e) ?: return - val dialog = - SourcegraphInstanceLoginDialog( - project = e.project, parent = e.getData(PlatformCoreDataKeys.CONTEXT_COMPONENT)) - - if (dialog.showAndGet()) { - val data = dialog.codyAuthData - accountsHost.addAccount( - data.account.server, data.login, data.account.displayName, data.token, data.account.id) - } - } -} - -abstract class BaseAddAccountWithTokenAction : DumbAwareEDTAction() { - abstract val defaultServer: String - - override fun update(e: AnActionEvent) { - val codyAccountsHost = getCodyAccountsHost(e) - e.presentation.isEnabledAndVisible = codyAccountsHost != null - } - - protected fun getCodyAccountsHost(e: AnActionEvent) = - (e.getData(CodyAccountsHost.DATA_KEY) - ?: DataManager.getInstance().loadFromDataContext(e.dataContext, CodyAccountsHost.KEY)) -} - -fun signInWithSourcegraphDialog(project: Project?, parent: Component?): BaseLoginDialog = - SourcegraphTokenLoginDialog(project, parent, SsoAuthMethod.DEFAULT).apply { - title = "Sign in with Sourcegraph" - setLoginButtonText("Sign in") - } - -internal class SourcegraphTokenLoginDialog( - project: Project?, - parent: Component?, - authMethod: SsoAuthMethod -) : BaseLoginDialog(project, parent, SourcegraphApiRequestExecutor.Factory.instance, authMethod) { - - init { - title = "Login to Sourcegraph" - setLoginButtonText("Login") - loginPanel.setTokenUi() - init() - } - - override fun createCenterPanel(): JComponent = loginPanel -} - -internal class CodyAuthLoginDialog( - project: Project?, - parent: Component?, - authMethod: SsoAuthMethod -) : BaseLoginDialog(project, parent, SourcegraphApiRequestExecutor.Factory.instance, authMethod) { - - init { - title = "Login to Sourcegraph" - loginPanel.setAuthUI() - init() - } - - override fun createActions(): Array = arrayOf(cancelAction) - - override fun show() { - doOKAction() - super.show() - } - - override fun createCenterPanel(): JComponent = - JBUI.Panels.simplePanel(loginPanel).withPreferredWidth(200) -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/ServerAuth.kt b/src/main/kotlin/com/sourcegraph/cody/config/ServerAuth.kt deleted file mode 100644 index d51e8aec7f..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/ServerAuth.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.sourcegraph.cody.config - -import com.sourcegraph.config.ConfigUtil - -data class ServerAuth( - val instanceUrl: String, - val accessToken: String, - val customRequestHeaders: String -) - -object ServerAuthLoader { - - @JvmStatic - fun loadServerAuth(): ServerAuth { - val codyAuthenticationManager = CodyAuthenticationManager.getInstance() - val defaultAccount = codyAuthenticationManager.account - if (defaultAccount != null) { - val accessToken = codyAuthenticationManager.getTokenForAccount(defaultAccount) ?: "" - return ServerAuth( - defaultAccount.server.url, accessToken, defaultAccount.server.customRequestHeaders) - } - return ServerAuth(ConfigUtil.DOTCOM_URL, "", "") - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/SourcegraphInstanceLoginDialog.kt b/src/main/kotlin/com/sourcegraph/cody/config/SourcegraphInstanceLoginDialog.kt deleted file mode 100644 index a5cc86fad7..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/SourcegraphInstanceLoginDialog.kt +++ /dev/null @@ -1,260 +0,0 @@ -package com.sourcegraph.cody.config - -import com.intellij.collaboration.async.CompletableFutureUtil -import com.intellij.collaboration.async.CompletableFutureUtil.errorOnEdt -import com.intellij.collaboration.async.CompletableFutureUtil.submitIOTask -import com.intellij.collaboration.async.CompletableFutureUtil.successOnEdt -import com.intellij.openapi.application.ModalityState -import com.intellij.openapi.application.invokeLater -import com.intellij.openapi.components.service -import com.intellij.openapi.observable.util.whenTextChanged -import com.intellij.openapi.progress.EmptyProgressIndicator -import com.intellij.openapi.progress.ProcessCanceledException -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.progress.ProgressManager -import com.intellij.openapi.progress.util.ProgressIndicatorUtils -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.DialogWrapper -import com.intellij.openapi.ui.ValidationInfo -import com.intellij.openapi.ui.setEmptyState -import com.intellij.openapi.util.Disposer -import com.intellij.ui.AnimatedIcon -import com.intellij.ui.components.JBLabel -import com.intellij.ui.components.JBTextField -import com.intellij.ui.components.fields.ExtendableTextField -import com.intellij.ui.dsl.builder.AlignX -import com.intellij.ui.dsl.builder.MAX_LINE_LENGTH_NO_WRAP -import com.intellij.ui.dsl.builder.panel -import com.intellij.ui.layout.not -import com.intellij.ui.layout.selected -import com.intellij.util.ui.UIUtil.getInactiveTextColor -import com.sourcegraph.cody.api.SourcegraphApiRequestExecutor -import com.sourcegraph.cody.api.SourcegraphAuthenticationException -import com.sourcegraph.cody.auth.SourcegraphAuthService -import com.sourcegraph.cody.auth.SsoAuthMethod -import com.sourcegraph.cody.config.DialogValidationUtils.custom -import com.sourcegraph.cody.config.DialogValidationUtils.notBlank -import com.sourcegraph.common.AuthorizationUtil -import com.sourcegraph.common.CodyBundle -import com.sourcegraph.common.CodyBundle.fmt -import java.awt.Component -import java.awt.event.ActionEvent -import java.net.UnknownHostException -import java.util.concurrent.CompletableFuture -import javax.swing.Action -import javax.swing.JCheckBox - -class SourcegraphInstanceLoginDialog( - project: Project?, - parent: Component?, - private val defaultInstanceUrl: String = "" -) : DialogWrapper(project, parent, false, IdeModalityType.PROJECT) { - - private var tokenAcquisitionError: ValidationInfo? = null - private lateinit var instanceUrlField: JBTextField - private lateinit var tokenField: JBTextField - private lateinit var customRequestHeadersField: ExtendableTextField - internal lateinit var codyAuthData: CodyAuthData - - private val advancedAction = - object : DialogWrapperAction(CodyBundle.getString("login.dialog.show-advanced")) { - override fun doAction(e: ActionEvent) { - advancedSettings.isSelected = advancedSettings.isSelected.not() - if (advancedSettings.isSelected) { - setOKButtonText(CodyBundle.getString("login.dialog.add-account")) - putValue(NAME, CodyBundle.getString("login.dialog.hide-advanced")) - } else { - setOKButtonText(CodyBundle.getString("login.dialog.authorize-in-browser")) - putValue(NAME, CodyBundle.getString("login.dialog.show-advanced")) - tokenAcquisitionError = null - } - - invokeLater { pack() } - } - } - - private val isAcquiringToken = JCheckBox().also { it.isSelected = false } - private val advancedSettings = JCheckBox().also { it.isSelected = false } - - init { - title = CodyBundle.getString("login.dialog.title") - setOKButtonText(CodyBundle.getString("login.dialog.authorize-in-browser")) - init() - } - - override fun createCenterPanel() = panel { - row { - cell( - JBLabel(CodyBundle.getString("login.dialog.check-browser")).apply { - icon = AnimatedIcon.Default.INSTANCE - foreground = getInactiveTextColor() - }) - } - .visibleIf(isAcquiringToken.selected) - row(CodyBundle.getString("login.dialog.instance-url.label")) { - textField() - .applyToComponent { - emptyText.text = CodyBundle.getString("login.dialog.instance-url.empty") - instanceUrlField = this - text = defaultInstanceUrl - } - .align(AlignX.FILL) - } - .rowComment( - CodyBundle.getString("login.dialog.instance-url.comment"), - maxLineLength = MAX_LINE_LENGTH_NO_WRAP) - .visibleIf(isAcquiringToken.selected.not()) - row(CodyBundle.getString("login.dialog.token.label")) { - textField() - .applyToComponent { - tokenField = this - tokenField.document.whenTextChanged { tokenAcquisitionError = null } - } - .align(AlignX.FILL) - } - .visibleIf(advancedSettings.selected) - group(CodyBundle.getString("login.dialog.optional.group"), indent = false) { - row(CodyBundle.getString("login.dialog.custom-headers.label")) { - cell(ExtendableTextField(/*columns =*/ 0)) - .align(AlignX.FILL) - .comment( - CodyBundle.getString("login.dialog.custom-headers.comment"), - maxLineLength = MAX_LINE_LENGTH_NO_WRAP) - .applyToComponent { - setEmptyState(CodyBundle.getString("login.dialog.custom-headers.empty")) - customRequestHeadersField = this - } - } - } - .visibleIf(advancedSettings.selected) - } - - override fun createActions(): Array = arrayOf(cancelAction, advancedAction, okAction) - - override fun doOKAction() { - if (advancedSettings.isSelected.not()) { - isAcquiringToken.isSelected = true - } - okAction.isEnabled = false - advancedAction.isEnabled = false - - val emptyProgressIndicator = EmptyProgressIndicator(ModalityState.defaultModalityState()) - Disposer.register(disposable) { emptyProgressIndicator.cancel() } - val server = deriveServerPath() - - acquireDetailsAndToken(emptyProgressIndicator) - .successOnEdt(ModalityState.nonModal()) { (details, token) -> - codyAuthData = - CodyAuthData( - CodyAccount(details.username, details.displayName, server, details.id), - details.username, - token) - close(OK_EXIT_CODE, true) - } - .errorOnEdt(ModalityState.nonModal()) { - if (advancedSettings.isSelected.not()) { - isAcquiringToken.isSelected = false - } - okAction.isEnabled = true - advancedAction.isEnabled = true - if (!CompletableFutureUtil.isCancellation(it)) startTrackingValidation() - } - } - - override fun getPreferredFocusedComponent() = instanceUrlField - - override fun doValidateAll(): MutableList { - val tokenFieldErrors = - if (advancedSettings.isSelected) { - notBlank(tokenField, CodyBundle.getString("login.dialog.validation.empty-token")) - ?: custom(tokenField, CodyBundle.getString("login.dialog.validation.invalid-token")) { - AuthorizationUtil.isValidAccessToken(tokenField.text) - } - } else null - - return listOfNotNull( - notBlank( - instanceUrlField, - CodyBundle.getString("login.dialog.validation.empty-instance-url")) - ?: validateServerPath(), - tokenAcquisitionError, - tokenFieldErrors) - .toMutableList() - } - - private fun validateServerPath(): ValidationInfo? = - if (!isInstanceUrlValid(instanceUrlField)) { - ValidationInfo( - CodyBundle.getString("login.dialog.validation.invalid-instance-url"), instanceUrlField) - } else { - null - } - - private fun isInstanceUrlValid(textField: JBTextField): Boolean = - runCatching { SourcegraphServerPath.from(textField.text, "") }.getOrNull() != null - - private fun acquireDetailsAndToken( - progressIndicator: ProgressIndicator - ): CompletableFuture> { - tokenAcquisitionError = null - - val server = deriveServerPath() - - return service() - .submitIOTask(progressIndicator) { - val token = acquireToken(indicator = it, server.url) - val executor = SourcegraphApiRequestExecutor.Factory.instance.create(server, token) - val details = - CodyTokenCredentialsUi.acquireDetails(executor, indicator = it, fixedLogin = null) - return@submitIOTask details to token - } - .errorOnEdt(progressIndicator.modalityState) { error -> - tokenAcquisitionError = - when (error) { - is SourcegraphParseException -> - ValidationInfo( - error.message - ?: CodyBundle.getString("login.dialog.validation.invalid-instance-url"), - instanceUrlField) - is UnknownHostException -> - ValidationInfo(CodyBundle.getString("login.dialog.error.server-unreachable")) - .withOKEnabled() - is SourcegraphAuthenticationException -> - ValidationInfo( - CodyBundle.getString("login.dialog.error.incorrect-credentials") - .fmt(error.message.orEmpty())) - else -> - ValidationInfo( - CodyBundle.getString("login.dialog.error.invalid-authentication") - .fmt(error.message.orEmpty())) - } - } - } - - private fun acquireToken(indicator: ProgressIndicator, server: String): String { - val credentialsFuture = - if (advancedSettings.isSelected) { - CompletableFuture.completedFuture(tokenField.text) - } else { - SourcegraphAuthService.instance.authorize(server, SsoAuthMethod.DEFAULT) - } - - try { - return ProgressIndicatorUtils.awaitWithCheckCanceled(credentialsFuture, indicator) - } catch (pce: ProcessCanceledException) { - credentialsFuture.completeExceptionally(pce) - throw pce - } - } - - private fun deriveServerPath(): SourcegraphServerPath { - val customRequestHeaders = - if (advancedSettings.isSelected) { - customRequestHeadersField.text.trim() - } else { - "" - } - return SourcegraphServerPath.from( - uri = instanceUrlField.text.trim().lowercase(), customRequestHeaders) - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/migration/AccountsMigration.kt b/src/main/kotlin/com/sourcegraph/cody/config/migration/AccountsMigration.kt new file mode 100644 index 0000000000..67fa13ca85 --- /dev/null +++ b/src/main/kotlin/com/sourcegraph/cody/config/migration/AccountsMigration.kt @@ -0,0 +1,18 @@ +package com.sourcegraph.cody.config.migration + +import com.sourcegraph.cody.auth.CodyAccount +import com.sourcegraph.cody.auth.deprecated.DeprecatedCodyAccountManager + +object AccountsMigration { + fun migrate() { + val codyAccountManager = DeprecatedCodyAccountManager.getInstance() + codyAccountManager.getAccounts().forEach { oldAccount -> + val token = codyAccountManager.getTokenForAccount(oldAccount) + if (token != null) { + CodyAccount(oldAccount.server).storeToken(token) + } + } + + codyAccountManager.account?.server?.let { CodyAccount.setActiveAccount(CodyAccount(it)) } + } +} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/migration/ChatHistoryMigration.kt b/src/main/kotlin/com/sourcegraph/cody/config/migration/ChatHistoryMigration.kt index 1f68dbaecc..e6a739e4de 100644 --- a/src/main/kotlin/com/sourcegraph/cody/config/migration/ChatHistoryMigration.kt +++ b/src/main/kotlin/com/sourcegraph/cody/config/migration/ChatHistoryMigration.kt @@ -6,8 +6,8 @@ import com.sourcegraph.cody.agent.protocol_generated.Chat_ImportParams import com.sourcegraph.cody.agent.protocol_generated.SerializedChatInteraction import com.sourcegraph.cody.agent.protocol_generated.SerializedChatMessage import com.sourcegraph.cody.agent.protocol_generated.SerializedChatTranscript -import com.sourcegraph.cody.config.CodyAccount -import com.sourcegraph.cody.config.CodyAuthenticationManager +import com.sourcegraph.cody.auth.deprecated.DeprecatedCodyAccount +import com.sourcegraph.cody.auth.deprecated.DeprecatedCodyAccountManager import com.sourcegraph.cody.history.HistoryService import com.sourcegraph.cody.history.state.ChatState import com.sourcegraph.cody.history.state.MessageState @@ -18,7 +18,7 @@ object ChatHistoryMigration { fun migrate(project: Project) { CodyAgentService.withAgent(project) { agent -> val chats = - CodyAuthenticationManager.getInstance().getAccounts().associateWith { account -> + DeprecatedCodyAccountManager.getInstance().getAccounts().associateWith { account -> (HistoryService.getInstance(project).getChatHistoryFor(account.id) ?: listOf()) } val history = toChatInput(chats) @@ -28,7 +28,7 @@ object ChatHistoryMigration { } fun toChatInput( - chats: Map> + chats: Map> ): Map> { return chats .map { (account, chats) -> diff --git a/src/main/kotlin/com/sourcegraph/cody/config/migration/SettingsMigration.kt b/src/main/kotlin/com/sourcegraph/cody/config/migration/SettingsMigration.kt index 55002ad10f..2c259a82f8 100644 --- a/src/main/kotlin/com/sourcegraph/cody/config/migration/SettingsMigration.kt +++ b/src/main/kotlin/com/sourcegraph/cody/config/migration/SettingsMigration.kt @@ -14,12 +14,11 @@ import com.intellij.openapi.wm.ToolWindowManager import com.sourcegraph.cody.CodyToolWindowFactory import com.sourcegraph.cody.api.SourcegraphApiRequestExecutor import com.sourcegraph.cody.api.SourcegraphApiRequests -import com.sourcegraph.cody.config.CodyAccount -import com.sourcegraph.cody.config.CodyAccountDetails +import com.sourcegraph.cody.auth.SourcegraphServerPath +import com.sourcegraph.cody.auth.deprecated.DeprecatedCodyAccount +import com.sourcegraph.cody.auth.deprecated.DeprecatedCodyAccountManager import com.sourcegraph.cody.config.CodyApplicationSettings -import com.sourcegraph.cody.config.CodyAuthenticationManager import com.sourcegraph.cody.config.CodyProjectSettings -import com.sourcegraph.cody.config.SourcegraphServerPath import com.sourcegraph.cody.history.HistoryService import com.sourcegraph.cody.history.state.AccountData import com.sourcegraph.cody.history.state.ChatState @@ -61,16 +60,22 @@ class SettingsMigration : Activity { RunOnceUtil.runOnceForProject(project, "CodyAssignOrphanedChatsToActiveAccount") { migrateOrphanedChatsToActiveAccount(project) } - - DeprecatedChatLlmMigration.migrate(project) - ChatTagsLlmMigration.migrate(project) + RunOnceUtil.runOnceForProject(project, "DeprecatedChatLlmMigration") { + DeprecatedChatLlmMigration.migrate(project) + } + RunOnceUtil.runOnceForProject(project, "ChatTagsLlmMigration") { + ChatTagsLlmMigration.migrate(project) + } RunOnceUtil.runOnceForProject(project, "CodyMigrateChatHistory-v2") { ChatHistoryMigration.migrate(project) } + RunOnceUtil.runOnceForProject(project, "AccountsToCodyMigration") { + AccountsMigration.migrate() + } } private fun migrateOrphanedChatsToActiveAccount(project: Project) { - val activeAccountId = CodyAuthenticationManager.getInstance().account?.id + val activeAccountId = DeprecatedCodyAccountManager.getInstance().account?.id HistoryService.getInstance(project) .state .chats @@ -80,9 +85,9 @@ class SettingsMigration : Activity { private fun refreshAccountsIds(project: Project) { val customRequestHeaders = extractCustomRequestHeaders(project) - CodyAuthenticationManager.getInstance().getAccounts().forEach { codyAccount -> + DeprecatedCodyAccountManager.getInstance().getAccounts().forEach { codyAccount -> val server = SourcegraphServerPath.from(codyAccount.server.url, customRequestHeaders) - val token = CodyAuthenticationManager.getInstance().getTokenForAccount(codyAccount) + val token = DeprecatedCodyAccountManager.getInstance().getTokenForAccount(codyAccount) if (token != null) { loadUserDetails( SourcegraphApiRequestExecutor.Factory.instance, @@ -90,7 +95,7 @@ class SettingsMigration : Activity { EmptyProgressIndicator(ModalityState.nonModal()), server) { codyAccount.id = it.id - CodyAuthenticationManager.getInstance().updateAccountToken(codyAccount, token) + DeprecatedCodyAccountManager.getInstance().addOrUpdateAccountToken(codyAccount, token) } } } @@ -178,7 +183,6 @@ class SettingsMigration : Activity { EmptyProgressIndicator(ModalityState.nonModal())) } else { addAccountIfUnique( - project, dotcomAccessToken, server, requestExecutorFactory, @@ -206,7 +210,6 @@ class SettingsMigration : Activity { EmptyProgressIndicator(ModalityState.nonModal())) } else { addAccountIfUnique( - project, enterpriseAccessToken, it, requestExecutorFactory, @@ -219,14 +222,13 @@ class SettingsMigration : Activity { } private fun addAccountIfUnique( - project: Project, accessToken: String, server: SourcegraphServerPath, requestExecutorFactory: SourcegraphApiRequestExecutor.Factory, progressIndicator: EmptyProgressIndicator, ) { loadUserDetails(requestExecutorFactory, accessToken, progressIndicator, server) { - addAccount(CodyAccount(it.name, it.displayName, server, it.id), accessToken) + addAccount(DeprecatedCodyAccount(it.name, it.displayName, server, it.id), accessToken) } } @@ -237,10 +239,10 @@ class SettingsMigration : Activity { progressIndicator: EmptyProgressIndicator, ) { loadUserDetails(requestExecutorFactory, accessToken, progressIndicator, server) { - val codyAccount = CodyAccount(it.name, it.displayName, server, it.id) + val codyAccount = DeprecatedCodyAccount(it.name, it.displayName, server, it.id) addAccount(codyAccount, accessToken) - if (CodyAuthenticationManager.getInstance().hasNoActiveAccount()) - CodyAuthenticationManager.getInstance().setActiveAccount(codyAccount) + if (!DeprecatedCodyAccountManager.getInstance().hasActiveAccount()) + DeprecatedCodyAccountManager.getInstance().setActiveAccount(codyAccount) } } @@ -249,7 +251,7 @@ class SettingsMigration : Activity { accessToken: String, progressIndicator: EmptyProgressIndicator, server: SourcegraphServerPath, - onSuccess: (CodyAccountDetails) -> Unit + onSuccess: (SourcegraphApiRequests.CurrentUser.CodyAccountDetails) -> Unit ): CompletableFuture = service() .submitIOTask(progressIndicator) { @@ -265,14 +267,14 @@ class SettingsMigration : Activity { } } - private fun addAccount(codyAccount: CodyAccount, token: String) { + private fun addAccount(codyAccount: DeprecatedCodyAccount, token: String) { if (isAccountUnique(codyAccount)) { - CodyAuthenticationManager.getInstance().updateAccountToken(codyAccount, token) + DeprecatedCodyAccountManager.getInstance().addOrUpdateAccountToken(codyAccount, token) } } - private fun isAccountUnique(codyAccount: CodyAccount): Boolean { - return CodyAuthenticationManager.getInstance() + private fun isAccountUnique(codyAccount: DeprecatedCodyAccount): Boolean { + return DeprecatedCodyAccountManager.getInstance() .isAccountUnique(codyAccount.name, codyAccount.server) } diff --git a/src/main/kotlin/com/sourcegraph/cody/config/notification/AccountSettingChangeActionNotifier.kt b/src/main/kotlin/com/sourcegraph/cody/config/notification/AccountSettingChangeActionNotifier.kt deleted file mode 100644 index 53555af14d..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/notification/AccountSettingChangeActionNotifier.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.sourcegraph.cody.config.notification - -import com.intellij.util.messages.Topic - -interface AccountSettingChangeActionNotifier { - companion object { - @JvmStatic - val TOPIC = - Topic.create( - "Sourcegraph Cody + Code Search plugin settings have changed", - AccountSettingChangeActionNotifier::class.java) - } - - fun beforeAction(serverUrlChanged: Boolean) - - fun afterAction(context: AccountSettingChangeContext) -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/notification/AccountSettingChangeContext.kt b/src/main/kotlin/com/sourcegraph/cody/config/notification/AccountSettingChangeContext.kt deleted file mode 100644 index ce418beebe..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/notification/AccountSettingChangeContext.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.sourcegraph.cody.config.notification - -class AccountSettingChangeContext( - val serverUrlChanged: Boolean = false, - val accessTokenChanged: Boolean = false, - // We are currently not using `accountTierChanged` explicitly anywhere, but we include it to - // make clear that `AccountSettingChangeContext` is not only about server url and token changes. - // There are code paths which needs to be executed even if account is not switched and only tier - // changes. - val accountTierChanged: Boolean = false, - val isTokenInvalidChanged: Boolean = false -) { - fun accountSwitched(): Boolean = serverUrlChanged || accessTokenChanged - - companion object { - const val UNAUTHORIZED_ERROR_MESSAGE = "Request response: 401 Unauthorized" - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/notification/AccountSettingChangeListener.kt b/src/main/kotlin/com/sourcegraph/cody/config/notification/AccountSettingChangeListener.kt deleted file mode 100644 index 33b10f6324..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/notification/AccountSettingChangeListener.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.sourcegraph.cody.config.notification - -import com.intellij.openapi.components.Service -import com.intellij.openapi.project.Project -import com.sourcegraph.cody.CodyToolWindowContent -import com.sourcegraph.cody.statusbar.CodyStatusService -import com.sourcegraph.cody.telemetry.TelemetryV2 -import com.sourcegraph.common.UpgradeToCodyProNotification -import com.sourcegraph.config.ConfigUtil - -@Service(Service.Level.PROJECT) -class AccountSettingChangeListener(project: Project) : ChangeListener(project) { - init { - connection.subscribe( - AccountSettingChangeActionNotifier.TOPIC, - object : AccountSettingChangeActionNotifier { - override fun beforeAction(serverUrlChanged: Boolean) {} - - override fun afterAction(context: AccountSettingChangeContext) { - // Notify JCEF about the config changes - javaToJSBridge?.callJS("pluginSettingsChanged", ConfigUtil.getConfigAsJson()) - - UpgradeToCodyProNotification.autocompleteRateLimitError.set(null) - UpgradeToCodyProNotification.chatRateLimitError.set(null) - CodyStatusService.resetApplication(project) - - if (ConfigUtil.isCodyEnabled()) { - CodyToolWindowContent.executeOnInstanceIfNotDisposed(project) { - refreshPanelsVisibility() - } - } - - if (context.serverUrlChanged) { - TelemetryV2.sendTelemetryEvent( - project, feature = "settings.serverURL", action = "changed") - } else if (context.accessTokenChanged) { - TelemetryV2.sendTelemetryEvent( - project, feature = "settings.accessToken", action = "changed") - } - } - }) - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/config/ui/AccountConfigurable.kt b/src/main/kotlin/com/sourcegraph/cody/config/ui/AccountConfigurable.kt deleted file mode 100644 index 87385534c9..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/config/ui/AccountConfigurable.kt +++ /dev/null @@ -1,145 +0,0 @@ -package com.sourcegraph.cody.config.ui - -import com.intellij.collaboration.util.ProgressIndicatorsProvider -import com.intellij.ide.DataManager -import com.intellij.openapi.components.service -import com.intellij.openapi.options.BoundConfigurable -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.DialogPanel -import com.intellij.openapi.updateSettings.impl.UpdateSettings -import com.intellij.openapi.util.Disposer -import com.intellij.ui.SimpleListCellRenderer -import com.intellij.ui.dsl.builder.Align -import com.intellij.ui.dsl.builder.bindItem -import com.intellij.ui.dsl.builder.bindSelected -import com.intellij.ui.dsl.builder.panel -import com.intellij.util.ui.EmptyIcon -import com.sourcegraph.cody.auth.Account -import com.sourcegraph.cody.auth.ui.customAccountsPanel -import com.sourcegraph.cody.config.CodyAccountDetailsProvider -import com.sourcegraph.cody.config.CodyAccountListModel -import com.sourcegraph.cody.config.CodyAccountManager -import com.sourcegraph.cody.config.CodyAccountsHost -import com.sourcegraph.cody.config.CodyApplicationSettings -import com.sourcegraph.cody.config.CodyAuthenticationManager -import com.sourcegraph.cody.config.SettingsModel -import com.sourcegraph.cody.config.getFirstAccountOrNull -import com.sourcegraph.config.ConfigUtil -import java.awt.Dimension - -class AccountConfigurable(val project: Project) : - BoundConfigurable(ConfigUtil.SOURCEGRAPH_DISPLAY_NAME) { - private val accountManager = service() - private val accountsModel = CodyAccountListModel(project) - private lateinit var dialogPanel: DialogPanel - private var channel: UpdateChannel = findConfiguredChannel() - private val codyApplicationSettings = service() - private val settingsModel = - SettingsModel(shouldCheckForUpdates = codyApplicationSettings.shouldCheckForUpdates) - private val authManager = CodyAuthenticationManager.getInstance() - - private val initialActiveAccount: Account? - private val initialToken: String? - - init { - initialActiveAccount = authManager.account - initialToken = - initialActiveAccount?.let { authManager.getTokenForAccount(initialActiveAccount) } - } - - override fun createPanel(): DialogPanel { - dialogPanel = panel { - group("Authentication") { - row { - customAccountsPanel( - accountManager, - authManager, - accountsModel, - CodyAccountDetailsProvider( - ProgressIndicatorsProvider().also { Disposer.register(disposable!!, it) }, - accountManager, - accountsModel), - disposable!!, - true, - EmptyIcon.ICON_16) { - it.copy(server = it.server.copy()) - } - .align(Align.FILL) - .applyToComponent { this.preferredSize = Dimension(Int.MAX_VALUE, 200) } - .also { - DataManager.registerDataProvider(it.component) { key -> - if (CodyAccountsHost.DATA_KEY.`is`(key)) accountsModel else null - } - } - } - } - - group("Plugin") { - row { - label("Update channel:") - comboBox( - UpdateChannel.values().toList(), - SimpleListCellRenderer.create("") { it.presentableText }) - .bindItem({ channel }, { channel = it!! }) - } - row { - checkBox("Automatically check for plugin updates") - .bindSelected(settingsModel::shouldCheckForUpdates) - } - } - } - return dialogPanel - } - - override fun reset() { - dialogPanel.reset() - codyApplicationSettings.shouldCheckForUpdates = settingsModel.shouldCheckForUpdates - } - - override fun apply() { - super.apply() - - var activeAccount = accountsModel.activeAccount - val activeAccountRemoved = !accountsModel.accounts.contains(activeAccount) - if (activeAccountRemoved || activeAccount == null) { - activeAccount = accountsModel.accounts.getFirstAccountOrNull() - } - - CodyAuthenticationManager.getInstance().setActiveAccount(activeAccount) - accountsModel.activeAccount = activeAccount - - codyApplicationSettings.shouldCheckForUpdates = settingsModel.shouldCheckForUpdates - if (codyApplicationSettings.shouldCheckForUpdates) { - CheckUpdatesTask(project).queue() - } - - applyChannelConfiguration() - } - - private fun applyChannelConfiguration() { - val configuredChannel = findConfiguredChannel() - val newChannel = channel - - if (!configuredChannel.equals(newChannel)) { - if (!UpdateChannel.Stable.equals(configuredChannel)) { - UpdateSettings.getInstance().storedPluginHosts.remove(configuredChannel.channelUrl) - } - - if (!UpdateChannel.Stable.equals(newChannel)) { - UpdateSettings.getInstance().storedPluginHosts.add(newChannel.channelUrl) - } - } - } - - private fun findConfiguredChannel(): UpdateChannel { - var currentChannel = UpdateChannel.Stable - for (channel in UpdateChannel.values()) { - val url = channel.channelUrl - if (url != null && UpdateSettings.getInstance().storedPluginHosts.contains(url)) { - currentChannel = channel - break - } - } - return currentChannel - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/edit/actions/BaseEditCodeAction.kt b/src/main/kotlin/com/sourcegraph/cody/edit/actions/BaseEditCodeAction.kt index 76a81b2e16..99570616a1 100644 --- a/src/main/kotlin/com/sourcegraph/cody/edit/actions/BaseEditCodeAction.kt +++ b/src/main/kotlin/com/sourcegraph/cody/edit/actions/BaseEditCodeAction.kt @@ -1,13 +1,16 @@ package com.sourcegraph.cody.edit.actions +import com.intellij.codeInsight.hint.HintManager import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.runInEdt import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.actionSystem.EditorAction import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.Project +import com.sourcegraph.cody.CodyToolWindowContent import com.sourcegraph.cody.agent.CodyAgentService +import com.sourcegraph.cody.auth.CodyAccount import com.sourcegraph.cody.autocomplete.action.CodyAction -import com.sourcegraph.cody.config.CodyAuthenticationManager import com.sourcegraph.cody.ignore.IgnoreOracle import com.sourcegraph.cody.ignore.IgnorePolicy import com.sourcegraph.common.CodyBundle @@ -30,16 +33,28 @@ open class BaseEditCodeAction(runAction: (Editor) -> Unit) : super.update(event) val project = event.project ?: return - event.presentation.description = + val (isEnabled, description) = if (!isCodyWorking(project)) { - CodyBundle.getString("action.cody.not-working") + false to CodyBundle.getString("action.cody.not-working") } else if (isBlockedByPolicy(project, event)) { - CodyBundle.getString("filter.action-in-ignored-file.detail") - } else if (CodyAuthenticationManager.getInstance().hasNoActiveAccount()) { - CodyBundle.getString("action.sourcegraph.disabled.description") + false to CodyBundle.getString("filter.action-in-ignored-file.detail") + } else if (!CodyAccount.hasActiveAccount()) { + false to CodyBundle.getString("action.sourcegraph.disabled.description") } else { - "" + true to "" } - event.presentation.isEnabled = event.presentation.description.isBlank() + + if (!isEnabled) { + runInEdt { + val editor = event.getData(com.intellij.openapi.actionSystem.CommonDataKeys.EDITOR) + if (editor != null) { + HintManager.getInstance().showErrorHint(editor, description) + } + CodyToolWindowContent.show(project) + } + } + + event.presentation.description = description + event.presentation.isEnabled = isEnabled } } diff --git a/src/main/kotlin/com/sourcegraph/cody/edit/actions/EditCodeAction.kt b/src/main/kotlin/com/sourcegraph/cody/edit/actions/EditCodeAction.kt index 8e786baeff..027de8d111 100644 --- a/src/main/kotlin/com/sourcegraph/cody/edit/actions/EditCodeAction.kt +++ b/src/main/kotlin/com/sourcegraph/cody/edit/actions/EditCodeAction.kt @@ -19,7 +19,8 @@ class EditCodeAction : override fun update(event: AnActionEvent) { super.update(event) event.presentation.isEnabledAndVisible = - event.project?.let { !EditCommandPrompt.isVisible(it) } ?: true + event.presentation.isEnabledAndVisible && + (event.project?.let { !EditCommandPrompt.isVisible(it) } ?: true) } companion object { diff --git a/src/main/kotlin/com/sourcegraph/cody/edit/lenses/actions/LensEditAction.kt b/src/main/kotlin/com/sourcegraph/cody/edit/lenses/actions/LensEditAction.kt index 307062dc37..215f5f24bc 100644 --- a/src/main/kotlin/com/sourcegraph/cody/edit/lenses/actions/LensEditAction.kt +++ b/src/main/kotlin/com/sourcegraph/cody/edit/lenses/actions/LensEditAction.kt @@ -9,7 +9,7 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.Project -import com.sourcegraph.cody.config.CodyAuthenticationManager +import com.sourcegraph.cody.auth.CodyAccount import com.sourcegraph.cody.edit.lenses.LensesService import com.sourcegraph.common.CodyBundle @@ -22,8 +22,7 @@ abstract class LensEditAction(val editAction: (Project, AnActionEvent, Editor, S } override fun update(event: AnActionEvent) { - val hasActiveAccount = CodyAuthenticationManager.getInstance().hasActiveAccount() - event.presentation.isEnabled = hasActiveAccount + event.presentation.isEnabled = CodyAccount.hasActiveAccount() if (!event.presentation.isEnabled) { event.presentation.description = CodyBundle.getString("action.sourcegraph.disabled.description") diff --git a/src/main/kotlin/com/sourcegraph/cody/history/HistoryService.kt b/src/main/kotlin/com/sourcegraph/cody/history/HistoryService.kt index 48fb6d06db..77cb0e088a 100644 --- a/src/main/kotlin/com/sourcegraph/cody/history/HistoryService.kt +++ b/src/main/kotlin/com/sourcegraph/cody/history/HistoryService.kt @@ -6,7 +6,7 @@ import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage import com.intellij.openapi.components.service import com.intellij.openapi.project.Project -import com.sourcegraph.cody.config.CodyAuthenticationManager +import com.sourcegraph.cody.auth.deprecated.DeprecatedCodyAccountManager import com.sourcegraph.cody.history.state.AccountData import com.sourcegraph.cody.history.state.ChatState import com.sourcegraph.cody.history.state.HistoryState @@ -19,7 +19,7 @@ class HistoryService(private val project: Project) : @Synchronized fun getDefaultLlm(): LLMState? { - val account = CodyAuthenticationManager.getInstance().account + val account = DeprecatedCodyAccountManager.getInstance().account val llm = account?.let { findEntry(it.id) }?.defaultLlm if (llm == null) return null return LLMState().also { it.copyFrom(llm) } @@ -49,11 +49,11 @@ class HistoryService(private val project: Project) : @Synchronized fun getActiveAccountHistory(): AccountData? = - CodyAuthenticationManager.getInstance().account?.let { findEntry(it.id) } + DeprecatedCodyAccountManager.getInstance().account?.let { findEntry(it.id) } private fun getOrCreateActiveAccountEntry(): AccountData { val activeAccount = - CodyAuthenticationManager.getInstance().account + DeprecatedCodyAccountManager.getInstance().account ?: throw IllegalStateException("No active account") val existingEntry = findEntry(activeAccount.id) diff --git a/src/main/kotlin/com/sourcegraph/cody/ignore/ActionInIgnoredFileNotification.kt b/src/main/kotlin/com/sourcegraph/cody/ignore/ActionInIgnoredFileNotification.kt index 6e1d8afc21..23100a09cb 100644 --- a/src/main/kotlin/com/sourcegraph/cody/ignore/ActionInIgnoredFileNotification.kt +++ b/src/main/kotlin/com/sourcegraph/cody/ignore/ActionInIgnoredFileNotification.kt @@ -10,7 +10,7 @@ import com.intellij.openapi.application.runInEdt import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.sourcegraph.Icons -import com.sourcegraph.cody.config.CodyAuthenticationManager +import com.sourcegraph.cody.auth.CodyAccount import com.sourcegraph.cody.statusbar.CodyStatus import com.sourcegraph.cody.statusbar.CodyStatusService import com.sourcegraph.common.CodyBundle @@ -31,7 +31,7 @@ class ActionInIgnoredFileNotification : fun maybeNotify(project: Project) { val status = CodyStatusService.getCurrentStatus(project) - val account = CodyAuthenticationManager.getInstance().account + val account = CodyAccount.getActiveAccount() when { status == CodyStatus.CodyUninit || status == CodyStatus.CodyDisabled || diff --git a/src/main/kotlin/com/sourcegraph/cody/initialization/EndOfTrialNotificationScheduler.kt b/src/main/kotlin/com/sourcegraph/cody/initialization/EndOfTrialNotificationScheduler.kt index cb0f061edc..3a3316566c 100644 --- a/src/main/kotlin/com/sourcegraph/cody/initialization/EndOfTrialNotificationScheduler.kt +++ b/src/main/kotlin/com/sourcegraph/cody/initialization/EndOfTrialNotificationScheduler.kt @@ -9,7 +9,7 @@ import com.sourcegraph.cody.agent.protocol.CurrentUserCodySubscription import com.sourcegraph.cody.agent.protocol.GetFeatureFlag import com.sourcegraph.cody.agent.protocol.Plan import com.sourcegraph.cody.agent.protocol.Status -import com.sourcegraph.cody.config.CodyAuthenticationManager +import com.sourcegraph.cody.auth.CodyAccount import com.sourcegraph.config.ConfigUtil import java.util.concurrent.Executors import java.util.concurrent.TimeUnit @@ -31,7 +31,7 @@ class EndOfTrialNotificationScheduler private constructor(val project: Project) this.dispose() } - if (CodyAuthenticationManager.getInstance().account?.isDotcomAccount() != true) { + if (CodyAccount.getActiveAccount()?.isDotcomAccount() != true) { return@scheduleAtFixedRate } diff --git a/src/main/kotlin/com/sourcegraph/cody/initialization/PostStartupActivity.kt b/src/main/kotlin/com/sourcegraph/cody/initialization/PostStartupActivity.kt index 114d4cf20f..95f29ffbbc 100644 --- a/src/main/kotlin/com/sourcegraph/cody/initialization/PostStartupActivity.kt +++ b/src/main/kotlin/com/sourcegraph/cody/initialization/PostStartupActivity.kt @@ -8,10 +8,8 @@ import com.intellij.openapi.editor.ex.EditorEventMulticasterEx import com.intellij.openapi.project.Project import com.intellij.openapi.startup.ProjectActivity import com.sourcegraph.cody.agent.CodyAgentService -import com.sourcegraph.cody.config.CodyAuthenticationManager import com.sourcegraph.cody.config.CodySettingsFileChangeListener import com.sourcegraph.cody.config.migration.SettingsMigration -import com.sourcegraph.cody.config.notification.AccountSettingChangeListener import com.sourcegraph.cody.config.notification.CodySettingChangeListener import com.sourcegraph.cody.config.ui.CheckUpdatesTask import com.sourcegraph.cody.listeners.CodyCaretListener @@ -31,7 +29,6 @@ class PostStartupActivity : ProjectActivity { override suspend fun execute(project: Project) { SettingsMigration().runActivity(project) CodyAuthNotificationActivity().runActivity(project) - CodyAuthenticationManager.getInstance().addAuthChangeListener(project) ApplicationManager.getApplication().executeOnPooledThread { // Scheduling because this task takes ~2s to run CheckUpdatesTask(project).queue() @@ -58,7 +55,6 @@ class PostStartupActivity : ProjectActivity { // DO NOT remove those lines. // Project level listeners need to be used at least once to get initialized. - project.service() project.service() TelemetryV2.sendTelemetryEvent(project, "extension", "started") diff --git a/src/main/kotlin/com/sourcegraph/cody/initialization/UninstallListener.kt b/src/main/kotlin/com/sourcegraph/cody/initialization/UninstallListener.kt index a4f8ccfd6d..7eda5e6a9c 100644 --- a/src/main/kotlin/com/sourcegraph/cody/initialization/UninstallListener.kt +++ b/src/main/kotlin/com/sourcegraph/cody/initialization/UninstallListener.kt @@ -10,7 +10,7 @@ import com.sourcegraph.cody.agent.protocol.BillingCategory import com.sourcegraph.cody.agent.protocol.BillingMetadata import com.sourcegraph.cody.agent.protocol.BillingProduct import com.sourcegraph.cody.agent.protocol.TelemetryEventParameters -import com.sourcegraph.cody.config.CodyAuthenticationManager +import com.sourcegraph.cody.auth.CodyAccount import com.sourcegraph.cody.telemetry.TelemetryV2 import java.util.concurrent.TimeUnit @@ -19,9 +19,7 @@ class UninstallListener : StartupActivity { PluginInstaller.addStateListener( object : PluginStateListener { override fun uninstall(descriptor: IdeaPluginDescriptor) { - val authManager = CodyAuthenticationManager.getInstance() - authManager.setActiveAccount(null) - authManager.removeAll() + CodyAccount.setActiveAccount(null) TelemetryV2.sendTelemetryEvent( project, "cody.extension", diff --git a/src/main/kotlin/com/sourcegraph/cody/listeners/CodyDocumentListener.kt b/src/main/kotlin/com/sourcegraph/cody/listeners/CodyDocumentListener.kt index 51b65414cf..69f18f86d6 100644 --- a/src/main/kotlin/com/sourcegraph/cody/listeners/CodyDocumentListener.kt +++ b/src/main/kotlin/com/sourcegraph/cody/listeners/CodyDocumentListener.kt @@ -12,17 +12,17 @@ import com.sourcegraph.cody.agent.protocol.CompletionItemParams import com.sourcegraph.cody.agent.protocol.ProtocolTextDocument import com.sourcegraph.cody.autocomplete.CodyAutocompleteManager import com.sourcegraph.cody.autocomplete.action.AcceptCodyAutocompleteAction -import com.sourcegraph.cody.chat.CodeEditorFactory import com.sourcegraph.cody.telemetry.TelemetryV2 import com.sourcegraph.cody.vscode.InlineCompletionTriggerKind import com.sourcegraph.utils.CodyEditorUtil class CodyDocumentListener(val project: Project) : BulkAwareDocumentListener { + // TODO: Looks like this functionality is broken after the migration to webview private fun logCodeCopyPastedFromChat(event: DocumentEvent) { val pastedCode = event.newFragment.toString() - if (pastedCode.isNotBlank() && CodeEditorFactory.lastCopiedText == pastedCode) { - CodeEditorFactory.lastCopiedText = null + if (pastedCode.isNotBlank() && lastCopiedText == pastedCode) { + lastCopiedText = null TelemetryV2.sendCodeGenerationEvent( project, feature = "keyDown", @@ -72,4 +72,8 @@ class CodyDocumentListener(val project: Project) : BulkAwareDocumentListener { } } } + + companion object { + var lastCopiedText: String? = null + } } diff --git a/src/main/kotlin/com/sourcegraph/cody/listeners/CodySelectionInlayManager.kt b/src/main/kotlin/com/sourcegraph/cody/listeners/CodySelectionInlayManager.kt index c80617425f..139675c4cc 100644 --- a/src/main/kotlin/com/sourcegraph/cody/listeners/CodySelectionInlayManager.kt +++ b/src/main/kotlin/com/sourcegraph/cody/listeners/CodySelectionInlayManager.kt @@ -12,7 +12,7 @@ import com.intellij.openapi.keymap.KeymapManager import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import com.sourcegraph.cody.agent.CodyAgentService -import com.sourcegraph.cody.config.CodyAuthenticationManager +import com.sourcegraph.cody.auth.CodyAccount import com.sourcegraph.cody.ignore.IgnoreOracle import com.sourcegraph.cody.ignore.IgnorePolicy import com.sourcegraph.config.ConfigUtil @@ -37,7 +37,7 @@ class CodySelectionInlayManager(val project: Project) { !CodyAgentService.isConnected(project) || !ConfigUtil.isCodyUIHintsEnabled() || !CodyEditorUtil.isEditorValidForAutocomplete(editor) || - CodyAuthenticationManager.getInstance().hasNoActiveAccount() || + !CodyAccount.hasActiveAccount() || IgnoreOracle.getInstance(project).policyForEditor(editor) != IgnorePolicy.USE) { return } diff --git a/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyDisableAutocompleteAction.kt b/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyDisableAutocompleteAction.kt index e367bde271..2843a0be46 100644 --- a/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyDisableAutocompleteAction.kt +++ b/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyDisableAutocompleteAction.kt @@ -1,9 +1,9 @@ package com.sourcegraph.cody.statusbar import com.intellij.openapi.actionSystem.AnActionEvent +import com.sourcegraph.cody.auth.CodyAccount import com.sourcegraph.cody.autocomplete.CodyAutocompleteManager import com.sourcegraph.cody.config.CodyApplicationSettings -import com.sourcegraph.cody.config.CodyAuthenticationManager import com.sourcegraph.common.ui.DumbAwareEDTAction import com.sourcegraph.config.ConfigUtil @@ -15,8 +15,9 @@ class CodyDisableAutocompleteAction : DumbAwareEDTAction("Disable Cody Autocompl override fun update(e: AnActionEvent) { super.update(e) - val hasActiveAccount = CodyAuthenticationManager.getInstance().hasActiveAccount() e.presentation.isEnabledAndVisible = - ConfigUtil.isCodyEnabled() && ConfigUtil.isCodyAutocompleteEnabled() && hasActiveAccount + ConfigUtil.isCodyEnabled() && + ConfigUtil.isCodyAutocompleteEnabled() && + CodyAccount.hasActiveAccount() } } diff --git a/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyDisableLanguageForAutocompleteAction.kt b/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyDisableLanguageForAutocompleteAction.kt index bdc217d9db..89b5928324 100644 --- a/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyDisableLanguageForAutocompleteAction.kt +++ b/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyDisableLanguageForAutocompleteAction.kt @@ -1,9 +1,9 @@ package com.sourcegraph.cody.statusbar import com.intellij.openapi.actionSystem.AnActionEvent +import com.sourcegraph.cody.auth.CodyAccount import com.sourcegraph.cody.autocomplete.CodyAutocompleteManager import com.sourcegraph.cody.config.CodyApplicationSettings -import com.sourcegraph.cody.config.CodyAuthenticationManager import com.sourcegraph.common.ui.DumbAwareEDTAction import com.sourcegraph.config.ConfigUtil import com.sourcegraph.utils.CodyEditorUtil @@ -25,13 +25,12 @@ class CodyDisableLanguageForAutocompleteAction : DumbAwareEDTAction() { val isLanguageBlacklisted = languageForFocusedEditor?.let { CodyLanguageUtil.isLanguageBlacklisted(it) } ?: false val languageName = languageForFocusedEditor?.displayName ?: "" - val hasActiveAccount = CodyAuthenticationManager.getInstance().hasActiveAccount() e.presentation.isEnabledAndVisible = languageForFocusedEditor != null && ConfigUtil.isCodyEnabled() && ConfigUtil.isCodyAutocompleteEnabled() && !isLanguageBlacklisted && - hasActiveAccount + CodyAccount.hasActiveAccount() e.presentation.text = "Disable Cody Autocomplete for $languageName" } } diff --git a/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyManageAccountsAction.kt b/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyManageAccountsAction.kt deleted file mode 100644 index 3ce8f61ff7..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyManageAccountsAction.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.sourcegraph.cody.statusbar - -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.options.ShowSettingsUtil -import com.sourcegraph.cody.config.ui.AccountConfigurable -import com.sourcegraph.common.ui.DumbAwareEDTAction - -class CodyManageAccountsAction : DumbAwareEDTAction("Manage Accounts") { - override fun actionPerformed(e: AnActionEvent) { - ShowSettingsUtil.getInstance().showSettingsDialog(e.project, AccountConfigurable::class.java) - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyStatusBarActionGroup.kt b/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyStatusBarActionGroup.kt index acff9b611f..dde41efdeb 100644 --- a/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyStatusBarActionGroup.kt +++ b/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyStatusBarActionGroup.kt @@ -27,7 +27,6 @@ class CodyStatusBarActionGroup : DefaultActionGroup() { addAll(listOfNotNull(deriveRateLimitErrorAction())) addSeparator() addAll( - CodyManageAccountsAction(), CodyOpenSettingsAction(), ) addSeparator() diff --git a/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyStatusService.kt b/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyStatusService.kt index d5df509833..eac2dd2a55 100644 --- a/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyStatusService.kt +++ b/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyStatusService.kt @@ -8,8 +8,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectManager import com.intellij.util.ui.UIUtil import com.sourcegraph.cody.agent.CodyAgentService -import com.sourcegraph.cody.config.CodyAccountManager -import com.sourcegraph.cody.config.CodyAuthenticationManager +import com.sourcegraph.cody.auth.CodyAccount import com.sourcegraph.cody.ignore.IgnoreOracle import com.sourcegraph.common.UpgradeToCodyProNotification import com.sourcegraph.config.ConfigUtil @@ -45,11 +44,7 @@ class CodyStatusService(val project: Project) : Disposable { private fun didStatusChange(project: Project): Boolean { synchronized(this) { val oldStatus = status - val service = ApplicationManager.getApplication().getService(CodyAccountManager::class.java) - val authManager = CodyAuthenticationManager.getInstance() - val isTokenInvalid = authManager.getIsTokenInvalid().getNow(null) == true - val token = CodyAuthenticationManager.getInstance().account?.let(service::findCredentials) // Note, the order of these clauses is important because earlier clauses take precedence over // later ones. // Fundamental issues are tested first. @@ -60,13 +55,11 @@ class CodyStatusService(val project: Project) : Disposable { CodyStatus.AgentError } else if (!CodyAgentService.isConnected(project)) { CodyStatus.CodyAgentNotRunning - } else if (token == null) { + } else if (!CodyAccount.hasActiveAccount()) { CodyStatus.CodyNotSignedIn } else if (UpgradeToCodyProNotification.autocompleteRateLimitError.get() != null || UpgradeToCodyProNotification.chatRateLimitError.get() != null) { CodyStatus.RateLimitError - } else if (isTokenInvalid) { - CodyStatus.CodyInvalidToken } else if (IgnoreOracle.getInstance(project).isEditingIgnoredFile) { CodyStatus.InIgnoredFile } else if (!ConfigUtil.isCodyAutocompleteEnabled()) { diff --git a/src/main/kotlin/com/sourcegraph/cody/ui/HtmlViewer.kt b/src/main/kotlin/com/sourcegraph/cody/ui/HtmlViewer.kt deleted file mode 100644 index fffcb49034..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/ui/HtmlViewer.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.sourcegraph.cody.ui - -import com.intellij.openapi.project.Project -import com.intellij.ui.BrowserHyperlinkListener -import com.intellij.util.ui.HTMLEditorKitBuilder -import com.intellij.util.ui.JBInsets -import com.intellij.util.ui.SwingHelper -import com.sourcegraph.cody.chat.ChatUIConstants -import com.sourcegraph.cody.telemetry.TelemetryV2 -import com.sourcegraph.common.CodyBundle -import java.awt.Insets -import java.net.URL -import javax.swing.JEditorPane -import javax.swing.event.HyperlinkEvent - -object HtmlViewer { - @JvmStatic - fun createHtmlViewer(project: Project): JEditorPane { - val jEditorPane = SwingHelper.createHtmlViewer(true, null, null, null) - jEditorPane.editorKit = HTMLEditorKitBuilder().withWordWrapViewFactory().build() - jEditorPane.isFocusable = true - jEditorPane.margin = - JBInsets.create( - Insets( - ChatUIConstants.TEXT_MARGIN, - ChatUIConstants.TEXT_MARGIN, - ChatUIConstants.TEXT_MARGIN, - ChatUIConstants.TEXT_MARGIN)) - jEditorPane.addHyperlinkListener(MyBrowserHyperlinkListener(project)) - return jEditorPane - } - - class MyBrowserHyperlinkListener(val project: Project) : BrowserHyperlinkListener() { - override fun hyperlinkActivated(e: HyperlinkEvent) { - if (e.url.sameFile(URL(CodyBundle.getString("url.sourcegraph.subscription")))) { - TelemetryV2.sendTelemetryEvent(project, "upsellUsageLimitCTA", "clicked") - } - super.hyperlinkActivated(e) - } - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/ui/UnderlinedActionLink.kt b/src/main/kotlin/com/sourcegraph/cody/ui/UnderlinedActionLink.kt deleted file mode 100644 index 9e8b83010c..0000000000 --- a/src/main/kotlin/com/sourcegraph/cody/ui/UnderlinedActionLink.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.sourcegraph.cody.ui - -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.ui.components.AnActionLink -import java.awt.Graphics -import java.awt.Rectangle -import org.jetbrains.annotations.Nls - -class UnderlinedActionLink(@Nls text: String, anAction: AnAction) : AnActionLink(text, anAction) { - init { - foreground = Colors.SECONDARY_LINK_COLOR - } - - override fun paint(g: Graphics) { - super.paint(g) - val bounds: Rectangle = g.clipBounds - g.color = Colors.SECONDARY_LINK_COLOR - g.drawLine(0, bounds.height, getFontMetrics(font).stringWidth(text), bounds.height) - } -} diff --git a/src/main/kotlin/com/sourcegraph/cody/ui/web/WebUIHost.kt b/src/main/kotlin/com/sourcegraph/cody/ui/web/WebUIHost.kt index 1854906557..3dcc8779eb 100644 --- a/src/main/kotlin/com/sourcegraph/cody/ui/web/WebUIHost.kt +++ b/src/main/kotlin/com/sourcegraph/cody/ui/web/WebUIHost.kt @@ -15,8 +15,6 @@ import com.sourcegraph.cody.agent.WebviewDidDisposeParams import com.sourcegraph.cody.agent.WebviewReceiveMessageStringEncodedParams import com.sourcegraph.cody.agent.protocol.WebviewOptions import com.sourcegraph.cody.agent.protocol_generated.ExecuteCommandParams -import com.sourcegraph.cody.config.CodyAuthenticationManager -import com.sourcegraph.cody.config.ui.AccountConfigurable import com.sourcegraph.cody.config.ui.CodyConfigurable import com.sourcegraph.utils.CodyEditorUtil import java.net.URLDecoder @@ -71,14 +69,7 @@ internal class WebUIHostImpl( val id = decodedJson?.get("id")?.asString val arg = decodedJson?.get("arg")?.asString - if ((command == "auth" && decodedJson.get("authKind")?.asString == "signout") || - (isCommand && id == "cody.auth.signout")) { - CodyAuthenticationManager.getInstance().setActiveAccount(null) - } else if (isCommand && id == "cody.auth.switchAccount") { - runInEdt { - ShowSettingsUtil.getInstance().showSettingsDialog(project, AccountConfigurable::class.java) - } - } else if (isCommand && id == "cody.status-bar.interacted") { + if (isCommand && id == "cody.status-bar.interacted") { runInEdt { ShowSettingsUtil.getInstance().showSettingsDialog(project, CodyConfigurable::class.java) } diff --git a/src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt b/src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt index 93abe6a557..a343e0ea53 100644 --- a/src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt +++ b/src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt @@ -12,11 +12,10 @@ import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectManager import com.sourcegraph.cody.agent.protocol_generated.ExtensionConfiguration +import com.sourcegraph.cody.auth.CodyAccount +import com.sourcegraph.cody.auth.SourcegraphServerPath +import com.sourcegraph.cody.auth.SourcegraphServerPath.Companion.from import com.sourcegraph.cody.config.CodyApplicationSettings -import com.sourcegraph.cody.config.CodyAuthenticationManager -import com.sourcegraph.cody.config.ServerAuthLoader -import com.sourcegraph.cody.config.SourcegraphServerPath -import com.sourcegraph.cody.config.SourcegraphServerPath.Companion.from import java.nio.file.Path import java.nio.file.Paths import kotlin.io.path.readText @@ -25,10 +24,8 @@ import org.jetbrains.annotations.VisibleForTesting object ConfigUtil { const val DOTCOM_URL = "https://sourcegraph.com/" - const val SERVICE_DISPLAY_NAME = "Sourcegraph" const val CODY_DISPLAY_NAME = "Cody" const val CODE_SEARCH_DISPLAY_NAME = "Code Search" - const val SOURCEGRAPH_DISPLAY_NAME = "Sourcegraph" private const val FEATURE_FLAGS_ENV_VAR = "CODY_JETBRAINS_FEATURES" private val logger = Logger.getInstance(ConfigUtil::class.java) @@ -68,13 +65,13 @@ object ConfigUtil { project: Project, customConfigContent: String? = null ): ExtensionConfiguration { - val serverAuth = ServerAuthLoader.loadServerAuth() + val account = CodyAccount.getActiveAccount() return ExtensionConfiguration( anonymousUserID = CodyApplicationSettings.instance.anonymousUserId, - serverEndpoint = serverAuth.instanceUrl, - accessToken = serverAuth.accessToken, - customHeaders = getCustomRequestHeadersAsMap(serverAuth.customRequestHeaders), + serverEndpoint = account?.server?.url ?: "", + accessToken = account?.getToken() ?: "", + customHeaders = emptyMap(), proxy = UserLevelConfig.getProxy(), autocompleteAdvancedProvider = UserLevelConfig.getAutocompleteProviderType()?.vscodeSettingString(), @@ -86,11 +83,11 @@ object ConfigUtil { @JvmStatic fun getConfigAsJson(): JsonObject { - val (instanceUrl, accessToken, customRequestHeaders) = ServerAuthLoader.loadServerAuth() + val account = CodyAccount.getActiveAccount() return JsonObject().apply { - addProperty("instanceURL", instanceUrl) - addProperty("accessToken", accessToken) - addProperty("customRequestHeadersAsString", customRequestHeaders) + addProperty("instanceURL", account?.server?.url) + addProperty("accessToken", account?.getToken()) + addProperty("customRequestHeadersAsString", "") addProperty("pluginVersion", getPluginVersion()) addProperty("anonymousUserId", CodyApplicationSettings.instance.anonymousUserId) } @@ -98,23 +95,10 @@ object ConfigUtil { @JvmStatic fun getServerPath(): SourcegraphServerPath { - val activeAccount = CodyAuthenticationManager.getInstance().account + val activeAccount = CodyAccount.getActiveAccount() return activeAccount?.server ?: from(DOTCOM_URL, "") } - @JvmStatic - fun getCustomRequestHeadersAsMap(customRequestHeaders: String): Map { - val result: MutableMap = HashMap() - val pairs = - customRequestHeaders.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - var i = 0 - while (i + 1 < pairs.size) { - result[pairs[i]] = pairs[i + 1] - i += 2 - } - return result - } - @JvmStatic fun shouldConnectToDebugAgent() = System.getenv("CODY_AGENT_DEBUG_REMOTE") == "true" @JvmStatic diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 535fba71bd..bed98b9c06 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -25,16 +25,7 @@ serviceImplementation="com.sourcegraph.cody.config.CodyApplicationSettings"/> - - + serviceImplementation="com.sourcegraph.cody.auth.deprecated.DeprecatedCodyPersistentAccounts"/> @@ -194,9 +185,9 @@ + class="com.sourcegraph.cody.chat.actions.ExportChatsAction" + text="Export All Chats As JSON" + description="Export all chats as JSON"> @@ -318,23 +309,6 @@ - - - - - - - - - @@ -404,13 +378,6 @@ description="Restarts Cody"> - - -