From 0813d9579939e248f309179287e2e8fa0d512bf1 Mon Sep 17 00:00:00 2001 From: Lin Zhenyun Date: Fri, 21 Mar 2025 16:36:18 +0800 Subject: [PATCH 1/5] fix: handle multiple copilot server instances - Stop old client instance instead of erroring when multiple servers are started - Replace deprecated vim.loop with vim.uv --- lua/copilot/client.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lua/copilot/client.lua b/lua/copilot/client.lua index c501e0be..54e2dbb2 100644 --- a/lua/copilot/client.lua +++ b/lua/copilot/client.lua @@ -23,8 +23,7 @@ local M = { local function store_client_id(id) if M.id and M.id ~= id then if vim.lsp.get_client_by_id(M.id) then - logger.error("unexpectedly started multiple copilot servers") - return + vim.lsp.stop_client(M.id) end end @@ -170,7 +169,7 @@ function M.use_client(callback) return end - local timer, err, _ = vim.loop.new_timer() + local timer, err, _ = vim.uv.new_timer() if not timer then logger.error(string.format("error creating timer: %s", err)) From 039541596b610e428eb3d63882cd2d95802dc7b0 Mon Sep 17 00:00:00 2001 From: Lin Zhenyun Date: Fri, 21 Mar 2025 17:41:11 +0800 Subject: [PATCH 2/5] fix: avoid LSP error with unsupported window/logMessage Some LSP servers like jsonlsp don't support window/logMessage method, causing potential errors. Move the handler to client config to avoid this. --- lua/copilot/client.lua | 23 +++ lua/copilot/logger.lua | 327 +++++++++++++++++++---------------------- 2 files changed, 175 insertions(+), 175 deletions(-) diff --git a/lua/copilot/client.lua b/lua/copilot/client.lua index 54e2dbb2..d01c7547 100644 --- a/lua/copilot/client.lua +++ b/lua/copilot/client.lua @@ -222,6 +222,29 @@ local function prepare_client_config(overrides) PanelSolutionsDone = api.handlers.PanelSolutionsDone, statusNotification = api.handlers.statusNotification, ["copilot/openURL"] = api.handlers["copilot/openURL"], + -- set up "window/logMessage" handler here instead of in `logger.lua`, since some other lsp servers don't support this method, such as jsonlsp + ["window/logMessage"] = function(_, result, _) + if not result then + return + end + + local message = string.format("LSP message: %s", result.message) + local message_type = result.type --[[@as integer]] + + if message_type == 1 then + logger.error(message) + elseif message_type == 2 then + logger.warn(message) + elseif message_type == 3 then + logger.info(message) + elseif message_type == 4 then + logger.info(message) + elseif message_type == 5 then + logger.debug(message) + else + logger.trace(message) + end + end, } local root_dir = config.get_root_dir() diff --git a/lua/copilot/logger.lua b/lua/copilot/logger.lua index 053cfd88..6c27e14d 100644 --- a/lua/copilot/logger.lua +++ b/lua/copilot/logger.lua @@ -1,175 +1,152 @@ -local uv = vim.uv - ----@class logger -local mod = { - log_to_file = false, - log_file = vim.fn.stdpath("log") .. "/copilot-lua.log", - file_log_level = vim.log.levels.WARN, - print_log = true, - print_log_level = vim.log.levels.WARN, -} - -local log_level_names = { - [vim.log.levels.ERROR] = "ERROR", --4 - [vim.log.levels.WARN] = "WARN", --3 - [vim.log.levels.INFO] = "INFO", --2 - [vim.log.levels.DEBUG] = "DEBUG", --1 - [vim.log.levels.TRACE] = "TRACE", --0 -} - ----@return string timestamp -local function get_timestamp_with_ms() - local seconds = os.time() - local milliseconds = math.floor((os.clock() % 1) * 1000) - return string.format("%s.%03d", os.date("%Y-%m-%d %H:%M:%S", seconds), milliseconds) -end - ----@param log_level integer --vim.log.levels ----@param msg string ----@param data any ----@return string log_msg -local function format_log(log_level, msg, data) - local log_level_name = log_level_names[log_level] - local log_msg = string.format("%s [%s]: %s", get_timestamp_with_ms(), log_level_name, msg) - - if data then - log_msg = string.format("%s\n%s", log_msg, vim.inspect(data)) - end - - return log_msg -end - ----@param log_level integer -- one of the vim.log.levels ----@param msg string ----@param data any -local function notify_log(log_level, msg, data) - local log_msg = format_log(log_level, msg, data) - vim.notify(log_msg, log_level) -end - ----@param log_level integer -- one of the vim.log.levels ----@param log_file string ----@param msg string ----@param data any -local function write_log(log_level, log_file, msg, data) - local log_msg = format_log(log_level, msg, data) .. "\n" - - uv.fs_open(log_file, "a", tonumber("644", 8), function(err, fd) - if err then - notify_log(vim.log.levels.ERROR, "Failed to open log file: " .. err) - return - end - - uv.fs_write(fd, log_msg, -1, function(write_err) - if write_err then - notify_log(vim.log.levels.ERROR, "Failed to write to log file: " .. write_err) - end - - uv.fs_close(fd) - end) - end) -end - ----@param log_level integer -- one of the vim.log.levels ----@param msg string ----@param data any ----@param force_print boolean -function mod.log(log_level, msg, data, force_print) - if mod.log_to_file and (mod.file_log_level <= log_level) then - write_log(log_level, mod.log_file, msg, data) - end - - if force_print or (mod.print_log and (mod.print_log_level <= log_level)) then - notify_log(log_level, msg, data) - end -end - ----@param msg string ----@param data any -function mod.debug(msg, data) - mod.log(vim.log.levels.DEBUG, msg, data, false) -end - ----@param msg string ----@param data any -function mod.trace(msg, data) - mod.log(vim.log.levels.TRACE, msg, data, false) -end - ----@param msg string ----@param data any -function mod.error(msg, data) - mod.log(vim.log.levels.ERROR, msg, data, false) -end - ----@param msg string ----@param data any -function mod.warn(msg, data) - mod.log(vim.log.levels.WARN, msg, data, false) -end - ----@param msg string ----@param data any -function mod.info(msg, data) - mod.log(vim.log.levels.INFO, msg, data, false) -end - ----@param msg string ----@param data any -function mod.notify(msg, data) - mod.log(vim.log.levels.INFO, msg, data, true) -end - ----@param conf copilot_config_logging -function mod.setup(conf) - mod.log_file = conf.file - mod.file_log_level = conf.file_log_level - mod.print_log_level = conf.print_log_level - mod.log_to_file = conf.log_to_file - mod.print_log = conf.print_log - - if conf.trace_lsp ~= "off" then - vim.lsp.handlers["$/logTrace"] = function(_, result, _) - if not result then - return - end - - mod.trace(string.format("LSP trace - %s", result.message), result.verbose) - end - end - - if conf.trace_lsp_progress then - vim.lsp.handlers["$/progress"] = function(_, result, _) - if not result then - return - end - - mod.trace(string.format("LSP progress - token %s", result.token), result.value) - end - end - - vim.lsp.handlers["window/logMessage"] = function(_, result, _) - if not result then - return - end - - local message = string.format("LSP message: %s", result.message) - local message_type = result.type --[[@as integer]] - - if message_type == 1 then - mod.error(message) - elseif message_type == 2 then - mod.warn(message) - elseif message_type == 3 then - mod.info(message) - elseif message_type == 4 then - mod.info(message) - elseif message_type == 5 then - mod.debug(message) - else - mod.trace(message) - end - end -end - -return mod +local uv = vim.uv + +---@class logger +local mod = { + log_to_file = false, + log_file = vim.fn.stdpath("log") .. "/copilot-lua.log", + file_log_level = vim.log.levels.WARN, + print_log = true, + print_log_level = vim.log.levels.WARN, +} + +local log_level_names = { + [vim.log.levels.ERROR] = "ERROR", --4 + [vim.log.levels.WARN] = "WARN", --3 + [vim.log.levels.INFO] = "INFO", --2 + [vim.log.levels.DEBUG] = "DEBUG", --1 + [vim.log.levels.TRACE] = "TRACE", --0 +} + +---@return string timestamp +local function get_timestamp_with_ms() + local seconds = os.time() + local milliseconds = math.floor((os.clock() % 1) * 1000) + return string.format("%s.%03d", os.date("%Y-%m-%d %H:%M:%S", seconds), milliseconds) +end + +---@param log_level integer --vim.log.levels +---@param msg string +---@param data any +---@return string log_msg +local function format_log(log_level, msg, data) + local log_level_name = log_level_names[log_level] + local log_msg = string.format("%s [%s]: %s", get_timestamp_with_ms(), log_level_name, msg) + + if data then + log_msg = string.format("%s\n%s", log_msg, vim.inspect(data)) + end + + return log_msg +end + +---@param log_level integer -- one of the vim.log.levels +---@param msg string +---@param data any +local function notify_log(log_level, msg, data) + local log_msg = format_log(log_level, msg, data) + vim.notify(log_msg, log_level) +end + +---@param log_level integer -- one of the vim.log.levels +---@param log_file string +---@param msg string +---@param data any +local function write_log(log_level, log_file, msg, data) + local log_msg = format_log(log_level, msg, data) .. "\n" + + uv.fs_open(log_file, "a", tonumber("644", 8), function(err, fd) + if err then + notify_log(vim.log.levels.ERROR, "Failed to open log file: " .. err) + return + end + + uv.fs_write(fd, log_msg, -1, function(write_err) + if write_err then + notify_log(vim.log.levels.ERROR, "Failed to write to log file: " .. write_err) + end + + uv.fs_close(fd) + end) + end) +end + +---@param log_level integer -- one of the vim.log.levels +---@param msg string +---@param data any +---@param force_print boolean +function mod.log(log_level, msg, data, force_print) + if mod.log_to_file and (mod.file_log_level <= log_level) then + write_log(log_level, mod.log_file, msg, data) + end + + if force_print or (mod.print_log and (mod.print_log_level <= log_level)) then + notify_log(log_level, msg, data) + end +end + +---@param msg string +---@param data any +function mod.debug(msg, data) + mod.log(vim.log.levels.DEBUG, msg, data, false) +end + +---@param msg string +---@param data any +function mod.trace(msg, data) + mod.log(vim.log.levels.TRACE, msg, data, false) +end + +---@param msg string +---@param data any +function mod.error(msg, data) + mod.log(vim.log.levels.ERROR, msg, data, false) +end + +---@param msg string +---@param data any +function mod.warn(msg, data) + mod.log(vim.log.levels.WARN, msg, data, false) +end + +---@param msg string +---@param data any +function mod.info(msg, data) + mod.log(vim.log.levels.INFO, msg, data, false) +end + +---@param msg string +---@param data any +function mod.notify(msg, data) + mod.log(vim.log.levels.INFO, msg, data, true) +end + +---@param conf copilot_config_logging +function mod.setup(conf) + mod.log_file = conf.file + mod.file_log_level = conf.file_log_level + mod.print_log_level = conf.print_log_level + mod.log_to_file = conf.log_to_file + mod.print_log = conf.print_log + + if conf.trace_lsp ~= "off" then + vim.lsp.handlers["$/logTrace"] = function(_, result, _) + if not result then + return + end + + mod.trace(string.format("LSP trace - %s", result.message), result.verbose) + end + end + + if conf.trace_lsp_progress then + vim.lsp.handlers["$/progress"] = function(_, result, _) + if not result then + return + end + + mod.trace(string.format("LSP progress - token %s", result.token), result.value) + end + end +end + +return mod From fe3b387e7400edcf87d1e269da656261288a8154 Mon Sep 17 00:00:00 2001 From: Lin Zhenyun Date: Fri, 21 Mar 2025 17:53:18 +0800 Subject: [PATCH 3/5] fix: add required editor info for LSP initialization Fix LSP warning by providing required editorInfo and editorPluginInfo fields in initializationOptions for the Copilot LSP client. --- lua/copilot/client.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lua/copilot/client.lua b/lua/copilot/client.lua index d01c7547..cb531180 100644 --- a/lua/copilot/client.lua +++ b/lua/copilot/client.lua @@ -272,6 +272,8 @@ local function prepare_client_config(overrides) end end + local editor_info = util.get_editor_info() + -- LSP config, not to be confused with config.lua return vim.tbl_deep_extend("force", { cmd = { @@ -326,6 +328,9 @@ local function prepare_client_config(overrides) handlers = handlers, init_options = { copilotIntegrationId = "vscode-chat", + -- Fix LSP warning: editorInfo and editorPluginInfo will soon be required in initializationOptions + editorInfo = editor_info.editorInfo, + editorPluginInfo = editor_info.editorPluginInfo, }, workspace_folders = workspace_folders, trace = config.get("trace") or "off", From 865e74b9c640a63b06a2b06a460d64dab4f9c976 Mon Sep 17 00:00:00 2001 From: Antoine Gaudreau Simard Date: Fri, 21 Mar 2025 08:17:35 -0400 Subject: [PATCH 4/5] add some context to comment --- lua/copilot/client.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/copilot/client.lua b/lua/copilot/client.lua index cb531180..d53f9532 100644 --- a/lua/copilot/client.lua +++ b/lua/copilot/client.lua @@ -329,6 +329,8 @@ local function prepare_client_config(overrides) init_options = { copilotIntegrationId = "vscode-chat", -- Fix LSP warning: editorInfo and editorPluginInfo will soon be required in initializationOptions + -- We are sending these twice for the time being as it will become required here and we get a warning if not set. + -- However if not passed in setEditorInfo, that one returns an error. editorInfo = editor_info.editorInfo, editorPluginInfo = editor_info.editorPluginInfo, }, From e0801e6f3269efc932ad2576a6b95bd004d35c73 Mon Sep 17 00:00:00 2001 From: Antoine Gaudreau Simard Date: Fri, 21 Mar 2025 08:17:58 -0400 Subject: [PATCH 5/5] Revert "fix: avoid LSP error with unsupported window/logMessage" This reverts commit 039541596b610e428eb3d63882cd2d95802dc7b0. --- lua/copilot/client.lua | 23 --- lua/copilot/logger.lua | 327 ++++++++++++++++++++++------------------- 2 files changed, 175 insertions(+), 175 deletions(-) diff --git a/lua/copilot/client.lua b/lua/copilot/client.lua index d53f9532..ca9cd201 100644 --- a/lua/copilot/client.lua +++ b/lua/copilot/client.lua @@ -222,29 +222,6 @@ local function prepare_client_config(overrides) PanelSolutionsDone = api.handlers.PanelSolutionsDone, statusNotification = api.handlers.statusNotification, ["copilot/openURL"] = api.handlers["copilot/openURL"], - -- set up "window/logMessage" handler here instead of in `logger.lua`, since some other lsp servers don't support this method, such as jsonlsp - ["window/logMessage"] = function(_, result, _) - if not result then - return - end - - local message = string.format("LSP message: %s", result.message) - local message_type = result.type --[[@as integer]] - - if message_type == 1 then - logger.error(message) - elseif message_type == 2 then - logger.warn(message) - elseif message_type == 3 then - logger.info(message) - elseif message_type == 4 then - logger.info(message) - elseif message_type == 5 then - logger.debug(message) - else - logger.trace(message) - end - end, } local root_dir = config.get_root_dir() diff --git a/lua/copilot/logger.lua b/lua/copilot/logger.lua index 6c27e14d..053cfd88 100644 --- a/lua/copilot/logger.lua +++ b/lua/copilot/logger.lua @@ -1,152 +1,175 @@ -local uv = vim.uv - ----@class logger -local mod = { - log_to_file = false, - log_file = vim.fn.stdpath("log") .. "/copilot-lua.log", - file_log_level = vim.log.levels.WARN, - print_log = true, - print_log_level = vim.log.levels.WARN, -} - -local log_level_names = { - [vim.log.levels.ERROR] = "ERROR", --4 - [vim.log.levels.WARN] = "WARN", --3 - [vim.log.levels.INFO] = "INFO", --2 - [vim.log.levels.DEBUG] = "DEBUG", --1 - [vim.log.levels.TRACE] = "TRACE", --0 -} - ----@return string timestamp -local function get_timestamp_with_ms() - local seconds = os.time() - local milliseconds = math.floor((os.clock() % 1) * 1000) - return string.format("%s.%03d", os.date("%Y-%m-%d %H:%M:%S", seconds), milliseconds) -end - ----@param log_level integer --vim.log.levels ----@param msg string ----@param data any ----@return string log_msg -local function format_log(log_level, msg, data) - local log_level_name = log_level_names[log_level] - local log_msg = string.format("%s [%s]: %s", get_timestamp_with_ms(), log_level_name, msg) - - if data then - log_msg = string.format("%s\n%s", log_msg, vim.inspect(data)) - end - - return log_msg -end - ----@param log_level integer -- one of the vim.log.levels ----@param msg string ----@param data any -local function notify_log(log_level, msg, data) - local log_msg = format_log(log_level, msg, data) - vim.notify(log_msg, log_level) -end - ----@param log_level integer -- one of the vim.log.levels ----@param log_file string ----@param msg string ----@param data any -local function write_log(log_level, log_file, msg, data) - local log_msg = format_log(log_level, msg, data) .. "\n" - - uv.fs_open(log_file, "a", tonumber("644", 8), function(err, fd) - if err then - notify_log(vim.log.levels.ERROR, "Failed to open log file: " .. err) - return - end - - uv.fs_write(fd, log_msg, -1, function(write_err) - if write_err then - notify_log(vim.log.levels.ERROR, "Failed to write to log file: " .. write_err) - end - - uv.fs_close(fd) - end) - end) -end - ----@param log_level integer -- one of the vim.log.levels ----@param msg string ----@param data any ----@param force_print boolean -function mod.log(log_level, msg, data, force_print) - if mod.log_to_file and (mod.file_log_level <= log_level) then - write_log(log_level, mod.log_file, msg, data) - end - - if force_print or (mod.print_log and (mod.print_log_level <= log_level)) then - notify_log(log_level, msg, data) - end -end - ----@param msg string ----@param data any -function mod.debug(msg, data) - mod.log(vim.log.levels.DEBUG, msg, data, false) -end - ----@param msg string ----@param data any -function mod.trace(msg, data) - mod.log(vim.log.levels.TRACE, msg, data, false) -end - ----@param msg string ----@param data any -function mod.error(msg, data) - mod.log(vim.log.levels.ERROR, msg, data, false) -end - ----@param msg string ----@param data any -function mod.warn(msg, data) - mod.log(vim.log.levels.WARN, msg, data, false) -end - ----@param msg string ----@param data any -function mod.info(msg, data) - mod.log(vim.log.levels.INFO, msg, data, false) -end - ----@param msg string ----@param data any -function mod.notify(msg, data) - mod.log(vim.log.levels.INFO, msg, data, true) -end - ----@param conf copilot_config_logging -function mod.setup(conf) - mod.log_file = conf.file - mod.file_log_level = conf.file_log_level - mod.print_log_level = conf.print_log_level - mod.log_to_file = conf.log_to_file - mod.print_log = conf.print_log - - if conf.trace_lsp ~= "off" then - vim.lsp.handlers["$/logTrace"] = function(_, result, _) - if not result then - return - end - - mod.trace(string.format("LSP trace - %s", result.message), result.verbose) - end - end - - if conf.trace_lsp_progress then - vim.lsp.handlers["$/progress"] = function(_, result, _) - if not result then - return - end - - mod.trace(string.format("LSP progress - token %s", result.token), result.value) - end - end -end - -return mod +local uv = vim.uv + +---@class logger +local mod = { + log_to_file = false, + log_file = vim.fn.stdpath("log") .. "/copilot-lua.log", + file_log_level = vim.log.levels.WARN, + print_log = true, + print_log_level = vim.log.levels.WARN, +} + +local log_level_names = { + [vim.log.levels.ERROR] = "ERROR", --4 + [vim.log.levels.WARN] = "WARN", --3 + [vim.log.levels.INFO] = "INFO", --2 + [vim.log.levels.DEBUG] = "DEBUG", --1 + [vim.log.levels.TRACE] = "TRACE", --0 +} + +---@return string timestamp +local function get_timestamp_with_ms() + local seconds = os.time() + local milliseconds = math.floor((os.clock() % 1) * 1000) + return string.format("%s.%03d", os.date("%Y-%m-%d %H:%M:%S", seconds), milliseconds) +end + +---@param log_level integer --vim.log.levels +---@param msg string +---@param data any +---@return string log_msg +local function format_log(log_level, msg, data) + local log_level_name = log_level_names[log_level] + local log_msg = string.format("%s [%s]: %s", get_timestamp_with_ms(), log_level_name, msg) + + if data then + log_msg = string.format("%s\n%s", log_msg, vim.inspect(data)) + end + + return log_msg +end + +---@param log_level integer -- one of the vim.log.levels +---@param msg string +---@param data any +local function notify_log(log_level, msg, data) + local log_msg = format_log(log_level, msg, data) + vim.notify(log_msg, log_level) +end + +---@param log_level integer -- one of the vim.log.levels +---@param log_file string +---@param msg string +---@param data any +local function write_log(log_level, log_file, msg, data) + local log_msg = format_log(log_level, msg, data) .. "\n" + + uv.fs_open(log_file, "a", tonumber("644", 8), function(err, fd) + if err then + notify_log(vim.log.levels.ERROR, "Failed to open log file: " .. err) + return + end + + uv.fs_write(fd, log_msg, -1, function(write_err) + if write_err then + notify_log(vim.log.levels.ERROR, "Failed to write to log file: " .. write_err) + end + + uv.fs_close(fd) + end) + end) +end + +---@param log_level integer -- one of the vim.log.levels +---@param msg string +---@param data any +---@param force_print boolean +function mod.log(log_level, msg, data, force_print) + if mod.log_to_file and (mod.file_log_level <= log_level) then + write_log(log_level, mod.log_file, msg, data) + end + + if force_print or (mod.print_log and (mod.print_log_level <= log_level)) then + notify_log(log_level, msg, data) + end +end + +---@param msg string +---@param data any +function mod.debug(msg, data) + mod.log(vim.log.levels.DEBUG, msg, data, false) +end + +---@param msg string +---@param data any +function mod.trace(msg, data) + mod.log(vim.log.levels.TRACE, msg, data, false) +end + +---@param msg string +---@param data any +function mod.error(msg, data) + mod.log(vim.log.levels.ERROR, msg, data, false) +end + +---@param msg string +---@param data any +function mod.warn(msg, data) + mod.log(vim.log.levels.WARN, msg, data, false) +end + +---@param msg string +---@param data any +function mod.info(msg, data) + mod.log(vim.log.levels.INFO, msg, data, false) +end + +---@param msg string +---@param data any +function mod.notify(msg, data) + mod.log(vim.log.levels.INFO, msg, data, true) +end + +---@param conf copilot_config_logging +function mod.setup(conf) + mod.log_file = conf.file + mod.file_log_level = conf.file_log_level + mod.print_log_level = conf.print_log_level + mod.log_to_file = conf.log_to_file + mod.print_log = conf.print_log + + if conf.trace_lsp ~= "off" then + vim.lsp.handlers["$/logTrace"] = function(_, result, _) + if not result then + return + end + + mod.trace(string.format("LSP trace - %s", result.message), result.verbose) + end + end + + if conf.trace_lsp_progress then + vim.lsp.handlers["$/progress"] = function(_, result, _) + if not result then + return + end + + mod.trace(string.format("LSP progress - token %s", result.token), result.value) + end + end + + vim.lsp.handlers["window/logMessage"] = function(_, result, _) + if not result then + return + end + + local message = string.format("LSP message: %s", result.message) + local message_type = result.type --[[@as integer]] + + if message_type == 1 then + mod.error(message) + elseif message_type == 2 then + mod.warn(message) + elseif message_type == 3 then + mod.info(message) + elseif message_type == 4 then + mod.info(message) + elseif message_type == 5 then + mod.debug(message) + else + mod.trace(message) + end + end +end + +return mod