|
15 | 15 |
|
16 | 16 | import json
|
17 | 17 | import os
|
18 |
| -import subprocess |
19 | 18 | import uuid
|
20 | 19 | from abc import ABC
|
21 | 20 | from collections.abc import Callable
|
|
70 | 69 | prepare_conversation_turn_request,
|
71 | 70 | preprocess_chat_message,
|
72 | 71 | preprocess_message_for_html,
|
| 72 | + GitHelper, |
73 | 73 | )
|
74 | 74 | from .log import log_info
|
75 | 75 | from .types import (
|
@@ -609,8 +609,34 @@ def run(self, plugin: CopilotPlugin, session: Session, _: sublime.Edit) -> None:
|
609 | 609 | session.send_request(Request(REQ_CONVERSATION_AGENTS, {}), self._on_result_conversation_agents)
|
610 | 610 |
|
611 | 611 | def _on_result_conversation_agents(self, payload: list[CopilotRequestConversationAgent]) -> None:
|
612 |
| - for agent in payload: |
613 |
| - print(agent["slug"], agent["name"], agent["description"]) |
| 612 | + if not payload: |
| 613 | + status_message("No conversation agents available", icon="❌") |
| 614 | + return |
| 615 | + |
| 616 | + window = self.view.window() or sublime.active_window() |
| 617 | + window.show_quick_panel( |
| 618 | + [ |
| 619 | + sublime.QuickPanelItem( |
| 620 | + trigger=agent["slug"], |
| 621 | + details=agent["name"], |
| 622 | + annotation=agent["description"], |
| 623 | + ) |
| 624 | + for agent in payload |
| 625 | + ], |
| 626 | + lambda index: self._on_agent_selected(index, payload), |
| 627 | + ) |
| 628 | + |
| 629 | + def _on_agent_selected(self, index: int, agents: list[CopilotRequestConversationAgent]) -> None: |
| 630 | + if index == -1: |
| 631 | + return |
| 632 | + |
| 633 | + # For now, just show detailed info about the selected agent |
| 634 | + agent = agents[index] |
| 635 | + message_dialog( |
| 636 | + f"Agent: {agent['name']}\n" |
| 637 | + f"Slug: {agent['slug']}\n" |
| 638 | + f"Description: {agent['description']}" |
| 639 | + ) |
614 | 640 |
|
615 | 641 |
|
616 | 642 | class CopilotRegisterConversationToolsCommand(CopilotTextCommand):
|
@@ -792,175 +818,21 @@ def _on_result_code_review(self, payload: dict, window: sublime.Window) -> None:
|
792 | 818 | class CopilotGitCommitGenerateCommand(CopilotTextCommand):
|
793 | 819 | """Command to generate Git commit messages using GitHub Copilot."""
|
794 | 820 |
|
795 |
| - def _run_git_command(self, cmd: list[str], cwd: str | None = None) -> str | None: |
796 |
| - """Run a git command and return the output, or None if it fails.""" |
797 |
| - try: |
798 |
| - result = subprocess.run( |
799 |
| - cmd, |
800 |
| - cwd=cwd, |
801 |
| - capture_output=True, |
802 |
| - text=True, |
803 |
| - timeout=10, |
804 |
| - check=True |
805 |
| - ) |
806 |
| - return result.stdout.strip() |
807 |
| - except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError): |
808 |
| - return None |
809 |
| - |
810 |
| - def _get_git_repo_root(self, start_path: str) -> str | None: |
811 |
| - """Find the Git repository root starting from the given path.""" |
812 |
| - try: |
813 |
| - result = subprocess.run( |
814 |
| - ["git", "rev-parse", "--show-toplevel"], |
815 |
| - cwd=start_path, |
816 |
| - capture_output=True, |
817 |
| - text=True, |
818 |
| - timeout=5, |
819 |
| - check=True |
820 |
| - ) |
821 |
| - return result.stdout.strip() |
822 |
| - except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError): |
823 |
| - return None |
824 |
| - |
825 |
| - def _get_git_changes(self, repo_root: str) -> list[str]: |
826 |
| - """Get actual diff content for staged and unstaged changes.""" |
827 |
| - changes = [] |
828 |
| - |
829 |
| - # Get staged changes (actual diff content) |
830 |
| - staged_output = self._run_git_command(["git", "diff", "--cached"], repo_root) |
831 |
| - if staged_output: |
832 |
| - changes.append(f"=== STAGED CHANGES ===\n{staged_output}") |
833 |
| - |
834 |
| - # Get unstaged changes (actual diff content) |
835 |
| - unstaged_output = self._run_git_command(["git", "diff"], repo_root) |
836 |
| - if unstaged_output: |
837 |
| - changes.append(f"=== UNSTAGED CHANGES ===\n{unstaged_output}") |
838 |
| - |
839 |
| - # Get untracked files content (for small files) |
840 |
| - untracked_output = self._run_git_command(["git", "ls-files", "--others", "--exclude-standard"], repo_root) |
841 |
| - if untracked_output: |
842 |
| - untracked_files = [f.strip() for f in untracked_output.split('\n') if f.strip()] |
843 |
| - untracked_content = [] |
844 |
| - |
845 |
| - for file_path in untracked_files[:5]: # Limit to first 5 untracked files |
846 |
| - full_path = Path(repo_root) / file_path |
847 |
| - try: |
848 |
| - if full_path.is_file() and full_path.stat().st_size < 10000: # Only read files < 10KB |
849 |
| - content = full_path.read_text(encoding='utf-8', errors='ignore') |
850 |
| - untracked_content.append(f"=== NEW FILE: {file_path} ===\n{content}") |
851 |
| - except (OSError, UnicodeDecodeError): |
852 |
| - untracked_content.append(f"=== NEW FILE: {file_path} ===\n[Binary or unreadable file]") |
853 |
| - |
854 |
| - if untracked_content: |
855 |
| - changes.extend(untracked_content) |
856 |
| - |
857 |
| - return changes |
858 |
| - |
859 |
| - def _get_current_user_email(self, repo_root: str) -> str | None: |
860 |
| - """Get the current Git user email.""" |
861 |
| - return self._run_git_command(["git", "config", "user.email"], repo_root) |
862 |
| - |
863 |
| - def _get_user_commits(self, repo_root: str, user_email: str | None, limit: int = 10) -> list[str]: |
864 |
| - """Get recent commits by the current user.""" |
865 |
| - if not user_email: |
866 |
| - return [] |
867 |
| - |
868 |
| - cmd = [ |
869 |
| - "git", "log", |
870 |
| - f"--author={user_email}", |
871 |
| - f"--max-count={limit}", |
872 |
| - "--oneline", |
873 |
| - "--no-merges" |
874 |
| - ] |
875 |
| - |
876 |
| - output = self._run_git_command(cmd, repo_root) |
877 |
| - if output: |
878 |
| - return [line.strip() for line in output.split('\n') if line.strip()] |
879 |
| - return [] |
880 |
| - |
881 |
| - def _get_recent_commits(self, repo_root: str, limit: int = 20) -> list[str]: |
882 |
| - """Get recent commits from all contributors.""" |
883 |
| - cmd = [ |
884 |
| - "git", "log", |
885 |
| - f"--max-count={limit}", |
886 |
| - "--oneline", |
887 |
| - "--no-merges" |
888 |
| - ] |
889 |
| - |
890 |
| - output = self._run_git_command(cmd, repo_root) |
891 |
| - if output: |
892 |
| - return [line.strip() for line in output.split('\n') if line.strip()] |
893 |
| - return [] |
894 |
| - |
895 |
| - def _get_workspace_folder(self) -> str | None: |
896 |
| - """Get the workspace folder path.""" |
897 |
| - if not (window := self.view.window()): |
898 |
| - return None |
899 |
| - |
900 |
| - # Try to get the project path |
901 |
| - if folders := window.folders(): |
902 |
| - return folders[0] |
903 |
| - |
904 |
| - # Fallback to file directory |
905 |
| - if file_name := self.view.file_name(): |
906 |
| - return str(Path(file_name).parent) |
907 |
| - |
908 |
| - return None |
909 |
| - |
910 |
| - def _get_user_language(self) -> str | None: |
911 |
| - """Get user's language preference from Sublime Text settings.""" |
912 |
| - # Try to get language from various Sublime Text settings |
913 |
| - settings = sublime.load_settings("Preferences.sublime-settings") |
914 |
| - |
915 |
| - # Check for explicit language setting |
916 |
| - if lang := settings.get("language"): |
917 |
| - return lang |
918 |
| - |
919 |
| - # Fallback to system locale |
920 |
| - try: |
921 |
| - import locale |
922 |
| - return locale.getdefaultlocale()[0] |
923 |
| - except: |
924 |
| - return "en-US" |
925 |
| - |
926 | 821 | @_provide_plugin_session()
|
927 | 822 | def run(self, plugin: CopilotPlugin, session: Session, _: sublime.Edit) -> None:
|
928 | 823 | if not (window := self.view.window()):
|
929 | 824 | return
|
930 | 825 |
|
931 |
| - # Get workspace folder |
932 |
| - workspace_folder = self._get_workspace_folder() |
933 |
| - if not workspace_folder: |
934 |
| - status_message("No workspace folder found", icon="❌") |
935 |
| - return |
936 |
| - |
937 |
| - # Find Git repository root |
938 |
| - repo_root = self._get_git_repo_root(workspace_folder) |
939 |
| - if not repo_root: |
940 |
| - status_message("Not in a Git repository", icon="❌") |
| 826 | + # Gather all Git data using GitHelper |
| 827 | + git_data = GitHelper.gather_git_commit_data(self.view) |
| 828 | + if not git_data: |
| 829 | + status_message("Not in a Git repository or no workspace folder found", icon="❌") |
941 | 830 | return
|
942 |
| - |
943 |
| - # Gather Git information |
944 |
| - changes = self._get_git_changes(repo_root) |
945 |
| - user_email = self._get_current_user_email(repo_root) |
946 |
| - user_commits = self._get_user_commits(repo_root, user_email) |
947 |
| - recent_commits = self._get_recent_commits(repo_root) |
948 |
| - user_language = self._get_user_language() |
949 |
| - |
950 |
| - # Prepare payload according to expected structure |
951 |
| - payload = { |
952 |
| - "changes": changes, |
953 |
| - "userCommits": user_commits, |
954 |
| - "recentCommits": recent_commits, |
955 |
| - "workspaceFolder": workspace_folder, |
956 |
| - "userLanguage": user_language, |
957 |
| - } |
958 |
| - |
959 | 831 | status_message("Generating commit message...", icon="⏳")
|
960 | 832 |
|
961 | 833 | # Send request to generate commit message
|
962 | 834 | session.send_request(
|
963 |
| - Request(REQ_GIT_COMMIT_GENERATE, payload), |
| 835 | + Request(REQ_GIT_COMMIT_GENERATE, git_data), |
964 | 836 | lambda response: self._on_result_git_commit_generate(response, window)
|
965 | 837 | )
|
966 | 838 |
|
|
0 commit comments