From 28aca0c1f50e5a40eea472eea302ce4cd035ea65 Mon Sep 17 00:00:00 2001 From: Yu Guo Date: Sat, 26 Oct 2024 01:07:11 +0900 Subject: [PATCH 01/34] chore: add __pycache__ to gitignore (#498) * add __pycache__ to gitignore * doc: fix typo --------- Co-authored-by: Steven Arcangeli <506791+stevearc@users.noreply.github.com> --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index d8cb86e1..290f6569 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ luac.out *.zip *.tar.gz +# python bytecode +__pycache__ + # Object files *.o *.os From cca1631d5ea450c09ba72f3951a9e28105a3632c Mon Sep 17 00:00:00 2001 From: Yu Guo Date: Sat, 26 Oct 2024 01:08:39 +0900 Subject: [PATCH 02/34] fix: actions.preview accepts options (#497) * fix: pass opts to actions.preview * add opts type to action.preview * run generate.py script --- doc/oil.txt | 6 ++++++ lua/oil/actions.lua | 18 ++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/doc/oil.txt b/doc/oil.txt index 8e96cf45..70c589dd 100644 --- a/doc/oil.txt +++ b/doc/oil.txt @@ -527,6 +527,12 @@ preview *actions.previe Open the entry under the cursor in a preview window, or close the preview window if already open + Parameters: + {horizontal} `boolean` Open the buffer in a horizontal split + {split} `"aboveleft"|"belowright"|"topleft"|"botright"` Split + modifier + {vertical} `boolean` Open the buffer in a vertical split + preview_scroll_down *actions.preview_scroll_down* Scroll down in the preview window diff --git a/lua/oil/actions.lua b/lua/oil/actions.lua index 6a2a5ffe..b097e680 100644 --- a/lua/oil/actions.lua +++ b/lua/oil/actions.lua @@ -69,7 +69,21 @@ M.select_tab = { M.preview = { desc = "Open the entry under the cursor in a preview window, or close the preview window if already open", - callback = function() + parameters = { + vertical = { + type = "boolean", + desc = "Open the buffer in a vertical split", + }, + horizontal = { + type = "boolean", + desc = "Open the buffer in a horizontal split", + }, + split = { + type = '"aboveleft"|"belowright"|"topleft"|"botright"', + desc = "Split modifier", + }, + }, + callback = function(opts) local entry = oil.get_cursor_entry() if not entry then vim.notify("Could not find entry under cursor", vim.log.levels.ERROR) @@ -88,7 +102,7 @@ M.preview = { return end end - oil.open_preview() + oil.open_preview(opts) end, } From 42333bb46e34dd47e13927010b1dcd30e6e4ca96 Mon Sep 17 00:00:00 2001 From: Foo-x Date: Tue, 29 Oct 2024 02:55:25 +0900 Subject: [PATCH 03/34] fix: add trailing slash to directories on yank_entry (#504) * feat: add trailing slash on yank_entry Closes #503 * style: format --- lua/oil/actions.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lua/oil/actions.lua b/lua/oil/actions.lua index b097e680..50a266ab 100644 --- a/lua/oil/actions.lua +++ b/lua/oil/actions.lua @@ -366,7 +366,11 @@ M.yank_entry = { if not entry or not dir then return end - local path = dir .. entry.name + local name = entry.name + if entry.type == "directory" then + name = name .. "/" + end + local path = dir .. name if opts.modify then path = vim.fn.fnamemodify(path, opts.modify) end From 52cc8a1fb35ea6ce1df536143add7ce7215c63c0 Mon Sep 17 00:00:00 2001 From: Foo-x Date: Thu, 31 Oct 2024 00:53:36 +0900 Subject: [PATCH 04/34] fix: sort keymap help entries by description (#506) Closes #376 --- lua/oil/keymap_util.lua | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lua/oil/keymap_util.lua b/lua/oil/keymap_util.lua index b94581cb..b62756eb 100644 --- a/lua/oil/keymap_util.lua +++ b/lua/oil/keymap_util.lua @@ -78,31 +78,30 @@ M.show_help = function(keymaps) end end - local col_left = {} - local col_desc = {} local max_lhs = 1 + local keymap_entries = {} for k, rhs in pairs(keymaps) do local all_lhs = lhs_to_all_lhs[k] if all_lhs then local _, opts = resolve(rhs) local keystr = table.concat(all_lhs, "/") max_lhs = math.max(max_lhs, vim.api.nvim_strwidth(keystr)) - table.insert(col_left, { str = keystr, all_lhs = all_lhs }) - table.insert(col_desc, opts.desc or "") + table.insert(keymap_entries, { str = keystr, all_lhs = all_lhs, desc = opts.desc or "" }) end end + table.sort(keymap_entries, function(a, b) + return a.desc < b.desc + end) local lines = {} local highlights = {} local max_line = 1 - for i = 1, #col_left do - local left = col_left[i] - local desc = col_desc[i] - local line = string.format(" %s %s", util.rpad(left.str, max_lhs), desc) + for _, entry in ipairs(keymap_entries) do + local line = string.format(" %s %s", util.rpad(entry.str, max_lhs), entry.desc) max_line = math.max(max_line, vim.api.nvim_strwidth(line)) table.insert(lines, line) local start = 1 - for _, key in ipairs(left.all_lhs) do + for _, key in ipairs(entry.all_lhs) do local keywidth = vim.api.nvim_strwidth(key) table.insert(highlights, { "Special", #lines, start, start + keywidth }) start = start + keywidth + 1 From 709403ccd6f22d859c2e42c780ab558ae89284d9 Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Sat, 9 Nov 2024 22:28:24 -0800 Subject: [PATCH 05/34] fix: don't deep merge keymaps (#510) --- lua/oil/config.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lua/oil/config.lua b/lua/oil/config.lua index 5ccde173..2505c3e2 100644 --- a/lua/oil/config.lua +++ b/lua/oil/config.lua @@ -355,9 +355,16 @@ local M = {} ---@field border? string|string[] Window border M.setup = function(opts) - local new_conf = vim.tbl_deep_extend("keep", opts or {}, default_config) + opts = opts or {} + local new_conf = vim.tbl_deep_extend("keep", opts, default_config) if not new_conf.use_default_keymaps then new_conf.keymaps = opts.keymaps or {} + else + -- We don't want to deep merge the keymaps, we want any keymap defined by the user to override + -- everything about the default. + for k, v in pairs(opts.keymaps) do + new_conf.keymaps[k] = v + end end if new_conf.lsp_rename_autosave ~= nil then From 621f8ba4fa821724e9b646732a26fb2e795fe008 Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Sat, 9 Nov 2024 22:31:35 -0800 Subject: [PATCH 06/34] fix: guard against nil keymaps --- lua/oil/config.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/oil/config.lua b/lua/oil/config.lua index 2505c3e2..0c4f1302 100644 --- a/lua/oil/config.lua +++ b/lua/oil/config.lua @@ -359,7 +359,7 @@ M.setup = function(opts) local new_conf = vim.tbl_deep_extend("keep", opts, default_config) if not new_conf.use_default_keymaps then new_conf.keymaps = opts.keymaps or {} - else + elseif opts.keymaps then -- We don't want to deep merge the keymaps, we want any keymap defined by the user to override -- everything about the default. for k, v in pairs(opts.keymaps) do From 1f5b002270addb9010740e34824244e777de7c9e Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Sun, 10 Nov 2024 15:51:46 -0800 Subject: [PATCH 07/34] refactor: rename action preview window to 'confirmation' window --- README.md | 9 ++++--- doc/oil.txt | 9 ++++--- lua/oil/config.lua | 27 ++++++++++++++----- lua/oil/layout.lua | 5 ++++ .../mutator/{preview.lua => confirmation.lua} | 8 +++--- lua/oil/mutator/init.lua | 4 +-- lua/oil/view.lua | 2 +- 7 files changed, 45 insertions(+), 19 deletions(-) rename lua/oil/mutator/{preview.lua => confirmation.lua} (97%) diff --git a/README.md b/README.md index 46063b2c..4d91f1dd 100644 --- a/README.md +++ b/README.md @@ -267,8 +267,13 @@ require("oil").setup({ return conf end, }, - -- Configuration for the actions floating preview window + -- Configuration for the file preview window preview = { + -- Whether the preview window is automatically updated when the cursor is moved + update_on_cursor_moved = true, + }, + -- Configuration for the floating action confirmation window + confirmation = { -- Width dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%) -- min_width and max_width can be a single value or a list of mixed integer/float types. -- max_width = {100, 0.8} means "the lesser of 100 columns or 80% of total" @@ -289,8 +294,6 @@ require("oil").setup({ win_options = { winblend = 0, }, - -- Whether the preview window is automatically updated when the cursor is moved - update_on_cursor_moved = true, }, -- Configuration for the floating progress window progress = { diff --git a/doc/oil.txt b/doc/oil.txt index 70c589dd..cafd61e6 100644 --- a/doc/oil.txt +++ b/doc/oil.txt @@ -152,8 +152,13 @@ CONFIG *oil-confi return conf end, }, - -- Configuration for the actions floating preview window + -- Configuration for the file preview window preview = { + -- Whether the preview window is automatically updated when the cursor is moved + update_on_cursor_moved = true, + }, + -- Configuration for the floating action confirmation window + confirmation = { -- Width dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%) -- min_width and max_width can be a single value or a list of mixed integer/float types. -- max_width = {100, 0.8} means "the lesser of 100 columns or 80% of total" @@ -174,8 +179,6 @@ CONFIG *oil-confi win_options = { winblend = 0, }, - -- Whether the preview window is automatically updated when the cursor is moved - update_on_cursor_moved = true, }, -- Configuration for the floating progress window progress = { diff --git a/lua/oil/config.lua b/lua/oil/config.lua index 0c4f1302..bcb7225d 100644 --- a/lua/oil/config.lua +++ b/lua/oil/config.lua @@ -137,8 +137,13 @@ local default_config = { return conf end, }, - -- Configuration for the actions floating preview window + -- Configuration for the file preview window preview = { + -- Whether the preview window is automatically updated when the cursor is moved + update_on_cursor_moved = true, + }, + -- Configuration for the floating action confirmation window + confirmation = { -- Width dimensions can be integers or a float between 0 and 1 (e.g. 0.4 for 40%) -- min_width and max_width can be a single value or a list of mixed integer/float types. -- max_width = {100, 0.8} means "the lesser of 100 columns or 80% of total" @@ -159,8 +164,6 @@ local default_config = { win_options = { winblend = 0, }, - -- Whether the preview window is automatically updated when the cursor is moved - update_on_cursor_moved = true, }, -- Configuration for the floating progress window progress = { @@ -219,6 +222,7 @@ default_config.adapter_aliases = {} ---@field git oil.GitOptions ---@field float oil.FloatWindowConfig ---@field preview oil.PreviewWindowConfig +---@field confirmation oil.ConfirmationWindowConfig ---@field progress oil.ProgressWindowConfig ---@field ssh oil.SimpleWindowConfig ---@field keymaps_help oil.SimpleWindowConfig @@ -245,7 +249,8 @@ local M = {} ---@field extra_scp_args? string[] Extra arguments to pass to SCP when moving/copying files over SSH ---@field git? oil.SetupGitOptions EXPERIMENTAL support for performing file operations with git ---@field float? oil.SetupFloatWindowConfig Configuration for the floating window in oil.open_float ----@field preview? oil.SetupPreviewWindowConfig Configuration for the actions floating preview window +---@field preview? oil.SetupPreviewWindowConfig Configuration for the file preview window +---@field confirmation? oil.SetupConfirmationWindowConfig Configuration for the floating action confirmation window ---@field progress? oil.SetupProgressWindowConfig Configuration for the floating progress window ---@field ssh? oil.SetupSimpleWindowConfig Configuration for the floating SSH window ---@field keymaps_help? oil.SetupSimpleWindowConfig Configuration for the floating keymaps help window @@ -316,12 +321,16 @@ local M = {} ---@field border? string|string[] Window border ---@field win_options? table ----@class (exact) oil.PreviewWindowConfig : oil.WindowConfig +---@class (exact) oil.PreviewWindowConfig ---@field update_on_cursor_moved boolean ----@class (exact) oil.SetupPreviewWindowConfig : oil.SetupWindowConfig +---@class (exact) oil.ConfirmationWindowConfig : oil.WindowConfig + +---@class (exact) oil.SetupPreviewWindowConfig ---@field update_on_cursor_moved? boolean Whether the preview window is automatically updated when the cursor is moved +---@class (exact) oil.SetupConfirmationWindowConfig : oil.SetupWindowConfig + ---@class (exact) oil.ProgressWindowConfig : oil.WindowConfig ---@field minimized_border string|string[] @@ -356,6 +365,7 @@ local M = {} M.setup = function(opts) opts = opts or {} + local new_conf = vim.tbl_deep_extend("keep", opts, default_config) if not new_conf.use_default_keymaps then new_conf.keymaps = opts.keymaps or {} @@ -367,6 +377,11 @@ M.setup = function(opts) end end + -- Backwards compatibility. We renamed the 'preview' window config to be called 'confirmation'. + if opts.preview and not opts.confirmation then + new_conf.confirmation = vim.tbl_deep_extend("keep", opts.preview, default_config.confirmation) + end + if new_conf.lsp_rename_autosave ~= nil then new_conf.lsp_file_methods.autosave_changes = new_conf.lsp_rename_autosave new_conf.lsp_rename_autosave = nil diff --git a/lua/oil/layout.lua b/lua/oil/layout.lua index f22d26af..8ed7b4e6 100644 --- a/lua/oil/layout.lua +++ b/lua/oil/layout.lua @@ -182,6 +182,11 @@ M.split_window = function(winid, direction, gap) return dim_root, dim_new end +---@param desired_width integer +---@param desired_height integer +---@param opts table +---@return integer width +---@return integer height M.calculate_dims = function(desired_width, desired_height, opts) local width = M.calculate_width(desired_width, opts) local height = M.calculate_height(desired_height, opts) diff --git a/lua/oil/mutator/preview.lua b/lua/oil/mutator/confirmation.lua similarity index 97% rename from lua/oil/mutator/preview.lua rename to lua/oil/mutator/confirmation.lua index 3f8d87dc..4e86abc3 100644 --- a/lua/oil/mutator/preview.lua +++ b/lua/oil/mutator/confirmation.lua @@ -91,7 +91,7 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb) table.insert(lines, "") -- Create the floating window - local width, height = layout.calculate_dims(max_line_width, #lines + 1, config.preview) + local width, height = layout.calculate_dims(max_line_width, #lines + 1, config.confirmation) local ok, winid = pcall(vim.api.nvim_open_win, bufnr, true, { relative = "editor", width = width, @@ -100,7 +100,7 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb) col = math.floor((layout.get_editor_width() - width) / 2), zindex = 152, -- render on top of the floating window title style = "minimal", - border = config.preview.border, + border = config.confirmation.border, }) if not ok then vim.notify(string.format("Error showing oil preview window: %s", winid), vim.log.levels.ERROR) @@ -108,7 +108,7 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb) end vim.bo[bufnr].filetype = "oil_preview" vim.bo[bufnr].syntax = "oil_preview" - for k, v in pairs(config.preview.win_options) do + for k, v in pairs(config.confirmation.win_options) do vim.api.nvim_set_option_value(k, v, { scope = "local", win = winid }) end @@ -155,7 +155,7 @@ M.show = vim.schedule_wrap(function(actions, should_confirm, cb) vim.api.nvim_create_autocmd("VimResized", { callback = function() if vim.api.nvim_win_is_valid(winid) then - width, height = layout.calculate_dims(max_line_width, #lines, config.preview) + width, height = layout.calculate_dims(max_line_width, #lines, config.confirmation) vim.api.nvim_win_set_config(winid, { relative = "editor", width = width, diff --git a/lua/oil/mutator/init.lua b/lua/oil/mutator/init.lua index 8dfd4b05..f15c0692 100644 --- a/lua/oil/mutator/init.lua +++ b/lua/oil/mutator/init.lua @@ -3,12 +3,12 @@ local Trie = require("oil.mutator.trie") local cache = require("oil.cache") local columns = require("oil.columns") local config = require("oil.config") +local confirmation = require("oil.mutator.confirmation") local constants = require("oil.constants") local fs = require("oil.fs") local lsp_helpers = require("oil.lsp.helpers") local oil = require("oil") local parser = require("oil.mutator.parser") -local preview = require("oil.mutator.preview") local util = require("oil.util") local view = require("oil.view") local M = {} @@ -564,7 +564,7 @@ M.try_write_changes = function(confirm, cb) end local actions = M.create_actions_from_diffs(all_diffs) - preview.show(actions, confirm, function(proceed) + confirmation.show(actions, confirm, function(proceed) if not proceed then unlock() cb("Canceled") diff --git a/lua/oil/view.lua b/lua/oil/view.lua index 89e4b8fc..bf34e8b2 100644 --- a/lua/oil/view.lua +++ b/lua/oil/view.lua @@ -413,7 +413,7 @@ M.initialize = function(bufnr) timer:again() return end - timer = vim.loop.new_timer() + timer = uv.new_timer() if not timer then return end From eb5497f0ac54f646a14f52002516c540bf5b72fc Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Sun, 10 Nov 2024 15:56:15 -0800 Subject: [PATCH 08/34] refactor: rename 'preview' config to 'preview_win' --- README.md | 2 +- doc/oil.txt | 2 +- lua/oil/config.lua | 10 +++++++--- lua/oil/view.lua | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4d91f1dd..85b5f01e 100644 --- a/README.md +++ b/README.md @@ -268,7 +268,7 @@ require("oil").setup({ end, }, -- Configuration for the file preview window - preview = { + preview_win = { -- Whether the preview window is automatically updated when the cursor is moved update_on_cursor_moved = true, }, diff --git a/doc/oil.txt b/doc/oil.txt index cafd61e6..03b7bc3a 100644 --- a/doc/oil.txt +++ b/doc/oil.txt @@ -153,7 +153,7 @@ CONFIG *oil-confi end, }, -- Configuration for the file preview window - preview = { + preview_win = { -- Whether the preview window is automatically updated when the cursor is moved update_on_cursor_moved = true, }, diff --git a/lua/oil/config.lua b/lua/oil/config.lua index bcb7225d..31e25f2a 100644 --- a/lua/oil/config.lua +++ b/lua/oil/config.lua @@ -138,7 +138,7 @@ local default_config = { end, }, -- Configuration for the file preview window - preview = { + preview_win = { -- Whether the preview window is automatically updated when the cursor is moved update_on_cursor_moved = true, }, @@ -221,7 +221,7 @@ default_config.adapter_aliases = {} ---@field extra_scp_args string[] ---@field git oil.GitOptions ---@field float oil.FloatWindowConfig ----@field preview oil.PreviewWindowConfig +---@field preview_win oil.PreviewWindowConfig ---@field confirmation oil.ConfirmationWindowConfig ---@field progress oil.ProgressWindowConfig ---@field ssh oil.SimpleWindowConfig @@ -249,7 +249,7 @@ local M = {} ---@field extra_scp_args? string[] Extra arguments to pass to SCP when moving/copying files over SSH ---@field git? oil.SetupGitOptions EXPERIMENTAL support for performing file operations with git ---@field float? oil.SetupFloatWindowConfig Configuration for the floating window in oil.open_float ----@field preview? oil.SetupPreviewWindowConfig Configuration for the file preview window +---@field preview_win? oil.SetupPreviewWindowConfig Configuration for the file preview window ---@field confirmation? oil.SetupConfirmationWindowConfig Configuration for the floating action confirmation window ---@field progress? oil.SetupProgressWindowConfig Configuration for the floating progress window ---@field ssh? oil.SetupSimpleWindowConfig Configuration for the floating SSH window @@ -381,6 +381,10 @@ M.setup = function(opts) if opts.preview and not opts.confirmation then new_conf.confirmation = vim.tbl_deep_extend("keep", opts.preview, default_config.confirmation) end + -- Backwards compatibility. We renamed the 'preview' config to 'preview_win' + if opts.preview and opts.preview.update_on_cursor_moved ~= nil then + new_conf.preview_win.update_on_cursor_moved = opts.preview.update_on_cursor_moved + end if new_conf.lsp_rename_autosave ~= nil then new_conf.lsp_file_methods.autosave_changes = new_conf.lsp_rename_autosave diff --git a/lua/oil/view.lua b/lua/oil/view.lua index bf34e8b2..da404a30 100644 --- a/lua/oil/view.lua +++ b/lua/oil/view.lua @@ -407,7 +407,7 @@ M.initialize = function(bufnr) constrain_cursor() - if config.preview.update_on_cursor_moved then + if config.preview_win.update_on_cursor_moved then -- Debounce and update the preview window if timer then timer:again() From 2f5d4353ee62e117b4e7c3856f16aaf8760867b3 Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Sun, 10 Nov 2024 16:06:43 -0800 Subject: [PATCH 09/34] doc: improve type annotations for oil.open_preview --- README.md | 2 +- doc/api.md | 19 ++++++++++--------- doc/oil.txt | 12 +++++++----- lua/oil/init.lua | 11 +++++++---- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 85b5f01e..6bf02823 100644 --- a/README.md +++ b/README.md @@ -360,7 +360,7 @@ Note that at the moment the ssh adapter does not support Windows machines, and i - [toggle_float(dir)](doc/api.md#toggle_floatdir) - [open(dir)](doc/api.md#opendir) - [close()](doc/api.md#close) -- [open_preview(opts)](doc/api.md#open_previewopts) +- [open_preview(opts, callback)](doc/api.md#open_previewopts-callback) - [select(opts, callback)](doc/api.md#selectopts-callback) - [save(opts, cb)](doc/api.md#saveopts-cb) - [setup(opts)](doc/api.md#setupopts) diff --git a/doc/api.md b/doc/api.md index e107293f..b1db7cd0 100644 --- a/doc/api.md +++ b/doc/api.md @@ -14,7 +14,7 @@ - [toggle_float(dir)](#toggle_floatdir) - [open(dir)](#opendir) - [close()](#close) -- [open_preview(opts)](#open_previewopts) +- [open_preview(opts, callback)](#open_previewopts-callback) - [select(opts, callback)](#selectopts-callback) - [save(opts, cb)](#saveopts-cb) - [setup(opts)](#setupopts) @@ -125,17 +125,18 @@ Open oil browser for a directory Restore the buffer that was present when oil was opened -## open_preview(opts) +## open_preview(opts, callback) -`open_preview(opts)` \ +`open_preview(opts, callback)` \ Preview the entry under the cursor in a split -| Param | Type | Desc | | -| ----- | ------------ | -------------------------------------------------- | ------------------------------------- | -| opts | `nil\|table` | | | -| | vertical | `boolean` | Open the buffer in a vertical split | -| | horizontal | `boolean` | Open the buffer in a horizontal split | -| | split | `"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier | +| Param | Type | Desc | | +| -------- | ---------------------------- | ------------------------------------------------------- | ------------------------------------- | +| opts | `nil\|oil.OpenPreviewOpts` | | | +| | vertical | `nil\|boolean` | Open the buffer in a vertical split | +| | horizontal | `nil\|boolean` | Open the buffer in a horizontal split | +| | split | `nil\|"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier | +| callback | `nil\|fun(err: nil\|string)` | Called once the preview window has been opened | | ## select(opts, callback) diff --git a/doc/oil.txt b/doc/oil.txt index 03b7bc3a..b4ebf1fd 100644 --- a/doc/oil.txt +++ b/doc/oil.txt @@ -315,15 +315,17 @@ close() *oil.clos Restore the buffer that was present when oil was opened -open_preview({opts}) *oil.open_preview* +open_preview({opts}, {callback}) *oil.open_preview* Preview the entry under the cursor in a split Parameters: - {opts} `nil|table` - {vertical} `boolean` Open the buffer in a vertical split - {horizontal} `boolean` Open the buffer in a horizontal split - {split} `"aboveleft"|"belowright"|"topleft"|"botright"` Split + {opts} `nil|oil.OpenPreviewOpts` + {vertical} `nil|boolean` Open the buffer in a vertical split + {horizontal} `nil|boolean` Open the buffer in a horizontal split + {split} `nil|"aboveleft"|"belowright"|"topleft"|"botright"` Split modifier + {callback} `nil|fun(err: nil|string)` Called once the preview window has + been opened select({opts}, {callback}) *oil.select* Select the entry under the cursor diff --git a/lua/oil/init.lua b/lua/oil/init.lua index 8c04d8bc..600ccc5e 100644 --- a/lua/oil/init.lua +++ b/lua/oil/init.lua @@ -410,11 +410,14 @@ M.close = function() vim.api.nvim_buf_delete(oilbuf, { force = true }) end +---@class oil.OpenPreviewOpts +---@field vertical? boolean Open the buffer in a vertical split +---@field horizontal? boolean Open the buffer in a horizontal split +---@field split? "aboveleft"|"belowright"|"topleft"|"botright" Split modifier + ---Preview the entry under the cursor in a split ----@param opts nil|table ---- vertical boolean Open the buffer in a vertical split ---- horizontal boolean Open the buffer in a horizontal split ---- split "aboveleft"|"belowright"|"topleft"|"botright" Split modifier +---@param opts? oil.OpenPreviewOpts +---@param callback? fun(err: nil|string) Called once the preview window has been opened M.open_preview = function(opts, callback) opts = opts or {} local config = require("oil.config") From 3499e26ef4784a3e09902518a65c21f55fce018a Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Sun, 10 Nov 2024 16:38:45 -0800 Subject: [PATCH 10/34] chore: rework Makefile to not depend on direnv --- .envrc | 1 + .gitignore | 1 + Makefile | 36 ++++++++++++++++++++++++++++-------- scripts/requirements.txt | 4 ++++ 4 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 scripts/requirements.txt diff --git a/.envrc b/.envrc index 175de894..32465e7a 100644 --- a/.envrc +++ b/.envrc @@ -1 +1,2 @@ +export VIRTUAL_ENV=venv layout python diff --git a/.gitignore b/.gitignore index 290f6569..c90db5d4 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ __pycache__ .direnv/ .testenv/ +venv/ doc/tags scripts/nvim_doc_tools scripts/nvim-typecheck-action diff --git a/Makefile b/Makefile index 71447f8f..47993682 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,37 @@ -.PHONY: all doc test lint fastlint clean - +## help: print this help message +.PHONY: help +help: + @echo 'Usage:' + @sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /' + +## all: generate docs, lint, and run tests +.PHONY: all all: doc lint test -doc: scripts/nvim_doc_tools - python scripts/main.py generate - python scripts/main.py lint +venv: + python3 -m venv venv + venv/bin/pip install -r scripts/requirements.txt + +## doc: generate documentation +.PHONY: doc +doc: scripts/nvim_doc_tools venv + venv/bin/python scripts/main.py generate + venv/bin/python scripts/main.py lint +## test: run tests +.PHONY: test test: ./run_tests.sh +## lint: run linters and LuaLS typechecking +.PHONY: lint lint: scripts/nvim-typecheck-action fastlint ./scripts/nvim-typecheck-action/typecheck.sh --workdir scripts/nvim-typecheck-action lua -fastlint: scripts/nvim_doc_tools - python scripts/main.py lint +## fastlint: run only fast linters +.PHONY: fastlint +fastlint: scripts/nvim_doc_tools venv + venv/bin/python scripts/main.py lint luacheck lua tests --formatter plain stylua --check lua tests @@ -23,5 +41,7 @@ scripts/nvim_doc_tools: scripts/nvim-typecheck-action: git clone https://github.com/stevearc/nvim-typecheck-action scripts/nvim-typecheck-action +## clean: reset the repository to a clean state +.PHONY: clean clean: - rm -rf scripts/nvim_doc_tools scripts/nvim-typecheck-action + rm -rf scripts/nvim_doc_tools scripts/nvim-typecheck-action venv .testenv diff --git a/scripts/requirements.txt b/scripts/requirements.txt new file mode 100644 index 00000000..2c6271fd --- /dev/null +++ b/scripts/requirements.txt @@ -0,0 +1,4 @@ +pyparsing==3.0.9 +black +isort +mypy From 6e754e66997a1739a7cb0476195c8522fd72fe3a Mon Sep 17 00:00:00 2001 From: Github Actions Date: Mon, 11 Nov 2024 00:40:33 +0000 Subject: [PATCH 11/34] [docgen] Update docs skip-checks: true --- doc/api.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/doc/api.md b/doc/api.md index b1db7cd0..35c63920 100644 --- a/doc/api.md +++ b/doc/api.md @@ -130,39 +130,39 @@ Restore the buffer that was present when oil was opened `open_preview(opts, callback)` \ Preview the entry under the cursor in a split -| Param | Type | Desc | | -| -------- | ---------------------------- | ------------------------------------------------------- | ------------------------------------- | -| opts | `nil\|oil.OpenPreviewOpts` | | | -| | vertical | `nil\|boolean` | Open the buffer in a vertical split | -| | horizontal | `nil\|boolean` | Open the buffer in a horizontal split | -| | split | `nil\|"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier | -| callback | `nil\|fun(err: nil\|string)` | Called once the preview window has been opened | | +| Param | Type | Desc | +| ----------- | ------------------------------------------------------- | ---------------------------------------------- | +| opts | `nil\|oil.OpenPreviewOpts` | | +| >vertical | `nil\|boolean` | Open the buffer in a vertical split | +| >horizontal | `nil\|boolean` | Open the buffer in a horizontal split | +| >split | `nil\|"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier | +| callback | `nil\|fun(err: nil\|string)` | Called once the preview window has been opened | ## select(opts, callback) `select(opts, callback)` \ Select the entry under the cursor -| Param | Type | Desc | | -| -------- | ---------------------------- | ------------------------------------------------------- | ---------------------------------------------------- | -| opts | `nil\|oil.SelectOpts` | | | -| | vertical | `nil\|boolean` | Open the buffer in a vertical split | -| | horizontal | `nil\|boolean` | Open the buffer in a horizontal split | -| | split | `nil\|"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier | -| | tab | `nil\|boolean` | Open the buffer in a new tab | -| | close | `nil\|boolean` | Close the original oil buffer once selection is made | -| callback | `nil\|fun(err: nil\|string)` | Called once all entries have been opened | | +| Param | Type | Desc | +| ----------- | ------------------------------------------------------- | ---------------------------------------------------- | +| opts | `nil\|oil.SelectOpts` | | +| >vertical | `nil\|boolean` | Open the buffer in a vertical split | +| >horizontal | `nil\|boolean` | Open the buffer in a horizontal split | +| >split | `nil\|"aboveleft"\|"belowright"\|"topleft"\|"botright"` | Split modifier | +| >tab | `nil\|boolean` | Open the buffer in a new tab | +| >close | `nil\|boolean` | Close the original oil buffer once selection is made | +| callback | `nil\|fun(err: nil\|string)` | Called once all entries have been opened | ## save(opts, cb) `save(opts, cb)` \ Save all changes -| Param | Type | Desc | | -| ----- | ---------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------- | -| opts | `nil\|table` | | | -| | confirm | `nil\|boolean` | Show confirmation when true, never when false, respect skip_confirm_for_simple_edits if nil | -| cb | `nil\|fun(err: nil\|string)` | Called when mutations complete. | | +| Param | Type | Desc | +| -------- | ---------------------------- | ------------------------------------------------------------------------------------------- | +| opts | `nil\|table` | | +| >confirm | `nil\|boolean` | Show confirmation when true, never when false, respect skip_confirm_for_simple_edits if nil | +| cb | `nil\|fun(err: nil\|string)` | Called when mutations complete. | **Note:**

From 50c4bd4ee216f08907f64d0295c0663a69e58ffb Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 10 Nov 2024 19:18:46 -0800
Subject: [PATCH 12/34] chore(master): release 2.13.0 (#478)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
 CHANGELOG.md | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 39811f85..3e7284a4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,23 @@
 # Changelog
 
+## [2.13.0](https://github.com/stevearc/oil.nvim/compare/v2.12.2...v2.13.0) (2024-11-11)
+
+
+### Features
+
+* config option to customize floating window title ([#482](https://github.com/stevearc/oil.nvim/issues/482)) ([5d2dfae](https://github.com/stevearc/oil.nvim/commit/5d2dfae655b9b689bd4017b3bdccd52cbee5b92f))
+* config option to disable lsp file methods ([#477](https://github.com/stevearc/oil.nvim/issues/477)) ([f60bb7f](https://github.com/stevearc/oil.nvim/commit/f60bb7f793477d99ef1acf39e920bf2ca4e644de))
+
+
+### Bug Fixes
+
+* actions.preview accepts options ([#497](https://github.com/stevearc/oil.nvim/issues/497)) ([cca1631](https://github.com/stevearc/oil.nvim/commit/cca1631d5ea450c09ba72f3951a9e28105a3632c))
+* add trailing slash to directories on yank_entry ([#504](https://github.com/stevearc/oil.nvim/issues/504)) ([42333bb](https://github.com/stevearc/oil.nvim/commit/42333bb46e34dd47e13927010b1dcd30e6e4ca96))
+* don't deep merge keymaps ([#510](https://github.com/stevearc/oil.nvim/issues/510)) ([709403c](https://github.com/stevearc/oil.nvim/commit/709403ccd6f22d859c2e42c780ab558ae89284d9))
+* guard against nil keymaps ([621f8ba](https://github.com/stevearc/oil.nvim/commit/621f8ba4fa821724e9b646732a26fb2e795fe008))
+* only map ~ for normal mode ([#484](https://github.com/stevearc/oil.nvim/issues/484)) ([ccab9d5](https://github.com/stevearc/oil.nvim/commit/ccab9d5e09e2d0042fbbe5b6bd05e82426247067))
+* sort keymap help entries by description ([#506](https://github.com/stevearc/oil.nvim/issues/506)) ([52cc8a1](https://github.com/stevearc/oil.nvim/commit/52cc8a1fb35ea6ce1df536143add7ce7215c63c0)), closes [#376](https://github.com/stevearc/oil.nvim/issues/376)
+
 ## [2.12.2](https://github.com/stevearc/oil.nvim/compare/v2.12.1...v2.12.2) (2024-09-10)
 
 

From c23fe08e0546d9efc242e19f0d829efa7e7b2743 Mon Sep 17 00:00:00 2001
From: Steve Walker <65963536+stevalkr@users.noreply.github.com>
Date: Wed, 13 Nov 2024 00:24:39 +0800
Subject: [PATCH 13/34] feat: disable preview for large files (#511)

* feat: disable preview for large files

fix: update oil.PreviewWindowConfig

* refactor: remove unnecessary shim in config.lua

* refactor: revert changes to shim

---------

Co-authored-by: Steve Walker <65963536+etherswangel@users.noreply.github.com>
Co-authored-by: Steven Arcangeli <506791+stevearc@users.noreply.github.com>
---
 README.md          | 2 ++
 doc/oil.txt        | 2 ++
 lua/oil/config.lua | 4 ++++
 lua/oil/init.lua   | 7 +++++++
 4 files changed, 15 insertions(+)

diff --git a/README.md b/README.md
index 6bf02823..79b96362 100644
--- a/README.md
+++ b/README.md
@@ -271,6 +271,8 @@ require("oil").setup({
   preview_win = {
     -- Whether the preview window is automatically updated when the cursor is moved
     update_on_cursor_moved = true,
+    -- Maximum file size in megabytes to preview
+    max_file_size_mb = 100,
   },
   -- Configuration for the floating action confirmation window
   confirmation = {
diff --git a/doc/oil.txt b/doc/oil.txt
index b4ebf1fd..32830365 100644
--- a/doc/oil.txt
+++ b/doc/oil.txt
@@ -156,6 +156,8 @@ CONFIG                                                                *oil-confi
       preview_win = {
         -- Whether the preview window is automatically updated when the cursor is moved
         update_on_cursor_moved = true,
+        -- Maximum file size in megabytes to preview
+        max_file_size_mb = 100,
       },
       -- Configuration for the floating action confirmation window
       confirmation = {
diff --git a/lua/oil/config.lua b/lua/oil/config.lua
index 31e25f2a..17874b7c 100644
--- a/lua/oil/config.lua
+++ b/lua/oil/config.lua
@@ -141,6 +141,8 @@ local default_config = {
   preview_win = {
     -- Whether the preview window is automatically updated when the cursor is moved
     update_on_cursor_moved = true,
+    -- Maximum file size in megabytes to preview
+    max_file_size_mb = 100,
   },
   -- Configuration for the floating action confirmation window
   confirmation = {
@@ -323,11 +325,13 @@ local M = {}
 
 ---@class (exact) oil.PreviewWindowConfig
 ---@field update_on_cursor_moved boolean
+---@field max_file_size_mb number
 
 ---@class (exact) oil.ConfirmationWindowConfig : oil.WindowConfig
 
 ---@class (exact) oil.SetupPreviewWindowConfig
 ---@field update_on_cursor_moved? boolean Whether the preview window is automatically updated when the cursor is moved
+---@field max_file_size_mb? number Maximum file size in megabytes to preview
 
 ---@class (exact) oil.SetupConfirmationWindowConfig : oil.SetupWindowConfig
 
diff --git a/lua/oil/init.lua b/lua/oil/init.lua
index 600ccc5e..69f97802 100644
--- a/lua/oil/init.lua
+++ b/lua/oil/init.lua
@@ -452,6 +452,13 @@ M.open_preview = function(opts, callback)
   if not entry then
     return finish("Could not find entry under cursor")
   end
+  if entry.meta ~= nil and entry.meta.stat ~= nil then
+    if entry.meta.stat.size >= config.preview_win.max_file_size_mb * 1e6 then
+      return finish(
+        "File over " .. config.preview_win.max_file_size_mb .. "MB is too large to preview"
+      )
+    end
+  end
   local entry_title = entry.name
   if entry.type == "directory" then
     entry_title = entry_title .. "/"

From bbeed86bde134da8d09bed64b6aa0d65642e6b23 Mon Sep 17 00:00:00 2001
From: Micah Halter 
Date: Tue, 12 Nov 2024 13:38:35 -0500
Subject: [PATCH 14/34] feat: add `win_options` to `preview_win` (#514)

---
 lua/oil/config.lua | 4 ++++
 lua/oil/init.lua   | 3 +++
 lua/oil/view.lua   | 5 +++++
 3 files changed, 12 insertions(+)

diff --git a/lua/oil/config.lua b/lua/oil/config.lua
index 17874b7c..f203e873 100644
--- a/lua/oil/config.lua
+++ b/lua/oil/config.lua
@@ -143,6 +143,8 @@ local default_config = {
     update_on_cursor_moved = true,
     -- Maximum file size in megabytes to preview
     max_file_size_mb = 100,
+    -- Window-local options to use for preview window buffers
+    win_options = {},
   },
   -- Configuration for the floating action confirmation window
   confirmation = {
@@ -326,12 +328,14 @@ local M = {}
 ---@class (exact) oil.PreviewWindowConfig
 ---@field update_on_cursor_moved boolean
 ---@field max_file_size_mb number
+---@field win_options table
 
 ---@class (exact) oil.ConfirmationWindowConfig : oil.WindowConfig
 
 ---@class (exact) oil.SetupPreviewWindowConfig
 ---@field update_on_cursor_moved? boolean Whether the preview window is automatically updated when the cursor is moved
 ---@field max_file_size_mb? number Maximum file size in megabytes to preview
+---@field win_options? table Window-local options to use for preview window buffers
 
 ---@class (exact) oil.SetupConfirmationWindowConfig : oil.SetupWindowConfig
 
diff --git a/lua/oil/init.lua b/lua/oil/init.lua
index 69f97802..2399d39f 100644
--- a/lua/oil/init.lua
+++ b/lua/oil/init.lua
@@ -551,6 +551,9 @@ M.open_preview = function(opts, callback)
     end
 
     vim.api.nvim_set_option_value("previewwindow", true, { scope = "local", win = 0 })
+    for k, v in pairs(config.preview_win.win_options) do
+      vim.api.nvim_set_option_value(k, v, { scope = "local", win = preview_win })
+    end
     vim.w.oil_entry_id = entry.id
     vim.w.oil_source_win = prev_win
     if is_visual_mode then
diff --git a/lua/oil/view.lua b/lua/oil/view.lua
index da404a30..ddc5db3c 100644
--- a/lua/oil/view.lua
+++ b/lua/oil/view.lua
@@ -182,6 +182,11 @@ M.set_win_options = function()
   for k, v in pairs(config.win_options) do
     vim.api.nvim_set_option_value(k, v, { scope = "local", win = winid })
   end
+  if vim.wo[winid].previewwindow then -- apply preview window options last
+    for k, v in pairs(config.preview_win.win_options) do
+      vim.api.nvim_set_option_value(k, v, { scope = "local", win = winid })
+    end
+  end
 end
 
 ---Get a list of visible oil buffers and a list of hidden oil buffers

From 8735d185b37457bd899cd4e47a4517b899407949 Mon Sep 17 00:00:00 2001
From: Github Actions 
Date: Tue, 12 Nov 2024 18:38:53 +0000
Subject: [PATCH 15/34] [docgen] Update docs skip-checks: true

---
 README.md   | 2 ++
 doc/oil.txt | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/README.md b/README.md
index 79b96362..b6b970ee 100644
--- a/README.md
+++ b/README.md
@@ -273,6 +273,8 @@ require("oil").setup({
     update_on_cursor_moved = true,
     -- Maximum file size in megabytes to preview
     max_file_size_mb = 100,
+    -- Window-local options to use for preview window buffers
+    win_options = {},
   },
   -- Configuration for the floating action confirmation window
   confirmation = {
diff --git a/doc/oil.txt b/doc/oil.txt
index 32830365..44bdcfac 100644
--- a/doc/oil.txt
+++ b/doc/oil.txt
@@ -158,6 +158,8 @@ CONFIG                                                                *oil-confi
         update_on_cursor_moved = true,
         -- Maximum file size in megabytes to preview
         max_file_size_mb = 100,
+        -- Window-local options to use for preview window buffers
+        win_options = {},
       },
       -- Configuration for the floating action confirmation window
       confirmation = {

From 0472d9296ace2d57769eb3e022a918803f096ea4 Mon Sep 17 00:00:00 2001
From: Steven Arcangeli 
Date: Wed, 13 Nov 2024 08:58:16 -0800
Subject: [PATCH 16/34] lint: fix typechecking for new LuaLS version

---
 lua/oil/adapters/files/permissions.lua | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lua/oil/adapters/files/permissions.lua b/lua/oil/adapters/files/permissions.lua
index cf50b551..6c306a64 100644
--- a/lua/oil/adapters/files/permissions.lua
+++ b/lua/oil/adapters/files/permissions.lua
@@ -1,6 +1,6 @@
 local M = {}
 
----@param exe_modifier nil|false|string
+---@param exe_modifier false|string
 ---@param num integer
 ---@return string
 local function perm_to_str(exe_modifier, num)

From 7d4e62942f647796d24b4ae22b84a75c41750fb7 Mon Sep 17 00:00:00 2001
From: Steven Arcangeli 
Date: Thu, 14 Nov 2024 19:18:18 -0800
Subject: [PATCH 17/34] test: add harness for measuring performance

---
 .envrc                 |   1 +
 .gitignore             |   2 +
 Makefile               |  19 ++++++-
 tests/perf_harness.lua | 122 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 143 insertions(+), 1 deletion(-)
 create mode 100644 tests/perf_harness.lua

diff --git a/.envrc b/.envrc
index 32465e7a..d522e346 100644
--- a/.envrc
+++ b/.envrc
@@ -1,2 +1,3 @@
 export VIRTUAL_ENV=venv
 layout python
+python -c 'import pyparsing' 2>/dev/null || pip install -r scripts/requirements.txt
diff --git a/.gitignore b/.gitignore
index c90db5d4..bb036c18 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,3 +48,5 @@ venv/
 doc/tags
 scripts/nvim_doc_tools
 scripts/nvim-typecheck-action
+tests/perf/
+profile.json
diff --git a/Makefile b/Makefile
index 47993682..bd7bf6cd 100644
--- a/Makefile
+++ b/Makefile
@@ -35,6 +35,23 @@ fastlint: scripts/nvim_doc_tools venv
 	luacheck lua tests --formatter plain
 	stylua --check lua tests
 
+## profile: use LuaJIT profiler to profile the plugin
+.PHONY: profile
+profile:
+	nvim --clean -u tests/perf_harness.lua -c 'lua jit_profile()'
+
+## flame_profile: create a trace in the chrome profiler format
+.PHONY: flame_profile
+flame_profile:
+	nvim --clean -u tests/perf_harness.lua -c 'lua flame_profile()'
+	@echo "Visit https://ui.perfetto.dev/ and load the profile.json file"
+
+## benchmark: benchmark performance opening directory with many files
+.PHONY: benchmark
+benchmark:
+	nvim --clean -u tests/perf_harness.lua -c 'lua benchmark(10)'
+	@cat tests/perf/benchmark.txt
+
 scripts/nvim_doc_tools:
 	git clone https://github.com/stevearc/nvim_doc_tools scripts/nvim_doc_tools
 
@@ -44,4 +61,4 @@ scripts/nvim-typecheck-action:
 ## clean: reset the repository to a clean state
 .PHONY: clean
 clean:
-	rm -rf scripts/nvim_doc_tools scripts/nvim-typecheck-action venv .testenv
+	rm -rf scripts/nvim_doc_tools scripts/nvim-typecheck-action venv .testenv tests/perf profile.json
diff --git a/tests/perf_harness.lua b/tests/perf_harness.lua
new file mode 100644
index 00000000..679004d2
--- /dev/null
+++ b/tests/perf_harness.lua
@@ -0,0 +1,122 @@
+vim.fn.mkdir("tests/perf/.env", "p")
+local root = vim.fn.fnamemodify("./tests/perf/.env", ":p")
+
+for _, name in ipairs({ "config", "data", "state", "runtime", "cache" }) do
+  vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. name
+end
+
+vim.opt.runtimepath:prepend(vim.fn.fnamemodify(".", ":p"))
+
+---@module 'oil'
+---@type oil.SetupOpts
+local setup_opts = {
+  -- columns = { "icon", "permissions", "size", "mtime" },
+}
+
+local num_files = 100000
+
+if not vim.uv.fs_stat(string.format("tests/perf/file %d.txt", num_files)) then
+  vim.notify("Creating files")
+  for i = 1, num_files, 1 do
+    local filename = ("tests/perf/file %d.txt"):format(i)
+    local fd = vim.uv.fs_open(filename, "a", 420)
+    assert(fd)
+    vim.uv.fs_close(fd)
+  end
+end
+
+local function wait_for_done(callback)
+  vim.api.nvim_create_autocmd("User", {
+    pattern = "OilEnter",
+    once = true,
+    callback = callback,
+  })
+end
+
+function _G.jit_profile()
+  require("oil").setup(setup_opts)
+  local outfile = "tests/perf/profile.txt"
+  require("jit.p").start("3Fpli1s", outfile)
+  local start = vim.uv.hrtime()
+  require("oil").open("tests/perf")
+
+  wait_for_done(function()
+    local delta = vim.uv.hrtime() - start
+    require("jit.p").stop()
+    print("Elapsed:", delta / 1e6, "ms")
+    vim.cmd.edit({ args = { outfile } })
+  end)
+end
+
+function _G.benchmark(iterations)
+  require("oil").setup(setup_opts)
+  local num_outliers = math.floor(0.1 * iterations)
+  local times = {}
+
+  local run_profile
+  run_profile = function()
+    -- Clear out state
+    vim.cmd.enew()
+    for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
+      if vim.api.nvim_buf_is_valid(bufnr) and bufnr ~= vim.api.nvim_get_current_buf() then
+        vim.api.nvim_buf_delete(bufnr, { force = true })
+      end
+    end
+
+    local start = vim.uv.hrtime()
+    wait_for_done(function()
+      local delta = vim.uv.hrtime() - start
+      table.insert(times, delta / 1e6)
+      if #times < iterations then
+        vim.schedule(run_profile)
+      else
+        -- Remove the outliers
+        table.sort(times)
+        for _ = 1, num_outliers do
+          table.remove(times, 1)
+          table.remove(times)
+        end
+
+        local total = 0
+        for _, time in ipairs(times) do
+          total = total + time
+        end
+
+        local lines = {
+          table.concat(
+            vim.tbl_map(function(t)
+              return string.format("%dms", math.floor(t))
+            end, times),
+            " "
+          ),
+          string.format("Average: %dms", math.floor(total / #times)),
+        }
+        vim.fn.writefile(lines, "tests/perf/benchmark.txt")
+        vim.cmd.qall()
+      end
+    end)
+    require("oil").open("tests/perf")
+  end
+
+  run_profile()
+end
+
+function _G.flame_profile()
+  if not vim.uv.fs_stat("tests/perf/profile.nvim") then
+    vim
+      .system({ "git", "clone", "https://github.com/stevearc/profile.nvim", "tests/perf/profile.nvim" })
+      :wait()
+  end
+  vim.opt.runtimepath:prepend(vim.fn.fnamemodify("./tests/perf/profile.nvim", ":p"))
+  local profile = require("profile")
+  profile.instrument_autocmds()
+  profile.instrument("oil*")
+
+  require("oil").setup(setup_opts)
+  profile.start()
+  require("oil").open("tests/perf")
+  wait_for_done(function()
+    profile.stop("profile.json")
+    vim.cmd.qall()
+  end)
+end

From 01b0b9d8ef79b7b631e92f6b5fed1c639262d570 Mon Sep 17 00:00:00 2001
From: Steven Arcangeli 
Date: Thu, 14 Nov 2024 19:18:19 -0800
Subject: [PATCH 18/34] perf: change default view_options.natural_order
 behavior to disable on large directories

---
 README.md           |  6 +++---
 doc/oil.txt         |  6 +++---
 lua/oil/columns.lua | 36 +++++++++++++++++++++++++-----------
 lua/oil/config.lua  | 10 +++++-----
 lua/oil/view.lua    |  9 ++++++---
 5 files changed, 42 insertions(+), 25 deletions(-)

diff --git a/README.md b/README.md
index b6b970ee..8c0bdb3f 100644
--- a/README.md
+++ b/README.md
@@ -220,9 +220,9 @@ require("oil").setup({
     is_always_hidden = function(name, bufnr)
       return false
     end,
-    -- Sort file names in a more intuitive order for humans. Is less performant,
-    -- so you may want to set to false if you work with large directories.
-    natural_order = true,
+    -- Sort file names with numbers in a more intuitive order for humans.
+    -- Can be "fast", true, or false. "fast" will turn it off for large directories.
+    natural_order = "fast",
     -- Sort file and directory names case insensitive
     case_insensitive = false,
     sort = {
diff --git a/doc/oil.txt b/doc/oil.txt
index 44bdcfac..b6552065 100644
--- a/doc/oil.txt
+++ b/doc/oil.txt
@@ -105,9 +105,9 @@ CONFIG                                                                *oil-confi
         is_always_hidden = function(name, bufnr)
           return false
         end,
-        -- Sort file names in a more intuitive order for humans. Is less performant,
-        -- so you may want to set to false if you work with large directories.
-        natural_order = true,
+        -- Sort file names with numbers in a more intuitive order for humans.
+        -- Can be "fast", true, or false. "fast" will turn it off for large directories.
+        natural_order = "fast",
         -- Sort file and directory names case insensitive
         case_insensitive = false,
         sort = {
diff --git a/lua/oil/columns.lua b/lua/oil/columns.lua
index 40b7d745..d8827922 100644
--- a/lua/oil/columns.lua
+++ b/lua/oil/columns.lua
@@ -19,6 +19,7 @@ local all_columns = {}
 ---@field render_action? fun(action: oil.ChangeAction): string
 ---@field perform_action? fun(action: oil.ChangeAction, callback: fun(err: nil|string))
 ---@field get_sort_value? fun(entry: oil.InternalEntry): number|string
+---@field create_sort_value_factory? fun(num_entries: integer): fun(entry: oil.InternalEntry): number|string
 
 ---@param name string
 ---@param column oil.ColumnDefinition
@@ -292,18 +293,31 @@ M.register("name", {
     error("Do not use the name column. It is for sorting only")
   end,
 
-  get_sort_value = function(entry)
-    local sort_value = entry[FIELD_NAME]
-
-    if config.view_options.natural_order then
-      sort_value = sort_value:gsub("%d+", pad_number)
-    end
-
-    if config.view_options.case_insensitive then
-      sort_value = sort_value:lower()
+  create_sort_value_factory = function(num_entries)
+    if
+      config.view_options.natural_order == false
+      or (config.view_options.natural_order == "fast" and num_entries > 5000)
+    then
+      if config.view_options.case_insensitive then
+        return function(entry)
+          return entry[FIELD_NAME]:lower()
+        end
+      else
+        return function(entry)
+          return entry[FIELD_NAME]
+        end
+      end
+    else
+      if config.view_options.case_insensitive then
+        return function(entry)
+          return entry[FIELD_NAME]:gsub("%d+", pad_number):lower()
+        end
+      else
+        return function(entry)
+          return entry[FIELD_NAME]:gsub("%d+", pad_number)
+        end
+      end
     end
-
-    return sort_value
   end,
 })
 
diff --git a/lua/oil/config.lua b/lua/oil/config.lua
index f203e873..5ced37da 100644
--- a/lua/oil/config.lua
+++ b/lua/oil/config.lua
@@ -90,9 +90,9 @@ local default_config = {
     is_always_hidden = function(name, bufnr)
       return false
     end,
-    -- Sort file names in a more intuitive order for humans. Is less performant,
-    -- so you may want to set to false if you work with large directories.
-    natural_order = true,
+    -- Sort file names with numbers in a more intuitive order for humans.
+    -- Can be "fast", true, or false. "fast" will turn it off for large directories.
+    natural_order = "fast",
     -- Sort file and directory names case insensitive
     case_insensitive = false,
     sort = {
@@ -273,7 +273,7 @@ local M = {}
 ---@field show_hidden boolean
 ---@field is_hidden_file fun(name: string, bufnr: integer): boolean
 ---@field is_always_hidden fun(name: string, bufnr: integer): boolean
----@field natural_order boolean
+---@field natural_order boolean|"fast"
 ---@field case_insensitive boolean
 ---@field sort oil.SortSpec[]
 
@@ -281,7 +281,7 @@ local M = {}
 ---@field show_hidden? boolean Show files and directories that start with "."
 ---@field is_hidden_file? fun(name: string, bufnr: integer): boolean This function defines what is considered a "hidden" file
 ---@field is_always_hidden? fun(name: string, bufnr: integer): boolean This function defines what will never be shown, even when `show_hidden` is set
----@field natural_order? boolean Sort file names in a more intuitive order for humans. Is less performant, so you may want to set to false if you work with large directories.
+---@field natural_order? boolean|"fast" Sort file names with numbers in a more intuitive order for humans. Can be slow for large directories.
 ---@field case_insensitive? boolean Sort file and directory names case insensitive
 ---@field sort? oil.SortSpec[] Sort order for the file list
 
diff --git a/lua/oil/view.lua b/lua/oil/view.lua
index ddc5db3c..8aa8ed71 100644
--- a/lua/oil/view.lua
+++ b/lua/oil/view.lua
@@ -537,8 +537,9 @@ M.initialize = function(bufnr)
 end
 
 ---@param adapter oil.Adapter
+---@param num_entries integer
 ---@return fun(a: oil.InternalEntry, b: oil.InternalEntry): boolean
-local function get_sort_function(adapter)
+local function get_sort_function(adapter, num_entries)
   local idx_funs = {}
   local sort_config = config.view_options.sort
 
@@ -560,7 +561,9 @@ local function get_sort_function(adapter)
       )
     end
     local col = columns.get_column(adapter, col_name)
-    if col and col.get_sort_value then
+    if col and col.create_sort_value_factory then
+      table.insert(idx_funs, { col.create_sort_value_factory(num_entries), order })
+    elseif col and col.get_sort_value then
       table.insert(idx_funs, { col.get_sort_value, order })
     else
       vim.notify_once(
@@ -611,7 +614,7 @@ local function render_buffer(bufnr, opts)
   local entries = cache.list_url(bufname)
   local entry_list = vim.tbl_values(entries)
 
-  table.sort(entry_list, get_sort_function(adapter))
+  table.sort(entry_list, get_sort_function(adapter, #entry_list))
 
   local jump_idx
   if opts.jump_first then

From 4de30256c32cd272482bc6df0c6de78ffc389153 Mon Sep 17 00:00:00 2001
From: Steven Arcangeli 
Date: Thu, 14 Nov 2024 19:18:20 -0800
Subject: [PATCH 19/34] perf: replace vim.endswith and vim.startswith with
 string.match

---
 README.md          | 3 ++-
 doc/oil.txt        | 3 ++-
 lua/oil/config.lua | 3 ++-
 lua/oil/util.lua   | 3 ++-
 4 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index 8c0bdb3f..a3f30134 100644
--- a/README.md
+++ b/README.md
@@ -214,7 +214,8 @@ require("oil").setup({
     show_hidden = false,
     -- This function defines what is considered a "hidden" file
     is_hidden_file = function(name, bufnr)
-      return vim.startswith(name, ".")
+      local m = name:match("^%.")
+      return m ~= nil
     end,
     -- This function defines what will never be shown, even when `show_hidden` is set
     is_always_hidden = function(name, bufnr)
diff --git a/doc/oil.txt b/doc/oil.txt
index b6552065..58dee6a9 100644
--- a/doc/oil.txt
+++ b/doc/oil.txt
@@ -99,7 +99,8 @@ CONFIG                                                                *oil-confi
         show_hidden = false,
         -- This function defines what is considered a "hidden" file
         is_hidden_file = function(name, bufnr)
-          return vim.startswith(name, ".")
+          local m = name:match("^%.")
+          return m ~= nil
         end,
         -- This function defines what will never be shown, even when `show_hidden` is set
         is_always_hidden = function(name, bufnr)
diff --git a/lua/oil/config.lua b/lua/oil/config.lua
index 5ced37da..d3eca474 100644
--- a/lua/oil/config.lua
+++ b/lua/oil/config.lua
@@ -84,7 +84,8 @@ local default_config = {
     show_hidden = false,
     -- This function defines what is considered a "hidden" file
     is_hidden_file = function(name, bufnr)
-      return vim.startswith(name, ".")
+      local m = name:match("^%.")
+      return m ~= nil
     end,
     -- This function defines what will never be shown, even when `show_hidden` is set
     is_always_hidden = function(name, bufnr)
diff --git a/lua/oil/util.lua b/lua/oil/util.lua
index f5c63e2a..947b38a2 100644
--- a/lua/oil/util.lua
+++ b/lua/oil/util.lua
@@ -347,7 +347,8 @@ M.addslash = function(path, os_slash)
     slash = "\\"
   end
 
-  if not vim.endswith(path, slash) then
+  local endslash = path:match(slash .. "$")
+  if not endslash then
     return path .. slash
   else
     return path

From 792f0db6ba8b626b14bc127e1ce7247185b3be91 Mon Sep 17 00:00:00 2001
From: Steven Arcangeli 
Date: Thu, 14 Nov 2024 19:18:21 -0800
Subject: [PATCH 20/34] perf: only sort entries after we have them all

---
 lua/oil/view.lua | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/lua/oil/view.lua b/lua/oil/view.lua
index 8aa8ed71..cd39a52c 100644
--- a/lua/oil/view.lua
+++ b/lua/oil/view.lua
@@ -614,7 +614,10 @@ local function render_buffer(bufnr, opts)
   local entries = cache.list_url(bufname)
   local entry_list = vim.tbl_values(entries)
 
-  table.sort(entry_list, get_sort_function(adapter, #entry_list))
+  -- Only sort the entries once we have them all
+  if not vim.b[bufnr].oil_rendering then
+    table.sort(entry_list, get_sort_function(adapter, #entry_list))
+  end
 
   local jump_idx
   if opts.jump_first then

From c96f93d894cc97e76b0871bec4058530eee8ece4 Mon Sep 17 00:00:00 2001
From: Steven Arcangeli 
Date: Thu, 14 Nov 2024 19:18:21 -0800
Subject: [PATCH 21/34] perf: optimize rendering cadence

---
 lua/oil/view.lua | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/lua/oil/view.lua b/lua/oil/view.lua
index cd39a52c..3200c492 100644
--- a/lua/oil/view.lua
+++ b/lua/oil/view.lua
@@ -849,6 +849,7 @@ M.render_buffer_async = function(bufnr, opts, callback)
   end
 
   cache.begin_update_url(bufname)
+  local num_iterations = 0
   adapter.list(bufname, get_used_columns(), function(err, entries, fetch_more)
     loading.set_loading(bufnr, false)
     if err then
@@ -865,11 +866,13 @@ M.render_buffer_async = function(bufnr, opts, callback)
       local now = uv.hrtime() / 1e6
       local delta = now - start_ms
       -- If we've been chugging for more than 40ms, go ahead and render what we have
-      if delta > 40 then
+      if (delta > 25 and num_iterations < 1) or delta > 500 then
+        num_iterations = num_iterations + 1
         start_ms = now
         vim.schedule(function()
           seek_after_render_found =
             render_buffer(bufnr, { jump = not seek_after_render_found, jump_first = first })
+          start_ms = uv.hrtime() / 1e6
         end)
       end
       first = false

From 651299a6ca799f09997956f30c67329c6033dcd3 Mon Sep 17 00:00:00 2001
From: Steven Arcangeli 
Date: Thu, 14 Nov 2024 19:47:50 -0800
Subject: [PATCH 22/34] doc: trashing on windows works now

---
 doc/oil.txt         | 2 +-
 scripts/generate.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/doc/oil.txt b/doc/oil.txt
index 58dee6a9..5bb0953e 100644
--- a/doc/oil.txt
+++ b/doc/oil.txt
@@ -667,7 +667,7 @@ Mac:
     (instead of being able to see files that were trashed from a directory).
 
 Windows:
-    Oil does not yet support the Windows trash. PRs are welcome!
+    Oil supports the Windows Recycle Bin. All features should work.
 
 ================================================================================
 vim:tw=80:ts=2:ft=help:norl:syntax=help:
diff --git a/scripts/generate.py b/scripts/generate.py
index 5cc5fe35..a20ad532 100755
--- a/scripts/generate.py
+++ b/scripts/generate.py
@@ -366,7 +366,7 @@ def get_trash_vimdoc() -> "VimdocSection":
     (instead of being able to see files that were trashed from a directory).
 
 Windows:
-    Oil does not yet support the Windows trash. PRs are welcome!
+    Oil supports the Windows Recycle Bin. All features should work.
 """
     )
     return section

From 8ea40b5506115b6d355e304dd9ee5089f7d78601 Mon Sep 17 00:00:00 2001
From: Steven Arcangeli 
Date: Thu, 14 Nov 2024 22:21:11 -0800
Subject: [PATCH 23/34] fix: cursor sometimes does not hover previous file

---
 lua/oil/view.lua | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lua/oil/view.lua b/lua/oil/view.lua
index 3200c492..b36140b0 100644
--- a/lua/oil/view.lua
+++ b/lua/oil/view.lua
@@ -646,7 +646,6 @@ local function render_buffer(bufnr, opts)
       if seek_after_render == name then
         seek_after_render_found = true
         jump_idx = #line_table
-        M.set_last_cursor(bufname, nil)
       end
     end
   end
@@ -825,6 +824,7 @@ M.render_buffer_async = function(bufnr, opts, callback)
     vim.b[bufnr].oil_rendering = false
     loading.set_loading(bufnr, false)
     render_buffer(bufnr, { jump = true })
+    M.set_last_cursor(bufname, nil)
     vim.bo[bufnr].undolevels = vim.api.nvim_get_option_value("undolevels", { scope = "global" })
     vim.bo[bufnr].modifiable = not buffers_locked and adapter.is_modifiable(bufnr)
     if callback then

From 21705a1debe6d85a53c138ab944484b685432b2b Mon Sep 17 00:00:00 2001
From: Jalal El Mansouri 
Date: Wed, 20 Nov 2024 02:24:24 +0100
Subject: [PATCH 24/34] feat: use scratch buffer for file previews (#467)

* Initial implementation of scratch based preview

* Fix call to buf is valid in loop

* Fixing call to be made only from the main event loop

* Improve handling of large files from @pkazmier

* Better error handling and simplifying the code

* Default to old behavior

* Add documentation

* Fix readfile

* Fix the configuration

* refactor: single config enum and load real buffer on BufEnter

* doc: regenerate documentation

---------

Co-authored-by: Steven Arcangeli 
---
 README.md          |  4 ++--
 doc/oil.txt        |  4 ++--
 lua/oil/config.lua | 13 +++++++----
 lua/oil/init.lua   | 24 ++++++++++----------
 lua/oil/util.lua   | 55 ++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 79 insertions(+), 21 deletions(-)

diff --git a/README.md b/README.md
index a3f30134..0f4f3fe0 100644
--- a/README.md
+++ b/README.md
@@ -272,8 +272,8 @@ require("oil").setup({
   preview_win = {
     -- Whether the preview window is automatically updated when the cursor is moved
     update_on_cursor_moved = true,
-    -- Maximum file size in megabytes to preview
-    max_file_size_mb = 100,
+    -- How to open the preview window "load"|"scratch"|"fast_scratch"
+    preview_method = "fast_scratch",
     -- Window-local options to use for preview window buffers
     win_options = {},
   },
diff --git a/doc/oil.txt b/doc/oil.txt
index 5bb0953e..48df3a5e 100644
--- a/doc/oil.txt
+++ b/doc/oil.txt
@@ -157,8 +157,8 @@ CONFIG                                                                *oil-confi
       preview_win = {
         -- Whether the preview window is automatically updated when the cursor is moved
         update_on_cursor_moved = true,
-        -- Maximum file size in megabytes to preview
-        max_file_size_mb = 100,
+        -- How to open the preview window "load"|"scratch"|"fast_scratch"
+        preview_method = "fast_scratch",
         -- Window-local options to use for preview window buffers
         win_options = {},
       },
diff --git a/lua/oil/config.lua b/lua/oil/config.lua
index d3eca474..3b6b57ea 100644
--- a/lua/oil/config.lua
+++ b/lua/oil/config.lua
@@ -142,8 +142,8 @@ local default_config = {
   preview_win = {
     -- Whether the preview window is automatically updated when the cursor is moved
     update_on_cursor_moved = true,
-    -- Maximum file size in megabytes to preview
-    max_file_size_mb = 100,
+    -- How to open the preview window "load"|"scratch"|"fast_scratch"
+    preview_method = "fast_scratch",
     -- Window-local options to use for preview window buffers
     win_options = {},
   },
@@ -326,16 +326,21 @@ local M = {}
 ---@field border? string|string[] Window border
 ---@field win_options? table
 
+---@alias oil.PreviewMethod
+---| '"load"' # Load the previewed file into a buffer
+---| '"scratch"' # Put the text into a scratch buffer to avoid LSP attaching
+---| '"fast_scratch"' # Put only the visible text into a scratch buffer
+
 ---@class (exact) oil.PreviewWindowConfig
 ---@field update_on_cursor_moved boolean
----@field max_file_size_mb number
+---@field preview_method oil.PreviewMethod
 ---@field win_options table
 
 ---@class (exact) oil.ConfirmationWindowConfig : oil.WindowConfig
 
 ---@class (exact) oil.SetupPreviewWindowConfig
 ---@field update_on_cursor_moved? boolean Whether the preview window is automatically updated when the cursor is moved
----@field max_file_size_mb? number Maximum file size in megabytes to preview
+---@field preview_method? oil.PreviewMethod How to open the preview window
 ---@field win_options? table Window-local options to use for preview window buffers
 
 ---@class (exact) oil.SetupConfirmationWindowConfig : oil.SetupWindowConfig
diff --git a/lua/oil/init.lua b/lua/oil/init.lua
index 2399d39f..d6734849 100644
--- a/lua/oil/init.lua
+++ b/lua/oil/init.lua
@@ -452,13 +452,6 @@ M.open_preview = function(opts, callback)
   if not entry then
     return finish("Could not find entry under cursor")
   end
-  if entry.meta ~= nil and entry.meta.stat ~= nil then
-    if entry.meta.stat.size >= config.preview_win.max_file_size_mb * 1e6 then
-      return finish(
-        "File over " .. config.preview_win.max_file_size_mb .. "MB is too large to preview"
-      )
-    end
-  end
   local entry_title = entry.name
   if entry.type == "directory" then
     entry_title = entry_title .. "/"
@@ -529,14 +522,19 @@ M.open_preview = function(opts, callback)
       end
     end
 
-    local filebufnr = vim.fn.bufadd(normalized_url)
     local entry_is_file = not vim.endswith(normalized_url, "/")
+    local filebufnr
+    if entry_is_file and config.preview_win.preview_method ~= "load" then
+      filebufnr =
+        util.read_file_to_scratch_buffer(normalized_url, config.preview_win.preview_method)
+    end
 
-    -- If we're previewing a file that hasn't been opened yet, make sure it gets deleted after
-    -- we close the window
-    if entry_is_file and vim.fn.bufloaded(filebufnr) == 0 then
-      vim.bo[filebufnr].bufhidden = "wipe"
-      vim.b[filebufnr].oil_preview_buffer = true
+    if not filebufnr then
+      filebufnr = vim.fn.bufadd(normalized_url)
+      if entry_is_file and vim.fn.bufloaded(filebufnr) == 0 then
+        vim.bo[filebufnr].bufhidden = "wipe"
+        vim.b[filebufnr].oil_preview_buffer = true
+      end
     end
 
     ---@diagnostic disable-next-line: param-type-mismatch
diff --git a/lua/oil/util.lua b/lua/oil/util.lua
index 947b38a2..24b714b0 100644
--- a/lua/oil/util.lua
+++ b/lua/oil/util.lua
@@ -897,4 +897,59 @@ M.get_icon_provider = function()
   end
 end
 
+---Read a buffer into a scratch buffer and apply syntactic highlighting when possible
+---@param path string The path to the file to read
+---@param preview_method oil.PreviewMethod
+---@return nil|integer
+M.read_file_to_scratch_buffer = function(path, preview_method)
+  local bufnr = vim.api.nvim_create_buf(false, true)
+  if bufnr == 0 then
+    return
+  end
+
+  vim.bo[bufnr].bufhidden = "wipe"
+  vim.bo[bufnr].buftype = "nofile"
+
+  local max_lines = preview_method == "fast_scratch" and vim.o.lines or nil
+  local has_lines, read_res = pcall(vim.fn.readfile, path, "", max_lines)
+  local lines = has_lines and vim.split(table.concat(read_res, "\n"), "\n") or {}
+
+  local ok = pcall(vim.api.nvim_buf_set_lines, bufnr, 0, -1, false, lines)
+  if not ok then
+    return
+  end
+  local ft = vim.filetype.match({ filename = path, buf = bufnr })
+  if ft and ft ~= "" then
+    local lang = vim.treesitter.language.get_lang(ft)
+    if not pcall(vim.treesitter.start, bufnr, lang) then
+      vim.bo[bufnr].syntax = ft
+    else
+    end
+  end
+
+  -- Replace the scratch buffer with a real buffer if we enter it
+  vim.api.nvim_create_autocmd("BufEnter", {
+    desc = "oil.nvim replace scratch buffer with real buffer",
+    buffer = bufnr,
+    callback = function()
+      local winid = vim.api.nvim_get_current_win()
+      -- Have to schedule this so all the FileType, etc autocmds will fire
+      vim.schedule(function()
+        if vim.api.nvim_get_current_win() == winid then
+          vim.cmd.edit({ args = { path } })
+
+          -- If we're still in a preview window, make sure this buffer still gets treated as a
+          -- preview
+          if vim.wo.previewwindow then
+            vim.bo.bufhidden = "wipe"
+            vim.b.oil_preview_buffer = true
+          end
+        end
+      end)
+    end,
+  })
+
+  return bufnr
+end
+
 return M

From 81cc9c3f62ddbef3687931d119e505643496fa0a Mon Sep 17 00:00:00 2001
From: cdmill <115658917+cdmill@users.noreply.github.com>
Date: Wed, 20 Nov 2024 22:06:09 -0700
Subject: [PATCH 25/34] feat: option to quite vim if oil is closed as last
 buffer (#491)

* feat: auto-quit vim if oil is closed as last buffer

* rename auto_close_vim to auto_close_last_buffer

* rework actions.close to be more like actions.cd

* fix: configure close action correctly

* add type annotation, future proofing

* fix: typo

* fix: typo

* refactor: better type annotations and backwards compatibility

---------

Co-authored-by: Steven Arcangeli <506791+stevearc@users.noreply.github.com>
---
 lua/oil/actions.lua | 11 ++++++++++-
 lua/oil/init.lua    | 16 +++++++++++++---
 2 files changed, 23 insertions(+), 4 deletions(-)

diff --git a/lua/oil/actions.lua b/lua/oil/actions.lua
index 50a266ab..8dabe40a 100644
--- a/lua/oil/actions.lua
+++ b/lua/oil/actions.lua
@@ -143,7 +143,16 @@ M.parent = {
 
 M.close = {
   desc = "Close oil and restore original buffer",
-  callback = oil.close,
+  callback = function(opts)
+    opts = opts or {}
+    oil.close(opts)
+  end,
+  parameters = {
+    exit_if_last_buf = {
+      type = "boolean",
+      desc = "Exit vim if oil is closed as the last buffer",
+    },
+  },
 }
 
 ---@param cmd string
diff --git a/lua/oil/init.lua b/lua/oil/init.lua
index d6734849..7645f351 100644
--- a/lua/oil/init.lua
+++ b/lua/oil/init.lua
@@ -379,8 +379,13 @@ M.open = function(dir)
   update_preview_window()
 end
 
+---@class oil.CloseOpts
+---@field exit_if_last_buf? boolean Exit vim if this oil buffer is the last open buffer
+
 ---Restore the buffer that was present when oil was opened
-M.close = function()
+---@param opts? oil.CloseOpts
+M.close = function(opts)
+  opts = opts or {}
   -- If we're in a floating oil window, close it and try to restore focus to the original window
   if vim.w.is_oil_win then
     local original_winid = vim.w.oil_original_win
@@ -403,9 +408,14 @@ M.close = function()
   -- buffer first
   local oilbuf = vim.api.nvim_get_current_buf()
   ok = pcall(vim.cmd.bprev)
+  -- If `bprev` failed, there are no buffers open
   if not ok then
-    -- If `bprev` failed, there are no buffers open so we should create a new one with enew
-    vim.cmd.enew()
+    -- either exit or create a new blank buffer
+    if opts.exit_if_last_buf then
+      vim.cmd.quit()
+    else
+      vim.cmd.enew()
+    end
   end
   vim.api.nvim_buf_delete(oilbuf, { force = true })
 end

From bf81e2a79a33d829226760781eaeeb553b8d0e4e Mon Sep 17 00:00:00 2001
From: Github Actions 
Date: Thu, 21 Nov 2024 05:06:28 +0000
Subject: [PATCH 26/34] [docgen] Update docs skip-checks: true

---
 README.md   |  2 +-
 doc/api.md  | 10 +++++++---
 doc/oil.txt |  9 ++++++++-
 3 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index 0f4f3fe0..e249c39b 100644
--- a/README.md
+++ b/README.md
@@ -364,7 +364,7 @@ Note that at the moment the ssh adapter does not support Windows machines, and i
 - [open_float(dir)](doc/api.md#open_floatdir)
 - [toggle_float(dir)](doc/api.md#toggle_floatdir)
 - [open(dir)](doc/api.md#opendir)
-- [close()](doc/api.md#close)
+- [close(opts)](doc/api.md#closeopts)
 - [open_preview(opts, callback)](doc/api.md#open_previewopts-callback)
 - [select(opts, callback)](doc/api.md#selectopts-callback)
 - [save(opts, cb)](doc/api.md#saveopts-cb)
diff --git a/doc/api.md b/doc/api.md
index 35c63920..2f51cddc 100644
--- a/doc/api.md
+++ b/doc/api.md
@@ -13,7 +13,7 @@
 - [open_float(dir)](#open_floatdir)
 - [toggle_float(dir)](#toggle_floatdir)
 - [open(dir)](#opendir)
-- [close()](#close)
+- [close(opts)](#closeopts)
 - [open_preview(opts, callback)](#open_previewopts-callback)
 - [select(opts, callback)](#selectopts-callback)
 - [save(opts, cb)](#saveopts-cb)
@@ -119,11 +119,15 @@ Open oil browser for a directory
 | ----- | ------------- | ------------------------------------------------------------------------------------------- |
 | dir   | `nil\|string` | When nil, open the parent of the current buffer, or the cwd if current buffer is not a file |
 
-## close()
+## close(opts)
 
-`close()` \
+`close(opts)` \
 Restore the buffer that was present when oil was opened
 
+| Param             | Type                 | Desc                                                |
+| ----------------- | -------------------- | --------------------------------------------------- |
+| opts              | `nil\|oil.CloseOpts` |                                                     |
+| >exit_if_last_buf | `nil\|boolean`       | Exit vim if this oil buffer is the last open buffer |
 
 ## open_preview(opts, callback)
 
diff --git a/doc/oil.txt b/doc/oil.txt
index 48df3a5e..1ee0d116 100644
--- a/doc/oil.txt
+++ b/doc/oil.txt
@@ -316,9 +316,13 @@ open({dir})                                                             *oil.ope
       {dir} `nil|string` When nil, open the parent of the current buffer, or the
             cwd if current buffer is not a file
 
-close()                                                                *oil.close*
+close({opts})                                                          *oil.close*
     Restore the buffer that was present when oil was opened
 
+    Parameters:
+      {opts} `nil|oil.CloseOpts`
+          {exit_if_last_buf} `nil|boolean` Exit vim if this oil buffer is the
+                             last open buffer
 
 open_preview({opts}, {callback})                                *oil.open_preview*
     Preview the entry under the cursor in a split
@@ -513,6 +517,9 @@ change_sort                                                  *actions.change_sor
 close                                                              *actions.close*
     Close oil and restore original buffer
 
+    Parameters:
+      {exit_if_last_buf} `boolean` Exit vim if oil is closed as the last buffer
+
 open_cmdline                                                *actions.open_cmdline*
     Open vim cmdline with current entry as an argument
 

From 5acab3d8a9bc85a571688db432f2702dd7d901a4 Mon Sep 17 00:00:00 2001
From: Steven Arcangeli 
Date: Thu, 21 Nov 2024 17:36:22 -0800
Subject: [PATCH 27/34] fix: image.nvim previews with preview_method=scratch

---
 lua/oil/init.lua |  6 +++++-
 lua/oil/util.lua | 22 ++++++++++++++++++++++
 2 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/lua/oil/init.lua b/lua/oil/init.lua
index 7645f351..bbd290dd 100644
--- a/lua/oil/init.lua
+++ b/lua/oil/init.lua
@@ -534,7 +534,11 @@ M.open_preview = function(opts, callback)
 
     local entry_is_file = not vim.endswith(normalized_url, "/")
     local filebufnr
-    if entry_is_file and config.preview_win.preview_method ~= "load" then
+    if
+      entry_is_file
+      and config.preview_win.preview_method ~= "load"
+      and not util.file_matches_bufreadcmd(normalized_url)
+    then
       filebufnr =
         util.read_file_to_scratch_buffer(normalized_url, config.preview_win.preview_method)
     end
diff --git a/lua/oil/util.lua b/lua/oil/util.lua
index 24b714b0..441421b9 100644
--- a/lua/oil/util.lua
+++ b/lua/oil/util.lua
@@ -952,4 +952,26 @@ M.read_file_to_scratch_buffer = function(path, preview_method)
   return bufnr
 end
 
+local _regcache = {}
+---Check if a file matches a BufReadCmd autocmd
+---@param filename string
+---@return boolean
+M.file_matches_bufreadcmd = function(filename)
+  local autocmds = vim.api.nvim_get_autocmds({
+    event = "BufReadCmd",
+  })
+  for _, au in ipairs(autocmds) do
+    local pat = _regcache[au.pattern]
+    if not pat then
+      pat = vim.fn.glob2regpat(au.pattern)
+      _regcache[au.pattern] = pat
+    end
+
+    if vim.fn.match(filename, pat) >= 0 then
+      return true
+    end
+  end
+  return false
+end
+
 return M

From 3fa3161aa9515ff6a7cf7e44458b6a2114262870 Mon Sep 17 00:00:00 2001
From: Steven Arcangeli 
Date: Thu, 21 Nov 2024 17:36:40 -0800
Subject: [PATCH 28/34] feat: config option to disable previewing a file

---
 README.md          | 4 ++++
 doc/oil.txt        | 4 ++++
 lua/oil/config.lua | 6 ++++++
 lua/oil/init.lua   | 5 +++++
 4 files changed, 19 insertions(+)

diff --git a/README.md b/README.md
index e249c39b..684ff248 100644
--- a/README.md
+++ b/README.md
@@ -274,6 +274,10 @@ require("oil").setup({
     update_on_cursor_moved = true,
     -- How to open the preview window "load"|"scratch"|"fast_scratch"
     preview_method = "fast_scratch",
+    -- A function that returns true to disable preview on a file e.g. to avoid lag
+    disable_preview = function(filename)
+      return false
+    end,
     -- Window-local options to use for preview window buffers
     win_options = {},
   },
diff --git a/doc/oil.txt b/doc/oil.txt
index 1ee0d116..0cfb872a 100644
--- a/doc/oil.txt
+++ b/doc/oil.txt
@@ -159,6 +159,10 @@ CONFIG                                                                *oil-confi
         update_on_cursor_moved = true,
         -- How to open the preview window "load"|"scratch"|"fast_scratch"
         preview_method = "fast_scratch",
+        -- A function that returns true to disable preview on a file e.g. to avoid lag
+        disable_preview = function(filename)
+          return false
+        end,
         -- Window-local options to use for preview window buffers
         win_options = {},
       },
diff --git a/lua/oil/config.lua b/lua/oil/config.lua
index 3b6b57ea..185cb159 100644
--- a/lua/oil/config.lua
+++ b/lua/oil/config.lua
@@ -144,6 +144,10 @@ local default_config = {
     update_on_cursor_moved = true,
     -- How to open the preview window "load"|"scratch"|"fast_scratch"
     preview_method = "fast_scratch",
+    -- A function that returns true to disable preview on a file e.g. to avoid lag
+    disable_preview = function(filename)
+      return false
+    end,
     -- Window-local options to use for preview window buffers
     win_options = {},
   },
@@ -334,12 +338,14 @@ local M = {}
 ---@class (exact) oil.PreviewWindowConfig
 ---@field update_on_cursor_moved boolean
 ---@field preview_method oil.PreviewMethod
+---@field disable_preview fun(filename: string): boolean
 ---@field win_options table
 
 ---@class (exact) oil.ConfirmationWindowConfig : oil.WindowConfig
 
 ---@class (exact) oil.SetupPreviewWindowConfig
 ---@field update_on_cursor_moved? boolean Whether the preview window is automatically updated when the cursor is moved
+---@field disable_preview? fun(filename: string): boolean A function that returns true to disable preview on a file e.g. to avoid lag
 ---@field preview_method? oil.PreviewMethod How to open the preview window
 ---@field win_options? table Window-local options to use for preview window buffers
 
diff --git a/lua/oil/init.lua b/lua/oil/init.lua
index bbd290dd..1450646f 100644
--- a/lua/oil/init.lua
+++ b/lua/oil/init.lua
@@ -541,6 +541,11 @@ M.open_preview = function(opts, callback)
     then
       filebufnr =
         util.read_file_to_scratch_buffer(normalized_url, config.preview_win.preview_method)
+    elseif entry_is_file and config.preview_win.disable_preview(normalized_url) then
+      filebufnr = vim.api.nvim_create_buf(false, true)
+      vim.bo[filebufnr].bufhidden = "wipe"
+      vim.bo[filebufnr].buftype = "nofile"
+      util.render_text(filebufnr, "Preview disabled", { winid = preview_win })
     end
 
     if not filebufnr then

From 5fa528f5528bf04a2d255108e59ed9cf53e85ae6 Mon Sep 17 00:00:00 2001
From: Steven Arcangeli 
Date: Thu, 21 Nov 2024 21:38:55 -0800
Subject: [PATCH 29/34] chore: refactor benchmarking to use benchmark.nvim

---
 .gitignore             |   3 +-
 Makefile               |  20 ++++---
 perf/bootstrap.lua     |  63 +++++++++++++++++++++
 tests/perf_harness.lua | 122 -----------------------------------------
 4 files changed, 76 insertions(+), 132 deletions(-)
 create mode 100644 perf/bootstrap.lua
 delete mode 100644 tests/perf_harness.lua

diff --git a/.gitignore b/.gitignore
index bb036c18..d427c406 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,5 +48,6 @@ venv/
 doc/tags
 scripts/nvim_doc_tools
 scripts/nvim-typecheck-action
-tests/perf/
+scripts/benchmark.nvim
+perf/tmp/
 profile.json
diff --git a/Makefile b/Makefile
index bd7bf6cd..10f01d11 100644
--- a/Makefile
+++ b/Makefile
@@ -37,20 +37,19 @@ fastlint: scripts/nvim_doc_tools venv
 
 ## profile: use LuaJIT profiler to profile the plugin
 .PHONY: profile
-profile:
-	nvim --clean -u tests/perf_harness.lua -c 'lua jit_profile()'
+profile: scripts/benchmark.nvim
+	nvim --clean -u perf/bootstrap.lua -c 'lua jit_profile()'
 
 ## flame_profile: create a trace in the chrome profiler format
 .PHONY: flame_profile
-flame_profile:
-	nvim --clean -u tests/perf_harness.lua -c 'lua flame_profile()'
-	@echo "Visit https://ui.perfetto.dev/ and load the profile.json file"
+flame_profile: scripts/benchmark.nvim
+	nvim --clean -u perf/bootstrap.lua -c 'lua flame_profile()'
 
 ## benchmark: benchmark performance opening directory with many files
 .PHONY: benchmark
-benchmark:
-	nvim --clean -u tests/perf_harness.lua -c 'lua benchmark(10)'
-	@cat tests/perf/benchmark.txt
+benchmark: scripts/benchmark.nvim
+	nvim --clean -u perf/bootstrap.lua -c 'lua benchmark()'
+	@cat perf/tmp/benchmark.txt
 
 scripts/nvim_doc_tools:
 	git clone https://github.com/stevearc/nvim_doc_tools scripts/nvim_doc_tools
@@ -58,7 +57,10 @@ scripts/nvim_doc_tools:
 scripts/nvim-typecheck-action:
 	git clone https://github.com/stevearc/nvim-typecheck-action scripts/nvim-typecheck-action
 
+scripts/benchmark.nvim:
+	git clone https://github.com/stevearc/benchmark.nvim scripts/benchmark.nvim
+
 ## clean: reset the repository to a clean state
 .PHONY: clean
 clean:
-	rm -rf scripts/nvim_doc_tools scripts/nvim-typecheck-action venv .testenv tests/perf profile.json
+	rm -rf scripts/nvim_doc_tools scripts/nvim-typecheck-action venv .testenv perf/tmp profile.json
diff --git a/perf/bootstrap.lua b/perf/bootstrap.lua
new file mode 100644
index 00000000..5f10c065
--- /dev/null
+++ b/perf/bootstrap.lua
@@ -0,0 +1,63 @@
+vim.opt.runtimepath:prepend("scripts/benchmark.nvim")
+vim.opt.runtimepath:prepend(".")
+
+local bm = require("benchmark")
+bm.sandbox()
+
+---@module 'oil'
+---@type oil.SetupOpts
+local setup_opts = {
+  -- columns = { "icon", "permissions", "size", "mtime" },
+}
+
+local DIR_SIZE = tonumber(vim.env.DIR_SIZE) or 100000
+local ITERATIONS = tonumber(vim.env.ITERATIONS) or 10
+local WARM_UP = tonumber(vim.env.WARM_UP) or 1
+local OUTLIERS = tonumber(vim.env.OUTLIERS) or math.floor(ITERATIONS / 10)
+local TEST_DIR = "perf/tmp/test_" .. DIR_SIZE
+
+vim.fn.mkdir(TEST_DIR, "p")
+require("benchmark.files").create_files(TEST_DIR, "file %d.txt", DIR_SIZE)
+
+function _G.jit_profile()
+  require("oil").setup(setup_opts)
+  local finish = bm.jit_profile({ filename = TEST_DIR .. "/profile.txt" })
+  bm.wait_for_user_event("OilEnter", function()
+    finish()
+  end)
+  require("oil").open(TEST_DIR)
+end
+
+function _G.flame_profile()
+  local start, stop = bm.flame_profile({
+    pattern = "oil*",
+    filename = "profile.json",
+  })
+  require("oil").setup(setup_opts)
+  start()
+  bm.wait_for_user_event("OilEnter", function()
+    stop(function()
+      vim.cmd.qall({ mods = { silent = true } })
+    end)
+  end)
+  require("oil").open(TEST_DIR)
+end
+
+function _G.benchmark()
+  require("oil").setup(setup_opts)
+  bm.run({ title = "oil.nvim", iterations = ITERATIONS, warm_up = WARM_UP }, function(callback)
+    bm.wait_for_user_event("OilEnter", callback)
+    require("oil").open(TEST_DIR)
+  end, function(times)
+    local avg = bm.avg(times, { trim_outliers = OUTLIERS })
+    local std_dev = bm.std_dev(times, { trim_outliers = OUTLIERS })
+    local lines = {
+      table.concat(vim.tbl_map(bm.format_time, times), " "),
+      string.format("Average: %s", bm.format_time(avg)),
+      string.format("Std deviation: %s", bm.format_time(std_dev)),
+    }
+
+    vim.fn.writefile(lines, "perf/tmp/benchmark.txt")
+    vim.cmd.qall({ mods = { silent = true } })
+  end)
+end
diff --git a/tests/perf_harness.lua b/tests/perf_harness.lua
deleted file mode 100644
index 679004d2..00000000
--- a/tests/perf_harness.lua
+++ /dev/null
@@ -1,122 +0,0 @@
-vim.fn.mkdir("tests/perf/.env", "p")
-local root = vim.fn.fnamemodify("./tests/perf/.env", ":p")
-
-for _, name in ipairs({ "config", "data", "state", "runtime", "cache" }) do
-  vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. name
-end
-
-vim.opt.runtimepath:prepend(vim.fn.fnamemodify(".", ":p"))
-
----@module 'oil'
----@type oil.SetupOpts
-local setup_opts = {
-  -- columns = { "icon", "permissions", "size", "mtime" },
-}
-
-local num_files = 100000
-
-if not vim.uv.fs_stat(string.format("tests/perf/file %d.txt", num_files)) then
-  vim.notify("Creating files")
-  for i = 1, num_files, 1 do
-    local filename = ("tests/perf/file %d.txt"):format(i)
-    local fd = vim.uv.fs_open(filename, "a", 420)
-    assert(fd)
-    vim.uv.fs_close(fd)
-  end
-end
-
-local function wait_for_done(callback)
-  vim.api.nvim_create_autocmd("User", {
-    pattern = "OilEnter",
-    once = true,
-    callback = callback,
-  })
-end
-
-function _G.jit_profile()
-  require("oil").setup(setup_opts)
-  local outfile = "tests/perf/profile.txt"
-  require("jit.p").start("3Fpli1s", outfile)
-  local start = vim.uv.hrtime()
-  require("oil").open("tests/perf")
-
-  wait_for_done(function()
-    local delta = vim.uv.hrtime() - start
-    require("jit.p").stop()
-    print("Elapsed:", delta / 1e6, "ms")
-    vim.cmd.edit({ args = { outfile } })
-  end)
-end
-
-function _G.benchmark(iterations)
-  require("oil").setup(setup_opts)
-  local num_outliers = math.floor(0.1 * iterations)
-  local times = {}
-
-  local run_profile
-  run_profile = function()
-    -- Clear out state
-    vim.cmd.enew()
-    for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
-      if vim.api.nvim_buf_is_valid(bufnr) and bufnr ~= vim.api.nvim_get_current_buf() then
-        vim.api.nvim_buf_delete(bufnr, { force = true })
-      end
-    end
-
-    local start = vim.uv.hrtime()
-    wait_for_done(function()
-      local delta = vim.uv.hrtime() - start
-      table.insert(times, delta / 1e6)
-      if #times < iterations then
-        vim.schedule(run_profile)
-      else
-        -- Remove the outliers
-        table.sort(times)
-        for _ = 1, num_outliers do
-          table.remove(times, 1)
-          table.remove(times)
-        end
-
-        local total = 0
-        for _, time in ipairs(times) do
-          total = total + time
-        end
-
-        local lines = {
-          table.concat(
-            vim.tbl_map(function(t)
-              return string.format("%dms", math.floor(t))
-            end, times),
-            " "
-          ),
-          string.format("Average: %dms", math.floor(total / #times)),
-        }
-        vim.fn.writefile(lines, "tests/perf/benchmark.txt")
-        vim.cmd.qall()
-      end
-    end)
-    require("oil").open("tests/perf")
-  end
-
-  run_profile()
-end
-
-function _G.flame_profile()
-  if not vim.uv.fs_stat("tests/perf/profile.nvim") then
-    vim
-      .system({ "git", "clone", "https://github.com/stevearc/profile.nvim", "tests/perf/profile.nvim" })
-      :wait()
-  end
-  vim.opt.runtimepath:prepend(vim.fn.fnamemodify("./tests/perf/profile.nvim", ":p"))
-  local profile = require("profile")
-  profile.instrument_autocmds()
-  profile.instrument("oil*")
-
-  require("oil").setup(setup_opts)
-  profile.start()
-  require("oil").open("tests/perf")
-  wait_for_done(function()
-    profile.stop("profile.json")
-    vim.cmd.qall()
-  end)
-end

From 740b8fd425a2b77f7f40eb5ac155ebe66ff9515c Mon Sep 17 00:00:00 2001
From: Foo-x 
Date: Sat, 23 Nov 2024 01:17:50 +0900
Subject: [PATCH 30/34] feat: add highlight group for orphaned links (#502)

* feat: add highlight for orphan links

Closes #501

* feat: add OilOrphanLinkTarget highlight group

---------

Co-authored-by: Steven Arcangeli 
---
 doc/oil.txt      |  6 ++++++
 lua/oil/init.lua | 10 ++++++++++
 lua/oil/view.lua |  5 +++--
 3 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/doc/oil.txt b/doc/oil.txt
index 0cfb872a..62e547e8 100644
--- a/doc/oil.txt
+++ b/doc/oil.txt
@@ -619,9 +619,15 @@ OilSocket                                                           *hl-OilSocke
 OilLink                                                               *hl-OilLink*
     Soft links in an oil buffer
 
+OilOrphanLink                                                   *hl-OilOrphanLink*
+    Orphaned soft links in an oil buffer
+
 OilLinkTarget                                                   *hl-OilLinkTarget*
     The target of a soft link
 
+OilOrphanLinkTarget                                       *hl-OilOrphanLinkTarget*
+    The target of an orphaned soft link
+
 OilFile                                                               *hl-OilFile*
     Normal files in an oil buffer
 
diff --git a/lua/oil/init.lua b/lua/oil/init.lua
index 1450646f..e8b8275f 100644
--- a/lua/oil/init.lua
+++ b/lua/oil/init.lua
@@ -816,11 +816,21 @@ M._get_highlights = function()
       link = nil,
       desc = "Soft links in an oil buffer",
     },
+    {
+      name = "OilOrphanLink",
+      link = nil,
+      desc = "Orphaned soft links in an oil buffer",
+    },
     {
       name = "OilLinkTarget",
       link = "Comment",
       desc = "The target of a soft link",
     },
+    {
+      name = "OilOrphanLinkTarget",
+      link = "DiagnosticError",
+      desc = "The target of an orphaned soft link",
+    },
     {
       name = "OilFile",
       link = nil,
diff --git a/lua/oil/view.lua b/lua/oil/view.lua
index b36140b0..6a5939de 100644
--- a/lua/oil/view.lua
+++ b/lua/oil/view.lua
@@ -728,10 +728,11 @@ M.format_entry_cols = function(entry, column_defs, col_width, adapter)
         end
       end
     end
+    local is_orphan = not (meta and meta.link_stat)
 
-    table.insert(cols, { name, "OilLink" })
+    table.insert(cols, { name, is_orphan and "OilOrphanLink" or "OilLink" })
     if link_text then
-      table.insert(cols, { link_text, "OilLinkTarget" })
+      table.insert(cols, { link_text, is_orphan and "OilOrphanLinkTarget" or "OilLinkTarget" })
     end
   else
     table.insert(cols, { name, "OilFile" })

From 60e68967e51ff1ecd264c29e3de0d52bfff22df3 Mon Sep 17 00:00:00 2001
From: Ezekiel Warren 
Date: Fri, 22 Nov 2024 08:55:55 -0800
Subject: [PATCH 31/34] feat: highlight groups for hidden files (#459)

* feat: hidden highlights

* feat: OilHidden for hidden highlights instead of Comment

* fix: add the new combinatoric highlight groups

* perf: get rid of a call to is_hidden_file

* fix: tweak the default highlight group links

* fix: update function call in unit tests

---------

Co-authored-by: Steven Arcangeli 
---
 doc/oil.txt           | 24 ++++++++++++++++++++++++
 lua/oil/init.lua      | 40 ++++++++++++++++++++++++++++++++++++++++
 lua/oil/view.lua      | 40 +++++++++++++++++++++++++++-------------
 tests/parser_spec.lua |  2 +-
 4 files changed, 92 insertions(+), 14 deletions(-)

diff --git a/doc/oil.txt b/doc/oil.txt
index 62e547e8..87d4bbb8 100644
--- a/doc/oil.txt
+++ b/doc/oil.txt
@@ -607,30 +607,54 @@ yank_entry                                                    *actions.yank_entr
 --------------------------------------------------------------------------------
 HIGHLIGHTS                                                        *oil-highlights*
 
+OilHidden                                                           *hl-OilHidden*
+    Hidden entry in an oil buffer
+
 OilDir                                                                 *hl-OilDir*
     Directory names in an oil buffer
 
+OilDirHidden                                                     *hl-OilDirHidden*
+    Hidden directory names in an oil buffer
+
 OilDirIcon                                                         *hl-OilDirIcon*
     Icon for directories
 
 OilSocket                                                           *hl-OilSocket*
     Socket files in an oil buffer
 
+OilSocketHidden                                               *hl-OilSocketHidden*
+    Hidden socket files in an oil buffer
+
 OilLink                                                               *hl-OilLink*
     Soft links in an oil buffer
 
 OilOrphanLink                                                   *hl-OilOrphanLink*
     Orphaned soft links in an oil buffer
 
+OilLinkHidden                                                   *hl-OilLinkHidden*
+    Hidden soft links in an oil buffer
+
+OilOrphanLinkHidden                                       *hl-OilOrphanLinkHidden*
+    Hidden orphaned soft links in an oil buffer
+
 OilLinkTarget                                                   *hl-OilLinkTarget*
     The target of a soft link
 
 OilOrphanLinkTarget                                       *hl-OilOrphanLinkTarget*
     The target of an orphaned soft link
 
+OilLinkTargetHidden                                       *hl-OilLinkTargetHidden*
+    The target of a hidden soft link
+
+OilOrphanLinkTargetHidden                           *hl-OilOrphanLinkTargetHidden*
+    The target of an hidden orphaned soft link
+
 OilFile                                                               *hl-OilFile*
     Normal files in an oil buffer
 
+OilFileHidden                                                   *hl-OilFileHidden*
+    Hidden normal files in an oil buffer
+
 OilCreate                                                           *hl-OilCreate*
     Create action in the oil preview window
 
diff --git a/lua/oil/init.lua b/lua/oil/init.lua
index e8b8275f..72d5538a 100644
--- a/lua/oil/init.lua
+++ b/lua/oil/init.lua
@@ -796,11 +796,21 @@ end
 ---@private
 M._get_highlights = function()
   return {
+    {
+      name = "OilHidden",
+      link = "Comment",
+      desc = "Hidden entry in an oil buffer",
+    },
     {
       name = "OilDir",
       link = "Directory",
       desc = "Directory names in an oil buffer",
     },
+    {
+      name = "OilDirHidden",
+      link = "OilHidden",
+      desc = "Hidden directory names in an oil buffer",
+    },
     {
       name = "OilDirIcon",
       link = "OilDir",
@@ -811,6 +821,11 @@ M._get_highlights = function()
       link = "Keyword",
       desc = "Socket files in an oil buffer",
     },
+    {
+      name = "OilSocketHidden",
+      link = "OilHidden",
+      desc = "Hidden socket files in an oil buffer",
+    },
     {
       name = "OilLink",
       link = nil,
@@ -821,6 +836,16 @@ M._get_highlights = function()
       link = nil,
       desc = "Orphaned soft links in an oil buffer",
     },
+    {
+      name = "OilLinkHidden",
+      link = "OilHidden",
+      desc = "Hidden soft links in an oil buffer",
+    },
+    {
+      name = "OilOrphanLinkHidden",
+      link = "OilLinkHidden",
+      desc = "Hidden orphaned soft links in an oil buffer",
+    },
     {
       name = "OilLinkTarget",
       link = "Comment",
@@ -831,11 +856,26 @@ M._get_highlights = function()
       link = "DiagnosticError",
       desc = "The target of an orphaned soft link",
     },
+    {
+      name = "OilLinkTargetHidden",
+      link = "OilHidden",
+      desc = "The target of a hidden soft link",
+    },
+    {
+      name = "OilOrphanLinkTargetHidden",
+      link = "OilOrphanLinkTarget",
+      desc = "The target of an hidden orphaned soft link",
+    },
     {
       name = "OilFile",
       link = nil,
       desc = "Normal files in an oil buffer",
     },
+    {
+      name = "OilFileHidden",
+      link = "OilHidden",
+      desc = "Hidden normal files in an oil buffer",
+    },
     {
       name = "OilCreate",
       link = "DiagnosticInfo",
diff --git a/lua/oil/view.lua b/lua/oil/view.lua
index 6a5939de..74ab145a 100644
--- a/lua/oil/view.lua
+++ b/lua/oil/view.lua
@@ -19,10 +19,16 @@ local last_cursor_entry = {}
 
 ---@param name string
 ---@param bufnr integer
----@return boolean
+---@return boolean display
+---@return boolean is_hidden Whether the file is classified as a hidden file
 M.should_display = function(name, bufnr)
-  return not config.view_options.is_always_hidden(name, bufnr)
-    and (config.view_options.show_hidden or not config.view_options.is_hidden_file(name, bufnr))
+  if config.view_options.is_always_hidden(name, bufnr) then
+    return false, true
+  else
+    local is_hidden = config.view_options.is_hidden_file(name, bufnr)
+    local display = config.view_options.show_hidden or not is_hidden
+    return display, is_hidden
+  end
 end
 
 ---@param bufname string
@@ -633,13 +639,15 @@ local function render_buffer(bufnr, opts)
   end
 
   if M.should_display("..", bufnr) then
-    local cols = M.format_entry_cols({ 0, "..", "directory" }, column_defs, col_width, adapter)
+    local cols =
+      M.format_entry_cols({ 0, "..", "directory" }, column_defs, col_width, adapter, true)
     table.insert(line_table, cols)
   end
 
   for _, entry in ipairs(entry_list) do
-    if M.should_display(entry[FIELD_NAME], bufnr) then
-      local cols = M.format_entry_cols(entry, column_defs, col_width, adapter)
+    local should_display, is_hidden = M.should_display(entry[FIELD_NAME], bufnr)
+    if should_display then
+      local cols = M.format_entry_cols(entry, column_defs, col_width, adapter, is_hidden)
       table.insert(line_table, cols)
 
       local name = entry[FIELD_NAME]
@@ -688,10 +696,15 @@ end
 ---@param column_defs table[]
 ---@param col_width integer[]
 ---@param adapter oil.Adapter
+---@param is_hidden boolean
 ---@return oil.TextChunk[]
-M.format_entry_cols = function(entry, column_defs, col_width, adapter)
+M.format_entry_cols = function(entry, column_defs, col_width, adapter, is_hidden)
   local name = entry[FIELD_NAME]
   local meta = entry[FIELD_META]
+  local hl_suffix = ""
+  if is_hidden then
+    hl_suffix = "Hidden"
+  end
   if meta and meta.display_name then
     name = meta.display_name
   end
@@ -711,9 +724,9 @@ M.format_entry_cols = function(entry, column_defs, col_width, adapter)
   -- Always add the entry name at the end
   local entry_type = entry[FIELD_TYPE]
   if entry_type == "directory" then
-    table.insert(cols, { name .. "/", "OilDir" })
+    table.insert(cols, { name .. "/", "OilDir" .. hl_suffix })
   elseif entry_type == "socket" then
-    table.insert(cols, { name, "OilSocket" })
+    table.insert(cols, { name, "OilSocket" .. hl_suffix })
   elseif entry_type == "link" then
     local link_text
     if meta then
@@ -722,7 +735,7 @@ M.format_entry_cols = function(entry, column_defs, col_width, adapter)
       end
 
       if meta.link then
-        link_text = "->" .. " " .. meta.link
+        link_text = "-> " .. meta.link
         if meta.link_stat and meta.link_stat.type == "directory" then
           link_text = util.addslash(link_text)
         end
@@ -730,12 +743,13 @@ M.format_entry_cols = function(entry, column_defs, col_width, adapter)
     end
     local is_orphan = not (meta and meta.link_stat)
 
-    table.insert(cols, { name, is_orphan and "OilOrphanLink" or "OilLink" })
+    table.insert(cols, { name, (is_orphan and "OilOrphanLink" or "OilLink") .. hl_suffix })
     if link_text then
-      table.insert(cols, { link_text, is_orphan and "OilOrphanLinkTarget" or "OilLinkTarget" })
+      local target_hl = (is_orphan and "OilOrphanLinkTarget" or "OilLinkTarget") .. hl_suffix
+      table.insert(cols, { link_text, target_hl })
     end
   else
-    table.insert(cols, { name, "OilFile" })
+    table.insert(cols, { name, "OilFile" .. hl_suffix })
   end
   return cols
 end
diff --git a/tests/parser_spec.lua b/tests/parser_spec.lua
index 527e8217..9884ca1b 100644
--- a/tests/parser_spec.lua
+++ b/tests/parser_spec.lua
@@ -90,7 +90,7 @@ describe("parser", function()
     local file = test_adapter.test_set("/foo/a.txt", "file")
     vim.cmd.edit({ args = { "oil-test:///foo/" } })
     local bufnr = vim.api.nvim_get_current_buf()
-    local cols = view.format_entry_cols(file, {}, {}, test_adapter)
+    local cols = view.format_entry_cols(file, {}, {}, test_adapter, false)
     local lines = util.render_table({ cols }, {})
     table.insert(lines, "")
     table.insert(lines, "     ")

From 99ce32f4a2ecf76263b72fcc31efb163faa1a941 Mon Sep 17 00:00:00 2001
From: Foo-x 
Date: Sat, 23 Nov 2024 03:23:08 +0900
Subject: [PATCH 32/34] feat: config option to customize filename highlight
 group (#508)

* feat: highlight config

Refs #402

* perf: minimize perf impact when option not provided

* doc: regenerate documentation

* fix: symbolic link rendering

* refactor: simplify conditional

---------

Co-authored-by: Steven Arcangeli 
---
 README.md          |  4 +++
 doc/oil.txt        |  4 +++
 lua/oil/config.lua |  9 ++++++
 lua/oil/view.lua   | 75 ++++++++++++++++++++++++++++++++++++----------
 4 files changed, 76 insertions(+), 16 deletions(-)

diff --git a/README.md b/README.md
index 684ff248..f690904e 100644
--- a/README.md
+++ b/README.md
@@ -232,6 +232,10 @@ require("oil").setup({
       { "type", "asc" },
       { "name", "asc" },
     },
+    -- Customize the highlight group for the file name
+    highlight_filename = function(entry, is_hidden, is_link_target, is_link_orphan)
+      return nil
+    end,
   },
   -- Extra arguments to pass to SCP when moving/copying files over SSH
   extra_scp_args = {},
diff --git a/doc/oil.txt b/doc/oil.txt
index 87d4bbb8..63bca11a 100644
--- a/doc/oil.txt
+++ b/doc/oil.txt
@@ -117,6 +117,10 @@ CONFIG                                                                *oil-confi
           { "type", "asc" },
           { "name", "asc" },
         },
+        -- Customize the highlight group for the file name
+        highlight_filename = function(entry, is_hidden, is_link_target, is_link_orphan)
+          return nil
+        end,
       },
       -- Extra arguments to pass to SCP when moving/copying files over SSH
       extra_scp_args = {},
diff --git a/lua/oil/config.lua b/lua/oil/config.lua
index 185cb159..0374e43b 100644
--- a/lua/oil/config.lua
+++ b/lua/oil/config.lua
@@ -102,6 +102,10 @@ local default_config = {
       { "type", "asc" },
       { "name", "asc" },
     },
+    -- Customize the highlight group for the file name
+    highlight_filename = function(entry, is_hidden, is_link_target, is_link_orphan)
+      return nil
+    end,
   },
   -- Extra arguments to pass to SCP when moving/copying files over SSH
   extra_scp_args = {},
@@ -207,6 +211,9 @@ default_config.adapters = {
   ["oil-trash://"] = "trash",
 }
 default_config.adapter_aliases = {}
+-- We want the function in the default config for documentation generation, but if we nil it out
+-- here we can get some performance wins
+default_config.view_options.highlight_filename = nil
 
 ---@class oil.Config
 ---@field adapters table Hidden from SetupOpts
@@ -281,6 +288,7 @@ local M = {}
 ---@field natural_order boolean|"fast"
 ---@field case_insensitive boolean
 ---@field sort oil.SortSpec[]
+---@field highlight_filename? fun(entry: oil.Entry, is_hidden: boolean, is_link_target: boolean, is_link_orphan: boolean): string|nil
 
 ---@class (exact) oil.SetupViewOptions
 ---@field show_hidden? boolean Show files and directories that start with "."
@@ -289,6 +297,7 @@ local M = {}
 ---@field natural_order? boolean|"fast" Sort file names with numbers in a more intuitive order for humans. Can be slow for large directories.
 ---@field case_insensitive? boolean Sort file and directory names case insensitive
 ---@field sort? oil.SortSpec[] Sort order for the file list
+---@field highlight_filename? fun(entry: oil.Entry, is_hidden: boolean, is_link_target: boolean, is_link_orphan: boolean): string|nil Customize the highlight group for the file name
 
 ---@class (exact) oil.SortSpec
 ---@field [1] string
diff --git a/lua/oil/view.lua b/lua/oil/view.lua
index 74ab145a..6b26182e 100644
--- a/lua/oil/view.lua
+++ b/lua/oil/view.lua
@@ -691,6 +691,28 @@ local function render_buffer(bufnr, opts)
   return seek_after_render_found
 end
 
+---@param name string
+---@param meta? table
+---@return string filename
+---@return string|nil link_target
+local function get_link_text(name, meta)
+  local link_text
+  if meta then
+    if meta.link_stat and meta.link_stat.type == "directory" then
+      name = name .. "/"
+    end
+
+    if meta.link then
+      link_text = "-> " .. meta.link
+      if meta.link_stat and meta.link_stat.type == "directory" then
+        link_text = util.addslash(link_text)
+      end
+    end
+  end
+
+  return name, link_text
+end
+
 ---@private
 ---@param entry oil.InternalEntry
 ---@param column_defs table[]
@@ -723,34 +745,55 @@ M.format_entry_cols = function(entry, column_defs, col_width, adapter, is_hidden
   end
   -- Always add the entry name at the end
   local entry_type = entry[FIELD_TYPE]
+
+  local get_custom_hl = config.view_options.highlight_filename
+  local link_name, link_name_hl, link_target, link_target_hl
+  if get_custom_hl then
+    local external_entry = util.export_entry(entry)
+
+    if entry_type == "link" then
+      link_name, link_target = get_link_text(name, meta)
+      local is_orphan = not (meta and meta.link_stat)
+      link_name_hl = get_custom_hl(external_entry, is_hidden, false, is_orphan)
+
+      if link_target then
+        link_target_hl = get_custom_hl(external_entry, is_hidden, true, is_orphan)
+      end
+
+      -- intentional fallthrough
+    else
+      local hl = get_custom_hl(external_entry, is_hidden, false, false)
+      if hl then
+        table.insert(cols, { name, hl })
+        return cols
+      end
+    end
+  end
+
   if entry_type == "directory" then
     table.insert(cols, { name .. "/", "OilDir" .. hl_suffix })
   elseif entry_type == "socket" then
     table.insert(cols, { name, "OilSocket" .. hl_suffix })
   elseif entry_type == "link" then
-    local link_text
-    if meta then
-      if meta.link_stat and meta.link_stat.type == "directory" then
-        name = name .. "/"
-      end
-
-      if meta.link then
-        link_text = "-> " .. meta.link
-        if meta.link_stat and meta.link_stat.type == "directory" then
-          link_text = util.addslash(link_text)
-        end
-      end
+    if not link_name then
+      link_name, link_target = get_link_text(name, meta)
     end
     local is_orphan = not (meta and meta.link_stat)
+    if not link_name_hl then
+      link_name_hl = (is_orphan and "OilOrphanLink" or "OilLink") .. hl_suffix
+    end
+    table.insert(cols, { link_name, link_name_hl })
 
-    table.insert(cols, { name, (is_orphan and "OilOrphanLink" or "OilLink") .. hl_suffix })
-    if link_text then
-      local target_hl = (is_orphan and "OilOrphanLinkTarget" or "OilLinkTarget") .. hl_suffix
-      table.insert(cols, { link_text, target_hl })
+    if link_target then
+      if not link_target_hl then
+        link_target_hl = (is_orphan and "OilOrphanLinkTarget" or "OilLinkTarget") .. hl_suffix
+      end
+      table.insert(cols, { link_target, link_target_hl })
     end
   else
     table.insert(cols, { name, "OilFile" .. hl_suffix })
   end
+
   return cols
 end
 

From da93d55e32d73a17c447067d168d80290ae96590 Mon Sep 17 00:00:00 2001
From: Steven Arcangeli 
Date: Sun, 24 Nov 2024 15:04:00 -0800
Subject: [PATCH 33/34] fix: work around performance issue with treesitter,
 folds, and large directories

---
 lua/oil/view.lua | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lua/oil/view.lua b/lua/oil/view.lua
index 6b26182e..15e50228 100644
--- a/lua/oil/view.lua
+++ b/lua/oil/view.lua
@@ -185,6 +185,10 @@ end
 
 M.set_win_options = function()
   local winid = vim.api.nvim_get_current_win()
+
+  -- work around https://github.com/neovim/neovim/pull/27422
+  vim.api.nvim_set_option_value("foldmethod", "manual", { scope = "local", win = winid })
+
   for k, v in pairs(config.win_options) do
     vim.api.nvim_set_option_value(k, v, { scope = "local", win = winid })
   end

From 3c2de37accead0240fbe812f5ccdedfe0b973557 Mon Sep 17 00:00:00 2001
From: Steven Arcangeli 
Date: Mon, 25 Nov 2024 09:10:29 -0800
Subject: [PATCH 34/34] debug: include shell command in error message

---
 lua/oil/shell.lua | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lua/oil/shell.lua b/lua/oil/shell.lua
index 2c401efc..b04b27be 100644
--- a/lua/oil/shell.lua
+++ b/lua/oil/shell.lua
@@ -26,7 +26,8 @@ M.run = function(cmd, opts, callback)
           if err == "" then
             err = "Unknown error"
           end
-          callback(err)
+          local cmd_str = type(cmd) == "string" and cmd or table.concat(cmd, " ")
+          callback(string.format("Error running command '%s'\n%s", cmd_str, err))
         end
       end),
     })