SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor-hover {
cursor: default;
position: absolute;
overflow: hidden;
z-index: 50;
-webkit-user-select: text;
-ms-user-select: text;
-khtml-user-select: text;
-moz-user-select: text;
-o-user-select: text;
user-select: text;
box-sizing: initial;
animation: fadein 100ms linear;
line-height: 1.5em;
}
.monaco-editor-hover.hidden {
display: none;
}
.monaco-editor-hover .monaco-editor-hover-content {
max-width: 500px;
}
/*
* https://github.com/Microsoft/monaco-editor/issues/417
* Safari 10.1, fails inherit correct visibility from parent when we change the visibility of parent element from hidden to inherit, in this particular case.
*/
.monaco-editor-hover .monaco-scrollable-element {
visibility: visible;
}
.monaco-editor-hover .hover-row {
padding: 4px 5px;
}
.monaco-editor-hover p,
.monaco-editor-hover ul {
margin: 8px 0;
}
.monaco-editor-hover p:first-child,
.monaco-editor-hover ul:first-child {
margin-top: 0;
}
.monaco-editor-hover p:last-child,
.monaco-editor-hover ul:last-child {
margin-bottom: 0;
}
.monaco-editor-hover ul {
padding-left: 20px;
}
.monaco-editor-hover li > p {
margin-bottom: 0;
}
.monaco-editor-hover li > ul {
margin-top: 0;
}
.monaco-editor-hover code {
border-radius: 3px;
padding: 0 0.4em;
}
.monaco-editor-hover .monaco-tokenized-source {
white-space: pre-wrap;
word-break: break-all;
}

View File

