summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helpfuncs.go405
-rw-r--r--tui.go396
2 files changed, 406 insertions, 395 deletions
diff --git a/helpfuncs.go b/helpfuncs.go
index 1c4aa2e..f5e64ae 100644
--- a/helpfuncs.go
+++ b/helpfuncs.go
@@ -7,12 +7,15 @@ import (
"image"
"net/url"
"os"
+ "os/exec"
"path"
"slices"
"strings"
"unicode"
"math/rand/v2"
+
+ "github.com/rivo/tview"
)
func isASCII(s string) bool {
@@ -374,3 +377,405 @@ func deepseekModelValidator() error {
}
return nil
}
+
+// == shellmode ==
+
+func toggleShellMode() {
+ shellMode = !shellMode
+ if shellMode {
+ // Update input placeholder to indicate shell mode
+ textArea.SetPlaceholder("SHELL MODE: Enter command and press <Esc> to execute")
+ } else {
+ // Reset to normal mode
+ textArea.SetPlaceholder("input is multiline; press <Enter> to start the next line;\npress <Esc> to send the message. Alt+1 to exit shell mode")
+ }
+ updateStatusLine()
+}
+
+func updateFlexLayout() {
+ if fullscreenMode {
+ // flex already contains only focused widget; do nothing
+ return
+ }
+ flex.Clear()
+ flex.AddItem(textView, 0, 40, false)
+ flex.AddItem(textArea, 0, 10, false)
+ if positionVisible {
+ flex.AddItem(statusLineWidget, 0, 2, false)
+ }
+ // Keep focus on currently focused widget
+ focused := app.GetFocus()
+ if focused == textView {
+ app.SetFocus(textView)
+ } else {
+ app.SetFocus(textArea)
+ }
+}
+
+func executeCommandAndDisplay(cmdText string) {
+ // Parse the command (split by spaces, but handle quoted arguments)
+ cmdParts := parseCommand(cmdText)
+ if len(cmdParts) == 0 {
+ fmt.Fprintf(textView, "\n[red]Error: No command provided[-:-:-]\n")
+ if scrollToEndEnabled {
+ textView.ScrollToEnd()
+ }
+ colorText()
+ return
+ }
+ command := cmdParts[0]
+ args := []string{}
+ if len(cmdParts) > 1 {
+ args = cmdParts[1:]
+ }
+ // Create the command execution
+ cmd := exec.Command(command, args...)
+ // Execute the command and get output
+ output, err := cmd.CombinedOutput()
+ // Add the command being executed to the chat
+ fmt.Fprintf(textView, "\n[yellow]$ %s[-:-:-]\n", cmdText)
+ var outputContent string
+ if err != nil {
+ // Include both output and error
+ errorMsg := "Error: " + err.Error()
+ fmt.Fprintf(textView, "[red]%s[-:-:-]\n", errorMsg)
+ if len(output) > 0 {
+ outputStr := string(output)
+ fmt.Fprintf(textView, "[red]%s[-:-:-]\n", outputStr)
+ outputContent = errorMsg + "\n" + outputStr
+ } else {
+ outputContent = errorMsg
+ }
+ } else {
+ // Only output if successful
+ if len(output) > 0 {
+ outputStr := string(output)
+ fmt.Fprintf(textView, "[green]%s[-:-:-]\n", outputStr)
+ outputContent = outputStr
+ } else {
+ successMsg := "Command executed successfully (no output)"
+ fmt.Fprintf(textView, "[green]%s[-:-:-]\n", successMsg)
+ outputContent = successMsg
+ }
+ }
+ // Combine command and output in a single message for chat history
+ combinedContent := "$ " + cmdText + "\n\n" + outputContent
+ combinedMsg := models.RoleMsg{
+ Role: cfg.ToolRole,
+ Content: combinedContent,
+ }
+ chatBody.Messages = append(chatBody.Messages, combinedMsg)
+ // Scroll to end and update colors
+ if scrollToEndEnabled {
+ textView.ScrollToEnd()
+ }
+ colorText()
+}
+
+// parseCommand splits command string handling quotes properly
+func parseCommand(cmd string) []string {
+ var args []string
+ var current string
+ var inQuotes bool
+ var quoteChar rune
+ for _, r := range cmd {
+ switch r {
+ case '"', '\'':
+ if inQuotes {
+ if r == quoteChar {
+ inQuotes = false
+ } else {
+ current += string(r)
+ }
+ } else {
+ inQuotes = true
+ quoteChar = r
+ }
+ case ' ', '\t':
+ if inQuotes {
+ current += string(r)
+ } else if current != "" {
+ args = append(args, current)
+ current = ""
+ }
+ default:
+ current += string(r)
+ }
+ }
+ if current != "" {
+ args = append(args, current)
+ }
+ return args
+}
+
+// == search ==
+
+// Global variables for search state
+var searchResults []int
+var searchResultLengths []int // To store the length of each match in the formatted string
+var searchIndex int
+var searchText string
+var originalTextForSearch string
+
+// performSearch searches for the given term in the textView content and highlights matches
+func performSearch(term string) {
+ searchText = term
+ if searchText == "" {
+ searchResults = nil
+ searchResultLengths = nil
+ originalTextForSearch = ""
+ // Re-render text without highlights
+ textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
+ colorText()
+ return
+ }
+ // Get formatted text and search directly in it to avoid mapping issues
+ formattedText := textView.GetText(true)
+ originalTextForSearch = formattedText
+ searchTermLower := strings.ToLower(searchText)
+ formattedTextLower := strings.ToLower(formattedText)
+ // Find all occurrences of the search term in the formatted text directly
+ formattedSearchResults := []int{}
+ searchStart := 0
+ for {
+ pos := strings.Index(formattedTextLower[searchStart:], searchTermLower)
+ if pos == -1 {
+ break
+ }
+ absolutePos := searchStart + pos
+ formattedSearchResults = append(formattedSearchResults, absolutePos)
+ searchStart = absolutePos + len(searchText)
+ }
+ if len(formattedSearchResults) == 0 {
+ // No matches found
+ searchResults = nil
+ searchResultLengths = nil
+ notification := "Pattern not found: " + term
+ if err := notifyUser("search", notification); err != nil {
+ logger.Error("failed to send notification", "error", err)
+ }
+ return
+ }
+ // Store the formatted text positions and lengths for accurate highlighting
+ searchResults = formattedSearchResults
+ // Create lengths array - all matches have the same length as the search term
+ searchResultLengths = make([]int, len(formattedSearchResults))
+ for i := range searchResultLengths {
+ searchResultLengths[i] = len(searchText)
+ }
+ searchIndex = 0
+ highlightCurrentMatch()
+}
+
+// highlightCurrentMatch highlights the current search match and scrolls to it
+func highlightCurrentMatch() {
+ if len(searchResults) == 0 || searchIndex >= len(searchResults) {
+ return
+ }
+ // Get the stored formatted text
+ formattedText := originalTextForSearch
+ // For tview to properly support highlighting and scrolling, we need to work with its region system
+ // Instead of just applying highlights, we need to add region tags to the text
+ highlightedText := addRegionTags(formattedText, searchResults, searchResultLengths, searchIndex, searchText)
+ // Update the text view with the text that includes region tags
+ textView.SetText(highlightedText)
+ // Highlight the current region and scroll to it
+ // Need to identify which position in the results array corresponds to the current match
+ // The region ID will be search_<position>_<index>
+ currentRegion := fmt.Sprintf("search_%d_%d", searchResults[searchIndex], searchIndex)
+ textView.Highlight(currentRegion).ScrollToHighlight()
+ // Send notification about which match we're at
+ notification := fmt.Sprintf("Match %d of %d", searchIndex+1, len(searchResults))
+ if err := notifyUser("search", notification); err != nil {
+ logger.Error("failed to send notification", "error", err)
+ }
+}
+
+// showSearchBar shows the search input field as an overlay
+func showSearchBar() {
+ // Create a temporary flex to combine search and main content
+ updatedFlex := tview.NewFlex().SetDirection(tview.FlexRow).
+ AddItem(searchField, 3, 0, true). // Search field at top
+ AddItem(flex, 0, 1, false) // Main flex layout below
+ // Add the search overlay as a page
+ pages.AddPage(searchPageName, updatedFlex, true, true)
+ app.SetFocus(searchField)
+}
+
+// hideSearchBar hides the search input field
+func hideSearchBar() {
+ pages.RemovePage(searchPageName)
+ // Return focus to the text view
+ app.SetFocus(textView)
+ // Clear the search field
+ searchField.SetText("")
+}
+
+// Global variables for index overlay functionality
+var indexPageName = "indexOverlay"
+
+// showIndexBar shows the index input field as an overlay at the top
+func showIndexBar() {
+ // Create a temporary flex to combine index input and main content
+ updatedFlex := tview.NewFlex().SetDirection(tview.FlexRow).
+ AddItem(indexPickWindow, 3, 0, true). // Index field at top
+ AddItem(flex, 0, 1, false) // Main flex layout below
+
+ // Add the index overlay as a page
+ pages.AddPage(indexPageName, updatedFlex, true, true)
+ app.SetFocus(indexPickWindow)
+}
+
+// hideIndexBar hides the index input field
+func hideIndexBar() {
+ pages.RemovePage(indexPageName)
+ // Return focus to the text view
+ app.SetFocus(textView)
+ // Clear the index field
+ indexPickWindow.SetText("")
+}
+
+// addRegionTags adds region tags to search matches in the text for tview highlighting
+func addRegionTags(text string, positions []int, lengths []int, currentIdx int, searchTerm string) string {
+ if len(positions) == 0 {
+ return text
+ }
+ var result strings.Builder
+ lastEnd := 0
+ for i, pos := range positions {
+ endPos := pos + lengths[i]
+ // Add text before this match
+ if pos > lastEnd {
+ result.WriteString(text[lastEnd:pos])
+ }
+ // The matched text, which may contain its own formatting tags
+ actualText := text[pos:endPos]
+ // Add region tag and highlighting for this match
+ // Use a unique region id that includes the match index to avoid conflicts
+ regionId := fmt.Sprintf("search_%d_%d", pos, i) // position + index to ensure uniqueness
+ var highlightStart, highlightEnd string
+ if i == currentIdx {
+ // Current match - use different highlighting
+ highlightStart = fmt.Sprintf(`["%s"][yellow:blue:b]`, regionId) // Current match with region and special highlight
+ highlightEnd = `[-:-:-][""]` // Reset formatting and close region
+ } else {
+ // Other matches - use regular highlighting
+ highlightStart = fmt.Sprintf(`["%s"][gold:red:u]`, regionId) // Other matches with region and highlight
+ highlightEnd = `[-:-:-][""]` // Reset formatting and close region
+ }
+ result.WriteString(highlightStart)
+ result.WriteString(actualText)
+ result.WriteString(highlightEnd)
+ lastEnd = endPos
+ }
+ // Add the rest of the text after the last processed match
+ if lastEnd < len(text) {
+ result.WriteString(text[lastEnd:])
+ }
+ return result.String()
+}
+
+// searchNext finds the next occurrence of the search term
+func searchNext() {
+ if len(searchResults) == 0 {
+ if err := notifyUser("search", "No search results to navigate"); err != nil {
+ logger.Error("failed to send notification", "error", err)
+ }
+ return
+ }
+ searchIndex = (searchIndex + 1) % len(searchResults)
+ highlightCurrentMatch()
+}
+
+// searchPrev finds the previous occurrence of the search term
+func searchPrev() {
+ if len(searchResults) == 0 {
+ if err := notifyUser("search", "No search results to navigate"); err != nil {
+ logger.Error("failed to send notification", "error", err)
+ }
+ return
+ }
+ if searchIndex == 0 {
+ searchIndex = len(searchResults) - 1
+ } else {
+ searchIndex--
+ }
+ highlightCurrentMatch()
+}
+
+// == tab completion ==
+
+func scanFiles(dir, filter string) []string {
+ var files []string
+ entries, err := os.ReadDir(dir)
+ if err != nil {
+ return files
+ }
+ for _, entry := range entries {
+ name := entry.Name()
+ if strings.HasPrefix(name, ".") {
+ continue
+ }
+ if filter == "" || strings.HasPrefix(strings.ToLower(name), strings.ToLower(filter)) {
+ if entry.IsDir() {
+ files = append(files, name+"/")
+ } else {
+ files = append(files, name)
+ }
+ }
+ }
+ return files
+}
+
+func showFileCompletion(filter string) {
+ baseDir := cfg.CodingDir
+ if baseDir == "" {
+ baseDir = "."
+ }
+ complMatches = scanFiles(baseDir, filter)
+ go func() {
+ app.QueueUpdateDraw(func() {
+ if len(complMatches) == 0 {
+ hideCompletion() // Make sure hideCompletion also uses QueueUpdate if it modifies UI
+ return
+ }
+ // Limit the number of complMatches
+ if len(complMatches) > 10 {
+ complMatches = complMatches[:10]
+ }
+ // Update the popup's content
+ complPopup.Clear()
+ for _, f := range complMatches {
+ complPopup.AddItem(f, "", 0, nil)
+ }
+ // Update state and UI
+ complPopup.SetCurrentItem(0)
+ complActive = true
+ // Show the popup and set focus
+ pages.AddPage("complPopup", complPopup, true, true)
+ app.SetFocus(complPopup)
+ // No need to call app.Draw() here, QueueUpdateDraw does it automatically
+ })
+ }()
+}
+
+func insertCompletion() {
+ if complIndex >= 0 && complIndex < len(complMatches) {
+ match := complMatches[complIndex]
+ currentText := textArea.GetText()
+ atIdx := strings.LastIndex(currentText, "@")
+ if atIdx >= 0 {
+ before := currentText[:atIdx]
+ textArea.SetText(before+match, true)
+ }
+ }
+ hideCompletion()
+}
+
+func hideCompletion() {
+ complActive = false
+ complMatches = nil
+ pages.RemovePage("complPopup")
+ app.SetFocus(textArea)
+ app.Draw()
+}
diff --git a/tui.go b/tui.go
index 07d211c..275aab7 100644
--- a/tui.go
+++ b/tui.go
@@ -7,7 +7,6 @@ import (
_ "image/jpeg"
_ "image/png"
"os"
- "os/exec"
"path"
"strconv"
"strings"
@@ -179,396 +178,6 @@ Press <Enter> or 'x' to return
}
)
-func toggleShellMode() {
- shellMode = !shellMode
- if shellMode {
- // Update input placeholder to indicate shell mode
- textArea.SetPlaceholder("SHELL MODE: Enter command and press <Esc> to execute")
- } else {
- // Reset to normal mode
- textArea.SetPlaceholder("input is multiline; press <Enter> to start the next line;\npress <Esc> to send the message. Alt+1 to exit shell mode")
- }
- updateStatusLine()
-}
-
-func updateFlexLayout() {
- if fullscreenMode {
- // flex already contains only focused widget; do nothing
- return
- }
- flex.Clear()
- flex.AddItem(textView, 0, 40, false)
- flex.AddItem(textArea, 0, 10, false)
- if positionVisible {
- flex.AddItem(statusLineWidget, 0, 2, false)
- }
- // Keep focus on currently focused widget
- focused := app.GetFocus()
- if focused == textView {
- app.SetFocus(textView)
- } else {
- app.SetFocus(textArea)
- }
-}
-
-func executeCommandAndDisplay(cmdText string) {
- // Parse the command (split by spaces, but handle quoted arguments)
- cmdParts := parseCommand(cmdText)
- if len(cmdParts) == 0 {
- fmt.Fprintf(textView, "\n[red]Error: No command provided[-:-:-]\n")
- if scrollToEndEnabled {
- textView.ScrollToEnd()
- }
- colorText()
- return
- }
- command := cmdParts[0]
- args := []string{}
- if len(cmdParts) > 1 {
- args = cmdParts[1:]
- }
- // Create the command execution
- cmd := exec.Command(command, args...)
- // Execute the command and get output
- output, err := cmd.CombinedOutput()
- // Add the command being executed to the chat
- fmt.Fprintf(textView, "\n[yellow]$ %s[-:-:-]\n", cmdText)
- var outputContent string
- if err != nil {
- // Include both output and error
- errorMsg := "Error: " + err.Error()
- fmt.Fprintf(textView, "[red]%s[-:-:-]\n", errorMsg)
- if len(output) > 0 {
- outputStr := string(output)
- fmt.Fprintf(textView, "[red]%s[-:-:-]\n", outputStr)
- outputContent = errorMsg + "\n" + outputStr
- } else {
- outputContent = errorMsg
- }
- } else {
- // Only output if successful
- if len(output) > 0 {
- outputStr := string(output)
- fmt.Fprintf(textView, "[green]%s[-:-:-]\n", outputStr)
- outputContent = outputStr
- } else {
- successMsg := "Command executed successfully (no output)"
- fmt.Fprintf(textView, "[green]%s[-:-:-]\n", successMsg)
- outputContent = successMsg
- }
- }
- // Combine command and output in a single message for chat history
- combinedContent := "$ " + cmdText + "\n\n" + outputContent
- combinedMsg := models.RoleMsg{
- Role: cfg.ToolRole,
- Content: combinedContent,
- }
- chatBody.Messages = append(chatBody.Messages, combinedMsg)
- // Scroll to end and update colors
- if scrollToEndEnabled {
- textView.ScrollToEnd()
- }
- colorText()
-}
-
-// parseCommand splits command string handling quotes properly
-func parseCommand(cmd string) []string {
- var args []string
- var current string
- var inQuotes bool
- var quoteChar rune
- for _, r := range cmd {
- switch r {
- case '"', '\'':
- if inQuotes {
- if r == quoteChar {
- inQuotes = false
- } else {
- current += string(r)
- }
- } else {
- inQuotes = true
- quoteChar = r
- }
- case ' ', '\t':
- if inQuotes {
- current += string(r)
- } else if current != "" {
- args = append(args, current)
- current = ""
- }
- default:
- current += string(r)
- }
- }
- if current != "" {
- args = append(args, current)
- }
- return args
-}
-
-// Global variables for search state
-var searchResults []int
-var searchResultLengths []int // To store the length of each match in the formatted string
-var searchIndex int
-var searchText string
-var originalTextForSearch string
-
-// performSearch searches for the given term in the textView content and highlights matches
-func performSearch(term string) {
- searchText = term
- if searchText == "" {
- searchResults = nil
- searchResultLengths = nil
- originalTextForSearch = ""
- // Re-render text without highlights
- textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
- colorText()
- return
- }
- // Get formatted text and search directly in it to avoid mapping issues
- formattedText := textView.GetText(true)
- originalTextForSearch = formattedText
- searchTermLower := strings.ToLower(searchText)
- formattedTextLower := strings.ToLower(formattedText)
- // Find all occurrences of the search term in the formatted text directly
- formattedSearchResults := []int{}
- searchStart := 0
- for {
- pos := strings.Index(formattedTextLower[searchStart:], searchTermLower)
- if pos == -1 {
- break
- }
- absolutePos := searchStart + pos
- formattedSearchResults = append(formattedSearchResults, absolutePos)
- searchStart = absolutePos + len(searchText)
- }
- if len(formattedSearchResults) == 0 {
- // No matches found
- searchResults = nil
- searchResultLengths = nil
- notification := "Pattern not found: " + term
- if err := notifyUser("search", notification); err != nil {
- logger.Error("failed to send notification", "error", err)
- }
- return
- }
- // Store the formatted text positions and lengths for accurate highlighting
- searchResults = formattedSearchResults
- // Create lengths array - all matches have the same length as the search term
- searchResultLengths = make([]int, len(formattedSearchResults))
- for i := range searchResultLengths {
- searchResultLengths[i] = len(searchText)
- }
- searchIndex = 0
- highlightCurrentMatch()
-}
-
-// highlightCurrentMatch highlights the current search match and scrolls to it
-func highlightCurrentMatch() {
- if len(searchResults) == 0 || searchIndex >= len(searchResults) {
- return
- }
- // Get the stored formatted text
- formattedText := originalTextForSearch
- // For tview to properly support highlighting and scrolling, we need to work with its region system
- // Instead of just applying highlights, we need to add region tags to the text
- highlightedText := addRegionTags(formattedText, searchResults, searchResultLengths, searchIndex, searchText)
- // Update the text view with the text that includes region tags
- textView.SetText(highlightedText)
- // Highlight the current region and scroll to it
- // Need to identify which position in the results array corresponds to the current match
- // The region ID will be search_<position>_<index>
- currentRegion := fmt.Sprintf("search_%d_%d", searchResults[searchIndex], searchIndex)
- textView.Highlight(currentRegion).ScrollToHighlight()
- // Send notification about which match we're at
- notification := fmt.Sprintf("Match %d of %d", searchIndex+1, len(searchResults))
- if err := notifyUser("search", notification); err != nil {
- logger.Error("failed to send notification", "error", err)
- }
-}
-
-// showSearchBar shows the search input field as an overlay
-func showSearchBar() {
- // Create a temporary flex to combine search and main content
- updatedFlex := tview.NewFlex().SetDirection(tview.FlexRow).
- AddItem(searchField, 3, 0, true). // Search field at top
- AddItem(flex, 0, 1, false) // Main flex layout below
-
- // Add the search overlay as a page
- pages.AddPage(searchPageName, updatedFlex, true, true)
- app.SetFocus(searchField)
-}
-
-// hideSearchBar hides the search input field
-func hideSearchBar() {
- pages.RemovePage(searchPageName)
- // Return focus to the text view
- app.SetFocus(textView)
- // Clear the search field
- searchField.SetText("")
-}
-
-// Global variables for index overlay functionality
-var indexPageName = "indexOverlay"
-
-// showIndexBar shows the index input field as an overlay at the top
-func showIndexBar() {
- // Create a temporary flex to combine index input and main content
- updatedFlex := tview.NewFlex().SetDirection(tview.FlexRow).
- AddItem(indexPickWindow, 3, 0, true). // Index field at top
- AddItem(flex, 0, 1, false) // Main flex layout below
-
- // Add the index overlay as a page
- pages.AddPage(indexPageName, updatedFlex, true, true)
- app.SetFocus(indexPickWindow)
-}
-
-// hideIndexBar hides the index input field
-func hideIndexBar() {
- pages.RemovePage(indexPageName)
- // Return focus to the text view
- app.SetFocus(textView)
- // Clear the index field
- indexPickWindow.SetText("")
-}
-
-// addRegionTags adds region tags to search matches in the text for tview highlighting
-func addRegionTags(text string, positions []int, lengths []int, currentIdx int, searchTerm string) string {
- if len(positions) == 0 {
- return text
- }
- var result strings.Builder
- lastEnd := 0
- for i, pos := range positions {
- endPos := pos + lengths[i]
- // Add text before this match
- if pos > lastEnd {
- result.WriteString(text[lastEnd:pos])
- }
- // The matched text, which may contain its own formatting tags
- actualText := text[pos:endPos]
- // Add region tag and highlighting for this match
- // Use a unique region id that includes the match index to avoid conflicts
- regionId := fmt.Sprintf("search_%d_%d", pos, i) // position + index to ensure uniqueness
- var highlightStart, highlightEnd string
- if i == currentIdx {
- // Current match - use different highlighting
- highlightStart = fmt.Sprintf(`["%s"][yellow:blue:b]`, regionId) // Current match with region and special highlight
- highlightEnd = `[-:-:-][""]` // Reset formatting and close region
- } else {
- // Other matches - use regular highlighting
- highlightStart = fmt.Sprintf(`["%s"][gold:red:u]`, regionId) // Other matches with region and highlight
- highlightEnd = `[-:-:-][""]` // Reset formatting and close region
- }
- result.WriteString(highlightStart)
- result.WriteString(actualText)
- result.WriteString(highlightEnd)
- lastEnd = endPos
- }
- // Add the rest of the text after the last processed match
- if lastEnd < len(text) {
- result.WriteString(text[lastEnd:])
- }
- return result.String()
-}
-
-// searchNext finds the next occurrence of the search term
-func searchNext() {
- if len(searchResults) == 0 {
- if err := notifyUser("search", "No search results to navigate"); err != nil {
- logger.Error("failed to send notification", "error", err)
- }
- return
- }
- searchIndex = (searchIndex + 1) % len(searchResults)
- highlightCurrentMatch()
-}
-
-// searchPrev finds the previous occurrence of the search term
-func searchPrev() {
- if len(searchResults) == 0 {
- if err := notifyUser("search", "No search results to navigate"); err != nil {
- logger.Error("failed to send notification", "error", err)
- }
- return
- }
- if searchIndex == 0 {
- searchIndex = len(searchResults) - 1
- } else {
- searchIndex--
- }
- highlightCurrentMatch()
-}
-
-func scanFiles(dir, filter string) []string {
- var files []string
- entries, err := os.ReadDir(dir)
- if err != nil {
- return files
- }
- for _, entry := range entries {
- name := entry.Name()
- if strings.HasPrefix(name, ".") {
- continue
- }
- if filter == "" || strings.HasPrefix(strings.ToLower(name), strings.ToLower(filter)) {
- if entry.IsDir() {
- files = append(files, name+"/")
- } else {
- files = append(files, name)
- }
- }
- }
- return files
-}
-
-func showFileCompletion(filter string) {
- baseDir := cfg.CodingDir
- if baseDir == "" {
- baseDir = "."
- }
- complMatches = scanFiles(baseDir, filter)
- if len(complMatches) == 0 {
- hideCompletion()
- return
- }
- if len(complMatches) > 10 {
- complMatches = complMatches[:10]
- }
- complPopup.Clear()
- for _, f := range complMatches {
- complPopup.AddItem(f, "", 0, nil)
- }
- complIndex = 0
- complPopup.SetCurrentItem(0)
- complActive = true
- pages.AddPage("complPopup", complPopup, true, false)
- app.SetFocus(complPopup)
- app.Draw()
-}
-
-func insertCompletion() {
- if complIndex >= 0 && complIndex < len(complMatches) {
- match := complMatches[complIndex]
- currentText := textArea.GetText()
- atIdx := strings.LastIndex(currentText, "@")
- if atIdx >= 0 {
- before := currentText[:atIdx]
- textArea.SetText(before+match, true)
- }
- }
- hideCompletion()
-}
-
-func hideCompletion() {
- complActive = false
- complMatches = nil
- pages.RemovePage("complPopup")
- app.SetFocus(textArea)
- app.Draw()
-}
-
func init() {
tview.Styles = colorschemes["default"]
app = tview.NewApplication()
@@ -584,7 +193,7 @@ func init() {
// Add input capture for @ completion
textArea.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
- if event.Key() == tcell.KeyRune && event.Rune() == '@' {
+ if shellMode && event.Key() == tcell.KeyRune && event.Rune() == '@' {
complAtPos = len(textArea.GetText())
showFileCompletion("")
return event
@@ -625,7 +234,6 @@ func init() {
}
return event
})
-
textView = tview.NewTextView().
SetDynamicColors(true).
SetRegions(true).
@@ -739,7 +347,6 @@ func init() {
// colorText()
// updateStatusLine()
})
-
roleEditWindow = tview.NewInputField().
SetLabel("Enter new role: ").
SetPlaceholder("e.g., user, assistant, system, tool").
@@ -1433,7 +1040,6 @@ func init() {
app.SetFocus(focusSwitcher[currentF])
return nil
}
-
if isASCII(string(event.Rune())) && !botRespMode {
return event
}