Skip to content

Commit

Permalink
feat: allow user customizable prompts for reusable logic (#194)
Browse files Browse the repository at this point in the history
Co-authored-by: Jack Cherng <[email protected]>
  • Loading branch information
TerminalFi and jfcherng authored Jul 31, 2024
1 parent cc443a4 commit 6f209db
Show file tree
Hide file tree
Showing 7 changed files with 319 additions and 119 deletions.
17 changes: 17 additions & 0 deletions LSP-copilot.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@
// The (Jinja2) template of the status bar text which is inside the parentheses `(...)`.
// See https://jinja.palletsprojects.com/templates/
"status_text": "{% if is_copilot_ignored %}{{ is_copilot_ignored }}{% elif is_waiting %}{{ is_waiting }}{% elif server_version %}v{{ server_version }}{% endif %}",
"prompts": [
{
"id": "review",
"description": "Review code and provide feedback.",
"prompt": [
"Review the referenced code and provide feedback.",
"Feedback should first reply back with the line or lines of code, followed by the feedback about the code.",
"Do not invent new problems.",
"The feedback should be constructive and aim to improve the code quality.",
"If there are no issues detected, reply that the code looks good and no changes are necessary.",
"Group related feedback into a single comment if possible.",
"Present each comment with a brief description of the issue and a suggestion for improvement.",
"Use the format `Comment #: [description] [suggestion]` for each comment, # representing the number of comments.",
"At last provide a summary of the overall code quality and any general suggestions for improvement.",
]
}
]
},
// ST4 configuration
"selector": "source | text | embedding"
Expand Down
6 changes: 3 additions & 3 deletions plugin/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
ActivityIndicator,
CopilotIgnore,
GithubInfo,
prepare_completion_request,
prepare_completion_request_doc,
preprocess_completions,
preprocess_panel_completions,
)
Expand Down Expand Up @@ -399,7 +399,7 @@ def _request_completions(self, view: sublime.View, request: str, *, no_callback:
):
return

if not (params := prepare_completion_request(view)):
if not (doc := prepare_completion_request_doc(view)):
return

