Terms used • The what • Screenshots • Requirements • Installation • Configuration • Highlights • Advanced configuration •
A pretty complete set of Neovim UI components for notification, input and progress.
Juu is swahili for "up" or "above."
Warning
This is a revamp of dressing.nvim and fidget.nvim.
All the hard work has been done by Steven Arcangeli and John Hui.
- Language Server Protocol (LSP): A protocol that defines how code editors and IDEs communicate with language servers.
Juu.nvim styles the input and select windows in Neovim,
provides a configurable
juu.notify (vim.notify) and
juu.progress backend,
and displays (LSP) progress notifications.
- Neovim 0.11.5+ (might work on earlier versions, but not tested)
juu.nvim supports all the usual plugin managers
lazy.nvim
{
'mistweaverco/juu.nvim',
opts = {},
}Packer
require('packer').startup(function()
use {'mistweaverco/juu.nvim'}
end)Paq
require "paq" {
{'mistweaverco/juu.nvim'};
}vim-plug
Plug 'mistweaverco/juu.nvim'dein
call dein#add('mistweaverco/juu.nvim')Pathogen
git clone --depth=1 https://github.com/mistweaverco/juu.nvim.git ~/.vim/bundle/Neovim native package
git clone --depth=1 https://github.com/mistweaverco/juu.nvim.git \
"${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/pack/juu.nvim/start/juu.nvimIf you're fine with the defaults, you're good to go after installation. If you want to tweak, call this function:
require("juu").setup({
-- Notification system (enabled by default, set to false to disable)
notify = {
-- Override vim.notify() by default
override_vim_notify = true,
-- See below for more notification options
},
-- LSP progress tracking (enabled by default, set to false to disable)
progress = {
-- See below for more progress options
},
-- Input styling configuration
input = {
-- Set to false to disable the vim.ui.input implementation
enabled = true,
-- Default prompt string
default_prompt = "Input",
-- Trim trailing `:` from prompt
trim_prompt = true,
-- Can be 'left', 'right', or 'center'
title_pos = "left",
-- The initial mode when the window opens (insert|normal|visual|select).
start_mode = "insert",
-- These are passed to nvim_open_win
border = "rounded",
-- 'editor' and 'win' will default to being centered
relative = "cursor",
-- These can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
prefer_width = 40,
width = nil,
-- min_width and max_width can be a list of mixed types.
-- min_width = {20, 0.2} means "the greater of 20 columns or 20% of total"
max_width = { 140, 0.9 },
min_width = { 20, 0.2 },
buf_options = {},
win_options = {
-- Disable line wrapping
wrap = false,
-- Indicator for when text exceeds window
list = true,
listchars = "precedes:…,extends:…",
-- Increase this for more context when text scrolls off the window
sidescrolloff = 0,
},
-- Set to `false` to disable
mappings = {
n = {
["<Esc>"] = "Close",
["<CR>"] = "Confirm",
},
i = {
["<C-c>"] = "Close",
["<CR>"] = "Confirm",
["<Up>"] = "HistoryPrev",
["<Down>"] = "HistoryNext",
},
},
override = function(conf)
-- This is the config that will be passed to nvim_open_win.
-- Change values here to customize the layout
return conf
end,
get_config = nil,
},
select = {
-- Set to false to disable the vim.ui.select implementation
enabled = true,
-- Priority list of preferred vim.select implementations
backend = { "telescope", "fzf_lua", "fzf", "builtin", "nui" },
-- Trim trailing `:` from prompt
trim_prompt = true,
-- Options for telescope selector
-- These are passed into the telescope picker directly. Can be used like:
-- telescope = require('telescope.themes').get_ivy({...})
telescope = nil,
-- Options for fzf selector
fzf = {
window = {
width = 0.5,
height = 0.4,
},
},
-- Options for fzf-lua
fzf_lua = {
-- winopts = {
-- height = 0.5,
-- width = 0.5,
-- },
},
-- Options for nui Menu
nui = {
position = "50%",
size = nil,
relative = "editor",
border = {
style = "rounded",
},
buf_options = {
swapfile = false,
filetype = "JuuSelect",
},
win_options = {
winblend = 0,
},
max_width = 80,
max_height = 40,
min_width = 40,
min_height = 10,
},
-- Options for built-in selector
builtin = {
-- Display numbers for options and set up keymaps
show_numbers = true,
-- These are passed to nvim_open_win
border = "rounded",
-- 'editor' and 'win' will default to being centered
relative = "editor",
buf_options = {},
win_options = {
cursorline = true,
cursorlineopt = "both",
-- disable highlighting for the brackets around the numbers
winhighlight = "MatchParen:",
-- adds padding at the left border
statuscolumn = " ",
},
-- These can be integers or a float between 0 and 1 (e.g. 0.4 for 40%)
-- the min_ and max_ options can be a list of mixed types.
-- max_width = {140, 0.8} means "the lesser of 140 columns or 80% of total"
width = nil,
max_width = { 140, 0.8 },
min_width = { 40, 0.2 },
height = nil,
max_height = 0.9,
min_height = { 10, 0.2 },
-- Set to `false` to disable
mappings = {
["<Esc>"] = "Close",
["<C-c>"] = "Close",
["<CR>"] = "Confirm",
},
override = function(conf)
-- This is the config that will be passed to nvim_open_win.
-- Change values here to customize the layout
return conf
end,
},
-- Used to override format_item.
format_item_override = {},
get_config = nil,
},
})Juu.nvim includes a notification system that replaces
vim.notify() by default.
Notifications are displayed in a corner window. You can configure it like this:
require("juu").setup({
notify = {
-- Override vim.notify() (default: true)
override_vim_notify = true,
-- Poll rate for updating notifications (Hz)
poll_rate = 10,
-- Minimum notification level to display
filter = vim.log.levels.INFO,
-- Number of removed messages to retain in history
history_size = 128,
-- Window configuration
window = {
normal_hl = "Comment", -- Base highlight group
winblend = 100, -- Background opacity
border = "none", -- Border style
zindex = 45, -- Stacking priority
max_width = 0, -- Maximum width (0 = auto)
max_height = 0, -- Maximum height (0 = auto)
x_padding = 1, -- Padding from right edge
y_padding = 0, -- Padding from bottom edge
align = "bottom", -- Window alignment
relative = "editor", -- Position relative to
avoid = {}, -- Filetypes to avoid (e.g., { "NvimTree" })
},
-- Notification group configuration
configs = {
default = {
-- Enable colored message text based on log level (default: true)
color_messages = true,
-- Enable borders around notification items (default: true)
borders = true,
-- Highlight styles for different log levels
debug_style = "Comment",
info_style = "Question",
warn_style = "WarningMsg",
error_style = "ErrorMsg",
},
},
},
})Notification titles (annotes) are displayed with inverted colors for better visibility:
the foreground color of the log level becomes the background, and the background becomes the foreground. This creates a badge-like appearance for the title.
For example, with an error notification:
- Message text: Uses the regular error colors (typically red foreground)
- Title/annote: Uses inverted colors (red background with black foreground)
If the base highlight has a transparent background, the inverted version automatically uses black for the foreground to ensure the title text remains visible.
You can view notification history using the :Notifications command,
which works similarly to :messages.
You can also filter by log level:
:Notifications " Show all notifications
:Notifications error " Show only error notifications
:Notifications info " Show only info notifications
:Notifications warn " Show only warning notifications
:Notifications debug " Show only debug notificationsThe notification system also supports
the title parameter from vim.notify():
vim.notify("Something went wrong", vim.log.levels.ERROR, { title = "Error" })
-- The title "Error" will be displayed with inverted error colors (red background)You can simulate progress notifications for
testing using the progress.handle.create() API:
local progress = require("juu.progress")
-- Create a progress handle
local handle = progress.handle.create({
title = "My Task",
message = "Starting...",
client = { name = "My Test-Client" },
percentage = 0,
cancellable = true,
})
-- Update progress over time
handle.message = "Processing..."
handle.percentage = 25
handle:report({
message = "Halfway there...",
percentage = 50,
})
-- Finish the task
handle:finish()For a more complete example that simulates progress over time:
-- Simulate a 5-second progress task
local progress = require("juu.progress")
local handle = progress.handle.create({
title = "Test Task",
message = "Starting...",
client = { name = "My Test-Client" },
percentage = 0,
})
local timer = vim.loop.new_timer()
local step = 0
timer:start(0, 100, function()
step = step + 1
local percentage = math.min(100, step * 2)
handle:report({
message = string.format("Processing... (%d%%)", percentage),
percentage = percentage,
})
if percentage >= 100 then
timer:stop()
timer:close()
handle:finish()
end
end)There is also a demo file included with the plugin at
lua/juu/demos/progress/loading.lua that you can run with:
:lua require("juu.demos.progress.loading").simulate()Juu.nvim automatically tracks and displays LSP progress messages.
You can disable LSP progress tracking by setting modules.lsp = nil:
require("juu").setup({
progress = {
modules = {
lsp = nil, -- Disable LSP progress tracking
},
},
})Configure it like this:
```lua
require("juu").setup({
progress = {
-- General progress options
-- Poll rate: 0 = immediate, >0 = Hz, false = disabled
poll_rate = 0,
-- Suppress new messages while in insert mode
suppress_on_insert = false,
-- Ignore new tasks that are already complete
ignore_done_already = false,
-- Ignore new tasks that don't contain a message
ignore_empty_message = false,
-- How to group progress messages (default: by client name)
notification_group = function(msg)
return msg.client.name
end,
-- List of clients to ignore
ignore = {},
-- Module-specific configuration
modules = {
-- LSP progress module configuration
-- Set to `nil` to disable LSP progress tracking entirely
lsp = {
-- Configure the LSP progress ring buffer size
progress_ringbuf_size = 0,
-- Log $/progress handler invocations (for debugging)
log_handler = false,
},
},
-- Display options
display = {
render_limit = 16, -- How many messages to show at once
done_ttl = 3, -- How long completed messages persist (seconds)
done_icon = "✔", -- Icon for completed tasks
progress_icon = { "dots" }, -- Icon for in-progress tasks (animated)
progress_ttl = math.huge, -- How long in-progress messages persist
priority = 30, -- Ordering priority
skip_history = true, -- Omit from history
},
},
})A common way to adjust the highlighting of just the juu windows is by
providing a winhighlight option in the config. See :help winhighlight
for more details. Example:
require('juu').setup({
input = {
win_options = {
winhighlight = 'NormalFloat:DiagnosticError'
}
}
})For each of the input and select configs, there is an option
get_config. This can be a function that accepts the opts parameter that
is passed in to vim.select or vim.input. It must return either nil (to
no-op) or config values to use in place of the global config values for that
module.
For example, if you want to use a specific configuration for code actions:
require('juu').setup({
select = {
get_config = function(opts)
if opts.kind == 'codeaction' then
return {
backend = 'nui',
nui = {
relative = 'cursor',
max_width = 40,
}
}
end
end
}
})

