From e32ddd135d50bdc17b44bde4a22d6e3474095bbf Mon Sep 17 00:00:00 2001 From: Jack Cherng Date: Fri, 21 Jun 2024 02:26:46 +0800 Subject: [PATCH 1/5] refactor: use Jinja2 for completion popup / panel completion (#171) --- .editorconfig | 3 + plugin/client.py | 4 +- plugin/template.py | 12 +- plugin/templates/completion@popup.md.jinja | 95 ++++++++++++++++ plugin/templates/panel_completion.md.jinja | 84 ++++++++++++++ plugin/ui/completion.py | 123 ++------------------- plugin/ui/panel_completion.py | 117 +++----------------- 7 files changed, 221 insertions(+), 217 deletions(-) create mode 100644 plugin/templates/completion@popup.md.jinja create mode 100644 plugin/templates/panel_completion.md.jinja diff --git a/.editorconfig b/.editorconfig index 7a79dea..24cc2a1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,6 +14,9 @@ indent_style = tab [Makefile.*] indent_style = tab +[*.jinja] +indent_size = 2 + [*.md] indent_size = 2 trim_trailing_whitespace = false diff --git a/plugin/client.py b/plugin/client.py index e25064e..e3362ad 100644 --- a/plugin/client.py +++ b/plugin/client.py @@ -30,7 +30,7 @@ REQ_SET_EDITOR_INFO, ) from .log import log_warning -from .template import render_template +from .template import load_string_template from .types import ( AccountStatus, CopilotPayloadCompletions, @@ -280,7 +280,7 @@ def update_status_bar_text(self) -> None: rendered_text = "" if template_text := str(session.config.settings.get("status_text") or ""): try: - rendered_text = render_template(template_text, variables) + rendered_text = load_string_template(template_text).render(variables) except Exception as e: log_warning(f'Invalid "status_text" template: {e}') session.set_config_status_async(rendered_text) diff --git a/plugin/template.py b/plugin/template.py index 7ebfff6..aed0f8a 100644 --- a/plugin/template.py +++ b/plugin/template.py @@ -1,9 +1,11 @@ from __future__ import annotations from functools import lru_cache -from typing import Any import jinja2 +import sublime + +from .constants import PACKAGE_NAME JINJA_TEMPLATE_ENV = jinja2.Environment( extensions=[ @@ -14,9 +16,11 @@ @lru_cache -def create_template(template: str) -> jinja2.Template: +def load_string_template(template: str) -> jinja2.Template: return JINJA_TEMPLATE_ENV.from_string(template) -def render_template(template: str, variables: dict[str, Any]) -> str: - return create_template(template).render(variables) +@lru_cache +def load_resource_template(resource_name: str) -> jinja2.Template: + content = sublime.load_resource(f"Packages/{PACKAGE_NAME}/plugin/templates/{resource_name}") + return load_string_template(content) diff --git a/plugin/templates/completion@popup.md.jinja b/plugin/templates/completion@popup.md.jinja new file mode 100644 index 0000000..a27d28b --- /dev/null +++ b/plugin/templates/completion@popup.md.jinja @@ -0,0 +1,95 @@ + + +
+ +
+ Accept + × Reject + {% if count > 1 %} + + ({{ index + 1 }} of {{ count }}) + {% endif %} + +
+ +``````{{ lang }} +{{ code }} +`````` + +
diff --git a/plugin/templates/panel_completion.md.jinja b/plugin/templates/panel_completion.md.jinja new file mode 100644 index 0000000..3c15df0 --- /dev/null +++ b/plugin/templates/panel_completion.md.jinja @@ -0,0 +1,84 @@ + + +
+ + + +{% for section in sections %} +
+ + + ``````{{ section["lang"] }} + {{ section["code"] }} + `````` + +{% endfor %} + +
diff --git a/plugin/ui/completion.py b/plugin/ui/completion.py index 90b1244..554ec80 100644 --- a/plugin/ui/completion.py +++ b/plugin/ui/completion.py @@ -9,6 +9,7 @@ import sublime from more_itertools import first_true +from ..template import load_resource_template from ..types import CopilotPayloadCompletion from ..utils import ( clamp, @@ -16,7 +17,6 @@ get_copilot_view_setting, get_view_language_id, is_active_view, - reformat, set_copilot_view_setting, ) @@ -95,8 +95,10 @@ def current_completion(self) -> CopilotPayloadCompletion | None: @property def completion_style_type(self) -> type[_BaseCompletion]: - completion_cls = first_true(_BaseCompletion.__subclasses__(), pred=lambda t: t.name == self.completion_style) - if completion_cls: + if completion_cls := first_true( + _BaseCompletion.__subclasses__(), + pred=lambda t: t.name == self.completion_style, + ): return completion_cls raise RuntimeError(f"Unknown completion style type: {self.completion_style}") @@ -214,115 +216,16 @@ def close(cls, view: sublime.View) -> None: class _PopupCompletion(_BaseCompletion): name = "popup" - CSS_CLASS_NAME = "copilot-completion-popup" - CSS = f""" - html {{ - --copilot-accept-foreground: var(--foreground); - --copilot-accept-background: var(--background); - --copilot-accept-border: var(--greenish); - --copilot-reject-foreground: var(--foreground); - --copilot-reject-background: var(--background); - --copilot-reject-border: var(--yellowish); - }} - - .{CSS_CLASS_NAME} {{ - margin: 1rem 0.5rem 0 0.5rem; - }} - - .{CSS_CLASS_NAME} .header {{ - display: block; - margin-bottom: 1rem; - }} - - .{CSS_CLASS_NAME} a {{ - border-radius: 3px; - border-style: solid; - border-width: 1px; - display: inline; - padding: 5px; - text-decoration: none; - }} - - .{CSS_CLASS_NAME} a.accept {{ - background: var(--copilot-accept-background); - border-color: var(--copilot-accept-border); - color: var(--copilot-accept-foreground); - }} - - .{CSS_CLASS_NAME} a.accept i {{ - color: var(--copilot-accept-border); - }} - - .{CSS_CLASS_NAME} a.reject {{ - background: var(--copilot-reject-background); - border-color: var(--copilot-reject-border); - color: var(--copilot-reject-foreground); - }} - - .{CSS_CLASS_NAME} a.reject i {{ - color: var(--copilot-reject-border); - }} - - .{CSS_CLASS_NAME} a.prev {{ - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-right-width: 0; - padding-left: 8px; - padding-right: 8px; - }} - - .{CSS_CLASS_NAME} a.next {{ - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left-width: 0; - padding-left: 8px; - padding-right: 8px; - }} - - .{CSS_CLASS_NAME} a.panel {{ - padding-left: 8px; - padding-right: 8px; - }} - """ - # We use many backticks to denote a fenced code block because if we are writing in Markdown, - # Copilot may suggest 3 backticks for a fenced code block and that can break our templating. - COMPLETION_TEMPLATE = reformat(""" -
{header_items}
- ``````{lang} - {code} - `````` - """) - @property def popup_content(self) -> str: - return self.COMPLETION_TEMPLATE.format( - header_items="  ".join(self.popup_header_items), - lang=get_view_language_id(self.view, self.completion["point"]), + return load_resource_template("completion@popup.md.jinja").render( code=self.popup_code, + completion=self.completion, + count=self.count, + index=self.index, + lang=get_view_language_id(self.view, self.completion["point"]), ) - @property - def popup_header_items(self) -> list[str]: - header_items = [ - ' Accept', - '× Reject', - ] - if self.count > 1: - header_items.append( - '' - + '' - ) - header_items.append( - "({completion_index_1} of {completions_cnt})".format( # noqa: UP032 - completion_index_1=self.index + 1, # 1-based index - completions_cnt=self.count, - ) - ) - header_items.append( - '' - ) - return header_items - @property def popup_code(self) -> str: return fix_completion_syntax_highlight( @@ -336,15 +239,13 @@ def show(self) -> None: view=self.view, content=self.popup_content, md=True, - css=self.CSS, layout=sublime.LAYOUT_INLINE, flags=sublime.COOPERATE_WITH_AUTO_COMPLETE, max_width=640, - wrapper_class=self.CSS_CLASS_NAME, ) - @staticmethod - def hide(view: sublime.View) -> None: + @classmethod + def hide(cls, view: sublime.View) -> None: mdpopups.hide_popup(view) diff --git a/plugin/ui/panel_completion.py b/plugin/ui/panel_completion.py index 14e3a6c..013d2e7 100644 --- a/plugin/ui/panel_completion.py +++ b/plugin/ui/panel_completion.py @@ -7,6 +7,7 @@ import sublime from more_itertools import first_true, unique_everseen +from ..template import load_resource_template from ..types import CopilotPayloadPanelSolution, StLayout from ..utils import ( all_views, @@ -143,63 +144,6 @@ def close(self) -> None: class _PanelCompletion: CSS_CLASS_NAME = "copilot-completion-panel" - CSS = f""" - html {{ - --copilot-close-foreground: var(--foreground); - --copilot-close-background: var(--background); - --copilot-close-border: var(--foreground); - --copilot-accept-foreground: var(--foreground); - --copilot-accept-background: var(--background); - --copilot-accept-border: var(--greenish); - }} - - .{CSS_CLASS_NAME} {{ - margin: 1rem 0.5rem 0 0.5rem; - }} - - .{CSS_CLASS_NAME} .navbar {{ - text-align: left; - }} - - .{CSS_CLASS_NAME} .synthesis-info {{ - display: inline-block; - text-size: 1.2em; - }} - - .{CSS_CLASS_NAME} .header {{ - display: block; - margin-bottom: 1rem; - }} - - .{CSS_CLASS_NAME} a {{ - border-radius: 3px; - border-style: solid; - border-width: 1px; - display: inline; - padding: 5px; - text-decoration: none; - }} - - .{CSS_CLASS_NAME} a.close {{ - background: var(--copilot-close-background); - border-color: var(--copilot-close-border); - color: var(--copilot-close-foreground); - }} - - .{CSS_CLASS_NAME} a.close i {{ - color: var(--copilot-close-border); - }} - - .{CSS_CLASS_NAME} a.accept {{ - background: var(--copilot-accept-background); - border-color: var(--copilot-accept-border); - color: var(--copilot-accept-foreground); - }} - - .{CSS_CLASS_NAME} a.accept i {{ - color: var(--copilot-accept-border); - }} - """ COMPLETION_TEMPLATE = reformat("""