Skip to content

Commit 4810edd

Browse files
authored
Lombok support v1 (#2519)
- Add 'java.jdt.ls.lombokSupport.enabled' setting - It can automatically detect whether the project has imported the lombok by checking all classpath - It can show the current version of the lombok in the language status bar - It can dynamically detect the version change of the lombok Signed-off-by: Yuhao Xiao <[email protected]>
1 parent 5d1c872 commit 4810edd

10 files changed

+506
-48
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ The following settings are supported:
202202
* `java.import.generatesMetadataFilesAtProjectRoot` : Specify whether the project metadata files(.project, .classpath, .factorypath, .settings/) will be generated at the project root. Defaults to `false`.
203203
* `java.inlayHints.parameterNames.enabled`: Enable/disable inlay hints for parameter names. Supported values are: `none`(disable parameter name hints), `literals`(Enable parameter name hints only for literal arguments) and `all`(Enable parameter name hints for literal and non-literal arguments). Defaults to `literals`.
204204

205+
New in 1.8.0
206+
* `java.jdt.ls.lombokSupport.enabled`: Whether to load lombok processors from project classpath. Defaults to `true`.
207+
205208
Semantic Highlighting
206209
===============
207210
[Semantic Highlighting](https://github.com/redhat-developer/vscode-java/wiki/Semantic-Highlighting) fixes numerous syntax highlighting issues with the default Java Textmate grammar. However, you might experience a few minor issues, particularly a delay when it kicks in, as it needs to be computed by the Java Language server, when opening a new file or when typing. Semantic highlighting can be disabled for all languages using the `editor.semanticHighlighting.enabled` setting, or for Java only using [language-specific editor settings](https://code.visualstudio.com/docs/getstarted/settings#_languagespecific-editor-settings).

package-lock.json

Lines changed: 118 additions & 45 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,12 @@
898898
"default": "ignore",
899899
"markdownDescription": "Project encoding settings",
900900
"scope": "window"
901+
},
902+
"java.jdt.ls.lombokSupport.enabled": {
903+
"type": "boolean",
904+
"default": true,
905+
"description": "Whether to load lombok processors from project classpath",
906+
"scope": "window"
901907
}
902908
}
903909
},
@@ -1237,6 +1243,7 @@
12371243
"vscode-languageclient": "7.1.0-next.5",
12381244
"winreg-utf8": "^0.1.1",
12391245
"winston": "^3.2.1",
1240-
"winston-daily-rotate-file": "^3.10.0"
1246+
"winston-daily-rotate-file": "^3.10.0",
1247+
"htmlparser2": "6.0.1"
12411248
}
12421249
}

src/commands.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,4 +275,6 @@ export namespace Commands {
275275
export const GET_WORKSPACE_PATH = '_java.workspace.path';
276276

277277
export const UPGRADE_GRADLE_WRAPPER = '_java.gradle.upgradeWrapper';
278+
279+
export const LOMBOK_CONFIGURE = "java.lombokConfigure";
278280
}

src/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
382382

383383
context.subscriptions.push(commands.registerCommand(Commands.GET_WORKSPACE_PATH, () => workspacePath));
384384

385-
context.subscriptions.push(onConfigurationChange(workspacePath));
385+
context.subscriptions.push(onConfigurationChange(workspacePath, context));
386386

