@ 261102.1

250621.4 · 247d168af6fabcc322f54ad366f4fb45f781137b

Terminate running background transform on exit

Close #4422

reading declarations

src/terminal.go

@@ -9,6 +9,7 @@ import ( 	"math" 	"net" 	"os"+	"os/exec" 	"os/signal" 	"regexp" 	"sort"@@ -377,6 +378,7 @@ type Terminal struct { 	version            int64 	revision           revision 	bgVersion          int64+	runningCmds        *util.ConcurrentSet[*exec.Cmd] 	reqBox             *util.EventBox 	initialPreviewOpts previewOpts 	previewOpts        previewOpts@@ -1030,6 +1032,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor 		proxyScript:        opts.ProxyScript, 		merger:             EmptyMerger(revision{}), 		selected:           make(map[int32]selectedItem),+		runningCmds:        util.NewConcurrentSet[*exec.Cmd](), 		reqBox:             util.NewEventBox(), 		initialPreviewOpts: opts.Preview, 		previewOpts:        opts.Preview,@@ -4377,6 +4380,7 @@ func (t *Terminal) captureAsync(a action, firstLineOnly bool, callback func(stri 			reader := bufio.NewReader(out) 			var output string 			if err := cmd.Start(); err == nil {+				t.runningCmds.Add(cmd) 				if firstLineOnly { 					output, _ = reader.ReadString('\n') 					output = strings.TrimRight(output, "\r\n")@@ -4385,6 +4389,7 @@ func (t *Terminal) captureAsync(a action, firstLineOnly bool, callback func(stri 					output = string(bytes) 				} 				cmd.Wait()+				t.runningCmds.Remove(cmd) 			} 			t.callbackChan <- versionedCallback{version, func() { callback(output) }} 		}@@ -5053,6 +5058,9 @@ func (t *Terminal) Loop() error { 			if code <= ExitNoMatch && t.history != nil { 				t.history.append(string(t.input)) 			}+			t.runningCmds.ForEach(func(cmd *exec.Cmd) {+				util.KillCommand(cmd)+			}) 			running = false 			t.mutex.Unlock() 		}

src/util/concurrent_set.go

new file mode 100644@@ -0,0 +1,39 @@+package util++import "sync"++// ConcurrentSet is a thread-safe set implementation.+type ConcurrentSet[T comparable] struct {+	lock  sync.RWMutex+	items map[T]struct{}+}++// NewConcurrentSet creates a new ConcurrentSet.+func NewConcurrentSet[T comparable]() *ConcurrentSet[T] {+	return &ConcurrentSet[T]{+		items: make(map[T]struct{}),+	}+}++// Add adds an item to the set.+func (s *ConcurrentSet[T]) Add(item T) {+	s.lock.Lock()+	defer s.lock.Unlock()+	s.items[item] = struct{}{}+}++// Remove removes an item from the set.+func (s *ConcurrentSet[T]) Remove(item T) {+	s.lock.Lock()+	defer s.lock.Unlock()+	delete(s.items, item)+}++// ForEach iterates over each item in the set and applies the provided function.+func (s *ConcurrentSet[T]) ForEach(fn func(item T)) {+	s.lock.RLock()+	defer s.lock.RUnlock()+	for item := range s.items {+		fn(item)+	}+}