diff --git a/README.md b/README.md index 226f031..e0139a9 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,12 @@ local default_config = { ---Automatically adds a dap on_config listener. ---@type boolean? add_dap_listener = true, + ---Also sets environment variables using the Neovim API. Only works if + ---`add_dap_listener` is enabled. This is enabled by default since it adds no + ---overhead and can be helpful for certain DAP servers that don’t support the + ---`env` option. + ---@type boolean? + use_neovim_env = true, ---Print additional debug messages. Useful to check what your inputs are ---evaluating to. ---@type boolean? @@ -51,7 +57,9 @@ manually. ### Using `envFile` -Your project-local `.nvim.lua` or any other per-project configuration file. +Below is an example for Go, using your project-local `.nvim.lua` or any other +per-project configuration file. If you’re unfamiliar with such setups, see +`:help exrc`. ```lua local dap = require("dap") @@ -63,7 +71,9 @@ table.insert(dap.configurations.go, { timeout = 10000, outputMode = "remote", program = "main.go", - envFile = "${workspaceFolder}/.env", -- will be read, parsed, and passed as "env" table. + + -- will be read, parsed, and passed as "env" table. + envFile = "${workspaceFolder}/.env", }) ``` @@ -91,20 +101,20 @@ custom one), you can still use our helper functions local envfile = require("nvim-dap-envfile") ``` -### `envfile.eval_vars(str: string) -> string` +### `envfile.expand_path_vars(str: string) -> string` Expands VS Code–style variables inside a string. For available variables see below. ```lua -local resolved = envfile.eval_vars("${workspaceFolder}/.env") +local resolved = envfile.expand_path_vars("${workspaceFolder}/.env") -- e.g. "/home/user/project/.env" ``` ### `envfile.load_env_file(path: string) -> table` Parses a `.env` file and returns a Lua table of key/value pairs. Path is -evaluated using `eval_vars`. +evaluated using `expand_path_vars`. ```lua local env = envfile.load_env_file("${workspaceFolder}/.env") @@ -114,6 +124,22 @@ vim.print(env) Quotes around values (`"foo"` or `'foo'`) are stripped automatically. +### `envfile.set_neovim_env(envs: table) -> nil` + +Sets environment variables for the current Neovim session using the Neovim API +(`vim.env`). Each key/value pair in `envs` is added to `vim.env`, making the +variables available to shell commands, plugins, and other Lua code executed +within Neovim. + +```lua +envfile.set_neovim_env({ + DB_HOST = "localhost", + PORT = "8080", +}) +-- vim.env.DB_HOST == "localhost" +-- vim.env.PORT == "8080" +``` + ### Variable Expansion | Variable | Example Result | diff --git a/lua/nvim-dap-envfile.lua b/lua/nvim-dap-envfile.lua index f435583..e3af1c8 100644 --- a/lua/nvim-dap-envfile.lua +++ b/lua/nvim-dap-envfile.lua @@ -44,6 +44,12 @@ local default_config = { ---Automatically adds a dap on_config listener. ---@type boolean? add_dap_listener = true, + ---Also sets environment variables using the Neovim API. Only works if + ---`add_dap_listener` is enabled. This is enabled by default since it adds no + ---overhead and can be helpful for certain DAP servers that don’t support the + ---`env` option. + ---@type boolean? + use_neovim_env = true, ---Print additional debug messages. Useful to check what your inputs are ---evaluating to. ---@type boolean? @@ -54,9 +60,11 @@ local M = { config = default_config, } +---@alias env_table table + ---@param str string ---@return string str String with evaluated variables (if any) -function M.eval_vars(str) +function M.expand_path_vars(str) for pattern, replace_fn in pairs(replacements) do str = str:gsub(pattern, replace_fn) end @@ -66,15 +74,16 @@ end ---@param path string Path for .env file. Variables like ${file} are --- automatically evaluated. ----@return table env Table with env values, read from path. +---@return env_table env A key-value table of envs. function M.load_env_file(path) - local env = {} + local env = {} ---@type table local file = io.open(path, "r") if not file then return env end for line in file:lines() do + ---@type string, string local key, value = line:match("^%s*([%w_]+)%s*=%s*(.+)%s*$") if key and value then -- remove quotes @@ -86,6 +95,16 @@ function M.load_env_file(path) return env end +---@param envs env_table +function M.set_neovim_env(envs) + for k, v in pairs(envs) do + vim.env[k] = v + if M.config.debug or false then + vim.print("vim.env: set " .. k .. " to " .. v) + end + end +end + ---@param opts Config function M.setup(opts) local ok, dap = pcall(require, "dap") @@ -101,7 +120,7 @@ function M.setup(opts) local config = vim.deepcopy(original_config) if config.envFile then - local path = M.eval_vars(config.envFile) + local path = M.expand_path_vars(config.envFile) if M.config.debug then vim.print("env path: " .. path) end @@ -109,10 +128,13 @@ function M.setup(opts) if vim.fn.filereadable(path) == 1 then local env_vars = M.load_env_file(path) if M.config.debug then - vim.print("envs: " .. env_vars) + vim.print("envs: " .. vim.inspect(env_vars)) end config.env = vim.tbl_extend("force", config.env or {}, env_vars) + if M.config.use_neovim_env then + M.set_neovim_env(env_vars) + end else vim.notify("No .env file found: " .. path, vim.log.levels.WARN, notify_opts) end diff --git a/tests/nvim-dap-envfile/eval_vars_spec.lua b/tests/nvim-dap-envfile/eval_vars_spec.lua index b60ce16..8b26973 100644 --- a/tests/nvim-dap-envfile/eval_vars_spec.lua +++ b/tests/nvim-dap-envfile/eval_vars_spec.lua @@ -27,39 +27,40 @@ describe("eval_vars()", function() return "/home/user/project" end, } + ---@diagnostic disable-next-line: duplicate-set-field os.getenv = function(_) return "test" end end) it("replaces ${fileBasename}", function() - local result = M.eval_vars("File: ${fileBasename}") + local result = M.expand_path_vars("File: ${fileBasename}") assert.are.equal("File: file.lua", result) end) it("replaces ${fileBasenameNoExtension}", function() - local result = M.eval_vars("Name: ${fileBasenameNoExtension}") + local result = M.expand_path_vars("Name: ${fileBasenameNoExtension}") assert.are.equal("Name: file", result) end) it("replaces ${workspaceFolderBasename}", function() - local result = M.eval_vars("Project: ${workspaceFolderBasename}") + local result = M.expand_path_vars("Project: ${workspaceFolderBasename}") assert.are.equal("Project: project", result) end) it("replaces ${env:TEST_ENV}", function() - local result = M.eval_vars("${env:TEST_ENV}") + local result = M.expand_path_vars("${env:TEST_ENV}") assert.are.equal("test", result) end) it("handles multiple replacements", function() local input = "Dir: ${workspaceFolder}, File: ${fileBasename}" local expected = "Dir: /home/user/project, File: file.lua" - assert.are.equal(expected, M.eval_vars(input)) + assert.are.equal(expected, M.expand_path_vars(input)) end) it("returns same string if no variables", function() local input = "no variables here" - assert.are.equal(input, M.eval_vars(input)) + assert.are.equal(input, M.expand_path_vars(input)) end) end) diff --git a/tests/nvim-dap-envfile/set_neovim_env_spec.lua b/tests/nvim-dap-envfile/set_neovim_env_spec.lua new file mode 100644 index 0000000..4c6dc1a --- /dev/null +++ b/tests/nvim-dap-envfile/set_neovim_env_spec.lua @@ -0,0 +1,52 @@ +local M = require("nvim-dap-envfile") + +describe("set_neovim_env", function() + local original_env = {} + + before_each(function() + -- Backup and clear test vars + original_env.TEST_VAR1 = vim.env.TEST_VAR1 + original_env.TEST_VAR2 = vim.env.TEST_VAR2 + vim.env.TEST_VAR1 = nil + vim.env.TEST_VAR2 = nil + end) + + after_each(function() + -- Restore environment + vim.env.TEST_VAR1 = original_env.TEST_VAR1 + vim.env.TEST_VAR2 = original_env.TEST_VAR2 + end) + + it("sets environment variables in vim.env", function() + local envs = { + TEST_VAR1 = "hello", + TEST_VAR2 = "world", + } + + M.set_neovim_env(envs) + + assert.equal("hello", vim.env.TEST_VAR1) + assert.equal("world", vim.env.TEST_VAR2) + end) + + it("works with os.getenv", function() + local envs = { + TEST_VAR1 = "hello", + TEST_VAR2 = "world", + } + + M.set_neovim_env(envs) + + assert.equal("hello", os.getenv("TEST_VAR1")) + assert.equal("world", os.getenv("TEST_VAR2")) + end) + + it("overwrites existing environment variables", function() + vim.env.TEST_VAR1 = "old" + local envs = { TEST_VAR1 = "new" } + + M.set_neovim_env(envs) + + assert.equal("new", vim.env.TEST_VAR1) + end) +end)