Skip to content

Commit a985422

Browse files
palukkuSiedlerchr
andauthored
LSP add definition links for Markdown (#14032)
* Add definition links for Markdown * add ability to jumpToEntry from LSP and when running in standalone open the article in the bibfile * fix jbang * fix jbang again * fix jbang again... * reformat * reformat again * rename Logging Class * fix modernizer * switch to jspecify annotation * remove workspace capabilities * remove unused messagehandler * remove unused import * refactor * refactor: enhance citation key pattern matching and validation * sort alphabetically * docs: add clarification comment regarding LSP position handling --------- Co-authored-by: Christoph <[email protected]>
1 parent bb36ce1 commit a985422

File tree

17 files changed

+483
-53
lines changed

17 files changed

+483
-53
lines changed

.jbang/JabLsLauncher.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/LspDiagnosticBuilder.java
1919
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/LspDiagnosticHandler.java
2020
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/LspIntegrityCheck.java
21+
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/LspLinkHandler.java
22+
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/LspParserHandler.java
23+
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/LspRangeUtil.java
24+
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProvider.java
25+
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProviderFactory.java
26+
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/definition/MarkdownDefinitionProvider.java
2127

2228
// REPOS mavencentral,snapshots=https://central.sonatype.com/repository/maven-snapshots/
2329
// 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

jabgui/src/main/java/org/jabref/gui/JabRefGUI.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -424,20 +424,18 @@ private boolean upperRightIsInBounds(CoreGuiPreferences coreGuiPreferences) {
424424
// Background tasks
425425
public void startBackgroundTasks() {
426426
RemotePreferences remotePreferences = preferences.getRemotePreferences();
427-
427+
CLIMessageHandler cliMessageHandler = new CLIMessageHandler(mainFrame, preferences);
428428
if (remotePreferences.useRemoteServer()) {
429429
remoteListenerServerManager.openAndStart(
430-
new CLIMessageHandler(
431-
mainFrame,
432-
preferences),
430+
cliMessageHandler,
433431
remotePreferences.getPort());
434432
}
435433

436434
if (remotePreferences.enableHttpServer()) {
437435
httpServerManager.start(stateManager, remotePreferences.getHttpServerUri());
438436
}
439437
if (remotePreferences.enableLanguageServer()) {
440-
languageServerController.start(remotePreferences.getLanguageServerPort());
438+
languageServerController.start(cliMessageHandler, remotePreferences.getLanguageServerPort());
441439
}
442440
}
443441

jabgui/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,13 +285,13 @@ public void storeSettings() {
285285
});
286286

287287
UiMessageHandler uiMessageHandler = Injector.instantiateModelOrService(UiMessageHandler.class);
288+
CLIMessageHandler messageHandler = new CLIMessageHandler(uiMessageHandler, preferences);
288289
RemoteListenerServerManager remoteListenerServerManager = Injector.instantiateModelOrService(RemoteListenerServerManager.class);
289290
// stop in all cases, because the port might have changed
290291
remoteListenerServerManager.stop();
291292
if (remoteServerProperty.getValue()) {
292293
remotePreferences.setUseRemoteServer(true);
293-
remoteListenerServerManager.openAndStart(
294-
new CLIMessageHandler(uiMessageHandler, preferences),
294+
remoteListenerServerManager.openAndStart(messageHandler,
295295
remotePreferences.getPort());
296296
} else {
297297
remotePreferences.setUseRemoteServer(false);
@@ -327,7 +327,7 @@ public void storeSettings() {
327327
languageServerController.stop();
328328
if (enableLanguageServerProperty.getValue()) {
329329
remotePreferences.setEnableLanguageServer(true);
330-
languageServerController.start(remotePreferences.getLanguageServerPort());
330+
languageServerController.start(messageHandler, remotePreferences.getLanguageServerPort());
331331
} else {
332332
remotePreferences.setEnableLanguageServer(false);
333333
languageServerController.stop();

jablib/src/main/java/org/jabref/logic/importer/ParserResult.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,11 @@ public void setPath(Path path) {
105105
*
106106
* @param s String Warning text. Must be pre-translated. Only added if there isn't already a dupe.
107107
*/
108-
public void addWarning(String s) {
108+
public void addWarning(@NonNull String s) {
109109
addWarning(Range.NULL_RANGE, s);
110110
}
111111

112-
public void addWarning(Range range, String s) {
112+
public void addWarning(Range range, @NonNull String s) {
113113
warnings.put(range, s);
114114
}
115115

jabls/src/main/java/org/jabref/languageserver/BibtexTextDocumentService.java

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,56 @@
11
package org.jabref.languageserver;
22

33
import java.util.List;
4+
import java.util.Map;
45
import java.util.concurrent.CompletableFuture;
6+
import java.util.concurrent.ConcurrentHashMap;
57

68
import org.jabref.languageserver.util.LspDiagnosticHandler;
9+
import org.jabref.languageserver.util.LspLinkHandler;
10+
import org.jabref.logic.remote.server.RemoteMessageHandler;
711

12+
import com.google.gson.JsonArray;
813
import org.eclipse.lsp4j.CompletionItem;
914
import org.eclipse.lsp4j.CompletionList;
1015
import org.eclipse.lsp4j.CompletionParams;
16+
import org.eclipse.lsp4j.DefinitionParams;
1117
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
1218
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
1319
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
1420
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
21+
import org.eclipse.lsp4j.DocumentLink;
22+
import org.eclipse.lsp4j.DocumentLinkParams;
23+
import org.eclipse.lsp4j.Location;
24+
import org.eclipse.lsp4j.LocationLink;
25+
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
26+
import org.eclipse.lsp4j.TextDocumentItem;
27+
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
1528
import org.eclipse.lsp4j.jsonrpc.messages.Either;
1629
import org.eclipse.lsp4j.services.LanguageClient;
1730
import org.eclipse.lsp4j.services.TextDocumentService;
1831
import org.jspecify.annotations.NonNull;
32+
import org.slf4j.Logger;
33+
import org.slf4j.LoggerFactory;
1934

2035
public class BibtexTextDocumentService implements TextDocumentService {
2136

22-
private final LspDiagnosticHandler diagnosticHandler;
37+
private static final Logger LOGGER = LoggerFactory.getLogger(BibtexTextDocumentService.class);
2338

39+
private final LspClientHandler clientHandler;
40+
private final LspDiagnosticHandler diagnosticHandler;
41+
private final LspLinkHandler linkHandler;
42+
private final RemoteMessageHandler messageHandler;
43+
private final Map<String, String> fileUriToLanguageId;
44+
private final Map<String, String> contentCache;
2445
private LanguageClient client;
2546

26-
public BibtexTextDocumentService(@NonNull LspDiagnosticHandler diagnosticHandler) {
47+
public BibtexTextDocumentService(@NonNull RemoteMessageHandler messageHandler, @NonNull LspClientHandler clientHandler, @NonNull LspDiagnosticHandler diagnosticHandler, @NonNull LspLinkHandler linkHandler) {
48+
this.clientHandler = clientHandler;
2749
this.diagnosticHandler = diagnosticHandler;
50+
this.linkHandler = linkHandler;
51+
this.fileUriToLanguageId = new ConcurrentHashMap<>();
52+
this.contentCache = new ConcurrentHashMap<>();
53+
this.messageHandler = messageHandler;
2854
}
2955

3056
public void setClient(LanguageClient client) {
@@ -33,22 +59,73 @@ public void setClient(LanguageClient client) {
3359

3460
@Override
3561
public void didOpen(DidOpenTextDocumentParams params) {
36-
diagnosticHandler.computeAndPublishDiagnostics(client, params.getTextDocument().getUri(), params.getTextDocument().getText(), params.getTextDocument().getVersion());
62+
TextDocumentItem textDocument = params.getTextDocument();
63+
LOGGER.debug("didOpen {}", textDocument.getUri());
64+
fileUriToLanguageId.putIfAbsent(textDocument.getUri(), textDocument.getLanguageId());
65+
66+
if ("bibtex".equals(textDocument.getLanguageId())) {
67+
diagnosticHandler.computeAndPublishDiagnostics(client, textDocument.getUri(), textDocument.getText(), textDocument.getVersion());
68+
} else {
69+
contentCache.put(textDocument.getUri(), textDocument.getText());
70+
}
3771
}
3872

3973
@Override
4074
public void didChange(DidChangeTextDocumentParams params) {
41-
diagnosticHandler.computeAndPublishDiagnostics(client, params.getTextDocument().getUri(), params.getContentChanges().getFirst().getText(), params.getTextDocument().getVersion());
75+
VersionedTextDocumentIdentifier textDocument = params.getTextDocument();
76+
TextDocumentContentChangeEvent contentChange = params.getContentChanges().getFirst();
77+
LOGGER.debug("didChange {}", textDocument.getUri());
78+
String languageId = fileUriToLanguageId.get(textDocument.getUri());
79+
80+
if ("bibtex".equalsIgnoreCase(languageId)) {
81+
diagnosticHandler.computeAndPublishDiagnostics(client, textDocument.getUri(), contentChange.getText(), textDocument.getVersion());
82+
} else {
83+
contentCache.put(textDocument.getUri(), contentChange.getText());
84+
}
4285
}
4386

4487
@Override
4588
public void didClose(DidCloseTextDocumentParams params) {
89+
fileUriToLanguageId.remove(params.getTextDocument().getUri());
90+
contentCache.remove(params.getTextDocument().getUri());
4691
}
4792

4893
@Override
4994
public void didSave(DidSaveTextDocumentParams params) {
5095
}
5196

97+
@Override
98+
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> definition(DefinitionParams params) {
99+
if (!clientHandler.isStandalone()) {
100+
return CompletableFuture.completedFuture(Either.forLeft(List.of()));
101+
}
102+
if (fileUriToLanguageId.containsKey(params.getTextDocument().getUri())) {
103+
String fileUri = params.getTextDocument().getUri();
104+
return linkHandler.provideDefinition(fileUriToLanguageId.get(fileUri), fileUri, contentCache.get(fileUri), params.getPosition());
105+
}
106+
return CompletableFuture.completedFuture(Either.forLeft(List.of()));
107+
}
108+
109+
@Override
110+
public CompletableFuture<List<DocumentLink>> documentLink(DocumentLinkParams params) {
111+
if (clientHandler.isStandalone()) {
112+
return CompletableFuture.completedFuture(List.of());
113+
}
114+
String fileUri = params.getTextDocument().getUri();
115+
return linkHandler.provideDocumentLinks(fileUriToLanguageId.get(fileUri), contentCache.get(fileUri));
116+
}
117+
118+
@Override
119+
public CompletableFuture<DocumentLink> documentLinkResolve(DocumentLink params) {
120+
if (clientHandler.isStandalone()) {
121+
return CompletableFuture.completedFuture(null);
122+
}
123+
if (params.getData() instanceof JsonArray data) {
124+
messageHandler.handleCommandLineArguments(new String[] {data.asList().getFirst().getAsString(), data.asList().getLast().getAsString()});
125+
}
126+
return CompletableFuture.completedFuture(null);
127+
}
128+
52129
@Override
53130
public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams position) {
54131
return TextDocumentService.super.completion(position);

jabls/src/main/java/org/jabref/languageserver/BibtexWorkspaceService.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ public BibtexWorkspaceService(LspClientHandler clientHandler, LspDiagnosticHandl
2424
this.diagnosticHandler = diagnosticHandler;
2525
}
2626

27-
// Todo: handle event
2827
@Override
2928
public void didChangeConfiguration(DidChangeConfigurationParams didChangeConfigurationParams) {
3029
if (didChangeConfigurationParams.getSettings() instanceof JsonObject settings) {

jabls/src/main/java/org/jabref/languageserver/LspClientHandler.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
import java.util.concurrent.CompletableFuture;
44

55
import org.jabref.languageserver.util.LspDiagnosticHandler;
6+
import org.jabref.languageserver.util.LspLinkHandler;
7+
import org.jabref.languageserver.util.LspParserHandler;
68
import org.jabref.logic.journals.JournalAbbreviationRepository;
79
import org.jabref.logic.preferences.CliPreferences;
10+
import org.jabref.logic.remote.server.RemoteMessageHandler;
811

12+
import org.eclipse.lsp4j.DocumentLinkOptions;
913
import org.eclipse.lsp4j.InitializeParams;
1014
import org.eclipse.lsp4j.InitializeResult;
1115
import org.eclipse.lsp4j.MessageParams;
@@ -27,17 +31,24 @@ public class LspClientHandler implements LanguageServer, LanguageClientAware {
2731
private static final Logger LOGGER = LoggerFactory.getLogger(LspClientHandler.class);
2832

2933
private final LspDiagnosticHandler diagnosticHandler;
34+
private final LspParserHandler parserHandler;
35+
private final LspLinkHandler linkHandler;
3036
private final BibtexWorkspaceService workspaceService;
3137
private final BibtexTextDocumentService textDocumentService;
3238
private final ExtensionSettings settings;
39+
private final RemoteMessageHandler messageHandler;
3340

3441
private LanguageClient client;
42+
private boolean standalone = false;
3543

36-
public LspClientHandler(CliPreferences cliPreferences, JournalAbbreviationRepository abbreviationRepository) {
44+
public LspClientHandler(RemoteMessageHandler messageHandler, CliPreferences cliPreferences, JournalAbbreviationRepository abbreviationRepository) {
3745
this.settings = ExtensionSettings.getDefaultSettings();
38-
this.diagnosticHandler = new LspDiagnosticHandler(this, cliPreferences, abbreviationRepository);
46+
this.parserHandler = new LspParserHandler();
47+
this.diagnosticHandler = new LspDiagnosticHandler(this, parserHandler, cliPreferences, abbreviationRepository);
48+
this.linkHandler = new LspLinkHandler(parserHandler);
3949
this.workspaceService = new BibtexWorkspaceService(this, diagnosticHandler);
40-
this.textDocumentService = new BibtexTextDocumentService(diagnosticHandler);
50+
this.textDocumentService = new BibtexTextDocumentService(messageHandler, this, diagnosticHandler, linkHandler);
51+
this.messageHandler = messageHandler;
4152
}
4253

4354
@Override
@@ -51,6 +62,11 @@ public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
5162

5263
capabilities.setTextDocumentSync(syncOptions);
5364
capabilities.setWorkspace(new WorkspaceServerCapabilities());
65+
capabilities.setDefinitionProvider(true);
66+
67+
DocumentLinkOptions linkOptions = new DocumentLinkOptions();
68+
linkOptions.setResolveProvider(true);
69+
capabilities.setDocumentLinkProvider(linkOptions);
5470

5571
return CompletableFuture.completedFuture(new InitializeResult(capabilities));
5672
}
@@ -88,4 +104,12 @@ public void connect(LanguageClient client) {
88104
textDocumentService.setClient(client);
89105
client.logMessage(new MessageParams(MessageType.Warning, "BibtexLSPServer connected."));
90106
}
107+
108+
public void setStandalone(boolean standalone) {
109+
this.standalone = standalone;
110+
}
111+
112+
public boolean isStandalone() {
113+
return standalone;
114+
}
91115
}

jabls/src/main/java/org/jabref/languageserver/LspLauncher.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
import org.jabref.logic.journals.JournalAbbreviationLoader;
1313
import org.jabref.logic.journals.JournalAbbreviationRepository;
1414
import org.jabref.logic.preferences.CliPreferences;
15+
import org.jabref.logic.preferences.JabRefCliPreferences;
16+
import org.jabref.logic.remote.server.RemoteMessageHandler;
1517

1618
import org.eclipse.lsp4j.jsonrpc.Launcher;
19+
import org.eclipse.lsp4j.launch.LSPLauncher;
1720
import org.eclipse.lsp4j.services.LanguageClient;
1821
import org.slf4j.Logger;
1922
import org.slf4j.LoggerFactory;
@@ -25,21 +28,29 @@ public class LspLauncher extends Thread {
2528
private final CliPreferences cliPreferences;
2629
private final JournalAbbreviationRepository abbreviationRepository;
2730
private final ExecutorService threadPool;
31+
private final RemoteMessageHandler messageHandler;
2832

2933
private final int port;
34+
private boolean standalone = false;
3035
private volatile boolean running;
3136
private ServerSocket serverSocket;
3237

33-
public LspLauncher(CliPreferences cliPreferences, JournalAbbreviationRepository abbreviationRepository, int port) {
38+
public LspLauncher(RemoteMessageHandler messageHandler, CliPreferences cliPreferences, JournalAbbreviationRepository abbreviationRepository, int port) {
3439
this.cliPreferences = cliPreferences;
3540
this.abbreviationRepository = abbreviationRepository;
3641
this.threadPool = Executors.newCachedThreadPool();
3742
this.port = port;
3843
this.setName("JabLs - JabRef Language Server on: " + port);
44+
this.messageHandler = messageHandler;
3945
}
4046

41-
public LspLauncher(CliPreferences cliPreferences, int port) {
42-
this(cliPreferences, JournalAbbreviationLoader.loadRepository(cliPreferences.getJournalAbbreviationPreferences()), port);
47+
public LspLauncher(RemoteMessageHandler messageHandler, CliPreferences cliPreferences, int port) {
48+
this(messageHandler, cliPreferences, JournalAbbreviationLoader.loadRepository(cliPreferences.getJournalAbbreviationPreferences()), port);
49+
}
50+
51+
public LspLauncher(JabRefCliPreferences instance, Integer port) {
52+
this(_ -> LOGGER.warn("LSP cannot handle UICommands in standalone mode."), instance, port);
53+
this.standalone = true;
4354
}
4455

4556
@Override
@@ -69,12 +80,13 @@ public void run() {
6980
}
7081

7182
private void handleClient(Socket socket) {
72-
LspClientHandler clientHandler = new LspClientHandler(cliPreferences, abbreviationRepository);
83+
LspClientHandler clientHandler = new LspClientHandler(messageHandler, cliPreferences, abbreviationRepository);
84+
clientHandler.setStandalone(standalone);
7385
LOGGER.debug("LSP clientHandler started.");
7486
try (socket; // socket should be closed on error
7587
InputStream in = socket.getInputStream();
7688
OutputStream out = socket.getOutputStream()) {
77-
Launcher<LanguageClient> launcher = org.eclipse.lsp4j.launch.LSPLauncher.createServerLauncher(clientHandler, in, out, Executors.newCachedThreadPool(), Function.identity());
89+
Launcher<LanguageClient> launcher = LSPLauncher.createServerLauncher(clientHandler, in, out, Executors.newCachedThreadPool(), Function.identity());
7890
LOGGER.debug("LSP clientHandler launched.");
7991
clientHandler.connect(launcher.getRemoteProxy());
8092
LOGGER.debug("LSP clientHandler connected.");
@@ -105,4 +117,8 @@ public void interrupt() {
105117
public boolean isRunning() {
106118
return running;
107119
}
120+
121+
public boolean isStandalone() {
122+
return standalone;
123+
}
108124
}

jabls/src/main/java/org/jabref/languageserver/controller/LanguageServerController.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.jabref.languageserver.LspLauncher;
44
import org.jabref.logic.journals.JournalAbbreviationRepository;
55
import org.jabref.logic.preferences.CliPreferences;
6+
import org.jabref.logic.remote.server.RemoteMessageHandler;
67

78
import org.jspecify.annotations.Nullable;
89
import org.slf4j.Logger;
@@ -24,13 +25,13 @@ public LanguageServerController(CliPreferences cliPreferences, JournalAbbreviati
2425
LOGGER.debug("LanguageServerController initialized.");
2526
}
2627

27-
public synchronized void start(int port) {
28+
public synchronized void start(RemoteMessageHandler messageHandler, int port) {
2829
if (lspLauncher != null) {
2930
LOGGER.warn("Language server controller already started, cannot start again.");
3031
return;
3132
}
3233

33-
lspLauncher = new LspLauncher(cliPreferences, abbreviationRepository, port);
34+
lspLauncher = new LspLauncher(messageHandler, cliPreferences, abbreviationRepository, port);
3435
// This enqueues the thread to run in the background
3536
// The JVM will take care of running it at some point in time in the future
3637
// Thus, we cannot check directly if it really runs

0 commit comments

Comments
 (0)