diff --git a/packages/core/src/tools/filesystem/edit_file.ts b/packages/core/src/tools/filesystem/edit_file.ts index e29d6df..c71584b 100644 --- a/packages/core/src/tools/filesystem/edit_file.ts +++ b/packages/core/src/tools/filesystem/edit_file.ts @@ -123,9 +123,22 @@ export const editFileTool: ToolWithMetadata = { output += `\n\n⚠️ 代码检查发现问题,请修复:${result.diagnostics}`; } + // 构建 diff 元数据 + const fileName = path.basename(absolutePath); + const metadata: Record = { + fileDiff: { + path: absolutePath, + name: fileName, + originalContent: result.originalContent ?? '', + newContent: result.newContent ?? '', + operation: 'edit', + }, + }; + return { success: true, output, + metadata, }; }, }; diff --git a/packages/core/src/tools/filesystem/write_file.ts b/packages/core/src/tools/filesystem/write_file.ts index 33f18e0..f865e23 100644 --- a/packages/core/src/tools/filesystem/write_file.ts +++ b/packages/core/src/tools/filesystem/write_file.ts @@ -92,9 +92,22 @@ export const writeFileTool: ToolWithMetadata = { output += `\n\n⚠️ 代码检查发现问题,请修复:${result.diagnostics}`; } + // 构建 diff 元数据 + const fileName = path.basename(absolutePath); + const metadata: Record = { + fileDiff: { + path: absolutePath, + name: fileName, + originalContent: result.originalContent ?? '', + newContent: result.newContent ?? content, + operation: 'write', + }, + }; + return { success: true, output, + metadata, }; }, }; diff --git a/packages/ui/package.json b/packages/ui/package.json index 0a1ddeb..897918c 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -28,6 +28,8 @@ "@codemirror/lang-json": "^6.0.2", "@codemirror/lang-markdown": "^6.5.0", "@codemirror/lang-python": "^6.2.1", + "@codemirror/merge": "^6.10.2", + "@codemirror/state": "^6.5.2", "@codemirror/theme-one-dark": "^6.1.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", @@ -39,6 +41,7 @@ "@uiw/react-codemirror": "^4.25.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "codemirror": "^6.0.2", "framer-motion": "^12.23.26", "lucide-react": "^0.344.0", "react-markdown": "^10.1.0", diff --git a/packages/ui/src/api/client.ts b/packages/ui/src/api/client.ts index 81aefdb..2b04ad7 100644 --- a/packages/ui/src/api/client.ts +++ b/packages/ui/src/api/client.ts @@ -153,6 +153,8 @@ export type { SystemCommandListResponse, // Active file types ActiveFileInfo, + // File diff types + FileDiffInfo, } from './types.js'; // API Configuration diff --git a/packages/ui/src/api/types.ts b/packages/ui/src/api/types.ts index 630be79..977568e 100644 --- a/packages/ui/src/api/types.ts +++ b/packages/ui/src/api/types.ts @@ -57,6 +57,8 @@ export interface ToolMessagePart { result?: unknown; error?: string; duration?: number; + /** 文件 Diff 信息(write_file/edit_file 工具) */ + fileDiff?: FileDiffInfo; } /** @@ -1158,3 +1160,21 @@ export interface ActiveFileInfo { language: string; } +// ============ 文件 Diff 相关 ============ + +/** 文件 Diff 信息(用于编辑器显示) */ +export interface FileDiffInfo { + /** 文件路径 */ + path: string; + /** 文件名 */ + name: string; + /** 原始内容(修改前) */ + originalContent: string; + /** 新内容(修改后) */ + newContent: string; + /** 操作类型 */ + operation: 'write' | 'edit'; + /** 工具调用 ID */ + toolCallId?: string; +} + diff --git a/packages/ui/src/components/ChatMessage.tsx b/packages/ui/src/components/ChatMessage.tsx index b5dfc2a..858ba40 100644 --- a/packages/ui/src/components/ChatMessage.tsx +++ b/packages/ui/src/components/ChatMessage.tsx @@ -14,6 +14,7 @@ import { AlertCircle, CheckCircle2, Loader2, + GitCompare, } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { useState, forwardRef } from 'react'; @@ -22,7 +23,7 @@ import { fadeInUp, smoothTransition } from '../utils/animations'; import { getAgentDisplayName } from '../utils/agent'; import { Markdown } from './Markdown'; import { FileMentionText } from './FileMentionTag'; -import type { Message, ToolCallInfo, ToolStatus, ToolMessagePart, QuestionMessagePart } from '../api/types.js'; +import type { Message, ToolCallInfo, ToolStatus, ToolMessagePart, QuestionMessagePart, FileDiffInfo } from '../api/types.js'; import { AskUserQuestion } from './AskUserQuestion.js'; interface ChatMessageProps { @@ -31,10 +32,12 @@ interface ChatMessageProps { isStreaming?: boolean; /** 回答问题的回调(用于 ask_user_question 工具) */ onAnswerQuestion?: (questionPartId: string, answers: string[]) => void; + /** 查看文件 Diff 的回调 */ + onViewDiff?: (diff: FileDiffInfo) => void; } export const ChatMessage = forwardRef( - ({ message, isStreaming = false, onAnswerQuestion }, ref) => { + ({ message, isStreaming = false, onAnswerQuestion, onViewDiff }, ref) => { const isUser = message.role === 'user'; const [copied, setCopied] = useState(false); @@ -83,7 +86,7 @@ export const ChatMessage = forwardRef( ); case 'tool': - return ; + return ; case 'question': { // 问题组件:即使在流式输出时也允许用户回答(除非已回答) const questionPart = part as QuestionMessagePart; @@ -250,15 +253,21 @@ export function TypingIndicator({ agentName }: TypingIndicatorProps) { */ interface ToolPartItemProps { part: ToolMessagePart; + /** 查看文件 Diff 的回调 */ + onViewDiff?: (diff: FileDiffInfo) => void; } -function ToolPartItem({ part }: ToolPartItemProps) { +function ToolPartItem({ part, onViewDiff }: ToolPartItemProps) { const [expanded, setExpanded] = useState(false); const hasDetails = Object.keys(part.arguments).length > 0 || part.result !== undefined || part.error !== undefined; + // 判断是否为文件操作工具且有 diff 数据 + const isFileOperation = part.toolName === 'write_file' || part.toolName === 'edit_file'; + const hasDiff = isFileOperation && part.fileDiff && part.status === 'completed'; + return (
{/* 头部:工具名称、状态、时长 */} @@ -278,6 +287,21 @@ function ToolPartItem({ part }: ToolPartItemProps) { {part.duration && ( {formatDuration(part.duration)} )} + {/* View Diff 按钮 */} + {hasDiff && onViewDiff && ( + { + e.stopPropagation(); + onViewDiff(part.fileDiff!); + }} + className="px-2 py-0.5 text-xs bg-primary-500/10 text-primary-400 rounded hover:bg-primary-500/20 transition-colors" + > + + View Diff + + )} {hasDetails && ( {expanded ? : } diff --git a/packages/ui/src/components/DiffEditor.tsx b/packages/ui/src/components/DiffEditor.tsx new file mode 100644 index 0000000..6e09013 --- /dev/null +++ b/packages/ui/src/components/DiffEditor.tsx @@ -0,0 +1,223 @@ +/** + * DiffEditor Component + * + * 基于 CodeMirror Merge View 的 Diff 编辑器 + * 用于显示文件修改前后的对比 + */ + +import { useEffect, useRef, useMemo } from 'react'; +import { EditorView, basicSetup } from 'codemirror'; +import { EditorState } from '@codemirror/state'; +import { MergeView } from '@codemirror/merge'; +import { javascript } from '@codemirror/lang-javascript'; +import { python } from '@codemirror/lang-python'; +import { json } from '@codemirror/lang-json'; +import { html } from '@codemirror/lang-html'; +import { css } from '@codemirror/lang-css'; +import { markdown } from '@codemirror/lang-markdown'; +import { oneDark } from '@codemirror/theme-one-dark'; +import { X, FileCode, GitCompare } from 'lucide-react'; +import { motion } from 'framer-motion'; +import { cn } from '../utils/cn.js'; +import { useTheme } from '../hooks/useTheme.js'; +import type { FileDiffInfo } from '../api/types.js'; + +interface DiffEditorProps { + /** Diff 信息 */ + diff: FileDiffInfo; + /** 关闭回调 */ + onClose?: () => void; + /** 容器类名 */ + className?: string; +} + +// 根据文件扩展名获取语言 +function getLanguageExtension(filename: string) { + const ext = filename.split('.').pop()?.toLowerCase() || ''; + switch (ext) { + case 'js': + case 'jsx': + return javascript({ jsx: true }); + case 'ts': + case 'tsx': + return javascript({ jsx: true, typescript: true }); + case 'py': + return python(); + case 'json': + return json(); + case 'html': + case 'htm': + return html(); + case 'css': + case 'scss': + case 'less': + return css(); + case 'md': + case 'markdown': + return markdown(); + default: + return []; + } +} + +// 计算变更统计 +function computeChangeStats(original: string, modified: string) { + const originalLines = original.split('\n'); + const modifiedLines = modified.split('\n'); + + // 简单统计:计算行数差异 + const additions = Math.max(0, modifiedLines.length - originalLines.length); + const deletions = Math.max(0, originalLines.length - modifiedLines.length); + + // 更精确的统计需要实际的 diff 算法 + // 这里用简化版本 + let changed = 0; + const minLen = Math.min(originalLines.length, modifiedLines.length); + for (let i = 0; i < minLen; i++) { + if (originalLines[i] !== modifiedLines[i]) { + changed++; + } + } + + return { + additions: additions + changed, + deletions: deletions + changed, + totalLines: modifiedLines.length, + }; +} + +export function DiffEditor({ diff, onClose, className }: DiffEditorProps) { + const containerRef = useRef(null); + const mergeViewRef = useRef(null); + const { resolvedTheme } = useTheme(); + + // 语言扩展 + const languageExtension = useMemo( + () => getLanguageExtension(diff.name), + [diff.name] + ); + + // 变更统计 + const stats = useMemo( + () => computeChangeStats(diff.originalContent, diff.newContent), + [diff.originalContent, diff.newContent] + ); + + // ESC 键关闭 Diff 视图 + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape' && onClose) { + onClose(); + } + }; + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [onClose]); + + // 创建 MergeView + useEffect(() => { + if (!containerRef.current) return; + + // 清理之前的实例 + if (mergeViewRef.current) { + mergeViewRef.current.destroy(); + } + + // 创建基础扩展 + const extensions = [ + basicSetup, + EditorView.editable.of(false), // 只读模式 + EditorState.readOnly.of(true), + ...(Array.isArray(languageExtension) ? languageExtension : [languageExtension]), + ]; + + // 深色主题 + if (resolvedTheme === 'dark') { + extensions.push(oneDark); + } + + // 创建 MergeView + const mergeView = new MergeView({ + a: { + doc: diff.originalContent, + extensions, + }, + b: { + doc: diff.newContent, + extensions, + }, + parent: containerRef.current, + collapseUnchanged: { margin: 3, minSize: 4 }, + gutter: true, + highlightChanges: true, + }); + + mergeViewRef.current = mergeView; + + return () => { + mergeView.destroy(); + }; + }, [diff.originalContent, diff.newContent, languageExtension, resolvedTheme]); + + return ( +
+ {/* Header */} +
+
+ {/* 文件图标和名称 */} +
+ + {diff.name} + ({diff.operation === 'write' ? 'Write' : 'Edit'}) +
+ + {/* 变更统计 */} +
+ +{stats.additions} + -{stats.deletions} +
+
+ + {/* 操作按钮 */} +
+ {/* 关闭按钮 */} + {onClose && ( + + + + )} +
+
+ + {/* 标签栏 */} +
+
+ + Original +
+
+ + Modified +
+
+ + {/* Diff 视图 */} +
+ + {/* 路径提示 */} +
+ {diff.path} +
+
+ ); +} diff --git a/packages/ui/src/components/IDE.tsx b/packages/ui/src/components/IDE.tsx index 5ca40de..a0c5714 100644 --- a/packages/ui/src/components/IDE.tsx +++ b/packages/ui/src/components/IDE.tsx @@ -4,13 +4,14 @@ * 整合文件浏览器和代码编辑器的 IDE 组件 */ -import { useState, useCallback, useEffect, useRef } from 'react'; +import { useState, useCallback, useEffect, useRef, useImperativeHandle, forwardRef } from 'react'; import { toast } from 'sonner'; import { cn } from '../utils/cn.js'; import { readFile, getWorkingDirectory } from '../api/client.js'; import { FileExplorer } from './FileExplorer.js'; import { CodeEditor, getLanguageFromFilename, type EditorTab } from './CodeEditor.js'; -import type { ActiveFileInfo } from '../api/types.js'; +import { DiffEditor } from './DiffEditor.js'; +import type { ActiveFileInfo, FileDiffInfo } from '../api/types.js'; // localStorage 键名 const STORAGE_KEY_TABS = 'ai-assistant-editor-tabs'; @@ -28,12 +29,28 @@ interface IDEProps { sidebarWidth?: number; /** 当前活动文件变化回调 */ onActiveFileChange?: (file: ActiveFileInfo | null) => void; + /** 要显示的 Diff 信息(外部传入) */ + pendingDiff?: FileDiffInfo | null; + /** Diff 关闭回调 */ + onDiffClose?: () => void; } -export function IDE({ className, sidebarWidth = 256, onActiveFileChange }: IDEProps) { +/** IDE 组件暴露的方法 */ +export interface IDEHandle { + /** 显示文件 diff */ + showDiff: (diff: FileDiffInfo) => void; + /** 关闭 diff 视图 */ + closeDiff: () => void; +} + +export const IDE = forwardRef(function IDE( + { className, sidebarWidth = 256, onActiveFileChange, pendingDiff, onDiffClose }, + ref +) { const [tabs, setTabs] = useState([]); const [activeTabId, setActiveTabId] = useState(null); const [workingDirectory, setWorkingDirectory] = useState(''); + const [currentDiff, setCurrentDiff] = useState(null); const isRestoringTabs = useRef(false); const hasRestoredTabs = useRef(false); @@ -235,6 +252,47 @@ export function IDE({ className, sidebarWidth = 256, onActiveFileChange }: IDEPr ); }, []); + // 显示 diff 视图 + const showDiff = useCallback((diff: FileDiffInfo) => { + setCurrentDiff(diff); + + // 如果文件已在编辑器中打开,更新其内容 + setTabs((prev) => + prev.map((tab) => { + if (tab.path === diff.path) { + return { + ...tab, + content: diff.newContent, + originalContent: diff.newContent, + }; + } + return tab; + }) + ); + }, []); + + // 关闭 diff 视图 + const closeDiff = useCallback(() => { + setCurrentDiff(null); + onDiffClose?.(); + }, [onDiffClose]); + + // 暴露方法给父组件 + useImperativeHandle(ref, () => ({ + showDiff, + closeDiff, + }), [showDiff, closeDiff]); + + // 处理外部传入的 pendingDiff + useEffect(() => { + if (pendingDiff) { + showDiff(pendingDiff); + } + }, [pendingDiff, showDiff]); + + // 判断是否显示 diff 视图 + const showDiffView = currentDiff !== null; + return (
{/* 文件浏览器 */} @@ -245,17 +303,24 @@ export function IDE({ className, sidebarWidth = 256, onActiveFileChange }: IDEPr
- {/* 代码编辑器 */} + {/* 代码编辑器 / Diff 视图 */}
- + {showDiffView ? ( + + ) : ( + + )}
); -} +}); diff --git a/packages/ui/src/hooks/useChat.ts b/packages/ui/src/hooks/useChat.ts index dc104f1..09b2dfe 100644 --- a/packages/ui/src/hooks/useChat.ts +++ b/packages/ui/src/hooks/useChat.ts @@ -24,6 +24,7 @@ import type { SubagentToolEndPayload, SubagentState, SubagentToolInfo, + FileDiffInfo, } from '../api/types.js'; interface UseChatOptions { @@ -35,6 +36,8 @@ interface UseChatOptions { onConfigError?: (error: ConfigErrorPayload) => void; /** 切换会话回调(如 :new 命令创建新会话) */ onSessionSwitch?: (newSessionId: string) => void; + /** 文件 diff 回调(当 AI 写入/编辑文件时触发) */ + onFileDiff?: (diff: FileDiffInfo) => void; } interface ChatState { @@ -54,7 +57,7 @@ interface ChatState { currentSubagent: SubagentState | null; } -export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdated, onConfigError, onSessionSwitch }: UseChatOptions) { +export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdated, onConfigError, onSessionSwitch, onFileDiff }: UseChatOptions) { const [state, setState] = useState({ messages: [], isConnected: false, @@ -80,11 +83,13 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate const onSessionUpdatedRef = useRef(onSessionUpdated); const onConfigErrorRef = useRef(onConfigError); const onSessionSwitchRef = useRef(onSessionSwitch); + const onFileDiffRef = useRef(onFileDiff); onErrorRef.current = onError; onSessionNotFoundRef.current = onSessionNotFound; onSessionUpdatedRef.current = onSessionUpdated; onConfigErrorRef.current = onConfigError; onSessionSwitchRef.current = onSessionSwitch; + onFileDiffRef.current = onFileDiff; // 加载历史消息 const loadMessages = useCallback(async () => { @@ -244,6 +249,34 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate case 'tool_end': { const payload = message.payload as ToolEndPayload; + + // 检查是否为文件写入/编辑工具,提取 diff 信息 + let fileDiffInfo: FileDiffInfo | undefined; + if (payload.status === 'completed' && payload.result) { + const result = payload.result as { + metadata?: { + fileDiff?: { + path: string; + name: string; + originalContent: string; + newContent: string; + operation: 'write' | 'edit'; + }; + }; + }; + + if (result.metadata?.fileDiff) { + fileDiffInfo = { + ...result.metadata.fileDiff, + toolCallId: payload.id, + }; + // 异步触发回调,避免阻塞状态更新 + if (onFileDiffRef.current) { + setTimeout(() => onFileDiffRef.current?.(fileDiffInfo!), 0); + } + } + } + setState((prev) => { if (!prev.streamingMessage) return prev; @@ -303,6 +336,7 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate result: payload.result, error: payload.error, duration: payload.duration, + fileDiff: fileDiffInfo, } as ToolMessagePart; } return part; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 210968b..93d626f 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -201,6 +201,8 @@ export type { DiagnosticsResponse, // Active file types ActiveFileInfo, + // File diff types + FileDiffInfo, } from './api/client.js'; // Primitives (shadcn/ui style) @@ -254,7 +256,8 @@ export { DiagnosticsIndicator, DiagnosticsIndicatorCompact } from './components/ export { SessionPanel } from './components/SessionPanel.js'; export { FileExplorer } from './components/FileExplorer.js'; export { CodeEditor, getLanguageFromFilename, type EditorTab } from './components/CodeEditor.js'; -export { IDE } from './components/IDE.js'; +export { DiffEditor } from './components/DiffEditor.js'; +export { IDE, type IDEHandle } from './components/IDE.js'; export { StatusBar } from './components/StatusBar.js'; export { Resizer } from './components/Resizer.js'; export { ToolbarOverflowMenu, type ToolbarMenuItem } from './components/ToolbarOverflowMenu.js'; diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx index b1a269d..f9466fa 100644 --- a/packages/web/src/App.tsx +++ b/packages/web/src/App.tsx @@ -24,6 +24,7 @@ import { createSession, type Session, type ActiveFileInfo, + type FileDiffInfo, } from '@ai-assistant/ui'; import { ChatPage } from './pages/Chat'; @@ -53,6 +54,9 @@ export function App() { return saved !== 'false'; // 默认开启 }); + // Diff 显示状态(当 AI 编辑/写入文件时触发) + const [pendingDiff, setPendingDiff] = useState(null); + // 持久化自动附加开关状态 useEffect(() => { localStorage.setItem('ai-assistant-auto-attach-file', String(autoAttachActiveFile)); @@ -121,6 +125,16 @@ export function App() { setCurrentSessionId(newSessionId); }, []); + // 文件 diff 回调(当 AI 编辑/写入文件时触发) + const handleFileDiff = useCallback((diff: FileDiffInfo) => { + setPendingDiff(diff); + }, []); + + // Diff 关闭回调 + const handleDiffClose = useCallback(() => { + setPendingDiff(null); + }, []); + // 处理面板宽度调整 const handleResize = useCallback((delta: number) => { setIdePanelWidth((prev) => { @@ -159,7 +173,11 @@ export function App() { className="hidden md:flex flex-col" style={{ width: `${idePanelWidth}%` }} > - +
{/* 可拖拽分割线 */} @@ -191,6 +209,8 @@ export function App() { activeFile={activeFile} autoAttachActiveFile={autoAttachActiveFile} onAutoAttachActiveFileToggle={setAutoAttachActiveFile} + onFileDiff={handleFileDiff} + onViewDiff={handleFileDiff} /> ) : (
diff --git a/packages/web/src/pages/Chat.tsx b/packages/web/src/pages/Chat.tsx index 30dfaea..2ed060a 100644 --- a/packages/web/src/pages/Chat.tsx +++ b/packages/web/src/pages/Chat.tsx @@ -17,6 +17,7 @@ import { DiagnosticsIndicator, ToolbarOverflowMenu, type ActiveFileInfo, + type FileDiffInfo, } from '@ai-assistant/ui'; interface ChatPageProps { @@ -43,6 +44,10 @@ interface ChatPageProps { autoAttachActiveFile?: boolean; /** 自动附加开关变更回调 */ onAutoAttachActiveFileToggle?: (enabled: boolean) => void; + /** 文件 diff 回调(当 AI 写入/编辑文件时触发) */ + onFileDiff?: (diff: FileDiffInfo) => void; + /** 查看文件 diff 回调(点击 View Diff 按钮) */ + onViewDiff?: (diff: FileDiffInfo) => void; } export function ChatPage({ @@ -63,6 +68,8 @@ export function ChatPage({ activeFile, autoAttachActiveFile, onAutoAttachActiveFileToggle, + onFileDiff, + onViewDiff, }: ChatPageProps) { const { messages, @@ -100,6 +107,7 @@ export function ChatPage({ : undefined, }); }, + onFileDiff, }); const messagesEndRef = useRef(null); @@ -208,13 +216,13 @@ export function ChatPage({ {messages.map((message) => ( - + ))} {/* 流式消息 - 复用 ChatMessage 组件 */} {streamingMessage && ( - + )} {/* 子 Agent 进度显示 */} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 553f818..76a4592 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -252,6 +252,12 @@ importers: '@codemirror/lang-python': specifier: ^6.2.1 version: 6.2.1 + '@codemirror/merge': + specifier: ^6.10.2 + version: 6.11.2 + '@codemirror/state': + specifier: ^6.5.2 + version: 6.5.2 '@codemirror/theme-one-dark': specifier: ^6.1.3 version: 6.1.3 @@ -285,6 +291,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + codemirror: + specifier: ^6.0.2 + version: 6.0.2 framer-motion: specifier: ^12.23.26 version: 12.23.26(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1007,6 +1016,9 @@ packages: '@codemirror/lint@6.9.2': resolution: {integrity: sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==} + '@codemirror/merge@6.11.2': + resolution: {integrity: sha512-NO5EJd2rLRbwVWLgMdhIntDIhfDtMOKYEZgqV5WnkNUS2oXOCVWLPjG/kgl/Jth2fGiOuG947bteqxP9nBXmMg==} + '@codemirror/search@6.5.11': resolution: {integrity: sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==} @@ -5988,6 +6000,14 @@ snapshots: '@codemirror/view': 6.39.4 crelt: 1.0.6 + '@codemirror/merge@6.11.2': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.39.4 + '@lezer/highlight': 1.2.3 + style-mod: 4.1.3 + '@codemirror/search@6.5.11': dependencies: '@codemirror/state': 6.5.2