Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1520bdb
feat: support multifile diff editor
ensorrow Apr 27, 2025
f4a114a
feat: implement ResourceLabel
ensorrow Apr 27, 2025
4f23a64
refactor: reorganize multi-diff related files and imports
ensorrow Apr 28, 2025
115c26f
chore: rename to multi-diff
ensorrow Apr 28, 2025
9819714
refactor: use IMultiDiffSourceResolverService to update resources
ensorrow Apr 28, 2025
74d2f2c
chore: use class MultiDiffEditorItem
ensorrow Apr 28, 2025
a1ad16c
feat: support default multi scheme
ensorrow Apr 28, 2025
9b4c904
feat: implement _workbench.openMultiDiffEditor
ensorrow Apr 28, 2025
0484e8b
fix: empty uri
ensorrow Apr 28, 2025
3b41908
feat: implement multi diff for apply
ensorrow Apr 28, 2025
d28fb9d
fix: missing register
ensorrow Apr 28, 2025
44de106
chore: use side to determine content source
ensorrow Apr 29, 2025
290385f
feat: reuse multi-diff-editor
ensorrow Apr 29, 2025
9c99f47
feat: support multi-diff viewState
ensorrow Apr 29, 2025
55db35a
feat: support multi-diff dirty
ensorrow Apr 29, 2025
aa5cd45
feat: support multi-diff command
ensorrow Apr 29, 2025
d916e08
feat: optimize styles of viewChanges
ensorrow Apr 29, 2025
c36eb92
refactor: update multi-diff source and editor handling
ensorrow May 7, 2025
a6f2284
refactor: update file paths in status bar contribution
ensorrow May 7, 2025
a55ed76
refactor: use doLayoutEditors to avoid skip
ensorrow May 7, 2025
e5fa5ee
fix: update filter condition for session code blocks
ensorrow May 7, 2025
1aefb9c
refactor: update ChangeList and MultiDiffEditor components
ensorrow May 7, 2025
08b6d70
refactor: update URI creation and handling in multi-diff components
ensorrow May 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion packages/ai-native/src/browser/ai-core.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
ChatFeatureRegistryToken,
ChatRenderRegistryToken,
CommandService,
IDisposable,
InlineChatFeatureRegistryToken,
IntelligentCompletionsRegistryToken,
MCPConfigServiceToken,
Expand All @@ -68,9 +69,12 @@ import { IEditor, WorkbenchEditorService } from '@opensumi/ide-editor';
import {
BrowserEditorContribution,
EditorComponentRegistry,
IEditorDocumentModelContentRegistry,
IEditorFeatureRegistry,
MultiDiffSourceContribution,
} from '@opensumi/ide-editor/lib/browser';
import { WorkbenchEditorServiceImpl } from '@opensumi/ide-editor/lib/browser/workbench-editor.service';
import { IMultiDiffSourceResolverService } from '@opensumi/ide-editor/lib/common/multi-diff';
import { IMainLayoutService } from '@opensumi/ide-main-layout';
import { ISettingRegistry, SettingContribution } from '@opensumi/ide-preferences';
import { EditorContributionInstantiation } from '@opensumi/monaco-editor-core/esm/vs/editor/browser/editorExtensions';
Expand All @@ -95,7 +99,9 @@ import {
import { MCPServerDescription, MCPServersEnabledKey } from '../common/mcp-server-manager';
import { MCP_SERVER_TYPE } from '../common/types';

import { ChatEditSchemeDocumentProvider } from './chat/chat-edit-resource';
import { ChatManagerService } from './chat/chat-manager.service';
import { ChatMultiDiffResolver } from './chat/chat-multi-diff-source';
import { ChatProxyService } from './chat/chat-proxy.service';
import { ChatInternalService } from './chat/chat.internal.service';
import { AIChatView } from './chat/chat.view';
Expand Down Expand Up @@ -151,6 +157,7 @@ export const INLINE_DIFF_MANAGER_WIDGET_ID = 'inline-diff-manager-widget';
ComponentContribution,
SlotRendererContribution,
MonacoContribution,
MultiDiffSourceContribution,
)
export class AINativeBrowserContribution
implements
Expand All @@ -161,7 +168,8 @@ export class AINativeBrowserContribution
KeybindingContribution,
ComponentContribution,
SlotRendererContribution,
MonacoContribution
MonacoContribution,
MultiDiffSourceContribution
{
@Autowired(AppConfig)
private readonly appConfig: AppConfig;
Expand Down Expand Up @@ -271,10 +279,24 @@ export class AINativeBrowserContribution
@Autowired(StorageProvider)
private readonly storageProvider: StorageProvider;

@Autowired()
private readonly chatEditResourceProvider: ChatEditSchemeDocumentProvider;

@Autowired()
private readonly chatMultiDiffResolver: ChatMultiDiffResolver;

constructor() {
this.registerFeature();
}

registerMultiDiffSourceResolver(resolverService: IMultiDiffSourceResolverService): IDisposable {
return resolverService.registerResolver(this.chatMultiDiffResolver);
}

registerEditorDocumentModelContentProvider(registry: IEditorDocumentModelContentRegistry): void {
registry.registerEditorDocumentModelContentProvider(this.chatEditResourceProvider);
}

async initialize() {
const { supportsChatAssistant } = this.aiNativeConfigService.capabilities;

Expand Down
53 changes: 53 additions & 0 deletions packages/ai-native/src/browser/chat/chat-edit-resource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Autowired, Injectable } from '@opensumi/di';
import { AppConfig, Emitter, Event, IApplicationService, PreferenceService, URI } from '@opensumi/ide-core-browser';
import { WorkbenchEditorService } from '@opensumi/ide-editor';
import {
IEditorDocumentModelContentProvider,
IEditorDocumentModelService,
} from '@opensumi/ide-editor/lib/browser/doc-model/types';

import { BaseApplyService } from '../mcp/base-apply.service';

@Injectable()
export class ChatEditSchemeDocumentProvider implements IEditorDocumentModelContentProvider {
@Autowired(IEditorDocumentModelService)
editorDocumentModelService: IEditorDocumentModelService;

@Autowired(WorkbenchEditorService)
workbenchEditorService: WorkbenchEditorService;

@Autowired(AppConfig)
protected readonly appConfig: AppConfig;

@Autowired(IApplicationService)
protected readonly applicationService: IApplicationService;

@Autowired(BaseApplyService)
private readonly baseApplyService: BaseApplyService;

private _onDidChangeContent: Emitter<URI> = new Emitter();

public onDidChangeContent: Event<URI> = this._onDidChangeContent.event;

@Autowired(PreferenceService)
protected readonly preferenceService: PreferenceService;

handlesScheme(scheme: string): boolean {
return scheme === BaseApplyService.CHAT_EDITING_SOURCE_RESOLVER_SCHEME;
}

async provideEditorDocumentModelContent(uri: URI, encoding?: string | undefined): Promise<string> {
// Get the content from the base apply service based on the uri query parameters
const { id, side } = uri.getParsedQuery();
const codeBlocks = this.baseApplyService.getSessionCodeBlocks();
const codeBlock = codeBlocks.find((block) => block.toolCallId === id);
const content = side === 'left' ? codeBlock?.originalCode : codeBlock?.updatedCode;
return content || '';
}

isReadonly(uri: URI): boolean {
return true;
}

onDidDisposeModel() {}
}
95 changes: 95 additions & 0 deletions packages/ai-native/src/browser/chat/chat-multi-diff-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Autowired, Injectable } from '@opensumi/di';
import { AppConfig, Event, URI, path } from '@opensumi/ide-core-browser';
import {
IMultiDiffSourceResolver,
IResolvedMultiDiffSource,
MultiDiffEditorItem,
} from '@opensumi/ide-editor/lib/common/multi-diff';
import { IValueWithChangeEvent } from '@opensumi/monaco-editor-core/esm/vs/base/common/event';

