webgal-parser 4.6.2 → 4.6.3-beta.2

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.
@@ -38,6 +38,7 @@ var webgalParser = (function (exports) {
38
38
  commandType[commandType["applyStyle"] = 32] = "applyStyle";
39
39
  commandType[commandType["wait"] = 33] = "wait";
40
40
  commandType[commandType["callSteam"] = 34] = "callSteam";
41
+ commandType[commandType["return"] = 35] = "return";
41
42
  })(commandType || (commandType = {}));
42
43
 
43
44
  const SCRIPT_CONFIG = [
@@ -79,6 +80,7 @@ var webgalParser = (function (exports) {
79
80
  { scriptString: 'applyStyle', scriptType: commandType.applyStyle },
80
81
  { scriptString: 'wait', scriptType: commandType.wait },
81
82
  { scriptString: 'callSteam', scriptType: commandType.callSteam },
83
+ { scriptString: 'return', scriptType: commandType.return },
82
84
  ];
83
85
  const ADD_NEXT_ARG_LIST = [
84
86
  commandType.bgm,
@@ -137,7 +139,7 @@ var webgalParser = (function (exports) {
137
139
  argValue = undefined;
138
140
  }
139
141
  // 判断是不是语音参数
140
- if (argName.toLowerCase().match(/.ogg|.mp3|.wav/)) {
142
+ if (argName.toLowerCase().match(/.ogg|.mp3|.wav|.opus/)) {
141
143
  returnArrayList.push({
142
144
  key: 'vocal',
143
145
  value: assetSetter(e, fileType.vocal),
@@ -462,7 +464,10 @@ var webgalParser = (function (exports) {
462
464
  args: [{ key: 'next', value: true }],
463
465
  sentenceAssets: [],
464
466
  subScene: [],
465
- inlineComment: '', // 行内注释
467
+ inlineComment: '',
468
+ startLine: lineNumber,
469
+ endLine: lineNumber,
470
+ isLineBreakHolder: false,
466
471
  };
467
472
  }
468
473
  // 截取命令
@@ -513,92 +518,14 @@ var webgalParser = (function (exports) {
513
518
  args: args,
514
519
  sentenceAssets: sentenceAssets,
515
520
  subScene: subScene,
516
- inlineComment: sentenceComment.trim(), // 行内注释
521
+ inlineComment: sentenceComment.trim(),
522
+ // 单行语句的行范围就是自己,多行语句的 endLine 由 sceneParser 回填
523
+ startLine: lineNumber,
524
+ endLine: lineNumber,
525
+ isLineBreakHolder: false,
517
526
  };
518
527
  };
519
528
 
520
- /**
521
- * 场景解析器
522
- * @param rawScene 原始场景
523
- * @param sceneName 场景名称
524
- * @param sceneUrl 场景url
525
- * @param assetsPrefetcher
526
- * @param assetSetter
527
- * @param ADD_NEXT_ARG_LIST
528
- * @param SCRIPT_CONFIG_MAP
529
- * @return {IScene} 解析后的场景
530
- */
531
- const sceneParser = (rawScene, sceneName, sceneUrl, assetsPrefetcher, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP) => {
532
- const rawSentenceList = rawScene.replaceAll('\r', '').split('\n'); // 原始句子列表
533
- // 去分号留到后面去做了,现在注释要单独处理
534
- const rawSentenceListWithoutEmpty = rawSentenceList;
535
- // .map((sentence) => sentence.split(";")[0])
536
- // .filter((sentence) => sentence.trim() !== "");
537
- let assetsList = []; // 场景资源列表
538
- let subSceneList = []; // 子场景列表
539
- const sentenceList = rawSentenceListWithoutEmpty.map((sentence, index) => {
540
- const returnSentence = scriptParser(sentence, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP, index);
541
- // 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList
542
- assetsList = [...assetsList, ...returnSentence.sentenceAssets];
543
- subSceneList = [...subSceneList, ...returnSentence.subScene];
544
- return returnSentence;
545
- });
546
- // 开始资源的预加载
547
- assetsList = deduplicateAssets(assetsList);
548
- assetsPrefetcher(assetsList);
549
- return {
550
- sceneName: sceneName,
551
- sceneUrl: sceneUrl,
552
- sentenceList: sentenceList,
553
- assetsList: assetsList,
554
- subSceneList: subSceneList, // 子场景列表
555
- };
556
- };
557
- const deduplicateAssets = (assetsList) => {
558
- const seenAssets = new Set();
559
- return assetsList.filter((asset) => {
560
- if (!asset || typeof asset.url !== 'string' || asset.url === '') {
561
- return false;
562
- }
563
- const assetKey = `${asset.type}:${asset.url}`;
564
- if (seenAssets.has(assetKey)) {
565
- return false;
566
- }
567
- seenAssets.add(assetKey);
568
- return true;
569
- });
570
- };
571
-
572
- function scss2cssinjsParser(scssString) {
573
- const [classNameStyles, others] = parseCSS(scssString);
574
- return {
575
- classNameStyles,
576
- others,
577
- };
578
- }
579
- /**
580
- * GPT 4 写的,临时用,以后要重构!!!
581
- * TODO:用人类智能重构,要是用着一直没问题,也不是不可以 trust AI
582
- * @param css
583
- */
584
- function parseCSS(css) {
585
- const result = {};
586
- let specialRules = '';
587
- let matches;
588
- // 使用非贪婪匹配,尝试正确处理任意层次的嵌套
589
- const classRegex = /\.([^{\s]+)\s*{((?:[^{}]*|{[^}]*})*)}/g;
590
- const specialRegex = /(@[^{]+{\s*(?:[^{}]*{[^}]*}[^{}]*)+\s*})/g;
591
- while ((matches = classRegex.exec(css)) !== null) {
592
- const key = matches[1];
593
- const value = matches[2].trim().replace(/\s*;\s*/g, ';\n');
594
- result[key] = value;
595
- }
596
- while ((matches = specialRegex.exec(css)) !== null) {
597
- specialRules += matches[1].trim() + '\n';
598
- }
599
- return [result, specialRules.trim()];
600
- }
601
-
602
529
  /**
603
530
  * Preprocessor for scene text.
604
531
  *
@@ -628,13 +555,11 @@ var webgalParser = (function (exports) {
628
555
  let lastLineIsMultiline = false;
629
556
  let thisLineIsMultiline = false;
630
557
  for (const line of lines) {
631
- thisLineIsMultiline = false;
632
- if (canBeMultiline(line)) {
633
- thisLineIsMultiline = true;
634
- }
635
- if (shouldNotBeMultiline(line, lastLineIsMultiline)) {
636
- thisLineIsMultiline = false;
637
- }
558
+ // 续行需要接到上一行,所以场景的第一行永远不会是续行。
559
+ thisLineIsMultiline =
560
+ processedLines.length > 0 &&
561
+ canBeMultiline(line) &&
562
+ !shouldNotBeMultiline(line, lastLineIsMultiline);
638
563
  if (thisLineIsMultiline) {
639
564
  processedLines[processedLines.length - 1] += '\\';
640
565
  }
@@ -681,7 +606,10 @@ var webgalParser = (function (exports) {
681
606
  */
682
607
  function sceneTextPreProcessPassTwo(lines) {
683
608
  const processedLines = [];
684
- let currentMultilineContent = "";
609
+ // null 表示当前不在多行序列里。这里不能用空串来判断:
610
+ // 序列的首行本身可能就是空行(比如空行后面紧跟着一条续行),
611
+ // 那样整条序列会被静默丢弃,破坏「预处理前后行数一致」这个前提。
612
+ let currentMultilineContent = null;
685
613
  let placeHolderLines = [];
686
614
  function concat(line) {
687
615
  let trimmed = line.trim();
@@ -691,11 +619,16 @@ var webgalParser = (function (exports) {
691
619
  currentMultilineContent = currentMultilineContent + trimmed;
692
620
  placeHolderLines.push(placeholderLine(line));
693
621
  }
622
+ /** 把折叠好的整条语句连同补齐行数的占位行一起输出 */
623
+ function flushMultiline() {
624
+ processedLines.push(currentMultilineContent, ...placeHolderLines);
625
+ placeHolderLines = [];
626
+ currentMultilineContent = null;
627
+ }
694
628
  for (const line of lines) {
695
- console.log(line);
696
629
  if (line.endsWith('\\')) {
697
630
  const trueLine = line.slice(0, -1);
698
- if (currentMultilineContent === "") {
631
+ if (currentMultilineContent === null) {
699
632
  // first line
700
633
  currentMultilineContent = trueLine;
701
634
  }
@@ -705,19 +638,32 @@ var webgalParser = (function (exports) {
705
638
  }
706
639
  continue;
707
640
  }
708
- if (currentMultilineContent !== "") {
641
+ if (currentMultilineContent !== null) {
709
642
  // end line
710
643
  concat(line);
711
- processedLines.push(currentMultilineContent);
712
- processedLines.push(...placeHolderLines);
713
- placeHolderLines = [];
714
- currentMultilineContent = "";
644
+ flushMultiline();
715
645
  continue;
716
646
  }
717
647
  processedLines.push(line);
718
648
  }
649
+ // 场景末行仍以 \ 结尾时循环里没有机会收尾,在这里把累积的内容补出去。
650
+ if (currentMultilineContent !== null) {
651
+ flushMultiline();
652
+ }
719
653
  return processedLines;
720
654
  }
655
+ /**
656
+ * 占位行的前缀。占位行本身是一条注释,引擎会空跑掉;
657
+ * 它的作用是让预处理后的行数与原始场景一致,从而保持
658
+ * 「解析后语句 index == 文件行号」这一不变量。
659
+ */
660
+ const WEBGAL_LINE_BREAK_MARK = ';_WEBGAL_LINE_BREAK_';
661
+ /**
662
+ * 判断预处理后的某一行是否是占位行,即它是上一条多行语句折叠掉的续行。
663
+ */
664
+ function isLineBreakPlaceholder(processedLine) {
665
+ return processedLine.startsWith(WEBGAL_LINE_BREAK_MARK);
666
+ }
721
667
  /**
722
668
  * Placeholder Line. Adding this line preserves the original number of lines
723
669
  * in the scene text, so that it can be compatible with the graphical editor.
@@ -726,70 +672,116 @@ var webgalParser = (function (exports) {
726
672
  * @returns The placeholder line
727
673
  */
728
674
  function placeholderLine(content = "") {
729
- return ";_WEBGAL_LINE_BREAK_" + content;
675
+ return WEBGAL_LINE_BREAK_MARK + content;
676
+ }
677
+
678
+ /**
679
+ * 场景解析器
680
+ * @param rawScene 原始场景
681
+ * @param sceneName 场景名称
682
+ * @param sceneUrl 场景url
683
+ * @param assetsPrefetcher
684
+ * @param assetSetter
685
+ * @param ADD_NEXT_ARG_LIST
686
+ * @param SCRIPT_CONFIG_MAP
687
+ * @return {IScene} 解析后的场景
688
+ */
689
+ const sceneParser = (rawScene, sceneName, sceneUrl, assetsPrefetcher, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP) => {
690
+ // 预处理把多行语句折叠进它的首行,并用占位行补齐被折叠掉的行,
691
+ // 因此这里的行数与原始场景严格一致,「语句 index == 文件行号」始终成立。
692
+ const rawSentenceList = sceneTextPreProcess(rawScene).split('\n'); // 原始句子列表
693
+ // 去分号留到后面去做了,现在注释要单独处理
694
+ const rawSentenceListWithoutEmpty = rawSentenceList;
695
+ // .map((sentence) => sentence.split(";")[0])
696
+ // .filter((sentence) => sentence.trim() !== "");
697
+ let assetsList = []; // 场景资源列表
698
+ let subSceneList = []; // 子场景列表
699
+ const sentenceList = rawSentenceListWithoutEmpty.map((sentence, index) => {
700
+ const returnSentence = scriptParser(sentence, assetSetter, ADD_NEXT_ARG_LIST, SCRIPT_CONFIG_MAP, index);
701
+ // 在这里解析出语句可能携带的资源和场景,合并到 assetsList 和 subSceneList
702
+ assetsList = [...assetsList, ...returnSentence.sentenceAssets];
703
+ subSceneList = [...subSceneList, ...returnSentence.subScene];
704
+ return returnSentence;
705
+ });
706
+ markMultilineRanges(sentenceList, rawSentenceList);
707
+ // 开始资源的预加载
708
+ assetsList = deduplicateAssets(assetsList);
709
+ assetsPrefetcher(assetsList);
710
+ return {
711
+ sceneName: sceneName,
712
+ sceneUrl: sceneUrl,
713
+ sentenceList: sentenceList,
714
+ assetsList: assetsList,
715
+ subSceneList: subSceneList, // 子场景列表
716
+ };
717
+ };
718
+ /**
719
+ * 回填多行语句的行范围。
720
+ *
721
+ * 预处理后,一条多行语句的完整内容都落在它的首行,紧跟其后的若干占位行
722
+ * 就是被折叠掉的续行。这里把占位行标记出来,并把首行语句的 endLine
723
+ * 推到最后一条续行,图形编辑器据此按整个行范围替换语句。
724
+ *
725
+ * @param sentenceList 解析后的语句列表
726
+ * @param processedLines 预处理后的行,与 sentenceList 一一对应
727
+ */
728
+ const markMultilineRanges = (sentenceList, processedLines) => {
729
+ let headIndex = -1; // 最近一条非占位语句的下标,即当前多行语句的首行
730
+ for (let index = 0; index < sentenceList.length; index++) {
731
+ if (!isLineBreakPlaceholder(processedLines[index])) {
732
+ headIndex = index;
733
+ continue;
734
+ }
735
+ sentenceList[index].isLineBreakHolder = true;
736
+ if (headIndex >= 0) {
737
+ sentenceList[headIndex].endLine = index;
738
+ }
739
+ }
740
+ };
741
+ const deduplicateAssets = (assetsList) => {
742
+ const seenAssets = new Set();
743
+ return assetsList.filter((asset) => {
744
+ if (!asset || typeof asset.url !== 'string' || asset.url === '') {
745
+ return false;
746
+ }
747
+ const assetKey = `${asset.type}:${asset.url}`;
748
+ if (seenAssets.has(assetKey)) {
749
+ return false;
750
+ }
751
+ seenAssets.add(assetKey);
752
+ return true;
753
+ });
754
+ };
755
+
756
+ function scss2cssinjsParser(scssString) {
757
+ const [classNameStyles, others] = parseCSS(scssString);
758
+ return {
759
+ classNameStyles,
760
+ others,
761
+ };
730
762
  }
731
- // export function sceneTextPreProcess(sceneText: string): string {
732
- // const lines = sceneText.replaceAll('\r', '').split('\n');
733
- // const processedLines: string[] = [];
734
- // let lastNonMultilineIndex = -1;
735
- // let isInMultilineSequence = false;
736
- // function isMultiline(line: string): boolean {
737
- // if (!line.startsWith(' ')) return false;
738
- // const trimmedLine = line.trimStart();
739
- // return trimmedLine.startsWith('|') || trimmedLine.startsWith('-');
740
- // }
741
- // for (let i = 0; i < lines.length; i++) {
742
- // const line = lines[i];
743
- // if (line.trim() === '') {
744
- // // Empty line handling
745
- // if (isInMultilineSequence) {
746
- // // Check if the next line is a multiline line
747
- // let isStillInMulti = false;
748
- // for (let j = i + 1; j < lines.length; j++) {
749
- // const lookForwardLine = lines[j] || '';
750
- // // 遇到正常语句了,直接中断
751
- // if (lookForwardLine.trim() !== '' && !isMultiline(lookForwardLine)) {
752
- // isStillInMulti = false;
753
- // break;
754
- // }
755
- // // 必须找到后面接的是参数,并且中间没有遇到任何正常语句才行
756
- // if (lookForwardLine.trim() !== '' && isMultiline(lookForwardLine)) {
757
- // isStillInMulti = true;
758
- // break;
759
- // }
760
- // }
761
- // if (isStillInMulti) {
762
- // // Still within a multiline sequence
763
- // processedLines.push(';_WEBGAL_LINE_BREAK_');
764
- // } else {
765
- // // End of multiline sequence
766
- // isInMultilineSequence = false;
767
- // processedLines.push(line);
768
- // }
769
- // } else {
770
- // // Preserve empty lines outside of multiline sequences
771
- // processedLines.push(line);
772
- // }
773
- // } else if (isMultiline(line)) {
774
- // // Multiline statement handling
775
- // if (lastNonMultilineIndex >= 0) {
776
- // // Concatenate to the previous non-multiline statement
777
- // const trimedLine = line.trimStart();
778
- // const addBlank = trimedLine.startsWith('-') ? ' ' : '';
779
- // processedLines[lastNonMultilineIndex] += addBlank + trimedLine;
780
- // }
781
- // // Add the special comment line
782
- // processedLines.push(';_WEBGAL_LINE_BREAK_' + line);
783
- // isInMultilineSequence = true;
784
- // } else {
785
- // // Non-multiline statement handling
786
- // processedLines.push(line);
787
- // lastNonMultilineIndex = processedLines.length - 1;
788
- // isInMultilineSequence = false;
789
- // }
790
- // }
791
- // return processedLines.join('\n');
792
- // }
763
+ /**
764
+ * GPT 4 写的,临时用,以后要重构!!!
765
+ * TODO:用人类智能重构,要是用着一直没问题,也不是不可以 trust AI
766
+ * @param css
767
+ */
768
+ function parseCSS(css) {
769
+ const result = {};
770
+ let specialRules = '';
771
+ let matches;
772
+ // 使用非贪婪匹配,尝试正确处理任意层次的嵌套
773
+ const classRegex = /\.([^{\s]+)\s*{((?:[^{}]*|{[^}]*})*)}/g;
774
+ const specialRegex = /(@[^{]+{\s*(?:[^{}]*{[^}]*}[^{}]*)+\s*})/g;
775
+ while ((matches = classRegex.exec(css)) !== null) {
776
+ const key = matches[1];
777
+ const value = matches[2].trim().replace(/\s*;\s*/g, ';\n');
778
+ result[key] = value;
779
+ }
780
+ while ((matches = specialRegex.exec(css)) !== null) {
781
+ specialRules += matches[1].trim() + '\n';
782
+ }
783
+ return [result, specialRules.trim()];
784
+ }
793
785
 
794
786
  class SceneParser {
795
787
  assetsPrefetcher;
@@ -836,7 +828,9 @@ var webgalParser = (function (exports) {
836
828
 
837
829
  exports.ADD_NEXT_ARG_LIST = ADD_NEXT_ARG_LIST;
838
830
  exports.SCRIPT_CONFIG = SCRIPT_CONFIG;
831
+ exports.WEBGAL_LINE_BREAK_MARK = WEBGAL_LINE_BREAK_MARK;
839
832
  exports.default = SceneParser;
833
+ exports.isLineBreakPlaceholder = isLineBreakPlaceholder;
840
834
  exports.sceneTextPreProcess = sceneTextPreProcess;
841
835
 
842
836
  Object.defineProperty(exports, '__esModule', { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webgal-parser",
3
- "version": "4.6.2",
3
+ "version": "4.6.3-beta.2",
4
4
  "description": "WebGAL script parser",
5
5
  "scripts": {
6
6
  "test": "vitest",
@@ -39,6 +39,7 @@ export const SCRIPT_CONFIG = [
39
39
  { scriptString: 'applyStyle', scriptType: commandType.applyStyle },
40
40
  { scriptString: 'wait', scriptType: commandType.wait },
41
41
  { scriptString: 'callSteam', scriptType: commandType.callSteam },
42
+ { scriptString: 'return', scriptType: commandType.return },
42
43
  ];
43
44
  export const ADD_NEXT_ARG_LIST = [
44
45
  commandType.bgm,
package/src/index.ts CHANGED
@@ -9,7 +9,11 @@ import { fileType } from './interface/assets';
9
9
  import { IAsset } from './interface/sceneInterface';
10
10
  import { sceneParser } from './sceneParser';
11
11
  import { IWebGALStyleObj, scss2cssinjsParser } from "./styleParser";
12
- import { sceneTextPreProcess } from "./sceneTextPreProcessor";
12
+ import {
13
+ isLineBreakPlaceholder,
14
+ sceneTextPreProcess,
15
+ WEBGAL_LINE_BREAK_MARK,
16
+ } from "./sceneTextPreProcessor";
13
17
 
14
18
  export default class SceneParser {
15
19
  private readonly SCRIPT_CONFIG_MAP: ConfigMap;
@@ -77,4 +81,4 @@ export default class SceneParser {
77
81
  }
78
82
 
79
83
  export { ADD_NEXT_ARG_LIST, SCRIPT_CONFIG };
80
- export { sceneTextPreProcess };
84
+ export { sceneTextPreProcess, isLineBreakPlaceholder, WEBGAL_LINE_BREAK_MARK };
@@ -6,6 +6,8 @@ export interface sceneEntry {
6
6
  sceneName: string; // 场景名称
7
7
  sceneUrl: string; // 场景url
8
8
  continueLine: number; // 继续原场景的行号
9
+ locals?: Record<string, any>; // 该帧的局部变量
10
+ writeReturnTo?: string; // 返回值写回该帧的哪个变量
9
11
  }
10
12
 
11
13
  /**
@@ -40,6 +40,7 @@ export enum commandType {
40
40
  applyStyle,
41
41
  wait,
42
42
  callSteam, // 调用Steam功能
43
+ return, // 从被调用的场景返回
43
44
  }
44
45
 
45
46
  /**
@@ -74,6 +75,15 @@ export interface ISentence {
74
75
  sentenceAssets: Array<IAsset>; // 语句携带的资源列表
75
76
  subScene: Array<string>; // 语句包含子场景列表
76
77
  inlineComment: string; // 行内注释
78
+ /**
79
+ * 本语句在原始场景文件中占据的行范围(0-based,含首尾)。
80
+ * 单行语句 startLine === endLine;多行语句 endLine 指向最后一条续行。
81
+ * 图形编辑器据此做「只替换被编辑语句所占的行」。
82
+ */
83
+ startLine: number;
84
+ endLine: number;
85
+ /** 本条是多行语句被折叠掉的续行占位,仅用于补齐行数,不应展示给用户 */
86
+ isLineBreakHolder: boolean;
77
87
  }
78
88
 
79
89
  /**
@@ -7,6 +7,10 @@ import {
7
7
  import { scriptParser } from './scriptParser/scriptParser';
8
8
  import { fileType } from './interface/assets';
9
9
  import { ConfigMap } from './config/scriptConfig';
10
+ import {
11
+ isLineBreakPlaceholder,
12
+ sceneTextPreProcess,
13
+ } from './sceneTextPreProcessor';
10
14
 
11
15
  /**
12
16
  * 场景解析器
@@ -28,7 +32,9 @@ export const sceneParser = (
28
32
  ADD_NEXT_ARG_LIST: commandType[],
29
33
  SCRIPT_CONFIG_MAP: ConfigMap,
30
34
  ): IScene => {
31
- const rawSentenceList = rawScene.replaceAll('\r', '').split('\n'); // 原始句子列表
35
+ // 预处理把多行语句折叠进它的首行,并用占位行补齐被折叠掉的行,
36
+ // 因此这里的行数与原始场景严格一致,「语句 index == 文件行号」始终成立。
37
+ const rawSentenceList = sceneTextPreProcess(rawScene).split('\n'); // 原始句子列表
32
38
 
33
39
  // 去分号留到后面去做了,现在注释要单独处理
34
40
  const rawSentenceListWithoutEmpty = rawSentenceList;
@@ -52,6 +58,8 @@ export const sceneParser = (
52
58
  },
53
59
  );
54
60
 
61
+ markMultilineRanges(sentenceList, rawSentenceList);
62
+
55
63
  // 开始资源的预加载
56
64
  assetsList = deduplicateAssets(assetsList);
57
65
  assetsPrefetcher(assetsList);
@@ -65,6 +73,33 @@ export const sceneParser = (
65
73
  };
66
74
  };
67
75
 
76
+ /**
77
+ * 回填多行语句的行范围。
78
+ *
79
+ * 预处理后,一条多行语句的完整内容都落在它的首行,紧跟其后的若干占位行
80
+ * 就是被折叠掉的续行。这里把占位行标记出来,并把首行语句的 endLine
81
+ * 推到最后一条续行,图形编辑器据此按整个行范围替换语句。
82
+ *
83
+ * @param sentenceList 解析后的语句列表
84
+ * @param processedLines 预处理后的行,与 sentenceList 一一对应
85
+ */
86
+ const markMultilineRanges = (
87
+ sentenceList: ISentence[],
88
+ processedLines: string[],
89
+ ) => {
90
+ let headIndex = -1; // 最近一条非占位语句的下标,即当前多行语句的首行
91
+ for (let index = 0; index < sentenceList.length; index++) {
92
+ if (!isLineBreakPlaceholder(processedLines[index])) {
93
+ headIndex = index;
94
+ continue;
95
+ }
96
+ sentenceList[index].isLineBreakHolder = true;
97
+ if (headIndex >= 0) {
98
+ sentenceList[headIndex].endLine = index;
99
+ }
100
+ }
101
+ };
102
+
68
103
  const deduplicateAssets = (assetsList: IAsset[]): IAsset[] => {
69
104
  const seenAssets = new Set<string>();
70
105
  return assetsList.filter((asset) => {