@@ -0,0 +1,222 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./hover';
import * as nls from 'vs/nls';
import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes';
import * as platform from 'vs/base/common/platform';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IModeService } from 'vs/editor/common/services/modeService';
import { Range } from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { editorAction, ServicesAccessor, EditorAction } from 'vs/editor/common/editorCommonExtensions';
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions';
import { ModesContentHoverWidget } from './modesContentHover';
import { ModesGlyphHoverWidget } from './modesGlyphHover';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { editorHoverHighlight, editorHoverBackground, editorHoverBorder, textLinkForeground, textCodeBlockBackground } from 'vs/platform/theme/common/colorRegistry';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
@editorContribution
export class ModesHoverController implements editorCommon.IEditorContribution {
private static ID = 'editor.contrib.hover';
private _editor: ICodeEditor;
private _toUnhook: IDisposable[];
private _contentWidget: ModesContentHoverWidget;
private _glyphWidget: ModesGlyphHoverWidget;
private _isMouseDown: boolean;
private _hoverClicked: boolean;
static get(editor: editorCommon.ICommonCodeEditor): ModesHoverController {
return editor.getContribution<ModesHoverController>(ModesHoverController.ID);
}
constructor(editor: ICodeEditor,
@IOpenerService openerService: IOpenerService,
@IModeService modeService: IModeService
) {
this._editor = editor;
this._toUnhook = [];
this._isMouseDown = false;
if (editor.getConfiguration().contribInfo.hover) {
this._toUnhook.push(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e)));
this._toUnhook.push(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e)));
this._toUnhook.push(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e)));
this._toUnhook.push(this._editor.onMouseLeave((e: IEditorMouseEvent) => this._hideWidgets()));
this._toUnhook.push(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e)));
this._toUnhook.push(this._editor.onDidChangeModel(() => this._hideWidgets()));
this._toUnhook.push(this._editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged()));
this._toUnhook.push(this._editor.onDidScrollChange((e) => {
if (e.scrollTopChanged || e.scrollLeftChanged) {
this._hideWidgets();
}
}));
this._contentWidget = new ModesContentHoverWidget(editor, openerService, modeService);
this._glyphWidget = new ModesGlyphHoverWidget(editor, openerService, modeService);
}
}
private _onModelDecorationsChanged(): void {
this._contentWidget.onModelDecorationsChanged();
this._glyphWidget.onModelDecorationsChanged();
}
private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void {
this._isMouseDown = true;
var targetType = mouseEvent.target.type;
if (targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ModesContentHoverWidget.ID) {
this._hoverClicked = true;
// mouse down on top of content hover widget
return;
}
if (targetType === MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail === ModesGlyphHoverWidget.ID) {
// mouse down on top of overlay hover widget
return;
}
if (targetType !== MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail !== ModesGlyphHoverWidget.ID) {
this._hoverClicked = false;
}
this._hideWidgets();
}
private _onEditorMouseUp(mouseEvent: IEditorMouseEvent): void {
this._isMouseDown = false;
}
private _onEditorMouseMove(mouseEvent: IEditorMouseEvent): void {
var targetType = mouseEvent.target.type;
var stopKey = platform.isMacintosh ? 'metaKey' : 'ctrlKey';
if (this._isMouseDown && this._hoverClicked && this._contentWidget.isColorPickerVisible()) {
return;
}
if (targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === ModesContentHoverWidget.ID && !mouseEvent.event[stopKey]) {
// mouse moved on top of content hover widget
return;
}
if (targetType === MouseTargetType.OVERLAY_WIDGET && mouseEvent.target.detail === ModesGlyphHoverWidget.ID && !mouseEvent.event[stopKey]) {
// mouse moved on top of overlay hover widget
return;
}
if (this._editor.getConfiguration().contribInfo.hover && targetType === MouseTargetType.CONTENT_TEXT) {
this._glyphWidget.hide();
this._contentWidget.startShowingAt(mouseEvent.target.range, false);
} else if (targetType === MouseTargetType.GUTTER_GLYPH_MARGIN) {
this._contentWidget.hide();
this._glyphWidget.startShowingAt(mouseEvent.target.position.lineNumber);
} else {
this._hideWidgets();
}
}
private _onKeyDown(e: IKeyboardEvent): void {
if (e.keyCode !== KeyCode.Ctrl && e.keyCode !== KeyCode.Alt && e.keyCode !== KeyCode.Meta) {
// Do not hide hover when Ctrl/Meta is pressed
this._hideWidgets();
}
}
private _hideWidgets(): void {
if (this._isMouseDown && this._hoverClicked && this._contentWidget.isColorPickerVisible()) {
return;
}
this._glyphWidget.hide();
this._contentWidget.hide();
}
public showContentHover(range: Range, focus: boolean): void {
this._contentWidget.startShowingAt(range, focus);
}
public getId(): string {
return ModesHoverController.ID;
}
public dispose(): void {
this._toUnhook = dispose(this._toUnhook);
if (this._glyphWidget) {
this._glyphWidget.dispose();
this._glyphWidget = null;
}
if (this._contentWidget) {
this._contentWidget.dispose();
this._contentWidget = null;
}
}
}
@editorAction
class ShowHoverAction extends EditorAction {
constructor() {
super({
id: 'editor.action.showHover',
label: nls.localize('showHover', "Show Hover"),
alias: 'Show Hover',
precondition: null,
kbOpts: {
kbExpr: EditorContextKeys.textFocus,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_I)
}
});
}
public run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor): void {
let controller = ModesHoverController.get(editor);
if (!controller) {
return;
}
const position = editor.getPosition();
const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column);
controller.showContentHover(range, true);
}
}
// theming
registerThemingParticipant((theme, collector) => {
let editorHoverHighlightColor = theme.getColor(editorHoverHighlight);
if (editorHoverHighlightColor) {
collector.addRule(`.monaco-editor .hoverHighlight { background-color: ${editorHoverHighlightColor}; }`);
}
let hoverBackground = theme.getColor(editorHoverBackground);
if (hoverBackground) {
collector.addRule(`.monaco-editor .monaco-editor-hover { background-color: ${hoverBackground}; }`);
}
let hoverBorder = theme.getColor(editorHoverBorder);
if (hoverBorder) {
collector.addRule(`.monaco-editor .monaco-editor-hover { border: 1px solid ${hoverBorder}; }`);
collector.addRule(`.monaco-editor .monaco-editor-hover .hover-row:not(:first-child):not(:empty) { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`);
}
let link = theme.getColor(textLinkForeground);
if (link) {
collector.addRule(`.monaco-editor .monaco-editor-hover a { color: ${link}; }`);
}
let codeBackground = theme.getColor(textCodeBlockBackground);
if (codeBackground) {
collector.addRule(`.monaco-editor .monaco-editor-hover code { background-color: ${codeBackground}; }`);
}
});

View File

