diff --git a/kittens/diff/highlight.go b/kittens/diff/highlight.go index 76add4d4b..01d73769d 100644 --- a/kittens/diff/highlight.go +++ b/kittens/diff/highlight.go @@ -32,7 +32,7 @@ var highlighter = sync.OnceValue(func() highlight.Highlighter { func highlight_all(paths []string, light bool) { ctx := images.Context{} srd := prefer_light_colors(light) - ctx.Parallel(0, len(paths), func(nums <-chan int) { + if err := ctx.SafeParallel(0, len(paths), func(nums <-chan int) { for i := range nums { path := paths[i] raw, err := highlighter().HighlightFile(path, &srd) @@ -45,5 +45,7 @@ func highlight_all(paths []string, light bool) { dark_highlighted_lines_cache.Set(path, text_to_lines(raw)) } } - }) + }); err != nil { + panic(err) + } } diff --git a/kittens/diff/patch.go b/kittens/diff/patch.go index 6201ff6b8..810f7e61b 100644 --- a/kittens/diff/patch.go +++ b/kittens/diff/patch.go @@ -358,14 +358,16 @@ func diff(jobs []diff_job, context_count int) (ans map[string]*Patch, err error) patch *Patch } results := make(chan result, len(jobs)) - ctx.Parallel(0, len(jobs), func(nums <-chan int) { + if err := ctx.SafeParallel(0, len(jobs), func(nums <-chan int) { for i := range nums { job := jobs[i] r := result{file1: job.file1, file2: job.file2} r.patch, r.err = do_diff(job.file1, job.file2, context_count) results <- r } - }) + }); err != nil { + panic(err) + } close(results) for r := range results { if r.err != nil { diff --git a/kittens/diff/search.go b/kittens/diff/search.go index 25ae7366b..8275b44a0 100644 --- a/kittens/diff/search.go +++ b/kittens/diff/search.go @@ -106,7 +106,7 @@ func (self *Search) search(logical_lines *LogicalLines) { self.matches = make(map[ScrollPos][]Span) ctx := images.Context{} mutex := sync.Mutex{} - ctx.Parallel(0, logical_lines.Len(), func(nums <-chan int) { + if err := ctx.SafeParallel(0, logical_lines.Len(), func(nums <-chan int) { for i := range nums { line := logical_lines.At(i) if line.line_type == EMPTY_LINE || line.line_type == IMAGE_LINE { @@ -121,7 +121,9 @@ func (self *Search) search(logical_lines *LogicalLines) { } }) } - }) + }); err != nil { + panic(err) + } for _, spans := range self.matches { slices.SortFunc(spans, func(a, b Span) int { return a.start - b.start }) } diff --git a/tools/tui/download_with_progress.go b/tools/tui/download_with_progress.go index 0239b0386..806857398 100644 --- a/tools/tui/download_with_progress.go +++ b/tools/tui/download_with_progress.go @@ -101,6 +101,7 @@ func DownloadFileWithProgress(destpath, url string, kill_if_signaled bool) (err } do_download := func() { + lp.RecoverFromPanicInGoRoutine() dl_data.mutex.Lock() dl_data.download_started = true dl_data.mutex.Unlock() diff --git a/tools/tui/graphics/collection.go b/tools/tui/graphics/collection.go index 663685503..6dbc86b45 100644 --- a/tools/tui/graphics/collection.go +++ b/tools/tui/graphics/collection.go @@ -136,14 +136,16 @@ func (self *ImageCollection) ResizeForPageSize(width, height int) { ctx := images.Context{} keys := utils.Keys(self.images) - ctx.Parallel(0, len(keys), func(nums <-chan int) { + if err := ctx.SafeParallel(0, len(keys), func(nums <-chan int) { for i := range nums { img := self.images[keys[i]] if img.src.loaded && img.err == nil { img.ResizeForPageSize(width, height) } } - }) + }); err != nil { + panic(err) + } } func (self *ImageCollection) DeleteAllVisiblePlacements(lp *loop.Loop) { @@ -294,7 +296,7 @@ func (self *ImageCollection) LoadAll() { defer self.mutex.Unlock() ctx := images.Context{} all := utils.Values(self.images) - ctx.Parallel(0, len(self.images), func(nums <-chan int) { + if err := ctx.SafeParallel(0, len(self.images), func(nums <-chan int) { for i := range nums { img := all[i] if !img.src.loaded { @@ -305,7 +307,9 @@ func (self *ImageCollection) LoadAll() { img.src.loaded = true } } - }) + }); err != nil { + panic(err) + } } func NewImageCollection(paths ...string) *ImageCollection { diff --git a/tools/tui/loop/read.go b/tools/tui/loop/read.go index 585f671be..80ab71f5f 100644 --- a/tools/tui/loop/read.go +++ b/tools/tui/loop/read.go @@ -44,6 +44,12 @@ func read_ignoring_temporary_errors(f *tty.Term, buf []byte) (int, error) { } func read_from_tty(pipe_r *os.File, term *tty.Term, results_channel chan<- []byte, err_channel chan<- error, quit_channel <-chan byte, leftover_channel chan<- []byte) { + defer func() { + if r := recover(); r != nil { + text, _ := utils.Format_stacktrace_on_panic(r) + err_channel <- fmt.Errorf("%s", text) + } + }() keep_going := true pipe_fd := int(pipe_r.Fd()) tty_fd := term.Fd() @@ -115,6 +121,12 @@ func read_until_primary_device_attributes_response(term *tty.Term, initial_bytes } received := make(chan error) go func() { + defer func() { + if r := recover(); r != nil { + text, _ := utils.Format_stacktrace_on_panic(r) + received <- fmt.Errorf("%s", text) + } + }() buf := make([]byte, 1024) n, err := read_ignoring_temporary_errors(term, buf) if n > 0 { diff --git a/tools/tui/loop/write.go b/tools/tui/loop/write.go index a94ba39d7..efd567fad 100644 --- a/tools/tui/loop/write.go +++ b/tools/tui/loop/write.go @@ -168,6 +168,12 @@ func write_to_tty( pipe_r *os.File, term *tty.Term, job_channel <-chan write_msg, err_channel chan<- error, write_done_channel chan<- IdType, ) { + defer func() { + if r := recover(); r != nil { + text, _ := utils.Format_stacktrace_on_panic(r) + err_channel <- fmt.Errorf("%s", text) + } + }() keep_going := true defer func() { pipe_r.Close() diff --git a/tools/unicode_names/query.go b/tools/unicode_names/query.go index bb1e9827e..32109e5a4 100644 --- a/tools/unicode_names/query.go +++ b/tools/unicode_names/query.go @@ -110,11 +110,13 @@ func marks_for_query(query string) (ans mark_set) { prefixes := strings.Split(strings.ToLower(query), " ") results := make(chan mark_set, len(prefixes)) ctx := images.Context{} - ctx.Parallel(0, len(prefixes), func(nums <-chan int) { + if err := ctx.SafeParallel(0, len(prefixes), func(nums <-chan int) { for i := range nums { results <- find_matching_codepoints(prefixes[i]) } - }) + }); err != nil { + panic(err) + } close(results) for x := range results { if ans == nil { diff --git a/tools/utils/images/to_rgba.go b/tools/utils/images/to_rgba.go index b312cf359..77606a629 100644 --- a/tools/utils/images/to_rgba.go +++ b/tools/utils/images/to_rgba.go @@ -321,7 +321,7 @@ func (self *Context) run_paste(src Scanner, background image.Image, pos image.Po default: panic(fmt.Sprintf("Unsupported image type: %v", v)) } - self.Parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) { + if err := self.SafeParallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) { for y := range ys { x1 := interRect.Min.X - pasteRect.Min.X x2 := interRect.Max.X - pasteRect.Min.X @@ -333,7 +333,9 @@ func (self *Context) run_paste(src Scanner, background image.Image, pos image.Po src.scan(x1, y1, x2, y2, dst) postprocess(dst) } - }) + }); err != nil { + panic(err) + } } diff --git a/tools/utils/images/transforms.go b/tools/utils/images/transforms.go index 921f67a6a..5fd5c7ff1 100644 --- a/tools/utils/images/transforms.go +++ b/tools/utils/images/transforms.go @@ -27,18 +27,20 @@ func reverse_row(bytes_per_pixel int, pix []uint8) { func (self *Context) FlipPixelsH(bytes_per_pixel, width, height int, pix []uint8) { stride := bytes_per_pixel * width - self.Parallel(0, height, func(ys <-chan int) { + if err := self.SafeParallel(0, height, func(ys <-chan int) { for y := range ys { i := y * stride reverse_row(bytes_per_pixel, pix[i:i+stride]) } - }) + }); err != nil { + panic(err) + } } func (self *Context) FlipPixelsV(bytes_per_pixel, width, height int, pix []uint8) { stride := bytes_per_pixel * width num := height / 2 - self.Parallel(0, num, func(ys <-chan int) { + if err := self.SafeParallel(0, num, func(ys <-chan int) { for y := range ys { upper := y lower := height - 1 - y @@ -50,6 +52,7 @@ func (self *Context) FlipPixelsV(bytes_per_pixel, width, height int, pix []uint8 as[i], bs[i] = bs[i], as[i] } } - }) - + }); err != nil { + panic(err) + } } diff --git a/tools/utils/images/utils.go b/tools/utils/images/utils.go index e25f83e53..9b49c7674 100644 --- a/tools/utils/images/utils.go +++ b/tools/utils/images/utils.go @@ -7,6 +7,8 @@ import ( "runtime" "sync" "sync/atomic" + + "github.com/kovidgoyal/kitty/tools/utils" ) var _ = fmt.Print @@ -31,8 +33,10 @@ func (self *Context) EffectiveNumberOfThreads() int { return ans } -// parallel processes the data in separate goroutines. -func (self *Context) Parallel(start, stop int, fn func(<-chan int)) { +// parallel processes the data in separate goroutines. If any of them panics, +// returns an error. Note that if multiple goroutines panic, only one error is +// returned. +func (self *Context) SafeParallel(start, stop int, fn func(<-chan int)) (err error) { count := stop - start if count < 1 { return @@ -49,9 +53,16 @@ func (self *Context) Parallel(start, stop int, fn func(<-chan int)) { for range procs { wg.Add(1) go func() { - defer wg.Done() + defer func() { + if r := recover(); r != nil { + text, _ := utils.Format_stacktrace_on_panic(r) + err = fmt.Errorf("%s", text) + } + wg.Done() + }() fn(c) }() } wg.Wait() + return }