@opensumi/ide-editor 2.27.3-rc-1714116491.0 → 2.27.3-rc-1714982362.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/lib/browser/doc-model/editor-document-model-service.d.ts +3 -1
- package/lib/browser/doc-model/editor-document-model-service.d.ts.map +1 -1
- package/lib/browser/doc-model/editor-document-model-service.js +6 -1
- package/lib/browser/doc-model/editor-document-model-service.js.map +1 -1
- package/lib/browser/doc-model/types.d.ts +1 -0
- package/lib/browser/doc-model/types.d.ts.map +1 -1
- package/lib/browser/doc-model/types.js.map +1 -1
- package/lib/browser/editor-collection.service.d.ts +6 -6
- package/lib/browser/editor-collection.service.d.ts.map +1 -1
- package/lib/browser/editor-collection.service.js.map +1 -1
- package/lib/browser/editor-electron.contribution.d.ts +2 -2
- package/lib/browser/editor-electron.contribution.d.ts.map +1 -1
- package/lib/browser/editor-electron.contribution.js +1 -1
- package/lib/browser/editor-electron.contribution.js.map +1 -1
- package/lib/browser/editor.context.d.ts +7 -0
- package/lib/browser/editor.context.d.ts.map +1 -0
- package/lib/browser/editor.context.js +10 -0
- package/lib/browser/editor.context.js.map +1 -0
- package/lib/browser/editor.contribution.d.ts.map +1 -1
- package/lib/browser/editor.contribution.js +0 -5
- package/lib/browser/editor.contribution.js.map +1 -1
- package/lib/browser/editor.decoration.service.d.ts +12 -1
- package/lib/browser/editor.decoration.service.d.ts.map +1 -1
- package/lib/browser/editor.decoration.service.js +59 -30
- package/lib/browser/editor.decoration.service.js.map +1 -1
- package/lib/browser/editor.module.less +0 -54
- package/lib/browser/editor.view.d.ts.map +1 -1
- package/lib/browser/editor.view.js +70 -51
- package/lib/browser/editor.view.js.map +1 -1
- package/lib/browser/hooks/useEditor.d.ts +4 -0
- package/lib/browser/hooks/useEditor.d.ts.map +1 -0
- package/lib/browser/hooks/useEditor.js +31 -0
- package/lib/browser/hooks/useEditor.js.map +1 -0
- package/lib/browser/hooks/useInMergeChanges.d.ts +3 -0
- package/lib/browser/hooks/useInMergeChanges.d.ts.map +1 -0
- package/lib/browser/hooks/useInMergeChanges.js +27 -0
- package/lib/browser/hooks/useInMergeChanges.js.map +1 -0
- package/lib/browser/merge-conflict/conflict-parser.d.ts +46 -0
- package/lib/browser/merge-conflict/conflict-parser.d.ts.map +1 -0
- package/lib/browser/merge-conflict/conflict-parser.js +209 -0
- package/lib/browser/merge-conflict/conflict-parser.js.map +1 -0
- package/lib/browser/merge-conflict/index.d.ts +3 -0
- package/lib/browser/merge-conflict/index.d.ts.map +1 -0
- package/lib/browser/merge-conflict/index.js +6 -0
- package/lib/browser/merge-conflict/index.js.map +1 -0
- package/lib/browser/merge-conflict/types.d.ts +60 -0
- package/lib/browser/merge-conflict/types.d.ts.map +1 -0
- package/lib/browser/merge-conflict/types.js +15 -0
- package/lib/browser/merge-conflict/types.js.map +1 -0
- package/lib/browser/merge-editor/MergeEditorFloatComponents.d.ts.map +1 -1
- package/lib/browser/merge-editor/MergeEditorFloatComponents.js +73 -49
- package/lib/browser/merge-editor/MergeEditorFloatComponents.js.map +1 -1
- package/lib/browser/merge-editor/merge-editor.module.less +71 -0
- package/lib/browser/merge-editor/merge-editor.provider.d.ts.map +1 -1
- package/lib/browser/merge-editor/merge-editor.provider.js +3 -0
- package/lib/browser/merge-editor/merge-editor.provider.js.map +1 -1
- package/lib/browser/tab.view.d.ts.map +1 -1
- package/lib/browser/tab.view.js +6 -5
- package/lib/browser/tab.view.js.map +1 -1
- package/lib/browser/types.d.ts +6 -0
- package/lib/browser/types.d.ts.map +1 -1
- package/lib/browser/types.js.map +1 -1
- package/lib/browser/workbench-editor.service.d.ts +1 -1
- package/lib/browser/workbench-editor.service.js +1 -1
- package/lib/common/editor.d.ts +3 -1
- package/lib/common/editor.d.ts.map +1 -1
- package/lib/common/editor.js.map +1 -1
- package/package.json +14 -14
- package/src/browser/doc-model/editor-document-model-service.ts +11 -1
- package/src/browser/doc-model/types.ts +4 -0
- package/src/browser/editor-collection.service.ts +7 -7
- package/src/browser/editor-electron.contribution.ts +1 -2
- package/src/browser/editor.context.ts +11 -0
- package/src/browser/editor.contribution.ts +0 -6
- package/src/browser/editor.decoration.service.ts +75 -34
- package/src/browser/editor.module.less +0 -54
- package/src/browser/editor.view.tsx +102 -77
- package/src/browser/hooks/useEditor.ts +34 -0
- package/src/browser/hooks/useInMergeChanges.ts +30 -0
- package/src/browser/merge-conflict/conflict-parser.ts +323 -0
- package/src/browser/merge-conflict/index.ts +2 -0
- package/src/browser/merge-conflict/types.ts +73 -0
- package/src/browser/merge-editor/MergeEditorFloatComponents.tsx +111 -73
- package/src/browser/merge-editor/merge-editor.module.less +71 -0
- package/src/browser/merge-editor/merge-editor.provider.ts +3 -0
- package/src/browser/tab.view.tsx +7 -6
- package/src/browser/types.ts +8 -0
- package/src/browser/workbench-editor.service.ts +1 -1
- package/src/common/editor.ts +3 -1
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
ComponentRegistry,
|
|
11
11
|
ConfigContext,
|
|
12
12
|
ConfigProvider,
|
|
13
|
+
DisposableStore,
|
|
13
14
|
ErrorBoundary,
|
|
14
15
|
IEventBus,
|
|
15
16
|
MaybeNull,
|
|
@@ -27,10 +28,12 @@ import {
|
|
|
27
28
|
} from '@opensumi/ide-core-browser/lib/components';
|
|
28
29
|
import { VIEW_CONTAINERS } from '@opensumi/ide-core-browser/lib/layout/view-id';
|
|
29
30
|
import { useInjectable, useUpdateOnEventBusEvent } from '@opensumi/ide-core-browser/lib/react-hooks';
|
|
31
|
+
import { monaco } from '@opensumi/ide-monaco/lib/browser/monaco-api';
|
|
30
32
|
|
|
31
33
|
import { IResource, WorkbenchEditorService } from '../common';
|
|
32
34
|
|
|
33
35
|
import { EditorComponentRegistryImpl } from './component';
|
|
36
|
+
import { EditorContext, IEditorContext, defaultEditorContext } from './editor.context';
|
|
34
37
|
import styles from './editor.module.less';
|
|
35
38
|
import { EditorGrid, SplitDirection } from './grid/grid.service';
|
|
36
39
|
import { NavigationBar } from './navigation.view';
|
|
@@ -324,6 +327,8 @@ export const EditorGroupView = observer(({ group }: { group: EditorGroup }) => {
|
|
|
324
327
|
});
|
|
325
328
|
|
|
326
329
|
export const EditorGroupBody = observer(({ group }: { group: EditorGroup }) => {
|
|
330
|
+
const [context, setContext] = React.useState<IEditorContext>(defaultEditorContext);
|
|
331
|
+
|
|
327
332
|
const editorBodyRef = React.useRef<HTMLDivElement>(null);
|
|
328
333
|
const editorService = useInjectable(WorkbenchEditorService) as WorkbenchEditorServiceImpl;
|
|
329
334
|
const eventBus = useInjectable(IEventBus) as IEventBus;
|
|
@@ -336,6 +341,14 @@ export const EditorGroupBody = observer(({ group }: { group: EditorGroup }) => {
|
|
|
336
341
|
const forceUpdate = React.useCallback(() => updateState({}), []);
|
|
337
342
|
|
|
338
343
|
React.useEffect(() => {
|
|
344
|
+
const disposables = new DisposableStore();
|
|
345
|
+
|
|
346
|
+
disposables.add(
|
|
347
|
+
group.onDidEditorGroupBodyChanged(() => {
|
|
348
|
+
forceUpdate();
|
|
349
|
+
}),
|
|
350
|
+
);
|
|
351
|
+
|
|
339
352
|
if (codeEditorRef.current) {
|
|
340
353
|
if (cachedEditor[group.name]) {
|
|
341
354
|
cachedEditor[group.name].remove();
|
|
@@ -345,6 +358,20 @@ export const EditorGroupBody = observer(({ group }: { group: EditorGroup }) => {
|
|
|
345
358
|
codeEditorRef.current.appendChild(container);
|
|
346
359
|
cachedEditor[group.name] = container;
|
|
347
360
|
group.createEditor(container);
|
|
361
|
+
const minimapWith = group.codeEditor.monacoEditor.getOption(monaco.editor.EditorOption.layoutInfo).minimap
|
|
362
|
+
.minimapWidth;
|
|
363
|
+
setContext({ minimapWidth: minimapWith });
|
|
364
|
+
|
|
365
|
+
disposables.add(
|
|
366
|
+
group.codeEditor.monacoEditor.onDidChangeConfiguration((e) => {
|
|
367
|
+
if (e.hasChanged(monaco.editor.EditorOption.layoutInfo)) {
|
|
368
|
+
setContext({
|
|
369
|
+
minimapWidth: group.codeEditor.monacoEditor.getOption(monaco.editor.EditorOption.layoutInfo).minimap
|
|
370
|
+
.minimapWidth,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}),
|
|
374
|
+
);
|
|
348
375
|
}
|
|
349
376
|
}
|
|
350
377
|
|
|
@@ -354,15 +381,11 @@ export const EditorGroupBody = observer(({ group }: { group: EditorGroup }) => {
|
|
|
354
381
|
if (mergeEditorRef.current) {
|
|
355
382
|
group.attachMergeEditorDom(mergeEditorRef.current);
|
|
356
383
|
}
|
|
357
|
-
}, [codeEditorRef.current, diffEditorRef.current, mergeEditorRef.current]);
|
|
358
384
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}),
|
|
364
|
-
[],
|
|
365
|
-
);
|
|
385
|
+
return () => {
|
|
386
|
+
disposables.dispose();
|
|
387
|
+
};
|
|
388
|
+
}, []);
|
|
366
389
|
|
|
367
390
|
group.activeComponents.forEach((resources, component) => {
|
|
368
391
|
const initialProps = group.activateComponentsProps.get(component);
|
|
@@ -410,79 +433,81 @@ export const EditorGroupBody = observer(({ group }: { group: EditorGroup }) => {
|
|
|
410
433
|
});
|
|
411
434
|
|
|
412
435
|
return (
|
|
413
|
-
<
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
e
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}}
|
|
424
|
-
onDragLeave={(e) => {
|
|
425
|
-
if (editorBodyRef.current) {
|
|
426
|
-
removeDecorationDragOverElement(editorBodyRef.current);
|
|
427
|
-
}
|
|
428
|
-
}}
|
|
429
|
-
onDrop={(e) => {
|
|
430
|
-
if (editorBodyRef.current) {
|
|
431
|
-
removeDecorationDragOverElement(editorBodyRef.current);
|
|
432
|
-
if (e.dataTransfer.getData('uri')) {
|
|
433
|
-
const uri = new URI(e.dataTransfer.getData('uri'));
|
|
434
|
-
let sourceGroup: EditorGroup | undefined;
|
|
435
|
-
if (e.dataTransfer.getData('uri-source-group')) {
|
|
436
|
-
sourceGroup = editorService.getEditorGroup(e.dataTransfer.getData('uri-source-group'));
|
|
437
|
-
}
|
|
438
|
-
group.dropUri(uri, getDragOverPosition(e.nativeEvent, editorBodyRef.current), sourceGroup);
|
|
436
|
+
<EditorContext.Provider value={context}>
|
|
437
|
+
<div
|
|
438
|
+
id={VIEW_CONTAINERS.EDITOR}
|
|
439
|
+
ref={editorBodyRef}
|
|
440
|
+
className={styles.kt_editor_body}
|
|
441
|
+
onDragOver={(e) => {
|
|
442
|
+
e.preventDefault();
|
|
443
|
+
if (editorBodyRef.current) {
|
|
444
|
+
const position = getDragOverPosition(e.nativeEvent, editorBodyRef.current);
|
|
445
|
+
decorateDragOverElement(editorBodyRef.current, position);
|
|
439
446
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
files: e.dataTransfer.files,
|
|
445
|
-
position: getDragOverPosition(e.nativeEvent, editorBodyRef.current),
|
|
446
|
-
}),
|
|
447
|
-
);
|
|
447
|
+
}}
|
|
448
|
+
onDragLeave={(e) => {
|
|
449
|
+
if (editorBodyRef.current) {
|
|
450
|
+
removeDecorationDragOverElement(editorBodyRef.current);
|
|
448
451
|
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
452
|
+
}}
|
|
453
|
+
onDrop={(e) => {
|
|
454
|
+
if (editorBodyRef.current) {
|
|
455
|
+
removeDecorationDragOverElement(editorBodyRef.current);
|
|
456
|
+
if (e.dataTransfer.getData('uri')) {
|
|
457
|
+
const uri = new URI(e.dataTransfer.getData('uri'));
|
|
458
|
+
let sourceGroup: EditorGroup | undefined;
|
|
459
|
+
if (e.dataTransfer.getData('uri-source-group')) {
|
|
460
|
+
sourceGroup = editorService.getEditorGroup(e.dataTransfer.getData('uri-source-group'));
|
|
461
|
+
}
|
|
462
|
+
group.dropUri(uri, getDragOverPosition(e.nativeEvent, editorBodyRef.current), sourceGroup);
|
|
463
|
+
}
|
|
464
|
+
if (e.dataTransfer.files.length > 0) {
|
|
465
|
+
eventBus.fire(
|
|
466
|
+
new EditorGroupFileDropEvent({
|
|
467
|
+
group,
|
|
468
|
+
files: e.dataTransfer.files,
|
|
469
|
+
position: getDragOverPosition(e.nativeEvent, editorBodyRef.current),
|
|
470
|
+
}),
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}}
|
|
475
|
+
>
|
|
476
|
+
{group.currentResource && <EditorSideView side={'top'} resource={group.currentResource}></EditorSideView>}
|
|
477
|
+
{!editorHasNoTab && <NavigationBar editorGroup={group} />}
|
|
478
|
+
<div className={styles.kt_editor_components}>
|
|
479
|
+
<div
|
|
480
|
+
className={cls({
|
|
481
|
+
[styles_kt_editor_component]: true,
|
|
482
|
+
[styles.kt_hidden]: !group.currentOpenType || group.currentOpenType.type !== EditorOpenType.component,
|
|
483
|
+
})}
|
|
484
|
+
>
|
|
485
|
+
{components}
|
|
486
|
+
</div>
|
|
487
|
+
<div
|
|
488
|
+
className={cls({
|
|
489
|
+
[styles.kt_editor_code_editor]: true,
|
|
490
|
+
[styles_kt_editor_component]: true,
|
|
491
|
+
[styles.kt_hidden]: !group.currentOpenType || group.currentOpenType.type !== EditorOpenType.code,
|
|
492
|
+
})}
|
|
493
|
+
ref={codeEditorRef}
|
|
494
|
+
/>
|
|
495
|
+
<div
|
|
496
|
+
className={cls(styles.kt_editor_diff_editor, styles_kt_editor_component, {
|
|
497
|
+
[styles.kt_hidden]: !group.currentOpenType || group.currentOpenType.type !== EditorOpenType.diff,
|
|
498
|
+
})}
|
|
499
|
+
ref={diffEditorRef}
|
|
500
|
+
/>
|
|
501
|
+
<div
|
|
502
|
+
className={cls(styles.kt_editor_diff_3_editor, styles_kt_editor_component, {
|
|
503
|
+
[styles.kt_hidden]: !group.currentOpenType || group.currentOpenType.type !== EditorOpenType.mergeEditor,
|
|
504
|
+
})}
|
|
505
|
+
ref={mergeEditorRef}
|
|
506
|
+
/>
|
|
462
507
|
</div>
|
|
463
|
-
<
|
|
464
|
-
className={cls({
|
|
465
|
-
[styles.kt_editor_code_editor]: true,
|
|
466
|
-
[styles_kt_editor_component]: true,
|
|
467
|
-
[styles.kt_hidden]: !group.currentOpenType || group.currentOpenType.type !== EditorOpenType.code,
|
|
468
|
-
})}
|
|
469
|
-
ref={codeEditorRef}
|
|
470
|
-
/>
|
|
471
|
-
<div
|
|
472
|
-
className={cls(styles.kt_editor_diff_editor, styles_kt_editor_component, {
|
|
473
|
-
[styles.kt_hidden]: !group.currentOpenType || group.currentOpenType.type !== EditorOpenType.diff,
|
|
474
|
-
})}
|
|
475
|
-
ref={diffEditorRef}
|
|
476
|
-
/>
|
|
477
|
-
<div
|
|
478
|
-
className={cls(styles.kt_editor_diff_3_editor, styles_kt_editor_component, {
|
|
479
|
-
[styles.kt_hidden]: !group.currentOpenType || group.currentOpenType.type !== EditorOpenType.mergeEditor,
|
|
480
|
-
})}
|
|
481
|
-
ref={mergeEditorRef}
|
|
482
|
-
/>
|
|
508
|
+
{group.currentResource && <EditorSideView side={'bottom'} resource={group.currentResource}></EditorSideView>}
|
|
483
509
|
</div>
|
|
484
|
-
|
|
485
|
-
</div>
|
|
510
|
+
</EditorContext.Provider>
|
|
486
511
|
);
|
|
487
512
|
});
|
|
488
513
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { URI, useInjectable } from '@opensumi/ide-core-browser';
|
|
4
|
+
|
|
5
|
+
import { IEditorDocumentModelService } from '../doc-model/types';
|
|
6
|
+
import { IEditorDocumentModelRef } from '../types';
|
|
7
|
+
|
|
8
|
+
export function useEditorDocumentModelRef(uri: URI) {
|
|
9
|
+
const documentService: IEditorDocumentModelService = useInjectable(IEditorDocumentModelService);
|
|
10
|
+
const [ref, setRef] = useState<IEditorDocumentModelRef | null>(null);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const run = () => {
|
|
14
|
+
const ref = documentService.getModelReference(uri);
|
|
15
|
+
if (ref) {
|
|
16
|
+
setRef(ref);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const toDispose = documentService.onDocumentModelCreated(uri.toString(), () => {
|
|
21
|
+
run();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
run();
|
|
25
|
+
return () => {
|
|
26
|
+
toDispose.dispose();
|
|
27
|
+
if (ref) {
|
|
28
|
+
ref.dispose();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}, [uri]);
|
|
32
|
+
|
|
33
|
+
return ref;
|
|
34
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { IContextKeyService, useInjectable } from '@opensumi/ide-core-browser';
|
|
4
|
+
|
|
5
|
+
const contextKey = 'git.mergeChangesObj';
|
|
6
|
+
|
|
7
|
+
export const gitMergeChangesSet = new Set([contextKey]);
|
|
8
|
+
|
|
9
|
+
export function useInMergeChanges(uriStr: string) {
|
|
10
|
+
const contextKeyService = useInjectable<IContextKeyService>(IContextKeyService);
|
|
11
|
+
|
|
12
|
+
const [inMergeChanges, setInMergeChanges] = useState(false);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
function run() {
|
|
16
|
+
const mergeChanges = contextKeyService.getValue<Record<string, boolean>>(contextKey) || {};
|
|
17
|
+
setInMergeChanges(mergeChanges[uriStr] || false);
|
|
18
|
+
}
|
|
19
|
+
run();
|
|
20
|
+
|
|
21
|
+
const disposed = contextKeyService.onDidChangeContext(({ payload }) => {
|
|
22
|
+
if (payload.affectsSome(gitMergeChangesSet)) {
|
|
23
|
+
run();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return () => disposed.dispose();
|
|
27
|
+
}, [uriStr]);
|
|
28
|
+
|
|
29
|
+
return inMergeChanges;
|
|
30
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/* ---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
// Some code copied and modified from https://github.com/microsoft/vscode/blob/main/extensions/merge-conflict/src/mergeConflictParser.ts
|
|
6
|
+
|
|
7
|
+
import { Injectable } from '@opensumi/di';
|
|
8
|
+
import { Disposable, LRUCache, uuid } from '@opensumi/ide-core-common';
|
|
9
|
+
import * as monaco from '@opensumi/ide-monaco';
|
|
10
|
+
|
|
11
|
+
import { ICacheDocumentMergeConflict, IDocumentMergeConflictDescriptor, IMergeRegion } from './types';
|
|
12
|
+
|
|
13
|
+
const startHeaderMarker = '<<<<<<<';
|
|
14
|
+
const commonAncestorsMarker = '|||||||';
|
|
15
|
+
const splitterMarker = '=======';
|
|
16
|
+
const endFooterMarker = '>>>>>>>';
|
|
17
|
+
|
|
18
|
+
interface IScanMergedConflict {
|
|
19
|
+
startHeader: TextLine;
|
|
20
|
+
commonAncestors: TextLine[];
|
|
21
|
+
splitter?: TextLine;
|
|
22
|
+
endFooter?: TextLine;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface IConflictCache {
|
|
26
|
+
id: string;
|
|
27
|
+
range: monaco.Range;
|
|
28
|
+
text: string;
|
|
29
|
+
isResolved: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class TextLine {
|
|
33
|
+
lineNumber: number;
|
|
34
|
+
text: string;
|
|
35
|
+
range: monaco.Range;
|
|
36
|
+
rangeIncludingLineBreak: monaco.Range;
|
|
37
|
+
firstNonWhitespaceCharacterIndex: number;
|
|
38
|
+
isEmptyOrWhitespace: boolean;
|
|
39
|
+
constructor(document: monaco.editor.ITextModel, line: number) {
|
|
40
|
+
if (typeof line !== 'number' || line <= 0 || line > document.getLineCount()) {
|
|
41
|
+
throw new Error('Illegal value for `line`');
|
|
42
|
+
}
|
|
43
|
+
this.text = document.getLineContent(line);
|
|
44
|
+
this.firstNonWhitespaceCharacterIndex = /^(\s*)/.exec(this.text)![1].length;
|
|
45
|
+
this.range = new monaco.Range(line, 1, line, this.text.length + 1);
|
|
46
|
+
this.rangeIncludingLineBreak =
|
|
47
|
+
line <= document.getLineCount() ? new monaco.Range(line, 1, line + 1, 1) : this.range;
|
|
48
|
+
this.lineNumber = line;
|
|
49
|
+
this.isEmptyOrWhitespace = this.firstNonWhitespaceCharacterIndex === this.text.length;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Injectable()
|
|
54
|
+
export class MergeConflictParser extends Disposable {
|
|
55
|
+
cache = new LRUCache<string, DocumentMergeConflict[]>(100);
|
|
56
|
+
|
|
57
|
+
private _conflictTextCaches = new Map<string, string>();
|
|
58
|
+
|
|
59
|
+
private _conflictRangeCaches = new Map<string, IConflictCache[]>();
|
|
60
|
+
|
|
61
|
+
private static createCacheKey(document: monaco.editor.ITextModel) {
|
|
62
|
+
return `${document.uri.toString()}-${document.getAlternativeVersionId()}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
scanDocument(document: monaco.editor.ITextModel) {
|
|
66
|
+
const cacheKey = MergeConflictParser.createCacheKey(document);
|
|
67
|
+
if (this.cache.has(cacheKey)) {
|
|
68
|
+
return this.cache.get(cacheKey)!;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Scan each line in the document, we already know there is at least a <<<<<<< and
|
|
72
|
+
// >>>>>> marker within the document, we need to group these into conflict ranges.
|
|
73
|
+
// We initially build a scan match, that references the lines of the header, splitter
|
|
74
|
+
// and footer. This is then converted into a full descriptor containing all required
|
|
75
|
+
// ranges.
|
|
76
|
+
|
|
77
|
+
let currentConflict: IScanMergedConflict | null = null;
|
|
78
|
+
const conflictDescriptors: IDocumentMergeConflictDescriptor[] = [];
|
|
79
|
+
const cacheConflictDescriptors = this._conflictTextCaches.get(document.uri.toString());
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < document.getLineCount(); i++) {
|
|
82
|
+
const line = new TextLine(document, i + 1);
|
|
83
|
+
// Ignore empty lines
|
|
84
|
+
if (!line || line.isEmptyOrWhitespace) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Is this a start line? <<<<<<<
|
|
89
|
+
if (line.text.startsWith(startHeaderMarker)) {
|
|
90
|
+
if (currentConflict !== null) {
|
|
91
|
+
// Error, we should not see a startMarker before we've seen an endMarker
|
|
92
|
+
currentConflict = null;
|
|
93
|
+
|
|
94
|
+
// Give up parsing, anything matched up this to this point will be decorated
|
|
95
|
+
// anything after will not
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Create a new conflict starting at this line
|
|
100
|
+
currentConflict = { startHeader: line, commonAncestors: [] };
|
|
101
|
+
}
|
|
102
|
+
// Are we within a conflict block and is this a common ancestors marker? |||||||
|
|
103
|
+
else if (currentConflict && !currentConflict.splitter && line.text.startsWith(commonAncestorsMarker)) {
|
|
104
|
+
currentConflict.commonAncestors.push(line);
|
|
105
|
+
}
|
|
106
|
+
// Are we within a conflict block and is this a splitter? =======
|
|
107
|
+
else if (currentConflict && !currentConflict.splitter && line.text === splitterMarker) {
|
|
108
|
+
currentConflict.splitter = line;
|
|
109
|
+
}
|
|
110
|
+
// Are we within a conflict block and is this a footer? >>>>>>>
|
|
111
|
+
else if (currentConflict && line.text.startsWith(endFooterMarker)) {
|
|
112
|
+
currentConflict.endFooter = line;
|
|
113
|
+
|
|
114
|
+
// Create a full descriptor from the lines that we matched. This can return
|
|
115
|
+
// null if the descriptor could not be completed.
|
|
116
|
+
const completeDescriptor = scanItemTolMergeConflictDescriptor(document, currentConflict);
|
|
117
|
+
|
|
118
|
+
if (completeDescriptor !== null) {
|
|
119
|
+
conflictDescriptors.push(completeDescriptor);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Reset the current conflict to be empty, so we can match the next
|
|
123
|
+
// starting header marker.
|
|
124
|
+
currentConflict = null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!cacheConflictDescriptors && conflictDescriptors.length) {
|
|
128
|
+
this._conflictTextCaches.set(document.uri.toString(), document.getValue());
|
|
129
|
+
const conflictRanges: IConflictCache[] = [];
|
|
130
|
+
conflictDescriptors.filter(Boolean).forEach((descriptor) => {
|
|
131
|
+
const range = descriptor.range;
|
|
132
|
+
conflictRanges.push({
|
|
133
|
+
id: uuid(),
|
|
134
|
+
range,
|
|
135
|
+
text: document.getValueInRange(range),
|
|
136
|
+
isResolved: false,
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
this._conflictRangeCaches.set(document.uri.toString(), conflictRanges);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const result = conflictDescriptors?.filter(Boolean).map((descriptor) => new DocumentMergeConflict(descriptor));
|
|
143
|
+
this.cache.set(cacheKey, result);
|
|
144
|
+
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
getConflictText(uri: string) {
|
|
148
|
+
return this._conflictTextCaches.get(uri);
|
|
149
|
+
}
|
|
150
|
+
getAllConflictsByUri(uri: string) {
|
|
151
|
+
return this._conflictRangeCaches.get(uri);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
getAllConflicts() {
|
|
155
|
+
return this._conflictRangeCaches;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
setConflictResolved(uri: string, id: string) {
|
|
159
|
+
const conflictRanges = this._conflictRangeCaches.get(uri);
|
|
160
|
+
if (conflictRanges) {
|
|
161
|
+
const conflictRange = conflictRanges.find((item) => item.id === id);
|
|
162
|
+
if (conflictRange) {
|
|
163
|
+
conflictRange.isResolved = true;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
deleteConflictText(uri: string) {
|
|
169
|
+
this._conflictTextCaches.delete(uri);
|
|
170
|
+
}
|
|
171
|
+
dispose() {
|
|
172
|
+
this._conflictTextCaches.clear();
|
|
173
|
+
this._conflictRangeCaches.clear();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function scanItemTolMergeConflictDescriptor(
|
|
178
|
+
document: monaco.editor.ITextModel,
|
|
179
|
+
scanned: IScanMergedConflict,
|
|
180
|
+
): IDocumentMergeConflictDescriptor | null {
|
|
181
|
+
// Validate we have all the required lines within the scan item.
|
|
182
|
+
if (!scanned.startHeader || !scanned.splitter || !scanned.endFooter) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const tokenAfterCurrentBlock: TextLine = scanned.commonAncestors[0] || scanned.splitter;
|
|
187
|
+
|
|
188
|
+
// Assume that descriptor.current.header, descriptor.incoming.header and descriptor.splitter
|
|
189
|
+
// have valid ranges, fill in content and total ranges from these parts.
|
|
190
|
+
// NOTE: We need to shift the decorator range back one character so the splitter does not end up with
|
|
191
|
+
// two decoration colors (current and splitter), if we take the new line from the content into account
|
|
192
|
+
// the decorator will wrap to the next line.
|
|
193
|
+
return {
|
|
194
|
+
current: {
|
|
195
|
+
header: scanned.startHeader.range,
|
|
196
|
+
decoratorContent: new monaco.Range(
|
|
197
|
+
scanned.startHeader.rangeIncludingLineBreak.endLineNumber,
|
|
198
|
+
scanned.startHeader.rangeIncludingLineBreak.endColumn,
|
|
199
|
+
shiftBackOneCharacter(
|
|
200
|
+
document,
|
|
201
|
+
tokenAfterCurrentBlock.range.getStartPosition(),
|
|
202
|
+
scanned.startHeader.rangeIncludingLineBreak.getEndPosition(),
|
|
203
|
+
).lineNumber,
|
|
204
|
+
shiftBackOneCharacter(
|
|
205
|
+
document,
|
|
206
|
+
tokenAfterCurrentBlock.range.getStartPosition(),
|
|
207
|
+
scanned.startHeader.rangeIncludingLineBreak.getEndPosition(),
|
|
208
|
+
).column,
|
|
209
|
+
),
|
|
210
|
+
// Current content is range between header (shifted for linebreak) and splitter or common ancestors mark start
|
|
211
|
+
content: new monaco.Range(
|
|
212
|
+
scanned.startHeader.rangeIncludingLineBreak.endLineNumber,
|
|
213
|
+
scanned.startHeader.rangeIncludingLineBreak.endColumn,
|
|
214
|
+
tokenAfterCurrentBlock.range.startLineNumber,
|
|
215
|
+
tokenAfterCurrentBlock.range.startColumn,
|
|
216
|
+
),
|
|
217
|
+
name: scanned.startHeader.text.substring(startHeaderMarker.length + 1),
|
|
218
|
+
},
|
|
219
|
+
commonAncestors: scanned.commonAncestors.map((currentTokenLine, index, commonAncestors) => {
|
|
220
|
+
const nextTokenLine = commonAncestors[index + 1] || scanned.splitter;
|
|
221
|
+
return {
|
|
222
|
+
header: currentTokenLine.range,
|
|
223
|
+
decoratorContent: new monaco.Range(
|
|
224
|
+
currentTokenLine.rangeIncludingLineBreak.endLineNumber,
|
|
225
|
+
currentTokenLine.rangeIncludingLineBreak.endColumn,
|
|
226
|
+
|
|
227
|
+
shiftBackOneCharacter(
|
|
228
|
+
document,
|
|
229
|
+
nextTokenLine.range.getStartPosition(),
|
|
230
|
+
currentTokenLine.rangeIncludingLineBreak.getEndPosition(),
|
|
231
|
+
).lineNumber,
|
|
232
|
+
shiftBackOneCharacter(
|
|
233
|
+
document,
|
|
234
|
+
nextTokenLine.range.getStartPosition(),
|
|
235
|
+
currentTokenLine.rangeIncludingLineBreak.getEndPosition(),
|
|
236
|
+
).lineNumber,
|
|
237
|
+
),
|
|
238
|
+
// Each common ancestors block is range between one common ancestors token
|
|
239
|
+
// (shifted for linebreak) and start of next common ancestors token or splitter
|
|
240
|
+
content: new monaco.Range(
|
|
241
|
+
currentTokenLine.rangeIncludingLineBreak.endLineNumber,
|
|
242
|
+
currentTokenLine.rangeIncludingLineBreak.endColumn,
|
|
243
|
+
nextTokenLine.range.startLineNumber,
|
|
244
|
+
nextTokenLine.range.startColumn,
|
|
245
|
+
),
|
|
246
|
+
name: currentTokenLine.text.substring(commonAncestorsMarker.length + 1),
|
|
247
|
+
};
|
|
248
|
+
}),
|
|
249
|
+
splitter: scanned.splitter.range,
|
|
250
|
+
incoming: {
|
|
251
|
+
header: scanned.endFooter.range,
|
|
252
|
+
decoratorContent: new monaco.Range(
|
|
253
|
+
scanned.splitter.rangeIncludingLineBreak.endLineNumber,
|
|
254
|
+
scanned.splitter.rangeIncludingLineBreak.endColumn,
|
|
255
|
+
shiftBackOneCharacter(
|
|
256
|
+
document,
|
|
257
|
+
scanned.endFooter.range.getStartPosition(),
|
|
258
|
+
scanned.splitter.rangeIncludingLineBreak.getEndPosition(),
|
|
259
|
+
).lineNumber,
|
|
260
|
+
shiftBackOneCharacter(
|
|
261
|
+
document,
|
|
262
|
+
scanned.endFooter.range.getStartPosition(),
|
|
263
|
+
scanned.splitter.rangeIncludingLineBreak.getEndPosition(),
|
|
264
|
+
).column,
|
|
265
|
+
),
|
|
266
|
+
// Incoming content is range between splitter (shifted for linebreak) and footer start
|
|
267
|
+
content: new monaco.Range(
|
|
268
|
+
scanned.splitter.rangeIncludingLineBreak.endLineNumber,
|
|
269
|
+
scanned.splitter.rangeIncludingLineBreak.endColumn,
|
|
270
|
+
scanned.endFooter.range.startLineNumber,
|
|
271
|
+
scanned.endFooter.range.startColumn,
|
|
272
|
+
),
|
|
273
|
+
name: scanned.endFooter.text.substring(endFooterMarker.length + 1),
|
|
274
|
+
},
|
|
275
|
+
// Entire range is between current header start and incoming header end (including line break)
|
|
276
|
+
range: new monaco.Range(
|
|
277
|
+
scanned.startHeader.range.startLineNumber,
|
|
278
|
+
scanned.startHeader.range.startColumn,
|
|
279
|
+
scanned.endFooter.range.endLineNumber,
|
|
280
|
+
scanned.endFooter.range.endColumn,
|
|
281
|
+
),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function shiftBackOneCharacter(
|
|
286
|
+
document: monaco.editor.ITextModel,
|
|
287
|
+
range: monaco.Position,
|
|
288
|
+
unlessEqual: monaco.Position,
|
|
289
|
+
): monaco.Position {
|
|
290
|
+
if (range.equals(unlessEqual)) {
|
|
291
|
+
return range;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let line = range.lineNumber;
|
|
295
|
+
let character = range.column - 1;
|
|
296
|
+
|
|
297
|
+
if (character < 0) {
|
|
298
|
+
line--;
|
|
299
|
+
character = new TextLine(document, line).range.endColumn;
|
|
300
|
+
}
|
|
301
|
+
return new monaco.Position(line, character);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export class DocumentMergeConflict implements ICacheDocumentMergeConflict {
|
|
305
|
+
public range: monaco.Range;
|
|
306
|
+
public current: IMergeRegion;
|
|
307
|
+
public incoming: IMergeRegion;
|
|
308
|
+
public commonAncestors: IMergeRegion[];
|
|
309
|
+
public splitter: monaco.Range;
|
|
310
|
+
public incomingContent: string;
|
|
311
|
+
public currentContent: string;
|
|
312
|
+
public bothContent: string;
|
|
313
|
+
public aiContent?: string;
|
|
314
|
+
public defaultContent: string;
|
|
315
|
+
private applied = false;
|
|
316
|
+
constructor(descriptor: IDocumentMergeConflictDescriptor) {
|
|
317
|
+
this.range = descriptor.range;
|
|
318
|
+
this.current = descriptor.current;
|
|
319
|
+
this.incoming = descriptor.incoming;
|
|
320
|
+
this.commonAncestors = descriptor.commonAncestors;
|
|
321
|
+
this.splitter = descriptor.splitter;
|
|
322
|
+
}
|
|
323
|
+
}
|