@@ -0,0 +1,189 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { RunOnceScheduler } from 'vs/base/common/async';
import { onUnexpectedError } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
export interface IHoverComputer<Result> {
/**
* Overwrite the default hover time
*/
getHoverTimeMillis?: () => number;
/**
* This is called after half the hover time
*/
computeAsync?: () => TPromise<Result>;
/**
* This is called after all the hover time
*/
computeSync?: () => Result;
/**
* This is called whenever one of the compute* methods returns a truey value
*/
onResult: (result: Result, isFromSynchronousComputation: boolean) => void;
/**
* This is what will be sent as progress/complete to the computation promise
*/
getResult: () => Result;
getResultWithLoadingMessage: () => Result;
}
const enum ComputeHoverOperationState {
IDLE = 0,
FIRST_WAIT = 1,
SECOND_WAIT = 2,
WAITING_FOR_ASYNC_COMPUTATION = 3
}
export class HoverOperation<Result> {
static HOVER_TIME = 300;
private _computer: IHoverComputer<Result>;
private _state: ComputeHoverOperationState;
private _firstWaitScheduler: RunOnceScheduler;
private _secondWaitScheduler: RunOnceScheduler;
private _loadingMessageScheduler: RunOnceScheduler;
private _asyncComputationPromise: TPromise<void>;
private _asyncComputationPromiseDone: boolean;
private _completeCallback: (r: Result) => void;
private _errorCallback: (err: any) => void;
private _progressCallback: (progress: any) => void;
constructor(computer: IHoverComputer<Result>, success: (r: Result) => void, error: (err: any) => void, progress: (progress: any) => void) {
this._computer = computer;
this._state = ComputeHoverOperationState.IDLE;
this._firstWaitScheduler = new RunOnceScheduler(() => this._triggerAsyncComputation(), this._getHoverTimeMillis() / 2);
this._secondWaitScheduler = new RunOnceScheduler(() => this._triggerSyncComputation(), this._getHoverTimeMillis() / 2);
this._loadingMessageScheduler = new RunOnceScheduler(() => this._showLoadingMessage(), 3 * this._getHoverTimeMillis());
this._asyncComputationPromise = null;
this._asyncComputationPromiseDone = false;
this._completeCallback = success;
this._errorCallback = error;
this._progressCallback = progress;
}
public getComputer(): IHoverComputer<Result> {
return this._computer;
}
private _getHoverTimeMillis(): number {
if (this._computer.getHoverTimeMillis) {
return this._computer.getHoverTimeMillis();
}
return HoverOperation.HOVER_TIME;
}
private _triggerAsyncComputation(): void {
this._state = ComputeHoverOperationState.SECOND_WAIT;
this._secondWaitScheduler.schedule();
if (this._computer.computeAsync) {
this._asyncComputationPromiseDone = false;
this._asyncComputationPromise = this._computer.computeAsync().then((asyncResult: Result) => {
this._asyncComputationPromiseDone = true;
this._withAsyncResult(asyncResult);
}, (e) => this._onError(e));
} else {
this._asyncComputationPromiseDone = true;
}
}
private _triggerSyncComputation(): void {
if (this._computer.computeSync) {
this._computer.onResult(this._computer.computeSync(), true);
}
if (this._asyncComputationPromiseDone) {
this._state = ComputeHoverOperationState.IDLE;
this._onComplete(this._computer.getResult());
} else {
this._state = ComputeHoverOperationState.WAITING_FOR_ASYNC_COMPUTATION;
this._onProgress(this._computer.getResult());
}
}
private _showLoadingMessage(): void {
if (this._state === ComputeHoverOperationState.WAITING_FOR_ASYNC_COMPUTATION) {
this._onProgress(this._computer.getResultWithLoadingMessage());
}
}
private _withAsyncResult(asyncResult: Result): void {
if (asyncResult) {
this._computer.onResult(asyncResult, false);
}
if (this._state === ComputeHoverOperationState.WAITING_FOR_ASYNC_COMPUTATION) {
this._state = ComputeHoverOperationState.IDLE;
this._onComplete(this._computer.getResult());
}
}
private _onComplete(value: Result): void {
if (this._completeCallback) {
this._completeCallback(value);
}
}
private _onError(error: any): void {
if (this._errorCallback) {
this._errorCallback(error);
} else {
onUnexpectedError(error);
}
}
private _onProgress(value: Result): void {
if (this._progressCallback) {
this._progressCallback(value);
}
}
public start(): void {
if (this._state === ComputeHoverOperationState.IDLE) {
this._state = ComputeHoverOperationState.FIRST_WAIT;
this._firstWaitScheduler.schedule();
this._loadingMessageScheduler.schedule();
}
}
public cancel(): void {
this._loadingMessageScheduler.cancel();
if (this._state === ComputeHoverOperationState.FIRST_WAIT) {
this._firstWaitScheduler.cancel();
}
if (this._state === ComputeHoverOperationState.SECOND_WAIT) {
this._secondWaitScheduler.cancel();
if (this._asyncComputationPromise) {
this._asyncComputationPromise.cancel();
this._asyncComputationPromise = null;
}
}
if (this._state === ComputeHoverOperationState.WAITING_FOR_ASYNC_COMPUTATION) {
if (this._asyncComputationPromise) {
this._asyncComputationPromise.cancel();
this._asyncComputationPromise = null;
}
}
this._state = ComputeHoverOperationState.IDLE;
}
}

View File