import { BaseApplyService } from '../mcp/base-apply.service';

@Injectable()
export class ChatMultiDiffResolver implements IMultiDiffSourceResolver {
static readonly CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME = 'chat-editing-multi-diff-source';

@Autowired(BaseApplyService)
private readonly baseApplyService: BaseApplyService;

@Autowired(AppConfig)
private readonly appConfig: AppConfig;

canHandleUri(uri: URI): boolean {
return uri.scheme === ChatMultiDiffResolver.CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME;
}

resolveDiffSource(uri: URI): Promise<IResolvedMultiDiffSource> {
return Promise.resolve(new ChatMultiDiffSource(this.baseApplyService, this.appConfig));
}
}

const getResourceUri = (filePath: string, id: string, side: 'left' | 'right') =>
URI.from({
scheme: BaseApplyService.CHAT_EDITING_SOURCE_RESOLVER_SCHEME,
path: filePath,
query: URI.stringifyQuery({ id, side }),
});

export class ChatMultiDiffSource implements IResolvedMultiDiffSource {
constructor(private readonly baseApplyService: BaseApplyService, private readonly appConfig: AppConfig) {}

readonly resources: IValueWithChangeEvent<readonly MultiDiffEditorItem[]> = (() => {
const applyService = this.baseApplyService;
const appConfig = this.appConfig;
return {
get value(): readonly MultiDiffEditorItem[] {
return applyService
.getSessionCodeBlocks()
.filter((block) => block.status === 'success' || block.status === 'pending')
.reduce(
(acc, cur) => {
const existingFile = acc.find((item) => item.relativePath === cur.relativePath);
if (existingFile) {
// Update versions and block IDs if needed
if (cur.version < existingFile.oldVersion) {
existingFile.oldVersion = cur.version;
existingFile.oldBlockId = cur.toolCallId;
}
if (cur.version > existingFile.newVersion) {
existingFile.newVersion = cur.version;
existingFile.newBlockId = cur.toolCallId;
}
} else {
// Add new file entry
acc.push({
relativePath: cur.relativePath,
oldBlockId: cur.toolCallId,
newBlockId: cur.toolCallId,
oldVersion: cur.version,
newVersion: cur.version,
});
}
return acc;
},
[] as {
relativePath: string;
oldBlockId: string;
newBlockId: string;
oldVersion: number;
newVersion: number;
}[],
)
.map((block) => {
const filePath = path.join(appConfig.workspaceDir, block.relativePath);
return new MultiDiffEditorItem(
getResourceUri(filePath, block.oldBlockId, 'left'),
getResourceUri(filePath, block.newBlockId, 'right'),
URI.file(filePath),
);
});
},
// 这里event类型错误不影响
onDidChange: this.baseApplyService.onCodeBlockUpdate as unknown as Event<void>,
};
})();
}
40 changes: 36 additions & 4 deletions packages/ai-native/src/browser/components/ChangeList.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React, { useMemo, useState } from 'react';

