summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNate Sesti <sestinj@gmail.com>2023-07-16 16:25:02 -0700
committerNate Sesti <sestinj@gmail.com>2023-07-16 16:25:02 -0700
commitda7827189181328baa329d2984d0fd9c6d476be3 (patch)
tree56308a5595e8e01ab83974688d8ac8e4945f319a
parentd80119982e9b60ca0022533a0086eb526dc7d957 (diff)
parenteab69781a3e3b5236916d9057ce29aba2e868913 (diff)
downloadsncontinue-da7827189181328baa329d2984d0fd9c6d476be3.tar.gz
sncontinue-da7827189181328baa329d2984d0fd9c6d476be3.tar.bz2
sncontinue-da7827189181328baa329d2984d0fd9c6d476be3.zip
Merge branch 'main' into ggml-server
-rw-r--r--README.md22
-rw-r--r--continuedev/src/continuedev/core/abstract_sdk.py4
-rw-r--r--continuedev/src/continuedev/core/autopilot.py44
-rw-r--r--continuedev/src/continuedev/core/config.py9
-rw-r--r--continuedev/src/continuedev/core/policy.py7
-rw-r--r--continuedev/src/continuedev/core/sdk.py95
-rw-r--r--continuedev/src/continuedev/libs/constants/main.py6
-rw-r--r--continuedev/src/continuedev/libs/llm/hf_inference_api.py6
-rw-r--r--continuedev/src/continuedev/libs/util/create_async_task.py24
-rw-r--r--continuedev/src/continuedev/libs/util/errors.py2
-rw-r--r--continuedev/src/continuedev/libs/util/paths.py17
-rw-r--r--continuedev/src/continuedev/libs/util/step_name_to_steps.py4
-rw-r--r--continuedev/src/continuedev/recipes/TemplateRecipe/main.py4
-rw-r--r--continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py2
-rw-r--r--continuedev/src/continuedev/server/gui.py83
-rw-r--r--continuedev/src/continuedev/server/ide.py149
-rw-r--r--continuedev/src/continuedev/server/ide_protocol.py27
-rw-r--r--continuedev/src/continuedev/server/main.py16
-rw-r--r--continuedev/src/continuedev/server/session_manager.py51
-rw-r--r--continuedev/src/continuedev/server/state_manager.py21
-rw-r--r--continuedev/src/continuedev/steps/chat.py21
-rw-r--r--continuedev/src/continuedev/steps/core/core.py178
-rw-r--r--continuedev/src/continuedev/steps/help.py59
-rw-r--r--continuedev/src/continuedev/steps/main.py48
-rw-r--r--continuedev/src/continuedev/steps/open_config.py9
-rw-r--r--continuedev/src/continuedev/steps/search_directory.py5
-rw-r--r--continuedev/src/continuedev/steps/welcome.py2
-rw-r--r--docs/docs/concepts/ide.md4
-rw-r--r--docs/docs/how-to-use-continue.md40
-rw-r--r--edit.gifbin11209423 -> 14939314 bytes
-rw-r--r--extension/DEV_README.md2
-rw-r--r--extension/README.md18
-rw-r--r--extension/media/edit.pngbin246984 -> 675283 bytes
-rw-r--r--extension/media/explain.pngbin464952 -> 897037 bytes
-rw-r--r--extension/media/generate.pngbin1049613 -> 411580 bytes
-rw-r--r--extension/package-lock.json4
-rw-r--r--extension/package.json26
-rw-r--r--extension/react-app/index.html2
-rw-r--r--extension/react-app/package-lock.json1473
-rw-r--r--extension/react-app/package.json2
-rw-r--r--extension/react-app/public/vite.svg1
-rw-r--r--extension/react-app/src/App.tsx13
-rw-r--r--extension/react-app/src/TestPage.tsx33
-rw-r--r--extension/react-app/src/assets/Hubot-Sans.woff2bin165932 -> 0 bytes
-rw-r--r--extension/react-app/src/assets/Mona-Sans.woff2bin133748 -> 0 bytes
-rw-r--r--extension/react-app/src/assets/react.svg1
-rw-r--r--extension/react-app/src/components/CodeMultiselect.tsx276
-rw-r--r--extension/react-app/src/components/ComboBox.tsx68
-rw-r--r--extension/react-app/src/components/LoadingCover.tsx50
-rw-r--r--extension/react-app/src/components/Onboarding.tsx136
-rw-r--r--extension/react-app/src/components/PillButton.tsx167
-rw-r--r--extension/react-app/src/components/StepContainer.tsx89
-rw-r--r--extension/react-app/src/components/TextDialog.tsx7
-rw-r--r--extension/react-app/src/highlight/dark.min.css53
-rw-r--r--extension/react-app/src/hooks/messenger.ts10
-rw-r--r--extension/react-app/src/pages/gui.tsx (renamed from extension/react-app/src/tabs/gui.tsx)37
-rw-r--r--extension/react-app/src/tabs/additionalContext.tsx18
-rw-r--r--extension/react-app/src/tabs/chat/MessageDiv.tsx75
-rw-r--r--extension/react-app/src/tabs/chat/index.tsx267
-rw-r--r--extension/react-app/src/tabs/main.tsx189
-rw-r--r--extension/react-app/src/tabs/welcome.tsx22
-rw-r--r--extension/react-app/src/util/api.ts43
-rw-r--r--extension/react-app/src/util/editCache.ts89
-rw-r--r--extension/react-app/src/util/index.ts62
-rw-r--r--extension/scripts/.gitignore5
-rw-r--r--extension/scripts/README.md5
-rw-r--r--extension/scripts/chroma.py152
-rw-r--r--extension/scripts/index.py52
-rw-r--r--extension/scripts/query.py63
-rw-r--r--extension/scripts/replace.py17
-rw-r--r--extension/scripts/requirements.txt6
-rw-r--r--extension/scripts/update.py185
-rw-r--r--extension/server/.gitignore1
-rw-r--r--extension/server/README.md3
-rw-r--r--extension/server/requirements.txt1
-rw-r--r--extension/server/run_continue_server.py (renamed from extension/scripts/run_continue_server.py)0
-rw-r--r--extension/src/activation/activate.ts47
-rw-r--r--extension/src/activation/environmentSetup.ts246
-rw-r--r--extension/src/bridge.ts213
-rw-r--r--extension/src/commands.ts117
-rw-r--r--extension/src/continueIdeClient.ts164
-rw-r--r--extension/src/debugPanel.ts114
-rw-r--r--extension/src/diffs.ts175
-rw-r--r--extension/src/extension.ts20
-rw-r--r--extension/src/lang-server/codeActions.ts53
-rw-r--r--extension/src/lang-server/codeLens.ts51
-rw-r--r--extension/src/suggestions.ts60
-rw-r--r--extension/src/util/messenger.ts10
-rw-r--r--extension/src/util/util.ts29
-rw-r--r--media/edit.gifbin0 -> 41667733 bytes
-rw-r--r--media/explain.gifbin0 -> 56475028 bytes
-rw-r--r--media/generate.gifbin0 -> 32532380 bytes
-rw-r--r--media/help.gifbin0 -> 1046474 bytes
-rw-r--r--media/highlight.gifbin0 -> 4103271 bytes
-rw-r--r--media/intro.gifbin0 -> 3976676 bytes
-rw-r--r--media/question.gifbin0 -> 2293656 bytes
96 files changed, 3192 insertions, 2789 deletions
diff --git a/README.md b/README.md
index a91fbdf4..01462247 100644
--- a/README.md
+++ b/README.md
@@ -19,26 +19,26 @@
## Task, not tab, auto-complete
-### Edit in natural language
-
### Get possible explainations
Ask Continue about a part of your code to get another perspective
-- `what might cause this error?`
-- `what is the load_dotenv library name?`
-- `how do I find running process on port 8000?`
+- “how can I set up a Prisma schema that cascades deletes?”
+- “where in the page should I be making this request to the backend?”
+- “how can I communicate between these iframes?”
+
+### Edit in natural language
Highlight a section of code and instruct Continue to refactor it
-- `/edit Make this use more descriptive variable names`
-- `/edit Rewrite this API call to grab all pages`
-- `/edit Use 'Union' instead of a vertical bar here`
+- “/edit migrate this digital ocean terraform file into one that works for GCP”
+- “/edit change this plot into a bar chart in this dashboard component”
+- “/edit rewrite this function to be async”
### Generate files from scratch
Let Continue build the scaffolding of Python scripts, React components, and more
-- `Create a shell script to back up my home dir to /tmp/`
-- `Write a Python script to get Posthog events`
-- `Add a React component for syntax highlighted code`
+- “/edit here is a connector for postgres, now write one for kafka”
+- “/edit make an IAM policy that creates a user with read-only access to S3”
+- “/edit use this schema to write me a SQL query that gets recently churned users”
## Getting Started
diff --git a/continuedev/src/continuedev/core/abstract_sdk.py b/continuedev/src/continuedev/core/abstract_sdk.py
index 7bd3da6c..94d7be10 100644
--- a/continuedev/src/continuedev/core/abstract_sdk.py
+++ b/continuedev/src/continuedev/core/abstract_sdk.py
@@ -76,9 +76,7 @@ class AbstractContinueSDK(ABC):
async def get_user_secret(self, env_var: str, prompt: str) -> str:
pass
- @abstractproperty
- def config(self) -> ContinueConfig:
- pass
+ config: ContinueConfig
@abstractmethod
def set_loading_message(self, message: str):
diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index 5c3baafd..0696c360 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -1,13 +1,13 @@
from functools import cached_property
import traceback
import time
-from typing import Any, Callable, Coroutine, Dict, List
+from typing import Any, Callable, Coroutine, Dict, List, Union
import os
from aiohttp import ClientPayloadError
+from pydantic import root_validator
from ..models.filesystem import RangeInFileWithContents
from ..models.filesystem_edit import FileEditWithFullContents
-from ..libs.llm import LLM
from .observation import Observation, InternalErrorObservation
from ..server.ide_protocol import AbstractIdeProtocolServer
from ..libs.util.queue import AsyncSubscriptionQueue
@@ -16,10 +16,10 @@ from .main import Context, ContinueCustomException, HighlightedRangeContext, Pol
from ..steps.core.core import ReversibleStep, ManualEditStep, UserInputStep
from ..libs.util.telemetry import capture_event
from .sdk import ContinueSDK
-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:
@@ -34,9 +34,11 @@ def get_error_title(e: Exception) -> str:
elif isinstance(e, ClientPayloadError):
return "The request to OpenAI failed. Please try again."
elif isinstance(e, openai_errors.APIConnectionError):
- return "The request failed. Please check your internet connection and try again."
+ return "The request failed. Please check your internet connection and try again. If this issue persists, you can use our API key for free by going to VS Code settings and changing the value of continue.OPENAI_API_KEY to \"\""
elif isinstance(e, openai_errors.InvalidRequestError):
return 'Your API key does not have access to GPT-4. You can use ours for free by going to VS Code settings and changing the value of continue.OPENAI_API_KEY to ""'
+ elif e.__str__().startswith("Cannot connect to host"):
+ return "The request failed. Please check your internet connection and try again."
return e.__str__() or e.__repr__()
@@ -45,8 +47,11 @@ class Autopilot(ContinueBaseModel):
ide: AbstractIdeProtocolServer
history: History = History.from_empty()
context: Context = Context()
+ full_state: Union[FullState, None] = None
_on_update_callbacks: List[Callable[[FullState], None]] = []
+ continue_sdk: ContinueSDK = None
+
_active: bool = False
_should_halt: bool = False
_main_user_input_queue: List[str] = []
@@ -54,16 +59,25 @@ class Autopilot(ContinueBaseModel):
_user_input_queue = AsyncSubscriptionQueue()
_retry_queue = AsyncSubscriptionQueue()
- @cached_property
- def continue_sdk(self) -> ContinueSDK:
- return ContinueSDK(self)
+ @classmethod
+ async def create(cls, policy: Policy, ide: AbstractIdeProtocolServer, full_state: FullState) -> "Autopilot":
+ autopilot = cls(ide=ide, policy=policy)
+ autopilot.continue_sdk = await ContinueSDK.create(autopilot)
+ return autopilot
class Config:
arbitrary_types_allowed = True
keep_untouched = (cached_property,)
+ @root_validator(pre=True)
+ def fill_in_values(cls, values):
+ full_state: FullState = values.get('full_state')
+ if full_state is not None:
+ values['history'] = full_state.history
+ return values
+
def get_full_state(self) -> FullState:
- return FullState(
+ full_state = FullState(
history=self.history,
active=self._active,
user_input_queue=self._main_user_input_queue,
@@ -72,6 +86,8 @@ class Autopilot(ContinueBaseModel):
slash_commands=self.get_available_slash_commands(),
adding_highlighted_code=self._adding_highlighted_code,
)
+ self.full_state = full_state
+ return full_state
def get_available_slash_commands(self) -> List[Dict]:
custom_commands = list(map(lambda x: {
@@ -207,6 +223,8 @@ class Autopilot(ContinueBaseModel):
async def delete_at_index(self, index: int):
self.history.timeline[index].step.hide = True
self.history.timeline[index].deleted = True
+ self.history.timeline[index].active = False
+
await self.update_subscribers()
async def delete_context_at_indices(self, indices: List[int]):
@@ -250,7 +268,7 @@ class Autopilot(ContinueBaseModel):
# i -= 1
capture_event(self.continue_sdk.ide.unique_id, 'step run', {
- 'step_name': step.name, 'params': step.dict()})
+ 'step_name': step.name, 'params': step.dict()})
if not is_future_step:
# Check manual edits buffer, clear out if needed by creating a ManualEditStep
@@ -284,12 +302,13 @@ class Autopilot(ContinueBaseModel):
e.__class__, ContinueCustomException)
error_string = e.message if is_continue_custom_exception else '\n'.join(
- traceback.format_tb(e.__traceback__)) + f"\n\n{e.__repr__()}"
+ traceback.format_exception(e))
error_title = e.title if is_continue_custom_exception else get_error_title(
e)
# Attach an InternalErrorObservation to the step and unhide it.
- print(f"Error while running step: \n{error_string}\n{error_title}")
+ print(
+ f"Error while running step: \n{error_string}\n{error_title}")
capture_event(self.continue_sdk.ide.unique_id, 'step error', {
'error_message': error_string, 'error_title': error_title, 'step_name': step.name, 'params': step.dict()})
@@ -341,7 +360,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/core/config.py b/continuedev/src/continuedev/core/config.py
index 55f5bc60..6e430c04 100644
--- a/continuedev/src/continuedev/core/config.py
+++ b/continuedev/src/continuedev/core/config.py
@@ -45,6 +45,11 @@ DEFAULT_SLASH_COMMANDS = [
step_name="OpenConfigStep",
),
SlashCommand(
+ name="help",
+ description="Ask a question like '/help what is given to the llm as context?'",
+ step_name="HelpStep",
+ ),
+ SlashCommand(
name="comment",
description="Write comments for the current file or highlighted code",
step_name="CommentCodeStep",
@@ -131,7 +136,7 @@ def load_global_config() -> ContinueConfig:
config_path = os.path.join(global_dir, 'config.json')
if not os.path.exists(config_path):
with open(config_path, 'w') as f:
- json.dump(ContinueConfig().dict(), f)
+ json.dump(ContinueConfig().dict(), f, indent=4)
with open(config_path, 'r') as f:
try:
config_dict = json.load(f)
@@ -151,7 +156,7 @@ def update_global_config(config: ContinueConfig):
yaml_path = os.path.join(global_dir, 'config.yaml')
if os.path.exists(yaml_path):
with open(config_path, 'w') as f:
- yaml.dump(config.dict(), f)
+ yaml.dump(config.dict(), f, indent=4)
else:
config_path = os.path.join(global_dir, 'config.json')
with open(config_path, 'w') as f:
diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py
index b8363df2..bc897357 100644
--- a/continuedev/src/continuedev/core/policy.py
+++ b/continuedev/src/continuedev/core/policy.py
@@ -59,11 +59,8 @@ class DemoPolicy(Policy):
return (
MessageStep(name="Welcome to Continue", message=dedent("""\
- Highlight code and ask a question or give instructions
- - Use `cmd+k` (Mac) / `ctrl+k` (Windows) to open Continue
- - Use `cmd+shift+e` / `ctrl+shift+e` to open file Explorer
- - Add your own OpenAI API key to VS Code Settings with `cmd+,`
- - Use slash commands when you want fine-grained control
- - Past steps are included as part of the context by default""")) >>
+ - Use `cmd+m` (Mac) / `ctrl+m` (Windows) to open Continue
+ - Use `/help` to ask questions about how to use Continue""")) >>
WelcomeStep() >>
# SetupContinueWorkspaceStep() >>
# CreateCodebaseIndexChroma() >>
diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py
index 22393746..9389e1e9 100644
--- a/continuedev/src/continuedev/core/sdk.py
+++ b/continuedev/src/continuedev/core/sdk.py
@@ -1,6 +1,6 @@
import asyncio
from functools import cached_property
-from typing import Coroutine, Union
+from typing import Coroutine, Dict, Union
import os
from ..steps.core.core import DefaultModelEditCodeStep
@@ -14,7 +14,7 @@ from ..libs.llm.openai import OpenAI
from ..libs.llm.ggml import GGML
from .observation import Observation
from ..server.ide_protocol import AbstractIdeProtocolServer
-from .main import Context, ContinueCustomException, HighlightedRangeContext, History, Step, ChatMessage, ChatMessageRole
+from .main import Context, ContinueCustomException, History, Step, ChatMessage
from ..steps.core.core import *
from ..libs.llm.proxy_server import ProxyServer
@@ -23,26 +23,46 @@ class Autopilot:
pass
+ModelProvider = Literal["openai", "hf_inference_api", "ggml", "anthropic"]
+MODEL_PROVIDER_TO_ENV_VAR = {
+ "openai": "OPENAI_API_KEY",
+ "hf_inference_api": "HUGGING_FACE_TOKEN",
+ "anthropic": "ANTHROPIC_API_KEY"
+}
+
+
class Models:
- def __init__(self, sdk: "ContinueSDK"):
+ provider_keys: Dict[ModelProvider, str] = {}
+ model_providers: List[ModelProvider]
+
+ def __init__(self, sdk: "ContinueSDK", model_providers: List[ModelProvider]):
self.sdk = sdk
+ self.model_providers = model_providers
+
+ @classmethod
+ async def create(cls, sdk: "ContinueSDK", with_providers: List[ModelProvider] = ["openai"]) -> "Models":
+ models = Models(sdk, with_providers)
+ for provider in with_providers:
+ if provider in MODEL_PROVIDER_TO_ENV_VAR:
+ env_var = MODEL_PROVIDER_TO_ENV_VAR[provider]
+ models.provider_keys[provider] = await sdk.get_user_secret(
+ env_var, f'Please add your {env_var} to the .env file')
+
+ return models
def __load_openai_model(self, model: str) -> OpenAI:
- async def load_openai_model():
- api_key = await self.sdk.get_user_secret(
- 'OPENAI_API_KEY', 'Enter your OpenAI API key or press enter to try for free')
- if api_key == "":
- return ProxyServer(self.sdk.ide.unique_id, model)
- return OpenAI(api_key=api_key, default_model=model)
- return asyncio.get_event_loop().run_until_complete(load_openai_model())
+ api_key = self.provider_keys["openai"]
+ if api_key == "":
+ return ProxyServer(self.sdk.ide.unique_id, model)
+ return OpenAI(api_key=api_key, default_model=model)
+
+ def __load_hf_inference_api_model(self, model: str) -> HuggingFaceInferenceAPI:
+ api_key = self.provider_keys["hf_inference_api"]
+ return HuggingFaceInferenceAPI(api_key=api_key, model=model)
@cached_property
def starcoder(self):
- async def load_starcoder():
- api_key = await self.sdk.get_user_secret(
- 'HUGGING_FACE_TOKEN', 'Please add your Hugging Face token to the .env file')
- return HuggingFaceInferenceAPI(api_key=api_key)
- return asyncio.get_event_loop().run_until_complete(load_starcoder())
+ return self.__load_hf_inference_api_model("bigcode/starcoder")
@cached_property
def gpt35(self):
@@ -80,7 +100,7 @@ class Models:
def default(self):
return self.ggml
default_model = self.sdk.config.default_model
- return self.__model_from_name(default_model) if default_model is not None else self.gpt35
+ return self.__model_from_name(default_model) if default_model is not None else self.gpt4
class ContinueSDK(AbstractContinueSDK):
@@ -93,8 +113,27 @@ class ContinueSDK(AbstractContinueSDK):
def __init__(self, autopilot: Autopilot):
self.ide = autopilot.ide
self.__autopilot = autopilot
- self.models = Models(self)
self.context = autopilot.context
+ self.config = self._load_config()
+
+ @classmethod
+ async def create(cls, autopilot: Autopilot) -> "ContinueSDK":
+ sdk = ContinueSDK(autopilot)
+ sdk.models = await Models.create(sdk)
+ return sdk
+
+ config: ContinueConfig
+
+ def _load_config(self) -> ContinueConfig:
+ dir = self.ide.workspace_directory
+ yaml_path = os.path.join(dir, '.continue', 'config.yaml')
+ json_path = os.path.join(dir, '.continue', 'config.json')
+ if os.path.exists(yaml_path):
+ return load_config(yaml_path)
+ elif os.path.exists(json_path):
+ return load_config(json_path)
+ else:
+ return load_global_config()
@property
def history(self) -> History:
@@ -172,18 +211,6 @@ class ContinueSDK(AbstractContinueSDK):
async def get_user_secret(self, env_var: str, prompt: str) -> str:
return await self.ide.getUserSecret(env_var)
- @property
- def config(self) -> ContinueConfig:
- dir = self.ide.workspace_directory
- yaml_path = os.path.join(dir, '.continue', 'config.yaml')
- json_path = os.path.join(dir, '.continue', 'config.json')
- if os.path.exists(yaml_path):
- return load_config(yaml_path)
- elif os.path.exists(json_path):
- return load_config(json_path)
- else:
- return load_global_config()
-
def get_code_context(self, only_editing: bool = False) -> List[RangeInFileWithContents]:
context = list(filter(lambda x: x.editing, self.__autopilot._highlighted_ranges)
) if only_editing else self.__autopilot._highlighted_ranges
@@ -208,14 +235,14 @@ class ContinueSDK(AbstractContinueSDK):
preface = "The following code is highlighted"
+ # If no higlighted ranges, use first file as context
if len(highlighted_code) == 0:
preface = "The following file is open"
- # Get the full contents of all open files
- files = await self.ide.getOpenFiles()
- if len(files) > 0:
- content = await self.ide.readFile(files[0])
+ visible_files = await self.ide.getVisibleFiles()
+ if len(visible_files) > 0:
+ content = await self.ide.readFile(visible_files[0])
highlighted_code = [
- RangeInFileWithContents.from_entire_file(files[0], content)]
+ RangeInFileWithContents.from_entire_file(visible_files[0], content)]
for rif in highlighted_code:
msg = ChatMessage(content=f"{preface} ({rif.filepath}):\n```\n{rif.contents}\n```",
diff --git a/continuedev/src/continuedev/libs/constants/main.py b/continuedev/src/continuedev/libs/constants/main.py
new file mode 100644
index 00000000..96eb6e69
--- /dev/null
+++ b/continuedev/src/continuedev/libs/constants/main.py
@@ -0,0 +1,6 @@
+## PATHS ##
+
+CONTINUE_GLOBAL_FOLDER = ".continue"
+CONTINUE_SESSIONS_FOLDER = "sessions"
+CONTINUE_SERVER_FOLDER = "server"
+
diff --git a/continuedev/src/continuedev/libs/llm/hf_inference_api.py b/continuedev/src/continuedev/libs/llm/hf_inference_api.py
index 1586c620..803ba122 100644
--- a/continuedev/src/continuedev/libs/llm/hf_inference_api.py
+++ b/continuedev/src/continuedev/libs/llm/hf_inference_api.py
@@ -9,7 +9,11 @@ DEFAULT_MAX_TIME = 120.
class HuggingFaceInferenceAPI(LLM):
api_key: str
- model: str = "bigcode/starcoder"
+ model: str
+
+ def __init__(self, api_key: str, model: str):
+ self.api_key = api_key
+ self.model = model
def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs):
"""Return the completion of the text with the given temperature."""
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..354cea82
--- /dev/null
+++ b/continuedev/src/continuedev/libs/util/create_async_task.py
@@ -0,0 +1,24 @@
+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: ",
+ '\n'.join(traceback.format_exception(e)))
+ capture_event(unique_id or "None", "async_task_error", {
+ "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e))
+ })
+
+ task.add_done_callback(callback)
+ return task
diff --git a/continuedev/src/continuedev/libs/util/errors.py b/continuedev/src/continuedev/libs/util/errors.py
new file mode 100644
index 00000000..46074cfc
--- /dev/null
+++ b/continuedev/src/continuedev/libs/util/errors.py
@@ -0,0 +1,2 @@
+class SessionNotFound(Exception):
+ pass
diff --git a/continuedev/src/continuedev/libs/util/paths.py b/continuedev/src/continuedev/libs/util/paths.py
new file mode 100644
index 00000000..fddef887
--- /dev/null
+++ b/continuedev/src/continuedev/libs/util/paths.py
@@ -0,0 +1,17 @@
+import os
+
+from ..constants.main import CONTINUE_SESSIONS_FOLDER, CONTINUE_GLOBAL_FOLDER, CONTINUE_SERVER_FOLDER
+
+def getGlobalFolderPath():
+ return os.path.join(os.path.expanduser("~"), CONTINUE_GLOBAL_FOLDER)
+
+
+
+def getSessionsFolderPath():
+ return os.path.join(getGlobalFolderPath(), CONTINUE_SESSIONS_FOLDER)
+
+def getServerFolderPath():
+ return os.path.join(getGlobalFolderPath(), CONTINUE_SERVER_FOLDER)
+
+def getSessionFilePath(session_id: str):
+ return os.path.join(getSessionsFolderPath(), f"{session_id}.json") \ No newline at end of file
diff --git a/continuedev/src/continuedev/libs/util/step_name_to_steps.py b/continuedev/src/continuedev/libs/util/step_name_to_steps.py
index d329e110..49056c81 100644
--- a/continuedev/src/continuedev/libs/util/step_name_to_steps.py
+++ b/continuedev/src/continuedev/libs/util/step_name_to_steps.py
@@ -13,6 +13,7 @@ from ...recipes.DeployPipelineAirflowRecipe.main import DeployPipelineAirflowRec
from ...steps.on_traceback import DefaultOnTracebackStep
from ...steps.clear_history import ClearHistoryStep
from ...steps.open_config import OpenConfigStep
+from ...steps.help import HelpStep
# This mapping is used to convert from string in ContinueConfig json to corresponding Step class.
# Used for example in slash_commands and steps_on_startup
@@ -28,7 +29,8 @@ step_name_to_step_class = {
"DeployPipelineAirflowRecipe": DeployPipelineAirflowRecipe,
"DefaultOnTracebackStep": DefaultOnTracebackStep,
"ClearHistoryStep": ClearHistoryStep,
- "OpenConfigStep": OpenConfigStep
+ "OpenConfigStep": OpenConfigStep,
+ "HelpStep": HelpStep,
}
diff --git a/continuedev/src/continuedev/recipes/TemplateRecipe/main.py b/continuedev/src/continuedev/recipes/TemplateRecipe/main.py
index 94675725..16132cfd 100644
--- a/continuedev/src/continuedev/recipes/TemplateRecipe/main.py
+++ b/continuedev/src/continuedev/recipes/TemplateRecipe/main.py
@@ -20,8 +20,8 @@ class TemplateRecipe(Step):
# The code executed when the recipe is run
async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
- open_files = await sdk.ide.getOpenFiles()
+ visible_files = await sdk.ide.getVisibleFiles()
await sdk.edit_file(
- filename=open_files[0],
+ filename=visible_files[0],
prompt=f"Append a statement to print `Hello, {self.name}!` at the end of the file."
)
diff --git a/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py b/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py
index 6e1244b3..c7a65fa6 100644
--- a/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py
+++ b/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py
@@ -14,7 +14,7 @@ class WritePytestsRecipe(Step):
async def run(self, sdk: ContinueSDK):
if self.for_filepath is None:
- self.for_filepath = (await sdk.ide.getOpenFiles())[0]
+ self.for_filepath = (await sdk.ide.getVisibleFiles())[0]
filename = os.path.basename(self.for_filepath)
dirname = os.path.dirname(self.for_filepath)
diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py
index 8e9b1fb9..4201353e 100644
--- a/continuedev/src/continuedev/server/gui.py
+++ b/continuedev/src/continuedev/server/gui.py
@@ -1,15 +1,17 @@
+import asyncio
import json
from fastapi import Depends, Header, WebSocket, APIRouter
+from starlette.websockets import WebSocketState, WebSocketDisconnect
from typing import Any, List, Type, TypeVar, Union
from pydantic import BaseModel
+import traceback
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"])
@@ -30,12 +32,12 @@ class AppStatus:
Server.handle_exit = AppStatus.handle_exit
-def session(x_continue_session_id: str = Header("anonymous")) -> Session:
- return session_manager.get_session(x_continue_session_id)
+async def session(x_continue_session_id: str = Header("anonymous")) -> Session:
+ return await session_manager.get_session(x_continue_session_id)
-def websocket_session(session_id: str) -> Session:
- return session_manager.get_session(session_id)
+async def websocket_session(session_id: str) -> Session:
+ return await session_manager.get_session(session_id)
T = TypeVar("T", bound=BaseModel)
@@ -52,13 +54,19 @@ class GUIProtocolServer(AbstractGUIProtocolServer):
self.session = session
async def _send_json(self, message_type: str, data: Any):
+ if self.websocket.application_state == WebSocketState.DISCONNECTED:
+ return
await self.websocket.send_json({
"messageType": message_type,
"data": data
})
- async def _receive_json(self, message_type: str) -> Any:
- return await self.sub_queue.get(message_type)
+ async def _receive_json(self, message_type: str, timeout: int = 5) -> Any:
+ try:
+ return await asyncio.wait_for(self.sub_queue.get(message_type), timeout=timeout)
+ except asyncio.TimeoutError:
+ raise Exception(
+ "GUI Protocol _receive_json timed out after 5 seconds")
async def _send_and_receive_json(self, data: Any, resp_model: Type[T], message_type: str) -> T:
await self._send_json(message_type, data)
@@ -102,51 +110,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
)
@@ -176,11 +193,17 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we
data = message["data"]
protocol.handle_json(message_type, data)
-
+ except WebSocketDisconnect as e:
+ print("GUI websocket disconnected")
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": '\n'.join(traceback.format_exception(e))})
raise e
finally:
print("Closing gui websocket")
- await websocket.close()
+ if websocket.client_state != WebSocketState.DISCONNECTED:
+ await websocket.close()
+
+ session_manager.persist_session(session.session_id)
session_manager.remove_session(session.session_id)
diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py
index e4a6266a..a91708ec 100644
--- a/continuedev/src/continuedev/server/ide.py
+++ b/continuedev/src/continuedev/server/ide.py
@@ -5,7 +5,9 @@ import os
from typing import Any, Dict, List, Type, TypeVar, Union
import uuid
from fastapi import WebSocket, Body, APIRouter
+from starlette.websockets import WebSocketState, WebSocketDisconnect
from uvicorn.main import Server
+import traceback
from ..libs.util.telemetry import capture_event
from ..libs.util.queue import AsyncSubscriptionQueue
@@ -15,6 +17,7 @@ from pydantic import BaseModel
from .gui import SessionManager, session_manager
from .ide_protocol import AbstractIdeProtocolServer
import asyncio
+from ..libs.util.create_async_task import create_async_task
import nest_asyncio
nest_asyncio.apply()
@@ -50,6 +53,10 @@ class OpenFilesResponse(BaseModel):
openFiles: List[str]
+class VisibleFilesResponse(BaseModel):
+ visibleFiles: List[str]
+
+
class HighlightedCodeResponse(BaseModel):
highlightedCode: List[RangeInFile]
@@ -110,19 +117,52 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
websocket: WebSocket
session_manager: SessionManager
sub_queue: AsyncSubscriptionQueue = AsyncSubscriptionQueue()
+ session_id: Union[str, None] = None
def __init__(self, session_manager: SessionManager, websocket: WebSocket):
self.websocket = websocket
self.session_manager = session_manager
+ workspace_directory: str = None
+ unique_id: str = None
+
+ async def initialize(self, session_id: str) -> List[str]:
+ self.session_id = session_id
+ await self._send_json("workspaceDirectory", {})
+ await self._send_json("uniqueId", {})
+ other_msgs = []
+ while True:
+ msg_string = await self.websocket.receive_text()
+ message = json.loads(msg_string)
+ if "messageType" not in message or "data" not in message:
+ continue
+ message_type = message["messageType"]
+ data = message["data"]
+ if message_type == "workspaceDirectory":
+ self.workspace_directory = data["workspaceDirectory"]
+ elif message_type == "uniqueId":
+ self.unique_id = data["uniqueId"]
+ else:
+ other_msgs.append(msg_string)
+
+ if self.workspace_directory is not None and self.unique_id is not None:
+ break
+ return other_msgs
+
async def _send_json(self, message_type: str, data: Any):
+ if self.websocket.application_state == WebSocketState.DISCONNECTED:
+ return
await self.websocket.send_json({
"messageType": message_type,
"data": data
})
- async def _receive_json(self, message_type: str) -> Any:
- return await self.sub_queue.get(message_type)
+ async def _receive_json(self, message_type: str, timeout: int = 5) -> Any:
+ try:
+ return await asyncio.wait_for(self.sub_queue.get(message_type), timeout=timeout)
+ except asyncio.TimeoutError:
+ raise Exception(
+ "IDE Protocol _receive_json timed out after 5 seconds")
async def _send_and_receive_json(self, data: Any, resp_model: Type[T], message_type: str) -> T:
await self._send_json(message_type, data)
@@ -130,8 +170,8 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
return resp_model.parse_obj(resp)
async def handle_json(self, message_type: str, data: Any):
- if message_type == "openGUI":
- await self.openGUI()
+ if message_type == "getSessionId":
+ await self.getSessionId()
elif message_type == "setFileOpen":
await self.setFileOpen(data["filepath"], data["open"])
elif message_type == "setSuggestionsLocked":
@@ -154,8 +194,12 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
self.onMainUserInput(data["input"])
elif message_type == "deleteAtIndex":
self.onDeleteAtIndex(data["index"])
- elif message_type in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory", "getUserSecret", "runCommand", "uniqueId"]:
+ elif message_type in ["highlightedCode", "openFiles", "visibleFiles", "readFile", "editFile", "getUserSecret", "runCommand"]:
self.sub_queue.post(message_type, data)
+ elif message_type == "workspaceDirectory":
+ self.workspace_directory = data["workspaceDirectory"]
+ elif message_type == "uniqueId":
+ self.unique_id = data["uniqueId"]
else:
raise ValueError("Unknown message type", message_type)
@@ -187,9 +231,10 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
"locked": locked
})
- async def openGUI(self):
- session_id = self.session_manager.new_session(self)
- await self._send_json("openGUI", {
+ async def getSessionId(self):
+ session_id = (await self.session_manager.new_session(
+ self, self.session_id)).session_id
+ await self._send_json("getSessionId", {
"sessionId": session_id
})
@@ -242,53 +287,40 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
def onOpenGUIRequest(self):
pass
+ def __get_autopilot(self):
+ return self.session_manager.sessions[self.session_id].autopilot
+
def onFileEdits(self, edits: List[FileEditWithFullContents]):
- # Send the file edits to ALL autopilots.
- # Maybe not ideal behavior
- for _, session in self.session_manager.sessions.items():
- session.autopilot.handle_manual_edits(edits)
+ if autopilot := self.__get_autopilot():
+ autopilot.handle_manual_edits(edits)
def onDeleteAtIndex(self, index: int):
- for _, session in self.session_manager.sessions.items():
- asyncio.create_task(session.autopilot.delete_at_index(index))
+ if autopilot := self.__get_autopilot():
+ create_async_task(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))
+ if autopilot := self.__get_autopilot():
+ create_async_task(
+ 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))
+ if autopilot := self.__get_autopilot():
+ create_async_task(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))
+ if autopilot := self.__get_autopilot():
+ create_async_task(
+ autopilot.accept_user_input(input), self.unique_id)
# Request information. Session doesn't matter.
async def getOpenFiles(self) -> List[str]:
resp = await self._send_and_receive_json({}, OpenFilesResponse, "openFiles")
return resp.openFiles
- async def getWorkspaceDirectory(self) -> str:
- resp = await self._send_and_receive_json({}, WorkspaceDirectoryResponse, "workspaceDirectory")
- return resp.workspaceDirectory
-
- async def get_unique_id(self) -> str:
- resp = await self._send_and_receive_json({}, UniqueIdResponse, "uniqueId")
- return resp.uniqueId
-
- @property
- def workspace_directory(self) -> str:
- return asyncio.run(self.getWorkspaceDirectory())
-
- @cached_property_no_none
- def unique_id(self) -> str:
- return asyncio.run(self.get_unique_id())
+ async def getVisibleFiles(self) -> List[str]:
+ resp = await self._send_and_receive_json({}, VisibleFilesResponse, "visibleFiles")
+ return resp.visibleFiles
async def getHighlightedCode(self) -> List[RangeInFile]:
resp = await self._send_and_receive_json({}, HighlightedCodeResponse, "highlightedCode")
@@ -389,28 +421,45 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
@router.websocket("/ws")
-async def websocket_endpoint(websocket: WebSocket):
+async def websocket_endpoint(websocket: WebSocket, session_id: str = None):
try:
await websocket.accept()
print("Accepted websocket connection from, ", websocket.client)
await websocket.send_json({"messageType": "connected", "data": {}})
- ideProtocolServer = IdeProtocolServer(session_manager, websocket)
-
- while AppStatus.should_exit is False:
- message = await websocket.receive_text()
- message = json.loads(message)
+ def handle_msg(msg):
+ message = json.loads(msg)
if "messageType" not in message or "data" not in message:
- continue
+ return
message_type = message["messageType"]
data = message["data"]
- await ideProtocolServer.handle_json(message_type, data)
+ create_async_task(
+ ideProtocolServer.handle_json(message_type, data))
+
+ ideProtocolServer = IdeProtocolServer(session_manager, websocket)
+ if session_id is not None:
+ session_manager.registered_ides[session_id] = ideProtocolServer
+ other_msgs = await ideProtocolServer.initialize(session_id)
+
+ for other_msg in other_msgs:
+ handle_msg(other_msg)
+
+ while AppStatus.should_exit is False:
+ message = await websocket.receive_text()
+ handle_msg(message)
print("Closing ide websocket")
- await websocket.close()
+ except WebSocketDisconnect as e:
+ print("IDE wbsocket disconnected")
except Exception as e:
print("Error in ide websocket: ", e)
- await websocket.close()
+ capture_event(ideProtocolServer.unique_id, "gui_error", {
+ "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e))})
raise e
+ finally:
+ if websocket.client_state != WebSocketState.DISCONNECTED:
+ await websocket.close()
+
+ session_manager.registered_ides.pop(ideProtocolServer.session_id)
diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py
index dfdca504..d0fb0bf8 100644
--- a/continuedev/src/continuedev/server/ide_protocol.py
+++ b/continuedev/src/continuedev/server/ide_protocol.py
@@ -1,5 +1,6 @@
-from typing import Any, List
+from typing import Any, List, Union
from abc import ABC, abstractmethod, abstractproperty
+from fastapi import WebSocket
from ..models.main import Traceback
from ..models.filesystem_edit import FileEdit, FileSystemEdit, EditDiff
@@ -7,6 +8,9 @@ from ..models.filesystem import RangeInFile, RangeInFileWithContents
class AbstractIdeProtocolServer(ABC):
+ websocket: WebSocket
+ session_id: Union[str, None]
+
@abstractmethod
async def handle_json(self, data: Any):
"""Handle a json message"""
@@ -16,10 +20,6 @@ class AbstractIdeProtocolServer(ABC):
"""Show a suggestion to the user"""
@abstractmethod
- async def getWorkspaceDirectory(self):
- """Get the workspace directory"""
-
- @abstractmethod
async def setFileOpen(self, filepath: str, open: bool = True):
"""Set whether a file is open"""
@@ -28,8 +28,8 @@ class AbstractIdeProtocolServer(ABC):
"""Set whether suggestions are locked"""
@abstractmethod
- async def openGUI(self):
- """Open a GUI"""
+ async def getSessionId(self):
+ """Get a new session ID"""
@abstractmethod
async def showSuggestionsAndWait(self, suggestions: List[FileEdit]) -> bool:
@@ -56,6 +56,10 @@ class AbstractIdeProtocolServer(ABC):
"""Get a list of open files"""
@abstractmethod
+ async def getVisibleFiles(self) -> List[str]:
+ """Get a list of visible files"""
+
+ @abstractmethod
async def getHighlightedCode(self) -> List[RangeInFile]:
"""Get a list of highlighted code"""
@@ -103,10 +107,5 @@ class AbstractIdeProtocolServer(ABC):
async def showDiff(self, filepath: str, replacement: str, step_index: int):
"""Show a diff"""
- @abstractproperty
- def workspace_directory(self) -> str:
- """Get the workspace directory"""
-
- @abstractproperty
- def unique_id(self) -> str:
- """Get a unique ID for this IDE"""
+ workspace_directory: str
+ unique_id: str
diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py
index f4d82903..aa093853 100644
--- a/continuedev/src/continuedev/server/main.py
+++ b/continuedev/src/continuedev/server/main.py
@@ -4,7 +4,8 @@ from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from .ide import router as ide_router
from .gui import router as gui_router
-import logging
+from .session_manager import session_manager
+import atexit
import uvicorn
import argparse
@@ -44,5 +45,16 @@ def run_server():
uvicorn.run(app, host="0.0.0.0", port=args.port)
+def cleanup():
+ print("Cleaning up sessions")
+ for session_id in session_manager.sessions:
+ session_manager.persist_session(session_id)
+
+
+atexit.register(cleanup)
if __name__ == "__main__":
- run_server()
+ try:
+ run_server()
+ except Exception as e:
+ cleanup()
+ raise e
diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py
index 99a38146..6d109ca6 100644
--- a/continuedev/src/continuedev/server/session_manager.py
+++ b/continuedev/src/continuedev/server/session_manager.py
@@ -1,20 +1,24 @@
+import os
from fastapi import WebSocket
from typing import Any, Dict, List, Union
from uuid import uuid4
+import json
+from ..libs.util.paths import getSessionFilePath, getSessionsFolderPath
from ..models.filesystem_edit import FileEditWithFullContents
+from ..libs.constants.main import CONTINUE_SESSIONS_FOLDER
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
+from ..libs.util.errors import SessionNotFound
class Session:
session_id: str
autopilot: Autopilot
+ # The GUI websocket for the session
ws: Union[WebSocket, None]
def __init__(self, session_id: str, autopilot: Autopilot):
@@ -38,18 +42,35 @@ class DemoAutopilot(Autopilot):
class SessionManager:
sessions: Dict[str, Session] = {}
- _event_loop: Union[asyncio.BaseEventLoop, None] = None
+ # Mapping of session_id to IDE, where the IDE is still alive
+ registered_ides: Dict[str, AbstractIdeProtocolServer] = {}
- def get_session(self, session_id: str) -> Session:
+ async def get_session(self, session_id: str) -> Session:
if session_id not in self.sessions:
+ # Check then whether it is persisted by listing all files in the sessions folder
+ # And only if the IDE is still alive
+ sessions_folder = getSessionsFolderPath()
+ session_files = os.listdir(sessions_folder)
+ if f"{session_id}.json" in session_files and session_id in self.registered_ides:
+ if self.registered_ides[session_id].session_id is not None:
+ return await self.new_session(self.registered_ides[session_id], session_id=session_id)
+
raise KeyError("Session ID not recognized", session_id)
return self.sessions[session_id]
- def new_session(self, ide: AbstractIdeProtocolServer) -> str:
- autopilot = DemoAutopilot(policy=DemoPolicy(), ide=ide)
- session_id = str(uuid4())
+ async def new_session(self, ide: AbstractIdeProtocolServer, session_id: Union[str, None] = None) -> Session:
+ full_state = None
+ if session_id is not None and os.path.exists(getSessionFilePath(session_id)):
+ with open(getSessionFilePath(session_id), "r") as f:
+ full_state = FullState(**json.load(f))
+
+ autopilot = await DemoAutopilot.create(
+ policy=DemoPolicy(), ide=ide, full_state=full_state)
+ session_id = session_id or str(uuid4())
+ ide.session_id = session_id
session = Session(session_id=session_id, autopilot=autopilot)
self.sessions[session_id] = session
+ self.registered_ides[session_id] = ide
async def on_update(state: FullState):
await session_manager.send_ws_data(session_id, "state_update", {
@@ -57,17 +78,27 @@ class SessionManager:
})
autopilot.on_update(on_update)
- asyncio.create_task(autopilot.run_policy())
- return session_id
+ create_async_task(autopilot.run_policy())
+ return session
def remove_session(self, session_id: str):
del self.sessions[session_id]
+ def persist_session(self, session_id: str):
+ """Save the session's FullState as a json file"""
+ full_state = self.sessions[session_id].autopilot.get_full_state()
+ if not os.path.exists(getSessionsFolderPath()):
+ os.mkdir(getSessionsFolderPath())
+ with open(getSessionFilePath(session_id), "w") as f:
+ json.dump(full_state.dict(), f)
+
def register_websocket(self, session_id: str, ws: WebSocket):
self.sessions[session_id].ws = ws
print("Registered websocket for session", session_id)
async def send_ws_data(self, session_id: str, message_type: str, data: Any):
+ if session_id not in self.sessions:
+ raise SessionNotFound(f"Session {session_id} not found")
if self.sessions[session_id].ws is None:
print(f"Session {session_id} has no websocket")
return
diff --git a/continuedev/src/continuedev/server/state_manager.py b/continuedev/src/continuedev/server/state_manager.py
deleted file mode 100644
index c9bd760b..00000000
--- a/continuedev/src/continuedev/server/state_manager.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from typing import Any, List, Tuple, Union
-from fastapi import WebSocket
-from pydantic import BaseModel
-from ..core.main import FullState
-
-# State updates represented as (path, replacement) pairs
-StateUpdate = Tuple[List[Union[str, int]], Any]
-
-
-class StateManager:
- """
- A class that acts as the source of truth for state, ingesting changes to the entire object and streaming only the updated portions to client.
- """
-
- def __init__(self, ws: WebSocket):
- self.ws = ws
-
- def _send_update(self, updates: List[StateUpdate]):
- self.ws.send_json(
- [update.dict() for update in updates]
- )
diff --git a/continuedev/src/continuedev/steps/chat.py b/continuedev/src/continuedev/steps/chat.py
index 1df1e0bf..3751dec2 100644
--- a/continuedev/src/continuedev/steps/chat.py
+++ b/continuedev/src/continuedev/steps/chat.py
@@ -27,14 +27,21 @@ class SimpleChatStep(Step):
async def run(self, sdk: ContinueSDK):
completion = ""
messages = self.messages or await sdk.get_chat_context()
- async for chunk in sdk.models.default.stream_chat(messages, temperature=0.5):
- if sdk.current_step_was_deleted():
- return
- if "content" in chunk:
- self.description += chunk["content"]
- completion += chunk["content"]
- await sdk.update_ui()
+ generator = sdk.models.default.stream_chat(messages, temperature=0.5)
+ try:
+ async for chunk in generator:
+ if sdk.current_step_was_deleted():
+ # So that the message doesn't disappear
+ self.hide = False
+ return
+
+ if "content" in chunk:
+ self.description += chunk["content"]
+ completion += chunk["content"]
+ await sdk.update_ui()
+ finally:
+ await generator.aclose()
self.name = (await sdk.models.gpt35.complete(
f"Write a short title for the following chat message: {self.description}")).strip()
diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py
index 0b067d7d..2c9d8c01 100644
--- a/continuedev/src/continuedev/steps/core/core.py
+++ b/continuedev/src/continuedev/steps/core/core.py
@@ -10,7 +10,7 @@ from ...libs.llm.prompt_utils import MarkdownStyleEncoderDecoder
from ...models.filesystem_edit import EditDiff, FileEdit, FileEditWithFullContents, FileSystemEdit
from ...models.filesystem import FileSystem, RangeInFile, RangeInFileWithContents
from ...core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation
-from ...core.main import ChatMessage, Step, SequentialStep
+from ...core.main import ChatMessage, ContinueCustomException, Step, SequentialStep
from ...libs.util.count_tokens import MAX_TOKENS_FOR_MODEL, DEFAULT_MAX_TOKENS
from ...libs.util.dedent import dedent_and_get_common_whitespace
import difflib
@@ -153,7 +153,8 @@ class DefaultModelEditCodeStep(Step):
Main task:
""")
-
+ _previous_contents: str = ""
+ _new_contents: str = ""
_prompt_and_completion: str = ""
def _cleanup_output(self, output: str) -> str:
@@ -168,10 +169,19 @@ class DefaultModelEditCodeStep(Step):
return output
async def describe(self, models: Models) -> Coroutine[str, None, None]:
- 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:"""))
+ if self._previous_contents.strip() == self._new_contents.strip():
+ description = "No edits were made"
+ else:
+ description = await models.gpt3516k.complete(dedent(f"""\
+ ```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)
@@ -263,6 +273,23 @@ class DefaultModelEditCodeStep(Step):
return file_prefix, rif.contents, file_suffix, model_to_use, max_tokens
def compile_prompt(self, file_prefix: str, contents: str, file_suffix: str, sdk: ContinueSDK) -> str:
+ if contents.strip() == "":
+ # Seperate prompt for insertion at the cursor, the other tends to cause it to repeat whole file
+ prompt = dedent(f"""\
+<file_prefix>
+{file_prefix}
+</file_prefix>
+<insertion_code_here>
+<file_suffix>
+{file_suffix}
+</file_suffix>
+<user_request>
+{self.user_input}
+</user_request>
+
+Please output the code to be inserted at the cursor in order to fulfill the user_request. Do NOT preface your answer or write anything other than code. You should not write any tags, just the code. Make sure to correctly indent the code:""")
+ return prompt
+
prompt = self._prompt
if file_prefix.strip() != "":
prompt += dedent(f"""
@@ -304,15 +331,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" if len(lines_to_display) > 0 else "") + "\n".join(full_suffix_lines)
step_index = sdk.history.current_index
@@ -431,6 +475,14 @@ class DefaultModelEditCodeStep(Step):
current_block_lines.append(line)
messages = await sdk.get_chat_context()
+ # Delete the last user and assistant messages
+ i = len(messages) - 1
+ deleted = 0
+ while i >= 0 and deleted < 2:
+ if messages[i].role == "user" or messages[i].role == "assistant":
+ messages.pop(i)
+ deleted += 1
+ i -= 1
messages.append(ChatMessage(
role="user",
content=prompt,
@@ -448,58 +500,63 @@ class DefaultModelEditCodeStep(Step):
messages = [ChatMessage(
role="user", content=f"```\n{rif.contents}\n```\n{self.user_input}\n```\n", summary=self.user_input)]
- async for chunk in model_to_use.stream_chat(messages, temperature=0, max_tokens=max_tokens):
- # Stop early if it is repeating the file_suffix or the step was deleted
- if repeating_file_suffix:
- break
- if sdk.current_step_was_deleted():
- return
+ generator = model_to_use.stream_chat(
+ messages, temperature=0, max_tokens=max_tokens)
- # Accumulate lines
- if "content" not in chunk:
- continue
- chunk = chunk["content"]
- chunk_lines = chunk.split("\n")
- chunk_lines[0] = unfinished_line + chunk_lines[0]
- if chunk.endswith("\n"):
- unfinished_line = ""
- chunk_lines.pop() # because this will be an empty string
- else:
- unfinished_line = chunk_lines.pop()
-
- # Deal with newly accumulated lines
- for i in range(len(chunk_lines)):
- # Trailing whitespace doesn't matter
- chunk_lines[i] = chunk_lines[i].rstrip()
- chunk_lines[i] = common_whitespace + chunk_lines[i]
-
- # Lines that should signify the end of generation
- if self.is_end_line(chunk_lines[i]):
- break
- # Lines that should be ignored, like the <> tags
- elif self.line_to_be_ignored(chunk_lines[i], completion_lines_covered == 0):
- continue
- # Check if we are currently just copying the prefix
- elif (lines_of_prefix_copied > 0 or completion_lines_covered == 0) and lines_of_prefix_copied < len(file_prefix.splitlines()) and chunk_lines[i] == full_file_contents_lines[lines_of_prefix_copied]:
- # This is a sketchy way of stopping it from repeating the file_prefix. Is a bug if output happens to have a matching line
- lines_of_prefix_copied += 1
- continue
- # Because really short lines might be expected to be repeated, this is only a !heuristic!
- # Stop when it starts copying the file_suffix
- elif chunk_lines[i].strip() == line_below_highlighted_range.strip() and len(chunk_lines[i].strip()) > 4 and not (len(original_lines_below_previous_blocks) > 0 and chunk_lines[i].strip() == original_lines_below_previous_blocks[0].strip()):
- repeating_file_suffix = True
+ try:
+ async for chunk in generator:
+ # Stop early if it is repeating the file_suffix or the step was deleted
+ if repeating_file_suffix:
break
+ if sdk.current_step_was_deleted():
+ return
- # If none of the above, insert the line!
- if False:
- await handle_generated_line(chunk_lines[i])
+ # Accumulate lines
+ if "content" not in chunk:
+ continue
+ chunk = chunk["content"]
+ chunk_lines = chunk.split("\n")
+ chunk_lines[0] = unfinished_line + chunk_lines[0]
+ if chunk.endswith("\n"):
+ unfinished_line = ""
+ chunk_lines.pop() # because this will be an empty string
+ else:
+ unfinished_line = chunk_lines.pop()
+
+ # Deal with newly accumulated lines
+ for i in range(len(chunk_lines)):
+ # Trailing whitespace doesn't matter
+ chunk_lines[i] = chunk_lines[i].rstrip()
+ chunk_lines[i] = common_whitespace + chunk_lines[i]
+
+ # Lines that should signify the end of generation
+ if self.is_end_line(chunk_lines[i]):
+ break
+ # Lines that should be ignored, like the <> tags
+ elif self.line_to_be_ignored(chunk_lines[i], completion_lines_covered == 0):
+ continue
+ # Check if we are currently just copying the prefix
+ elif (lines_of_prefix_copied > 0 or completion_lines_covered == 0) and lines_of_prefix_copied < len(file_prefix.splitlines()) and chunk_lines[i] == full_file_contents_lines[lines_of_prefix_copied]:
+ # This is a sketchy way of stopping it from repeating the file_prefix. Is a bug if output happens to have a matching line
+ lines_of_prefix_copied += 1
+ continue
+ # Because really short lines might be expected to be repeated, this is only a !heuristic!
+ # Stop when it starts copying the file_suffix
+ elif chunk_lines[i].strip() == line_below_highlighted_range.strip() and len(chunk_lines[i].strip()) > 4 and not (len(original_lines_below_previous_blocks) > 0 and chunk_lines[i].strip() == original_lines_below_previous_blocks[0].strip()):
+ repeating_file_suffix = True
+ break
- lines.append(chunk_lines[i])
- completion_lines_covered += 1
- current_line_in_file += 1
+ # If none of the above, insert the line!
+ if False:
+ await handle_generated_line(chunk_lines[i])
- await sendDiffUpdate(lines + [common_whitespace + unfinished_line], sdk)
+ lines.append(chunk_lines[i])
+ completion_lines_covered += 1
+ current_line_in_file += 1
+ await sendDiffUpdate(lines + [common_whitespace if unfinished_line.startswith("<") else (common_whitespace + unfinished_line)], sdk)
+ finally:
+ await generator.aclose()
# 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):
unfinished_line = common_whitespace + unfinished_line
@@ -508,7 +565,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
@@ -542,6 +599,8 @@ class DefaultModelEditCodeStep(Step):
# 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]:
@@ -562,6 +621,13 @@ class DefaultModelEditCodeStep(Step):
rif_dict[rif.filepath] = rif.contents
for rif in rif_with_contents:
+ # If the file doesn't exist, ask them to save it first
+ if not os.path.exists(rif.filepath):
+ message = f"The file {rif.filepath} does not exist. Please save it first."
+ raise ContinueCustomException(
+ title=message, message=message
+ )
+
await sdk.ide.setFileOpen(rif.filepath)
await sdk.ide.setSuggestionsLocked(rif.filepath, True)
await self.stream_rif(rif, sdk)
diff --git a/continuedev/src/continuedev/steps/help.py b/continuedev/src/continuedev/steps/help.py
new file mode 100644
index 00000000..ba1e6087
--- /dev/null
+++ b/continuedev/src/continuedev/steps/help.py
@@ -0,0 +1,59 @@
+from textwrap import dedent
+from ..core.main import ChatMessage, Step
+from ..core.sdk import ContinueSDK
+from ..libs.util.telemetry import capture_event
+
+help = dedent("""\
+ Continue is an open-source coding autopilot. It is a VS Code extension that brings the power of ChatGPT to your IDE.
+
+ It gathers context for you and stores your interactions automatically, so that you can avoid copy/paste now and benefit from a customized Large Language Model (LLM) later.
+
+ Continue can be used to...
+ 1. Edit chunks of code with specific instructions (e.g. "/edit migrate this digital ocean terraform file into one that works for GCP")
+ 2. Get answers to questions without switching windows (e.g. "how do I find running process on port 8000?")
+ 3. Generate files from scratch (e.g. "/edit Create a Python CLI tool that uses the posthog api to get events from DAUs")
+
+ You tell Continue to edit a specific section of code by highlighting it. If you highlight multiple code sections, then it will only edit the one with the purple glow around it. You can switch which one has the purple glow by clicking the paint brush.
+
+ If you don't highlight any code, then Continue will insert at the location of your cursor.
+
+ Continue passes all of the sections of code you highlight, the code above and below the to-be edited highlighted code section, and all previous steps above input box as context to the LLM.
+
+ You can use cmd+m (Mac) / ctrl+m (Windows) to open Continue. You can use cmd+shift+e / ctrl+shift+e to open file Explorer. You can add your own OpenAI API key to VS Code Settings with `cmd+,`
+
+ If Continue is stuck loading, try using `cmd+shift+p` to open the command palette, search "Reload Window", and then select it. This will reload VS Code and Continue and often fixes issues.
+
+ If you have feedback, please use /feedback to let us know how you would like to use Continue. We are excited to hear from you!""")
+
+
+class HelpStep(Step):
+
+ name: str = "Help"
+ user_input: str
+ manage_own_chat_context: bool = True
+ description: str = ""
+
+ async def run(self, sdk: ContinueSDK):
+
+ question = self.user_input
+
+ prompt = dedent(f"""Please us the information below to provide a succinct answer to the following quesiton: {question}
+
+ Information:
+
+ {help}""")
+
+ self.chat_context.append(ChatMessage(
+ role="user",
+ content=prompt,
+ summary="Help"
+ ))
+ messages = await sdk.get_chat_context()
+ generator = sdk.models.gpt4.stream_chat(messages)
+ async for chunk in generator:
+ if "content" in chunk:
+ self.description += chunk["content"]
+ await sdk.update_ui()
+
+ capture_event(sdk.ide.unique_id, "help", {
+ "question": question, "answer": self.description})
diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py
index 4f543022..ce7cbc60 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
@@ -99,8 +99,8 @@ class FasterEditHighlightedCodeStep(Step):
async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
range_in_files = await sdk.get_code_context(only_editing=True)
if len(range_in_files) == 0:
- # Get the full contents of all open files
- files = await sdk.ide.getOpenFiles()
+ # Get the full contents of all visible files
+ files = await sdk.ide.getVisibleFiles()
contents = {}
for file in files:
contents[file] = await sdk.ide.readFile(file)
@@ -191,8 +191,8 @@ class StarCoderEditHighlightedCodeStep(Step):
range_in_files = await sdk.get_code_context(only_editing=True)
found_highlighted_code = len(range_in_files) > 0
if not found_highlighted_code:
- # Get the full contents of all open files
- files = await sdk.ide.getOpenFiles()
+ # Get the full contents of all visible files
+ files = await sdk.ide.getVisibleFiles()
contents = {}
for file in files:
contents[file] = await sdk.ide.readFile(file)
@@ -251,44 +251,28 @@ class EditHighlightedCodeStep(Step):
highlighted_code = await sdk.ide.getHighlightedCode()
if highlighted_code is not None:
for rif in highlighted_code:
+ if os.path.dirname(rif.filepath) == os.path.expanduser(os.path.join("~", ".continue", "diffs")):
+ raise ContinueCustomException(
+ message="Please accept or reject the change before making another edit in this file.", title="Accept/Reject First")
if rif.range.start == rif.range.end:
range_in_files.append(
RangeInFileWithContents.from_range_in_file(rif, ""))
- # If nothing highlighted, edit the first open file
+ # If still no highlighted code, raise error
if len(range_in_files) == 0:
- # Get the full contents of all open files
- files = await sdk.ide.getOpenFiles()
- contents = {}
- for file in files:
- contents[file] = await sdk.ide.readFile(file)
-
- range_in_files = [RangeInFileWithContents.from_entire_file(
- filepath, content) for filepath, content in contents.items()]
-
- # If still no highlighted code, create a new file and edit there
- if len(range_in_files) == 0:
- # Create a new file
- new_file_path = "new_file.txt"
- await sdk.add_file(new_file_path, "")
- range_in_files = [
- RangeInFileWithContents.from_entire_file(new_file_path, "")]
+ raise ContinueCustomException(
+ message="Please highlight some code and try again.", title="No Code Selected")
range_in_files = list(map(lambda x: RangeInFile(
filepath=x.filepath, range=x.range
), range_in_files))
- await sdk.run_step(DefaultModelEditCodeStep(user_input=self.user_input, range_in_files=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
-class FindCodeStep(Step):
- prompt: str
-
- async def describe(self, models: Models) -> Coroutine[str, None, None]:
- return "Finding code"
-
- async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
- return await sdk.ide.getOpenFiles()
+ await sdk.run_step(DefaultModelEditCodeStep(user_input=self.user_input, range_in_files=range_in_files))
class UserInputStep(Step):
diff --git a/continuedev/src/continuedev/steps/open_config.py b/continuedev/src/continuedev/steps/open_config.py
index 441cb0e7..af55a95a 100644
--- a/continuedev/src/continuedev/steps/open_config.py
+++ b/continuedev/src/continuedev/steps/open_config.py
@@ -14,11 +14,14 @@ class OpenConfigStep(Step):
"custom_commands": [
{
"name": "test",
- "description": "Write unit tests like I do for the highlighted code"
+ "description": "Write unit tests like I do for the highlighted code",
"prompt": "Write a comprehensive set of unit tests for the selected code. It should setup, run tests that check for correctness including important edge cases, and teardown. Ensure that the tests are complete and sophisticated."
}
- ],
- ```""")
+ ]
+ ```
+ `"name"` is the command you will type.
+ `"description"` is the description displayed in the slash command menu.
+ `"prompt"` is the instruction given to the model. The overall prompt becomes "Task: {prompt}, Additional info: {user_input}". For example, if you entered "/test exactly 5 assertions", the overall prompt would become "Task: Write a comprehensive...and sophisticated, Additional info: exactly 5 assertions".""")
async def run(self, sdk: ContinueSDK):
global_dir = os.path.expanduser('~/.continue')
diff --git a/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/continuedev/src/continuedev/steps/welcome.py b/continuedev/src/continuedev/steps/welcome.py
index 32ebc3ba..2dece649 100644
--- a/continuedev/src/continuedev/steps/welcome.py
+++ b/continuedev/src/continuedev/steps/welcome.py
@@ -29,4 +29,4 @@ class WelcomeStep(Step):
- Ask about how the class works, how to write it in another language, etc.
\"\"\"""")))
- await sdk.ide.setFileOpen(filepath=filepath)
+ # await sdk.ide.setFileOpen(filepath=filepath)
diff --git a/docs/docs/concepts/ide.md b/docs/docs/concepts/ide.md
index dc7b9e23..bd31481b 100644
--- a/docs/docs/concepts/ide.md
+++ b/docs/docs/concepts/ide.md
@@ -41,9 +41,9 @@ Get the workspace directory
Set whether a file is open
-### openGUI
+### getSessionId
-Open a gui
+Get a new session ID
### showSuggestionsAndWait
diff --git a/docs/docs/how-to-use-continue.md b/docs/docs/how-to-use-continue.md
index 96c44228..66de7ee9 100644
--- a/docs/docs/how-to-use-continue.md
+++ b/docs/docs/how-to-use-continue.md
@@ -29,42 +29,82 @@ Here are tasks that Continue excels at helping you complete:
Continue works well in situations where find and replace does not work (i.e. “/edit change all of these to be like that”)
+Examples
+- "/edit Use 'Union' instead of a vertical bar here"
+- “/edit Make this use more descriptive variable names”
+
### Writing files from scratch
Continue can help you get started building React components, Python scripts, Shell scripts, Makefiles, Unit tests, etc.
+Examples
+- “/edit write a python script to get Posthog events"
+- “/edit add a React component for syntax highlighted code"
+
### Creating projects from scratch
Continue can go even further. For example, it can help build the scaffolding for a Python package, which includes a typer cli app to sort the arguments and print them back out.
+Examples
+- “/edit use this schema to write me a SQL query that gets recently churned users”
+- “/edit create a shell script to back up my home dir to /tmp/"
+
### Fix highlighted code
After selecting a code section, try to refactor it with Continue (e.g “/edit change the function to work like this”, “/edit do this everywhere”)
+Examples
+- “/edit migrate this digital ocean terraform file into one that works for GCP”
+- “/edit rewrite this function to be async”
+
### Ask about highlighted code or an entire file
If you don't understand how some code works, highlight it and ask "how does this code work?"
+Examples
+- “where in the page should I be making this request to the backend?”
+- “how can I communicate between these iframes?”
+
### Ask about errors
Continue can also help explain errors and offer possible solutions. You will need to copy and paste the error text into the text input though.
+Examples
+- “explain this error to me in human understandable way”
+- "what are some ideas for how I might solve this problem?"
+
### Figure out what shell command to run
Instead of switching windows and getting distracted, you can ask things like "How do I find running process on port 8000?"
+Examples
+- "what is the load_dotenv library name?"
+- "how do I find running process on port 8000?"
+
### Ask single-turn open-ended questions
Instead of leaving your IDE, you can ask open-ended questions that you don't expect to turn into multi-turn conversations.
+Examples
+- “how can I set up a Prisma schema that cascades deletes?”
+- "what is the difference between dense and sparse embeddings?"
+
### Editing small existing files
You can highlight an entire file and ask Continue to improve it as long as the file is not too large.
+Examples
+- “/edit here is a connector for postgres, now write one for kafka”
+- "/edit Rewrite this API call to grab all pages"
+
### Tasks with a few steps
There are many more tasks that Continue can help you complete. Typically, these will be tasks that don't involve too many steps to complete.
+Examples
+- “/edit make an IAM policy that creates a user with read-only access to S3”
+- “/edit change this plot into a bar chart in this dashboard component”
+
## When to not use Continue
Here are tasks that Continue is **not** helpful with today:
diff --git a/edit.gif b/edit.gif
index 6a53a14b..96240dfe 100644
--- a/edit.gif
+++ b/edit.gif
Binary files differ
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/README.md b/extension/README.md
index b57aedb7..2d449b92 100644
--- a/extension/README.md
+++ b/extension/README.md
@@ -7,23 +7,23 @@
### Get possible explainations
Ask Continue about a part of your code to get another perspective
-- `what might cause this error?`
-- `what is the load_dotenv library name?`
-- `how do I find running process on port 8000?`
+- “how can I set up a Prisma schema that cascades deletes?”
+- “where in the page should I be making this request to the backend?”
+- “how can I communicate between these iframes?”
### Edit in natural language
Highlight a section of code and instruct Continue to refactor it
-- `/edit Make this use more descriptive variable names`
-- `/edit Rewrite this API call to grab all pages`
-- `/edit Use 'Union' instead of a vertical bar here`
+- “/edit migrate this digital ocean terraform file into one that works for GCP”
+- “/edit change this plot into a bar chart in this dashboard component”
+- “/edit rewrite this function to be async”
### Generate files from scratch
Let Continue build the scaffolding of Python scripts, React components, and more
-- `Create a shell script to back up my home dir to /tmp/`
-- `Write Python in a new file to get Posthog events`
-- `Add a React component for syntax highlighted code`
+- “/edit here is a connector for postgres, now write one for kafka”
+- “/edit make an IAM policy that creates a user with read-only access to S3”
+- “/edit use this schema to write me a SQL query that gets recently churned users”
## OpenAI API Key
diff --git a/extension/media/edit.png b/extension/media/edit.png
index 5e77c0ea..f4ca623c 100644
--- a/extension/media/edit.png
+++ b/extension/media/edit.png
Binary files differ
diff --git a/extension/media/explain.png b/extension/media/explain.png
index 196ab914..79e8ccc9 100644
--- a/extension/media/explain.png
+++ b/extension/media/explain.png
Binary files differ
diff --git a/extension/media/generate.png b/extension/media/generate.png
index 9d84e4ae..c16d9f9f 100644
--- a/extension/media/generate.png
+++ b/extension/media/generate.png
Binary files differ
diff --git a/extension/package-lock.json b/extension/package-lock.json
index 5733c2dd..fbd3d92d 100644
--- a/extension/package-lock.json
+++ b/extension/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "continue",
- "version": "0.0.143",
+ "version": "0.0.175",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "continue",
- "version": "0.0.143",
+ "version": "0.0.175",
"license": "Apache-2.0",
"dependencies": {
"@electron/rebuild": "^3.2.10",
diff --git a/extension/package.json b/extension/package.json
index 444372f8..02a2ec1a 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.143",
+ "version": "0.0.175",
"publisher": "Continue",
"engines": {
"vscode": "^1.67.0"
@@ -44,18 +44,13 @@
"configuration": {
"title": "Continue",
"properties": {
- "continue.automode": {
- "type": "boolean",
- "default": true,
- "description": "Automatically find relevant code and suggest a fix whenever a traceback is found."
- },
"continue.serverUrl": {
"type": "string",
"default": "http://localhost:65432",
"description": "The URL of the Continue server to use."
},
"continue.OPENAI_API_KEY": {
- "type": "password",
+ "type": "string",
"default": null,
"description": "The OpenAI API key to use for code generation."
},
@@ -111,13 +106,18 @@
"command": "continue.quickTextEntry",
"category": "Continue",
"title": "Quick Text Entry"
+ },
+ {
+ "command": "continue.quickFix",
+ "category": "Continue",
+ "title": "Quick Fix"
}
],
"keybindings": [
{
"command": "continue.focusContinueInput",
- "mac": "cmd+k",
- "key": "ctrl+k"
+ "mac": "cmd+m",
+ "key": "ctrl+m"
},
{
"command": "continue.suggestionDown",
@@ -181,7 +181,7 @@
{
"id": "edit",
"title": "Edit in natural language",
- "description": "Highlight a section of code and instruct Continue to refactor it (e.g. `/edit Make this use more descriptive variable names`)",
+ "description": "Highlight a section of code and instruct Continue to refactor it (e.g. `/edit rewrite this function to be async`)",
"media": {
"image": "media/edit.png",
"altText": "Empty image"
@@ -191,7 +191,7 @@
{
"id": "explain",
"title": "Get possible explanations",
- "description": "Ask Continue about a part of your code to get another perspective (e.g. `how do I find running process on port 8000?`)",
+ "description": "Ask Continue about a part of your code to get another perspective (e.g. `where in the page should I be making this request to the backend?`)",
"media": {
"image": "media/explain.png",
"altText": "Empty image"
@@ -201,7 +201,7 @@
{
"id": "generate",
"title": "Generate files from scratch",
- "description": "Let Continue build the scaffolding of Python scripts, React components, and more (e.g. `Create a shell script to back up my home dir to /tmp/`)",
+ "description": "Let Continue build the scaffolding of Python scripts, React components, and more (e.g. `/edit here is a connector for postgres, now write one for kafka`)",
"media": {
"image": "media/generate.png",
"altText": "Empty image"
@@ -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/index.html b/extension/react-app/index.html
index e0d1c840..043c307e 100644
--- a/extension/react-app/index.html
+++ b/extension/react-app/index.html
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
- <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+ <link rel="icon" type="image/svg+xml" href="/play_button.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
diff --git a/extension/react-app/package-lock.json b/extension/react-app/package-lock.json
index 7316581d..13e02e86 100644
--- a/extension/react-app/package-lock.json
+++ b/extension/react-app/package-lock.json
@@ -11,12 +11,12 @@
"@styled-icons/heroicons-outline": "^10.47.0",
"@styled-icons/heroicons-solid": "^10.47.0",
"@types/vscode-webview": "^1.57.1",
+ "@uiw/react-markdown-preview": "^4.1.13",
"downshift": "^7.6.0",
"posthog-js": "^1.58.0",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-markdown": "^8.0.5",
"react-redux": "^8.0.5",
"react-switch": "^7.0.0",
"react-syntax-highlighter": "^15.5.0",
@@ -963,6 +963,16 @@
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
},
+ "node_modules/@types/parse5": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz",
+ "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g=="
+ },
+ "node_modules/@types/prismjs": {
+ "version": "1.26.0",
+ "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.0.tgz",
+ "integrity": "sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ=="
+ },
"node_modules/@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@@ -1027,6 +1037,34 @@
"resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.1.tgz",
"integrity": "sha512-ghW5SfuDmsGDS2A4xkvGsLwDRNc3Vj5rS6rPOyPm/IryZuf3wceZKxgYaUoW+k9f0f/CB7y2c1rRsdOWZWn0PQ=="
},
+ "node_modules/@uiw/copy-to-clipboard": {
+ "version": "1.0.15",
+ "resolved": "https://registry.npmjs.org/@uiw/copy-to-clipboard/-/copy-to-clipboard-1.0.15.tgz",
+ "integrity": "sha512-1bbGZ3T+SGmA07BoVPK4UCUDcowDN/moctviJGQexfOc9qL8TMLDQPr7mTPvDKhgJkgnlKkAQNFU8PiarIi9sQ=="
+ },
+ "node_modules/@uiw/react-markdown-preview": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@uiw/react-markdown-preview/-/react-markdown-preview-4.1.13.tgz",
+ "integrity": "sha512-fmIGvBpK6HJyDFf7EokjZSIS0713Bq5KwhOsZ8IkbCMYDcDThFlmMkTTqyzGjL3phrkP9ED5O63WSILzefqe6A==",
+ "dependencies": {
+ "@babel/runtime": "^7.17.2",
+ "@uiw/copy-to-clipboard": "~1.0.12",
+ "react-markdown": "~8.0.0",
+ "rehype-attr": "~2.1.0",
+ "rehype-autolink-headings": "~6.1.1",
+ "rehype-ignore": "^1.0.1",
+ "rehype-prism-plus": "~1.5.0",
+ "rehype-raw": "^6.1.1",
+ "rehype-rewrite": "~3.0.6",
+ "rehype-slug": "~5.1.0",
+ "remark-gfm": "~3.0.1",
+ "unist-util-visit": "^4.1.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
"node_modules/@vitejs/plugin-react-swc": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.2.0.tgz",
@@ -1163,6 +1201,15 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/bcp-47-match": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz",
+ "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -1172,6 +1219,11 @@
"node": ">=8"
}
},
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
+ },
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@@ -1245,6 +1297,15 @@
}
]
},
+ "node_modules/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -1370,6 +1431,11 @@
"node": ">=4"
}
},
+ "node_modules/css-selector-parser": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-1.4.1.tgz",
+ "integrity": "sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g=="
+ },
"node_modules/css-to-react-native": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
@@ -1473,6 +1539,18 @@
"node": ">=0.3.1"
}
},
+ "node_modules/direction": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz",
+ "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==",
+ "bin": {
+ "direction": "cli.js"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@@ -1684,6 +1762,11 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
+ "node_modules/github-slugger": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
+ "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="
+ },
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -1729,6 +1812,86 @@
"node": ">=4"
}
},
+ "node_modules/hast-util-from-parse5": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz",
+ "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "@types/unist": "^2.0.0",
+ "hastscript": "^7.0.0",
+ "property-information": "^6.0.0",
+ "vfile": "^5.0.0",
+ "vfile-location": "^4.0.0",
+ "web-namespaces": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-parse5/node_modules/hast-util-parse-selector": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz",
+ "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==",
+ "dependencies": {
+ "@types/hast": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-parse5/node_modules/hastscript": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz",
+ "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-parse-selector": "^3.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-has-property": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-2.0.1.tgz",
+ "integrity": "sha512-X2+RwZIMTMKpXUzlotatPzWj8bspCymtXH3cfG3iQKV+wPF53Vgaqxi/eLqGck0wKq1kS9nvoB1wchbCPEL8sg==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-heading-rank": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-2.1.1.tgz",
+ "integrity": "sha512-iAuRp+ESgJoRFJbSyaqsfvJDY6zzmFoEnL1gtz1+U8gKtGGj1p0CVlysuUAUjq95qlZESHINLThwJzNGmgGZxA==",
+ "dependencies": {
+ "@types/hast": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-is-element": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.3.tgz",
+ "integrity": "sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "@types/unist": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/hast-util-parse-selector": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
@@ -1738,6 +1901,83 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/hast-util-raw": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz",
+ "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "@types/parse5": "^6.0.0",
+ "hast-util-from-parse5": "^7.0.0",
+ "hast-util-to-parse5": "^7.0.0",
+ "html-void-elements": "^2.0.0",
+ "parse5": "^6.0.0",
+ "unist-util-position": "^4.0.0",
+ "unist-util-visit": "^4.0.0",
+ "vfile": "^5.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-select": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-5.0.5.tgz",
+ "integrity": "sha512-QQhWMhgTFRhCaQdgTKzZ5g31GLQ9qRb1hZtDPMqQaOhpLBziWcshUS0uCR5IJ0U1jrK/mxg35fmcq+Dp/Cy2Aw==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "@types/unist": "^2.0.0",
+ "bcp-47-match": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "css-selector-parser": "^1.0.0",
+ "direction": "^2.0.0",
+ "hast-util-has-property": "^2.0.0",
+ "hast-util-to-string": "^2.0.0",
+ "hast-util-whitespace": "^2.0.0",
+ "not": "^0.1.0",
+ "nth-check": "^2.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "unist-util-visit": "^4.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-parse5": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz",
+ "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz",
+ "integrity": "sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==",
+ "dependencies": {
+ "@types/hast": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/hast-util-whitespace": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz",
@@ -1814,6 +2054,15 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/html-void-elements": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
+ "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/inline-style-parser": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz",
@@ -1995,6 +2244,15 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
+ "node_modules/longest-streak": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -2019,6 +2277,15 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/markdown-table": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
+ "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/mdast-util-definitions": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz",
@@ -2033,6 +2300,32 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/mdast-util-find-and-replace": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz",
+ "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "escape-string-regexp": "^5.0.0",
+ "unist-util-is": "^5.0.0",
+ "unist-util-visit-parents": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/mdast-util-from-markdown": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.0.tgz",
@@ -2056,6 +2349,107 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/mdast-util-gfm": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz",
+ "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==",
+ "dependencies": {
+ "mdast-util-from-markdown": "^1.0.0",
+ "mdast-util-gfm-autolink-literal": "^1.0.0",
+ "mdast-util-gfm-footnote": "^1.0.0",
+ "mdast-util-gfm-strikethrough": "^1.0.0",
+ "mdast-util-gfm-table": "^1.0.0",
+ "mdast-util-gfm-task-list-item": "^1.0.0",
+ "mdast-util-to-markdown": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-autolink-literal": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz",
+ "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "ccount": "^2.0.0",
+ "mdast-util-find-and-replace": "^2.0.0",
+ "micromark-util-character": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-footnote": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz",
+ "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0",
+ "micromark-util-normalize-identifier": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-strikethrough": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz",
+ "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-table": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz",
+ "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "markdown-table": "^3.0.0",
+ "mdast-util-from-markdown": "^1.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-task-list-item": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz",
+ "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-phrasing": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz",
+ "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "unist-util-is": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/mdast-util-to-hast": {
"version": "12.3.0",
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz",
@@ -2075,6 +2469,25 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/mdast-util-to-markdown": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz",
+ "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "@types/unist": "^2.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-phrasing": "^3.0.0",
+ "mdast-util-to-string": "^3.0.0",
+ "micromark-util-decode-string": "^1.0.0",
+ "unist-util-visit": "^4.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/mdast-util-to-string": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.1.tgz",
@@ -2163,6 +2576,120 @@
"uvu": "^0.5.0"
}
},
+ "node_modules/micromark-extension-gfm": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz",
+ "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==",
+ "dependencies": {
+ "micromark-extension-gfm-autolink-literal": "^1.0.0",
+ "micromark-extension-gfm-footnote": "^1.0.0",
+ "micromark-extension-gfm-strikethrough": "^1.0.0",
+ "micromark-extension-gfm-table": "^1.0.0",
+ "micromark-extension-gfm-tagfilter": "^1.0.0",
+ "micromark-extension-gfm-task-list-item": "^1.0.0",
+ "micromark-util-combine-extensions": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-autolink-literal": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz",
+ "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==",
+ "dependencies": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-sanitize-uri": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-footnote": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz",
+ "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==",
+ "dependencies": {
+ "micromark-core-commonmark": "^1.0.0",
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-normalize-identifier": "^1.0.0",
+ "micromark-util-sanitize-uri": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-strikethrough": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz",
+ "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==",
+ "dependencies": {
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-classify-character": "^1.0.0",
+ "micromark-util-resolve-all": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-table": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz",
+ "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==",
+ "dependencies": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-tagfilter": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz",
+ "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==",
+ "dependencies": {
+ "micromark-util-types": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-task-list-item": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz",
+ "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==",
+ "dependencies": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/micromark-factory-destination": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz",
@@ -2589,6 +3116,22 @@
"node": ">=0.10.0"
}
},
+ "node_modules/not": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/not/-/not-0.1.0.tgz",
+ "integrity": "sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA=="
+ },
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -2632,6 +3175,16 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/parse-numeric-range": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz",
+ "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ=="
+ },
+ "node_modules/parse5": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
+ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
+ },
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -3047,6 +3600,257 @@
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
+ "node_modules/rehype-attr": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/rehype-attr/-/rehype-attr-2.1.4.tgz",
+ "integrity": "sha512-iAeaL5JyF4XxkcvWzpi/0SAF7iV7qOTaHS56tJuEsXziQc3+PEmMn65kV8OFgbO9mRVY7J1fRC/aLvot1PsNkg==",
+ "dependencies": {
+ "unified": "~10.1.1",
+ "unist-util-visit": "~4.1.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/rehype-autolink-headings": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-6.1.1.tgz",
+ "integrity": "sha512-NMYzZIsHM3sA14nC5rAFuUPIOfg+DFmf9EY1YMhaNlB7+3kK/ZlE6kqPfuxr1tsJ1XWkTrMtMoyHosU70d35mA==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "extend": "^3.0.0",
+ "hast-util-has-property": "^2.0.0",
+ "hast-util-heading-rank": "^2.0.0",
+ "hast-util-is-element": "^2.0.0",
+ "unified": "^10.0.0",
+ "unist-util-visit": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-ignore": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/rehype-ignore/-/rehype-ignore-1.0.5.tgz",
+ "integrity": "sha512-JQXS5eDwXaYKwB8JEYFJJA/YvGi0sSNUOYuiURMtuPTg8tuWHFB91JMYLbImH1FyvyGQM4fIBqNMAPB50WR2Bw==",
+ "dependencies": {
+ "hast-util-select": "^5.0.5",
+ "unified": "^10.1.2",
+ "unist-util-visit": "^4.1.2"
+ },
+ "engines": {
+ "node": "^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/rehype-parse": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-8.0.4.tgz",
+ "integrity": "sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "hast-util-from-parse5": "^7.0.0",
+ "parse5": "^6.0.0",
+ "unified": "^10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-prism-plus": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/rehype-prism-plus/-/rehype-prism-plus-1.5.1.tgz",
+ "integrity": "sha512-mowYefSfrIkMMxkb0fwuEXlvc5nA9b1vQ6mzujM81Qx28RI0mo7jCHsBZ2tJ4eIJKXdFn+EdPkZZBGB10K02vg==",
+ "dependencies": {
+ "hast-util-to-string": "^2.0.0",
+ "parse-numeric-range": "^1.3.0",
+ "refractor": "^4.7.0",
+ "rehype-parse": "^8.0.2",
+ "unist-util-filter": "^4.0.0",
+ "unist-util-visit": "^4.0.0"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/character-reference-invalid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/hast-util-parse-selector": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz",
+ "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==",
+ "dependencies": {
+ "@types/hast": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/hastscript": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz",
+ "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-parse-selector": "^3.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/is-alphabetical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/is-alphanumerical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+ "dependencies": {
+ "is-alphabetical": "^2.0.0",
+ "is-decimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/is-decimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/is-hexadecimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/parse-entities": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz",
+ "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "character-entities": "^2.0.0",
+ "character-entities-legacy": "^3.0.0",
+ "character-reference-invalid": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "is-alphanumerical": "^2.0.0",
+ "is-decimal": "^2.0.0",
+ "is-hexadecimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/rehype-prism-plus/node_modules/refractor": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/refractor/-/refractor-4.8.1.tgz",
+ "integrity": "sha512-/fk5sI0iTgFYlmVGYVew90AoYnNMP6pooClx/XKqyeeCQXrL0Kvgn8V0VEht5ccdljbzzF1i3Q213gcntkRExg==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "@types/prismjs": "^1.0.0",
+ "hastscript": "^7.0.0",
+ "parse-entities": "^4.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/rehype-raw": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-6.1.1.tgz",
+ "integrity": "sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "hast-util-raw": "^7.2.0",
+ "unified": "^10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-rewrite": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/rehype-rewrite/-/rehype-rewrite-3.0.6.tgz",
+ "integrity": "sha512-REDTNCvsKcAazy8IQWzKp66AhSUDSOIKssSCqNqCcT9sN7JCwAAm3mWGTUdUzq80ABuy8d0D6RBwbnewu1aY1g==",
+ "dependencies": {
+ "hast-util-select": "~5.0.1",
+ "unified": "~10.1.1",
+ "unist-util-visit": "~4.1.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/rehype-slug": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-5.1.0.tgz",
+ "integrity": "sha512-Gf91dJoXneiorNEnn+Phx97CO7oRMrpi+6r155tTxzGuLtm+QrI4cTwCa9e1rtePdL4i9tSO58PeSS6HWfgsiw==",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "github-slugger": "^2.0.0",
+ "hast-util-has-property": "^2.0.0",
+ "hast-util-heading-rank": "^2.0.0",
+ "hast-util-to-string": "^2.0.0",
+ "unified": "^10.0.0",
+ "unist-util-visit": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-gfm": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz",
+ "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-gfm": "^2.0.0",
+ "micromark-extension-gfm": "^2.0.0",
+ "unified": "^10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/remark-parse": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz",
@@ -3365,6 +4169,16 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/unist-util-filter": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/unist-util-filter/-/unist-util-filter-4.0.1.tgz",
+ "integrity": "sha512-RynicUM/vbOSTSiUK+BnaK9XMfmQUh6gyi7L6taNgc7FIf84GukXVV3ucGzEN/PhUUkdP5hb1MmXc+3cvPUm5Q==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "unist-util-is": "^5.0.0",
+ "unist-util-visit-parents": "^5.0.0"
+ }
+ },
"node_modules/unist-util-generated": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz",
@@ -3517,6 +4331,19 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/vfile-location": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz",
+ "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "vfile": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/vfile-message": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz",
@@ -3587,6 +4414,15 @@
"fs-extra": "^10.0.0"
}
},
+ "node_modules/web-namespaces": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
+ "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -3603,6 +4439,15 @@
"engines": {
"node": ">= 6"
}
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
}
},
"dependencies": {
@@ -4143,6 +4988,16 @@
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
},
+ "@types/parse5": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz",
+ "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g=="
+ },
+ "@types/prismjs": {
+ "version": "1.26.0",
+ "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.0.tgz",
+ "integrity": "sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ=="
+ },
"@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@@ -4207,6 +5062,30 @@
"resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.1.tgz",
"integrity": "sha512-ghW5SfuDmsGDS2A4xkvGsLwDRNc3Vj5rS6rPOyPm/IryZuf3wceZKxgYaUoW+k9f0f/CB7y2c1rRsdOWZWn0PQ=="
},
+ "@uiw/copy-to-clipboard": {
+ "version": "1.0.15",
+ "resolved": "https://registry.npmjs.org/@uiw/copy-to-clipboard/-/copy-to-clipboard-1.0.15.tgz",
+ "integrity": "sha512-1bbGZ3T+SGmA07BoVPK4UCUDcowDN/moctviJGQexfOc9qL8TMLDQPr7mTPvDKhgJkgnlKkAQNFU8PiarIi9sQ=="
+ },
+ "@uiw/react-markdown-preview": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@uiw/react-markdown-preview/-/react-markdown-preview-4.1.13.tgz",
+ "integrity": "sha512-fmIGvBpK6HJyDFf7EokjZSIS0713Bq5KwhOsZ8IkbCMYDcDThFlmMkTTqyzGjL3phrkP9ED5O63WSILzefqe6A==",
+ "requires": {
+ "@babel/runtime": "^7.17.2",
+ "@uiw/copy-to-clipboard": "~1.0.12",
+ "react-markdown": "~8.0.0",
+ "rehype-attr": "~2.1.0",
+ "rehype-autolink-headings": "~6.1.1",
+ "rehype-ignore": "^1.0.1",
+ "rehype-prism-plus": "~1.5.0",
+ "rehype-raw": "^6.1.1",
+ "rehype-rewrite": "~3.0.6",
+ "rehype-slug": "~5.1.0",
+ "remark-gfm": "~3.0.1",
+ "unist-util-visit": "^4.1.0"
+ }
+ },
"@vitejs/plugin-react-swc": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.2.0.tgz",
@@ -4299,12 +5178,22 @@
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
"integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="
},
+ "bcp-47-match": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz",
+ "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ=="
+ },
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
+ "boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
+ },
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@@ -4343,6 +5232,11 @@
"integrity": "sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==",
"dev": true
},
+ "ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="
+ },
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -4436,6 +5330,11 @@
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
"integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg=="
},
+ "css-selector-parser": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-1.4.1.tgz",
+ "integrity": "sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g=="
+ },
"css-to-react-native": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
@@ -4506,6 +5405,11 @@
"resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
"integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw=="
},
+ "direction": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz",
+ "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA=="
+ },
"dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@@ -4672,6 +5576,11 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
+ "github-slugger": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
+ "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="
+ },
"glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -4705,11 +5614,130 @@
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="
},
+ "hast-util-from-parse5": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz",
+ "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "@types/unist": "^2.0.0",
+ "hastscript": "^7.0.0",
+ "property-information": "^6.0.0",
+ "vfile": "^5.0.0",
+ "vfile-location": "^4.0.0",
+ "web-namespaces": "^2.0.0"
+ },
+ "dependencies": {
+ "hast-util-parse-selector": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz",
+ "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==",
+ "requires": {
+ "@types/hast": "^2.0.0"
+ }
+ },
+ "hastscript": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz",
+ "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-parse-selector": "^3.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0"
+ }
+ }
+ }
+ },
+ "hast-util-has-property": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-2.0.1.tgz",
+ "integrity": "sha512-X2+RwZIMTMKpXUzlotatPzWj8bspCymtXH3cfG3iQKV+wPF53Vgaqxi/eLqGck0wKq1kS9nvoB1wchbCPEL8sg=="
+ },
+ "hast-util-heading-rank": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-2.1.1.tgz",
+ "integrity": "sha512-iAuRp+ESgJoRFJbSyaqsfvJDY6zzmFoEnL1gtz1+U8gKtGGj1p0CVlysuUAUjq95qlZESHINLThwJzNGmgGZxA==",
+ "requires": {
+ "@types/hast": "^2.0.0"
+ }
+ },
+ "hast-util-is-element": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.3.tgz",
+ "integrity": "sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "@types/unist": "^2.0.0"
+ }
+ },
"hast-util-parse-selector": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
"integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ=="
},
+ "hast-util-raw": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz",
+ "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "@types/parse5": "^6.0.0",
+ "hast-util-from-parse5": "^7.0.0",
+ "hast-util-to-parse5": "^7.0.0",
+ "html-void-elements": "^2.0.0",
+ "parse5": "^6.0.0",
+ "unist-util-position": "^4.0.0",
+ "unist-util-visit": "^4.0.0",
+ "vfile": "^5.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
+ }
+ },
+ "hast-util-select": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-5.0.5.tgz",
+ "integrity": "sha512-QQhWMhgTFRhCaQdgTKzZ5g31GLQ9qRb1hZtDPMqQaOhpLBziWcshUS0uCR5IJ0U1jrK/mxg35fmcq+Dp/Cy2Aw==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "@types/unist": "^2.0.0",
+ "bcp-47-match": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "css-selector-parser": "^1.0.0",
+ "direction": "^2.0.0",
+ "hast-util-has-property": "^2.0.0",
+ "hast-util-to-string": "^2.0.0",
+ "hast-util-whitespace": "^2.0.0",
+ "not": "^0.1.0",
+ "nth-check": "^2.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "unist-util-visit": "^4.0.0",
+ "zwitch": "^2.0.0"
+ }
+ },
+ "hast-util-to-parse5": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz",
+ "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
+ }
+ },
+ "hast-util-to-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz",
+ "integrity": "sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==",
+ "requires": {
+ "@types/hast": "^2.0.0"
+ }
+ },
"hast-util-whitespace": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz",
@@ -4767,6 +5795,11 @@
}
}
},
+ "html-void-elements": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
+ "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A=="
+ },
"inline-style-parser": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz",
@@ -4880,6 +5913,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
+ "longest-streak": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="
+ },
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -4897,6 +5935,11 @@
"highlight.js": "~10.7.0"
}
},
+ "markdown-table": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
+ "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw=="
+ },
"mdast-util-definitions": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz",
@@ -4907,6 +5950,24 @@
"unist-util-visit": "^4.0.0"
}
},
+ "mdast-util-find-and-replace": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz",
+ "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "escape-string-regexp": "^5.0.0",
+ "unist-util-is": "^5.0.0",
+ "unist-util-visit-parents": "^5.0.0"
+ },
+ "dependencies": {
+ "escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="
+ }
+ }
+ },
"mdast-util-from-markdown": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.0.tgz",
@@ -4926,6 +5987,79 @@
"uvu": "^0.5.0"
}
},
+ "mdast-util-gfm": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz",
+ "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==",
+ "requires": {
+ "mdast-util-from-markdown": "^1.0.0",
+ "mdast-util-gfm-autolink-literal": "^1.0.0",
+ "mdast-util-gfm-footnote": "^1.0.0",
+ "mdast-util-gfm-strikethrough": "^1.0.0",
+ "mdast-util-gfm-table": "^1.0.0",
+ "mdast-util-gfm-task-list-item": "^1.0.0",
+ "mdast-util-to-markdown": "^1.0.0"
+ }
+ },
+ "mdast-util-gfm-autolink-literal": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz",
+ "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "ccount": "^2.0.0",
+ "mdast-util-find-and-replace": "^2.0.0",
+ "micromark-util-character": "^1.0.0"
+ }
+ },
+ "mdast-util-gfm-footnote": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz",
+ "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0",
+ "micromark-util-normalize-identifier": "^1.0.0"
+ }
+ },
+ "mdast-util-gfm-strikethrough": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz",
+ "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ }
+ },
+ "mdast-util-gfm-table": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz",
+ "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "markdown-table": "^3.0.0",
+ "mdast-util-from-markdown": "^1.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ }
+ },
+ "mdast-util-gfm-task-list-item": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz",
+ "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-markdown": "^1.3.0"
+ }
+ },
+ "mdast-util-phrasing": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz",
+ "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "unist-util-is": "^5.0.0"
+ }
+ },
"mdast-util-to-hast": {
"version": "12.3.0",
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz",
@@ -4941,6 +6075,21 @@
"unist-util-visit": "^4.0.0"
}
},
+ "mdast-util-to-markdown": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz",
+ "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "@types/unist": "^2.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-phrasing": "^3.0.0",
+ "mdast-util-to-string": "^3.0.0",
+ "micromark-util-decode-string": "^1.0.0",
+ "unist-util-visit": "^4.0.0",
+ "zwitch": "^2.0.0"
+ }
+ },
"mdast-util-to-string": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.1.tgz",
@@ -5002,6 +6151,92 @@
"uvu": "^0.5.0"
}
},
+ "micromark-extension-gfm": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz",
+ "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==",
+ "requires": {
+ "micromark-extension-gfm-autolink-literal": "^1.0.0",
+ "micromark-extension-gfm-footnote": "^1.0.0",
+ "micromark-extension-gfm-strikethrough": "^1.0.0",
+ "micromark-extension-gfm-table": "^1.0.0",
+ "micromark-extension-gfm-tagfilter": "^1.0.0",
+ "micromark-extension-gfm-task-list-item": "^1.0.0",
+ "micromark-util-combine-extensions": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "micromark-extension-gfm-autolink-literal": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz",
+ "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==",
+ "requires": {
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-sanitize-uri": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "micromark-extension-gfm-footnote": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz",
+ "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==",
+ "requires": {
+ "micromark-core-commonmark": "^1.0.0",
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-normalize-identifier": "^1.0.0",
+ "micromark-util-sanitize-uri": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "micromark-extension-gfm-strikethrough": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz",
+ "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==",
+ "requires": {
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-classify-character": "^1.0.0",
+ "micromark-util-resolve-all": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "micromark-extension-gfm-table": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz",
+ "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==",
+ "requires": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
+ "micromark-extension-gfm-tagfilter": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz",
+ "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==",
+ "requires": {
+ "micromark-util-types": "^1.0.0"
+ }
+ },
+ "micromark-extension-gfm-task-list-item": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz",
+ "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==",
+ "requires": {
+ "micromark-factory-space": "^1.0.0",
+ "micromark-util-character": "^1.0.0",
+ "micromark-util-symbol": "^1.0.0",
+ "micromark-util-types": "^1.0.0",
+ "uvu": "^0.5.0"
+ }
+ },
"micromark-factory-destination": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz",
@@ -5217,6 +6452,19 @@
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
"dev": true
},
+ "not": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/not/-/not-0.1.0.tgz",
+ "integrity": "sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA=="
+ },
+ "nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "requires": {
+ "boolbase": "^1.0.0"
+ }
+ },
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -5248,6 +6496,16 @@
}
}
},
+ "parse-numeric-range": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz",
+ "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ=="
+ },
+ "parse5": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
+ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
+ },
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
@@ -5510,6 +6768,190 @@
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
+ "rehype-attr": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/rehype-attr/-/rehype-attr-2.1.4.tgz",
+ "integrity": "sha512-iAeaL5JyF4XxkcvWzpi/0SAF7iV7qOTaHS56tJuEsXziQc3+PEmMn65kV8OFgbO9mRVY7J1fRC/aLvot1PsNkg==",
+ "requires": {
+ "unified": "~10.1.1",
+ "unist-util-visit": "~4.1.0"
+ }
+ },
+ "rehype-autolink-headings": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-6.1.1.tgz",
+ "integrity": "sha512-NMYzZIsHM3sA14nC5rAFuUPIOfg+DFmf9EY1YMhaNlB7+3kK/ZlE6kqPfuxr1tsJ1XWkTrMtMoyHosU70d35mA==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "extend": "^3.0.0",
+ "hast-util-has-property": "^2.0.0",
+ "hast-util-heading-rank": "^2.0.0",
+ "hast-util-is-element": "^2.0.0",
+ "unified": "^10.0.0",
+ "unist-util-visit": "^4.0.0"
+ }
+ },
+ "rehype-ignore": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/rehype-ignore/-/rehype-ignore-1.0.5.tgz",
+ "integrity": "sha512-JQXS5eDwXaYKwB8JEYFJJA/YvGi0sSNUOYuiURMtuPTg8tuWHFB91JMYLbImH1FyvyGQM4fIBqNMAPB50WR2Bw==",
+ "requires": {
+ "hast-util-select": "^5.0.5",
+ "unified": "^10.1.2",
+ "unist-util-visit": "^4.1.2"
+ }
+ },
+ "rehype-parse": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-8.0.4.tgz",
+ "integrity": "sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "hast-util-from-parse5": "^7.0.0",
+ "parse5": "^6.0.0",
+ "unified": "^10.0.0"
+ }
+ },
+ "rehype-prism-plus": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/rehype-prism-plus/-/rehype-prism-plus-1.5.1.tgz",
+ "integrity": "sha512-mowYefSfrIkMMxkb0fwuEXlvc5nA9b1vQ6mzujM81Qx28RI0mo7jCHsBZ2tJ4eIJKXdFn+EdPkZZBGB10K02vg==",
+ "requires": {
+ "hast-util-to-string": "^2.0.0",
+ "parse-numeric-range": "^1.3.0",
+ "refractor": "^4.7.0",
+ "rehype-parse": "^8.0.2",
+ "unist-util-filter": "^4.0.0",
+ "unist-util-visit": "^4.0.0"
+ },
+ "dependencies": {
+ "character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="
+ },
+ "character-reference-invalid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="
+ },
+ "hast-util-parse-selector": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz",
+ "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==",
+ "requires": {
+ "@types/hast": "^2.0.0"
+ }
+ },
+ "hastscript": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz",
+ "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-parse-selector": "^3.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0"
+ }
+ },
+ "is-alphabetical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="
+ },
+ "is-alphanumerical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+ "requires": {
+ "is-alphabetical": "^2.0.0",
+ "is-decimal": "^2.0.0"
+ }
+ },
+ "is-decimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="
+ },
+ "is-hexadecimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="
+ },
+ "parse-entities": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz",
+ "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==",
+ "requires": {
+ "@types/unist": "^2.0.0",
+ "character-entities": "^2.0.0",
+ "character-entities-legacy": "^3.0.0",
+ "character-reference-invalid": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "is-alphanumerical": "^2.0.0",
+ "is-decimal": "^2.0.0",
+ "is-hexadecimal": "^2.0.0"
+ }
+ },
+ "refractor": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/refractor/-/refractor-4.8.1.tgz",
+ "integrity": "sha512-/fk5sI0iTgFYlmVGYVew90AoYnNMP6pooClx/XKqyeeCQXrL0Kvgn8V0VEht5ccdljbzzF1i3Q213gcntkRExg==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "@types/prismjs": "^1.0.0",
+ "hastscript": "^7.0.0",
+ "parse-entities": "^4.0.0"
+ }
+ }
+ }
+ },
+ "rehype-raw": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-6.1.1.tgz",
+ "integrity": "sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "hast-util-raw": "^7.2.0",
+ "unified": "^10.0.0"
+ }
+ },
+ "rehype-rewrite": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/rehype-rewrite/-/rehype-rewrite-3.0.6.tgz",
+ "integrity": "sha512-REDTNCvsKcAazy8IQWzKp66AhSUDSOIKssSCqNqCcT9sN7JCwAAm3mWGTUdUzq80ABuy8d0D6RBwbnewu1aY1g==",
+ "requires": {
+ "hast-util-select": "~5.0.1",
+ "unified": "~10.1.1",
+ "unist-util-visit": "~4.1.0"
+ }
+ },
+ "rehype-slug": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-5.1.0.tgz",
+ "integrity": "sha512-Gf91dJoXneiorNEnn+Phx97CO7oRMrpi+6r155tTxzGuLtm+QrI4cTwCa9e1rtePdL4i9tSO58PeSS6HWfgsiw==",
+ "requires": {
+ "@types/hast": "^2.0.0",
+ "github-slugger": "^2.0.0",
+ "hast-util-has-property": "^2.0.0",
+ "hast-util-heading-rank": "^2.0.0",
+ "hast-util-to-string": "^2.0.0",
+ "unified": "^10.0.0",
+ "unist-util-visit": "^4.0.0"
+ }
+ },
+ "remark-gfm": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz",
+ "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==",
+ "requires": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-gfm": "^2.0.0",
+ "micromark-extension-gfm": "^2.0.0",
+ "unified": "^10.0.0"
+ }
+ },
"remark-parse": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz",
@@ -5722,6 +7164,16 @@
"vfile": "^5.0.0"
}
},
+ "unist-util-filter": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/unist-util-filter/-/unist-util-filter-4.0.1.tgz",
+ "integrity": "sha512-RynicUM/vbOSTSiUK+BnaK9XMfmQUh6gyi7L6taNgc7FIf84GukXVV3ucGzEN/PhUUkdP5hb1MmXc+3cvPUm5Q==",
+ "requires": {
+ "@types/unist": "^2.0.0",
+ "unist-util-is": "^5.0.0",
+ "unist-util-visit-parents": "^5.0.0"
+ }
+ },
"unist-util-generated": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz",
@@ -5819,6 +7271,15 @@
"vfile-message": "^3.0.0"
}
},
+ "vfile-location": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz",
+ "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==",
+ "requires": {
+ "@types/unist": "^2.0.0",
+ "vfile": "^5.0.0"
+ }
+ },
"vfile-message": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz",
@@ -5849,6 +7310,11 @@
"fs-extra": "^10.0.0"
}
},
+ "web-namespaces": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
+ "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="
+ },
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -5859,6 +7325,11 @@
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true
+ },
+ "zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="
}
}
}
diff --git a/extension/react-app/package.json b/extension/react-app/package.json
index 4bedb813..704f520a 100644
--- a/extension/react-app/package.json
+++ b/extension/react-app/package.json
@@ -12,12 +12,12 @@
"@styled-icons/heroicons-outline": "^10.47.0",
"@styled-icons/heroicons-solid": "^10.47.0",
"@types/vscode-webview": "^1.57.1",
+ "@uiw/react-markdown-preview": "^4.1.13",
"downshift": "^7.6.0",
"posthog-js": "^1.58.0",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-markdown": "^8.0.5",
"react-redux": "^8.0.5",
"react-switch": "^7.0.0",
"react-syntax-highlighter": "^15.5.0",
diff --git a/extension/react-app/public/vite.svg b/extension/react-app/public/vite.svg
deleted file mode 100644
index e7b8dfb1..00000000
--- a/extension/react-app/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg> \ No newline at end of file
diff --git a/extension/react-app/src/App.tsx b/extension/react-app/src/App.tsx
index 8785f88f..c9bd42e0 100644
--- a/extension/react-app/src/App.tsx
+++ b/extension/react-app/src/App.tsx
@@ -1,8 +1,5 @@
import DebugPanel from "./components/DebugPanel";
-import MainTab from "./tabs/main";
-import WelcomeTab from "./tabs/welcome";
-import ChatTab from "./tabs/chat";
-import GUI from "./tabs/gui";
+import GUI from "./pages/gui";
import { createContext } from "react";
import useContinueGUIProtocol from "./hooks/useWebsocket";
import ContinueGUIClientProtocol from "./hooks/useContinueGUIProtocol";
@@ -18,13 +15,7 @@ function App() {
<GUIClientContext.Provider value={client}>
<DebugPanel
tabs={[
- {
- element: <GUI />,
- title: "GUI",
- },
- // { element: <MainTab />, title: "Debug Panel" },
- // { element: <WelcomeTab />, title: "Welcome" },
- // { element: <ChatTab />, title: "Chat" },
+ { element: <GUI />, title: "GUI" }
]}
/>
</GUIClientContext.Provider>
diff --git a/extension/react-app/src/TestPage.tsx b/extension/react-app/src/TestPage.tsx
deleted file mode 100644
index d104980b..00000000
--- a/extension/react-app/src/TestPage.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from "react";
-import styled from "styled-components";
-
-const SideBySideDiv = styled.div`
- display: grid;
- grid-template-columns: 1fr 1fr;
- grid-template-rows: 1fr;
- grid-template-areas: "left right";
-`;
-
-const LeftDiv = styled.div`
- grid-area: left;
-`;
-
-const RightDiv = styled.div`
- grid-area: right;
-`;
-
-function TestPage() {
- return (
- <div>
- <h1>Continue</h1>
- <SideBySideDiv>
- <LeftDiv>
- <h2>Left</h2>
- </LeftDiv>
- <RightDiv>
- <h2>Right</h2>
- </RightDiv>
- </SideBySideDiv>
- </div>
- );
-}
diff --git a/extension/react-app/src/assets/Hubot-Sans.woff2 b/extension/react-app/src/assets/Hubot-Sans.woff2
deleted file mode 100644
index 5089fc47..00000000
--- a/extension/react-app/src/assets/Hubot-Sans.woff2
+++ /dev/null
Binary files differ
diff --git a/extension/react-app/src/assets/Mona-Sans.woff2 b/extension/react-app/src/assets/Mona-Sans.woff2
deleted file mode 100644
index 8208a500..00000000
--- a/extension/react-app/src/assets/Mona-Sans.woff2
+++ /dev/null
Binary files differ
diff --git a/extension/react-app/src/assets/react.svg b/extension/react-app/src/assets/react.svg
deleted file mode 100644
index 6c87de9b..00000000
--- a/extension/react-app/src/assets/react.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg> \ No newline at end of file
diff --git a/extension/react-app/src/components/CodeMultiselect.tsx b/extension/react-app/src/components/CodeMultiselect.tsx
deleted file mode 100644
index c0ab9400..00000000
--- a/extension/react-app/src/components/CodeMultiselect.tsx
+++ /dev/null
@@ -1,276 +0,0 @@
-import React, { useEffect, useState } from "react";
-import styled from "styled-components";
-import { Button, buttonColor, defaultBorderRadius, secondaryDark } from ".";
-import { useSelector } from "react-redux";
-import {
- selectDebugContext,
- selectAllRangesInFiles,
- selectRangesMask,
-} from "../redux/selectors/debugContextSelectors";
-import "../highlight/dark.min.css";
-import hljs from "highlight.js";
-import { postVscMessage } from "../vscode";
-import { RootStore } from "../redux/store";
-import { useDispatch } from "react-redux";
-import {
- addRangeInFile,
- deleteRangeInFileAt,
- toggleSelectionAt,
- updateFileSystem,
-} from "../redux/slices/debugContexSlice";
-import { RangeInFile } from "../../../src/client";
-import { readRangeInVirtualFileSystem } from "../util";
-
-//#region Styled Components
-
-const MultiSelectContainer = styled.div`
- border-radius: ${defaultBorderRadius};
- padding: 4px;
- display: flex;
- flex-direction: column;
- gap: 4px;
-`;
-
-const MultiSelectHeader = styled.div`
- display: flex;
- justify-content: space-between;
- align-items: left;
- border-bottom: 1px solid gray;
- padding-left: 4px;
- padding-right: 4px;
- & p {
- overflow-wrap: break-word;
- word-wrap: break-word;
- -ms-wrap-flow: break-word;
- overflow: hidden;
- }
-`;
-
-const MultiSelectOption = styled.div`
- border-radius: ${defaultBorderRadius};
- padding-top: 4px;
- cursor: pointer;
- background-color: ${secondaryDark};
-`;
-
-const DeleteSelectedRangeButton = styled(Button)`
- align-self: right;
- padding: 0px;
- margin-top: 0;
- aspect-ratio: 1/1;
- height: 28px;
-`;
-
-const ToggleHighlightButton = styled(Button)`
- display: grid;
- justify-content: center;
- align-items: center;
- grid-template-columns: 30px 1fr;
- margin-left: 20px;
- order: 1;
- width: fit-content;
-`;
-
-//#endregion
-
-//#region Path Formatting
-
-const filenameToLanguageMap: any = {
- py: "python",
- js: "javascript",
- ts: "typescript",
- html: "html",
- css: "css",
- java: "java",
- c: "c",
- cpp: "cpp",
- cs: "csharp",
- go: "go",
- rb: "ruby",
- rs: "rust",
- swift: "swift",
- php: "php",
- scala: "scala",
- kt: "kotlin",
- dart: "dart",
- hs: "haskell",
- lua: "lua",
- pl: "perl",
- r: "r",
- sql: "sql",
- vb: "vb",
- xml: "xml",
- yaml: "yaml",
-};
-
-function filenameToLanguage(filename: string): string {
- const extension = filename.split(".").pop();
- if (extension === undefined) {
- return "";
- }
- return filenameToLanguageMap[extension] || "";
-}
-
-function formatPathRelativeToWorkspace(
- path: string,
- workspacePath: string | undefined
-) {
- if (workspacePath === undefined) {
- return path;
- }
- if (path.startsWith(workspacePath)) {
- return path.substring(workspacePath.length + 1);
- } else {
- return path;
- }
-}
-
-function formatFileRange(
- rangeInFile: RangeInFile,
- workspacePath: string | undefined
-) {
- return `${formatPathRelativeToWorkspace(
- rangeInFile.filepath,
- workspacePath
- )} (lines ${rangeInFile.range.start.line + 1}-${
- rangeInFile.range.end.line + 1
- })`;
- // +1 because VS Code Ranges are 0-indexed
-}
-
-//#endregion
-
-function CodeMultiselect(props: {}) {
- // State
- const [highlightLocked, setHighlightLocked] = useState(true);
-
- // Redux
- const dispatch = useDispatch();
- const workspacePath = useSelector(
- (state: RootStore) => state.config.workspacePath
- );
- const debugContext = useSelector(selectDebugContext);
- const rangesInFiles = useSelector(selectAllRangesInFiles);
- const rangesInFilesMask = useSelector(selectRangesMask);
-
- useEffect(() => {
- let eventListener = (event: any) => {
- switch (event.data.type) {
- case "highlightedCode":
- if (!highlightLocked) {
- dispatch(
- addRangeInFile({
- rangeInFile: event.data.rangeInFile,
- canUpdateLast: true,
- })
- );
- dispatch(updateFileSystem(event.data.filesystem));
- }
- break;
- case "findSuspiciousCode":
- for (let c of event.data.codeLocations) {
- dispatch(addRangeInFile({ rangeInFile: c, canUpdateLast: false }));
- }
- dispatch(updateFileSystem(event.data.filesystem));
- postVscMessage("listTenThings", { debugContext });
- break;
- }
- };
- window.addEventListener("message", eventListener);
- return () => window.removeEventListener("message", eventListener);
- }, [debugContext, highlightLocked]);
-
- useEffect(() => {
- hljs.highlightAll();
- }, [rangesInFiles]);
-
- return (
- <MultiSelectContainer>
- {rangesInFiles.map((range: RangeInFile, index: number) => {
- return (
- <MultiSelectOption
- key={index}
- style={{
- border: `1px solid ${
- rangesInFilesMask[index] ? buttonColor : "gray"
- }`,
- }}
- onClick={() => {
- dispatch(toggleSelectionAt(index));
- }}
- >
- <MultiSelectHeader>
- <p style={{ margin: "4px" }}>
- {formatFileRange(range, workspacePath)}
- </p>
- <DeleteSelectedRangeButton
- onClick={() => dispatch(deleteRangeInFileAt(index))}
- >
- x
- </DeleteSelectedRangeButton>
- </MultiSelectHeader>
- <pre>
- <code
- className={"language-" + filenameToLanguage(range.filepath)}
- >
- {readRangeInVirtualFileSystem(range, debugContext.filesystem)}
- </code>
- </pre>
- </MultiSelectOption>
- );
- })}
- {rangesInFiles.length === 0 && (
- <>
- <p>Highlight relevant code in the editor.</p>
- </>
- )}
- <ToggleHighlightButton
- onClick={() => {
- setHighlightLocked(!highlightLocked);
- }}
- >
- {highlightLocked ? (
- <>
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="20px"
- fill="none"
- viewBox="0 0 24 24"
- strokeWidth="1.5"
- stroke="currentColor"
- className="w-6 h-6"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"
- />
- </svg>{" "}
- Enable Highlight
- </>
- ) : (
- <>
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="20px"
- fill="none"
- viewBox="0 0 24 24"
- strokeWidth="1.5"
- stroke="currentColor"
- className="w-6 h-6"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="M13.5 10.5V6.75a4.5 4.5 0 119 0v3.75M3.75 21.75h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H3.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"
- />
- </svg>{" "}
- Disable Highlight
- </>
- )}
- </ToggleHighlightButton>
- </MultiSelectContainer>
- );
-}
-
-export default CodeMultiselect;
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index 801c3a03..f11e07af 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -1,30 +1,19 @@
-import React, {
- useCallback,
- useEffect,
- useImperativeHandle,
- useState,
-} from "react";
+import React, { useEffect, useImperativeHandle, useState } from "react";
import { useCombobox } from "downshift";
import styled from "styled-components";
import {
- buttonColor,
defaultBorderRadius,
lightGray,
secondaryDark,
vscBackground,
} from ".";
import CodeBlock from "./CodeBlock";
-import { RangeInFile } from "../../../src/client";
import PillButton from "./PillButton";
import HeaderButtonWithText from "./HeaderButtonWithText";
-import {
- Trash,
- LockClosed,
- LockOpen,
- Plus,
- DocumentPlus,
-} from "@styled-icons/heroicons-outline";
+import { DocumentPlus } from "@styled-icons/heroicons-outline";
import { HighlightedRangeContext } from "../../../schema/FullState";
+import { postVscMessage } from "../vscode";
+import { getMetaKeyLabel } from "../util";
// #region styled components
const mainInputFontSize = 13;
@@ -180,6 +169,27 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
useImperativeHandle(ref, () => downshiftProps, [downshiftProps]);
+ const [metaKeyPressed, setMetaKeyPressed] = useState(false);
+ const [focused, setFocused] = 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;
@@ -221,6 +231,11 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
)} */}
{highlightedCodeSections.map((section, idx) => (
<PillButton
+ warning={
+ section.range.contents.length > 4000 && section.editing
+ ? "Editing such a large range may be slow"
+ : undefined
+ }
editing={section.editing}
pinned={section.pinned}
index={idx}
@@ -272,7 +287,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
<div className="flex px-2" ref={divRef} hidden={!downshiftProps.isOpen}>
<MainTextInput
disabled={props.disabled}
- placeholder="Ask a question, give instructions, or type '/' to see slash commands"
+ placeholder={`Ask a question, give instructions, or type '/' to see slash commands. ${getMetaKeyLabel()}⏎ to edit.`}
{...getInputProps({
onChange: (e) => {
const target = e.target as HTMLTextAreaElement;
@@ -285,6 +300,13 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
// setShowContextDropdown(target.value.endsWith("@"));
},
+ onFocus: (e) => {
+ setFocused(true);
+ },
+ onBlur: (e) => {
+ setFocused(false);
+ postVscMessage("blurContinueInput", {});
+ },
onKeyDown: (event) => {
if (event.key === "Enter" && event.shiftKey) {
// Prevent Downshift's default 'Enter' behavior.
@@ -311,7 +333,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
) {
(event.nativeEvent as any).preventDownshiftDefault = true;
} else if (event.key === "ArrowUp") {
- console.log("OWJFOIJO");
if (positionInHistory == 0) return;
else if (
positionInHistory == history.length &&
@@ -357,10 +378,15 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
))}
</Ul>
</div>
- {/* <span className="text-trueGray-400 ml-auto m-auto text-xs text-right">
- Highlight code to include as context. Currently open file included by
- default. {highlightedCodeSections.length === 0 && ""}
- </span> */}
+ {highlightedCodeSections.length === 0 &&
+ (downshiftProps.inputValue?.startsWith("/edit") ||
+ (focused &&
+ metaKeyPressed &&
+ downshiftProps.inputValue?.length > 0)) && (
+ <div className="text-trueGray-400 pr-4 text-xs text-right">
+ Inserting at cursor
+ </div>
+ )}
<ContextDropdown
onMouseEnter={() => {
setHoveringContextDropdown(true);
diff --git a/extension/react-app/src/components/LoadingCover.tsx b/extension/react-app/src/components/LoadingCover.tsx
deleted file mode 100644
index a0f8f7a2..00000000
--- a/extension/react-app/src/components/LoadingCover.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import React from "react";
-import styled from "styled-components";
-
-const StyledDiv = styled.div`
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100vh;
- background: linear-gradient(
- 101.79deg,
- #12887a 0%,
- #87245c 32%,
- #e12637 63%,
- #ffb215 100%
- );
- display: flex;
- justify-content: center;
- align-items: center;
- flex-direction: column;
- z-index: 10;
-`;
-
-const StyledImg = styled.img`
- /* add your styles here */
-`;
-
-const StyledDiv2 = styled.div`
- width: 50%;
- height: 5px;
- background: white;
- margin-top: 20px;
-`;
-
-interface LoadingCoverProps {
- message: string;
- hidden?: boolean;
-}
-
-const LoadingCover = (props: LoadingCoverProps) => {
- return (
- <StyledDiv style={{ display: props.hidden ? "none" : "inherit" }}>
- <StyledImg src="continue.gif" alt="centered image" width="50%" />
- <StyledDiv2></StyledDiv2>
- <p>{props.message}</p>
- </StyledDiv>
- );
-};
-
-export default LoadingCover;
diff --git a/extension/react-app/src/components/Onboarding.tsx b/extension/react-app/src/components/Onboarding.tsx
new file mode 100644
index 00000000..231c1e93
--- /dev/null
+++ b/extension/react-app/src/components/Onboarding.tsx
@@ -0,0 +1,136 @@
+import { useSelector } from "react-redux";
+import { RootStore } from "../redux/store";
+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;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: #1e1e1e;
+ z-index: 200;
+`;
+
+const StyledSpan = styled.span`
+ padding: 8px;
+ border-radius: ${defaultBorderRadius};
+ &:hover {
+ background-color: #ffffff33;
+ }
+ white-space: nowrap;
+`;
+
+const Onboarding = () => {
+ const [counter, setCounter] = useState(4);
+ const gifs = ["intro", "highlight", "question", "help"];
+ const topMessages = [
+ "Welcome!",
+ "Highlight code",
+ "Ask a question",
+ "Use /help to learn more",
+ ];
+
+ useEffect(() => {
+ const hasVisited = localStorage.getItem("hasVisited");
+ if (hasVisited) {
+ setCounter(4);
+ } else {
+ setCounter(0);
+ localStorage.setItem("hasVisited", "true");
+ }
+ }, []);
+
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ setLoading(true);
+ }, [counter]);
+
+ return (
+ <StyledDiv hidden={counter >= 4}>
+ <div
+ style={{
+ display: "grid",
+ justifyContent: "center",
+ alignItems: "center",
+ height: "100%",
+ textAlign: "center",
+ background: `linear-gradient(
+ 101.79deg,
+ #12887a66 0%,
+ #87245c66 32%,
+ #e1263766 63%,
+ #ffb21566 100%
+ )`,
+ paddingLeft: "16px",
+ paddingRight: "16px",
+ }}
+ >
+ <h1>{topMessages[counter]}</h1>
+ <div style={{ display: "flex", justifyContent: "center" }}>
+ {loading && (
+ <div style={{ margin: "auto", position: "absolute", zIndex: 0 }}>
+ <Loader />
+ </div>
+ )}
+ {counter % 2 === 0 ? (
+ <img
+ src={`https://github.com/continuedev/continue/blob/main/media/${gifs[counter]}.gif?raw=true`}
+ width="100%"
+ key={"even-gif"}
+ alt={topMessages[counter]}
+ onLoad={() => {
+ setLoading(false);
+ }}
+ style={{ zIndex: 1 }}
+ />
+ ) : (
+ <img
+ src={`https://github.com/continuedev/continue/blob/main/media/${gifs[counter]}.gif?raw=true`}
+ width="100%"
+ key={"odd-gif"}
+ alt={topMessages[counter]}
+ onLoad={() => {
+ setLoading(false);
+ }}
+ style={{ zIndex: 1 }}
+ />
+ )}
+ </div>
+ <p
+ style={{
+ paddingLeft: "50px",
+ paddingRight: "50px",
+ paddingBottom: "50px",
+ textAlign: "center",
+ cursor: "pointer",
+ whiteSpace: "nowrap",
+ }}
+ >
+ <StyledSpan
+ hidden={counter === 0}
+ onClick={() => setCounter((prev) => Math.max(prev - 1, 0))}
+ >
+ <ArrowLeft width="18px" strokeWidth="2px" /> Previous
+ </StyledSpan>
+ <span hidden={counter === 0}>{" | "}</span>
+ <StyledSpan onClick={() => setCounter((prev) => prev + 1)}>
+ {counter === 0
+ ? "Click to learn how to use Continue"
+ : counter === 3
+ ? "Get Started"
+ : "Next"}{" "}
+ <ArrowRight width="18px" strokeWidth="2px" />
+ </StyledSpan>
+ </p>
+ </div>
+ </StyledDiv>
+ );
+};
+
+export default Onboarding;
diff --git a/extension/react-app/src/components/PillButton.tsx b/extension/react-app/src/components/PillButton.tsx
index 31d98c0f..d9d779d1 100644
--- a/extension/react-app/src/components/PillButton.tsx
+++ b/extension/react-app/src/components/PillButton.tsx
@@ -1,12 +1,11 @@
import { useContext, useState } from "react";
import styled from "styled-components";
+import { StyledTooltip, defaultBorderRadius, secondaryDark } from ".";
import {
- StyledTooltip,
- defaultBorderRadius,
- lightGray,
- secondaryDark,
-} from ".";
-import { Trash, PaintBrush, MapPin } from "@styled-icons/heroicons-outline";
+ Trash,
+ PaintBrush,
+ ExclamationTriangle,
+} from "@styled-icons/heroicons-outline";
import { GUIClientContext } from "../App";
const Button = styled.button`
@@ -31,7 +30,6 @@ const GridDiv = styled.div`
grid-template-columns: 1fr 1fr;
align-items: center;
border-radius: ${defaultBorderRadius};
- overflow: hidden;
background-color: ${secondaryDark};
`;
@@ -48,6 +46,21 @@ const ButtonDiv = styled.div<{ backgroundColor: string }>`
}
`;
+const CircleDiv = styled.div`
+ position: absolute;
+ top: -10px;
+ right: -10px;
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ background-color: red;
+ color: white;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 2px;
+`;
+
interface PillButtonProps {
onHover?: (arg0: boolean) => void;
onDelete?: () => void;
@@ -55,6 +68,7 @@ interface PillButtonProps {
index: number;
editing: boolean;
pinned: boolean;
+ warning?: string;
}
const PillButton = (props: PillButtonProps) => {
@@ -63,75 +77,96 @@ const PillButton = (props: PillButtonProps) => {
return (
<>
- <Button
- style={{
- position: "relative",
- borderColor: props.editing
- ? "#8800aa"
- : props.pinned
- ? "#ffff0099"
- : "transparent",
- borderWidth: "1px",
- borderStyle: "solid",
- }}
- onMouseEnter={() => {
- setIsHovered(true);
- if (props.onHover) {
- props.onHover(true);
- }
- }}
- onMouseLeave={() => {
- setIsHovered(false);
- if (props.onHover) {
- props.onHover(false);
- }
- }}
- >
- {isHovered && (
- <GridDiv>
- <ButtonDiv
- data-tooltip-id={`edit-${props.index}`}
- backgroundColor={"#8800aa55"}
- onClick={() => {
- client?.setEditingAtIndices([props.index]);
- }}
- >
- <PaintBrush style={{ margin: "auto" }} width="1.6em"></PaintBrush>
- </ButtonDiv>
+ <div style={{ position: "relative" }}>
+ <Button
+ style={{
+ position: "relative",
+ borderColor: props.warning
+ ? "red"
+ : props.editing
+ ? "#8800aa"
+ : props.pinned
+ ? "#ffff0099"
+ : "transparent",
+ borderWidth: "1px",
+ borderStyle: "solid",
+ }}
+ onMouseEnter={() => {
+ setIsHovered(true);
+ if (props.onHover) {
+ props.onHover(true);
+ }
+ }}
+ onMouseLeave={() => {
+ setIsHovered(false);
+ if (props.onHover) {
+ props.onHover(false);
+ }
+ }}
+ >
+ {isHovered && (
+ <GridDiv>
+ <ButtonDiv
+ data-tooltip-id={`edit-${props.index}`}
+ backgroundColor={"#8800aa55"}
+ onClick={() => {
+ client?.setEditingAtIndices([props.index]);
+ }}
+ >
+ <PaintBrush
+ style={{ margin: "auto" }}
+ width="1.6em"
+ ></PaintBrush>
+ </ButtonDiv>
- {/* <ButtonDiv
+ {/* <ButtonDiv
data-tooltip-id={`pin-${props.index}`}
backgroundColor={"#ffff0055"}
onClick={() => {
client?.setPinnedAtIndices([props.index]);
}}
- >
+ >
<MapPin style={{ margin: "auto" }} width="1.6em"></MapPin>
</ButtonDiv> */}
- <StyledTooltip id={`pin-${props.index}`}>
- Edit this range
+ <StyledTooltip id={`pin-${props.index}`}>
+ Edit this range
+ </StyledTooltip>
+ <ButtonDiv
+ data-tooltip-id={`delete-${props.index}`}
+ backgroundColor={"#cc000055"}
+ onClick={() => {
+ if (props.onDelete) {
+ props.onDelete();
+ }
+ }}
+ >
+ <Trash style={{ margin: "auto" }} width="1.6em"></Trash>
+ </ButtonDiv>
+ </GridDiv>
+ )}
+ {props.title}
+ </Button>
+ <StyledTooltip id={`edit-${props.index}`}>
+ {props.editing
+ ? "Editing this range (with rest of file as context)"
+ : "Edit this range"}
+ </StyledTooltip>
+ <StyledTooltip id={`delete-${props.index}`}>Delete</StyledTooltip>
+ {props.warning && (
+ <>
+ <CircleDiv data-tooltip-id={`circle-div-${props.title}`}>
+ <ExclamationTriangle
+ style={{ margin: "auto" }}
+ width="1.0em"
+ strokeWidth={2}
+ />
+ </CircleDiv>
+ <StyledTooltip id={`circle-div-${props.title}`}>
+ {props.warning}
</StyledTooltip>
- <ButtonDiv
- data-tooltip-id={`delete-${props.index}`}
- backgroundColor={"#cc000055"}
- onClick={() => {
- if (props.onDelete) {
- props.onDelete();
- }
- }}
- >
- <Trash style={{ margin: "auto" }} width="1.6em"></Trash>
- </ButtonDiv>
- </GridDiv>
+ </>
)}
- {props.title}
- </Button>
- <StyledTooltip id={`edit-${props.index}`}>
- {props.editing
- ? "Editing this range (with rest of file as context)"
- : "Edit this range"}
- </StyledTooltip>
- <StyledTooltip id={`delete-${props.index}`}>Delete</StyledTooltip>
+ </div>
</>
);
};
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index d480c565..93bdbc89 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useRef, useState } from "react";
+import { useEffect, useRef, useState } from "react";
import styled, { keyframes } from "styled-components";
import {
appear,
@@ -15,9 +15,9 @@ import {
} from "@styled-icons/heroicons-outline";
import { StopCircle } from "@styled-icons/heroicons-solid";
import { HistoryNode } from "../../../schema/HistoryNode";
-import ReactMarkdown from "react-markdown";
import HeaderButtonWithText from "./HeaderButtonWithText";
-import CodeBlock from "./CodeBlock";
+import MarkdownPreview from "@uiw/react-markdown-preview";
+import { getMetaKeyLabel, isMetaEquivalentKeyPressed } from "../util";
interface StepContainerProps {
historyNode: HistoryNode;
@@ -72,19 +72,6 @@ const ContentDiv = styled.div<{ isUserInput: boolean }>`
font-size: 13px;
`;
-const MarkdownPre = styled.pre`
- background-color: ${secondaryDark};
- padding: 10px;
- border-radius: ${defaultBorderRadius};
- border: 0.5px solid white;
-`;
-
-const StyledCode = styled.code`
- word-wrap: break-word;
- color: #f69292;
- background: transparent;
-`;
-
const gradient = keyframes`
0% {
background-position: 0px 0;
@@ -124,6 +111,31 @@ const GradientBorder = styled.div<{
background-size: 200% 200%;
`;
+const StyledMarkdownPreview = styled(MarkdownPreview)`
+ pre {
+ background-color: ${secondaryDark};
+ padding: 1px;
+ border-radius: ${defaultBorderRadius};
+ border: 0.5px solid white;
+ }
+
+ code {
+ color: #f69292;
+ word-wrap: break-word;
+ }
+
+ pre > code {
+ background-color: ${secondaryDark};
+ color: white;
+ }
+
+ background-color: ${vscBackground};
+ font-family: "Lexend", sans-serif;
+ font-size: 13px;
+ padding: 8px;
+ color: white;
+`;
+
// #endregion
function StepContainer(props: StepContainerProps) {
@@ -158,7 +170,7 @@ function StepContainer(props: StepContainerProps) {
>
<StepContainerDiv open={props.open}>
<GradientBorder
- loading={props.historyNode.active as boolean | false}
+ loading={(props.historyNode.active as boolean) || false}
isFirst={props.isFirst}
isLast={props.isLast}
borderColor={
@@ -170,7 +182,7 @@ function StepContainer(props: StepContainerProps) {
}
className="overflow-hidden cursor-pointer"
onClick={(e) => {
- if (e.metaKey) {
+ if (isMetaEquivalentKeyPressed(e)) {
props.onToggleAll();
} else {
props.onToggle();
@@ -178,7 +190,7 @@ function StepContainer(props: StepContainerProps) {
}}
>
<HeaderDiv
- loading={props.historyNode.active as boolean | false}
+ loading={(props.historyNode.active as boolean) || false}
error={props.historyNode.observation?.error ? true : false}
>
<div className="m-2">
@@ -206,7 +218,11 @@ function StepContainer(props: StepContainerProps) {
e.stopPropagation();
props.onDelete();
}}
- text={props.historyNode.active ? "Stop (⌘⌫)" : "Delete"}
+ text={
+ props.historyNode.active
+ ? `Stop (${getMetaKeyLabel()}⌫)`
+ : "Delete"
+ }
>
{props.historyNode.active ? (
<StopCircle size="1.6em" onClick={props.onDelete} />
@@ -242,31 +258,16 @@ function StepContainer(props: StepContainerProps) {
)}
{props.historyNode.observation?.error ? (
- <pre className="overflow-x-scroll">
- {props.historyNode.observation.error as string}
- </pre>
+ <details>
+ <summary>View Traceback</summary>
+ <pre className="overflow-x-scroll">
+ {props.historyNode.observation.error as string}
+ </pre>
+ </details>
) : (
- <ReactMarkdown
- key={1}
- className="overflow-x-scroll"
- components={{
- pre: ({ node, ...props }) => {
- return (
- <CodeBlock
- children={(props.children[0] as any).props.children[0]}
- />
- );
- },
- code: ({ node, ...props }) => {
- return <StyledCode children={props.children[0] as any} />;
- },
- ul: ({ node, ...props }) => {
- return <ul className="ml-0" {...props} />;
- },
- }}
- >
- {props.historyNode.step.description as any}
- </ReactMarkdown>
+ <StyledMarkdownPreview
+ source={props.historyNode.step.description || ""}
+ />
)}
</ContentDiv>
</StepContainerDiv>
diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx
index ea5727f0..646d6846 100644
--- a/extension/react-app/src/components/TextDialog.tsx
+++ b/extension/react-app/src/components/TextDialog.tsx
@@ -2,6 +2,7 @@
import React, { useEffect, useState } from "react";
import styled from "styled-components";
import { Button, buttonColor, secondaryDark, vscBackground } from ".";
+import { isMetaEquivalentKeyPressed } from "../util";
const ScreenCover = styled.div`
position: absolute;
@@ -81,7 +82,11 @@ const TextDialog = (props: {
rows={10}
ref={textAreaRef}
onKeyDown={(e) => {
- if (e.key === "Enter" && e.metaKey && textAreaRef.current) {
+ if (
+ e.key === "Enter" &&
+ isMetaEquivalentKeyPressed(e) &&
+ textAreaRef.current
+ ) {
props.onEnter(textAreaRef.current.value);
setText("");
} else if (e.key === "Escape") {
diff --git a/extension/react-app/src/highlight/dark.min.css b/extension/react-app/src/highlight/dark.min.css
deleted file mode 100644
index 9268d7c9..00000000
--- a/extension/react-app/src/highlight/dark.min.css
+++ /dev/null
@@ -1,53 +0,0 @@
-pre code.hljs {
- display: block;
- overflow-x: auto;
- padding: 1em;
-}
-code.hljs {
- padding: 3px 5px;
-}
-.hljs {
- color: #ddd;
- background: #252526;
-}
-.hljs-keyword,
-.hljs-link,
-.hljs-literal,
-.hljs-section,
-.hljs-selector-tag {
- color: #fff;
-}
-.hljs-addition,
-.hljs-attribute,
-.hljs-built_in,
-.hljs-bullet,
-.hljs-name,
-.hljs-string,
-.hljs-symbol,
-.hljs-template-tag,
-.hljs-template-variable,
-.hljs-title,
-.hljs-type,
-.hljs-variable {
- color: #d88;
-}
-.hljs-comment,
-.hljs-deletion,
-.hljs-meta,
-.hljs-quote {
- color: #979797;
-}
-.hljs-doctag,
-.hljs-keyword,
-.hljs-literal,
-.hljs-name,
-.hljs-section,
-.hljs-selector-tag,
-.hljs-strong,
-.hljs-title,
-.hljs-type {
- font-weight: 700;
-}
-.hljs-emphasis {
- font-style: italic;
-}
diff --git a/extension/react-app/src/hooks/messenger.ts b/extension/react-app/src/hooks/messenger.ts
index e2a0bab8..00ce1fbb 100644
--- a/extension/react-app/src/hooks/messenger.ts
+++ b/extension/react-app/src/hooks/messenger.ts
@@ -1,6 +1,3 @@
-// console.log("Websocket import");
-// const WebSocket = require("ws");
-
export abstract class Messenger {
abstract send(messageType: string, data: object): void;
@@ -28,13 +25,6 @@ export class WebsocketMessenger extends Messenger {
private serverUrl: string;
_newWebsocket(): WebSocket {
- // // Dynamic import, because WebSocket is builtin with browser, but not with node. And can't use require in browser.
- // if (typeof process === "object") {
- // console.log("Using node");
- // // process is only available in Node
- // var WebSocket = require("ws");
- // }
-
const newWebsocket = new WebSocket(this.serverUrl);
for (const listener of this.onOpenListeners) {
this.onOpen(listener);
diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/pages/gui.tsx
index e1ecec9e..64207487 100644
--- a/extension/react-app/src/tabs/gui.tsx
+++ b/extension/react-app/src/pages/gui.tsx
@@ -20,9 +20,10 @@ import ReactSwitch from "react-switch";
import { usePostHog } from "posthog-js/react";
import { useSelector } from "react-redux";
import { RootStore } from "../redux/store";
-import LoadingCover from "../components/LoadingCover";
import { postVscMessage } from "../vscode";
import UserInputContainer from "../components/UserInputContainer";
+import Onboarding from "../components/Onboarding";
+import { isMetaEquivalentKeyPressed } from "../util";
const TopGUIDiv = styled.div`
overflow: hidden;
@@ -95,11 +96,8 @@ function GUI(props: GUIProps) {
name: "Welcome to Continue",
hide: false,
description: `- Highlight code and ask a question or give instructions
-- Use \`cmd+k\` (Mac) / \`ctrl+k\` (Windows) to open Continue
-- Use \`cmd+shift+e\` / \`ctrl+shift+e\` to open file Explorer
-- Add your own OpenAI API key to VS Code Settings with \`cmd+,\`
-- Use slash commands when you want fine-grained control
-- Past steps are included as part of the context by default`,
+ - Use \`cmd+m\` (Mac) / \`ctrl+m\` (Windows) to open Continue
+ - Use \`/help\` to ask questions about how to use Continue`,
system_message: null,
chat_context: [],
manage_own_chat_context: false,
@@ -140,13 +138,14 @@ function GUI(props: GUIProps) {
useEffect(() => {
const listener = (e: any) => {
// Cmd + i to toggle fast model
- if (e.key === "i" && e.metaKey && e.shiftKey) {
+ if (e.key === "i" && isMetaEquivalentKeyPressed(e) && e.shiftKey) {
setUsingFastModel((prev) => !prev);
// Cmd + backspace to stop currently running step
} else if (
e.key === "Backspace" &&
- e.metaKey &&
- typeof history?.current_index !== "undefined"
+ isMetaEquivalentKeyPressed(e) &&
+ typeof history?.current_index !== "undefined" &&
+ history.timeline[history.current_index]?.active
) {
client?.deleteAtIndex(history.current_index);
}
@@ -169,6 +168,7 @@ function GUI(props: GUIProps) {
const waitingForSteps =
state.active &&
state.history.current_index < state.history.timeline.length &&
+ state.history.timeline[state.history.current_index] &&
state.history.timeline[
state.history.current_index
].step.description?.trim() === "";
@@ -221,7 +221,7 @@ function GUI(props: GUIProps) {
if (mainTextInputRef.current) {
let input = (mainTextInputRef.current as any).inputValue;
// cmd+enter to /edit
- if (event?.metaKey) {
+ if (isMetaEquivalentKeyPressed(event)) {
input = `/edit ${input}`;
}
(mainTextInputRef.current as any).setInputValue("");
@@ -235,14 +235,14 @@ function GUI(props: GUIProps) {
history.current_index < history.timeline.length
) {
if (
- history.timeline[history.current_index].step.name ===
+ history.timeline[history.current_index]?.step.name ===
"Waiting for user input"
) {
if (input.trim() === "") return;
onStepUserInput(input, history!.current_index);
return;
} else if (
- history.timeline[history.current_index].step.name ===
+ history.timeline[history.current_index]?.step.name ===
"Waiting for user confirmation"
) {
onStepUserInput("ok", history!.current_index);
@@ -260,14 +260,13 @@ function GUI(props: GUIProps) {
const onStepUserInput = (input: string, index: number) => {
if (!client) return;
- console.log("Sending step user input", input, index);
client.sendStepUserInput(input, index);
};
// const iterations = useSelector(selectIterations);
return (
<>
- <LoadingCover hidden={true} message="Downloading local model..." />
+ <Onboarding />
<TextDialog
showDialog={showFeedbackDialog}
onEnter={(text) => {
@@ -278,7 +277,7 @@ function GUI(props: GUIProps) {
setShowFeedbackDialog(false);
}}
message={feedbackDialogMessage}
- ></TextDialog>
+ />
<TopGUIDiv
ref={topGuiDivRef}
@@ -348,12 +347,6 @@ function GUI(props: GUIProps) {
</div>
<ComboBox
- // disabled={
- // history?.timeline.length
- // ? history.timeline[history.current_index].step.name ===
- // "Waiting for user confirmation"
- // : false
- // }
ref={mainTextInputRef}
onEnter={(e) => {
onMainTextInput(e);
@@ -438,7 +431,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/react-app/src/tabs/additionalContext.tsx b/extension/react-app/src/tabs/additionalContext.tsx
deleted file mode 100644
index 98fce9f1..00000000
--- a/extension/react-app/src/tabs/additionalContext.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from "react";
-import { H3, TextArea } from "../components";
-
-function AdditionalContextTab() {
- return (
- <div className="mx-5">
- <H3>Additional Context</H3>
- <TextArea
- rows={8}
- placeholder="Copy and paste information related to the bug from GitHub Issues, Slack threads, or other notes here."
- className="additionalContextTextarea"
- ></TextArea>
- <br></br>
- </div>
- );
-}
-
-export default AdditionalContextTab;
diff --git a/extension/react-app/src/tabs/chat/MessageDiv.tsx b/extension/react-app/src/tabs/chat/MessageDiv.tsx
deleted file mode 100644
index 3543dd93..00000000
--- a/extension/react-app/src/tabs/chat/MessageDiv.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import React, { useEffect } from "react";
-import { ChatMessage } from "../../redux/store";
-import styled from "styled-components";
-import {
- buttonColor,
- defaultBorderRadius,
- secondaryDark,
-} from "../../components";
-import VSCodeFileLink from "../../components/VSCodeFileLink";
-import ReactMarkdown from "react-markdown";
-import "../../highlight/dark.min.css";
-import hljs from "highlight.js";
-import { useSelector } from "react-redux";
-import { selectIsStreaming } from "../../redux/selectors/chatSelectors";
-
-const Container = styled.div`
- padding-left: 8px;
- padding-right: 8px;
- border-radius: 8px;
- margin: 3px;
- width: fit-content;
- max-width: 75%;
- overflow-y: scroll;
- word-wrap: break-word;
- -ms-word-wrap: break-word;
- height: fit-content;
- overflow: hidden;
- background-color: ${(props) => {
- if (props.role === "user") {
- return buttonColor;
- } else {
- return secondaryDark;
- }
- }};
- float: ${(props) => {
- if (props.role === "user") {
- return "right";
- } else {
- return "left";
- }
- }};
- display: block;
-
- & pre {
- border: 1px solid gray;
- border-radius: ${defaultBorderRadius};
- }
-`;
-
-function MessageDiv(props: ChatMessage) {
- const [richContent, setRichContent] = React.useState<JSX.Element[]>([]);
- const isStreaming = useSelector(selectIsStreaming);
-
- useEffect(() => {
- if (!isStreaming) {
- hljs.highlightAll();
- }
- }, [richContent, isStreaming]);
-
- useEffect(() => {
- setRichContent([
- <ReactMarkdown key={1} children={props.content}></ReactMarkdown>,
- ]);
- }, [props.content]);
-
- return (
- <>
- <div className="overflow-auto">
- <Container role={props.role}>{richContent}</Container>
- </div>
- </>
- );
-}
-
-export default MessageDiv;
diff --git a/extension/react-app/src/tabs/chat/index.tsx b/extension/react-app/src/tabs/chat/index.tsx
deleted file mode 100644
index a93ad4f9..00000000
--- a/extension/react-app/src/tabs/chat/index.tsx
+++ /dev/null
@@ -1,267 +0,0 @@
-import React, { useCallback, useEffect, useRef, useState } from "react";
-import { useDispatch, useSelector } from "react-redux";
-import { selectChatMessages } from "../../redux/selectors/chatSelectors";
-import MessageDiv from "./MessageDiv";
-import styled from "styled-components";
-import { addMessage, setIsStreaming } from "../../redux/slices/chatSlice";
-import { AnyAction, Dispatch } from "@reduxjs/toolkit";
-import { closeStream, streamUpdate } from "../../redux/slices/chatSlice";
-import { ChatMessage, RootStore } from "../../redux/store";
-import { postVscMessage, vscRequest } from "../../vscode";
-import { defaultBorderRadius, Loader } from "../../components";
-import { selectHighlightedCode } from "../../redux/selectors/miscSelectors";
-import { readRangeInVirtualFileSystem } from "../../util";
-import { selectDebugContext } from "../../redux/selectors/debugContextSelectors";
-
-let textEntryBarHeight = "30px";
-
-const ChatContainer = styled.div`
- display: grid;
- grid-template-rows: 1fr auto;
- height: 100%;
-`;
-
-const BottomDiv = styled.div`
- display: grid;
- grid-template-rows: auto auto;
-`;
-
-const BottomButton = styled.button(
- (props: { active: boolean }) => `
- font-size: 10px;
- border: none;
- color: white;
- margin-right: 4px;
- cursor: pointer;
- background-color: ${props.active ? "black" : "gray"};
- border-radius: ${defaultBorderRadius};
- padding: 8px;
-`
-);
-
-const TextEntryBar = styled.input`
- height: ${textEntryBarHeight};
- border-bottom-left-radius: ${defaultBorderRadius};
- border-bottom-right-radius: ${defaultBorderRadius};
- padding: 8px;
- border: 1px solid white;
- background-color: black;
- color: white;
- outline: none;
-`;
-
-function ChatTab() {
- const dispatch = useDispatch();
- const chatMessages = useSelector(selectChatMessages);
- const isStreaming = useSelector((state: RootStore) => state.chat.isStreaming);
- const baseUrl = useSelector((state: RootStore) => state.config.apiUrl);
- const debugContext = useSelector(selectDebugContext);
-
- const [includeHighlightedCode, setIncludeHighlightedCode] = useState(true);
- const [writeToEditor, setWriteToEditor] = useState(false);
- const [waitingForResponse, setWaitingForResponse] = useState(false);
-
- const highlightedCode = useSelector(selectHighlightedCode);
-
- const streamToStateThunk = useCallback(
- (dispatch: Dispatch<AnyAction>, getResponse: () => Promise<Response>) => {
- let streamToCursor = writeToEditor;
- getResponse().then((resp) => {
- setWaitingForResponse(false);
- if (resp.body) {
- resp.body.pipeTo(
- new WritableStream({
- write(chunk) {
- let update = new TextDecoder("utf-8").decode(chunk);
- dispatch(streamUpdate(update));
- if (streamToCursor) {
- postVscMessage("streamUpdate", { update });
- }
- },
- close() {
- dispatch(closeStream());
- if (streamToCursor) {
- postVscMessage("closeStream", null);
- }
- },
- })
- );
- }
- });
- },
- [writeToEditor]
- );
-
- const compileHiddenChatMessages = useCallback(async () => {
- let messages: ChatMessage[] = [];
- if (
- includeHighlightedCode &&
- highlightedCode?.filepath !== undefined &&
- highlightedCode?.range !== undefined &&
- debugContext.filesystem[highlightedCode.filepath] !== undefined
- ) {
- let fileContents = readRangeInVirtualFileSystem(
- highlightedCode,
- debugContext.filesystem
- );
- if (fileContents) {
- messages.push({
- role: "user",
- content: fileContents,
- });
- }
- } else {
- // Similarity search over workspace
- let data = await vscRequest("queryEmbeddings", {
- query: chatMessages[chatMessages.length - 1].content,
- });
- let codeContextMessages = data.results.map(
- (result: { id: string; document: string }) => {
- let msg: ChatMessage = {
- role: "user",
- content: `File: ${result.id} \n ${result.document}`,
- };
- return msg;
- }
- );
- codeContextMessages.push({
- role: "user",
- content:
- "Use the above code to help you answer the question below. Answer in asterisk bullet points, and give the full path whenever you reference files.",
- });
- messages.push(...codeContextMessages);
- }
-
- let systemMsgContent = writeToEditor
- ? "Respond only with the exact code requested, no additional text."
- : "Use the above code to help you answer the question below. Respond in markdown if using bullets or other special formatting, being sure to specify language for code blocks.";
-
- messages.push({
- role: "system",
- content: systemMsgContent,
- });
- return messages;
- }, [highlightedCode, chatMessages, includeHighlightedCode, writeToEditor]);
-
- useEffect(() => {
- if (
- chatMessages.length > 0 &&
- chatMessages[chatMessages.length - 1].role === "user" &&
- !isStreaming
- ) {
- dispatch(setIsStreaming(true));
- streamToStateThunk(dispatch, async () => {
- if (chatMessages.length === 0) {
- return new Promise((resolve, _) => resolve(new Response()));
- }
- let hiddenChatMessages = await compileHiddenChatMessages();
- let augmentedMessages = [
- ...chatMessages.slice(0, -1),
- ...hiddenChatMessages,
- chatMessages[chatMessages.length - 1],
- ];
- console.log(augmentedMessages);
- // The autogenerated client can't handle streams, so have to go raw
- return fetch(`${baseUrl}/chat/complete`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- messages: augmentedMessages,
- }),
- });
- });
- }
- }, [chatMessages, dispatch, isStreaming, highlightedCode]);
-
- const chatMessagesDiv = useRef<HTMLDivElement>(null);
- useEffect(() => {
- // Scroll to bottom
- let interval = setInterval(() => {
- if (chatMessagesDiv.current && !waitingForResponse) {
- chatMessagesDiv.current.scrollTop += Math.max(
- 4,
- 0.05 * chatMessagesDiv.current.scrollHeight -
- chatMessagesDiv.current.clientHeight -
- chatMessagesDiv.current.scrollTop
- );
- if (
- chatMessagesDiv.current.scrollTop >=
- chatMessagesDiv.current.scrollHeight -
- chatMessagesDiv.current.clientHeight
- ) {
- clearInterval(interval);
- }
- }
- }, 10);
- }, [chatMessages, chatMessagesDiv, waitingForResponse]);
-
- return (
- <ChatContainer>
- <div className="mx-5 overflow-y-scroll" ref={chatMessagesDiv}>
- <h1>Chat</h1>
- <hr></hr>
- <div>
- {chatMessages.length > 0 ? (
- chatMessages.map((message, idx) => {
- return <MessageDiv key={idx} {...message}></MessageDiv>;
- })
- ) : (
- <p className="text-gray-400 m-auto text-center">
- You can ask questions about your codebase or ask for code written
- directly in the editor.
- </p>
- )}
- {waitingForResponse && <Loader></Loader>}
- </div>
- </div>
-
- <BottomDiv>
- <div className="h-12 bg-secondary-">
- <div className="flex items-center p-2">
- {/* <p className="mr-auto text-xs">
- Highlighted code is automatically included in your chat message.
- </p> */}
- <BottomButton
- className="ml-auto"
- active={writeToEditor}
- onClick={() => {
- setWriteToEditor(!writeToEditor);
- }}
- >
- {writeToEditor ? "Writing to editor" : "Write to editor"}
- </BottomButton>
-
- <BottomButton
- active={includeHighlightedCode}
- onClick={() => {
- setIncludeHighlightedCode(!includeHighlightedCode);
- }}
- >
- {includeHighlightedCode
- ? "Including highlighted code"
- : "Automatically finding relevant code"}
- </BottomButton>
- </div>
- </div>
- <TextEntryBar
- type="text"
- placeholder="Enter your message here"
- onKeyDown={(e) => {
- if (e.key === "Enter" && e.currentTarget.value !== "") {
- console.log("Sending message", e.currentTarget.value);
- dispatch(
- addMessage({ content: e.currentTarget.value, role: "user" })
- );
- (e.target as any).value = "";
- setWaitingForResponse(true);
- }
- }}
- ></TextEntryBar>
- </BottomDiv>
- </ChatContainer>
- );
-}
-
-export default ChatTab;
diff --git a/extension/react-app/src/tabs/main.tsx b/extension/react-app/src/tabs/main.tsx
deleted file mode 100644
index a8b3300d..00000000
--- a/extension/react-app/src/tabs/main.tsx
+++ /dev/null
@@ -1,189 +0,0 @@
-import React, { useEffect, useState } from "react";
-import { H3, TextArea, Button, Pre, Loader } from "../components";
-import styled from "styled-components";
-import { postVscMessage, withProgress } from "../vscode";
-import { useDebugContextValue } from "../redux/hooks";
-import CodeMultiselect from "../components/CodeMultiselect";
-import { useSelector } from "react-redux";
-import { selectDebugContext } from "../redux/selectors/debugContextSelectors";
-import { useDispatch } from "react-redux";
-import { updateValue } from "../redux/slices/debugContexSlice";
-import { setWorkspacePath } from "../redux/slices/configSlice";
-import { SerializedDebugContext } from "../../../src/client";
-import { useEditCache } from "../util/editCache";
-import { useApi } from "../util/api";
-
-const ButtonDiv = styled.div`
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 4px;
- margin: 4px;
- flex-wrap: wrap;
-
- & button {
- flex-grow: 1;
- }
-`;
-
-function MainTab(props: any) {
- const dispatch = useDispatch();
-
- const [suggestion, setSuggestion] = useState("");
- const [traceback, setTraceback] = useDebugContextValue("traceback", "");
- const [selectedRanges, setSelectedRanges] = useDebugContextValue(
- "rangesInFiles",
- []
- );
-
- const editCache = useEditCache();
- const { debugApi } = useApi();
-
- const [responseLoading, setResponseLoading] = useState(false);
-
- let debugContext = useSelector(selectDebugContext);
-
- useEffect(() => {
- editCache.preloadEdit(debugContext);
- }, [debugContext]);
-
- function postVscMessageWithDebugContext(
- type: string,
- overrideDebugContext: SerializedDebugContext | null = null
- ) {
- postVscMessage(type, {
- debugContext: overrideDebugContext || debugContext,
- });
- }
-
- function launchFindSuspiciousCode(newTraceback: string) {
- // setTraceback's effects don't occur immediately, so we have to add it to the debug context manually
- let updatedDebugContext = {
- ...debugContext,
- traceback: newTraceback,
- };
- postVscMessageWithDebugContext("findSuspiciousCode", updatedDebugContext);
- postVscMessageWithDebugContext("preloadEdit", updatedDebugContext);
- }
-
- useEffect(() => {
- const eventListener = (event: any) => {
- switch (event.data.type) {
- case "suggestFix":
- case "explainCode":
- case "listTenThings":
- setSuggestion(event.data.value);
- setResponseLoading(false);
- break;
- case "traceback":
- setTraceback(event.data.value);
- launchFindSuspiciousCode(event.data.value);
- break;
- case "workspacePath":
- dispatch(setWorkspacePath(event.data.value));
- break;
- }
- };
- window.addEventListener("message", eventListener);
-
- return () => window.removeEventListener("message", eventListener);
- }, [debugContext, selectedRanges]);
-
- return (
- <div className="mx-5">
- <h1>Debug Panel</h1>
-
- <H3>Code Sections</H3>
- <CodeMultiselect></CodeMultiselect>
-
- <H3>Bug Description</H3>
- <TextArea
- id="bugDescription"
- name="bugDescription"
- className="bugDescription"
- rows={4}
- cols={50}
- placeholder="Describe your bug..."
- ></TextArea>
-
- <H3>Stack Trace</H3>
- <TextArea
- id="traceback"
- className="traceback"
- name="traceback"
- rows={4}
- cols={50}
- placeholder="Paste stack trace here"
- onChange={(e) => {
- setTraceback(e.target.value);
- dispatch(updateValue({ key: "traceback", value: e.target.value }));
- // postVscMessageWithDebugContext("findSuspiciousCode");
- }}
- onPaste={(e) => {
- let pasted = e.clipboardData.getData("text");
- console.log("PASTED", pasted);
- setTraceback(pasted);
- launchFindSuspiciousCode(pasted);
- }}
- value={traceback}
- ></TextArea>
-
- <select
- hidden
- id="relevantVars"
- className="relevantVars"
- name="relevantVars"
- ></select>
-
- <ButtonDiv>
- <Button
- onClick={() => {
- postVscMessageWithDebugContext("explainCode");
- setResponseLoading(true);
- }}
- >
- Explain Code
- </Button>
- <Button
- onClick={() => {
- postVscMessageWithDebugContext("suggestFix");
- setResponseLoading(true);
- }}
- >
- Generate Ideas
- </Button>
- <Button
- disabled={selectedRanges.length === 0}
- onClick={async () => {
- withProgress("Generating Fix", async () => {
- let edits = await editCache.getEdit(debugContext);
- postVscMessage("makeEdit", { edits });
- });
- }}
- >
- Suggest Fix
- </Button>
- <Button
- disabled={selectedRanges.length === 0}
- onClick={() => {
- postVscMessageWithDebugContext("generateUnitTest");
- }}
- >
- Create Test
- </Button>
- </ButtonDiv>
- <Loader hidden={!responseLoading}></Loader>
-
- <Pre
- className="fixSuggestion"
- hidden={!(typeof suggestion === "string" && suggestion.length > 0)}
- >
- {suggestion}
- </Pre>
-
- <br></br>
- </div>
- );
-}
-
-export default MainTab;
diff --git a/extension/react-app/src/tabs/welcome.tsx b/extension/react-app/src/tabs/welcome.tsx
deleted file mode 100644
index c29d260a..00000000
--- a/extension/react-app/src/tabs/welcome.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from "react";
-
-function WelcomeTab() {
- return (
- <div className="mx-5">
- <h1>Welcome to Continue</h1>
-
- <p>
- Learn more in the{" "}
- <a href="https://www.notion.so/continue-dev/Continue-User-Guide-1c6ad99887d0474d9e42206f6c98efa4">
- Continue User Guide
- </a>{" "}
- </p>
- <p>Send Nate or Ty your feedback:</p>
- <p>1. What excites you about Continue?</p>
- <p>2. What did you struggle with when using Continue?</p>
- <p>3. How do you wish Continue worked?</p>
- </div>
- );
-}
-
-export default WelcomeTab;
diff --git a/extension/react-app/src/util/api.ts b/extension/react-app/src/util/api.ts
deleted file mode 100644
index bdec1d20..00000000
--- a/extension/react-app/src/util/api.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import {
- Configuration,
- DebugApi,
- UnittestApi,
- ChatApi,
-} from "../../../src/client";
-import { useSelector } from "react-redux";
-import { useEffect, useState } from "react";
-import { RootStore } from "../redux/store";
-
-export function useApi() {
- const apiUrl = useSelector((state: RootStore) => state.config.apiUrl);
- const vscMachineId = useSelector(
- (state: RootStore) => state.config.vscMachineId
- );
- const [debugApi, setDebugApi] = useState<DebugApi>();
- const [unittestApi, setUnittestApi] = useState<UnittestApi>();
- const [chatApi, setChatApi] = useState<ChatApi>();
-
- useEffect(() => {
- if (apiUrl && vscMachineId) {
- let config = new Configuration({
- basePath: apiUrl,
- fetchApi: fetch,
- middleware: [
- {
- pre: async (context) => {
- context.init.headers = {
- ...context.init.headers,
- "x-vsc-machine-id": vscMachineId,
- };
- },
- },
- ],
- });
- setDebugApi(new DebugApi(config));
- setUnittestApi(new UnittestApi(config));
- setChatApi(new ChatApi(config));
- }
- }, [apiUrl, vscMachineId]);
-
- return { debugApi, unittestApi, chatApi };
-}
diff --git a/extension/react-app/src/util/editCache.ts b/extension/react-app/src/util/editCache.ts
deleted file mode 100644
index b8071127..00000000
--- a/extension/react-app/src/util/editCache.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import { useApi } from "../util/api";
-import { FileEdit, SerializedDebugContext } from "../../../src/client";
-import { useCallback, useEffect, useState } from "react";
-
-export function useEditCache() {
- const { debugApi } = useApi();
-
- const fetchNewEdit = useCallback(
- async (debugContext: SerializedDebugContext) => {
- return (
- await debugApi?.editEndpointDebugEditPost({
- serializedDebugContext: debugContext,
- })
- )?.completion;
- },
- [debugApi]
- );
-
- const [editCache, setEditCache] = useState(new EditCache(fetchNewEdit));
-
- useEffect(() => {
- setEditCache(new EditCache(fetchNewEdit));
- }, [fetchNewEdit]);
-
- return editCache;
-}
-
-/**
- * Stores preloaded edits, invalidating based off of debug context changes
- */
-class EditCache {
- private _lastDebugContext: SerializedDebugContext | undefined;
- private _cachedEdits: FileEdit[] | undefined;
- private _fetchNewEdit: (
- debugContext: SerializedDebugContext
- ) => Promise<FileEdit[] | undefined>;
- private _debounceTimer: NodeJS.Timeout | undefined;
-
- private _debugContextChanged(debugContext: SerializedDebugContext): boolean {
- if (!this._lastDebugContext) {
- return true;
- }
-
- return (
- JSON.stringify(this._lastDebugContext) !== JSON.stringify(debugContext)
- );
- }
-
- private _debugContextComplete(debugContext: SerializedDebugContext): boolean {
- return debugContext.rangesInFiles.length > 0;
- }
-
- public async preloadEdit(debugContext: SerializedDebugContext) {
- if (this._debounceTimer) {
- clearTimeout(this._debounceTimer);
- }
- if (
- this._debugContextComplete(debugContext) &&
- this._debugContextChanged(debugContext)
- ) {
- this._debounceTimer = setTimeout(async () => {
- console.log("Preloading edits");
- this._cachedEdits = await this._fetchNewEdit(debugContext);
- this._lastDebugContext = debugContext;
- }, 200);
- }
- }
-
- public async getEdit(
- debugContext: SerializedDebugContext
- ): Promise<FileEdit[]> {
- if (this._debugContextChanged(debugContext)) {
- console.log("Cache miss");
- this._cachedEdits = await this._fetchNewEdit(debugContext);
- } else {
- console.log("Cache hit");
- }
-
- return this._cachedEdits!;
- }
-
- constructor(
- fetchNewEdit: (
- debugContext: SerializedDebugContext
- ) => Promise<FileEdit[] | undefined>
- ) {
- this._fetchNewEdit = fetchNewEdit;
- }
-}
diff --git a/extension/react-app/src/util/index.ts b/extension/react-app/src/util/index.ts
index 458f9d95..c4168e13 100644
--- a/extension/react-app/src/util/index.ts
+++ b/extension/react-app/src/util/index.ts
@@ -1,27 +1,43 @@
-import { RangeInFile } from "../../../src/client";
+type Platform = "mac" | "linux" | "windows" | "unknown";
-export function readRangeInVirtualFileSystem(
- rangeInFile: RangeInFile,
- filesystem: { [filepath: string]: string }
-): string | undefined {
- const range = rangeInFile.range;
-
- let data = filesystem[rangeInFile.filepath];
- if (data === undefined) {
- console.log("File not found");
- return undefined;
+export function getPlatform(): Platform {
+ const platform = window.navigator.platform.toUpperCase();
+ if (platform.indexOf("MAC") >= 0) {
+ return "mac";
+ } else if (platform.indexOf("LINUX") >= 0) {
+ return "linux";
+ } else if (platform.indexOf("WIN") >= 0) {
+ return "windows";
} else {
- let lines = data.toString().split("\n");
- if (range.start.line === range.end.line) {
- return lines[rangeInFile.range.start.line].slice(
- rangeInFile.range.start.character,
- rangeInFile.range.end.character
- );
- } else {
- let firstLine = lines[range.start.line].slice(range.start.character);
- let lastLine = lines[range.end.line].slice(0, range.end.character);
- let middleLines = lines.slice(range.start.line + 1, range.end.line);
- return [firstLine, ...middleLines, lastLine].join("\n");
- }
+ return "unknown";
+ }
+}
+
+export function isMetaEquivalentKeyPressed(event: {
+ metaKey: boolean;
+ ctrlKey: boolean;
+}): boolean {
+ const platform = getPlatform();
+ switch (platform) {
+ case "mac":
+ return event.metaKey;
+ case "linux":
+ case "windows":
+ return event.ctrlKey;
+ default:
+ return event.metaKey;
+ }
+}
+
+export function getMetaKeyLabel(): string {
+ const platform = getPlatform();
+ switch (platform) {
+ case "mac":
+ return "⌘";
+ case "linux":
+ case "windows":
+ return "^";
+ default:
+ return "⌘";
}
}
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/activate.ts b/extension/src/activation/activate.ts
index 18650561..5c6ffa02 100644
--- a/extension/src/activation/activate.ts
+++ b/extension/src/activation/activate.ts
@@ -2,24 +2,41 @@ import * as vscode from "vscode";
import { registerAllCommands } from "../commands";
import { registerAllCodeLensProviders } from "../lang-server/codeLens";
import { sendTelemetryEvent, TelemetryEvent } from "../telemetry";
-// import { openCapturedTerminal } from "../terminal/terminalEmulator";
import IdeProtocolClient from "../continueIdeClient";
import { getContinueServerUrl } from "../bridge";
-import { CapturedTerminal } from "../terminal/terminalEmulator";
-import { setupDebugPanel, ContinueGUIWebviewViewProvider } from "../debugPanel";
-import { startContinuePythonServer } from "./environmentSetup";
+import { ContinueGUIWebviewViewProvider } from "../debugPanel";
+import {
+ getExtensionVersion,
+ startContinuePythonServer,
+} from "./environmentSetup";
+import fetch from "node-fetch";
+import registerQuickFixProvider from "../lang-server/codeActions";
// import { CapturedTerminal } from "../terminal/terminalEmulator";
+const PACKAGE_JSON_RAW_GITHUB_URL =
+ "https://raw.githubusercontent.com/continuedev/continue/HEAD/extension/package.json";
+
export let extensionContext: vscode.ExtensionContext | undefined = undefined;
export let ideProtocolClient: IdeProtocolClient;
-export async function activateExtension(
- context: vscode.ExtensionContext,
- showTutorial: boolean
-) {
+export async function activateExtension(context: vscode.ExtensionContext) {
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));
+
+ // Start the Python server
await new Promise((resolve, reject) => {
vscode.window.withProgress(
{
@@ -35,19 +52,20 @@ export async function activateExtension(
);
});
+ // Register commands and providers
sendTelemetryEvent(TelemetryEvent.ExtensionActivated);
registerAllCodeLensProviders(context);
registerAllCommands(context);
+ registerQuickFixProvider();
+ // Initialize IDE Protocol Client
const serverUrl = getContinueServerUrl();
-
ideProtocolClient = new IdeProtocolClient(
`${serverUrl.replace("http", "ws")}/ide/ws`,
context
);
- // Setup the left panel
- (async () => {
+ {
const sessionIdPromise = await ideProtocolClient.getSessionId();
const provider = new ContinueGUIWebviewViewProvider(sessionIdPromise);
@@ -60,10 +78,5 @@ export async function activateExtension(
}
)
);
- })();
- // All opened terminals should be replaced by our own terminal
- // vscode.window.onDidOpenTerminal((terminal) => {});
-
- // If any terminals are open to start, replace them
- // vscode.window.terminals.forEach((terminal) => {}
+ }
}
diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts
index 714080e3..69a3b75a 100644
--- a/extension/src/activation/environmentSetup.ts
+++ b/extension/src/activation/environmentSetup.ts
@@ -7,23 +7,56 @@ 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";
+const WINDOWS_REMOTE_SIGNED_SCRIPTS_ERROR =
+ "A Python virtual enviroment cannot be activated because running scripts is disabled for this user. In order to use Continue, please enable signed scripts to run with this command in PowerShell: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`, reload VS Code, and then try again.";
+
const MAX_RETRIES = 3;
async function retryThenFail(
fn: () => Promise<any>,
retries: number = MAX_RETRIES
): Promise<any> {
try {
+ if (retries < MAX_RETRIES && process.platform === "win32") {
+ let [stdout, stderr] = await runCommand("Get-ExecutionPolicy");
+ if (!stdout.includes("RemoteSigned")) {
+ [stdout, stderr] = await runCommand(
+ "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser"
+ );
+ console.log("Execution policy stdout: ", stdout);
+ console.log("Execution policy stderr: ", stderr);
+ }
+ }
+
return await fn();
} catch (e: any) {
if (retries > 0) {
return await retryThenFail(fn, retries - 1);
}
- vscode.window.showErrorMessage(
- "Failed to set up Continue extension. Please email nate@continue.dev and we'll get this fixed ASAP!"
- );
+
+ // Show corresponding error message depending on the platform
+ let msg =
+ "Failed to set up Continue extension. Please email nate@continue.dev and we'll get this fixed ASAP!";
+ try {
+ switch (process.platform) {
+ case "win32":
+ msg = WINDOWS_REMOTE_SIGNED_SCRIPTS_ERROR;
+ break;
+ case "darwin":
+ break;
+ case "linux":
+ const [pythonCmd] = await getPythonPipCommands();
+ msg = await getLinuxAptInstallError(pythonCmd);
+ break;
+ }
+ } finally {
+ console.log("After retries, failed to set up Continue extension", msg);
+ vscode.window.showErrorMessage(msg);
+ }
+
sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, {
error: e.message,
});
@@ -127,8 +160,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 +172,29 @@ 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(" ; ");
+ await runCommand(removeOldVersionCommand);
+ return false;
+ }
+ }
+
let envLibsPath = path.join(
- getExtensionUri().fsPath,
- "scripts",
+ serverPath(),
"env",
process.platform == "win32" ? "Lib" : "lib"
);
@@ -165,27 +216,30 @@ 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() {
- console.log("Setting up python env for Continue extension...");
-
- const [pythonCmd, pipCmd] = await getPythonPipCommands();
- const [activateCmd, pipUpgradeCmd] = getActivateUpgradeCommands(
- pythonCmd,
- pipCmd
- );
+async function getLinuxAptInstallError(pythonCmd: string) {
+ // 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];
+ const installVenvCommand = `apt-get install python3.${version}-venv`;
+ await runCommand("apt-get update");
+ return `[Important] Continue needs to create a Python virtual environment, but python3.${version}-venv is not installed. Please run this command in your terminal: \`${installVenvCommand}\`, reload VS Code, and then try again.`;
+}
+async function createPythonVenv(pythonCmd: string) {
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(" ; ");
@@ -194,31 +248,37 @@ async function setupPythonEnv() {
stderr &&
stderr.includes("running scripts is disabled on this system")
) {
- await vscode.window.showErrorMessage(
- "A Python virtual enviroment cannot be activated because running scripts is disabled for this user. Please enable signed scripts to run with this command in PowerShell: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`, reload VS Code, and then try again."
- );
+ console.log("Scripts disabled error when trying to create env");
+ await vscode.window.showErrorMessage(WINDOWS_REMOTE_SIGNED_SCRIPTS_ERROR);
throw new Error(stderr);
} else if (
stderr?.includes("On Debian/Ubuntu systems") ||
stdout?.includes("On Debian/Ubuntu systems")
) {
- // First, try to run the command to install python3-venv
- let [stdout, stderr] = await runCommand(`${pythonCmd} --version`);
- if (stderr) {
- throw new Error(stderr);
- }
- const version = stdout.split(" ")[1].split(".")[1];
- const installVenvCommand = `apt-get install python3.${version}-venv`;
- await runCommand("apt-get update");
- // Ask the user to run the command to install python3-venv (requires sudo, so we can't)
- // First, get the python version
- const msg = `[Important] Continue needs to create a Python virtual environment, but python3.${version}-venv is not installed. Please run this command in your terminal: \`${installVenvCommand}\`, reload VS Code, and then try again.`;
+ const msg = await getLinuxAptInstallError(pythonCmd);
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 if (
+ stderr?.includes("Permission denied") &&
+ stderr?.includes("python.exe")
+ ) {
+ // This might mean that another window is currently using the python.exe file to install requirements
+ // So we want to wait and try again
+ let i = 0;
+ await new Promise((resolve, reject) =>
+ setInterval(() => {
+ if (i > 5) {
+ reject("Timed out waiting for other window to create env...");
+ }
+ if (checkEnvExists()) {
+ resolve(null);
+ } else {
+ console.log("Waiting for other window to create env...");
+ }
+ i++;
+ }, 5000)
);
} else {
const msg = [
@@ -230,13 +290,27 @@ async function setupPythonEnv() {
throw new Error(msg);
}
}
+}
+
+async function setupPythonEnv() {
+ console.log("Setting up python env for Continue extension...");
+
+ const [pythonCmd, pipCmd] = await getPythonPipCommands();
+ const [activateCmd, pipUpgradeCmd] = getActivateUpgradeCommands(
+ pythonCmd,
+ pipCmd
+ );
await retryThenFail(async () => {
- if (checkRequirementsInstalled()) {
+ // First, create the virtual environment
+ await createPythonVenv(pythonCmd);
+
+ // Install the requirements
+ 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 +319,8 @@ async function setupPythonEnv() {
if (stderr) {
throw new Error(stderr);
}
+ // Write the version number for which requirements were installed
+ fs.writeFileSync(requirementsVersionPath(), getExtensionVersion());
}
});
}
@@ -297,12 +373,50 @@ 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;
+}
+
+export function devDataPath(): string {
+ const sPath = path.join(getContinueGlobalPath(), "dev_data");
+ 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 getExtensionVersion() {
+function requirementsVersionPath(): string {
+ return path.join(serverPath(), "requirements_version.txt");
+}
+
+export function getExtensionVersion() {
const extension = vscode.extensions.getExtension("continue.continue");
return extension?.packageJSON.version || "";
}
@@ -314,39 +428,40 @@ export async function startContinuePythonServer() {
return;
}
+ 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;
- }
+ // 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
- );
+ }
+ console.log("Killing old server...");
+ try {
+ await fkill(":65432");
+ } catch (e: any) {
+ if (!e.message.includes("Process doesn't exist")) {
+ console.log("Failed to kill old server:", e);
}
}
// 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...");
@@ -359,7 +474,8 @@ export async function startContinuePythonServer() {
console.log(`stdout: ${data}`);
if (
data.includes("Uvicorn running on") || // Successfully started the server
- data.includes("address already in use") // The server is already running (probably a simultaneously opened VS Code window)
+ data.includes("only one usage of each socket address") || // [windows] The server is already running (probably a simultaneously opened VS Code window)
+ data.includes("address already in use") // [mac/linux] The server is already running (probably a simultaneously opened VS Code window)
) {
console.log("Successfully started Continue python server");
resolve(null);
@@ -392,8 +508,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() {
diff --git a/extension/src/bridge.ts b/extension/src/bridge.ts
index 92ba4044..7e6398be 100644
--- a/extension/src/bridge.ts
+++ b/extension/src/bridge.ts
@@ -4,14 +4,11 @@ import * as vscode from "vscode";
import {
Configuration,
DebugApi,
- RangeInFile,
- SerializedDebugContext,
UnittestApi,
} from "./client";
import { convertSingleToDoubleQuoteJSON } from "./util/util";
import { getExtensionUri } from "./util/vscode";
import { extensionContext } from "./activation/activate";
-const axios = require("axios").default;
const util = require("util");
const exec = util.promisify(require("child_process").exec);
@@ -36,21 +33,16 @@ const configuration = new Configuration({
export const debugApi = new DebugApi(configuration);
export const unittestApi = new UnittestApi(configuration);
-function get_python_path() {
- return path.join(getExtensionUri().fsPath, "..");
-}
-
export function get_api_url() {
- let extensionUri = getExtensionUri();
- let configFile = path.join(extensionUri.fsPath, "config/config.json");
- let config = require(configFile);
+ const extensionUri = getExtensionUri();
+ const configFile = path.join(extensionUri.fsPath, "config/config.json");
+ const config = require(configFile);
if (config.API_URL) {
return config.API_URL;
}
return "http://localhost:65432";
}
-const API_URL = get_api_url();
export function getContinueServerUrl() {
// If in debug mode, always use 8001
@@ -66,10 +58,6 @@ export function getContinueServerUrl() {
);
}
-function build_python_command(cmd: string): string {
- return `cd ${get_python_path()} && source env/bin/activate && ${cmd}`;
-}
-
function listToCmdLineArgs(list: string[]): string {
return list.map((el) => `"$(echo "${el}")"`).join(" ");
}
@@ -103,198 +91,3 @@ export async function runPythonScript(
}
}
}
-
-function parseStdout(
- stdout: string,
- key: string,
- until_end: boolean = false
-): string {
- const prompt = `${key}=`;
- let lines = stdout.split("\n");
-
- let value: string = "";
- for (let i = 0; i < lines.length; i++) {
- if (lines[i].startsWith(prompt)) {
- if (until_end) {
- return lines.slice(i).join("\n").substring(prompt.length);
- } else {
- return lines[i].substring(prompt.length);
- }
- }
- }
- return "";
-}
-
-export async function askQuestion(
- question: string,
- workspacePath: string
-): Promise<{ answer: string; range: vscode.Range; filename: string }> {
- const command = build_python_command(
- `python3 ${path.join(
- get_python_path(),
- "ask.py"
- )} ask ${workspacePath} "${question}"`
- );
-
- const { stdout, stderr } = await exec(command);
- if (stderr) {
- throw new Error(stderr);
- }
- // Use the output
- const answer = parseStdout(stdout, "Answer");
- const filename = parseStdout(stdout, "Filename");
- const startLineno = parseInt(parseStdout(stdout, "Start lineno"));
- const endLineno = parseInt(parseStdout(stdout, "End lineno"));
- const range = new vscode.Range(
- new vscode.Position(startLineno, 0),
- new vscode.Position(endLineno, 0)
- );
- if (answer && filename && startLineno && endLineno) {
- return { answer, filename, range };
- } else {
- throw new Error("Error: No answer found");
- }
-}
-
-export async function apiRequest(
- endpoint: string,
- options: {
- method?: string;
- query?: { [key: string]: any };
- body?: { [key: string]: any };
- }
-): Promise<any> {
- let defaults = {
- method: "GET",
- query: {},
- body: {},
- };
- options = Object.assign(defaults, options); // Second takes over first
- if (endpoint.startsWith("/")) endpoint = endpoint.substring(1);
- console.log("API request: ", options.body);
-
- let resp;
- try {
- resp = await axios({
- method: options.method,
- url: `${API_URL}/${endpoint}`,
- data: options.body,
- params: options.query,
- headers: {
- "x-vsc-machine-id": vscode.env.machineId,
- },
- });
- } catch (err) {
- console.log("Error: ", err);
- throw err;
- }
-
- return resp.data;
-}
-
-// Write a docstring for the most specific function or class at the current line in the given file
-export async function writeDocstringForFunction(
- filename: string,
- position: vscode.Position
-): Promise<{ lineno: number; docstring: string }> {
- let resp = await apiRequest("docstring/forline", {
- query: {
- filecontents: (
- await vscode.workspace.fs.readFile(vscode.Uri.file(filename))
- ).toString(),
- lineno: position.line.toString(),
- },
- });
-
- const lineno = resp.lineno;
- const docstring = resp.completion;
- if (lineno && docstring) {
- return { lineno, docstring };
- } else {
- throw new Error("Error: No docstring returned");
- }
-}
-
-export async function findSuspiciousCode(
- ctx: SerializedDebugContext
-): Promise<RangeInFile[]> {
- if (!ctx.traceback) return [];
- let files = await getFileContents(
- getFilenamesFromPythonStacktrace(ctx.traceback)
- );
- let resp = await debugApi.findSusCodeEndpointDebugFindPost({
- findBody: {
- traceback: ctx.traceback,
- description: ctx.description,
- filesystem: files,
- },
- });
- let ranges = resp.response;
- if (
- ranges.length <= 1 &&
- ctx.traceback &&
- ctx.traceback.includes("AssertionError")
- ) {
- let parsed_traceback =
- await debugApi.parseTracebackEndpointDebugParseTracebackGet({
- traceback: ctx.traceback,
- });
- let last_frame = parsed_traceback.frames[0];
- if (!last_frame) return [];
- ranges = (
- await runPythonScript("build_call_graph.py", [
- last_frame.filepath,
- last_frame.lineno.toString(),
- last_frame._function,
- ])
- ).value;
- }
-
- return ranges;
-}
-
-export async function writeUnitTestForFunction(
- filename: string,
- position: vscode.Position
-): Promise<string> {
- let resp = await apiRequest("unittest/forline", {
- method: "POST",
- body: {
- filecontents: (
- await vscode.workspace.fs.readFile(vscode.Uri.file(filename))
- ).toString(),
- lineno: position.line,
- userid: vscode.env.machineId,
- },
- });
-
- return resp.completion;
-}
-
-async function getFileContents(
- files: string[]
-): Promise<{ [key: string]: string }> {
- let contents = await Promise.all(
- files.map(async (file: string) => {
- return (
- await vscode.workspace.fs.readFile(vscode.Uri.file(file))
- ).toString();
- })
- );
- let fileContents: { [key: string]: string } = {};
- for (let i = 0; i < files.length; i++) {
- fileContents[files[i]] = contents[i];
- }
- return fileContents;
-}
-
-function getFilenamesFromPythonStacktrace(traceback: string): string[] {
- let filenames: string[] = [];
- for (let line of traceback.split("\n")) {
- let match = line.match(/File "(.*)", line/);
- if (match) {
- filenames.push(match[1]);
- }
- }
- return filenames;
-}
diff --git a/extension/src/commands.ts b/extension/src/commands.ts
index ffb67ab5..2b7f4c0c 100644
--- a/extension/src/commands.ts
+++ b/extension/src/commands.ts
@@ -16,40 +16,16 @@ import {
import { acceptDiffCommand, rejectDiffCommand } from "./diffs";
import * as bridge from "./bridge";
import { debugPanelWebview } from "./debugPanel";
-import { sendTelemetryEvent, TelemetryEvent } from "./telemetry";
import { ideProtocolClient } from "./activation/activate";
-// Copy everything over from extension.ts
-const commandsMap: { [command: string]: (...args: any) => any } = {
- "continue.askQuestion": (data: any, webviewView: vscode.WebviewView) => {
- if (!vscode.workspace.workspaceFolders) {
- return;
- }
-
- answerQuestion(
- data.question,
- vscode.workspace.workspaceFolders[0].uri.fsPath,
- webviewView.webview
- );
- },
- "continue.askQuestionFromInput": () => {
- vscode.window
- .showInputBox({ placeHolder: "Ask away!" })
- .then((question) => {
- if (!question || !vscode.workspace.workspaceFolders) {
- return;
- }
+let focusedOnContinueInput = false;
- sendTelemetryEvent(TelemetryEvent.UniversalPromptQuery, {
- query: question,
- });
+export const setFocusedOnContinueInput = (value: boolean) => {
+ focusedOnContinueInput = value;
+};
- answerQuestion(
- question,
- vscode.workspace.workspaceFolders[0].uri.fsPath
- );
- });
- },
+// Copy everything over from extension.ts
+const commandsMap: { [command: string]: (...args: any) => any } = {
"continue.suggestionDown": suggestionDownCommand,
"continue.suggestionUp": suggestionUpCommand,
"continue.acceptSuggestion": acceptSuggestionCommand,
@@ -58,11 +34,23 @@ const commandsMap: { [command: string]: (...args: any) => any } = {
"continue.rejectDiff": rejectDiffCommand,
"continue.acceptAllSuggestions": acceptAllSuggestionsCommand,
"continue.rejectAllSuggestions": rejectAllSuggestionsCommand,
+ "continue.quickFix": async (message: string, code: string, edit: boolean) => {
+ ideProtocolClient.sendMainUserInput(
+ `${
+ edit ? "/edit " : ""
+ }${code}\n\nHow do I fix this problem in the above code?: ${message}`
+ );
+ },
"continue.focusContinueInput": async () => {
- vscode.commands.executeCommand("continue.continueGUIView.focus");
- debugPanelWebview?.postMessage({
- type: "focusContinueInput",
- });
+ if (focusedOnContinueInput) {
+ vscode.commands.executeCommand("workbench.action.focusActiveEditorGroup");
+ } else {
+ vscode.commands.executeCommand("continue.continueGUIView.focus");
+ debugPanelWebview?.postMessage({
+ type: "focusContinueInput",
+ });
+ }
+ focusedOnContinueInput = !focusedOnContinueInput;
},
"continue.quickTextEntry": async () => {
const text = await vscode.window.showInputBox({
@@ -73,27 +61,6 @@ const commandsMap: { [command: string]: (...args: any) => any } = {
if (text) {
ideProtocolClient.sendMainUserInput(text);
}
- vscode.commands.executeCommand("continue.continueGUIView.focus");
- },
-};
-
-const textEditorCommandsMap: { [command: string]: (...args: any) => {} } = {
- "continue.writeDocstring": async (editor: vscode.TextEditor, _) => {
- sendTelemetryEvent(TelemetryEvent.GenerateDocstring);
- let gutterSpinnerKey = showGutterSpinner(
- editor,
- editor.selection.active.line
- );
-
- const { lineno, docstring } = await bridge.writeDocstringForFunction(
- editor.document.fileName,
- editor.selection.active
- );
- // Can't use the edit given above after an async call
- editor.edit((edit) => {
- edit.insert(new vscode.Position(lineno, 0), docstring);
- decorationManager.deleteDecoration(gutterSpinnerKey);
- });
},
};
@@ -103,44 +70,4 @@ export function registerAllCommands(context: vscode.ExtensionContext) {
vscode.commands.registerCommand(command, callback)
);
}
-
- for (const [command, callback] of Object.entries(textEditorCommandsMap)) {
- context.subscriptions.push(
- vscode.commands.registerTextEditorCommand(command, callback)
- );
- }
-}
-
-async function answerQuestion(
- question: string,
- workspacePath: string,
- webview: vscode.Webview | undefined = undefined
-) {
- vscode.window.withProgress(
- {
- location: vscode.ProgressLocation.Notification,
- title: "Anwering question...",
- cancellable: false,
- },
- async (progress, token) => {
- try {
- let resp = await bridge.askQuestion(question, workspacePath);
- // Send the answer back to the webview
- if (webview) {
- webview.postMessage({
- type: "answerQuestion",
- answer: resp.answer,
- });
- }
- showAnswerInTextEditor(resp.filename, resp.range, resp.answer);
- } catch (error: any) {
- if (webview) {
- webview.postMessage({
- type: "answerQuestion",
- answer: error,
- });
- }
- }
- }
- );
}
diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts
index 679d94ba..fac0a227 100644
--- a/extension/src/continueIdeClient.ts
+++ b/extension/src/continueIdeClient.ts
@@ -1,10 +1,9 @@
-// import { ShowSuggestionRequest } from "../schema/ShowSuggestionRequest";
import {
editorSuggestionsLocked,
showSuggestion as showSuggestionInEditor,
SuggestionRanges,
} from "./suggestions";
-import { openEditorAndRevealRange, getRightViewColumn } from "./util/vscode";
+import { openEditorAndRevealRange } from "./util/vscode";
import { FileEdit } from "../schema/FileEdit";
import { RangeInFile } from "../schema/RangeInFile";
import * as vscode from "vscode";
@@ -15,9 +14,8 @@ import {
import { FileEditWithFullContents } from "../schema/FileEditWithFullContents";
import fs = require("fs");
import { WebsocketMessenger } from "./util/messenger";
-import * as path from "path";
-import * as os from "os";
import { diffManager } from "./diffs";
+import path = require("path");
class IdeProtocolClient {
private messenger: WebsocketMessenger | null = null;
@@ -27,17 +25,54 @@ class IdeProtocolClient {
private _highlightDebounce: NodeJS.Timeout | null = null;
- constructor(serverUrl: string, context: vscode.ExtensionContext) {
- this.context = context;
+ private _lastReloadTime: number = 16;
+ private _reconnectionTimeouts: NodeJS.Timeout[] = [];
+
+ private _sessionId: string | null = null;
+ private _serverUrl: string;
- let messenger = new WebsocketMessenger(serverUrl);
+ private _newWebsocketMessenger() {
+ const requestUrl =
+ this._serverUrl +
+ (this._sessionId ? `?session_id=${this._sessionId}` : "");
+ const messenger = new WebsocketMessenger(requestUrl);
this.messenger = messenger;
- messenger.onClose(() => {
+
+ const reconnect = () => {
+ console.log("Trying to reconnect IDE protocol websocket...");
this.messenger = null;
+
+ // Exponential backoff to reconnect
+ this._reconnectionTimeouts.forEach((to) => clearTimeout(to));
+
+ const timeout = setTimeout(() => {
+ if (this.messenger?.websocket?.readyState === 1) {
+ return;
+ }
+ this._newWebsocketMessenger();
+ }, this._lastReloadTime);
+
+ this._reconnectionTimeouts.push(timeout);
+ this._lastReloadTime = Math.min(2 * this._lastReloadTime, 5000);
+ };
+ messenger.onOpen(() => {
+ this._reconnectionTimeouts.forEach((to) => clearTimeout(to));
+ });
+ messenger.onClose(() => {
+ reconnect();
+ });
+ messenger.onError(() => {
+ reconnect();
});
messenger.onMessage((messageType, data, messenger) => {
this.handleMessage(messageType, data, messenger);
});
+ }
+
+ constructor(serverUrl: string, context: vscode.ExtensionContext) {
+ this.context = context;
+ this._serverUrl = serverUrl;
+ this._newWebsocketMessenger();
// Setup listeners for any file changes in open editors
// vscode.workspace.onDidChangeTextDocument((event) => {
@@ -69,8 +104,11 @@ class IdeProtocolClient {
// }
// });
- // Setup listeners for any file changes in open editors
+ // Setup listeners for any selection changes in open editors
vscode.window.onDidChangeTextEditorSelection((event) => {
+ if (this.editorIsTerminal(event.textEditor)) {
+ return;
+ }
if (this._highlightDebounce) {
clearTimeout(this._highlightDebounce);
}
@@ -131,6 +169,11 @@ class IdeProtocolClient {
openFiles: this.getOpenFiles(),
});
break;
+ case "visibleFiles":
+ messenger.send("visibleFiles", {
+ visibleFiles: this.getVisibleFiles(),
+ });
+ break;
case "readFile":
messenger.send("readFile", {
contents: this.readFile(data.filepath),
@@ -166,7 +209,7 @@ class IdeProtocolClient {
case "showDiff":
this.showDiff(data.filepath, data.replacement, data.step_index);
break;
- case "openGUI":
+ case "getSessionId":
case "connected":
break;
default:
@@ -279,10 +322,6 @@ class IdeProtocolClient {
// ------------------------------------ //
// Initiate Request
- async openGUI(asRightWebviewPanel: boolean = false) {
- // Open the webview panel
- }
-
async getSessionId(): Promise<string> {
await new Promise((resolve, reject) => {
// Repeatedly try to connect to the server
@@ -298,10 +337,10 @@ class IdeProtocolClient {
}
}, 1000);
});
- const resp = await this.messenger?.sendAndReceive("openGUI", {});
- const sessionId = resp.sessionId;
- console.log("New Continue session with ID: ", sessionId);
- return sessionId;
+ const resp = await this.messenger?.sendAndReceive("getSessionId", {});
+ // console.log("New Continue session with ID: ", sessionId);
+ this._sessionId = resp.sessionId;
+ return resp.sessionId;
}
acceptRejectSuggestion(accept: boolean, key: SuggestionRanges) {
@@ -315,38 +354,55 @@ class IdeProtocolClient {
// ------------------------------------ //
// Respond to request
+ private editorIsTerminal(editor: vscode.TextEditor) {
+ return (
+ !!path.basename(editor.document.uri.fsPath).match(/\d/) ||
+ (editor.document.languageId === "plaintext" &&
+ editor.document.getText() === "accessible-buffer-accessible-buffer-")
+ );
+ }
+
getOpenFiles(): string[] {
return vscode.window.visibleTextEditors
- .filter((editor) => {
- return !(
- editor.document.uri.fsPath.endsWith("/1") ||
- (editor.document.languageId === "plaintext" &&
- editor.document.getText() ===
- "accessible-buffer-accessible-buffer-")
- );
- })
+ .filter((editor) => !this.editorIsTerminal(editor))
+ .map((editor) => {
+ return editor.document.uri.fsPath;
+ });
+ }
+
+ getVisibleFiles(): string[] {
+ return vscode.window.visibleTextEditors
+ .filter((editor) => !this.editorIsTerminal(editor))
.map((editor) => {
return editor.document.uri.fsPath;
});
}
saveFile(filepath: string) {
- vscode.window.visibleTextEditors.forEach((editor) => {
- if (editor.document.uri.fsPath === filepath) {
- editor.document.save();
- }
- });
+ vscode.window.visibleTextEditors
+ .filter((editor) => !this.editorIsTerminal(editor))
+ .forEach((editor) => {
+ if (editor.document.uri.fsPath === filepath) {
+ editor.document.save();
+ }
+ });
}
readFile(filepath: string): string {
let contents: string | undefined;
- vscode.window.visibleTextEditors.forEach((editor) => {
- if (editor.document.uri.fsPath === filepath) {
- contents = editor.document.getText();
+ vscode.window.visibleTextEditors
+ .filter((editor) => !this.editorIsTerminal(editor))
+ .forEach((editor) => {
+ if (editor.document.uri.fsPath === filepath) {
+ contents = editor.document.getText();
+ }
+ });
+ if (typeof contents === "undefined") {
+ if (fs.existsSync(filepath)) {
+ contents = fs.readFileSync(filepath, "utf-8");
+ } else {
+ contents = "";
}
- });
- if (!contents) {
- contents = fs.readFileSync(filepath, "utf-8");
}
return contents;
}
@@ -380,25 +436,27 @@ class IdeProtocolClient {
getHighlightedCode(): RangeInFile[] {
// TODO
let rangeInFiles: RangeInFile[] = [];
- vscode.window.visibleTextEditors.forEach((editor) => {
- editor.selections.forEach((selection) => {
- // if (!selection.isEmpty) {
- rangeInFiles.push({
- filepath: editor.document.uri.fsPath,
- range: {
- start: {
- line: selection.start.line,
- character: selection.start.character,
- },
- end: {
- line: selection.end.line,
- character: selection.end.character,
+ vscode.window.visibleTextEditors
+ .filter((editor) => !this.editorIsTerminal(editor))
+ .forEach((editor) => {
+ editor.selections.forEach((selection) => {
+ // if (!selection.isEmpty) {
+ rangeInFiles.push({
+ filepath: editor.document.uri.fsPath,
+ range: {
+ start: {
+ line: selection.start.line,
+ character: selection.start.character,
+ },
+ end: {
+ line: selection.end.line,
+ character: selection.end.character,
+ },
},
- },
+ });
+ // }
});
- // }
});
- });
return rangeInFiles;
}
diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts
index 487bbedf..dd24a8d8 100644
--- a/extension/src/debugPanel.ts
+++ b/extension/src/debugPanel.ts
@@ -6,78 +6,9 @@ import {
openEditorAndRevealRange,
} from "./util/vscode";
import { RangeInFile } from "./client";
+import { setFocusedOnContinueInput } from "./commands";
const WebSocket = require("ws");
-class StreamManager {
- private _fullText: string = "";
- private _insertionPoint: vscode.Position | undefined;
-
- private _addToEditor(update: string) {
- let editor =
- vscode.window.activeTextEditor || vscode.window.visibleTextEditors[0];
-
- if (typeof this._insertionPoint === "undefined") {
- if (editor?.selection.isEmpty) {
- this._insertionPoint = editor?.selection.active;
- } else {
- this._insertionPoint = editor?.selection.end;
- }
- }
- editor?.edit((editBuilder) => {
- if (this._insertionPoint) {
- editBuilder.insert(this._insertionPoint, update);
- this._insertionPoint = this._insertionPoint.translate(
- Array.from(update.matchAll(/\n/g)).length,
- update.length
- );
- }
- });
- }
-
- public closeStream() {
- this._fullText = "";
- this._insertionPoint = undefined;
- this._codeBlockStatus = "closed";
- this._pendingBackticks = 0;
- }
-
- private _codeBlockStatus: "open" | "closed" | "language-descriptor" =
- "closed";
- private _pendingBackticks: number = 0;
- public onStreamUpdate(update: string) {
- let textToInsert = "";
- for (let i = 0; i < update.length; i++) {
- switch (this._codeBlockStatus) {
- case "closed":
- if (update[i] === "`" && this._fullText.endsWith("``")) {
- this._codeBlockStatus = "language-descriptor";
- }
- break;
- case "language-descriptor":
- if (update[i] === " " || update[i] === "\n") {
- this._codeBlockStatus = "open";
- }
- break;
- case "open":
- if (update[i] === "`") {
- if (this._fullText.endsWith("``")) {
- this._codeBlockStatus = "closed";
- this._pendingBackticks = 0;
- } else {
- this._pendingBackticks += 1;
- }
- } else {
- textToInsert += "`".repeat(this._pendingBackticks) + update[i];
- this._pendingBackticks = 0;
- }
- break;
- }
- this._fullText += update[i];
- }
- this._addToEditor(textToInsert);
- }
-}
-
let websocketConnections: { [url: string]: WebsocketConnection | undefined } =
{};
@@ -127,8 +58,6 @@ class WebsocketConnection {
}
}
-let streamManager = new StreamManager();
-
export let debugPanelWebview: vscode.Webview | undefined;
export function setupDebugPanel(
panel: vscode.WebviewPanel | vscode.WebviewView,
@@ -147,10 +76,7 @@ export function setupDebugPanel(
.toString();
const isProduction = true; // context?.extensionMode === vscode.ExtensionMode.Development;
- if (!isProduction) {
- scriptUri = "http://localhost:5173/src/main.tsx";
- styleMainUri = "http://localhost:5173/src/main.css";
- } else {
+ if (isProduction) {
scriptUri = debugPanelWebview
.asWebviewUri(
vscode.Uri.joinPath(extensionUri, "react-app/dist/assets/index.js")
@@ -161,6 +87,9 @@ export function setupDebugPanel(
vscode.Uri.joinPath(extensionUri, "react-app/dist/assets/index.css")
)
.toString();
+ } else {
+ scriptUri = "http://localhost:5173/src/main.tsx";
+ styleMainUri = "http://localhost:5173/src/main.css";
}
panel.webview.options = {
@@ -175,11 +104,11 @@ export function setupDebugPanel(
return;
}
- let rangeInFile: RangeInFile = {
+ const rangeInFile: RangeInFile = {
range: e.selections[0],
filepath: e.textEditor.document.fileName,
};
- let filesystem = {
+ const filesystem = {
[rangeInFile.filepath]: e.textEditor.document.getText(),
};
panel.webview.postMessage({
@@ -217,13 +146,19 @@ export function setupDebugPanel(
url,
});
};
- const connection = new WebsocketConnection(
- url,
- onMessage,
- onOpen,
- onClose
- );
- websocketConnections[url] = connection;
+ try {
+ const connection = new WebsocketConnection(
+ url,
+ onMessage,
+ onOpen,
+ onClose
+ );
+ websocketConnections[url] = connection;
+ resolve(null);
+ } catch (e) {
+ console.log("Caught it!: ", e);
+ reject(e);
+ }
});
}
@@ -292,13 +227,8 @@ export function setupDebugPanel(
openEditorAndRevealRange(data.path, undefined, vscode.ViewColumn.One);
break;
}
- case "streamUpdate": {
- // Write code at the position of the cursor
- streamManager.onStreamUpdate(data.update);
- break;
- }
- case "closeStream": {
- streamManager.closeStream();
+ case "blurContinueInput": {
+ setFocusedOnContinueInput(false);
break;
}
case "withProgress": {
diff --git a/extension/src/diffs.ts b/extension/src/diffs.ts
index b9ef8384..0bab326a 100644
--- a/extension/src/diffs.ts
+++ b/extension/src/diffs.ts
@@ -2,22 +2,31 @@ 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";
+import { getMetaKeyLabel } from "./util/util";
+import { devDataPath } from "./activation/environmentSetup";
interface DiffInfo {
originalFilepath: string;
newFilepath: string;
editor?: vscode.TextEditor;
step_index: number;
+ range: vscode.Range;
}
-export const DIFF_DIRECTORY = path.join(os.homedir(), ".continue", "diffs");
+export const DIFF_DIRECTORY = path
+ .join(os.homedir(), ".continue", "diffs")
+ .replace(/^C:/, "c:");
class DiffManager {
// Create a temporary file in the global .continue directory which displays the updated version
// Doing this because virtual files are read-only
private diffs: Map<string, DiffInfo> = new Map();
+ diffAtNewFilepath(newFilepath: string): DiffInfo | undefined {
+ return this.diffs.get(newFilepath);
+ }
+
private setupDirectory() {
// Make sure the diff directory exists
if (!fs.existsSync(DIFF_DIRECTORY)) {
@@ -35,6 +44,10 @@ class DiffManager {
return filepath.replace(/\\/g, "_").replace(/\//g, "_");
}
+ private getNewFilepath(originalFilepath: string): string {
+ return path.join(DIFF_DIRECTORY, this.escapeFilepath(originalFilepath));
+ }
+
private openDiffEditor(
originalFilepath: string,
newFilepath: string
@@ -47,7 +60,7 @@ class DiffManager {
return undefined;
}
- const rightUri = vscode.Uri.parse(newFilepath);
+ const rightUri = vscode.Uri.file(newFilepath);
const leftUri = vscode.Uri.file(originalFilepath);
const title = "Continue Diff";
console.log(
@@ -70,9 +83,42 @@ class DiffManager {
.getConfiguration("diffEditor", editor.document.uri)
.update("codeLens", true, vscode.ConfigurationTarget.Global);
+ if (
+ extensionContext?.globalState.get<boolean>(
+ "continue.showDiffInfoMessage"
+ ) !== false
+ ) {
+ vscode.window
+ .showInformationMessage(
+ `Accept (${getMetaKeyLabel()}⇧↩) or reject (${getMetaKeyLabel()}⇧⌫) 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;
}
+ private _findFirstDifferentLine(contentA: string, contentB: string): number {
+ const linesA = contentA.split("\n");
+ const linesB = contentB.split("\n");
+ for (let i = 0; i < linesA.length && i < linesB.length; i++) {
+ if (linesA[i] !== linesB[i]) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
writeDiff(
originalFilepath: string,
newContent: string,
@@ -81,18 +127,20 @@ class DiffManager {
this.setupDirectory();
// Create or update existing diff
- const newFilepath = path.join(
- DIFF_DIRECTORY,
- this.escapeFilepath(originalFilepath)
- );
+ const newFilepath = this.getNewFilepath(originalFilepath);
fs.writeFileSync(newFilepath, newContent);
// Open the diff editor if this is a new diff
if (!this.diffs.has(newFilepath)) {
+ // Figure out the first line that is different
+ const oldContent = ideProtocolClient.readFile(originalFilepath);
+ const line = this._findFirstDifferentLine(oldContent, newContent);
+
const diffInfo: DiffInfo = {
originalFilepath,
newFilepath,
step_index,
+ range: new vscode.Range(line, 0, line + 1, 0),
};
this.diffs.set(newFilepath, diffInfo);
}
@@ -104,6 +152,11 @@ class DiffManager {
this.diffs.set(newFilepath, diffInfo);
}
+ vscode.commands.executeCommand(
+ "workbench.action.files.revert",
+ vscode.Uri.file(newFilepath)
+ );
+
return newFilepath;
}
@@ -117,10 +170,38 @@ class DiffManager {
fs.unlinkSync(diffInfo.newFilepath);
}
+ private inferNewFilepath() {
+ const activeEditorPath =
+ vscode.window.activeTextEditor?.document.uri.fsPath;
+ if (activeEditorPath && path.dirname(activeEditorPath) === DIFF_DIRECTORY) {
+ return activeEditorPath;
+ }
+ const visibleEditors = vscode.window.visibleTextEditors.map(
+ (editor) => editor.document.uri.fsPath
+ );
+ for (const editorPath of visibleEditors) {
+ if (path.dirname(editorPath) === DIFF_DIRECTORY) {
+ for (const otherEditorPath of visibleEditors) {
+ if (
+ path.dirname(otherEditorPath) !== DIFF_DIRECTORY &&
+ this.getNewFilepath(otherEditorPath) === editorPath
+ ) {
+ return editorPath;
+ }
+ }
+ }
+ }
+
+ if (this.diffs.size === 1) {
+ return Array.from(this.diffs.keys())[0];
+ }
+ return undefined;
+ }
+
acceptDiff(newFilepath?: string) {
- // If no newFilepath is provided and there is only one in the dictionary, use that
- if (!newFilepath && this.diffs.size === 1) {
- newFilepath = Array.from(this.diffs.keys())[0];
+ // When coming from a keyboard shortcut, we have to infer the newFilepath from visible text editors
+ if (!newFilepath) {
+ newFilepath = this.inferNewFilepath();
}
if (!newFilepath) {
console.log("No newFilepath provided to accept the diff");
@@ -132,20 +213,32 @@ 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);
+ });
+
+ recordAcceptReject(true, diffInfo);
}
rejectDiff(newFilepath?: string) {
// If no newFilepath is provided and there is only one in the dictionary, use that
- if (!newFilepath && this.diffs.size === 1) {
- newFilepath = Array.from(this.diffs.keys())[0];
+ if (!newFilepath) {
+ newFilepath = this.inferNewFilepath();
}
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);
@@ -157,12 +250,56 @@ 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);
+ });
+
+ recordAcceptReject(false, diffInfo);
}
}
export const diffManager = new DiffManager();
+function recordAcceptReject(accepted: boolean, diffInfo: DiffInfo) {
+ const collectOn = vscode.workspace
+ .getConfiguration("continue")
+ .get<boolean>("dataSwitch");
+
+ if (collectOn) {
+ const devDataDir = devDataPath();
+ const suggestionsPath = path.join(devDataDir, "suggestions.json");
+
+ // Initialize suggestions list
+ let suggestions = [];
+
+ // Check if suggestions.json exists
+ if (fs.existsSync(suggestionsPath)) {
+ const rawData = fs.readFileSync(suggestionsPath, "utf-8");
+ suggestions = JSON.parse(rawData);
+ }
+
+ // Add the new suggestion to the list
+ suggestions.push({
+ accepted,
+ timestamp: Date.now(),
+ suggestion: diffInfo.originalFilepath,
+ });
+
+ // Send the suggestion to the server
+ ideProtocolClient.sendAcceptRejectSuggestion(accepted);
+
+ // Write the updated suggestions back to the file
+ fs.writeFileSync(
+ suggestionsPath,
+ JSON.stringify(suggestions, null, 4),
+ "utf-8"
+ );
+ }
+}
+
export async function acceptDiffCommand(newFilepath?: string) {
diffManager.acceptDiff(newFilepath);
ideProtocolClient.sendAcceptRejectDiff(true);
diff --git a/extension/src/extension.ts b/extension/src/extension.ts
index de8f55e3..6959ec05 100644
--- a/extension/src/extension.ts
+++ b/extension/src/extension.ts
@@ -3,17 +3,17 @@
*/
import * as vscode from "vscode";
-import {
- isPythonEnvSetup,
- startContinuePythonServer,
-} from "./activation/environmentSetup";
-async function dynamicImportAndActivate(
- context: vscode.ExtensionContext,
- showTutorial: boolean
-) {
+async function dynamicImportAndActivate(context: vscode.ExtensionContext) {
const { activateExtension } = await import("./activation/activate");
- await activateExtension(context, showTutorial);
+ try {
+ await activateExtension(context);
+ } catch (e) {
+ console.log("Error activating extension: ", e);
+ vscode.window.showInformationMessage(
+ "Error activating the Continue extension."
+ );
+ }
}
export function activate(context: vscode.ExtensionContext) {
@@ -25,7 +25,7 @@ export function activate(context: vscode.ExtensionContext) {
cancellable: false,
},
async () => {
- dynamicImportAndActivate(context, true);
+ dynamicImportAndActivate(context);
}
);
}
diff --git a/extension/src/lang-server/codeActions.ts b/extension/src/lang-server/codeActions.ts
new file mode 100644
index 00000000..07cf5f4e
--- /dev/null
+++ b/extension/src/lang-server/codeActions.ts
@@ -0,0 +1,53 @@
+import * as vscode from "vscode";
+
+class ContinueQuickFixProvider implements vscode.CodeActionProvider {
+ public static readonly providedCodeActionKinds = [
+ vscode.CodeActionKind.QuickFix,
+ ];
+
+ provideCodeActions(
+ document: vscode.TextDocument,
+ range: vscode.Range | vscode.Selection,
+ context: vscode.CodeActionContext,
+ token: vscode.CancellationToken
+ ): vscode.ProviderResult<(vscode.Command | vscode.CodeAction)[]> {
+ if (context.diagnostics.length === 0) {
+ return [];
+ }
+
+ const createQuickFix = (edit: boolean) => {
+ const diagnostic = context.diagnostics[0];
+ const quickFix = new vscode.CodeAction(
+ edit ? "Fix with Continue" : "Ask Continue",
+ vscode.CodeActionKind.QuickFix
+ );
+ quickFix.isPreferred = false;
+ const surroundingRange = new vscode.Range(
+ range.start.translate(-3, 0),
+ range.end.translate(3, 0)
+ );
+ quickFix.command = {
+ command: "continue.quickFix",
+ title: "Continue Quick Fix",
+ arguments: [
+ diagnostic.message,
+ document.getText(surroundingRange),
+ edit,
+ ],
+ };
+ return quickFix;
+ };
+ return [createQuickFix(true), createQuickFix(false)];
+ }
+}
+
+export default function registerQuickFixProvider() {
+ // In your extension's activate function:
+ vscode.languages.registerCodeActionsProvider(
+ { language: "*" },
+ new ContinueQuickFixProvider(),
+ {
+ providedCodeActionKinds: ContinueQuickFixProvider.providedCodeActionKinds,
+ }
+ );
+}
diff --git a/extension/src/lang-server/codeLens.ts b/extension/src/lang-server/codeLens.ts
index 79126eaa..ba80e557 100644
--- a/extension/src/lang-server/codeLens.ts
+++ b/extension/src/lang-server/codeLens.ts
@@ -2,11 +2,12 @@ import * as vscode from "vscode";
import { editorToSuggestions, editorSuggestionsLocked } from "../suggestions";
import * as path from "path";
import * as os from "os";
-import { DIFF_DIRECTORY } from "../diffs";
+import { DIFF_DIRECTORY, diffManager } from "../diffs";
+import { getMetaKeyLabel } from "../util/util";
class SuggestionsCodeLensProvider implements vscode.CodeLensProvider {
public provideCodeLenses(
document: vscode.TextDocument,
- token: vscode.CancellationToken
+ _: vscode.CancellationToken
): vscode.CodeLens[] | Thenable<vscode.CodeLens[]> {
const suggestions = editorToSuggestions.get(document.uri.toString());
if (!suggestions) {
@@ -35,7 +36,7 @@ class SuggestionsCodeLensProvider implements vscode.CodeLensProvider {
if (codeLenses.length === 2) {
codeLenses.push(
new vscode.CodeLens(range, {
- title: "(⌘⇧↩/⌘⇧⌫ to accept/reject all)",
+ title: `(${getMetaKeyLabel()}⇧↩/${getMetaKeyLabel()}⇧⌫ to accept/reject all)`,
command: "",
})
);
@@ -44,40 +45,28 @@ class SuggestionsCodeLensProvider implements vscode.CodeLensProvider {
return codeLenses;
}
-
- onDidChangeCodeLenses?: vscode.Event<void> | undefined;
-
- constructor(emitter?: vscode.EventEmitter<void>) {
- if (emitter) {
- this.onDidChangeCodeLenses = emitter.event;
- this.onDidChangeCodeLenses(() => {
- if (vscode.window.activeTextEditor) {
- this.provideCodeLenses(
- vscode.window.activeTextEditor.document,
- new vscode.CancellationTokenSource().token
- );
- }
- });
- }
- }
}
class DiffViewerCodeLensProvider implements vscode.CodeLensProvider {
public provideCodeLenses(
document: vscode.TextDocument,
- token: vscode.CancellationToken
+ _: vscode.CancellationToken
): vscode.CodeLens[] | Thenable<vscode.CodeLens[]> {
if (path.dirname(document.uri.fsPath) === DIFF_DIRECTORY) {
const codeLenses: vscode.CodeLens[] = [];
- const range = new vscode.Range(0, 0, 1, 0);
+ let range = new vscode.Range(0, 0, 1, 0);
+ const diffInfo = diffManager.diffAtNewFilepath(document.uri.fsPath);
+ if (diffInfo) {
+ range = diffInfo.range;
+ }
codeLenses.push(
new vscode.CodeLens(range, {
- title: "Accept ✅ (⌘⇧↩)",
+ title: `Accept All ✅ (${getMetaKeyLabel()}⇧↩)`,
command: "continue.acceptDiff",
arguments: [document.uri.fsPath],
}),
new vscode.CodeLens(range, {
- title: "Reject ❌ (⌘⇧⌫)",
+ title: `Reject All ❌ (${getMetaKeyLabel()}⇧⌫)`,
command: "continue.rejectDiff",
arguments: [document.uri.fsPath],
})
@@ -87,22 +76,6 @@ class DiffViewerCodeLensProvider implements vscode.CodeLensProvider {
return [];
}
}
-
- onDidChangeCodeLenses?: vscode.Event<void> | undefined;
-
- constructor(emitter?: vscode.EventEmitter<void>) {
- if (emitter) {
- this.onDidChangeCodeLenses = emitter.event;
- this.onDidChangeCodeLenses(() => {
- if (vscode.window.activeTextEditor) {
- this.provideCodeLenses(
- vscode.window.activeTextEditor.document,
- new vscode.CancellationTokenSource().token
- );
- }
- });
- }
- }
}
let diffsCodeLensDisposable: vscode.Disposable | undefined = undefined;
diff --git a/extension/src/suggestions.ts b/extension/src/suggestions.ts
index 6e5a444f..c2373223 100644
--- a/extension/src/suggestions.ts
+++ b/extension/src/suggestions.ts
@@ -1,9 +1,7 @@
import * as vscode from "vscode";
import { sendTelemetryEvent, TelemetryEvent } from "./telemetry";
import { openEditorAndRevealRange } from "./util/vscode";
-import { translate, readFileAtRange } from "./util/vscode";
-import * as fs from "fs";
-import * as path from "path";
+import { translate } from "./util/vscode";
import { registerAllCodeLensProviders } from "./lang-server/codeLens";
import { extensionContext, ideProtocolClient } from "./activation/activate";
@@ -214,62 +212,6 @@ function selectSuggestion(
: suggestion.newRange;
}
- let workspaceDir = vscode.workspace.workspaceFolders
- ? vscode.workspace.workspaceFolders[0]?.uri.fsPath
- : undefined;
-
- let collectOn = vscode.workspace
- .getConfiguration("continue")
- .get<boolean>("dataSwitch");
-
- if (workspaceDir && collectOn) {
- let continueDir = path.join(workspaceDir, ".continue");
-
- // Check if .continue directory doesn't exists
- if (!fs.existsSync(continueDir)) {
- fs.mkdirSync(continueDir);
- }
-
- let suggestionsPath = path.join(continueDir, "suggestions.json");
-
- // Initialize suggestions list
- let suggestions = [];
-
- // Check if suggestions.json exists
- if (fs.existsSync(suggestionsPath)) {
- let rawData = fs.readFileSync(suggestionsPath, "utf-8");
- suggestions = JSON.parse(rawData);
- }
-
- const accepted =
- accept === "new" || (accept === "selected" && suggestion.newSelected);
- suggestions.push({
- accepted,
- timestamp: Date.now(),
- suggestion: suggestion.newContent,
- });
- ideProtocolClient.sendAcceptRejectSuggestion(accepted);
-
- // Write the updated suggestions back to the file
- fs.writeFileSync(
- suggestionsPath,
- JSON.stringify(suggestions, null, 4),
- "utf-8"
- );
-
- // If it's not already there, add .continue to .gitignore
- const gitignorePath = path.join(workspaceDir, ".gitignore");
- if (fs.existsSync(gitignorePath)) {
- const gitignoreData = fs.readFileSync(gitignorePath, "utf-8");
- const gitIgnoreLines = gitignoreData.split("\n");
- if (!gitIgnoreLines.includes(".continue")) {
- fs.appendFileSync(gitignorePath, "\n.continue\n");
- }
- } else {
- fs.writeFileSync(gitignorePath, ".continue\n");
- }
- }
-
rangeToDelete = new vscode.Range(
rangeToDelete.start,
new vscode.Position(rangeToDelete.end.line, 0)
diff --git a/extension/src/util/messenger.ts b/extension/src/util/messenger.ts
index b1df161b..3044898e 100644
--- a/extension/src/util/messenger.ts
+++ b/extension/src/util/messenger.ts
@@ -16,6 +16,8 @@ export abstract class Messenger {
abstract onClose(callback: () => void): void;
+ abstract onError(callback: () => void): void;
+
abstract sendAndReceive(messageType: string, data: any): Promise<any>;
}
@@ -26,6 +28,7 @@ export class WebsocketMessenger extends Messenger {
} = {};
private onOpenListeners: (() => void)[] = [];
private onCloseListeners: (() => void)[] = [];
+ private onErrorListeners: (() => void)[] = [];
private serverUrl: string;
_newWebsocket(): WebSocket {
@@ -43,6 +46,9 @@ export class WebsocketMessenger extends Messenger {
for (const listener of this.onCloseListeners) {
this.onClose(listener);
}
+ for (const listener of this.onErrorListeners) {
+ this.onError(listener);
+ }
for (const messageType in this.onMessageListeners) {
for (const listener of this.onMessageListeners[messageType]) {
this.onMessageType(messageType, listener);
@@ -151,4 +157,8 @@ export class WebsocketMessenger extends Messenger {
onClose(callback: () => void): void {
this.websocket.addEventListener("close", callback);
}
+
+ onError(callback: () => void): void {
+ this.websocket.addEventListener("error", callback);
+ }
}
diff --git a/extension/src/util/util.ts b/extension/src/util/util.ts
index d33593e1..dfc10c90 100644
--- a/extension/src/util/util.ts
+++ b/extension/src/util/util.ts
@@ -1,5 +1,6 @@
import { RangeInFile, SerializedDebugContext } from "../client";
import * as fs from "fs";
+const os = require("os");
function charIsEscapedAtIndex(index: number, str: string): boolean {
if (index === 0) return false;
@@ -113,3 +114,31 @@ export function debounced(delay: number, fn: Function) {
}, delay);
};
}
+
+type Platform = "mac" | "linux" | "windows" | "unknown";
+
+function getPlatform(): Platform {
+ const platform = os.platform();
+ if (platform === "darwin") {
+ return "mac";
+ } else if (platform === "linux") {
+ return "linux";
+ } else if (platform === "win32") {
+ return "windows";
+ } else {
+ return "unknown";
+ }
+}
+
+export function getMetaKeyLabel() {
+ const platform = getPlatform();
+ switch (platform) {
+ case "mac":
+ return "⌘";
+ case "linux":
+ case "windows":
+ return "^";
+ default:
+ return "⌘";
+ }
+}
diff --git a/media/edit.gif b/media/edit.gif
new file mode 100644
index 00000000..6780cdf7
--- /dev/null
+++ b/media/edit.gif
Binary files differ
diff --git a/media/explain.gif b/media/explain.gif
new file mode 100644
index 00000000..e74803dc
--- /dev/null
+++ b/media/explain.gif
Binary files differ
diff --git a/media/generate.gif b/media/generate.gif
new file mode 100644
index 00000000..5c1d112b
--- /dev/null
+++ b/media/generate.gif
Binary files differ
diff --git a/media/help.gif b/media/help.gif
new file mode 100644
index 00000000..96c24b47
--- /dev/null
+++ b/media/help.gif
Binary files differ
diff --git a/media/highlight.gif b/media/highlight.gif
new file mode 100644
index 00000000..2b77383f
--- /dev/null
+++ b/media/highlight.gif
Binary files differ
diff --git a/media/intro.gif b/media/intro.gif
new file mode 100644
index 00000000..f872dc91
--- /dev/null
+++ b/media/intro.gif
Binary files differ
diff --git a/media/question.gif b/media/question.gif
new file mode 100644
index 00000000..be3fac3c
--- /dev/null
+++ b/media/question.gif
Binary files differ