[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ninsert_final_newline = true\nend_of_line = lf\n\n[*.lua]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Environment (please complete the following information):**\n - nvim version\n - sidebar setup\n - dotfiles (if any)\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/workflows/docgen.yaml",
    "content": "name: Generate Documentation\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - dev\n      - feat/docs\n\njobs:\n  docs:\n    runs-on: ubuntu-latest\n    name: pandoc to vimdoc\n    steps:\n      - uses: actions/checkout@v2\n      - name: panvimdoc\n        uses: kdheepak/panvimdoc@main\n        with:\n          vimdoc: sidebar\n          # Show Last Change on header\n          description: \"\"\n          # Input file\n          pandoc: \"doc/general.md\"\n          # Show Table of Content\n          toc: true\n          version: \"NVIM v0.6.0\"\n      - uses: peter-evans/create-pull-request@v3\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          author: sidebar-nvim-bot <sidebar-nvim-bot@users.noreply.github.com>\n          commit-message: \"docs: autogenerate\"\n          branch: \"docs/auto-update\"\n          delete-branch: true\n          base: \"dev\"\n          title: \"[docs]: Update documentation\"\n          body: |\n            Automated changes by the [docgen workflow](https://github.com/sidebar-nvim/sidebar.nvim/actions/workflows/docgen.yaml)\n"
  },
  {
    "path": ".github/workflows/lint.yaml",
    "content": "name: Linting & Formatting\n\non:\n  push:\n    branches: [main, dev]\n  pull_request:\n\nconcurrency: \n  group: ci-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: leafo/gh-actions-lua@master\n        with:\n          luaVersion: \"luajit-2.1.0-beta3\"\n\n      - uses: leafo/gh-actions-luarocks@v4.0.0\n\n      - name: Install linter\n        run: luarocks install luacheck\n\n      - name: Lint\n        run: luacheck lua\n\n      - uses: JohnnyMorganz/stylua-action@v3\n        with:\n         token: ${{ secrets.GITHUB_TOKEN }}\n         version: v0.20.0 # NOTE: we recommend pinning to a specific version in case of formatting changes\n         # CLI arguments\n         args: --check lua\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n\ndoc/tags\n"
  },
  {
    "path": ".luacheckrc",
    "content": "-- vim: ft=lua tw=80\n\n-- Don't report unused self arguments of methods.\nself = false\n\nignore = {\n  \"631\",  -- max_line_length\n}\n\n-- Global objects defined by the C code\nglobals = {\n  \"vim\",\n}\n"
  },
  {
    "path": "README.md",
    "content": "# sidebar.nvim\n\nA generic and modular lua sidebar inspired by [lualine](https://github.com/hoob3rt/lualine.nvim)\n\nDevelopment status: Alpha - bugs are expected\n\n![screenshot](./demo/screenshot.png)\n\n## Quick start\n\n```lua\nlocal sidebar = require(\"sidebar-nvim\")\nlocal opts = {open = true}\nsidebar.setup(opts)\n```\n\nSee [options](./doc/general.md#options) for a full list of setup options\n\n## Requirements\n\n- Neovim 0.6\n- Nerdfonts (requirement by the default icons, you can change the icons to remove this requirement)\n\n## Quick links\n\n- [Options, commands, api and colors](./doc/general.md)\n- [Builtin sections](./doc/general.md#builtin-sections)\n- [Custom sections](./doc/general.md#custom-sections)\n\n\n## TODOs (Need help)\n\n- [ ] Allow sections to define custom hl groups for icons\n- [ ] See repo issues, any contribution is really appreciated\n- [x] Better section icons - users can define custom icons see [Options](./doc/general.md#options)\n- [x] Improve docs + write vim help files - thanks @aspeddro\n\n\n## Third party sections\n\n- [dap-sidebar.nvim](https://github.com/GustavoKatel/dap-sidebar.nvim) - Show Dap breakpoints in the sidebar\n\n## Development\n\nWe are trying to limit the frequency of merges to the default branch, therefore less noise during plugin update and possible less bugs.\n\nIf you don't mind frequent updates and/or want to test new features, you might want to check the `dev` branch.\n\nBefore reporting a bug, please check if the bug still exists in the `dev` branch.\n\n## References\n\nWe based most of the code from the awesome work of @kyazdani42 in [nvim-tree](https://github.com/kyazdani42/nvim-tree.lua)\n\n## Contributors\n\n[@GustavoKatel](https://github.com/GustavoKatel/)\n[@davysson](https://github.com/davysson/)\n"
  },
  {
    "path": "doc/general.md",
    "content": "# Overview\n\nA generic and modular lua sidebar inspired by lualine\n\n# Installing\n\nYou can install sidebar using any package manager.\n\nWith [packer.nvim](https://github.com/wbthomason/packer.nvim)\n\n```lua\nuse 'sidebar-nvim/sidebar.nvim'\n```\n\n# Setup\n\nMinimal setup:\n\n```lua\nrequire(\"sidebar-nvim\").setup()\n```\n\n# Options\n\nThe following code block shows the defaults options:\n\n```lua\nrequire(\"sidebar-nvim\").setup({\n    disable_default_keybindings = 0,\n    bindings = nil,\n    open = false,\n    side = \"left\",\n    initial_width = 35,\n    hide_statusline = false,\n    update_interval = 1000,\n    sections = { \"datetime\", \"git\", \"diagnostics\" },\n    section_separator = {\"\", \"-----\", \"\"},\n    section_title_separator = {\"\"},\n    containers = {\n        attach_shell = \"/bin/sh\", show_all = true, interval = 5000,\n    },\n    datetime = { format = \"%a %b %d, %H:%M\", clocks = { { name = \"local\" } } },\n    todos = { ignored_paths = { \"~\" } },\n})\n```\n\n- `disable_default_keybindings` (number): Enable/disable the default keybindings. Default is `0`\n\n- `bindings` (function): Attach custom bindings to the sidebar buffer. Default is `nil`\n\nExample:\n\n```lua\nrequire(\"sidebar-nvim\").setup({\n    bindings = { [\"q\"] = function() require(\"sidebar-nvim\").close() end }\n})\n```\n\nNote sections can override these bindings, please see [Section Bindings](#section-bindings)\n\n- `side` (string): Side of sidebar. Default is `'left'`\n\n- `initial_width` (number): Width of sidebar. Default is `50`\n\n- `hide_statusline` (bool): Show or hide sidebar statusline. Default is `false`\n\n- `update_interval` (number): Update frequency in milliseconds. Default is `1000`\n\n- `sections` (table): Which sections should the sidebar render. Default is `{ \"datetime\", \"git\", \"diagnostics\" }`\n\nSee [Builtin Sections](#builtin-sections) and [Custom Sections](#custom-sections)\n\n- `section_separator` (string | table | function): Section separator mark, can be a string, a table or a function. Default is `{\"\", \"-----\", \"\"}`\n\n    ```lua\n    -- Using a function\n    -- It needs to return a table\n    function section_separator(section, index)\n        return { \"-----\" }\n    end\n    ```\n\n  `section` is the section definition. See [Custom Sections](#custom-sections) for more info\n\n  `index` count from the `sections` table\n\n- `section_title_separator` (string | table | function): Section title separator mark. This is rendered between the section title and the section content. It can be a string, a table or a function. Default is `{\"\"}`\n\n    ```lua\n    -- Using a function\n    -- It needs to return a table\n    function section_title_separator(section, index)\n        return { \"-----\" }\n    end\n    ```\n\n  `section` is the section definition. See [Custom Sections](#custom-sections) for more info\n\n  `index` count from the `sections` table\n\n# Lua API\n\nPublic Lua api is available as: `require(\"sidebar-nvim\").<function>`\n\n- `toggle()`: Open/close the view\n\n- `close()`: Close if open, otherwise no-op\n\n- `open()`: Open if closed, otherwise no-op\n\n- `update()`: Immediately update the view and the sections\n\n- `resize(size)`: Resize the view width to `size`\n\n  Parameters:\n\n    - `size` (number): Resize the view width\n\n- `focus()`: Move the cursor to the sidebar window\n\n- `get_width(tabpage)`: Get the current width of the view from the current `tabpage`.\n\n  Parameters:\n\n    - `tabpage` (number): is the tab page number, if null it will return the width in the current tab page\n\n- `reset_highlight()`: Use in case of errors. Clear the current highlighting so it can be re-rendered\n\n# Commands\n\n- `SidebarNvimToggle`: Open/Close the view\n- `SidebarNvimClose`: Close if open, otherwise no-op\n- `SidebarNvimOpen`: Open if closed, otherwise no-op\n- `SidebarNvimUpdate`: Immediately update the view and the sections\n- `SidebarNvimResize size`: Resize the view width to size, `size` is a number\n- `SidebarNvimFocus`: Move the cursor to the sidebar window\n\n\n# Custom Sections\n\nsidebar.nvim accepts user defined sections. The minimal section definition is a table with a `draw` function that returns the string ready to render in the sidebar and a title. See below the list of available properties\n\n```lua\nlocal section = {\n    title = \"Section Title\",\n    icon = \"->\",\n    setup = function(ctx)\n        -- called only once and if the section is being used\n    end,\n    update = function(ctx)\n        -- hook callback, called when an update was requested by either the user of external events (using autocommands)\n    end,\n    draw = function(ctx)\n        return \"> string here\\n> multiline\"\n    end,\n    highlights = {\n        groups = { MyHighlightGroup = { gui=\"#C792EA\", fg=\"#ff0000\", bg=\"#00ff00\" } },\n        links = { MyHighlightGroupLink = \"Keyword\" }\n    }\n}\n\n```\n\n## section.icon\n\nString with the icon or a function that returns a string\n\n```lua\nlocal section = {\n    icon = function()\n        return \"#\"\n    end,\n    -- or\n    -- icon = \"#\"\n}\n```\n\n## section.setup\n\nThis function is called only once *and* only if the section is being used\nYou can use this function to create timers, background jobs etc\n\n## section.update\n\nThis plugin can request the section to update its internal state by calling this function. You may use this to avoid calling expensive functions during draw.\n\nNOTE: This does not have any debouncing and it may be called multiples times, you may want to use a [debouncer](#debouncer)\n\nEvents that trigger section updates:\n\n- `BufWritePost *`\n- `VimResume *`\n- `FocusGained *`\n\n## section.draw\n\nThe function accepts a single parameter `ctx` containing the current width of the sidebar:\n\n```lua\n{ width = 90 }\n```\n\nThe draw function may appear in three forms:\n\n- Returning a string\n- Returning a table of strings\n- Returning a table like `{ lines = \"\", hl = {} }`\n\nThe later is used to specify the highlight groups related to the lines returned\n\nExample:\n\n```lua\n\nlocal section = {\n    title = \"test\",\n    draw = function()\n        return {\n            lines = {\"> item1\", \"> item2\"},\n            hl = {\n                -- { <group name>, <line index relative to the returned lines>, <column start>, <column end, -1 means end of the line> }\n                { \"SectionMarker\", 0, 0, 1 },\n            }\n        }\n    end\n}\n\n```\n\n## section.highlights\n\nSpecify the highlight groups associated with this section. This table contains two properties: `groups` and `links`\n\n- `groups` define new highlight groups\n- `links` link highlight groups to another\n\nExample:\n\n```lua\nlocal section = {\n    title = \"Custom Section\",\n    icon = \"->\",\n    draw = function()\n        return {\n            lines = {\"hello world\"},\n            hl = {\n                -- more info see `:h nvim_buf_add_highlight()`\n                { \"CustomHighlightGroupHello\", 0, 0, 5 }, -- adds `CustomHighlightGroupHello` to the word \"hello\"\n                { \"CustomHighlightGroupWorld\", 0, 6, -1 }, -- adds `CustomHighlightGroupWorld` to the word \"world\"\n            },\n        }\n    end,\n    highlights = {\n        groups = { CustomHighlightGroupHello = { gui=\"#ff0000\", fg=\"#00ff00\", bg=\"#0000ff\" } },\n        links = { CustomHighlightGroupWorld = \"Keyword\" }\n    }\n}\n```\n\nmore info see: [:h nvim_buf_add_highlight](https://neovim.io/doc/user/api.html#nvim_buf_add_highlight())\n\n## section.bindings\n\nCustom sections can define custom bindings. Bindings are dispatched to the section that the cursor is currently over.\n\nThis means that multiple sections can define the same bindings and SidebarNvim will dispatch to the correct section depending on the cursor position.\n\nExample:\n\n```lua\nlocal lines = {\"hello\", \"world\"}\nlocal section = {\n    title = \"Custom Section\",\n    icon = \"->\",\n    draw = function()\n        return lines\n    end,\n    bindings = {\n        [\"e\"] = function(line, col)\n            print(\"current word: \"..lines[line])\n        end,\n    },\n}\n```\n\n# Builtin components\n\nBuiltin components abstract ui elements that can be reused within sections.\n\n## Loclist\n\nCreate a location list with collapsable groups.\n\nSections using it: [git](#git), [diagnostics](#diagnostics) and [todos](#todos)\n\nExample:\n```lua\nlocal Loclist = require(\"sidebar-nvim.components.loclist\")\nlocal loclist = Loclist:new({\n    group_icon = { closed = \"\", opened = \"\" },\n    -- badge showing the number of items in each group\n    show_group_count = true,\n    -- if empty groups should be displayed\n    show_empty_groups = true,\n    -- if there's a single group, skip rendering the group controls\n    omit_single_group = false,\n    -- initial state of the groups\n    groups_initially_closed = false,\n    -- highlight groups for each control element\n    highlights = {\n        group = \"SidebarNvimLabel\",\n        group_count = \"SidebarNvimSectionTitle\",\n    },\n})\n\nloclist:add_item({\n    group = \"my_group\",\n    lnum = 1,\n    col = 2,\n    left = {\n        { text = \"text on the left\", hl = \"MyHighlightGroup\" }\n    },\n    right = {\n        { text = \"text on the right\", hl = \"MyHighlightGroup\" }\n    },\n    order = 1\n})\n\n-- inside the section draw function\nlocal lines, hl = {}, {}\n\ntable.insert(lines, \"Here's the location list you asked:\")\n\nloclist:draw(ctx, lines, hl)\n\nreturn { lines = lines, hl = hl }\n\n```\n\n### loclist:add_item {doc=sidebar-loclist-add-item}\n\nadds a new item to the loclist. Example: `loclist:add_item(item)`\n\nParameters:\n\n- `item.group` (string) the group name that this item will live in\n- `item.lnum` (number) the line number of this item\n- `item.col` (number) the col number of this item\n- `item.left` (table) the text that should be shown on the left of the item in the format:\n\n`item.left = { { text = \"my\", hl = \"MyHighlightGroup\" }, { text = \" text\", hl = \"MyHighlightGroup2\" } }`\n\nThis will result in `my text` in the section with the first word with highlight group `MyHighlightGroup` and the second with `MyHighlightGroup2`\n\n- `item.right` (table) same as `item.left` but shown on the right side\n- `item.order` (number) all items are sorted before drawn on the screen, use this to define each item priority\n\n### loclist:set_items {doc=sidebar-loclist-set-items}\n\nthis method receive a list of items and call [loclist:add_item](###loclist-add_item) to each one of them\n\noptionally users can pass a second parameter `clear_opts` (table) which is passed to [loclist:clear](###loclist-clear) before adding new items\n\n### loclist:clear {doc=sidebar-loclist-clear}\n\nremove all items\n\nParameters:\n\n- `clear_opts` (table)\n- `clear_opts.remove_groups` (boolean) if true, also remove groups from the list, otherwise only items will be removed, removing groups from the list also means that the state of groups will be cleared\n\n# Utils\n\n## Debouncer\n\nThis can be used to avoid multiple calls within a certain time frame. It's useful if you want to avoid multiple expensive computations in sequence.\n\nExample:\n\n```lua\nlocal Debouncer = require(\"sidebar-nvim.debouncer\")\n\nlocal function expensive_computation(n)\n    print(n + 1)\nend\n\nlocal expensive_computation_debounced = Debouncer:new(expensive_computation, 1000)\n\nexpensive_computation_debounced:call(42) -- print(43)\nexpensive_computation_debounced:call(42) -- does nothing\n\nvim.defer_fn(function()\n    expensive_computation_debounced:call(43) -- print(44)\n    expensive_computation_debounced:call(43) -- does nothing\nend, 1500)\n```\n\n# Builtin Sections\n\n## datetime\n\nPrints the current date and time using. You can define multiple clocks with different timezones or offsets.\n\nNOTE: In order to use timezones you need to install `luatz` from luarocks, like the following if using `packer`:\n```lua\nuse {\n    \"sidebar-nvim/sidebar.nvim\",\n    rocks = {'luatz'}\n}\n```\n\nThis dependency is optional, you can use the `offset` parameter to change the clock, which does not require extra dependencies.\n\n### config\n\nExample configuration:\n\n```lua\nrequire(\"sidebar-nvim\").setup({\n    ...\n    datetime = {\n        icon = \"\",\n        format = \"%a %b %d, %H:%M\",\n        clocks = {\n            { name = \"local\" }\n        }\n    }\n    ...\n})\n```\n\nClock options:\n```lua\n{\n    name = \"clock name\", -- defaults to `tz`\n    tz = \"America/Los_Angeles\", -- only works if using `luatz`, defaults to current timezone\n    offset = -8, -- this is ignored if tz is present, defaults to 0\n}\n```\n\nYou can see a list of all [available timezones here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)\n\n## git\n\nPrints the status of the repo as returned by `git status --porcelain`\n\n### config\n\nExample configuration:\n\n```lua\nrequire(\"sidebar-nvim\").setup({\n    ...\n    [\"git\"] = {\n        icon = \"\",\n    }\n    ...\n})\n```\n\n### keybindings\n\n| key | when | action |\n|-----|------|--------|\n| `e` | hovering filename | open file in the previous window\n| `s` | hovering filename | stage files\n| `u` | hovering filename | unstage files\n\n## diagnostics\n\nPrints the current status of the builtin lsp grouper by file. It shows only loaded buffers\n\n### config\n\n```lua\nrequire(\"sidebar-nvim\").setup({\n    ...\n    [\"diagnostics\"] = {\n        icon = \"\",\n    }\n    ...\n})\n```\n\n\n### keybindings\n\n| key | when | action |\n|-----|------|--------|\n| `e` | hovering diagnostic message | open file in the previous window at the diagnostic position\n| `t` | hovering filename | toggle collapse on the group\n\n## todos\n\nShows the TODOs in source. Provided by RipGrep.\n\n### config\n\n```lua\nrequire(\"sidebar-nvim\").setup({\n    ...\n    todos = {\n        icon = \"\",\n        ignored_paths = {'~'}, -- ignore certain paths, this will prevent huge folders like $HOME to hog Neovim with TODO searching\n        initially_closed = false, -- whether the groups should be initially closed on start. You can manually open/close groups later.\n    }\n    ...\n})\n```\n\n### keybindings\n\n| key | when | action |\n|-----|------|--------|\n| `e` | hovering todo location | open file in the previous window at the todo position\n| `t` | hovering the group | toggle collapse on the group\n\n### functions\n\nThe following functions are available to the user to control this specific section elements.\n\n<!-- panvimdoc renders subheading-4 differnt, so use heading-5 here instead -->\n\n##### toggle_all()\n\nToggle all groups, i.e.: NOTE, TODO, FIXME etc.\n\nCall like the following: `require(\"sidebar-nvim.builtin.todos\").<function>`\n\n##### close_all()\n\nClose all groups.\n\n##### open_all()\n\nOpen all groups.\n\n##### open(group_name)\n\nOpens the group with name `group_name`. Example `require(\"sidebar-nvim.builtin.todos\").open(\"NOTE\")`\n\n##### close(group_name)\n\nCloses the group with name `group_name`. Example `require(\"sidebar-nvim.builtin.todos\").close(\"NOTE\")`\n\n##### toggle(group_name)\n\nToggle the group with name `group_name`. Example `require(\"sidebar-nvim.builtin.todos\").toggle(\"NOTE\")`\n\n## containers\n\nShows the system docker containers. Collected from `docker ps -a '--format=\\'{\"Names\": {{json .Names}}, \"State\": {{json .State}}, \"ID\": {{json .ID}} }\\''`\n\nNOTE: in some environments this can be a very intensive command to run. You may see increased cpu usage when this section is enabled.\n\n### config\n\n```lua\nrequire(\"sidebar-nvim\").setup({\n    ...\n    containers = {\n        icon = \"\",\n        use_podman = false,\n        attach_shell = \"/bin/sh\",\n        show_all = true, -- whether to run `docker ps` or `docker ps -a`\n        interval = 5000, -- the debouncer time frame to limit requests to the docker daemon\n    }\n    ...\n})\n```\n\n### keybindings\n\n| key | when | action |\n|-----|------|--------|\n| `e` | hovering a container location | open a new terminal and attach to the container with `docker exec -it <container id> ${config.containers.attach_shell}`\n\n## buffers\n\nShows current loaded buffers.\n\n\n### config\n\n```lua\nrequire(\"sidebar-nvim\").setup({\n    ...\n    buffers = {\n        icon = \"\",\n        ignored_buffers = {}, -- ignore buffers by regex\n        sorting = \"id\", -- alternatively set it to \"name\" to sort by buffer name instead of buf id\n        show_numbers = true, -- whether to also show the buffer numbers\n        ignore_not_loaded = false, -- whether to ignore not loaded buffers\n        ignore_terminal = true, -- whether to show terminal buffers in the list\n    }\n    ...\n})\n```\n\n### keybindings\n\n| key | when | action |\n|-----|------|--------|\n| `d` | hovering an item | close the identified buffer\n| `e` | hovering an item | open the identified buffer in a window\n| `w` | hovering an item | save the identified buffer\n\n\n## files\n\nShows/manage current directory structure.\n\n\n### config\n\n```lua\nrequire(\"sidebar-nvim\").setup({\n    ...\n    files = {\n        icon = \"\",\n        show_hidden = false,\n        ignored_paths = {\"%.git$\"}\n    }\n    ...\n})\n```\n\n### keybindings\n\n| key | when | action |\n|-----|------|--------|\n| `d` | hovering an item | delete file/folder\n| `y` | hovering an item | yank/copy a file/folder\n| `x` | hovering an item | cut a file/folder\n| `p` | hovering an item | paste a file/folder\n| `c` | hovering an item | create a new file\n| `e` | hovering an item | open the current file/folder\n| `r` | hovering an item | rename file/folder\n| `u` | hovering the section | undo operation\n| `<C-r>` | hovering the section | redo operation\n| `<CR>` | hovering an item | open file/folder\n\n\n## symbols\n\nShows lsp symbols for the current buffer.\n\n\n### config\n\n```lua\nrequire(\"sidebar-nvim\").setup({\n    ...\n    symbols = {\n        icon = \"ƒ\",\n    }\n    ...\n})\n```\n\n### keybindings\n\n| key | when | action |\n|-----|------|--------|\n| `t` | hovering an item | toggle group\n| `e` | hovering an item | open location\n\n# Colors\n\n| Highlight Group | Defaults To |\n| --------------- | ----------- |\n| *SidebarNvimSectionTitle* | Directory |\n| *SidebarNvimSectionSeparator* | Comment |\n| *SidebarNvimSectionTitleSeparator* | Comment |\n| *SidebarNvimNormal* | Normal |\n| *SidebarNvimLabel* | Label |\n| *SidebarNvimComment* | Comment |\n| *SidebarNvimLineNr* | LineNr |\n| *SidebarNvimKeyword* | Keyword |\n| *SidebarNvimGitStatusState* | SidebarNvimKeyword |\n| *SidebarNvimGitStatusFileName* | SidebarNvimNormal |\n| *SidebarNvimLspDiagnosticsError* | LspDiagnosticsDefaultError |\n| *SidebarNvimLspDiagnosticsWarn* | LspDiagnosticsDefaultWarning |\n| *SidebarNvimLspDiagnosticsInfo* | LspDiagnosticsDefaultInformation |\n| *SidebarNvimLspDiagnosticsHint* | LspDiagnosticsDefaultHint |\n| *SidebarNvimLspDiagnosticsLineNumber* | SidebarNvimLineNr |\n| *SidebarNvimLspDiagnosticsColNumber* | SidebarNvimLineNr |\n| *SidebarNvimLspDiagnosticsFilename* | SidebarNvimLabel |\n| *SidebarNvimLspDiagnosticsTotalNumber* | LspTroubleCount |\n| *SidebarNvimLspDiagnosticsMessage* | SidebarNvimNormal |\n| *SidebarNvimTodoTag* | SidebarNvimLabel |\n| *SidebarNvimTodoTotalNumber* | SidebarNvimNormal |\n| *SidebarNvimTodoFilename* | SidebarNvimNormal |\n| *SidebarNvimTodoLineNumber* | SidebarNvimLineNr |\n| *SidebarNvimTodoColNumber* | SidebarNvimLineNr |\n| *SidebarNvimDockerContainerStatusRunning* | LspDiagnosticsDefaultInformation |\n| *SidebarNvimDockerContainerStatusExited* | LspDiagnosticsDefaultError |\n| *SidebarNvimDockerContainerName* | SidebarNvimNormal |\n| *SidebarNvimDatetimeClockName* | SidebarNvimComment |\n| *SidebarNvimDatetimeClockValue* | SidebarNvimNormal |\n| *SidebarNvimBuffersActive* | SidebarNvimSectionTitle |\n| *SidebarNvimBuffersNumber* | SidebarNvimComment |\n"
  },
  {
    "path": "doc/sidebar.txt",
    "content": "*sidebar.txt*             For NVIM v0.6.0            Last change: 2022 June 21\n\n==============================================================================\nTable of Contents                                  *sidebar-table-of-contents*\n\n1. Overview                                                 |sidebar-overview|\n2. Installing                                             |sidebar-installing|\n3. Setup                                                       |sidebar-setup|\n4. Options                                                   |sidebar-options|\n5. Lua API                                                   |sidebar-lua-api|\n6. Commands                                                 |sidebar-commands|\n7. Custom Sections                                   |sidebar-custom-sections|\n  - section.icon                                        |sidebar-section.icon|\n  - section.setup                                      |sidebar-section.setup|\n  - section.update                                    |sidebar-section.update|\n  - section.draw                                        |sidebar-section.draw|\n  - section.highlights                            |sidebar-section.highlights|\n  - section.bindings                                |sidebar-section.bindings|\n8. Builtin components                             |sidebar-builtin-components|\n  - Loclist                                                  |sidebar-loclist|\n9. Utils                                                       |sidebar-utils|\n  - Debouncer                                              |sidebar-debouncer|\n10. Builtin Sections                                |sidebar-builtin-sections|\n  - datetime                                                |sidebar-datetime|\n  - git                                                          |sidebar-git|\n  - diagnostics                                          |sidebar-diagnostics|\n  - todos                                                      |sidebar-todos|\n  - containers                                            |sidebar-containers|\n  - buffers                                                  |sidebar-buffers|\n  - files                                                      |sidebar-files|\n  - symbols                                                  |sidebar-symbols|\n11. Colors                                                    |sidebar-colors|\n\n==============================================================================\n1. Overview                                                 *sidebar-overview*\n\nA generic and modular lua sidebar inspired by lualine\n\n==============================================================================\n2. Installing                                             *sidebar-installing*\n\nYou can install sidebar using any package manager.\n\nWith packer.nvim <https://github.com/wbthomason/packer.nvim>\n\n>\n    use 'sidebar-nvim/sidebar.nvim'\n<\n\n\n==============================================================================\n3. Setup                                                       *sidebar-setup*\n\nMinimal setup:\n\n>\n    require(\"sidebar-nvim\").setup()\n<\n\n\n==============================================================================\n4. Options                                                   *sidebar-options*\n\nThe following code block shows the defaults options:\n\n>\n    require(\"sidebar-nvim\").setup({\n        disable_default_keybindings = 0,\n        bindings = nil,\n        open = false,\n        side = \"left\",\n        initial_width = 35,\n        hide_statusline = false,\n        update_interval = 1000,\n        sections = { \"datetime\", \"git\", \"diagnostics\" },\n        section_separator = {\"\", \"-----\", \"\"},\n        section_title_separator = {\"\"},\n        containers = {\n            attach_shell = \"/bin/sh\", show_all = true, interval = 5000,\n        },\n        datetime = { format = \"%a %b %d, %H:%M\", clocks = { { name = \"local\" } } },\n        todos = { ignored_paths = { \"~\" } },\n    })\n<\n\n\n\n- `disable_default_keybindings` (number): Enable/disable the default keybindings.\n    Default is `0`\n- `bindings` (function): Attach custom bindings to the sidebar buffer. Default is\n    `nil`\n\n\nExample:\n\n>\n    require(\"sidebar-nvim\").setup({\n        bindings = { [\"q\"] = function() require(\"sidebar-nvim\").close() end }\n    })\n<\n\n\nNote sections can override these bindings, please see\n|sidebar-section-bindings|\n\n\n- `side` (string): Side of sidebar. Default is `'left'`\n- `initial_width` (number): Width of sidebar. Default is `50`\n- `hide_statusline` (bool): Show or hide sidebar statusline. Default is `false`\n- `update_interval` (number): Update frequency in milliseconds. Default is `1000`\n- `sections` (table): Which sections should the sidebar render. Default is `{\n    \"datetime\", \"git\", \"diagnostics\" }`\n\n\nSee |sidebar-builtin-sections| and |sidebar-custom-sections|\n\n\n- `section_separator` (string | table | function): Section separator mark, can be\n    a string, a table or a function. Default is `{\"\", \"-----\", \"\"}`\n    >\n        -- Using a function\n        -- It needs to return a table\n        function section_separator(section, index)\n            return { \"-----\" }\n        end\n    <\n    `section` is the section definition. See |sidebar-custom-sections| for more\n    info\n    `index` count from the `sections` table\n- `section_title_separator` (string | table | function): Section title separator\n    mark. This is rendered between the section title and the section content. It\n    can be a string, a table or a function. Default is `{\"\"}`\n    >\n        -- Using a function\n        -- It needs to return a table\n        function section_title_separator(section, index)\n            return { \"-----\" }\n        end\n    <\n    `section` is the section definition. See |sidebar-custom-sections| for more\n    info\n    `index` count from the `sections` table\n\n\n==============================================================================\n5. Lua API                                                   *sidebar-lua-api*\n\nPublic Lua api is available as: `require(\"sidebar-nvim\").<function>`\n\n\n- `toggle()`: Open/close the view\n- `close()`: Close if open, otherwise no-op\n- `open()`: Open if closed, otherwise no-op\n- `update()`: Immediately update the view and the sections\n- `resize(size)`: Resize the view width to `size`\n    Parameters:\n    - `size` (number): Resize the view width\n- `focus()`: Move the cursor to the sidebar window\n- `get_width(tabpage)`: Get the current width of the view from the current\n    `tabpage`.\n    Parameters:\n    - `tabpage` (number): is the tab page number, if null it will return the width in the current tab page\n- `reset_highlight()`: Use in case of errors. Clear the current highlighting so\n    it can be re-rendered\n\n\n==============================================================================\n6. Commands                                                 *sidebar-commands*\n\n\n- `SidebarNvimToggle`: Open/Close the view\n- `SidebarNvimClose`: Close if open, otherwise no-op\n- `SidebarNvimOpen`: Open if closed, otherwise no-op\n- `SidebarNvimUpdate`: Immediately update the view and the sections\n- `SidebarNvimResize size`: Resize the view width to size, `size` is a number\n- `SidebarNvimFocus`: Move the cursor to the sidebar window\n\n\n==============================================================================\n7. Custom Sections                                   *sidebar-custom-sections*\n\nsidebar.nvim accepts user defined sections. The minimal section definition is a\ntable with a `draw` function that returns the string ready to render in the\nsidebar and a title. See below the list of available properties\n\n>\n    local section = {\n        title = \"Section Title\",\n        icon = \"->\",\n        setup = function(ctx)\n            -- called only once and if the section is being used\n        end,\n        update = function(ctx)\n            -- hook callback, called when an update was requested by either the user of external events (using autocommands)\n        end,\n        draw = function(ctx)\n            return \"> string here\\n> multiline\"\n        end,\n        highlights = {\n            groups = { MyHighlightGroup = { gui=\"#C792EA\", fg=\"#ff0000\", bg=\"#00ff00\" } },\n            links = { MyHighlightGroupLink = \"Keyword\" }\n        }\n    }\n<\n\n\nSECTION.ICON                                            *sidebar-section.icon*\n\nString with the icon or a function that returns a string\n\n>\n    local section = {\n        icon = function()\n            return \"#\"\n        end,\n        -- or\n        -- icon = \"#\"\n    }\n<\n\n\nSECTION.SETUP                                          *sidebar-section.setup*\n\nThis function is called only once _and_ only if the section is being used You\ncan use this function to create timers, background jobs etc\n\nSECTION.UPDATE                                        *sidebar-section.update*\n\nThis plugin can request the section to update its internal state by calling\nthis function. You may use this to avoid calling expensive functions during\ndraw.\n\nNOTE: This does not have any debouncing and it may be called multiples times,\nyou may want to use a |sidebar-debouncer|\n\nEvents that trigger section updates:\n\n\n- `BufWritePost *`\n- `VimResume *`\n- `FocusGained *`\n\n\nSECTION.DRAW                                            *sidebar-section.draw*\n\nThe function accepts a single parameter `ctx` containing the current width of\nthe sidebar:\n\n>\n    { width = 90 }\n<\n\n\nThe draw function may appear in three forms:\n\n\n- Returning a string\n- Returning a table of strings\n- Returning a table like `{ lines = \"\", hl = {} }`\n\n\nThe later is used to specify the highlight groups related to the lines returned\n\nExample:\n\n>\n    \n    local section = {\n        title = \"test\",\n        draw = function()\n            return {\n                lines = {\"> item1\", \"> item2\"},\n                hl = {\n                    -- { <group name>, <line index relative to the returned lines>, <column start>, <column end, -1 means end of the line> }\n                    { \"SectionMarker\", 0, 0, 1 },\n                }\n            }\n        end\n    }\n<\n\n\nSECTION.HIGHLIGHTS                                *sidebar-section.highlights*\n\nSpecify the highlight groups associated with this section. This table contains\ntwo properties: `groups` and `links`\n\n\n- `groups` define new highlight groups\n- `links` link highlight groups to another\n\n\nExample:\n\n>\n    local section = {\n        title = \"Custom Section\",\n        icon = \"->\",\n        draw = function()\n            return {\n                lines = {\"hello world\"},\n                hl = {\n                    -- more info see `:h nvim_buf_add_highlight()`\n                    { \"CustomHighlightGroupHello\", 0, 0, 5 }, -- adds `CustomHighlightGroupHello` to the word \"hello\"\n                    { \"CustomHighlightGroupWorld\", 0, 6, -1 }, -- adds `CustomHighlightGroupWorld` to the word \"world\"\n                },\n            }\n        end,\n        highlights = {\n            groups = { CustomHighlightGroupHello = { gui=\"#ff0000\", fg=\"#00ff00\", bg=\"#0000ff\" } },\n            links = { CustomHighlightGroupWorld = \"Keyword\" }\n        }\n    }\n<\n\n\nmore info see: |:h nvim_buf_add_highlight|\n\nSECTION.BINDINGS                                    *sidebar-section.bindings*\n\nCustom sections can define custom bindings. Bindings are dispatched to the\nsection that the cursor is currently over.\n\nThis means that multiple sections can define the same bindings and SidebarNvim\nwill dispatch to the correct section depending on the cursor position.\n\nExample:\n\n>\n    local lines = {\"hello\", \"world\"}\n    local section = {\n        title = \"Custom Section\",\n        icon = \"->\",\n        draw = function()\n            return lines\n        end,\n        bindings = {\n            [\"e\"] = function(line, col)\n                print(\"current word: \"..lines[line])\n            end,\n        },\n    }\n<\n\n\n==============================================================================\n8. Builtin components                             *sidebar-builtin-components*\n\nBuiltin components abstract ui elements that can be reused within sections.\n\nLOCLIST                                                      *sidebar-loclist*\n\nCreate a location list with collapsable groups.\n\nSections using it: |sidebar-git|, |sidebar-diagnostics| and |sidebar-todos|\n\nExample:\n\n>\n    local Loclist = require(\"sidebar-nvim.components.loclist\")\n    local loclist = Loclist:new({\n        group_icon = { closed = \"\", opened = \"\" },\n        -- badge showing the number of items in each group\n        show_group_count = true,\n        -- if empty groups should be displayed\n        show_empty_groups = true,\n        -- if there's a single group, skip rendering the group controls\n        omit_single_group = false,\n        -- initial state of the groups\n        groups_initially_closed = false,\n        -- highlight groups for each control element\n        highlights = {\n            group = \"SidebarNvimLabel\",\n            group_count = \"SidebarNvimSectionTitle\",\n        },\n    })\n    \n    loclist:add_item({\n        group = \"my_group\",\n        lnum = 1,\n        col = 2,\n        left = {\n            { text = \"text on the left\", hl = \"MyHighlightGroup\" }\n        },\n        right = {\n            { text = \"text on the right\", hl = \"MyHighlightGroup\" }\n        },\n        order = 1\n    })\n    \n    -- inside the section draw function\n    local lines, hl = {}, {}\n    \n    table.insert(lines, \"Here's the location list you asked:\")\n    \n    loclist:draw(ctx, lines, hl)\n    \n    return { lines = lines, hl = hl }\n<\n\n\nLOCLIST:ADD_ITEM ~\n\nadds a new item to the loclist. Example: `loclist:add_item(item)`\n\nParameters:\n\n\n- `item.group` (string) the group name that this item will live in\n- `item.lnum` (number) the line number of this item\n- `item.col` (number) the col number of this item\n- `item.left` (table) the text that should be shown on the left of the item in the format:\n\n\n`item.left = { { text = \"my\", hl = \"MyHighlightGroup\" }, { text = \" text\", hl =\n\"MyHighlightGroup2\" } }`\n\nThis will result in `my text` in the section with the first word with highlight\ngroup `MyHighlightGroup` and the second with `MyHighlightGroup2`\n\n\n- `item.right` (table) same as `item.left` but shown on the right side\n- `item.order` (number) all items are sorted before drawn on the screen, use this to define each item priority\n\n\nLOCLIST:SET_ITEMS ~\n\nthis method receive a list of items and call |sidebar-loclist:add_item| to each\none of them\n\noptionally users can pass a second parameter `clear_opts` (table) which is\npassed to |sidebar-loclist:clear| before adding new items\n\nLOCLIST:CLEAR ~\n\nremove all items\n\nParameters:\n\n\n- `clear_opts` (table)\n- `clear_opts.remove_groups` (boolean) if true, also remove groups from the list, otherwise only items will be removed, removing groups from the list also means that the state of groups will be cleared\n\n\n==============================================================================\n9. Utils                                                       *sidebar-utils*\n\nDEBOUNCER                                                  *sidebar-debouncer*\n\nThis can be used to avoid multiple calls within a certain time frame. It’s\nuseful if you want to avoid multiple expensive computations in sequence.\n\nExample:\n\n>\n    local Debouncer = require(\"sidebar-nvim.debouncer\")\n    \n    local function expensive_computation(n)\n        print(n + 1)\n    end\n    \n    local expensive_computation_debounced = Debouncer:new(expensive_computation, 1000)\n    \n    expensive_computation_debounced:call(42) -- print(43)\n    expensive_computation_debounced:call(42) -- does nothing\n    \n    vim.defer_fn(function()\n        expensive_computation_debounced:call(43) -- print(44)\n        expensive_computation_debounced:call(43) -- does nothing\n    end, 1500)\n<\n\n\n==============================================================================\n10. Builtin Sections                                *sidebar-builtin-sections*\n\nDATETIME                                                    *sidebar-datetime*\n\nPrints the current date and time using. You can define multiple clocks with\ndifferent timezones or offsets.\n\nNOTE: In order to use timezones you need to install `luatz` from luarocks, like\nthe following if using `packer`:\n\n>\n    use {\n        \"sidebar-nvim/sidebar.nvim\",\n        rocks = {'luatz'}\n    }\n<\n\n\nThis dependency is optional, you can use the `offset` parameter to change the\nclock, which does not require extra dependencies.\n\nCONFIG ~\n\nExample configuration:\n\n>\n    require(\"sidebar-nvim\").setup({\n        ...\n        datetime = {\n            icon = \"\",\n            format = \"%a %b %d, %H:%M\",\n            clocks = {\n                { name = \"local\" }\n            }\n        }\n        ...\n    })\n<\n\n\nClock options:\n\n>\n    {\n        name = \"clock name\", -- defaults to `tz`\n        tz = \"America/Los_Angeles\", -- only works if using `luatz`, defaults to current timezone\n        offset = -8, -- this is ignored if tz is present, defaults to 0\n    }\n<\n\n\nYou can see a list of all available timezones here\n<https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>\n\nGIT                                                              *sidebar-git*\n\nPrints the status of the repo as returned by `git status --porcelain`\n\nCONFIG ~\n\nExample configuration:\n\n>\n    require(\"sidebar-nvim\").setup({\n        ...\n        [\"git\"] = {\n            icon = \"\",\n        }\n        ...\n    })\n<\n\n\nKEYBINDINGS ~\n\n│ key │      when       │             action             │\n│e    │hovering filename│open file in the previous window│\n│s    │hovering filename│stage files                     │\n│u    │hovering filename│unstage files                   │\n\n\nDIAGNOSTICS                                              *sidebar-diagnostics*\n\nPrints the current status of the builtin lsp grouper by file. It shows only\nloaded buffers\n\nCONFIG ~\n\n>\n    require(\"sidebar-nvim\").setup({\n        ...\n        [\"diagnostics\"] = {\n            icon = \"\",\n        }\n        ...\n    })\n<\n\n\nKEYBINDINGS ~\n\n│ key │           when            │                          action                           │\n│e    │hovering diagnostic message│open file in the previous window at the diagnostic position│\n│t    │hovering filename          │toggle collapse on the group                               │\n\n\nTODOS                                                          *sidebar-todos*\n\nShows the TODOs in source. Provided by RipGrep.\n\nCONFIG ~\n\n>\n    require(\"sidebar-nvim\").setup({\n        ...\n        todos = {\n            icon = \"\",\n            ignored_paths = {'~'}, -- ignore certain paths, this will prevent huge folders like $HOME to hog Neovim with TODO searching\n            initially_closed = false, -- whether the groups should be initially closed on start. You can manually open/close groups later.\n        }\n        ...\n    })\n<\n\n\nKEYBINDINGS ~\n\n│ key │         when         │                       action                        │\n│e    │hovering todo location│open file in the previous window at the todo position│\n│t    │hovering the group    │toggle collapse on the group                         │\n\n\nFUNCTIONS ~\n\nThe following functions are available to the user to control this specific\nsection elements.\n\nTOGGLE_ALL()\n\nToggle all groups, i.e.: NOTE, TODO, FIXME etc.\n\nCall like the following: `require(\"sidebar-nvim.builtin.todos\").<function>`\n\nCLOSE_ALL()\n\nClose all groups.\n\nOPEN_ALL()\n\nOpen all groups.\n\nOPEN(GROUP_NAME)\n\nOpens the group with name `group_name`. Example\n`require(\"sidebar-nvim.builtin.todos\").open(\"NOTE\")`\n\nCLOSE(GROUP_NAME)\n\nCloses the group with name `group_name`. Example\n`require(\"sidebar-nvim.builtin.todos\").close(\"NOTE\")`\n\nTOGGLE(GROUP_NAME)\n\nToggle the group with name `group_name`. Example\n`require(\"sidebar-nvim.builtin.todos\").toggle(\"NOTE\")`\n\nCONTAINERS                                                *sidebar-containers*\n\nShows the system docker containers. Collected from `docker ps -a\n'--format=\\'{\"Names\": {{json .Names}}, \"State\": {{json .State}}, \"ID\": {{json\n.ID}} }\\''`\n\nNOTE: in some environments this can be a very intensive command to run. You may\nsee increased cpu usage when this section is enabled.\n\nCONFIG ~\n\n>\n    require(\"sidebar-nvim\").setup({\n        ...\n        containers = {\n            icon = \"\",\n            use_podman = false,\n            attach_shell = \"/bin/sh\",\n            show_all = true, -- whether to run `docker ps` or `docker ps -a`\n            interval = 5000, -- the debouncer time frame to limit requests to the docker daemon\n        }\n        ...\n    })\n<\n\n\nKEYBINDINGS ~\n\n│ key │            when             │                                                         action                                                          │\n│e    │hovering a container location│open a new terminal and attach to the container with docker exec -it <container id> ${config.containers.attach_shell}    │\n\n\nBUFFERS                                                      *sidebar-buffers*\n\nShows current loaded buffers.\n\nCONFIG ~\n\n>\n    require(\"sidebar-nvim\").setup({\n        ...\n        buffers = {\n            icon = \"\",\n            ignored_buffers = {}, -- ignore buffers by regex\n            sorting = \"id\", -- alternatively set it to \"name\" to sort by buffer name instead of buf id\n            show_numbers = true, -- whether to also show the buffer numbers\n            ignore_not_loaded = false, -- whether to ignore not loaded buffers\n            ignore_terminal = true, -- whether to show terminal buffers in the list\n        }\n        ...\n    })\n<\n\n\nKEYBINDINGS ~\n\n│ key │      when      │                action                │\n│d    │hovering an item│close the identified buffer           │\n│e    │hovering an item│open the identified buffer in a window│\n│w    │hovering an item│save the identified buffer            │\n\n\nFILES                                                          *sidebar-files*\n\nShows/manage current directory structure.\n\nCONFIG ~\n\n>\n    require(\"sidebar-nvim\").setup({\n        ...\n        files = {\n            icon = \"\",\n            show_hidden = false,\n            ignored_paths = {\"%.git$\"}\n        }\n        ...\n    })\n<\n\n\nKEYBINDINGS ~\n\n│   key   │        when        │           action           │\n│d        │hovering an item    │delete file/folder          │\n│y        │hovering an item    │yank/copy a file/folder     │\n│x        │hovering an item    │cut a file/folder           │\n│p        │hovering an item    │paste a file/folder         │\n│c        │hovering an item    │create a new file           │\n│e        │hovering an item    │open the current file/folder│\n│r        │hovering an item    │rename file/folder          │\n│u        │hovering the section│undo operation              │\n│<C-r>    │hovering the section│redo operation              │\n│<CR>     │hovering an item    │open file/folder            │\n\n\nSYMBOLS                                                      *sidebar-symbols*\n\nShows lsp symbols for the current buffer.\n\nCONFIG ~\n\n>\n    require(\"sidebar-nvim\").setup({\n        ...\n        symbols = {\n            icon = \"ƒ\",\n        }\n        ...\n    })\n<\n\n\nKEYBINDINGS ~\n\n│ key │      when      │   action    │\n│t    │hovering an item│toggle group │\n│e    │hovering an item│open location│\n\n\n==============================================================================\n11. Colors                                                    *sidebar-colors*\n\n│             Highlight Group             │          Defaults To           │\n│_SidebarNvimSectionTitle_                │Directory                       │\n│_SidebarNvimSectionSeparator_            │Comment                         │\n│_SidebarNvimSectionTitleSeparator_       │Comment                         │\n│_SidebarNvimNormal_                      │Normal                          │\n│_SidebarNvimLabel_                       │Label                           │\n│_SidebarNvimComment_                     │Comment                         │\n│_SidebarNvimLineNr_                      │LineNr                          │\n│_SidebarNvimKeyword_                     │Keyword                         │\n│_SidebarNvimGitStatusState_              │SidebarNvimKeyword              │\n│_SidebarNvimGitStatusFileName_           │SidebarNvimNormal               │\n│_SidebarNvimLspDiagnosticsError_         │LspDiagnosticsDefaultError      │\n│_SidebarNvimLspDiagnosticsWarn_          │LspDiagnosticsDefaultWarning    │\n│_SidebarNvimLspDiagnosticsInfo_          │LspDiagnosticsDefaultInformation│\n│_SidebarNvimLspDiagnosticsHint_          │LspDiagnosticsDefaultHint       │\n│_SidebarNvimLspDiagnosticsLineNumber_    │SidebarNvimLineNr               │\n│_SidebarNvimLspDiagnosticsColNumber_     │SidebarNvimLineNr               │\n│_SidebarNvimLspDiagnosticsFilename_      │SidebarNvimLabel                │\n│_SidebarNvimLspDiagnosticsTotalNumber_   │LspTroubleCount                 │\n│_SidebarNvimLspDiagnosticsMessage_       │SidebarNvimNormal               │\n│_SidebarNvimTodoTag_                     │SidebarNvimLabel                │\n│_SidebarNvimTodoTotalNumber_             │SidebarNvimNormal               │\n│_SidebarNvimTodoFilename_                │SidebarNvimNormal               │\n│_SidebarNvimTodoLineNumber_              │SidebarNvimLineNr               │\n│_SidebarNvimTodoColNumber_               │SidebarNvimLineNr               │\n│_SidebarNvimDockerContainerStatusRunning_│LspDiagnosticsDefaultInformation│\n│_SidebarNvimDockerContainerStatusExited_ │LspDiagnosticsDefaultError      │\n│_SidebarNvimDockerContainerName_         │SidebarNvimNormal               │\n│_SidebarNvimDatetimeClockName_           │SidebarNvimComment              │\n│_SidebarNvimDatetimeClockValue_          │SidebarNvimNormal               │\n│_SidebarNvimBuffersActive_               │SidebarNvimSectionTitle         │\n│_SidebarNvimBuffersNumber_               │SidebarNvimComment              │\n\n\nGenerated by panvimdoc <https://github.com/kdheepak/panvimdoc>\n\nvim:tw=78:ts=8:noet:ft=help:norl:\n"
  },
  {
    "path": "lua/sidebar-nvim/bindings.lua",
    "content": "local api = vim.api\nlocal utils = require(\"sidebar-nvim.utils\")\n\nlocal config = require(\"sidebar-nvim.config\")\n\nlocal M = {}\n\nM.State = {\n    -- bindings defined by the sections\n    -- map(index -> key string)\n    section_bindings = {},\n    -- fallback bindings if none of the sections have overrided them\n    view_bindings = {\n        [\"q\"] = function()\n            require(\"sidebar-nvim\").close()\n        end,\n    },\n}\n\nfunction M.setup()\n    local user_mappings = config.bindings or {}\n    if config.disable_default_keybindings == 1 then\n        M.State.view_bindings = user_mappings\n    else\n        local result = vim.tbl_extend(\"force\", M.State.view_bindings, user_mappings)\n        M.State.view_bindings = result\n    end\n\n    for section_index, section_data in ipairs(config.sections) do\n        local section = utils.resolve_section(section_index, section_data)\n        if section and section.bindings ~= nil then\n            M.update_section_bindings(section_index, section.bindings)\n        end\n    end\nend\n\nfunction M.update_section_bindings(index, bindings)\n    for key, binding in pairs(bindings) do\n        M.State.section_bindings[key] = M.State.section_bindings[key] or {}\n        M.State.section_bindings[key][index] = binding\n    end\nend\n\nfunction M.inject(bufnr)\n    for direction, keys in pairs({ down = { \"<Down>\", \"j\" }, up = { \"<Up>\", \"k\" } }) do\n        for _, key in ipairs(keys) do\n            api.nvim_buf_set_keymap(\n                bufnr,\n                \"n\",\n                key,\n                utils.sidebar_nvim_cursor_move_callback(direction),\n                { noremap = true, silent = true, nowait = true }\n            )\n        end\n    end\n\n    for key, _ in pairs(M.State.view_bindings) do\n        api.nvim_buf_set_keymap(\n            bufnr,\n            \"n\",\n            key,\n            utils.sidebar_nvim_callback(key),\n            { noremap = true, silent = true, nowait = true }\n        )\n    end\n\n    for key, _ in pairs(M.State.section_bindings) do\n        api.nvim_buf_set_keymap(\n            bufnr,\n            \"n\",\n            key,\n            utils.sidebar_nvim_callback(key),\n            { noremap = true, silent = true, nowait = true }\n        )\n    end\nend\n\nlocal function execute_binding(key, binding, ...)\n    if type(binding) ~= \"function\" then\n        utils.echo_warning(\"binding for '\" .. key .. \"' expected to be a function\")\n        return\n    end\n    binding(...)\nend\n\n-- @return boolean\n-- @return whether the binding was defined or not\nlocal function on_keypress_section(key, section_match, bindings)\n    -- no section in the cursor\n    if section_match == nil then\n        return false\n    end\n\n    local binding = bindings[section_match.section_index]\n\n    if binding == nil then\n        return false\n    end\n\n    execute_binding(key, binding, section_match.section_content_current_line, section_match.cursor_col)\n    return true\nend\n\nlocal function on_keypress_view(key, binding)\n    execute_binding(key, binding)\nend\n\nfunction M.on_keypress(key, section_index)\n    local section_bindings = M.State.section_bindings[key]\n\n    if section_bindings ~= nil then\n        if on_keypress_section(key, section_index, section_bindings) then\n            return\n        end\n    end\n\n    local view_bindings = M.State.view_bindings[key]\n\n    if view_bindings ~= nil then\n        on_keypress_view(key, view_bindings)\n    end\nend\n\nreturn M\n"
  },
  {
    "path": "lua/sidebar-nvim/builtin/buffers.lua",
    "content": "local utils = require(\"sidebar-nvim.utils\")\nlocal view = require(\"sidebar-nvim.view\")\nlocal Loclist = require(\"sidebar-nvim.components.loclist\")\nlocal config = require(\"sidebar-nvim.config\")\nlocal has_devicons, devicons = pcall(require, \"nvim-web-devicons\")\n\nlocal loclist = Loclist:new({ omit_single_group = true })\nlocal loclist_items = {}\n\nlocal function get_fileicon(filename)\n    if has_devicons and devicons.has_loaded() then\n        local extension = filename:match(\"^.+%.(.+)$\")\n\n        local fileicon = \"\"\n        local icon, highlight = devicons.get_icon(filename, extension)\n        if icon then\n            fileicon = icon\n        end\n\n        if not highlight then\n            highlight = \"SidebarNvimNormal\"\n        end\n\n        return {\n            text = \"  \" .. fileicon .. \" \",\n            hl = highlight,\n        }\n    else\n        return { text = \"   \" }\n    end\nend\n\nlocal function get_buffers(ctx)\n    local lines = {}\n    local hl = {}\n    local current_buffer = vim.api.nvim_get_current_buf()\n    loclist_items = {}\n\n    for _, buffer in ipairs(vim.api.nvim_list_bufs()) do\n        if buffer ~= view.View.bufnr then\n            local ignored = false\n            local bufname = vim.api.nvim_buf_get_name(buffer)\n\n            for _, ignored_buffer in ipairs(config.buffers.ignored_buffers or {}) do\n                if string.match(bufname, ignored_buffer) then\n                    ignored = true\n                end\n            end\n\n            if bufname == \"\" then\n                ignored = true\n            end\n\n            if config.buffers.ignore_not_loaded and not vim.api.nvim_buf_is_loaded(buffer) then\n                ignored = true\n            end\n\n            -- NOTE: should we be more specific?\n            if vim.api.nvim_buf_get_option(buffer, \"bufhidden\") ~= \"\" then\n                ignored = true\n            end\n\n            -- always ignore terminals\n            if config.buffers.ignore_terminal and string.match(bufname, \"term://.*\") then\n                ignored = true\n            end\n\n            if not ignored then\n                local name_hl = \"SidebarNvimNormal\"\n                local modified = \"\"\n\n                if buffer == current_buffer then\n                    name_hl = \"SidebarNvimBuffersActive\"\n                end\n\n                if vim.api.nvim_buf_get_option(buffer, \"modified\") then\n                    modified = \" *\"\n                end\n\n                -- sorting = \"id\"\n                local order = buffer\n                if config[\"buffers\"].sorting == \"name\" then\n                    order = bufname\n                end\n\n                local numbers_text = {}\n                if config.buffers.show_numbers then\n                    numbers_text = { text = buffer .. \" \", hl = \"SidebarNvimBuffersNumber\" }\n                end\n\n                loclist_items[#loclist_items + 1] = {\n                    group = \"buffers\",\n                    left = {\n                        get_fileicon(bufname),\n                        numbers_text,\n                        { text = utils.filename(bufname) .. modified, hl = name_hl },\n                    },\n                    data = { buffer = buffer, filepath = bufname },\n                    order = order,\n                }\n            end\n        end\n    end\n\n    loclist:set_items(loclist_items, { remove_groups = false })\n    loclist:draw(ctx, lines, hl)\n\n    if lines == nil or #lines == 0 then\n        return \"<no buffers>\"\n    else\n        return { lines = lines, hl = hl }\n    end\nend\n\nreturn {\n    title = \"Buffers\",\n    icon = config[\"buffers\"].icon,\n    draw = function(ctx)\n        return get_buffers(ctx)\n    end,\n    highlights = {\n        groups = {},\n        links = {\n            SidebarNvimBuffersActive = \"SidebarNvimSectionTitle\",\n            SidebarNvimBuffersNumber = \"SIdebarnvimLineNr\",\n        },\n    },\n    bindings = {\n        [\"d\"] = function(line)\n            local location = loclist:get_location_at(line)\n\n            if location == nil then\n                return\n            end\n\n            local buffer = location.data.buffer\n            local is_modified = vim.api.nvim_buf_get_option(buffer, \"modified\")\n\n            if is_modified then\n                local action = vim.fn.input(\n                    'file \"' .. location.data.filepath .. '\" has been modified. [w]rite/[d]iscard/[c]ancel: '\n                )\n\n                if action == \"w\" then\n                    vim.api.nvim_buf_call(buffer, function()\n                        vim.cmd(\"silent! w\")\n                    end)\n                    vim.api.nvim_buf_delete(buffer, { force = true })\n                elseif action == \"d\" then\n                    vim.api.nvim_buf_delete(buffer, { force = true })\n                end\n            else\n                vim.api.nvim_buf_delete(buffer, { force = true })\n            end\n        end,\n        [\"e\"] = function(line)\n            local location = loclist:get_location_at(line)\n            if location == nil then\n                return\n            end\n\n            vim.cmd(\"wincmd p\")\n            vim.cmd(\"e \" .. location.data.filepath)\n        end,\n        [\"w\"] = function(line)\n            local location = loclist:get_location_at(line)\n\n            if location == nil then\n                return\n            end\n\n            vim.api.nvim_buf_call(location.data.buffer, function()\n                vim.cmd(\"silent! w\")\n            end)\n        end,\n    },\n}\n"
  },
  {
    "path": "lua/sidebar-nvim/builtin/containers.lua",
    "content": "local utils = require(\"sidebar-nvim.utils\")\nlocal docker_utils = require(\"sidebar-nvim.docker_utils\")\nlocal Loclist = require(\"sidebar-nvim.components.loclist\")\nlocal Debouncer = require(\"sidebar-nvim.debouncer\")\nlocal config = require(\"sidebar-nvim.config\")\nlocal luv = vim.loop\n\nlocal loclist = Loclist:new({\n    omit_single_group = true,\n})\n\nlocal output_tmp = \"\"\n\nlocal function get_container_icon(container)\n    local default = { hl = \"SidebarNvimDockerContainerStatusRunning\", text = \"✓\" }\n\n    local mapping = { running = default, exited = { hl = \"SidebarNvimDockerContainerStatusExited\", text = \"☒\" } }\n\n    local icon = mapping[container.State] or default\n\n    return icon\nend\n\nlocal state_order_mapping = { running = 0, exited = 1 }\n\nlocal function async_update(_)\n    local stdout = luv.new_pipe(false)\n    local stderr = luv.new_pipe(false)\n\n    local handle\n\n    local args = { \"ps\" }\n    if config.containers.show_all then\n        args = { \"ps\", \"-a\" }\n    end\n\n    local cmd = docker_utils.build_docker_command(args, stdout, stderr)\n    handle = luv.spawn(cmd.bin, cmd.opts, function()\n        vim.schedule(function()\n            local loclist_items = {}\n            if output_tmp ~= \"\" then\n                for _, line in ipairs(vim.split(output_tmp, \"\\n\")) do\n                    line = string.sub(line, 2, #line - 1)\n                    if line ~= \"\" then\n                        -- TODO: on nightly change `vim.fn.json_*` to `vim.json_decode`, which is way faster and no need for schedule wrap\n                        local ret, container = pcall(vim.fn.json_decode, line)\n                        if ret then\n                            local icon = get_container_icon(container)\n                            table.insert(loclist_items, {\n                                group = \"containers\",\n                                left = {\n                                    { text = icon.text .. \" \", hl = icon.hl },\n                                    { text = container.Names },\n                                },\n                                order = state_order_mapping[container.State] or 999,\n                                id = container.ID,\n                            })\n                        else\n                            vim.schedule(function()\n                                utils.echo_warning(\"invalid container output: \" .. container)\n                            end)\n                        end\n                    end\n                end\n            end\n            loclist:set_items(loclist_items, { remove_groups = false })\n        end)\n\n        luv.read_stop(stdout)\n        luv.read_stop(stderr)\n        stdout:close()\n        stderr:close()\n        handle:close()\n    end)\n\n    output_tmp = \"\"\n\n    luv.read_start(stdout, function(err, data)\n        if data == nil then\n            return\n        end\n\n        output_tmp = output_tmp .. data\n\n        if err ~= nil then\n            vim.schedule(function()\n                utils.echo_warning(err)\n            end)\n        end\n    end)\n\n    luv.read_start(stderr, function(err, data)\n        if data == nil then\n            return\n        end\n\n        if err ~= nil then\n            vim.schedule(function()\n                utils.echo_warning(err)\n            end)\n        end\n    end)\nend\n\nlocal async_update_debounced\n\nreturn {\n    title = \"Containers\",\n    icon = config.containers.icon,\n    setup = function()\n        local interval = config.containers.interval or 2000\n        async_update_debounced = Debouncer:new(async_update, interval)\n        async_update_debounced:call()\n    end,\n    update = function(ctx)\n        async_update_debounced:call(ctx)\n    end,\n    draw = function(ctx)\n        async_update_debounced:call(ctx)\n\n        local lines = {}\n        local hl = {}\n\n        loclist:draw(ctx, lines, hl)\n\n        if #lines == 0 then\n            lines = { \"<no containers>\" }\n        end\n\n        return { lines = lines, hl = hl }\n    end,\n    highlights = {\n        groups = {},\n        links = {\n            SidebarNvimDockerContainerStatusRunning = \"LspDiagnosticsDefaultInformation\",\n            SidebarNvimDockerContainerStatusExited = \"LspDiagnosticsDefaultError\",\n        },\n    },\n    bindings = {\n        [\"e\"] = function(line)\n            local location = loclist:get_location_at(line)\n            if location == nil then\n                return\n            end\n            vim.cmd(\"wincmd p\")\n            vim.cmd(\"terminal \" .. docker_utils.build_docker_attach_command(location.id))\n        end,\n    },\n}\n"
  },
  {
    "path": "lua/sidebar-nvim/builtin/datetime.lua",
    "content": "local config = require(\"sidebar-nvim.config\")\nlocal utils = require(\"sidebar-nvim.utils\")\nlocal has_luatz, luatz = pcall(require, \"luatz\")\nlocal _, timetable = pcall(require, \"luatz.timetable\")\n\nlocal is_config_valid = false\nlocal config_error_messages = {}\n\nlocal function get_clock_value_using_luatz(clock, format)\n    local dt = luatz.time()\n\n    local tzinfo = luatz.get_tz(clock.tz)\n    if tzinfo then\n        dt = tzinfo:localise(dt)\n    else\n        utils.echo_warning(string.format(\"tz '%s' not found\", clock.tz))\n    end\n\n    return luatz.strftime.strftime(format, timetable.new_from_timestamp(dt))\nend\n\nlocal function validate_config()\n    if not config.datetime or not config.datetime.clocks or #config.datetime.clocks == 0 then\n        is_config_valid = true\n        return\n    end\n\n    for _, clock in ipairs(config.datetime.clocks) do\n        if clock.tz then\n            if not has_luatz then\n                utils.echo_warning(\"luatz not installed. Cannot use 'tz' option without luatz\")\n                config_error_messages = { \"luatz not installed.\", \"Cannot use 'tz' option without luatz\" }\n                is_config_valid = false\n                return\n            end\n        end\n    end\n\n    is_config_valid = true\nend\n\nreturn {\n    title = \"Current datetime\",\n    icon = config.datetime.icon,\n    setup = function()\n        validate_config()\n    end,\n    draw = function()\n        local lines = {}\n        local hl = {}\n\n        if not is_config_valid then\n            for _, msg in ipairs(config_error_messages) do\n                table.insert(lines, msg)\n            end\n            return { lines = lines, hl = hl }\n        end\n\n        if not config.datetime or not config.datetime.clocks or #config.datetime.clocks == 0 then\n            table.insert(lines, \"<no clocks>\")\n            return { lines = lines, hl = hl }\n        end\n\n        local clocks_num = #config.datetime.clocks\n        for i, clock in ipairs(config.datetime.clocks) do\n            local format = clock.format or config.datetime.format\n\n            local clock_value\n            if has_luatz then\n                clock_value = get_clock_value_using_luatz(clock, format)\n            else\n                local offset = clock.offset or 0\n                clock_value = os.date(format, os.time() + offset * 60 * 60)\n            end\n\n            table.insert(hl, { \"SidebarNvimDatetimeClockName\", #lines, 0, -1 })\n            table.insert(lines, \"# \" .. (clock.name or clock.offset or clock.tz))\n\n            table.insert(hl, { \"SidebarNvimDatetimeClockValue\", #lines, 0, -1 })\n            table.insert(lines, clock_value)\n\n            if i < clocks_num then\n                table.insert(lines, \"\")\n            end\n        end\n\n        return { lines = lines, hl = hl }\n    end,\n    highlights = {\n        groups = {},\n        links = {\n            SidebarNvimDatetimeClockName = \"SidebarNvimComment\",\n            SidebarNvimDatetimeClockValue = \"SidebarNvimNormal\",\n        },\n    },\n}\n"
  },
  {
    "path": "lua/sidebar-nvim/builtin/diagnostics.lua",
    "content": "local Loclist = require(\"sidebar-nvim.components.loclist\")\nlocal config = require(\"sidebar-nvim.config\")\n\nlocal loclist = Loclist:new({})\n\nlocal severity_level = { \"Error\", \"Warning\", \"Info\", \"Hint\" }\nlocal icons = { \"\", \"\", \"\", \"\" }\nlocal use_icons = true\n\nlocal function get_diagnostics()\n    local current_buf = vim.api.nvim_get_current_buf()\n    local current_buf_filepath = vim.api.nvim_buf_get_name(current_buf)\n    local current_buf_filename = vim.fn.fnamemodify(current_buf_filepath, \":t\")\n\n    local open_bufs = vim.api.nvim_list_bufs()\n\n    local all_diagnostics = vim.diagnostic.get()\n    local loclist_items = {}\n\n    for _, diag in pairs(all_diagnostics) do\n        local bufnr = diag.bufnr\n        if open_bufs[bufnr] ~= nil and vim.api.nvim_buf_is_loaded(bufnr) then\n            local filepath = vim.api.nvim_buf_get_name(bufnr)\n            local filename = vim.fn.fnamemodify(filepath, \":t\")\n\n            local message = diag.message\n            message = message:gsub(\"\\n\", \" \")\n\n            local severity = diag.severity\n            local level = severity_level[severity]\n            local icon = icons[severity]\n\n            if not use_icons then\n                icon = level\n            end\n\n            table.insert(loclist_items, {\n                group = filename,\n                left = {\n                    { text = icon .. \" \", hl = \"SidebarNvimLspDiagnostics\" .. level },\n                    {\n                        text = diag.lnum + 1,\n                        hl = \"SidebarNvimLspDiagnosticsLineNumber\",\n                    },\n                    { text = \":\" },\n                    {\n                        text = (diag.col + 1) .. \" \",\n                        hl = \"SidebarNvimLspDiagnosticsColNumber\",\n                    },\n                    { text = message },\n                },\n                lnum = diag.lnum + 1,\n                col = diag.col + 1,\n                filepath = filepath,\n            })\n        end\n    end\n\n    local previous_state = vim.tbl_map(function(group)\n        return group.is_closed\n    end, loclist.groups)\n\n    loclist:set_items(loclist_items, { remove_groups = true })\n    loclist:close_all_groups()\n\n    for group_name, is_closed in pairs(previous_state) do\n        if loclist.groups[group_name] ~= nil then\n            loclist.groups[group_name].is_closed = is_closed\n        end\n    end\n\n    if loclist.groups[current_buf_filename] ~= nil then\n        loclist.groups[current_buf_filename].is_closed = false\n    end\nend\n\nreturn {\n    title = \"Diagnostics\",\n    icon = config[\"diagnostics\"].icon,\n    setup = function(_)\n        vim.api.nvim_exec(\n            [[\n          augroup sidebar_nvim_diagnostics_update\n              autocmd!\n              autocmd DiagnosticChanged * lua require'sidebar-nvim.builtin.diagnostics'.update()\n          augroup END\n          ]],\n            false\n        )\n\n        get_diagnostics()\n    end,\n    update = function(_)\n        get_diagnostics()\n    end,\n    draw = function(ctx)\n        local lines = {}\n        local hl = {}\n\n        loclist:draw(ctx, lines, hl)\n\n        if lines == nil or #lines == 0 then\n            return \"<no diagnostics>\"\n        else\n            return { lines = lines, hl = hl }\n        end\n    end,\n    highlights = {\n        groups = {},\n        links = {\n            SidebarNvimLspDiagnosticsError = \"LspDiagnosticsDefaultError\",\n            SidebarNvimLspDiagnosticsWarning = \"LspDiagnosticsDefaultWarning\",\n            SidebarNvimLspDiagnosticsInfo = \"LspDiagnosticsDefaultInformation\",\n            SidebarNvimLspDiagnosticsHint = \"LspDiagnosticsDefaultHint\",\n            SidebarNvimLspDiagnosticsLineNumber = \"SidebarNvimLineNr\",\n            SidebarNvimLspDiagnosticsColNumber = \"SidebarNvimLineNr\",\n        },\n    },\n    bindings = {\n        [\"t\"] = function(line)\n            loclist:toggle_group_at(line)\n        end,\n        [\"e\"] = function(line)\n            local location = loclist:get_location_at(line)\n            if location == nil then\n                return\n            end\n            -- TODO: I believe there is a better way to do this, but I haven't had the time to do investigate\n            vim.cmd(\"wincmd p\")\n            vim.cmd(\"e \" .. location.filepath)\n            vim.fn.cursor(location.lnum, location.col)\n        end,\n    },\n}\n"
  },
  {
    "path": "lua/sidebar-nvim/builtin/files.lua",
    "content": "local utils = require(\"sidebar-nvim.utils\")\nlocal Loclist = require(\"sidebar-nvim.components.loclist\")\nlocal config = require(\"sidebar-nvim.config\")\nlocal has_devicons, devicons = pcall(require, \"nvim-web-devicons\")\nlocal luv = vim.loop\n\nlocal loclist = Loclist:new({ omit_single_group = false, show_group_count = false })\n\nlocal icons = {\n    directory_closed = \"\",\n    directory_open = \"\",\n    file = \"\",\n    closed = \"\",\n    opened = \"\",\n}\n\nlocal yanked_files = {}\nlocal cut_files = {}\nlocal open_directories = {}\n\nlocal history = { position = 0, groups = {} }\nlocal trash_dir = luv.os_homedir() .. \"/.local/share/Trash/files/\"\n\nlocal function get_fileicon(filename)\n    if has_devicons and devicons.has_loaded() then\n        local extension = filename:match(\"^.+%.(.+)$\")\n        local fileicon, highlight = devicons.get_icon(filename, extension)\n\n        if not highlight then\n            highlight = \"SidebarNvimNormal\"\n        end\n        return { text = fileicon or icons[\"file\"], hl = highlight }\n    end\n    return { text = icons[\"file\"] .. \" \" }\nend\n\n-- scan directory recursively\nlocal function scan_dir(directory)\n    if not open_directories[directory] then\n        return\n    end\n\n    local show_hidden = config.files.show_hidden\n    local handle = luv.fs_scandir(directory)\n    local children = {}\n    local children_directories = {}\n    local children_files = {}\n\n    while handle do\n        local filename, filetype = luv.fs_scandir_next(handle)\n\n        if not filename then\n            break\n        end\n\n        local path = directory .. \"/\" .. filename\n        local ignored = false\n\n        for _, ignored_path in ipairs(config.files.ignored_paths or {}) do\n            if string.match(path, ignored_path) then\n                ignored = true\n            end\n        end\n\n        if not ignored then\n            if show_hidden or filename:sub(1, 1) ~= \".\" then\n                if filetype == \"file\" then\n                    table.insert(children_files, {\n                        name = filename,\n                        type = \"file\",\n                        path = path,\n                        parent = directory,\n                    })\n                elseif filetype == \"directory\" then\n                    table.insert(children_directories, {\n                        name = filename,\n                        type = \"directory\",\n                        path = path,\n                        parent = directory,\n                        children = scan_dir(directory .. \"/\" .. filename),\n                    })\n                end\n            end\n        end\n    end\n\n    table.sort(children_directories, function(a, b)\n        return a.name < b.name\n    end)\n    vim.list_extend(children, children_directories)\n\n    table.sort(children_files, function(a, b)\n        return a.name < b.name\n    end)\n    vim.list_extend(children, children_files)\n\n    return children\nend\n\nlocal function build_loclist(group, directory, level)\n    local loclist_items = {}\n\n    if directory.children then\n        for _, node in ipairs(directory.children) do\n            if node.type == \"file\" then\n                local icon = get_fileicon(node.name)\n                local selected = { text = \"\" }\n\n                if yanked_files[node.path] then\n                    selected = { text = \" *\", hl = \"SidebarNvimFilesYanked\" }\n                elseif cut_files[node.path] then\n                    selected = { text = \" *\", hl = \"SidebarNvimFilesCut\" }\n                end\n\n                loclist_items[#loclist_items + 1] = {\n                    group = group,\n                    left = {\n                        { text = string.rep(\"  \", level) .. icon.text .. \" \", hl = icon.hl },\n                        { text = node.name },\n                        selected,\n                    },\n                    name = node.name,\n                    path = node.path,\n                    type = node.type,\n                    parent = node.parent,\n                    node = node,\n                }\n            elseif node.type == \"directory\" then\n                local icon\n                if open_directories[node.path] then\n                    icon = icons[\"directory_open\"]\n                else\n                    icon = icons[\"directory_closed\"]\n                end\n\n                local selected = { text = \"\" }\n\n                if yanked_files[node.path] then\n                    selected = { text = \" *\", hl = \"SidebarNvimFilesYanked\" }\n                elseif cut_files[node.path] then\n                    selected = { text = \" *\", hl = \"SidebarNvimFilesCut\" }\n                end\n\n                loclist_items[#loclist_items + 1] = {\n                    group = group,\n                    left = {\n                        {\n                            text = string.rep(\"  \", level) .. icon .. \" \" .. node.name,\n                            hl = \"SidebarNvimFilesDirectory\",\n                        },\n                        selected,\n                    },\n                    name = node.name,\n                    path = node.path,\n                    type = node.type,\n                    parent = node.parent,\n                    node = node,\n                }\n            end\n\n            if node.type == \"directory\" and open_directories[node.path] then\n                vim.list_extend(loclist_items, build_loclist(group, node, level + 1))\n            end\n        end\n    end\n    return loclist_items\nend\n\nlocal function update(group, directory)\n    local node = { path = directory, children = scan_dir(directory) }\n\n    loclist:set_items(build_loclist(group, node, 0), { remove_groups = true })\nend\n\nlocal function exec(group)\n    for _, op in ipairs(group.operations) do\n        op.exec()\n    end\n\n    group.executed = true\nend\n\n-- undo the operation\nlocal function undo(group)\n    for _, op in ipairs(group.operations) do\n        op.undo()\n    end\nend\n\nlocal function copy_file(src, dest, confirm_overwrite)\n    if confirm_overwrite and luv.fs_access(dest, \"r\") ~= false then\n        local overwrite = vim.fn.input('file \"' .. dest .. '\" already exists. Overwrite? y/n: ')\n\n        if overwrite ~= \"y\" then\n            return\n        end\n    end\n\n    luv.fs_copyfile(src, dest, function(err, _)\n        if err ~= nil then\n            vim.schedule(function()\n                utils.echo_warning(err)\n            end)\n        end\n    end)\nend\n\nlocal function create_file(dest)\n    if luv.fs_access(dest, \"r\") ~= false then\n        vim.schedule(function()\n            utils.echo_warning('file \"' .. dest .. '\" already exists.')\n        end)\n        return\n    end\n\n    local is_file = not dest:match(\"/$\")\n    local parent_folders = vim.fn.fnamemodify(dest, \":h\")\n\n    if not utils.file_exist(parent_folders) then\n        local success = vim.fn.mkdir(parent_folders, \"p\")\n        if not success then\n            utils.echo_warning(\"Could not create directory \" .. parent_folders)\n        end\n    end\n\n    if is_file then\n        luv.fs_open(dest, \"w\", 420, function(err, file)\n            if err ~= nil then\n                vim.schedule(function()\n                    utils.echo_warning(err)\n                end)\n            else\n                luv.fs_close(file)\n            end\n        end)\n    end\nend\n\nlocal function delete_file(src, trash, confirm_deletion)\n    if confirm_deletion then\n        local delete = vim.fn.input('delete file \"' .. src .. '\"? y/n: ')\n\n        if delete ~= \"y\" then\n            return\n        end\n    end\n\n    luv.fs_rename(src, trash, function(err, _)\n        if err ~= nil then\n            vim.schedule(function()\n                utils.echo_warning(err)\n            end)\n        end\n    end)\nend\n\nlocal function move_file(src, dest, confirm_overwrite)\n    if confirm_overwrite and luv.fs_access(dest, \"r\") ~= false then\n        local overwrite = vim.fn.input('file \"' .. dest .. '\" already exists. Overwrite? y/n: ')\n\n        if overwrite ~= \"y\" then\n            return\n        end\n    end\n\n    luv.fs_rename(src, dest, function(err, _)\n        if err ~= nil then\n            vim.schedule(function()\n                utils.echo_warning(err)\n            end)\n        end\n    end)\nend\n\nreturn {\n    title = \"Files\",\n    icon = config[\"files\"].icon,\n    setup = function(_)\n        vim.api.nvim_exec(\n            [[\n          augroup sidebar_nvim_files_update\n              autocmd!\n              autocmd ShellCmdPost * lua require'sidebar-nvim.builtin.files'.update()\n              autocmd BufLeave term://* lua require'sidebar-nvim.builtin.files'.update()\n          augroup END\n          ]],\n            false\n        )\n    end,\n    update = function(_)\n        local cwd = vim.fn.getcwd()\n        local group = utils.shortest_path(cwd)\n\n        open_directories[cwd] = true\n\n        update(group, cwd)\n    end,\n    draw = function(ctx)\n        local lines = {}\n        local hl = {}\n\n        loclist:draw(ctx, lines, hl)\n\n        return { lines = lines, hl = hl }\n    end,\n\n    highlights = {\n        groups = {},\n        links = {\n            SidebarNvimFilesDirectory = \"SidebarNvimSectionTitle\",\n            SidebarNvimFilesYanked = \"SidebarNvimLabel\",\n            SidebarNvimFilesCut = \"DiagnosticError\",\n        },\n    },\n\n    bindings = {\n        -- delete\n        [\"d\"] = function(line)\n            local location = loclist:get_location_at(line)\n            if location == nil then\n                return\n            end\n\n            local operation\n            operation = {\n                exec = function()\n                    delete_file(operation.src, operation.dest, true)\n                end,\n                undo = function()\n                    move_file(operation.dest, operation.src, true)\n                end,\n                src = location.node.path,\n                dest = trash_dir .. location.node.name,\n            }\n            local group = { executed = false, operations = { operation } }\n\n            history.position = history.position + 1\n            history.groups = vim.list_slice(history.groups, 1, history.position)\n            history.groups[history.position] = group\n\n            exec(group)\n        end,\n        -- yank\n        [\"y\"] = function(line)\n            local location = loclist:get_location_at(line)\n            if location == nil then\n                return\n            end\n\n            yanked_files[location.node.path] = true\n            cut_files = {}\n        end,\n        -- cut\n        [\"x\"] = function(line)\n            local location = loclist:get_location_at(line)\n            if location == nil then\n                return\n            end\n\n            cut_files[location.node.path] = true\n            yanked_files = {}\n        end,\n        -- paste\n        [\"p\"] = function(line)\n            local location = loclist:get_location_at(line)\n            if location == nil then\n                return\n            end\n\n            local dest_dir\n\n            if location.node.type == \"directory\" then\n                dest_dir = location.node.path\n            else\n                dest_dir = location.node.parent\n            end\n\n            open_directories[dest_dir] = true\n\n            local group = { executed = false, operations = {} }\n\n            for path, _ in pairs(yanked_files) do\n                local operation\n                operation = {\n                    exec = function()\n                        copy_file(operation.src, operation.dest, true)\n                    end,\n                    undo = function()\n                        delete_file(operation.dest, trash_dir .. utils.filename(operation.src), true)\n                    end,\n                    src = path,\n                    dest = dest_dir .. \"/\" .. utils.filename(path),\n                }\n                table.insert(group.operations, operation)\n            end\n\n            for path, _ in pairs(cut_files) do\n                local operation\n                operation = {\n                    exec = function()\n                        move_file(operation.src, operation.dest, true)\n                    end,\n                    undo = function()\n                        move_file(operation.dest, operation.src, true)\n                    end,\n                    src = path,\n                    dest = dest_dir .. \"/\" .. utils.filename(path),\n                }\n                table.insert(group.operations, operation)\n            end\n            history.position = history.position + 1\n            history.groups = vim.list_slice(history.groups, 1, history.position)\n            history.groups[history.position] = group\n\n            yanked_files = {}\n            cut_files = {}\n\n            exec(group)\n        end,\n        -- create\n        [\"c\"] = function(line)\n            local location = loclist:get_location_at(line)\n            if location == nil then\n                return\n            end\n\n            local parent\n\n            if location.type == \"directory\" then\n                parent = location.path\n            else\n                parent = location.parent\n            end\n\n            open_directories[parent] = true\n\n            local name = vim.fn.input(\"file name: \")\n\n            if string.len(vim.trim(name)) == 0 then\n                return\n            end\n\n            local operation\n\n            operation = {\n                success = true,\n                exec = function()\n                    create_file(operation.dest)\n                end,\n                undo = function()\n                    delete_file(operation.dest, trash_dir .. name, true)\n                end,\n                src = nil,\n                dest = parent .. \"/\" .. name,\n            }\n\n            local group = { executed = false, operations = { operation } }\n\n            history.position = history.position + 1\n            history.groups = vim.list_slice(history.groups, 1, history.position)\n            history.groups[history.position] = group\n\n            exec(group)\n        end,\n        -- open current file\n        [\"e\"] = function(line)\n            local location = loclist:get_location_at(line)\n            if location == nil then\n                return\n            end\n\n            if location.type == \"file\" then\n                vim.cmd(\"wincmd p\")\n                vim.cmd(\"e \" .. location.node.path)\n            else\n                if open_directories[location.node.path] == nil then\n                    open_directories[location.node.path] = true\n                else\n                    open_directories[location.node.path] = nil\n                end\n            end\n        end,\n        -- rename\n        [\"r\"] = function(line)\n            local location = loclist:get_location_at(line)\n\n            if location == nil then\n                return\n            end\n\n            local new_name = vim.fn.input('rename file \"' .. location.node.name .. '\" to: ')\n            local operation\n\n            operation = {\n                exec = function()\n                    move_file(operation.src, operation.dest, true)\n                end,\n                undo = function()\n                    move_file(operation.dest, operation.src, true)\n                end,\n                src = location.node.path,\n                dest = location.node.parent .. \"/\" .. new_name,\n            }\n\n            local group = { executed = false, operations = { operation } }\n\n            history.position = history.position + 1\n            history.groups = vim.list_slice(history.groups, 1, history.position)\n            history.groups[history.position] = group\n\n            exec(group)\n        end,\n        -- undo\n        [\"u\"] = function(_)\n            if history.position > 0 then\n                undo(history.groups[history.position])\n                history.position = history.position - 1\n            end\n        end,\n        -- redo\n        [\"<C-r>\"] = function(_)\n            if history.position < #history.groups then\n                history.position = history.position + 1\n                exec(history.groups[history.position])\n            end\n        end,\n        [\"<CR>\"] = function(line)\n            local location = loclist:get_location_at(line)\n            if location == nil then\n                return\n            end\n            if location.node.type == \"file\" then\n                vim.cmd(\"wincmd p\")\n                vim.cmd(\"e \" .. location.node.path)\n            else\n                if open_directories[location.node.path] == nil then\n                    open_directories[location.node.path] = true\n                else\n                    open_directories[location.node.path] = nil\n                end\n            end\n        end,\n    },\n}\n"
  },
  {
    "path": "lua/sidebar-nvim/builtin/git-status.lua",
    "content": "local utils = require(\"sidebar-nvim.utils\")\n\nlocal git = require(\"sidebar-nvim.builtin.git\")\n\nlocal deprecated_git = vim.tbl_deep_extend(\"force\", git, {\n    setup = function(ctx)\n        utils.echo_warning(\"Section 'git-status' is deprecated. Please use 'git'\")\n\n        if git.setup ~= nil then\n            return git.setup(ctx)\n        end\n    end,\n})\n\nreturn deprecated_git\n"
  },
  {
    "path": "lua/sidebar-nvim/builtin/git.lua",
    "content": "local utils = require(\"sidebar-nvim.utils\")\nlocal sidebar = require(\"sidebar-nvim\")\nlocal Loclist = require(\"sidebar-nvim.components.loclist\")\nlocal Debouncer = require(\"sidebar-nvim.debouncer\")\nlocal config = require(\"sidebar-nvim.config\")\nlocal luv = vim.loop\nlocal has_devicons, devicons = pcall(require, \"nvim-web-devicons\")\n\nlocal loclist = Loclist:new({})\n\n-- Make sure all groups exist\nloclist:add_group(\"Staged\")\nloclist:add_group(\"Unstaged\")\nloclist:add_group(\"Unmerged\")\nloclist:add_group(\"Untracked\")\n\nlocal loclist_items = {}\nlocal finished = 0\nlocal expected_job_count = 4\n\n-- parse line from git diff --numstat into a loclist item\nlocal function parse_git_diff(group, line)\n    local t = vim.split(line, \"\\t\")\n    local added, removed, filepath = t[1], t[2], t[3]\n    local extension = filepath:match(\"^.+%.(.+)$\")\n    local fileicon = \"\"\n    local filehighlight = \"SidebarNvimGitStatusFileIcon\"\n\n    if has_devicons and devicons.has_loaded() then\n        local icon, highlight = devicons.get_icon(filepath, extension)\n\n        if icon then\n            fileicon = icon\n            filehighlight = highlight\n        end\n    end\n\n    if filepath ~= \"\" then\n        loclist:open_group(group)\n\n        table.insert(loclist_items, {\n            group = group,\n            left = {\n                {\n                    text = fileicon .. \" \",\n                    hl = filehighlight,\n                },\n                {\n                    text = utils.shortest_path(filepath) .. \" \",\n                    hl = \"SidebarNvimGitStatusFileName\",\n                },\n                {\n                    text = added,\n                    hl = \"SidebarNvimGitStatusDiffAdded\",\n                },\n                {\n                    text = \", \",\n                },\n                {\n                    text = removed,\n                    hl = \"SidebarNvimGitStatusDiffRemoved\",\n                },\n            },\n            filepath = filepath,\n        })\n    end\nend\n\n-- parse line from git status --porcelain into a loclist item\nlocal function parse_git_status(group, line)\n    local striped = line:match(\"^%s*(.-)%s*$\")\n    local status = striped:sub(0, 2)\n    local filepath = striped:sub(3, -1):match(\"^%s*(.-)%s*$\")\n    local extension = filepath:match(\"^.+%.(.+)$\")\n\n    if status == \"??\" then\n        local fileicon = \"\"\n\n        if has_devicons and devicons.has_loaded() then\n            local icon = vim.schedule_wrap(function()\n                return devicons.get_icon_color(filepath, extension)\n            end)()\n            if icon then\n                fileicon = icon\n            end\n        end\n\n        loclist:open_group(group)\n\n        table.insert(loclist_items, {\n            group = group,\n            left = {\n                {\n                    text = fileicon .. \" \",\n                    hl = \"SidebarNvimGitStatusFileIcon\",\n                },\n                {\n                    text = utils.shortest_path(filepath),\n                    hl = \"SidebarNvimGitStatusFileName\",\n                },\n            },\n            filepath = filepath,\n        })\n    end\nend\n\n-- execute async command and parse result into loclist items\nlocal function async_cmd(group, command, args, parse_fn)\n    local stdout = luv.new_pipe(false)\n    local stderr = luv.new_pipe(false)\n\n    local handle\n    handle = luv.spawn(command, { args = args, stdio = { nil, stdout, stderr }, cwd = luv.cwd() }, function()\n        finished = finished + 1\n\n        if finished == expected_job_count then\n            loclist:set_items(loclist_items, { remove_groups = false })\n        end\n\n        luv.read_stop(stdout)\n        luv.read_stop(stderr)\n        stdout:close()\n        stderr:close()\n        handle:close()\n    end)\n\n    luv.read_start(stdout, function(err, data)\n        if data == nil then\n            return\n        end\n\n        for _, line in ipairs(vim.split(data, \"\\n\")) do\n            if line ~= \"\" then\n                parse_fn(group, line)\n            end\n        end\n\n        if err ~= nil then\n            vim.schedule(function()\n                utils.echo_warning(err)\n            end)\n        end\n    end)\n\n    luv.read_start(stderr, function(err, data)\n        if data == nil then\n            return\n        end\n\n        if err ~= nil then\n            vim.schedule(function()\n                utils.echo_warning(err)\n            end)\n        end\n    end)\nend\n\nlocal function async_update(_)\n    loclist_items = {}\n    finished = 0\n\n    -- if add a new job, please update `expected_job_count` at the top\n    -- TODO: investigate using coroutines to wait for all jobs and then update the loclist\n    async_cmd(\"Staged\", \"git\", { \"diff\", \"--numstat\", \"--staged\", \"--diff-filter=u\" }, parse_git_diff)\n    async_cmd(\"Unstaged\", \"git\", { \"diff\", \"--numstat\", \"--diff-filter=u\" }, parse_git_diff)\n    async_cmd(\"Unmerged\", \"git\", { \"diff\", \"--numstat\", \"--diff-filter=U\" }, parse_git_diff)\n    async_cmd(\"Untracked\", \"git\", { \"status\", \"--porcelain\" }, parse_git_status)\nend\n\nlocal async_update_debounced = Debouncer:new(async_update, 1000)\n\nreturn {\n    title = \"Git Status\",\n    icon = config[\"git\"].icon,\n    setup = function(ctx)\n        -- ShellCmdPost triggered after \":!<cmd>\"\n        -- BufLeave triggered only after leaving terminal buffers\n        vim.api.nvim_exec(\n            [[\n          augroup sidebar_nvim_git_status_update\n              autocmd!\n              autocmd ShellCmdPost * lua require'sidebar-nvim.builtin.git'.update()\n              autocmd BufLeave term://* lua require'sidebar-nvim.builtin.git'.update()\n          augroup END\n          ]],\n            false\n        )\n        async_update_debounced:call(ctx)\n    end,\n    update = function(ctx)\n        if not ctx then\n            ---@diagnostic disable-next-line: missing-parameter\n            ctx = { width = sidebar.get_width() }\n        end\n        async_update_debounced:call(ctx)\n    end,\n    draw = function(ctx)\n        local lines = {}\n        local hl = {}\n\n        loclist:draw(ctx, lines, hl)\n\n        if #lines == 0 then\n            lines = { \"<no changes>\" }\n        end\n\n        return { lines = lines, hl = hl }\n    end,\n    highlights = {\n        groups = {},\n        links = {\n            SidebarNvimGitStatusFileName = \"SidebarNvimNormal\",\n            SidebarNvimGitStatusFileIcon = \"SidebarNvimSectionTitle\",\n            SidebarNvimGitStatusDiffAdded = \"DiffAdded\",\n            SidebarNvimGitStatusDiffRemoved = \"DiffRemoved\",\n        },\n    },\n    bindings = {\n        [\"e\"] = function(line)\n            local location = loclist:get_location_at(line)\n            if location == nil then\n                return\n            end\n            vim.cmd(\"wincmd p\")\n            vim.cmd(\"e \" .. location.filepath)\n        end,\n        -- stage files\n        [\"s\"] = function(line)\n            local location = loclist:get_location_at(line)\n            if location == nil then\n                return\n            end\n\n            utils.async_cmd(\"git\", { \"add\", location.filepath }, function()\n                async_update_debounced:call()\n            end)\n        end,\n        -- unstage files\n        [\"u\"] = function(line)\n            local location = loclist:get_location_at(line)\n            if location == nil then\n                return\n            end\n\n            utils.async_cmd(\"git\", { \"restore\", \"--staged\", location.filepath }, function()\n                async_update_debounced:call()\n            end)\n        end,\n    },\n}\n"
  },
  {
    "path": "lua/sidebar-nvim/builtin/lsp-diagnostics.lua",
    "content": "local utils = require(\"sidebar-nvim.utils\")\n\nlocal diagnostics = require(\"sidebar-nvim.builtin.diagnostics\")\n\nlocal deprecated_diagnostics = vim.tbl_deep_extend(\"force\", diagnostics, {\n    setup = function(ctx)\n        utils.echo_warning(\"Section 'lsp-diagnostics' is deprecated. Please use 'diagnostics'\")\n\n        if diagnostics.setup ~= nil then\n            return diagnostics.setup(ctx)\n        end\n    end,\n})\n\nreturn deprecated_diagnostics\n"
  },
  {
    "path": "lua/sidebar-nvim/builtin/symbols.lua",
    "content": "local Loclist = require(\"sidebar-nvim.components.loclist\")\nlocal config = require(\"sidebar-nvim.config\")\nlocal view = require(\"sidebar-nvim.view\")\n\nlocal loclist = Loclist:new({ omit_single_group = true })\nlocal open_symbols = {}\nlocal last_buffer\nlocal last_pos\n\nlocal kinds = {\n    { text = \" \", hl = \"TSURI\" },\n    { text = \" \", hl = \"TSNamespace\" },\n    { text = \" \", hl = \"TSNamespace\" },\n    { text = \" \", hl = \"TSNamespace\" },\n    { text = \"𝓒 \", hl = \"TSType\" },\n    { text = \"ƒ \", hl = \"TSMethod\" },\n    { text = \" \", hl = \"TSMethod\" },\n    { text = \" \", hl = \"TSField\" },\n    { text = \" \", hl = \"TSConstructor\" },\n    { text = \"ℰ \", hl = \"TSType\" },\n    { text = \"ﰮ \", hl = \"TSType\" },\n    { text = \" \", hl = \"TSFunction\" },\n    { text = \" \", hl = \"TSConstant\" },\n    { text = \" \", hl = \"TSConstant\" },\n    { text = \"𝓐 \", hl = \"TSString\" },\n    { text = \"# \", hl = \"TSNumber\" },\n    { text = \"⊨ \", hl = \"TSBoolean\" },\n    { text = \" \", hl = \"TSConstant\" },\n    { text = \"⦿ \", hl = \"TSType\" },\n    { text = \" \", hl = \"TSType\" },\n    { text = \"NULL \", hl = \"TSType\" },\n    { text = \" \", hl = \"TSField\" },\n    { text = \"𝓢 \", hl = \"TSType\" },\n    { text = \"🗲 \", hl = \"TSType\" },\n    { text = \"+ \", hl = \"TSOperator\" },\n    { text = \"𝙏 \", hl = \"TSParameter\" },\n}\n\nlocal function get_range(s)\n    return s.range or s.location.range\nend\n\nlocal function build_loclist(filepath, loclist_items, symbols, level)\n    table.sort(symbols, function(a, _)\n        return get_range(a).start.line < get_range(a).start.line\n    end)\n\n    for _, symbol in ipairs(symbols) do\n        local kind = kinds[symbol.kind]\n        loclist_items[#loclist_items + 1] = {\n            group = \"symbols\",\n            left = {\n                { text = string.rep(\" \", level) .. kind.text, hl = kind.hl },\n                { text = symbol.name .. \" \", hl = \"SidebarNvimSymbolsName\" },\n                { text = symbol.detail, hl = \"SidebarNvimSymbolsDetail\" },\n            },\n            right = {},\n            data = { symbol = symbol, filepath = filepath },\n        }\n\n        -- uses a unique key for each symbol appending the name and position\n        if symbol.children and open_symbols[symbol.name .. symbol.range.start.line .. symbol.range.start.character] then\n            build_loclist(filepath, loclist_items, symbol.children, level + 1)\n        end\n    end\nend\n\nlocal function get_symbols(_)\n    local current_buf = vim.api.nvim_get_current_buf()\n    local current_pos = vim.lsp.util.make_position_params()\n\n    -- if current buffer is sidebar's own buffer, use previous buffer\n    if current_buf ~= view.View.bufnr then\n        last_buffer = current_buf\n        last_pos = current_pos\n    else\n        current_buf = last_buffer\n        current_pos = last_pos\n    end\n\n    if\n        current_buf == view.View.bufnr\n        or not current_buf\n        or not vim.api.nvim_buf_is_loaded(current_buf)\n        or vim.api.nvim_buf_get_option(current_buf, \"buftype\") ~= \"\"\n    then\n        loclist:clear()\n        return\n    end\n\n    local clients = vim.lsp.buf_get_clients(current_buf)\n    local clients_filtered = vim.tbl_filter(function(client)\n        return client.supports_method(\"textDocument/documentSymbol\")\n    end, clients)\n\n    if #clients_filtered == 0 then\n        loclist:clear()\n        return\n    end\n\n    vim.lsp.buf_request(current_buf, \"textDocument/documentSymbol\", current_pos, function(err, method, symbols)\n        if vim.fn.has(\"nvim-0.5.1\") == 1 or vim.fn.has(\"nvim-0.8\") == 1 then\n            symbols = method\n        end\n\n        local loclist_items = {}\n        local filepath = vim.api.nvim_buf_get_name(current_buf)\n        if err ~= nil then\n            return\n        end\n\n        if symbols ~= nil then\n            build_loclist(filepath, loclist_items, symbols, 1)\n            loclist:set_items(loclist_items, { remove_groups = false })\n        end\n    end)\nend\n\nreturn {\n    title = \"Symbols\",\n    icon = config[\"symbols\"].icon,\n    draw = function(ctx)\n        local lines = {}\n        local hl = {}\n\n        get_symbols(ctx)\n\n        loclist:draw(ctx, lines, hl)\n\n        if lines == nil or #lines == 0 then\n            return \"<no symbols>\"\n        else\n            return { lines = lines, hl = hl }\n        end\n    end,\n    highlights = {\n        groups = {},\n        links = {\n            SidebarNvimSymbolsName = \"SidebarNvimNormal\",\n            SidebarNvimSymbolsDetail = \"SidebarNvimLineNr\",\n        },\n    },\n    bindings = {\n        [\"t\"] = function(line)\n            local location = loclist:get_location_at(line)\n            if location == nil then\n                return\n            end\n            local symbol = location.data.symbol\n            local key = symbol.name .. symbol.range.start.line .. symbol.range.start.character\n\n            if open_symbols[key] == nil then\n                open_symbols[key] = true\n            else\n                open_symbols[key] = nil\n            end\n        end,\n        [\"e\"] = function(line)\n            local location = loclist:get_location_at(line)\n            if location == nil then\n                return\n            end\n\n            local symbol = location.data.symbol\n\n            vim.cmd(\"wincmd p\")\n            vim.cmd(\"e \" .. location.data.filepath)\n            vim.fn.cursor(symbol.range.start.line + 1, symbol.range.start.character + 1)\n        end,\n    },\n}\n"
  },
  {
    "path": "lua/sidebar-nvim/builtin/todos.lua",
    "content": "local utils = require(\"sidebar-nvim.utils\")\nlocal Loclist = require(\"sidebar-nvim.components.loclist\")\nlocal config = require(\"sidebar-nvim.config\")\nlocal luv = vim.loop\n\nlocal loclist = Loclist:new({\n    groups_initially_closed = config.todos.initially_closed,\n    show_empty_groups = false,\n})\n\n-- Make sure all groups exist\nloclist:add_group(\"TODO\")\nloclist:add_group(\"HACK\")\nloclist:add_group(\"WARN\")\nloclist:add_group(\"PERF\")\nloclist:add_group(\"NOTE\")\nloclist:add_group(\"FIX\")\n\nlocal icons = {\n    TODO = { text = \"\", hl = \"SidebarNvimTodoIconTodo\" },\n    HACK = { text = \"\", hl = \"SidebarNvimTodoIconHack\" },\n    WARN = { text = \"\", hl = \"SidebarNvimTodoIconWarn\" },\n    PERF = { text = \"\", hl = \"SidebarNvimTodoIconPerf\" },\n    NOTE = { text = \"\", hl = \"SidebarNvimTodoIconNote\" },\n    FIX = { text = \"\", hl = \"SidebarNvimTodoIconFix\" },\n}\n\nlocal current_path_ignored_cache = false\n\nlocal function is_current_path_ignored()\n    local cwd = vim.loop.cwd()\n    for _, path in pairs(config.todos.ignored_paths or {}) do\n        if vim.fn.expand(path) == cwd then\n            return true\n        end\n    end\n\n    return false\nend\n\nlocal function async_update(ctx)\n    current_path_ignored_cache = is_current_path_ignored()\n    if current_path_ignored_cache then\n        return\n    end\n\n    local todos = {}\n\n    local stdout = luv.new_pipe(false)\n    local stderr = luv.new_pipe(false)\n    local handle\n    local cmd\n    local args\n    local keywords_regex = [[(TODO|NOTE|FIX|PERF|HACK|WARN)]]\n    local regex_end = [[\\s*(\\(.*\\))?:.*]]\n\n    -- Use ripgrep by default, if it's installed\n    if vim.fn.executable(\"rg\") == 1 then\n        cmd = \"rg\"\n        args = {\n            \"--no-hidden\",\n            \"--column\",\n            \"--only-matching\",\n            keywords_regex .. regex_end,\n        }\n    else\n        cmd = \"git\"\n        args = { \"grep\", \"-no\", \"--column\", \"-EI\", keywords_regex .. regex_end }\n    end\n\n    handle = luv.spawn(cmd, {\n        args = args,\n        stdio = { nil, stdout, stderr },\n        cmd = luv.cwd(),\n    }, function()\n        local loclist_items = {}\n        for _, items in pairs(todos) do\n            for _, item in ipairs(items) do\n                table.insert(loclist_items, {\n                    group = item.tag,\n                    left = {\n                        icons[item.tag],\n                        { text = \" \" .. item.lnum, hl = \"SidebarNvimTodoLineNumber\" },\n                        { text = \":\" },\n                        { text = item.col, hl = \"SidebarNvimTodoColNumber\" },\n                        { text = utils.truncate(item.text, ctx.width / 2) },\n                    },\n                    right = {\n                        {\n                            text = utils.filename(item.filepath),\n                            hl = \"SidebarNvimLineNr\",\n                        },\n                    },\n                    filepath = item.filepath,\n                    order = item.filepath,\n                    lnum = item.lnum,\n                    col = item.col,\n                })\n            end\n        end\n        loclist:set_items(loclist_items, { remove_groups = false })\n\n        luv.read_stop(stdout)\n        luv.read_stop(stderr)\n        stdout:close()\n        stderr:close()\n        handle:close()\n    end)\n\n    luv.read_start(stdout, function(err, data)\n        if data == nil then\n            return\n        end\n\n        for _, line in ipairs(vim.split(data, \"\\n\")) do\n            if line ~= \"\" then\n                local filepath, lnum, col, tag, text = line:match(\"^(.+):(%d+):(%d+):([%w%(%)]+):(.*)$\")\n\n                if filepath and tag then\n                    local tag_with_scope = { tag:match(\"(%w+)%(.*%)\") }\n                    if #tag_with_scope > 0 then\n                        tag = tag_with_scope[1]\n                    end\n\n                    if not todos[tag] then\n                        todos[tag] = {}\n                    end\n\n                    local category_tbl = todos[tag]\n\n                    category_tbl[#category_tbl + 1] = {\n                        filepath = filepath,\n                        lnum = lnum,\n                        col = col,\n                        tag = tag,\n                        text = text,\n                    }\n                end\n            end\n        end\n\n        if err ~= nil then\n            vim.schedule(function()\n                utils.echo_warning(err)\n            end)\n        end\n    end)\n\n    luv.read_start(stderr, function(err, data)\n        if data == nil then\n            return\n        end\n\n        if err ~= nil then\n            vim.schedule(function()\n                utils.echo_warning(err)\n            end)\n        end\n    end)\nend\n\nreturn {\n    title = \"TODOs\",\n    icon = config.todos.icon,\n    draw = function(ctx)\n        local lines = {}\n        local hl = {}\n\n        if current_path_ignored_cache then\n            lines = { \"<path ignored>\" }\n        end\n\n        loclist:draw(ctx, lines, hl)\n\n        if #lines == 0 then\n            lines = { \"<no TODOs>\" }\n        end\n\n        return { lines = lines, hl = hl }\n    end,\n    highlights = {\n        groups = {},\n        links = {\n            SidebarNvimTodoFilename = \"SidebarNvimLineNr\",\n            SidebarNvimTodoLineNumber = \"SidebarNvimLineNr\",\n            SidebarNvimTodoColNumber = \"SidebarNvimLineNr\",\n            SidebarNvimTodoIconTodo = \"DiagnosticInfo\",\n            SidebarNvimTodoIconHack = \"DiagnosticWarning\",\n            SidebarNvimTodoIconWarn = \"DiagnosticWarning\",\n            SidebarNvimTodoIconPerf = \"DiagnosticError\",\n            SidebarNvimTodoIconNote = \"DiagnosticHint\",\n            SidebarNvimTodoIconFix = \"DiagnosticError\",\n        },\n    },\n    bindings = {\n        [\"t\"] = function(line)\n            loclist:toggle_group_at(line)\n        end,\n        [\"e\"] = function(line)\n            local location = loclist:get_location_at(line)\n            if not location then\n                return\n            end\n            vim.cmd(\"wincmd p\")\n            vim.cmd(\"e \" .. location.filepath)\n            vim.fn.cursor(location.lnum, location.col)\n        end,\n    },\n    setup = function(ctx)\n        async_update(ctx)\n    end,\n    update = function(ctx)\n        async_update(ctx)\n    end,\n    toggle_all = function()\n        loclist:toggle_all_groups()\n    end,\n    close_all = function()\n        loclist:close_all_groups()\n    end,\n    open_all = function()\n        loclist:open_all_groups()\n    end,\n    open = function(group)\n        loclist:open_group(group)\n    end,\n    close = function(group)\n        loclist:close_group(group)\n    end,\n    toggle = function(group)\n        loclist:toggle_group(group)\n    end,\n}\n"
  },
  {
    "path": "lua/sidebar-nvim/colors.lua",
    "content": "local api = vim.api\n\nlocal M = {}\n\nlocal function get_hl_groups()\n    return {}\nend\n\nlocal function get_links()\n    return {\n        SidebarNvimSectionTitle = \"Directory\",\n        SidebarNvimSectionSeperator = \"Comment\",\n        SidebarNvimSectionTitleSeperator = \"Comment\",\n        SidebarNvimNormal = \"Normal\",\n        SidebarNvimLabel = \"Label\",\n        SidebarNvimComment = \"Comment\",\n        SidebarNvimLineNr = \"LineNr\",\n        SidebarNvimKeyword = \"Keyword\",\n    }\nend\n\nfunction M.def_hl_group(group, gui, fg, bg)\n    gui = gui and \" gui=\" .. gui or \"\"\n    fg = fg and \" guifg=\" .. fg or \"\"\n    bg = bg and \" guibg=\" .. bg or \"\"\n\n    api.nvim_command(\"hi def \" .. group .. gui .. fg .. bg)\nend\n\nfunction M.def_hl_link(group, link_to)\n    api.nvim_command(\"hi def link \" .. group .. \" \" .. link_to)\nend\n\nfunction M.setup()\n    local higlight_groups = get_hl_groups()\n    for k, d in pairs(higlight_groups) do\n        M.def_hl_group(k, d.gui, d.fg, d.bg)\n    end\n\n    local links = get_links()\n    for k, d in pairs(links) do\n        M.def_hl_link(k, d)\n    end\nend\n\nreturn M\n"
  },
  {
    "path": "lua/sidebar-nvim/components/basic.lua",
    "content": "local Component = {}\n\n-- convert the current data structure into a list of lines + highlight groups\n-- @param (table) ctx the draw context\n-- @param (table) list of lines (strings)\n-- @param (table) list of hl groups\n-- luacheck: push ignore\nfunction Component:draw(ctx, section_lines, section_hl) end\n-- luacheck: pop\n\nreturn Component\n"
  },
  {
    "path": "lua/sidebar-nvim/components/loclist.lua",
    "content": "local Component = require(\"sidebar-nvim.components.basic\")\n\nlocal Loclist = {}\n\nLoclist.DEFAULT_OPTIONS = {\n    groups = {},\n    group_icon = { closed = \"\", opened = \"\" },\n    -- badge showing the number of items in each group\n    show_group_count = true,\n    -- if empty groups should be displayed\n    show_empty_groups = true,\n    -- if there's a single group, skip rendering the group controls\n    omit_single_group = false,\n    -- initial state of the groups\n    groups_initially_closed = false,\n    -- highlight groups for each control element\n    highlights = {\n        group = \"SidebarNvimLabel\",\n        group_count = \"SidebarNvimSectionTitle\",\n    },\n}\n\nsetmetatable(Loclist, { __index = Component })\n\n-- creates a new loclist component\n-- @param (table) o\n-- |- (table) o.groups list of groups containing (table) items. See Loclist:add_item\n-- |- (boolean) o.show_group_count show a badge after the group name with the count of items contained in the group\n-- |- (boolean) o.omit_single_group whether this component should draw the group line if there's only one group present\nfunction Loclist:new(o)\n    o = vim.tbl_deep_extend(\"force\", vim.deepcopy(Loclist.DEFAULT_OPTIONS), o or {}, {\n        -- table(line_number -> group ref)\n        _group_indexes = {},\n        -- table(line__number -> item ref)\n        _location_indexes = {},\n        -- used to keep the group list stable\n        _group_keys = {},\n    })\n\n    o._group_keys = vim.tbl_keys(o.groups or {})\n\n    setmetatable(o, self)\n    self.__index = self\n    return o\nend\n\n-- adds a new item to the loclist\n-- @param (table) item\n-- |- (string) item.group the group name that this item will live\n-- |- (number) item.lnum the line number of this item\n-- |- (number) item.col the col number of this item\n-- |- (table|array) item.left\n-- |--|- (string) item.left[n].text = \"abc\"\n-- |--|- (string) item.left[n].hl = \"<highlight group>\"\n-- |- (table|array) item.right\n-- |--|- (string) item.left[n].text = \"abc\"\n-- |--|- (string) item.left[n].hl = \"<highlight group>\"\n-- |- (number) item.order items are sorted based on order within each group\nfunction Loclist:add_item(item)\n    if not self.groups[item.group] then\n        self.groups[item.group] = { is_closed = self.groups_initially_closed or false }\n    end\n\n    if not vim.tbl_contains(self._group_keys, item.group) then\n        table.insert(self._group_keys, item.group)\n    end\n\n    local group_tbl = self.groups[item.group]\n    group_tbl[#group_tbl + 1] = item\n\n    if item.order then\n        table.sort(self.groups[item.group], function(a, b)\n            return a.order < b.order\n        end)\n    else\n        item.order = 0\n    end\nend\n\n-- replace all the items with the new list\n-- @param (table) list of items\n-- |- items[...]\n-- |-- (string) item.group the group name that this item will live\n-- |-- (number) item.lnum the line number of this item\n-- |-- (number) item.col the col number of this item\n-- |- (table|array) item.left\n-- |--|- (string) item.left[n].text = \"abc\"\n-- |--|- (string) item.left[n].hl = \"<highlight group>\"\n-- |- (table|array) item.right\n-- |--|- (string) item.left[n].text = \"abc\"\n-- |--|- (string) item.left[n].hl = \"<highlight group>\"\n-- |- clear_opts (table) see Loclist:clear\nfunction Loclist:set_items(items, clear_opts)\n    self:clear(clear_opts)\n\n    for _, item in ipairs(items) do\n        self:add_item(item)\n    end\n\n    if clear_opts and clear_opts.remove_groups then\n        self._group_keys = vim.tbl_keys(self.groups)\n    end\nend\n\n-- add an empty group\n-- @param group string: name of the group\nfunction Loclist:add_group(group)\n    if not self.groups[group] then\n        self.groups[group] = { is_closed = true }\n        self._group_keys[#self._group_keys + 1] = group\n    end\nend\n\n-- clear all the groups\n-- @param opts (table)\n-- |- opts.remove_groups (boolean) also remove groups from the list, otherwise only items will be removed, removing groups from the list also means that the state of groups will be cleared\nfunction Loclist:clear(opts)\n    opts = opts or {}\n\n    if opts.remove_groups then\n        self.groups = {}\n        self._group_keys = {}\n        return\n    end\n\n    for _, key in ipairs(self._group_keys) do\n        self.groups[key] = { is_closed = self.groups[key].is_closed }\n    end\nend\n\nfunction Loclist:draw_group(ctx, group_name, with_label, section_lines, section_hl)\n    local group = self.groups[group_name]\n\n    if #group == 0 and not self.show_empty_groups then\n        return\n    end\n\n    if with_label then\n        local icon = self.group_icon.opened\n        if #group == 0 or group.is_closed then\n            icon = self.group_icon.closed\n        end\n\n        local group_title = icon .. \" \" .. group_name\n\n        local line = group_title\n\n        if line:len() > ctx.width - 1 then\n            line = line:sub(1, ctx.width - 5) .. \"...\"\n        end\n\n        table.insert(section_hl, { self.highlights.group, #section_lines, 0, #line })\n        if self.show_group_count then\n            table.insert(section_hl, { self.highlights.group_count, #section_lines, #line, -1 })\n            local total = #group\n            if total > 99 then\n                total = \"++\"\n            end\n            line = line .. \" (\" .. total .. \")\"\n        end\n\n        self._group_indexes[#section_lines] = group\n        table.insert(section_lines, line)\n    end\n\n    if group.is_closed then\n        return\n    end\n\n    for _, item in ipairs(group) do\n        self._location_indexes[#section_lines] = item\n        local line = \"\"\n\n        if with_label then\n            line = \"  \"\n        end\n\n        if type(item.left) == \"table\" and #item.left ~= 0 then\n            for _, i in ipairs(item.left) do\n                if i ~= nil and i.text ~= nil then\n                    -- Calculate space left in line\n                    local space_left = ctx.width - #line\n\n                    -- Break if line is already full\n                    if space_left <= 0 then\n                        break\n                    end\n\n                    if i.hl then\n                        table.insert(section_hl, { i.hl, #section_lines, #line, -1 })\n                    else\n                        table.insert(section_hl, { \"SidebarNvimNormal\", #section_lines, #line, -1 })\n                    end\n                    line = line .. tostring(i.text):sub(1, space_left)\n                end\n            end\n        end\n\n        if type(item.right) == \"table\" and #item.right ~= 0 then\n            local temp_line = \"\"\n            local temp_hl = {}\n\n            for _, i in ipairs(item.right) do\n                if i ~= nil and i.text ~= nil then\n                    -- Calculate space left in line\n                    local space_left = ctx.width - #line - #temp_line - 1\n\n                    -- Break if line is already full\n                    if space_left <= 0 then\n                        break\n                    end\n\n                    if i.hl then\n                        table.insert(temp_hl, { i.hl, #section_lines, #line + #temp_line, -1 })\n                    else\n                        table.insert(temp_hl, { \"SidebarNvimNormal\", #section_lines, #line + #temp_line, -1 })\n                    end\n\n                    temp_line = temp_line .. i.text:sub(1, space_left)\n                end\n            end\n\n            -- Calculate offset and add empty space in the middle\n            local offset = ctx.width - #temp_line - #line\n            local gap = string.rep(\" \", offset)\n            line = line .. gap .. temp_line\n\n            -- Add highlights accounting for offset\n            for _, hl in ipairs(temp_hl) do\n                table.insert(section_hl, { hl[1], hl[2], hl[3] + offset, hl[4] })\n            end\n        end\n\n        table.insert(section_lines, line)\n    end\nend\n\n-- convert the current data structure into a list of lines + highlight groups\n-- @return (table) list of lines (strings)\n-- @return (table) list of hl groups\nfunction Loclist:draw(ctx, section_lines, section_hl)\n    self._group_indexes = {}\n    self._location_indexes = {}\n\n    if #self._group_keys == 1 and self.omit_single_group then\n        self:draw_group(ctx, self._group_keys[1], false, section_lines, section_hl)\n        return\n    end\n\n    for _, group_name in ipairs(self._group_keys) do\n        self:draw_group(ctx, group_name, true, section_lines, section_hl)\n    end\nend\n\n-- returns the location specified in the location printed on line `line`\n-- if the line does not have a location rendered, return nil\n-- @param (number) line\nfunction Loclist:get_location_at(line)\n    local location = self._location_indexes[line]\n    return location\nend\n\n-- toggles the group open/close that is printed on line `line`\n-- if there is no group at `line`, then do nothing\n-- @param (number) line\nfunction Loclist:toggle_group_at(line)\n    local group = self._group_indexes[line]\n    if not group then\n        return\n    end\n\n    group.is_closed = not group.is_closed\nend\n\n-- Toggle group with name `group_name`\n-- @param group_name string: the name of group to toggle\nfunction Loclist:toggle_group(group_name)\n    local group = self.groups[group_name]\n    if not group then\n        return\n    end\n\n    group.is_closed = not group.is_closed\nend\n\n-- Open group with name `group_name`\n-- @param group_name string: the name of group to open\nfunction Loclist:open_group(group_name)\n    local group = self.groups[group_name]\n    if not group then\n        return\n    end\n\n    group.is_closed = false\nend\n\n-- Close group with name `group_name`\n-- @param group_name string: the name of group to close\nfunction Loclist:close_group(group_name)\n    local group = self.groups[group_name]\n    if not group then\n        return\n    end\n\n    group.is_closed = true\nend\n\n-- toggle all groups\nfunction Loclist:toggle_all_groups()\n    for _, group in pairs(self.groups) do\n        group.is_closed = not group.is_closed\n    end\nend\n\n-- opens all groups\nfunction Loclist:open_all_groups()\n    for _, group in pairs(self.groups) do\n        group.is_closed = false\n    end\nend\n\n-- closes all groups\nfunction Loclist:close_all_groups()\n    for _, group in pairs(self.groups) do\n        group.is_closed = true\n    end\nend\n\nreturn Loclist\n"
  },
  {
    "path": "lua/sidebar-nvim/config.lua",
    "content": "local M = {}\n\nM.disable_default_keybindings = 0\nM.bindings = nil\nM.side = \"left\"\nM.initial_width = 35\n\nM.hide_statusline = false\n\nM.update_interval = 1000\n\nM.enable_profile = false\n\nM.sections = { \"datetime\", \"git\", \"diagnostics\" }\n\nM.section_separator = { \"\", \"-----\", \"\" }\n\nM.section_title_separator = { \"\" }\n\nM.git = { icon = \"\" }\n\nM.diagnostics = { icon = \"\" }\n\nM.buffers = {\n    icon = \"\",\n    ignored_buffers = {},\n    sorting = \"id\",\n    show_numbers = true,\n    ignore_not_loaded = false,\n    ignore_terminal = true,\n}\n\nM.symbols = { icon = \"ƒ\" }\n\nM.containers = { icon = \"\", use_podman = false, attach_shell = \"/bin/sh\", show_all = true, interval = 5000 }\n\nM.datetime = { icon = \"\", format = \"%a %b %d, %H:%M\", clocks = { { name = \"local\" } } }\n\nM.todos = { icon = \"\", ignored_paths = { \"~\" }, initially_closed = false }\n\nM.files = { icon = \"\", show_hidden = false, ignored_paths = { \"%.git$\" } }\n\nreturn M\n"
  },
  {
    "path": "lua/sidebar-nvim/debouncer.lua",
    "content": "local luv = vim.loop\n\nlocal Debouncer = {}\n\nfunction Debouncer:new(fn, delay)\n    local o = { fn = fn, delay = delay, locked = false }\n\n    setmetatable(o, self)\n    self.__index = self\n\n    return o\nend\n\nfunction Debouncer:start_timer()\n    if self.timer then\n        self.timer:stop()\n        self.timer:close()\n        self.timer = nil\n    end\n\n    self.timer = luv.new_timer()\n    self.timer:start(\n        self.delay,\n        0,\n        vim.schedule_wrap(function()\n            self.locked = false\n            self.timer:stop()\n            self.timer:close()\n            self.timer = nil\n        end)\n    )\nend\n\nfunction Debouncer:call(...)\n    local args = { ... }\n    vim.schedule(function()\n        if self.locked then\n            return\n        end\n\n        self.locked = true\n        self.fn(unpack(args))\n        self:start_timer()\n    end)\nend\n\nreturn Debouncer\n"
  },
  {
    "path": "lua/sidebar-nvim/docker_utils.lua",
    "content": "local config = require(\"sidebar-nvim.config\")\nlocal luv = vim.loop\n\nlocal M = {}\n\nfunction M.get_docker_bin()\n    local bin = \"docker\"\n\n    if config.containers.use_podman then\n        bin = \"podman\"\n    end\n\n    return bin\nend\n\nfunction M.build_docker_command(args, stdout, stderr)\n    local bin = M.get_docker_bin()\n\n    args = args or {}\n    -- make sure the command only fetches the bare minimum fields to work\n    -- otherwise docker goes crazy with high load. See https://github.com/sidebar-nvim/sidebar.nvim/issues/3\n    -- we also need to make sure that each line has valid json syntax so the parser can understand\n    -- the formatting string below is to make sure we reconstruct the json object with only the fields we want\n    table.insert(args, '--format=\\'{\"Names\": {{json .Names}}, \"State\": {{json .State}}, \"ID\": {{json .ID}} }\\'')\n\n    return { bin = bin, opts = { args = args, stdio = { nil, stdout, stderr }, cwd = luv.cwd() } }\nend\n\nfunction M.build_docker_attach_command(container_id)\n    local bin = M.get_docker_bin()\n\n    return bin .. \" exec -it \" .. container_id .. \" \" .. config.containers.attach_shell\nend\n\nreturn M\n"
  },
  {
    "path": "lua/sidebar-nvim/events.lua",
    "content": "local M = {}\n\nlocal global_handlers = {}\n\nlocal Event = { Ready = \"Ready\" }\n\nlocal function get_handlers(event_name)\n    return global_handlers[event_name] or {}\nend\n\nlocal function register_handler(event_name, handler)\n    local handlers = get_handlers(event_name)\n    table.insert(handlers, handler)\n    global_handlers[event_name] = handlers\nend\n\nlocal function dispatch(event_name, payload)\n    for _, handler in pairs(get_handlers(event_name)) do\n        local success, error = pcall(handler, payload)\n        if not success then\n            vim.api.nvim_err_writeln(\"Handler for event \" .. event_name .. \" errored. \" .. vim.inspect(error))\n        end\n    end\nend\n\n-- @private\nfunction M._dispatch_ready()\n    dispatch(Event.Ready)\nend\n\n-- Registers a handler for the Ready event.\n-- @param handler (function) Handler with the signature `function()`\nfunction M.on_sidebar_nvim_ready(handler)\n    register_handler(Event.Ready, handler)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/sidebar-nvim/lib.lua",
    "content": "local luv = vim.loop\nlocal api = vim.api\n\nlocal renderer = require(\"sidebar-nvim.renderer\")\nlocal view = require(\"sidebar-nvim.view\")\nlocal events = require(\"sidebar-nvim.events\")\nlocal updater = require(\"sidebar-nvim.updater\")\nlocal config = require(\"sidebar-nvim.config\")\nlocal bindings = require(\"sidebar-nvim.bindings\")\nlocal utils = require(\"sidebar-nvim.utils\")\n\nlocal first_init_done = false\n\nlocal M = {}\n\nM.State = { section_line_indexes = {} }\n\nM.timer = nil\n\nlocal function _redraw()\n    if vim.v.exiting ~= vim.NIL then\n        return\n    end\n\n    M.State.section_line_indexes = renderer.draw(updater.sections_data)\nend\n\nlocal function loop()\n    if not view.is_win_open({ any_tabpage = true }) then\n        return\n    end\n\n    updater.draw()\n    _redraw()\nend\n\nlocal function _start_timer(should_delay)\n    M.timer = luv.new_timer()\n\n    local delay = 100\n    if should_delay then\n        delay = config.update_interval\n    end\n\n    -- wait `delay`ms and then repeats every `config.update_interval`ms\n    M.timer:start(\n        delay,\n        config.update_interval,\n        vim.schedule_wrap(function()\n            loop()\n        end)\n    )\nend\n\nfunction M.setup()\n    _redraw()\n\n    _start_timer(false)\n\n    if not first_init_done then\n        events._dispatch_ready()\n        first_init_done = true\n    end\nend\n\nfunction M.update()\n    if M.timer ~= nil then\n        M.timer:stop()\n        M.timer:close()\n        M.timer = nil\n    end\n\n    if view.is_win_open({ any_tabpage = true }) then\n        updater.update()\n    end\n    loop()\n\n    _start_timer(true)\nend\n\nfunction M.open(opts)\n    view.open(opts or { focus = false })\n    M.update()\nend\n\nfunction M.close()\n    if view.is_win_open() then\n        view.close()\n    end\nend\n\nfunction M.toggle(opts)\n    if view.is_win_open() then\n        M.close()\n        return\n    end\n\n    M.open(opts)\nend\n\n-- Resize the sidebar to the requested size\n-- @param size number\nfunction M.resize(size)\n    view.View.width = size\n    view.resize()\nend\n\n-- @param opts table\n-- @param |- opts.any_tabpage boolean if true check if is open in any tabpage, if false check in current tab\nfunction M.is_open(opts)\n    return view.is_win_open(opts)\nend\n--\n-- Focus or open the sidebar\n-- @param opts table\n-- @param opts.section_index number\n-- @param opts.cursor_at_content boolean\nfunction M.focus(opts)\n    if view.is_win_open() then\n        local winnr = view.get_winnr()\n        view.focus(winnr)\n    else\n        M.open({ focus = true })\n    end\n\n    if opts and opts.section_index then\n        local content_only = true\n\n        if opts.cursor_at_content == false then\n            content_only = false\n        end\n\n        local cursor = M.find_cursor_at_section_index(opts.section_index, { content_only = content_only })\n\n        if cursor then\n            api.nvim_win_set_cursor(0, cursor)\n        end\n    end\nend\n\n--- Returns the window width for sidebar-nvim within the tabpage specified\n---@param tabpage number: (optional) the number of the chosen tabpage. Defaults to current tabpage.\n---@return number\nfunction M.get_width(tabpage)\n    return view.get_width(tabpage)\nend\n\nfunction M.destroy()\n    view.close()\n\n    if M.timer ~= nil then\n        M.timer:stop()\n        M.timer:close()\n        M.timer = nil\n    end\n\n    view._wipe_rogue_buffer()\nend\n\nlocal function get_start_line(content_only, indexes)\n    if content_only then\n        return indexes.content_start\n    end\n\n    return indexes.section_start\nend\n\nlocal function get_end_line(content_only, indexes)\n    if content_only then\n        return indexes.content_start + indexes.content_length\n    end\n\n    return indexes.section_start + indexes.section_length - 1\nend\n\n-- @param opts: table\n-- @param opts.content_only: boolean = whether the it should only check if the cursor is hovering the contents of the section\n-- @return table{section_index = number, section_content_current_line = number, cursor_col = number, cursor_line = number)\nfunction M.find_section_at_cursor(opts)\n    opts = opts or { content_only = true }\n\n    local cursor = opts.cursor or api.nvim_win_get_cursor(0)\n    local cursor_line = cursor[1]\n    local cursor_col = cursor[2]\n\n    for section_index, section_line_index in ipairs(M.State.section_line_indexes) do\n        local start_line = get_start_line(opts.content_only, section_line_index)\n        local end_line = get_end_line(opts.content_only, section_line_index)\n        -- check if the start of this section is after the cursor line\n\n        if cursor_line >= start_line and cursor_line <= end_line then\n            return {\n                section_index = section_index,\n                section_content_current_line = cursor_line - section_line_index.content_start,\n                cursor_line = cursor_line,\n                cursor_col = cursor_col,\n                line_index = section_line_index,\n            }\n        end\n    end\n\n    return nil\nend\n\n-- this is the oposite of `find_section_at_cursor`, given a section index, find the current line in the buffer\n-- @param index number\n-- @param opts table\n-- @param |- opts.content_only boolean whether the cursor should be placed at the first line of content or the section title\n-- @return table with cursor {line: number, col: number}\n-- @return nil\nfunction M.find_cursor_at_section_index(index, opts)\n    opts = opts or { content_only = false }\n\n    local cursor = { 0, 0 }\n\n    for section_index, section_line_index in ipairs(M.State.section_line_indexes) do\n        if section_index == index then\n            local start_line = get_start_line(opts.content_only, section_line_index)\n\n            cursor[1] = start_line\n            return cursor\n        end\n    end\n\n    return nil\nend\n\nfunction M.on_keypress(key)\n    local section_match = M.find_section_at_cursor()\n    bindings.on_keypress(utils.unescape_keycode(key), section_match)\n    M.update()\nend\n\nfunction M.on_cursor_move(direction)\n    local cursor = api.nvim_win_get_cursor(0)\n    local line = cursor[1]\n\n    local current_section = M.find_section_at_cursor({ content_only = false })\n\n    if not current_section then\n        current_section = M.find_section_at_cursor({ content_only = false, cursor = { 1, 1 } })\n    end\n\n    local current_section_index = current_section.section_index\n    local current_line_index = current_section.line_index\n\n    local next_line = line + 1\n    if direction == \"up\" then\n        next_line = line - 1\n    end\n\n    if direction == \"down\" then\n        if next_line > current_line_index.section_start and next_line < current_line_index.content_start then\n            next_line = current_line_index.content_start\n        elseif next_line >= current_line_index.content_start + current_line_index.content_length then\n            local next_section = M.State.section_line_indexes[current_section_index + 1]\n            if not next_section then\n                return\n            end\n            next_line = next_section.section_start\n        end\n    else\n        if next_line < current_line_index.section_start then\n            local next_section = M.State.section_line_indexes[current_section_index - 1]\n            if not next_section then\n                return\n            end\n            next_line = next_section.content_start + next_section.content_length - 1\n        elseif next_line < current_line_index.content_start and next_line > current_line_index.section_start then\n            next_line = current_line_index.section_start\n        end\n    end\n\n    api.nvim_win_set_cursor(0, { next_line, 1 })\nend\n\nfunction M.on_tab_change()\n    vim.schedule(function()\n        if not view.is_win_open() and view.is_win_open({ any_tabpage = true }) then\n            view.open({ focus = false })\n        end\n    end)\nend\n\nfunction M.on_win_leave()\n    vim.defer_fn(function()\n        if not view.is_win_open() then\n            return\n        end\n\n        local windows = api.nvim_list_wins()\n        local curtab = api.nvim_get_current_tabpage()\n        local wins_in_tabpage = vim.tbl_filter(function(w)\n            return api.nvim_win_get_tabpage(w) == curtab\n        end, windows)\n        if #windows == 1 then\n            M.close()\n        elseif #wins_in_tabpage == 1 then\n            api.nvim_command(\":tabclose\")\n        end\n    end, 50)\nend\n\nfunction M.on_vim_leave()\n    M.destroy()\nend\n\nreturn M\n"
  },
  {
    "path": "lua/sidebar-nvim/profile.lua",
    "content": "local config = require(\"sidebar-nvim.config\")\n\nlocal M = {}\n\nM.entries = {}\n\nfunction M.clear()\n    M.entries = {}\nend\n\nfunction M.add_point(name, value, unit)\n    M.entries[name] = { value = value, unit = unit }\nend\n\nfunction M.run(name, fn, ...)\n    if not config.enable_profile then\n        return fn(...)\n    end\n\n    local time_before = vim.loop.hrtime()\n\n    local ret = { fn(...) }\n\n    local duration = vim.loop.hrtime() - time_before\n\n    M.add_point(name, duration, \"ns\")\n\n    return unpack(ret)\nend\n\nfunction M.wrap_fn(name, fn)\n    return function(...)\n        return M.run(name, fn, ...)\n    end\nend\n\nfunction M.print_summary(filter)\n    local filtered_keys = vim.tbl_keys(M.entries)\n\n    if filter then\n        filtered_keys = vim.tbl_filter(function(entry)\n            return vim.tbl_contains(filter, entry)\n        end, vim.tbl_keys(M.entries))\n    end\n\n    local entries = {}\n    for _, key in ipairs(filtered_keys) do\n        table.insert(entries, vim.tbl_deep_extend(\"force\", { name = key }, M.entries[key]))\n    end\n\n    -- TODO: convert units\n\n    table.sort(entries, function(a, b)\n        -- reverse sort\n        return a.value > b.value\n    end)\n\n    for _, entry in ipairs(entries) do\n        print(string.format(\"Name: %s Value: %f\", entry.name, entry.value / 1000000))\n    end\nend\n\nreturn M\n"
  },
  {
    "path": "lua/sidebar-nvim/renderer.lua",
    "content": "local view = require(\"sidebar-nvim.view\")\nlocal config = require(\"sidebar-nvim.config\")\nlocal utils = require(\"sidebar-nvim.utils\")\nlocal profile = require(\"sidebar-nvim.profile\")\n\nlocal api = vim.api\n\nlocal namespace_id = api.nvim_create_namespace(\"SidebarNvimHighlights\")\n\nlocal M = {}\n\n-- extracted from this PR: https://github.com/sidebar-nvim/sidebar.nvim/pull/41\n-- thanks @lambdahands\nlocal function sanitize_lines(lines)\n    local lines_ = {}\n    for _, line_ in ipairs(lines) do\n        local line = string.gsub(line_, \"[\\n\\r]\", \" \")\n        table.insert(lines_, line)\n    end\n    return lines_\nend\n\nlocal function expand_section_lines(section_lines, lines_offset)\n    if type(section_lines) == \"string\" then\n        return vim.split(section_lines, \"\\n\"), nil\n    elseif type(section_lines) == \"table\" and section_lines.lines == nil then\n        return sanitize_lines(section_lines), nil\n    end\n\n    -- we have here section_lines = { lines = string|table of strings, hl = table }\n\n    local section_hl = section_lines.hl or {}\n    section_lines = section_lines.lines\n\n    if type(section_lines) == \"string\" then\n        section_lines = vim.split(section_lines, \"\\n\")\n    else\n        section_lines = sanitize_lines(section_lines)\n    end\n\n    -- we must offset the hl lines so it matches the current section position\n    for _, hl_entry in ipairs(section_hl) do\n        hl_entry[2] = hl_entry[2] + lines_offset\n    end\n\n    return section_lines, section_hl\nend\n\nlocal function build_section_title(section)\n    local icon = \"#\"\n    if section.icon ~= nil then\n        icon = section.icon\n    end\n\n    if type(icon) == \"function\" then\n        icon = icon()\n    end\n\n    return icon .. \" \" .. section.title\nend\n\nlocal function build_section_separator(section, index)\n    if type(config.section_separator) == \"string\" then\n        return { config.section_separator }\n    end\n\n    if type(config.section_separator) == \"table\" then\n        return config.section_separator\n    end\n\n    if type(config.section_separator) ~= \"function\" then\n        utils.echo_warning(\"'section_separator' must be string, table or function\")\n        return\n    end\n\n    return config.section_separator(section, index)\nend\n\nlocal function build_section_title_separator(section, index)\n    if type(config.section_title_separator) == \"string\" then\n        return { config.section_title_separator }\n    end\n\n    if type(config.section_title_separator) == \"table\" then\n        return config.section_title_separator\n    end\n\n    if type(config.section_title_separator) ~= \"function\" then\n        utils.echo_warning(\"'section_title_separator' must be false, string, table or function\")\n        return\n    end\n\n    return config.section_title_separator(section, index)\nend\n\nlocal function get_lines_and_hl(sections_data)\n    local lines = {}\n    local hl = {}\n    local section_line_indexes = {}\n\n    for index, data in ipairs(sections_data) do\n        local section_title = build_section_title(data.section)\n        local section_title_length = 1\n\n        table.insert(hl, { \"SidebarNvimSectionTitle\", #lines, 0, #section_title })\n\n        local section_title_separator = build_section_title_separator(data.section, index)\n\n        local section_content_start = #lines + section_title_length + #section_title_separator + 1\n        local section_title_start = #lines + section_title_length\n        table.insert(lines, section_title)\n\n        for _, line in ipairs(section_title_separator) do\n            table.insert(hl, { \"SidebarNvimSectionTitleSeperator\", #lines, 0, #line })\n            table.insert(lines, line)\n        end\n\n        local section_lines, section_hl = expand_section_lines(data.lines, #lines)\n\n        table.insert(section_line_indexes, {\n            content_start = section_content_start,\n            content_length = #section_lines,\n            section_start = section_title_start,\n            section_length = #section_lines + section_title_length + #section_title_separator + 1,\n        })\n\n        for _, line in ipairs(section_lines) do\n            table.insert(lines, line)\n        end\n\n        for _, hl_entry in ipairs(section_hl or {}) do\n            table.insert(hl, hl_entry)\n        end\n\n        if index ~= #sections_data then\n            local separator = build_section_separator(data.section, index)\n\n            for _, line in ipairs(separator) do\n                table.insert(hl, { \"SidebarNvimSectionSeperator\", #lines, 0, #line })\n                table.insert(lines, line)\n            end\n        end\n    end\n\n    return lines, hl, section_line_indexes\nend\n\nfunction M.draw(sections_data)\n    return profile.run(\"view.render\", function()\n        if not api.nvim_buf_is_loaded(view.View.bufnr) then\n            return\n        end\n\n        local cursor\n        if view.is_win_open() then\n            cursor = api.nvim_win_get_cursor(view.get_winnr())\n        end\n\n        local lines, hl, section_line_indexes = get_lines_and_hl(sections_data)\n\n        api.nvim_buf_set_option(view.View.bufnr, \"modifiable\", true)\n        api.nvim_buf_set_lines(view.View.bufnr, 0, -1, false, lines)\n        M.render_hl(view.View.bufnr, hl)\n        api.nvim_buf_set_option(view.View.bufnr, \"modifiable\", false)\n\n        if view.is_win_open() then\n            if cursor and #lines >= cursor[1] then\n                api.nvim_win_set_cursor(view.get_winnr(), cursor)\n            end\n            if cursor then\n                api.nvim_win_set_option(view.get_winnr(), \"wrap\", false)\n            end\n\n            if config.hide_statusline then\n                api.nvim_win_set_option(view.get_winnr(), \"statusline\", \"%#NonText#\")\n            end\n        end\n\n        return section_line_indexes\n    end)\nend\n\nfunction M.render_hl(bufnr, hl)\n    if not api.nvim_buf_is_loaded(bufnr) then\n        return\n    end\n    api.nvim_buf_clear_namespace(bufnr, namespace_id, 0, -1)\n    for _, data in ipairs(hl) do\n        api.nvim_buf_add_highlight(bufnr, namespace_id, data[1], data[2], data[3], data[4])\n    end\nend\n\nreturn M\n"
  },
  {
    "path": "lua/sidebar-nvim/updater.lua",
    "content": "local utils = require(\"sidebar-nvim.utils\")\nlocal view = require(\"sidebar-nvim.view\")\nlocal config = require(\"sidebar-nvim.config\")\nlocal profile = require(\"sidebar-nvim.profile\")\nlocal colors = require(\"sidebar-nvim.colors\")\n\nlocal M = {}\n\n-- list of sections rendered\n-- { { lines = lines..., section = <table> }, { lines =  lines..., section = <table> } }\nM.sections_data = {}\n\nfunction M.setup()\n    if config.sections == nil then\n        return\n    end\n\n    local ctx = { width = view.get_width() }\n\n    for section_index, section_data in ipairs(config.sections) do\n        local section = utils.resolve_section(section_index, section_data)\n        if section then\n            local hl_def = section.highlights or {}\n\n            for hl_group, hl_group_data in pairs(hl_def.groups or {}) do\n                colors.def_hl_group(hl_group, hl_group_data.gui, hl_group_data.fg, hl_group_data.bg)\n            end\n\n            for hl_group, hl_group_link_to in pairs(hl_def.links or {}) do\n                colors.def_hl_link(hl_group, hl_group_link_to)\n            end\n\n            if section.setup then\n                section.setup(ctx)\n            end\n        end\n    end\nend\n\nfunction M.update()\n    return profile.run(\"update.sections.total\", function()\n        if vim.v.exiting ~= vim.NIL then\n            return\n        end\n\n        local ctx = { width = view.View.width }\n\n        for section_index, section_data in pairs(config.sections) do\n            local section = utils.resolve_section(section_index, section_data)\n\n            if section ~= nil and section.update ~= nil then\n                profile.run(\"update.sections.\" .. section_index, section.update, ctx)\n            end\n        end\n    end)\nend\n\nfunction M.draw()\n    return profile.run(\"draw.sections.total\", function()\n        if vim.v.exiting ~= vim.NIL then\n            return\n        end\n\n        M.sections_data = {}\n\n        local draw_ctx = { width = view.View.width }\n\n        for section_index, section_data in pairs(config.sections) do\n            local section = utils.resolve_section(section_index, section_data)\n\n            if section ~= nil then\n                local section_lines = profile.run(\"draw.sections.\" .. section_index, section.draw, draw_ctx)\n                local data = { lines = section_lines, section = section }\n                table.insert(M.sections_data, data)\n            end\n        end\n    end)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/sidebar-nvim/utils.lua",
    "content": "local M = {}\nlocal api = vim.api\nlocal luv = vim.loop\n\nfunction M.echo_warning(msg)\n    api.nvim_command(\"echohl WarningMsg\")\n    api.nvim_command(\"echom '[SidebarNvim] \" .. msg:gsub(\"'\", \"''\") .. \"'\")\n    api.nvim_command(\"echohl None\")\nend\n\nfunction M.escape_keycode(key)\n    return key:gsub(\"<\", \"[\"):gsub(\">\", \"]\")\nend\n\nfunction M.unescape_keycode(key)\n    return key:gsub(\"%[\", \"<\"):gsub(\"%]\", \">\")\nend\n\nfunction M.sidebar_nvim_callback(key)\n    return string.format(\":lua require('sidebar-nvim.lib').on_keypress('%s')<CR>\", M.escape_keycode(key))\nend\n\nfunction M.sidebar_nvim_cursor_move_callback(direction)\n    return string.format(\":lua require('sidebar-nvim')._on_cursor_move('%s')<CR>\", direction)\nend\n\nlocal function get_builtin_section(name)\n    local ret, section = pcall(require, \"sidebar-nvim.builtin.\" .. name)\n    if not ret then\n        M.echo_warning(\"error trying to load section: \" .. name)\n        return nil\n    end\n\n    return section\nend\n\nfunction M.resolve_section(index, section)\n    if type(section) == \"string\" then\n        return get_builtin_section(section)\n    elseif type(section) == \"table\" then\n        return section\n    end\n\n    M.echo_warning(\"invalid SidebarNvim section at: index=\" .. index .. \" section=\" .. section)\n    return nil\nend\n\nfunction M.is_instance(o, class)\n    while o do\n        o = getmetatable(o)\n        if class == o then\n            return true\n        end\n    end\n    return false\nend\n\n-- Reference: https://github.com/hoob3rt/lualine.nvim/blob/master/lua/lualine/components/filename.lua#L9\n\nlocal function count(base, pattern)\n    return select(2, string.gsub(base, pattern, \"\"))\nend\n\nfunction M.shorten_path(path, min_len)\n    if #path <= min_len then\n        return path\n    end\n\n    local sep = package.config:sub(1, 1)\n\n    for _ = 0, count(path, sep) do\n        if #path <= min_len then\n            return path\n        end\n\n        -- ('([^/])[^/]+%/', '%1/', 1)\n        path = path:gsub(string.format(\"([^%s])[^%s]+%%%s\", sep, sep, sep), \"%1\" .. sep, 1)\n    end\n\n    return path\nend\n\nfunction M.shortest_path(path)\n    local sep = package.config:sub(1, 1)\n\n    for _ = 0, count(path, sep) do\n        -- ('([^/])[^/]+%/', '%1/', 1)\n        path = path:gsub(string.format(\"([^%s])[^%s]+%%%s\", sep, sep, sep), \"%1\" .. sep, 1)\n    end\n\n    return path\nend\n\nfunction M.dir(path)\n    return path:match(\"^(.+/)\")\nend\n\nfunction M.filename(path)\n    local split = vim.split(path, \"/\")\n    return split[#split]\nend\n\nfunction M.file_exist(path)\n    local _, err = luv.fs_stat(path)\n    return err == nil\nend\n\nfunction M.truncate(s, size)\n    local length = #s\n\n    if length <= size then\n        return s\n    else\n        return s:sub(1, size) .. \"..\"\n    end\nend\n\nfunction M.async_cmd(cmd, args, callback)\n    local stdout = luv.new_pipe(false)\n    local stderr = luv.new_pipe(false)\n    local handle\n\n    handle = luv.spawn(cmd, { args = args, stdio = { nil, stdout, stderr }, cwd = luv.cwd() }, function()\n        if callback then\n            callback()\n        end\n\n        luv.read_stop(stdout)\n        luv.read_stop(stderr)\n        stdout:close()\n        stderr:close()\n        handle:close()\n    end)\n\n    luv.read_start(stdout, function(err, _)\n        if err ~= nil then\n            vim.schedule(function()\n                M.echo_warning(err)\n            end)\n        end\n    end)\n\n    luv.read_start(stderr, function(err, data)\n        if data ~= nil then\n            vim.schedule(function()\n                M.echo_warning(data)\n            end)\n        end\n\n        if err ~= nil then\n            vim.schedule(function()\n                M.echo_warning(err)\n            end)\n        end\n    end)\nend\n\n-- @param opts table\n-- @param opts.modified boolean filter buffers by modified or not\nfunction M.get_existing_buffers(opts)\n    return vim.tbl_filter(function(buf)\n        local modified_filter = true\n        if opts and opts.modified ~= nil then\n            local is_ok, is_modified = pcall(api.nvim_buf_get_option, buf, \"modified\")\n\n            if is_ok then\n                modified_filter = is_modified == opts.modified\n            end\n        end\n\n        return api.nvim_buf_is_valid(buf) and vim.fn.buflisted(buf) == 1 and modified_filter\n    end, api.nvim_list_bufs())\nend\n\nreturn M\n"
  },
  {
    "path": "lua/sidebar-nvim/view.lua",
    "content": "local bindings = require(\"sidebar-nvim.bindings\")\nlocal config = require(\"sidebar-nvim.config\")\nlocal utils = require(\"sidebar-nvim.utils\")\n\nlocal a = vim.api\n\nlocal M = {}\n\nM.is_prompt_exiting = false\n\nM.View = {\n    bufnr = nil,\n    tabpages = {},\n    width = 30,\n    side = \"left\",\n    winopts = {\n        relativenumber = false,\n        number = false,\n        list = false,\n        winfixwidth = true,\n        winfixheight = true,\n        foldenable = false,\n        spell = false,\n        signcolumn = \"yes\",\n        foldmethod = \"manual\",\n        foldcolumn = \"0\",\n        cursorcolumn = false,\n        colorcolumn = \"0\",\n    },\n    bufopts = {\n        { name = \"swapfile\", val = false },\n        { name = \"buftype\", val = \"nofile\" },\n        { name = \"modifiable\", val = false },\n        { name = \"filetype\", val = \"SidebarNvim\" },\n        { name = \"bufhidden\", val = \"hide\" },\n    },\n}\n\n---Find a rogue SidebarNvim buffer that might have been spawned by i.e. a session.\n---@return integer|nil\nlocal function find_rogue_buffer()\n    for _, v in ipairs(a.nvim_list_bufs()) do\n        if string.match(vim.fn.bufname(v), \"^SidebarNvim_.*\") then\n            return v\n        end\n    end\n    return nil\nend\n\n---Check if the tree buffer is valid and loaded.\n---@return boolean\nlocal function is_buf_valid()\n    if M.View.bufnr == nil then\n        return false\n    end\n    return a.nvim_buf_is_valid(M.View.bufnr) and a.nvim_buf_is_loaded(M.View.bufnr)\nend\n\n---Find pre-existing SidebarNvim buffer, delete its windows then wipe it.\n---@private\nfunction M._wipe_rogue_buffer()\n    local bn = find_rogue_buffer()\n    if bn then\n        local win_ids = vim.fn.win_findbuf(bn)\n        for _, id in ipairs(win_ids) do\n            if vim.fn.win_gettype(id) ~= \"autocmd\" then\n                a.nvim_win_close(id, true)\n            end\n        end\n\n        a.nvim_buf_set_name(bn, \"\")\n        vim.schedule(function()\n            pcall(a.nvim_buf_delete, bn, {})\n        end)\n    end\nend\n\nlocal function generate_buffer_name()\n    return \"SidebarNvim_\" .. math.random(1000000)\nend\n\n-- set user options and create tree buffer (should never be wiped)\nfunction M.setup()\n    M.View.side = config.side or M.View.side\n    M.View.width = config.initial_width or M.View.width\n\n    M.View.bufnr = a.nvim_create_buf(false, false)\n    bindings.inject(M.View.bufnr)\n\n    local buffer_name = generate_buffer_name()\n\n    if not pcall(a.nvim_buf_set_name, M.View.bufnr, buffer_name) then\n        M._wipe_rogue_buffer()\n        a.nvim_buf_set_name(M.View.bufnr, buffer_name)\n    end\n\n    for _, opt in ipairs(M.View.bufopts) do\n        vim.bo[M.View.bufnr][opt.name] = opt.val\n    end\n\n    vim.api.nvim_exec(\n        [[\naugroup sidebar_nvim_prevent_buffer_override\n    autocmd!\n    autocmd BufWinEnter * lua require('sidebar-nvim.view')._prevent_buffer_override()\naugroup END\n]],\n        false\n    )\nend\n\nlocal goto_tbl = { right = \"h\", left = \"l\", top = \"j\", bottom = \"k\" }\n\nfunction M._prevent_buffer_override()\n    vim.schedule(function()\n        local curwin = a.nvim_get_current_win()\n        local curbuf = a.nvim_win_get_buf(curwin)\n        if curwin ~= M.get_winnr() or curbuf == M.View.bufnr then\n            return\n        end\n\n        vim.cmd(\"buffer \" .. M.View.bufnr)\n\n        if #vim.api.nvim_list_wins() < 2 then\n            vim.cmd(\"vsplit\")\n        else\n            vim.cmd(\"wincmd \" .. goto_tbl[M.View.side])\n        end\n\n        -- copy target window options\n        local winopts_target = vim.deepcopy(M.View.winopts)\n        for key, _ in pairs(winopts_target) do\n            winopts_target[key] = a.nvim_win_get_option(0, key)\n        end\n\n        -- change the buffer will override the target window with the sidebar window opts\n        vim.cmd(\"buffer \" .. curbuf)\n\n        -- revert the changes made when changing buffer\n        for key, value in pairs(winopts_target) do\n            a.nvim_win_set_option(0, key, value)\n        end\n\n        M.resize()\n    end)\nend\n\nfunction M.win_open(opts)\n    -- TODO: [deprecated] to remove\n    utils.echo_warning(\"view.win_open() is now deprecated, please use 'require('sidebar-nvim').is_open()'\")\n    return M.is_win_open(opts)\nend\n\n-- @param opts table\n-- @param |- opts.any_tabpage boolean if true check if is open in any tabpage, if false check in current tab\nfunction M.is_win_open(opts)\n    if opts and opts.any_tabpage then\n        for _, v in pairs(M.View.tabpages) do\n            if a.nvim_win_is_valid(v.winnr) then\n                return true\n            end\n        end\n        return false\n    else\n        return M.get_winnr() ~= nil and a.nvim_win_is_valid(M.get_winnr())\n    end\nend\n\nfunction M.set_cursor(opts)\n    if M.is_win_open() then\n        pcall(a.nvim_win_set_cursor, M.get_winnr(), opts)\n    end\nend\n\nfunction M.focus(winnr)\n    local wnr = winnr or M.get_winnr()\n\n    if wnr == nil then\n        return\n    end\n\n    if a.nvim_win_get_tabpage(wnr) ~= a.nvim_win_get_tabpage(0) then\n        M.close()\n        M.open()\n        wnr = M.get_winnr()\n    end\n\n    a.nvim_set_current_win(wnr)\nend\n\nlocal function get_defined_width()\n    if type(M.View.width) == \"number\" then\n        return M.View.width\n    end\n    local width_as_number = tonumber(M.View.width:sub(0, -2))\n    local percent_as_decimal = width_as_number / 100\n    return math.floor(vim.o.columns * percent_as_decimal)\nend\n\nfunction M.resize()\n    if not M.is_win_open() then\n        return\n    end\n\n    if not a.nvim_win_is_valid(M.get_winnr()) then\n        return\n    end\n\n    a.nvim_win_set_width(M.get_winnr(), get_defined_width())\nend\n\nlocal move_tbl = { left = \"H\", right = \"L\", bottom = \"J\", top = \"K\" }\n\nlocal function set_local(opt, value)\n    a.nvim_win_set_option(0, opt, value)\nend\n\nfunction M.open(options)\n    options = options or { focus = false }\n    if not is_buf_valid() then\n        M.setup()\n    end\n\n    a.nvim_command(\"vsp\")\n\n    local move_to = move_tbl[M.View.side]\n    a.nvim_command(\"wincmd \" .. move_to)\n    a.nvim_command(\"vertical resize \" .. get_defined_width())\n    local winnr = a.nvim_get_current_win()\n    local tabpage = a.nvim_get_current_tabpage()\n    M.View.tabpages[tabpage] = vim.tbl_extend(\"force\", M.View.tabpages[tabpage] or {}, { winnr = winnr })\n    vim.cmd(\"buffer \" .. M.View.bufnr)\n    for k, v in pairs(M.View.winopts) do\n        set_local(k, v)\n    end\n    vim.cmd(\":wincmd =\")\n    if not options.focus then\n        vim.cmd(\"wincmd p\")\n    end\nend\n\nfunction M.close()\n    if not M.is_win_open() then\n        return\n    end\n    if #a.nvim_list_wins() == 1 then\n        local modified_buffers = utils.get_existing_buffers({ modified = true })\n\n        if #modified_buffers == 0 then\n            a.nvim_command(\":silent q!\")\n        else\n            utils.echo_warning(\"cannot exit with modified buffers!\")\n            a.nvim_command(\":sb \" .. modified_buffers[1])\n        end\n    end\n    a.nvim_win_hide(M.get_winnr())\nend\n\n--- Returns the window number for sidebar-nvim within the tabpage specified\n---@param tabpage number: (optional) the number of the chosen tabpage. Defaults to current tabpage.\n---@return number\nfunction M.get_winnr(tabpage)\n    tabpage = tabpage or a.nvim_get_current_tabpage()\n    local tabinfo = M.View.tabpages[tabpage]\n    if tabinfo ~= nil then\n        return tabinfo.winnr\n    end\nend\n\n--- Returns the window width for sidebar-nvim within the tabpage specified\n---@param tabpage number: (optional) the number of the chosen tabpage. Defaults to current tabpage.\n---@return number\nfunction M.get_width(tabpage)\n    local winnr = M.get_winnr(tabpage)\n    return vim.fn.winwidth(winnr)\nend\n\nreturn M\n"
  },
  {
    "path": "lua/sidebar-nvim.lua",
    "content": "local lib = require(\"sidebar-nvim.lib\")\nlocal colors = require(\"sidebar-nvim.colors\")\nlocal renderer = require(\"sidebar-nvim.renderer\")\nlocal view = require(\"sidebar-nvim.view\")\nlocal updater = require(\"sidebar-nvim.updater\")\nlocal config = require(\"sidebar-nvim.config\")\nlocal bindings = require(\"sidebar-nvim.bindings\")\nlocal profile = require(\"sidebar-nvim.profile\")\nlocal utils = require(\"sidebar-nvim.utils\")\n\nlocal M = { open_on_start = false, setup_called = false }\n\nlocal deprecated_config_map = { docker = \"containers\" }\nlocal function check_deprecated_field(key)\n    if not vim.tbl_contains(vim.tbl_keys(deprecated_config_map), key) then\n        return\n    end\n\n    local new_key = deprecated_config_map[key]\n    utils.echo_warning(\"config '\" .. key .. \"' is deprecated. Please use '\" .. new_key .. \"' instead\")\nend\n\nfunction M.setup(opts)\n    opts = opts or {}\n\n    -- this keys should not be merged by tbl_deep_merge, they should be overriden completely\n    local full_override_keys = { \"sections\", \"section_separator\", \"section_title_separator\" }\n\n    for key, value in pairs(opts) do\n        check_deprecated_field(key)\n\n        if key == \"open\" then\n            M.open_on_start = value\n        else\n            if type(value) ~= \"table\" or vim.tbl_contains(full_override_keys, key) then\n                config[key] = value\n            else\n                if type(config[key]) == \"table\" then\n                    config[key] = vim.tbl_deep_extend(\"force\", config[key], value)\n                else\n                    config[key] = value\n                end\n            end\n        end\n    end\n\n    M.setup_called = true\n    -- check if vim enter has already been called, if so, do initialize\n    -- docs for `vim.v.vim_did_enter`: https://neovim.io/doc/user/autocmd.html#VimEnter\n    if vim.v.vim_did_enter == 1 then\n        M._internal_setup()\n    end\nend\n\nfunction M._internal_setup()\n    colors.setup()\n    bindings.setup()\n    view.setup()\n\n    updater.setup()\n    lib.setup()\n\n    if M.open_on_start then\n        M._internal_open()\n    end\nend\n\nfunction M._vim_enter()\n    if M.setup_called then\n        M._internal_setup()\n    end\nend\n\n-- toggle the sidebar\n-- @param (table) opts (optional)\n-- |- boolean opts.focus whether it should focus once open or not\nfunction M.toggle(opts)\n    lib.toggle(opts)\nend\n\nfunction M.close()\n    lib.close()\nend\n\nfunction M._internal_open(opts)\n    if not lib.is_open() then\n        lib.open(opts)\n    end\nend\n\nfunction M.open()\n    M._internal_open()\nend\n\n-- Force immediate update\nfunction M.update()\n    lib.update()\nend\n\n-- Resize the sidebar to the requested size\n-- @param size number\nfunction M.resize(size)\n    lib.resize(size)\n    lib.update()\nend\n\n--- Returns the window width for sidebar-nvim within the tabpage specified\n---@param tabpage number: (optional) the number of the chosen tabpage. Defaults to current tabpage.\n---@return number\nfunction M.get_width(tabpage)\n    return lib.get_width(tabpage)\nend\n\n-- Focus or open the sidebar\n-- @param opts table\n-- @param opts.section_index number\n-- @param opts.cursor_at_content boolean\nfunction M.focus(opts)\n    lib.focus(opts)\nend\n\n-- @param opts table\n-- @param |- opts.any_tabpage boolean if true check if is open in any tabpage, if false check in current tab\nfunction M.is_open(opts)\n    return lib.is_open(opts)\nend\n\nfunction M.reset_highlight()\n    if M.setup_called then\n        colors.setup()\n        renderer.render_hl(view.View.bufnr, {})\n    end\nend\n\nfunction M._on_cursor_move(direction)\n    lib.on_cursor_move(direction)\nend\n\nfunction M.print_profile_summary()\n    if not config.enable_profile then\n        utils.echo_warning(\"Profile not enabled\")\n        return\n    end\n\n    profile.print_summary()\nend\n\nreturn M\n"
  },
  {
    "path": "plugin/sidebar-nvim.vim",
    "content": "if !has('nvim-0.5') || exists('g:loaded_sidebar_nvim') | finish | endif\n\nlet s:save_cpo = &cpo\nset cpo&vim\n\naugroup SidebarNvim\nau!\nau VimEnter * lua require'sidebar-nvim'._vim_enter()\nau VimLeavePre * lua require'sidebar-nvim.lib'.on_vim_leave()\nau TabEnter * lua require'sidebar-nvim.lib'.on_tab_change()\nau WinClosed * lua require'sidebar-nvim.lib'.on_win_leave()\nau BufWritePost * lua require'sidebar-nvim.lib'.update()\nau VimResume * lua require'sidebar-nvim.lib'.update()\nau FocusGained * lua require'sidebar-nvim.lib'.update()\naugroup end\n\ncommand! SidebarNvimOpen lua require'sidebar-nvim'.open()\ncommand! SidebarNvimClose lua require'sidebar-nvim'.close()\ncommand! SidebarNvimToggle lua require'sidebar-nvim'.toggle()\ncommand! SidebarNvimUpdate lua require'sidebar-nvim'.update()\ncommand! SidebarNvimFocus lua require'sidebar-nvim'.focus()\ncommand! -nargs=1 SidebarNvimResize lua require'sidebar-nvim'.resize(<args>)\n\nlet &cpo = s:save_cpo\nunlet s:save_cpo\n\nlet g:loaded_sidebar_nvim = 1\n"
  },
  {
    "path": "selene.toml",
    "content": "std=\"vim\"\n"
  },
  {
    "path": "stylua.toml",
    "content": "column_width = 120\nindent_type = \"Spaces\"\n"
  },
  {
    "path": "vim.toml",
    "content": "[selene]\nbase = \"lua51\"\nname = \"vim\"\n\n[vim]\nany = true\n\n[[describe.args]]\ntype = \"string\"\n[[describe.args]]\ntype = \"function\"\n\n[[it.args]]\ntype = \"string\"\n[[it.args]]\ntype = \"function\"\n\n[[before_each.args]]\ntype = \"function\"\n[[after_each.args]]\ntype = \"function\"\n\n[assert.is_not]\nany = true\n\n[[assert.equals.args]]\ntype = \"any\"\n[[assert.equals.args]]\ntype = \"any\"\n[[assert.equals.args]]\ntype = \"any\"\nrequired = false\n\n[[assert.same.args]]\ntype = \"any\"\n[[assert.same.args]]\ntype = \"any\"\n\n[[assert.truthy.args]]\ntype = \"any\"\n\n[[assert.spy.args]]\ntype = \"any\"\n\n[[assert.stub.args]]\ntype = \"any\"\n\n\n"
  }
]