nvim/lua/user/android.lua
2026-07-02 13:49:44 +05:30

86 lines
4.2 KiB
Lua

-- 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" })