@parcel/codeframe 2.0.0-nightly.97 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|
3
|
+
"version": "2.1.0",
|
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';
|