From 1b2c70761a6bf12147919cb12a586d740c824dd1 Mon Sep 17 00:00:00 2001 From: ardnew Date: Mon, 8 Jan 2024 16:31:08 -0600 Subject: [PATCH] add cmd/lk command as submodule --- LICENSE | 1 + README.md | 126 +++-------------------------------------------- cmd/lk/LICENSE | 22 +++++++++ cmd/lk/README.md | 115 ++++++++++++++++++++++++++++++++++++++++++ cmd/lk/go.mod | 2 +- cmd/lk/main.go | 77 ++++++++++++++++++++++++----- walk.go | 125 ++++++++++++++++++++++++++-------------------- 7 files changed, 282 insertions(+), 186 deletions(-) create mode 100644 cmd/lk/LICENSE create mode 100644 cmd/lk/README.md 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 demo -
-

+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. - -Walk Preview Mode - -### Delete file or directory - -Press `dd` to delete file or directory. Press `u` to undo. - -Walk Deletes a File - -### Display icons - -Install [Nerd Fonts](https://www.nerdfonts.com) and add `--icons` flag. - -Walk Icons Support - -### Image preview - -No additional setup is required. - -Walk Image Preview - -## 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 demo +
+

+ +**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. + +Walk Preview Mode + +### Delete file or directory + +Press `dd` to delete file or directory. Press `u` to undo. + +Walk Deletes a File + +### Display icons + +Install [Nerd Fonts](https://www.nerdfonts.com) and add `--icons` flag. + +Walk Icons Support + +### Image preview + +No additional setup is required. + +Walk Image Preview + +## 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