diff options
author | Nate Sesti <33237525+sestinj@users.noreply.github.com> | 2023-07-11 15:14:53 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-07-11 15:14:53 -0700 |
commit | 9adfb8bf5b9f44be82488e37bc32cbd9a8817b53 (patch) | |
tree | 8399d02483267ec024034a923475bd6eaf397154 | |
parent | 3f9e7a1fa59c4f684aef544436062f8825d77b31 (diff) | |
parent | 9846f4769ff33a346d76e26ad730d19770fe7e02 (diff) | |
download | sncontinue-9adfb8bf5b9f44be82488e37bc32cbd9a8817b53.tar.gz sncontinue-9adfb8bf5b9f44be82488e37bc32cbd9a8817b53.tar.bz2 sncontinue-9adfb8bf5b9f44be82488e37bc32cbd9a8817b53.zip |
Merge pull request #237 from continuedev/bug-squashing
Bug squashing
-rw-r--r-- | continuedev/src/continuedev/core/autopilot.py | 2 | ||||
-rw-r--r-- | continuedev/src/continuedev/core/config.py | 4 | ||||
-rw-r--r-- | continuedev/src/continuedev/libs/util/create_async_task.py | 2 | ||||
-rw-r--r-- | continuedev/src/continuedev/server/gui.py | 2 | ||||
-rw-r--r-- | continuedev/src/continuedev/server/ide.py | 2 | ||||
-rw-r--r-- | continuedev/src/continuedev/steps/chat.py | 2 | ||||
-rw-r--r-- | continuedev/src/continuedev/steps/core/core.py | 44 | ||||
-rw-r--r-- | continuedev/src/continuedev/steps/main.py | 30 | ||||
-rw-r--r-- | continuedev/src/continuedev/steps/open_config.py | 5 | ||||
-rw-r--r-- | extension/package-lock.json | 4 | ||||
-rw-r--r-- | extension/package.json | 2 | ||||
-rw-r--r-- | extension/react-app/src/components/ComboBox.tsx | 33 | ||||
-rw-r--r-- | extension/react-app/src/tabs/gui.tsx | 11 | ||||
-rw-r--r-- | extension/src/activation/activate.ts | 22 | ||||
-rw-r--r-- | extension/src/activation/environmentSetup.ts | 46 | ||||
-rw-r--r-- | extension/src/diffs.ts | 24 |
16 files changed, 160 insertions, 75 deletions
diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 615e7657..11fc9cb1 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -285,7 +285,7 @@ class Autopilot(ContinueBaseModel): e.__class__, ContinueCustomException) error_string = e.message if is_continue_custom_exception else '\n'.join( - traceback.format_tb(e.__traceback__)) + f"\n\n{e.__repr__()}" + traceback.format_exception(e)) error_title = e.title if is_continue_custom_exception else get_error_title( e) diff --git a/continuedev/src/continuedev/core/config.py b/continuedev/src/continuedev/core/config.py index 55f5bc60..f6167638 100644 --- a/continuedev/src/continuedev/core/config.py +++ b/continuedev/src/continuedev/core/config.py @@ -131,7 +131,7 @@ def load_global_config() -> ContinueConfig: config_path = os.path.join(global_dir, 'config.json') if not os.path.exists(config_path): with open(config_path, 'w') as f: - json.dump(ContinueConfig().dict(), f) + json.dump(ContinueConfig().dict(), f, indent=4) with open(config_path, 'r') as f: try: config_dict = json.load(f) @@ -151,7 +151,7 @@ def update_global_config(config: ContinueConfig): 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) + yaml.dump(config.dict(), f, indent=4) else: config_path = os.path.join(global_dir, 'config.json') with open(config_path, 'w') as f: diff --git a/continuedev/src/continuedev/libs/util/create_async_task.py b/continuedev/src/continuedev/libs/util/create_async_task.py index 608d4977..62ff30ec 100644 --- a/continuedev/src/continuedev/libs/util/create_async_task.py +++ b/continuedev/src/continuedev/libs/util/create_async_task.py @@ -16,7 +16,7 @@ def create_async_task(coro: Coroutine, unique_id: Union[str, None] = None): except Exception as e: print("Exception caught from async task: ", e) capture_event(unique_id or "None", "async_task_error", { - "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format_tb(e.__traceback__) + "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e)) }) task.add_done_callback(callback) diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index ae53be00..dbc063c8 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -189,7 +189,7 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we except Exception as e: print("ERROR in gui websocket: ", e) capture_event(session.autopilot.continue_sdk.ide.unique_id, "gui_error", { - "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format_tb(e.__traceback__)}) + "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e))}) raise e finally: print("Closing gui websocket") diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 93996edd..782c2ba6 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -414,6 +414,6 @@ async def websocket_endpoint(websocket: WebSocket): except Exception as e: print("Error in ide websocket: ", e) capture_event(ideProtocolServer.unique_id, "gui_error", { - "error_title": e.__str__() or e.__repr__(), "error_message": traceback.format_tb(e.__traceback__)}) + "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e))}) await websocket.close() raise e diff --git a/continuedev/src/continuedev/steps/chat.py b/continuedev/src/continuedev/steps/chat.py index a10319d8..e1e041d0 100644 --- a/continuedev/src/continuedev/steps/chat.py +++ b/continuedev/src/continuedev/steps/chat.py @@ -29,6 +29,8 @@ class SimpleChatStep(Step): messages = self.messages or await sdk.get_chat_context() async for chunk in sdk.models.gpt4.stream_chat(messages, temperature=0.5): if sdk.current_step_was_deleted(): + # So that the message doesn't disappear + self.hide = False return if "content" in chunk: diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index b0d9d719..d4d067ba 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -265,6 +265,23 @@ class DefaultModelEditCodeStep(Step): return file_prefix, rif.contents, file_suffix, model_to_use, max_tokens def compile_prompt(self, file_prefix: str, contents: str, file_suffix: str, sdk: ContinueSDK) -> str: + if contents.strip() == "": + # Seperate prompt for insertion at the cursor, the other tends to cause it to repeat whole file + prompt = dedent(f"""\ +<file_prefix> +{file_prefix} +</file_prefix> +<insertion_code_here> +<file_suffix> +{file_suffix} +</file_suffix> +<user_request> +{self.user_input} +</user_request> + +Please output the code to be inserted at the cursor in order to fulfill the user_request. Do NOT preface your answer or write anything other than code. You should not write any tags, just the code. Make sure to correctly indent the code:""") + return prompt + prompt = self._prompt if file_prefix.strip() != "": prompt += dedent(f""" @@ -306,15 +323,32 @@ class DefaultModelEditCodeStep(Step): prompt = self.compile_prompt(file_prefix, contents, file_suffix, sdk) full_file_contents_lines = full_file_contents.split("\n") - async def sendDiffUpdate(lines: List[str], sdk: ContinueSDK): - nonlocal full_file_contents_lines, rif + lines_to_display = [] + + async def sendDiffUpdate(lines: List[str], sdk: ContinueSDK, final: bool = False): + nonlocal full_file_contents_lines, rif, lines_to_display completion = "\n".join(lines) full_prefix_lines = full_file_contents_lines[:rif.range.start.line] full_suffix_lines = full_file_contents_lines[rif.range.end.line:] + + # Don't do this at the very end, just show the inserted code + if final: + lines_to_display = [] + # Only recalculate at every new-line, because this is sort of expensive + elif completion.endswith("\n"): + contents_lines = rif.contents.split("\n") + rewritten_lines = 0 + for line in lines: + for i in range(rewritten_lines, len(contents_lines)): + if difflib.SequenceMatcher(None, line, contents_lines[i]).ratio() > 0.7 and contents_lines[i].strip() != "": + rewritten_lines = i + 1 + break + lines_to_display = contents_lines[rewritten_lines:] + new_file_contents = "\n".join( - full_prefix_lines) + "\n" + completion + "\n" + "\n".join(full_suffix_lines) + full_prefix_lines) + "\n" + completion + "\n" + "\n".join(lines_to_display) + "\n" + "\n".join(full_suffix_lines) step_index = sdk.history.current_index @@ -495,7 +529,7 @@ class DefaultModelEditCodeStep(Step): completion_lines_covered += 1 current_line_in_file += 1 - await sendDiffUpdate(lines + [common_whitespace + unfinished_line], sdk) + await sendDiffUpdate(lines + [common_whitespace if unfinished_line.startswith("<") else (common_whitespace + unfinished_line)], sdk) # Add the unfinished line if unfinished_line != "" and not self.line_to_be_ignored(unfinished_line, completion_lines_covered == 0) and not self.is_end_line(unfinished_line): @@ -505,7 +539,7 @@ class DefaultModelEditCodeStep(Step): completion_lines_covered += 1 current_line_in_file += 1 - await sendDiffUpdate(lines, sdk) + await sendDiffUpdate(lines, sdk, final=True) if False: # If the current block isn't empty, add that suggestion diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py index 4f543022..e6ef9281 100644 --- a/continuedev/src/continuedev/steps/main.py +++ b/continuedev/src/continuedev/steps/main.py @@ -10,7 +10,7 @@ from ..models.filesystem import RangeInFile, RangeInFileWithContents from ..core.observation import Observation, TextObservation, TracebackObservation from ..libs.llm.prompt_utils import MarkdownStyleEncoderDecoder from textwrap import dedent -from ..core.main import Step +from ..core.main import ContinueCustomException, Step from ..core.sdk import ContinueSDK, Models from ..core.observation import Observation import subprocess @@ -251,33 +251,27 @@ class EditHighlightedCodeStep(Step): highlighted_code = await sdk.ide.getHighlightedCode() if highlighted_code is not None: for rif in highlighted_code: + if os.path.dirname(rif.filepath) == os.path.expanduser(os.path.join("~", ".continue", "diffs")): + raise ContinueCustomException( + message="Please accept or reject the change before making another edit in this file.", title="Accept/Reject First") if rif.range.start == rif.range.end: range_in_files.append( RangeInFileWithContents.from_range_in_file(rif, "")) - # If nothing highlighted, edit the first open file + # If still no highlighted code, raise error if len(range_in_files) == 0: - # Get the full contents of all open files - files = await sdk.ide.getOpenFiles() - contents = {} - for file in files: - contents[file] = await sdk.ide.readFile(file) - - range_in_files = [RangeInFileWithContents.from_entire_file( - filepath, content) for filepath, content in contents.items()] - - # If still no highlighted code, create a new file and edit there - if len(range_in_files) == 0: - # Create a new file - new_file_path = "new_file.txt" - await sdk.add_file(new_file_path, "") - range_in_files = [ - RangeInFileWithContents.from_entire_file(new_file_path, "")] + raise ContinueCustomException( + message="Please highlight some code and try again.", title="No Code Selected") range_in_files = list(map(lambda x: RangeInFile( filepath=x.filepath, range=x.range ), range_in_files)) + for range_in_file in range_in_files: + if os.path.dirname(range_in_file.filepath) == os.path.expanduser(os.path.join("~", ".continue", "diffs")): + self.description = "Please accept or reject the change before making another edit in this file." + return + await sdk.run_step(DefaultModelEditCodeStep(user_input=self.user_input, range_in_files=range_in_files)) diff --git a/continuedev/src/continuedev/steps/open_config.py b/continuedev/src/continuedev/steps/open_config.py index 441cb0e7..87f03e9f 100644 --- a/continuedev/src/continuedev/steps/open_config.py +++ b/continuedev/src/continuedev/steps/open_config.py @@ -18,7 +18,10 @@ class OpenConfigStep(Step): "prompt": "Write a comprehensive set of unit tests for the selected code. It should setup, run tests that check for correctness including important edge cases, and teardown. Ensure that the tests are complete and sophisticated." } ], - ```""") + ``` + `"name"` is the command you will type. + `"description"` is the description displayed in the slash command menu. + `"prompt"` is the instruction given to the model. The overall prompt becomes "Task: {prompt}, Additional info: {user_input}". For example, if you entered "/test exactly 5 assertions", the overall prompt would become "Task: Write a comprehensive...and sophisticated, Additional info: exactly 5 assertions".""") async def run(self, sdk: ContinueSDK): global_dir = os.path.expanduser('~/.continue') diff --git a/extension/package-lock.json b/extension/package-lock.json index 71f4d974..b6147b2c 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.147", + "version": "0.0.151", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.147", + "version": "0.0.151", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index d9f155df..a57f6065 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "The open-source coding autopilot", - "version": "0.0.147", + "version": "0.0.151", "publisher": "Continue", "engines": { "vscode": "^1.67.0" diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index 801c3a03..ac994b0a 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -180,6 +180,26 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { useImperativeHandle(ref, () => downshiftProps, [downshiftProps]); + const [metaKeyPressed, setMetaKeyPressed] = useState(false); + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Meta") { + setMetaKeyPressed(true); + } + }; + const handleKeyUp = (e: KeyboardEvent) => { + if (e.key === "Meta") { + setMetaKeyPressed(false); + } + }; + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); + return () => { + window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("keyup", handleKeyUp); + }; + }); + useEffect(() => { if (!inputRef.current) { return; @@ -272,7 +292,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { <div className="flex px-2" ref={divRef} hidden={!downshiftProps.isOpen}> <MainTextInput disabled={props.disabled} - placeholder="Ask a question, give instructions, or type '/' to see slash commands" + placeholder="Ask a question, give instructions, or type '/' to see slash commands. ⌘⏎ to edit." {...getInputProps({ onChange: (e) => { const target = e.target as HTMLTextAreaElement; @@ -357,10 +377,13 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { ))} </Ul> </div> - {/* <span className="text-trueGray-400 ml-auto m-auto text-xs text-right"> - Highlight code to include as context. Currently open file included by - default. {highlightedCodeSections.length === 0 && ""} - </span> */} + {highlightedCodeSections.length === 0 && + (downshiftProps.inputValue?.startsWith("/edit") || + (metaKeyPressed && downshiftProps.inputValue?.length > 0)) && ( + <div className="text-trueGray-400 pr-4 text-xs text-right"> + Inserting at cursor + </div> + )} <ContextDropdown onMouseEnter={() => { setHoveringContextDropdown(true); diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx index 0e60e05c..619b91e1 100644 --- a/extension/react-app/src/tabs/gui.tsx +++ b/extension/react-app/src/tabs/gui.tsx @@ -170,6 +170,7 @@ function GUI(props: GUIProps) { const waitingForSteps = state.active && state.history.current_index < state.history.timeline.length && + state.history.timeline[state.history.current_index] && state.history.timeline[ state.history.current_index ].step.description?.trim() === ""; @@ -236,14 +237,14 @@ function GUI(props: GUIProps) { history.current_index < history.timeline.length ) { if ( - history.timeline[history.current_index].step.name === + history.timeline[history.current_index]?.step.name === "Waiting for user input" ) { if (input.trim() === "") return; onStepUserInput(input, history!.current_index); return; } else if ( - history.timeline[history.current_index].step.name === + history.timeline[history.current_index]?.step.name === "Waiting for user confirmation" ) { onStepUserInput("ok", history!.current_index); @@ -350,12 +351,6 @@ function GUI(props: GUIProps) { </div> <ComboBox - // disabled={ - // history?.timeline.length - // ? history.timeline[history.current_index].step.name === - // "Waiting for user confirmation" - // : false - // } ref={mainTextInputRef} onEnter={(e) => { onMainTextInput(e); diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index 18650561..2c5ba58c 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -7,9 +7,16 @@ import IdeProtocolClient from "../continueIdeClient"; import { getContinueServerUrl } from "../bridge"; import { CapturedTerminal } from "../terminal/terminalEmulator"; import { setupDebugPanel, ContinueGUIWebviewViewProvider } from "../debugPanel"; -import { startContinuePythonServer } from "./environmentSetup"; +import { + getExtensionVersion, + startContinuePythonServer, +} from "./environmentSetup"; +import fetch from "node-fetch"; // import { CapturedTerminal } from "../terminal/terminalEmulator"; +const PACKAGE_JSON_RAW_GITHUB_URL = + "https://raw.githubusercontent.com/continuedev/continue/main/extension/package.json"; + export let extensionContext: vscode.ExtensionContext | undefined = undefined; export let ideProtocolClient: IdeProtocolClient; @@ -20,6 +27,19 @@ export async function activateExtension( ) { extensionContext = context; + // Before anything else, check whether this is an out-of-date version of the extension + // Do so by grabbing the package.json off of the GitHub respository for now. + fetch(PACKAGE_JSON_RAW_GITHUB_URL) + .then(async (res) => res.json()) + .then((packageJson) => { + if (packageJson.version !== getExtensionVersion()) { + vscode.window.showInformationMessage( + `You are using an out-of-date version of the Continue extension. Please update to the latest version.` + ); + } + }) + .catch((e) => console.log("Error checking for extension updates: ", e)); + await new Promise((resolve, reject) => { vscode.window.withProgress( { diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 02118501..c277a539 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -22,7 +22,7 @@ async function retryThenFail( if (retries > 0) { return await retryThenFail(fn, retries - 1); } - vscode.window.showErrorMessage( + vscode.window.showInformationMessage( "Failed to set up Continue extension. Please email nate@continue.dev and we'll get this fixed ASAP!" ); sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, { @@ -156,10 +156,7 @@ async function checkRequirementsInstalled() { activateCmd, `${pipCmd} uninstall -y continuedev`, ].join(" ; "); - const [, stderr] = await runCommand(removeOldVersionCommand); - if (stderr) { - throw new Error(stderr); - } + await runCommand(removeOldVersionCommand); return false; } } @@ -224,6 +221,9 @@ async function setupPythonEnv() { // First, try to run the command to install python3-venv let [stdout, stderr] = await runCommand(`${pythonCmd} --version`); if (stderr) { + await vscode.window.showErrorMessage( + "Python3 is not installed. Please install from https://www.python.org/downloads, reload VS Code, and try again." + ); throw new Error(stderr); } const version = stdout.split(" ")[1].split(".")[1]; @@ -351,7 +351,7 @@ function requirementsVersionPath(): string { return path.join(serverPath(), "requirements_version.txt"); } -function getExtensionVersion() { +export function getExtensionVersion() { const extension = vscode.extensions.getExtension("continue.continue"); return extension?.packageJSON.version || ""; } @@ -366,24 +366,26 @@ export async function startContinuePythonServer() { setupServerPath(); return await retryThenFail(async () => { - if (await checkServerRunning(serverUrl)) { - // Kill the server if it is running an old version - if (fs.existsSync(serverVersionPath())) { - const serverVersion = fs.readFileSync(serverVersionPath(), "utf8"); - if (serverVersion === getExtensionVersion()) { - return; - } - } - console.log("Killing old server..."); - try { - await fkill(":65432"); - } catch (e) { - console.log( - "Failed to kill old server, likely because it didn't exist:", - e - ); + // Kill the server if it is running an old version + if (fs.existsSync(serverVersionPath())) { + const serverVersion = fs.readFileSync(serverVersionPath(), "utf8"); + if ( + serverVersion === getExtensionVersion() && + (await checkServerRunning(serverUrl)) + ) { + // The current version is already up and running, no need to continue + return; } } + console.log("Killing old server..."); + try { + await fkill(":65432"); + } catch (e) { + console.log( + "Failed to kill old server, likely because it didn't exist:", + e + ); + } // Do this after above check so we don't have to waste time setting up the env await setupPythonEnv(); diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts index b9ef8384..3ea6b4f8 100644 --- a/extension/src/diffs.ts +++ b/extension/src/diffs.ts @@ -132,11 +132,18 @@ class DiffManager { console.log("No corresponding diffInfo found for newFilepath"); return; } - fs.writeFileSync( - diffInfo.originalFilepath, - fs.readFileSync(diffInfo.newFilepath) - ); - this.cleanUpDiff(diffInfo); + + // Save the right-side file, then copy over to original + vscode.workspace.textDocuments + .find((doc) => doc.uri.fsPath === newFilepath) + ?.save() + .then(() => { + fs.writeFileSync( + diffInfo.originalFilepath, + fs.readFileSync(diffInfo.newFilepath) + ); + this.cleanUpDiff(diffInfo); + }); } rejectDiff(newFilepath?: string) { @@ -157,7 +164,12 @@ class DiffManager { // Stop the step at step_index in case it is still streaming ideProtocolClient.deleteAtIndex(diffInfo.step_index); - this.cleanUpDiff(diffInfo); + vscode.workspace.textDocuments + .find((doc) => doc.uri.fsPath === newFilepath) + ?.save() + .then(() => { + this.cleanUpDiff(diffInfo); + }); } } |