├── .editorconfig ├── .gitignore ├── .luacheckrc ├── .luarc.json ├── .stylua.toml ├── README.md ├── doc └── nvim-tree.lua-float-preview.txt └── lua ├── float-preview.lua └── float-preview ├── config.lua └── utils.lua /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | end_of_line = lf 6 | 7 | [*.lua] 8 | indent_style = space 9 | indent_size = 2 10 | 11 | [nvim-tree-lua.txt] 12 | max_line_length = 78 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vimrc.lua 2 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | -- vim: ft=lua tw=80 2 | 3 | -- Don't report unused self arguments of methods. 4 | self = false 5 | 6 | ignore = { 7 | "631", -- max_line_length 8 | } 9 | 10 | -- Global objects defined by the C code 11 | globals = { 12 | "vim", 13 | "TreeExplorer" 14 | } 15 | -------------------------------------------------------------------------------- /.luarc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", 3 | "runtime.version": "Lua 5.1", 4 | "diagnostics": { 5 | "globals": ["vim"], 6 | "neededFileStatus": { 7 | "ambiguity-1": "Any", 8 | "assign-type-mismatch": "Any", 9 | "await-in-sync": "Any", 10 | "cast-local-type": "Any", 11 | "cast-type-mismatch": "Any", 12 | "circle-doc-class": "Any", 13 | "close-non-object": "Any", 14 | "code-after-break": "Any", 15 | "codestyle-check": "None", 16 | "count-down-loop": "Any", 17 | "deprecated": "Any", 18 | "different-requires": "Any", 19 | "discard-returns": "Any", 20 | "doc-field-no-class": "Any", 21 | "duplicate-doc-alias": "Any", 22 | "duplicate-doc-field": "Any", 23 | "duplicate-doc-param": "Any", 24 | "duplicate-index": "Any", 25 | "duplicate-set-field": "Any", 26 | "empty-block": "Any", 27 | "global-in-nil-env": "Any", 28 | "lowercase-global": "Any", 29 | "missing-parameter": "Any", 30 | "missing-return": "Any", 31 | "missing-return-value": "Any", 32 | "need-check-nil": "Any", 33 | "newfield-call": "Any", 34 | "newline-call": "Any", 35 | "no-unknown": "None", 36 | "not-yieldable": "Any", 37 | "param-type-mismatch": "Any", 38 | "redefined-local": "Any", 39 | "redundant-parameter": "Any", 40 | "redundant-return": "Any", 41 | "redundant-return-value": "Any", 42 | "redundant-value": "Any", 43 | "return-type-mismatch": "Any", 44 | "spell-check": "None", 45 | "trailing-space": "Any", 46 | "unbalanced-assignments": "Any", 47 | "undefined-doc-class": "Any", 48 | "undefined-doc-name": "Any", 49 | "undefined-doc-param": "Any", 50 | "undefined-env-child": "Any", 51 | "undefined-field": "None", 52 | "undefined-global": "Any", 53 | "unknown-cast-variable": "Any", 54 | "unknown-diag-code": "Any", 55 | "unknown-operator": "Any", 56 | "unreachable-code": "Any", 57 | "unused-function": "Any", 58 | "unused-label": "Any", 59 | "unused-local": "Any", 60 | "unused-vararg": "Any" 61 | } 62 | }, 63 | "workspace.checkThirdParty": "Disable", 64 | "workspace.library": [ 65 | "/home/kron/.local/share/nvim/lazy/neodev.nvim/types/nightly", 66 | "/home/kron/.local/share/bob/nightly-SVMd8w7v/nvim-linux64/share/nvim/runtime/lua", 67 | "/Users/kron/projects/nvim-tree.lua-float-preview/lua", 68 | "${3rd}/luv/library" 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /.stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 120 2 | line_endings = "Unix" 3 | indent_type = "Spaces" 4 | indent_width = 2 5 | quote_style = "AutoPreferDouble" 6 | call_parentheses = "None" 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Float Preview for nvim-tree.lua 2 | 3 | Example 4 | ![ezgif com-video-to-gif](https://github.com/JMarkin/nvim-tree.lua-float-preview/assets/15740814/cc2ba591-131b-42e0-afc8-1bac97b1e72a) 5 | 6 | ## How to add 7 | 8 | 1. Install plugin 9 | lazy.nvim example 10 | 11 | ```lua 12 | { 13 | "nvim-tree/nvim-tree.lua", 14 | dependencies = { 15 | { 16 | "JMarkin/nvim-tree.lua-float-preview", 17 | lazy = true, 18 | -- default 19 | opts = { 20 | -- Whether the float preview is enabled by default. When set to false, it has to be "toggled" on. 21 | toggled_on = true, 22 | -- wrap nvimtree commands 23 | wrap_nvimtree_commands = true, 24 | -- lines for scroll 25 | scroll_lines = 20, 26 | -- window config 27 | window = { 28 | style = "minimal", 29 | relative = "win", 30 | border = "rounded", 31 | wrap = false, 32 | }, 33 | mapping = { 34 | -- scroll down float buffer 35 | down = { "" }, 36 | -- scroll up float buffer 37 | up = { "", "" }, 38 | -- enable/disable float windows 39 | toggle = { "" }, 40 | }, 41 | -- hooks if return false preview doesn't shown 42 | hooks = { 43 | pre_open = function(path) 44 | -- if file > 5 MB or not text -> not preview 45 | local size = require("float-preview.utils").get_size(path) 46 | if type(size) ~= "number" then 47 | return false 48 | end 49 | local is_text = require("float-preview.utils").is_text(path) 50 | return size < 5 and is_text 51 | end, 52 | post_open = function(bufnr) 53 | return true 54 | end, 55 | }, 56 | }, 57 | }, 58 | }, 59 | 60 | ``` 61 | 62 | 2. In nvim-tree.lua on_attach function 63 | 64 | ```lua 65 | local function on_attach(bufnr) 66 | local api = require("nvim-tree.api") 67 | local FloatPreview = require("float-preview") 68 | 69 | FloatPreview.attach_nvimtree(bufnr) 70 | 71 | ... 72 | end 73 | ``` 74 | 75 | By default we wrap there are api funcs for close float window before execute: 76 | 77 | - api.node.open.tab 78 | - api.node.open.vertical 79 | - api.node.open.horizontal 80 | - api.node.open.edit 81 | - api.node.open.preview 82 | - api.node.open.no_window_picker 83 | - api.fs.create 84 | - api.fs.remove 85 | - api.fs.rename 86 | 87 | You can manually wrap this in on attach function like 88 | 89 | ```lua 90 | local function on_attach(bufnr) 91 | local api = require("nvim-tree.api") 92 | local FloatPreview = require("float-preview") 93 | 94 | FloatPreview.attach_nvimtree(bufnr) 95 | local close_wrap = FloatPreview.close_wrap 96 | 97 | -- ... 98 | vim.keymap.set("n", "", close_wrap(api.node.open.tab), opts("Open: New Tab")) 99 | vim.keymap.set("n", "", close_wrap(api.node.open.vertical), opts("Open: Vertical Split")) 100 | vim.keymap.set("n", "", close_wrap(api.node.open.horizontal), opts("Open: Horizontal Split")) 101 | vim.keymap.set("n", "", close_wrap(api.node.open.edit), opts("Open")) 102 | vim.keymap.set("n", "", close_wrap(api.node.open.preview), opts("Open")) 103 | vim.keymap.set("n", "o", close_wrap(api.node.open.edit), opts("Open")) 104 | vim.keymap.set("n", "O", close_wrap(api.node.open.no_window_picker), opts("Open: No Window Picker")) 105 | vim.keymap.set("n", "a", close_wrap(api.fs.create), opts("Create")) 106 | vim.keymap.set("n", "d", close_wrap(api.fs.remove), opts("Delete")) 107 | vim.keymap.set("n", "r", close_wrap(api.fs.rename), opts("Rename")) 108 | end 109 | ``` 110 | 111 | ## Configure Window Size 112 | 113 | You can augment the window config by adding arguments to be passed to `nvim_open_win` by providing a `window.open_win_config`. This can be either a table or a function that returns a table. 114 | 115 | ```lua 116 | HEIGHT_PADDING = 10 117 | WIDTH_PADDING = 15 118 | require('float-preview').setup({ 119 | window = { 120 | wrap = false, 121 | trim_height = false, 122 | open_win_config = function() 123 | local screen_w = vim.opt.columns:get() 124 | local screen_h = vim.opt.lines:get() - vim.opt.cmdheight:get() 125 | local window_w_f = (screen_w - WIDTH_PADDING * 2 -1) / 2 126 | local window_w = math.floor(window_w_f) 127 | local window_h = screen_h - HEIGHT_PADDING * 2 128 | local center_x = window_w_f + WIDTH_PADDING + 2 129 | local center_y = ((vim.opt.lines:get() - window_h) / 2) - vim.opt.cmdheight:get() 130 | 131 | return { 132 | style = "minimal", 133 | relative = "editor", 134 | border = "single", 135 | row = center_y, 136 | col = center_x, 137 | width = window_w, 138 | height = window_h 139 | } 140 | end 141 | } 142 | }) 143 | 144 | ``` 145 | 146 | 147 | -------------------------------------------------------------------------------- /doc/nvim-tree.lua-float-preview.txt: -------------------------------------------------------------------------------- 1 | *nvim-tree.lua-float-preview* Float Preview for |nvim-tree.lua| 2 | 3 | Author: JMarkin 4 | 5 | ============================================================================== 6 | CONTENTS *float-preview* 7 | 8 | 1. Introduction |float-preview-introduction| 9 | 2. Setup/Configuration |float-preview-setup| 10 | 3. Mappings |float-preview-mappings| 11 | 4. API |float-preview-api| 12 | 5. Prevent actions |float-preview-prevent-actions| 13 | 14 | ============================================================================== 15 | 1. INTRODUCTION *float-preview-introduction* 16 | 17 | |nvim-tree.lua| operations may be enhanced to show a floating preview window 18 | in addition to their normal functionality. 19 | 20 | The floating window may be customised. 21 | 22 | ============================================================================== 23 | 2. SETUP *float-preview-setup* 24 | 25 | You may optionally run setup function once at neovim startup to apply your 26 | configuration. 27 | 28 | Additional setup calls may be made to update the configuration. See example 29 | with defaults: > 30 | 31 | require("float-preview").setup({ 32 | -- Whether the float preview is enabled by default. When set to false, it has to be "toggled" on. 33 | toggled_on = true, 34 | -- lines for scroll 35 | scroll_lines = 20, 36 | -- window config 37 | window = { 38 | style = "minimal", 39 | relative = "win", 40 | border = "rounded", 41 | }, 42 | mapping = { 43 | -- scroll down float buffer 44 | down = { "" }, 45 | -- scroll up float buffer 46 | up = { "", "" }, 47 | -- enable/disable float windows 48 | toggle = { "" }, 49 | }, 50 | -- hooks if return false preview doesn't shown 51 | hooks = { 52 | pre_open = function(path) 53 | -- if file > 5 MB or not text -> not preview 54 | local size = require("float-preview.utils").get_size(path) 55 | if type(size) ~= "number" then 56 | return false 57 | end 58 | local is_text = require("float-preview.utils").is_text(path) 59 | return size < 5 and is_text 60 | end, 61 | post_open = function(bufnr) 62 | return true 63 | end, 64 | }, 65 | }, 66 | }) 67 | 68 | 69 | *float-preview.wrap_nvimtree_commands* 70 | Wrap nvimtree commands. 71 | By default we wrap there are api funcs for close float window before execute: 72 | - api.node.open.tab 73 | - api.node.open.vertical 74 | - api.node.open.horizontal 75 | - api.node.open.edit 76 | - api.node.open.preview 77 | - api.node.open.no_window_picker 78 | - api.fs.create 79 | - api.fs.remove 80 | - api.fs.rename 81 | 82 | You can manually wrap this in on attach function like 83 | 84 | local function on_attach(bufnr) 85 | local api = require("nvim-tree.api") 86 | local FloatPreview = require("float-preview") 87 | 88 | FloatPreview.attach_nvimtree(bufnr) 89 | local close_wrap = FloatPreview.close_wrap 90 | 91 | --- There are keymaps must to wrap for correct work 92 | -- ... 93 | vim.keymap.set("n", "", close_wrap(api.node.open.tab), opts("Open: New Tab")) 94 | vim.keymap.set("n", "", close_wrap(api.node.open.vertical), opts("Open: Vertical Split")) 95 | vim.keymap.set("n", "", close_wrap(api.node.open.horizontal), opts("Open: Horizontal Split")) 96 | vim.keymap.set("n", "", close_wrap(api.node.open.edit), opts("Open")) 97 | vim.keymap.set("n", "", close_wrap(api.node.open.preview), opts("Open")) 98 | vim.keymap.set("n", "o", close_wrap(api.node.open.edit), opts("Open")) 99 | vim.keymap.set("n", "O", close_wrap(api.node.open.no_window_picker), opts("Open: No Window Picker")) 100 | vim.keymap.set("n", "a", close_wrap(api.fs.create), opts("Create")) 101 | vim.keymap.set("n", "d", close_wrap(api.fs.remove), opts("Delete")) 102 | vim.keymap.set("n", "r", close_wrap(api.fs.rename), opts("Rename")) 103 | end 104 | 105 | 106 | Type: `boolean`, Default: `true` 107 | < 108 | *float-preview.toggled_on* 109 | Whether the float preview is enabled by default. When set to false, it has to be "toggled" on. 110 | Type: `boolean`, Default: `true` 111 | 112 | *float-preview.scroll_lines* 113 | Maximum number of lines the preview window will scroll vertically. 114 | Type: `number`, Default: `20` 115 | 116 | *float-preview.window* 117 | Float window options 118 | 119 | *float-preview.window.style* 120 | Style of window 121 | Type: `string`, Default: `minimal` 122 | 123 | *float-preview.window.relative* 124 | Type: `string`, Default: `win` 125 | 126 | *float-preview.window.border* 127 | Type: `string`, Default: `rounded` 128 | 129 | *float-preview.window.wrap* 130 | wrap lines inside window 131 | Type: `boolean`, Default: `false` 132 | 133 | *float-preview.window.trim_height* 134 | trim window height when file has fewer lines than configured window height 135 | Type: `boolean`, Default: `true` 136 | 137 | *float-preview.window.open_win_config* 138 | Additional floating window config. See |nvim_open_win| for more details. 139 | Type: `table | function` returning a table 140 | Default: `nil` 141 | 142 | *float-preview.mapping* 143 | Mapping for manipulate float window if focus on nvim-tree. 144 | 145 | *float-preview.mapping.down* 146 | Scroll the flaot window down. 147 | Type: `table`, Default: `{ "", }` 148 | 149 | *float-preview.mapping.up* 150 | Scroll the flaot window up. 151 | Type: `table`, Default: `{ "", "", }` 152 | 153 | *float-preview.mapping.toggle* 154 | Enable/disable float window 155 | Type: `table`, Default: `{ "" }` 156 | 157 | *float-preview.hooks* 158 | Hooks for float window. 159 | 160 | *float-preview.hooks.pre_open* 161 | Pre open hook. `path` argument. 162 | Type: `function`, Default: > 163 | function(path) 164 | -- if file > 5 MB or not text -> not preview 165 | local size = require("float-preview.utils").get_size(path) 166 | if type(size) ~= "number" then 167 | return false 168 | end 169 | local is_text = require("float-preview.utils").is_text(path) 170 | return size < 5 and is_text 171 | end 172 | < 173 | 174 | *float-preview.hooks.post_open* 175 | Post open hook. `bufnr` argument. 176 | Type: `function`, Default: > 177 | post_open = function(bufnr) 178 | return true 179 | end 180 | < 181 | 182 | ============================================================================== 183 | 3. MAPPINGS *float-preview-mappings* 184 | 185 | |nvim-tree| buffer mappings are needed to activate functionality. These take 186 | the form of a wrapper around |nvim-tree-api| calls in your |nvim-tree.on_attach| 187 | function. See |nvim-tree-mappings| for more details. 188 | 189 | Common functions to wrap may include: 190 | - |nvim-tree-api.node| for opening and previewing nodes 191 | - |nvim-tree-api.tree| to handle tree open/close 192 | - |nvim-tree-api.fs| for file creation 193 | 194 | The float preview is then attached to the nvim-tree buffer: > 195 | require("float-preview").attach_nvimtree(bufnr) 196 | < 197 | The `close_wrap` function is then used to wrap |nvim-tree-api calls e.g.: > 198 | require("float-preview").close_wrap(api.node.open.edit) 199 | < 200 | Full example |nvim-tree.on_attach|: > 201 | local function my_on_attach(bufnr) 202 | local api = require("nvim-tree.api") 203 | local FloatPreview = require("float-preview") 204 | 205 | FloatPreview.attach_nvimtree(bufnr) 206 | local float_close_wrap = FloatPreview.close_wrap 207 | 208 | --- There are keymaps must to wrap for correct work 209 | -- ... 210 | vim.keymap.set("n", "", float_close_wrap(api.node.open.tab), opts("Open: New Tab")) 211 | vim.keymap.set("n", "", float_close_wrap(api.node.open.vertical), opts("Open: Vertical Split")) 212 | vim.keymap.set("n", "", float_close_wrap(api.node.open.horizontal), opts("Open: Horizontal Split")) 213 | vim.keymap.set("n", "", float_close_wrap(api.node.open.edit), opts("Open")) 214 | vim.keymap.set("n", "", float_close_wrap(api.node.open.preview), opts("Open")) 215 | vim.keymap.set("n", "o", float_close_wrap(api.node.open.edit), opts("Open")) 216 | vim.keymap.set("n", "O", float_close_wrap(api.node.open.no_window_picker), opts("Open: No Window Picker")) 217 | vim.keymap.set("n", "q", float_close_wrap(api.tree.close), opts("Close")) 218 | vim.keymap.set("n", "a", float_close_wrap(api.fs.create), opts("Create")) 219 | vim.keymap.set("n", "d", float_close_wrap(api.fs.remove), opts("Delete")) 220 | vim.keymap.set("n", "r", float_close_wrap(api.fs.rename), opts("Rename")) 221 | end 222 | < 223 | Do not forget to set `my_on_attach` in |nvim-tree-setup| e.g. > 224 | require("nvim-tree").setup { 225 | --- 226 | on_attach = my_on_attach, 227 | --- 228 | } 229 | < 230 | ============================================================================== 231 | 4. API *float-preview-api* 232 | 233 | is_float({bufnr}) *float-preview-api.is_float()* 234 | Detect if buffer is own float nvim-tree window. Useful for prevent 235 | actions, diagnostics, attach lsp, etc 236 | 237 | Parameters: ~ 238 | • {bufnr} (number|nil) buffer handle, 0 or nil for current buffer 239 | 240 | Return: ~ 241 | (boolean) buffer is a floating preview 242 | 243 | 244 | vim:tw=78:ts=4:sw=4:et:ft=help:norl: 245 | -------------------------------------------------------------------------------- /lua/float-preview.lua: -------------------------------------------------------------------------------- 1 | local CFG = require "float-preview.config" 2 | 3 | local api = require "nvim-tree.api" 4 | local Event = api.events.Event 5 | 6 | local get_node = api.tree.get_node_under_cursor 7 | 8 | local FloatPreview = {} 9 | FloatPreview.__index = FloatPreview 10 | 11 | local preview_au = "float_preview_au" 12 | vim.api.nvim_create_augroup(preview_au, { clear = true }) 13 | 14 | local st = {} 15 | local all_floats = {} 16 | local disabled = false 17 | 18 | -- orignal fzf lua 19 | local read_file_async = function(filepath, callback) 20 | vim.loop.fs_open(filepath, "r", 438, function(err_open, fd) 21 | if err_open then 22 | -- we must schedule this or we get 23 | -- E5560: nvim_exec must not be called in a lua loop callback 24 | vim.schedule(function() 25 | vim.notify(("Unable to open file '%s', error: %s"):format(filepath, err_open), vim.log.levels.WARN) 26 | end) 27 | return 28 | end 29 | vim.loop.fs_fstat(fd, function(err_fstat, stat) 30 | assert(not err_fstat, err_fstat) 31 | if stat.type ~= "file" then 32 | return callback "" 33 | end 34 | vim.loop.fs_read(fd, stat.size, 0, function(err_read, data) 35 | assert(not err_read, err_read) 36 | vim.loop.fs_close(fd, function(err_close) 37 | assert(not err_close, err_close) 38 | return callback(data) 39 | end) 40 | end) 41 | end) 42 | end) 43 | end 44 | 45 | function FloatPreview.is_float(bufnr, path) 46 | if path then 47 | return st[path] ~= nil 48 | end 49 | if not bufnr then 50 | bufnr = vim.api.nvim_get_current_buf() 51 | end 52 | 53 | return st[bufnr] ~= nil 54 | end 55 | 56 | local function all_close() 57 | for _, fl in pairs(all_floats) do 58 | fl:_close() 59 | end 60 | end 61 | 62 | FloatPreview.close = all_close 63 | 64 | local function all_open() 65 | for _, fl in pairs(all_floats) do 66 | fl:preview_under_cursor() 67 | end 68 | end 69 | 70 | function FloatPreview.setup(cfg) 71 | CFG.update(cfg) 72 | 73 | cfg = CFG.config() 74 | 75 | disabled = not cfg.toggled_on 76 | 77 | if cfg.wrap_nvimtree_commands then 78 | api.node.open.tab = FloatPreview.close_wrap(api.node.open.tab) 79 | api.node.open.vertical = FloatPreview.close_wrap(api.node.open.vertical) 80 | api.node.open.horizontal = FloatPreview.close_wrap(api.node.open.horizontal) 81 | api.node.open.edit = FloatPreview.close_wrap(api.node.open.edit) 82 | api.node.open.preview = FloatPreview.close_wrap(api.node.open.preview) 83 | api.node.open.no_window_picker = FloatPreview.close_wrap(api.node.open.no_window_picker) 84 | api.fs.create = FloatPreview.close_wrap(api.fs.create) 85 | api.fs.remove = FloatPreview.close_wrap(api.fs.remove) 86 | api.fs.rename = FloatPreview.close_wrap(api.fs.rename) 87 | end 88 | end 89 | 90 | function FloatPreview.attach_nvimtree(bufnr) 91 | local prev = FloatPreview:new() 92 | prev:attach(bufnr) 93 | return prev 94 | end 95 | 96 | function FloatPreview.close_wrap(f) 97 | return function(...) 98 | all_close() 99 | local args = ... 100 | return vim.schedule(function() 101 | f(args) 102 | end) 103 | end 104 | end 105 | 106 | function FloatPreview.toggle() 107 | disabled = not disabled 108 | if disabled then 109 | all_close() 110 | else 111 | all_open() 112 | end 113 | end 114 | 115 | function FloatPreview:new(cfg) 116 | local prev = {} 117 | setmetatable(prev, FloatPreview) 118 | 119 | cfg = cfg or CFG.config() 120 | prev.buf = nil 121 | prev.win = nil 122 | prev.path = nil 123 | prev.current_line = 1 124 | prev.max_line = 999999 125 | prev.disabled = false 126 | prev.cfg = cfg 127 | 128 | local function action_wrap(f) 129 | return function(...) 130 | prev:_close() 131 | return f(...) 132 | end 133 | end 134 | 135 | return prev, action_wrap 136 | end 137 | 138 | function FloatPreview:_close(reason) 139 | if self.path ~= nil and self.buf ~= nil then 140 | if reason then 141 | -- vim.notify(string.format("fp close %s", reason)) 142 | end 143 | pcall(vim.api.nvim_win_close, self.win, { force = true }) 144 | pcall(vim.api.nvim_buf_delete, self.buf, { force = true }) 145 | self.win = nil 146 | st[self.buf] = nil 147 | st[self.path] = nil 148 | self.buf = nil 149 | self.path = nil 150 | self.current_line = 1 151 | self.max_line = 999999 152 | end 153 | end 154 | 155 | --TODO: add bat preview 156 | function FloatPreview:preview(path) 157 | if disabled then 158 | return 159 | end 160 | 161 | if not self.cfg.hooks.pre_open(path) then 162 | return 163 | end 164 | 165 | self.path = path 166 | self.buf = vim.api.nvim_create_buf(false, false) 167 | st[self.path] = 1 168 | st[self.buf] = 1 169 | 170 | vim.api.nvim_set_option_value("bufhidden", "wipe", { buf = self.buf }) 171 | vim.api.nvim_set_option_value("buftype", "nofile", { buf = self.buf }) 172 | vim.api.nvim_set_option_value("buflisted", false, { buf = self.buf }) 173 | 174 | local width = vim.api.nvim_get_option_value("columns", {}) 175 | local height = vim.api.nvim_get_option_value("lines", {}) 176 | local prev_height = math.ceil(height / 2) 177 | local opts = { 178 | width = math.ceil(width / 2), 179 | height = prev_height, 180 | row = vim.fn.line ".", 181 | col = vim.fn.winwidth(0) + 1, 182 | focusable = false, 183 | noautocmd = true, 184 | style = self.cfg.window.style, 185 | relative = self.cfg.window.relative, 186 | border = self.cfg.window.border, 187 | } 188 | 189 | local open_win_config = self.cfg.window.open_win_config 190 | if type(open_win_config) == "function" then 191 | open_win_config = open_win_config() 192 | end 193 | 194 | if open_win_config then 195 | for k, v in pairs(open_win_config) do 196 | opts[k] = v 197 | end 198 | end 199 | 200 | self.win = vim.api.nvim_open_win(self.buf, true, opts) 201 | vim.api.nvim_set_option_value("wrap", self.cfg.window.wrap, { win = self.win }) 202 | 203 | read_file_async( 204 | path, 205 | vim.schedule_wrap(function(data) 206 | if not self.buf then 207 | return 208 | end 209 | local lines = vim.split(data, "[\r]?\n") 210 | 211 | -- if file ends in new line, don't write an empty string as the last 212 | -- line. 213 | if data:sub(#data, #data) == "\n" or data:sub(#data - 1, #data) == "\r\n" then 214 | table.remove(lines) 215 | end 216 | self.max_line = #lines 217 | if self.cfg.window.trim_height then 218 | if self.max_line < prev_height then 219 | opts.height = self.max_line + 1 220 | opts.noautocmd = nil 221 | vim.api.nvim_win_set_config(self.win, opts) 222 | end 223 | end 224 | vim.api.nvim_buf_set_lines(self.buf, 0, -1, false, lines) 225 | 226 | local ft = vim.filetype.match { buf = self.buf, filename = path } 227 | vim.bo[self.buf].ft = ft 228 | 229 | local has_lang, lang = pcall(vim.treesitter.language.get_lang, ft) 230 | local has_ts, _ = pcall(vim.treesitter.start, self.buf, has_lang and lang or ft) 231 | local syntax_ = vim.g.syntax_on or vim.g.syntax_manual 232 | 233 | if not has_ts and syntax_ then 234 | vim.bo[self.buf].syntax = ft 235 | end 236 | end) 237 | ) 238 | if not self.cfg.hooks.post_open(self.buf) then 239 | self:_close "post open" 240 | end 241 | end 242 | 243 | function FloatPreview:preview_under_cursor() 244 | local _, node = pcall(get_node) 245 | if not node then 246 | return 247 | end 248 | 249 | if node.absolute_path == self.path then 250 | return 251 | end 252 | self:_close "change file" 253 | 254 | if node.type ~= "file" then 255 | return 256 | end 257 | 258 | local win = vim.api.nvim_get_current_win() 259 | self:preview(node.absolute_path) 260 | 261 | local ok, _ = pcall(vim.api.nvim_set_current_win, win) 262 | if not ok then 263 | self:_close "cant set win" 264 | end 265 | end 266 | 267 | function FloatPreview:scroll(line) 268 | if self.win then 269 | local ok, _ = pcall(vim.api.nvim_win_set_cursor, self.win, { line, 0 }) 270 | if ok then 271 | self.current_line = line 272 | end 273 | end 274 | end 275 | 276 | function FloatPreview:scroll_down() 277 | if self.buf then 278 | local next_line = math.min(self.current_line + self.cfg.scroll_lines, self.max_line) 279 | self:scroll(next_line) 280 | end 281 | end 282 | 283 | function FloatPreview:scroll_up() 284 | if self.buf then 285 | local next_line = math.max(self.current_line - self.cfg.scroll_lines, 1) 286 | self:scroll(next_line) 287 | end 288 | end 289 | 290 | function FloatPreview:attach(bufnr) 291 | for _, key in ipairs(self.cfg.mapping.up) do 292 | vim.keymap.set("n", key, function() 293 | self:scroll_up() 294 | end, { buffer = bufnr }) 295 | end 296 | 297 | for _, key in ipairs(self.cfg.mapping.down) do 298 | vim.keymap.set("n", key, function() 299 | self:scroll_down() 300 | end, { buffer = bufnr }) 301 | end 302 | 303 | for _, key in ipairs(self.cfg.mapping.toggle) do 304 | vim.keymap.set("n", key, function() 305 | FloatPreview.toggle() 306 | end, { buffer = bufnr }) 307 | end 308 | local au = {} 309 | 310 | table.insert( 311 | au, 312 | vim.api.nvim_create_autocmd({ "CursorHold" }, { 313 | group = preview_au, 314 | callback = function() 315 | if bufnr == vim.api.nvim_get_current_buf() then 316 | self:preview_under_cursor() 317 | else 318 | self:_close "changed buffer" 319 | end 320 | end, 321 | }) 322 | ) 323 | 324 | api.events.subscribe(Event.TreeClose, function(opts) 325 | if not self then 326 | return 327 | end 328 | all_floats[bufnr] = nil 329 | for _, au_id in pairs(au) do 330 | vim.api.nvim_del_autocmd(au_id) 331 | end 332 | self:_close "nvim close" 333 | self = nil 334 | end) 335 | 336 | all_floats[bufnr] = self 337 | end 338 | 339 | return FloatPreview 340 | -------------------------------------------------------------------------------- /lua/float-preview/config.lua: -------------------------------------------------------------------------------- 1 | local CFG = { 2 | _cfg = { 3 | -- Whether the float preview is enabled by default. When set to false, it has to be "toggled" on. 4 | toggled_on = true, 5 | -- wrap nvimtree commands 6 | wrap_nvimtree_commands = true, 7 | -- lines for scroll 8 | scroll_lines = 20, 9 | -- window config 10 | window = { 11 | style = "minimal", 12 | relative = "win", 13 | border = "rounded", 14 | wrap = false, 15 | trim_height = true, 16 | }, 17 | mapping = { 18 | -- scroll down float buffer 19 | down = { "" }, 20 | -- scroll up float buffer 21 | up = { "", "" }, 22 | -- enable/disable float windows 23 | toggle = { "" }, 24 | }, 25 | -- hooks if return false preview doesn't shown 26 | hooks = { 27 | pre_open = function(path) 28 | -- if file > 5 MB or not text -> not preview 29 | local size = require("float-preview.utils").get_size(path) 30 | if type(size) ~= "number" then 31 | return false 32 | end 33 | local is_text = require("float-preview.utils").is_text(path) 34 | return size < 5 and is_text 35 | end, 36 | post_open = function(bufnr) 37 | return true 38 | end, 39 | }, 40 | }, 41 | } 42 | 43 | CFG.config = function() 44 | return CFG._cfg 45 | end 46 | 47 | CFG.update = function(cfg) 48 | if not cfg then 49 | return 50 | end 51 | 52 | CFG._cfg = vim.tbl_extend("force", CFG._cfg, cfg) 53 | end 54 | 55 | return CFG 56 | -------------------------------------------------------------------------------- /lua/float-preview/utils.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | M.get_size = function(path) 3 | local ok, stats = pcall(function() 4 | return vim.loop.fs_stat(path) 5 | end) 6 | if not (ok and stats) then 7 | return 8 | end 9 | return math.floor(0.5 + (stats.size / (1024 * 1024))) 10 | end 11 | 12 | M.is_text = function(path) 13 | -- Determine if file is text. This is not 100% proof, but good enough. 14 | -- Source: https://github.com/sharkdp/content_inspector 15 | local fd = vim.loop.fs_open(path, "r", 1) 16 | if not fd then 17 | return false 18 | end 19 | local is_text = vim.loop.fs_read(fd, 1024):find "\0" == nil 20 | vim.loop.fs_close(fd) 21 | return is_text 22 | end 23 | 24 | M.detach_lsp_clients = function(bufnr) 25 | -- currently not working 26 | bufnr = bufnr or vim.api.nvim_get_current_buf() 27 | local clients = vim.lsp.buf_get_clients(bufnr) 28 | -- disable notified from buf_detach_client 29 | local prev = vim.notify 30 | vim.notify = function() end 31 | for client_id, _ in pairs(clients) do 32 | vim.lsp.buf_detach_client(bufnr, client_id) 33 | end 34 | vim.notify = prev 35 | end 36 | 37 | M.is_showed = function(path) 38 | for _, winnr in ipairs(vim.api.nvim_tabpage_list_wins(vim.api.nvim_get_current_tabpage())) do 39 | local buf = vim.api.nvim_win_get_buf(winnr) 40 | local _path = vim.api.nvim_buf_get_name(buf) 41 | if _path == path then 42 | return true 43 | end 44 | end 45 | return false 46 | end 47 | 48 | return M 49 | --------------------------------------------------------------------------------