From c1c2ffae5ec570167c1bc755ecb217ee9bc33237 Mon Sep 17 00:00:00 2001 From: Nicholas deLannoy Date: Wed, 23 Jul 2025 18:07:48 +0000 Subject: [PATCH 1/6] graceful handling for unauthenticated users with is_unauthenticated() --- lua/copilot/auth/init.lua | 21 ++++++++++++++++++--- lua/copilot/suggestion/init.lua | 3 ++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lua/copilot/auth/init.lua b/lua/copilot/auth/init.lua index 82b669e7..d9a36c75 100644 --- a/lua/copilot/auth/init.lua +++ b/lua/copilot/auth/init.lua @@ -151,18 +151,23 @@ function M.find_config_path() logger.error("could not find config path") end -M.get_creds = function() +M.get_creds = function(opts) local filename = M.find_config_path() .. "/github-copilot/apps.json" + opts = opts or {} if vim.fn.filereadable(filename) == 0 then - logger.error("Copilot auth file could not be read from:" .. filename) + if not opts.silent then + logger.error("Copilot auth file could not be read from:" .. filename) + end return end local filedata = vim.api.nvim_eval("readfile('" .. filename .. "')") if not filedata or #filedata == 0 then - logger.error("Copilot's apps.json file not found or empty, make sure to sign in first") + if not opts.silent then + logger.error("Copilot's apps.json file not found or empty, make sure to sign in first") + end return end @@ -180,4 +185,14 @@ function M.info() logger.notify("GitHub Copilot token information: ", info) end +function M.is_authenticated() + local token_env_set = (os.getenv("GITHUB_COPILOT_TOKEN") ~= nil) or (os.getenv("GH_COPILOT_TOKEN") ~= nil) + if token_env_set then + return true + end + + local creds = M.get_creds({ silent = true }) + return creds ~= nil +end + return M diff --git a/lua/copilot/suggestion/init.lua b/lua/copilot/suggestion/init.lua index e83ab888..7b477a2c 100644 --- a/lua/copilot/suggestion/init.lua +++ b/lua/copilot/suggestion/init.lua @@ -1,4 +1,5 @@ local api = require("copilot.api") +local auth = require("copilot.auth") local c = require("copilot.client") local config = require("copilot.config") local hl_group = require("copilot.highlight").group @@ -484,7 +485,7 @@ local function advance(count, ctx) end local function schedule(ctx) - if not is_enabled() or not c.initialized then + if not is_enabled() or not c.initialized or not auth.is_authenticated() then clear() return end From 7d014f4957423f135c31f21f59e1de0afa3794ab Mon Sep 17 00:00:00 2001 From: Nicholas deLannoy Date: Fri, 25 Jul 2025 17:07:58 +0000 Subject: [PATCH 2/6] move is_authenticated to look at LSP server and add health check module --- lua/copilot/auth/init.lua | 68 ++++++++++++++++++++++------ lua/copilot/health.lua | 93 +++++++++++++++++++++++++++++++++++++++ tests/test_auth.lua | 37 ++++++++++++++++ 3 files changed, 184 insertions(+), 14 deletions(-) create mode 100644 lua/copilot/health.lua diff --git a/lua/copilot/auth/init.lua b/lua/copilot/auth/init.lua index d9a36c75..84ac4d7d 100644 --- a/lua/copilot/auth/init.lua +++ b/lua/copilot/auth/init.lua @@ -151,23 +151,18 @@ function M.find_config_path() logger.error("could not find config path") end -M.get_creds = function(opts) +M.get_creds = function() local filename = M.find_config_path() .. "/github-copilot/apps.json" - opts = opts or {} if vim.fn.filereadable(filename) == 0 then - if not opts.silent then - logger.error("Copilot auth file could not be read from:" .. filename) - end + logger.error("Copilot auth file could not be read from:" .. filename) return end local filedata = vim.api.nvim_eval("readfile('" .. filename .. "')") if not filedata or #filedata == 0 then - if not opts.silent then - logger.error("Copilot's apps.json file not found or empty, make sure to sign in first") - end + logger.error("Copilot's apps.json file not found or empty, make sure to sign in first") return end @@ -185,14 +180,59 @@ function M.info() logger.notify("GitHub Copilot token information: ", info) end +local auth_cache = { + status = nil, + timestamp = 0, + lsp_check_pending = false, +} + +local function get_cache_ttl(status) + if status then + return 300000 -- 5 minutes for true status + else + return 30000 -- 30 seconds for false status + end +end + function M.is_authenticated() - local token_env_set = (os.getenv("GITHUB_COPILOT_TOKEN") ~= nil) or (os.getenv("GH_COPILOT_TOKEN") ~= nil) - if token_env_set then - return true + local current_time = vim.loop.now() + + if auth_cache.status ~= nil and not auth_cache.lsp_check_pending then + + local ttl = get_cache_ttl(auth_cache.status) + if (current_time - auth_cache.timestamp) < ttl then + return auth_cache.status + end + end + + local client = c.get() + if not client then + auth_cache.status = false + auth_cache.timestamp = current_time + return false + end + + if auth_cache.lsp_check_pending then + return auth_cache.status or false end - - local creds = M.get_creds({ silent = true }) - return creds ~= nil + + auth_cache.lsp_check_pending = true + vim.schedule(function() + api.check_status(client, {}, function(err, status) + auth_cache.lsp_check_pending = false + local current_check_time = vim.loop.now() + + if not err and status and status.user then + auth_cache.status = true + auth_cache.timestamp = current_check_time + else + auth_cache.status = false + auth_cache.timestamp = current_check_time + end + end) + end) + + return auth_cache.status or false end return M diff --git a/lua/copilot/health.lua b/lua/copilot/health.lua new file mode 100644 index 00000000..116c1528 --- /dev/null +++ b/lua/copilot/health.lua @@ -0,0 +1,93 @@ +local M = {} + +local api = require("copilot.api") +local auth = require("copilot.auth") +local c = require("copilot.client") +local config = require("copilot.config") + +local start = vim.health.start or vim.health.report_start +local ok = vim.health.ok or vim.health.report_ok +local warn = vim.health.warn or vim.health.report_warn +local error = vim.health.error or vim.health.report_error +local info = vim.health.info or vim.health.report_info + +function M.check() + start("{copilot.lua}") + info("{copilot.lua} GitHub Copilot plugin for Neovim") + + start("Copilot Dependencies") + + if vim.fn.executable("node") == 1 then + local node_version = vim.fn.system("node --version"):gsub("\n", "") + ok("`node` found: " .. node_version) + else + error("`node` not found in PATH") + info("Install Node.js from https://nodejs.org") + end + + start("Copilot Authentication") + + local github_token = os.getenv("GITHUB_COPILOT_TOKEN") + local gh_token = os.getenv("GH_COPILOT_TOKEN") + if github_token or gh_token then + ok("Environment token found: " .. (github_token and "`GITHUB_COPILOT_TOKEN`" or "`GH_COPILOT_TOKEN`")) + else + info("No environment token set (`GITHUB_COPILOT_TOKEN` or `GH_COPILOT_TOKEN`)") + end + + local config_path = auth.find_config_path() + local creds_ok, creds = pcall(auth.get_creds) + if creds_ok and creds then + ok("Local credentials file found") + info("Location: `" .. (config_path or "unknown") .. "/github-copilot/apps.json`") + else + info("No local credentials file found") + info("Expected location: `" .. (config_path or "unknown") .. "/github-copilot/apps.json`") + info("Run `:Copilot auth` to authenticate") + end + + local client = c.get() + if not client then + error("Copilot LSP client not available") + info("Check that the plugin is properly loaded and configured") + info("Or restart Neovim if the plugin was just installed") + return + end + + start("Copilot LSP Status") + ok("LSP client is available and running") + info("Client ID: " .. tostring(client.id)) + + local lsp_authenticated = auth.is_authenticated() + if lsp_authenticated then + ok("LSP authentication status: authenticated") + else + warn("LSP authentication status: not authenticated") + end + info("For detailed authentication status, run `:Copilot status`") + + start("Copilot Configuration") + local suggestion_config = config.suggestion + if suggestion_config and suggestion_config.enabled ~= false then + ok("Suggestions enabled") + if suggestion_config.auto_trigger ~= false then + info("Auto-trigger: enabled") + else + info("Auto-trigger: disabled (manual trigger only)") + end + else + warn("Suggestions disabled in configuration") + info("Enable with `suggestion = { enabled = true }` in setup()") + end + + local panel_config = config.panel + if panel_config and panel_config.enabled ~= false then + ok("Panel enabled") + info("Panel Keybinding: " .. (panel_config.keymap and panel_config.keymap.open or "")) + else + info("Panel disabled in configuration") + info("Enable with `panel = { enabled = true }` in setup()") + end +end + +return M diff --git a/tests/test_auth.lua b/tests/test_auth.lua index aa9ccf08..f6457cca 100644 --- a/tests/test_auth.lua +++ b/tests/test_auth.lua @@ -84,4 +84,41 @@ T["auth()"]["auth issue replication"] = function() u.expect_match(messages, ".*Online.*Enabled.*") end +T["auth()"]["is_authenticated when not authed returns false"] = function() + child.configure_copilot() + + local result = child.lua([[ + local auth = require("copilot.auth") + local auth_result = auth.is_authenticated() + return tostring(auth_result) + ]]) + + u.expect_match(result, "false") +end + +T["auth()"]["is_authenticated when authed returns true"] = function() + child.configure_copilot() + child.cmd("Copilot auth") + + child.lua([[ + local messages = "" + local function has_passed() + messages = vim.api.nvim_exec("messages", { output = true }) or "" + return string.find(messages, ".*Authenticated as GitHub user.*") ~= nil + end + + vim.wait(30000, function() + return has_passed() + end, 50) + ]]) + + local result = child.lua([[ + local auth = require("copilot.auth") + local auth_result = auth.is_authenticated() + return tostring(auth_result) + ]]) + + u.expect_match(result, "true") +end + return T From 3d0d7a4843ce18b68428adc00ff5dde8bd52be58 Mon Sep 17 00:00:00 2001 From: Nicholas deLannoy Date: Fri, 25 Jul 2025 21:51:57 +0000 Subject: [PATCH 3/6] fix auth test it looks like the suggestions tests are failing because the new is_authenticated check on schedule is blocking initial suggestions, I'll have to tweak my approach a bit to preserve existing speedy behavior for authenticated users --- tests/test_auth.lua | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/test_auth.lua b/tests/test_auth.lua index f6457cca..444c91a7 100644 --- a/tests/test_auth.lua +++ b/tests/test_auth.lua @@ -113,8 +113,16 @@ T["auth()"]["is_authenticated when authed returns true"] = function() ]]) local result = child.lua([[ - local auth = require("copilot.auth") - local auth_result = auth.is_authenticated() + local auth_result = "" + local function has_passed() + auth_result = require("copilot.auth").is_authenticated() or "" + return auth_result == true + end + + vim.wait(30000, function() + return has_passed() + end, 50) + return tostring(auth_result) ]]) From 1a31f1b2e0fc9b8a4a91e613426fb6150096e112 Mon Sep 17 00:00:00 2001 From: Nicholas deLannoy Date: Fri, 25 Jul 2025 22:03:05 +0000 Subject: [PATCH 4/6] added wait_for_lsp_authentication() test helper to fix suggestion tests --- tests/child_helper.lua | 8 ++++++++ tests/test_suggestion.lua | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/tests/child_helper.lua b/tests/child_helper.lua index 0b6b0cb4..13ea19c9 100644 --- a/tests/child_helper.lua +++ b/tests/child_helper.lua @@ -105,6 +105,14 @@ function M.new_child_neovim(test_name) ]]) end + function child.wait_for_lsp_authentication() + child.lua([[ + vim.wait(30000, function() + return require("copilot.auth").is_authenticated() + end, 10) + ]]) + end + function child.wait_for_suggestion() child.lua([[ vim.wait(30000, function() diff --git a/tests/test_suggestion.lua b/tests/test_suggestion.lua index 5b8248d1..2deaa875 100644 --- a/tests/test_suggestion.lua +++ b/tests/test_suggestion.lua @@ -19,6 +19,7 @@ T["suggestion()"]["suggestion works"] = function() child.o.lines, child.o.columns = 10, 15 child.config.suggestion = child.config.suggestion .. "auto_trigger = true," child.configure_copilot() + child.wait_for_lsp_authentication() child.type_keys("i123", "", "o456", "", "o7") child.wait_for_suggestion() @@ -28,6 +29,7 @@ end T["suggestion()"]["auto_trigger is false, will not show ghost test"] = function() child.o.lines, child.o.columns = 10, 15 child.configure_copilot() + child.wait_for_lsp_authentication() child.type_keys("i123", "", "o456", "", "o7") vim.loop.sleep(3000) child.lua("vim.wait(0)") @@ -39,6 +41,7 @@ T["suggestion()"]["accept keymap to trigger sugestion"] = function() child.o.lines, child.o.columns = 10, 15 child.config.suggestion = child.config.suggestion .. "keymap = { accept = '' }," child.configure_copilot() + child.wait_for_lsp_authentication() child.type_keys("i123", "", "o456", "", "o7", "") child.wait_for_suggestion() @@ -51,6 +54,7 @@ T["suggestion()"]["accept keymap, no suggestion, execute normal keystroke"] = fu .. "keymap = { accept = '' },\n" .. "trigger_on_accept = false," child.configure_copilot() + child.wait_for_lsp_authentication() child.type_keys("i123", "", "o456", "", "o7", "") reference_screenshot(child.get_screenshot(), nil, { ignore_lines = { 9, 10 } }) @@ -60,6 +64,7 @@ T["suggestion()"]["accept_word, 1 word, works"] = function() child.o.lines, child.o.columns = 10, 15 child.config.suggestion = child.config.suggestion .. "auto_trigger = true," .. "keymap = { accept_word = '' }," child.configure_copilot() + child.wait_for_lsp_authentication() child.type_keys("i1, 2, 3,", "", "o4, 5, 6,", "", "o7, ") child.wait_for_suggestion() child.type_keys("", "") @@ -71,6 +76,7 @@ T["suggestion()"]["accept_word, 3 words, works"] = function() child.o.lines, child.o.columns = 10, 15 child.config.suggestion = child.config.suggestion .. "auto_trigger = true," .. "keymap = { accept_word = '' }," child.configure_copilot() + child.wait_for_lsp_authentication() child.type_keys("i1, 2, 3,", "", "o4, 5, 6,", "", "o7, ") child.wait_for_suggestion() child.type_keys("", "", "", "") @@ -87,6 +93,7 @@ T["suggestion()"]["accept_word, 1 word, then dismiss"] = function() .. "auto_trigger = true," .. "keymap = { accept_word = '', dismiss = '' }," child.configure_copilot() + child.wait_for_lsp_authentication() child.type_keys("i1, 2, 3,", "", "o4, 5, 6,", "", "o7, ") child.wait_for_suggestion() child.type_keys("", "") @@ -100,6 +107,7 @@ T["suggestion()"]["accept_word, 1 word, then accept"] = function() .. "auto_trigger = true," .. "keymap = { accept_word = '', accept = '' }," child.configure_copilot() + child.wait_for_lsp_authentication() child.type_keys("i1, 2, 3,", "", "o4, 5, 6,", "", "o7, ") child.wait_for_suggestion() child.type_keys("", "") @@ -111,6 +119,7 @@ T["suggestion()"]["accept_line, 1 line, works"] = function() child.o.lines, child.o.columns = 30, 15 child.config.suggestion = child.config.suggestion .. "auto_trigger = true," .. "keymap = { accept_line = '' }," child.configure_copilot() + child.wait_for_lsp_authentication() child.type_keys("i{", "o", " 1,2,3", "o", "4,5,6", "o", "7,8,9", "o", "}", "") child.type_keys("o{", "o", " 10,11,12", "", "o13,14,15", "", "o16,17,18", "o", "}", "") child.type_keys("o{", "o") @@ -124,6 +133,7 @@ T["suggestion()"]["accept_line, 3 lines, works"] = function() child.o.lines, child.o.columns = 50, 15 child.config.suggestion = child.config.suggestion .. "auto_trigger = true," .. "keymap = { accept_line = '' }," child.configure_copilot() + child.wait_for_lsp_authentication() child.type_keys("i{", "o", " 1,2,3", "o", "4,5,6", "o", "7,8,9", "o", "}", "") child.type_keys("o{", "o", " 10,11,12", "", "o13,14,15", "", "o16,17,18", "o", "}", "") child.type_keys("o{", "o") @@ -142,6 +152,7 @@ T["suggestion()"]["accept_line, 1 line, then dismiss"] = function() .. "auto_trigger = true," .. "keymap = { accept_line = '', dismiss = '' }," child.configure_copilot() + child.wait_for_lsp_authentication() child.type_keys("i{", "o", " 1,2,3", "o", "4,5,6", "o", "7,8,9", "o", "}", "") child.type_keys("o{", "o", " 10,11,12", "", "o13,14,15", "", "o16,17,18", "o", "}", "") child.type_keys("o{", "o") @@ -157,6 +168,7 @@ T["suggestion()"]["accept_line, 1 line, then accept"] = function() .. "auto_trigger = true," .. "keymap = { accept_line = '', accept = '' }," child.configure_copilot() + child.wait_for_lsp_authentication() child.type_keys("i# Numbers in a 3x3 grid, up to 63", "") child.type_keys("o{", "o", " 1,2,3", "o", "4,5,6", "o", "7,8,9", "o", "}", "") child.type_keys("o{", "o", " 10,11,12", "", "o13,14,15", "", "o16,17,18", "o", "}", "") From 87ed23a00562cde278835dfe86337f8d35934842 Mon Sep 17 00:00:00 2001 From: Antoine Gaudreau Simard Date: Sun, 27 Jul 2025 17:49:41 -0400 Subject: [PATCH 5/6] Revert "added wait_for_lsp_authentication() test helper to fix suggestion tests" This reverts commit 1a31f1b2e0fc9b8a4a91e613426fb6150096e112. --- tests/child_helper.lua | 8 -------- tests/test_suggestion.lua | 12 ------------ 2 files changed, 20 deletions(-) diff --git a/tests/child_helper.lua b/tests/child_helper.lua index 13ea19c9..0b6b0cb4 100644 --- a/tests/child_helper.lua +++ b/tests/child_helper.lua @@ -105,14 +105,6 @@ function M.new_child_neovim(test_name) ]]) end - function child.wait_for_lsp_authentication() - child.lua([[ - vim.wait(30000, function() - return require("copilot.auth").is_authenticated() - end, 10) - ]]) - end - function child.wait_for_suggestion() child.lua([[ vim.wait(30000, function() diff --git a/tests/test_suggestion.lua b/tests/test_suggestion.lua index ee77e885..241d90e8 100644 --- a/tests/test_suggestion.lua +++ b/tests/test_suggestion.lua @@ -19,7 +19,6 @@ T["suggestion()"]["suggestion works"] = function() child.o.lines, child.o.columns = 10, 15 child.config.suggestion = child.config.suggestion .. "auto_trigger = true," child.configure_copilot() - child.wait_for_lsp_authentication() child.type_keys("i123", "", "o456", "", "o7") child.wait_for_suggestion() @@ -29,7 +28,6 @@ end T["suggestion()"]["auto_trigger is false, will not show ghost test"] = function() child.o.lines, child.o.columns = 10, 15 child.configure_copilot() - child.wait_for_lsp_authentication() child.type_keys("i123", "", "o456", "", "o7") vim.loop.sleep(3000) child.lua("vim.wait(0)") @@ -41,7 +39,6 @@ T["suggestion()"]["accept keymap to trigger sugestion"] = function() child.o.lines, child.o.columns = 10, 15 child.config.suggestion = child.config.suggestion .. "keymap = { accept = '' }," child.configure_copilot() - child.wait_for_lsp_authentication() child.type_keys("i123", "", "o456", "", "o7", "") child.wait_for_suggestion() @@ -54,7 +51,6 @@ T["suggestion()"]["accept keymap, no suggestion, execute normal keystroke"] = fu .. "keymap = { accept = '' },\n" .. "trigger_on_accept = false," child.configure_copilot() - child.wait_for_lsp_authentication() child.type_keys("i123", "", "o456", "", "o7", "") reference_screenshot(child.get_screenshot(), nil, { ignore_text = { 9, 10 }, ignore_attr = { 9, 10 } }) @@ -64,7 +60,6 @@ T["suggestion()"]["accept_word, 1 word, works"] = function() child.o.lines, child.o.columns = 10, 15 child.config.suggestion = child.config.suggestion .. "auto_trigger = true," .. "keymap = { accept_word = '' }," child.configure_copilot() - child.wait_for_lsp_authentication() child.type_keys("i1, 2, 3,", "", "o4, 5, 6,", "", "o7, ") child.wait_for_suggestion() child.type_keys("", "") @@ -76,7 +71,6 @@ T["suggestion()"]["accept_word, 3 words, works"] = function() child.o.lines, child.o.columns = 10, 15 child.config.suggestion = child.config.suggestion .. "auto_trigger = true," .. "keymap = { accept_word = '' }," child.configure_copilot() - child.wait_for_lsp_authentication() child.type_keys("i1, 2, 3,", "", "o4, 5, 6,", "", "o7, ") child.wait_for_suggestion() child.type_keys("", "", "", "") @@ -93,7 +87,6 @@ T["suggestion()"]["accept_word, 1 word, then dismiss"] = function() .. "auto_trigger = true," .. "keymap = { accept_word = '', dismiss = '' }," child.configure_copilot() - child.wait_for_lsp_authentication() child.type_keys("i1, 2, 3,", "", "o4, 5, 6,", "", "o7, ") child.wait_for_suggestion() child.type_keys("", "") @@ -107,7 +100,6 @@ T["suggestion()"]["accept_word, 1 word, then accept"] = function() .. "auto_trigger = true," .. "keymap = { accept_word = '', accept = '' }," child.configure_copilot() - child.wait_for_lsp_authentication() child.type_keys("i1, 2, 3,", "", "o4, 5, 6,", "", "o7, ") child.wait_for_suggestion() child.type_keys("", "") @@ -119,7 +111,6 @@ T["suggestion()"]["accept_line, 1 line, works"] = function() child.o.lines, child.o.columns = 30, 15 child.config.suggestion = child.config.suggestion .. "auto_trigger = true," .. "keymap = { accept_line = '' }," child.configure_copilot() - child.wait_for_lsp_authentication() child.type_keys("i{", "o", " 1,2,3", "o", "4,5,6", "o", "7,8,9", "o", "}", "") child.type_keys("o{", "o", " 10,11,12", "", "o13,14,15", "", "o16,17,18", "o", "}", "") child.type_keys("o{", "o") @@ -133,7 +124,6 @@ T["suggestion()"]["accept_line, 3 lines, works"] = function() child.o.lines, child.o.columns = 50, 15 child.config.suggestion = child.config.suggestion .. "auto_trigger = true," .. "keymap = { accept_line = '' }," child.configure_copilot() - child.wait_for_lsp_authentication() child.type_keys("i{", "o", " 1,2,3", "o", "4,5,6", "o", "7,8,9", "o", "}", "") child.type_keys("o{", "o", " 10,11,12", "", "o13,14,15", "", "o16,17,18", "o", "}", "") child.type_keys("o{", "o") @@ -152,7 +142,6 @@ T["suggestion()"]["accept_line, 1 line, then dismiss"] = function() .. "auto_trigger = true," .. "keymap = { accept_line = '', dismiss = '' }," child.configure_copilot() - child.wait_for_lsp_authentication() child.type_keys("i{", "o", " 1,2,3", "o", "4,5,6", "o", "7,8,9", "o", "}", "") child.type_keys("o{", "o", " 10,11,12", "", "o13,14,15", "", "o16,17,18", "o", "}", "") child.type_keys("o{", "o") @@ -168,7 +157,6 @@ T["suggestion()"]["accept_line, 1 line, then accept"] = function() .. "auto_trigger = true," .. "keymap = { accept_line = '', accept = '' }," child.configure_copilot() - child.wait_for_lsp_authentication() child.type_keys("i# Numbers in a 3x3 grid, up to 63", "") child.type_keys("o{", "o", " 1,2,3", "o", "4,5,6", "o", "7,8,9", "o", "}", "") child.type_keys("o{", "o", " 10,11,12", "", "o13,14,15", "", "o16,17,18", "o", "}", "") From 8668e856ca2aa1f5847c6782ea474c4153045d9b Mon Sep 17 00:00:00 2001 From: Antoine Gaudreau Simard Date: Sun, 27 Jul 2025 18:51:01 -0400 Subject: [PATCH 6/6] prevent having to request a second suggestion after the auth request goes through --- lua/copilot/auth/init.lua | 70 ++++++++++++++++++--------------- lua/copilot/suggestion/init.lua | 9 ++++- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/lua/copilot/auth/init.lua b/lua/copilot/auth/init.lua index 84ac4d7d..b47cd894 100644 --- a/lua/copilot/auth/init.lua +++ b/lua/copilot/auth/init.lua @@ -132,6 +132,7 @@ function M.signout() end) end +---@return string|nil function M.find_config_path() local config = vim.fn.expand("$XDG_CONFIG_HOME") if config and vim.fn.isdirectory(config) > 0 then @@ -151,6 +152,7 @@ function M.find_config_path() logger.error("could not find config path") end +---@return table|nil M.get_creds = function() local filename = M.find_config_path() .. "/github-copilot/apps.json" @@ -180,59 +182,65 @@ function M.info() logger.notify("GitHub Copilot token information: ", info) end +---@class copilot_auth_cache +---@field authenticated boolean|nil +---@field timestamp number + +---@type copilot_auth_cache local auth_cache = { - status = nil, + authenticated = nil, timestamp = 0, - lsp_check_pending = false, } -local function get_cache_ttl(status) - if status then +---@param authenticated boolean +---@return number +local function get_cache_ttl(authenticated) + if authenticated then return 300000 -- 5 minutes for true status else - return 30000 -- 30 seconds for false status + return 30000 -- 30 seconds for false status end end -function M.is_authenticated() - local current_time = vim.loop.now() +---@param client vim.lsp.Client +---@param callback? fun() +local function check_status(client, callback) + api.check_status(client, {}, function(err, status) + auth_cache.timestamp = vim.loop.now() + + if not err and status and status.user then + auth_cache.authenticated = true + else + auth_cache.authenticated = false + end - if auth_cache.status ~= nil and not auth_cache.lsp_check_pending then + if callback then + callback() + end + end) +end - local ttl = get_cache_ttl(auth_cache.status) +---@param callback? fun() +function M.is_authenticated(callback) + local current_time = vim.loop.now() + + if auth_cache.authenticated ~= nil then + local ttl = get_cache_ttl(auth_cache.authenticated) if (current_time - auth_cache.timestamp) < ttl then - return auth_cache.status + return auth_cache.authenticated end end local client = c.get() if not client then - auth_cache.status = false + auth_cache.authenticated = false auth_cache.timestamp = current_time return false end - if auth_cache.lsp_check_pending then - return auth_cache.status or false - end - - auth_cache.lsp_check_pending = true - vim.schedule(function() - api.check_status(client, {}, function(err, status) - auth_cache.lsp_check_pending = false - local current_check_time = vim.loop.now() - - if not err and status and status.user then - auth_cache.status = true - auth_cache.timestamp = current_check_time - else - auth_cache.status = false - auth_cache.timestamp = current_check_time - end - end) - end) + check_status(client, callback) - return auth_cache.status or false + return auth_cache.authenticated or false end return M diff --git a/lua/copilot/suggestion/init.lua b/lua/copilot/suggestion/init.lua index 7b477a2c..9127df7b 100644 --- a/lua/copilot/suggestion/init.lua +++ b/lua/copilot/suggestion/init.lua @@ -485,10 +485,17 @@ local function advance(count, ctx) end local function schedule(ctx) - if not is_enabled() or not c.initialized or not auth.is_authenticated() then + -- in case we need to call the lsp API to know if we are authenticated, + -- we want to retry this method after the authentication check + local is_authenticated = auth.is_authenticated(function() + schedule(ctx) + end) + + if not is_enabled() or not c.initialized or not is_authenticated then clear() return end + logger.trace("suggestion schedule", ctx) if copilot._copilot_timer then