Skip to content
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
36 changes: 31 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -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")
Expand All @@ -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",
})
```

Expand Down Expand Up @@ -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")
Expand All @@ -114,6 +124,22 @@ vim.print(env)

Quotes around values (`"foo"` or `'foo'`) are stripped automatically.

### `envfile.set_neovim_env(envs: table<string, string>) -> 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 |
Expand Down
32 changes: 27 additions & 5 deletions lua/nvim-dap-envfile.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -54,9 +60,11 @@ local M = {
config = default_config,
}

---@alias env_table table<string, string>

---@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
Expand All @@ -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<string,string>
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
Expand All @@ -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")
Expand All @@ -101,18 +120,21 @@ 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

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
Expand Down
13 changes: 7 additions & 6 deletions tests/nvim-dap-envfile/eval_vars_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
52 changes: 52 additions & 0 deletions tests/nvim-dap-envfile/set_neovim_env_spec.lua
Original file line number Diff line number Diff line change
@@ -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)