From 41ae0c78757a3e3320e3d9765bebaac63ed73cb5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 7 Jan 2019 22:23:48 +0100 Subject: [PATCH] move rendering marker hover to hover widget --- src/vs/base/browser/htmlContentRenderer.ts | 4 +- src/vs/base/common/htmlContent.ts | 1 - .../services/markerDecorationsServiceImpl.ts | 199 ++++++++++++++++++ .../services/markersDecorationService.ts | 16 ++ .../common/services/modelServiceImpl.ts | 191 +---------------- src/vs/editor/contrib/hover/hover.ts | 4 +- .../editor/contrib/hover/modesContentHover.ts | 95 +++++++-- .../standalone/browser/standaloneServices.ts | 4 + src/vs/workbench/electron-browser/shell.ts | 5 + 9 files changed, 309 insertions(+), 210 deletions(-) create mode 100644 src/vs/editor/common/services/markerDecorationsServiceImpl.ts create mode 100644 src/vs/editor/common/services/markersDecorationService.ts diff --git a/src/vs/base/browser/htmlContentRenderer.ts b/src/vs/base/browser/htmlContentRenderer.ts index cee63325b9b..93144774216 100644 --- a/src/vs/base/browser/htmlContentRenderer.ts +++ b/src/vs/base/browser/htmlContentRenderer.ts @@ -6,7 +6,7 @@ import * as DOM from 'vs/base/browser/dom'; import { defaultGenerator } from 'vs/base/common/idGenerator'; import { escape } from 'vs/base/common/strings'; -import { removeMarkdownEscapes, IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { removeMarkdownEscapes, IMarkdownString } from 'vs/base/common/htmlContent'; import * as marked from 'vs/base/common/marked/marked'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -211,7 +211,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions } const markedOptions: marked.MarkedOptions = { - sanitize: markdown instanceof MarkdownString ? markdown.sanitize : true, + sanitize: true, renderer }; diff --git a/src/vs/base/common/htmlContent.ts b/src/vs/base/common/htmlContent.ts index def335d55d2..201e378e11e 100644 --- a/src/vs/base/common/htmlContent.ts +++ b/src/vs/base/common/htmlContent.ts @@ -16,7 +16,6 @@ export class MarkdownString implements IMarkdownString { value: string; isTrusted?: boolean; - sanitize: boolean = true; constructor(value: string = '') { this.value = value; diff --git a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts new file mode 100644 index 00000000000..a642f442f0c --- /dev/null +++ b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts @@ -0,0 +1,199 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IMarkerService, IMarker, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IModelDeltaDecoration, ITextModel, IModelDecorationOptions, TrackedRangeStickiness, OverviewRulerLane, IModelDecoration } from 'vs/editor/common/model'; +import { ClassName } from 'vs/editor/common/model/intervalTree'; +import { themeColorFromId, ThemeColor } from 'vs/platform/theme/common/themeService'; +import { overviewRulerWarning, overviewRulerInfo, overviewRulerError } from 'vs/editor/common/view/editorColorRegistry'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { Range } from 'vs/editor/common/core/range'; +import { keys } from 'vs/base/common/map'; +import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; + +function MODEL_ID(resource: URI): string { + return resource.toString(); +} + +class MarkerDecorations extends Disposable { + + private readonly _markersData: Map = new Map(); + + constructor( + readonly model: ITextModel + ) { + super(); + this._register(toDisposable(() => { + this.model.deltaDecorations(keys(this._markersData), []); + this._markersData.clear(); + })); + } + + public update(markers: IMarker[], newDecorations: IModelDeltaDecoration[]): void { + const ids = this.model.deltaDecorations(keys(this._markersData), newDecorations); + for (let index = 0; index < ids.length; index++) { + this._markersData.set(ids[index], markers[index]); + } + } + + getMarker(decoration: IModelDecoration): IMarker | null { + return this._markersData.get(decoration.id); + } +} + +export class MarkerDecorationsService extends Disposable implements IMarkerDecorationsService { + + _serviceBrand: any; + + private readonly _markerDecorations: Map = new Map(); + + constructor( + @IModelService modelService: IModelService, + @IMarkerService private readonly _markerService: IMarkerService + ) { + super(); + this._register(modelService.onModelAdded(this._onModelAdded, this)); + this._register(modelService.onModelRemoved(this._onModelRemoved, this)); + this._register(this._markerService.onMarkerChanged(this._handleMarkerChange, this)); + } + + getMarker(model: ITextModel, decoration: IModelDecoration): IMarker | null { + const markerDecorations = this._markerDecorations.get(MODEL_ID(model.uri)); + return markerDecorations ? markerDecorations.getMarker(decoration) : null; + } + + private _handleMarkerChange(changedResources: URI[]): void { + changedResources.forEach((resource) => { + const markerDecorations = this._markerDecorations.get(MODEL_ID(resource)); + if (markerDecorations) { + this.updateDecorations(markerDecorations); + } + }); + } + + private _onModelAdded(model: ITextModel): void { + const markerDecorations = new MarkerDecorations(model); + this._markerDecorations.set(MODEL_ID(model.uri), markerDecorations); + this.updateDecorations(markerDecorations); + } + + private _onModelRemoved(model: ITextModel): void { + const markerDecorations = this._markerDecorations.get(MODEL_ID(model.uri)); + if (markerDecorations) { + markerDecorations.dispose(); + this._markerDecorations.delete(MODEL_ID(model.uri)); + } + } + + private updateDecorations(markerDecorations: MarkerDecorations): void { + // Limit to the first 500 errors/warnings + const markers = this._markerService.read({ resource: markerDecorations.model.uri, take: 500 }); + let newModelDecorations: IModelDeltaDecoration[] = markers.map((marker) => { + return { + range: this._createDecorationRange(markerDecorations.model, marker), + options: this._createDecorationOption(marker) + }; + }); + markerDecorations.update(markers, newModelDecorations); + } + + private _createDecorationRange(model: ITextModel, rawMarker: IMarker): Range { + + let ret = Range.lift(rawMarker); + + if (rawMarker.severity === MarkerSeverity.Hint) { + if (!rawMarker.tags || rawMarker.tags.indexOf(MarkerTag.Unnecessary) === -1) { + // * never render hints on multiple lines + // * make enough space for three dots + ret = ret.setEndPosition(ret.startLineNumber, ret.startColumn + 2); + } + } + + ret = model.validateRange(ret); + + if (ret.isEmpty()) { + let word = model.getWordAtPosition(ret.getStartPosition()); + if (word) { + ret = new Range(ret.startLineNumber, word.startColumn, ret.endLineNumber, word.endColumn); + } else { + let maxColumn = model.getLineLastNonWhitespaceColumn(ret.startLineNumber) || + model.getLineMaxColumn(ret.startLineNumber); + + if (maxColumn === 1) { + // empty line + // console.warn('marker on empty line:', marker); + } else if (ret.endColumn >= maxColumn) { + // behind eol + ret = new Range(ret.startLineNumber, maxColumn - 1, ret.endLineNumber, maxColumn); + } else { + // extend marker to width = 1 + ret = new Range(ret.startLineNumber, ret.startColumn, ret.endLineNumber, ret.endColumn + 1); + } + } + } else if (rawMarker.endColumn === Number.MAX_VALUE && rawMarker.startColumn === 1 && ret.startLineNumber === ret.endLineNumber) { + let minColumn = model.getLineFirstNonWhitespaceColumn(rawMarker.startLineNumber); + if (minColumn < ret.endColumn) { + ret = new Range(ret.startLineNumber, minColumn, ret.endLineNumber, ret.endColumn); + rawMarker.startColumn = minColumn; + } + } + return ret; + } + + private _createDecorationOption(marker: IMarker): IModelDecorationOptions { + + let className: string; + let color: ThemeColor | undefined = undefined; + let zIndex: number; + let inlineClassName: string | undefined = undefined; + + switch (marker.severity) { + case MarkerSeverity.Hint: + if (marker.tags && marker.tags.indexOf(MarkerTag.Unnecessary) >= 0) { + className = ClassName.EditorUnnecessaryDecoration; + } else { + className = ClassName.EditorHintDecoration; + } + zIndex = 0; + break; + case MarkerSeverity.Warning: + className = ClassName.EditorWarningDecoration; + color = themeColorFromId(overviewRulerWarning); + zIndex = 20; + break; + case MarkerSeverity.Info: + className = ClassName.EditorInfoDecoration; + color = themeColorFromId(overviewRulerInfo); + zIndex = 10; + break; + case MarkerSeverity.Error: + default: + className = ClassName.EditorErrorDecoration; + color = themeColorFromId(overviewRulerError); + zIndex = 30; + break; + } + + if (marker.tags) { + if (marker.tags.indexOf(MarkerTag.Unnecessary) !== -1) { + inlineClassName = ClassName.EditorUnnecessaryInlineDecoration; + } + } + + return { + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + className, + showIfCollapsed: true, + overviewRuler: { + color, + position: OverviewRulerLane.Right + }, + zIndex, + inlineClassName, + }; + } +} diff --git a/src/vs/editor/common/services/markersDecorationService.ts b/src/vs/editor/common/services/markersDecorationService.ts new file mode 100644 index 00000000000..b2424a4d312 --- /dev/null +++ b/src/vs/editor/common/services/markersDecorationService.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITextModel, IModelDecoration } from 'vs/editor/common/model'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IMarker } from 'vs/platform/markers/common/markers'; + +export const IMarkerDecorationsService = createDecorator('markerDecorationsService'); + +export interface IMarkerDecorationsService { + _serviceBrand: any; + + getMarker(model: ITextModel, decoration: IModelDecoration): IMarker | null; +} \ No newline at end of file diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 8cf29738393..10b9b3b181f 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -3,20 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isNonEmptyArray } from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; -import { MarkdownString } from 'vs/base/common/htmlContent'; -import { escape } from 'vs/base/common/strings'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import * as network from 'vs/base/common/network'; -import { basename } from 'vs/base/common/paths'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; -import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, IModelDecorationOptions, IModelDeltaDecoration, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; -import { ClassName } from 'vs/editor/common/model/intervalTree'; +import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model'; import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel'; import { IModelLanguageChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { LanguageIdentifier } from 'vs/editor/common/modes'; @@ -24,10 +19,8 @@ import { PLAINTEXT_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/modesRegis import { ILanguageSelection } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; -import { overviewRulerError, overviewRulerInfo, overviewRulerWarning } from 'vs/editor/common/view/editorColorRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IMarker, IMarkerService, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; -import { ThemeColor, themeColorFromId } from 'vs/platform/theme/common/themeService'; +import { IMarkerService } from 'vs/platform/markers/common/markers'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -39,7 +32,6 @@ class ModelData implements IDisposable { private _languageSelection: ILanguageSelection | null; private _languageSelectionListener: IDisposable | null; - private _markerDecorations: string[]; private _modelEventListeners: IDisposable[]; constructor( @@ -52,8 +44,6 @@ class ModelData implements IDisposable { this._languageSelection = null; this._languageSelectionListener = null; - this._markerDecorations = []; - this._modelEventListeners = []; this._modelEventListeners.push(model.onWillDispose(() => onWillDispose(model))); this._modelEventListeners.push(model.onDidChangeLanguage((e) => onDidChangeLanguage(model, e))); @@ -71,15 +61,10 @@ class ModelData implements IDisposable { } public dispose(): void { - this._markerDecorations = this.model.deltaDecorations(this._markerDecorations, []); this._modelEventListeners = dispose(this._modelEventListeners); this._disposeLanguageSelection(); } - public acceptMarkerDecorations(newDecorations: IModelDeltaDecoration[]): void { - this._markerDecorations = this.model.deltaDecorations(this._markerDecorations, newDecorations); - } - public setLanguage(languageSelection: ILanguageSelection): void { this._disposeLanguageSelection(); this._languageSelection = languageSelection; @@ -88,155 +73,6 @@ class ModelData implements IDisposable { } } -class ModelMarkerHandler { - - public static setMarkers(modelData: ModelData, markerService: IMarkerService): void { - - // Limit to the first 500 errors/warnings - const markers = markerService.read({ resource: modelData.model.uri, take: 500 }); - - let newModelDecorations: IModelDeltaDecoration[] = markers.map((marker) => { - return { - range: ModelMarkerHandler._createDecorationRange(modelData.model, marker), - options: ModelMarkerHandler._createDecorationOption(marker) - }; - }); - - modelData.acceptMarkerDecorations(newModelDecorations); - } - - private static _createDecorationRange(model: ITextModel, rawMarker: IMarker): Range { - - let ret = Range.lift(rawMarker); - - if (rawMarker.severity === MarkerSeverity.Hint) { - if (!rawMarker.tags || rawMarker.tags.indexOf(MarkerTag.Unnecessary) === -1) { - // * never render hints on multiple lines - // * make enough space for three dots - ret = ret.setEndPosition(ret.startLineNumber, ret.startColumn + 2); - } - } - - ret = model.validateRange(ret); - - if (ret.isEmpty()) { - let word = model.getWordAtPosition(ret.getStartPosition()); - if (word) { - ret = new Range(ret.startLineNumber, word.startColumn, ret.endLineNumber, word.endColumn); - } else { - let maxColumn = model.getLineLastNonWhitespaceColumn(ret.startLineNumber) || - model.getLineMaxColumn(ret.startLineNumber); - - if (maxColumn === 1) { - // empty line - // console.warn('marker on empty line:', marker); - } else if (ret.endColumn >= maxColumn) { - // behind eol - ret = new Range(ret.startLineNumber, maxColumn - 1, ret.endLineNumber, maxColumn); - } else { - // extend marker to width = 1 - ret = new Range(ret.startLineNumber, ret.startColumn, ret.endLineNumber, ret.endColumn + 1); - } - } - } else if (rawMarker.endColumn === Number.MAX_VALUE && rawMarker.startColumn === 1 && ret.startLineNumber === ret.endLineNumber) { - let minColumn = model.getLineFirstNonWhitespaceColumn(rawMarker.startLineNumber); - if (minColumn < ret.endColumn) { - ret = new Range(ret.startLineNumber, minColumn, ret.endLineNumber, ret.endColumn); - rawMarker.startColumn = minColumn; - } - } - return ret; - } - - private static _createDecorationOption(marker: IMarker): IModelDecorationOptions { - - let className: string; - let color: ThemeColor | undefined = undefined; - let zIndex: number; - let inlineClassName: string | undefined = undefined; - - switch (marker.severity) { - case MarkerSeverity.Hint: - if (marker.tags && marker.tags.indexOf(MarkerTag.Unnecessary) >= 0) { - className = ClassName.EditorUnnecessaryDecoration; - } else { - className = ClassName.EditorHintDecoration; - } - zIndex = 0; - break; - case MarkerSeverity.Warning: - className = ClassName.EditorWarningDecoration; - color = themeColorFromId(overviewRulerWarning); - zIndex = 20; - break; - case MarkerSeverity.Info: - className = ClassName.EditorInfoDecoration; - color = themeColorFromId(overviewRulerInfo); - zIndex = 10; - break; - case MarkerSeverity.Error: - default: - className = ClassName.EditorErrorDecoration; - color = themeColorFromId(overviewRulerError); - zIndex = 30; - break; - } - - if (marker.tags) { - if (marker.tags.indexOf(MarkerTag.Unnecessary) !== -1) { - inlineClassName = ClassName.EditorUnnecessaryInlineDecoration; - } - } - - let hoverMessage: MarkdownString | null = null; - let { message, source, relatedInformation, code } = marker; - - if (typeof message === 'string') { - - hoverMessage = new MarkdownString(); - // Disable markdown renderer sanitize to allow html - // Hence, escape all input strings - hoverMessage.sanitize = false; - - hoverMessage.appendMarkdown(`
`); - hoverMessage.appendMarkdown(`${escape(message.trim())}`); - if (source) { - hoverMessage.appendMarkdown(`${escape(source)}`); - if (code) { - hoverMessage.appendMarkdown(`(${escape(code)})`); - } - } else if (code) { - hoverMessage.appendMarkdown(`(${escape(code)})`); - } - hoverMessage.appendMarkdown(`
`); - - if (isNonEmptyArray(relatedInformation)) { - hoverMessage.appendMarkdown(`
    `); - for (const { message, resource, startLineNumber, startColumn } of relatedInformation) { - hoverMessage.appendMarkdown(`
  • `); - hoverMessage.appendMarkdown(`${escape(basename(resource.path))}(${startLineNumber}, ${startColumn})`); - hoverMessage.appendMarkdown(`: ${escape(message)}`); - hoverMessage.appendMarkdown(`
  • `); - } - hoverMessage.appendMarkdown(`
`); - } - } - - return { - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - className, - hoverMessage, - showIfCollapsed: true, - overviewRuler: { - color, - position: OverviewRulerLane.Right - }, - zIndex, - inlineClassName, - }; - } -} - interface IRawEditorConfig { tabSize?: any; insertSpaces?: any; @@ -283,7 +119,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { constructor( @IMarkerService markerService: IMarkerService | null, @IConfigurationService configurationService: IConfigurationService, - @ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService, + @ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService ) { super(); this._markerService = markerService; @@ -292,11 +128,6 @@ export class ModelServiceImpl extends Disposable implements IModelService { this._models = {}; this._modelCreationOptionsByLanguageAndResource = Object.create(null); - if (this._markerService) { - this._markerServiceSubscription = this._markerService.onMarkerChanged(this._handleMarkerChange, this); - this._handleMarkerChange(this._markerService.read().map(m => m.resource)); - } - this._configurationServiceSubscription = this._configurationService.onDidChangeConfiguration(e => this._updateModelOptions()); this._updateModelOptions(); } @@ -413,17 +244,6 @@ export class ModelServiceImpl extends Disposable implements IModelService { super.dispose(); } - private _handleMarkerChange(changedResources: URI[]): void { - changedResources.forEach((resource) => { - let modelId = MODEL_ID(resource); - let modelData = this._models[modelId]; - if (!modelData) { - return; - } - ModelMarkerHandler.setMarkers(modelData, this._markerService!); - }); - } - private _cleanUp(model: ITextModel): void { // clean up markers for internal, transient models if (model.uri.scheme === network.Schemas.inMemory @@ -541,11 +361,6 @@ export class ModelServiceImpl extends Disposable implements IModelService { modelData = this._createModelData(value, PLAINTEXT_LANGUAGE_IDENTIFIER, resource, isForSimpleWidget); } - // handle markers (marker service => model) - if (this._markerService) { - ModelMarkerHandler.setMarkers(modelData, this._markerService); - } - this._onModelAdded.fire(modelData.model); return modelData.model; diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index 5a2f1b51188..d729d098ccc 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -24,6 +24,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; export class ModesHoverController implements IEditorContribution { @@ -61,6 +62,7 @@ export class ModesHoverController implements IEditorContribution { constructor(private readonly _editor: ICodeEditor, @IOpenerService private readonly _openerService: IOpenerService, @IModeService private readonly _modeService: IModeService, + @IMarkerDecorationsService private readonly _markerDecorationsService: IMarkerDecorationsService, @IThemeService private readonly _themeService: IThemeService ) { this._toUnhook = []; @@ -204,7 +206,7 @@ export class ModesHoverController implements IEditorContribution { private _createHoverWidget() { const renderer = new MarkdownRenderer(this._editor, this._modeService, this._openerService); - this._contentWidget = new ModesContentHoverWidget(this._editor, renderer, this._themeService); + this._contentWidget = new ModesContentHoverWidget(this._editor, renderer, this._markerDecorationsService, this._themeService); this._glyphWidget = new ModesGlyphHoverWidget(this._editor, renderer); } diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 1a37b64e5fd..94e66e196e4 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -13,7 +13,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { DocumentColorProvider, Hover, HoverProviderRegistry, IColor } from 'vs/editor/common/modes'; +import { DocumentColorProvider, Hover as MarkdownHover, HoverProviderRegistry, IColor } from 'vs/editor/common/modes'; import { getColorPresentations } from 'vs/editor/contrib/colorPicker/color'; import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector'; import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel'; @@ -23,7 +23,10 @@ import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contri import { ContentHoverWidget } from 'vs/editor/contrib/hover/hoverWidgets'; import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { coalesce } from 'vs/base/common/arrays'; +import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays'; +import { IMarker, IMarkerData } from 'vs/platform/markers/common/markers'; +import { basename } from 'vs/base/common/paths'; +import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; const $ = dom.$; @@ -36,7 +39,15 @@ class ColorHover { ) { } } -type HoverPart = Hover | ColorHover; +class MarkerHover { + + constructor( + public readonly range: IRange, + public readonly marker: IMarker, + ) { } +} + +type HoverPart = MarkdownHover | ColorHover | MarkerHover; class ModesContentComputer implements IHoverComputer { @@ -44,7 +55,10 @@ class ModesContentComputer implements IHoverComputer { private _result: HoverPart[]; private _range: Range | null; - constructor(editor: ICodeEditor) { + constructor( + editor: ICodeEditor, + private _markerDecorationsService: IMarkerDecorationsService + ) { this._editor = editor; this._range = null; } @@ -80,6 +94,7 @@ class ModesContentComputer implements IHoverComputer { return []; } + const model = this._editor.getModel(); const lineNumber = this._range.startLineNumber; if (lineNumber > this._editor.getModel().getLineCount()) { @@ -88,7 +103,7 @@ class ModesContentComputer implements IHoverComputer { } const colorDetector = ColorDetector.get(this._editor); - const maxColumn = this._editor.getModel().getLineMaxColumn(lineNumber); + const maxColumn = model.getLineMaxColumn(lineNumber); const lineDecorations = this._editor.getLineDecorations(lineNumber); let didFindColor = false; @@ -102,6 +117,11 @@ class ModesContentComputer implements IHoverComputer { } const range = new Range(hoverRange.startLineNumber, startColumn, hoverRange.startLineNumber, endColumn); + const marker = this._markerDecorationsService.getMarker(model, d); + if (marker) { + return new MarkerHover(range, marker); + } + const colorData = colorDetector.getColorData(d.range.getStartPosition()); if (!didFindColor && colorData) { @@ -182,13 +202,14 @@ export class ModesContentHoverWidget extends ContentHoverWidget { constructor( editor: ICodeEditor, markdownRenderer: MarkdownRenderer, + markerDecorationsService: IMarkerDecorationsService, private readonly _themeService: IThemeService ) { super(ModesContentHoverWidget.ID, editor); this._messages = []; this._lastRange = null; - this._computer = new ModesContentComputer(this._editor); + this._computer = new ModesContentComputer(this._editor, markerDecorationsService); this._highlightDecorations = []; this._isChangingDecorations = false; @@ -328,16 +349,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { renderColumn = Math.min(renderColumn, msg.range.startColumn); highlightRange = highlightRange ? Range.plusRange(highlightRange, msg.range) : Range.lift(msg.range); - if (!(msg instanceof ColorHover)) { - msg.contents - .filter(contents => !isEmptyMarkdownString(contents)) - .forEach(contents => { - const renderedContents = this._markdownRenderer.render(contents); - markdownDisposeable = renderedContents; - fragment.appendChild($('div.hover-row', undefined, renderedContents.element)); - isEmptyHoverContent = false; - }); - } else { + if (msg instanceof ColorHover) { containColorPicker = true; const { red, green, blue, alpha } = msg.color; @@ -420,6 +432,20 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this.renderDisposable = combinedDisposable([colorListener, colorChangeListener, widget, markdownDisposeable]); }); + } else { + if (msg instanceof MarkerHover) { + isEmptyHoverContent = false; + fragment.appendChild($('div.hover-row', undefined, this.renderMarkerHover(msg))); + } else { + msg.contents + .filter(contents => !isEmptyMarkdownString(contents)) + .forEach(contents => { + const renderedContents = this._markdownRenderer.render(contents); + markdownDisposeable = renderedContents; + fragment.appendChild($('div.hover-row', undefined, renderedContents.element)); + isEmptyHoverContent = false; + }); + } } }); @@ -438,6 +464,36 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._isChangingDecorations = false; } + private renderMarkerHover(markerHover: MarkerHover): HTMLElement { + const hoverElement = $('div'); + const { source, message, code, relatedInformation } = markerHover.marker; + + const messageElement = dom.append(hoverElement, $('span')); + messageElement.style.whiteSpace = 'pre-wrap'; + messageElement.innerText = message.trim(); + this._editor.applyFontInfo(messageElement); + + if (source || code) { + const detailsElement = dom.append(hoverElement, $('span')); + detailsElement.style.opacity = '0.6'; + detailsElement.style.paddingLeft = '6px'; + detailsElement.innerText = source && code ? `${source}(${code})` : `(${code})`; + } + + if (isNonEmptyArray(relatedInformation)) { + const listElement = dom.append(hoverElement, $('ul')); + for (const { message, resource, startLineNumber, startColumn } of relatedInformation) { + const item = dom.append(listElement, $('li')); + const a = dom.append(item, $('a')); + a.setAttribute('data-href', `${resource.toString(false)}#${startLineNumber},${startColumn}`); + a.innerText = `${basename(resource.path)}(${startLineNumber}, ${startColumn})`; + const messageElement = dom.append(item, $('span')); + messageElement.innerText = `: ${message}`; + } + } + return hoverElement; + } + private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({ className: 'hoverHighlight' }); @@ -450,10 +506,13 @@ function hoverContentsEquals(first: HoverPart[], second: HoverPart[]): boolean { for (let i = 0; i < first.length; i++) { const firstElement = first[i]; const secondElement = second[i]; - if (firstElement instanceof ColorHover) { + if (firstElement instanceof MarkerHover && secondElement instanceof MarkerHover) { + return IMarkerData.makeKey(firstElement.marker) === IMarkerData.makeKey(secondElement.marker); + } + if (firstElement instanceof ColorHover || secondElement instanceof ColorHover) { return false; } - if (secondElement instanceof ColorHover) { + if (firstElement instanceof MarkerHover || secondElement instanceof MarkerHover) { return false; } if (!markedStringsEquals(firstElement.contents, secondElement.contents)) { diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index fa8461b4b37..88dca974e28 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -42,6 +42,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { MenuService } from 'vs/platform/actions/common/menuService'; +import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; +import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsServiceImpl'; export interface IEditorOverrideServices { [index: string]: any; @@ -135,6 +137,8 @@ export module StaticServices { export const modelService = define(IModelService, (o) => new ModelServiceImpl(markerService.get(o), configurationService.get(o), resourcePropertiesService.get(o))); + export const markerDecorationsService = define(IMarkerDecorationsService, (o) => new MarkerDecorationsService(modelService.get(o), markerService.get(o))); + export const editorWorkerService = define(IEditorWorkerService, (o) => new EditorWorkerServiceImpl(modelService.get(o), resourceConfigurationService.get(o))); export const standaloneThemeService = define(IStandaloneThemeService, () => new StandaloneThemeServiceImpl()); diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index 4db5b14227f..564fad93637 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -104,6 +104,8 @@ import { TextResourcePropertiesService } from 'vs/workbench/services/textfile/el import { MultiExtensionManagementService } from 'vs/platform/extensionManagement/node/multiExtensionManagement'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-browser/remoteAuthorityResolverService'; +import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; +import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsServiceImpl'; /** * Services that we require for the Shell @@ -488,6 +490,7 @@ export class WorkbenchShell extends Disposable { serviceCollection.set(IMarkerService, new SyncDescriptor(MarkerService, undefined, true)); + serviceCollection.set(IModeService, new SyncDescriptor(WorkbenchModeServiceImpl)); serviceCollection.set(ITextResourceConfigurationService, new SyncDescriptor(TextResourceConfigurationService)); @@ -496,6 +499,8 @@ export class WorkbenchShell extends Disposable { serviceCollection.set(IModelService, new SyncDescriptor(ModelServiceImpl, undefined, true)); + serviceCollection.set(IMarkerDecorationsService, new SyncDescriptor(MarkerDecorationsService)); + serviceCollection.set(IEditorWorkerService, new SyncDescriptor(EditorWorkerServiceImpl)); serviceCollection.set(IUntitledEditorService, new SyncDescriptor(UntitledEditorService, undefined, true)); -- GitLab