diff --git a/lua/oil/view.lua b/lua/oil/view.lua index 001240b..2a03dee 100644 --- a/lua/oil/view.lua +++ b/lua/oil/view.lua @@ -145,6 +145,9 @@ end ---@type table local session = {} +---@type table +local insert_boundary = {} + ---@return integer[] M.get_all_buffers = function() return vim.tbl_filter(vim.api.nvim_buf_is_loaded, vim.tbl_keys(session)) @@ -418,6 +421,79 @@ local function show_insert_guide(bufnr) }) end +---@param bufnr integer +---@return integer +local function update_insert_boundary(bufnr) + local cur = vim.api.nvim_win_get_cursor(0) + local cached = insert_boundary[bufnr] + if cached and cached.lnum == cur[1] then + return cached.min_col + end + + local adapter = util.get_adapter(bufnr, true) + if not adapter then + return 0 + end + + local parser = require('oil.mutator.parser') + local line = vim.api.nvim_buf_get_lines(bufnr, cur[1] - 1, cur[1], true)[1] + local column_defs = columns.get_supported_columns(adapter) + local result = parser.parse_line(adapter, line, column_defs) + local min_col = 0 + if result and result.ranges then + min_col = result.ranges.name[1] + end + insert_boundary[bufnr] = { lnum = cur[1], min_col = min_col } + return min_col +end + +---@param bufnr integer +local function setup_insert_constraints(bufnr) + if not config.constrain_cursor then + return + end + + local function make_bs_rhs(bufnr_inner) + return function() + local min_col = update_insert_boundary(bufnr_inner) + local col = vim.fn.col('.') + if col <= min_col + 1 then + return '' + end + return '' + end + end + + local function make_cu_rhs(bufnr_inner) + return function() + local min_col = update_insert_boundary(bufnr_inner) + local col = vim.fn.col('.') + if col <= min_col + 1 then + return '' + end + local count = col - min_col - 1 + return string.rep('', count) + end + end + + local function make_cw_rhs(bufnr_inner) + return function() + local min_col = update_insert_boundary(bufnr_inner) + local col = vim.fn.col('.') + if col <= min_col + 1 then + return '' + end + return '' + end + end + + local opts = { buffer = bufnr, expr = true, nowait = true, silent = true } + vim.keymap.set('i', '', make_bs_rhs(bufnr), opts) + vim.keymap.set('i', '', make_bs_rhs(bufnr), opts) + vim.keymap.set('i', '', make_cu_rhs(bufnr), opts) + vim.keymap.set('i', '', make_cw_rhs(bufnr), opts) +end + ---Redraw original path virtual text for trash buffer ---@param bufnr integer local function redraw_trash_virtual_text(bufnr) @@ -512,6 +588,7 @@ M.initialize = function(bufnr) callback = function() local view_data = session[bufnr] session[bufnr] = nil + insert_boundary[bufnr] = nil if view_data and view_data.fs_event then view_data.fs_event:stop() end @@ -546,6 +623,7 @@ M.initialize = function(bufnr) group = 'Oil', buffer = bufnr, callback = function() + insert_boundary[bufnr] = nil if vim.w.oil_saved_ve ~= nil then vim.wo.virtualedit = vim.w.oil_saved_ve vim.w.oil_saved_ve = nil @@ -686,6 +764,7 @@ M.initialize = function(bufnr) end end) keymap_util.set_keymaps(config.keymaps, bufnr) + setup_insert_constraints(bufnr) end ---@param adapter oil.Adapter diff --git a/spec/regression_spec.lua b/spec/regression_spec.lua index bfa5e53..80a81f8 100644 --- a/spec/regression_spec.lua +++ b/spec/regression_spec.lua @@ -124,6 +124,68 @@ describe('regression tests', function() }) end) + it('BS at constraint boundary is a no-op', function() + tmpdir:create({ 'a.txt' }) + oil.setup({ constrain_cursor = 'name', columns = {} }) + vim.cmd.edit({ args = { 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') } }) + test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' }) + test_util.actions.focus('a.txt') + local line_before = vim.api.nvim_get_current_line() + local cur = vim.api.nvim_win_get_cursor(0) + local parser = require('oil.mutator.parser') + local adapter = require('oil.util').get_adapter(0, true) + local columns_mod = require('oil.columns') + local column_defs = columns_mod.get_supported_columns(adapter) + local result = parser.parse_line(adapter, line_before, column_defs) + local name_col = result.ranges.name[1] + vim.api.nvim_win_set_cursor(0, { cur[1], name_col }) + vim.cmd.startinsert() + vim.wait(20) + test_util.feedkeys({ '' }, 10) + vim.cmd.stopinsert() + vim.wait(10) + local line_after = vim.api.nvim_get_current_line() + assert.equals(line_before, line_after) + end) + + it('BS within name works normally', function() + tmpdir:create({ 'a.txt' }) + oil.setup({ constrain_cursor = 'name', columns = {} }) + vim.cmd.edit({ args = { 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') } }) + test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' }) + test_util.actions.focus('a.txt') + local line_before = vim.api.nvim_get_current_line() + test_util.feedkeys({ 'A', 'x', '' }, 10) + vim.cmd.stopinsert() + vim.wait(10) + local line_after = vim.api.nvim_get_current_line() + assert.equals(line_before, line_after) + end) + + it('BS does not cross into prefix area', function() + tmpdir:create({ 'a.txt' }) + oil.setup({ constrain_cursor = 'name', columns = {} }) + vim.cmd.edit({ args = { 'oil://' .. vim.fn.fnamemodify(tmpdir.path, ':p') } }) + test_util.wait_for_autocmd({ 'User', pattern = 'OilEnter' }) + test_util.actions.focus('a.txt') + local line_before = vim.api.nvim_get_current_line() + local parser = require('oil.mutator.parser') + local adapter = require('oil.util').get_adapter(0, true) + local columns_mod = require('oil.columns') + local column_defs = columns_mod.get_supported_columns(adapter) + local result = parser.parse_line(adapter, line_before, column_defs) + local name_col = result.ranges.name[1] + vim.api.nvim_win_set_cursor(0, { vim.api.nvim_win_get_cursor(0)[1], name_col + 1 }) + vim.cmd.startinsert() + vim.wait(20) + test_util.feedkeys({ '', '', '' }, 10) + vim.cmd.stopinsert() + vim.wait(10) + local line_after = vim.api.nvim_get_current_line() + local id_prefix = line_before:match('^(/%d+ )') + assert.truthy(line_after:find(id_prefix, 1, true)) + end) + it('can open files from floating window', function() tmpdir:create({ 'a.txt' }) oil.open_float(tmpdir.path)