[
  {
    "path": "README.md",
    "content": "# 🚢 Notebook Navigator 🚢\n\nNotebook Navigator lets you manipulate and send code cells to a REPL.\n\nA great feature that comes on by default with VSCode is the ability to define\ncode cells and send them to a REPL like you would do in a Jupyter notebook but\nwithout the hassle of notebook files. Notebook Navigator brings you back that\nfunctionality and more!\n\nNotebook Navigator comes with the following functions and features:\n- Jump up/down between cells\n- Run cells (with and without jumping to the next one)\n- Create cells above/below the current one\n- Split cells\n- Comment whole cells\n- A [mini.ai](https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-ai.md) textobject\n  specification that you can use standalone\n- A [Hydra](https://github.com/anuvyklack/hydra.nvim) mode to quickly manipulate and run\n  cells\n- Code cell marker highlighting\n- Support for multiple languages. Notebooks are not just for Pythonistas!\n- ... [and more](#full-api)\n\nThis plugin also pairs really well with tools like Jupytext that allow you to\nconvert easily between `ipynb` and `py` files. For this you may want to use a\nplugin such as [jupytext.nvim](https://github.com/GCBallesteros/jupytext.nvim).\n\n![notebook-navigator](assets/notebook_navigator.gif)\n\nThis plugin is an evolution of my previous setup which you can find\n[here](https://www.maxwellrules.com/misc/nvim_jupyter.html).\n\n## What is a code cell?\nA code cell is any code between a cell marker, usually a specially designated comment\nand the next cell marker or the end of the buffer. The first line of a buffer has an\nimplicit cell marker before it.\n\nFor example here are a bunch of cells on a Python script\n```python\nprint(\"Cell 1\")\n# %%\nprint(\"This is cell 2!\")\n# %%\nprint(\"This is the last cell!\")\n```\n\n\n## Installation\nHere is my [lazy.nvim](https://www.github.com/folke/lazy.nvim) specification for Notebook\nNavigator.\n\nI personally like to have the moving between cell commands and cell executing functions\navailable through leader keymaps but will turn to the Hydra head when many cells need to\nbe run (just by smashing `x`) or for less commonly used functionality.\n```lua\n{\n  \"GCBallesteros/NotebookNavigator.nvim\",\n  keys = {\n    { \"]h\", function() require(\"notebook-navigator\").move_cell \"d\" end },\n    { \"[h\", function() require(\"notebook-navigator\").move_cell \"u\" end },\n    { \"<leader>X\", \"<cmd>lua require('notebook-navigator').run_cell()<cr>\" },\n    { \"<leader>x\", \"<cmd>lua require('notebook-navigator').run_and_move()<cr>\" },\n  },\n  dependencies = {\n    \"echasnovski/mini.comment\",\n    \"hkupty/iron.nvim\", -- repl provider\n    -- \"akinsho/toggleterm.nvim\", -- alternative repl provider\n    -- \"benlubas/molten-nvim\", -- alternative repl provider\n    \"anuvyklack/hydra.nvim\",\n  },\n  event = \"VeryLazy\",\n  config = function()\n    local nn = require \"notebook-navigator\"\n    nn.setup({ activate_hydra_keys = \"<leader>h\" })\n  end,\n}\n```\n\n## Enabling Mini.hipatterns cell highlighting\nThe lines delimiting the code cells can have pretty highlighting if you install \n[mini.hipatterns](https://github.com/echasnovski/mini.hipatterns). To activate them you will\nhave to add an entry into the `highlighters` option of 'mini.hipatterns'. If you are using\n[lazy.nvim](https//www.github.com/folke/lazy.nvim) `minihipatterns_spec` and your __only__\nconfiguration was meant to activate cell highlighting then your 'mini.hipatterns' could\nlook like:\n\n```lua\nreturn {\n  \"echasnovski/mini.hipatterns\",\n  event = \"VeryLazy\",\n  dependencies = { \"GCBallesteros/NotebookNavigator.nvim\" },\n  opts = function()\n    local nn = require \"notebook-navigator\"\n\n    local opts = { highlighters = { cells = nn.minihipatterns_spec } }\n    return opts\n  end,\n}\n```\n\nIf you are after a more simple solution that doesn't require new plugins and looks\nmore minimal just set the `syntax_highlight` option to `true`.\n\n\n## Mini.ai integration\nThe `miniai_spec` function is also a valid mini.ai textobject specification.\nAll you need to do to add it is to add the `custom_textobjects`  to your 'mini.ai' setup. If you\nare using [layz.nvim](https://www.github.com/folke/lazy.nvim) and your __only__ configuration was\nmeant include the _code cell_ text object then your 'mini.ai' could look like:\n\n```lua\nreturn {\n  \"echasnovski/mini.ai\",\n  event = \"VeryLazy\",\n  dependencies = { \"GCBallesteros/NotebookNavigator.nvim\" },\n  opts = function()\n    local nn = require \"notebook-navigator\"\n\n    local opts = { custom_textobjects = { h = nn.miniai_spec } }\n    return opts\n  end,\n}\n```\n\n\n## Detailed configuration\nAny options that are not specified when calling `setup` will take on their default values.\n```lua\n{\n  -- Code cell marker. Cells start with the marker and end either at the beginning\n  -- of the next cell or at the end of the file.\n  -- By default, uses language-specific double percent comments like `# %%`.\n  -- This can be overridden for each language with this setting.\n  cell_markers = {\n    -- python = \"# %%\",\n  },\n  -- If not `nil` the keymap defined in the string will activate the hydra head.\n  -- If you don't want to use hydra you don't need to install it either.\n  activate_hydra_keys = nil,\n  -- If `true` a hint panel will be shown when the hydra head is active. If `false`\n  -- you get a minimalistic hint on the command line.\n  show_hydra_hint = true,\n  -- Mappings while the hydra head is active.\n  -- Any of the mappings can be set to \"nil\", the string! Not the value! to unamp it\n  hydra_keys = {\n    comment = \"c\",\n    run = \"X\",\n    run_and_move = \"x\",\n    move_up = \"k\",\n    move_down = \"j\",\n    add_cell_before = \"a\",\n    add_cell_after = \"b\",\n  },\n  -- The repl plugin with which to interface\n  -- Current options: \"iron\" for iron.nvim, \"toggleterm\" for toggleterm.nvim,\n  -- \"molten\" for molten-nvim or \"auto\" which checks which of the above are \n  -- installed\n  repl_provider = \"auto\",\n  -- Syntax based highlighting. If you don't want to install mini.hipattners or\n  -- enjoy a more minimalistic look\n  syntax_highlight = false,\n  -- (Optional) for use with `mini.hipatterns` to highlight cell markers\n  cell_highlight_group = \"Folded\",\n}\n```\n\n## Current limitations\nIf any key gets remapped or unmapped to a different key you will need to set `show_hydra_hint`\nto `false`. See issue for more details.\n\n\n## Dependencies\nThe currently supported REPLs are:\n- [iron.nvim](https://github.com/Vigemus/iron.nvim),\n- [toggleterm.nvim](https://github.com/akinsho/toggleterm.nvim) or\n- [molten-nvim](https://github.com/benlubas/molten-nvim)\n\nThe latter are automatically detected. Support for others like `conjure`\nor `yarepl` may be added if people want them or are willing to send in PRs.\n\nCommenting cells of code depends on an external plugin. Either\n[comment.nvim](https://github.com/numToStr/Comment.nvim) or\n[mini.comment](https://github.com/echasnovski/mini.comment) the two most\npopular choices by quite a bit. If you want support for more PRs are welcome.\n\n'mini.ai' is not a dependency but if you want to use the provided\ntextobject specification (highly recommended) you will then need to have it\ninstalled.\n\nFinally, 'mini.hipatterns' is also not a dependency but can provide line\nhighlighting to distinguish cell markers from the rest of the text.\n\n## Yanking/Deleting cells\nIf you setup the mini.ai integration (see below) you can then do things like,\n`dah` to delete a cell, `yih` to copy just the code or `vah` to select the full\ncell in visual mode. (y)ank, (d)elete and (v)isual also work while inside the\nHydra mode!\n\n## Full API\n\nNotebookNavigator provides more functionality to manipulate cells than it is\ndirectly exposed on the Hydra mode. You may use any of those as additional\nkeymaps on the plugin configuration or even map them on they Hydra mode as long\nas you take heed of the [advice given above](#current-limitations).\n\n- `move_cell(dir)`: Move up or done a cell in the `u`p or `d`own direction.\n- `run_cell(repl_args)`: Run the current cell. You may optionally pass a table\nof `repl_args` that will be forwarded to the repl. For the details of what is\nforwarded exactly and how it is used check `repls.lua` and look for your repl\nprovider.\n- `run_and_move(repl_args)`: Same as above but also move down to the next cell.\n- `swap_cell(dir)`: Swap the current cell with the cell above (`dir='u'`) or\nbelow (`dir='d'`).\n- `run_all_cells(repl_args)`: Run all the file.\n- `run_cells_below(repl_args)`: Run the current cell and all of the ones below.\n- `comment_cell`: Comment the code inside the current cell.\n- `add_cell_below`: Add a cell marker below the current cell.\n- `add_cell_after`: Same as above (deprecated).\n- `add_cell_above`: Add a cell marker above the current cell.\n- `add_cell_before`: Same as above (deprecated).\n- `split_cell`: Add a cell marker at the current line effectively splitting the\ncell.\n- `merge_cell`: Merge the current cell ith the one above (`dir='u'`) or below\n(`dir='d'`)\n\n## Related plugins\n\n- [nvim-various-textobjs](https://github.com/chrisgrieser/nvim-various-textobjs)\nalso provides a notebook cell object that you might want to consider if you are\nnot interested on all the other functionality in NotebookNavigator.\n\n## Contributors\n\nA list of contributors can be found on `contributors.txt`.\n\nSpecial shoutout to `@cnrobertson` who contributed a significant number of\nfeatures, some of which are not reflected on the commit history.\n"
  },
  {
    "path": "contributors.txt",
    "content": "Guillem Ballesteros (@GCBallesteros)\nConnor Robertson (@cnrrobertson)\nThomas M Kehrenberg (@tmke8)\nGregory Power (@gregorywaynepower)\npseudometa (@chrisgrieser)\nTyler Baur (@TillerBurr)\n"
  },
  {
    "path": "doc/NotebookNavigator.txt",
    "content": "==============================================================================\n------------------------------------------------------------------------------\n*NotebookNavigator.doc* Easily navigate and work with code cells\n*NotebookNavigator*\n\nMIT License Copyright (c) 2023 Guillem Ballesteros\n\n==============================================================================\n\nJupyter notebooks are great for prototyping and quickly iterating on an idea\nbut the are hard to version control and track how code has been executed. Both\nissues are much less problematic when working with scripts. VSCode does this\nbetter. This plugin attempts to bring the functionality of Jupyter and VSCode\nstyle to neovim.\n\n# What is a code cell?\nA code cell is any code between a cell marker, usually a specially designated comment\nand the next cell marker or the end of the buffer. The first line of a buffer has an\nimplicit cell marker before it.\n\n# What comes bundled?~\n- Jump up/down between cells\n- Run cells (with and without jumping to the next one)\n- Create cells above/below the current one\n- Comment whole cells\n- Split cells\n- A mini.ai textobject specification that you can use standalone\n- A Hydra mode to quickly manipulate and run cells\n- Support for multiple languages\n\n# Setup~\nJust run `require(\"notebook-navigator\").setup(opts)` as you would with most Lua\nnvim packages. Any options that are left unspecified will take on their default\nvalues.\n\n------------------------------------------------------------------------------\n                                                               *M.miniai_spec()*\n                            `M.miniai_spec`({opts})\nReturns the boundaries of the current code cell.\n\nParameters~\n{opts} `(string)` Either \"i\" to select the inner lines of the cell or \"a\" for\n  the outer cell.\n\nReturn~\n`(table)` Table with keys from/to indicating the start and end of the cell.\n  The from/to fields themselves have a line and col field.\n\n------------------------------------------------------------------------------\n                                                                 *M.move_cell()*\n                              `M.move_cell`({dir})\nMove between cells\n\nMove between cells and indicate wheter we are at the first or last cell via the\nstring output.\n\nParameters~\n{dir} `(string)` Movement direction. \"d\" for down and \"u\" for up.\n\nReturn~\n`(string)` If movement failed return \"first\" or \"last\" if we where at the\n  first/last cell.\n\n------------------------------------------------------------------------------\n                                                                  *M.run_cell()*\n                           `M.run_cell`({repl_args})\nRun the current cell under the cursor\n\nParameters~\n{repl_args} `(table|nil)` Optional config for the repl.\n\n------------------------------------------------------------------------------\n                                                              *M.run_and_move()*\n                         `M.run_and_move`({repl_args})\nRun the current cell under the cursor and jump to next cell. If no next cell\nis available it will create one like Jupyter notebooks.\n\nParameters~\n{repl_args} `(table|nil)` Optional config for the repl.\n\n------------------------------------------------------------------------------\n                                                                 *M.swap_cell()*\n                              `M.swap_cell`({dir})\nSwap the current cell with the cell immediately above or below\n\nSwap cell with the above or below\n\nParameters~\n{dir} `(string)` Swap direction. \"d\" for down and \"u\" for up.\n\n------------------------------------------------------------------------------\n                                                                *M.merge_cell()*\n                             `M.merge_cell`({dir})\nMerge cell\n\nMerge cell with the above or below\n\nParameters~\n{dir} `(string)` Merge direction. \"d\" for down and \"u\" for up.\n\n------------------------------------------------------------------------------\n                                                             *M.run_all_cells()*\n                         `M.run_all_cells`({repl_args})\nRun all cells in the file\n\nParameters~\n{repl_args} `(table|nil)` Optional config for the repl.\n\n------------------------------------------------------------------------------\n                                                           *M.run_cells_below()*\n                        `M.run_cells_below`({repl_args})\nRun all cells below (including current cell)\n\nParameters~\n{repl_args} `(table|nil)` Optional config for the repl.\n\n------------------------------------------------------------------------------\n                                                              *M.comment_cell()*\n                               `M.comment_cell`()\nComment all the contents of the cell under the cursor\n\nThe commenting functionality is supported by external plugins. Currently the\nfollowing are supported:\n- mini.comment\n- comment.nvim\n\n------------------------------------------------------------------------------\n                                                            *M.add_cell_after()*\n                              `M.add_cell_after`()\n[Deprecated] Create a cell under the current one and move to it\n\n------------------------------------------------------------------------------\n                                                           *M.add_cell_before()*\n                             `M.add_cell_before`()\n[Deperecated] Create a cell on top of the current one and move to it\n\n------------------------------------------------------------------------------\n                                                            *M.add_cell_below()*\n                              `M.add_cell_below`()\nCreate a cell under the current one and move to it\n\n------------------------------------------------------------------------------\n                                                            *M.add_cell_above()*\n                              `M.add_cell_above`()\nCreate a cell on top of the current one and move to it\n\n------------------------------------------------------------------------------\n                                                                *M.split_cell()*\n                                `M.split_cell`()\nSpit the cell at the current position by inserting a cell marker\n\n------------------------------------------------------------------------------\n                                                                      *M.config*\n                                   `M.config`\nModule config\n\nDefault values:\n>\n  M.config = {\n    -- Code cell marker. Cells start with the marker and end either at the beginning\n    -- of the next cell or at the end of the file.\n    -- By default, uses language-specific double percent comments like `# %%`.\n    -- This can be overridden for each language with this setting.\n    cell_markers = {\n      -- python = \"# %%\",\n    },\n\n    -- If not `nil` the keymap defined in the string will activate the hydra head\n    activate_hydra_keys = nil,\n    -- If `true` a hint panel will be shown when the hydra head is active\n    show_hydra_hint = true,\n    -- Mappings while the hydra head is active.\n    hydra_keys = {\n      comment = \"c\",\n      run = \"X\",\n      run_and_move = \"x\",\n      move_up = \"k\",\n      move_down = \"j\",\n      add_cell_before = \"a\",\n      add_cell_after = \"b\",\n      split_cell = \"s\",\n    },\n    -- The repl plugin with which to interface\n    -- Current options: \"iron\" for iron.nvim, \"toggleterm\" for toggleterm.nvim,\n    -- \"molten\" for molten-nvim or \"auto\" which checks which of the above are \n    -- installed\n    repl_provider = \"auto\",\n    -- Syntax based highlighting. If you don't want to install mini.hipattners or\n    -- enjoy a more minimalistic look\n    syntax_highlight = false,\n    -- (Optional) for use with `mini.hipatterns` to highlight cell markers\n    cell_highlight_group = \"Folded\",\n  }\n<\n\n------------------------------------------------------------------------------\n                                                                     *M.setup()*\n                              `M.setup`({config})\nModule setup\n\nAny of the `hydra_keys` mappings can be set to `nil` in order to stop them\nfrom being mapped.\nParameters~\n{config} `(table|nil)` Module config table. See |NotebookNavigator.config|.\n\nUsage~\n`require('cell-navigator').setup({})` (replace `{}` with your `config` table)\n   any config parameter which you not pass will take on its default value.\n\n\n vim:tw=78:ts=8:noet:ft=help:norl:"
  },
  {
    "path": "doc/tags",
    "content": "M.add_cell_above()\tNotebookNavigator.txt\t/*M.add_cell_above()*\nM.add_cell_after()\tNotebookNavigator.txt\t/*M.add_cell_after()*\nM.add_cell_before()\tNotebookNavigator.txt\t/*M.add_cell_before()*\nM.add_cell_below()\tNotebookNavigator.txt\t/*M.add_cell_below()*\nM.comment_cell()\tNotebookNavigator.txt\t/*M.comment_cell()*\nM.config\tNotebookNavigator.txt\t/*M.config*\nM.merge_cell()\tNotebookNavigator.txt\t/*M.merge_cell()*\nM.miniai_spec()\tNotebookNavigator.txt\t/*M.miniai_spec()*\nM.move_cell()\tNotebookNavigator.txt\t/*M.move_cell()*\nM.run_all_cells()\tNotebookNavigator.txt\t/*M.run_all_cells()*\nM.run_and_move()\tNotebookNavigator.txt\t/*M.run_and_move()*\nM.run_cell()\tNotebookNavigator.txt\t/*M.run_cell()*\nM.run_cells_below()\tNotebookNavigator.txt\t/*M.run_cells_below()*\nM.setup()\tNotebookNavigator.txt\t/*M.setup()*\nM.split_cell()\tNotebookNavigator.txt\t/*M.split_cell()*\nM.swap_cell()\tNotebookNavigator.txt\t/*M.swap_cell()*\nNotebookNavigator\tNotebookNavigator.txt\t/*NotebookNavigator*\nNotebookNavigator.doc\tNotebookNavigator.txt\t/*NotebookNavigator.doc*\n"
  },
  {
    "path": "lua/notebook-navigator/commenters.lua",
    "content": "local commenters = {}\n\n-- comment.nvim\ncommenters.comment_nvim = function(cell_object)\n  local comment = require \"Comment.api\"\n  local curr_pos = vim.api.nvim_win_get_cursor(0)\n  local n_lines = cell_object.to.line - cell_object.from.line + 1\n\n  vim.api.nvim_win_set_cursor(0, { cell_object.from.line, 0 })\n  comment.toggle.linewise.count(n_lines)\n  vim.api.nvim_win_set_cursor(0, curr_pos)\nend\n\n-- mini.comment\ncommenters.mini_comment = function(cell_object)\n  local comment = require \"mini.comment\"\n  comment.toggle_lines(cell_object.from.line, cell_object.to.line)\nend\n\n-- no recognized comment plugin\ncommenters.no_comments = function(_)\n  vim.notify \"[Notebook Navigator] No supported comment plugin available\"\nend\n\nlocal has_mini_comment, _ = pcall(require, \"mini.comment\")\nlocal has_comment_nvim, _ = pcall(require, \"Comment.api\")\nlocal commenter\nif has_mini_comment then\n  commenter = commenters[\"mini_comment\"]\nelseif has_comment_nvim then\n  commenter = commenters[\"comment_nvim\"]\nelse\n  commenter = commenters[\"no_comments\"]\nend\n\nreturn commenter\n"
  },
  {
    "path": "lua/notebook-navigator/core.lua",
    "content": "local commenter = require \"notebook-navigator.commenters\"\nlocal get_repl = require \"notebook-navigator.repls\"\nlocal miniai_spec = require(\"notebook-navigator.miniai_spec\").miniai_spec\n\nlocal M = {}\n\nM.move_cell = function(dir, cell_marker)\n  local search_res\n  local result\n\n  if dir == \"d\" then\n    search_res = vim.fn.search(\"^\" .. cell_marker, \"W\")\n    if search_res == 0 then\n      result = \"last\"\n    end\n  else\n    search_res = vim.fn.search(\"^\" .. cell_marker, \"bW\")\n    if search_res == 0 then\n      result = \"first\"\n      vim.api.nvim_win_set_cursor(0, { 1, 0 })\n    end\n  end\n\n  return result\nend\n\nM.swap_cell = function(dir, cell_marker)\n  local buf_length = vim.api.nvim_buf_line_count(0)\n  local should_insert_marker = false\n\n  -- Get cells in their future order\n  local starting_cursor = vim.api.nvim_win_get_cursor(0)\n  local first_cell\n  local second_cell\n  if dir == \"d\" then\n    second_cell = miniai_spec(\"a\", cell_marker)\n    if second_cell.to.line + 1 > buf_length then\n      return\n    end\n    vim.api.nvim_win_set_cursor(0, { second_cell.to.line + 2, 0 })\n    first_cell = miniai_spec(\"a\", cell_marker)\n  else\n    first_cell = miniai_spec(\"a\", cell_marker)\n    if first_cell.from.line - 1 < 1 then\n      return\n    end\n    vim.api.nvim_win_set_cursor(0, { first_cell.from.line - 1, 0 })\n    second_cell = miniai_spec(\"a\", cell_marker)\n\n    -- The first cell may not have a marker. If this is the case and we attempt to\n    -- swap it down we will be in trouble. In that case we first insert a marker at\n    -- the top.\n    -- If the line does not start with the cell_marker with set a marker to add\n    -- the line later on.\n    local first_cell_line =\n      vim.api.nvim_buf_get_lines(0, second_cell.from.line - 1, second_cell.from.line, false)[1]\n\n    if string.sub(first_cell_line, 1, string.len(cell_marker)) ~= cell_marker then\n      should_insert_marker = true\n    end\n    --\n  end\n\n  -- Combine cells and set in place\n  local first_lines = vim.api.nvim_buf_get_lines(0, first_cell.from.line - 1, first_cell.to.line, false)\n  local second_lines = vim.api.nvim_buf_get_lines(0, second_cell.from.line - 1, second_cell.to.line, false)\n\n  local final_lines = {}\n\n  for _, v in ipairs(first_lines) do\n    table.insert(final_lines, v)\n  end\n\n  -- This extra marker protects us agains malformed notebooks that don't have a cell\n  -- marker at the top of the file. See the \"up\" case a few lines above.\n  if should_insert_marker then\n    table.insert(final_lines, cell_marker)\n  end\n  for _, v in ipairs(second_lines) do\n    table.insert(final_lines, v)\n  end\n  vim.api.nvim_buf_set_lines(0, second_cell.from.line - 1, first_cell.to.line, false, final_lines)\n\n  -- Put cursor in previous position\n  local new_cursor = starting_cursor\n  if dir == \"d\" then\n    new_cursor[1] = new_cursor[1] + (first_cell.to.line - first_cell.from.line + 1)\n  else\n    new_cursor[1] = new_cursor[1] - (second_cell.to.line - second_cell.from.line + 1)\n  end\n  vim.api.nvim_win_set_cursor(0, new_cursor)\nend\n\nM.run_cell = function(cell_marker, repl_provider, repl_args)\n  repl_args = repl_args or nil\n  repl_provider = repl_provider or \"auto\"\n  local cell_object = miniai_spec(\"i\", cell_marker)\n\n  -- protect ourselves against the case with no actual lines of code\n  local n_lines = cell_object.to.line - cell_object.from.line + 1\n  if n_lines < 1 then\n    return nil\n  end\n\n  local repl = get_repl(repl_provider)\n  return repl(cell_object.from.line, cell_object.to.line, repl_args, cell_marker)\nend\n\nM.run_and_move = function(cell_marker, repl_provider, repl_args)\n  local success = M.run_cell(cell_marker, repl_provider, repl_args)\n\n  if success then\n    local is_last_cell = M.move_cell(\"d\", cell_marker) == \"last\"\n\n    -- insert a new cell to replicate the behaviour of jupyter notebooks\n    if is_last_cell then\n      vim.api.nvim_buf_set_lines(0, -1, -1, false, { cell_marker, \"\" })\n      -- and move to it\n      M.move_cell(\"d\", cell_marker)\n    end\n  end\nend\n\nM.run_all_cells = function(repl_provider, repl_args)\n  local buf_length = vim.api.nvim_buf_line_count(0)\n\n  local repl = get_repl(repl_provider)\n  repl(1, buf_length, repl_args)\nend\n\nM.run_cells_below = function(cell_marker, repl_provider, repl_args)\n  local buf_length = vim.api.nvim_buf_line_count(0)\n  local cell_object = miniai_spec(\"i\", cell_marker)\n\n  local repl = get_repl(repl_provider)\n  repl(cell_object.from.line, buf_length, repl_args)\nend\n\nM.merge_cell = function(dir, cell_marker)\n  local search_res\n  local result\n\n  if dir == \"d\" then\n    search_res = vim.fn.search(\"^\" .. cell_marker, \"nW\")\n    vim.api.nvim_buf_set_lines(0, search_res - 1, search_res, false, { \"\" })\n  else\n    search_res = vim.fn.search(\"^\" .. cell_marker, \"nbW\")\n    if search_res == 0 then\n      return \"first\"\n    else\n      vim.api.nvim_buf_set_lines(0, search_res - 1, search_res, false, { \"\" })\n    end\n  end\n\n  return result\nend\n\nM.comment_cell = function(cell_marker)\n  local cell_object = miniai_spec(\"i\", cell_marker)\n\n  -- protect against empty cells\n  local n_lines = cell_object.to.line - cell_object.from.line + 1\n  if n_lines < 1 then\n    return nil\n  end\n  commenter(cell_object)\nend\n\nM.add_cell_below = function(cell_marker)\n  local cell_object = miniai_spec(\"a\", cell_marker)\n\n  vim.api.nvim_buf_set_lines(0, cell_object.to.line, cell_object.to.line, false, { cell_marker, \"\" })\n  M.move_cell(\"d\", cell_marker)\nend\n\nM.add_cell_above = function(cell_marker)\n  local cell_object = miniai_spec(\"a\", cell_marker)\n\n  -- What to do on malformed notebooks? I.e. with no upper cell marker? are they malformed?\n  -- What if we have a jupytext header? Code doesn't start at top of buffer.\n  vim.api.nvim_buf_set_lines(\n    0,\n    cell_object.from.line - 1,\n    cell_object.from.line - 1,\n    false,\n    { cell_marker, \"\" }\n  )\n  M.move_cell(\"u\", cell_marker)\nend\n\n-- We keep this two for backwards compatibility but the prefered way is to use\n-- the above/below functions for consistency with jupyter nomenclature\nM.add_cell_before = function(cell_marker)\n  M.add_cell_above(cell_marker)\nend\n\nM.add_cell_after = function(cell_marker)\n  M.add_cell_below(cell_marker)\nend\n\nM.split_cell = function(cell_marker)\n  local cursor_line = vim.api.nvim_win_get_cursor(0)[1]\n  vim.api.nvim_buf_set_lines(0, cursor_line - 1, cursor_line - 1, false, { cell_marker })\n  vim.api.nvim_win_set_cursor(0, { cursor_line + 1, 0 })\nend\n\nreturn M\n"
  },
  {
    "path": "lua/notebook-navigator/highlight.lua",
    "content": "local core = require \"notebook-navigator.core\"\nlocal utils = require \"notebook-navigator.utils\"\n\nlocal highlight = {}\n\nhighlight.minihipatterns_spec = function(cell_markers, hl_group)\n  local notebook_cells = {\n    pattern = function(buf_id)\n      local cell_marker = utils.get_cell_marker(buf_id, cell_markers)\n      if cell_marker then\n        local regex_cell_marker = string.gsub(\"^\" .. cell_marker, \"%%\", \"%%%%\")\n        return regex_cell_marker\n      else\n        return nil\n      end\n    end,\n    group = \"\",\n    extmark_opts = {\n      virt_text = {\n        {\n          \"───────────────────────────────────────────────────────────────\",\n          hl_group,\n        },\n      },\n      line_hl_group = hl_group,\n      hl_eol = true,\n    },\n  }\n  return notebook_cells\nend\n\nhighlight.setup_autocmd_syntax_highlights = function(cell_markers, hl_group)\n  vim.api.nvim_create_augroup(\"NotebookNavigator\", { clear = true })\n\n  -- Create autocmd for every language\n  for ft, marker in pairs(cell_markers) do\n    local syntax_rule = [[ /^\\s*]] .. marker .. [[.*$/]]\n    local syntax_cmd = \"syntax match CodeCell\" .. syntax_rule\n    vim.api.nvim_create_autocmd(\"FileType\", {\n      pattern = ft,\n      group = \"NotebookNavigator\",\n      command = syntax_cmd,\n    })\n  end\n  vim.api.nvim_set_hl(0, \"CodeCell\", { link = hl_group })\n  vim.api.nvim_exec_autocmds(\"FileType\", { group = \"NotebookNavigator\" })\nend\n\nreturn highlight\n"
  },
  {
    "path": "lua/notebook-navigator/init.lua",
    "content": "--- *NotebookNavigator.doc* Easily navigate and work with code cells\n--- *NotebookNavigator*\n---\n--- MIT License Copyright (c) 2023 Guillem Ballesteros\n---\n--- ==============================================================================\n---\n--- Jupyter notebooks are great for prototyping and quickly iterating on an idea\n--- but the are hard to version control and track how code has been executed. Both\n--- issues are much less problematic when working with scripts. VSCode does this\n--- better. This plugin attempts to bring the functionality of Jupyter and VSCode\n--- style to neovim.\n---\n--- # What is a code cell?\n--- A code cell is any code between a cell marker, usually a specially designated comment\n--- and the next cell marker or the end of the buffer. The first line of a buffer has an\n--- implicit cell marker before it.\n---\n--- # What comes bundled?~\n--- - Jump up/down between cells\n--- - Run cells (with and without jumping to the next one)\n--- - Create cells above/below the current one\n--- - Comment whole cells\n--- - Split cells\n--- - A mini.ai textobject specification that you can use standalone\n--- - A Hydra mode to quickly manipulate and run cells\n--- - Support for multiple languages\n---\n--- # Setup~\n--- Just run `require(\"notebook-navigator\").setup(opts)` as you would with most Lua\n--- nvim packages. Any options that are left unspecified will take on their default\n--- values.\n\nlocal M = {}\n\nlocal got_hydra, hydra = pcall(require, \"hydra\")\n\nlocal core = require \"notebook-navigator.core\"\nlocal highlight = require \"notebook-navigator.highlight\"\nlocal miniai_spec = require(\"notebook-navigator.miniai_spec\").miniai_spec\nlocal utils = require \"notebook-navigator.utils\"\n\nlocal cell_marker = function()\n  return utils.get_cell_marker(0, M.config.cell_markers)\nend\n\n-- Export directly the core functions specialized for cell markers\n\n--- Returns the boundaries of the current code cell.\n---\n---@param opts string Either \"i\" to select the inner lines of the cell or \"a\" for\n---   the outer cell.\n---\n---@return table Table with keys from/to indicating the start and end of the cell.\n---   The from/to fields themselves have a line and col field.\nM.miniai_spec = function(opts)\n  return miniai_spec(opts, cell_marker())\nend\n\n--- Move between cells\n---\n--- Move between cells and indicate wheter we are at the first or last cell via the\n--- string output.\n---\n---@param dir string Movement direction. \"d\" for down and \"u\" for up.\n---\n---@return string If movement failed return \"first\" or \"last\" if we where at the\n---   first/last cell.\nM.move_cell = function(dir)\n  return core.move_cell(dir, cell_marker())\nend\n\n--- Run the current cell under the cursor\n---\n---@param repl_args table|nil Optional config for the repl.\nM.run_cell = function(repl_args)\n  core.run_cell(cell_marker(), M.config.repl_provider, repl_args)\nend\n\n--- Run the current cell under the cursor and jump to next cell. If no next cell\n--- is available it will create one like Jupyter notebooks.\n---\n---@param repl_args table|nil Optional config for the repl.\nM.run_and_move = function(repl_args)\n  core.run_and_move(cell_marker(), M.config.repl_provider, repl_args)\nend\n\n--- Swap the current cell with the cell immediately above or below\n---\n--- Swap cell with the above or below\n---\n---@param dir string Swap direction. \"d\" for down and \"u\" for up.\nM.swap_cell = function(dir)\n  return core.swap_cell(dir, cell_marker())\nend\n\n--- Merge cell\n---\n--- Merge cell with the above or below\n---\n---@param dir string Merge direction. \"d\" for down and \"u\" for up.\nM.merge_cell = function(dir)\n  return core.merge_cell(dir, cell_marker())\nend\n\n--- Run all cells in the file\n---\n---@param repl_args table|nil Optional config for the repl.\nM.run_all_cells = function(repl_args)\n  core.run_all_cells(M.config.repl_provider, repl_args)\nend\n\n--- Run all cells below (including current cell)\n---\n---@param repl_args table|nil Optional config for the repl.\nM.run_cells_below = function(repl_args)\n  core.run_cells_below(cell_marker(), M.config.repl_provider, repl_args)\nend\n\n--- Comment all the contents of the cell under the cursor\n---\n--- The commenting functionality is supported by external plugins. Currently the\n--- following are supported:\n--- - mini.comment\n--- - comment.nvim\nM.comment_cell = function()\n  core.comment_cell(cell_marker())\nend\n\n--- [Deprecated] Create a cell under the current one and move to it\nM.add_cell_after = function()\n  core.add_cell_after(cell_marker())\nend\n\n--- [Deperecated] Create a cell on top of the current one and move to it\nM.add_cell_before = function()\n  core.add_cell_before(cell_marker())\nend\n\n--- Create a cell under the current one and move to it\nM.add_cell_below = function()\n  core.add_cell_below(cell_marker())\nend\n\n--- Create a cell on top of the current one and move to it\nM.add_cell_above = function()\n  core.add_cell_above(cell_marker())\nend\n\n--- Spit the cell at the current position by inserting a cell marker\nM.split_cell = function()\n  core.split_cell(cell_marker())\nend\n\nlocal hydra_hint = [[\n _j_/_k_: move down/up   _c_: comment     _a_/_b_: add cell above/below\n_x_: run & move down  _s_: split cell   _X_: run\n                    _<esc>_/_q_: exit\n]]\n\nlocal function activate_hydra(config)\n  -- `hydra_heads` contains all the potential actions that our hydra will be able\n  -- to execute. After these definitions the list will get filtered down by checking\n  -- if the mapped key is nil\n  local hydra_heads = {\n    {\n      config.hydra_keys.move_up,\n      function()\n        M.move_cell \"u\"\n      end,\n      { desc = \"Move up\" },\n    },\n    {\n      config.hydra_keys.move_down,\n      function()\n        M.move_cell \"d\"\n      end,\n      { desc = \"Move down\" },\n    },\n    {\n      config.hydra_keys.comment,\n      M.comment_cell,\n      { desc = \"Comment\" },\n    },\n    {\n      config.hydra_keys.run,\n      M.run_cell,\n      { desc = \"Run\", nowait = true },\n    },\n    {\n      config.hydra_keys.run_and_move,\n      M.run_and_move,\n      { desc = \"Run & Move\", nowait = true },\n    },\n    {\n      config.hydra_keys.add_cell_after,\n      M.add_cell_below,\n      { desc = \"Add cell below\", nowait = true },\n    },\n    {\n      config.hydra_keys.add_cell_before,\n      M.add_cell_above,\n      { desc = \"Add cell above\", nowait = true },\n    },\n    {\n      config.hydra_keys.split_cell,\n      M.split_cell,\n      { desc = \"Split cell\", nowait = true },\n    },\n    { \"q\", nil, { exit = true, nowait = true, desc = \"exit\" } },\n    { \"<esc>\", nil, { exit = true, nowait = true, desc = \"exit\" } },\n  }\n\n  local active_hydra_heads = {}\n  for _, h in ipairs(hydra_heads) do\n    if h[1] ~= \"nil\" then\n      table.insert(active_hydra_heads, h)\n    end\n  end\n\n  local hydra_config = {\n    name = \"NotebookNavigator\",\n    mode = { \"n\" },\n    config = {\n      invoke_on_body = true,\n      color = \"pink\",\n      hint = { border = \"rounded\" },\n    },\n    body = config.activate_hydra_keys,\n    heads = active_hydra_heads,\n  }\n  if config.show_hydra_hint then\n    hydra_config.hint = hydra_hint\n  end\n\n  hydra(hydra_config)\nend\n\n--- Module config\n---\n--- Default values:\n---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)\nM.config = {\n  -- Code cell marker. Cells start with the marker and end either at the beginning\n  -- of the next cell or at the end of the file.\n  -- By default, uses language-specific double percent comments like `# %%`.\n  -- This can be overridden for each language with this setting.\n  cell_markers = {\n    -- python = \"# %%\",\n  },\n\n  -- If not `nil` the keymap defined in the string will activate the hydra head\n  activate_hydra_keys = nil,\n  -- If `true` a hint panel will be shown when the hydra head is active\n  show_hydra_hint = true,\n  -- Mappings while the hydra head is active.\n  hydra_keys = {\n    comment = \"c\",\n    run = \"X\",\n    run_and_move = \"x\",\n    move_up = \"k\",\n    move_down = \"j\",\n    add_cell_before = \"a\",\n    add_cell_after = \"b\",\n    split_cell = \"s\",\n  },\n  -- The repl plugin with which to interface\n  -- Current options: \"iron\" for iron.nvim, \"toggleterm\" for toggleterm.nvim,\n  -- or \"auto\" which checks which of the above are installed\n  repl_provider = \"auto\",\n  -- Syntax based highlighting. If you don't want to install mini.hipattners or\n  -- enjoy a more minimalistic look\n  syntax_highlight = false,\n  -- (Optional) for use with `mini.hipatterns` to highlight cell markers\n  cell_highlight_group = \"Folded\",\n}\n--minidoc_afterlines_end\n\n--- Module setup\n---\n--- Any of the `hydra_keys` mappings can be set to `nil` in order to stop them\n--- from being mapped.\n---@param config table|nil Module config table. See |NotebookNavigator.config|.\n---\n---@usage `require('cell-navigator').setup({})` (replace `{}` with your `config` table)\n---    any config parameter which you not pass will take on its default value.\nM.setup = function(config)\n  vim.validate({ config = { config, \"table\", true } })\n  M.config = vim.tbl_deep_extend(\"force\", M.config, config or {})\n\n  vim.validate({\n    cell_markers = { M.config.cell_markers, \"table\" },\n    activate_hydra_keys = { M.config.activate_hydra_keys, \"string\", true },\n    show_hydra_hint = { M.config.show_hydra_hint, \"boolean\" },\n    hydra_keys = { M.config.hydra_keys, \"table\" },\n  })\n\n  vim.validate({\n    [\"config.hydra_keys.comment\"] = { M.config.hydra_keys.comment, \"string\" },\n    [\"config.hydra_keys.run\"] = { M.config.hydra_keys.run, \"string\" },\n    [\"config.hydra_keys.run_and_move\"] = { M.config.hydra_keys.run_and_move, \"string\" },\n    [\"config.hydra_keys.move_up\"] = { M.config.hydra_keys.move_up, \"string\" },\n    [\"config.hydra_keys.move_down\"] = { M.config.hydra_keys.move_down, \"string\" },\n    [\"config.hydra_keys.add_cell_before\"] = { M.config.hydra_keys.add_cell_before, \"string\" },\n    [\"config.hydra_keys.add_cell_after\"] = { M.config.hydra_keys.add_cell_after, \"string\" },\n  })\n\n  for ft, marker in pairs(M.config.cell_markers) do\n    vim.validate({\n      [\"config.cell_markers.\" .. ft] = { marker, \"string\" },\n    })\n  end\n\n  if (not got_hydra) and (M.config.activate_hydra_keys ~= nil) then\n    vim.notify \"[NotebookNavigator] Hydra is not available.\\nHydra will not be available.\"\n  end\n\n  if #utils.available_repls == 0 then\n    vim.notify \"[NotebookNavigator] No supported REPLs available.\\nMost functionality will error out.\"\n  elseif\n    M.config.repl_provider ~= \"auto\" and not utils.has_value(utils.available_repls, M.config.repl_provider)\n  then\n    vim.notify(\"[NotebookNavigator] The requested repl (\" .. M.config.repl_provider .. \") is not available.\")\n  end\n\n  if (M.config.activate_hydra_keys ~= nil) and got_hydra then\n    activate_hydra(M.config)\n  end\n\n  --- Highlight spec for mini.hipatterns\n  M.minihipatterns_spec = highlight.minihipatterns_spec(M.config.cell_markers, M.config.cell_highlight_group)\n\n  -- Apply syntax highlight rule for cell markers\n  if M.config.syntax_highlight and M.config.cell_highlight_group ~= nil then\n    highlight.setup_autocmd_syntax_highlights(M.config.cell_markers, M.config.cell_highlight_group)\n  end\nend\n\nreturn M\n"
  },
  {
    "path": "lua/notebook-navigator/miniai_spec.lua",
    "content": "local M = {}\n\nM.miniai_spec = function(opts, cell_marker)\n  local start_line = vim.fn.search(\"^\" .. cell_marker, \"bcnW\")\n\n  -- Just in case the notebook is malformed and doesnt  have a cell marker at the start.\n  if start_line == 0 then\n    start_line = 1\n  else\n    if opts == \"i\" then\n      start_line = start_line + 1\n    end\n  end\n\n  local end_line = vim.fn.search(\"^\" .. cell_marker, \"nW\") - 1\n  if end_line == -1 then\n    end_line = vim.fn.line \"$\"\n  end\n\n  local last_col = math.max(vim.fn.getline(end_line):len(), 1)\n\n  local from = { line = start_line, col = 1 }\n  local to = { line = end_line, col = last_col }\n\n  return { from = from, to = to }\nend\n\nreturn M\n"
  },
  {
    "path": "lua/notebook-navigator/repls.lua",
    "content": "local repls = {}\n\nlocal utils = require \"notebook-navigator.utils\"\n\n-- iron.nvim\n---@diagnostic disable-next-line: unused-local\nrepls.iron = function(start_line, end_line, repl_args, _cell_marker)\n  local lines = vim.api.nvim_buf_get_lines(0, start_line - 1, end_line, false)\n  require(\"iron.core\").send(nil, lines)\n\n  return true\nend\n\n-- toggleterm\n---@diagnostic disable-next-line: unused-local\nrepls.toggleterm = function(start_line, end_line, repl_args, cell_marker)\n  local id = 1\n  local trim_spaces = false\n  if repl_args then\n    id = repl_args.id or 1\n    trim_spaces = (repl_args.trim_spaces == nil) or repl_args.trim_spaces\n  end\n  local current_window = vim.api.nvim_get_current_win()\n  local lines = vim.api.nvim_buf_get_lines(0, start_line - 1, end_line, false)\n\n  if not lines or not next(lines) then\n    return\n  end\n\n  for _, line in ipairs(lines) do\n    local l = trim_spaces and line:gsub(\"^%s+\", \"\"):gsub(\"%s+$\", \"\") or line\n    require(\"toggleterm\").exec(l, id)\n  end\n\n  -- Jump back with the cursor where we were at the beginning of the selection\n  local cursor_line, cursor_col = unpack(vim.api.nvim_win_get_cursor(0))\n  vim.api.nvim_set_current_win(current_window)\n\n  vim.api.nvim_win_set_cursor(current_window, { cursor_line, cursor_col })\n\n  return true\nend\n\n-- molten\n---@diagnostic disable-next-line: unused-local\nrepls.molten = function(start_line, end_line, repl_args, cell_marker)\n  local line_count = vim.api.nvim_buf_line_count(0)\n\n  if line_count < (end_line + 1) then\n    vim.api.nvim_buf_set_lines(0, end_line + 1, end_line + 1, false, { cell_marker, \"\" })\n  end\n\n  local ok, _ = pcall(vim.fn.MoltenEvaluateRange, start_line, end_line + 1)\n  if not ok then\n    vim.cmd \"MoltenInit\"\n    return false\n  end\n\n  return true\nend\n\n-- no repl\nrepls.no_repl = function(_) end\n\nlocal get_repl = function(repl_provider)\n  local available_repls = utils.available_repls\n  local chosen_repl = nil\n  if repl_provider == \"auto\" then\n    for _, r in ipairs(available_repls) do\n      chosen_repl = repls[r]\n      break\n    end\n  else\n    chosen_repl = repls[repl_provider]\n  end\n\n  -- Check if we actuall got out a supported repl\n  if chosen_repl == nil then\n    vim.notify(\"[NotebookNavigator] The provided repl, \" .. repl_provider .. \", is not supported.\")\n    chosen_repl = repls[\"no_repl\"]\n  end\n\n  return chosen_repl\nend\n\nreturn get_repl\n"
  },
  {
    "path": "lua/notebook-navigator/utils.lua",
    "content": "local utils = {}\n\nutils.get_cell_marker = function(bufnr, cell_markers)\n  local ft = vim.bo[bufnr].filetype\n\n  if ft == nil or ft == \"\" then\n    print \"[NotebookNavigator] utils.lua: Empty filetype\"\n  end\n\n  local user_opt_cell_marker = cell_markers[ft]\n  if user_opt_cell_marker then\n    return user_opt_cell_marker\n  end\n\n  -- use double percent markers as default for cell markers\n  -- DOCS https://jupytext.readthedocs.io/en/latest/formats-scripts.html#the-percent-format\n  if not vim.bo.commentstring then\n    error(\"There's no cell marker and no commentstring defined for filetype \" .. ft)\n  end\n  local cstring = string.gsub(vim.bo.commentstring, \"^%%\", \"%%%%\")\n  local double_percent_cell_marker = cstring:format \"%%\"\n  return double_percent_cell_marker\nend\n\nlocal find_supported_repls = function()\n  local supported_repls = {\n    { name = \"iron\", module = \"iron\" },\n    { name = \"toggleterm\", module = \"toggleterm\" },\n    { name = \"molten\", module = \"molten.health\" },\n  }\n\n  local available_repls = {}\n  for _, repl in pairs(supported_repls) do\n    if pcall(require, repl.module) then\n      available_repls[#available_repls + 1] = repl.name\n    end\n  end\n\n  return available_repls\nend\n\nutils.available_repls = find_supported_repls()\n\nutils.has_value = function(tab, val)\n  for _, value in ipairs(tab) do\n    if value == val then\n      return true\n    end\n  end\n\n  return false\nend\n\nreturn utils\n"
  },
  {
    "path": "stylua.toml",
    "content": "indent_type = \"Spaces\"\nindent_width = 2\ncolumn_width = 110\ncall_parentheses = \"NoSingleString\"\n"
  }
]