/harpoon

Primary LanguageLuaMIT LicenseMIT

WARNING

This is not fully baked, though used by several people. If you experience any issues, see some improvement you think would be amazing, or just have some feedback for harpoon (or me), make a ticket!

Harpoon -- Image provided by Liberty_DevCap

harpoon

The goal of Harpoon is to get you where you want with the fewest keystrokes.

The Problem

You work on code. The code base is medium, large, tiny, whatever. You find yourself frequenting a small set of files (maybe it depends on task) and you are tired of using a fuzzy finder, :bnext/prev, alternate file doesn't quite cut it, etc etc.

The Other Problem

You want to execute some project specific commands or have any number of persistent terminals that can be easily navigated to.

The Solution

The ability to specify, or on the fly, mark and create persisting key strokes to go to the files you want.

The Other Solution

Unlimited terminals and navigation.

Installation

Requires Neovim version 0.5.0+

Simply install via your favorite plugin manager.

Plug 'nvim-lua/plenary.nvim' " don't forget to add this one if you don't have it yet!
Plug 'ThePrimeagen/harpoon'

Harpooning

There are two modes of harpoon. File navigation and terminal navigation. Setup of harpoon configuration is at the bottom since its for more advanced use cases.

File Navigation

Mark a file

Marking a file is similar to vim global marks, but differ in a few key ways.

  • They auto update their position
  • They are unique per project.
  • They can be hand edited vs replaced (swapping is easier)

To mark a file simply call the following lua function

:lua require("harpoon.mark").add_file()

This will mark the file and add it to the end of the mark list.

Navigate to file

To navigate to any of the marked files simply call the navigation function with which index.

:lua require("harpoon.ui").nav_file(3) -- This will navigate to file 3

Manipulating current marks

There is a quick menu that will allow for you to edit your marks. You can hand edit the name, its position within the list, or remove it from the list. To bring up the quick list execute the following lua command.

:lua require("harpoon.ui").toggle_quick_menu()

You can simply edit this list as if it were a document in vim. :wq to save the new edits or :q to ignore the edits. There is to save upon call to toggle if you prefer that way.

You can also exit the list with q or <ESC>, which will call toggle_quick_menu() again.

Terminal Navigation

Motivation for terminals in neovim

I want to use the terminal since I can gF and gF to any errors arising from execution that are within the terminal that are not appropriate for something like dispatch. (not just running tests but perhaps a server that runs for X amount of time before crashing).

I want the terminal to be persistent and I can return to one of many terminals with some finger wizardry and reparse any of the execution information that was not necessarily error related.

I would like to have commands that can be tied to terminals and sent them without much thinking. Some sort of middle ground between vim-test and just typing them into a terminal (configuring netflix's television project isn't quite building and there are tons of ways to configure).

Navigating to a terminal

To navigate to a terminal simply provide an index and it will go. If there is no terminal in that index or the terminal has been closed by some means, harpoon will create a new terminal at that location.

lua require("harpoon.term").gotoTerminal(1)

You can provide as high of a number as you would like. There is no terminal count limitation though I personally find anything beyond two oft confusing.

Commands to terminal

Sometimes you wish to send commands to terminals that have been preconfigured for a project. To make this work properly you must predefine a command or hard code it as part of the send process.

" This will send to terminal 1 either the predefined command 1 in the terminal
" config or "ls -la"
lua require("harpoon.term").sendCommand(1, 1)
lua require("harpoon.term").sendCommand(1, "ls -la")

Dynamic commands to terminal

This feature adds ability to change commands while working inside a project. Just call the following function to edit commands inside the list

lua require('harpoon.cmd-ui').toggle_quick_menu()

Setup

Setup should be called once.

TODO: Make this callable more than once and just layer in the commands

Yes... A todo in a readme. Deal with it.

The Configuration File

You can configure harpoon via lua in your rc. Here is a simple example that will add a specific command to a project.

Global Settings
Here is the set of global settings and their default values.

require("harpoon").setup({
    global_settings = {
        save_on_toggle = false,
        save_on_change = true,
        enter_on_sendcmd = false,
        excluded_filetypes = { "harpoon" }
    },
    ... your other configs ...
})
  • save_on_toggle will set the marks upon calling toggle on the ui, instead of require :w.
  • save_on_change will save the harpoon file upon every change. If you don't enable this option (on by default) harpoon will not save any changes to your file. It is very unreliable to save your harpoon on exit (at least that is what I have found).
  • enter_on_sendcmd will set harpoon to run the command immediately as it's passed to the terminal when calling sendCommand.
  • excluded_filetypes filetypes that you want to prevent from adding to the harpoon list menu.

Preconfigured Terminal Commands

These are project specific commands that you wish to execute on the regular.

require("harpoon").setup({
    projects = {
        -- Yes $HOME works
        ["$HOME/personal/vim-with-me/server"] = {
            term = {
                cmds = {
                    "./env && npx ts-node src/index.ts"
                }
            }
        },

Debugging

Harpoon writes logs to a harpoon.log file that resides in Neovim's cache path. (:echo stdpath("cache") to find where that is for you.)

By default, logging is enabled for warnings and above. This can be changed by setting vim.g.harpoon_log_level variable to one of the following log levels: trace, debug, info, warn, error, or fatal. Note that this would have to be done before harpoon's setup call. Alternatively, it can be more convenient to launch Neovim with an environment variable, e.g. > HARPOON_LOG=trace nvim. In case both, vim.g and an environment variable are used, the log level set by the environment variable overrules. Supplying an invalid log level defaults back to warnings.