Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
05b827d
feat: v4.0.0
s1n7ax Nov 30, 2025
c2044eb
status colors + handling for skipped/errored test results
Sep 13, 2025
1466e03
chore: code format
s1n7ax Nov 30, 2025
5909052
fix: workspace_execute calling client command handler
s1n7ax Nov 30, 2025
bd122eb
chore: add keymap in dev config
s1n7ax Nov 30, 2025
c7de470
chore: add debug messages
s1n7ax Nov 30, 2025
4a21dfe
refactor: move checks to separate directory
s1n7ax Nov 30, 2025
452e440
feat: add jproperties filetype support
s1n7ax Nov 30, 2025
eff3614
chore: remove nvim-jdtls from devcontainer
s1n7ax Nov 30, 2025
1c17cf2
docs: update README for v4.0.0 release
s1n7ax Nov 30, 2025
4fea572
chore: disable line length check in luacheck
s1n7ax Nov 30, 2025
ee27605
chore(ci): enable debug logging in CI tests
s1n7ax Nov 30, 2025
83f8b86
docs: update README with native lsp config info
s1n7ax Nov 30, 2025
3886f25
docs: clarify feat: usage in commit messages
s1n7ax Nov 30, 2025
97e4331
docs: update installation instructions for native LSP
s1n7ax Nov 30, 2025
5ad9a94
fix: update nvim version check to 0.11
s1n7ax Nov 30, 2025
51690a8
chore(ci): add lsp log output for mac tests
s1n7ax Nov 30, 2025
c0cf386
fix: nvim-java does not work on mac with the embeded jdk
s1n7ax Nov 30, 2025
dc090d5
fix: java validation is invalid
s1n7ax Nov 30, 2025
dbb1755
chore: remove unwanted code
s1n7ax Nov 30, 2025
e2ce4b9
fix: java version check does not consider env passed for LSP
s1n7ax Nov 30, 2025
14e3722
fix: PATH env separator is not windows compatible
s1n7ax Nov 30, 2025
68ade55
fix: windows compatibility issue (#439)
s1n7ax Dec 3, 2025
ca68ebd
chore(doc): update docs (#440)
s1n7ax Dec 3, 2025
105f234
docs: remove starter configs, simplify install structure
s1n7ax Dec 3, 2025
71d3a83
chore: release 4.0.0
s1n7ax Dec 3, 2025
cdbcecc
docs: remove why section
s1n7ax Dec 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions .devcontainer/config/nvim/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
local colemak = true

local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim'

if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
'git',
'clone',
'--filter=blob:none',
'https://github.com/folke/lazy.nvim.git',
'--branch=stable',
lazypath,
})
end

vim.opt.rtp:prepend(lazypath)

-- Setup lazy.nvim
require('lazy').setup({
{
'nvim-java/nvim-java',
dir = '/workspaces/nvim-java',
config = function()
require('java').setup()
vim.lsp.config('jdtls', {
handlers = {
['language/status'] = function(_, data)
vim.notify(data.message, vim.log.levels.INFO)
end,

['$/progress'] = function(_, data)
vim.notify(data.value.message, vim.log.levels.INFO)
end,
},
})
vim.lsp.enable('jdtls')
end,
},
})

-- Basic settings
vim.g.mapleader = ' '
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.tabstop = 2
vim.opt.shiftwidth = 2
vim.opt.expandtab = false
vim.opt.completeopt = { 'menu', 'menuone', 'noselect' }

vim.keymap.set('n', '<c-q>', '<cmd>q<CR>')

if colemak then
vim.keymap.set('n', '<c-l>', '<c-i>')
vim.keymap.set('n', 'E', 'K')
vim.keymap.set('n', 'H', 'I')
vim.keymap.set('n', 'K', 'N')
vim.keymap.set('n', 'L', 'E')
vim.keymap.set('n', 'N', 'J')
vim.keymap.set('n', 'e', '<up>')
vim.keymap.set('n', 'h', 'i')
vim.keymap.set('n', 'i', '<right>')
vim.keymap.set('n', 'j', 'm')
vim.keymap.set('n', 'k', 'n')
vim.keymap.set('n', 'l', 'e')
vim.keymap.set('n', 'm', '<left>')
vim.keymap.set('n', 'n', '<down>')
end

vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args)
vim.lsp.completion.enable(true, args.data.client_id, args.buf, { autotrigger = true })
vim.keymap.set('i', '<C-Space>', function()
vim.lsp.completion.get()
end, { buffer = args.buf })

if colemak then
vim.keymap.set('i', '<C-n>', '<C-n>', { buffer = args.buf })
vim.keymap.set('i', '<C-e>', '<C-p>', { buffer = args.buf })
end
end,
})

vim.keymap.set('n', ']d', function()
vim.diagnostic.jump({ count = 1, float = true })
end, { desc = 'Jump to next diagnostic' })

vim.keymap.set('n', '[d', function()
vim.diagnostic.jump({ count = -1, float = true })
end, { desc = 'Jump to previous diagnostic' })

