A Neovim plugin for seamless Home Assistant integration via LSP, providing intelligent auto-completion, entity management, and configuration assistance.
- 🚀 LSP-Based Architecture: Built on homeassistant-lsp for robust language server features
- 🎯 Smart Auto-completion: Real-time entity and service completion from your Home Assistant instance
- 💡 Hover Documentation: View entity states and details on hover
- 🔍 Diagnostics: Real-time validation of entity IDs and service calls
- 📝 Dashboard Editor: Edit Home Assistant Lovelace dashboards directly from Neovim
- 📊 Live State Viewing: View and monitor entity states directly in Neovim
- 🎨 Dashboard: Quick access to entities in a floating window
- Neovim >= 0.9.0
- homeassistant-lsp - The LSP server (install globally with npm)
- nvim-lspconfig - LSP configuration
- telescope.nvim (optional) - For entity picker
- Home Assistant instance with API access
This project uses pre-commit hooks to ensure code quality. To set up:
# Install the hooks
prek install
# Run on all files
prek runThe hooks include:
- StyLua - Lua code formatter
- Luacheck - Lua linter and static analyzer
- Standard checks (trailing whitespace, end-of-file, etc.)
The plugin requires the Home Assistant LSP server to be installed globally:
npm install -g homeassistant-lspOr see the homeassistant-lsp installation guide for more options.
Basic setup (loads on all files):
{
"myakove/homeassistant-nvim",
dependencies = {
"neovim/nvim-lspconfig", -- Required for LSP
"nvim-telescope/telescope.nvim", -- Optional, for entity picker
},
event = { "BufRead", "BufNewFile" }, -- Load on file open
config = function()
require("homeassistant").setup({
lsp = {
enabled = true,
-- LSP server command (default: homeassistant-lsp --stdio)
cmd = { "homeassistant-lsp", "--stdio" },
-- File types to attach LSP to
filetypes = { "yaml", "yaml.homeassistant", "python", "json" },
-- LSP server settings
settings = {
homeassistant = {
host = "ws://homeassistant.local:8123/api/websocket",
token = "your_long_lived_access_token_here",
timeout = 5000,
},
cache = {
enabled = true,
ttl = 300, -- 5 minutes
},
diagnostics = {
enabled = true,
debounce = 500,
},
},
},
-- Optional: UI settings
ui = {
dashboard = {
width = 0.8,
height = 0.8,
border = "rounded",
},
},
-- Optional: Custom keymaps (set to false to disable defaults)
keymaps = {
dashboard = "<leader>hd",
picker = "<leader>hp",
reload_cache = "<leader>hr",
debug = "<leader>hD",
edit_dashboard = "<leader>he",
},
})
end,
}Path-based loading (recommended if you only work with HA files in specific directories):
{
"myakove/homeassistant-nvim",
dependencies = {
"neovim/nvim-lspconfig",
"nvim-telescope/telescope.nvim",
},
event = { "BufRead", "BufNewFile" }, -- Plugin file loads on file open
config = function()
require("homeassistant").setup({
-- Only initialize when files match these paths
paths = { "config/homeassistant/", "~/dotfiles/" },
lsp = {
enabled = true,
cmd = { "homeassistant-lsp", "--stdio" },
filetypes = { "yaml", "yaml.homeassistant", "python", "json" },
settings = {
homeassistant = {
host = "ws://homeassistant.local:8123/api/websocket",
token = "your_long_lived_access_token_here",
timeout = 5000,
},
cache = { enabled = true, ttl = 300 },
diagnostics = { enabled = true, debounce = 500 },
},
},
})
end,
}Note: Completion is provided by the LSP server and works automatically with any LSP-compatible completion plugin (blink.cmp, nvim-cmp, coq_nvim, etc.). No additional configuration needed!
All configuration options with their default values:
By default, the plugin loads automatically on all files. You can configure it to only load when files match specific path patterns:
require("homeassistant").setup({
-- Only load plugin when editing files in Home Assistant config directory
paths = { "config/homeassistant/", "/homeassistant/" },
-- Or match by file extension
paths = { "%.yaml$", "%.yml$" },
-- Or combine both
paths = { "config/homeassistant/", "%.yaml$" },
-- Patterns are Lua patterns (see :help pattern)
-- Use %- to match literal - (hyphen)
paths = { "home%-assistant%-config/" },
-- If paths is nil or empty, plugin loads everywhere (default)
paths = nil, -- or paths = {}
})When to use path-based loading:
- ✅ You only work with Home Assistant files in specific directories
- ✅ You want to reduce plugin overhead when editing other files
- ✅ You have multiple Home Assistant instances and want to load selectively
Note: If paths is not configured (or is nil/empty), the plugin loads on all files for backward compatibility.
Important distinction:
pathscontrols when the plugin initializes (loads only for matching file paths)lsp.filetypescontrols which file types get LSP features (completion, hover, diagnostics)- When
pathsis configured: LSP only attaches to buffers that match BOTH the configured paths AND the configured filetypes - When
pathsis NOT configured: LSP attaches to all buffers matching the configured filetypes (default behavior)
Benefits of path-based LSP attachment:
- ✅ Prevents interference with other LSPs (e.g., Lua LSP won't conflict on Lua files in matching paths)
- ✅ More efficient: LSP only runs where needed
- ✅ Better isolation: Each file type gets its appropriate LSP
Example:
require("homeassistant").setup({
-- Plugin only loads when editing files in Home Assistant config directory
paths = { "config/homeassistant/" },
-- LSP attaches only to YAML/Python/JSON files in matching paths
-- Other file types (like Lua) in matching paths won't get HA LSP attached
lsp = {
filetypes = { "yaml", "yaml.homeassistant", "python", "json" },
},
})Real-world scenario:
If you have paths = { "~/dotfiles/" } and open a Lua file in ~/dotfiles/config.lua:
- ✅ Plugin file loads (because path matches)
- ✅ Plugin initializes (because path matches)
- ❌ HA LSP does NOT attach (because Lua is not in
filetypes) - ✅ Your Lua LSP works normally (no interference)
Full Configuration Example
require("homeassistant").setup({
-- LSP Server configuration
lsp = {
enabled = true, -- Default: true - Enable LSP client
cmd = { "homeassistant-lsp", "--stdio" }, -- Default: homeassistant-lsp --stdio
filetypes = { "yaml", "yaml.homeassistant", "python", "json" }, -- Default file types
root_dir = nil, -- Default: auto-detect via lspconfig
settings = {
homeassistant = {
host = "ws://localhost:8123/api/websocket", -- WebSocket URL
token = nil, -- REQUIRED: Your long-lived access token
timeout = 5000, -- Default: 5000ms
},
cache = {
enabled = true, -- Default: true
ttl = 300, -- Default: 300 seconds (5 minutes)
},
diagnostics = {
enabled = true, -- Default: true
debounce = 500, -- Default: 500ms
},
completion = {
minChars = 3, -- Default: 3 - Min characters for domain completion
},
},
},
-- UI settings (Neovim-specific)
ui = {
dashboard = {
width = 0.8, -- Default: 0.8 (80% of screen)
height = 0.8, -- Default: 0.8
border = "rounded", -- Default: "rounded"
favorites = {}, -- Default: empty list
},
state_viewer = {
border = "rounded", -- Default: "rounded"
show_attributes = true, -- Default: true
},
},
-- Logging (plugin-level, not LSP)
logging = {
level = "info", -- Default: "info" (debug, info, warn, error)
},
-- Keymaps
keymaps = {
enabled = true, -- Default: true - Set to false to disable all keymaps
dashboard = "<leader>hd", -- Default: <leader>hd - Toggle dashboard
picker = "<leader>hp", -- Default: <leader>hp - Entity picker (requires telescope)
edit_dashboard = "<leader>he", -- Default: <leader>he - Edit HA dashboards
reload_cache = "<leader>hr", -- Default: <leader>hr - Reload LSP cache
debug = "<leader>hD", -- Default: <leader>hD - Show debug info
},
-- Path-based loading (optional)
-- If nil or empty, plugin loads on all files (default behavior)
-- If set to array of patterns, plugin only loads when file path matches any pattern
-- Patterns are Lua patterns (see :help pattern)
paths = nil, -- Default: nil - Load everywhere
-- Example: paths = { "config/homeassistant/", "/homeassistant/", "%.yaml$" }
-- Example: paths = { "home%-assistant%-config/" } -- Use %- to match literal -
})Keymaps Configuration:
The plugin sets up default keymaps automatically. You can:
- Disable all keymaps: Set
keymaps.enabled = false - Customize individual keymaps: Change the key in config
- Set a keymap to
nil: Disable that specific keymap
require("homeassistant").setup({
keymaps = {
enabled = true,
dashboard = "<leader>ha", -- Custom keymap
picker = nil, -- Disable picker keymap
reload_cache = "<F5>", -- Use F5 for reload
},
})For better security, you can use environment variables to avoid hardcoding sensitive tokens in your config:
require("homeassistant").setup({
lsp = {
settings = {
homeassistant = {
host = os.getenv("HOMEASSISTANT_HOST") or "ws://localhost:8123/api/websocket",
token = os.getenv("HOMEASSISTANT_TOKEN"), -- Read from environment
},
},
},
})Then set in your shell profile (~/.bashrc or ~/.zshrc):
export HOMEASSISTANT_HOST="ws://homeassistant.local:8123/api/websocket"
export HOMEASSISTANT_TOKEN="your-long-lived-access-token-here"Benefits:
- ✅ Tokens not stored in config files or git repos
- ✅ Easy to switch between dev/prod environments
- Go to your Home Assistant profile:
http://your-ha-instance:8123/profile - Scroll down to "Long-Lived Access Tokens"
- Click "Create Token"
- Give it a name (e.g., "Neovim Plugin")
- Copy the token and add it to your configuration
:HADashboard- Toggle the entity dashboard:HAEntityState <entity_id>- View entity state in floating window:HAPicker- Open Telescope picker for entity selection:HAEditDashboard- Edit Home Assistant Lovelace dashboards:HAReloadCache- Manually reload LSP entity cache:HAComplete- Manually trigger LSP completion:HADebug- Show plugin debug information:checkhealth homeassistant- Run health check (verify installation and LSP connection)
The plugin uses a dedicated LSP server (homeassistant-lsp) that provides full language server features:
Hover (K key):
- Press
Kon any entity ID to see entity state and attributes - Works in YAML and Python files
- Shows:
- Entity name and current state
- All attributes
- Last changed/updated times
Completion:
- Automatic entity ID completion while typing
- Service name completion
- Domain completion
- Works with any LSP-compatible completion plugin
Diagnostics:
- Real-time validation of entity references
- Warns about unknown/invalid entities
- Validates service calls
- Debounced for performance (500ms by default)
- Falls back to LSP definition for code
Example:
automation:
- alias: "Turn on lights"
trigger:
- platform: state
entity_id: sensor.motion # Press K here for HA info
action:
- service: light.turn_on
target:
entity_id: light.unknown # Warning: Unknown entityManual Command:
:HAHover- Force show HA entity info (bypasses LSP fallback)
The plugin provides intelligent, context-aware completion in both YAML (.yaml and .yml) and Python (.py) files.
Note: Completion is available in all YAML and Python files, not just Home Assistant configs. The triggers are specific enough that they won't interfere with regular YAML/Python work.
Stage 1: Domain Completion (3+ characters)
Type any 3+ character word to see matching Home Assistant domains:
# YAML example
inp # Shows: input_number, input_text, input_boolean, input_select, input_datetime
sen # Shows: sensor
lig # Shows: light
dev # Shows: device_tracker# Python/AppDaemon example
self.get_state("inp # Shows domain suggestions
self.turn_on("cov # Shows: coverStage 2: Entity Completion (domain + dot)
After selecting a domain, type . to see all entities in that domain:
# YAML example
sensor. # Shows all sensors
sensor.temp # Filters to sensors containing "temp"
input_boolean. # Shows all input_boolean entities
light.kitchen # Filters to lights containing "kitchen"# Python/AppDaemon example
self.get_state("sensor.") # All sensors
self.turn_on("light.living") # Lights with "living"
self.call_service("climate.") # All climate entities- Domain completion: Type 3+ characters (e.g.,
inp,sen) to see matching domains - Entity filtering: Type after the dot to filter entities (e.g.,
sensor.temp) - Works with underscores:
input_boolean.,device_tracker., etc. - Case-insensitive:
sensor,Sensor,SENSORall work - Real-time data: Entities and states are fetched directly from your Home Assistant instance
The plugin automatically sets up these keymaps:
| Keymap | Command | Description |
|---|---|---|
<leader>hd |
:HADashboard |
Toggle entity dashboard |
<leader>hp |
:HAPicker |
Open entity picker (requires telescope) |
<leader>he |
:HAEditDashboard |
Edit HA Lovelace dashboards |
<leader>hr |
:HAReloadCache |
Reload entity cache |
<leader>hD |
:HADebug |
Show debug information |
Customize or disable keymaps in your config:
require("homeassistant").setup({
keymaps = {
enabled = true, -- Set to false to disable all keymaps
dashboard = "<leader>ha", -- Change to your preferred key
picker = nil, -- Set to nil to disable this keymap
edit_dashboard = "<leader>he", -- Keep default
reload_cache = "<F5>", -- Use F5 for reload
debug = "<leader>hD", -- Keep default
},
})Edit Home Assistant Lovelace dashboards directly from Neovim:
How it works:
- Run
:HAEditDashboardor press<leader>he - Select a dashboard from the picker (Telescope or vim.ui.select)
- Edit the configuration in JSON format
- Save with
:wto update Home Assistant - Changes appear immediately in HA's UI
Features:
- ✅ Storage-mode dashboards only - YAML-mode dashboards should be edited in your config files
- ✅ JSON format - Easy to read and edit with syntax highlighting
- ✅ Real-time sync - Changes saved directly to Home Assistant
- ✅ Telescope picker - Quick dashboard selection (with fallback to vim.ui.select)
Example workflow:
:HAEditDashboard " Opens picker with available dashboards
" Select 'Home' dashboard
" Edit the JSON configuration
:w " Save to Home AssistantConfiguration:
require("homeassistant").setup({
keymaps = {
edit_dashboard = "<leader>he", -- Or set to nil to disable
},
})Note: This feature allows write access to your Home Assistant instance to update dashboard configurations. Only storage-mode dashboards (created/edited via UI) can be modified through the API.
This plugin uses the official Home Assistant WebSocket API for all communications:
- Real-time connection: Persistent WebSocket connection for low latency
- Event-driven: Subscribe to state changes and events
- Efficient: No polling required, updates pushed from Home Assistant
- Official protocol: Uses the same API as Home Assistant's frontend
Run the health check to verify your setup:
:checkhealth homeassistantThis will check:
- ✅ uv installation and version
- ✅ Python 3 availability
- ✅ Plugin initialization
- ✅ WebSocket connection status
- ✅ Home Assistant version, location, and state
- ✅ Total entities and services
- ✅ Completion engine detection
- ✅ Optional dependencies (telescope.nvim)
If you're having trouble connecting:
- Run health check:
:checkhealth homeassistant - Verify
uvis installed:uv --version - Verify Home Assistant URL is correct (including
http://orhttps://) - Check your access token is valid (from HA profile page)
- Check plugin debug info:
:HADebug(shows HA version when connected) - Enable debug logging: Set
logging.level = "debug"in config and check:messages
Install uv:
curl -LsSf https://astral.sh/uv/install.sh | shMake sure ~/.cargo/bin is in your $PATH (or restart your terminal after installation).
- WebSocket API client
- Entity auto-completion
- Service auto-completion
- Dashboard UI
- Real-time state updates
- LSP features (hover, diagnostics, go-to-definition)
- Snippet generation for automations
- Blueprint editor support
- State history visualization
- Advanced YAML validation
Contributions are welcome! Please feel free to submit a Pull Request.
MIT License - see LICENSE file for details
homeassistant-nvim/
├── lua/homeassistant/ # Lua plugin code
│ ├── init.lua # Main entry point
│ ├── config.lua # Configuration management
│ ├── health.lua # Health check integration
│ ├── lsp_client.lua # LSP client setup
│ ├── ui/ # UI components
│ │ ├── dashboard.lua # Entity dashboard
│ │ ├── dashboard_editor.lua # Lovelace dashboard editor
│ │ ├── picker.lua # Telescope entity picker
│ │ ├── state_viewer.lua # Entity state viewer
│ │ └── floating.lua # Floating window utilities
│ └── utils/ # Utilities
│ └── logger.lua # Logging utility
├── plugin/ # Vim plugin loader
├── doc/ # Vim help documentation
└── README.md
Note: The heavy lifting (WebSocket, caching, completion, diagnostics, hover) is done by the separate homeassistant-lsp server. This plugin focuses on Neovim-specific UI and LSP client integration.
- homeassistant-lsp - The LSP server powering this plugin
- Home Assistant team for the excellent WebSocket API
- LSP community for the Language Server Protocol specification
- Neovim community for the powerful plugin ecosystem