@@ -0,0 +1,251 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { toggleClass } from 'vs/base/browser/dom';
import { Position } from 'vs/editor/common/core/position';
import * as editorBrowser from 'vs/editor/browser/editorBrowser';
import { Widget } from 'vs/base/browser/ui/widget';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
export class ContentHoverWidget extends Widget implements editorBrowser.IContentWidget {
private _id: string;
protected _editor: editorBrowser.ICodeEditor;
private _isVisible: boolean;
private _containerDomNode: HTMLElement;
private _domNode: HTMLElement;
protected _showAtPosition: Position;
private _stoleFocus: boolean;
private scrollbar: DomScrollableElement;
private disposables: IDisposable[] = [];
// Editor.IContentWidget.allowEditorOverflow
public allowEditorOverflow = true;
protected get isVisible(): boolean {
return this._isVisible;
}
protected set isVisible(value: boolean) {
this._isVisible = value;
toggleClass(this._containerDomNode, 'hidden', !this._isVisible);
}
constructor(id: string, editor: editorBrowser.ICodeEditor) {
super();
this._id = id;
this._editor = editor;
this._isVisible = false;
this._containerDomNode = document.createElement('div');
this._containerDomNode.className = 'monaco-editor-hover hidden';
this._containerDomNode.tabIndex = 0;
this._domNode = document.createElement('div');
this._domNode.className = 'monaco-editor-hover-content';
this.scrollbar = new DomScrollableElement(this._domNode, {});
this.disposables.push(this.scrollbar);
this._containerDomNode.appendChild(this.scrollbar.getDomNode());
this.onkeydown(this._containerDomNode, (e: IKeyboardEvent) => {
if (e.equals(KeyCode.Escape)) {
this.hide();
}
});
this._register(this._editor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => {
if (e.fontInfo) {
this.updateFont();
}
}));
this._editor.onDidLayoutChange(e => this.updateMaxHeight());
this.updateMaxHeight();
this._editor.addContentWidget(this);
this._showAtPosition = null;
}
public getId(): string {
return this._id;
}
public getDomNode(): HTMLElement {
return this._containerDomNode;
}
public showAt(position: Position, focus: boolean): void {
// Position has changed
this._showAtPosition = new Position(position.lineNumber, position.column);
this.isVisible = true;
this._editor.layoutContentWidget(this);
// Simply force a synchronous render on the editor
// such that the widget does not really render with left = '0px'
this._editor.render();
this._stoleFocus = focus;
if (focus) {
this._containerDomNode.focus();
}
}
public hide(): void {
if (!this.isVisible) {
return;
}
this.isVisible = false;
this._editor.layoutContentWidget(this);
if (this._stoleFocus) {
this._editor.focus();
}
}
public getPosition(): editorBrowser.IContentWidgetPosition {
if (this.isVisible) {
return {
position: this._showAtPosition,
preference: [
editorBrowser.ContentWidgetPositionPreference.ABOVE,
editorBrowser.ContentWidgetPositionPreference.BELOW
]
};
}
return null;
}
public dispose(): void {
this._editor.removeContentWidget(this);
this.disposables = dispose(this.disposables);
super.dispose();
}
private updateFont(): void {
const codeTags: HTMLElement[] = Array.prototype.slice.call(this._domNode.getElementsByTagName('code'));
const codeClasses: HTMLElement[] = Array.prototype.slice.call(this._domNode.getElementsByClassName('code'));
[...codeTags, ...codeClasses].forEach(node => this._editor.applyFontInfo(node));
}
protected updateContents(node: Node): void {
this._domNode.textContent = '';
this._domNode.appendChild(node);
this.updateFont();
this._editor.layoutContentWidget(this);
this.scrollbar.scanDomNode();
}
private updateMaxHeight(): void {
const height = Math.max(this._editor.getLayoutInfo().height / 4, 250);
const { fontSize, lineHeight } = this._editor.getConfiguration().fontInfo;
this._domNode.style.fontSize = `${fontSize}px`;
this._domNode.style.lineHeight = `${lineHeight}px`;
this._domNode.style.maxHeight = `${height}px`;
}
}
export class GlyphHoverWidget extends Widget implements editorBrowser.IOverlayWidget {
private _id: string;
protected _editor: editorBrowser.ICodeEditor;
private _isVisible: boolean;
private _domNode: HTMLElement;
protected _showAtLineNumber: number;
constructor(id: string, editor: editorBrowser.ICodeEditor) {
super();
this._id = id;
this._editor = editor;
this._isVisible = false;
this._domNode = document.createElement('div');
this._domNode.className = 'monaco-editor-hover hidden';
this._domNode.setAttribute('aria-hidden', 'true');
this._domNode.setAttribute('role', 'presentation');
this._showAtLineNumber = -1;
this._register(this._editor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => {
if (e.fontInfo) {
this.updateFont();
}
}));
this._editor.addOverlayWidget(this);
}
protected get isVisible(): boolean {
return this._isVisible;
}
protected set isVisible(value: boolean) {
this._isVisible = value;
toggleClass(this._domNode, 'hidden', !this._isVisible);
}
public getId(): string {
return this._id;
}
public getDomNode(): HTMLElement {
return this._domNode;
}
public showAt(lineNumber: number): void {
this._showAtLineNumber = lineNumber;
if (!this.isVisible) {
this.isVisible = true;
}
const editorLayout = this._editor.getLayoutInfo();
const topForLineNumber = this._editor.getTopForLineNumber(this._showAtLineNumber);
const editorScrollTop = this._editor.getScrollTop();
const lineHeight = this._editor.getConfiguration().lineHeight;
const nodeHeight = this._domNode.clientHeight;
const top = topForLineNumber - editorScrollTop - ((nodeHeight - lineHeight) / 2);
this._domNode.style.left = `${editorLayout.glyphMarginLeft + editorLayout.glyphMarginWidth}px`;
this._domNode.style.top = `${Math.max(Math.round(top), 0)}px`;
}
public hide(): void {
if (!this.isVisible) {
return;
}
this.isVisible = false;
}
public getPosition(): editorBrowser.IOverlayWidgetPosition {
return null;
}
public dispose(): void {
this._editor.removeOverlayWidget(this);
super.dispose();
}
private updateFont(): void {
const codeTags: HTMLElement[] = Array.prototype.slice.call(this._domNode.getElementsByTagName('code'));
const codeClasses: HTMLElement[] = Array.prototype.slice.call(this._domNode.getElementsByClassName('code'));
[...codeTags, ...codeClasses].forEach(node => this._editor.applyFontInfo(node));
}
protected updateContents(node: Node): void {
this._domNode.textContent = '';
this._domNode.appendChild(node);
this.updateFont();
}
}

