Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { IEditorService } from '../../../services/editor/common/editorService.js
import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js';
import { EditorResourceAccessor } from '../../../common/editor.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { extractCodeFromRegular } from './helpers/extractCodeFromResult.js';
import { extractCodeFromRegular } from '../common/helpers/extractCodeFromResult.js';
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
import { ILLMMessageService } from '../common/sendLLMMessageService.js';
import { isWindows } from '../../../../base/common/platform.js';
Expand Down
323 changes: 245 additions & 78 deletions src/vs/workbench/contrib/void/browser/chatThreadService.ts

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions src/vs/workbench/contrib/void/browser/editCodeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ import * as dom from '../../../../base/browser/dom.js';
import { Widget } from '../../../../base/browser/ui/widget.js';
import { URI } from '../../../../base/common/uri.js';
import { IConsistentEditorItemService, IConsistentItemService } from './helperServices/consistentItemService.js';
import { voidPrefixAndSuffix, ctrlKStream_userMessage, ctrlKStream_systemMessage, defaultQuickEditFimTags, rewriteCode_systemMessage, rewriteCode_userMessage, searchReplace_systemMessage, searchReplace_userMessage, } from './prompt/prompts.js';
import { voidPrefixAndSuffix, ctrlKStream_userMessage, ctrlKStream_systemMessage, defaultQuickEditFimTags, rewriteCode_systemMessage, rewriteCode_userMessage, searchReplace_systemMessage, searchReplace_userMessage, } from '../common/prompt/prompts.js';

import { mountCtrlK } from './react/out/quick-edit-tsx/index.js'
import { QuickEditPropsType } from './quickEditActions.js';
import { IModelContentChangedEvent } from '../../../../editor/common/textModelEvents.js';
import { extractCodeFromFIM, extractCodeFromRegular, ExtractedSearchReplaceBlock, extractSearchReplaceBlocks } from './helpers/extractCodeFromResult.js';
import { extractCodeFromFIM, extractCodeFromRegular, ExtractedSearchReplaceBlock, extractSearchReplaceBlocks } from '../common/helpers/extractCodeFromResult.js';
import { filenameToVscodeLanguage } from '../common/helpers/detectLanguage.js';
import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';
import { isMacintosh } from '../../../../base/common/platform.js';
Expand Down Expand Up @@ -1527,11 +1527,12 @@ class EditCodeService extends Disposable implements IEditCodeService {
}


const errHelper = (erroneousOriginal: string) => `All previous SEARCH/REPLACE blocks (if any) have been applied except the latest erroneous one. Please continue outputting SEARCH/REPLACE blocks. The ORIGINAL code with an error was: ${JSON.stringify(erroneousOriginal)}`
const errMsgOfInvalidStr = (str: string & ReturnType<typeof findTextInCode>, blockOrig: string) => {
return str === `Not found` ?
`The ORIGINAL code provided could not be found in the file. Please output all SEARCH/REPLACE blocks again, making sure the code in ORIGINAL is identical to a code snippet in the file. The ORIGINAL code provided: ${JSON.stringify(blockOrig)}`
`The ORIGINAL code provided could not be found in the file. You should make sure the text in ORIGINAL matches lines of code EXACTLY. ${errHelper(blockOrig)}`
: str === `Not unique` ?
`The ORIGINAL code provided shows up multiple times in the file. Please output all SEARCH/REPLACE blocks again, making sure the code in each ORIGINAL section is unique in the file. The ORIGINAL code provided: ${JSON.stringify(blockOrig)}`
`The ORIGINAL code provided shows up multiple times in the file. We recommend making the ORIGINAL portion bigger so we can find a unique match. ${errHelper(blockOrig)}`
: ``
}

Expand Down Expand Up @@ -1627,6 +1628,7 @@ class EditCodeService extends Disposable implements IEditCodeService {


// REVERT
// TODO!!!!! don't actually revert - we want to change this so it doesn't revert but isntead gives the current file contents
const numLines = this._getNumLines(uri)
if (numLines !== null) this._writeText(uri, originalFileCode,
{ startLineNumber: 1, startColumn: 1, endLineNumber: numLines, endColumn: Number.MAX_SAFE_INTEGER },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ const CopyButton = ({ codeStr }: { codeStr: string }) => {
metricsService.capture('Copy Code', { length: codeStr.length }) // capture the length only
}, [metricsService, clipboardService, codeStr, setCopyButtonText])

const isSingleLine = !codeStr.includes('\n')
const isSingleLine = false //!codeStr.includes('\n')

return <button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-1 rounded`}
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-2 rounded`}
onClick={onCopy}
>
{copyButtonText}
Expand Down Expand Up @@ -101,25 +101,25 @@ export const ApplyBlockHoverButtons = ({ codeStr, applyBoxId }: { codeStr: strin
}, [streamState, applyingUri, editCodeService, metricsService])


