[
  {
    "path": ".editorconfig",
    "content": "# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file\n[*]\nend_of_line = lf\nindent_style = space\nindent_size = 4\ninsert_final_newline = true\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: theprimeagen\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Found something wrong with Harpoon2?\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**WARNING**\nIf this is about Harpoon1, the issue will be closed.  All support and everything of harpoon1 will be frozen on `master` until 4/20 or 6/9 and then harpoon2 will become master\n\nPlease use `harpoon2` for branch\n---------------\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**What issue are you having that you need harpoon to solve?**\n\n**Why doesn't the current config help?**\n\n**What proposed api changes are you suggesting?**\n"
  },
  {
    "path": ".github/workflows/format.yml",
    "content": "name: Format\n\non: [push, pull_request]\n\njobs:\n    format:\n        name: Stylua\n        runs-on: ubuntu-latest\n        steps:\n            - uses: actions/checkout@v2\n            - run: date +%W > weekly\n\n            - name: Restore cache\n              id: cache\n              uses: actions/cache@v2\n              with:\n                path: |\n                  ~/.cargo/bin\n                key: ${{ runner.os }}-cargo-${{ hashFiles('weekly') }}\n\n            - name: Install\n              if: steps.cache.outputs.cache-hit != 'true'\n              run: cargo install stylua\n\n            - name: Format\n              run: stylua --check lua/ --config-path=.stylua.toml\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\n\non: [push, pull_request]\n\njobs:\n    lint:\n        name: Luacheck\n        runs-on: ubuntu-latest\n        steps:\n            - uses: actions/checkout@v2\n            - name: Setup\n              run: |\n                  sudo apt-get update\n                  sudo apt-get install luarocks\n                  sudo luarocks install luacheck\n\n            - name: Lint\n              run: luacheck lua/ --globals vim\n"
  },
  {
    "path": ".luacheckrc",
    "content": "std = luajit\ncache = true\ncodes = true\n\nglobals = {\n    \"HarpoonConfig\",\n    \"Harpoon_bufh\",\n    \"Harpoon_win_id\",\n    \"Harpoon_cmd_win_id\",\n    \"Harpoon_cmd_bufh\",\n}\nread_globals = { \"vim\" }\n"
  },
  {
    "path": ".stylua.toml",
    "content": "column_width = 80\nline_endings = \"Unix\"\nindent_type = \"Spaces\"\nindent_width = 4\nquote_style = \"AutoPreferDouble\"\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 ThePrimeagen\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "fmt:\n\techo \"===> Formatting\"\n\tstylua lua/ --config-path=.stylua.toml\n\nlint:\n\techo \"===> Linting\"\n\tluacheck lua/ --globals vim\n\npr-ready: fmt lint\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\n## ⇁  HARPOON 2\nThis is a deprecated and all future changes will be to the branch `harpoon2`.\n\n[Harpoon 2](https://github.com/ThePrimeagen/harpoon/tree/harpoon2)\n\n**STATUS**: Merging into mainline April 20th or June 9th (nice)\n\n-------------------------------\n# Legacy Harpoon README\n\n# Harpoon\n##### Getting you where you want with the fewest keystrokes.\n\n[![Lua](https://img.shields.io/badge/Lua-blue.svg?style=for-the-badge&logo=lua)](http://www.lua.org)\n[![Neovim](https://img.shields.io/badge/Neovim%200.5+-green.svg?style=for-the-badge&logo=neovim)](https://neovim.io)\n</div>\n\n![Harpoon](harpoon.png)\n-- image provided by **Bob Rust**\n\n## ⇁  WIP\nThis is not fully baked, though used by several people. If you experience any\nissues, see some improvement you think would be amazing, or just have some\nfeedback for harpoon (or me), make an issue!\n\n\n## ⇁ The Problems:\n1. You're working on a codebase. medium, large, tiny, whatever. You find\nyourself frequenting a small set of files and you are tired of using a fuzzy finder,\n`:bnext` & `:bprev` are getting too repetitive, alternate file doesn't quite cut it, etc etc.\n1. You want to execute some project specific commands or have any number of\npersistent terminals that can be easily navigated to.\n\n\n## ⇁  The Solutions:\n1. The ability to specify, or on the fly, mark and create persisting key strokes\nto go to the files you want.\n1. Unlimited terminals and navigation.\n\n\n## ⇁ Installation\n* neovim 0.5.0+ required\n* install using your favorite plugin manager (`vim-plug` in this example)\n```vim\nPlug 'nvim-lua/plenary.nvim' \" don't forget to add this one if you don't have it yet!\nPlug 'ThePrimeagen/harpoon'\n```\n\n## ⇁ Harpooning\nhere we'll explain how to wield the power of the harpoon:\n\n\n### Marks\nyou mark files you want to revisit later on\n```lua\n:lua require(\"harpoon.mark\").add_file()\n```\n\n### File Navigation\nview all project marks with:\n```lua\n:lua require(\"harpoon.ui\").toggle_quick_menu()\n```\nyou can go up and down the list, enter, delete or reorder. `q` and `<ESC>` exit and save the menu\n\nyou also can switch to any mark without bringing up the menu, use the below with the desired mark index\n```lua\n:lua require(\"harpoon.ui\").nav_file(3)                  -- navigates to file 3\n```\nyou can also cycle the list in both directions\n```lua\n:lua require(\"harpoon.ui\").nav_next()                   -- navigates to next mark\n:lua require(\"harpoon.ui\").nav_prev()                   -- navigates to previous mark\n```\nfrom the quickmenu, open a file in:\na vertical split with control+v,\na horizontal split with control+x,\na new tab with control+t\n\n### Terminal Navigation\nthis works like file navigation except that if there is no terminal at the specified index\na new terminal is created.\n```lua\nlua require(\"harpoon.term\").gotoTerminal(1)             -- navigates to term 1\n```\n\n### Commands to Terminals\ncommands can be sent to any terminal\n```lua\nlua require(\"harpoon.term\").sendCommand(1, \"ls -La\")    -- sends ls -La to tmux window 1\n```\nfurther more commands can be stored for later quick\n```lua\nlua require('harpoon.cmd-ui').toggle_quick_menu()       -- shows the commands menu\nlua require(\"harpoon.term\").sendCommand(1, 1)           -- sends command 1 to term 1\n```\n\n### Tmux Support\ntmux is supported out of the box and can be used as a drop-in replacement to normal terminals\nby simply switching `'term' with 'tmux'` like so\n\n```lua\nlua require(\"harpoon.tmux\").gotoTerminal(1)             -- goes to the first tmux window\nlua require(\"harpoon.tmux\").sendCommand(1, \"ls -La\")    -- sends ls -La to tmux window 1\nlua require(\"harpoon.tmux\").sendCommand(1, 1)           -- sends command 1 to tmux window 1\n```\n\n`sendCommand` and `goToTerminal` also accept any valid [tmux pane identifier](https://man7.org/linux/man-pages/man1/tmux.1.html#COMMANDS).\n```lua\nlua require(\"harpoon.tmux\").gotoTerminal(\"{down-of}\")   -- focus the pane directly below\nlua require(\"harpoon.tmux\").sendCommand(\"%3\", \"ls\")     -- send a command to the pane with id '%3'\n```\n\nOnce you switch to a tmux window you can always switch back to neovim, this is a\nlittle bash script that will switch to the window which is running neovim.\n\nIn your `tmux.conf` (or anywhere you have keybinds), add this\n```bash\nbind-key -r G run-shell \"path-to-harpoon/harpoon/scripts/tmux/switch-back-to-nvim\"\n```\n\n### Telescope Support\n1st register harpoon as a telescope extension\n```lua\nrequire(\"telescope\").load_extension('harpoon')\n```\ncurrently only marks are supported in telescope\n```\n:Telescope harpoon marks\n```\n\n## ⇁ Configuration\nif configuring harpoon is desired it must be done through harpoons setup function\n```lua\nrequire(\"harpoon\").setup({ ... })\n```\n\n### Global Settings\nhere are all the available global settings with their default values\n```lua\nglobal_settings = {\n    -- sets the marks upon calling `toggle` on the ui, instead of require `:w`.\n    save_on_toggle = false,\n\n    -- saves the harpoon file upon every change. disabling is unrecommended.\n    save_on_change = true,\n\n    -- sets harpoon to run the command immediately as it's passed to the terminal when calling `sendCommand`.\n    enter_on_sendcmd = false,\n\n    -- closes any tmux windows harpoon that harpoon creates when you close Neovim.\n    tmux_autoclose_windows = false,\n\n    -- filetypes that you want to prevent from adding to the harpoon list menu.\n    excluded_filetypes = { \"harpoon\" },\n\n    -- set marks specific to each git branch inside git repository\n    mark_branch = false,\n\n    -- enable tabline with harpoon marks\n    tabline = false,\n    tabline_prefix = \"   \",\n    tabline_suffix = \"   \",\n}\n```\n\n\n### Preconfigured Terminal Commands\nto preconfigure terminal commands for later use\n```lua\nprojects = {\n    -- Yes $HOME works\n    [\"$HOME/personal/vim-with-me/server\"] = {\n        term = {\n            cmds = {\n                \"./env && npx ts-node src/index.ts\"\n            }\n        }\n    }\n}\n```\n\n## ⇁ Logging\n- logs are written to `harpoon.log` within the nvim cache path (`:echo stdpath(\"cache\")`)\n- available log levels are `trace`, `debug`, `info`, `warn`, `error`, or `fatal`. `warn` is default\n- log level can be set with `vim.g.harpoon_log_level` (must be **before** `setup()`)\n- launching nvim with `HARPOON_LOG=debug nvim` takes precedence over `vim.g.harpoon_log_level`.\n- invalid values default back to `warn`.\n\n## ⇁ Others\n#### How do Harpoon marks differ from vim global marks\nthey serve a similar purpose however harpoon marks differ in a few key ways:\n1. They auto update their position within the file\n1. They are saved _per project_.\n1. They can be hand edited vs replaced (swapping is easier)\n\n#### The Motivation behind Harpoon terminals\n1. I want to use the terminal since I can gF and <c-w>gF to any errors arising\nfrom execution that are within the terminal that are not appropriate for\nsomething like dispatch. (not just running tests but perhaps a server that runs\nfor X amount of time before crashing).\n1. I want the terminal to be persistent and I can return to one of many terminals\nwith some finger wizardry and reparse any of the execution information that was\nnot necessarily error related.\n1. I would like to have commands that can be tied to terminals and sent them\nwithout much thinking. Some sort of middle ground between vim-test and just\ntyping them into a terminal (configuring netflix's television project isn't\nquite building and there are tons of ways to configure).\n\n#### Use a dynamic width for the Harpoon popup menu\nSometimes the default width of `60` is not wide enough.\nThe following example demonstrates how to configure a custom width by setting\nthe menu's width relative to the current window's width.\n\n```lua\nrequire(\"harpoon\").setup({\n    menu = {\n        width = vim.api.nvim_win_get_width(0) - 4,\n    }\n})\n```\n\n\n#### Tabline\n\nBy default, the tabline will use the default theme of your theme.  You can customize by editing the following highlights:\n\n* HarpoonInactive\n* HarpoonActive\n* HarpoonNumberActive\n* HarpoonNumberInactive\n\nExample to make it cleaner:\n\n```lua\nvim.cmd('highlight! HarpoonInactive guibg=NONE guifg=#63698c')\nvim.cmd('highlight! HarpoonActive guibg=NONE guifg=white')\nvim.cmd('highlight! HarpoonNumberActive guibg=NONE guifg=#7aa2f7')\nvim.cmd('highlight! HarpoonNumberInactive guibg=NONE guifg=#7aa2f7')\nvim.cmd('highlight! TabLineFill guibg=NONE guifg=white')\n```\n\nResult:\n![tabline](https://i.imgur.com/8i8mKJD.png)\n\n## ⇁ Social\nFor questions about Harpoon, there's a #harpoon channel on [the Primeagen's Discord](https://discord.gg/theprimeagen) server.\n* [Discord](https://discord.gg/theprimeagen)\n* [Twitch](https://www.twitch.tv/theprimeagen)\n* [Twitter](https://twitter.com/ThePrimeagen)\n"
  },
  {
    "path": "TODO.md",
    "content": "### Manage A Mark 1.0\n* Logo\n* floating term / split term\n* TODO: Fill me in, that one really important thing....\n* README.md\n\n### Harpoon (upon requests)\n* Add hooks for vim so that someone can make it for me\n* ackshual tests.\n* interactive menu\n* cycle\n* make setup() callable more than once and just layer in the commands\n"
  },
  {
    "path": "lua/harpoon/cmd-ui.lua",
    "content": "local harpoon = require(\"harpoon\")\nlocal popup = require(\"plenary.popup\")\nlocal utils = require(\"harpoon.utils\")\nlocal log = require(\"harpoon.dev\").log\nlocal term = require(\"harpoon.term\")\n\nlocal M = {}\n\nHarpoon_cmd_win_id = nil\nHarpoon_cmd_bufh = nil\n\nlocal function close_menu(force_save)\n    force_save = force_save or false\n    local global_config = harpoon.get_global_settings()\n\n    if global_config.save_on_toggle or force_save then\n        require(\"harpoon.cmd-ui\").on_menu_save()\n    end\n\n    vim.api.nvim_win_close(Harpoon_cmd_win_id, true)\n\n    Harpoon_cmd_win_id = nil\n    Harpoon_cmd_bufh = nil\nend\n\nlocal function create_window()\n    log.trace(\"_create_window()\")\n    local config = harpoon.get_menu_config()\n    local width = config.width or 60\n    local height = config.height or 10\n    local borderchars = config.borderchars\n        or { \"─\", \"│\", \"─\", \"│\", \"╭\", \"╮\", \"╯\", \"╰\" }\n    local bufnr = vim.api.nvim_create_buf(false, false)\n\n    local Harpoon_cmd_win_id, win = popup.create(bufnr, {\n        title = \"Harpoon Commands\",\n        highlight = \"HarpoonWindow\",\n        line = math.floor(((vim.o.lines - height) / 2) - 1),\n        col = math.floor((vim.o.columns - width) / 2),\n        minwidth = width,\n        minheight = height,\n        borderchars = borderchars,\n    })\n\n    vim.api.nvim_win_set_option(\n        win.border.win_id,\n        \"winhl\",\n        \"Normal:HarpoonBorder\"\n    )\n\n    return {\n        bufnr = bufnr,\n        win_id = Harpoon_cmd_win_id,\n    }\nend\n\nlocal function get_menu_items()\n    log.trace(\"_get_menu_items()\")\n    local lines = vim.api.nvim_buf_get_lines(Harpoon_cmd_bufh, 0, -1, true)\n    local indices = {}\n\n    for _, line in pairs(lines) do\n        if not utils.is_white_space(line) then\n            table.insert(indices, line)\n        end\n    end\n\n    return indices\nend\n\nfunction M.toggle_quick_menu()\n    log.trace(\"cmd-ui#toggle_quick_menu()\")\n    if\n        Harpoon_cmd_win_id ~= nil\n        and vim.api.nvim_win_is_valid(Harpoon_cmd_win_id)\n    then\n        close_menu()\n        return\n    end\n\n    local win_info = create_window()\n    local contents = {}\n    local global_config = harpoon.get_global_settings()\n\n    Harpoon_cmd_win_id = win_info.win_id\n    Harpoon_cmd_bufh = win_info.bufnr\n\n    for idx, cmd in pairs(harpoon.get_term_config().cmds) do\n        contents[idx] = cmd\n    end\n\n    vim.api.nvim_win_set_option(Harpoon_cmd_win_id, \"number\", true)\n    vim.api.nvim_buf_set_name(Harpoon_cmd_bufh, \"harpoon-cmd-menu\")\n    vim.api.nvim_buf_set_lines(Harpoon_cmd_bufh, 0, #contents, false, contents)\n    vim.api.nvim_buf_set_option(Harpoon_cmd_bufh, \"filetype\", \"harpoon\")\n    vim.api.nvim_buf_set_option(Harpoon_cmd_bufh, \"buftype\", \"acwrite\")\n    vim.api.nvim_buf_set_option(Harpoon_cmd_bufh, \"bufhidden\", \"delete\")\n    vim.api.nvim_buf_set_keymap(\n        Harpoon_cmd_bufh,\n        \"n\",\n        \"q\",\n        \"<Cmd>lua require('harpoon.cmd-ui').toggle_quick_menu()<CR>\",\n        { silent = true }\n    )\n    vim.api.nvim_buf_set_keymap(\n        Harpoon_cmd_bufh,\n        \"n\",\n        \"<ESC>\",\n        \"<Cmd>lua require('harpoon.cmd-ui').toggle_quick_menu()<CR>\",\n        { silent = true }\n    )\n    vim.api.nvim_buf_set_keymap(\n        Harpoon_cmd_bufh,\n        \"n\",\n        \"<CR>\",\n        \"<Cmd>lua require('harpoon.cmd-ui').select_menu_item()<CR>\",\n        {}\n    )\n    vim.cmd(\n        string.format(\n            \"autocmd BufWriteCmd <buffer=%s> lua require('harpoon.cmd-ui').on_menu_save()\",\n            Harpoon_cmd_bufh\n        )\n    )\n    if global_config.save_on_change then\n        vim.cmd(\n            string.format(\n                \"autocmd TextChanged,TextChangedI <buffer=%s> lua require('harpoon.cmd-ui').on_menu_save()\",\n                Harpoon_cmd_bufh\n            )\n        )\n    end\n    vim.cmd(\n        string.format(\n            \"autocmd BufModifiedSet <buffer=%s> set nomodified\",\n            Harpoon_cmd_bufh\n        )\n    )\nend\n\nfunction M.select_menu_item()\n    log.trace(\"cmd-ui#select_menu_item()\")\n    local cmd = vim.fn.line(\".\")\n    close_menu(true)\n    local answer = vim.fn.input(\"Terminal index (default to 1): \")\n    if answer == \"\" then\n        answer = \"1\"\n    end\n    local idx = tonumber(answer)\n    if idx then\n        term.sendCommand(idx, cmd)\n    end\nend\n\nfunction M.on_menu_save()\n    log.trace(\"cmd-ui#on_menu_save()\")\n    term.set_cmd_list(get_menu_items())\nend\n\nreturn M\n"
  },
  {
    "path": "lua/harpoon/dev.lua",
    "content": "-- Don't include this file, we should manually include it via\n-- require(\"harpoon.dev\").reload();\n--\n-- A quick mapping can be setup using something like:\n-- :nmap <leader>rr :lua require(\"harpoon.dev\").reload()<CR>\nlocal M = {}\n\nfunction M.reload()\n    require(\"plenary.reload\").reload_module(\"harpoon\")\nend\n\nlocal log_levels = { \"trace\", \"debug\", \"info\", \"warn\", \"error\", \"fatal\" }\nlocal function set_log_level()\n    local log_level = vim.env.HARPOON_LOG or vim.g.harpoon_log_level\n\n    for _, level in pairs(log_levels) do\n        if level == log_level then\n            return log_level\n        end\n    end\n\n    return \"warn\" -- default, if user hasn't set to one from log_levels\nend\n\nlocal log_level = set_log_level()\nM.log = require(\"plenary.log\").new({\n    plugin = \"harpoon\",\n    level = log_level,\n})\n\nlocal log_key = os.time()\n\nlocal function override(key)\n    local fn = M.log[key]\n    M.log[key] = function(...)\n        fn(log_key, ...)\n    end\nend\n\nfor _, v in pairs(log_levels) do\n    override(v)\nend\n\nfunction M.get_log_key()\n    return log_key\nend\n\nreturn M\n"
  },
  {
    "path": "lua/harpoon/init.lua",
    "content": "local Path = require(\"plenary.path\")\nlocal utils = require(\"harpoon.utils\")\nlocal Dev = require(\"harpoon.dev\")\nlocal log = Dev.log\n\nlocal config_path = vim.fn.stdpath(\"config\")\nlocal data_path = vim.fn.stdpath(\"data\")\nlocal user_config = string.format(\"%s/harpoon.json\", config_path)\nlocal cache_config = string.format(\"%s/harpoon.json\", data_path)\n\nlocal M = {}\n\nlocal the_primeagen_harpoon = vim.api.nvim_create_augroup(\n    \"THE_PRIMEAGEN_HARPOON\",\n    { clear = true }\n)\n\nvim.api.nvim_create_autocmd({ \"BufLeave\", \"VimLeave\" }, {\n    callback = function()\n        require(\"harpoon.mark\").store_offset()\n    end,\n    group = the_primeagen_harpoon,\n})\n\nvim.api.nvim_create_autocmd(\"FileType\", {\n    pattern = \"harpoon\",\n    group = the_primeagen_harpoon,\n\n    callback = function()\n        -- Open harpoon file choice in useful ways\n        --\n        -- vertical split (control+v)\n        vim.keymap.set(\"n\", \"<C-V>\", function()\n            local curline = vim.api.nvim_get_current_line()\n            local working_directory = vim.fn.getcwd() .. \"/\"\n            vim.cmd(\"vs\")\n            vim.cmd(\"e \" .. working_directory .. curline)\n        end, { buffer = true, noremap = true, silent = true })\n\n        -- horizontal split (control+x)\n        vim.keymap.set(\"n\", \"<C-x>\", function()\n            local curline = vim.api.nvim_get_current_line()\n            local working_directory = vim.fn.getcwd() .. \"/\"\n            vim.cmd(\"sp\")\n            vim.cmd(\"e \" .. working_directory .. curline)\n        end, { buffer = true, noremap = true, silent = true })\n\n        -- new tab (control+t)\n        vim.keymap.set(\"n\", \"<C-t>\", function()\n            local curline = vim.api.nvim_get_current_line()\n            local working_directory = vim.fn.getcwd() .. \"/\"\n            vim.cmd(\"tabnew\")\n            vim.cmd(\"e \" .. working_directory .. curline)\n        end, { buffer = true, noremap = true, silent = true })\n    end,\n})\n--[[\n{\n    projects = {\n        [\"/path/to/director\"] = {\n            term = {\n                cmds = {\n                }\n                ... is there anything that could be options?\n            },\n            mark = {\n                marks = {\n                }\n                ... is there anything that could be options?\n            }\n        }\n    },\n    ... high level settings\n}\n--]]\nHarpoonConfig = HarpoonConfig or {}\n\n-- tbl_deep_extend does not work the way you would think\nlocal function merge_table_impl(t1, t2)\n    for k, v in pairs(t2) do\n        if type(v) == \"table\" then\n            if type(t1[k]) == \"table\" then\n                merge_table_impl(t1[k], v)\n            else\n                t1[k] = v\n            end\n        else\n            t1[k] = v\n        end\n    end\nend\n\nlocal function mark_config_key(global_settings)\n    global_settings = global_settings or M.get_global_settings()\n    if global_settings.mark_branch then\n        return utils.branch_key()\n    else\n        return utils.project_key()\n    end\nend\n\nlocal function merge_tables(...)\n    log.trace(\"_merge_tables()\")\n    local out = {}\n    for i = 1, select(\"#\", ...) do\n        merge_table_impl(out, select(i, ...))\n    end\n    return out\nend\n\nlocal function ensure_correct_config(config)\n    log.trace(\"_ensure_correct_config()\")\n    local projects = config.projects\n    local mark_key = mark_config_key(config.global_settings)\n    if projects[mark_key] == nil then\n        log.debug(\"ensure_correct_config(): No config found for:\", mark_key)\n        projects[mark_key] = {\n            mark = { marks = {} },\n            term = {\n                cmds = {},\n            },\n        }\n    end\n\n    local proj = projects[mark_key]\n    if proj.mark == nil then\n        log.debug(\"ensure_correct_config(): No marks found for\", mark_key)\n        proj.mark = { marks = {} }\n    end\n\n    if proj.term == nil then\n        log.debug(\n            \"ensure_correct_config(): No terminal commands found for\",\n            mark_key\n        )\n        proj.term = { cmds = {} }\n    end\n\n    local marks = proj.mark.marks\n\n    for idx, mark in pairs(marks) do\n        if type(mark) == \"string\" then\n            mark = { filename = mark }\n            marks[idx] = mark\n        end\n\n        marks[idx].filename = utils.normalize_path(mark.filename)\n    end\n\n    return config\nend\n\nlocal function expand_dir(config)\n    log.trace(\"_expand_dir(): Config pre-expansion:\", config)\n\n    local projects = config.projects or {}\n    for k in pairs(projects) do\n        local expanded_path = Path.new(k):expand()\n        projects[expanded_path] = projects[k]\n        if expanded_path ~= k then\n            projects[k] = nil\n        end\n    end\n\n    log.trace(\"_expand_dir(): Config post-expansion:\", config)\n    return config\nend\n\nfunction M.save()\n    -- first refresh from disk everything but our project\n    M.refresh_projects_b4update()\n\n    log.trace(\"save(): Saving cache config to\", cache_config)\n    Path:new(cache_config):write(vim.fn.json_encode(HarpoonConfig), \"w\")\nend\n\nlocal function read_config(local_config)\n    log.trace(\"_read_config():\", local_config)\n    return vim.json.decode(Path:new(local_config):read())\nend\n\n-- 1. saved.  Where do we save?\nfunction M.setup(config)\n    log.trace(\"setup(): Setting up...\")\n\n    if not config then\n        config = {}\n    end\n\n    local ok, u_config = pcall(read_config, user_config)\n\n    if not ok then\n        log.debug(\"setup(): No user config present at\", user_config)\n        u_config = {}\n    end\n\n    local ok2, c_config = pcall(read_config, cache_config)\n\n    if not ok2 then\n        log.debug(\"setup(): No cache config present at\", cache_config)\n        c_config = {}\n    end\n\n    local complete_config = merge_tables({\n        projects = {},\n        global_settings = {\n            [\"save_on_toggle\"] = false,\n            [\"save_on_change\"] = true,\n            [\"enter_on_sendcmd\"] = false,\n            [\"tmux_autoclose_windows\"] = false,\n            [\"excluded_filetypes\"] = { \"harpoon\" },\n            [\"mark_branch\"] = false,\n            [\"tabline\"] = false,\n            [\"tabline_suffix\"] = \"   \",\n            [\"tabline_prefix\"] = \"   \",\n        },\n    }, expand_dir(c_config), expand_dir(u_config), expand_dir(config))\n\n    -- There was this issue where the vim.loop.cwd() didn't have marks or term, but had\n    -- an object for vim.loop.cwd()\n    ensure_correct_config(complete_config)\n\n    if complete_config.tabline then\n        require(\"harpoon.tabline\").setup(complete_config)\n    end\n\n    HarpoonConfig = complete_config\n\n    log.debug(\"setup(): Complete config\", HarpoonConfig)\n    log.trace(\"setup(): log_key\", Dev.get_log_key())\nend\n\nfunction M.get_global_settings()\n    log.trace(\"get_global_settings()\")\n    return HarpoonConfig.global_settings\nend\n\n-- refresh all projects from disk, except our current one\nfunction M.refresh_projects_b4update()\n    log.trace(\n        \"refresh_projects_b4update(): refreshing other projects\",\n        cache_config\n    )\n    -- save current runtime version of our project config for merging back in later\n    local cwd = mark_config_key()\n    local current_p_config = {\n        projects = {\n            [cwd] = ensure_correct_config(HarpoonConfig).projects[cwd],\n        },\n    }\n\n    -- erase all projects from global config, will be loaded back from disk\n    HarpoonConfig.projects = nil\n\n    -- this reads a stale version of our project but up-to-date versions\n    -- of all other projects\n    local ok2, c_config = pcall(read_config, cache_config)\n\n    if not ok2 then\n        log.debug(\n            \"refresh_projects_b4update(): No cache config present at\",\n            cache_config\n        )\n        c_config = { projects = {} }\n    end\n    -- don't override non-project config in HarpoonConfig later\n    c_config = { projects = c_config.projects }\n\n    -- erase our own project, will be merged in from current_p_config later\n    c_config.projects[cwd] = nil\n\n    local complete_config = merge_tables(\n        HarpoonConfig,\n        expand_dir(c_config),\n        expand_dir(current_p_config)\n    )\n\n    -- There was this issue where the vim.loop.cwd() didn't have marks or term, but had\n    -- an object for vim.loop.cwd()\n    ensure_correct_config(complete_config)\n\n    HarpoonConfig = complete_config\n    log.debug(\"refresh_projects_b4update(): Complete config\", HarpoonConfig)\n    log.trace(\"refresh_projects_b4update(): log_key\", Dev.get_log_key())\nend\n\nfunction M.get_term_config()\n    log.trace(\"get_term_config()\")\n    return ensure_correct_config(HarpoonConfig).projects[utils.project_key()].term\nend\n\nfunction M.get_mark_config()\n    log.trace(\"get_mark_config()\")\n    return ensure_correct_config(HarpoonConfig).projects[mark_config_key()].mark\nend\n\nfunction M.get_menu_config()\n    log.trace(\"get_menu_config()\")\n    return HarpoonConfig.menu or {}\nend\n\n-- should only be called for debug purposes\nfunction M.print_config()\n    print(vim.inspect(HarpoonConfig))\nend\n\n-- Sets a default config with no values\nM.setup()\n\nreturn M\n"
  },
  {
    "path": "lua/harpoon/mark.lua",
    "content": "local harpoon = require(\"harpoon\")\nlocal utils = require(\"harpoon.utils\")\nlocal log = require(\"harpoon.dev\").log\n\n-- I think that I may have to organize this better.  I am not the biggest fan\n-- of procedural all the things\nlocal M = {}\nlocal callbacks = {}\n\n-- I am trying to avoid over engineering the whole thing.  We will likely only\n-- need one event emitted\nlocal function emit_changed()\n    log.trace(\"_emit_changed()\")\n\n    local global_settings = harpoon.get_global_settings()\n\n    if global_settings.save_on_change then\n        harpoon.save()\n    end\n\n    if global_settings.tabline then\n        vim.cmd(\"redrawt\")\n    end\n\n    if not callbacks[\"changed\"] then\n        log.trace(\"_emit_changed(): no callbacks for 'changed', returning\")\n        return\n    end\n\n    for idx, cb in pairs(callbacks[\"changed\"]) do\n        log.trace(\n            string.format(\n                \"_emit_changed(): Running callback #%d for 'changed'\",\n                idx\n            )\n        )\n        cb()\n    end\nend\n\nlocal function filter_empty_string(list)\n    log.trace(\"_filter_empty_string()\")\n    local next = {}\n    for idx = 1, #list do\n        if list[idx] ~= \"\" then\n            table.insert(next, list[idx].filename)\n        end\n    end\n\n    return next\nend\n\nlocal function get_first_empty_slot()\n    log.trace(\"_get_first_empty_slot()\")\n    for idx = 1, M.get_length() do\n        local filename = M.get_marked_file_name(idx)\n        if filename == \"\" then\n            return idx\n        end\n    end\n\n    return M.get_length() + 1\nend\n\nlocal function get_buf_name(id)\n    log.trace(\"_get_buf_name():\", id)\n    if id == nil then\n        return utils.normalize_path(vim.api.nvim_buf_get_name(0))\n    elseif type(id) == \"string\" then\n        return utils.normalize_path(id)\n    end\n\n    local idx = M.get_index_of(id)\n    if M.valid_index(idx) then\n        return M.get_marked_file_name(idx)\n    end\n    --\n    -- not sure what to do here...\n    --\n    return \"\"\nend\n\nlocal function create_mark(filename)\n    local cursor_pos = vim.api.nvim_win_get_cursor(0)\n    log.trace(\n        string.format(\n            \"_create_mark(): Creating mark at row: %d, col: %d for %s\",\n            cursor_pos[1],\n            cursor_pos[2],\n            filename\n        )\n    )\n    return {\n        filename = filename,\n        row = cursor_pos[1],\n        col = cursor_pos[2],\n    }\nend\n\nlocal function mark_exists(buf_name)\n    log.trace(\"_mark_exists()\")\n    for idx = 1, M.get_length() do\n        if M.get_marked_file_name(idx) == buf_name then\n            log.debug(\"_mark_exists(): Mark exists\", buf_name)\n            return true\n        end\n    end\n\n    log.debug(\"_mark_exists(): Mark doesn't exist\", buf_name)\n    return false\nend\n\nlocal function validate_buf_name(buf_name)\n    log.trace(\"_validate_buf_name():\", buf_name)\n    if buf_name == \"\" or buf_name == nil then\n        log.error(\n            \"_validate_buf_name(): Not a valid name for a mark,\",\n            buf_name\n        )\n        error(\"Couldn't find a valid file name to mark, sorry.\")\n        return\n    end\nend\n\nlocal function filter_filetype()\n    local current_filetype = vim.bo.filetype\n    local excluded_filetypes = harpoon.get_global_settings().excluded_filetypes\n\n    if current_filetype == \"harpoon\" then\n        log.error(\"filter_filetype(): You can't add harpoon to the harpoon\")\n        error(\"You can't add harpoon to the harpoon\")\n        return\n    end\n\n    if vim.tbl_contains(excluded_filetypes, current_filetype) then\n        log.error(\n            'filter_filetype(): This filetype cannot be added or is included in the \"excluded_filetypes\" option'\n        )\n        error(\n            'This filetype cannot be added or is included in the \"excluded_filetypes\" option'\n        )\n        return\n    end\nend\n\nfunction M.get_index_of(item, marks)\n    log.trace(\"get_index_of():\", item)\n    if item == nil then\n        log.error(\n            \"get_index_of(): Function has been supplied with a nil value.\"\n        )\n        error(\n            \"You have provided a nil value to Harpoon, please provide a string rep of the file or the file idx.\"\n        )\n        return\n    end\n\n    if type(item) == \"string\" then\n        local relative_item = utils.normalize_path(item)\n        if marks == nil then\n            marks = harpoon.get_mark_config().marks\n        end\n        for idx = 1, M.get_length(marks) do\n            if marks[idx] and marks[idx].filename == relative_item then\n                return idx\n            end\n        end\n\n        return nil\n    end\n\n    -- TODO move this to a \"harpoon_\" prefix or global config?\n    if vim.g.manage_a_mark_zero_index then\n        item = item + 1\n    end\n\n    if item <= M.get_length() and item >= 1 then\n        return item\n    end\n\n    log.debug(\"get_index_of(): No item found,\", item)\n    return nil\nend\n\nfunction M.status(bufnr)\n    log.trace(\"status()\")\n    local buf_name\n    if bufnr then\n        buf_name = vim.api.nvim_buf_get_name(bufnr)\n    else\n        buf_name = vim.api.nvim_buf_get_name(0)\n    end\n\n    local norm_name = utils.normalize_path(buf_name)\n    local idx = M.get_index_of(norm_name)\n\n    if M.valid_index(idx) then\n        return \"M\" .. idx\n    end\n    return \"\"\nend\n\nfunction M.valid_index(idx, marks)\n    log.trace(\"valid_index():\", idx)\n    if idx == nil then\n        return false\n    end\n\n    local file_name = M.get_marked_file_name(idx, marks)\n    return file_name ~= nil and file_name ~= \"\"\nend\n\nfunction M.add_file(file_name_or_buf_id)\n    filter_filetype()\n    local buf_name = get_buf_name(file_name_or_buf_id)\n    log.trace(\"add_file():\", buf_name)\n\n    if M.valid_index(M.get_index_of(buf_name)) then\n        -- we don't alter file layout.\n        return\n    end\n\n    validate_buf_name(buf_name)\n\n    local found_idx = get_first_empty_slot()\n    harpoon.get_mark_config().marks[found_idx] = create_mark(buf_name)\n    M.remove_empty_tail(false)\n    emit_changed()\nend\n\n-- _emit_on_changed == false should only be used internally\nfunction M.remove_empty_tail(_emit_on_changed)\n    log.trace(\"remove_empty_tail()\")\n    _emit_on_changed = _emit_on_changed == nil or _emit_on_changed\n    local config = harpoon.get_mark_config()\n    local found = false\n\n    for i = M.get_length(), 1, -1 do\n        local filename = M.get_marked_file_name(i)\n        if filename ~= \"\" then\n            return\n        end\n\n        if filename == \"\" then\n            table.remove(config.marks, i)\n            found = found or _emit_on_changed\n        end\n    end\n\n    if found then\n        emit_changed()\n    end\nend\n\nfunction M.store_offset()\n    log.trace(\"store_offset()\")\n    local ok, res = pcall(function()\n        local marks = harpoon.get_mark_config().marks\n        local buf_name = get_buf_name()\n        local idx = M.get_index_of(buf_name, marks)\n        if not M.valid_index(idx, marks) then\n            return\n        end\n\n        local cursor_pos = vim.api.nvim_win_get_cursor(0)\n        log.debug(\n            string.format(\n                \"store_offset(): Stored row: %d, col: %d\",\n                cursor_pos[1],\n                cursor_pos[2]\n            )\n        )\n        marks[idx].row = cursor_pos[1]\n        marks[idx].col = cursor_pos[2]\n    end)\n\n    if not ok then\n        log.warn(\"store_offset(): Could not store offset:\", res)\n    end\n\n    emit_changed()\nend\n\nfunction M.rm_file(file_name_or_buf_id)\n    local buf_name = get_buf_name(file_name_or_buf_id)\n    local idx = M.get_index_of(buf_name)\n    log.trace(\"rm_file(): Removing mark at id\", idx)\n\n    if not M.valid_index(idx) then\n        log.debug(\"rm_file(): No mark exists for id\", file_name_or_buf_id)\n        return\n    end\n\n    harpoon.get_mark_config().marks[idx] = create_mark(\"\")\n    M.remove_empty_tail(false)\n    emit_changed()\nend\n\nfunction M.clear_all()\n    harpoon.get_mark_config().marks = {}\n    log.trace(\"clear_all(): Clearing all marks.\")\n    emit_changed()\nend\n\n--- ENTERPRISE PROGRAMMING\nfunction M.get_marked_file(idxOrName)\n    log.trace(\"get_marked_file():\", idxOrName)\n    if type(idxOrName) == \"string\" then\n        idxOrName = M.get_index_of(idxOrName)\n    end\n    return harpoon.get_mark_config().marks[idxOrName]\nend\n\nfunction M.get_marked_file_name(idx, marks)\n    local mark\n    if marks ~= nil then\n        mark = marks[idx]\n    else\n        mark = harpoon.get_mark_config().marks[idx]\n    end\n    log.trace(\"get_marked_file_name():\", mark and mark.filename)\n    return mark and mark.filename\nend\n\nfunction M.get_length(marks)\n    if marks == nil then\n        marks = harpoon.get_mark_config().marks\n    end\n    log.trace(\"get_length()\")\n    return table.maxn(marks)\nend\n\nfunction M.set_current_at(idx)\n    filter_filetype()\n    local buf_name = get_buf_name()\n    log.trace(\"set_current_at(): Setting id\", idx, buf_name)\n    local config = harpoon.get_mark_config()\n    local current_idx = M.get_index_of(buf_name)\n\n    -- Remove it if it already exists\n    if M.valid_index(current_idx) then\n        config.marks[current_idx] = create_mark(\"\")\n    end\n\n    config.marks[idx] = create_mark(buf_name)\n\n    for i = 1, M.get_length() do\n        if not config.marks[i] then\n            config.marks[i] = create_mark(\"\")\n        end\n    end\n\n    emit_changed()\nend\n\nfunction M.to_quickfix_list()\n    log.trace(\"to_quickfix_list(): Sending marks to quickfix list.\")\n    local config = harpoon.get_mark_config()\n    local file_list = filter_empty_string(config.marks)\n    local qf_list = {}\n    for idx = 1, #file_list do\n        local mark = M.get_marked_file(idx)\n        qf_list[idx] = {\n            text = string.format(\"%d: %s\", idx, file_list[idx]),\n            filename = mark.filename,\n            row = mark.row,\n            col = mark.col,\n        }\n    end\n    log.debug(\"to_quickfix_list(): qf_list:\", qf_list)\n    vim.fn.setqflist(qf_list)\nend\n\nfunction M.set_mark_list(new_list)\n    log.trace(\"set_mark_list(): New list:\", new_list)\n\n    local config = harpoon.get_mark_config()\n\n    for k, v in pairs(new_list) do\n        if type(v) == \"string\" then\n            local mark = M.get_marked_file(v)\n            if not mark then\n                mark = create_mark(v)\n            end\n\n            new_list[k] = mark\n        end\n    end\n\n    config.marks = new_list\n    emit_changed()\nend\n\nfunction M.toggle_file(file_name_or_buf_id)\n    local buf_name = get_buf_name(file_name_or_buf_id)\n    log.trace(\"toggle_file():\", buf_name)\n\n    validate_buf_name(buf_name)\n\n    if mark_exists(buf_name) then\n        M.rm_file(buf_name)\n        print(\"Mark removed\")\n        log.debug(\"toggle_file(): Mark removed\")\n    else\n        M.add_file(buf_name)\n        print(\"Mark added\")\n        log.debug(\"toggle_file(): Mark added\")\n    end\nend\n\nfunction M.get_current_index()\n    log.trace(\"get_current_index()\")\n    return M.get_index_of(vim.api.nvim_buf_get_name(0))\nend\n\nfunction M.on(event, cb)\n    log.trace(\"on():\", event)\n    if not callbacks[event] then\n        log.debug(\"on(): no callbacks yet for\", event)\n        callbacks[event] = {}\n    end\n\n    table.insert(callbacks[event], cb)\n    log.debug(\"on(): All callbacks:\", callbacks)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/harpoon/tabline.lua",
    "content": "local Dev = require(\"harpoon.dev\")\nlocal log = Dev.log\n\nlocal M = {}\n\nlocal function get_color(group, attr)\n    return vim.fn.synIDattr(vim.fn.synIDtrans(vim.fn.hlID(group)), attr)\nend\n\n\nlocal function shorten_filenames(filenames)\n    local shortened = {}\n\n    local counts = {}\n    for _, file in ipairs(filenames) do\n        local name = vim.fn.fnamemodify(file.filename, \":t\")\n        counts[name] = (counts[name] or 0) + 1\n    end\n\n    for _, file in ipairs(filenames) do\n        local name = vim.fn.fnamemodify(file.filename, \":t\")\n\n        if counts[name] == 1 then\n            table.insert(shortened, { filename = vim.fn.fnamemodify(name, \":t\") })\n        else\n            table.insert(shortened, { filename = file.filename })\n        end\n    end\n\n    return shortened\nend\n\nfunction M.setup(opts)\n    function _G.tabline()\n        local tabs = shorten_filenames(require('harpoon').get_mark_config().marks)\n        local tabline = ''\n\n        local index = require('harpoon.mark').get_index_of(vim.fn.bufname())\n\n        for i, tab in ipairs(tabs) do\n            local is_current = i == index\n\n            local label\n\n            if tab.filename == \"\" or tab.filename == \"(empty)\" then\n                label = \"(empty)\"\n                is_current = false\n            else\n                label = tab.filename\n            end\n\n\n            if is_current then\n                tabline = tabline ..\n                    '%#HarpoonNumberActive#' .. (opts.tabline_prefix or '   ') .. i .. ' %*' .. '%#HarpoonActive#'\n            else\n                tabline = tabline ..\n                    '%#HarpoonNumberInactive#' .. (opts.tabline_prefix or '   ') .. i .. ' %*' .. '%#HarpoonInactive#'\n            end\n\n            tabline = tabline .. label .. (opts.tabline_suffix or '   ') .. '%*'\n\n            if i < #tabs then\n                tabline = tabline .. '%T'\n            end\n        end\n\n        return tabline\n    end\n\n    vim.opt.showtabline = 2\n\n    vim.o.tabline = '%!v:lua.tabline()'\n\n    vim.api.nvim_create_autocmd(\"ColorScheme\", {\n        group = vim.api.nvim_create_augroup(\"harpoon\", { clear = true }),\n        pattern = { \"*\" },\n        callback = function()\n            local color = get_color('HarpoonActive', 'bg#')\n\n            if (color == \"\" or color == nil) then\n                vim.api.nvim_set_hl(0, \"HarpoonInactive\", { link = \"Tabline\" })\n                vim.api.nvim_set_hl(0, \"HarpoonActive\", { link = \"TablineSel\" })\n                vim.api.nvim_set_hl(0, \"HarpoonNumberActive\", { link = \"TablineSel\" })\n                vim.api.nvim_set_hl(0, \"HarpoonNumberInactive\", { link = \"Tabline\" })\n            end\n        end,\n    })\n\n    log.debug(\"setup(): Tabline Setup\", opts)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/harpoon/term.lua",
    "content": "local harpoon = require(\"harpoon\")\nlocal log = require(\"harpoon.dev\").log\nlocal global_config = harpoon.get_global_settings()\n\nlocal M = {}\nlocal terminals = {}\n\nlocal function create_terminal(create_with)\n    if not create_with then\n        create_with = \":terminal\"\n    end\n    log.trace(\"term: _create_terminal(): Init:\", create_with)\n    local current_id = vim.api.nvim_get_current_buf()\n\n    vim.cmd(create_with)\n    local buf_id = vim.api.nvim_get_current_buf()\n    local term_id = vim.b.terminal_job_id\n\n    if term_id == nil then\n        log.error(\"_create_terminal(): term_id is nil\")\n        -- TODO: Throw an error?\n        return nil\n    end\n\n    -- Make sure the term buffer has \"hidden\" set so it doesn't get thrown\n    -- away and cause an error\n    vim.api.nvim_buf_set_option(buf_id, \"bufhidden\", \"hide\")\n\n    -- Resets the buffer back to the old one\n    vim.api.nvim_set_current_buf(current_id)\n    return buf_id, term_id\nend\n\nlocal function find_terminal(args)\n    log.trace(\"term: _find_terminal(): Terminal:\", args)\n    if type(args) == \"number\" then\n        args = { idx = args }\n    end\n    local term_handle = terminals[args.idx]\n    if not term_handle or not vim.api.nvim_buf_is_valid(term_handle.buf_id) then\n        local buf_id, term_id = create_terminal(args.create_with)\n        if buf_id == nil then\n            error(\"Failed to find and create terminal.\")\n            return\n        end\n\n        term_handle = {\n            buf_id = buf_id,\n            term_id = term_id,\n        }\n        terminals[args.idx] = term_handle\n    end\n    return term_handle\nend\n\nlocal function get_first_empty_slot()\n    log.trace(\"_get_first_empty_slot()\")\n    for idx, cmd in pairs(harpoon.get_term_config().cmds) do\n        if cmd == \"\" then\n            return idx\n        end\n    end\n    return M.get_length() + 1\nend\n\nfunction M.gotoTerminal(idx)\n    log.trace(\"term: gotoTerminal(): Terminal:\", idx)\n    local term_handle = find_terminal(idx)\n\n    vim.api.nvim_set_current_buf(term_handle.buf_id)\nend\n\nfunction M.sendCommand(idx, cmd, ...)\n    log.trace(\"term: sendCommand(): Terminal:\", idx)\n    local term_handle = find_terminal(idx)\n\n    if type(cmd) == \"number\" then\n        cmd = harpoon.get_term_config().cmds[cmd]\n    end\n\n    if global_config.enter_on_sendcmd then\n        cmd = cmd .. \"\\n\"\n    end\n\n    if cmd then\n        log.debug(\"sendCommand:\", cmd)\n        vim.api.nvim_chan_send(term_handle.term_id, string.format(cmd, ...))\n    end\nend\n\nfunction M.clear_all()\n    log.trace(\"term: clear_all(): Clearing all terminals.\")\n    for _, term in ipairs(terminals) do\n        vim.api.nvim_buf_delete(term.buf_id, { force = true })\n    end\n    terminals = {}\nend\n\nfunction M.get_length()\n    log.trace(\"_get_length()\")\n    return table.maxn(harpoon.get_term_config().cmds)\nend\n\nfunction M.valid_index(idx)\n    if idx == nil or idx > M.get_length() or idx <= 0 then\n        return false\n    end\n    return true\nend\n\nfunction M.emit_changed()\n    log.trace(\"_emit_changed()\")\n    if harpoon.get_global_settings().save_on_change then\n        harpoon.save()\n    end\nend\n\nfunction M.add_cmd(cmd)\n    log.trace(\"add_cmd()\")\n    local found_idx = get_first_empty_slot()\n    harpoon.get_term_config().cmds[found_idx] = cmd\n    M.emit_changed()\nend\n\nfunction M.rm_cmd(idx)\n    log.trace(\"rm_cmd()\")\n    if not M.valid_index(idx) then\n        log.debug(\"rm_cmd(): no cmd exists for index\", idx)\n        return\n    end\n    table.remove(harpoon.get_term_config().cmds, idx)\n    M.emit_changed()\nend\n\nfunction M.set_cmd_list(new_list)\n    log.trace(\"set_cmd_list(): New list:\", new_list)\n    for k in pairs(harpoon.get_term_config().cmds) do\n        harpoon.get_term_config().cmds[k] = nil\n    end\n    for k, v in pairs(new_list) do\n        harpoon.get_term_config().cmds[k] = v\n    end\n    M.emit_changed()\nend\n\nreturn M\n"
  },
  {
    "path": "lua/harpoon/test/manage-a-mark.lua",
    "content": "-- TODO: Harpooned\n-- local Marker = require('harpoon.mark')\n-- local eq = assert.are.same\n"
  },
  {
    "path": "lua/harpoon/test/manage_cmd_spec.lua",
    "content": "local harpoon = require(\"harpoon\")\nlocal term = require(\"harpoon.term\")\n\nlocal function assert_table_equals(tbl1, tbl2)\n    if #tbl1 ~= #tbl2 then\n        assert(false, \"\" .. #tbl1 .. \" != \" .. #tbl2)\n    end\n    for i = 1, #tbl1 do\n        if tbl1[i] ~= tbl2[i] then\n            assert.equals(tbl1[i], tbl2[i])\n        end\n    end\nend\n\ndescribe(\"basic functionalities\", function()\n    local emitted\n    local cmds\n\n    before_each(function()\n        emitted = false\n        cmds = {}\n        harpoon.get_term_config = function()\n            return {\n                cmds = cmds,\n            }\n        end\n        term.emit_changed = function()\n            emitted = true\n        end\n    end)\n\n    it(\"add_cmd for empty\", function()\n        term.add_cmd(\"cmake ..\")\n        local expected_result = {\n            \"cmake ..\",\n        }\n        assert_table_equals(harpoon.get_term_config().cmds, expected_result)\n        assert.equals(emitted, true)\n    end)\n\n    it(\"add_cmd for non_empty\", function()\n        term.add_cmd(\"cmake ..\")\n        term.add_cmd(\"make\")\n        term.add_cmd(\"ninja\")\n        local expected_result = {\n            \"cmake ..\",\n            \"make\",\n            \"ninja\",\n        }\n        assert_table_equals(harpoon.get_term_config().cmds, expected_result)\n        assert.equals(emitted, true)\n    end)\n\n    it(\"rm_cmd: removing a valid element\", function()\n        term.add_cmd(\"cmake ..\")\n        term.add_cmd(\"make\")\n        term.add_cmd(\"ninja\")\n        term.rm_cmd(2)\n        local expected_result = {\n            \"cmake ..\",\n            \"ninja\",\n        }\n        assert_table_equals(harpoon.get_term_config().cmds, expected_result)\n        assert.equals(emitted, true)\n    end)\n\n    it(\"rm_cmd: remove first element\", function()\n        term.add_cmd(\"cmake ..\")\n        term.add_cmd(\"make\")\n        term.add_cmd(\"ninja\")\n        term.rm_cmd(1)\n        local expected_result = {\n            \"make\",\n            \"ninja\",\n        }\n        assert_table_equals(harpoon.get_term_config().cmds, expected_result)\n        assert.equals(emitted, true)\n    end)\n\n    it(\"rm_cmd: remove last element\", function()\n        term.add_cmd(\"cmake ..\")\n        term.add_cmd(\"make\")\n        term.add_cmd(\"ninja\")\n        term.rm_cmd(3)\n        local expected_result = {\n            \"cmake ..\",\n            \"make\",\n        }\n        assert_table_equals(harpoon.get_term_config().cmds, expected_result)\n        assert.equals(emitted, true)\n    end)\n\n    it(\"rm_cmd: trying to remove invalid element\", function()\n        term.add_cmd(\"cmake ..\")\n        term.add_cmd(\"make\")\n        term.add_cmd(\"ninja\")\n        term.rm_cmd(5)\n        local expected_result = {\n            \"cmake ..\",\n            \"make\",\n            \"ninja\",\n        }\n        assert_table_equals(harpoon.get_term_config().cmds, expected_result)\n        assert.equals(emitted, true)\n        term.rm_cmd(0)\n        assert_table_equals(harpoon.get_term_config().cmds, expected_result)\n        term.rm_cmd(-1)\n        assert_table_equals(harpoon.get_term_config().cmds, expected_result)\n    end)\n\n    it(\"get_length\", function()\n        term.add_cmd(\"cmake ..\")\n        term.add_cmd(\"make\")\n        term.add_cmd(\"ninja\")\n        assert.equals(term.get_length(), 3)\n    end)\n\n    it(\"valid_index\", function()\n        term.add_cmd(\"cmake ..\")\n        term.add_cmd(\"make\")\n        term.add_cmd(\"ninja\")\n        assert(term.valid_index(1))\n        assert(term.valid_index(2))\n        assert(term.valid_index(3))\n        assert(not term.valid_index(0))\n        assert(not term.valid_index(-1))\n        assert(not term.valid_index(4))\n    end)\n\n    it(\"set_cmd_list\", function()\n        term.add_cmd(\"cmake ..\")\n        term.add_cmd(\"make\")\n        term.add_cmd(\"ninja\")\n        term.set_cmd_list({ \"make uninstall\", \"make install\" })\n        local expected_result = {\n            \"make uninstall\",\n            \"make install\",\n        }\n        assert_table_equals(expected_result, harpoon.get_term_config().cmds)\n    end)\nend)\n"
  },
  {
    "path": "lua/harpoon/tmux.lua",
    "content": "local harpoon = require(\"harpoon\")\nlocal log = require(\"harpoon.dev\").log\nlocal global_config = harpoon.get_global_settings()\nlocal utils = require(\"harpoon.utils\")\n\nlocal M = {}\nlocal tmux_windows = {}\n\nif global_config.tmux_autoclose_windows then\n    local harpoon_tmux_group = vim.api.nvim_create_augroup(\n        \"HARPOON_TMUX\",\n        { clear = true }\n    )\n\n    vim.api.nvim_create_autocmd(\"VimLeave\", {\n        callback = function()\n            require(\"harpoon.tmux\").clear_all()\n        end,\n        group = harpoon_tmux_group,\n    })\nend\n\nlocal function create_terminal()\n    log.trace(\"tmux: _create_terminal())\")\n\n    local window_id\n\n    -- Create a new tmux window and store the window id\n    local out, ret, _ = utils.get_os_command_output({\n        \"tmux\",\n        \"new-window\",\n        \"-P\",\n        \"-F\",\n        \"#{pane_id}\",\n    }, vim.loop.cwd())\n\n    if ret == 0 then\n        window_id = out[1]:sub(2)\n    end\n\n    if window_id == nil then\n        log.error(\"tmux: _create_terminal(): window_id is nil\")\n        return nil\n    end\n\n    return window_id\nend\n\n-- Checks if the tmux window with the given window id exists\nlocal function terminal_exists(window_id)\n    log.trace(\"_terminal_exists(): Window:\", window_id)\n\n    local exists = false\n\n    local window_list, _, _ = utils.get_os_command_output({\n        \"tmux\",\n        \"list-windows\",\n    }, vim.loop.cwd())\n\n    -- This has to be done this way because tmux has-session does not give\n    -- updated results\n    for _, line in pairs(window_list) do\n        local window_info = utils.split_string(line, \"@\")[2]\n\n        if string.find(window_info, string.sub(window_id, 2)) then\n            exists = true\n        end\n    end\n\n    return exists\nend\n\nlocal function find_terminal(args)\n    log.trace(\"tmux: _find_terminal(): Window:\", args)\n\n    if type(args) == \"string\" then\n        -- assume args is a valid tmux target identifier\n        -- if invalid, the error returned by tmux will be thrown\n        return {\n            window_id = args,\n            pane = true,\n        }\n    end\n\n    if type(args) == \"number\" then\n        args = { idx = args }\n    end\n\n    local window_handle = tmux_windows[args.idx]\n    local window_exists\n\n    if window_handle then\n        window_exists = terminal_exists(window_handle.window_id)\n    end\n\n    if not window_handle or not window_exists then\n        local window_id = create_terminal()\n\n        if window_id == nil then\n            error(\"Failed to find and create tmux window.\")\n            return\n        end\n\n        window_handle = {\n            window_id = \"%\" .. window_id,\n        }\n\n        tmux_windows[args.idx] = window_handle\n    end\n\n    return window_handle\nend\n\nlocal function get_first_empty_slot()\n    log.trace(\"_get_first_empty_slot()\")\n    for idx, cmd in pairs(harpoon.get_term_config().cmds) do\n        if cmd == \"\" then\n            return idx\n        end\n    end\n    return M.get_length() + 1\nend\n\nfunction M.gotoTerminal(idx)\n    log.trace(\"tmux: gotoTerminal(): Window:\", idx)\n    local window_handle = find_terminal(idx)\n\n    local _, ret, stderr = utils.get_os_command_output({\n        \"tmux\",\n        window_handle.pane and \"select-pane\" or \"select-window\",\n        \"-t\",\n        window_handle.window_id,\n    }, vim.loop.cwd())\n\n    if ret ~= 0 then\n        error(\"Failed to go to terminal.\" .. stderr[1])\n    end\nend\n\nfunction M.sendCommand(idx, cmd, ...)\n    log.trace(\"tmux: sendCommand(): Window:\", idx)\n    local window_handle = find_terminal(idx)\n\n    if type(cmd) == \"number\" then\n        cmd = harpoon.get_term_config().cmds[cmd]\n    end\n\n    if global_config.enter_on_sendcmd then\n        cmd = cmd .. \"\\n\"\n    end\n\n    if cmd then\n        log.debug(\"sendCommand:\", cmd)\n\n        local _, ret, stderr = utils.get_os_command_output({\n            \"tmux\",\n            \"send-keys\",\n            \"-t\",\n            window_handle.window_id,\n            string.format(cmd, ...),\n        }, vim.loop.cwd())\n\n        if ret ~= 0 then\n            error(\"Failed to send command. \" .. stderr[1])\n        end\n    end\nend\n\nfunction M.clear_all()\n    log.trace(\"tmux: clear_all(): Clearing all tmux windows.\")\n\n    for _, window in pairs(tmux_windows) do\n        -- Delete the current tmux window\n        utils.get_os_command_output({\n            \"tmux\",\n            \"kill-window\",\n            \"-t\",\n            window.window_id,\n        }, vim.loop.cwd())\n    end\n\n    tmux_windows = {}\nend\n\nfunction M.get_length()\n    log.trace(\"_get_length()\")\n    return table.maxn(harpoon.get_term_config().cmds)\nend\n\nfunction M.valid_index(idx)\n    if idx == nil or idx > M.get_length() or idx <= 0 then\n        return false\n    end\n    return true\nend\n\nfunction M.emit_changed()\n    log.trace(\"_emit_changed()\")\n    if harpoon.get_global_settings().save_on_change then\n        harpoon.save()\n    end\nend\n\nfunction M.add_cmd(cmd)\n    log.trace(\"add_cmd()\")\n    local found_idx = get_first_empty_slot()\n    harpoon.get_term_config().cmds[found_idx] = cmd\n    M.emit_changed()\nend\n\nfunction M.rm_cmd(idx)\n    log.trace(\"rm_cmd()\")\n    if not M.valid_index(idx) then\n        log.debug(\"rm_cmd(): no cmd exists for index\", idx)\n        return\n    end\n    table.remove(harpoon.get_term_config().cmds, idx)\n    M.emit_changed()\nend\n\nfunction M.set_cmd_list(new_list)\n    log.trace(\"set_cmd_list(): New list:\", new_list)\n    for k in pairs(harpoon.get_term_config().cmds) do\n        harpoon.get_term_config().cmds[k] = nil\n    end\n    for k, v in pairs(new_list) do\n        harpoon.get_term_config().cmds[k] = v\n    end\n    M.emit_changed()\nend\n\nreturn M\n"
  },
  {
    "path": "lua/harpoon/ui.lua",
    "content": "local harpoon = require(\"harpoon\")\nlocal popup = require(\"plenary.popup\")\nlocal Marked = require(\"harpoon.mark\")\nlocal utils = require(\"harpoon.utils\")\nlocal log = require(\"harpoon.dev\").log\n\nlocal M = {}\n\nHarpoon_win_id = nil\nHarpoon_bufh = nil\n\n-- We save before we close because we use the state of the buffer as the list\n-- of items.\nlocal function close_menu(force_save)\n    force_save = force_save or false\n    local global_config = harpoon.get_global_settings()\n\n    if global_config.save_on_toggle or force_save then\n        require(\"harpoon.ui\").on_menu_save()\n    end\n\n    vim.api.nvim_win_close(Harpoon_win_id, true)\n\n    Harpoon_win_id = nil\n    Harpoon_bufh = nil\nend\n\nlocal function create_window()\n    log.trace(\"_create_window()\")\n    local config = harpoon.get_menu_config()\n    local width = config.width or 60\n    local height = config.height or 10\n    local borderchars = config.borderchars\n        or { \"─\", \"│\", \"─\", \"│\", \"╭\", \"╮\", \"╯\", \"╰\" }\n    local bufnr = vim.api.nvim_create_buf(false, false)\n\n    local Harpoon_win_id, win = popup.create(bufnr, {\n        title = \"Harpoon\",\n        highlight = \"HarpoonWindow\",\n        line = math.floor(((vim.o.lines - height) / 2) - 1),\n        col = math.floor((vim.o.columns - width) / 2),\n        minwidth = width,\n        minheight = height,\n        borderchars = borderchars,\n    })\n\n    vim.api.nvim_win_set_option(\n        win.border.win_id,\n        \"winhl\",\n        \"Normal:HarpoonBorder\"\n    )\n\n    return {\n        bufnr = bufnr,\n        win_id = Harpoon_win_id,\n    }\nend\n\nlocal function get_menu_items()\n    log.trace(\"_get_menu_items()\")\n    local lines = vim.api.nvim_buf_get_lines(Harpoon_bufh, 0, -1, true)\n    local indices = {}\n\n    for _, line in pairs(lines) do\n        if not utils.is_white_space(line) then\n            table.insert(indices, line)\n        end\n    end\n\n    return indices\nend\n\nfunction M.toggle_quick_menu()\n    log.trace(\"toggle_quick_menu()\")\n    if Harpoon_win_id ~= nil and vim.api.nvim_win_is_valid(Harpoon_win_id) then\n        close_menu()\n        return\n    end\n\n    local curr_file = utils.normalize_path(vim.api.nvim_buf_get_name(0))\n    vim.cmd(\n        string.format(\n            \"autocmd Filetype harpoon \"\n                .. \"let path = '%s' | call clearmatches() | \"\n                -- move the cursor to the line containing the current filename\n                .. \"call search('\\\\V'.path.'\\\\$') | \"\n                -- add a hl group to that line\n                .. \"call matchadd('HarpoonCurrentFile', '\\\\V'.path.'\\\\$')\",\n            curr_file:gsub(\"\\\\\", \"\\\\\\\\\")\n        )\n    )\n\n    local win_info = create_window()\n    local contents = {}\n    local global_config = harpoon.get_global_settings()\n\n    Harpoon_win_id = win_info.win_id\n    Harpoon_bufh = win_info.bufnr\n\n    for idx = 1, Marked.get_length() do\n        local file = Marked.get_marked_file_name(idx)\n        if file == \"\" then\n            file = \"(empty)\"\n        end\n        contents[idx] = string.format(\"%s\", file)\n    end\n\n    vim.api.nvim_win_set_option(Harpoon_win_id, \"number\", true)\n    vim.api.nvim_buf_set_name(Harpoon_bufh, \"harpoon-menu\")\n    vim.api.nvim_buf_set_lines(Harpoon_bufh, 0, #contents, false, contents)\n    vim.api.nvim_buf_set_option(Harpoon_bufh, \"filetype\", \"harpoon\")\n    vim.api.nvim_buf_set_option(Harpoon_bufh, \"buftype\", \"acwrite\")\n    vim.api.nvim_buf_set_option(Harpoon_bufh, \"bufhidden\", \"delete\")\n    vim.api.nvim_buf_set_keymap(\n        Harpoon_bufh,\n        \"n\",\n        \"q\",\n        \"<Cmd>lua require('harpoon.ui').toggle_quick_menu()<CR>\",\n        { silent = true }\n    )\n    vim.api.nvim_buf_set_keymap(\n        Harpoon_bufh,\n        \"n\",\n        \"<ESC>\",\n        \"<Cmd>lua require('harpoon.ui').toggle_quick_menu()<CR>\",\n        { silent = true }\n    )\n    vim.api.nvim_buf_set_keymap(\n        Harpoon_bufh,\n        \"n\",\n        \"<CR>\",\n        \"<Cmd>lua require('harpoon.ui').select_menu_item()<CR>\",\n        {}\n    )\n    vim.cmd(\n        string.format(\n            \"autocmd BufWriteCmd <buffer=%s> lua require('harpoon.ui').on_menu_save()\",\n            Harpoon_bufh\n        )\n    )\n    if global_config.save_on_change then\n        vim.cmd(\n            string.format(\n                \"autocmd TextChanged,TextChangedI <buffer=%s> lua require('harpoon.ui').on_menu_save()\",\n                Harpoon_bufh\n            )\n        )\n    end\n    vim.cmd(\n        string.format(\n            \"autocmd BufModifiedSet <buffer=%s> set nomodified\",\n            Harpoon_bufh\n        )\n    )\n    vim.cmd(\n        \"autocmd BufLeave <buffer> ++nested ++once silent lua require('harpoon.ui').toggle_quick_menu()\"\n    )\nend\n\nfunction M.select_menu_item()\n    local idx = vim.fn.line(\".\")\n    close_menu(true)\n    M.nav_file(idx)\nend\n\nfunction M.on_menu_save()\n    log.trace(\"on_menu_save()\")\n    Marked.set_mark_list(get_menu_items())\nend\n\nlocal function get_or_create_buffer(filename)\n    local buf_exists = vim.fn.bufexists(filename) ~= 0\n    if buf_exists then\n        return vim.fn.bufnr(filename)\n    end\n\n    return vim.fn.bufadd(filename)\nend\n\nfunction M.nav_file(id)\n    log.trace(\"nav_file(): Navigating to\", id)\n    local idx = Marked.get_index_of(id)\n    if not Marked.valid_index(idx) then\n        log.debug(\"nav_file(): No mark exists for id\", id)\n        return\n    end\n\n    local mark = Marked.get_marked_file(idx)\n    local filename = vim.fs.normalize(mark.filename)\n    local buf_id = get_or_create_buffer(filename)\n    local set_row = not vim.api.nvim_buf_is_loaded(buf_id)\n\n    local old_bufnr = vim.api.nvim_get_current_buf()\n\n    vim.api.nvim_set_current_buf(buf_id)\n    vim.api.nvim_buf_set_option(buf_id, \"buflisted\", true)\n    if set_row and mark.row and mark.col then\n        vim.api.nvim_win_set_cursor(0, { mark.row, mark.col })\n        log.debug(\n            string.format(\n                \"nav_file(): Setting cursor to row: %d, col: %d\",\n                mark.row,\n                mark.col\n            )\n        )\n    end\n\n    local old_bufinfo = vim.fn.getbufinfo(old_bufnr)\n    if type(old_bufinfo) == \"table\" and #old_bufinfo >= 1 then\n        old_bufinfo = old_bufinfo[1]\n        local no_name = old_bufinfo.name == \"\"\n        local one_line = old_bufinfo.linecount == 1\n        local unchanged = old_bufinfo.changed == 0\n        if no_name and one_line and unchanged then\n            vim.api.nvim_buf_delete(old_bufnr, {})\n        end\n    end\nend\n\nfunction M.location_window(options)\n    local default_options = {\n        relative = \"editor\",\n        style = \"minimal\",\n        width = 30,\n        height = 15,\n        row = 2,\n        col = 2,\n    }\n    options = vim.tbl_extend(\"keep\", options, default_options)\n\n    local bufnr = options.bufnr or vim.api.nvim_create_buf(false, true)\n    local win_id = vim.api.nvim_open_win(bufnr, true, options)\n\n    return {\n        bufnr = bufnr,\n        win_id = win_id,\n    }\nend\n\nfunction M.notification(text)\n    local win_stats = vim.api.nvim_list_uis()[1]\n    local win_width = win_stats.width\n\n    local prev_win = vim.api.nvim_get_current_win()\n\n    local info = M.location_window({\n        width = 20,\n        height = 2,\n        row = 1,\n        col = win_width - 21,\n    })\n\n    vim.api.nvim_buf_set_lines(\n        info.bufnr,\n        0,\n        5,\n        false,\n        { \"!!! Notification\", text }\n    )\n    vim.api.nvim_set_current_win(prev_win)\n\n    return {\n        bufnr = info.bufnr,\n        win_id = info.win_id,\n    }\nend\n\nfunction M.close_notification(bufnr)\n    vim.api.nvim_buf_delete(bufnr)\nend\n\nfunction M.nav_next()\n    log.trace(\"nav_next()\")\n    local current_index = Marked.get_current_index()\n    local number_of_items = Marked.get_length()\n\n    if current_index == nil then\n        current_index = 1\n    else\n        current_index = current_index + 1\n    end\n\n    if current_index > number_of_items then\n        current_index = 1\n    end\n    M.nav_file(current_index)\nend\n\nfunction M.nav_prev()\n    log.trace(\"nav_prev()\")\n    local current_index = Marked.get_current_index()\n    local number_of_items = Marked.get_length()\n\n    if current_index == nil then\n        current_index = number_of_items\n    else\n        current_index = current_index - 1\n    end\n\n    if current_index < 1 then\n        current_index = number_of_items\n    end\n\n    M.nav_file(current_index)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/harpoon/utils.lua",
    "content": "local Path = require(\"plenary.path\")\nlocal data_path = vim.fn.stdpath(\"data\")\nlocal Job = require(\"plenary.job\")\n\nlocal M = {}\n\nM.data_path = data_path\n\nfunction M.project_key()\n    return vim.loop.cwd()\nend\n\nfunction M.branch_key()\n    local branch\n\n    -- use tpope's fugitive for faster branch name resolution if available\n    if vim.fn.exists(\"*FugitiveHead\") == 1 then\n        branch = vim.fn[\"FugitiveHead\"]()\n        -- return \"HEAD\" for parity with `git rev-parse` in detached head state\n        if #branch == 0 then\n            branch = \"HEAD\"\n        end\n    else\n        -- `git branch --show-current` requires Git v2.22.0+ so going with more\n        -- widely available command\n        branch = M.get_os_command_output({\n            \"git\",\n            \"rev-parse\",\n            \"--abbrev-ref\",\n            \"HEAD\",\n        })[1]\n    end\n\n    if branch then\n        return vim.loop.cwd() .. \"-\" .. branch\n    else\n        return M.project_key()\n    end\nend\n\nfunction M.normalize_path(item)\n    return Path:new(item):make_relative(M.project_key())\nend\n\nfunction M.get_os_command_output(cmd, cwd)\n    if type(cmd) ~= \"table\" then\n        print(\"Harpoon: [get_os_command_output]: cmd has to be a table\")\n        return {}\n    end\n    local command = table.remove(cmd, 1)\n    local stderr = {}\n    local stdout, ret = Job\n        :new({\n            command = command,\n            args = cmd,\n            cwd = cwd,\n            on_stderr = function(_, data)\n                table.insert(stderr, data)\n            end,\n        })\n        :sync()\n    return stdout, ret, stderr\nend\n\nfunction M.split_string(str, delimiter)\n    local result = {}\n    for match in (str .. delimiter):gmatch(\"(.-)\" .. delimiter) do\n        table.insert(result, match)\n    end\n    return result\nend\n\nfunction M.is_white_space(str)\n    return str:gsub(\"%s\", \"\") == \"\"\nend\n\nreturn M\n"
  },
  {
    "path": "lua/telescope/_extensions/harpoon.lua",
    "content": "local has_telescope, telescope = pcall(require, \"telescope\")\n\nif not has_telescope then\n    error(\"harpoon.nvim requires nvim-telescope/telescope.nvim\")\nend\n\nreturn telescope.register_extension({\n    exports = {\n        marks = require(\"telescope._extensions.marks\"),\n    },\n})\n"
  },
  {
    "path": "lua/telescope/_extensions/marks.lua",
    "content": "local action_state = require(\"telescope.actions.state\")\nlocal action_utils = require(\"telescope.actions.utils\")\nlocal entry_display = require(\"telescope.pickers.entry_display\")\nlocal finders = require(\"telescope.finders\")\nlocal pickers = require(\"telescope.pickers\")\nlocal conf = require(\"telescope.config\").values\nlocal harpoon = require(\"harpoon\")\nlocal harpoon_mark = require(\"harpoon.mark\")\n\nlocal function prepare_results(list)\n    local next = {}\n    for idx = 1, #list do\n        if list[idx].filename ~= \"\" then\n            list[idx].index = idx\n            table.insert(next, list[idx])\n        end\n    end\n\n    return next\nend\n\nlocal generate_new_finder = function()\n    return finders.new_table({\n        results = prepare_results(harpoon.get_mark_config().marks),\n        entry_maker = function(entry)\n            local line = entry.filename .. \":\" .. entry.row .. \":\" .. entry.col\n            local displayer = entry_display.create({\n                separator = \" - \",\n                items = {\n                    { width = 2 },\n                    { width = 50 },\n                    { remaining = true },\n                },\n            })\n            local make_display = function()\n                return displayer({\n                    tostring(entry.index),\n                    line,\n                })\n            end\n            return {\n                value = entry,\n                ordinal = line,\n                display = make_display,\n                lnum = entry.row,\n                col = entry.col,\n                filename = entry.filename,\n            }\n        end,\n    })\nend\n\nlocal delete_harpoon_mark = function(prompt_bufnr)\n    local confirmation = vim.fn.input(\n        string.format(\"Delete current mark(s)? [y/n]: \")\n    )\n    if\n        string.len(confirmation) == 0\n        or string.sub(string.lower(confirmation), 0, 1) ~= \"y\"\n    then\n        print(string.format(\"Didn't delete mark\"))\n        return\n    end\n\n    local selection = action_state.get_selected_entry()\n    harpoon_mark.rm_file(selection.filename)\n\n    local function get_selections()\n        local results = {}\n        action_utils.map_selections(prompt_bufnr, function(entry)\n            table.insert(results, entry)\n        end)\n        return results\n    end\n\n    local selections = get_selections()\n    for _, current_selection in ipairs(selections) do\n        harpoon_mark.rm_file(current_selection.filename)\n    end\n\n    local current_picker = action_state.get_current_picker(prompt_bufnr)\n    current_picker:refresh(generate_new_finder(), { reset_prompt = true })\nend\n\nlocal move_mark_up = function(prompt_bufnr)\n    local selection = action_state.get_selected_entry()\n    local length = harpoon_mark.get_length()\n\n    if selection.index == length then\n        return\n    end\n\n    local mark_list = harpoon.get_mark_config().marks\n\n    table.remove(mark_list, selection.index)\n    table.insert(mark_list, selection.index + 1, selection.value)\n\n    local current_picker = action_state.get_current_picker(prompt_bufnr)\n    current_picker:refresh(generate_new_finder(), { reset_prompt = true })\nend\n\nlocal move_mark_down = function(prompt_bufnr)\n    local selection = action_state.get_selected_entry()\n    if selection.index == 1 then\n        return\n    end\n    local mark_list = harpoon.get_mark_config().marks\n    table.remove(mark_list, selection.index)\n    table.insert(mark_list, selection.index - 1, selection.value)\n    local current_picker = action_state.get_current_picker(prompt_bufnr)\n    current_picker:refresh(generate_new_finder(), { reset_prompt = true })\nend\n\nreturn function(opts)\n    opts = opts or {}\n\n    pickers.new(opts, {\n        prompt_title = \"harpoon marks\",\n        finder = generate_new_finder(),\n        sorter = conf.generic_sorter(opts),\n        previewer = conf.grep_previewer(opts),\n        attach_mappings = function(_, map)\n            map(\"i\", \"<c-d>\", delete_harpoon_mark)\n            map(\"n\", \"<c-d>\", delete_harpoon_mark)\n\n            map(\"i\", \"<c-p>\", move_mark_up)\n            map(\"n\", \"<c-p>\", move_mark_up)\n\n            map(\"i\", \"<c-n>\", move_mark_down)\n            map(\"n\", \"<c-n>\", move_mark_down)\n            return true\n        end,\n    }):find()\nend\n"
  },
  {
    "path": "scripts/tmux/switch-back-to-nvim",
    "content": "#!/usr/bin/env bash\n\n# Make sure tmux is running\ntmux_running=$(pgrep tmux)\n\nif [[ -z $TMUX ]] && [[ -z $tmux_running ]]; then\n    echo \"tmux needs to be running\"\n    exit 1\nfi\n\n# Switch to a window called nvim in tmux - if it exists\nsession_name=$(tmux display-message -p \"#S\")\n\ntmux switch-client -t \"$session_name:nvim\"\n"
  }
]