@lexical/code-shiki 0.44.1-nightly.20260519.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,14 @@
8
8
  import type { LexicalEditor, LexicalNode } from 'lexical';
9
9
  import { CodeNode } from '@lexical/code-core';
10
10
  export interface Tokenizer {
11
- defaultLanguage: string;
11
+ /**
12
+ * Language to fall back to when a {@link CodeNode} doesn't carry one.
13
+ * Set to `null` to opt out of the implicit fallback — code blocks
14
+ * without a language stay untouched (no `data-language` attribute, no
15
+ * syntax highlighting) so a markdown round-trip can preserve ``` with
16
+ * no info string.
17
+ */
18
+ defaultLanguage: string | null;
12
19
  defaultTheme: string;
13
20
  $tokenize: (this: Tokenizer, codeNode: CodeNode, language?: string) => LexicalNode[];
14
21
  }
@@ -181,7 +181,8 @@ function mapTokensToLexicalStructure(tokens, diff) {
181
181
  const DEFAULT_CODE_THEME = 'one-light';
182
182
  const ShikiTokenizer = {
183
183
  $tokenize(codeNode, language) {
184
- return $getHighlightNodes(codeNode, language || this.defaultLanguage);
184
+ const lang = language || this.defaultLanguage;
185
+ return lang === null ? codeCore.$plainifyCodeContent(codeNode.getTextContent()) : $getHighlightNodes(codeNode, lang);
185
186
  },
186
187
  defaultLanguage: codeCore.DEFAULT_CODE_LANGUAGE,
187
188
  defaultTheme: DEFAULT_CODE_THEME
@@ -227,9 +228,12 @@ function $codeNodeTransform(editor, tokenizer, transformState, node) {
227
228
  nodesCurrentlyHighlighting
228
229
  } = transformState;
229
230
 
230
- // When new code block inserted it might not have language selected
231
+ // When new code block inserted it might not have language selected.
232
+ // Tokenizers configured with `defaultLanguage: null` opt out of the
233
+ // implicit fallback — leave the node unset and skip highlighting so
234
+ // markdown round-trips ``` (no info string) without injecting one.
231
235
  let language = node.getLanguage();
232
- if (!language) {
236
+ if (!language && tokenizer.defaultLanguage !== null) {
233
237
  language = tokenizer.defaultLanguage;
234
238
  node.setLanguage(language);
235
239
  }
@@ -243,20 +247,31 @@ function $codeNodeTransform(editor, tokenizer, transformState, node) {
243
247
  let inFlight = false;
244
248
  if (!isCodeThemeLoaded(theme)) {
245
249
  loadCodeTheme(theme, editor, nodeKey);
246
- inFlight = true;
250
+ // Only the highlight path (a resolved language) consumes the theme. With
251
+ // no language the text is plainified, which needs no theme, so don't defer
252
+ // the split on a theme load that won't be used — otherwise a code block
253
+ // with `defaultLanguage: null` stays an unsplit TextNode until the theme
254
+ // happens to finish loading.
255
+ if (language) {
256
+ inFlight = true;
257
+ }
247
258
  }
248
259
 
249
260
  // dynamic import of languages
250
- if (isCodeLanguageLoaded(language)) {
251
- if (!node.getIsSyntaxHighlightSupported()) {
252
- node.setIsSyntaxHighlightSupported(true);
253
- }
254
- } else {
255
- if (node.getIsSyntaxHighlightSupported()) {
256
- node.setIsSyntaxHighlightSupported(false);
261
+ if (language) {
262
+ if (isCodeLanguageLoaded(language)) {
263
+ if (!node.getIsSyntaxHighlightSupported()) {
264
+ node.setIsSyntaxHighlightSupported(true);
265
+ }
266
+ } else {
267
+ if (node.getIsSyntaxHighlightSupported()) {
268
+ node.setIsSyntaxHighlightSupported(false);
269
+ }
270
+ loadCodeLanguage(language, editor, nodeKey);
271
+ inFlight = true;
257
272
  }
258
- loadCodeLanguage(language, editor, nodeKey);
259
- inFlight = true;
273
+ } else if (node.getIsSyntaxHighlightSupported()) {
274
+ node.setIsSyntaxHighlightSupported(false);
260
275
  }
261
276
  if (inFlight) {
262
277
  return;
@@ -278,7 +293,7 @@ function $codeNodeTransform(editor, tokenizer, transformState, node) {
278
293
  return false;
279
294
  }
280
295
  const lang = currentNode.getLanguage() || tokenizer.defaultLanguage;
281
- const highlightNodes = tokenizer.$tokenize(currentNode, lang);
296
+ const highlightNodes = tokenizer.$tokenize(currentNode, lang ?? undefined);
282
297
  const diffRange = getDiffRange(currentNode.getChildren(), highlightNodes);
283
298
  const {
284
299
  from,
@@ -6,7 +6,7 @@
6
6
  *
7
7
  */
8
8
 
9
- import { $isCodeNode, $createCodeHighlightNode, CodeExtension, CodeIndentExtension, DEFAULT_CODE_LANGUAGE, CodeNode, CodeHighlightNode, $isCodeHighlightNode, registerCodeIndentation } from '@lexical/code-core';
9
+ import { $isCodeNode, $createCodeHighlightNode, CodeExtension, CodeIndentExtension, DEFAULT_CODE_LANGUAGE, CodeNode, CodeHighlightNode, $plainifyCodeContent, $isCodeHighlightNode, registerCodeIndentation } from '@lexical/code-core';
10
10
  import { effect, namedSignals } from '@lexical/extension';
11
11
  import { $getNodeByKey, $createLineBreakNode, $createTabNode, defineExtension, safeCast, TextNode, mergeRegister, $isLineBreakNode, $onUpdate, $createTextNode, $getSelection, $isRangeSelection, $isTextNode, $isTabNode } from 'lexical';
12
12
  import { createHighlighterCoreSync, isSpecialTheme, isSpecialLang, stringifyTokenStyle, getTokenStyleObject } from '@shikijs/core';
@@ -179,7 +179,8 @@ function mapTokensToLexicalStructure(tokens, diff) {
179
179
  const DEFAULT_CODE_THEME = 'one-light';
180
180
  const ShikiTokenizer = {
181
181
  $tokenize(codeNode, language) {
182
- return $getHighlightNodes(codeNode, language || this.defaultLanguage);
182
+ const lang = language || this.defaultLanguage;
183
+ return lang === null ? $plainifyCodeContent(codeNode.getTextContent()) : $getHighlightNodes(codeNode, lang);
183
184
  },
184
185
  defaultLanguage: DEFAULT_CODE_LANGUAGE,
185
186
  defaultTheme: DEFAULT_CODE_THEME
@@ -225,9 +226,12 @@ function $codeNodeTransform(editor, tokenizer, transformState, node) {
225
226
  nodesCurrentlyHighlighting
226
227
  } = transformState;
227
228
 
228
- // When new code block inserted it might not have language selected
229
+ // When new code block inserted it might not have language selected.
230
+ // Tokenizers configured with `defaultLanguage: null` opt out of the
231
+ // implicit fallback — leave the node unset and skip highlighting so
232
+ // markdown round-trips ``` (no info string) without injecting one.
229
233
  let language = node.getLanguage();
230
- if (!language) {
234
+ if (!language && tokenizer.defaultLanguage !== null) {
231
235
  language = tokenizer.defaultLanguage;
232
236
  node.setLanguage(language);
233
237
  }
@@ -241,20 +245,31 @@ function $codeNodeTransform(editor, tokenizer, transformState, node) {
241
245
  let inFlight = false;
242
246
  if (!isCodeThemeLoaded(theme)) {
243
247
  loadCodeTheme(theme, editor, nodeKey);
244
- inFlight = true;
248
+ // Only the highlight path (a resolved language) consumes the theme. With
249
+ // no language the text is plainified, which needs no theme, so don't defer
250
+ // the split on a theme load that won't be used — otherwise a code block
251
+ // with `defaultLanguage: null` stays an unsplit TextNode until the theme
252
+ // happens to finish loading.
253
+ if (language) {
254
+ inFlight = true;
255
+ }
245
256
  }
246
257
 
247
258
  // dynamic import of languages
248
- if (isCodeLanguageLoaded(language)) {
249
- if (!node.getIsSyntaxHighlightSupported()) {
250
- node.setIsSyntaxHighlightSupported(true);
251
- }
252
- } else {
253
- if (node.getIsSyntaxHighlightSupported()) {
254
- node.setIsSyntaxHighlightSupported(false);
259
+ if (language) {
260
+ if (isCodeLanguageLoaded(language)) {
261
+ if (!node.getIsSyntaxHighlightSupported()) {
262
+ node.setIsSyntaxHighlightSupported(true);
263
+ }
264
+ } else {
265
+ if (node.getIsSyntaxHighlightSupported()) {
266
+ node.setIsSyntaxHighlightSupported(false);
267
+ }
268
+ loadCodeLanguage(language, editor, nodeKey);
269
+ inFlight = true;
255
270
  }
256
- loadCodeLanguage(language, editor, nodeKey);
257
- inFlight = true;
271
+ } else if (node.getIsSyntaxHighlightSupported()) {
272
+ node.setIsSyntaxHighlightSupported(false);
258
273
  }
259
274
  if (inFlight) {
260
275
  return;
@@ -276,7 +291,7 @@ function $codeNodeTransform(editor, tokenizer, transformState, node) {
276
291
  return false;
277
292
  }
278
293
  const lang = currentNode.getLanguage() || tokenizer.defaultLanguage;
279
- const highlightNodes = tokenizer.$tokenize(currentNode, lang);
294
+ const highlightNodes = tokenizer.$tokenize(currentNode, lang ?? undefined);
280
295
  const diffRange = getDiffRange(currentNode.getChildren(), highlightNodes);
281
296
  const {
282
297
  from,
@@ -19,7 +19,7 @@ import type {CodeNode} from '@lexical/code';
19
19
  */
20
20
 
21
21
  export type Tokenizer = {
22
- defaultLanguage: string;
22
+ defaultLanguage: string | null;
23
23
  defaultTheme: string;
24
24
  $tokenize: (codeNode: CodeNode, language?: string) => LexicalNode[];
25
25
  }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ "use strict";var e=require("@lexical/code-core"),t=require("@lexical/extension"),n=require("lexical"),i=require("@shikijs/core"),o=require("@shikijs/engine-javascript"),r=require("shiki/langs"),d=require("shiki/themes");const s=i.createHighlighterCoreSync({engine:o.createJavaScriptRegexEngine(),langs:[],themes:[]});function g(e){const t=/^diff-([\w-]+)/i.exec(e);return t?t[1]:null}function a(e){const t=g(e)||e;return!!i.isSpecialLang(t)||s.getLoadedLanguages().includes(t)}function l(t,i,o){const d=g(t),l=d||t;if(!a(l)){const d=r.bundledLanguagesInfo.find(e=>e.id===l||e.aliases&&e.aliases.includes(l));if(d)return s.loadLanguage(d.import()).then(()=>{i&&o&&i.update(()=>{const i=n.$getNodeByKey(o);e.$isCodeNode(i)&&i.getLanguage()===t&&!i.getIsSyntaxHighlightSupported()&&i.setIsSyntaxHighlightSupported(!0)})})}}function u(e){const t=e;return!!i.isSpecialTheme(t)||s.getLoadedThemes().includes(t)}function c(t,i,o){if(!u(t)){const r=d.bundledThemesInfo.find(e=>e.id===t);if(r)return s.loadTheme(r.import()).then(()=>{i&&o&&i.update(()=>{const t=n.$getNodeByKey(o);e.$isCodeNode(t)&&t.markDirty()})})}}function h(t,o){const r=/^diff-([\w-]+)/i.exec(o),d=t.getTextContent(),g=s.codeToTokens(d,{lang:r?r[1]:o,theme:t.getTheme()||"poimandres"}),{tokens:a,bg:l,fg:u}=g;let c="";return l&&(c+=`background-color: ${l};`),u&&(c+=`color: ${u};`),t.getStyle()!==c&&t.setStyle(c),function(t,o){const r=[];return t.forEach((t,d)=>{d&&r.push(n.$createLineBreakNode()),t.forEach((t,d)=>{let s=t.content;if(o&&0===d&&s.length>0){const t=["+","-",">","<"," "],n=["inserted","deleted","inserted","deleted","unchanged"],i=t.indexOf(s[0]);-1!==i&&(r.push(e.$createCodeHighlightNode(t[i],n[i])),s=s.slice(1))}s.split("\t").forEach((o,d)=>{if(d&&r.push(n.$createTabNode()),""!==o){const n=e.$createCodeHighlightNode(o),d=i.stringifyTokenStyle(t.htmlStyle||i.getTokenStyleObject(t));n.setStyle(d),r.push(n)}})})}),r}(a,!!r)}const f={$tokenize(t,n){const i=n||this.defaultLanguage;return null===i?e.$plainifyCodeContent(t.getTextContent()):h(t,i)},defaultLanguage:e.DEFAULT_CODE_LANGUAGE,defaultTheme:"one-light"};function p(t,i,o,r){const d=r.getParent();e.$isCodeNode(d)?m(t,i,o,d):e.$isCodeHighlightNode(r)&&r.replace(n.$createTextNode(r.__text))}function C(e,t){const i=t.getElementByKey(e.getKey());if(null===i)return;const o=e.getChildren(),r=o.length;if(r===i.__cachedChildrenLength)return;i.__cachedChildrenLength=r;let d="1",s=1;for(let e=0;e<r;e++)n.$isLineBreakNode(o[e])&&(d+="\n"+ ++s);i.setAttribute("data-gutter",d)}function m(t,i,o,r){const d=r.getKey(),{nodesCurrentlyHighlighting:s}=o;let g=r.getLanguage();g||null===i.defaultLanguage||(g=i.defaultLanguage,r.setLanguage(g));let h=r.getTheme();h||(h=i.defaultTheme,r.setTheme(h));let f=!1;u(h)||(c(h,t,d),g&&(f=!0)),g?a(g)?r.getIsSyntaxHighlightSupported()||r.setIsSyntaxHighlightSupported(!0):(r.getIsSyntaxHighlightSupported()&&r.setIsSyntaxHighlightSupported(!1),l(g,t,d),f=!0):r.getIsSyntaxHighlightSupported()&&r.setIsSyntaxHighlightSupported(!1),f||s.has(d)||(s.add(d),o.didTransform||(o.didTransform=!0,n.$onUpdate(()=>{o.didTransform=!1,s.clear()})),function(t,i){const o=n.$getNodeByKey(t);if(!e.$isCodeNode(o)||!o.isAttached())return;const r=n.$getSelection();if(!n.$isRangeSelection(r))return void i();const d=r.anchor,s=d.offset,g="element"===d.type&&n.$isLineBreakNode(o.getChildAtIndex(d.offset-1));let a=0;if(!g){const e=d.getNode();a=s+e.getPreviousSiblings().reduce((e,t)=>e+t.getTextContentSize(),0)}if(!i())return;if(g)return void d.getNode().select(s,s);o.getChildren().some(e=>{const t=n.$isTextNode(e);if(t||n.$isLineBreakNode(e)){const n=e.getTextContentSize();if(t&&n>=a)return e.select(a,a),!0;a-=n}return!1})}(d,()=>{const t=n.$getNodeByKey(d);if(!e.$isCodeNode(t)||!t.isAttached())return!1;const o=t.getLanguage()||i.defaultLanguage,s=i.$tokenize(t,o??void 0),g=function(e,t){let n=0;for(;n<e.length&&x(e[n],t[n]);)n++;const i=e.length,o=t.length,r=Math.min(i,o)-n;let d=0;for(;d<r;)if(d++,!x(e[i-d],t[o-d])){d--;break}const s=n,g=i-d,a=t.slice(n,o-d);return{from:s,nodesForReplacement:a,to:g}}(t.getChildren(),s),{from:a,to:l,nodesForReplacement:u}=g;return!(a===l&&!u.length)&&(r.splice(a,l-a,u),!0)}))}function x(t,i){return e.$isCodeHighlightNode(t)&&e.$isCodeHighlightNode(i)&&t.__text===i.__text&&t.__highlightType===i.__highlightType&&t.__style===i.__style||n.$isTabNode(t)&&n.$isTabNode(i)||n.$isLineBreakNode(t)&&n.$isLineBreakNode(i)}function y(t,i){const o=[];!0!==t._headless&&o.push(t.registerMutationListener(e.CodeNode,e=>{t.getEditorState().read(()=>{for(const[i,o]of e)if("destroyed"!==o){const e=n.$getNodeByKey(i);null!==e&&C(e,t)}})},{skipInitialization:!1}));const r={didTransform:!1,nodesCurrentlyHighlighting:new Set};return o.push(t.registerNodeTransform(e.CodeNode,m.bind(null,t,i,r)),t.registerNodeTransform(n.TextNode,p.bind(null,t,i,r)),t.registerNodeTransform(e.CodeHighlightNode,p.bind(null,t,i,r))),n.mergeRegister(...o)}const N=n.defineExtension({build:(e,n)=>t.namedSignals(n),config:n.safeCast({disabled:!1,tokenizer:f}),dependencies:[e.CodeExtension,e.CodeIndentExtension],name:"@lexical/code-shiki",register:(e,n,i)=>{const o=i.getOutput();return t.effect(()=>{if(!o.disabled.value)return y(e,o.tokenizer.value)})}}),S=n.defineExtension({config:n.safeCast(f),dependencies:[N],init:(e,t,n)=>{n.getDependency(N).config.tokenizer=t},name:"@lexical/code-shiki/legacy"});exports.CodeHighlighterShikiExtension=S,exports.CodeShikiExtension=N,exports.ShikiTokenizer=f,exports.getCodeLanguageOptions=function(){return r.bundledLanguagesInfo.map(e=>[e.id,e.name])},exports.getCodeThemeOptions=function(){return d.bundledThemesInfo.map(e=>[e.id,e.displayName])},exports.isCodeLanguageLoaded=a,exports.loadCodeLanguage=l,exports.loadCodeTheme=c,exports.normalizeCodeLanguage=function(e){const t=e,n=r.bundledLanguagesInfo.find(e=>e.id===t||e.aliases&&e.aliases.includes(t));return n?n.id:e},exports.registerCodeHighlighting=function(t,i=f){if(!t.hasNodes([e.CodeNode,e.CodeHighlightNode]))throw new Error("CodeHighlightPlugin: CodeNode or CodeHighlightNode not registered on editor");return n.mergeRegister(y(t,i),e.registerCodeIndentation(t))};
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import{$isCodeNode as e,$createCodeHighlightNode as t,CodeExtension as n,CodeIndentExtension as i,DEFAULT_CODE_LANGUAGE as o,CodeNode as r,CodeHighlightNode as s,$plainifyCodeContent as l,$isCodeHighlightNode as d,registerCodeIndentation as g}from"@lexical/code-core";import{effect as a,namedSignals as u}from"@lexical/extension";import{$getNodeByKey as c,$createLineBreakNode as f,$createTabNode as h,defineExtension as p,safeCast as m,TextNode as y,mergeRegister as x,$isLineBreakNode as S,$onUpdate as T,$createTextNode as _,$getSelection as k,$isRangeSelection as L,$isTextNode as C,$isTabNode as H}from"lexical";import{createHighlighterCoreSync as b,isSpecialTheme as I,isSpecialLang as N,stringifyTokenStyle as z,getTokenStyleObject as v}from"@shikijs/core";import{createJavaScriptRegexEngine as E}from"@shikijs/engine-javascript";import{bundledLanguagesInfo as w}from"shiki/langs";import{bundledThemesInfo as A}from"shiki/themes";const $=b({engine:E(),langs:[],themes:[]});function j(e){const t=/^diff-([\w-]+)/i.exec(e);return t?t[1]:null}function K(e){const t=j(e)||e;return!!N(t)||$.getLoadedLanguages().includes(t)}function P(t,n,i){const o=j(t),r=o||t;if(!K(r)){const o=w.find(e=>e.id===r||e.aliases&&e.aliases.includes(r));if(o)return $.loadLanguage(o.import()).then(()=>{n&&i&&n.update(()=>{const n=c(i);e(n)&&n.getLanguage()===t&&!n.getIsSyntaxHighlightSupported()&&n.setIsSyntaxHighlightSupported(!0)})})}}function D(e){const t=e;return!!I(t)||$.getLoadedThemes().includes(t)}function F(t,n,i){if(!D(t)){const o=A.find(e=>e.id===t);if(o)return $.loadTheme(o.import()).then(()=>{n&&i&&n.update(()=>{const t=c(i);e(t)&&t.markDirty()})})}}function M(){return w.map(e=>[e.id,e.name])}function O(){return A.map(e=>[e.id,e.displayName])}function R(e){const t=e,n=w.find(e=>e.id===t||e.aliases&&e.aliases.includes(t));return n?n.id:e}function B(e,n){const i=/^diff-([\w-]+)/i.exec(n),o=e.getTextContent(),r=$.codeToTokens(o,{lang:i?i[1]:n,theme:e.getTheme()||"poimandres"}),{tokens:s,bg:l,fg:d}=r;let g="";return l&&(g+=`background-color: ${l};`),d&&(g+=`color: ${d};`),e.getStyle()!==g&&e.setStyle(g),function(e,n){const i=[];return e.forEach((e,o)=>{o&&i.push(f()),e.forEach((e,o)=>{let r=e.content;if(n&&0===o&&r.length>0){const e=["+","-",">","<"," "],n=["inserted","deleted","inserted","deleted","unchanged"],o=e.indexOf(r[0]);-1!==o&&(i.push(t(e[o],n[o])),r=r.slice(1))}r.split("\t").forEach((n,o)=>{if(o&&i.push(h()),""!==n){const o=t(n),r=z(e.htmlStyle||v(e));o.setStyle(r),i.push(o)}})})}),i}(s,!!i)}const q={$tokenize(e,t){const n=t||this.defaultLanguage;return null===n?l(e.getTextContent()):B(e,n)},defaultLanguage:o,defaultTheme:"one-light"};function G(t,n,i,o){const r=o.getParent();e(r)?Q(t,n,i,r):d(o)&&o.replace(_(o.__text))}function J(e,t){const n=t.getElementByKey(e.getKey());if(null===n)return;const i=e.getChildren(),o=i.length;if(o===n.__cachedChildrenLength)return;n.__cachedChildrenLength=o;let r="1",s=1;for(let e=0;e<o;e++)S(i[e])&&(r+="\n"+ ++s);n.setAttribute("data-gutter",r)}function Q(t,n,i,o){const r=o.getKey(),{nodesCurrentlyHighlighting:s}=i;let l=o.getLanguage();l||null===n.defaultLanguage||(l=n.defaultLanguage,o.setLanguage(l));let d=o.getTheme();d||(d=n.defaultTheme,o.setTheme(d));let g=!1;D(d)||(F(d,t,r),l&&(g=!0)),l?K(l)?o.getIsSyntaxHighlightSupported()||o.setIsSyntaxHighlightSupported(!0):(o.getIsSyntaxHighlightSupported()&&o.setIsSyntaxHighlightSupported(!1),P(l,t,r),g=!0):o.getIsSyntaxHighlightSupported()&&o.setIsSyntaxHighlightSupported(!1),g||s.has(r)||(s.add(r),i.didTransform||(i.didTransform=!0,T(()=>{i.didTransform=!1,s.clear()})),function(t,n){const i=c(t);if(!e(i)||!i.isAttached())return;const o=k();if(!L(o))return void n();const r=o.anchor,s=r.offset,l="element"===r.type&&S(i.getChildAtIndex(r.offset-1));let d=0;if(!l){const e=r.getNode();d=s+e.getPreviousSiblings().reduce((e,t)=>e+t.getTextContentSize(),0)}if(!n())return;if(l)return void r.getNode().select(s,s);i.getChildren().some(e=>{const t=C(e);if(t||S(e)){const n=e.getTextContentSize();if(t&&n>=d)return e.select(d,d),!0;d-=n}return!1})}(r,()=>{const t=c(r);if(!e(t)||!t.isAttached())return!1;const i=t.getLanguage()||n.defaultLanguage,s=n.$tokenize(t,i??void 0),l=function(e,t){let n=0;for(;n<e.length&&U(e[n],t[n]);)n++;const i=e.length,o=t.length,r=Math.min(i,o)-n;let s=0;for(;s<r;)if(s++,!U(e[i-s],t[o-s])){s--;break}const l=n,d=i-s,g=t.slice(n,o-s);return{from:l,nodesForReplacement:g,to:d}}(t.getChildren(),s),{from:d,to:g,nodesForReplacement:a}=l;return!(d===g&&!a.length)&&(o.splice(d,g-d,a),!0)}))}function U(e,t){return d(e)&&d(t)&&e.__text===t.__text&&e.__highlightType===t.__highlightType&&e.__style===t.__style||H(e)&&H(t)||S(e)&&S(t)}function V(e,t){const n=[];!0!==e._headless&&n.push(e.registerMutationListener(r,t=>{e.getEditorState().read(()=>{for(const[n,i]of t)if("destroyed"!==i){const t=c(n);null!==t&&J(t,e)}})},{skipInitialization:!1}));const i={didTransform:!1,nodesCurrentlyHighlighting:new Set};return n.push(e.registerNodeTransform(r,Q.bind(null,e,t,i)),e.registerNodeTransform(y,G.bind(null,e,t,i)),e.registerNodeTransform(s,G.bind(null,e,t,i))),x(...n)}function W(e,t=q){if(!e.hasNodes([r,s]))throw new Error("CodeHighlightPlugin: CodeNode or CodeHighlightNode not registered on editor");return x(V(e,t),g(e))}const X=p({build:(e,t)=>u(t),config:m({disabled:!1,tokenizer:q}),dependencies:[n,i],name:"@lexical/code-shiki",register:(e,t,n)=>{const i=n.getOutput();return a(()=>{if(!i.disabled.value)return V(e,i.tokenizer.value)})}}),Y=p({config:m(q),dependencies:[X],init:(e,t,n)=>{n.getDependency(X).config.tokenizer=t},name:"@lexical/code-shiki/legacy"});export{Y as CodeHighlighterShikiExtension,X as CodeShikiExtension,q as ShikiTokenizer,M as getCodeLanguageOptions,O as getCodeThemeOptions,K as isCodeLanguageLoaded,P as loadCodeLanguage,F as loadCodeTheme,R as normalizeCodeLanguage,W as registerCodeHighlighting};
package/package.json CHANGED
@@ -8,18 +8,18 @@
8
8
  "code"
9
9
  ],
10
10
  "license": "MIT",
11
- "version": "0.44.1-nightly.20260519.0",
12
- "main": "LexicalCodeShiki.js",
13
- "types": "index.d.ts",
11
+ "version": "0.45.0",
12
+ "main": "./dist/LexicalCodeShiki.js",
13
+ "types": "./dist/index.d.ts",
14
14
  "dependencies": {
15
- "@lexical/code-core": "0.44.1-nightly.20260519.0",
16
- "@lexical/extension": "0.44.1-nightly.20260519.0",
17
15
  "@shikijs/core": "^4.0.2",
18
16
  "@shikijs/engine-javascript": "^4.0.2",
19
17
  "@shikijs/langs": "^4.0.2",
20
18
  "@shikijs/themes": "^4.0.2",
21
- "lexical": "0.44.1-nightly.20260519.0",
22
- "shiki": "^4.0.2"
19
+ "shiki": "^4.0.2",
20
+ "@lexical/code-core": "0.45.0",
21
+ "lexical": "0.45.0",
22
+ "@lexical/extension": "0.45.0"
23
23
  },
24
24
  "repository": {
25
25
  "type": "git",
@@ -29,23 +29,37 @@
29
29
  "devDependencies": {
30
30
  "@shikijs/types": "^4.0.2"
31
31
  },
32
- "module": "LexicalCodeShiki.mjs",
32
+ "module": "./dist/LexicalCodeShiki.mjs",
33
33
  "sideEffects": true,
34
34
  "exports": {
35
35
  ".": {
36
+ "source": "./src/index.ts",
36
37
  "import": {
37
- "types": "./index.d.ts",
38
- "development": "./LexicalCodeShiki.dev.mjs",
39
- "production": "./LexicalCodeShiki.prod.mjs",
40
- "node": "./LexicalCodeShiki.node.mjs",
41
- "default": "./LexicalCodeShiki.mjs"
38
+ "types": "./dist/index.d.ts",
39
+ "development": "./dist/LexicalCodeShiki.dev.mjs",
40
+ "production": "./dist/LexicalCodeShiki.prod.mjs",
41
+ "node": "./dist/LexicalCodeShiki.node.mjs",
42
+ "default": "./dist/LexicalCodeShiki.mjs"
42
43
  },
43
44
  "require": {
44
- "types": "./index.d.ts",
45
- "development": "./LexicalCodeShiki.dev.js",
46
- "production": "./LexicalCodeShiki.prod.js",
47
- "default": "./LexicalCodeShiki.js"
45
+ "types": "./dist/index.d.ts",
46
+ "development": "./dist/LexicalCodeShiki.dev.js",
47
+ "production": "./dist/LexicalCodeShiki.prod.js",
48
+ "default": "./dist/LexicalCodeShiki.js"
48
49
  }
49
50
  }
50
- }
51
+ },
52
+ "files": [
53
+ "dist",
54
+ "src",
55
+ "!src/__tests__",
56
+ "!src/__bench__",
57
+ "!src/__mocks__",
58
+ "!src/**/*.test.ts",
59
+ "!src/**/*.test.tsx",
60
+ "!src/**/*.bench.ts",
61
+ "!src/**/*.bench.tsx",
62
+ "README.md",
63
+ "LICENSE"
64
+ ]
51
65
  }
@@ -0,0 +1,506 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import type {LexicalEditor, LexicalNode, NodeKey} from 'lexical';
10
+
11
+ import {
12
+ $isCodeHighlightNode,
13
+ $isCodeNode,
14
+ $plainifyCodeContent,
15
+ CodeExtension,
16
+ CodeHighlightNode,
17
+ CodeIndentExtension,
18
+ CodeNode,
19
+ DEFAULT_CODE_LANGUAGE,
20
+ registerCodeIndentation,
21
+ } from '@lexical/code-core';
22
+ import {effect, namedSignals} from '@lexical/extension';
23
+ import {
24
+ $createTextNode,
25
+ $getNodeByKey,
26
+ $getSelection,
27
+ $isLineBreakNode,
28
+ $isRangeSelection,
29
+ $isTabNode,
30
+ $isTextNode,
31
+ $onUpdate,
32
+ defineExtension,
33
+ mergeRegister,
34
+ safeCast,
35
+ TextNode,
36
+ } from 'lexical';
37
+
38
+ import {
39
+ $getHighlightNodes,
40
+ isCodeLanguageLoaded,
41
+ isCodeThemeLoaded,
42
+ loadCodeLanguage,
43
+ loadCodeTheme,
44
+ } from './FacadeShiki';
45
+
46
+ export interface Tokenizer {
47
+ /**
48
+ * Language to fall back to when a {@link CodeNode} doesn't carry one.
49
+ * Set to `null` to opt out of the implicit fallback — code blocks
50
+ * without a language stay untouched (no `data-language` attribute, no
51
+ * syntax highlighting) so a markdown round-trip can preserve ``` with
52
+ * no info string.
53
+ */
54
+ defaultLanguage: string | null;
55
+ defaultTheme: string;
56
+ $tokenize: (
57
+ this: Tokenizer,
58
+ codeNode: CodeNode,
59
+ language?: string,
60
+ ) => LexicalNode[];
61
+ }
62
+
63
+ const DEFAULT_CODE_THEME = 'one-light';
64
+
65
+ export const ShikiTokenizer: Tokenizer = {
66
+ $tokenize(
67
+ this: Tokenizer,
68
+ codeNode: CodeNode,
69
+ language?: string,
70
+ ): LexicalNode[] {
71
+ const lang = language || this.defaultLanguage;
72
+ return lang === null
73
+ ? $plainifyCodeContent(codeNode.getTextContent())
74
+ : $getHighlightNodes(codeNode, lang);
75
+ },
76
+ defaultLanguage: DEFAULT_CODE_LANGUAGE,
77
+ defaultTheme: DEFAULT_CODE_THEME,
78
+ };
79
+
80
+ function $textNodeTransform(
81
+ editor: LexicalEditor,
82
+ tokenizer: Tokenizer,
83
+ transformState: TransformState,
84
+ node: TextNode,
85
+ ): void {
86
+ // Since CodeNode has flat children structure we only need to check
87
+ // if node's parent is a code node and run highlighting if so
88
+ const parentNode = node.getParent();
89
+ if ($isCodeNode(parentNode)) {
90
+ $codeNodeTransform(editor, tokenizer, transformState, parentNode);
91
+ } else if ($isCodeHighlightNode(node)) {
92
+ // When code block converted into paragraph or other element
93
+ // code highlight nodes converted back to normal text
94
+ node.replace($createTextNode(node.__text));
95
+ }
96
+ }
97
+
98
+ function updateCodeGutter(node: CodeNode, editor: LexicalEditor): void {
99
+ const codeElement = editor.getElementByKey(node.getKey());
100
+ if (codeElement === null) {
101
+ return;
102
+ }
103
+ const children = node.getChildren();
104
+ const childrenLength = children.length;
105
+ // @ts-ignore: internal field
106
+ if (childrenLength === codeElement.__cachedChildrenLength) {
107
+ // Avoid updating the attribute if the children length hasn't changed.
108
+ return;
109
+ }
110
+ // @ts-ignore:: internal field
111
+ codeElement.__cachedChildrenLength = childrenLength;
112
+ let gutter = '1';
113
+ let count = 1;
114
+ for (let i = 0; i < childrenLength; i++) {
115
+ if ($isLineBreakNode(children[i])) {
116
+ gutter += '\n' + ++count;
117
+ }
118
+ }
119
+ codeElement.setAttribute('data-gutter', gutter);
120
+ }
121
+
122
+ interface TransformState {
123
+ didTransform: boolean;
124
+ // Using extra cache (`nodesCurrentlyHighlighting`) since both CodeNode and CodeHighlightNode
125
+ // transforms might be called at the same time (e.g. new CodeHighlight node inserted) and
126
+ // in both cases we'll rerun whole reformatting over CodeNode, which is redundant.
127
+ // Especially when pasting code into CodeBlock.
128
+ nodesCurrentlyHighlighting: Set<NodeKey>;
129
+ }
130
+
131
+ function $codeNodeTransform(
132
+ editor: LexicalEditor,
133
+ tokenizer: Tokenizer,
134
+ transformState: TransformState,
135
+ node: CodeNode,
136
+ ) {
137
+ const nodeKey = node.getKey();
138
+ const {nodesCurrentlyHighlighting} = transformState;
139
+
140
+ // When new code block inserted it might not have language selected.
141
+ // Tokenizers configured with `defaultLanguage: null` opt out of the
142
+ // implicit fallback — leave the node unset and skip highlighting so
143
+ // markdown round-trips ``` (no info string) without injecting one.
144
+ let language = node.getLanguage();
145
+ if (!language && tokenizer.defaultLanguage !== null) {
146
+ language = tokenizer.defaultLanguage;
147
+ node.setLanguage(language);
148
+ }
149
+
150
+ let theme = node.getTheme();
151
+ if (!theme) {
152
+ theme = tokenizer.defaultTheme;
153
+ node.setTheme(theme);
154
+ }
155
+
156
+ // dynamic import of themes
157
+ let inFlight = false;
158
+ if (!isCodeThemeLoaded(theme)) {
159
+ loadCodeTheme(theme, editor, nodeKey);
160
+ // Only the highlight path (a resolved language) consumes the theme. With
161
+ // no language the text is plainified, which needs no theme, so don't defer
162
+ // the split on a theme load that won't be used — otherwise a code block
163
+ // with `defaultLanguage: null` stays an unsplit TextNode until the theme
164
+ // happens to finish loading.
165
+ if (language) {
166
+ inFlight = true;
167
+ }
168
+ }
169
+
170
+ // dynamic import of languages
171
+ if (language) {
172
+ if (isCodeLanguageLoaded(language)) {
173
+ if (!node.getIsSyntaxHighlightSupported()) {
174
+ node.setIsSyntaxHighlightSupported(true);
175
+ }
176
+ } else {
177
+ if (node.getIsSyntaxHighlightSupported()) {
178
+ node.setIsSyntaxHighlightSupported(false);
179
+ }
180
+ loadCodeLanguage(language, editor, nodeKey);
181
+ inFlight = true;
182
+ }
183
+ } else if (node.getIsSyntaxHighlightSupported()) {
184
+ node.setIsSyntaxHighlightSupported(false);
185
+ }
186
+
187
+ if (inFlight) {
188
+ return;
189
+ }
190
+
191
+ if (nodesCurrentlyHighlighting.has(nodeKey)) {
192
+ return;
193
+ }
194
+
195
+ nodesCurrentlyHighlighting.add(nodeKey);
196
+ if (!transformState.didTransform) {
197
+ transformState.didTransform = true;
198
+ $onUpdate(() => {
199
+ transformState.didTransform = false;
200
+ nodesCurrentlyHighlighting.clear();
201
+ });
202
+ }
203
+
204
+ $updateAndRetainSelection(nodeKey, () => {
205
+ const currentNode = $getNodeByKey(nodeKey);
206
+
207
+ if (!$isCodeNode(currentNode) || !currentNode.isAttached()) {
208
+ return false;
209
+ }
210
+
211
+ const lang = currentNode.getLanguage() || tokenizer.defaultLanguage;
212
+ const highlightNodes = tokenizer.$tokenize(currentNode, lang ?? undefined);
213
+ const diffRange = getDiffRange(currentNode.getChildren(), highlightNodes);
214
+ const {from, to, nodesForReplacement} = diffRange;
215
+
216
+ if (from !== to || nodesForReplacement.length) {
217
+ node.splice(from, to - from, nodesForReplacement);
218
+ return true;
219
+ }
220
+
221
+ return false;
222
+ });
223
+ }
224
+
225
+ // Wrapping update function into selection retainer, that tries to keep cursor at the same
226
+ // position as before.
227
+ function $updateAndRetainSelection(
228
+ nodeKey: NodeKey,
229
+ updateFn: () => boolean,
230
+ ): void {
231
+ const node = $getNodeByKey(nodeKey);
232
+ if (!$isCodeNode(node) || !node.isAttached()) {
233
+ return;
234
+ }
235
+ const selection = $getSelection();
236
+ // If it's not range selection (or null selection) there's no need to change it,
237
+ // but we can still run highlighting logic
238
+ if (!$isRangeSelection(selection)) {
239
+ updateFn();
240
+ return;
241
+ }
242
+
243
+ const anchor = selection.anchor;
244
+ const anchorOffset = anchor.offset;
245
+ const isNewLineAnchor =
246
+ anchor.type === 'element' &&
247
+ $isLineBreakNode(node.getChildAtIndex(anchor.offset - 1));
248
+ let textOffset = 0;
249
+
250
+ // Calculating previous text offset (all text node prior to anchor + anchor own text offset)
251
+ if (!isNewLineAnchor) {
252
+ const anchorNode = anchor.getNode();
253
+ textOffset =
254
+ anchorOffset +
255
+ anchorNode.getPreviousSiblings().reduce((offset, _node) => {
256
+ return offset + _node.getTextContentSize();
257
+ }, 0);
258
+ }
259
+
260
+ const hasChanges = updateFn();
261
+ if (!hasChanges) {
262
+ return;
263
+ }
264
+
265
+ // Non-text anchors only happen for line breaks, otherwise
266
+ // selection will be within text node (code highlight node)
267
+ if (isNewLineAnchor) {
268
+ anchor.getNode().select(anchorOffset, anchorOffset);
269
+ return;
270
+ }
271
+
272
+ // If it was non-element anchor then we walk through child nodes
273
+ // and looking for a position of original text offset
274
+ node.getChildren().some(_node => {
275
+ const isText = $isTextNode(_node);
276
+ if (isText || $isLineBreakNode(_node)) {
277
+ const textContentSize = _node.getTextContentSize();
278
+ if (isText && textContentSize >= textOffset) {
279
+ _node.select(textOffset, textOffset);
280
+ return true;
281
+ }
282
+ textOffset -= textContentSize;
283
+ }
284
+ return false;
285
+ });
286
+ }
287
+
288
+ // Finds minimal diff range between two nodes lists. It returns from/to range boundaries of prevNodes
289
+ // that needs to be replaced with `nodes` (subset of nextNodes) to make prevNodes equal to nextNodes.
290
+ function getDiffRange(
291
+ prevNodes: Array<LexicalNode>,
292
+ nextNodes: Array<LexicalNode>,
293
+ ): {
294
+ from: number;
295
+ nodesForReplacement: Array<LexicalNode>;
296
+ to: number;
297
+ } {
298
+ let leadingMatch = 0;
299
+ while (leadingMatch < prevNodes.length) {
300
+ if (!isEqual(prevNodes[leadingMatch], nextNodes[leadingMatch])) {
301
+ break;
302
+ }
303
+ leadingMatch++;
304
+ }
305
+
306
+ const prevNodesLength = prevNodes.length;
307
+ const nextNodesLength = nextNodes.length;
308
+ const maxTrailingMatch =
309
+ Math.min(prevNodesLength, nextNodesLength) - leadingMatch;
310
+
311
+ let trailingMatch = 0;
312
+ while (trailingMatch < maxTrailingMatch) {
313
+ trailingMatch++;
314
+ if (
315
+ !isEqual(
316
+ prevNodes[prevNodesLength - trailingMatch],
317
+ nextNodes[nextNodesLength - trailingMatch],
318
+ )
319
+ ) {
320
+ trailingMatch--;
321
+ break;
322
+ }
323
+ }
324
+
325
+ const from = leadingMatch;
326
+ const to = prevNodesLength - trailingMatch;
327
+ const nodesForReplacement = nextNodes.slice(
328
+ leadingMatch,
329
+ nextNodesLength - trailingMatch,
330
+ );
331
+ return {
332
+ from,
333
+ nodesForReplacement,
334
+ to,
335
+ };
336
+ }
337
+
338
+ function isEqual(nodeA: LexicalNode, nodeB: LexicalNode): boolean {
339
+ // Only checking for code highlight nodes, tabs and linebreaks. If it's regular text node
340
+ // returning false so that it's transformed into code highlight node
341
+ return (
342
+ ($isCodeHighlightNode(nodeA) &&
343
+ $isCodeHighlightNode(nodeB) &&
344
+ nodeA.__text === nodeB.__text &&
345
+ nodeA.__highlightType === nodeB.__highlightType &&
346
+ nodeA.__style === nodeB.__style) ||
347
+ ($isTabNode(nodeA) && $isTabNode(nodeB)) ||
348
+ ($isLineBreakNode(nodeA) && $isLineBreakNode(nodeB))
349
+ );
350
+ }
351
+
352
+ /**
353
+ * @internal
354
+ * Register only the Shiki highlighting transforms and the gutter
355
+ * mutation listener. No keyboard / indent handlers — those are the
356
+ * responsibility of
357
+ * {@link "@lexical/code-core".registerCodeIndentation} /
358
+ * {@link "@lexical/code-core".CodeIndentExtension}.
359
+ *
360
+ * Used by {@link CodeShikiExtension}, whose `CodeIndentExtension`
361
+ * dependency handles the indent side. The legacy
362
+ * {@link registerCodeHighlighting} wrapper combines this helper with
363
+ * `registerCodeIndentation` for direct callers that want the original
364
+ * single-call setup.
365
+ *
366
+ * Exported for use by the package's own unit tests; not re-exported
367
+ * from the package entry point.
368
+ */
369
+ export function registerHighlightingOnly(
370
+ editor: LexicalEditor,
371
+ tokenizer: Tokenizer,
372
+ ): () => void {
373
+ const registrations = [];
374
+
375
+ // Only register the mutation listener if not in headless mode
376
+ if (editor._headless !== true) {
377
+ registrations.push(
378
+ editor.registerMutationListener(
379
+ CodeNode,
380
+ mutations => {
381
+ editor.getEditorState().read(() => {
382
+ for (const [key, type] of mutations) {
383
+ if (type !== 'destroyed') {
384
+ const node = $getNodeByKey(key);
385
+ if (node !== null) {
386
+ updateCodeGutter(node as CodeNode, editor);
387
+ }
388
+ }
389
+ }
390
+ });
391
+ },
392
+ {skipInitialization: false},
393
+ ),
394
+ );
395
+ }
396
+
397
+ const transformState: TransformState = {
398
+ didTransform: false,
399
+ nodesCurrentlyHighlighting: new Set(),
400
+ };
401
+ registrations.push(
402
+ editor.registerNodeTransform(
403
+ CodeNode,
404
+ $codeNodeTransform.bind(null, editor, tokenizer, transformState),
405
+ ),
406
+ editor.registerNodeTransform(
407
+ TextNode,
408
+ $textNodeTransform.bind(null, editor, tokenizer, transformState),
409
+ ),
410
+ editor.registerNodeTransform(
411
+ CodeHighlightNode,
412
+ $textNodeTransform.bind(null, editor, tokenizer, transformState),
413
+ ),
414
+ );
415
+
416
+ return mergeRegister(...registrations);
417
+ }
418
+
419
+ /**
420
+ * Register the Shiki tokenizer-driven highlighting on the editor along
421
+ * with the indent / Tab / arrow-key keyboard handlers. This function
422
+ * is provided for legacy code that has not upgraded to using
423
+ * {@link CodeShikiExtension}.
424
+ */
425
+ export function registerCodeHighlighting(
426
+ editor: LexicalEditor,
427
+ tokenizer: Tokenizer = ShikiTokenizer,
428
+ ): () => void {
429
+ if (!editor.hasNodes([CodeNode, CodeHighlightNode])) {
430
+ throw new Error(
431
+ 'CodeHighlightPlugin: CodeNode or CodeHighlightNode not registered on editor',
432
+ );
433
+ }
434
+ return mergeRegister(
435
+ registerHighlightingOnly(editor, tokenizer),
436
+ registerCodeIndentation(editor),
437
+ );
438
+ }
439
+
440
+ export interface CodeShikiConfig {
441
+ /**
442
+ * When true, the Shiki code highlighter is not registered on the editor.
443
+ * This signal can be flipped at runtime to enable or disable the
444
+ * highlighter, for example to switch between the Prism and Shiki
445
+ * highlighters without rebuilding the editor.
446
+ */
447
+ disabled: boolean;
448
+ tokenizer: Tokenizer;
449
+ }
450
+
451
+ /**
452
+ * Add code highlighting support for code blocks with Shiki.
453
+ *
454
+ * {@link CodeExtension} is a dependency, so the required `CodeNode` and
455
+ * `CodeHighlightNode` nodes are registered automatically.
456
+ * {@link CodeIndentExtension} is also a dependency, so Tab / Shift+Tab
457
+ * and the related keyboard handlers are activated automatically. Set
458
+ * `tabSize` on `CodeIndentExtension` to enable space-indent outdent.
459
+ */
460
+ export const CodeShikiExtension = defineExtension({
461
+ build: (editor, config) => namedSignals(config),
462
+ config: safeCast<CodeShikiConfig>({
463
+ disabled: false,
464
+ tokenizer: ShikiTokenizer,
465
+ }),
466
+ dependencies: [CodeExtension, CodeIndentExtension],
467
+ name: '@lexical/code-shiki',
468
+ register: (editor, config, state) => {
469
+ const stores = state.getOutput();
470
+ return effect(() => {
471
+ if (stores.disabled.value) {
472
+ return;
473
+ }
474
+ return registerHighlightingOnly(editor, stores.tokenizer.value);
475
+ });
476
+ },
477
+ });
478
+
479
+ /**
480
+ * @deprecated Use {@link CodeShikiExtension} instead. This type is a
481
+ * flat alias for {@link Tokenizer} kept for backward compatibility with
482
+ * {@link CodeHighlighterShikiExtension}.
483
+ */
484
+ export type CodeHighlighterShikiConfig = Tokenizer;
485
+
486
+ /**
487
+ * @deprecated Use {@link CodeShikiExtension} instead.
488
+ *
489
+ * This is a thin backward-compatibility shim that preserves the original
490
+ * flat {@link Tokenizer} config API. It depends on
491
+ * {@link CodeShikiExtension} and routes its configured tokenizer to the
492
+ * underlying extension during `init` (before `CodeShikiExtension` builds),
493
+ * so consumers using
494
+ * `configExtension(CodeHighlighterShikiExtension, customTokenizer)`
495
+ * continue to work without modification.
496
+ */
497
+ export const CodeHighlighterShikiExtension = defineExtension({
498
+ config: safeCast<CodeHighlighterShikiConfig>(ShikiTokenizer),
499
+ dependencies: [CodeShikiExtension],
500
+ init: (editorConfig, config, state) => {
501
+ // Forward the flat Tokenizer config to CodeShikiExtension's `tokenizer`
502
+ // field before it builds.
503
+ state.getDependency(CodeShikiExtension).config.tokenizer = config;
504
+ },
505
+ name: '@lexical/code-shiki/legacy',
506
+ });
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import type {CodeNode} from '@lexical/code-core';
10
+ import type {ThemedToken, TokensResult} from '@shikijs/types';
11
+ import type {LexicalEditor, LexicalNode, NodeKey} from 'lexical';
12
+
13
+ import {$createCodeHighlightNode, $isCodeNode} from '@lexical/code-core';
14
+ import {
15
+ createHighlighterCoreSync,
16
+ getTokenStyleObject,
17
+ isSpecialLang,
18
+ isSpecialTheme,
19
+ stringifyTokenStyle,
20
+ } from '@shikijs/core';
21
+ import {createJavaScriptRegexEngine} from '@shikijs/engine-javascript';
22
+ import {$createLineBreakNode, $createTabNode, $getNodeByKey} from 'lexical';
23
+ import {bundledLanguagesInfo} from 'shiki/langs';
24
+ import {bundledThemesInfo} from 'shiki/themes';
25
+
26
+ const shiki = createHighlighterCoreSync({
27
+ engine: createJavaScriptRegexEngine(),
28
+ langs: [],
29
+ themes: [],
30
+ });
31
+
32
+ function getDiffedLanguage(language: string) {
33
+ const DIFF_LANGUAGE_REGEX = /^diff-([\w-]+)/i;
34
+ const diffLanguageMatch = DIFF_LANGUAGE_REGEX.exec(language);
35
+ return diffLanguageMatch ? diffLanguageMatch[1] : null;
36
+ }
37
+
38
+ export function isCodeLanguageLoaded(language: string) {
39
+ const diffedLanguage = getDiffedLanguage(language);
40
+ const langId = diffedLanguage || language;
41
+
42
+ // handle shiki Hard-coded languages ['ansi', '', 'plaintext', 'txt', 'text', 'plain']
43
+ if (isSpecialLang(langId)) {
44
+ return true;
45
+ }
46
+
47
+ // note: getLoadedLanguages() also returns aliases
48
+ return shiki.getLoadedLanguages().includes(langId);
49
+ }
50
+
51
+ export function loadCodeLanguage(
52
+ language: string,
53
+ editor?: LexicalEditor,
54
+ codeNodeKey?: NodeKey,
55
+ ) {
56
+ const diffedLanguage = getDiffedLanguage(language);
57
+ const langId = diffedLanguage ? diffedLanguage : language;
58
+ if (!isCodeLanguageLoaded(langId)) {
59
+ const languageInfo = bundledLanguagesInfo.find(
60
+ desc =>
61
+ desc.id === langId || (desc.aliases && desc.aliases.includes(langId)),
62
+ );
63
+ if (languageInfo) {
64
+ // in case we arrive here concurrently (not yet loaded language is loaded twice)
65
+ // shiki's synchronous checks make sure to load it only once
66
+ return shiki.loadLanguage(languageInfo.import()).then(() => {
67
+ // here we know that the language is loaded
68
+ // make sure the code is highlighed with the correct language
69
+ if (editor && codeNodeKey) {
70
+ editor.update(() => {
71
+ const codeNode = $getNodeByKey(codeNodeKey);
72
+ if (
73
+ $isCodeNode(codeNode) &&
74
+ codeNode.getLanguage() === language &&
75
+ !codeNode.getIsSyntaxHighlightSupported()
76
+ ) {
77
+ codeNode.setIsSyntaxHighlightSupported(true);
78
+ }
79
+ });
80
+ }
81
+ });
82
+ }
83
+ }
84
+ }
85
+
86
+ export function isCodeThemeLoaded(theme: string) {
87
+ const themeId = theme;
88
+
89
+ // handle shiki special theme ['none']
90
+ if (isSpecialTheme(themeId)) {
91
+ return true;
92
+ }
93
+
94
+ return shiki.getLoadedThemes().includes(themeId);
95
+ }
96
+
97
+ export function loadCodeTheme(
98
+ theme: string,
99
+ editor?: LexicalEditor,
100
+ codeNodeKey?: NodeKey,
101
+ ) {
102
+ if (!isCodeThemeLoaded(theme)) {
103
+ const themeInfo = bundledThemesInfo.find(info => info.id === theme);
104
+ if (themeInfo) {
105
+ return shiki.loadTheme(themeInfo.import()).then(() => {
106
+ if (editor && codeNodeKey) {
107
+ editor.update(() => {
108
+ const codeNode = $getNodeByKey(codeNodeKey);
109
+ if ($isCodeNode(codeNode)) {
110
+ codeNode.markDirty();
111
+ }
112
+ });
113
+ }
114
+ });
115
+ }
116
+ }
117
+ }
118
+
119
+ export function getCodeLanguageOptions(): [string, string][] {
120
+ return bundledLanguagesInfo.map(i => [i.id, i.name]);
121
+ }
122
+ export function getCodeThemeOptions(): [string, string][] {
123
+ return bundledThemesInfo.map(i => [i.id, i.displayName]);
124
+ }
125
+
126
+ export function normalizeCodeLanguage(language: string): string {
127
+ const langId = language;
128
+ const languageInfo = bundledLanguagesInfo.find(
129
+ desc =>
130
+ desc.id === langId || (desc.aliases && desc.aliases.includes(langId)),
131
+ );
132
+ if (languageInfo) {
133
+ return languageInfo.id;
134
+ }
135
+ return language;
136
+ }
137
+
138
+ export function $getHighlightNodes(
139
+ codeNode: CodeNode,
140
+ language: string,
141
+ ): LexicalNode[] {
142
+ const DIFF_LANGUAGE_REGEX = /^diff-([\w-]+)/i;
143
+ const diffLanguageMatch = DIFF_LANGUAGE_REGEX.exec(language);
144
+ const code: string = codeNode.getTextContent();
145
+ const tokensResult: TokensResult = shiki.codeToTokens(code, {
146
+ lang: diffLanguageMatch ? diffLanguageMatch[1] : language,
147
+ theme: codeNode.getTheme() || 'poimandres',
148
+ });
149
+ const {tokens, bg, fg} = tokensResult;
150
+ let style = '';
151
+ if (bg) {
152
+ style += `background-color: ${bg};`;
153
+ }
154
+ if (fg) {
155
+ style += `color: ${fg};`;
156
+ }
157
+ if (codeNode.getStyle() !== style) {
158
+ codeNode.setStyle(style);
159
+ }
160
+ return mapTokensToLexicalStructure(tokens, !!diffLanguageMatch);
161
+ }
162
+
163
+ function mapTokensToLexicalStructure(
164
+ tokens: ThemedToken[][],
165
+ diff: boolean,
166
+ ): LexicalNode[] {
167
+ const nodes: LexicalNode[] = [];
168
+
169
+ tokens.forEach((line, idx) => {
170
+ if (idx) {
171
+ nodes.push($createLineBreakNode());
172
+ }
173
+ line.forEach((token, tidx) => {
174
+ let text = token.content;
175
+
176
+ // implement diff-xxxx languages
177
+ if (diff && tidx === 0 && text.length > 0) {
178
+ const prefixes = ['+', '-', '>', '<', ' '];
179
+ const prefixTypes = [
180
+ 'inserted',
181
+ 'deleted',
182
+ 'inserted',
183
+ 'deleted',
184
+ 'unchanged',
185
+ ];
186
+ const prefixIndex = prefixes.indexOf(text[0]);
187
+ if (prefixIndex !== -1) {
188
+ nodes.push(
189
+ $createCodeHighlightNode(
190
+ prefixes[prefixIndex],
191
+ prefixTypes[prefixIndex],
192
+ ),
193
+ );
194
+ text = text.slice(1);
195
+ }
196
+ }
197
+
198
+ const parts = text.split('\t');
199
+ parts.forEach((part: string, pidx: number) => {
200
+ if (pidx) {
201
+ nodes.push($createTabNode());
202
+ }
203
+ if (part !== '') {
204
+ const node = $createCodeHighlightNode(part);
205
+ const style = stringifyTokenStyle(
206
+ token.htmlStyle || getTokenStyleObject(token),
207
+ );
208
+ node.setStyle(style);
209
+ nodes.push(node);
210
+ }
211
+ });
212
+ });
213
+ });
214
+
215
+ return nodes;
216
+ }
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ export {
10
+ type CodeHighlighterShikiConfig,
11
+ CodeHighlighterShikiExtension,
12
+ type CodeShikiConfig,
13
+ CodeShikiExtension,
14
+ registerCodeHighlighting,
15
+ ShikiTokenizer,
16
+ type Tokenizer,
17
+ } from './CodeHighlighterShiki';
18
+ export {
19
+ getCodeLanguageOptions,
20
+ getCodeThemeOptions,
21
+ isCodeLanguageLoaded,
22
+ loadCodeLanguage,
23
+ loadCodeTheme,
24
+ normalizeCodeLanguage,
25
+ } from './FacadeShiki';
@@ -1,9 +0,0 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- */
8
-
9
- "use strict";var e=require("@lexical/code-core"),t=require("@lexical/extension"),n=require("lexical"),i=require("@shikijs/core"),o=require("@shikijs/engine-javascript"),r=require("shiki/langs"),d=require("shiki/themes");const s=i.createHighlighterCoreSync({engine:o.createJavaScriptRegexEngine(),langs:[],themes:[]});function g(e){const t=/^diff-([\w-]+)/i.exec(e);return t?t[1]:null}function a(e){const t=g(e)||e;return!!i.isSpecialLang(t)||s.getLoadedLanguages().includes(t)}function l(t,i,o){const d=g(t),l=d||t;if(!a(l)){const d=r.bundledLanguagesInfo.find(e=>e.id===l||e.aliases&&e.aliases.includes(l));if(d)return s.loadLanguage(d.import()).then(()=>{i&&o&&i.update(()=>{const i=n.$getNodeByKey(o);e.$isCodeNode(i)&&i.getLanguage()===t&&!i.getIsSyntaxHighlightSupported()&&i.setIsSyntaxHighlightSupported(!0)})})}}function u(e){const t=e;return!!i.isSpecialTheme(t)||s.getLoadedThemes().includes(t)}function c(t,i,o){if(!u(t)){const r=d.bundledThemesInfo.find(e=>e.id===t);if(r)return s.loadTheme(r.import()).then(()=>{i&&o&&i.update(()=>{const t=n.$getNodeByKey(o);e.$isCodeNode(t)&&t.markDirty()})})}}function h(t,o){const r=/^diff-([\w-]+)/i.exec(o),d=t.getTextContent(),g=s.codeToTokens(d,{lang:r?r[1]:o,theme:t.getTheme()||"poimandres"}),{tokens:a,bg:l,fg:u}=g;let c="";return l&&(c+=`background-color: ${l};`),u&&(c+=`color: ${u};`),t.getStyle()!==c&&t.setStyle(c),function(t,o){const r=[];return t.forEach((t,d)=>{d&&r.push(n.$createLineBreakNode()),t.forEach((t,d)=>{let s=t.content;if(o&&0===d&&s.length>0){const t=["+","-",">","<"," "],n=["inserted","deleted","inserted","deleted","unchanged"],i=t.indexOf(s[0]);-1!==i&&(r.push(e.$createCodeHighlightNode(t[i],n[i])),s=s.slice(1))}s.split("\t").forEach((o,d)=>{if(d&&r.push(n.$createTabNode()),""!==o){const n=e.$createCodeHighlightNode(o),d=i.stringifyTokenStyle(t.htmlStyle||i.getTokenStyleObject(t));n.setStyle(d),r.push(n)}})})}),r}(a,!!r)}const f={$tokenize(e,t){return h(e,t||this.defaultLanguage)},defaultLanguage:e.DEFAULT_CODE_LANGUAGE,defaultTheme:"one-light"};function p(t,i,o,r){const d=r.getParent();e.$isCodeNode(d)?C(t,i,o,d):e.$isCodeHighlightNode(r)&&r.replace(n.$createTextNode(r.__text))}function m(e,t){const i=t.getElementByKey(e.getKey());if(null===i)return;const o=e.getChildren(),r=o.length;if(r===i.__cachedChildrenLength)return;i.__cachedChildrenLength=r;let d="1",s=1;for(let e=0;e<r;e++)n.$isLineBreakNode(o[e])&&(d+="\n"+ ++s);i.setAttribute("data-gutter",d)}function C(t,i,o,r){const d=r.getKey(),{nodesCurrentlyHighlighting:s}=o;let g=r.getLanguage();g||(g=i.defaultLanguage,r.setLanguage(g));let h=r.getTheme();h||(h=i.defaultTheme,r.setTheme(h));let f=!1;u(h)||(c(h,t,d),f=!0),a(g)?r.getIsSyntaxHighlightSupported()||r.setIsSyntaxHighlightSupported(!0):(r.getIsSyntaxHighlightSupported()&&r.setIsSyntaxHighlightSupported(!1),l(g,t,d),f=!0),f||s.has(d)||(s.add(d),o.didTransform||(o.didTransform=!0,n.$onUpdate(()=>{o.didTransform=!1,s.clear()})),function(t,i){const o=n.$getNodeByKey(t);if(!e.$isCodeNode(o)||!o.isAttached())return;const r=n.$getSelection();if(!n.$isRangeSelection(r))return void i();const d=r.anchor,s=d.offset,g="element"===d.type&&n.$isLineBreakNode(o.getChildAtIndex(d.offset-1));let a=0;if(!g){const e=d.getNode();a=s+e.getPreviousSiblings().reduce((e,t)=>e+t.getTextContentSize(),0)}if(!i())return;if(g)return void d.getNode().select(s,s);o.getChildren().some(e=>{const t=n.$isTextNode(e);if(t||n.$isLineBreakNode(e)){const n=e.getTextContentSize();if(t&&n>=a)return e.select(a,a),!0;a-=n}return!1})}(d,()=>{const t=n.$getNodeByKey(d);if(!e.$isCodeNode(t)||!t.isAttached())return!1;const o=t.getLanguage()||i.defaultLanguage,s=i.$tokenize(t,o),g=function(e,t){let n=0;for(;n<e.length&&x(e[n],t[n]);)n++;const i=e.length,o=t.length,r=Math.min(i,o)-n;let d=0;for(;d<r;)if(d++,!x(e[i-d],t[o-d])){d--;break}const s=n,g=i-d,a=t.slice(n,o-d);return{from:s,nodesForReplacement:a,to:g}}(t.getChildren(),s),{from:a,to:l,nodesForReplacement:u}=g;return!(a===l&&!u.length)&&(r.splice(a,l-a,u),!0)}))}function x(t,i){return e.$isCodeHighlightNode(t)&&e.$isCodeHighlightNode(i)&&t.__text===i.__text&&t.__highlightType===i.__highlightType&&t.__style===i.__style||n.$isTabNode(t)&&n.$isTabNode(i)||n.$isLineBreakNode(t)&&n.$isLineBreakNode(i)}function N(t,i){const o=[];!0!==t._headless&&o.push(t.registerMutationListener(e.CodeNode,e=>{t.getEditorState().read(()=>{for(const[i,o]of e)if("destroyed"!==o){const e=n.$getNodeByKey(i);null!==e&&m(e,t)}})},{skipInitialization:!1}));const r={didTransform:!1,nodesCurrentlyHighlighting:new Set};return o.push(t.registerNodeTransform(e.CodeNode,C.bind(null,t,i,r)),t.registerNodeTransform(n.TextNode,p.bind(null,t,i,r)),t.registerNodeTransform(e.CodeHighlightNode,p.bind(null,t,i,r))),n.mergeRegister(...o)}const y=n.defineExtension({build:(e,n)=>t.namedSignals(n),config:n.safeCast({disabled:!1,tokenizer:f}),dependencies:[e.CodeExtension,e.CodeIndentExtension],name:"@lexical/code-shiki",register:(e,n,i)=>{const o=i.getOutput();return t.effect(()=>{if(!o.disabled.value)return N(e,o.tokenizer.value)})}}),T=n.defineExtension({config:n.safeCast(f),dependencies:[y],init:(e,t,n)=>{n.getDependency(y).config.tokenizer=t},name:"@lexical/code-shiki/legacy"});exports.CodeHighlighterShikiExtension=T,exports.CodeShikiExtension=y,exports.ShikiTokenizer=f,exports.getCodeLanguageOptions=function(){return r.bundledLanguagesInfo.map(e=>[e.id,e.name])},exports.getCodeThemeOptions=function(){return d.bundledThemesInfo.map(e=>[e.id,e.displayName])},exports.isCodeLanguageLoaded=a,exports.loadCodeLanguage=l,exports.loadCodeTheme=c,exports.normalizeCodeLanguage=function(e){const t=e,n=r.bundledLanguagesInfo.find(e=>e.id===t||e.aliases&&e.aliases.includes(t));return n?n.id:e},exports.registerCodeHighlighting=function(t,i=f){if(!t.hasNodes([e.CodeNode,e.CodeHighlightNode]))throw new Error("CodeHighlightPlugin: CodeNode or CodeHighlightNode not registered on editor");return n.mergeRegister(N(t,i),e.registerCodeIndentation(t))};
@@ -1,9 +0,0 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- */
8
-
9
- import{$isCodeNode as e,$createCodeHighlightNode as t,CodeExtension as n,CodeIndentExtension as i,DEFAULT_CODE_LANGUAGE as o,CodeNode as r,CodeHighlightNode as s,$isCodeHighlightNode as l,registerCodeIndentation as d}from"@lexical/code-core";import{effect as a,namedSignals as g}from"@lexical/extension";import{$getNodeByKey as c,$createLineBreakNode as u,$createTabNode as f,defineExtension as h,safeCast as m,TextNode as p,mergeRegister as y,$isLineBreakNode as x,$onUpdate as S,$createTextNode as T,$getSelection as _,$isRangeSelection as k,$isTextNode as L,$isTabNode as C}from"lexical";import{createHighlighterCoreSync as b,isSpecialTheme as H,isSpecialLang as N,stringifyTokenStyle as z,getTokenStyleObject as I}from"@shikijs/core";import{createJavaScriptRegexEngine as v}from"@shikijs/engine-javascript";import{bundledLanguagesInfo as E}from"shiki/langs";import{bundledThemesInfo as w}from"shiki/themes";const A=b({engine:v(),langs:[],themes:[]});function $(e){const t=/^diff-([\w-]+)/i.exec(e);return t?t[1]:null}function j(e){const t=$(e)||e;return!!N(t)||A.getLoadedLanguages().includes(t)}function K(t,n,i){const o=$(t),r=o||t;if(!j(r)){const o=E.find(e=>e.id===r||e.aliases&&e.aliases.includes(r));if(o)return A.loadLanguage(o.import()).then(()=>{n&&i&&n.update(()=>{const n=c(i);e(n)&&n.getLanguage()===t&&!n.getIsSyntaxHighlightSupported()&&n.setIsSyntaxHighlightSupported(!0)})})}}function P(e){const t=e;return!!H(t)||A.getLoadedThemes().includes(t)}function D(t,n,i){if(!P(t)){const o=w.find(e=>e.id===t);if(o)return A.loadTheme(o.import()).then(()=>{n&&i&&n.update(()=>{const t=c(i);e(t)&&t.markDirty()})})}}function F(){return E.map(e=>[e.id,e.name])}function M(){return w.map(e=>[e.id,e.displayName])}function O(e){const t=e,n=E.find(e=>e.id===t||e.aliases&&e.aliases.includes(t));return n?n.id:e}function R(e,n){const i=/^diff-([\w-]+)/i.exec(n),o=e.getTextContent(),r=A.codeToTokens(o,{lang:i?i[1]:n,theme:e.getTheme()||"poimandres"}),{tokens:s,bg:l,fg:d}=r;let a="";return l&&(a+=`background-color: ${l};`),d&&(a+=`color: ${d};`),e.getStyle()!==a&&e.setStyle(a),function(e,n){const i=[];return e.forEach((e,o)=>{o&&i.push(u()),e.forEach((e,o)=>{let r=e.content;if(n&&0===o&&r.length>0){const e=["+","-",">","<"," "],n=["inserted","deleted","inserted","deleted","unchanged"],o=e.indexOf(r[0]);-1!==o&&(i.push(t(e[o],n[o])),r=r.slice(1))}r.split("\t").forEach((n,o)=>{if(o&&i.push(f()),""!==n){const o=t(n),r=z(e.htmlStyle||I(e));o.setStyle(r),i.push(o)}})})}),i}(s,!!i)}const B={$tokenize(e,t){return R(e,t||this.defaultLanguage)},defaultLanguage:o,defaultTheme:"one-light"};function q(t,n,i,o){const r=o.getParent();e(r)?J(t,n,i,r):l(o)&&o.replace(T(o.__text))}function G(e,t){const n=t.getElementByKey(e.getKey());if(null===n)return;const i=e.getChildren(),o=i.length;if(o===n.__cachedChildrenLength)return;n.__cachedChildrenLength=o;let r="1",s=1;for(let e=0;e<o;e++)x(i[e])&&(r+="\n"+ ++s);n.setAttribute("data-gutter",r)}function J(t,n,i,o){const r=o.getKey(),{nodesCurrentlyHighlighting:s}=i;let l=o.getLanguage();l||(l=n.defaultLanguage,o.setLanguage(l));let d=o.getTheme();d||(d=n.defaultTheme,o.setTheme(d));let a=!1;P(d)||(D(d,t,r),a=!0),j(l)?o.getIsSyntaxHighlightSupported()||o.setIsSyntaxHighlightSupported(!0):(o.getIsSyntaxHighlightSupported()&&o.setIsSyntaxHighlightSupported(!1),K(l,t,r),a=!0),a||s.has(r)||(s.add(r),i.didTransform||(i.didTransform=!0,S(()=>{i.didTransform=!1,s.clear()})),function(t,n){const i=c(t);if(!e(i)||!i.isAttached())return;const o=_();if(!k(o))return void n();const r=o.anchor,s=r.offset,l="element"===r.type&&x(i.getChildAtIndex(r.offset-1));let d=0;if(!l){const e=r.getNode();d=s+e.getPreviousSiblings().reduce((e,t)=>e+t.getTextContentSize(),0)}if(!n())return;if(l)return void r.getNode().select(s,s);i.getChildren().some(e=>{const t=L(e);if(t||x(e)){const n=e.getTextContentSize();if(t&&n>=d)return e.select(d,d),!0;d-=n}return!1})}(r,()=>{const t=c(r);if(!e(t)||!t.isAttached())return!1;const i=t.getLanguage()||n.defaultLanguage,s=n.$tokenize(t,i),l=function(e,t){let n=0;for(;n<e.length&&Q(e[n],t[n]);)n++;const i=e.length,o=t.length,r=Math.min(i,o)-n;let s=0;for(;s<r;)if(s++,!Q(e[i-s],t[o-s])){s--;break}const l=n,d=i-s,a=t.slice(n,o-s);return{from:l,nodesForReplacement:a,to:d}}(t.getChildren(),s),{from:d,to:a,nodesForReplacement:g}=l;return!(d===a&&!g.length)&&(o.splice(d,a-d,g),!0)}))}function Q(e,t){return l(e)&&l(t)&&e.__text===t.__text&&e.__highlightType===t.__highlightType&&e.__style===t.__style||C(e)&&C(t)||x(e)&&x(t)}function U(e,t){const n=[];!0!==e._headless&&n.push(e.registerMutationListener(r,t=>{e.getEditorState().read(()=>{for(const[n,i]of t)if("destroyed"!==i){const t=c(n);null!==t&&G(t,e)}})},{skipInitialization:!1}));const i={didTransform:!1,nodesCurrentlyHighlighting:new Set};return n.push(e.registerNodeTransform(r,J.bind(null,e,t,i)),e.registerNodeTransform(p,q.bind(null,e,t,i)),e.registerNodeTransform(s,q.bind(null,e,t,i))),y(...n)}function V(e,t=B){if(!e.hasNodes([r,s]))throw new Error("CodeHighlightPlugin: CodeNode or CodeHighlightNode not registered on editor");return y(U(e,t),d(e))}const W=h({build:(e,t)=>g(t),config:m({disabled:!1,tokenizer:B}),dependencies:[n,i],name:"@lexical/code-shiki",register:(e,t,n)=>{const i=n.getOutput();return a(()=>{if(!i.disabled.value)return U(e,i.tokenizer.value)})}}),X=h({config:m(B),dependencies:[W],init:(e,t,n)=>{n.getDependency(W).config.tokenizer=t},name:"@lexical/code-shiki/legacy"});export{X as CodeHighlighterShikiExtension,W as CodeShikiExtension,B as ShikiTokenizer,F as getCodeLanguageOptions,M as getCodeThemeOptions,j as isCodeLanguageLoaded,K as loadCodeLanguage,D as loadCodeTheme,O as normalizeCodeLanguage,V as registerCodeHighlighting};
File without changes
File without changes