From 0dfa81f637f56beb9d4de8cf06f473892de36b3f Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Wed, 12 Feb 2025 01:43:50 -0800 Subject: [PATCH] Search and replace? --- .../void/browser/inlineDiffsService.ts | 104 ++++++------------ .../react/src/markdown/ChatMarkdownRender.tsx | 26 +++-- .../src/quick-edit-tsx/QuickEditChat.tsx | 1 + .../react/src/sidebar-tsx/SidebarChat.tsx | 6 +- .../void/browser/searchAndReplaceService.ts | 7 +- 5 files changed, 55 insertions(+), 89 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index 88dc49e6..ed435a26 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -1084,56 +1084,6 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { - // // if streaming, use diffs to figure out where to write new code - // // these are two different coordinate systems - new and old line number - // let newFileEndLine: number // get new[0...newStoppingPoint] with line=newStoppingPoint highlighted - // let originalCodeStartLine: number // get original[oldStartingPoint...] - - // const lastDiff = computedDiffs.pop() - - // if (!lastDiff) { - // // if the writing is identical so far, display no changes - // newFileEndLine = diffZone.startLine - // originalCodeStartLine = 1 - // } - // else { - // if (lastDiff.type === 'insertion') { - // newFileEndLine = lastDiff.endLine - // originalCodeStartLine = lastDiff.originalStartLine - // } - // else if (lastDiff.type === 'deletion') { - // newFileEndLine = lastDiff.startLine - // originalCodeStartLine = lastDiff.originalStartLine - // } - // else if (lastDiff.type === 'edit') { - // newFileEndLine = lastDiff.endLine - // originalCodeStartLine = lastDiff.originalStartLine - // } - // else { - // throw new Error(`Void: diff.type not recognized on: ${lastDiff}`) - // } - // } - - // diffZone._streamState.line = newFileEndLine - - // // lines are 1-indexed - // const newFileTop = llmText.split('\n').slice(diffZone.startLine, (newFileEndLine - 1)).join('\n') - // const oldFileBottom = diffZone.originalCode.split('\n').slice((originalCodeStartLine - 1), Infinity).join('\n') - - // const newCode = `${newFileTop}\n${oldFileBottom}` - - // this._writeText(uri, newCode, - // { startLineNumber: diffZone.startLine, startColumn: 1, endLineNumber: diffZone.endLine, endColumn: Number.MAX_SAFE_INTEGER, }, // 1-indexed - // { shouldRealignDiffAreas: true } - // ) - - - // return computedDiffs - - - - - // called first, then call startApplying public addCtrlKZone({ startLine, endLine, editor }: AddCtrlKOpts) { @@ -1191,8 +1141,19 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { public startApplying(opts: StartApplyingOpts) { - const addedDiffZone = this._initializeRewriteStream(opts) - return addedDiffZone?.diffareaid + + if (opts.type === 'rewrite') { + const addedDiffZone = this._initializeRewriteStream(opts) + return addedDiffZone?.diffareaid + } + + else if (opts.type === 'searchReplace') { + this._initializeSearchAndReplaceStream(opts) + return undefined + } + + else return undefined + } @@ -1213,7 +1174,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { } - private async _generateSearchAndReplaceBlocks({ uri, applyStr }: { uri: URI, applyStr: string }): Promise { + private async _initializeSearchAndReplaceStream({ applyStr }: { applyStr: string }) { const ORIGINAL = `<<<<<<< ORIGINAL` const DIVIDER = `=======` const FINAL = `>>>>>>> UPDATED` @@ -1268,6 +1229,14 @@ ${FINAL} ${tripleTick[1]} ` + const uri_ = this._getActiveEditorURI() + if (!uri_) return + const uri = uri_ + + // generate search/replace block text + const fileContents = await VSReadFile(this._modelService, uri) + if (fileContents === null) return + const searchReplaceUserMessage = ({ originalCode, applyStr }: { originalCode: string, applyStr: string }) => `\ ORIGINAL_FILE @@ -1280,7 +1249,7 @@ INSTRUCTIONS Please output SEARCH/REPLACE blocks to make the change. Return ONLY your suggested SEARCH/REPLACE blocks, without any explanation. ` - function endsWithAnyPrefixOf(str: string, anyPrefix: string) { + const endsWithAnyPrefixOf = (str: string, anyPrefix: string) => { // for each prefix for (let i = anyPrefix.length; i >= 0; i--) { const prefix = anyPrefix.slice(0, i) @@ -1344,9 +1313,6 @@ Please output SEARCH/REPLACE blocks to make the change. Return ONLY your suggest } } - // generate search/replace block text - const fileContents = await VSReadFile(this._modelService, uri) - if (fileContents === null) return // reject all diffZones on this URI, adding to history (there can't possibly be overlap after this) this.removeDiffAreas({ uri, behavior: 'reject', removeCtrlKs: true }) @@ -1364,26 +1330,24 @@ Please output SEARCH/REPLACE blocks to make the change. Return ONLY your suggest const blocks = extractBlocks(fullText) // find block.orig in fileContents and return its range in file - const findBlock = (block: { orig: string }, fileContents: string) => { - const origText = block.orig - const idx = fileContents.indexOf(origText) + const findTextInCode = (text: string, fileContents: string) => { + const idx = fileContents.indexOf(text) if (idx === -1) return 'Not found' as const - const lastIdx = fileContents.lastIndexOf(origText) + const lastIdx = fileContents.lastIndexOf(text) if (lastIdx !== idx) return 'Not unique' as const - const startLine = fileContents.substring(0, idx).split('\n').length - const numLines = origText.split('\n').length + const numLines = text.split('\n').length const endLine = startLine + numLines - 1 - return [startLine, endLine] } let latestStreamInfoMutable: any = {} - for (let blockNum = 0; blockNum < blocks.length; blockNum += 1) { const block = blocks[blockNum] - const foundInCode = findBlock(block, fileContents) + if (block.state === 'writingOriginal') continue + + const foundInCode = findTextInCode(block.orig, fileContents) if (typeof foundInCode === 'string') { console.log('ERROR!!!!', foundInCode) continue @@ -1391,8 +1355,6 @@ Please output SEARCH/REPLACE blocks to make the change. Return ONLY your suggest const [startLine, endLine] = foundInCode - if (block.state === 'writingOriginal') continue - // if should add new diffarea if (blockNum > diffareaidOfBlockNum.length) { const adding: Omit = { @@ -1443,13 +1405,11 @@ Please output SEARCH/REPLACE blocks to make the change. Return ONLY your suggest }) - - - - } + + private _initializeRewriteStream(opts: StartApplyingOpts): DiffZone | undefined { const { from } = opts diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx index 86afcc33..7b03f068 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx @@ -7,7 +7,7 @@ import React, { JSX, useCallback, useEffect, useState } from 'react' import { marked, MarkedToken, Token } from 'marked' import { BlockCode } from './BlockCode.js' import { useAccessor, useChatThreadsState, useChatThreadsStreamState } from '../util/services.js' -import { ChatLocation, getApplyBoxId, } from '../../../searchAndReplaceService.js' +import { ChatMessageLocation, } from '../../../searchAndReplaceService.js' import { nameToVscodeLanguage } from '../../../helpers/detectLanguage.js' @@ -19,6 +19,16 @@ enum CopyButtonState { const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!' + + +type ApplyBoxLocation = ChatMessageLocation & { tokenIdx: number } + +const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) => { + return `${threadId}-${messageIdx}-${tokenIdx}` +} + + + const ApplyButtonsOnHover = ({ applyStr, applyBoxId }: { applyStr: string, applyBoxId: string }) => { const accessor = useAccessor() @@ -47,9 +57,9 @@ const ApplyButtonsOnHover = ({ applyStr, applyBoxId }: { applyStr: string, apply const onApply = useCallback(() => { inlineDiffService.startApplying({ - from: 'Chat', + from: 'ClickApply', + type: 'searchReplace', applyStr, - applyBoxId, }) metricsService.capture('Apply Code', { length: applyStr.length }) // capture the length only }, [metricsService, inlineDiffService, applyStr]) @@ -87,7 +97,7 @@ export const CodeSpan = ({ children, className }: { children: React.ReactNode, c } -const RenderToken = ({ token, nested = false, noSpace = false, chatLocation, tokenId = '', }: { token: Token | string, nested?: boolean, noSpace?: boolean, chatLocation?: ChatLocation, tokenId?: string, }): JSX.Element => { +const RenderToken = ({ token, nested = false, noSpace = false, chatMessageLocation: chatLocation, tokenIdx }: { token: Token | string, nested?: boolean, noSpace?: boolean, chatMessageLocation?: ChatMessageLocation, tokenIdx: number }): JSX.Element => { // deal with built-in tokens first (assume marked token) @@ -104,7 +114,7 @@ const RenderToken = ({ token, nested = false, noSpace = false, chatLocation, tok const applyBoxId = getApplyBoxId({ threadId: chatLocation!.threadId, messageIdx: chatLocation!.messageIdx, - codeblockId: tokenId, + tokenIdx: tokenIdx, }) return {t.tokens.map((token, index) => ( - // assign a unique tokenId to nested components + // assign a unique tokenId to nested components ))} if (nested) return contents @@ -279,12 +289,12 @@ const RenderToken = ({ token, nested = false, noSpace = false, chatLocation, tok ) } -export const ChatMarkdownRender = ({ string, nested = false, noSpace, chatLocation }: { string: string, nested?: boolean, noSpace?: boolean, chatLocation?: ChatLocation }) => { +export const ChatMarkdownRender = ({ string, nested = false, noSpace, chatMessageLocation }: { string: string, nested?: boolean, noSpace?: boolean, chatMessageLocation?: ChatMessageLocation }) => { const tokens = marked.lexer(string); // https://marked.js.org/using_pro#renderer return ( <> {tokens.map((token, index) => ( - + ))} ) diff --git a/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx index 37dd91bf..57dbb472 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx @@ -59,6 +59,7 @@ export const QuickEditChat = ({ const id = inlineDiffsService.startApplying({ from: 'QuickEdit', + type:'rewrite', diffareaid: diffareaid, }) setCurrentlyStreamingDiffZone(id ?? null) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 2aaf9dd2..d8b4ef93 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -24,7 +24,7 @@ import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js'; import { Pencil, X } from 'lucide-react'; import { FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js'; import { WarningBox } from '../void-settings-tsx/WarningBox.js'; -import { ChatLocation } from '../../../searchAndReplaceService.js'; +import { ChatMessageLocation } from '../../../searchAndReplaceService.js'; @@ -675,12 +675,12 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx }: { chatMessage: ChatM else if (role === 'assistant') { const thread = chatThreadsService.getCurrentThread() - const chatLocation: ChatLocation = { + const chatMessageLocation: ChatMessageLocation = { threadId: thread.id, messageIdx: messageIdx!, } - chatbubbleContents = + chatbubbleContents = } return
{ - return `${threadId}-${messageIdx}-${codeblockId}}` -} export type SearchAndReplaceBlock = { search: string;