Skip to content

Commit e8712bd

Browse files
committed
Add concept of gutter cells
1 parent bd95106 commit e8712bd

File tree

4 files changed

+97
-4
lines changed

4 files changed

+97
-4
lines changed

src/factory/html.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,26 @@ function createLineElement(line, meta, index, language, getLineClassName, tokens
3030
const lineClassName = joinClassNames(getLineClassName(lineData), 'grvsc-line');
3131
const attrs = mergeAttributes({ class: lineClassName }, line.attrs);
3232
const children = typeof tokens === 'string' ? [tokens] : mergeSimilarTokens(tokens);
33-
return span(attrs, children, { whitespace: TriviaRenderFlags.NoWhitespace });
33+
const gutterCells = line.gutterCells.map((cell, i) =>
34+
createGutterCellElement(cell, i === line.gutterCells.length - 1)
35+
);
36+
return span(
37+
attrs,
38+
[...gutterCells, span({ class: 'grvsc-source' }, children, { whitespace: TriviaRenderFlags.NoWhitespace })],
39+
{ whitespace: TriviaRenderFlags.NoWhitespace }
40+
);
41+
}
42+
43+
/**
44+
* @param {GutterCell} cell
45+
* @param {boolean} isLast
46+
*/
47+
function createGutterCellElement(cell, isLast) {
48+
return span(
49+
{ class: joinClassNames('grvsc-gutter', cell.className, isLast && 'grvsc-gutter-last') },
50+
[escapeHTML(cell.text || '')],
51+
{ whitespace: TriviaRenderFlags.NoWhitespace }
52+
);
3453
}
3554

3655
/**

src/registerCodeNode.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ async function registerCodeSpan(
8282
const [registry, unlockRegistry] = await getTextMateRegistry();
8383
try {
8484
/** @type {Line[]} */
85-
const lines = [{ text, data: {}, attrs: {} }];
85+
const lines = [{ text, data: {}, attrs: {}, gutterCells: [] }];
8686
const { tokenTypes, languageId } = getGrammar(scope, grammarCache);
8787
const grammar = await registry.loadGrammarWithConfiguration(scope, languageId, { tokenTypes });
8888
codeBlockRegistry.register(registryKey, {

src/transformers/getTransformedLines.js

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,18 @@
99
* @returns {Promise<Line[]>}
1010
*/
1111
async function getTransformedLines(transformers, text, languageName, meta) {
12-
/** @type {Line[]} */
12+
/** @type {Omit<Line, 'gutterCells'>[]} */
1313
const result = [];
1414
const rawLines = text.split(/\r?\n/);
1515
const prevTransformerStates = [];
16+
const gutterCellsPerTransformer = [];
17+
/** @type {GutterCell[][][]} */
18+
const gutterCells = [];
1619

1720
linesLoop: for (let lineIndex = 0; lineIndex < rawLines.length; lineIndex++) {
1821
let line = rawLines[lineIndex];
22+
/** @type {GutterCell[][]} */
23+
const lineGutterCells = [];
1924
const attrs = {};
2025
const graphQLData = {};
2126
for (let i = 0; i < transformers.length; i++) {
@@ -33,14 +38,76 @@ async function getTransformedLines(transformers, text, languageName, meta) {
3338
if (!txResult.line) {
3439
continue linesLoop;
3540
}
41+
if (txResult.gutterCells) {
42+
gutterCellsPerTransformer[i] = Math.max(txResult.gutterCells.length, gutterCellsPerTransformer[i] || 0);
43+
lineGutterCells[i] = txResult.gutterCells;
44+
}
3645
line = txResult.line.text;
3746
Object.assign(attrs, txResult.line.attrs);
3847
Object.assign(graphQLData, txResult.data);
3948
}
49+
gutterCells.push(lineGutterCells);
4050
result.push({ text: line, attrs, data: graphQLData });
4151
}
4252

43-
return result;
53+
const flattenedGutterCells = flattenGutterCells(gutterCells, gutterCellsPerTransformer);
54+
return result.map((line, i) => ({ ...line, gutterCells: flattenedGutterCells[i] }));
55+
}
56+
57+
/**
58+
* Transforms a 3D array of gutter cells into a 2D array of gutter cells.
59+
* The input is in the form of gutter cells per line transformer per line,
60+
* whereas the output is is gutter cells per line. Each line transformer can
61+
* return more than one gutter cell, and need not return the same number of
62+
* cells for each line, so the flattening must be done in a way that ensures
63+
* that each line transformer has its gutter cells aligned to the same index
64+
* in every line. For example, for the input
65+
*
66+
* ```
67+
* [
68+
* [[t0], [t1a, t1b], [t2]], // Line 1
69+
* [undefined, [t1], [t2]], // Line 2
70+
* [[t0a, t0b], undefined, [t2a, t2b]] // Line 3
71+
* ]
72+
* ```
73+
*
74+
* we would flatten to
75+
*
76+
* ```
77+
* [
78+
* [t0, undefined, t1a, t1b, t2, undefined], // Line 1
79+
* [undefined, undefined, t1, undefined, t2, undefined], // Line 2
80+
* [t0a, t0b, undefined, undefined, t2a, t2b] // Line 3
81+
* ]
82+
* ```
83+
*
84+
* such that each of the three transformers (t0, t1, t2) reserve two gutter
85+
* cells for itself, padding empty spaces in the final array with `undefined`
86+
* to ensure correct vertical alignment.
87+
*
88+
* The parameter `gutterCellsPerTransformer` can be derived from `gutterCells`,
89+
* but as an optimization, we already know it from previously iterating through
90+
* line transformer results.
91+
*
92+
* @param {GutterCell[][][]} gutterCells
93+
* @param {number[]} gutterCellsPerTransformer
94+
* @returns {GutterCell[][]}
95+
*/
96+
function flattenGutterCells(gutterCells, gutterCellsPerTransformer) {
97+
return gutterCells.map(transformerResults => {
98+
/** @type {GutterCell[]} */
99+
const result = [];
100+
for (let i = 0; i < transformerResults.length; i++) {
101+
const currentTransformerCells = transformerResults[i];
102+
const length = currentTransformerCells ? currentTransformerCells.length : 0;
103+
const padding = gutterCellsPerTransformer[i] - length;
104+
if (currentTransformerCells) {
105+
result.push(...currentTransformerCells);
106+
}
107+
result.fill(undefined, result.length, result.length + padding);
108+
}
109+
return result;
110+
});
44111
}
45112

46113
module.exports = getTransformedLines;

src/types.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ interface MarkdownNode extends grvsc.gql.Node {
180180
}
181181

182182
type Line = {
183+
gutterCells: (GutterCell | undefined)[];
183184
text: string;
184185
attrs: object;
185186
data: object;
@@ -213,8 +214,14 @@ interface LineTransformerInfo<T> {
213214
state: T | undefined;
214215
}
215216

217+
interface GutterCell {
218+
className?: string;
219+
text?: string;
220+
}
221+
216222
interface LineTransformerResult<T> extends LineTransformerInfo<T> {
217223
data?: object;
224+
gutterCells?: (GutterCell | undefined)[];
218225
}
219226

220227
interface LineTransformerArgs<T> extends LineTransformerInfo<T> {

0 commit comments

Comments
 (0)