├── .eslintrc.js
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── _config.yml
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── MonacoJSXHighlighter.css
├── MonacoJSXHighlighter.js
└── index.js
├── tsconfig.json
└── webpack.config.js
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "es2021": true,
6 | "node": true
7 | },
8 | "extends": [
9 | // "standard-with-typescript",
10 | // "plugin:react/recommended"
11 | ],
12 | "overrides": [
13 | {
14 | "env": {
15 | "node": true
16 | },
17 | "files": [
18 | ".eslintrc.{js,cjs}"
19 | ],
20 | "parserOptions": {
21 | "sourceType": "module"
22 | }
23 | }
24 | ],
25 | "parserOptions": {
26 | "ecmaVersion": "latest",
27 | "sourceType": "module",
28 | },
29 | "plugins": [
30 | "react"
31 | ],
32 | "rules": {
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build
39 |
40 | # TypeScript v1 declaration files
41 | typings/
42 |
43 | # TypeScript cache
44 | *.tsbuildinfo
45 |
46 | # Optional npm cache directory
47 | .npm
48 |
49 | # Optional eslint cache
50 | .eslintcache
51 |
52 | # Microbundle cache
53 | .rpt2_cache/
54 | .rts2_cache_cjs/
55 | .rts2_cache_es/
56 | .rts2_cache_umd/
57 |
58 | # Optional REPL history
59 | .node_repl_history
60 |
61 | # Output of 'npm pack'
62 | *.tgz
63 |
64 | # Yarn Integrity file
65 | .yarn-integrity
66 |
67 | # dotenv environment variables file
68 | .env
69 | .env.test
70 |
71 | # parcel-bundler cache (https://parceljs.org/)
72 | .cache
73 |
74 | # Next.js build output
75 | .next
76 |
77 | # Nuxt.js build / generate output
78 | .nuxt
79 | dist
80 |
81 | # Gatsby files
82 | .cache/
83 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
84 | # https://nextjs.org/blog/next-9-1#public-directory-support
85 | # public
86 |
87 | # vuepress build output
88 | .vuepress/dist
89 |
90 | # Serverless directories
91 | .serverless/
92 |
93 | # FuseBox cache
94 | .fusebox/
95 |
96 | # DynamoDB Local files
97 | .dynamodb/
98 |
99 | # TernJS port file
100 | .tern-port
101 |
102 | .github/
103 | gh-pages/
104 |
105 | node_modules
106 | .DS_Store
107 | .vscode
108 | jsconfig.json
109 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src/
2 | build/
3 | .eslintrc.js
4 | .gitignore
5 | _config.yml
6 | rollup.config.js
7 | webpack.config.js
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 David Gonzalez
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # monaco-jsx-highlighter
2 | [](https://www.npmjs.com/package/monaco-jsx-highlighter)
3 | [](https://www.npmjs.com/package/monaco-jsx-highlighter)
4 |
5 | An extensible library to highlight (and comment) JSX syntax in the Monaco Editor
6 | using Babel. It exposes its AST after it does its magic, so you can add your own
7 | syntax-based or custom highlights.
8 |
9 | ## [LIVE DEMO](https://codesandbox.io/s/monaco-editor-react-with-jsx-highlighting-and-commenting-v1-urce8?file=/src/index.js)
10 | [](https://codesandbox.io/p/sandbox/monaco-editor-react-with-jsx-highlighting-and-commenting-v2-urce8?file=/src/index.js)
11 | ```sh
12 | # with npm (assuming you are already using monaco-editor)
13 | npm i @babel/parser @babel/traverse monaco-jsx-highlighter
14 | # with yarn (assuming you are already using monaco-editor)
15 | yarn add @babel/parser @babel/traverse monaco-jsx-highlighter
16 | ```
17 |
18 | ## TL;DR
19 |
20 | ```js
21 | import monaco from 'monaco-editor';
22 | import {parse} from "@babel/parser";
23 | import traverse from "@babel/traverse";
24 | import MonacoJSXHighlighter, {makeBabelParse} from 'monaco-jsx-highlighter';
25 |
26 | // Minimal Babel setup for React JSX parsing:
27 | const babelParse = code => parse(code, {
28 | sourceType: "module",
29 | plugins: ["jsx"]
30 | });
31 |
32 | // Instantiate the highlighter
33 | const monacoJSXHighlighter = new MonacoJSXHighlighter(
34 | monaco, babelParse, traverse, getMonacoEditor()
35 | );
36 | // Activate highlighting (debounceTime default: 100ms)
37 | monacoJSXHighlighter.highlightOnDidChangeModelContent(100);
38 | // Activate JSX commenting
39 | monacoJSXHighlighter.addJSXCommentCommand();
40 | // Done =)
41 |
42 | function getMonacoEditor(){
43 | return monaco.editor.create(
44 | document.getElementById("editor"), {
45 | value: 'const AB={"hello"};',
46 | language: 'javascript'
47 | });
48 | }
49 |
50 | //use makeBabelParse if unsure of the config you need for TSX
51 | ```
52 |
53 | ## NL;PR
54 |
55 | ## New in v2
56 |
57 | - Adds `makeBabelParse`: babel's parse configuration for JSX/TSX (thanks [@HaimCandiTech](https://github.com/HaimCandiTech))
58 | - TS codebase migration start.
59 | - Reported defect fixes(dispose).
60 |
61 | ## New in v1
62 |
63 | - Babel is now used directly instead of via JsCodeShift.
64 | - React fragment, spread child, spread attribute, and container expression
65 | highlighting.
66 | - highlightOnDidChangeModelContent(debounceTime) method debounces highlight
67 | updates.
68 | - Several defect repairs.
69 |
70 | ### Breaking Changes
71 |
72 | If you have used 0.x versions, you'll notice JsCodeShift has been replaced with
73 | Babel:
74 |
75 | ```diff
76 | - import j from 'jscodeshift';
77 | + import {parse} from "@babel/parser";
78 | + import traverse from "@babel/traverse";
79 | ```
80 |
81 | This only affects the constructor signature:
82 |
83 | ```diff
84 | + const babelParse = code => parse(code, {sourceType: "module", plugins: ["jsx"]});
85 | const monacoJSXHighlighter = new MonacoJSXHighlighter(
86 | monaco,
87 | - j,
88 | + babelParse, traverse,
89 | monacoEditor
90 | );
91 | ```
92 |
93 | Also, `monacoJSXHighlighter.highlightOnDidChangeModelContent` method now has an
94 | optional debounce time as first parameter on its signature:
95 |
96 | ```diff
97 | monacoJSXHighlighter.highlightOnDidChangeModelContent(
98 | - afterHighlight: func,
99 | + debounceTime: number, afterHighlight: func,
100 | ...)
101 | ```
102 |
103 | ### Dependencies
104 |
105 | It requires [`monaco-editor`](https://www.npmjs.com/package/monaco-editor)
106 | , [`@babel/parser`](https://www.npmjs.com/package/@babel/parser)
107 | and [`@babel/traverse`](https://www.npmjs.com/package/@babel/traverse), for
108 | convenience, they are listed as peer dependencies and passed by reference (so
109 | you can do lazy loading). Please install them before `monaco-jsx-highlighter`;
110 |
111 | ### Installation
112 |
113 | Install the package in your project directory with:
114 |
115 | #### NPM:
116 | ```sh
117 | # with npm
118 | npm install @babel/parser
119 | npm install @babel/traverse
120 | npm install monaco-jsx-highlighter
121 | ```
122 | #### YARN:
123 | ```sh
124 | # with yarn
125 | yarn add @babel/parser
126 | yarn add @babel/traverse
127 | yarn add monaco-jsx-highlighter
128 | ```
129 |
130 | ### Replacing CSS classes with your own
131 |
132 | ```js
133 | import {JSXTypes} from 'monaco-jsx-highlighter';
134 | // JSXTypes:JSX Syntax types and their CSS classnames.
135 | // Customize the color font in JSX texts: .myCustomCSS {color: red;}
136 | JSXTypes.JSXText.options.inlineClassName = "myCustomCSS";
137 | ```
138 |
139 | ### Overriding CSS classes
140 |
141 | Take a look of
142 | the [`src/JSXColoringProvider.css` file](https://github.com/luminaxster/syntax-highlighter/blob/master/src/MonacoJSXHighlighter.css)
143 | and override the CSS classes you need. Make sure to import your customization
144 | CSS files after you import `monaco-jsx-highlighter`.
145 |
146 | ### Advanced Usage
147 | After your have a Monaco JSX Highlighter instance, `monacoJSXHighlighter`:
148 | ```js
149 | const defaultOptions = {
150 | parser: 'babel', // for reference only, only babel is supported right now
151 | isHighlightGlyph: false, // if JSX elements should decorate the line number gutter
152 | iShowHover: false, // if JSX types should tooltip with their type info
153 | isUseSeparateElementStyles: false, // if opening elements and closing elements have different styling
154 | isThrowJSXParseErrors: false, // Only JSX Syntax Errors are not thrown by default when parsing, true will throw like any other parsign error
155 | };
156 |
157 | const monacoJSXHighlighter = new MonacoJSXHighlighter(
158 | monaco, babelParse, traverse, monacoEditor, defaultOptions
159 | );
160 | ```
161 | The highlight activation method, `monacoJSXHighlighter.highlightOnDidChangeModelContent(debounceTime: number, afterHighlight: func, ...)`
162 | , accepts a callback among other parameters. The callback `afterHighlight`
163 | passes the AST used to highlight the code. Passing parameters and using the disposer function returned by the call are optional.
164 |
165 | **Note:** The disposer is always called when the editor is disposed.
166 |
167 | ```js
168 | // Optional: Disable highlighting when needed (e.g. toggling, unmounting, pausing)
169 | const highlighterDisposeFunc = monacoJSXHighlighter.
170 | highlightOnDidChangeModelContent(
171 | 100,
172 | ast=>{}
173 | );
174 | highlighterDisposeFunc(); // if you need to
175 |
176 | // Internally the highlighter is triggering after each code change debounced
177 | let tid = null;
178 | let debounceTime = 100; // default
179 | monacoEditor.onDidChangeModelContent(() => {
180 | clearTimeout(tid);
181 | tid = setTimeout(() => {
182 | monacoJSXHighlighter.highlightCode();
183 | },
184 | debounceTime,
185 | );
186 | });
187 |
188 | // You can do the higlighting directly at anytime
189 | monacoJSXHighlighter.highlightCode();
190 | // or customize its behavior by adding custom highlighting after the JSX highlighting
191 | const afterHighlight = (
192 | ast // the ast generate by Babel
193 | ) => {
194 | //... your customization code, check Babel for more info about AST types
195 | //optional: array with the decorators created by the highlighter, push your decorator ids to this array
196 | monacoJSXHighlighter.JSXDecoratorIds.push(...yourdecoratorsIds);
197 | };
198 |
199 | monacoJSXHighlighter.highlightCode(
200 | afterHighlight, //default: ast=>ast
201 | onError, // default: error=>console.error(error)
202 | getAstPromise, // default: parse(monacoEditor.getValue())
203 | onParseErrors, // default: error=>error
204 | );
205 | ```
206 |
207 | Additionally, you can add JSX commenting to your monaco editor with
208 | `monacoJSXHighlighter.addJSXCommentCommand()`:
209 | comments in JSX children will result in `{/*...*/}` instead of `//...`. It mimics the commenting behavior of
210 | the [WebStorm IDE](https://www.jetbrains.com/webstorm/).
211 |
212 | Follow this code to find out other perks:
213 |
214 | ```js
215 | // Optional: Disable JSX commenting when needed (e.g. toggling, unmounting, pausing)
216 | const commentDisposeFunc = monacoJSXHighlighter.addJSXCommentCommand();
217 | commentDisposeFunc(); // if you need to
218 | ```
219 |
220 | ### Creating Monaco compatible ranges from Babel
221 |
222 | ```js
223 | import {configureLocToMonacoRange} from 'monaco-jsx-highlighter';
224 | // locToMonacoRange: converts Babel locations to Monaco Ranges
225 | const locToMonacoRange = configureLocToMonacoRange(monaco);
226 | const monacoRange = locToMonacoRange(babelAstNode.loc);
227 | ```
228 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-midnight
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monaco-jsx-highlighter",
3 | "version": "2.7.77",
4 | "description": "An extensible library to highlight JSX syntax in the Monaco Editor using Babel.",
5 | "author": "luminaxster",
6 | "license": "MIT",
7 | "bugs": {
8 | "url": "https://github.com/luminaxster/syntax-highlighter/issues"
9 | },
10 | "homepage": "https://luminaxster.github.io/syntax-highlighter/",
11 | "main": "dist/cjs/monaco-jsx-highlighter.js",
12 | "module": "dist/es/monaco-jsx-highlighter.js",
13 | "scripts": {
14 | "build-dev": "webpack --env development",
15 | "build": "rollup -c --environment INCLUDE_DEPS,BUILD:production --bundleConfigAsCjs",
16 | "clean": "rimraf dist",
17 | "lint": "eslint src --fix",
18 | "prerelease": "npm run lint && npm run test && npm run clean && npm run build",
19 | "release": "npm publish . --access public",
20 | "test": "echo \"Warning: no test specified\" && exit 0"
21 | },
22 | "peerDependencies": {
23 | "@babel/parser": "6.x || 7.x",
24 | "@babel/traverse": "6.x || 7.x",
25 | "monaco-editor": ">=0.21"
26 | },
27 | "devDependencies": {
28 | "@babel/cli": "^7.13.0",
29 | "@babel/core": "^7.13.8",
30 | "@babel/parser": "^7.13.9",
31 | "@babel/preset-env": "^7.13.9",
32 | "@babel/preset-react": "^7.12.13",
33 | "@babel/traverse": "^7.13.0",
34 | "@typescript-eslint/eslint-plugin": "^6.19.1",
35 | "babel-eslint": "^10.1.0",
36 | "babel-loader": "^8.2.2",
37 | "css-loader": "^6.9.1",
38 | "eslint": "^8.56.0",
39 | "eslint-config-standard-with-typescript": "^43.0.1",
40 | "eslint-plugin-import": "^2.29.1",
41 | "eslint-plugin-n": "^16.6.2",
42 | "eslint-plugin-node": "^11.1.0",
43 | "eslint-plugin-promise": "^6.1.1",
44 | "eslint-plugin-react": "^7.33.2",
45 | "monaco-editor": "^0.23.0",
46 | "react-refresh": "^0.14.0",
47 | "rimraf": "^3.0.2",
48 | "rollup": "^4.9.6",
49 | "rollup-plugin-babel": "^5.0.0-alpha.2",
50 | "rollup-plugin-cleanup": "^3.2.1",
51 | "rollup-plugin-commonjs": "^10.1.0",
52 | "rollup-plugin-node-resolve": "^5.2.0",
53 | "rollup-plugin-postcss": "^4.0.2",
54 | "rollup-plugin-terser": "^7.0.2",
55 | "schema-utils": "^4.2.0",
56 | "style-loader": "^3.3.4",
57 | "typescript": "^5.3.3",
58 | "url-loader": "^4.1.1",
59 | "webpack": "^5.90.0",
60 | "webpack-cli": "^5.1.4"
61 | },
62 | "repository": {
63 | "type": "git",
64 | "url": "git+https://github.com/luminaxster/syntax-highlighter.git"
65 | },
66 | "keywords": [
67 | "monaco",
68 | "editor",
69 | "babel",
70 | "jsx",
71 | "syntax",
72 | "color",
73 | "coding",
74 | "highlighting"
75 | ],
76 | "engines": {
77 | "node": ">=10",
78 | "npm": ">=7"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Adapted from https://github.com/reduxjs/redux/blob/master/rollup.config.js
3 | * Copyright (c) 2015-present Dan Abramov
4 | */
5 |
6 | import nodeResolve from 'rollup-plugin-node-resolve';
7 | import babel from 'rollup-plugin-babel';
8 | import commonjs from 'rollup-plugin-commonjs';
9 | import cleanup from 'rollup-plugin-cleanup';
10 | import postcss from "rollup-plugin-postcss";
11 | import {terser} from 'rollup-plugin-terser';
12 |
13 | const isProduction = true;
14 |
15 | export default [
16 | {
17 | input: 'src/index.js',
18 | output:
19 | [
20 | {
21 | file: 'dist/cjs/monaco-jsx-highlighter.js',
22 | format: 'cjs',
23 | indent: false,
24 | sourcemap: true,
25 | exports: 'named',
26 | },
27 | {
28 | file: 'dist/cjs/monaco-jsx-highlighter.min.js',
29 | format: 'iife',
30 | name: 'version',
31 | exports: 'named',
32 | plugins: [terser()]
33 | }
34 | ],
35 | external: ['prop-types', 'react'],
36 | plugins: [
37 | postcss({minimize: isProduction}),
38 | nodeResolve({
39 | mainFields: ['module', 'jsnext:main', 'main'],
40 | }),
41 | commonjs({
42 | include: 'node_modules/**',
43 | }),
44 | babel({
45 | babelHelpers: 'bundled'
46 | }),
47 | cleanup()
48 | ],
49 | },
50 | {
51 | input: 'src/index.js',
52 | output:
53 | [
54 | {
55 | file: 'dist/es/monaco-jsx-highlighter.js',
56 | format: 'es',
57 | indent: false,
58 | sourcemap: true,
59 | exports: 'named',
60 | },
61 | {
62 | file: 'dist/es/monaco-jsx-highlighter.min.js',
63 | format: 'iife',
64 | name: 'version',
65 | exports: 'named',
66 | plugins: [terser()]
67 | }
68 | ],
69 | external: ['prop-types', 'react'],
70 | plugins: [
71 | postcss({minimize: isProduction}),
72 | nodeResolve({
73 | mainFields: ['module', 'jsnext:main', 'main'],
74 | }),
75 | commonjs({
76 | include: 'node_modules/**',
77 | }),
78 | babel({
79 | babelHelpers: 'bundled'
80 | }),
81 | cleanup()
82 | ],
83 | },
84 | {
85 | input: 'src/index.js',
86 | output:
87 | [
88 | {
89 | file: 'dist/umd/monaco-jsx-highlighter.js',
90 | format: 'umd',
91 | name: 'MonacoJSXHighlighter',
92 | indent: false,
93 | exports: 'named',
94 | sourcemap: true,
95 | },
96 | {
97 | file: 'dist/umd/monaco-jsx-highlighter.min.js',
98 | format: 'iife',
99 | name: 'version',
100 | exports: 'named',
101 | plugins: [terser()]
102 | }
103 | ],
104 | plugins: [
105 | postcss({minimize: isProduction}),
106 | nodeResolve({
107 | mainFields: ['module', 'jsnext:main', 'main'],
108 | }),
109 | babel({
110 | babelHelpers: 'bundled',
111 | exclude: 'node_modules/**',
112 | }),
113 | commonjs({
114 | namedExports: {
115 | 'node_modules/react/index.js': [
116 | 'useContext',
117 | 'useLayoutEffect',
118 | 'useCallback',
119 | 'useState',
120 | 'useMemo',
121 | 'createContext',
122 | 'memo',
123 | 'Children',
124 | ],
125 | },
126 | }),
127 | cleanup()
128 | ],
129 | },
130 | ];
131 |
--------------------------------------------------------------------------------
/src/MonacoJSXHighlighter.css:
--------------------------------------------------------------------------------
1 | .JSXElement.JSXIdentifier{
2 | color: royalblue;
3 | }
4 |
5 | .JSXElement.JSXBracket {
6 | color: darkorange;
7 | }
8 |
9 | .JSXElement.JSXText {
10 | color: darkgoldenrod;
11 | }
12 |
13 | .JSXElement.JSXGlyph {
14 | background: cyan;
15 | opacity: 0.25;
16 | }
17 |
18 | .JSXOpeningFragment.JSXBracket {
19 | color: darkorange;
20 | font-weight: bold;
21 | }
22 |
23 | .JSXClosingFragment.JSXBracket {
24 | color: darkorange;
25 | font-weight: bold;
26 | }
27 |
28 | .JSXOpeningElement.JSXBracket {
29 | color: darkorange;
30 | font-weight: bold;
31 | }
32 |
33 | .JSXOpeningElement.JSXIdentifier {
34 | color: royalblue;
35 | }
36 |
37 | .JSXClosingElement.JSXBracket {
38 | color: darkorange;
39 | font-weight: lighter;
40 | }
41 |
42 | .JSXClosingElement.JSXIdentifier {
43 | color: royalblue;
44 | font-weight: lighter;
45 | }
46 |
47 | .JSXAttribute.JSXIdentifier {
48 | color: steelblue;
49 | }
50 |
51 | .JSXExpressionContainer.JSXBracket {
52 | color: darkorange;
53 | }
54 |
55 | .JSXSpreadChild.JSXBracket {
56 | color: darkorange;
57 | }
58 |
59 | .JSXSpreadAttribute.JSXBracket {
60 | color: darkorange;
61 | }
62 |
--------------------------------------------------------------------------------
/src/MonacoJSXHighlighter.js:
--------------------------------------------------------------------------------
1 | import './MonacoJSXHighlighter.css'
2 |
3 | let monaco = null
4 |
5 | const defaultOptions = {
6 | parser: 'babel',
7 | isHighlightGlyph: false,
8 | iShowHover: false,
9 | isUseSeparateElementStyles: false,
10 | isThrowJSXParseErrors: false
11 | }
12 |
13 | export const configureLocToMonacoRange = (
14 | _monaco = monaco, parser = 'babel'
15 | ) => {
16 | switch (parser) {
17 | case 'babel':
18 | default:
19 | return (
20 | loc,
21 | startLineOffset = 0,
22 | startColumnOffset = 0,
23 | endLineOffset = 0,
24 | endColumnOffset = 0
25 | ) => {
26 | if (!loc?.start) {
27 | return new _monaco.Range(1, 1, 1, 1)
28 | }
29 | return new _monaco.Range(
30 | startLineOffset + loc.start.line,
31 | startColumnOffset + loc.start.column + 1,
32 | endLineOffset + loc.end
33 | ? loc.end.line
34 | : loc.start.line,
35 | endColumnOffset + loc.end
36 | ? loc.end.column + 1
37 | : loc.start.column + 1
38 | )
39 | }
40 | }
41 | }
42 |
43 | export function makeJSXTraverse() {
44 | const jsxExpressions = []
45 | const jsxTraverse = (path) => {
46 | if (path.type.toUpperCase().includes('JSX')) {
47 | jsxExpressions.push(path)
48 | }
49 | }
50 |
51 | const find = (type) => {
52 | return jsxExpressions.filter(p => p.type === type)
53 | }
54 |
55 | const findJSXElements = () => find('JSXElement')
56 |
57 | return {
58 | jsxExpressions,
59 | find,
60 | findJSXElements,
61 | jsxTraverse
62 | }
63 | }
64 |
65 | export function prepareOptions(
66 | path,
67 | jsxTypeOptions = {},
68 | highlighterOptions = {}
69 | ) {
70 | return highlighterOptions.iShowHover
71 | ? {...jsxTypeOptions, ...{hoverMessage: `(${path.type})`}}
72 | : jsxTypeOptions
73 | }
74 |
75 | export const HIGHLIGHT_TYPE = {
76 | ELEMENT: 'ELEMENT', // jsx elements
77 | ALL: 'ALL', // the whole node's location, e.g. identifier names
78 | IDENTIFIER: 'IDENTIFIER', // JSX identifiers
79 | EDGE: 'EDGE', // only the starting and ending characters in node's
80 | // location e.g. spread child or attribute, container expressions
81 | STYLE: 'STYLE' // for styling only, not used by node locations
82 | }
83 |
84 | export const HIGHLIGHT_MODE = {
85 | [HIGHLIGHT_TYPE.ELEMENT]: (
86 | path,
87 | jsxTypeOptions,
88 | decorators = [],
89 | highlighterOptions,
90 | locToMonacoRange
91 | ) => {
92 | const loc = path.node.loc
93 | const openingElement = path.node.openingElement
94 | let elementName = null
95 | if (openingElement) {
96 | elementName = openingElement.name.name
97 |
98 | const startLoc = {
99 | start: {...openingElement.loc.start},
100 | end: {...openingElement.name.loc.start}
101 | }
102 |
103 | const endLoc = {
104 | start: {...openingElement.loc.end},
105 | end: {...openingElement.loc.end}
106 | }
107 | endLoc.start.column--
108 |
109 | if (openingElement.selfClosing) {
110 | endLoc.start.column--
111 | }
112 |
113 | decorators.push({
114 | range: locToMonacoRange(startLoc),
115 | options: highlighterOptions.isUseSeparateElementStyles
116 | ? JSXTypes.JSXBracket.openingElementOptions
117 | : JSXTypes.JSXBracket.options
118 | })
119 |
120 | decorators.push({
121 | range: locToMonacoRange(endLoc),
122 | options: highlighterOptions.isUseSeparateElementStyles
123 | ? JSXTypes.JSXBracket.openingElementOptions
124 | : JSXTypes.JSXBracket.options
125 | })
126 | }
127 |
128 | const closingElement = path.node.closingElement
129 | if (closingElement) {
130 | const startLoc = {
131 | start: {...closingElement.loc.start},
132 | end: {...closingElement.name.loc.start}
133 | }
134 |
135 | const endLoc = {
136 | start: {...closingElement.loc.end},
137 | end: {...closingElement.loc.end}
138 | }
139 | endLoc.start.column--
140 |
141 | decorators.push({
142 | range: locToMonacoRange(startLoc),
143 | options: highlighterOptions.isUseSeparateElementStyles
144 | ? JSXTypes.JSXBracket.closingElementOptions
145 | : JSXTypes.JSXBracket.options
146 | })
147 | decorators.push({
148 | range: locToMonacoRange(endLoc),
149 | options: highlighterOptions.isUseSeparateElementStyles
150 | ? JSXTypes.JSXBracket.closingElementOptions
151 | : JSXTypes.JSXBracket.options
152 | })
153 | }
154 |
155 | highlighterOptions.isHighlightGlyph && decorators.push({
156 | range: locToMonacoRange(loc),
157 | options: JSXTypes.JSXElement.options(elementName)
158 | })
159 | },
160 | [HIGHLIGHT_TYPE.ALL]: (
161 | path,
162 | jsxTypeOptions,
163 | decorators = [],
164 | highlighterOptions,
165 | locToMonacoRange
166 | ) => {
167 | const loc = {
168 | start: {...path.node.loc.start},
169 | end: {...path.node.loc.end}
170 | }
171 |
172 | if (path.key === 'object') {
173 | loc.end = {...path.container.property.loc.start}
174 | }
175 | locToMonacoRange && decorators.push({
176 | range: locToMonacoRange(loc),
177 | options: prepareOptions(path, jsxTypeOptions, highlighterOptions)
178 | })
179 | return decorators
180 | },
181 | [HIGHLIGHT_TYPE.IDENTIFIER]: (
182 | path,
183 | jsxTypeOptions = {},
184 | decorators = [],
185 | highlighterOptions = {},
186 | locToMonacoRange
187 | ) => {
188 | if (
189 | path.key === 'object' ||
190 | path.key === 'property' ||
191 | path.key === 'name' ||
192 | path.key === 'namespace'
193 | ) {
194 | HIGHLIGHT_MODE[HIGHLIGHT_TYPE.ALL](
195 | path,
196 | path.parentPath?.isJSXAttribute()
197 | ? JSXTypes.JSXAttribute.options
198 | : jsxTypeOptions,
199 | decorators,
200 | highlighterOptions,
201 | locToMonacoRange
202 | )
203 | }
204 | return decorators
205 | },
206 | [HIGHLIGHT_TYPE.EDGE]: (
207 | path,
208 | jsxTypeOptions,
209 | decorators = [],
210 | highlighterOptions,
211 | locToMonacoRange
212 | ) => {
213 | const options = prepareOptions(path, jsxTypeOptions, highlighterOptions)
214 |
215 | const loc = path.node.loc
216 | const innerLocKey =
217 | path.isJSXSpreadChild()
218 | ? 'expression'
219 | : path.isJSXSpreadAttribute() ? 'argument' : null
220 |
221 | let innerLoc = null
222 |
223 | if (innerLocKey) {
224 | const innerNode = path.node[innerLocKey]
225 | innerLoc = {
226 | start: {...innerNode.loc.start},
227 | end: {...innerNode.loc.end}
228 | }
229 | if (innerNode.extra?.parenthesized) {
230 | innerLoc.start.column--
231 | innerLoc.end.column++
232 | }
233 | } else {
234 | innerLoc = {start: {...loc.start}, end: {...loc.end}}
235 | innerLoc.start.column++
236 | innerLoc.end.column--
237 | }
238 |
239 | const startEdgeLoc = {start: {...loc.start}, end: {...innerLoc.start}}
240 |
241 | const endEdgeLoc = {start: {...innerLoc.end}, end: {...loc.end}}
242 |
243 | decorators.push({
244 | range: locToMonacoRange(startEdgeLoc),
245 | options
246 | })
247 | decorators.push({
248 | range: locToMonacoRange(endEdgeLoc),
249 | options
250 | })
251 |
252 | return decorators
253 | },
254 | [HIGHLIGHT_TYPE.STYLE]: () => [] // noop
255 | }
256 |
257 | export const JSXTypes = {
258 | JSXIdentifier: {
259 | highlightScope: HIGHLIGHT_TYPE.IDENTIFIER,
260 | options: {
261 | inlineClassName: 'JSXElement.JSXIdentifier'
262 | }
263 | },
264 | JSXOpeningFragment: {
265 | highlightScope: HIGHLIGHT_TYPE.ALL,
266 | options: {
267 | inlineClassName: 'JSXOpeningFragment.JSXBracket'
268 | }
269 | },
270 | JSXClosingFragment: {
271 | highlightScope: HIGHLIGHT_TYPE.ALL,
272 | options: {
273 | inlineClassName: 'JSXClosingFragment.JSXBracket'
274 | }
275 | },
276 | JSXText: {
277 | highlightScope: HIGHLIGHT_TYPE.ALL,
278 | options: {
279 | inlineClassName: 'JSXElement.JSXText'
280 | }
281 | },
282 | JSXExpressionContainer: {
283 | highlightScope: HIGHLIGHT_TYPE.EDGE,
284 | options: {
285 | inlineClassName: 'JSXExpressionContainer.JSXBracket'
286 | }
287 | },
288 | JSXSpreadChild: {
289 | highlightScope: HIGHLIGHT_TYPE.EDGE,
290 | options: {
291 | inlineClassName: 'JSXSpreadChild.JSXBracket'
292 | }
293 | },
294 | JSXSpreadAttribute: {
295 | highlightScope: HIGHLIGHT_TYPE.EDGE,
296 | options: {
297 | inlineClassName: 'JSXSpreadAttribute.JSXBracket'
298 | }
299 | },
300 | JSXElement: {
301 | highlightScope: HIGHLIGHT_TYPE.STYLE,
302 | options: (elementName) => (
303 | {
304 | glyphMarginClassName: 'JSXElement.JSXGlyph',
305 | glyphMarginHoverMessage:
306 | `JSX Element${elementName ? ': ' + elementName : ''}`
307 | }
308 | )
309 | },
310 | JSXBracket: {
311 | highlightScope: HIGHLIGHT_TYPE.STYLE,
312 | options: {
313 | inlineClassName: 'JSXElement.JSXBracket'
314 | },
315 | openingElementOptions: {
316 | inlineClassName: 'JSXOpeningElement.JSXBracket'
317 | },
318 | closingElementOptions: {
319 | inlineClassName: 'JSXClosingElement.JSXBracket'
320 | }
321 | },
322 | JSXOpeningElement: {
323 | highlightScope: HIGHLIGHT_TYPE.STYLE,
324 | options: {
325 | inlineClassName: 'JSXOpeningElement.JSXIdentifier'
326 | }
327 | },
328 | JSXClosingElement: {
329 | highlightScope: HIGHLIGHT_TYPE.STYLE,
330 | options: {
331 | inlineClassName: 'JSXClosingElement.JSXIdentifier'
332 | }
333 | },
334 | JSXAttribute: {
335 | highlightScope: HIGHLIGHT_TYPE.STYLE,
336 | options: {
337 | inlineClassName: 'JSXAttribute.JSXIdentifier'
338 | }
339 | }
340 | }
341 |
342 | export const JSXCommentContexts = {
343 | JS: 'JS',
344 | JSX: 'JSX'
345 | }
346 |
347 | export const COMMENT_ACTION_ID = 'editor.action.commentLine'
348 |
349 | // thanks https://github.com/HaimCandiTech
350 | export const makeBabelParse = (parse, enableTsxHighlight) => {
351 | return (code, options = {}) => {
352 | return parse(
353 | code,
354 | {
355 | sourceType: 'module',
356 | plugins: ['jsx', ...(enableTsxHighlight ? ['typescript'] : [])],
357 | errorRecovery: true,
358 | ...options
359 | })
360 | }
361 | }
362 |
363 | class MonacoJSXHighlighter {
364 | constructor(
365 | globalMonaco,
366 | parse,
367 | traverse,
368 | monacoEditor,
369 | options = {}
370 | ) {
371 | monaco = globalMonaco;
372 | this.monaco = monaco;
373 | this.parse = parse;
374 | this.traverse = traverse;
375 | this.monacoEditor = monacoEditor;
376 | this.options = {...defaultOptions, ...options}
377 | this.locToMonacoRange = configureLocToMonacoRange(monaco, this.options.parser)
378 | //avoiding breaking changes form v1s
379 | this.highLightOnDidChangeModelContent = this.highlightOnDidChangeModelContent;
380 | this.bindMethods();
381 |
382 | this._isHighlightBoundToModelContentChanges = false
383 | this._isJSXCommentCommandActive = false
384 |
385 | this.resetState()
386 | }
387 |
388 | bindMethods() {
389 | const methods = [
390 | 'resetState', 'resetDeltaDecorations', 'getAstPromise', 'highlightOnDidChangeModelContent', 'highlight',
391 | 'highlightCode', 'createDecoratorsByType', 'createJSXElementDecorators', 'extractAllDecorators', 'jsxTraverseAst',
392 | 'getJSXContext', 'runJSXCommentContextAndAction', 'addJSXCommentCommand', 'highLightOnDidChangeModelContent'
393 | ]
394 | methods.forEach(method => this[method] = this[method].bind(this));
395 | }
396 |
397 | resetState() {
398 | this.prevEditorValue = null
399 | this.editorValue = null
400 | this.ast = null
401 | this.jsxManager = null
402 | }
403 |
404 | resetDeltaDecorations() {
405 | const model = this.monacoEditor?.getModel();
406 | if(model.deltaDecorations){
407 | this.JSXDecoratorIds = (model.deltaDecorations(
408 | this.JSXDecoratorIds ?? [],
409 | []
410 | )
411 | )
412 | }else{
413 | this.JSXDecoratorIds = [];
414 | }
415 | }
416 |
417 | async getAstPromise(forceUpdate) {
418 | return await new Promise((resolve) => {
419 | if (
420 | !!forceUpdate ||
421 | !this.editorValue ||
422 | this.editorValue !== this.prevEditorValue
423 | ) {
424 | this.prevEditorValue = this.editorValue
425 | this.editorValue = this.monacoEditor.getValue()
426 | try {
427 | this.ast = this.parse(this.editorValue)
428 | } catch (e) {
429 | if (
430 | e instanceof SyntaxError &&
431 | !e.message.includes('JSX')
432 | ) {
433 | this.resetState()
434 | throw e
435 | } else {
436 | if (this.options.isThrowJSXParseErrors) {
437 | throw e
438 | } else {
439 | resolve(this.ast)
440 | }
441 | }
442 | }
443 | }
444 | resolve(this.ast)
445 | })
446 | }
447 |
448 | highlightOnDidChangeModelContent(
449 | debounceTime = 100,
450 | afterHighlight = ast => ast,
451 | onHighlightError = error => {
452 | console.error(error)
453 | },
454 | getAstPromise,
455 | onParseAstError = error => {
456 | console.log(error)
457 | }
458 | ) {
459 | getAstPromise = getAstPromise ?? this.getAstPromise
460 | const highlightCallback = () => {
461 | this.highlightCode(
462 | afterHighlight,
463 | onHighlightError,
464 | getAstPromise,
465 | onParseAstError
466 | )
467 | }
468 |
469 | highlightCallback()
470 |
471 | const tid = null
472 |
473 | let highlighterDisposer = {
474 | onDidChangeModelContentDisposer:
475 | this.monacoEditor.onDidChangeModelContent(
476 | () => {
477 | clearTimeout(tid)
478 | setTimeout(
479 | highlightCallback,
480 | debounceTime
481 | )
482 | }),
483 | onDidChangeModelDisposer: this.monacoEditor.onDidChangeModel(
484 | () => {
485 | highlightCallback()
486 | })
487 | }
488 |
489 | highlighterDisposer.dispose = () => {
490 | highlighterDisposer.onDidChangeModelContentDisposer.dispose()
491 | highlighterDisposer.onDidChangeModelDisposer.dispose()
492 | }
493 |
494 | this._isHighlightBoundToModelContentChanges = true
495 |
496 | const onDispose = () => {
497 | clearTimeout(tid)
498 | this.resetState()
499 | this.resetDeltaDecorations()
500 | if (
501 | !this._isHighlightBoundToModelContentChanges
502 | ) {
503 | return
504 | }
505 | this._isHighlightBoundToModelContentChanges = false
506 | highlighterDisposer?.dispose()
507 | highlighterDisposer = null
508 | }
509 |
510 | this.monacoEditor.onDidDispose(() => {
511 | clearTimeout(tid)
512 | this.resetDeltaDecorations()
513 | highlighterDisposer = null
514 | this._isHighlightBoundToModelContentChanges = false
515 | })
516 | return onDispose
517 | }
518 |
519 | highlightCode(
520 | afterHighlight = ast => ast,
521 | onError = error => {
522 | console.error(error)
523 | },
524 | getAstPromise,
525 | onJsParserErrors = error => error
526 | ) {
527 | getAstPromise = getAstPromise ?? this.getAstPromise
528 | return (
529 | getAstPromise()
530 | .then(async ast => await this.highlight(ast))
531 | .catch(onJsParserErrors)
532 | )
533 | .then(afterHighlight)
534 | .catch(onError)
535 | }
536 |
537 | jsxTraverseAst(ast, traverse) {
538 | traverse = traverse ?? this.traverse;
539 |
540 | const jsxManager = makeJSXTraverse()
541 | traverse(ast, {enter: jsxManager.jsxTraverse})
542 | return jsxManager
543 | }
544 |
545 | async highlight(ast, jsxTraverseAst) {
546 | jsxTraverseAst = jsxTraverseAst ?? this.jsxTraverseAst;
547 |
548 | return await new Promise((resolve) => {
549 | if (ast) {
550 | this.jsxManager = jsxTraverseAst(ast)
551 | this.decorators = this.extractAllDecorators(this.jsxManager)
552 | }
553 | resolve(ast)
554 | })
555 | }
556 |
557 | createDecoratorsByType(
558 | jsxManager,
559 | jsxType,
560 | jsxTypeOptions,
561 | highlightScope,
562 | decorators = [],
563 | highlighterOptions,
564 | locToMonacoRange
565 | ) {
566 | highlighterOptions = highlighterOptions ?? this.options
567 | locToMonacoRange = locToMonacoRange ?? this.locToMonacoRange
568 | jsxManager?.find(jsxType).forEach(path => HIGHLIGHT_MODE[highlightScope](
569 | path,
570 | jsxTypeOptions,
571 | decorators,
572 | highlighterOptions,
573 | locToMonacoRange
574 | )
575 | )
576 |
577 | return decorators
578 | }
579 |
580 | createJSXElementDecorators(
581 | jsxManager,
582 | decorators = [],
583 | highlighterOptions,
584 | locToMonacoRange
585 | ) {
586 | highlighterOptions = highlighterOptions ?? this.options
587 | locToMonacoRange = locToMonacoRange ?? this.locToMonacoRange
588 | jsxManager?.findJSXElements().forEach(path => HIGHLIGHT_MODE.ELEMENT(
589 | path,
590 | null,
591 | decorators,
592 | highlighterOptions,
593 | locToMonacoRange
594 | ))
595 | return decorators
596 | }
597 |
598 | extractAllDecorators(jsxManager) {
599 | jsxManager = jsxManager ?? this.jsxManager
600 | const decorators = this.createJSXElementDecorators(jsxManager)
601 | for (const jsxType in JSXTypes) {
602 | this.createDecoratorsByType(
603 | jsxManager,
604 | jsxType,
605 | JSXTypes[jsxType].options,
606 | JSXTypes[jsxType].highlightScope,
607 | decorators
608 | )
609 | }
610 |
611 | this.JSXDecoratorIds =
612 | this.monacoEditor.getModel().deltaDecorations(
613 | this.JSXDecoratorIds ?? [],
614 | decorators
615 | )
616 | return decorators
617 | }
618 |
619 | getJSXContext(
620 | selection,
621 | ast,
622 | monacoEditor,
623 | locToMonacoRange,
624 | jsxTraverseAst
625 | ) {
626 | monacoEditor = monacoEditor ?? this.monacoEditor
627 | locToMonacoRange = locToMonacoRange ?? this.locToMonacoRange
628 | jsxTraverseAst = jsxTraverseAst ?? this.jsxTraverseAst
629 | let jsxManager = ast ? this.jsxManager : null
630 | if (!this._isHighlightBoundToModelContentChanges) {
631 | jsxManager = ast ? jsxTraverseAst(ast) : null
632 | }
633 |
634 | if (!jsxManager) {
635 | return JSXCommentContexts.JS
636 | }
637 |
638 | let startColumn =
639 | monacoEditor.getModel().getLineFirstNonWhitespaceColumn(
640 | selection.startLineNumber
641 | )
642 |
643 | const commentableRange = new monaco.Range(
644 | selection.startLineNumber,
645 | startColumn,
646 | selection.startLineNumber,
647 | startColumn
648 | )
649 |
650 | startColumn = startColumn ? startColumn - 1 : 0
651 | const containingRange = new monaco.Range(
652 | selection.startLineNumber,
653 | startColumn,
654 | selection.startLineNumber,
655 | startColumn
656 | )
657 |
658 | let minRange = null
659 | let minCommentableRange = null
660 | let path = null
661 | let commentablePath = null
662 |
663 | jsxManager.jsxExpressions.forEach(p => {
664 | const jsxRange = locToMonacoRange(p.node.loc)
665 | if ((p.key === 'name' || p.key === 'property') &&
666 | p.isJSXIdentifier() &&
667 | jsxRange.intersectRanges(commentableRange)) {
668 | if (
669 | !minCommentableRange ||
670 | minCommentableRange.containsRange(jsxRange)
671 | ) {
672 | minCommentableRange = jsxRange
673 | commentablePath = p
674 | }
675 | }
676 | if (jsxRange.intersectRanges(containingRange)) {
677 | if (!minRange || minRange.containsRange(jsxRange)) {
678 | minRange = jsxRange
679 | path = p
680 | }
681 | }
682 | })
683 |
684 | if (!path || path.isJSXExpressionContainer() || commentablePath) {
685 | return JSXCommentContexts.JS
686 | } else {
687 | return JSXCommentContexts.JSX
688 | }
689 | }
690 |
691 | async runJSXCommentContextAndAction(
692 | selection,
693 | getAstPromise,
694 | onParseErrors = error => error,
695 | editor,
696 | runJsxCommentAction
697 | ) {
698 | getAstPromise = getAstPromise ?? this.getAstPromise
699 | editor = editor ?? this.monacoEditor
700 |
701 | return await new Promise((resolve) => {
702 | if (this._isHighlightBoundToModelContentChanges) {
703 | resolve(
704 | runJsxCommentAction(
705 | this.getJSXContext(selection, this.ast, editor)
706 | )
707 | )
708 | } else {
709 | getAstPromise().then(ast => {
710 | resolve(
711 | runJsxCommentAction(
712 | this.getJSXContext(selection, ast, editor)
713 | )
714 | )
715 | })
716 | .catch(
717 | (error) => resolve(
718 | runJsxCommentAction(
719 | this.getJSXContext(selection, null, editor)
720 | )
721 | ) ?? onParseErrors(error)
722 | )
723 | }
724 | }
725 | ).catch(error => (
726 | runJsxCommentAction(
727 | this.getJSXContext(selection, null, editor)) ??
728 | onParseErrors(error)
729 | )
730 | )
731 | }
732 |
733 | addJSXCommentCommand(
734 | getAstPromise,
735 | onParseErrors = error => error,
736 | editor
737 | ) {
738 | getAstPromise = getAstPromise ?? this.getAstPromise
739 | editor = editor ?? this.monacoEditor
740 |
741 | if (this._editorCommandId) {
742 | this._isJSXCommentCommandActive = true
743 | return this.editorCommandOnDispose
744 | }
745 |
746 | this._editorCommandId = editor.addCommand(
747 | monaco.KeyMod.CtrlCmd | monaco.KeyCode.US_SLASH,
748 | () => {
749 | if (!this._isJSXCommentCommandActive) {
750 | editor.getAction(COMMENT_ACTION_ID).run()
751 | return
752 | }
753 | const selection = editor.getSelection()
754 | const model = editor.getModel()
755 |
756 | const jsCommentRange = new monaco.Range(
757 | selection.startLineNumber,
758 | model.getLineFirstNonWhitespaceColumn(selection.startLineNumber),
759 | selection.startLineNumber,
760 | model.getLineMaxColumn(selection.startLineNumber)
761 | )
762 | const jsCommentText = model.getValueInRange(jsCommentRange)
763 |
764 | if (jsCommentText.match(/^\s*\/[/*]/)) {
765 | editor.getAction(COMMENT_ACTION_ID).run()
766 | this.resetState()
767 | return
768 | }
769 |
770 | const runJsxCommentAction = (commentContext) => {
771 | let isUnCommentAction = true
772 | const commentsData = []
773 |
774 | for (let i = selection.startLineNumber;
775 | i <= selection.endLineNumber;
776 | i++) {
777 | const commentRange = new monaco.Range(
778 | i,
779 | model.getLineFirstNonWhitespaceColumn(i),
780 | i,
781 | model.getLineMaxColumn(i)
782 | )
783 |
784 | const commentText = model.getValueInRange(commentRange)
785 |
786 | commentsData.push({
787 | commentRange,
788 | commentText
789 | })
790 |
791 | isUnCommentAction = isUnCommentAction &&
792 | !!commentText.match(/{\/\*/)
793 | }
794 |
795 | if (commentContext !== JSXCommentContexts.JSX &&
796 | !isUnCommentAction) {
797 | editor.getAction(COMMENT_ACTION_ID).run()
798 | this.resetState()
799 | return
800 | }
801 |
802 | const editOperations = []
803 | let commentsDataIndex = 0
804 |
805 | for (let i = selection.startLineNumber;
806 | i <= selection.endLineNumber;
807 | i++) {
808 | let {
809 | commentText,
810 | commentRange
811 | } = commentsData[commentsDataIndex++]
812 |
813 | if (isUnCommentAction) {
814 | commentText = commentText.replace(/{\/\*/, '')
815 | commentText = commentText.replace(/\*\/}/, '')
816 | } else {
817 | commentText = `{/*${commentText}*/}`
818 | }
819 |
820 | editOperations.push({
821 | identifier: {major: 1, minor: 1},
822 | range: commentRange,
823 | text: commentText,
824 | forceMoveMarkers: true
825 | })
826 | }
827 | ;(editOperations.length > 0) &&
828 | editor.executeEdits(this._editorCommandId, editOperations)
829 | }
830 |
831 | this.runJSXCommentContextAndAction(
832 | selection,
833 | getAstPromise,
834 | onParseErrors,
835 | editor,
836 | runJsxCommentAction
837 | ).catch(onParseErrors)
838 | })
839 |
840 | this.editorCommandOnDispose = () => {
841 | this._isJSXCommentCommandActive = false
842 | }
843 |
844 | this._isJSXCommentCommandActive = true
845 |
846 | editor.onDidDispose(this.editorCommandOnDispose)
847 |
848 | return this.editorCommandOnDispose
849 | }
850 | }
851 |
852 | // "standard-with-typescript",
853 | // "plugin:react/recommended"
854 |
855 | export default MonacoJSXHighlighter
856 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { default as MonacoJSXHighlighter } from './MonacoJSXHighlighter'
2 | export * from './MonacoJSXHighlighter'
3 | export default MonacoJSXHighlighter
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
26 |
27 | /* Modules */
28 | "module": "commonjs", /* Specify what module code is generated. */
29 | // "rootDir": "./", /* Specify the root folder within your source files. */
30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
42 | // "resolveJsonModule": true, /* Enable importing .json files. */
43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
45 |
46 | /* JavaScript Support */
47 | "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
50 |
51 | /* Emit */
52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
58 | // "outDir": "./", /* Specify an output folder for all emitted files. */
59 | // "removeComments": true, /* Disable emitting comments. */
60 | // "noEmit": true, /* Disable emitting files from a compilation. */
61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
68 | // "newLine": "crlf", /* Set the newline character for emitting files. */
69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
75 |
76 | /* Interop Constraints */
77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
83 |
84 | /* Type Checking */
85 | "strict": true, /* Enable all strict type-checking options. */
86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
104 |
105 | /* Completeness */
106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | // now using rollup
2 | const path = require('path');
3 |
4 | module.exports = function (env) {
5 | const devMode = !env.production;
6 |
7 | return {
8 | mode: devMode ? 'development' : 'production',
9 | entry: path.resolve(__dirname, 'src/index.js'),
10 | output: {
11 | path: path.resolve(__dirname, 'build'),
12 | publicPath: 'build/',
13 | filename: 'monaco-jsx-highlighter.js',
14 | sourceMapFilename: 'monaco-jsx-highlighter.map',
15 | library: 'MonacoJSXHighlighter',
16 | libraryTarget: 'umd',
17 | },
18 | module: {
19 | rules: [
20 | {
21 | test: /\.(js|jsx)$/i,
22 | exclude: /node_modules/,
23 | use: {
24 | loader: "babel-loader",
25 | options: {
26 | presets: [
27 | '@babel/preset-env',
28 | '@babel/preset-react',
29 | ]
30 | }
31 | }
32 | },
33 | {
34 | test: /\.css$/i,
35 | exclude: /node_modules/,
36 | use: [
37 | 'style-loader',
38 | 'css-loader'
39 | ]
40 | },
41 | {
42 | test: /\.(png|jpg|gif)$/i,
43 | exclude: /node_modules/,
44 | use: {
45 | loader: 'url-loader',
46 | options: {
47 | limit: 8192
48 | }
49 | }
50 | }
51 | ],
52 | },
53 | resolve: {
54 | extensions: ['.js']
55 | },
56 | }
57 | };
58 |
--------------------------------------------------------------------------------