@pierre/diffs 1.3.0-beta.1 → 1.3.0-beta.3

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.
Files changed (82) hide show
  1. package/dist/components/CodeView.d.ts +4 -0
  2. package/dist/components/CodeView.d.ts.map +1 -1
  3. package/dist/components/CodeView.js +38 -0
  4. package/dist/components/CodeView.js.map +1 -1
  5. package/dist/components/File.d.ts.map +1 -1
  6. package/dist/components/File.js +9 -9
  7. package/dist/components/File.js.map +1 -1
  8. package/dist/components/FileDiff.d.ts.map +1 -1
  9. package/dist/components/FileDiff.js +3 -2
  10. package/dist/components/FileDiff.js.map +1 -1
  11. package/dist/components/VirtualizedFile.js +6 -1
  12. package/dist/components/VirtualizedFile.js.map +1 -1
  13. package/dist/components/VirtualizedFileDiff.js +22 -42
  14. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  15. package/dist/components/Virtualizer.js +5 -3
  16. package/dist/components/Virtualizer.js.map +1 -1
  17. package/dist/editor/editor.d.ts +7 -1
  18. package/dist/editor/editor.d.ts.map +1 -1
  19. package/dist/editor/editor.js +550 -405
  20. package/dist/editor/editor.js.map +1 -1
  21. package/dist/editor/editor2.js +6 -0
  22. package/dist/editor/editor2.js.map +1 -0
  23. package/dist/editor/pieceTable.d.ts +1 -1
  24. package/dist/editor/pieceTable.d.ts.map +1 -1
  25. package/dist/editor/pieceTable.js +2 -22
  26. package/dist/editor/pieceTable.js.map +1 -1
  27. package/dist/editor/quickEdit.js +2 -4
  28. package/dist/editor/quickEdit.js.map +1 -1
  29. package/dist/editor/searchPanel.d.ts +6 -7
  30. package/dist/editor/searchPanel.d.ts.map +1 -1
  31. package/dist/editor/searchPanel.js +102 -137
  32. package/dist/editor/searchPanel.js.map +1 -1
  33. package/dist/editor/selection.js +8 -2
  34. package/dist/editor/selection.js.map +1 -1
  35. package/dist/editor/sprite.d.ts +7 -0
  36. package/dist/editor/sprite.d.ts.map +1 -0
  37. package/dist/editor/sprite.js +38 -0
  38. package/dist/editor/sprite.js.map +1 -0
  39. package/dist/editor/textDocument.d.ts +1 -1
  40. package/dist/editor/textDocument.d.ts.map +1 -1
  41. package/dist/editor/textDocument.js +2 -2
  42. package/dist/editor/textDocument.js.map +1 -1
  43. package/dist/editor/textMeasure.js +3 -3
  44. package/dist/editor/textMeasure.js.map +1 -1
  45. package/dist/editor/tokenzier.d.ts +6 -2
  46. package/dist/editor/tokenzier.d.ts.map +1 -1
  47. package/dist/editor/tokenzier.js +127 -85
  48. package/dist/editor/tokenzier.js.map +1 -1
  49. package/dist/index.d.ts +2 -2
  50. package/dist/react/index.d.ts +2 -2
  51. package/dist/react/jsx.d.ts +1 -0
  52. package/dist/react/jsx.d.ts.map +1 -1
  53. package/dist/react/types.js +1 -0
  54. package/dist/renderers/DiffHunksRenderer.js +5 -9
  55. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  56. package/dist/ssr/index.d.ts +2 -2
  57. package/dist/style.js +1 -1
  58. package/dist/style.js.map +1 -1
  59. package/dist/types.d.ts +13 -12
  60. package/dist/types.d.ts.map +1 -1
  61. package/dist/utils/computeEstimatedDiffHeights.js +9 -20
  62. package/dist/utils/computeEstimatedDiffHeights.js.map +1 -1
  63. package/dist/utils/iterateOverDiff.js +147 -182
  64. package/dist/utils/iterateOverDiff.js.map +1 -1
  65. package/dist/utils/virtualDiffLayout.d.ts +23 -2
  66. package/dist/utils/virtualDiffLayout.d.ts.map +1 -1
  67. package/dist/utils/virtualDiffLayout.js +41 -1
  68. package/dist/utils/virtualDiffLayout.js.map +1 -1
  69. package/dist/worker/WorkerPoolManager.js +1 -1
  70. package/dist/worker/WorkerPoolManager.js.map +1 -1
  71. package/dist/worker/{wasm-D4DU5jgR.js → wasm-BaDzIkIn.js} +2 -2
  72. package/dist/worker/wasm-BaDzIkIn.js.map +1 -0
  73. package/dist/worker/worker-portable.js +294 -292
  74. package/dist/worker/worker-portable.js.map +1 -1
  75. package/dist/worker/worker.js +179 -181
  76. package/dist/worker/worker.js.map +1 -1
  77. package/package.json +22 -21
  78. package/dist/editor/css.d.ts +0 -6
  79. package/dist/editor/css.d.ts.map +0 -1
  80. package/dist/editor/css.js +0 -218
  81. package/dist/editor/css.js.map +0 -1
  82. package/dist/worker/wasm-D4DU5jgR.js.map +0 -1