const isSingleLine = !codeStr.includes('\n')
const isSingleLine = false //!codeStr.includes('\n')

const applyButton = <button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-1 rounded`}
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-2 rounded`}
onClick={onSubmit}
>
Apply
</button>

const stopButton = <button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-1 rounded`}
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-2 rounded`}
onClick={onInterrupt}
>
Stop
</button>

const acceptRejectButtons = <>
<button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-1 rounded`}
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-2 rounded`}
onClick={() => {
const uri = applyingUri()
if (uri) editCodeService.removeDiffAreas({ uri, behavior: 'accept', removeCtrlKs: false })
Expand All @@ -128,7 +128,7 @@ export const ApplyBlockHoverButtons = ({ codeStr, applyBoxId }: { codeStr: strin
Accept
</button>
<button
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-1 rounded`}
className={`${isSingleLine ? '' : 'px-1 py-0.5'} text-sm bg-void-bg-2 text-void-fg-1 hover:brightness-110 border border-void-border-2 rounded`}
onClick={() => {
const uri = applyingUri()
if (uri) editCodeService.removeDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/

import React, { JSX } from 'react'
import React, { JSX, useState } from 'react'
import { marked, MarkedToken, Token } from 'marked'
import { BlockCode } from './BlockCode.js'
import { nameToVscodeLanguage } from '../../../../common/helpers/detectLanguage.js'
import { ApplyBlockHoverButtons } from './ApplyBlockHoverButtons.js'
import { useAccessor, useChatThreadsState } from '../util/services.js'
import { Range } from '../../../../../../services/search/common/searchExtTypes.js'
import { IRange } from '../../../../../../../base/common/range.js'
import { ScrollType } from '../../../../../../../editor/common/editorCommon.js'


export type ChatMessageLocation = {
threadId: string;
Expand All @@ -20,7 +25,81 @@ const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) =>
return `${threadId}-${messageIdx}-${tokenIdx}`
}

const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx }: { token: Token | string, nested?: boolean, chatMessageLocationForApply?: ChatMessageLocation, tokenIdx: string }): JSX.Element => {

const Codespan = ({ text, className, onClick }: { text: string, className?: string, onClick?: () => void }) => {

return <code
className={`font-mono font-medium rounded-sm bg-void-bg-1 px-1 ${className}`}
onClick={onClick}
>
{text}
</code>

}

const CodespanWithLink = ({ text, rawText, chatMessageLocation }: { text: string, rawText: string, chatMessageLocation: ChatMessageLocation }) => {

const accessor = useAccessor()

const chatThreadService = accessor.get('IChatThreadService')
const commandSerivce = accessor.get('ICommandService')
const editorService = accessor.get('ICodeEditorService')

const { messageIdx, threadId } = chatMessageLocation

const [didComputeCodespanLink, setDidComputeCodespanLink] = useState<boolean>(false)

let link = undefined
if (rawText.endsWith("`")) { // if codespan was completed

// get link from cache
link = chatThreadService.getCodespanLink({ codespanStr: text, messageIdx, threadId })

if (link === undefined) {
// generate link and add to cache
(chatThreadService.generateCodespanLink(text)
.then(link => {
chatThreadService.addCodespanLink({ newLinkText: text, newLinkLocation: link, messageIdx, threadId })
setDidComputeCodespanLink(true) // rerender
})
)
}

}


const onClick = () => {

if (!link) return;
const selection = link.selection

// open the file
commandSerivce.executeCommand('vscode.open', link.uri).then(() => {

// select the text
setTimeout(() => {
if (!selection) return;

const editor = editorService.getActiveCodeEditor()
if (!editor) return;

editor.setSelection(selection)
editor.revealRange(selection, ScrollType.Immediate)

}, 50) // needed when document was just opened and needs to initialize

})

}

return <Codespan
text={text}
onClick={onClick}
className={link ? 'underline hover:brightness-90 transition-all duration-200 cursor-pointer' : ''}
/>
}

const RenderToken = ({ token, nested, chatMessageLocation, tokenIdx }: { token: Token | string, nested?: boolean, chatMessageLocation?: ChatMessageLocation, tokenIdx: string }): JSX.Element => {

// deal with built-in tokens first (assume marked token)
const t = token as MarkedToken
Expand All @@ -35,12 +114,14 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx }: {

if (t.type === "code") {

const applyBoxId = chatMessageLocationForApply ? getApplyBoxId({
threadId: chatMessageLocationForApply.threadId,
messageIdx: chatMessageLocationForApply.messageIdx,
const applyBoxId = chatMessageLocation ? getApplyBoxId({
threadId: chatMessageLocation.threadId,
messageIdx: chatMessageLocation.messageIdx,
tokenIdx: tokenIdx,
}) : null

// TODO user should only be able to apply this when the code has been closed (t.raw ends with "```")

return <div>
<BlockCode
initValue={t.text}
Expand Down Expand Up @@ -132,7 +213,7 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx }: {
return <li>
<input type="checkbox" checked={t.checked} readOnly />
<span>
<ChatMarkdownRender chatMessageLocationForApply={chatMessageLocationForApply} string={t.text} nested={true} />
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={t.text} nested={true} />
</span>
</li>
}
Expand All @@ -148,7 +229,7 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx }: {
<input type="checkbox" checked={item.checked} readOnly />
)}
<span>
<ChatMarkdownRender chatMessageLocationForApply={chatMessageLocationForApply} string={item.text} nested={true} />
<ChatMarkdownRender chatMessageLocation={chatMessageLocation} string={item.text} nested={true} />
</span>
</li>
))}
Expand All @@ -162,6 +243,7 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx }: {
<RenderToken key={index}
token={token}
tokenIdx={`${tokenIdx ? `${tokenIdx}-` : ''}${index}`} // assign a unique tokenId to nested components
chatMessageLocation={chatMessageLocation}
/>
))}
</>
Expand Down Expand Up @@ -221,11 +303,16 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx }: {

// inline code
if (t.type === "codespan") {
return (
<code className="font-mono font-medium rounded-sm bg-void-bg-1 px-1">
{t.text}
</code>
)

if (chatMessageLocation) {
return <CodespanWithLink
text={t.text}
rawText={t.raw}
chatMessageLocation={chatMessageLocation}
/>
}

return <Codespan text={t.text} />
}

if (t.type === "br") {
Expand All @@ -244,12 +331,12 @@ const RenderToken = ({ token, nested, chatMessageLocationForApply, tokenIdx }: {
)
}

export const ChatMarkdownRender = ({ string, nested = false, chatMessageLocationForApply }: { string: string, nested?: boolean, chatMessageLocationForApply?: ChatMessageLocation }) => {
export const ChatMarkdownRender = ({ string, nested = false, chatMessageLocation }: { string: string, nested?: boolean, chatMessageLocation: ChatMessageLocation | undefined }) => {
const tokens = marked.lexer(string); // https://marked.js.org/using_pro#renderer
return (
<>
{tokens.map((token, index) => (
<RenderToken key={index} token={token} nested={nested} chatMessageLocationForApply={chatMessageLocationForApply} tokenIdx={index + ''} />
<RenderToken key={index} token={token} nested={nested} chatMessageLocation={chatMessageLocation} tokenIdx={index + ''} />
))}
</>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ export const QuickEditChat = ({
onClose={onX}
isStreaming={isStreamingRef.current}
isDisabled={isDisabled}
featureName="Ctrl+K"
className="py-2 w-full"
onClickAnywhere={() => { textAreaRef.current?.focus() }}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

import React, { useEffect, useState } from 'react';
import { AlertCircle, ChevronDown, ChevronUp, X } from 'lucide-react';
import { errorDetails } from '../../../../../../../workbench/contrib/void/common/llmMessageTypes.js';
import { useSettingsState } from '../util/services.js';
import { errorDetails } from '../../../../common/sendLLMMessageTypes.js';


export const ErrorDisplay = ({
Expand Down
Loading