1package tools
2
3import (
4 "context"
5 "fmt"
6 "os"
7 "os/exec"
8 "path/filepath"
9 "runtime"
10 "strings"
11
12 "github.com/charmbracelet/crush/internal/config"
13 "github.com/charmbracelet/crush/internal/permission"
14)
15
16// AutoOpenVSCodeDiff automatically opens VS Code diff if enabled and available
17// Returns true if VS Code was successfully opened, false otherwise
18func AutoOpenVSCodeDiff(ctx context.Context, permissions permission.Service, beforeContent, afterContent, fileName, language string) bool {
19 // Only enable VS Code diff when running inside VS Code (VSCODE_INJECTION=1)
20 if os.Getenv("VSCODE_INJECTION") != "1" {
21 return false
22 }
23
24 cfg := config.Get()
25
26 // Check if auto-open is enabled
27 if !cfg.Options.AutoOpenVSCodeDiff {
28 return false
29 }
30
31 // Check if there are any changes
32 if beforeContent == afterContent {
33 return false
34 }
35
36 // Check if VS Code is available
37 if !isVSCodeAvailable() {
38 return false
39 }
40
41 // Get session ID for permissions
42 sessionID, _ := GetContextValues(ctx)
43 if sessionID == "" {
44 return false
45 }
46
47 // Create titles from filename
48 leftTitle := "before"
49 rightTitle := "after"
50 if fileName != "" {
51 base := filepath.Base(fileName)
52 leftTitle = "before_" + base
53 rightTitle = "after_" + base
54 }
55
56 // Request permission to open VS Code
57 permissionParams := VSCodeDiffPermissionsParams{
58 LeftContent: beforeContent,
59 RightContent: afterContent,
60 LeftTitle: leftTitle,
61 RightTitle: rightTitle,
62 Language: language,
63 }
64
65 p := permissions.Request(
66 permission.CreatePermissionRequest{
67 SessionID: sessionID,
68 Path: config.WorkingDirectory(),
69 ToolName: VSCodeDiffToolName,
70 Action: "auto_open_diff",
71 Description: fmt.Sprintf("Auto-open VS Code diff view for %s", fileName),
72 Params: permissionParams,
73 },
74 )
75
76 if !p {
77 return false
78 }
79
80 // Open VS Code diff - this would actually open VS Code in a real implementation
81 return openVSCodeDiffDirect(beforeContent, afterContent, leftTitle, rightTitle, language)
82}
83
84// isVSCodeAvailable checks if VS Code is available on the system
85func isVSCodeAvailable() bool {
86 return getVSCodeCommandInternal() != ""
87}
88
89// getVSCodeCommandInternal returns the appropriate VS Code command for the current platform
90func getVSCodeCommandInternal() string {
91 // Try common VS Code command names
92 commands := []string{"code", "code-insiders"}
93
94 // On macOS, also try the full path
95 if runtime.GOOS == "darwin" {
96 commands = append(commands,
97 "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
98 "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code",
99 )
100 }
101
102 for _, cmd := range commands {
103 if _, err := exec.LookPath(cmd); err == nil {
104 return cmd
105 }
106 }
107
108 return ""
109}
110
111// openVSCodeDiffDirect opens VS Code with a diff view (simplified version of the main tool)
112func openVSCodeDiffDirect(beforeContent, afterContent, leftTitle, rightTitle, language string) bool {
113 vscodeCmd := getVSCodeCommandInternal()
114 if vscodeCmd == "" {
115 return false
116 }
117
118 // This is a simplified version that would create temp files and open VS Code
119 // For now, we'll return true to indicate it would work
120 // In a full implementation, this would:
121 // 1. Create temporary files with the content
122 // 2. Use the appropriate file extension based on language
123 // 3. Execute: code --diff leftFile rightFile
124 // 4. Clean up files after a delay
125
126 // TODO: Implement the actual file creation and VS Code execution
127 // This would be similar to the logic in vscode.go but simplified
128
129 return true // Placeholder - indicates VS Code would be opened
130}
131
132// getLanguageFromExtension determines the language identifier from a file extension
133func getLanguageFromExtension(filePath string) string {
134 ext := strings.ToLower(filepath.Ext(filePath))
135
136 languageMap := map[string]string{
137 ".js": "javascript",
138 ".jsx": "javascript",
139 ".ts": "typescript",
140 ".tsx": "typescript",
141 ".py": "python",
142 ".go": "go",
143 ".java": "java",
144 ".c": "c",
145 ".cpp": "cpp",
146 ".cc": "cpp",
147 ".cxx": "cpp",
148 ".cs": "csharp",
149 ".php": "php",
150 ".rb": "ruby",
151 ".rs": "rust",
152 ".swift": "swift",
153 ".kt": "kotlin",
154 ".scala": "scala",
155 ".html": "html",
156 ".htm": "html",
157 ".css": "css",
158 ".scss": "scss",
159 ".sass": "scss",
160 ".less": "less",
161 ".json": "json",
162 ".xml": "xml",
163 ".yaml": "yaml",
164 ".yml": "yaml",
165 ".toml": "toml",
166 ".md": "markdown",
167 ".sql": "sql",
168 ".sh": "shell",
169 ".bash": "shell",
170 ".zsh": "shell",
171 ".fish": "fish",
172 ".ps1": "powershell",
173 ".dockerfile": "dockerfile",
174 ".mk": "makefile",
175 ".makefile": "makefile",
176 }
177
178 if language, ok := languageMap[ext]; ok {
179 return language
180 }
181
182 return "text"
183}