@@ -18,15 +18,15 @@ var Metrics = class {
18
18
  this.#canvasCtx ??= document.createElement("canvas").getContext("2d") ?? void 0;
19
19
  if (this.#canvasCtx === void 0) throw new Error("Could not get canvas context");
20
20
  const { fontSize, fontFamily, tabSize, lineHeight } = getComputedStyle(root);
21
- if (lineHeight.endsWith("px")) this.lineHeight = Number(lineHeight.slice(0, -2));
22
- else if (fontSize.endsWith("px")) this.lineHeight = round(Number(fontSize.slice(0, -2)) * Number(lineHeight));
21
+ if (lineHeight.endsWith("px")) this.lineHeight = parseFloat(lineHeight.slice(0, -2));
22
+ else if (fontSize.endsWith("px")) this.lineHeight = round(parseFloat(fontSize.slice(0, -2)) * parseFloat(lineHeight));
23
23
  const font = fontSize + " " + fontFamily;
24
24
  if (this.#font !== font || this.ch === -1) {
25
25
  this.#font = font;
26
26
  this.#canvasCtx.font = font;
27
27
  this.ch = this.canvasMeasureTextWidth("0");
28
28
  }
29
- this.tabSize = Number(tabSize);
29
+ this.tabSize = parseInt(tabSize, 10);
30
30
  }
31
31
  /** measure the width of the text */
32
32
  measureTextWidth(text) {
@@ -1 +1 @@
1
- {"version":3,"file":"textMeasure.js","names":["#root","#canvasCtx","#font"],"sources":["../../src/editor/textMeasure.ts"],"sourcesContent":["import { h, round } from './utils';\n\nexport class Metrics {\n #root?: HTMLElement;\n #canvasCtx?: CanvasRenderingContext2D;\n #font?: string;\n\n /** Width of the '0' character. */\n ch: number = -1;\n /** Size of a tab(\\t) character. */\n tabSize: number = 2;\n /** Height of the code line. */\n lineHeight: number = 20;\n\n /** initialize the metrics */\n init(root: HTMLElement): void {\n if (\n this.#root === root &&\n this.#canvasCtx !== undefined &&\n this.ch !== -1\n ) {\n // already initialized\n return;\n }\n\n this.#root = root;\n this.#canvasCtx ??=\n document.createElement('canvas').getContext('2d') ?? undefined;\n if (this.#canvasCtx === undefined) {\n throw new Error('Could not get canvas context');\n }\n\n const { fontSize, fontFamily, tabSize, lineHeight } =\n getComputedStyle(root);\n if (lineHeight.endsWith('px')) {\n this.lineHeight = Number(lineHeight.slice(0, -2));\n } else if (fontSize.endsWith('px')) {\n this.lineHeight = round(\n Number(fontSize.slice(0, -2)) * Number(lineHeight)\n );\n }\n const font = fontSize + ' ' + fontFamily;\n if (this.#font !== font || this.ch === -1) {\n this.#font = font;\n this.#canvasCtx.font = font;\n this.ch = this.canvasMeasureTextWidth('0');\n }\n this.tabSize = Number(tabSize);\n }\n\n /** measure the width of the text */\n measureTextWidth(text: string): number {\n const textWithExpandedTabs = text.replaceAll(\n '\\t',\n ' '.repeat(this.tabSize)\n );\n if (needsDomTextMeasurement(textWithExpandedTabs)) {\n return this.domMeasureTextWidth(textWithExpandedTabs);\n }\n return this.canvasMeasureTextWidth(textWithExpandedTabs);\n }\n\n /** measure the width of the text using the canvas measureText API */\n canvasMeasureTextWidth(text: string): number {\n if (this.#canvasCtx === undefined) {\n throw new Error('Metrics not initialized');\n }\n return round(this.#canvasCtx.measureText(text).width);\n }\n\n /**\n * measure the width of the text using the DOM\n * this is slow because it cause a reflow, use it for non-ascii text\n */\n domMeasureTextWidth(text: string): number {\n if (this.#root === undefined) {\n throw new Error('Metrics not initialized');\n }\n const measureEl = h(\n 'span',\n {\n style: {\n position: 'absolute',\n top: '0',\n left: '0',\n visibility: 'hidden',\n pointerEvents: 'none',\n whiteSpace: 'pre',\n font: 'inherit',\n },\n textContent: text,\n },\n this.#root\n );\n try {\n return measureEl.getBoundingClientRect().width;\n } finally {\n measureEl.remove();\n }\n }\n}\n\n/** Check if the text needs DOM text measurement. */\nexport function needsDomTextMeasurement(text: string): boolean {\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n if (\n (code >= 0xd800 && code <= 0xdfff) ||\n code === 0x200d ||\n code === 0xfe0e ||\n code === 0xfe0f\n ) {\n return true;\n }\n }\n return false;\n}\n\n/** snap the text offset to the Unicode boundary */\nexport function snapTextOffsetToUnicodeBoundary(\n text: string,\n offset: number\n): number {\n const boundedOffset = Math.max(0, Math.min(offset, text.length));\n if (\n boundedOffset === 0 ||\n boundedOffset === text.length ||\n !needsDomTextMeasurement(text)\n ) {\n return boundedOffset;\n }\n // Avoid measuring a caret position inside one visual emoji/grapheme.\n // Browser caret movement can report offsets around UTF-16 surrogate\n // pairs and emoji joiners; measuring a partial sequence gives a\n // replacement-glyph width.\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: 'grapheme',\n });\n for (const segment of segmenter.segment(text)) {\n const segmentStart = segment.index;\n const segmentEnd = segmentStart + segment.segment.length;\n if (boundedOffset > segmentStart && boundedOffset < segmentEnd) {\n return segmentEnd;\n }\n if (boundedOffset <= segmentStart) {\n break;\n }\n }\n return boundedOffset;\n}\n\n/** get the offsets of the Unicode grapheme clusters in the text */\nexport function getUnicodeMeasurementOffsets(\n text: string\n): number[] | undefined {\n if (!needsDomTextMeasurement(text)) {\n return undefined;\n }\n const offsets = [0];\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: 'grapheme',\n });\n for (const segment of segmenter.segment(text)) {\n offsets.push(segment.index + segment.segment.length);\n }\n return offsets;\n}\n\n/** get the number of columns of the ASCII text */\nexport function getExpandedAsciiTextColumns(\n text: string,\n tabSize: number\n): number {\n let columns = 0;\n for (let i = 0; i < text.length; i++) {\n if (text.charCodeAt(i) > 127) {\n return -1;\n }\n columns += text.charCodeAt(i) === /* '\\t' */ 9 ? tabSize : 1;\n }\n return columns;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"textMeasure.js","names":["#root","#canvasCtx","#font"],"sources":["../../src/editor/textMeasure.ts"],"sourcesContent":["import { h, round } from './utils';\n\nexport class Metrics {\n #root?: HTMLElement;\n #canvasCtx?: CanvasRenderingContext2D;\n #font?: string;\n\n /** Width of the '0' character. */\n ch: number = -1;\n /** Size of a tab(\\t) character. */\n tabSize: number = 2;\n /** Height of the code line. */\n lineHeight: number = 20;\n\n /** initialize the metrics */\n init(root: HTMLElement): void {\n if (\n this.#root === root &&\n this.#canvasCtx !== undefined &&\n this.ch !== -1\n ) {\n // already initialized\n return;\n }\n\n this.#root = root;\n this.#canvasCtx ??=\n document.createElement('canvas').getContext('2d') ?? undefined;\n if (this.#canvasCtx === undefined) {\n throw new Error('Could not get canvas context');\n }\n\n const { fontSize, fontFamily, tabSize, lineHeight } =\n getComputedStyle(root);\n if (lineHeight.endsWith('px')) {\n this.lineHeight = parseFloat(lineHeight.slice(0, -2));\n } else if (fontSize.endsWith('px')) {\n this.lineHeight = round(\n parseFloat(fontSize.slice(0, -2)) * parseFloat(lineHeight)\n );\n }\n const font = fontSize + ' ' + fontFamily;\n if (this.#font !== font || this.ch === -1) {\n this.#font = font;\n this.#canvasCtx.font = font;\n this.ch = this.canvasMeasureTextWidth('0');\n }\n this.tabSize = parseInt(tabSize, 10);\n }\n\n /** measure the width of the text */\n measureTextWidth(text: string): number {\n const textWithExpandedTabs = text.replaceAll(\n '\\t',\n ' '.repeat(this.tabSize)\n );\n if (needsDomTextMeasurement(textWithExpandedTabs)) {\n return this.domMeasureTextWidth(textWithExpandedTabs);\n }\n return this.canvasMeasureTextWidth(textWithExpandedTabs);\n }\n\n /** measure the width of the text using the canvas measureText API */\n canvasMeasureTextWidth(text: string): number {\n if (this.#canvasCtx === undefined) {\n throw new Error('Metrics not initialized');\n }\n return round(this.#canvasCtx.measureText(text).width);\n }\n\n /**\n * measure the width of the text using the DOM\n * this is slow because it cause a reflow, use it for non-ascii text\n */\n domMeasureTextWidth(text: string): number {\n if (this.#root === undefined) {\n throw new Error('Metrics not initialized');\n }\n const measureEl = h(\n 'span',\n {\n style: {\n position: 'absolute',\n top: '0',\n left: '0',\n visibility: 'hidden',\n pointerEvents: 'none',\n whiteSpace: 'pre',\n font: 'inherit',\n },\n textContent: text,\n },\n this.#root\n );\n try {\n return measureEl.getBoundingClientRect().width;\n } finally {\n measureEl.remove();\n }\n }\n}\n\n/** Check if the text needs DOM text measurement. */\nexport function needsDomTextMeasurement(text: string): boolean {\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n if (\n (code >= 0xd800 && code <= 0xdfff) ||\n code === 0x200d ||\n code === 0xfe0e ||\n code === 0xfe0f\n ) {\n return true;\n }\n }\n return false;\n}\n\n/** snap the text offset to the Unicode boundary */\nexport function snapTextOffsetToUnicodeBoundary(\n text: string,\n offset: number\n): number {\n const boundedOffset = Math.max(0, Math.min(offset, text.length));\n if (\n boundedOffset === 0 ||\n boundedOffset === text.length ||\n !needsDomTextMeasurement(text)\n ) {\n return boundedOffset;\n }\n // Avoid measuring a caret position inside one visual emoji/grapheme.\n // Browser caret movement can report offsets around UTF-16 surrogate\n // pairs and emoji joiners; measuring a partial sequence gives a\n // replacement-glyph width.\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: 'grapheme',\n });\n for (const segment of segmenter.segment(text)) {\n const segmentStart = segment.index;\n const segmentEnd = segmentStart + segment.segment.length;\n if (boundedOffset > segmentStart && boundedOffset < segmentEnd) {\n return segmentEnd;\n }\n if (boundedOffset <= segmentStart) {\n break;\n }\n }\n return boundedOffset;\n}\n\n/** get the offsets of the Unicode grapheme clusters in the text */\nexport function getUnicodeMeasurementOffsets(\n text: string\n): number[] | undefined {\n if (!needsDomTextMeasurement(text)) {\n return undefined;\n }\n const offsets = [0];\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: 'grapheme',\n });\n for (const segment of segmenter.segment(text)) {\n offsets.push(segment.index + segment.segment.length);\n }\n return offsets;\n}\n\n/** get the number of columns of the ASCII text */\nexport function getExpandedAsciiTextColumns(\n text: string,\n tabSize: number\n): number {\n let columns = 0;\n for (let i = 0; i < text.length; i++) {\n if (text.charCodeAt(i) > 127) {\n return -1;\n }\n columns += text.charCodeAt(i) === /* '\\t' */ 9 ? tabSize : 1;\n }\n return columns;\n}\n"],"mappings":"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"}
@@ -9,6 +9,7 @@ interface EditorTokenizerProps {
9
9
  codeOptions: BaseCodeOptions;
10
10
  setStyle: (style: string) => void;
11
11
  onDeferTokenize: (lines: Map<number, Array<HighlightedToken>>, themeType: 'dark' | 'light') => void;
12
+ __debug?: boolean;
12
13
  }
13
14
  /** Stoppable code tokenizer for the editor */
14
15
  declare class EditorTokenizer {
@@ -20,12 +21,15 @@ declare class EditorTokenizer {
20
21
  highlighter,
21
22
  textDocument,
22
23
  setStyle,
23
- onDeferTokenize
24
+ onDeferTokenize,
25
+ __debug
24
26
  }: EditorTokenizerProps);
25
27
  cleanUp(): void;
26
28
  tokenize(change: TextDocumentChange, renderRange?: RenderRange): Map<number, Array<HighlightedToken>>;
27
- prebuildStateStackMap(renderRange?: RenderRange): void;
29
+ prebuildStateStack(renderRange?: RenderRange): void;
28
30
  stopBackgroundTokenize(): void;
31
+ pauseBackgroundTokenize(): void;
32
+ resumeBackgroundTokenize(): void;
29
33
  }
30
34
  declare function tokenizeLine(grammar: IGrammar, colorMap: string[], lineText: string, stateStack: StateStack, timeLimit?: number): {
31
35
  ruleStack: StateStack;
@@ -1 +1 @@
1
- {"version":3,"file":"tokenzier.d.ts","names":["IGrammar","StateStack","BaseCodeOptions","DiffsHighlighter","HighlightedToken","RenderRange","TextDocument","TextDocumentChange","EditorTokenizerProps","Array","Map","EditorTokenizer","codeOptions","highlighter","textDocument","setStyle","onDeferTokenize","tokenizeLine","renderLineTokens","HTMLElement"],"sources":["../../src/editor/tokenzier.d.ts"],"sourcesContent":["import { type IGrammar, type StateStack } from 'shiki/textmate';\nimport type { BaseCodeOptions, DiffsHighlighter, HighlightedToken, RenderRange } from '../types';\nimport type { TextDocument, TextDocumentChange } from './textDocument';\nexport interface EditorTokenizerProps {\n highlighter: DiffsHighlighter;\n textDocument: TextDocument<unknown>;\n codeOptions: BaseCodeOptions;\n setStyle: (style: string) => void;\n onDeferTokenize: (lines: Map<number, Array<HighlightedToken>>, themeType: 'dark' | 'light') => void;\n}\n/** Stoppable code tokenizer for the editor */\nexport declare class EditorTokenizer {\n #private;\n static TOKENIZE_TIME_LIMIT: number;\n get themeType(): 'light' | 'dark';\n constructor({ codeOptions, highlighter, textDocument, setStyle, onDeferTokenize }: EditorTokenizerProps);\n cleanUp(): void;\n tokenize(change: TextDocumentChange, renderRange?: RenderRange): Map<number, Array<HighlightedToken>>;\n prebuildStateStackMap(renderRange?: RenderRange): void;\n stopBackgroundTokenize(): void;\n}\nexport declare function tokenizeLine(grammar: IGrammar, colorMap: string[], lineText: string, stateStack: StateStack, timeLimit?: number): {\n ruleStack: StateStack;\n resolvedTokens: Array<HighlightedToken>;\n};\nexport declare function renderLineTokens(tokens: Array<HighlightedToken>, themeType: 'light' | 'dark'): (HTMLElement | string)[];\n//# sourceMappingURL=tokenzier.d.ts.map"],"mappings":";;;;;UAGiBQ,oBAAAA;eACAL;EADAK,YAAAA,EAECF,YAFmB,CAAA,OAAA,CAAA;EACpBH,WAAAA,EAEAD,eAFAC;EACCG,QAAAA,EAAAA,CAAAA,KAAAA,EAAAA,MAAAA,EAAAA,GAAAA,IAAAA;EACDJ,eAAAA,EAAAA,CAAAA,KAAAA,EAEYQ,GAFZR,CAAAA,MAAAA,EAEwBO,KAFxBP,CAE8BE,gBAF9BF,CAAAA,CAAAA,EAAAA,SAAAA,EAAAA,MAAAA,GAAAA,OAAAA,EAAAA,GAAAA,IAAAA;;;AAEYQ,cAGRC,eAAAA,CAHQD;EAAG,CAAA,OAAA;EAGXC,OAAAA,mBAAe,EAAA,MAAA;EAIlBC,IAAAA,SAAAA,CAAAA,CAAAA,EAAAA,OAAAA,GAAAA,MAAAA;EAAaC,WAAAA,CAAAA;IAAAA,WAAAA;IAAAA,WAAAA;IAAAA,YAAAA;IAAAA,QAAAA;IAAAA;EAAAA,CAAAA,EAAwDL,oBAAxDK;EAAaC,OAAAA,CAAAA,CAAAA,EAAAA,IAAAA;EAAcC,QAAAA,CAAAA,MAAAA,EAErCR,kBAFqCQ,EAAAA,WAAAA,CAAAA,EAEHV,WAFGU,CAAAA,EAEWL,GAFXK,CAAAA,MAAAA,EAEuBN,KAFvBM,CAE6BX,gBAF7BW,CAAAA,CAAAA;EAAUC,qBAAAA,CAAAA,WAAAA,CAAAA,EAG5BX,WAH4BW,CAAAA,EAAAA,IAAAA;EAAmBR,sBAAAA,CAAAA,CAAAA,EAAAA,IAAAA;;AAEhCH,iBAI/BY,YAAAA,CAJ+BZ,OAAAA,EAITL,QAJSK,EAAAA,QAAAA,EAAAA,MAAAA,EAAAA,EAAAA,QAAAA,EAAAA,MAAAA,EAAAA,UAAAA,EAImDJ,UAJnDI,EAAAA,SAAAA,CAAAA,EAAAA,MAAAA,CAAAA,EAAAA;EAAgCD,SAAAA,EAKxEH,UALwEG;EAANK,cAAAA,EAM7DA,KAN6DA,CAMvDL,gBANuDK,CAAAA;CAAZC;AAC7BL,iBAOhBa,gBAAAA,CAPgBb,MAAAA,EAOSI,KAPTJ,CAOeD,gBAPfC,CAAAA,EAAAA,SAAAA,EAAAA,OAAAA,GAAAA,MAAAA,CAAAA,EAAAA,CAOiEc,WAPjEd,GAAAA,MAAAA,CAAAA,EAAAA"}
1
+ {"version":3,"file":"tokenzier.d.ts","names":["IGrammar","StateStack","BaseCodeOptions","DiffsHighlighter","HighlightedToken","RenderRange","TextDocument","TextDocumentChange","EditorTokenizerProps","Array","Map","EditorTokenizer","codeOptions","highlighter","textDocument","setStyle","onDeferTokenize","__debug","tokenizeLine","renderLineTokens","HTMLElement"],"sources":["../../src/editor/tokenzier.d.ts"],"sourcesContent":["import { type IGrammar, type StateStack } from 'shiki/textmate';\nimport type { BaseCodeOptions, DiffsHighlighter, HighlightedToken, RenderRange } from '../types';\nimport type { TextDocument, TextDocumentChange } from './textDocument';\nexport interface EditorTokenizerProps {\n highlighter: DiffsHighlighter;\n textDocument: TextDocument<unknown>;\n codeOptions: BaseCodeOptions;\n setStyle: (style: string) => void;\n onDeferTokenize: (lines: Map<number, Array<HighlightedToken>>, themeType: 'dark' | 'light') => void;\n __debug?: boolean;\n}\n/** Stoppable code tokenizer for the editor */\nexport declare class EditorTokenizer {\n #private;\n static TOKENIZE_TIME_LIMIT: number;\n get themeType(): 'light' | 'dark';\n constructor({ codeOptions, highlighter, textDocument, setStyle, onDeferTokenize, __debug }: EditorTokenizerProps);\n cleanUp(): void;\n tokenize(change: TextDocumentChange, renderRange?: RenderRange): Map<number, Array<HighlightedToken>>;\n prebuildStateStack(renderRange?: RenderRange): void;\n stopBackgroundTokenize(): void;\n pauseBackgroundTokenize(): void;\n resumeBackgroundTokenize(): void;\n}\nexport declare function tokenizeLine(grammar: IGrammar, colorMap: string[], lineText: string, stateStack: StateStack, timeLimit?: number): {\n ruleStack: StateStack;\n resolvedTokens: Array<HighlightedToken>;\n};\nexport declare function renderLineTokens(tokens: Array<HighlightedToken>, themeType: 'light' | 'dark'): (HTMLElement | string)[];\n//# sourceMappingURL=tokenzier.d.ts.map"],"mappings":";;;;;UAGiBQ,oBAAAA;eACAL;EADAK,YAAAA,EAECF,YAFmB,CAAA,OAAA,CAAA;EACpBH,WAAAA,EAEAD,eAFAC;EACCG,QAAAA,EAAAA,CAAAA,KAAAA,EAAAA,MAAAA,EAAAA,GAAAA,IAAAA;EACDJ,eAAAA,EAAAA,CAAAA,KAAAA,EAEYQ,GAFZR,CAAAA,MAAAA,EAEwBO,KAFxBP,CAE8BE,gBAF9BF,CAAAA,CAAAA,EAAAA,SAAAA,EAAAA,MAAAA,GAAAA,OAAAA,EAAAA,GAAAA,IAAAA;EAE8BE,OAAAA,CAAAA,EAAAA,OAAAA;;;AAAf,cAIXO,eAAAA,CAJW;EAIXA,CAAAA,OAAAA;EAIHC,OAAAA,mBAAAA,EAAAA,MAAAA;EAAaC,IAAAA,SAAAA,CAAAA,CAAAA,EAAAA,OAAAA,GAAAA,MAAAA;EAAaC,WAAAA,CAAAA;IAAAA,WAAAA;IAAAA,WAAAA;IAAAA,YAAAA;IAAAA,QAAAA;IAAAA,eAAAA;IAAAA;EAAAA,CAAAA,EAAoDN,oBAApDM;EAAcC,OAAAA,CAAAA,CAAAA,EAAAA,IAAAA;EAAUC,QAAAA,CAAAA,MAAAA,EAE/CT,kBAF+CS,EAAAA,WAAAA,CAAAA,EAEbX,WAFaW,CAAAA,EAECN,GAFDM,CAAAA,MAAAA,EAEaP,KAFbO,CAEmBZ,gBAFnBY,CAAAA,CAAAA;EAAiBC,kBAAAA,CAAAA,WAAAA,CAAAA,EAGhDZ,WAHgDY,CAAAA,EAAAA,IAAAA;EAAWT,sBAAAA,CAAAA,CAAAA,EAAAA,IAAAA;EAE3ED,uBAAAA,CAAAA,CAAAA,EAAAA,IAAAA;EAAkCF,wBAAAA,CAAAA,CAAAA,EAAAA,IAAAA;;AAA0BI,iBAMzDS,YAAAA,CANyDT,OAAAA,EAMnCT,QANmCS,EAAAA,QAAAA,EAAAA,MAAAA,EAAAA,EAAAA,QAAAA,EAAAA,MAAAA,EAAAA,UAAAA,EAMyBR,UANzBQ,EAAAA,SAAAA,CAAAA,EAAAA,MAAAA,CAAAA,EAAAA;EAAZC,SAAAA,EAOtDT,UAPsDS;EAChCL,cAAAA,EAOjBI,KAPiBJ,CAOXD,gBAPWC,CAAAA;CAAW;AAKxBa,iBAIAC,gBAAAA,CAJY,MAAA,EAIaV,KAJb,CAImBL,gBAJnB,CAAA,EAAA,SAAA,EAAA,OAAA,GAAA,MAAA,CAAA,EAAA,CAIqEgB,WAJrE,GAAA,MAAA,CAAA,EAAA"}
@@ -15,84 +15,96 @@ var EditorTokenizer = class EditorTokenizer {
15
15
  #tokenizeMaxLineLength;
16
16
  #setStyle;
17
17
  #onDeferTokenize;
18
- #editorEventDisposes;
19
- #stateStackCache = [INITIAL];
18
+ #debug;
19
+ #disposes;
20
+ #stateStack = [INITIAL];
20
21
  #lastLine = -1;
21
22
  #isStopped = true;
23
+ #isPaused = false;
22
24
  #backgroundJobId = 0;
23
25
  #backgroundChangedLineRanges;
24
26
  #backgroundChangedRangeIndex = 0;
25
- #prebuildStateStackMap = debounce(async (renderRange) => {
27
+ #isMessageListenerAttached = false;
28
+ #prebuildStateStack = debounce(async (renderRange) => {
26
29
  const { startingLine = 0, totalLines = Infinity } = renderRange ?? {};
27
30
  const endLine = Math.min(totalLines === Infinity ? Infinity : startingLine + totalLines, this.#textDocument.lineCount);
28
31
  if (this.#grammar === void 0) {
29
32
  await this.#highlighter.loadLanguage(this.#textDocument.languageId);
30
33
  this.#grammar = this.#highlighter.getLanguage(this.#textDocument.languageId);
31
34
  }
32
- this.#buildStateStackMap(endLine);
35
+ this.#buildStateStack(endLine);
33
36
  }, 500);
34
37
  #onMessage = ({ data }) => {
35
- if (data.type === "tokenize" && data.jobId === this.#backgroundJobId) this.#backgroundTokenize(data.jobId);
38
+ if (typeof data !== "object" || data === null) return;
39
+ const { type, jobId } = data;
40
+ if (type === "tokenize" && typeof jobId === "number" && jobId === this.#backgroundJobId) this.#backgroundTokenize(jobId);
36
41
  };
37
- #onThemeChange = (themeName, themeType) => {
42
+ get themeType() {
43
+ return this.#themeType;
44
+ }
45
+ constructor({ codeOptions, highlighter, textDocument, setStyle, onDeferTokenize, __debug }) {
46
+ const { themeType = "system", theme = DEFAULT_THEMES, tokenizeMaxLineLength = 1e3 } = codeOptions;
47
+ this.#mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
48
+ if (themeType === "system") this.#themeType = this.#mediaQueryList.matches ? "dark" : "light";
49
+ else this.#themeType = themeType;
50
+ if (typeof theme !== "string") {
51
+ const observer = new MutationObserver((mutations) => {
52
+ for (const { type, attributeName } of mutations) if (type === "attributes" && attributeName !== null && (attributeName === "class" || attributeName.startsWith("data-"))) {
53
+ const themeType$1 = getComputedStyle(document.body).colorScheme === "dark" ? "dark" : "light";
54
+ this.#emitThemeChange(theme[themeType$1], themeType$1);
55
+ break;
56
+ }
57
+ });
58
+ observer.observe(document.documentElement, { attributes: true });
59
+ observer.observe(document.body, { attributes: true });
60
+ this.#disposes = [addEventListener(this.#mediaQueryList, "change", (e) => {
61
+ const themeType$1 = e.matches ? "dark" : "light";
62
+ this.#emitThemeChange(theme[themeType$1], themeType$1);
63
+ }), () => observer.disconnect()];
64
+ }
65
+ this.#highlighter = highlighter;
66
+ this.#textDocument = textDocument;
67
+ this.#tokenizeMaxLineLength = tokenizeMaxLineLength;
68
+ this.#setStyle = setStyle;
69
+ this.#onDeferTokenize = onDeferTokenize;
70
+ this.#debug = __debug ?? false;
71
+ if (highlighter.getLoadedLanguages().includes(textDocument.languageId)) this.#grammar = highlighter.getLanguage(textDocument.languageId);
72
+ this.#colorMap = [];
73
+ this.#setTheme(typeof theme === "string" ? theme : theme[this.#themeType]);
74
+ }
75
+ #emitThemeChange(themeName, themeType) {
38
76
  this.#themeType = themeType;
39
77
  this.#setTheme(themeName);
40
78
  this.stopBackgroundTokenize();
41
- this.#stateStackCache = [INITIAL];
79
+ this.#stateStack = [INITIAL];
42
80
  if (this.#grammar !== void 0 && this.#textDocument.lineCount > 0) this.#scheduleBackgroundTokenize(0);
43
- };
44
- #setTheme = (themeName) => {
81
+ }
82
+ #setTheme(themeName) {
45
83
  this.#colorMap = this.#highlighter.setTheme(themeName).colorMap;
46
84
  const { colors = {} } = this.#highlighter.getTheme(themeName);
47
85
  const selectionBackground = colors["editor.selectionBackground"];
48
86
  const lineHighlightBackground = colors["editor.lineHighlightBackground"];
49
87
  const gutterForeground = colors["editorLineNumber.foreground"];
50
88
  const gutterActiveForeground = colors["editorLineNumber.activeForeground"];
89
+ const cursorForeground = colors["editorCursor.foreground"];
90
+ const findMatchBackground = colors["editor.findMatchBackground"];
91
+ const findMatchHighlightBackground = colors["editor.findMatchHighlightBackground"];
51
92
  this.#setStyle(`:host {
52
93
  --diffs-editor-selection-bg: ${selectionBackground ?? "var(--diffs-line-bg)"};
53
94
  --diffs-editor-line-highlight-bg: ${lineHighlightBackground ?? "var(--diffs-line-bg)"};
54
95
  --diffs-editor-line-number-fg: ${gutterForeground ?? "var(--diffs-fg-number)"};
55
96
  --diffs-editor-line-number-active-bg: ${lineHighlightBackground ?? "var(--diffs-line-bg, var(--diffs-bg))"};
56
97
  --diffs-editor-line-number-active-fg: ${gutterActiveForeground ?? "var(--diffs-selection-number-fg)"};
98
+ ${cursorForeground !== void 0 ? `--diffs-editor-cursor-fg: ${cursorForeground};` : ""}
99
+ ${findMatchBackground !== void 0 ? `--diffs-editor-match-bg: ${findMatchBackground};` : ""}
100
+ ${findMatchHighlightBackground !== void 0 ? `--diffs-editor-match-highlight-bg: ${findMatchHighlightBackground};` : ""}
57
101
  }`);
58
- };
59
- #watchColorScheme = (theme) => {
60
- const observer = new MutationObserver((mutations) => {
61
- for (const { type, attributeName } of mutations) if (type === "attributes" && attributeName !== null && (attributeName === "class" || attributeName.startsWith("data-"))) {
62
- const themeType = getComputedStyle(document.body).colorScheme === "dark" ? "dark" : "light";
63
- this.#onThemeChange(theme[themeType], themeType);
64
- break;
65
- }
66
- });
67
- observer.observe(document.documentElement, { attributes: true });
68
- observer.observe(document.body, { attributes: true });
69
- this.#editorEventDisposes = [addEventListener(this.#mediaQueryList, "change", (e) => {
70
- const themeType = e.matches ? "dark" : "light";
71
- this.#onThemeChange(theme[themeType], themeType);
72
- }), () => observer.disconnect()];
73
- };
74
- get themeType() {
75
- return this.#themeType;
76
- }
77
- constructor({ codeOptions, highlighter, textDocument, setStyle, onDeferTokenize }) {
78
- const { themeType = "system", theme = DEFAULT_THEMES, tokenizeMaxLineLength = 1e3 } = codeOptions;
79
- this.#mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
80
- if (themeType === "system") this.#themeType = this.#mediaQueryList.matches ? "dark" : "light";
81
- else this.#themeType = themeType;
82
- if (typeof theme !== "string") this.#watchColorScheme(theme);
83
- this.#highlighter = highlighter;
84
- this.#textDocument = textDocument;
85
- this.#tokenizeMaxLineLength = tokenizeMaxLineLength;
86
- this.#setStyle = setStyle;
87
- this.#onDeferTokenize = onDeferTokenize;
88
- if (highlighter.getLoadedLanguages().includes(textDocument.languageId)) this.#grammar = highlighter.getLanguage(textDocument.languageId);
89
- this.#colorMap = [];
90
- this.#setTheme(typeof theme === "string" ? theme : theme[this.#themeType]);
91
102
  }
92
103
  cleanUp() {
104
+ this.#detachMessageListener();
93
105
  this.stopBackgroundTokenize();
94
- this.#editorEventDisposes?.forEach((dispose) => dispose());
95
- this.#editorEventDisposes = void 0;
106
+ this.#disposes?.forEach((dispose) => dispose());
107
+ this.#disposes = void 0;
96
108
  }
97
109
  tokenize(change, renderRange) {
98
110
  if (this.#grammar === void 0) throw new Error("Grammar not loaded");
@@ -110,42 +122,42 @@ var EditorTokenizer = class EditorTokenizer {
110
122
  for (const [rangeStart, rangeEnd] of changedLineRanges) if (rangeStart < viewStart) offscreenSyncEnd = Math.max(offscreenSyncEnd, Math.min(rangeEnd, viewStart - 1));
111
123
  }
112
124
  const shouldFlushOffscreenLines = offscreenSyncEnd >= dirtyStart && (canReuseCachedStates || change.lineDelta < 0);
113
- if (canReuseCachedStates) this.#buildStateStackMap(dirtyStart);
125
+ if (canReuseCachedStates) this.#buildStateStack(dirtyStart);
114
126
  else {
115
- this.#stateStackCache.length = Math.min(this.#stateStackCache.length, dirtyStart + 1);
116
- if (renderRange === void 0 || dirtyStart >= viewStart) this.#buildStateStackMap(viewStart);
127
+ this.#stateStack.length = Math.min(this.#stateStack.length, dirtyStart + 1);
128
+ if (renderRange === void 0 || dirtyStart >= viewStart) this.#buildStateStack(viewStart);
117
129
  }
118
130
  let changedRangeIndex = 0;
119
131
  let currentChangedRangeEnd = changedLineRanges[changedRangeIndex][1];
120
132
  let backgroundStartLine;
121
133
  let backgroundChangedRangeIndex = 0;
122
134
  let line = canReuseCachedStates ? changedLineRanges[changedRangeIndex][0] : viewStart;
123
- let state = this.#stateStackCache[line] ?? INITIAL;
135
+ let state = this.#stateStack[line] ?? INITIAL;
124
136
  let settled = false;
125
137
  const dirtyLines = /* @__PURE__ */ new Map();
126
138
  const offscreenDirtyLines = shouldFlushOffscreenLines ? /* @__PURE__ */ new Map() : void 0;
127
139
  if (offscreenDirtyLines !== void 0 && !canReuseCachedStates) {
128
140
  const offscreenEnd = Math.min(offscreenSyncEnd + 1, viewStart, renderRangeEndLine);
129
141
  if (offscreenEnd > dirtyStart) {
130
- this.#buildStateStackMap(offscreenEnd);
142
+ this.#buildStateStack(offscreenEnd);
131
143
  let offscreenLine = dirtyStart;
132
- let offscreenState = this.#stateStackCache[offscreenLine] ?? INITIAL;
144
+ let offscreenState = this.#stateStack[offscreenLine] ?? INITIAL;
133
145
  for (; offscreenLine < offscreenEnd; offscreenLine++) {
134
146
  const resolved = this.#tokenizeLineAt(offscreenLine, offscreenState);
135
147
  offscreenState = resolved.state;
136
148
  offscreenDirtyLines.set(offscreenLine, resolved.resolvedTokens);
137
149
  }
138
- if (canCacheTokenizedStates) this.#stateStackCache[offscreenEnd] = offscreenState;
150
+ if (canCacheTokenizedStates) this.#stateStack[offscreenEnd] = offscreenState;
139
151
  }
140
152
  }
141
153
  for (; line < renderRangeEndLine;) {
142
- const previousNextState = canReuseCachedStates ? this.#stateStackCache[line + 1] : void 0;
143
- if (canCacheTokenizedStates) this.#stateStackCache[line] = state;
154
+ const previousNextState = canReuseCachedStates ? this.#stateStack[line + 1] : void 0;
155
+ if (canCacheTokenizedStates) this.#stateStack[line] = state;
144
156
  const { resolvedTokens, state: nextState } = this.#tokenizeLineAt(line, state);
145
157
  state = nextState;
146
158
  if (line >= viewStart) dirtyLines.set(line, resolvedTokens);
147
159
  else offscreenDirtyLines?.set(line, resolvedTokens);
148
- if (canCacheTokenizedStates) this.#stateStackCache[line + 1] = state;
160
+ if (canCacheTokenizedStates) this.#stateStack[line + 1] = state;
149
161
  settled = line >= currentChangedRangeEnd && canReuseCachedStates && previousNextState !== void 0 && state.equals(previousNextState);
150
162
  if (settled) {
151
163
  changedRangeIndex++;
@@ -156,12 +168,12 @@ var EditorTokenizer = class EditorTokenizer {
156
168
  backgroundChangedRangeIndex = changedRangeIndex;
157
169
  break;
158
170
  }
159
- if (this.#stateStackCache[nextRange[0]] === void 0) {
171
+ if (this.#stateStack[nextRange[0]] === void 0) {
160
172
  currentChangedRangeEnd = nextRange[1];
161
173
  line++;
162
174
  } else {
163
175
  line = nextRange[0];
164
- state = this.#stateStackCache[line] ?? state;
176
+ state = this.#stateStack[line] ?? state;
165
177
  currentChangedRangeEnd = nextRange[1];
166
178
  }
167
179
  settled = false;
@@ -169,8 +181,8 @@ var EditorTokenizer = class EditorTokenizer {
169
181
  }
170
182
  line++;
171
183
  }
172
- if (canCacheTokenizedStates) if (line < renderRangeEndLine) this.#stateStackCache[line + 1] = state;
173
- else this.#stateStackCache[line] = state;
184
+ if (canCacheTokenizedStates) if (line < renderRangeEndLine) this.#stateStack[line + 1] = state;
185
+ else this.#stateStack[line] = state;
174
186
  if (offscreenDirtyLines !== void 0 && offscreenDirtyLines.size > 0) this.#onDeferTokenize(offscreenDirtyLines, this.#themeType);
175
187
  if (backgroundStartLine !== void 0) this.#scheduleBackgroundTokenize(backgroundStartLine, changedLineRanges, backgroundChangedRangeIndex);
176
188
  else if (!settled && line < lineCount) {
@@ -179,31 +191,61 @@ var EditorTokenizer = class EditorTokenizer {
179
191
  }
180
192
  return dirtyLines;
181
193
  }
182
- prebuildStateStackMap(renderRange) {
183
- this.#prebuildStateStackMap(renderRange);
194
+ prebuildStateStack(renderRange) {
195
+ this.#prebuildStateStack(renderRange);
184
196
  }
185
197
  stopBackgroundTokenize() {
186
- removeEventListener("message", this.#onMessage);
198
+ if (this.#isStopped) return;
187
199
  this.#isStopped = true;
200
+ this.#isPaused = false;
188
201
  this.#lastLine = -1;
189
202
  this.#backgroundChangedLineRanges = void 0;
190
203
  this.#backgroundChangedRangeIndex = 0;
204
+ this.#detachMessageListener();
191
205
  }
192
- #scheduleBackgroundTokenize(startLine, changedLineRanges, changedRangeIndex = 0) {
193
- const jobId = ++this.#backgroundJobId;
194
- this.#isStopped = false;
195
- this.#lastLine = startLine;
196
- this.#backgroundChangedLineRanges = changedLineRanges;
197
- this.#backgroundChangedRangeIndex = changedRangeIndex;
206
+ pauseBackgroundTokenize() {
207
+ if (this.#isStopped || this.#isPaused) return;
208
+ if (this.#debug) console.log("[diffs/editor] background tokenization paused", { jobId: this.#backgroundJobId });
209
+ this.#isPaused = true;
210
+ }
211
+ resumeBackgroundTokenize() {
212
+ if (this.#isStopped || !this.#isPaused || this.#grammar === void 0 || this.#lastLine < 0) return;
213
+ if (this.#debug) console.log("[diffs/editor] background tokenization resumed", { jobId: this.#backgroundJobId });
214
+ this.#isPaused = false;
215
+ this.#postTokenizeMessage(this.#backgroundJobId);
216
+ }
217
+ #attachMessageListener() {
218
+ if (this.#isMessageListenerAttached) return;
198
219
  globalThis.addEventListener("message", this.#onMessage);
199
- this.#postBackgroundTokenizeMessage(jobId);
220
+ this.#isMessageListenerAttached = true;
221
+ }
222
+ #detachMessageListener() {
223
+ if (!this.#isMessageListenerAttached) return;
224
+ globalThis.removeEventListener("message", this.#onMessage);
225
+ this.#isMessageListenerAttached = false;
200
226
  }
201
- #postBackgroundTokenizeMessage(jobId) {
227
+ #postTokenizeMessage(jobId) {
202
228
  globalThis.postMessage({
203
229
  type: "tokenize",
204
230
  jobId
205
231
  });
206
232
  }
233
+ #scheduleBackgroundTokenize(startLine, changedLineRanges, changedRangeIndex = 0) {
234
+ const jobId = ++this.#backgroundJobId;
235
+ if (this.#debug) console.log("[diffs/editor] background tokenization scheduled", {
236
+ jobId,
237
+ startLine,
238
+ changedLineRanges,
239
+ changedRangeIndex
240
+ });
241
+ this.#isStopped = false;
242
+ this.#isPaused = false;
243
+ this.#lastLine = startLine;
244
+ this.#backgroundChangedLineRanges = changedLineRanges;
245
+ this.#backgroundChangedRangeIndex = changedRangeIndex;
246
+ this.#attachMessageListener();
247
+ this.#postTokenizeMessage(jobId);
248
+ }
207
249
  #tokenizeLineAt(line, state) {
208
250
  if (this.#grammar === void 0) throw new Error("Grammar not loaded");
209
251
  const lineText = this.#textDocument.getLineText(line);
@@ -232,32 +274,32 @@ var EditorTokenizer = class EditorTokenizer {
232
274
  state: result.ruleStack
233
275
  };
234
276
  }
235
- #buildStateStackMap(endAt) {
277
+ #buildStateStack(endAt) {
236
278
  const boundedEndAt = Math.min(Math.max(0, endAt), this.#textDocument.lineCount);
237
- if (this.#stateStackCache.length > boundedEndAt || this.#grammar === void 0) return;
238
- let line = this.#stateStackCache.length - 1;
239
- let state = this.#stateStackCache[line] ?? INITIAL;
279
+ if (this.#stateStack.length > boundedEndAt || this.#grammar === void 0) return;
280
+ let line = this.#stateStack.length - 1;
281
+ let state = this.#stateStack[line] ?? INITIAL;
240
282
  for (; line < boundedEndAt; line++) {
241
- this.#stateStackCache[line] = state;
283
+ this.#stateStack[line] = state;
242
284
  const lineText = this.#textDocument.getLineText(line);
243
285
  if (lineText.length <= this.#tokenizeMaxLineLength && lineText !== "" && lineText.trim() !== "") state = this.#grammar.tokenizeLine2(lineText, state, EditorTokenizer.TOKENIZE_TIME_LIMIT).ruleStack;
244
286
  }
245
- this.#stateStackCache[line] = state;
287
+ this.#stateStack[line] = state;
246
288
  }
247
289
  #backgroundTokenize(jobId) {
248
- if (this.#isStopped || this.#grammar === void 0 || jobId !== this.#backgroundJobId) return;
290
+ if (this.#isStopped || this.#isPaused || this.#grammar === void 0 || jobId !== this.#backgroundJobId) return;
249
291
  const t = performance.now();
250
292
  const lines = /* @__PURE__ */ new Map();
251
293
  const totalLines = this.#textDocument.lineCount;
252
294
  const changedLineRanges = this.#backgroundChangedLineRanges;
253
295
  let line = this.#lastLine;
254
- let state = this.#stateStackCache[line] ?? INITIAL;
296
+ let state = this.#stateStack[line] ?? INITIAL;
255
297
  let settled = false;
256
298
  let changedRangeIndex = this.#backgroundChangedRangeIndex;
257
299
  let currentChangedRangeEnd = changedLineRanges?.[changedRangeIndex]?.[1];
258
300
  for (; line < totalLines;) {
259
- this.#stateStackCache[line] = state;
260
- const previousNextState = currentChangedRangeEnd !== void 0 ? this.#stateStackCache[line + 1] : void 0;
301
+ this.#stateStack[line] = state;
302
+ const previousNextState = currentChangedRangeEnd !== void 0 ? this.#stateStack[line + 1] : void 0;
261
303
  const lineText = this.#textDocument.getLineText(line);
262
304
  if (lineText.length > this.#tokenizeMaxLineLength) {
263
305
  console.warn(`[diffs] Line(${line}) too long to tokenize: ${lineText.length}`);
@@ -276,7 +318,7 @@ var EditorTokenizer = class EditorTokenizer {
276
318
  lines.set(line, ret.resolvedTokens);
277
319
  state = ret.ruleStack;
278
320
  }
279
- this.#stateStackCache[line + 1] = state;
321
+ this.#stateStack[line + 1] = state;
280
322
  settled = currentChangedRangeEnd !== void 0 && line >= currentChangedRangeEnd && previousNextState !== void 0 && state.equals(previousNextState);
281
323
  line++;
282
324
  if (settled) {
@@ -284,25 +326,25 @@ var EditorTokenizer = class EditorTokenizer {
284
326
  const nextRange = changedLineRanges?.[changedRangeIndex];
285
327
  if (nextRange === void 0) break;
286
328
  currentChangedRangeEnd = nextRange[1];
287
- if (this.#stateStackCache[nextRange[0]] === void 0) settled = false;
329
+ if (this.#stateStack[nextRange[0]] === void 0) settled = false;
288
330
  else {
289
331
  line = nextRange[0];
290
- state = this.#stateStackCache[line] ?? state;
332
+ state = this.#stateStack[line] ?? state;
291
333
  settled = false;
292
334
  continue;
293
335
  }
294
336
  }
295
- if (performance.now() - t > 2) break;
337
+ if (performance.now() - t > 1) break;
296
338
  }
297
339
  this.#onDeferTokenize(lines, this.#themeType);
298
- if (this.#isStopped || jobId !== this.#backgroundJobId) return;
340
+ if (this.#isStopped || this.#isPaused || jobId !== this.#backgroundJobId) return;
299
341
  if (settled || line >= totalLines) {
300
342
  this.stopBackgroundTokenize();
301
343
  return;
302
344
  }
303
345
  this.#lastLine = line;
304
346
  this.#backgroundChangedRangeIndex = changedRangeIndex;
305
- this.#postBackgroundTokenizeMessage(jobId);
347
+ this.#postTokenizeMessage(jobId);
306
348
  }
307
349
  };
308
350
  function tokenizeLine(grammar, colorMap, lineText, stateStack, timeLimit) {
@@ -1 +1 @@
1
- {"version":3,"file":"tokenzier.js","names":["#textDocument","#grammar","#highlighter","#buildStateStackMap","#backgroundJobId","#backgroundTokenize","#themeType","#setTheme","#stateStackCache","#scheduleBackgroundTokenize","#colorMap","#setStyle","#onThemeChange","#editorEventDisposes","#mediaQueryList","#watchColorScheme","#tokenizeMaxLineLength","#onDeferTokenize","changedLineRanges: readonly [number, number][]","backgroundStartLine: number | undefined","dirtyLines: Map<number, Array<HighlightedToken>>","offscreenDirtyLines:\n | Map<number, Array<HighlightedToken>>\n | undefined","#tokenizeLineAt","#prebuildStateStackMap","#onMessage","#isStopped","#lastLine","#backgroundChangedLineRanges","#backgroundChangedRangeIndex","#postBackgroundTokenizeMessage","resolvedTokens: Array<HighlightedToken>"],"sources":["../../src/editor/tokenzier.ts"],"sourcesContent":["import {\n EncodedTokenMetadata,\n type IGrammar,\n INITIAL,\n type StateStack,\n} from 'shiki/textmate';\n\nimport { DEFAULT_THEMES } from '../constants';\nimport type {\n BaseCodeOptions,\n DiffsHighlighter,\n HighlightedToken,\n RenderRange,\n ThemesType,\n} from '../types';\nimport type { TextDocument, TextDocumentChange } from './textDocument';\nimport { addEventListener, debounce, h } from './utils';\n\nexport interface EditorTokenizerProps {\n highlighter: DiffsHighlighter;\n textDocument: TextDocument<unknown>;\n codeOptions: BaseCodeOptions;\n setStyle: (style: string) => void;\n onDeferTokenize: (\n lines: Map<number, Array<HighlightedToken>>,\n themeType: 'dark' | 'light'\n ) => void;\n}\n\n/** Stoppable code tokenizer for the editor */\nexport class EditorTokenizer {\n static TOKENIZE_TIME_LIMIT = 500;\n\n #highlighter: DiffsHighlighter;\n #grammar: IGrammar | undefined;\n #mediaQueryList: MediaQueryList;\n #themeType: 'light' | 'dark';\n #colorMap: string[];\n #textDocument: TextDocument<unknown>;\n #tokenizeMaxLineLength: number;\n #setStyle: EditorTokenizerProps['setStyle'];\n #onDeferTokenize: EditorTokenizerProps['onDeferTokenize'];\n #editorEventDisposes?: (() => void)[];\n\n // state\n #stateStackCache: StateStack[] = [INITIAL];\n #lastLine: number = -1;\n #isStopped: boolean = true;\n #backgroundJobId: number = 0;\n #backgroundChangedLineRanges: readonly [number, number][] | undefined;\n #backgroundChangedRangeIndex: number = 0;\n\n #prebuildStateStackMap = debounce(async (renderRange?: RenderRange) => {\n const { startingLine = 0, totalLines = Infinity } = renderRange ?? {};\n const endLine = Math.min(\n totalLines === Infinity ? Infinity : startingLine + totalLines,\n this.#textDocument.lineCount\n );\n if (this.#grammar === undefined) {\n await this.#highlighter.loadLanguage(this.#textDocument.languageId);\n this.#grammar = this.#highlighter.getLanguage(\n this.#textDocument.languageId\n );\n }\n this.#buildStateStackMap(endLine);\n }, 500);\n\n #onMessage = ({\n data,\n }: MessageEvent<{ type: 'tokenize'; jobId: number }>) => {\n if (data.type === 'tokenize' && data.jobId === this.#backgroundJobId) {\n this.#backgroundTokenize(data.jobId);\n }\n };\n\n // By default, diffs components support dual themes, but the tokenizer only renders\n // the preferred theme. When the theme type is changed, the tokenizer will re-tokenize the document.\n #onThemeChange = (themeName: string, themeType: 'light' | 'dark') => {\n this.#themeType = themeType;\n this.#setTheme(themeName);\n this.stopBackgroundTokenize();\n this.#stateStackCache = [INITIAL];\n if (this.#grammar !== undefined && this.#textDocument.lineCount > 0) {\n this.#scheduleBackgroundTokenize(0);\n }\n };\n\n #setTheme = (themeName: string): void => {\n this.#colorMap = this.#highlighter.setTheme(themeName).colorMap;\n const { colors = {} } = this.#highlighter.getTheme(themeName);\n const selectionBackground = colors['editor.selectionBackground'];\n const lineHighlightBackground = colors['editor.lineHighlightBackground'];\n const gutterForeground = colors['editorLineNumber.foreground'];\n const gutterActiveForeground = colors['editorLineNumber.activeForeground'];\n this.#setStyle(`:host {\n --diffs-editor-selection-bg: ${selectionBackground ?? 'var(--diffs-line-bg)'};\n --diffs-editor-line-highlight-bg: ${lineHighlightBackground ?? 'var(--diffs-line-bg)'};\n --diffs-editor-line-number-fg: ${gutterForeground ?? 'var(--diffs-fg-number)'};\n --diffs-editor-line-number-active-bg: ${lineHighlightBackground ?? 'var(--diffs-line-bg, var(--diffs-bg))'};\n --diffs-editor-line-number-active-fg: ${gutterActiveForeground ?? 'var(--diffs-selection-number-fg)'};\n }`);\n };\n\n #watchColorScheme = (theme: ThemesType) => {\n const observer = new MutationObserver((mutations) => {\n for (const { type, attributeName } of mutations) {\n if (\n type === 'attributes' &&\n attributeName !== null &&\n (attributeName === 'class' || attributeName.startsWith('data-'))\n ) {\n const themeType =\n getComputedStyle(document.body).colorScheme === 'dark'\n ? 'dark'\n : 'light';\n this.#onThemeChange(theme[themeType], themeType);\n break;\n }\n }\n });\n observer.observe(document.documentElement, { attributes: true });\n observer.observe(document.body, { attributes: true });\n this.#editorEventDisposes = [\n addEventListener(this.#mediaQueryList, 'change', (e) => {\n const themeType = e.matches ? 'dark' : 'light';\n this.#onThemeChange(theme[themeType], themeType);\n }),\n () => observer.disconnect(),\n ];\n };\n\n get themeType(): 'light' | 'dark' {\n return this.#themeType;\n }\n\n constructor({\n codeOptions,\n highlighter,\n textDocument,\n setStyle,\n onDeferTokenize,\n }: EditorTokenizerProps) {\n const {\n themeType = 'system',\n theme = DEFAULT_THEMES,\n tokenizeMaxLineLength = 1000,\n } = codeOptions;\n this.#mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');\n if (themeType === 'system') {\n this.#themeType = this.#mediaQueryList.matches ? 'dark' : 'light';\n } else {\n this.#themeType = themeType;\n }\n if (typeof theme !== 'string') {\n this.#watchColorScheme(theme);\n }\n this.#highlighter = highlighter;\n this.#textDocument = textDocument;\n this.#tokenizeMaxLineLength = tokenizeMaxLineLength;\n this.#setStyle = setStyle;\n this.#onDeferTokenize = onDeferTokenize;\n if (highlighter.getLoadedLanguages().includes(textDocument.languageId)) {\n this.#grammar = highlighter.getLanguage(textDocument.languageId);\n }\n this.#colorMap = [];\n this.#setTheme(typeof theme === 'string' ? theme : theme[this.#themeType]);\n }\n\n cleanUp(): void {\n this.stopBackgroundTokenize();\n this.#editorEventDisposes?.forEach((dispose) => dispose());\n this.#editorEventDisposes = undefined;\n }\n\n // to use `tokenize`, call `prebuildStateStackMap` first to prebuild\n // the state stack map for the given render range.\n tokenize(\n change: TextDocumentChange,\n renderRange?: RenderRange\n ): Map<number, Array<HighlightedToken>> {\n if (this.#grammar === undefined) {\n throw new Error('Grammar not loaded');\n }\n\n const { lineCount } = this.#textDocument;\n const { startingLine = 0, totalLines = Infinity } = renderRange ?? {};\n const renderRangeEndLine =\n totalLines === Infinity\n ? lineCount\n : Math.min(startingLine + totalLines, lineCount);\n\n const dirtyStart = change.startLine;\n const viewStart = Math.max(startingLine, dirtyStart);\n const crossesRenderRangeEnd =\n renderRange !== undefined &&\n totalLines !== Infinity &&\n change.lineDelta > 0 &&\n dirtyStart < renderRangeEndLine &&\n change.endLine >= renderRangeEndLine;\n const canReuseCachedStates = change.lineDelta === 0;\n const canCacheTokenizedStates =\n canReuseCachedStates ||\n renderRange === undefined ||\n dirtyStart >= viewStart;\n const changedLineRanges: readonly [number, number][] = canReuseCachedStates\n ? (change.changedLineRanges ?? [[dirtyStart, change.endLine]])\n : [[dirtyStart, change.endLine]];\n let offscreenSyncEnd = -1;\n if (dirtyStart < viewStart) {\n for (const [rangeStart, rangeEnd] of changedLineRanges) {\n if (rangeStart < viewStart) {\n offscreenSyncEnd = Math.max(\n offscreenSyncEnd,\n Math.min(rangeEnd, viewStart - 1)\n );\n }\n }\n }\n const shouldFlushOffscreenLines =\n offscreenSyncEnd >= dirtyStart &&\n (canReuseCachedStates || change.lineDelta < 0);\n if (canReuseCachedStates) {\n this.#buildStateStackMap(dirtyStart);\n } else {\n this.#stateStackCache.length = Math.min(\n this.#stateStackCache.length,\n dirtyStart + 1\n );\n if (renderRange === undefined || dirtyStart >= viewStart) {\n this.#buildStateStackMap(viewStart);\n }\n }\n\n let changedRangeIndex = 0;\n let currentChangedRangeEnd = changedLineRanges[changedRangeIndex][1];\n let backgroundStartLine: number | undefined;\n let backgroundChangedRangeIndex = 0;\n let line = canReuseCachedStates\n ? changedLineRanges[changedRangeIndex][0]\n : viewStart;\n let state = this.#stateStackCache[line] ?? INITIAL;\n let settled = false;\n const dirtyLines: Map<number, Array<HighlightedToken>> = new Map();\n const offscreenDirtyLines:\n | Map<number, Array<HighlightedToken>>\n | undefined = shouldFlushOffscreenLines ? new Map() : undefined;\n if (offscreenDirtyLines !== undefined && !canReuseCachedStates) {\n const offscreenEnd = Math.min(\n offscreenSyncEnd + 1,\n viewStart,\n renderRangeEndLine\n );\n if (offscreenEnd > dirtyStart) {\n this.#buildStateStackMap(offscreenEnd);\n let offscreenLine = dirtyStart;\n let offscreenState = this.#stateStackCache[offscreenLine] ?? INITIAL;\n for (; offscreenLine < offscreenEnd; offscreenLine++) {\n const resolved = this.#tokenizeLineAt(offscreenLine, offscreenState);\n offscreenState = resolved.state;\n offscreenDirtyLines.set(offscreenLine, resolved.resolvedTokens);\n }\n if (canCacheTokenizedStates) {\n this.#stateStackCache[offscreenEnd] = offscreenState;\n }\n }\n }\n for (; line < renderRangeEndLine; ) {\n const previousNextState = canReuseCachedStates\n ? this.#stateStackCache[line + 1]\n : undefined;\n if (canCacheTokenizedStates) {\n this.#stateStackCache[line] = state;\n }\n\n const { resolvedTokens, state: nextState } = this.#tokenizeLineAt(\n line,\n state\n );\n state = nextState;\n\n if (line >= viewStart) {\n dirtyLines.set(line, resolvedTokens);\n } else {\n offscreenDirtyLines?.set(line, resolvedTokens);\n }\n\n if (canCacheTokenizedStates) {\n this.#stateStackCache[line + 1] = state;\n }\n settled =\n line >= currentChangedRangeEnd &&\n canReuseCachedStates &&\n previousNextState !== undefined &&\n state.equals(previousNextState);\n if (settled) {\n changedRangeIndex++;\n const nextRange = changedLineRanges[changedRangeIndex];\n if (nextRange === undefined) {\n break;\n }\n if (nextRange[0] >= renderRangeEndLine) {\n backgroundStartLine = nextRange[0];\n backgroundChangedRangeIndex = changedRangeIndex;\n break;\n }\n if (this.#stateStackCache[nextRange[0]] === undefined) {\n currentChangedRangeEnd = nextRange[1];\n line++;\n } else {\n line = nextRange[0];\n state = this.#stateStackCache[line] ?? state;\n currentChangedRangeEnd = nextRange[1];\n }\n settled = false;\n continue;\n }\n line++;\n }\n\n if (canCacheTokenizedStates) {\n if (line < renderRangeEndLine) {\n this.#stateStackCache[line + 1] = state;\n } else {\n this.#stateStackCache[line] = state;\n }\n }\n\n if (offscreenDirtyLines !== undefined && offscreenDirtyLines.size > 0) {\n this.#onDeferTokenize(offscreenDirtyLines, this.#themeType);\n }\n\n if (backgroundStartLine !== undefined) {\n this.#scheduleBackgroundTokenize(\n backgroundStartLine,\n changedLineRanges,\n backgroundChangedRangeIndex\n );\n } else if (!settled && line < lineCount) {\n const backgroundLine =\n crossesRenderRangeEnd && dirtyStart >= viewStart\n ? renderRangeEndLine\n : dirtyStart < viewStart && !canReuseCachedStates\n ? dirtyStart\n : line;\n this.#scheduleBackgroundTokenize(\n backgroundLine,\n canReuseCachedStates ? changedLineRanges : undefined,\n changedRangeIndex\n );\n }\n\n return dirtyLines;\n }\n\n prebuildStateStackMap(renderRange?: RenderRange): void {\n this.#prebuildStateStackMap(renderRange);\n }\n\n stopBackgroundTokenize(): void {\n removeEventListener('message', this.#onMessage);\n this.#isStopped = true;\n this.#lastLine = -1;\n this.#backgroundChangedLineRanges = undefined;\n this.#backgroundChangedRangeIndex = 0;\n }\n\n #scheduleBackgroundTokenize(\n startLine: number,\n changedLineRanges?: readonly [number, number][],\n changedRangeIndex = 0\n ): void {\n const jobId = ++this.#backgroundJobId;\n\n this.#isStopped = false;\n this.#lastLine = startLine;\n this.#backgroundChangedLineRanges = changedLineRanges;\n this.#backgroundChangedRangeIndex = changedRangeIndex;\n\n globalThis.addEventListener('message', this.#onMessage);\n this.#postBackgroundTokenizeMessage(jobId);\n }\n\n #postBackgroundTokenizeMessage(jobId: number): void {\n // use `postMessage` instead of `setTimeout(fn, 0)` to avoid 4ms delay\n globalThis.postMessage({ type: 'tokenize', jobId });\n }\n\n #tokenizeLineAt(\n line: number,\n state: StateStack\n ): { resolvedTokens: Array<HighlightedToken>; state: StateStack } {\n if (this.#grammar === undefined) {\n throw new Error('Grammar not loaded');\n }\n const lineText = this.#textDocument.getLineText(line);\n if (lineText.length > this.#tokenizeMaxLineLength) {\n console.warn(\n `[diffs] Line(${line}) too long to tokenize: ${lineText.length}`\n );\n return { resolvedTokens: [[0, '', lineText]], state };\n }\n if (lineText === '' || lineText.trim() === '') {\n return { resolvedTokens: [[0, '', lineText]], state };\n }\n const result = tokenizeLine(\n this.#grammar,\n this.#colorMap,\n lineText,\n state,\n EditorTokenizer.TOKENIZE_TIME_LIMIT\n );\n return {\n resolvedTokens: result.resolvedTokens,\n state: result.ruleStack,\n };\n }\n\n #buildStateStackMap(endAt: number) {\n const boundedEndAt = Math.min(\n Math.max(0, endAt),\n this.#textDocument.lineCount\n );\n if (\n this.#stateStackCache.length > boundedEndAt ||\n this.#grammar === undefined\n ) {\n return;\n }\n let line = this.#stateStackCache.length - 1;\n let state = this.#stateStackCache[line] ?? INITIAL;\n for (; line < boundedEndAt; line++) {\n this.#stateStackCache[line] = state;\n const lineText = this.#textDocument.getLineText(line);\n if (\n lineText.length <= this.#tokenizeMaxLineLength &&\n lineText !== '' &&\n lineText.trim() !== ''\n ) {\n state = this.#grammar.tokenizeLine2(\n lineText,\n state,\n EditorTokenizer.TOKENIZE_TIME_LIMIT\n ).ruleStack;\n }\n }\n this.#stateStackCache[line] = state;\n }\n\n #backgroundTokenize(jobId: number) {\n if (\n this.#isStopped ||\n this.#grammar === undefined ||\n jobId !== this.#backgroundJobId\n ) {\n return;\n }\n\n const t = performance.now();\n const lines = new Map<number, Array<HighlightedToken>>();\n const totalLines = this.#textDocument.lineCount;\n const changedLineRanges = this.#backgroundChangedLineRanges;\n\n let line = this.#lastLine;\n let state = this.#stateStackCache[line] ?? INITIAL;\n let settled = false;\n let changedRangeIndex = this.#backgroundChangedRangeIndex;\n let currentChangedRangeEnd = changedLineRanges?.[changedRangeIndex]?.[1];\n for (; line < totalLines; ) {\n this.#stateStackCache[line] = state;\n\n const previousNextState =\n currentChangedRangeEnd !== undefined\n ? this.#stateStackCache[line + 1]\n : undefined;\n const lineText = this.#textDocument.getLineText(line);\n if (lineText.length > this.#tokenizeMaxLineLength) {\n console.warn(\n `[diffs] Line(${line}) too long to tokenize: ${lineText.length}`\n );\n lines.set(line, [[0, '', lineText]]);\n } else if (lineText === '' || lineText.trim() === '') {\n lines.set(line, [[0, '', lineText]]);\n } else {\n const ret = tokenizeLine(\n this.#grammar,\n this.#colorMap,\n lineText,\n state,\n EditorTokenizer.TOKENIZE_TIME_LIMIT\n );\n lines.set(line, ret.resolvedTokens);\n state = ret.ruleStack;\n }\n\n this.#stateStackCache[line + 1] = state;\n settled =\n currentChangedRangeEnd !== undefined &&\n line >= currentChangedRangeEnd &&\n previousNextState !== undefined &&\n state.equals(previousNextState);\n line++;\n if (settled) {\n changedRangeIndex++;\n const nextRange = changedLineRanges?.[changedRangeIndex];\n if (nextRange === undefined) {\n break;\n }\n currentChangedRangeEnd = nextRange[1];\n if (this.#stateStackCache[nextRange[0]] === undefined) {\n settled = false;\n } else {\n line = nextRange[0];\n state = this.#stateStackCache[line] ?? state;\n settled = false;\n continue;\n }\n }\n\n // limit the time of partial tokenize to 2ms\n if (performance.now() - t > 2) {\n break;\n }\n }\n\n this.#onDeferTokenize(lines, this.#themeType);\n if (this.#isStopped || jobId !== this.#backgroundJobId) {\n return;\n }\n\n if (settled || line >= totalLines) {\n this.stopBackgroundTokenize();\n return;\n }\n\n this.#lastLine = line;\n this.#backgroundChangedRangeIndex = changedRangeIndex;\n this.#postBackgroundTokenizeMessage(jobId);\n }\n}\n\nexport function tokenizeLine(\n grammar: IGrammar,\n colorMap: string[],\n lineText: string,\n stateStack: StateStack,\n timeLimit?: number\n): {\n ruleStack: StateStack;\n resolvedTokens: Array<HighlightedToken>;\n} {\n const result = grammar.tokenizeLine2(lineText, stateStack, timeLimit);\n if (result.stoppedEarly) {\n console.warn(\n `[diffs] Time limit reached when tokenizing line: ${lineText.substring(0, 100)}`\n );\n }\n const rawTokens = result.tokens;\n const tokensLength = rawTokens.length / 2;\n const resolvedTokens: Array<HighlightedToken> = [];\n for (let j = 0; j < tokensLength; j++) {\n const offset = rawTokens[2 * j];\n const nextOffset =\n j + 1 < tokensLength ? rawTokens[2 * j + 2] : lineText.length;\n if (offset === nextOffset) {\n // should never reach here, skip if happens anyway\n continue;\n }\n const metadata = rawTokens[2 * j + 1];\n const bg = EncodedTokenMetadata.getForeground(metadata);\n const fg = colorMap[bg];\n const tokenText = lineText.slice(offset, nextOffset);\n resolvedTokens.push([offset, fg, tokenText]);\n }\n return {\n ruleStack: result.ruleStack,\n resolvedTokens,\n };\n}\n\nexport function renderLineTokens(\n tokens: Array<HighlightedToken>,\n themeType: 'light' | 'dark'\n): (HTMLElement | string)[] {\n return tokens.map(([char, fg, textContent]) => {\n if (char === 0 && fg === '') {\n if (textContent === '') {\n return h('br');\n }\n return textContent;\n }\n return h('span', {\n dataset: {\n char: char.toString(),\n },\n style: `--diffs-token-${themeType}:${fg};`,\n textContent: textContent,\n });\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tokenzier.js","names":["#textDocument","#grammar","#highlighter","#buildStateStack","#backgroundJobId","#backgroundTokenize","#themeType","#mediaQueryList","themeType","#emitThemeChange","#disposes","#tokenizeMaxLineLength","#setStyle","#onDeferTokenize","#debug","#colorMap","#setTheme","#stateStack","#scheduleBackgroundTokenize","#detachMessageListener","changedLineRanges: readonly [number, number][]","backgroundStartLine: number | undefined","dirtyLines: Map<number, Array<HighlightedToken>>","offscreenDirtyLines:\n | Map<number, Array<HighlightedToken>>\n | undefined","#tokenizeLineAt","#prebuildStateStack","#isStopped","#isPaused","#lastLine","#backgroundChangedLineRanges","#backgroundChangedRangeIndex","#postTokenizeMessage","#isMessageListenerAttached","#onMessage","#attachMessageListener","resolvedTokens: Array<HighlightedToken>"],"sources":["../../src/editor/tokenzier.ts"],"sourcesContent":["import {\n EncodedTokenMetadata,\n type IGrammar,\n INITIAL,\n type StateStack,\n} from 'shiki/textmate';\n\nimport { DEFAULT_THEMES } from '../constants';\nimport type {\n BaseCodeOptions,\n DiffsHighlighter,\n HighlightedToken,\n RenderRange,\n} from '../types';\nimport type { TextDocument, TextDocumentChange } from './textDocument';\nimport { addEventListener, debounce, h } from './utils';\n\nexport interface EditorTokenizerProps {\n highlighter: DiffsHighlighter;\n textDocument: TextDocument<unknown>;\n codeOptions: BaseCodeOptions;\n setStyle: (style: string) => void;\n onDeferTokenize: (\n lines: Map<number, Array<HighlightedToken>>,\n themeType: 'dark' | 'light'\n ) => void;\n __debug?: boolean;\n}\n\n/** Stoppable code tokenizer for the editor */\nexport class EditorTokenizer {\n static TOKENIZE_TIME_LIMIT = 500;\n\n #highlighter: DiffsHighlighter;\n #grammar: IGrammar | undefined;\n #mediaQueryList: MediaQueryList;\n #themeType: 'light' | 'dark';\n #colorMap: string[];\n #textDocument: TextDocument<unknown>;\n #tokenizeMaxLineLength: number;\n #setStyle: EditorTokenizerProps['setStyle'];\n #onDeferTokenize: EditorTokenizerProps['onDeferTokenize'];\n #debug: boolean;\n #disposes?: (() => void)[];\n\n // state\n #stateStack: StateStack[] = [INITIAL]; // cached state stack by line index\n #lastLine: number = -1;\n #isStopped: boolean = true;\n #isPaused: boolean = false;\n #backgroundJobId: number = 0;\n #backgroundChangedLineRanges: readonly [number, number][] | undefined;\n #backgroundChangedRangeIndex: number = 0;\n #isMessageListenerAttached: boolean = false;\n\n #prebuildStateStack = debounce(async (renderRange?: RenderRange) => {\n const { startingLine = 0, totalLines = Infinity } = renderRange ?? {};\n const endLine = Math.min(\n totalLines === Infinity ? Infinity : startingLine + totalLines,\n this.#textDocument.lineCount\n );\n if (this.#grammar === undefined) {\n await this.#highlighter.loadLanguage(this.#textDocument.languageId);\n this.#grammar = this.#highlighter.getLanguage(\n this.#textDocument.languageId\n );\n }\n this.#buildStateStack(endLine);\n }, 500);\n\n #onMessage = ({ data }: MessageEvent<unknown>) => {\n if (typeof data !== 'object' || data === null) {\n return;\n }\n const { type, jobId } = data as {\n type?: unknown;\n jobId?: unknown;\n };\n if (\n type === 'tokenize' &&\n typeof jobId === 'number' &&\n jobId === this.#backgroundJobId\n ) {\n this.#backgroundTokenize(jobId);\n }\n };\n\n get themeType(): 'light' | 'dark' {\n return this.#themeType;\n }\n\n constructor({\n codeOptions,\n highlighter,\n textDocument,\n setStyle,\n onDeferTokenize,\n __debug,\n }: EditorTokenizerProps) {\n const {\n themeType = 'system',\n theme = DEFAULT_THEMES,\n tokenizeMaxLineLength = 1000,\n } = codeOptions;\n this.#mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');\n if (themeType === 'system') {\n this.#themeType = this.#mediaQueryList.matches ? 'dark' : 'light';\n } else {\n this.#themeType = themeType;\n }\n if (typeof theme !== 'string') {\n const observer = new MutationObserver((mutations) => {\n for (const { type, attributeName } of mutations) {\n if (\n type === 'attributes' &&\n attributeName !== null &&\n (attributeName === 'class' || attributeName.startsWith('data-'))\n ) {\n const themeType =\n getComputedStyle(document.body).colorScheme === 'dark'\n ? 'dark'\n : 'light';\n this.#emitThemeChange(theme[themeType], themeType);\n break;\n }\n }\n });\n observer.observe(document.documentElement, { attributes: true });\n observer.observe(document.body, { attributes: true });\n this.#disposes = [\n addEventListener(this.#mediaQueryList, 'change', (e) => {\n const themeType = e.matches ? 'dark' : 'light';\n this.#emitThemeChange(theme[themeType], themeType);\n }),\n () => observer.disconnect(),\n ];\n }\n this.#highlighter = highlighter;\n this.#textDocument = textDocument;\n this.#tokenizeMaxLineLength = tokenizeMaxLineLength;\n this.#setStyle = setStyle;\n this.#onDeferTokenize = onDeferTokenize;\n this.#debug = __debug ?? false;\n if (highlighter.getLoadedLanguages().includes(textDocument.languageId)) {\n this.#grammar = highlighter.getLanguage(textDocument.languageId);\n }\n this.#colorMap = [];\n this.#setTheme(typeof theme === 'string' ? theme : theme[this.#themeType]);\n }\n\n // By default, diffs components support dual themes, but the tokenizer only renders\n // the preferred theme. When the theme type is changed, the tokenizer will re-tokenize the document.\n #emitThemeChange(themeName: string, themeType: 'light' | 'dark') {\n this.#themeType = themeType;\n this.#setTheme(themeName);\n this.stopBackgroundTokenize();\n this.#stateStack = [INITIAL];\n if (this.#grammar !== undefined && this.#textDocument.lineCount > 0) {\n this.#scheduleBackgroundTokenize(0);\n }\n }\n\n #setTheme(themeName: string) {\n this.#colorMap = this.#highlighter.setTheme(themeName).colorMap;\n const { colors = {} } = this.#highlighter.getTheme(themeName);\n const selectionBackground = colors['editor.selectionBackground'];\n const lineHighlightBackground = colors['editor.lineHighlightBackground'];\n const gutterForeground = colors['editorLineNumber.foreground'];\n const gutterActiveForeground = colors['editorLineNumber.activeForeground'];\n const cursorForeground = colors['editorCursor.foreground'];\n const findMatchBackground = colors['editor.findMatchBackground'];\n const findMatchHighlightBackground =\n colors['editor.findMatchHighlightBackground'];\n this.#setStyle(`:host {\n --diffs-editor-selection-bg: ${selectionBackground ?? 'var(--diffs-line-bg)'};\n --diffs-editor-line-highlight-bg: ${lineHighlightBackground ?? 'var(--diffs-line-bg)'};\n --diffs-editor-line-number-fg: ${gutterForeground ?? 'var(--diffs-fg-number)'};\n --diffs-editor-line-number-active-bg: ${lineHighlightBackground ?? 'var(--diffs-line-bg, var(--diffs-bg))'};\n --diffs-editor-line-number-active-fg: ${gutterActiveForeground ?? 'var(--diffs-selection-number-fg)'};\n ${cursorForeground !== undefined ? `--diffs-editor-cursor-fg: ${cursorForeground};` : ''}\n ${findMatchBackground !== undefined ? `--diffs-editor-match-bg: ${findMatchBackground};` : ''}\n ${findMatchHighlightBackground !== undefined ? `--diffs-editor-match-highlight-bg: ${findMatchHighlightBackground};` : ''}\n }`);\n }\n\n cleanUp(): void {\n this.#detachMessageListener();\n this.stopBackgroundTokenize();\n this.#disposes?.forEach((dispose) => dispose());\n this.#disposes = undefined;\n }\n\n // to use `tokenize`, call `prebuildStateStackMap` first to prebuild\n // the state stack map for the given render range.\n tokenize(\n change: TextDocumentChange,\n renderRange?: RenderRange\n ): Map<number, Array<HighlightedToken>> {\n if (this.#grammar === undefined) {\n throw new Error('Grammar not loaded');\n }\n\n const { lineCount } = this.#textDocument;\n const { startingLine = 0, totalLines = Infinity } = renderRange ?? {};\n const renderRangeEndLine =\n totalLines === Infinity\n ? lineCount\n : Math.min(startingLine + totalLines, lineCount);\n\n const dirtyStart = change.startLine;\n const viewStart = Math.max(startingLine, dirtyStart);\n const crossesRenderRangeEnd =\n renderRange !== undefined &&\n totalLines !== Infinity &&\n change.lineDelta > 0 &&\n dirtyStart < renderRangeEndLine &&\n change.endLine >= renderRangeEndLine;\n const canReuseCachedStates = change.lineDelta === 0;\n const canCacheTokenizedStates =\n canReuseCachedStates ||\n renderRange === undefined ||\n dirtyStart >= viewStart;\n const changedLineRanges: readonly [number, number][] = canReuseCachedStates\n ? (change.changedLineRanges ?? [[dirtyStart, change.endLine]])\n : [[dirtyStart, change.endLine]];\n let offscreenSyncEnd = -1;\n if (dirtyStart < viewStart) {\n for (const [rangeStart, rangeEnd] of changedLineRanges) {\n if (rangeStart < viewStart) {\n offscreenSyncEnd = Math.max(\n offscreenSyncEnd,\n Math.min(rangeEnd, viewStart - 1)\n );\n }\n }\n }\n const shouldFlushOffscreenLines =\n offscreenSyncEnd >= dirtyStart &&\n (canReuseCachedStates || change.lineDelta < 0);\n if (canReuseCachedStates) {\n this.#buildStateStack(dirtyStart);\n } else {\n this.#stateStack.length = Math.min(\n this.#stateStack.length,\n dirtyStart + 1\n );\n if (renderRange === undefined || dirtyStart >= viewStart) {\n this.#buildStateStack(viewStart);\n }\n }\n\n let changedRangeIndex = 0;\n let currentChangedRangeEnd = changedLineRanges[changedRangeIndex][1];\n let backgroundStartLine: number | undefined;\n let backgroundChangedRangeIndex = 0;\n let line = canReuseCachedStates\n ? changedLineRanges[changedRangeIndex][0]\n : viewStart;\n let state = this.#stateStack[line] ?? INITIAL;\n let settled = false;\n const dirtyLines: Map<number, Array<HighlightedToken>> = new Map();\n const offscreenDirtyLines:\n | Map<number, Array<HighlightedToken>>\n | undefined = shouldFlushOffscreenLines ? new Map() : undefined;\n if (offscreenDirtyLines !== undefined && !canReuseCachedStates) {\n const offscreenEnd = Math.min(\n offscreenSyncEnd + 1,\n viewStart,\n renderRangeEndLine\n );\n if (offscreenEnd > dirtyStart) {\n this.#buildStateStack(offscreenEnd);\n let offscreenLine = dirtyStart;\n let offscreenState = this.#stateStack[offscreenLine] ?? INITIAL;\n for (; offscreenLine < offscreenEnd; offscreenLine++) {\n const resolved = this.#tokenizeLineAt(offscreenLine, offscreenState);\n offscreenState = resolved.state;\n offscreenDirtyLines.set(offscreenLine, resolved.resolvedTokens);\n }\n if (canCacheTokenizedStates) {\n this.#stateStack[offscreenEnd] = offscreenState;\n }\n }\n }\n for (; line < renderRangeEndLine; ) {\n const previousNextState = canReuseCachedStates\n ? this.#stateStack[line + 1]\n : undefined;\n if (canCacheTokenizedStates) {\n this.#stateStack[line] = state;\n }\n\n const { resolvedTokens, state: nextState } = this.#tokenizeLineAt(\n line,\n state\n );\n state = nextState;\n\n if (line >= viewStart) {\n dirtyLines.set(line, resolvedTokens);\n } else {\n offscreenDirtyLines?.set(line, resolvedTokens);\n }\n\n if (canCacheTokenizedStates) {\n this.#stateStack[line + 1] = state;\n }\n settled =\n line >= currentChangedRangeEnd &&\n canReuseCachedStates &&\n previousNextState !== undefined &&\n state.equals(previousNextState);\n if (settled) {\n changedRangeIndex++;\n const nextRange = changedLineRanges[changedRangeIndex];\n if (nextRange === undefined) {\n break;\n }\n if (nextRange[0] >= renderRangeEndLine) {\n backgroundStartLine = nextRange[0];\n backgroundChangedRangeIndex = changedRangeIndex;\n break;\n }\n if (this.#stateStack[nextRange[0]] === undefined) {\n currentChangedRangeEnd = nextRange[1];\n line++;\n } else {\n line = nextRange[0];\n state = this.#stateStack[line] ?? state;\n currentChangedRangeEnd = nextRange[1];\n }\n settled = false;\n continue;\n }\n line++;\n }\n\n if (canCacheTokenizedStates) {\n if (line < renderRangeEndLine) {\n this.#stateStack[line + 1] = state;\n } else {\n this.#stateStack[line] = state;\n }\n }\n\n if (offscreenDirtyLines !== undefined && offscreenDirtyLines.size > 0) {\n this.#onDeferTokenize(offscreenDirtyLines, this.#themeType);\n }\n\n if (backgroundStartLine !== undefined) {\n this.#scheduleBackgroundTokenize(\n backgroundStartLine,\n changedLineRanges,\n backgroundChangedRangeIndex\n );\n } else if (!settled && line < lineCount) {\n const backgroundLine =\n crossesRenderRangeEnd && dirtyStart >= viewStart\n ? renderRangeEndLine\n : dirtyStart < viewStart && !canReuseCachedStates\n ? dirtyStart\n : line;\n this.#scheduleBackgroundTokenize(\n backgroundLine,\n canReuseCachedStates ? changedLineRanges : undefined,\n changedRangeIndex\n );\n }\n\n return dirtyLines;\n }\n\n prebuildStateStack(renderRange?: RenderRange): void {\n this.#prebuildStateStack(renderRange);\n }\n\n stopBackgroundTokenize(): void {\n if (this.#isStopped) {\n return;\n }\n this.#isStopped = true;\n this.#isPaused = false;\n this.#lastLine = -1;\n this.#backgroundChangedLineRanges = undefined;\n this.#backgroundChangedRangeIndex = 0;\n this.#detachMessageListener();\n }\n\n pauseBackgroundTokenize(): void {\n if (this.#isStopped || this.#isPaused) {\n return;\n }\n if (this.#debug) {\n console.log('[diffs/editor] background tokenization paused', {\n jobId: this.#backgroundJobId,\n });\n }\n this.#isPaused = true;\n }\n\n resumeBackgroundTokenize(): void {\n if (\n this.#isStopped ||\n !this.#isPaused ||\n this.#grammar === undefined ||\n this.#lastLine < 0\n ) {\n return;\n }\n if (this.#debug) {\n console.log('[diffs/editor] background tokenization resumed', {\n jobId: this.#backgroundJobId,\n });\n }\n this.#isPaused = false;\n this.#postTokenizeMessage(this.#backgroundJobId);\n }\n\n #attachMessageListener(): void {\n if (this.#isMessageListenerAttached) {\n return;\n }\n globalThis.addEventListener('message', this.#onMessage);\n this.#isMessageListenerAttached = true;\n }\n\n #detachMessageListener(): void {\n if (!this.#isMessageListenerAttached) {\n return;\n }\n globalThis.removeEventListener('message', this.#onMessage);\n this.#isMessageListenerAttached = false;\n }\n\n #postTokenizeMessage(jobId: number): void {\n // use `postMessage` instead of `setTimeout(fn, 0)` to avoid 4ms delay\n globalThis.postMessage({ type: 'tokenize', jobId });\n }\n\n #scheduleBackgroundTokenize(\n startLine: number,\n changedLineRanges?: readonly [number, number][],\n changedRangeIndex = 0\n ): void {\n const jobId = ++this.#backgroundJobId;\n\n if (this.#debug) {\n console.log('[diffs/editor] background tokenization scheduled', {\n jobId,\n startLine,\n changedLineRanges,\n changedRangeIndex,\n });\n }\n\n this.#isStopped = false;\n this.#isPaused = false;\n this.#lastLine = startLine;\n this.#backgroundChangedLineRanges = changedLineRanges;\n this.#backgroundChangedRangeIndex = changedRangeIndex;\n this.#attachMessageListener();\n this.#postTokenizeMessage(jobId);\n }\n\n #tokenizeLineAt(\n line: number,\n state: StateStack\n ): { resolvedTokens: Array<HighlightedToken>; state: StateStack } {\n if (this.#grammar === undefined) {\n throw new Error('Grammar not loaded');\n }\n const lineText = this.#textDocument.getLineText(line);\n if (lineText.length > this.#tokenizeMaxLineLength) {\n console.warn(\n `[diffs] Line(${line}) too long to tokenize: ${lineText.length}`\n );\n return { resolvedTokens: [[0, '', lineText]], state };\n }\n if (lineText === '' || lineText.trim() === '') {\n return { resolvedTokens: [[0, '', lineText]], state };\n }\n const result = tokenizeLine(\n this.#grammar,\n this.#colorMap,\n lineText,\n state,\n EditorTokenizer.TOKENIZE_TIME_LIMIT\n );\n return {\n resolvedTokens: result.resolvedTokens,\n state: result.ruleStack,\n };\n }\n\n #buildStateStack(endAt: number) {\n const boundedEndAt = Math.min(\n Math.max(0, endAt),\n this.#textDocument.lineCount\n );\n if (this.#stateStack.length > boundedEndAt || this.#grammar === undefined) {\n return;\n }\n let line = this.#stateStack.length - 1;\n let state = this.#stateStack[line] ?? INITIAL;\n for (; line < boundedEndAt; line++) {\n this.#stateStack[line] = state;\n const lineText = this.#textDocument.getLineText(line);\n if (\n lineText.length <= this.#tokenizeMaxLineLength &&\n lineText !== '' &&\n lineText.trim() !== ''\n ) {\n state = this.#grammar.tokenizeLine2(\n lineText,\n state,\n EditorTokenizer.TOKENIZE_TIME_LIMIT\n ).ruleStack;\n }\n }\n this.#stateStack[line] = state;\n }\n\n #backgroundTokenize(jobId: number) {\n if (\n this.#isStopped ||\n this.#isPaused ||\n this.#grammar === undefined ||\n jobId !== this.#backgroundJobId\n ) {\n return;\n }\n\n const t = performance.now();\n const lines = new Map<number, Array<HighlightedToken>>();\n const totalLines = this.#textDocument.lineCount;\n const changedLineRanges = this.#backgroundChangedLineRanges;\n\n let line = this.#lastLine;\n let state = this.#stateStack[line] ?? INITIAL;\n let settled = false;\n let changedRangeIndex = this.#backgroundChangedRangeIndex;\n let currentChangedRangeEnd = changedLineRanges?.[changedRangeIndex]?.[1];\n for (; line < totalLines; ) {\n this.#stateStack[line] = state;\n\n const previousNextState =\n currentChangedRangeEnd !== undefined\n ? this.#stateStack[line + 1]\n : undefined;\n const lineText = this.#textDocument.getLineText(line);\n if (lineText.length > this.#tokenizeMaxLineLength) {\n console.warn(\n `[diffs] Line(${line}) too long to tokenize: ${lineText.length}`\n );\n lines.set(line, [[0, '', lineText]]);\n } else if (lineText === '' || lineText.trim() === '') {\n lines.set(line, [[0, '', lineText]]);\n } else {\n const ret = tokenizeLine(\n this.#grammar,\n this.#colorMap,\n lineText,\n state,\n EditorTokenizer.TOKENIZE_TIME_LIMIT\n );\n lines.set(line, ret.resolvedTokens);\n state = ret.ruleStack;\n }\n\n this.#stateStack[line + 1] = state;\n settled =\n currentChangedRangeEnd !== undefined &&\n line >= currentChangedRangeEnd &&\n previousNextState !== undefined &&\n state.equals(previousNextState);\n line++;\n if (settled) {\n changedRangeIndex++;\n const nextRange = changedLineRanges?.[changedRangeIndex];\n if (nextRange === undefined) {\n break;\n }\n currentChangedRangeEnd = nextRange[1];\n if (this.#stateStack[nextRange[0]] === undefined) {\n settled = false;\n } else {\n line = nextRange[0];\n state = this.#stateStack[line] ?? state;\n settled = false;\n continue;\n }\n }\n\n // limit the time of partial tokenize to 1ms\n if (performance.now() - t > 1) {\n break;\n }\n }\n\n this.#onDeferTokenize(lines, this.#themeType);\n if (this.#isStopped || this.#isPaused || jobId !== this.#backgroundJobId) {\n return;\n }\n\n if (settled || line >= totalLines) {\n this.stopBackgroundTokenize();\n return;\n }\n\n this.#lastLine = line;\n this.#backgroundChangedRangeIndex = changedRangeIndex;\n this.#postTokenizeMessage(jobId);\n }\n}\n\nexport function tokenizeLine(\n grammar: IGrammar,\n colorMap: string[],\n lineText: string,\n stateStack: StateStack,\n timeLimit?: number\n): {\n ruleStack: StateStack;\n resolvedTokens: Array<HighlightedToken>;\n} {\n const result = grammar.tokenizeLine2(lineText, stateStack, timeLimit);\n if (result.stoppedEarly) {\n console.warn(\n `[diffs] Time limit reached when tokenizing line: ${lineText.substring(0, 100)}`\n );\n }\n const rawTokens = result.tokens;\n const tokensLength = rawTokens.length / 2;\n const resolvedTokens: Array<HighlightedToken> = [];\n for (let j = 0; j < tokensLength; j++) {\n const offset = rawTokens[2 * j];\n const nextOffset =\n j + 1 < tokensLength ? rawTokens[2 * j + 2] : lineText.length;\n if (offset === nextOffset) {\n // should never reach here, skip if happens anyway\n continue;\n }\n const metadata = rawTokens[2 * j + 1];\n const bg = EncodedTokenMetadata.getForeground(metadata);\n const fg = colorMap[bg];\n const tokenText = lineText.slice(offset, nextOffset);\n resolvedTokens.push([offset, fg, tokenText]);\n }\n return {\n ruleStack: result.ruleStack,\n resolvedTokens,\n };\n}\n\nexport function renderLineTokens(\n tokens: Array<HighlightedToken>,\n themeType: 'light' | 'dark'\n): (HTMLElement | string)[] {\n return tokens.map(([char, fg, textContent]) => {\n if (char === 0 && fg === '') {\n if (textContent === '') {\n return h('br');\n }\n return textContent;\n }\n return h('span', {\n dataset: {\n char: char.toString(),\n },\n style: `--diffs-token-${themeType}:${fg};`,\n textContent: textContent,\n });\n });\n}\n"],"mappings":";;;;;;AA8BA,IAAa,kBAAb,MAAa,gBAAgB;CAC3B,OAAO,sBAAsB;CAE7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA,cAA4B,CAAC,QAAQ;CACrC,YAAoB;CACpB,aAAsB;CACtB,YAAqB;CACrB,mBAA2B;CAC3B;CACA,+BAAuC;CACvC,6BAAsC;CAEtC,sBAAsB,SAAS,OAAO,gBAA8B;EAClE,MAAM,EAAE,eAAe,GAAG,aAAa,aAAa,eAAe,EAAE;EACrE,MAAM,UAAU,KAAK,IACnB,eAAe,WAAW,WAAW,eAAe,YACpD,MAAKA,aAAc,UACpB;AACD,MAAI,MAAKC,YAAa,QAAW;AAC/B,SAAM,MAAKC,YAAa,aAAa,MAAKF,aAAc,WAAW;AACnE,SAAKC,UAAW,MAAKC,YAAa,YAChC,MAAKF,aAAc,WACpB;;AAEH,QAAKG,gBAAiB,QAAQ;IAC7B,IAAI;CAEP,cAAc,EAAE,WAAkC;AAChD,MAAI,OAAO,SAAS,YAAY,SAAS,KACvC;EAEF,MAAM,EAAE,MAAM,UAAU;AAIxB,MACE,SAAS,cACT,OAAO,UAAU,YACjB,UAAU,MAAKC,gBAEf,OAAKC,mBAAoB,MAAM;;CAInC,IAAI,YAA8B;AAChC,SAAO,MAAKC;;CAGd,YAAY,EACV,aACA,aACA,cACA,UACA,iBACA,WACuB;EACvB,MAAM,EACJ,YAAY,UACZ,QAAQ,gBACR,wBAAwB,QACtB;AACJ,QAAKC,iBAAkB,OAAO,WAAW,+BAA+B;AACxE,MAAI,cAAc,SAChB,OAAKD,YAAa,MAAKC,eAAgB,UAAU,SAAS;MAE1D,OAAKD,YAAa;AAEpB,MAAI,OAAO,UAAU,UAAU;GAC7B,MAAM,WAAW,IAAI,kBAAkB,cAAc;AACnD,SAAK,MAAM,EAAE,MAAM,mBAAmB,UACpC,KACE,SAAS,gBACT,kBAAkB,SACjB,kBAAkB,WAAW,cAAc,WAAW,QAAQ,GAC/D;KACA,MAAME,cACJ,iBAAiB,SAAS,KAAK,CAAC,gBAAgB,SAC5C,SACA;AACN,WAAKC,gBAAiB,MAAMD,cAAYA,YAAU;AAClD;;KAGJ;AACF,YAAS,QAAQ,SAAS,iBAAiB,EAAE,YAAY,MAAM,CAAC;AAChE,YAAS,QAAQ,SAAS,MAAM,EAAE,YAAY,MAAM,CAAC;AACrD,SAAKE,WAAY,CACf,iBAAiB,MAAKH,gBAAiB,WAAW,MAAM;IACtD,MAAMC,cAAY,EAAE,UAAU,SAAS;AACvC,UAAKC,gBAAiB,MAAMD,cAAYA,YAAU;KAClD,QACI,SAAS,YAAY,CAC5B;;AAEH,QAAKN,cAAe;AACpB,QAAKF,eAAgB;AACrB,QAAKW,wBAAyB;AAC9B,QAAKC,WAAY;AACjB,QAAKC,kBAAmB;AACxB,QAAKC,QAAS,WAAW;AACzB,MAAI,YAAY,oBAAoB,CAAC,SAAS,aAAa,WAAW,CACpE,OAAKb,UAAW,YAAY,YAAY,aAAa,WAAW;AAElE,QAAKc,WAAY,EAAE;AACnB,QAAKC,SAAU,OAAO,UAAU,WAAW,QAAQ,MAAM,MAAKV,WAAY;;CAK5E,iBAAiB,WAAmB,WAA6B;AAC/D,QAAKA,YAAa;AAClB,QAAKU,SAAU,UAAU;AACzB,OAAK,wBAAwB;AAC7B,QAAKC,aAAc,CAAC,QAAQ;AAC5B,MAAI,MAAKhB,YAAa,UAAa,MAAKD,aAAc,YAAY,EAChE,OAAKkB,2BAA4B,EAAE;;CAIvC,UAAU,WAAmB;AAC3B,QAAKH,WAAY,MAAKb,YAAa,SAAS,UAAU,CAAC;EACvD,MAAM,EAAE,SAAS,EAAE,KAAK,MAAKA,YAAa,SAAS,UAAU;EAC7D,MAAM,sBAAsB,OAAO;EACnC,MAAM,0BAA0B,OAAO;EACvC,MAAM,mBAAmB,OAAO;EAChC,MAAM,yBAAyB,OAAO;EACtC,MAAM,mBAAmB,OAAO;EAChC,MAAM,sBAAsB,OAAO;EACnC,MAAM,+BACJ,OAAO;AACT,QAAKU,SAAU;qCACkB,uBAAuB,uBAAuB;0CACzC,2BAA2B,uBAAuB;uCACrD,oBAAoB,yBAAyB;8CACtC,2BAA2B,wCAAwC;8CACnE,0BAA0B,mCAAmC;QACnG,qBAAqB,SAAY,6BAA6B,iBAAiB,KAAK,GAAG;QACvF,wBAAwB,SAAY,4BAA4B,oBAAoB,KAAK,GAAG;QAC5F,iCAAiC,SAAY,sCAAsC,6BAA6B,KAAK,GAAG;OACzH;;CAGL,UAAgB;AACd,QAAKO,uBAAwB;AAC7B,OAAK,wBAAwB;AAC7B,QAAKT,UAAW,SAAS,YAAY,SAAS,CAAC;AAC/C,QAAKA,WAAY;;CAKnB,SACE,QACA,aACsC;AACtC,MAAI,MAAKT,YAAa,OACpB,OAAM,IAAI,MAAM,qBAAqB;EAGvC,MAAM,EAAE,cAAc,MAAKD;EAC3B,MAAM,EAAE,eAAe,GAAG,aAAa,aAAa,eAAe,EAAE;EACrE,MAAM,qBACJ,eAAe,WACX,YACA,KAAK,IAAI,eAAe,YAAY,UAAU;EAEpD,MAAM,aAAa,OAAO;EAC1B,MAAM,YAAY,KAAK,IAAI,cAAc,WAAW;EACpD,MAAM,wBACJ,gBAAgB,UAChB,eAAe,YACf,OAAO,YAAY,KACnB,aAAa,sBACb,OAAO,WAAW;EACpB,MAAM,uBAAuB,OAAO,cAAc;EAClD,MAAM,0BACJ,wBACA,gBAAgB,UAChB,cAAc;EAChB,MAAMoB,oBAAiD,uBAClD,OAAO,qBAAqB,CAAC,CAAC,YAAY,OAAO,QAAQ,CAAC,GAC3D,CAAC,CAAC,YAAY,OAAO,QAAQ,CAAC;EAClC,IAAI,mBAAmB;AACvB,MAAI,aAAa,WACf;QAAK,MAAM,CAAC,YAAY,aAAa,kBACnC,KAAI,aAAa,UACf,oBAAmB,KAAK,IACtB,kBACA,KAAK,IAAI,UAAU,YAAY,EAAE,CAClC;;EAIP,MAAM,4BACJ,oBAAoB,eACnB,wBAAwB,OAAO,YAAY;AAC9C,MAAI,qBACF,OAAKjB,gBAAiB,WAAW;OAC5B;AACL,SAAKc,WAAY,SAAS,KAAK,IAC7B,MAAKA,WAAY,QACjB,aAAa,EACd;AACD,OAAI,gBAAgB,UAAa,cAAc,UAC7C,OAAKd,gBAAiB,UAAU;;EAIpC,IAAI,oBAAoB;EACxB,IAAI,yBAAyB,kBAAkB,mBAAmB;EAClE,IAAIkB;EACJ,IAAI,8BAA8B;EAClC,IAAI,OAAO,uBACP,kBAAkB,mBAAmB,KACrC;EACJ,IAAI,QAAQ,MAAKJ,WAAY,SAAS;EACtC,IAAI,UAAU;EACd,MAAMK,6BAAmD,IAAI,KAAK;EAClE,MAAMC,sBAEU,4CAA4B,IAAI,KAAK,GAAG;AACxD,MAAI,wBAAwB,UAAa,CAAC,sBAAsB;GAC9D,MAAM,eAAe,KAAK,IACxB,mBAAmB,GACnB,WACA,mBACD;AACD,OAAI,eAAe,YAAY;AAC7B,UAAKpB,gBAAiB,aAAa;IACnC,IAAI,gBAAgB;IACpB,IAAI,iBAAiB,MAAKc,WAAY,kBAAkB;AACxD,WAAO,gBAAgB,cAAc,iBAAiB;KACpD,MAAM,WAAW,MAAKO,eAAgB,eAAe,eAAe;AACpE,sBAAiB,SAAS;AAC1B,yBAAoB,IAAI,eAAe,SAAS,eAAe;;AAEjE,QAAI,wBACF,OAAKP,WAAY,gBAAgB;;;AAIvC,SAAO,OAAO,qBAAsB;GAClC,MAAM,oBAAoB,uBACtB,MAAKA,WAAY,OAAO,KACxB;AACJ,OAAI,wBACF,OAAKA,WAAY,QAAQ;GAG3B,MAAM,EAAE,gBAAgB,OAAO,cAAc,MAAKO,eAChD,MACA,MACD;AACD,WAAQ;AAER,OAAI,QAAQ,UACV,YAAW,IAAI,MAAM,eAAe;OAEpC,sBAAqB,IAAI,MAAM,eAAe;AAGhD,OAAI,wBACF,OAAKP,WAAY,OAAO,KAAK;AAE/B,aACE,QAAQ,0BACR,wBACA,sBAAsB,UACtB,MAAM,OAAO,kBAAkB;AACjC,OAAI,SAAS;AACX;IACA,MAAM,YAAY,kBAAkB;AACpC,QAAI,cAAc,OAChB;AAEF,QAAI,UAAU,MAAM,oBAAoB;AACtC,2BAAsB,UAAU;AAChC,mCAA8B;AAC9B;;AAEF,QAAI,MAAKA,WAAY,UAAU,QAAQ,QAAW;AAChD,8BAAyB,UAAU;AACnC;WACK;AACL,YAAO,UAAU;AACjB,aAAQ,MAAKA,WAAY,SAAS;AAClC,8BAAyB,UAAU;;AAErC,cAAU;AACV;;AAEF;;AAGF,MAAI,wBACF,KAAI,OAAO,mBACT,OAAKA,WAAY,OAAO,KAAK;MAE7B,OAAKA,WAAY,QAAQ;AAI7B,MAAI,wBAAwB,UAAa,oBAAoB,OAAO,EAClE,OAAKJ,gBAAiB,qBAAqB,MAAKP,UAAW;AAG7D,MAAI,wBAAwB,OAC1B,OAAKY,2BACH,qBACA,mBACA,4BACD;WACQ,CAAC,WAAW,OAAO,WAAW;GACvC,MAAM,iBACJ,yBAAyB,cAAc,YACnC,qBACA,aAAa,aAAa,CAAC,uBACzB,aACA;AACR,SAAKA,2BACH,gBACA,uBAAuB,oBAAoB,QAC3C,kBACD;;AAGH,SAAO;;CAGT,mBAAmB,aAAiC;AAClD,QAAKO,mBAAoB,YAAY;;CAGvC,yBAA+B;AAC7B,MAAI,MAAKC,UACP;AAEF,QAAKA,YAAa;AAClB,QAAKC,WAAY;AACjB,QAAKC,WAAY;AACjB,QAAKC,8BAA+B;AACpC,QAAKC,8BAA+B;AACpC,QAAKX,uBAAwB;;CAG/B,0BAAgC;AAC9B,MAAI,MAAKO,aAAc,MAAKC,SAC1B;AAEF,MAAI,MAAKb,MACP,SAAQ,IAAI,iDAAiD,EAC3D,OAAO,MAAKV,iBACb,CAAC;AAEJ,QAAKuB,WAAY;;CAGnB,2BAAiC;AAC/B,MACE,MAAKD,aACL,CAAC,MAAKC,YACN,MAAK1B,YAAa,UAClB,MAAK2B,WAAY,EAEjB;AAEF,MAAI,MAAKd,MACP,SAAQ,IAAI,kDAAkD,EAC5D,OAAO,MAAKV,iBACb,CAAC;AAEJ,QAAKuB,WAAY;AACjB,QAAKI,oBAAqB,MAAK3B,gBAAiB;;CAGlD,yBAA+B;AAC7B,MAAI,MAAK4B,0BACP;AAEF,aAAW,iBAAiB,WAAW,MAAKC,UAAW;AACvD,QAAKD,4BAA6B;;CAGpC,yBAA+B;AAC7B,MAAI,CAAC,MAAKA,0BACR;AAEF,aAAW,oBAAoB,WAAW,MAAKC,UAAW;AAC1D,QAAKD,4BAA6B;;CAGpC,qBAAqB,OAAqB;AAExC,aAAW,YAAY;GAAE,MAAM;GAAY;GAAO,CAAC;;CAGrD,4BACE,WACA,mBACA,oBAAoB,GACd;EACN,MAAM,QAAQ,EAAE,MAAK5B;AAErB,MAAI,MAAKU,MACP,SAAQ,IAAI,oDAAoD;GAC9D;GACA;GACA;GACA;GACD,CAAC;AAGJ,QAAKY,YAAa;AAClB,QAAKC,WAAY;AACjB,QAAKC,WAAY;AACjB,QAAKC,8BAA+B;AACpC,QAAKC,8BAA+B;AACpC,QAAKI,uBAAwB;AAC7B,QAAKH,oBAAqB,MAAM;;CAGlC,gBACE,MACA,OACgE;AAChE,MAAI,MAAK9B,YAAa,OACpB,OAAM,IAAI,MAAM,qBAAqB;EAEvC,MAAM,WAAW,MAAKD,aAAc,YAAY,KAAK;AACrD,MAAI,SAAS,SAAS,MAAKW,uBAAwB;AACjD,WAAQ,KACN,gBAAgB,KAAK,0BAA0B,SAAS,SACzD;AACD,UAAO;IAAE,gBAAgB,CAAC;KAAC;KAAG;KAAI;KAAS,CAAC;IAAE;IAAO;;AAEvD,MAAI,aAAa,MAAM,SAAS,MAAM,KAAK,GACzC,QAAO;GAAE,gBAAgB,CAAC;IAAC;IAAG;IAAI;IAAS,CAAC;GAAE;GAAO;EAEvD,MAAM,SAAS,aACb,MAAKV,SACL,MAAKc,UACL,UACA,OACA,gBAAgB,oBACjB;AACD,SAAO;GACL,gBAAgB,OAAO;GACvB,OAAO,OAAO;GACf;;CAGH,iBAAiB,OAAe;EAC9B,MAAM,eAAe,KAAK,IACxB,KAAK,IAAI,GAAG,MAAM,EAClB,MAAKf,aAAc,UACpB;AACD,MAAI,MAAKiB,WAAY,SAAS,gBAAgB,MAAKhB,YAAa,OAC9D;EAEF,IAAI,OAAO,MAAKgB,WAAY,SAAS;EACrC,IAAI,QAAQ,MAAKA,WAAY,SAAS;AACtC,SAAO,OAAO,cAAc,QAAQ;AAClC,SAAKA,WAAY,QAAQ;GACzB,MAAM,WAAW,MAAKjB,aAAc,YAAY,KAAK;AACrD,OACE,SAAS,UAAU,MAAKW,yBACxB,aAAa,MACb,SAAS,MAAM,KAAK,GAEpB,SAAQ,MAAKV,QAAS,cACpB,UACA,OACA,gBAAgB,oBACjB,CAAC;;AAGN,QAAKgB,WAAY,QAAQ;;CAG3B,oBAAoB,OAAe;AACjC,MACE,MAAKS,aACL,MAAKC,YACL,MAAK1B,YAAa,UAClB,UAAU,MAAKG,gBAEf;EAGF,MAAM,IAAI,YAAY,KAAK;EAC3B,MAAM,wBAAQ,IAAI,KAAsC;EACxD,MAAM,aAAa,MAAKJ,aAAc;EACtC,MAAM,oBAAoB,MAAK6B;EAE/B,IAAI,OAAO,MAAKD;EAChB,IAAI,QAAQ,MAAKX,WAAY,SAAS;EACtC,IAAI,UAAU;EACd,IAAI,oBAAoB,MAAKa;EAC7B,IAAI,yBAAyB,oBAAoB,qBAAqB;AACtE,SAAO,OAAO,aAAc;AAC1B,SAAKb,WAAY,QAAQ;GAEzB,MAAM,oBACJ,2BAA2B,SACvB,MAAKA,WAAY,OAAO,KACxB;GACN,MAAM,WAAW,MAAKjB,aAAc,YAAY,KAAK;AACrD,OAAI,SAAS,SAAS,MAAKW,uBAAwB;AACjD,YAAQ,KACN,gBAAgB,KAAK,0BAA0B,SAAS,SACzD;AACD,UAAM,IAAI,MAAM,CAAC;KAAC;KAAG;KAAI;KAAS,CAAC,CAAC;cAC3B,aAAa,MAAM,SAAS,MAAM,KAAK,GAChD,OAAM,IAAI,MAAM,CAAC;IAAC;IAAG;IAAI;IAAS,CAAC,CAAC;QAC/B;IACL,MAAM,MAAM,aACV,MAAKV,SACL,MAAKc,UACL,UACA,OACA,gBAAgB,oBACjB;AACD,UAAM,IAAI,MAAM,IAAI,eAAe;AACnC,YAAQ,IAAI;;AAGd,SAAKE,WAAY,OAAO,KAAK;AAC7B,aACE,2BAA2B,UAC3B,QAAQ,0BACR,sBAAsB,UACtB,MAAM,OAAO,kBAAkB;AACjC;AACA,OAAI,SAAS;AACX;IACA,MAAM,YAAY,oBAAoB;AACtC,QAAI,cAAc,OAChB;AAEF,6BAAyB,UAAU;AACnC,QAAI,MAAKA,WAAY,UAAU,QAAQ,OACrC,WAAU;SACL;AACL,YAAO,UAAU;AACjB,aAAQ,MAAKA,WAAY,SAAS;AAClC,eAAU;AACV;;;AAKJ,OAAI,YAAY,KAAK,GAAG,IAAI,EAC1B;;AAIJ,QAAKJ,gBAAiB,OAAO,MAAKP,UAAW;AAC7C,MAAI,MAAKoB,aAAc,MAAKC,YAAa,UAAU,MAAKvB,gBACtD;AAGF,MAAI,WAAW,QAAQ,YAAY;AACjC,QAAK,wBAAwB;AAC7B;;AAGF,QAAKwB,WAAY;AACjB,QAAKE,8BAA+B;AACpC,QAAKC,oBAAqB,MAAM;;;AAIpC,SAAgB,aACd,SACA,UACA,UACA,YACA,WAIA;CACA,MAAM,SAAS,QAAQ,cAAc,UAAU,YAAY,UAAU;AACrE,KAAI,OAAO,aACT,SAAQ,KACN,oDAAoD,SAAS,UAAU,GAAG,IAAI,GAC/E;CAEH,MAAM,YAAY,OAAO;CACzB,MAAM,eAAe,UAAU,SAAS;CACxC,MAAMI,iBAA0C,EAAE;AAClD,MAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK;EACrC,MAAM,SAAS,UAAU,IAAI;EAC7B,MAAM,aACJ,IAAI,IAAI,eAAe,UAAU,IAAI,IAAI,KAAK,SAAS;AACzD,MAAI,WAAW,WAEb;EAEF,MAAM,WAAW,UAAU,IAAI,IAAI;EAEnC,MAAM,KAAK,SADA,qBAAqB,cAAc,SAAS;EAEvD,MAAM,YAAY,SAAS,MAAM,QAAQ,WAAW;AACpD,iBAAe,KAAK;GAAC;GAAQ;GAAI;GAAU,CAAC;;AAE9C,QAAO;EACL,WAAW,OAAO;EAClB;EACD;;AAGH,SAAgB,iBACd,QACA,WAC0B;AAC1B,QAAO,OAAO,KAAK,CAAC,MAAM,IAAI,iBAAiB;AAC7C,MAAI,SAAS,KAAK,OAAO,IAAI;AAC3B,OAAI,gBAAgB,GAClB,QAAO,EAAE,KAAK;AAEhB,UAAO;;AAET,SAAO,EAAE,QAAQ;GACf,SAAS,EACP,MAAM,KAAK,UAAU,EACtB;GACD,OAAO,iBAAiB,UAAU,GAAG,GAAG;GAC3B;GACd,CAAC;GACF"}