fix: scrolling on suggestions
This commit is contained in:
@@ -2,10 +2,15 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/charmbracelet/x/ansi"
|
"github.com/charmbracelet/x/ansi"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const suggestionRenderCacheLimit = 256
|
||||||
|
|
||||||
|
var renderedSuggestions = newSuggestionRenderCache(suggestionRenderCacheLimit)
|
||||||
|
|
||||||
type parsedCommentBody struct {
|
type parsedCommentBody struct {
|
||||||
Prose string
|
Prose string
|
||||||
Suggestions []string
|
Suggestions []string
|
||||||
@@ -16,6 +21,98 @@ type codeRange struct {
|
|||||||
End int
|
End int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type suggestionRenderCacheKey struct {
|
||||||
|
path string
|
||||||
|
removed string
|
||||||
|
removedLines int
|
||||||
|
replacement string
|
||||||
|
width int
|
||||||
|
}
|
||||||
|
|
||||||
|
type suggestionRenderCacheEntry struct {
|
||||||
|
removed []detailLine
|
||||||
|
added []detailLine
|
||||||
|
}
|
||||||
|
|
||||||
|
type suggestionRenderCache struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
limit int
|
||||||
|
entries map[suggestionRenderCacheKey]suggestionRenderCacheEntry
|
||||||
|
order []suggestionRenderCacheKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSuggestionRenderCache(limit int) *suggestionRenderCache {
|
||||||
|
return &suggestionRenderCache{
|
||||||
|
limit: limit, entries: make(map[suggestionRenderCacheKey]suggestionRenderCacheEntry),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *suggestionRenderCache) get(
|
||||||
|
key suggestionRenderCacheKey,
|
||||||
|
) (suggestionRenderCacheEntry, bool) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
entry, ok := c.entries[key]
|
||||||
|
return cloneSuggestionRenderEntry(entry), ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *suggestionRenderCache) put(
|
||||||
|
key suggestionRenderCacheKey, entry suggestionRenderCacheEntry,
|
||||||
|
) suggestionRenderCacheEntry {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
if cached, ok := c.entries[key]; ok {
|
||||||
|
return cloneSuggestionRenderEntry(cached)
|
||||||
|
}
|
||||||
|
if len(c.entries) >= c.limit {
|
||||||
|
delete(c.entries, c.order[0])
|
||||||
|
c.order = c.order[1:]
|
||||||
|
}
|
||||||
|
c.entries[key] = cloneSuggestionRenderEntry(entry)
|
||||||
|
c.order = append(c.order, key)
|
||||||
|
return cloneSuggestionRenderEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *suggestionRenderCache) clear() {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.entries = make(map[suggestionRenderCacheKey]suggestionRenderCacheEntry)
|
||||||
|
c.order = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneSuggestionRenderEntry(entry suggestionRenderCacheEntry) suggestionRenderCacheEntry {
|
||||||
|
return suggestionRenderCacheEntry{
|
||||||
|
removed: append([]detailLine(nil), entry.removed...),
|
||||||
|
added: append([]detailLine(nil), entry.added...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderSuggestion(
|
||||||
|
path string, reviewed []string, replacement string, width int,
|
||||||
|
) suggestionRenderCacheEntry {
|
||||||
|
key := suggestionRenderCacheKey{
|
||||||
|
path: path, removed: strings.Join(reviewed, "\n"), removedLines: len(reviewed),
|
||||||
|
replacement: replacement, width: width,
|
||||||
|
}
|
||||||
|
if cached, ok := renderedSuggestions.get(key); ok {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
removed, added := normalizeSuggestion(reviewed, replacement)
|
||||||
|
removedRanges, addedRanges := suggestionChangedRanges(removed, added)
|
||||||
|
entry := suggestionRenderCacheEntry{}
|
||||||
|
for index, source := range removed {
|
||||||
|
entry.removed = append(entry.removed, wrapSuggestionLine(
|
||||||
|
path, source, '-', removedRanges[index], width,
|
||||||
|
)...)
|
||||||
|
}
|
||||||
|
for index, source := range added {
|
||||||
|
entry.added = append(entry.added, wrapSuggestionLine(
|
||||||
|
path, source, '+', addedRanges[index], width,
|
||||||
|
)...)
|
||||||
|
}
|
||||||
|
return renderedSuggestions.put(key, entry)
|
||||||
|
}
|
||||||
|
|
||||||
func parseCommentBody(body string) parsedCommentBody {
|
func parseCommentBody(body string) parsedCommentBody {
|
||||||
var (
|
var (
|
||||||
result parsedCommentBody
|
result parsedCommentBody
|
||||||
|
|||||||
@@ -85,6 +85,27 @@ func TestDetailRendersSuggestionAsRemovalAndAddition(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderedSuggestionsAreCachedWithoutSharingMutableLines(t *testing.T) {
|
||||||
|
renderedSuggestions.clear()
|
||||||
|
reviewed := []string{"old_value = compute()", "return old_value"}
|
||||||
|
first := renderSuggestion(
|
||||||
|
"example.py", reviewed, "new_value = compute()\nreturn new_value", 50,
|
||||||
|
)
|
||||||
|
if len(renderedSuggestions.entries) != 1 {
|
||||||
|
t.Fatalf("suggestion cache entries = %d, want 1", len(renderedSuggestions.entries))
|
||||||
|
}
|
||||||
|
first.removed[0].rail = "mutated"
|
||||||
|
second := renderSuggestion(
|
||||||
|
"example.py", reviewed, "new_value = compute()\nreturn new_value", 50,
|
||||||
|
)
|
||||||
|
if second.removed[0].rail != "" {
|
||||||
|
t.Fatal("caller mutation changed cached suggestion lines")
|
||||||
|
}
|
||||||
|
if len(renderedSuggestions.entries) != 1 {
|
||||||
|
t.Fatalf("cache miss for unchanged suggestion: %d entries", len(renderedSuggestions.entries))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSuggestionBackgroundIsDirectionalWithoutTextUnderline(t *testing.T) {
|
func TestSuggestionBackgroundIsDirectionalWithoutTextUnderline(t *testing.T) {
|
||||||
removed := suggestionHighlight(" - ", "old", 12, '-')
|
removed := suggestionHighlight(" - ", "old", 12, '-')
|
||||||
added := suggestionHighlight(" + ", "new", 12, '+')
|
added := suggestionHighlight(" + ", "new", 12, '+')
|
||||||
|
|||||||
1
theme.go
1
theme.go
@@ -102,6 +102,7 @@ func applyTheme(name string, custom ...CustomThemeConfig) error {
|
|||||||
currentThemeName = name
|
currentThemeName = name
|
||||||
commentMarkdownRenderers.Clear()
|
commentMarkdownRenderers.Clear()
|
||||||
commentMarkdownLines.clear()
|
commentMarkdownLines.clear()
|
||||||
|
renderedSuggestions.clear()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
21
tui.go
21
tui.go
@@ -3766,27 +3766,16 @@ func (m App) detailLines(width int) []detailLine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for suggestionIndex, suggestion := range content.Suggestions {
|
for suggestionIndex, suggestion := range content.Suggestions {
|
||||||
removed, added := normalizeSuggestion(reviewedSourceLines(thread, comment), suggestion)
|
|
||||||
removedRanges, addedRanges := suggestionChangedRanges(removed, added)
|
|
||||||
label := "suggested change"
|
label := "suggested change"
|
||||||
if len(content.Suggestions) > 1 {
|
if len(content.Suggestions) > 1 {
|
||||||
label = fmt.Sprintf("suggested change %d/%d", suggestionIndex+1, len(content.Suggestions))
|
label = fmt.Sprintf("suggested change %d/%d", suggestionIndex+1, len(content.Suggestions))
|
||||||
}
|
}
|
||||||
lines = append(lines, detailLine{rail: rail}, detailLine{rail: rail, text: dimStyle.Render(label)})
|
lines = append(lines, detailLine{rail: rail}, detailLine{rail: rail, text: dimStyle.Render(label)})
|
||||||
for sourceIndex, source := range removed {
|
rendered := renderSuggestion(
|
||||||
lines = append(lines, addCommentRail(
|
thread.Path, reviewedSourceLines(thread, comment), suggestion, max(1, width-4),
|
||||||
wrapSuggestionLine(
|
)
|
||||||
thread.Path, source, '-', removedRanges[sourceIndex], max(1, width-4),
|
lines = append(lines, addCommentRail(rendered.removed, rail)...)
|
||||||
), rail,
|
lines = append(lines, addCommentRail(rendered.added, rail)...)
|
||||||
)...)
|
|
||||||
}
|
|
||||||
for sourceIndex, source := range added {
|
|
||||||
lines = append(lines, addCommentRail(
|
|
||||||
wrapSuggestionLine(
|
|
||||||
thread.Path, source, '+', addedRanges[sourceIndex], max(1, width-4),
|
|
||||||
), rail,
|
|
||||||
)...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for reactionIndex, reactionLine := range renderReactionSummary(
|
for reactionIndex, reactionLine := range renderReactionSummary(
|
||||||
comment.Reactions, max(1, width-4),
|
comment.Reactions, max(1, width-4),
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
const dipleVersion = "0.3.1"
|
const dipleVersion = "0.3.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user