Add .config/nvim/lua/project_nvim/project.lua

This commit is contained in:
coast 2025-05-29 22:37:16 +02:00
parent 82805695c5
commit 7e8738af90

View file

@ -0,0 +1,286 @@
local config = require("project_nvim.config")
local history = require("project_nvim.utils.history")
local glob = require("project_nvim.utils.globtopattern")
local path = require("project_nvim.utils.path")
local uv = vim.loop
local M = {}
-- Internal states
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.get_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
return client.config.root_dir, client.name
end
end
end
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 = "/"
end
return path
end
local function get_files(file_dir)
last_dir_cache = file_dir
curr_dir_cache = {}
local dir = uv.fs_scandir(file_dir)
if dir == nil then
return
end
while true do
local file = uv.fs_scandir_next(dir)
if file == nil then
return
end
table.insert(curr_dir_cache, file)
end
end
local function is(dir, identifier)
dir = dir:match(".*/(.*)")
return dir == identifier
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
end
end
local function child(dir, identifier)
local path = get_parent(dir)
return is(path, identifier)
end
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
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)
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
else
return search_dir, "pattern " .. pattern
end
end
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
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
end
return _start_client(lsp_config)
end
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
end
return true
end
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
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
end
end
if not is_in_whitelist then
return false
end
return true
end
function M.on_buf_enter()
if vim.v.vim_did_enter == 0 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 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')
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()'
if vim.tbl_contains(config.options.detection_methods, "lsp") then
M.attach_to_lsp()
end
end
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] =
'autocmd VimLeavePre * lua require("project_nvim.utils.history").write_projects_to_history()'
vim.cmd([[augroup project_nvim
au!
]])
for _, value in ipairs(autocmds) do
vim.cmd(value)
end
vim.cmd("augroup END")
history.read_projects_from_history()
end
return M