@parcel/codeframe 2.0.0-nightly.97 → 2.0.0-nightly.970
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/codeframe.js +157 -65
- package/package.json +14 -5
- package/src/codeframe.js +158 -92
- package/test/codeframe.test.js +222 -11
- package/test/fixtures/a.js +13 -0
package/lib/codeframe.js
CHANGED
@@ -5,58 +5,98 @@ Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
});
|
6
6
|
exports.default = codeFrame;
|
7
7
|
|
8
|
-
|
8
|
+
function _chalk() {
|
9
|
+
const data = _interopRequireDefault(require("chalk"));
|
9
10
|
|
10
|
-
|
11
|
+
_chalk = function () {
|
12
|
+
return data;
|
13
|
+
};
|
14
|
+
|
15
|
+
return data;
|
16
|
+
}
|
17
|
+
|
18
|
+
function _emphasize() {
|
19
|
+
const data = _interopRequireDefault(require("emphasize"));
|
20
|
+
|
21
|
+
_emphasize = function () {
|
22
|
+
return data;
|
23
|
+
};
|
24
|
+
|
25
|
+
return data;
|
26
|
+
}
|
27
|
+
|
28
|
+
function _stringWidth() {
|
29
|
+
const data = _interopRequireDefault(require("string-width"));
|
30
|
+
|
31
|
+
_stringWidth = function () {
|
32
|
+
return data;
|
33
|
+
};
|
34
|
+
|
35
|
+
return data;
|
36
|
+
}
|
37
|
+
|
38
|
+
function _sliceAnsi() {
|
39
|
+
const data = _interopRequireDefault(require("slice-ansi"));
|
40
|
+
|
41
|
+
_sliceAnsi = function () {
|
42
|
+
return data;
|
43
|
+
};
|
44
|
+
|
45
|
+
return data;
|
46
|
+
}
|
11
47
|
|
12
48
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
13
49
|
|
14
50
|
const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
|
15
51
|
const TAB_REPLACE_REGEX = /\t/g;
|
16
52
|
const TAB_REPLACEMENT = ' ';
|
53
|
+
const DEFAULT_TERMINAL_WIDTH = 80;
|
17
54
|
|
18
55
|
const highlightSyntax = (txt, lang) => {
|
19
56
|
if (lang) {
|
20
57
|
try {
|
21
|
-
return _emphasize.default.highlight(lang, txt).value;
|
58
|
+
return _emphasize().default.highlight(lang, txt).value;
|
22
59
|
} catch (e) {// fallback for unknown languages...
|
23
60
|
}
|
24
61
|
}
|
25
62
|
|
26
|
-
return _emphasize.default.highlightAuto(txt).value;
|
63
|
+
return _emphasize().default.highlightAuto(txt).value;
|
27
64
|
};
|
28
65
|
|
29
|
-
function codeFrame(code, highlights,
|
30
|
-
|
66
|
+
function codeFrame(code, highlights, inputOpts = {}) {
|
67
|
+
var _inputOpts$maxLines;
|
68
|
+
|
31
69
|
if (highlights.length < 1) return '';
|
32
70
|
let opts = {
|
33
71
|
useColor: !!inputOpts.useColor,
|
34
72
|
syntaxHighlighting: !!inputOpts.syntaxHighlighting,
|
35
73
|
language: inputOpts.language,
|
36
|
-
maxLines: inputOpts.maxLines !==
|
74
|
+
maxLines: (_inputOpts$maxLines = inputOpts.maxLines) !== null && _inputOpts$maxLines !== void 0 ? _inputOpts$maxLines : 12,
|
75
|
+
terminalWidth: inputOpts.terminalWidth || DEFAULT_TERMINAL_WIDTH,
|
37
76
|
padding: inputOpts.padding || {
|
38
77
|
before: 1,
|
39
78
|
after: 2
|
40
79
|
}
|
41
|
-
};
|
80
|
+
}; // Highlights messages and prefixes when colors are enabled
|
42
81
|
|
43
82
|
const highlighter = (s, bold) => {
|
44
83
|
if (opts.useColor) {
|
45
|
-
let redString = _chalk.default.red(s);
|
84
|
+
let redString = _chalk().default.red(s);
|
46
85
|
|
47
|
-
return bold ? _chalk.default.bold(redString) : redString;
|
86
|
+
return bold ? _chalk().default.bold(redString) : redString;
|
48
87
|
}
|
49
88
|
|
50
89
|
return s;
|
51
|
-
};
|
90
|
+
}; // Prefix lines with the line number
|
91
|
+
|
52
92
|
|
53
93
|
const lineNumberPrefixer = params => {
|
54
94
|
let {
|
55
95
|
lineNumber,
|
56
|
-
|
96
|
+
lineNumberLength,
|
57
97
|
isHighlighted
|
58
98
|
} = params;
|
59
|
-
return `${isHighlighted ? highlighter('>') : ' '} ${lineNumber ? lineNumber.
|
99
|
+
return `${isHighlighted ? highlighter('>') : ' '} ${lineNumber ? lineNumber.padStart(lineNumberLength, ' ') : ' '.repeat(lineNumberLength)} | `;
|
60
100
|
}; // Make columns/lines start at 1
|
61
101
|
|
62
102
|
|
@@ -72,75 +112,127 @@ inputOpts = {}) {
|
|
72
112
|
},
|
73
113
|
message: h.message
|
74
114
|
};
|
75
|
-
});
|
115
|
+
}); // Find first and last highlight
|
116
|
+
|
76
117
|
let firstHighlight = highlights.length > 1 ? highlights.sort((a, b) => a.start.line - b.start.line)[0] : highlights[0];
|
77
|
-
let lastHighlight = highlights.length > 1 ? highlights.sort((a, b) => b.end.line - a.end.line)[0] : highlights[0];
|
118
|
+
let lastHighlight = highlights.length > 1 ? highlights.sort((a, b) => b.end.line - a.end.line)[0] : highlights[0]; // Calculate first and last line index of codeframe
|
119
|
+
|
78
120
|
let startLine = firstHighlight.start.line - opts.padding.before;
|
79
121
|
startLine = startLine < 0 ? 0 : startLine;
|
80
|
-
let
|
81
|
-
|
82
|
-
let
|
83
|
-
|
122
|
+
let endLineIndex = lastHighlight.end.line + opts.padding.after;
|
123
|
+
endLineIndex = endLineIndex - startLine > opts.maxLines ? startLine + opts.maxLines - 1 : endLineIndex;
|
124
|
+
let lineNumberLength = (endLineIndex + 1).toString(10).length; // Split input into lines and highlight syntax
|
125
|
+
|
84
126
|
let lines = code.split(NEWLINE);
|
85
|
-
let syntaxHighlightedLines = (opts.syntaxHighlighting ? highlightSyntax(code, opts.language) : code).replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).split(NEWLINE);
|
127
|
+
let syntaxHighlightedLines = (opts.syntaxHighlighting ? highlightSyntax(code, opts.language) : code).replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).split(NEWLINE); // Loop over all lines and create codeframe
|
128
|
+
|
129
|
+
let resultLines = [];
|
130
|
+
|
131
|
+
for (let currentLineIndex = startLine; currentLineIndex < syntaxHighlightedLines.length; currentLineIndex++) {
|
132
|
+
if (currentLineIndex > endLineIndex) break;
|
133
|
+
if (currentLineIndex > syntaxHighlightedLines.length - 1) break; // Find highlights that need to get rendered on the current line
|
134
|
+
|
135
|
+
let lineHighlights = highlights.filter(highlight => highlight.start.line <= currentLineIndex && highlight.end.line >= currentLineIndex).sort((a, b) => (a.start.line < currentLineIndex ? 0 : a.start.column) - (b.start.line < currentLineIndex ? 0 : b.start.column)); // Check if this line has a full line highlight
|
136
|
+
|
137
|
+
let isWholeLine = lineHighlights.length && !!lineHighlights.find(h => h.start.line < currentLineIndex && h.end.line > currentLineIndex);
|
138
|
+
let lineLengthLimit = opts.terminalWidth > lineNumberLength + 7 ? opts.terminalWidth - (lineNumberLength + 5) : 10; // Split the line into line parts that will fit the provided terminal width
|
139
|
+
|
140
|
+
let colOffset = 0;
|
141
|
+
let lineEndCol = lineLengthLimit;
|
142
|
+
let syntaxHighlightedLine = syntaxHighlightedLines[currentLineIndex];
|
143
|
+
|
144
|
+
if ((0, _stringWidth().default)(syntaxHighlightedLine) > lineLengthLimit) {
|
145
|
+
if (lineHighlights.length > 0) {
|
146
|
+
if (lineHighlights[0].start.line === currentLineIndex) {
|
147
|
+
colOffset = lineHighlights[0].start.column - 5;
|
148
|
+
} else if (lineHighlights[0].end.line === currentLineIndex) {
|
149
|
+
colOffset = lineHighlights[0].end.column - 5;
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
153
|
+
colOffset = colOffset > 0 ? colOffset : 0;
|
154
|
+
lineEndCol = colOffset + lineLengthLimit;
|
155
|
+
syntaxHighlightedLine = (0, _sliceAnsi().default)(syntaxHighlightedLine, colOffset, lineEndCol);
|
156
|
+
} // Write the syntax highlighted line part
|
157
|
+
|
86
158
|
|
87
|
-
for (let i = startLine; i < lines.length; i++) {
|
88
|
-
if (i > endLine) break;
|
89
|
-
let originalLine = lines[i];
|
90
|
-
let syntaxHighlightedLine = syntaxHighlightedLines[i];
|
91
|
-
let foundHighlights = highlights.filter(highlight => highlight.start.line <= i && highlight.end.line >= i);
|
92
159
|
resultLines.push(lineNumberPrefixer({
|
93
|
-
lineNumber: (
|
94
|
-
|
95
|
-
isHighlighted:
|
160
|
+
lineNumber: (currentLineIndex + 1).toString(10),
|
161
|
+
lineNumberLength,
|
162
|
+
isHighlighted: lineHighlights.length > 0
|
96
163
|
}) + syntaxHighlightedLine);
|
164
|
+
let lineWidth = (0, _stringWidth().default)(syntaxHighlightedLine);
|
165
|
+
let highlightLine = '';
|
97
166
|
|
98
|
-
if (
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
let
|
104
|
-
|
105
|
-
if (isWholeLine) {
|
106
|
-
// If there's a whole line highlight
|
107
|
-
// don't even bother creating seperate highlight
|
108
|
-
highlightLine += highlighter('^'.repeat(originalLine.replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).length));
|
109
|
-
} else {
|
110
|
-
let sortedColumns = foundHighlights.length > 1 ? foundHighlights.sort((a, b) => (a.start.line < i ? 0 : a.start.column) - (b.start.line < i ? 0 : b.start.column)) : foundHighlights;
|
111
|
-
let lastCol = 0;
|
112
|
-
|
113
|
-
for (let col of sortedColumns) {
|
114
|
-
let startCol = col.start.line === i ? col.start.column : 0;
|
115
|
-
let endCol = (col.end.line === i ? col.end.column : originalLine.length - (lastCol || 1)) + 1;
|
116
|
-
|
117
|
-
if (startCol - lastCol > 0) {
|
118
|
-
let whitespace = originalLine.substring(lastCol, startCol).replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).replace(/\S/g, ' ');
|
119
|
-
highlightLine += whitespace;
|
120
|
-
}
|
167
|
+
if (isWholeLine) {
|
168
|
+
highlightLine = highlighter('^'.repeat(lineWidth));
|
169
|
+
} else if (lineHighlights.length > 0) {
|
170
|
+
let lastCol = 0;
|
171
|
+
let highlight = null;
|
172
|
+
let highlightHasEnded = false;
|
121
173
|
|
122
|
-
|
174
|
+
for (let highlightIndex = 0; highlightIndex < lineHighlights.length; highlightIndex++) {
|
175
|
+
// Set highlight to current highlight
|
176
|
+
highlight = lineHighlights[highlightIndex];
|
177
|
+
highlightHasEnded = false; // Calculate the startColumn and get the real width by doing a substring of the original
|
178
|
+
// line and replacing tabs with our tab replacement to support tab handling
|
179
|
+
|
180
|
+
let startCol = 0;
|
181
|
+
|
182
|
+
if (highlight.start.line === currentLineIndex && highlight.start.column > colOffset) {
|
183
|
+
startCol = lines[currentLineIndex].substring(colOffset, highlight.start.column).replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).length;
|
184
|
+
} // Calculate the endColumn and get the real width by doing a substring of the original
|
185
|
+
// line and replacing tabs with our tab replacement to support tab handling
|
123
186
|
|
124
|
-
if (endCol - highlightStartColumn > 0) {
|
125
|
-
let renderedHighlightLength = originalLine.substring(highlightStartColumn, endCol).replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).length;
|
126
|
-
highlightLine += highlighter('^'.repeat(renderedHighlightLength));
|
127
|
-
lastCol = endCol;
|
128
|
-
}
|
129
|
-
}
|
130
187
|
|
131
|
-
let
|
188
|
+
let endCol = lineWidth - 1;
|
132
189
|
|
133
|
-
if (
|
134
|
-
|
135
|
-
let lastHighlightForColumn = sortedByEnd[sortedByEnd.length - 1];
|
190
|
+
if (highlight.end.line === currentLineIndex) {
|
191
|
+
endCol = lines[currentLineIndex].substring(colOffset, highlight.end.column).replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).length; // If the endCol is too big for this line part, trim it so we can handle it in the next one
|
136
192
|
|
137
|
-
if (
|
138
|
-
|
193
|
+
if (endCol > lineWidth) {
|
194
|
+
endCol = lineWidth - 1;
|
139
195
|
}
|
196
|
+
|
197
|
+
highlightHasEnded = true;
|
198
|
+
} // If endcol is smaller than lastCol it overlaps with another highlight and is no longer visible, we can skip those
|
199
|
+
|
200
|
+
|
201
|
+
if (endCol >= lastCol) {
|
202
|
+
let characters = endCol - startCol + 1;
|
203
|
+
|
204
|
+
if (startCol > lastCol) {
|
205
|
+
// startCol is before lastCol, so add spaces as padding before the highlight indicators
|
206
|
+
highlightLine += ' '.repeat(startCol - lastCol);
|
207
|
+
} else if (lastCol > startCol) {
|
208
|
+
// If last column is larger than the start, there's overlap in highlights
|
209
|
+
// This line adjusts the characters count to ensure we don't add too many characters
|
210
|
+
characters += startCol - lastCol;
|
211
|
+
} // Append the highlight indicators
|
212
|
+
|
213
|
+
|
214
|
+
highlightLine += highlighter('^'.repeat(characters)); // Set the lastCol equal to character count between start of line part and highlight end-column
|
215
|
+
|
216
|
+
lastCol = endCol + 1;
|
217
|
+
} // There's no point in processing more highlights if we reached the end of the line
|
218
|
+
|
219
|
+
|
220
|
+
if (endCol >= lineEndCol - 1) {
|
221
|
+
break;
|
140
222
|
}
|
223
|
+
} // Append the highlight message if the current highlights ends on this line part
|
224
|
+
|
225
|
+
|
226
|
+
if (highlight && highlight.message && highlightHasEnded) {
|
227
|
+
highlightLine += ' ' + highlighter(highlight.message, true);
|
141
228
|
}
|
229
|
+
}
|
142
230
|
|
143
|
-
|
231
|
+
if (highlightLine) {
|
232
|
+
resultLines.push(lineNumberPrefixer({
|
233
|
+
lineNumberLength,
|
234
|
+
isHighlighted: true
|
235
|
+
}) + highlightLine);
|
144
236
|
}
|
145
237
|
}
|
146
238
|
|
package/package.json
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
{
|
2
2
|
"name": "@parcel/codeframe",
|
3
|
-
"version": "2.0.0-nightly.
|
3
|
+
"version": "2.0.0-nightly.970+5a80fbe4",
|
4
4
|
"description": "Blazing fast, zero configuration web application bundler",
|
5
5
|
"license": "MIT",
|
6
6
|
"publishConfig": {
|
7
7
|
"access": "public"
|
8
8
|
},
|
9
|
+
"funding": {
|
10
|
+
"type": "opencollective",
|
11
|
+
"url": "https://opencollective.com/parcel"
|
12
|
+
},
|
9
13
|
"repository": {
|
10
14
|
"type": "git",
|
11
15
|
"url": "https://github.com/parcel-bundler/parcel.git"
|
@@ -13,11 +17,16 @@
|
|
13
17
|
"main": "lib/codeframe.js",
|
14
18
|
"source": "src/codeframe.js",
|
15
19
|
"engines": {
|
16
|
-
"node": ">=
|
20
|
+
"node": ">= 12.0.0"
|
17
21
|
},
|
18
22
|
"dependencies": {
|
19
|
-
"chalk": "^
|
20
|
-
"emphasize": "^2.
|
23
|
+
"chalk": "^4.1.0",
|
24
|
+
"emphasize": "^4.2.0",
|
25
|
+
"slice-ansi": "^4.0.0",
|
26
|
+
"string-width": "^4.2.0"
|
27
|
+
},
|
28
|
+
"devDependencies": {
|
29
|
+
"strip-ansi": "^6.0.0"
|
21
30
|
},
|
22
|
-
"gitHead": "
|
31
|
+
"gitHead": "5a80fbe4af8797d0aab248a66f5254f42f00e83e"
|
23
32
|
}
|
package/src/codeframe.js
CHANGED
@@ -3,31 +3,29 @@ import type {DiagnosticCodeHighlight} from '@parcel/diagnostic';
|
|
3
3
|
|
4
4
|
import chalk from 'chalk';
|
5
5
|
import emphasize from 'emphasize';
|
6
|
+
import stringWidth from 'string-width';
|
7
|
+
import sliceAnsi from 'slice-ansi';
|
6
8
|
|
7
9
|
type CodeFramePadding = {|
|
8
10
|
before: number,
|
9
11
|
after: number,
|
10
12
|
|};
|
11
13
|
|
12
|
-
type CodeFrameOptionsInput =
|
13
|
-
useColor?: boolean,
|
14
|
-
maxLines?: number,
|
15
|
-
padding?: CodeFramePadding,
|
16
|
-
syntaxHighlighting?: boolean,
|
17
|
-
language?: string,
|
18
|
-
|};
|
14
|
+
type CodeFrameOptionsInput = $Shape<CodeFrameOptions>;
|
19
15
|
|
20
16
|
type CodeFrameOptions = {|
|
21
17
|
useColor: boolean,
|
22
18
|
syntaxHighlighting: boolean,
|
23
19
|
maxLines: number,
|
24
20
|
padding: CodeFramePadding,
|
21
|
+
terminalWidth: number,
|
25
22
|
language?: string,
|
26
23
|
|};
|
27
24
|
|
28
25
|
const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
|
29
26
|
const TAB_REPLACE_REGEX = /\t/g;
|
30
27
|
const TAB_REPLACEMENT = ' ';
|
28
|
+
const DEFAULT_TERMINAL_WIDTH = 80;
|
31
29
|
|
32
30
|
const highlightSyntax = (txt: string, lang?: string): string => {
|
33
31
|
if (lang) {
|
@@ -44,7 +42,6 @@ const highlightSyntax = (txt: string, lang?: string): string => {
|
|
44
42
|
export default function codeFrame(
|
45
43
|
code: string,
|
46
44
|
highlights: Array<DiagnosticCodeHighlight>,
|
47
|
-
// $FlowFixMe
|
48
45
|
inputOpts: CodeFrameOptionsInput = {},
|
49
46
|
): string {
|
50
47
|
if (highlights.length < 1) return '';
|
@@ -53,13 +50,15 @@ export default function codeFrame(
|
|
53
50
|
useColor: !!inputOpts.useColor,
|
54
51
|
syntaxHighlighting: !!inputOpts.syntaxHighlighting,
|
55
52
|
language: inputOpts.language,
|
56
|
-
maxLines: inputOpts.maxLines
|
53
|
+
maxLines: inputOpts.maxLines ?? 12,
|
54
|
+
terminalWidth: inputOpts.terminalWidth || DEFAULT_TERMINAL_WIDTH,
|
57
55
|
padding: inputOpts.padding || {
|
58
56
|
before: 1,
|
59
57
|
after: 2,
|
60
58
|
},
|
61
59
|
};
|
62
60
|
|
61
|
+
// Highlights messages and prefixes when colors are enabled
|
63
62
|
const highlighter = (s: string, bold?: boolean) => {
|
64
63
|
if (opts.useColor) {
|
65
64
|
let redString = chalk.red(s);
|
@@ -69,17 +68,18 @@ export default function codeFrame(
|
|
69
68
|
return s;
|
70
69
|
};
|
71
70
|
|
71
|
+
// Prefix lines with the line number
|
72
72
|
const lineNumberPrefixer = (params: {|
|
73
73
|
lineNumber?: string,
|
74
|
-
|
74
|
+
lineNumberLength: number,
|
75
75
|
isHighlighted: boolean,
|
76
76
|
|}) => {
|
77
|
-
let {lineNumber,
|
77
|
+
let {lineNumber, lineNumberLength, isHighlighted} = params;
|
78
78
|
|
79
79
|
return `${isHighlighted ? highlighter('>') : ' '} ${
|
80
80
|
lineNumber
|
81
|
-
? lineNumber.
|
82
|
-
: ' '.repeat(
|
81
|
+
? lineNumber.padStart(lineNumberLength, ' ')
|
82
|
+
: ' '.repeat(lineNumberLength)
|
83
83
|
} | `;
|
84
84
|
};
|
85
85
|
|
@@ -98,6 +98,7 @@ export default function codeFrame(
|
|
98
98
|
};
|
99
99
|
});
|
100
100
|
|
101
|
+
// Find first and last highlight
|
101
102
|
let firstHighlight =
|
102
103
|
highlights.length > 1
|
103
104
|
? highlights.sort((a, b) => a.start.line - b.start.line)[0]
|
@@ -107,111 +108,176 @@ export default function codeFrame(
|
|
107
108
|
? highlights.sort((a, b) => b.end.line - a.end.line)[0]
|
108
109
|
: highlights[0];
|
109
110
|
|
111
|
+
// Calculate first and last line index of codeframe
|
110
112
|
let startLine = firstHighlight.start.line - opts.padding.before;
|
111
113
|
startLine = startLine < 0 ? 0 : startLine;
|
112
|
-
let
|
113
|
-
|
114
|
-
|
114
|
+
let endLineIndex = lastHighlight.end.line + opts.padding.after;
|
115
|
+
endLineIndex =
|
116
|
+
endLineIndex - startLine > opts.maxLines
|
115
117
|
? startLine + opts.maxLines - 1
|
116
|
-
:
|
117
|
-
let endLineString = endLine.toString(10);
|
118
|
+
: endLineIndex;
|
118
119
|
|
119
|
-
let
|
120
|
+
let lineNumberLength = (endLineIndex + 1).toString(10).length;
|
121
|
+
|
122
|
+
// Split input into lines and highlight syntax
|
120
123
|
let lines = code.split(NEWLINE);
|
121
|
-
let syntaxHighlightedLines = (
|
122
|
-
? highlightSyntax(code, opts.language)
|
123
|
-
: code
|
124
|
+
let syntaxHighlightedLines = (
|
125
|
+
opts.syntaxHighlighting ? highlightSyntax(code, opts.language) : code
|
124
126
|
)
|
125
127
|
.replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT)
|
126
128
|
.split(NEWLINE);
|
127
|
-
for (let i = startLine; i < lines.length; i++) {
|
128
|
-
if (i > endLine) break;
|
129
129
|
|
130
|
-
|
131
|
-
|
130
|
+
// Loop over all lines and create codeframe
|
131
|
+
let resultLines = [];
|
132
|
+
for (
|
133
|
+
let currentLineIndex = startLine;
|
134
|
+
currentLineIndex < syntaxHighlightedLines.length;
|
135
|
+
currentLineIndex++
|
136
|
+
) {
|
137
|
+
if (currentLineIndex > endLineIndex) break;
|
138
|
+
if (currentLineIndex > syntaxHighlightedLines.length - 1) break;
|
132
139
|
|
133
|
-
|
134
|
-
|
135
|
-
|
140
|
+
// Find highlights that need to get rendered on the current line
|
141
|
+
let lineHighlights = highlights
|
142
|
+
.filter(
|
143
|
+
highlight =>
|
144
|
+
highlight.start.line <= currentLineIndex &&
|
145
|
+
highlight.end.line >= currentLineIndex,
|
146
|
+
)
|
147
|
+
.sort(
|
148
|
+
(a, b) =>
|
149
|
+
(a.start.line < currentLineIndex ? 0 : a.start.column) -
|
150
|
+
(b.start.line < currentLineIndex ? 0 : b.start.column),
|
151
|
+
);
|
152
|
+
|
153
|
+
// Check if this line has a full line highlight
|
154
|
+
let isWholeLine =
|
155
|
+
lineHighlights.length &&
|
156
|
+
!!lineHighlights.find(
|
157
|
+
h => h.start.line < currentLineIndex && h.end.line > currentLineIndex,
|
158
|
+
);
|
159
|
+
|
160
|
+
let lineLengthLimit =
|
161
|
+
opts.terminalWidth > lineNumberLength + 7
|
162
|
+
? opts.terminalWidth - (lineNumberLength + 5)
|
163
|
+
: 10;
|
164
|
+
|
165
|
+
// Split the line into line parts that will fit the provided terminal width
|
166
|
+
let colOffset = 0;
|
167
|
+
let lineEndCol = lineLengthLimit;
|
168
|
+
let syntaxHighlightedLine = syntaxHighlightedLines[currentLineIndex];
|
169
|
+
if (stringWidth(syntaxHighlightedLine) > lineLengthLimit) {
|
170
|
+
if (lineHighlights.length > 0) {
|
171
|
+
if (lineHighlights[0].start.line === currentLineIndex) {
|
172
|
+
colOffset = lineHighlights[0].start.column - 5;
|
173
|
+
} else if (lineHighlights[0].end.line === currentLineIndex) {
|
174
|
+
colOffset = lineHighlights[0].end.column - 5;
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
colOffset = colOffset > 0 ? colOffset : 0;
|
179
|
+
lineEndCol = colOffset + lineLengthLimit;
|
136
180
|
|
181
|
+
syntaxHighlightedLine = sliceAnsi(
|
182
|
+
syntaxHighlightedLine,
|
183
|
+
colOffset,
|
184
|
+
lineEndCol,
|
185
|
+
);
|
186
|
+
}
|
187
|
+
|
188
|
+
// Write the syntax highlighted line part
|
137
189
|
resultLines.push(
|
138
190
|
lineNumberPrefixer({
|
139
|
-
lineNumber: (
|
140
|
-
|
141
|
-
isHighlighted:
|
191
|
+
lineNumber: (currentLineIndex + 1).toString(10),
|
192
|
+
lineNumberLength,
|
193
|
+
isHighlighted: lineHighlights.length > 0,
|
142
194
|
}) + syntaxHighlightedLine,
|
143
195
|
);
|
144
196
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
197
|
+
let lineWidth = stringWidth(syntaxHighlightedLine);
|
198
|
+
let highlightLine = '';
|
199
|
+
if (isWholeLine) {
|
200
|
+
highlightLine = highlighter('^'.repeat(lineWidth));
|
201
|
+
} else if (lineHighlights.length > 0) {
|
202
|
+
let lastCol = 0;
|
203
|
+
let highlight = null;
|
204
|
+
let highlightHasEnded = false;
|
150
205
|
|
151
|
-
|
152
|
-
|
153
|
-
|
206
|
+
for (
|
207
|
+
let highlightIndex = 0;
|
208
|
+
highlightIndex < lineHighlights.length;
|
209
|
+
highlightIndex++
|
210
|
+
) {
|
211
|
+
// Set highlight to current highlight
|
212
|
+
highlight = lineHighlights[highlightIndex];
|
213
|
+
highlightHasEnded = false;
|
154
214
|
|
155
|
-
|
156
|
-
//
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
? foundHighlights.sort(
|
167
|
-
(a, b) =>
|
168
|
-
(a.start.line < i ? 0 : a.start.column) -
|
169
|
-
(b.start.line < i ? 0 : b.start.column),
|
170
|
-
)
|
171
|
-
: foundHighlights;
|
172
|
-
|
173
|
-
let lastCol = 0;
|
174
|
-
for (let col of sortedColumns) {
|
175
|
-
let startCol = col.start.line === i ? col.start.column : 0;
|
176
|
-
let endCol =
|
177
|
-
(col.end.line === i
|
178
|
-
? col.end.column
|
179
|
-
: originalLine.length - (lastCol || 1)) + 1;
|
180
|
-
|
181
|
-
if (startCol - lastCol > 0) {
|
182
|
-
let whitespace = originalLine
|
183
|
-
.substring(lastCol, startCol)
|
184
|
-
.replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT)
|
185
|
-
.replace(/\S/g, ' ');
|
186
|
-
|
187
|
-
highlightLine += whitespace;
|
188
|
-
}
|
215
|
+
// Calculate the startColumn and get the real width by doing a substring of the original
|
216
|
+
// line and replacing tabs with our tab replacement to support tab handling
|
217
|
+
let startCol = 0;
|
218
|
+
if (
|
219
|
+
highlight.start.line === currentLineIndex &&
|
220
|
+
highlight.start.column > colOffset
|
221
|
+
) {
|
222
|
+
startCol = lines[currentLineIndex]
|
223
|
+
.substring(colOffset, highlight.start.column)
|
224
|
+
.replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).length;
|
225
|
+
}
|
189
226
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
227
|
+
// Calculate the endColumn and get the real width by doing a substring of the original
|
228
|
+
// line and replacing tabs with our tab replacement to support tab handling
|
229
|
+
let endCol = lineWidth - 1;
|
230
|
+
if (highlight.end.line === currentLineIndex) {
|
231
|
+
endCol = lines[currentLineIndex]
|
232
|
+
.substring(colOffset, highlight.end.column)
|
233
|
+
.replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).length;
|
234
|
+
|
235
|
+
// If the endCol is too big for this line part, trim it so we can handle it in the next one
|
236
|
+
if (endCol > lineWidth) {
|
237
|
+
endCol = lineWidth - 1;
|
197
238
|
}
|
198
|
-
}
|
199
239
|
|
200
|
-
|
201
|
-
|
202
|
-
let sortedByEnd = sortedColumns.sort(
|
203
|
-
(a, b) => a.end.column - b.end.column,
|
204
|
-
);
|
240
|
+
highlightHasEnded = true;
|
241
|
+
}
|
205
242
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
243
|
+
// If endcol is smaller than lastCol it overlaps with another highlight and is no longer visible, we can skip those
|
244
|
+
if (endCol >= lastCol) {
|
245
|
+
let characters = endCol - startCol + 1;
|
246
|
+
if (startCol > lastCol) {
|
247
|
+
// startCol is before lastCol, so add spaces as padding before the highlight indicators
|
248
|
+
highlightLine += ' '.repeat(startCol - lastCol);
|
249
|
+
} else if (lastCol > startCol) {
|
250
|
+
// If last column is larger than the start, there's overlap in highlights
|
251
|
+
// This line adjusts the characters count to ensure we don't add too many characters
|
252
|
+
characters += startCol - lastCol;
|
210
253
|
}
|
254
|
+
|
255
|
+
// Append the highlight indicators
|
256
|
+
highlightLine += highlighter('^'.repeat(characters));
|
257
|
+
|
258
|
+
// Set the lastCol equal to character count between start of line part and highlight end-column
|
259
|
+
lastCol = endCol + 1;
|
260
|
+
}
|
261
|
+
|
262
|
+
// There's no point in processing more highlights if we reached the end of the line
|
263
|
+
if (endCol >= lineEndCol - 1) {
|
264
|
+
break;
|
211
265
|
}
|
212
266
|
}
|
213
267
|
|
214
|
-
|
268
|
+
// Append the highlight message if the current highlights ends on this line part
|
269
|
+
if (highlight && highlight.message && highlightHasEnded) {
|
270
|
+
highlightLine += ' ' + highlighter(highlight.message, true);
|
271
|
+
}
|
272
|
+
}
|
273
|
+
|
274
|
+
if (highlightLine) {
|
275
|
+
resultLines.push(
|
276
|
+
lineNumberPrefixer({
|
277
|
+
lineNumberLength,
|
278
|
+
isHighlighted: true,
|
279
|
+
}) + highlightLine,
|
280
|
+
);
|
215
281
|
}
|
216
282
|
}
|
217
283
|
|
package/test/codeframe.test.js
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
import assert from 'assert';
|
2
|
+
import {readFileSync} from 'fs';
|
3
|
+
import {join as joinPath} from 'path';
|
2
4
|
|
3
5
|
import codeframe from '../src/codeframe';
|
4
6
|
|
@@ -393,7 +395,7 @@ describe('codeframe', () => {
|
|
393
395
|
assert.equal(lines[7], ' 9 | test');
|
394
396
|
});
|
395
397
|
|
396
|
-
it('should properly pad numbers', () => {
|
398
|
+
it('should properly pad numbers for large files', () => {
|
397
399
|
let codeframeString = codeframe('test\n'.repeat(1000), [
|
398
400
|
{
|
399
401
|
start: {
|
@@ -415,17 +417,22 @@ describe('codeframe', () => {
|
|
415
417
|
column: 2,
|
416
418
|
line: 100,
|
417
419
|
},
|
418
|
-
message: 'test',
|
420
|
+
message: 'test 2',
|
419
421
|
},
|
420
422
|
]);
|
421
423
|
|
422
424
|
let lines = codeframeString.split(LINE_END);
|
423
425
|
assert.equal(lines.length, 7);
|
424
|
-
assert.equal(lines[0], '
|
426
|
+
assert.equal(lines[0], ' 98 | test');
|
427
|
+
assert.equal(lines[1], '> 99 | test');
|
428
|
+
assert.equal(lines[2], '> | ^ test');
|
429
|
+
assert.equal(lines[3], '> 100 | test');
|
430
|
+
assert.equal(lines[4], '> | ^ test 2');
|
431
|
+
assert.equal(lines[5], ' 101 | test');
|
425
432
|
assert.equal(lines[6], ' 102 | test');
|
426
433
|
});
|
427
434
|
|
428
|
-
it('should properly pad numbers', () => {
|
435
|
+
it('should properly pad numbers for short files', () => {
|
429
436
|
let codeframeString = codeframe('test\n'.repeat(1000), [
|
430
437
|
{
|
431
438
|
start: {
|
@@ -453,13 +460,17 @@ describe('codeframe', () => {
|
|
453
460
|
|
454
461
|
let lines = codeframeString.split(LINE_END);
|
455
462
|
assert.equal(lines.length, 11);
|
456
|
-
assert.equal(lines[0], '
|
463
|
+
assert.equal(lines[0], ' 6 | test');
|
464
|
+
assert.equal(lines[4], ' 9 | test');
|
465
|
+
assert.equal(lines[5], ' 10 | test');
|
466
|
+
assert.equal(lines[6], ' 11 | test');
|
457
467
|
assert.equal(lines[10], ' 14 | test');
|
458
468
|
});
|
459
469
|
|
460
470
|
it('should properly use maxLines', () => {
|
471
|
+
let line = 'test '.repeat(100);
|
461
472
|
let codeframeString = codeframe(
|
462
|
-
|
473
|
+
`${line}\n`.repeat(100),
|
463
474
|
[
|
464
475
|
{
|
465
476
|
start: {
|
@@ -487,14 +498,16 @@ describe('codeframe', () => {
|
|
487
498
|
{
|
488
499
|
useColor: false,
|
489
500
|
maxLines: 10,
|
501
|
+
terminalWidth: 5,
|
490
502
|
},
|
491
503
|
);
|
492
504
|
|
493
505
|
let lines = codeframeString.split(LINE_END);
|
494
506
|
assert.equal(lines.length, 13);
|
495
|
-
assert.equal(lines[0], '
|
496
|
-
assert.equal(lines[
|
497
|
-
assert.equal(lines[
|
507
|
+
assert.equal(lines[0], ' 4 | test test ');
|
508
|
+
assert.equal(lines[7], ' 10 | test test ');
|
509
|
+
assert.equal(lines[11], '> 13 | test test ');
|
510
|
+
assert.equal(lines[12], '> | ^^^^^^^^^^');
|
498
511
|
});
|
499
512
|
|
500
513
|
it('should be able to handle tabs', () => {
|
@@ -507,7 +520,7 @@ describe('codeframe', () => {
|
|
507
520
|
line: 1,
|
508
521
|
},
|
509
522
|
end: {
|
510
|
-
column:
|
523
|
+
column: 8,
|
511
524
|
line: 1,
|
512
525
|
},
|
513
526
|
message: 'test',
|
@@ -518,7 +531,7 @@ describe('codeframe', () => {
|
|
518
531
|
|
519
532
|
let lines = codeframeString.split(LINE_END);
|
520
533
|
assert.equal(lines[0], '> 1 | hel lo wor ld');
|
521
|
-
assert.equal(lines[1], '> |
|
534
|
+
assert.equal(lines[1], '> | ^^^^ test');
|
522
535
|
assert.equal(lines[2], ' 2 | Enjoy thi s nice cod eframe');
|
523
536
|
});
|
524
537
|
|
@@ -585,4 +598,202 @@ describe('codeframe', () => {
|
|
585
598
|
assert.equal(lines[4], '> 3 | test');
|
586
599
|
assert.equal(lines[5], '> | ^^ test');
|
587
600
|
});
|
601
|
+
|
602
|
+
it('Should truncate long lines and print message', () => {
|
603
|
+
let originalLine = 'hello world '.repeat(1000);
|
604
|
+
let codeframeString = codeframe(
|
605
|
+
originalLine,
|
606
|
+
[
|
607
|
+
{
|
608
|
+
start: {
|
609
|
+
column: 1000,
|
610
|
+
line: 1,
|
611
|
+
},
|
612
|
+
end: {
|
613
|
+
column: 1200,
|
614
|
+
line: 1,
|
615
|
+
},
|
616
|
+
message: 'This is a message',
|
617
|
+
},
|
618
|
+
],
|
619
|
+
{useColor: false, terminalWidth: 25},
|
620
|
+
);
|
621
|
+
|
622
|
+
let lines = codeframeString.split(LINE_END);
|
623
|
+
assert.equal(lines.length, 2);
|
624
|
+
assert.equal(lines[0], '> 1 | d hello world hello');
|
625
|
+
assert.equal(lines[1], '> | ^^^^^^^^^^^^^^ This is a message');
|
626
|
+
});
|
627
|
+
|
628
|
+
it('Truncation across multiple lines', () => {
|
629
|
+
let originalLine =
|
630
|
+
'hello world '.repeat(100) + '\n' + 'new line '.repeat(100);
|
631
|
+
let codeframeString = codeframe(
|
632
|
+
originalLine,
|
633
|
+
[
|
634
|
+
{
|
635
|
+
start: {
|
636
|
+
column: 15,
|
637
|
+
line: 1,
|
638
|
+
},
|
639
|
+
end: {
|
640
|
+
column: 400,
|
641
|
+
line: 1,
|
642
|
+
},
|
643
|
+
message: 'This is the first line',
|
644
|
+
},
|
645
|
+
{
|
646
|
+
start: {
|
647
|
+
column: 2,
|
648
|
+
line: 2,
|
649
|
+
},
|
650
|
+
end: {
|
651
|
+
column: 100,
|
652
|
+
line: 2,
|
653
|
+
},
|
654
|
+
message: 'This is the second line',
|
655
|
+
},
|
656
|
+
],
|
657
|
+
{useColor: false, terminalWidth: 25},
|
658
|
+
);
|
659
|
+
|
660
|
+
let lines = codeframeString.split(LINE_END);
|
661
|
+
assert.equal(lines.length, 4);
|
662
|
+
assert.equal(lines[0], '> 1 | ld hello world hell');
|
663
|
+
assert.equal(lines[1], '> | ^^^^^^^^^^^^^^ This is the first line');
|
664
|
+
assert.equal(lines[2], '> 2 | new line new line n');
|
665
|
+
assert.equal(lines[3], '> | ^^^^^^^^^^^^^^^^^^ This is the second line');
|
666
|
+
});
|
667
|
+
|
668
|
+
it('Truncation across various types and positions of highlights', () => {
|
669
|
+
let originalLine =
|
670
|
+
'hello world '.repeat(100) + '\n' + 'new line '.repeat(100);
|
671
|
+
let codeframeString = codeframe(
|
672
|
+
originalLine,
|
673
|
+
[
|
674
|
+
{
|
675
|
+
start: {
|
676
|
+
column: 2,
|
677
|
+
line: 1,
|
678
|
+
},
|
679
|
+
end: {
|
680
|
+
column: 5,
|
681
|
+
line: 1,
|
682
|
+
},
|
683
|
+
},
|
684
|
+
{
|
685
|
+
start: {
|
686
|
+
column: 6,
|
687
|
+
line: 1,
|
688
|
+
},
|
689
|
+
end: {
|
690
|
+
column: 10,
|
691
|
+
line: 1,
|
692
|
+
},
|
693
|
+
message: 'I have a message',
|
694
|
+
},
|
695
|
+
{
|
696
|
+
start: {
|
697
|
+
column: 15,
|
698
|
+
line: 1,
|
699
|
+
},
|
700
|
+
end: {
|
701
|
+
column: 25,
|
702
|
+
line: 1,
|
703
|
+
},
|
704
|
+
message: 'I also have a message',
|
705
|
+
},
|
706
|
+
{
|
707
|
+
start: {
|
708
|
+
column: 2,
|
709
|
+
line: 2,
|
710
|
+
},
|
711
|
+
end: {
|
712
|
+
column: 5,
|
713
|
+
line: 2,
|
714
|
+
},
|
715
|
+
message: 'This is the second line',
|
716
|
+
},
|
717
|
+
],
|
718
|
+
{useColor: false, terminalWidth: 25},
|
719
|
+
);
|
720
|
+
|
721
|
+
let lines = codeframeString.split(LINE_END);
|
722
|
+
assert.equal(lines.length, 4);
|
723
|
+
assert.equal(lines[0], '> 1 | hello world hello w');
|
724
|
+
assert.equal(lines[1], '> | ^^^^^^^^^ ^^^^^ I also have a message');
|
725
|
+
assert.equal(lines[2], '> 2 | new line new line n');
|
726
|
+
assert.equal(lines[3], '> | ^^^^ This is the second line');
|
727
|
+
});
|
728
|
+
|
729
|
+
it('Multi-line highlight w/ truncation', () => {
|
730
|
+
let originalLine =
|
731
|
+
'hello world '.repeat(100) + '\n' + 'new line '.repeat(100);
|
732
|
+
let codeframeString = codeframe(
|
733
|
+
originalLine,
|
734
|
+
[
|
735
|
+
{
|
736
|
+
start: {
|
737
|
+
column: 2,
|
738
|
+
line: 1,
|
739
|
+
},
|
740
|
+
end: {
|
741
|
+
column: 151,
|
742
|
+
line: 2,
|
743
|
+
},
|
744
|
+
message: 'I have a message',
|
745
|
+
},
|
746
|
+
],
|
747
|
+
{useColor: false, terminalWidth: 25},
|
748
|
+
);
|
749
|
+
|
750
|
+
let lines = codeframeString.split(LINE_END);
|
751
|
+
assert.equal(lines.length, 4);
|
752
|
+
assert.equal(lines[0], '> 1 | hello world hello w');
|
753
|
+
assert.equal(lines[1], '> | ^^^^^^^^^^^^^^^^^^');
|
754
|
+
assert.equal(lines[2], '> 2 | ew line new line ne');
|
755
|
+
assert.equal(lines[3], '> | ^^^^^^ I have a message');
|
756
|
+
});
|
757
|
+
|
758
|
+
it('Should pad properly, T-650', () => {
|
759
|
+
let fileContent = readFileSync(
|
760
|
+
joinPath(__dirname, './fixtures/a.js'),
|
761
|
+
'utf8',
|
762
|
+
);
|
763
|
+
let codeframeString = codeframe(
|
764
|
+
fileContent,
|
765
|
+
[
|
766
|
+
{
|
767
|
+
start: {
|
768
|
+
line: 8,
|
769
|
+
column: 10,
|
770
|
+
},
|
771
|
+
end: {
|
772
|
+
line: 8,
|
773
|
+
column: 48,
|
774
|
+
},
|
775
|
+
},
|
776
|
+
],
|
777
|
+
{
|
778
|
+
useColor: false,
|
779
|
+
syntaxHighlighting: false,
|
780
|
+
language: 'js',
|
781
|
+
terminalWidth: 100,
|
782
|
+
},
|
783
|
+
);
|
784
|
+
|
785
|
+
let lines = codeframeString.split(LINE_END);
|
786
|
+
assert.equal(lines.length, 5);
|
787
|
+
assert.equal(lines[0], ` 7 | import Tooltip from '../tooltip';`);
|
788
|
+
assert.equal(
|
789
|
+
lines[1],
|
790
|
+
`> 8 | import VisuallyHidden from '../visually-hidden';`,
|
791
|
+
);
|
792
|
+
assert.equal(
|
793
|
+
lines[2],
|
794
|
+
'> | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^',
|
795
|
+
);
|
796
|
+
assert.equal(lines[3], ' 9 | ');
|
797
|
+
assert.equal(lines[4], ' 10 | /**');
|
798
|
+
});
|
588
799
|
});
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import test from 'test';
|
2
|
+
import component from './component';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* This is a comment
|
6
|
+
*/
|
7
|
+
import Tooltip from '../tooltip';
|
8
|
+
import VisuallyHidden from '../visually-hidden';
|
9
|
+
|
10
|
+
/**
|
11
|
+
* This is another comment
|
12
|
+
*/
|
13
|
+
import {Label} from './label';
|