diff --git a/lazy-lock.json b/lazy-lock.json index 1608a3f..9b8df1b 100644 --- a/lazy-lock.json +++ b/lazy-lock.json @@ -34,6 +34,7 @@ "minty": { "branch": "main", "commit": "aafc9e8e0afe6bf57580858a2849578d8d8db9e0" }, "neotest": { "branch": "master", "commit": "ef492755730e59e1d8122c461abbd086bee4c76b" }, "neotest-golang": { "branch": "main", "commit": "0a0acff3cf9a0bf140c84ea9e66d818df7c2608f" }, + "neotest-jest": { "branch": "main", "commit": "dd82016c01704d9bbd5188749fe0586b3b620693" }, "neotest-plenary": { "branch": "master", "commit": "3523adcf9ffaad1911960c5813b0136c1b63a2ec" }, "neotest-vim-test": { "branch": "master", "commit": "75c4228882ae4883b11bfce9b8383e637eb44192" }, "noice.nvim": { "branch": "main", "commit": "0427460c2d7f673ad60eb02b35f5e9926cf67c59" }, @@ -45,8 +46,9 @@ "nvim-dap-go": { "branch": "main", "commit": "8763ced35b19c8dc526e04a70ab07c34e11ad064" }, "nvim-dap-ui": { "branch": "master", "commit": "73a26abf4941aa27da59820fd6b028ebcdbcf932" }, "nvim-dap-virtual-text": { "branch": "master", "commit": "fbdb48c2ed45f4a8293d0d483f7730d24467ccb6" }, + "nvim-dap-vscode-js": { "branch": "main", "commit": "03bd29672d7fab5e515fc8469b7d07cc5994bbf6" }, "nvim-lint": { "branch": "master", "commit": "2b0039b8be9583704591a13129c600891ac2c596" }, - "nvim-lspconfig": { "branch": "master", "commit": "0112e1f77983141e1453bd37d124302f1c876c46" }, + "nvim-lspconfig": { "branch": "master", "commit": "69a1624aff5dc30dddd0c59d99a947b63c80bf2a" }, "nvim-material-icon": { "branch": "main", "commit": "38fc13fe4811c4bf62533180ff5e7bbd237c5ef5" }, "nvim-navic": { "branch": "master", "commit": "f887d794a0f4594882814d7780980a949200a238" }, "nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" }, @@ -70,6 +72,7 @@ "vim-visual-multi": { "branch": "master", "commit": "a6975e7c1ee157615bbc80fc25e4392f71c344d4" }, "virt-column.nvim": { "branch": "master", "commit": "b87e3e0864211a32724a2ebf3be37e24e9e2fa99" }, "volt": { "branch": "main", "commit": "7b8c5e790120d9f08c8487dcb80692db6d2087a1" }, + "vscode-js-debug": { "branch": "main", "commit": "a3279c2abc1162069b0f5c014542b8b849cefa70" }, "which-key.nvim": { "branch": "main", "commit": "370ec46f710e058c9c1646273e6b225acf47cbed" }, "yanky.nvim": { "branch": "main", "commit": "04775cc6e10ef038c397c407bc17f00a2f52b378" } } diff --git a/lua/pcode/plugins/extras/indentscupe.lua b/lua/pcode/plugins/extras/indentscupe.lua index 6bde87b..ad4a10a 100644 --- a/lua/pcode/plugins/extras/indentscupe.lua +++ b/lua/pcode/plugins/extras/indentscupe.lua @@ -23,6 +23,9 @@ return { "toggleterm", "Trouble", "trouble", + "term", + "terminal", + "zsh", }, callback = function() vim.b.miniindentscope_disable = true diff --git a/lua/pcode/plugins/lang/javascript.lua b/lua/pcode/plugins/lang/javascript.lua index 6996fe6..fd637a3 100644 --- a/lua/pcode/plugins/lang/javascript.lua +++ b/lua/pcode/plugins/lang/javascript.lua @@ -1,104 +1,101 @@ local config_file = "jest.config.ts" local M = { - { - "nvim-treesitter/nvim-treesitter", - opts = function(_, opts) - opts.ensure_installed = opts.ensure_installed or {} - vim.list_extend( - opts.ensure_installed, - { "html", "javascript", "typescript", "tsx", "css", "json", "jsonc" } - ) - end, - }, - { - "williamboman/mason-lspconfig.nvim", - opts = function(_, opts) - opts.ensure_installed = opts.ensure_installed or {} - vim.list_extend(opts.ensure_installed, { "html", "eslint", "cssls", "emmet_ls", "jsonls", "ts_ls" }) - end, - }, - { - "pojokcodeid/auto-conform.nvim", - event = "VeryLazy", - opts = function(_, opts) - opts.formatters_by_ft = opts.formatters_by_ft or {} - local package = "prettier" - vim.list_extend(opts.ensure_installed, { package }) - opts.formatters_by_ft.javascript = { package } - end, - }, - { - "nvim-neotest/neotest", - dependencies = { - "antoinemadec/FixCursorHold.nvim", - "nvim-neotest/neotest-jest", - "nvim-neotest/nvim-nio", - }, - opts = { - adapters = { - ["neotest-jest"] = { - jestCommand = "npm test -- ", - jestConfigFile = function() - local file = vim.fn.expand("%:p") - if string.find(file, "/packages/") then - return string.match(file, "(.-/[^/]+/)src") .. config_file - end - return vim.fn.getcwd() .. "/" .. config_file - end, - cwd = function() - local file = vim.fn.expand("%:p") - if string.find(file, "/packages/") then - return string.match(file, "(.-/[^/]+/)src") - end - return vim.fn.getcwd() - end, - }, - }, - status = { virtual_text = true }, - output = { open_on_run = true }, - }, - config = function(_, opts) - local neotest_ns = vim.api.nvim_create_namespace("neotest") - vim.diagnostic.config({ - virtual_text = { - format = function(diagnostic) - -- Replace newline and tab characters with space for more compact diagnostics - local message = - diagnostic.message:gsub("\n", " "):gsub("\t", " "):gsub("%s+", " "):gsub("^%s+", "") - return message - end, - }, - }, neotest_ns) + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + opts.ensure_installed = opts.ensure_installed or {} + vim.list_extend(opts.ensure_installed, { "html", "javascript", "typescript", "tsx", "css", "json", "jsonc" }) + end, + }, + { + "williamboman/mason-lspconfig.nvim", + opts = function(_, opts) + opts.ensure_installed = opts.ensure_installed or {} + vim.list_extend(opts.ensure_installed, { "html", "eslint", "cssls", "emmet_ls", "jsonls", "ts_ls" }) + end, + }, + { + "pojokcodeid/auto-conform.nvim", + event = "VeryLazy", + opts = function(_, opts) + opts.formatters_by_ft = opts.formatters_by_ft or {} + local package = "prettier" + vim.list_extend(opts.ensure_installed, { package }) + opts.formatters_by_ft.javascript = { package } + end, + }, + { + "nvim-neotest/neotest", + dependencies = { + "antoinemadec/FixCursorHold.nvim", + "nvim-neotest/neotest-jest", + "nvim-neotest/nvim-nio", + }, + opts = { + adapters = { + ["neotest-jest"] = { + jestCommand = "npm test -- ", + jestConfigFile = function() + local file = vim.fn.expand("%:p") + if string.find(file, "/packages/") then + return string.match(file, "(.-/[^/]+/)src") .. config_file + end + return vim.fn.getcwd() .. "/" .. config_file + end, + cwd = function() + local file = vim.fn.expand("%:p") + if string.find(file, "/packages/") then + return string.match(file, "(.-/[^/]+/)src") + end + return vim.fn.getcwd() + end, + }, + }, + status = { virtual_text = true }, + output = { open_on_run = true }, + }, + config = function(_, opts) + local neotest_ns = vim.api.nvim_create_namespace("neotest") + vim.diagnostic.config({ + virtual_text = { + format = function(diagnostic) + -- Replace newline and tab characters with space for more compact diagnostics + local message = diagnostic.message:gsub("\n", " "):gsub("\t", " "):gsub("%s+", " "):gsub("^%s+", "") + return message + end, + }, + }, neotest_ns) - opts.consumers = opts.consumers or {} - if opts.adapters then - local adapters = {} - for name, config in pairs(opts.adapters or {}) do - if type(name) == "number" then - if type(config) == "string" then - config = require(config) - end - adapters[#adapters + 1] = config - elseif config ~= false then - local adapter = require(name) - if type(config) == "table" and not vim.tbl_isempty(config) then - local meta = getmetatable(adapter) - if adapter.setup then - adapter.setup(config) - elseif meta and meta.__call then - adapter(config) - else - error("Adapter " .. name .. " does not support setup") - end - end - adapters[#adapters + 1] = adapter - end - end - opts.adapters = adapters - end + opts.consumers = opts.consumers or {} + if opts.adapters then + local adapters = {} + for name, config in pairs(opts.adapters or {}) do + if type(name) == "number" then + if type(config) == "string" then + config = require(config) + end + adapters[#adapters + 1] = config + elseif config ~= false then + local adapter = require(name) + if type(config) == "table" and not vim.tbl_isempty(config) then + local meta = getmetatable(adapter) + if adapter.setup then + adapter.setup(config) + elseif meta and meta.__call then + adapter(config) + else + error("Adapter " .. name .. " does not support setup") + end + end + adapters[#adapters + 1] = adapter + end + end + opts.adapters = adapters + end - require("neotest").setup(opts) - end, + require("neotest").setup(opts) + require("pcode.user.npmrun").setup("npm run dev") + end, -- stylua: ignore keys = { { "T","",desc="  Test"}, @@ -111,92 +108,92 @@ local M = { { "TO", function() require("neotest").output_panel.toggle() end, desc = "Toggle Output Panel" }, { "TS", function() require("neotest").run.stop() end, desc = "Stop" }, }, - }, - { - "rcarriga/nvim-dap-ui", - lazy = true, - event = "BufRead", - dependencies = { - { "mfussenegger/nvim-dap", lazy = true }, - { "nvim-neotest/nvim-nio", lazy = true }, - { - "mxsdev/nvim-dap-vscode-js", - dependencies = { - "microsoft/vscode-js-debug", - version = "1.x", - build = "npm i && npm run compile vsDebugServerBundle && mv dist out", - }, - config = function() - require("dap-vscode-js").setup({ - -- node_path = "node", -- Path of node executable. Defaults to $NODE_PATH, and then "node" - debugger_path = vim.fn.stdpath("data") .. "/lazy/vscode-js-debug", - -- debugger_cmd = { "extension" }, -- Command to use to launch the debug server. Takes precedence over `node_path` and `debugger_path`. - adapters = { - "chrome", - "pwa-node", - "pwa-chrome", - "pwa-msedge", - "node-terminal", - "pwa-extensionHost", - "node", - "chrome", - }, -- which adapters to register in nvim-dap - -- log_file_path = "(stdpath cache)/dap_vscode_js.log" -- Path for file logging - -- log_file_level = false -- Logging level for output to file. Set to false to disable file logging. - -- log_console_level = vim.log.levels.ERROR -- Logging level for output to console. Set to false to disable console output. - }) - end, - }, - }, - config = function() - require("pcode.user.dapui") - local js_based_languages = { "typescript", "javascript", "typescriptreact" } + }, + { + "rcarriga/nvim-dap-ui", + lazy = true, + event = "BufRead", + dependencies = { + { "mfussenegger/nvim-dap", lazy = true }, + { "nvim-neotest/nvim-nio", lazy = true }, + { + "mxsdev/nvim-dap-vscode-js", + dependencies = { + "microsoft/vscode-js-debug", + version = "1.x", + build = "npm i && npm run compile vsDebugServerBundle && mv dist out", + }, + config = function() + require("dap-vscode-js").setup({ + -- node_path = "node", -- Path of node executable. Defaults to $NODE_PATH, and then "node" + debugger_path = vim.fn.stdpath("data") .. "/lazy/vscode-js-debug", + -- debugger_cmd = { "extension" }, -- Command to use to launch the debug server. Takes precedence over `node_path` and `debugger_path`. + adapters = { + "chrome", + "pwa-node", + "pwa-chrome", + "pwa-msedge", + "node-terminal", + "pwa-extensionHost", + "node", + "chrome", + }, -- which adapters to register in nvim-dap + -- log_file_path = "(stdpath cache)/dap_vscode_js.log" -- Path for file logging + -- log_file_level = false -- Logging level for output to file. Set to false to disable file logging. + -- log_console_level = vim.log.levels.ERROR -- Logging level for output to console. Set to false to disable console output. + }) + end, + }, + }, + config = function() + require("pcode.user.dapui") + local js_based_languages = { "typescript", "javascript", "typescriptreact" } - for _, language in ipairs(js_based_languages) do - require("dap").configurations[language] = { - { - type = "pwa-node", - request = "launch", - name = "Launch file", - program = "${file}", - cwd = "${workspaceFolder}", - }, - { - type = "pwa-node", - request = "attach", - name = "Attach", - processId = require("dap.utils").pick_process, - cwd = "${workspaceFolder}", - }, - { - type = "pwa-chrome", - request = "launch", - name = 'Start Chrome with "localhost"', - url = "http://localhost:3000", - webRoot = "${workspaceFolder}", - userDataDir = "${workspaceFolder}/.vscode/vscode-chrome-debug-userdatadir", - }, - } - end - end, - keys = { - { "d", "", desc = "  Debug" }, - { "dt", "lua require'dap'.toggle_breakpoint()", desc = "Toggle Breakpoint" }, - { "db", "lua require'dap'.step_back()", desc = "Step Back" }, - { "dc", "lua require'dap'.continue()", desc = "Continue" }, - { "dC", "lua require'dap'.run_to_cursor()", desc = "Run To Cursor" }, - { "dd", "lua require'dap'.disconnect()", desc = "Disconnect" }, - { "dg", "lua require'dap'.session()", desc = "Get Session" }, - { "di", "lua require'dap'.step_into()", desc = "Step Into" }, - { "do", "lua require'dap'.step_over()", desc = "Step Over" }, - { "du", "lua require'dap'.step_out()", desc = "Step Out" }, - { "dp", "lua require'dap'.pause()", desc = "Pause" }, - { "dr", "lua require'dap'.repl.toggle()", desc = "Toggle Repl" }, - { "ds", "lua require'dap'.continue()", desc = "Start" }, - { "dq", "lua require'dap'.close()", desc = "Quit" }, - { "dU", "lua require'dapui'.toggle({reset = true})", desc = "Toggle UI" }, - }, - }, + for _, language in ipairs(js_based_languages) do + require("dap").configurations[language] = { + { + type = "pwa-node", + request = "launch", + name = "Launch file", + program = "${file}", + cwd = "${workspaceFolder}", + }, + { + type = "pwa-node", + request = "attach", + name = "Attach", + processId = require("dap.utils").pick_process, + cwd = "${workspaceFolder}", + }, + { + type = "pwa-chrome", + request = "launch", + name = 'Start Chrome with "localhost"', + url = "http://localhost:3000", + webRoot = "${workspaceFolder}", + userDataDir = "${workspaceFolder}/.vscode/vscode-chrome-debug-userdatadir", + }, + } + end + end, + keys = { + { "d", "", desc = "  Debug" }, + { "dt", "lua require'dap'.toggle_breakpoint()", desc = "Toggle Breakpoint" }, + { "db", "lua require'dap'.step_back()", desc = "Step Back" }, + { "dc", "lua require'dap'.continue()", desc = "Continue" }, + { "dC", "lua require'dap'.run_to_cursor()", desc = "Run To Cursor" }, + { "dd", "lua require'dap'.disconnect()", desc = "Disconnect" }, + { "dg", "lua require'dap'.session()", desc = "Get Session" }, + { "di", "lua require'dap'.step_into()", desc = "Step Into" }, + { "do", "lua require'dap'.step_over()", desc = "Step Over" }, + { "du", "lua require'dap'.step_out()", desc = "Step Out" }, + { "dp", "lua require'dap'.pause()", desc = "Pause" }, + { "dr", "lua require'dap'.repl.toggle()", desc = "Toggle Repl" }, + { "ds", "lua require'dap'.continue()", desc = "Start" }, + { "dq", "lua require'dap'.close()", desc = "Quit" }, + { "dU", "lua require'dapui'.toggle({reset = true})", desc = "Toggle UI" }, + }, + }, } return M diff --git a/lua/pcode/user/default.lua b/lua/pcode/user/default.lua index 9bac5ae..d151a30 100644 --- a/lua/pcode/user/default.lua +++ b/lua/pcode/user/default.lua @@ -9,7 +9,7 @@ pcode.lang = { java2 = false, java3 = false, java4 = false, - javascript = false, + javascript = true, kotlin = false, markdown = false, php = false, diff --git a/lua/pcode/user/npmrun.lua b/lua/pcode/user/npmrun.lua new file mode 100644 index 0000000..e510cf1 --- /dev/null +++ b/lua/pcode/user/npmrun.lua @@ -0,0 +1,171 @@ +local function open_new_buffer(name) + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_set_current_buf(buf) + vim.api.nvim_buf_set_option(buf, "modifiable", true) + if name then + vim.api.nvim_buf_set_name(buf, name) + end + return buf +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 buffer_name = "npmrun.txt" + local output_buf = open_new_buffer(buffer_name) + vim.api.nvim_buf_set_lines(output_buf, 0, -1, false, {}) + + local function append_to_buffer(lines) + if not lines then + return + end + if not vim.api.nvim_buf_is_valid(output_buf) then + return + end + local filtered = {} + for _, l in ipairs(lines) do + if l ~= "" then + table.insert(filtered, l) + end + end + if #filtered > 0 then + local line_count = vim.api.nvim_buf_line_count(output_buf) + vim.api.nvim_buf_set_lines(output_buf, line_count, line_count, false, filtered) + end + end + + local function close_output_buffer() + if output_buf and vim.api.nvim_buf_is_valid(output_buf) then + vim.api.nvim_buf_delete(output_buf, { force = true }) + 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(_, code) + append_to_buffer({ string.format(main_cmd .. " exited with code %d", code) }) + close_output_buffer() + job_cache[dir] = nil + end, + }) + + job_cache[dir] = { job_id = job_id, buf = output_buf } + 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) + if job_entry.buf and vim.api.nvim_buf_is_valid(job_entry.buf) then + vim.api.nvim_buf_delete(job_entry.buf, { force = true }) + end + end + job_cache[cached_dir] = nil + log(main_cmd .. " stopped", "INFO") + end + end +end + +return M