View File

@@ -0,0 +1,397 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import URI from 'vs/base/common/uri';
import { onUnexpectedError } from 'vs/base/common/errors';
import * as dom from 'vs/base/browser/dom';
import { TPromise } from 'vs/base/common/winjs.base';
import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer';
import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IRange, Range } from 'vs/editor/common/core/range';
import { Position } from 'vs/editor/common/core/position';
import { HoverProviderRegistry, Hover, IColor, IColorFormatter } from 'vs/editor/common/modes';
import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { getHover } from '../common/hover';
import { HoverOperation, IHoverComputer } from './hoverOperation';
import { ContentHoverWidget } from './hoverWidgets';
import { IMarkdownString, MarkdownString, isEmptyMarkdownString } from 'vs/base/common/htmlContent';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations';
import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/browser/colorPickerModel';
import { ColorPickerWidget } from 'vs/editor/contrib/colorPicker/browser/colorPickerWidget';
import { ColorDetector } from 'vs/editor/contrib/colorPicker/browser/colorDetector';
import { Color, RGBA } from 'vs/base/common/color';
import { IDisposable, empty as EmptyDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
const $ = dom.$;
class ColorHover {
constructor(
public readonly range: IRange,
public readonly color: IColor,
public readonly formatters: IColorFormatter[]
) { }
}
type HoverPart = Hover | ColorHover;
class ModesContentComputer implements IHoverComputer<HoverPart[]> {
private _editor: ICodeEditor;
private _result: HoverPart[];
private _range: Range;
constructor(editor: ICodeEditor) {
this._editor = editor;
this._range = null;
}
setRange(range: Range): void {
this._range = range;
this._result = [];
}
clearResult(): void {
this._result = [];
}
computeAsync(): TPromise<HoverPart[]> {
const model = this._editor.getModel();
if (!HoverProviderRegistry.has(model)) {
return TPromise.as(null);
}
return getHover(model, new Position(
this._range.startLineNumber,
this._range.startColumn
));
}
computeSync(): HoverPart[] {
const lineNumber = this._range.startLineNumber;
if (lineNumber > this._editor.getModel().getLineCount()) {
// Illegal line number => no results
return [];
}
const colorDetector = ColorDetector.get(this._editor);
const maxColumn = this._editor.getModel().getLineMaxColumn(lineNumber);
const lineDecorations = this._editor.getLineDecorations(lineNumber);
let didFindColor = false;
const result = lineDecorations.map(d => {
const startColumn = (d.range.startLineNumber === lineNumber) ? d.range.startColumn : 1;
const endColumn = (d.range.endLineNumber === lineNumber) ? d.range.endColumn : maxColumn;
if (startColumn > this._range.startColumn || this._range.endColumn > endColumn) {
return null;
}
const range = new Range(this._range.startLineNumber, startColumn, this._range.startLineNumber, endColumn);
const colorRange = colorDetector.getColorRange(d.range.getStartPosition());
if (!didFindColor && colorRange) {
didFindColor = true;
const { color, formatters } = colorRange;
return new ColorHover(d.range, color, formatters);
} else {
if (isEmptyMarkdownString(d.options.hoverMessage)) {
return null;
}
let contents: IMarkdownString[];
if (d.options.hoverMessage) {
if (Array.isArray(d.options.hoverMessage)) {
contents = [...d.options.hoverMessage];
} else {
contents = [d.options.hoverMessage];
}
}
return { contents, range };
}
});
return result.filter(d => !!d);
}
onResult(result: HoverPart[], isFromSynchronousComputation: boolean): void {
// Always put synchronous messages before asynchronous ones
if (isFromSynchronousComputation) {
this._result = result.concat(this._result.sort((a, b) => {
if (a instanceof ColorHover) { // sort picker messages at to the top
return -1;
} else if (b instanceof ColorHover) {
return 1;
}
return 0;
}));
} else {
this._result = this._result.concat(result);
}
}
getResult(): HoverPart[] {
return this._result.slice(0);
}
getResultWithLoadingMessage(): HoverPart[] {
return this._result.slice(0).concat([this._getLoadingMessage()]);
}
private _getLoadingMessage(): HoverPart {
return {
range: this._range,
contents: [new MarkdownString().appendText(nls.localize('modesContentHover.loading', "Loading..."))]
};
}
}
export class ModesContentHoverWidget extends ContentHoverWidget {
static ID = 'editor.contrib.modesContentHoverWidget';
private _messages: HoverPart[];
private _lastRange: Range;
private _computer: ModesContentComputer;
private _hoverOperation: HoverOperation<HoverPart[]>;
private _highlightDecorations: string[];
private _isChangingDecorations: boolean;
private _openerService: IOpenerService;
private _modeService: IModeService;
private _shouldFocus: boolean;
private _colorPicker: ColorPickerWidget;
private renderDisposable: IDisposable = EmptyDisposable;
private toDispose: IDisposable[];
constructor(editor: ICodeEditor, openerService: IOpenerService, modeService: IModeService) {
super(ModesContentHoverWidget.ID, editor);
this._computer = new ModesContentComputer(this._editor);
this._highlightDecorations = [];
this._isChangingDecorations = false;
this._openerService = openerService || NullOpenerService;
this._modeService = modeService;
this._hoverOperation = new HoverOperation(
this._computer,
result => this._withResult(result, true),
null,
result => this._withResult(result, false)
);
this.toDispose = [];
this.toDispose.push(dom.addStandardDisposableListener(this.getDomNode(), dom.EventType.FOCUS, () => {
if (this._colorPicker) {
dom.addClass(this.getDomNode(), 'colorpicker-hover');
}
}));
this.toDispose.push(dom.addStandardDisposableListener(this.getDomNode(), dom.EventType.BLUR, () => {
dom.removeClass(this.getDomNode(), 'colorpicker-hover');
}));
}
dispose(): void {
this.renderDisposable.dispose();
this.renderDisposable = EmptyDisposable;
this._hoverOperation.cancel();
this.toDispose = dispose(this.toDispose);
super.dispose();
}
onModelDecorationsChanged(): void {
if (this._isChangingDecorations) {
return;
}
if (this.isVisible) {
// The decorations have changed and the hover is visible,
// we need to recompute the displayed text
this._hoverOperation.cancel();
this._computer.clearResult();
if (!this._colorPicker) { // TODO@Michel ensure that displayed text for other decorations is computed even if color picker is in place
this._hoverOperation.start();
}
}
}
startShowingAt(range: Range, focus: boolean): void {
if (this._lastRange && this._lastRange.equalsRange(range)) {
// We have to show the widget at the exact same range as before, so no work is needed
return;
}
this._hoverOperation.cancel();
if (this.isVisible) {
// The range might have changed, but the hover is visible
// Instead of hiding it completely, filter out messages that are still in the new range and
// kick off a new computation
if (this._showAtPosition.lineNumber !== range.startLineNumber) {
this.hide();
} else {
var filteredMessages: HoverPart[] = [];
for (var i = 0, len = this._messages.length; i < len; i++) {
var msg = this._messages[i];
var rng = msg.range;
if (rng.startColumn <= range.startColumn && rng.endColumn >= range.endColumn) {
filteredMessages.push(msg);
}
}
if (filteredMessages.length > 0) {
this._renderMessages(range, filteredMessages);
} else {
this.hide();
}
}
}
this._lastRange = range;
this._computer.setRange(range);
this._shouldFocus = focus;
this._hoverOperation.start();
}
hide(): void {
this._lastRange = null;
this._hoverOperation.cancel();
super.hide();
this._isChangingDecorations = true;
this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, []);
this._isChangingDecorations = false;
this.renderDisposable.dispose();
this.renderDisposable = EmptyDisposable;
this._colorPicker = null;
}
isColorPickerVisible(): boolean {
if (this._colorPicker) {
return true;
}
return false;
}
private _withResult(result: HoverPart[], complete: boolean): void {
this._messages = result;
if (this._lastRange && this._messages.length > 0) {
this._renderMessages(this._lastRange, this._messages);
} else if (complete) {
this.hide();
}
}
private _renderMessages(renderRange: Range, messages: HoverPart[]): void {
this.renderDisposable.dispose();
this._colorPicker = null;
// update column from which to show
var renderColumn = Number.MAX_VALUE,
highlightRange = messages[0].range,
fragment = document.createDocumentFragment();
messages.forEach((msg) => {
if (!msg.range) {
return;
}
renderColumn = Math.min(renderColumn, msg.range.startColumn);
highlightRange = Range.plusRange(highlightRange, msg.range);
if (!(msg instanceof ColorHover)) {
msg.contents
.filter(contents => !isEmptyMarkdownString(contents))
.forEach(contents => {
const renderedContents = renderMarkdown(contents, {
actionCallback: (content) => {
this._openerService.open(URI.parse(content)).then(void 0, onUnexpectedError);
},
codeBlockRenderer: (languageAlias, value): string | TPromise<string> => {
// In markdown,
// it is possible that we stumble upon language aliases (e.g.js instead of javascript)
// it is possible no alias is given in which case we fall back to the current editor lang
const modeId = languageAlias
? this._modeService.getModeIdForLanguageName(languageAlias)
: this._editor.getModel().getLanguageIdentifier().language;
return this._modeService.getOrCreateMode(modeId).then(_ => {
return tokenizeToString(value, modeId);
});
}
});
fragment.appendChild($('div.hover-row', null, renderedContents));
});
} else {
const { red, green, blue, alpha } = msg.color;
const rgba = new RGBA(red * 255, green * 255, blue * 255, alpha);
const color = new Color(rgba);
const formatters = [...msg.formatters];
const text = this._editor.getModel().getValueInRange(msg.range);
let formatterIndex = 0;
for (let i = 0; i < formatters.length; i++) {
if (text === formatters[i].format(msg.color)) {
formatterIndex = i;
break;
}
}
const model = new ColorPickerModel(color, formatters, formatterIndex);
const widget = new ColorPickerWidget(fragment, model, this._editor.getConfiguration().pixelRatio);
const editorModel = this._editor.getModel();
let range = new Range(msg.range.startLineNumber, msg.range.startColumn, msg.range.endLineNumber, msg.range.endColumn);
const updateEditorModel = () => {
const text = model.formatter.format({
red: model.color.rgba.r / 255,
green: model.color.rgba.g / 255,
blue: model.color.rgba.b / 255,
alpha: model.color.rgba.a
});
editorModel.pushEditOperations([], [{ identifier: null, range, text, forceMoveMarkers: false }], () => []);
this._editor.pushUndoStop();
range = range.setEndPosition(range.endLineNumber, range.startColumn + text.length);
};
const colorListener = model.onColorFlushed(updateEditorModel);
this._colorPicker = widget;
this.renderDisposable = combinedDisposable([colorListener, widget]);
}
});
// show
this.showAt(new Position(renderRange.startLineNumber, renderColumn), this._shouldFocus);
this.updateContents(fragment);
if (this._colorPicker) {
this._colorPicker.layout();
}
this._isChangingDecorations = true;
this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, [{
range: highlightRange,
options: ModesContentHoverWidget._DECORATION_OPTIONS
}]);
this._isChangingDecorations = false;
}
private static _DECORATION_OPTIONS = ModelDecorationOptions.register({
className: 'hoverHighlight'
});
}

