|
| 1 | +/*--------------------------------------------------------------------------------------------- |
| 2 | + * Copyright (c) Microsoft Corporation. All rights reserved. |
| 3 | + * Licensed under the MIT License. See License.txt in the project root for license information. |
| 4 | + *--------------------------------------------------------------------------------------------*/ |
| 5 | + |
| 6 | +import { IMarkerService, IMarker, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; |
| 7 | +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; |
| 8 | +import { URI } from 'vs/base/common/uri'; |
| 9 | +import { IModelDeltaDecoration, ITextModel, IModelDecorationOptions, TrackedRangeStickiness, OverviewRulerLane, IModelDecoration } from 'vs/editor/common/model'; |
| 10 | +import { ClassName } from 'vs/editor/common/model/intervalTree'; |
| 11 | +import { themeColorFromId, ThemeColor } from 'vs/platform/theme/common/themeService'; |
| 12 | +import { overviewRulerWarning, overviewRulerInfo, overviewRulerError } from 'vs/editor/common/view/editorColorRegistry'; |
| 13 | +import { IModelService } from 'vs/editor/common/services/modelService'; |
| 14 | +import { Range } from 'vs/editor/common/core/range'; |
| 15 | +import { keys } from 'vs/base/common/map'; |
| 16 | +import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; |
| 17 | + |
| 18 | +function MODEL_ID(resource: URI): string { |
| 19 | + return resource.toString(); |
| 20 | +} |
| 21 | + |
| 22 | +class MarkerDecorations extends Disposable { |
| 23 | + |
| 24 | + private readonly _markersData: Map<string, IMarker> = new Map<string, IMarker>(); |
| 25 | + |
| 26 | + constructor( |
| 27 | + readonly model: ITextModel |
| 28 | + ) { |
| 29 | + super(); |
| 30 | + this._register(toDisposable(() => { |
| 31 | + this.model.deltaDecorations(keys(this._markersData), []); |
| 32 | + this._markersData.clear(); |
| 33 | + })); |
| 34 | + } |
| 35 | + |
| 36 | + public update(markers: IMarker[], newDecorations: IModelDeltaDecoration[]): void { |
| 37 | + const ids = this.model.deltaDecorations(keys(this._markersData), newDecorations); |
| 38 | + for (let index = 0; index < ids.length; index++) { |
| 39 | + this._markersData.set(ids[index], markers[index]); |
| 40 | + } |
| 41 | + } |
| 42 | + |
| 43 | + getMarker(decoration: IModelDecoration): IMarker | null { |
| 44 | + return this._markersData.get(decoration.id); |
| 45 | + } |
| 46 | +} |
| 47 | + |
| 48 | +export class MarkerDecorationsService extends Disposable implements IMarkerDecorationsService { |
| 49 | + |
| 50 | + _serviceBrand: any; |
| 51 | + |
| 52 | + private readonly _markerDecorations: Map<string, MarkerDecorations> = new Map<string, MarkerDecorations>(); |
| 53 | + |
| 54 | + constructor( |
| 55 | + @IModelService modelService: IModelService, |
| 56 | + @IMarkerService private readonly _markerService: IMarkerService |
| 57 | + ) { |
| 58 | + super(); |
| 59 | + this._register(modelService.onModelAdded(this._onModelAdded, this)); |
| 60 | + this._register(modelService.onModelRemoved(this._onModelRemoved, this)); |
| 61 | + this._register(this._markerService.onMarkerChanged(this._handleMarkerChange, this)); |
| 62 | + } |
| 63 | + |
| 64 | + getMarker(model: ITextModel, decoration: IModelDecoration): IMarker | null { |
| 65 | + const markerDecorations = this._markerDecorations.get(MODEL_ID(model.uri)); |
| 66 | + return markerDecorations ? markerDecorations.getMarker(decoration) : null; |
| 67 | + } |
| 68 | + |
| 69 | + private _handleMarkerChange(changedResources: URI[]): void { |
| 70 | + changedResources.forEach((resource) => { |
| 71 | + const markerDecorations = this._markerDecorations.get(MODEL_ID(resource)); |
| 72 | + if (markerDecorations) { |
| 73 | + this.updateDecorations(markerDecorations); |
| 74 | + } |
| 75 | + }); |
| 76 | + } |
| 77 | + |
| 78 | + private _onModelAdded(model: ITextModel): void { |
| 79 | + const markerDecorations = new MarkerDecorations(model); |
| 80 | + this._markerDecorations.set(MODEL_ID(model.uri), markerDecorations); |
| 81 | + this.updateDecorations(markerDecorations); |
| 82 | + } |
| 83 | + |
| 84 | + private _onModelRemoved(model: ITextModel): void { |
| 85 | + const markerDecorations = this._markerDecorations.get(MODEL_ID(model.uri)); |
| 86 | + if (markerDecorations) { |
| 87 | + markerDecorations.dispose(); |
| 88 | + this._markerDecorations.delete(MODEL_ID(model.uri)); |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + private updateDecorations(markerDecorations: MarkerDecorations): void { |
| 93 | + // Limit to the first 500 errors/warnings |
| 94 | + const markers = this._markerService.read({ resource: markerDecorations.model.uri, take: 500 }); |
| 95 | + let newModelDecorations: IModelDeltaDecoration[] = markers.map((marker) => { |
| 96 | + return { |
| 97 | + range: this._createDecorationRange(markerDecorations.model, marker), |
| 98 | + options: this._createDecorationOption(marker) |
| 99 | + }; |
| 100 | + }); |
| 101 | + markerDecorations.update(markers, newModelDecorations); |
| 102 | + } |
| 103 | + |
| 104 | + private _createDecorationRange(model: ITextModel, rawMarker: IMarker): Range { |
| 105 | + |
| 106 | + let ret = Range.lift(rawMarker); |
| 107 | + |
| 108 | + if (rawMarker.severity === MarkerSeverity.Hint) { |
| 109 | + if (!rawMarker.tags || rawMarker.tags.indexOf(MarkerTag.Unnecessary) === -1) { |
| 110 | + // * never render hints on multiple lines |
| 111 | + // * make enough space for three dots |
| 112 | + ret = ret.setEndPosition(ret.startLineNumber, ret.startColumn + 2); |
| 113 | + } |
| 114 | + } |
| 115 | + |
| 116 | + ret = model.validateRange(ret); |
| 117 | + |
| 118 | + if (ret.isEmpty()) { |
| 119 | + let word = model.getWordAtPosition(ret.getStartPosition()); |
| 120 | + if (word) { |
| 121 | + ret = new Range(ret.startLineNumber, word.startColumn, ret.endLineNumber, word.endColumn); |
| 122 | + } else { |
| 123 | + let maxColumn = model.getLineLastNonWhitespaceColumn(ret.startLineNumber) || |
| 124 | + model.getLineMaxColumn(ret.startLineNumber); |
| 125 | + |
| 126 | + if (maxColumn === 1) { |
| 127 | + // empty line |
| 128 | + // console.warn('marker on empty line:', marker); |
| 129 | + } else if (ret.endColumn >= maxColumn) { |
| 130 | + // behind eol |
| 131 | + ret = new Range(ret.startLineNumber, maxColumn - 1, ret.endLineNumber, maxColumn); |
| 132 | + } else { |
| 133 | + // extend marker to width = 1 |
| 134 | + ret = new Range(ret.startLineNumber, ret.startColumn, ret.endLineNumber, ret.endColumn + 1); |
| 135 | + } |
| 136 | + } |
| 137 | + } else if (rawMarker.endColumn === Number.MAX_VALUE && rawMarker.startColumn === 1 && ret.startLineNumber === ret.endLineNumber) { |
| 138 | + let minColumn = model.getLineFirstNonWhitespaceColumn(rawMarker.startLineNumber); |
| 139 | + if (minColumn < ret.endColumn) { |
| 140 | + ret = new Range(ret.startLineNumber, minColumn, ret.endLineNumber, ret.endColumn); |
| 141 | + rawMarker.startColumn = minColumn; |
| 142 | + } |
| 143 | + } |
| 144 | + return ret; |
| 145 | + } |
| 146 | + |
| 147 | + private _createDecorationOption(marker: IMarker): IModelDecorationOptions { |
| 148 | + |
| 149 | + let className: string; |
| 150 | + let color: ThemeColor | undefined = undefined; |
| 151 | + let zIndex: number; |
| 152 | + let inlineClassName: string | undefined = undefined; |
| 153 | + |
| 154 | + switch (marker.severity) { |
| 155 | + case MarkerSeverity.Hint: |
| 156 | + if (marker.tags && marker.tags.indexOf(MarkerTag.Unnecessary) >= 0) { |
| 157 | + className = ClassName.EditorUnnecessaryDecoration; |
| 158 | + } else { |
| 159 | + className = ClassName.EditorHintDecoration; |
| 160 | + } |
| 161 | + zIndex = 0; |
| 162 | + break; |
| 163 | + case MarkerSeverity.Warning: |
| 164 | + className = ClassName.EditorWarningDecoration; |
| 165 | + color = themeColorFromId(overviewRulerWarning); |
| 166 | + zIndex = 20; |
| 167 | + break; |
| 168 | + case MarkerSeverity.Info: |
| 169 | + className = ClassName.EditorInfoDecoration; |
| 170 | + color = themeColorFromId(overviewRulerInfo); |
| 171 | + zIndex = 10; |
| 172 | + break; |
| 173 | + case MarkerSeverity.Error: |
| 174 | + default: |
| 175 | + className = ClassName.EditorErrorDecoration; |
| 176 | + color = themeColorFromId(overviewRulerError); |
| 177 | + zIndex = 30; |
| 178 | + break; |
| 179 | + } |
| 180 | + |
| 181 | + if (marker.tags) { |
| 182 | + if (marker.tags.indexOf(MarkerTag.Unnecessary) !== -1) { |
| 183 | + inlineClassName = ClassName.EditorUnnecessaryInlineDecoration; |
| 184 | + } |
| 185 | + } |
| 186 | + |
| 187 | + return { |
| 188 | + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, |
| 189 | + className, |
| 190 | + showIfCollapsed: true, |
| 191 | + overviewRuler: { |
| 192 | + color, |
| 193 | + position: OverviewRulerLane.Right |
| 194 | + }, |
| 195 | + zIndex, |
| 196 | + inlineClassName, |
| 197 | + }; |
| 198 | + } |
| 199 | +} |
0 commit comments