ide-assi 0.438.0 → 0.439.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.
- package/dist/bundle.cjs.js +96 -108
- package/dist/bundle.esm.js +96 -108
- package/dist/components/ideDiff.js +96 -108
- package/package.json +1 -1
- package/src/components/ideDiff.js +96 -108
|
@@ -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,35 +61,61 @@ 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;
|
|
69
|
-
this.textToApply = textToApply;
|
|
70
|
-
this.targetEditorView = targetEditorView;
|
|
71
|
-
this.diffRange = diffRange;
|
|
72
|
-
this.hostComponent = hostComponent;
|
|
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 ? "→ 적용" : "← 되돌리기";
|
|
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
|
-
console.log("대상 에디터:", this.targetEditorView === this.hostComponent
|
|
118
|
+
console.log("대상 에디터:", this.targetEditorView === this.hostComponent.#asisEditorView ? "ASIS" : "TOBE");
|
|
89
119
|
console.log("대상 범위:", this.diffRange);
|
|
90
120
|
|
|
91
121
|
this.applyChanges(this.textToApply, this.targetEditorView, this.diffRange);
|
|
@@ -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
|
});
|
|
@@ -135,79 +171,16 @@ export class IdeDiff extends HTMLElement {
|
|
|
135
171
|
_tobeScrollHandler = null;
|
|
136
172
|
|
|
137
173
|
|
|
138
|
-
get asisEditorView() {
|
|
139
|
-
return this.#asisEditorView;
|
|
140
|
-
};
|
|
141
|
-
|
|
142
174
|
constructor() {
|
|
143
175
|
super();
|
|
144
176
|
this.attachShadow({ mode: 'open' });
|
|
145
177
|
}
|
|
146
178
|
|
|
147
|
-
|
|
148
|
-
|
|
149
179
|
connectedCallback() {
|
|
150
180
|
this.shadowRoot.innerHTML = `
|
|
151
181
|
<style>
|
|
152
|
-
/* ninegrid CSS 및 필요한 기본 스타일 */
|
|
153
182
|
@import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideDiff.css";
|
|
154
183
|
${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
184
|
</style>
|
|
212
185
|
|
|
213
186
|
<div class="wrapper">
|
|
@@ -264,22 +237,22 @@ export class IdeDiff extends HTMLElement {
|
|
|
264
237
|
...historyKeymap,
|
|
265
238
|
...lintKeymap,
|
|
266
239
|
...completionKeymap,
|
|
267
|
-
indentWithTab,
|
|
268
|
-
selectAll
|
|
240
|
+
indentWithTab,
|
|
241
|
+
selectAll
|
|
269
242
|
]),
|
|
270
243
|
syntaxHighlighting(defaultHighlightStyle),
|
|
271
244
|
autocompletion(),
|
|
272
245
|
];
|
|
273
246
|
|
|
274
|
-
// ASIS 에디터 뷰 초기화
|
|
275
247
|
this.#asisEditorView = new EditorView({
|
|
276
248
|
state: EditorState.create({
|
|
277
249
|
doc: '',
|
|
278
250
|
extensions: [
|
|
279
251
|
basicExtensions,
|
|
280
252
|
this.#languageCompartment.of(javascript()),
|
|
281
|
-
EditorState.readOnly.of(true),
|
|
253
|
+
EditorState.readOnly.of(true),
|
|
282
254
|
asisDiffDecorations,
|
|
255
|
+
asisButtonDecorations, // ⭐️ 새로운 StateField 추가
|
|
283
256
|
EditorView.updateListener.of((update) => {
|
|
284
257
|
if (update.view.contentDOM.firstChild && !update.view._initialAsisContentLoaded) {
|
|
285
258
|
update.view._initialAsisContentLoaded = true;
|
|
@@ -291,15 +264,14 @@ export class IdeDiff extends HTMLElement {
|
|
|
291
264
|
parent: this.#asisEditorEl
|
|
292
265
|
});
|
|
293
266
|
|
|
294
|
-
// TOBE 에디터 뷰 초기화
|
|
295
267
|
this.#tobeEditorView = new EditorView({
|
|
296
268
|
state: EditorState.create({
|
|
297
269
|
doc: '',
|
|
298
270
|
extensions: [
|
|
299
271
|
basicExtensions,
|
|
300
272
|
this.#languageCompartment.of(javascript()),
|
|
301
|
-
// EditorState.readOnly.of(true), // TOBE는 편집 가능하도록 주석 처리 유지
|
|
302
273
|
tobeDiffDecorations,
|
|
274
|
+
tobeButtonDecorations, // ⭐️ 새로운 StateField 추가
|
|
303
275
|
EditorView.updateListener.of((update) => {
|
|
304
276
|
if (update.view.contentDOM.firstChild && !update.view._initialTobeContentLoaded) {
|
|
305
277
|
update.view._initialTobeContentLoaded = true;
|
|
@@ -361,13 +333,15 @@ export class IdeDiff extends HTMLElement {
|
|
|
361
333
|
dmp.diff_cleanupSemantic(diffs);
|
|
362
334
|
dmp.diff_charsToLines_(diffs, lineArray);
|
|
363
335
|
|
|
364
|
-
console.log("Calculated Diffs:", diffs);
|
|
336
|
+
console.log("Calculated Diffs:", diffs);
|
|
365
337
|
|
|
366
338
|
const asisLineBuilder = new RangeSetBuilder();
|
|
367
339
|
const tobeLineBuilder = new RangeSetBuilder();
|
|
340
|
+
const asisButtonBuilder = new RangeSetBuilder(); // ⭐️ 새로운 빌더
|
|
341
|
+
const tobeButtonBuilder = new RangeSetBuilder(); // ⭐️ 새로운 빌더
|
|
368
342
|
|
|
369
|
-
let asisCursor = 0;
|
|
370
|
-
let tobeCursor = 0;
|
|
343
|
+
let asisCursor = 0;
|
|
344
|
+
let tobeCursor = 0;
|
|
371
345
|
|
|
372
346
|
const insertedLineDeco = Decoration.line({ class: "cm-inserted-line-bg" });
|
|
373
347
|
const deletedLineDeco = Decoration.line({ class: "cm-deleted-line-bg" });
|
|
@@ -377,7 +351,6 @@ export class IdeDiff extends HTMLElement {
|
|
|
377
351
|
for (const [op, text] of diffs) {
|
|
378
352
|
const len = text.length;
|
|
379
353
|
|
|
380
|
-
// 각 diff op 이전에 현재 커서 위치를 저장
|
|
381
354
|
const asisRangeStart = asisCursor;
|
|
382
355
|
const tobeRangeStart = tobeCursor;
|
|
383
356
|
|
|
@@ -392,20 +365,22 @@ export class IdeDiff extends HTMLElement {
|
|
|
392
365
|
tobeCursor += tobeLines[i].length + (i < tobeLines.length - 1 ? 1 : 0);
|
|
393
366
|
}
|
|
394
367
|
|
|
395
|
-
// ⭐️ TOBE 에디터에 버튼 추가: TOBE의 추가 내용을 ASIS로 '되돌리기' (ASIS에서
|
|
396
|
-
//
|
|
397
|
-
|
|
368
|
+
// ⭐️ TOBE 에디터에 버튼 추가: TOBE의 추가 내용을 ASIS로 '되돌리기' (ASIS에서 제거)
|
|
369
|
+
// TOBE의 해당 Diff가 끝나는 지점에 버튼을 붙이는 것이 자연스럽습니다.
|
|
370
|
+
// tobeRangeStart는 Diff 청크의 시작 오프셋입니다.
|
|
371
|
+
// 버튼을 Line Decoration과 같은 위치에 추가하되, 별도의 builder 사용
|
|
372
|
+
tobeButtonBuilder.add(
|
|
398
373
|
tobeRangeStart, // TOBE에서의 해당 Diff 청크 시작 오프셋
|
|
399
374
|
tobeRangeStart,
|
|
400
375
|
Decoration.widget({
|
|
401
376
|
widget: new MergeButtonWidget(
|
|
402
377
|
false, // 이 버튼은 TOBE 에디터에 붙는 버튼 (되돌리기)
|
|
403
|
-
"",
|
|
378
|
+
"", // ASIS에서 해당 내용을 제거하므로 삽입할 텍스트는 없음 (즉, 삭제)
|
|
404
379
|
currentInstance.#asisEditorView, // 대상 에디터는 ASIS
|
|
405
|
-
{ from: asisRangeStart, to: asisRangeStart +
|
|
380
|
+
{ from: asisRangeStart, to: asisRangeStart + text.length }, // ASIS에서 '삭제될' 범위
|
|
406
381
|
currentInstance
|
|
407
382
|
),
|
|
408
|
-
side: 1 // 텍스트 뒤에 삽입
|
|
383
|
+
side: 1 // 텍스트 뒤에 삽입 (이전에 `side: -1`로 시도했다면 `-1`이 우선순위가 더 높습니다.)
|
|
409
384
|
})
|
|
410
385
|
);
|
|
411
386
|
break;
|
|
@@ -420,9 +395,8 @@ export class IdeDiff extends HTMLElement {
|
|
|
420
395
|
asisCursor += asisLines[i].length + (i < asisLines.length - 1 ? 1 : 0);
|
|
421
396
|
}
|
|
422
397
|
|
|
423
|
-
// ⭐️ ASIS 에디터에 버튼 추가: ASIS의 삭제 내용을 TOBE로 '적용' (TOBE에 삽입)
|
|
424
|
-
|
|
425
|
-
asisLineBuilder.add(
|
|
398
|
+
// ⭐️ ASIS 에디터에 버튼 추가: ASIS의 삭제 내용을 TOBE로 '적용' (TOBE에 해당 내용 삽입)
|
|
399
|
+
asisButtonBuilder.add(
|
|
426
400
|
asisRangeStart, // ASIS에서의 해당 Diff 청크 시작 오프셋
|
|
427
401
|
asisRangeStart,
|
|
428
402
|
Decoration.widget({
|
|
@@ -430,7 +404,7 @@ export class IdeDiff extends HTMLElement {
|
|
|
430
404
|
true, // 이 버튼은 ASIS 에디터에 붙는 버튼 (적용)
|
|
431
405
|
text, // ASIS에서 삭제된 내용을 TOBE에 삽입
|
|
432
406
|
currentInstance.#tobeEditorView, // 대상 에디터는 TOBE
|
|
433
|
-
{ from: tobeRangeStart, to: tobeRangeStart + 0 }, // TOBE
|
|
407
|
+
{ from: tobeRangeStart, to: tobeRangeStart + 0 }, // TOBE에서 '삽입될' 위치
|
|
434
408
|
currentInstance
|
|
435
409
|
),
|
|
436
410
|
side: 1 // 텍스트 뒤에 삽입
|
|
@@ -447,7 +421,9 @@ export class IdeDiff extends HTMLElement {
|
|
|
447
421
|
|
|
448
422
|
return {
|
|
449
423
|
asisDecorations: asisLineBuilder.finish(),
|
|
450
|
-
tobeDecorations: tobeLineBuilder.finish()
|
|
424
|
+
tobeDecorations: tobeLineBuilder.finish(),
|
|
425
|
+
asisButtonDecorations: asisButtonBuilder.finish(), // ⭐️ 추가 반환
|
|
426
|
+
tobeButtonDecorations: tobeButtonBuilder.finish() // ⭐️ 추가 반환
|
|
451
427
|
};
|
|
452
428
|
};
|
|
453
429
|
|
|
@@ -455,13 +431,19 @@ export class IdeDiff extends HTMLElement {
|
|
|
455
431
|
const asisDoc = this.#asisEditorView.state.doc.toString();
|
|
456
432
|
const tobeDoc = this.#tobeEditorView.state.doc.toString();
|
|
457
433
|
|
|
458
|
-
const { asisDecorations, tobeDecorations } = this.#applyDiffDecorations(asisDoc, tobeDoc);
|
|
434
|
+
const { asisDecorations, tobeDecorations, asisButtonDecorations, tobeButtonDecorations } = this.#applyDiffDecorations(asisDoc, tobeDoc);
|
|
459
435
|
|
|
460
436
|
this.#asisEditorView.dispatch({
|
|
461
|
-
effects: [
|
|
437
|
+
effects: [
|
|
438
|
+
setAsisDecorationsEffect.of(asisDecorations),
|
|
439
|
+
setAsisButtonDecorationsEffect.of(asisButtonDecorations) // ⭐️ Effect 추가
|
|
440
|
+
]
|
|
462
441
|
});
|
|
463
442
|
this.#tobeEditorView.dispatch({
|
|
464
|
-
effects: [
|
|
443
|
+
effects: [
|
|
444
|
+
setTobeDecorationsEffect.of(tobeDecorations),
|
|
445
|
+
setTobeButtonDecorationsEffect.of(tobeButtonDecorations) // ⭐️ Effect 추가
|
|
446
|
+
]
|
|
465
447
|
});
|
|
466
448
|
};
|
|
467
449
|
|
|
@@ -497,13 +479,19 @@ export class IdeDiff extends HTMLElement {
|
|
|
497
479
|
});
|
|
498
480
|
|
|
499
481
|
requestAnimationFrame(() => {
|
|
500
|
-
const { asisDecorations, tobeDecorations } = this.#applyDiffDecorations(src1, src2);
|
|
482
|
+
const { asisDecorations, tobeDecorations, asisButtonDecorations, tobeButtonDecorations } = this.#applyDiffDecorations(src1, src2);
|
|
501
483
|
|
|
502
484
|
this.#asisEditorView.dispatch({
|
|
503
|
-
effects: [
|
|
485
|
+
effects: [
|
|
486
|
+
setAsisDecorationsEffect.of(asisDecorations),
|
|
487
|
+
setAsisButtonDecorationsEffect.of(asisButtonDecorations)
|
|
488
|
+
]
|
|
504
489
|
});
|
|
505
490
|
this.#tobeEditorView.dispatch({
|
|
506
|
-
effects: [
|
|
491
|
+
effects: [
|
|
492
|
+
setTobeDecorationsEffect.of(tobeDecorations),
|
|
493
|
+
setTobeButtonDecorationsEffect.of(tobeButtonDecorations)
|
|
494
|
+
]
|
|
507
495
|
});
|
|
508
496
|
|
|
509
497
|
requestAnimationFrame(() => {
|