From d342c55ed2b8b229ccd40cc45f7af05cb73e7ad0 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 9 Jul 2023 13:09:34 -0700 Subject: expand max_tokens for large highlighted ranges --- extension/src/activation/environmentSetup.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'extension/src') diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 90ec9259..714080e3 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -324,7 +324,14 @@ export async function startContinuePythonServer() { } } console.log("Killing old server..."); - await fkill(":65432"); + 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 -- cgit v1.2.3-70-g09d2 From d566dc90a32db926393e1d10f983c79bc3feb56a Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 9 Jul 2023 16:19:16 -0700 Subject: cache server env in .continue folder --- extension/DEV_README.md | 2 +- extension/package.json | 2 +- extension/react-app/src/tabs/gui.tsx | 2 +- extension/scripts/.gitignore | 5 - extension/scripts/README.md | 5 - extension/scripts/chroma.py | 152 ---------------------- extension/scripts/index.py | 52 -------- extension/scripts/query.py | 63 --------- extension/scripts/replace.py | 17 --- extension/scripts/requirements.txt | 6 - extension/scripts/run_continue_server.py | 4 - extension/scripts/update.py | 185 --------------------------- extension/server/.gitignore | 1 + extension/server/README.md | 3 + extension/server/requirements.txt | 1 + extension/server/run_continue_server.py | 4 + extension/src/activation/environmentSetup.ts | 97 ++++++++++---- 17 files changed, 85 insertions(+), 516 deletions(-) delete mode 100644 extension/scripts/.gitignore delete mode 100644 extension/scripts/README.md delete mode 100644 extension/scripts/chroma.py delete mode 100644 extension/scripts/index.py delete mode 100644 extension/scripts/query.py delete mode 100644 extension/scripts/replace.py delete mode 100644 extension/scripts/requirements.txt delete mode 100644 extension/scripts/run_continue_server.py delete mode 100644 extension/scripts/update.py create mode 100644 extension/server/.gitignore create mode 100644 extension/server/README.md create mode 100644 extension/server/requirements.txt create mode 100644 extension/server/run_continue_server.py (limited to 'extension/src') diff --git a/extension/DEV_README.md b/extension/DEV_README.md index c247b82c..87ed9334 100644 --- a/extension/DEV_README.md +++ b/extension/DEV_README.md @@ -4,7 +4,7 @@ This is the Continue VS Code Extension. Its primary jobs are 1. Implement the IDE side of the Continue IDE protocol, allowing a Continue server to interact natively in an IDE. This happens in `src/continueIdeClient.ts`. 2. Open the Continue React app in a side panel. The React app's source code lives in the `react-app` directory. The panel is opened by the `continue.openContinueGUI` command, as defined in `src/commands.ts`. -3. Run a Continue server in the background, which connects to both the IDE protocol and the React app. The server is launched in `src/activation/environmentSetup.ts` by calling Python code that lives in `scripts/` (unless extension settings define a server URL other than localhost:65432, in which case the extension will just connect to that). +3. Run a Continue server in the background, which connects to both the IDE protocol and the React app. The server is launched in `src/activation/environmentSetup.ts` by calling Python code that lives in `server/` (unless extension settings define a server URL other than localhost:65432, in which case the extension will just connect to that). 4. Open Continue diff --git a/extension/package.json b/extension/package.json index 444372f8..3b23ff19 100644 --- a/extension/package.json +++ b/extension/package.json @@ -227,7 +227,7 @@ "test": "node ./out/test/runTest.js", "jest": "jest --config ./jest.config.js", "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && cp ./config/dev_config.json ./config/config.json", - "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.2-py3-none-any.whl ../extension/scripts/continuedev-0.1.2-py3-none-any.whl && cd ../extension && npm install && npm run typegen && npm run clientgen && cd react-app && npm install && npm run build && cd .. && npm run package", + "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.2-py3-none-any.whl ../extension/server/continuedev-0.1.2-py3-none-any.whl && cd ../extension && npm install && npm run typegen && npm run clientgen && cd react-app && npm install && npm run build && cd .. && npm run package", "install-extension": "code --install-extension ./build/continue-0.0.8.vsix", "uninstall": "code --uninstall-extension .continue", "reinstall": "rm -rf ./build && npm run package && npm run uninstall && npm run install-extension" diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx index e1ecec9e..646aef50 100644 --- a/extension/react-app/src/tabs/gui.tsx +++ b/extension/react-app/src/tabs/gui.tsx @@ -438,7 +438,7 @@ function GUI(props: GUIProps) { if (!usingFastModel) { // Show the dialog setFeedbackDialogMessage( - "We don't yet support local models, but we're working on it! If privacy is a concern of yours, please use the feedback button in the bottom right to let us know." + "We don't yet support local models, but we're working on it! If privacy is a concern of yours, please write a short note to let us know." ); setShowFeedbackDialog(true); } diff --git a/extension/scripts/.gitignore b/extension/scripts/.gitignore deleted file mode 100644 index fbb3bf9f..00000000 --- a/extension/scripts/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -testdb -env -stdout.txt -.continue_env_installed -**.whl \ No newline at end of file diff --git a/extension/scripts/README.md b/extension/scripts/README.md deleted file mode 100644 index da1ad493..00000000 --- a/extension/scripts/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Scripts - -Whenever we need python to run on the client side, we include a script file at the top level of this folder. All other files that are not to be run directly as a script (utility files) should be in a subfolder of `scripts`. You can call one of these scripts from the VS Code extension using the `runPythonScript` function in `bridge.ts`. - -When the extension is activated (`activate` function in `src/extension.ts`), we call `setupPythonEnv`, which makes the virtual environment and downloads all the necessary requirements as given in `requirements.txt`. With this in mind, be sure to run `pip freeze > requirements.txt` whenever you add a new requirement. diff --git a/extension/scripts/chroma.py b/extension/scripts/chroma.py deleted file mode 100644 index 7425394e..00000000 --- a/extension/scripts/chroma.py +++ /dev/null @@ -1,152 +0,0 @@ -import chromadb -import os -import json -import subprocess - -from typing import List, Tuple - -from chromadb.config import Settings - -client = chromadb.Client(Settings( - chroma_db_impl="duckdb+parquet", - persist_directory="./data/" -)) - -FILE_TYPES_TO_IGNORE = [ - '.pyc', - '.png', - '.jpg', - '.jpeg', - '.gif', - '.svg', - '.ico' -] - -def further_filter(files: List[str], root_dir: str): - """Further filter files before indexing.""" - for file in files: - if file.endswith(tuple(FILE_TYPES_TO_IGNORE)) or file.startswith('.git') or file.startswith('archive'): - continue - yield root_dir + "/" + file - -def get_git_root_dir(path: str): - """Get the root directory of a Git repository.""" - try: - return subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], cwd=path).strip().decode() - except subprocess.CalledProcessError: - return None - -def get_git_ignored_files(root_dir: str): - """Get the list of ignored files in a Git repository.""" - try: - output = subprocess.check_output(['git', 'ls-files', '--ignored', '--others', '--exclude-standard'], cwd=root_dir).strip().decode() - return output.split('\n') - except subprocess.CalledProcessError: - return [] - -def get_all_files(root_dir: str): - """Get a list of all files in a directory.""" - for dir_path, _, file_names in os.walk(root_dir): - for file_name in file_names: - yield os.path.join(os.path.relpath(dir_path, root_dir), file_name) - -def get_input_files(root_dir: str): - """Get a list of all files in a Git repository that are not ignored.""" - ignored_files = set(get_git_ignored_files(root_dir)) - all_files = set(get_all_files(root_dir)) - nonignored_files = all_files - ignored_files - return further_filter(nonignored_files, root_dir) - -def get_git_root_dir(cwd: str): - """Get the root directory of a Git repository.""" - result = subprocess.run(['git', 'rev-parse', '--show-toplevel'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) - return result.stdout.decode().strip() - -def get_current_branch(cwd: str) -> str: - """Get the current Git branch.""" - try: - return subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=cwd).decode("utf-8").strip() - except: - return "main" - -def get_current_commit(cwd: str) -> str: - try: - return subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=cwd).decode("utf-8").strip() - except: - return "NO_COMMITS" - -def get_modified_deleted_files(cwd: str) -> Tuple[List[str], List[str]]: - """Get a list of all files that have been modified since the last commit.""" - branch = get_current_branch(cwd) - current_commit = get_current_commit(cwd) - - with open(f"./data/{branch}.json", 'r') as f: - previous_commit = json.load(f)["commit"] - - modified_deleted_files = subprocess.check_output(["git", "diff", "--name-only", previous_commit, current_commit], cwd=cwd).decode("utf-8").strip() - modified_deleted_files = modified_deleted_files.split("\n") - modified_deleted_files = [f for f in modified_deleted_files if f] - - root = get_git_root_dir(cwd) - deleted_files = [f for f in modified_deleted_files if not os.path.exists(root + "/" + f)] - modified_files = [f for f in modified_deleted_files if os.path.exists(root + "/" + f)] - - return further_filter(modified_files, root), further_filter(deleted_files, root) - -def create_collection(branch: str, cwd: str): - """Create a new collection, returning whether it already existed.""" - try: - collection = client.create_collection(name=branch) - except Exception as e: - print(e) - return - - files = get_input_files(get_git_root_dir(cwd)) - for file in files: - with open(file, 'r') as f: - collection.add(documents=[f.read()], ids=[file]) - print(f"Added {file}") - with open(f"./data/{branch}.json", 'w') as f: - json.dump({"commit": get_current_commit(cwd)}, f) - -def collection_exists(cwd: str): - """Check if a collection exists.""" - branch = get_current_branch(cwd) - return branch in client.list_collections() - -def update_collection(cwd: str): - """Update the collection.""" - branch = get_current_branch(cwd) - - try: - - collection = client.get_collection(branch) - - modified_files, deleted_files = get_modified_deleted_files(cwd) - - for file in deleted_files: - collection.delete(ids=[file]) - print(f"Deleted {file}") - - for file in modified_files: - with open(file, 'r') as f: - collection.update(documents=[f.read()], ids=[file]) - print(f"Updated {file}") - - with open(f"./data/{branch}.json", 'w') as f: - json.dump({"commit": get_current_commit(cwd)}, f) - - except: - - create_collection(branch, cwd) - -def query_collection(query: str, n_results: int, cwd: str): - """Query the collection.""" - branch = get_current_branch(cwd) - try: - collection = client.get_collection(branch) - except: - create_collection(branch, cwd) - collection = client.get_collection(branch) - results = collection.query(query_texts=[query], n_results=n_results) - return results \ No newline at end of file diff --git a/extension/scripts/index.py b/extension/scripts/index.py deleted file mode 100644 index 3afc9131..00000000 --- a/extension/scripts/index.py +++ /dev/null @@ -1,52 +0,0 @@ -import sys -import os -from typing import TextIO -from chroma import update_collection, query_collection, create_collection, collection_exists, get_current_branch -from typer import Typer - -app = Typer() - -class SilenceStdoutContextManager: - saved_stdout: TextIO - - def __enter__(self): - self._original_stdout = sys.stdout - sys.stdout = open(os.devnull, 'w') - - def __exit__(self, exc_type, exc_val, exc_tb): - sys.stdout.close() - sys.stdout = self._original_stdout - -silence = SilenceStdoutContextManager() - -@app.command("exists") -def exists(cwd: str): - with silence: - exists = collection_exists(cwd) - print({"exists": exists}) - -@app.command("create") -def create(cwd: str): - with silence: - branch = get_current_branch(cwd) - create_collection(branch, cwd) - print({"success": True}) - -@app.command("update") -def update(cwd: str): - with silence: - update_collection(cwd) - print({"success": True}) - -@app.command("query") -def query(query: str, n_results: int, cwd: str): - with silence: - resp = query_collection(query, n_results, cwd) - results = [{ - "id": resp["ids"][0][i], - "document": resp["documents"][0][i] - } for i in range(len(resp["ids"][0]))] - print({"results": results}) - -if __name__ == "__main__": - app() \ No newline at end of file diff --git a/extension/scripts/query.py b/extension/scripts/query.py deleted file mode 100644 index f2e44413..00000000 --- a/extension/scripts/query.py +++ /dev/null @@ -1,63 +0,0 @@ -import subprocess -import sys -from gpt_index import GPTSimpleVectorIndex, GPTFaissIndex -import os -from typer import Typer -from enum import Enum -from update import update_codebase_index, create_codebase_index, index_dir_for, get_current_branch -from replace import replace_additional_index - -app = Typer() - -def query_codebase_index(query: str) -> str: - """Query the codebase index.""" - branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]).decode("utf-8").strip() - path = 'data/{branch}/index.json' - if not os.path.exists(path): - print("No index found for the codebase") - return "" - index = GPTFaissIndex.load_from_disk(path) - return index.query(query) - -def query_additional_index(query: str) -> str: - """Query the additional index.""" - index = GPTSimpleVectorIndex.load_from_disk('data/additional_index.json') - return index.query(query) - -class IndexTypeOption(str, Enum): - codebase = "codebase" - additional = "additional" - -@app.command() -def query(context: IndexTypeOption, query: str): - if context == IndexTypeOption.additional: - response = query_additional_index(query) - elif context == IndexTypeOption.codebase: - response = query_codebase_index(query) - else: - print("Error: unknown context") - print({ "response": response }) - -@app.command() -def check_index_exists(root_path: str): - branch = get_current_branch() - exists = os.path.exists(index_dir_for(branch)) - print({ "exists": exists }) - -@app.command() -def update(): - update_codebase_index() - print("Updated codebase index") - -@app.command() -def create_index(path: str): - create_codebase_index() - print("Created file index") - -@app.command() -def replace_additional_index(info: str): - replace_additional_index() - print("Replaced additional index") - -if __name__ == '__main__': - app() \ No newline at end of file diff --git a/extension/scripts/replace.py b/extension/scripts/replace.py deleted file mode 100644 index 08810243..00000000 --- a/extension/scripts/replace.py +++ /dev/null @@ -1,17 +0,0 @@ -import sys -from gpt_index import GPTSimpleVectorIndex, Document - -def replace_additional_index(info: str): - """Replace the additional index.""" - with open('data/additional_context.txt', 'w') as f: - f.write(info) - documents = [Document(info)] - index = GPTSimpleVectorIndex(documents) - index.save_to_disk('data/additional_index.json') - print("Additional index replaced") - -if __name__ == "__main__": - """python3 replace.py """ - info = sys.argv[1] if len(sys.argv) > 1 else None - if info: - replace_additional_index(info) \ No newline at end of file diff --git a/extension/scripts/requirements.txt b/extension/scripts/requirements.txt deleted file mode 100644 index c51c9d73..00000000 --- a/extension/scripts/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -# chromadb==0.3.10 -# pathspec==0.11.0 -# typer==0.7.0 -# pydantic -# pytest -./continuedev-0.1.2-py3-none-any.whl \ No newline at end of file diff --git a/extension/scripts/run_continue_server.py b/extension/scripts/run_continue_server.py deleted file mode 100644 index 089cc54d..00000000 --- a/extension/scripts/run_continue_server.py +++ /dev/null @@ -1,4 +0,0 @@ -from continuedev.server.main import run_server - -if __name__ == "__main__": - run_server() diff --git a/extension/scripts/update.py b/extension/scripts/update.py deleted file mode 100644 index 15ad6ac0..00000000 --- a/extension/scripts/update.py +++ /dev/null @@ -1,185 +0,0 @@ -# import faiss -import json -import os -import subprocess - -from gpt_index.langchain_helpers.text_splitter import TokenTextSplitter -from gpt_index import GPTSimpleVectorIndex, SimpleDirectoryReader, Document, GPTFaissIndex -from typing import List, Generator, Tuple - -FILE_TYPES_TO_IGNORE = [ - '.pyc', - '.png', - '.jpg', - '.jpeg', - '.gif', - '.svg', - '.ico' -] - -def further_filter(files: List[str], root_dir: str): - """Further filter files before indexing.""" - for file in files: - if file.endswith(tuple(FILE_TYPES_TO_IGNORE)) or file.startswith('.git') or file.startswith('archive'): - continue - yield root_dir + "/" + file - -def get_git_root_dir(path: str): - """Get the root directory of a Git repository.""" - try: - return subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], cwd=path).strip().decode() - except subprocess.CalledProcessError: - return None - -def get_git_ignored_files(root_dir: str): - """Get the list of ignored files in a Git repository.""" - try: - output = subprocess.check_output(['git', 'ls-files', '--ignored', '--others', '--exclude-standard'], cwd=root_dir).strip().decode() - return output.split('\n') - except subprocess.CalledProcessError: - return [] - -def get_all_files(root_dir: str): - """Get a list of all files in a directory.""" - for dir_path, _, file_names in os.walk(root_dir): - for file_name in file_names: - yield os.path.join(os.path.relpath(dir_path, root_dir), file_name) - -def get_input_files(root_dir: str): - """Get a list of all files in a Git repository that are not ignored.""" - ignored_files = set(get_git_ignored_files(root_dir)) - all_files = set(get_all_files(root_dir)) - nonignored_files = all_files - ignored_files - return further_filter(nonignored_files, root_dir) - -def load_gpt_index_documents(root: str) -> List[Document]: - """Loads a list of GPTIndex Documents, respecting .gitignore files.""" - # Get input files - input_files = get_input_files(root) - # Use SimpleDirectoryReader to load the files into Documents - return SimpleDirectoryReader(root, input_files=input_files, file_metadata=lambda filename: {"filename": filename}).load_data() - -def index_dir_for(branch: str) -> str: - return f"data/{branch}" - -def get_git_root_dir(): - result = subprocess.run(['git', 'rev-parse', '--show-toplevel'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - return result.stdout.decode().strip() - -def get_current_branch() -> str: - return subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]).decode("utf-8").strip() - -def get_current_commit() -> str: - return subprocess.check_output(["git", "rev-parse", "HEAD"]).decode("utf-8").strip() - -def create_codebase_index(): - """Create a new index for the current branch.""" - branch = get_current_branch() - if not os.path.exists(index_dir_for(branch)): - os.makedirs(index_dir_for(branch)) - - documents = load_gpt_index_documents(get_git_root_dir()) - - chunks = {} - doc_chunks = [] - for doc in documents: - text_splitter = TokenTextSplitter() - text_chunks = text_splitter.split_text(doc.text) - filename = doc.extra_info["filename"] - chunks[filename] = len(text_chunks) - for i, text in enumerate(text_chunks): - doc_chunks.append(Document(text, doc_id=f"{filename}::{i}")) - - with open(f"{index_dir_for(branch)}/metadata.json", "w") as f: - json.dump({"commit": get_current_commit(), "chunks" : chunks}, f, indent=4) - - index = GPTSimpleVectorIndex([]) - for chunk in doc_chunks: - index.insert(chunk) - - # d = 1536 # Dimension of text-ada-embedding-002 - # faiss_index = faiss.IndexFlatL2(d) - # index = GPTFaissIndex(documents, faiss_index=faiss_index) - # index.save_to_disk(f"{index_dir_for(branch)}/index.json", faiss_index_save_path=f"{index_dir_for(branch)}/index_faiss_core.index") - - index.save_to_disk(f"{index_dir_for(branch)}/index.json") - - print("Codebase index created") - -def get_modified_deleted_files() -> Tuple[List[str], List[str]]: - """Get a list of all files that have been modified since the last commit.""" - branch = get_current_branch() - current_commit = get_current_commit() - - metadata = f"{index_dir_for(branch)}/metadata.json" - with open(metadata, "r") as f: - previous_commit = json.load(f)["commit"] - - modified_deleted_files = subprocess.check_output(["git", "diff", "--name-only", previous_commit, current_commit]).decode("utf-8").strip() - modified_deleted_files = modified_deleted_files.split("\n") - modified_deleted_files = [f for f in modified_deleted_files if f] - - root = get_git_root_dir() - deleted_files = [f for f in modified_deleted_files if not os.path.exists(root + "/" + f)] - modified_files = [f for f in modified_deleted_files if os.path.exists(root + "/" + f)] - - return further_filter(modified_files, index_dir_for(branch)), further_filter(deleted_files, index_dir_for(branch)) - -def update_codebase_index(): - """Update the index with a list of files.""" - branch = get_current_branch() - - if not os.path.exists(index_dir_for(branch)): - create_codebase_index() - else: - # index = GPTFaissIndex.load_from_disk(f"{index_dir_for(branch)}/index.json", faiss_index_save_path=f"{index_dir_for(branch)}/index_faiss_core.index") - index = GPTSimpleVectorIndex.load_from_disk(f"{index_dir_for(branch)}/index.json") - modified_files, deleted_files = get_modified_deleted_files() - - with open(f"{index_dir_for(branch)}/metadata.json", "r") as f: - metadata = json.load(f) - - for file in deleted_files: - - num_chunks = metadata["chunks"][file] - for i in range(num_chunks): - index.delete(f"{file}::{i}") - - del metadata["chunks"][file] - - print(f"Deleted {file}") - - for file in modified_files: - - if file in metadata["chunks"]: - - num_chunks = metadata["chunks"][file] - - for i in range(num_chunks): - index.delete(f"{file}::{i}") - - print(f"Deleted old version of {file}") - - with open(file, "r") as f: - text = f.read() - - text_splitter = TokenTextSplitter() - text_chunks = text_splitter.split_text(text) - - for i, text in enumerate(text_chunks): - index.insert(Document(text, doc_id=f"{file}::{i}")) - - metadata["chunks"][file] = len(text_chunks) - - print(f"Inserted new version of {file}") - - metadata["commit"] = get_current_commit() - - with open(f"{index_dir_for(branch)}/metadata.json", "w") as f: - json.dump(metadata, f, indent=4) - - print("Codebase index updated") - -if __name__ == "__main__": - """python3 update.py""" - update_codebase_index() \ No newline at end of file diff --git a/extension/server/.gitignore b/extension/server/.gitignore new file mode 100644 index 00000000..0b6e11dd --- /dev/null +++ b/extension/server/.gitignore @@ -0,0 +1 @@ +**.whl \ No newline at end of file diff --git a/extension/server/README.md b/extension/server/README.md new file mode 100644 index 00000000..56208b5a --- /dev/null +++ b/extension/server/README.md @@ -0,0 +1,3 @@ +# server + +These are the files that get copied over to `~/.continue/server` in order to create the Python environment and start the Continue server. diff --git a/extension/server/requirements.txt b/extension/server/requirements.txt new file mode 100644 index 00000000..eb1f3738 --- /dev/null +++ b/extension/server/requirements.txt @@ -0,0 +1 @@ +./continuedev-0.1.2-py3-none-any.whl \ No newline at end of file diff --git a/extension/server/run_continue_server.py b/extension/server/run_continue_server.py new file mode 100644 index 00000000..089cc54d --- /dev/null +++ b/extension/server/run_continue_server.py @@ -0,0 +1,4 @@ +from continuedev.server.main import run_server + +if __name__ == "__main__": + run_server() diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 714080e3..02118501 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -7,6 +7,7 @@ import * as fs from "fs"; import { getContinueServerUrl } from "../bridge"; import fetch from "node-fetch"; import * as vscode from "vscode"; +import * as os from "os"; import fkill from "fkill"; import { sendTelemetryEvent, TelemetryEvent } from "../telemetry"; @@ -127,8 +128,7 @@ function getActivateUpgradeCommands(pythonCmd: string, pipCmd: string) { function checkEnvExists() { const envBinPath = path.join( - getExtensionUri().fsPath, - "scripts", + serverPath(), "env", process.platform == "win32" ? "Scripts" : "bin" ); @@ -140,10 +140,32 @@ function checkEnvExists() { ); } -function checkRequirementsInstalled() { +async function checkRequirementsInstalled() { + // First, check if the requirements have been installed most recently for a later version of the extension + if (fs.existsSync(requirementsVersionPath())) { + const requirementsVersion = fs.readFileSync( + requirementsVersionPath(), + "utf8" + ); + if (requirementsVersion !== getExtensionVersion()) { + // Remove the old version of continuedev from site-packages + const [pythonCmd, pipCmd] = await getPythonPipCommands(); + const [activateCmd] = getActivateUpgradeCommands(pythonCmd, pipCmd); + const removeOldVersionCommand = [ + `cd "${serverPath()}"`, + activateCmd, + `${pipCmd} uninstall -y continuedev`, + ].join(" ; "); + const [, stderr] = await runCommand(removeOldVersionCommand); + if (stderr) { + throw new Error(stderr); + } + return false; + } + } + let envLibsPath = path.join( - getExtensionUri().fsPath, - "scripts", + serverPath(), "env", process.platform == "win32" ? "Lib" : "lib" ); @@ -165,10 +187,6 @@ function checkRequirementsInstalled() { const continuePath = path.join(envLibsPath, "continuedev"); return fs.existsSync(continuePath); - - // return fs.existsSync( - // path.join(getExtensionUri().fsPath, "scripts", ".continue_env_installed") - // ); } async function setupPythonEnv() { @@ -180,12 +198,13 @@ async function setupPythonEnv() { pipCmd ); + // First, create the virtual environment if (checkEnvExists()) { console.log("Python env already exists, skipping..."); } else { // Assemble the command to create the env const createEnvCommand = [ - `cd "${path.join(getExtensionUri().fsPath, "scripts")}"`, + `cd "${serverPath()}"`, `${pythonCmd} -m venv env`, ].join(" ; "); @@ -216,10 +235,7 @@ async function setupPythonEnv() { console.log(msg); await vscode.window.showErrorMessage(msg); } else if (checkEnvExists()) { - console.log( - "Successfully set up python env at ", - getExtensionUri().fsPath + "/scripts/env" - ); + console.log("Successfully set up python env at ", `${serverPath()}/env`); } else { const msg = [ "Python environment not successfully created. Trying again. Here was the stdout + stderr: ", @@ -231,12 +247,13 @@ async function setupPythonEnv() { } } + // Install the requirements await retryThenFail(async () => { - if (checkRequirementsInstalled()) { + if (await checkRequirementsInstalled()) { console.log("Python requirements already installed, skipping..."); } else { const installRequirementsCommand = [ - `cd "${path.join(getExtensionUri().fsPath, "scripts")}"`, + `cd "${serverPath()}"`, activateCmd, pipUpgradeCmd, `${pipCmd} install -r requirements.txt`, @@ -245,6 +262,8 @@ async function setupPythonEnv() { if (stderr) { throw new Error(stderr); } + // Write the version number for which requirements were installed + fs.writeFileSync(requirementsVersionPath(), getExtensionVersion()); } }); } @@ -297,9 +316,39 @@ async function checkServerRunning(serverUrl: string): Promise { } } +export function getContinueGlobalPath(): string { + // This is ~/.continue on mac/linux + const continuePath = path.join(os.homedir(), ".continue"); + if (!fs.existsSync(continuePath)) { + fs.mkdirSync(continuePath); + } + return continuePath; +} + +function setupServerPath() { + const sPath = serverPath(); + const extensionServerPath = path.join(getExtensionUri().fsPath, "server"); + const files = fs.readdirSync(extensionServerPath); + files.forEach((file) => { + const filePath = path.join(extensionServerPath, file); + fs.copyFileSync(filePath, path.join(sPath, file)); + }); +} + +function serverPath(): string { + const sPath = path.join(getContinueGlobalPath(), "server"); + if (!fs.existsSync(sPath)) { + fs.mkdirSync(sPath); + } + return sPath; +} + function serverVersionPath(): string { - const extensionPath = getExtensionUri().fsPath; - return path.join(extensionPath, "server_version.txt"); + return path.join(serverPath(), "server_version.txt"); +} + +function requirementsVersionPath(): string { + return path.join(serverPath(), "requirements_version.txt"); } function getExtensionVersion() { @@ -314,6 +363,8 @@ export async function startContinuePythonServer() { return; } + setupServerPath(); + return await retryThenFail(async () => { if (await checkServerRunning(serverUrl)) { // Kill the server if it is running an old version @@ -337,16 +388,14 @@ export async function startContinuePythonServer() { // Do this after above check so we don't have to waste time setting up the env await setupPythonEnv(); + // Spawn the server process on port 65432 const [pythonCmd] = await getPythonPipCommands(); const activateCmd = process.platform == "win32" ? ".\\env\\Scripts\\activate" : ". env/bin/activate"; - const command = `cd "${path.join( - getExtensionUri().fsPath, - "scripts" - )}" && ${activateCmd} && cd .. && ${pythonCmd} -m scripts.run_continue_server`; + const command = `cd "${serverPath()}" && ${activateCmd} && cd .. && ${pythonCmd} -m server.run_continue_server`; console.log("Starting Continue python server..."); @@ -392,8 +441,8 @@ export async function startContinuePythonServer() { } export function isPythonEnvSetup(): boolean { - let pathToEnvCfg = getExtensionUri().fsPath + "/scripts/env/pyvenv.cfg"; - return fs.existsSync(path.join(pathToEnvCfg)); + const pathToEnvCfg = path.join(serverPath(), "env", "pyvenv.cfg"); + return fs.existsSync(pathToEnvCfg); } export async function downloadPython3() { -- cgit v1.2.3-70-g09d2 From 55ff206a9c24e67cd1f32ec86d705206530e668f Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 10 Jul 2023 19:31:48 -0700 Subject: catch errors during asyncio tasks --- continuedev/src/continuedev/core/autopilot.py | 4 +- .../src/continuedev/libs/util/create_async_task.py | 23 +++++++++ continuedev/src/continuedev/server/gui.py | 54 +++++++++++++--------- continuedev/src/continuedev/server/ide.py | 20 ++++---- .../src/continuedev/server/session_manager.py | 9 ++-- .../src/continuedev/steps/search_directory.py | 5 +- extension/src/continueIdeClient.ts | 2 +- 7 files changed, 77 insertions(+), 40 deletions(-) create mode 100644 continuedev/src/continuedev/libs/util/create_async_task.py (limited to 'extension/src') diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 5c3baafd..615e7657 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -20,6 +20,7 @@ import asyncio from ..libs.util.step_name_to_steps import get_step_from_name from ..libs.util.traceback_parsers import get_python_traceback, get_javascript_traceback from openai import error as openai_errors +from ..libs.util.create_async_task import create_async_task def get_error_title(e: Exception) -> str: @@ -341,7 +342,8 @@ class Autopilot(ContinueBaseModel): # Update subscribers with new description await self.update_subscribers() - asyncio.create_task(update_description()) + create_async_task(update_description(), + self.continue_sdk.ide.unique_id) return observation diff --git a/continuedev/src/continuedev/libs/util/create_async_task.py b/continuedev/src/continuedev/libs/util/create_async_task.py new file mode 100644 index 00000000..89b84868 --- /dev/null +++ b/continuedev/src/continuedev/libs/util/create_async_task.py @@ -0,0 +1,23 @@ +from typing import Coroutine, Union +import traceback +from .telemetry import capture_event +import asyncio +import nest_asyncio +nest_asyncio.apply() + + +def create_async_task(coro: Coroutine, unique_id: Union[str, None] = None): + """asyncio.create_task and log errors by adding a callback""" + task = asyncio.create_task(coro) + + def callback(future: asyncio.Future): + try: + future.result() + 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(e.__traceback__) + }) + + task.add_done_callback(callback) + return task diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index 8e9b1fb9..8daad73a 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -7,9 +7,8 @@ from uvicorn.main import Server from .session_manager import SessionManager, session_manager, Session from .gui_protocol import AbstractGUIProtocolServer from ..libs.util.queue import AsyncSubscriptionQueue -import asyncio -import nest_asyncio -nest_asyncio.apply() +from ..libs.util.telemetry import capture_event +from ..libs.util.create_async_task import create_async_task router = APIRouter(prefix="/gui", tags=["gui"]) @@ -102,51 +101,60 @@ class GUIProtocolServer(AbstractGUIProtocolServer): def on_main_input(self, input: str): # Do something with user input - asyncio.create_task(self.session.autopilot.accept_user_input(input)) + create_async_task(self.session.autopilot.accept_user_input( + input), self.session.autopilot.continue_sdk.ide.unique_id) def on_reverse_to_index(self, index: int): # Reverse the history to the given index - asyncio.create_task(self.session.autopilot.reverse_to_index(index)) + create_async_task(self.session.autopilot.reverse_to_index( + index), self.session.autopilot.continue_sdk.ide.unique_id) def on_step_user_input(self, input: str, index: int): - asyncio.create_task( - self.session.autopilot.give_user_input(input, index)) + create_async_task( + self.session.autopilot.give_user_input(input, index), self.session.autopilot.continue_sdk.ide.unique_id) def on_refinement_input(self, input: str, index: int): - asyncio.create_task( - self.session.autopilot.accept_refinement_input(input, index)) + create_async_task( + self.session.autopilot.accept_refinement_input(input, index), self.session.autopilot.continue_sdk.ide.unique_id) def on_retry_at_index(self, index: int): - asyncio.create_task( - self.session.autopilot.retry_at_index(index)) + create_async_task( + self.session.autopilot.retry_at_index(index), self.session.autopilot.continue_sdk.ide.unique_id) def on_change_default_model(self, model: str): - asyncio.create_task(self.session.autopilot.change_default_model(model)) + create_async_task(self.session.autopilot.change_default_model( + model), self.session.autopilot.continue_sdk.ide.unique_id) def on_clear_history(self): - asyncio.create_task(self.session.autopilot.clear_history()) + create_async_task(self.session.autopilot.clear_history( + ), self.session.autopilot.continue_sdk.ide.unique_id) def on_delete_at_index(self, index: int): - asyncio.create_task(self.session.autopilot.delete_at_index(index)) + create_async_task(self.session.autopilot.delete_at_index( + index), self.session.autopilot.continue_sdk.ide.unique_id) def on_delete_context_at_indices(self, indices: List[int]): - asyncio.create_task( - self.session.autopilot.delete_context_at_indices(indices) + create_async_task( + self.session.autopilot.delete_context_at_indices( + indices), self.session.autopilot.continue_sdk.ide.unique_id ) def on_toggle_adding_highlighted_code(self): - asyncio.create_task( - self.session.autopilot.toggle_adding_highlighted_code() + create_async_task( + self.session.autopilot.toggle_adding_highlighted_code( + ), self.session.autopilot.continue_sdk.ide.unique_id ) def on_set_editing_at_indices(self, indices: List[int]): - asyncio.create_task( - self.session.autopilot.set_editing_at_indices(indices) + create_async_task( + self.session.autopilot.set_editing_at_indices( + indices), self.session.autopilot.continue_sdk.ide.unique_id ) def on_set_pinned_at_indices(self, indices: List[int]): - asyncio.create_task( - self.session.autopilot.set_pinned_at_indices(indices) + create_async_task( + self.session.autopilot.set_pinned_at_indices( + indices), self.session.autopilot.continue_sdk.ide.unique_id ) @@ -179,6 +187,8 @@ 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": e.__traceback__}) raise e finally: print("Closing gui websocket") diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index e4a6266a..f84f3de2 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -15,8 +15,7 @@ from pydantic import BaseModel from .gui import SessionManager, session_manager from .ide_protocol import AbstractIdeProtocolServer import asyncio -import nest_asyncio -nest_asyncio.apply() +from ..libs.util.create_async_task import create_async_task router = APIRouter(prefix="/ide", tags=["ide"]) @@ -250,24 +249,25 @@ class IdeProtocolServer(AbstractIdeProtocolServer): def onDeleteAtIndex(self, index: int): for _, session in self.session_manager.sessions.items(): - asyncio.create_task(session.autopilot.delete_at_index(index)) + create_async_task( + session.autopilot.delete_at_index(index), self.unique_id) def onCommandOutput(self, output: str): # Send the output to ALL autopilots. # Maybe not ideal behavior for _, session in self.session_manager.sessions.items(): - asyncio.create_task( - session.autopilot.handle_command_output(output)) + create_async_task( + session.autopilot.handle_command_output(output), self.unique_id) def onHighlightedCodeUpdate(self, range_in_files: List[RangeInFileWithContents]): for _, session in self.session_manager.sessions.items(): - asyncio.create_task( - session.autopilot.handle_highlighted_code(range_in_files)) + create_async_task( + session.autopilot.handle_highlighted_code(range_in_files), self.unique_id) def onMainUserInput(self, input: str): for _, session in self.session_manager.sessions.items(): - asyncio.create_task( - session.autopilot.accept_user_input(input)) + create_async_task( + session.autopilot.accept_user_input(input), self.unique_id) # Request information. Session doesn't matter. async def getOpenFiles(self) -> List[str]: @@ -412,5 +412,7 @@ async def websocket_endpoint(websocket: WebSocket): await websocket.close() 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": e.__traceback__}) await websocket.close() raise e diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py index 99a38146..873a379e 100644 --- a/continuedev/src/continuedev/server/session_manager.py +++ b/continuedev/src/continuedev/server/session_manager.py @@ -1,3 +1,4 @@ +from asyncio import BaseEventLoop from fastapi import WebSocket from typing import Any, Dict, List, Union from uuid import uuid4 @@ -7,9 +8,7 @@ from ..core.policy import DemoPolicy from ..core.main import FullState from ..core.autopilot import Autopilot from .ide_protocol import AbstractIdeProtocolServer -import asyncio -import nest_asyncio -nest_asyncio.apply() +from ..libs.util.create_async_task import create_async_task class Session: @@ -38,7 +37,7 @@ class DemoAutopilot(Autopilot): class SessionManager: sessions: Dict[str, Session] = {} - _event_loop: Union[asyncio.BaseEventLoop, None] = None + _event_loop: Union[BaseEventLoop, None] = None def get_session(self, session_id: str) -> Session: if session_id not in self.sessions: @@ -57,7 +56,7 @@ class SessionManager: }) autopilot.on_update(on_update) - asyncio.create_task(autopilot.run_policy()) + create_async_task(autopilot.run_policy()) return session_id def remove_session(self, session_id: str): diff --git a/continuedev/src/continuedev/steps/search_directory.py b/continuedev/src/continuedev/steps/search_directory.py index 2eecc99c..bfb97630 100644 --- a/continuedev/src/continuedev/steps/search_directory.py +++ b/continuedev/src/continuedev/steps/search_directory.py @@ -6,6 +6,7 @@ from ..models.filesystem import RangeInFile from ..models.main import Range from ..core.main import Step from ..core.sdk import ContinueSDK +from ..libs.util.create_async_task import create_async_task import os import re @@ -60,9 +61,9 @@ class EditAllMatchesStep(Step): # Search all files for a given string range_in_files = find_all_matches_in_dir(self.pattern, self.directory or await sdk.ide.getWorkspaceDirectory()) - tasks = [asyncio.create_task(sdk.edit_file( + tasks = [create_async_task(sdk.edit_file( range=range_in_file.range, filename=range_in_file.filepath, prompt=self.user_request - )) for range_in_file in range_in_files] + ), sdk.ide.unique_id) for range_in_file in range_in_files] await asyncio.gather(*tasks) diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 679d94ba..304c592b 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -300,7 +300,7 @@ class IdeProtocolClient { }); const resp = await this.messenger?.sendAndReceive("openGUI", {}); const sessionId = resp.sessionId; - console.log("New Continue session with ID: ", sessionId); + // console.log("New Continue session with ID: ", sessionId); return sessionId; } -- cgit v1.2.3-70-g09d2 From 50adabaef68917a65b123dbf6026c410b8c5a006 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 00:51:42 -0700 Subject: don't allow /edit in diff editor, save diff editor --- continuedev/src/continuedev/steps/main.py | 10 +++++++++- extension/src/diffs.ts | 17 ++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) (limited to 'extension/src') diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py index 4f543022..2a8cd250 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,6 +251,9 @@ 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, "")) @@ -278,6 +281,11 @@ class EditHighlightedCodeStep(Step): 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/extension/src/diffs.ts b/extension/src/diffs.ts index b9ef8384..1dc292e1 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) { -- cgit v1.2.3-70-g09d2 From b1a39567addc511ac0b477aaedaae4e10d7f5d31 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 11:31:41 -0700 Subject: explain insert at cursor, better diff streaming --- continuedev/src/continuedev/steps/core/core.py | 44 ++++++++++++++++++++++--- extension/react-app/src/components/ComboBox.tsx | 32 +++++++++++++++--- extension/src/diffs.ts | 7 +++- 3 files changed, 72 insertions(+), 11 deletions(-) (limited to 'extension/src') 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_suffix} + + +{self.user_input} + + +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/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index 801c3a03..585a0584 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) => { - {/* - Highlight code to include as context. Currently open file included by - default. {highlightedCodeSections.length === 0 && ""} - */} + {highlightedCodeSections.length === 0 && + (downshiftProps.inputValue?.startsWith("/edit") || metaKeyPressed) && ( +
+ Inserting at cursor +
+ )} { setHoveringContextDropdown(true); diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts index 1dc292e1..3ea6b4f8 100644 --- a/extension/src/diffs.ts +++ b/extension/src/diffs.ts @@ -164,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); + }); } } -- cgit v1.2.3-70-g09d2 From 84910d445f44f8de1f160ff81e06c4eaf9c17ee7 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 11:48:43 -0700 Subject: air on the side of killing server --- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- extension/src/activation/environmentSetup.ts | 34 +++++++++++++++------------- 3 files changed, 21 insertions(+), 19 deletions(-) (limited to 'extension/src') diff --git a/extension/package-lock.json b/extension/package-lock.json index 71f4d974..d778b097 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.147", + "version": "0.0.149", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.147", + "version": "0.0.149", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index d9f155df..56c49f27 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.149", "publisher": "Continue", "engines": { "vscode": "^1.67.0" diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 02118501..ff8d3158 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -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(); -- cgit v1.2.3-70-g09d2 From a3e0a8432c096de463378756a82979f5d327b3a3 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 12:15:35 -0700 Subject: don't override error messages with a second one --- extension/src/activation/environmentSetup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'extension/src') diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index ff8d3158..f5d825c9 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, { -- cgit v1.2.3-70-g09d2 From f515c9179b27885a024c197f8dead0c2e311aa8e Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 12:33:02 -0700 Subject: patch --- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- extension/src/activation/environmentSetup.ts | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) (limited to 'extension/src') diff --git a/extension/package-lock.json b/extension/package-lock.json index d778b097..c43851a2 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.149", + "version": "0.0.150", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.149", + "version": "0.0.150", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 56c49f27..e2fe6e8e 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.149", + "version": "0.0.150", "publisher": "Continue", "engines": { "vscode": "^1.67.0" diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index f5d825c9..8b384e1d 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -224,6 +224,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]; -- cgit v1.2.3-70-g09d2 From ea76462f971d6d235baacbb85d6e9ae6fe09d7e0 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 12:43:15 -0700 Subject: fix bug uninstalling continuedev --- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- extension/src/activation/environmentSetup.ts | 5 +---- 3 files changed, 4 insertions(+), 7 deletions(-) (limited to 'extension/src') diff --git a/extension/package-lock.json b/extension/package-lock.json index c43851a2..b6147b2c 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.150", + "version": "0.0.151", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.150", + "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 e2fe6e8e..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.150", + "version": "0.0.151", "publisher": "Continue", "engines": { "vscode": "^1.67.0" diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 8b384e1d..881da9b5 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -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; } } -- cgit v1.2.3-70-g09d2 From 9846f4769ff33a346d76e26ad730d19770fe7e02 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 15:14:03 -0700 Subject: check for old version of Continue, notify --- extension/src/activation/activate.ts | 22 +++++++++++++++++++++- extension/src/activation/environmentSetup.ts | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) (limited to 'extension/src') 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 881da9b5..c277a539 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -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 || ""; } -- cgit v1.2.3-70-g09d2 From 19e79b9336fd9abe5776655ece25224d22d92ad8 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 11 Jul 2023 16:40:11 -0700 Subject: explain accept/reject, better edit summaries --- continuedev/src/continuedev/steps/core/core.py | 19 ++++++++++++----- extension/src/diffs.ts | 29 ++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 7 deletions(-) (limited to 'extension/src') diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index d4d067ba..9eddc03f 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -152,7 +152,8 @@ class DefaultModelEditCodeStep(Step): Main task: """) - + _previous_contents: str = "" + _new_contents: str = "" _prompt_and_completion: str = "" def _cleanup_output(self, output: str) -> str: @@ -167,13 +168,19 @@ class DefaultModelEditCodeStep(Step): return output async def describe(self, models: Models) -> Coroutine[str, None, None]: - if self._prompt_and_completion == "": + if self._previous_contents.strip() == self._new_contents.strip(): description = "No edits were made" else: description = await models.gpt3516k.complete(dedent(f"""\ - {self._prompt_and_completion} - - Please give brief a description of the changes made above using markdown bullet points. Be concise and only mention changes made to the commit before, not prefix or suffix:""")) + ```original + {self._previous_contents} + ``` + + ```new + {self._new_contents} + ``` + + Please give brief a description of the changes made above using markdown bullet points. Be concise:""")) name = await models.gpt3516k.complete(f"Write a very short title to describe this requested change (no quotes): '{self.user_input}'. This is the title:") self.name = self._cleanup_output(name) @@ -573,6 +580,8 @@ Please output the code to be inserted at the cursor in order to fulfill the user # Record the completion completion = "\n".join(lines) + self._previous_contents = "\n".join(original_lines) + self._new_contents = completion self._prompt_and_completion += prompt + completion async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts index 3ea6b4f8..28089fc6 100644 --- a/extension/src/diffs.ts +++ b/extension/src/diffs.ts @@ -2,7 +2,7 @@ import * as os from "os"; import * as path from "path"; import * as fs from "fs"; import * as vscode from "vscode"; -import { ideProtocolClient } from "./activation/activate"; +import { extensionContext, ideProtocolClient } from "./activation/activate"; interface DiffInfo { originalFilepath: string; @@ -70,6 +70,28 @@ class DiffManager { .getConfiguration("diffEditor", editor.document.uri) .update("codeLens", true, vscode.ConfigurationTarget.Global); + if ( + extensionContext?.globalState.get( + "continue.showDiffInfoMessage" + ) !== false + ) { + vscode.window + .showInformationMessage( + "Accept (⌘⇧↩) or reject (⌘⇧⌫) at the top of the file.", + "Got it", + "Don't show again" + ) + .then((selection) => { + if (selection === "Don't show again") { + // Get the global state + extensionContext?.globalState.update( + "continue.showDiffInfoMessage", + false + ); + } + }); + } + return editor; } @@ -152,7 +174,10 @@ class DiffManager { newFilepath = Array.from(this.diffs.keys())[0]; } if (!newFilepath) { - console.log("No newFilepath provided to reject the diff"); + console.log( + "No newFilepath provided to reject the diff, diffs.size was", + this.diffs.size + ); return; } const diffInfo = this.diffs.get(newFilepath); -- cgit v1.2.3-70-g09d2 From 5ed9aff7b888f849f1c7b0d7c150f44b56ffc67f Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Wed, 12 Jul 2023 10:03:11 -0700 Subject: better onboarding --- extension/package-lock.json | 4 +- extension/package.json | 2 +- extension/react-app/src/components/Onboarding.tsx | 51 ++++++++++++++++++----- extension/src/activation/activate.ts | 2 +- 4 files changed, 45 insertions(+), 14 deletions(-) (limited to 'extension/src') diff --git a/extension/package-lock.json b/extension/package-lock.json index d310643b..5d1cf395 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.153", + "version": "0.0.154", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.153", + "version": "0.0.154", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 61eee205..ab23b0b0 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.153", + "version": "0.0.154", "publisher": "Continue", "engines": { "vscode": "^1.67.0" diff --git a/extension/react-app/src/components/Onboarding.tsx b/extension/react-app/src/components/Onboarding.tsx index 1b37acc4..776ad460 100644 --- a/extension/react-app/src/components/Onboarding.tsx +++ b/extension/react-app/src/components/Onboarding.tsx @@ -4,6 +4,7 @@ import React, { useState, useEffect } from "react"; import styled from "styled-components"; import { ArrowLeft, ArrowRight } from "@styled-icons/heroicons-outline"; import { defaultBorderRadius } from "."; +import Loader from "./Loader"; const StyledDiv = styled.div` position: absolute; @@ -39,13 +40,9 @@ const Onboarding = () => { "Let Continue build the scaffolding of Python scripts, React components, and more", ]; - const vscMediaUrl = useSelector( - (state: RootStore) => state.config.vscMediaUrl - ); - useEffect(() => { const hasVisited = localStorage.getItem("hasVisited"); - if (hasVisited) { + if (hasVisited && false) { setCounter(4); } else { setCounter(0); @@ -53,6 +50,12 @@ const Onboarding = () => { } }, []); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setLoading(true); + }, [counter]); + return (