diff --git a/LICENSE b/LICENSE
index 708a2d1..f2ce71b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,7 @@
MIT License
Copyright (c) 2021 Anton Medvedev
+Modified (c) 2023 Andrew Shultzabarger
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 0591c37..6bed722 100644
--- a/README.md
+++ b/README.md
@@ -1,130 +1,16 @@
-# 🥾 walk
+# walk
-
-
-
-
-
+Walk is based on [antonmedv/walk](https://github.com/antonmedv/walk) with modifications to enable reuse as a component in other [BubbleTea](https://github.com/charmbracelet/bubbletea) applications, along with some other bugfixes and enhancements.
-**Walk** — a terminal navigator.
-
-Why another terminal navigator? I wanted something simple and minimalistic.
-Something to help me with faster navigation in the filesystem; a `cd` and `ls`
-replacement. So I build **walk**. It allows for quick navigation with fuzzy
-searching, `cd` integration is quite simple. And you can open `vim` right from
-the walk. That's it.
-
-## Install
+To use the `walk` component, install the module using:
```
-brew install walk
+go get github.com/ardnew/walk/v2
```
-```
-pkg_add walk
-```
+The original `walk` command has been refactored into the [cmd/lk] submodule ([README.md](cmd/lk/README.md)) and can be installed using:
```
-pacman -S walk
-```
-
+go get github.com/ardnew/walk/v2/cmd/lk
```
-go install github.com/antonmedv/walk@latest
-```
-
-Or download [prebuild binaries](https://github.com/antonmedv/walk/releases).
-
-Put the next function into the **.bashrc** or a similar config:
-
-
-
- Bash/Zsh |
- Fish |
- PowerShell |
-
-
-
-
-```bash
-function lk {
- cd "$(walk "$@")"
-}
-```
-
- |
-
-
-```fish
-function lk
- set loc (walk $argv); and cd $loc;
-end
-```
-
- |
-
-
-```powershell
-function lk() {
- cd $(walk $args)
-}
-```
-
- |
-
-
-
-
-Now use `lk` command to start walking.
-
-## Usage
-
-| Key binding | Description |
-|------------------|--------------------|
-| `Arrows`, `hjkl` | Move cursor |
-| `Enter` | Enter directory |
-| `Backspace` | Exit directory |
-| `Space` | Toggle preview |
-| `Esc`, `q` | Exit with cd |
-| `Ctrl+c` | Exit without cd |
-| `/` | Fuzzy search |
-| `dd` | Delete file or dir |
-| `y` | yank current dir |
-
-The `EDITOR` or `WALK_EDITOR` environment variable used for opening files from
-the walk.
-
-```bash
-export EDITOR=vim
-```
-
-### Preview mode
-
-Press `Space` to toggle preview mode.
-
-
-
-### Delete file or directory
-
-Press `dd` to delete file or directory. Press `u` to undo.
-
-
-
-### Display icons
-
-Install [Nerd Fonts](https://www.nerdfonts.com) and add `--icons` flag.
-
-
-
-### Image preview
-
-No additional setup is required.
-
-
-
-## Become a sponsor
-
-Every line of code in my repositories 📖 signifies my unwavering commitment to open source 💡. Your support 🤝 ensures these projects keep thriving, innovating, and benefiting all 💼. If my work has ever resonated 🎵 or helped you, kindly consider showing love ❤️ by sponsoring. [**🚀 Sponsor Me Today! 🚀**](https://github.com/sponsors/antonmedv)
-
-## License
-[MIT](LICENSE)
diff --git a/cmd/lk/LICENSE b/cmd/lk/LICENSE
new file mode 100644
index 0000000..f2ce71b
--- /dev/null
+++ b/cmd/lk/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2021 Anton Medvedev
+Modified (c) 2023 Andrew Shultzabarger
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/cmd/lk/README.md b/cmd/lk/README.md
new file mode 100644
index 0000000..41fce55
--- /dev/null
+++ b/cmd/lk/README.md
@@ -0,0 +1,115 @@
+# 🥾 lk
+
+
+
+
+
+
+
+**lk** — a terminal navigator.
+
+Taken from [antonmedv/walk](https://github.com/antonmedv/walk):
+
+> Why another terminal navigator? I wanted something simple and minimalistic. Something to help me with faster navigation in the filesystem; a `cd` and `ls`replacement. So I build \[`lk`\]. It allows for quick navigation with fuzzy searching, `cd` integration is quite simple. And you can open `vim` right from \[`lk`\]. That's it.
+
+## Install
+
+```
+go install github.com/ardnew/walk/v2/cmd/lk@latest
+```
+
+Or download [prebuild binaries](https://github.com/ardnew/walk/releases).
+
+Put the next function into the `.bashrc` or a similar config:
+
+
+
+ Bash/Zsh |
+ Fish |
+ PowerShell |
+
+
+
+
+```bash
+function clk {
+ cd "$(lk "$@")"
+}
+```
+
+ |
+
+
+```fish
+function clk
+ set loc (lk $argv); and cd $loc;
+end
+```
+
+ |
+
+
+```powershell
+function clk() {
+ cd $(lk $args)
+}
+```
+
+ |
+
+
+
+
+Now use `clk` command to start walking.
+
+## Usage
+
+| Key binding | Description |
+|------------------|--------------------|
+| `Arrows`, `hjkl` | Move cursor |
+| `Enter` | Enter directory |
+| `Backspace` | Exit directory |
+| `Space` | Toggle preview |
+| `Esc`, `q` | Exit with cd |
+| `Ctrl+c` | Exit without cd |
+| `/` | Fuzzy search |
+| `dd` | Delete file or dir |
+| `y` | yank current dir |
+
+The `EDITOR` or `LK_EDITOR` environment variable used for opening files from lk.
+
+```bash
+export EDITOR=vim
+```
+
+### Preview mode
+
+Press `Space` to toggle preview mode.
+
+
+
+### Delete file or directory
+
+Press `dd` to delete file or directory. Press `u` to undo.
+
+
+
+### Display icons
+
+Install [Nerd Fonts](https://www.nerdfonts.com) and add `--icons` flag.
+
+
+
+### Image preview
+
+No additional setup is required.
+
+
+
+## Become a sponsor
+
+Every line of code in my repositories 📖 signifies my unwavering commitment to open source 💡. Your support 🤝 ensures these projects keep thriving, innovating, and benefiting all 💼. If my work has ever resonated 🎵 or helped you, kindly consider showing love ❤️ by sponsoring. [**🚀 Sponsor Me Today! 🚀**](https://github.com/sponsors/antonmedv)
+
+## License
+
+[MIT](LICENSE)
diff --git a/cmd/lk/go.mod b/cmd/lk/go.mod
index c6293cd..93fcf6c 100644
--- a/cmd/lk/go.mod
+++ b/cmd/lk/go.mod
@@ -3,7 +3,7 @@ module github.com/ardnew/walk/cmd/lk
go 1.21.4
require (
- github.com/ardnew/walk/v2 v2.0.0
+ github.com/ardnew/walk/v2 v2.1.0
github.com/charmbracelet/bubbletea v0.25.0
github.com/charmbracelet/lipgloss v0.9.1
github.com/muesli/termenv v0.15.2
diff --git a/cmd/lk/main.go b/cmd/lk/main.go
index 38c63e7..0332b8e 100644
--- a/cmd/lk/main.go
+++ b/cmd/lk/main.go
@@ -1,8 +1,11 @@
package main
import (
+ "fmt"
"os"
"path/filepath"
+ "strings"
+ "text/tabwriter"
"github.com/ardnew/walk/v2"
tea "github.com/charmbracelet/bubbletea"
@@ -10,8 +13,48 @@ import (
"github.com/muesli/termenv"
)
+func usage(s *walk.Styles) {
+ exe, err := os.Executable()
+ if err != nil {
+ exe = os.Args[0]
+ }
+ exe = filepath.Base(exe)
+ _, _ = fmt.Fprintf(os.Stderr, "\n "+s.Cursor.Render(" " + exe + " ")+"\n\n Usage: " + exe + " [flags] [path]\n\n")
+ w := tabwriter.NewWriter(os.Stderr, 0, 8, 2, ' ', 0)
+ put := func(s string) {
+ _, _ = fmt.Fprintln(w, s)
+ }
+ put(" Arrows, hjkl\tMove cursor")
+ put(" Enter\tEnter directory")
+ put(" Backspace\tExit directory")
+ put(" Space\tToggle preview")
+ put(" Esc, q\tExit with cd")
+ put(" Ctrl+c\tExit without cd")
+ put(" /\tFuzzy search")
+ put(" dd\tDelete file or dir")
+ put(" y\tYank current directory path to clipboard")
+ put("\n Flags:\n")
+ put(" --help\t-h\tdisplay help")
+ put(" --version\t-v\tdisplay version")
+ put(" --icons\t-i\tdisplay icons")
+ put(" --command\t-c\t\"open\" file command line")
+ put(" (path replaces first {}, else appended)")
+ _ = w.Flush()
+ _, _ = fmt.Fprintf(os.Stderr, "\n")
+ os.Exit(1)
+}
+
+func version(s *walk.Styles) {
+ fmt.Printf("\n %s %s\n\n", s.Cursor.Render(" walk "), walk.Version())
+ os.Exit(0)
+}
+
func main() {
- options := []walk.Option{}
+ style := walk.DefaultStyle()
+ options := []walk.Option{
+ walk.Style(style),
+ walk.Size(80, 60),
+ }
startPath, err := os.Getwd()
if err != nil {
@@ -19,30 +62,40 @@ func main() {
}
for i := 1; i < len(os.Args); i++ {
- if os.Args[i] == "--help" || os.Args[1] == "-h" {
- walk.Usage()
+ if os.Args[i] == "--help" || os.Args[i] == "-h" {
+ usage(style)
}
- if os.Args[i] == "--version" || os.Args[1] == "-v" {
- walk.Version()
+ if os.Args[i] == "--version" || os.Args[i] == "-v" {
+ version(style)
}
- if os.Args[i] == "--icons" {
+ if os.Args[i] == "--icons" || os.Args[i] == "-i" {
options = append(options, walk.Icons())
continue
}
- startPath, err = filepath.Abs(os.Args[1])
+ const cmdflag = "--command"
+ if strings.HasPrefix(os.Args[i], cmdflag + "=") {
+ options = append(options, walk.Command(
+ strings.TrimPrefix(os.Args[i], cmdflag + "="),
+ ))
+ continue
+ } else if os.Args[i] == cmdflag || os.Args[i] == "-c" {
+ i++
+ if i < len(os.Args) {
+ options = append(options, walk.Command(os.Args[i]))
+ }
+ continue
+ }
+
+ startPath, err = filepath.Abs(os.Args[i])
if err != nil {
panic(err)
}
+ options = append(options, walk.Path(startPath))
}
- options = append(options,
- walk.Path(startPath),
- walk.Size(80, 60),
- )
-
output := termenv.NewOutput(os.Stderr)
lipgloss.SetColorProfile(output.ColorProfile())
diff --git a/walk.go b/walk.go
index bc656a8..786e09a 100644
--- a/walk.go
+++ b/walk.go
@@ -10,7 +10,6 @@ import (
"path/filepath"
"runtime"
. "strings"
- "text/tabwriter"
"time"
"unicode/utf8"
@@ -21,26 +20,39 @@ import (
"github.com/sahilm/fuzzy"
)
-var version = "v1.7.0"
+var version = "v2.1.0"
+
+func Version() string { return version }
const separator = " " // Separator between columns.
var (
- warning = lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).PaddingLeft(1).PaddingRight(1)
- preview = lipgloss.NewStyle().PaddingLeft(2)
- cursor = lipgloss.NewStyle().Background(lipgloss.Color("#825DF2")).Foreground(lipgloss.Color("#FFFFFF"))
- bar = lipgloss.NewStyle().Background(lipgloss.Color("#5C5C5C")).Foreground(lipgloss.Color("#FFFFFF"))
- search = lipgloss.NewStyle().Background(lipgloss.Color("#499F1C")).Foreground(lipgloss.Color("#FFFFFF"))
- danger = lipgloss.NewStyle().Background(lipgloss.Color("#FF0000")).Foreground(lipgloss.Color("#FFFFFF"))
fileSeparator = string(filepath.Separator)
showIcons = false
)
+type Styles struct {
+ Warning, Preview, Cursor, Bar, Search, Danger lipgloss.Style
+}
+
+func DefaultStyle() *Styles {
+ return &Styles{
+ Warning: lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).PaddingLeft(1).PaddingRight(1),
+ Preview: lipgloss.NewStyle().PaddingLeft(2),
+ Cursor: lipgloss.NewStyle().Background(lipgloss.Color("#825DF2")).Foreground(lipgloss.Color("#FFFFFF")),
+ Bar: lipgloss.NewStyle().Background(lipgloss.Color("#5C5C5C")).Foreground(lipgloss.Color("#FFFFFF")),
+ Search: lipgloss.NewStyle().Background(lipgloss.Color("#499F1C")).Foreground(lipgloss.Color("#FFFFFF")),
+ Danger: lipgloss.NewStyle().Background(lipgloss.Color("#FF0000")).Foreground(lipgloss.Color("#FFFFFF")),
+ }
+}
+
type Model struct {
path string // Current dir path we are looking at.
files []fs.DirEntry // Files we are looking at.
err error // Error while listing files.
kb *keyMap // Key bindings.
+ st *Styles // Rendering attributes.
+ cmdline []string // Command line to open files.
c, r int // Selector position in columns and rows.
columns, rows int // Displayed amount of rows and columns.
width, height int // Terminal size.
@@ -106,12 +118,34 @@ func (m *Model) WithIcons() *Model {
return m
}
+func Command(cmd string) Option {
+ return func(m *Model) *Model { return m.WithCommand(cmd) }
+}
+
+func (m *Model) WithCommand(cmd string) *Model {
+ m.cmdline = Fields(cmd)
+ return m
+}
+
+func Style(styles *Styles) Option {
+ return func(m *Model) *Model { return m.WithStyle(styles) }
+}
+
+func (m *Model) WithStyle(styles *Styles) *Model {
+ m.st = styles
+ return m
+}
+
func New(options ...Option) *Model {
- m := &Model{
+ m := (&Model{
kb: newKeyMap(),
positions: make(map[string]position),
+ }).With(options...)
+
+ if m.st == nil {
+ m.st = DefaultStyle()
}
- return m.With(options...)
+ return m
}
func (m *Model) With(options ...Option) *Model {
@@ -130,7 +164,7 @@ func (m *Model) Init() tea.Cmd {
m.path, err = os.Getwd()
if err != nil {
_, _ = fmt.Fprintln(os.Stderr,
- danger.Render("error: failed to get working directory"))
+ m.st.Danger.Render("error: failed to get working directory"))
}
}
m.list()
@@ -224,7 +258,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.list()
} else {
// Open file. This will block until complete.
- return m, m.openEditor()
+ return m, m.openCommand()
}
case key.Matches(msg, m.kb.back):
@@ -426,9 +460,9 @@ func (m *Model) View() string {
for i := 0; i < m.columns; i++ {
if i == m.c && j == m.r {
if m.deleteCurrentFile {
- row[i] = danger.Render(names[i][j])
+ row[i] = m.st.Danger.Render(names[i][j])
} else {
- row[i] = cursor.Render(names[i][j])
+ row[i] = m.st.Cursor.Render(names[i][j])
}
} else {
row[i] = names[i][j]
@@ -443,7 +477,7 @@ func (m *Model) View() string {
// Preview pane.
fileName, _ := m.fileName()
- previewPane := bar.Render(fileName) + "\n"
+ previewPane := m.st.Bar.Render(fileName) + "\n"
previewPane += m.previewContent
// Location bar (grey).
@@ -465,14 +499,14 @@ func (m *Model) View() string {
if barLen > outputWidth {
location = location[min(barLen-outputWidth, len(location)):]
}
- barStr := bar.Render(location) + search.Render(filter)
+ barStr := m.st.Bar.Render(location) + m.st.Search.Render(filter)
main := barStr + "\n" + Join(output, "\n")
if m.err != nil {
- main = barStr + "\n" + warning.Render(m.err.Error())
+ main = barStr + "\n" + m.st.Warning.Render(m.err.Error())
} else if len(m.files) == 0 {
- main = barStr + "\n" + warning.Render("No files")
+ main = barStr + "\n" + m.st.Warning.Render("No files")
}
// Delete bar.
@@ -480,20 +514,20 @@ func (m *Model) View() string {
toDelete := m.toBeDeleted[len(m.toBeDeleted)-1]
timeLeft := int(time.Until(toDelete.at).Seconds())
deleteBar := fmt.Sprintf("%v deleted. (u)ndo %v", path.Base(toDelete.path), timeLeft)
- main += "\n" + danger.Render(deleteBar)
+ main += "\n" + m.st.Danger.Render(deleteBar)
}
// Yank success.
if m.yankSuccess {
yankBar := fmt.Sprintf("yanked path to clipboard: %v", m.path)
- main += "\n" + bar.Render(yankBar)
+ main += "\n" + m.st.Bar.Render(yankBar)
}
if m.previewMode {
return lipgloss.JoinHorizontal(
lipgloss.Top,
main,
- preview.
+ m.st.Preview.
MaxHeight(m.height).
Render(previewPane),
)
@@ -654,14 +688,26 @@ func (m *Model) filePath() (string, bool) {
return path.Join(m.path, fileName), true
}
-func (m *Model) openEditor() tea.Cmd {
+func (m *Model) openCommand() tea.Cmd {
filePath, ok := m.filePath()
if !ok {
return nil
}
- cmdline := Split(lookup([]string{"WALK_EDITOR", "EDITOR"}, "less"), " ")
- cmdline = append(cmdline, filePath)
+ cmdline := m.cmdline
+ if len(cmdline) == 0 || cmdline[0] == "" {
+ cmdline = Fields(lookup([]string{"LK_COMMAND", "EDITOR"}, "less"))
+ }
+ var replace bool
+ for i, s := range cmdline {
+ if replace = Contains(s, "{}"); replace {
+ cmdline[i] = ReplaceAll(s, "{}", filePath)
+ break
+ }
+ }
+ if !replace {
+ cmdline = append(cmdline, filePath)
+ }
execCmd := exec.Command(cmdline[0], cmdline[1:]...)
return tea.ExecProcess(execCmd, func(err error) tea.Msg {
@@ -715,7 +761,7 @@ func (m *Model) preview() {
if isImageExt(filePath) {
img, err := drawImage(filePath, width, height)
if err != nil {
- m.previewContent = warning.Render("No image preview available")
+ m.previewContent = m.st.Warning.Render("No image preview available")
return
}
m.previewContent = img
@@ -749,7 +795,7 @@ func (m *Model) preview() {
case utf8.Valid(content):
m.previewContent = leaveOnlyAscii(content)
default:
- m.previewContent = warning.Render("No preview available")
+ m.previewContent = m.st.Warning.Render("No preview available")
}
}
@@ -865,33 +911,6 @@ func lookup(names []string, val string) string {
return val
}
-func Usage() {
- _, _ = fmt.Fprintf(os.Stderr, "\n "+cursor.Render(" walk ")+"\n\n Usage: walk [path]\n\n")
- w := tabwriter.NewWriter(os.Stderr, 0, 8, 2, ' ', 0)
- put := func(s string) {
- _, _ = fmt.Fprintln(w, s)
- }
- put(" Arrows, hjkl\tMove cursor")
- put(" Enter\tEnter directory")
- put(" Backspace\tExit directory")
- put(" Space\tToggle preview")
- put(" Esc, q\tExit with cd")
- put(" Ctrl+c\tExit without cd")
- put(" /\tFuzzy search")
- put(" dd\tDelete file or dir")
- put(" y\tYank current directory path to clipboard")
- put("\n Flags:\n")
- put(" --icons\tdisplay icons")
- _ = w.Flush()
- _, _ = fmt.Fprintf(os.Stderr, "\n")
- os.Exit(1)
-}
-
-func Version() {
- fmt.Printf("\n %s %s\n\n", cursor.Render(" walk "), Version)
- os.Exit(0)
-}
-
func min(a, b int) int {
if a < b {
return a