fix: debugConsole context menus excute (#2394)

* fix: debug console context menus

* fix: improve debug console output style

* test: add debugConsole contextMenu test
This commit is contained in:
Dan 2023-03-13 12:02:39 +08:00 committed by GitHub
parent 75ac54e8fe
commit 62c02aafb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 112 additions and 20 deletions

2
.vscode/launch.json vendored
View File

@ -111,7 +111,7 @@
"name": "Playwright Current File", "name": "Playwright Current File",
"type": "node", "type": "node",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/tools/playwright/node_modules/.bin/playwright", "program": "${workspaceFolder}/node_modules/.bin/playwright",
"args": ["test", "--debug", "--config=./configs/playwright.debug.config.ts", "${fileBasenameNoExtension}"], "args": ["test", "--debug", "--config=./configs/playwright.debug.config.ts", "${fileBasenameNoExtension}"],
"cwd": "${workspaceFolder}/tools/playwright", "cwd": "${workspaceFolder}/tools/playwright",
"console": "integratedTerminal", "console": "integratedTerminal",

View File

@ -2,7 +2,7 @@
export const MARKER_CONTAINER_ID = 'markers'; export const MARKER_CONTAINER_ID = 'markers';
export const OUTPUT_CONTAINER_ID = 'output'; export const OUTPUT_CONTAINER_ID = 'output';
export const TERMINAL_CONTAINER_ID = 'terminal'; export const TERMINAL_CONTAINER_ID = 'terminal';
export const DEBUG_CONSOLE_CONTAINER_ID = 'debug-console-container'; export const DEBUG_CONSOLE_CONTAINER_ID = 'debug-console';
// Left Panel 的 Container // Left Panel 的 Container
export const DEBUG_CONTAINER_ID = 'debug'; export const DEBUG_CONTAINER_ID = 'debug';

View File

@ -1,7 +1,7 @@
import throttle from 'lodash/throttle'; import throttle from 'lodash/throttle';
import { Autowired, Injectable, Optional } from '@opensumi/di'; import { Autowired, Injectable, Optional } from '@opensumi/di';
import { DisposableCollection, Emitter, Event, MessageType, ILogger } from '@opensumi/ide-core-common'; import { DisposableCollection, Emitter, Event, MessageType, ILogger, localize } from '@opensumi/ide-core-common';
import { IThemeService } from '@opensumi/ide-theme'; import { IThemeService } from '@opensumi/ide-theme';
import { DebugProtocol } from '@opensumi/vscode-debugprotocol/lib/debugProtocol'; import { DebugProtocol } from '@opensumi/vscode-debugprotocol/lib/debugProtocol';
@ -68,6 +68,8 @@ export class DebugConsoleSession implements IDebugConsoleSession {
} }
protected async logOutput(session: DebugSession, event: DebugProtocol.OutputEvent): Promise<void> { protected async logOutput(session: DebugSession, event: DebugProtocol.OutputEvent): Promise<void> {
// [2J is the ansi escape sequence for clearing the display http://ascii-table.com/ansi-escape-sequences.php
const clearAnsiSequence = '\u001b[2J';
const body = event.body; const body = event.body;
const { category, variablesReference, source, line } = body; const { category, variablesReference, source, line } = body;
if (!this.treeModel) { if (!this.treeModel) {
@ -76,7 +78,9 @@ export class DebugConsoleSession implements IDebugConsoleSession {
const severity = const severity =
category === 'stderr' category === 'stderr'
? MessageType.Error ? MessageType.Error
: event.body.category === 'console' : category === 'stdout'
? MessageType.Info
: category === 'console'
? MessageType.Warning ? MessageType.Warning
: MessageType.Info; : MessageType.Info;
if (category === 'telemetry') { if (category === 'telemetry') {
@ -95,11 +99,23 @@ export class DebugConsoleSession implements IDebugConsoleSession {
} }
} }
} else if (typeof body.output === 'string') { } else if (typeof body.output === 'string') {
let output = body.output;
if (output.indexOf(clearAnsiSequence) >= 0) {
this.clearConsole();
await this.insertItemWithAnsi(localize('debug.console.consoleCleare'), MessageType.Info);
output = output.substring(output.lastIndexOf(clearAnsiSequence) + clearAnsiSequence.length);
}
const previousItem = this.getLastItem(); const previousItem = this.getLastItem();
/** /**
* output output * MessageType
*
*/ */
if (previousItem && !previousItem.description.endsWith('\n') && !previousItem.description.endsWith('\r\n')) { if (
previousItem &&
!previousItem.description.endsWith('\n') &&
!previousItem.description.endsWith('\r\n') &&
(previousItem as AnsiConsoleNode).severity === severity
) {
this.treeModel.root.unlinkItem(previousItem); this.treeModel.root.unlinkItem(previousItem);
await this.insertItemWithAnsi(previousItem.description + body.output, severity, source, line); await this.insertItemWithAnsi(previousItem.description + body.output, severity, source, line);
} else { } else {
@ -110,6 +126,16 @@ export class DebugConsoleSession implements IDebugConsoleSession {
this.fireDidChange(); this.fireDidChange();
} }
private async clearConsole() {
const items = this.treeModel.root.flattenedBranch?.map((id) => this.treeModel.root.getTreeNodeById(id));
if (!items) {
return;
}
for (const item of items) {
this.treeModel.root.unlinkItem(item as AnsiConsoleNode);
}
}
private async insertItemWithAnsi( private async insertItemWithAnsi(
output: string, output: string,
severity?: MessageType, severity?: MessageType,

View File

@ -21,7 +21,7 @@ import {
} from '@opensumi/ide-core-browser'; } from '@opensumi/ide-core-browser';
import { AbstractContextMenuService, MenuId, ICtxMenuRenderer } from '@opensumi/ide-core-browser/lib/menu/next'; import { AbstractContextMenuService, MenuId, ICtxMenuRenderer } from '@opensumi/ide-core-browser/lib/menu/next';
import { IDebugSessionManager } from '../../../common'; import { IDebugConsoleModelService, IDebugSessionManager } from '../../../common';
import { LinkDetector } from '../../debug-link-detector'; import { LinkDetector } from '../../debug-link-detector';
import { DebugSession } from '../../debug-session'; import { DebugSession } from '../../debug-session';
import { DidChangeActiveDebugSession } from '../../debug-session-manager'; import { DidChangeActiveDebugSession } from '../../debug-session-manager';
@ -50,7 +50,7 @@ export interface IDebugConsoleModel {
} }
@Injectable() @Injectable()
export class DebugConsoleModelService { export class DebugConsoleModelService implements IDebugConsoleModelService {
private static DEFAULT_REFRESH_DELAY = 200; private static DEFAULT_REFRESH_DELAY = 200;
@Autowired(INJECTOR_TOKEN) @Autowired(INJECTOR_TOKEN)
@ -164,16 +164,16 @@ export class DebugConsoleModelService {
return this.onDidRefreshedEmitter.event; return this.onDidRefreshedEmitter.event;
} }
clear = () => { clear() {
// 重新初始化Console中渲染的TreeModel // 重新初始化Console中渲染的TreeModel
this.initTreeModel(this.manager.currentSession, true); this.initTreeModel(this.manager.currentSession, true);
}; }
collapseAll = () => { collapseAll() {
this.treeModel?.root.collapsedAll(); this.treeModel?.root.collapsedAll();
}; }
copyAll = () => { copyAll() {
let text = ''; let text = '';
if (!this.treeModel?.root || !this.treeModel.root.children) { if (!this.treeModel?.root || !this.treeModel.root.children) {
return; return;
@ -182,15 +182,18 @@ export class DebugConsoleModelService {
text += this.getValidText(child as DebugConsoleNode) + '\n'; text += this.getValidText(child as DebugConsoleNode) + '\n';
} }
this.clipboardService.writeText(text.slice(0, -'\n'.length)); this.clipboardService.writeText(text.slice(0, -'\n'.length));
}; }
copy = (node: DebugConsoleNode) => { copy(node: DebugConsoleNode) {
if (node) { if (node) {
this.clipboardService.writeText(this.getValidText(node)); this.clipboardService.writeText(this.getValidText(node));
} }
}; }
private getValidText(node: DebugConsoleNode) { private getValidText(node: DebugConsoleNode) {
if (node.description.endsWith('\n')) {
return node.description.slice(0, -'\n'.length);
}
return node.description; return node.description;
} }

View File

@ -22,6 +22,7 @@ import {
IDebugSessionManager, IDebugSessionManager,
CONTEXT_IN_DEBUG_MODE_KEY, CONTEXT_IN_DEBUG_MODE_KEY,
DebugState, DebugState,
IDebugConsoleModelService,
} from '../../../common'; } from '../../../common';
import { DebugSessionManager } from '../../debug-session-manager'; import { DebugSessionManager } from '../../debug-session-manager';
@ -47,7 +48,7 @@ const consoleInputMonacoOptions: monaco.editor.IEditorOptions = {
@Injectable() @Injectable()
export class DebugConsoleService implements IHistoryNavigationWidget { export class DebugConsoleService implements IHistoryNavigationWidget {
@Autowired(DebugConsoleModelService) @Autowired(IDebugConsoleModelService)
protected readonly debugConsoleModelService: DebugConsoleModelService; protected readonly debugConsoleModelService: DebugConsoleModelService;
@Autowired(IMainLayoutService) @Autowired(IMainLayoutService)
@ -115,7 +116,7 @@ export class DebugConsoleService implements IHistoryNavigationWidget {
return bottomPanelHandler && bottomPanelHandler.isVisible; return bottomPanelHandler && bottomPanelHandler.isVisible;
} }
public get consoleModel(): DebugConsoleModelService { public get consoleModel() {
return this.debugConsoleModelService; return this.debugConsoleModelService;
} }

View File

@ -12,9 +12,10 @@ export interface IDebugConsoleSession extends ITree {
} }
export interface IDebugConsoleModelService { export interface IDebugConsoleModelService {
debugConsoleSession: IDebugConsoleSession; debugConsoleSession?: IDebugConsoleSession;
clear(): void; clear(): void;
copyAll(): void; copyAll(): void;
collapseAll(): void; collapseAll(): void;
copy(node: ITreeNode): void; copy(node: ITreeNode): void;
execute(value: string): Promise<void>;
} }

View File

@ -23,6 +23,7 @@ import { DebugConfigurationManager } from '@opensumi/ide-debug/lib/browser/debug
import { DebugPreferences } from '@opensumi/ide-debug/lib/browser/debug-preferences'; import { DebugPreferences } from '@opensumi/ide-debug/lib/browser/debug-preferences';
import { DebugSessionContributionRegistry } from '@opensumi/ide-debug/lib/browser/debug-session-contribution'; import { DebugSessionContributionRegistry } from '@opensumi/ide-debug/lib/browser/debug-session-contribution';
import { DebugSessionManager } from '@opensumi/ide-debug/lib/browser/debug-session-manager'; import { DebugSessionManager } from '@opensumi/ide-debug/lib/browser/debug-session-manager';
import { DebugConsoleModelService } from '@opensumi/ide-debug/lib/browser/view/console/debug-console-tree.model.service';
import { IDebugService, IDebugServer } from '@opensumi/ide-debug/lib/common/debug-service'; import { IDebugService, IDebugServer } from '@opensumi/ide-debug/lib/common/debug-service';
import { IDebugSessionManager, IDebugSessionOptions } from '@opensumi/ide-debug/lib/common/debug-session'; import { IDebugSessionManager, IDebugSessionOptions } from '@opensumi/ide-debug/lib/common/debug-session';
import { WorkbenchEditorService } from '@opensumi/ide-editor'; import { WorkbenchEditorService } from '@opensumi/ide-editor';
@ -98,7 +99,7 @@ export class MainThreadDebug implements IMainThreadDebug {
protected readonly terminalService: ITerminalApiService; protected readonly terminalService: ITerminalApiService;
@Autowired(IDebugConsoleModelService) @Autowired(IDebugConsoleModelService)
protected readonly debugConsoleModelService: IDebugConsoleModelService; protected readonly debugConsoleModelService: DebugConsoleModelService;
@Autowired(OutputService) @Autowired(OutputService)
protected readonly outputService: OutputService; protected readonly outputService: OutputService;

View File

@ -309,6 +309,7 @@ export const localizationBundle = {
'debug.console.followLink': '{0} + click to follow link', 'debug.console.followLink': '{0} + click to follow link',
'debug.console.input.placeholder': 'Please start a debug session to evaluate expressions', 'debug.console.input.placeholder': 'Please start a debug session to evaluate expressions',
'debug.console.errorMessage': 'Debug session initialization failed. See console for details.', 'debug.console.errorMessage': 'Debug session initialization failed. See console for details.',
'debug.console.consoleCleared': 'Console was cleared',
'debug.notSupported.type': 'Debug type "{0}" is not supported, please check your launch config.', 'debug.notSupported.type': 'Debug type "{0}" is not supported, please check your launch config.',
'debug.notSupported.any': 'Debug is not supported, please check your launch config.', 'debug.notSupported.any': 'Debug is not supported, please check your launch config.',

View File

@ -269,6 +269,7 @@ export const localizationBundle = {
'debug.console.followLink': '按住 {0} 并单击可访问链接', 'debug.console.followLink': '按住 {0} 并单击可访问链接',
'debug.console.errorMessage': '调试进程初始化异常,请打开控制面板查看错误日志', 'debug.console.errorMessage': '调试进程初始化异常,请打开控制面板查看错误日志',
'debug.console.input.placeholder': '请发起调试会话来对表达式求值', 'debug.console.input.placeholder': '请发起调试会话来对表达式求值',
'debug.console.consoleCleared': '输出已清理',
'debug.notSupported.type': '调试类型 "{0}" 不是支持的调试类型,请检查配置或安装对应调试插件', 'debug.notSupported.type': '调试类型 "{0}" 不是支持的调试类型,请检查配置或安装对应调试插件',
'debug.notSupported.any': '当前调试配置不支持,请检查配置或安装对应调试插件', 'debug.notSupported.any': '当前调试配置不支持,请检查配置或安装对应调试插件',

View File

@ -0,0 +1,21 @@
import { OpenSumiApp } from './app';
import { OpenSumiContextMenu } from './context-menu';
import { OpenSumiPanel } from './panel';
export class OpenSumiDebugConsoleView extends OpenSumiPanel {
constructor(app: OpenSumiApp) {
super(app, 'DEBUG-CONSOLE');
}
async getOutputContainer() {
return this.view?.$('[class*="debug_console_output__"]');
}
async openConsoleContextMenu() {
const view = await this.getOutputContainer();
if (!view) {
return;
}
return OpenSumiContextMenu.open(this.app, async () => view);
}
}

View File

@ -3,6 +3,7 @@ import path from 'path';
import { expect } from '@playwright/test'; import { expect } from '@playwright/test';
import { OpenSumiApp } from '../app'; import { OpenSumiApp } from '../app';
import { OpenSumiDebugConsoleView } from '../debug-console-view';
import { OpenSumiDebugView } from '../debug-view'; import { OpenSumiDebugView } from '../debug-view';
import { OpenSumiExplorerView } from '../explorer-view'; import { OpenSumiExplorerView } from '../explorer-view';
import { OpenSumiTerminalView } from '../terminal-view'; import { OpenSumiTerminalView } from '../terminal-view';
@ -80,6 +81,41 @@ test.describe('OpenSumi Debug', () => {
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
}); });
test('ContextMenu on DebugConsole should be work', async () => {
editor = await app.openEditor(OpenSumiTextEditor, explorer, 'index.js', false);
await app.page.waitForTimeout(1000);
debugView = await app.open(OpenSumiDebugView);
const glyphMarginModel = await editor.getGlyphMarginModel();
const glyphOverlay = await glyphMarginModel.getOverlay(6);
expect(glyphOverlay).toBeDefined();
if (!glyphOverlay) {
return;
}
const isClicked = await glyphMarginModel.hasBreakpoint(glyphOverlay);
if (!isClicked) {
await glyphOverlay?.click({ position: { x: 9, y: 9 }, force: true });
await app.page.waitForTimeout(1000);
}
await debugView.start();
await app.page.waitForTimeout(2000);
const debugConsole = await app.open(OpenSumiDebugConsoleView);
const contextMenu = await debugConsole.openConsoleContextMenu();
await app.page.waitForTimeout(200);
expect(await contextMenu?.isOpen()).toBeTruthy();
const copyAll = await contextMenu?.menuItemByName('Copy All');
await copyAll?.click();
await app.page.waitForTimeout(1000);
const text = (await page.evaluate('navigator.clipboard.readText()')) as string;
expect(text.includes('Debugger attached.')).toBeTruthy();
await editor.close();
await debugView.stop();
await page.waitForTimeout(1000);
});
test('Run Debug by Javascript Debug Terminal', async () => { test('Run Debug by Javascript Debug Terminal', async () => {
await explorer.open(); await explorer.open();
editor = await app.openEditor(OpenSumiTextEditor, explorer, 'index.js', false); editor = await app.openEditor(OpenSumiTextEditor, explorer, 'index.js', false);

View File

@ -9,6 +9,7 @@
"request": "launch", "request": "launch",
"name": "Launch Program", "name": "Launch Program",
"program": "${workspaceFolder}/index.js", "program": "${workspaceFolder}/index.js",
"outputCapture": "std",
"skipFiles": ["<node_internals>/**"] "skipFiles": ["<node_internals>/**"]
} }
] ]