├── .npmignore
├── .flowconfig
├── .gitignore
├── prism
└── package.json
├── demo
├── .gitignore
├── src
│ ├── styles.js
│ └── index.js
├── package.json
└── public
│ └── index.html
├── src
├── index.js
├── defaultProps.js
├── utils
│ ├── themeToDict.js
│ ├── __tests__
│ │ ├── themeToDict.test.js
│ │ └── normalizeTokens.test.js
│ └── normalizeTokens.js
├── vendor
│ └── prism
│ │ ├── includeLangs.js
│ │ ├── index.js
│ │ └── prism-core.js
├── types.js
└── components
│ ├── Highlight.js
│ └── __tests__
│ ├── Highlight.test.js
│ └── __snapshots__
│ └── Highlight.test.js.snap
├── tools
└── themeFromVsCode
│ ├── package.json
│ ├── README.md
│ ├── src
│ ├── template.js
│ ├── transformSettings.js
│ ├── minify.js
│ ├── index.js
│ ├── scopeScore.js
│ ├── collectStyles.js
│ └── scopeMapper.js
│ └── yarn.lock
├── themes
├── ultramin.js
├── dracula.js
├── vsDark.js
├── vsDarkPlus.js
├── shadesOfPurple.js
├── duotoneDark.js
├── nightOwl.js
├── duotoneLight.js
└── oceanicNext.js
├── LICENSE
├── package.json
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | .vscode
4 | *.log
5 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 |
3 | [include]
4 |
5 | [libs]
6 |
7 | [lints]
8 |
9 | [options]
10 |
11 | [strict]
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .DS_Store
4 | bundle-stats.html
5 | .vscode
6 | __diff_output__
7 | lib/
8 | es/
9 | sandbox/node_modules
10 | *.log
11 | test-results.json
12 |
--------------------------------------------------------------------------------
/prism/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "prism-react-renderer/prism",
3 | "private": true,
4 | "main": "../lib/vendor/prism/index.js",
5 | "module": "../es/vendor/prism/index.js"
6 | }
7 |
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .DS_Store
4 | bundle-stats.html
5 | .vscode
6 | __diff_output__
7 | lib/
8 | es/
9 | sandbox/node_modules
10 | *.log
11 | test-results.json
12 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import Prism from './vendor/prism'
4 | import defaultProps from './defaultProps'
5 | import Highlight from './components/Highlight'
6 |
7 | export {
8 | Prism,
9 | defaultProps
10 | }
11 |
12 | export default Highlight
13 |
--------------------------------------------------------------------------------
/src/defaultProps.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import Prism from './vendor/prism'
4 | import theme from '../themes/duotoneDark'
5 |
6 | import type { PrismLib } from './types'
7 |
8 | const defaultProps = {
9 | // $FlowFixMe
10 | Prism: (Prism: PrismLib),
11 | theme
12 | };
13 |
14 | export default defaultProps;
15 |
--------------------------------------------------------------------------------
/tools/themeFromVsCode/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@prism-react-renderer/theme-from-vscode",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "private": true,
7 | "dependencies": {
8 | "color": "^3.0.0",
9 | "is-equal": "^1.5.5"
10 | },
11 | "scripts": {
12 | "start": "node ./src/index.js"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tools/themeFromVsCode/README.md:
--------------------------------------------------------------------------------
1 | # Generate a Prism Theme from VSCode `.json` Themes
2 |
3 | 1. Open this directory and run `npm install`
4 | 2. Save your VSCode theme in a file called `theme.json` (inside root same as the `README.md` file)
5 | 3. Run `npm start` and your theme will be created in a file called `outputTheme.js` (inside root same as the `README.md` file)
6 |
--------------------------------------------------------------------------------
/tools/themeFromVsCode/src/template.js:
--------------------------------------------------------------------------------
1 | const makeOutput = styles => `
2 | // @flow
3 | // Converted automatically using ./tools/themeFromVsCode
4 |
5 | /*:: import type { PrismTheme } from '../src/types' */
6 |
7 | var theme /*: PrismTheme */ = ${JSON.stringify(styles, null, 2)};
8 |
9 | module.exports = theme;
10 | `.trim() + '\n';
11 |
12 | module.exports = { makeOutput };
13 |
--------------------------------------------------------------------------------
/demo/src/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const Wrapper = styled.div`
4 | font-family: sans-serif;
5 | text-align: center;
6 | `
7 |
8 | export const Pre = styled.pre`
9 | text-align: left;
10 | margin: 1em 0;
11 | padding: 0.5em;
12 | line-height: 1.3;
13 | `
14 |
15 | export const LineNo = styled.span`
16 | display: inline-block;
17 | width: 2em;
18 | user-select: none;
19 | opacity: 0.3;
20 | `
21 |
--------------------------------------------------------------------------------
/tools/themeFromVsCode/src/transformSettings.js:
--------------------------------------------------------------------------------
1 | const color = require('color');
2 |
3 | const transformSettings = settings => {
4 | const output = {};
5 |
6 | if (settings.foreground) {
7 | output.color = color(settings.foreground).string();
8 | }
9 |
10 | if (settings.background) {
11 | output.backgroundColor = color(settings.background).string();
12 | }
13 |
14 | if (settings.fontStyle === 'italic') {
15 | output.fontStyle = 'italic';
16 | }
17 |
18 | return output;
19 | };
20 |
21 | module.exports = { transformSettings };
22 |
--------------------------------------------------------------------------------
/tools/themeFromVsCode/src/minify.js:
--------------------------------------------------------------------------------
1 | const isEqual = require('is-equal');
2 |
3 | const minify = styles => {
4 | const output = [];
5 |
6 | styles.forEach(style => {
7 | const item = output.find(x => {
8 | return isEqual(style.settings, x.style);
9 | });
10 |
11 | if (!item) {
12 | output.push({
13 | types: [style.scope],
14 | style: style.settings
15 | });
16 | } else {
17 | item.types.push(style.scope);
18 | }
19 | });
20 |
21 | return output;
22 | };
23 |
24 | module.exports = { minify };
25 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "prism-react-renderer-example",
3 | "version": "1.0.0",
4 | "description": "",
5 | "keywords": [],
6 | "main": "src/index.js",
7 | "dependencies": {
8 | "prism-react-renderer": "file:../",
9 | "react": "16.3.2",
10 | "react-dom": "16.3.2",
11 | "react-scripts": "1.1.4",
12 | "styled-components": "latest"
13 | },
14 | "devDependencies": {},
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test --env=jsdom",
19 | "eject": "react-scripts eject"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tools/themeFromVsCode/src/index.js:
--------------------------------------------------------------------------------
1 | const { writeFileSync } = require('fs');
2 | const { collectAllSettings } = require('./collectStyles');
3 | const { makeOutput } = require('./template');
4 |
5 | // Input
6 | const theme = require('../theme.json');
7 | const prismTheme = collectAllSettings(theme.tokenColors);
8 |
9 | const json = {
10 | plain: {
11 | color: theme.colors['editor.foreground'],
12 | backgroundColor: theme.colors['editor.background']
13 | },
14 | ...prismTheme
15 | };
16 |
17 | const output = makeOutput(json);
18 |
19 | writeFileSync('./outputTheme.js', output);
20 |
--------------------------------------------------------------------------------
/themes/ultramin.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | // Original: https://github.com/damienstanton/ultramin
3 | // Converted automatically using ./tools/themeFromVsCode
4 |
5 | /*:: import type { PrismTheme } from '../src/types' */
6 |
7 | var theme /*: PrismTheme */ = {
8 | plain: {
9 | color: "#282a2e",
10 | backgroundColor: "#ffffff"
11 | },
12 | styles: [
13 | {
14 | types: ["comment"],
15 | style: {
16 | color: "rgb(197, 200, 198)"
17 | }
18 | },
19 | {
20 | types: ["string", "number", "builtin", "variable"],
21 | style: {
22 | color: "rgb(150, 152, 150)"
23 | }
24 | },
25 | {
26 | types: ["class-name", "function", "tag", "attr-name"],
27 | style: {
28 | color: "rgb(40, 42, 46)"
29 | }
30 | }
31 | ]
32 | };
33 |
34 | module.exports = theme;
35 |
--------------------------------------------------------------------------------
/src/utils/themeToDict.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Language, StyleObj, PrismTheme, PrismThemeEntry } from "../types";
4 |
5 | export type ThemeDict = {
6 | root: StyleObj,
7 | plain: StyleObj,
8 | [type: string]: StyleObj
9 | };
10 |
11 | const themeToDict = (theme: PrismTheme, language: Language): ThemeDict => {
12 | const { plain } = theme;
13 |
14 | const themeDict = theme.styles.reduce((acc, themeEntry) => {
15 | const { types, languages, style } = themeEntry;
16 | if (languages && !languages.includes(language)) {
17 | return acc;
18 | }
19 |
20 | themeEntry.types.forEach(type => {
21 | // $FlowFixMe
22 | const accStyle: StyleObj = { ...acc[type], ...style };
23 |
24 | acc[type] = accStyle;
25 | });
26 |
27 | return acc;
28 | }, Object.create(null));
29 |
30 | // $FlowFixMe
31 | themeDict.root = (plain: StyleObj);
32 | // $FlowFixMe
33 | themeDict.plain = ({ ...plain, backgroundColor: null }: StyleObj);
34 |
35 | return themeDict;
36 | };
37 |
38 | export default themeToDict;
39 |
--------------------------------------------------------------------------------
/src/vendor/prism/includeLangs.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | // These are the languages that'll be included in the generated
4 | // prism/index.js file
5 |
6 | module.exports = {
7 | "markup": true,
8 | "bash": true,
9 | "clike": true,
10 | "c": true,
11 | "cpp": true,
12 | "css": true,
13 | "javascript": true,
14 | "jsx": true,
15 | "coffeescript": true,
16 | "actionscript": true,
17 | "css-extras": true,
18 | "diff": true,
19 | "docker": true,
20 | "elixir": true,
21 | "erlang": true,
22 | "git": true,
23 | "go": true,
24 | "graphql": true,
25 | "handlebars": true,
26 | "haskell": true,
27 | "java": true,
28 | "json": true,
29 | "latex": true,
30 | "less": true,
31 | "makefile": true,
32 | "markdown": true,
33 | "objectivec": true,
34 | "ocaml": true,
35 | "php": true,
36 | "php-extras": true,
37 | "python": true,
38 | "reason": true,
39 | "ruby": true,
40 | "rust": true,
41 | "sass": true,
42 | "scss": true,
43 | "sql": true,
44 | "stylus": true,
45 | "swift": true,
46 | "typescript": true,
47 | "vim": true,
48 | "yaml": true
49 | }
50 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Formidable
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/demo/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import { Wrapper, Pre, LineNo } from './styles'
4 |
5 | import Highlight, { defaultProps } from 'prism-react-renderer'
6 | import theme from 'prism-react-renderer/themes/oceanicNext'
7 |
8 | const exampleCode = `
9 | (function someDemo() {
10 | var test = "Hello World!";
11 | console.log(test);
12 | })();
13 |
14 | return () => ;
15 | `.trim()
16 |
17 | const App = () => (
18 |
19 | Welcome to prism-react-renderer!
20 |
21 |
22 | {({ className, style, tokens, getLineProps, getTokenProps }) => (
23 |
24 | {tokens.map((line, i) => (
25 |
26 | {i + 1}
27 | {line.map((token, key) => )}
28 |
29 | ))}
30 |
31 | )}
32 |
33 |
34 | )
35 |
36 | render( , document.getElementById('root'))
37 |
--------------------------------------------------------------------------------
/tools/themeFromVsCode/src/scopeScore.js:
--------------------------------------------------------------------------------
1 | const scorePerScope = {
2 | builtin: [
3 | 'variable.other.constant',
4 | 'constant.language'
5 | ],
6 | punctuation: [
7 | 'punctuation.accessor'
8 | ],
9 | tag: [
10 | 'meta.tag.html',
11 | 'entity.name.tag'
12 | ]
13 | }
14 |
15 | // The higher the better
16 | const score = [
17 | // These are sure matches
18 | 'markup', // diffs
19 | 'comment',
20 | 'punctuation',
21 | 'string',
22 | 'variable',
23 |
24 | // These are more "meta" scopes
25 | 'meta', // good guesses
26 | 'entity',
27 | 'constant',
28 | 'support',
29 | 'variable'
30 | ];
31 |
32 | const baseScoreSize = score.length
33 |
34 | const getScoreForScope = (scope, mappedScope) => {
35 | // Get scores for specific mapped scopes first
36 | const scoreForMapped = scorePerScope[mappedScope];
37 | if (scoreForMapped) {
38 | // If the scope is in the specific mapped scope we add the baseScoreSize to this
39 | // score
40 | const mappedIndex = scoreForMapped.findIndex(x => scope.startsWith(x));
41 | if (mappedIndex !== -1) {
42 | return baseScoreSize + (scoreForMapped.length - mappedIndex);
43 | }
44 | }
45 |
46 | const parentScope = scope.split('.')[0];
47 | const index = score.indexOf(parentScope);
48 |
49 | if (index === -1) {
50 | // Otherwise it's a negative score based on length
51 | return -1 * scope.length;
52 | }
53 |
54 | // If it's found we return the score from the main `score` arr
55 | return baseScoreSize - index;
56 | };
57 |
58 | module.exports = { getScoreForScope };
59 |
--------------------------------------------------------------------------------
/themes/dracula.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | // Original: https://github.com/dracula/visual-studio-code
3 | // Converted automatically using ./tools/themeFromVsCode
4 |
5 | /*:: import type { PrismTheme } from '../src/types' */
6 |
7 | var theme /*: PrismTheme */ = {
8 | plain: {
9 | color: "#F8F8F2",
10 | backgroundColor: "#282A36"
11 | },
12 | styles: [
13 | {
14 | types: ["prolog", "constant", "builtin"],
15 | style: {
16 | color: "rgb(189, 147, 249)"
17 | }
18 | },
19 | {
20 | types: ["inserted", "function"],
21 | style: {
22 | color: "rgb(80, 250, 123)"
23 | }
24 | },
25 | {
26 | types: ["deleted"],
27 | style: {
28 | color: "rgb(255, 85, 85)"
29 | }
30 | },
31 | {
32 | types: ["changed"],
33 | style: {
34 | color: "rgb(255, 184, 108)"
35 | }
36 | },
37 | {
38 | types: ["punctuation", "symbol"],
39 | style: {
40 | color: "rgb(248, 248, 242)"
41 | }
42 | },
43 | {
44 | types: ["string", "char", "tag", "selector"],
45 | style: {
46 | color: "rgb(255, 121, 198)"
47 | }
48 | },
49 | {
50 | types: ["keyword", "variable"],
51 | style: {
52 | color: "rgb(189, 147, 249)",
53 | fontStyle: "italic"
54 | }
55 | },
56 | {
57 | types: ["comment"],
58 | style: {
59 | color: "rgb(98, 114, 164)"
60 | }
61 | },
62 | {
63 | types: ["attr-name"],
64 | style: {
65 | color: "rgb(241, 250, 140)"
66 | }
67 | }
68 | ]
69 | };
70 |
71 | module.exports = theme;
72 |
--------------------------------------------------------------------------------
/themes/vsDark.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | // Converted automatically using ./tools/themeFromVsCode
3 |
4 | /*:: import type { PrismTheme } from '../src/types' */
5 |
6 | var theme /*: PrismTheme */ = {
7 | plain: {
8 | color: "#D4D4D4",
9 | backgroundColor: "#1E1E1E"
10 | },
11 | styles: [
12 | {
13 | types: ["prolog"],
14 | style: {
15 | color: "rgb(0, 0, 128)"
16 | }
17 | },
18 | {
19 | types: ["comment"],
20 | style: {
21 | color: "rgb(106, 153, 85)"
22 | }
23 | },
24 | {
25 | types: ["builtin", "tag", "changed", "keyword"],
26 | style: {
27 | color: "rgb(86, 156, 214)"
28 | }
29 | },
30 | {
31 | types: ["number", "inserted"],
32 | style: {
33 | color: "rgb(181, 206, 168)"
34 | }
35 | },
36 | {
37 | types: ["constant"],
38 | style: {
39 | color: "rgb(100, 102, 149)"
40 | }
41 | },
42 | {
43 | types: ["attr-name", "variable"],
44 | style: {
45 | color: "rgb(156, 220, 254)"
46 | }
47 | },
48 | {
49 | types: ["deleted", "string"],
50 | style: {
51 | color: "rgb(206, 145, 120)"
52 | }
53 | },
54 | {
55 | types: ["selector"],
56 | style: {
57 | color: "rgb(215, 186, 125)"
58 | }
59 | },
60 | {
61 | types: ["punctuation"],
62 | style: {
63 | color: "rgb(128, 128, 128)"
64 | }
65 | },
66 | {
67 | types: ["operator"],
68 | style: {
69 | color: "rgb(212, 212, 212)"
70 | }
71 | }
72 | ]
73 | };
74 |
75 | module.exports = theme;
76 |
--------------------------------------------------------------------------------
/demo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
23 | React App
24 |
25 |
26 |
27 |
28 | You need to enable JavaScript to run this app.
29 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/utils/__tests__/themeToDict.test.js:
--------------------------------------------------------------------------------
1 | import themeToDict from "../themeToDict"
2 |
3 | describe("themeToDict", () => {
4 | it("converts entry.types to dictionary", () => {
5 | const input = {
6 | plain: { color: "red" },
7 | styles: [
8 | {
9 | types: ["1", "2"],
10 | style: {
11 | color: "green",
12 | },
13 | },
14 | {
15 | types: ["3"],
16 | style: {
17 | color: "blue",
18 | },
19 | },
20 | {
21 | types: ["2"],
22 | style: {
23 | color: "orange",
24 | },
25 | },
26 | ],
27 | }
28 |
29 | const expected = {
30 | root: {
31 | color: "red",
32 | },
33 | plain: {
34 | color: "red",
35 | backgroundColor: null,
36 | },
37 | 1: {
38 | color: "green",
39 | },
40 | 2: {
41 | color: "orange",
42 | },
43 | 3: {
44 | color: "blue",
45 | },
46 | }
47 |
48 | expect(themeToDict(input)).toEqual(expected)
49 | // Check order in which keys were added to implicitly test merge strategy
50 | expect(Object.keys(themeToDict(input, 'js'))).toEqual(Object.keys(expected))
51 | })
52 |
53 | it("limits entries by entry.languages", () => {
54 | const input = {
55 | plain: {},
56 | styles: [
57 | {
58 | types: ['test'],
59 | languages: ['js'],
60 | style: {
61 | color: "green",
62 | },
63 | }
64 | ],
65 | }
66 |
67 | expect(themeToDict(input, 'js').test).toEqual({
68 | color: 'green'
69 | })
70 |
71 | expect(themeToDict(input, 'ocaml').test).toEqual(undefined)
72 | })
73 | })
74 |
--------------------------------------------------------------------------------
/themes/vsDarkPlus.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | // Converted automatically using ./tools/themeFromVsCode
3 |
4 | /*:: import type { PrismTheme } from '../src/types' */
5 |
6 | var theme /*: PrismTheme */ = {
7 | plain: {
8 | color: "#D4D4D4",
9 | backgroundColor: "#1E1E1E"
10 | },
11 | styles: [
12 | {
13 | types: ["prolog"],
14 | style: {
15 | color: "rgb(0, 0, 128)"
16 | }
17 | },
18 | {
19 | types: ["comment"],
20 | style: {
21 | color: "rgb(106, 153, 85)"
22 | }
23 | },
24 | {
25 | types: ["builtin", "tag", "changed", "punctuation", "keyword"],
26 | style: {
27 | color: "rgb(86, 156, 214)"
28 | }
29 | },
30 | {
31 | types: ["number", "inserted"],
32 | style: {
33 | color: "rgb(181, 206, 168)"
34 | }
35 | },
36 | {
37 | types: ["constant"],
38 | style: {
39 | color: "rgb(100, 102, 149)"
40 | }
41 | },
42 | {
43 | types: ["attr-name", "variable"],
44 | style: {
45 | color: "rgb(156, 220, 254)"
46 | }
47 | },
48 | {
49 | types: ["deleted", "string"],
50 | style: {
51 | color: "rgb(206, 145, 120)"
52 | }
53 | },
54 | {
55 | types: ["selector"],
56 | style: {
57 | color: "rgb(215, 186, 125)"
58 | }
59 | },
60 | {
61 | types: ["operator"],
62 | style: {
63 | color: "rgb(212, 212, 212)"
64 | }
65 | },
66 | {
67 | types: ["function"],
68 | style: {
69 | color: "rgb(220, 220, 170)"
70 | }
71 | },
72 | {
73 | types: ["class-name"],
74 | style: {
75 | color: "rgb(78, 201, 176)"
76 | }
77 | },
78 | {
79 | types: ["char"],
80 | style: {
81 | color: "rgb(209, 105, 105)"
82 | }
83 | }
84 | ]
85 | };
86 |
87 | module.exports = theme;
88 |
--------------------------------------------------------------------------------
/tools/themeFromVsCode/src/collectStyles.js:
--------------------------------------------------------------------------------
1 | const { mapScope } = require('./scopeMapper');
2 | const { getScoreForScope } = require('./scopeScore');
3 | const { transformSettings } = require('./transformSettings');
4 | const { minify } = require('./minify');
5 |
6 | const collectAllSettings = tokenColors => {
7 | const output = {}
8 |
9 | tokenColors.forEach(({ name, scope, settings }) => {
10 | // We only care about colouring here
11 | if (!settings.foreground && !settings.fontStyle) {
12 | return;
13 | }
14 |
15 | const normScope = typeof scope === 'string' ? [scope] : scope;
16 | // Return when no input scopes are present
17 | if (!normScope || !normScope.length) {
18 | return;
19 | }
20 |
21 | normScope.forEach(scopeName => {
22 | const mappedScope = mapScope(scopeName);
23 | // Return when no mapping scope has been returned
24 | if (!mappedScope) {
25 | return;
26 | }
27 |
28 | if (output[mappedScope] === undefined) {
29 | output[mappedScope] = [];
30 | }
31 |
32 | output[mappedScope].push({
33 | scope: scopeName,
34 | settings
35 | });
36 | });
37 | });
38 |
39 | const styles = Object.keys(output).map(mappedScope => {
40 | const matchesArr = output[mappedScope];
41 |
42 | // Get score for each match
43 | const scored = matchesArr.map(match => {
44 | const score = getScoreForScope(match.scope, mappedScope);
45 |
46 | return {
47 | score,
48 | scope: mappedScope,
49 | settings: transformSettings(match.settings)
50 | };
51 | });
52 |
53 | // Sort by score asc
54 | const sorted = scored.sort((a, b) => b.score - a.score);
55 |
56 | // Return highest-scored one
57 | return sorted[0];
58 | });
59 |
60 | const themeStyles = minify(styles);
61 |
62 | return {
63 | styles: themeStyles
64 | };
65 | };
66 |
67 | module.exports = { collectAllSettings };
68 |
--------------------------------------------------------------------------------
/tools/themeFromVsCode/src/scopeMapper.js:
--------------------------------------------------------------------------------
1 | const scopeMap = {
2 | comment: "comment",
3 | punctuation: "punctuation",
4 | string: "string",
5 | variable: "variable",
6 | constant: "constant",
7 | header: "prolog",
8 | "support.function.magic": "constant",
9 | "support.variable": "constant",
10 | "entity.name.type.namespace": "namespace",
11 | "keyword.operator": "operator",
12 | "constant.numeric": "number",
13 | "constant.character.numeric": "number",
14 | "support.type.vendor.property-name": "property",
15 | "support.type.property-name": "property",
16 | "meta.property-list": "property",
17 | "entity.name.tag": "tag",
18 | "entity.name.function": "function",
19 | "entity.name.class": "class-name",
20 | "entity.name.tag.doctype": "doctype",
21 | "meta.selector": "selector",
22 | "entity.other.attribute-name": "attr-name",
23 | "meta.attribute-selector": "attr-name",
24 | "constant.other": "constant",
25 | "constant.other.symbol": "symbol",
26 | "constant.language.boolean": "boolean",
27 | "constant.character": "char",
28 | "entity.name.tag": "tag",
29 | "meta.tag.html": "tag",
30 | "meta.tag.js": "tag",
31 | "meta.selector": "selector",
32 | "support.function": "builtin",
33 | "support.type.property-name": "builtin",
34 | "variable.other.constant": "builtin",
35 | "constant.language": "builtin",
36 | "keyword.control": "keyword",
37 | "keyword.other": "keyword",
38 | "variable.parameter.url": "url",
39 | "meta.at-rule": "at-rule",
40 | "source.css.scss": "at-rule",
41 | "markup.inserted": "inserted",
42 | "markup.deleted": "deleted",
43 | "markup.changed": "changed",
44 | }
45 |
46 | const mapScope = scope => {
47 | // If the scope includes a whitespace, it's a specific
48 | // type that we don't support
49 | if (scope.includes(" ")) {
50 | return undefined
51 | }
52 |
53 | const scopeAccess = scope.split(".")
54 |
55 | for (let i = scopeAccess.length; i >= 0; i--) {
56 | const searchScope = scopeAccess.slice(0, i).join(".")
57 | const outputScope = scopeMap[searchScope]
58 | if (outputScope !== undefined) {
59 | return outputScope
60 | }
61 | }
62 |
63 | return undefined
64 | }
65 |
66 | module.exports = { mapScope }
67 |
--------------------------------------------------------------------------------
/themes/shadesOfPurple.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | // Shades of Purple
3 | // Author: Ahmad Awais https://twitter.com/MrAhmadAwais
4 | // Original: https://github.com/ahmadawais/shades-of-purple-vscode/
5 | // Converted automatically using ./tools/themeFromVsCode and then customized manually.
6 |
7 | /*:: import type { PrismTheme } from '../src/types' */
8 |
9 | var theme /*: PrismTheme */ = {
10 | plain: {
11 | color: "#9EFEFF",
12 | backgroundColor: "#2D2A55"
13 | },
14 | styles: [
15 | {
16 | types: ["changed"],
17 | style: {
18 | color: "rgb(255, 238, 128)"
19 | }
20 | },
21 | {
22 | types: ["deleted"],
23 | style: {
24 | color: "rgba(239, 83, 80, 0.56)"
25 | }
26 | },
27 | {
28 | types: ["inserted"],
29 | style: {
30 | color: "rgb(173, 219, 103)"
31 | }
32 | },
33 | {
34 | types: ["comment"],
35 | style: {
36 | color: "rgb(179, 98, 255)",
37 | fontStyle: "italic"
38 | }
39 | },
40 | {
41 | types: ["punctuation"],
42 | style: {
43 | color: "rgb(255, 255, 255)"
44 | }
45 | },
46 | {
47 | types: ["constant"],
48 | style: {
49 | color: "rgb(255, 98, 140)"
50 | }
51 | },
52 | {
53 | types: ["string", "url"],
54 | style: {
55 | color: "rgb(165, 255, 144)"
56 | }
57 | },
58 | {
59 | types: ["variable"],
60 | style: {
61 | color: "rgb(255, 238, 128)"
62 | }
63 | },
64 | {
65 | types: ["number", "boolean"],
66 | style: {
67 | color: "rgb(255, 98, 140)"
68 | }
69 | },
70 | {
71 | types: ["attr-name"],
72 | style: {
73 | color: "rgb(255, 180, 84)"
74 | }
75 | },
76 | {
77 | types: [
78 | "keyword",
79 | "operator",
80 | "property",
81 | "namespace",
82 | "tag",
83 | "selector",
84 | "doctype"
85 | ],
86 | style: {
87 | color: "rgb(255, 157, 0)"
88 | }
89 | },
90 | {
91 | types: ["builtin", "char", "constant", "function", "class-name"],
92 | style: {
93 | color: "rgb(250, 208, 0)"
94 | }
95 | }
96 | ]
97 | };
98 |
99 | module.exports = theme;
100 |
--------------------------------------------------------------------------------
/themes/duotoneDark.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | // Duotone Dark
3 | // Author: Simurai, adapted from DuoTone themes for Atom (http://simurai.com/projects/2016/01/01/duotone-themes)
4 | // Conversion: Bram de Haan (http://atelierbram.github.io/Base2Tone-prism/output/prism/prism-base2tone-evening-dark.css)
5 | // Generated with Base16 Builder (https://github.com/base16-builder/base16-builder)
6 |
7 | /*:: import type { PrismTheme } from '../src/types' */
8 |
9 | var theme /*: PrismTheme */ = {
10 | plain: {
11 | backgroundColor: "#2a2734",
12 | color: "#9a86fd"
13 | },
14 | styles: [
15 | {
16 | types: ["comment", "prolog", "doctype", "cdata", "punctuation"],
17 | style: {
18 | color: "#6c6783"
19 | }
20 | },
21 | {
22 | types: ["namespace"],
23 | style: {
24 | opacity: 0.7
25 | }
26 | },
27 | {
28 | types: ["tag", "operator", "number"],
29 | style: {
30 | color: "#e09142"
31 | }
32 | },
33 | {
34 | types: ["property", "function"],
35 | style: {
36 | color: "#9a86fd"
37 | }
38 | },
39 | {
40 | types: ["tag-id", "selector", "atrule-id"],
41 | style: {
42 | color: "#eeebff"
43 | }
44 | },
45 | {
46 | types: ["attr-name"],
47 | style: {
48 | color: "#c4b9fe"
49 | }
50 | },
51 | {
52 | types: [
53 | "boolean",
54 | "string",
55 | "entity",
56 | "url",
57 | "attr-value",
58 | "keyword",
59 | "control",
60 | "directive",
61 | "unit",
62 | "statement",
63 | "regex",
64 | "at-rule",
65 | "placeholder",
66 | "variable"
67 | ],
68 | style: {
69 | color: "#ffcc99"
70 | }
71 | },
72 | {
73 | types: ["deleted"],
74 | style: {
75 | textDecorationLine: "line-through"
76 | }
77 | },
78 | {
79 | types: ["inserted"],
80 | style: {
81 | textDecorationLine: "underline"
82 | }
83 | },
84 | {
85 | types: ["italic"],
86 | style: {
87 | fontStyle: "italic"
88 | }
89 | },
90 | {
91 | types: ["important", "bold"],
92 | style: {
93 | fontWeight: "bold"
94 | }
95 | },
96 | {
97 | types: ["important"],
98 | style: {
99 | color: "#c4b9fe"
100 | }
101 | }
102 | ]
103 | };
104 |
105 | module.exports = theme;
106 |
--------------------------------------------------------------------------------
/src/vendor/prism/index.js:
--------------------------------------------------------------------------------
1 | import Prism from './prism-core'
2 | import codegen from 'codegen.macro'
3 |
4 | // Babel Codegen Macro:
5 | // Get a list of all prismjs languages and inline them here.
6 | // They should only depend on "Prism" being present in the current scope.
7 |
8 | codegen`
9 | const { readFileSync } = require('fs')
10 | const { dirname, join } = require('path')
11 | const { languages } = require('prismjs/components')
12 | const prismPath = dirname(require.resolve('prismjs'))
13 |
14 | let output = '/* This content is auto-generated to include some prismjs language components: */\\n'
15 |
16 | const toDependencies = arr => {
17 | if (typeof arr === 'string') {
18 | return [arr]
19 | }
20 |
21 | return arr;
22 | };
23 |
24 | const addLanguageToOutput = language => {
25 | const pathToLanguage = 'components/prism-' + language
26 | const fullPath = join(prismPath, pathToLanguage + '.js')
27 | const contents = readFileSync(fullPath, 'utf8')
28 | const header = '\\n\\n/* "prismjs/' + pathToLanguage + '" */\\n'
29 | output += header + contents
30 | }
31 |
32 | const visitedLanguages = {}
33 |
34 | const visitLanguage = (language, langEntry) => {
35 | // Mark language as visited or return if it was
36 | if (visitedLanguages[language]) {
37 | return
38 | } else {
39 | visitedLanguages[language] = true
40 | }
41 |
42 | // Required dependencies come before the actual language
43 | const required = toDependencies(langEntry.require)
44 |
45 | if (Array.isArray(required)) {
46 | required.forEach(x => {
47 | if (languages[x]) {
48 | visitLanguage(x, languages[x])
49 | } else {
50 | console.warn('[prismjs/components]: Language', x, 'does not exist!')
51 | }
52 | })
53 | }
54 |
55 | // Add current language to output
56 | addLanguageToOutput(language)
57 |
58 | // Peer dependencies come after the actual language
59 | const peerDependencies = toDependencies(langEntry.peerDependencies)
60 |
61 | if (Array.isArray(peerDependencies)) {
62 | peerDependencies.forEach(x => {
63 | if (languages[x]) {
64 | visitLanguage(x, languages[x])
65 | } else {
66 | console.warn('[prismjs/components]: Language', x, 'does not exist!')
67 | }
68 | })
69 | }
70 | };
71 |
72 | // This json defines which languages to include
73 | const includedLangs = require('./includeLangs')
74 |
75 | Object.keys(includedLangs).forEach(language => {
76 | visitLanguage(language, languages[language])
77 | })
78 |
79 | module.exports = output
80 | `
81 |
82 | export default Prism
83 |
--------------------------------------------------------------------------------
/themes/nightOwl.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | // Original: https://github.com/sdras/night-owl-vscode-theme
3 | // Converted automatically using ./tools/themeFromVsCode
4 |
5 | /*:: import type { PrismTheme } from '../src/types' */
6 |
7 | var theme /*: PrismTheme */ = {
8 | plain: {
9 | color: "#d6deeb",
10 | backgroundColor: "#011627"
11 | },
12 | styles: [
13 | {
14 | types: ["changed"],
15 | style: {
16 | color: "rgb(162, 191, 252)",
17 | fontStyle: "italic"
18 | }
19 | },
20 | {
21 | types: ["deleted"],
22 | style: {
23 | color: "rgba(239, 83, 80, 0.56)",
24 | fontStyle: "italic"
25 | }
26 | },
27 | {
28 | types: ["inserted", "attr-name"],
29 | style: {
30 | color: "rgb(173, 219, 103)",
31 | fontStyle: "italic"
32 | }
33 | },
34 | {
35 | types: ["comment"],
36 | style: {
37 | color: "rgb(99, 119, 119)",
38 | fontStyle: "italic"
39 | }
40 | },
41 | {
42 | types: ["string", "url"],
43 | style: {
44 | color: "rgb(173, 219, 103)"
45 | }
46 | },
47 | {
48 | types: ["variable"],
49 | style: {
50 | color: "rgb(214, 222, 235)"
51 | }
52 | },
53 | {
54 | types: ["number"],
55 | style: {
56 | color: "rgb(247, 140, 108)"
57 | }
58 | },
59 | {
60 | types: ["builtin", "char", "constant", "function"],
61 | style: {
62 | color: "rgb(130, 170, 255)"
63 | }
64 | },
65 | {
66 | // This was manually added after the auto-generation
67 | // so that punctuations are not italicised
68 | types: ["punctuation"],
69 | style: {
70 | color: "rgb(199, 146, 234)",
71 | }
72 | },
73 | {
74 | types: ["selector", "doctype"],
75 | style: {
76 | color: "rgb(199, 146, 234)",
77 | fontStyle: "italic"
78 | }
79 | },
80 | {
81 | types: ["class-name"],
82 | style: {
83 | color: "rgb(255, 203, 139)"
84 | }
85 | },
86 | {
87 | types: ["tag", "operator", "keyword"],
88 | style: {
89 | color: "rgb(127, 219, 202)"
90 | }
91 | },
92 | {
93 | types: ["boolean"],
94 | style: {
95 | color: "rgb(255, 88, 116)"
96 | }
97 | },
98 | {
99 | types: ["property"],
100 | style: {
101 | color: "rgb(128, 203, 196)"
102 | }
103 | },
104 | {
105 | types: ["namespace"],
106 | style: {
107 | color: "rgb(178, 204, 214)"
108 | }
109 | }
110 | ]
111 | };
112 |
113 | module.exports = theme;
114 |
--------------------------------------------------------------------------------
/themes/duotoneLight.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | // Duotone Light
3 | // Author: Simurai, adapted from DuoTone themes for Atom (http://simurai.com/projects/2016/01/01/duotone-themes)
4 | // Conversion: Bram de Haan (http://atelierbram.github.io/Base2Tone-prism/output/prism/prism-base2tone-evening-dark.css)
5 | // Generated with Base16 Builder (https://github.com/base16-builder/base16-builder)
6 |
7 | /*:: import type { PrismTheme } from '../src/types' */
8 |
9 | var theme /*: PrismTheme */ = {
10 | plain: {
11 | backgroundColor: "#faf8f5",
12 | color: "#728fcb"
13 | },
14 | styles: [
15 | {
16 | types: ["comment", "prolog", "doctype", "cdata", "punctuation"],
17 | style: {
18 | color: "#b6ad9a"
19 | }
20 | },
21 | {
22 | types: ["namespace"],
23 | style: {
24 | opacity: 0.7
25 | }
26 | },
27 | {
28 | types: ["tag", "operator", "number"],
29 | style: {
30 | color: "#063289"
31 | }
32 | },
33 | {
34 | types: ["property", "function"],
35 | style: {
36 | color: "#b29762"
37 | }
38 | },
39 | {
40 | types: ["tag-id", "selector", "atrule-id"],
41 | style: {
42 | color: "#2d2006"
43 | }
44 | },
45 | {
46 | types: ["attr-name"],
47 | style: {
48 | color: "#896724"
49 | }
50 | },
51 | {
52 | types: [
53 | "boolean",
54 | "string",
55 | "entity",
56 | "url",
57 | "attr-value",
58 | "keyword",
59 | "control",
60 | "directive",
61 | "unit",
62 | "statement",
63 | "regex",
64 | "at-rule"
65 | ],
66 | style: {
67 | color: "#728fcb"
68 | }
69 | },
70 | {
71 | types: ["placeholder", "variable"],
72 | style: {
73 | color: "#93abdc"
74 | }
75 | },
76 | {
77 | types: ["deleted"],
78 | style: {
79 | textDecorationLine: "line-through"
80 | }
81 | },
82 | {
83 | types: ["inserted"],
84 | style: {
85 | textDecorationLine: "underline"
86 | }
87 | },
88 | {
89 | types: ["italic"],
90 | style: {
91 | fontStyle: "italic"
92 | }
93 | },
94 | {
95 | types: ["important", "bold"],
96 | style: {
97 | fontWeight: "bold"
98 | }
99 | },
100 | {
101 | types: ["important"],
102 | style: {
103 | color: "#896724"
104 | }
105 | }
106 | ]
107 | };
108 |
109 | module.exports = theme;
110 |
--------------------------------------------------------------------------------
/src/types.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { Key } from "react";
4 | import includedLangs from "./vendor/prism/includeLangs";
5 |
6 | export type Language = $Keys;
7 |
8 | type PrismGrammar = {
9 | [key: string]: mixed
10 | };
11 |
12 | type LanguagesDict = {
13 | [lang: Language]: PrismGrammar
14 | };
15 |
16 | export type PrismToken = {
17 | type: string,
18 | content: Array | string
19 | };
20 |
21 | export type Token = {
22 | types: string[],
23 | content: string,
24 | empty?: boolean
25 | };
26 |
27 | export type PrismLib = {
28 | languages: LanguagesDict,
29 | tokenize: (
30 | code: string,
31 | grammar: PrismGrammar,
32 | language: Language
33 | ) => Array,
34 | highlight: (code: string, grammar: PrismGrammar, language: Language) => string
35 | };
36 |
37 | export type StyleObj = {
38 | [key: string]: string | number | null
39 | };
40 |
41 | export type LineInputProps = {
42 | key?: Key,
43 | style?: StyleObj,
44 | className?: string,
45 | line: Token[],
46 | [key: string]: mixed
47 | };
48 |
49 | export type LineOutputProps = {
50 | key?: Key,
51 | style?: StyleObj,
52 | className: string,
53 | [key: string]: mixed
54 | };
55 |
56 | export type TokenInputProps = {
57 | key?: Key,
58 | style?: StyleObj,
59 | className?: string,
60 | token: Token,
61 | [key: string]: mixed
62 | };
63 |
64 | export type TokenOutputProps = {
65 | key?: Key,
66 | style?: StyleObj,
67 | className: string,
68 | children: string,
69 | [key: string]: mixed
70 | };
71 |
72 | export type RenderProps = {
73 | tokens: Token[][],
74 | className: string,
75 | getLineProps: (input: LineInputProps) => LineOutputProps,
76 | getTokenProps: (input: TokenInputProps) => TokenOutputProps
77 | };
78 |
79 | export type PrismThemeEntry = {
80 | color?: string,
81 | backgroundColor?: string,
82 | fontStyle?: "normal" | "italic",
83 | fontWeight?:
84 | | "normal"
85 | | "bold"
86 | | "100"
87 | | "200"
88 | | "300"
89 | | "400"
90 | | "500"
91 | | "600"
92 | | "700"
93 | | "800"
94 | | "900",
95 | textDecorationLine?:
96 | | "none"
97 | | "underline"
98 | | "line-through"
99 | | "underline line-through",
100 | opacity?: number,
101 | [styleKey: string]: string | number | void
102 | };
103 |
104 | export type PrismTheme = {
105 | plain: PrismThemeEntry,
106 | styles: Array<{
107 | types: string[],
108 | style: PrismThemeEntry,
109 | languages?: Language[]
110 | }>
111 | };
112 |
--------------------------------------------------------------------------------
/src/utils/normalizeTokens.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import type { PrismToken, Token } from "../types";
4 |
5 | const newlineRe = /\r\n|\r|\n/;
6 |
7 | // Empty lines need to contain a single empty token, denoted with { empty: true }
8 | const normalizeEmptyLines = (line: Token[]) => {
9 | if (line.length === 0) {
10 | line.push({
11 | types: ["plain"],
12 | content: "",
13 | empty: true
14 | });
15 | } else if (line.length === 1 && line[0].content === "") {
16 | line[0].empty = true;
17 | }
18 | };
19 |
20 | // Takes an array of Prism's tokens and groups them by line, turning plain
21 | // strings into tokens as well. Tokens can become recursive in some cases,
22 | // which means that their types are concatenated. Plain-string tokens however
23 | // are always of type "plain".
24 | // This is not recursive to avoid exceeding the call-stack limit, since it's unclear
25 | // how nested Prism's tokens can become
26 | const normalizeTokens = (tokens: Array): Token[][] => {
27 | const typeArrStack = [[]];
28 | const tokenArrStack = [tokens];
29 | const tokenArrIndexStack = [0];
30 | const tokenArrSizeStack = [tokens.length];
31 |
32 | let i = 0;
33 | let stackIndex = 0;
34 | let currentLine = [];
35 |
36 | const acc = [currentLine];
37 |
38 | while (stackIndex > -1) {
39 | while (
40 | (i = tokenArrIndexStack[stackIndex]++) < tokenArrSizeStack[stackIndex]
41 | ) {
42 | let content;
43 | let types = typeArrStack[stackIndex];
44 | const tokenArr = tokenArrStack[stackIndex];
45 | const token = tokenArr[i];
46 |
47 | // Determine content and append type to types if necessary
48 | if (typeof token === "string") {
49 | types = stackIndex > 0 ? types : ["plain"];
50 | content = token;
51 | } else {
52 | types = types[0] === token.type ? types : types.concat(token.type);
53 | content = token.content;
54 | }
55 |
56 | // If token.content is an array, increase the stack depth and repeat this while-loop
57 | if (typeof content !== "string") {
58 | stackIndex++;
59 | typeArrStack.push(types);
60 | tokenArrStack.push(content);
61 | tokenArrIndexStack.push(0);
62 | tokenArrSizeStack.push(content.length);
63 | continue;
64 | }
65 |
66 | // Split by newlines
67 | const splitByNewlines = content.split(newlineRe);
68 | const newlineCount = splitByNewlines.length;
69 |
70 | currentLine.push({ types, content: splitByNewlines[0] });
71 |
72 | // Create a new line for each string on a new line
73 | for (let i = 1; i < newlineCount; i++) {
74 | normalizeEmptyLines(currentLine);
75 | acc.push((currentLine = []));
76 | currentLine.push({ types, content: splitByNewlines[i] });
77 | }
78 | }
79 |
80 | // Decreate the stack depth
81 | stackIndex--;
82 | typeArrStack.pop();
83 | tokenArrStack.pop();
84 | tokenArrIndexStack.pop();
85 | tokenArrSizeStack.pop();
86 | }
87 |
88 | normalizeEmptyLines(currentLine);
89 | return acc;
90 | };
91 |
92 | export default normalizeTokens;
93 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "prism-react-renderer",
3 | "version": "0.1.3",
4 | "description": "Renders highlighted Prism output using React",
5 | "main": "lib/index.js",
6 | "module": "es/index.js",
7 | "license": "MIT",
8 | "files": [
9 | "es",
10 | "lib",
11 | "prism",
12 | "themes"
13 | ],
14 | "scripts": {
15 | "precommit": "lint-staged",
16 | "build": "run-s build:lib build:es",
17 | "build:lib": "cross-env BABEL_ENV=cjs babel --ignore '**/__tests__/*' --out-dir lib src",
18 | "build:es": "cross-env BABEL_ENV=es babel --ignore '**/__tests__/*' --out-dir es src",
19 | "test": "jest",
20 | "flow": "flow check",
21 | "format": "prettier --write src/**/*.js themes/**/*.js",
22 | "prepublishOnly": "run-p flow build"
23 | },
24 | "lint-staged": {
25 | "linters": {
26 | "{src,themes}/**/*.js": [
27 | "jest --bail --findRelatedTests",
28 | "flow focus-check",
29 | "prettier --write",
30 | "git add"
31 | ]
32 | }
33 | },
34 | "babel": {
35 | "env": {
36 | "test": {
37 | "presets": [
38 | "@babel/preset-flow",
39 | "@babel/preset-react",
40 | [
41 | "@babel/preset-env",
42 | {
43 | "loose": true,
44 | "modules": "commonjs"
45 | }
46 | ]
47 | ],
48 | "plugins": [
49 | "babel-plugin-macros"
50 | ]
51 | },
52 | "cjs": {
53 | "presets": [
54 | "@babel/preset-flow",
55 | "@babel/preset-react",
56 | [
57 | "@babel/preset-env",
58 | {
59 | "loose": true,
60 | "modules": "commonjs"
61 | }
62 | ]
63 | ],
64 | "plugins": [
65 | "babel-plugin-macros"
66 | ]
67 | },
68 | "es": {
69 | "presets": [
70 | "@babel/preset-flow",
71 | "@babel/preset-react",
72 | [
73 | "@babel/preset-env",
74 | {
75 | "loose": true,
76 | "modules": false
77 | }
78 | ]
79 | ],
80 | "plugins": [
81 | "babel-plugin-macros"
82 | ]
83 | }
84 | },
85 | "plugins": [
86 | [
87 | "@babel/plugin-proposal-class-properties",
88 | {
89 | "loose": true
90 | }
91 | ]
92 | ]
93 | },
94 | "peerDependencies": {
95 | "react": ">=0.14.9"
96 | },
97 | "devDependencies": {
98 | "@babel/cli": "^7.0.0-rc.1",
99 | "@babel/core": "^7.0.0-rc.1",
100 | "@babel/plugin-proposal-class-properties": "^7.0.0-rc.1",
101 | "@babel/preset-env": "^7.0.0-rc.1",
102 | "@babel/preset-flow": "^7.0.0-rc.1",
103 | "@babel/preset-react": "^7.0.0-rc.1",
104 | "babel-core": "^7.0.0-bridge.0",
105 | "babel-jest": "^23.4.2",
106 | "babel-plugin-macros": "^2.4.0",
107 | "codegen.macro": "^3.0.0",
108 | "cross-env": "^5.2.0",
109 | "flow-bin": "^0.79.0",
110 | "husky": "^0.14.3",
111 | "jest": "^23.5.0",
112 | "lint-staged": "^7.2.2",
113 | "npm-run-all": "^4.1.3",
114 | "prettier": "^1.14.2",
115 | "prismjs": "^1.15.0",
116 | "react": "^16.4.2",
117 | "react-dom": "^16.4.2",
118 | "react-testing-library": "^5.0.0"
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/themes/oceanicNext.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | // Oceanic Next
3 | // Author: Dmitri Voronianski (https://github.com/voronianski)
4 | // https://github.com/voronianski/oceanic-next-color-scheme
5 | // Adapted from: https://github.com/reactjs/reactjs.org/blob/428d52b/src/prism-styles.js
6 |
7 | /*:: import type { PrismTheme } from '../src/types' */
8 |
9 | var colors = {
10 | char: "#D8DEE9",
11 | comment: "#999999",
12 | keyword: "#c5a5c5",
13 | primitive: "#5a9bcf",
14 | string: "#8dc891",
15 | variable: "#d7deea",
16 | boolean: "#ff8b50",
17 | punctuation: "#5FB3B3",
18 | tag: "#fc929e",
19 | function: "#79b6f2",
20 | className: "#FAC863",
21 | method: "#6699CC",
22 | operator: "#fc929e",
23 | }
24 |
25 | var theme /*: PrismTheme */ = {
26 | plain: {
27 | backgroundColor: "#282c34",
28 | color: "#ffffff",
29 | },
30 | styles: [
31 | {
32 | types: ["attr-name"],
33 | style: {
34 | color: colors.keyword,
35 | },
36 | },
37 | {
38 | types: ["attr-value"],
39 | style: {
40 | color: colors.string,
41 | },
42 | },
43 | {
44 | types: ["comment", "block-comment", "prolog", "doctype", "cdata"],
45 | style: {
46 | color: colors.comment,
47 | },
48 | },
49 | {
50 | types: [
51 | "property",
52 | "number",
53 | "function-name",
54 | "constant",
55 | "symbol",
56 | "deleted",
57 | ],
58 | style: {
59 | color: colors.primitive,
60 | },
61 | },
62 | {
63 | types: ["boolean"],
64 | style: {
65 | color: colors.boolean,
66 | },
67 | },
68 | {
69 | types: ["tag"],
70 | style: {
71 | color: colors.tag,
72 | },
73 | },
74 | {
75 | types: ["string"],
76 | style: {
77 | color: colors.string,
78 | },
79 | },
80 | {
81 | types: ["punctuation"],
82 | style: {
83 | color: colors.string,
84 | },
85 | },
86 | {
87 | types: ["selector", "char", "builtin", "inserted"],
88 | style: {
89 | color: colors.char,
90 | },
91 | },
92 | {
93 | types: ["function"],
94 | style: {
95 | color: colors.function,
96 | },
97 | },
98 | {
99 | types: ["operator", "entity", "url", "variable"],
100 | style: {
101 | color: colors.variable,
102 | },
103 | },
104 | {
105 | types: ["keyword"],
106 | style: {
107 | color: colors.keyword,
108 | },
109 | },
110 | {
111 | types: ["at-rule", "class-name"],
112 | style: {
113 | color: colors.className,
114 | },
115 | },
116 | {
117 | types: ["important"],
118 | style: {
119 | fontWeight: "400",
120 | },
121 | },
122 | {
123 | types: ["bold"],
124 | style: {
125 | fontWeight: "bold",
126 | },
127 | },
128 | {
129 | types: ["italic"],
130 | style: {
131 | fontStyle: "italic",
132 | },
133 | },
134 | {
135 | types: ["namespace"],
136 | style: {
137 | opacity: 0.7,
138 | },
139 | },
140 | ],
141 | }
142 |
143 | module.exports = theme
144 |
--------------------------------------------------------------------------------
/src/components/Highlight.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import React, { Component, type Node } from "react";
4 | import normalizeTokens from "../utils/normalizeTokens";
5 | import themeToDict, { type ThemeDict } from "../utils/themeToDict";
6 |
7 | import type {
8 | Language,
9 | Token,
10 | LineInputProps,
11 | LineOutputProps,
12 | TokenInputProps,
13 | TokenOutputProps,
14 | RenderProps,
15 | PrismLib,
16 | PrismTheme
17 | } from "../types";
18 |
19 | type Props = {
20 | Prism: PrismLib,
21 | theme?: PrismTheme,
22 | language: Language,
23 | code: string,
24 | children: (props: RenderProps) => Node
25 | };
26 |
27 | class Highlight extends Component {
28 | themeDict: ThemeDict | void;
29 |
30 | constructor(props: Props) {
31 | super(props);
32 | if (props.theme) {
33 | this.themeDict = themeToDict(props.theme, props.language);
34 | }
35 | }
36 |
37 | getLineProps = ({
38 | key,
39 | className,
40 | style,
41 | line,
42 | ...rest
43 | }: LineInputProps): LineOutputProps => {
44 | const output: LineOutputProps = {
45 | ...rest,
46 | className: "token-line",
47 | style: undefined,
48 | key: undefined
49 | };
50 |
51 | if (this.themeDict !== undefined) {
52 | output.style = this.themeDict.plain;
53 | }
54 |
55 | if (style !== undefined) {
56 | output.style =
57 | output.style !== undefined ? { ...output.style, ...style } : style;
58 | }
59 |
60 | if (key !== undefined) output.key = key;
61 | if (className) output.className += ` ${className}`;
62 |
63 | return output;
64 | };
65 |
66 | getStyleForToken = ({ types, empty }: Token) => {
67 | const typesSize = types.length;
68 |
69 | if (this.themeDict === undefined) {
70 | return undefined;
71 | } else if (typesSize === 1 && types[0] === "plain") {
72 | return empty ? { display: "inline-block" } : undefined;
73 | } else if (typesSize === 1 && !empty) {
74 | return this.themeDict[types[0]];
75 | }
76 |
77 | const baseStyle = empty ? { display: "inline-block" } : {};
78 | // $FlowFixMe
79 | const typeStyles = types.map(type => this.themeDict[type]);
80 | return Object.assign(baseStyle, ...typeStyles);
81 | };
82 |
83 | getTokenProps = ({
84 | key,
85 | className,
86 | style,
87 | token,
88 | ...rest
89 | }: TokenInputProps): TokenOutputProps => {
90 | const output: TokenOutputProps = {
91 | ...rest,
92 | className: `token ${token.types.join(" ")}`,
93 | children: token.content,
94 | style: this.getStyleForToken(token),
95 | key: undefined
96 | };
97 |
98 | if (style !== undefined) {
99 | output.style =
100 | output.style !== undefined ? { ...output.style, ...style } : style;
101 | }
102 |
103 | if (key !== undefined) output.key = key;
104 | if (className) output.className += ` ${className}`;
105 |
106 | return output;
107 | };
108 |
109 | render() {
110 | const { Prism, language, code, children } = this.props;
111 |
112 | const grammar = Prism.languages[language];
113 | const mixedTokens =
114 | grammar !== undefined ? Prism.tokenize(code, grammar, language) : [code];
115 | const tokens = normalizeTokens(mixedTokens);
116 |
117 | return children({
118 | tokens,
119 | className: `prism-code language-${language}`,
120 | style: this.themeDict ? this.themeDict.root : {},
121 | getLineProps: this.getLineProps,
122 | getTokenProps: this.getTokenProps
123 | });
124 | }
125 | }
126 |
127 | export default Highlight;
128 |
--------------------------------------------------------------------------------
/tools/themeFromVsCode/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | color-convert@^1.9.1:
6 | version "1.9.2"
7 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.2.tgz#49881b8fba67df12a96bdf3f56c0aab9e7913147"
8 | dependencies:
9 | color-name "1.1.1"
10 |
11 | color-name@1.1.1:
12 | version "1.1.1"
13 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.1.tgz#4b1415304cf50028ea81643643bd82ea05803689"
14 |
15 | color-name@^1.0.0:
16 | version "1.1.3"
17 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
18 |
19 | color-string@^1.5.2:
20 | version "1.5.3"
21 | resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc"
22 | dependencies:
23 | color-name "^1.0.0"
24 | simple-swizzle "^0.2.2"
25 |
26 | color@^3.0.0:
27 | version "3.0.0"
28 | resolved "https://registry.yarnpkg.com/color/-/color-3.0.0.tgz#d920b4328d534a3ac8295d68f7bd4ba6c427be9a"
29 | dependencies:
30 | color-convert "^1.9.1"
31 | color-string "^1.5.2"
32 |
33 | define-properties@^1.1.2:
34 | version "1.1.3"
35 | resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
36 | dependencies:
37 | object-keys "^1.0.12"
38 |
39 | es-abstract@^1.6.1:
40 | version "1.12.0"
41 | resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165"
42 | dependencies:
43 | es-to-primitive "^1.1.1"
44 | function-bind "^1.1.1"
45 | has "^1.0.1"
46 | is-callable "^1.1.3"
47 | is-regex "^1.0.4"
48 |
49 | es-to-primitive@^1.1.1:
50 | version "1.1.1"
51 | resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
52 | dependencies:
53 | is-callable "^1.1.1"
54 | is-date-object "^1.0.1"
55 | is-symbol "^1.0.1"
56 |
57 | function-bind@^1.1.0, function-bind@^1.1.1:
58 | version "1.1.1"
59 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
60 |
61 | has@^1.0.1:
62 | version "1.0.3"
63 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
64 | dependencies:
65 | function-bind "^1.1.1"
66 |
67 | is-arrayish@^0.3.1:
68 | version "0.3.2"
69 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
70 |
71 | is-arrow-function@^2.0.3:
72 | version "2.0.3"
73 | resolved "https://registry.yarnpkg.com/is-arrow-function/-/is-arrow-function-2.0.3.tgz#29be2c2d8d9450852b8bbafb635ba7b8d8e87ec2"
74 | dependencies:
75 | is-callable "^1.0.4"
76 |
77 | is-boolean-object@^1.0.0:
78 | version "1.0.0"
79 | resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93"
80 |
81 | is-callable@^1.0.4, is-callable@^1.1.1, is-callable@^1.1.3:
82 | version "1.1.4"
83 | resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
84 |
85 | is-date-object@^1.0.1:
86 | version "1.0.1"
87 | resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
88 |
89 | is-equal@^1.5.5:
90 | version "1.5.5"
91 | resolved "https://registry.yarnpkg.com/is-equal/-/is-equal-1.5.5.tgz#5e85f1957e052883247feb386965a3bba15fbb3d"
92 | dependencies:
93 | has "^1.0.1"
94 | is-arrow-function "^2.0.3"
95 | is-boolean-object "^1.0.0"
96 | is-callable "^1.1.3"
97 | is-date-object "^1.0.1"
98 | is-generator-function "^1.0.6"
99 | is-number-object "^1.0.3"
100 | is-regex "^1.0.3"
101 | is-string "^1.0.4"
102 | is-symbol "^1.0.1"
103 | object.entries "^1.0.4"
104 |
105 | is-generator-function@^1.0.6:
106 | version "1.0.7"
107 | resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522"
108 |
109 | is-number-object@^1.0.3:
110 | version "1.0.3"
111 | resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.3.tgz#f265ab89a9f445034ef6aff15a8f00b00f551799"
112 |
113 | is-regex@^1.0.3, is-regex@^1.0.4:
114 | version "1.0.4"
115 | resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
116 | dependencies:
117 | has "^1.0.1"
118 |
119 | is-string@^1.0.4:
120 | version "1.0.4"
121 | resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.4.tgz#cc3a9b69857d621e963725a24caeec873b826e64"
122 |
123 | is-symbol@^1.0.1:
124 | version "1.0.1"
125 | resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
126 |
127 | object-keys@^1.0.12:
128 | version "1.0.12"
129 | resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2"
130 |
131 | object.entries@^1.0.4:
132 | version "1.0.4"
133 | resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f"
134 | dependencies:
135 | define-properties "^1.1.2"
136 | es-abstract "^1.6.1"
137 | function-bind "^1.1.0"
138 | has "^1.0.1"
139 |
140 | simple-swizzle@^0.2.2:
141 | version "0.2.2"
142 | resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
143 | dependencies:
144 | is-arrayish "^0.3.1"
145 |
--------------------------------------------------------------------------------
/src/components/__tests__/Highlight.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, cleanup } from "react-testing-library";
3 |
4 | import Highlight from "../Highlight";
5 | import defaultProps from "../../defaultProps";
6 |
7 | const exampleCode = `
8 | (function someDemo() {
9 | var test = "Hello World!";
10 | console.log(test);
11 | })();
12 |
13 | return () => ;
14 | `.trim();
15 |
16 | describe(" ", () => {
17 | afterEach(cleanup);
18 |
19 | describe("snapshots", () => {
20 | it("renders correctly", () => {
21 | const { container } = render(
22 |
23 | {({ className, style, tokens, getLineProps, getTokenProps }) => (
24 |
25 | {tokens.map((line, i) => (
26 |
27 | {line.map((token, key) => (
28 |
29 | ))}
30 |
31 | ))}
32 |
33 | )}
34 |
35 | );
36 |
37 | expect(container).toMatchSnapshot();
38 | });
39 |
40 | it("renders unsupported languages correctly", () => {
41 | const { container } = render(
42 |
47 | {({ className, style, tokens, getLineProps, getTokenProps }) => (
48 |
49 | {tokens.map((line, i) => (
50 |
51 | {line.map((token, key) => (
52 |
53 | ))}
54 |
55 | ))}
56 |
57 | )}
58 |
59 | );
60 |
61 | expect(container).toMatchSnapshot();
62 | });
63 |
64 | it("renders without style props when no theme is passed", () => {
65 | const { container } = render(
66 |
72 | {({ className, style, tokens, getLineProps, getTokenProps }) => (
73 |
74 | {tokens.map((line, i) => (
75 |
76 | {line.map((token, key) => (
77 |
78 | ))}
79 |
80 | ))}
81 |
82 | )}
83 |
84 | );
85 |
86 | expect(container.innerHTML.includes("style")).toBeFalsy();
87 | });
88 | });
89 |
90 | describe("getLineProps", () => {
91 | it("transforms lineProps inputs correctly", () => {
92 | const input = {
93 | key: "line-1",
94 | style: { cssProp: "test" },
95 | className: "line-class",
96 | line: [{ types: ["punctuation"], content: "!" }],
97 | restPropsTest: true
98 | };
99 |
100 | render(
101 |
102 | {({ getLineProps }) => {
103 | const output = getLineProps(input);
104 |
105 | expect(output).toEqual({
106 | key: "line-1",
107 | style: {
108 | cssProp: "test",
109 | backgroundColor: null,
110 | color: expect.any(String)
111 | },
112 | className: "token-line line-class",
113 | restPropsTest: true
114 | });
115 |
116 | return null;
117 | }}
118 |
119 | );
120 | });
121 | });
122 |
123 | describe("getTokenProps", () => {
124 | it("transforms tokenProps inputs correctly", () => {
125 | const input = {
126 | key: "token-1",
127 | style: { cssProp: "test" },
128 | className: "token-class",
129 | token: { types: ["punctuation"], content: "!" },
130 | restPropsTest: true
131 | };
132 |
133 | render(
134 |
135 | {({ getTokenProps }) => {
136 | const output = getTokenProps(input);
137 |
138 | expect(output).toEqual({
139 | key: "token-1",
140 | style: { cssProp: "test", color: expect.any(String) },
141 | className: "token punctuation token-class",
142 | restPropsTest: true,
143 | children: "!"
144 | });
145 |
146 | return null;
147 | }}
148 |
149 | );
150 | });
151 |
152 | it("transforms constructor token style correctly", () => {
153 | // From https://github.com/FormidableLabs/prism-react-renderer/issues/11
154 | render(
155 |
156 | {({ tokens, getTokenProps }) => {
157 | const line = tokens[0];
158 | const token = line[2];
159 | const output = getTokenProps({ token, key: 2 });
160 |
161 | expect(typeof output.style).not.toBe("function");
162 |
163 | return null;
164 | }}
165 |
166 | );
167 | });
168 | });
169 | });
170 |
--------------------------------------------------------------------------------
/src/utils/__tests__/normalizeTokens.test.js:
--------------------------------------------------------------------------------
1 | import normalizeTokens from "../normalizeTokens";
2 |
3 | describe("normalizeTokens", () => {
4 | it("handles plain strings", () => {
5 | const input = ["hello", "world"];
6 | const output = normalizeTokens(input);
7 |
8 | expect(output).toEqual([
9 | [
10 | { types: ["plain"], content: "hello" },
11 | { types: ["plain"], content: "world" }
12 | ]
13 | ]);
14 | });
15 |
16 | it("handles flat tokens", () => {
17 | const input = [
18 | { type: "test1", content: "hello" },
19 | { type: "test2", content: "world" }
20 | ];
21 | const output = normalizeTokens(input);
22 |
23 | expect(output).toEqual([
24 | [
25 | { types: ["test1"], content: "hello" },
26 | { types: ["test2"], content: "world" }
27 | ]
28 | ]);
29 | });
30 |
31 | it("handles nested tokens", () => {
32 | const input = [
33 | {
34 | type: "test1",
35 | content: [
36 | { type: "nest1", content: "he" },
37 | { type: "nest2", content: "llo" }
38 | ]
39 | },
40 | { type: "test2", content: "world" }
41 | ];
42 | const output = normalizeTokens(input);
43 |
44 | expect(output).toEqual([
45 | [
46 | { types: ["test1", "nest1"], content: "he" },
47 | { types: ["test1", "nest2"], content: "llo" },
48 | { types: ["test2"], content: "world" }
49 | ]
50 | ]);
51 | });
52 |
53 | it("handles nested & mixed tokens", () => {
54 | const input = [
55 | {
56 | type: "test1",
57 | content: [{ type: "nest", content: "he" }, "llo"]
58 | },
59 | { type: "test2", content: "world" },
60 | "!"
61 | ];
62 | const output = normalizeTokens(input);
63 |
64 | expect(output).toEqual([
65 | [
66 | { types: ["test1", "nest"], content: "he" },
67 | { types: ["test1"], content: "llo" },
68 | { types: ["test2"], content: "world" },
69 | { types: ["plain"], content: "!" }
70 | ]
71 | ]);
72 | });
73 |
74 | it("handles deeply nested tokens", () => {
75 | const input = [
76 | {
77 | type: "1",
78 | content: [
79 | {
80 | type: "2",
81 | content: [{ type: "3", content: "hello" }]
82 | }
83 | ]
84 | }
85 | ];
86 | const output = normalizeTokens(input);
87 |
88 | expect(output).toEqual([[{ types: ["1", "2", "3"], content: "hello" }]]);
89 | });
90 |
91 | it("handles plain strings with newlines", () => {
92 | const input = ["hello", " \nworld"];
93 | const output = normalizeTokens(input);
94 |
95 | expect(output).toEqual([
96 | [
97 | { types: ["plain"], content: "hello" },
98 | { types: ["plain"], content: " " }
99 | ],
100 | [{ types: ["plain"], content: "world" }]
101 | ]);
102 | });
103 |
104 | it("handles flat tokens with newlines", () => {
105 | const input = [
106 | { type: "test1", content: "hello" },
107 | { type: "test2", content: "wor\nld" }
108 | ];
109 | const output = normalizeTokens(input);
110 |
111 | expect(output).toEqual([
112 | [
113 | { types: ["test1"], content: "hello" },
114 | { types: ["test2"], content: "wor" }
115 | ],
116 | [{ types: ["test2"], content: "ld" }]
117 | ]);
118 | });
119 |
120 | it("handles nested tokens with newlines", () => {
121 | const input = [
122 | {
123 | type: "test1",
124 | content: [
125 | { type: "nest1", content: "he" },
126 | { type: "nest2", content: "l\nlo" }
127 | ]
128 | },
129 | { type: "test2", content: "wor\nld" }
130 | ];
131 | const output = normalizeTokens(input);
132 |
133 | expect(output).toEqual([
134 | [
135 | { types: ["test1", "nest1"], content: "he" },
136 | { types: ["test1", "nest2"], content: "l" }
137 | ],
138 | [
139 | { types: ["test1", "nest2"], content: "lo" },
140 | { types: ["test2"], content: "wor" }
141 | ],
142 | [{ types: ["test2"], content: "ld" }]
143 | ]);
144 | });
145 |
146 | it("handles nested & mixed tokens with newlines", () => {
147 | const input = [
148 | {
149 | type: "test1",
150 | content: [{ type: "nest", content: "h\ne" }, "l\nlo"]
151 | },
152 | "world\n!"
153 | ];
154 | const output = normalizeTokens(input);
155 |
156 | expect(output).toEqual([
157 | [{ types: ["test1", "nest"], content: "h" }],
158 | [
159 | { types: ["test1", "nest"], content: "e" },
160 | { types: ["test1"], content: "l" }
161 | ],
162 | [
163 | { types: ["test1"], content: "lo" },
164 | { types: ["plain"], content: "world" }
165 | ],
166 | [{ types: ["plain"], content: "!" }]
167 | ]);
168 | });
169 |
170 | it("handles deeply nested tokens with newlines", () => {
171 | const input = [
172 | {
173 | type: "1",
174 | content: [
175 | {
176 | type: "2",
177 | content: [{ type: "3", content: "hel\nlo" }]
178 | }
179 | ]
180 | }
181 | ];
182 | const output = normalizeTokens(input);
183 |
184 | expect(output).toEqual([
185 | [{ types: ["1", "2", "3"], content: "hel" }],
186 | [{ types: ["1", "2", "3"], content: "lo" }]
187 | ]);
188 | });
189 |
190 | it("handles empty lines gracefully", () => {
191 | const input = ["\n\n"];
192 | const output = normalizeTokens(input);
193 |
194 | expect(output).toEqual([
195 | [{ types: ["plain"], content: "", empty: true }],
196 | [{ types: ["plain"], content: "", empty: true }],
197 | [{ types: ["plain"], content: "", empty: true }]
198 | ]);
199 | });
200 | });
201 |
--------------------------------------------------------------------------------
/src/components/__tests__/__snapshots__/Highlight.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` snapshots renders correctly 1`] = `
4 |
5 |
9 |
13 |
17 | (
18 |
19 |
23 | function
24 |
25 |
28 |
29 |
30 |
34 | someDemo
35 |
36 |
40 | (
41 |
42 |
46 | )
47 |
48 |
51 |
52 |
53 |
57 | {
58 |
59 |
62 |
63 |
67 |
70 |
71 |
72 |
76 | var
77 |
78 |
81 | test
82 |
83 |
87 | =
88 |
89 |
92 |
93 |
94 |
98 | "Hello World!"
99 |
100 |
104 | ;
105 |
106 |
109 |
110 |
114 |
117 | console
118 |
119 |
123 | .
124 |
125 |
129 | log
130 |
131 |
135 | (
136 |
137 |
140 | test
141 |
142 |
146 | )
147 |
148 |
152 | ;
153 |
154 |
157 |
158 |
162 |
165 |
169 | }
170 |
171 |
175 | )
176 |
177 |
181 | (
182 |
183 |
187 | )
188 |
189 |
193 | ;
194 |
195 |
198 |
199 |
203 |
207 |
208 |
212 |
215 |
219 | return
220 |
221 |
224 |
225 |
226 |
230 | (
231 |
232 |
236 | )
237 |
238 |
241 |
242 |
243 |
247 | =>
248 |
249 |
252 |
253 |
254 |
258 | <
259 |
260 |
264 | App
265 |
266 |
270 |
271 |
272 |
276 | />
277 |
278 |
282 | ;
283 |
284 |
285 |
286 |
287 | `;
288 |
289 | exports[` snapshots renders unsupported languages correctly 1`] = `
290 |
291 |
295 |
299 |
302 | (function someDemo() {
303 |
304 |
305 |
309 |
312 | var test = "Hello World!";
313 |
314 |
315 |
319 |
322 | console.log(test);
323 |
324 |
325 |
329 |
332 | })();
333 |
334 |
335 |
339 |
343 |
344 |
348 |
351 | return () => <App />;
352 |
353 |
354 |
355 |
356 | `;
357 |
--------------------------------------------------------------------------------
/src/vendor/prism/prism-core.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Prism: Lightweight, robust, elegant syntax highlighting
3 | * MIT license http://www.opensource.org/licenses/mit-license.php/
4 | * @author Lea Verou http://lea.verou.me
5 | */
6 |
7 | /**
8 | * prism-react-renderer:
9 | * This file has been modified to remove:
10 | * - globals and window dependency
11 | * - worker support
12 | * - highlightAll and other element dependent methods
13 | * - _.hooks helpers
14 | * - UMD/node-specific hacks
15 | * It has also been run through prettier
16 | */
17 |
18 | var Prism = (function() {
19 | // Private helper vars
20 | var lang = /\blang(?:uage)?-([\w-]+)\b/i
21 | var uniqueId = 0
22 |
23 | var _ = {
24 | util: {
25 | encode: function(tokens) {
26 | if (tokens instanceof Token) {
27 | return new Token(
28 | tokens.type,
29 | _.util.encode(tokens.content),
30 | tokens.alias
31 | )
32 | } else if (_.util.type(tokens) === "Array") {
33 | return tokens.map(_.util.encode)
34 | } else {
35 | return tokens
36 | .replace(/&/g, "&")
37 | .replace(/ text.length) {
239 | // Something went terribly wrong, ABORT, ABORT!
240 | return
241 | }
242 |
243 | if (str instanceof Token) {
244 | continue
245 | }
246 |
247 | if (greedy && i != strarr.length - 1) {
248 | pattern.lastIndex = pos
249 | var match = pattern.exec(text)
250 | if (!match) {
251 | break
252 | }
253 |
254 | var from = match.index + (lookbehind ? match[1].length : 0),
255 | to = match.index + match[0].length,
256 | k = i,
257 | p = pos
258 |
259 | for (
260 | var len = strarr.length;
261 | k < len &&
262 | (p < to || (!strarr[k].type && !strarr[k - 1].greedy));
263 | ++k
264 | ) {
265 | p += strarr[k].length
266 | // Move the index i to the element in strarr that is closest to from
267 | if (from >= p) {
268 | ++i
269 | pos = p
270 | }
271 | }
272 |
273 | // If strarr[i] is a Token, then the match starts inside another Token, which is invalid
274 | if (strarr[i] instanceof Token) {
275 | continue
276 | }
277 |
278 | // Number of tokens to delete and replace with the new match
279 | delNum = k - i
280 | str = text.slice(pos, p)
281 | match.index -= pos
282 | } else {
283 | pattern.lastIndex = 0
284 |
285 | var match = pattern.exec(str),
286 | delNum = 1
287 | }
288 |
289 | if (!match) {
290 | if (oneshot) {
291 | break
292 | }
293 |
294 | continue
295 | }
296 |
297 | if (lookbehind) {
298 | lookbehindLength = match[1] ? match[1].length : 0
299 | }
300 |
301 | var from = match.index + lookbehindLength,
302 | match = match[0].slice(lookbehindLength),
303 | to = from + match.length,
304 | before = str.slice(0, from),
305 | after = str.slice(to)
306 |
307 | var args = [i, delNum]
308 |
309 | if (before) {
310 | ++i
311 | pos += before.length
312 | args.push(before)
313 | }
314 |
315 | var wrapped = new Token(
316 | token,
317 | inside ? _.tokenize(match, inside) : match,
318 | alias,
319 | match,
320 | greedy
321 | )
322 |
323 | args.push(wrapped)
324 |
325 | if (after) {
326 | args.push(after)
327 | }
328 |
329 | Array.prototype.splice.apply(strarr, args)
330 |
331 | if (delNum != 1)
332 | _.matchGrammar(text, strarr, grammar, i, pos, true, token)
333 |
334 | if (oneshot) break
335 | }
336 | }
337 | }
338 | },
339 |
340 | hooks: {
341 | add: function () {}
342 | },
343 |
344 | tokenize: function(text, grammar, language) {
345 | var strarr = [text]
346 |
347 | var rest = grammar.rest
348 |
349 | if (rest) {
350 | for (var token in rest) {
351 | grammar[token] = rest[token]
352 | }
353 |
354 | delete grammar.rest
355 | }
356 |
357 | _.matchGrammar(text, strarr, grammar, 0, 0, false)
358 |
359 | return strarr
360 | },
361 | }
362 |
363 | var Token = (_.Token = function(type, content, alias, matchedStr, greedy) {
364 | this.type = type
365 | this.content = content
366 | this.alias = alias
367 | // Copy of the full string this token was created from
368 | this.length = (matchedStr || "").length | 0
369 | this.greedy = !!greedy
370 | })
371 |
372 | Token.stringify = function(o, language, parent) {
373 | if (typeof o == "string") {
374 | return o
375 | }
376 |
377 | if (_.util.type(o) === "Array") {
378 | return o
379 | .map(function(element) {
380 | return Token.stringify(element, language, o)
381 | })
382 | .join("")
383 | }
384 |
385 | var env = {
386 | type: o.type,
387 | content: Token.stringify(o.content, language, parent),
388 | tag: "span",
389 | classes: ["token", o.type],
390 | attributes: {},
391 | language: language,
392 | parent: parent,
393 | }
394 |
395 | if (o.alias) {
396 | var aliases = _.util.type(o.alias) === "Array" ? o.alias : [o.alias]
397 | Array.prototype.push.apply(env.classes, aliases)
398 | }
399 |
400 | var attributes = Object.keys(env.attributes)
401 | .map(function(name) {
402 | return (
403 | name +
404 | '="' +
405 | (env.attributes[name] || "").replace(/"/g, """) +
406 | '"'
407 | )
408 | })
409 | .join(" ")
410 |
411 | return (
412 | "<" +
413 | env.tag +
414 | ' class="' +
415 | env.classes.join(" ") +
416 | '"' +
417 | (attributes ? " " + attributes : "") +
418 | ">" +
419 | env.content +
420 | "" +
421 | env.tag +
422 | ">"
423 | )
424 | }
425 |
426 | return _
427 | })()
428 |
429 | module.exports = Prism
430 | Prism.default = Prism
431 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | prism-react-renderer 🖌️
3 |
4 |
5 |
6 | A lean Prism highlighter component for React
7 | Comes with everything to render Prismjs highlighted code directly to React (Native) elements, global-pollution-free!
8 |
9 |
10 | ## Why?
11 |
12 | Maybe you need to render some extra UI with your Prismjs-highlighted code,
13 | or maybe you'd like to manipulate what Prism renders completely,
14 | or maybe you're just using Prism with React and are searching for an easier,
15 | global-pollution-free way?
16 |
17 | Then you're right where you want to be!
18 |
19 | ## How?
20 |
21 | This library tokenises code using Prism and provides a small render-props-driven
22 | component to quickly render it out into React. This is why it even works with
23 | React Native! It's bundled with a modified version of Prism that won't pollute
24 | the global namespace and comes with
25 | [a couple of common language syntaxes](./src/vendor/prism/includeLangs.js).
26 |
27 | _(There's also an [escape-hatch](https://github.com/FormidableLabs/prism-react-renderer#prism) to use your own Prism setup, just in case)_
28 |
29 | It also comes with its own [VSCode-like theming format](#theming), which means by default
30 | you can easily drop in different themes, use the ones this library ships with, or
31 | create new ones programmatically on the fly.
32 |
33 | _(If you just want to use your Prism CSS-file themes, that's also no problem)_
34 |
35 | ## Table of Contents
36 |
37 |
38 |
39 |
40 | - [Installation](#installation)
41 | - [Usage](#usage)
42 | - [Basic Props](#basic-props)
43 | - [children](#children)
44 | - [language](#language)
45 | - [code](#code)
46 | - [Advanced Props](#advanced-props)
47 | - [theme](#theme)
48 | - [Prism](#prism)
49 | - [Children Function](#children-function)
50 | - [state](#state)
51 | - [prop getters](#prop-getters)
52 | - [`getLineProps`](#getlineprops)
53 | - [`getTokenProps`](#gettokenprops)
54 | - [Theming](#theming)
55 | - [FAQ](#faq)
56 | - [LICENSE](#license)
57 |
58 |
59 |
60 | ## Installation
61 |
62 | This module is distributed via npm which is bundled with node and
63 | should be installed as one of your project's `dependencies`:
64 |
65 | ```sh
66 | # npm
67 | npm install --save prism-react-renderer
68 | # yarn
69 | yarn add prism-react-renderer
70 | ```
71 |
72 | > This package also depends on `react`. Please make sure you
73 | > have those installed as well.
74 |
75 | ## Usage
76 |
77 | > [Try it out in the browser](https://codesandbox.io/s/00o4wx0jqv)
78 |
79 | ```jsx
80 | import React from "react";
81 | import { render } from "react-dom";
82 | import Highlight, { defaultProps } from "prism-react-renderer";
83 |
84 | const exampleCode = `
85 | (function someDemo() {
86 | var test = "Hello World!";
87 | console.log(test);
88 | })();
89 |
90 | return () => ;
91 | `;
92 |
93 | render((
94 |
95 | {({ className, style, tokens, getLineProps, getTokenProps }) => (
96 |
97 | {tokens.map((line, i) => (
98 |
99 | {line.map((token, key) => (
100 |
101 | ))}
102 |
103 | ))}
104 |
105 | )}
106 | ,
107 | document.getElementById('root')
108 | );
109 | ```
110 |
111 | ` ` is the only component exposed by this package, as inspired by
112 | [downshift](https://github.com/paypal/downshift).
113 |
114 | It also exports a `defaultProps` object which for basic usage can simply be spread
115 | onto the ` ` component. It also provides some default theming.
116 |
117 | It doesn't render anything itself, it just calls the render function and renders that.
118 | ["Use a render prop!"](https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce)!
119 | `{highlight => /* your JSX here! */ } `
120 |
121 | ## Basic Props
122 |
123 | This is the list of props that you should probably know about. There are some
124 | [advanced props](#advanced-props) below as well.
125 |
126 | Most of these [advanced props](#advanced-props) are included in the `defaultProps`.
127 |
128 | ### children
129 |
130 | > `function({})` | _required_
131 |
132 | This is called with an object. Read more about the properties of this object in
133 | the section "[Children Function](#children-function)".
134 |
135 | ### language
136 |
137 | > `string` | _required_
138 |
139 | This is the language that your code will be highlighted as. You can see a list
140 | of all languages that are supported out of the box [here](./src/vendor/prism/includeLangs.js).
141 |
142 | ### code
143 |
144 | > `string` | _required_
145 |
146 | This is the code that will be highlighted.
147 |
148 | ## Advanced Props
149 |
150 | ### theme
151 |
152 | > `PrismTheme` | _required; default is provided in `defaultProps` export_
153 |
154 | If a theme is passed, it is used to generate style props which can be retrieved
155 | via the prop-getters which are described in "[Children Function](#children-function)".
156 |
157 | A default theme is provided by the `defaultProps` object.
158 |
159 | Read more about how to theme `react-prism-renderer` in
160 | the section "[Theming](#theming)".
161 |
162 | ### Prism
163 |
164 | > `PrismLib` | _required; default is provided in `defaultProps` export_
165 |
166 | This is the [Prismjs](https://github.com/PrismJS/prism) library itself.
167 | A vendored version of Prism is provided (and also exported) as part of this library.
168 | This vendored version doesn't pollute the global namespace, is slimmed down,
169 | and doesn't conflict with any installation of `prismjs` you might have.
170 |
171 | If you're only using `Prism.highlight` you can choose to use `prism-react-renderer`'s
172 | exported, vendored version of Prism instead.
173 |
174 | But if you choose to use your own Prism setup, simply pass Prism as a prop:
175 |
176 | ```jsx
177 | // Whichever way you're retrieving Prism here:
178 | import Prism from 'prismjs/components/prism-core';
179 |
180 |
181 | ```
182 |
183 | ## Children Function
184 |
185 | This is where you render whatever you want to based on the output of ` `.
186 | You use it like so:
187 |
188 | ```js
189 | const ui = (
190 |
191 | {highlight => (
192 | // use utilities and prop getters here, like highlight.className, highlight.getTokenProps, etc.
193 | {/* more jsx here */}
194 | )}
195 |
196 | );
197 | ```
198 |
199 | The properties of this `highlight` object can be split into two categories as indicated below:
200 |
201 | ### state
202 |
203 | These properties are the flat output of ` `. They're generally "state" and are what
204 | you'd usually expect from a render-props-based API.
205 |
206 | | property | type | description |
207 | | ----------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------- |
208 | | `tokens` | `Token[][]` | This is a doubly nested array of tokens. The outer array is for separate lines, the inner for tokens, so the actual content. |
209 | | `className` | `string` | This is the class you should apply to your wrapping element, typically a `` |
210 |
211 | A "Token" is an object that represents a piece of content for Prism. It has a `types` property, which is an array
212 | of types that indicate the purpose and styling of a piece of text, and a `content` property, which is the actual
213 | text.
214 |
215 | You'd typically iterate over `tokens`, rendering each line, and iterate over its items, rendering out each token, which is a piece of
216 | this line.
217 |
218 | ### prop getters
219 |
220 | > See
221 | > [Kent C. Dodds' blog post about prop getters](https://blog.kentcdodds.com/how-to-give-rendering-control-to-users-with-prop-getters-549eaef76acf)
222 |
223 | These functions are used to apply props to the elements that you render. This
224 | gives you maximum flexibility to render what, when, and wherever you like.
225 |
226 | You'd typically call these functions with some dictated input and add on all other
227 | props that it should pass through. It'll correctly override and modify the props
228 | that it returns to you, so passing props to it instead of adding them directly is
229 | advisable.
230 |
231 | | property | type | description |
232 | | --------------- | -------------- | ----------------------------------------------------------------------------------------------------- |
233 | | `getLineProps` | `function({})` | returns the props you should apply to any list of tokens, i.e. the element that contains your tokens. |
234 | | `getTokenProps` | `function({})` | returns the props you should apply to the elements displaying tokens that you render. |
235 |
236 | #### `getLineProps`
237 |
238 | You need to add a `line` property (type: `Token[]`) to the object you're passing to
239 | `getLineProps`; It's also advisable to add a `key`.
240 |
241 | This getter will return you props to spread onto your line elements (typically `s`).
242 |
243 | It will typically return a `className` (if you pass one it'll be appended), `children`,
244 | `style` (if you pass one it'll be merged). It also passes on all other props you pass
245 | to the input.
246 |
247 | The `className` will always contain `.token-line`.
248 |
249 | #### `getTokenProps`
250 |
251 | You need to add a `token` property (type: `Token`) to the object you're passing to
252 | `getTokenProps`; It's also advisable to add a `key`.
253 |
254 | This getter will return you props to spread onto your token elements (typically `s`).
255 |
256 | It will typically return a `className` (if you pass one it'll be appended), `children`,
257 | `style` (if you pass one it'll be merged). It also passes on all other props you pass
258 | to the input.
259 |
260 | The `className` will always contain `.token`. This also provides full compatibility with
261 | your old Prism CSS-file themes.
262 |
263 | ## Theming
264 |
265 | The `defaultProps` you'd typically apply in a basic use-case, contain a default theme.
266 | This theme is [duotoneDark](./themes/duotoneDark.js).
267 |
268 | While all `className`s are provided with ` `, so that you could use your good
269 | old Prism CSS-file themes, you can also choose to use `react-prism-renderer`'s themes.
270 |
271 | These themes are JSON-based and are heavily inspired by VSCode's theme format.
272 |
273 | Their syntax, expressed in Flow looks like the following:
274 |
275 | ```js
276 | {
277 | plain: StyleObj,
278 | styles: Array<{
279 | types: string[],
280 | languages?: string[],
281 | style: StyleObj
282 | }>
283 | }
284 | ```
285 |
286 | The `plain` property provides a base style-object. This style object is directly used
287 | in the `style` props that you'll receive from the prop getters, if a `theme` prop has
288 | been passed to ` `.
289 |
290 | The `styles` property contains an array of definitions. Each definition contains a `style`
291 | property, that is also just a style object. These styles are limited by the `types`
292 | and `languages` properties.
293 |
294 | The `types` properties is an array of token types that Prism outputs. The `languages`
295 | property limits styles to highlighted languages.
296 |
297 | When converting a Prism CSS theme it's mostly just necessary to use classes as
298 | `types` and convert the declarations to object-style-syntax and put them on `style`.
299 |
300 | ## FAQ
301 |
302 |
303 |
304 | How do I use my old Prism css themes?
305 |
306 | `prism-react-renderer` still returns you all proper `className`s via the prop getters,
307 | when you use it. By default however it uses its new theming system, which output a
308 | couple of `style` props as well.
309 |
310 | If you don't pass `theme` to the ` ` component it will default to not
311 | outputting any `style` props, while still returning you the `className` props, like
312 | so:
313 |
314 | ```js
315 |
321 | {highlight => null /* ... */}
322 |
323 | ```
324 |
325 |
326 |
327 |
328 |
329 | How do I prevent a theme and the vendored Prism to be bundled?
330 |
331 | Since the default theme and the vendored Prism library in `prism-react-renderer`
332 | come from `defaultProps`, if you wish to pass your own Prism library in, and not
333 | use the built-in theming, you simply need to leave it out to allow your bundler
334 | to tree-shake those:
335 |
336 | ```js
337 | import Highlight from "prism-react-renderer";
338 | import Prism from "prismjs"; // Different source
339 |
340 |
341 | {highlight => null /* ... */}
342 | ;
343 | ```
344 |
345 | You can also import the vendored Prism library on its own:
346 |
347 | ```js
348 | import { Prism } from "prism-react-renderer";
349 | // or
350 | import Prism from "prism-react-renderer/prism";
351 | ```
352 |
353 |
354 |
355 | ## LICENSE
356 |
357 | MIT
358 |
--------------------------------------------------------------------------------