local args = ...
local M = {}
M.private = {}
vim.g.neovide_channel_id = args.neovide_channel_id
vim.g.neovide_version = args.neovide_version
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
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
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,
})
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) },
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", {})
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()
pcall(rpcnotify, "neovide.set_redraw", false)
end
M.enable_redraw = function()
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)
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 and ev.data.status == 'running' then
progress_bar({
percent = ev.data.percent or 0,
title = ev.data.title or "",
message = ev.data.message or "",
})
else
progress_bar({ percent = 100 })
end
end,
})
M.preedit_handler = function(preedit_raw_text, cursor_offset_start, cursor_offset_end) end
M.commit_handler = function(commit_raw_text, commit_formatted_text)
vim.api.nvim_input(commit_formatted_text)
end
_G["neovide"] = M