1package prompt
  2
  3import (
  4	"os"
  5	"path/filepath"
  6	"strings"
  7	"sync"
  8
  9	"github.com/charmbracelet/crush/internal/config"
 10	"github.com/charmbracelet/crush/internal/csync"
 11	"github.com/charmbracelet/crush/internal/env"
 12)
 13
 14type PromptID string
 15
 16const (
 17	PromptCoder      PromptID = "coder"
 18	PromptTitle      PromptID = "title"
 19	PromptTask       PromptID = "task"
 20	PromptSummarizer PromptID = "summarizer"
 21	PromptDefault    PromptID = "default"
 22)
 23
 24func GetPrompt(promptID PromptID, provider string, contextPaths ...string) string {
 25	basePrompt := ""
 26	switch promptID {
 27	case PromptCoder:
 28		basePrompt = CoderPrompt(provider, contextPaths...)
 29	case PromptTitle:
 30		basePrompt = TitlePrompt()
 31	case PromptTask:
 32		basePrompt = TaskPrompt()
 33	case PromptSummarizer:
 34		basePrompt = SummarizerPrompt()
 35	default:
 36		basePrompt = "You are a helpful assistant"
 37	}
 38	return basePrompt
 39}
 40
 41func getContextFromPaths(workingDir string, contextPaths []string) string {
 42	return processContextPaths(workingDir, contextPaths)
 43}
 44
 45// expandPath expands ~ and environment variables in file paths
 46func expandPath(path string) string {
 47	// Handle tilde expansion
 48	if strings.HasPrefix(path, "~/") {
 49		homeDir, err := os.UserHomeDir()
 50		if err == nil {
 51			path = filepath.Join(homeDir, path[2:])
 52		}
 53	} else if path == "~" {
 54		homeDir, err := os.UserHomeDir()
 55		if err == nil {
 56			path = homeDir
 57		}
 58	}
 59
 60	// Handle environment variable expansion using the same pattern as config
 61	if strings.HasPrefix(path, "$") {
 62		resolver := config.NewEnvironmentVariableResolver(env.New())
 63		if expanded, err := resolver.ResolveValue(path); err == nil {
 64			path = expanded
 65		}
 66	}
 67
 68	return path
 69}
 70
 71func processContextPaths(workDir string, paths []string) string {
 72	var (
 73		wg       sync.WaitGroup
 74		resultCh = make(chan string)
 75	)
 76
 77	// Track processed files to avoid duplicates
 78	processedFiles := csync.NewMap[string, bool]()
 79
 80	for _, path := range paths {
 81		wg.Add(1)
 82		go func(p string) {
 83			defer wg.Done()
 84
 85			// Expand ~ and environment variables before processing
 86			p = expandPath(p)
 87
 88			// Use absolute path if provided, otherwise join with workDir
 89			fullPath := p
 90			if !filepath.IsAbs(p) {
 91				fullPath = filepath.Join(workDir, p)
 92			}
 93
 94			// Check if the path is a directory using os.Stat
 95			info, err := os.Stat(fullPath)
 96			if err != nil {
 97				return // Skip if path doesn't exist or can't be accessed
 98			}
 99
100			if info.IsDir() {
101				filepath.WalkDir(fullPath, func(path string, d os.DirEntry, err error) error {
102					if err != nil {
103						return err
104					}
105					if !d.IsDir() {
106						// Check if we've already processed this file (case-insensitive)
107						lowerPath := strings.ToLower(path)
108
109						if alreadyProcessed, _ := processedFiles.Get(lowerPath); !alreadyProcessed {
110							processedFiles.Set(lowerPath, true)
111							if result := processFile(path); result != "" {
112								resultCh <- result
113							}
114						}
115					}
116					return nil
117				})
118			} else {
119				// It's a file, process it directly
120				// Check if we've already processed this file (case-insensitive)
121				lowerPath := strings.ToLower(fullPath)
122
123				if alreadyProcessed, _ := processedFiles.Get(lowerPath); !alreadyProcessed {
124					processedFiles.Set(lowerPath, true)
125					result := processFile(fullPath)
126					if result != "" {
127						resultCh <- result
128					}
129				}
130			}
131		}(path)
132	}
133
134	go func() {
135		wg.Wait()
136		close(resultCh)
137	}()
138
139	results := make([]string, 0)
140	for result := range resultCh {
141		results = append(results, result)
142	}
143
144	return strings.Join(results, "\n")
145}
146
147func processFile(filePath string) string {
148	content, err := os.ReadFile(filePath)
149	if err != nil {
150		return ""
151	}
152	return "# From:" + filePath + "\n" + string(content)
153}