Line data Source code
1 : -- lua/yoda/large_file.lua
2 : -- Large file detection and optimization system
3 :
4 1 : local M = {}
5 :
6 1 : local notify = require("yoda-adapters.notification")
7 :
8 : -- ============================================================================
9 : -- Configuration
10 : -- ============================================================================
11 :
12 1 : local DEFAULT_CONFIG = {
13 : size_threshold = 100 * 1024, -- 100KB
14 : show_notification = true,
15 1 : disable = {
16 : editorconfig = true,
17 : treesitter = true,
18 : lsp = true,
19 : gitsigns = true,
20 : autosave = true,
21 : diagnostics = true,
22 : syntax = false, -- Keep basic syntax for now
23 : swap = true,
24 : undo = true,
25 : backup = true,
26 1 : },
27 : }
28 :
29 : -- ============================================================================
30 : -- State Management
31 : -- ============================================================================
32 :
33 1 : local config = DEFAULT_CONFIG
34 :
35 : --- Get configuration
36 : --- @return table Configuration
37 1 : function M.get_config()
38 3 : return config
39 1 : end
40 :
41 : --- Update configuration, register the BufReadPre detection autocmd, and set up
42 : --- user commands. All three are bundled here so callers need only one setup()
43 : --- call, matching the pattern used by filetype_detection, performance_autocmds,
44 : --- and git_refresh.
45 : --- @param user_config table User configuration
46 1 : function M.setup(user_config)
47 50 : config = vim.tbl_deep_extend("force", DEFAULT_CONFIG, user_config or {})
48 :
49 50 : vim.api.nvim_create_autocmd("BufReadPre", {
50 25 : group = vim.api.nvim_create_augroup("YodaLargeFile", { clear = true }),
51 : desc = "Detect and optimize for large files",
52 : callback = function(args)
53 : M.on_buf_read(args.buf)
54 25 : end,
55 : })
56 :
57 25 : M.setup_commands()
58 26 : end
59 :
60 : --- Check if buffer is marked as large file
61 : --- @param buf number Buffer number
62 : --- @return boolean
63 1 : function M.is_large_file(buf)
64 20 : buf = buf or vim.api.nvim_get_current_buf()
65 60 : return vim.b[buf].large_file == true
66 1 : end
67 :
68 : --- Get file size in bytes
69 : --- @param filepath string File path
70 : --- @return number|nil size File size in bytes or nil on error
71 : local function get_file_size(filepath)
72 3 : if not filepath or filepath == "" then
73 1 : return nil
74 : end
75 :
76 2 : local ok, stats = pcall(vim.uv.fs_stat, filepath)
77 2 : if ok and stats then
78 2 : return stats.size
79 : end
80 : return nil
81 1 : end
82 :
83 : --- Format file size for display
84 : --- @param bytes number File size in bytes
85 : --- @return string Formatted size
86 : local function format_size(bytes)
87 9 : if bytes < 1024 then
88 : return bytes .. "B"
89 9 : elseif bytes < 1024 * 1024 then
90 9 : return string.format("%.1fKB", bytes / 1024)
91 : else
92 : return string.format("%.1fMB", bytes / (1024 * 1024))
93 : end
94 1 : end
95 :
96 : -- ============================================================================
97 : -- Feature Disabling
98 : -- ============================================================================
99 :
100 : --- Disable EditorConfig for buffer
101 : --- @param buf number Buffer number
102 : local function disable_editorconfig(buf)
103 18 : vim.b[buf].editorconfig = false
104 10 : end
105 :
106 : --- Disable TreeSitter for buffer
107 : --- @param buf number Buffer number
108 : local function disable_treesitter(buf)
109 18 : vim.schedule(function()
110 : if vim.api.nvim_buf_is_valid(buf) then
111 : local ok, ts_highlight = pcall(require, "nvim-treesitter.highlight")
112 : if ok and ts_highlight then
113 : pcall(ts_highlight.detach, buf)
114 : end
115 : end
116 9 : end)
117 10 : end
118 :
119 : --- Disable LSP for buffer
120 : --- @param buf number Buffer number
121 : local function disable_lsp(buf)
122 18 : vim.schedule(function()
123 : if vim.api.nvim_buf_is_valid(buf) then
124 : -- Stop all LSP clients attached to this buffer
125 : local clients = vim.lsp.get_clients({ bufnr = buf })
126 : for _, client in ipairs(clients) do
127 : client:detach(buf)
128 : end
129 : end
130 9 : end)
131 10 : end
132 :
133 : --- Disable git signs for buffer
134 : --- @param buf number Buffer number
135 : local function disable_gitsigns(buf)
136 18 : vim.schedule(function()
137 : if vim.api.nvim_buf_is_valid(buf) then
138 : local gs = package.loaded.gitsigns
139 : if gs then
140 : pcall(gs.detach, buf)
141 : end
142 : end
143 9 : end)
144 10 : end
145 :
146 : --- Disable diagnostics for buffer
147 : --- @param buf number Buffer number
148 : local function disable_diagnostics(buf)
149 10 : vim.diagnostic.enable(false, { bufnr = buf })
150 10 : end
151 :
152 : --- Set buffer-local options for large files
153 : --- @param buf number Buffer number
154 : local function set_large_file_options(buf)
155 : -- Buffer-local options only (verified with nvim_get_option_info2)
156 9 : local buffer_opts = {
157 : swapfile = false, -- buf scope
158 : undofile = false, -- buf scope
159 : undolevels = -1, -- buf scope
160 : synmaxcol = 200, -- buf scope - Limit syntax highlighting columns
161 : }
162 :
163 45 : for opt, value in pairs(buffer_opts) do
164 36 : vim.api.nvim_set_option_value(opt, value, { buf = buf })
165 : end
166 :
167 : -- Window-local options (use scope = "local")
168 9 : vim.api.nvim_set_option_value("foldmethod", "manual", { scope = "local" })
169 9 : vim.api.nvim_set_option_value("foldenable", false, { scope = "local" })
170 :
171 : -- Global options (cannot use buf parameter)
172 9 : vim.opt.backup = false
173 9 : vim.opt.writebackup = false
174 9 : vim.opt.lazyredraw = true
175 18 : vim.opt.eventignore:append("FileType")
176 10 : end
177 :
178 : -- ============================================================================
179 : -- Detection and Setup
180 : -- ============================================================================
181 :
182 : --- Enable large file mode for buffer
183 : --- @param buf number Buffer number
184 : --- @param size number File size in bytes
185 1 : function M.enable_large_file_mode(buf, size)
186 20 : if M.is_large_file(buf) then
187 1 : return -- Already enabled
188 : end
189 :
190 : -- Mark buffer as large file
191 18 : vim.b[buf].large_file = true
192 18 : vim.b[buf].large_file_size = size
193 :
194 : -- Apply optimizations based on config
195 9 : if config.disable.editorconfig then
196 9 : disable_editorconfig(buf)
197 : end
198 :
199 9 : if config.disable.treesitter then
200 9 : disable_treesitter(buf)
201 : end
202 :
203 9 : if config.disable.lsp then
204 9 : disable_lsp(buf)
205 : end
206 :
207 9 : if config.disable.gitsigns then
208 9 : disable_gitsigns(buf)
209 : end
210 :
211 9 : if config.disable.diagnostics then
212 9 : disable_diagnostics(buf)
213 : end
214 :
215 : -- Set buffer options
216 9 : set_large_file_options(buf)
217 :
218 : -- Notify user
219 9 : if config.show_notification then
220 9 : local filename = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(buf), ":t")
221 9 : local size_str = format_size(size)
222 18 : notify.notify(
223 9 : string.format("📊 Large file mode enabled for %s (%s)\nSome features disabled for better performance", filename, size_str),
224 9 : "info",
225 : { title = "Large File" }
226 : )
227 : end
228 10 : end
229 :
230 : --- Disable large file mode for buffer
231 : --- @param buf number Buffer number
232 1 : function M.disable_large_file_mode(buf)
233 3 : buf = buf or vim.api.nvim_get_current_buf()
234 :
235 6 : if not M.is_large_file(buf) then
236 1 : return
237 : end
238 :
239 : -- Clear large file markers
240 4 : vim.b[buf].large_file = false
241 4 : vim.b[buf].large_file_size = nil
242 :
243 : -- Re-enable editorconfig
244 4 : vim.b[buf].editorconfig = true
245 :
246 : -- Notify user
247 2 : notify.notify("📊 Large file mode disabled - reload buffer to re-enable features", "info", { title = "Large File" })
248 3 : end
249 :
250 : --- Check and handle large file on buffer read
251 : --- @param buf number Buffer number
252 1 : function M.on_buf_read(buf)
253 3 : local filepath = vim.api.nvim_buf_get_name(buf)
254 3 : local size = get_file_size(filepath)
255 :
256 3 : if size and size > config.size_threshold then
257 1 : M.enable_large_file_mode(buf, size)
258 : end
259 4 : end
260 :
261 : -- ============================================================================
262 : -- Commands
263 : -- ============================================================================
264 :
265 : --- Setup user commands
266 1 : function M.setup_commands()
267 52 : vim.api.nvim_create_user_command("LargeFileEnable", function()
268 : local buf = vim.api.nvim_get_current_buf()
269 : local filepath = vim.api.nvim_buf_get_name(buf)
270 : local size = get_file_size(filepath) or 0
271 : M.enable_large_file_mode(buf, size)
272 26 : end, { desc = "Enable large file mode for current buffer" })
273 :
274 52 : vim.api.nvim_create_user_command("LargeFileDisable", function()
275 : M.disable_large_file_mode()
276 26 : end, { desc = "Disable large file mode for current buffer" })
277 :
278 52 : vim.api.nvim_create_user_command("LargeFileStatus", function()
279 : local buf = vim.api.nvim_get_current_buf()
280 : if M.is_large_file(buf) then
281 : local size = vim.b[buf].large_file_size or 0
282 : local filename = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(buf), ":t")
283 : notify.notify(
284 : string.format("Large file mode: ENABLED\nFile: %s\nSize: %s\nThreshold: %s", filename, format_size(size), format_size(config.size_threshold)),
285 : "info",
286 : { title = "Large File Status" }
287 : )
288 : else
289 : notify.notify("Large file mode: DISABLED", "info", { title = "Large File Status" })
290 : end
291 26 : end, { desc = "Show large file mode status" })
292 :
293 52 : vim.api.nvim_create_user_command("LargeFileConfig", function()
294 : notify.notify(vim.inspect(config), "info", { title = "Large File Configuration" })
295 26 : end, { desc = "Show large file configuration" })
296 27 : end
297 :
298 : -- ============================================================================
299 : -- Auto-save Protection
300 : -- ============================================================================
301 :
302 : --- Check if auto-save should be skipped for buffer
303 : --- @param buf number Buffer number
304 : --- @return boolean should_skip
305 1 : function M.should_skip_autosave(buf)
306 3 : if not config.disable.autosave then
307 1 : return false
308 : end
309 2 : return M.is_large_file(buf)
310 1 : end
311 :
312 1 : return M
|