Skip to content

Commit 36bb2ba

Browse files
author
Zhiwen Dai
committed
feat(panel): Enhance variable pairing and session management
- Update context conditions for variable pairing commands to use regex for improved matching. - Introduce sessionId in CVVariable to track panel status and enhance user experience. - Implement checks for open panels to provide visual indicators in variable descriptions. - Refactor pixel highlight synchronization to ensure accurate updates across panels. - Improve panel management logic to handle auxiliary windows and refresh states effectively.
1 parent 5248568 commit 36bb2ba

File tree

6 files changed

+125
-41
lines changed

6 files changed

+125
-41
lines changed

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,22 +91,22 @@
9191
"view/item/context": [
9292
{
9393
"command": "cv-debugmate.pairVariable",
94-
"when": "view == cv-debugmate-variables && (viewItem == 'cvVariable:mat' || viewItem == 'cvVariable:pointcloud')",
94+
"when": "view == cv-debugmate-variables && (viewItem =~ /^cvVariable:(mat|pointcloud)/)",
9595
"group": "inline@2"
9696
},
9797
{
9898
"command": "cv-debugmate.unpairVariable",
99-
"when": "view == cv-debugmate-variables && (viewItem == 'cvVariablePaired:mat' || viewItem == 'cvVariablePaired:pointcloud')",
99+
"when": "view == cv-debugmate-variables && (viewItem =~ /^cvVariablePaired:(mat|pointcloud)/)",
100100
"group": "inline@2"
101101
},
102102
{
103103
"command": "cv-debugmate.pairVariable",
104-
"when": "view == cv-debugmate-variables && (viewItem == 'cvVariable:mat' || viewItem == 'cvVariable:pointcloud')",
104+
"when": "view == cv-debugmate-variables && (viewItem =~ /^cvVariable:(mat|pointcloud)/)",
105105
"group": "1_pairing"
106106
},
107107
{
108108
"command": "cv-debugmate.unpairVariable",
109-
"when": "view == cv-debugmate-variables && (viewItem == 'cvVariablePaired:mat' || viewItem == 'cvVariablePaired:pointcloud')",
109+
"when": "view == cv-debugmate-variables && (viewItem =~ /^cvVariablePaired:(mat|pointcloud)/)",
110110
"group": "1_pairing"
111111
}
112112
]

src/cvVariablesProvider.ts

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as vscode from 'vscode';
22
import { isMat, isPoint3Vector, is1DVector, isLikely1DMat, is1DSet, isMatx, is2DStdArray, is1DStdArray, isPoint3StdArray, is2DCStyleArray, is1DCStyleArray, is3DCStyleArray, is3DStdArray, isUninitializedOrInvalid, isUninitializedMat, isUninitializedMatFromChildren, isUninitializedVector, isPointerType, getPointerEvaluateExpression } from './utils/opencv';
33
import { SyncManager } from './utils/syncManager';
4+
import { PanelManager } from './utils/panelManager';
45

