summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrail Finder <wohilas@gmail.com>2025-11-30 11:22:48 +0300
committerGrail Finder <wohilas@gmail.com>2025-11-30 11:22:48 +0300
commit5e6bc40853d2f76b0b6622c0e5c09c64fb22c954 (patch)
treeaedb10e5f5cf198737a7a8071754588087a83893
parenta8f942cb0dab0d88a6376b9615a845f14616eda1 (diff)
Fix: highlight shift because of injected tags
-rw-r--r--tui.go177
1 files changed, 11 insertions, 166 deletions
diff --git a/tui.go b/tui.go
index bd44ff0..ff63ed7 100644
--- a/tui.go
+++ b/tui.go
@@ -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