10

Fast navigation in terminal: coming full circle

 4 years ago
source link: http://karolis.koncevicius.lt/posts/fast_navigation_in_terminal_coming_full_cirlce/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Fast Navigation in Terminal: Coming Full Circle

A small list of methods for fast file system navigation from the command line. While I tried all of them and finally settled on a minimal approach described at the end, the article doesn’t advocate for one method over the others. Different users obviously have different needs and should choose the approach most suitable in their circumstances.

Spring

At the beginning, just after learning to use the command line, my terminal navigation included a lot of cd and a lot of ls . I jumped around folders, learned shortcuts like cd .. , cd ~ , cd - , and started becoming familiar with the shell. Having multi-argument commands for quick file and folder manipulation felt powerful and fresh, or so I remember.

After going to command line you no longer have to open folders in a graphical window manager, scan their names, use <ctrl + mouse click> to select them, and then drag the mouse to move those selected folders to another place. You do 'mv pattern* place/' instead. All is nice.

But there was one problem.

Summer

Along the way I started organizing my folders using a structure like this:

└── work
    ├── bitbucket
    │   └── user1
    │       ├── repo1
    │       └── repo2
    ├── github
    │   ├── user1
    │   │   ├── repo1
    │   │   └── repo2
    │   ├── user2
    │   │   ├── repo1
    │   │   └── repo2
    │   ├── user3
    │   │   ├── repo1
    │   │   ├── repo2
    │   │   ├── repo3
    │   │   └── repo4
    │   └── user4
    │       └── repo1
    ├── teaching
    │   ├── class1
    │   │   ├── lecture1
    │   │   └── lecture2
    │   └── class2
    │       ├── lecture1
    │       └── lecture2
    ├─── clients
    │   ├── client1
    │   │   ├── project1
    │   │   ├── project2
    │   │   └── project3
    │   ├── client2
    │   │   ├── project1
    │   │   ├── project2
    │   │   └── project3
    │   └── client3
    │      └── project1
    └─── ...

I often had to go in and out of various project folders and my cd + ls navigation became tedious. Reaching a project involved navigating a big tree of directories. On top of that a lot of directories had similar names which got in the way of tab completion. This is where I found “z” - a command line utility that tracks your most visited folders based on frequency and recency.

“z” keeps a database of your most frequently used folders and allows to quickly jump to these folders even if you don’t remember their full name. Instead of doing a lot of cd + <tab> all you have to do is type 'z proj' and z will take you to your most frequently/recently accessed folder that has proj somewhere in its name. It takes a short amount of time for “z” to learn about your most visited places but after that it becomes quick and convenient.

But there was one problem.

Autumn

“z” didn’t always take me where I wanted to be. It worked, I would say, around 95% of the time or more. But those other times it took me to an unrelated directory and distracted from the work at hand forcing to navigate my way back using the old cd . Moreover, sometimes I moved and renamed various folders which likely contributed to its disorientation.

After this experience I decided that the terminal should be dumb and deterministic without any fuzziness or smart guessing. And here I found marks - a short and sweet command line script for keeping manual directory bookmarks.It consisted of 4 tiny functions: one for creating a mark, one for removing, one for listing all the marks, and one for jumping to the bookmarked folder:

export MARKPATH=$HOME/.marks

function mark {
  mkdir -p "$MARKPATH"; ln -s "$(pwd)" "$MARKPATH/$1"
}

function unmark {
  rm -i "$MARKPATH/$1"
}

function marks {
  ls -l "$MARKPATH" | sed 's/  / /g' | cut -d' ' -f9- | sed 's/ -/\t-/g' && echo
}

function jump {
  cd -P "$MARKPATH/$1" 2>/dev/null || echo "No such mark: $1"
}

As well as a function generating completion suggestions after pressing <tab> :

_completemarks() {
  local curw=${COMP_WORDS[COMP_CWORD]}
  local wordlist=$(find $MARKPATH -type l -printf "%f\n")
    COMPREPLY=($(compgen -W '${wordlist[@]}' -- "$curw"))
    return 0
}

complete -F _completemarks jump unmark

