diff --git a/lua/project_nvim/project.lua b/lua/project_nvim/project.lua index 76972ee..21a6386 100644 --- a/lua/project_nvim/project.lua +++ b/lua/project_nvim/project.lua @@ -10,277 +10,292 @@ M.attached_lsp = false M.last_project = nil function M.find_lsp_root() - -- Get lsp client for current buffer - -- Returns nil or string - local buf_ft = vim.api.nvim_buf_get_option(0, "filetype") - local clients = vim.lsp.buf_get_clients() - if next(clients) == nil then - return nil - end - - for _, client in pairs(clients) do - local filetypes = client.config.filetypes - if filetypes and vim.tbl_contains(filetypes, buf_ft) then - if not vim.tbl_contains(config.options.ignore_lsp, client.name) then - return client.config.root_dir, client.name - end + -- Get lsp client for current buffer + -- Returns nil or string + local buf_ft = vim.api.nvim_buf_get_option(0, "filetype") + local clients = vim.lsp.get_active_clients({ bufnr = 0 }) + if next(clients) == nil then + return nil + end + + for _, client in pairs(clients) do + local filetypes = client.config.filetypes + if filetypes and vim.tbl_contains(filetypes, buf_ft) then + if not vim.tbl_contains(config.options.ignore_lsp, client.name) then + local workspace_folders = client.config.workspace_folders + + -- if there is more than 1 workspace_folder find the best one + -- otherwise, rely on current behavior + if workspace_folders and #workspace_folders > 1 then + local buf_path = 'file://' .. vim.api.nvim_buf_get_name(0) + for _, wrks in ipairs(workspace_folders) do + if buf_path:find(wrks.uri, 1, true) then + -- remove file:// prefix + return wrks.uri:sub(8), client.name + end + end + else + return client.config.root_dir, client.name + end + end + end end - end - return nil + return nil end function M.find_pattern_root() - local search_dir = vim.fn.expand("%:p:h", true) - if vim.fn.has("win32") > 0 then - search_dir = search_dir:gsub("\\", "/") - end - - local last_dir_cache = "" - local curr_dir_cache = {} - - local function get_parent(path) - path = path:match("^(.*)/") - if path == "" then - path = "/" + local search_dir = vim.fn.expand("%:p:h", true) + if vim.fn.has("win32") > 0 then + search_dir = search_dir:gsub("\\", "/") end - return path - end - local function get_files(file_dir) - last_dir_cache = file_dir - curr_dir_cache = {} + local last_dir_cache = "" + local curr_dir_cache = {} - local dir = uv.fs_scandir(file_dir) - if dir == nil then - return + local function get_parent(path) + path = path:match("^(.*)/") + if path == "" then + path = "/" + end + return path end - while true do - local file = uv.fs_scandir_next(dir) - if file == nil then - return - end + local function get_files(file_dir) + last_dir_cache = file_dir + curr_dir_cache = {} - table.insert(curr_dir_cache, file) - end - end + local dir = uv.fs_scandir(file_dir) + if dir == nil then + return + end - local function is(dir, identifier) - dir = dir:match(".*/(.*)") - return dir == identifier - end + while true do + local file = uv.fs_scandir_next(dir) + if file == nil then + return + end - local function sub(dir, identifier) - local path = get_parent(dir) - while true do - if is(path, identifier) then - return true - end - local current = path - path = get_parent(path) - if current == path then - return false - end + table.insert(curr_dir_cache, file) + end end - end - local function child(dir, identifier) - local path = get_parent(dir) - return is(path, identifier) - end + local function is(dir, identifier) + dir = dir:match(".*/(.*)") + return dir == identifier + end - local function has(dir, identifier) - if last_dir_cache ~= dir then - get_files(dir) + local function sub(dir, identifier) + local path = get_parent(dir) + while true do + if is(path, identifier) then + return true + end + local current = path + path = get_parent(path) + if current == path then + return false + end + end end - local pattern = glob.globtopattern(identifier) - for _, file in ipairs(curr_dir_cache) do - if file:match(pattern) ~= nil then - return true - end + + local function child(dir, identifier) + local path = get_parent(dir) + return is(path, identifier) end - return false - end - - local function match(dir, pattern) - local first_char = pattern:sub(1, 1) - if first_char == "=" then - return is(dir, pattern:sub(2)) - elseif first_char == "^" then - return sub(dir, pattern:sub(2)) - elseif first_char == ">" then - return child(dir, pattern:sub(2)) - else - return has(dir, pattern) + + local function has(dir, identifier) + if last_dir_cache ~= dir then + get_files(dir) + end + local pattern = glob.globtopattern(identifier) + for _, file in ipairs(curr_dir_cache) do + if file:match(pattern) ~= nil then + return true + end + end + return false end - end - - -- breadth-first search - while true do - for _, pattern in ipairs(config.options.patterns) do - local exclude = false - if pattern:sub(1, 1) == "!" then - exclude = true - pattern = pattern:sub(2) - end - if match(search_dir, pattern) then - if exclude then - break + + local function match(dir, pattern) + local first_char = pattern:sub(1, 1) + if first_char == "=" then + return is(dir, pattern:sub(2)) + elseif first_char == "^" then + return sub(dir, pattern:sub(2)) + elseif first_char == ">" then + return child(dir, pattern:sub(2)) else - return search_dir, "pattern " .. pattern + return has(dir, pattern) end - end end - local parent = get_parent(search_dir) - if parent == search_dir or parent == nil then - return nil - end + -- breadth-first search + while true do + for _, pattern in ipairs(config.options.patterns) do + local exclude = false + if pattern:sub(1, 1) == "!" then + exclude = true + pattern = pattern:sub(2) + end + if match(search_dir, pattern) then + if exclude then + break + else + return search_dir, "pattern " .. pattern + end + end + end - search_dir = parent - end + local parent = get_parent(search_dir) + if parent == search_dir or parent == nil then + return nil + end + + search_dir = parent + end end ---@diagnostic disable-next-line: unused-local local on_attach_lsp = function(client, bufnr) - M.on_buf_enter() -- Recalculate root dir after lsp attaches + M.on_buf_enter() -- Recalculate root dir after lsp attaches end function M.attach_to_lsp() - if M.attached_lsp then - return - end - - local _start_client = vim.lsp.start_client - vim.lsp.start_client = function(lsp_config) - if lsp_config.on_attach == nil then - lsp_config.on_attach = on_attach_lsp - else - local _on_attach = lsp_config.on_attach - lsp_config.on_attach = function(client, bufnr) - on_attach_lsp(client, bufnr) - _on_attach(client, bufnr) - end + if M.attached_lsp then + return + end + + local _start_client = vim.lsp.start_client + vim.lsp.start_client = function(lsp_config) + if lsp_config.on_attach == nil then + lsp_config.on_attach = on_attach_lsp + else + local _on_attach = lsp_config.on_attach + lsp_config.on_attach = function(client, bufnr) + on_attach_lsp(client, bufnr) + _on_attach(client, bufnr) + end + end + return _start_client(lsp_config) end - return _start_client(lsp_config) - end - M.attached_lsp = true + M.attached_lsp = true end function M.set_pwd(dir, method) - if dir ~= nil then - M.last_project = dir - table.insert(history.session_projects, dir) - - if vim.fn.getcwd() ~= dir then - local scope_chdir = config.options.scope_chdir - if scope_chdir == 'global' then - vim.api.nvim_set_current_dir(dir) - elseif scope_chdir == 'tab' then - vim.cmd('tcd ' .. dir) - elseif scope_chdir == 'win' then - vim.cmd('lcd ' .. dir) - else - return - end - - if config.options.silent_chdir == false then - vim.notify("Set CWD to " .. dir .. " using " .. method) - end + if dir ~= nil then + M.last_project = dir + table.insert(history.session_projects, dir) + + if vim.fn.getcwd() ~= dir then + local scope_chdir = config.options.scope_chdir + if scope_chdir == 'global' then + vim.api.nvim_set_current_dir(dir) + elseif scope_chdir == 'tab' then + vim.cmd('tcd ' .. dir) + elseif scope_chdir == 'win' then + vim.cmd('lcd ' .. dir) + else + return + end + + if config.options.silent_chdir == false then + vim.notify("Set CWD to " .. dir .. " using " .. method) + end + end + return true end - return true - end - return false + return false end function M.get_project_root() - -- returns project root, as well as method - for _, detection_method in ipairs(config.options.detection_methods) do - if detection_method == "lsp" then - local root, lsp_name = M.find_lsp_root() - if root ~= nil then - return root, '"' .. lsp_name .. '"' .. " lsp" - end - elseif detection_method == "pattern" then - local root, method = M.find_pattern_root() - if root ~= nil then - return root, method - end + -- returns project root, as well as method + for _, detection_method in ipairs(config.options.detection_methods) do + if detection_method == "lsp" then + local root, lsp_name = M.find_lsp_root() + if root ~= nil then + return root, '"' .. lsp_name .. '"' .. " lsp" + end + elseif detection_method == "pattern" then + local root, method = M.find_pattern_root() + if root ~= nil then + return root, method + end + end end - end end function M.is_file() - local buf_type = vim.api.nvim_buf_get_option(0, "buftype") - - local whitelisted_buf_type = { "", "acwrite" } - local is_in_whitelist = false - for _, wtype in ipairs(whitelisted_buf_type) do - if buf_type == wtype then - is_in_whitelist = true - break + local buf_type = vim.api.nvim_buf_get_option(0, "buftype") + + local whitelisted_buf_type = { "", "acwrite" } + local is_in_whitelist = false + for _, wtype in ipairs(whitelisted_buf_type) do + if buf_type == wtype then + is_in_whitelist = true + break + end + end + if not is_in_whitelist then + return false end - end - if not is_in_whitelist then - return false - end - return true + return true end function M.on_buf_enter() - if vim.v.vim_did_enter == 0 then - return - end + if vim.v.vim_did_enter == 0 then + return + end - if not M.is_file() then - return - end + if not M.is_file() then + return + end - local current_dir = vim.fn.expand("%:p:h", true) - if not path.exists(current_dir) or path.is_excluded(current_dir) then - return - end + local current_dir = vim.fn.expand("%:p:h", true) + if not path.exists(current_dir) or path.is_excluded(current_dir) then + return + end - local root, method = M.get_project_root() - M.set_pwd(root, method) + local root, method = M.get_project_root() + M.set_pwd(root, method) end function M.add_project_manually() - local current_dir = vim.fn.expand("%:p:h", true) - M.set_pwd(current_dir, 'manual') + local current_dir = vim.fn.expand("%:p:h", true) + M.set_pwd(current_dir, 'manual') end function M.init() - local autocmds = {} - if not config.options.manual_mode then - autocmds[#autocmds + 1] = 'autocmd VimEnter,BufEnter * ++nested lua require("project_nvim.project").on_buf_enter()' + local autocmds = {} + if not config.options.manual_mode then + autocmds[#autocmds + 1] = + 'autocmd VimEnter,BufEnter * ++nested lua require("project_nvim.project").on_buf_enter()' - if vim.tbl_contains(config.options.detection_methods, "lsp") then - M.attach_to_lsp() + if vim.tbl_contains(config.options.detection_methods, "lsp") then + M.attach_to_lsp() + end end - end - vim.cmd([[ + vim.cmd([[ command! ProjectRoot lua require("project_nvim.project").on_buf_enter() command! AddProject lua require("project_nvim.project").add_project_manually() ]]) - autocmds[#autocmds + 1] = + autocmds[#autocmds + 1] = 'autocmd VimLeavePre * lua require("project_nvim.utils.history").write_projects_to_history()' - vim.cmd([[augroup project_nvim + vim.cmd([[augroup project_nvim au! ]]) - for _, value in ipairs(autocmds) do - vim.cmd(value) - end - vim.cmd("augroup END") + for _, value in ipairs(autocmds) do + vim.cmd(value) + end + vim.cmd("augroup END") - history.read_projects_from_history() + history.read_projects_from_history() end return M