This commit is contained in:
Prabhat Maurya 2026-07-02 13:49:44 +05:30
parent b659d61c26
commit f4119e7818
10 changed files with 320 additions and 6 deletions

View file

@ -15,7 +15,8 @@ require("user.files_utils")
require("user.commands") require("user.commands")
require("user.comment") require("user.comment")
require("user.trouble") require("user.trouble")
require("user.java") require("user.android")
require("user.flutter")
return require("packer").startup(function(use) return require("packer").startup(function(use)
-- Let packer manage itself -- Let packer manage itself

BIN
jars/ktfmt.jar Normal file

Binary file not shown.

86
lua/user/android.lua Normal file
View file

@ -0,0 +1,86 @@
-- lua/user/android.lua
-- Android development support for Neovim:
-- * exports the Android SDK env (so LSP, Gradle and :terminal jobs see it)
-- * :Android* commands + <leader>a* keymaps for build / run / test / logcat / emulator
--
-- Java (nvim-jdtls) and Kotlin (JetBrains kotlin-lsp) are configured elsewhere; this
-- module is purely the build/run/device tooling around them.
local sdk = vim.fn.expand("~/Android/Sdk")
-- 1. Make the Android SDK visible to LSP, Gradle and terminal jobs launched from nvim.
vim.env.ANDROID_HOME = vim.env.ANDROID_HOME or sdk
vim.env.ANDROID_SDK_ROOT = vim.env.ANDROID_SDK_ROOT or sdk
do
local path = vim.env.PATH or ""
local extra = {
sdk .. "/platform-tools", -- adb
sdk .. "/emulator", -- emulator
sdk .. "/cmdline-tools/latest/bin",-- sdkmanager/avdmanager
vim.fn.expand("~/.local/bin"), -- the `android` CLI
}
for _, p in ipairs(extra) do
if vim.fn.isdirectory(p) == 1 and not string.find(path, p, 1, true) then
path = p .. ":" .. path
end
end
vim.env.PATH = path
end
-- 2. Locate the Gradle project root (nearest ancestor with a gradlew wrapper).
local function gradle_root()
local start = vim.fn.expand("%:p:h")
if start == "" then start = vim.fn.getcwd() end
local found = vim.fs.find("gradlew", { upward = true, path = start })
if found and found[1] then
return vim.fn.fnamemodify(found[1], ":h")
end
return vim.fn.getcwd()
end
-- 3. Run a shell command in a bottom terminal split (live, scrollable output).
local function run_in_term(shell_cmd)
vim.cmd("botright 15split")
vim.cmd("terminal " .. shell_cmd)
vim.cmd("startinsert")
end
local function run_gradle(args)
run_in_term(string.format("cd %s && ./gradlew %s", vim.fn.shellescape(gradle_root()), args))
end
local function run_cmd(c)
run_in_term(string.format("cd %s && %s", vim.fn.shellescape(gradle_root()), c))
end
-- 4. User commands.
local cmd = vim.api.nvim_create_user_command
cmd("AndroidBuild", function() run_gradle("assembleDebug") end, { desc = "Gradle assembleDebug" })
cmd("AndroidClean", function() run_gradle("clean") end, { desc = "Gradle clean" })
cmd("AndroidInstall", function() run_gradle("installDebug") end, { desc = "Gradle installDebug" })
cmd("AndroidTest", function() run_gradle("testDebugUnitTest") end,{ desc = "Gradle unit tests" })
cmd("AndroidRun", function() run_cmd("android run") end, { desc = "Deploy app (android run)" })
cmd("AndroidDevices", function() run_cmd("adb devices -l") end, { desc = "List adb devices" })
cmd("AndroidLogcat", function() run_cmd("adb logcat -c; adb logcat") end, { desc = "adb logcat (cleared)" })
cmd("AndroidEmulators", function() run_cmd("android emulator list") end, { desc = "List AVDs" })
cmd("AndroidEmulator", function(o)
if o.args ~= "" then
run_cmd("android emulator start " .. vim.fn.shellescape(o.args))
else
-- Default: start the first available AVD.
run_cmd([[android emulator start "$("]] .. sdk .. [[/emulator/emulator" -list-avds | head -1)"]])
end
end, { nargs = "?", desc = "Start an emulator (optional AVD name)" })
cmd("AndroidStopEmulator", function() run_cmd("android emulator stop") end, { desc = "Stop emulator" })
-- 5. Keymaps (<leader>A* = Android).
-- NOTE: uppercase A so it does NOT clash with <leader>a (LSP code action).
local map = vim.keymap.set
map("n", "<leader>Ab", "<cmd>AndroidBuild<CR>", { silent = true, desc = "Android: build (assembleDebug)" })
map("n", "<leader>Ar", "<cmd>AndroidRun<CR>", { silent = true, desc = "Android: run/deploy" })
map("n", "<leader>Ai", "<cmd>AndroidInstall<CR>", { silent = true, desc = "Android: install" })
map("n", "<leader>At", "<cmd>AndroidTest<CR>", { silent = true, desc = "Android: unit tests" })
map("n", "<leader>Al", "<cmd>AndroidLogcat<CR>", { silent = true, desc = "Android: logcat" })
map("n", "<leader>Ad", "<cmd>AndroidDevices<CR>", { silent = true, desc = "Android: devices" })
map("n", "<leader>Ae", "<cmd>AndroidEmulator<CR>", { silent = true, desc = "Android: start emulator" })
map("n", "<leader>Ac", "<cmd>AndroidClean<CR>", { silent = true, desc = "Android: clean" })

View file

@ -3,6 +3,13 @@
local cmp = require("cmp") local cmp = require("cmp")
cmp.setup({ cmp.setup({
-- Throttle LSP completion requests.
-- Default: no debounce → every keystroke fires a compiler pass (CPU heat).
performance = {
debounce = 300, -- wait 300ms after typing stops before requesting
throttle = 500, -- max one completion request every 500ms
max_view_entries = 30,
},
snippet = { snippet = {
expand = function(args) expand = function(args)
require("luasnip").lsp_expand(args.body) require("luasnip").lsp_expand(args.body)

63
lua/user/flutter.lua Normal file
View file

@ -0,0 +1,63 @@
-- Flutter-specific Neovim configuration
-- Keymaps for Flutter development workflow
local function flutter_root()
return vim.fn.finddir("android", vim.fn.getcwd() .. ";") ~= ""
or vim.fn.findfile("pubspec.yaml", vim.fn.getcwd() .. ";") ~= ""
end
-- Create Flutter-specific user commands
vim.api.nvim_create_user_command("FlutterRun", function()
vim.cmd("split | terminal flutter run")
end, {})
vim.api.nvim_create_user_command("FlutterClean", function()
vim.cmd("terminal flutter clean")
end, {})
vim.api.nvim_create_user_command("FlutterPubGet", function()
vim.cmd("terminal flutter pub get")
end, {})
vim.api.nvim_create_user_command("FlutterPubOutdated", function()
vim.cmd("terminal flutter pub outdated")
end, {})
vim.api.nvim_create_user_command("FlutterPubUpgrade", function()
vim.cmd("terminal flutter pub upgrade")
end, {})
vim.api.nvim_create_user_command("FlutterAnalyze", function()
vim.cmd("terminal flutter analyze")
end, {})
vim.api.nvim_create_user_command("FlutterTest", function()
vim.cmd("terminal flutter test")
end, {})
vim.api.nvim_create_user_command("FlutterBuildApk", function()
vim.cmd("terminal flutter build apk")
end, {})
vim.api.nvim_create_user_command("FlutterDevices", function()
vim.cmd("terminal flutter devices")
end, {})
-- Dart filetype-specific settings
vim.api.nvim_create_autocmd("FileType", {
pattern = "dart",
callback = function()
vim.opt_local.expandtab = true
vim.opt_local.tabstop = 2
vim.opt_local.shiftwidth = 2
vim.opt_local.softtabstop = 2
vim.opt_local.commentstring = "// %s"
vim.opt_local.colorcolumn = "80"
end,
})
-- Open Flutter DevTools in browser
vim.api.nvim_set_keymap("n", "<leader>fd", ":!flutter devtools<CR>", { noremap = true, silent = false, desc = "Flutter: DevTools" })
-- Flutter Run in a new tmux split (if tmux available)
vim.api.nvim_set_keymap("n", "<leader>fe", ":terminal flutter emulators<CR>", { noremap = true, silent = false, desc = "Flutter: List Emulators" })

View file

@ -31,3 +31,4 @@ require("user.lsp_servers.ts_ls")
require("user.lsp_servers.marksman") require("user.lsp_servers.marksman")
require("user.lsp_servers.kotlin_ls") require("user.lsp_servers.kotlin_ls")
require("user.lsp_servers.graphql") require("user.lsp_servers.graphql")
require("user.lsp_servers.dartls")

View file

@ -0,0 +1,79 @@
-- Dart/Flutter LSP configuration
-- dartls is bundled with the Dart SDK (which ships with Flutter)
local function get_dart_sdk_path()
-- Try Flutter's bundled Dart SDK first
local flutter_dart = vim.fn.expand("~/development/flutter/bin/cache/dart-sdk/bin/dart")
if vim.fn.executable(flutter_dart) == 1 then
return flutter_dart
end
-- Fall back to system dart
return "dart"
end
vim.lsp.config("dartls", {
cmd = { get_dart_sdk_path(), "language-server", "--protocol=lsp" },
filetypes = { "dart" },
root_markers = { "pubspec.yaml", "pubspec.yml" },
-- Dart-specific settings
settings = {
dart = {
-- Enable Flutter integration
completeFunctionCalls = true,
showTodos = true,
runPubGetOnPubspecChanges = true,
updateImportsOnRename = true,
closingLabels = true,
-- Flutter-specific
enableSdkFormatter = true,
lineLength = 80,
-- Analysis
enableSnippets = true,
},
},
})
-- Dart/Flutter-specific LSP attach handlers
vim.api.nvim_create_autocmd("LspAttach", {
callback = function(args)
local client = vim.lsp.get_client_by_id(args.data.client_id)
if client and client.name == "dartls" then
local bufnr = args.buf
-- Flutter hot reload
vim.keymap.set("n", "<leader>fr", function()
vim.lsp.buf_request(bufnr, "dart/reanalyze", nil)
vim.cmd("write")
vim.cmd("!flutter hot-reload 2>/dev/null || flutter run --hot")
end, { buffer = bufnr, desc = "Flutter: Hot Reload" })
-- Flutter hot restart
vim.keymap.set("n", "<leader>fR", function()
vim.cmd("write")
vim.cmd("!flutter run --restart 2>/dev/null")
end, { buffer = bufnr, desc = "Flutter: Hot Restart" })
-- Organize imports
vim.keymap.set("n", "<leader>fo", function()
vim.lsp.buf.execute_command({ command = "dart.organizeImports", arguments = { vim.fn.expand("%:p") } })
end, { buffer = bufnr, desc = "Dart: Organize Imports" })
-- Fix all auto-fixable issues
vim.keymap.set("n", "<leader>fa", function()
vim.lsp.buf.code_action({
context = { only = { "source.fixAll" } },
apply = true,
})
end, { buffer = bufnr, desc = "Dart: Fix All" })
-- Sort members
vim.keymap.set("n", "<leader>fs", function()
vim.lsp.buf.execute_command({ command = "dart.sortMembers", arguments = { vim.fn.expand("%:p") } })
end, { buffer = bufnr, desc = "Dart: Sort Members" })
end
end,
})
-- Enable the server
vim.lsp.enable("dartls")

View file

@ -1,19 +1,83 @@
-- Kotlin LSP = fwcd kotlin-language-server (installed via Mason).
--
-- NOTE: the JetBrains `kotlin-lsp` (intellij-server) was tried but it imports
-- Android Gradle modules with an EMPTY set of source sets, so it resolves
-- nothing for Android (hover/definition/references all return empty). fwcd
-- resolves the Gradle compile classpath itself and DOES navigate Android
-- projects, so we use it here. (kotlin-lsp remains fine for plain Kotlin/JVM.)
--
-- CPU/Memory notes for Android projects:
-- - Formatting disabled in LSP capabilities — ktfmt in-process spawns a SECOND
-- Kotlin compiler, doubling memory (OOMs even at 4GB). Formatting is handled
-- externally by null-ls running ktfmt as a separate process.
-- - Linting enabled — runs on the same compiler already loaded for completions,
-- so no extra memory cost. Provides error diagnostics (red squiggles).
-- - Semantic tokens disabled — extra compilation passes, not worth the CPU.
-- - 500ms debounce reduces compilation frequency during typing.
-- - Persistent tmpdir so the SQLite compilation cache survives across sessions.
local bin = vim.fn.expand("~/.local/share/nvim/mason/bin/kotlin-language-server")
if vim.fn.executable(bin) == 0 then
vim.notify("kotlin-language-server not found — run :MasonInstall kotlin-language-server",
vim.log.levels.WARN)
return
end
-- Build capabilities, then strip formatting to prevent ktfmt OOMs.
local capabilities = require("cmp_nvim_lsp").default_capabilities()
capabilities.textDocument.formatting = nil
capabilities.textDocument.rangeFormatting = nil
capabilities.textDocument.onTypeFormatting = nil
-- 1. Configuration -- 1. Configuration
vim.lsp.config('kotlin_language_server', { vim.lsp.config('kotlin_language_server', {
cmd = { "kotlin-language-server" }, cmd = { bin },
filetypes = { "kotlin" }, filetypes = { "kotlin" },
-- Kotlin projects are usually defined by build files or git
root_markers = { "settings.gradle", "settings.gradle.kts", "pom.xml", ".git" },
-- flags in lspconfig setup are now part of the general config table -- Kotlin/Android projects are rooted at a Gradle build file or git repo.
-- vim.lsp.config expects callback form: function(bufnr, on_dir).
root_dir = function(bufnr, on_dir)
local fname = vim.api.nvim_buf_get_name(bufnr)
local root = vim.fs.root(fname, {
"settings.gradle.kts",
"settings.gradle",
"build.gradle.kts",
"build.gradle",
"pom.xml",
".git",
})
on_dir(root or vim.fs.dirname(fname))
end,
capabilities = capabilities,
-- Strip formatting from the server's advertised capabilities.
-- client-side capabilities only prevent the server from expecting format
-- requests; server-side capabilities are what vim.lsp.buf.format() actually
-- checks. Without this, format-on-save routes through the LSP's ktfmt →
-- second Kotlin compiler → OOM.
on_attach = function(client)
client.server_capabilities.documentFormattingProvider = false
client.server_capabilities.documentRangeFormattingProvider = false
end,
-- 6GB heap — Android classpath is large (163 deps + 320 source files).
-- Persistent tmpdir so the compilation cache survives across sessions.
-- Use Android Studio's JBR 21 — optimised GC/JIT for Kotlin workloads.
cmd_env = {
KOTLIN_LANGUAGE_SERVER_OPTS = "-Xmx6G -Xms512m -Djava.io.tmpdir=/home/prabhat/.cache/kotlin-ls-fwcd",
JAVA_HOME = "/opt/android-studio/jbr",
},
init_options = { init_options = {
debounceTextChanges = 150, debounceTextChanges = 500,
}, },
settings = { settings = {
kotlin = { kotlin = {
linting = { enabled = true }, linting = { enabled = true },
completion = { enabled = true }, completion = { enabled = true },
semanticTokens = { enabled = false },
}, },
}, },
}) })

View file

@ -16,6 +16,18 @@ local sources = {
extra_args = { "--aosp" }, -- optional: use AOSP style formatting extra_args = { "--aosp" }, -- optional: use AOSP style formatting
--command = "google-java-format", -- specify command if not in PATH --command = "google-java-format", -- specify command if not in PATH
}), }),
-- Kotlin formatting via ktfmt — runs as separate JVM, no OOM risk.
-- Custom formatter (not in null-ls builtins for this version).
{
name = "ktfmt",
method = null_ls.methods.FORMATTING,
filetypes = { "kotlin" },
generator = null_ls.generator({
command = "java",
args = { "-jar", "/home/prabhat/.config/nvim/jars/ktfmt.jar", "-" },
to_stdin = true,
}),
},
-- Add other formatters based on your needs -- Add other formatters based on your needs
} }

View file

@ -10,5 +10,6 @@ require("nvim-treesitter").setup({
"javascript", "javascript",
"kotlin", "kotlin",
"go", "go",
"dart",
}, },
}) })