ide-assi 0.435.0 → 0.437.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -223561,6 +223561,13 @@ const defaultKeymap = /*@__PURE__*/[
223561
223561
  { key: "Alt-A", run: toggleBlockComment },
223562
223562
  { key: "Ctrl-m", mac: "Shift-Alt-m", run: toggleTabFocusMode },
223563
223563
  ].concat(standardKeymap);
223564
+ /**
223565
+ A binding that binds Tab to [`indentMore`](https://codemirror.net/6/docs/ref/#commands.indentMore) and
223566
+ Shift-Tab to [`indentLess`](https://codemirror.net/6/docs/ref/#commands.indentLess).
223567
+ Please see the [Tab example](../../examples/tab/) before using
223568
+ this.
223569
+ */
223570
+ const indentWithTab = { key: "Tab", run: indentMore, shift: indentLess };
223564
223571
 
223565
223572
  const basicNormalize = typeof String.prototype.normalize == "function"
223566
223573
  ? x => x.normalize("NFKD") : x => x;
@@ -234855,8 +234862,71 @@ const tobeDiffDecorations = StateField.define({
234855
234862
  });
234856
234863
 
234857
234864
 
234858
- class IdeDiff extends HTMLElement {
234865
+ // IdeDiff 클래스 외부 (파일 상단 or 하단)에 추가
234866
+ class MergeButtonWidget extends WidgetType {
234867
+ // ⭐️ diffRange는 변경이 발생할 대상 에디터의 범위입니다.
234868
+ // ⭐️ isAsisButton: ASIS 에디터 쪽에 붙는 버튼인가? (true면 ASIS -> TOBE 적용)
234869
+ // ⭐️ isAsisButton이 false면 TOBE 에디터 쪽에 붙는 버튼 (TOBE -> ASIS 되돌리기)
234870
+ constructor(isAsisButton, textToApply, targetEditorView, diffRange, hostComponent) {
234871
+ super();
234872
+ this.isAsisButton = isAsisButton; // 이 버튼이 ASIS 에디터에 붙는 버튼인가 (true) TOBE 에디터에 붙는 버튼인가 (false)
234873
+ this.textToApply = textToApply; // 적용할 텍스트
234874
+ this.targetEditorView = targetEditorView; // 텍스트를 적용할 에디터 뷰 (상대편 에디터)
234875
+ this.diffRange = diffRange; // 대상 에디터에서 변경이 일어날 정확한 from/to 오프셋
234876
+ this.hostComponent = hostComponent; // IdeDiff 인스턴스 참조
234877
+ }
234878
+
234879
+ // 위젯이 차지할 공간을 텍스트 줄에 할당할지 여부. false로 설정하여 버튼이 줄 사이에 끼어들지 않도록 함.
234880
+ eq(other) { return false; }
234881
+
234882
+ // 위젯의 DOM 요소를 생성합니다.
234883
+ toDOM(view) {
234884
+ const button = document.createElement("button");
234885
+ // 버튼 클래스 및 텍스트는 버튼의 위치(ASIS/TOBE)에 따라 결정됩니다.
234886
+ button.className = `cm-merge-button ${this.isAsisButton ? 'accept' : 'revert'}`;
234887
+ button.textContent = this.isAsisButton ? "→ 적용" : "← 되돌리기"; // ASIS 버튼: TOBE로 적용, TOBE 버튼: ASIS로 되돌리기
234888
+
234889
+ // 클릭 이벤트 핸들러
234890
+ button.addEventListener("click", () => {
234891
+ console.log(`버튼 클릭: ${this.isAsisButton ? 'ASIS -> TOBE' : 'TOBE -> ASIS'}`, this.textToApply);
234892
+ console.log("대상 에디터:", this.targetEditorView === this.hostComponent.#asisEditorView ? "ASIS" : "TOBE");
234893
+ console.log("대상 범위:", this.diffRange);
234894
+
234895
+ this.applyChanges(this.textToApply, this.targetEditorView, this.diffRange);
234896
+ });
234897
+
234898
+ const container = document.createElement("div");
234899
+ container.className = "cm-merge-button-container";
234900
+ container.appendChild(button);
234901
+ return container;
234902
+ }
234903
+
234904
+ // 실제 변경 적용 로직
234905
+ applyChanges(text, editorView, range) {
234906
+ if (!editorView || !range) {
234907
+ console.error("Target editor view or range is undefined.", editorView, range);
234908
+ return;
234909
+ }
234910
+
234911
+ editorView.dispatch({
234912
+ changes: {
234913
+ from: range.from,
234914
+ to: range.to,
234915
+ insert: text
234916
+ }
234917
+ });
234859
234918
 
234919
+ // 변경 후 Diff를 다시 계산하고 데코레이션을 업데이트합니다.
234920
+ // requestAnimationFrame으로 감싸서 다음 렌더링 사이클에서 Diff 계산이 이루어지도록 합니다.
234921
+ // 이렇게 하면 UI 블로킹을 줄일 수 있습니다.
234922
+ requestAnimationFrame(() => {
234923
+ this.hostComponent.recalculateDiff();
234924
+ });
234925
+ }
234926
+ }
234927
+
234928
+
234929
+ class IdeDiff extends HTMLElement {
234860
234930
  #asisEditorView;
234861
234931
  #tobeEditorView;
234862
234932
  #asisEditorEl;
@@ -234880,6 +234950,62 @@ class IdeDiff extends HTMLElement {
234880
234950
  /* ninegrid CSS 및 필요한 기본 스타일 */
234881
234951
  @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideDiff.css";
234882
234952
  ${ninegrid.getCustomPath(this, "ideDiff.css")}
234953
+
234954
+ /* --- 추가된 CSS 규칙 (버튼 및 선택 관련) --- */
234955
+ .cm-line {
234956
+ position: relative;
234957
+ }
234958
+ .cm-merge-button-container {
234959
+ position: absolute;
234960
+ right: 5px;
234961
+ top: 50%;
234962
+ transform: translateY(-50%);
234963
+ z-index: 10;
234964
+ display: flex;
234965
+ gap: 5px;
234966
+ }
234967
+ .cm-merge-button {
234968
+ background-color: #f0f0f0;
234969
+ border: 1px solid #ccc;
234970
+ border-radius: 3px;
234971
+ padding: 2px 6px;
234972
+ cursor: pointer;
234973
+ font-size: 0.8em;
234974
+ line-height: 1;
234975
+ white-space: nowrap;
234976
+ opacity: 0.7;
234977
+ transition: opacity 0.2s ease-in-out;
234978
+ }
234979
+ .cm-merge-button:hover {
234980
+ opacity: 1;
234981
+ background-color: #e0e0e0;
234982
+ }
234983
+ .cm-merge-button.accept {
234984
+ background-color: #4CAF50;
234985
+ color: white;
234986
+ border-color: #4CAF50;
234987
+ }
234988
+ .cm-merge-button.revert {
234989
+ background-color: #f44336;
234990
+ color: white;
234991
+ border-color: #f44336;
234992
+ }
234993
+ /* Diff 데코레이션 CSS (ideDiff.css에 없으면 여기에 추가) */
234994
+ .cm-inserted-line-bg { background-color: #e6ffed; border-left: 3px solid #66bb6a; }
234995
+ .cm-deleted-line-bg { background-color: #ffebe9; border-left: 3px solid #ef5350; }
234996
+
234997
+ /* CodeMirror 선택 스타일 (ideDiff.css에 없으면 여기에 추가) */
234998
+ .cm-selectionBackground {
234999
+ background-color: #d7d4f9 !important;
235000
+ }
235001
+ .cm-editor ::selection {
235002
+ background-color: #d7d4f9 !important;
235003
+ color: inherit !important;
235004
+ }
235005
+ .cm-editor::-moz-selection {
235006
+ background-color: #d7d4f9 !important;
235007
+ color: inherit !important;
235008
+ }
234883
235009
  </style>
234884
235010
 
234885
235011
  <div class="wrapper">
@@ -234925,7 +235051,7 @@ class IdeDiff extends HTMLElement {
234925
235051
  drawSelection(),
234926
235052
  dropCursor(),
234927
235053
  EditorState.allowMultipleSelections.of(true),
234928
- indentOnInput(), // ⭐️ 함수 호출
235054
+ indentOnInput(),
234929
235055
  bracketMatching(),
234930
235056
  highlightActiveLine(),
234931
235057
  highlightActiveLineGutter(),
@@ -234936,11 +235062,11 @@ class IdeDiff extends HTMLElement {
234936
235062
  ...historyKeymap,
234937
235063
  ...lintKeymap,
234938
235064
  ...completionKeymap,
234939
- //indentWithTab(), // ⭐️ 함수 호출
234940
- //selectAll() // ⭐️ 함수 호출
235065
+ indentWithTab, // ⭐️ 함수가 아닌 확장 자체를 전달
235066
+ selectAll // ⭐️ 함수가 아닌 확장 자체를 전달
234941
235067
  ]),
234942
- syntaxHighlighting(defaultHighlightStyle), // { fallback: true } 제거 확인
234943
- autocompletion(), // ⭐️ 함수 호출
235068
+ syntaxHighlighting(defaultHighlightStyle),
235069
+ autocompletion(),
234944
235070
  ];
234945
235071
 
234946
235072
  // ASIS 에디터 뷰 초기화
@@ -234949,9 +235075,9 @@ class IdeDiff extends HTMLElement {
234949
235075
  doc: '',
234950
235076
  extensions: [
234951
235077
  basicExtensions,
234952
- this.#languageCompartment.of(javascript()), // 초기 언어 설정
234953
- EditorState.readOnly.of(true),
234954
- asisDiffDecorations, // ASIS Diff 데코레이션 필드
235078
+ this.#languageCompartment.of(javascript()),
235079
+ EditorState.readOnly.of(true), // ASIS는 읽기 전용 유지
235080
+ asisDiffDecorations,
234955
235081
  EditorView.updateListener.of((update) => {
234956
235082
  if (update.view.contentDOM.firstChild && !update.view._initialAsisContentLoaded) {
234957
235083
  update.view._initialAsisContentLoaded = true;
@@ -234969,9 +235095,9 @@ class IdeDiff extends HTMLElement {
234969
235095
  doc: '',
234970
235096
  extensions: [
234971
235097
  basicExtensions,
234972
- this.#languageCompartment.of(javascript()), // 초기 언어 설정
234973
- EditorState.readOnly.of(true),
234974
- tobeDiffDecorations, // TOBE Diff 데코레이션 필드
235098
+ this.#languageCompartment.of(javascript()),
235099
+ // EditorState.readOnly.of(true), // TOBE는 편집 가능하도록 주석 처리 유지
235100
+ tobeDiffDecorations,
234975
235101
  EditorView.updateListener.of((update) => {
234976
235102
  if (update.view.contentDOM.firstChild && !update.view._initialTobeContentLoaded) {
234977
235103
  update.view._initialTobeContentLoaded = true;
@@ -235024,72 +235150,66 @@ class IdeDiff extends HTMLElement {
235024
235150
  #applyDiffDecorations = (asisSrc, tobeSrc) => {
235025
235151
  const dmp = new diffMatchPatchExports.diff_match_patch();
235026
235152
 
235027
- // dmp의 기본 cleanupThreshold를 줄여서 더 세밀한 차이를 감지하도록 시도
235028
- // 기본값은 0.5인데, 0으로 하면 더 많은 변화를 감지할 수 있습니다.
235029
- // 하지만 너무 낮추면 노이즈가 많아질 수 있습니다.
235030
- // dmp.Diff_EditCost = 4; // 편집 비용 조정, 기본 4
235031
- // dmp.Diff_DualThreshold = 0.5; // 불명확한 Diff에 대한 임계값. 기본 0.5
235032
- // dmp.Diff_Timeout = 1.0; // 타임아웃, 기본 1.0
235033
-
235034
- // 텍스트를 줄 단위로 변환하여 Diff 수행
235035
235153
  const a = dmp.diff_linesToChars_(asisSrc, tobeSrc);
235036
235154
  const lineText1 = a.chars1;
235037
235155
  const lineText2 = a.chars2;
235038
235156
  const lineArray = a.lineArray;
235039
235157
 
235040
- // dmp.diff_main(text1, text2, checklines, deadline)
235041
- // checklines: true (기본값)는 줄 단위 최적화, false는 문자 단위로.
235042
- // 여기서는 diff_linesToChars_를 사용했으므로 true가 적절.
235043
235158
  const diffs = dmp.diff_main(lineText1, lineText2, true);
235044
-
235045
- // cleanupEfficiency를 먼저 적용하여 효율적인 Diff를 만듭니다.
235046
- // 이는 작은 변경사항이 인접해 있을 때 하나의 큰 변경으로 합치는 경향이 있습니다.
235047
- // dmp.diff_cleanupEfficiency(diffs); // 오히려 이동을 놓칠 수 있음
235048
-
235049
- // 그 다음 의미론적 정리를 적용합니다.
235050
235159
  dmp.diff_cleanupSemantic(diffs);
235051
-
235052
- // 다시 문자열로 변환
235053
235160
  dmp.diff_charsToLines_(diffs, lineArray);
235054
235161
 
235055
- console.log(diffs);
235162
+ console.log("Calculated Diffs:", diffs); // Diff 결과 확인용
235056
235163
 
235057
235164
  const asisLineBuilder = new RangeSetBuilder();
235058
235165
  const tobeLineBuilder = new RangeSetBuilder();
235059
235166
 
235060
- let asisCursor = 0;
235061
- let tobeCursor = 0;
235167
+ let asisCursor = 0; // ASIS 텍스트에서의 현재 오프셋
235168
+ let tobeCursor = 0; // TOBE 텍스트에서의 현재 오프셋
235062
235169
 
235063
- // 변경된 줄에 대한 새로운 데코레이션 클래스를 추가할 수 있습니다.
235064
235170
  const insertedLineDeco = Decoration.line({ class: "cm-inserted-line-bg" });
235065
235171
  const deletedLineDeco = Decoration.line({ class: "cm-deleted-line-bg" });
235066
- // const changedLineDeco = Decoration.line({ class: "cm-changed-line-bg" }); // 필요하다면
235172
+
235173
+ const currentInstance = this;
235067
235174
 
235068
235175
  for (const [op, text] of diffs) {
235069
235176
  const len = text.length;
235070
235177
 
235178
+ // 각 diff op 이전에 현재 커서 위치를 저장
235179
+ const asisRangeStart = asisCursor;
235180
+ const tobeRangeStart = tobeCursor;
235181
+
235071
235182
  switch (op) {
235072
- case diffMatchPatchExports.diff_match_patch.DIFF_INSERT:
235073
- console.log(diffMatchPatchExports.diff_match_patch.DIFF_INSERT, text);
235183
+ case diffMatchPatchExports.diff_match_patch.DIFF_INSERT: // TOBE에 추가된 내용 (ASIS에는 없음)
235184
+ console.log("DIFF_INSERT (TOBE added):", JSON.stringify(text));
235074
235185
  const tobeLines = text.split('\n');
235075
-
235076
235186
  for (let i = 0; i < tobeLines.length; i++) {
235077
- // 빈 줄이 아니고, 마지막 줄이면서 줄바꿈이 없는 경우가 아니면 데코레이션
235078
- /**
235079
- if (!(i === tobeLines.length - 1 && tobeLines[i] === '' && !text.endsWith('\n'))) {
235080
- tobeLineBuilder.add(tobeCursor, tobeCursor, insertedLineDeco);
235081
- }
235082
- tobeCursor += tobeLines[i].length + (i < tobeLines.length - 1 ? 1 : 0);
235083
- */
235084
235187
  if (!(i === tobeLines.length - 1 && tobeLines[i] === '' && text.endsWith('\n'))) {
235085
235188
  tobeLineBuilder.add(tobeCursor, tobeCursor, insertedLineDeco);
235086
235189
  }
235087
235190
  tobeCursor += tobeLines[i].length + (i < tobeLines.length - 1 ? 1 : 0);
235088
235191
  }
235192
+
235193
+ // ⭐️ TOBE 에디터에 버튼 추가: TOBE의 추가 내용을 ASIS로 '되돌리기' (ASIS에서 삭제)
235194
+ // 즉, TOBE에서 삽입된 내용을 ASIS에서 '없애는' 작업
235195
+ tobeLineBuilder.add(
235196
+ tobeRangeStart, // TOBE에서의 해당 Diff 청크 시작 오프셋
235197
+ tobeRangeStart,
235198
+ Decoration.widget({
235199
+ widget: new MergeButtonWidget(
235200
+ false, // 이 버튼은 TOBE 에디터에 붙는 버튼 (되돌리기)
235201
+ "", // 텍스트를 ""로 삽입하면 삭제 효과 (ASIS에서 없앨 내용)
235202
+ currentInstance.#asisEditorView, // 대상 에디터는 ASIS
235203
+ { from: asisRangeStart, to: asisRangeStart + 0 }, // ASIS에서의 대상 범위 (삽입 지점)
235204
+ currentInstance
235205
+ ),
235206
+ side: 1 // 텍스트 뒤에 삽입
235207
+ })
235208
+ );
235089
235209
  break;
235090
235210
 
235091
- case diffMatchPatchExports.diff_match_patch.DIFF_DELETE:
235092
- console.log(diffMatchPatchExports.diff_match_patch.DIFF_DELETE, text);
235211
+ case diffMatchPatchExports.diff_match_patch.DIFF_DELETE: // ASIS에서 삭제된 내용 (TOBE에는 없음)
235212
+ console.log("DIFF_DELETE (ASIS deleted):", JSON.stringify(text));
235093
235213
  const asisLines = text.split('\n');
235094
235214
  for (let i = 0; i < asisLines.length; i++) {
235095
235215
  if (!(i === asisLines.length - 1 && asisLines[i] === '' && text.endsWith('\n'))) {
@@ -235097,16 +235217,29 @@ class IdeDiff extends HTMLElement {
235097
235217
  }
235098
235218
  asisCursor += asisLines[i].length + (i < asisLines.length - 1 ? 1 : 0);
235099
235219
  }
235220
+
235221
+ // ⭐️ ASIS 에디터에 버튼 추가: ASIS의 삭제 내용을 TOBE로 '적용' (TOBE에 삽입)
235222
+ // 즉, ASIS에서 삭제된 내용을 TOBE에 '넣는' 작업
235223
+ asisLineBuilder.add(
235224
+ asisRangeStart, // ASIS에서의 해당 Diff 청크 시작 오프셋
235225
+ asisRangeStart,
235226
+ Decoration.widget({
235227
+ widget: new MergeButtonWidget(
235228
+ true, // 이 버튼은 ASIS 에디터에 붙는 버튼 (적용)
235229
+ text, // ASIS에서 삭제된 내용을 TOBE에 삽입
235230
+ currentInstance.#tobeEditorView, // 대상 에디터는 TOBE
235231
+ { from: tobeRangeStart, to: tobeRangeStart + 0 }, // TOBE에서의 대상 범위 (삽입 지점)
235232
+ currentInstance
235233
+ ),
235234
+ side: 1 // 텍스트 뒤에 삽입
235235
+ })
235236
+ );
235100
235237
  break;
235101
235238
 
235102
- case diffMatchPatchExports.diff_match_patch.DIFF_EQUAL:
235239
+ case diffMatchPatchExports.diff_match_patch.DIFF_EQUAL: // 동일한 내용
235103
235240
  asisCursor += len;
235104
235241
  tobeCursor += len;
235105
235242
  break;
235106
-
235107
- default:
235108
- console.log(op);
235109
- break;
235110
235243
  }
235111
235244
  }
235112
235245
 
@@ -235116,6 +235249,20 @@ class IdeDiff extends HTMLElement {
235116
235249
  };
235117
235250
  };
235118
235251
 
235252
+ recalculateDiff = () => {
235253
+ const asisDoc = this.#asisEditorView.state.doc.toString();
235254
+ const tobeDoc = this.#tobeEditorView.state.doc.toString();
235255
+
235256
+ const { asisDecorations, tobeDecorations } = this.#applyDiffDecorations(asisDoc, tobeDoc);
235257
+
235258
+ this.#asisEditorView.dispatch({
235259
+ effects: [setAsisDecorationsEffect.of(asisDecorations)]
235260
+ });
235261
+ this.#tobeEditorView.dispatch({
235262
+ effects: [setTobeDecorationsEffect.of(tobeDecorations)]
235263
+ });
235264
+ };
235265
+
235119
235266
  initialize = (src1, src2, language = 'javascript') => {
235120
235267
  if (!this.#asisEditorView || !this.#tobeEditorView) {
235121
235268
  console.warn('CodeMirror Editors not initialized yet.');