diff options
| author | Grail Finder <wohilas@gmail.com> | 2025-11-30 11:22:48 +0300 |
|---|---|---|
| committer | Grail Finder <wohilas@gmail.com> | 2025-11-30 11:22:48 +0300 |
| commit | 5e6bc40853d2f76b0b6622c0e5c09c64fb22c954 (patch) | |
| tree | aedb10e5f5cf198737a7a8071754588087a83893 /tui.go | |
| parent | a8f942cb0dab0d88a6376b9615a845f14616eda1 (diff) | |
Fix: highlight shift because of injected tags
Diffstat (limited to 'tui.go')
| -rw-r--r-- | tui.go | 177 |
1 files changed, 11 insertions, 166 deletions
@@ -31,7 +31,6 @@ var ( defaultImage = "sysprompts/llama.png" indexPickWindow *tview.InputField renameWindow *tview.InputField - searchWindow *tview.InputField fullscreenMode bool // pages historyPage = "historyPage" @@ -50,7 +49,6 @@ var ( // For overlay search functionality searchField *tview.InputField - isSearching bool searchPageName = "searchOverlay" // help text helpText = ` @@ -241,22 +239,17 @@ func executeCommandAndDisplay(cmdText string) { 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) - if err != nil { // Include both output and error fmt.Fprintf(textView, "[red]Error: %s[-:-:-]\n", err.Error()) @@ -271,7 +264,6 @@ func executeCommandAndDisplay(cmdText string) { fmt.Fprintf(textView, "[green]Command executed successfully (no output)[-:-:-]\n") } } - // Scroll to end and update colors textView.ScrollToEnd() colorText() @@ -283,7 +275,6 @@ func parseCommand(cmd string) []string { var current string var inQuotes bool var quoteChar rune - for _, r := range cmd { switch r { case '"', '\'': @@ -308,11 +299,9 @@ func parseCommand(cmd string) []string { current += string(r) } } - if current != "" { args = append(args, current) } - return args } @@ -321,62 +310,9 @@ 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 -// stripTags creates a plain text version of a tview formatted string and a mapping -// from plain text indices to formatted text indices. -func stripTags(formatted string) (string, []int) { - var plain strings.Builder - // The mapping will store the byte index in the formatted string for each byte in the plain string. - mapping := make([]int, 0, len(formatted)) - - i := 0 - for i < len(formatted) { - if formatted[i] != '[' { - mapping = append(mapping, i) - plain.WriteByte(formatted[i]) - i++ - continue - } - // We are at a '[' - if i+1 < len(formatted) && formatted[i+1] == '[' { // Escaped '[[' - mapping = append(mapping, i) - plain.WriteByte('[') - i += 2 - continue - } - - // It's a tag. Find its end. - end := -1 - // Region tags are of the form ["..."] - if i+1 < len(formatted) && formatted[i+1] == '"' { - // Find `"]` - for j := i + 2; j < len(formatted)-1; j++ { - if formatted[j] == '"' && formatted[j+1] == ']' { - end = j + 1 - break - } - } - } else { - // Color/attr tag [...] - closeBracket := strings.IndexRune(formatted[i:], ']') - if closeBracket != -1 { - end = i + closeBracket - } - } - - if end == -1 { - // Unterminated tag. Treat as literal. - mapping = append(mapping, i) - plain.WriteByte(formatted[i]) - i++ - } else { - // Skip tag - i = end + 1 - } - } - return plain.String(), mapping -} // performSearch searches for the given term in the textView content and highlights matches func performSearch(term string) { @@ -384,6 +320,7 @@ func performSearch(term string) { if searchText == "" { searchResults = nil searchResultLengths = nil + originalTextForSearch = "" // Re-render text without highlights textView.SetText(chatToText(cfg.ShowSys)) colorText() @@ -391,6 +328,7 @@ func performSearch(term string) { } // 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 @@ -409,7 +347,7 @@ func performSearch(term string) { // No matches found searchResults = nil searchResultLengths = nil - notification := fmt.Sprintf("Pattern not found: %s", term) + notification := "Pattern not found: " + term if err := notifyUser("search", notification); err != nil { logger.Error("failed to send notification", "error", err) } @@ -431,23 +369,18 @@ func highlightCurrentMatch() { if len(searchResults) == 0 || searchIndex >= len(searchResults) { return } - // Get the stored formatted text - formattedText := textView.GetText(true) - + 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 { @@ -455,67 +388,10 @@ func highlightCurrentMatch() { } } -// applyAllHighlights applies highlighting to all search matches in the text -func applyAllHighlights(text string, positions []int, currentIdx int, searchTerm string) string { - if len(positions) == 0 { - return text - } - - // For performance and to avoid freezing, use a simpler approach just highlighting the positions - // that were found in the initial search (even if not perfectly mapped to formatted text) - var result strings.Builder - - // For simplicity and to prevent freezing, don't do complex recalculations - // Instead, we'll just highlight based on the initial search results - lastEnd := 0 - // Since positions come from plain text search, they may not align with formatted text - // For robustness, only process positions that are within bounds - for i, pos := range positions { - // Only process if within text bounds - if pos >= len(text) { - continue - } - - endPos := pos + len(searchTerm) - if endPos > len(text) { - continue - } - - // Check if the actual text matches the search term (case insensitive) - actualText := text[pos:endPos] - if strings.ToLower(actualText) != strings.ToLower(searchTerm) { - continue // Skip if the text doesn't actually match at this position - } - - // Add text before this match - if pos > lastEnd { - result.WriteString(text[lastEnd:pos]) - } - - // Highlight this match - highlight := `[gold:red:u]` // All matches - gold on red - if i == currentIdx { - highlight = `[yellow:blue:b]` // Current match - yellow on blue bold - } - result.WriteString(highlight) - result.WriteString(actualText) - result.WriteString(`[-:-:-]`) // Reset formatting - - lastEnd = endPos - } - - // Add the rest of the text after the last processed match - if lastEnd < len(text) { - result.WriteString(text[lastEnd:]) - } - - return result.String() -} // showSearchBar shows the search input field as an overlay func showSearchBar() { - isSearching = true // 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 @@ -528,7 +404,6 @@ func showSearchBar() { // hideSearchBar hides the search input field func hideSearchBar() { - isSearching = false pages.RemovePage(searchPageName) // Return focus to the text view app.SetFocus(textView) @@ -541,21 +416,16 @@ func addRegionTags(text string, positions []int, lengths []int, currentIdx int, 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 @@ -569,45 +439,20 @@ func addRegionTags(text string, positions []int, lengths []int, currentIdx int, 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() } -// insertHighlightAtPosition inserts highlight tags around a specific position in the text -func insertHighlightAtPosition(originalText string, pos int, length int) string { - if pos < 0 || pos >= len(originalText) || pos+length > len(originalText) { - return originalText - } - - // Insert highlight tags around the match - var result strings.Builder - result.WriteString(originalText[:pos]) - result.WriteString(`[gold:red:u]`) // Highlight with gold text on red background and underline - result.WriteString(originalText[pos : pos+length]) - result.WriteString(`[-]`) // Reset to default formatting - result.WriteString(originalText[pos+length:]) - return result.String() -} -// highlightTextWithRegions adds region tags to highlight search matches -func highlightTextWithRegions(originalText string, matchStart int, matchLength int) string { - // For now, we'll return the original text and use tview's highlight system differently - // The highlighting will be applied via the textView.Highlight() method - return originalText -} // searchNext finds the next occurrence of the search term func searchNext() { @@ -617,7 +462,6 @@ func searchNext() { } return } - searchIndex = (searchIndex + 1) % len(searchResults) highlightCurrentMatch() } @@ -630,7 +474,6 @@ func searchPrev() { } return } - if searchIndex == 0 { searchIndex = len(searchResults) - 1 } else { @@ -649,7 +492,7 @@ func init() { textView = tview.NewTextView(). SetDynamicColors(true). SetRegions(true) - + // flex = tview.NewFlex().SetDirection(tview.FlexRow). AddItem(textView, 0, 40, false). AddItem(textArea, 0, 10, true). // Restore original height @@ -658,9 +501,10 @@ func init() { textView.SetDoneFunc(func(key tcell.Key) { if key == tcell.KeyEnter { if len(searchResults) > 0 { // Check if a search is active - hideSearchBar() // Hide the search bar if visible - searchResults = nil // Clear search results - searchResultLengths = nil // Clear search result lengths + hideSearchBar() // Hide the search bar if visible + searchResults = nil // Clear search results + searchResultLengths = nil // Clear search result lengths + originalTextForSearch = "" textView.SetText(chatToText(cfg.ShowSys)) // Reset text without search regions colorText() // Apply normal chat coloring } else { @@ -846,6 +690,7 @@ func init() { hideSearchBar() searchResults = nil searchResultLengths = nil + originalTextForSearch = "" textView.SetText(chatToText(cfg.ShowSys)) colorText() return |