387387
/**
388388
* Command to switch the server mode. Currently it only supports switch from lightweight to standard.

src/javaServerStarter.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { getJavaEncoding, IS_WORKSPACE_VMARGS_ALLOWED, getKey, getJavaagentFlag,
1010
import { logger } from './log';
1111
import { getJavaConfiguration, deleteDirectory, ensureExists, getTimestamp } from './utils';
1212
import { workspace, ExtensionContext, window } from 'vscode';
13+
import { addLombokParam, isLombokSupportEnabled } from './lombokSupport';
1314

1415
declare var v8debug;
1516
const DEBUG = (typeof v8debug === 'object') || startedInDebugMode();
@@ -113,6 +114,10 @@ function prepareParams(requirements: RequirementsData, javaConfiguration, worksp
113114

114115
parseVMargs(params, vmargs);
115116

117+
if (isLombokSupportEnabled()) {
118+
addLombokParam(context, params);
119+
}
120+
116121
if (!isSyntaxServer) {
117122
if (vmargs.indexOf(HEAP_DUMP) < 0) {
118123
params.push(HEAP_DUMP);

src/lombokSupport.ts

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
'use strict';
2+
3+
import * as fse from "fs-extra";
4+
import * as path from "path";
5+
import * as vscode from "vscode";
6+
import { ExtensionContext, window, commands, Uri, Position, Location, Selection, env } from "vscode";
7+
import { Commands } from "./commands";
8+
import { apiManager } from "./apiManager";
9+
import { supportsLanguageStatus } from "./languageStatusItemFactory";
10+
import htmlparser2 = require("htmlparser2");
11+
12+
export const JAVA_LOMBOK_PATH = "java.lombokPath";
13+
14+
const languageServerDocumentSelector = [
15+
{ scheme: 'file', language: 'java' },
16+
{ scheme: 'jdt', language: 'java' },
17+
{ scheme: 'untitled', language: 'java' },
18+
{ pattern: '**/pom.xml' },
19+
{ pattern: '**/{build,settings}.gradle'},
20+
{ pattern: '**/{build,settings}.gradle.kts'}
21+
];
22+
23+
let activeLombokPath: string = undefined;
24+
let isLombokCommandInitialized: boolean = false;
25+
26+
export function isLombokSupportEnabled(): boolean {
27+
return vscode.workspace.getConfiguration().get("java.jdt.ls.lombokSupport.enabled");
28+
}
29+
30+
export function isLombokImported(context: ExtensionContext): boolean {
31+
return context.workspaceState.get(JAVA_LOMBOK_PATH)!==undefined;
32+
}
33+
34+
export function updateActiveLombokPath(path: string) {
35+
activeLombokPath = path;
36+
}
37+
38+
export function isLombokActive(context: ExtensionContext): boolean {
39+
return activeLombokPath!==undefined;
40+
}
41+
42+
export function cleanupLombokCache(context: ExtensionContext): boolean {
43+
const result = isLombokImported(context);
44+
context.workspaceState.update(JAVA_LOMBOK_PATH, undefined);
45+
return result;
46+
}
47+
48+
export function getLombokVersion(context: ExtensionContext): string {
49+
const reg = /lombok-.*\.jar/;
50+
const lombokVersion = reg.exec(activeLombokPath)[0].split('.jar')[0];
51+
return lombokVersion;
52+
}
53+
54+
export function addLombokParam(context: ExtensionContext, params: string[]) {
55+
if (isLombokImported(context)) {
56+
// Exclude user setting lombok agent parameter
57+
const reg = /-javaagent:.*[\\|/]lombok.*\.jar/;
58+
const deleteIndex = [];
59+
for (let i = 0; i<params.length; i++) {
60+
if (reg.test(params[i])) {
61+
deleteIndex.push(i);
62+
}
63+
}
64+
for (let i = deleteIndex.length - 1; i>=0; i--) {
65+
params.splice(deleteIndex[i], 1);
66+
}
67+
// add -javaagent arg to support lombok
68+
const lombokAgentParam = '-javaagent:' + context.workspaceState.get(JAVA_LOMBOK_PATH);
69+
params.push(lombokAgentParam);
70+
updateActiveLombokPath(context.workspaceState.get(JAVA_LOMBOK_PATH));
71+
}
72+
}
73+
74+
export async function checkLombokDependency(context: ExtensionContext) {
75+
if (!isLombokSupportEnabled()) {
76+
return;
77+
}
78+
const reg = /lombok-.*\.jar/;
79+
let needReload = false;
80+
let versionChange = false;
81+
let currentLombokVersion = "";
82+
let previousLombokVersion = "";
83+
let currentLombokClasspath = "";
84+
const projectUris: string[] = await commands.executeCommand<string[]>(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.GET_ALL_JAVA_PROJECTS);
85+
for (const projectUri of projectUris) {
86+
const classpathResult = await apiManager.getApiInstance().getClasspaths(projectUri, {scope: 'runtime'});
87+
for (const classpath of classpathResult.classpaths) {
88+
if (reg.test(classpath)) {
89+
currentLombokClasspath = classpath;
90+
if (isLombokImported(context)) {
91+
currentLombokVersion = reg.exec(classpath)[0];
92+
previousLombokVersion = reg.exec(context.workspaceState.get(JAVA_LOMBOK_PATH))[0];
93+
if (currentLombokVersion!==previousLombokVersion) {
94+
needReload = true;
95+
versionChange = true;
96+
context.workspaceState.update(JAVA_LOMBOK_PATH, classpath);
97+
}
98+
}
99+
else {
100+
needReload = true;
101+
context.workspaceState.update(JAVA_LOMBOK_PATH, currentLombokClasspath);
102+
}
103+
break;
104+
}
105+
}
106+
if (needReload) {
107+
break;
108+
}
109+
}
110+
if (needReload) {
111+
if (versionChange) {
112+
const msg = `Lombok version changed from ${previousLombokVersion.split('.jar')[0].split('-')[1]} to ${currentLombokVersion.split('.jar')[0].split('-')[1]} \
113+
. Do you want to reload the window to load the new lombok version?`;
114+
const action = 'Reload Now';
115+
const restartId = Commands.RELOAD_WINDOW;
116+
window.showInformationMessage(msg, action).then((selection) => {
117+
if (action === selection) {
118+
commands.executeCommand(restartId);
119+
}
120+
});
121+
}
122+
else {
123+
const msg = `Lombok is detected in your project, please reload the window to enable lombok support.`;
124+
const action = 'Reload Now';
125+
const restartId = Commands.RELOAD_WINDOW;
126+
window.showInformationMessage(msg, action).then((selection) => {
127+
if (action === selection) {
128+
commands.executeCommand(restartId);
129+
}
130+
});
131+
}
132+
}
133+
}
134+
135+
async function getParentBuildFilePath(buildFilePath: string, relativePath: string) {
136+
const parentBuildFilePath = path.join(path.dirname(buildFilePath), relativePath, path.basename(buildFilePath));
137+
if (await fse.pathExists(parentBuildFilePath)) {
138+
return parentBuildFilePath;
139+
}
140+
return undefined;
141+
}
142+
143+
export function registerLombokConfigureCommand(context: ExtensionContext) {
144+
if (isLombokCommandInitialized) {
145+
return;
146+
}
147+
context.subscriptions.push(commands.registerCommand(Commands.LOMBOK_CONFIGURE, async (buildFilePath: string) => {
148+
if (isMavenProject(buildFilePath)) {
149+
let pos = 0;
150+
while (true) {
151+
const document = await vscode.workspace.openTextDocument(Uri.file(buildFilePath));
152+
const fullText = document.getText();
153+
let tagList: [string, number][] = [];
154+
let hasParent: boolean = false;
155+
let getRelativePath: boolean = false;
156+
let relativePath: string = '..';
157+
const parser = new htmlparser2.Parser({
158+
onopentag(name, attributes) {
159+
if (name==="dependency"||tagList.length>0) {
160+
tagList.push([name, parser.startIndex]);
161+
}
162+
if (name==="parent") {
163+
hasParent = true;
164+
}
165+
if (hasParent&&name==="relativePath") {
166+
getRelativePath = true;
167+
}
168+
},
169+
ontext(text) {
170+
if (tagList.length) {
171+
tagList.push([text, parser.startIndex]);
172+
}
173+
if (getRelativePath) {
174+
relativePath = text;
175+
}
176+
},
177+
onclosetag(name) {
178+
if (name==="dependency") {
179+
tagList.push([name, parser.startIndex]);
180+
let lombokIndex = -1;
181+
let versionIndex = -1;
182+
for (let i = 0; i<tagList.length-1; i++) {
183+
if (tagList[i][0]==="artifactid"&&tagList[i+1][0]==="lombok") {
184+
lombokIndex = tagList[i+1][1];
185+
}
186+
if (tagList[i][0]==="version") {
187+
versionIndex = tagList[i+1][1];
188+
}
189+
}
190+
if (lombokIndex>=0) {
191+
pos = lombokIndex;
192+
if (versionIndex>=0) {
193+
pos = versionIndex;
194+
parser.end();
195+
}
196+
}
197+
tagList = [];
198+
}
199+
if (getRelativePath&&name==="relativePath") {
200+
getRelativePath = false;
201+
}
202+
},
203+
});
204+
parser.write(fullText);
205+
parser.end();
206+
if (pos>0) {
207+
break;
208+
}
209+
if (hasParent) {
210+
buildFilePath = await getParentBuildFilePath(buildFilePath, relativePath);
211+
if (buildFilePath===undefined) {
212+
break;
213+
}
214+
}
215+
else {
216+
break;
217+
}
218+
}
219+
if (buildFilePath) {
220+
await commands.executeCommand(Commands.OPEN_BROWSER, Uri.file(buildFilePath));
221+
if (pos>0) {
222+
gotoLombokLocation(pos, buildFilePath);
223+
}
224+
}
225+
}
226+
else if (isGradleProject(buildFilePath)) {
227+
while (true) {
228+
const document = await vscode.workspace.openTextDocument(Uri.file(buildFilePath));
229+
const fullText = document.getText();
230+
const deleteCommentReg = /\/\/.*|(\/\*[\s\S]*?\*\/)/g;
231+
const content = fullText.replace(deleteCommentReg, (match) => {
232+
let newString = '';
233+
for (const letter of match) {
234+
newString += '@';
235+
}
236+
return newString;
237+
});
238+
const lombokReg = /org.projectlombok/;
239+
const result = lombokReg.exec(content);
240+
if (result) {
241+
const pos = result.index;
242+
await commands.executeCommand(Commands.OPEN_BROWSER, Uri.file(buildFilePath));
243+
gotoLombokLocation(pos, buildFilePath);
244+
break;
245+
}
246+
else {
247+
const currentBuildFilePath = buildFilePath;
248+
buildFilePath = await getParentBuildFilePath(buildFilePath, "..");
249+
if (buildFilePath===undefined) {
250+
await commands.executeCommand(Commands.OPEN_BROWSER, Uri.file(currentBuildFilePath));
251+
break;
252+
}
253+
}
254+
}
255+
}
256+
}));
257+
isLombokCommandInitialized = true;
258+
}
259+
260+
export namespace LombokVersionItemFactory {
261+
export function create(text: string, buildFilePath: string): any {
262+
if (supportsLanguageStatus()) {
263+
const item = vscode.languages.createLanguageStatusItem("javaLombokVersionItem", languageServerDocumentSelector);
264+
item.severity = vscode.LanguageStatusSeverity?.Information;
265+
item.name = "Lombok Version";
266+
item.text = text;
267+
if (buildFilePath) {
268+
item.command = getLombokChangeCommand(buildFilePath);
269+
}
270+
return item;
271+
}
272+
return undefined;
273+
}
274+
275+
export function update(item: any, text: string, buildFilePath: string): void {
276+
item.text = text;
277+
if (buildFilePath) {
278+
item.command = getLombokChangeCommand(buildFilePath);
279+
}
280+
}
281+
282+
function getLombokChangeCommand(buildFilePath: string): vscode.Command {
283+
return {
284+
title: `Configure Version`,
285+
command: Commands.LOMBOK_CONFIGURE,
286+
arguments: [buildFilePath],
287+
tooltip: `Configure Lombok Version`
288+
};
289+
}
290+
}
291+
292+
function gotoLombokLocation(position: number, buildFilePath: string): void {
293+
const newPosition = window.activeTextEditor.document.positionAt(position);
294+
const newSelection = new Selection(newPosition, newPosition);
295+
window.activeTextEditor.selection = newSelection;
296+
const newLocation = new Location(Uri.file(buildFilePath), newPosition);
297+
commands.executeCommand(
298+
Commands.GOTO_LOCATION,
299+
window.activeTextEditor.document.uri,
300+
window.activeTextEditor.selection.active,
301+
[newLocation],
302+
'goto'
303+
);
304+
}
305+
306+
function isMavenProject(buildFilePath: string): boolean {
307+
const buildFileNames = ["pom.xml"];
308+
for (const buildFileName of buildFileNames) {
309+
if (buildFilePath.indexOf(buildFileName)>=0) {
310+
return true;
311+
}
312+
}
313+
return false;
314+
}
315+
316+
function isGradleProject(buildFilePath: string): boolean {
317+
const buildFileNames = ["build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts"];
318+
for (const buildFileName of buildFileNames) {
319+
if (buildFilePath.indexOf(buildFileName)>=0) {
320+
return true;
321+
}
322+
}
323+
return false;
324+
}

0 commit comments

Comments
 (0)