Skip to content

Commit e4a237f

Browse files
authored
Merge pull request #89 from andrewbranch/feat/inline
Code span highlighting
2 parents cf3d33d + 87813b1 commit e4a237f

28 files changed

+748
-175
lines changed

README.md

Lines changed: 131 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ If you’re updating from v1.x.x to v2.x.x, see [MIGRATING.md](./MIGRATING.md).
2424
- [Variables](#variables)
2525
- [Tweaking or replacing theme colors](#tweaking-or-replacing-theme-colors)
2626
- [Extra stuff](#extra-stuff)
27+
- [Inline code highlighting](#inline-code-highlighting)
2728
- [Line highlighting](#line-highlighting)
2829
- [Using different themes for different code fences](#using-different-themes-for-different-code-fences)
2930
- [Arbitrary code fence options](#arbitrary-code-fence-options)
31+
- [Options reference](#options-reference)
3032
- [Contributing](#contributing)
3133

3234
## Why gatsby-remark-vscode?
@@ -48,7 +50,7 @@ Install the package:
4850
npm install --save gatsby-remark-vscode
4951
```
5052

51-
Add to your `gatsby-config.js` (all options are optional; defaults shown here):
53+
Add to your `gatsby-config.js`:
5254

5355
```js
5456
{
@@ -58,21 +60,8 @@ Add to your `gatsby-config.js` (all options are optional; defaults shown here):
5860
options: {
5961
plugins: [{
6062
resolve: `gatsby-remark-vscode`,
61-
// All options are optional. Defaults shown here.
6263
options: {
63-
theme: 'Dark+ (default dark)', // Read on for list of included themes. Also accepts object and function forms.
64-
wrapperClassName: '', // Additional class put on 'pre' tag. Also accepts function to set the class dynamically.
65-
injectStyles: true, // Injects (minimal) additional CSS for layout and scrolling
66-
extensions: [], // Third-party extensions providing additional themes and languages
67-
languageAliases: {}, // Map of custom/unknown language codes to standard/known language codes
68-
replaceColor: x => x, // Function allowing replacement of a theme color with another. Useful for replacing hex colors with CSS variables.
69-
getLineClassName: ({ // Function allowing dynamic setting of additional class names on individual lines
70-
content, // - the string content of the line
71-
index, // - the zero-based index of the line within the code fence
72-
language, // - the language specified for the code fence
73-
meta // - any options set on the code fence alongside the language (more on this later)
74-
}) => '',
75-
logLevel: 'warn' // Set to 'info' to debug if something looks wrong
64+
theme: 'Abyss' // Or install your favorite theme from GitHub
7665
}
7766
}]
7867
}
@@ -329,6 +318,41 @@ Since the CSS for token colors is auto-generated, it’s fragile and inconvenien
329318

330319
## Extra stuff
331320

321+
### Inline code highlighting
322+
323+
To highlight inline code spans, add an `inlineCode` key to the plugin options and choose a `marker` string:
324+
325+
```js
326+
{
327+
inlineCode: {
328+
marker: ''
329+
}
330+
}
331+
```
332+
333+
Then, in your Markdown, you can prefix code spans by the language name followed by the `marker` string to opt into highlighting that span:
334+
335+
```md
336+
Now you can highlight inline code: `js•Array.prototype.concat.apply([], array)`.
337+
```
338+
339+
The syntax theme defaults to the one selected for code blocks, but you can control the inline code theme independently:
340+
341+
```js
342+
{
343+
theme: 'Default Dark+',
344+
inlineCode: {
345+
marker: '',
346+
theme: {
347+
default: 'Default Light+',
348+
dark: 'Default Dark+'
349+
}
350+
}
351+
}
352+
```
353+
354+
See [`inlineCode`](#inlinecode) in the options reference for more API details.
355+
332356
### Line highlighting
333357

334358
`gatsby-remark-vscode` offers the same line-range-after-language-name strategy of highlighting or emphasizing lines as [gatsby-remark-prismjs](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-remark-prismjs):
@@ -416,6 +440,98 @@ Line numbers and ranges aren’t the only things you can pass as options on your
416440
}
417441
```
418442

443+
## Options reference
444+
445+
### `theme`
446+
447+
The syntax theme used for code blocks.
448+
449+
- **Default:** `'Default Dark+'`
450+
- **Accepted types**:
451+
- **`string`:** The name or id of a theme. (See [Built-in themes](#themes) and [Using languages and themes from an extension](#using-languages-and-themes-from-an-extension).)
452+
- **`ThemeSettings`:** An object that selects different themes to use in different contexts. (See [Multi-theme support](#multi-theme-support).)
453+
- **`(data: CodeBlockData) => string | ThemeSettings`:** A function returning the theme selection for a given code block. `CodeBlockData` is an object with properties:
454+
- **`language`:** The language of the code block, if one was specified.
455+
- **`markdownNode`:** The MarkdownRemark GraphQL node.
456+
- **`node`:** The Remark AST node of the code block.
457+
- **`parsedOptions`:** The object form of of any code fence info supplied. (See [Arbitrary code fence options](#arbitrary-code-fence-options).)
458+
459+
### `wrapperClassName`
460+
461+
A custom class name to be set on the `pre` tag.
462+
463+
- **Default:** None, but the class `grvsc-container` will always be on the tag.
464+
- **Accepted types:**
465+
- **`string`:** The class name to add.
466+
- **`(data: CodeBlockData) => string`:** A function returning the class name to add for a given code block. (See the [`theme`](#theme) option above for the details of `CodeBlockData`.)
467+
468+
### `languageAliases`
469+
470+
An object that allows additional language names to be mapped to recognized languages so they can be used on opening code fences:
471+
472+
```js
473+
{
474+
languageAliases: {
475+
fish: 'sh'
476+
}
477+
}
478+
```
479+
480+
````md
481+
Then you can use code fences like this:
482+
483+
```fish
484+
ls -la
485+
```
486+
487+
And they’ll be parsed as shell script (`sh`).
488+
````
489+
490+
- **Default:** None, but many built-in languages are already recognized by a variety of names.
491+
- **Accepted type:** `Record<string, string>`; that is, an object with string keys and string values.
492+
493+
### `extensions`
494+
495+
A list of third party extensions to search for additional langauges and themes. (See [Using languages and themes from an extension](#using-languages-and-themes-from-an-extension).)
496+
497+
- **Default:** None
498+
- **Accepted type:** `string[]`; that is, an array of strings, where the strings are the package names of the extensions.
499+
500+
### `inlineCode`
501+
502+
Enables syntax highlighting for inline code spans. (See [Inline code highlighting](#inline-code-highlighting).)
503+
504+
- **Default:** None
505+
- **Accepted type:** An object with properties:
506+
- **`theme`:** A string or `ThemeSettings` object selecting the theme, or a function returning a string or `ThemeSettings` object for a given code span. The type is the same as the one documented in the top-level [theme option](#theme). Defaults to the value of the top-level [theme option](#theme).
507+
- **`marker`:** A string used as a separator between the language name and the content of a code span. For example, with a `marker` of value `'•'`, you can highlight a code span as JavaScript by writing the Markdown code span as `` `js•Code.to.highlight("inline")` ``.
508+
- **`className`:** A string, or function returning a string for a given code span, that sets a custom class name on the wrapper `code` HTML tag. If the function form is used, it is passed an object parameter describing the code span with properties:
509+
- **`language`:** The language of the code span (the bit before the `marker` character).
510+
- **`markdownNode`:** The MarkdownRemark GraphQL node.
511+
- **`node`:** The Remark AST node of the code span.
512+
513+
### `injectStyles`
514+
515+
Whether to add supporting CSS to the end of the Markdown document. (See [Styles](#styles).)
516+
517+
- **Default:** `true`
518+
- **Accepted type:** `boolean`
519+
520+
### `replaceColor`
521+
522+
A function allowing individual color values to be replaced in the generated CSS. (See [Tweaking or replacing theme colors](#tweaking-or-replacing-theme-colors).)
523+
524+
- **Default:** None
525+
- **Accepted type:** `(colorValue: string, theme: string) => string`; that is, a function that takes the original color and the identifier of the theme it came from and returns a new color value.
526+
527+
### `logLevel`
528+
529+
The verbosity of logging. Useful for diagnosing unexpected behavior.
530+
531+
- **Default**: `'warn'`
532+
- **Accepted values:** From most verbose to least verbose, `'trace'`, `'debug'`, `'info'`, `'warn'`, or `'error'`.
533+
534+
419535
## Contributing
420536

421537
Please note that this project is released with a Contributor [Code of Conduct](./CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.

gatsby-node.js

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,45 +13,15 @@ exports.createResolvers = ({
1313
grvscCodeBlocks: {
1414
type: ['GRVSCCodeBlock'],
1515
resolve(source, _, context) {
16-
return getFromCache();
17-
18-
/** @param {boolean=} stop */
19-
async function getFromCache(stop) {
20-
const childNodes = await getChildNodes(cache, source.id, source.internal.contentDigest);
21-
// Hack alert: ensure plugin has been run by querying htmlAst,
22-
// which is set via `setFieldsOnGraphQLNodeType` by gatsby-transformer-remark,
23-
// therefore might not have been run before this resolver runs.
24-
if (!childNodes && !stop) {
25-
await context.nodeModel.runQuery({
26-
query: {
27-
filter: {
28-
id: { eq: source.id },
29-
htmlAst: { ne: null },
30-
},
31-
},
32-
type: 'MarkdownRemark',
33-
firstOnly: true,
34-
});
35-
return getFromCache(true);
36-
}
37-
if (!childNodes) {
38-
logger.error(
39-
'gatsby-remark-vscode couldn’t retrieve up-to-date GRVSCCodeBlock GraphQL nodes. ' +
40-
'The `GRVSCCodeBlocks` field may be missing, empty or stale. ' +
41-
'The Gatsby cache is probably in a weird state. Try running `gatsby clean`, and file an ' +
42-
'issue at https://github.com/andrewbranch/gatsby-remark-vscode/issues/new if the problem persists.'
43-
);
44-
45-
return context.nodeModel.runQuery({
46-
query: { parent: { id: { eq: source.id } } },
47-
type: 'GRVSCCodeBlock',
48-
firstOnly: false
49-
});
50-
}
51-
return childNodes || [];
52-
}
53-
},
16+
return getFromCache('GRVSCCodeBlock', cache, source, context);
17+
}
5418
},
19+
grvscCodeSpans: {
20+
type: ['GRVSCCodeSpan'],
21+
resolve(source, _, context) {
22+
return getFromCache('GRVSCCodeSpan', cache, source, context);
23+
}
24+
}
5525
},
5626

5727
Query: {
@@ -83,3 +53,45 @@ exports.createResolvers = ({
8353
}
8454
});
8555
};
56+
57+
/**
58+
* @param {string} type
59+
* @param {any} cache
60+
* @param {any} source
61+
* @param {any} context
62+
* @param {boolean=} stop
63+
*/
64+
async function getFromCache(type, cache, source, context, stop) {
65+
const childNodes = await getChildNodes(cache, source.id, source.internal.contentDigest);
66+
// Hack alert: ensure plugin has been run by querying htmlAst,
67+
// which is set via `setFieldsOnGraphQLNodeType` by gatsby-transformer-remark,
68+
// therefore might not have been run before this resolver runs.
69+
if (!childNodes && !stop) {
70+
await context.nodeModel.runQuery({
71+
query: {
72+
filter: {
73+
id: { eq: source.id },
74+
htmlAst: { ne: null },
75+
},
76+
},
77+
type: 'MarkdownRemark',
78+
firstOnly: true,
79+
});
80+
return getFromCache(cache, source, context, true);
81+
}
82+
if (!childNodes) {
83+
logger.error(
84+
'gatsby-remark-vscode couldn’t retrieve up-to-date GRVSCCodeBlock GraphQL nodes. ' +
85+
'The `GRVSCCodeBlocks` field may be missing, empty or stale. ' +
86+
'The Gatsby cache is probably in a weird state. Try running `gatsby clean`, and file an ' +
87+
'issue at https://github.com/andrewbranch/gatsby-remark-vscode/issues/new if the problem persists.'
88+
);
89+
90+
return context.nodeModel.runQuery({
91+
query: { parent: { id: { eq: source.id } } },
92+
type,
93+
firstOnly: false
94+
});
95+
}
96+
return childNodes || [];
97+
}

src/createCodeBlockRegistry.js renamed to src/createCodeNodeRegistry.js

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ const { getTokenDataFromMetadata } = require('../lib/vscode/modes');
55
const { declaration } = require('./renderers/css');
66

77
/**
8-
* @template TKey
9-
* @param {CodeBlockRegistryOptions=} options
10-
* @returns {CodeBlockRegistry<TKey>}
8+
* @template {Keyable} TKey
9+
* @param {CodeNodeRegistryOptions=} options
10+
* @returns {CodeNodeRegistry<TKey>}
1111
*/
12-
function createCodeBlockRegistry({ prefixAllClassNames } = {}) {
13-
/** @type {Map<TKey, RegisteredCodeBlockData & { index: number }>} */
14-
const nodeMap = new Map();
12+
function createCodeNodeRegistry({ prefixAllClassNames } = {}) {
13+
/** @type {Map<TKey, RegisteredCodeNodeData>} */
14+
const blockMap = new Map();
15+
/** @type {Map<TKey, RegisteredCodeNodeData>} */
16+
const spanMap = new Map();
1517
/** @type {ConditionalTheme[]} */
1618
let themes = [];
1719
/** @type {Map<string, { colorMap: string[], settings: Record<string, string> }>} */
@@ -23,16 +25,18 @@ function createCodeBlockRegistry({ prefixAllClassNames } = {}) {
2325

2426
return {
2527
register: (key, data) => {
26-
nodeMap.set(key, { ...data, index: nodeMap.size });
28+
const map = key.type === 'code' ? blockMap : spanMap;
29+
map.set(key, { ...data, index: map.size });
2730
themes = concatConditionalThemes(themes, data.possibleThemes);
2831
data.tokenizationResults.forEach(({ theme, colorMap, settings }) =>
2932
themeColors.set(theme.identifier, { colorMap, settings })
3033
);
3134
},
32-
forEachLine: (node, action) => nodeMap.get(node).lines.forEach(action),
35+
forEachLine: (node, action) => blockMap.get(node).lines.forEach(action),
3336
forEachToken: (node, lineIndex, tokenAction) => {
3437
generateClassNames();
35-
const { tokenizationResults, isTokenized, lines } = nodeMap.get(node);
38+
const map = node.type === 'code' ? blockMap : spanMap;
39+
const { tokenizationResults, isTokenized, lines } = map.get(node);
3640
if (!isTokenized) {
3741
return;
3842
}
@@ -78,10 +82,11 @@ function createCodeBlockRegistry({ prefixAllClassNames } = {}) {
7882
});
7983
});
8084
},
81-
forEachCodeBlock: nodeMap.forEach.bind(nodeMap),
85+
forEachCodeBlock: blockMap.forEach.bind(blockMap),
86+
forEachCodeSpan: spanMap.forEach.bind(spanMap),
8287
getAllPossibleThemes: () => themes.map(theme => ({ theme, settings: themeColors.get(theme.identifier).settings })),
8388
getTokenStylesForTheme: themeIdentifier => {
84-
/** @type {ReturnType<CodeBlockRegistry['getTokenStylesForTheme']>} */
89+
/** @type {ReturnType<CodeNodeRegistry['getTokenStylesForTheme']>} */
8590
const result = [];
8691
const colors = themeColors.get(themeIdentifier);
8792
const classNameMap = themeTokenClassNameMap && themeTokenClassNameMap.get(themeIdentifier);
@@ -113,7 +118,14 @@ function createCodeBlockRegistry({ prefixAllClassNames } = {}) {
113118
if (themeTokenClassNameMap) return;
114119
themeTokenClassNameMap = new Map();
115120
zippedLines = new Map();
116-
nodeMap.forEach(({ lines, tokenizationResults, isTokenized }, node) => {
121+
blockMap.forEach(generate);
122+
spanMap.forEach(generate);
123+
124+
/**
125+
* @param {RegisteredCodeNodeData} data
126+
* @param {TKey} node
127+
*/
128+
function generate({ lines, tokenizationResults, isTokenized }, node) {
117129
if (!isTokenized) return;
118130
/** @type {Token[][][]} */
119131
const zippedLinesForNode = [];
@@ -141,7 +153,7 @@ function createCodeBlockRegistry({ prefixAllClassNames } = {}) {
141153
});
142154
});
143155
});
144-
});
156+
}
145157
}
146158
}
147159

@@ -202,4 +214,4 @@ function getColorFromColorMap(colorMap, canonicalClassName) {
202214
return colorMap[index];
203215
}
204216

205-
module.exports = createCodeBlockRegistry;
217+
module.exports = createCodeNodeRegistry;

0 commit comments

Comments
 (0)