import { Button, Icon } from '@opensumi/ide-components';
import { LabelService, URI, useInjectable } from '@opensumi/ide-core-browser';
import { LabelService, URI, localize, useInjectable } from '@opensumi/ide-core-browser';
import { WorkbenchEditorService } from '@opensumi/ide-editor';

import { CodeBlockStatus } from '../../common/types';
import { ChatMultiDiffResolver } from '../chat/chat-multi-diff-source';

import { ApplyStatus } from './ApplyStatus';
import styles from './change-list.module.less';
Expand All @@ -25,6 +27,7 @@ interface FileListDisplayProps {
export const FileListDisplay: React.FC<FileListDisplayProps> = (props) => {
const { files, onFileClick, onRejectAll, onAcceptAll } = props;
const [isExpanded, setIsExpanded] = useState(true);
const editorService = useInjectable<WorkbenchEditorService>(WorkbenchEditorService);
const labelService = useInjectable<LabelService>(LabelService);
const fileIcons = useMemo(
() =>
Expand All @@ -49,6 +52,32 @@ export const FileListDisplay: React.FC<FileListDisplayProps> = (props) => {
setIsExpanded(!isExpanded);
};

const renderViewChanges = (totalFiles: number) => {
if (!totalFiles) {
return null;
}
return (
<span
className={styles.viewChanges}
onClick={(e) => {
e.stopPropagation();
editorService.open(
URI.from({
scheme: ChatMultiDiffResolver.CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME,
path: 'chat-editing-multi-diff-source',
}),
{
label: localize('aiNative.chat.view-changes'),
},
);
}}
>
<Icon icon='unorderedlist' size='small' />
{localize('aiNative.chat.view-changes')}
</span>
);
};

const renderChangeStats = (additions: number, deletions: number) => {
const parts: React.ReactNode[] = [];

Expand Down Expand Up @@ -84,10 +113,13 @@ export const FileListDisplay: React.FC<FileListDisplayProps> = (props) => {

return (
<div className={styles.container}>
<div className={styles.header} onClick={handleToggle}>
<div className={styles.header}>
<span className={styles.title}>
<button className={styles.toggleButton}>{isExpanded ? <Icon icon='down' /> : <Icon icon='right' />}</button>
Changed Files ({totalFiles}) {renderChangeStats(totalChanges.additions, totalChanges.deletions)}
<button className={styles.toggleButton} onClick={handleToggle}>
{isExpanded ? <Icon icon='down' /> : <Icon icon='right' />} Changed Files({totalFiles})
</button>
{renderChangeStats(totalChanges.additions, totalChanges.deletions)}
{renderViewChanges(files.length)}
</span>
{files.some((file) => file.status === 'pending') && (
<div className={styles.actions}>
Expand Down
27 changes: 21 additions & 6 deletions packages/ai-native/src/browser/components/change-list.module.less
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,32 @@
color: var(--descriptionForeground);
}

.title:hover {
.toggleButton:hover {
color: var(--design-text-highlightForeground);
.toggleButton :global(.kt-icon) {
color: var(--design-text-highlightForeground);
transition: color 0.2s ease-in-out;
}
transition: color 0.2s ease-in-out;
}

.toggleButton {
background: none;
border: none;
cursor: pointer;
padding: 0;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
gap: 2px;
:global(.kt-icon) {
color: inherit;
}
}

.title {
gap: 6px;
transition: color 0.2s ease-in-out;
display: flex;
align-items: center;
}

.fileList {
Expand Down Expand Up @@ -126,3 +127,17 @@
.collapsed {
display: none;
}

.viewChanges {
margin-left: 6px;
display: inline-flex;
align-items: center;
gap: 3px;
:global(.kt-icon) {
color: inherit;
}
&:hover {
color: var(--design-text-highlightForeground);
transition: color 0.2s ease-in-out;
}
}
2 changes: 2 additions & 0 deletions packages/ai-native/src/browser/mcp/base-apply.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import { BaseInlineDiffPreviewer, InlineDiffController, LiveInlineDiffPreviewer
import type { BaseInlineStreamDiffHandler } from '../widget/inline-stream-diff/inline-stream-diff.handler';

export abstract class BaseApplyService extends WithEventBus {
static readonly CHAT_EDITING_SOURCE_RESOLVER_SCHEME = 'chat-editing-source';

@Autowired(IChatInternalService)
protected chatInternalService: ChatInternalService;

Expand Down
2 changes: 2 additions & 0 deletions packages/core-browser/src/common/common.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ export namespace EDITOR_COMMANDS {
export const API_OPEN_DIFF_EDITOR_COMMAND_ID = '_workbench.diff';
export const API_OPEN_WITH_EDITOR_COMMAND_ID = '_workbench.openWith';

export const VSCODE_OPEN_MULTI_DIFF_EDITOR_COMMAND_ID = '_workbench.openMultiDiffEditor';

export const NEW_UNTITLED_FILE: Command = {
id: 'file.new.untitled',
category: CATEGORY,
Expand Down
6 changes: 6 additions & 0 deletions packages/core-browser/src/monaco/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BasicEvent, Event, IDisposable, IJSONSchema } from '@opensumi/ide-core-common';
import { EditorContributionInstantiation } from '@opensumi/monaco-editor-core/esm/vs/editor/browser/editorExtensions';
import { MultiDiffEditorWidget } from '@opensumi/monaco-editor-core/esm/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidget';
import { SyncDescriptor } from '@opensumi/monaco-editor-core/esm/vs/platform/instantiation/common/descriptors';

import { IMergeEditorEditor } from './merge-editor-widget';
Expand Down Expand Up @@ -49,6 +50,11 @@ export abstract class MonacoService {
overrides?: { [key: string]: any },
): IDiffEditor;

public abstract createMultiDiffEditorWidget(
monacoContainer: HTMLElement,
overrides?: { [key: string]: any },
): MultiDiffEditorWidget;

public abstract createMergeEditor(
monacoContainer: HTMLElement,
options?: IDiffEditorConstructionOptions,
Expand Down
Loading
Loading