I wrote a small program to browse folders in the terminal. The main inspiration was type-ahead search in GUI file managers. There exist several programs that are similar (see the listing in the README), but none of them do it quite the way I like, and often they have a very complex UI and a ton of features. I tried to make something that is obvious how to use and gets out of your way. (I also wanted an excuse to learn Rust.)
Let me know what you think!
curl https://raw.githubusercontent.com/mgunyho/tere/master/Cargo.lock | grep source | wc -l
51
How come that such a simple tool has more than half a century of dependencies? This is terrifying.The answer to your question "How come a simple thing is more complex than I first thought?" is: "Because you've spent less time thinking about it than the author".
Whether this is better or worse seems to be a matter of contention for a lot of people, but it certainly poses a challenge for supply-chain auditing. I suspect for Rust to truly ever replace C++ in its domain, the ecosystem is going to need to come up with something like boost so you can grab one library that does all the things nearly any program wants (regex, serialization, cli opts, etc) but that don't get included in the core language's stdlib.
Yes, but then you are stuck with glibc + kitchen sink and GNU code quality, which is atrocious.
The comment I replied to is less than 100 characters long plus a command line invocation.
One of these two has spent more time thinking about whether some dependencies are needed.
But you're right that it could always be simpler, in fact I wrote tere originally in C with curses as the only dependency, and it compiles >10x faster. But there I had to manually write some (pretty certainly buggy) unicode handling, and I think adding extra features (proper arg parsing, json for history file etc) would be way more painful.
As long as the use of dependencies remains reasonable, the number of dependencies does not immediately mean that the code is "bloated".
With rust you just check in your Cargo.lock file to your VCS and then the versions of your dependencies and their dependencies (etc) are pinned, so if it works now then it will always work. For dependencies on crates.io authors don’t even have the option to remove a version once it’s published.
Upgrading dependencies is an explicit operation, so you only do it when required and run your tests afterwards.
In practice I’ve had far fewer issues with dependency versions using rust than I’ve had using dynamic system libraries in the C/C++ ecosystem
The deps for each of these packages is really out of the authors hands, this is really the same for any programming language though which need to pull in dependencies of their own.
That's what I mean when I say Rust community is toxic here https://news.ycombinator.com/item?id=32105449
For context, I totally agree that rust often ends up with more dependencies than I would like and that crate names are often super unhelpful, but I downvoted the comment.
And unless you're actively developing in Rust, why would some other people's choice of naming their projects in a language and tool chain you don't ever plan on using be such a big deal?
crossterm = "0.24.0"
dirs = "4.0.0"
regex = "1.5.4"
serde_json = "1.0"
serde = { version = "1.0", features = ["rc"] }
textwrap = "0.14"
unicode-segmentation = "1.7"
I guess one could write their own textwrap and probably also dirs, but other than that they all are probably vital to this tool.
The only reason this wouldn't be a problem in another language would be because that language already includes the functionality of such libraries
[dependencies.clap]
version = "3"
default-features = false
features = ["wrap_help", "suggestions", "std"] tere v1.0.0
├── clap v3.0.10
│ ├── bitflags v1.3.2
│ ├── indexmap v1.8.0
│ │ └── hashbrown v0.11.2
│ │ [build-dependencies]
│ │ └── autocfg v1.1.0
│ ├── os_str_bytes v6.0.0
│ │ └── memchr v2.4.1
│ ├── strsim v0.10.0
│ ├── terminal_size v0.1.17
│ │ └── libc v0.2.126
│ └── textwrap v0.14.2
│ ├── smawk v0.3.1
│ ├── terminal_size v0.1.17 (*)
│ ├── unicode-linebreak v0.1.2
│ │ [build-dependencies]
│ │ └── regex v1.5.4
│ │ ├── aho-corasick v0.7.18
│ │ │ └── memchr v2.4.1
│ │ ├── memchr v2.4.1
│ │ └── regex-syntax v0.6.25
│ └── unicode-width v0.1.9
├── crossterm v0.24.0
│ ├── bitflags v1.3.2
│ ├── libc v0.2.126
│ ├── mio v0.8.4
│ │ ├── libc v0.2.126
│ │ └── log v0.4.14
│ │ └── cfg-if v1.0.0
│ ├── parking_lot v0.12.1
│ │ ├── lock_api v0.4.7
│ │ │ └── scopeguard v1.1.0
│ │ │ [build-dependencies]
│ │ │ └── autocfg v1.1.0
│ │ └── parking_lot_core v0.9.3
│ │ ├── cfg-if v1.0.0
│ │ ├── libc v0.2.126
│ │ └── smallvec v1.8.0
│ ├── signal-hook v0.3.13
│ │ ├── libc v0.2.126
│ │ └── signal-hook-registry v1.4.0
│ │ └── libc v0.2.126
│ └── signal-hook-mio v0.2.3
│ ├── libc v0.2.126
│ ├── mio v0.8.4 (*)
│ └── signal-hook v0.3.13 (*)
├── dirs v4.0.0
│ └── dirs-sys v0.3.6
│ └── libc v0.2.126
├── regex v1.5.4 (*)
├── serde v1.0.134
├── serde_json v1.0.75
│ ├── itoa v1.0.1
│ ├── ryu v1.0.9
│ └── serde v1.0.134
├── textwrap v0.14.2 (*)
└── unicode-segmentation v1.8.0
I can easily look at each one of these and understand why it’s there, save only a few: most of crossterm’s recursive dependencies, which are fairly involved but the immediate dependencies at least are clearly explained in its README (and you could probably remove one or two of them at minor performance or similar costs); mio’s use of log, which I believe should be optional and I would say not enabled by default, but I’d rather use it with log than miss out on it, where it’s useful; uses of autocfg, which are build-time implementation detail for taking advantage of rustc features where available and readily understood on analysis; and I was going to say cfg-if, which I have a minor personal vendetta against due to extensive unnecessary use, but after looking at parking_lot_core’s src/thread_parker/mod.rs I will begrudgingly admit this is one of the rare cases where it is fairly well-justified. All up, none of the dependencies are in any way unreasonable.Having over fifty dependencies for something like this is not in any way terrifying—you’re just starting with a different set of expectations and understandings of how things are done. It’s a natural and reasonable outworking of (a) using the right tools for the job, (b) doing things properly rather than half-heartedly (most obviously where terminal interactions are involved), and (c) Rust’s deliberately thin standard library.
Writing in Rust looks like casting spells in an Infocom game.
Gnusto rezrov!
https://en.m.wikibooks.org/wiki/C_Programming/stdlib.h/itoa
I get the others, but that ones a pretty standard type converter, the opposite of the C standard atoi
This is the sort of thing that I meant by “doing things properly rather than half-heartedly”. You could make something like this by reimplementing half the libraries in an inferior fashion, full of bugs, cross-platform inconsistencies and missing functionality. Or you can take an extra dependency. Even in Python, the recommendation would be firmly to add dependencies like appdirs for dirs and I dunno what for the rest.
You can do a lot with pythons stdlib, and a lot of popular libraries (requests, for example) largely are just usability wrappers for underlying stdlib stuff.
Whereas with Rust, you have a very minimal std, and things you want to achieve are imported specifically.
I recommend either CBT or writing your own version with fewer dependencies to ease your terror.
> I also wanted an excuse to learn
This is the best reason ever, and not just for you: seeing your GIF I thought "could I achieve that with fzf?". Turns out, yes I can:
function fcd() {
local dir;
while true; do
# exit with ^D
dir="$(ls -a1p | grep '/$' | grep -v '^./$' | fzf --height 40% --reverse --no-multi --preview 'pwd' --preview-window=up,1,border-none --no-info)"
if [[ -z "${dir}" ]]; then
break
else
cd "${dir}"
fi
done
}
Certainly not the best code (and definitely not a jab at your implementation!) but, hey, it was purely for the heck of it.Lately I've been trying to find the joy in programming again, these kind of fun little challenges help a lot, so thanks for sharing, enthusiasm and creativity is contaminating :)
I've been sporting this alias for a while now:
alias pf="fzf --preview='less {}' --bind shift-up:preview-page-up,shift-down:preview-page-down"
You can run `pf` (preview file) in a directory and it opens a split window with fzf where you can preview text files with less and optionally filter down which files are matched with fzf.fzf is great.
git log --oneline --decorate --color | fzf --ansi --preview 'git show $(echo {} | cut -d" " -f1)'
The possibilities, they are _endless_ !!11!1!If you're using the fzf.vim plugin[0] you can run `:Commits` to do something similar to your command too. It adds a bit more detail such as when the commit was made in relative format and color codes the git diff.
While digging up alternatives (of course after I had already written most of the functionality), I briefly tried out fzf. I think at that time I couldn't find an example snippet like yours to do the cding, so I didn't look into it much more. With some basic settings, it was also not easy (or even possible?) to go up in the folder tree, but I see that your example handles that.
If you're unfamiliar with a big folder tree, fzf (or another very similar tool that is designed for this purpose, broot) can be more efficient. But it might take a while for it to scan all subfolders.
Well, fzf is only a selection tool, it's more about the tool that feeds fzf data ;) That's why I used a dumb ls in my example. Maybe a difference with yours is that it does perform cd on each loop, and there's no way to bail out and not cd.
> it might take a while for it to scan all subfolders.
Haha yeah, for recursive cases I was using ripgrep as fzf default command:
rg --files --no-ignore --hidden --follow --glob "!{.git,node_modules}/*" | fzf
And now I just discovered fd[0]: fd --no-ignore --hidden --follow --strip-cwd-prefix | fzf
(...at least essentially, my setup is a bit more tunable and complex, I have a few of these with varying flags depending on the situation and goal) function fcd() {
local dir;
while true; do
# exit with ^D
dir="$(ls -a1F | grep '[/@]$' | grep -v '^./$' | sed 's/@$//' | fzf --height 40% --reverse --no-multi --preview 'pwd' --preview-window=up,1,border-none --no-info)"
if [[ -z "${dir}" ]]; then
break
elif [[ -d "${dir}" ]]; then
cd "${dir}"
fi
done
} # For fuzzy-jumping down your home directory.
# See http://richardmavis.info/fuzzy-jumping
function fcd {
cache=~/.config/home-dirs-list
if (( $# == 0 )); then
if [ -e $cache ]; then
cd "`fzf --height=10 < ${cache}`"
else
echo "No directory cache."
cd `find ~ -type d | fzf --height=10`
fi
elif (( $# == 1 )); then
if [[ $1 == "--cache" ]]; then
echo "Making home directory cache."
find ~ -type d | sort > $cache
elif [ -e $cache ]; then
cd "`cat ${cache} | grep $1 | fzf --height=10`"
else
echo "No directory cache."
cd `find $1 -type d | fzf --height=10`
fi
else
echo "Usage: fcd [DIR]|--cache"
fi
}bind -x '"\C-p": cd "$(dirname "$(fd | fzf )")"'
The auto-cd can also be turned off with a CLI option.
I think you're underestimating how efficient your brain can get at guesstimating how many characters are required to navigate to a directory, especially when the directory structure is familiar (at which point I virtually never overshoot).
And, I still find value in this feature despite being a fast typist (90 WPM).
I think the word you are looking for is “indirectly”.
[1]: https://github.com/junegunn/fzf#key-bindings-for-command-lin...
Essentially the same UI.
I do think your program does add some features not found in VIM.
Also netrw is a bit of a mess; I once found a bug and thought "I'll write a patch". After five minutes of browsing through the code ([insert Eddie Murphy meme]): "yeah, never mind".
In the demo, how did you get the key press indicator to show? I haven't seen that before, but it's a wonderful addition. And may I ask what you used to record it?
I used screenkey to display the keystrokes and Peek to record the gif. I actually wrote some instructions for myself so I remember how I did it if I want to update the gif: https://github.com/mgunyho/tere/blob/master/demo/README.md
Very well executed.
I'm not sure there's a level between fifth and "being obnoxiously pushy," but if there's such a thing, I'd be interested to know. :)
I really thought it was initially a typo on "tree" (the tool's purpose is to navigate the folder tree) that the author decided to keep.
”Tere” is also, perhaps unsurprisingly, used in Finnish.
For example
vim ~
vim /etc
It has more or less the same UI as the one in this Show HN but with the added feature of opening any files you select.That said, I do think your reply here is a little disingenuous because one extra key stroke is hardly "cumbersome". Especially when you advertise your tool as having VIM bindings and then criticise VIM for the same thing. I have no issues with you promoting your tool but lets be pragmatic about our comparisons here.
You also overlooked the fact that VIM will open any documents from within in. Which would save you a lot more key strokes than tere due to not having to `$EDITOR $file` after the whole (tere|vim) {navigate} process.
As for automatically changing directory. Lets be clear, `tere` doesn't either. You need to configure your shell to do that. Granted you provide the code to set that up but there's nothing stopping someone from writing a similar script for VIM (there's a VimLeave event so you could do something like
:autocmd VimLeave *
and the path would be stored in expand('%:p:h')
so it's certainly doable to replicate the same behaviour in VIM. In fact I might even knock up a working script if anything wants it?I'm honestly not saying any of this to be negative about your project here though. It's a nice little tool and I'm sure some people will find it very useful. It's just a common problem so you'll probably find a lot of of people have already solved this with other tools. But there's absolutely nothing wrong with having another tool out there :)
To me, there really is a significant difference in friction when navigating in vim vs tere. Maybe it's because I need to use shift to type '/' on my keyboard layout. But I also have to press enter twice to cd after searching.
The point about opening the files within vim is valid, and I have been considering adding the option to call xdg-open on highlighted files, like many such tools do. I haven't decided yet if that's within the scope of tere.
> It's just a common problem so you'll probably find a lot of people have already solved this with other tools
I agree, and indeed there are a lot of existing alternatives as mentioned in the README. The main motivation for me was to make something that works just the way I want, and secondarily, to write something fun in Rust.
On qwerty I think that's the case :)
Just run your command from the repo root or $HOME.
It is such a common need (for me at least) that I'm surprised the standard cd doesn't do it already.
I find myself using find or locate, then wanting to change to some file's directory, thus needing to manually delete the file's name before hitting enter. When you do that enough times you start to wish it worked automatically :-)
I also submitted few days ago here on HN: https://news.ycombinator.com/item?id=32081263
To compile it, I had to sudo apt-get install libacl1-dev, which was not mentioned in the instructions.
Ideally, I would provide pre-built packages from CI, of course.
Later i "upgrade" it to a Star Trek LCARS Design.... i have to look if i still had the sources.
History repeats.
I haven't seen it mentioned yet, but Ranger is a great alternative tool, it can even be integrated into vim
xplr.config.modes.builtin.default.key_bindings.on_key.T = {
help = "tere nav",
messages = {
{ BashExec = [[echo ChangeDirectory: "'"$(tere)"'" >> "$XPLR_PIPE_MSG_IN"]] },
},
}Once I select a directory I want to dump myself back out into a command line in that directory, I couldn't see the key for that? Or am I misunderstanding the purpose?
I'll add ~ as a shortcut for the home directory, thanks for the good idea! (It conflicts with type-to-search for folders starting with '~', but I think that's much less common than cding to home.)
cd tab tab directories listed
cat tab tab files listed
(this^^ is on Debian, it can probably be changed but I've not looked into it)
it's pretty annoying when I'm halfway through and want to use tab tab as a periscope to see all file+dirs
autoload -Uz compinit compinit
zstyle ':completion:*' menu select
Then ‘cd <tab>’
https://github.com/antonmedv/llama
But with fuzzy searching.
Also typical hacker news - "why don't you just use Vim"? Ha. Ignore the naysayers.
OTOH, people have named a number of similar tools, and I'm going to point out 'mc' aka Midnight Commander, which is part of most linux distro's, and is a clone of the 1980's Norton commander. Its designed to be a command line TUI helper, sorta like the project here, with not only cursor/etc based directory navigation but copy/rename/delete/etc functionality just a keystroke away. Its fairly convenient because it overlays the command line rather than replacing it.
I find these kinds of text based, keyboard centric explorers to be far superior for navigating around a codebase, then giving you back your screen space as soon as you're found what you're looking for.
I implemented something similar but for VSCode: https://github.com/danprince/vsnetrw
It's written for fish, but could be ported to bash easily:
# cat ~/.config/fish/functions/cd.fish
function cd
builtin cd $argv;
if test $status -gt 0 # there was an error, stop
return
end
# auto print dir info
if test "$argv" != "" # not home though
dir
end
end
There's probably a way to optimize out the "test " call as well, but I haven't got around to it.BTW, checkout `nnn` search-as-you-type and cd-on-quit features. I think its similar in scope to this project.
https://github.com/jarun/nnn/wiki/Basic-use-cases#configure-...
Good to see the idea getting more traction!
https://github.com/seligman/ccd
ccd is more concerned with an edge-first search, and since it's Windows, actually changing the directory involves a thread insertion in the cmd process to do the work ... which somehow works.
I don't see a use case for myself. I use cd with TAB autocomplete and I'm set. I'm old enough and lazy enough.