Skip to content

Commit d33d55c

Browse files
authored
Initial start of implementing a LSP for integrity checks (#13612)
* init lsp in multi module build * changes to build logic * fix * fix lsp server Co-authored-by: Oliver Kopp <[email protected]> * fix jbang and tests * refactor to jabls as a new module * fix jbang and modernizer * remove typo * requested changes * swap * add NULL_RANGE * fix README and add comments * remove --help * add npx to jabsrv * remove --help from jabkit
1 parent f63eb46 commit d33d55c

File tree

19 files changed

+703
-8
lines changed

19 files changed

+703
-8
lines changed

.jbang/JabLsLauncher.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
///usr/bin/env jbang "$0" "$@" ; exit $?
2+
3+
//DESCRIPTION jabls - start a bibtex languageserver
4+
5+
//JAVA 24
6+
//RUNTIME_OPTIONS --enable-native-access=ALL-UNNAMED
7+
8+
//SOURCES ../jabls-cli/src/main/java/org/jabref/languageserver/cli/ServerCli.java
9+
//FILES tinylog.properties=../jabls-cli/src/main/resources/tinylog.properties
10+
11+
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/BibtexTextDocumentService.java
12+
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/BibtexWorkspaceService.java
13+
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/LSPLauncher.java
14+
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/LSPServer.java
15+
16+
// REPOS mavencentral,snapshots=https://central.sonatype.com/repository/maven-snapshots/
17+
// REPOS mavencentral,mavencentralsnapshots=https://central.sonatype.com/repository/maven-snapshots/,s01oss=https://s01.oss.sonatype.org/content/repositories/snapshots/,oss=https://oss.sonatype.org/content/repositories,jitpack=https://jitpack.io,oss2=https://oss.sonatype.org/content/groups/public,ossrh=https://oss.sonatype.org/content/repositories/snapshots
18+
//REPOS mavencentral,mavencentralsnapshots=https://central.sonatype.com/repository/maven-snapshots/,s01oss=https://s01.oss.sonatype.org/content/repositories/snapshots/,oss=https://oss.sonatype.org/content/repositories,jitpack=https://jitpack.io,oss2=https://oss.sonatype.org/content/groups/public,ossrh=https://oss.sonatype.org/content/repositories/snapshots,raw=https://raw.githubusercontent.com/JabRef/jabref/refs/heads/main/jablib/lib/
19+
// REPOS mavencentral,jitpack=https://jitpack.io
20+
21+
// Choose one - both should work
22+
// https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/org/jabref/jablib/
23+
//DEPS org.jabref:jablib:6.0-SNAPSHOT
24+
// https://jitpack.io/#jabref/jabref/main-SNAPSHOT
25+
// DEPS com.github.jabref:jabref:main-SNAPSHOT
26+
27+
//DEPS info.picocli:picocli:4.7.7
28+
//DEPS org.jspecify:jspecify:1.0.0
29+
30+
// from jabls
31+
//DEPS org.slf4j:slf4j-api:2.0.17
32+
//DEPS org.tinylog:slf4j-tinylog:2.7.0
33+
//DEPS org.tinylog:tinylog-impl:2.7.0
34+
//DEPS org.slf4j:jul-to-slf4j:2.0.17
35+
//DEPS org.apache.logging.log4j:log4j-to-slf4j:2.25.1
36+
//DEPS info.picocli:picocli:4.7.7
37+
//DEPS org.jabref:afterburner.fx:2.0.0
38+
//DEPS com.github.eclipse:lsp4j:0.24.0
39+
40+
/// This class is required for [jbang](https://www.jbang.dev/)
41+
public class JabLsLauncher {
42+
public static void main(String[] args) throws Exception {
43+
org.jabref.languageserver.cli.ServerCli.main(args);
44+
}
45+
}

.jbang/README.md

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
This directory contains JBang scripts for JabRef.
44
[JBang](https://www.jbang.dev/) allows for running Java applications without having a JDK installed (before).
55

6-
Three use cases:
6+
Four use cases:
77

88
- Try out any pull request with minimal installation. See [our blog entry](https://blog.jabref.org/2025/05/31/run-pr/) for details.
99
- Run JabKit - JabRef's CLI tool.
10+
- Run JabLs - JabRef's Language Server.
1011
- Run JabSrv - JabRef's HTTP server.
1112

1213
## Running JabKit without installation
@@ -16,8 +17,8 @@ By using [gg.cmd](https://github.com/eirikb/gg#ggcmd) you can "just run" JabKit
1617
1. Download `gg.cmd` from: <https://github.com/eirikb/gg#ggcmd>. `gg.cmd` is a "binary" running on macOS, Linux, and Windows. No need for different binaries on different operating systems.
1718
2. Run `gg.cmd`. This will download and use JBang as wrapper around running JabKit:
1819

19-
- Linux/macOS: Run `sh ./gg.cmd jbang jabkit@jabref --help`.
20-
- Windows: Run `gg.cmd jbang jabkit@jabref --help`.
20+
- Linux/macOS: Run `sh ./gg.cmd jbang jabkit@jabref`.
21+
- Windows: Run `gg.cmd jbang jabkit@jabref`.
2122

2223
You can also put `gg.cmd` on your `PATH` and make it executable.
2324
Then you enable `alias jabkit='gg.cmd jbang jabkit@jabref`.
@@ -27,27 +28,57 @@ Then you enable `alias jabkit='gg.cmd jbang jabkit@jabref`.
2728
If you have JBang installed, just run
2829

2930
```terminal
30-
jbang jabkit@jabref --help
31+
jbang jabkit@jabref
3132
```
3233

3334
You can also install `jabkit` permanently in your `PATH`:
3435

3536
1. [Install JBang](https://www.jbang.dev/download/). E.g., by `brew install jbangdev/tap/jbang` or `choco install jbang`
3637
2. Make `jabkit` available on the command line: `jbang app install jabkit@jabref`
37-
3. Run `jabkit --help`
38+
3. Run `jabkit`
3839

3940
[JBang takes care about updating JabKit automatically](https://github.com/orgs/jbangdev/discussions/1636#discussioncomment-6150992).
4041

42+
## Running JabLs
43+
44+
If you have JBang installed, just run following command
45+
46+
```terminal
47+
jbang jabls@jabref
48+
```
49+
50+
With `gg.cmd`:
51+
52+
```terminal
53+
sh ./gg.cmd jbang jabls@jabref
54+
```
55+
56+
With `npx`:
57+
58+
```terminal
59+
npx @jbangdev/jbang jabls@jabref
60+
```
61+
62+
One can add `--help` to see available options.
63+
4164
## Running JabSrv
4265

4366
If you have JBang installed, just run following command
4467

4568
```terminal
46-
jbang jabsrv@jabref --help
69+
jbang jabsrv@jabref
4770
```
4871

4972
With `gg.cmd`:
5073

5174
```terminal
52-
sh ./gg.cmd jbang jabsrv@jabref --help
75+
sh ./gg.cmd jbang jabsrv@jabref
5376
```
77+
78+
With `npx`:
79+
80+
```terminal
81+
npx @jbangdev/jbang jabsrv@jabref
82+
```
83+
84+
One can add `--help` to see available options. E.g., how to set another port and how to specify served libraries.

build-logic/src/main/kotlin/org.jabref.gradle.base.dependency-rules.gradle.kts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,70 @@ extraJavaModuleInfo {
285285
module("com.github.javaparser:javaparser-symbol-solver-core", "com.github.javaparser.symbolsolver.core")
286286
module("net.sf.jopt-simple:jopt-simple", "jopt.simple")
287287

288+
// "com.github.eclipse:org.eclipse.lsp4j", "lsp4j"
289+
// - The name 'org.eclipse.lsp4j' is different than the name derived from the Jar file name 'lsp4j'; turn off 'failOnModifiedDerivedModuleNames' or explicitly allow override via 'overrideModuleName()'
290+
// - Not a module and no mapping defined: lsp4j-0.24.0.jar
291+
module("com.github.eclipse.lsp4j:org.eclipse.lsp4j", "lsp4j") {
292+
overrideModuleName()
293+
exportAllPackages()
294+
requireAllDefinedDependencies()
295+
// Note the missing "lsp4j" at the group
296+
mergeJar("com.github.eclipse:lsp4j")
297+
requires("com.google.gson")
298+
299+
}
300+
module("com.github.eclipse.lsp4j:org.eclipse.lsp4j.debug", "lsp4j.debug") {
301+
overrideModuleName()
302+
exportAllPackages()
303+
}
304+
module("com.github.eclipse.lsp4j:org.eclipse.lsp4j.generator", "lsp4j.generator") {
305+
overrideModuleName()
306+
exportAllPackages()
307+
}
308+
module("com.github.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc", "lsp4j.jsonrpc") {
309+
overrideModuleName()
310+
exportAllPackages()
311+
requires("com.google.gson")
312+
requires("java.logging")
313+
}
314+
module("com.github.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc.debug", "lsp4j.jsonrpc.debug") {
315+
overrideModuleName()
316+
exportAllPackages()
317+
}
318+
module("com.github.eclipse.lsp4j:org.eclipse.lsp4j.websocket", "lsp4j.websocket") {
319+
overrideModuleName()
320+
exportAllPackages()
321+
requireAllDefinedDependencies()
322+
}
323+
module("com.github.eclipse.lsp4j:org.eclipse.lsp4j.websocket.jakarta", "lsp4j.websocket.jakarta") {
324+
overrideModuleName()
325+
exportAllPackages()
326+
requireAllDefinedDependencies()
327+
}
328+
module("jakarta.websocket:jakarta.websocket-api", "jakarta.websocket") {
329+
overrideModuleName()
330+
exportAllPackages()
331+
}
332+
module("javax.websocket:javax.websocket-api", "javax.websocket") {
333+
overrideModuleName()
334+
exportAllPackages()
335+
}
336+
module("org.eclipse.xtend:org.eclipse.xtend", "xtend") {
337+
exportAllPackages()
338+
}
339+
module("org.eclipse.xtend:org.eclipse.xtend.lib", "xtend.lib") {
340+
overrideModuleName()
341+
exportAllPackages()
342+
}
343+
module("org.eclipse.xtend:org.eclipse.xtend.lib.macro", "xtend.lib.macro") {
344+
overrideModuleName()
345+
exportAllPackages()
346+
}
347+
module("org.eclipse.xtext:org.eclipse.xtext.xbase.lib", "xtext.xbase.lib") {
348+
overrideModuleName()
349+
exportAllPackages()
350+
}
351+
288352
module("com.tngtech.archunit:archunit-junit5-api", "com.tngtech.archunit.junit5.api") {
289353
exportAllPackages()
290354
requireAllDefinedDependencies()

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ rewrite {
3737
requirementTracing {
3838
inputDirectories.setFrom(files("docs",
3939
"jablib/src/main/java", "jablib/src/test/java",
40+
"jabls/src/main/java", "jabls/src/test/java",
4041
"jabkit/src/main/java", "jabkit/src/test/java",
4142
"jabgui/src/main/java", "jabgui/src/test/java",
4243
"jabsrv/src/main/java", "jabsrv/src/test/java"

jabls-cli/build.gradle.kts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
2+
3+
plugins {
4+
id("org.jabref.gradle.module")
5+
id("application")
6+
}
7+
8+
application{
9+
mainClass.set("org.jabref.languageserver.cli.ServerCli")
10+
mainModule.set("org.jabref.jabls.cli")
11+
12+
applicationDefaultJvmArgs = listOf(
13+
// Enable JEP 450: Compact Object Headers
14+
"-XX:+UnlockExperimentalVMOptions", "-XX:+UseCompactObjectHeaders",
15+
16+
"-XX:+UseZGC", "-XX:+ZUncommit",
17+
"-XX:+UseStringDeduplication",
18+
19+
"--enable-native-access=com.sun.jna,org.apache.lucene.core"
20+
)
21+
}
22+
23+
dependencies {
24+
implementation(project(":jablib"))
25+
implementation(project(":jabls"))
26+
27+
implementation("org.openjfx:javafx-controls")
28+
implementation("org.openjfx:javafx-fxml")
29+
implementation("org.jabref:afterburner.fx")
30+
31+
implementation("org.slf4j:slf4j-api")
32+
implementation("org.tinylog:slf4j-tinylog")
33+
implementation("org.tinylog:tinylog-impl")
34+
// route all requests to java.util.logging to SLF4J (which in turn routes to tinylog)
35+
implementation("org.slf4j:jul-to-slf4j")
36+
// route all requests to log4j to SLF4J
37+
implementation("org.apache.logging.log4j:log4j-to-slf4j")
38+
implementation("info.picocli:picocli")
39+
annotationProcessor("info.picocli:picocli-codegen")
40+
}
41+
42+
tasks.test {
43+
testLogging {
44+
// set options for log level LIFECYCLE
45+
events("FAILED")
46+
exceptionFormat = TestExceptionFormat.FULL
47+
}
48+
maxParallelForks = 1
49+
}
50+
51+
tasks.named<JavaExec>("run") {
52+
doFirst {
53+
application.applicationDefaultJvmArgs =
54+
listOf(
55+
"--enable-native-access=com.sun.jna"
56+
)
57+
}
58+
}
59+
60+
javaModulePackaging {
61+
applicationName = "jabös"
62+
vendor = "JabRef"
63+
64+
// All targets have to have "app-image" as sole target, since we do not distribute an installer
65+
targetsWithOs("windows") {
66+
appImageOptions.addAll("--win-console")
67+
packageTypes = listOf("app-image")
68+
}
69+
targetsWithOs("linux") {
70+
options.addAll(
71+
"--icon", "$projectDir/../jabgui/src/main/resources/icons/JabRef-linux-icon-64.png",
72+
)
73+
packageTypes = listOf("app-image")
74+
}
75+
targetsWithOs("macos") {
76+
packageTypes = listOf("app-image")
77+
}
78+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module org.jabref.jabls.cli {
2+
opens org.jabref.languageserver.cli to info.picocli;
3+
4+
requires transitive org.jabref.jablib;
5+
requires transitive org.jabref.jabls;
6+
7+
requires afterburner.fx;
8+
9+
requires org.slf4j;
10+
requires jul.to.slf4j;
11+
12+
requires info.picocli;
13+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.jabref.languageserver.cli;
2+
3+
import java.util.ResourceBundle;
4+
5+
import org.jabref.logic.l10n.Localization;
6+
7+
import com.airhacks.afterburner.views.ResourceLocator;
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
11+
// TODO: Code duplication of org.jabref.gui.util.JabRefResourceLocator - should be streamlined
12+
// Reason for duplication: The CLI does not depend on the GUI module, so it cannot use the GUI's JabRefResourceLocator directly.
13+
public class JabRefResourceLocator implements ResourceLocator {
14+
private static final Logger LOGGER = LoggerFactory.getLogger(JabRefResourceLocator.class);
15+
16+
@Override
17+
public ResourceBundle getResourceBundle(String s) {
18+
LOGGER.debug("Requested bundle for '{}'.", s);
19+
20+
return Localization.getMessages();
21+
}
22+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.jabref.languageserver.cli;
2+
3+
import java.util.concurrent.Callable;
4+
5+
import org.jabref.architecture.AllowedToUseStandardStreams;
6+
import org.jabref.languageserver.LSPLauncher;
7+
import org.jabref.logic.preferences.JabRefCliPreferences;
8+
9+
import org.slf4j.Logger;
10+
import org.slf4j.LoggerFactory;
11+
import org.slf4j.bridge.SLF4JBridgeHandler;
12+
import picocli.CommandLine;
13+
14+
@AllowedToUseStandardStreams("This is a CLI application. It resides in the package languageserver.server to be close to the other languageserver related classes.")
15+
@CommandLine.Command(name = "languageserver", mixinStandardHelpOptions = true, description = "JabLS - JabRef LanguageServer")
16+
public class ServerCli implements Callable<Void> {
17+
private static final Logger LOGGER = LoggerFactory.getLogger(ServerCli.class);
18+
19+
public static void main(final String[] args) throws InterruptedException {
20+
SLF4JBridgeHandler.removeHandlersForRootLogger();
21+
SLF4JBridgeHandler.install();
22+
23+
new CommandLine(new ServerCli()).execute(args);
24+
}
25+
26+
@Override
27+
public Void call() throws InterruptedException {
28+
LSPLauncher lspLauncher = new LSPLauncher();
29+
lspLauncher.run(JabRefCliPreferences.getInstance());
30+
31+
// Keep the server running until user kills the process (e.g., presses Ctrl+C)
32+
Thread.currentThread().join();
33+
34+
return null;
35+
}
36+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
level = info
2+
writerConsole = console
3+
4+
# More shrunk exception logs. See https://tinylog.org/v2/configuration/#strip-stack-trace-elements for details
5+
exception = strip: jdk.internal
6+
7+
8+
9+
10+
11+
12+
13+
14+
15+

0 commit comments

Comments
 (0)