UNL.nvim (Unreal Neovim Library) is a shared, common library for a suite of plugins (UEP.nvim, UCM.nvim, UBT.nvim, etc.) designed to enhance Unreal Engine development on Neovim.
This library is designed to save plugin developers from writing boilerplate code when creating plugins for Unreal Engine, allowing them to focus on plugin-specific logic. Typically, it is not installed or configured directly by end-users but is managed automatically as a dependency of other plugins.
- Layered Configuration Management:
- Provides a hierarchical configuration system that automatically merges plugin defaults, user's global settings, and project-local settings (
.unlrc.json).
- Provides a hierarchical configuration system that automatically merges plugin defaults, user's global settings, and project-local settings (
- UI Abstraction Backend:
- Picker: Automatically detects Telescope ,fzf-lua and snacks, with support for the native UI as a fallback.
- Progress: Transparently handles multiple progress display methods, including fidget.nvim, custom windows, and notifications.
- Filer: Automatically detects neo-tree.nvim and nvim-tree, with support for
netrwas a fallback.
- Declarative Command Builder:
- A utility for easily creating Neovim user commands. It allows for declarative definitions of subcommands, argument parsing,
!(bang) support, and completions.
- A utility for easily creating Neovim user commands. It allows for declarative definitions of subcommands, argument parsing,
- Advanced Finder Utilities:
- Provides an
ancestor finderto search upwards through parent directories for directories containing.uprojectfiles or module roots (.build.cs). - Includes logic to resolve the engine version from
.uprojectfiles.
- Provides an
- Analysis:
- Includes a built-in feature to parse
.build.csfiles and analyze their dependencies.
- Includes a built-in feature to parse
- Robust Logging System:
- Easily create per-plugin loggers with multiple output targets, such as files, notifications (
vim.notify), and the command line (echo). Log levels and output formats are also flexible.
- Easily create per-plugin loggers with multiple output targets, such as files, notifications (
- Project Management & RPC Server:
- Centralizes the management of the Rust-based RPC server (
unl-server) via the:UNL startcommand. It prevents multiple instances and allows safe sharing across multiple Neovim sessions. - Handles project-specific database initialization and registration via
:UNL setup. - Provides server-side file filtering and symbol search APIs for high performance even in massive projects.
- Centralizes the management of the Rust-based RPC server (
- High-Performance Scanner (Rust):
- Includes a built-in Rust-based binary scanner for lightning-fast C++ header analysis. It utilizes Tree-sitter for accurate parsing of Unreal Engine macros and class structures.
- Neovim v0.11.3 or higher
- Rust and Cargo (Required to build the scanner binary)
- (Optional) Various UI plugins to enhance the user experience (
Telescope,fzf-lua, etc.)
Typically, users do not need to install this library manually, as it will be automatically installed as a dependency of other Unreal Neovim plugins.
However, since this plugin includes a Rust-based scanner, you need to add a build hook to compile the binary during installation or updates.
Using lazy.nvim
return {
'taku25/UNL.nvim',
build = "cargo build --release --manifest-path scanner/Cargo.toml",
}If the automatic build fails, you can manually build the scanner by running the following command in the plugin's root directory:
cargo build --release --manifest-path scanner/Cargo.tomlExample installation configuration for UEP.nvim:
return {
'taku25/UEP.nvim',
-- lazy.nvim will automatically install and manage UNL.nvim
dependencies = {
{ 'taku25/UNL.nvim', build = "cargo build --release --manifest-path scanner/Cargo.toml" }
},
opts = {
-- Configuration is handled through the UNL.nvim system
},
}UNL.nvim is the central hub for managing the configuration of itself and all plugins that use it. The opts table passed to the setup function in lazy.nvim will be merged with the default settings of all plugins and the Localrc.
The following are all available options with their default values:
opts = {
-- Configuration for UI backends
ui = {
picker = {
mode = "auto", -- "auto", "telescope", "fzf_lua", "native"
prefer = { "telescope", "fzf_lua", "native" },
},
filer = {
mode = "auto",
prefer = { "nvim-tree", "neo-tree", "native" },
},
progress = {
enable = true,
mode = "auto", -- "auto", "fidget", "window", "notify"
prefer = { "fidget", "window", "notify" },
},
},
-- Configuration for logging
logging = {
level = "info", -- Global base log level (trace, debug, info, warn, error)
echo = { level = "warn" }, -- Minimum level to display with :echo
notify = { level = "error", prefix = "[UNL]" }, -- Minimum level and prefix for vim.notify
file = { enable = true, max_kb = 512, rotate = 3, filename = "unl.log" }, -- File log settings
},
-- Configuration for the cache directory
cache = {
-- The directory name where this library and related plugins
-- will store cache files, i.e., <nvim_cache_dir>/<dirname>
dirname = "UNL_cache"
},
-- Configuration for project searching
project = {
-- The filename for project-local settings
localrc_filename = ".unlrc.json",
-- If true, the search will not go above the home directory
search_stop_at_home = true,
},
}Using UNL.nvim can significantly simplify your plugin development.
-- my_plugin/init.lua
-- Import core UNL modules
local unl_log = require("UNL.logging")
local unl_config = require("UNL.config")
-- Define your plugin's default settings
local my_defaults = require("my_plugin.config.defaults")
local M = {}
function M.setup(user_config)
-- 1. Register your plugin with the UNL system
-- This initializes the logger and configuration
unl_log.setup("MyPlugin", my_defaults, user_config or {})
-- 2. From here on, you can get the logger and config for your plugin
local log = unl_log.get("MyPlugin")
local conf = unl_config.get("MyPlugin")
log.info("MyPlugin has been set up successfully!")
log.debug("Current picker mode is: %s", conf.ui.picker.mode)
end
return MYou can control UNL programmatically via require("UNL.api"). This is useful for creating custom keymaps or integrating with other plugins.
local unl = require("UNL.api")
-- Start the UNL server and file watcher
unl.start()
-- Refresh the project database (scans files and updates DB)
-- scope: "Game", "Engine", "Full"
unl.refresh({ scope = "Game" })
-- Setup UNL for the current project (usually called by start, but can be manual)
unl.setup()
-- Start the file watcher explicitly
unl.watch()
-- Search files using server-side filtering (High Performance)
-- modules: list of module names to search in
-- filter: keyword to match file paths
-- limit: max number of results
unl.db.search_files_in_modules({"Core", "Engine"}, "Actor", 100, function(files)
for _, file in ipairs(files) do
print(file.file_path)
end
end)Use require("UNL.picker").open(spec) to display the best picker based on the user's environment (Telescope, fzf-lua, Snacks, etc.). This allows plugin developers to provide a consistent rich UI without worrying about backend differences.
local unl_picker = require("UNL.picker")
unl_picker.open({
title = "Select Module",
items = { "Core", "Engine", "Project" }, -- Static list
on_confirm = function(selection)
print("Selected: " .. selection)
end,
})The spec table passed to open() supports the following fields:
| Field | Type | Description |
|---|---|---|
title |
string |
The title of the picker. |
source |
table |
Data source definition (see below). |
items |
table |
Shorthand for source.type = "static". A list of items. |
on_confirm |
function |
Callback when an item is confirmed. Receives the selection. |
multiselect |
string/bool |
Selection mode: "loop" (safe looping UI), "native" (backend native UI), or "none" (single selection). |
preview_enabled |
boolean |
Whether to enable the previewer. |
devicons_enabled |
boolean |
Whether to show icons. |
You can handle different data formats by specifying the source field:
static: Displays a fixed list.source = { type = "static", items = { { label = "Item 1", value = 1 }, ... } }
grep: Performs a dynamic search likelive_grep.source = { type = "grep", search_paths = { "Source/Runtime" }, include_extensions = { "h", "cpp" } }
job: Runs an external command (e.g.,fd) and lists its output.source = { type = "job", command = { "fd", "--type", "f", "." } }
callback: Dynamically push items via a function.source = { type = "callback", fn = function(push) push({ "Item A", "Item B" }) -- Push a list -- Supports asynchronous updates some_async_request(function(data) push(data) end) end }
Users can use a completely custom picker implementation by passing a function to ui.picker.mode.
require('UNL').setup({
ui = {
picker = {
mode = function(spec)
-- Interpret the spec and show your preferred UI (e.g., mini.pick)
require('mini.pick').start({
source = { items = spec.items, name = spec.title },
callback = spec.on_confirm
})
end
}
}
})MIT License
Copyright (c) 2025 taku25
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
