fix: file type handling on Enter - extensions checked before executable bit
- Reorder Category() to check known extensions (text, config, image, pdf, audio, video, archive) before the executable bit check. Fixes video/audio/image files with executable bit being opened in editor instead of system default application. - Remove 'executable' from isEditableEntry() - executables are now launched via handleExecute() instead of opened in Neovim. - Add handleExecute() method that runs executable files in the terminal via tea.ExecProcess. - Update handleOpenSelected() to route: text/config -> editor, executable -> launch, everything else -> system default (xdg-open). - Bump version to v0.2.1
This commit is contained in:
parent
c0df75c57e
commit
df4df6b8f6
3 changed files with 35 additions and 17 deletions
14
README.md
14
README.md
|
|
@ -36,13 +36,13 @@ 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.2.0
|
nix run github:vrubelroman/vcom?ref=v0.2.1
|
||||||
```
|
```
|
||||||
|
|
||||||
Install into user profile:
|
Install into user profile:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nix profile add github:vrubelroman/vcom?ref=v0.2.0
|
nix profile add github:vrubelroman/vcom?ref=v0.2.1
|
||||||
```
|
```
|
||||||
|
|
||||||
The Nix package wraps `vcom` with `ueberzugpp` in `PATH`, so image preview works in non-`kitty` terminals out of the box.
|
The Nix package wraps `vcom` with `ueberzugpp` in `PATH`, so image preview works in non-`kitty` terminals out of the box.
|
||||||
|
|
@ -52,7 +52,7 @@ The Nix package wraps `vcom` with `ueberzugpp` in `PATH`, so image preview works
|
||||||
Download and install the latest release:
|
Download and install the latest release:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -sL https://github.com/vrubelroman/vcom/releases/download/v0.2.0/vcom_0.2.0_amd64.deb -o /tmp/vcom_0.2.0_amd64.deb
|
curl -sL https://github.com/vrubelroman/vcom/releases/download/v0.2.1/vcom_0.2.1_amd64.deb -o /tmp/vcom_0.2.1_amd64.deb
|
||||||
sudo apt install /tmp/vcom_0.2.0_amd64.deb
|
sudo apt install /tmp/vcom_0.2.0_amd64.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -183,7 +183,7 @@ Built-in themes (use `T` to cycle or set `ui.theme` in config):
|
||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
|
|
||||||
Pushing a tag like `v0.2.0` triggers GitHub Actions release workflow (`.github/workflows/release.yml`) which:
|
Pushing a tag like `v0.2.1` triggers GitHub Actions release workflow (`.github/workflows/release.yml`) which:
|
||||||
|
|
||||||
- runs tests
|
- runs tests
|
||||||
- vendors Go modules
|
- vendors Go modules
|
||||||
|
|
@ -193,9 +193,9 @@ Pushing a tag like `v0.2.0` triggers GitHub Actions release workflow (`.github/w
|
||||||
|
|
||||||
Release artifacts:
|
Release artifacts:
|
||||||
|
|
||||||
- `vcom-v0.2.0-x86_64-unknown-linux-gnu.tar.gz`
|
- `vcom-v0.2.1-x86_64-unknown-linux-gnu.tar.gz`
|
||||||
- `vcom_0.2.0_amd64.deb`
|
- `vcom_0.2.1_amd64.deb`
|
||||||
- `vcom-v0.2.0-checksums.txt`
|
- `vcom-v0.2.1-checksums.txt`
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,14 +116,14 @@ func (e Entry) Category() string {
|
||||||
return "parent"
|
return "parent"
|
||||||
case e.IsDir:
|
case e.IsDir:
|
||||||
return "directory"
|
return "directory"
|
||||||
case e.IsExecutable():
|
|
||||||
return "executable"
|
|
||||||
case hasExt(configExtensions, e.Extension):
|
case hasExt(configExtensions, e.Extension):
|
||||||
return "config"
|
return "config"
|
||||||
case hasExt(imageExtensions, e.Extension):
|
|
||||||
return "image"
|
|
||||||
case hasExt(textExtensions, e.Extension):
|
case hasExt(textExtensions, e.Extension):
|
||||||
return "text"
|
return "text"
|
||||||
|
case hasExt(textFilenames, strings.ToLower(e.Name)):
|
||||||
|
return "text"
|
||||||
|
case hasExt(imageExtensions, e.Extension):
|
||||||
|
return "image"
|
||||||
case hasExt(pdfExtensions, e.Extension):
|
case hasExt(pdfExtensions, e.Extension):
|
||||||
return "pdf"
|
return "pdf"
|
||||||
case hasExt(audioExtensions, e.Extension):
|
case hasExt(audioExtensions, e.Extension):
|
||||||
|
|
@ -132,8 +132,8 @@ func (e Entry) Category() string {
|
||||||
return "video"
|
return "video"
|
||||||
case hasExt(archiveExtensions, e.Extension):
|
case hasExt(archiveExtensions, e.Extension):
|
||||||
return "archive"
|
return "archive"
|
||||||
case hasExt(textFilenames, strings.ToLower(e.Name)):
|
case e.IsExecutable():
|
||||||
return "text"
|
return "executable"
|
||||||
default:
|
default:
|
||||||
return "binary"
|
return "binary"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"vcom/internal/theme"
|
"vcom/internal/theme"
|
||||||
)
|
)
|
||||||
|
|
||||||
const version = "v0.2.0"
|
const version = "v0.2.1"
|
||||||
|
|
||||||
type modalKind int
|
type modalKind int
|
||||||
|
|
||||||
|
|
@ -51,6 +51,7 @@ const (
|
||||||
opEdit
|
opEdit
|
||||||
opView
|
opView
|
||||||
opArchive
|
opArchive
|
||||||
|
opExecute
|
||||||
)
|
)
|
||||||
|
|
||||||
type pendingOperation struct {
|
type pendingOperation struct {
|
||||||
|
|
@ -353,6 +354,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
case opView:
|
case opView:
|
||||||
m.status = "Viewer closed"
|
m.status = "Viewer closed"
|
||||||
return m, enableMouseCmd()
|
return m, enableMouseCmd()
|
||||||
|
case opExecute:
|
||||||
|
m.status = "Executable closed"
|
||||||
|
return m, tea.Batch(m.loadPreviewCmd(), enableMouseCmd())
|
||||||
}
|
}
|
||||||
|
|
||||||
leftSelection := selectedName(&m.left)
|
leftSelection := selectedName(&m.left)
|
||||||
|
|
@ -1440,10 +1444,14 @@ func (m *Model) handleOpenSelected() (tea.Model, tea.Cmd) {
|
||||||
return m, m.loadPreviewCmd()
|
return m, m.loadPreviewCmd()
|
||||||
}
|
}
|
||||||
|
|
||||||
if isEditableEntry(selected) {
|
switch selected.Category() {
|
||||||
|
case "text", "config":
|
||||||
return m.handleEdit()
|
return m.handleEdit()
|
||||||
}
|
case "executable":
|
||||||
|
return m.handleExecute(selected)
|
||||||
|
default:
|
||||||
return m.handleOpenExternal()
|
return m.handleOpenExternal()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) goParent() error {
|
func (m *Model) goParent() error {
|
||||||
|
|
@ -1769,6 +1777,16 @@ func (m *Model) handleEdit() (tea.Model, tea.Cmd) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Model) handleExecute(entry vfs.Entry) (tea.Model, tea.Cmd) {
|
||||||
|
m.cleanupImageOverlay()
|
||||||
|
cmd := exec.Command(entry.Path)
|
||||||
|
cmd.Dir = filepath.Dir(entry.Path)
|
||||||
|
m.status = fmt.Sprintf("Executing %s", entry.DisplayName())
|
||||||
|
return m, tea.ExecProcess(cmd, func(err error) tea.Msg {
|
||||||
|
return opMsg{kind: opExecute, sourcePath: entry.Path, err: err}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Model) handleMouse(msg tea.MouseMsg) (tea.Model, tea.Cmd) {
|
func (m *Model) handleMouse(msg tea.MouseMsg) (tea.Model, tea.Cmd) {
|
||||||
if m.viewMode {
|
if m.viewMode {
|
||||||
switch {
|
switch {
|
||||||
|
|
@ -4242,7 +4260,7 @@ func paneIndexFromMouse(localY int, height int, pane *BrowserPane) (int, bool) {
|
||||||
|
|
||||||
func isEditableEntry(entry vfs.Entry) bool {
|
func isEditableEntry(entry vfs.Entry) bool {
|
||||||
switch entry.Category() {
|
switch entry.Category() {
|
||||||
case "text", "config", "executable":
|
case "text", "config":
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue