diff --git a/docs/installation.md b/docs/installation.md index 0597072..9c1a863 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -13,19 +13,23 @@ This is the **recommended** way if you're using [Lazy.nvim](https://github.com/f ```lua { - "DarthMooMancer/Polydev", - opts = { - lua = { - project_root = "~/Projects/Lua", - keybinds = { - ["pr"] = "LuaRun", - }, + "DarthMooMancer/Polydev", + dependencies = { + "MunifTanjim/nui.nvim", + "nvim-lua/plenary.nvim" }, - terminal = { - border = true, - number = true, + opts = { + lua = { + project_root = "~/Projects/Lua", + keybinds = { + ["pr"] = "LuaRun", + }, + }, + terminal = { + border = true, + number = true, + }, }, - }, } ``` @@ -38,20 +42,26 @@ This is the **recommended** way if you're using [Lazy.nvim](https://github.com/f For users of [mini.nvim](https://github.com/echasnovski/mini.nvim)'s dependency manager: ```lua -MiniDeps.add({ source = "DarthMooMancer/Polydev" }) +MiniDeps.add({ + source = "DarthMooMancer/Polydev" + depends = { + "MunifTanjim/nui.nvim", + "nvim-lua/plenary.nvim" + } +}) require("Polydev").setup({ - rust = { - project_root = "~/Projects/Rust", - keybinds = { - ["pb"] = "RustBuild", - ["pr"] = "RustRun", + rust = { + project_root = "~/Projects/Rust", + keybinds = { + ["pb"] = "RustBuild", + ["pr"] = "RustRun", + }, + }, + terminal = { + border = true, + number = true, }, - }, - terminal = { - border = true, - number = true, - }, }) ``` @@ -63,21 +73,25 @@ For fans of [Packer.nvim](https://github.com/wbthomason/packer.nvim): ```lua use({ - "DarthMooMancer/Polydev", - config = function() - require("Polydev").setup({ - python = { - project_root = "~/Projects/Python", - keybinds = { - ["pb"] = "PythonPip", - ["pr"] = "PythonRun", - }, - }, - terminal = { - relativenumber = true, - }, - }) - end, + "DarthMooMancer/Polydev", + requires = { + "MunifTanjim/nui.nvim", + "nvim-lua/plenary.nvim" + }, + config = function() + require("Polydev").setup({ + python = { + project_root = "~/Projects/Python", + keybinds = { + ["pb"] = "PythonPip", + ["pr"] = "PythonRun", + }, + }, + terminal = { + relativenumber = true, + }, + }) + end, }) ``` @@ -89,20 +103,22 @@ Even if you're still using [vim-plug](https://github.com/junegunn/vim-plug), you ```vim Plug 'DarthMooMancer/Polydev' +Plug 'MunifTanjim/nui.nvim' +Plug 'nvim-lua/plenary.nvim' lua << EOF - require("Polydev").setup({ +require("Polydev").setup({ c = { - project_root = "~/Projects/C", - keybinds = { - ["pb"] = "CBuild", - ["pr"] = "CRun", - }, + project_root = "~/Projects/C", + keybinds = { + ["pb"] = "CBuild", + ["pr"] = "CRun", + }, }, terminal = { - number = true, + number = true, }, - }) +}) EOF ``` @@ -120,8 +136,8 @@ Then in your config: ```lua require("Polydev").setup({ - terminal = { - border = true, - }, + terminal = { + border = true, + }, }) ``` diff --git a/docs/setup.md b/docs/setup.md index ba32a3a..e015b38 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -1,5 +1,8 @@ # Setup and Customizing Polydev +## Project Manager +Follows the netrw keybinds and allows for project management all in one spot + ## 🔧 Project Name Customization You can easily customize the default directory for your project! The project can be created from any directory, and Polydev will automatically create the required project root directory for you. @@ -44,11 +47,10 @@ return { ... opts = { = { - [""] = "CloseTerminal", -- Universal, can be changed per language for now ["pb"] = "languageBuild", -- Per-language ["pr"] = "languageRun", -- Per-language - ["nf"] = "NewLanguageFile", -- Universal ["nh"] = "NewLanguageSecondaryFile", -- Universal + ["po"] = "OpenSmartTable", -- Universal } } ... @@ -60,11 +62,10 @@ return { ```lua require("Polydev").language.setup({ keybinds = { - [""] = "CloseTerminal", -- Universal, can be changed per language for now ["pb"] = "languageBuild", -- Per-language ["pr"] = "languageRun", -- Per-language - ["nf"] = "NewLanguageFile", -- Universal ["nh"] = "NewLanguageSecondaryFile", -- Universal + ["po"] = "OpenSmartTable", -- Universal } }) ``` diff --git a/lua/Polydev/init.lua b/lua/Polydev/init.lua index b4bc945..8bf7d94 100644 --- a/lua/Polydev/init.lua +++ b/lua/Polydev/init.lua @@ -1,11 +1,9 @@ ----@module "Polydev.configs" local config = require("Polydev.configs") - ----@module "Polydev.utils" local utils = require("Polydev.utils") - ----@module "Polydev.templates" local templates = require("Polydev.templates") +local Popup = require("nui.popup") +local event = require("nui.utils.autocmd").event +local Path = require("plenary.path") ---@type table local M = {} @@ -21,37 +19,247 @@ function M.load_language_module(lang, opts) local ok, lang_module = pcall(require, "Polydev.languages." .. lang) if ok and lang_module.setup then - local lang_opts = opts and opts[lang] or {} - lang_module.setup(lang_opts) - M.loaded_languages[lang] = lang_module - return true + local lang_opts = opts and opts[lang] or {} + lang_module.setup(lang_opts) + M.loaded_languages[lang] = lang_module + return true end return false end -function M.create_project() - local opts = {} - vim.ui.input({ prompt = "Enter language for project: " }, function(lang) - if not lang or lang == "" then return print("Project creation canceled.") end - opts = vim.tbl_deep_extend("force", {}, config.get(lang), opts or {}) - if M.load_language_module(lang) and templates.projects[lang] and templates.projects[lang].run then - templates.create_project(lang, opts.project_root) - else - print("Error: No project creation method for " .. lang) +-- Get entries in a given path +local function get_entries(path) + local result = {} + local handle = vim.loop.fs_scandir(path) + if not handle then return result end + while true do + local name, type = vim.loop.fs_scandir_next(handle) + if not name then break end + -- Check if this is a project file + local is_project = false + if type == "file" then + local ext = name:match("%.([^%.]+)$") + if ext == "project" then + is_project = true + type = "project" + end end + table.insert(result, { + name = name .. (type == "directory" and "/" or ""), + type = type, + full_path = path .. "/" .. name, + is_project = is_project + }) + end + table.sort(result, function(a, b) + -- Sort projects first, then directories, then files + if a.is_project ~= b.is_project then + return a.is_project + end + if a.type == "directory" and b.type ~= "directory" then + return true + end + if b.type == "directory" and a.type ~= "directory" then + return false + end + return a.name < b.name end) + return result end -function M.create_new_file() - vim.ui.input({ prompt = "Enter language for new file: " }, function(lang) - if not lang or lang == "" then return print("File creation canceled.") end +-- Fuzzy search +local function fuzzy_filter(list, query) + local result = {} + query = query:lower() + for _, entry in ipairs(list) do + if entry.name:lower():find(query) then + table.insert(result, entry) + end + end + return result +end - if M.load_language_module(lang) and templates.files[lang] and templates.files[lang].run then - templates.create_new_file(lang) - else - print("Error: No File creation method for " .. lang) +-- Main function +local function open_filtered_table() + local display_dir = vim.fn.expand(vim.fn.getcwd()) + local cwd = display_dir + local entries = get_entries(cwd) + local current_view = vim.deepcopy(entries) + + local popup = Popup({ + enter = true, + focusable = true, + border = { + style = "rounded", + text = { top = " Project Manager ", top_align = "center" }, + highlight = "Normal", + }, + position = "50%", + size = { width = 60, height = 14 }, + buf_options = { modifiable = true, readonly = false }, + }) + + local function get_selected_entry() + local row = vim.api.nvim_win_get_cursor(0)[1] - 1 + return current_view[row], row + end + + local function refresh() + entries = get_entries(cwd) + local root = vim.fn.expand("~/Projects") + if cwd ~= root then + table.insert(entries, 1, { + name = "../", + type = "directory", + full_path = Path:new(cwd):parent():absolute(), + is_up = true + }) + end + current_view = vim.deepcopy(entries) + end + + local function render() + if not popup.bufnr then return end + vim.api.nvim_buf_set_lines(popup.bufnr, 0, -1, false, {}) + for i, item in ipairs(current_view) do + local line = string.format("%-3d | %-30s | %-6s", i, item.name, item.type) + vim.api.nvim_buf_set_lines(popup.bufnr, -1, -1, false, { line }) + end + + -- Add key mappings legend + local hints = { + "", + "Keybinds:", + " : Open file / Enter directory", + " : Go to parent directory", + " / : Filter (fuzzy search)", + " a : Create new folder", + " % : Create new file", + " d : Create new project", + " R : Rename file/folder", + " D : Delete file/folder", + " q : Quit popup", + } + + vim.api.nvim_buf_set_lines(popup.bufnr, -1, -1, false, hints) + end + + local function prompt_filter() + vim.ui.input({ prompt = "Search: " }, function(input) + if input then + current_view = fuzzy_filter(entries, input) + render() + end + end) + end + + -- Mappings + popup:map("n", "q", function() popup:unmount() end) + popup:map("n", "/", prompt_filter) + + -- Creates a new folder + popup:map("n", "a", function() + vim.ui.input({ prompt = "New folder name:" }, function(input) + if input then + vim.fn.mkdir(cwd .. "/" .. input, "p") + refresh() + render() + end + end) + end) + + -- Deletes Folder and confirms deletion + popup:map("n", "D", function() + local entry = get_selected_entry() + if entry and not entry.is_up then + vim.ui.input({ prompt = "(y/N) Defaults to N: " .. entry.name }, function(input) + if input == "y" or input == "Y" then + vim.fn.delete(entry.full_path, "rf") + refresh() + render() + end + end) end end) + + -- Creates a new file in project + popup:map("n", "%", function() + vim.ui.input({ prompt = "Enter language for new file: " }, function(lang) + if not lang or lang == "" then return print("File creation canceled.") end + + if M.load_language_module(lang) and templates.files[lang] and templates.files[lang].run then + vim.ui.input({ prompt = "Enter file name: " }, function(file_name) + if not file_name or file_name == "" then return print("File creation canceled.") end + templates.files[lang].run(file_name) + refresh() + render() + end) + else + print("Error: No File creation method for " .. lang) + end + end) + end) + + -- Creates a new project + popup:map("n", "d", function() + local opts = {} + vim.ui.input({ prompt = "Enter language for project: " }, function(lang) + if not lang or lang == "" then return print("Project creation canceled.") end + opts = vim.tbl_deep_extend("force", {}, config.get(lang), opts or {}) + if M.load_language_module(lang) and templates.projects[lang] and templates.projects[lang].run then + templates.create_project(lang, opts.project_root) + vim.cmd("cd " .. templates.dir .. "/src") + refresh() + render() + else + print("Error: No project creation method for " .. lang) + end + end) + end) + + -- Renames Files and Folders + popup:map("n", "R", function() + local entry = get_selected_entry() + if entry and not entry.is_up then + vim.ui.input({ prompt = "Rename:", default = entry.name }, function(input) + if input then + local new_path = cwd .. "/" .. input + vim.fn.rename(entry.full_path, new_path) + refresh() + render() + end + end) + end + end) + + -- Changes working directory or edits a file + popup:map("n", "", function() + local entry = get_selected_entry() + if entry and entry.type == "directory" then + cwd = entry.full_path + vim.cmd("cd " .. cwd) + refresh() + render() + end + if entry and entry.type == "file" then + vim.cmd("edit " .. entry.name) + end + end) + + popup:map("n", "", function() + local parent = Path:new(cwd):parent():absolute() + if parent ~= cwd then + cwd = parent + refresh() + render() + end + end) + + popup:on(event.BufLeave, function() popup:unmount() end) + + popup:mount() + refresh() + render() end ---@param opts table @@ -64,10 +272,8 @@ function M.setup(opts) config.user_config[lang] = vim.tbl_deep_extend("force", default, user_opts) end - vim.api.nvim_create_user_command("NewProject", M.create_project, {}) - vim.api.nvim_create_user_command("NewFile", M.create_new_file, {}) - vim.keymap.set("n", "np", ":NewProject", { silent = true }) - vim.keymap.set("n", "nf", ":NewFile", { silent = true }) + vim.api.nvim_create_user_command("OpenSmartTable", open_filtered_table, {}) + vim.keymap.set("n", "po", ":OpenSmartTable", { silent = true }) vim.api.nvim_create_autocmd("FileType", { pattern = "*", callback = function() diff --git a/lua/Polydev/templates.lua b/lua/Polydev/templates.lua index eed4976..e9d21a8 100644 --- a/lua/Polydev/templates.lua +++ b/lua/Polydev/templates.lua @@ -9,7 +9,7 @@ local utils = require("Polydev.utils") local M = {} ---@class TemplateLanguage ----@field run fun(project_name: string, project_root: string): nil +---@field run fun(project_name: string, project_root: string): string ---@class Projects ---@field java TemplateLanguage @@ -35,7 +35,7 @@ public class Main { } ]] utils.write_file(full_project_root .. "/src/Main.java", main_java_content) - return nil + return full_project_root end }, python = { @@ -69,7 +69,7 @@ setup( vim.fn.setenv("VIRTUAL_ENV", venv_path) vim.fn.setenv("PATH", venv_path .. "/bin:" .. vim.fn.getenv("PATH")) print("Virtual environment activated for " .. project_name) - return nil + return full_project_root end }, lua = { @@ -83,7 +83,7 @@ return M ]]) utils.write_file(full_project_root .. "/" .. project_name .. ".polydev", "[[DO NOT REMOVE THIS FILE]]") vim.cmd("edit " .. full_project_root .. "/lua/" .. project_name .. "/init.lua") - return nil + return full_project_root end }, c = { @@ -150,6 +150,7 @@ set(CMAKE_C_STANDARD_REQUIRED ON) ]])) vim.cmd("edit " .. full_project_root .. "/src/main.c") vim.fn.system(string.format("cd %s/build/ && cmake .. && cmake --build .", full_project_root)) + return full_project_root end }, cpp = { @@ -218,7 +219,7 @@ set(CMAKE_C_STANDARD_REQUIRED ON) ]])) vim.fn.system(string.format("cd %s/build/ && cmake .. && cmake --build .", full_project_root)) vim.cmd("edit " .. full_project_root .. "/src/main.cpp") - return nil + return full_project_root end }, rust = { @@ -226,6 +227,7 @@ set(CMAKE_C_STANDARD_REQUIRED ON) local full_project_root = vim.fn.expand(project_root) .. "/" .. project_name vim.fn.system("cargo new " .. full_project_root) vim.cmd("edit " .. full_project_root .. "/src/main.rs") + return full_project_root end }, html = { @@ -246,6 +248,7 @@ set(CMAKE_C_STANDARD_REQUIRED ON) ]] utils.write_file(full_project_root .. "index.html", main_html_content) + return full_project_root end }, } @@ -376,12 +379,15 @@ public class %s { } } +---@type string +M.dir = "" + ---@param lang TemplatesLanguageName ---@param project_root string function M.create_project(lang, project_root) vim.ui.input({ prompt = "Enter project name: " }, function(project_name) if not project_name or project_name == "" then return print("Project creation canceled.") end - M.projects[lang].run(project_name, project_root) + M.dir = M.projects[lang].run(project_name, project_root) end) end