From a993b2ff472b9999ce287c2eccefd9371204f3c2 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 24 Jul 2023 22:53:10 -0700 Subject: config cleaning --- continuedev/src/continuedev/core/sdk.py | 3 +- .../libs/constants/default_config.py.txt | 87 ++++++++++ continuedev/src/continuedev/libs/util/paths.py | 19 ++ .../continuedev/plugins/context_providers/file.py | 56 ++++++ .../context_providers/file_context_provider.py | 56 ------ .../plugins/context_providers/github.py | 35 ++++ .../plugins/context_providers/google.py | 64 +++++++ .../plugins/context_providers/highlighted_code.py | 191 +++++++++++++++++++++ .../highlighted_code_context_provider.py | 191 --------------------- .../src/continuedev/plugins/steps/open_config.py | 5 +- 10 files changed, 456 insertions(+), 251 deletions(-) create mode 100644 continuedev/src/continuedev/libs/constants/default_config.py.txt create mode 100644 continuedev/src/continuedev/plugins/context_providers/file.py delete mode 100644 continuedev/src/continuedev/plugins/context_providers/file_context_provider.py create mode 100644 continuedev/src/continuedev/plugins/context_providers/github.py create mode 100644 continuedev/src/continuedev/plugins/context_providers/google.py create mode 100644 continuedev/src/continuedev/plugins/context_providers/highlighted_code.py delete mode 100644 continuedev/src/continuedev/plugins/context_providers/highlighted_code_context_provider.py (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 6668c8c3..e9aefa76 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -18,6 +18,7 @@ from .main import Context, ContinueCustomException, History, HistoryNode, Step, from ..plugins.steps.core.core import * from ..libs.llm.proxy_server import ProxyServer from ..libs.util.telemetry import posthog_logger +from ..libs.util.paths import getConfigFilePath class Autopilot: @@ -258,7 +259,7 @@ class ContinueSDK(AbstractContinueSDK): def _load_config_dot_py(self) -> ContinueConfig: # Use importlib to load the config file config.py at the given path - path = os.path.join(os.path.expanduser("~"), ".continue", "config.py") + path = getConfigFilePath() try: import importlib.util spec = importlib.util.spec_from_file_location("config", path) diff --git a/continuedev/src/continuedev/libs/constants/default_config.py.txt b/continuedev/src/continuedev/libs/constants/default_config.py.txt new file mode 100644 index 00000000..f80a9ff0 --- /dev/null +++ b/continuedev/src/continuedev/libs/constants/default_config.py.txt @@ -0,0 +1,87 @@ +""" +This is the Continue configuration file. + +If you aren't getting strong typing on these imports, +be sure to select the Python interpreter in ~/.continue/server/env. +""" + +import subprocess + +from continuedev.src.continuedev.core.main import Step +from continuedev.src.continuedev.core.sdk import ContinueSDK +from continuedev.src.continuedev.core.config import CustomCommand, SlashCommand, ContinueConfig +from continuedev.src.continuedev.plugins.context_providers.github import GitHubIssuesContextProvider +from continuedev.src.continuedev.plugins.context_providers.google import GoogleContextProvider + + +class CommitMessageStep(Step): + """ + This is a Step, the building block of Continue. + It can be used below as a slash command, so that + run will be called when you type '/commit'. + """ + async def run(self, sdk: ContinueSDK): + + # Get the root directory of the workspace + dir = sdk.ide.workspace_directory + + # Run git diff in that directory + diff = subprocess.check_output( + ["git", "diff"], cwd=dir).decode("utf-8") + + # Ask gpt-3.5-16k to write a commit message, + # and set it as the description of this step + self.description = await sdk.models.gpt3516k.complete( + f"{diff}\n\nWrite a short, specific (less than 50 chars) commit message about the above changes:") + + +config = ContinueConfig( + + # If set to False, we will not collect any usage data + # See here to learn what anonymous data we collect: https://continue.dev/docs/telemetry + allow_anonymous_telemetry=True, + + # GPT-4 is recommended for best results + # See options here: https://continue.dev/docs/customization#change-the-default-llm + default_model="gpt-4", + + # Set a system message with information that the LLM should always keep in mind + # E.g. "Please give concise answers. Always respond in Spanish." + system_message=None, + + # Set temperature to any value between 0 and 1. Higher values will make the LLM + # more creative, while lower values will make it more predictable. + temperature=0.5, + + # Custom commands let you map a prompt to a shortened slash command + # They are like slash commands, but more easily defined - write just a prompt instead of a Step class + # Their output will always be in chat form + custom_commands=[CustomCommand( + name="test", + description="This is an example custom command. Use /config to edit it and create more", + 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. Give the tests just as chat output, don't edit any file.", + )], + + # Slash commands let you run a Step from a slash command + slash_commands=[ + # SlashCommand( + # name="commit", + # description="This is an example slash command. Use /config to edit it and create more", + # step=CommitMessageStep, + # ) + ], + + # Context providers let you quickly select context by typing '@' + # Uncomment the following to + # - quickly reference GitHub issues + # - show Google search results to the LLM + context_providers=[ + # GitHubIssuesContextProvider( + # repo_name="/", + # auth_token="" + # ), + # GoogleContextProvider( + # serper_api_key="" + # ) + ] +) diff --git a/continuedev/src/continuedev/libs/util/paths.py b/continuedev/src/continuedev/libs/util/paths.py index d6ce13b3..14a97f57 100644 --- a/continuedev/src/continuedev/libs/util/paths.py +++ b/continuedev/src/continuedev/libs/util/paths.py @@ -25,3 +25,22 @@ def getSessionFilePath(session_id: str): path = os.path.join(getSessionsFolderPath(), f"{session_id}.json") os.makedirs(os.path.dirname(path), exist_ok=True) return path + + +def getDefaultConfigFile() -> str: + current_path = os.path.dirname(os.path.realpath(__file__)) + config_path = os.path.join( + current_path, "..", "constants", "default_config.py.txt") + with open(config_path, 'r') as f: + return f.read() + + +def getConfigFilePath() -> str: + path = os.path.join(getGlobalFolderPath(), "config.py") + os.makedirs(os.path.dirname(path), exist_ok=True) + + if not os.path.exists(path): + with open(path, 'w') as f: + f.write(getDefaultConfigFile()) + + return path diff --git a/continuedev/src/continuedev/plugins/context_providers/file.py b/continuedev/src/continuedev/plugins/context_providers/file.py new file mode 100644 index 00000000..632a876c --- /dev/null +++ b/continuedev/src/continuedev/plugins/context_providers/file.py @@ -0,0 +1,56 @@ +import os +import re +from typing import List +from ...core.main import ContextItem, ContextItemDescription, ContextItemId +from ...core.context import ContextProvider +from fnmatch import fnmatch + + +def get_file_contents(filepath: str) -> str: + with open(filepath, "r") as f: + return f.read() + + +class FileContextProvider(ContextProvider): + """ + The FileContextProvider is a ContextProvider that allows you to search files in the open workspace. + """ + + title = "file" + workspace_dir: str + ignore_patterns: List[str] = list(map(lambda folder: f"**/{folder}", [ + ".git", + ".vscode", + ".idea", + ".vs", + ".venv", + "env", + ".env", + "node_modules", + "dist", + "build", + "target", + "out", + "bin", + ])) + + async def provide_context_items(self) -> List[ContextItem]: + filepaths = [] + for root, dir_names, file_names in os.walk(self.workspace_dir): + dir_names[:] = [d for d in dir_names if not any( + fnmatch(d, pattern) for pattern in self.ignore_patterns)] + for file_name in file_names: + filepaths.append(os.path.join(root, file_name)) + + return [ContextItem( + content=get_file_contents(file)[:min( + 2000, len(get_file_contents(file)))], + description=ContextItemDescription( + name=os.path.basename(file), + description=file, + id=ContextItemId( + provider_title=self.title, + item_id=re.sub(r'[^0-9a-zA-Z_-]', '', file) + ) + ) + ) for file in filepaths] diff --git a/continuedev/src/continuedev/plugins/context_providers/file_context_provider.py b/continuedev/src/continuedev/plugins/context_providers/file_context_provider.py deleted file mode 100644 index 632a876c..00000000 --- a/continuedev/src/continuedev/plugins/context_providers/file_context_provider.py +++ /dev/null @@ -1,56 +0,0 @@ -import os -import re -from typing import List -from ...core.main import ContextItem, ContextItemDescription, ContextItemId -from ...core.context import ContextProvider -from fnmatch import fnmatch - - -def get_file_contents(filepath: str) -> str: - with open(filepath, "r") as f: - return f.read() - - -class FileContextProvider(ContextProvider): - """ - The FileContextProvider is a ContextProvider that allows you to search files in the open workspace. - """ - - title = "file" - workspace_dir: str - ignore_patterns: List[str] = list(map(lambda folder: f"**/{folder}", [ - ".git", - ".vscode", - ".idea", - ".vs", - ".venv", - "env", - ".env", - "node_modules", - "dist", - "build", - "target", - "out", - "bin", - ])) - - async def provide_context_items(self) -> List[ContextItem]: - filepaths = [] - for root, dir_names, file_names in os.walk(self.workspace_dir): - dir_names[:] = [d for d in dir_names if not any( - fnmatch(d, pattern) for pattern in self.ignore_patterns)] - for file_name in file_names: - filepaths.append(os.path.join(root, file_name)) - - return [ContextItem( - content=get_file_contents(file)[:min( - 2000, len(get_file_contents(file)))], - description=ContextItemDescription( - name=os.path.basename(file), - description=file, - id=ContextItemId( - provider_title=self.title, - item_id=re.sub(r'[^0-9a-zA-Z_-]', '', file) - ) - ) - ) for file in filepaths] diff --git a/continuedev/src/continuedev/plugins/context_providers/github.py b/continuedev/src/continuedev/plugins/context_providers/github.py new file mode 100644 index 00000000..765a534d --- /dev/null +++ b/continuedev/src/continuedev/plugins/context_providers/github.py @@ -0,0 +1,35 @@ +from typing import List +from github import Github +from github import Auth + +from ...core.context import ContextProvider, ContextItemDescription, ContextItem, ContextItemId + + +class GitHubIssuesContextProvider(ContextProvider): + """ + The GitHubIssuesContextProvider is a ContextProvider + that allows you to search GitHub issues in a repo. + """ + + title = "issues" + repo_name: str + auth_token: str + + async def provide_context_items(self) -> List[ContextItem]: + auth = Auth.Token(self.auth_token) + gh = Github(auth=auth) + + repo = gh.get_repo(self.repo_name) + issues = repo.get_issues().get_page(0) + + return [ContextItem( + content=issue.body, + description=ContextItemDescription( + name=f"Issue #{issue.number}", + description=issue.title, + id=ContextItemId( + provider_title=self.title, + item_id=issue.id + ) + ) + ) for issue in issues] diff --git a/continuedev/src/continuedev/plugins/context_providers/google.py b/continuedev/src/continuedev/plugins/context_providers/google.py new file mode 100644 index 00000000..64954833 --- /dev/null +++ b/continuedev/src/continuedev/plugins/context_providers/google.py @@ -0,0 +1,64 @@ +import json +from typing import List + +import aiohttp +from ...core.main import ContextItem, ContextItemDescription, ContextItemId +from ...core.context import ContextProvider + + +class GoogleContextProvider(ContextProvider): + title = "google" + + serper_api_key: str + + GOOGLE_CONTEXT_ITEM_ID = "google_search" + + @property + def BASE_CONTEXT_ITEM(self): + return ContextItem( + content="", + description=ContextItemDescription( + name="Google Search", + description="Enter a query to search google", + id=ContextItemId( + provider_title=self.title, + item_id=self.GOOGLE_CONTEXT_ITEM_ID + ) + ) + ) + + async def _google_search(self, query: str) -> str: + url = "https://google.serper.dev/search" + + payload = json.dumps({ + "q": query + }) + headers = { + 'X-API-KEY': self.serper_api_key, + 'Content-Type': 'application/json' + } + + async with aiohttp.ClientSession() as session: + async with session.post(url, headers=headers, data=payload) as response: + return await response.text() + + async def provide_context_items(self) -> List[ContextItem]: + return [self.BASE_CONTEXT_ITEM] + + async def get_item(self, id: ContextItemId, query: str, _) -> ContextItem: + if not id.item_id == self.GOOGLE_CONTEXT_ITEM_ID: + raise Exception("Invalid item id") + + results = await self._google_search(query) + json_results = json.loads(results) + content = f"Google Search: {query}\n\n" + if answerBox := json_results.get("answerBox"): + content += f"Answer Box ({answerBox['title']}): {answerBox['answer']}\n\n" + + for result in json_results["organic"]: + content += f"{result['title']}\n{result['link']}\n{result['snippet']}\n\n" + + ctx_item = self.BASE_CONTEXT_ITEM.copy() + ctx_item.content = content + ctx_item.description.id.item_id = query + return ctx_item diff --git a/continuedev/src/continuedev/plugins/context_providers/highlighted_code.py b/continuedev/src/continuedev/plugins/context_providers/highlighted_code.py new file mode 100644 index 00000000..23d4fc86 --- /dev/null +++ b/continuedev/src/continuedev/plugins/context_providers/highlighted_code.py @@ -0,0 +1,191 @@ +import os +from typing import Any, Dict, List + +import meilisearch +from ...core.main import ChatMessage +from ...models.filesystem import RangeInFile, RangeInFileWithContents +from ...core.context import ContextItem, ContextItemDescription, ContextItemId +from pydantic import BaseModel + + +class HighlightedRangeContextItem(BaseModel): + rif: RangeInFileWithContents + item: ContextItem + + +class HighlightedCodeContextProvider(BaseModel): + """ + The ContextProvider class is a plugin that lets you provide new information to the LLM by typing '@'. + When you type '@', the context provider will be asked to populate a list of options. + These options will be updated on each keystroke. + When you hit enter on an option, the context provider will add that item to the autopilot's list of context (which is all stored in the ContextManager object). + """ + + title = "code" + + ide: Any # IdeProtocolServer + + highlighted_ranges: List[HighlightedRangeContextItem] = [] + adding_highlighted_code: bool = False + + should_get_fallback_context_item: bool = True + last_added_fallback: bool = False + + async def _get_fallback_context_item(self) -> HighlightedRangeContextItem: + if not self.should_get_fallback_context_item: + return None + + visible_files = await self.ide.getVisibleFiles() + if len(visible_files) > 0: + content = await self.ide.readFile(visible_files[0]) + rif = RangeInFileWithContents.from_entire_file( + visible_files[0], content) + + item = self._rif_to_context_item(rif, 0, True) + item.description.name = self._rif_to_name( + rif, show_line_nums=False) + + self.last_added_fallback = True + return HighlightedRangeContextItem(rif=rif, item=item) + + return None + + async def get_selected_items(self) -> List[ContextItem]: + items = [hr.item for hr in self.highlighted_ranges] + + if len(items) == 0 and (fallback_item := await self._get_fallback_context_item()): + items = [fallback_item.item] + + return items + + async def get_chat_messages(self) -> List[ContextItem]: + ranges = self.highlighted_ranges + if len(ranges) == 0 and (fallback_item := await self._get_fallback_context_item()): + ranges = [fallback_item] + + return [ChatMessage( + role="user", + content=f"Code in this file is highlighted ({r.rif.filepath}):\n```\n{r.rif.contents}\n```", + summary=f"Code in this file is highlighted: {r.rif.filepath}" + ) for r in ranges] + + def _make_sure_is_editing_range(self): + """If none of the highlighted ranges are currently being edited, the first should be selected""" + if len(self.highlighted_ranges) == 0: + return + if not any(map(lambda x: x.item.editing, self.highlighted_ranges)): + self.highlighted_ranges[0].item.editing = True + + def _disambiguate_highlighted_ranges(self): + """If any files have the same name, also display their folder name""" + name_status: Dict[str, set] = { + } # basename -> set of full paths with that basename + for hr in self.highlighted_ranges: + basename = os.path.basename(hr.rif.filepath) + if basename in name_status: + name_status[basename].add(hr.rif.filepath) + else: + name_status[basename] = {hr.rif.filepath} + + for hr in self.highlighted_ranges: + if len(name_status[basename]) > 1: + hr.item.description.name = self._rif_to_name(hr.rif, display_filename=os.path.join( + os.path.basename(os.path.dirname(hr.rif.filepath)), basename)) + else: + hr.item.description.name = self._rif_to_name( + hr.rif, display_filename=basename) + + async def provide_context_items(self) -> List[ContextItem]: + return [] + + async def delete_context_with_ids(self, ids: List[ContextItemId]) -> List[ContextItem]: + indices_to_delete = [ + int(id.item_id) for id in ids + ] + + kept_ranges = [] + for i, hr in enumerate(self.highlighted_ranges): + if i not in indices_to_delete: + kept_ranges.append(hr) + self.highlighted_ranges = kept_ranges + + self._make_sure_is_editing_range() + + if len(self.highlighted_ranges) == 0 and self.last_added_fallback: + self.should_get_fallback_context_item = False + + return [hr.item for hr in self.highlighted_ranges] + + def _rif_to_name(self, rif: RangeInFileWithContents, display_filename: str = None, show_line_nums: bool = True) -> str: + line_nums = f" ({rif.range.start.line + 1}-{rif.range.end.line + 1})" if show_line_nums else "" + return f"{display_filename or os.path.basename(rif.filepath)}{line_nums}" + + def _rif_to_context_item(self, rif: RangeInFileWithContents, idx: int, editing: bool) -> ContextItem: + return ContextItem( + description=ContextItemDescription( + name=self._rif_to_name(rif), + description=rif.filepath, + id=ContextItemId( + provider_title=self.title, + item_id=str(idx) + ) + ), + content=rif.contents, + editing=editing, + editable=True + ) + + async def handle_highlighted_code(self, range_in_files: List[RangeInFileWithContents]): + self.should_get_fallback_context_item = True + self.last_added_fallback = False + + # Filter out rifs from ~/.continue/diffs folder + range_in_files = [ + rif for rif in range_in_files if not os.path.dirname(rif.filepath) == os.path.expanduser("~/.continue/diffs")] + + # If not adding highlighted code + if not self.adding_highlighted_code: + if len(self.highlighted_ranges) == 1 and len(range_in_files) <= 1 and (len(range_in_files) == 0 or range_in_files[0].range.start == range_in_files[0].range.end): + # If un-highlighting the range to edit, then remove the range + self.highlighted_ranges = [] + elif len(range_in_files) > 0: + # Otherwise, replace the current range with the new one + # This is the first range to be highlighted + self.highlighted_ranges = [ + HighlightedRangeContextItem( + rif=range_in_files[0], + item=self._rif_to_context_item(range_in_files[0], 0, True))] + + return + + # If current range overlaps with any others, delete them and only keep the new range + new_ranges = [] + for i, hr in enumerate(self.highlighted_ranges): + found_overlap = False + for new_rif in range_in_files: + if hr.rif.filepath == new_rif.filepath and hr.rif.range.overlaps_with(new_rif.range): + found_overlap = True + break + + # Also don't allow multiple ranges in same file with same content. This is useless to the model, and avoids + # the bug where cmd+f causes repeated highlights + if hr.rif.filepath == new_rif.filepath and hr.rif.contents == new_rif.contents: + found_overlap = True + break + + if not found_overlap: + new_ranges.append(HighlightedRangeContextItem(rif=hr.rif, item=self._rif_to_context_item( + hr.rif, len(new_ranges), False))) + + self.highlighted_ranges = new_ranges + [HighlightedRangeContextItem(rif=rif, item=self._rif_to_context_item( + rif, len(new_ranges) + idx, False)) for idx, rif in enumerate(range_in_files)] + + self._make_sure_is_editing_range() + self._disambiguate_highlighted_ranges() + + async def set_editing_at_ids(self, ids: List[str]): + for hr in self.highlighted_ranges: + hr.item.editing = hr.item.description.id.to_string() in ids + + async def add_context_item(self, id: ContextItemId, query: str, search_client: meilisearch.Client, prev: List[ContextItem] = None) -> List[ContextItem]: + raise NotImplementedError() diff --git a/continuedev/src/continuedev/plugins/context_providers/highlighted_code_context_provider.py b/continuedev/src/continuedev/plugins/context_providers/highlighted_code_context_provider.py deleted file mode 100644 index 23d4fc86..00000000 --- a/continuedev/src/continuedev/plugins/context_providers/highlighted_code_context_provider.py +++ /dev/null @@ -1,191 +0,0 @@ -import os -from typing import Any, Dict, List - -import meilisearch -from ...core.main import ChatMessage -from ...models.filesystem import RangeInFile, RangeInFileWithContents -from ...core.context import ContextItem, ContextItemDescription, ContextItemId -from pydantic import BaseModel - - -class HighlightedRangeContextItem(BaseModel): - rif: RangeInFileWithContents - item: ContextItem - - -class HighlightedCodeContextProvider(BaseModel): - """ - The ContextProvider class is a plugin that lets you provide new information to the LLM by typing '@'. - When you type '@', the context provider will be asked to populate a list of options. - These options will be updated on each keystroke. - When you hit enter on an option, the context provider will add that item to the autopilot's list of context (which is all stored in the ContextManager object). - """ - - title = "code" - - ide: Any # IdeProtocolServer - - highlighted_ranges: List[HighlightedRangeContextItem] = [] - adding_highlighted_code: bool = False - - should_get_fallback_context_item: bool = True - last_added_fallback: bool = False - - async def _get_fallback_context_item(self) -> HighlightedRangeContextItem: - if not self.should_get_fallback_context_item: - return None - - visible_files = await self.ide.getVisibleFiles() - if len(visible_files) > 0: - content = await self.ide.readFile(visible_files[0]) - rif = RangeInFileWithContents.from_entire_file( - visible_files[0], content) - - item = self._rif_to_context_item(rif, 0, True) - item.description.name = self._rif_to_name( - rif, show_line_nums=False) - - self.last_added_fallback = True - return HighlightedRangeContextItem(rif=rif, item=item) - - return None - - async def get_selected_items(self) -> List[ContextItem]: - items = [hr.item for hr in self.highlighted_ranges] - - if len(items) == 0 and (fallback_item := await self._get_fallback_context_item()): - items = [fallback_item.item] - - return items - - async def get_chat_messages(self) -> List[ContextItem]: - ranges = self.highlighted_ranges - if len(ranges) == 0 and (fallback_item := await self._get_fallback_context_item()): - ranges = [fallback_item] - - return [ChatMessage( - role="user", - content=f"Code in this file is highlighted ({r.rif.filepath}):\n```\n{r.rif.contents}\n```", - summary=f"Code in this file is highlighted: {r.rif.filepath}" - ) for r in ranges] - - def _make_sure_is_editing_range(self): - """If none of the highlighted ranges are currently being edited, the first should be selected""" - if len(self.highlighted_ranges) == 0: - return - if not any(map(lambda x: x.item.editing, self.highlighted_ranges)): - self.highlighted_ranges[0].item.editing = True - - def _disambiguate_highlighted_ranges(self): - """If any files have the same name, also display their folder name""" - name_status: Dict[str, set] = { - } # basename -> set of full paths with that basename - for hr in self.highlighted_ranges: - basename = os.path.basename(hr.rif.filepath) - if basename in name_status: - name_status[basename].add(hr.rif.filepath) - else: - name_status[basename] = {hr.rif.filepath} - - for hr in self.highlighted_ranges: - if len(name_status[basename]) > 1: - hr.item.description.name = self._rif_to_name(hr.rif, display_filename=os.path.join( - os.path.basename(os.path.dirname(hr.rif.filepath)), basename)) - else: - hr.item.description.name = self._rif_to_name( - hr.rif, display_filename=basename) - - async def provide_context_items(self) -> List[ContextItem]: - return [] - - async def delete_context_with_ids(self, ids: List[ContextItemId]) -> List[ContextItem]: - indices_to_delete = [ - int(id.item_id) for id in ids - ] - - kept_ranges = [] - for i, hr in enumerate(self.highlighted_ranges): - if i not in indices_to_delete: - kept_ranges.append(hr) - self.highlighted_ranges = kept_ranges - - self._make_sure_is_editing_range() - - if len(self.highlighted_ranges) == 0 and self.last_added_fallback: - self.should_get_fallback_context_item = False - - return [hr.item for hr in self.highlighted_ranges] - - def _rif_to_name(self, rif: RangeInFileWithContents, display_filename: str = None, show_line_nums: bool = True) -> str: - line_nums = f" ({rif.range.start.line + 1}-{rif.range.end.line + 1})" if show_line_nums else "" - return f"{display_filename or os.path.basename(rif.filepath)}{line_nums}" - - def _rif_to_context_item(self, rif: RangeInFileWithContents, idx: int, editing: bool) -> ContextItem: - return ContextItem( - description=ContextItemDescription( - name=self._rif_to_name(rif), - description=rif.filepath, - id=ContextItemId( - provider_title=self.title, - item_id=str(idx) - ) - ), - content=rif.contents, - editing=editing, - editable=True - ) - - async def handle_highlighted_code(self, range_in_files: List[RangeInFileWithContents]): - self.should_get_fallback_context_item = True - self.last_added_fallback = False - - # Filter out rifs from ~/.continue/diffs folder - range_in_files = [ - rif for rif in range_in_files if not os.path.dirname(rif.filepath) == os.path.expanduser("~/.continue/diffs")] - - # If not adding highlighted code - if not self.adding_highlighted_code: - if len(self.highlighted_ranges) == 1 and len(range_in_files) <= 1 and (len(range_in_files) == 0 or range_in_files[0].range.start == range_in_files[0].range.end): - # If un-highlighting the range to edit, then remove the range - self.highlighted_ranges = [] - elif len(range_in_files) > 0: - # Otherwise, replace the current range with the new one - # This is the first range to be highlighted - self.highlighted_ranges = [ - HighlightedRangeContextItem( - rif=range_in_files[0], - item=self._rif_to_context_item(range_in_files[0], 0, True))] - - return - - # If current range overlaps with any others, delete them and only keep the new range - new_ranges = [] - for i, hr in enumerate(self.highlighted_ranges): - found_overlap = False - for new_rif in range_in_files: - if hr.rif.filepath == new_rif.filepath and hr.rif.range.overlaps_with(new_rif.range): - found_overlap = True - break - - # Also don't allow multiple ranges in same file with same content. This is useless to the model, and avoids - # the bug where cmd+f causes repeated highlights - if hr.rif.filepath == new_rif.filepath and hr.rif.contents == new_rif.contents: - found_overlap = True - break - - if not found_overlap: - new_ranges.append(HighlightedRangeContextItem(rif=hr.rif, item=self._rif_to_context_item( - hr.rif, len(new_ranges), False))) - - self.highlighted_ranges = new_ranges + [HighlightedRangeContextItem(rif=rif, item=self._rif_to_context_item( - rif, len(new_ranges) + idx, False)) for idx, rif in enumerate(range_in_files)] - - self._make_sure_is_editing_range() - self._disambiguate_highlighted_ranges() - - async def set_editing_at_ids(self, ids: List[str]): - for hr in self.highlighted_ranges: - hr.item.editing = hr.item.description.id.to_string() in ids - - async def add_context_item(self, id: ContextItemId, query: str, search_client: meilisearch.Client, prev: List[ContextItem] = None) -> List[ContextItem]: - raise NotImplementedError() diff --git a/continuedev/src/continuedev/plugins/steps/open_config.py b/continuedev/src/continuedev/plugins/steps/open_config.py index 7264a59b..64ead547 100644 --- a/continuedev/src/continuedev/plugins/steps/open_config.py +++ b/continuedev/src/continuedev/plugins/steps/open_config.py @@ -1,6 +1,7 @@ from textwrap import dedent from ...core.main import Step from ...core.sdk import ContinueSDK +from ...libs.util.paths import getConfigFilePath import os @@ -25,6 +26,4 @@ class OpenConfigStep(Step): `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') - config_path = os.path.join(global_dir, 'config.py') - await sdk.ide.setFileOpen(config_path) + await sdk.ide.setFileOpen(getConfigFilePath()) -- cgit v1.2.3-70-g09d2