Skip to content

Commit

Permalink
feat(auto-edit): add basic webview debug-panel (#7304)
Browse files Browse the repository at this point in the history
- Integrates the basic auto-edit debug panel. This includes all the
infrastructure:
    - Build config updates.
    - Command to open the panel.
    - HTML and CSS files used to render the panel.
- Integration with the analytics logger used to pipe auto-edit request
updates to the webview.
- Actual content will be added to a follow-up PR.
- Built on top of #7303
  • Loading branch information
valerybugakov authored Mar 5, 2025
1 parent 558db4e commit 5ee987e
Show file tree
Hide file tree
Showing 13 changed files with 484 additions and 14 deletions.
18 changes: 18 additions & 0 deletions vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,14 @@
"group": "Cody",
"icon": "$(feedback)"
},
{
"command": "cody.command.autoedit.open-debug-panel",
"title": "Debug Auto-Edit",
"category": "Cody",
"group": "Cody",
"icon": "$(debug)",
"enablement": "cody.activated"
},
{
"command": "cody.command.explain-output",
"title": "Ask Cody to Explain",
Expand Down Expand Up @@ -582,6 +590,12 @@
"command": "cody.command.autoedit-manual-trigger",
"title": "Autoedits Manual Trigger",
"enablement": "cody.activated && config.cody.suggestions.mode == 'auto-edit (Experimental)'"
},
{
"command": "cody.command.autoedit.open-debug-panel",
"category": "Cody",
"title": "Debug Auto-Edit",
"enablement": "cody.activated"
}
],
"keybindings": [
Expand Down Expand Up @@ -721,6 +735,10 @@
],
"menus": {
"commandPalette": [
{
"command": "cody.command.autoedit.open-debug-panel",
"when": "cody.activated"
},
{
"command": "cody.command.edit-code",
"when": "cody.activated && editorIsOpen"
Expand Down
4 changes: 4 additions & 0 deletions vscode/src/autoedits/analytics-logger/analytics-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type { CodeToReplaceData } from '../prompt/prompt-utils'
import type { DecorationInfo } from '../renderer/decorators/base'
import { getDecorationStats } from '../renderer/diff-utils'

import { autoeditDebugStore } from '../debug-panel/debug-store'
import { autoeditIdRegistry } from './suggestion-id-registry'
import {
type AcceptedState,
Expand Down Expand Up @@ -358,6 +359,9 @@ export class AutoeditAnalyticsLogger {
phase: nextPhase,
} as PhaseStates[P]

// Integrate auto-edit analytics logger with the auto-edit debug panel.
autoeditDebugStore.addAutoeditRequestDebugState(updatedRequest)

this.activeRequests.set(requestId, updatedRequest)

return { updatedRequest, currentRequest }
Expand Down
2 changes: 1 addition & 1 deletion vscode/src/autoedits/autoedits-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface BaseAutoeditsProviderConfig {
isChatModel: boolean
}

interface AutoeditsProviderConfig extends BaseAutoeditsProviderConfig {
export interface AutoeditsProviderConfig extends BaseAutoeditsProviderConfig {
experimentalAutoeditsConfigOverride: AutoEditsModelConfig | undefined
isMockResponseFromCurrentDocumentTemplateEnabled: boolean
}
Expand Down
6 changes: 6 additions & 0 deletions vscode/src/autoedits/create-autoedits-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { FixupController } from '../non-stop/FixupController'

import type { CodyStatusBar } from '../services/StatusBar'
import { AutoeditsProvider } from './autoedits-provider'
import { AutoeditDebugPanel } from './debug-panel/debug-panel'
import { autoeditsOutputChannelLogger } from './output-channel-logger'
import { initImageSuggestionService } from './renderer/image-gen'

Expand Down Expand Up @@ -54,6 +55,7 @@ interface AutoeditsItemProviderArgs {
autoeditImageRenderingEnabled: boolean
fixupController: FixupController
statusBar: CodyStatusBar
context: vscode.ExtensionContext
}

export function createAutoEditsProvider({
Expand All @@ -64,6 +66,7 @@ export function createAutoEditsProvider({
autoeditImageRenderingEnabled,
fixupController,
statusBar,
context,
}: AutoeditsItemProviderArgs): Observable<void> {
if (!configuration.experimentalAutoEditEnabled) {
return NEVER
Expand Down Expand Up @@ -109,6 +112,9 @@ export function createAutoEditsProvider({
[{ scheme: 'file', language: '*' }, { notebookType: '*' }],
provider
),
vscode.commands.registerCommand('cody.command.autoedit.open-debug-panel', () => {
AutoeditDebugPanel.showPanel(context)
}),
provider,
]
}),
Expand Down
172 changes: 172 additions & 0 deletions vscode/src/autoedits/debug-panel/debug-panel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import * as vscode from 'vscode'

import { manipulateWebviewHTML } from '../../chat/chat-view/ChatController'
import type { AutoeditDebugMessageFromExtension } from './debug-protocol'

import { autoeditDebugStore } from './debug-store'

/**
* A panel that displays debug information about auto-edit requests.
*/
export class AutoeditDebugPanel {
public static currentPanel: AutoeditDebugPanel | undefined
private static readonly viewType = 'codyAutoeditDebugPanel'

private readonly panel: vscode.WebviewPanel
private readonly extensionContext: vscode.ExtensionContext
private disposables: vscode.Disposable[] = []
private updatePending = false
private readonly throttleMs = 500 // Throttle updates to at most once per 500ms

private constructor(panel: vscode.WebviewPanel, extensionContext: vscode.ExtensionContext) {
this.panel = panel
this.extensionContext = extensionContext

// Set the webview's initial content
void this.updateContent()

// Listen for when the panel is disposed
// This happens when the user closes the panel or when the panel is closed programmatically
this.panel.onDidDispose(() => this.dispose(), null, this.disposables)

// Subscribe to store changes with throttling
this.disposables.push(
autoeditDebugStore.onDidChange(() => {
// If an update is already pending, don't schedule another one
if (!this.updatePending) {
this.updatePending = true
setTimeout(() => {
this.updatePending = false
void this.updateContent()
}, this.throttleMs)
}
})
)

// Handle messages from the webview
this.panel.webview.onDidReceiveMessage(
message => {
if (message.type === 'ready') {
// Send the initial data when the webview is ready
void this.updateContent()
}
},
null,
this.disposables
)
}

/**
* Type-safe wrapper for sending messages to the webview.
* Ensures that only valid messages defined in the protocol are sent.
*/
private postMessageToWebview(message: AutoeditDebugMessageFromExtension): void {
this.panel.webview.postMessage(message)
}

/**
* Shows the debug panel in the editor.
* If the panel already exists, it will be revealed.
*/
public static showPanel(extensionContext: vscode.ExtensionContext): void {
// Try to reveal existing panel if available
if (AutoeditDebugPanel.currentPanel) {
try {
const viewColumn = AutoeditDebugPanel.currentPanel.panel.viewColumn
AutoeditDebugPanel.currentPanel.panel.reveal(viewColumn, false)
return
} catch (error) {
console.log('Error revealing panel:', error)
AutoeditDebugPanel.currentPanel = undefined
}
}

// Determine view column for a new panel
let viewColumn = vscode.ViewColumn.Beside

if (vscode.window.activeTextEditor?.viewColumn === vscode.ViewColumn.One) {
viewColumn = vscode.ViewColumn.Two
} else if (vscode.window.activeTextEditor?.viewColumn) {
viewColumn = vscode.window.activeTextEditor.viewColumn
}

// Create a new panel
const panel = vscode.window.createWebviewPanel(
AutoeditDebugPanel.viewType,
'Cody Auto-Edit Debug Panel',
viewColumn,
{
enableScripts: true,
localResourceRoots: [vscode.Uri.joinPath(extensionContext.extensionUri, 'dist')],
}
)

AutoeditDebugPanel.currentPanel = new AutoeditDebugPanel(panel, extensionContext)
}

/**
* Updates the content of the panel with the latest auto-edit requests.
*/
private async updateContent(): Promise<void> {
const entries = autoeditDebugStore.getAutoeditRequestDebugStates()

// Send the updated entries to the webview using the type-safe protocol
this.postMessageToWebview({
type: 'updateEntries',
entries,
})

// If no HTML content is set yet, set the initial HTML
if (!this.panel.webview.html) {
this.panel.webview.html = await this.getHtmlForWebview(this.panel.webview)
}
}

/**
* Generates the HTML for the webview panel, including the React app.
*/
private async getHtmlForWebview(webview: vscode.Webview): Promise<string> {
// Read the compiled HTML file using VS Code's file system API
try {
const htmlPath = vscode.Uri.joinPath(
this.extensionContext.extensionUri,
'dist',
'webviews',
'autoedit-debug.html'
)
const htmlBytes = await vscode.workspace.fs.readFile(htmlPath)
const htmlContent = new TextDecoder('utf-8').decode(htmlBytes)

// Create URI for the webview resources
const webviewResourcesUri = webview.asWebviewUri(
vscode.Uri.joinPath(this.extensionContext.extensionUri, 'dist', 'webviews')
)

// Use the shared manipulateWebviewHTML function
return manipulateWebviewHTML(htmlContent, {
cspSource: webview.cspSource,
resources: webviewResourcesUri,
})
} catch (error) {
console.error('Error getting HTML for webview:', error)
return ''
}
}

/**
* Dispose of the panel when it's closed.
*/
public dispose(): void {
AutoeditDebugPanel.currentPanel = undefined

// Clean up our resources
this.panel.dispose()

while (this.disposables.length) {
const disposable = this.disposables.pop()
if (disposable) {
disposable.dispose()
}
}
}
}
13 changes: 13 additions & 0 deletions vscode/src/autoedits/debug-panel/debug-protocol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { AutoeditRequestDebugState } from './debug-store'

export type AutoeditDebugMessageFromExtension = {
type: 'updateEntries'
entries: ReadonlyArray<AutoeditRequestDebugState>
}

export type AutoeditDebugMessageFromWebview = { type: 'ready' }

export interface VSCodeAutoeditDebugWrapper {
postMessage: (message: AutoeditDebugMessageFromWebview) => void
onMessage: (callback: (message: AutoeditDebugMessageFromExtension) => void) => () => void
}
Loading

0 comments on commit 5ee987e

Please sign in to comment.