mirror of
https://github.com/neovide/neovide.git
synced 2026-09-10 07:16:25 -04:00
Some checks failed
after some debugging, stable and nightly currently encode buffer writes
differently meaning that stable has something like:
{
data = ...,
id = "bufwrite",
percent = 0,
source = "nvim",
status = "running",
text = { '[file-path]' },
title = ""
}
against the current nightly
{
data = ...,
id = 'nvim.bufwrite "[file-path]"',
source = "nvim",
status = "running",
text = { '"[file-path]" ' },
title = ""
}
so missing percentages now finish the active progress item instead of starting
a 0% animation, while determinate progress continues to animate by id.
for example now we will show the progress bar only when a neovim producer
(a plugin or any process event that uses nvim_echo) sends a real determinate percent.
vim.api.nvim_echo({ { "my-progress-bar." } }, true, {
kind = "progress",
id = "neovide-task",
source = "plugin-name",
percent = 25,
status = "running",
})
462 lines
14 KiB
Lua
462 lines
14 KiB
Lua
---@class Args
|
|
---@field neovide_channel_id integer
|
|
---@field neovide_version string
|
|
---@field config_path string
|
|
---@field register_clipboard boolean
|
|
---@field register_right_click boolean
|
|
---@field remote boolean
|
|
---@field enable_focus_command boolean
|
|
---@field global_variable_settings string[]
|
|
---@field option_settings string[]
|
|
|
|
---@type Args
|
|
local args = ...
|
|
|
|
local M = {}
|
|
M.private = {}
|
|
|
|
vim.g.neovide_channel_id = args.neovide_channel_id
|
|
vim.g.neovide_version = args.neovide_version
|
|
|
|
-- Set some basic rendering options.
|
|
vim.o.lazyredraw = false
|
|
vim.o.termguicolors = true
|
|
|
|
local function set_default_window_title()
|
|
local title_info = vim.api.nvim_get_option_info2("title", {})
|
|
local titlestring_info = vim.api.nvim_get_option_info2("titlestring", {})
|
|
|
|
if title_info.was_set or titlestring_info.was_set then
|
|
return
|
|
end
|
|
|
|
vim.o.title = true
|
|
vim.o.titlestring = "%F"
|
|
end
|
|
|
|
set_default_window_title()
|
|
|
|
local function rpcnotify(method, ...)
|
|
vim.rpcnotify(vim.g.neovide_channel_id, method, ...)
|
|
end
|
|
|
|
local function rpcrequest(method, ...)
|
|
return vim.rpcrequest(vim.g.neovide_channel_id, method, ...)
|
|
end
|
|
|
|
local function set_clipboard(register)
|
|
return function(lines)
|
|
rpcrequest("neovide.set_clipboard", lines, register)
|
|
end
|
|
end
|
|
|
|
local function get_clipboard(register)
|
|
return function()
|
|
return rpcrequest("neovide.get_clipboard", register)
|
|
end
|
|
end
|
|
|
|
-- Quit when Command+Q is pressed on macOS
|
|
if vim.fn.has("macunix") then
|
|
vim.keymap.set({ "n", "i", "c", "v", "o", "t", "l" }, "<D-q>", function()
|
|
rpcnotify("neovide.exec_detach_handler")
|
|
end)
|
|
end
|
|
|
|
if args.register_clipboard and not vim.g.neovide_no_custom_clipboard then
|
|
vim.g.clipboard = {
|
|
name = "neovide",
|
|
copy = {
|
|
["+"] = set_clipboard("+"),
|
|
["*"] = set_clipboard("*"),
|
|
},
|
|
paste = {
|
|
["+"] = get_clipboard("+"),
|
|
["*"] = get_clipboard("*"),
|
|
},
|
|
cache_enabled = false,
|
|
}
|
|
vim.g.loaded_clipboard_provider = nil
|
|
vim.cmd.runtime("autoload/provider/clipboard.vim")
|
|
end
|
|
|
|
if args.register_right_click then
|
|
vim.api.nvim_create_user_command("NeovideRegisterRightClick", function()
|
|
rpcnotify("neovide.register_right_click")
|
|
end, {})
|
|
vim.api.nvim_create_user_command("NeovideUnregisterRightClick", function()
|
|
rpcnotify("neovide.unregister_right_click")
|
|
end, {})
|
|
end
|
|
|
|
vim.api.nvim_create_user_command("NeovideFocus", function()
|
|
rpcnotify("neovide.focus_window")
|
|
end, {})
|
|
|
|
|
|
if vim.fn.has("mac") == 1 then
|
|
local URL_PATTERN = "https?://[%w-_%.]+%.%w[%w-_%.%%%?%.:/+=&%%[%]#]*"
|
|
local compat_unpack = table.unpack or unpack
|
|
local function find_span(line, cursor_column, pattern, plain)
|
|
local start_pos = 1
|
|
local search_limit = #line + 1
|
|
for _ = 1, search_limit do
|
|
local match_start, match_end = line:find(pattern, start_pos, plain)
|
|
if not match_start then
|
|
break
|
|
end
|
|
if cursor_column >= match_start and cursor_column <= match_end then
|
|
return match_start, match_end
|
|
end
|
|
start_pos = match_end + 1
|
|
end
|
|
return nil, nil
|
|
end
|
|
|
|
local function detect_url(line, cursor_column)
|
|
local url_start, url_end = find_span(line, cursor_column, URL_PATTERN, false)
|
|
if not url_start then
|
|
return nil
|
|
end
|
|
return {
|
|
entity = line:sub(url_start, url_end),
|
|
col = url_start - 1,
|
|
kind = "url",
|
|
}
|
|
end
|
|
|
|
local function detect_file(line, cursor_column)
|
|
local cfile = vim.fn.expand("<cfile>")
|
|
if not cfile or cfile == "" then
|
|
return nil
|
|
end
|
|
local literal_start = find_span(line, cursor_column, cfile, true)
|
|
if not literal_start then
|
|
return nil
|
|
end
|
|
local absolute_path = vim.fn.fnamemodify(cfile, ":p")
|
|
if absolute_path == "" or not vim.loop.fs_stat(absolute_path) then
|
|
return nil
|
|
end
|
|
return {
|
|
entity = absolute_path,
|
|
col = literal_start - 1,
|
|
kind = "file",
|
|
}
|
|
end
|
|
|
|
local function detect_word(line, cursor_column)
|
|
local word, word_col =
|
|
compat_unpack(vim.fn.matchstrpos(line, [[\k*\%]] .. cursor_column .. [[c\k*]]))
|
|
if not word or word == "" then
|
|
return nil
|
|
end
|
|
return {
|
|
entity = word,
|
|
col = word_col,
|
|
kind = "text",
|
|
}
|
|
end
|
|
|
|
local ENTITY_DETECTORS = { detect_url, detect_file, detect_word }
|
|
|
|
local function detect_entity(line, cursor_col)
|
|
local cursor_column = cursor_col + 1
|
|
for _, detector in ipairs(ENTITY_DETECTORS) do
|
|
local match = detector(line, cursor_column)
|
|
if match then
|
|
return match
|
|
end
|
|
end
|
|
return {
|
|
entity = "",
|
|
col = cursor_col,
|
|
kind = "text",
|
|
}
|
|
end
|
|
|
|
local function take_entity_under_cursor()
|
|
local mouse_pos = vim.fn.getmousepos()
|
|
local guifont = vim.api.nvim_get_option("guifont")
|
|
local cursor = vim.api.nvim_win_get_cursor(0)
|
|
local line = vim.api.nvim_get_current_line()
|
|
|
|
local match = detect_entity(line, cursor[2])
|
|
local screenpos = vim.fn.screenpos(mouse_pos.winid, cursor[1], match.col + 1)
|
|
local screen_row = math.max(screenpos.row - 1, 0)
|
|
local screen_col = math.max(screenpos.col - 1, 0)
|
|
|
|
return screen_col, screen_row, match.entity, guifont, match.kind
|
|
end
|
|
|
|
vim.api.nvim_create_user_command("NeovideForceClick", function()
|
|
local col, row, entity, guifont, entity_kind = take_entity_under_cursor()
|
|
rpcnotify("neovide.force_click", col, row, entity, guifont, entity_kind)
|
|
end, {})
|
|
end
|
|
|
|
vim.api.nvim_exec2(
|
|
[[
|
|
function! WatchGlobal(variable, callback)
|
|
call dictwatcheradd(g:, a:variable, a:callback)
|
|
endfunction
|
|
]],
|
|
{ output = false }
|
|
)
|
|
|
|
for _, global_variable_setting in ipairs(args.global_variable_settings) do
|
|
local callback = function()
|
|
rpcnotify("setting_changed", global_variable_setting, vim.g["neovide_" .. global_variable_setting])
|
|
end
|
|
vim.fn.WatchGlobal("neovide_" .. global_variable_setting, callback)
|
|
end
|
|
|
|
for _, option_setting in ipairs(args.option_settings) do
|
|
vim.api.nvim_create_autocmd({ "OptionSet" }, {
|
|
pattern = option_setting,
|
|
once = false,
|
|
nested = true,
|
|
callback = function()
|
|
rpcnotify("option_changed", option_setting, vim.o[option_setting])
|
|
end,
|
|
})
|
|
end
|
|
|
|
-- Ignore initial values of lines and columns because they are set by neovim directly.
|
|
-- See https://github.com/neovide/neovide/issues/2300
|
|
vim.api.nvim_create_autocmd({ "VimEnter" }, {
|
|
once = true,
|
|
nested = true,
|
|
callback = function()
|
|
for _, option_setting in ipairs(args.option_settings) do
|
|
if option_setting ~= "lines" and option_setting ~= "columns" then
|
|
rpcnotify("option_changed", option_setting, vim.o[option_setting])
|
|
end
|
|
end
|
|
end,
|
|
})
|
|
|
|
if vim.fn.has("macunix") == 1 then
|
|
local document_state_group = vim.api.nvim_create_augroup("NeovideDocumentState", { clear = true })
|
|
local function notify_document_state()
|
|
local bufnr = vim.api.nvim_get_current_buf()
|
|
local path = vim.api.nvim_buf_get_name(bufnr)
|
|
local modified = vim.api.nvim_get_option_value("modified", { buf = bufnr })
|
|
pcall(rpcnotify, "neovide.document_state", path, modified)
|
|
end
|
|
|
|
local function enable_document_state()
|
|
vim.api.nvim_create_autocmd({
|
|
"BufEnter",
|
|
"BufFilePost",
|
|
"BufModifiedSet",
|
|
"BufWritePost",
|
|
}, {
|
|
group = document_state_group,
|
|
callback = notify_document_state,
|
|
})
|
|
|
|
vim.api.nvim_create_autocmd("VimEnter", {
|
|
group = document_state_group,
|
|
once = true,
|
|
nested = true,
|
|
callback = notify_document_state,
|
|
})
|
|
|
|
if vim.v.vim_did_enter == 1 then
|
|
notify_document_state()
|
|
end
|
|
end
|
|
|
|
local function update_document_state()
|
|
vim.api.nvim_clear_autocmds({ group = document_state_group })
|
|
|
|
if vim.g.neovide_proxy_icon then
|
|
enable_document_state()
|
|
else
|
|
pcall(rpcnotify, "neovide.document_state", "", false)
|
|
end
|
|
end
|
|
|
|
vim.fn.WatchGlobal("neovide_proxy_icon", update_document_state)
|
|
update_document_state()
|
|
end
|
|
|
|
-- Create auto command for retrieving exit code from neovim on quit.
|
|
vim.api.nvim_create_autocmd({ "VimLeavePre" }, {
|
|
pattern = "*",
|
|
once = true,
|
|
nested = true,
|
|
callback = function()
|
|
rpcrequest("neovide.quit", vim.v.exiting)
|
|
end,
|
|
})
|
|
|
|
M.private.dropfile = function(filename, tabs)
|
|
vim.api.nvim_cmd({
|
|
cmd = "drop",
|
|
args = { vim.fn.fnameescape(filename) },
|
|
-- Always open as the last tabpage
|
|
mods = tabs and { tab = #vim.api.nvim_list_tabpages() } or {},
|
|
}, {})
|
|
end
|
|
|
|
M.private.can_set_background = function()
|
|
local info = vim.api.nvim_get_option_info2("background", {})
|
|
-- Don't change the background if someone else has set it
|
|
if info.was_set and info.last_set_chan ~= args.neovide_channel_id then
|
|
return false
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
|
|
M.disable_redraw = function()
|
|
-- Wrap inside pcall to avoid errors if Neovide disconnects
|
|
pcall(rpcnotify, "neovide.set_redraw", false)
|
|
end
|
|
|
|
M.enable_redraw = function()
|
|
-- Wrap inside pcall to avoid errors if Neovide disconnects
|
|
pcall(rpcnotify, "neovide.set_redraw", true)
|
|
end
|
|
|
|
vim.api.nvim_create_user_command("NeovideConfig", function()
|
|
if args.remote then
|
|
if vim.fn.filereadable(args.config_path) ~= 0 then
|
|
vim.notify(
|
|
"Neovide is running as a remote server. So the config file may not be on this machine.\n"
|
|
.. "Open it manually if you intend to edit the file anyway.\n"
|
|
.. "Config file location: "
|
|
.. args.config_path,
|
|
vim.log.levels.WARN,
|
|
{ title = "Neovide" }
|
|
)
|
|
end
|
|
else
|
|
vim.cmd('edit ' .. vim.fn.fnameescape(args.config_path))
|
|
end
|
|
end, {})
|
|
|
|
local function progress_bar(data)
|
|
-- Wrap inside pcall to avoid errors if Neovide disconnects.
|
|
pcall(rpcnotify, "neovide.progress_bar", data)
|
|
end
|
|
|
|
local function buffer_is_empty(bufnr)
|
|
if vim.api.nvim_buf_line_count(bufnr) ~= 1 then
|
|
return false
|
|
end
|
|
local first_line = vim.api.nvim_buf_get_lines(bufnr, 0, 1, true)[1]
|
|
return first_line == nil or first_line == ""
|
|
end
|
|
|
|
local function current_window_is_only_normal()
|
|
local current = vim.api.nvim_get_current_win()
|
|
local found
|
|
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
|
local config = vim.api.nvim_win_get_config(win)
|
|
if config.relative == "" then
|
|
if found ~= nil then
|
|
return false
|
|
end
|
|
found = win
|
|
end
|
|
end
|
|
return found ~= nil and found == current
|
|
end
|
|
|
|
local function shortmess_allows_intro()
|
|
return not string.find(vim.o.shortmess or "", "I", 1, true)
|
|
end
|
|
|
|
local function should_show_intro_banner()
|
|
local current_buffer = vim.api.nvim_get_current_buf()
|
|
local buffer_name = vim.api.nvim_buf_get_name(current_buffer)
|
|
local current_window = vim.api.nvim_get_current_win()
|
|
|
|
return buffer_is_empty(current_buffer)
|
|
and buffer_name == ""
|
|
and current_buffer == 1
|
|
and current_window == 1000
|
|
and current_window_is_only_normal()
|
|
and shortmess_allows_intro()
|
|
end
|
|
|
|
local intro_banner_state
|
|
|
|
local function notify_intro_state()
|
|
local allowed = false
|
|
local ok, result = pcall(should_show_intro_banner)
|
|
if ok then
|
|
allowed = result
|
|
end
|
|
|
|
if intro_banner_state ~= allowed then
|
|
intro_banner_state = allowed
|
|
pcall(rpcnotify, "neovide.intro_banner_allowed", allowed)
|
|
end
|
|
end
|
|
|
|
local function schedule_intro_state_check()
|
|
vim.schedule(notify_intro_state)
|
|
end
|
|
|
|
local intro_group = vim.api.nvim_create_augroup("NeovideIntroBanner", { clear = true })
|
|
|
|
vim.api.nvim_create_autocmd({
|
|
"VimEnter",
|
|
"BufEnter",
|
|
"BufFilePost",
|
|
"BufNewFile",
|
|
"BufReadPost",
|
|
"TextChanged",
|
|
"TextChangedI",
|
|
"WinEnter",
|
|
"WinNew",
|
|
"WinClosed",
|
|
"TabEnter",
|
|
"TabClosed",
|
|
}, {
|
|
group = intro_group,
|
|
callback = schedule_intro_state_check,
|
|
})
|
|
|
|
vim.api.nvim_create_autocmd("OptionSet", {
|
|
group = intro_group,
|
|
pattern = "shortmess",
|
|
callback = schedule_intro_state_check,
|
|
})
|
|
|
|
notify_intro_state()
|
|
|
|
pcall(vim.api.nvim_create_autocmd, "Progress", {
|
|
group = vim.api.nvim_create_augroup("NeovideProgressBar", { clear = true }),
|
|
desc = "Forward progress events to Neovide",
|
|
callback = function(ev)
|
|
if ev.data then
|
|
progress_bar({
|
|
id = ev.data.id,
|
|
source = ev.data.source,
|
|
status = ev.data.status,
|
|
percent = ev.data.percent,
|
|
title = ev.data.title or "",
|
|
message = ev.data.message or "",
|
|
})
|
|
end
|
|
end,
|
|
})
|
|
|
|
---@param preedit_raw_text string
|
|
---@param cursor_offset_start integer This values show the cursor begin position. The position is byte-wise indexed.
|
|
---@param cursor_offset_end integer This values show the cursor end position. The position is byte-wise indexed.
|
|
M.preedit_handler = function(preedit_raw_text, cursor_offset_start, cursor_offset_end) end
|
|
|
|
---@param commit_raw_text string
|
|
---@param commit_formatted_text string It's escaped.
|
|
M.commit_handler = function(commit_raw_text, commit_formatted_text)
|
|
vim.api.nvim_input(commit_formatted_text)
|
|
end
|
|
|
|
_G["neovide"] = M
|