Ecolog (ัะบะพะปะพะณ) - your environment guardian in Neovim. Named after the Russian word for "environmentalist", this plugin protects and manages your environment variables with the same care an ecologist shows for nature.
A Neovim plugin for seamless environment variable integration and management. Provides intelligent autocompletion, type checking, and value peeking for environment variables in your projects. All in one place.
Using lazy.nvim:
๐ก Quick Start: If you want to quickly start with ecolog.nvim, check out the author's personal setup section.
{
'sstba/ecolog.nvim',
-- Optional: you can add some keybindings
-- (I personally use lspsaga so check out lspsaga integration or lsp integration for a smoother experience without separate keybindings)
keys = {
{ '<leader>ge', '<cmd>EcologGoto<cr>', desc = 'Go to env file' },
{ '<leader>ep', '<cmd>EcologPeek<cr>', desc = 'Ecolog peek variable' },
{ '<leader>es', '<cmd>EcologSelect<cr>', desc = 'Switch env file' },
},
-- Lazy loading is done internally
lazy = false,
opts = {
integrations = {
-- WARNING: for both cmp integrations see readme section below
nvim_cmp = true, -- If you dont plan to use nvim_cmp set to false, enabled by default
-- If you are planning to use blink cmp uncomment this line
-- blink_cmp = true,
},
-- Enables shelter mode for sensitive values
shelter = {
configuration = {
-- Partial mode configuration:
-- false: completely mask values (default)
-- true: use default partial masking settings
-- table: customize partial masking
-- partial_mode = false,
-- or with custom settings:
partial_mode = {
show_start = 3, -- Show first 3 characters
show_end = 3, -- Show last 3 characters
min_mask = 3, -- Minimum masked characters
},
mask_char = "*", -- Character used for masking
mask_length = nil, -- Optional: fixed length for masked portion (defaults to value length)
skip_comments = false, -- Skip masking comment lines in environment files (default: false)
},
modules = {
cmp = true, -- Enabled to mask values in completion
peek = false, -- Enable to mask values in peek view
files = true, -- Enabled to mask values in file buffers
telescope = false, -- Enable to mask values in telescope integration
telescope_previewer = false, -- Enable to mask values in telescope preview buffers
fzf = false, -- Enable to mask values in fzf picker
fzf_previewer = false, -- Enable to mask values in fzf preview buffers
snacks_previewer = false, -- Enable to mask values in snacks previewer
snacks = false, -- Enable to mask values in snacks picker
}
},
-- true by default, enables built-in types (database_url, url, etc.)
types = true,
path = vim.fn.getcwd(), -- Path to search for .env files
preferred_environment = "development", -- Optional: prioritize specific env files
-- Controls how environment variables are extracted from code and how cmp works
provider_patterns = true, -- true by default, when false will not check provider patterns
},
}
To use the latest features and improvements, you can use the beta branch:
{
'sstba/ecolog.nvim',
branch = 'beta',
-- ... rest of your configuration
}
Even though beta branch may contain more experimental changes, new and shiny features will appear faster here. Consider using it as a contribution to the development of the main branch. Since you can share your feedback.
Setup auto-completion with nvim-cmp
:
require('cmp').setup({
sources = {
{ name = 'ecolog' },
-- your other sources...
},
})
If you use blink.cmp
see Blink-cmp Integration guide
๐ Advanced Environment Variable Management
๐ค Smart Autocompletion
๐ก๏ธ Enhanced Security Features
๐ Integrations
๐ Multi-Environment Support
๐ก Type System
๐จ UI/UX Features
Ecolog supports advanced variable interpolation with shell-like syntax in your environment files.
$VAR
or ${VAR}
${VAR:-default}
(use default if VAR is unset or empty)${VAR-alternate}
(use alternate if VAR is unset)$(command)
'...'
): No interpolation"..."
): With interpolation# Basic variable interpolation
APP_URL=${HOST}:${PORT}
# Default values
API_TIMEOUT=${TIMEOUT:-5000}
DB_HOST=${DATABASE_HOST:-localhost}
# Alternate values
CACHE_DIR=${CUSTOM_CACHE-/tmp/cache}
# Command substitution
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
TIMESTAMP=$(date +%Y%m%d)
# Nested interpolation
DATABASE_URL="postgres://${DB_USER:-postgres}:${DB_PASS}@${DB_HOST:-localhost}:${DB_PORT:-5432}/${DB_NAME}"
You can customize the interpolation behavior through the plugin's configuration:
require('ecolog').setup({
-- Enable interpolation with default settings
interpolation = true,
-- Or disable interpolation
interpolation = false,
-- Or customize interpolation settings
interpolation = {
enabled = true, -- Enable/disable interpolation
max_iterations = 10, -- Maximum iterations for nested interpolation
warn_on_undefined = true, -- Warn about undefined variables
fail_on_cmd_error = false, -- How to handle command substitution errors
features = {
variables = true, -- Enable variable interpolation ($VAR, ${VAR})
defaults = true, -- Enable default value syntax (${VAR:-default})
alternates = true, -- Enable alternate value syntax (${VAR-alternate})
commands = true, -- Enable command substitution ($(command))
escapes = true, -- Enable escape sequences (\n, \t, etc.)
}
}
})
The configuration options are:
Option | Type | Default | Description |
---|---|---|---|
enabled | boolean | false | Enable/disable interpolation |
max_iterations | number | 10 | Maximum iterations for nested variable interpolation |
warn_on_undefined | boolean | true | Whether to warn when undefined variables are referenced |
fail_on_cmd_error | boolean | false | Whether to error or warn on command substitution failures |
features | table | - | Control specific interpolation features |
features.variables | boolean | true | Enable variable interpolation ($VAR, ${VAR}) |
features.defaults | boolean | true | Enable default value syntax (${VAR:-default}) |
features.alternates | boolean | true | Enable alternate value syntax (${VAR-alternate}) |
features.commands | boolean | true | Enable command substitution ($(command)) |
features.escapes | boolean | true | Enable escape sequences (\n, \t, etc.) |
\n
, \t
, etc.)${}
for clarity and to avoid ambiguitycommands
in production to prevent command injectionalternates
and defaults
if not neededvariables
enabled for basic interpolationescapes
if not using special charactersEcolog provides intelligent environment variable detection and completion for multiple programming languages:
Language | File Extensions | Environment Variable Access Patterns |
---|---|---|
TypeScript | .ts, .tsx | process.env.VAR , process.env['VAR'] , import.meta.env.VAR , Bun.env.VAR , Deno.env.get('VAR') |
JavaScript | .js, .jsx | process.env.VAR , process.env['VAR'] , Bun.env.VAR |
Python | .py | os.environ.get('VAR') |
PHP | .php | getenv('VAR') , $_ENV['VAR'] , $_SERVER['VAR'] |
Lua | .lua | os.getenv('VAR') |
Go | .go | os.Getenv('VAR') |
Rust | .rs | env::var('VAR') , std::env::var('VAR') , std::env::var_os('VAR') |
Java | .java | System.getenv('VAR') , env.get('VAR') |
C# | .cs, .csharp | Environment.GetEnvironmentVariable('VAR') , System.Environment.GetEnvironmentVariable('VAR') |
Ruby | .rb | ENV['VAR'] , ENV.fetch('VAR') |
Shell | .sh, .bash, .zsh | $VAR , ${VAR} |
Kotlin | .kt, .kotlin | System.getenv('VAR') |
Dockerfile | Dockerfile | ${VAR} |
Each language provider is optimized for its specific environment variable access patterns and supports both completion and detection. The providers are loaded lazily to maintain performance.
You can add support for additional languages by registering custom providers. Each provider defines how environment variables are detected and extracted in specific file types.
require('ecolog').setup({
providers = {
{
-- Pattern to match environment variable access
pattern = "ENV%[['\"]%w['\"]%]",
-- Filetype(s) this provider supports (string or table)
filetype = "custom_lang",
-- Function to extract variable name from the line
extract_var = function(line, col)
local before_cursor = line:sub(1, col + 1)
return before_cursor:match("ENV%['\"['\"]%]$")
end,
-- Function to return completion trigger pattern
get_completion_trigger = function()
return "ENV['"
end
}
}
})
Each provider must specify:
pattern
: A Lua pattern to match environment variable access in the codefiletype
: The filetype(s) this provider supports (string or table)extract_var
: Function to extract the variable name from the lineget_completion_trigger
: Function to return the completion trigger patternThe provider will be automatically loaded when editing files of the specified filetype.
Command | Description |
---|---|
:EcologPeek [variable_name] |
Peek at environment variable value and metadata |
:EcologPeek |
Peek at environment variable under cursor |
:EcologRefresh |
Refresh environment variable cache |
:EcologSelect |
Open a selection window to choose environment file |
:EcologSelect [file_path] |
Directly load a specific environment file without selection prompt |
:EcologGoto |
Open selected environment file in buffer |
:EcologGotoVar |
Go to specific variable definition in env file |
:EcologGotoVar [variable_name] |
Go to specific variable definition in env file with variable under cursor |
:EcologShelterToggle [command] [feature] |
Control shelter mode for masking sensitive values |
:EcologShelterLinePeek |
Temporarily reveal value on current line in env file |
:Telescope ecolog env |
Alternative way to open Telescope picker |
:EcologFzf |
Alternative way to open fzf-lua picker (must have fzf-lua installed) |
:EcologSnacks |
Open environment variables picker using snacks.nvim (must have snacks.nvim installed) |
:EcologEnvGet |
Get the value of a specific environment variable |
:EcologEnvSet |
Sets the value of a specified environment variable |
:EcologCopy [variable_name] |
Copy raw value of environment variable to clipboard |
:EcologCopy |
Copy raw value of environment variable under cursor to clipboard |
:EcologAWSConfig |
Open configuration menu for AWS Secrets Manager (region, profile, secrets) |
:EcologVaultConfig |
Open configuration menu for HCP Vault (organization, project, apps) |
:EcologInterpolationToggle |
Toggle environment variable interpolation on/off |
:EcologShellToggle |
Loads and unloads shell variables |
Files are loaded in the following priority order:
.env.{preferred_environment}
(if preferred_environment is set).env
.env.*
files (alphabetically)Ecolog can load environment variables directly from your shell environment. This is useful when you want to:
Enable shell variable loading with default settings:
require('ecolog').setup({
load_shell = true
})
For more control over shell variable handling:
require('ecolog').setup({
load_shell = {
enabled = true, -- Enable shell variable loading
override = false, -- When false, .env files take precedence over shell variables
-- Optional: filter specific shell variables
filter = function(key, value)
-- Example: only load specific variables
return key:match("^(PATH|HOME|USER)$") ~= nil
end,
-- Optional: transform shell variables before loading
transform = function(key, value)
-- Example: prefix shell variables for clarity
return "[shell] " .. value
end
}
})
Option | Type | Default | Description |
---|---|---|---|
enabled |
boolean | false |
Enable/disable shell variable loading |
override |
boolean | false |
When true, shell variables take precedence over .env files |
filter |
function|nil | nil |
Optional function to filter which shell variables to load |
transform |
function|nil | nil |
Optional function to transform shell variable values |
filter
to limit which shell variables are loaded to avoid clutteringtransform
to clearly mark shell-sourced variablesoverride
setting when working with both shell and .env variablesEcolog can automatically sync your environment variables with Neovim's built-in vim.env
table, making them available to any Neovim process or plugin.
Enable vim.env module in your setup:
{
vim_env = true, -- false by default
}
vim.env
vim.env
in real-time when environment files change-- In your config
require('ecolog').setup({
vim_env = true,
-- ... other options
})
-- After setup, variables from your .env file will be available in vim.env:
print(vim.env.DATABASE_URL) -- prints your database URL
print(vim.env.API_KEY) -- prints your API key
The provider_patterns
option controls how environment variables are extracted from your code and how completion works. It can be configured in two ways:
As a boolean (for backward compatibility):
provider_patterns = true -- Enables both extraction and completion with language patterns
-- or
provider_patterns = false -- Disables both, falls back to word under cursor and basic completion
As a table for fine-grained control:
provider_patterns = {
extract = true, -- Controls variable extraction from code
cmp = true -- Controls completion behavior
}
The extract
field controls how variables are extracted from code for features like peek, goto definition, etc:
When true
(default): Only recognizes environment variables through language-specific patterns
process.env.MY_VAR
or import.meta.env.MY_VAR
os.environ.get('MY_VAR')
or os.environ['MY_VAR']
When false
: Falls back to the word under cursor if no language provider matches
The cmp
field controls how completion behaves:
When true
(default):
process.env.
in JavaScript)When false
:
Default behavior (strict mode):
provider_patterns = {
extract = true, -- Only extract vars from language patterns
cmp = true -- Only complete in valid contexts
}
Flexible extraction, strict completion:
provider_patterns = {
extract = false, -- Extract any word as potential var
cmp = true -- Only complete in valid contexts
}
Strict extraction, flexible completion:
provider_patterns = {
extract = true, -- Only extract vars from language patterns
cmp = false -- Complete anywhere
}
Maximum flexibility:
provider_patterns = {
extract = false, -- Extract any word as potential var
cmp = false -- Complete anywhere
}
This affects all features that extract variables from code (peek, goto definition, etc.) and how completion behaves.
Ecolog supports custom patterns for matching environment files. This allows you to define your own naming conventions and directory structures beyond the default .env*
pattern.
Set a single custom pattern:
require('ecolog').setup({
env_file_patterns = { "config/env.*" } -- Matches env.any_here file in the config directory
})
Use multiple patterns:
require('ecolog').setup({
env_file_patterns = {
"config/.env", -- Matches .env file in config directory
"config/.env.*", -- Matches any .env.* file in config directory
"environments/*" -- Matches any file in environments directory
}
})
*
matches any characters)path
option).env
, .envrc
, .env.*
) are used if no custom patterns are specifiedconfig/.env
, environments/.env.*
).*
to match any extension (e.g., .env.*
matches .env.development
, .env.test
)*
to match any characters (e.g., config/*
matches any file in the config directory)-- Match specific environments in config directory
env_file_patterns = {
"config/.env.development",
"config/.env.production"
}
-- Match all env files in multiple directories
env_file_patterns = {
"config/env/*",
"environments/*",
".env*"
}
-- Match specific naming convention
env_file_patterns = {
"env.*.config",
"env.*.local"
}
If no custom patterns are specified, Ecolog uses these default patterns:
.env
- Main environment file.envrc
- Shell environment file.env.*
- Environment-specific files (e.g., .env.development
, .env.test
)Ecolog provides two types of sort functions to customize how both environment files and variables are ordered in various interfaces:
sort_file_fn
): Controls how environment files are prioritized and orderedsort_var_fn
): Controls how environment variables are sorted in completion, pickers, and other interfacessort_file_fn
)Ecolog allows you to customize how environment files are sorted using the sort_file_fn
option (previously named sort_fn
). This is useful when you need specific ordering beyond the default alphabetical sorting.
require('ecolog').setup({
sort_file_fn = function(a, b)
-- Sort by file size (smaller files first)
local a_size = vim.fn.getfsize(a)
local b_size = vim.fn.getfsize(b)
return a_size < b_size
end
})
sort_file_fn = function(a, b)
local priority = {
[".env.production"] = 1,
[".env.staging"] = 2,
[".env.development"] = 3,
[".env"] = 4
}
local a_name = vim.fn.fnamemodify(a, ":t")
local b_name = vim.fn.fnamemodify(b, ":t")
return (priority[a_name] or 99) < (priority[b_name] or 99)
end
sort_file_fn = function(a, b)
local a_time = vim.fn.getftime(a)
local b_time = vim.fn.getftime(b)
return a_time > b_time -- Most recently modified first
end
sort_file_fn = function(a, b)
-- Extract environment type from filename
local function get_env_type(file)
local name = vim.fn.fnamemodify(file, ":t")
return name:match("^%.env%.(.+)$") or ""
end
return get_env_type(a) < get_env_type(b)
end
sort_var_fn
)The sort_var_fn
option allows you to customize how environment variables are sorted in various interfaces (completion, pickers, etc.).
require('ecolog').setup({
sort_var_fn = function(a, b)
-- Sort by variable name length (shorter names first)
return #a.name < #b.name
end
})
sort_var_fn = function(a, b)
-- Sort variables with types before those without
if a.type and not b.type then
return true
elseif not a.type and b.type then
return false
end
-- Then sort alphabetically by name
return a.name < b.name
end
sort_var_fn = function(a, b)
local priority = {
[".env.local"] = 1,
[".env"] = 2,
["shell"] = 3
}
local a_priority = priority[a.source] or 99
local b_priority = priority[b.source] or 99
return a_priority < b_priority
end
sort_var_fn = function(a, b)
-- Sort API keys first, then database variables, then others
local function get_category(var)
if var.name:match("_KEY$") then return 1
elseif var.name:match("^DB_") then return 2
else return 3 end
end
local a_cat = get_category(a)
local b_cat = get_category(b)
if a_cat ~= b_cat then
return a_cat < b_cat
end
return a.name < b.name
end
Add ecolog
to your nvim-cmp sources:
require('cmp').setup({
sources = {
{ name = 'ecolog' },
-- your other sources...
},
})
Nvim-cmp integration is enabled by default. To disable it:
require('ecolog').setup({
integrations = {
nvim_cmp = false,
},
})
See Currently Supported Languages for available completion triggers and Custom Providers for adding support for additional languages.
PS: When blink_cmp is enabled, nvim_cmp is disabled by default.
Ecolog provides an integration with blink.cmp for environment variable completions. To enable it:
require('ecolog').setup({
integrations = {
blink_cmp = true,
},
})
{
"saghen/blink.cmp",
opts = {
sources = {
default = { 'ecolog', 'lsp', 'path', 'snippets', 'buffer' },
providers = {
ecolog = { name = 'ecolog', module = 'ecolog.integrations.cmp.blink_cmp' },
},
},
},
}
See Currently Supported Languages for available completion triggers and Custom Providers for adding support for additional languages.
Ecolog provides a built-in omnifunc integration that enables environment variable completion using Vim's native completion system (<C-x><C-o>
). This is particularly useful if you prefer not to use nvim-cmp or blink-cmp, or want a lightweight completion option.
The omnifunc integration is disabled by default. To enable it:
require('ecolog').setup({
integrations = {
omnifunc = true, -- Enable omnifunc integration with automatic setup (default)
-- Or with configuration options:
omnifunc = {
auto_setup = false, -- Disable automatic setup, allowing manual configuration
},
},
})
When enabled with auto_setup = true
(the default), Ecolog will automatically set itself as the omnifunc provider for filetypes that don't already have one configured.
process.env.
for JavaScript)<C-x><C-o>
to trigger omni completion<C-n>
and <C-p>
<C-w><C-z>
to close it manually<Enter>
to select a completionThe preview window will show information in this format:
VARIABLE_NAME [type] = value # comment (if any)
The omnifunc integration respects shelter mode settings. When shelter mode is enabled for cmp:
require('ecolog').setup({
shelter = {
modules = {
cmp = true, -- Enable shelter mode for all completion interfaces including omnifunc
}
}
})
Variable values will be masked in the completion menu according to your shelter mode configuration. Note that this setting affects all completion interfaces (nvim-cmp, blink-cmp, and omnifunc) since they share the same completion infrastructure.
If you prefer to have full control over where and when the omnifunc is set, you can disable automatic setup with auto_setup = false
and configure it manually:
-- In your configuration, for specific filetypes:
vim.api.nvim_create_autocmd("FileType", {
pattern = { "javascript", "typescript", "python" }, -- Add your desired filetypes
callback = function()
vim.bo.omnifunc = "v:lua.require'ecolog.integrations.cmp.omnifunc'.complete"
end,
})
-- Or for a specific buffer:
vim.bo.omnifunc = "v:lua.require'ecolog.integrations.cmp.omnifunc'.complete"
This gives you full control over when and where Ecolog's environment variable completion is available. The automatic setup only applies to filetypes without existing omnifunc settings, ensuring it doesn't override your existing configurations.
โ ๏ธ Warning: The LSP integration is currently experimental and may interfere with your existing LSP setup. Use with caution.
Ecolog provides optional LSP integration that enhances the hover and definition functionality for environment variables. When enabled, it will:
meaning you dont need any custom keymaps
To enable LSP integration, add this to your Neovim configuration:
require('ecolog').setup({
integrations = {
lsp = true,
}
})
PS: If you're using lspsaga, please see section LSP Saga Integration don't use lsp integration use one or the other.
If you experience any issues, you can disable the LSP integration:
require('ecolog').setup({
integrations = {
lsp = false,
}
})
Please report such issues on our GitHub repository
Ecolog provides integration with lspsaga.nvim that enhances hover and goto-definition functionality for environment variables while preserving Saga's features for other code elements.
To enable LSP Saga integration, add this to your configuration:
require('ecolog').setup({
integrations = {
lspsaga = true,
}
})
PS: If you're using lspsaga then don't use lsp integration use one or the other.
The integration adds two commands that intelligently handle both environment variables and regular code:
EcologSagaHover:
EcologSagaGD (Goto Definition):
๐ก Note: When enabled, the integration automatically detects and updates your existing Lspsaga keymaps to use Ecolog's enhanced functionality. No manual keymap configuration required!
{
'sstba/ecolog.nvim',
opts = {
integrations = {
lspsaga = true,
}
},
}
๐ก Note: The LSP Saga integration provides a smoother experience than the experimental LSP integration if you're already using Saga in your setup.
First, load the extension:
require('telescope').load_extension('ecolog')
Then configure it in your Telescope setup (optional):
require('telescope').setup({
extensions = {
ecolog = {
shelter = {
-- Whether to show masked values when copying to clipboard
mask_on_copy = false,
},
-- Default keybindings
mappings = {
-- Key to copy value to clipboard
copy_value = "<C-y>",
-- Key to copy name to clipboard
copy_name = "<C-n>",
-- Key to append value to buffer
append_value = "<C-a>",
-- Key to append name to buffer (defaults to <CR>)
append_name = "<CR>",
-- Key to edit environment variable
edit_var = "<C-e>",
},
}
}
})
Ecolog integrates with fzf-lua to provide a fuzzy finder interface for environment variables.
require('ecolog').setup({
integrations = {
fzf = {
shelter = {
mask_on_copy = false, -- Whether to mask values when copying
},
mappings = {
copy_value = "ctrl-y", -- Copy variable value to clipboard
copy_name = "ctrl-n", -- Copy variable name to clipboard
append_value = "ctrl-a", -- Append value at cursor position
append_name = "enter", -- Append name at cursor position
edit_var = "ctrl-e", -- Edit environment variable
},
}
}
})
You can trigger the FZF picker using :EcologFzf
command.
Open the environment variables picker:
:EcologFzf
Key | Action |
---|---|
<Enter> |
Insert variable name |
<C-y> |
Copy value to clipboard |
<C-n> |
Copy name to clipboard |
<C-a> |
Append value to buffer |
<C-e> |
Edit variable value |
All keymaps are customizable through the configuration.
Ecolog integrates with snacks.nvim to provide a modern and beautiful picker interface for environment variables.
require('ecolog').setup({
integrations = {
snacks = {
shelter = {
mask_on_copy = false, -- Whether to mask values when copying
},
keys = {
copy_value = "<C-y>", -- Copy variable value to clipboard
copy_name = "<C-u>", -- Copy variable name to clipboard
append_value = "<C-a>", -- Append value at cursor position
append_name = "<CR>", -- Append name at cursor position
edit_var = "<C-e>", -- Edit environment variable
},
layout = { -- Any Snacks layout configuration
preset = "dropdown",
preview = false,
},
}
}
})
You can trigger the Snacks picker using :EcologSnacks
command.
Open the environment variables picker:
:EcologSnacks
Key | Action |
---|---|
<CR> |
Insert variable name |
<C-y> |
Copy value to clipboard |
<C-u> |
Copy name to clipboard |
<C-a> |
Append value to buffer |
<C-e> |
Edit variable value |
All keymaps are customizable through the configuration.
All picker integrations (Telescope, FZF, and Snacks) support custom actions. This allows you to define your own key mappings and actions to perform on environment variables.
You can add custom actions in your Neovim configuration when setting up Ecolog:
require("ecolog").setup({
-- Your other configuration options
integrations = {
telescope = {
custom_actions = {
-- Define a custom action that appends the variable with special formatting
format_var = {
key = "<C-f>",
callback = function(item, picker)
-- item contains: name, value, masked_value, source, type
return "${" .. item.name .. "}"
end,
opts = {
close_on_action = true, -- Close the picker after action
notify = true, -- Show notification after action
message = "Formatted variable appended" -- Custom notification message
}
}
}
},
fzf = {
custom_actions = {
-- Similar structure for FZF custom actions
}
},
snacks = {
custom_actions = {
-- Similar structure for Snacks custom actions
}
}
}
})
You can also add custom actions after Ecolog has been set up:
-- For Telescope
require("telescope").extensions.ecolog.add_action(
"format_var", -- Action name
"<C-f>", -- Key mapping
function(item, picker) -- Callback function
return "${" .. item.name .. "}"
end,
{ -- Options
close_on_action = true,
notify = true,
message = "Formatted variable appended"
}
)
-- For FZF
require("ecolog.integrations.fzf").add_action("format_var", "ctrl-f", function(item, picker)
return "${" .. item.name .. "}"
end, { notify = true })
-- For Snacks
require("ecolog.integrations.snacks").add_action("format_var", "<C-f>", function(item, picker)
return "${" .. item.name .. "}"
end, { close_on_action = true })
name
: A unique name for the actionkey
: A string or table of strings representing the key mappingscallback
: A function that receives:item
: The selected environment variable datapicker
: The picker instanceopts
: Options table with:close_on_action
: Whether to close the picker after action (default: true)notify
: Whether to show a notification after action (default: true)message
: Custom notification messageThe item
parameter contains the following fields:
name
: The environment variable namevalue
: The actual valuemasked_value
: The masked value (if shelter.mask_on_copy is enabled)source
: The source of the variabletype
: The type of the variableEcolog provides a built-in statusline component that shows your current environment file, variable count, and shelter mode status. It supports both native statusline and lualine integration.
require('ecolog').setup({
integrations = {
statusline = {
hidden_mode = false, -- Hide when no env file is loaded
icons = {
enabled = true, -- Enable icons in statusline
env = "๐ฒ", -- Icon for environment file
shelter = "๐ก๏ธ", -- Icon for shelter mode
},
format = {
env_file = function(name)
return name -- Format environment file name
end,
vars_count = function(count)
return string.format("%d vars", count) -- Format variables count
end,
},
highlights = {
enabled = true, -- Enable custom highlights
env_file = "Directory", -- Highlight group for file name
vars_count = "Number", -- Highlight group for vars count
icons = "Special"
},
}
}
})
Add to your statusline:
vim.o.statusline = "%{%v:lua.require'ecolog'.get_status()%}"
Add to your lualine config:
require('lualine').setup({
sections = {
lualine_x = {
require('ecolog').get_lualine,
}
}
})
The statusline integration supports customizable highlighting with both highlight group names and hex color codes:
require('ecolog').setup({
integrations = {
statusline = {
highlights = {
enabled = true,
-- Using highlight groups
env_file = "Directory", -- Highlight group for file name
vars_count = "Number", -- Highlight group for vars count
icons = "Special", -- Highlight group for icons
-- OR using hex color codes
env_file = "#7FBBB3", -- Hex color for file name
vars_count = "#A7C080", -- Hex color for vars count
icons = "#ED8796", -- Hex color for icons
-- OR different highlights for env and shelter icons
icons = {
env = "String", -- Highlight group for env icon
shelter = "WarningMsg" -- Highlight group for shelter icon
},
-- OR with hex colors
icons = {
env = "#83C092", -- Hex color for env icon
shelter = "#E67E80" -- Hex color for shelter icon
}
},
}
}
})
The highlighting system automatically detects the format and applies the appropriate highlighting:
Both the native statusline and lualine integration fully support these highlighting options for all elements (file name, variable count, and icons), ensuring a consistent appearance across different statusline implementations.
The AWS Secrets Manager integration allows you to load secrets from AWS Secrets Manager into your environment variables. This integration requires the AWS CLI to be installed and configured with appropriate credentials.
โ ๏ธ Note: This is a WIP feature and may have breaking changes in future releases.
require('ecolog').setup({
integrations = {
secret_managers = {
aws = {
enabled = true, -- Enable AWS Secrets Manager integration
override = false, -- When true, AWS secrets take precedence over .env files and shell variables
region = "us-west-2", -- Required: AWS region where your secrets are stored
profile = "default", -- Optional: AWS profile to use
secrets = { -- Optional: List of secret names to fetch on startup
"my-app/dev/database",
"my-app/dev/api"
},
filter = function(key, value) -- Optional: Filter function for secrets
return true -- Return true to include the secret, false to exclude it
end,
transform = function(key, value) -- Optional: Transform function for secret values
return value -- Return the transformed value
end
}
}
}
})
:EcologAWSConfig
commandThe :EcologAWSConfig
command provides access to three main configuration options:
Each option can be accessed directly using:
:EcologAWSConfig region " Configure AWS region
:EcologAWSConfig profile " Configure AWS profile
:EcologAWSConfig secrets " Configure which secrets to load
Default keybindings in the configuration UI:
Key | Action |
---|---|
j /k |
Navigate through options |
<space> |
Toggle selection (for multi-select) |
<CR> |
Select option or confirm selection |
q /ESC |
Close without changes |
aws --version
should show version 2.x.x)aws configure
to set up credentialsaws sso login
)The integration provides clear error messages for common issues:
The HashiCorp Vault Secrets integration allows you to load secrets from HCP Vault Secrets into your environment variables. This integration requires the HCP CLI to be installed and configured with appropriate credentials.
โ ๏ธ Note: This is a WIP feature and may have breaking changes in future releases.
require('ecolog').setup({
integrations = {
secret_managers = {
vault = {
enabled = true, -- Enable HCP Vault Secrets integration
override = false, -- When true, Vault secrets take precedence over .env files and shell variables
apps = { -- Optional: List of application names to fetch secrets from by default
"sample-app",
"database"
},
filter = function(key, value) -- Optional: Filter function for secrets
return true -- Return true to include the secret, false to exclude it
end,
transform = function(key, value) -- Optional: Transform function for secret values
return value -- Return the transformed value
end
}
}
}
})
:EcologVaultConfig
commandThe :EcologVaultConfig
command provides access to three main configuration options:
Each option can be accessed directly using:
:EcologVaultConfig organization " Configure HCP organization
:EcologVaultConfig project " Configure HCP project
:EcologVaultConfig apps " Configure which apps to load secrets from
Default keybindings in the configuration UI:
Key | Action |
---|---|
j /k |
Navigate through options |
<space> |
Toggle selection (for multi-select) |
<CR> |
Select option or confirm selection |
q /ESC |
Close without changes |
hcp --version
should be available)hcp auth login
commandThe integration provides clear error messages for common issues:
ecolog.nvim
integrates with various file pickers to provide a secure way to use file picker without leaking sensitive data, when searching for files.
Configuration:
require('ecolog').setup({
shelter = {
modules = {
telescope_previewer = true, -- Mask values in telescope preview buffers
}
}
})
Configuration:
require('ecolog').setup({
shelter = {
modules = {
fzf_previewer = true, -- Mask values in fzf preview buffers
}
}
})
Configuration:
require('ecolog').setup({
shelter = {
modules = {
snacks_previewer = true, -- Mask values in snacks previewer
}
}
})
Shelter mode provides a secure way to work with sensitive environment variables by masking their values in different contexts. This feature helps prevent accidental exposure of sensitive data like API keys, passwords, tokens, and other credentials.
require('ecolog').setup({
shelter = {
configuration = {
-- Partial mode configuration:
-- false: completely mask values (default)
-- true: use default partial masking settings
-- table: customize partial masking
-- partial_mode = false,
-- or with custom settings:
partial_mode = {
show_start = 3, -- Show first 3 characters
show_end = 3, -- Show last 3 characters
min_mask = 3, -- Minimum masked characters
},
mask_char = "*", -- Character used for masking
mask_length = nil, -- Optional: fixed length for masked portion (defaults to value length)
skip_comments = false, -- Skip masking comment lines in environment files (default: false)
},
modules = {
cmp = false, -- Mask values in completion
peek = false, -- Mask values in peek view
files = false, -- Mask values in files
telescope = false, -- Mask values in telescope integration
telescope_previewer = false, -- Mask values in telescope preview buffers
fzf = false, -- Mask values in fzf picker
fzf_previewer = false, -- Mask values in fzf preview buffers
snacks = false, -- Mask values in snacks picker
snacks_previewer = false, -- Mask values in snacks previewer
}
},
path = vim.fn.getcwd(), -- Path to search for .env files
preferred_environment = "development", -- Optional: prioritize specific env files
})
Completion Menu (cmp = true
)
Peek View (peek = true
)
File View (files = true
)
:EcologShelterLinePeek
to temporarily reveal valuesTelescope Preview (telescope_previewer = true
)
.env
file previewed in telescope with support of custom env file patternsFZF Preview (fzf_previewer = true
)
.env
file previewed in fzf-lua with support of custom env file patternsFZF Picker (fzf = true
)
Telescope Integration (telescope = true
)
Snacks Integration (snacks = true
, snacks_previewer = true
)
Three modes of operation:
Full Masking (Default)
partial_mode = false
-- Example: "my-secret-key" -> "************"
Default Partial Masking
partial_mode = true
-- Example: "my-secret-key" -> "my-***-key"
Custom Partial Masking
partial_mode = {
show_start = 4, -- Show more start characters
show_end = 2, -- Show fewer end characters
min_mask = 3, -- Minimum mask length
}
-- Example: "my-secret-key" -> "my-s***ey"
You can control the length of masked portions using the mask_length
option:
Full Masking Mode:
shelter = {
configuration = {
partial_mode = false,
mask_length = 5 -- All masked values will be 5 characters long
}
}
-- Examples:
-- "my-secret-key" -> "*****" -- Long value becomes 5 chars
-- "short" -> "*****" -- Short value padded to 5 chars
-- "very-long-secret" -> "*****" -- Long value truncated to 5 chars
Partial Masking Mode:
shelter = {
configuration = {
partial_mode = true,
mask_length = 5 -- Masked portion will be 5 characters
}
}
-- Example: "my-secret-key" -> "my-*****-key"
-- Example: "short-key" -> "sho*****key"
When mask_length
is not set (nil), the masked portion will match the length of the original value.
:EcologShelterToggle
provides flexible control over shelter mode:
Basic Usage:
:EcologShelterToggle " Toggle between all-off and initial settings
Global Control:
:EcologShelterToggle enable " Enable all shelter modes
:EcologShelterToggle disable " Disable all shelter modes
Feature-Specific Control:
:EcologShelterToggle enable cmp " Enable shelter for completion only
:EcologShelterToggle disable peek " Disable shelter for peek only
:EcologShelterToggle enable files " Enable shelter for file display
Quick Value Reveal:
:EcologShelterLinePeek " Temporarily reveal value on current line
Original .env
file:
# Authentication
JWT_SECRET=my-super-secret-key
AUTH_TOKEN="bearer 1234567890"
# Database Configuration
DB_HOST=localhost
DB_USER=admin
DB_PASS=secure_password123
With full masking (partial_mode = false):
# Authentication
JWT_SECRET=********************
AUTH_TOKEN=******************
# Database Configuration
DB_HOST=*********
DB_USER=*****
DB_PASS=******************
With default settings (show_start=3, show_end=3, min_mask=3):
"mysecretkey" -> "mys***key" # Enough space for min_mask (3) characters
"secret" -> "******" # Not enough space for min_mask between shown parts
"api_key" -> "*******" # Would only have 1 char for masking, less than min_mask
"very_long_key" -> "ver*****key" # Plenty of space for masking
The min_mask setting ensures that sensitive values are properly protected by requiring a minimum number of masked characters between the visible parts. If this minimum cannot be met, the entire value is masked for security.
The files module can be configured in two ways:
shelter = {
modules = {
files = true -- Simply enable/disable files module
}
}
shelter = {
modules = {
files = {
shelter_on_leave = false, -- Control automatic re-enabling of shelter when leaving buffer
disable_cmp = true, -- Disable completion in sheltered buffers (default: true)
skip_comments = false, -- Skip masking comment lines in environment files (default: false)
}
},
configuration = {
skip_comments = false, -- Skip masking comment lines in environment files (default: false)
}
}
When shelter_on_leave
is enabled (default when using boolean configuration), the shelter mode will automatically re-enable itself when you leave an environment file buffer. This provides an extra layer of security by ensuring sensitive data is always masked when not actively being viewed.
The disable_cmp
option (enabled by default) will automatically disable both nvim-cmp and blink-cmp completions in sheltered buffers. This prevents sensitive values from being exposed through the completion menu while editing environment files. Completion is automatically re-enabled when unsheltering the buffer.
The skip_comments
option (disabled by default) allows you to keep comments visible while masking the actual environment variable values. This can be useful when you want to maintain readability of documentation in your environment files while still protecting sensitive data.
Ecolog now intelligently masks key-value pairs found in comments. This provides additional security for comments that might contain sensitive information. For example:
# Configuration for testing - api_key=MY_SECRET_KEY user=admin
DB_URL=postgres://user:password@localhost:5432/db
With comment masking enabled (the default), any key-value pairs in comments will also be masked, providing more comprehensive protection. This feature works across:
You can define different masking rules based on variable names or file sources:
shelter = {
configuration = {
-- Pattern-based rules take precedence
patterns = {
["*_KEY"] = "full", -- Always fully mask API keys
["TEST_*"] = "none", -- Never mask test variables
},
-- Source-based rules as fallback
sources = {
[".env.*"] = "full",
[".env.local"] = "none",
["shell"] = "none",
},
}
}
Custom Mask Character:
shelter = {
configuration = {
mask_char = "โข" -- Use dots
}
}
-- or
shelter = {
configuration = {
mask_char = "โ" -- Use blocks
}
}
Custom Highlighting:
shelter = {
configuration = {
highlight_group = "NonText" -- Use a different highlight group for masked values
}
}
Ecolog includes a flexible type system for environment variables with built-in and custom types.
Configure types through the types
option in setup:
require('ecolog').setup({
custom_types = {
semver = {
pattern = "^v?%d+%.%d+%.%d+%-?[%w]*$",
validate = function(value)
local major, minor, patch = value:match("^v?(%d+)%.(%d+)%.(%d+)")
return major and minor and patch
end,
},
aws_region = {
pattern = "^[a-z]{2}%-[a-z]+%-[0-9]$",
validate = function(value)
local valid_regions = {
["us-east-1"] = true,
["us-west-2"] = true,
-- ... etc
}
return valid_regions[value] == true
end
}
},
types = {
-- Built-in types
url = true, -- URLs (http/https)
localhost = true, -- Localhost URLs
ipv4 = true, -- IPv4 addresses
database_url = true, -- Database connection strings
number = true, -- Integers and decimals
boolean = true, -- true/false/yes/no/1/0
json = true, -- JSON objects and arrays
iso_date = true, -- ISO 8601 dates (YYYY-MM-DD)
iso_time = true, -- ISO 8601 times (HH:MM:SS)
hex_color = true, -- Hex color codes (#RGB or #RRGGBB)
}
})
You can also:
types = true
types = false
require('ecolog').setup({
custom_types = {
jwt = {
pattern = "^[A-Za-z0-9%-_]+%.[A-Za-z0-9%-_]+%.[A-Za-z0-9%-_]+$",
validate = function(value)
local parts = vim.split(value, ".", { plain = true })
return #parts == 3
end
},
}
types = {
url = true,
number = true,
}
})
Each custom type requires:
pattern
(required): A Lua pattern string for initial matchingvalidate
(optional): A function for additional validationtransform
(optional): A function to transform the valueExample usage in .env files:
VERSION=v1.2.3 # Will be detected as semver type
REGION=us-east-1 # Will be detected as aws_region type
AUTH_TOKEN=eyJhbG.eyJzd.iOiJ # Will be detected as jwt type
Selective Protection: Enable shelter mode only for sensitive environments:
-- In your config
if vim.fn.getcwd():match("production") then
require('ecolog').setup({
shelter = {
configuration = {
partial_mode = {
show_start = 3, -- Number of characters to show at start
show_end = 3, -- Number of characters to show at end
min_mask = 3, -- Minimum number of mask characters
},
mask_char = "*", -- Character used for masking
-- Mask all values from production files
sources = {
[".env.prod"] = "full",
[".env.local"] = "partial",
["shell"] = "none",
},
},
modules = {
cmp = true, -- Mask values in completion
peek = true, -- Mask values in peek view
files = true, -- Mask values in files
telescope = false, -- Mask values in telescope
telescope_previewer = false, -- Mask values in telescope preview buffers
}
},
path = vim.fn.getcwd(), -- Path to search for .env files
preferred_environment = "development", -- Optional: prioritize specific env files
})
end
Source-based Protection: Use different masking levels based on file sources:
shelter = {
configuration = {
-- Mask values based on their source file
sources = {
[".env.prod"] = "full",
[".env.local"] = "partial",
["shell"] = "none",
},
-- Pattern-based rules take precedence
patterns = {
["*_KEY"] = "full", -- Always fully mask API keys
["TEST_*"] = "none", -- Never mask test variables
},
}
}
Custom Masking: Use different characters for masking:
shelter = {
configuration = {
mask_char = "โข" -- Use dots
}
}
-- or
shelter = {
configuration = {
mask_char = "โ" -- Use blocks
}
}
-- or
shelter = {
configuration = {
highlight_group = "NonText" -- Use a different highlight group for masked values
}
}
The highlight_group
option allows you to customize the highlight group used for masked values. By default, it uses the Comment
highlight group. You can use any valid Neovim highlight group name.
Temporary Viewing: Use :EcologShelterToggle disable
temporarily when you need to view values, then re-enable with :EcologShelterToggle enable
Security Best Practices:
The plugin seamlessly integrates with your current colorscheme:
Element | Color Source |
---|---|
Variable names | Identifier |
Types | Type |
Values | String |
Sources | Directory |
It's author's (sstba
) personal setup for ecolog.nvim, it is opionated. However, it usefull to quickly get started especially if you don't want to think much of a setup and reading docs:
Note: Additional setup is required for blink-cmp and statusline integrations.
{
'sstba/ecolog.nvim',
keys = {
{ '<leader>el', '<Cmd>EcologShelterLinePeek<cr>', desc = 'Ecolog peek line' },
{ '<leader>eh', '<Cmd>EcologShellToggle<cr>', desc = 'Toggle shell variables' },
{ '<leader>ei', '<Cmd>EcologInterpolationToggle<cr>', desc = 'Toggle shell variables' },
{ '<leader>ge', '<cmd>EcologGoto<cr>', desc = 'Go to env file' },
{ '<leader>ec', '<cmd>EcologSnacks<cr>', desc = 'Open a picker' },
{ '<leader>eS', '<cmd>EcologSelect<cr>', desc = 'Switch env file' },
{ '<leader>es', '<cmd>EcologShelterToggle<cr>', desc = 'Ecolog shelter toggle' },
},
lazy = false,
opts = {
preferred_environment = 'local',
types = true,
providers = {
{
pattern = '{{[%w_]+}}?$',
filetype = 'http',
extract_var = function(line, col)
local utils = require 'ecolog.utils'
return utils.extract_env_var(line, col, '{{([%w_]+)}}?$')
end,
get_completion_trigger = function()
return '{{'
end,
},
},
interpolation = {
enabled = true,
features = {
commands = false,
},
},
sort_var_fn = function(a, b)
if a.source == 'shell' and b.source ~= 'shell' then
return false
end
if a.source ~= 'shell' and b.source == 'shell' then
return true
end
return a.name < b.name
end,
integrations = {
lspsaga = true,
blink_cmp = true,
statusline = {
hidden_mode = true,
highlights = {
env_file = 'Directory',
vars_count = 'Number',
},
},
snacks = true,
},
shelter = {
configuration = {
sources = {
['.env.example'] = 'none',
},
partial_mode = {
min_mask = 5,
show_start = 1,
show_end = 1,
},
mask_char = '*',
},
modules = {
files = true,
peek = false,
snacks_previewer = true,
snacks = false,
cmp = true,
},
},
path = vim.fn.getcwd(),
},
}
While ecolog.nvim
has many great and unique features, here are some comparisons with other plugins in neovim ecosystem in their specific fields:
Feature | ecolog.nvim | cmp-dotenv |
---|---|---|
Language-aware Completion | โ Fully configurable context-aware triggers for multiple languages and filetypes | โ Basic environment variable completion only on every char |
Type System | โ Built-in type validation and custom types | โ No type system |
Nvim-cmp support | โ Nvim-cmp integration | โ Nvim-cmp integration |
Blink-cmp support | โ Native blink-cmp integration | โ Doesn't support blink-cmp natively |
Omnifunc support | โ Native vim's omnifunc | โ Doesn't support omnifunc |
Documentation Support | โ Rich documentation with type info and source | ๐ก Basic documentation support |
Shell Variable Integration | โ Configurable shell variable loading and filtering | ๐ก Basic shell variable support |
Sorting | โ Built-in fully customizable sorting for both variables and files, involving all all integrations | โ No sorting supported |
Multiple Environment Files | โ Priority-based loading with custom sorting and switching between multiple environment files | ๐ก Basic environment variable loading |
Feature | ecolog.nvim | cloak.nvim |
---|---|---|
Partial Value Masking | โ Configurable partial masking with patterns | ๐ก Full masking only |
Pattern-based Security | โ Custom patterns for different security levels | ๐ก Basic pattern matching |
Preview Protection | โ Telescope/FZF/Snacks picker preview protection | ๐ก Only Telescope preview protection |
Avoid value leaking | โ Full support, never leak environment variables | โ Doesn't support masking on startup and pasting content from insert mode, flashes values |
Mask on leave | โ Supports | โ Supports |
Completion disable | โ Supports both blink-cmp and nvim-cmp, configurable | ๐ก Only nvim-cmp and can't disable |
Custom mask and highlights | โ Supports | โ Supports |
Performance | โ Better performance, especially in previewer buffers due to LRU caching, opening files is ~20ms faster then normal neovim(from my experience) | ๐ก Significantly slower. However, minimal implementation and also good |
Line of code | ๐ก ~1500+ LOC actively used on average, the rest is lazy loaded | โ Only ~300 LOC |
Supports custom integrations | โ Supports all ecolog.nvim features telescope-lua, snacks, fzf-lua, cmp, peek and etc. | ๐ก Only works in file buffers and telescope previewer |
Setup and Docs | ๐ก Docs are big due to the amount of features, but it's well documented and the plugin provides a lot of defaults, so it should be relatively easy to setup | โ Well documented and easy to setup |
Filetype support | ๐ก Supports only sh and .env files |
โ Can work in any filetype |
Feature | ecolog.nvim | telescope-env.nvim |
---|---|---|
Environment Variable Search | โ Basic search | โ Basic search |
Customizable keymaps | โ Fully customizable | โ Fully customizable |
Value Preview | โ Protected value preview | ๐ก Basic value preview |
Multiple Picker Support | โ Telescope, Snacks picker and FZF support | ๐ก Telescope only |
Security Features | โ Integrated security in previews | โ No security features |
Custom Sort/Filter | โ Advanced sorting and filtering options | ๐ก Basic sorting only |
Feature | ecolog.nvim | dotenv.nvim |
---|---|---|
Environment File Detection | โ Custom patterns and priority-based loading | ๐ก Basic env file loading |
Multiple Environment Support | โ Advanced environment file switching | ๐ก Basic environment support |
Shell Variable Integration | โ Configurable shell variable loading and filtering | โ No shell integration |
Contributions are welcome! Feel free to:
MIT License - See LICENSE for details.