Skip to content
This repository was archived by the owner on Feb 12, 2025. It is now read-only.
Open
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
51 changes: 45 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
# Dressing.nvim

With the release of Neovim 0.6 we were given the start of extensible core UI
hooks ([vim.ui.select](https://github.com/neovim/neovim/pull/15771) and
[vim.ui.input](https://github.com/neovim/neovim/pull/15959)). They exist to
allow plugin authors to override them with improvements upon the default
behavior, so that's exactly what we're going to do.
Neovim has several utility functions under the `vim.ui.*` namespace for accepting input from the user. Dressing replaces the default functions with prettier implementations that use floating windows.

It is a goal to match and not extend the core Neovim API. All options that core
respects will be respected, and we will not accept any custom parameters or
Expand All @@ -26,10 +22,14 @@ Neovim 0.7.0+ (for earlier versions, use the [nvim-0.5 branch](https://github.co

## Screenshots

`vim.input` replacement (handling a LSP rename)
`vim.input` (handling a LSP rename)

![Screenshot from 2021-12-09 17-36-16](https://user-images.githubusercontent.com/506791/145502533-3dc2f87d-95ea-422d-a318-12c0092f1bdf.png)

`vim.confirm`

![Screenshot 2023-04-12 at 9 27 53 AM](https://user-images.githubusercontent.com/506791/231521989-3a4049ae-f56e-4864-b0df-4dc9702f14b1.png)

`vim.select` (telescope)

![Screenshot from 2021-12-02 19-46-01](https://user-images.githubusercontent.com/506791/144541916-4fa60c50-cadc-4f0f-b3c1-6307310e6e99.png)
Expand Down Expand Up @@ -275,6 +275,45 @@ require('dressing').setup({
-- Used to override format_item. See :help dressing-format
format_item_override = {},

-- see :help dressing_get_config
get_config = nil,
},
confirm = {
-- Set to false to disable the vim.ui.confirm implementation
enabled = true,

-- Title of the confirm window
title = "Confirm",

-- Can be 'left', 'right', or 'center'
title_pos = "center",

border = "rounded",

-- These can be integers or a float between 0 and 1 (e.g. 0.4 for 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 },
height = nil,
max_height = 0.9,
min_height = { 4, 0.2 },

buf_options = {},
win_options = {
-- Window transparency (0-100)
winblend = 10,
-- Disable line wrapping
wrap = false,
},

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,

-- see :help dressing_get_config
get_config = nil,
},
Expand Down
39 changes: 39 additions & 0 deletions doc/dressing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,45 @@ Configure dressing.nvim by calling the setup() function.
-- Used to override format_item. See :help dressing-format
format_item_override = {},

-- see :help dressing_get_config
get_config = nil,
},
confirm = {
-- Set to false to disable the vim.ui.confirm implementation
enabled = true,

-- Title of the confirm window
title = "Confirm",

-- Can be 'left', 'right', or 'center'
title_pos = "center",

border = "rounded",

-- These can be integers or a float between 0 and 1 (e.g. 0.4 for 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 },
height = nil,
max_height = 0.9,
min_height = { 4, 0.2 },

buf_options = {},
win_options = {
-- Window transparency (0-100)
winblend = 10,
-- Disable line wrapping
wrap = false,
},

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,

-- see :help dressing_get_config
get_config = nil,
},
Expand Down
39 changes: 39 additions & 0 deletions lua/dressing/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,45 @@ local default_config = {
-- Used to override format_item. See :help dressing-format
format_item_override = {},

-- see :help dressing_get_config
get_config = nil,
},
confirm = {
-- Set to false to disable the vim.ui.confirm implementation
enabled = true,

-- Title of the confirm window
title = "Confirm",

-- Can be 'left', 'right', or 'center'
title_pos = "center",

border = "rounded",

-- These can be integers or a float between 0 and 1 (e.g. 0.4 for 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 },
height = nil,
max_height = 0.9,
min_height = { 4, 0.2 },

buf_options = {},
win_options = {
-- Window transparency (0-100)
winblend = 10,
-- Disable line wrapping
wrap = false,
},

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,

-- see :help dressing_get_config
get_config = nil,
},
Expand Down
176 changes: 176 additions & 0 deletions lua/dressing/confirm.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
local global_config = require("dressing.config")
local patch = require("dressing.patch")
local util = require("dressing.util")

return function(message, opts, callback)
local config = global_config.get_mod_config("confirm", message, opts)
if not config.enabled then
return patch.original_mods.confirm(opts, callback)
end
vim.validate({
message = { message, "s" },
opts = { opts, "t" },
callback = { callback, "f" },
})
vim.validate({
choices = { opts.choices, "t", true },
default = { opts.default, "n", true },
type = { opts.type, "s", true },
})
vim.api.nvim_set_hl(0, "ConfirmCursor", { blend = 100, default = true })
if not opts.choices or vim.tbl_isempty(opts.choices) then
opts.choices = { "&OK" }
end
if not opts.default then
opts.default = 1
end
-- TODO this doesn't do anything yet
if not opts.type then
opts.type = "G"
else
opts.type = string.sub(opts.type, 1, 1)
end

local bufnr = vim.api.nvim_create_buf(false, true)
vim.bo[bufnr].buftype = "nofile"
vim.bo[bufnr].bufhidden = "wipe"
vim.bo[bufnr].swapfile = false
local winid

local guicursor = vim.o.guicursor
local function choose(idx)
local cb = callback
callback = function(_) end
if winid then
vim.api.nvim_win_close(winid, true)
end
vim.o.guicursor = guicursor
local choice = opts.choices[idx]
if choice then
choice = choice:gsub("&", "")
end
cb(idx, choice)
end
local function cancel()
choose(0)
end

local clean_choices = {}
local choice_shortcut_idx = {}
for i, choice in ipairs(opts.choices) do
local idx = choice:find("&")
local key
if idx and idx < string.len(choice) then
table.insert(clean_choices, choice:sub(1, idx - 1) .. choice:sub(idx + 1))
key = choice:sub(idx + 1, idx + 1)
table.insert(choice_shortcut_idx, idx)
else
key = choice:sub(1, 1)
table.insert(clean_choices, choice)
table.insert(choice_shortcut_idx, 1)
end
vim.keymap.set("n", key:lower(), function()
choose(i)
end, { buffer = bufnr })
vim.keymap.set("n", key:upper(), function()
choose(i)
end, { buffer = bufnr })
end
vim.keymap.set("n", "<C-c>", cancel, { buffer = bufnr })
vim.keymap.set("n", "<Esc>", cancel, { buffer = bufnr })

local lines = vim.split(message, "\n")
local highlights = {}
table.insert(lines, "")

-- Calculate the width of the choices if they are on a single line
local choices_width = 0
for _, choice in ipairs(clean_choices) do
choices_width = choices_width + vim.api.nvim_strwidth(choice)
end
-- Make sure to account for spacing
choices_width = choices_width + #clean_choices - 1

local desired_width = choices_width
for _, line in ipairs(lines) do
local len = string.len(line)
if len > desired_width then
desired_width = len
end
end

local width = util.calculate_width("editor", desired_width, config)

if width < choices_width then
-- Render one choice per line
for i, choice in ipairs(clean_choices) do
table.insert(lines, choice)
table.insert(highlights, { "Keyword", #lines, choice_shortcut_idx[i] - 1 })
end
else
-- Render all choices on a single line
local extra_spacing = width - choices_width
local line = ""
local num_dividers = #clean_choices - 1
for i, choice in ipairs(clean_choices) do
if i > 1 then
line = line .. " " .. string.rep(" ", math.floor(extra_spacing / num_dividers))
if extra_spacing % num_dividers >= i then
line = line .. " "
end
end
local col_start = line:len() - 1
line = line .. choice
table.insert(highlights, { "Keyword", #lines + 1, col_start + choice_shortcut_idx[i] })
end
table.insert(lines, line)
end

vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
local ns = vim.api.nvim_create_namespace("confirm")
for _, hl in ipairs(highlights) do
local group, lnum, col_start, col_end = unpack(hl)
if not col_end then
col_end = col_start + 1
end
vim.api.nvim_buf_add_highlight(bufnr, ns, group, lnum - 1, col_start, col_end)
end

local height = util.calculate_height("editor", #lines, config)
local winopt = {
relative = "editor",
border = config.border,
zindex = config.zindex,
style = "minimal",
width = width,
height = height,
col = math.floor((util.get_editor_width() - width) / 2),
row = math.floor((util.get_editor_height() - height) / 2),
}
if vim.fn.has("nvim-0.9") == 1 and config.title ~= "" then
winopt.title = config.title
winopt.title_pos = config.title_pos
end
winopt = config.override(winopt) or winopt
winid = vim.api.nvim_open_win(bufnr, true, winopt)
for k, v in pairs(config.buf_options) do
vim.api.nvim_buf_set_option(bufnr, k, v)
end
for k, v in pairs(config.win_options) do
vim.api.nvim_win_set_option(winid, k, v)
end
vim.o.guicursor = "a:ConfirmCursor"

vim.api.nvim_create_autocmd("BufLeave", {
buffer = bufnr,
callback = cancel,
once = true,
nested = true,
})
vim.api.nvim_create_autocmd("WinLeave", {
callback = cancel,
once = true,
nested = true,
})
vim.bo[bufnr].modifiable = false
end
2 changes: 1 addition & 1 deletion lua/dressing/patch.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local all_modules = { "input", "select" }
local all_modules = { "confirm", "input", "select" }

local M = {}

Expand Down
25 changes: 23 additions & 2 deletions lua/dressing/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,38 @@ local function calculate_dim(desired_size, size, min_size, max_size, total_size)
return math.floor(ret)
end

---@return integer
M.get_editor_width = function()
return vim.o.columns
end

---@return integer
M.get_editor_height = function()
local editor_height = vim.o.lines - vim.o.cmdheight
-- Subtract 1 if tabline is visible
if vim.o.showtabline == 2 or (vim.o.showtabline == 1 and #vim.api.nvim_list_tabpages() > 1) then
editor_height = editor_height - 1
end
-- Subtract 1 if statusline is visible
if
vim.o.laststatus >= 2 or (vim.o.laststatus == 1 and #vim.api.nvim_tabpage_list_wins(0) > 1)
then
editor_height = editor_height - 1
end
return editor_height
end

local function get_max_width(relative, winid)
if relative == "editor" then
return vim.o.columns
return M.get_editor_width()
else
return vim.api.nvim_win_get_width(winid or 0)
end
end

local function get_max_height(relative, winid)
if relative == "editor" then
return vim.o.lines - vim.o.cmdheight
return M.get_editor_height()
else
return vim.api.nvim_win_get_height(winid or 0)
end
Expand Down
Loading