Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use builtin methods for getting selection. #1013

Merged
merged 3 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion DOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -3455,7 +3455,9 @@ These are the settings you can provide to `luasnip.setup()`:
fine (alternatively, this can also be mapped using
`<Plug>luasnip-delete-check`).
- `store_selection_keys`: Mapping for populating `TM_SELECTED_TEXT` and related
variables (not set by default).
variables (not set by default).
If you want to set this mapping yourself, map `ls.select_keys` (not a
function, actually a string/key-combination) as a rhs.
- `enable_autosnippets`: Autosnippets are disabled by default to minimize
performance penalty if unused. Set to `true` to enable.
- `ext_opts`: Additional options passed to extmarks. Can be used to add
Expand Down
5 changes: 3 additions & 2 deletions doc/luasnip.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
*luasnip.txt* For NVIM v0.8.0 Last change: 2023 October 17
*luasnip.txt* For NVIM v0.8.0 Last change: 2023 November 10

==============================================================================
Table of Contents *luasnip-table-of-contents*
Expand Down Expand Up @@ -3288,7 +3288,8 @@ These are the settings you can provide to `luasnip.setup()`:
`'InsertLeave'`, to react to changes done in Insert mode) should work just fine
(alternatively, this can also be mapped using `<Plug>luasnip-delete-check`).
- `store_selection_keys`: Mapping for populating `TM_SELECTED_TEXT` and related
variables (not set by default).
variables (not set by default). If you want to set this mapping yourself, map
`ls.select_keys` (not a function, actually a string/key-combination) as a rhs.
- `enable_autosnippets`: Autosnippets are disabled by default to minimize
performance penalty if unused. Set to `true` to enable.
- `ext_opts`: Additional options passed to extmarks. Can be used to add
Expand Down
5 changes: 3 additions & 2 deletions lua/luasnip/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,9 @@ c = {
if session.config.store_selection_keys then
vim.cmd(
string.format(
[[xnoremap <silent> %s :lua require('luasnip.util.util').store_selection()<cr>gv"_s]],
session.config.store_selection_keys
[[xnoremap <silent> %s %s]],
session.config.store_selection_keys,
require("luasnip.util.select").select_keys
)
)
end
Expand Down
1 change: 1 addition & 0 deletions lua/luasnip/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,7 @@ local ls_lazy = {
config = function() return require("luasnip.config") end,
multi_snippet = function() return require("luasnip.nodes.multiSnippet").new_multisnippet end,
snippet_source = function() return require("luasnip.session.snippet_collection.source") end,
select_keys = function() return require("luasnip.util.select").select_keys end
}

ls = util.lazy_table({
Expand Down
3 changes: 2 additions & 1 deletion lua/luasnip/util/_builtin_vars.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local util = require("luasnip.util.util")
local select_util = require("luasnip.util.select")
local lazy_vars = {}

-- Variables defined in https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables
Expand Down Expand Up @@ -156,7 +157,7 @@ local function eager_vars(info)
vars.TM_LINE_INDEX = tostring(pos[1])
vars.TM_LINE_NUMBER = tostring(pos[1] + 1)
vars.LS_SELECT_RAW, vars.LS_SELECT_DEDENT, vars.TM_SELECTED_TEXT =
util.get_selection()
select_util.retrieve()
-- These are for backward compatibility, for now on all builtins that are not part of TM_ go in LS_
vars.SELECT_RAW, vars.SELECT_DEDENT =
vars.LS_SELECT_RAW, vars.LS_SELECT_DEDENT
Expand Down
141 changes: 141 additions & 0 deletions lua/luasnip/util/select.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
local M = {}

local SELECT_RAW = "LUASNIP_SELECT_RAW"
local SELECT_DEDENT = "LUASNIP_SELECT_DEDENT"
local TM_SELECT = "LUASNIP_TM_SELECT"

function M.retrieve()
local ok, val = pcall(vim.api.nvim_buf_get_var, 0, SELECT_RAW)
if ok then
local result = {
val,
vim.api.nvim_buf_get_var(0, SELECT_DEDENT),
vim.api.nvim_buf_get_var(0, TM_SELECT),
}

vim.api.nvim_buf_del_var(0, SELECT_RAW)
vim.api.nvim_buf_del_var(0, SELECT_DEDENT)
vim.api.nvim_buf_del_var(0, TM_SELECT)

return unpack(result)
end
return {}, {}, {}
end

local function get_min_indent(lines)
-- "^(%s*)%S": match only lines that actually contain text.
local min_indent = lines[1]:match("^(%s*)%S")
for i = 2, #lines do
-- %s* -> at least matches
local line_indent = lines[i]:match("^(%s*)%S")
-- ignore if not matched.
if line_indent then
-- if no line until now matched, use line_indent.
if not min_indent or #line_indent < #min_indent then
min_indent = line_indent
end
end
end
return min_indent
end

local function store_registers(...)
local names = { ... }
local restore_data = {}
for _, name in ipairs(names) do
restore_data[name] = {
data = vim.fn.getreg(name),
type = vim.fn.getregtype(name),
}
end
return restore_data
end

local function restore_registers(restore_data)
for name, name_restore_data in pairs(restore_data) do
vim.fn.setreg(name, name_restore_data.data, name_restore_data.type)
end
end

-- subtle: `:lua` exits VISUAL, which means that the '< '>-marks will be set correctly!
-- Afterwards, we can just use <cmd>lua, which does not change the mode.
M.select_keys =
[[:lua require("luasnip.util.select").pre_cut()<Cr>gv"zs<cmd>lua require('luasnip.util.select').post_cut("z")<Cr>]]

local saved_registers
local lines
local start_line, start_col, end_line, end_col
local mode
function M.pre_cut()
-- store registers so we don't change any of them.
-- "" is affected since we perform a cut (s), 1-9 also (although :h
-- quote_number seems to state otherwise for cuts to specific registers..?).
saved_registers =
store_registers("", "1", "2", "3", "4", "5", "6", "7", "8", "9", "z")

-- store data needed for de-indenting lines.
start_line = vim.fn.line("'<") - 1
start_col = vim.fn.col("'<")
end_line = vim.fn.line("'>") - 1
end_col = vim.fn.col("'>")
-- +1: include final line.
lines = vim.api.nvim_buf_get_lines(0, start_line, end_line + 1, true)
mode = vim.fn.visualmode()
end

function M.post_cut(register_name)
-- remove trailing newline.
local chunks = vim.split(vim.fn.getreg(register_name):gsub("\n$", ""), "\n")

-- make sure to restore the registers to the state they were before cutting.
restore_registers(saved_registers)

local tm_select, select_dedent = vim.deepcopy(chunks), vim.deepcopy(chunks)

local min_indent = get_min_indent(lines) or ""
if mode == "V" then
tm_select[1] = tm_select[1]:gsub("^%s+", "")
-- remove indent from all lines:
for i = 1, #select_dedent do
select_dedent[i] = select_dedent[i]:gsub("^" .. min_indent, "")
end
-- due to the trailing newline of the last line, and vim.split's
-- behaviour, the last line of `chunks` is always empty.
-- Keep this
elseif mode == "v" then
-- if selection starts inside indent, remove indent.
if #min_indent > start_col then
select_dedent[1] = lines[1]:gsub(min_indent, "")
end
for i = 2, #select_dedent - 1 do
select_dedent[i] = select_dedent[i]:gsub(min_indent, "")
end

-- remove as much indent from the last line as possible.
if #min_indent > end_col then
select_dedent[#select_dedent] = ""
else
select_dedent[#select_dedent] =
select_dedent[#select_dedent]:gsub("^" .. min_indent, "")
end
else
-- in block: if indent is in block, remove the part of it that is inside
-- it for select_dedent.
if #min_indent > start_col then
local indent_in_block = min_indent:sub(start_col, #min_indent)
for i, line in ipairs(chunks) do
select_dedent[i] = line:gsub("^" .. indent_in_block, "")
end
end
end

vim.api.nvim_buf_set_var(0, SELECT_RAW, chunks)
vim.api.nvim_buf_set_var(0, SELECT_DEDENT, select_dedent)
vim.api.nvim_buf_set_var(0, TM_SELECT, tm_select)

lines = nil
start_line, start_col, end_line, end_col = nil, nil, nil, nil
mode = nil
end

return M
148 changes: 0 additions & 148 deletions lua/luasnip/util/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -268,152 +268,6 @@ local function wrap_nodes(nodes)
end
end

local SELECT_RAW = "LUASNIP_SELECT_RAW"
local SELECT_DEDENT = "LUASNIP_SELECT_DEDENT"
local TM_SELECT = "LUASNIP_TM_SELECT"

local function get_selection()
local ok, val = pcall(vim.api.nvim_buf_get_var, 0, SELECT_RAW)
if ok then
local result = {
val,
vim.api.nvim_buf_get_var(0, SELECT_DEDENT),
vim.api.nvim_buf_get_var(0, TM_SELECT),
}

vim.api.nvim_buf_del_var(0, SELECT_RAW)
vim.api.nvim_buf_del_var(0, SELECT_DEDENT)
vim.api.nvim_buf_del_var(0, TM_SELECT)

return unpack(result)
end
return {}, {}, {}
end

local function get_min_indent(lines)
-- "^(%s*)%S": match only lines that actually contain text.
local min_indent = lines[1]:match("^(%s*)%S")
for i = 2, #lines do
-- %s* -> at least matches
local line_indent = lines[i]:match("^(%s*)%S")
-- ignore if not matched.
if line_indent then
-- if no line until now matched, use line_indent.
if not min_indent or #line_indent < #min_indent then
min_indent = line_indent
end
end
end
return min_indent
end

-- nvim0.7 is missing vim.v.maxcol :/
-- but this should suffice.
local maxcol = vim.v.maxcol or 2147483647
-- displaycol_to may be nil for end-of-line
local function displaycol_trim_line(line, displaycol_from, displaycol_to)
local line_len = vim.str_utfindex(line, #line)

-- bytes are 0-based, start- and end-inclusive.
-- str_byteindex gives last byte, get first byte is last byte of previous
-- symbol +1.
local start_byte = vim.str_byteindex(line, displaycol_from - 1) + 1
local end_byte =
vim.str_byteindex(line, math.min(displaycol_to or maxcol, line_len))

return line:sub(start_byte, end_byte)
end

local function store_selection()
-- _col are positions in display-columns, so utf-index, 1-based.
-- lines are 0-based.
local _, start_line, start_col, _ = unpack(vim.fn.getcharpos("'<"))
start_line = start_line - 1

local _, end_line, end_col, _ = unpack(vim.fn.getcharpos("'>"))
end_line = end_line - 1

local mode = vim.fn.visualmode()
if
not vim.o.selection == "exclusive"
and not (start_line == end_line and start_col == end_col)
then
end_col = end_col - 1
end

-- include final line.
local lines = vim.api.nvim_buf_get_lines(0, start_line, end_line + 1, true)

local chunks = {}
if start_line == end_line then
chunks = { displaycol_trim_line(lines[1], start_col, end_col) }
else
-- displaycolumns!
local first_col = 1
local last_col = nil
if mode:lower() ~= "v" then -- mode is block
first_col = start_col
last_col = end_col
end
chunks = { displaycol_trim_line(lines[1], start_col, last_col) }

-- potentially trim lines (Block).
for cl = 2, #lines - 1 do
table.insert(
chunks,
displaycol_trim_line(lines[cl], first_col, last_col)
)
end
table.insert(
chunks,
displaycol_trim_line(lines[#lines], first_col, end_col)
)
end

-- init with raw selection.
local tm_select, select_dedent = vim.deepcopy(chunks), vim.deepcopy(chunks)
-- may be nil if no indent.
local min_indent = get_min_indent(lines) or ""
-- TM_SELECTED_TEXT contains text from new cursor position(for V the first
-- non-whitespace of first line, v and c-v raw) to end of selection.
if mode == "V" then
tm_select[1] = tm_select[1]:gsub("^%s+", "")
-- remove indent from all lines:
for i = 1, #select_dedent do
select_dedent[i] = select_dedent[i]:gsub("^" .. min_indent, "")
end
elseif mode == "v" then
-- if selection starts inside indent, remove indent.
if #min_indent > start_col then
select_dedent[1] = lines[1]:gsub(min_indent, "")
end
for i = 2, #select_dedent - 1 do
select_dedent[i] = select_dedent[i]:gsub(min_indent, "")
end

-- remove as much indent from the last line as possible.
if #min_indent > end_col then
select_dedent[#select_dedent] = ""
else
select_dedent[#select_dedent] =
select_dedent[#select_dedent]:gsub("^" .. min_indent, "")
end
else
-- in block: if indent is in block, remove the part of it that is inside
-- it for select_dedent.
if #min_indent > start_col then
local indent_in_block = min_indent:sub(start_col, #min_indent)
for i, line in ipairs(chunks) do
select_dedent[i] = line:gsub("^" .. indent_in_block, "")
end
end
end

vim.api.nvim_buf_set_var(0, SELECT_RAW, chunks)
vim.api.nvim_buf_set_var(0, SELECT_DEDENT, select_dedent)
vim.api.nvim_buf_set_var(0, TM_SELECT, tm_select)
end

local function pos_equal(p1, p2)
return p1[1] == p2[1] and p1[2] == p2[2]
end
Expand Down Expand Up @@ -646,8 +500,6 @@ return {
put = put,
to_string_table = to_string_table,
wrap_nodes = wrap_nodes,
store_selection = store_selection,
get_selection = get_selection,
pos_equal = pos_equal,
dedent = dedent,
indent = indent,
Expand Down
4 changes: 3 additions & 1 deletion tests/integration/parser_spec.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
local helpers = require("test.functional.helpers")(after_each)
local exec_lua, feed = helpers.exec_lua, helpers.feed
local exec_lua, feed, exec = helpers.exec_lua, helpers.feed, helpers.exec
local ls_helpers = require("helpers")
local Screen = require("test.functional.ui.screen")

Expand Down Expand Up @@ -725,6 +725,8 @@ describe("Parser", function()

-- expand snippet with selected multiline-text.
feed("iasdf<Cr>asdf<Esc>Vk<Tab>")
-- wait a bit..
exec('call wait(200, "0")')
exec_lua("ls.lsp_expand([[" .. snip .. "]])")

screen:expect({
Expand Down
Loading