Day 16: Raku powered jmp-ing to the coalface

Santa makes sure his elves can get to and from the workplace quickly. I wish it was the same for computer programming! Sometimes it takes a while to wade through a codebase to find where the real work needs to happen.

jmp is a Raku powered command-line utility I use for searching and jumping around large codebases and command output. It provides a terminal frontend to your favourite $code-searching-tool (e.g., ag, git grep, ack etc) so you can quickly jump into your favourite $EDITOR (e.g., vim, code, emacs etc).

It works like this:

jmp-demo

When jmp-ing into large codebases I often need to visit lots of different files and repositories. Sometimes this blows my short-term memory buffer. To try and stay in flow I leave a breadcrumb trail of comments throughout the code (i.e., “# COAL”).  Later when I need to get back to the coalface of file locations I do a jmp search for ‘COAL’.

shell> jmp to COAL

That works OK but it’s a manual process and requires cleaning up all the COAL markers afterwards. Can jmp help out a bit more here?

It would be cool if jmp automatically remembered where I’d visited in a codebase so I didn’t need to leave COAL markers around. Sounds like a bit more Raku is required!

To achieve this – jmp needs a memory:

# keep a file-based record of the searches and jmps
class JMP::Memory {

    has $.max-entries = 100; # keep a record of the last 100 jmps
    has $!file;
    has @!latest-jmps;

    #| get a list of the most recent JMP::File::Hits
    method get-recent-jmps ($last-n-jmps) { ... }

    #| write the memory file 
    method save ($jmp-command, $hit) { ... }

    submethod TWEAK { ... }

}

So let’s fill this class out.

TWEAK is a special submethod for helping to lazily finish building objects after an instance is created. Not all invocations of jmp need memory recall so it’s good not to process the history file if we can avoid it. The $!file and @!latest-jmps are encapsulated as private attributes so they are easy to change in future if needed.

submethod TWEAK {

    $!file = $*HOME.add('.jmp.hist').path;
    return unless $!file.IO.e;
    return without my $history = from-json($!file.IO.slurp);
    @!latest-jmps = $history.List;

}

The handy global $*HOME IO::Path object helps to create a cross-platform location for the jmp history file (e.g., ~/.jmp.hist). If the file exists we slurp the file in one go and parse the json payload into a .List of jmp records with the help of JSON::Tiny::from-json.

Whenever a search hit occurs we need to save the jmp command and the selected hit:

#| write the memory file 
method save ($jmp-command, $hit) {

    # for each incoming hit - we record two entries - the jmp command
    my %jmp-record = %(
        current-directory => $*CWD.path,
        jmp-command       => $jmp-command,
    );

    # and the selected destination
    my %hit-record = %(
        line-number   => $hit.line-number,
        file-path     => $hit.file-path,
        full-path     => $hit.full-path,
        matching-text => $hit.matching-text,
    );

    @!latest-jmps.unshift(%hit-record);
    @!latest-jmps.unshift(%jmp-record);

    my @hits = @!latest-jmps;

    @!latest-jmps = @hits.head($!max-entries);

    # dump to disk
    $!file.IO.spurt(to-json(@!latest-jmps));
}

Unshifting the record to the start of the list means more recent jmps appear at the top. Limiting the head() of the list ensures the list won’t grow too big and jmp stays fast.

When the user calls jmp with no arguments a list of recent jmps is displayed.

method get-recent-jmps ($last-n-jmps) {
    # return a list of JMP::File::Hits 
    my @recent-jmps;
    for @!latest-jmps.head($last-n-jmps) -> %hit {
        my $hit = %hit<jmp-command>:exists
                ?? JMP::Memory::Command.new(|%hit)
                !! JMP::Memory::Hit.new(|%hit);
        @recent-jmps.push($hit);
    }
    return @recent-jmps;
}

The for loop iterates through the previously recorded jmp commands and the selected search hits. Here is the full source code of JMP::Memory.

Now jmp can help you to return quickly to the coding coalface without leaving a mess behind.

To install the latest version of jmp, first install Raku, then use zef the Perl 6 module manager to install it:

shell> zef install jmp
shell> zef upgrade jmp   # if you've installed it before

Looking forward to using more Raku-powered utilities in 2020.

Happy Christmas!

2 thoughts on “Day 16: Raku powered jmp-ing to the coalface

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.