提交 f31b3de9 编写于 作者: M Martin Aeschlimann

improve inspect token hover

上级 8345780f
...@@ -33,6 +33,17 @@ ...@@ -33,6 +33,17 @@
font-family: var(--monaco-monospace-font); font-family: var(--monaco-monospace-font);
text-align: right; text-align: right;
} }
.tiw-metadata-key {
vertical-align: top;
}
.tiw-metadata-semantic {
font-style: italic;
}
.tiw-metadata-scopes {
line-height: normal;
}
.tiw-theme-selector { .tiw-theme-selector {
font-family: var(--monaco-monospace-font); font-family: var(--monaco-monospace-font);
......
...@@ -17,7 +17,7 @@ import { Position } from 'vs/editor/common/core/position'; ...@@ -17,7 +17,7 @@ import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model'; import { ITextModel } from 'vs/editor/common/model';
import { FontStyle, LanguageIdentifier, StandardTokenType, TokenMetadata, DocumentSemanticTokensProviderRegistry, SemanticTokensLegend, SemanticTokens, LanguageId } from 'vs/editor/common/modes'; import { FontStyle, LanguageIdentifier, StandardTokenType, TokenMetadata, DocumentSemanticTokensProviderRegistry, SemanticTokensLegend, SemanticTokens, LanguageId, ColorId } from 'vs/editor/common/modes';
import { IModeService } from 'vs/editor/common/services/modeService'; import { IModeService } from 'vs/editor/common/services/modeService';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry'; import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry';
...@@ -27,7 +27,7 @@ import { ITextMateService, IGrammar, IToken, StackElement } from 'vs/workbench/s ...@@ -27,7 +27,7 @@ import { ITextMateService, IGrammar, IToken, StackElement } from 'vs/workbench/s
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { ColorThemeData, TokenStyleDefinitions, TokenStyleDefinition } from 'vs/workbench/services/themes/common/colorThemeData'; import { ColorThemeData, TokenStyleDefinitions, TokenStyleDefinition } from 'vs/workbench/services/themes/common/colorThemeData';
import { TokenStylingRule } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { TokenStylingRule, TokenStyleData } from 'vs/platform/theme/common/tokenClassificationRegistry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import type { IEditorSemanticHighlightingOptions } from 'vs/editor/common/config/editorOptions'; import type { IEditorSemanticHighlightingOptions } from 'vs/editor/common/config/editorOptions';
...@@ -128,14 +128,16 @@ interface ISemanticTokenInfo { ...@@ -128,14 +128,16 @@ interface ISemanticTokenInfo {
type: string; type: string;
modifiers: string[]; modifiers: string[];
range: Range; range: Range;
metadata: IDecodedMetadata, metadata?: IDecodedMetadata,
definitions: TokenStyleDefinitions definitions: TokenStyleDefinitions
} }
interface IDecodedMetadata { interface IDecodedMetadata {
languageIdentifier: LanguageIdentifier; languageIdentifier: LanguageIdentifier;
tokenType: StandardTokenType; tokenType: StandardTokenType;
fontStyle: string; bold?: boolean;
italic?: boolean;
underline?: boolean;
foreground?: string; foreground?: string;
background?: string; background?: string;
} }
...@@ -262,97 +264,137 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { ...@@ -262,97 +264,137 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {
private _compute(grammar: IGrammar | null, semanticTokens: SemanticTokensResult | null, position: Position): string { private _compute(grammar: IGrammar | null, semanticTokens: SemanticTokensResult | null, position: Position): string {
const textMateTokenInfo = grammar && this._getTokensAtPosition(grammar, position); const textMateTokenInfo = grammar && this._getTokensAtPosition(grammar, position);
const semanticTokenInfo = semanticTokens && this._getSemanticTokenAtPosition(semanticTokens, position); const semanticTokenInfo = semanticTokens && this._getSemanticTokenAtPosition(semanticTokens, position);
if (!textMateTokenInfo && !semanticTokenInfo) {
return 'No grammar or semantic tokens available.';
}
let tokenText; let tmMetadata = textMateTokenInfo?.metadata;
let semMetadata = semanticTokenInfo?.metadata;
let metadata: IDecodedMetadata | undefined; const semTokenText = semanticTokenInfo && renderTokenText(this._model.getValueInRange(semanticTokenInfo.range));
let tmFallback: IDecodedMetadata | undefined; const tmTokenText = textMateTokenInfo && renderTokenText(this._model.getLineContent(position.lineNumber).substring(textMateTokenInfo.token.startIndex, textMateTokenInfo.token.endIndex));
if (semanticTokenInfo) { const tokenText = semTokenText || tmTokenText || '';
tokenText = this._model.getValueInRange(semanticTokenInfo.range);
metadata = semanticTokenInfo.metadata;
if (textMateTokenInfo) {
tmFallback = textMateTokenInfo.metadata;
}
} else if (textMateTokenInfo) {
let tokenStartIndex = textMateTokenInfo.token.startIndex;
let tokenEndIndex = textMateTokenInfo.token.endIndex;
tokenText = this._model.getLineContent(position.lineNumber).substring(tokenStartIndex, tokenEndIndex);
metadata = textMateTokenInfo.metadata;
} else {
return 'No grammar or semantic tokens available.';
}
let result = ''; let result = '';
result += `<h2 class="tiw-token">${renderTokenText(tokenText)}<span class="tiw-token-length">(${tokenText.length} ${tokenText.length === 1 ? 'char' : 'chars'})</span></h2>`; result += `<h2 class="tiw-token">${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 += `<hr class="tiw-metadata-separator" style="clear:both"/>`;
result += `<table class="tiw-metadata-table"><tbody>`; result += `<table class="tiw-metadata-table"><tbody>`;
result += `<tr><td class="tiw-metadata-key">language</td><td class="tiw-metadata-value">${escape(textMateTokenInfo?.metadata.languageIdentifier.language || '')}</td></tr>`; result += `<tr><td class="tiw-metadata-key">language</td><td class="tiw-metadata-value">${escape(tmMetadata?.languageIdentifier.language || '')}</td></tr>`;
result += `<tr><td class="tiw-metadata-key">standard token type</td><td class="tiw-metadata-value">${this._tokenTypeToString(textMateTokenInfo?.metadata.tokenType || StandardTokenType.Other)}</td></tr>`; result += `<tr><td class="tiw-metadata-key">standard token type</td><td class="tiw-metadata-value">${this._tokenTypeToString(tmMetadata?.tokenType || StandardTokenType.Other)}</td></tr>`;
result += `</tbody></table>`; result += `</tbody></table>`;
result += `<hr class="tiw-metadata-separator"/>`; result += `<hr class="tiw-metadata-separator"/>`;
result += `<table class="tiw-metadata-table"><tbody>`; result += `<table class="tiw-metadata-table"><tbody>`;
result += this._formatMetadata(metadata, tmFallback); result += this._formatMetadata(semMetadata, tmMetadata);
result += `</tbody></table>`; result += `</tbody></table>`;
if (semanticTokenInfo) { if (semanticTokenInfo) {
result += `<hr class="tiw-metadata-separator"/>`; result += `<hr class="tiw-metadata-separator"/>`;
result += `<table class="tiw-metadata-table"><tbody>`; result += `<table class="tiw-metadata-table"><tbody>`;
result += `<tr><td class="tiw-metadata-key">semantic token type</td><td class="tiw-metadata-value">${semanticTokenInfo.type}</td></tr>`; 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(' ') || '-'; if (semanticTokenInfo.modifiers.length) {
result += `<tr><td class="tiw-metadata-key">semantic token modifiers</td><td class="tiw-metadata-value">${modifiers}</td></tr>`; result += `<tr><td class="tiw-metadata-key">modifiers</td><td class="tiw-metadata-value">${semanticTokenInfo.modifiers.join(' ')}</td></tr>`;
}
if (semanticTokenInfo.metadata) {
const properties: (keyof TokenStyleData)[] = ['foreground', 'bold', 'italic', 'underline'];
const propertiesByDefValue: { [rule: string]: string[] } = {};
const allDefValues = []; // remember the order
// first collect to detect when the same rule is used fro multiple properties
for (let property of properties) {
if (semanticTokenInfo.metadata[property]) {
const definition = semanticTokenInfo.definitions[property];
const defValue = this._renderTokenStyleDefinition(definition, property);
let properties = propertiesByDefValue[defValue];
if (!properties) {
propertiesByDefValue[defValue] = properties = [];
allDefValues.push(defValue);
}
properties.push(property);
}
}
for (let defValue of allDefValues) {
result += `<tr><td class="tiw-metadata-key">${propertiesByDefValue[defValue].join(', ')}</td><td class="tiw-metadata-value">${defValue}</td></tr>`;
}
}
result += `</tbody></table>`; result += `</tbody></table>`;
result += `<div>${this._renderTokenStyleDefinition(semanticTokenInfo.definitions.foreground)}</div>`;
} }
if (textMateTokenInfo) { if (textMateTokenInfo) {
let theme = this._themeService.getColorTheme(); let theme = this._themeService.getColorTheme();
result += `<hr class="tiw-metadata-separator"/>`; result += `<hr class="tiw-metadata-separator"/>`;
if (!semanticTokenInfo) { result += `<table class="tiw-metadata-table"><tbody>`;
let matchingRule = findMatchingThemeRule(theme, textMateTokenInfo.token.scopes, false); if (tmTokenText && tmTokenText !== tokenText) {
if (matchingRule) { result += `<tr><td class="tiw-metadata-key">textmate token</td><td class="tiw-metadata-value">${tmTokenText} (${tmTokenText.length})</td></tr>`;
result += `<code class="tiw-theme-selector">${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}</code>`; }
} else { let scopes = '';
result += `<span class="tiw-theme-selector">No theme selector.</span>`; for (let i = textMateTokenInfo.token.scopes.length - 1; i >= 0; i--) {
scopes += escape(textMateTokenInfo.token.scopes[i]);
if (i > 0) {
scopes += '<br>';
} }
} }
result += `<tr><td class="tiw-metadata-key">textmate scopes</td><td class="tiw-metadata-value tiw-metadata-scopes">${scopes}</td></tr>`;
result += `<ul>`; let matchingRule = findMatchingThemeRule(theme, textMateTokenInfo.token.scopes, false);
for (let i = textMateTokenInfo.token.scopes.length - 1; i >= 0; i--) { const semForeground = semanticTokenInfo?.metadata?.foreground;
result += `<li>${escape(textMateTokenInfo.token.scopes[i])}</li>`; if (matchingRule) {
let defValue = `<code class="tiw-theme-selector">${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}</code>`;
if (semForeground !== textMateTokenInfo.metadata.foreground) {
if (semForeground) {
defValue = `<s>${defValue}</s>`;
}
result += `<tr><td class="tiw-metadata-key">foreground</td><td class="tiw-metadata-value">${defValue}</td></tr>`;
}
} else if (!semForeground) {
result += `<tr><td class="tiw-metadata-key">foreground</td><td class="tiw-metadata-value">No theme selector</td></tr>`;
} }
result += `</ul>`; result += `</tbody></table>`;
} }
return result; return result;
} }
private _formatMetadata(metadata?: IDecodedMetadata, fallback?: IDecodedMetadata) { private _formatMetadata(semantic?: IDecodedMetadata, tm?: IDecodedMetadata) {
let result = ''; let result = '';
function render(label: string, value: string | undefined, property: keyof IDecodedMetadata) { function render(property: 'foreground' | 'background') {
const info = metadata?.[property] !== value ? ` (tm)` : ''; let value = semantic?.[property] || tm?.[property];
return `<tr><td class="tiw-metadata-key">${label}</td><td class="tiw-metadata-value">${value + info}</td></tr>`; if (value !== undefined) {
} const semanticStyle = semantic?.[property] ? 'tiw-metadata-semantic' : '';
result += `<tr><td class="tiw-metadata-key">${property}</td><td class="tiw-metadata-value ${semanticStyle}">${value}</td></tr>`;
const fontStyle = metadata?.fontStyle || fallback?.fontStyle; }
result += render('font style', fontStyle, 'fontStyle'); return value;
const foreground = metadata?.foreground || fallback?.foreground; }
result += render('foreground', foreground, 'foreground');
const background = metadata?.background || fallback?.background;
result += render('background', background, 'background');
const foreground = render('foreground');
const background = render('background');
if (foreground && background) { if (foreground && background) {
const backgroundColor = Color.fromHex(background), foregroundColor = Color.fromHex(foreground); const backgroundColor = Color.fromHex(background), foregroundColor = Color.fromHex(foreground);
if (backgroundColor.isOpaque()) { 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>`; 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 { } 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>'; 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>';
} }
} }
let fontStyleLabels: string[] = [];
function addStyle(key: 'bold' | 'italic' | 'underline') {
if (semantic && semantic[key]) {
fontStyleLabels.push(`<span class='tiw-metadata-semantic'>${key}</span>`);
} else if (tm && tm[key]) {
fontStyleLabels.push(key);
}
}
addStyle('bold');
addStyle('italic');
addStyle('underline');
if (fontStyleLabels.length) {
result += `<tr><td class="tiw-metadata-key">font style</td><td class="tiw-metadata-value">${fontStyleLabels.join(' ')}</td></tr>`;
}
return result; return result;
} }
...@@ -366,7 +408,9 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { ...@@ -366,7 +408,9 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {
return { return {
languageIdentifier: this._modeService.getLanguageIdentifier(languageId)!, languageIdentifier: this._modeService.getLanguageIdentifier(languageId)!,
tokenType: tokenType, tokenType: tokenType,
fontStyle: this._fontStyleToString(fontStyle), bold: (fontStyle & FontStyle.Bold) ? true : undefined,
italic: (fontStyle & FontStyle.Italic) ? true : undefined,
underline: (fontStyle & FontStyle.Underline) ? true : undefined,
foreground: colorMap[foreground], foreground: colorMap[foreground],
background: colorMap[background] background: colorMap[background]
}; };
...@@ -382,23 +426,6 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { ...@@ -382,23 +426,6 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {
return '??'; 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 { private _getTokensAtPosition(grammar: IGrammar, position: Position): ITextMateTokenInfo {
const lineNumber = position.lineNumber; const lineNumber = position.lineNumber;
let stateBeforeLine = this._getStateBeforeLine(grammar, lineNumber); let stateBeforeLine = this._getStateBeforeLine(grammar, lineNumber);
...@@ -478,26 +505,18 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { ...@@ -478,26 +505,18 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {
const theme = this._themeService.getTheme() as ColorThemeData; const theme = this._themeService.getTheme() as ColorThemeData;
const tokenStyle = theme.getTokenStyleMetadata(type, modifiers, true, definitions); const tokenStyle = theme.getTokenStyleMetadata(type, modifiers, true, definitions);
let fontStyle = FontStyle.None; let metadata: IDecodedMetadata | undefined = undefined;
let foreground: string | undefined = undefined;
if (tokenStyle) { if (tokenStyle) {
fontStyle = ( metadata = {
(tokenStyle.italic ? FontStyle.Italic : 0) languageIdentifier: this._modeService.getLanguageIdentifier(LanguageId.Null)!,
| (tokenStyle.bold ? FontStyle.Bold : 0) tokenType: StandardTokenType.Other,
| (tokenStyle.underline ? FontStyle.Underline : 0) bold: tokenStyle?.bold,
); italic: tokenStyle?.italic,
if (tokenStyle.foreground) { underline: tokenStyle?.underline,
foreground = colorMap[tokenStyle.foreground]; foreground: colorMap[tokenStyle?.foreground || ColorId.None]
} };
} }
const metadata: IDecodedMetadata = {
languageIdentifier: this._modeService.getLanguageIdentifier(LanguageId.Null)!,
tokenType: StandardTokenType.Other,
fontStyle: this._fontStyleToString(fontStyle),
foreground: foreground,
};
return { type, modifiers, range, metadata, definitions }; return { type, modifiers, range, metadata, definitions };
} }
lastLine = line; lastLine = line;
...@@ -506,7 +525,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { ...@@ -506,7 +525,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {
return null; return null;
} }
private _renderTokenStyleDefinition(definition: TokenStyleDefinition | undefined): string { private _renderTokenStyleDefinition(definition: TokenStyleDefinition | undefined, property: keyof TokenStyleData): string {
if (definition === undefined) { if (definition === undefined) {
return ''; return '';
} }
...@@ -514,30 +533,19 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { ...@@ -514,30 +533,19 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {
const isTokenStylingRule = (d: any): d is TokenStylingRule => !!d.value; const isTokenStylingRule = (d: any): d is TokenStylingRule => !!d.value;
if (Array.isArray(definition)) { if (Array.isArray(definition)) {
let result = '';
let matchingRule = undefined;
result += `<ul>`;
for (const d of definition) { for (const d of definition) {
result += `<li>${escape(d.join(' '))}</li>`; const matchingRule = findMatchingThemeRule(theme, d, false);
matchingRule = findMatchingThemeRule(theme, d, false);
if (matchingRule) { if (matchingRule) {
break; return `${escape(d.join(' '))}<br><code class="tiw-theme-selector">${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}</code>`;
} }
} }
result += `</ul>`; return '';
if (matchingRule) {
result += `<code class="tiw-theme-selector">${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}</code>`;
}
return result;
} else if (isTokenStylingRule(definition)) { } else if (isTokenStylingRule(definition)) {
const scope = theme.getTokenStylingRuleScope(definition); const scope = theme.getTokenStylingRuleScope(definition);
if (scope === 'setting') { if (scope === 'setting') {
return `User settings: ${definition.selector}`; return `User settings`; // todo: print selector and style once selector is a string
} else if (scope === 'theme') { } else if (scope === 'theme') {
return `Color theme: ${definition.selector}`; return `Color theme`; // todo: print selector and style once selector is a string
} }
return ''; return '';
} else if (typeof definition === 'string') { } else if (typeof definition === 'string') {
...@@ -545,11 +553,11 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { ...@@ -545,11 +553,11 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget {
const definitions: TokenStyleDefinitions = {}; const definitions: TokenStyleDefinitions = {};
const m = theme.getTokenStyleMetadata(type, modifiers, true, definitions); const m = theme.getTokenStyleMetadata(type, modifiers, true, definitions);
if (m && definitions.foreground) { if (m && definitions.foreground) {
return this._renderTokenStyleDefinition(definitions.foreground); return this._renderTokenStyleDefinition(definitions[property], property);
} }
return ''; return '';
} else { } else {
return `Token style: Foreground: ${definition.foreground}, bold: ${definition.bold}, italic: ${definition.italic}, underline: ${definition.underline},`; return String(definition[property]);
} }
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册