diff --git a/init.lua b/init.lua index 5cac3d14601..0db4ff4227a 100644 --- a/init.lua +++ b/init.lua @@ -91,7 +91,8 @@ vim.g.mapleader = ' ' vim.g.maplocalleader = ' ' -- Set to true if you have a Nerd Font installed and selected in the terminal -vim.g.have_nerd_font = false +-- vim.g.have_nerd_font = false +vim.g.have_nerd_font = true -- [[ Setting options ]] -- See `:help vim.opt` @@ -393,11 +394,18 @@ require('lazy').setup({ -- You can put your default mappings / updates / etc. in here -- All the info you're looking for is in `:help telescope.setup()` -- - -- defaults = { - -- mappings = { - -- i = { [''] = 'to_fuzzy_refine' }, - -- }, - -- }, + defaults = { + mappings = { + n = { + [''] = require('telescope.actions').delete_buffer, + }, + i = { + [''] = 'which_key', + [''] = require('telescope.actions').delete_buffer, + }, + -- i = { [''] = 'to_fuzzy_refine' }, + }, + }, -- pickers = {} extensions = { ['ui-select'] = { @@ -554,10 +562,25 @@ require('lazy').setup({ -- or a suggestion from your LSP for this to activate. map('ca', vim.lsp.buf.code_action, '[C]ode [A]ction', { 'n', 'x' }) + -- Organize imports (Python via Pyright code actions) + map('co', function() + vim.lsp.buf.code_action({ + context = { + only = { 'source.organizeImports' }, + }, + apply = true, + }) + end, '[C]ode [O]rganize Imports') + -- WARN: This is not Goto Definition, this is Goto Declaration. -- For example, in C this would take you to the header. map('gD', vim.lsp.buf.declaration, '[G]oto [D]eclaration') + map('gDef', vim.lsp.buf.hover, '[G]oto [Def]inition') + + -- Show LSP message + map('gM', vim.diagnostic.open_float, '[G]oto [M]essage') + -- This function resolves a difference between neovim nightly (version 0.11) and stable (version 0.10) ---@param client vim.lsp.Client ---@param method vim.lsp.protocol.Method @@ -660,7 +683,21 @@ require('lazy').setup({ local servers = { -- clangd = {}, -- gopls = {}, - -- pyright = {}, + pyright = { + settings = { + python = { + analysis = { + autoSearchPaths = true, + useLibraryCodeForTypes = true, + diagnosticMode = 'workspace', + typeCheckingMode = 'basic', + autoImportCompletions = true, + }, + }, + }, + }, + -- NOTE: jdtls is configured separately via nvim-jdtls plugin (see custom/plugins/jdtls.lua) + -- Do not configure it here as it requires special handling -- rust_analyzer = {}, -- ... etc. See `:help lspconfig-all` for a list of all the pre-configured LSPs -- @@ -703,6 +740,11 @@ require('lazy').setup({ local ensure_installed = vim.tbl_keys(servers or {}) vim.list_extend(ensure_installed, { 'stylua', -- Used to format Lua code + 'jdtls', -- Java language server + 'java-debug-adapter', -- Java debugger + 'java-test', -- Java test runner + 'isort', -- Python import organizer + 'black', -- Python formatter }) require('mason-tool-installer').setup { ensure_installed = ensure_installed } @@ -711,6 +753,10 @@ require('lazy').setup({ automatic_installation = false, handlers = { function(server_name) + -- Skip jdtls as it's configured separately via nvim-jdtls plugin + if server_name == 'jdtls' then + return + end local server = servers[server_name] or {} -- This handles overriding only values explicitly passed -- by the server configuration above. Useful when disabling @@ -758,7 +804,7 @@ require('lazy').setup({ formatters_by_ft = { lua = { 'stylua' }, -- Conform can also run multiple formatters sequentially - -- python = { "isort", "black" }, + python = { "isort", "black" }, -- -- You can use 'stop_after_first' to run the first available formatter from the list -- javascript = { "prettierd", "prettier", stop_after_first = true }, @@ -985,14 +1031,14 @@ require('lazy').setup({ -- require 'kickstart.plugins.indent_line', -- require 'kickstart.plugins.lint', -- require 'kickstart.plugins.autopairs', - -- require 'kickstart.plugins.neo-tree', - -- require 'kickstart.plugins.gitsigns', -- adds gitsigns recommend keymaps + require 'kickstart.plugins.neo-tree', + require 'kickstart.plugins.gitsigns', -- adds gitsigns recommend keymaps -- NOTE: The import below can automatically add your own plugins, configuration, etc from `lua/custom/plugins/*.lua` -- This is the easiest way to modularize your config. -- -- Uncomment the following line and add your plugins to `lua/custom/plugins/*.lua` to get going. - -- { import = 'custom.plugins' }, + { import = 'custom.plugins' }, -- -- For additional information with loading, sourcing and examples see `:help lazy.nvim-🔌-plugin-spec` -- Or use telescope! diff --git a/lua/custom/maven.lua b/lua/custom/maven.lua new file mode 100644 index 00000000000..58b3d850a8a --- /dev/null +++ b/lua/custom/maven.lua @@ -0,0 +1,195 @@ +-- Maven command runner with UI +local M = {} + +-- Store last command for quick re-run +M.last_command = nil + +-- Common Maven phases and goals +M.maven_commands = { + -- Lifecycle Phases + { name = "clean", desc = "Clean the project (remove target/)" }, + { name = "validate", desc = "Validate project structure" }, + { name = "compile", desc = "Compile source code" }, + { name = "test", desc = "Run unit tests" }, + { name = "test-compile", desc = "Compile test sources" }, + { name = "package", desc = "Package compiled code (JAR/WAR)" }, + { name = "verify", desc = "Run integration tests" }, + { name = "install", desc = "Install package to local repository" }, + { name = "deploy", desc = "Deploy to remote repository" }, + + -- Common Combined Commands + { name = "clean compile", desc = "Clean and compile" }, + { name = "clean test", desc = "Clean and test" }, + { name = "clean package", desc = "Clean and package" }, + { name = "clean install", desc = "Clean and install" }, + + -- Plugin Goals + { name = "dependency:tree", desc = "Display dependency tree" }, + { name = "dependency:analyze", desc = "Analyze dependencies" }, + { name = "versions:display-dependency-updates", desc = "Check for dependency updates" }, + { name = "help:effective-pom", desc = "Show effective POM" }, + + -- Spring Boot specific (if applicable) + { name = "spring-boot:run", desc = "Run Spring Boot application" }, + + -- Testing specific + { name = "test -Dtest=", desc = "Run specific test class (specify after =)", custom_input = true }, + { name = "test -Dtest=*Test", desc = "Run all test classes" }, + { name = "surefire-report:report", desc = "Generate test report" }, +} + +-- Execute Maven command +function M.execute_maven(cmd, opts) + opts = opts or {} + + -- Check if mvn exists + local mvn_cmd = vim.fn.executable("mvn") == 1 and "mvn" or nil + if not mvn_cmd then + vim.notify("Maven (mvn) not found in PATH. Install with: brew install maven", vim.log.levels.ERROR) + return + end + + -- Store last command + M.last_command = cmd + + -- Determine if we're in a Maven project + local pom_exists = vim.fn.filereadable(vim.fn.getcwd() .. "/pom.xml") == 1 + if not pom_exists then + vim.notify("No pom.xml found in current directory: " .. vim.fn.getcwd(), vim.log.levels.WARN) + local proceed = vim.fn.confirm("Run Maven anyway?", "&Yes\n&No", 2) + if proceed ~= 1 then + return + end + end + + local full_cmd = "mvn " .. cmd + + -- Choose display method based on preference + if opts.background then + -- Run in background, show in quickfix + vim.notify("Running: " .. full_cmd, vim.log.levels.INFO) + vim.fn.setqflist({}, 'r', { title = full_cmd, lines = {} }) + vim.cmd("copen") + + vim.fn.jobstart(full_cmd, { + on_stdout = function(_, data) + if data then + vim.fn.setqflist({}, 'a', { lines = data }) + end + end, + on_stderr = function(_, data) + if data then + vim.fn.setqflist({}, 'a', { lines = data }) + end + end, + on_exit = function(_, exit_code) + if exit_code == 0 then + vim.notify("Maven command completed successfully", vim.log.levels.INFO) + else + vim.notify("Maven command failed with exit code: " .. exit_code, vim.log.levels.ERROR) + end + end, + }) + else + -- Run in terminal split + vim.cmd("botright 15split | terminal " .. full_cmd) + vim.cmd("startinsert") + end +end + +-- Show Maven menu using vim.ui.select +function M.show_menu() + local choices = {} + for i, cmd in ipairs(M.maven_commands) do + choices[i] = string.format("%-40s | %s", cmd.name, cmd.desc) + end + + vim.ui.select(choices, { + prompt = "Select Maven command:", + format_item = function(item) + return item + end, + }, function(choice, idx) + if not choice then + return + end + + local selected = M.maven_commands[idx] + + -- Handle custom input commands + if selected.custom_input then + vim.ui.input({ + prompt = "Enter Maven command: ", + default = selected.name, + }, function(input) + if input and input ~= "" then + M.execute_maven(input) + end + end) + else + M.execute_maven(selected.name) + end + end) +end + +-- Run last Maven command +function M.run_last() + if M.last_command then + vim.notify("Re-running: mvn " .. M.last_command, vim.log.levels.INFO) + M.execute_maven(M.last_command) + else + vim.notify("No previous Maven command to run", vim.log.levels.WARN) + M.show_menu() + end +end + +-- Quick command to run specific test +function M.run_test(test_name) + if test_name then + M.execute_maven("test -Dtest=" .. test_name) + else + -- Try to infer test name from current file + local current_file = vim.fn.expand("%:t:r") -- filename without extension + if current_file:match("Test$") or current_file:match("Tests$") then + vim.ui.input({ + prompt = "Test class name:", + default = current_file, + }, function(input) + if input and input ~= "" then + M.execute_maven("test -Dtest=" .. input) + end + end) + else + M.execute_maven("test") + end + end +end + +-- User command to run Maven +vim.api.nvim_create_user_command("Maven", function(opts) + if opts.args and opts.args ~= "" then + M.execute_maven(opts.args) + else + M.show_menu() + end +end, { + nargs = "*", + desc = "Run Maven command", + complete = function() + local completions = {} + for _, cmd in ipairs(M.maven_commands) do + table.insert(completions, cmd.name) + end + return completions + end, +}) + +-- User command to run tests +vim.api.nvim_create_user_command("MavenTest", function(opts) + M.run_test(opts.args ~= "" and opts.args or nil) +end, { + nargs = "?", + desc = "Run Maven tests", +}) + +return M diff --git a/lua/custom/plugins/dap-config.lua b/lua/custom/plugins/dap-config.lua new file mode 100644 index 00000000000..f1a092ef844 --- /dev/null +++ b/lua/custom/plugins/dap-config.lua @@ -0,0 +1,239 @@ +-- Custom DAP configuration for Python and Java +return { + -- Main DAP plugin + { + "mfussenegger/nvim-dap", + dependencies = { + "williamboman/mason.nvim", + "jay-babu/mason-nvim-dap.nvim", + }, + keys = { + -- Basic debugging keymaps + { + "", + function() + require("dap").continue() + end, + desc = "Debug: Start/Continue", + }, + { + "", + function() + require("dap").step_into() + end, + desc = "Debug: Step Into", + }, + { + "", + function() + require("dap").step_over() + end, + desc = "Debug: Step Over", + }, + { + "", + function() + require("dap").step_out() + end, + desc = "Debug: Step Out", + }, + { + "b", + function() + require("dap").toggle_breakpoint() + end, + desc = "Debug: Toggle Breakpoint", + }, + { + "B", + function() + require("dap").set_breakpoint(vim.fn.input("Breakpoint condition: ")) + end, + desc = "Debug: Set Conditional Breakpoint", + }, + -- Additional keymaps + { + "dc", + function() + require("dap").continue() + end, + desc = "Debug: Continue", + }, + { + "dr", + function() + require("dap").repl.toggle() + end, + desc = "Debug: Toggle REPL", + }, + { + "dl", + function() + require("dap").run_last() + end, + desc = "Debug: Run Last", + }, + { + "dt", + function() + require("dap").terminate() + end, + desc = "Debug: Terminate", + }, + }, + config = function() + local dap = require("dap") + + -- Mason DAP setup - auto-install debuggers + require("mason-nvim-dap").setup({ + automatic_installation = true, + ensure_installed = { + "python", + "javadbg", + "javatest", + }, + handlers = {}, + }) + + -- Set up breakpoint icons + local icons = vim.g.have_nerd_font + and { Breakpoint = "", BreakpointCondition = "", BreakpointRejected = "", LogPoint = "", Stopped = "" } + or { Breakpoint = "●", BreakpointCondition = "⊜", BreakpointRejected = "⊘", LogPoint = "◆", Stopped = "⭔" } + + for type, icon in pairs(icons) do + local hl = (type == "Stopped") and "DapStop" or "DapBreak" + vim.fn.sign_define("Dap" .. type, { text = icon, texthl = hl, numhl = hl }) + end + end, + }, + + -- Python DAP support + { + "mfussenegger/nvim-dap-python", + ft = "python", + dependencies = { + "mfussenegger/nvim-dap", + }, + config = function() + -- Find python path - prefer virtual environment + local python_path = function() + local cwd = vim.fn.getcwd() + if vim.fn.executable(cwd .. "/venv/bin/python") == 1 then + return cwd .. "/venv/bin/python" + elseif vim.fn.executable(cwd .. "/.venv/bin/python") == 1 then + return cwd .. "/.venv/bin/python" + else + return "python3" + end + end + + require("dap-python").setup(python_path()) + + -- Add custom configurations + table.insert(require("dap").configurations.python, { + type = "python", + request = "launch", + name = "Launch file with arguments", + program = "${file}", + args = function() + local args_string = vim.fn.input("Arguments: ") + return vim.split(args_string, " +") + end, + }) + + table.insert(require("dap").configurations.python, { + type = "python", + request = "launch", + name = "Django", + program = vim.fn.getcwd() .. "/manage.py", + args = { "runserver", "--noreload" }, + }) + end, + }, + + -- Java DAP support is handled by nvim-jdtls (see jdtls.lua) + -- No separate configuration needed here + + -- Enhanced UI for debugging + { + "rcarriga/nvim-dap-ui", + dependencies = { "mfussenegger/nvim-dap", "nvim-neotest/nvim-nio" }, + keys = { + { + "", + function() + require("dapui").toggle() + end, + desc = "Debug: Toggle UI", + }, + { + "du", + function() + require("dapui").toggle() + end, + desc = "Debug: Toggle UI", + }, + }, + config = function() + local dapui = require("dapui") + local dap = require("dap") + + -- Configure UI + dapui.setup({ + layouts = { + { + elements = { + { id = "scopes", size = 0.25 }, + { id = "breakpoints", size = 0.25 }, + { id = "stacks", size = 0.25 }, + { id = "watches", size = 0.25 }, + }, + position = "left", + size = 40, + }, + { + elements = { + { id = "repl", size = 0.5 }, + { id = "console", size = 0.5 }, + }, + position = "bottom", + size = 10, + }, + }, + icons = vim.g.have_nerd_font and { + expanded = "▾", + collapsed = "▸", + current_frame = "▸", + } or { + expanded = "▾", + collapsed = "▸", + current_frame = "*", + }, + controls = { + icons = { + pause = "⏸", + play = "▶", + step_into = "⏎", + step_over = "⏭", + step_out = "⏮", + step_back = "b", + run_last = "▶▶", + terminate = "⏹", + disconnect = "⏏", + }, + }, + }) + + -- Auto open/close UI + dap.listeners.after.event_initialized["dapui_config"] = function() + dapui.open() + end + dap.listeners.before.event_terminated["dapui_config"] = function() + dapui.close() + end + dap.listeners.before.event_exited["dapui_config"] = function() + dapui.close() + end + end, + }, +} \ No newline at end of file diff --git a/lua/custom/plugins/init.lua b/lua/custom/plugins/init.lua index be0eb9d8d7a..3e40c2e5c61 100644 --- a/lua/custom/plugins/init.lua +++ b/lua/custom/plugins/init.lua @@ -2,4 +2,12 @@ -- I promise not to create any merge conflicts in this directory :) -- -- See the kickstart.nvim README for more information -return {} +-- All .lua files in this directory will be automatically loaded by lazy.nvim +return { + { + 'MeanderingProgrammer/render-markdown.nvim', + dependencies = { 'nvim-treesitter/nvim-treesitter', 'nvim-tree/nvim-web-devicons' }, + ft = { 'markdown' }, + opts = {}, + }, +} diff --git a/lua/custom/plugins/jdtls.lua b/lua/custom/plugins/jdtls.lua new file mode 100644 index 00000000000..0e711bf74b5 --- /dev/null +++ b/lua/custom/plugins/jdtls.lua @@ -0,0 +1,221 @@ +-- Java LSP configuration using nvim-jdtls +-- This provides better Java support including auto-imports, code actions, etc. +return { + "mfussenegger/nvim-jdtls", + ft = "java", + dependencies = { + "mfussenegger/nvim-dap", + "williamboman/mason.nvim", + }, + config = function() + -- This function will be called when a Java file is opened + local function setup_jdtls() + local jdtls = require("jdtls") + + -- Find the Mason installation path + local mason_registry = require("mason-registry") + local jdtls_path = mason_registry.get_package("jdtls"):get_install_path() + + -- Find the appropriate jar file (the launcher) + local jar_patterns = { + jdtls_path .. "/plugins/org.eclipse.equinox.launcher_*.jar", + } + + local jar_path = nil + for _, pattern in ipairs(jar_patterns) do + local matches = vim.fn.glob(pattern, true, true) + if #matches > 0 then + jar_path = matches[1] + break + end + end + + if not jar_path then + vim.notify("Could not find JDTLS launcher jar", vim.log.levels.ERROR) + return + end + + -- Determine the OS-specific configuration + local os_config = "linux" + if vim.fn.has("mac") == 1 then + os_config = "mac" + elseif vim.fn.has("win32") == 1 then + os_config = "win" + end + + -- Data directory for this workspace + local project_name = vim.fn.fnamemodify(vim.fn.getcwd(), ":p:h:t") + local workspace_dir = vim.fn.stdpath("data") .. "/jdtls-workspace/" .. project_name + + -- Get the Java debug adapter bundles + local bundles = {} + local java_debug_path = mason_registry.get_package("java-debug-adapter"):get_install_path() + + -- Add java-debug adapter (required for debugging) + vim.list_extend(bundles, vim.fn.glob(java_debug_path .. "/extension/server/com.microsoft.java.debug.plugin-*.jar", true, true)) + + -- NOTE: java-test bundles disabled due to org.objectweb.asm dependency conflict + -- To run tests, use: mvn test or gradle test from command line + -- local java_test_path = mason_registry.get_package("java-test"):get_install_path() + -- vim.list_extend(bundles, vim.fn.glob(java_test_path .. "/extension/server/*.jar", true, true)) + + -- Find Lombok jar + local lombok_path = jdtls_path .. "/lombok.jar" + + -- LSP settings for Java + local extendedClientCapabilities = jdtls.extendedClientCapabilities + extendedClientCapabilities.resolveAdditionalTextEditsSupport = true + + -- Build cmd array with Lombok support + local cmd = { + "java", + "-Declipse.application=org.eclipse.jdt.ls.core.id1", + "-Dosgi.bundles.defaultStartLevel=4", + "-Declipse.product=org.eclipse.jdt.ls.core.product", + "-Dlog.protocol=true", + "-Dlog.level=ALL", + "-Xmx1g", + "--add-modules=ALL-SYSTEM", + "--add-opens", "java.base/java.util=ALL-UNNAMED", + "--add-opens", "java.base/java.lang=ALL-UNNAMED", + } + + -- Add Lombok agent if jar exists + if vim.fn.filereadable(lombok_path) == 1 then + table.insert(cmd, "-javaagent:" .. lombok_path) + end + + -- Add jar and configuration + table.insert(cmd, "-jar") + table.insert(cmd, jar_path) + table.insert(cmd, "-configuration") + table.insert(cmd, jdtls_path .. "/config_" .. os_config) + table.insert(cmd, "-data") + table.insert(cmd, workspace_dir) + + local config = { + cmd = cmd, + + root_dir = require("jdtls.setup").find_root({".git", "mvnw", "gradlew", "pom.xml", "build.gradle"}), + + settings = { + java = { + eclipse = { + downloadSources = true, + }, + configuration = { + updateBuildConfiguration = "interactive", + }, + maven = { + downloadSources = true, + }, + implementationsCodeLens = { + enabled = true, + }, + referencesCodeLens = { + enabled = true, + }, + references = { + includeDecompiledSources = true, + }, + format = { + enabled = true, + }, + -- Enable auto-import completion + completion = { + favoriteStaticMembers = { + "org.junit.jupiter.api.Assertions.*", + "org.junit.Assert.*", + "org.mockito.Mockito.*", + "org.mockito.ArgumentMatchers.*", + }, + importOrder = { + "java", + "javax", + "com", + "org", + }, + }, + sources = { + organizeImports = { + starThreshold = 9999, + staticStarThreshold = 9999, + }, + }, + codeGeneration = { + toString = { + template = "${object.className}{${member.name()}=${member.value}, ${otherMembers}}", + }, + useBlocks = true, + }, + }, + signatureHelp = { enabled = true }, + extendedClientCapabilities = extendedClientCapabilities, + }, + + init_options = { + bundles = bundles, + }, + + on_attach = function(client, bufnr) + -- Enable completion triggered by + vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc') + + -- JDTLS-specific keybindings + local opts = { buffer = bufnr, noremap = true, silent = true } + + -- Organize imports + vim.keymap.set("n", "co", function() + require("jdtls").organize_imports() + end, vim.tbl_extend("force", opts, { desc = "LSP: [C]ode [O]rganize Imports" })) + + -- Extract variable + vim.keymap.set("n", "cv", function() + require("jdtls").extract_variable() + end, vim.tbl_extend("force", opts, { desc = "LSP: [C]ode Extract [V]ariable" })) + + vim.keymap.set("v", "cv", function() + require("jdtls").extract_variable(true) + end, vim.tbl_extend("force", opts, { desc = "LSP: [C]ode Extract [V]ariable" })) + + -- Extract constant + vim.keymap.set("n", "cc", function() + require("jdtls").extract_constant() + end, vim.tbl_extend("force", opts, { desc = "LSP: [C]ode Extract [C]onstant" })) + + vim.keymap.set("v", "cc", function() + require("jdtls").extract_constant(true) + end, vim.tbl_extend("force", opts, { desc = "LSP: [C]ode Extract [C]onstant" })) + + -- Extract method + vim.keymap.set("v", "cm", function() + require("jdtls").extract_method(true) + end, vim.tbl_extend("force", opts, { desc = "LSP: [C]ode Extract [M]ethod" })) + + -- Test class/method (disabled - java-test bundles not loaded) + -- Use command line instead: mvn test -Dtest=ClassName or mvn test -Dtest=ClassName#methodName + -- vim.keymap.set("n", "tn", function() + -- require("jdtls").test_nearest_method() + -- end, vim.tbl_extend("force", opts, { desc = "LSP: [T]est [N]earest Method" })) + -- + -- vim.keymap.set("n", "tc", function() + -- require("jdtls").test_class() + -- end, vim.tbl_extend("force", opts, { desc = "LSP: [T]est [C]lass" })) + + -- DAP setup + jdtls.setup_dap({ hotcodereplace = "auto" }) + require("jdtls.dap").setup_dap_main_class_configs() + end, + } + + -- Start or attach to the language server + jdtls.start_or_attach(config) + end + + -- Set up an autocommand to start JDTLS when opening Java files + vim.api.nvim_create_autocmd("FileType", { + pattern = "java", + callback = setup_jdtls, + }) + end, +} diff --git a/lua/custom/plugins/maven-runner.lua b/lua/custom/plugins/maven-runner.lua new file mode 100644 index 00000000000..10baba8be7e --- /dev/null +++ b/lua/custom/plugins/maven-runner.lua @@ -0,0 +1,22 @@ +-- Maven Runner - UI for running Maven phases and goals +return { + "mfussenegger/nvim-jdtls", + keys = { + { + "m", + function() + require("custom.maven").show_menu() + end, + desc = "Maven: Show Menu", + ft = "java", + }, + { + "mr", + function() + require("custom.maven").run_last() + end, + desc = "Maven: Run Last Command", + ft = "java", + }, + }, +} diff --git a/lua/kickstart/plugins/lint.lua b/lua/kickstart/plugins/lint.lua index 907c6bf3e31..1698855c2b4 100644 --- a/lua/kickstart/plugins/lint.lua +++ b/lua/kickstart/plugins/lint.lua @@ -7,6 +7,7 @@ return { local lint = require 'lint' lint.linters_by_ft = { markdown = { 'markdownlint' }, + python = { 'flake8' }, } -- To allow other plugins to add linters to require('lint').linters_by_ft,