View File

@@ -0,0 +1,186 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { HoverOperation, IHoverComputer } from './hoverOperation';
import { GlyphHoverWidget } from './hoverWidgets';
import { $ } from 'vs/base/browser/dom';
import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer';
import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener';
import URI from 'vs/base/common/uri';
import { onUnexpectedError } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { IModeService } from 'vs/editor/common/services/modeService';
import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer';
import { IMarkdownString, isEmptyMarkdownString } from 'vs/base/common/htmlContent';
export interface IHoverMessage {
value: IMarkdownString;
}
class MarginComputer implements IHoverComputer<IHoverMessage[]> {
private _editor: ICodeEditor;
private _lineNumber: number;
private _result: IHoverMessage[];
constructor(editor: ICodeEditor) {
this._editor = editor;
this._lineNumber = -1;
}
public setLineNumber(lineNumber: number): void {
this._lineNumber = lineNumber;
this._result = [];
}
public clearResult(): void {
this._result = [];
}
public computeSync(): IHoverMessage[] {
const toHoverMessage = (contents: IMarkdownString): IHoverMessage => {
return {
value: contents
};
};
let lineDecorations = this._editor.getLineDecorations(this._lineNumber);
let result: IHoverMessage[] = [];
for (let i = 0, len = lineDecorations.length; i < len; i++) {
let d = lineDecorations[i];
if (!d.options.glyphMarginClassName) {
continue;
}
let hoverMessage = d.options.glyphMarginHoverMessage;
if (isEmptyMarkdownString(hoverMessage)) {
continue;
}
if (Array.isArray(hoverMessage)) {
result = result.concat(hoverMessage.map(toHoverMessage));
} else {
result.push(toHoverMessage(hoverMessage));
}
}
return result;
}
public onResult(result: IHoverMessage[], isFromSynchronousComputation: boolean): void {
this._result = this._result.concat(result);
}
public getResult(): IHoverMessage[] {
return this._result;
}
public getResultWithLoadingMessage(): IHoverMessage[] {
return this.getResult();
}
}
export class ModesGlyphHoverWidget extends GlyphHoverWidget {
public static ID = 'editor.contrib.modesGlyphHoverWidget';
private _messages: IHoverMessage[];
private _lastLineNumber: number;
private _computer: MarginComputer;
private _hoverOperation: HoverOperation<IHoverMessage[]>;
constructor(editor: ICodeEditor, private openerService: IOpenerService, private modeService: IModeService) {
super(ModesGlyphHoverWidget.ID, editor);
this.openerService = openerService || NullOpenerService;
this._lastLineNumber = -1;
this._computer = new MarginComputer(this._editor);
this._hoverOperation = new HoverOperation(
this._computer,
(result: IHoverMessage[]) => this._withResult(result),
null,
(result: any) => this._withResult(result)
);
}
public dispose(): void {
this._hoverOperation.cancel();
super.dispose();
}
public onModelDecorationsChanged(): void {
if (this.isVisible) {
// The decorations have changed and the hover is visible,
// we need to recompute the displayed text
this._hoverOperation.cancel();
this._computer.clearResult();
this._hoverOperation.start();
}
}
public startShowingAt(lineNumber: number): void {
if (this._lastLineNumber === lineNumber) {
// We have to show the widget at the exact same line number as before, so no work is needed
return;
}
this._hoverOperation.cancel();
this.hide();
this._lastLineNumber = lineNumber;
this._computer.setLineNumber(lineNumber);
this._hoverOperation.start();
}
public hide(): void {
this._lastLineNumber = -1;
this._hoverOperation.cancel();
super.hide();
}
public _withResult(result: IHoverMessage[]): void {
this._messages = result;
if (this._messages.length > 0) {
this._renderMessages(this._lastLineNumber, this._messages);
} else {
this.hide();
}
}
private _renderMessages(lineNumber: number, messages: IHoverMessage[]): void {
const fragment = document.createDocumentFragment();
messages.forEach((msg) => {
const renderedContents = renderMarkdown(msg.value, {
actionCallback: content => this.openerService.open(URI.parse(content)).then(undefined, onUnexpectedError),
codeBlockRenderer: (languageAlias, value): string | TPromise<string> => {
// In markdown, it is possible that we stumble upon language aliases (e.g. js instead of javascript)
const modeId = this.modeService.getModeIdForLanguageName(languageAlias);
return this.modeService.getOrCreateMode(modeId).then(_ => {
return tokenizeToString(value, modeId);
});
}
});
fragment.appendChild($('div.hover-row', null, renderedContents));
});
this.updateContents(fragment);
this.showAt(lineNumber);
}
}

View File

@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { coalesce } from 'vs/base/common/arrays';
import { onUnexpectedExternalError } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { IReadOnlyModel } from 'vs/editor/common/editorCommon';
import { CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions';
import { Hover, HoverProviderRegistry } from 'vs/editor/common/modes';
import { asWinJsPromise } from 'vs/base/common/async';
import { Position } from 'vs/editor/common/core/position';
export function getHover(model: IReadOnlyModel, position: Position): TPromise<Hover[]> {
const supports = HoverProviderRegistry.ordered(model);
const values: Hover[] = [];
const promises = supports.map((support, idx) => {
return asWinJsPromise((token) => {
return support.provideHover(model, position, token);
}).then((result) => {
if (result) {
let hasRange = (typeof result.range !== 'undefined');
let hasHtmlContent = typeof result.contents !== 'undefined' && result.contents && result.contents.length > 0;
if (hasRange && hasHtmlContent) {
values[idx] = result;
}
}
}, err => {
onUnexpectedExternalError(err);
});
});
return TPromise.join(promises).then(() => coalesce(values));
}
CommonEditorRegistry.registerDefaultLanguageCommand('_executeHoverProvider', getHover);