Created
April 11, 2026 14:03
-
-
Save brainlet-ali/1a5d10d8118fc4090f281ed5df7f9596 to your computer and use it in GitHub Desktop.
PhpStorm to LazyVim — full config for every replaced feature (dap, test runner, REST client, database, git, Laravel navigation, LSP)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| return { | |
| { | |
| "Weissle/persistent-breakpoints.nvim", | |
| lazy = true, -- Only load when DAP is used | |
| opts = { | |
| load_breakpoints_event = nil, -- Don't auto-load on BufReadPost | |
| }, | |
| }, | |
| { | |
| "mfussenegger/nvim-dap", | |
| lazy = true, | |
| dependencies = { | |
| { "rcarriga/nvim-dap-ui" }, | |
| { "theHamsta/nvim-dap-virtual-text" }, | |
| { "nvim-telescope/telescope-dap.nvim" }, | |
| { "folke/which-key.nvim" }, | |
| { "jay-babu/mason-nvim-dap.nvim" }, | |
| }, | |
| keys = { | |
| { "<leader>dB", function() require("persistent-breakpoints.api").set_conditional_breakpoint() end, desc = "Breakpoint Condition" }, | |
| { "<leader>db", function() require("persistent-breakpoints.api").toggle_breakpoint() end, desc = "Toggle Breakpoint" }, | |
| { "<leader>dc", function() require("dap").continue() end, desc = "Continue" }, | |
| { "<leader>dd", function() require("dap").continue() end, desc = "Continue (F5)" }, | |
| { "<leader>dC", function() require("dap").run_to_cursor() end, desc = "Run to Cursor" }, | |
| { "<leader>dg", function() require("dap").goto_() end, desc = "Go to line (no execute)" }, | |
| { "<leader>di", function() require("dap").step_into() end, desc = "Step Into" }, | |
| { "<leader>dj", function() require("dap").down() end, desc = "Down" }, | |
| { "<leader>dk", function() require("dap").up() end, desc = "Up" }, | |
| { "<leader>dl", function() require("dap").run_last() end, desc = "Run Last" }, | |
| { "<leader>do", function() require("dap").step_out() end, desc = "Step Out" }, | |
| { "<leader>dO", function() require("dap").step_over() end, desc = "Step Over" }, | |
| { "<leader>ds", function() require("dap").step_over() end, desc = "Step Over" }, | |
| { "<leader>dp", function() require("dap").pause() end, desc = "Pause" }, | |
| { "<leader>de", function() | |
| local widgets = require('dap.ui.widgets') | |
| local sidebar = widgets.sidebar(widgets.frames) | |
| sidebar.open() | |
| end, desc = "Show Call Stack" }, | |
| { "<leader>dE", function() | |
| local expression = vim.fn.input("Expression: ") | |
| require('dap.ui.widgets').hover(expression) | |
| end, desc = "Evaluate Expression" }, | |
| { "<leader>dr", function() require("dap").repl.toggle() end, desc = "Toggle REPL" }, | |
| { "<leader>dt", function() require("dap").terminate() end, desc = "Terminate" }, | |
| { "<leader>dw", function() require('dap.ui.widgets').hover() end, desc = "Hover at Point" }, | |
| { "<leader>dW", function() | |
| local widgets = require('dap.ui.widgets') | |
| local sidebar = widgets.sidebar(widgets.scopes) | |
| sidebar.open() | |
| end, desc = "Show Variables/Watches" }, | |
| { "<leader>du", function() | |
| require("dapui").close({ layout = 2 }) | |
| require("dapui").close({ layout = 3 }) | |
| require("dapui").toggle({ layout = 1 }) | |
| end, desc = "Toggle Minimal (Scopes/Breakpoints)" }, | |
| { "<leader>d1", function() | |
| require("dapui").close({ layout = 1 }) | |
| require("dapui").toggle({ layout = 2 }) | |
| require("dapui").toggle({ layout = 3 }) | |
| end, desc = "Toggle Full DAP UI" }, | |
| }, | |
| config = function() | |
| local dap = require("dap") | |
| local home = os.getenv('HOME') | |
| -- Default PHP adapter configuration | |
| dap.adapters.php = { | |
| type = 'executable', | |
| command = 'node', | |
| args = { home .. '/.local/share/nvim/mason/packages/php-debug-adapter/extension/out/phpDebug.js' } | |
| } | |
| -- Load project-specific DAP configuration | |
| local function load_project_dap_config() | |
| local project_config_path = vim.fn.getcwd() .. "/.nvim/dap.lua" | |
| if vim.fn.filereadable(project_config_path) == 1 then | |
| -- Load and execute the project's DAP configuration | |
| local ok, project_config = pcall(dofile, project_config_path) | |
| if ok and type(project_config) == "function" then | |
| -- Call the function with dap as argument | |
| project_config(dap) | |
| elseif ok and type(project_config) == "table" then | |
| -- Merge configurations | |
| for lang, configs in pairs(project_config) do | |
| dap.configurations[lang] = configs | |
| end | |
| end | |
| else | |
| -- Default configuration if no project-specific config exists | |
| dap.configurations.php = { | |
| { | |
| type = 'php', | |
| request = 'launch', | |
| name = 'Listen for Xdebug', | |
| port = 9003, | |
| pathMappings = { | |
| ["/var/www/html"] = vim.fn.getcwd() | |
| }, | |
| hostname = "0.0.0.0", | |
| log = true, | |
| xdebugSettings = { | |
| max_children = 100, | |
| max_data = 1024, | |
| max_depth = 5, | |
| }, | |
| } | |
| } | |
| end | |
| end | |
| -- Load configurations on startup and when changing directories | |
| load_project_dap_config() | |
| -- Auto-reload when changing directories | |
| vim.api.nvim_create_autocmd("DirChanged", { | |
| callback = function() | |
| load_project_dap_config() | |
| end, | |
| }) | |
| -- UI customization with better icons | |
| vim.fn.sign_define('DapBreakpoint', { text = '●', texthl = 'DiagnosticError', linehl = '', numhl = '' }) | |
| vim.fn.sign_define('DapBreakpointCondition', { text = '◆', texthl = 'DiagnosticError', linehl = '', numhl = '' }) | |
| vim.fn.sign_define('DapBreakpointRejected', { text = '○', texthl = 'DiagnosticHint', linehl = '', numhl = '' }) | |
| vim.fn.sign_define('DapLogPoint', { text = '◉', texthl = 'DiagnosticInfo', linehl = '', numhl = '' }) | |
| vim.fn.sign_define('DapStopped', { text = '▶', texthl = 'DiagnosticWarn', linehl = 'DapStoppedLine', numhl = 'DapStoppedLine' }) | |
| -- Create a highlight group for the stopped line (PHPStorm-like) | |
| vim.api.nvim_set_hl(0, 'DapStoppedLine', { bg = '#CCDDFF', default = true }) | |
| -- Focus terminal when breakpoint is hit (macOS) | |
| dap.listeners.after.event_stopped["focus_nvim"] = function() | |
| vim.fn.system([[osascript -e 'tell application "Ghostty" to activate']]) | |
| end | |
| end, | |
| }, | |
| { | |
| "rcarriga/nvim-dap-ui", | |
| lazy = true, | |
| dependencies = { | |
| "nvim-neotest/nvim-nio", | |
| }, | |
| opts = { | |
| icons = { expanded = "▾", collapsed = "▸", current_frame = "▸" }, | |
| mappings = { | |
| expand = { "<CR>", "<2-LeftMouse>" }, | |
| open = "o", | |
| remove = "d", | |
| edit = "e", | |
| repl = "r", | |
| toggle = "t", | |
| }, | |
| layouts = { | |
| { | |
| -- Layout 1: Minimal (scopes + breakpoints only) | |
| elements = { | |
| { id = "scopes", size = 0.6 }, | |
| { id = "breakpoints", size = 0.4 }, | |
| }, | |
| size = 0.30, | |
| position = "right", | |
| }, | |
| { | |
| -- Layout 2: Full sidebar | |
| elements = { | |
| { id = "scopes", size = 0.33 }, | |
| { id = "breakpoints", size = 0.17 }, | |
| { id = "stacks", size = 0.25 }, | |
| { id = "watches", size = 0.25 }, | |
| }, | |
| size = 0.33, | |
| position = "right", | |
| }, | |
| { | |
| -- Layout 3: Bottom console/repl | |
| elements = { | |
| { id = "repl", size = 0.45 }, | |
| { id = "console", size = 0.55 }, | |
| }, | |
| size = 0.27, | |
| position = "bottom", | |
| }, | |
| }, | |
| floating = { | |
| max_height = 0.9, | |
| max_width = 0.5, | |
| border = "single", | |
| mappings = { | |
| close = { "q", "<Esc>" }, | |
| }, | |
| }, | |
| }, | |
| config = function(_, opts) | |
| local dap = require("dap") | |
| local dapui = require("dapui") | |
| dapui.setup(opts) | |
| dap.listeners.after.event_initialized["dapui_config"] = function() | |
| dapui.open({ layout = 1 }) | |
| end | |
| dap.listeners.before.event_terminated["dapui_config"] = function() | |
| dapui.close() | |
| end | |
| dap.listeners.before.event_exited["dapui_config"] = function() | |
| dapui.close() | |
| end | |
| end, | |
| }, | |
| { | |
| "theHamsta/nvim-dap-virtual-text", | |
| lazy = true, | |
| opts = {}, | |
| }, | |
| { | |
| "folke/which-key.nvim", | |
| optional = true, | |
| opts = { | |
| spec = { | |
| ["<leader>d"] = { name = "+debug" }, | |
| }, | |
| }, | |
| }, | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| return { | |
| "vim-test/vim-test", | |
| dependencies = { | |
| "preservim/vimux", | |
| "christoomey/vim-tmux-navigator", | |
| }, | |
| keys = { | |
| { "<leader>Tt", "<cmd>TestNearest<cr>", desc = "Run nearest test" }, | |
| { "<leader>Tf", "<cmd>TestFile<cr>", desc = "Run test file" }, | |
| { "<leader>Ts", "<cmd>TestSuite<cr>", desc = "Run test suite" }, | |
| { "<leader>Tl", "<cmd>TestLast<cr>", desc = "Run last test" }, | |
| { "<leader>Tv", "<cmd>TestVisit<cr>", desc = "Visit test file" }, | |
| }, | |
| config = function() | |
| -- Load project-specific test config from .nvim/test.lua | |
| local function load_project_test_config() | |
| local config_path = vim.fn.getcwd() .. "/.nvim/test.lua" | |
| if vim.fn.filereadable(config_path) == 1 then | |
| local ok, config = pcall(dofile, config_path) | |
| if ok and type(config) == "function" then | |
| config(vim.g) | |
| elseif ok and type(config) == "table" then | |
| for key, value in pairs(config) do | |
| vim.g[key] = value | |
| end | |
| end | |
| end | |
| -- No fallback - project must provide .nvim/test.lua | |
| end | |
| load_project_test_config() | |
| -- Reload on directory change | |
| vim.api.nvim_create_autocmd("DirChanged", { | |
| callback = load_project_test_config, | |
| }) | |
| -- Vimux defaults | |
| vim.g.VimuxHeight = "30%" | |
| vim.g.VimuxOrientation = "h" | |
| vim.g.VimuxCloseOnExit = 0 | |
| vim.g.VimuxUseNearest = 0 | |
| vim.g.VimuxRunnerName = "nvim-test" | |
| end, | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| return { | |
| "mistweaverco/kulala.nvim", | |
| dependencies = { "nvim-lua/plenary.nvim" }, | |
| ft = "http", | |
| keys = { | |
| { | |
| "<leader>rs", | |
| function() | |
| require("kulala").run() | |
| end, | |
| desc = "Send request", | |
| mode = { "n", "v" }, | |
| }, | |
| { | |
| "<leader>rr", | |
| function() | |
| require("kulala").replay() | |
| end, | |
| desc = "Replay last request", | |
| }, | |
| { | |
| "<leader>rc", | |
| function() | |
| require("kulala").copy() | |
| end, | |
| desc = "Copy as cURL", | |
| }, | |
| { | |
| "<leader>re", | |
| function() | |
| require("kulala").set_selected_env() | |
| -- Switch to normal mode after picker opens | |
| vim.schedule(function() | |
| vim.cmd("stopinsert") | |
| end) | |
| end, | |
| desc = "Switch environment", | |
| }, | |
| { | |
| "<leader>rb", | |
| function() | |
| require("kulala").scratchpad() | |
| end, | |
| desc = "Open scratchpad", | |
| }, | |
| }, | |
| config = function() | |
| -- Default configuration | |
| local default_opts = { | |
| -- Default environment and view settings | |
| default_env = "local", | |
| default_view = "headers_body", | |
| environment_scope = "g", -- Use global scope for environment variables | |
| vscode_rest_client_environmentvars = true, -- Enable VSCode REST client environment variables | |
| -- Content type settings | |
| contenttypes = { | |
| ["application/json"] = { | |
| ft = "json", | |
| formatter = { "jq", "." }, | |
| pathresolver = require("kulala.parser.jsonpath").parse, | |
| }, | |
| }, | |
| -- Visual feedback settings | |
| show_icons = "on_request", | |
| icons = { | |
| inlay = { | |
| loading = "⏳", | |
| done = "✅", | |
| error = "❌", | |
| }, | |
| lualine = "🐼", | |
| }, | |
| -- Request settings | |
| urlencode = "skipencoded", | |
| show_variable_info_text = "float", | |
| -- Scratchpad template | |
| scratchpad_default_contents = { | |
| "# @name example-request", | |
| "GET {{host}}/{{api}}/users", | |
| "Authorization: Bearer {{token}}", | |
| "Content-Type: application/json", | |
| "", | |
| "{", | |
| ' "query": "example"', | |
| "}", | |
| }, | |
| -- Enable winbar for better navigation | |
| winbar = true, | |
| default_winbar_panes = { "body", "headers", "headers_body", "verbose" }, | |
| } | |
| -- Load project-specific kulala configuration | |
| local function load_project_kulala_config() | |
| local project_config_path = vim.fn.getcwd() .. "/.nvim/kulala.lua" | |
| if vim.fn.filereadable(project_config_path) == 1 then | |
| -- Load and execute the project's kulala configuration | |
| local ok, project_config = pcall(dofile, project_config_path) | |
| if ok and type(project_config) == "function" then | |
| -- Call the function with default_opts as argument for configuration | |
| return project_config(default_opts) | |
| elseif ok and type(project_config) == "table" then | |
| -- Merge project config with default config | |
| return vim.tbl_deep_extend("force", default_opts, project_config) | |
| end | |
| end | |
| -- Return default configuration if no project-specific config exists | |
| return default_opts | |
| end | |
| -- Get final configuration and setup kulala | |
| local final_opts = load_project_kulala_config() | |
| require("kulala").setup(final_opts) | |
| -- Auto-reload when changing directories | |
| vim.api.nvim_create_autocmd("DirChanged", { | |
| callback = function() | |
| local new_opts = load_project_kulala_config() | |
| require("kulala").setup(new_opts) | |
| end, | |
| }) | |
| end, | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| return { | |
| { | |
| "tpope/vim-dadbod", | |
| cmd = { "DBUI", "DBUIToggle", "DBUIFindBuffer", "DBUIRenameBuffer", "DBUILastQueryInfo" }, | |
| ft = { "sql", "mysql", "plsql" }, | |
| dependencies = { | |
| "kristijanhusak/vim-dadbod-ui", | |
| "kristijanhusak/vim-dadbod-completion", | |
| "pbogut/vim-dadbod-ssh", | |
| }, | |
| config = function() | |
| -- Database connections | |
| vim.g.db_ui_save_location = vim.fn.stdpath("config") .. require("plenary.path").path.sep .. "db_ui" | |
| -- Load project-specific config from .nvim/dadbod.lua | |
| local function load_project_config() | |
| local config_path = vim.fn.getcwd() .. "/.nvim/dadbod.lua" | |
| if vim.fn.filereadable(config_path) == 1 then | |
| local ok, config = pcall(dofile, config_path) | |
| if ok and type(config) == "function" then | |
| config(vim.g) | |
| elseif ok and type(config) == "table" then | |
| if config.dbs then vim.g.dbs = config.dbs end | |
| for k, v in pairs(config) do | |
| if k ~= "dbs" then vim.g[k] = v end | |
| end | |
| end | |
| end | |
| end | |
| load_project_config() | |
| vim.api.nvim_create_autocmd("DirChanged", { | |
| callback = load_project_config, | |
| }) | |
| -- DBUI settings (global defaults) | |
| vim.g.db_ui_use_nerd_fonts = 1 | |
| vim.g.db_ui_show_database_icon = 1 | |
| vim.g.db_ui_force_echo_messages = 1 | |
| vim.g.db_ui_win_position = "right" | |
| vim.g.db_ui_winwidth = 40 | |
| -- Auto-completion for SQL | |
| vim.api.nvim_create_autocmd("FileType", { | |
| pattern = { "sql", "mysql", "plsql" }, | |
| callback = function() | |
| -- Disable built-in SQL completion (causes sqlcomplete#DrillIntoTable error) | |
| vim.bo.omnifunc = "" | |
| local ok, cmp = pcall(require, "cmp") | |
| if ok then | |
| cmp.setup.buffer({ | |
| sources = { | |
| { name = "luasnip" }, | |
| { name = "vim-dadbod-completion" }, | |
| { name = "buffer" }, | |
| }, | |
| }) | |
| end | |
| end, | |
| }) | |
| -- Connection-specific background colors for safety | |
| -- Define highlight groups for different environments (light theme) | |
| vim.api.nvim_set_hl(0, "DbLocalBg", { bg = "NONE" }) -- default | |
| vim.api.nvim_set_hl(0, "DbStagingBg", { bg = "#fff3e0" }) -- orange/peach for staging | |
| vim.api.nvim_set_hl(0, "DbProdBg", { bg = "#ffebee" }) -- light red for prod (danger) | |
| -- Apply background color based on connection name | |
| local function get_db_name() | |
| -- Try multiple sources for connection name | |
| local db_name = vim.b.dbui_db_key_name | |
| or vim.b.db_name | |
| or vim.fn.expand("%:p"):match("db_ui/([^/]+)/") | |
| or vim.fn.expand("%"):match("^([^-]+)%-") -- parse from buffer name like "STAGING4-query" | |
| or "" | |
| return string.upper(db_name) | |
| end | |
| local function apply_db_color() | |
| local ft = vim.bo.filetype | |
| if ft ~= "sql" and ft ~= "mysql" and ft ~= "plsql" and ft ~= "dbout" then | |
| return | |
| end | |
| local db_name = get_db_name() | |
| -- Check STAGING first (more specific) | |
| if db_name:match("STAGING") then | |
| vim.wo.winhighlight = "Normal:DbStagingBg" | |
| elseif db_name:match("PROD") then | |
| vim.wo.winhighlight = "Normal:DbProdBg" | |
| else | |
| vim.wo.winhighlight = "" | |
| end | |
| -- Store for dbout to inherit | |
| if ft ~= "dbout" and db_name ~= "" then | |
| vim.g.last_db_name = db_name | |
| end | |
| end | |
| vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter", "FileType" }, { | |
| pattern = "*", | |
| callback = function() | |
| local ft = vim.bo.filetype | |
| local bufname = vim.fn.expand("%") | |
| -- Only apply to SQL-related buffers | |
| local is_sql = ft == "sql" or ft == "mysql" or ft == "plsql" | |
| local is_dbout = ft == "dbout" or bufname:match("%.dbout$") | |
| if not is_sql and not is_dbout then | |
| -- Reset highlight for non-SQL buffers | |
| vim.wo.winhighlight = "" | |
| return | |
| end | |
| -- For dbout (result) buffers, use last known connection | |
| if is_dbout then | |
| local db_name = vim.g.last_db_name or "" | |
| if db_name:match("STAGING") then | |
| vim.wo.winhighlight = "Normal:DbStagingBg" | |
| elseif db_name:match("PROD") then | |
| vim.wo.winhighlight = "Normal:DbProdBg" | |
| else | |
| vim.wo.winhighlight = "" | |
| end | |
| return | |
| end | |
| apply_db_color() | |
| end, | |
| }) | |
| end, | |
| keys = { | |
| { "<leader>;", "<cmd>DBUI<cr>", desc = "Database UI" }, | |
| { "<leader>;u", "<cmd>DBUI<cr>", desc = "Open Database UI" }, | |
| { "<leader>;t", "<cmd>DBUIToggle<cr>", desc = "Toggle Database UI" }, | |
| { "<leader>;f", "<cmd>DBUIFindBuffer<cr>", desc = "Find Database Buffer" }, | |
| { "<leader>;r", "<cmd>DBUIRenameBuffer<cr>", desc = "Rename Database Buffer" }, | |
| { "<leader>;l", "<cmd>DBUILastQueryInfo<cr>", desc = "Last Query Info" }, | |
| { "<leader>;a", "<cmd>DBUIAddConnection<cr>", desc = "Add Connection" }, | |
| { "<leader>;x", "<Plug>(DBUI_ExecuteQuery)", desc = "Execute Query", ft = { "sql", "mysql", "plsql" } }, | |
| }, | |
| }, | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| return { | |
| { | |
| "kdheepak/lazygit.nvim", | |
| keys = { | |
| { "<leader>gg", "<cmd>LazyGit<cr>", desc = "LazyGit" }, | |
| }, | |
| }, | |
| { | |
| "lewis6991/gitsigns.nvim", | |
| keys = { | |
| -- Find PR that introduced current line | |
| { | |
| "<leader>gP", | |
| function() | |
| local file = vim.fn.expand("%:p") | |
| local line = vim.fn.line(".") | |
| local blame = vim.fn.system(string.format("git blame -L %d,%d -l %s 2>/dev/null", line, line, vim.fn.shellescape(file))) | |
| if blame == "" then | |
| vim.notify("Could not get blame info", vim.log.levels.WARN) | |
| return | |
| end | |
| local pr_num = blame:match("%(#(%d+)%)") | |
| if pr_num then | |
| vim.notify("PR #" .. pr_num, vim.log.levels.INFO) | |
| vim.fn.system("gh pr view " .. pr_num .. " --web") | |
| return | |
| end | |
| local sha = blame:match("(%x%x%x%x%x%x%x%x%x%x+)") | |
| if sha and not sha:match("^0+$") then | |
| local pr = vim.fn.system(string.format("gh api /repos/{owner}/{repo}/commits/%s/pulls --jq '.[0].number' 2>/dev/null", sha)) | |
| pr_num = pr:match("(%d+)") | |
| if pr_num then | |
| vim.notify("PR #" .. pr_num, vim.log.levels.INFO) | |
| vim.fn.system("gh pr view " .. pr_num .. " --web") | |
| else | |
| vim.notify("No PR found for commit: " .. sha:sub(1, 8), vim.log.levels.WARN) | |
| end | |
| else | |
| vim.notify("Line not committed yet", vim.log.levels.WARN) | |
| end | |
| end, | |
| desc = "Find PR for line", | |
| }, | |
| }, | |
| }, | |
| { | |
| "nvim-telescope/telescope.nvim", | |
| keys = { | |
| -- Modified files since branch diverged | |
| { "<leader>gm", function() | |
| local actions = require("telescope.actions") | |
| local action_state = require("telescope.actions.state") | |
| local pickers = require("telescope.pickers") | |
| local finders = require("telescope.finders") | |
| local conf = require("telescope.config").values | |
| local previewers = require("telescope.previewers") | |
| local base_branch = vim.fn.system("git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@'"):gsub("\n", "") | |
| if base_branch == "" then | |
| base_branch = vim.fn.system("git rev-parse --verify main 2>/dev/null && echo main || echo master"):gsub("\n", "") | |
| end | |
| local cmd = "git diff --name-only " .. base_branch .. "...HEAD" | |
| local files = vim.fn.systemlist(cmd) | |
| if #files == 0 then | |
| vim.notify("No modified files since " .. base_branch, vim.log.levels.INFO) | |
| return | |
| end | |
| pickers.new({}, { | |
| prompt_title = "Modified Files (vs " .. base_branch .. ")", | |
| finder = finders.new_table({ | |
| results = files, | |
| entry_maker = function(entry) | |
| return { | |
| value = entry, | |
| display = entry, | |
| ordinal = entry, | |
| path = entry, | |
| } | |
| end, | |
| }), | |
| sorter = conf.file_sorter({}), | |
| previewer = previewers.new_termopen_previewer({ | |
| get_command = function(entry) | |
| return { "git", "diff", base_branch .. "...HEAD", "--", entry.value } | |
| end, | |
| }), | |
| attach_mappings = function(prompt_bufnr, map) | |
| actions.select_default:replace(function() | |
| actions.close(prompt_bufnr) | |
| local selection = action_state.get_selected_entry() | |
| vim.cmd("edit " .. selection.value) | |
| end) | |
| return true | |
| end, | |
| initial_mode = "normal", | |
| layout_strategy = "horizontal", | |
| layout_config = { | |
| horizontal = { | |
| preview_width = 0.6, | |
| width = 0.95, | |
| height = 0.95, | |
| }, | |
| }, | |
| }):find() | |
| end, desc = "Modified files vs main" }, | |
| -- Commit search | |
| { "<leader>g/", function() | |
| require("telescope.builtin").git_commits({ | |
| initial_mode = "normal", | |
| git_command = { "git", "log", "--pretty=format:%h %ad %an %s", "--date=short", "--abbrev-commit", "--all", "--", "." }, | |
| }) | |
| end, desc = "Search git commits" }, | |
| -- Branch comparison | |
| { "<leader>gc", function() | |
| local actions = require("telescope.actions") | |
| local action_state = require("telescope.actions.state") | |
| local pickers = require("telescope.pickers") | |
| local finders = require("telescope.finders") | |
| local conf = require("telescope.config").values | |
| local previewers = require("telescope.previewers") | |
| local current_branch = vim.fn.system("git branch --show-current"):gsub("\n", "") | |
| local branches = vim.fn.systemlist("git branch -a --format='%(refname:short)'") | |
| pickers.new({}, { | |
| prompt_title = "Compare branch with " .. current_branch, | |
| finder = finders.new_table({ | |
| results = branches, | |
| entry_maker = function(entry) | |
| return { value = entry, display = entry, ordinal = entry } | |
| end, | |
| }), | |
| sorter = conf.generic_sorter({}), | |
| previewer = previewers.new_termopen_previewer({ | |
| get_command = function(entry) | |
| return { "git", "log", "--oneline", "--graph", current_branch .. ".." .. entry.value } | |
| end, | |
| }), | |
| attach_mappings = function(prompt_bufnr, map) | |
| actions.select_default:replace(function() | |
| local selection = action_state.get_selected_entry() | |
| actions.close(prompt_bufnr) | |
| vim.cmd("tabnew | terminal git diff " .. current_branch .. ".." .. selection.value) | |
| end) | |
| map("i", "<C-f>", function() | |
| local selection = action_state.get_selected_entry() | |
| actions.close(prompt_bufnr) | |
| local files = vim.fn.systemlist("git diff --name-only " .. current_branch .. ".." .. selection.value) | |
| pickers.new({}, { | |
| prompt_title = "Files changed: " .. current_branch .. ".." .. selection.value, | |
| finder = finders.new_table({ results = files }), | |
| sorter = conf.file_sorter({}), | |
| previewer = previewers.new_termopen_previewer({ | |
| get_command = function(entry) | |
| return { "git", "diff", current_branch .. ".." .. selection.value, "--", entry[1] } | |
| end, | |
| }), | |
| }):find() | |
| end) | |
| return true | |
| end, | |
| initial_mode = "insert", | |
| }):find() | |
| end, desc = "Compare branches" }, | |
| -- Unique commits between branches | |
| { "<leader>gu", function() | |
| local actions = require("telescope.actions") | |
| local action_state = require("telescope.actions.state") | |
| local pickers = require("telescope.pickers") | |
| local finders = require("telescope.finders") | |
| local conf = require("telescope.config").values | |
| local previewers = require("telescope.previewers") | |
| local current_branch = vim.fn.system("git branch --show-current"):gsub("\n", "") | |
| local branches = vim.fn.systemlist("git branch -a --format='%(refname:short)'") | |
| pickers.new({}, { | |
| prompt_title = "Compare unique commits with " .. current_branch, | |
| finder = finders.new_table({ results = branches }), | |
| sorter = conf.generic_sorter({}), | |
| initial_mode = "normal", | |
| attach_mappings = function(prompt_bufnr, map) | |
| actions.select_default:replace(function() | |
| local selection = action_state.get_selected_entry() | |
| actions.close(prompt_bufnr) | |
| local target_branch = selection[1] | |
| local outgoing = vim.fn.systemlist("git log " .. target_branch .. ".." .. current_branch .. " --oneline --no-merges") | |
| local incoming = vim.fn.systemlist("git log " .. current_branch .. ".." .. target_branch .. " --oneline --no-merges") | |
| local results = {} | |
| for _, c in ipairs(outgoing) do | |
| local hash = c:match("^(%S+)") | |
| if hash then table.insert(results, { commit = c, direction = "↑ out", hash = hash }) end | |
| end | |
| for _, c in ipairs(incoming) do | |
| local hash = c:match("^(%S+)") | |
| if hash then table.insert(results, { commit = c, direction = "↓ in", hash = hash }) end | |
| end | |
| if #results == 0 then | |
| vim.notify("Branches are identical", vim.log.levels.INFO) | |
| return | |
| end | |
| pickers.new({}, { | |
| prompt_title = current_branch .. " <-> " .. target_branch .. " (unique commits)", | |
| finder = finders.new_table({ | |
| results = results, | |
| entry_maker = function(entry) | |
| return { value = entry.hash, display = entry.direction .. " " .. entry.commit, ordinal = entry.commit } | |
| end, | |
| }), | |
| sorter = conf.generic_sorter({}), | |
| previewer = previewers.new_termopen_previewer({ | |
| get_command = function(entry) return { "git", "show", entry.value } end, | |
| }), | |
| attach_mappings = function(prompt_bufnr2, map2) | |
| actions.select_default:replace(function() | |
| local sel = action_state.get_selected_entry() | |
| actions.close(prompt_bufnr2) | |
| vim.cmd("tabnew | terminal git show " .. sel.value) | |
| end) | |
| return true | |
| end, | |
| initial_mode = "normal", | |
| }):find() | |
| end) | |
| return true | |
| end, | |
| }):find() | |
| end, desc = "Unique commits between branches" }, | |
| -- Reflog | |
| { "<leader>gR", function() | |
| local actions = require("telescope.actions") | |
| local action_state = require("telescope.actions.state") | |
| local pickers = require("telescope.pickers") | |
| local finders = require("telescope.finders") | |
| local conf = require("telescope.config").values | |
| local previewers = require("telescope.previewers") | |
| local reflog = vim.fn.systemlist("git reflog --pretty=format:'%h|%gd|%gs|%ar'") | |
| if #reflog == 0 then | |
| vim.notify("No reflog entries found", vim.log.levels.INFO) | |
| return | |
| end | |
| pickers.new({}, { | |
| prompt_title = "Git Reflog", | |
| finder = finders.new_table({ | |
| results = reflog, | |
| entry_maker = function(entry) | |
| local parts = vim.split(entry, "|") | |
| if #parts >= 4 then | |
| return { value = parts[1], display = string.format("%s %s %s (%s)", parts[1], parts[2], parts[3], parts[4]), ordinal = entry } | |
| end | |
| return { value = entry, display = entry, ordinal = entry } | |
| end, | |
| }), | |
| sorter = conf.generic_sorter({}), | |
| previewer = previewers.new_termopen_previewer({ | |
| get_command = function(entry) return { "git", "show", "--stat", entry.value } end, | |
| }), | |
| attach_mappings = function(prompt_bufnr, map) | |
| actions.select_default:replace(function() | |
| local selection = action_state.get_selected_entry() | |
| actions.close(prompt_bufnr) | |
| vim.cmd("!git checkout " .. selection.value) | |
| end) | |
| map("i", "<C-r>", function() | |
| local selection = action_state.get_selected_entry() | |
| vim.ui.select({ "soft", "mixed", "hard", "cancel" }, { prompt = "Reset to " .. selection.value .. " using?" }, function(choice) | |
| if choice and choice ~= "cancel" then | |
| actions.close(prompt_bufnr) | |
| vim.cmd("!git reset --" .. choice .. " " .. selection.value) | |
| end | |
| end) | |
| end) | |
| return true | |
| end, | |
| initial_mode = "normal", | |
| }):find() | |
| end, desc = "Git reflog" }, | |
| -- Git blame history for line | |
| { "<leader>gB", function() | |
| local pickers = require("telescope.pickers") | |
| local finders = require("telescope.finders") | |
| local conf = require("telescope.config").values | |
| local actions = require("telescope.actions") | |
| local action_state = require("telescope.actions.state") | |
| local previewers = require("telescope.previewers") | |
| local current_file = vim.fn.expand("%:p") | |
| local relfile = vim.fn.expand("%") | |
| if current_file == "" then | |
| vim.notify("No file open", vim.log.levels.WARN) | |
| return | |
| end | |
| local current_line = vim.fn.line(".") | |
| local commits = vim.fn.systemlist( | |
| "git log --pretty=format:'%h|%ad|%an|%s' --date=short -L " .. current_line .. "," .. current_line .. ":" .. vim.fn.shellescape(current_file) | |
| ) | |
| local filtered = {} | |
| for _, line in ipairs(commits) do | |
| if line:match("^%w+|") then table.insert(filtered, line) end | |
| end | |
| if #filtered == 0 then | |
| vim.notify("No commits found for this line", vim.log.levels.INFO) | |
| return | |
| end | |
| pickers.new({}, { | |
| prompt_title = "Line History: " .. relfile .. ":" .. current_line, | |
| finder = finders.new_table({ | |
| results = filtered, | |
| entry_maker = function(entry) | |
| local parts = vim.split(entry, "|") | |
| if #parts >= 4 then | |
| local hash, date, author = parts[1], parts[2], parts[3] | |
| local msg = table.concat({ unpack(parts, 4) }, "|") | |
| return { value = hash, display = string.format("%s %s %s %s", hash, date, author, msg), ordinal = entry } | |
| end | |
| return { value = entry, display = entry, ordinal = entry } | |
| end, | |
| }), | |
| sorter = conf.generic_sorter({}), | |
| previewer = previewers.new_termopen_previewer({ | |
| get_command = function(entry) return { "git", "show", entry.value, "--", current_file } end, | |
| }), | |
| attach_mappings = function(prompt_bufnr, map) | |
| actions.select_default:replace(function() | |
| local selection = action_state.get_selected_entry() | |
| actions.close(prompt_bufnr) | |
| vim.cmd("tabnew | terminal git show " .. selection.value) | |
| end) | |
| return true | |
| end, | |
| initial_mode = "normal", | |
| }):find() | |
| end, desc = "Git blame history for line" }, | |
| }, | |
| }, | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| return { | |
| "adibhanna/laravel.nvim", | |
| dependencies = { | |
| "MunifTanjim/nui.nvim", | |
| "nvim-lua/plenary.nvim", | |
| }, | |
| cond = function() | |
| return vim.fn.filereadable(vim.fn.getcwd() .. "/artisan") == 1 | |
| end, | |
| event = "VeryLazy", | |
| opts = { | |
| notifications = false, | |
| debug = false, | |
| keymaps = false, -- we'll set our own with <leader>l prefix | |
| sail = { | |
| enabled = true, | |
| auto_detect = true, | |
| }, | |
| }, | |
| config = function(_, opts) | |
| require("laravel").setup(opts) | |
| -- Register which-key group for Laravel (only in Laravel projects) | |
| local ok, wk = pcall(require, "which-key") | |
| if ok then | |
| wk.add({ | |
| { "<leader>L", group = "Laravel" }, | |
| { "<leader>Ls", group = "Sail" }, | |
| }) | |
| end | |
| -- Jump to Laravel config/route/view string under cursor | |
| local function goto_laravel_config_string() | |
| local line = vim.api.nvim_get_current_line() | |
| local col = vim.api.nvim_win_get_cursor(0)[2] + 1 | |
| local root = vim.fn.getcwd() | |
| -- Find the string under cursor: match function('string') patterns with position tracking | |
| local str = nil | |
| local func_name = nil | |
| local search_start = 1 | |
| while true do | |
| local fn_start, fn_end, fn, q = line:find("(%w+)%((['\"])", search_start) | |
| if not fn_start then break end | |
| local str_start = fn_end + 1 | |
| local str_end = line:find(q, str_start, true) | |
| if str_end then | |
| if col >= fn_start and col <= str_end then | |
| str = line:sub(str_start, str_end - 1) | |
| func_name = fn | |
| break | |
| end | |
| search_start = str_end + 1 | |
| else | |
| break | |
| end | |
| end | |
| -- If no function pattern found, check context-specific navigation | |
| if not str then | |
| local filepath = vim.fn.expand("%:p") | |
| local escaped_root = root:gsub("([%-%.%+%[%]%(%)%$%^%%%?%*])", "%%%1") | |
| -- Config file reverse lookup: 'default' => ... in config/filesystems.php → grep config('filesystems.default') | |
| local config_name = filepath:match(escaped_root .. "/config/(.+)%.php$") | |
| if config_name then | |
| local key = line:match("['\"]([%w_%-]+)['\"]%s*=>") | |
| if key then | |
| config_name = config_name:gsub("/", ".") | |
| require("telescope.builtin").live_grep({ | |
| default_text = config_name .. "." .. key, | |
| initial_mode = "normal", | |
| }) | |
| return true | |
| end | |
| end | |
| -- Route file: 'Controller@method' pattern | |
| local controller_str, method = line:match("['\"]([%w\\]+)@(%w+)['\"]") | |
| if controller_str then | |
| local word = vim.fn.expand("<cword>") | |
| -- Extract just the class name (last part after \) | |
| local class_name = controller_str:match("([%w]+)$") | |
| -- Build namespace path: Server\ManageController → Server/ManageController | |
| local rel_path = controller_str:gsub("\\", "/") | |
| if word == method or word == method:sub(1, #word) then | |
| -- Cursor on method name → jump to method in controller | |
| local candidates = vim.fn.globpath(root .. "/app", "**/" .. rel_path .. ".php", false, true) | |
| if #candidates > 0 then | |
| vim.cmd("edit " .. candidates[1]) | |
| vim.fn.search("function\\s\\+" .. method, "w") | |
| return true | |
| end | |
| elseif word == class_name or controller_str:find(word, 1, true) then | |
| -- Cursor on controller name → jump to controller | |
| local candidates = vim.fn.globpath(root .. "/app", "**/" .. rel_path .. ".php", false, true) | |
| if #candidates > 0 then | |
| vim.cmd("edit " .. candidates[1]) | |
| return true | |
| end | |
| end | |
| end | |
| -- Controller file: cursor on a function definition → find route referencing it | |
| local is_controller = filepath:match("Controller%.php$") | |
| if is_controller then | |
| local method_name = line:match("function%s+(%w+)") | |
| if method_name then | |
| local class_name = filepath:match("([%w]+)%.php$") | |
| if class_name then | |
| -- Search both old ('Controller@method') and new ([Controller::class, 'method']) syntax | |
| require("telescope.builtin").live_grep({ | |
| default_text = class_name .. ".*" .. method_name, | |
| search_dirs = { | |
| root .. "/routes", | |
| root .. "/app/Http", | |
| }, | |
| initial_mode = "normal", | |
| }) | |
| return true | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| if func_name == "config" then | |
| -- config('spark.trial_days') → config/spark.php, search for 'trial_days' | |
| local parts = vim.split(str, ".", { plain = true }) | |
| local file = root .. "/config/" .. parts[1] .. ".php" | |
| if vim.fn.filereadable(file) == 1 then | |
| vim.cmd("edit " .. file) | |
| if parts[2] then | |
| vim.fn.search("['\"]" .. parts[#parts] .. "['\"]", "w") | |
| end | |
| return true | |
| end | |
| elseif func_name == "view" or func_name == "markdown" then | |
| -- view('emails.welcome') → resources/views/emails/welcome.blade.php | |
| local path = str:gsub("%.", "/") | |
| local file = root .. "/resources/views/" .. path .. ".blade.php" | |
| if vim.fn.filereadable(file) == 1 then | |
| vim.cmd("edit " .. file) | |
| return true | |
| end | |
| elseif func_name == "route" then | |
| -- route('login') → search routes/ for 'login' | |
| vim.cmd("Telescope live_grep cwd=" .. root .. "/routes default_text=" .. str) | |
| return true | |
| elseif func_name == "trans" or func_name == "__" or func_name == "lang" then | |
| -- __('auth.failed') → lang/en/auth.php, search for 'failed' | |
| local parts = vim.split(str, ".", { plain = true }) | |
| local file = root .. "/lang/en/" .. parts[1] .. ".php" | |
| if vim.fn.filereadable(file) == 1 then | |
| vim.cmd("edit " .. file) | |
| if parts[2] then | |
| vim.fn.search("['\"]" .. parts[#parts] .. "['\"]", "w") | |
| end | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| -- Smart goto: Toggle between definition and references, with Laravel fallback | |
| local function smart_goto() | |
| -- Try Laravel config string navigation first (config, view, route, trans) | |
| if goto_laravel_config_string() then return end | |
| local client = vim.lsp.get_clients({ bufnr = 0 })[1] | |
| local encoding = client and client.offset_encoding or "utf-16" | |
| local params = vim.lsp.util.make_position_params(0, encoding) | |
| local current_buf = vim.api.nvim_get_current_buf() | |
| local current_pos = vim.api.nvim_win_get_cursor(0) | |
| vim.lsp.buf_request(current_buf, 'textDocument/definition', params, function(err, result, ctx, config) | |
| if err or not result or vim.tbl_isempty(result) then | |
| vim.lsp.buf.references() | |
| return | |
| end | |
| -- Normalize the result (can be Location or Location[]) | |
| local locations = result[1] and result or { result } | |
| -- Check if we're at any of the definition locations | |
| local at_definition = false | |
| for _, location in ipairs(locations) do | |
| local def_uri = location.uri or location.targetUri | |
| local def_range = location.range or location.targetRange | |
| if def_uri then | |
| local def_bufnr = vim.uri_to_bufnr(def_uri) | |
| if def_bufnr == current_buf and def_range then | |
| local start_line = def_range.start.line + 1 | |
| local end_line = def_range['end'].line + 1 | |
| if current_pos[1] >= start_line and current_pos[1] <= end_line then | |
| at_definition = true | |
| break | |
| end | |
| end | |
| end | |
| end | |
| if at_definition then | |
| -- At definition, show references | |
| require('telescope.builtin').lsp_references({ initial_mode = "normal" }) | |
| else | |
| -- Not at definition, go to it | |
| vim.lsp.buf.definition() | |
| end | |
| end) | |
| end | |
| -- Override gd and <leader>t after LSP attaches | |
| vim.api.nvim_create_autocmd("LspAttach", { | |
| callback = function(args) | |
| local ft = vim.bo[args.buf].filetype | |
| if ft ~= "php" and ft ~= "blade" then return end | |
| vim.keymap.set("n", "gd", smart_goto, { buffer = args.buf, desc = "Smart goto (LSP + Laravel)" }) | |
| vim.keymap.set("n", "<leader>t", smart_goto, { buffer = args.buf, desc = "Smart goto (LSP + Laravel)" }) | |
| end, | |
| }) | |
| -- Laravel keymaps with <leader>L prefix (global, since this file only loads in Laravel projects) | |
| vim.keymap.set("n", "<leader>Lc", function() require("laravel.navigate").goto_controller() end, { desc = "Go to controller" }) | |
| vim.keymap.set("n", "<leader>Lr", function() require("laravel.routes").show_routes() end, { desc = "Show routes" }) | |
| vim.keymap.set("n", "<leader>Ll", function() | |
| local pickers = require("telescope.pickers") | |
| local finders = require("telescope.finders") | |
| local actions = require("telescope.actions") | |
| local action_state = require("telescope.actions.state") | |
| local conf = require("telescope.config").values | |
| -- Get all commands starting with Laravel, Sail, or Artisan | |
| local all_commands = vim.api.nvim_get_commands({}) | |
| local laravel_commands = {} | |
| for name, _ in pairs(all_commands) do | |
| if name:match("^Laravel") or name:match("^Sail") or name:match("^Artisan") then | |
| table.insert(laravel_commands, name) | |
| end | |
| end | |
| table.sort(laravel_commands) | |
| pickers.new({}, { | |
| prompt_title = "Laravel Commands", | |
| initial_mode = "normal", | |
| finder = finders.new_table({ results = laravel_commands }), | |
| sorter = conf.generic_sorter({}), | |
| attach_mappings = function(prompt_bufnr) | |
| actions.select_default:replace(function() | |
| actions.close(prompt_bufnr) | |
| local selection = action_state.get_selected_entry() | |
| vim.cmd(selection.value) | |
| end) | |
| return true | |
| end, | |
| }):find() | |
| end, { desc = "Laravel commands" }) | |
| -- Sail | |
| vim.keymap.set("n", "<leader>Lsu", "<cmd>SailUp<cr>", { desc = "Sail up" }) | |
| vim.keymap.set("n", "<leader>Lsd", "<cmd>SailDown<cr>", { desc = "Sail down" }) | |
| vim.keymap.set("n", "<leader>Lsr", "<cmd>SailRestart<cr>", { desc = "Sail restart" }) | |
| vim.keymap.set("n", "<leader>Lsh", "<cmd>SailShell<cr>", { desc = "Sail shell" }) | |
| end, | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| return { | |
| "ThePrimeagen/harpoon", | |
| branch = "harpoon2", | |
| dependencies = { "nvim-lua/plenary.nvim" }, | |
| keys = { | |
| { | |
| "<leader>m", | |
| function() | |
| require("harpoon"):list():add() | |
| end, | |
| desc = "Harpoon add file", | |
| }, | |
| { | |
| "<C-e>", | |
| function() | |
| local harpoon = require("harpoon") | |
| harpoon.ui:toggle_quick_menu(harpoon:list()) | |
| end, | |
| desc = "Harpoon menu", | |
| }, | |
| { | |
| "<leader>j", | |
| function() | |
| require("harpoon"):list():select(1) | |
| end, | |
| desc = "Harpoon file 1", | |
| }, | |
| { | |
| "<leader>k", | |
| function() | |
| require("harpoon"):list():select(2) | |
| end, | |
| desc = "Harpoon file 2", | |
| }, | |
| { | |
| "<leader>l", | |
| function() | |
| require("harpoon"):list():select(3) | |
| end, | |
| desc = "Harpoon file 3", | |
| }, | |
| }, | |
| config = function() | |
| require("harpoon"):setup({ | |
| settings = { | |
| save_on_toggle = true, | |
| save_on_change = true, | |
| }, | |
| }) | |
| end, | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| return { | |
| { | |
| "neovim/nvim-lspconfig", | |
| event = { "BufReadPre", "BufNewFile" }, | |
| dependencies = { | |
| { "folke/neoconf.nvim", cmd = "Neoconf", config = false, dependencies = { "nvim-lspconfig" } }, | |
| "mason.nvim", | |
| "mason-org/mason-lspconfig.nvim", | |
| { | |
| "hrsh7th/nvim-cmp", | |
| dependencies = { "hrsh7th/cmp-nvim-lsp" } | |
| }, | |
| { | |
| "nvim-telescope/telescope.nvim", | |
| dependencies = { | |
| "nvim-lua/plenary.nvim", | |
| }, | |
| }, | |
| }, | |
| opts = { | |
| diagnostics = { | |
| underline = true, | |
| update_in_insert = false, | |
| virtual_text = { | |
| spacing = 4, | |
| source = "if_many", | |
| prefix = "●", | |
| }, | |
| severity_sort = true, | |
| }, | |
| inlay_hints = { | |
| enabled = false, | |
| }, | |
| capabilities = {}, | |
| format = { | |
| formatting_options = nil, | |
| timeout_ms = 3000, | |
| }, | |
| servers = { | |
| -- Automatically configure common language servers | |
| bashls = {}, | |
| cssls = {}, | |
| dockerls = {}, | |
| docker_compose_language_service = {}, | |
| emmet_ls = {}, | |
| gopls = {}, | |
| html = {}, | |
| jsonls = {}, | |
| lua_ls = { | |
| settings = { | |
| Lua = { | |
| workspace = { | |
| checkThirdParty = false, | |
| }, | |
| codeLens = { | |
| enable = true, | |
| }, | |
| completion = { | |
| callSnippet = "Replace", | |
| }, | |
| diagnostics = { | |
| globals = { "vim" }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| pyright = {}, | |
| tailwindcss = { | |
| -- Only start if tailwind config exists (prevents runaway processes) | |
| root_dir = function(fname) | |
| local root_pattern = require("lspconfig.util").root_pattern( | |
| "tailwind.config.js", | |
| "tailwind.config.cjs", | |
| "tailwind.config.mjs", | |
| "tailwind.config.ts" | |
| ) | |
| return root_pattern(fname) | |
| end, | |
| -- Don't attach to random files without a project | |
| single_file_support = false, | |
| -- Limit filetypes to reduce unnecessary spawning | |
| filetypes = { | |
| "html", | |
| "css", | |
| "scss", | |
| "javascript", | |
| "javascriptreact", | |
| "typescript", | |
| "typescriptreact", | |
| "vue", | |
| "svelte", | |
| }, | |
| }, | |
| terraformls = { | |
| root_dir = function(fname) | |
| local root_pattern = require("lspconfig.util").root_pattern( | |
| "*.tf", | |
| ".terraform", | |
| "terraform.tfstate" | |
| ) | |
| return root_pattern(fname) | |
| end, | |
| }, | |
| ts_ls = {}, | |
| marksman = { | |
| init_options = { | |
| renderers = { | |
| codeBlock = { | |
| languageAliases = {} | |
| } | |
| } | |
| }, | |
| settings = {} | |
| }, | |
| yamlls = {}, | |
| -- Load Intelephense config from lsp-servers/ | |
| intelephense = (function() | |
| local config_path = vim.fn.stdpath("config") .. "/lua/lsp-servers/intelephense.lua" | |
| if vim.fn.filereadable(config_path) == 1 then | |
| return dofile(config_path) | |
| else | |
| return { cmd = { "intelephense", "--stdio" }, filetypes = { "php" } } | |
| end | |
| end)(), | |
| }, | |
| setup = {}, | |
| }, | |
| config = function(_, opts) | |
| local servers = opts.servers | |
| local has_cmp, cmp_nvim_lsp = pcall(require, "cmp_nvim_lsp") | |
| local capabilities = vim.tbl_deep_extend( | |
| "force", | |
| {}, | |
| vim.lsp.protocol.make_client_capabilities(), | |
| has_cmp and cmp_nvim_lsp.default_capabilities() or {}, | |
| opts.capabilities or {} | |
| ) | |
| local function setup(server) | |
| local server_opts = vim.tbl_deep_extend("force", { | |
| capabilities = vim.deepcopy(capabilities), | |
| }, servers[server] or {}) | |
| if opts.setup[server] then | |
| if opts.setup[server](server, server_opts) then | |
| return | |
| end | |
| elseif opts.setup["*"] then | |
| if opts.setup["*"](server, server_opts) then | |
| return | |
| end | |
| end | |
| require("lspconfig")[server].setup(server_opts) | |
| end | |
| local have_mason, mlsp = pcall(require, "mason-lspconfig") | |
| local ensure_installed = {} ---@type string[] | |
| for server, server_opts in pairs(servers) do | |
| -- Skip special keys that aren't actual server names | |
| if server == "*" then | |
| goto continue | |
| end | |
| if server_opts then | |
| server_opts = server_opts == true and {} or server_opts | |
| -- Only setup manually if explicitly disabled for mason | |
| if server_opts.mason == false then | |
| setup(server) | |
| else | |
| ensure_installed[#ensure_installed + 1] = server | |
| end | |
| end | |
| ::continue:: | |
| end | |
| if have_mason then | |
| mlsp.setup({ ensure_installed = ensure_installed, handlers = { setup } }) | |
| end | |
| -- Custom diagnostic filtering for Laravel | |
| local function filter_diagnostics(diagnostic) | |
| -- Filter out "Non static method 'where' should not be called statically" for Laravel facades | |
| if diagnostic.source == "intelephense" and diagnostic.message:match("Non static method") and diagnostic.message:match("should not be called statically") then | |
| -- Check if it's a common Laravel facade method | |
| local facade_methods = { "where", "find", "create", "update", "delete", "get", "first", "with", "select", "join", "orderBy", "groupBy", "having", "take", "skip", "count", "sum", "avg", "min", "max", "paginate", "latest", "oldest" } | |
| for _, method in ipairs(facade_methods) do | |
| if diagnostic.message:match("'" .. method .. "'") then | |
| return false -- Filter out this diagnostic | |
| end | |
| end | |
| end | |
| -- Filter out undefined function/method errors for common Laravel helpers | |
| if diagnostic.source == "intelephense" and diagnostic.message:match("Undefined") then | |
| local laravel_helpers = { "request", "config", "route", "auth", "session", "cache", "view", "redirect", "response", "abort", "app", "bcrypt", "collect", "env", "event", "factory", "info", "logger", "old", "resolve", "url", "asset", "trans", "__", "dispatch", "dispatch_now", "optional", "now", "today", "validator", "storage", "cookie", "dd", "dump", "tap", "filled", "blank", "rescue", "throw_if", "throw_unless", "report", "retry", "value", "with" } | |
| for _, helper in ipairs(laravel_helpers) do | |
| if diagnostic.message:match("'" .. helper .. "'") then | |
| return false -- Filter out this diagnostic | |
| end | |
| end | |
| end | |
| return true -- Keep all other diagnostics | |
| end | |
| -- Apply diagnostic filter | |
| local orig_handler = vim.lsp.handlers["textDocument/publishDiagnostics"] | |
| vim.lsp.handlers["textDocument/publishDiagnostics"] = function(err, result, ctx, config) | |
| if result and result.diagnostics then | |
| result.diagnostics = vim.tbl_filter(filter_diagnostics, result.diagnostics) | |
| end | |
| orig_handler(err, result, ctx, config) | |
| end | |
| -- Add key mappings for common LSP functions | |
| vim.api.nvim_create_autocmd('LspAttach', { | |
| group = vim.api.nvim_create_augroup('UserLspConfig', {}), | |
| callback = function(ev) | |
| local opts = { buffer = ev.buf } | |
| local client = vim.lsp.get_client_by_id(ev.data.client_id) | |
| -- Debug: Check if client supports code actions | |
| -- Commented out to avoid repetitive warnings for copilot | |
| -- if client and not client.server_capabilities.codeActionProvider then | |
| -- vim.notify( | |
| -- string.format("LSP server '%s' does not support code actions for %s files", | |
| -- client.name, | |
| -- vim.bo[ev.buf].filetype | |
| -- ), | |
| -- vim.log.levels.WARN | |
| -- ) | |
| -- end | |
| -- Setup telescope keymaps | |
| local function telescope_builtin(method, telescopeOpts) | |
| return function() | |
| require('telescope.builtin')[method](telescopeOpts) | |
| end | |
| end | |
| -- Use Telescope for goto/reference actions with lazy loading | |
| vim.keymap.set('n', 'gD', telescope_builtin('lsp_declarations', { initial_mode = "normal" }), opts) | |
| vim.keymap.set('n', 'gd', telescope_builtin('lsp_definitions', { initial_mode = "normal" }), opts) | |
| vim.keymap.set('n', 'gi', telescope_builtin('lsp_implementations', { initial_mode = "normal" }), opts) | |
| vim.keymap.set('n', 'gr', telescope_builtin('lsp_references', { initial_mode = "normal" }), opts) | |
| end, | |
| }) | |
| -- Add command to check LSP status | |
| vim.api.nvim_create_user_command('LspInfo', function() | |
| local clients = vim.lsp.get_active_clients() | |
| if #clients == 0 then | |
| vim.notify("No active LSP clients", vim.log.levels.INFO) | |
| return | |
| end | |
| local info = {} | |
| for _, client in ipairs(clients) do | |
| local capabilities = {} | |
| if client.server_capabilities.codeActionProvider then | |
| table.insert(capabilities, "codeActions") | |
| end | |
| if client.server_capabilities.definitionProvider then | |
| table.insert(capabilities, "definition") | |
| end | |
| if client.server_capabilities.referencesProvider then | |
| table.insert(capabilities, "references") | |
| end | |
| table.insert(info, string.format( | |
| "%s (id=%d): %s", | |
| client.name, | |
| client.id, | |
| table.concat(capabilities, ", ") | |
| )) | |
| end | |
| vim.notify(table.concat(info, "\n"), vim.log.levels.INFO) | |
| end, { desc = "Show LSP server information" }) | |
| end, | |
| }, | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| return { | |
| { | |
| "ThePrimeagen/refactoring.nvim", | |
| dependencies = { | |
| "nvim-lua/plenary.nvim", | |
| "nvim-treesitter/nvim-treesitter", | |
| }, | |
| keys = { | |
| { | |
| "<leader>pp", | |
| function() | |
| require("refactoring").select_refactor({ | |
| show_success_message = true, | |
| }) | |
| end, | |
| mode = { "n", "x" }, | |
| desc = "Refactoring menu", | |
| }, | |
| -- Quick access to common refactorings | |
| { | |
| "<leader>pe", | |
| function() require("refactoring").refactor("Extract Function") end, | |
| mode = "x", | |
| desc = "Extract function", | |
| }, | |
| { | |
| "<leader>pf", | |
| function() require("refactoring").refactor("Extract Function To File") end, | |
| mode = "x", | |
| desc = "Extract function to file", | |
| }, | |
| { | |
| "<leader>pv", | |
| function() require("refactoring").refactor("Extract Variable") end, | |
| mode = "x", | |
| desc = "Extract variable", | |
| }, | |
| { | |
| "<leader>pi", | |
| function() require("refactoring").refactor("Inline Variable") end, | |
| mode = { "n", "x" }, | |
| desc = "Inline variable", | |
| }, | |
| { | |
| "<leader>pb", | |
| function() require("refactoring").refactor("Extract Block") end, | |
| mode = "x", | |
| desc = "Extract block", | |
| }, | |
| { | |
| "<leader>pbf", | |
| function() require("refactoring").refactor("Extract Block To File") end, | |
| mode = "x", | |
| desc = "Extract block to file", | |
| }, | |
| -- File operations and imports | |
| { | |
| "<leader>pr", | |
| function() vim.lsp.buf.rename() end, | |
| mode = "n", | |
| desc = "Rename symbol", | |
| }, | |
| { | |
| "<leader>po", | |
| function() | |
| -- Organize imports | |
| vim.lsp.buf.code_action({ | |
| filter = function(action) | |
| return action.title:match("Organize") or action.title:match("Import") | |
| end, | |
| apply = true, | |
| }) | |
| end, | |
| mode = "n", | |
| desc = "Organize imports", | |
| }, | |
| { | |
| "<leader>pa", | |
| function() vim.lsp.buf.code_action() end, | |
| mode = { "n", "x" }, | |
| desc = "Code actions", | |
| }, | |
| { | |
| "<leader>pF", | |
| function() vim.lsp.buf.format() end, | |
| mode = { "n", "x" }, | |
| desc = "Format code", | |
| }, | |
| }, | |
| config = function() | |
| require("refactoring").setup({ | |
| prompt_func_return_type = { | |
| go = false, | |
| java = false, | |
| cpp = false, | |
| c = false, | |
| h = false, | |
| hpp = false, | |
| cxx = false, | |
| }, | |
| prompt_func_param_type = { | |
| go = false, | |
| java = false, | |
| cpp = false, | |
| c = false, | |
| h = false, | |
| hpp = false, | |
| cxx = false, | |
| }, | |
| printf_statements = {}, | |
| print_var_statements = {}, | |
| show_success_message = true, | |
| }) | |
| -- Load refactoring Telescope extension | |
| require("telescope").load_extension("refactoring") | |
| -- Telescope refactoring helper | |
| vim.keymap.set( | |
| {"n", "x"}, | |
| "<leader>pR", | |
| function() require('telescope').extensions.refactoring.refactors() end, | |
| { desc = "Telescope refactoring" } | |
| ) | |
| -- File operations | |
| vim.keymap.set("n", "<leader>pm", function() | |
| local current_file = vim.fn.expand("%:p") | |
| if current_file == "" then | |
| vim.notify("No file to move", vim.log.levels.WARN) | |
| return | |
| end | |
| vim.ui.input({ | |
| prompt = "Move file to: ", | |
| default = current_file, | |
| completion = "file", | |
| }, function(new_path) | |
| if new_path and new_path ~= current_file then | |
| -- Create directory if it doesn't exist | |
| local new_dir = vim.fn.fnamemodify(new_path, ":h") | |
| vim.fn.mkdir(new_dir, "p") | |
| -- Move the file | |
| local success = vim.loop.fs_rename(current_file, new_path) | |
| if success then | |
| -- Update buffer | |
| vim.cmd("saveas " .. new_path) | |
| vim.fn.delete(current_file) | |
| vim.notify("File moved to: " .. new_path) | |
| else | |
| vim.notify("Failed to move file", vim.log.levels.ERROR) | |
| end | |
| end | |
| end) | |
| end, { desc = "Move/rename file" }) | |
| -- Auto-import missing symbols | |
| vim.keymap.set("n", "<leader>pI", function() | |
| vim.lsp.buf.code_action({ | |
| filter = function(action) | |
| return action.title:match("Import") or action.title:match("Add import") | |
| end, | |
| apply = true, | |
| }) | |
| end, { desc = "Auto import" }) | |
| end, | |
| }, | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment