Skip to content

Commit

Permalink
Cody Ignore: Manual autocomplete trigger notification, sidebar comman…
Browse files Browse the repository at this point in the history
…d button sidebar UX and sundry (#1432)

- Intercepts the manual autocomplete trigger and displays a notification
- Disables the buttons in the sidebar command tab when in an ignored
file
- Displays a banner in the sidebar command tab when in an ignored file
- No longer displays banners on individual editors
- Notification text, tooltip text has been updated to match latest UX
proposal

https://www.loom.com/share/3a417616ef4f4701ac0e02bcc0c61e64

Part of #1252, #1256

Closes #1253

## Test plan

Manually tested:

- Run plugin with environment
`CODY_JETBRAINS_FEATURES=cody.feature.internals-menu=true`
- Use status bar, Internals, Testing: Cody Ignore and change the policy
to match your repo, turn the policy override on.
- Check that autocomplete does not happen
- Check that the manual autocomplete trigger displays a notification
- Check that right click menu actions for Explain, Smell and "Test V1"
display a notification; same for their keyboard shortcuts
- Check that the buttons in the sidebar command tab are disabled and a
banner is displayed
- Check that the status bar Cody icon displays a slash and tooltip when
in an ignored file
- Check that all the above and UX affordances respond when the policy
changes (override off, different repo name, etc.)
  • Loading branch information
dominiccooney authored May 2, 2024
1 parent 99e5835 commit 4dc5732
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.sourcegraph.cody.autocomplete

import com.intellij.codeInsight.hint.HintManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.components.Service
Expand Down Expand Up @@ -34,6 +35,9 @@ 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.ignore.ActionInIgnoredFileNotification
import com.sourcegraph.cody.ignore.IgnoreOracle
import com.sourcegraph.cody.ignore.IgnorePolicy
import com.sourcegraph.cody.statusbar.CodyStatus
import com.sourcegraph.cody.statusbar.CodyStatusService.Companion.notifyApplication
import com.sourcegraph.cody.statusbar.CodyStatusService.Companion.resetApplication
Expand Down Expand Up @@ -234,42 +238,52 @@ class CodyAutocompleteManager {

val resultOuter = CompletableFuture<Void?>()
CodyAgentService.withAgent(project) { agent ->
val completions = agent.server.autocompleteExecute(params)

// Important: we have to `.cancel()` the original `CompletableFuture<T>` from lsp4j. As soon
// as we use `thenAccept()` we get a new instance of `CompletableFuture<Void>` which does not
// correctly propagate the cancellation to the agent.
cancellationToken.onCancellationRequested { completions.cancel(true) }

ApplicationManager.getApplication().executeOnPooledThread {
completions
.handle { result, error ->
if (error != null) {
if (triggerKind == InlineCompletionTriggerKind.INVOKE ||
!UpgradeToCodyProNotification.isFirstRLEOnAutomaticAutocompletionsShown) {
handleError(project, error)
if (triggerKind == InlineCompletionTriggerKind.INVOKE &&
IgnoreOracle.getInstance(project).policyForUri(virtualFile.url, agent).get() !=
IgnorePolicy.USE) {
runInEdt { ActionInIgnoredFileNotification().notify(project) }
resetApplication(project)
resultOuter.cancel(true)
null
} else {
val completions = agent.server.autocompleteExecute(params)

// Important: we have to `.cancel()` the original `CompletableFuture<T>` from lsp4j. As soon
// as we use `thenAccept()` we get a new instance of `CompletableFuture<Void>` which does
// not
// correctly propagate the cancellation to the agent.
cancellationToken.onCancellationRequested { completions.cancel(true) }

ApplicationManager.getApplication().executeOnPooledThread {
completions
.handle { result, error ->
if (error != null) {
if (triggerKind == InlineCompletionTriggerKind.INVOKE ||
!UpgradeToCodyProNotification.isFirstRLEOnAutomaticAutocompletionsShown) {
handleError(project, error)
}
} else if (result != null && result.items.isNotEmpty()) {
UpgradeToCodyProNotification.isFirstRLEOnAutomaticAutocompletionsShown = false
UpgradeToCodyProNotification.autocompleteRateLimitError.set(null)
CodyToolWindowContent.executeOnInstanceIfNotDisposed(project) {
refreshMyAccountTab()
}
processAutocompleteResult(editor, offset, triggerKind, result, cancellationToken)
}
} else if (result != null && result.items.isNotEmpty()) {
UpgradeToCodyProNotification.isFirstRLEOnAutomaticAutocompletionsShown = false
UpgradeToCodyProNotification.autocompleteRateLimitError.set(null)
CodyToolWindowContent.executeOnInstanceIfNotDisposed(project) {
refreshMyAccountTab()
null
}
.exceptionally { error: Throwable? ->
if (!(error is CancellationException || error is CompletionException)) {
logger.warn("failed autocomplete request $params", error)
}
processAutocompleteResult(editor, offset, triggerKind, result, cancellationToken)
null
}
null
}
.exceptionally { error: Throwable? ->
if (!(error is CancellationException || error is CompletionException)) {
logger.warn("failed autocomplete request $params", error)
.completeOnTimeout(null, 3, TimeUnit.SECONDS)
.thenRun { // This is a terminal operation, so we needn't call get().
resetApplication(project)
resultOuter.complete(null)
}
null
}
.completeOnTimeout(null, 3, TimeUnit.SECONDS)
.thenRun { // This is a terminal operation, so we needn't call get().
resetApplication(project)
resultOuter.complete(null)
}
}
}
}
cancellationToken.onCancellationRequested { resultOuter.cancel(true) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import com.sourcegraph.cody.CodyToolWindowContent
import com.sourcegraph.cody.agent.protocol.ProtocolTextDocument
import com.sourcegraph.cody.chat.AgentChatSession
import com.sourcegraph.cody.commands.CommandId
import com.sourcegraph.cody.context.ui.ActionInIgnoredFileNotification
import com.sourcegraph.cody.ignore.ActionInIgnoredFileNotification
import com.sourcegraph.cody.ignore.IgnoreOracle
import com.sourcegraph.cody.ignore.IgnorePolicy
import java.util.concurrent.Callable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,37 @@ import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.Presentation
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.ui.components.JBPanelWithEmptyText
import com.sourcegraph.cody.commands.CommandId
import com.sourcegraph.cody.config.CodyApplicationSettings
import com.sourcegraph.cody.ignore.CommandPanelIgnoreBanner
import com.sourcegraph.cody.ignore.IgnoreOracle
import com.sourcegraph.cody.ignore.IgnorePolicy
import com.sourcegraph.config.ConfigUtil
import java.awt.Component
import java.awt.Dimension
import java.awt.GridLayout
import javax.swing.BoxLayout
import javax.swing.JButton
import javax.swing.JComponent
import javax.swing.plaf.ButtonUI

class CommandsTabPanel(
private val project: Project,
private val executeCommand: (CommandId) -> Unit
) : JBPanelWithEmptyText(GridLayout(/* rows = */ 0, /* cols = */ 1)) {
) :
JBPanelWithEmptyText(GridLayout(/* rows = */ 0, /* cols = */ 1)),
IgnoreOracle.FocusedFileIgnorePolicyListener {
private val ignoreBanner = CommandPanelIgnoreBanner()
private val buttons = mutableListOf<JComponent>()

init {
layout = BoxLayout(this, BoxLayout.Y_AXIS)

CommandId.values().forEach { command -> addCommandButton(command) }

if (ConfigUtil.isFeatureFlagEnabled("cody.feature.inline-edits") ||
Expand All @@ -35,6 +45,16 @@ class CommandsTabPanel(
addInlineEditActionButton("cody.documentCodeAction")
addInlineEditActionButton("cody.testCodeAction")
}

addHierarchyListener {
if (!project.isDisposed) {
if (it.component.isShowing) {
IgnoreOracle.getInstance(project).addListener(this)
} else {
IgnoreOracle.getInstance(project).removeListener(this)
}
}
}
}

private fun addInlineEditActionButton(actionId: String) {
Expand Down Expand Up @@ -63,5 +83,28 @@ class CommandsTabPanel(
button.setUI(buttonUI)
button.addActionListener { action() }
add(button)

buttons.add(button)
}

private fun update(policy: IgnorePolicy) {
// Dis/enable all the buttons.
for (button in buttons) {
button.isEnabled = policy == IgnorePolicy.USE
}
when (policy) {
IgnorePolicy.USE -> {
remove(ignoreBanner)
}
IgnorePolicy.IGNORE -> {
add(ignoreBanner, 0)
}
}
revalidate()
repaint()
}

override fun focusedFileIgnorePolicyChanged(policy: IgnorePolicy) {
runInEdt { update(policy) }
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.sourcegraph.cody.context.ui
package com.sourcegraph.cody.ignore

import com.intellij.ide.BrowserUtil
import com.intellij.notification.Notification
Expand All @@ -7,14 +7,15 @@ import com.intellij.notification.NotificationType
import com.intellij.notification.impl.NotificationFullContent
import com.intellij.openapi.actionSystem.AnActionEvent
import com.sourcegraph.Icons
import com.sourcegraph.cody.ignore.CODY_IGNORE_DOCS_URL
import com.sourcegraph.common.CodyBundle
import com.sourcegraph.common.NotificationGroups

const val CODY_IGNORE_DOCS_URL = "https://sourcegraph.com/docs/cody/capabilities/ignore-context"

class ActionInIgnoredFileNotification :
Notification(
NotificationGroups.SOURCEGRAPH_ERRORS,
"",
CodyBundle.getString("ignore.action-in-ignored-file.title"),
CodyBundle.getString("ignore.action-in-ignored-file.detail"),
NotificationType.INFORMATION),
NotificationFullContent {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.sourcegraph.cody.ignore

import com.intellij.ide.BrowserUtil
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.ui.EditorNotificationPanel
import com.intellij.ui.SideBorder
import com.intellij.ui.components.panels.NonOpaquePanel
import com.sourcegraph.Icons
import com.sourcegraph.common.CodyBundle
import java.awt.Dimension

class CommandPanelIgnoreBanner() : NonOpaquePanel() {
init {
ApplicationManager.getApplication().assertIsDispatchThread()

add(
EditorNotificationPanel().apply {
text = CodyBundle.getString("ignore.sidebar-panel-ignored-file.text")
createActionLabel(
CodyBundle.getString("ignore.sidebar-panel-ignored-file.learn-more-cta"),
{ BrowserUtil.browse(CODY_IGNORE_DOCS_URL) },
false)
icon(Icons.CodyLogoSlash)
})

// These colors cribbed from EditorComposite, createTopBottomSideBorder
val scheme = EditorColorsManager.getInstance().globalScheme
val borderColor =
scheme.getColor(EditorColors.SEPARATOR_ABOVE_COLOR)
?: scheme.getColor(EditorColors.TEARLINE_COLOR)
border = SideBorder(borderColor, SideBorder.TOP or SideBorder.BOTTOM)
}

override fun getMaximumSize(): Dimension {
val size = super.getMaximumSize()
size.height = preferredSize.height
return size
}
}

This file was deleted.

Loading

0 comments on commit 4dc5732

Please sign in to comment.