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 <sidebar-nvim-bot@users.noreply.github.com>
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

## 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").<function>`
- `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 = {
-- { <group name>, <line index relative to the returned lines>, <column start>, <column end, -1 means end of the line> }
{ "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.
<!-- panvimdoc renders subheading-4 differnt, so use heading-5 here instead -->
##### toggle_all()
Toggle all groups, i.e.: NOTE, TODO, FIXME etc.
Call like the following: `require("sidebar-nvim.builtin.todos").<function>`
##### 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 <container id> ${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
| `<C-r>` | hovering the section | redo operation
| `<CR>` | 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 <https://github.com/wbthomason/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").<function>`
- `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 = {
-- { <group name>, <line index relative to the returned lines>, <column start>, <column end, -1 means end of the line> }
{ "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
<https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>
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").<function>`
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 <container id> ${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 │
│<C-r> │hovering the section│redo operation │
│<CR> │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 <https://github.com/kdheepak/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 = { "<Down>", "j" }, up = { "<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 "<no buffers>"
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 = { "<no containers>" }
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, "<no clocks>")
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 "<no diagnostics>"
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
["<C-r>"] = function(_)
if history.position < #history.groups then
history.position = history.position + 1
exec(history.groups[history.position])
end
end,
["<CR>"] = 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 ":!<cmd>"
-- 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 = { "<no changes>" }
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 "<no symbols>"
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 = { "<path ignored>" }
end
loclist:draw(ctx, lines, hl)
if #lines == 0 then
lines = { "<no TODOs>" }
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 = "<highlight group>"
-- |- (table|array) item.right
-- |--|- (string) item.left[n].text = "abc"
-- |--|- (string) item.left[n].hl = "<highlight group>"
-- |- (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 = "<highlight group>"
-- |- (table|array) item.right
-- |--|- (string) item.left[n].text = "abc"
-- |--|- (string) item.left[n].hl = "<highlight group>"
-- |- 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 = <table> }, { lines = lines..., section = <table> } }
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')<CR>", M.escape_keycode(key))
end
function M.sidebar_nvim_cursor_move_callback(direction)
return string.format(":lua require('sidebar-nvim')._on_cursor_move('%s')<CR>", 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(<args>)
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"
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
Condensed preview — 39 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (169K chars).
[
{
"path": ".editorconfig",
"chars": 108,
"preview": "root = true\n\n[*]\ninsert_final_newline = true\nend_of_line = lf\n\n[*.lua]\nindent_style = space\nindent_size = 2\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 652,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the "
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 587,
"preview": "---\nname: Feature request\nabout: Suggest an idea\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**Is your feature req"
},
{
"path": ".github/workflows/docgen.yaml",
"chars": 1053,
"preview": "name: Generate Documentation\n\non:\n workflow_dispatch:\n push:\n branches:\n - dev\n - feat/docs\n\njobs:\n docs"
},
{
"path": ".github/workflows/lint.yaml",
"chars": 767,
"preview": "name: Linting & Formatting\n\non:\n push:\n branches: [main, dev]\n pull_request:\n\nconcurrency: \n group: ci-${{ github."
},
{
"path": ".gitignore",
"chars": 20,
"preview": ".DS_Store\n\ndoc/tags\n"
},
{
"path": ".luacheckrc",
"chars": 192,
"preview": "-- vim: ft=lua tw=80\n\n-- Don't report unused self arguments of methods.\nself = false\n\nignore = {\n \"631\", -- max_line_l"
},
{
"path": "README.md",
"chars": 1738,
"preview": "# sidebar.nvim\n\nA generic and modular lua sidebar inspired by [lualine](https://github.com/hoob3rt/lualine.nvim)\n\nDevelo"
},
{
"path": "doc/general.md",
"chars": 19136,
"preview": "# Overview\n\nA generic and modular lua sidebar inspired by lualine\n\n# Installing\n\nYou can install sidebar using any packa"
},
{
"path": "doc/sidebar.txt",
"chars": 25925,
"preview": "*sidebar.txt* For NVIM v0.6.0 Last change: 2022 June 21\n\n========================================"
},
{
"path": "lua/sidebar-nvim/bindings.lua",
"chars": 3390,
"preview": "local api = vim.api\nlocal utils = require(\"sidebar-nvim.utils\")\n\nlocal config = require(\"sidebar-nvim.config\")\n\nlocal M "
},
{
"path": "lua/sidebar-nvim/builtin/buffers.lua",
"chars": 5421,
"preview": "local utils = require(\"sidebar-nvim.utils\")\nlocal view = require(\"sidebar-nvim.view\")\nlocal Loclist = require(\"sidebar-n"
},
{
"path": "lua/sidebar-nvim/builtin/containers.lua",
"chars": 4522,
"preview": "local utils = require(\"sidebar-nvim.utils\")\nlocal docker_utils = require(\"sidebar-nvim.docker_utils\")\nlocal Loclist = re"
},
{
"path": "lua/sidebar-nvim/builtin/datetime.lua",
"chars": 2981,
"preview": "local config = require(\"sidebar-nvim.config\")\nlocal utils = require(\"sidebar-nvim.utils\")\nlocal has_luatz, luatz = pcall"
},
{
"path": "lua/sidebar-nvim/builtin/diagnostics.lua",
"chars": 4303,
"preview": "local Loclist = require(\"sidebar-nvim.components.loclist\")\nlocal config = require(\"sidebar-nvim.config\")\n\nlocal loclist "
},
{
"path": "lua/sidebar-nvim/builtin/files.lua",
"chars": 16539,
"preview": "local utils = require(\"sidebar-nvim.utils\")\nlocal Loclist = require(\"sidebar-nvim.components.loclist\")\nlocal config = re"
},
{
"path": "lua/sidebar-nvim/builtin/git-status.lua",
"chars": 377,
"preview": "local utils = require(\"sidebar-nvim.utils\")\n\nlocal git = require(\"sidebar-nvim.builtin.git\")\n\nlocal deprecated_git = vim"
},
{
"path": "lua/sidebar-nvim/builtin/git.lua",
"chars": 7448,
"preview": "local utils = require(\"sidebar-nvim.utils\")\nlocal sidebar = require(\"sidebar-nvim\")\nlocal Loclist = require(\"sidebar-nvi"
},
{
"path": "lua/sidebar-nvim/builtin/lsp-diagnostics.lua",
"chars": 446,
"preview": "local utils = require(\"sidebar-nvim.utils\")\n\nlocal diagnostics = require(\"sidebar-nvim.builtin.diagnostics\")\n\nlocal depr"
},
{
"path": "lua/sidebar-nvim/builtin/symbols.lua",
"chars": 5352,
"preview": "local Loclist = require(\"sidebar-nvim.components.loclist\")\nlocal config = require(\"sidebar-nvim.config\")\nlocal view = re"
},
{
"path": "lua/sidebar-nvim/builtin/todos.lua",
"chars": 6654,
"preview": "local utils = require(\"sidebar-nvim.utils\")\nlocal Loclist = require(\"sidebar-nvim.components.loclist\")\nlocal config = re"
},
{
"path": "lua/sidebar-nvim/colors.lua",
"chars": 1082,
"preview": "local api = vim.api\n\nlocal M = {}\n\nlocal function get_hl_groups()\n return {}\nend\n\nlocal function get_links()\n retu"
},
{
"path": "lua/sidebar-nvim/components/basic.lua",
"chars": 337,
"preview": "local Component = {}\n\n-- convert the current data structure into a list of lines + highlight groups\n-- @param (table) ct"
},
{
"path": "lua/sidebar-nvim/components/loclist.lua",
"chars": 10182,
"preview": "local Component = require(\"sidebar-nvim.components.basic\")\n\nlocal Loclist = {}\n\nLoclist.DEFAULT_OPTIONS = {\n groups ="
},
{
"path": "lua/sidebar-nvim/config.lua",
"chars": 923,
"preview": "local M = {}\n\nM.disable_default_keybindings = 0\nM.bindings = nil\nM.side = \"left\"\nM.initial_width = 35\n\nM.hide_statusline"
},
{
"path": "lua/sidebar-nvim/debouncer.lua",
"chars": 874,
"preview": "local luv = vim.loop\n\nlocal Debouncer = {}\n\nfunction Debouncer:new(fn, delay)\n local o = { fn = fn, delay = delay, lo"
},
{
"path": "lua/sidebar-nvim/docker_utils.lua",
"chars": 1143,
"preview": "local config = require(\"sidebar-nvim.config\")\nlocal luv = vim.loop\n\nlocal M = {}\n\nfunction M.get_docker_bin()\n local "
},
{
"path": "lua/sidebar-nvim/events.lua",
"chars": 955,
"preview": "local M = {}\n\nlocal global_handlers = {}\n\nlocal Event = { Ready = \"Ready\" }\n\nlocal function get_handlers(event_name)\n "
},
{
"path": "lua/sidebar-nvim/lib.lua",
"chars": 8304,
"preview": "local luv = vim.loop\nlocal api = vim.api\n\nlocal renderer = require(\"sidebar-nvim.renderer\")\nlocal view = require(\"sideba"
},
{
"path": "lua/sidebar-nvim/profile.lua",
"chars": 1318,
"preview": "local config = require(\"sidebar-nvim.config\")\n\nlocal M = {}\n\nM.entries = {}\n\nfunction M.clear()\n M.entries = {}\nend\n\n"
},
{
"path": "lua/sidebar-nvim/renderer.lua",
"chars": 6011,
"preview": "local view = require(\"sidebar-nvim.view\")\nlocal config = require(\"sidebar-nvim.config\")\nlocal utils = require(\"sidebar-n"
},
{
"path": "lua/sidebar-nvim/updater.lua",
"chars": 2420,
"preview": "local utils = require(\"sidebar-nvim.utils\")\nlocal view = require(\"sidebar-nvim.view\")\nlocal config = require(\"sidebar-nv"
},
{
"path": "lua/sidebar-nvim/utils.lua",
"chars": 4260,
"preview": "local M = {}\nlocal api = vim.api\nlocal luv = vim.loop\n\nfunction M.echo_warning(msg)\n api.nvim_command(\"echohl Warning"
},
{
"path": "lua/sidebar-nvim/view.lua",
"chars": 7599,
"preview": "local bindings = require(\"sidebar-nvim.bindings\")\nlocal config = require(\"sidebar-nvim.config\")\nlocal utils = require(\"s"
},
{
"path": "lua/sidebar-nvim.lua",
"chars": 3746,
"preview": "local lib = require(\"sidebar-nvim.lib\")\nlocal colors = require(\"sidebar-nvim.colors\")\nlocal renderer = require(\"sidebar-"
},
{
"path": "plugin/sidebar-nvim.vim",
"chars": 997,
"preview": "if !has('nvim-0.5') || exists('g:loaded_sidebar_nvim') | finish | endif\n\nlet s:save_cpo = &cpo\nset cpo&vim\n\naugroup Side"
},
{
"path": "selene.toml",
"chars": 10,
"preview": "std=\"vim\"\n"
},
{
"path": "stylua.toml",
"chars": 42,
"preview": "column_width = 120\nindent_type = \"Spaces\"\n"
},
{
"path": "vim.toml",
"chars": 594,
"preview": "[selene]\nbase = \"lua51\"\nname = \"vim\"\n\n[vim]\nany = true\n\n[[describe.args]]\ntype = \"string\"\n[[describe.args]]\ntype = \"func"
}
]
About this extraction
This page contains the full source code of the GustavoKatel/sidebar.nvim GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 39 files (154.7 KB), approximately 37.6k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.