mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-10 18:22:34 -05:00
Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2 (#8911)
* Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2 * update distro * fix layering * update distro * fix tests
This commit is contained in:
@@ -8,7 +8,7 @@ import './accessibility/accessibility';
|
||||
import './diffEditorHelper';
|
||||
import './inspectKeybindings';
|
||||
import './largeFileOptimizations';
|
||||
import './inspectTMScopes/inspectTMScopes';
|
||||
import './inspectEditorTokens/inspectEditorTokens';
|
||||
import './toggleMinimap';
|
||||
import './toggleMultiCursorModifier';
|
||||
import './toggleRenderControlCharacter';
|
||||
|
||||
@@ -54,7 +54,6 @@ export abstract class SimpleFindWidget extends Widget {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
/* tslint:disable-next-line:no-unused-expression */
|
||||
new RegExp(value);
|
||||
return null;
|
||||
} catch (e) {
|
||||
|
||||
@@ -3,37 +3,37 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.tm-inspect-widget {
|
||||
.token-inspect-widget {
|
||||
z-index: 50;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.tm-token {
|
||||
.tiw-token {
|
||||
font-family: var(--monaco-monospace-font);
|
||||
}
|
||||
|
||||
.tm-metadata-separator {
|
||||
.tiw-metadata-separator {
|
||||
height: 1px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.tm-token-length {
|
||||
.tiw-token-length {
|
||||
font-weight: normal;
|
||||
font-size: 60%;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.tm-metadata-table {
|
||||
.tiw-metadata-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tm-metadata-value {
|
||||
.tiw-metadata-value {
|
||||
font-family: var(--monaco-monospace-font);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.tm-theme-selector {
|
||||
.tiw-theme-selector {
|
||||
font-family: var(--monaco-monospace-font);
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./inspectEditorTokens';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { ContentWidgetPositionPreference, IActiveCodeEditor, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { FontStyle, LanguageIdentifier, StandardTokenType, TokenMetadata, DocumentSemanticTokensProviderRegistry, SemanticTokensLegend, SemanticTokens } from 'vs/editor/common/modes';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { HIGH_CONTRAST, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { findMatchingThemeRule } from 'vs/workbench/services/textMate/common/TMHelper';
|
||||
import { ITextMateService, IGrammar, IToken, StackElement } from 'vs/workbench/services/textMate/common/textMateService';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
|
||||
class InspectEditorTokensController extends Disposable implements IEditorContribution {
|
||||
|
||||
public static readonly ID = 'editor.contrib.inspectEditorTokens';
|
||||
|
||||
public static get(editor: ICodeEditor): InspectEditorTokensController {
|
||||
return editor.getContribution<InspectEditorTokensController>(InspectEditorTokensController.ID);
|
||||
}
|
||||
|
||||
private _editor: ICodeEditor;
|
||||
private _textMateService: ITextMateService;
|
||||
private _themeService: IWorkbenchThemeService;
|
||||
private _modeService: IModeService;
|
||||
private _notificationService: INotificationService;
|
||||
private _widget: InspectEditorTokensWidget | null;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
@ITextMateService textMateService: ITextMateService,
|
||||
@IModeService modeService: IModeService,
|
||||
@IWorkbenchThemeService themeService: IWorkbenchThemeService,
|
||||
@INotificationService notificationService: INotificationService
|
||||
) {
|
||||
super();
|
||||
this._editor = editor;
|
||||
this._textMateService = textMateService;
|
||||
this._themeService = themeService;
|
||||
this._modeService = modeService;
|
||||
this._notificationService = notificationService;
|
||||
this._widget = null;
|
||||
|
||||
this._register(this._editor.onDidChangeModel((e) => this.stop()));
|
||||
this._register(this._editor.onDidChangeModelLanguage((e) => this.stop()));
|
||||
this._register(this._editor.onKeyUp((e) => e.keyCode === KeyCode.Escape && this.stop()));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.stop();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public launch(): void {
|
||||
if (this._widget) {
|
||||
return;
|
||||
}
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
this._widget = new InspectEditorTokensWidget(this._editor, this._textMateService, this._modeService, this._themeService, this._notificationService);
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
if (this._widget) {
|
||||
this._widget.dispose();
|
||||
this._widget = null;
|
||||
}
|
||||
}
|
||||
|
||||
public toggle(): void {
|
||||
if (!this._widget) {
|
||||
this.launch();
|
||||
} else {
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InspectEditorTokens extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.inspectEditorTokens',
|
||||
label: nls.localize('inspectEditorTokens', "Developer: Inspect Editor Tokens and Scopes"),
|
||||
alias: 'Developer: Inspect Editor Tokens and Scopes',
|
||||
precondition: undefined
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
let controller = InspectEditorTokensController.get(editor);
|
||||
if (controller) {
|
||||
controller.toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ITextMateTokenInfo {
|
||||
token: IToken;
|
||||
metadata: IDecodedMetadata;
|
||||
}
|
||||
|
||||
interface ISemanticTokenInfo {
|
||||
type: string;
|
||||
modifiers: string[];
|
||||
range: Range;
|
||||
metadata: IDecodedMetadata
|
||||
}
|
||||
|
||||
interface IDecodedMetadata {
|
||||
languageIdentifier: LanguageIdentifier;
|
||||
tokenType: StandardTokenType;
|
||||
fontStyle: string;
|
||||
foreground?: string;
|
||||
background?: string;
|
||||
}
|
||||
|
||||
function renderTokenText(tokenText: string): string {
|
||||
if (tokenText.length > 40) {
|
||||
tokenText = tokenText.substr(0, 20) + '…' + tokenText.substr(tokenText.length - 20);
|
||||
}
|
||||
let result: string = '';
|
||||
for (let charIndex = 0, len = tokenText.length; charIndex < len; charIndex++) {
|
||||
let charCode = tokenText.charCodeAt(charIndex);
|
||||
switch (charCode) {
|
||||
case CharCode.Tab:
|
||||
result += '→';
|
||||
break;
|
||||
|
||||
case CharCode.Space:
|
||||
result += '·';
|
||||
break;
|
||||
|
||||
case CharCode.LessThan:
|
||||
result += '<';
|
||||
break;
|
||||
|
||||
case CharCode.GreaterThan:
|
||||
result += '>';
|
||||
break;
|
||||
|
||||
case CharCode.Ampersand:
|
||||
result += '&';
|
||||
break;
|
||||
|
||||
default:
|
||||
result += String.fromCharCode(charCode);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
type SemanticTokensResult = { tokens: SemanticTokens, legend: SemanticTokensLegend };
|
||||
|
||||
class InspectEditorTokensWidget extends Disposable implements IContentWidget {
|
||||
|
||||
private static readonly _ID = 'editor.contrib.inspectEditorTokensWidget';
|
||||
|
||||
// Editor.IContentWidget.allowEditorOverflow
|
||||
public readonly allowEditorOverflow = true;
|
||||
|
||||
private _isDisposed: boolean;
|
||||
private readonly _editor: IActiveCodeEditor;
|
||||
private readonly _modeService: IModeService;
|
||||
private readonly _themeService: IWorkbenchThemeService;
|
||||
private readonly _notificationService: INotificationService;
|
||||
private readonly _model: ITextModel;
|
||||
private readonly _domNode: HTMLElement;
|
||||
private readonly _grammar: Promise<IGrammar | null>;
|
||||
private readonly _semanticTokens: Promise<SemanticTokensResult | null>;
|
||||
private readonly _currentRequestCancellationTokenSource: CancellationTokenSource;
|
||||
|
||||
constructor(
|
||||
editor: IActiveCodeEditor,
|
||||
textMateService: ITextMateService,
|
||||
modeService: IModeService,
|
||||
themeService: IWorkbenchThemeService,
|
||||
notificationService: INotificationService
|
||||
) {
|
||||
super();
|
||||
this._isDisposed = false;
|
||||
this._editor = editor;
|
||||
this._modeService = modeService;
|
||||
this._themeService = themeService;
|
||||
this._notificationService = notificationService;
|
||||
this._model = this._editor.getModel();
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.className = 'token-inspect-widget';
|
||||
this._currentRequestCancellationTokenSource = new CancellationTokenSource();
|
||||
this._grammar = textMateService.createGrammar(this._model.getLanguageIdentifier().language);
|
||||
this._semanticTokens = this._computeSemanticTokens();
|
||||
this._beginCompute(this._editor.getPosition());
|
||||
this._register(this._editor.onDidChangeCursorPosition((e) => this._beginCompute(this._editor.getPosition())));
|
||||
this._editor.addContentWidget(this);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._isDisposed = true;
|
||||
this._editor.removeContentWidget(this);
|
||||
this._currentRequestCancellationTokenSource.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return InspectEditorTokensWidget._ID;
|
||||
}
|
||||
|
||||
private _beginCompute(position: Position): void {
|
||||
dom.clearNode(this._domNode);
|
||||
this._domNode.appendChild(document.createTextNode(nls.localize('inspectTMScopesWidget.loading', "Loading...")));
|
||||
|
||||
Promise.all([this._grammar, this._semanticTokens]).then(([grammar, semanticTokens]) => {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
let text = this._compute(grammar, semanticTokens, position);
|
||||
this._domNode.innerHTML = text;
|
||||
this._editor.layoutContentWidget(this);
|
||||
}, (err) => {
|
||||
this._notificationService.warn(err);
|
||||
|
||||
setTimeout(() => {
|
||||
InspectEditorTokensController.get(this._editor).stop();
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private _compute(grammar: IGrammar | null, semanticTokens: SemanticTokensResult | null, position: Position): string {
|
||||
const textMateTokenInfo = grammar && this._getTokensAtPosition(grammar, position);
|
||||
const semanticTokenInfo = semanticTokens && this._getSemanticTokenAtPosition(semanticTokens, position);
|
||||
|
||||
let tokenText;
|
||||
let metadata: IDecodedMetadata | undefined;
|
||||
let primary: IDecodedMetadata | undefined;
|
||||
if (textMateTokenInfo) {
|
||||
let tokenStartIndex = textMateTokenInfo.token.startIndex;
|
||||
let tokenEndIndex = textMateTokenInfo.token.endIndex;
|
||||
tokenText = this._model.getLineContent(position.lineNumber).substring(tokenStartIndex, tokenEndIndex);
|
||||
metadata = textMateTokenInfo.metadata;
|
||||
primary = semanticTokenInfo?.metadata;
|
||||
} else if (semanticTokenInfo) {
|
||||
tokenText = this._model.getValueInRange(semanticTokenInfo.range);
|
||||
metadata = semanticTokenInfo.metadata;
|
||||
} else {
|
||||
return 'No grammar or semantic tokens available.';
|
||||
}
|
||||
|
||||
let result = '';
|
||||
result += `<h2 class="tiw-token">${renderTokenText(tokenText)}<span class="tiw-token-length">(${tokenText.length} ${tokenText.length === 1 ? 'char' : 'chars'})</span></h2>`;
|
||||
result += `<hr class="tiw-metadata-separator" style="clear:both"/>`;
|
||||
|
||||
result += `<table class="tiw-metadata-table"><tbody>`;
|
||||
result += `<tr><td class="tiw-metadata-key">language</td><td class="tiw-metadata-value">${escape(metadata.languageIdentifier.language)}</td></tr>`;
|
||||
result += `<tr><td class="tiw-metadata-key">standard token type</td><td class="tiw-metadata-value">${this._tokenTypeToString(metadata.tokenType)}</td></tr>`;
|
||||
if (semanticTokenInfo) {
|
||||
result += `<tr><td class="tiw-metadata-key">semantic token type</td><td class="tiw-metadata-value">${semanticTokenInfo.type}</td></tr>`;
|
||||
const modifiers = semanticTokenInfo.modifiers.join(' ') || '-';
|
||||
result += `<tr><td class="tiw-metadata-key">semantic token modifiers</td><td class="tiw-metadata-value">${modifiers}</td></tr>`;
|
||||
}
|
||||
result += `</tbody></table>`;
|
||||
|
||||
result += `<hr class="tiw-metadata-separator"/>`;
|
||||
result += `<table class="tiw-metadata-table"><tbody>`;
|
||||
result += this._formatMetadata(metadata, primary);
|
||||
result += `</tbody></table>`;
|
||||
|
||||
if (textMateTokenInfo) {
|
||||
let theme = this._themeService.getColorTheme();
|
||||
result += `<hr class="tiw-metadata-separator"/>`;
|
||||
let matchingRule = findMatchingThemeRule(theme, textMateTokenInfo.token.scopes, false);
|
||||
if (matchingRule) {
|
||||
result += `<code class="tiw-theme-selector">${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}</code>`;
|
||||
} else {
|
||||
result += `<span class="tiw-theme-selector">No theme selector.</span>`;
|
||||
}
|
||||
|
||||
result += `<ul>`;
|
||||
for (let i = textMateTokenInfo.token.scopes.length - 1; i >= 0; i--) {
|
||||
result += `<li>${escape(textMateTokenInfo.token.scopes[i])}</li>`;
|
||||
}
|
||||
result += `</ul>`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _formatMetadata(metadata: IDecodedMetadata, master?: IDecodedMetadata) {
|
||||
let result = '';
|
||||
|
||||
const fontStyle = master ? master.fontStyle : metadata.fontStyle;
|
||||
result += `<tr><td class="tiw-metadata-key">font style</td><td class="tiw-metadata-value">${fontStyle}</td></tr>`;
|
||||
const foreground = master && master.foreground || metadata.foreground;
|
||||
result += `<tr><td class="tiw-metadata-key">foreground</td><td class="tiw-metadata-value">${foreground}</td></tr>`;
|
||||
const background = master && master.background || metadata.background;
|
||||
result += `<tr><td class="tiw-metadata-key">background</td><td class="tiw-metadata-value">${background}</td></tr>`;
|
||||
|
||||
if (foreground && background) {
|
||||
const backgroundColor = Color.fromHex(background), foregroundColor = Color.fromHex(foreground);
|
||||
|
||||
if (backgroundColor.isOpaque()) {
|
||||
result += `<tr><td class="tiw-metadata-key">contrast ratio</td><td class="tiw-metadata-value">${backgroundColor.getContrastRatio(foregroundColor.makeOpaque(backgroundColor)).toFixed(2)}</td></tr>`;
|
||||
} else {
|
||||
result += '<tr><td class="tiw-metadata-key">Contrast ratio cannot be precise for background colors that use transparency</td><td class="tiw-metadata-value"></td></tr>';
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _decodeMetadata(metadata: number): IDecodedMetadata {
|
||||
let colorMap = this._themeService.getColorTheme().tokenColorMap;
|
||||
let languageId = TokenMetadata.getLanguageId(metadata);
|
||||
let tokenType = TokenMetadata.getTokenType(metadata);
|
||||
let fontStyle = TokenMetadata.getFontStyle(metadata);
|
||||
let foreground = TokenMetadata.getForeground(metadata);
|
||||
let background = TokenMetadata.getBackground(metadata);
|
||||
return {
|
||||
languageIdentifier: this._modeService.getLanguageIdentifier(languageId)!,
|
||||
tokenType: tokenType,
|
||||
fontStyle: this._fontStyleToString(fontStyle),
|
||||
foreground: colorMap[foreground],
|
||||
background: colorMap[background]
|
||||
};
|
||||
}
|
||||
|
||||
private _tokenTypeToString(tokenType: StandardTokenType): string {
|
||||
switch (tokenType) {
|
||||
case StandardTokenType.Other: return 'Other';
|
||||
case StandardTokenType.Comment: return 'Comment';
|
||||
case StandardTokenType.String: return 'String';
|
||||
case StandardTokenType.RegEx: return 'RegEx';
|
||||
}
|
||||
return '??';
|
||||
}
|
||||
|
||||
private _fontStyleToString(fontStyle: FontStyle): string {
|
||||
let r = '';
|
||||
if (fontStyle & FontStyle.Italic) {
|
||||
r += 'italic ';
|
||||
}
|
||||
if (fontStyle & FontStyle.Bold) {
|
||||
r += 'bold ';
|
||||
}
|
||||
if (fontStyle & FontStyle.Underline) {
|
||||
r += 'underline ';
|
||||
}
|
||||
if (r.length === 0) {
|
||||
r = '---';
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private _getTokensAtPosition(grammar: IGrammar, position: Position): ITextMateTokenInfo {
|
||||
const lineNumber = position.lineNumber;
|
||||
let stateBeforeLine = this._getStateBeforeLine(grammar, lineNumber);
|
||||
|
||||
let tokenizationResult1 = grammar.tokenizeLine(this._model.getLineContent(lineNumber), stateBeforeLine);
|
||||
let tokenizationResult2 = grammar.tokenizeLine2(this._model.getLineContent(lineNumber), stateBeforeLine);
|
||||
|
||||
let token1Index = 0;
|
||||
for (let i = tokenizationResult1.tokens.length - 1; i >= 0; i--) {
|
||||
let t = tokenizationResult1.tokens[i];
|
||||
if (position.column - 1 >= t.startIndex) {
|
||||
token1Index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let token2Index = 0;
|
||||
for (let i = (tokenizationResult2.tokens.length >>> 1); i >= 0; i--) {
|
||||
if (position.column - 1 >= tokenizationResult2.tokens[(i << 1)]) {
|
||||
token2Index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
token: tokenizationResult1.tokens[token1Index],
|
||||
metadata: this._decodeMetadata(tokenizationResult2.tokens[(token2Index << 1) + 1])
|
||||
};
|
||||
}
|
||||
|
||||
private _getStateBeforeLine(grammar: IGrammar, lineNumber: number): StackElement | null {
|
||||
let state: StackElement | null = null;
|
||||
|
||||
for (let i = 1; i < lineNumber; i++) {
|
||||
let tokenizationResult = grammar.tokenizeLine(this._model.getLineContent(i), state);
|
||||
state = tokenizationResult.ruleStack;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private isSemanticTokens(token: any): token is SemanticTokens {
|
||||
return token && token.data;
|
||||
}
|
||||
|
||||
private async _computeSemanticTokens(): Promise<SemanticTokensResult | null> {
|
||||
const tokenProviders = DocumentSemanticTokensProviderRegistry.ordered(this._model);
|
||||
if (tokenProviders.length) {
|
||||
const provider = tokenProviders[0];
|
||||
const tokens = await Promise.resolve(provider.provideDocumentSemanticTokens(this._model, null, this._currentRequestCancellationTokenSource.token));
|
||||
if (this.isSemanticTokens(tokens)) {
|
||||
return { tokens, legend: provider.getLegend() };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private _getSemanticTokenAtPosition(semanticTokens: SemanticTokensResult, pos: Position): ISemanticTokenInfo | null {
|
||||
const tokenData = semanticTokens.tokens.data;
|
||||
let lastLine = 0;
|
||||
let lastCharacter = 0;
|
||||
const posLine = pos.lineNumber - 1, posCharacter = pos.column - 1; // to 0-based position
|
||||
for (let i = 0; i < tokenData.length; i += 5) {
|
||||
const lineDelta = tokenData[i], charDelta = tokenData[i + 1], len = tokenData[i + 2], typeIdx = tokenData[i + 3], modSet = tokenData[i + 4];
|
||||
const line = lastLine + lineDelta; // 0-based
|
||||
const character = lineDelta === 0 ? lastCharacter + charDelta : charDelta; // 0-based
|
||||
if (posLine === line && character <= posCharacter && posCharacter < character + len) {
|
||||
const type = semanticTokens.legend.tokenTypes[typeIdx];
|
||||
const modifiers = semanticTokens.legend.tokenModifiers.filter((_, k) => modSet & 1 << k);
|
||||
const range = new Range(line + 1, character + 1, line + 1, character + 1 + len);
|
||||
const metadata = this._decodeMetadata(this._themeService.getTheme().getTokenStyleMetadata(type, modifiers) || 0);
|
||||
return { type, modifiers, range, metadata };
|
||||
}
|
||||
lastLine = line;
|
||||
lastCharacter = character;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public getDomNode(): HTMLElement {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
public getPosition(): IContentWidgetPosition {
|
||||
return {
|
||||
position: this._editor.getPosition(),
|
||||
preference: [ContentWidgetPositionPreference.BELOW, ContentWidgetPositionPreference.ABOVE]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorContribution(InspectEditorTokensController.ID, InspectEditorTokensController);
|
||||
registerEditorAction(InspectEditorTokens);
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const border = theme.getColor(editorHoverBorder);
|
||||
if (border) {
|
||||
let borderWidth = theme.type === HIGH_CONTRAST ? 2 : 1;
|
||||
collector.addRule(`.monaco-editor .token-inspect-widget { border: ${borderWidth}px solid ${border}; }`);
|
||||
collector.addRule(`.monaco-editor .token-inspect-widget .tiw-metadata-separator { background-color: ${border}; }`);
|
||||
}
|
||||
const background = theme.getColor(editorHoverBackground);
|
||||
if (background) {
|
||||
collector.addRule(`.monaco-editor .token-inspect-widget { background-color: ${background}; }`);
|
||||
}
|
||||
});
|
||||
@@ -1,392 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./inspectTMScopes';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { ContentWidgetPositionPreference, IActiveCodeEditor, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { FontStyle, LanguageIdentifier, StandardTokenType, TokenMetadata, TokenizationRegistry } from 'vs/editor/common/modes';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { HIGH_CONTRAST, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { findMatchingThemeRule } from 'vs/workbench/services/textMate/common/TMHelper';
|
||||
import { ITextMateService, IGrammar, IToken, StackElement } from 'vs/workbench/services/textMate/common/textMateService';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
|
||||
class InspectTMScopesController extends Disposable implements IEditorContribution {
|
||||
|
||||
public static readonly ID = 'editor.contrib.inspectTMScopes';
|
||||
|
||||
public static get(editor: ICodeEditor): InspectTMScopesController {
|
||||
return editor.getContribution<InspectTMScopesController>(InspectTMScopesController.ID);
|
||||
}
|
||||
|
||||
private _editor: ICodeEditor;
|
||||
private _textMateService: ITextMateService;
|
||||
private _themeService: IWorkbenchThemeService;
|
||||
private _modeService: IModeService;
|
||||
private _notificationService: INotificationService;
|
||||
private _widget: InspectTMScopesWidget | null;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
@ITextMateService textMateService: ITextMateService,
|
||||
@IModeService modeService: IModeService,
|
||||
@IWorkbenchThemeService themeService: IWorkbenchThemeService,
|
||||
@INotificationService notificationService: INotificationService
|
||||
) {
|
||||
super();
|
||||
this._editor = editor;
|
||||
this._textMateService = textMateService;
|
||||
this._themeService = themeService;
|
||||
this._modeService = modeService;
|
||||
this._notificationService = notificationService;
|
||||
this._widget = null;
|
||||
|
||||
this._register(this._editor.onDidChangeModel((e) => this.stop()));
|
||||
this._register(this._editor.onDidChangeModelLanguage((e) => this.stop()));
|
||||
this._register(this._editor.onKeyUp((e) => e.keyCode === KeyCode.Escape && this.stop()));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.stop();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public launch(): void {
|
||||
if (this._widget) {
|
||||
return;
|
||||
}
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
this._widget = new InspectTMScopesWidget(this._editor, this._textMateService, this._modeService, this._themeService, this._notificationService);
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
if (this._widget) {
|
||||
this._widget.dispose();
|
||||
this._widget = null;
|
||||
}
|
||||
}
|
||||
|
||||
public toggle(): void {
|
||||
if (!this._widget) {
|
||||
this.launch();
|
||||
} else {
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InspectTMScopes extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.inspectTMScopes',
|
||||
label: nls.localize('inspectTMScopes', "Developer: Inspect TM Scopes"),
|
||||
alias: 'Developer: Inspect TM Scopes',
|
||||
precondition: undefined
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
let controller = InspectTMScopesController.get(editor);
|
||||
if (controller) {
|
||||
controller.toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ICompleteLineTokenization {
|
||||
startState: StackElement | null;
|
||||
tokens1: IToken[];
|
||||
tokens2: Uint32Array;
|
||||
endState: StackElement;
|
||||
}
|
||||
|
||||
interface IDecodedMetadata {
|
||||
languageIdentifier: LanguageIdentifier;
|
||||
tokenType: StandardTokenType;
|
||||
fontStyle: FontStyle;
|
||||
foreground: Color;
|
||||
background: Color;
|
||||
}
|
||||
|
||||
function renderTokenText(tokenText: string): string {
|
||||
if (tokenText.length > 40) {
|
||||
tokenText = tokenText.substr(0, 20) + '…' + tokenText.substr(tokenText.length - 20);
|
||||
}
|
||||
let result: string = '';
|
||||
for (let charIndex = 0, len = tokenText.length; charIndex < len; charIndex++) {
|
||||
let charCode = tokenText.charCodeAt(charIndex);
|
||||
switch (charCode) {
|
||||
case CharCode.Tab:
|
||||
result += '→';
|
||||
break;
|
||||
|
||||
case CharCode.Space:
|
||||
result += '·';
|
||||
break;
|
||||
|
||||
case CharCode.LessThan:
|
||||
result += '<';
|
||||
break;
|
||||
|
||||
case CharCode.GreaterThan:
|
||||
result += '>';
|
||||
break;
|
||||
|
||||
case CharCode.Ampersand:
|
||||
result += '&';
|
||||
break;
|
||||
|
||||
default:
|
||||
result += String.fromCharCode(charCode);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class InspectTMScopesWidget extends Disposable implements IContentWidget {
|
||||
|
||||
private static readonly _ID = 'editor.contrib.inspectTMScopesWidget';
|
||||
|
||||
// Editor.IContentWidget.allowEditorOverflow
|
||||
public readonly allowEditorOverflow = true;
|
||||
|
||||
private _isDisposed: boolean;
|
||||
private readonly _editor: IActiveCodeEditor;
|
||||
private readonly _modeService: IModeService;
|
||||
private readonly _themeService: IWorkbenchThemeService;
|
||||
private readonly _notificationService: INotificationService;
|
||||
private readonly _model: ITextModel;
|
||||
private readonly _domNode: HTMLElement;
|
||||
private readonly _grammar: Promise<IGrammar | null>;
|
||||
|
||||
constructor(
|
||||
editor: IActiveCodeEditor,
|
||||
textMateService: ITextMateService,
|
||||
modeService: IModeService,
|
||||
themeService: IWorkbenchThemeService,
|
||||
notificationService: INotificationService
|
||||
) {
|
||||
super();
|
||||
this._isDisposed = false;
|
||||
this._editor = editor;
|
||||
this._modeService = modeService;
|
||||
this._themeService = themeService;
|
||||
this._notificationService = notificationService;
|
||||
this._model = this._editor.getModel();
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.className = 'tm-inspect-widget';
|
||||
this._grammar = textMateService.createGrammar(this._model.getLanguageIdentifier().language);
|
||||
this._beginCompute(this._editor.getPosition());
|
||||
this._register(this._editor.onDidChangeCursorPosition((e) => this._beginCompute(this._editor.getPosition())));
|
||||
this._editor.addContentWidget(this);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._isDisposed = true;
|
||||
this._editor.removeContentWidget(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return InspectTMScopesWidget._ID;
|
||||
}
|
||||
|
||||
private _beginCompute(position: Position): void {
|
||||
dom.clearNode(this._domNode);
|
||||
this._domNode.appendChild(document.createTextNode(nls.localize('inspectTMScopesWidget.loading', "Loading...")));
|
||||
this._grammar.then(
|
||||
(grammar) => {
|
||||
if (!grammar) {
|
||||
throw new Error(`Could not find grammar for language!`);
|
||||
}
|
||||
this._compute(grammar, position);
|
||||
},
|
||||
(err) => {
|
||||
this._notificationService.warn(err);
|
||||
setTimeout(() => {
|
||||
InspectTMScopesController.get(this._editor).stop();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _compute(grammar: IGrammar, position: Position): void {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
let data = this._getTokensAtLine(grammar, position.lineNumber);
|
||||
|
||||
let token1Index = 0;
|
||||
for (let i = data.tokens1.length - 1; i >= 0; i--) {
|
||||
let t = data.tokens1[i];
|
||||
if (position.column - 1 >= t.startIndex) {
|
||||
token1Index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let token2Index = 0;
|
||||
for (let i = (data.tokens2.length >>> 1); i >= 0; i--) {
|
||||
if (position.column - 1 >= data.tokens2[(i << 1)]) {
|
||||
token2Index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let result = '';
|
||||
|
||||
let tokenStartIndex = data.tokens1[token1Index].startIndex;
|
||||
let tokenEndIndex = data.tokens1[token1Index].endIndex;
|
||||
let tokenText = this._model.getLineContent(position.lineNumber).substring(tokenStartIndex, tokenEndIndex);
|
||||
result += `<h2 class="tm-token">${renderTokenText(tokenText)}<span class="tm-token-length">(${tokenText.length} ${tokenText.length === 1 ? 'char' : 'chars'})</span></h2>`;
|
||||
|
||||
result += `<hr class="tm-metadata-separator" style="clear:both"/>`;
|
||||
|
||||
let metadata = this._decodeMetadata(data.tokens2[(token2Index << 1) + 1]);
|
||||
result += `<table class="tm-metadata-table"><tbody>`;
|
||||
result += `<tr><td class="tm-metadata-key">language</td><td class="tm-metadata-value">${escape(metadata.languageIdentifier.language)}</td></tr>`;
|
||||
result += `<tr><td class="tm-metadata-key">token type</td><td class="tm-metadata-value">${this._tokenTypeToString(metadata.tokenType)}</td></tr>`;
|
||||
result += `<tr><td class="tm-metadata-key">font style</td><td class="tm-metadata-value">${this._fontStyleToString(metadata.fontStyle)}</td></tr>`;
|
||||
result += `<tr><td class="tm-metadata-key">foreground</td><td class="tm-metadata-value">${Color.Format.CSS.formatHexA(metadata.foreground)}</td></tr>`;
|
||||
result += `<tr><td class="tm-metadata-key">background</td><td class="tm-metadata-value">${Color.Format.CSS.formatHexA(metadata.background)}</td></tr>`;
|
||||
if (metadata.background.isOpaque()) {
|
||||
result += `<tr><td class="tm-metadata-key">contrast ratio</td><td class="tm-metadata-value">${metadata.background.getContrastRatio(metadata.foreground.makeOpaque(metadata.background)).toFixed(2)}</td></tr>`;
|
||||
} else {
|
||||
result += '<tr><td class="tm-metadata-key">Contrast ratio cannot be precise for background colors that use transparency</td><td class="tm-metadata-value"></td></tr>';
|
||||
}
|
||||
result += `</tbody></table>`;
|
||||
|
||||
let theme = this._themeService.getColorTheme();
|
||||
result += `<hr class="tm-metadata-separator"/>`;
|
||||
let matchingRule = findMatchingThemeRule(theme, data.tokens1[token1Index].scopes, false);
|
||||
if (matchingRule) {
|
||||
result += `<code class="tm-theme-selector">${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}</code>`;
|
||||
} else {
|
||||
result += `<span class="tm-theme-selector">No theme selector.</span>`;
|
||||
}
|
||||
|
||||
result += `<hr class="tm-metadata-separator"/>`;
|
||||
|
||||
result += `<ul>`;
|
||||
for (let i = data.tokens1[token1Index].scopes.length - 1; i >= 0; i--) {
|
||||
result += `<li>${escape(data.tokens1[token1Index].scopes[i])}</li>`;
|
||||
}
|
||||
result += `</ul>`;
|
||||
|
||||
|
||||
this._domNode.innerHTML = result;
|
||||
this._editor.layoutContentWidget(this);
|
||||
}
|
||||
|
||||
private _decodeMetadata(metadata: number): IDecodedMetadata {
|
||||
let colorMap = TokenizationRegistry.getColorMap()!;
|
||||
let languageId = TokenMetadata.getLanguageId(metadata);
|
||||
let tokenType = TokenMetadata.getTokenType(metadata);
|
||||
let fontStyle = TokenMetadata.getFontStyle(metadata);
|
||||
let foreground = TokenMetadata.getForeground(metadata);
|
||||
let background = TokenMetadata.getBackground(metadata);
|
||||
return {
|
||||
languageIdentifier: this._modeService.getLanguageIdentifier(languageId)!,
|
||||
tokenType: tokenType,
|
||||
fontStyle: fontStyle,
|
||||
foreground: colorMap[foreground],
|
||||
background: colorMap[background]
|
||||
};
|
||||
}
|
||||
|
||||
private _tokenTypeToString(tokenType: StandardTokenType): string {
|
||||
switch (tokenType) {
|
||||
case StandardTokenType.Other: return 'Other';
|
||||
case StandardTokenType.Comment: return 'Comment';
|
||||
case StandardTokenType.String: return 'String';
|
||||
case StandardTokenType.RegEx: return 'RegEx';
|
||||
}
|
||||
return '??';
|
||||
}
|
||||
|
||||
private _fontStyleToString(fontStyle: FontStyle): string {
|
||||
let r = '';
|
||||
if (fontStyle & FontStyle.Italic) {
|
||||
r += 'italic ';
|
||||
}
|
||||
if (fontStyle & FontStyle.Bold) {
|
||||
r += 'bold ';
|
||||
}
|
||||
if (fontStyle & FontStyle.Underline) {
|
||||
r += 'underline ';
|
||||
}
|
||||
if (r.length === 0) {
|
||||
r = '---';
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private _getTokensAtLine(grammar: IGrammar, lineNumber: number): ICompleteLineTokenization {
|
||||
let stateBeforeLine = this._getStateBeforeLine(grammar, lineNumber);
|
||||
|
||||
let tokenizationResult1 = grammar.tokenizeLine(this._model.getLineContent(lineNumber), stateBeforeLine);
|
||||
let tokenizationResult2 = grammar.tokenizeLine2(this._model.getLineContent(lineNumber), stateBeforeLine);
|
||||
|
||||
return {
|
||||
startState: stateBeforeLine,
|
||||
tokens1: tokenizationResult1.tokens,
|
||||
tokens2: tokenizationResult2.tokens,
|
||||
endState: tokenizationResult1.ruleStack
|
||||
};
|
||||
}
|
||||
|
||||
private _getStateBeforeLine(grammar: IGrammar, lineNumber: number): StackElement | null {
|
||||
let state: StackElement | null = null;
|
||||
|
||||
for (let i = 1; i < lineNumber; i++) {
|
||||
let tokenizationResult = grammar.tokenizeLine(this._model.getLineContent(i), state);
|
||||
state = tokenizationResult.ruleStack;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
public getDomNode(): HTMLElement {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
public getPosition(): IContentWidgetPosition {
|
||||
return {
|
||||
position: this._editor.getPosition(),
|
||||
preference: [ContentWidgetPositionPreference.BELOW, ContentWidgetPositionPreference.ABOVE]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorContribution(InspectTMScopesController.ID, InspectTMScopesController);
|
||||
registerEditorAction(InspectTMScopes);
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const border = theme.getColor(editorHoverBorder);
|
||||
if (border) {
|
||||
let borderWidth = theme.type === HIGH_CONTRAST ? 2 : 1;
|
||||
collector.addRule(`.monaco-editor .tm-inspect-widget { border: ${borderWidth}px solid ${border}; }`);
|
||||
collector.addRule(`.monaco-editor .tm-inspect-widget .tm-metadata-separator { background-color: ${border}; }`);
|
||||
}
|
||||
const background = theme.getColor(editorHoverBackground);
|
||||
if (background) {
|
||||
collector.addRule(`.monaco-editor .tm-inspect-widget { background-color: ${background}; }`);
|
||||
}
|
||||
});
|
||||
@@ -226,8 +226,8 @@ export class SuggestEnabledInput extends Widget implements IThemable {
|
||||
public style(colors: ISuggestEnabledInputStyles): void {
|
||||
this.placeholderText.style.backgroundColor =
|
||||
this.stylingContainer.style.backgroundColor = colors.inputBackground ? colors.inputBackground.toString() : '';
|
||||
this.stylingContainer.style.color = colors.inputForeground ? colors.inputForeground.toString() : null;
|
||||
this.placeholderText.style.color = colors.inputPlaceholderForeground ? colors.inputPlaceholderForeground.toString() : null;
|
||||
this.stylingContainer.style.color = colors.inputForeground ? colors.inputForeground.toString() : '';
|
||||
this.placeholderText.style.color = colors.inputPlaceholderForeground ? colors.inputPlaceholderForeground.toString() : '';
|
||||
|
||||
this.stylingContainer.style.borderWidth = '1px';
|
||||
this.stylingContainer.style.borderStyle = 'solid';
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { registerEditorContribution, EditorAction, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions';
|
||||
import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { IEditorContribution, Handler } from 'vs/editor/common/editorCommon';
|
||||
import { EndOfLinePreference } from 'vs/editor/common/model';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard';
|
||||
@@ -19,6 +20,10 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
|
||||
export class SelectionClipboard extends Disposable implements IEditorContribution {
|
||||
private static readonly SELECTION_LENGTH_LIMIT = 65536;
|
||||
@@ -107,5 +112,41 @@ class SelectionClipboardPastePreventer implements IWorkbenchContribution {
|
||||
}
|
||||
}
|
||||
|
||||
class PasteSelectionClipboardAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.selectionClipboardPaste',
|
||||
label: nls.localize('actions.pasteSelectionClipboard', "Paste Selection Clipboard"),
|
||||
alias: 'Paste Selection Clipboard',
|
||||
precondition: EditorContextKeys.writable,
|
||||
kbOpts: {
|
||||
kbExpr: ContextKeyExpr.and(
|
||||
EditorContextKeys.editorTextFocus,
|
||||
ContextKeyExpr.has('config.editor.selectionClipboard')
|
||||
),
|
||||
primary: KeyMod.Shift | KeyCode.Insert,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise<void> {
|
||||
const clipboardService = accessor.get(IClipboardService);
|
||||
|
||||
// read selection clipboard
|
||||
const text = await clipboardService.readText('selection');
|
||||
|
||||
editor.trigger('keyboard', Handler.Paste, {
|
||||
text: text,
|
||||
pasteOnNewLine: false,
|
||||
multicursorText: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorContribution(SelectionClipboardContributionID, SelectionClipboard);
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SelectionClipboardPastePreventer, LifecyclePhase.Ready);
|
||||
if (platform.isLinux) {
|
||||
registerEditorAction(PasteSelectionClipboardAction);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user