if no_callback:
Expand All @@ -410,7 +410,7 @@ def _request_completions(self, view: sublime.View, request: str, *, no_callback:
self._activity_indicator.start()
callback = functools.partial(self._on_get_completions, view, region=sel[0].to_tuple())

session.send_request_async(Request(request, params), callback)
session.send_request_async(Request(request, {"doc": doc}), callback)

def _on_get_completions(
self,
Expand Down
131 changes: 80 additions & 51 deletions plugin/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@
from .decorators import _must_be_active_view
from .helpers import (
GithubInfo,
prepare_completion_request,
prepare_completion_request_doc,
prepare_conversation_turn_request,
preprocess_chat_message,
preprocess_message_for_html,
)
from .types import (
CopilotPayloadConversationCreate,
CopilotPayloadConversationPreconditions,
CopilotPayloadConversationTemplate,
CopilotPayloadFileStatus,
CopilotPayloadGetVersion,
Expand All @@ -54,7 +57,8 @@
CopilotPayloadSignInConfirm,
CopilotPayloadSignInInitiate,
CopilotPayloadSignOut,
CopilotRequestCoversationAgent,
CopilotRequestConversationAgent,
CopilotUserDefinedPromptTemplates,
T_Callable,
)
from .ui import ViewCompletionManager, ViewPanelCompletionManager, WindowConversationManager
Expand Down Expand Up @@ -196,12 +200,14 @@ def run(self, view_id: int | None = None) -> None:
view = self.window.active_view()
else:
view = find_view_by_id(view_id)

if not view:
return

ViewPanelCompletionManager(view).close()


class CopilotConversationChatShimCommand(LspWindowCommand):
class CopilotConversationChatShimCommand(CopilotWindowCommand):
def run(self, window_id: int, message: str = "") -> None:
if not (window := find_window_by_id(window_id)):
return
Expand All @@ -213,7 +219,7 @@ def run(self, window_id: int, message: str = "") -> None:
view.run_command("copilot_conversation_chat", {"message": message})


class CopilotConversationChatCommand(LspTextCommand):
class CopilotConversationChatCommand(CopilotTextCommand):
@_provide_plugin_session()
def run(self, plugin: CopilotPlugin, session: Session, _: sublime.Edit, message: str = "") -> None:
if not (window := self.view.window()):
Expand All @@ -234,7 +240,11 @@ def run(self, plugin: CopilotPlugin, session: Session, _: sublime.Edit, message:
)

def _on_result_conversation_preconditions(
self, plugin: CopilotPlugin, session: Session, payload, initial_message: str
self,
plugin: CopilotPlugin,
session: Session,
payload: CopilotPayloadConversationPreconditions,
initial_message: str,
) -> None:
if not (window := self.view.window()):
return
Expand All @@ -243,7 +253,8 @@ def _on_result_conversation_preconditions(
if not (view := find_view_by_id(wcm.last_active_view_id)):
return

is_template, msg = preprocess_chat_message(view, initial_message)
user_prompts: list[CopilotUserDefinedPromptTemplates] = session.config.settings.get("prompts") or []
is_template, msg = preprocess_chat_message(view, initial_message, user_prompts)
if msg:
wcm.append_conversation_entry({
"kind": plugin.get_account_status().user or "user",
Expand Down Expand Up @@ -272,7 +283,12 @@ def _on_result_conversation_preconditions(
wcm.is_waiting = True
wcm.update()

def _on_result_conversation_create(self, plugin: CopilotPlugin, session: Session, payload) -> None:
def _on_result_conversation_create(
self,
plugin: CopilotPlugin,
session: Session,
payload: CopilotPayloadConversationCreate,
) -> None:
if not (window := self.view.window()):
return

Expand All @@ -292,8 +308,8 @@ def _on_prompt(self, plugin: CopilotPlugin, session: Session, msg: str):

if not (view := find_view_by_id(wcm.last_active_view_id)):
return

is_template, msg = preprocess_chat_message(view, msg)
user_prompts: list[CopilotUserDefinedPromptTemplates] = session.config.settings.get("prompts") or []
is_template, msg = preprocess_chat_message(view, msg, user_prompts)
wcm.append_conversation_entry({
"kind": plugin.get_account_status().user or "user",
"conversationId": wcm.conversation_id,
Expand All @@ -302,23 +318,11 @@ def _on_prompt(self, plugin: CopilotPlugin, session: Session, msg: str):
"annotations": [],
"hideText": False,
})

if not (request := prepare_completion_request(view)):
if not (request := prepare_conversation_turn_request(wcm.conversation_id, wcm.window.id(), msg, view)):
return

session.send_request(
Request(
REQ_CONVERSATION_TURN,
{
"conversationId": wcm.conversation_id,
"message": msg,
"workDoneToken": f"copilot_chat://{wcm.window.id()}",
"doc": request["doc"],
"computeSuggestions": True,
"references": [],
"source": "panel",
},
),
Request(REQ_CONVERSATION_TURN, request),
lambda _: wcm.prompt(callback=lambda x: self._on_prompt(plugin, session, x)),
)
wcm.is_waiting = True
Expand All @@ -335,15 +339,15 @@ def run(self, window_id: int | None = None) -> None:
WindowConversationManager(window).close()


class CopilotConversationRatingShimCommand(LspWindowCommand):
class CopilotConversationRatingShimCommand(CopilotWindowCommand):
def run(self, turn_id: str, rating: int) -> None:
wcm = WindowConversationManager(self.window)
if not (view := find_view_by_id(wcm.last_active_view_id)):
return
view.run_command("copilot_conversation_rating", {"turn_id": turn_id, "rating": rating})


class CopilotConversationRatingCommand(LspTextCommand):
class CopilotConversationRatingCommand(CopilotTextCommand):
@_provide_plugin_session()
def run(self, plugin: CopilotPlugin, session: Session, _: sublime.Edit, turn_id: str, rating: int) -> None:
session.send_request(
Expand All @@ -354,23 +358,23 @@ def run(self, plugin: CopilotPlugin, session: Session, _: sublime.Edit, turn_id:
"rating": rating,
},
),
self._on_result_coversation_rating,
self._on_result_conversation_rating,
)

def _on_result_coversation_rating(self, payload: Literal["OK"]) -> None:
def _on_result_conversation_rating(self, payload: Literal["OK"]) -> None:
# Returns OK
pass


class CopilotConversationDestroyShimCommand(LspWindowCommand):
class CopilotConversationDestroyShimCommand(CopilotWindowCommand):
def run(self, conversation_id: str) -> None:
wcm = WindowConversationManager(self.window)
if not (view := find_view_by_id(wcm.last_active_view_id)):
return
view.run_command("copilot_conversation_destroy", {"conversation_id": conversation_id})


class CopilotConversationDestroyCommand(LspTextCommand):
class CopilotConversationDestroyCommand(CopilotTextCommand):
@_provide_plugin_session()
def run(self, plugin: CopilotPlugin, session: Session, _: sublime.Edit, conversation_id: str) -> None:
if not (
Expand All @@ -388,10 +392,10 @@ def run(self, plugin: CopilotPlugin, session: Session, _: sublime.Edit, conversa
"options": {},
},
),
self._on_result_coversation_destroy,
self._on_result_conversation_destroy,
)

def _on_result_coversation_destroy(self, payload) -> None:
def _on_result_conversation_destroy(self, payload: str) -> None:
if not (window := self.view.window()):
return
if payload != "OK":
Expand All @@ -403,13 +407,16 @@ def _on_result_coversation_destroy(self, payload) -> None:
wcm.close()
wcm.reset()

def is_enabled(self, event: dict[Any, Any] | None = None, point: int | None = None) -> bool:
def is_enabled(self, event: dict[Any, Any] | None = None, point: int | None = None) -> bool: # type: ignore
if not (window := self.view.window()):
return False
return super().is_enabled() and bool(WindowConversationManager(window).conversation_id)
return bool(
super().is_enabled() # type: ignore
and WindowConversationManager(window).conversation_id
)


class CopilotConversationTurnDeleteShimCommand(LspWindowCommand):
class CopilotConversationTurnDeleteShimCommand(CopilotWindowCommand):
def run(self, window_id: int, conversation_id: str, turn_id: str) -> None:
wcm = WindowConversationManager(self.window)
if not (view := find_view_by_id(wcm.last_active_view_id)):
Expand All @@ -420,7 +427,7 @@ def run(self, window_id: int, conversation_id: str, turn_id: str) -> None:
)


class CopilotConversationTurnDeleteCommand(LspTextCommand):
class CopilotConversationTurnDeleteCommand(CopilotTextCommand):
@_provide_plugin_session()
def run(
self,
Expand Down Expand Up @@ -453,10 +460,16 @@ def run(
"options": {},
},
),
lambda x: self._on_result_coversation_turn_delete(window_id, conversation_id, turn_id, x),
lambda x: self._on_result_conversation_turn_delete(window_id, conversation_id, turn_id, x),
)

def _on_result_coversation_turn_delete(self, window_id: int, conversation_id: str, turn_id: str, payload) -> None:
def _on_result_conversation_turn_delete(
self,
window_id: int,
conversation_id: str,
turn_id: str,
payload: str,
) -> None:
if payload != "OK":
status_message("Failed to delete turn.")
return
Expand All @@ -476,7 +489,7 @@ def _on_result_coversation_turn_delete(self, window_id: int, conversation_id: st
wcm.update()


class CopilotConversationCopyCodeCommand(LspWindowCommand):
class CopilotConversationCopyCodeCommand(CopilotWindowCommand):
def run(self, window_id: int, code_block_index: int) -> None:
if not (window := find_window_by_id(window_id)):
return
Expand All @@ -488,7 +501,7 @@ def run(self, window_id: int, code_block_index: int) -> None:
sublime.set_clipboard(code)


class CopilotConversationInsertCodeShimCommand(LspWindowCommand):
class CopilotConversationInsertCodeShimCommand(CopilotWindowCommand):
def run(self, window_id: int, code_block_index: int) -> None:
if not (window := find_window_by_id(window_id)):
return
Expand All @@ -503,7 +516,7 @@ def run(self, window_id: int, code_block_index: int) -> None:
view.run_command("copilot_conversation_insert_code", {"characters": code})


class CopilotConversationInsertCodeCommand(LspTextCommand):
class CopilotConversationInsertCodeCommand(CopilotTextCommand):
def run(self, edit: sublime.Edit, characters: str) -> None:
if len(self.view.sel()) > 1:
return
Expand All @@ -513,34 +526,50 @@ def run(self, edit: sublime.Edit, characters: str) -> None:
self.view.insert(edit, begin, characters)


class CopilotConversationAgentsCommand(LspTextCommand):
class CopilotConversationAgentsCommand(CopilotTextCommand):
@_provide_plugin_session()
def run(self, plugin: CopilotPlugin, session: Session, _: sublime.Edit) -> None:
session.send_request(Request(REQ_CONVERSATION_AGENTS, {"options": {}}), self._on_result_coversation_agents)
session.send_request(Request(REQ_CONVERSATION_AGENTS, {"options": {}}), self._on_result_conversation_agents)

def _on_result_coversation_agents(self, payload: list[CopilotRequestCoversationAgent]) -> None:
def _on_result_conversation_agents(self, payload: list[CopilotRequestConversationAgent]) -> None:
window = self.view.window()
if not window:
return
window.show_quick_panel([[item["slug"], item["description"]] for item in payload], lambda _: None)


class CopilotConversationTemplatesCommand(LspTextCommand):
class CopilotConversationTemplatesCommand(CopilotTextCommand):
@_provide_plugin_session()
def run(self, plugin: CopilotPlugin, session: Session, _: sublime.Edit) -> None:
user_prompts: list[CopilotUserDefinedPromptTemplates] = session.config.settings.get("prompts") or []
session.send_request(
Request(REQ_CONVERSATION_TEMPLATES, {"options": {}}), self._on_result_conversation_templates
Request(REQ_CONVERSATION_TEMPLATES, {"options": {}}),
lambda payload: self._on_result_conversation_templates(user_prompts, payload),
)

def _on_result_conversation_templates(self, payload: list[CopilotPayloadConversationTemplate]) -> None:
def _on_result_conversation_templates(
self,
user_prompts: list[CopilotUserDefinedPromptTemplates],
payload: list[CopilotPayloadConversationTemplate],
) -> None:
if not (window := self.view.window()):
return

templates = payload + user_prompts
prompts = [
[item["id"], item["description"], ", ".join(item["scopes"]) if item.get("scopes", None) else "chat-panel"]
for item in templates
]
window.show_quick_panel(
[[item["id"], item["description"], ", ".join(item["scopes"])] for item in payload],
lambda index: self._on_selected(index, payload),
prompts,
lambda index: self._on_selected(index, templates),
)

def _on_selected(self, index: int, items: list[CopilotPayloadConversationTemplate]) -> None:
def _on_selected(
self,
index: int,
items: list[CopilotPayloadConversationTemplate | CopilotUserDefinedPromptTemplates],
) -> None:
if index == -1:
return
self.view.run_command("copilot_conversation_chat", {"message": f'/{items[index]["id"]}'})
Expand Down Expand Up @@ -588,15 +617,15 @@ def run(self, plugin: CopilotPlugin, session: Session, _: sublime.Edit) -> None:
class CopilotGetPanelCompletionsCommand(CopilotTextCommand):
@_provide_plugin_session()
def run(self, plugin: CopilotPlugin, session: Session, _: sublime.Edit) -> None:
if not (params := prepare_completion_request(self.view)):
if not (doc := prepare_completion_request_doc(self.view)):
return

vcm = ViewPanelCompletionManager(self.view)
vcm.is_waiting = True
vcm.is_visible = True
vcm.completions = []

params["panelId"] = vcm.panel_id
params = {"doc": doc, "panelId": vcm.panel_id}
session.send_request(Request(REQ_GET_PANEL_COMPLETIONS, params), self._on_result_get_panel_completions)

def _on_result_get_panel_completions(self, payload: CopilotPayloadPanelCompletionSolutionCount) -> None:
Expand Down
Loading

0 comments on commit 6f209db

Please sign in to comment.