mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
77
src/vs/editor/contrib/hover/browser/hover.css
Normal file
77
src/vs/editor/contrib/hover/browser/hover.css
Normal 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;
|
||||
}
|
||||
222
src/vs/editor/contrib/hover/browser/hover.ts
Normal file
222
src/vs/editor/contrib/hover/browser/hover.ts
Normal 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}; }`);
|
||||
}
|
||||
|
||||
});
|
||||
189
src/vs/editor/contrib/hover/browser/hoverOperation.ts
Normal file
189
src/vs/editor/contrib/hover/browser/hoverOperation.ts
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
251
src/vs/editor/contrib/hover/browser/hoverWidgets.ts
Normal file
251
src/vs/editor/contrib/hover/browser/hoverWidgets.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
397
src/vs/editor/contrib/hover/browser/modesContentHover.ts
Normal file
397
src/vs/editor/contrib/hover/browser/modesContentHover.ts
Normal 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'
|
||||
});
|
||||
}
|
||||
186
src/vs/editor/contrib/hover/browser/modesGlyphHover.ts
Normal file
186
src/vs/editor/contrib/hover/browser/modesGlyphHover.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
41
src/vs/editor/contrib/hover/common/hover.ts
Normal file
41
src/vs/editor/contrib/hover/common/hover.ts
Normal 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);
|
||||
Reference in New Issue
Block a user