enc: update modal npm runner

This commit is contained in:
pojok code 2025-06-22 08:43:35 +07:00
parent 23a8cf07bf
commit 7450625ba8
6 changed files with 588 additions and 161 deletions

View file

@ -1,40 +1,43 @@
return { return {
"pojokcodeid/auto-conform.nvim", "pojokcodeid/auto-conform.nvim",
dependencies = { dependencies = {
"williamboman/mason.nvim", "williamboman/mason.nvim",
"stevearc/conform.nvim", "stevearc/conform.nvim",
}, },
event = "VeryLazy", event = "VeryLazy",
opts = function(_, opts) opts = function(_, opts)
opts.formatters = opts.formatters or {} opts.formatters = opts.formatters or {}
opts.formatters_by_ft = opts.formatters_by_ft or {} opts.formatters_by_ft = opts.formatters_by_ft or {}
opts.ensure_installed = opts.ensure_installed or {} opts.ensure_installed = opts.ensure_installed or {}
-- vim.list_extend(opts.ensure_installed, { "stylua" }) -- vim.list_extend(opts.ensure_installed, { "stylua" })
opts.lang_maps = opts.lang_maps or {} opts.lang_maps = opts.lang_maps or {}
opts.name_maps = opts.name_maps or {} opts.name_maps = opts.name_maps or {}
opts.add_new = opts.add_new or {} opts.add_new = opts.add_new or {}
opts.ignore = opts.ignore or {} opts.ignore = opts.ignore or {}
opts.format_on_save = opts.format_on_save or true opts.format_on_save = opts.format_on_save or true
opts.format_timeout_ms = opts.format_timeout_ms or 5000 opts.format_timeout_ms = opts.format_timeout_ms or 5000
end, end,
config = function(_, opts) config = function(_, opts)
require("auto-conform").setup(opts) require("auto-conform").setup(opts)
-- other conform config -- other conform config
local conform = require("conform") local conform = require("conform")
if opts.format_on_save then if opts.format_on_save then
conform.setup({ conform.setup({
format_on_save = { format_on_save = {
lsp_fallback = true, lsp_fallback = true,
timeout_ms = opts.format_timeout_ms or 5000, timeout_ms = opts.format_timeout_ms or 5000,
}, },
}) })
end end
vim.keymap.set({ "n", "v" }, "<leader>lF", function() vim.keymap.set({ "n", "v" }, "<leader>lF", function()
conform.format({ conform.format({
lsp_fallback = true, lsp_fallback = true,
async = false, async = false,
timeout_ms = opts.format_timeout_ms or 5000, timeout_ms = opts.format_timeout_ms or 5000,
}) })
end, { desc = "Format file or range (in visual mode)" }) end, { desc = "Format file or range (in visual mode)" })
end, end,
keys = {
{ "<leader>l", "", desc = " 󰘦 Lsp" },
},
} }

View file

