I was discussing with a reddit user u//SH4DOW_KING how to implement IntelliJ’s “hungry backspace,” which behaves as follows, where |
is the cursor:
local function foo()
|return
end
becomes
local function foo()
|return
end
after hitting backspace, regardless of indent level, and
local function foo()
|return
end
becomes
local function foo()
|return
end
Essentially, if deleting whitespace, it deletes as much as possible until the next syntactically proper indentation.
I recommend binding Shift-Backspace, as I do in the mapping, to normal backspace in instances where overriding this behavior is useful, which I’ve found to be the case particularly in Python
Implementation:
local escape_code = vim.api.nvim_replace_termcodes(
"<Esc>",
false, false, true
)
local backspace_code = vim.api.nvim_replace_termcodes(
"<BS>",
false, false, true
)
local function viml_backspace()
-- expression from a deleted reddit user
vim.cmd([[
let g:exprvalue =
\ (&indentexpr isnot '' ? &indentkeys : &cinkeys) =~? '!\^F' &&
\ &backspace =~? '.*eol\&.*start\&.*indent\&' &&
\ !search('\S','nbW',line('.')) ? (col('.') != 1 ? "\<C-U>" : "") .
\ "\<bs>" . (getline(line('.')-1) =~ '\S' ? "" : "\<C-F>") : "\<bs>"
]])
return vim.g.exprvalue
end
local function backspace()
local line, col = unpack(vim.api.nvim_win_get_cursor(0))
local before_cursor_is_whitespace = vim.api.nvim_get_current_line()
:sub(0, col)
:match("^%s*$")
if not before_cursor_is_whitespace then
return require('nvim-autopairs').autopairs_bs()
end
if line == 1 then
return viml_backspace()
end
local correct_indent = require("nvim-treesitter.indent").get_indent(line)
local current_indent = vim.fn.indent(line)
local previous_line_is_whitespace = vim.api.nvim_buf_get_lines(
0, line - 2, line - 1, false
)[1]:match("^%s*$")
if current_indent == correct_indent then
if previous_line_is_whitespace then
return viml_backspace()
end
return backspace_code
elseif current_indent > correct_indent then
return escape_code .. "==0wi"
end
return backspace_code
end
m_o('i', '<BS>', backspace, {
expr = true,
noremap = true,
replace_keycodes = false,
})
m('i', '<S-BS>', '<BS>')
The convoluted viml expression at the beginning is a good implementation of the first feature of hungry whitespace, which I may rewrite in lua at some point, but for now is pretty convenient—the issue is, it doesn’t implement the second part of the functionality I demonstrated, and nvim-autopairs has its own backspace which automatically deletes pairs, and I would like to keep that functionality as well.
The finished mapping after a couple of iterations uses a series of checks to apply the appropriate backspace output, and I tried to include every edge case and minimize external function calls. If I get around to the lua rewrite, I will certainly update this with that code!