ide-assi 0.438.0 → 0.440.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.
@@ -24,6 +24,10 @@ import { diff_match_patch } from 'diff-match-patch';
24
24
  const setAsisDecorationsEffect = StateEffect.define();
25
25
  const setTobeDecorationsEffect = StateEffect.define();
26
26
 
27
+ // --- 버튼 데코레이션을 위한 새로운 StateEffect 정의 ---
28
+ const setAsisButtonDecorationsEffect = StateEffect.define();
29
+ const setTobeButtonDecorationsEffect = StateEffect.define();
30
+
27
31
  // --- Diff 데코레이션을 위한 StateField 정의 ---
28
32
  const asisDiffDecorations = StateField.define({
29
33
  create() {
@@ -57,32 +61,58 @@ const tobeDiffDecorations = StateField.define({
57
61
  provide: f => EditorView.decorations.from(f)
58
62
  });
59
63
 
64
+ // --- 버튼 데코레이션을 위한 새로운 StateField 정의 ---
65
+ const asisButtonDecorations = StateField.define({
66
+ create() {
67
+ return Decoration.none;
68
+ },
69
+ update(decorations, tr) {
70
+ decorations = decorations.map(tr.changes);
71
+ for (let effect of tr.effects) {
72
+ if (effect.is(setAsisButtonDecorationsEffect)) {
73
+ return effect.value;
74
+ }
75
+ }
76
+ return decorations;
77
+ },
78
+ provide: f => EditorView.decorations.from(f)
79
+ });
80
+
81
+ const tobeButtonDecorations = StateField.define({
82
+ create() {
83
+ return Decoration.none;
84
+ },
85
+ update(decorations, tr) {
86
+ decorations = decorations.map(tr.changes);
87
+ for (let effect of tr.effects) {
88
+ if (effect.is(setTobeButtonDecorationsEffect)) {
89
+ return effect.value;
90
+ }
91
+ }
92
+ return decorations;
93
+ },
94
+ provide: f => EditorView.decorations.from(f)
95
+ });
96
+
60
97
 
61
98
  // IdeDiff 클래스 외부 (파일 상단 or 하단)에 추가
62
99
  class MergeButtonWidget extends WidgetType {
63
- // ⭐️ diffRange는 변경이 발생할 대상 에디터의 범위입니다.
64
- // ⭐️ isAsisButton: ASIS 에디터 쪽에 붙는 버튼인가? (true면 ASIS -> TOBE 적용)
65
- // ⭐️ isAsisButton이 false면 TOBE 에디터 쪽에 붙는 버튼 (TOBE -> ASIS 되돌리기)
66
100
  constructor(isAsisButton, textToApply, targetEditorView, diffRange, hostComponent) {
67
101
  super();
68
- this.isAsisButton = isAsisButton; // 이 버튼이 ASIS 에디터에 붙는 버튼인가 (true) TOBE 에디터에 붙는 버튼인가 (false)
69
- this.textToApply = textToApply; // 적용할 텍스트
70
- this.targetEditorView = targetEditorView; // 텍스트를 적용할 에디터 뷰 (상대편 에디터)
71
- this.diffRange = diffRange; // 대상 에디터에서 변경이 일어날 정확한 from/to 오프셋
72
- this.hostComponent = hostComponent; // IdeDiff 인스턴스 참조
102
+ this.isAsisButton = isAsisButton;
103
+ this.textToApply = textToApply;
104
+ this.targetEditorView = targetEditorView;
105
+ this.diffRange = diffRange;
106
+ this.hostComponent = hostComponent;
73
107
  }
74
108
 
75
- // 위젯이 차지할 공간을 텍스트 줄에 할당할지 여부. false로 설정하여 버튼이 줄 사이에 끼어들지 않도록 함.
76
109
  eq(other) { return false; }
77
110
 
78
- // 위젯의 DOM 요소를 생성합니다.
79
111
  toDOM(view) {
80
112
  const button = document.createElement("button");
81
- // 버튼 클래스 및 텍스트는 버튼의 위치(ASIS/TOBE)에 따라 결정됩니다.
82
113
  button.className = `cm-merge-button ${this.isAsisButton ? 'accept' : 'revert'}`;
83
- button.textContent = this.isAsisButton ? "→ 적용" : "← 되돌리기"; // ASIS 버튼: TOBE로 적용, TOBE 버튼: ASIS로 되돌리기
114
+ button.textContent = this.isAsisButton ? "→ 적용" : "← 되돌리기";
84
115
 
85
- // 클릭 이벤트 핸들러
86
116
  button.addEventListener("click", () => {
87
117
  console.log(`버튼 클릭: ${this.isAsisButton ? 'ASIS -> TOBE' : 'TOBE -> ASIS'}`, this.textToApply);
88
118
  console.log("대상 에디터:", this.targetEditorView === this.hostComponent.asisEditorView ? "ASIS" : "TOBE");
@@ -97,13 +127,22 @@ class MergeButtonWidget extends WidgetType {
97
127
  return container;
98
128
  }
99
129
 
100
- // 실제 변경 적용 로직
101
130
  applyChanges(text, editorView, range) {
102
131
  if (!editorView || !range) {
103
132
  console.error("Target editor view or range is undefined.", editorView, range);
104
133
  return;
105
134
  }
106
135
 
136
+ // 특정 줄의 시작 오프셋과 끝 오프셋을 정확히 계산하여 적용해야 합니다.
137
+ // 현재 로직은 단순 치환이므로 Diff 유형에 따라 복잡해질 수 있습니다.
138
+ // 예를 들어, TOBE에 삽입된 내용을 ASIS에서 되돌릴 경우, ASIS에서 해당 내용 길이만큼의 공백을 지워야 합니다.
139
+ // ASIS에서 삭제된 내용을 TOBE에 적용할 경우, TOBE의 해당 위치에 내용을 삽입해야 합니다.
140
+
141
+ // CodeMirror의 Doc에서 특정 범위를 가져오는 것은 `state.sliceDoc(from, to)`
142
+ // `dmp.diff_main`의 결과는 줄 단위로 매핑된 문자열이므로,
143
+ // 실제 오프셋 계산 및 적용 로직은 더 정교해야 합니다.
144
+ // 지금은 임시로 range.from, range.to를 사용합니다.
145
+
107
146
  editorView.dispatch({
108
147
  changes: {
109
148
  from: range.from,
@@ -112,9 +151,6 @@ class MergeButtonWidget extends WidgetType {
112
151
  }
113
152
  });
114
153
 
115
- // 변경 후 Diff를 다시 계산하고 데코레이션을 업데이트합니다.
116
- // requestAnimationFrame으로 감싸서 다음 렌더링 사이클에서 Diff 계산이 이루어지도록 합니다.
117
- // 이렇게 하면 UI 블로킹을 줄일 수 있습니다.
118
154
  requestAnimationFrame(() => {
119
155
  this.hostComponent.recalculateDiff();
120
156
  });
@@ -134,7 +170,6 @@ export class IdeDiff extends HTMLElement {
134
170
  _asisScrollHandler = null;
135
171
  _tobeScrollHandler = null;
136
172
 
137
-
138
173
  get asisEditorView() {
139
174
  return this.#asisEditorView;
140
175
  };
@@ -144,70 +179,11 @@ export class IdeDiff extends HTMLElement {
144
179
  this.attachShadow({ mode: 'open' });
145
180
  }
146
181
 
147
-
148
-
149
182
  connectedCallback() {
150
183
  this.shadowRoot.innerHTML = `
151
184
  <style>
152
- /* ninegrid CSS 및 필요한 기본 스타일 */
153
185
  @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideDiff.css";
154
186
  ${ninegrid.getCustomPath(this, "ideDiff.css")}
155
-
156
- /* --- 추가된 CSS 규칙 (버튼 및 선택 관련) --- */
157
- .cm-line {
158
- position: relative;
159
- }
160
- .cm-merge-button-container {
161
- position: absolute;
162
- right: 5px;
163
- top: 50%;
164
- transform: translateY(-50%);
165
- z-index: 10;
166
- display: flex;
167
- gap: 5px;
168
- }
169
- .cm-merge-button {
170
- background-color: #f0f0f0;
171
- border: 1px solid #ccc;
172
- border-radius: 3px;
173
- padding: 2px 6px;
174
- cursor: pointer;
175
- font-size: 0.8em;
176
- line-height: 1;
177
- white-space: nowrap;
178
- opacity: 0.7;
179
- transition: opacity 0.2s ease-in-out;
180
- }
181
- .cm-merge-button:hover {
182
- opacity: 1;
183
- background-color: #e0e0e0;
184
- }
185
- .cm-merge-button.accept {
186
- background-color: #4CAF50;
187
- color: white;
188
- border-color: #4CAF50;
189
- }
190
- .cm-merge-button.revert {
191
- background-color: #f44336;
192
- color: white;
193
- border-color: #f44336;
194
- }
195
- /* Diff 데코레이션 CSS (ideDiff.css에 없으면 여기에 추가) */
196
- .cm-inserted-line-bg { background-color: #e6ffed; border-left: 3px solid #66bb6a; }
197
- .cm-deleted-line-bg { background-color: #ffebe9; border-left: 3px solid #ef5350; }
198
-
199
- /* CodeMirror 선택 스타일 (ideDiff.css에 없으면 여기에 추가) */
200
- .cm-selectionBackground {
201
- background-color: #d7d4f9 !important;
202
- }
203
- .cm-editor ::selection {
204
- background-color: #d7d4f9 !important;
205
- color: inherit !important;
206
- }
207
- .cm-editor::-moz-selection {
208
- background-color: #d7d4f9 !important;
209
- color: inherit !important;
210
- }
211
187
  </style>
212
188
 
213
189
  <div class="wrapper">
@@ -264,22 +240,22 @@ export class IdeDiff extends HTMLElement {
264
240
  ...historyKeymap,
265
241
  ...lintKeymap,
266
242
  ...completionKeymap,
267
- indentWithTab, // ⭐️ 함수가 아닌 확장 자체를 전달
268
- selectAll // ⭐️ 함수가 아닌 확장 자체를 전달
243
+ indentWithTab,
244
+ selectAll
269
245
  ]),
270
246
  syntaxHighlighting(defaultHighlightStyle),
271
247
  autocompletion(),
272
248
  ];
273
249
 
274
- // ASIS 에디터 뷰 초기화
275
250
  this.#asisEditorView = new EditorView({
276
251
  state: EditorState.create({
277
252
  doc: '',
278
253
  extensions: [
279
254
  basicExtensions,
280
255
  this.#languageCompartment.of(javascript()),
281
- EditorState.readOnly.of(true), // ASIS는 읽기 전용 유지
256
+ EditorState.readOnly.of(true),
282
257
  asisDiffDecorations,
258
+ asisButtonDecorations, // ⭐️ 새로운 StateField 추가
283
259
  EditorView.updateListener.of((update) => {
284
260
  if (update.view.contentDOM.firstChild && !update.view._initialAsisContentLoaded) {
285
261
  update.view._initialAsisContentLoaded = true;
@@ -291,15 +267,14 @@ export class IdeDiff extends HTMLElement {
291
267
  parent: this.#asisEditorEl
292
268
  });
293
269
 
294
- // TOBE 에디터 뷰 초기화
295
270
  this.#tobeEditorView = new EditorView({
296
271
  state: EditorState.create({
297
272
  doc: '',
298
273
  extensions: [
299
274
  basicExtensions,
300
275
  this.#languageCompartment.of(javascript()),
301
- // EditorState.readOnly.of(true), // TOBE는 편집 가능하도록 주석 처리 유지
302
276
  tobeDiffDecorations,
277
+ tobeButtonDecorations, // ⭐️ 새로운 StateField 추가
303
278
  EditorView.updateListener.of((update) => {
304
279
  if (update.view.contentDOM.firstChild && !update.view._initialTobeContentLoaded) {
305
280
  update.view._initialTobeContentLoaded = true;
@@ -361,13 +336,15 @@ export class IdeDiff extends HTMLElement {
361
336
  dmp.diff_cleanupSemantic(diffs);
362
337
  dmp.diff_charsToLines_(diffs, lineArray);
363
338
 
364
- console.log("Calculated Diffs:", diffs); // Diff 결과 확인용
339
+ console.log("Calculated Diffs:", diffs);
365
340
 
366
341
  const asisLineBuilder = new RangeSetBuilder();
367
342
  const tobeLineBuilder = new RangeSetBuilder();
343
+ const asisButtonBuilder = new RangeSetBuilder(); // ⭐️ 새로운 빌더
344
+ const tobeButtonBuilder = new RangeSetBuilder(); // ⭐️ 새로운 빌더
368
345
 
369
- let asisCursor = 0; // ASIS 텍스트에서의 현재 오프셋
370
- let tobeCursor = 0; // TOBE 텍스트에서의 현재 오프셋
346
+ let asisCursor = 0;
347
+ let tobeCursor = 0;
371
348
 
372
349
  const insertedLineDeco = Decoration.line({ class: "cm-inserted-line-bg" });
373
350
  const deletedLineDeco = Decoration.line({ class: "cm-deleted-line-bg" });
@@ -377,7 +354,6 @@ export class IdeDiff extends HTMLElement {
377
354
  for (const [op, text] of diffs) {
378
355
  const len = text.length;
379
356
 
380
- // 각 diff op 이전에 현재 커서 위치를 저장
381
357
  const asisRangeStart = asisCursor;
382
358
  const tobeRangeStart = tobeCursor;
383
359
 
@@ -392,20 +368,22 @@ export class IdeDiff extends HTMLElement {
392
368
  tobeCursor += tobeLines[i].length + (i < tobeLines.length - 1 ? 1 : 0);
393
369
  }
394
370
 
395
- // ⭐️ TOBE 에디터에 버튼 추가: TOBE의 추가 내용을 ASIS로 '되돌리기' (ASIS에서 삭제)
396
- // 즉, TOBE에서 삽입된 내용을 ASIS에서 '없애는' 작업
397
- tobeLineBuilder.add(
371
+ // ⭐️ TOBE 에디터에 버튼 추가: TOBE의 추가 내용을 ASIS로 '되돌리기' (ASIS에서 제거)
372
+ // TOBE 해당 Diff가 끝나는 지점에 버튼을 붙이는 것이 자연스럽습니다.
373
+ // tobeRangeStart는 Diff 청크의 시작 오프셋입니다.
374
+ // 버튼을 Line Decoration과 같은 위치에 추가하되, 별도의 builder 사용
375
+ tobeButtonBuilder.add(
398
376
  tobeRangeStart, // TOBE에서의 해당 Diff 청크 시작 오프셋
399
377
  tobeRangeStart,
400
378
  Decoration.widget({
401
379
  widget: new MergeButtonWidget(
402
380
  false, // 이 버튼은 TOBE 에디터에 붙는 버튼 (되돌리기)
403
- "", // 텍스트를 ""로 삽입하면 삭제 효과 (ASIS에서 없앨 내용)
381
+ "", // ASIS에서 해당 내용을 제거하므로 삽입할 텍스트는 없음 (즉, 삭제)
404
382
  currentInstance.#asisEditorView, // 대상 에디터는 ASIS
405
- { from: asisRangeStart, to: asisRangeStart + 0 }, // ASIS에서의 대상 범위 (삽입 지점)
383
+ { from: asisRangeStart, to: asisRangeStart + text.length }, // ASIS에서 '삭제될' 범위
406
384
  currentInstance
407
385
  ),
408
- side: 1 // 텍스트 뒤에 삽입
386
+ side: 1 // 텍스트 뒤에 삽입 (이전에 `side: -1`로 시도했다면 `-1`이 우선순위가 더 높습니다.)
409
387
  })
410
388
  );
411
389
  break;
@@ -420,9 +398,8 @@ export class IdeDiff extends HTMLElement {
420
398
  asisCursor += asisLines[i].length + (i < asisLines.length - 1 ? 1 : 0);
421
399
  }
422
400
 
423
- // ⭐️ ASIS 에디터에 버튼 추가: ASIS의 삭제 내용을 TOBE로 '적용' (TOBE에 삽입)
424
- // 즉, ASIS에서 삭제된 내용을 TOBE에 '넣는' 작업
425
- asisLineBuilder.add(
401
+ // ⭐️ ASIS 에디터에 버튼 추가: ASIS의 삭제 내용을 TOBE로 '적용' (TOBE에 해당 내용 삽입)
402
+ asisButtonBuilder.add(
426
403
  asisRangeStart, // ASIS에서의 해당 Diff 청크 시작 오프셋
427
404
  asisRangeStart,
428
405
  Decoration.widget({
@@ -430,7 +407,7 @@ export class IdeDiff extends HTMLElement {
430
407
  true, // 이 버튼은 ASIS 에디터에 붙는 버튼 (적용)
431
408
  text, // ASIS에서 삭제된 내용을 TOBE에 삽입
432
409
  currentInstance.#tobeEditorView, // 대상 에디터는 TOBE
433
- { from: tobeRangeStart, to: tobeRangeStart + 0 }, // TOBE에서의 대상 범위 (삽입 지점)
410
+ { from: tobeRangeStart, to: tobeRangeStart + 0 }, // TOBE에서 '삽입될' 위치
434
411
  currentInstance
435
412
  ),
436
413
  side: 1 // 텍스트 뒤에 삽입
@@ -447,7 +424,9 @@ export class IdeDiff extends HTMLElement {
447
424
 
448
425
  return {
449
426
  asisDecorations: asisLineBuilder.finish(),
450
- tobeDecorations: tobeLineBuilder.finish()
427
+ tobeDecorations: tobeLineBuilder.finish(),
428
+ asisButtonDecorations: asisButtonBuilder.finish(), // ⭐️ 추가 반환
429
+ tobeButtonDecorations: tobeButtonBuilder.finish() // ⭐️ 추가 반환
451
430
  };
452
431
  };
453
432
 
@@ -455,13 +434,19 @@ export class IdeDiff extends HTMLElement {
455
434
  const asisDoc = this.#asisEditorView.state.doc.toString();
456
435
  const tobeDoc = this.#tobeEditorView.state.doc.toString();
457
436
 
458
- const { asisDecorations, tobeDecorations } = this.#applyDiffDecorations(asisDoc, tobeDoc);
437
+ const { asisDecorations, tobeDecorations, asisButtonDecorations, tobeButtonDecorations } = this.#applyDiffDecorations(asisDoc, tobeDoc);
459
438
 
460
439
  this.#asisEditorView.dispatch({
461
- effects: [setAsisDecorationsEffect.of(asisDecorations)]
440
+ effects: [
441
+ setAsisDecorationsEffect.of(asisDecorations),
442
+ setAsisButtonDecorationsEffect.of(asisButtonDecorations) // ⭐️ Effect 추가
443
+ ]
462
444
  });
463
445
  this.#tobeEditorView.dispatch({
464
- effects: [setTobeDecorationsEffect.of(tobeDecorations)]
446
+ effects: [
447
+ setTobeDecorationsEffect.of(tobeDecorations),
448
+ setTobeButtonDecorationsEffect.of(tobeButtonDecorations) // ⭐️ Effect 추가
449
+ ]
465
450
  });
466
451
  };
467
452
 
@@ -497,13 +482,19 @@ export class IdeDiff extends HTMLElement {
497
482
  });
498
483
 
499
484
  requestAnimationFrame(() => {
500
- const { asisDecorations, tobeDecorations } = this.#applyDiffDecorations(src1, src2);
485
+ const { asisDecorations, tobeDecorations, asisButtonDecorations, tobeButtonDecorations } = this.#applyDiffDecorations(src1, src2);
501
486
 
502
487
  this.#asisEditorView.dispatch({
503
- effects: [setAsisDecorationsEffect.of(asisDecorations)]
488
+ effects: [
489
+ setAsisDecorationsEffect.of(asisDecorations),
490
+ setAsisButtonDecorationsEffect.of(asisButtonDecorations)
491
+ ]
504
492
  });
505
493
  this.#tobeEditorView.dispatch({
506
- effects: [setTobeDecorationsEffect.of(tobeDecorations)]
494
+ effects: [
495
+ setTobeDecorationsEffect.of(tobeDecorations),
496
+ setTobeButtonDecorationsEffect.of(tobeButtonDecorations)
497
+ ]
507
498
  });
508
499
 
509
500
  requestAnimationFrame(() => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ide-assi",
3
3
  "type": "module",
4
- "version": "0.438.0",
4
+ "version": "0.440.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {