update edit aply file content

This commit is contained in:
duanfuxiang 2025-04-01 11:41:29 +08:00
parent c415175de8
commit 4fb0bb22ac
5 changed files with 54 additions and 50 deletions

View File

@ -7,7 +7,7 @@ import { AppProvider } from './contexts/AppContext'
export type ApplyViewState = { export type ApplyViewState = {
file: TFile file: TFile
originalContent: string oldContent: string
newContent: string newContent: string
onClose: (applied: boolean) => void onClose: (applied: boolean) => void
} }

View File

@ -28,7 +28,7 @@ export default function ApplyViewRoot({
const app = useApp() const app = useApp()
const [diff, setDiff] = useState<Change[]>( const [diff, setDiff] = useState<Change[]>(
diffLines(state.originalContent, state.newContent), diffLines(state.oldContent, state.newContent),
) )
const handleAccept = async () => { const handleAccept = async () => {

View File

@ -48,7 +48,7 @@ import { PromptGenerator, addLineNumbers } from '../../utils/prompt-generator'
import { fetchUrlsContent, webSearch } from '../../utils/web-search' import { fetchUrlsContent, webSearch } from '../../utils/web-search'
// Simple file reading function that returns a placeholder content for testing // Simple file reading function that returns a placeholder content for testing
const readFileContent = async (app: App, filePath: string): Promise<string> => { const readFileContentByPath = async (app: App, filePath: string): Promise<string> => {
const file = app.vault.getFileByPath(filePath) const file = app.vault.getFileByPath(filePath)
if (!file) { if (!file) {
throw new Error(`File not found: ${filePath}`) throw new Error(`File not found: ${filePath}`)
@ -367,24 +367,23 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
>({ >({
mutationFn: async ({ applyMsgId, toolArgs }) => { mutationFn: async ({ applyMsgId, toolArgs }) => {
try { try {
const activeFile = app.workspace.getActiveFile() let opFile = app.workspace.getActiveFile()
if (!activeFile) { if ('filepath' in toolArgs && toolArgs.filepath) {
throw new Error( opFile = app.vault.getFileByPath(toolArgs.filepath)
'No file is currently open to apply changes. Please open a file and try again.',
)
} }
const activeFileContent = await readTFileContent(activeFile, app.vault)
if (toolArgs.type === 'write_to_file' || toolArgs.type === 'insert_content') { if (toolArgs.type === 'write_to_file' || toolArgs.type === 'insert_content') {
const applyRes = await ApplyEditToFile( if (!opFile) {
activeFile, throw new Error(`File not found: ${toolArgs.filepath}`)
activeFileContent, }
const fileContent = await readTFileContent(opFile, app.vault)
const appliedFileContent = await ApplyEditToFile(
fileContent,
toolArgs.content, toolArgs.content,
toolArgs.startLine, toolArgs.startLine,
toolArgs.endLine toolArgs.endLine
) )
if (!applyRes) { if (!appliedFileContent) {
throw new Error('Failed to apply edit changes') throw new Error('Failed to apply edit changes')
} }
// return a Promise, which will be resolved after user makes a choice // return a Promise, which will be resolved after user makes a choice
@ -393,9 +392,9 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
type: APPLY_VIEW_TYPE, type: APPLY_VIEW_TYPE,
active: true, active: true,
state: { state: {
file: activeFile, file: opFile,
originalContent: activeFileContent, oldContent: fileContent,
newContent: applyRes, newContent: appliedFileContent,
onClose: (applied: boolean) => { onClose: (applied: boolean) => {
const applyStatus = applied ? ApplyStatus.Applied : ApplyStatus.Rejected const applyStatus = applied ? ApplyStatus.Applied : ApplyStatus.Rejected
const applyEditContent = applied ? 'Changes successfully applied' const applyEditContent = applied ? 'Changes successfully applied'
@ -418,13 +417,15 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
}) })
}) })
} else if (toolArgs.type === 'search_and_replace') { } else if (toolArgs.type === 'search_and_replace') {
const fileContent = activeFile.path === toolArgs.filepath ? activeFileContent : await readFileContent(app, toolArgs.filepath) if (!opFile) {
const applyRes = await SearchAndReplace( throw new Error(`File not found: ${toolArgs.filepath}`)
activeFile, }
const fileContent = await readTFileContent(opFile, app.vault)
const appliedFileContent = await SearchAndReplace(
fileContent, fileContent,
toolArgs.operations toolArgs.operations
) )
if (!applyRes) { if (!appliedFileContent) {
throw new Error('Failed to search_and_replace') throw new Error('Failed to search_and_replace')
} }
// return a Promise, which will be resolved after user makes a choice // return a Promise, which will be resolved after user makes a choice
@ -433,9 +434,9 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
type: APPLY_VIEW_TYPE, type: APPLY_VIEW_TYPE,
active: true, active: true,
state: { state: {
file: activeFile, file: opFile,
originalContent: activeFileContent, oldContent: fileContent,
newContent: applyRes, newContent: appliedFileContent,
onClose: (applied: boolean) => { onClose: (applied: boolean) => {
const applyStatus = applied ? ApplyStatus.Applied : ApplyStatus.Rejected const applyStatus = applied ? ApplyStatus.Applied : ApplyStatus.Rejected
const applyEditContent = applied ? 'Changes successfully applied' const applyEditContent = applied ? 'Changes successfully applied'
@ -458,12 +459,15 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
}) })
}) })
} else if (toolArgs.type === 'apply_diff') { } else if (toolArgs.type === 'apply_diff') {
const diffResult = await diffStrategy.applyDiff( if (!opFile) {
activeFileContent, throw new Error(`File not found: ${toolArgs.filepath}`)
}
const fileContent = await readTFileContent(opFile, app.vault)
const appliedResult = await diffStrategy.applyDiff(
fileContent,
toolArgs.diff toolArgs.diff
) )
if (!diffResult.success) { if (!appliedResult || !appliedResult.success) {
console.log(diffResult)
throw new Error(`Failed to apply_diff`) throw new Error(`Failed to apply_diff`)
} }
// return a Promise, which will be resolved after user makes a choice // return a Promise, which will be resolved after user makes a choice
@ -472,9 +476,9 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
type: APPLY_VIEW_TYPE, type: APPLY_VIEW_TYPE,
active: true, active: true,
state: { state: {
file: activeFile, file: opFile,
originalContent: activeFileContent, oldContent: fileContent,
newContent: diffResult.content, newContent: appliedResult.content,
onClose: (applied: boolean) => { onClose: (applied: boolean) => {
const applyStatus = applied ? ApplyStatus.Applied : ApplyStatus.Rejected const applyStatus = applied ? ApplyStatus.Applied : ApplyStatus.Rejected
const applyEditContent = applied ? 'Changes successfully applied' const applyEditContent = applied ? 'Changes successfully applied'
@ -497,7 +501,10 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
}) })
}) })
} else if (toolArgs.type === 'read_file') { } else if (toolArgs.type === 'read_file') {
const fileContent = await readFileContent(app, toolArgs.filepath) if (!opFile) {
throw new Error(`File not found: ${toolArgs.filepath}`)
}
const fileContent = await readTFileContent(opFile, app.vault)
const formattedContent = `[read_file for '${toolArgs.filepath}'] Result:\n${addLineNumbers(fileContent)}\n`; const formattedContent = `[read_file for '${toolArgs.filepath}'] Result:\n${addLineNumbers(fileContent)}\n`;
return { return {
type: 'read_file', type: 'read_file',

View File

@ -241,8 +241,7 @@ export const InlineEdit: React.FC<InlineEditProps> = ({
const endLine = parsedBlock?.endLine || defaultEndLine; const endLine = parsedBlock?.endLine || defaultEndLine;
const updatedContent = await ApplyEditToFile( const updatedContent = await ApplyEditToFile(
activeFile, await plugin.app.vault.cachedRead(activeFile),
await plugin.app.vault.read(activeFile),
finalContent, finalContent,
startLine, startLine,
endLine endLine

View File

@ -4,32 +4,31 @@ import { SearchAndReplaceToolArgs } from '../types/apply';
/** /**
* Applies changes to a file by replacing content within specified line range * Applies changes to a file by replacing content within specified line range
* @param content - The new content to insert * @param updateContent - The new content to insert
* @param currentFile - The file being modified * @param currentFile - The file being modified
* @param currentFileContent - The current content of the file * @param rawContent - The current content of the file
* @param startLine - Starting line number (1-based indexing, optional) * @param startLine - Starting line number (1-based indexing, optional)
* @param endLine - Ending line number (1-based indexing, optional) * @param endLine - Ending line number (1-based indexing, optional)
* @returns Promise resolving to the modified content or null if operation fails * @returns Promise resolving to the modified content or null if operation fails
*/ */
export const ApplyEditToFile = async ( export const ApplyEditToFile = async (
currentFile: TFile, rawContent: string,
currentFileContent: string, updateContent: string,
content: string,
startLine?: number, startLine?: number,
endLine?: number, endLine?: number,
): Promise<string | null> => { ): Promise<string | null> => {
try { try {
// 如果文件为空,直接返回新内容 // if file is empty, return new content
if (!currentFileContent || currentFileContent.trim() === '') { if (!rawContent || rawContent.trim() === '') {
return content; return updateContent;
} }
// 如果要清空文件,直接返回空字符串 // if content is empty, return empty string
if (content === '') { if (updateContent === '') {
return ''; return '';
} }
const lines = currentFileContent.split('\n') const lines = rawContent.split('\n')
const effectiveStartLine = Math.max(1, startLine ?? 1) const effectiveStartLine = Math.max(1, startLine ?? 1)
const effectiveEndLine = Math.min(endLine ?? lines.length, lines.length) const effectiveEndLine = Math.min(endLine ?? lines.length, lines.length)
@ -41,7 +40,7 @@ export const ApplyEditToFile = async (
// Construct new content // Construct new content
return [ return [
...lines.slice(0, effectiveStartLine - 1), ...lines.slice(0, effectiveStartLine - 1),
content, updateContent,
...lines.slice(effectiveEndLine) ...lines.slice(effectiveEndLine)
].join('\n') ].join('\n')
} catch (error) { } catch (error) {
@ -58,16 +57,15 @@ function escapeRegExp(string: string): string {
/** /**
* *
* @param currentFile - * @param currentFile -
* @param currentFileContent - * @param rawContent -
* @param search - * @param search -
* @param replace - * @param replace -
*/ */
export const SearchAndReplace = async ( export const SearchAndReplace = async (
currentFile: TFile, rawContent: string,
currentFileContent: string,
operations: SearchAndReplaceToolArgs['operations'] operations: SearchAndReplaceToolArgs['operations']
) => { ) => {
let lines = currentFileContent.split("\n") let lines = rawContent.split("\n")
for (const op of operations) { for (const op of operations) {
const flags = op.regexFlags ?? (op.ignoreCase ? "gi" : "g") const flags = op.regexFlags ?? (op.ignoreCase ? "gi" : "g")