56
const COLORS = [
67
'#3794ef', // Blue
@@ -59,7 +60,8 @@ export class CVVariable extends vscode.TreeItem {
5960
public pairedWith?: string,
6061
public groupIndex?: number,
6162
public readonly isPointer: boolean = false,
62-
public readonly baseType: string = ''
63+
public readonly baseType: string = '',
64+
public readonly sessionId?: string // Add sessionId to check panel status
6365
) {
6466
super(name, collapsibleState);
6567
this.tooltip = `${this.name}: ${this.type}${this.pairedWith ? ` (Paired with ${this.pairedWith})` : ''}${this.isPointer ? ' (pointer)' : ''}`;
@@ -71,24 +73,28 @@ export class CVVariable extends vscode.TreeItem {
7173
this.isEmpty = (sizeInfo === '0' || sizeInfo === '0x0' || sizeInfo === '' || size === 0);
7274
const displaySize = this.isEmpty ? 'empty' : sizeInfo;
7375

74-
// Show pointer indicator in description
76+
// Check if panel is open
77+
const isOpen = sessionId ? this.checkIfPanelOpen(kind, sessionId, name) : false;
78+
79+
// Show pointer indicator and open status in description
7580
const pointerIndicator = this.isPointer ? '→ ' : '';
76-
this.description = `${pointerIndicator}[${displaySize}]`;
81+
const openIndicator = isOpen ? '● ' : '';
82+
this.description = `${openIndicator}${pointerIndicator}[${displaySize}]`;
7783

7884
// For mat (image) variables, always use file-media icon regardless of pairing
7985
if (kind === 'mat') {
8086
this.iconPath = new vscode.ThemeIcon('file-media');
8187
this.contextValue = isPaired
82-
? `cvVariablePaired:${kind}${this.isEmpty ? ':empty' : ''}${this.isPointer ? ':pointer' : ''}`
83-
: `cvVariable:${kind}${this.isEmpty ? ':empty' : ''}${this.isPointer ? ':pointer' : ''}`;
88+
? `cvVariablePaired:${kind}${this.isEmpty ? ':empty' : ''}${this.isPointer ? ':pointer' : ''}${isOpen ? ':open' : ''}`
89+
: `cvVariable:${kind}${this.isEmpty ? ':empty' : ''}${this.isPointer ? ':pointer' : ''}${isOpen ? ':open' : ''}`;
8490
} else if (isPaired && groupIndex !== undefined && kind !== 'plot') {
8591
// For pointcloud, use colored icon when paired
8692
const color = COLORS[groupIndex % COLORS.length];
8793
this.iconPath = getColoredIcon(kind, color);
88-
this.contextValue = `cvVariablePaired:${kind}${this.isEmpty ? ':empty' : ''}${this.isPointer ? ':pointer' : ''}`;
94+
this.contextValue = `cvVariablePaired:${kind}${this.isEmpty ? ':empty' : ''}${this.isPointer ? ':pointer' : ''}${isOpen ? ':open' : ''}`;
8995
} else {
9096
this.iconPath = new vscode.ThemeIcon(typeIcon);
91-
this.contextValue = `cvVariable:${kind}${this.isEmpty ? ':empty' : ''}${this.isPointer ? ':pointer' : ''}`;
97+
this.contextValue = `cvVariable:${kind}${this.isEmpty ? ':empty' : ''}${this.isPointer ? ':pointer' : ''}${isOpen ? ':open' : ''}`;
9298
}
9399

94100
if (!this.isEmpty) {
@@ -99,6 +105,17 @@ export class CVVariable extends vscode.TreeItem {
99105
};
100106
}
101107
}
108+
109+
private checkIfPanelOpen(kind: string, sessionId: string, variableName: string): boolean {
110+
const viewTypeMap: Record<string, string> = {
111+
'mat': 'MatImageViewer',
112+
'pointcloud': 'PointCloudViewer',
113+
'plot': 'PlotViewer'
114+
};
115+
const viewType = viewTypeMap[kind];
116+
if (!viewType) return false;
117+
return PanelManager.hasPanel(viewType, sessionId, variableName);
118+
}
102119
}
103120

104121
export class CVGroup extends vscode.TreeItem {
@@ -576,7 +593,8 @@ export class CVVariablesProvider implements vscode.TreeDataProvider<CVVariable |
576593
pairedVars.length > 0 ? pairedVars.join(', ') : undefined,
577594
groupIndex,
578595
pointerInfo.isPointer,
579-
pointerInfo.baseType
596+
pointerInfo.baseType,
597+
debugSession.id // Pass sessionId
580598
);
581599
}
582600
return null;

