├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── ci.yml ├── .stylua.toml ├── LICENSE ├── README.md ├── doc └── dashboard.txt ├── lua └── dashboard │ ├── events.lua │ ├── init.lua │ ├── preview.lua │ ├── theme │ ├── doom.lua │ ├── header.lua │ └── hyper.lua │ └── utils.lua └── plugin └── dashboard.lua /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: glepnir 2 | custom: ['https://www.paypal.me/bobbyhub'] 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Stylua 18 | uses: JohnnyMorganz/stylua-action@v1.1.2 19 | with: 20 | token: ${{ secrets.GITHUB_TOKEN }} 21 | args: --check . 22 | 23 | docs: 24 | runs-on: ubuntu-latest 25 | name: pandoc to vimdoc 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: panvimdoc 29 | uses: kdheepak/panvimdoc@main 30 | with: 31 | vimdoc: dashboard 32 | version: Nvim 0.8.0 33 | - uses: stefanzweifel/git-auto-commit-action@v4 34 | with: 35 | commit_message: 'chore(doc): auto generate docs' 36 | commit_user_name: "github-actions[bot]" 37 | commit_user_email: "github-actions[bot]@users.noreply.github.com" 38 | commit_author: "github-actions[bot] " 39 | -------------------------------------------------------------------------------- /.stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 100 2 | line_endings = "Unix" 3 | indent_type = "Spaces" 4 | indent_width = 2 5 | quote_style = "AutoPreferSingle" 6 | call_parentheses = "Always" 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 StephenHuan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Fancy and Blazing Fast start screen plugin of neovim 3 |

