1

Update generated nvim config

This commit is contained in:
2024-06-05 22:05:42 +02:00
parent 859ee3a2ba
commit 075fe5f587
1292 changed files with 152601 additions and 0 deletions

View File

@ -0,0 +1,66 @@
local M = {}
---@class ProjectOptions
M.defaults = {
-- Manual mode doesn't automatically change your root directory, so you have
-- the option to manually do so using `:ProjectRoot` command.
manual_mode = false,
-- Methods of detecting the root directory. **"lsp"** uses the native neovim
-- lsp, while **"pattern"** uses vim-rooter like glob pattern matching. Here
-- order matters: if one is not detected, the other is used as fallback. You
-- can also delete or rearangne the detection methods.
detection_methods = { "lsp", "pattern" },
-- All the patterns used to detect root dir, when **"pattern"** is in
-- detection_methods
patterns = { ".git", "_darcs", ".hg", ".bzr", ".svn", "Makefile", "package.json" },
-- Table of lsp clients to ignore by name
-- eg: { "efm", ... }
ignore_lsp = {},
-- Don't calculate root dir on specific directories
-- Ex: { "~/.cargo/*", ... }
exclude_dirs = {},
-- Show hidden files in telescope
show_hidden = false,
-- When set to false, you will get a message when project.nvim changes your
-- directory.
silent_chdir = true,
-- What scope to change the directory, valid options are
-- * global (default)
-- * tab
-- * win
scope_chdir = 'global',
-- Path where project.nvim will store the project history for use in
-- telescope
datapath = vim.fn.stdpath("data"),
}
---@type ProjectOptions
M.options = {}
M.setup = function(options)
M.options = vim.tbl_deep_extend("force", M.defaults, options or {})
local glob = require("project_nvim.utils.globtopattern")
local home = vim.fn.expand("~")
M.options.exclude_dirs = vim.tbl_map(function(pattern)
if vim.startswith(pattern, "~/") then
pattern = home .. "/" .. pattern:sub(3, #pattern)
end
return glob.globtopattern(pattern)
end, M.options.exclude_dirs)
vim.opt.autochdir = false -- implicitly unset autochdir
require("project_nvim.utils.path").init()
require("project_nvim.project").init()
end
return M

View File

@ -0,0 +1,8 @@
local config = require("project_nvim.config")
local history = require("project_nvim.utils.history")
local M = {}
M.setup = config.setup
M.get_recent_projects = history.get_recent_projects
return M

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.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
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

View File

@ -0,0 +1,139 @@
-- Credits for this module goes to: David Manura
-- https://github.com/davidm/lua-glob-pattern
local M = { _TYPE = "module", _NAME = "globtopattern", _VERSION = "0.2.1.20120406" }
function M.globtopattern(g)
-- Some useful references:
-- - apr_fnmatch in Apache APR. For example,
-- http://apr.apache.org/docs/apr/1.3/group__apr__fnmatch.html
-- which cites POSIX 1003.2-1992, section B.6.
local p = "^" -- pattern being built
local i = 0 -- index in g
local c -- char at index i in g.
-- unescape glob char
local function unescape()
if c == "\\" then
i = i + 1
c = g:sub(i, i)
if c == "" then
p = "[^]"
return false
end
end
return true
end
-- escape pattern char
local function escape(c)
return c:match("^%w$") and c or "%" .. c
end
-- Convert tokens at end of charset.
local function charset_end()
while 1 do
if c == "" then
p = "[^]"
return false
elseif c == "]" then
p = p .. "]"
break
else
if not unescape() then
break
end
local c1 = c
i = i + 1
c = g:sub(i, i)
if c == "" then
p = "[^]"
return false
elseif c == "-" then
i = i + 1
c = g:sub(i, i)
if c == "" then
p = "[^]"
return false
elseif c == "]" then
p = p .. escape(c1) .. "%-]"
break
else
if not unescape() then
break
end
p = p .. escape(c1) .. "-" .. escape(c)
end
elseif c == "]" then
p = p .. escape(c1) .. "]"
break
else
p = p .. escape(c1)
i = i - 1 -- put back
end
end
i = i + 1
c = g:sub(i, i)
end
return true
end
-- Convert tokens in charset.
local function charset()
i = i + 1
c = g:sub(i, i)
if c == "" or c == "]" then
p = "[^]"
return false
elseif c == "^" or c == "!" then
i = i + 1
c = g:sub(i, i)
if c == "]" then
-- ignored
else
p = p .. "[^"
if not charset_end() then
return false
end
end
else
p = p .. "["
if not charset_end() then
return false
end
end
return true
end
-- Convert tokens.
while 1 do
i = i + 1
c = g:sub(i, i)
if c == "" then
p = p .. "$"
break
elseif c == "?" then
p = p .. "."
elseif c == "*" then
p = p .. ".*"
elseif c == "[" then
if not charset() then
break
end
elseif c == "\\" then
i = i + 1
c = g:sub(i, i)
if c == "" then
p = p .. "\\$"
break
end
p = p .. escape(c)
else
p = p .. escape(c)
end
end
return p
end
return M

View File

@ -0,0 +1,178 @@
local path = require("project_nvim.utils.path")
local uv = vim.loop
local M = {}
local is_windows = vim.fn.has('win32') or vim.fn.has('wsl')
M.recent_projects = nil -- projects from previous neovim sessions
M.session_projects = {} -- projects from current neovim session
M.has_watch_setup = false
local function open_history(mode, callback)
if callback ~= nil then -- async
path.create_scaffolding(function(_, _)
uv.fs_open(path.historyfile, mode, 438, callback)
end)
else -- sync
path.create_scaffolding()
return uv.fs_open(path.historyfile, mode, 438)
end
end
local function dir_exists(dir)
local stat = uv.fs_stat(dir)
if stat ~= nil and stat.type == "directory" then
return true
end
return false
end
local function normalise_path(path_to_normalise)
local normalised_path = path_to_normalise:gsub("\\", "/"):gsub("//", "/")
if is_windows then
normalised_path = normalised_path:sub(1,1):lower()..normalised_path:sub(2)
end
return normalised_path
end
local function delete_duplicates(tbl)
local cache_dict = {}
for _, v in ipairs(tbl) do
local normalised_path = normalise_path(v)
if cache_dict[normalised_path] == nil then
cache_dict[normalised_path] = 1
else
cache_dict[normalised_path] = cache_dict[normalised_path] + 1
end
end
local res = {}
for _, v in ipairs(tbl) do
local normalised_path = normalise_path(v)
if cache_dict[normalised_path] == 1 then
table.insert(res, normalised_path)
else
cache_dict[normalised_path] = cache_dict[normalised_path] - 1
end
end
return res
end
function M.delete_project(project)
for k, v in ipairs(M.recent_projects) do
if v == project.value then
M.recent_projects[k] = nil
end
end
end
local function deserialize_history(history_data)
-- split data to table
local projects = {}
for s in history_data:gmatch("[^\r\n]+") do
if not path.is_excluded(s) and dir_exists(s) then
table.insert(projects, s)
end
end
projects = delete_duplicates(projects)
M.recent_projects = projects
end
local function setup_watch()
-- Only runs once
if M.has_watch_setup == false then
M.has_watch_setup = true
local event = uv.new_fs_event()
if event == nil then
return
end
event:start(path.projectpath, {}, function(err, _, events)
if err ~= nil then
return
end
if events["change"] then
M.recent_projects = nil
M.read_projects_from_history()
end
end)
end
end
function M.read_projects_from_history()
open_history("r", function(_, fd)
setup_watch()
if fd ~= nil then
uv.fs_fstat(fd, function(_, stat)
if stat ~= nil then
uv.fs_read(fd, stat.size, -1, function(_, data)
uv.fs_close(fd, function(_, _) end)
deserialize_history(data)
end)
end
end)
end
end)
end
local function sanitize_projects()
local tbl = {}
if M.recent_projects ~= nil then
vim.list_extend(tbl, M.recent_projects)
vim.list_extend(tbl, M.session_projects)
else
tbl = M.session_projects
end
tbl = delete_duplicates(tbl)
local real_tbl = {}
for _, dir in ipairs(tbl) do
if dir_exists(dir) then
table.insert(real_tbl, dir)
end
end
return real_tbl
end
function M.get_recent_projects()
return sanitize_projects()
end
function M.write_projects_to_history()
-- Unlike read projects, write projects is synchronous
-- because it runs when vim ends
local mode = "w"
if M.recent_projects == nil then
mode = "a"
end
local file = open_history(mode)
if file ~= nil then
local res = sanitize_projects()
-- Trim table to last 100 entries
local len_res = #res
local tbl_out
if #res > 100 then
tbl_out = vim.list_slice(res, len_res - 100, len_res)
else
tbl_out = res
end
-- Transform table to string
local out = ""
for _, v in ipairs(tbl_out) do
out = out .. v .. "\n"
end
-- Write string out to file and close
uv.fs_write(file, out, -1)
uv.fs_close(file)
end
end
return M

View File

@ -0,0 +1,37 @@
local config = require("project_nvim.config")
local uv = vim.loop
local M = {}
M.datapath = vim.fn.stdpath("data") -- directory
M.projectpath = M.datapath .. "/project_nvim" -- directory
M.historyfile = M.projectpath .. "/project_history" -- file
function M.init()
M.datapath = require("project_nvim.config").options.datapath
M.projectpath = M.datapath .. "/project_nvim" -- directory
M.historyfile = M.projectpath .. "/project_history" -- file
end
function M.create_scaffolding(callback)
if callback ~= nil then -- async
uv.fs_mkdir(M.projectpath, 448, callback)
else -- sync
uv.fs_mkdir(M.projectpath, 448)
end
end
function M.is_excluded(dir)
for _, dir_pattern in ipairs(config.options.exclude_dirs) do
if dir:match(dir_pattern) ~= nil then
return true
end
end
return false
end
function M.exists(path)
return vim.fn.empty(vim.fn.glob(path)) == 0
end
return M

View File

@ -0,0 +1,180 @@
-- Inspiration from:
-- https://github.com/nvim-telescope/telescope-project.nvim
local has_telescope, telescope = pcall(require, "telescope")
if not has_telescope then
return
end
local finders = require("telescope.finders")
local pickers = require("telescope.pickers")
local telescope_config = require("telescope.config").values
local actions = require("telescope.actions")
local state = require("telescope.actions.state")
local builtin = require("telescope.builtin")
local entry_display = require("telescope.pickers.entry_display")
local history = require("project_nvim.utils.history")
local project = require("project_nvim.project")
local config = require("project_nvim.config")
----------
-- Actions
----------
local function create_finder()
local results = history.get_recent_projects()
-- Reverse results
for i = 1, math.floor(#results / 2) do
results[i], results[#results - i + 1] = results[#results - i + 1], results[i]
end
local displayer = entry_display.create({
separator = " ",
items = {
{
width = 30,
},
{
remaining = true,
},
},
})
local function make_display(entry)
return displayer({ entry.name, { entry.value, "Comment" } })
end
return finders.new_table({
results = results,
entry_maker = function(entry)
local name = vim.fn.fnamemodify(entry, ":t")
return {
display = make_display,
name = name,
value = entry,
ordinal = name .. " " .. entry,
}
end,
})
end
local function change_working_directory(prompt_bufnr, prompt)
local selected_entry = state.get_selected_entry(prompt_bufnr)
if selected_entry == nil then
actions.close(prompt_bufnr)
return
end
local project_path = selected_entry.value
if prompt == true then
actions._close(prompt_bufnr, true)
else
actions.close(prompt_bufnr)
end
local cd_successful = project.set_pwd(project_path, "telescope")
return project_path, cd_successful
end
local function find_project_files(prompt_bufnr)
local project_path, cd_successful = change_working_directory(prompt_bufnr, true)
local opt = {
cwd = project_path,
hidden = config.options.show_hidden,
mode = "insert",
}
if cd_successful then
builtin.find_files(opt)
end
end
local function browse_project_files(prompt_bufnr)
local project_path, cd_successful = change_working_directory(prompt_bufnr, true)
local opt = {
cwd = project_path,
hidden = config.options.show_hidden,
}
if cd_successful then
builtin.file_browser(opt)
end
end
local function search_in_project_files(prompt_bufnr)
local project_path, cd_successful = change_working_directory(prompt_bufnr, true)
local opt = {
cwd = project_path,
hidden = config.options.show_hidden,
mode = "insert",
}
if cd_successful then
builtin.live_grep(opt)
end
end
local function recent_project_files(prompt_bufnr)
local _, cd_successful = change_working_directory(prompt_bufnr, true)
local opt = {
cwd_only = true,
hidden = config.options.show_hidden,
}
if cd_successful then
builtin.oldfiles(opt)
end
end
local function delete_project(prompt_bufnr)
local selectedEntry = state.get_selected_entry(prompt_bufnr)
if selectedEntry == nil then
actions.close(prompt_bufnr)
return
end
local choice = vim.fn.confirm("Delete '" .. selectedEntry.value .. "' from project list?", "&Yes\n&No", 2)
if choice == 1 then
history.delete_project(selectedEntry)
local finder = create_finder()
state.get_current_picker(prompt_bufnr):refresh(finder, {
reset_prompt = true,
})
end
end
---Main entrypoint for Telescope.
---@param opts table
local function projects(opts)
opts = opts or {}
pickers.new(opts, {
prompt_title = "Recent Projects",
finder = create_finder(),
previewer = false,
sorter = telescope_config.generic_sorter(opts),
attach_mappings = function(prompt_bufnr, map)
map("n", "f", find_project_files)
map("n", "b", browse_project_files)
map("n", "d", delete_project)
map("n", "s", search_in_project_files)
map("n", "r", recent_project_files)
map("n", "w", change_working_directory)
map("i", "<c-f>", find_project_files)
map("i", "<c-b>", browse_project_files)
map("i", "<c-d>", delete_project)
map("i", "<c-s>", search_in_project_files)
map("i", "<c-r>", recent_project_files)
map("i", "<c-w>", change_working_directory)
local on_project_selected = function()
find_project_files(prompt_bufnr)
end
actions.select_default:replace(on_project_selected)
return true
end,
}):find()
end
return telescope.register_extension({
exports = {
projects = projects,
},
})