Skip to content

Commit

Permalink
Merge pull request #330 from PDConSec:feature/code-in-md
Browse files Browse the repository at this point in the history
0.12.7
  • Loading branch information
PeterWone authored Jul 7, 2024
2 parents 591dff2 + 6fb7709 commit 175d6a5
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 112 deletions.
13 changes: 9 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
# Change Log

### 0.12.7

- [#329](https://github.com/PDConSec/vsc-print/issues/329) - syntax coloured source in Markdown fenced blocks
- [#328](https://github.com/PDConSec/vsc-print/issues/328) - user supplied CSS

### 0.12.5

- [#326](https://github.com/PDConSec/vsc-print/issues/326) - support for Kroki
- this unifies the rendering of a large number of diagram notations, notably Mermaid and C4
- Kroki is server based. In line with our philosophy of off-line operation, there is a setting for the URL of the Kroki server and a link in the setting description to instructions for setting up a local installation of Kroki.
- # Reworked Katex integration to support
- [#324](https://github.com/PDConSec/vsc-print/issues/324) - Reworked Katex integration to support
- `$$` fenced display blocks
- [#324](https://github.com/PDConSec/vsc-print/issues/324) `$%...%$` inline equations
- `$%...%$` inline equations
- # MHCHEM equations
- [#327](https://github.com/PDConSec/vsc-print/issues/327) - separate settings for visibility of print and preview icons
- localised several recently added settings
- [#327](https://github.com/PDConSec/vsc-print/issues/327) - separate settings for visibility of print and preview icons
- localised several recently added settings
- [#287](https://github.com/PDConSec/vsc-print/issues/287) = new scheme [none] is black and white

### 0.12.3
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ Kroki is server-based. Normally we won't do anything that can't work offline, bu

So while there was a drop in functionality for 0.12.3, with this release you can use the following:

| | | | | | | |
|--------------|------------------|-----------|------------|---------|--------|------------|
| BlockDiag | BPMN | Bytefield | SeqDiag | ActDiag | NwDiag | PacketDiag |
| RackDiag | C4 with PlantUML| D2 | DBML | Ditaa | Erd | Excalidraw |
| GraphViz | KaTeX | Mermaid | MHCHEM | Nomnoml | Pikchr | PlantUML |
| SmilesDrawer | Structurizr | Svgbob | Symbolator | Tikz | UMLet | Vega |
| Vega-lite | WaveDrom | WireViz |   |   |   |   |
| | | | | | |
|------------|------------|------------------|--------------|-------------|----------|
| BlockDiag | BPMN | Bytefield | SeqDiag | ActDiag | NwDiag |
| PacketDiag | RackDiag | C4 with PlantUML | D2 | DBML | Ditaa |
| Erd | Excalidraw | GraphViz | KaTeX | Mermaid | MHCHEM |
| Nomnoml | Pikchr | PlantUML | SmilesDrawer | Structurizr | Svgbob |
| Symbolator | Tikz | UMLet | Vega | Vega-lite | WaveDrom |
| WireViz | | | | | |

## Cross-platform printing

Expand Down
2 changes: 1 addition & 1 deletion doc/SDK.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Here's something meatier - this is how the default renderer applies syntax-colou

After that a line numbers are added, or not, depending on settings. Finally optional word breaks are inserted to improve the breaking of long spans of code that lack natural opportunities.

```ts
```typescript
export function getBodyHtml(raw: string, languageId: string, options?:any): string {
let renderedCode = "";
try {
Expand Down
25 changes: 23 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "vscode-print",
"displayName": "Print",
"description": "Rendered Markdown, coloured code.",
"version": "0.12.5",
"version": "0.12.7",
"icon": "assets/vscode-print-128.png",
"author": {
"name": "Peter Wone",
Expand Down Expand Up @@ -263,6 +263,27 @@
},
"description": "%print.folder.include.description%"
},
"print.stylesheets.markdown": {
"type": "array",
"items": {
"type": "string"
},
"description": "%print.stylesheets%"
},
"print.stylesheets.plaintext": {
"type": "array",
"items": {
"type": "string"
},
"description": "%print.stylesheets%"
},
"print.stylesheets.sourcecode": {
"type": "array",
"items": {
"type": "string"
},
"description": "%print.stylesheets%"
},
"print.folder.exclude": {
"default": [
"{bin,obj,out}",
Expand Down Expand Up @@ -656,4 +677,4 @@
"publisherId": "a803a703-65fb-4fa9-955a-c9259bf2560d",
"isPreReleaseVersion": false
}
}
}
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"print.lineSpacing.double": "Double spaced",
"print.preview.description": "Preview the rendered document in a brower",
"print.renderMarkdown.description": "Render Markdown as HTML when printing",
"print.stylesheets": "URLs or paths for extra CSS stylesheets. For workspace relative paths, use 'workspace.resource/path/to/your.css'. Otherwise, paths should be relative to the base document ('./my.css' would be in the same folder as the document), or absolute (https://...)",
"print.logLevel.description": "Log level 'Error' will log only errors (very few entries) whereas 'info' will log errors, warnings and info. 'Debug' is good for troubleshooting. 'Silly' will log absolutely everything but the log file will grow fast.",
"print.krokiUrl.markdownDescription": "URL for Kroki diagram rendering service. To get you going this defaults to the internet service but you should [install your own](https://kroki.io/#install) as soon as possible."
}
11 changes: 10 additions & 1 deletion src/css/settings.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ body {
}

.hljs {
background-color: unset;
background-color: transparent;
font-family: FONT_FAMILY;
font-size: FONT_SIZE;
}

.line-number,
Expand All @@ -27,3 +29,10 @@ pre.plaintext {
padding-bottom: 3px;
white-space: pre-wrap;
}

.code-box {
background-color: #f8f8f8;
border-radius: 0.5em;
border: thin solid silver;
white-space: pre-wrap;
}
5 changes: 4 additions & 1 deletion src/renderers/html-renderer-markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,13 @@ export async function getBodyHtml(generatedResources: Map<string, IResourceDescr
}

export function getCssUriStrings(): Array<string> {
const userSuppliedCssUrls: string[] = vscode.workspace.getConfiguration("print.stylesheets").markdown;
const cssUriStrings = [
"bundled/default-markdown.css",
"bundled/settings.css",
"bundled/katex.css",
"bundled/colour-scheme.css",
...userSuppliedCssUrls,
"bundled/settings.css"//ensure settings are always last so they take precedence
];
return cssUriStrings;
}
Expand Down
14 changes: 8 additions & 6 deletions src/renderers/html-renderer-plaintext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { escapeHtml } from "markdown-it/lib/common/utils";
import * as vscode from 'vscode';
import { IResourceDescriptor } from "./IResourceDescriptor";

export async function getBodyHtml(generatedResources: Map<string, IResourceDescriptor>, raw: string): Promise<string> {
return `<pre class="plaintext">\n${escapeHtml(raw)}\n</pre>`;
export async function getBodyHtml(_: Map<string, IResourceDescriptor>, raw: string): Promise<string> {
return `<pre class="plaintext">\n${escapeHtml(raw)}\n</pre>`;
}

export function getCssUriStrings(uri: vscode.Uri): Array<string> {
return [
"bundled/default.css",
"bundled/settings.css",
];
const userSuppliedCssUrls: string[] = vscode.workspace.getConfiguration("print.stylesheets").plaintext;
return [
"bundled/default.css",
...userSuppliedCssUrls,
"bundled/settings.css",
];
}
178 changes: 90 additions & 88 deletions src/renderers/html-renderer-sourcecode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,114 +4,116 @@ import { logger } from '../logger';
import hljs from 'highlight.js';
const resources = new Map<string, IResourceDescriptor>();
resources.set("default.css", {
content: require("highlight.js/styles/default.css").default.toString(),
mimeType: "text/css; charset=utf-8;"
content: require("highlight.js/styles/default.css").default.toString(),
mimeType: "text/css; charset=utf-8;"
});
resources.set("line-numbers.css", {
content: require("../css/line-numbers.css").default.toString(),
mimeType: "text/css; charset=utf-8;"
content: require("../css/line-numbers.css").default.toString(),
mimeType: "text/css; charset=utf-8;"
});

export async function getBodyHtml(generatedResources: Map<string, IResourceDescriptor>, raw: string, languageId: string, options?: any): Promise<string> {
let renderedCode = "";
try {
try {
renderedCode = hljs.highlight(raw, { language: languageId }).value;
if (!renderedCode.includes('"hljs-keyword"')) {
logger.warn(`Language identifier "${languageId}" could not be honoured. Autodetecting.`);
renderedCode = hljs.highlightAuto(raw).value;
}
}
catch (err) {
logger.warn(`Language identifier "${languageId}" could not be honoured. Autodetecting.`);
renderedCode = hljs.highlightAuto(raw).value;
}
if (languageId === "css") {
renderedCode = addCssColourSwatches(renderedCode);
}
renderedCode = fixMultilineSpans(renderedCode);
const printConfig = vscode.workspace.getConfiguration("print");
const pattern = /((?:(?:\w{40}|[\])},;]))(?![^<>]*>))/g;
const replacement = "$1<wbr>";
logger.debug(`Line numbering: ${printConfig.lineNumbers} (resolves to ${options.lineNumbers})`)
if (options.lineNumbers) {
renderedCode = renderedCode
.split("\n")
.map(line => line || "&nbsp;")
.map((line, i) => `<tr><td class="line-number">${options.startLine + i}</td><td class="line-text">${line.replace(/(\w{20})/g, "$1&shy;")}</td></tr>`)
.join("\n")
.replace("\n</td>", "</td>")
;
} else {
renderedCode = renderedCode
.split("\n")
.map(line => line || "&nbsp;")
.map((line) => `<tr><td class="line-text">${line.replace(pattern, replacement)}</td></tr>`)
.join("\n")
.replace("\n</td>", "</td>")
;
}
} catch (err) {
logger.error(`Markdown could not be rendered\n${err}`);
renderedCode = "<div>Could not render this file.</end>";
}
return `<table class="hljs">\n${renderedCode}\n</table>`;
let renderedCode = "";
try {
try {
renderedCode = hljs.highlight(raw, { language: languageId }).value;
if (!renderedCode.includes('"hljs-keyword"')) {
logger.warn(`Language identifier "${languageId}" could not be honoured. Autodetecting.`);
renderedCode = hljs.highlightAuto(raw).value;
}
}
catch (err) {
logger.warn(`Language identifier "${languageId}" could not be honoured. Autodetecting.`);
renderedCode = hljs.highlightAuto(raw).value;
}
if (languageId === "css") {
renderedCode = addCssColourSwatches(renderedCode);
}
renderedCode = fixMultilineSpans(renderedCode);
const printConfig = vscode.workspace.getConfiguration("print");
const pattern = /((?:(?:\w{40}|[\])},;]))(?![^<>]*>))/g;
const replacement = "$1<wbr>";
logger.debug(`Line numbering: ${printConfig.lineNumbers} (resolves to ${options.lineNumbers})`)
if (options.lineNumbers) {
renderedCode = renderedCode
.split("\n")
.map(line => line || "&nbsp;")
.map((line, i) => `<tr><td class="line-number">${options.startLine + i}</td><td class="line-text">${line.replace(/(\w{20})/g, "$1&shy;")}</td></tr>`)
.join("\n")
.replace("\n</td>", "</td>")
;
} else {
renderedCode = renderedCode
.split("\n")
.map(line => line || "&nbsp;")
.map((line) => `<tr><td class="line-text">${line.replace(pattern, replacement)}</td></tr>`)
.join("\n")
.replace("\n</td>", "</td>")
;
}
} catch (err) {
logger.error(`Markdown could not be rendered\n${err}`);
renderedCode = "<div>Could not render this file.</end>";
}
return `<table class="hljs">\n${renderedCode}\n</table>`;
}

export function getCssUriStrings(uri: vscode.Uri): Array<string> {
return [
"bundled/default.css",
"bundled/line-numbers.css",
"bundled/colour-scheme.css",
"bundled/settings.css",
];
const userSuppliedCssUrls: string[] = vscode.workspace.getConfiguration("print.stylesheets").sourcecode;
return [
"bundled/default.css",
"bundled/line-numbers.css",
"bundled/colour-scheme.css",
...userSuppliedCssUrls,
"bundled/settings.css",
];
}

export function getResource(name: string): IResourceDescriptor {
return resources.get(name)!;
return resources.get(name)!;
}

function fixMultilineSpans(text: string): string {
let classes: string[] = [];
let classes: string[] = [];

// since this code runs on simple, well-behaved, escaped HTML, we can just
// use regex matching for the span tags and classes
// since this code runs on simple, well-behaved, escaped HTML, we can just
// use regex matching for the span tags and classes

// first capture group is if it's a closing tag, second is tag attributes
const spanRegex = /<(\/?)span(.*?)>/g;
// https://stackoverflow.com/questions/317053/regular-expression-for-extracting-tag-attributes
// matches single html attribute, first capture group is attr name and second is value
const tagAttrRegex = /(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?/g;
// first capture group is if it's a closing tag, second is tag attributes
const spanRegex = /<(\/?)span(.*?)>/g;
// https://stackoverflow.com/questions/317053/regular-expression-for-extracting-tag-attributes
// matches single html attribute, first capture group is attr name and second is value
const tagAttrRegex = /(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?/g;

return text.split("\n").map(line => {
const pre = classes.map(classVal => `<span class="${classVal}">`);
return text.split("\n").map(line => {
const pre = classes.map(classVal => `<span class="${classVal}">`);

let spanMatch;
spanRegex.lastIndex = 0; // exec maintains state which we need to reset
while ((spanMatch = spanRegex.exec(line)) !== null) {
if (spanMatch[1] !== "") {
classes.pop();
continue;
}
let attrMatch;
tagAttrRegex.lastIndex = 0;
while ((attrMatch = tagAttrRegex.exec(spanMatch[2])) !== null) {
if (attrMatch[1].toLowerCase().trim() === "class") {
classes.push(attrMatch[2]);
}
}
}
let spanMatch;
spanRegex.lastIndex = 0; // exec maintains state which we need to reset
while ((spanMatch = spanRegex.exec(line)) !== null) {
if (spanMatch[1] !== "") {
classes.pop();
continue;
}
let attrMatch;
tagAttrRegex.lastIndex = 0;
while ((attrMatch = tagAttrRegex.exec(spanMatch[2])) !== null) {
if (attrMatch[1].toLowerCase().trim() === "class") {
classes.push(attrMatch[2]);
}
}
}

return `${pre.join("")}${line}${"</span>".repeat(classes.length)}`;
}).join("\n");
return `${pre.join("")}${line}${"</span>".repeat(classes.length)}`;
}).join("\n");
}

function addCssColourSwatches(text: string): string {
return text.replace(
/(:\s*<span class="hljs-number">)([#A-Za-z][A-Za-z0-9]+)/g,
'$1<svg height="1em" width="1em"><rect width="1em" height="1em" style="fill:$2; stroke:black"/></svg> $2'
).replace(
/(<span class="hljs-attribute">.*<\/span>\s*:\s*)(aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|grey|green|greenyellow|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|navyblue|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen)(?:\s*);/gm,
'$1<svg height="1em" width="1em"><rect width="1em" height="1em" style="fill:$2; stroke:black"/></svg> $2'
);
return text.replace(
/(:\s*<span class="hljs-number">)([#A-Za-z][A-Za-z0-9]+)/g,
'$1<svg height="1em" width="1em"><rect width="1em" height="1em" style="fill:$2; stroke:black"/></svg> $2'
).replace(
/(<span class="hljs-attribute">.*<\/span>\s*:\s*)(aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|grey|green|greenyellow|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|navyblue|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen)(?:\s*);/gm,
'$1<svg height="1em" width="1em"><rect width="1em" height="1em" style="fill:$2; stroke:black"/></svg> $2'
);
}
Loading

0 comments on commit 175d6a5

Please sign in to comment.