use modern methods to render HTML diffs

This commit is contained in:
Lordfirespeed 2023-03-05 22:48:40 +00:00
parent 551a0b407f
commit ab5ff8d148
No known key found for this signature in database
GPG key ID: 9C01965EC9DE6690
10 changed files with 202 additions and 195 deletions

View file

@ -82,6 +82,7 @@ export default class LineByLineRenderer {
generateEmptyDiff(): string { generateEmptyDiff(): string {
return this.hoganUtils.render(genericTemplatesPath, 'empty-diff', { return this.hoganUtils.render(genericTemplatesPath, 'empty-diff', {
contentClass: 'd2h-code-line', contentClass: 'd2h-code-line',
colspan: '2',
CSSLineClass: renderUtils.CSSLineClass, CSSLineClass: renderUtils.CSSLineClass,
}); });
} }
@ -93,35 +94,43 @@ export default class LineByLineRenderer {
return file.blocks return file.blocks
.map(block => { .map(block => {
let lines = this.hoganUtils.render(genericTemplatesPath, 'block-header', { const lines: string[] = [];
lines.push(
this.hoganUtils.render(genericTemplatesPath, 'block-header', {
CSSLineClass: renderUtils.CSSLineClass, CSSLineClass: renderUtils.CSSLineClass,
margin_colspan: '2',
colspan: '1',
blockHeader: file.isTooBig ? block.header : renderUtils.escapeForHtml(block.header), blockHeader: file.isTooBig ? block.header : renderUtils.escapeForHtml(block.header),
lineClass: 'd2h-code-linenumber', lineClass: 'd2h-code-linenumber',
contentClass: 'd2h-code-line', contentClass: 'd2h-code-line',
}); }),
);
this.applyLineGroupping(block).forEach(([contextLines, oldLines, newLines]) => { this.applyLineGrouping(block).forEach(([contextLines, oldLines, newLines]) => {
if (oldLines.length && newLines.length && !contextLines.length) { if (oldLines.length && newLines.length && !contextLines.length) {
this.applyRematchMatching(oldLines, newLines, matcher).map(([oldLines, newLines]) => { this.applyRematchMatching(oldLines, newLines, matcher).map(([oldLines, newLines]) => {
const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines); const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines);
lines += left; lines.push(...left);
lines += right; lines.push(...right);
}); });
} else if (contextLines.length) { } else if (contextLines.length) {
contextLines.forEach(line => { contextLines.forEach(line => {
const { prefix, content } = renderUtils.deconstructLine(line.content, file.isCombined); const { prefix, content } = renderUtils.deconstructLine(line.content, file.isCombined);
lines += this.generateSingleLineHtml({ lines.push(
this.generateSingleLineHtml({
type: renderUtils.CSSLineClass.CONTEXT, type: renderUtils.CSSLineClass.CONTEXT,
prefix: prefix, prefix: prefix,
content: content, content: content,
oldNumber: line.oldNumber, oldNumber: line.oldNumber,
newNumber: line.newNumber, newNumber: line.newNumber,
}); }),
);
}); });
} else if (oldLines.length || newLines.length) { } else if (oldLines.length || newLines.length) {
const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines); const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines);
lines += left; lines.push(...left);
lines += right; lines.push(...right);
} else { } else {
console.error('Unknown state reached while processing groups of lines', contextLines, oldLines, newLines); console.error('Unknown state reached while processing groups of lines', contextLines, oldLines, newLines);
} }
@ -129,10 +138,13 @@ export default class LineByLineRenderer {
return lines; return lines;
}) })
.map((block_html: string[]) => {
return block_html.map(line => `<tr>${line}</tr>`).join('\n');
})
.join('\n'); .join('\n');
} }
applyLineGroupping(block: DiffBlock): DiffLineGroups { applyLineGrouping(block: DiffBlock): DiffLineGroups {
const blockLinesGroups: DiffLineGroups = []; const blockLinesGroups: DiffLineGroups = [];
let oldLines: (DiffLineDeleted & DiffLineContent)[] = []; let oldLines: (DiffLineDeleted & DiffLineContent)[] = [];
@ -189,9 +201,9 @@ export default class LineByLineRenderer {
} }
processChangedLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml { processChangedLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml {
const fileHtml = { const fileHtml: FileHtml = {
right: '', left: [],
left: '', right: [],
}; };
const maxLinesNumber = Math.max(oldLines.length, newLines.length); const maxLinesNumber = Math.max(oldLines.length, newLines.length);
@ -241,8 +253,8 @@ export default class LineByLineRenderer {
: undefined; : undefined;
const { left, right } = this.generateLineHtml(preparedOldLine, preparedNewLine); const { left, right } = this.generateLineHtml(preparedOldLine, preparedNewLine);
fileHtml.left += left; fileHtml.left.push(...left);
fileHtml.right += right; fileHtml.right.push(...right);
} }
return fileHtml; return fileHtml;
@ -250,27 +262,34 @@ export default class LineByLineRenderer {
generateLineHtml(oldLine?: DiffPreparedLine, newLine?: DiffPreparedLine): FileHtml { generateLineHtml(oldLine?: DiffPreparedLine, newLine?: DiffPreparedLine): FileHtml {
return { return {
left: this.generateSingleLineHtml(oldLine), left: [this.generateSingleLineHtml(oldLine)],
right: this.generateSingleLineHtml(newLine), right: [this.generateSingleLineHtml(newLine)],
}; };
} }
generateSingleLineHtml(line?: DiffPreparedLine): string { generateSingleLineHtml(line?: DiffPreparedLine): string {
if (line === undefined) return ''; if (line === undefined) return '';
const lineNumberHtml = this.hoganUtils.render(baseTemplatesPath, 'numbers', { const oldLineNumberHtml = this.hoganUtils.render(genericTemplatesPath, 'line-number', {
oldNumber: line.oldNumber || '', type: line.type,
newNumber: line.newNumber || '', lineClass: line.oldNumber ? 'd2h-code-linenumber' : 'd2h-code-emptyplaceholder',
lineNumber: line.oldNumber,
}); });
return this.hoganUtils.render(genericTemplatesPath, 'line', { const newLineNumberHtml = this.hoganUtils.render(genericTemplatesPath, 'line-number', {
type: line.type,
lineClass: line.newNumber ? 'd2h-code-linenumber' : 'd2h-code-emptyplaceholder',
lineNumber: line.newNumber,
});
const newLineContentHtml = this.hoganUtils.render(genericTemplatesPath, 'line', {
type: line.type, type: line.type,
lineClass: 'd2h-code-linenumber',
contentClass: 'd2h-code-line', contentClass: 'd2h-code-line',
prefix: line.prefix === ' ' ? '&nbsp;' : line.prefix, prefix: line.prefix === ' ' ? '&nbsp;' : line.prefix,
content: line.content, content: line.content,
lineNumber: lineNumberHtml,
}); });
return oldLineNumberHtml.concat(newLineNumberHtml, newLineContentHtml);
} }
} }
@ -289,6 +308,6 @@ type DiffPreparedLine = {
}; };
type FileHtml = { type FileHtml = {
left: string; left: string[];
right: string; right: string[];
}; };

View file

@ -55,7 +55,7 @@ export default class SideBySideRenderer {
return this.hoganUtils.render(genericTemplatesPath, 'wrapper', { content: diffsHtml }); return this.hoganUtils.render(genericTemplatesPath, 'wrapper', { content: diffsHtml });
} }
makeFileDiffHtml(file: DiffFile, diffs: FileHtml): string { makeFileDiffHtml(file: DiffFile, diffs: string): string {
if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return ''; if (this.config.renderNothingWhenEmpty && Array.isArray(file.blocks) && file.blocks.length === 0) return '';
const fileDiffTemplate = this.hoganUtils.template(baseTemplatesPath, 'file-diff'); const fileDiffTemplate = this.hoganUtils.template(baseTemplatesPath, 'file-diff');
@ -79,34 +79,32 @@ export default class SideBySideRenderer {
}); });
} }
generateEmptyDiff(): FileHtml { generateEmptyDiff(): string {
return { return this.hoganUtils.render(genericTemplatesPath, 'empty-diff', {
right: '',
left: this.hoganUtils.render(genericTemplatesPath, 'empty-diff', {
contentClass: 'd2h-code-side-line', contentClass: 'd2h-code-side-line',
colspan: '4',
CSSLineClass: renderUtils.CSSLineClass, CSSLineClass: renderUtils.CSSLineClass,
}), });
};
} }
generateFileHtml(file: DiffFile): FileHtml { generateFileHtml(file: DiffFile): string {
const matcher = Rematch.newMatcherFn( const matcher = Rematch.newMatcherFn(
Rematch.newDistanceFn((e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content), Rematch.newDistanceFn((e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content),
); );
return file.blocks return file.blocks
.map(block => { .map(block => {
const fileHtml = { const fileHtml: FileHtml = {
left: this.makeHeaderHtml(block.header, file), left: [this.makeHeaderHtml(block.header, file)],
right: this.makeHeaderHtml(''), right: [''],
}; };
this.applyLineGroupping(block).forEach(([contextLines, oldLines, newLines]) => { this.applyLineGrouping(block).forEach(([contextLines, oldLines, newLines]) => {
if (oldLines.length && newLines.length && !contextLines.length) { if (oldLines.length && newLines.length && !contextLines.length) {
this.applyRematchMatching(oldLines, newLines, matcher).map(([oldLines, newLines]) => { this.applyRematchMatching(oldLines, newLines, matcher).map(([oldLines, newLines]) => {
const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines); const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines);
fileHtml.left += left; fileHtml.left.push(...left);
fileHtml.right += right; fileHtml.right.push(...right);
}); });
} else if (contextLines.length) { } else if (contextLines.length) {
contextLines.forEach(line => { contextLines.forEach(line => {
@ -125,13 +123,13 @@ export default class SideBySideRenderer {
number: line.newNumber, number: line.newNumber,
}, },
); );
fileHtml.left += left; fileHtml.left.push(...left);
fileHtml.right += right; fileHtml.right.push(...right);
}); });
} else if (oldLines.length || newLines.length) { } else if (oldLines.length || newLines.length) {
const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines); const { left, right } = this.processChangedLines(file.isCombined, oldLines, newLines);
fileHtml.left += left; fileHtml.left.push(...left);
fileHtml.right += right; fileHtml.right.push(...right);
} else { } else {
console.error('Unknown state reached while processing groups of lines', contextLines, oldLines, newLines); console.error('Unknown state reached while processing groups of lines', contextLines, oldLines, newLines);
} }
@ -139,15 +137,19 @@ export default class SideBySideRenderer {
return fileHtml; return fileHtml;
}) })
.reduce( .map((block_html: FileHtml) => {
(accomulated, html) => { let block_html_string = '';
return { left: accomulated.left + html.left, right: accomulated.right + html.right }; for (let block_line_index = 0; block_line_index < block_html.left.length; block_line_index++) {
}, block_html_string = block_html_string.concat(
{ left: '', right: '' }, `<tr>${block_html.left[block_line_index]} ${block_html.right[block_line_index]}</tr>`,
); );
} }
return block_html_string;
})
.join('\n');
}
applyLineGroupping(block: DiffBlock): DiffLineGroups { applyLineGrouping(block: DiffBlock): DiffLineGroups {
const blockLinesGroups: DiffLineGroups = []; const blockLinesGroups: DiffLineGroups = [];
let oldLines: (DiffLineDeleted & DiffLineContent)[] = []; let oldLines: (DiffLineDeleted & DiffLineContent)[] = [];
@ -206,6 +208,8 @@ export default class SideBySideRenderer {
makeHeaderHtml(blockHeader: string, file?: DiffFile): string { makeHeaderHtml(blockHeader: string, file?: DiffFile): string {
return this.hoganUtils.render(genericTemplatesPath, 'block-header', { return this.hoganUtils.render(genericTemplatesPath, 'block-header', {
CSSLineClass: renderUtils.CSSLineClass, CSSLineClass: renderUtils.CSSLineClass,
margin_colspan: '1',
colspan: '3',
blockHeader: file?.isTooBig ? blockHeader : renderUtils.escapeForHtml(blockHeader), blockHeader: file?.isTooBig ? blockHeader : renderUtils.escapeForHtml(blockHeader),
lineClass: 'd2h-code-side-linenumber', lineClass: 'd2h-code-side-linenumber',
contentClass: 'd2h-code-side-line', contentClass: 'd2h-code-side-line',
@ -213,9 +217,9 @@ export default class SideBySideRenderer {
} }
processChangedLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml { processChangedLines(isCombined: boolean, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml {
const fileHtml = { const fileHtml: FileHtml = {
right: '', left: [],
left: '', right: [],
}; };
const maxLinesNumber = Math.max(oldLines.length, newLines.length); const maxLinesNumber = Math.max(oldLines.length, newLines.length);
@ -263,8 +267,8 @@ export default class SideBySideRenderer {
: undefined; : undefined;
const { left, right } = this.generateLineHtml(preparedOldLine, preparedNewLine); const { left, right } = this.generateLineHtml(preparedOldLine, preparedNewLine);
fileHtml.left += left; fileHtml.left.push(...left);
fileHtml.right += right; fileHtml.right.push(...right);
} }
return fileHtml; return fileHtml;
@ -272,8 +276,8 @@ export default class SideBySideRenderer {
generateLineHtml(oldLine?: DiffPreparedLine, newLine?: DiffPreparedLine): FileHtml { generateLineHtml(oldLine?: DiffPreparedLine, newLine?: DiffPreparedLine): FileHtml {
return { return {
left: this.generateSingleHtml(oldLine), left: [this.generateSingleHtml(oldLine)],
right: this.generateSingleHtml(newLine), right: [this.generateSingleHtml(newLine)],
}; };
} }
@ -281,14 +285,22 @@ export default class SideBySideRenderer {
const lineClass = 'd2h-code-side-linenumber'; const lineClass = 'd2h-code-side-linenumber';
const contentClass = 'd2h-code-side-line'; const contentClass = 'd2h-code-side-line';
return this.hoganUtils.render(genericTemplatesPath, 'line', { const type = line?.type || `${renderUtils.CSSLineClass.CONTEXT} d2h-emptyplaceholder`;
type: line?.type || `${renderUtils.CSSLineClass.CONTEXT} d2h-emptyplaceholder`,
const line_number_cell = this.hoganUtils.render(genericTemplatesPath, 'line-number', {
type: type,
lineClass: line !== undefined ? lineClass : `${lineClass} d2h-code-side-emptyplaceholder`, lineClass: line !== undefined ? lineClass : `${lineClass} d2h-code-side-emptyplaceholder`,
lineNumber: line?.number,
});
const line_content_cell = this.hoganUtils.render(genericTemplatesPath, 'line', {
type: type,
contentClass: line !== undefined ? contentClass : `${contentClass} d2h-code-side-emptyplaceholder`, contentClass: line !== undefined ? contentClass : `${contentClass} d2h-code-side-emptyplaceholder`,
prefix: line?.prefix === ' ' ? '&nbsp;' : line?.prefix, prefix: line?.prefix === ' ' ? '&nbsp;' : line?.prefix,
content: line?.content, content: line?.content,
lineNumber: line?.number,
}); });
return line_number_cell.concat(line_content_cell);
} }
} }
@ -306,6 +318,6 @@ type DiffPreparedLine = {
}; };
type FileHtml = { type FileHtml = {
left: string; left: string[];
right: string; right: string[];
}; };

View file

@ -1,6 +1,4 @@
<tr> <td class="{{lineClass}} {{CSSLineClass.INFO}}" colspan="{{margin_colspan}}"></td>
<td class="{{lineClass}} {{CSSLineClass.INFO}}"></td> <td class="{{CSSLineClass.INFO}}" colspan="{{colspan}}">
<td class="{{CSSLineClass.INFO}}">
<div class="{{contentClass}}">{{#blockHeader}}{{{blockHeader}}}{{/blockHeader}}{{^blockHeader}}&nbsp;{{/blockHeader}}</div> <div class="{{contentClass}}">{{#blockHeader}}{{{blockHeader}}}{{/blockHeader}}{{^blockHeader}}&nbsp;{{/blockHeader}}</div>
</td> </td>
</tr>

View file

@ -1,5 +1,5 @@
<tr> <tr>
<td class="{{CSSLineClass.INFO}}"> <td class="{{CSSLineClass.INFO}}" colspan="{{#colspan}}{{{.}}}{{/colspan}}{{^colspan}}2{{/colspan}}">
<div class="{{contentClass}}"> <div class="{{contentClass}}">
File without changes File without changes
</div> </div>

View file

@ -0,0 +1 @@
<td class="{{lineClass}} {{type}}" {{#lineNumber}}data-line-number="{{{lineNumber}}}"{{/lineNumber}}></td>

View file

@ -1,21 +1,5 @@
<tr>
<td class="{{lineClass}} {{type}}">
{{{lineNumber}}}
</td>
<td class="{{type}}"> <td class="{{type}}">
<div class="{{contentClass}}"> <div class="{{contentClass}}">
{{#prefix}} {{#prefix}}<span class="d2h-code-line-ctn d2h-code-marker" data-code-marker="{{{prefix}}}">{{/prefix}}{{^prefix}}<span class="d2h-code-line-ctn d2h-code-marker" data-code-marker=&nsbp;>{{/prefix}}{{#content}}{{content}}</span>{{/content}}{{^content}}<br></span>{{/content}}
<span class="d2h-code-line-prefix">{{{prefix}}}</span>
{{/prefix}}
{{^prefix}}
<span class="d2h-code-line-prefix">&nbsp;</span>
{{/prefix}}
{{#content}}
<span class="d2h-code-line-ctn">{{{content}}}</span>
{{/content}}
{{^content}}
<span class="d2h-code-line-ctn"><br></span>
{{/content}}
</div> </div>
</td> </td>
</tr>

View file

@ -5,6 +5,14 @@
<div class="d2h-file-diff"> <div class="d2h-file-diff">
<div class="d2h-code-wrapper"> <div class="d2h-code-wrapper">
<table class="d2h-diff-table"> <table class="d2h-diff-table">
<thead hidden>
<tr>
<th scope="col">Original file line number</th>
<th scope="col">Diff line number</th>
<th scope="col">Content</th>
</tr>
</thead>
<tbody class="d2h-diff-tbody"> <tbody class="d2h-diff-tbody">
{{{diffs}}} {{{diffs}}}
</tbody> </tbody>

View file

@ -1,2 +0,0 @@
<div class="line-num1">{{oldNumber}}</div>
<div class="line-num2">{{newNumber}}</div>

View file

@ -5,18 +5,25 @@
<div class="d2h-files-diff"> <div class="d2h-files-diff">
<div class="d2h-file-side-diff"> <div class="d2h-file-side-diff">
<div class="d2h-code-wrapper"> <div class="d2h-code-wrapper">
<table class="d2h-diff-table"> <table class="d2h-diff-table d2h-split-diff">
<thead hidden>
<tr>
<th scope="col">Original file line number</th>
<th scope="col">Original file content</th>
<th scope="col">Diff line number</th>
<th scope="col">Diff content</th>
</tr>
</thead>
<colgroup>
<col width="40">
<col>
<col width="40">
<col>
</colgroup>
<tbody class="d2h-diff-tbody"> <tbody class="d2h-diff-tbody">
{{{diffs.left}}} {{{diffs}}}
</tbody>
</table>
</div>
</div>
<div class="d2h-file-side-diff">
<div class="d2h-code-wrapper">
<table class="d2h-diff-table">
<tbody class="d2h-diff-tbody">
{{{diffs.right}}}
</tbody> </tbody>
</table> </table>
</div> </div>

View file

@ -7,6 +7,17 @@
.d2h-wrapper { .d2h-wrapper {
text-align: left; text-align: left;
word-wrap: break-word;
}
.d2h-wrapper * {
box-sizing: border-box;
}
.d2h-wrapper table {
border-spacing: 0;
border-collapse: collapse;
table-layout: fixed;
} }
.d2h-file-header { .d2h-file-header {
@ -94,9 +105,14 @@
.d2h-diff-table { .d2h-diff-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: separate;
font-family: 'Menlo', 'Consolas', monospace; font-family: 'Menlo', 'Consolas', monospace;
font-size: 13px; font-size: 13px;
word-wrap: break-word;
}
.d2h-diff-table.d2h-split-diff {
table-layout: fixed;
} }
.d2h-files-diff { .d2h-files-diff {
@ -104,46 +120,40 @@
width: 100%; width: 100%;
} }
.d2h-file-diff {
overflow-y: hidden;
}
.d2h-files-diff.d2h-d-none, .d2h-files-diff.d2h-d-none,
.d2h-file-diff.d2h-d-none { .d2h-file-diff.d2h-d-none {
display: none; display: none;
} }
.d2h-file-side-diff { .d2h-file-side-diff {
display: inline-block; width: 100%;
overflow-x: scroll;
overflow-y: hidden;
width: 50%;
}
.d2h-code-line {
display: inline-block;
white-space: nowrap;
user-select: none;
width: calc(100% - 16em);
/* Compensate for the absolute positioning of the line numbers */
padding: 0 8em;
} }
.d2h-code-line,
.d2h-code-side-line { .d2h-code-side-line {
display: inline-block;
white-space: nowrap;
user-select: none; user-select: none;
width: calc(100% - 9em); padding-left: 22px;
/* Compensate for the absolute positioning of the line numbers */ position: relative;
padding: 0 4.5em; padding-right: 10px;
line-height: 20px;
vertical-align: top;
}
.d2h-code-marker::before {
position: absolute;
top: 1px;
left: 8px;
padding-right: 8px;
content: attr(data-code-marker);
} }
.d2h-code-line-ctn { .d2h-code-line-ctn {
display: inline-block; display: table-cell;
background: none; background: none;
padding: 0; padding: 0;
word-wrap: normal; word-wrap: anywhere;
white-space: pre; white-space: pre-wrap;
overflow: visible;
user-select: text; user-select: text;
width: 100%; width: 100%;
vertical-align: middle; vertical-align: middle;
@ -178,63 +188,33 @@
white-space: pre; white-space: pre;
} }
.line-num1 { .d2h-code-linenumber::before,
box-sizing: border-box; .d2h-code-side-linenumber::before {
float: left; content: attr(data-line-number);
width: 3.5em;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 0.5em 0 0.5em;
}
.line-num2 {
box-sizing: border-box;
float: right;
width: 3.5em;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 0.5em 0 0.5em;
}
.d2h-code-linenumber {
box-sizing: border-box;
width: 7.5em;
/* Keep the numbers fixed on line contents scroll */
position: absolute;
display: inline-block;
background-color: #fff;
color: rgba(0, 0, 0, 0.3);
text-align: right;
border: solid #eeeeee;
border-width: 0 1px 0 1px;
cursor: pointer;
}
.d2h-code-linenumber:after {
content: '\200b';
}
.d2h-code-side-linenumber {
/* Keep the numbers fixed on line contents scroll */
position: absolute;
display: inline-block;
box-sizing: border-box;
width: 4em;
background-color: #fff;
color: rgba(0, 0, 0, 0.3);
text-align: right;
border: solid #eeeeee;
border-width: 0 1px 0 1px;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 0.5em 0 0.5em;
} }
.d2h-code-linenumber:after,
.d2h-code-side-linenumber:after { .d2h-code-side-linenumber:after {
content: '\200b'; content: '\200b';
} }
.d2h-code-side-linenumber,
.d2h-code-linenumber {
/* Keep the numbers fixed on line contents scroll */
position: relative;
background-color: #fff;
color: rgba(0, 0, 0, 0.3);
text-align: right;
border: solid #eeeeee;
border-width: 0 1px 0 1px;
line-height: 20px;
font-size: 12px;
width: 1%;
min-width: 50px;
padding-right: 10px;
padding-left: 10px;
}
.d2h-code-side-emptyplaceholder, .d2h-code-side-emptyplaceholder,
.d2h-emptyplaceholder { .d2h-emptyplaceholder {
background-color: #f1f1f1; background-color: #f1f1f1;