update
This commit is contained in:
parent
b659d61c26
commit
f4119e7818
10 changed files with 320 additions and 6 deletions
3
init.lua
3
init.lua
|
|
@ -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
BIN
jars/ktfmt.jar
Normal file
Binary file not shown.
86
lua/user/android.lua
Normal file
86
lua/user/android.lua
Normal 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" })
|
||||||
|
|
@ -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
63
lua/user/flutter.lua
Normal 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" })
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
79
lua/user/lsp_servers/dartls.lua
Normal file
79
lua/user/lsp_servers/dartls.lua
Normal 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")
|
||||||
|
|
@ -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 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,5 +10,6 @@ require("nvim-treesitter").setup({
|
||||||
"javascript",
|
"javascript",
|
||||||
"kotlin",
|
"kotlin",
|
||||||
"go",
|
"go",
|
||||||
|
"dart",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue