Skip to content

Commit 0ed2ae9

Browse files
authored
Merge pull request #2566 from dgageot/board/refactor-codebase-to-use-slices-package-16f36c45
Use the slices package to simplify slice operations
2 parents 84b859b + 52802d4 commit 0ed2ae9

10 files changed

Lines changed: 79 additions & 101 deletions

File tree

pkg/config/hooks.go

Lines changed: 39 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"slices"
45
"strings"
56

67
"github.com/docker/docker-agent/pkg/config/latest"
@@ -10,55 +11,41 @@ import (
1011
// Each string is treated as a shell command to run.
1112
// Empty strings are silently skipped.
1213
func HooksFromCLI(preToolUse, postToolUse, sessionStart, sessionEnd, onUserInput []string) *latest.HooksConfig {
13-
hooks := &latest.HooksConfig{}
14-
15-
if len(preToolUse) > 0 {
16-
var defs []latest.HookDefinition
17-
for _, cmd := range preToolUse {
18-
if strings.TrimSpace(cmd) == "" {
19-
continue
20-
}
21-
defs = append(defs, latest.HookDefinition{Type: "command", Command: cmd})
22-
}
23-
if len(defs) > 0 {
24-
hooks.PreToolUse = []latest.HookMatcherConfig{{Hooks: defs}}
25-
}
14+
hooks := &latest.HooksConfig{
15+
PreToolUse: matcherFromCommands(preToolUse),
16+
PostToolUse: matcherFromCommands(postToolUse),
17+
SessionStart: defsFromCommands(sessionStart),
18+
SessionEnd: defsFromCommands(sessionEnd),
19+
OnUserInput: defsFromCommands(onUserInput),
2620
}
2721

28-
if len(postToolUse) > 0 {
29-
var defs []latest.HookDefinition
30-
for _, cmd := range postToolUse {
31-
if strings.TrimSpace(cmd) == "" {
32-
continue
33-
}
34-
defs = append(defs, latest.HookDefinition{Type: "command", Command: cmd})
35-
}
36-
if len(defs) > 0 {
37-
hooks.PostToolUse = []latest.HookMatcherConfig{{Hooks: defs}}
38-
}
22+
if hooks.IsEmpty() {
23+
return nil
3924
}
25+
return hooks
26+
}
4027

41-
for _, cmd := range sessionStart {
42-
if strings.TrimSpace(cmd) != "" {
43-
hooks.SessionStart = append(hooks.SessionStart, latest.HookDefinition{Type: "command", Command: cmd})
44-
}
45-
}
46-
for _, cmd := range sessionEnd {
47-
if strings.TrimSpace(cmd) != "" {
48-
hooks.SessionEnd = append(hooks.SessionEnd, latest.HookDefinition{Type: "command", Command: cmd})
49-
}
50-
}
51-
for _, cmd := range onUserInput {
52-
if strings.TrimSpace(cmd) != "" {
53-
hooks.OnUserInput = append(hooks.OnUserInput, latest.HookDefinition{Type: "command", Command: cmd})
28+
// defsFromCommands turns a list of CLI shell commands into hook definitions,
29+
// skipping any blank entries.
30+
func defsFromCommands(cmds []string) []latest.HookDefinition {
31+
var defs []latest.HookDefinition
32+
for _, cmd := range cmds {
33+
if strings.TrimSpace(cmd) == "" {
34+
continue
5435
}
36+
defs = append(defs, latest.HookDefinition{Type: "command", Command: cmd})
5537
}
38+
return defs
39+
}
5640

57-
if hooks.IsEmpty() {
41+
// matcherFromCommands wraps the result of defsFromCommands in a single
42+
// HookMatcherConfig so the commands apply to all tools (empty matcher).
43+
func matcherFromCommands(cmds []string) []latest.HookMatcherConfig {
44+
defs := defsFromCommands(cmds)
45+
if len(defs) == 0 {
5846
return nil
5947
}
60-
61-
return hooks
48+
return []latest.HookMatcherConfig{{Hooks: defs}}
6249
}
6350

6451
// MergeHooks merges CLI hooks into an existing HooksConfig.
@@ -74,18 +61,18 @@ func MergeHooks(base, cli *latest.HooksConfig) *latest.HooksConfig {
7461
}
7562

7663
merged := &latest.HooksConfig{
77-
PreToolUse: append(append([]latest.HookMatcherConfig{}, base.PreToolUse...), cli.PreToolUse...),
78-
PostToolUse: append(append([]latest.HookMatcherConfig{}, base.PostToolUse...), cli.PostToolUse...),
79-
SessionStart: append(append([]latest.HookDefinition{}, base.SessionStart...), cli.SessionStart...),
80-
TurnStart: append(append([]latest.HookDefinition{}, base.TurnStart...), cli.TurnStart...),
81-
BeforeLLMCall: append(append([]latest.HookDefinition{}, base.BeforeLLMCall...), cli.BeforeLLMCall...),
82-
AfterLLMCall: append(append([]latest.HookDefinition{}, base.AfterLLMCall...), cli.AfterLLMCall...),
83-
SessionEnd: append(append([]latest.HookDefinition{}, base.SessionEnd...), cli.SessionEnd...),
84-
OnUserInput: append(append([]latest.HookDefinition{}, base.OnUserInput...), cli.OnUserInput...),
85-
Stop: append(append([]latest.HookDefinition{}, base.Stop...), cli.Stop...),
86-
Notification: append(append([]latest.HookDefinition{}, base.Notification...), cli.Notification...),
87-
OnError: append(append([]latest.HookDefinition{}, base.OnError...), cli.OnError...),
88-
OnMaxIterations: append(append([]latest.HookDefinition{}, base.OnMaxIterations...), cli.OnMaxIterations...),
64+
PreToolUse: slices.Concat(base.PreToolUse, cli.PreToolUse),
65+
PostToolUse: slices.Concat(base.PostToolUse, cli.PostToolUse),
66+
SessionStart: slices.Concat(base.SessionStart, cli.SessionStart),
67+
TurnStart: slices.Concat(base.TurnStart, cli.TurnStart),
68+
BeforeLLMCall: slices.Concat(base.BeforeLLMCall, cli.BeforeLLMCall),
69+
AfterLLMCall: slices.Concat(base.AfterLLMCall, cli.AfterLLMCall),
70+
SessionEnd: slices.Concat(base.SessionEnd, cli.SessionEnd),
71+
OnUserInput: slices.Concat(base.OnUserInput, cli.OnUserInput),
72+
Stop: slices.Concat(base.Stop, cli.Stop),
73+
Notification: slices.Concat(base.Notification, cli.Notification),
74+
OnError: slices.Concat(base.OnError, cli.OnError),
75+
OnMaxIterations: slices.Concat(base.OnMaxIterations, cli.OnMaxIterations),
8976
}
9077
return merged
9178
}

pkg/fsx/collect_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package fsx
33
import (
44
"os"
55
"path/filepath"
6-
"sort"
6+
"slices"
77
"strings"
88
"testing"
99

@@ -379,7 +379,7 @@ func TestCollectFiles_SortedOutput(t *testing.T) {
379379

380380
// Note: CollectFiles doesn't guarantee order, but results should be consistent
381381
// Sort for comparison
382-
sort.Strings(got)
382+
slices.Sort(got)
383383
assert.True(t, strings.HasSuffix(got[0], "a.go"))
384384
assert.True(t, strings.HasSuffix(got[1], "m.go"))
385385
assert.True(t, strings.HasSuffix(got[2], "z.go"))

pkg/hooks/builtins/add_directory_listing.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"fmt"
66
"log/slog"
77
"os"
8-
"sort"
8+
"slices"
99
"strings"
1010

1111
"github.com/docker/docker-agent/pkg/hooks"
@@ -56,7 +56,7 @@ func addDirectoryListing(_ context.Context, in *hooks.Input, _ []string) (*hooks
5656
if len(names) == 0 {
5757
return nil, nil
5858
}
59-
sort.Strings(names)
59+
slices.Sort(names)
6060

6161
truncated := 0
6262
if len(names) > maxDirectoryEntries {

pkg/hooks/handler.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"os"
1010
"os/exec"
1111
"path/filepath"
12+
"slices"
13+
"strings"
1214
"sync"
1315

1416
"github.com/docker/docker-agent/pkg/shellpath"
@@ -150,27 +152,26 @@ func hookEnv(base []string, overrides map[string]string) []string {
150152
if len(overrides) == 0 {
151153
return base
152154
}
153-
env := append([]string{}, base...)
155+
env := slices.Clone(base)
154156
if len(env) == 0 {
155157
env = os.Environ()
156158
}
159+
160+
// Map each existing key to its position so overrides can replace in place.
157161
index := make(map[string]int, len(env))
158162
for i, entry := range env {
159-
for j, ch := range entry {
160-
if ch == '=' {
161-
index[entry[:j]] = i
162-
break
163-
}
163+
if key, _, ok := strings.Cut(entry, "="); ok {
164+
index[key] = i
164165
}
165166
}
167+
166168
for key, value := range overrides {
167169
entry := key + "=" + value
168170
if i, ok := index[key]; ok {
169171
env[i] = entry
170-
continue
172+
} else {
173+
env = append(env, entry)
171174
}
172-
index[key] = len(env)
173-
env = append(env, entry)
174175
}
175176
return env
176177
}

pkg/runtime/runtime_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"io"
77
"reflect"
8+
"slices"
89
"strings"
910
"sync"
1011
"testing"
@@ -2405,7 +2406,7 @@ func (r *recordingProvider) CreateChatCompletionStream(_ context.Context, _ []ch
24052406
defer r.mu.Unlock()
24062407

24072408
// Record the tool names for this call.
2408-
r.recordedCalls = append(r.recordedCalls, append([]tools.Tool{}, toolList...))
2409+
r.recordedCalls = append(r.recordedCalls, slices.Clone(toolList))
24092410

24102411
if r.callIdx >= len(r.streams) {
24112412
return newStreamBuilder().AddStopWithUsage(1, 1).Build(), nil

pkg/session/migrations_unit_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"database/sql"
66
"errors"
7+
"slices"
78
"testing"
89

910
"github.com/stretchr/testify/assert"
@@ -156,7 +157,7 @@ func TestMigrationManager_CheckForUnknownMigrations_AllowsEqualOrOlderDB(t *test
156157
require.NoError(t, NewMigrationManagerWithMigrations(db, migrations).checkForUnknownMigrations(t.Context()))
157158

158159
// Newer binary that knows about more migrations than the DB has applied: fine.
159-
newer := append([]Migration{}, migrations...)
160+
newer := slices.Clone(migrations)
160161
newer = append(newer, Migration{ID: 2, Name: "002_b", UpSQL: `CREATE TABLE b (id INTEGER)`})
161162
require.NoError(t, NewMigrationManagerWithMigrations(db, newer).checkForUnknownMigrations(t.Context()))
162163
}

pkg/tui/components/editor/editor.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os"
88
"path/filepath"
99
"regexp"
10+
"slices"
1011
"strings"
1112
"time"
1213

@@ -1506,7 +1507,7 @@ func (e *editor) removeLastNAttachments(n int) {
15061507
if !e.attachments[i].isTemp {
15071508
// Strip the placeholder text ("@/path/file.png ") that AttachFile inserted
15081509
value = strings.Replace(value, e.attachments[i].placeholder+" ", "", 1)
1509-
e.attachments = append(e.attachments[:i], e.attachments[i+1:]...)
1510+
e.attachments = slices.Delete(e.attachments, i, i+1)
15101511
removed++
15111512
}
15121513
}

pkg/tui/components/messages/messages.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,9 +1151,9 @@ func (m *model) AddLoadingMessage(description string) tea.Cmd {
11511151
func (m *model) ReplaceLoadingWithUser(content string, sessionPos int) tea.Cmd {
11521152
for i := len(m.messages) - 1; i >= 0; i-- {
11531153
if m.messages[i].Type == types.MessageTypeLoading {
1154-
m.messages = append(m.messages[:i], m.messages[i+1:]...)
1154+
m.messages = slices.Delete(m.messages, i, i+1)
11551155
if i < len(m.views) {
1156-
m.views = append(m.views[:i], m.views[i+1:]...)
1156+
m.views = slices.Delete(m.views, i, i+1)
11571157
}
11581158
m.invalidateAllItems()
11591159
break

pkg/tui/dialog/working_dir_picker.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -417,11 +417,8 @@ func (d *workingDirPickerDialog) cycleSectionBackward() { d.cycleSection(-1) }
417417

418418
func (d *workingDirPickerDialog) cycleSection(delta int) {
419419
n := len(dirPickerSectionOrder)
420-
for i, s := range dirPickerSectionOrder {
421-
if s == d.section {
422-
d.section = dirPickerSectionOrder[(i+delta+n)%n]
423-
break
424-
}
420+
if i := slices.Index(dirPickerSectionOrder, d.section); i >= 0 {
421+
d.section = dirPickerSectionOrder[(i+delta+n)%n]
425422
}
426423
d.updateSectionFocus()
427424
}

pkg/tui/service/supervisor/supervisor.go

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"log/slog"
88
"path/filepath"
9+
"slices"
910
"sync"
1011

1112
tea "charm.land/bubbletea/v2"
@@ -166,9 +167,7 @@ func (s *Supervisor) handleRuntimeEvent(sessionID string, msg tea.Msg) {
166167
case *runtime.StreamStoppedEvent:
167168
runner.IsRunning = false
168169
runner.PendingEvent = nil // Clear any pending attention event since stream ended
169-
if runner.NeedsAttn {
170-
runner.NeedsAttn = false
171-
}
170+
runner.NeedsAttn = false
172171
s.notifyTabsUpdated()
173172

174173
case *runtime.SessionTitleEvent:
@@ -234,12 +233,7 @@ func (s *Supervisor) buildTabInfoLocked() []messages.TabInfo {
234233

235234
// activeIndexLocked returns the index of the active tab (must be called with lock held).
236235
func (s *Supervisor) activeIndexLocked() int {
237-
for i, id := range s.order {
238-
if id == s.activeID {
239-
return i
240-
}
241-
}
242-
return 0
236+
return max(0, slices.Index(s.order, s.activeID))
243237
}
244238

245239
// SwitchTo switches to a different session.
@@ -354,14 +348,14 @@ func (s *Supervisor) Spawner() SessionSpawner {
354348
}
355349

356350
// CloseSession closes a session and removes it from the supervisor.
357-
func (s *Supervisor) CloseSession(sessionID string) (nextActiveID string) {
351+
func (s *Supervisor) CloseSession(sessionID string) string {
358352
s.mu.Lock()
359353

360354
runner, ok := s.runners[sessionID]
361355
if !ok {
362-
nextActiveID = s.activeID
356+
activeID := s.activeID
363357
s.mu.Unlock()
364-
return nextActiveID
358+
return activeID
365359
}
366360

367361
// Cancel the session context
@@ -375,35 +369,31 @@ func (s *Supervisor) CloseSession(sessionID string) (nextActiveID string) {
375369

376370
// Remove from order slice, remembering where it was.
377371
closedIdx := 0
378-
for i, id := range s.order {
379-
if id == sessionID {
380-
closedIdx = i
381-
s.order = append(s.order[:i], s.order[i+1:]...)
382-
break
383-
}
372+
if i := slices.Index(s.order, sessionID); i >= 0 {
373+
closedIdx = i
374+
s.order = slices.Delete(s.order, i, i+1)
384375
}
385376

386377
// If this was the active session, switch to the previous tab (or the
387378
// first one when closing the first tab).
388379
if s.activeID == sessionID {
389-
if len(s.order) > 0 {
390-
prevIdx := max(closedIdx-1, 0)
391-
s.activeID = s.order[prevIdx]
392-
} else {
380+
if len(s.order) == 0 {
393381
s.activeID = ""
382+
} else {
383+
s.activeID = s.order[max(closedIdx-1, 0)]
394384
}
395385
}
396386

397387
s.notifyTabsUpdated()
398-
nextActiveID = s.activeID
388+
activeID := s.activeID
399389
s.mu.Unlock()
400390

401391
// Run cleanup outside the lock so it can't deadlock.
402392
if cleanup != nil {
403393
go cleanup()
404394
}
405395

406-
return nextActiveID
396+
return activeID
407397
}
408398

409399
// Count returns the number of sessions.
@@ -430,8 +420,8 @@ func (s *Supervisor) ReorderTab(fromIdx, toIdx int) {
430420
}
431421

432422
id := s.order[fromIdx]
433-
s.order = append(s.order[:fromIdx], s.order[fromIdx+1:]...)
434-
s.order = append(s.order[:toIdx], append([]string{id}, s.order[toIdx:]...)...)
423+
s.order = slices.Delete(s.order, fromIdx, fromIdx+1)
424+
s.order = slices.Insert(s.order, toIdx, id)
435425
s.notifyTabsUpdated()
436426
}
437427

0 commit comments

Comments
 (0)