diff --git a/kittens/choose_files/image_preview.go b/kittens/choose_files/image_preview.go index d95943eb4..9b96be5a2 100644 --- a/kittens/choose_files/image_preview.go +++ b/kittens/choose_files/image_preview.go @@ -57,6 +57,7 @@ type ImagePreview struct { cached_data map[string]string render_err Preview render_channel chan render_data + ready atomic.Bool source_img *images.ImageData img_metadata *images.SerializableImageMetadata renderer PreviewRenderer @@ -65,6 +66,7 @@ type ImagePreview struct { } func (p *ImagePreview) IsValidForColorScheme(bool) bool { return true } +func (p *ImagePreview) IsReady() bool { return p.ready.Load() || p.render_channel == nil } func (p *ImagePreview) Unload() { p.source_img = nil @@ -163,6 +165,7 @@ func (p *ImagePreview) start_rendering() { p.render_channel <- render_data{err: parallel.Format_stacktrace_on_panic(r, 1)} } close(p.render_channel) + p.ready.Store(true) p.WakeupMainThread() }() key, ans, err := p.disk_cache.GetPath(p.abspath) diff --git a/kittens/choose_files/main.go b/kittens/choose_files/main.go index d6030f091..31fb96ef2 100644 --- a/kittens/choose_files/main.go +++ b/kittens/choose_files/main.go @@ -207,19 +207,20 @@ type ScreenSize struct { } type Handler struct { - state State - screen_size ScreenSize - result_manager *ResultManager - lp *loop.Loop - rl *readline.Readline - err_chan chan error - shortcut_tracker config.ShortcutTracker - msg_printer *message.Printer - spinner *tui.Spinner - preview_manager *PreviewManager - last_rendered_preview Preview - last_rendered_preview_abspath string - graphics_handler GraphicsHandler + state State + screen_size ScreenSize + result_manager *ResultManager + lp *loop.Loop + rl *readline.Readline + err_chan chan error + shortcut_tracker config.ShortcutTracker + msg_printer *message.Printer + spinner *tui.Spinner + preview_manager *PreviewManager + last_rendered_preview Preview + last_rendered_preview_abspath string + prev_preview_for_smooth_transition Preview + graphics_handler GraphicsHandler } func (self *Handler) on_escape_code(etype loop.EscapeCodeType, payload []byte) error { @@ -838,6 +839,7 @@ func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) { } lp.OnResize = func(old, new_size loop.ScreenSize) (err error) { handler.init_sizes(new_size) + handler.clear_cached_previews() return handler.draw_screen() } lp.OnColorSchemeChange = func(p loop.ColorPreference) (err error) { diff --git a/kittens/choose_files/preview.go b/kittens/choose_files/preview.go index 5a91dfcbd..bd84fcfa0 100644 --- a/kittens/choose_files/preview.go +++ b/kittens/choose_files/preview.go @@ -9,6 +9,7 @@ import ( "slices" "strings" "sync" + "sync/atomic" "unicode/utf8" "github.com/kovidgoyal/go-parallel" @@ -27,6 +28,7 @@ type Preview interface { Render(h *Handler, x, y, width, height int) IsValidForColorScheme(light bool) bool Unload() + IsReady() bool } type PreviewManager struct { @@ -81,6 +83,7 @@ type MessagePreview struct { } func (p MessagePreview) IsValidForColorScheme(bool) bool { return true } +func (p MessagePreview) IsReady() bool { return true } func (p MessagePreview) Unload() {} func (p MessagePreview) Render(h *Handler, x, y, width, height int) { @@ -186,14 +189,16 @@ type highlighed_data struct { type TextFilePreview struct { plain_text, highlighted_text string highlighted_chan chan highlighed_data + ready atomic.Bool light bool path string } -func (p TextFilePreview) IsValidForColorScheme(light bool) bool { return p.light == light } +func (p *TextFilePreview) IsValidForColorScheme(light bool) bool { return p.light == light } func (p *TextFilePreview) Unload() {} +func (p *TextFilePreview) IsReady() bool { return p.ready.Load() || p.highlighted_chan == nil } func (p *TextFilePreview) Render(h *Handler, x, y, width, height int) { if p.highlighted_chan != nil { select { @@ -256,22 +261,20 @@ func (s style_resolver) TextForPath(path string) (string, error) { return "", err } -func (pm *PreviewManager) highlight_file_async(path string, output chan highlighed_data) { +func (pm *PreviewManager) highlight_file_async(path string, output chan highlighed_data, ready *atomic.Bool) { s := style_resolver{light: use_light_colors, syntax_aliases: pm.settings.SyntaxAliases()} s.light_style, s.dark_style = pm.settings.HighlightStyles() go func() { defer func() { if r := recover(); r != nil { err := parallel.Format_stacktrace_on_panic(r, 1) - debugprintln(fmt.Sprintf("Failed to highlight: %s with panic: %s", path, err)) + output <- highlighed_data{err: err, light: s.light} } close(output) + ready.Store(true) pm.WakeupMainThread() }() highlighted, err := pm.highlighter.HighlightFile(path, &s) - if err != nil { - debugprintln(fmt.Sprintf("Failed to highlight: %s with error: %s", path, err)) - } output <- highlighed_data{text: highlighted, err: err, light: s.light} }() } @@ -305,8 +308,11 @@ func (pm *PreviewManager) preview_for(abspath string, ftype fs.FileMode) (ans Pr const MAX_TEXT_FILE_SIZE = 16 * 1024 * 1024 if s.Size() <= MAX_TEXT_FILE_SIZE && (utils.KnownTextualMimes[mt] || strings.HasPrefix(mt, "text/")) { ch := make(chan highlighed_data, 2) - pm.highlight_file_async(abspath, ch) - return NewTextFilePreview(abspath, s, ch, pm.highlighter.Sanitize) + ans := NewTextFilePreview(abspath, s, ch, pm.highlighter.Sanitize) + if p, ok := ans.(*TextFilePreview); ok { + pm.highlight_file_async(abspath, ch, &p.ready) + } + return ans } if strings.HasPrefix(mt, "image/") { var r ImagePreviewRenderer @@ -319,6 +325,17 @@ func (pm *PreviewManager) preview_for(abspath string, ftype fs.FileMode) (ans Pr return NewFileMetadataPreview(abspath, s) } +func (h *Handler) clear_cached_previews() { + if h.last_rendered_preview != nil { + h.last_rendered_preview.Unload() + h.last_rendered_preview = nil + } + if h.prev_preview_for_smooth_transition != nil { + h.prev_preview_for_smooth_transition.Unload() + h.prev_preview_for_smooth_transition = nil + } +} + func (h *Handler) draw_preview_content(x, y, width, height int) { matches, _ := h.get_results() r := matches.At(h.state.CurrentIndex()) @@ -326,13 +343,29 @@ func (h *Handler) draw_preview_content(x, y, width, height int) { h.render_wrapped_text_in_region("No preview available", x, y, width, height, false) return } + render := func() { + p := h.last_rendered_preview + if p.IsReady() || h.prev_preview_for_smooth_transition == nil { + p.Render(h, x, y, width, height) + if h.prev_preview_for_smooth_transition != nil { + h.prev_preview_for_smooth_transition.Unload() + h.prev_preview_for_smooth_transition = nil + } + } else { + h.prev_preview_for_smooth_transition.Render(h, x, y, width, height) + } + } + abspath := h.current_abspath() if h.last_rendered_preview != nil { if abspath == h.last_rendered_preview_abspath { - h.last_rendered_preview.Render(h, x, y, width, height) + render() return } - h.last_rendered_preview.Unload() + if h.prev_preview_for_smooth_transition != nil { + h.prev_preview_for_smooth_transition.Unload() + } + h.prev_preview_for_smooth_transition = h.last_rendered_preview h.last_rendered_preview = nil } if p := h.preview_manager.preview_for(abspath, r.ftype); p == nil { @@ -340,6 +373,6 @@ func (h *Handler) draw_preview_content(x, y, width, height int) { } else { h.last_rendered_preview = p h.last_rendered_preview_abspath = abspath - p.Render(h, x, y, width, height) + render() } }