diff options
author | Nate Sesti <sestinj@gmail.com> | 2023-08-25 13:38:41 -0700 |
---|---|---|
committer | Nate Sesti <sestinj@gmail.com> | 2023-08-25 13:38:41 -0700 |
commit | e5f56308c5fd87695278682b2a36ca60df0db863 (patch) | |
tree | c7d66f5a3b56ce762bfd26033890597a07099007 | |
parent | a55d64127a1e972d03f54a175b54eb0ad78e2b0e (diff) | |
download | sncontinue-e5f56308c5fd87695278682b2a36ca60df0db863.tar.gz sncontinue-e5f56308c5fd87695278682b2a36ca60df0db863.tar.bz2 sncontinue-e5f56308c5fd87695278682b2a36ca60df0db863.zip |
fix: :bug: ssh compatibility by reading from vscode.workspace.fs
-rw-r--r-- | continuedev/src/continuedev/plugins/context_providers/file.py | 66 | ||||
-rw-r--r-- | continuedev/src/continuedev/server/ide.py | 14 | ||||
-rw-r--r-- | continuedev/src/continuedev/server/ide_protocol.py | 4 | ||||
-rw-r--r-- | docs/docs/customization.md | 2 | ||||
-rw-r--r-- | extension/react-app/src/pages/settings.tsx | 2 | ||||
-rw-r--r-- | extension/src/continueIdeClient.ts | 65 | ||||
-rw-r--r-- | extension/src/diffs.ts | 84 | ||||
-rw-r--r-- | extension/src/util/util.ts | 45 | ||||
-rw-r--r-- | extension/src/util/vscode.ts | 65 |
9 files changed, 151 insertions, 196 deletions
diff --git a/continuedev/src/continuedev/plugins/context_providers/file.py b/continuedev/src/continuedev/plugins/context_providers/file.py index a748379e..9846dd3e 100644 --- a/continuedev/src/continuedev/plugins/context_providers/file.py +++ b/continuedev/src/continuedev/plugins/context_providers/file.py @@ -1,25 +1,20 @@ import asyncio import os -from fnmatch import fnmatch from typing import List from ...core.context import ContextProvider from ...core.main import ContextItem, ContextItemDescription, ContextItemId +from ...core.sdk import ContinueSDK from .util import remove_meilisearch_disallowed_chars -MAX_SIZE_IN_BYTES = 1024 * 1024 * 1 +MAX_SIZE_IN_CHARS = 25_000 -def get_file_contents(filepath: str) -> str: +async def get_file_contents(filepath: str, sdk: ContinueSDK) -> str: try: - filesize = os.path.getsize(filepath) - if filesize > MAX_SIZE_IN_BYTES: - return None - - with open(filepath, "r") as f: - return f.read() - except Exception: - # Some files cannot be read, e.g. binary files + return (await sdk.ide.readFile(filepath))[:MAX_SIZE_IN_CHARS] + except Exception as e: + print(f"Failed to read file {filepath}: {e}") return None @@ -105,7 +100,7 @@ class FileContextProvider(ContextProvider): async def get_context_item_for_filepath( self, absolute_filepath: str ) -> ContextItem: - content = get_file_contents(absolute_filepath) + content = await get_file_contents(absolute_filepath, self.sdk) if content is None: return None @@ -128,26 +123,35 @@ class FileContextProvider(ContextProvider): ) async def provide_context_items(self, workspace_dir: str) -> List[ContextItem]: + contents = await self.sdk.ide.listDirectoryContents(workspace_dir) + if contents is None: + return [] + absolute_filepaths: List[str] = [] - for root, dir_names, file_names in os.walk(workspace_dir): - dir_names[:] = [ - d - for d in dir_names - if not any(fnmatch(d, pattern) for pattern in self.ignore_patterns) + for filepath in contents[:1000]: + absolute_filepaths.append(filepath) + + # for root, dir_names, file_names in os.walk(workspace_dir): + # dir_names[:] = [ + # d + # for d in dir_names + # if not any(fnmatch(d, pattern) for pattern in self.ignore_patterns) + # ] + # for file_name in file_names: + # absolute_filepaths.append(os.path.join(root, file_name)) + + # if len(absolute_filepaths) > 1000: + # break + + # if len(absolute_filepaths) > 1000: + # break + + items = await asyncio.gather( + *[ + self.get_context_item_for_filepath(filepath) + for filepath in absolute_filepaths ] - for file_name in file_names: - absolute_filepaths.append(os.path.join(root, file_name)) - - if len(absolute_filepaths) > 1000: - break - - if len(absolute_filepaths) > 1000: - break - - items = [] - for absolute_filepath in absolute_filepaths: - item = await self.get_context_item_for_filepath(absolute_filepath) - if item is not None: - items.append(item) + ) + items = list(filter(lambda item: item is not None, items)) return items diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 610a1a48..871724db 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -120,6 +120,10 @@ class TerminalContentsResponse(BaseModel): contents: str +class ListDirectoryContentsResponse(BaseModel): + contents: List[str] + + T = TypeVar("T", bound=BaseModel) @@ -241,6 +245,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer): "getUserSecret", "runCommand", "getTerminalContents", + "listDirectoryContents", ]: self.sub_queue.post(message_type, data) elif message_type == "workspaceDirectory": @@ -477,6 +482,15 @@ class IdeProtocolServer(AbstractIdeProtocolServer): ) return resp.fileEdit + async def listDirectoryContents(self, directory: str) -> List[str]: + """List the contents of a directory""" + resp = await self._send_and_receive_json( + {"directory": directory}, + ListDirectoryContentsResponse, + "listDirectoryContents", + ) + return resp.contents + async def applyFileSystemEdit(self, edit: FileSystemEdit) -> EditDiff: """Apply a file edit""" backward = None diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py index 435c82e2..f37c1737 100644 --- a/continuedev/src/continuedev/server/ide_protocol.py +++ b/continuedev/src/continuedev/server/ide_protocol.py @@ -147,5 +147,9 @@ class AbstractIdeProtocolServer(ABC): def onFileSaved(self, filepath: str, contents: str): """Called when a file is saved""" + @abstractmethod + async def listDirectoryContents(self, directory: str) -> List[str]: + """List directory contents""" + workspace_directory: str unique_id: str diff --git a/docs/docs/customization.md b/docs/docs/customization.md index 3240c185..a1a9111e 100644 --- a/docs/docs/customization.md +++ b/docs/docs/customization.md @@ -156,7 +156,7 @@ config = ContinueConfig( default=OpenAI(api_key="my-api-key", model="gpt-3.5-turbo", openai_server_info=OpenAIServerInfo( api_base="https://my-azure-openai-instance.openai.azure.com/", engine="my-azure-openai-deployment", - api_version="2023-03-15-preview", + api_version="2023-03-15-preview", # NOTE: It is recommended not to change api_version. Newer versions may not work correctly. api_type="azure" )) ) diff --git a/extension/react-app/src/pages/settings.tsx b/extension/react-app/src/pages/settings.tsx index 8fd91ff5..8bb9b5d2 100644 --- a/extension/react-app/src/pages/settings.tsx +++ b/extension/react-app/src/pages/settings.tsx @@ -94,7 +94,7 @@ function Settings() { const temperature = formMethods.watch("temperature"); // const models = formMethods.watch("models"); - if (systemMessage) client.setSystemMessage(systemMessage); + client.setSystemMessage(systemMessage || ""); if (temperature) client.setTemperature(temperature); // if (models) { diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 430bb9dd..19575b13 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -12,10 +12,10 @@ import { rejectSuggestionCommand, } from "./suggestions"; import { FileEditWithFullContents } from "../schema/FileEditWithFullContents"; -import * as fs from "fs"; import { WebsocketMessenger } from "./util/messenger"; import { diffManager } from "./diffs"; const os = require("os"); +const path = require("path"); const continueVirtualDocumentScheme = "continue"; @@ -253,7 +253,7 @@ class IdeProtocolClient { break; case "readFile": messenger.send("readFile", { - contents: this.readFile(data.filepath), + contents: await this.readFile(data.filepath), }); break; case "getTerminalContents": @@ -261,6 +261,44 @@ class IdeProtocolClient { contents: await this.getTerminalContents(), }); break; + case "listDirectoryContents": + messenger.send("listDirectoryContents", { + contents: ( + await vscode.workspace.fs.readDirectory( + vscode.Uri.file(data.directory) + ) + ) + .map(([name, type]) => name) + .filter((name) => { + const DEFAULT_IGNORE_DIRS = [ + ".git", + ".vscode", + ".idea", + ".vs", + ".venv", + "env", + ".env", + "node_modules", + "dist", + "build", + "target", + "out", + "bin", + ".pytest_cache", + ".vscode-test", + ".continue", + "__pycache__", + ]; + if ( + !DEFAULT_IGNORE_DIRS.some((dir) => + name.split(path.sep).includes(dir) + ) + ) { + return name; + } + }), + }); + break; case "editFile": const fileEdit = await this.editFile(data.edit); messenger.send("editFile", { @@ -306,7 +344,7 @@ class IdeProtocolClient { this.showSuggestion(data.edit); break; case "showDiff": - this.showDiff(data.filepath, data.replacement, data.step_index); + await this.showDiff(data.filepath, data.replacement, data.step_index); break; case "getSessionId": case "connected": @@ -385,8 +423,8 @@ class IdeProtocolClient { ); } - showDiff(filepath: string, replacement: string, step_index: number) { - diffManager.writeDiff(filepath, replacement, step_index); + async showDiff(filepath: string, replacement: string, step_index: number) { + await diffManager.writeDiff(filepath, replacement, step_index); } openFile(filepath: string) { @@ -506,19 +544,14 @@ class IdeProtocolClient { }); } - readFile(filepath: string): string { + async readFile(filepath: string): Promise<string> { let contents: string | undefined; - vscode.window.visibleTextEditors - .filter((editor) => this.editorIsCode(editor)) - .forEach((editor) => { - if (editor.document.uri.fsPath === filepath) { - contents = editor.document.getText(); - } - }); if (typeof contents === "undefined") { - if (fs.existsSync(filepath)) { - contents = fs.readFileSync(filepath, "utf-8"); - } else { + try { + contents = await vscode.workspace.fs + .readFile(vscode.Uri.file(filepath)) + .then((bytes) => new TextDecoder().decode(bytes)); + } catch { contents = ""; } } diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts index efaf7626..d2d1dae3 100644 --- a/extension/src/diffs.ts +++ b/extension/src/diffs.ts @@ -14,6 +14,19 @@ interface DiffInfo { range: vscode.Range; } +async function readFile(path: string): Promise<string> { + return await vscode.workspace.fs + .readFile(vscode.Uri.file(path)) + .then((bytes) => new TextDecoder().decode(bytes)); +} + +async function writeFile(path: string, contents: string) { + await vscode.workspace.fs.writeFile( + vscode.Uri.file(path), + new TextEncoder().encode(contents) + ); +} + export const DIFF_DIRECTORY = path .join(os.homedir(), ".continue", "diffs") .replace(/^C:/, "c:"); @@ -27,13 +40,9 @@ class DiffManager { return this.diffs.get(newFilepath); } - private setupDirectory() { + private async setupDirectory() { // Make sure the diff directory exists - if (!fs.existsSync(DIFF_DIRECTORY)) { - fs.mkdirSync(DIFF_DIRECTORY, { - recursive: true, - }); - } + await vscode.workspace.fs.createDirectory(vscode.Uri.file(DIFF_DIRECTORY)); } constructor() { @@ -57,15 +66,17 @@ class DiffManager { return path.join(DIFF_DIRECTORY, this.escapeFilepath(originalFilepath)); } - private openDiffEditor( + private async openDiffEditor( originalFilepath: string, newFilepath: string - ): vscode.TextEditor | undefined { - // If the file doesn't yet exist or the basename is a single digit number (git hash object or something), don't open the diff editor - if ( - !fs.existsSync(newFilepath) || - path.basename(originalFilepath).match(/^\d$/) - ) { + ): Promise<vscode.TextEditor | undefined> { + // If the file doesn't yet exist or the basename is a single digit number (vscode terminal), don't open the diff editor + try { + await vscode.workspace.fs.stat(vscode.Uri.file(newFilepath)); + } catch { + return undefined; + } + if (path.basename(originalFilepath).match(/^\d$/)) { return undefined; } @@ -128,21 +139,21 @@ class DiffManager { return 0; } - writeDiff( + async writeDiff( originalFilepath: string, newContent: string, step_index: number - ): string { - this.setupDirectory(); + ): Promise<string> { + await this.setupDirectory(); // Create or update existing diff const newFilepath = this.getNewFilepath(originalFilepath); - fs.writeFileSync(newFilepath, newContent); + await writeFile(newFilepath, newContent); // Open the diff editor if this is a new diff if (!this.diffs.has(newFilepath)) { // Figure out the first line that is different - const oldContent = ideProtocolClient.readFile(originalFilepath); + const oldContent = await ideProtocolClient.readFile(originalFilepath); const line = this._findFirstDifferentLine(oldContent, newContent); const diffInfo: DiffInfo = { @@ -157,7 +168,10 @@ class DiffManager { // Open the editor if it hasn't been opened yet const diffInfo = this.diffs.get(newFilepath); if (diffInfo && !diffInfo?.editor) { - diffInfo.editor = this.openDiffEditor(originalFilepath, newFilepath); + diffInfo.editor = await this.openDiffEditor( + originalFilepath, + newFilepath + ); this.diffs.set(newFilepath, diffInfo); } @@ -207,7 +221,7 @@ class DiffManager { return undefined; } - acceptDiff(newFilepath?: string) { + async acceptDiff(newFilepath?: string) { // When coming from a keyboard shortcut, we have to infer the newFilepath from visible text editors if (!newFilepath) { newFilepath = this.inferNewFilepath(); @@ -227,18 +241,18 @@ class DiffManager { vscode.workspace.textDocuments .find((doc) => doc.uri.fsPath === newFilepath) ?.save() - .then(() => { - fs.writeFileSync( + .then(async () => { + await writeFile( diffInfo.originalFilepath, - fs.readFileSync(diffInfo.newFilepath) + await readFile(diffInfo.newFilepath) ); this.cleanUpDiff(diffInfo); }); - recordAcceptReject(true, diffInfo); + await recordAcceptReject(true, diffInfo); } - rejectDiff(newFilepath?: string) { + async rejectDiff(newFilepath?: string) { // If no newFilepath is provided and there is only one in the dictionary, use that if (!newFilepath) { newFilepath = this.inferNewFilepath(); @@ -266,13 +280,13 @@ class DiffManager { this.cleanUpDiff(diffInfo); }); - recordAcceptReject(false, diffInfo); + await recordAcceptReject(false, diffInfo); } } export const diffManager = new DiffManager(); -function recordAcceptReject(accepted: boolean, diffInfo: DiffInfo) { +async function recordAcceptReject(accepted: boolean, diffInfo: DiffInfo) { const devDataDir = devDataPath(); const suggestionsPath = path.join(devDataDir, "suggestions.json"); @@ -280,10 +294,10 @@ function recordAcceptReject(accepted: boolean, diffInfo: DiffInfo) { let suggestions = []; // Check if suggestions.json exists - if (fs.existsSync(suggestionsPath)) { - const rawData = fs.readFileSync(suggestionsPath, "utf-8"); + try { + const rawData = await readFile(suggestionsPath); suggestions = JSON.parse(rawData); - } + } catch {} // Add the new suggestion to the list suggestions.push({ @@ -296,19 +310,15 @@ function recordAcceptReject(accepted: boolean, diffInfo: DiffInfo) { // ideProtocolClient.sendAcceptRejectSuggestion(accepted); // Write the updated suggestions back to the file - fs.writeFileSync( - suggestionsPath, - JSON.stringify(suggestions, null, 4), - "utf-8" - ); + await writeFile(suggestionsPath, JSON.stringify(suggestions, null, 4)); } export async function acceptDiffCommand(newFilepath?: string) { - diffManager.acceptDiff(newFilepath); + await diffManager.acceptDiff(newFilepath); ideProtocolClient.sendAcceptRejectDiff(true); } export async function rejectDiffCommand(newFilepath?: string) { - diffManager.rejectDiff(newFilepath); + await diffManager.rejectDiff(newFilepath); ideProtocolClient.sendAcceptRejectDiff(false); } diff --git a/extension/src/util/util.ts b/extension/src/util/util.ts index 15b34267..38c955e7 100644 --- a/extension/src/util/util.ts +++ b/extension/src/util/util.ts @@ -1,5 +1,3 @@ -import { RangeInFile } from "../../schema/RangeInFile"; -import * as fs from "fs"; const os = require("os"); function charIsEscapedAtIndex(index: number, str: string): boolean { @@ -52,49 +50,6 @@ export function convertSingleToDoubleQuoteJSON(json: string): string { return newJson; } -export async function readRangeInFile( - rangeInFile: RangeInFile -): Promise<string> { - const range = rangeInFile.range; - return new Promise((resolve, reject) => { - fs.readFile(rangeInFile.filepath, (err, data) => { - if (err) { - reject(err); - } else { - let lines = data.toString().split("\n"); - if (range.start.line === range.end.line) { - resolve( - lines[rangeInFile.range.start.line].slice( - rangeInFile.range.start.character, - rangeInFile.range.end.character - ) - ); - } else { - let firstLine = lines[range.start.line].slice(range.start.character); - let lastLine = lines[range.end.line].slice(0, range.end.character); - let middleLines = lines.slice(range.start.line + 1, range.end.line); - resolve([firstLine, ...middleLines, lastLine].join("\n")); - } - } - }); - }); -} - -export function codeSelectionsToVirtualFileSystem( - codeSelections: RangeInFile[] -): { - [filepath: string]: string; -} { - let virtualFileSystem: { [filepath: string]: string } = {}; - for (let cs of codeSelections) { - if (!cs.filepath) continue; - if (cs.filepath in virtualFileSystem) continue; - let content = fs.readFileSync(cs.filepath, "utf8"); - virtualFileSystem[cs.filepath] = content; - } - return virtualFileSystem; -} - export function debounced(delay: number, fn: Function) { let timerId: NodeJS.Timeout | null; return function (...args: any[]) { diff --git a/extension/src/util/vscode.ts b/extension/src/util/vscode.ts index 3110d589..bf0fa1e5 100644 --- a/extension/src/util/vscode.ts +++ b/extension/src/util/vscode.ts @@ -1,6 +1,4 @@ import * as vscode from "vscode"; -import * as path from "path"; -import * as fs from "fs"; export function translate(range: vscode.Range, lines: number): vscode.Range { return new vscode.Range( @@ -21,39 +19,6 @@ export function getNonce() { return text; } -export function getTestFile( - filename: string, - createFile: boolean = false -): string { - let basename = path.basename(filename).split(".")[0]; - switch (path.extname(filename)) { - case ".py": - basename += "_test"; - break; - case ".js": - case ".jsx": - case ".ts": - case ".tsx": - basename += ".test"; - break; - default: - basename += "_test"; - } - - const directory = path.join(path.dirname(filename), "tests"); - const testFilename = path.join(directory, basename + path.extname(filename)); - - // Optionally, create the file if it doesn't exist - if (createFile && !fs.existsSync(testFilename)) { - if (!fs.existsSync(directory)) { - fs.mkdirSync(directory); - } - fs.writeFileSync(testFilename, ""); - } - - return testFilename; -} - export function getExtensionUri(): vscode.Uri { return vscode.extensions.getExtension("Continue.continue")!.extensionUri; } @@ -100,36 +65,6 @@ export function getRightViewColumn(): vscode.ViewColumn { return column; } -export async function readFileAtRange( - range: vscode.Range, - filepath: string -): Promise<string> { - return new Promise((resolve, reject) => { - fs.readFile(filepath, (err, data) => { - if (err) { - reject(err); - } else { - let lines = data.toString().split("\n"); - if (range.isSingleLine) { - resolve( - lines[range.start.line].slice( - range.start.character, - range.end.character - ) - ); - } else { - const firstLine = lines[range.start.line].slice( - range.start.character - ); - const lastLine = lines[range.end.line].slice(0, range.end.character); - const middleLines = lines.slice(range.start.line + 1, range.end.line); - resolve([firstLine, ...middleLines, lastLine].join("\n")); - } - } - }); - }); -} - let showTextDocumentInProcess = false; export function openEditorAndRevealRange( |