src/matImage/matProvider.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ export async function drawMatImage(
287287
// Send ready signal immediately so webview knows this is not a moved panel
288288
panel.webview.postMessage({ command: 'ready' });
289289

290-
SyncManager.registerPanel(variableName, panel);
290+
SyncManager.registerPanel(panelName, panel);
291291

292292
// Dispose previous listener if it exists to avoid multiple listeners on reused panel
293293
if ((panel as any)._syncListener) {
@@ -301,9 +301,9 @@ export async function drawMatImage(
301301
return;
302302
}
303303
if (message.command === 'viewChanged') {
304-
SyncManager.syncView(variableName, message.state);
304+
SyncManager.syncView(panelName, message.state);
305305
} else if (message.command === 'pixelHighlight') {
306-
SyncManager.syncPixelHighlight(variableName, message.pixelX, message.pixelY);
306+
SyncManager.syncPixelHighlight(panelName, message.pixelX, message.pixelY);
307307
} else if (message.command === 'reload') {
308308
const reloadStartTime = Date.now();
309309
console.log(`[DEBUG-TRACE] reload message received for ${variableName} at ${reloadStartTime}`);
@@ -765,9 +765,9 @@ export async function drawMatxImage(
765765
(panel as any)._syncListener = panel.webview.onDidReceiveMessage(
766766
async (message) => {
767767
if (message.command === 'viewChanged') {
768-
SyncManager.syncView(variableName, message.state);
768+
SyncManager.syncView(panelName, message.state);
769769
} else if (message.command === 'pixelHighlight') {
770-
SyncManager.syncPixelHighlight(variableName, message.pixelX, message.pixelY);
770+
SyncManager.syncPixelHighlight(panelName, message.pixelX, message.pixelY);
771771
} else if (message.command === 'reload') {
772772
// Check if debug session is still active before reloading
773773
const currentSession = vscode.debug.activeDebugSession;
@@ -963,9 +963,9 @@ export async function draw2DStdArrayImage(
963963
(panel as any)._syncListener = panel.webview.onDidReceiveMessage(
964964
async (message) => {
965965
if (message.command === 'viewChanged') {
966-
SyncManager.syncView(variableName, message.state);
966+
SyncManager.syncView(panelName, message.state);
967967
} else if (message.command === 'pixelHighlight') {
968-
SyncManager.syncPixelHighlight(variableName, message.pixelX, message.pixelY);
968+
SyncManager.syncPixelHighlight(panelName, message.pixelX, message.pixelY);
969969
} else if (message.command === 'reload') {
970970
// Check if debug session is still active before reloading
971971
const currentSession = vscode.debug.activeDebugSession;

src/matImage/matWebview.ts

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -444,13 +444,21 @@ export function getWebviewContentForMat(
444444
}
445445
applyViewState(state);
446446
} else if (message.command === 'setPixelHighlight') {
447+
console.log('[MatWebview] Received setPixelHighlight: pixel=(' + message.pixelX + ', ' + message.pixelY + '), localHover=(' + localHoverPixelX + ', ' + localHoverPixelY + ')');
447448
// Receive pixel highlight from synced panel
449+
// Always update the synced highlight
448450
highlightPixelX = message.pixelX;
449451
highlightPixelY = message.pixelY;
452+
453+
// Always render - drawPixelHighlight will handle priority
454+
// (local hover takes priority over synced highlight in drawPixelHighlight)
455+
console.log('[MatWebview] Updating synced highlight and rendering: (' + highlightPixelX + ', ' + highlightPixelY + ')');
450456
requestRender();
451457
452-
// Update pixel info display for synced highlight
453-
updatePixelInfoForHighlight(message.pixelX, message.pixelY);
458+
// Update pixel info display for synced highlight only if local mouse is not hovering
459+
if (localHoverPixelX === null && localHoverPixelY === null) {
460+
updatePixelInfoForHighlight(message.pixelX, message.pixelY);
461+
}
454462
}
455463
});
456464
@@ -1334,11 +1342,22 @@ export function getWebviewContentForMat(
13341342
13351343
// Draw pixel highlight with blue border (for synced panels)
13361344
function drawPixelHighlight() {
1337-
// Use either synced highlight or local hover
1338-
const px = highlightPixelX !== null ? highlightPixelX : localHoverPixelX;
1339-
const py = highlightPixelY !== null ? highlightPixelY : localHoverPixelY;
1345+
// Priority: local hover > synced highlight
1346+
// If local mouse is hovering, show local; otherwise show synced
1347+
let px, py;
1348+
if (localHoverPixelX !== null && localHoverPixelY !== null) {
1349+
// Local mouse is active, use local hover
1350+
px = localHoverPixelX;
1351+
py = localHoverPixelY;
1352+
} else if (highlightPixelX !== null && highlightPixelY !== null) {
1353+
// No local hover, show synced highlight
1354+
px = highlightPixelX;
1355+
py = highlightPixelY;
1356+
} else {
1357+
// No highlight to show
1358+
return;
1359+
}
13401360
1341-
if (px === null || py === null) return;
13421361
if (px < 0 || px >= cols || py < 0 || py >= rows) return;
13431362
13441363
// Calculate screen position of the pixel
@@ -1568,25 +1587,35 @@ export function getWebviewContentForMat(
15681587
container.addEventListener('mousedown', (e) => {
15691588
if (isShuttingDown) return;
15701589
isDragging = true;
1571-
startX = e.clientX - offsetX;
1572-
startY = e.clientY - offsetY;
1590+
const containerRect = container.getBoundingClientRect();
1591+
const mouseX = e.clientX - containerRect.left;
1592+
const mouseY = e.clientY - containerRect.top;
1593+
startX = mouseX - offsetX;
1594+
startY = mouseY - offsetY;
15731595
});
15741596
1575-
document.addEventListener('mousemove', (e) => {
1597+
// Use container for mousemove to ensure it works in both main window and auxiliary window
1598+
container.addEventListener('mousemove', (e) => {
15761599
if (isShuttingDown) return;
1577-
lastMouseX = e.clientX;
1578-
lastMouseY = e.clientY;
1600+
1601+
// Get mouse position relative to container
1602+
const containerRect = container.getBoundingClientRect();
1603+
const mouseX = e.clientX - containerRect.left;
1604+
const mouseY = e.clientY - containerRect.top;
1605+
1606+
lastMouseX = mouseX;
1607+
lastMouseY = mouseY;
15791608
hasLastMouse = true;
15801609
15811610
if (isDragging) {
1582-
offsetX = e.clientX - startX;
1583-
offsetY = e.clientY - startY;
1611+
offsetX = mouseX - startX;
1612+
offsetY = mouseY - startY;
15841613
requestRenderWithSync();
15851614
}
15861615
1587-
// Update pixel info
1588-
const imgX = Math.floor((e.clientX - offsetX) / scale);
1589-
const imgY = Math.floor((e.clientY - offsetY) / scale);
1616+
// Update pixel info - use relative coordinates
1617+
const imgX = Math.floor((mouseX - offsetX) / scale);
1618+
const imgY = Math.floor((mouseY - offsetY) / scale);
15901619
15911620
if (imgX >= 0 && imgX < cols && imgY >= 0 && imgY < rows) {
15921621
const idx = (imgY * cols + imgX) * channels;
@@ -1604,9 +1633,11 @@ export function getWebviewContentForMat(
16041633
if (localHoverPixelX !== imgX || localHoverPixelY !== imgY) {
16051634
localHoverPixelX = imgX;
16061635
localHoverPixelY = imgY;
1636+
// Don't clear synced highlight - it's stored and drawPixelHighlight will decide priority
16071637
requestRender();
16081638
// Send pixel highlight to synced panels
16091639
if (!isShuttingDown && isInitialized) {
1640+
console.log('[MatWebview] Sending pixelHighlight: (' + imgX + ', ' + imgY + ')');
16101641
vscode.postMessage({
16111642
command: 'pixelHighlight',
16121643
pixelX: imgX,
@@ -1616,7 +1647,7 @@ export function getWebviewContentForMat(
16161647
}
16171648
} else {
16181649
pixelInfo.textContent = '';
1619-
// Clear local hover
1650+
// Clear local hover when mouse leaves image area
16201651
if (localHoverPixelX !== null || localHoverPixelY !== null) {
16211652
localHoverPixelX = null;
16221653
localHoverPixelY = null;
@@ -1630,6 +1661,8 @@ export function getWebviewContentForMat(
16301661
});
16311662
}
16321663
}
1664+
// When mouse leaves image area, allow synced highlight to show (don't clear it)
1665+
// This allows other windows' highlights to be visible when local mouse is outside
16331666
}
16341667
});
16351668
@@ -1642,7 +1675,10 @@ export function getWebviewContentForMat(
16421675
e.preventDefault();
16431676
const delta = -e.deltaY;
16441677
const factor = delta > 0 ? 1.1 : 1 / 1.1;
1645-
setZoomAt(e.clientX, e.clientY, scale * factor);
1678+
const containerRect = container.getBoundingClientRect();
1679+
const mouseX = e.clientX - containerRect.left;
1680+
const mouseY = e.clientY - containerRect.top;
1681+
setZoomAt(mouseX, mouseY, scale * factor);
16461682
}, { passive: false });
16471683
16481684
window.addEventListener('resize', () => {

src/utils/panelManager.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ export class PanelManager {
4848
}
4949
}
5050

51+
/**
52+
* Check if a panel exists for the given view type, session, and variable name.
53+
*/
54+
static hasPanel(viewType: string, sessionId: string, variableName: string): boolean {
55+
const key = `${viewType}:::${sessionId}:::${variableName}`;
56+
return this.panels.has(key);
57+
}
58+
5159
/**
5260
* Check if a panel needs refreshing because the debug state has moved forward.
5361
*/
@@ -174,15 +182,21 @@ export class PanelManager {
174182
const ptrKey = `${viewType}:::${sessionId}:::ptr:${dataPtr}`;
175183
this.dataPtrToKey.set(ptrKey, key);
176184
}
185+
186+
// Trigger tree view refresh after panel creation
187+
Promise.resolve(vscode.commands.executeCommand('cv-debugmate.refreshVariables')).catch(() => {
188+
// Silently ignore if command doesn't exist yet
189+
});
177190

178191
panel.onDidDispose(() => {
179192
// Mark as disposing to prevent any other code from using this panel
180193
(panel as any)._isDisposing = true;
181194

182195
// Show shortcut tip when closing panel (helps with auxiliary window freeze issue)
183-
if (vscode.debug.activeDebugSession) {
196+
// Panel is in auxiliary window if viewColumn is undefined
197+
if (vscode.debug.activeDebugSession && panel.viewColumn === undefined) {
184198
vscode.window.showInformationMessage(
185-
'⚠️ Closing auxiliary window may cause debug buttons to freeze. Use shortcuts: ▶️ Continue (F5) | ⏭️ Step Over (F10) | ⬇️ Step Into (F11) | ⬆️ Step Out (Shift+F11) | ⏹️ Stop (Shift+F5)'
199+
'Debug shortcuts: Continue(F5) | Step Over(F10) | Step Into(F11) | Step Out(Shift+F11) | Stop(Shift+F5). Closing auxiliary window may freeze debug buttons.'
186200
);
187201
}
188202

@@ -199,6 +213,11 @@ export class PanelManager {
199213
this.panels.delete(k);
200214
}
201215
}
216+
217+
// Trigger tree view refresh after panel disposal
218+
Promise.resolve(vscode.commands.executeCommand('cv-debugmate.refreshVariables')).catch(() => {
219+
// Silently ignore if command doesn't exist yet
220+
});
202221
});
203222

204223
panel.onDidChangeViewState(e => {

0 commit comments

Comments
 (0)