@ -94,10 +94,29 @@ local M = {
end end
require("neotest").setup(opts) require("neotest").setup(opts)
require("pcode.user.npmrun").setup("npm run dev") local command = pcode.npm_commad
or {
dev = {
start = "NpmRunDev",
stop = "NpmStopDev",
cmd = "npm run dev",
},
prod = {
start = "NpmStart",
stop = "NpmStop",
cmd = "npm start",
},
}
require("pcode.user.npmrun").setup(command, {
show_mapping = "<leader>nm",
hide_mapping = "<leader>nh",
width = 70,
height = 20,
})
end, end,
-- stylua: ignore -- stylua: ignore
keys = { keys = {
{"<leader>n","",desc="  Npm"},
{ "<leader>T","",desc="  Test"}, { "<leader>T","",desc="  Test"},
{ "<leader>Tt", function() require("neotest").run.run(vim.fn.expand("%")) end, desc = "Run File" }, { "<leader>Tt", function() require("neotest").run.run(vim.fn.expand("%")) end, desc = "Run File" },
{ "<leader>Tr", function() require("neotest").run.run() end, desc = "Run Nearest" }, { "<leader>Tr", function() require("neotest").run.run() end, desc = "Run Nearest" },

View file

@ -3,6 +3,7 @@ return {
lazy = true, lazy = true,
event = "VeryLazy", event = "VeryLazy",
keys = { keys = {
{ "<leader>u", "", desc = " 󰀦 Notify" },
{ {
"<leader>uN", "<leader>uN",
function() function()

View file

@ -1,52 +1,112 @@
local function show_modal(text) local last_modal = nil -- global modal manager instance
local buf = vim.api.nvim_create_buf(false, true) -- buffer untuk modal
vim.api.nvim_buf_set_lines(buf, 0, -1, false, { text, "", "Press q to close" }) local function modal_manager(opts)
opts = opts or {}
local win_id = nil
local buf_id = nil
local last_content = ""
local is_shown = false
local width = 50 local function close()
local height = 5 if win_id and vim.api.nvim_win_is_valid(win_id) then
local row = math.floor((vim.o.lines - height) / 2) vim.api.nvim_win_close(win_id, true)
local col = math.floor((vim.o.columns - width) / 2) win_id = nil
end
is_shown = false
end
local win = vim.api.nvim_open_win(buf, true, { local function open(content)
relative = "editor", local lines = vim.split(content or "", "\n")
width = width, local width = opts.width or 70
height = height, local height = math.min(opts.height or 15, #lines + 6)
row = row, local row = math.floor((vim.o.lines - height) / 2)
col = col, local col = math.floor((vim.o.columns - width) / 2)
style = "minimal", if not buf_id or not vim.api.nvim_buf_is_valid(buf_id) then
border = "rounded", buf_id = vim.api.nvim_create_buf(false, true)
}) vim.api.nvim_buf_set_keymap(buf_id, "n", "q", "", {
noremap = true,
silent = true,
callback = function()
close()
end,
})
end
-- Pastikan buffer modifiable sebelum update, abaikan jika error (buffer deleted)
pcall(vim.api.nvim_buf_set_option, buf_id, "modifiable", true)
pcall(vim.api.nvim_buf_set_lines, buf_id, 0, -1, false, lines)
pcall(vim.api.nvim_buf_set_option, buf_id, "modifiable", false)
if not win_id or not vim.api.nvim_win_is_valid(win_id) then
win_id = vim.api.nvim_open_win(buf_id, true, {
relative = "editor",
width = width,
height = height,
row = row,
col = col,
style = "minimal",
border = "rounded",
})
end
is_shown = true
end
-- keymap untuk menutup modal dengan 'q' local function show()
vim.api.nvim_buf_set_keymap(buf, "n", "q", "", { if is_shown then
noremap = true, return
silent = true, end
callback = function() if last_content ~= "" then
vim.api.nvim_win_close(win, true) open(last_content)
end
end
local function hide()
close()
end
local function update(content)
last_content = content
if is_shown and buf_id and vim.api.nvim_buf_is_valid(buf_id) then
local lines = vim.split(content or "", "\n")
pcall(vim.api.nvim_buf_set_option, buf_id, "modifiable", true)
pcall(vim.api.nvim_buf_set_lines, buf_id, 0, -1, false, lines)
pcall(vim.api.nvim_buf_set_option, buf_id, "modifiable", false)
end
end
local function set_content(content)
last_content = content
if is_shown then
update(content)
else
open(content)
end
end
return {
show = show,
hide = hide,
update = update,
close = close,
set_content = set_content,
is_shown = function()
return is_shown
end, end,
}) }
end end
local M = {} local M = {}
M._last_modal = nil
local main_cmd = "npm run dev" -- default
local function log(message, level) local function log(message, level)
vim.notify(string.format("npm-dev-runner: %s", message), vim.log.levels[level]) vim.notify(string.format("npm-dev-runner: %s", message), vim.log.levels[level])
end end
-- Cache: dir -> { job_id=..., buf=... } local function find_cached_dir(dir, cache)
local job_cache = {}
local function find_cached_dir(dir)
if not dir then if not dir then
vim.notify("npm-dev-runner: No directory provided to find_cached_dir()", vim.log.levels.ERROR) vim.notify("npm-dev-runner: No directory provided to find_cached_dir()", vim.log.levels.ERROR)
return return
end end
local cur = dir local cur = dir
while not job_cache[cur] do while not cache[cur] do
if cur == "/" or string.match(cur, "^[A-Z]:\\$") then if cur == "/" or string.match(cur, "^[A-Z]:\\$") then
return return
end end
@ -55,119 +115,131 @@ local function find_cached_dir(dir)
return cur return cur
end end
local function is_running(dir) local function is_running(dir, cache)
local cached_dir = find_cached_dir(dir) local cached_dir = find_cached_dir(dir, cache)
return cached_dir and job_cache[cached_dir] return cached_dir and cache[cached_dir]
end end
local function is_windows() local function is_windows()
return vim.loop.os_uname().version:match("Windows") return vim.loop.os_uname().version:match("Windows")
end end
M.toggle = function(dir) local default_opts = {
local running = is_running(dir) show_mapping = "<leader>nm",
if not running then hide_mapping = "<leader>nh",
M.start(dir) }
return
end
M.stop(dir)
end
--- Fungsi setup menerima argumen command utama, contoh: require("npmrun").setup("pnpm dev") M.setup = function(command_table, opts)
M.setup = function(cmd) opts = vim.tbl_deep_extend("force", {}, default_opts, opts or {})
main_cmd = cmd or "npm run dev" command_table = command_table or {}
if not vim.fn.executable(main_cmd:match("%S+")) then
log(main_cmd .. " is not executable. Make sure it is installed and in PATH.", "ERROR") -- Keymap global, pakai modal terakhir yang aktif
return if opts.show_mapping then
vim.keymap.set("n", opts.show_mapping, function()
if last_modal then
last_modal.show()
end
end, { desc = "Show last NPM modal output" })
end
if opts.hide_mapping then
vim.keymap.set("n", opts.hide_mapping, function()
if last_modal then
last_modal.hide()
end
end, { desc = "Hide last NPM modal output" })
end end
local function find_dir(args) for key, conf in pairs(command_table) do
local dir = args ~= "" and args or "%:p:h" local start_cmd = conf.start or ("NpmRun" .. key)
return vim.fn.expand(vim.fn.fnamemodify(vim.fn.expand(dir), ":p")) local stop_cmd = conf.stop or ("NpmStop" .. key)
end local cmd_str = conf.cmd or "npm run dev"
local cache = {}
vim.api.nvim_create_user_command("DevStart", function(opts) local function do_start(dir)
M.start(find_dir(opts.args)) if is_running(dir, cache) then
end, { nargs = "?" }) log(cmd_str .. " already running", "INFO")
return
end
vim.api.nvim_create_user_command("DevStop", function(opts) local all_output = {}
M.stop(find_dir(opts.args)) local modal = modal_manager(opts)
end, { nargs = "?" }) last_modal = modal
M._last_modal = modal
vim.api.nvim_create_user_command("DevToggle", function(opts) local cmd
M.toggle(find_dir(opts.args)) if is_windows() then
end, { nargs = "?" }) cmd = { "cmd.exe", "/C" }
end for word in cmd_str:gmatch("%S+") do
table.insert(cmd, word)
end
else
cmd = {}
for word in cmd_str:gmatch("%S+") do
table.insert(cmd, word)
end
end
M.start = function(dir) local function process_lines(lines)
if is_running(dir) then if not lines then
log(main_cmd .. " already running", "INFO") return
return end
end for _, l in ipairs(lines) do
table.insert(all_output, tostring(l))
end
modal.set_content(table.concat(all_output, "\n"))
end
local cmd local job_id = vim.fn.jobstart(cmd, {
if is_windows() then cwd = dir,
cmd = { "cmd.exe", "/C" } stdout_buffered = false,
for word in main_cmd:gmatch("%S+") do stderr_buffered = false,
table.insert(cmd, word) on_stdout = function(_, data)
end process_lines(data)
else end,
cmd = {} on_stderr = function(_, data)
for word in main_cmd:gmatch("%S+") do process_lines(data)
table.insert(cmd, word) end,
end on_exit = function(_, code)
end table.insert(all_output, ("Process exited with code: %d"):format(code))
modal.set_content(table.concat(all_output, "\n"))
cache[dir] = nil
end,
})
local function append_to_buffer(lines) cache[dir] = { job_id = job_id, modal = modal }
if not lines then log(cmd_str .. " started", "INFO")
return
end end
for _, line in ipairs(lines) do local function do_stop(dir)
if line ~= "" then local running = is_running(dir, cache)
line = tostring(line) if running then
line = line:gsub("^%s*(.-)%s*$", "%1") local cached_dir = find_cached_dir(dir, cache)
if string.find(line, "http") then if cached_dir then
show_modal(line) local job_entry = cache[cached_dir]
break if job_entry then
vim.fn.jobstop(job_entry.job_id)
if job_entry.modal then
job_entry.modal.close()
end
end
cache[cached_dir] = nil
log(cmd_str .. " stopped", "INFO")
end end
end end
end end
end
local job_id = vim.fn.jobstart(cmd, { local function find_dir(args)
cwd = dir, local dir = args ~= "" and args or "%:p:h"
stdout_buffered = false, -- streaming mode return vim.fn.expand(vim.fn.fnamemodify(vim.fn.expand(dir), ":p"))
stderr_buffered = false,
on_stdout = function(_, data)
append_to_buffer(data)
end,
on_stderr = function(_, data)
append_to_buffer(vim.tbl_map(function(l)
return "[ERR] " .. l
end, data))
end,
on_exit = function(_, _)
job_cache[dir] = nil
end,
})
job_cache[dir] = { job_id = job_id }
log(main_cmd .. " started", "INFO")
end
M.stop = function(dir)
local running = is_running(dir)
if running then
local cached_dir = find_cached_dir(dir)
if cached_dir then
local job_entry = job_cache[cached_dir]
if job_entry then
vim.fn.jobstop(job_entry.job_id)
end
job_cache[cached_dir] = nil
log(main_cmd .. " stopped", "INFO")
end end
vim.api.nvim_create_user_command(start_cmd, function(opts)
do_start(find_dir(opts.args))
end, { nargs = "?" })
vim.api.nvim_create_user_command(stop_cmd, function(opts)
do_stop(find_dir(opts.args))
end, { nargs = "?" })
end end
end end

174
lua/pcode/user/npmrun3.lua Normal file
View file

@ -0,0 +1,174 @@
local function show_modal(text)
local buf = vim.api.nvim_create_buf(false, true) -- buffer untuk modal
vim.api.nvim_buf_set_lines(buf, 0, -1, false, { text, "", "Press q to close" })
local width = 50
local height = 5
local row = math.floor((vim.o.lines - height) / 2)
local col = math.floor((vim.o.columns - width) / 2)
local win = vim.api.nvim_open_win(buf, true, {
relative = "editor",
width = width,
height = height,
row = row,
col = col,
style = "minimal",
border = "rounded",
})
-- keymap untuk menutup modal dengan 'q'
vim.api.nvim_buf_set_keymap(buf, "n", "q", "", {
noremap = true,
silent = true,
callback = function()
vim.api.nvim_win_close(win, true)
end,
})
end
local M = {}
local main_cmd = "npm run dev" -- default
local function log(message, level)
vim.notify(string.format("npm-dev-runner: %s", message), vim.log.levels[level])
end
-- Cache: dir -> { job_id=..., buf=... }
local job_cache = {}
local function find_cached_dir(dir)
if not dir then
vim.notify("npm-dev-runner: No directory provided to find_cached_dir()", vim.log.levels.ERROR)
return
end
local cur = dir
while not job_cache[cur] do
if cur == "/" or string.match(cur, "^[A-Z]:\\$") then
return
end
cur = vim.fn.fnamemodify(cur, ":h")
end
return cur
end
local function is_running(dir)
local cached_dir = find_cached_dir(dir)
return cached_dir and job_cache[cached_dir]
end
local function is_windows()
return vim.loop.os_uname().version:match("Windows")
end
M.toggle = function(dir)
local running = is_running(dir)
if not running then
M.start(dir)
return
end
M.stop(dir)
end
--- Fungsi setup menerima argumen command utama, contoh: require("npmrun").setup("pnpm dev")
M.setup = function(cmd)
main_cmd = cmd or "npm run dev"
if not vim.fn.executable(main_cmd:match("%S+")) then
log(main_cmd .. " is not executable. Make sure it is installed and in PATH.", "ERROR")
return
end
local function find_dir(args)
local dir = args ~= "" and args or "%:p:h"
return vim.fn.expand(vim.fn.fnamemodify(vim.fn.expand(dir), ":p"))
end
vim.api.nvim_create_user_command("DevStart", function(opts)
M.start(find_dir(opts.args))
end, { nargs = "?" })
vim.api.nvim_create_user_command("DevStop", function(opts)
M.stop(find_dir(opts.args))
end, { nargs = "?" })
vim.api.nvim_create_user_command("DevToggle", function(opts)
M.toggle(find_dir(opts.args))
end, { nargs = "?" })
end
M.start = function(dir)
if is_running(dir) then
log(main_cmd .. " already running", "INFO")
return
end
local cmd
if is_windows() then
cmd = { "cmd.exe", "/C" }
for word in main_cmd:gmatch("%S+") do
table.insert(cmd, word)
end
else
cmd = {}
for word in main_cmd:gmatch("%S+") do
table.insert(cmd, word)
end
end
local function append_to_buffer(lines)
if not lines then
return
end
for _, line in ipairs(lines) do
if line ~= "" then
line = tostring(line)
line = line:gsub("^%s*(.-)%s*$", "%1")
if string.find(line, "http") then
show_modal(line)
break
end
end
end
end
local job_id = vim.fn.jobstart(cmd, {
cwd = dir,
stdout_buffered = false, -- streaming mode
stderr_buffered = false,
on_stdout = function(_, data)
append_to_buffer(data)
end,
on_stderr = function(_, data)
append_to_buffer(vim.tbl_map(function(l)
return "[ERR] " .. l
end, data))
end,
on_exit = function(_, _)
job_cache[dir] = nil
end,
})
job_cache[dir] = { job_id = job_id }
log(main_cmd .. " started", "INFO")
end
M.stop = function(dir)
local running = is_running(dir)
if running then
local cached_dir = find_cached_dir(dir)
if cached_dir then
local job_entry = job_cache[cached_dir]
if job_entry then
vim.fn.jobstop(job_entry.job_id)
end
job_cache[cached_dir] = nil
log(main_cmd .. " stopped", "INFO")
end
end
end
return M

158
lua/pcode/user/npmrun4.lua Normal file
View file

@ -0,0 +1,158 @@
local function show_modal(text)
local buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(buf, 0, -1, false, { text, "", "Press q to close" })
local width = 50
local height = 5
local row = math.floor((vim.o.lines - height) / 2)
local col = math.floor((vim.o.columns - width) / 2)
local win = vim.api.nvim_open_win(buf, true, {
relative = "editor",
width = width,
height = height,
row = row,
col = col,
style = "minimal",
border = "rounded",
})
vim.api.nvim_buf_set_keymap(buf, "n", "q", "", {
noremap = true,
silent = true,
callback = function()
vim.api.nvim_win_close(win, true)
end,
})
end
local M = {}
local function log(message, level)
vim.notify(string.format("npm-dev-runner: %s", message), vim.log.levels[level])
end
-- Fungsi untuk mencari cache dir (supaya stop/start tetap per dir/project)
local function find_cached_dir(dir, cache)
if not dir then
vim.notify("npm-dev-runner: No directory provided to find_cached_dir()", vim.log.levels.ERROR)
return
end
local cur = dir
while not cache[cur] do
if cur == "/" or string.match(cur, "^[A-Z]:\\$") then
return
end
cur = vim.fn.fnamemodify(cur, ":h")
end
return cur
end
-- Fungsi cek proses running
local function is_running(dir, cache)
local cached_dir = find_cached_dir(dir, cache)
return cached_dir and cache[cached_dir]
end
local function is_windows()
return vim.loop.os_uname().version:match("Windows")
end
M.setup = function(command_table)
command_table = command_table or {}
for key, conf in pairs(command_table) do
local start_cmd = conf.start or ("NpmRun" .. key)
local stop_cmd = conf.stop or ("NpmStop" .. key)
local cmd_str = conf.cmd or "npm run dev"
-- cache khusus untuk mode ini
local cache = {}
-- Fungsi Start
local function do_start(dir)
if is_running(dir, cache) then
log(cmd_str .. " already running", "INFO")
return
end
local cmd
if is_windows() then
cmd = { "cmd.exe", "/C" }
for word in cmd_str:gmatch("%S+") do
table.insert(cmd, word)
end
else
cmd = {}
for word in cmd_str:gmatch("%S+") do
table.insert(cmd, word)
end
end
local function process_lines(lines)
if not lines then
return
end
for _, line in ipairs(lines) do
if line ~= "" then
local str = tostring(line):gsub("^%s*(.-)%s*$", "%1")
if string.find(str, "http") then
show_modal(str)
end
end
end
end
local job_id = vim.fn.jobstart(cmd, {
cwd = dir,
stdout_buffered = false,
stderr_buffered = false,
on_stdout = function(_, data)
process_lines(data)
end,
on_stderr = function(_, data)
process_lines(vim.tbl_map(function(l)
return "[ERR] " .. l
end, data))
end,
on_exit = function(_, _)
cache[dir] = nil
end,
})
cache[dir] = { job_id = job_id }
log(cmd_str .. " started", "INFO")
end
-- Fungsi Stop
local function do_stop(dir)
local running = is_running(dir, cache)
if running then
local cached_dir = find_cached_dir(dir, cache)
if cached_dir then
local job_entry = cache[cached_dir]
if job_entry then
vim.fn.jobstop(job_entry.job_id)
end
cache[cached_dir] = nil
log(cmd_str .. " stopped", "INFO")
end
end
end
local function find_dir(args)
local dir = args ~= "" and args or "%:p:h"
return vim.fn.expand(vim.fn.fnamemodify(vim.fn.expand(dir), ":p"))
end
-- Register Perintah
vim.api.nvim_create_user_command(start_cmd, function(opts)
do_start(find_dir(opts.args))
end, { nargs = "?" })
vim.api.nvim_create_user_command(stop_cmd, function(opts)
do_stop(find_dir(opts.args))
end, { nargs = "?" })
end
end
return M