diff options
28 files changed, 139 insertions, 593 deletions
@@ -19,7 +19,7 @@ ### Install for VS Code -Learn how to install locally in VS Code [here](https://continue.dev/docs/install) +Learn how to install locally in VS Code [here](https://continue.dev/docs/getting-started) ### GitHub Codespaces diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index a6e688ae..f14a4127 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -40,11 +40,14 @@ class Autopilot(ContinueBaseModel): keep_untouched = (cached_property,) def get_full_state(self) -> FullState: - return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) + return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue, default_model=self.continue_sdk.config.default_model) async def get_available_slash_commands(self) -> List[Dict]: return list(map(lambda x: {"name": x.name, "description": x.description}, self.continue_sdk.config.slash_commands)) or [] + async def change_default_model(self, model: str): + self.continue_sdk.update_default_model(model) + async def clear_history(self): self.history = History.from_empty() self._main_user_input_queue = [] diff --git a/continuedev/src/continuedev/core/config.py b/continuedev/src/continuedev/core/config.py index 652320fb..01316f1b 100644 --- a/continuedev/src/continuedev/core/config.py +++ b/continuedev/src/continuedev/core/config.py @@ -86,3 +86,46 @@ def load_config(config_file: str) -> ContinueConfig: else: raise ValueError(f'Unknown config file extension: {ext}') return ContinueConfig(**config_dict) + + +def load_global_config() -> ContinueConfig: + """ + Load the global config file and return a ContinueConfig object. + """ + global_dir = os.path.expanduser('~/.continue') + if not os.path.exists(global_dir): + os.mkdir(global_dir) + + yaml_path = os.path.join(global_dir, 'config.yaml') + if os.path.exists(yaml_path): + with open(config_path, 'r') as f: + try: + config_dict = yaml.safe_load(f) + except: + return ContinueConfig() + else: + config_path = os.path.join(global_dir, 'config.json') + with open(config_path, 'r') as f: + try: + config_dict = json.load(f) + except: + return ContinueConfig() + return ContinueConfig(**config_dict) + + +def update_global_config(config: ContinueConfig): + """ + Update the config file with the given ContinueConfig object. + """ + global_dir = os.path.expanduser('~/.continue') + if not os.path.exists(global_dir): + os.mkdir(global_dir) + + yaml_path = os.path.join(global_dir, 'config.yaml') + if os.path.exists(yaml_path): + with open(config_path, 'w') as f: + yaml.dump(config.dict(), f) + else: + config_path = os.path.join(global_dir, 'config.json') + with open(config_path, 'w') as f: + json.dump(config.dict(), f) diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py index 0dd5bb64..b9ae9eba 100644 --- a/continuedev/src/continuedev/core/main.py +++ b/continuedev/src/continuedev/core/main.py @@ -200,6 +200,7 @@ class FullState(ContinueBaseModel): history: History active: bool user_input_queue: List[str] + default_model: str class ContinueSDK: diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index be3bc9d4..62361250 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -6,7 +6,7 @@ import os from ..steps.core.core import DefaultModelEditCodeStep from ..models.main import Range from .abstract_sdk import AbstractContinueSDK -from .config import ContinueConfig, load_config +from .config import ContinueConfig, load_config, load_global_config, update_global_config from ..models.filesystem_edit import FileEdit, FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory from ..models.filesystem import RangeInFile from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI @@ -71,7 +71,7 @@ class Models: else: raise Exception(f"Unknown model {model_name}") - @cached_property + @property def default(self): default_model = self.sdk.config.default_model return self.__model_from_name(default_model) if default_model is not None else self.gpt35 @@ -163,7 +163,12 @@ class ContinueSDK(AbstractContinueSDK): elif os.path.exists(json_path): return load_config(json_path) else: - return ContinueConfig() + return load_global_config() + + def update_default_model(self, model: str): + config = self.config + config.default_model = model + update_global_config(config) def set_loading_message(self, message: str): # self.__autopilot.set_loading_message(message) diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index cf046734..cc6235e9 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -77,6 +77,8 @@ class GUIProtocolServer(AbstractGUIProtocolServer): self.on_reverse_to_index(data["index"]) elif message_type == "retry_at_index": self.on_retry_at_index(data["index"]) + elif message_type == "change_default_model": + self.on_change_default_model(data["model"]) elif message_type == "clear_history": self.on_clear_history() elif message_type == "delete_at_index": @@ -116,6 +118,9 @@ class GUIProtocolServer(AbstractGUIProtocolServer): asyncio.create_task( self.session.autopilot.retry_at_index(index)) + def on_change_default_model(self, model: str): + asyncio.create_task(self.session.autopilot.change_default_model(model)) + def on_clear_history(self): asyncio.create_task(self.session.autopilot.clear_history()) diff --git a/continuedev/src/continuedev/server/gui_protocol.py b/continuedev/src/continuedev/server/gui_protocol.py index d9506c6f..66839d9b 100644 --- a/continuedev/src/continuedev/server/gui_protocol.py +++ b/continuedev/src/continuedev/server/gui_protocol.py @@ -36,6 +36,10 @@ class AbstractGUIProtocolServer(ABC): """Called when the user requests a retry at a previous index""" @abstractmethod + def on_change_default_model(self): + """Called when the user requests to change the default model""" + + @abstractmethod def on_clear_history(self): """Called when the user requests to clear the history""" diff --git a/extension/package-lock.json b/extension/package-lock.json index 79b010cc..12b2fe13 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.60", + "version": "0.0.61", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.60", + "version": "0.0.61", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 94a58ebd..9fd1d7a6 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Accelerating software development with language models", - "version": "0.0.60", + "version": "0.0.61", "publisher": "Continue", "engines": { "vscode": "^1.74.0" diff --git a/extension/react-app/src/components/ContinueButton.tsx b/extension/react-app/src/components/ContinueButton.tsx index c6117bf9..ef6719b7 100644 --- a/extension/react-app/src/components/ContinueButton.tsx +++ b/extension/react-app/src/components/ContinueButton.tsx @@ -1,6 +1,8 @@ import styled, { keyframes } from "styled-components"; import { Button } from "."; import { Play } from "@styled-icons/heroicons-outline"; +import { useSelector } from "react-redux"; +import { RootStore } from "../redux/store"; let StyledButton = styled(Button)` margin: auto; @@ -25,14 +27,21 @@ let StyledButton = styled(Button)` `; function ContinueButton(props: { onClick?: () => void; hidden?: boolean }) { + const vscMediaUrl = useSelector( + (state: RootStore) => state.config.vscMediaUrl + ); + return ( <StyledButton hidden={props.hidden} className="m-auto" onClick={props.onClick} > - <Play /> - {/* <img src={"/continue_arrow.png"} width="16px"></img> */} + {vscMediaUrl ? ( + <img src={`${vscMediaUrl}/play_button.png`} width="22px" /> + ) : ( + <Play /> + )} Continue </StyledButton> ); diff --git a/extension/react-app/src/components/DebugPanel.tsx b/extension/react-app/src/components/DebugPanel.tsx index 30f38779..94dbac9e 100644 --- a/extension/react-app/src/components/DebugPanel.tsx +++ b/extension/react-app/src/components/DebugPanel.tsx @@ -6,6 +6,7 @@ import { setApiUrl, setVscMachineId, setSessionId, + setVscMediaUrl, } from "../redux/slices/configSlice"; import { setHighlightedCode } from "../redux/slices/miscSlice"; import { updateFileSystem } from "../redux/slices/debugContexSlice"; @@ -37,6 +38,7 @@ function DebugPanel(props: DebugPanelProps) { dispatch(setApiUrl(event.data.apiUrl)); dispatch(setVscMachineId(event.data.vscMachineId)); dispatch(setSessionId(event.data.sessionId)); + dispatch(setVscMediaUrl(event.data.vscMediaUrl)); break; case "highlightedCode": dispatch(setHighlightedCode(event.data.rangeInFile)); diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts index 824bb086..3d8e0a38 100644 --- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts +++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts @@ -13,6 +13,8 @@ abstract class AbstractContinueGUIClientProtocol { callback: (commands: { name: string; description: string }[]) => void ): void; + abstract changeDefaultModel(model: string): void; + abstract sendClear(): void; abstract retryAtIndex(index: number): void; diff --git a/extension/react-app/src/hooks/useContinueGUIProtocol.ts b/extension/react-app/src/hooks/useContinueGUIProtocol.ts index 59397742..f43a66ff 100644 --- a/extension/react-app/src/hooks/useContinueGUIProtocol.ts +++ b/extension/react-app/src/hooks/useContinueGUIProtocol.ts @@ -55,6 +55,10 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { }); } + changeDefaultModel(model: string) { + this.messenger.send("change_default_model", { model }); + } + sendClear() { this.messenger.send("clear_history", {}); } diff --git a/extension/react-app/src/redux/slices/configSlice.ts b/extension/react-app/src/redux/slices/configSlice.ts index a6a641e6..1b107bed 100644 --- a/extension/react-app/src/redux/slices/configSlice.ts +++ b/extension/react-app/src/redux/slices/configSlice.ts @@ -37,9 +37,21 @@ export const configSlice = createSlice({ ...state, sessionId: action.payload, }), + setVscMediaUrl: ( + state: RootStore["config"], + action: { type: string; payload: string } + ) => ({ + ...state, + vscMediaUrl: action.payload, + }), }, }); -export const { setVscMachineId, setApiUrl, setWorkspacePath, setSessionId } = - configSlice.actions; +export const { + setVscMachineId, + setApiUrl, + setWorkspacePath, + setSessionId, + setVscMediaUrl, +} = configSlice.actions; export default configSlice.reducer; diff --git a/extension/react-app/src/redux/store.ts b/extension/react-app/src/redux/store.ts index f9eb0517..a5eef4ba 100644 --- a/extension/react-app/src/redux/store.ts +++ b/extension/react-app/src/redux/store.ts @@ -21,6 +21,7 @@ export interface RootStore { vscMachineId: string | undefined; sessionId: string | undefined; sessionStarted: number | undefined; + vscMediaUrl: string | undefined; }; chat: { messages: ChatMessage[]; diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx index d6131174..5001fe4b 100644 --- a/extension/react-app/src/tabs/gui.tsx +++ b/extension/react-app/src/tabs/gui.tsx @@ -215,7 +215,7 @@ function GUI(props: GUIProps) { useEffect(() => { const listener = (e: any) => { // Cmd + J to toggle fast model - if (e.key === "j" && e.metaKey) { + if (e.key === "i" && e.metaKey && e.shiftKey) { setUsingFastModel((prev) => !prev); } }; @@ -230,21 +230,24 @@ function GUI(props: GUIProps) { console.log("CLIENT ON STATE UPDATE: ", client, client?.onStateUpdate); client?.onStateUpdate((state) => { // Scroll only if user is at very bottom of the window. + setUsingFastModel(state.default_model === "gpt-3.5-turbo"); const shouldScrollToBottom = topGuiDivRef.current && topGuiDivRef.current?.offsetHeight - window.scrollY < 100; setWaitingForSteps(state.active); setHistory(state.history); setUserInputQueue(state.user_input_queue); - const nextStepsOpen = [...stepsOpen]; - for ( - let i = nextStepsOpen.length; - i < state.history.timeline.length; - i++ - ) { - nextStepsOpen.push(true); - } - setStepsOpen(nextStepsOpen); + setStepsOpen((prev) => { + const nextStepsOpen = [...prev]; + for ( + let i = nextStepsOpen.length; + i < state.history.timeline.length; + i++ + ) { + nextStepsOpen.push(true); + } + return nextStepsOpen; + }); if (shouldScrollToBottom) { scrollToBottom(); @@ -470,8 +473,11 @@ function GUI(props: GUIProps) { Contribute Data </span> </div> - {/* <HeaderButtonWithText + <HeaderButtonWithText onClick={() => { + client?.changeDefaultModel( + usingFastModel ? "gpt-4" : "gpt-3.5-turbo" + ); setUsingFastModel((prev) => !prev); }} text={usingFastModel ? "gpt-3.5-turbo" : "gpt-4"} @@ -481,7 +487,7 @@ function GUI(props: GUIProps) { > {usingFastModel ? "⚡" : "🧠"} </div> - </HeaderButtonWithText> */} + </HeaderButtonWithText> <HeaderButtonWithText onClick={() => { client?.sendClear(); diff --git a/extension/scripts/continuedev-0.1.1-py3-none-any.whl b/extension/scripts/continuedev-0.1.1-py3-none-any.whl Binary files differindex 99f96d80..29e41d3c 100644 --- a/extension/scripts/continuedev-0.1.1-py3-none-any.whl +++ b/extension/scripts/continuedev-0.1.1-py3-none-any.whl diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index 32726c86..c4bdbaa9 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -2,13 +2,12 @@ import * as vscode from "vscode"; import { registerAllCommands } from "../commands"; import { registerAllCodeLensProviders } from "../lang-server/codeLens"; import { sendTelemetryEvent, TelemetryEvent } from "../telemetry"; -import { getExtensionUri } from "../util/vscode"; -import * as path from "path"; // import { openCapturedTerminal } from "../terminal/terminalEmulator"; import IdeProtocolClient from "../continueIdeClient"; import { getContinueServerUrl } from "../bridge"; -import { setupDebugPanel, ContinueGUIWebviewViewProvider } from "../debugPanel"; import { CapturedTerminal } from "../terminal/terminalEmulator"; +import { setupDebugPanel, ContinueGUIWebviewViewProvider } from "../debugPanel"; +// import { CapturedTerminal } from "../terminal/terminalEmulator"; export let extensionContext: vscode.ExtensionContext | undefined = undefined; @@ -49,41 +48,10 @@ export function activateExtension( })(); // All opened terminals should be replaced by our own terminal - vscode.window.onDidOpenTerminal((terminal) => { - if (terminal.name === "Continue") { - return; - } - const options = terminal.creationOptions; - const capturedTerminal = new CapturedTerminal({ - ...options, - name: "Continue", - }); - terminal.dispose(); - if (!ideProtocolClient.continueTerminal) { - ideProtocolClient.continueTerminal = capturedTerminal; - } - }); + // vscode.window.onDidOpenTerminal((terminal) => {}); // If any terminals are open to start, replace them - vscode.window.terminals.forEach((terminal) => { - if (terminal.name === "Continue") { - return; - } - const options = terminal.creationOptions; - const capturedTerminal = new CapturedTerminal( - { - ...options, - name: "Continue", - }, - (commandOutput: string) => { - ideProtocolClient.sendCommandOutput(commandOutput); - } - ); - terminal.dispose(); - if (!ideProtocolClient.continueTerminal) { - ideProtocolClient.continueTerminal = capturedTerminal; - } - }); + // vscode.window.terminals.forEach((terminal) => {} extensionContext = context; } diff --git a/extension/src/commands.ts b/extension/src/commands.ts index 22e15c43..77273343 100644 --- a/extension/src/commands.ts +++ b/extension/src/commands.ts @@ -3,7 +3,6 @@ import { decorationManager, showAnswerInTextEditor, showGutterSpinner, - writeAndShowUnitTest, } from "./decorations"; import { acceptSuggestionCommand, @@ -12,19 +11,8 @@ import { suggestionUpCommand, } from "./suggestions"; import * as bridge from "./bridge"; -import { debugPanelWebview, setupDebugPanel } from "./debugPanel"; -// import { openCapturedTerminal } from "./terminal/terminalEmulator"; -import { getRightViewColumn } from "./util/vscode"; -import { - findSuspiciousCode, - runPythonScript, - writeUnitTestForFunction, -} from "./bridge"; +import { debugPanelWebview } from "./debugPanel"; import { sendTelemetryEvent, TelemetryEvent } from "./telemetry"; -import { getLanguageLibrary } from "./languages"; -import { SerializedDebugContext } from "./client"; -import { addFileSystemToDebugContext } from "./util/util"; -import { ideProtocolClient } from "./activation/activate"; // Copy everything over from extension.ts const commandsMap: { [command: string]: (...args: any) => any } = { @@ -67,72 +55,9 @@ const commandsMap: { [command: string]: (...args: any) => any } = { type: "focusContinueInput", }); }, - "continue.openCapturedTerminal": () => { - // Happens in webview resolution function - // openCapturedTerminal(); - }, - "continue.findSuspiciousCode": async ( - debugContext: SerializedDebugContext - ) => { - vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: "Finding suspicious code", - cancellable: false, - }, - async (progress, token) => { - let suspiciousCode = await findSuspiciousCode(debugContext); - debugContext.rangesInFiles = suspiciousCode; - let { filesystem } = addFileSystemToDebugContext(debugContext); - debugPanelWebview?.postMessage({ - type: "findSuspiciousCode", - codeLocations: suspiciousCode, - filesystem, - }); - } - ); - }, - "continue.debugTest": async (fileAndFunctionSpecifier: string) => { - sendTelemetryEvent(TelemetryEvent.AutoDebugThisTest); - let editor = vscode.window.activeTextEditor; - if (editor) editor.document.save(); - let { stdout } = await runPythonScript("run_unit_test.py", [ - fileAndFunctionSpecifier, - ]); - let traceback = getLanguageLibrary( - fileAndFunctionSpecifier.split("::")[0] - ).parseFirstStacktrace(stdout); - if (!traceback) { - vscode.window.showInformationMessage("The test passes!"); - return; - } - vscode.commands.executeCommand("continue.openContinueGUI").then(() => { - setTimeout(() => { - debugPanelWebview?.postMessage({ - type: "traceback", - value: traceback, - }); - }, 500); - }); - }, }; const textEditorCommandsMap: { [command: string]: (...args: any) => {} } = { - "continue.writeUnitTest": async (editor: vscode.TextEditor) => { - let position = editor.selection.active; - - let gutterSpinnerKey = showGutterSpinner(editor, position.line); - try { - let test = await writeUnitTestForFunction( - editor.document.fileName, - position - ); - writeAndShowUnitTest(editor.document.fileName, test); - } catch { - } finally { - decorationManager.deleteDecoration(gutterSpinnerKey); - } - }, "continue.writeDocstring": async (editor: vscode.TextEditor, _) => { sendTelemetryEvent(TelemetryEvent.GenerateDocstring); let gutterSpinnerKey = showGutterSpinner( @@ -199,20 +124,3 @@ async function answerQuestion( } ); } - -// async function suggestFixForAllWorkspaceProblems() { -// Something like this, just figure out the loops for diagnostics vs problems -// let problems = vscode.languages.getDiagnostics(); -// let codeSuggestions = await Promise.all(problems.map((problem) => { -// return bridge.suggestFixForProblem(problem[0].fsPath, problem[1]); -// })); -// for (const [uri, diagnostics] of problems) { -// for (let i = 0; i < diagnostics.length; i++) { -// let diagnostic = diagnostics[i]; -// let suggestedCode = codeSuggestions[i]; -// // If you're going to do this for a bunch of files at once, it will show the unsaved icon in the tab -// // BUT it would be better to have a single window to review all edits -// showSuggestion(uri.fsPath, diagnostic.range, suggestedCode) -// } -// } -// } diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index fbad5f5d..3a77e348 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -12,7 +12,6 @@ import { debugPanelWebview, setupDebugPanel } from "./debugPanel"; import { FileEditWithFullContents } from "../schema/FileEditWithFullContents"; import fs = require("fs"); import { WebsocketMessenger } from "./util/messenger"; -import { CapturedTerminal } from "./terminal/terminalEmulator"; import { decorationManager } from "./decorations"; class IdeProtocolClient { @@ -326,17 +325,14 @@ class IdeProtocolClient { return rangeInFiles; } - public continueTerminal: CapturedTerminal | undefined; - async runCommand(command: string) { - if (!this.continueTerminal || this.continueTerminal.isClosed()) { - this.continueTerminal = new CapturedTerminal({ - name: "Continue", - }); + if (vscode.window.terminals.length) { + vscode.window.terminals[0].sendText(command); + } else { + const terminal = vscode.window.createTerminal(); + terminal.show(); + terminal.sendText(command); } - - this.continueTerminal.show(); - return await this.continueTerminal.runCommand(command); } sendCommandOutput(output: string) { diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts index bb98eb46..b0db590a 100644 --- a/extension/src/debugPanel.ts +++ b/extension/src/debugPanel.ts @@ -1,21 +1,11 @@ import * as vscode from "vscode"; -import { - debugApi, - getContinueServerUrl, - runPythonScript, - unittestApi, -} from "./bridge"; -import { writeAndShowUnitTest } from "./decorations"; -import { showSuggestion } from "./suggestions"; -import { getLanguageLibrary } from "./languages"; +import { getContinueServerUrl } from "./bridge"; import { getExtensionUri, getNonce, openEditorAndRevealRange, } from "./util/vscode"; -import { sendTelemetryEvent, TelemetryEvent } from "./telemetry"; -import { RangeInFile, SerializedDebugContext } from "./client"; -import { addFileSystemToDebugContext } from "./util/util"; +import { RangeInFile } from "./client"; const WebSocket = require("ws"); class StreamManager { @@ -146,6 +136,9 @@ export function setupDebugPanel( let extensionUri = getExtensionUri(); let scriptUri: string; let styleMainUri: string; + let vscMediaUrl: string = debugPanelWebview + .asWebviewUri(vscode.Uri.joinPath(extensionUri, "react-app/dist")) + .toString(); const isProduction = true; // context?.extensionMode === vscode.ExtensionMode.Development; if (!isProduction) { @@ -236,6 +229,7 @@ export function setupDebugPanel( vscMachineId: vscode.env.machineId, apiUrl: getContinueServerUrl(), sessionId, + vscMediaUrl, }); // // Listen for changes to server URL in settings @@ -273,71 +267,6 @@ export function setupDebugPanel( connection.send(data.message); break; } - case "listTenThings": { - sendTelemetryEvent(TelemetryEvent.GenerateIdeas); - let resp = await debugApi.listtenDebugListPost({ - serializedDebugContext: data.debugContext, - }); - panel.webview.postMessage({ - type: "listTenThings", - value: resp.completion, - }); - break; - } - case "suggestFix": { - let completion: string; - let codeSelection = data.debugContext.rangesInFiles?.at(0); - if (codeSelection) { - completion = ( - await debugApi.inlineDebugInlinePost({ - inlineBody: { - filecontents: await vscode.workspace.fs - .readFile(vscode.Uri.file(codeSelection.filepath)) - .toString(), - startline: codeSelection.range.start.line, - endline: codeSelection.range.end.line, - traceback: data.debugContext.traceback, - }, - }) - ).completion; - } else if (data.debugContext.traceback) { - completion = ( - await debugApi.suggestionDebugSuggestionGet({ - traceback: data.debugContext.traceback, - }) - ).completion; - } else { - break; - } - panel.webview.postMessage({ - type: "suggestFix", - value: completion, - }); - break; - } - case "findSuspiciousCode": { - let traceback = getLanguageLibrary(".py").parseFirstStacktrace( - data.debugContext.traceback - ); - if (traceback === undefined) return; - vscode.commands.executeCommand( - "continue.findSuspiciousCode", - data.debugContext - ); - break; - } - case "queryEmbeddings": { - let { results } = await runPythonScript("index.py query", [ - data.query, - 2, - vscode.workspace.workspaceFolders?.[0].uri.fsPath, - ]); - panel.webview.postMessage({ - type: "queryEmbeddings", - results, - }); - break; - } case "openFile": { openEditorAndRevealRange(data.path, undefined, vscode.ViewColumn.One); break; @@ -351,20 +280,6 @@ export function setupDebugPanel( streamManager.closeStream(); break; } - case "explainCode": { - sendTelemetryEvent(TelemetryEvent.ExplainCode); - let debugContext: SerializedDebugContext = addFileSystemToDebugContext( - data.debugContext - ); - let resp = await debugApi.explainDebugExplainPost({ - serializedDebugContext: debugContext, - }); - panel.webview.postMessage({ - type: "explainCode", - value: resp.completion, - }); - break; - } case "withProgress": { // This message allows withProgress to be used in the webview if (data.done) { @@ -395,83 +310,6 @@ export function setupDebugPanel( ); break; } - case "makeEdit": { - sendTelemetryEvent(TelemetryEvent.SuggestFix); - let suggestedEdits = data.edits; - - if ( - typeof suggestedEdits === "undefined" || - suggestedEdits.length === 0 - ) { - vscode.window.showInformationMessage( - "Continue couldn't find a fix for this error." - ); - return; - } - - for (let i = 0; i < suggestedEdits.length; i++) { - let edit = suggestedEdits[i]; - await showSuggestion( - edit.filepath, - new vscode.Range( - edit.range.start.line, - edit.range.start.character, - edit.range.end.line, - edit.range.end.character - ), - edit.replacement - ); - } - break; - } - case "generateUnitTest": { - sendTelemetryEvent(TelemetryEvent.CreateTest); - vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: "Generating Unit Test...", - cancellable: false, - }, - async () => { - for (let i = 0; i < data.debugContext.rangesInFiles?.length; i++) { - let codeSelection = data.debugContext.rangesInFiles?.at(i); - if ( - codeSelection && - codeSelection.filepath && - codeSelection.range - ) { - try { - let filecontents = ( - await vscode.workspace.fs.readFile( - vscode.Uri.file(codeSelection.filepath) - ) - ).toString(); - let resp = - await unittestApi.failingtestUnittestFailingtestPost({ - failingTestBody: { - fp: { - filecontents, - lineno: codeSelection.range.end.line, - }, - description: data.debugContext.description || "", - }, - }); - - if (resp.completion) { - let decorationKey = await writeAndShowUnitTest( - codeSelection.filepath, - resp.completion - ); - break; - } - } catch {} - } - } - } - ); - - break; - } } }); diff --git a/extension/src/decorations.ts b/extension/src/decorations.ts index d2c94135..0587110c 100644 --- a/extension/src/decorations.ts +++ b/extension/src/decorations.ts @@ -1,7 +1,5 @@ import * as vscode from "vscode"; -import { getRightViewColumn, getTestFile } from "./util/vscode"; import * as path from "path"; -import { getLanguageLibrary } from "./languages"; export function showAnswerInTextEditor( filename: string, @@ -223,98 +221,3 @@ export function highlightCode( return key; } - -// Show unit test -const pythonImportDistinguisher = (line: string): boolean => { - if (line.startsWith("from") || line.startsWith("import")) { - return true; - } - return false; -}; -const javascriptImportDistinguisher = (line: string): boolean => { - if (line.startsWith("import")) { - return true; - } - return false; -}; -const importDistinguishersMap: { - [fileExtension: string]: (line: string) => boolean; -} = { - js: javascriptImportDistinguisher, - ts: javascriptImportDistinguisher, - py: pythonImportDistinguisher, -}; -function getImportsFromFileString( - fileString: string, - importDistinguisher: (line: string) => boolean -): Set<string> { - let importLines = new Set<string>(); - for (let line of fileString.split("\n")) { - if (importDistinguisher(line)) { - importLines.add(line); - } - } - return importLines; -} -function removeRedundantLinesFrom( - fileContents: string, - linesToRemove: Set<string> -): string { - let fileLines = fileContents.split("\n"); - fileLines = fileLines.filter((line: string) => { - return !linesToRemove.has(line); - }); - return fileLines.join("\n"); -} - -export async function writeAndShowUnitTest( - filename: string, - test: string -): Promise<DecorationKey> { - return new Promise((resolve, reject) => { - let testFilename = getTestFile(filename, true); - vscode.workspace.openTextDocument(testFilename).then((doc) => { - let fileContent = doc.getText(); - let fileEmpty = fileContent.trim() === ""; - let existingImportLines = getImportsFromFileString( - fileContent, - importDistinguishersMap[doc.fileName.split(".").at(-1) || ".py"] - ); - - // Remove redundant imports, make sure pytest is there - test = removeRedundantLinesFrom(test, existingImportLines); - test = - (fileEmpty - ? `${getLanguageLibrary(".py").writeImport( - testFilename, - filename - )}\nimport pytest\n\n` - : "\n\n") + - test.trim() + - "\n"; - - vscode.window - .showTextDocument(doc, getRightViewColumn()) - .then((editor) => { - let lastLine = editor.document.lineAt(editor.document.lineCount - 1); - let testRange = new vscode.Range( - lastLine.range.end, - new vscode.Position( - test.split("\n").length + lastLine.range.end.line, - 0 - ) - ); - editor - .edit((edit) => { - edit.insert(lastLine.range.end, test); - return true; - }) - .then((success) => { - if (!success) reject("Failed to insert test"); - let key = highlightCode(editor, testRange); - resolve(key); - }); - }); - }); - }); -} diff --git a/extension/src/lang-server/codeLens.ts b/extension/src/lang-server/codeLens.ts index 2a362b62..26528d96 100644 --- a/extension/src/lang-server/codeLens.ts +++ b/extension/src/lang-server/codeLens.ts @@ -1,5 +1,4 @@ import * as vscode from "vscode"; -import { getLanguageLibrary } from "../languages"; import { editorToSuggestions } from "../suggestions"; class SuggestionsCodeLensProvider implements vscode.CodeLensProvider { @@ -52,40 +51,9 @@ class SuggestionsCodeLensProvider implements vscode.CodeLensProvider { } } -class PytestCodeLensProvider implements vscode.CodeLensProvider { - public provideCodeLenses( - document: vscode.TextDocument, - token: vscode.CancellationToken - ): vscode.CodeLens[] | Thenable<vscode.CodeLens[]> { - let codeLenses: vscode.CodeLens[] = []; - let lineno = 1; - let languageLibrary = getLanguageLibrary(document.fileName); - for (let line of document.getText().split("\n")) { - if ( - languageLibrary.lineIsFunctionDef(line) && - languageLibrary.parseFunctionDefForName(line).startsWith("test_") - ) { - let functionToTest = languageLibrary.parseFunctionDefForName(line); - let fileAndFunctionNameSpecifier = - document.fileName + "::" + functionToTest; - codeLenses.push( - new vscode.CodeLens(new vscode.Range(lineno, 0, lineno, 1), { - title: "Debug This Test", - command: "continue.debugTest", - arguments: [fileAndFunctionNameSpecifier], - }) - ); - } - lineno++; - } - - return codeLenses; - } -} - const allCodeLensProviders: { [langauge: string]: vscode.CodeLensProvider[] } = { - python: [new SuggestionsCodeLensProvider(), new PytestCodeLensProvider()], + python: [new SuggestionsCodeLensProvider()], }; export function registerAllCodeLensProviders(context: vscode.ExtensionContext) { diff --git a/extension/src/languages/index.d.ts b/extension/src/languages/index.d.ts deleted file mode 100644 index be7ddfbc..00000000 --- a/extension/src/languages/index.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface LanguageLibrary { - language: string; - fileExtensions: string[]; - parseFirstStacktrace: (stdout: string) => string | undefined; - lineIsFunctionDef: (line: string) => boolean; - parseFunctionDefForName: (line: string) => string; - lineIsComment: (line: string) => boolean; - writeImport: ( - sourcePath: string, - pathToImport: string, - namesToImport?: string[] | undefined - ) => string; -} diff --git a/extension/src/languages/index.ts b/extension/src/languages/index.ts deleted file mode 100644 index 31d73a0b..00000000 --- a/extension/src/languages/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import pythonLanguageLibrary from "./python"; -import javascriptLanguageLibrary from "./javascript"; -import { LanguageLibrary } from "./index.d"; - -export const languageLibraries: LanguageLibrary[] = [ - pythonLanguageLibrary, - javascriptLanguageLibrary, -]; - -export function getLanguageLibrary(filepath: string): LanguageLibrary { - for (let languageLibrary of languageLibraries) { - for (let fileExtension of languageLibrary.fileExtensions) { - if (filepath.endsWith(fileExtension)) { - return languageLibrary; - } - } - } - throw new Error(`No language library found for file ${filepath}`); -} diff --git a/extension/src/languages/javascript/index.ts b/extension/src/languages/javascript/index.ts deleted file mode 100644 index 1c21a2fc..00000000 --- a/extension/src/languages/javascript/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { LanguageLibrary } from "../index.d"; -import { notImplemented } from "../notImplemented"; - -const NI = (propertyName: string) => notImplemented(propertyName, "javascript"); - -const javascriptLangaugeLibrary: LanguageLibrary = { - language: "javascript", - fileExtensions: [".js", ".jsx", ".ts", ".tsx"], - parseFirstStacktrace: NI("parseFirstStacktrace"), - lineIsFunctionDef: NI("lineIsFunctionDef"), - parseFunctionDefForName: NI("parseFunctionDefForName"), - lineIsComment: NI("lineIsComment"), - writeImport: NI("writeImport"), -}; - -export default javascriptLangaugeLibrary; diff --git a/extension/src/languages/notImplemented.ts b/extension/src/languages/notImplemented.ts deleted file mode 100644 index bbba2382..00000000 --- a/extension/src/languages/notImplemented.ts +++ /dev/null @@ -1,10 +0,0 @@ -export function notImplemented( - propertyName: string, - langauge: string -): (...args: any[]) => never { - return (...args: any[]) => { - throw new Error( - `Property ${propertyName} not implemented for language ${langauge}.` - ); - }; -} diff --git a/extension/src/languages/python/index.ts b/extension/src/languages/python/index.ts deleted file mode 100644 index 50282b45..00000000 --- a/extension/src/languages/python/index.ts +++ /dev/null @@ -1,74 +0,0 @@ -import path = require("path"); -import { LanguageLibrary } from "../index.d"; - -const tracebackStart = "Traceback (most recent call last):"; -const tracebackEnd = (buf: string): string | undefined => { - let lines = buf - .split("\n") - .filter((line: string) => line.trim() !== "~~^~~") - .filter((line: string) => line.trim() !== ""); - for (let i = 0; i < lines.length; i++) { - if ( - lines[i].startsWith(" File") && - i + 2 < lines.length && - lines[i + 2][0] !== " " - ) { - return lines.slice(0, i + 3).join("\n"); - } - } - return undefined; -}; - -function parseFirstStacktrace(stdout: string): string | undefined { - let startIdx = stdout.indexOf(tracebackStart); - if (startIdx < 0) return undefined; - stdout = stdout.substring(startIdx); - return tracebackEnd(stdout); -} - -function lineIsFunctionDef(line: string): boolean { - return line.startsWith("def "); -} - -function parseFunctionDefForName(line: string): string { - return line.split("def ")[1].split("(")[0]; -} - -function lineIsComment(line: string): boolean { - return line.trim().startsWith("#"); -} - -function writeImport( - sourcePath: string, - pathToImport: string, - namesToImport: string[] | undefined = undefined -): string { - let segs = path.relative(sourcePath, pathToImport).split(path.sep); - let importFrom = ""; - for (let seg of segs) { - if (seg === "..") { - importFrom = "." + importFrom; - } else { - if (!importFrom.endsWith(".")) { - importFrom += "."; - } - importFrom += seg.split(".").slice(0, -1).join("."); - } - } - - return `from ${importFrom} import ${ - namesToImport ? namesToImport.join(", ") : "*" - }`; -} - -const pythonLangaugeLibrary: LanguageLibrary = { - language: "python", - fileExtensions: [".py"], - parseFirstStacktrace, - lineIsFunctionDef, - parseFunctionDefForName, - lineIsComment, - writeImport, -}; - -export default pythonLangaugeLibrary; |