detached 1bc97e8

perf(sessions): cache search projections

commit
1bc97e8414b7244f5d5f965590873110b2069c9f
author
brice · · 2026-08-19 05:50 UTC
parent
caa47a7
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +22 -9
M packages/agent/modes/session_dialog.go +14 -2
@@ -544,9 +544,21 @@ func matchSessionSearchSegments(ctx context.Context, query string, segments []co
544544 if query == "" {
545545 return nil
546546 }
547+ if ctx.Err() != nil {
548+ return nil
549+ }
547550 texts := make([]string, len(segments))
548551 for index, segment := range segments {
549- texts[index] = core.NormalizeSessionSearchText(segment.Text)
552+ if ctx.Err() != nil {
553+ return nil
554+ }
555+ texts[index] = segment.Normalized
556+ if texts[index] == "" {
557+ texts[index] = core.NormalizeSessionSearchText(segment.Text)
558+ }
559+ }
560+ if ctx.Err() != nil {
561+ return nil
550562 }
551563 ranked := fuzzy.Find(query, texts)
552564 matches := make(map[string]sessionSearchMatch)
@@ -558,7 +570,7 @@ func matchSessionSearchSegments(ctx context.Context, query string, segments []co
558570 current := matches[segment.Path]
559571 current.count++
560572 if current.excerpt == "" || match.Score > current.score {
561- current.excerpt = segment.Text
573+ current.excerpt = texts[match.Index]
562574 current.indexes = append([]int(nil), match.MatchedIndexes...)
563575 current.score = match.Score
564576 }
@@ -544,9 +544,21 @@ func matchSessionSearchSegments(ctx context.Context, query string, segments []co
544 if query == "" {544 if query == "" {
545 return nil545 return nil
546 }546 }
547+ if ctx.Err() != nil {
548+ return nil
549+ }
547 texts := make([]string, len(segments))550 texts := make([]string, len(segments))
548 for index, segment := range segments {551 for index, segment := range segments {
549- texts[index] = core.NormalizeSessionSearchText(segment.Text)552+ if ctx.Err() != nil {
553+ return nil
554+ }
555+ texts[index] = segment.Normalized
556+ if texts[index] == "" {
557+ texts[index] = core.NormalizeSessionSearchText(segment.Text)
558+ }
559+ }
560+ if ctx.Err() != nil {
561+ return nil
550 }562 }
551 ranked := fuzzy.Find(query, texts)563 ranked := fuzzy.Find(query, texts)
552 matches := make(map[string]sessionSearchMatch)564 matches := make(map[string]sessionSearchMatch)
@@ -558,7 +570,7 @@ func matchSessionSearchSegments(ctx context.Context, query string, segments []co
558 current := matches[segment.Path]570 current := matches[segment.Path]
559 current.count++571 current.count++
560 if current.excerpt == "" || match.Score > current.score {572 if current.excerpt == "" || match.Score > current.score {
561- current.excerpt = segment.Text573+ current.excerpt = texts[match.Index]
562 current.indexes = append([]int(nil), match.MatchedIndexes...)574 current.indexes = append([]int(nil), match.MatchedIndexes...)
563 current.score = match.Score575 current.score = match.Score
564 }576 }
M packages/core/session_picker.go +8 -7
@@ -27,11 +27,12 @@ const (
2727 // excerpt from a persisted session. It deliberately never contains tool,
2828 // developer, image, or raw JSON data.
2929 type SessionSearchSegment struct {
30- Path string
31- Text string
32- Role provider.Role
33- Time time.Time
34- Order int
30+ Path string
31+ Text string
32+ Normalized string
33+ Role provider.Role
34+ Time time.Time
35+ Order int
3536 }
3637
3738 // ListSessionPathsContext lists eligible session files for cwd or, when all is
@@ -191,7 +192,7 @@ func ReadSessionSearchSegments(ctx context.Context, path string) ([]SessionSearc
191192 if message.Role == provider.RoleUser || message.Role == provider.RoleAssistant {
192193 for _, rawText := range message.Texts {
193194 for _, text := range splitSessionSearchText(rawText) {
194- segments = append(segments, SessionSearchSegment{Path: path, Text: text, Role: message.Role, Time: message.Time, Order: order})
195+ segments = append(segments, SessionSearchSegment{Path: path, Text: text, Normalized: NormalizeSessionSearchText(text), Role: message.Role, Time: message.Time, Order: order})
195196 order++
196197 }
197198 }
@@ -284,7 +285,7 @@ func splitSessionSearchText(text string) []string {
284285 for len(runes) > 0 {
285286 end := min(len(runes), maxSessionSearchRunes)
286287 if end < len(runes) {
287- for split := end; split > end/2; split-- {
288+ for split := end - 1; split > end/2; split-- {
288289 if unicode.IsSpace(runes[split]) || unicode.Is(unicode.Sentence_Terminal, runes[split]) {
289290 end = split + 1
290291 break
@@ -27,11 +27,12 @@ const (
27 // excerpt from a persisted session. It deliberately never contains tool,27 // excerpt from a persisted session. It deliberately never contains tool,
28 // developer, image, or raw JSON data.28 // developer, image, or raw JSON data.
29 type SessionSearchSegment struct {29 type SessionSearchSegment struct {
30- Path string30+ Path string
31- Text string31+ Text string
32- Role provider.Role32+ Normalized string
33- Time time.Time33+ Role provider.Role
34- Order int34+ Time time.Time
35+ Order int
35 }36 }
36 37
37 // ListSessionPathsContext lists eligible session files for cwd or, when all is38 // ListSessionPathsContext lists eligible session files for cwd or, when all is
@@ -191,7 +192,7 @@ func ReadSessionSearchSegments(ctx context.Context, path string) ([]SessionSearc
191 if message.Role == provider.RoleUser || message.Role == provider.RoleAssistant {192 if message.Role == provider.RoleUser || message.Role == provider.RoleAssistant {
192 for _, rawText := range message.Texts {193 for _, rawText := range message.Texts {
193 for _, text := range splitSessionSearchText(rawText) {194 for _, text := range splitSessionSearchText(rawText) {
194- segments = append(segments, SessionSearchSegment{Path: path, Text: text, Role: message.Role, Time: message.Time, Order: order})195+ segments = append(segments, SessionSearchSegment{Path: path, Text: text, Normalized: NormalizeSessionSearchText(text), Role: message.Role, Time: message.Time, Order: order})
195 order++196 order++
196 }197 }
197 }198 }
@@ -284,7 +285,7 @@ func splitSessionSearchText(text string) []string {
284 for len(runes) > 0 {285 for len(runes) > 0 {
285 end := min(len(runes), maxSessionSearchRunes)286 end := min(len(runes), maxSessionSearchRunes)
286 if end < len(runes) {287 if end < len(runes) {
287- for split := end; split > end/2; split-- {288+ for split := end - 1; split > end/2; split-- {
288 if unicode.IsSpace(runes[split]) || unicode.Is(unicode.Sentence_Terminal, runes[split]) {289 if unicode.IsSpace(runes[split]) || unicode.Is(unicode.Sentence_Terminal, runes[split]) {
289 end = split + 1290 end = split + 1
290 break291 break

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close