deploy: current vibn theia state
Made-with: Cursor
This commit is contained in:
48
packages/console/src/browser/ansi-console-item.tsx
Normal file
48
packages/console/src/browser/ansi-console-item.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import * as React from '@theia/core/shared/react';
|
||||
import * as DOMPurify from '@theia/core/shared/dompurify';
|
||||
import { ConsoleItem } from './console-session';
|
||||
import { Severity } from '@theia/core/lib/common/severity';
|
||||
import Anser = require('anser');
|
||||
|
||||
export class AnsiConsoleItem implements ConsoleItem {
|
||||
|
||||
protected readonly htmlContent: string;
|
||||
|
||||
constructor(
|
||||
public readonly content: string,
|
||||
public readonly severity?: Severity
|
||||
) {
|
||||
this.htmlContent = new Anser().ansiToHtml(this.content, {
|
||||
use_classes: true,
|
||||
remove_empty: true
|
||||
});
|
||||
}
|
||||
|
||||
get visible(): boolean {
|
||||
return !!this.htmlContent;
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
return <div
|
||||
className='theia-console-ansi-console-item'
|
||||
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(this.htmlContent) }} // eslint-disable-line react/no-danger
|
||||
/>;
|
||||
}
|
||||
|
||||
}
|
||||
68
packages/console/src/browser/console-content-widget.tsx
Normal file
68
packages/console/src/browser/console-content-widget.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { interfaces, Container, injectable } from '@theia/core/shared/inversify';
|
||||
import { MenuPath } from '@theia/core';
|
||||
import { TreeProps } from '@theia/core/lib/browser/tree';
|
||||
import { SourceTreeWidget, TreeElementNode } from '@theia/core/lib/browser/source-tree';
|
||||
import { ConsoleItem } from './console-session';
|
||||
import { Severity } from '@theia/core/lib/common/severity';
|
||||
|
||||
@injectable()
|
||||
export class ConsoleContentWidget extends SourceTreeWidget {
|
||||
|
||||
static CONTEXT_MENU: MenuPath = ['console-context-menu'];
|
||||
|
||||
static override createContainer(parent: interfaces.Container, props?: Partial<TreeProps>): Container {
|
||||
const child = SourceTreeWidget.createContainer(parent, {
|
||||
contextMenuPath: ConsoleContentWidget.CONTEXT_MENU,
|
||||
viewProps: {
|
||||
followOutput: true
|
||||
},
|
||||
...props
|
||||
});
|
||||
child.unbind(SourceTreeWidget);
|
||||
child.bind(ConsoleContentWidget).toSelf();
|
||||
return child;
|
||||
}
|
||||
|
||||
protected override createTreeElementNodeClassNames(node: TreeElementNode): string[] {
|
||||
const classNames = super.createTreeElementNodeClassNames(node);
|
||||
if (node.element) {
|
||||
const className = this.toClassName((node.element as ConsoleItem));
|
||||
if (className) {
|
||||
classNames.push(className);
|
||||
}
|
||||
}
|
||||
return classNames;
|
||||
}
|
||||
protected toClassName(item: ConsoleItem): string | undefined {
|
||||
if (item.severity === Severity.Error) {
|
||||
return ConsoleItem.errorClassName;
|
||||
}
|
||||
if (item.severity === Severity.Warning) {
|
||||
return ConsoleItem.warningClassName;
|
||||
}
|
||||
if (item.severity === Severity.Info) {
|
||||
return ConsoleItem.infoClassName;
|
||||
}
|
||||
if (item.severity === Severity.Log) {
|
||||
return ConsoleItem.logClassName;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
}
|
||||
143
packages/console/src/browser/console-contribution.ts
Normal file
143
packages/console/src/browser/console-contribution.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import { Command, CommandContribution, CommandRegistry, MenuContribution, MenuModelRegistry, CommandHandler } from '@theia/core';
|
||||
import { FrontendApplicationContribution, KeybindingContribution, KeybindingRegistry, CommonCommands } from '@theia/core/lib/browser';
|
||||
import { ConsoleManager } from './console-manager';
|
||||
import { ConsoleWidget } from './console-widget';
|
||||
import { ConsoleContentWidget } from './console-content-widget';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
|
||||
export namespace ConsoleCommands {
|
||||
export const SELECT_ALL: Command = {
|
||||
id: 'console.selectAll'
|
||||
};
|
||||
export const COLLAPSE_ALL: Command = {
|
||||
id: 'console.collapseAll'
|
||||
};
|
||||
export const CLEAR: Command = {
|
||||
id: 'console.clear'
|
||||
};
|
||||
export const EXECUTE: Command = {
|
||||
id: 'console.execute'
|
||||
};
|
||||
export const NAVIGATE_BACK: Command = {
|
||||
id: 'console.navigatePrevious'
|
||||
};
|
||||
export const NAVIGATE_FORWARD: Command = {
|
||||
id: 'console.navigateNext'
|
||||
};
|
||||
}
|
||||
|
||||
export namespace ConsoleContextMenu {
|
||||
export const CLIPBOARD = [...ConsoleContentWidget.CONTEXT_MENU, '1_clipboard'];
|
||||
export const CLEAR = [...ConsoleContentWidget.CONTEXT_MENU, '2_clear'];
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ConsoleContribution implements FrontendApplicationContribution, CommandContribution, KeybindingContribution, MenuContribution {
|
||||
|
||||
@inject(ConsoleManager)
|
||||
protected readonly manager: ConsoleManager;
|
||||
|
||||
initialize(): void { }
|
||||
|
||||
registerCommands(commands: CommandRegistry): void {
|
||||
commands.registerCommand(ConsoleCommands.SELECT_ALL, this.newCommandHandler(console => console.selectAll()));
|
||||
commands.registerCommand(ConsoleCommands.COLLAPSE_ALL, this.newCommandHandler(console => console.collapseAll()));
|
||||
commands.registerCommand(ConsoleCommands.CLEAR, this.newCommandHandler(console => console.clear()));
|
||||
commands.registerCommand(ConsoleCommands.EXECUTE, this.newCommandHandler(console => console.execute()));
|
||||
commands.registerCommand(ConsoleCommands.NAVIGATE_BACK, this.newCommandHandler(console => console.navigateBack()));
|
||||
commands.registerCommand(ConsoleCommands.NAVIGATE_FORWARD, this.newCommandHandler(console => console.navigateForward()));
|
||||
}
|
||||
|
||||
registerKeybindings(keybindings: KeybindingRegistry): void {
|
||||
keybindings.registerKeybinding({
|
||||
command: ConsoleCommands.SELECT_ALL.id,
|
||||
keybinding: 'ctrlcmd+a',
|
||||
when: 'consoleContentFocus'
|
||||
});
|
||||
keybindings.registerKeybinding({
|
||||
command: ConsoleCommands.EXECUTE.id,
|
||||
keybinding: 'enter',
|
||||
when: 'consoleInputFocus'
|
||||
});
|
||||
keybindings.registerKeybinding({
|
||||
command: ConsoleCommands.NAVIGATE_BACK.id,
|
||||
keybinding: 'up',
|
||||
when: 'consoleInputFocus && consoleNavigationBackEnabled'
|
||||
});
|
||||
keybindings.registerKeybinding({
|
||||
command: ConsoleCommands.NAVIGATE_FORWARD.id,
|
||||
keybinding: 'down',
|
||||
when: 'consoleInputFocus && consoleNavigationForwardEnabled'
|
||||
});
|
||||
}
|
||||
|
||||
registerMenus(menus: MenuModelRegistry): void {
|
||||
menus.registerMenuAction(ConsoleContextMenu.CLIPBOARD, {
|
||||
commandId: CommonCommands.COPY.id,
|
||||
label: CommonCommands.COPY.label,
|
||||
order: 'a1',
|
||||
});
|
||||
menus.registerMenuAction(ConsoleContextMenu.CLIPBOARD, {
|
||||
commandId: ConsoleCommands.SELECT_ALL.id,
|
||||
label: CommonCommands.SELECT_ALL.label,
|
||||
order: 'a2'
|
||||
});
|
||||
menus.registerMenuAction(ConsoleContextMenu.CLIPBOARD, {
|
||||
commandId: ConsoleCommands.COLLAPSE_ALL.id,
|
||||
label: nls.localizeByDefault('Collapse All'),
|
||||
order: 'a3'
|
||||
});
|
||||
menus.registerMenuAction(ConsoleContextMenu.CLEAR, {
|
||||
commandId: ConsoleCommands.CLEAR.id,
|
||||
label: nls.localizeByDefault('Clear Console')
|
||||
});
|
||||
}
|
||||
|
||||
protected newCommandHandler(execute: ConsoleExecuteFunction): ConsoleCommandHandler {
|
||||
return new ConsoleCommandHandler(this.manager, execute);
|
||||
}
|
||||
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type ConsoleExecuteFunction = (console: ConsoleWidget, ...args: any[]) => any;
|
||||
export class ConsoleCommandHandler implements CommandHandler {
|
||||
|
||||
constructor(
|
||||
protected readonly manager: ConsoleManager,
|
||||
protected readonly doExecute: ConsoleExecuteFunction
|
||||
) { }
|
||||
|
||||
isEnabled(): boolean {
|
||||
return !!this.manager.currentConsole;
|
||||
}
|
||||
|
||||
isVisible(): boolean {
|
||||
return !!this.manager.currentConsole;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
execute(...args: any[]): any {
|
||||
const { currentConsole } = this.manager;
|
||||
if (currentConsole) {
|
||||
return this.doExecute(currentConsole, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
32
packages/console/src/browser/console-frontend-module.ts
Normal file
32
packages/console/src/browser/console-frontend-module.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { ContainerModule } from '@theia/core/shared/inversify';
|
||||
import { CommandContribution, MenuContribution } from '@theia/core';
|
||||
import { FrontendApplicationContribution, KeybindingContribution } from '@theia/core/lib/browser';
|
||||
import { ConsoleContribution } from './console-contribution';
|
||||
import { ConsoleManager } from './console-manager';
|
||||
|
||||
import '../../src/browser/style/index.css';
|
||||
|
||||
export default new ContainerModule(bind => {
|
||||
bind(ConsoleManager).toSelf().inSingletonScope();
|
||||
bind(ConsoleContribution).toSelf().inSingletonScope();
|
||||
bind(FrontendApplicationContribution).toService(ConsoleContribution);
|
||||
bind(CommandContribution).toService(ConsoleContribution);
|
||||
bind(KeybindingContribution).toService(ConsoleContribution);
|
||||
bind(MenuContribution).toService(ConsoleContribution);
|
||||
});
|
||||
76
packages/console/src/browser/console-history.ts
Normal file
76
packages/console/src/browser/console-history.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
|
||||
@injectable()
|
||||
export class ConsoleHistory {
|
||||
|
||||
static limit = 50;
|
||||
|
||||
protected values: string[] = [];
|
||||
protected index = -1;
|
||||
|
||||
push(value: string): void {
|
||||
this.delete(value);
|
||||
this.values.push(value);
|
||||
this.trim();
|
||||
this.index = this.values.length;
|
||||
}
|
||||
protected delete(value: string): void {
|
||||
const index = this.values.indexOf(value);
|
||||
if (index !== -1) {
|
||||
this.values.splice(index, 1);
|
||||
}
|
||||
}
|
||||
protected trim(): void {
|
||||
const index = this.values.length - ConsoleHistory.limit;
|
||||
if (index > 0) {
|
||||
this.values = this.values.slice(index);
|
||||
}
|
||||
}
|
||||
|
||||
get current(): string | undefined {
|
||||
return this.values[this.index];
|
||||
}
|
||||
|
||||
get previous(): string | undefined {
|
||||
this.index = Math.max(this.index - 1, -1);
|
||||
return this.current;
|
||||
}
|
||||
|
||||
get next(): string | undefined {
|
||||
this.index = Math.min(this.index + 1, this.values.length);
|
||||
return this.current;
|
||||
}
|
||||
|
||||
store(): ConsoleHistory.Data {
|
||||
const { values, index } = this;
|
||||
return { values, index };
|
||||
}
|
||||
|
||||
restore(object: ConsoleHistory): void {
|
||||
this.values = object.values;
|
||||
this.index = object.index;
|
||||
}
|
||||
|
||||
}
|
||||
export namespace ConsoleHistory {
|
||||
export interface Data {
|
||||
values: string[],
|
||||
index: number
|
||||
}
|
||||
}
|
||||
37
packages/console/src/browser/console-manager.ts
Normal file
37
packages/console/src/browser/console-manager.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import { ApplicationShell } from '@theia/core/lib/browser';
|
||||
import { ConsoleWidget } from './console-widget';
|
||||
|
||||
@injectable()
|
||||
export class ConsoleManager {
|
||||
|
||||
@inject(ApplicationShell)
|
||||
protected readonly shell: ApplicationShell;
|
||||
|
||||
get activeConsole(): ConsoleWidget | undefined {
|
||||
const widget = this.shell.activeWidget;
|
||||
return widget instanceof ConsoleWidget ? widget : undefined;
|
||||
}
|
||||
|
||||
get currentConsole(): ConsoleWidget | undefined {
|
||||
const widget = this.shell.currentWidget;
|
||||
return widget instanceof ConsoleWidget ? widget : undefined;
|
||||
}
|
||||
|
||||
}
|
||||
121
packages/console/src/browser/console-session-manager.ts
Normal file
121
packages/console/src/browser/console-session-manager.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2021 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { Emitter, Event, Disposable, DisposableCollection } from '@theia/core';
|
||||
import { ConsoleSession } from './console-session';
|
||||
import { Severity } from '@theia/core/lib/common/severity';
|
||||
|
||||
@injectable()
|
||||
export class ConsoleSessionManager implements Disposable {
|
||||
|
||||
protected readonly sessions = new Map<string, ConsoleSession>();
|
||||
protected _selectedSession: ConsoleSession | undefined;
|
||||
protected _severity: Severity | undefined;
|
||||
|
||||
protected readonly sessionAddedEmitter = new Emitter<ConsoleSession>();
|
||||
protected readonly sessionDeletedEmitter = new Emitter<ConsoleSession>();
|
||||
protected readonly sessionWasShownEmitter = new Emitter<ConsoleSession>();
|
||||
protected readonly sessionWasHiddenEmitter = new Emitter<ConsoleSession>();
|
||||
protected readonly selectedSessionChangedEmitter = new Emitter<ConsoleSession | undefined>();
|
||||
protected readonly severityChangedEmitter = new Emitter<void>();
|
||||
|
||||
get onDidAddSession(): Event<ConsoleSession> {
|
||||
return this.sessionAddedEmitter.event;
|
||||
}
|
||||
get onDidDeleteSession(): Event<ConsoleSession> {
|
||||
return this.sessionDeletedEmitter.event;
|
||||
}
|
||||
get onDidShowSession(): Event<ConsoleSession> {
|
||||
return this.sessionWasShownEmitter.event;
|
||||
}
|
||||
get onDidHideSession(): Event<ConsoleSession> {
|
||||
return this.sessionWasHiddenEmitter.event;
|
||||
}
|
||||
get onDidChangeSelectedSession(): Event<ConsoleSession | undefined> {
|
||||
return this.selectedSessionChangedEmitter.event;
|
||||
}
|
||||
get onDidChangeSeverity(): Event<void> {
|
||||
return this.severityChangedEmitter.event;
|
||||
}
|
||||
|
||||
protected readonly toDispose = new DisposableCollection();
|
||||
protected readonly toDisposeOnSessionDeletion = new Map<string, Disposable>();
|
||||
|
||||
dispose(): void {
|
||||
this.toDispose.dispose();
|
||||
}
|
||||
|
||||
get severity(): Severity | undefined {
|
||||
return this._severity;
|
||||
}
|
||||
|
||||
set severity(value: Severity | undefined) {
|
||||
value = value || Severity.Ignore;
|
||||
this._severity = value;
|
||||
for (const session of this.sessions.values()) {
|
||||
session.severity = value;
|
||||
}
|
||||
this.severityChangedEmitter.fire(undefined);
|
||||
}
|
||||
|
||||
get all(): ConsoleSession[] {
|
||||
return Array.from(this.sessions.values());
|
||||
}
|
||||
|
||||
get selectedSession(): ConsoleSession | undefined {
|
||||
return this._selectedSession;
|
||||
}
|
||||
|
||||
set selectedSession(session: ConsoleSession | undefined) {
|
||||
const oldSession = this.selectedSession;
|
||||
this._selectedSession = session;
|
||||
this.selectedSessionChangedEmitter.fire(session);
|
||||
if (oldSession !== session) {
|
||||
if (oldSession) {
|
||||
this.sessionWasHiddenEmitter.fire(oldSession);
|
||||
}
|
||||
if (session) {
|
||||
this.sessionWasShownEmitter.fire(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get(id: string): ConsoleSession | undefined {
|
||||
return this.sessions.get(id);
|
||||
}
|
||||
|
||||
add(session: ConsoleSession): void {
|
||||
this.sessions.set(session.id, session);
|
||||
this.sessionAddedEmitter.fire(session);
|
||||
if (this.sessions.size === 1) {
|
||||
this.selectedSession = session;
|
||||
}
|
||||
}
|
||||
|
||||
delete(id: string): void {
|
||||
const session = this.sessions.get(id);
|
||||
if (this.sessions.delete(id) && session) {
|
||||
if (this.selectedSession === session) {
|
||||
// select a new sessions or undefined if none are left
|
||||
this.selectedSession = this.sessions.values().next().value;
|
||||
}
|
||||
session.dispose();
|
||||
this.sessionDeletedEmitter.fire(session);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
78
packages/console/src/browser/console-session.ts
Normal file
78
packages/console/src/browser/console-session.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { TreeSource, TreeElement, CompositeTreeElement } from '@theia/core/lib/browser/source-tree';
|
||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||
import { Severity } from '@theia/core/lib/common/severity';
|
||||
|
||||
export interface ConsoleItem extends TreeElement {
|
||||
readonly severity?: Severity;
|
||||
}
|
||||
export namespace ConsoleItem {
|
||||
export const errorClassName = 'theia-console-error';
|
||||
export const warningClassName = 'theia-console-warning';
|
||||
export const infoClassName = 'theia-console-info';
|
||||
export const logClassName = 'theia-console-log';
|
||||
}
|
||||
|
||||
export interface CompositeConsoleItem extends ConsoleItem, CompositeTreeElement {
|
||||
getElements(): MaybePromise<IterableIterator<ConsoleItem>>
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export abstract class ConsoleSession extends TreeSource {
|
||||
protected selectedSeverity?: Severity;
|
||||
protected filterTextValue?: string;
|
||||
protected readonly selectionEmitter: Emitter<void> = new Emitter<void>();
|
||||
protected readonly filterEmitter: Emitter<void> = new Emitter<void>();
|
||||
readonly onSelectionChange: Event<void> = this.selectionEmitter.event;
|
||||
readonly onFilterChange: Event<void> = this.filterEmitter.event;
|
||||
override id: string;
|
||||
|
||||
get severity(): Severity | undefined {
|
||||
return this.selectedSeverity;
|
||||
}
|
||||
|
||||
set severity(severity: Severity | undefined) {
|
||||
if (severity === this.selectedSeverity) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedSeverity = severity;
|
||||
this.selectionEmitter.fire(undefined);
|
||||
this.fireDidChange();
|
||||
}
|
||||
|
||||
get filterText(): string | undefined {
|
||||
return this.filterTextValue;
|
||||
}
|
||||
|
||||
set filterText(value: string | undefined) {
|
||||
const normalized = value?.trim() || undefined;
|
||||
if (normalized === this.filterTextValue) {
|
||||
return;
|
||||
}
|
||||
this.filterTextValue = normalized;
|
||||
this.filterEmitter.fire(undefined);
|
||||
this.fireDidChange();
|
||||
}
|
||||
|
||||
abstract override getElements(): MaybePromise<IterableIterator<ConsoleItem>>;
|
||||
abstract execute(value: string): MaybePromise<void>;
|
||||
abstract clear(): MaybePromise<void>;
|
||||
}
|
||||
351
packages/console/src/browser/console-widget.ts
Normal file
351
packages/console/src/browser/console-widget.ts
Normal file
@@ -0,0 +1,351 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { ElementExt } from '@theia/core/shared/@lumino/domutils';
|
||||
import { injectable, inject, postConstruct, interfaces, Container } from '@theia/core/shared/inversify';
|
||||
import { TreeSourceNode } from '@theia/core/lib/browser/source-tree';
|
||||
import { ContextKeyService, ContextKey } from '@theia/core/lib/browser/context-key-service';
|
||||
import { BaseWidget, PanelLayout, Widget, Message, MessageLoop, StatefulWidget, CompositeTreeNode } from '@theia/core/lib/browser';
|
||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { MonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
|
||||
import { ConsoleHistory } from './console-history';
|
||||
import { ConsoleContentWidget } from './console-content-widget';
|
||||
import { ConsoleSession } from './console-session';
|
||||
import { ConsoleSessionManager } from './console-session-manager';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { Disposable } from '@theia/core/lib/common/disposable';
|
||||
import { EditorManager } from '@theia/editor/lib/browser';
|
||||
import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service';
|
||||
|
||||
export const ConsoleOptions = Symbol('ConsoleWidgetOptions');
|
||||
export interface ConsoleOptions {
|
||||
id: string
|
||||
title?: {
|
||||
label?: string
|
||||
iconClass?: string
|
||||
caption?: string
|
||||
}
|
||||
input: {
|
||||
uri: URI
|
||||
options?: MonacoEditor.IOptions
|
||||
}
|
||||
inputFocusContextKey?: ContextKey<boolean>
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ConsoleWidget extends BaseWidget implements StatefulWidget {
|
||||
|
||||
static styles = {
|
||||
node: 'theia-console-widget',
|
||||
content: 'theia-console-content',
|
||||
input: 'theia-console-input',
|
||||
};
|
||||
|
||||
static createContainer(parent: interfaces.Container, options: ConsoleOptions): Container {
|
||||
const child = ConsoleContentWidget.createContainer(parent);
|
||||
child.bind(ConsoleHistory).toSelf();
|
||||
child.bind(ConsoleOptions).toConstantValue(options);
|
||||
child.bind(ConsoleWidget).toSelf();
|
||||
return child;
|
||||
}
|
||||
|
||||
@inject(ConsoleOptions)
|
||||
protected readonly options: ConsoleOptions;
|
||||
|
||||
@inject(ConsoleContentWidget)
|
||||
readonly content: ConsoleContentWidget;
|
||||
|
||||
@inject(ConsoleHistory)
|
||||
protected readonly history: ConsoleHistory;
|
||||
|
||||
@inject(ConsoleSessionManager)
|
||||
protected readonly sessionManager: ConsoleSessionManager;
|
||||
|
||||
@inject(MonacoEditorProvider)
|
||||
protected readonly editorProvider: MonacoEditorProvider;
|
||||
|
||||
@inject(ContextKeyService)
|
||||
protected readonly contextKeyService: ContextKeyService;
|
||||
|
||||
@inject(MonacoEditorService)
|
||||
protected readonly editorService: MonacoEditorService;
|
||||
|
||||
@inject(EditorManager)
|
||||
protected readonly editorManager: EditorManager;
|
||||
|
||||
protected _input: MonacoEditor;
|
||||
protected _inputFocusContextKey: ContextKey<boolean>;
|
||||
protected modelChangeListener = Disposable.NULL;
|
||||
|
||||
protected _ready: Promise<void> | undefined;
|
||||
get ready(): Promise<void> {
|
||||
if (!this._ready) {
|
||||
throw new Error('ready must not be accessed in the construction phase');
|
||||
}
|
||||
return this._ready;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.node.classList.add(ConsoleWidget.styles.node);
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this._ready = this.doInit();
|
||||
}
|
||||
|
||||
protected async doInit(): Promise<void> {
|
||||
const { id, title, inputFocusContextKey } = this.options;
|
||||
const { label, iconClass, caption } = Object.assign({}, title);
|
||||
this.id = id;
|
||||
this.title.closable = true;
|
||||
this.title.label = label || id;
|
||||
if (iconClass) {
|
||||
this.title.iconClass = iconClass;
|
||||
}
|
||||
this.title.caption = caption || label || id;
|
||||
|
||||
const layout = this.layout = new PanelLayout();
|
||||
|
||||
this.content.node.classList.add(ConsoleWidget.styles.content);
|
||||
this.toDispose.push(this.content);
|
||||
layout.addWidget(this.content);
|
||||
|
||||
const inputWidget = new Widget();
|
||||
inputWidget.node.classList.add(ConsoleWidget.styles.input);
|
||||
layout.addWidget(inputWidget);
|
||||
|
||||
const input = this._input = await this.createInput(inputWidget.node);
|
||||
this.toDispose.push(input);
|
||||
this.toDispose.push(input.getControl().onDidLayoutChange(() => this.resizeContent()));
|
||||
|
||||
this.toDispose.push(input.getControl().onDidChangeConfiguration(event => {
|
||||
if (event.hasChanged(monaco.editor.EditorOption.fontInfo)) {
|
||||
this.updateFont();
|
||||
}
|
||||
}));
|
||||
|
||||
this.session = this.sessionManager.selectedSession;
|
||||
this.toDispose.push(this.sessionManager.onDidChangeSelectedSession(session => {
|
||||
// Do not clear the session output when `undefined`.
|
||||
if (session) {
|
||||
this.session = session;
|
||||
}
|
||||
}));
|
||||
|
||||
this.updateFont();
|
||||
if (inputFocusContextKey) {
|
||||
this.toDispose.push(input.onFocusChanged(() => inputFocusContextKey.set(this.hasInputFocus())));
|
||||
this.toDispose.push(input.onCursorPositionChanged(() => input.getControl().createContextKey('consoleNavigationBackEnabled', this.consoleNavigationBackEnabled)));
|
||||
this.toDispose.push(input.onCursorPositionChanged(() => input.getControl().createContextKey('consoleNavigationForwardEnabled', this.consoleNavigationForwardEnabled)));
|
||||
}
|
||||
input.getControl().createContextKey('consoleInputFocus', true);
|
||||
const contentContext = this.contextKeyService.createScoped(this.content.node);
|
||||
contentContext.setContext('consoleContentFocus', true);
|
||||
|
||||
this.toDispose.pushAll([
|
||||
this.editorManager.onActiveEditorChanged(() => this.setMode()),
|
||||
this.onDidChangeVisibility(() => this.setMode())
|
||||
]);
|
||||
}
|
||||
|
||||
protected createInput(node: HTMLElement): Promise<MonacoEditor> {
|
||||
return this.editorProvider.createInline(this.options.input.uri, node, this.options.input.options);
|
||||
}
|
||||
|
||||
protected updateFont(): void {
|
||||
const { fontFamily, fontSize, lineHeight } = this._input.getControl().getOption(monaco.editor.EditorOption.fontInfo);
|
||||
this.content.node.style.fontFamily = fontFamily;
|
||||
this.content.node.style.fontSize = fontSize + 'px';
|
||||
this.content.node.style.lineHeight = lineHeight + 'px';
|
||||
}
|
||||
|
||||
protected _session: ConsoleSession | undefined;
|
||||
set session(session: ConsoleSession | undefined) {
|
||||
if (this._session === session) {
|
||||
return;
|
||||
}
|
||||
this._session = session;
|
||||
this.content.source = session;
|
||||
}
|
||||
get session(): ConsoleSession | undefined {
|
||||
return this._session;
|
||||
}
|
||||
|
||||
get input(): MonacoEditor {
|
||||
return this._input;
|
||||
}
|
||||
|
||||
get consoleNavigationBackEnabled(): boolean {
|
||||
const editor = this.input.getControl();
|
||||
return !!editor.getPosition()!.equals({ lineNumber: 1, column: 1 });
|
||||
}
|
||||
|
||||
get consoleNavigationForwardEnabled(): boolean {
|
||||
const editor = this.input.getControl();
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return false;
|
||||
}
|
||||
const lineNumber = editor.getModel()!.getLineCount();
|
||||
const column = editor.getModel()!.getLineMaxColumn(lineNumber);
|
||||
return !!editor.getPosition()!.equals({ lineNumber, column });
|
||||
}
|
||||
|
||||
selectAll(): void {
|
||||
const selection = document.getSelection();
|
||||
if (selection) {
|
||||
selection.selectAllChildren(this.content.node);
|
||||
}
|
||||
}
|
||||
|
||||
collapseAll(): void {
|
||||
const { root } = this.content.model;
|
||||
if (CompositeTreeNode.is(root)) {
|
||||
this.content.model.collapseAll(root);
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
if (this.session) {
|
||||
this.session.clear();
|
||||
}
|
||||
}
|
||||
|
||||
async execute(value?: string): Promise<void> {
|
||||
if (value === undefined) {
|
||||
value = this._input.getControl().getValue();
|
||||
this._input.getControl().setValue('');
|
||||
}
|
||||
this.history.push(value);
|
||||
if (this.session) {
|
||||
const listener = this.content.model.onNodeRefreshed(() => {
|
||||
listener.dispose();
|
||||
this.revealLastOutput();
|
||||
});
|
||||
await this.session.execute(value);
|
||||
}
|
||||
}
|
||||
|
||||
navigateBack(): void {
|
||||
const value = this.history.previous;
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
const editor = this.input.getControl();
|
||||
editor.setValue(value);
|
||||
editor.setPosition({
|
||||
lineNumber: 1,
|
||||
column: 1
|
||||
});
|
||||
}
|
||||
|
||||
navigateForward(): void {
|
||||
const value = this.history.next || '';
|
||||
const editor = this.input.getControl();
|
||||
editor.setValue(value);
|
||||
const lineNumber = editor.getModel()!.getLineCount();
|
||||
const column = editor.getModel()!.getLineMaxColumn(lineNumber);
|
||||
editor.setPosition({ lineNumber, column });
|
||||
}
|
||||
|
||||
protected revealLastOutput(): void {
|
||||
const { root } = this.content.model;
|
||||
if (TreeSourceNode.is(root)) {
|
||||
this.content.model.selectNode(root.children[root.children.length - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
protected override onActivateRequest(msg: Message): void {
|
||||
super.onActivateRequest(msg);
|
||||
this._input.focus();
|
||||
}
|
||||
|
||||
protected totalHeight = -1;
|
||||
protected totalWidth = -1;
|
||||
protected override onResize(msg: Widget.ResizeMessage): void {
|
||||
super.onResize(msg);
|
||||
this.totalWidth = msg.width;
|
||||
this.totalHeight = msg.height;
|
||||
this._input.resizeToFit();
|
||||
this.resizeContent();
|
||||
}
|
||||
|
||||
protected resizeContent(): void {
|
||||
this.totalHeight = this.totalHeight < 0 ? this.computeHeight() : this.totalHeight;
|
||||
const inputHeight = this._input.getControl().getLayoutInfo().height;
|
||||
const contentHeight = this.totalHeight - inputHeight;
|
||||
this.content.node.style.height = `${contentHeight}px`;
|
||||
MessageLoop.sendMessage(this.content, new Widget.ResizeMessage(this.totalWidth, contentHeight));
|
||||
}
|
||||
|
||||
protected computeHeight(): number {
|
||||
const { verticalSum } = ElementExt.boxSizing(this.node);
|
||||
return this.node.offsetHeight - verticalSum;
|
||||
}
|
||||
|
||||
storeState(): object {
|
||||
const history = this.history.store();
|
||||
const input = this.input.storeViewState();
|
||||
return {
|
||||
history,
|
||||
input
|
||||
};
|
||||
}
|
||||
|
||||
restoreState(oldState: object): void {
|
||||
if ('history' in oldState) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
this.history.restore((<any>oldState)['history']);
|
||||
}
|
||||
this.input.getControl().setValue(this.history.current || '');
|
||||
if ('input' in oldState) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
this.input.restoreViewState((<any>oldState)['input']);
|
||||
}
|
||||
}
|
||||
|
||||
hasInputFocus(): boolean {
|
||||
return this._input && this._input.isFocused({ strict: true });
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
super.dispose();
|
||||
this.modelChangeListener.dispose();
|
||||
}
|
||||
|
||||
// To set the active language for the console input text model.
|
||||
// https://github.com/microsoft/vscode/blob/2af422737386e792c3fcde7884f9bf47a1aff2f5/src/vs/workbench/contrib/debug/browser/repl.ts#L371-L384
|
||||
protected setMode(): void {
|
||||
if (this.isHidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeEditorControl = this.editorService.getActiveCodeEditor();
|
||||
if (activeEditorControl) {
|
||||
this.modelChangeListener.dispose();
|
||||
this.modelChangeListener = activeEditorControl.onDidChangeModelLanguage(() => this.setMode());
|
||||
const consoleModel = this._input.getControl().getModel();
|
||||
const activeEditorModel = activeEditorControl.getModel();
|
||||
if (consoleModel && activeEditorModel) {
|
||||
monaco.editor.setModelLanguage(consoleModel, activeEditorModel.getLanguageId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
49
packages/console/src/browser/style/index.css
Normal file
49
packages/console/src/browser/style/index.css
Normal file
@@ -0,0 +1,49 @@
|
||||
/********************************************************************************
|
||||
* Copyright (C) 2018 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the Eclipse
|
||||
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
* with the GNU Classpath Exception which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
********************************************************************************/
|
||||
|
||||
.theia-console-content {
|
||||
font-size: var(--theia-code-font-size);
|
||||
line-height: var(--theia-code-line-height);
|
||||
font-family: var(--theia-code-font-family);
|
||||
}
|
||||
|
||||
.theia-console-input {
|
||||
padding-left: 20px;
|
||||
border-top: var(--theia-panel-border-width) solid var(--theia-panel-border);
|
||||
height: calc(var(--theia-content-line-height) * 2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.theia-console-input:before {
|
||||
left: 8px;
|
||||
top: 3px;
|
||||
position: absolute;
|
||||
content: "\276f";
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.theia-console-error {
|
||||
color: var(--theia-errorForeground);
|
||||
}
|
||||
|
||||
.theia-console-warning {
|
||||
color: var(--theia-editorWarning-foreground);
|
||||
}
|
||||
|
||||
.theia-console-ansi-console-item {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
28
packages/console/src/package.spec.ts
Normal file
28
packages/console/src/package.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2018 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
/* note: this bogus test file is required so that
|
||||
we are able to run mocha unit tests on this
|
||||
package, without having any actual unit tests in it.
|
||||
This way a coverage report will be generated,
|
||||
showing 0% coverage, instead of no report.
|
||||
This file can be removed once we have real unit
|
||||
tests in place. */
|
||||
|
||||
describe('console package', () => {
|
||||
|
||||
it('support code coverage statistics', () => true);
|
||||
});
|
||||
Reference in New Issue
Block a user