mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-27 15:20:30 -04:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
414
src/vs/workbench/parts/debug/electron-browser/repl.ts
Normal file
414
src/vs/workbench/parts/debug/electron-browser/repl.ts
Normal file
@@ -0,0 +1,414 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!vs/workbench/parts/debug/browser/media/repl';
|
||||
import * as nls from 'vs/nls';
|
||||
import uri from 'vs/base/common/uri';
|
||||
import { wireCancellationToken } from 'vs/base/common/async';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ITree, ITreeOptions } from 'vs/base/parts/tree/browser/tree';
|
||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||
import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/suggest';
|
||||
import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController';
|
||||
import { IReadOnlyModel, ICommonCodeEditor } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { editorAction, ServicesAccessor, EditorAction, EditorCommand, CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { ReplExpressionsRenderer, ReplExpressionsController, ReplExpressionsDataSource, ReplExpressionsActionProvider, ReplExpressionsAccessibilityProvider } from 'vs/workbench/parts/debug/electron-browser/replViewer';
|
||||
import { ReplInputEditor } from 'vs/workbench/parts/debug/electron-browser/replEditor';
|
||||
import * as debug from 'vs/workbench/parts/debug/common/debug';
|
||||
import { ClearReplAction } from 'vs/workbench/parts/debug/browser/debugActions';
|
||||
import { ReplHistory } from 'vs/workbench/parts/debug/common/replHistory';
|
||||
import { Panel } from 'vs/workbench/browser/panel';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IListService } from 'vs/platform/list/browser/listService';
|
||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { clipboard } from 'electron';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
const replTreeOptions: ITreeOptions = {
|
||||
twistiePixels: 20,
|
||||
ariaLabel: nls.localize('replAriaLabel', "Read Eval Print Loop Panel"),
|
||||
keyboardSupport: false
|
||||
};
|
||||
|
||||
const HISTORY_STORAGE_KEY = 'debug.repl.history';
|
||||
const IPrivateReplService = createDecorator<IPrivateReplService>('privateReplService');
|
||||
|
||||
export interface IPrivateReplService {
|
||||
_serviceBrand: any;
|
||||
navigateHistory(previous: boolean): void;
|
||||
acceptReplInput(): void;
|
||||
getVisibleContent(): string;
|
||||
}
|
||||
|
||||
export class Repl extends Panel implements IPrivateReplService {
|
||||
public _serviceBrand: any;
|
||||
|
||||
private static HALF_WIDTH_TYPICAL = 'n';
|
||||
|
||||
private static HISTORY: ReplHistory;
|
||||
private static REFRESH_DELAY = 500; // delay in ms to refresh the repl for new elements to show
|
||||
private static REPL_INPUT_INITIAL_HEIGHT = 19;
|
||||
private static REPL_INPUT_MAX_HEIGHT = 170;
|
||||
|
||||
private tree: ITree;
|
||||
private renderer: ReplExpressionsRenderer;
|
||||
private characterWidthSurveyor: HTMLElement;
|
||||
private treeContainer: HTMLElement;
|
||||
private replInput: ReplInputEditor;
|
||||
private replInputContainer: HTMLElement;
|
||||
private refreshTimeoutHandle: number;
|
||||
private actions: IAction[];
|
||||
private dimension: Dimension;
|
||||
private replInputHeight: number;
|
||||
|
||||
constructor(
|
||||
@debug.IDebugService private debugService: debug.IDebugService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@IPanelService private panelService: IPanelService,
|
||||
@IThemeService protected themeService: IThemeService,
|
||||
@IModelService private modelService: IModelService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IListService private listService: IListService
|
||||
) {
|
||||
super(debug.REPL_ID, telemetryService, themeService);
|
||||
|
||||
this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT;
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toUnbind.push(this.debugService.getModel().onDidChangeReplElements(() => {
|
||||
this.refreshReplElements(this.debugService.getModel().getReplElements().length === 0);
|
||||
}));
|
||||
this.toUnbind.push(this.panelService.onDidPanelOpen(panel => this.refreshReplElements(true)));
|
||||
}
|
||||
|
||||
private refreshReplElements(noDelay: boolean): void {
|
||||
if (this.tree && this.isVisible()) {
|
||||
if (this.refreshTimeoutHandle) {
|
||||
return; // refresh already triggered
|
||||
}
|
||||
|
||||
const delay = noDelay ? 0 : Repl.REFRESH_DELAY;
|
||||
this.refreshTimeoutHandle = setTimeout(() => {
|
||||
this.refreshTimeoutHandle = null;
|
||||
const previousScrollPosition = this.tree.getScrollPosition();
|
||||
this.tree.refresh().then(() => {
|
||||
if (previousScrollPosition === 1 || previousScrollPosition === 0) {
|
||||
// Only scroll if we were scrolled all the way down before tree refreshed #10486
|
||||
this.tree.setScrollPosition(1);
|
||||
}
|
||||
}, errors.onUnexpectedError);
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
|
||||
public create(parent: Builder): TPromise<void> {
|
||||
super.create(parent);
|
||||
const container = dom.append(parent.getHTMLElement(), $('.repl'));
|
||||
this.treeContainer = dom.append(container, $('.repl-tree'));
|
||||
this.createReplInput(container);
|
||||
|
||||
this.characterWidthSurveyor = dom.append(container, $('.surveyor'));
|
||||
this.characterWidthSurveyor.textContent = Repl.HALF_WIDTH_TYPICAL;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
this.characterWidthSurveyor.textContent += this.characterWidthSurveyor.textContent;
|
||||
}
|
||||
this.characterWidthSurveyor.style.fontSize = isMacintosh ? '12px' : '14px';
|
||||
|
||||
this.renderer = this.instantiationService.createInstance(ReplExpressionsRenderer);
|
||||
const controller = this.instantiationService.createInstance(ReplExpressionsController, new ReplExpressionsActionProvider(this.instantiationService), MenuId.DebugConsoleContext);
|
||||
controller.toFocusOnClick = this.replInput;
|
||||
|
||||
this.tree = new Tree(this.treeContainer, {
|
||||
dataSource: new ReplExpressionsDataSource(),
|
||||
renderer: this.renderer,
|
||||
accessibilityProvider: new ReplExpressionsAccessibilityProvider(),
|
||||
controller
|
||||
}, replTreeOptions);
|
||||
|
||||
this.toUnbind.push(attachListStyler(this.tree, this.themeService));
|
||||
this.toUnbind.push(this.listService.register(this.tree));
|
||||
|
||||
if (!Repl.HISTORY) {
|
||||
Repl.HISTORY = new ReplHistory(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')));
|
||||
}
|
||||
|
||||
return this.tree.setInput(this.debugService.getModel());
|
||||
}
|
||||
|
||||
private createReplInput(container: HTMLElement): void {
|
||||
this.replInputContainer = dom.append(container, $('.repl-input-wrapper'));
|
||||
|
||||
const scopedContextKeyService = this.contextKeyService.createScoped(this.replInputContainer);
|
||||
this.toUnbind.push(scopedContextKeyService);
|
||||
debug.CONTEXT_IN_DEBUG_REPL.bindTo(scopedContextKeyService).set(true);
|
||||
const onFirstReplLine = debug.CONTEXT_ON_FIRST_DEBUG_REPL_LINE.bindTo(scopedContextKeyService);
|
||||
onFirstReplLine.set(true);
|
||||
const onLastReplLine = debug.CONTEXT_ON_LAST_DEBUG_REPL_LINE.bindTo(scopedContextKeyService);
|
||||
onLastReplLine.set(true);
|
||||
|
||||
const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection(
|
||||
[IContextKeyService, scopedContextKeyService], [IPrivateReplService, this]));
|
||||
this.replInput = scopedInstantiationService.createInstance(ReplInputEditor, this.replInputContainer, this.getReplInputOptions());
|
||||
const model = this.modelService.createModel('', null, uri.parse(`${debug.DEBUG_SCHEME}:input`));
|
||||
this.replInput.setModel(model);
|
||||
|
||||
modes.SuggestRegistry.register({ scheme: debug.DEBUG_SCHEME }, {
|
||||
triggerCharacters: ['.'],
|
||||
provideCompletionItems: (model: IReadOnlyModel, position: Position, token: CancellationToken): Thenable<modes.ISuggestResult> => {
|
||||
const word = this.replInput.getModel().getWordAtPosition(position);
|
||||
const overwriteBefore = word ? word.word.length : 0;
|
||||
const text = this.replInput.getModel().getLineContent(position.lineNumber);
|
||||
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
|
||||
const frameId = focusedStackFrame ? focusedStackFrame.frameId : undefined;
|
||||
const focusedProcess = this.debugService.getViewModel().focusedProcess;
|
||||
const completions = focusedProcess ? focusedProcess.completions(frameId, text, position, overwriteBefore) : TPromise.as([]);
|
||||
return wireCancellationToken(token, completions.then(suggestions => ({
|
||||
suggestions
|
||||
})));
|
||||
}
|
||||
});
|
||||
|
||||
this.toUnbind.push(this.replInput.onDidScrollChange(e => {
|
||||
if (!e.scrollHeightChanged) {
|
||||
return;
|
||||
}
|
||||
this.replInputHeight = Math.max(Repl.REPL_INPUT_INITIAL_HEIGHT, Math.min(Repl.REPL_INPUT_MAX_HEIGHT, e.scrollHeight, this.dimension.height));
|
||||
this.layout(this.dimension);
|
||||
}));
|
||||
this.toUnbind.push(this.replInput.onDidChangeCursorPosition(e => {
|
||||
onFirstReplLine.set(e.position.lineNumber === 1);
|
||||
onLastReplLine.set(e.position.lineNumber === this.replInput.getModel().getLineCount());
|
||||
}));
|
||||
|
||||
this.toUnbind.push(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.FOCUS, () => dom.addClass(this.replInputContainer, 'synthetic-focus')));
|
||||
this.toUnbind.push(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.BLUR, () => dom.removeClass(this.replInputContainer, 'synthetic-focus')));
|
||||
}
|
||||
|
||||
public navigateHistory(previous: boolean): void {
|
||||
const historyInput = previous ? Repl.HISTORY.previous() : Repl.HISTORY.next();
|
||||
if (historyInput) {
|
||||
Repl.HISTORY.remember(this.replInput.getValue(), previous);
|
||||
this.replInput.setValue(historyInput);
|
||||
// always leave cursor at the end.
|
||||
this.replInput.setPosition({ lineNumber: 1, column: historyInput.length + 1 });
|
||||
}
|
||||
}
|
||||
|
||||
public acceptReplInput(): void {
|
||||
this.debugService.addReplExpression(this.replInput.getValue());
|
||||
Repl.HISTORY.evaluated(this.replInput.getValue());
|
||||
this.replInput.setValue('');
|
||||
// Trigger a layout to shrink a potential multi line input
|
||||
this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT;
|
||||
this.layout(this.dimension);
|
||||
}
|
||||
|
||||
public getVisibleContent(): string {
|
||||
let text = '';
|
||||
const navigator = this.tree.getNavigator();
|
||||
// skip first navigator element - the root node
|
||||
while (navigator.next()) {
|
||||
if (text) {
|
||||
text += `\n`;
|
||||
}
|
||||
text += navigator.current().toString();
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
this.dimension = dimension;
|
||||
if (this.tree) {
|
||||
this.renderer.setWidth(dimension.width - 25, this.characterWidthSurveyor.clientWidth / this.characterWidthSurveyor.textContent.length);
|
||||
const treeHeight = dimension.height - this.replInputHeight;
|
||||
this.treeContainer.style.height = `${treeHeight}px`;
|
||||
this.tree.layout(treeHeight);
|
||||
}
|
||||
this.replInputContainer.style.height = `${this.replInputHeight}px`;
|
||||
|
||||
this.replInput.layout({ width: dimension.width - 20, height: this.replInputHeight });
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.replInput.focus();
|
||||
}
|
||||
|
||||
public getActions(): IAction[] {
|
||||
if (!this.actions) {
|
||||
this.actions = [
|
||||
this.instantiationService.createInstance(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL)
|
||||
];
|
||||
|
||||
this.actions.forEach(a => {
|
||||
this.toUnbind.push(a);
|
||||
});
|
||||
}
|
||||
|
||||
return this.actions;
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
const replHistory = Repl.HISTORY.save();
|
||||
if (replHistory.length) {
|
||||
this.storageService.store(HISTORY_STORAGE_KEY, JSON.stringify(replHistory), StorageScope.WORKSPACE);
|
||||
} else {
|
||||
this.storageService.remove(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
}
|
||||
}
|
||||
|
||||
private getReplInputOptions(): IEditorOptions {
|
||||
return {
|
||||
wordWrap: 'on',
|
||||
overviewRulerLanes: 0,
|
||||
glyphMargin: false,
|
||||
lineNumbers: 'off',
|
||||
folding: false,
|
||||
selectOnLineNumbers: false,
|
||||
selectionHighlight: false,
|
||||
scrollbar: {
|
||||
horizontal: 'hidden'
|
||||
},
|
||||
lineDecorationsWidth: 0,
|
||||
scrollBeyondLastLine: false,
|
||||
renderLineHighlight: 'none',
|
||||
fixedOverflowWidgets: true,
|
||||
acceptSuggestionOnEnter: 'smart',
|
||||
minimap: {
|
||||
enabled: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.replInput.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
class ReplHistoryPreviousAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'repl.action.historyPrevious',
|
||||
label: nls.localize('actions.repl.historyPrevious', "History Previous"),
|
||||
alias: 'History Previous',
|
||||
precondition: debug.CONTEXT_IN_DEBUG_REPL,
|
||||
kbOpts: {
|
||||
kbExpr: ContextKeyExpr.and(EditorContextKeys.textFocus, debug.CONTEXT_ON_FIRST_DEBUG_REPL_LINE),
|
||||
primary: KeyCode.UpArrow,
|
||||
weight: 50
|
||||
},
|
||||
menuOpts: {
|
||||
group: 'debug'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void | TPromise<void> {
|
||||
accessor.get(IPrivateReplService).navigateHistory(true);
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
class ReplHistoryNextAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'repl.action.historyNext',
|
||||
label: nls.localize('actions.repl.historyNext', "History Next"),
|
||||
alias: 'History Next',
|
||||
precondition: debug.CONTEXT_IN_DEBUG_REPL,
|
||||
kbOpts: {
|
||||
kbExpr: ContextKeyExpr.and(EditorContextKeys.textFocus, debug.CONTEXT_ON_LAST_DEBUG_REPL_LINE),
|
||||
primary: KeyCode.DownArrow,
|
||||
weight: 50
|
||||
},
|
||||
menuOpts: {
|
||||
group: 'debug'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void | TPromise<void> {
|
||||
accessor.get(IPrivateReplService).navigateHistory(false);
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
class AcceptReplInputAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'repl.action.acceptInput',
|
||||
label: nls.localize({ key: 'actions.repl.acceptInput', comment: ['Apply input from the debug console input box'] }, "REPL Accept Input"),
|
||||
alias: 'REPL Accept Input',
|
||||
precondition: debug.CONTEXT_IN_DEBUG_REPL,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.textFocus,
|
||||
primary: KeyCode.Enter
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void | TPromise<void> {
|
||||
SuggestController.get(editor).acceptSelectedSuggestion();
|
||||
accessor.get(IPrivateReplService).acceptReplInput();
|
||||
}
|
||||
}
|
||||
|
||||
const SuggestCommand = EditorCommand.bindToContribution<SuggestController>(SuggestController.get);
|
||||
CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
|
||||
id: 'repl.action.acceptSuggestion',
|
||||
precondition: ContextKeyExpr.and(debug.CONTEXT_IN_DEBUG_REPL, SuggestContext.Visible),
|
||||
handler: x => x.acceptSelectedSuggestion(),
|
||||
kbOpts: {
|
||||
weight: 50,
|
||||
kbExpr: EditorContextKeys.textFocus,
|
||||
primary: KeyCode.RightArrow
|
||||
}
|
||||
}));
|
||||
|
||||
@editorAction
|
||||
export class ReplCopyAllAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'repl.action.copyAll',
|
||||
label: nls.localize('actions.repl.copyAll', "Debug: Console Copy All"),
|
||||
alias: 'Debug Console Copy All',
|
||||
precondition: debug.CONTEXT_IN_DEBUG_REPL,
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void | TPromise<void> {
|
||||
clipboard.writeText(accessor.get(IPrivateReplService).getVisibleContent());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user