Search and replace?

This commit is contained in:
Andrew Pareles 2025-02-12 01:43:50 -08:00
parent d504daffa5
commit 0dfa81f637
5 changed files with 55 additions and 89 deletions

View file

@ -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<ExtractedCodeBlock[] | undefined> {
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<DiffZone, 'diffareaid'> = {
@ -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

View file

@ -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
</code>
}
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 <BlockCode
@ -196,7 +206,7 @@ const RenderToken = ({ token, nested = false, noSpace = false, chatLocation, tok
if (t.type === "paragraph") {
const contents = <>
{t.tokens.map((token, index) => (
<RenderToken key={index} token={token} tokenId={`${tokenId}-${index}`} /> // assign a unique tokenId to nested components
<RenderToken key={index} token={token} tokenIdx={index} /> // 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) => (
<RenderToken key={index} token={token} nested={nested} noSpace={noSpace} chatLocation={chatLocation} />
<RenderToken key={index} token={token} nested={nested} noSpace={noSpace} chatMessageLocation={chatMessageLocation} tokenIdx={index} />
))}
</>
)

View file

@ -59,6 +59,7 @@ export const QuickEditChat = ({
const id = inlineDiffsService.startApplying({
from: 'QuickEdit',
type:'rewrite',
diffareaid: diffareaid,
})
setCurrentlyStreamingDiffZone(id ?? null)

View file

@ -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 = <ChatMarkdownRender string={chatMessage.displayContent ?? ''} chatLocation={chatLocation} />
chatbubbleContents = <ChatMarkdownRender string={chatMessage.displayContent ?? ''} chatMessageLocation={chatMessageLocation} />
}
return <div

View file

@ -10,16 +10,11 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta
export type ChatLocation = {
export type ChatMessageLocation = {
threadId: string;
messageIdx: number;
}
export type ApplyBoxLocation = ChatLocation & { codeblockId: string }
export const getApplyBoxId = ({ threadId, messageIdx, codeblockId }: ApplyBoxLocation) => {
return `${threadId}-${messageIdx}-${codeblockId}}`
}
export type SearchAndReplaceBlock = {
search: string;