diff --git a/src/vs/workbench/contrib/void/browser/MarkerCheckService.ts b/src/vs/workbench/contrib/void/browser/MarkerCheckService.ts index f41c0513..7cdcb1e2 100644 --- a/src/vs/workbench/contrib/void/browser/MarkerCheckService.ts +++ b/src/vs/workbench/contrib/void/browser/MarkerCheckService.ts @@ -12,6 +12,7 @@ import { ITextModelService } from '../../../../editor/common/services/resolverSe import { Range } from '../../../../editor/common/core/range.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { CodeActionContext, CodeActionTriggerType } from '../../../../editor/common/languages.js'; +import { URI } from '../../../../base/common/uri.js'; export interface IMarkerCheckService { readonly _serviceBrand: undefined; @@ -99,6 +100,21 @@ class MarkerCheckService extends Disposable implements IMarkerCheckService { } + + + fixErrorsInFiles(uris: URI[], contextSoFar: []) { + const allMarkers = this._markerService.read(); + + + // check errors in files + + + // give LLM errors in files + + + + } + // private _onMarkersChanged = (changedResources: readonly URI[]): void => { // for (const resource of changedResources) { // const markers = this._markerService.read({ resource }); diff --git a/src/vs/workbench/contrib/void/browser/autocompleteService.ts b/src/vs/workbench/contrib/void/browser/autocompleteService.ts index aa8902f3..5fc8ac76 100644 --- a/src/vs/workbench/contrib/void/browser/autocompleteService.ts +++ b/src/vs/workbench/contrib/void/browser/autocompleteService.ts @@ -16,9 +16,9 @@ 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 { isWindows } from '../../../../base/common/platform.js'; import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; import { ILLMMessageService } from '../common/llmMessageService.js'; +import { _ln, allLinebreakSymbols } from '../common/voidFileService.js'; // import { IContextGatheringService } from './contextGatheringService.js'; // The extension this was called from is here - https://github.com/voideditor/void/blob/autocomplete/extensions/void/src/extension/extension.ts @@ -415,9 +415,6 @@ const toInlineCompletions = ({ autocompletionMatchup, autocompletion, prefixAndS // } -const allLinebreakSymbols = ['\r\n', '\n'] -const _ln = isWindows ? allLinebreakSymbols[0] : allLinebreakSymbols[1] - type PrefixAndSuffixInfo = { prefix: string, suffix: string, prefixLines: string[], suffixLines: string[], prefixToTheLeftOfCursor: string, suffixToTheRightOfCursor: string } const getPrefixAndSuffixInfo = (model: ITextModel, position: Position): PrefixAndSuffixInfo => { diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index cc875a79..8bbab4ad 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -12,12 +12,11 @@ import { URI } from '../../../../base/common/uri.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { IRange } from '../../../../editor/common/core/range.js'; import { ILLMMessageService } from '../common/llmMessageService.js'; -import { IModelService } from '../../../../editor/common/services/model.js'; import { chat_userMessageContent, chat_systemMessage, chat_userMessageContentWithAllFilesToo as chat_userMessageContentWithAllFiles, chat_selectionsString } from './prompt/prompts.js'; -import { IFileService } from '../../../../platform/files/common/files.js'; import { InternalToolInfo, IToolsService, ToolCallReturnType, ToolFns, ToolName, voidTools } from '../common/toolsService.js'; import { toLLMChatMessage } from '../common/llmMessageTypes.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; +import { IVoidFileService } from '../common/voidFileService.js'; const findLastIndex = (arr: T[], condition: (t: T) => boolean): number => { @@ -189,8 +188,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { constructor( @IStorageService private readonly _storageService: IStorageService, - @IModelService private readonly _modelService: IModelService, - @IFileService private readonly _fileService: IFileService, + @IVoidFileService private readonly _voidFileService: IVoidFileService, @ILLMMessageService private readonly _llmMessageService: ILLMMessageService, @IToolsService private readonly _toolsService: IToolsService, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, @@ -358,7 +356,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { // add user's message to chat history const instructions = userMessage const userMessageContent = await chat_userMessageContent(instructions, currSelns) - const selectionsStr = await chat_selectionsString(prevSelns, currSelns, this._modelService, this._fileService) + const selectionsStr = await chat_selectionsString(prevSelns, currSelns, this._voidFileService) const userMessageFullContent = chat_userMessageContentWithAllFiles(userMessageContent, selectionsStr) const userHistoryElt: ChatMessage = { role: 'user', content: userMessageContent, displayContent: instructions, selections: currSelns, state: defaultMessageState } diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index e3a5d998..02e3fc23 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -41,8 +41,7 @@ import { ICommandService } from '../../../../platform/commands/common/commands.j import { ILLMMessageService } from '../common/llmMessageService.js'; import { LLMChatMessage, errorDetails } from '../common/llmMessageTypes.js'; import { IMetricsService } from '../common/metricsService.js'; -import { VSReadFile } from './helpers/readFile.js'; -import { IFileService } from '../../../../platform/files/common/files.js'; +import { IVoidFileService } from '../common/voidFileService.js'; const configOfBG = (color: Color) => { return { dark: color, light: color, hcDark: color, hcLight: color, } @@ -255,7 +254,7 @@ class EditCodeService extends Disposable implements IEditCodeService { @IMetricsService private readonly _metricsService: IMetricsService, @INotificationService private readonly _notificationService: INotificationService, @ICommandService private readonly _commandService: ICommandService, - @IFileService private readonly _fileService: IFileService, + @IVoidFileService private readonly _voidFileService: IVoidFileService, ) { super(); @@ -1184,7 +1183,7 @@ class EditCodeService extends Disposable implements IEditCodeService { const uri = uri_ // generate search/replace block text - const origFileContents = await VSReadFile(uri, this._modelService, this._fileService) + const origFileContents = await this._voidFileService.readFile(uri) if (origFileContents === null) return diff --git a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts index f04fcb5c..90e01d50 100644 --- a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts @@ -7,10 +7,9 @@ import { URI } from '../../../../../base/common/uri.js'; import { filenameToVscodeLanguage } from '../helpers/detectLanguage.js'; import { CodeSelection, StagingSelectionItem, FileSelection } from '../chatThreadService.js'; -import { VSReadFile } from '../helpers/readFile.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; import { os } from '../helpers/systemInfo.js'; -import { IFileService } from '../../../../../platform/files/common/files.js'; +import { IVoidFileService } from '../../common/voidFileService.js'; // this is just for ease of readability @@ -169,10 +168,10 @@ ${tripleTick[1]} } const failToReadStr = 'Could not read content. This file may have been deleted. If you expected content here, you can tell the user about this as they might not know.' -const stringifyFileSelections = async (fileSelections: FileSelection[], modelService: IModelService, fileService: IFileService) => { +const stringifyFileSelections = async (fileSelections: FileSelection[], voidFileService: IVoidFileService) => { if (fileSelections.length === 0) return null const fileSlns: FileSelnLocal[] = await Promise.all(fileSelections.map(async (sel) => { - const content = await VSReadFile(sel.fileURI, modelService, fileService) ?? failToReadStr + const content = await voidFileService.readFile(sel.fileURI) ?? failToReadStr return { ...sel, content } })) return fileSlns.map(sel => stringifyFileSelection(sel)).join('\n') @@ -195,7 +194,7 @@ export const chat_userMessageContent = async (instructions: string, currSelns: S return str; }; -export const chat_selectionsString = async (prevSelns: StagingSelectionItem[] | null, currSelns: StagingSelectionItem[] | null, modelService: IModelService, fileService: IFileService) => { +export const chat_selectionsString = async (prevSelns: StagingSelectionItem[] | null, currSelns: StagingSelectionItem[] | null, voidFileService: IVoidFileService) => { // ADD IN FILES AT TOP const allSelections = [...currSelns || [], ...prevSelns || []] @@ -220,7 +219,7 @@ export const chat_selectionsString = async (prevSelns: StagingSelectionItem[] | } } - const filesStr = await stringifyFileSelections(fileSelections, modelService, fileService) + const filesStr = await stringifyFileSelections(fileSelections, voidFileService) const selnsStr = stringifyCodeSelections(codeSelections) @@ -297,12 +296,12 @@ For example, if the user is asking you to "make this variable a better name", ma - Make sure you give enough context in the code block to apply the changes to the correct location in the code` -export const aiRegex_computeReplacementsForFile_userMessage = async ({ searchClause, replaceClause, fileURI, modelService, fileService }: { searchClause: string, replaceClause: string, fileURI: URI, modelService: IModelService, fileService: IFileService }) => { +export const aiRegex_computeReplacementsForFile_userMessage = async ({ searchClause, replaceClause, fileURI, voidFileService }: { searchClause: string, replaceClause: string, fileURI: URI, modelService: IModelService, voidFileService: IVoidFileService }) => { // we may want to do this in batches const fileSelection: FileSelection = { type: 'File', fileURI, selectionStr: null, range: null } - const file = await stringifyFileSelections([fileSelection], modelService, fileService) + const file = await stringifyFileSelections([fileSelection], voidFileService) return `\ ## FILE diff --git a/src/vs/workbench/contrib/void/common/toolsService.ts b/src/vs/workbench/contrib/void/common/toolsService.ts index 3b609f60..fadbf333 100644 --- a/src/vs/workbench/contrib/void/common/toolsService.ts +++ b/src/vs/workbench/contrib/void/common/toolsService.ts @@ -5,9 +5,9 @@ import { IFileService } from '../../../../platform/files/common/files.js' import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js' import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js' import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js' -import { VSReadFile } from '../../../../workbench/contrib/void/browser/helpers/readFile.js' import { QueryBuilder } from '../../../../workbench/services/search/common/queryBuilder.js' import { ISearchService } from '../../../../workbench/services/search/common/search.js' +import { IVoidFileService } from './voidFileService.js' // tool use for AI @@ -196,6 +196,7 @@ export class ToolsService implements IToolsService { @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, @ISearchService searchService: ISearchService, @IInstantiationService instantiationService: IInstantiationService, + @IVoidFileService voidFileService: IVoidFileService, ) { const queryBuilder = instantiationService.createInstance(QueryBuilder); @@ -208,7 +209,7 @@ export class ToolsService implements IToolsService { const uri = validateURI(uriStr) const pageNumber = validatePageNum(pageNumberUnknown) - const readFileContents = await VSReadFile(uri, modelService, fileService) + const readFileContents = await voidFileService.readFile(uri) const fromIdx = MAX_FILE_CHARS_PAGE * (pageNumber - 1) const toIdx = MAX_FILE_CHARS_PAGE * pageNumber - 1 diff --git a/src/vs/workbench/contrib/void/common/voidFileService.ts b/src/vs/workbench/contrib/void/common/voidFileService.ts new file mode 100644 index 00000000..668f1869 --- /dev/null +++ b/src/vs/workbench/contrib/void/common/voidFileService.ts @@ -0,0 +1,109 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +import { isWindows } from '../../../../base/common/platform.js'; +import { URI } from '../../../../base/common/uri.js'; +import { EndOfLinePreference } from '../../../../editor/common/model.js'; +import { IModelService } from '../../../../editor/common/services/model.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; +import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; +import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; + + +// linebreak symbols +export const allLinebreakSymbols = ['\r\n', '\n'] +export const _ln = isWindows ? allLinebreakSymbols[0] : allLinebreakSymbols[1] + +export interface IVoidFileService { + readonly _serviceBrand: undefined; + + readFile(uri: URI, range?: { startLineNumber: number, endLineNumber: number }): Promise; + +} + +export const IVoidFileService = createDecorator('VoidFileService'); + +// implemented by calling channel +export class VoidFileService implements IVoidFileService { + readonly _serviceBrand: undefined; + + constructor( + @IModelService private readonly modelService: IModelService, + @IFileService private readonly fileService: IFileService, + ) { + + } + + readFile = async (uri: URI, range?: { startLineNumber: number, endLineNumber: number }): Promise => { + + // attempt to read the model + const modelResult = await this._readModel(uri, range); + if (modelResult) return modelResult; + + // if no model, read the raw file + const fileResult = await this._readFileRaw(uri, range); + if (fileResult) return fileResult; + + return ''; + } + + _readFileRaw = async (uri: URI, range?: { startLineNumber: number, endLineNumber: number }): Promise => { + + try { // this throws an error if no file exists (eg it was deleted) + + const res = await this.fileService.readFile(uri); + + if (range) { + return res.value.toString() + .split(_ln) + .slice(range.startLineNumber - 1, range.endLineNumber) + .join(_ln) + } + + return res.value.toString(); + + + } catch (e) { + return null; + } + } + + + _readModel = async (uri: URI, range?: { startLineNumber: number, endLineNumber: number }): Promise => { + + // read saved model (sometimes null if the user reloads application) + let model = this.modelService.getModel(uri); + + // check all opened models for the same `fsPath` + if (!model) { + const models = this.modelService.getModels(); + for (const m of models) { + if (m.uri.fsPath === uri.fsPath) { + model = m + break; + } + } + } + + // if still not found, return + if (!model) { return null } + + // if range, read it + if (range) { + return model.getValueInRange({ + startLineNumber: range.startLineNumber, + endLineNumber: range.endLineNumber, + startColumn: 1, + endColumn: Number.MAX_VALUE + }, EndOfLinePreference.LF); + } else { + return model.getValue(EndOfLinePreference.LF) + } + + } + +} + +registerSingleton(IVoidFileService, VoidFileService, InstantiationType.Eager); diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/postprocessToolCalls.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/postprocessToolCalls.ts index 2feeeb80..aee52dcb 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/postprocessToolCalls.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/postprocessToolCalls.ts @@ -1,7 +1,5 @@ import { ToolName, toolNames } from '../../common/toolsService.js'; - - const toolNamesSet = new Set(toolNames) export const isAToolName = (toolName: string): toolName is ToolName => {