4 | 5 | |
Hyper
|
Doom
| 6 | | --- | --- | 7 | |
|
| 8 | 9 | # Feature 10 | 11 | - Low memory usage. dashboard does not store the all user configs in memory like header etc these string will take some memory. now it will be clean after you open a file. you can still use dashboard command to open a new one , then dashboard will read the config from cache. 12 | - Blazing fast 13 | 14 | 15 | # Install 16 | 17 | - Lazy.nvim 18 | 19 | ```lua 20 | { 21 | 'nvimdev/dashboard-nvim', 22 | event = 'VimEnter', 23 | config = function() 24 | require('dashboard').setup { 25 | -- config 26 | } 27 | end, 28 | dependencies = { {'nvim-tree/nvim-web-devicons'}} 29 | } 30 | ``` 31 | 32 | - Packer 33 | 34 | ```lua 35 | use { 36 | 'nvimdev/dashboard-nvim', 37 | event = 'VimEnter', 38 | config = function() 39 | require('dashboard').setup { 40 | -- config 41 | } 42 | end, 43 | requires = {'nvim-tree/nvim-web-devicons'} 44 | } 45 | ``` 46 | 47 | # Configuration 48 | 49 | ## Options 50 | 51 | ```lua 52 | theme = 'hyper' -- theme is doom and hyper default is hyper 53 | disable_move -- default is false disable move keymap for hyper 54 | shortcut_type -- shortcut type 'letter' or 'number' 55 | shuffle_letter -- default is false, shortcut 'letter' will be randomize, set to false to have ordered letter 56 | letter_list -- default is a-z, excluding j and k 57 | change_to_vcs_root -- default is false,for open file in hyper mru. it will change to the root of vcs 58 | config = {}, -- config used for theme 59 | hide = { 60 | statusline -- hide statusline default is true 61 | tabline -- hide the tabline 62 | winbar -- hide winbar 63 | }, 64 | preview = { 65 | command -- preview command 66 | file_path -- preview file path 67 | file_height -- preview file height 68 | file_width -- preview file width 69 | }, 70 | ``` 71 | 72 | ## Theme config 73 | 74 | the `config` field is used for theme. general field 75 | 76 | ```lua 77 | config = { 78 | header -- type is table def 79 | week_header = { 80 | enable --boolean use a week header 81 | concat --concat string after time string line 82 | append --table append after time string line 83 | }, 84 | disable_move -- boolean default is false disable move key 85 | } 86 | ``` 87 | 88 | ### Hyper 89 | 90 | when use `hyper` theme the available options in `config` is 91 | 92 | ```lua 93 | config = { 94 | shortcut = { 95 | -- action can be a function type 96 | { desc = string, group = 'highlight group', key = 'shortcut key', action = 'action when you press key' }, 97 | }, 98 | packages = { enable = true }, -- show how many plugins neovim loaded 99 | -- limit how many projects list, action when you press key or enter it will run this action. 100 | -- action can be a function type, e.g. 101 | -- action = func(path) vim.cmd('Telescope find_files cwd=' .. path) end 102 | project = { enable = true, limit = 8, icon = 'your icon', label = '', action = 'Telescope find_files cwd=' }, 103 | mru = { enable = true, limit = 10, icon = 'your icon', label = '', cwd_only = false }, 104 | footer = {}, -- footer 105 | } 106 | ``` 107 | 108 | ### Doom 109 | 110 | when use `doom` theme the available options in `config` is 111 | 112 | ```lua 113 | config = { 114 | center = { 115 | { 116 | icon = '', 117 | icon_hl = 'group', 118 | desc = 'description', 119 | desc_hl = 'group', 120 | key = 'shortcut key in dashboard buffer not keymap !!', 121 | key_hl = 'group', 122 | key_format = ' [%s]', -- `%s` will be substituted with value of `key` 123 | action = '', 124 | }, 125 | }, 126 | footer = {}, 127 | vertical_center = false, -- Center the Dashboard on the vertical (from top to bottom) 128 | } 129 | ``` 130 | 131 | notice if you don't link config every highlight group. you can ignore this key. 132 | dashboard will use default highlight group like `DashboardKey/Icon/Desc` instead 133 | 134 | ### Commands 135 | 136 | - `Dashboard` open dashboard 137 | - `DbProjectDelete count` delete project in cache works for hyper theme. count is number 138 | - `DashboardUpdateFooter` updates the content of the Footer 139 | 140 | ### Highlight 141 | 142 | all highlight groups 143 | 144 | ``` 145 | -- General 146 | DashboardHeader DashboardFooter 147 | -- Hyper theme 148 | DashboardProjectTitle DashboardProjectTitleIcon DashboardProjectIcon 149 | DashboardMruTitle DashboardMruIcon DashboardFiles DashboardShortCutIcon 150 | -- Doom theme 151 | DashboardDesc DashboardKey DashboardIcon DashboardShortCut 152 | ``` 153 | 154 | ### Example config 155 | 156 | example config of screenshot 157 | 158 |
159 | Hyper 160 | 161 | ```lua 162 | db.setup({ 163 | theme = 'hyper', 164 | config = { 165 | week_header = { 166 | enable = true, 167 | }, 168 | shortcut = { 169 | { desc = '󰊳 Update', group = '@property', action = 'Lazy update', key = 'u' }, 170 | { 171 | icon = ' ', 172 | icon_hl = '@variable', 173 | desc = 'Files', 174 | group = 'Label', 175 | action = 'Telescope find_files', 176 | key = 'f', 177 | }, 178 | { 179 | desc = ' Apps', 180 | group = 'DiagnosticHint', 181 | action = 'Telescope app', 182 | key = 'a', 183 | }, 184 | { 185 | desc = ' dotfiles', 186 | group = 'Number', 187 | action = 'Telescope dotfiles', 188 | key = 'd', 189 | }, 190 | }, 191 | }, 192 | }) 193 | ``` 194 |
195 | 196 |
197 | Doom 198 | 199 | ```lua 200 | db.setup({ 201 | theme = 'doom', 202 | config = { 203 | header = {}, --your header 204 | center = { 205 | { 206 | icon = ' ', 207 | icon_hl = 'Title', 208 | desc = 'Find File ', 209 | desc_hl = 'String', 210 | key = 'b', 211 | keymap = 'SPC f f', 212 | key_hl = 'Number', 213 | key_format = ' %s', -- remove default surrounding `[]` 214 | action = 'lua print(2)' 215 | }, 216 | { 217 | icon = ' ', 218 | desc = 'Find Dotfiles', 219 | key = 'f', 220 | keymap = 'SPC f d', 221 | key_format = ' %s', -- remove default surrounding `[]` 222 | action = 'lua print(3)' 223 | }, 224 | }, 225 | footer = {} --your footer 226 | } 227 | }) 228 | ``` 229 |
lua 33 | { 34 | 'nvimdev/dashboard-nvim', 35 | event = 'VimEnter', 36 | config = function() 37 | require('dashboard').setup { 38 | -- config 39 | } 40 | end, 41 | dependencies = { {'nvim-tree/nvim-web-devicons'}} 42 | } 43 | < 44 | 45 | - Packer 46 | 47 | >lua 48 | use { 49 | 'nvimdev/dashboard-nvim', 50 | event = 'VimEnter', 51 | config = function() 52 | require('dashboard').setup { 53 | -- config 54 | } 55 | end, 56 | requires = {'nvim-tree/nvim-web-devicons'} 57 | } 58 | < 59 | 60 | 61 | ============================================================================== 62 | 3. Configuration *dashboard-configuration* 63 | 64 | 65 | OPTIONS *dashboard-configuration-options* 66 | 67 | >lua 68 | theme = 'hyper' -- theme is doom and hyper default is hyper 69 | disable_move -- default is false disable move keymap for hyper 70 | shortcut_type -- shortcut type 'letter' or 'number' 71 | shuffle_letter -- default is false, shortcut 'letter' will be randomize, set to false to have ordered letter 72 | letter_list -- default is a-z, excluding j and k 73 | change_to_vcs_root -- default is false,for open file in hyper mru. it will change to the root of vcs 74 | config = {}, -- config used for theme 75 | hide = { 76 | statusline -- hide statusline default is true 77 | tabline -- hide the tabline 78 | winbar -- hide winbar 79 | }, 80 | preview = { 81 | command -- preview command 82 | file_path -- preview file path 83 | file_height -- preview file height 84 | file_width -- preview file width 85 | }, 86 | < 87 | 88 | 89 | THEME CONFIG *dashboard-configuration-theme-config* 90 | 91 | the `config` field is used for theme. general field 92 | 93 | >lua 94 | config = { 95 | header -- type is table def 96 | week_header = { 97 | enable --boolean use a week header 98 | concat --concat string after time string line 99 | append --table append after time string line 100 | }, 101 | disable_move -- boolean default is false disable move key 102 | } 103 | < 104 | 105 | 106 | HYPER ~ 107 | 108 | when use `hyper` theme the available options in `config` is 109 | 110 | >lua 111 | config = { 112 | shortcut = { 113 | -- action can be a function type 114 | { desc = string, group = 'highlight group', key = 'shortcut key', action = 'action when you press key' }, 115 | }, 116 | packages = { enable = true }, -- show how many plugins neovim loaded 117 | -- limit how many projects list, action when you press key or enter it will run this action. 118 | -- action can be a function type, e.g. 119 | -- action = func(path) vim.cmd('Telescope find_files cwd=' .. path) end 120 | project = { enable = true, limit = 8, icon = 'your icon', label = '', action = 'Telescope find_files cwd=' }, 121 | mru = { enable = true, limit = 10, icon = 'your icon', label = '', cwd_only = false }, 122 | footer = {}, -- footer 123 | } 124 | < 125 | 126 | 127 | DOOM ~ 128 | 129 | when use `doom` theme the available options in `config` is 130 | 131 | >lua 132 | config = { 133 | center = { 134 | { 135 | icon = '', 136 | icon_hl = 'group', 137 | desc = 'description', 138 | desc_hl = 'group', 139 | key = 'shortcut key in dashboard buffer not keymap !!', 140 | key_hl = 'group', 141 | key_format = ' [%s]', -- `%s` will be substituted with value of `key` 142 | action = '', 143 | }, 144 | }, 145 | footer = {}, 146 | vertical_center = false, -- Center the Dashboard on the vertical (from top to bottom) 147 | } 148 | < 149 | 150 | notice if you don’t link config every highlight group. you can ignore this 151 | key. dashboard will use default highlight group like `DashboardKey/Icon/Desc` 152 | instead 153 | 154 | 155 | COMMANDS ~ 156 | 157 | - `Dashboard` open dashboard 158 | - `DbProjectDelete count` delete project in cache works for hyper theme. count is number 159 | - `DashboardUpdateFooter` updates the content of the Footer 160 | 161 | 162 | HIGHLIGHT ~ 163 | 164 | all highlight groups 165 | 166 | > 167 | -- General 168 | DashboardHeader DashboardFooter 169 | -- Hyper theme 170 | DashboardProjectTitle DashboardProjectTitleIcon DashboardProjectIcon 171 | DashboardMruTitle DashboardMruIcon DashboardFiles DashboardShortCutIcon 172 | -- Doom theme 173 | DashboardDesc DashboardKey DashboardIcon DashboardShortCut 174 | < 175 | 176 | 177 | EXAMPLE CONFIG ~ 178 | 179 | example config of screenshot 180 | 181 | Hyper ~ 182 | 183 | >lua 184 | db.setup({ 185 | theme = 'hyper', 186 | config = { 187 | week_header = { 188 | enable = true, 189 | }, 190 | shortcut = { 191 | { desc = '󰊳 Update', group = '@property', action = 'Lazy update', key = 'u' }, 192 | { 193 | icon = ' ', 194 | icon_hl = '@variable', 195 | desc = 'Files', 196 | group = 'Label', 197 | action = 'Telescope find_files', 198 | key = 'f', 199 | }, 200 | { 201 | desc = ' Apps', 202 | group = 'DiagnosticHint', 203 | action = 'Telescope app', 204 | key = 'a', 205 | }, 206 | { 207 | desc = ' dotfiles', 208 | group = 'Number', 209 | action = 'Telescope dotfiles', 210 | key = 'd', 211 | }, 212 | }, 213 | }, 214 | }) 215 | < 216 | 217 | Doom ~ 218 | 219 | >lua 220 | db.setup({ 221 | theme = 'doom', 222 | config = { 223 | header = {}, --your header 224 | center = { 225 | { 226 | icon = ' ', 227 | icon_hl = 'Title', 228 | desc = 'Find File ', 229 | desc_hl = 'String', 230 | key = 'b', 231 | keymap = 'SPC f f', 232 | key_hl = 'Number', 233 | key_format = ' %s', -- remove default surrounding `[]` 234 | action = 'lua print(2)' 235 | }, 236 | { 237 | icon = ' ', 238 | desc = 'Find Dotfiles', 239 | key = 'f', 240 | keymap = 'SPC f d', 241 | key_format = ' %s', -- remove default surrounding `[]` 242 | action = 'lua print(3)' 243 | }, 244 | }, 245 | footer = {} --your footer 246 | } 247 | }) 248 | < 249 | 250 | 257 | - Removed Ueberzug script, as the Ueberzug author has deleted the repository. 258 | 259 | 260 | TODO ~ 261 | 262 | - I will write a plugin to implement some popular terminal evaluators image protocol then I think 263 | can make it work with dashboard 264 | 265 | 266 | ============================================================================== 267 | 4. Backers *dashboard-backers* 268 | 269 | @RakerZh 270 | 271 | 272 | ============================================================================== 273 | 5. Donate *dashboard-donate* 274 | 275 | If you’d like to support my work financially, buy me a drink through Github 276 | Sponsor or 277 | 278 | 279 | ============================================================================== 280 | 6. LICENSE *dashboard-license* 281 | 282 | MIT 283 | 284 | ============================================================================== 285 | 7. Links *dashboard-links* 286 | 287 | 1. *@RakerZh*: 288 | 2. **: https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white 289 | 290 | Generated by panvimdoc 291 | 292 | vim:tw=78:ts=8:noet:ft=help:norl: 293 | -------------------------------------------------------------------------------- /lua/dashboard/events.lua: -------------------------------------------------------------------------------- 1 | local api, lsp, uv = vim.api, vim.lsp, vim.loop 2 | local au = {} 3 | local get_lsp_clients = vim.fn.has('nvim-0.10') == 1 and vim.lsp.get_clients 4 | or lsp.get_active_clients 5 | 6 | function au.register_lsp_root(path) 7 | api.nvim_create_autocmd('VimLeavePre', { 8 | callback = function() 9 | local projects = {} 10 | for _, client in pairs(get_lsp_clients() or {}) do 11 | local root_dir = client.config.root_dir 12 | if root_dir and not vim.tbl_contains(projects, root_dir) then 13 | table.insert(projects, root_dir) 14 | end 15 | 16 | for _, folder in pairs(client.workspace_folders or {}) do 17 | if not vim.tbl_contains(projects, folder.name) then 18 | table.insert(projects, folder.name) 19 | end 20 | end 21 | end 22 | 23 | if #projects == 0 then 24 | return 25 | end 26 | 27 | -- callback hell holy shit but simply than write a async await lib 28 | -- also I don't link to add a thirdpart plugin. this is just a small code 29 | uv.fs_open(path, 'r+', 384, function(err, fd) 30 | assert(not err, err) 31 | uv.fs_fstat(fd, function(err, stat) 32 | assert(not err, err) 33 | uv.fs_read(fd, stat.size, 0, function(err, data) 34 | assert(not err, err) 35 | local before = assert(loadstring(data)) 36 | local plist = before() 37 | if plist and #plist > 10 then 38 | plist = vim.list_slice(plist, 10) 39 | end 40 | plist = vim.tbl_filter(function(k) 41 | return not vim.tbl_contains(projects, k) 42 | end, plist or {}) 43 | plist = vim.list_extend(plist, projects) 44 | local dump = 'return ' .. vim.inspect(plist) 45 | uv.fs_write(fd, dump, 0, function(err, _) 46 | assert(not err, err) 47 | uv.fs_ftruncate(fd, #dump, function(err, _) 48 | assert(not err, err) 49 | uv.fs_close(fd) 50 | end) 51 | end) 52 | end) 53 | end) 54 | end) 55 | end, 56 | }) 57 | end 58 | 59 | return au 60 | -------------------------------------------------------------------------------- /lua/dashboard/init.lua: -------------------------------------------------------------------------------- 1 | local api, fn = vim.api, vim.fn 2 | local utils = require('dashboard.utils') 3 | local ctx = {} 4 | local db = {} 5 | 6 | db.__index = db 7 | db.__newindex = function(t, k, v) 8 | rawset(t, k, v) 9 | end 10 | 11 | local function clean_ctx() 12 | for k, _ in pairs(ctx) do 13 | ctx[k] = nil 14 | end 15 | end 16 | 17 | local function cache_dir() 18 | local dir = utils.path_join(vim.fn.stdpath('cache'), 'dashboard') 19 | if fn.isdirectory(dir) == 0 then 20 | fn.mkdir(dir, 'p') 21 | end 22 | return dir 23 | end 24 | 25 | local function cache_path() 26 | local dir = cache_dir() 27 | return utils.path_join(dir, 'cache') 28 | end 29 | 30 | local function conf_cache_path() 31 | return utils.path_join(cache_dir(), 'conf') 32 | end 33 | 34 | local function default_options() 35 | return { 36 | theme = 'hyper', 37 | disable_move = false, 38 | shortcut_type = 'letter', 39 | shuffle_letter = false, 40 | letter_list = 'abcdefghilmnopqrstuvwxyz', 41 | buffer_name = 'Dashboard', 42 | change_to_vcs_root = false, 43 | config = { 44 | week_header = { 45 | enable = false, 46 | concat = nil, 47 | append = nil, 48 | }, 49 | }, 50 | hide = { 51 | statusline = true, 52 | tabline = true, 53 | }, 54 | preview = { 55 | command = '', 56 | file_path = nil, 57 | file_height = 0, 58 | file_width = 0, 59 | }, 60 | } 61 | end 62 | 63 | local function buf_local() 64 | local opts = { 65 | ['bufhidden'] = 'wipe', 66 | ['colorcolumn'] = '', 67 | ['foldcolumn'] = '0', 68 | ['matchpairs'] = '', 69 | ['buflisted'] = false, 70 | ['cursorcolumn'] = false, 71 | ['cursorline'] = false, 72 | ['list'] = false, 73 | ['number'] = false, 74 | ['relativenumber'] = false, 75 | ['spell'] = false, 76 | ['swapfile'] = false, 77 | ['readonly'] = false, 78 | ['filetype'] = 'dashboard', 79 | ['wrap'] = false, 80 | ['signcolumn'] = 'no', 81 | } 82 | for opt, val in pairs(opts) do 83 | vim.opt_local[opt] = val 84 | end 85 | if fn.has('nvim-0.9') == 1 then 86 | vim.opt_local.stc = '' 87 | end 88 | end 89 | 90 | function db:new_file() 91 | vim.cmd('enew') 92 | end 93 | 94 | function db:save_user_options() 95 | self.user_cursor_line = vim.opt.cursorline:get() 96 | self.user_laststatus_value = vim.opt.laststatus:get() 97 | self.user_tabline_value = vim.opt.showtabline:get() 98 | self.user_winbar_value = vim.opt.winbar:get() 99 | end 100 | 101 | function db:set_ui_options(opts) 102 | if opts.hide.statusline then 103 | vim.opt.laststatus = 0 104 | end 105 | if opts.hide.tabline then 106 | vim.opt.showtabline = 0 107 | end 108 | if opts.hide.winbar then 109 | vim.opt.winbar = '' 110 | end 111 | end 112 | 113 | function db:restore_user_options(opts) 114 | if self.user_cursor_line then 115 | vim.opt.cursorline = self.user_cursor_line 116 | end 117 | 118 | if opts.hide.statusline and self.user_laststatus_value then 119 | vim.opt.laststatus = tonumber(self.user_laststatus_value) 120 | end 121 | 122 | if opts.hide.tabline and self.user_tabline_value then 123 | vim.opt.showtabline = tonumber(self.user_tabline_value) 124 | end 125 | 126 | if opts.hide.winbar and self.user_winbar_value then 127 | vim.opt.winbar = self.user_winbar_value 128 | end 129 | end 130 | 131 | function db:cache_opts() 132 | if not self.opts then 133 | return 134 | end 135 | local uv = vim.loop 136 | local path = conf_cache_path() 137 | if self.opts.config.shortcut then 138 | for _, item in pairs(self.opts.config.shortcut) do 139 | if type(item.action) == 'function' then 140 | ---@diagnostic disable-next-line: param-type-mismatch 141 | local dump = assert(string.dump(item.action)) 142 | item.action = dump 143 | end 144 | end 145 | end 146 | 147 | if self.opts.config.project and type(self.opts.config.project.action) == 'function' then 148 | ---@diagnostic disable-next-line: param-type-mismatch 149 | local dump = assert(string.dump(self.opts.config.project.action)) 150 | self.opts.config.project.action = dump 151 | end 152 | 153 | if self.opts.config.center then 154 | for _, item in pairs(self.opts.config.center) do 155 | if type(item.action) == 'function' then 156 | ---@diagnostic disable-next-line: param-type-mismatch 157 | local dump = assert(string.dump(item.action)) 158 | item.action = dump 159 | end 160 | end 161 | end 162 | 163 | if self.opts.config.footer and type(self.opts.config.footer) == 'function' then 164 | ---@diagnostic disable-next-line: param-type-mismatch 165 | local dump = assert(string.dump(self.opts.config.footer)) 166 | self.opts.config.footer = dump 167 | end 168 | 169 | local dump = vim.json.encode(self.opts) 170 | uv.fs_open(path, 'w+', tonumber('664', 8), function(err, fd) 171 | assert(not err, err) 172 | ---@diagnostic disable-next-line: redefined-local 173 | uv.fs_write(fd, dump, 0, function(err, _) 174 | assert(not err, err) 175 | uv.fs_close(fd) 176 | end) 177 | end) 178 | end 179 | 180 | function db:get_opts(callback) 181 | utils.async_read( 182 | conf_cache_path(), 183 | vim.schedule_wrap(function(data) 184 | if not data or #data == 0 then 185 | return 186 | end 187 | local obj = vim.json.decode(data) 188 | if obj then 189 | callback(obj) 190 | end 191 | end) 192 | ) 193 | end 194 | 195 | function db:load_theme(opts) 196 | local config = vim.tbl_extend('force', opts.config, { 197 | path = cache_path(), 198 | bufnr = self.bufnr, 199 | winid = self.winid, 200 | confirm_key = opts.confirm_key or nil, 201 | shortcut_type = opts.shortcut_type, 202 | shuffle_letter = opts.shuffle_letter, 203 | letter_list = opts.letter_list, 204 | change_to_vcs_root = opts.change_to_vcs_root, 205 | }) 206 | 207 | if #opts.preview.command > 0 then 208 | config = vim.tbl_extend('force', config, opts.preview) 209 | end 210 | 211 | require('dashboard.theme.' .. opts.theme)(config) 212 | 213 | self:set_ui_options(opts) 214 | 215 | api.nvim_create_autocmd('VimResized', { 216 | buffer = self.bufnr, 217 | callback = function() 218 | require('dashboard.theme.' .. opts.theme)(config) 219 | vim.bo[self.bufnr].modifiable = false 220 | end, 221 | }) 222 | 223 | api.nvim_create_autocmd('BufEnter', { 224 | callback = function(opt) 225 | if vim.bo.filetype == 'dashboard' then 226 | self:set_ui_options(opts) 227 | return 228 | end 229 | 230 | local bufs = api.nvim_list_bufs() 231 | 232 | bufs = vim.tbl_filter(function(k) 233 | return vim.bo[k].filetype == 'dashboard' 234 | end, bufs) 235 | 236 | -- restore the user's UI settings is no dashboard buffers are visible 237 | local wins = api.nvim_tabpage_list_wins(0) 238 | wins = vim.tbl_filter(function(k) 239 | return vim.tbl_contains(bufs, api.nvim_win_get_buf(k)) 240 | end, wins) 241 | 242 | if #wins == 0 then 243 | self:restore_user_options(opts) 244 | end 245 | 246 | -- clean up if there are no dashboard buffers at all 247 | if #bufs == 0 then 248 | self:cache_opts() 249 | clean_ctx() 250 | pcall(api.nvim_del_autocmd, opt.id) 251 | end 252 | end, 253 | desc = '[Dashboard] clean dashboard data reduce memory', 254 | }) 255 | end 256 | 257 | -- create dashboard instance 258 | function db:instance() 259 | local mode = api.nvim_get_mode().mode 260 | if mode == 'i' or not vim.bo.modifiable then 261 | return 262 | end 263 | 264 | if not vim.o.hidden and vim.bo.modified then 265 | --save before open 266 | vim.cmd.write() 267 | return 268 | end 269 | 270 | if not utils.buf_is_empty(0) then 271 | self.bufnr = api.nvim_create_buf(false, true) 272 | else 273 | self.bufnr = api.nvim_get_current_buf() 274 | end 275 | 276 | self.winid = api.nvim_get_current_win() 277 | api.nvim_win_set_buf(self.winid, self.bufnr) 278 | 279 | self:save_user_options() 280 | 281 | buf_local() 282 | if self.opts then 283 | self:load_theme(self.opts) 284 | else 285 | self:get_opts(function(obj) 286 | self:load_theme(obj) 287 | end) 288 | end 289 | end 290 | 291 | function db.setup(opts) 292 | opts = opts or {} 293 | ctx.opts = vim.tbl_deep_extend('force', default_options(), opts) 294 | end 295 | 296 | return setmetatable(ctx, db) 297 | -------------------------------------------------------------------------------- /lua/dashboard/preview.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local db = require('dashboard') 3 | 4 | local view = {} 5 | 6 | function view:open_window(opt) 7 | local row = math.floor(opt.height / 5) 8 | local col = math.floor((vim.o.columns - opt.width) / 2) 9 | 10 | local opts = { 11 | relative = 'editor', 12 | row = row, 13 | col = col, 14 | width = opt.width, 15 | height = opt.height, 16 | style = 'minimal', 17 | noautocmd = true, 18 | } 19 | 20 | self.bufnr = api.nvim_create_buf(false, true) 21 | api.nvim_buf_set_option(self.bufnr, 'filetype', 'dashboardpreview') 22 | self.winid = api.nvim_open_win(self.bufnr, false, opts) 23 | if vim.fn.has('nvim-0.8') == 1 then 24 | local normal = api.nvim_get_hl_by_name('Normal', true) 25 | pcall(api.nvim_set_hl, 0, 'DashboardPreview', normal) 26 | else 27 | api.nvim_set_hl(0, 'DashboardPreview', { bg = 'none' }) 28 | end 29 | api.nvim_win_set_option(self.winid, 'winhl', 'Normal:DashboardPreview') 30 | return { self.bufnr, self.winid } 31 | end 32 | 33 | function view:close_preview_window() 34 | if self.bufnr and api.nvim_buf_is_loaded(self.bufnr) then 35 | api.nvim_buf_delete(self.bufnr, { force = true }) 36 | self.bufnr = nil 37 | end 38 | 39 | if self.winid and api.nvim_win_is_valid(self.winid) then 40 | api.nvim_win_close(self.winid, true) 41 | self.winid = nil 42 | end 43 | end 44 | 45 | function view:preview_events() 46 | local group = 47 | api.nvim_create_augroup('DashboardClosePreview' .. self.preview_bufnr, { clear = true }) 48 | 49 | --refresh the preview window col position. 50 | local function refresh_preview_wincol() 51 | if not self.preview_winid or not api.nvim_win_is_valid(self.preview_winid) then 52 | return 53 | end 54 | 55 | local winconfig = api.nvim_win_get_config(self.preview_winid) 56 | local cur_width = api.nvim_win_get_width(self.main_winid) 57 | if cur_width ~= self.win_width then 58 | local wins = api.nvim_list_wins() 59 | if #wins == 2 then 60 | local scol = bit.rshift(vim.o.columns, 1) - bit.rshift(winconfig.width, 1) 61 | winconfig.col[false] = scol 62 | api.nvim_win_set_config(self.preview_winid, winconfig) 63 | self.win_width = cur_width 64 | return 65 | end 66 | 67 | if #wins == 3 then 68 | local new_win = vim.tbl_filter(function(k) 69 | return k ~= self.main_winid and k ~= self.preview_winid 70 | end, wins)[1] 71 | winconfig.col[false] = winconfig.col[false] + api.nvim_win_get_width(new_win) 72 | api.nvim_win_set_config(self.preview_winid, winconfig) 73 | self.win_width = cur_width 74 | end 75 | end 76 | end 77 | 78 | local function winresized() 79 | api.nvim_create_autocmd('WinResized', { 80 | group = group, 81 | callback = function() 82 | refresh_preview_wincol() 83 | end, 84 | desc = ' Dashboard preview window resized for nvim 0.9', 85 | }) 86 | end 87 | 88 | api.nvim_create_autocmd('VimResized', { 89 | group = group, 90 | callback = function() 91 | refresh_preview_wincol() 92 | end, 93 | }) 94 | 95 | if vim.fn.has('nvim-0.9') == 1 then 96 | winresized() 97 | else 98 | ---@deprecated when 0.9 version release remove 99 | api.nvim_create_autocmd('BufEnter', { 100 | group = group, 101 | callback = function() 102 | refresh_preview_wincol() 103 | end, 104 | desc = 'dashboard preview window resize for neovim 0.8+ version', 105 | }) 106 | end 107 | end 108 | 109 | function view:open_preview(opt) 110 | self.preview_bufnr, self.preview_winid = unpack(view:open_window(opt)) 111 | 112 | api.nvim_buf_call(self.preview_bufnr, function() 113 | vim.fn.termopen(opt.cmd, { 114 | on_exit = function() end, 115 | }) 116 | end) 117 | self.main_winid = api.nvim_get_current_win() 118 | self.win_width = api.nvim_win_get_width(self.main_winid) 119 | 120 | api.nvim_create_autocmd('BufWipeout', { 121 | buffer = db.bufnr, 122 | callback = function() 123 | if self.winid and api.nvim_win_is_valid(self.preview_winid) then 124 | api.nvim_win_close(self.preview_winid, true) 125 | self.preview_winid = nil 126 | self.preview_bufnr = nil 127 | self.main_winid = nil 128 | self.win_width = nil 129 | end 130 | end, 131 | once = true, 132 | desc = 'make preview have same lifetime with dashboard buffer', 133 | }) 134 | 135 | self:preview_events() 136 | end 137 | 138 | return view 139 | -------------------------------------------------------------------------------- /lua/dashboard/theme/doom.lua: -------------------------------------------------------------------------------- 1 | local api, keymap = vim.api, vim.keymap 2 | local utils = require('dashboard.utils') 3 | 4 | local vert_offset 5 | 6 | local function gen_center_icons_and_descriptions(config) 7 | local lines = {} 8 | local center = config.center 9 | 10 | local counts = {} 11 | for _, item in pairs(center) do 12 | local count = item.keymap and #item.keymap or 0 13 | local line = (item.icon or '') .. item.desc 14 | 15 | if item.key then 16 | line = line .. (' '):rep(#item.key + 4) 17 | count = count + #item.key + 3 18 | local desc = 'Dashboard-action: ' .. item.desc:gsub('^%s+', '') 19 | keymap.set('n', item.key, function() 20 | if type(item.action) == 'string' then 21 | local dump = loadstring(item.action) 22 | if not dump then 23 | vim.cmd(item.action) 24 | else 25 | dump() 26 | end 27 | elseif type(item.action) == 'function' then 28 | item.action() 29 | end 30 | end, { buffer = config.bufnr, nowait = true, silent = true, desc = desc }) 31 | end 32 | 33 | if item.keymap then 34 | line = line .. (' '):rep(#item.keymap) 35 | end 36 | 37 | table.insert(lines, line) 38 | table.insert(lines, '') 39 | table.insert(counts, count) 40 | table.insert(counts, 0) 41 | end 42 | 43 | lines = utils.element_align(lines) 44 | lines = utils.center_align(lines) 45 | for i, count in ipairs(counts) do 46 | lines[i] = lines[i]:sub(1, #lines[i] - count) 47 | end 48 | 49 | api.nvim_buf_set_lines(config.bufnr, vert_offset, -1, false, lines) 50 | return lines 51 | end 52 | 53 | local function gen_center_highlights_and_keys(config, lines) 54 | local ns = api.nvim_create_namespace('DashboardDoom') 55 | local seed = 0 56 | local position_map = {} 57 | for i = 1, #lines do 58 | if lines[i]:find('%w') then 59 | local idx = i == 1 and i or i - seed 60 | seed = seed + 1 61 | position_map[i] = idx 62 | local _, scol = lines[i]:find('%s+') 63 | local ecol = scol + (config.center[idx].icon and #config.center[idx].icon or 0) 64 | 65 | if config.center[idx].icon then 66 | api.nvim_buf_add_highlight( 67 | config.bufnr, 68 | 0, 69 | config.center[idx].icon_hl or 'DashboardIcon', 70 | vert_offset + i - 1, 71 | 0, 72 | ecol 73 | ) 74 | end 75 | 76 | api.nvim_buf_add_highlight( 77 | config.bufnr, 78 | 0, 79 | config.center[idx].desc_hl or 'DashboardDesc', 80 | vert_offset + i - 1, 81 | ecol, 82 | -1 83 | ) 84 | 85 | if config.center[idx].key then 86 | local virt_tbl = {} 87 | if config.center[idx].keymap then 88 | table.insert(virt_tbl, { config.center[idx].keymap, 'DashboardShortCut' }) 89 | end 90 | table.insert(virt_tbl, { 91 | string.format(config.center[idx].key_format or ' [%s]', config.center[idx].key), 92 | config.center[idx].key_hl or 'DashboardKey', 93 | }) 94 | api.nvim_buf_set_extmark(config.bufnr, ns, vert_offset + i - 1, 0, { 95 | virt_text_pos = 'eol', 96 | virt_text = virt_tbl, 97 | }) 98 | end 99 | end 100 | end 101 | return position_map 102 | end 103 | 104 | local function gen_center_base(config) 105 | if not config.center then 106 | local msg = utils.center_align({ 'Please config your own center section', '' }) 107 | api.nvim_buf_set_lines(config.bufnr, vert_offset, -1, false, msg) 108 | return {} 109 | end 110 | 111 | local lines = gen_center_icons_and_descriptions(config) 112 | return lines 113 | end 114 | 115 | local function move_cursor_behaviour(config, bottom_linenr) 116 | local line = api.nvim_buf_get_lines(config.bufnr, vert_offset, vert_offset + 1, false)[1] 117 | local col = line:find('%w') 118 | local col_width = api.nvim_strwidth(line:sub(1, col)) 119 | col = col and col - 1 or 9999 120 | api.nvim_win_set_cursor(config.winid, { vert_offset + 1, col }) 121 | 122 | vim.defer_fn(function() 123 | local before = 0 124 | if api.nvim_get_current_buf() ~= config.bufnr then 125 | return 126 | end 127 | 128 | local dashboard_group = api.nvim_create_augroup('DashboardDoomCursor', { clear = true }) 129 | api.nvim_create_autocmd('CursorMoved', { 130 | buffer = config.bufnr, 131 | group = dashboard_group, 132 | callback = function() 133 | local buf = api.nvim_win_get_buf(0) 134 | if vim.api.nvim_get_option_value('filetype', { buf = buf }) ~= 'dashboard' then 135 | return 136 | end 137 | 138 | local curline = api.nvim_win_get_cursor(0)[1] 139 | if curline < vert_offset + 1 then 140 | curline = bottom_linenr - 1 141 | elseif curline > bottom_linenr - 1 then 142 | curline = vert_offset + 1 143 | elseif not api.nvim_get_current_line():find('%w') then 144 | curline = curline + (before > curline and -1 or 1) 145 | end 146 | before = curline 147 | 148 | -- NOTE: #422: In Lua the length of a string is the number of bytes not 149 | -- the number of characters. 150 | local curline_str = api.nvim_buf_get_lines(config.bufnr, curline - 1, curline, false)[1] 151 | local strwidth = api.nvim_strwidth(curline_str:sub(1, col + 1)) 152 | local col_with_offset = col + col_width - strwidth 153 | if col_with_offset < 0 then 154 | col_with_offset = 0 155 | end 156 | api.nvim_win_set_cursor(config.winid, { curline, col_with_offset }) 157 | end, 158 | }) 159 | end, 0) 160 | end 161 | 162 | local function confirm_key(config, position_map) 163 | keymap.set('n', config.confirm_key or '', function() 164 | local curline = api.nvim_win_get_cursor(0)[1] 165 | local index = position_map[curline - vert_offset] 166 | if index and config.center[index].action then 167 | if type(config.center[index].action) == 'string' then 168 | local dump = loadstring(config.center[index].action) 169 | if not dump then 170 | vim.cmd(config.center[index].action) 171 | else 172 | dump() 173 | end 174 | elseif type(config.center[index].action) == 'function' then 175 | config.center[index].action() 176 | else 177 | print('Error with action, check your config') 178 | end 179 | end 180 | end, { buffer = config.bufnr, nowait = true, silent = true }) 181 | end 182 | 183 | local function load_packages(config) 184 | local packages = config.packages or { 185 | enable = true, 186 | } 187 | if not packages.enable then 188 | return 189 | end 190 | 191 | local package_manager_stats = utils.get_package_manager_stats() 192 | local lines = {} 193 | if package_manager_stats.name == 'lazy' then 194 | lines = { 195 | '', 196 | 'Startuptime: ' .. package_manager_stats.time .. ' ms', 197 | 'Plugins: ' 198 | .. package_manager_stats.loaded 199 | .. ' loaded / ' 200 | .. package_manager_stats.count 201 | .. ' installed', 202 | } 203 | else 204 | lines = { 205 | '', 206 | 'neovim loaded ' .. package_manager_stats.count .. ' plugins', 207 | } 208 | end 209 | 210 | local first_line = api.nvim_buf_line_count(config.bufnr) 211 | api.nvim_buf_set_lines(config.bufnr, first_line, -1, false, utils.center_align(lines)) 212 | 213 | for i, _ in pairs(lines) do 214 | api.nvim_buf_add_highlight(config.bufnr, 0, 'Comment', first_line + i - 1, 0, -1) 215 | end 216 | 217 | return lines 218 | end 219 | 220 | local function gen_footer(config) 221 | local lines = {} ---@type table? 222 | if config.footer then 223 | if type(config.footer) == 'function' then 224 | lines = config.footer() 225 | elseif type(config.footer) == 'string' then 226 | local dump = loadstring(config.footer) 227 | if dump then 228 | lines = dump() 229 | end 230 | elseif type(config.footer) == 'table' then 231 | lines = config.footer 232 | end 233 | local first_line = api.nvim_buf_line_count(config.bufnr) 234 | api.nvim_buf_set_lines(config.bufnr, first_line, -1, false, utils.center_align(lines)) 235 | for i = 1, #lines do 236 | api.nvim_buf_add_highlight(config.bufnr, 0, 'DashboardFooter', first_line + i - 1, 0, -1) 237 | end 238 | else 239 | lines = load_packages(config) 240 | end 241 | utils.add_update_footer_command(config.bufnr, lines) 242 | return #lines 243 | end 244 | 245 | local function vertical_center(config) 246 | if config.vertical_center ~= true then 247 | return 248 | end 249 | 250 | local size = math.floor(vim.o.lines / 2) 251 | - math.ceil(api.nvim_buf_line_count(config.bufnr) / 2) 252 | - 2 253 | local fill = utils.generate_empty_table(size) 254 | api.nvim_buf_set_lines(config.bufnr, 0, 0, false, fill) 255 | 256 | vert_offset = vert_offset + (size < 0 and 0 or size) 257 | end 258 | 259 | local function theme_instance(config) 260 | require('dashboard.theme.header').generate_header(config) 261 | vert_offset = api.nvim_buf_line_count(config.bufnr) 262 | 263 | local lines = gen_center_base(config) 264 | local footer_size = gen_footer(config) 265 | vertical_center(config) 266 | local actions_position_map = gen_center_highlights_and_keys(config, lines) 267 | 268 | local last_entry_pos = api.nvim_buf_line_count(config.bufnr) - footer_size 269 | move_cursor_behaviour(config, last_entry_pos) 270 | confirm_key(config, actions_position_map) 271 | 272 | vim.bo[config.bufnr].modifiable = false 273 | vim.bo[config.bufnr].modified = false 274 | -- defer until next event loop 275 | vim.schedule(function() 276 | api.nvim_exec_autocmds('User', { 277 | pattern = 'DashboardLoaded', 278 | modeline = false, 279 | }) 280 | end) 281 | end 282 | 283 | return setmetatable({}, { 284 | __call = function(_, t) 285 | return theme_instance(t) 286 | end, 287 | }) 288 | -------------------------------------------------------------------------------- /lua/dashboard/theme/header.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local utils = require('dashboard.utils') 3 | 4 | local function week_ascii_text() 5 | return { 6 | ['Monday'] = { 7 | '', 8 | '███╗ ███╗ ██████╗ ███╗ ██╗██████╗ █████╗ ██╗ ██╗', 9 | '████╗ ████║██╔═══██╗████╗ ██║██╔══██╗██╔══██╗╚██╗ ██╔╝', 10 | '██╔████╔██║██║ ██║██╔██╗ ██║██║ ██║███████║ ╚████╔╝ ', 11 | '██║╚██╔╝██║██║ ██║██║╚██╗██║██║ ██║██╔══██║ ╚██╔╝ ', 12 | '██║ ╚═╝ ██║╚██████╔╝██║ ╚████║██████╔╝██║ ██║ ██║ ', 13 | '╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ', 14 | '', 15 | }, 16 | ['Tuesday'] = { 17 | '', 18 | '████████╗██╗ ██╗███████╗███████╗██████╗ █████╗ ██╗ ██╗', 19 | '╚══██╔══╝██║ ██║██╔════╝██╔════╝██╔══██╗██╔══██╗╚██╗ ██╔╝', 20 | ' ██║ ██║ ██║█████╗ ███████╗██║ ██║███████║ ╚████╔╝ ', 21 | ' ██║ ██║ ██║██╔══╝ ╚════██║██║ ██║██╔══██║ ╚██╔╝ ', 22 | ' ██║ ╚██████╔╝███████╗███████║██████╔╝██║ ██║ ██║ ', 23 | ' ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ', 24 | '', 25 | }, 26 | ['Wednesday'] = { 27 | '', 28 | '██╗ ██╗███████╗██████╗ ███╗ ██╗███████╗███████╗██████╗ █████╗ ██╗ ██╗', 29 | '██║ ██║██╔════╝██╔══██╗████╗ ██║██╔════╝██╔════╝██╔══██╗██╔══██╗╚██╗ ██╔╝', 30 | '██║ █╗ ██║█████╗ ██║ ██║██╔██╗ ██║█████╗ ███████╗██║ ██║███████║ ╚████╔╝ ', 31 | '██║███╗██║██╔══╝ ██║ ██║██║╚██╗██║██╔══╝ ╚════██║██║ ██║██╔══██║ ╚██╔╝ ', 32 | '╚███╔███╔╝███████╗██████╔╝██║ ╚████║███████╗███████║██████╔╝██║ ██║ ██║ ', 33 | ' ╚══╝╚══╝ ╚══════╝╚═════╝ ╚═╝ ╚═══╝╚══════╝╚══════╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ', 34 | '', 35 | }, 36 | ['Thursday'] = { 37 | '', 38 | '████████╗██╗ ██╗██╗ ██╗██████╗ ███████╗██████╗ █████╗ ██╗ ██╗', 39 | '╚══██╔══╝██║ ██║██║ ██║██╔══██╗██╔════╝██╔══██╗██╔══██╗╚██╗ ██╔╝', 40 | ' ██║ ███████║██║ ██║██████╔╝███████╗██║ ██║███████║ ╚████╔╝ ', 41 | ' ██║ ██╔══██║██║ ██║██╔══██╗╚════██║██║ ██║██╔══██║ ╚██╔╝ ', 42 | ' ██║ ██║ ██║╚██████╔╝██║ ██║███████║██████╔╝██║ ██║ ██║ ', 43 | ' ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ', 44 | '', 45 | }, 46 | ['Friday'] = { 47 | '', 48 | '███████╗██████╗ ██╗██████╗ █████╗ ██╗ ██╗', 49 | '██╔════╝██╔══██╗██║██╔══██╗██╔══██╗╚██╗ ██╔╝', 50 | '█████╗ ██████╔╝██║██║ ██║███████║ ╚████╔╝ ', 51 | '██╔══╝ ██╔══██╗██║██║ ██║██╔══██║ ╚██╔╝ ', 52 | '██║ ██║ ██║██║██████╔╝██║ ██║ ██║ ', 53 | '╚═╝ ╚═╝ ╚═╝╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ', 54 | '', 55 | }, 56 | ['Saturday'] = { 57 | '', 58 | '███████╗ █████╗ ████████╗██╗ ██╗██████╗ ██████╗ █████╗ ██╗ ██╗', 59 | '██╔════╝██╔══██╗╚══██╔══╝██║ ██║██╔══██╗██╔══██╗██╔══██╗╚██╗ ██╔╝', 60 | '███████╗███████║ ██║ ██║ ██║██████╔╝██║ ██║███████║ ╚████╔╝ ', 61 | '╚════██║██╔══██║ ██║ ██║ ██║██╔══██╗██║ ██║██╔══██║ ╚██╔╝ ', 62 | '███████║██║ ██║ ██║ ╚██████╔╝██║ ██║██████╔╝██║ ██║ ██║ ', 63 | '╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ', 64 | '', 65 | }, 66 | ['Sunday'] = { 67 | '', 68 | '███████╗██╗ ██╗███╗ ██╗██████╗ █████╗ ██╗ ██╗', 69 | '██╔════╝██║ ██║████╗ ██║██╔══██╗██╔══██╗╚██╗ ██╔╝', 70 | '███████╗██║ ██║██╔██╗ ██║██║ ██║███████║ ╚████╔╝ ', 71 | '╚════██║██║ ██║██║╚██╗██║██║ ██║██╔══██║ ╚██╔╝ ', 72 | '███████║╚██████╔╝██║ ╚████║██████╔╝██║ ██║ ██║ ', 73 | '╚══════╝ ╚═════╝ ╚═╝ ╚═══╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ', 74 | '', 75 | }, 76 | } 77 | end 78 | 79 | local function default_header() 80 | return { 81 | '', 82 | ' ██████╗ █████╗ ███████╗██╗ ██╗██████╗ ██████╗ █████╗ ██████╗ ██████╗ ', 83 | ' ██╔══██╗██╔══██╗██╔════╝██║ ██║██╔══██╗██╔═══██╗██╔══██╗██╔══██╗██╔══██╗ ', 84 | ' ██║ ██║███████║███████╗███████║██████╔╝██║ ██║███████║██████╔╝██║ ██║ ', 85 | ' ██║ ██║██╔══██║╚════██║██╔══██║██╔══██╗██║ ██║██╔══██║██╔══██╗██║ ██║ ', 86 | ' ██████╔╝██║ ██║███████║██║ ██║██████╔╝╚██████╔╝██║ ██║██║ ██║██████╔╝ ', 87 | ' ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ', 88 | '', 89 | } 90 | end 91 | 92 | local function week_header(concat, append) 93 | local week = week_ascii_text() 94 | local daysoftheweek = 95 | { 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' } 96 | local day = daysoftheweek[os.date('*t').wday] 97 | local tbl = week[day] 98 | table.insert(tbl, os.date('%Y-%m-%d %H:%M:%S ') .. (concat or '')) 99 | if append then 100 | vim.list_extend(tbl, append) 101 | end 102 | table.insert(tbl, '') 103 | return tbl 104 | end 105 | 106 | local function generate_header(config) 107 | if not vim.bo[config.bufnr].modifiable then 108 | vim.bo[config.bufnr].modifiable = true 109 | end 110 | if not config.command then 111 | local header = config.week_header 112 | and config.week_header.enable 113 | and week_header(config.week_header.concat, config.week_header.append) 114 | or (config.header or default_header()) 115 | api.nvim_buf_set_lines(config.bufnr, 0, -1, false, utils.center_align(header)) 116 | 117 | for i, _ in ipairs(header) do 118 | vim.api.nvim_buf_add_highlight(config.bufnr, 0, 'DashboardHeader', i - 1, 0, -1) 119 | end 120 | return 121 | end 122 | 123 | local empty_table = utils.generate_empty_table(config.file_height + 4) 124 | api.nvim_buf_set_lines(config.bufnr, 0, -1, false, utils.center_align(empty_table)) 125 | local preview = require('dashboard.preview') 126 | preview:open_preview({ 127 | width = config.file_width, 128 | height = config.file_height, 129 | cmd = config.command .. ' ' .. config.file_path, 130 | }) 131 | end 132 | 133 | return { 134 | generate_header = generate_header, 135 | } 136 | -------------------------------------------------------------------------------- /lua/dashboard/theme/hyper.lua: -------------------------------------------------------------------------------- 1 | local api, keymap, uv = vim.api, vim.keymap, vim.loop 2 | local utils = require('dashboard.utils') 3 | local ns = api.nvim_create_namespace('dashboard') 4 | 5 | local function gen_shortcut(config) 6 | local shortcut = config.shortcut 7 | or { 8 | { desc = '[ Github]', group = 'DashboardShortCut' }, 9 | { desc = '[ glepnir]', group = 'DashboardShortCut' }, 10 | { desc = '[ 0.2.3]', group = 'DashboardShortCut' }, 11 | } 12 | 13 | if vim.tbl_isempty(shortcut) then 14 | shortcut = {} 15 | end 16 | 17 | local lines = '' 18 | for _, item in pairs(shortcut) do 19 | local str = item.icon and item.icon .. item.desc or item.desc 20 | if item.key then 21 | str = str .. '[' .. item.key .. ']' 22 | end 23 | lines = lines .. ' ' .. str 24 | end 25 | 26 | local first_line = api.nvim_buf_line_count(config.bufnr) 27 | api.nvim_buf_set_lines(config.bufnr, first_line, -1, false, utils.center_align({ lines })) 28 | 29 | local line = api.nvim_buf_get_lines(config.bufnr, first_line, -1, false)[1] 30 | local start = line:find('[^%s]') - 1 31 | for _, item in pairs(shortcut) do 32 | local _end = start + (item.icon and #(item.icon .. item.desc) or #item.desc) 33 | if item.key then 34 | _end = _end + api.nvim_strwidth(item.key) + 2 35 | keymap.set('n', item.key, function() 36 | if type(item.action) == 'string' then 37 | local dump = loadstring(item.action) 38 | if not dump then 39 | vim.cmd(item.action) 40 | else 41 | dump() 42 | end 43 | elseif type(item.action) == 'function' then 44 | item.action() 45 | end 46 | end, { buffer = config.bufnr, nowait = true, silent = true }) 47 | end 48 | 49 | api.nvim_buf_add_highlight( 50 | config.bufnr, 51 | 0, 52 | item.group or 'DashboardShortCut', 53 | first_line, 54 | start, 55 | _end 56 | ) 57 | 58 | if item.icon then 59 | api.nvim_buf_add_highlight( 60 | config.bufnr, 61 | 0, 62 | item.icon_hl or 'DashboardShortCutIcon', 63 | first_line, 64 | start, 65 | start + #item.icon 66 | ) 67 | end 68 | start = _end + 2 69 | end 70 | end 71 | 72 | local function load_packages(config) 73 | local packages = config.packages or { 74 | enable = true, 75 | } 76 | if not packages.enable then 77 | return 78 | end 79 | 80 | local package_manager_stats = utils.get_package_manager_stats() 81 | local lines = {} 82 | if package_manager_stats.name == 'lazy' then 83 | lines = { 84 | '', 85 | 'Startuptime: ' .. package_manager_stats.time .. ' ms', 86 | 'Plugins: ' 87 | .. package_manager_stats.loaded 88 | .. ' loaded / ' 89 | .. package_manager_stats.count 90 | .. ' installed', 91 | } 92 | elseif package_manager_stats.name == 'strive' then 93 | lines = { 94 | '', 95 | 'Startuptime: ' .. package_manager_stats.time .. ' ms', 96 | 'Plugins: ' 97 | .. package_manager_stats.loaded 98 | .. ' loaded / ' 99 | .. package_manager_stats.count 100 | .. ' installed', 101 | } 102 | else 103 | lines = { 104 | '', 105 | 'neovim loaded ' .. package_manager_stats.count .. ' plugins', 106 | } 107 | end 108 | 109 | local first_line = api.nvim_buf_line_count(config.bufnr) 110 | api.nvim_buf_set_lines(config.bufnr, first_line, -1, false, utils.center_align(lines)) 111 | 112 | for i, _ in pairs(lines) do 113 | api.nvim_buf_add_highlight(config.bufnr, 0, 'Comment', first_line + i - 1, 0, -1) 114 | end 115 | end 116 | 117 | local function reverse(tbl) 118 | for i = 1, math.floor(#tbl / 2) do 119 | tbl[i], tbl[#tbl - i + 1] = tbl[#tbl - i + 1], tbl[i] 120 | end 121 | end 122 | 123 | local function project_list(config, callback) 124 | config.project = vim.tbl_extend('force', { 125 | limit = 8, 126 | enable = true, 127 | icon = '󰏓 ', 128 | icon_hl = 'DashboardRecentProjectIcon', 129 | action = 'Telescope find_files cwd=', 130 | label = ' Recent Projects:', 131 | }, config.project or {}) 132 | 133 | local function read_project(data) 134 | local res = {} 135 | data = string.gsub(data, '%z', '') 136 | local dump = assert(loadstring(data)) 137 | local list = dump() 138 | if list then 139 | list = vim.list_slice(list, #list - config.project.limit) 140 | end 141 | for _, dir in ipairs(list or {}) do 142 | dir = dir:gsub(vim.env.HOME, '~') 143 | table.insert(res, (' '):rep(3) .. ' ' .. dir) 144 | end 145 | 146 | if #res == 0 then 147 | table.insert(res, (' '):rep(3) .. ' empty project') 148 | else 149 | reverse(res) 150 | end 151 | 152 | table.insert(res, 1, config.project.icon .. config.project.label) 153 | table.insert(res, 1, '') 154 | table.insert(res, '') 155 | return res 156 | end 157 | 158 | utils.async_read( 159 | config.path, 160 | vim.schedule_wrap(function(data) 161 | local res = {} 162 | if config.project.enable then 163 | res = read_project(data) 164 | end 165 | callback(res) 166 | end) 167 | ) 168 | end 169 | 170 | local function mru_list(config) 171 | config.mru = vim.tbl_extend('force', { 172 | icon = ' ', 173 | limit = 10, 174 | icon_hl = 'DashboardMruIcon', 175 | label = ' Most Recent Files:', 176 | cwd_only = false, 177 | enable = true, 178 | }, config.mru or {}) 179 | 180 | if not config.mru.enable then 181 | return {}, {} 182 | end 183 | 184 | local list = { 185 | config.mru.icon .. config.mru.label, 186 | } 187 | 188 | local groups = {} 189 | local mlist = utils.get_mru_list() 190 | 191 | if config.mru.cwd_only then 192 | local cwd = uv.cwd() 193 | -- Normalize both paths to use forward slashes 194 | cwd = vim.fn.fnamemodify(cwd, ':p') 195 | mlist = vim.tbl_filter(function(file) 196 | local file_path = vim.fn.fnamemodify(file, ':p') 197 | local file_dir = vim.fn.fnamemodify(file_path, ':h') .. '/' 198 | -- Ensure both paths end with separator and are normalized 199 | return file_dir:sub(1, #cwd) == cwd 200 | end, mlist) 201 | end 202 | 203 | for _, file in pairs(vim.list_slice(mlist, 1, config.mru.limit)) do 204 | local filename = vim.fn.fnamemodify(file, ':t') 205 | local icon, group = utils.get_icon(filename) 206 | icon = icon or '' 207 | if config.mru.cwd_only then 208 | file = vim.fn.fnamemodify(file, ':.') 209 | elseif not utils.is_win then 210 | file = vim.fn.fnamemodify(file, ':~') 211 | end 212 | file = icon .. ' ' .. file 213 | table.insert(groups, { #icon, group }) 214 | table.insert(list, (' '):rep(3) .. file) 215 | end 216 | 217 | if #list == 1 then 218 | table.insert(list, (' '):rep(3) .. ' empty files') 219 | end 220 | return list, groups 221 | end 222 | 223 | local function shuffle_table(table) 224 | for i = #table, 2, -1 do 225 | local j = math.random(i) 226 | table[i], table[j] = table[j], table[i] 227 | end 228 | end 229 | 230 | local function letter_hotkey(config) 231 | local used_keys = {} 232 | local shuffle = config.shuffle_letter 233 | local letter_list = config.letter_list 234 | 235 | for _, item in pairs(config.shortcut or {}) do 236 | if item.key then 237 | table.insert(used_keys, item.key:byte()) 238 | end 239 | end 240 | 241 | local confirm_keys = type(config.confirm_key) == 'table' and config.confirm_key 242 | or { config.confirm_key } 243 | for _, key in ipairs(confirm_keys) do 244 | table.insert(used_keys, key:byte()) 245 | end 246 | 247 | math.randomseed(os.time()) 248 | 249 | -- Create key table, fill it with unused characters. 250 | local function collect_unused_keys(uppercase) 251 | local unused_keys = {} 252 | for key in letter_list:gmatch('.') do 253 | if uppercase then 254 | key = key:upper() 255 | end 256 | key = key:byte() 257 | if not vim.tbl_contains(used_keys, key) then 258 | table.insert(unused_keys, key) 259 | end 260 | end 261 | if shuffle then 262 | shuffle_table(unused_keys) 263 | end 264 | return unused_keys 265 | end 266 | 267 | local unused_keys_lowercase = collect_unused_keys(false) 268 | local unused_keys_uppercase = collect_unused_keys(true) 269 | 270 | -- Push shuffled uppercase keys after the lowercase ones 271 | for _, key in pairs(unused_keys_uppercase) do 272 | table.insert(unused_keys_lowercase, key) 273 | end 274 | 275 | local fallback_hotkey = 0 276 | 277 | return function() 278 | if #unused_keys_lowercase ~= 0 then 279 | -- Pop an unused key to use it as a hotkey. 280 | local key = table.remove(unused_keys_lowercase, 1) 281 | return string.char(key) 282 | else 283 | -- All keys are already used. Fallback to the number generation. 284 | fallback_hotkey = fallback_hotkey + 1 285 | return fallback_hotkey 286 | end 287 | end 288 | end 289 | 290 | local function number_hotkey() 291 | local start = 0 292 | return function() 293 | start = start + 1 294 | return start 295 | end 296 | end 297 | 298 | local function gen_hotkey(config) 299 | if config.shortcut_type == 'number' then 300 | return number_hotkey() 301 | end 302 | return letter_hotkey(config) 303 | end 304 | 305 | local function map_key(config, key, content) 306 | keymap.set('n', key, function() 307 | local text = content or api.nvim_get_current_line() 308 | local scol = utils.is_win and text:find('%w') or text:find('%p') 309 | local path = nil 310 | 311 | if scol ~= nil then -- scol == nil if pressing enter in empty space 312 | if text:sub(scol, scol + 1) ~= '~/' then -- is relative path 313 | scol = math.min(text:find('%w'), text:find('%p')) 314 | end 315 | text = text:sub(scol) 316 | path = text:sub(1, text:find('%w(%s+)$')) 317 | path = vim.fs.normalize(path) 318 | end 319 | 320 | if path == nil then 321 | vim.cmd('enew') 322 | elseif vim.fn.isdirectory(path) == 1 then 323 | vim.cmd('lcd ' .. path) 324 | if type(config.project.action) == 'function' then 325 | config.project.action(path) 326 | elseif type(config.project.action) == 'string' then 327 | local dump = loadstring(config.project.action) 328 | if not dump then 329 | vim.cmd(config.project.action .. path) 330 | else 331 | dump(path) 332 | end 333 | end 334 | else 335 | vim.cmd('edit ' .. vim.fn.fnameescape(path)) 336 | local root = utils.get_vcs_root() 337 | if not config.change_to_vcs_root then 338 | return 339 | end 340 | if #root > 0 then 341 | vim.cmd('lcd ' .. vim.fn.fnamemodify(root[#root], ':h')) 342 | else 343 | vim.cmd('lcd ' .. vim.fn.fnamemodify(path, ':h')) 344 | end 345 | end 346 | end, { buffer = config.bufnr, silent = true, nowait = true }) 347 | end 348 | 349 | local function gen_center(plist, config) 350 | local mlist, mgroups = mru_list(config) 351 | local plist_len = #plist 352 | if plist_len == 0 then 353 | plist[#plist + 1] = '' 354 | plist_len = 1 355 | end 356 | ---@diagnostic disable-next-line: param-type-mismatch 357 | vim.list_extend(plist, mlist) 358 | local max_len = utils.get_max_len(plist) 359 | if max_len <= 40 then 360 | local fill = (' '):rep(math.floor(vim.o.columns / 4)) 361 | for i, v in pairs(plist) do 362 | plist[i] = v .. fill 363 | end 364 | end 365 | 366 | plist = utils.element_align(plist) 367 | plist = utils.center_align(plist) 368 | local first_line = api.nvim_buf_line_count(config.bufnr) 369 | api.nvim_buf_set_lines(config.bufnr, first_line, -1, false, plist) 370 | 371 | if not config.project.enable and not config.mru.enable then 372 | return 373 | end 374 | 375 | local _, scol = plist[2]:find('%S') 376 | if scol == nil then 377 | scol = 0 378 | end 379 | 380 | local start_col = scol 381 | if config.mru.enable then 382 | start_col = plist[plist_len + 2]:find('[^%s]') - 1 383 | end 384 | 385 | local hotkey = gen_hotkey(config) 386 | 387 | api.nvim_buf_add_highlight(config.bufnr, 0, 'DashboardProjectTitle', first_line + 1, 0, -1) 388 | api.nvim_buf_add_highlight( 389 | config.bufnr, 390 | 0, 391 | 'DashboardProjectTitleIcon', 392 | first_line + 1, 393 | 0, 394 | scol + #config.project.icon 395 | ) 396 | 397 | for i = 3, plist_len do 398 | api.nvim_buf_add_highlight( 399 | config.bufnr, 400 | 0, 401 | 'DashboardProjectIcon', 402 | first_line + i - 1, 403 | 0, 404 | start_col + 3 405 | ) 406 | api.nvim_buf_add_highlight( 407 | config.bufnr, 408 | 0, 409 | 'DashboardFiles', 410 | first_line + i - 1, 411 | start_col + 3, 412 | -1 413 | ) 414 | local text = api.nvim_buf_get_lines(config.bufnr, first_line + i - 1, first_line + i, false)[1] 415 | if text and text:find('%w') and not text:find('empty') then 416 | local key = tostring(hotkey()) 417 | api.nvim_buf_set_extmark(config.bufnr, ns, first_line + i - 1, 0, { 418 | virt_text = { { key, 'DashboardShortCut' } }, 419 | virt_text_pos = 'eol', 420 | }) 421 | map_key(config, key, text) 422 | end 423 | end 424 | 425 | -- initialize the cursor pos 426 | api.nvim_win_set_cursor(config.winid, { first_line + 3, start_col + 4 }) 427 | 428 | api.nvim_buf_add_highlight(config.bufnr, 0, 'DashboardMruTitle', first_line + plist_len, 0, -1) 429 | api.nvim_buf_add_highlight( 430 | config.bufnr, 431 | 0, 432 | 'DashboardMruIcon', 433 | first_line + plist_len, 434 | 0, 435 | scol + #config.mru.icon 436 | ) 437 | 438 | for i, data in pairs(mgroups) do 439 | local len, group = unpack(data) 440 | if group then 441 | api.nvim_buf_add_highlight( 442 | config.bufnr, 443 | 0, 444 | group, 445 | first_line + i + plist_len, 446 | start_col, 447 | start_col + len 448 | ) 449 | end 450 | api.nvim_buf_add_highlight( 451 | config.bufnr, 452 | 0, 453 | 'DashboardFiles', 454 | first_line + i + plist_len, 455 | start_col + len, 456 | -1 457 | ) 458 | 459 | local text = api.nvim_buf_get_lines( 460 | config.bufnr, 461 | first_line + i + plist_len, 462 | first_line + i + plist_len + 1, 463 | false 464 | )[1] 465 | if text and text:find('%w') then 466 | local key = tostring(hotkey()) 467 | api.nvim_buf_set_extmark(config.bufnr, ns, first_line + i + plist_len, 0, { 468 | virt_text = { { key, 'DashboardShortCut' } }, 469 | virt_text_pos = 'eol', 470 | }) 471 | map_key(config, key, text) 472 | end 473 | end 474 | end 475 | 476 | local function gen_footer(config) 477 | local footer = { 478 | '', 479 | ' 🚀 Sharp tools make good work.', 480 | } 481 | 482 | if type(config.footer) == 'string' then 483 | local dump = loadstring(config.footer) 484 | if dump then 485 | footer = dump() 486 | end 487 | elseif type(config.footer) == 'function' then 488 | footer = config.footer() 489 | elseif type(config.footer) == 'table' then 490 | footer = config.footer 491 | end 492 | 493 | local first_line = api.nvim_buf_line_count(config.bufnr) 494 | api.nvim_buf_set_lines(config.bufnr, first_line, -1, false, utils.center_align(footer)) 495 | 496 | ---@diagnostic disable-next-line: param-type-mismatch 497 | for i, _ in pairs(footer) do 498 | api.nvim_buf_add_highlight(config.bufnr, 0, 'DashboardFooter', first_line + i - 1, 0, -1) 499 | end 500 | 501 | utils.add_update_footer_command(config.bufnr, footer) 502 | end 503 | 504 | local function project_delete() 505 | api.nvim_create_user_command('DbProjectDelete', function(args) 506 | local path = utils.path_join(vim.fn.stdpath('cache'), 'dashboard', 'cache') 507 | utils.async_read( 508 | path, 509 | vim.schedule_wrap(function(data) 510 | local dump = assert(loadstring(data)) 511 | local list = dump() 512 | local count = tonumber(args.args) 513 | if vim.tbl_count(list) < count then 514 | return 515 | end 516 | list = vim.list_slice(list, count + 1) 517 | local str = string.dump(assert(loadstring('return ' .. vim.inspect(list)))) 518 | local handle = io.open(path, 'w+') 519 | if not handle then 520 | return 521 | end 522 | handle:write(str) 523 | handle:close() 524 | end) 525 | ) 526 | end, { 527 | nargs = '+', 528 | }) 529 | end 530 | 531 | local function theme_instance(config) 532 | project_list(config, function(plist) 533 | if not api.nvim_buf_is_valid(config.bufnr) then 534 | return 535 | end 536 | if config.disable_move then 537 | utils.disable_move_key(config.bufnr) 538 | end 539 | require('dashboard.theme.header').generate_header(config) 540 | if not config.shortcut or not vim.tbl_isempty(config.shortcut) then 541 | gen_shortcut(config) 542 | end 543 | load_packages(config) 544 | gen_center(plist, config) 545 | gen_footer(config) 546 | local confirm_keys = type(config.confirm_key) == 'table' and config.confirm_key 547 | or { config.confirm_key or '' } 548 | for _, key in ipairs(confirm_keys) do 549 | map_key(config, key) 550 | end 551 | require('dashboard.events').register_lsp_root(config.path) 552 | local size = math.floor(vim.o.lines / 2) 553 | - math.ceil(api.nvim_buf_line_count(config.bufnr) / 2) 554 | - 2 555 | local fill = utils.generate_empty_table(size) 556 | api.nvim_buf_set_lines(config.bufnr, 0, 0, false, fill) 557 | vim.bo[config.bufnr].modifiable = false 558 | vim.bo[config.bufnr].modified = false 559 | --defer until next event loop 560 | vim.schedule(function() 561 | api.nvim_exec_autocmds('User', { 562 | pattern = 'DashboardLoaded', 563 | modeline = false, 564 | }) 565 | end) 566 | project_delete() 567 | end) 568 | end 569 | 570 | return setmetatable({}, { 571 | __call = function(_, t) 572 | theme_instance(t) 573 | end, 574 | }) 575 | -------------------------------------------------------------------------------- /lua/dashboard/utils.lua: -------------------------------------------------------------------------------- 1 | local uv = vim.loop 2 | local utils = {} 3 | 4 | utils.is_win = uv.os_uname().version:match('Windows') 5 | local is_nvim_11_or_newer = vim.fn.has('nvim-0.11') == 1 6 | 7 | function utils.path_join(...) 8 | local path_sep = utils.is_win and '\\' or '/' 9 | return table.concat({ ... }, path_sep) 10 | end 11 | 12 | -- dynamically use correct validate form method 13 | local function validate_table(name, value) 14 | if is_nvim_11_or_newer then 15 | vim.validate(name, value, 'table') 16 | else 17 | vim.validate({ [name] = { value, 't' } }) 18 | end 19 | end 20 | 21 | function utils.element_align(tbl) 22 | local lens = {} 23 | vim.tbl_map(function(k) 24 | table.insert(lens, vim.api.nvim_strwidth(k)) 25 | end, tbl) 26 | table.sort(lens) 27 | local max = lens[#lens] 28 | local res = {} 29 | for _, item in pairs(tbl) do 30 | local len = vim.api.nvim_strwidth(item) 31 | local times = math.floor((max - len) / vim.api.nvim_strwidth(' ')) 32 | item = item .. (' '):rep(times) 33 | table.insert(res, item) 34 | end 35 | return res 36 | end 37 | 38 | function utils.get_max_len(contents) 39 | validate_table('contents', contents) 40 | local cells = {} 41 | for _, v in pairs(contents) do 42 | table.insert(cells, vim.api.nvim_strwidth(v)) 43 | end 44 | table.sort(cells) 45 | return cells[#cells] 46 | end 47 | 48 | -- draw the graphics into the screen center 49 | function utils.center_align(tbl) 50 | validate_table('tbl', tbl) 51 | local function fill_sizes(lines) 52 | local fills = {} 53 | for _, line in pairs(lines) do 54 | table.insert(fills, math.floor((vim.o.columns - vim.api.nvim_strwidth(line)) / 2)) 55 | end 56 | return fills 57 | end 58 | 59 | local centered_lines = {} 60 | local fills = fill_sizes(tbl) 61 | 62 | for i = 1, #tbl do 63 | local fill_line = (' '):rep(fills[i]) .. tbl[i] 64 | table.insert(centered_lines, fill_line) 65 | end 66 | 67 | return centered_lines 68 | end 69 | 70 | function utils.get_icon(filename) 71 | local ok, devicons = pcall(require, 'nvim-web-devicons') 72 | if not ok then 73 | return nil 74 | end 75 | return devicons.get_icon(filename, nil, { default = true }) 76 | end 77 | 78 | function utils.read_project_cache(path) 79 | local fd = assert(uv.fs_open(path, 'r', tonumber('644', 8))) 80 | local stat = uv.fs_fstat(fd) 81 | local chunk = uv.fs_read(fd, stat.size, 0) 82 | local dump = assert(loadstring(chunk)) 83 | return dump() 84 | end 85 | 86 | function utils.async_read(path, callback) 87 | uv.fs_open(path, 'a+', 438, function(err, fd) 88 | assert(not err, err) 89 | uv.fs_fstat(fd, function(err, stat) 90 | assert(not err, err) 91 | uv.fs_read(fd, stat.size, 0, function(err, data) 92 | assert(not err, err) 93 | uv.fs_close(fd, function(err) 94 | assert(not err, err) 95 | callback(data) 96 | end) 97 | end) 98 | end) 99 | end) 100 | end 101 | 102 | function utils.disable_move_key(bufnr) 103 | local keys = { 'w', 'f', 'b', 'h', 'j', 'k', 'l', '', '', '', '' } 104 | vim.tbl_map(function(k) 105 | vim.keymap.set('n', k, '', { buffer = bufnr }) 106 | end, keys) 107 | end 108 | 109 | -- return the most recently files list 110 | function utils.get_mru_list() 111 | local mru = {} 112 | for _, file in pairs(vim.v.oldfiles or {}) do 113 | if file and vim.fn.filereadable(file) == 1 then 114 | table.insert(mru, file) 115 | end 116 | end 117 | return mru 118 | end 119 | 120 | function utils.get_package_manager_stats() 121 | local package_manager_stats = { name = '', count = 0, loaded = 0, time = 0 } 122 | ---@diagnostic disable-next-line: undefined-global 123 | if packer_plugins then 124 | package_manager_stats.name = 'packer' 125 | ---@diagnostic disable-next-line: undefined-global 126 | package_manager_stats.count = #vim.tbl_keys(packer_plugins) 127 | end 128 | local status, lazy = pcall(require, 'lazy') 129 | if status then 130 | package_manager_stats.name = 'lazy' 131 | local stats = lazy.stats() 132 | package_manager_stats.loaded = stats.loaded 133 | package_manager_stats.count = stats.count 134 | package_manager_stats.time = stats.startuptime 135 | end 136 | local ok = pcall(require, 'strive') 137 | if ok then 138 | package_manager_stats.name = 'strive' 139 | package_manager_stats.loaded = vim.g.strive_loaded 140 | package_manager_stats.time = vim.g.strive_startup_time 141 | package_manager_stats.count = vim.g.strive_count 142 | end 143 | return package_manager_stats 144 | end 145 | 146 | --- generate an empty table by length 147 | function utils.generate_empty_table(length) 148 | local empty_tbl = {} 149 | if length == 0 then 150 | return empty_tbl 151 | end 152 | for _ = 1, length do 153 | table.insert(empty_tbl, '') 154 | end 155 | return empty_tbl 156 | end 157 | 158 | function utils.generate_truncateline(cells) 159 | local char = '┉' 160 | return char:rep(math.floor(cells / vim.api.nvim_strwidth(char))) 161 | end 162 | 163 | function utils.get_vcs_root(buf) 164 | buf = buf or 0 165 | local path = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(buf), ':p:h') 166 | local patterns = { '.git', '.hg', '.bzr', '.svn' } 167 | for _, pattern in pairs(patterns) do 168 | local root = vim.fs.find(pattern, { path = path, upward = true, stop = vim.env.HOME }) 169 | if root then 170 | return root 171 | end 172 | end 173 | end 174 | 175 | local index = 0 176 | function utils.gen_bufname(prefix) 177 | index = index + 1 178 | return prefix .. '-' .. index 179 | end 180 | 181 | function utils.buf_is_empty(bufnr) 182 | bufnr = bufnr or 0 183 | return vim.api.nvim_buf_line_count(0) == 1 184 | and vim.api.nvim_buf_get_lines(0, 0, -1, false)[1] == '' 185 | end 186 | 187 | local last_footer_size = nil 188 | function utils.add_update_footer_command(bufnr, footer) 189 | vim.api.nvim_create_user_command('DashboardUpdateFooter', function(args) 190 | if last_footer_size == nil then 191 | last_footer_size = #footer 192 | end 193 | 194 | local first_line = vim.api.nvim_buf_line_count(bufnr) 195 | vim.bo[bufnr].modifiable = true 196 | vim.api.nvim_buf_set_lines( 197 | bufnr, 198 | first_line - last_footer_size, 199 | -1, 200 | false, 201 | utils.center_align(args.fargs) 202 | ) 203 | vim.bo[bufnr].modifiable = false 204 | vim.bo[bufnr].modified = false 205 | 206 | last_footer_size = #args.fargs -- For future calculation of size 207 | end, { nargs = '*' }) 208 | end 209 | 210 | return utils 211 | -------------------------------------------------------------------------------- /plugin/dashboard.lua: -------------------------------------------------------------------------------- 1 | -- version 0.2.3 2 | 3 | local g = vim.api.nvim_create_augroup('dashboard', { clear = true }) 4 | 5 | vim.api.nvim_create_autocmd('VimEnter', { 6 | group = g, 7 | callback = function() 8 | for _, v in pairs(vim.v.argv) do 9 | if v == "-" then 10 | vim.g.read_from_stdin = 1 11 | break 12 | end 13 | end 14 | end, 15 | }) 16 | 17 | vim.api.nvim_create_autocmd('UIEnter', { 18 | group = g, 19 | callback = function() 20 | if 21 | vim.fn.argc() == 0 22 | and vim.api.nvim_buf_get_name(0) == '' 23 | and vim.g.read_from_stdin == nil 24 | then 25 | require('dashboard'):instance() 26 | end 27 | end, 28 | }) 29 | 30 | vim.api.nvim_create_user_command('Dashboard', function() 31 | require('dashboard'):instance() 32 | end, {}) 33 | --------------------------------------------------------------------------------