676 lines
25 KiB
TypeScript
676 lines
25 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { RunOnceScheduler } from 'vs/base/common/async';
|
|
import { CharCode } from 'vs/base/common/charCode';
|
|
import { Codicon } from 'vs/base/common/codicons';
|
|
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
|
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
|
import { InvisibleCharacters } from 'vs/base/common/strings';
|
|
import 'vs/css!./unicodeHighlighter';
|
|
import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
|
import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
|
import { DeriveFromWorkspaceTrust, deriveFromWorkspaceTrust, EditorOption, InternalUnicodeHighlightOptions, unicodeHighlightConfigKeys } from 'vs/editor/common/config/editorOptions';
|
|
import { Range } from 'vs/editor/common/core/range';
|
|
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
|
import { IModelDecoration, IModelDeltaDecoration, ITextModel, MinimapPosition, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model';
|
|
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
|
import { UnicodeHighlighterOptions, UnicodeHighlighterReason, UnicodeHighlighterReasonKind, UnicodeTextModelHighlighter } from 'vs/editor/common/modes/unicodeTextModelHighlighter';
|
|
import { IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorkerService';
|
|
import { IModeService } from 'vs/editor/common/services/modeService';
|
|
import { HoverAnchor, HoverAnchorType, IEditorHover, IEditorHoverParticipant, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/hoverTypes';
|
|
import { MarkdownHover, renderMarkdownHovers } from 'vs/editor/contrib/hover/markdownHoverParticipant';
|
|
import { BannerController } from 'vs/editor/contrib/unicodeHighlighter/bannerController';
|
|
import * as nls from 'vs/nls';
|
|
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
|
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
|
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
|
import { minimapFindMatch, minimapUnicodeHighlight, overviewRulerFindMatchForeground, overviewRulerUnicodeHighlightForeground } from 'vs/platform/theme/common/colorRegistry';
|
|
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
|
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
|
|
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
|
|
|
|
export const warningIcon = registerIcon('extensions-warning-message', Codicon.warning, nls.localize('warningIcon', 'Icon shown with a warning message in the extensions editor.'));
|
|
|
|
export class UnicodeHighlighter extends Disposable implements IEditorContribution {
|
|
public static readonly ID = 'editor.contrib.unicodeHighlighter';
|
|
|
|
private _highlighter: DocumentUnicodeHighlighter | ViewportUnicodeHighlighter | null = null;
|
|
private _options: InternalUnicodeHighlightOptions;
|
|
|
|
private readonly _bannerController: BannerController;
|
|
private _bannerClosed: boolean = false;
|
|
|
|
constructor(
|
|
private readonly _editor: ICodeEditor,
|
|
@IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService,
|
|
@IWorkspaceTrustManagementService private readonly _workspaceTrustService: IWorkspaceTrustManagementService,
|
|
@IInstantiationService instantiationService: IInstantiationService,
|
|
) {
|
|
super();
|
|
|
|
this._bannerController = this._register(instantiationService.createInstance(BannerController, _editor));
|
|
|
|
this._register(this._editor.onDidChangeModel(() => {
|
|
this._bannerClosed = false;
|
|
this._updateHighlighter();
|
|
}));
|
|
|
|
this._options = _editor.getOption(EditorOption.unicodeHighlighting);
|
|
|
|
this._register(_workspaceTrustService.onDidChangeTrust(e => {
|
|
this._updateHighlighter();
|
|
}));
|
|
|
|
this._register(_editor.onDidChangeConfiguration(e => {
|
|
if (e.hasChanged(EditorOption.unicodeHighlighting)) {
|
|
this._options = _editor.getOption(EditorOption.unicodeHighlighting);
|
|
this._updateHighlighter();
|
|
}
|
|
}));
|
|
|
|
this._updateHighlighter();
|
|
}
|
|
|
|
public override dispose(): void {
|
|
if (this._highlighter) {
|
|
this._highlighter.dispose();
|
|
this._highlighter = null;
|
|
}
|
|
super.dispose();
|
|
}
|
|
|
|
private readonly _updateState = (state: IUnicodeHighlightsResult | null): void => {
|
|
if (state && state.hasMore) {
|
|
if (this._bannerClosed) {
|
|
return;
|
|
}
|
|
|
|
// This document contains many non-basic ASCII characters.
|
|
const max = Math.max(state.ambiguousCharacterCount, state.nonBasicAsciiCharacterCount, state.invisibleCharacterCount);
|
|
|
|
let data;
|
|
if (state.nonBasicAsciiCharacterCount >= max) {
|
|
data = {
|
|
message: nls.localize('unicodeHighlighting.thisDocumentHasManyNonBasicAsciiUnicodeCharacters', 'This document contains many non-basic ASCII unicode characters'),
|
|
command: new DisableHighlightingOfNonBasicAsciiCharactersAction(),
|
|
};
|
|
} else if (state.ambiguousCharacterCount >= max) {
|
|
data = {
|
|
message: nls.localize('unicodeHighlighting.thisDocumentHasManyAmbiguousUnicodeCharacters', 'This document contains many ambiguous unicode characters'),
|
|
command: new DisableHighlightingOfAmbiguousCharactersAction(),
|
|
};
|
|
} else if (state.invisibleCharacterCount >= max) {
|
|
data = {
|
|
message: nls.localize('unicodeHighlighting.thisDocumentHasManyInvisibleUnicodeCharacters', 'This document contains many invisible unicode characters'),
|
|
command: new DisableHighlightingOfInvisibleCharactersAction(),
|
|
};
|
|
} else {
|
|
throw new Error('Unreachable');
|
|
}
|
|
|
|
this._bannerController.show({
|
|
id: 'unicodeHighlightBanner',
|
|
message: data.message,
|
|
icon: warningIcon,
|
|
actions: [
|
|
{
|
|
label: data.command.shortLabel,
|
|
href: `command:${data.command.id}`
|
|
}
|
|
],
|
|
onClose: () => {
|
|
this._bannerClosed = true;
|
|
},
|
|
});
|
|
} else {
|
|
this._bannerController.hide();
|
|
}
|
|
};
|
|
|
|
private _updateHighlighter(): void {
|
|
this._updateState(null);
|
|
|
|
if (this._highlighter) {
|
|
this._highlighter.dispose();
|
|
this._highlighter = null;
|
|
}
|
|
if (!this._editor.hasModel()) {
|
|
return;
|
|
}
|
|
const options = resolveOptions(this._workspaceTrustService.isWorkspaceTrusted(), this._options);
|
|
|
|
if (
|
|
[
|
|
options.nonBasicASCII,
|
|
options.ambiguousCharacters,
|
|
options.invisibleCharacters,
|
|
].every((option) => option === false)
|
|
) {
|
|
// Don't do anything if the feature is fully disabled
|
|
return;
|
|
}
|
|
|
|
const highlightOptions: UnicodeHighlighterOptions = {
|
|
nonBasicASCII: options.nonBasicASCII,
|
|
ambiguousCharacters: options.ambiguousCharacters,
|
|
invisibleCharacters: options.invisibleCharacters,
|
|
includeComments: options.includeComments,
|
|
allowedCodePoints: Array.from(options.allowedCharacters).map(c => c.codePointAt(0)!),
|
|
};
|
|
|
|
if (this._editorWorkerService.canComputeUnicodeHighlights(this._editor.getModel().uri)) {
|
|
this._highlighter = new DocumentUnicodeHighlighter(this._editor, highlightOptions, this._updateState, this._editorWorkerService);
|
|
} else {
|
|
this._highlighter = new ViewportUnicodeHighlighter(this._editor, highlightOptions, this._updateState);
|
|
}
|
|
}
|
|
|
|
public getDecorationInfo(decorationId: string): UnicodeHighlighterDecorationInfo | null {
|
|
if (this._highlighter) {
|
|
return this._highlighter.getDecorationInfo(decorationId);
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export interface UnicodeHighlighterDecorationInfo {
|
|
reason: UnicodeHighlighterReason;
|
|
}
|
|
|
|
type RemoveDeriveFromWorkspaceTrust<T> = T extends DeriveFromWorkspaceTrust ? never : T;
|
|
type ResolvedOptions = { [TKey in keyof InternalUnicodeHighlightOptions]: RemoveDeriveFromWorkspaceTrust<InternalUnicodeHighlightOptions[TKey]> };
|
|
|
|
function resolveOptions(trusted: boolean, options: InternalUnicodeHighlightOptions): ResolvedOptions {
|
|
let defaults;
|
|
if (trusted) {
|
|
defaults = {
|
|
nonBasicASCII: false,
|
|
ambiguousCharacters: true,
|
|
invisibleCharacters: true,
|
|
includeComments: true,
|
|
};
|
|
} else {
|
|
defaults = {
|
|
nonBasicASCII: true,
|
|
ambiguousCharacters: true,
|
|
invisibleCharacters: true,
|
|
includeComments: false,
|
|
};
|
|
}
|
|
|
|
return {
|
|
nonBasicASCII: options.nonBasicASCII !== deriveFromWorkspaceTrust ? options.nonBasicASCII : defaults.nonBasicASCII,
|
|
ambiguousCharacters: options.ambiguousCharacters !== deriveFromWorkspaceTrust ? options.ambiguousCharacters : defaults.ambiguousCharacters,
|
|
invisibleCharacters: options.invisibleCharacters !== deriveFromWorkspaceTrust ? options.invisibleCharacters : defaults.invisibleCharacters,
|
|
includeComments: options.includeComments !== deriveFromWorkspaceTrust ? options.includeComments : defaults.includeComments,
|
|
allowedCharacters: options.allowedCharacters ?? [],
|
|
};
|
|
}
|
|
|
|
class DocumentUnicodeHighlighter extends Disposable {
|
|
private readonly _model: ITextModel = this._editor.getModel();
|
|
private readonly _updateSoon: RunOnceScheduler;
|
|
private _decorationIds = new Set<string>();
|
|
|
|
constructor(
|
|
private readonly _editor: IActiveCodeEditor,
|
|
private readonly _options: UnicodeHighlighterOptions,
|
|
private readonly _updateState: (state: IUnicodeHighlightsResult | null) => void,
|
|
@IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService,
|
|
) {
|
|
super();
|
|
this._updateSoon = this._register(new RunOnceScheduler(() => this._update(), 250));
|
|
|
|
this._register(this._editor.onDidChangeModelContent(() => {
|
|
this._updateSoon.schedule();
|
|
}));
|
|
|
|
this._updateSoon.schedule();
|
|
}
|
|
|
|
public override dispose() {
|
|
this._decorationIds = new Set(this._model.deltaDecorations(Array.from(this._decorationIds), []));
|
|
super.dispose();
|
|
}
|
|
|
|
private _update(): void {
|
|
if (!this._model.mightContainNonBasicASCII()) {
|
|
this._decorationIds = new Set(this._editor.deltaDecorations(Array.from(this._decorationIds), []));
|
|
return;
|
|
}
|
|
|
|
const modelVersionId = this._model.getVersionId();
|
|
this._editorWorkerService
|
|
.computedUnicodeHighlights(this._model.uri, this._options)
|
|
.then((info) => {
|
|
if (this._model.getVersionId() !== modelVersionId) {
|
|
// model changed in the meantime
|
|
return;
|
|
}
|
|
this._updateState(info);
|
|
|
|
const decorations: IModelDeltaDecoration[] = [];
|
|
if (!info.hasMore) {
|
|
// Don't show decoration if there are too many.
|
|
// In this case, a banner is shown.
|
|
for (const range of info.ranges) {
|
|
decorations.push({ range: range, options: this._options.includeComments ? DECORATION : DECORATION_HIDE_IN_COMMENTS });
|
|
}
|
|
}
|
|
this._decorationIds = new Set(this._editor.deltaDecorations(
|
|
Array.from(this._decorationIds),
|
|
decorations
|
|
));
|
|
});
|
|
}
|
|
|
|
public getDecorationInfo(decorationId: string): UnicodeHighlighterDecorationInfo | null {
|
|
if (!this._decorationIds.has(decorationId)) {
|
|
return null;
|
|
}
|
|
const range = this._editor.getModel().getDecorationRange(decorationId)!;
|
|
const text = this._editor.getModel().getValueInRange(range);
|
|
return {
|
|
reason: computeReason(text, this._options)!,
|
|
};
|
|
}
|
|
}
|
|
|
|
class ViewportUnicodeHighlighter extends Disposable {
|
|
|
|
private readonly _model: ITextModel = this._editor.getModel();
|
|
private readonly _updateSoon: RunOnceScheduler;
|
|
private _decorationIds = new Set<string>();
|
|
|
|
constructor(
|
|
private readonly _editor: IActiveCodeEditor,
|
|
private readonly _options: UnicodeHighlighterOptions,
|
|
private readonly _updateState: (state: IUnicodeHighlightsResult | null) => void,
|
|
) {
|
|
super();
|
|
|
|
this._updateSoon = this._register(new RunOnceScheduler(() => this._update(), 250));
|
|
|
|
this._register(this._editor.onDidLayoutChange(() => {
|
|
this._updateSoon.schedule();
|
|
}));
|
|
this._register(this._editor.onDidScrollChange(() => {
|
|
this._updateSoon.schedule();
|
|
}));
|
|
this._register(this._editor.onDidChangeHiddenAreas(() => {
|
|
this._updateSoon.schedule();
|
|
}));
|
|
this._register(this._editor.onDidChangeModelContent(() => {
|
|
this._updateSoon.schedule();
|
|
}));
|
|
|
|
this._updateSoon.schedule();
|
|
}
|
|
|
|
public override dispose() {
|
|
this._decorationIds = new Set(this._model.deltaDecorations(Array.from(this._decorationIds), []));
|
|
super.dispose();
|
|
}
|
|
|
|
private _update(): void {
|
|
if (!this._model.mightContainNonBasicASCII()) {
|
|
this._decorationIds = new Set(this._editor.deltaDecorations(Array.from(this._decorationIds), []));
|
|
return;
|
|
}
|
|
|
|
const ranges = this._editor.getVisibleRanges();
|
|
const decorations: IModelDeltaDecoration[] = [];
|
|
const totalResult: IUnicodeHighlightsResult = {
|
|
ranges: [],
|
|
ambiguousCharacterCount: 0,
|
|
invisibleCharacterCount: 0,
|
|
nonBasicAsciiCharacterCount: 0,
|
|
hasMore: false,
|
|
};
|
|
for (const range of ranges) {
|
|
const result = UnicodeTextModelHighlighter.computeUnicodeHighlights(this._model, this._options, range);
|
|
for (const r of result.ranges) {
|
|
totalResult.ranges.push(r);
|
|
}
|
|
totalResult.ambiguousCharacterCount += totalResult.ambiguousCharacterCount;
|
|
totalResult.invisibleCharacterCount += totalResult.invisibleCharacterCount;
|
|
totalResult.nonBasicAsciiCharacterCount += totalResult.nonBasicAsciiCharacterCount;
|
|
totalResult.hasMore = totalResult.hasMore || result.hasMore;
|
|
}
|
|
|
|
if (!totalResult.hasMore) {
|
|
// Don't show decorations if there are too many.
|
|
// A banner will be shown instead.
|
|
for (const range of totalResult.ranges) {
|
|
decorations.push({ range, options: this._options.includeComments ? DECORATION : DECORATION_HIDE_IN_COMMENTS });
|
|
}
|
|
}
|
|
this._updateState(totalResult);
|
|
|
|
this._decorationIds = new Set(this._editor.deltaDecorations(Array.from(this._decorationIds), decorations));
|
|
}
|
|
|
|
public getDecorationInfo(decorationId: string): UnicodeHighlighterDecorationInfo | null {
|
|
if (!this._decorationIds.has(decorationId)) {
|
|
return null;
|
|
}
|
|
const range = this._editor.getModel().getDecorationRange(decorationId)!;
|
|
const text = this._editor.getModel().getValueInRange(range);
|
|
return {
|
|
reason: computeReason(text, this._options)!,
|
|
};
|
|
}
|
|
}
|
|
|
|
export class UnicodeHighlighterHover implements IHoverPart {
|
|
constructor(
|
|
public readonly owner: IEditorHoverParticipant<UnicodeHighlighterHover>,
|
|
public readonly range: Range,
|
|
public readonly decoration: IModelDecoration
|
|
) { }
|
|
|
|
public isValidForHoverAnchor(anchor: HoverAnchor): boolean {
|
|
return (
|
|
anchor.type === HoverAnchorType.Range
|
|
&& this.range.startColumn <= anchor.range.startColumn
|
|
&& this.range.endColumn >= anchor.range.endColumn
|
|
);
|
|
}
|
|
}
|
|
|
|
export class UnicodeHighlighterHoverParticipant implements IEditorHoverParticipant<MarkdownHover> {
|
|
|
|
constructor(
|
|
private readonly _editor: ICodeEditor,
|
|
private readonly _hover: IEditorHover,
|
|
@IModeService private readonly _modeService: IModeService,
|
|
@IOpenerService private readonly _openerService: IOpenerService,
|
|
) {
|
|
}
|
|
|
|
computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): MarkdownHover[] {
|
|
if (!this._editor.hasModel() || anchor.type !== HoverAnchorType.Range) {
|
|
return [];
|
|
}
|
|
|
|
const model = this._editor.getModel();
|
|
|
|
const unicodeHighlighter = this._editor.getContribution<UnicodeHighlighter>(UnicodeHighlighter.ID);
|
|
|
|
|
|
const result: MarkdownHover[] = [];
|
|
let index = 300;
|
|
for (const d of lineDecorations) {
|
|
|
|
const highlightInfo = unicodeHighlighter.getDecorationInfo(d.id);
|
|
if (!highlightInfo) {
|
|
continue;
|
|
}
|
|
const char = model.getValueInRange(d.range);
|
|
// text refers to a single character.
|
|
const codePoint = char.codePointAt(0)!;
|
|
|
|
function formatCodePoint(codePoint: number) {
|
|
let value = `\`U+${codePoint.toString(16).padStart(4, '0')}\``;
|
|
if (!InvisibleCharacters.isInvisibleCharacter(codePoint)) {
|
|
// Don't render any control characters or any invisible characters, as they cannot be seen anyways.
|
|
value += ` "${`${renderCodePointAsInlineCode(codePoint)}`}"`;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
const codePointStr = formatCodePoint(codePoint);
|
|
|
|
let reason: string;
|
|
switch (highlightInfo.reason.kind) {
|
|
case UnicodeHighlighterReasonKind.Ambiguous:
|
|
reason = nls.localize(
|
|
'unicodeHighlight.characterIsAmbiguous',
|
|
'The character {0} could be confused with the character {1}, which is more common in source code.',
|
|
codePointStr,
|
|
formatCodePoint(highlightInfo.reason.confusableWith.codePointAt(0)!)
|
|
);
|
|
break;
|
|
|
|
case UnicodeHighlighterReasonKind.Invisible:
|
|
reason = nls.localize(
|
|
'unicodeHighlight.characterIsInvisible',
|
|
'The character {0} is invisible.',
|
|
codePointStr
|
|
);
|
|
break;
|
|
|
|
case UnicodeHighlighterReasonKind.NonBasicAscii:
|
|
reason = nls.localize(
|
|
'unicodeHighlight.characterIsNonBasicAscii',
|
|
'The character {0} is not a basic ASCII character.',
|
|
codePoint
|
|
);
|
|
break;
|
|
}
|
|
|
|
const adjustSettingsArgs: ShowExcludeOptionsArgs = {
|
|
codePoint: codePoint,
|
|
reason: highlightInfo.reason.kind,
|
|
};
|
|
|
|
const adjustSettings = nls.localize('unicodeHighlight.adjustSettings', 'Adjust settings');
|
|
const contents: Array<IMarkdownString> = [{
|
|
value: `${reason} [${adjustSettings}](command:${ShowExcludeOptions.ID}?${encodeURIComponent(JSON.stringify(adjustSettingsArgs))})`,
|
|
isTrusted: true,
|
|
}];
|
|
|
|
result.push(new MarkdownHover(this, d.range, contents, index++));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public renderHoverParts(hoverParts: MarkdownHover[], fragment: DocumentFragment, statusBar: IEditorHoverStatusBar): IDisposable {
|
|
return renderMarkdownHovers(hoverParts, fragment, this._editor, this._hover, this._modeService, this._openerService);
|
|
}
|
|
}
|
|
|
|
function renderCodePointAsInlineCode(codePoint: number): string {
|
|
if (codePoint === CharCode.BackTick) {
|
|
return '`` ` ``';
|
|
}
|
|
return '`' + String.fromCodePoint(codePoint) + '`';
|
|
}
|
|
|
|
function computeReason(char: string, options: UnicodeHighlighterOptions): UnicodeHighlighterReason | null {
|
|
return UnicodeTextModelHighlighter.computeUnicodeHighlightReason(char, options);
|
|
}
|
|
|
|
const DECORATION_HIDE_IN_COMMENTS = ModelDecorationOptions.register({
|
|
description: 'unicode-highlight',
|
|
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
|
className: 'unicode-highlight',
|
|
showIfCollapsed: true,
|
|
overviewRuler: {
|
|
color: themeColorFromId(overviewRulerUnicodeHighlightForeground),
|
|
position: OverviewRulerLane.Center
|
|
},
|
|
minimap: {
|
|
color: themeColorFromId(minimapUnicodeHighlight),
|
|
position: MinimapPosition.Inline
|
|
},
|
|
hideInCommentTokens: true
|
|
});
|
|
|
|
const DECORATION = ModelDecorationOptions.register({
|
|
description: 'unicode-highlight',
|
|
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
|
className: 'unicode-highlight',
|
|
showIfCollapsed: true,
|
|
overviewRuler: {
|
|
color: themeColorFromId(overviewRulerFindMatchForeground),
|
|
position: OverviewRulerLane.Center
|
|
},
|
|
minimap: {
|
|
color: themeColorFromId(minimapFindMatch),
|
|
position: MinimapPosition.Inline
|
|
}
|
|
});
|
|
|
|
interface IDisableUnicodeHighlightAction {
|
|
shortLabel: string;
|
|
}
|
|
|
|
export class DisableHighlightingOfAmbiguousCharactersAction extends EditorAction implements IDisableUnicodeHighlightAction {
|
|
public static ID = 'editor.action.unicodeHighlight.disableHighlightingOfAmbiguousCharacters';
|
|
public readonly shortLabel = nls.localize('unicodeHighlight.disableHighlightingOfAmbiguousCharacters.shortLabel', '');
|
|
constructor() {
|
|
super({
|
|
id: DisableHighlightingOfAmbiguousCharactersAction.ID,
|
|
label: nls.localize('action.unicodeHighlight.disableHighlightingOfAmbiguousCharacters', 'Disable Ambiguous Highlight'),
|
|
alias: 'Disable highlighting of ambiguous characters',
|
|
precondition: undefined
|
|
});
|
|
}
|
|
|
|
public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor, args: any): Promise<void> {
|
|
let configurationService = accessor?.get(IConfigurationService);
|
|
if (configurationService) {
|
|
this.runAction(configurationService);
|
|
}
|
|
}
|
|
|
|
public async runAction(configurationService: IConfigurationService): Promise<void> {
|
|
await configurationService.updateValue(unicodeHighlightConfigKeys.ambiguousCharacters, false, ConfigurationTarget.USER);
|
|
}
|
|
}
|
|
|
|
export class DisableHighlightingOfInvisibleCharactersAction extends EditorAction implements IDisableUnicodeHighlightAction {
|
|
public static ID = 'editor.action.unicodeHighlight.disableHighlightingOfInvisibleCharacters';
|
|
public readonly shortLabel = nls.localize('unicodeHighlight.disableHighlightingOfInvisibleCharacters.shortLabel', 'Disable Invisible Highlight');
|
|
constructor() {
|
|
super({
|
|
id: DisableHighlightingOfInvisibleCharactersAction.ID,
|
|
label: nls.localize('action.unicodeHighlight.disableHighlightingOfInvisibleCharacters', 'Disable highlighting of invisible characters'),
|
|
alias: 'Disable highlighting of invisible characters',
|
|
precondition: undefined
|
|
});
|
|
}
|
|
|
|
public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor, args: any): Promise<void> {
|
|
let configurationService = accessor?.get(IConfigurationService);
|
|
if (configurationService) {
|
|
this.runAction(configurationService);
|
|
}
|
|
}
|
|
|
|
public async runAction(configurationService: IConfigurationService): Promise<void> {
|
|
await configurationService.updateValue(unicodeHighlightConfigKeys.invisibleCharacters, false, ConfigurationTarget.USER);
|
|
}
|
|
}
|
|
|
|
export class DisableHighlightingOfNonBasicAsciiCharactersAction extends EditorAction implements IDisableUnicodeHighlightAction {
|
|
public static ID = 'editor.action.unicodeHighlight.disableHighlightingOfNonBasicAsciiCharacters';
|
|
public readonly shortLabel = nls.localize('unicodeHighlight.disableHighlightingOfNonBasicAsciiCharacters.shortLabel', 'Disable Non ASCII Highlight');
|
|
constructor() {
|
|
super({
|
|
id: DisableHighlightingOfNonBasicAsciiCharactersAction.ID,
|
|
label: nls.localize('action.unicodeHighlight.dhowDisableHighlightingOfNonBasicAsciiCharacters', 'Disable highlighting of non basic ASCII characters'),
|
|
alias: 'Disable highlighting of non basic ASCII characters',
|
|
precondition: undefined
|
|
});
|
|
}
|
|
|
|
public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor, args: any): Promise<void> {
|
|
let configurationService = accessor?.get(IConfigurationService);
|
|
if (configurationService) {
|
|
this.runAction(configurationService);
|
|
}
|
|
}
|
|
|
|
public async runAction(configurationService: IConfigurationService): Promise<void> {
|
|
await configurationService.updateValue(unicodeHighlightConfigKeys.nonBasicASCII, false, ConfigurationTarget.USER);
|
|
}
|
|
}
|
|
|
|
interface ShowExcludeOptionsArgs {
|
|
codePoint: number;
|
|
reason: UnicodeHighlighterReason['kind'];
|
|
}
|
|
|
|
export class ShowExcludeOptions extends EditorAction {
|
|
public static ID = 'editor.action.unicodeHighlight.showExcludeOptions';
|
|
constructor() {
|
|
super({
|
|
id: ShowExcludeOptions.ID,
|
|
label: nls.localize('action.unicodeHighlight.showExcludeOptions', "Show Exclude Options"),
|
|
alias: 'Show Exclude Options',
|
|
precondition: undefined
|
|
});
|
|
}
|
|
|
|
public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor, args: any): Promise<void> {
|
|
const { codePoint, reason } = args as ShowExcludeOptionsArgs;
|
|
|
|
const char = String.fromCodePoint(codePoint);
|
|
|
|
const quickPickService = accessor!.get(IQuickInputService);
|
|
const configurationService = accessor!.get(IConfigurationService);
|
|
|
|
interface ExtendedOptions extends IQuickPickItem {
|
|
run(): Promise<void>;
|
|
}
|
|
|
|
const options: ExtendedOptions[] = [
|
|
{
|
|
label: nls.localize('unicodeHighlight.excludeCharFromBeingHighlighted', 'Exclude {0} from being highlighted', `U+${codePoint.toString(16)} "${char}"`),
|
|
run: async () => {
|
|
const existingValue = configurationService.getValue(unicodeHighlightConfigKeys.allowedCharacters);
|
|
let value: string;
|
|
if (typeof existingValue === 'string') {
|
|
value = existingValue;
|
|
} else {
|
|
value = '';
|
|
}
|
|
|
|
value += char;
|
|
await configurationService.updateValue(unicodeHighlightConfigKeys.allowedCharacters, value, ConfigurationTarget.USER);
|
|
}
|
|
},
|
|
];
|
|
|
|
if (reason === UnicodeHighlighterReasonKind.Ambiguous) {
|
|
const action = new DisableHighlightingOfAmbiguousCharactersAction();
|
|
options.push({ label: action.label, run: async () => action.runAction(configurationService) });
|
|
}
|
|
else if (reason === UnicodeHighlighterReasonKind.Invisible) {
|
|
const action = new DisableHighlightingOfInvisibleCharactersAction();
|
|
options.push({ label: action.label, run: async () => action.runAction(configurationService) });
|
|
}
|
|
else if (reason === UnicodeHighlighterReasonKind.NonBasicAscii) {
|
|
const action = new DisableHighlightingOfNonBasicAsciiCharactersAction();
|
|
options.push({ label: action.label, run: async () => action.runAction(configurationService) });
|
|
} else {
|
|
expectNever(reason);
|
|
}
|
|
|
|
const result = await quickPickService.pick(
|
|
options,
|
|
{ title: nls.localize('unicodeHighlight.configureUnicodeHighlightOptions', 'Configure Unicode Highlight Options') }
|
|
);
|
|
|
|
if (result) {
|
|
await result.run();
|
|
}
|
|
}
|
|
}
|
|
|
|
function expectNever(value: never) {
|
|
throw new Error(`Unexpected value: ${value}`);
|
|
}
|
|
|
|
registerEditorAction(DisableHighlightingOfAmbiguousCharactersAction);
|
|
registerEditorAction(DisableHighlightingOfInvisibleCharactersAction);
|
|
registerEditorAction(DisableHighlightingOfNonBasicAsciiCharactersAction);
|
|
registerEditorAction(ShowExcludeOptions);
|
|
registerEditorContribution(UnicodeHighlighter.ID, UnicodeHighlighter);
|