Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts
3296 views
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 './inspectTokens.css';
7
import { $, append, reset } from '../../../../base/browser/dom.js';
8
import { CharCode } from '../../../../base/common/charCode.js';
9
import { Color } from '../../../../base/common/color.js';
10
import { KeyCode } from '../../../../base/common/keyCodes.js';
11
import { Disposable } from '../../../../base/common/lifecycle.js';
12
import { ContentWidgetPositionPreference, IActiveCodeEditor, ICodeEditor, IContentWidget, IContentWidgetPosition } from '../../../browser/editorBrowser.js';
13
import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution, EditorContributionInstantiation } from '../../../browser/editorExtensions.js';
14
import { Position } from '../../../common/core/position.js';
15
import { IEditorContribution } from '../../../common/editorCommon.js';
16
import { ITextModel } from '../../../common/model.js';
17
import { IState, ITokenizationSupport, TokenizationRegistry, ILanguageIdCodec, Token } from '../../../common/languages.js';
18
import { FontStyle, StandardTokenType, TokenMetadata } from '../../../common/encodedTokenAttributes.js';
19
import { NullState, nullTokenize, nullTokenizeEncoded } from '../../../common/languages/nullTokenize.js';
20
import { ILanguageService } from '../../../common/languages/language.js';
21
import { IStandaloneThemeService } from '../../common/standaloneTheme.js';
22
import { InspectTokensNLS } from '../../../common/standaloneStrings.js';
23
24
25
class InspectTokensController extends Disposable implements IEditorContribution {
26
27
public static readonly ID = 'editor.contrib.inspectTokens';
28
29
public static get(editor: ICodeEditor): InspectTokensController | null {
30
return editor.getContribution<InspectTokensController>(InspectTokensController.ID);
31
}
32
33
private readonly _editor: ICodeEditor;
34
private readonly _languageService: ILanguageService;
35
private _widget: InspectTokensWidget | null;
36
37
constructor(
38
editor: ICodeEditor,
39
@IStandaloneThemeService standaloneColorService: IStandaloneThemeService,
40
@ILanguageService languageService: ILanguageService
41
) {
42
super();
43
this._editor = editor;
44
this._languageService = languageService;
45
this._widget = null;
46
47
this._register(this._editor.onDidChangeModel((e) => this.stop()));
48
this._register(this._editor.onDidChangeModelLanguage((e) => this.stop()));
49
this._register(TokenizationRegistry.onDidChange((e) => this.stop()));
50
this._register(this._editor.onKeyUp((e) => e.keyCode === KeyCode.Escape && this.stop()));
51
}
52
53
public override dispose(): void {
54
this.stop();
55
super.dispose();
56
}
57
58
public launch(): void {
59
if (this._widget) {
60
return;
61
}
62
if (!this._editor.hasModel()) {
63
return;
64
}
65
this._widget = new InspectTokensWidget(this._editor, this._languageService);
66
}
67
68
public stop(): void {
69
if (this._widget) {
70
this._widget.dispose();
71
this._widget = null;
72
}
73
}
74
}
75
76
class InspectTokens extends EditorAction {
77
78
constructor() {
79
super({
80
id: 'editor.action.inspectTokens',
81
label: InspectTokensNLS.inspectTokensAction,
82
alias: 'Developer: Inspect Tokens',
83
precondition: undefined
84
});
85
}
86
87
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
88
const controller = InspectTokensController.get(editor);
89
controller?.launch();
90
}
91
}
92
93
interface ICompleteLineTokenization {
94
startState: IState;
95
tokens1: Token[];
96
tokens2: Uint32Array;
97
endState: IState;
98
}
99
100
interface IDecodedMetadata {
101
languageId: string;
102
tokenType: StandardTokenType;
103
fontStyle: FontStyle;
104
foreground: Color;
105
background: Color;
106
}
107
108
function renderTokenText(tokenText: string): string {
109
let result: string = '';
110
for (let charIndex = 0, len = tokenText.length; charIndex < len; charIndex++) {
111
const charCode = tokenText.charCodeAt(charIndex);
112
switch (charCode) {
113
case CharCode.Tab:
114
result += '\u2192'; // &rarr;
115
break;
116
117
case CharCode.Space:
118
result += '\u00B7'; // &middot;
119
break;
120
121
default:
122
result += String.fromCharCode(charCode);
123
}
124
}
125
return result;
126
}
127
128
function getSafeTokenizationSupport(languageIdCodec: ILanguageIdCodec, languageId: string): ITokenizationSupport {
129
const tokenizationSupport = TokenizationRegistry.get(languageId);
130
if (tokenizationSupport) {
131
return tokenizationSupport;
132
}
133
const encodedLanguageId = languageIdCodec.encodeLanguageId(languageId);
134
return {
135
getInitialState: () => NullState,
136
tokenize: (line: string, hasEOL: boolean, state: IState) => nullTokenize(languageId, state),
137
tokenizeEncoded: (line: string, hasEOL: boolean, state: IState) => nullTokenizeEncoded(encodedLanguageId, state)
138
};
139
}
140
141
class InspectTokensWidget extends Disposable implements IContentWidget {
142
143
private static readonly _ID = 'editor.contrib.inspectTokensWidget';
144
145
// Editor.IContentWidget.allowEditorOverflow
146
public allowEditorOverflow = true;
147
148
private readonly _editor: IActiveCodeEditor;
149
private readonly _languageService: ILanguageService;
150
private readonly _tokenizationSupport: ITokenizationSupport;
151
private readonly _model: ITextModel;
152
private readonly _domNode: HTMLElement;
153
154
constructor(
155
editor: IActiveCodeEditor,
156
languageService: ILanguageService
157
) {
158
super();
159
this._editor = editor;
160
this._languageService = languageService;
161
this._model = this._editor.getModel();
162
this._domNode = document.createElement('div');
163
this._domNode.className = 'tokens-inspect-widget';
164
this._tokenizationSupport = getSafeTokenizationSupport(this._languageService.languageIdCodec, this._model.getLanguageId());
165
this._compute(this._editor.getPosition());
166
this._register(this._editor.onDidChangeCursorPosition((e) => this._compute(this._editor.getPosition())));
167
this._editor.addContentWidget(this);
168
}
169
170
public override dispose(): void {
171
this._editor.removeContentWidget(this);
172
super.dispose();
173
}
174
175
public getId(): string {
176
return InspectTokensWidget._ID;
177
}
178
179
private _compute(position: Position): void {
180
const data = this._getTokensAtLine(position.lineNumber);
181
182
let token1Index = 0;
183
for (let i = data.tokens1.length - 1; i >= 0; i--) {
184
const t = data.tokens1[i];
185
if (position.column - 1 >= t.offset) {
186
token1Index = i;
187
break;
188
}
189
}
190
191
let token2Index = 0;
192
for (let i = (data.tokens2.length >>> 1); i >= 0; i--) {
193
if (position.column - 1 >= data.tokens2[(i << 1)]) {
194
token2Index = i;
195
break;
196
}
197
}
198
199
const lineContent = this._model.getLineContent(position.lineNumber);
200
let tokenText = '';
201
if (token1Index < data.tokens1.length) {
202
const tokenStartIndex = data.tokens1[token1Index].offset;
203
const tokenEndIndex = token1Index + 1 < data.tokens1.length ? data.tokens1[token1Index + 1].offset : lineContent.length;
204
tokenText = lineContent.substring(tokenStartIndex, tokenEndIndex);
205
}
206
reset(this._domNode,
207
$('h2.tm-token', undefined, renderTokenText(tokenText),
208
$('span.tm-token-length', undefined, `${tokenText.length} ${tokenText.length === 1 ? 'char' : 'chars'}`)));
209
210
append(this._domNode, $('hr.tokens-inspect-separator', { 'style': 'clear:both' }));
211
212
const metadata = (token2Index << 1) + 1 < data.tokens2.length ? this._decodeMetadata(data.tokens2[(token2Index << 1) + 1]) : null;
213
append(this._domNode, $('table.tm-metadata-table', undefined,
214
$('tbody', undefined,
215
$('tr', undefined,
216
$('td.tm-metadata-key', undefined, 'language'),
217
$('td.tm-metadata-value', undefined, `${metadata ? metadata.languageId : '-?-'}`)
218
),
219
$('tr', undefined,
220
$('td.tm-metadata-key', undefined, 'token type' as string),
221
$('td.tm-metadata-value', undefined, `${metadata ? this._tokenTypeToString(metadata.tokenType) : '-?-'}`)
222
),
223
$('tr', undefined,
224
$('td.tm-metadata-key', undefined, 'font style' as string),
225
$('td.tm-metadata-value', undefined, `${metadata ? this._fontStyleToString(metadata.fontStyle) : '-?-'}`)
226
),
227
$('tr', undefined,
228
$('td.tm-metadata-key', undefined, 'foreground'),
229
$('td.tm-metadata-value', undefined, `${metadata ? Color.Format.CSS.formatHex(metadata.foreground) : '-?-'}`)
230
),
231
$('tr', undefined,
232
$('td.tm-metadata-key', undefined, 'background'),
233
$('td.tm-metadata-value', undefined, `${metadata ? Color.Format.CSS.formatHex(metadata.background) : '-?-'}`)
234
)
235
)
236
));
237
append(this._domNode, $('hr.tokens-inspect-separator'));
238
239
if (token1Index < data.tokens1.length) {
240
append(this._domNode, $('span.tm-token-type', undefined, data.tokens1[token1Index].type));
241
}
242
243
this._editor.layoutContentWidget(this);
244
}
245
246
private _decodeMetadata(metadata: number): IDecodedMetadata {
247
const colorMap = TokenizationRegistry.getColorMap()!;
248
const languageId = TokenMetadata.getLanguageId(metadata);
249
const tokenType = TokenMetadata.getTokenType(metadata);
250
const fontStyle = TokenMetadata.getFontStyle(metadata);
251
const foreground = TokenMetadata.getForeground(metadata);
252
const background = TokenMetadata.getBackground(metadata);
253
return {
254
languageId: this._languageService.languageIdCodec.decodeLanguageId(languageId),
255
tokenType: tokenType,
256
fontStyle: fontStyle,
257
foreground: colorMap[foreground],
258
background: colorMap[background]
259
};
260
}
261
262
private _tokenTypeToString(tokenType: StandardTokenType): string {
263
switch (tokenType) {
264
case StandardTokenType.Other: return 'Other';
265
case StandardTokenType.Comment: return 'Comment';
266
case StandardTokenType.String: return 'String';
267
case StandardTokenType.RegEx: return 'RegEx';
268
default: return '??';
269
}
270
}
271
272
private _fontStyleToString(fontStyle: FontStyle): string {
273
let r = '';
274
if (fontStyle & FontStyle.Italic) {
275
r += 'italic ';
276
}
277
if (fontStyle & FontStyle.Bold) {
278
r += 'bold ';
279
}
280
if (fontStyle & FontStyle.Underline) {
281
r += 'underline ';
282
}
283
if (fontStyle & FontStyle.Strikethrough) {
284
r += 'strikethrough ';
285
}
286
if (r.length === 0) {
287
r = '---';
288
}
289
return r;
290
}
291
292
private _getTokensAtLine(lineNumber: number): ICompleteLineTokenization {
293
const stateBeforeLine = this._getStateBeforeLine(lineNumber);
294
295
const tokenizationResult1 = this._tokenizationSupport.tokenize(this._model.getLineContent(lineNumber), true, stateBeforeLine);
296
const tokenizationResult2 = this._tokenizationSupport.tokenizeEncoded(this._model.getLineContent(lineNumber), true, stateBeforeLine);
297
298
return {
299
startState: stateBeforeLine,
300
tokens1: tokenizationResult1.tokens,
301
tokens2: tokenizationResult2.tokens,
302
endState: tokenizationResult1.endState
303
};
304
}
305
306
private _getStateBeforeLine(lineNumber: number): IState {
307
let state: IState = this._tokenizationSupport.getInitialState();
308
309
for (let i = 1; i < lineNumber; i++) {
310
const tokenizationResult = this._tokenizationSupport.tokenize(this._model.getLineContent(i), true, state);
311
state = tokenizationResult.endState;
312
}
313
314
return state;
315
}
316
317
public getDomNode(): HTMLElement {
318
return this._domNode;
319
}
320
321
public getPosition(): IContentWidgetPosition {
322
return {
323
position: this._editor.getPosition(),
324
preference: [ContentWidgetPositionPreference.BELOW, ContentWidgetPositionPreference.ABOVE]
325
};
326
}
327
}
328
329
registerEditorContribution(InspectTokensController.ID, InspectTokensController, EditorContributionInstantiation.Lazy);
330
registerEditorAction(InspectTokens);
331
332