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