|
| 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 { IDecorationOptions, IModel } from 'vs/editor/common/editorCommon'; |
| 7 | +import { StandardTokenType } from 'vs/editor/common/modes'; |
| 8 | +import { IExpression } from 'vs/workbench/parts/debug/common/debug'; |
| 9 | + |
| 10 | +export const MAX_INLINE_VALUE_LENGTH = 50; // Max string length of each inline 'x = y' string. If exceeded ... is added |
| 11 | +export const MAX_INLINE_DECORATOR_LENGTH = 150; // Max string length of each inline decorator when debugging. If exceeded ... is added |
| 12 | +export const MAX_NUM_INLINE_VALUES = 100; // JS Global scope can have 700+ entries. We want to limit ourselves for perf reasons |
| 13 | +export const MAX_TOKENIZATION_LINE_LEN = 500; // If line is too long, then inline values for the line are skipped |
| 14 | +// LanguageConfigurationRegistry.getWordDefinition() return regexes that allow spaces and punctuation characters for languages like python |
| 15 | +// Using that approach is not viable so we are using a simple regex to look for word tokens. |
| 16 | +export const WORD_REGEXP = /[\$\_A-Za-z][\$\_A-Za-z0-9]*/g; |
| 17 | + |
| 18 | +export function toNameValueMap(expressions: IExpression[]): Map<string, string> { |
| 19 | + const result = new Map<string, string>(); |
| 20 | + let valueCount = 0; |
| 21 | + |
| 22 | + for (let expr of expressions) { |
| 23 | + // Put ellipses in value if its too long. Preserve last char e.g "longstr…" or {a:true, b:true, …} |
| 24 | + let value = expr.value; |
| 25 | + if (value && value.length > MAX_INLINE_VALUE_LENGTH) { |
| 26 | + value = value.substr(0, MAX_INLINE_VALUE_LENGTH) + '…' + value[value.length - 1]; |
| 27 | + } |
| 28 | + |
| 29 | + result.set(expr.name, value); |
| 30 | + |
| 31 | + // Limit the size of map. Too large can have a perf impact |
| 32 | + if (++valueCount >= MAX_NUM_INLINE_VALUES) { |
| 33 | + break; |
| 34 | + } |
| 35 | + } |
| 36 | + |
| 37 | + return result; |
| 38 | +} |
| 39 | + |
| 40 | +export function getDecorations(nameValueMap: Map<string, string>, wordToLineNumbersMap: Map<string, number[]>): IDecorationOptions[] { |
| 41 | + const lineToNamesMap: Map<number, string[]> = new Map<number, string[]>(); |
| 42 | + const decorations: IDecorationOptions[] = []; |
| 43 | + |
| 44 | + // Compute unique set of names on each line |
| 45 | + nameValueMap.forEach((value, name) => { |
| 46 | + if (wordToLineNumbersMap.has(name)) { |
| 47 | + for (let lineNumber of wordToLineNumbersMap.get(name)) { |
| 48 | + if (!lineToNamesMap.has(lineNumber)) { |
| 49 | + lineToNamesMap.set(lineNumber, []); |
| 50 | + } |
| 51 | + |
| 52 | + lineToNamesMap.get(lineNumber).push(name); |
| 53 | + } |
| 54 | + } |
| 55 | + }); |
| 56 | + |
| 57 | + // Compute decorators for each line |
| 58 | + lineToNamesMap.forEach((names, line) => { |
| 59 | + // Wrap with 1em unicode space for readability |
| 60 | + const contentText = '\u2003' + names.map(name => `${name} = ${nameValueMap.get(name)}`).join(', ') + '\u2003'; |
| 61 | + decorations.push(createDecoration(line, contentText)); |
| 62 | + }); |
| 63 | + |
| 64 | + return decorations; |
| 65 | +} |
| 66 | + |
| 67 | +function createDecoration(lineNumber: number, contentText: string): IDecorationOptions { |
| 68 | + const margin = '10px'; |
| 69 | + const backgroundColor = 'rgba(255, 200, 0, 0.2)'; |
| 70 | + const lightForegroundColor = 'rgba(0, 0, 0, 0.5)'; |
| 71 | + const darkForegroundColor = 'rgba(255, 255, 255, 0.5)'; |
| 72 | + |
| 73 | + // If decoratorText is too long, trim and add ellipses. This could happen for minified files with everything on a single line |
| 74 | + if (contentText.length > MAX_INLINE_DECORATOR_LENGTH) { |
| 75 | + contentText = contentText.substr(0, MAX_INLINE_DECORATOR_LENGTH) + '...'; |
| 76 | + } |
| 77 | + |
| 78 | + return { |
| 79 | + range: { |
| 80 | + startLineNumber: lineNumber, |
| 81 | + endLineNumber: lineNumber, |
| 82 | + startColumn: Number.MAX_VALUE, |
| 83 | + endColumn: Number.MAX_VALUE |
| 84 | + }, |
| 85 | + renderOptions: { |
| 86 | + dark: { |
| 87 | + after: { |
| 88 | + contentText, |
| 89 | + backgroundColor, |
| 90 | + color: darkForegroundColor, |
| 91 | + margin |
| 92 | + } |
| 93 | + }, |
| 94 | + light: { |
| 95 | + after: { |
| 96 | + contentText, |
| 97 | + backgroundColor, |
| 98 | + color: lightForegroundColor, |
| 99 | + margin |
| 100 | + } |
| 101 | + } |
| 102 | + } |
| 103 | + }; |
| 104 | +} |
| 105 | + |
| 106 | +export function getWordToLineNumbersMap(model: IModel): Map<string, number[]> { |
| 107 | + const result = new Map<string, number[]>(); |
| 108 | + |
| 109 | + // For every word in every line, map its ranges for fast lookup |
| 110 | + for (let lineNumber = 1, len = model.getLineCount(); lineNumber <= len; ++lineNumber) { |
| 111 | + const lineContent = model.getLineContent(lineNumber); |
| 112 | + |
| 113 | + // If line is too long then skip the line |
| 114 | + if (lineContent.length > MAX_TOKENIZATION_LINE_LEN) { |
| 115 | + continue; |
| 116 | + } |
| 117 | + |
| 118 | + const lineTokens = model.getLineTokens(lineNumber); |
| 119 | + for (let token = lineTokens.firstToken(); !!token; token = token.next()) { |
| 120 | + const tokenStr = lineContent.substring(token.startOffset, token.endOffset); |
| 121 | + |
| 122 | + // Token is a word and not a comment |
| 123 | + if (token.tokenType === StandardTokenType.Other) { |
| 124 | + WORD_REGEXP.lastIndex = 0; // We assume tokens will usually map 1:1 to words if they match |
| 125 | + const wordMatch = WORD_REGEXP.exec(tokenStr); |
| 126 | + |
| 127 | + if (wordMatch) { |
| 128 | + const word = wordMatch[0]; |
| 129 | + if (!result.has(word)) { |
| 130 | + result.set(word, []); |
| 131 | + } |
| 132 | + |
| 133 | + result.get(word).push(lineNumber); |
| 134 | + } |
| 135 | + } |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + return result; |
| 140 | +} |
0 commit comments