From c919d04d796800a979f634a966e06ff7a3065da2 Mon Sep 17 00:00:00 2001 From: leiserfg Date: Fri, 2 Sep 2022 11:55:56 +0200 Subject: [PATCH] Add LS_CAPTURE_n and LS_TRIGGER environment variables Breaking change: LS is now a builtin namespace so user defined environments can't use it any more, here we will add any other builtin we find useful in the future. --- DOC.md | 13 +++++++--- doc/luasnip.txt | 24 +++++++++++------- lua/luasnip/init.lua | 4 ++- lua/luasnip/util/_builtin_vars.lua | 14 ++++++++--- lua/luasnip/util/environ.lua | 5 ++-- tests/integration/parser_spec.lua | 8 +++--- tests/unit/environ_spec.lua | 39 ++++++++++++++++++++++++++---- 7 files changed, 79 insertions(+), 28 deletions(-) diff --git a/DOC.md b/DOC.md index a799020ea..f650da542 100644 --- a/DOC.md +++ b/DOC.md @@ -1399,10 +1399,13 @@ If `jsregexp` is not available, transformation are replaced by a simple copy. # VARIABLES All `TM_something`-variables are supported with two additions: -`SELECT_RAW` and `SELECT_DEDENT`. These were introduced because +`LS_SELECT_RAW` and `LS_SELECT_DEDENT`. These were introduced because `TM_SELECTED_TEXT` is designed to be compatible with vscodes' behavior, which can be counterintuitive when the snippet can be expanded at places other than the point where selection started (or when doing transformations on selected text). +Besides those we also provide `LS_TRIGGER` which contains the trigger of the snippet, +and `LS_CAPTURE_n` (where n is a positive integer) that contains the n-th capture +when using a regex with capture groups as `trig` in the snippet definition. All variables can be used outside of lsp-parsed snippets as their values are stored in a snippets' `snip.env`-table: @@ -1410,7 +1413,7 @@ stored in a snippets' `snip.env`-table: s("selected_text", f(function(args, snip) local res, env = {}, snip.env table.insert(res, "Selected Text (current line is " .. env.TM_LINE_NUMBER .. "):") - for _, ele in ipairs(env.SELECT_RAW) do table.insert(res, ele) end + for _, ele in ipairs(env.LS_SELECT_RAW) do table.insert(res, ele) end return res end, {})) ``` @@ -1436,9 +1439,11 @@ You can also add your own variables by using the `ls.env_namespace(name, opts)` Is a function that receives a string and returns a value for the var with that name or a table from var name to a value (in this case, if the value is a function it will be executed lazily once per snippet expansion). - * `init`: `fn(pos: pair[int])->map[string, EnvVal]` Takes the 0-based position of the cursor and returns + * `init`: `fn(info: table)->map[string, EnvVal]` Returns a table of variables that will set to the environment of the snippet on expansion, use this for vars that have to be calculated in that moment or that depend on each other. + The `info` table argument contains `pos` (0-based position of the cursor on expansion), + the `trigger` of the snippet and the `captures` list. * `eager`: `list[string]` names of variables that will be taken from `vars` and appended eagerly (like those in init) * `multiline_vars`: `(fn(name:string)->bool)|map[sting, bool]|bool|string[]` Says if certain vars are a table or just a string, can be a function that get's the name of the var and returns true if the var is a key, @@ -1464,7 +1469,7 @@ ls.env_namespace("SYS", {vars=os.getenv, eager={"HOME"}}) -- then you can use $SYS_HOME which was eagerly initialized but also $SYS_USER (or any other system environment var) in your snippets -lsp.env_namespace("POS", {init=function(pos) return {"VAL": vim.inspect(pos)}} end) +lsp.env_namespace("POS", {init=function(info) return {"VAL": vim.inspect(info.pos)}} end) -- then you can use $POS_VAL in your snippets diff --git a/doc/luasnip.txt b/doc/luasnip.txt index d115fff75..d1271121b 100644 --- a/doc/luasnip.txt +++ b/doc/luasnip.txt @@ -1,4 +1,4 @@ -*luasnip.txt* For NVIM v0.5.0 Last change: 2022 August 30 +*luasnip.txt* For NVIM v0.5.0 Last change: 2022 September 02 ============================================================================== Table of Contents *luasnip-table-of-contents* @@ -1340,11 +1340,15 @@ If `jsregexp` is not available, transformation are replaced by a simple copy. ============================================================================== 17. VARIABLES *luasnip-variables* -All `TM_something`-variables are supported with two additions: `SELECT_RAW` and -`SELECT_DEDENT`. These were introduced because `TM_SELECTED_TEXT` is designed -to be compatible with vscodes’ behavior, which can be counterintuitive when -the snippet can be expanded at places other than the point where selection -started (or when doing transformations on selected text). +All `TM_something`-variables are supported with two additions: `LS_SELECT_RAW` +and `LS_SELECT_DEDENT`. These were introduced because `TM_SELECTED_TEXT` is +designed to be compatible with vscodes’ behavior, which can be +counterintuitive when the snippet can be expanded at places other than the +point where selection started (or when doing transformations on selected text). +Besides those we also provide `LS_TRIGGER` which contains the trigger of the +snippet, and `LS_CAPTURE_n` (where n is a positive integer) that contains the +n-th capture when using a regex with capture groups as `trig` in the snippet +definition. All variables can be used outside of lsp-parsed snippets as their values are stored in a snippets’ `snip.env`-table: @@ -1353,7 +1357,7 @@ stored in a snippets’ `snip.env`-table: s("selected_text", f(function(args, snip) local res, env = {}, snip.env table.insert(res, "Selected Text (current line is " .. env.TM_LINE_NUMBER .. "):") - for _, ele in ipairs(env.SELECT_RAW) do table.insert(res, ele) end + for _, ele in ipairs(env.LS_SELECT_RAW) do table.insert(res, ele) end return res end, {})) < @@ -1376,9 +1380,11 @@ where: Is a function that receives a string and returns a value for the var with that name or a table from var name to a value (in this case, if the value is a function it will be executed lazily once per snippet expansion). - - `init`: `fn(pos: pair[int])->map[string, EnvVal]` Takes the 0-based position of the cursor and returns + - `init`: `fn(info: table)->map[string, EnvVal]` Returns a table of variables that will set to the environment of the snippet on expansion, use this for vars that have to be calculated in that moment or that depend on each other. + The `info` table argument contains `pos` (0-based position of the cursor on expansion), + the `trigger` of the snippet and the `captures` list. - `eager`: `list[string]` names of variables that will be taken from `vars` and appended eagerly (like those in init) - `multiline_vars`: `(fn(name:string)->bool)|map[sting, bool]|bool|string[]` Says if certain vars are a table or just a string, can be a function that get’s the name of the var and returns true if the var is a key, @@ -1405,7 +1411,7 @@ A simple example to make it more clear: -- then you can use $SYS_HOME which was eagerly initialized but also $SYS_USER (or any other system environment var) in your snippets - lsp.env_namespace("POS", {init=function(pos) return {"VAL": vim.inspect(pos)}} end) + lsp.env_namespace("POS", {init=function(info) return {"VAL": vim.inspect(info.pos)}} end) -- then you can use $POS_VAL in your snippets diff --git a/lua/luasnip/init.lua b/lua/luasnip/init.lua index fa8e17132..63c11ae02 100644 --- a/lua/luasnip/init.lua +++ b/lua/luasnip/init.lua @@ -191,7 +191,9 @@ local function snip_expand(snippet, opts) snip.trigger = opts.expand_params.trigger or snip.trigger snip.captures = opts.expand_params.captures or {} - local env = Environ:new(opts.pos) + local info = + { trigger = snip.trigger, captures = snip.captures, pos = opts.pos } + local env = Environ:new(info) local pos_id = vim.api.nvim_buf_set_extmark( 0, diff --git a/lua/luasnip/util/_builtin_vars.lua b/lua/luasnip/util/_builtin_vars.lua index f05cf483b..ee1635af3 100644 --- a/lua/luasnip/util/_builtin_vars.lua +++ b/lua/luasnip/util/_builtin_vars.lua @@ -147,19 +147,27 @@ function lazy_vars.BLOCK_COMMENT_END() end -- These are the vars that have to be populated once the snippet starts to avoid any issue -local function eager_vars(pos) +local function eager_vars(info) local vars = {} + local pos = info.pos vars.TM_CURRENT_LINE = vim.api.nvim_buf_get_lines(0, pos[1], pos[1] + 1, false)[1] vars.TM_CURRENT_WORD = util.word_under_cursor(pos, vars.TM_CURRENT_LINE) vars.TM_LINE_INDEX = tostring(pos[1]) vars.TM_LINE_NUMBER = tostring(pos[1] + 1) - vars.SELECT_RAW, vars.SELECT_DEDENT, vars.TM_SELECTED_TEXT = + vars.LS_SELECT_RAW, vars.LS_SELECT_DEDENT, vars.TM_SELECTED_TEXT = util.get_selection() + -- 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 + for i, cap in ipairs(info.captures) do + vars["LS_CAPTURE_" .. i] = cap + end + vars.LS_TRIGGER = info.trigger return vars end -local builtin_ns = { SELECT = true } +local builtin_ns = { SELECT = true, LS = true } for name, _ in pairs(lazy_vars) do local parts = vim.split(name, "_") diff --git a/lua/luasnip/util/environ.lua b/lua/luasnip/util/environ.lua index af8f6b341..bd73d1a2f 100644 --- a/lua/luasnip/util/environ.lua +++ b/lua/luasnip/util/environ.lua @@ -39,14 +39,15 @@ function Environ.is_table(var_fullname) return nmsp.is_table(varname) end -function Environ:new(pos, o) +function Environ:new(info, o) o = o or {} setmetatable(o, self) + vim.list_extend(info, info.pos) -- For compatibility with old user defined namespaces for ns_name, ns in pairs(namespaces) do local eager_vars = {} if ns.init then - eager_vars = ns.init(pos) + eager_vars = ns.init(info) end for _, eager in ipairs(ns.eager) do if not eager_vars[eager] then diff --git a/tests/integration/parser_spec.lua b/tests/integration/parser_spec.lua index 48f86af87..99651514d 100644 --- a/tests/integration/parser_spec.lua +++ b/tests/integration/parser_spec.lua @@ -623,7 +623,7 @@ describe("Parser", function() it("Inserts default when the variable is empty", function() ls_helpers.session_setup_luasnip() - local snip = "${SELECT_DEDENT: a ${2:default}}" + local snip = "${LS_SELECT_DEDENT: a ${2:default}}" exec_lua("ls.lsp_expand([[" .. snip .. "]])") @@ -678,7 +678,7 @@ describe("Parser", function() it("handles default correctly inside placeholder", function() ls_helpers.session_setup_luasnip() - local snip = "${1: ${SELECT_DEDENT: a ${2:default}} }" + local snip = "${1: ${LS_SELECT_DEDENT: a ${2:default}} }" exec_lua("ls.lsp_expand([[" .. snip .. "]])") @@ -708,7 +708,7 @@ describe("Parser", function() it("handles copy-source inside default.", function() ls_helpers.session_setup_luasnip() - local snip = "${1: ${SELECT_DEDENT: a ${2:default} ${3:copied}}} $3" + local snip = "${1: ${LS_SELECT_DEDENT: a ${2:default} ${3:copied}}} $3" exec_lua("ls.lsp_expand([[" .. snip .. "]])") @@ -745,7 +745,7 @@ describe("Parser", function() it("handles copy inside default", function() ls_helpers.session_setup_luasnip() - local snip = "$1 ${2: ${SELECT_DEDENT: a ${3:default} $1} }" + local snip = "$1 ${2: ${LS_SELECT_DEDENT: a ${3:default} $1} }" -- indent, insert text, SELECT. exec_lua("ls.lsp_expand([[" .. snip .. "]])") diff --git a/tests/unit/environ_spec.lua b/tests/unit/environ_spec.lua index 3c70f70c3..b9161a1f7 100644 --- a/tests/unit/environ_spec.lua +++ b/tests/unit/environ_spec.lua @@ -10,7 +10,7 @@ describe("luasnip.util.environ", function() local Environ = require("luasnip.util.environ") %s - local env = Environ:new({0, 0}) + local env = Environ:new({pos={0, 0}, captures={}, trigger=""}) local result = env["%s"] return #(result) > 0 ]=]):format( @@ -30,7 +30,7 @@ describe("luasnip.util.environ", function() local Environ = require("luasnip.util.environ") %s - local env = Environ:new({0, 0}) + local env = Environ:new({pos={0, 0}, captures={}, trigger=""}) return env["%s"] ]=]):format( namespace_setup, @@ -48,7 +48,7 @@ describe("luasnip.util.environ", function() ([=[ local Environ = require("luasnip.util.environ") %s - local env = Environ:new({0, 0}) + local env = Environ:new({pos={0, 0}, captures={}, trigger=""}) return env["%s"] == nil ]=]):format( namespace_setup, @@ -71,7 +71,7 @@ describe("luasnip.util.environ", function() ([=[ local Environ = require("luasnip.util.environ") %s - local env = Environ:new({0, 0}) + local env = Environ:new({pos={0, 0}, captures={}, trigger=""}) return rawget(env, "%s") ~= nil ]=]):format( namespace_setup, @@ -144,9 +144,16 @@ describe("luasnip.util.environ", function() false, "VAR" ) + check( + "Init funtion old api", + [[Environ.env_namespace("OLD", {init=function(pos) return {POS = table.concat(pos, ',')} end})]], + "OLD_POS", + true, + "0,0" + ) check( "Init funtion", - [[Environ.env_namespace("IN", {init=function(pos) return {POS = table.concat(pos, ',')} end})]], + [[Environ.env_namespace("IN", {init=function(info) return {POS = table.concat(info.pos, ',')} end})]], "IN_POS", true, "0,0" @@ -180,4 +187,26 @@ describe("luasnip.util.environ", function() "Environ with multiline_vars incorrect type", [[Environ.env_namespace("TES_T", {var={A='s', multiline_vars = 9 }})]] ) + + local function check_builtin(var_name, test) + it("Test builtin " .. var_name, function() + assert.is_true( + exec_lua( + ([=[ + local Environ = require("luasnip.util.environ") + local env = Environ:new({pos={0, 0}, captures={"one"}, trigger="trigg"}) + local result = env["%s"] + local test = %s + return test(result) + ]=]):format( + var_name, + test + ) + ) + ) + end) + end + + check_builtin("LS_TRIGGER", [[function(r) return r == "trigg" end]]) + check_builtin("LS_CAPTURE_1", [[function(r) return r == "one" end]]) end)