vim.keymap.set('n', '<leader>ta', vim.lsp.buf.code_action, {})

-- DAP keymaps
vim.keymap.set('n', '<leader>dd', function()
require('dap').toggle_breakpoint()
end, { desc = 'Toggle breakpoint' })

vim.keymap.set('n', '<leader>dc', function()
require('dap').continue()
end, { desc = 'Continue' })

vim.keymap.set('n', '<leader>dn', function()
require('dap').step_over()
end, { desc = 'Step over' })

vim.keymap.set('n', '<leader>di', function()
require('dap').step_into()
end, { desc = 'Step into' })

vim.keymap.set('n', '<leader>do', function()
require('dap').step_out()
end, { desc = 'Step out' })

vim.keymap.set('n', '<leader>dr', function()
require('dap').repl.open()
end, { desc = 'Open REPL' })

vim.keymap.set('n', '<leader>dl', function()
require('dap').run_last()
end, { desc = 'Run last' })

vim.keymap.set('n', '<leader>dt', function()
require('dap').terminate()
end, { desc = 'Terminate' })

vim.keymap.set('n', 'gd', function()
vim.lsp.buf.definition()
end, { desc = 'Terminate' })

vim.keymap.set('n', '<leader>m', "<cmd>vnew<Cr><cmd>put = execute('messages')<Cr>")
6 changes: 6 additions & 0 deletions .devcontainer/config/nvim/lazy-lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
"nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" },
"nvim-dap": { "branch": "master", "commit": "5860c7c501eb428d3137ee22c522828d20cca0b3" },
"spring-boot.nvim": { "branch": "main", "commit": "218c0c26c14d99feca778e4d13f5ec3e8b1b60f0" }
}
21 changes: 21 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://raw.githubusercontent.com/devcontainers/spec/refs/heads/main/schemas/devContainer.base.schema.json",
"name": "nvim-java",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/java:1": {},
"ghcr.io/devcontainers/features/python:1": {},
"ghcr.io/devcontainers-extra/features/wget-apt-get:1": {},
"ghcr.io/duduribeiro/devcontainer-features/neovim:1": {
"version": "nightly"
},
"ghcr.io/devcontainers-extra/features/springboot-sdkman:2": {}
},
"postCreateCommand": "bash .devcontainer/setup.sh",
"customizations": {
"vscode": {
"extensions": ["sumneko.lua"]
}
}
}
5 changes: 5 additions & 0 deletions .devcontainer/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash
set -euxo pipefail

mkdir -p ~/.config
ln -sf /workspaces/nvim-java/.devcontainer/config/nvim ~/.config/nvim
7 changes: 4 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ on:

jobs:
test:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
nvim-versions: ["stable", "nightly"]

name: test
name: test (${{ matrix.os }}, nvim-${{ matrix.nvim-versions }})
steps:
- name: checkout
uses: actions/checkout@v3
Expand All @@ -24,4 +25,4 @@ jobs:
version: ${{ matrix.nvim-versions }}

- name: run tests
run: make test
run: make tests
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
vendor/plenary.nvim
.test_plugins
.luarc.json
proj
4 changes: 4 additions & 0 deletions .luacheckrc
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ read_globals = {
'it',
'assert',
}
ignore = {
'212/self',
}
max_line_length = false
4 changes: 4 additions & 0 deletions .luarc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"diagnostics.globals": ["describe"]
}

2 changes: 1 addition & 1 deletion .stylua.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
column_width = 80
column_width = 120
line_endings = "Unix"
indent_type = "Tabs"
indent_width = 2
Expand Down
15 changes: 8 additions & 7 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
{
"tasks": [
{
"label": "Run Tests",
"type": "shell",
"command": "make test"
}
]
"tasks": [
{
"label": "run current test file",
"type": "shell",
"command": "make test FILE=${file}",
"problemMatcher": []
}
]
}
156 changes: 156 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## General Guidelines

- Be extremely concise in all interactions and commit messages
- Sacrifice grammar for sake of concision
- Keep CLAUDE.md updated when changes make it outdated; add noteworthy patterns/conventions after implementing changes

## Documentation

Additional documentation available in `/doc`:
- `development.md` - Development environment setup (devcontainer, Spring Boot test projects)
- `nvim-java.txt` - Plugin documentation
- `server-capabilities.md` - Server capabilities reference
- `ts-to-lua-guide.md` - TypeScript to Lua translation guide

## Build Commands

```bash
make tests # Run Plenary/Busted tests (headless Neovim)
make test FILE=path # Run specific test file
make lint # Run luacheck linter
make format # Format with stylua
make all # lint -> format -> tests
```

## Architecture

nvim-java is a Neovim plugin providing Java IDE features via JDTLS wrapper. Monorepo structure with bundled submodules:

