mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-24 01:08:10 +02:00
Avoid flicker when changing preview
Only render the new preview when it is fully ready, keep showing previous preview till then.
This commit is contained in:
@@ -57,6 +57,7 @@ type ImagePreview struct {
|
|||||||
cached_data map[string]string
|
cached_data map[string]string
|
||||||
render_err Preview
|
render_err Preview
|
||||||
render_channel chan render_data
|
render_channel chan render_data
|
||||||
|
ready atomic.Bool
|
||||||
source_img *images.ImageData
|
source_img *images.ImageData
|
||||||
img_metadata *images.SerializableImageMetadata
|
img_metadata *images.SerializableImageMetadata
|
||||||
renderer PreviewRenderer
|
renderer PreviewRenderer
|
||||||
@@ -65,6 +66,7 @@ type ImagePreview struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *ImagePreview) IsValidForColorScheme(bool) bool { return true }
|
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() {
|
func (p *ImagePreview) Unload() {
|
||||||
p.source_img = nil
|
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)}
|
p.render_channel <- render_data{err: parallel.Format_stacktrace_on_panic(r, 1)}
|
||||||
}
|
}
|
||||||
close(p.render_channel)
|
close(p.render_channel)
|
||||||
|
p.ready.Store(true)
|
||||||
p.WakeupMainThread()
|
p.WakeupMainThread()
|
||||||
}()
|
}()
|
||||||
key, ans, err := p.disk_cache.GetPath(p.abspath)
|
key, ans, err := p.disk_cache.GetPath(p.abspath)
|
||||||
|
|||||||
@@ -207,19 +207,20 @@ type ScreenSize struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
state State
|
state State
|
||||||
screen_size ScreenSize
|
screen_size ScreenSize
|
||||||
result_manager *ResultManager
|
result_manager *ResultManager
|
||||||
lp *loop.Loop
|
lp *loop.Loop
|
||||||
rl *readline.Readline
|
rl *readline.Readline
|
||||||
err_chan chan error
|
err_chan chan error
|
||||||
shortcut_tracker config.ShortcutTracker
|
shortcut_tracker config.ShortcutTracker
|
||||||
msg_printer *message.Printer
|
msg_printer *message.Printer
|
||||||
spinner *tui.Spinner
|
spinner *tui.Spinner
|
||||||
preview_manager *PreviewManager
|
preview_manager *PreviewManager
|
||||||
last_rendered_preview Preview
|
last_rendered_preview Preview
|
||||||
last_rendered_preview_abspath string
|
last_rendered_preview_abspath string
|
||||||
graphics_handler GraphicsHandler
|
prev_preview_for_smooth_transition Preview
|
||||||
|
graphics_handler GraphicsHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Handler) on_escape_code(etype loop.EscapeCodeType, payload []byte) error {
|
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) {
|
lp.OnResize = func(old, new_size loop.ScreenSize) (err error) {
|
||||||
handler.init_sizes(new_size)
|
handler.init_sizes(new_size)
|
||||||
|
handler.clear_cached_previews()
|
||||||
return handler.draw_screen()
|
return handler.draw_screen()
|
||||||
}
|
}
|
||||||
lp.OnColorSchemeChange = func(p loop.ColorPreference) (err error) {
|
lp.OnColorSchemeChange = func(p loop.ColorPreference) (err error) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/kovidgoyal/go-parallel"
|
"github.com/kovidgoyal/go-parallel"
|
||||||
@@ -27,6 +28,7 @@ type Preview interface {
|
|||||||
Render(h *Handler, x, y, width, height int)
|
Render(h *Handler, x, y, width, height int)
|
||||||
IsValidForColorScheme(light bool) bool
|
IsValidForColorScheme(light bool) bool
|
||||||
Unload()
|
Unload()
|
||||||
|
IsReady() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type PreviewManager struct {
|
type PreviewManager struct {
|
||||||
@@ -81,6 +83,7 @@ type MessagePreview struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p MessagePreview) IsValidForColorScheme(bool) bool { return true }
|
func (p MessagePreview) IsValidForColorScheme(bool) bool { return true }
|
||||||
|
func (p MessagePreview) IsReady() bool { return true }
|
||||||
|
|
||||||
func (p MessagePreview) Unload() {}
|
func (p MessagePreview) Unload() {}
|
||||||
func (p MessagePreview) Render(h *Handler, x, y, width, height int) {
|
func (p MessagePreview) Render(h *Handler, x, y, width, height int) {
|
||||||
@@ -186,14 +189,16 @@ type highlighed_data struct {
|
|||||||
type TextFilePreview struct {
|
type TextFilePreview struct {
|
||||||
plain_text, highlighted_text string
|
plain_text, highlighted_text string
|
||||||
highlighted_chan chan highlighed_data
|
highlighted_chan chan highlighed_data
|
||||||
|
ready atomic.Bool
|
||||||
light bool
|
light bool
|
||||||
path string
|
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) 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) {
|
func (p *TextFilePreview) Render(h *Handler, x, y, width, height int) {
|
||||||
if p.highlighted_chan != nil {
|
if p.highlighted_chan != nil {
|
||||||
select {
|
select {
|
||||||
@@ -256,22 +261,20 @@ func (s style_resolver) TextForPath(path string) (string, error) {
|
|||||||
return "", err
|
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 := style_resolver{light: use_light_colors, syntax_aliases: pm.settings.SyntaxAliases()}
|
||||||
s.light_style, s.dark_style = pm.settings.HighlightStyles()
|
s.light_style, s.dark_style = pm.settings.HighlightStyles()
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
err := parallel.Format_stacktrace_on_panic(r, 1)
|
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)
|
close(output)
|
||||||
|
ready.Store(true)
|
||||||
pm.WakeupMainThread()
|
pm.WakeupMainThread()
|
||||||
}()
|
}()
|
||||||
highlighted, err := pm.highlighter.HighlightFile(path, &s)
|
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}
|
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
|
const MAX_TEXT_FILE_SIZE = 16 * 1024 * 1024
|
||||||
if s.Size() <= MAX_TEXT_FILE_SIZE && (utils.KnownTextualMimes[mt] || strings.HasPrefix(mt, "text/")) {
|
if s.Size() <= MAX_TEXT_FILE_SIZE && (utils.KnownTextualMimes[mt] || strings.HasPrefix(mt, "text/")) {
|
||||||
ch := make(chan highlighed_data, 2)
|
ch := make(chan highlighed_data, 2)
|
||||||
pm.highlight_file_async(abspath, ch)
|
ans := NewTextFilePreview(abspath, s, ch, pm.highlighter.Sanitize)
|
||||||
return 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/") {
|
if strings.HasPrefix(mt, "image/") {
|
||||||
var r ImagePreviewRenderer
|
var r ImagePreviewRenderer
|
||||||
@@ -319,6 +325,17 @@ func (pm *PreviewManager) preview_for(abspath string, ftype fs.FileMode) (ans Pr
|
|||||||
return NewFileMetadataPreview(abspath, s)
|
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) {
|
func (h *Handler) draw_preview_content(x, y, width, height int) {
|
||||||
matches, _ := h.get_results()
|
matches, _ := h.get_results()
|
||||||
r := matches.At(h.state.CurrentIndex())
|
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)
|
h.render_wrapped_text_in_region("No preview available", x, y, width, height, false)
|
||||||
return
|
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()
|
abspath := h.current_abspath()
|
||||||
if h.last_rendered_preview != nil {
|
if h.last_rendered_preview != nil {
|
||||||
if abspath == h.last_rendered_preview_abspath {
|
if abspath == h.last_rendered_preview_abspath {
|
||||||
h.last_rendered_preview.Render(h, x, y, width, height)
|
render()
|
||||||
return
|
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
|
h.last_rendered_preview = nil
|
||||||
}
|
}
|
||||||
if p := h.preview_manager.preview_for(abspath, r.ftype); p == 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 {
|
} else {
|
||||||
h.last_rendered_preview = p
|
h.last_rendered_preview = p
|
||||||
h.last_rendered_preview_abspath = abspath
|
h.last_rendered_preview_abspath = abspath
|
||||||
p.Render(h, x, y, width, height)
|
render()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user