on main help sign in
small fixes
c42d94cKujtim Hoxha
internal/lsp/watcher/watcher.go+36−32
@@ -96,19 +96,19 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
96 // Determine server type for specialized handling
96 // Determine server type for specialized handling
97 serverName := getServerNameFromContext(ctx)
97 serverName := getServerNameFromContext(ctx)
98 logging.Debug("Server type detected", "serverName", serverName)
98 logging.Debug("Server type detected", "serverName", serverName)
99−
99+
100 // Check if this server has sent file watchers
100 // Check if this server has sent file watchers
101 hasFileWatchers := len(watchers) > 0
101 hasFileWatchers := len(watchers) > 0
102−
102+
103 // For servers that need file preloading, we'll use a smart approach
103 // For servers that need file preloading, we'll use a smart approach
104 if shouldPreloadFiles(serverName) || !hasFileWatchers {
104 if shouldPreloadFiles(serverName) || !hasFileWatchers {
105 go func() {
105 go func() {
106 startTime := time.Now()
106 startTime := time.Now()
107 filesOpened := 0
107 filesOpened := 0
108−
108+
109 // Determine max files to open based on server type
109 // Determine max files to open based on server type
110 maxFilesToOpen := 50 // Default conservative limit
110 maxFilesToOpen := 50 // Default conservative limit
111−
111+
112 switch serverName {
112 switch serverName {
113 case "typescript", "typescript-language-server", "tsserver", "vtsls":
113 case "typescript", "typescript-language-server", "tsserver", "vtsls":
114 // TypeScript servers benefit from seeing more files
114 // TypeScript servers benefit from seeing more files
@@ -117,17 +117,17 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
117 // Java servers need to see many files for project model
117 // Java servers need to see many files for project model
118 maxFilesToOpen = 200
118 maxFilesToOpen = 200
119 }
119 }
120−
120+
121 // First, open high-priority files
121 // First, open high-priority files
122 highPriorityFilesOpened := w.openHighPriorityFiles(ctx, serverName)
122 highPriorityFilesOpened := w.openHighPriorityFiles(ctx, serverName)
123 filesOpened += highPriorityFilesOpened
123 filesOpened += highPriorityFilesOpened
124−
124+
125 if cnf.DebugLSP {
125 if cnf.DebugLSP {
126− logging.Debug("Opened high-priority files",
126+ logging.Debug("Opened high-priority files",
127 "count", highPriorityFilesOpened,
127 "count", highPriorityFilesOpened,
128 "serverName", serverName)
128 "serverName", serverName)
129 }
129 }
130−
130+
131 // If we've already opened enough high-priority files, we might not need more
131 // If we've already opened enough high-priority files, we might not need more
132 if filesOpened >= maxFilesToOpen {
132 if filesOpened >= maxFilesToOpen {
133 if cnf.DebugLSP {
133 if cnf.DebugLSP {
@@ -137,9 +137,9 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
137 }
137 }
138 return
138 return
139 }
139 }
140−
140+
141 // For the remaining slots, walk the directory and open matching files
141 // For the remaining slots, walk the directory and open matching files
142−
142+
143 err := filepath.WalkDir(w.workspacePath, func(path string, d os.DirEntry, err error) error {
143 err := filepath.WalkDir(w.workspacePath, func(path string, d os.DirEntry, err error) error {
144 if err != nil {
144 if err != nil {
145 return err
145 return err
@@ -199,10 +199,10 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
199func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName string) int {
199func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName string) int {
200 cnf := config.Get()
200 cnf := config.Get()
201 filesOpened := 0
201 filesOpened := 0
202−
202+
203 // Define patterns for high-priority files based on server type
203 // Define patterns for high-priority files based on server type
204 var patterns []string
204 var patterns []string
205−
205+
206 switch serverName {
206 switch serverName {
207 case "typescript", "typescript-language-server", "tsserver", "vtsls":
207 case "typescript", "typescript-language-server", "tsserver", "vtsls":
208 patterns = []string{
208 patterns = []string{
@@ -256,7 +256,7 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
256 "**/.editorconfig",
256 "**/.editorconfig",
257 }
257 }
258 }
258 }
259−
259+
260 // For each pattern, find and open matching files
260 // For each pattern, find and open matching files
261 for _, pattern := range patterns {
261 for _, pattern := range patterns {
262 // Use doublestar.Glob to find files matching the pattern (supports ** patterns)
262 // Use doublestar.Glob to find files matching the pattern (supports ** patterns)
@@ -267,17 +267,17 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
267 }
267 }
268 continue
268 continue
269 }
269 }
270−
270+
271 for _, match := range matches {
271 for _, match := range matches {
272 // Convert relative path to absolute
272 // Convert relative path to absolute
273 fullPath := filepath.Join(w.workspacePath, match)
273 fullPath := filepath.Join(w.workspacePath, match)
274−
274+
275 // Skip directories and excluded files
275 // Skip directories and excluded files
276 info, err := os.Stat(fullPath)
276 info, err := os.Stat(fullPath)
277 if err != nil || info.IsDir() || shouldExcludeFile(fullPath) {
277 if err != nil || info.IsDir() || shouldExcludeFile(fullPath) {
278 continue
278 continue
279 }
279 }
280−
280+
281 // Open the file
281 // Open the file
282 if err := w.client.OpenFile(ctx, fullPath); err != nil {
282 if err := w.client.OpenFile(ctx, fullPath); err != nil {
283 if cnf.DebugLSP {
283 if cnf.DebugLSP {
@@ -289,17 +289,17 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
289 logging.Debug("Opened high-priority file", "path", fullPath)
289 logging.Debug("Opened high-priority file", "path", fullPath)
290 }
290 }
291 }
291 }
292−
292+
293 // Add a small delay to prevent overwhelming the server
293 // Add a small delay to prevent overwhelming the server
294 time.Sleep(20 * time.Millisecond)
294 time.Sleep(20 * time.Millisecond)
295−
295+
296 // Limit the number of files opened per pattern
296 // Limit the number of files opened per pattern
297 if filesOpened >= 5 && (serverName != "java" && serverName != "jdtls") {
297 if filesOpened >= 5 && (serverName != "java" && serverName != "jdtls") {
298 break
298 break
299 }
299 }
300 }
300 }
301 }
301 }
302−
302+
303 return filesOpened
303 return filesOpened
304}
304}
305
305
@@ -310,16 +310,16 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
310
310
311 // Store the watcher in the context for later use
311 // Store the watcher in the context for later use
312 ctx = context.WithValue(ctx, "workspaceWatcher", w)
312 ctx = context.WithValue(ctx, "workspaceWatcher", w)
313−
313+
314 // If the server name isn't already in the context, try to detect it
314 // If the server name isn't already in the context, try to detect it
315 if _, ok := ctx.Value("serverName").(string); !ok {
315 if _, ok := ctx.Value("serverName").(string); !ok {
316 serverName := getServerNameFromContext(ctx)
316 serverName := getServerNameFromContext(ctx)
317 ctx = context.WithValue(ctx, "serverName", serverName)
317 ctx = context.WithValue(ctx, "serverName", serverName)
318 }
318 }
319−
319+
320 serverName := getServerNameFromContext(ctx)
320 serverName := getServerNameFromContext(ctx)
321 logging.Debug("Starting workspace watcher", "workspacePath", workspacePath, "serverName", serverName)
321 logging.Debug("Starting workspace watcher", "workspacePath", workspacePath, "serverName", serverName)
322−
322+
323 // Register handler for file watcher registrations from the server
323 // Register handler for file watcher registrations from the server
324 lsp.RegisterFileWatchHandler(func(id string, watchers []protocol.FileSystemWatcher) {
324 lsp.RegisterFileWatchHandler(func(id string, watchers []protocol.FileSystemWatcher) {
325 w.AddRegistrations(ctx, id, watchers)
325 w.AddRegistrations(ctx, id, watchers)
@@ -414,7 +414,11 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
414 case event.Op&fsnotify.Create != 0:
414 case event.Op&fsnotify.Create != 0:
415 // Already handled earlier in the event loop
415 // Already handled earlier in the event loop
416 // Just send the notification if needed
416 // Just send the notification if needed
417− info, _ := os.Stat(event.Name)
417+ info, err := os.Stat(event.Name)
418+ if err != nil {
419+ logging.Error("Error getting file info", "path", event.Name, "error", err)
420+ return
421+ }
418 if !info.IsDir() && watchKind&protocol.WatchCreate != 0 {
422 if !info.IsDir() && watchKind&protocol.WatchCreate != 0 {
419 w.debounceHandleFileEvent(ctx, uri, protocol.FileChangeType(protocol.Created))
423 w.debounceHandleFileEvent(ctx, uri, protocol.FileChangeType(protocol.Created))
420 }
424 }
@@ -682,7 +686,7 @@ func getServerNameFromContext(ctx context.Context) string {
682 if serverName, ok := ctx.Value("serverName").(string); ok && serverName != "" {
686 if serverName, ok := ctx.Value("serverName").(string); ok && serverName != "" {
683 return strings.ToLower(serverName)
687 return strings.ToLower(serverName)
684 }
688 }
685−
689+
686 // Otherwise, try to extract server name from the client command path
690 // Otherwise, try to extract server name from the client command path
687 if w, ok := ctx.Value("workspaceWatcher").(*WorkspaceWatcher); ok && w != nil && w.client != nil && w.client.Cmd != nil {
691 if w, ok := ctx.Value("workspaceWatcher").(*WorkspaceWatcher); ok && w != nil && w.client != nil && w.client.Cmd != nil {
688 path := strings.ToLower(w.client.Cmd.Path)
692 path := strings.ToLower(w.client.Cmd.Path)
@@ -865,7 +869,7 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
865 if watched, _ := w.isPathWatched(path); watched {
869 if watched, _ := w.isPathWatched(path); watched {
866 // Get server name for specialized handling
870 // Get server name for specialized handling
867 serverName := getServerNameFromContext(ctx)
871 serverName := getServerNameFromContext(ctx)
868−
872+
869 // Check if the file is a high-priority file that should be opened immediately
873 // Check if the file is a high-priority file that should be opened immediately
870 // This helps with project initialization for certain language servers
874 // This helps with project initialization for certain language servers
871 if isHighPriorityFile(path, serverName) {
875 if isHighPriorityFile(path, serverName) {
@@ -881,7 +885,7 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
881 // For non-high-priority files, we'll use different strategies based on server type
885 // For non-high-priority files, we'll use different strategies based on server type
882 if shouldPreloadFiles(serverName) {
886 if shouldPreloadFiles(serverName) {
883 // For servers that benefit from preloading, open files but with limits
887 // For servers that benefit from preloading, open files but with limits
884−
888+
885 // Check file size - for preloading we're more conservative
889 // Check file size - for preloading we're more conservative
886 if info.Size() > (1 * 1024 * 1024) { // 1MB limit for preloaded files
890 if info.Size() > (1 * 1024 * 1024) { // 1MB limit for preloaded files
887 if cnf.DebugLSP {
891 if cnf.DebugLSP {
@@ -889,13 +893,13 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
889 }
893 }
890 return
894 return
891 }
895 }
892−
896+
893 // Check file extension for common source files
897 // Check file extension for common source files
894 ext := strings.ToLower(filepath.Ext(path))
898 ext := strings.ToLower(filepath.Ext(path))
895−
899+
896 // Only preload source files for the specific language
900 // Only preload source files for the specific language
897 shouldOpen := false
901 shouldOpen := false
898−
902+
899 switch serverName {
903 switch serverName {
900 case "typescript", "typescript-language-server", "tsserver", "vtsls":
904 case "typescript", "typescript-language-server", "tsserver", "vtsls":
901 shouldOpen = ext == ".ts" || ext == ".js" || ext == ".tsx" || ext == ".jsx"
905 shouldOpen = ext == ".ts" || ext == ".js" || ext == ".tsx" || ext == ".jsx"
@@ -913,7 +917,7 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
913 // For unknown servers, be conservative
917 // For unknown servers, be conservative
914 shouldOpen = false
918 shouldOpen = false
915 }
919 }
916−
920+
917 if shouldOpen {
921 if shouldOpen {
918 // Don't need to check if it's already open - the client.OpenFile handles that
922 // Don't need to check if it's already open - the client.OpenFile handles that
919 if err := w.client.OpenFile(ctx, path); err != nil && cnf.DebugLSP {
923 if err := w.client.OpenFile(ctx, path); err != nil && cnf.DebugLSP {
@@ -943,13 +947,13 @@ func isHighPriorityFile(path string, serverName string) bool {
943 fileName == "main.js"
947 fileName == "main.js"
944 case "gopls":
948 case "gopls":
945 // For Go, we want to open go.mod files immediately
949 // For Go, we want to open go.mod files immediately
946− return fileName == "go.mod" ||
950+ return fileName == "go.mod" ||
947 fileName == "go.sum" ||
951 fileName == "go.sum" ||
948 // Also open main.go files
952 // Also open main.go files
949 fileName == "main.go"
953 fileName == "main.go"
950 case "rust-analyzer":
954 case "rust-analyzer":
951 // For Rust, we want to open Cargo.toml files immediately
955 // For Rust, we want to open Cargo.toml files immediately
952− return fileName == "Cargo.toml" ||
956+ return fileName == "Cargo.toml" ||
953 fileName == "Cargo.lock" ||
957 fileName == "Cargo.lock" ||
954 // Also open lib.rs and main.rs
958 // Also open lib.rs and main.rs
955 fileName == "lib.rs" ||
959 fileName == "lib.rs" ||
internal/tui/components/chat/editor.go−5
@@ -116,11 +116,6 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
116 }
116 }
117 return m, openEditor()
117 return m, openEditor()
118 }
118 }
119− // if the key does not match any binding, return
120− if m.textarea.Focused() && key.Matches(msg, editorMaps.Send) {
121− return m, m.send()
122− }
123−
124 // Handle Enter key
119 // Handle Enter key
125 if m.textarea.Focused() && key.Matches(msg, editorMaps.Send) {
120 if m.textarea.Focused() && key.Matches(msg, editorMaps.Send) {
126 value := m.textarea.Value()
121 value := m.textarea.Value()
internal/tui/components/chat/list.go+18−4
@@ -83,6 +83,14 @@ func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
83 m.rendering = false
83 m.rendering = false
84 return m, nil
84 return m, nil
85
85
86+ case tea.KeyMsg:
87+ if key.Matches(msg, messageKeys.PageUp) || key.Matches(msg, messageKeys.PageDown) ||
88+ key.Matches(msg, messageKeys.HalfPageUp) || key.Matches(msg, messageKeys.HalfPageDown) {
89+ u, cmd := m.viewport.Update(msg)
90+ m.viewport = u
91+ cmds = append(cmds, cmd)
92+ }
93+
86 case renderFinishedMsg:
94 case renderFinishedMsg:
87 m.rendering = false
95 m.rendering = false
88 m.viewport.GotoBottom()
96 m.viewport.GotoBottom()
@@ -141,10 +149,6 @@ func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
141 }
149 }
142 }
150 }
143
151
144− u, cmd := m.viewport.Update(msg)
145− m.viewport = u
146− cmds = append(cmds, cmd)
147−
148 spinner, cmd := m.spinner.Update(msg)
152 spinner, cmd := m.spinner.Update(msg)
149 m.spinner = spinner
153 m.spinner = spinner
150 cmds = append(cmds, cmd)
154 cmds = append(cmds, cmd)
@@ -352,6 +356,16 @@ func (m *messagesCmp) help() string {
352 styles.BaseStyle.Foreground(styles.Forground).Bold(true).Render("esc"),
356 styles.BaseStyle.Foreground(styles.Forground).Bold(true).Render("esc"),
353 styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" to exit cancel"),
357 styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" to exit cancel"),
354 )
358 )
359+ } else {
360+ text += lipgloss.JoinHorizontal(
361+ lipgloss.Left,
362+ styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render("press "),
363+ styles.BaseStyle.Foreground(styles.Forground).Bold(true).Render("enter"),
364+ styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" to send the message,"),
365+ styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" write"),
366+ styles.BaseStyle.Foreground(styles.Forground).Bold(true).Render(" \\"),
367+ styles.BaseStyle.Foreground(styles.ForgroundDim).Bold(true).Render(" and enter to add a new line"),
368+ )
355 }
369 }
356 return styles.BaseStyle.
370 return styles.BaseStyle.
357 Width(m.width).
371 Width(m.width).
internal/tui/components/dialog/permission.go+4−4
@@ -64,15 +64,15 @@ var permissionsKeys = permissionsMapping{
64 ),
64 ),
65 Allow: key.NewBinding(
65 Allow: key.NewBinding(
66 key.WithKeys("a"),
66 key.WithKeys("a"),
67− key.WithHelp("a", "[a]llow"),
67+ key.WithHelp("a", "allow"),
68 ),
68 ),
69 AllowSession: key.NewBinding(
69 AllowSession: key.NewBinding(
70 key.WithKeys("s"),
70 key.WithKeys("s"),
71− key.WithHelp("s", "allow for [s]ession"),
71+ key.WithHelp("s", "allow for session"),
72 ),
72 ),
73 Deny: key.NewBinding(
73 Deny: key.NewBinding(
74 key.WithKeys("d"),
74 key.WithKeys("d"),
75− key.WithHelp("d", "[d]eny"),
75+ key.WithHelp("d", "deny"),
76 ),
76 ),
77 Tab: key.NewBinding(
77 Tab: key.NewBinding(
78 key.WithKeys("tab"),
78 key.WithKeys("tab"),
@@ -171,7 +171,7 @@ func (p *permissionDialogCmp) renderButtons() string {
171 }
171 }
172
172
173 allowButton := allowStyle.Padding(0, 1).Render("Allow (a)")
173 allowButton := allowStyle.Padding(0, 1).Render("Allow (a)")
174− allowSessionButton := allowSessionStyle.Padding(0, 1).Render("Allow for session (A)")
174+ allowSessionButton := allowSessionStyle.Padding(0, 1).Render("Allow for session (s)")
175 denyButton := denyStyle.Padding(0, 1).Render("Deny (d)")
175 denyButton := denyStyle.Padding(0, 1).Render("Deny (d)")
176
176
177 content := lipgloss.JoinHorizontal(
177 content := lipgloss.JoinHorizontal(