import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Icon, Popover } from '@opensumi/ide-components'; import { AppConfig, DisposableStore, URI, localize, path, useInjectable } from '@opensumi/ide-core-browser'; import { IResource, WorkbenchEditorService } from '@opensumi/ide-editor'; import { BaseApplyService } from '../../mcp/base-apply.service'; import styles from './inline-diff-widget.module.less'; const IconWithPopover = (props: { icon: string; content: string; id: string; onClick?: () => void; disabled?: boolean; }) => ( ); export const InlineDiffManager: React.FC<{ resource: IResource }> = (props) => { const { resource } = props; const applyService = useInjectable(BaseApplyService); const editorService = useInjectable(WorkbenchEditorService); const appConfig = useInjectable(AppConfig); const [show, setShow] = useState(true); const [changesCount, setChangesCount] = useState(0); const [currentChangeIndex, setCurrentChangeIndex] = useState(0); const [filePaths, setFilePaths] = useState(applyService.getPendingPaths()); const currentFilePath = useMemo( () => path.relative(appConfig.workspaceDir, resource.uri.path.toString()), [resource], ); // 不同编辑器是不同实例,所以不需要监听 const decorationModelService = applyService.currentPreviewer?.getNode()?.livePreviewDiffDecorationModel; useEffect(() => { const toDispose = new DisposableStore(); if (decorationModelService) { setChangesCount(decorationModelService.partialEditWidgetCount); toDispose.add( decorationModelService.onPartialEditWidgetListChange((e) => { setChangesCount(e.filter((item) => item.status === 'pending').length); }), ); } toDispose.add( applyService.onCodeBlockUpdate((codeBlock) => { if (path.relative(appConfig.workspaceDir, props.resource.uri.path.toString()) === codeBlock.relativePath) { setShow(codeBlock.status === 'pending'); } const pendingPaths = applyService.getPendingPaths(); setFilePaths(pendingPaths); if (decorationModelService) { // codeBlock 更新时 changeCount 也可能变化 setChangesCount(decorationModelService.partialEditWidgetCount); } }), ); return () => { toDispose.dispose(); }; }, [decorationModelService]); const handleSiblingChange = useCallback( (direction: 'up' | 'down') => { const index = decorationModelService?.revealSiblingChange(direction); if (index !== undefined) { setCurrentChangeIndex(index); } }, [decorationModelService], ); const handleSiblingFile = useCallback( (direction: 'up' | 'down') => { const index = filePaths.indexOf(currentFilePath!); if (index === -1) { return; } const uri = URI.file(path.join(appConfig.workspaceDir, filePaths[index + (direction === 'up' ? -1 : 1)])); editorService.open(uri); }, [currentFilePath, filePaths], ); const changeTip = useMemo(() => { if (changesCount === 0) { return ''; } return ` ${currentChangeIndex + 1} of ${changesCount}`; }, [changesCount, currentChangeIndex]); const fileTip = useMemo(() => { if (!currentFilePath) { return ''; } return ` ${filePaths.indexOf(currentFilePath) + 1} of ${filePaths.length}`; }, [currentFilePath, filePaths]); return (
applyService.processAll('accept', props.resource.uri)} content={localize('aiNative.inlineDiff.acceptAll')} id='inline-diff-manager-accept-all' /> applyService.processAll('reject', props.resource.uri)} content={localize('aiNative.inlineDiff.rejectAll')} id='inline-diff-manager-reject-all' /> handleSiblingChange('up')} /> handleSiblingChange('down')} />
handleSiblingFile('up')} disabled={filePaths.length === 0 || filePaths[0] === currentFilePath} content={localize('aiNative.inlineDiff.left') + fileTip} id='inline-diff-manager-left' /> handleSiblingFile('down')} disabled={filePaths.length === 0 || filePaths[filePaths.length - 1] === currentFilePath} content={localize('aiNative.inlineDiff.right') + fileTip} id='inline-diff-manager-right' />
); };