Refine image preview integration
This commit is contained in:
parent
9dcef02e0d
commit
912de45e19
5 changed files with 43 additions and 48 deletions
|
|
@ -147,6 +147,5 @@ Release artifacts:
|
|||
## Notes
|
||||
|
||||
- File creation time depends on filesystem/OS support; unavailable values are shown as `n/a`.
|
||||
- Image preview in info pane (`F9`) and image full-screen view (`F3`) use `chafa`.
|
||||
|
||||
Architecture notes: [docs/architecture.md](/home/vrubel/projects/vcom/docs/architecture.md)
|
||||
|
|
|
|||
|
|
@ -35,11 +35,6 @@
|
|||
package = pkgs.symlinkJoin {
|
||||
name = "vcom";
|
||||
paths = [ packageBase ];
|
||||
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||
postBuild = ''
|
||||
wrapProgram "$out/bin/vcom" \
|
||||
--prefix PATH : "${lib.makeBinPath [ pkgs.chafa ]}"
|
||||
'';
|
||||
};
|
||||
in {
|
||||
packages.default = package;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
_ "image/png"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
|
@ -123,9 +122,7 @@ func BuildPreview(entry Entry, options PreviewOptions) Preview {
|
|||
preview.Metadata.ImageFormat = format
|
||||
preview.Metadata.ImageSize = dimensions
|
||||
inline := renderImageInlinePreview(entry.Path, options.ImagePreviewWidth, options.ImagePreviewHeight)
|
||||
if inline == "" {
|
||||
preview.Body = "Image preview unavailable.\n\nInstall `chafa` for inline preview in info pane."
|
||||
} else {
|
||||
if inline != "" {
|
||||
preview.Body = inline
|
||||
}
|
||||
preview.PlainBody = preview.Body
|
||||
|
|
@ -381,38 +378,9 @@ func previewIcon(entry Entry, useNerdIcons bool) string {
|
|||
}
|
||||
|
||||
func renderImageInlinePreview(path string, width int, height int) string {
|
||||
if width < 20 {
|
||||
width = 20
|
||||
}
|
||||
if height < 8 {
|
||||
height = 8
|
||||
}
|
||||
|
||||
if _, err := exec.LookPath("chafa"); err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
cmd := exec.Command(
|
||||
"chafa",
|
||||
"--format=symbols",
|
||||
"--symbols=vhalf",
|
||||
"--animate=off",
|
||||
"--fg-only",
|
||||
"--size", fmt.Sprintf("%dx%d", width, height),
|
||||
path,
|
||||
)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
view := strings.TrimSpace(string(out))
|
||||
if view == "" {
|
||||
return ""
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
func detectImage(data []byte) (string, string, bool) {
|
||||
cfg, format, err := image.DecodeConfig(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ func (m *imageOverlayManager) backendOutput() string {
|
|||
case os.Getenv("DISPLAY") != "":
|
||||
order = append(order, "x11")
|
||||
}
|
||||
order = append(order, "wayland", "x11", "sixel", "kitty", "chafa")
|
||||
order = append(order, "wayland", "x11", "sixel", "kitty")
|
||||
|
||||
unique := make([]string, 0, len(order))
|
||||
for _, backend := range order {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package ui
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
|
@ -111,6 +112,10 @@ type copyDoneMsg struct {
|
|||
}
|
||||
|
||||
type dismissNoticeMsg struct{}
|
||||
type externalOpenMsg struct {
|
||||
path string
|
||||
err error
|
||||
}
|
||||
|
||||
type copyJobState struct {
|
||||
id int
|
||||
|
|
@ -412,6 +417,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
}
|
||||
return m, nil
|
||||
|
||||
case externalOpenMsg:
|
||||
if msg.err != nil {
|
||||
m.status = fmt.Sprintf("Open failed: %v", msg.err)
|
||||
return m, nil
|
||||
}
|
||||
m.status = fmt.Sprintf("Opened %s", filepath.Base(msg.path))
|
||||
return m, nil
|
||||
|
||||
case tea.KeyMsg:
|
||||
if m.modal.kind != modalNone {
|
||||
return m.handleModalKey(msg)
|
||||
|
|
@ -1026,9 +1039,7 @@ func (m *Model) handleOpenExternal() (tea.Model, tea.Cmd) {
|
|||
|
||||
m.cleanupImageOverlay()
|
||||
m.status = fmt.Sprintf("Opening %s with %s", selected.DisplayName(), name)
|
||||
return m, tea.ExecProcess(command, func(err error) tea.Msg {
|
||||
return opMsg{kind: opView, sourcePath: selected.Path, err: err}
|
||||
})
|
||||
return m, startExternalOpenCmd(command, selected.Path)
|
||||
}
|
||||
|
||||
func (m *Model) handleEdit() (tea.Model, tea.Cmd) {
|
||||
|
|
@ -2598,6 +2609,18 @@ func externalCommandFromEnv(envVars []string, fallbacks []string, path string) (
|
|||
return exec.Command(parts[0], args...), filepath.Base(parts[0]), nil
|
||||
}
|
||||
|
||||
func startExternalOpenCmd(command *exec.Cmd, path string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
command.Stdin = nil
|
||||
command.Stdout = io.Discard
|
||||
command.Stderr = io.Discard
|
||||
if err := command.Start(); err != nil {
|
||||
return externalOpenMsg{path: path, err: err}
|
||||
}
|
||||
return externalOpenMsg{path: path}
|
||||
}
|
||||
}
|
||||
|
||||
func enableMouseCmd() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return tea.EnableMouseCellMotion()
|
||||
|
|
@ -2753,11 +2776,21 @@ func (m Model) syncImageOverlay(leftWidth int, previewWidth int, bodyHeight int)
|
|||
if m.active == PaneLeft {
|
||||
startX = leftWidth + m.cfg.UI.PaneGap
|
||||
}
|
||||
innerWidth := max(previewWidth-2, 1)
|
||||
metaHeight := 0
|
||||
if m.cfg.Preview.ShowMetadata {
|
||||
metaHeight = lipgloss.Height(renderMetadata(m.previewData.Metadata, m.palette, innerWidth))
|
||||
}
|
||||
titleHeight := 1
|
||||
topInset := 1
|
||||
contentBorder := 1
|
||||
safetyGap := 1
|
||||
contentTop := topInset + titleHeight + metaHeight + contentBorder + safetyGap
|
||||
rect = overlayRect{
|
||||
x: startX + 2,
|
||||
y: 9,
|
||||
width: max(previewWidth-4, 1),
|
||||
height: max(bodyHeight-11, 1),
|
||||
x: startX + 3,
|
||||
y: contentTop,
|
||||
width: max(previewWidth-6, 1),
|
||||
height: max(bodyHeight-contentTop-2, 1),
|
||||
}
|
||||
} else {
|
||||
m.overlay.hide()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue