Add terminal image preview via chafa and release v0.1.3 updates
This commit is contained in:
parent
6a518896b8
commit
6b23717572
4 changed files with 82 additions and 18 deletions
25
README.md
25
README.md
|
|
@ -23,6 +23,13 @@ You can force behavior in config:
|
||||||
- `ui.icon_mode = "nerd"`: always use Nerd icons
|
- `ui.icon_mode = "nerd"`: always use Nerd icons
|
||||||
- `ui.icon_mode = "ascii"`: always use ASCII icons
|
- `ui.icon_mode = "ascii"`: always use ASCII icons
|
||||||
|
|
||||||
|
How to make terminal use the installed Nerd Font:
|
||||||
|
|
||||||
|
- GNOME Terminal: `Preferences -> Profile -> Text -> Custom font` -> choose `JetBrainsMono Nerd Font`
|
||||||
|
- Konsole: `Settings -> Edit Current Profile -> Appearance` -> choose a Nerd Font profile
|
||||||
|
- Alacritty: set `font.normal.family: "JetBrainsMono Nerd Font"` in `~/.config/alacritty/alacritty.yml`
|
||||||
|
- Kitty: set `font_family JetBrainsMono Nerd Font` in `~/.config/kitty/kitty.conf`
|
||||||
|
|
||||||
Preview mode (`F9` / `i`) temporarily replaces the inactive pane and shows:
|
Preview mode (`F9` / `i`) temporarily replaces the inactive pane and shows:
|
||||||
|
|
||||||
- directory listing preview
|
- directory listing preview
|
||||||
|
|
@ -57,21 +64,21 @@ go build -o vcom ./cmd/vcom
|
||||||
Run directly from the flake:
|
Run directly from the flake:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nix run github:vrubelroman/vcom?ref=v0.1.2
|
nix run github:vrubelroman/vcom?ref=v0.1.3
|
||||||
```
|
```
|
||||||
|
|
||||||
Install into user profile:
|
Install into user profile:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nix profile add github:vrubelroman/vcom?ref=v0.1.2
|
nix profile add github:vrubelroman/vcom?ref=v0.1.3
|
||||||
```
|
```
|
||||||
|
|
||||||
### Debian / Ubuntu
|
### Debian / Ubuntu
|
||||||
|
|
||||||
Download the release `.deb` for `v0.1.2`, then install:
|
Download the release `.deb` for `v0.1.3`, then install:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt install ./vcom_0.1.2_amd64.deb
|
sudo apt install ./vcom_0.1.3_amd64.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
Install a Nerd Font (example):
|
Install a Nerd Font (example):
|
||||||
|
|
@ -123,7 +130,7 @@ Built-in themes:
|
||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
|
|
||||||
Pushing a tag like `v0.1.2` triggers GitHub Actions release workflow (`.github/workflows/release.yml`) which:
|
Pushing a tag like `v0.1.3` triggers GitHub Actions release workflow (`.github/workflows/release.yml`) which:
|
||||||
|
|
||||||
- runs tests
|
- runs tests
|
||||||
- vendors Go modules
|
- vendors Go modules
|
||||||
|
|
@ -133,13 +140,13 @@ Pushing a tag like `v0.1.2` triggers GitHub Actions release workflow (`.github/w
|
||||||
|
|
||||||
Release artifacts:
|
Release artifacts:
|
||||||
|
|
||||||
- `vcom-v0.1.2-x86_64-unknown-linux-gnu.tar.gz`
|
- `vcom-v0.1.3-x86_64-unknown-linux-gnu.tar.gz`
|
||||||
- `vcom_0.1.2_amd64.deb`
|
- `vcom_0.1.3_amd64.deb`
|
||||||
- `vcom-v0.1.2-checksums.txt`
|
- `vcom-v0.1.3-checksums.txt`
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- File creation time depends on filesystem/OS support; unavailable values are shown as `n/a`.
|
- File creation time depends on filesystem/OS support; unavailable values are shown as `n/a`.
|
||||||
- Inline image rendering is intentionally disabled for now due to terminal compatibility differences.
|
- 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)
|
Architecture notes: [docs/architecture.md](/home/vrubel/projects/vcom/docs/architecture.md)
|
||||||
|
|
|
||||||
13
flake.nix
13
flake.nix
|
|
@ -11,9 +11,9 @@
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs { inherit system; };
|
pkgs = import nixpkgs { inherit system; };
|
||||||
lib = pkgs.lib;
|
lib = pkgs.lib;
|
||||||
package = pkgs.buildGoModule {
|
packageBase = pkgs.buildGoModule {
|
||||||
pname = "vcom";
|
pname = "vcom";
|
||||||
version = "0.1.0";
|
version = "0.1.3";
|
||||||
src = ./.;
|
src = ./.;
|
||||||
vendorHash = null;
|
vendorHash = null;
|
||||||
|
|
||||||
|
|
@ -32,6 +32,15 @@
|
||||||
platforms = platforms.linux;
|
platforms = platforms.linux;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
package = pkgs.symlinkJoin {
|
||||||
|
name = "vcom";
|
||||||
|
paths = [ packageBase ];
|
||||||
|
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||||
|
postBuild = ''
|
||||||
|
wrapProgram "$out/bin/vcom" \
|
||||||
|
--prefix PATH : "${lib.makeBinPath [ pkgs.chafa ]}"
|
||||||
|
'';
|
||||||
|
};
|
||||||
in {
|
in {
|
||||||
packages.default = package;
|
packages.default = package;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -65,6 +66,8 @@ type PreviewOptions struct {
|
||||||
HumanReadableSize bool
|
HumanReadableSize bool
|
||||||
ThemeName string
|
ThemeName string
|
||||||
UseNerdIcons bool
|
UseNerdIcons bool
|
||||||
|
ImagePreviewWidth int
|
||||||
|
ImagePreviewHeight int
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildPreview(entry Entry, options PreviewOptions) Preview {
|
func BuildPreview(entry Entry, options PreviewOptions) Preview {
|
||||||
|
|
@ -119,12 +122,12 @@ func BuildPreview(entry Entry, options PreviewOptions) Preview {
|
||||||
preview.Kind = PreviewKindImage
|
preview.Kind = PreviewKindImage
|
||||||
preview.Metadata.ImageFormat = format
|
preview.Metadata.ImageFormat = format
|
||||||
preview.Metadata.ImageSize = dimensions
|
preview.Metadata.ImageSize = dimensions
|
||||||
preview.Body = fmt.Sprintf(
|
inline := renderImageInlinePreview(entry.Path, options.ImagePreviewWidth, options.ImagePreviewHeight)
|
||||||
"Image preview is metadata-only for now.\n\nFormat: %s\nDimensions: %s\nPath: %s",
|
if inline == "" {
|
||||||
format,
|
preview.Body = "Image preview unavailable.\n\nInstall `chafa` for inline preview in info pane."
|
||||||
dimensions,
|
} else {
|
||||||
entry.Path,
|
preview.Body = inline
|
||||||
)
|
}
|
||||||
preview.PlainBody = preview.Body
|
preview.PlainBody = preview.Body
|
||||||
return preview
|
return preview
|
||||||
}
|
}
|
||||||
|
|
@ -377,6 +380,39 @@ 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) {
|
func detectImage(data []byte) (string, string, bool) {
|
||||||
cfg, format, err := image.DecodeConfig(bytes.NewReader(data))
|
cfg, format, err := image.DecodeConfig(bytes.NewReader(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -869,6 +869,12 @@ func (m Model) loadPreviewCmd() tea.Cmd {
|
||||||
HumanReadableSize: m.cfg.Browser.HumanReadableSize,
|
HumanReadableSize: m.cfg.Browser.HumanReadableSize,
|
||||||
ThemeName: m.cfg.UI.Theme,
|
ThemeName: m.cfg.UI.Theme,
|
||||||
UseNerdIcons: m.nerdIcons,
|
UseNerdIcons: m.nerdIcons,
|
||||||
|
ImagePreviewWidth: max(m.previewModel.Width-2, 20),
|
||||||
|
ImagePreviewHeight: max(m.previewModel.Height-6, 8),
|
||||||
|
}
|
||||||
|
if m.viewMode {
|
||||||
|
options.ImagePreviewWidth = max(m.width-8, 20)
|
||||||
|
options.ImagePreviewHeight = max(m.bodyHeight()-8, 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
|
|
@ -970,6 +976,12 @@ func (m *Model) handleView() (tea.Model, tea.Cmd) {
|
||||||
m.status = "Select a file to view"
|
m.status = "Select a file to view"
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
if selected.Category() == "image" {
|
||||||
|
if _, err := exec.LookPath("chafa"); err != nil {
|
||||||
|
m.status = "Install `chafa` to view images in terminal"
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
if m.viewMode {
|
if m.viewMode {
|
||||||
return m.exitViewMode()
|
return m.exitViewMode()
|
||||||
}
|
}
|
||||||
|
|
@ -1287,7 +1299,7 @@ func (m *Model) openHelpModal() {
|
||||||
"",
|
"",
|
||||||
"View and Panels",
|
"View and Panels",
|
||||||
" F9 / i toggle preview/info pane",
|
" F9 / i toggle preview/info pane",
|
||||||
" F3 / v open read-only view mode",
|
" F3 / v text view mode or fullscreen image viewer",
|
||||||
" F3 / Esc / q close view mode",
|
" F3 / Esc / q close view mode",
|
||||||
" Ctrl+t toggle text selection mode in text preview",
|
" Ctrl+t toggle text selection mode in text preview",
|
||||||
" Space calculate selected directory size",
|
" Space calculate selected directory size",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue