ide-assi 0.402.0 → 0.403.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 +121 -176
- package/dist/bundle.esm.js +121 -176
- package/dist/components/ideDiff.js +102 -136
- package/package.json +1 -1
- package/src/components/ideDiff.js +102 -136
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ninegrid from "ninegrid2";
|
|
2
2
|
|
|
3
|
-
// CodeMirror 6 핵심 및 확장 임포트
|
|
3
|
+
// CodeMirror 6 핵심 및 확장 임포트
|
|
4
4
|
import {
|
|
5
5
|
EditorView, lineNumbers, highlightSpecialChars, drawSelection,
|
|
6
6
|
dropCursor, keymap, highlightActiveLine, highlightActiveLineGutter,
|
|
@@ -12,47 +12,57 @@ import {
|
|
|
12
12
|
StateEffect,
|
|
13
13
|
Text
|
|
14
14
|
} from "@codemirror/state";
|
|
15
|
-
import { history, historyKeymap, indentWithTab } from "@codemirror/commands";
|
|
16
|
-
import { defaultKeymap
|
|
15
|
+
import { history, historyKeymap, indentWithTab, selectAll } from "@codemirror/commands"; // selectAll, indentWithTab 임포트 확인
|
|
16
|
+
import { defaultKeymap } from "@codemirror/commands"; // defaultKeymap 별도 임포트
|
|
17
17
|
import { searchKeymap, highlightSelectionMatches } from "@codemirror/search";
|
|
18
|
-
import { bracketMatching } from "@codemirror/language";
|
|
19
|
-
import { javascript } from "@codemirror/lang-javascript";
|
|
20
|
-
|
|
18
|
+
import { bracketMatching, syntaxHighlighting, defaultHighlightStyle } from "@codemirror/language";
|
|
19
|
+
import { javascript } from "@codemirror/lang-javascript"; // 언어 확장 임포트
|
|
20
|
+
|
|
21
21
|
import { lintKeymap } from "@codemirror/lint";
|
|
22
22
|
import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
|
|
23
23
|
|
|
24
|
-
// Diff 로직을 위해 diff-match-patch 사용
|
|
24
|
+
// Diff 로직을 위해 diff-match-patch 사용
|
|
25
25
|
import { diff_match_patch } from 'diff-match-patch';
|
|
26
26
|
|
|
27
|
-
// Diff 데코레이션을 위한
|
|
27
|
+
// --- Diff 데코레이션을 위한 StateEffect 정의 (수정: DecorationSet 타입 명시) ---
|
|
28
|
+
const setAsisDecorationsEffect = StateEffect.define();
|
|
29
|
+
const setTobeDecorationsEffect = StateEffect.define();
|
|
30
|
+
|
|
31
|
+
// --- Diff 데코레이션을 위한 StateField 정의 ---
|
|
28
32
|
const asisDiffDecorations = StateField.define({
|
|
29
|
-
create() {
|
|
33
|
+
create() {
|
|
34
|
+
return Decoration.none; // 초기 상태: 데코레이션 없음
|
|
35
|
+
},
|
|
30
36
|
update(decorations, tr) {
|
|
31
|
-
|
|
37
|
+
// 트랜잭션의 변화를 데코레이션에 매핑하여 위치가 변경되어도 데코레이션이 유지되도록 함
|
|
38
|
+
decorations = decorations.map(tr.changes);
|
|
39
|
+
// setAsisDecorationsEffect가 있다면 해당 효과의 값을 적용
|
|
40
|
+
for (let effect of tr.effects) {
|
|
32
41
|
if (effect.is(setAsisDecorationsEffect)) {
|
|
33
|
-
return effect.value;
|
|
42
|
+
return effect.value; // 새로운 DecorationSet으로 교체
|
|
34
43
|
}
|
|
35
|
-
|
|
36
|
-
|
|
44
|
+
}
|
|
45
|
+
return decorations; // 효과가 없으면 기존 데코레이션 반환
|
|
37
46
|
},
|
|
38
|
-
provide: f => EditorView.decorations.from(f)
|
|
47
|
+
provide: f => EditorView.decorations.from(f) // 이 필드가 제공하는 데코레이션을 EditorView.decorations 확장으로 등록
|
|
39
48
|
});
|
|
40
49
|
|
|
41
50
|
const tobeDiffDecorations = StateField.define({
|
|
42
|
-
create() {
|
|
51
|
+
create() {
|
|
52
|
+
return Decoration.none; // 초기 상태: 데코레이션 없음
|
|
53
|
+
},
|
|
43
54
|
update(decorations, tr) {
|
|
44
|
-
|
|
55
|
+
decorations = decorations.map(tr.changes);
|
|
56
|
+
for (let effect of tr.effects) {
|
|
45
57
|
if (effect.is(setTobeDecorationsEffect)) {
|
|
46
|
-
return effect.value;
|
|
58
|
+
return effect.value; // 새로운 DecorationSet으로 교체
|
|
47
59
|
}
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
}
|
|
61
|
+
return decorations;
|
|
50
62
|
},
|
|
51
63
|
provide: f => EditorView.decorations.from(f)
|
|
52
64
|
});
|
|
53
65
|
|
|
54
|
-
const setAsisDecorationsEffect = StateEffect.define();
|
|
55
|
-
const setTobeDecorationsEffect = StateEffect.define();
|
|
56
66
|
|
|
57
67
|
export class IdeDiff extends HTMLElement {
|
|
58
68
|
|
|
@@ -76,9 +86,13 @@ export class IdeDiff extends HTMLElement {
|
|
|
76
86
|
connectedCallback() {
|
|
77
87
|
this.shadowRoot.innerHTML = `
|
|
78
88
|
<style>
|
|
79
|
-
/* ninegrid CSS 및 필요한 기본 스타일
|
|
89
|
+
/* ninegrid CSS 및 필요한 기본 스타일 */
|
|
80
90
|
@import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideDiff.css";
|
|
81
91
|
${ninegrid.getCustomPath(this, "ideDiff.css")}
|
|
92
|
+
|
|
93
|
+
/* Diff 데코레이션 CSS 직접 추가 (없으면 ideDiff.css에 추가) */
|
|
94
|
+
.cm-inserted-line-bg { background-color: #e6ffed; border-left: 3px solid #66bb6a; }
|
|
95
|
+
.cm-deleted-line-bg { background-color: #ffebe9; border-left: 3px solid #ef5350; }
|
|
82
96
|
</style>
|
|
83
97
|
|
|
84
98
|
<div class="wrapper">
|
|
@@ -93,6 +107,22 @@ export class IdeDiff extends HTMLElement {
|
|
|
93
107
|
});
|
|
94
108
|
}
|
|
95
109
|
|
|
110
|
+
disconnectedCallback() {
|
|
111
|
+
// 컴포넌트 해제 시 스크롤 리스너 제거
|
|
112
|
+
if (this._asisScrollHandler) {
|
|
113
|
+
this.#asisEditorView.scrollDOM.removeEventListener('scroll', this._asisScrollHandler);
|
|
114
|
+
this.#tobeEditorView.scrollDOM.removeEventListener('scroll', this._tobeScrollHandler);
|
|
115
|
+
this._asisScrollHandler = null;
|
|
116
|
+
this._tobeScrollHandler = null;
|
|
117
|
+
}
|
|
118
|
+
if (this.#asisEditorView) {
|
|
119
|
+
this.#asisEditorView.destroy();
|
|
120
|
+
}
|
|
121
|
+
if (this.#tobeEditorView) {
|
|
122
|
+
this.#tobeEditorView.destroy();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
96
126
|
#initCodeMirror = () => {
|
|
97
127
|
this.#asisEditorEl = this.shadowRoot.querySelector('.panel.asis');
|
|
98
128
|
this.#tobeEditorEl = this.shadowRoot.querySelector('.panel.tobe');
|
|
@@ -109,9 +139,10 @@ export class IdeDiff extends HTMLElement {
|
|
|
109
139
|
drawSelection(),
|
|
110
140
|
dropCursor(),
|
|
111
141
|
EditorState.allowMultipleSelections.of(true),
|
|
112
|
-
indentOnInput(),
|
|
142
|
+
indentOnInput(), // 함수 호출
|
|
113
143
|
bracketMatching(),
|
|
114
144
|
highlightActiveLine(),
|
|
145
|
+
highlightActiveLineGutter(),
|
|
115
146
|
highlightSelectionMatches(),
|
|
116
147
|
keymap.of([
|
|
117
148
|
...defaultKeymap,
|
|
@@ -119,11 +150,11 @@ export class IdeDiff extends HTMLElement {
|
|
|
119
150
|
...historyKeymap,
|
|
120
151
|
...lintKeymap,
|
|
121
152
|
...completionKeymap,
|
|
122
|
-
indentWithTab,
|
|
123
|
-
selectAll
|
|
153
|
+
indentWithTab(), // 함수 호출
|
|
154
|
+
selectAll() // 함수 호출
|
|
124
155
|
]),
|
|
125
|
-
syntaxHighlighting(defaultHighlightStyle, { fallback: true }
|
|
126
|
-
autocompletion(),
|
|
156
|
+
syntaxHighlighting(defaultHighlightStyle), // { fallback: true } 제거
|
|
157
|
+
autocompletion(), // 함수 호출
|
|
127
158
|
];
|
|
128
159
|
|
|
129
160
|
// ASIS 에디터 뷰 초기화
|
|
@@ -132,11 +163,9 @@ export class IdeDiff extends HTMLElement {
|
|
|
132
163
|
doc: '',
|
|
133
164
|
extensions: [
|
|
134
165
|
basicExtensions,
|
|
135
|
-
javascript(),
|
|
166
|
+
this.#languageCompartment.of(javascript()), // 초기 언어 설정
|
|
136
167
|
EditorState.readOnly.of(true),
|
|
137
|
-
|
|
138
|
-
asisDiffDecorations,
|
|
139
|
-
// ⭐️ updateListener는 유지: 뷰가 DOM에 완전히 렌더링될 때까지 기다림
|
|
168
|
+
asisDiffDecorations, // ASIS Diff 데코레이션 필드
|
|
140
169
|
EditorView.updateListener.of((update) => {
|
|
141
170
|
if (update.view.contentDOM.firstChild && !update.view._initialAsisContentLoaded) {
|
|
142
171
|
update.view._initialAsisContentLoaded = true;
|
|
@@ -154,11 +183,9 @@ export class IdeDiff extends HTMLElement {
|
|
|
154
183
|
doc: '',
|
|
155
184
|
extensions: [
|
|
156
185
|
basicExtensions,
|
|
157
|
-
javascript(),
|
|
186
|
+
this.#languageCompartment.of(javascript()), // 초기 언어 설정
|
|
158
187
|
EditorState.readOnly.of(true),
|
|
159
|
-
|
|
160
|
-
tobeDiffDecorations,
|
|
161
|
-
// ⭐️ updateListener는 유지: 뷰가 DOM에 완전히 렌더링될 때까지 기다림
|
|
188
|
+
tobeDiffDecorations, // TOBE Diff 데코레이션 필드
|
|
162
189
|
EditorView.updateListener.of((update) => {
|
|
163
190
|
if (update.view.contentDOM.firstChild && !update.view._initialTobeContentLoaded) {
|
|
164
191
|
update.view._initialTobeContentLoaded = true;
|
|
@@ -208,71 +235,49 @@ export class IdeDiff extends HTMLElement {
|
|
|
208
235
|
this.#tobeEditorView.scrollDOM.addEventListener('scroll', this._tobeScrollHandler);
|
|
209
236
|
};
|
|
210
237
|
|
|
211
|
-
//
|
|
238
|
+
// Diff 데코레이션 계산 및 반환 함수
|
|
212
239
|
#applyDiffDecorations = (asisSrc, tobeSrc) => {
|
|
213
240
|
const dmp = new diff_match_patch();
|
|
214
241
|
const diffs = dmp.diff_main(asisSrc, tobeSrc);
|
|
215
242
|
dmp.diff_cleanupSemantic(diffs);
|
|
216
243
|
|
|
217
|
-
const
|
|
218
|
-
const
|
|
244
|
+
const asisLineBuilder = new RangeSetBuilder();
|
|
245
|
+
const tobeLineBuilder = new RangeSetBuilder();
|
|
219
246
|
|
|
220
|
-
let asisCursor = 0;
|
|
221
|
-
let tobeCursor = 0;
|
|
247
|
+
let asisCursor = 0; // ASIS 텍스트에서의 현재 커서 위치 (문자 오프셋)
|
|
248
|
+
let tobeCursor = 0; // TOBE 텍스트에서의 현재 커서 위치 (문자 오프셋)
|
|
222
249
|
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
const tobeDoc = Text.of(tobeSrc.split('\n'));
|
|
250
|
+
// Decoration 인스턴스 미리 생성
|
|
251
|
+
const insertedLineDeco = Decoration.line({ class: "cm-inserted-line-bg" });
|
|
252
|
+
const deletedLineDeco = Decoration.line({ class: "cm-deleted-line-bg" });
|
|
227
253
|
|
|
228
254
|
for (const [op, text] of diffs) {
|
|
229
|
-
const len = text.length;
|
|
255
|
+
const len = text.length; // 현재 Diff 청크의 텍스트 길이
|
|
256
|
+
const linesInChunk = text.split('\n');
|
|
230
257
|
|
|
231
258
|
switch (op) {
|
|
232
|
-
case diff_match_patch.DIFF_INSERT: // Added text
|
|
233
|
-
let
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (currentTobeLineOffset < tobeDoc.length) {
|
|
238
|
-
const line = tobeDoc.lineAt(currentTobeLineOffset);
|
|
239
|
-
tobeLineDecos.push({
|
|
240
|
-
from: line.from,
|
|
241
|
-
to: line.to,
|
|
242
|
-
deco: Decoration.line({ class: "cm-inserted-line-bg" })
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
// 다음 줄의 시작 오프셋 계산 (개행 문자 고려)
|
|
246
|
-
currentTobeLineOffset += tobeLines[i].length + (i < tobeLines.length - 1 ? 1 : 0);
|
|
247
|
-
// 마지막 줄이 비어 있고 개행으로 끝나지 않으면 루프 종료 (lineAt 오류 방지)
|
|
248
|
-
if (i === tobeLines.length - 1 && tobeLines[i].length === 0 && text.endsWith('\n') === false) {
|
|
259
|
+
case diff_match_patch.DIFF_INSERT: // Added text (TOBE에만 존재)
|
|
260
|
+
for (let i = 0; i < linesInChunk.length; i++) {
|
|
261
|
+
// 마지막 줄이 비어있고 해당 청크가 개행으로 끝나지 않으면 스킵
|
|
262
|
+
if (i === linesInChunk.length - 1 && linesInChunk[i] === '' && !text.endsWith('\n')) {
|
|
263
|
+
tobeCursor += len; // tobeCursor만 해당 청크 길이만큼 증가
|
|
249
264
|
break;
|
|
250
265
|
}
|
|
266
|
+
// Decoration.line은 줄의 시작 위치에만 추가하면 CodeMirror가 해당 줄 전체에 적용합니다.
|
|
267
|
+
tobeLineBuilder.add(tobeCursor, tobeCursor, insertedLineDeco);
|
|
268
|
+
tobeCursor += linesInChunk[i].length + (i < linesInChunk.length - 1 ? 1 : 0);
|
|
251
269
|
}
|
|
252
|
-
tobeCursor += len;
|
|
253
270
|
break;
|
|
254
271
|
|
|
255
|
-
case diff_match_patch.DIFF_DELETE: // Deleted text
|
|
256
|
-
let
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// 줄의 시작 오프셋이 asisDoc의 길이를 넘지 않는지 확인
|
|
260
|
-
if (currentAsisLineOffset < asisDoc.length) {
|
|
261
|
-
const line = asisDoc.lineAt(currentAsisLineOffset);
|
|
262
|
-
asisLineDecos.push({
|
|
263
|
-
from: line.from,
|
|
264
|
-
to: line.to,
|
|
265
|
-
deco: Decoration.line({ class: "cm-deleted-line-bg" })
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
// 다음 줄의 시작 오프셋 계산 (개행 문자 고려)
|
|
269
|
-
currentAsisLineOffset += asisLines[i].length + (i < asisLines.length - 1 ? 1 : 0);
|
|
270
|
-
// 마지막 줄이 비어 있고 개행으로 끝나지 않으면 루프 종료 (lineAt 오류 방지)
|
|
271
|
-
if (i === asisLines.length - 1 && asisLines[i].length === 0 && text.endsWith('\n') === false) {
|
|
272
|
+
case diff_match_patch.DIFF_DELETE: // Deleted text (ASIS에만 존재)
|
|
273
|
+
for (let i = 0; i < linesInChunk.length; i++) {
|
|
274
|
+
if (i === linesInChunk.length - 1 && linesInChunk[i] === '' && !text.endsWith('\n')) {
|
|
275
|
+
asisCursor += len;
|
|
272
276
|
break;
|
|
273
277
|
}
|
|
278
|
+
asisLineBuilder.add(asisCursor, asisCursor, deletedLineDeco);
|
|
279
|
+
asisCursor += linesInChunk[i].length + (i < linesInChunk.length - 1 ? 1 : 0);
|
|
274
280
|
}
|
|
275
|
-
asisCursor += len;
|
|
276
281
|
break;
|
|
277
282
|
|
|
278
283
|
case diff_match_patch.DIFF_EQUAL: // Unchanged text
|
|
@@ -282,26 +287,9 @@ export class IdeDiff extends HTMLElement {
|
|
|
282
287
|
}
|
|
283
288
|
}
|
|
284
289
|
|
|
285
|
-
// 데코레이션 중복 및 겹침 방지를 위해 정렬
|
|
286
|
-
asisLineDecos.sort((a, b) => a.from - b.from);
|
|
287
|
-
tobeLineDecos.sort((a, b) => a.from - b.from);
|
|
288
|
-
|
|
289
|
-
// 라인 데코레이션 빌더 생성
|
|
290
|
-
const asisLineBuilder = new RangeSetBuilder();
|
|
291
|
-
for (const { from, to, deco } of asisLineDecos) {
|
|
292
|
-
asisLineBuilder.add(from, to, deco);
|
|
293
|
-
}
|
|
294
|
-
const finalAsisDecorations = asisLineBuilder.finish(); // ⭐️ .update({ add: ... }) 부분 제거
|
|
295
|
-
|
|
296
|
-
const tobeLineBuilder = new RangeSetBuilder();
|
|
297
|
-
for (const { from, to, deco } of tobeLineDecos) {
|
|
298
|
-
tobeLineBuilder.add(from, to, deco);
|
|
299
|
-
}
|
|
300
|
-
const finalTobeDecorations = tobeLineBuilder.finish(); // ⭐️ .update({ add: ... }) 부분 제거
|
|
301
|
-
|
|
302
290
|
return {
|
|
303
|
-
|
|
304
|
-
|
|
291
|
+
asisDecorations: asisLineBuilder.finish(),
|
|
292
|
+
tobeDecorations: tobeLineBuilder.finish()
|
|
305
293
|
};
|
|
306
294
|
};
|
|
307
295
|
|
|
@@ -318,16 +306,17 @@ export class IdeDiff extends HTMLElement {
|
|
|
318
306
|
case 'javascript':
|
|
319
307
|
langExtension = javascript();
|
|
320
308
|
break;
|
|
309
|
+
// case 'java':
|
|
310
|
+
// langExtension = java();
|
|
311
|
+
// break;
|
|
312
|
+
// case 'xml':
|
|
313
|
+
// langExtension = xml();
|
|
314
|
+
// break;
|
|
321
315
|
default:
|
|
322
|
-
langExtension = javascript();
|
|
316
|
+
langExtension = javascript(); // 기본값
|
|
323
317
|
}
|
|
324
318
|
|
|
325
|
-
// 데코레이션 효과는 미리 계산해 둡니다.
|
|
326
|
-
const { asisEffect, tobeEffect } = this.#applyDiffDecorations(src1, src2);
|
|
327
|
-
|
|
328
319
|
// 1단계: 텍스트 내용 변경과 언어 확장을 먼저 디스패치합니다.
|
|
329
|
-
// 'to' 값을 현재 문서 길이 전체로 지정하여 정확한 교체를 보장합니다.
|
|
330
|
-
|
|
331
320
|
this.#asisEditorView.dispatch({
|
|
332
321
|
changes: { from: 0, to: this.#asisEditorView.state.doc.length, insert: src1 },
|
|
333
322
|
effects: [
|
|
@@ -342,27 +331,19 @@ export class IdeDiff extends HTMLElement {
|
|
|
342
331
|
]
|
|
343
332
|
});
|
|
344
333
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
// ⭐️ 2단계: 텍스트 및 언어 변경이 완전히 적용되고 뷰가 안정화될 시간을 줍니다.
|
|
334
|
+
// 2단계: 텍스트 및 언어 변경이 완전히 적용되고 뷰가 안정화될 시간을 줍니다.
|
|
348
335
|
// 다음 프레임에서 데코레이션 효과만 별도로 디스패치합니다.
|
|
349
336
|
requestAnimationFrame(() => {
|
|
337
|
+
const { asisDecorations, tobeDecorations } = this.#applyDiffDecorations(src1, src2);
|
|
338
|
+
|
|
350
339
|
this.#asisEditorView.dispatch({
|
|
351
|
-
effects: [
|
|
352
|
-
StateEffect.reconfigure.of(
|
|
353
|
-
EditorView.decorations.of(
|
|
354
|
-
Decoration.set(
|
|
355
|
-
Decoration.line({ attributes: { style: "font-weight: bold" } }).range(0)
|
|
356
|
-
)
|
|
357
|
-
)
|
|
358
|
-
)
|
|
359
|
-
]
|
|
340
|
+
effects: [setAsisDecorationsEffect.of(asisDecorations)] // StateField가 처리할 효과
|
|
360
341
|
});
|
|
361
342
|
this.#tobeEditorView.dispatch({
|
|
362
|
-
|
|
343
|
+
effects: [setTobeDecorationsEffect.of(tobeDecorations)] // StateField가 처리할 효과
|
|
363
344
|
});
|
|
364
345
|
|
|
365
|
-
//
|
|
346
|
+
// 3단계: 데코레이션까지 적용된 후 뷰가 다시 안정화될 시간을 한 번 더 줍니다.
|
|
366
347
|
// 그 다음 프레임에서 스크롤 동기화를 활성화합니다.
|
|
367
348
|
requestAnimationFrame(() => {
|
|
368
349
|
this.#isScrollSyncActive = true;
|
|
@@ -372,21 +353,6 @@ export class IdeDiff extends HTMLElement {
|
|
|
372
353
|
});
|
|
373
354
|
});
|
|
374
355
|
};
|
|
375
|
-
|
|
376
|
-
disconnectedCallback() {
|
|
377
|
-
// 컴포넌트 해제 시 스크롤 리스너 제거
|
|
378
|
-
if (this._asisScrollHandler) {
|
|
379
|
-
this.#asisEditorView.scrollDOM.removeEventListener('scroll', this._asisScrollHandler);
|
|
380
|
-
this.#tobeEditorView.scrollDOM.removeEventListener('scroll', this._tobeScrollHandler);
|
|
381
|
-
this._asisScrollHandler = null;
|
|
382
|
-
this._tobeScrollHandler = null;
|
|
383
|
-
}
|
|
384
|
-
if (this.#asisEditorView) {
|
|
385
|
-
this.#asisEditorView.destroy();
|
|
386
|
-
}
|
|
387
|
-
if (this.#tobeEditorView) {
|
|
388
|
-
this.#tobeEditorView.destroy();
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
356
|
}
|
|
357
|
+
|
|
392
358
|
customElements.define("ide-diff", IdeDiff);
|