diff options
Diffstat (limited to 'extension')
| -rw-r--r-- | extension/DEV_README.md | 2 | ||||
| -rw-r--r-- | extension/package.json | 2 | ||||
| -rw-r--r-- | extension/react-app/src/tabs/gui.tsx | 2 | ||||
| -rw-r--r-- | extension/scripts/.gitignore | 5 | ||||
| -rw-r--r-- | extension/scripts/README.md | 5 | ||||
| -rw-r--r-- | extension/scripts/chroma.py | 152 | ||||
| -rw-r--r-- | extension/scripts/index.py | 52 | ||||
| -rw-r--r-- | extension/scripts/query.py | 63 | ||||
| -rw-r--r-- | extension/scripts/replace.py | 17 | ||||
| -rw-r--r-- | extension/scripts/requirements.txt | 6 | ||||
| -rw-r--r-- | extension/scripts/update.py | 185 | ||||
| -rw-r--r-- | extension/server/.gitignore | 1 | ||||
| -rw-r--r-- | extension/server/README.md | 3 | ||||
| -rw-r--r-- | extension/server/requirements.txt | 1 | ||||
| -rw-r--r-- | extension/server/run_continue_server.py (renamed from extension/scripts/run_continue_server.py) | 0 | ||||
| -rw-r--r-- | extension/src/activation/environmentSetup.ts | 97 | 
16 files changed, 81 insertions, 512 deletions
| 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>""" -    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/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/scripts/run_continue_server.py b/extension/server/run_continue_server.py index 089cc54d..089cc54d 100644 --- a/extension/scripts/run_continue_server.py +++ b/extension/server/run_continue_server.py 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<boolean> {    }  } +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() { | 