```
lua/
├── java.lua # Main API entry point
├── java/ # Core plugin (startup, config, api/, runner/, ui/, utils/)
├── java-core/ # LSP integration (ls/servers/jdtls/, clients/, adapters/, utils/)
├── java-test/ # Test runner (ui/, results/, reports/)
├── java-dap/ # Debug adapter (api/, data/)
├── java-refactor/ # Refactoring tools (api/, client-commands/)
├── pkgm/ # Package management (downloaders/, extractors/, version control)
└── async/ # Custom async/await wrapper
plugin/java.lua # User command registration
```

**java-core Details:**
- Core JDTLS features implementation
- `ls/servers/jdtls/` - JDTLS config generator, loads extensions (java-test, java-debug, spring-boot, lombok). Uses mason.nvim APIs for extension paths
- `ls/clients/` - LSP request wrappers:
- `jdtls-client.lua` - Core JDTLS LSP calls
- `java-test-client.lua`, `java-debug-client.lua` - Extension-specific calls
- Purpose: wrap async APIs with coroutines for sync-style calls, add typing
- 1:1 mappings of VSCode projects for Neovim:
- vscode-java, vscode-java-test, vscode-java-debug

**pkgm Details:**
- Package management utilities
- `downloaders/` - Download implementations (wget, etc.)
- `extractors/` - Archive extraction utilities
- Version control and package lifecycle management
- `specs/jdtls-spec/version-map.lua` - JDTLS version to timestamp mapping

**Updating JDTLS Versions:**
1. Visit https://download.eclipse.org/jdtls/milestones/
2. Click version link in 'Directory Contents' section
3. Find file: `jdt-language-server-X.Y.Z-YYYYMMDDHHSS.tar.gz`
4. Extract version (X.Y.Z) and timestamp (YYYYMMDDHHSS)
5. Add to `version-map.lua`: `['X.Y.Z'] = 'YYYYMMDDHHSS'`

**Key Files:**
- `lua/java/config.lua` - Default configuration (JDTLS version, plugins, JDK)
- `lua/java-core/ls/servers/jdtls/config.lua` - JDTLS server configuration
- `lazy.lua` - lazy.nvim plugin spec with dependencies

## Test Structure

```
tests/
├── assets/ # Test fixtures and assets (e.g., HelloWorld.java)
├── constants/ # Test constants (e.g., capabilities.lua)
├── utils/ # Test utilities and config files
│ ├── lsp-utils.lua # LSP test helpers
│ ├── prepare-config.lua # Lazy.nvim test setup
│ └── test-config.lua # Manual test setup
└── specs/ # Test specifications
├── lsp_spec.lua # All LSP-related tests
└── pkgm_spec.lua # All pkgm-related tests
```

**Test Guidelines:**
- Group related tests in single spec file (e.g., all pkgm tests in `pkgm_spec.lua`)
- Extract reusable logic to `utils/` to keep test steps clean
- Store test data/fixtures in `assets/`
- Store constants (capabilities, expected values) in `constants/`

## Code Patterns

**Event-driven registration:** Modules register on JDTLS attach via `event.on_jdtls_attach()`

**Config merging:** `vim.tbl_deep_extend('force', global_config, user_config or {})`

**Config type sync:** When modifying `lua/java/config.lua` (add/update/delete properties), update both `java.Config` type and `java.PartialConfig` in `lua/java.lua` to keep types in sync

**Complex types:** If type contains complex object, create class type instead of inlining type everywhere

**Async operations:** Uses custom async/await in `lua/async/` instead of raw coroutines

**User commands:** Registered in `plugin/java.lua`, map to nested API in `lua/java/api/`

**Class creation:** Use `java-core/utils/class` (Penlight class system):
```lua
local class = require('java-core.utils.class')

local Base = class()
function Base:_init(name)
self.name = name
end

local Child = class(Base)
function Child:_init(name, age)
self:super(name)
self.age = age
end
```

**Logging:** Use `java-core/utils/log2` for all logging:
```lua
local log = require('java-core.utils.log2')
log.trace('trace message')
log.debug('debug message')
log.info('info message')
log.warn('warning message')
log.error('error message')
log.fatal('fatal message')
```

## Code Style

- Tabs for indentation (tab width: 2)
- Line width: 80 chars
- Single quotes preferred
- LuaDoc annotations (`---@`) for types
- Type naming: Prefix types with package name (e.g., `java.Config`, `java-debug.DebugConfig`)
- Neovim globals allowed: vim.o, vim.g, vim.wo, vim.bo, vim.opt, vim.lsp
- Private methods: Use `@private` annotation, NOT `_` prefix (except `_init` constructor)
- Method syntax: Always use `:` for class methods regardless of `self` usage
- All class methods: `function ClassName:method_name()` (with or without `self`)

## Git Guidelines

- Use conventional commit messages per [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
- `feat:` is ONLY for end-user features (e.g., `feat: add code completion`)
- CI/internal features use `chore(ci):` (e.g., `chore(ci): enable debug logs`)
- Build/tooling features use `chore(build):`, `chore(test):`, etc.
- Never append "generated by AI" message
- Split unrelated changes into separate commits
Loading