The workflow of marks is manual but convenient. You have to go into the directory you want to bookmark and execute the mark command followed by the custom name, like 'mark myproject' . After that you can quickly jump back to this bookmarked folder using 'jump myproject' and when the bookmark is no longer relevant you can get rid of it with 'unmark myproject' .

With this approach the user is in control. There are no hidden smart bookmarks which means no auto-magic and no surprises. And after moving a project to another place the bookmark can be easily adjusted to point to its new location immediately, without waiting for a hidden automatic process to update its location, frequency, and recency.

But there was one problem.

Winter

Neither of those jump nor z commands could take me everywhere I needed to go. So the typical workflow would start with jumping to a project via the jump command but then switching to the old navigation via cd once inside it. Which meant that for navigating the file system I always had to keep using both: jump and cd . And having two distinct commands for doing the same thing felt a bit weird. This is where I learned about the $CDPATH variable and rolled out my own solution.

All you have to do is add this to your .bashrc:

export CDPATH=.:~/.marks/

Then, to add a bookmark called @name pointing to a "dir" directory:

ln -sr dir ~/.marks/@name

To delete a bookmark:

rm ~/.marks/@name

To list all available bookmarks

cd @<tab>

The $CDPATH solution works nicely when all the bookmarks are started with a special symbol which in my case was @ . This solves a couple of problems. One, the bookmarked directories, if prefixed with @ symbol, will not interfere with directories in the current folder. Second, all the available bookmarks will be displayed by typing cd @<tab> . And third, all bookmarks are now part of cd and so they gain tab completion for free.

Using this method you no longer have to maintain a short list of custom bookmark functions and their autocompletion in your .bashrc. And command for “change directory” can always be done with the same old and familiar cd command, independant of context.

But there was one problem.

Spring again

After working with the $CDPATH approach for a while I began noticing something peculiar. At no point in time did I have a list with a dozen or more bookmarks. Instead I found myself constantly going to the same 2 or 3 projects, finishing them, removing them from the $CDPATH list, adding new ones, and repeating the cycle again. Why would someone keep bookmarks for 3 directories?

This time, instead of changing the command, I tried to do something different and changed the folder structure. My projects moved from the state of being relevant to being archived so why not create an archive directory and organize folders based not on their name or type but on state? Thus the "zzz" folder was born. And now my project structure looks something like this:

└── work
    ├── active_project1
    ├── active_project2
    └── zzz
        ├── bitbucket
        │   └── user1
        │       ├── repo1
        │       └── repo2
        ├── github
        │   ├── user1
        │   │   ├── repo1
        │   │   └── repo2
        │   ├── user2
        │   │   ├── repo1
        │   │   └── repo2
        │   ├── user3
        │   │   ├── repo1
        │   │   ├── repo2
        │   │   ├── repo3
        │   │   └── repo4
        │   └── user4
        │       └── repo1
        ├── teaching
        │   ├── class1
        │   │   ├── lecture1
        │   │   └── lecture2
        │   └── class2
        │       ├── lecture1
        │       └── lecture2
        ├─── clients
        │   ├── client1
        │   │   ├── project1
        │   │   ├── project2
        │   │   └── project3
        │   ├── client2
        │   │   ├── project1
        │   │   ├── project2
        │   │   └── project3
        │   └── client3
        │      └── project1
        └─── ...

With this structure the bookmarking systems no longer provide big benefits as most frequent and recent folders are always under ~/work/some_project. And since at any timepoint only a few of them are visible the tab completion will do its job: 'cd ~/w<tab>/s<tab>' .

The name "zzz" might seem strange but is convenient: the letters “zzz” symbolically marks the projects within as being in a “sleeping” state while the 3 “z” letters in a row make sure that this folder is always placed at the very end of your active project list. A nice folder structure is still maintained in the archive but for the ease of access all active folders are layed flat under the ~/work/ directory. When the project is paused or completed it is moved to its place within the "zzz" hierarchy, maintaining the previous structure. There is one extra benefit - contents of the "work" directory act as a reminder about projects that currently in progress. If necessary a to-do list with concrete details might be placed at the same level.

And just like that, like a newbie, I navigate the file system with cd and ls again.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK